[RFC] Gdb line table implementation tweak

Fred Fish
2002-02-13T00:28:51Z

The implementation of gdb's line number table can cause gdb to give
misleading results when some parts of a program are compiled with
debugging enabled and other parts are not. Here is an example:

$ cat Makefile
CXX = g++

all: p p.stabs p1.o.stabs

p: p1.o p2.o
$(CXX) -o p p1.o p2.o

p1.o: p1.s
$(CXX) -c p1.s

p2.o: p2.s
$(CXX) -c p2.s

p1.s: p1.cpp
$(CXX) -g -S p1.cpp

p2.s: p2.cpp
$(CXX) -S p2.cpp

p.stabs: p
objdump --stabs p >p.stabs

p1.o.stabs: p1.o
objdump --stabs p1.o >p1.o.stabs

clean:
rm -f p1.o p2.o p1.s p2.s p Makefile~ *.syms *.stabs

$ cat p1.cpp
#include <stdio.h>

class MainClass
{
public:
MainClass() {};
~MainClass() {};
virtual void main();
};

void MainClass::main()
{
}

int main(int argc, char** argv)
{
extern void subr (int);
subr (5);
}

$ cat p2.cpp
#include <stdio.h>

void subr (int x)
{
printf ("x = %d\n", x);
}
$

If we run make to build executable 'p', where p1.o is compiled with
debugging enabled and p2.o is compiled without debugging, gdb gets
confused about what file subr() is in. It thinks it is in p1.cpp,
when it is really in p2.cpp:

$make
g++ -g -S p1.cpp
g++ -c p1.s
g++ -S p2.cpp
g++ -c p2.s
g++ -o p p1.o p2.o
objdump --stabs p >p.stabs
objdump --stabs p1.o >p1.o.stabs
$ /usr/sourceware/bin/gdb p
GNU gdb 2002-02-12-cvs
Copyright 2002 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i686-pc-linux-gnu"...
(gdb) br *0x8048634
Breakpoint 1 at 0x8048634: file p1.cpp, line 19.
(gdb) x/i 0x8048634
0x8048634 <subr__Fi>: push %ebp
(gdb) run
Starting program: /cygnus/cases/106539/example5-linux/p

Breakpoint 1, 0x08048634 in subr () at p1.cpp:19
19 }
(gdb) bt
#0 0x08048634 in subr () at p1.cpp:19
#1 0x400b2306 in __libc_start_main (main=0x8048618 , argc=1, ubp_av=0xbfffeef4, init=0x8048474 <_init>, fini=0x80486f8 <_fini>, rtld_fini=0x4000d2dc <_dl_fini>,
stack_end=0xbfffeeec) at ../sysdeps/generic/libc-start.c:129
(gdb) quit
The program is running. Exit anyway? (y or n) y
$

Note in the above, setting the breakpoint at the first instruction of
subr() appears to put it at line 19 in p1.cpp. When the program is
run and gdb stops at subr(), which is actually in p2.cpp, it prints it
wrong again. And in the backtrace, gdb gets the right function, but
the wrong file and line number.

One way to fix this is to slightly change the line table such that it
allows gdb to know what ranges of PC's represent ranges for which line
number info is valid and which don't. An easy way to do that is to
use an entry with a line number of zero to mark ranges that have no
valid line number info.

For example, the line table for p1.cpp is:

Line table:

line 12 at 0x8048610
line 13 at 0x8048616
line 16 at 0x8048618
line 18 at 0x804861e
line 19 at 0x804862b
line 9 at 0x8048690
line 19 at 0x8048696
line 6 at 0x80486bc
line 7 at 0x80486cc

After applying the attached patch, gdb's internal representation of
the line table for p1.cpp changes to the following:

Line table:

line 12 at 0x8048610
line 13 at 0x8048616
line 0 at 0x8048618
line 16 at 0x8048618
line 18 at 0x804861e
line 19 at 0x804862b
line 0 at 0x8048632
line 9 at 0x8048690
line 19 at 0x8048696
line 0 at 0x80486bb
line 6 at 0x80486bc
line 0 at 0x80486ca
line 7 at 0x80486cc
line 0 at 0x80486f5

And rerunning gdb on the test case produces:

$ /tmp/gdb p
GNU gdb 2002-02-12-cvs
Copyright 2002 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i686-pc-linux-gnu"...
(gdb) br *0x8048634
Breakpoint 1 at 0x8048634
(gdb) x/i 0x8048634
0x8048634 <subr__Fi>: push %ebp
(gdb) run
Starting program: /cygnus/cases/106539/example5-linux/p

Breakpoint 1, 0x08048634 in subr ()
(gdb) bt
#0 0x08048634 in subr ()
#1 0x400b2306 in __libc_start_main (main=0x8048618 , argc=1, ubp_av=0xbfffe674, init=0x8048474 <_init>, fini=0x80486f8 <_fini>, rtld_fini=0x4000d2dc <_dl_fini>,
stack_end=0xbfffe66c) at ../sysdeps/generic/libc-start.c:129
(gdb) quit
The program is running. Exit anyway? (y or n) y
$

