Unwinding with no symbols

Hi Jason,

I know we talked about this a long time ago with regards to stepping over a method that you don’t have symbols for. At that time, the conclusion was that we should get at least function bounds for each function, otherwise we can’t guarantee that we’ll be able to unwind properly. I plan to do that (fairly soon), but in the meantime I want to see if we can improve the unwinder in the presence of no symbols. I think this is still a useful thing to have, and could potentially be an easy fix. Currently if I try to do this, I get the following output from LLDB.

(lldb) Process 10848 stopped

  • thread #1: tid = 0x11fc, 0x011882be simple_step.exemain(argc=1, argv=0x015e3be0) + 606 at simple_step.cpp:19, stop reason = breakpoint 1.3 frame #0: 0x011882be simple_step.exemain(argc=1, argv=0x015e3be0) + 606 at simple_step.cpp:19
    16 std::cout << "fib(5) = " << fib(5) << std::endl;
    17 std::cout << "fib(6) = " << fib(6) << std::endl;
    18
    → 19 printf(“The value of fib(7) is %u\n”, fib(7));
    20 return 0;
    21 }
    (lldb) bt
  • thread #1: tid = 0x11fc, 0x011882be simple_step.exe`main(argc=1, argv=0x015e3be0) + 606 at simple_step.cpp:19, stop reason = breakpoint 1.3
  • frame #0: 0x011882be simple_step.exemain(argc=1, argv=0x015e3be0) + 606 at simple_step.cpp:19 frame #1: 0x01190a41 simple_step.exe frame #2: 0x75107c04 kernel32.dllBaseThreadInitThunk + 36
    frame #3: 0x7774b54f ntdll.dll`RtlInitializeExceptionChain + 143
    frame #4: 0xffffffff
    (lldb) thread step-over
    (lldb) Process 10848 stopped
  • thread #1: tid = 0x11fc, 0x01190a41 simple_step.exe, stop reason = step over
    frame #0: 0x01190a41 simple_step.exe
    → 0x1190a41: addl $0xc, %esp
    0x1190a44: movl %eax, %esi
    0x1190a46: movl %esi, -0x24(%ebp)
    0x1190a49: testl %ebx, %ebx
    (lldb) bt
  • thread #1: tid = 0x11fc, 0x01190a41 simple_step.exe, stop reason = step over
  • frame #0: 0x01190a41 simple_step.exe
    frame #1: 0x75107c04 kernel32.dllBaseThreadInitThunk + 36 frame #2: 0x7774b54f ntdll.dllRtlInitializeExceptionChain + 143
    frame #3: 0xffffffff

0x1190a41 if you look at the original callstack is higher up on the frame than main(), so it’s basically the function in the CRT that calls main.

th1/fr0 with pc value of 0x11882be, function name is ‘main’
th1/fr0 0x0000000001188064: CFA=ebp +8 => esi=[CFA-12] ebp=[CFA-8] esp=CFA+0 eip=[CFA-4]

th1/fr0 CFA is 0x114fbbc: Register ebp (6) contents are 0x114fbb4, offset is 8
th1/fr0 initialized frame current pc is 0x11882be cfa is 0x114fbbc using assembly insn profiling UnwindPlan
th1/fr0 with pc value of 0x11882be, function name is ‘main’
th1/fr0 0x0000000001188064: CFA=ebp +8 => esi=[CFA-12] ebp=[CFA-8] esp=CFA+0 eip=[CFA-4]

th1/fr0 CFA is 0x114fbbc: Register ebp (6) contents are 0x114fbb4, offset is 8
th1/fr0 initialized frame current pc is 0x11882be cfa is 0x114fbbc using assembly insn profiling UnwindPlan
th1/fr0 supplying caller’s saved eip (8)'s location using assembly insn profiling UnwindPlan
th1/fr0 supplying caller’s register eip (8) from the stack, saved at CFA plus offset -4 [saved at 0x114fbb8]
th1/fr1 pc = 0x1190a41
th1/fr0 supplying caller’s saved ebp (6)'s location using assembly insn profiling UnwindPlan
th1/fr0 supplying caller’s register ebp (6) from the stack, saved at CFA plus offset -8 [saved at 0x114fbb4]
th1/fr1 fp = 0x114fbfc
th1/fr0 supplying caller’s saved esp (7)'s location using assembly insn profiling UnwindPlan
th1/fr0 supplying caller’s register esp (7), value is CFA plus offset 0 [value is 0x114fbbc]
th1/fr1 sp = 0x114fbbc
th1/fr1 with pc value of 0x1190a41, no symbol/function name is known.
th1/fr1 Backing up the pc value of 0x1190a41 by 1 and re-doing symbol lookup; old symbol was
th1/fr1 Symbol is now
th1/fr1 active row: 0x0000000001190a41: CFA=ebp +8 => esp=CFA+0 ebp=[CFA-8] eip=[CFA-4]

