Re: [patch] Stop runaway unwinding on stripped executables

Mark Kettenis
2012-03-16T13:51:20Z

> Date: Fri, 16 Mar 2012 13:02:07 +0100
> From: Jan Kratochvil <jan.kratochvil@redhat.com>
>
> 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.

People need to learn that if they are debugging stripped stuff they're
going to end up with runaway backtraces every now and then. In this
particular example you're just getting lucky that you're hitting
__libc_start_main(). That probably wouldn't happen if you're somewhat
deeper into the call stack of the (stripped) program that you're
trying to debug.

> 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".

But the implementation and actually the whole idea is *very*
glibc-specific. It relies on the fact that that the startup code
calls a function in libc which in turn calls main(). You hardcode the
name of the libc shared library. And you hardcode the name of the
function. IMHO this does notbelong in the generic frame unwinding
code.

> 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
>