assemblygccx86-64low-leveldwarf

Issue with DWARF DIE's and tracking variables


My compiler is writing DWARF DIE's and I am currently coming across an issue with tracking variables. For example, the abbreviation would look something like:

DW_AT_name         DW_FORM_string
DW_AT_decl_file    DW_FORM_data1
DW_AT_decl_line    DW_FORM_data1
DW_AT_decl_column  DW_FORM_data1
DW_AT_type         DW_FORM_ref4
DW_AT_location     DW_FORM_exprloc

And the debug_info for an abbreviation would look like:

DW_AT_name        : x
DW_AT_decl_file   : 1
DW_AT_decl_line   : 2
DW_AT_decl_column : 24
DW_AT_type        : <0x65>
DW_AT_location    : 2 byte block: 91 68      (DW_OP_fbreg: -24)

C adds an offset of -16 to the location, which should be -8(%rbp) in the assembly. In my language, a variable is stored on -8(%rbp) also, and the offset is -24, as well. Other than the FORM_string being a FORM_strp, which I assume makes no difference in the actual debugging process, I can't see any other differences. Is there any reason for why my language would return an error like this when using gdb?

(gdb) start
........ Not important
(gdb) watch x
Hardware watchpoint 2: x
(gdb) step
Warning:
Cannot insert breakpoint -1.
Cannot access memory at address 0x1

Command aborted.

I've tried removing the -16 offset, but to no avail. Note: GDB version 15.2, compiling AT&T syntax assembly with gcc 14.2.1. DWARF version 5.

Here is a example of the generated asm from the compiler

.file "main.zu"
.text
.globl main
.file 0 "main.zu"
.Ltext0:
.weak .Ltext0
.loc 0 1 6
    
.Ldie1_debug_start:
    
.type main, @function
main:
    .loc 0 1 17
    .cfi_startproc
    pushq %rbp
    .cfi_def_cfa_offset 16
    movq %rsp, %rbp
    .cfi_def_cfa_register 6
    .loc 0 1 26
    # Variable declaration for 'stinky'
    movq $415, -8(%rbp)
    # End of variable declaration for 'stinky'
    movq $913, -8(%rbp)
    movq $69, %rdi
    movq $60, %rax
    syscall # SYS_EXIT
    ret
    .Ldie1_debug_end:
.cfi_endproc
.size main, .-main
    .Ldebug_text0:
.section    .debug_info,"",@progbits
    
.long .Ldebug_end - .Ldebug_info
.Ldebug_info:
.weak .Ldebug_info
.word 0x5
.byte 0x1
.byte 0x8
.long .Ldebug_abbrev

.uleb128 1
.long .Ldebug_producer_string
.byte 0x8042
.long .Ldebug_file_string
.long .Ldebug_file_dir
.quad .Ltext0
.quad .Ldebug_text0 - .Ltext0
.long .Ldebug_line0
.uleb128 2
.long .Ldie1_string
.byte 0
.byte 1
.byte 17
.long .Lint_debug_type
.quad .Ldie1_debug_start
.quad .Ldie1_debug_end - .Ldie1_debug_start
.uleb128 0x01
.byte 0x9c
.uleb128 7
.long .Ldie2_string
.byte 0
.byte 2
.byte 7
.long .Lint_debug_type
.uleb128 0x02
.byte 0x91
.sleb128 -24
.byte 0

.Lint_debug_type:
.uleb128 8
.byte 8
.byte 5
.string "int"
.Lfloat_debug_type:
.uleb128 8
.byte 4
.byte 4
.string "float"
.Lstr_debug_type:
.uleb128 9
.byte 8
.long .Lchar_debug_type
.Lchar_debug_type:
.uleb128 8
.byte 1
.byte 6
.string "char"

.byte 0
.Ldebug_end:
.section .debug_abbrev,"",@progbits
.Ldebug_abbrev:

.uleb128 1
.uleb128 0x11 # TAG_compile_unit
.byte   0x1 # No children
.uleb128 0x25 # AT_producer
.uleb128 0xe # FORM_strp
.uleb128 0x13 # AT_language
.uleb128 0xb # FORM_data1
.uleb128 0x3 # AT_name
.uleb128 0x1f # FORM_line_strp
.uleb128 0x1b # AT_comp_dir
.uleb128 0x1f # FORM_line_strp
.uleb128 0x11 # AT_low_pc
.uleb128 0x1 # FORM_addr
.uleb128 0x12 # AT_high_pc
.uleb128 0x7 # FORM_data8
.uleb128 0x10 # AT_stmt_list
.uleb128 0x17 # FORM_sec_offset
.byte 0
.byte 0