th1/fr0 supplying caller’s saved ebp (6)'s location, cached
th1/fr1 CFA is 0x114fc04: Register ebp (6) contents are 0x114fbfc, offset is 8
th1/fr1 m_cfa = 0x114fc04
th1/fr1 initialized frame current pc is 0x1190a41 cfa is 0x114fc04
th1/fr0 supplying caller’s saved eip (8)'s location, cached
th1/fr1 supplying caller’s saved eip (8)'s location using i386 default unwind plan UnwindPlan
th1/fr1 supplying caller’s register eip (8) from the stack, saved at CFA plus offset -4 [saved at 0x114fc00]
th1/fr2 pc = 0x75107c04
th1/fr1 supplying caller’s saved ebp (6)'s location using i386 default unwind plan UnwindPlan
th1/fr1 supplying caller’s register ebp (6) from the stack, saved at CFA plus offset -8 [saved at 0x114fbfc]
th1/fr2 fp = 0x114fc10
th1/fr1 supplying caller’s saved esp (7)'s location using i386 default unwind plan UnwindPlan
th1/fr1 supplying caller’s register esp (7), value is CFA plus offset 0 [value is 0x114fc04]
th1/fr2 sp = 0x114fc04
th1/fr2 with pc value of 0x75107c04, symbol name is ‘BaseThreadInitThunk’
th1/fr2 active row: 0x0000000075107bf1: CFA=ebp +8 => esi=[CFA-16] ebp=[CFA-8] esp=CFA+0 eip=[CFA-4]

th1/fr1 supplying caller’s saved ebp (6)'s location, cached
th1/fr2 CFA is 0x114fc18: Register ebp (6) contents are 0x114fc10, offset is 8
th1/fr2 m_cfa = 0x114fc18
th1/fr2 initialized frame current pc is 0x75107c04 cfa is 0x114fc18
th1/fr1 supplying caller’s saved eip (8)'s location, cached
th1/fr2 supplying caller’s saved eip (8)'s location using assembly insn profiling UnwindPlan
th1/fr2 supplying caller’s register eip (8) from the stack, saved at CFA plus offset -4 [saved at 0x114fc14]
th1/fr3 pc = 0x7774b54f
th1/fr2 supplying caller’s saved ebp (6)'s location using assembly insn profiling UnwindPlan
th1/fr2 supplying caller’s register ebp (6) from the stack, saved at CFA plus offset -8 [saved at 0x114fc10]
th1/fr3 fp = 0x114fc58
th1/fr2 supplying caller’s saved esp (7)'s location using assembly insn profiling UnwindPlan
th1/fr2 supplying caller’s register esp (7), value is CFA plus offset 0 [value is 0x114fc18]
th1/fr3 sp = 0x114fc18
th1/fr3 with pc value of 0x7774b54f, symbol name is ‘RtlInitializeExceptionChain’
th1/fr3 active row: 0x000000007774b504: CFA=ebp+12 => ebp=[CFA-12] esp=CFA+0 eip=[CFA-4]

