[lldb-dev] LLDB: Unwinding based on Assembly Instruction Profiling

Jason Molenda via lldb-dev lldb-dev at lists.llvm.org
Fri Oct 30 19:47:49 PDT 2015


Hi Abhishek,


> On Oct 30, 2015, at 6:56 AM, Abhishek Aggarwal <abhiinnitald at gmail.com> wrote:
> 
> When eh_frame has epilogue description as well, the Assembly profiler
> doesn't need to augment it. In this case, is eh_frame augmented unwind
> plan used as Non Call Site Unwind Plan or Assembly based Unwind Plan
> is used?

Yes, you're correct.

If an eh_frame unwind plan describes the epilogue and the prologue, we will use it at "non-call sites", that is, the currently executing function.  

If we augment an eh_frame unwind plan by adding epilogue instructions, we will use it at non-call sites.

If an eh_frame unwind plan is missing epilogue, and we can't augment it for some reason, then it will not be used at non-call sites (the currently executing function).

The assembly unwind plan will be used for the currently executing function if we can't use the eh_frame unwind plan.



> I checked FuncUnwinders::GetUnwindPlanAtNonCallSite()
> function. When there is nothing to augment in eh_frame Unwind plan,
> then GetEHFrameAugmentedUnwindPlan() function returns nullptr and
> AssemblyUnwindPlan is used as Non Call Site Unwind Plan. Is it the
> expected behavior?


Yes.  FuncUnwinders::GetEHFrameAugmentedUnwindPlan gets the plain eh_frame unwind plan, passes it to UnwindAssembly_x86::AugmentUnwindPlanFromCallSite().

UnwindAssembly_x86::AugmentUnwindPlanFromCallSite will verify that the unwind plan describes the prologue.  If the prologue isn't described, it says that this cannot be augmented.

It then looks to see if the epilogue is described.  If the epilogue is described, it says the unwind plan is usable as-is.

If the epilogue is not described, it will use the assembly unwinder to add the epilogue unwind instructions.

> 
> About your comments on gcc producing ''asynchronous unwind tables'',
> do you mean that gcc is not producing asynchronous unwind tables as it
> keeps *some* async unwind instructions and not all of them?


"asynchronous" means that the unwind instructions are valid at every instruction location.

"synchronous" means that the unwind instructions are only valid at places where an exception can be thrown, or a function is called that may throw an exception.


Inside lldb, I use the terminology "non-call site" to mean "asynchronous".  You're at an arbitrary instruction location, for instance, you're in the currently-executing function.  I use "call site" to mean synchronous - a function has called another function, so it's in the middle of the function body, past the prologue, before the epilogue.  This is a function higher up on the stack.

The terms are confusing, I know.

The last time I checked, gcc cannot be made to emit truly asynchronous unwind instructions.  This is easy to test on a i386 binary compiled with -fomit-frame-pointer.  For instance (the details will be a little different on an ELF system but I bet it will be similar if the program runs position independent aka pic):