.uleb128 2
.uleb128 0x2e # TAG_subprogram - FunctionNoParams, non-void
.byte 0x1 # Has children
.uleb128 0x3f # AT_external
.uleb128 0x19 # FORM_flag_present
.uleb128 0x3 # AT_name
.uleb128 0xe # FORM_strp
.uleb128 0x3a # AT_decl_file
.uleb128 0xb # FORM_data1
.uleb128 0x3b # AT_decl_line
.uleb128 0xb # FORM_data1
.uleb128 0x39 # AT_decl_column
.uleb128 0xb # FORM_data1
.uleb128 0x49 # AT_type
.uleb128 0x13 # FORM_ref4
.uleb128 0x11 # AT_low_pc
.uleb128 0x1 # FORM_addr
.uleb128 0x12 # AT_high_pc
.uleb128 0x7 # FORM_data8
.uleb128 0x40 # AT_frame_base
.uleb128 0x18 # FORM_exprloc
.uleb128 0x7a # AT_call_all_calls
.uleb128 0x19 # FORM_flag_present

.byte 0
.byte 0

.uleb128 7
.uleb128 0x34 # TAG_variable
.byte 0 # No children
.uleb128 0x3 # AT_name
.uleb128 0xe # FORM_strp
.uleb128 0x3a # AT_decl_file
.uleb128 0xb # FORM_data1
.uleb128 0x3b # AT_decl_line
.uleb128 0xb # FORM_data1
.uleb128 0x39 # AT_decl_column
.uleb128 0xb # FORM_data1
.uleb128 0x49 # AT_type
.uleb128 0x13 # FORM_ref4
.uleb128 0x2 # AT_location
.uleb128 0x18 # FORM_exprloc

.byte 0
.byte 0

.uleb128 8
.uleb128 0x24 # TAG_base_type
.byte   0 # no children
.uleb128 0xb # AT_byte_size
.uleb128 0xb # FORM_data1
.uleb128 0x3e # AT_encoding
.uleb128 0xb # FORM_data1
.uleb128 0x3 # AT_name
.uleb128 0x8 # FORM_string

.byte 0
.byte 0

.uleb128 9
.uleb128 0xF # TAG_pointer_type
.byte 0 # No children
.uleb128 0xB # AT_byte_size
.uleb128 0xb # FORM_data1
.uleb128 0x49 #  AT_type
.uleb128 0x13 #  FORM_ref4
.byte 0
.byte 0
.byte 0
.byte 0
.section .debug_aranges,"",@progbits
.Ldebug_aranges:
.long .Ldebug_aranges_end - 4 - .Ldebug_aranges
.value 0x5
.long .Ldebug_text0
.byte 0x8
.byte 0x0
.value 0
.value 0
.quad .Ltext0
.quad .Ldebug_text0-.Ltext0
.quad 0
.quad 0
.Ldebug_aranges_end:
.section .debug_line,"",@progbits
.Ldebug_line0:
.section .debug_str,"MS",@progbits,1
.Ldebug_producer_string: .string "Zura compiler v0.1.25"
.Ldie1_string: .string "main"

.Ldie2_string:
    .string "stinky"

.section .debug_line_str,"MS",@progbits,1
.Ldebug_file_string: .string "main.zu"
.Ldebug_file_dir: .string "zura_files"

Solution

  • You forgot to provide build instructions. I can reproduce the problem only if the code is not linked to libc, rather main is the process entry point. In that case the topmost item on the stack is going to be argc. When adding a watchpoint for a local variable gdb also inserts an internal breakpoint (I believe to detect leaving the frame). The unwinder uses the topmost item from the stack as the address of the breakpoint, assuming that is the return address. However in this case that is argc which is 1 if no command line arguments are used. This is where the Cannot access memory at address 0x1 error is coming from. You can verify this by passing an argument, in which case you should see that change to 0x2 (or however many arguments you passed + 1). If you link with libc, that will call your main and hence there will be a proper return address on the stack and gdb will not be confused:

    Hardware watchpoint 2: stinky
    
    Old value = 0
    New value = 415
    

    There may be a way to specify that your main is the outermost frame using DWARF but I have not looked into that.