th1/fr2 supplying caller’s saved ebp (6)'s location, cached
th1/fr3 CFA is 0x114fc64: Register ebp (6) contents are 0x114fc58, offset is 12
th1/fr3 m_cfa = 0x114fc64
th1/fr3 initialized frame current pc is 0x7774b54f cfa is 0x114fc64
th1/fr2 supplying caller’s saved eip (8)'s location, cached
th1/fr3 supplying caller’s saved eip (8)'s location using assembly insn profiling UnwindPlan
th1/fr3 supplying caller’s register eip (8) from the stack, saved at CFA plus offset -4 [saved at 0x114fc60]
th1/fr4 pc = 0xffffffff
th1/fr3 supplying caller’s saved ebp (6)'s location using assembly insn profiling UnwindPlan
th1/fr3 supplying caller’s register ebp (6) from the stack, saved at CFA plus offset -12 [saved at 0x114fc58]
th1/fr4 fp = 0x114fc68
th1/fr3 supplying caller’s saved esp (7)'s location using assembly insn profiling UnwindPlan
th1/fr3 supplying caller’s register esp (7), value is CFA plus offset 0 [value is 0x114fc64]
th1/fr4 sp = 0x114fc64
th1/fr4 using architectural default unwind method
th1/fr3 supplying caller’s saved ebp (6)'s location, cached
th1/fr4 CFA is 0x114fc70: Register ebp (6) contents are 0x114fc68, offset is 8
th1/fr4 initialized frame cfa is 0x114fc70
th1/fr3 supplying caller’s saved eip (8)'s location, cached
th1/fr4 supplying caller’s saved eip (8)'s location using i386 default unwind plan UnwindPlan
th1/fr4 supplying caller’s register eip (8) from the stack, saved at CFA plus offset -4 [saved at 0x114fc6c]
th1/fr5 pc = 0x0
th1/fr4 supplying caller’s saved ebp (6)'s location using i386 default unwind plan UnwindPlan
th1/fr4 supplying caller’s register ebp (6) from the stack, saved at CFA plus offset -8 [saved at 0x114fc68]
th1/fr5 fp = 0x0
th1/fr4 supplying caller’s saved esp (7)'s location using i386 default unwind plan UnwindPlan
th1/fr4 supplying caller’s register esp (7), value is CFA plus offset 0 [value is 0x114fc70]
th1/fr5 sp = 0x114fc70
th1/fr5 this frame has a pc of 0x0
Frame 5 invalid RegisterContext for this frame, stopping stack walk
th1 Unwind of this thread is complete.
th1/fr0 with pc value of 0x11882c5, function name is ‘main’
th1/fr0 0x0000000001188064: CFA=ebp +8 => esi=[CFA-12] ebp=[CFA-8] esp=CFA+0 eip=[CFA-4]

th1/fr0 CFA is 0x114fbbc: Register ebp (6) contents are 0x114fbb4, offset is 8
th1/fr0 initialized frame current pc is 0x11882c5 cfa is 0x114fbbc using assembly insn profiling UnwindPlan
th1/fr0 with pc value of 0x11882cb, function name is ‘main’
th1/fr0 0x0000000001188064: CFA=ebp +8 => esi=[CFA-12] ebp=[CFA-8] esp=CFA+0 eip=[CFA-4]

th1/fr0 CFA is 0x114fbbc: Register ebp (6) contents are 0x114fbb4, offset is 8
th1/fr0 initialized frame current pc is 0x11882cb cfa is 0x114fbbc using assembly insn profiling UnwindPlan
th1/fr0 with pc value of 0x11882cb, function name is ‘main’
th1/fr0 0x0000000001188064: CFA=ebp +8 => esi=[CFA-12] ebp=[CFA-8] esp=CFA+0 eip=[CFA-4]

th1/fr0 CFA is 0x114fbbc: Register ebp (6) contents are 0x114fbb4, offset is 8
th1/fr0 initialized frame current pc is 0x11882cb cfa is 0x114fbbc using assembly insn profiling UnwindPlan
th1/fr0 with pc value of 0x1188000, function name is ‘unsigned int fib(unsigned int)’
th1/fr0 0x0000000001188000: CFA=esp +4 => esp=CFA+0 eip=[CFA-4]

th1/fr0 CFA is 0x114fb40: Register esp (7) contents are 0x114fb3c, offset is 4
th1/fr0 initialized frame current pc is 0x1188000 cfa is 0x114fb40 using assembly insn profiling UnwindPlan
th1/fr0 supplying caller’s saved eip (8)'s location using assembly insn profiling UnwindPlan
th1/fr0 supplying caller’s register eip (8) from the stack, saved at CFA plus offset -4 [saved at 0x114fb3c]
th1/fr1 pc = 0x11882d0
th1/fr0 supplying caller’s register ebp (6) from the live RegisterContext at frame 0
th1/fr1 fp = 0x114fbb4
th1/fr0 supplying caller’s saved esp (7)'s location using assembly insn profiling UnwindPlan
th1/fr0 supplying caller’s register esp (7), value is CFA plus offset 0 [value is 0x114fb40]
th1/fr1 sp = 0x114fb40
th1/fr1 with pc value of 0x11882d0, function name is ‘main’
th1/fr1 active row: 0x0000000001188060: CFA=ebp +8 => esp=CFA+0 ebp=[CFA-8] eip=[CFA-4]

