[RFA] Fix "break foo" when `foo's prologue ends before line table

Eli Zaretskii
2009-05-09T14:26:31Z

With the following test program (tchset.c)

#include <stdio.h>
#include <libcharset.h>

int main (void)
{
const char *cset = locale_charset ();

if (cset)
printf ("locale_charset: `%s'\n", cset);
else
printf ("locale_charset: NULL\n");

return 0;
}

and the DJGPP build of a recent snapshot, trying to set a breakpoint
in `main' makes GDB think `main' has no source line info, if I compile
the program with COFF debug info ("gcc -gcoff -o tchset44c.exe tchset.c").

Observe:

D:\usr\djgpp\gnu\gdb-68.410\gdb>.\gdb.exe --nx ./tchset44c.exe
GNU gdb (GDB) 6.8.50.20090410
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=i586-pc-msdosdjgpp --target=djgpp".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
(gdb) break main
Breakpoint 1 at 0x1732
(gdb)

With GDB 6.1, it does work:

GNU gdb 6.1
Copyright 2004 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 "--host=i386-pc-msdosdjgpp --target=djgpp"...
(gdb) break main
Breakpoint 1 at 0x1748: file tchset.c, line 6.

The reason is this change:

2006-11-28 Daniel Jacobowitz <dan@codesourcery.com>

* symtab.c (find_pc_sect_line): Do not return a line before
the start of a symtab.

(No, I'm not suggesting to revert that change. I want to show my
analysis of this problem and suggest a solution.)

Using "maint print symbols", I see this:

Symtab for file tchset.c
Read from object file d:/usr/djgpp/gnu/gdb-68.410/gdb/./tchset44c.exe (0x43be20)
Language: c

Line table:

line 6 at 0x1748
line 8 at 0x1750
line 9 at 0x1756
line 11 at 0x176b
line 13 at 0x177b
line 14 at 0x1780

Blockvector:

block #000, object at 0x4403fc, 1 syms/buckets in 0x1700..0x1782
int main(); block object 0x440394, 0x172c..0x1782 section .text
block #001, object at 0x4403c4 under 0x4403fc, 2 syms/buckets in 0x1700..0x1782
[...]
block #002, object at 0x440394 under 0x4403c4, 0 syms/buckets in 0x172c..0x1782, function main

Note that the lineinfo table for the program starts at line 6 at PC
0x1748, whereas the function `main' begins at PC 0x172c.

When I type "break main", we eventually call find_function_start_sal,
which skips the prologue, and then calls find_pc_sect_line to find the
sal for the PC after the prologue. But skipping the prologue in this
cases gets us to PC 0x1732, which is still outside the lineinfo
table. So find_pc_sect_line fails to find a suitable source line,
returns a sal without a symtab, and GDB then thinks `main' is a
function with no source line information.

Here's the disassembly of `main', in case someone thinks that
skip_prologue function didn't do a good job (I think it did a
reasonable job, but I'm far from being an expert on prologue
analysis):

Dump of assembler code for function main:
0x0000172c <main+0>: push %ebp
0x0000172d <main+1>: mov %esp,%ebp
0x0000172f <main+3>: sub $0x8,%esp
0x00001732 <main+6>: and $0xfffffff0,%esp
0x00001735 <main+9>: mov $0x0,%eax
0x0000173a <main+14>: add $0xf,%eax
0x0000173d <main+17>: add $0xf,%eax
0x00001740 <main+20>: shr $0x4,%eax
0x00001743 <main+23>: shl $0x4,%eax
0x00001746 <main+26>: sub %eax,%esp
0x00001748 <main+28>: call 0x17e0 <locale_charset>
0x0000174d <main+33>: mov %eax,0xfffffffc(%ebp)
0x00001750 <main+36>: cmpl $0x0,0xfffffffc(%ebp)
0x00001754 <main+40>: je 0x176b <main+63>
0x00001756 <main+42>: sub $0x8,%esp
0x00001759 <main+45>: pushl 0xfffffffc(%ebp)
0x0000175c <main+48>: push $0x1700
0x00001761 <main+53>: call 0x3950 <printf>
0x00001766 <main+58>: add $0x10,%esp
0x00001769 <main+61>: jmp 0x177b <main+79>
0x0000176b <main+63>: sub $0xc,%esp
0x0000176e <main+66>: push $0x1716
0x00001773 <main+71>: call 0x3950 <printf>
0x00001778 <main+76>: add $0x10,%esp
0x0000177b <main+79>: mov $0x0,%eax
0x00001780 <main+84>: leave
0x00001781 <main+85>: ret