It might be a little easier to understand how the line table describes
the entire range of PC's from it's lowest to highest if we match up
the new line table entries with a disassembly produced by gdb. Note
in the following that now gdb knows which parts actually come from
p1.cpp (a range starting with a nonzero line number) and which parts
have no valid line info (a range starting with a zero line number):

line 12 at 0x8048610 0x8048610 <main__9MainClass>: push %ebp
0x8048611 <main__9MainClass+1>: mov %esp,%ebp
0x8048613 <main__9MainClass+3>: mov 0x8(%ebp),%eax
line 13 at 0x8048616 0x8048616 <main__9MainClass+6>: pop %ebp
0x8048617 <main__9MainClass+7>: ret
line 0 at 0x8048618
line 16 at 0x8048618 0x8048618 : push %ebp
0x8048619 <main+1>: mov %esp,%ebp
0x804861b <main+3>: sub $0x8,%esp

line 18 at 0x804861e 0x804861e <main+6>: sub $0xc,%esp
0x8048621 <main+9>: push $0x5
0x8048623 <main+11>: call 0x8048634 <subr__Fi>
0x8048628 <main+16>: add $0x10,%esp

line 19 at 0x804862b 0x804862b <main+19>: mov $0x0,%eax
0x8048630 <main+24>: leave
0x8048631 <main+25>: ret

line 0 at 0x8048632 0x8048632 <main+26>: mov %esi,%esi
0x8048634 <subr__Fi>: push %ebp
0x8048635 <subr__Fi+1>: mov %esp,%ebp
0x8048637 <subr__Fi+3>: sub $0x8,%esp
0x804863a <subr__Fi+6>: sub $0x8,%esp
0x804863d <subr__Fi+9>: pushl 0x8(%ebp)
0x8048640 <subr__Fi+12>: push $0x804872b
0x8048645 <subr__Fi+17>: call 0x80484cc <printf>
0x804864a <subr__Fi+22>: add $0x10,%esp
0x804864d <subr__Fi+25>: leave
0x804864e <subr__Fi+26>: ret
0x804864f <subr__Fi+27>: nop
0x8048650 <__do_global_ctors_aux>: push %ebp
0x8048651 <__do_global_ctors_aux+1>: mov %esp,%ebp
0x8048653 <__do_global_ctors_aux+3>: push %ebx
0x8048654 <__do_global_ctors_aux+4>: sub $0x4,%esp
0x8048657 <__do_global_ctors_aux+7>: mov 0x804978c,%eax
0x804865c <__do_global_ctors_aux+12>: mov $0x804978c,%ebx
0x8048661 <__do_global_ctors_aux+17>: cmp $0xffffffff,%eax
0x8048664 <__do_global_ctors_aux+20>: je 0x804867c <__do_global_ctors_aux+44>
0x8048666 <__do_global_ctors_aux+22>: lea 0x0(%esi),%esi
0x8048669 <__do_global_ctors_aux+25>: lea 0x0(%edi,1),%edi
0x8048670 <__do_global_ctors_aux+32>: sub $0x4,%ebx
0x8048673 <__do_global_ctors_aux+35>: call *%eax
0x8048675 <__do_global_ctors_aux+37>: mov (%ebx),%eax
0x8048677 <__do_global_ctors_aux+39>: cmp $0xffffffff,%eax
0x804867a <__do_global_ctors_aux+42>: jne 0x8048670 <__do_global_ctors_aux+32>
0x804867c <__do_global_ctors_aux+44>: pop %eax
0x804867d <__do_global_ctors_aux+45>: pop %ebx
0x804867e <__do_global_ctors_aux+46>: pop %ebp
0x804867f <__do_global_ctors_aux+47>: ret
0x8048680 <init_dummy>: push %ebp
0x8048681 <init_dummy+1>: mov %esp,%ebp
0x8048683 <init_dummy+3>: sub $0x8,%esp
0x8048686 <init_dummy+6>: mov %ebp,%esp
0x8048688 <init_dummy+8>: pop %ebp
0x8048689 <init_dummy+9>: ret
0x804868a <init_dummy+10>: lea 0x0(%esi),%esi

line 9 at 0x8048690 0x8048690 <__tf9MainClass>: push %ebp
0x8048691 <__tf9MainClass+1>: mov %esp,%ebp
0x8048693 <__tf9MainClass+3>: sub $0x8,%esp

line 19 at 0x8048696 0x8048696 <__tf9MainClass+6>: cmpl $0x0,0x80498b8
0x804869d <__tf9MainClass+13>: jne 0x80486b4 <__tf9MainClass+36>
0x804869f <__tf9MainClass+15>: sub $0x8,%esp
0x80486a2 <__tf9MainClass+18>: push $0x8048720
0x80486a7 <__tf9MainClass+23>: push $0x80498b8
0x80486ac <__tf9MainClass+28>: call 0x804849c <__rtti_user>
0x80486b1 <__tf9MainClass+33>: add $0x10,%esp
0x80486b4 <__tf9MainClass+36>: mov $0x80498b8,%eax
0x80486b9 <__tf9MainClass+41>: leave
0x80486ba <__tf9MainClass+42>: ret