th1/fr0 supplying caller’s saved ebp (6)'s location, cached
th1/fr1 CFA is 0x114fbbc: Register ebp (6) contents are 0x114fbb4, offset is 8
th1/fr1 m_cfa = 0x114fbbc
th1/fr1 initialized frame current pc is 0x11882d0 cfa is 0x114fbbc
th1/fr0 supplying caller’s saved eip (8)'s location, cached
th1/fr0 with pc value of 0x11882d0, function name is ‘main’
th1/fr0 0x0000000001188064: CFA=ebp +8 => esi=[CFA-12] ebp=[CFA-8] esp=CFA+0 eip=[CFA-4]

th1/fr0 CFA is 0x114fbbc: Register ebp (6) contents are 0x114fbb4, offset is 8
th1/fr0 initialized frame current pc is 0x11882d0 cfa is 0x114fbbc using assembly insn profiling UnwindPlan
th1/fr0 with pc value of 0x11882d0, function name is ‘main’
th1/fr0 0x0000000001188064: CFA=ebp +8 => esi=[CFA-12] ebp=[CFA-8] esp=CFA+0 eip=[CFA-4]

th1/fr0 CFA is 0x114fbbc: Register ebp (6) contents are 0x114fbb4, offset is 8
th1/fr0 initialized frame current pc is 0x11882d0 cfa is 0x114fbbc using assembly insn profiling UnwindPlan
th1/fr0 with pc value of 0x11882d6, function name is ‘main’
th1/fr0 0x0000000001188064: CFA=ebp +8 => esi=[CFA-12] ebp=[CFA-8] esp=CFA+0 eip=[CFA-4]

th1/fr0 CFA is 0x114fbbc: Register ebp (6) contents are 0x114fbb4, offset is 8
th1/fr0 initialized frame current pc is 0x11882d6 cfa is 0x114fbbc using assembly insn profiling UnwindPlan
th1/fr0 with pc value of 0x11882dd, function name is ‘main’
th1/fr0 0x0000000001188064: CFA=ebp +8 => esi=[CFA-12] ebp=[CFA-8] esp=CFA+0 eip=[CFA-4]

th1/fr0 CFA is 0x114fbbc: Register ebp (6) contents are 0x114fbb4, offset is 8
th1/fr0 initialized frame current pc is 0x11882dd cfa is 0x114fbbc using assembly insn profiling UnwindPlan
th1/fr0 with pc value of 0x11882dd, function name is ‘main’
th1/fr0 0x0000000001188064: CFA=ebp +8 => esi=[CFA-12] ebp=[CFA-8] esp=CFA+0 eip=[CFA-4]

th1/fr0 CFA is 0x114fbbc: Register ebp (6) contents are 0x114fbb4, offset is 8
th1/fr0 initialized frame current pc is 0x11882dd cfa is 0x114fbbc using assembly insn profiling UnwindPlan
th1/fr0 with pc value of 0x11906d5, no symbol/function name is known.
th1/fr0 0x00000000011906d5: CFA=ebp +8 => esp=CFA+0 ebp=[CFA-8] eip=[CFA-4]