% cat >test.c
#include <stdio.h>
int main () { puts ("HI"); }
^D
% clang  -arch i386 -fomit-frame-pointer test.c
% lldb a.out
(lldb) target create "a.out"
Current executable set to 'a.out' (i386).(lldb) disass -b -n main
a.out`main:
a.out[0x1f70] <+0>:  83 ec 0c           subl   $0xc, %esp
a.out[0x1f73] <+3>:  e8 00 00 00 00     calll  0x1f78                    ; <+8>
a.out[0x1f78] <+8>:  58                 popl   %eax
a.out[0x1f79] <+9>:  8d 80 3a 00 00 00  leal   0x3a(%eax), %eax
a.out[0x1f7f] <+15>: 89 04 24           movl   %eax, (%esp)
a.out[0x1f82] <+18>: e8 0d 00 00 00     calll  0x1f94                    ; symbol stub for: puts

Look at the call instruction at +3.  What is this doing?  It calls the next instruction, which does a pop %eax. This is loading the address main+8 into eax so it can get the address of the "HI" string which is at main+8+0x3a.  It's called a "pic base", or position independent code base, because this program could be loaded at any address when it is run, the instructions can't directly reference the address of the "HI" string.

If I run this program and have lldb dump its assembly unwind rules for the function:

(lldb) image show-unwind -n main
row[0]:    0: CFA=esp +4 => esp=CFA+0 eip=[CFA-4] 
row[1]:    3: CFA=esp+16 => esp=CFA+0 eip=[CFA-4] 
row[2]:    8: CFA=esp+20 => esp=CFA+0 eip=[CFA-4] 
row[3]:    9: CFA=esp+16 => esp=CFA+0 eip=[CFA-4] 
row[4]:   34: CFA=esp +4 => esp=CFA+0 eip=[CFA-4] 

It gets this right.  After the call instruction at +3, the CFA is now esp+20 because we just pushed a word on to the tack.  And after the pop instruction at +8, the CFA is back to esp+16 because we popped that word off the stack.

An asynchronous unwind plan would describe these stack movements.  A synchronous unwind plan will not -- they are before any point where we could throw an exception, or before we call another function.

(notice that you need to use -fomit-frame-pointer to get this problem.  If ebp is set up as the frame pointer, it doesn't matter how we change the stack pointer for the rest of the function.)


Hope that helps.







> 
> Abhishek
> 
> 
>> Hi all, sorry I missed this discussion last week, I was a little busy.
>> 
>> Greg's original statement isn't correct -- about a year ago Tong Shen changed lldb to using eh_frame for the currently-executing frame.  While it is true that eh_frame is not guaranteed to describe the prologue/epilogue, in practice eh_frame always describes the epilogue (gdb wouldn't couldn't without this, with its much more simplistic unwinder).  Newer gcc's also describe the epilogue.  clang does not (currently) describe the epilogue. Tong's changes *augment* the eh_frame with an epilogue description if it doesn't already have one.
>> 
>> gcc does have an "asynchronous unwind tables" option -- "asynchronous" meaning the unwind rules are defined at every instruction location.  But the last time I tried it, it did nothing.  They've settled on an unfortunate middle ground where eh_frame (which should be compact and only describe enough for exception handling) has *some* async unwind instructions.  And the same unwind rules are emitted into the debug_frame section, even if -fasynchronous-unwind-tables is used.
>> 
>> In the ideal world, eh_frame should be extremely compact and only sufficient for exception handling.  debug_frame should be extremely verbose and describe the unwind rules at all unwind locations.
>> 
>> As Tamas says, there's no indication in eh_frame or debug_frame as to how much is described:  call-sites only (for exception handling), call-sites + prologue, call-sites + prologue + epilogue, or fully asynchronous.  It's a drag, if the DWARF committee ever has enough reason to break open the debug_frame format for some other changes, I'd like to get more information in there.
>> 
>> 
>> Anyway, point is, we're living off of eh_frame (possibly "augmented") for the currently-executing stack frame these days.  lldb may avoid using the assembly unwinder altogether in an environment where it finds eh_frame unwind instructions for every stack frame.
>> 
>> 
>> (on Mac, we've switched to a format called "compact unwind" -- much like the ARM unwind info that Tamas recently added support for, this is an extremely small bit of information which describes one unwind rule for the entire function.  It is only applicable or exception handling, it has no way to describe prologues/epilogues.  compact unwind is two 4-byte words per function.  lldb will use compact unwind / ARM unwind info for the non-zeroth stack frames.  It will use its assembly instruction profiler for the currently-executing stack frame.)
>> 
>> Hope that helps.
>> 
>> J
>> 
>> 
>>> On Oct 15, 2015, at 2:56 AM, Tamas Berghammer via lldb-dev <lldb-dev at lists.llvm.org> wrote:
>>> 
>>> If we are trying to unwind from a non call site (frame 0 or signal handler) then the current implementation first try to use the non call site unwind plan (usually assembly emulation) and if that one fails then it will fall back to the call site unwind plan (eh_frame, compact unwind info, etc.) instead of falling back to the architecture default unwind plan because it should be a better guess in general and we usually fail with the assembly emulation based unwind plan for hand written assembly functions where eh_frame is usually valid at all address.
>>> 
>>> Generating asynchronous eh_frame (valid at all address) is possible with gcc (I am not sure about clang) but there is no way to tell if a given eh_frame inside an object file is valid at all address or only at call sites. The best approximation what we can do is to say that each eh_frame entry is valid only at the address what it specifies as start address but we don't make a use of it in LLDB at the moment.
>>> 
>>> For the 2nd part of the original question, I think changing the eh_frame based unwind plan after a failed unwind using instruction emulation is only a valid option for the PC where we tried to unwind from because the assembly based unwind plan could be valid at other parts of the function. Making the change for that 1 concrete PC address would make sense, but have practically no effect because the next time we want to unwind from the given address we use the same fall back mechanism as in the first case and the change would have only a very small performance gain.
>>> 
>>> Tamas
>>> 
>>> On Wed, Oct 14, 2015 at 9:36 PM Greg Clayton via lldb-dev <lldb-dev at lists.llvm.org> wrote:
>>> 
>>>> On Oct 14, 2015, at 1:02 PM, Joerg Sonnenberger via lldb-dev <lldb-dev at lists.llvm.org> wrote:
>>>> 
>>>> On Wed, Oct 14, 2015 at 11:42:06AM -0700, Greg Clayton via lldb-dev wrote:
>>>>> EH frame can't be used to unwind when we are in the first frame because
>>>>> it is only valid at call sites. It also can't be used in frames that
>>>>> are asynchronously interrupted like signal handler frames.
>>>> 
>>>> This is not necessarily true, GCC can build them like that. I don't
>>>> think we have a flag for clang/LLVM to create full async unwind tables.
>>> 
>>> Most compilers don't generate stuff that is complete, and if it is complete, I am not aware of any markings on EH frame that states it is complete. So we really can't use it unless we know the info is complete. Was there ever an additional augmentation letter that was attached to the complete EH frame info?
>>> 
>>> 
>>> _______________________________________________
>>> lldb-dev mailing list
>>> lldb-dev at lists.llvm.org
>>> http://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-dev
>>> _______________________________________________
>>> lldb-dev mailing list
>>> lldb-dev at lists.llvm.org
>>> http://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-dev


More information about the lldb-dev mailing list