Hi Abhishek,
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.