th1/fr0 CFA is 0x114fbbc: Register ebp (6) contents are 0x114fbb4, offset is 8
th1/fr0 initialized frame current pc is 0x11906d5 cfa is 0x114fbbc using i386 default unwind plan UnwindPlan
th1/fr0 supplying caller’s saved eip (8)'s location using i386 default unwind plan UnwindPlan
th1/fr0 supplying caller’s register eip (8) from the stack, saved at CFA plus offset -4 [saved at 0x114fbb8]
th1/fr1 pc = 0x1190a41
th1/fr0 supplying caller’s saved ebp (6)'s location using i386 default unwind plan UnwindPlan
th1/fr0 supplying caller’s register ebp (6) from the stack, saved at CFA plus offset -8 [saved at 0x114fbb4]
th1/fr1 fp = 0x114fbfc
th1/fr0 supplying caller’s saved esp (7)'s location using i386 default unwind plan UnwindPlan
th1/fr0 supplying caller’s register esp (7), value is CFA plus offset 0 [value is 0x114fbbc]
th1/fr1 sp = 0x114fbbc
th1/fr1 with pc value of 0x1190a41, no symbol/function name is known.
th1/fr1 Backing up the pc value of 0x1190a41 by 1 and re-doing symbol lookup; old symbol was
th1/fr1 Symbol is now
th1/fr1 active row: 0x0000000001190a41: CFA=ebp +8 => esp=CFA+0 ebp=[CFA-8] eip=[CFA-4]

th1/fr0 supplying caller’s saved ebp (6)'s location, cached
th1/fr1 CFA is 0x114fc04: Register ebp (6) contents are 0x114fbfc, offset is 8
th1/fr1 m_cfa = 0x114fc04
th1/fr1 initialized frame current pc is 0x1190a41 cfa is 0x114fc04
th1/fr0 supplying caller’s saved eip (8)'s location, cached
th1/fr0 with pc value of 0x1190a41, no symbol/function name is known.
th1/fr0 0x0000000001190a41: CFA=ebp +8 => esp=CFA+0 ebp=[CFA-8] eip=[CFA-4]

th1/fr0 CFA is 0x114fc04: Register ebp (6) contents are 0x114fbfc, offset is 8
th1/fr0 initialized frame current pc is 0x1190a41 cfa is 0x114fc04 using i386 default unwind plan UnwindPlan
th1/fr0 with pc value of 0x1190a41, no symbol/function name is known.
th1/fr0 0x0000000001190a41: CFA=ebp +8 => esp=CFA+0 ebp=[CFA-8] eip=[CFA-4]

th1/fr0 CFA is 0x114fc04: Register ebp (6) contents are 0x114fbfc, offset is 8
th1/fr0 initialized frame current pc is 0x1190a41 cfa is 0x114fc04 using i386 default unwind plan UnwindPlan
th1/fr0 supplying caller’s saved eip (8)'s location using i386 default unwind plan UnwindPlan
th1/fr0 supplying caller’s register eip (8) from the stack, saved at CFA plus offset -4 [saved at 0x114fc00]
th1/fr1 pc = 0x75107c04
th1/fr0 supplying caller’s saved ebp (6)'s location using i386 default unwind plan UnwindPlan
th1/fr0 supplying caller’s register ebp (6) from the stack, saved at CFA plus offset -8 [saved at 0x114fbfc]
th1/fr1 fp = 0x114fc10
th1/fr0 supplying caller’s saved esp (7)'s location using i386 default unwind plan UnwindPlan
th1/fr0 supplying caller’s register esp (7), value is CFA plus offset 0 [value is 0x114fc04]
th1/fr1 sp = 0x114fc04
th1/fr1 with pc value of 0x75107c04, symbol name is ‘BaseThreadInitThunk’
th1/fr1 active row: 0x0000000075107bf1: CFA=ebp +8 => esi=[CFA-16] ebp=[CFA-8] esp=CFA+0 eip=[CFA-4]

th1/fr0 supplying caller’s saved ebp (6)'s location, cached
th1/fr1 CFA is 0x114fc18: Register ebp (6) contents are 0x114fc10, offset is 8
th1/fr1 m_cfa = 0x114fc18
th1/fr1 initialized frame current pc is 0x75107c04 cfa is 0x114fc18
th1/fr0 supplying caller’s saved eip (8)'s location, cached
th1/fr1 supplying caller’s saved eip (8)'s location using assembly insn profiling UnwindPlan
th1/fr1 supplying caller’s register eip (8) from the stack, saved at CFA plus offset -4 [saved at 0x114fc14]
th1/fr2 pc = 0x7774b54f
th1/fr1 supplying caller’s saved ebp (6)'s location using assembly insn profiling UnwindPlan
th1/fr1 supplying caller’s register ebp (6) from the stack, saved at CFA plus offset -8 [saved at 0x114fc10]
th1/fr2 fp = 0x114fc58
th1/fr1 supplying caller’s saved esp (7)'s location using assembly insn profiling UnwindPlan
th1/fr1 supplying caller’s register esp (7), value is CFA plus offset 0 [value is 0x114fc18]
th1/fr2 sp = 0x114fc18
th1/fr2 with pc value of 0x7774b54f, symbol name is ‘RtlInitializeExceptionChain’
th1/fr2 active row: 0x000000007774b504: CFA=ebp+12 => ebp=[CFA-12] esp=CFA+0 eip=[CFA-4]

