Search code examples
linuxelfdwarf

Find frame base and variable locations using DWARF version 4


I'm following Eli Bendersky's blog on parsing the DWARF debug information. He shows an example of parsing the binary with DWARF version 2 in his blog. The frame base of a function (further used for retrieving local variables) can be retrieved from the location list:

<1><71>: Abbrev Number: 5 (DW_TAG_subprogram)
    <72>   DW_AT_external    : 1
    <73>   DW_AT_name        : (...): do_stuff
    <77>   DW_AT_decl_file   : 1
    <78>   DW_AT_decl_line   : 4
    <79>   DW_AT_prototyped  : 1
    <7a>   DW_AT_low_pc      : 0x8048604
    <7e>   DW_AT_high_pc     : 0x804863e
    <82>   DW_AT_frame_base  : 0x0      (location list)
    <86>   DW_AT_sibling     : <0xb3>
...
$ objdump --dwarf=loc tracedprog2
Contents of the .debug_loc section:

    Offset   Begin    End      Expression
    00000000 08048604 08048605 (DW_OP_breg4: 4 )
    00000000 08048605 08048607 (DW_OP_breg4: 8 )
    00000000 08048607 0804863e (DW_OP_breg5: 8 )

However, I find in DWARF version 4 there is no such .debug_loc section. Here is the function info on my machine:

<1><300>: Abbrev Number: 17 (DW_TAG_subprogram)
    <301>   DW_AT_external    : 1
    <301>   DW_AT_name        : (indirect string, offset: 0x1e0): do_stuff
    <305>   DW_AT_decl_file   : 1
    <306>   DW_AT_decl_line   : 3
    <307>   DW_AT_decl_column : 6
    <308>   DW_AT_prototyped  : 1
    <308>   DW_AT_low_pc      : 0x1149
    <310>   DW_AT_high_pc     : 0x47
    <318>   DW_AT_frame_base  : 1 byte block: 9c        (DW_OP_call_frame_cfa)
    <31a>   DW_AT_GNU_all_tail_call_sites: 1

Line <318> indicates the frame base is 1 byte block: 9c (DW_OP_call_frame_cfa). Any idea how to find the frame base for the DWARF v4 binaries?


Update based on @Employed Russian's answer: The frame_base of a subprogram seems to point to the Canonical Frame Address (CFA), which is the RBP value before the call instruction.

 <2><329>: Abbrev Number: 19 (DW_TAG_variable)
    <32a>   DW_AT_name        : (indirect string, offset: 0x7d): my_local
    <32e>   DW_AT_decl_file   : 1
    <32f>   DW_AT_decl_line   : 5
    <330>   DW_AT_decl_column : 9
    <331>   DW_AT_type        : <0x65>
    <335>   DW_AT_location    : 2 byte block: 91 6c     (DW_OP_fbreg: -20)

So a local variable (my_local in the above example) can be located by the CFA using this calculation: &my_local = CFA - 20 = (current RBP + 16) - 20 = current RBP - 4. Verify it by checking the assembly:

void do_stuff(int my_arg)
{
    1149:   f3 0f 1e fa             endbr64 
    114d:   55                      push   %rbp
    114e:   48 89 e5                mov    %rsp,%rbp
    1151:   48 83 ec 20             sub    $0x20,%rsp
    1155:   89 7d ec                mov    %edi,-0x14(%rbp)
    int my_local = my_arg + 2;
    1158:   8b 45 ec                mov    -0x14(%rbp),%eax
    115b:   83 c0 02                add    $0x2,%eax
    115e:   89 45 fc                mov    %eax,-0x4(%rbp)

my_local is at -0x4(%rbp).


Solution

  • This isn't about DWARFv2 vs. DWARFv4 -- using either version the compiler may chose to use or not use location lists. Your compiler chose not to.

    Any idea how to find the frame base for the DWARF v4 binaries?

    It tells you right there: use the CFA pseudo-register, also known as "canonical frame address".

    That "imaginary" register has the same value that %rsp had just before the current function was called. That is, current function's return address is always stored at CFA+0, and %rsp == CFA+8 on entry into the function.

    If the function uses frame pointer, then previous value of %rbp is usually stored at CFA+8.

    More info here.