line 0 at 0x80486bb 0x80486bb <__tf9MainClass+43>: nop

line 6 at 0x80486bc 0x80486bc <__9MainClass>: push %ebp
0x80486bd <__9MainClass+1>: mov %esp,%ebp
0x80486bf <__9MainClass+3>: mov 0x8(%ebp),%eax
0x80486c2 <__9MainClass+6>: movl $0x8049748,(%eax)
0x80486c8 <__9MainClass+12>: pop %ebp
0x80486c9 <__9MainClass+13>: ret

line 0 at 0x80486ca 0x80486ca <__9MainClass+14>: mov %esi,%esi

line 7 at 0x80486cc 0x80486cc <_._9MainClass>: push %ebp
0x80486cd <_._9MainClass+1>: mov %esp,%ebp
0x80486cf <_._9MainClass+3>: sub $0x8,%esp
0x80486d2 <_._9MainClass+6>: mov 0xc(%ebp),%eax
0x80486d5 <_._9MainClass+9>: mov 0x8(%ebp),%edx
0x80486d8 <_._9MainClass+12>: movl $0x8049748,(%edx)
0x80486de <_._9MainClass+18>: and $0x1,%eax
0x80486e1 <_._9MainClass+21>: test %al,%al
0x80486e3 <_._9MainClass+23>: je 0x80486f3 <_._9MainClass+39>
0x80486e5 <_._9MainClass+25>: sub $0xc,%esp
0x80486e8 <_._9MainClass+28>: pushl 0x8(%ebp)
0x80486eb <_._9MainClass+31>: call 0x80484ec <__builtin_delete>
0x80486f0 <_._9MainClass+36>: add $0x10,%esp
0x80486f3 <_._9MainClass+39>: leave
0x80486f4 <_._9MainClass+40>: ret

line 0 at 0x80486f5 0x80486f5 <_._9MainClass+41>: lea 0x0(%esi),%esi

The patch to make this work for stabs is very simple and supplied
below for discussion purposes. I presume it would be fairly easy to
also change the other debug info readers to do the same thing.
Obviously someday we would like to have gdb take full advantage of
more expressive formats like DWARF, but for now this patch seems to
have substantial advantages.

I did run before and after testing with the gdb testsuite and it
didn't show any regressions, or improvements for that matter, but that
is to be expected since we don't actually test for functionality with
mixed levels of debugging information.

-Fred

Index: ChangeLog
===================================================================
RCS file: /cvs/src/src/gdb/ChangeLog,v
retrieving revision 1.2184
diff -c -p -r1.2184 ChangeLog
*** ChangeLog 2002/02/12 00:59:27 1.2184
--- ChangeLog 2002/02/13 00:22:47
***************
*** 1,3 ****
--- 1,14 ----
+ 2002-02-11 Fred Fish <fnf@redhat.com>
+
+ * dbxread.c (process_one_symbol): When finding an N_FUN symbol
+ that marks the end of the range of a function, enter a line number
+ entry that has a line number of zero and a PC offset that matches
+ the end of the function. This starts a range of PC's for which no
+ line number information is known.
+ * symtab.c (find_pc_sect_line): If our best fit is in a range of
+ PC's for which no line number info is found (line number is zero)
+ then we didn't find any valid line information.
+
2002-02-11 Richard Earnshaw <rearnsha@arm.com>

* arm-linux-nat.c: Really include arm-tdep.h.
Index: dbxread.c
===================================================================
RCS file: /cvs/src/src/gdb/dbxread.c,v
retrieving revision 1.29
diff -c -p -r1.29 dbxread.c
*** dbxread.c 2002/02/04 11:55:34 1.29
--- dbxread.c 2002/02/13 00:22:50
*************** process_one_symbol (int type, int desc,
*** 2741,2746 ****
--- 2741,2747 ----
{
/* This N_FUN marks the end of a function. This closes off the
current block. */
+ record_line (current_subfile, 0, function_start_offset + valu);
within_function = 0;
new = pop_context ();

Index: symtab.c
===================================================================
RCS file: /cvs/src/src/gdb/symtab.c,v
retrieving revision 1.54
diff -c -p -r1.54 symtab.c
*** symtab.c 2002/02/11 03:21:53 1.54
--- symtab.c 2002/02/13 00:22:54
*************** find_pc_sect_line (CORE_ADDR pc, struct
*** 1823,1828 ****
--- 1823,1835 ----
val.end = alt->pc;
}
}
+ else if (best->line == 0)
+ {
+ /* If our best fit is in a range of PC's for which no line
+ number info is available (line number is zero) then we didn't
+ find any valid line information. */
+ val.pc = pc;
+ }
else
{
val.symtab = best_symtab;