gdb: Better frame tracking for inline frames
authorAndrew Burgess <andrew.burgess@embecosm.com>
Mon, 11 Nov 2019 22:41:13 +0000 (22:41 +0000)
committerAndrew Burgess <andrew.burgess@embecosm.com>
Fri, 24 Jan 2020 23:44:16 +0000 (23:44 +0000)
commit7ffa82e1222fbe038dedf3531c655969fdda5c16
tree6b6ec5b3b54bf2547bc6e58df726847ce9f6fe6c
parent3d92a3e313a3dfd531d5e32bbd703a8a5b03293b
gdb: Better frame tracking for inline frames

This commit improves GDB's handling of inline functions when there are
more than one inline function in a stack, so for example if we have a
stack like:

   main -> aaa -> bbb -> ccc -> ddd

And aaa, bbb, and ccc are all inline within main GDB should (when
given sufficient debug information) be able to step from main through
aaa, bbb, and ccc.  Unfortunately, this currently doesn't work, here's
an example session:

  (gdb) start
  Temporary breakpoint 1 at 0x4003b0: file test.c, line 38.
  Starting program: /project/gdb/tests/inline/test

  Temporary breakpoint 1, main () at test.c:38
  38   global_var = 0;
  (gdb) step
  39   return aaa () + 1;
  (gdb) step
  aaa () at test.c:39
  39   return aaa () + 1;
  (gdb) step
  bbb () at test.c:39
  39   return aaa () + 1;
  (gdb) step
  ccc () at test.c:39
  39   return aaa () + 1;
  (gdb) step
  ddd () at test.c:32
  32   return global_var;
  (gdb) bt
  #0  ddd () at test.c:32
  #1  0x00000000004003c1 in ccc () at test.c:39
  #2  bbb () at test.c:26
  #3  aaa () at test.c:14
  #4  main () at test.c:39

Notice that once we get to line 39 in main, GDB keeps reporting line
39 in main as the location despite understanding that the inferior is
stepping through the nested inline functions with each use of step.

The problem is that as soon as the inferior stops we call
skip_inline_frames (from inline-frame.c) which calculates the
inferiors current state in relation to inline functions - it figures
out if we're in an inline function, and if we are counts how many
inline frames there are at the current location.

So, in our example above, when we step from line 38 in main to line 39
we stop at a location that is simultaneously in all of main, aaa, bbb,
and ccc.  The block structure reflects the order in which the
functions would be called, with ccc being the most inner block and
main being the most outer block.  When we stop GDB naturally finds the
block for ccc, however within skip_inline_frames we spot that bbb,
aaa, and main are super-blocks of the current location and that each
layer represents an inline function.  The skip_inline_frames then
records the depth of inline functions (3 in this case for aaa, bbb,
and ccc) and also the symbol of the outermost inline function (in this
case 'aaa' as main isn't an inline function, it just has things inline
within it).

Now GDB understands the stack to be main -> aaa -> bbb -> ccc,
however, the state initialised in skip_inline_frames starts off
indicating that we should hide 3 frames from the user, so we report
that we're in main at line 39.  The location of main, line 39 is
derived by asking the inline function state for the last symbol in the
stack (aaa in this case), and then asking for it's location - the
location of an inlined function symbol is its call site, so main, line
39 in this case.

If the user then asks GDB to step we don't actually move the inferior
at all, instead we spot that we are in an inline function stack,
lookup the inline state data, and reduce the skip depth by 1.  We then
report to the user that GDB has stopped.  GDB now understands that we
are in 'aaa'.  In order to get the precise location we again ask GDB
for the last symbol from the inline data structure, and we are again
told 'aaa', we then get the location from 'aaa', and report that we
are in main, line 39.

Hopefully it's clear what the mistake here is, once we've reduced the
inline skip depth we should not be using 'aaa' to compute the precise
location, instead we should be using 'bbb'.  That is what this patch
does.

Now, when we call skip_inline_frames instead of just recording the
last skipped symbol we now record all symbols in the inline frame
stack.  When we ask GDB for the last skipped symbol we return a symbol
based on how many frames we are skipping, not just the last know
symbol.

With this fix in place, the same session as above now looks much
better:

  (gdb) start
  Temporary breakpoint 1 at 0x4003b0: file test.c, line 38.
  Starting program: /project/gdb/tests/inline/test

  Temporary breakpoint 1, main () at test.c:38
  38   global_var = 0;
  (gdb) s
  39   return aaa () + 1;
  (gdb) s
  aaa () at test.c:14
  14   return bbb () + 1;
  (gdb) s
  bbb () at test.c:26
  26   return ccc () + 1;
  (gdb) s
  ccc () at test.c:20
  20   return ddd () + 1;
  (gdb) s
  ddd () at test.c:32
  32   return global_var;
  (gdb) bt
  #0  ddd () at test.c:32
  #1  0x00000000004003c1 in ccc () at test.c:20
  #2  bbb () at test.c:26
  #3  aaa () at test.c:14
  #4  main () at test.c:39

gdb/ChangeLog:

* frame.c (find_frame_sal): Move call to get_next_frame into more
inner scope.
* inline-frame.c (inilne_state) <inline_state>: Update argument
types.
(inilne_state) <skipped_symbol>: Rename to...
(inilne_state) <skipped_symbols>: ...this, and change to a vector.
(skip_inline_frames): Build vector of skipped symbols and use this
to reate the inline_state.
(inline_skipped_symbol): Add a comment and some assertions, fetch
skipped symbol from the list.

gdb/testsuite/ChangeLog:

* gdb.dwarf2/dw2-inline-many-frames.c: New file.
* gdb.dwarf2/dw2-inline-many-frames.exp: New file.

Change-Id: I99def5ffb44eb9e58cda4b449bf3d91ab0386c62
gdb/ChangeLog
gdb/frame.c
gdb/inline-frame.c
gdb/testsuite/ChangeLog
gdb/testsuite/gdb.dwarf2/dw2-inline-many-frames.c [new file with mode: 0644]
gdb/testsuite/gdb.dwarf2/dw2-inline-many-frames.exp [new file with mode: 0644]