mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2025-01-18 12:24:38 +08:00
8a2fb168c9
This commit aims to fix an issue where GDB would report the wrong line for frames other than #0 if a previous frame had just left an inline function. Consider this example which is compiled at -Og: volatile int global = 0; static inline int bar (void) { asm (""); return 1; } static void foo (int count) { global += count; } int main (void) { foo (bar ()); return 0; } Used in this GDB session: (gdb) break foo Breakpoint 1 at 0x401106: file test.c, line 6. (gdb) run Starting program: /tmp/inline-bt/test.x Breakpoint 1, foo (count=count@entry=1) at test.c:6 6 { global += count; } (gdb) frame 1 #1 0x0000000000401121 in main () at test.c:3 3 static inline int bar (void) { asm (""); return 1; } Notice that GDB incorrectly reports frame #1 as being at line 3 when it should really be reporting this line: foo (bar ()); The cause of this problem is in find_pc_sect_line (symtab.c). This function is passed a PC for which GDB must find the symtab_and_line information. The function can be called in two modes based on the NOTCURRENT argument. When NOTCURRENT is false then we are looking for information about the current PC, i.e. the PC at which the inferior is currently stopped at. When NOTCURRENT is true we are looking for information about a PC that it not the current PC, but is instead the PC for a previous frame. The interesting thing in this case is that the PC passed in will be the address after the address we actually want to lookup information for, this is because as we unwind the program counter from frame #0 what we get is the return address in frame #1. The return address is often (or sometimes) on the line after the calling line, and so in find_pc_sect_line, when NOTCURRENT is true, we subtract 1 from PC and then proceed as normal looking for information about this new PC value. Now lets look at the x86-64 disassembly for 'main' from the above example. The location marker (=>) represents the return address in 'main' after calling 'foo': (gdb) run Starting program: /tmp/inline-bt/test.x Breakpoint 1, foo (count=count@entry=1) at test.c:6 6 { global += count; } #0 foo (count=count@entry=1) at test.c:6 #1 0x000000000040111f in main () at test.c:3 (gdb) up #1 0x000000000040111f in main () at test.c:3 3 static inline int bar (void) { asm (""); return 1; } (gdb) disassemble Dump of assembler code for function main: 0x0000000000401115 <+0>: mov $0x1,%edi 0x000000000040111a <+5>: call 0x401106 <foo> => 0x000000000040111f <+10>: mov $0x0,%eax 0x0000000000401124 <+15>: ret End of assembler dump. And the corresponding line table: (gdb) maintenance info line-table objfile: /tmp/inline-bt/test.x ((struct objfile *) 0x59405a0) compunit_symtab: test.c ((struct compunit_symtab *) 0x53ad320) symtab: /tmp/inline-bt/test.c ((struct symtab *) 0x53ad3a0) linetable: ((struct linetable *) 0x53adc90): INDEX LINE REL-ADDRESS UNREL-ADDRESS IS-STMT PROLOGUE-END EPILOGUE-BEGIN 0 6 0x0000000000401106 0x0000000000401106 Y 1 6 0x0000000000401106 0x0000000000401106 Y 2 6 0x0000000000401106 0x0000000000401106 3 6 0x0000000000401114 0x0000000000401114 4 9 0x0000000000401115 0x0000000000401115 Y 5 10 0x0000000000401115 0x0000000000401115 Y 6 3 0x0000000000401115 0x0000000000401115 Y 7 3 0x0000000000401115 0x0000000000401115 Y 8 3 0x0000000000401115 0x0000000000401115 Y 9 10 0x0000000000401115 0x0000000000401115 10 11 0x000000000040111f 0x000000000040111f Y 11 12 0x000000000040111f 0x000000000040111f 12 END 0x0000000000401125 0x0000000000401125 Y When looking for the line information of frame #1 we start with the return address 0x40111f, however, as this is not the current program counter value we subtract one and look for line information for 0x40111e. We will find the entry at index 9, this is the last entry with an address less than the address we're looking for, the next entry has an address greater than the one we're looking for. The entry at index 9 is for line 10 which is the correct line, but GDB reports line 3, so what's going on? Having found a matching entry GDB checks to see if the entry is marked as is-stmt (is statement). In our case index 9 (line 10) is not a statement, and so GDB looks backwards for entries at the same address, if any of these are marked is-stmt then GDB will use the last of these instead. In our case the previous entry at index 8 is marked is-stmt, and so GDB uses that. The entry at index 8 is for line 3, and that is why GDB reports the wrong line. So why perform the backward is-stmt check? When NOTCURRENT is false (not our case) the backward scan makes sense. If the inferior has just stopped at some new location, and we want to report that location to the user, then it is better (I think) to select an is-stmt entry. In this way we will report a line number for a line which the inferior is just about to start executing, and non of the side effects of that line have yet taken place. The line GDB prints will correspond with the reported line, and if the user queries the inferior state, the inferior should (assuming the compiler emitted correct is-stmt markers) correspond to the line in question having not yet been started. However, in our case NOTCURRENT is true. We're looking back to previous frames that are currently in-progress. If upon return to the previous frame we are about to execute the next line then (is seems to me) that this indicates we must be performing the very last action from the previous line. As such, looking back through the line table in order to report a line that has not yet started is the wrong thing to do. We really want to report the very last line table entry for the previous address as this is (I think) most likely to represent the previous line that is just about to complete. Further, in the NOTCURRENT case, we should care less about reporting an is-stmt line. When a user looks back to a previous frame I don't think they expect the line being reported to have not yet started. In fact I think the expectation is the reverse ... after all, the previous line must have executed enough to call the current frame. So my proposal is that the backward scan of the line table looking for an is-stmt entry should not be performed when NOTCURRENT is true. In the case above this means we will report the entry at index 9, which is for line 10, which is correct. For testing this commit I have: 1. Extended the existing gdb.opt/inline-bt.exp test. I've extended the source code to include a test similar to the example above. I have also extended the script so that the test is compiled at a variety of optimisation levels (O0, Og, O1, O2). 2. Added a new DWARF assembler test which hard codes a line table similar to the example given above. My hope is that even if test case (1) changes (due to compiler changes) this test will continue to test the specific case I'm interested in. I have tested the gdb.opt/inline-bt.exp test with gcc versions 8.4.0, 9.3.1, 10.5.0, 11.5.0, 12.2.0, and 14.2.0, in each case the test will fail (with the expected error) without this patch applied, and will pass with this patch applied. I was inspired to write this patch while reviewing these patches: https://inbox.sourceware.org/gdb-patches/AS8P193MB1285C58F6F09502252CEC16FE4DF2@AS8P193MB1285.EURP193.PROD.OUTLOOK.COM https://inbox.sourceware.org/gdb-patches/AS8P193MB12855708DFF59A5309F5B19EE4DF2@AS8P193MB1285.EURP193.PROD.OUTLOOK.COM though this patch only covers one of the issues addressed by these patches, and the approach taken is quite different. Still, those patches are worth reading for the history of this fix. Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=25987 |
||
---|---|---|
bfd | ||
binutils | ||
config | ||
contrib | ||
cpu | ||
elfcpp | ||
etc | ||
gas | ||
gdb | ||
gdbserver | ||
gdbsupport | ||
gnulib | ||
gold | ||
gprof | ||
gprofng | ||
include | ||
ld | ||
libbacktrace | ||
libctf | ||
libdecnumber | ||
libiberty | ||
libsframe | ||
opcodes | ||
readline | ||
sim | ||
texinfo | ||
zlib | ||
.cvsignore | ||
.editorconfig | ||
.gitattributes | ||
.gitignore | ||
.pre-commit-config.yaml | ||
ar-lib | ||
ChangeLog | ||
compile | ||
config-ml.in | ||
config.guess | ||
config.rpath | ||
config.sub | ||
configure | ||
configure.ac | ||
COPYING | ||
COPYING3 | ||
COPYING3.LIB | ||
COPYING.LIB | ||
COPYING.LIBGLOSS | ||
COPYING.NEWLIB | ||
depcomp | ||
djunpack.bat | ||
install-sh | ||
libtool.m4 | ||
lt~obsolete.m4 | ||
ltgcc.m4 | ||
ltmain.sh | ||
ltoptions.m4 | ||
ltsugar.m4 | ||
ltversion.m4 | ||
MAINTAINERS | ||
Makefile.def | ||
Makefile.in | ||
Makefile.tpl | ||
makefile.vms | ||
missing | ||
mkdep | ||
mkinstalldirs | ||
move-if-change | ||
multilib.am | ||
README | ||
README-maintainer-mode | ||
SECURITY.txt | ||
setup.com | ||
src-release.sh | ||
symlink-tree | ||
test-driver | ||
ylwrap |
README for GNU development tools This directory contains various GNU compilers, assemblers, linkers, debuggers, etc., plus their support routines, definitions, and documentation. If you are receiving this as part of a GDB release, see the file gdb/README. If with a binutils release, see binutils/README; if with a libg++ release, see libg++/README, etc. That'll give you info about this package -- supported targets, how to use it, how to report bugs, etc. It is now possible to automatically configure and build a variety of tools with one command. To build all of the tools contained herein, run the ``configure'' script here, e.g.: ./configure make To install them (by default in /usr/local/bin, /usr/local/lib, etc), then do: make install (If the configure script can't determine your type of computer, give it the name as an argument, for instance ``./configure sun4''. You can use the script ``config.sub'' to test whether a name is recognized; if it is, config.sub translates it to a triplet specifying CPU, vendor, and OS.) If you have more than one compiler on your system, it is often best to explicitly set CC in the environment before running configure, and to also set CC when running make. For example (assuming sh/bash/ksh): CC=gcc ./configure make A similar example using csh: setenv CC gcc ./configure make Much of the code and documentation enclosed is copyright by the Free Software Foundation, Inc. See the file COPYING or COPYING.LIB in the various directories, for a description of the GNU General Public License terms under which you can copy the files. REPORTING BUGS: Again, see gdb/README, binutils/README, etc., for info on where and how to report problems.