If I compile the same program with -gdwarf-2, the problem does not
happen because the lineinfo table now includes one more entry, for
line 5 of the file. But I cannot deprecate usage of COFF debug info
in DJGPP, because that is the only way to build the DJGPP port of
Emacs (the code in Emacs's unexec.c was never updated to handle
DWARF-2, and probably never will).

My suggestion for solving this is in the patch below. The idea is
that we make one more attempt to find the first line of the function's
body, using the lineinfo table. We loop over all symtabs for the
function's file, looking for an entry in a lineinfo table whose PC is
in the range [FUNC_START..FUNC_END] and whose line number is the
smallest. FUNC_START and FUNC_END are obtained via a call to
find_pc_partial_function.

Is this idea reasonable? Is the patch okay to commit?

2009-05-09 Eli Zaretskii <eliz@gnu.org>

* symtab.c (skip_prologue_using_lineinfo): New function.
(find_function_start_sal): Use it to get to the first line of
function's body that has an entry in the lineinfo table.

--- symtab.c~0 2009-04-01 02:21:07.000000000 +0300
+++ symtab.c 2009-05-09 16:40:36.442027000 +0300
@@ -2599,6 +2599,68 @@
return pc;
}

+/* Given a function start address FUNC_ADDR and SYMTAB, find the first
+ address for that function that has an entry in any line info table
+ that comes from any symtab for the same file as SYMTAB. If such an
+ entry cannot be found, return FUNC_ADDR unaltered. */
+CORE_ADDR
+skip_prologue_using_lineinfo (CORE_ADDR func_addr, struct symtab *symtab)
+{
+ CORE_ADDR func_start, func_end;
+ struct objfile *objfile;
+ struct partial_symtab *p;
+ struct symtab *s;
+ int best_lineno = 0;
+ CORE_ADDR best_pc = func_addr;
+
+ /* Get the range for the function's PC values, or give up if we
+ cannot, for some reason. */
+ if (!find_pc_partial_function (func_addr, NULL, &func_start, &func_end))
+ return func_addr;
+
+ ALL_PSYMTABS (objfile, p)
+ {
+ if (FILENAME_CMP (symtab->filename, p->filename) != 0)
+ continue;
+ PSYMTAB_TO_SYMTAB (p);
+ }
+
+ /* Loop over all symtabs for the function's file, looking for an
+ entry in a lineinfo table whose PC is in the range
+ [FUNC_START..FUNC_END] and whose line number is the smallest. */
+ ALL_SYMTABS (objfile, s)
+ {
+ struct linetable *l;
+ int ind, i, len;
+
+ if (FILENAME_CMP (symtab->filename, s->filename) != 0)
+ continue;
+ l = LINETABLE (s);
+ if (l == NULL)
+ continue;
+
+ len = l->nitems;
+ for (i = 0; i < len; i++)
+ {
+ struct linetable_entry *item = &(l->item[i]);
+
+ /* Don't use line numbers of zero, they mark special entries
+ in the table. See the commentary on symtab.h before the
+ definition of struct linetable. */
+ if (item->line > 0
+ && func_start <= item->pc && item->pc <= func_end)
+ {
+ if (!best_lineno || item->line < best_lineno)
+ {
+ best_lineno = item->line;
+ best_pc = item->pc;
+ }
+ }
+ }
+ }
+ return best_pc;
+}
+
/* Given a function symbol SYM, find the symtab and line for the start
of the function.
If the argument FUNFIRSTLINE is nonzero, we want the first line
@@ -2649,6 +2711,15 @@
sal = find_pc_sect_line (pc, SYMBOL_OBJ_SECTION (sym), 0);
}

+ /* If we still don't have a valid source line, try to find the first
+ line in the lineinfo table that belongs to the same function. */
+ if (funfirstline && sal.symtab == NULL)
+ {
+ pc = skip_prologue_using_lineinfo (pc, SYMBOL_SYMTAB (sym));
+ /* Recalculate the line number. */
+ sal = find_pc_sect_line (pc, SYMBOL_OBJ_SECTION (sym), 0);
+ }
+
sal.pc = pc;

return sal;