btrace: preserve function level for unexpected returns
authorMarkus Metzger <markus.t.metzger@intel.com>
Tue, 19 Jan 2016 13:54:19 +0000 (14:54 +0100)
committerMarkus Metzger <markus.t.metzger@intel.com>
Fri, 28 Oct 2016 09:00:08 +0000 (11:00 +0200)
When encountering a return for which we have not seen a corresponding call, GDB
starts a new back trace from level -1, i.e. from the level of the first function
in the trace.

In the presence of trace gaps, this may cause some rather big jump.

    (gdb) record function-call-history /c 192, +8
    192                                           sbrk
    193                                             brk
    194                                               __x86.get_pc_thunk.bx
    195                                             brk
    196                                               __kernel_vsyscall
    197 [disabled]
    198                                               __kernel_vsyscall
    199         brk
    200       sbrk

This doesn't help to make things more clear.  Let's remain on the same level
instead.

    (gdb) record function-call-history /c 192, +8
    192       sbrk
    193         brk
    194           __x86.get_pc_thunk.bx
    195         brk
    196           __kernel_vsyscall
    197 [disabled]
    198           __kernel_vsyscall
    199         brk
    200       sbrk

In this case it will look like we were able to connect the trace parts across
the disabled gap.  We were not.  More work is required to achieve this.

In the general case, the function-call history for the two trace parts won't
match.  They may be off by a few levels or they may be entirely different.  All
this patch does is to preserve the indentation level of the record
function-call-history command.

The disabled gap is caused by a sysenter not returning to the next instruction.

    (gdb) record function-call-history /i 196, +1
    196     __kernel_vsyscall       inst 66515,66519
    (gdb) record instruction-history 66515
    66515      0xb7fdcbf8 <__kernel_vsyscall+0>:    push   %ecx
    66516      0xb7fdcbf9 <__kernel_vsyscall+1>:    push   %edx
    66517      0xb7fdcbfa <__kernel_vsyscall+2>:    push   %ebp
    66518      0xb7fdcbfb <__kernel_vsyscall+3>:    mov    %esp,%ebp
    66519      0xb7fdcbfd <__kernel_vsyscall+5>:    sysenter
    [disabled]
    66520      0xb7fdcc08 <__kernel_vsyscall+16>:   pop    %ebp
    66521      0xb7fdcc09 <__kernel_vsyscall+17>:   pop    %edx
    66522      0xb7fdcc0a <__kernel_vsyscall+18>:   pop    %ecx
    66523      0xb7fdcc0b <__kernel_vsyscall+19>:   ret
    66524      0xb7e8e09e <brk+30>: xchg   %ecx,%ebx
    (gdb) disassemble 0xb7fdcbf8, 0xb7fdcc0c
    Dump of assembler code from 0xb7fdcbf8 to 0xb7fdcc0c:
       0xb7fdcbf8 <__kernel_vsyscall+0>:    push   %ecx
       0xb7fdcbf9 <__kernel_vsyscall+1>:    push   %edx
       0xb7fdcbfa <__kernel_vsyscall+2>:    push   %ebp
       0xb7fdcbfb <__kernel_vsyscall+3>:    mov    %esp,%ebp
       0xb7fdcbfd <__kernel_vsyscall+5>:    sysenter
       0xb7fdcbff <__kernel_vsyscall+7>:    nop
       0xb7fdcc00 <__kernel_vsyscall+8>:    nop
       0xb7fdcc01 <__kernel_vsyscall+9>:    nop
       0xb7fdcc02 <__kernel_vsyscall+10>:   nop
       0xb7fdcc03 <__kernel_vsyscall+11>:   nop
       0xb7fdcc04 <__kernel_vsyscall+12>:   nop
       0xb7fdcc05 <__kernel_vsyscall+13>:   nop
       0xb7fdcc06 <__kernel_vsyscall+14>:   int    $0x80
       0xb7fdcc08 <__kernel_vsyscall+16>:   pop    %ebp
       0xb7fdcc09 <__kernel_vsyscall+17>:   pop    %edx
       0xb7fdcc0a <__kernel_vsyscall+18>:   pop    %ecx
       0xb7fdcc0b <__kernel_vsyscall+19>:   ret
    End of assembler dump.

I've seen this on 32-bit Fedora 23.  I have not investigated what causes this
and whether we can avoid the gap in the first place.  Let's first try to make
GDB handle such gaps more gracefully.

gdb/
* btrace.c (ftrace_new_return): Start from the previous function's level
if we can't find a matching call for a return.

gdb/ChangeLog
gdb/btrace.c

index 98e13ea9b6027f38ef96b88f65e77849fba507c5..2a601a9626d86ce1f8c9be68ba58da0160d81a06 100644 (file)
@@ -1,3 +1,8 @@
+2016-10-28  Markus Metzger  <markus.t.metzger@intel.com>
+
+       * btrace.c (ftrace_new_return): Start from the previous function's
+       level if we can't find a matching call for a return.
+
 2016-10-28  Markus Metzger  <markus.t.metzger@intel.com>
 
        * btrace.c (ftrace_update_function): Update tail call heuristic.
index 445f0a4c165d17534d6c808c6323811609e63fee..10b6db47ee6655c785825cc8a18b13d652cd5d17 100644 (file)
@@ -387,16 +387,12 @@ ftrace_new_return (struct btrace_function *prev,
          /* There is no call in PREV's back trace.  We assume that the
             branch trace did not include it.  */
 
-         /* Let's find the topmost call function - this skips tail calls.  */
+         /* Let's find the topmost function and add a new caller for it.
+            This should handle a series of initial tail calls.  */
          while (prev->up != NULL)
            prev = prev->up;
 
-         /* We maintain levels for a series of returns for which we have
-            not seen the calls.
-            We start at the preceding function's level in case this has
-            already been a return for which we have not seen the call.
-            We start at level 0 otherwise, to handle tail calls correctly.  */
-         bfun->level = std::min (0, prev->level) - 1;
+         bfun->level = prev->level - 1;
 
          /* Fix up the call stack for PREV.  */
          ftrace_fixup_caller (prev, bfun, BFUN_UP_LINKS_TO_RET);
@@ -406,8 +402,16 @@ ftrace_new_return (struct btrace_function *prev,
       else
        {
          /* There is a call in PREV's back trace to which we should have
-            returned.  Let's remain at this level.  */
-         bfun->level = prev->level;
+            returned but didn't.  Let's start a new, separate back trace
+            from PREV's level.  */
+         bfun->level = prev->level - 1;
+
+         /* We fix up the back trace for PREV but leave other function segments
+            on the same level as they are.
+            This should handle things like schedule () correctly where we're
+            switching contexts.  */
+         prev->up = bfun;
+         prev->flags = BFUN_UP_LINKS_TO_RET;
 
          ftrace_debug (bfun, "new return - unknown caller");
        }