th1/fr1 supplying caller’s saved ebp (6)'s location, cached
th1/fr2 CFA is 0x114fc64: Register ebp (6) contents are 0x114fc58, offset is 12
th1/fr2 m_cfa = 0x114fc64
th1/fr2 initialized frame current pc is 0x7774b54f cfa is 0x114fc64
th1/fr1 supplying caller’s saved eip (8)'s location, cached
th1/fr2 supplying caller’s saved eip (8)'s location using assembly insn profiling UnwindPlan
th1/fr2 supplying caller’s register eip (8) from the stack, saved at CFA plus offset -4 [saved at 0x114fc60]
th1/fr3 pc = 0xffffffff
th1/fr2 supplying caller’s saved ebp (6)'s location using assembly insn profiling UnwindPlan
th1/fr2 supplying caller’s register ebp (6) from the stack, saved at CFA plus offset -12 [saved at 0x114fc58]
th1/fr3 fp = 0x114fc68
th1/fr2 supplying caller’s saved esp (7)'s location using assembly insn profiling UnwindPlan
th1/fr2 supplying caller’s register esp (7), value is CFA plus offset 0 [value is 0x114fc64]
th1/fr3 sp = 0x114fc64
th1/fr3 using architectural default unwind method
th1/fr2 supplying caller’s saved ebp (6)'s location, cached
th1/fr3 CFA is 0x114fc70: Register ebp (6) contents are 0x114fc68, offset is 8
th1/fr3 initialized frame cfa is 0x114fc70
th1/fr2 supplying caller’s saved eip (8)'s location, cached
th1/fr3 supplying caller’s saved eip (8)'s location using i386 default unwind plan UnwindPlan
th1/fr3 supplying caller’s register eip (8) from the stack, saved at CFA plus offset -4 [saved at 0x114fc6c]
th1/fr4 pc = 0x0
th1/fr3 supplying caller’s saved ebp (6)'s location using i386 default unwind plan UnwindPlan
th1/fr3 supplying caller’s register ebp (6) from the stack, saved at CFA plus offset -8 [saved at 0x114fc68]
th1/fr4 fp = 0x0
th1/fr3 supplying caller’s saved esp (7)'s location using i386 default unwind plan UnwindPlan
th1/fr3 supplying caller’s register esp (7), value is CFA plus offset 0 [value is 0x114fc70]
th1/fr4 sp = 0x114fc70
th1/fr4 this frame has a pc of 0x0
Frame 4 invalid RegisterContext for this frame, stopping stack walk
th1 Unwind of this thread is complete.

Can you help me interepret this and figure out exactly the point at which things are going wrong? printf() uses a standard calling convention, so I feel like in theory if I can identify the first location that things start to go awry, I may just be able to bail out and have the unwinder fall back to the architecture default unwind plan, which should work.

One more question. The original “bt” command works, and that 0x1190a41 function (frame 1) didn’t have symbols either. Why is it able to unwind through this function, but not through printf()? They use the same calling convention and there are symbols for neither one (which is another reason I think there might be a simple fix hiding in the unwinder).

Yeah, this is fallout from a stack walk algorithm that uses the rule "if we don't know any better, assume the function saved the caller's ebp on the stack and keep walking via that".

(lldb) bt
* thread #1: tid = 0x11fc, 0x011882be simple_step.exe`main(argc=1, argv=0x015e3be0) + 606 at simple_step.cpp:19, stop reason = breakpoint 1.3
  * frame #0: 0x011882be simple_step.exe`main(argc=1, argv=0x015e3be0) + 606 at simple_step.cpp:19
    frame #1: 0x01190a41 simple_step.exe
    frame #2: 0x75107c04 kernel32.dll`BaseThreadInitThunk + 36
    frame #3: 0x7774b54f ntdll.dll`RtlInitializeExceptionChain + 143
    frame #4: 0xffffffff
