[patch] Stop runaway unwinding on stripped executables

Jan Kratochvil
2012-03-16T12:02:32Z

Hi,

this situation came from the real world out there:

# inferior has symbols
echo 'main(){pause();}'|gcc -x c -; ./a.out& gdb -p $! -ex bt
#0 0x00007f9dc4d4f0d0 in __pause_nocancel () at ../sysdeps/unix/syscall-template.S:82
#1 0x00000000004004ea in main ()
(gdb) q

PROBLEM:
--------
# -s: inferior has no symbols
echo 'main(){pause();}'|gcc -x c - -s; ./a.out& gdb -p $! -ex bt
#0 0x00007f274fd5a0d0 in __pause_nocancel () at ../sysdeps/unix/syscall-template.S:82
#1 0x00000000004004ea in ?? ()
#2 0x00007f274fcc1735 in __libc_start_main (main=0x4004dc, ...) at libc-start.c:226
#3 0x00000000004003f9 in ?? ()
#4 0x00007fffdbdec7a8 in ?? ()
#5 0x000000000000001c in ?? ()
#6 0x0000000000000001 in ?? ()
#7 0x00007fffdbdede72 in ?? ()
#8 0x0000000000000000 in ?? ()
(gdb) q
--- In reality this backtrace can be much longer confusing the people
thinking they have wrong backtrace; it is correctly unwound, it is just
runaway unwinding garbage.

# -s, now with the fix below:
#0 0x00007f1b912b80d0 in __pause_nocancel () at ../sysdeps/unix/syscall-template.S:82
#1 0x00000000004004ea in ?? ()
#2 0x00007f1b9121f735 in __libc_start_main (main=0x4004dc, argc=1, ubp_av=0x7fff150f9038, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fff150f9028) at libc-start.c:226

# inferior has symbols, with set backtrace past-main
echo 'main(){pause();}'|gcc -x c -; ./a.out& ./gdb -nx -p $! -ex 'set backtrace past-main' -ex bt
#0 0x00007f8930f090d0 in __pause_nocancel () at ../sysdeps/unix/syscall-template.S:82
#1 0x00000000004004ea in main ()
^^^^^^^ Both addresses were unwound above but not
printed.
#2 0x00007f8930e70735 in __libc_start_main (main=0x4004dc , ...) at libc-start.c:226
#3 0x00000000004003f9 in _start ()
^^^^^^^^^ Both addresses were unwound above but not
printed. GDB needs to know at least size
of "_start" for inside_entry_func but
without symbols it knows neither.
(gdb) q

One can see that with stripped inferior (-s) GDB cannot even stop at the entry
symbol as "_start" is also missing there.

I find the patch safe enough, if we want to stop at "main" and we see
"__libc_start_main" there is no chance "main" would be seen anymore.
Sure this whole patch has no effect with "set backtrace past-main".

I do not think a testcase makes sense, it would just duplicate the C code
logic in TCL being untested if "__libc_start_main" is not found.

No regressions on {x86_64,x86_64-m32,i686}-fedora17-linux-gnu.

Thanks,
Jan

gdb/
2012-03-16 Jan Kratochvil <jan.kratochvil@redhat.com>

Stop runaway unwinding of stripped executables.
* frame.c: Include objfiles.h.
(past_main_func): New function.
(get_prev_frame): Call it besides inside_main_func.

--- a/gdb/frame.c
+++ b/gdb/frame.c
@@ -43,7 +43,8 @@
#include "gdbthread.h"
#include "block.h"
#include "inline-frame.h"
-#include "tracepoint.h"
+#include "tracepoint.h"
+#include "objfiles.h"

static struct frame_info *get_prev_frame_1 (struct frame_info *this_frame);
static struct frame_info *get_prev_frame_raw (struct frame_info *this_frame);
@@ -1856,6 +1857,43 @@ inside_main_func (struct frame_info *this_frame)
return maddr == get_frame_func (this_frame);
}

+/* Is this (non-sentinel) frame verified to be after (missed) "main"()
+ function? This is a safety stop of runaway unwinding on stripped
+ executables missing both "main" and "_start" (entry) symbols when
+ "set backtrace past-main on" in in use. */
+
+static int
+past_main_func (struct frame_info *this_frame)
+{
+ struct objfile *objfile;
+
+ ALL_OBJFILES (objfile)
+ {
+ struct minimal_symbol *msymbol;
+ CORE_ADDR maddr;
+
+ if (objfile->separate_debug_objfile_backlink)
+ continue;
+
+ if (strcmp (lbasename (objfile->name), "libc.so.6") != 0)
+ continue;
+
+ msymbol = lookup_minimal_symbol ("__libc_start_main", NULL, objfile);
+ if (msymbol == NULL)
+ continue;
+
+ /* Make certain that the code, and not descriptor, address is
+ returned. */
+ maddr = gdbarch_convert_from_func_ptr_addr (get_frame_arch (this_frame),
+ SYMBOL_VALUE_ADDRESS (msymbol),
+ &current_target);
+ if (maddr == get_frame_func (this_frame))
+ return 1;
+ }
+
+ return 0;
+}
+
/* Test whether THIS_FRAME is inside the process entry point function. */

static int
@@ -1904,7 +1942,7 @@ get_prev_frame (struct frame_info *this_frame)
&& get_frame_type (this_frame) == NORMAL_FRAME
&& !backtrace_past_main
&& frame_pc_p
- && inside_main_func (this_frame))
+ && (inside_main_func (this_frame) || past_main_func (this_frame)))
/* Don't unwind past main(). Note, this is done _before_ the
frame has been marked as previously unwound. That way if the
user later decides to enable unwinds past main(), that will