determining whether page 0 (or low addresses in general) is executable

Doug Evans
2008-04-16T19:22:04Z

Hi. We're trying to come up with a workaround for an apparent gcc/linker issue
where debug info can be left behind that messes up backtraces.
The erroneous debug info shouldn't be generated, but gdb could do a better
job when faced with it.

foo.h:
struct Foo { Foo() { } };

foo.cc:
#include "foo.h"
typedef void (*FN)(void);
void crash()
{
FN f = (FN)1;
f();
}
int main()
{
Foo f;
crash();
return 0;
}

bar.cc:
#include "foo.h"
int bar() { Foo f; return 0; }

bash$ x86_64-linux-g++ --version
x86_64-linux-g++ (GCC) 4.2.1
[...]
bash$ x86_64-linux-g++ -g foo.cc bar.cc
bash$ readelf -wf a.out | grep -A5 pc=0000
00000090 0000001c 00000078 FDE cie=00000078 pc=00000000..0000000a
DW_CFA_advance_loc: 1 to 00000001
DW_CFA_def_cfa_offset: 16
DW_CFA_offset: r6 at cfa-16
DW_CFA_advance_loc: 3 to 00000004
DW_CFA_def_cfa_reg: r6

The constructor for Foo is emitted in both foo.o and bar.o, but
the duplicate is only partially removed.

bash$ gdb a.out
GNU gdb 6.8
[...]
(gdb) r
Starting program: /tmp/a.out

Program received signal SIGSEGV, Segmentation fault.
0x0000000000000001 in ?? ()
(gdb) bt
#0 0x0000000000000001 in ?? ()
#1 0x0000000000400670 in ?? ()
#2 0x0000000000000001 in ?? ()
#3 0x0000000000400642 in main () at foo.cc:21
(gdb)

If we apply the following hack, which makes GDB ignore unwind descriptors
in the first page, we get the expected backtrace.

*** dwarf2-frame.c~ Wed Apr 16 11:16:43 2008
--- dwarf2-frame.c Wed Apr 16 11:16:43 2008
*************** dwarf2_frame_find_fde (CORE_ADDR *pc)
*** 1571,1576 ****
--- 1571,1583 ----
{
struct objfile *objfile;

+ if (*pc < (CORE_ADDR)0x1000)
+ {
+ /* Workaround for apparent toolchain issue,
+ ignore any unwind descriptors that describe code on the zero page.
+ We know there is no code there. */
+ return NULL;
+ }
ALL_OBJFILES (objfile)
{
struct dwarf2_fde *fde;

bash$ hacked-gdb a.out
(gdb) r
Starting program: /tmp/a.out

Program received signal SIGSEGV, Segmentation fault.
0x0000000000000001 in ?? ()
(gdb) bt
#0 0x0000000000000001 in ?? ()
#1 0x000000000040062a in crash () at foo.cc:15
#2 0x0000000000400642 in main () at foo.cc:21
(gdb)

Assuming we want to have dwarf2_frame_find_fde reject clearly bogus
pc values, the issue is of course how to cleanly recognize them.
Suggestions?

One alternative is something like this, the catch being
how to write is_zero_page_executable.

*************** dwarf2_frame_find_fde (CORE_ADDR *pc)
@@ -1585,6 +1608,13 @@

while (fde)
{
+ if (fde->initial_location + offset == 0
+ && !is_zero_page_executable ())
+ {
+ /* Ignore this FDE -- linker bug. */
+ fde = fde->next;
+ continue;
+ }