(lldb) thread step-over
(lldb) Process 10848 stopped
* thread #1: tid = 0x11fc, 0x01190a41 simple_step.exe, stop reason = step over
    frame #0: 0x01190a41 simple_step.exe
-> 0x1190a41: addl $0xc, %esp
    0x1190a44: movl %eax, %esi
    0x1190a46: movl %esi, -0x24(%ebp)
    0x1190a49: testl %ebx, %ebx
(lldb) bt
* thread #1: tid = 0x11fc, 0x01190a41 simple_step.exe, stop reason = step over
  * frame #0: 0x01190a41 simple_step.exe
    frame #1: 0x75107c04 kernel32.dll`BaseThreadInitThunk + 36
    frame #2: 0x7774b54f ntdll.dll`RtlInitializeExceptionChain + 143
    frame #3: 0xffffffff

When we're stopped in 0x01190a41, lldb doesn't have any information about this function. Are we at the first instruction? Has the prologue completed? Are we in the epilogue? We have no way of knowing. We have to make a guess, and the guess, the "Architectural Default Unwind Plan", says to assume we're in the middle of the function body and that the caller's ebp has been saved and that this function sets up & uses ebp too.

I think in our previous discussions you argued that lldb should *know* that it just single instruction stepped into a new function. If that knowledge could be transmitted down to the unwinder, we could use the ABI::CreateFunctionEntryUnwindPlan unwind plan instead of the ABI::CreateDefaultUnwindPlan. The former describes how to unwind when we're sitting at the first instruction of a function. lldb doesn't have any ability to do this today, though.

Anyway, because we use the "arch default unwind plan" for a function that hasn't saved the caller's ebp and copied the stack pointer into ebp itself to indicate the start of its stack frame, ebp still has main()'s stack frame value. So when the arch default unwind plan says "look at ebp to find the caller's pc address", we're essentially skipping over main().

You can always spot an unwind algorithm that makes this assumption because they have the same "skip-one-frame" behavior when the 0th frame is unknown code and the unwinder skips the 1st frame. It's a common behavior in sampling type programs.

One example of where the unwinder is extra smart is when the pc value is 0. When the program jumps through a null function pointer, we end up at address 0. The simplistic behavior would be to use the arch default unwind plan which would skip the 1st frame -- the one that actually jumped through the null pointer. Very unhelpful. The unwinder has code specifically to spot a pc value of 0 for the 0th frame and uses the ABI::CreateFunctionEntryUnwindPlan unwindplan instead of the ABI::CreateDefaultUnwindPlan so we don't skip that critical frame #1.

I know it's noisy but it is MUCH easier to inline the unwind logging in the console with the commands you're typing than to have it in a separate logfile. The critical unwind failure is here:

th1/fr0 initialized frame current pc is 0x1190a41 cfa is 0x114fc04 using i386 default unwind plan UnwindPlan
th1/fr0 with pc value of 0x1190a41, no symbol/function name is known.
th1/fr0 0x0000000001190a41: CFA=ebp +8 => esp=CFA+0 ebp=[CFA-8] eip=[CFA-4]

th1/fr0 CFA is 0x114fc04: Register ebp (6) contents are 0x114fbfc, offset is 8
th1/fr0 initialized frame current pc is 0x1190a41 cfa is 0x114fc04 using i386 default unwind plan UnwindPlan
th1/fr0 supplying caller's saved eip (8)'s location using i386 default unwind plan UnwindPlan
th1/fr0 supplying caller's register eip (8) from the stack, saved at CFA plus offset -4 [saved at 0x114fc00]
th1/fr1 pc = 0x75107c04
th1/fr1 with pc value of 0x75107c04, symbol name is 'BaseThreadInitThunk'

We have a pc of 0x1190a41. We fall back to using the i386 default unwind plan. The unwind rule is "CFA=ebp +8 => esp=CFA+0 ebp=[CFA-8] eip=[CFA-4]". This says that we can find the caller's return address by dereferencing ebp+4 (the "Canonical Frame Address" is ebp+8 and the saved eip is at the CFA address - 4). Notice how the CFA is set in terms of ebp but your printf() hasn't set up ebp yet. That's because the unwinder is using the wrong unwind rule.

