3

I have a process where one .o file is built without any .eh_frame or .debug_frame section (via an assembler) but with other types of debug info such as .debug_info. Apparently this triggers gdb to stop using frame-pointer (rbp) based unwinding for any functions from that object, and it produces invalid backtraces (it isn't clear how it is trying to unwind the stack at all).

Now the functions in this binary set up the stack frame properly (i.e., rbp points to correctly to the base of the frame) and if GDB were just to use that to unwind, everything would be great. Is there some way I can tell it to ignore the dwarf2 info and use frame-pointer based unwinding?

BeeOnRope
  • 60,350
  • 16
  • 207
  • 386
  • related: https://stackoverflow.com/questions/24160995/gdb-backtrace-by-walking-frame-pointers, apparently working but OP wants to add printing and being able to use `frame 0x0123456789ABCDEF` to walk back to a given frame. – Peter Cordes Dec 10 '17 at 16:20
  • 1
    @PeterCordes - what's actually frustrating is that an assembler like `nasm` doesn't actually generate `debug_frame` or `eh_frame` sections which are used for stack walking without frame pointers, but somehow just having any debug info for that `.o` causes it to not use frame-pointer based stack walking and just totally blow up (perhaps because it is linked with C code that does have `debug_info`, not sure...). – BeeOnRope Dec 10 '17 at 16:34

2 Answers2

4

if gcc were just to use that to unwind, everything would be great.

You mean GDB.

I use the following routine in my ~/.gdbinit to unwind $rbp frame chain:

define xbt
  set $xbp = (void **)$arg0
  while 1
    x/2a $xbp
    set $xbp = (void **)$xbp[0]
  end
end

Call it with the initial base pointer address you want to start from, e.g., xbt $rbp to use the current base pointer.

This isn't as good as allowing GDB to do it (no access to parameters or locals), but it does get at least the call trace.

For making GDB to ignore existing DWARF unwind info, you'll have to patch it out and build your own GDB.

P.S. Using --strip-dwo will not help.

Update:

why stripping isn't feasible?

Well, --strip-dwo only strips .dwo sections, and that's not where unwind info is (it's in .eh_frame and .debug_frame sections).

That said, you should try to strip .debug_frame with strip -g bad.o -- if your file only has bad .debug_frame but correct (or missing) .eh_frame, then removing .debug_frame should work.

strip doesn't remove .eh_frame because that info is usually required for unwinding.

If .eh_frame is also bad, you may be able to remove it with objcopy.

Some more info on unwinding here.

BeeOnRope
  • 60,350
  • 16
  • 207
  • 386
Employed Russian
  • 199,314
  • 34
  • 295
  • 362
  • Thanks. Do you know why stripping isn't feasible? If the offending info is not present (e.g., I don't build with dwarf info), `gdb` does a fine job unwinding. – BeeOnRope Mar 11 '17 at 22:26
  • Thanks looks good. The `.o` is pure ASM without callouts back to C, so I assume I could perhaps also do away with `.eh_frame` safely. – BeeOnRope Mar 11 '17 at 23:00
  • As it turns out, the `.o` files assembled with debug info enabled contain _neither_ `.eh_frame` nor `.debug_frame`. They have only `.debug_line`, `.debug_abbrev`, `.debug_info`, `.debug_aranges` and associated `.rela` sections. This makes sense since an assembler wouldn't have any context about "functions" or "frames" to allow it to generate `.debug_frame` sections (absent special directives). So somehow just the inclusion of the other debug sections causes gdb to barf. – BeeOnRope Dec 10 '17 at 21:25
  • @BeeOnRope, I have a similar issue with gdb. I wonder if you could point to the correct solution. I have a stripped `.o` file generated from my asm and containing *no* debug info or `.eh_frame`. It is linked in a shared lib with other C files. Now when I try to play with `$rbp` during a gdb session, I notice that in my asm function, gdb looks into `*($rsp)` for the previous `$rip` and simply performs `$rsp+8` for the previous `$rsp` (i.e., gdb believes the previous stack frame is 8 bytes up the stack). Do you have any idea why gdb uses this logic and why it decides to add 8 bytes? – Dmitrii Kuvaiskii Apr 22 '19 at 00:47
  • @DmitriiKuvaiskii - probably gcc assumes a standard stack frame layout and `rsp + 8` is where it expects to find the saved `rbp` pointer in the standard layout. Comments on a tangentially related question probably aren't the best place to discuss it though - why don't you start a new question with an MVCE and link it from here? – BeeOnRope Apr 22 '19 at 01:04
-1

I've found a very simple hack that was good enough for my purposes. In my case there is a single function that didn't work with up command.

Here are the steps:

set $rip = *((void**)$rbp+ 1)
set $rbp = *((void**)$rbp)

First line manually patches the instruction pointer. This seems similar to calling up on gdb, but function arguments are still broken. Second line sets rbp to it's value from caller - this fixes arguments for me.

It's probably ok to call this multiple times to go up multiple functions. In my case after single iteration of these commands up and frame start to work. You might also need to set rsp.

Warning: there is no easy way to go back (down)

S. Kaczor
  • 401
  • 3
  • 8
  • This actually jumps there so it's destructive. If you used GDB tmp vars like the other answer suggests, you could "just look" without actually modifying the process or thread you're trying to backtrace. – Peter Cordes Mar 20 '20 at 21:56