binutils-gdb/gdb/testsuite/gdb.dwarf2/dw2-inline-many-frames.c
Andrew Burgess 7ffa82e122 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
2020-01-24 23:44:16 +00:00

159 lines
4.4 KiB
C

/* Copyright 2019-2020 Free Software Foundation, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
/* This test sets up a call stack that looks like this:
#11 #10 #9 #8 #7 #6 #5 #4 #3 #2 #1 #0
main -> aaa -> bbb -> ccc -> ddd -> eee -> fff -> ggg -> hhh -> iii -> jjj -> kkk
\_______________________/ \________/ \______________________/ \________/
Inline sequence #1 Normal Inline sequence #2 Normal
We use the 'start' command to move into main, after that we 'step'
through each function until we are in kkk. We then use the 'up' command
to look back at each from to main.
The test checks that we can handle and step through sequences of more
than one inline frame (so 'main .... ccc', and 'fff .... iii'), and also
that we can move around in a stack that contains more than one disjoint
sequence of inline frames.
The order of the functions in this file is deliberately mixed up so that
the line numbers are not "all ascending" or "all descending" in the line
table. */
#define INLINE_FUNCTION __attribute__ ((always_inline)) static inline
#define NON_INLINE_FUNCTION __attribute__ ((noinline))
volatile int global_var = 0;
INLINE_FUNCTION int aaa ();
INLINE_FUNCTION int bbb ();
INLINE_FUNCTION int ccc ();
NON_INLINE_FUNCTION int ddd ();
NON_INLINE_FUNCTION int eee ();
NON_INLINE_FUNCTION int fff ();
INLINE_FUNCTION int ggg ();
INLINE_FUNCTION int hhh ();
INLINE_FUNCTION int iii ();
NON_INLINE_FUNCTION int jjj ();
NON_INLINE_FUNCTION int kkk ();
INLINE_FUNCTION int
aaa ()
{ /* aaa prologue */
asm ("aaa_label: .globl aaa_label");
return bbb () + 1; /* aaa return */
} /* aaa end */
NON_INLINE_FUNCTION int
jjj ()
{ /* jjj prologue */
int ans;
asm ("jjj_label: .globl jjj_label");
ans = kkk () + 1; /* jjj return */
asm ("jjj_label2: .globl jjj_label2");
return ans;
} /* jjj end */
INLINE_FUNCTION int
ggg ()
{ /* ggg prologue */
asm ("ggg_label: .globl ggg_label");
return hhh () + 1; /* ggg return */
} /* ggg end */
INLINE_FUNCTION int
ccc ()
{ /* ccc prologue */
asm ("ccc_label: .globl ccc_label");
return ddd () + 1; /* ccc return */
} /* ccc end */
NON_INLINE_FUNCTION int
fff ()
{ /* fff prologue */
int ans;
asm ("fff_label: .globl fff_label");
ans = ggg () + 1; /* fff return */
asm ("fff_label2: .globl fff_label2");
return ans;
} /* fff end */
NON_INLINE_FUNCTION int
kkk ()
{ /* kkk prologue */
asm ("kkk_label: .globl kkk_label");
return global_var; /* kkk return */
} /* kkk end */
INLINE_FUNCTION int
bbb ()
{ /* bbb prologue */
asm ("bbb_label: .globl bbb_label");
return ccc () + 1; /* bbb return */
} /* bbb end */
INLINE_FUNCTION int
hhh ()
{ /* hhh prologue */
asm ("hh_label: .globl hhh_label");
return iii () + 1; /* hhh return */
} /* hhh end */
int
main ()
{ /* main prologue */
int ans;
asm ("main_label: .globl main_label");
global_var = 0; /* main set global_var */
asm ("main_label2: .globl main_label2");
ans = aaa () + 1; /* main call aaa */
asm ("main_label3: .globl main_label3");
return ans;
} /* main end */
NON_INLINE_FUNCTION int
ddd ()
{ /* ddd prologue */
int ans;
asm ("ddd_label: .globl ddd_label");
ans = eee () + 1; /* ddd return */
asm ("ddd_label2: .globl ddd_label2");
return ans;
} /* ddd end */
INLINE_FUNCTION int
iii ()
{ /* iii prologue */
int ans;
asm ("iii_label: .globl iii_label");
ans = jjj () + 1; /* iii return */
asm ("iii_label2: .globl iii_label2");
return ans;
} /* iii end */
NON_INLINE_FUNCTION int
eee ()
{ /* eee prologue */
int ans;
asm ("eee_label: .globl eee_label");
ans = fff () + 1; /* eee return */
asm ("eee_label2: .globl eee_label2");
return ans;
} /* eee end */