A really helpful command with unwind questions is to do "image show-unwind" with -n or -a. It shows all of the different unwind rules lldb knows about for that function. I don't know if it works for a raw address like your printf() here; probably not. But you can tell from the unwind log output that lldb used the arch default unwind plan and that's the same for all functions.

J

One small caveat.

One small caveat.

I think in our previous discussions you argued that lldb should *know* that it just single instruction stepped into a new function. If that knowledge could be transmitted down to the unwinder, we could use the ABI::CreateFunctionEntryUnwindPlan unwind plan instead of the ABI::CreateDefaultUnwindPlan. The former describes how to unwind when we're sitting at the first instruction of a function. lldb doesn't have any ability to do this today, though.

This isn't as easy as I make it out to be. The ThreadPlan knows that it instruction stepped and (1) left the range of addresses we're stepping through, and (2) the StackID has changed (the stack pointer has changed).

This is getting quickly more into Jim's bailiwick than mine but you can see two possibilities right away - we stepped out of our original function or we stepped into a new function. If we assume an always-decreasing (on x86) stack pointer, we can disambiguate but there are people who do crazy things with discontiguous stacks that may not make that a safe assumption.

You really need to know in from the ABI who is responsible for changing the CFA. If you know the callee always does this on the way in & then out again, then if the PC moved out of the range of the current function but the CFA hasn't changed yet you're probably stepping IN and thus on the first instruction (unless you are stepping out of a frameless function...) And of course PC moved out of the range of the current function is also not reliable if you are stepping recursively. Again, you can generally get this right in the easy cases. But there are always exceptions...

I don't know how much knowledge the ThreadPlan has here, setting aside the issue of how to pass that knowledge down to the unwinder.

The ThreadPlans can capture whatever they need before each step. Right now they store the pc & StackID they were stepping from. But I'm not convinced that with just that and no help from the unwinder we can know with enough confidence what just happened to give useful hints.

Jim

I’m wondering how it even made it to 0x1190a41. From the first bt command, that address is higher up than main. The fact that it stops there when stepping over something in main suggests that it actually got all the way through the call to printf, then returned from main then stopped at the return address of main. Does that seem like the expected behavior?

I forgot to Cc the list with my reply. Resending in case anyone else is following along

I'm wondering how it even made it to 0x1190a41. From the first bt command, that address is higher up than main. The fact that it stops there when stepping over something in main suggests that it actually got all the way through the call to printf, then returned from main then stopped at the return address of main. Does that seem like the expected behavior?

Looking at the unwind logging output,

th1/fr0 with pc value of 0x11882be, function name is 'main'
th1/fr0 with pc value of 0x11882be, function name is 'main'
th1/fr0 with pc value of 0x11882c5, function name is 'main'
th1/fr0 with pc value of 0x11882cb, function name is 'main'
th1/fr0 with pc value of 0x11882cb, function name is 'main'
th1/fr0 with pc value of 0x1188000, function name is 'unsigned int fib(unsigned int)'
th1/fr0 with pc value of 0x11882d0, function name is 'main'
th1/fr0 with pc value of 0x11882d0, function name is 'main'
th1/fr0 with pc value of 0x11882d6, function name is 'main'
th1/fr0 with pc value of 0x11882dd, function name is 'main'
th1/fr0 with pc value of 0x11882dd, function name is 'main'
th1/fr0 with pc value of 0x11906d5, no symbol/function name is known.
th1/fr0 with pc value of 0x1190a41, no symbol/function name is known.
th1/fr0 with pc value of 0x1190a41, no symbol/function name is known.

it's instruction stepping through main. steps into fib(), continues out of fib(). At 0x11882dd that's when things start to be a problem. As soon as we're in 0x11906d5 lldb doesn't know how to backtrace correctly (to find that main() is the caller) so it can't do the same thing it did with fib(). Things aren't going to work correct after this - I thought lldb should stop stepping. We'd need to see the step log in addition to the unwind log to tell what's really going on. The unwind log tells you how lldb decided what it's backtrace looks like. The step log tells you what it decided to do based on that backtrace.

J