[Dwarf-Discuss] Stack tracing and return addresses off by 1

Greg Clayton clayborg at gmail.com
Fri Jul 31 13:04:19 PDT 2020

You are correct. For the first frame you don't adjust the PC when looking up the unwind row. For the second frame on up you can decrement the PC value by 1 before when doing the row lookup and that is usually enough to get you to the correct unwind row. The issue is that the return address points to the next instruction after the instruction that called the function. This also fixes issues with tail calls. Now you might ask why decrementing by 1 works when instructions can often be larger that 1 byte. We just need to get to the previous row in the unwind table, and decrementing by 1 is usually enough to get us there because unwind rows start at valid instruction opcode addresses.

The other tricky thing to watch out for is that the unwind information isn't always valid for the first frame for all values of the PC in a function. Why? Most unwind information is only valid at places that can throw exceptions. This means that when unwinding the first frame, you really can't trust the unwind info unless you know you are at a location that can throw an exception which is hard to detect by just looking at disassembly. Compilers have the ability to enable asynchronous unwinding with a compiler option, but even if we do enable this, there is no way to look at the unwind information at run time and tell the difference between synchronous unwind info (not valid everywhere in the function, only at places that can throw exceptions) and asynchronous unwind info (valid for any PC value in the function). To make things worse, the information that is put into .debug_frame often is just the same unwind info that is put into .eh_frame (with a few syntactic differences in encoding), so just know that .debug_frame is often only valid at exception call sites but there is not way to tell unless your compiler emits it compiler invocation flags in the DWARF in the DW_TAG_compile_unit as an attribute.

The LLDB debugger will use unwind info from the binary for all frames except the first frame. For the first frame, we actually decode assembly and create our own unwind information that is valid everywhere in the function. 

Now, the good news is, if you are making a backtrace for a thread that has crashed, it should be at a valid address and allow you to use the unwind information. If you also have other threads that were stopped when one thread crashes, they won't be at valid locations for unwind for the first frames.

> On Jul 31, 2020, at 5:37 AM, Jayvee Neumann via Dwarf-Discuss <dwarf-discuss at lists.dwarfstd.org> wrote:
> Hello together!
> I am running into a problem while performing a stack trace of x86 code. The assembly code I am running has been generated by mingw from C++ code and looks like this:
> 6c9c1210 <__ZN7my_class9my_methodEs>:
> 6c9c1210: sub    $0x1c,%esp
> 6c9c1213: mov    0x20(%esp),%edx
> 6c9c1217: mov    %edx,%eax
> 6c9c1219: test   %dx,%dx
> 6c9c121c: jle    6c9c1221 <__ZN7my_class9my_methodEs+0x11>
> 6c9c121e: lea    0x1(%edx),%eax
> 6c9c1221: cwtl   
> 6c9c1222: mov    %eax,(%esp)
> 6c9c1225: call   6c9c1190 <__Z12my_dummy_functions>
> 6c9c122a: add    $0x1c,%esp
> 6c9c122d: ret    $0x4
> 6c9c1230 <__ZN8my_struct9my_methodEv>:
> 6c9c1230: sub    $0x1c,%esp
> 6c9c1233: movzwl 0x4(%ecx),%eax
> 6c9c1237: test   %ax,%ax
> 6c9c123a: jle    6c9c1241 <__ZN8my_struct9my_methodEv+0x11>
> 6c9c123c: add    $0x1,%eax
> 6c9c123f: jmp    6c9c1246 <__ZN8my_struct9my_methodEv+0x16>
> 6c9c1241: mov    $0x0,%eax
> 6c9c1246: cwtl   
> 6c9c1247: add    $0x8,%ecx
> 6c9c124a: mov    %eax,(%esp)
> 6c9c124d: call   6c9c1210 <__ZN7my_class9my_methodEs>
> 6c9c1252: sub    $0x4,%esp
> 6c9c1255: add    $0x1c,%esp
> 6c9c1258: ret    
> 6c9c1259: nop
> 6c9c125a: lea    0x0(%esi),%esi
> The problem manifests itself, when the instruction pointer is inside " __ZN7my_class9my_methodEs" (called at 0x6c9c124d).
> In order to perform the stack trace, I use the DWARF frame information for calculating the previous instruction pointer. This is done by assuming the return address is the instruction pointer of the previous frame. This is obviously not entirely correct, since the return address points to a location AFTER the previous call. Nevertheless, this assumption seems to be standard for other stack tracers.
> I am having a problem with this though:
> The address where I start is 0x6c9c121e. Frame information tells me the following:
> 00000144 0000001c 00000000 FDE cie=00000000 pc=6c9c1210...6c9c1230
>   DW_CFA_advance_loc4: 3
>   DW_CFA_def_cfa_offset: +32
>   DW_CFA_advance_loc4: 26
>   DW_CFA_def_cfa_offset: +4
>   DW_CFA_nop:
>   DW_CFA_nop:
> So the CFA offset is 32. There I find the next return address 0x6c9c124d. Frame information tells me the following:
> 00000164 00000028 00000000 FDE cie=00000000 pc=6c9c1230...6c9c1259
>   DW_CFA_advance_loc4: 3
>   DW_CFA_def_cfa_offset: +32
>   DW_CFA_advance_loc4: 31
>   DW_CFA_def_cfa_offset: +28
>   DW_CFA_advance_loc4: 3
>   DW_CFA_def_cfa_offset: +32
>   DW_CFA_advance_loc4: 3
>   DW_CFA_def_cfa_offset: +4
> And here the problem arises. Due to 0x6c9c124d being the return address, the CFA offset I read is invalid. By assuming an instruction pointer of 0x6c9c124d I also assume that the "ret $0x4" instruction from 0x6c9c122d has been executed and the stack is 4 bytes shorter. This is however not the case. The return has not been executed yet.
> So my question here is, how shall the stack tracer solve this issue? My first Idea is to decrement the instruction pointer when looking through the frame information (except for the deepest frame, where the instruction pointer is correct). Would that be an approach that works always? How do other consumers solve this issue?
> Best regards
> Jayvee
> _______________________________________________
> Dwarf-Discuss mailing list
> Dwarf-Discuss at lists.dwarfstd.org
> http://lists.dwarfstd.org/listinfo.cgi/dwarf-discuss-dwarfstd.org

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.dwarfstd.org/pipermail/dwarf-discuss-dwarfstd.org/attachments/20200731/1f951d8a/attachment-0001.html>

More information about the Dwarf-Discuss mailing list