This is the mail archive of the gdb-patches@sourceware.org mailing list for the GDB project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Re: [RFC] i386 PLT stub unwinder


On Monday 13 June 2011 16:55:56, Mark Kettenis wrote:
> Jan's mail about DWARF CFI for PLT stubs prompted me to write an
> undinder for the PLT stubs as defined by the i386 ABI.  With this
> change I can step through the PLT stubs and always have a proper
> backtrace.
> 
> The reason that this is an RFC, is that I'm a little bit confused
> about the TRY_CATCH stuff that was introduced in some of the i386
> unwinders by Pedro.  It isn't entirely clear to me when that is
> needed.  Unwinding the PC should always succeed at this point, since
> the sniffers already rely on that.  And unwinding the stack pointer
> needs to be working as well, otherwise unwinding the PC will never
> have worked.  So I don't think I need to worry about unavailable
> registers here.

Not correct.  If the PC was not available, the sniffer wouldn't be
able to detect the program is sitting in a plt, so in that case
you indeed don't have to worry.  But, all the sniffer needs to
be able to decide the plt unwinder is the best unwinder is to have the
PC available and being able to read some instructions off of that address.
If the SP is unavailable, and so you can't compute the frame
base, the frame needs to be marked un-unwindable.

E.g., with your patch as is:

$ gdb ~/gdb/tests/loop32 
...
(gdb) tar remote  :9999
Remote debugging using :9999
Reading symbols from /lib/ld-linux.so.2...(no debugging symbols found)...done.
Loaded symbols for /lib/ld-linux.so.2
0xf7fe0850 in ?? () from /lib/ld-linux.so.2
(gdb) b main
Breakpoint 1 at 0x804850e: file loop.c, line 84.
(gdb) c
Continuing.

Breakpoint 1, main () at loop.c:84
84          long i = 0;
(gdb) si
...
(gdb) 
0x080483c0 in pthread_create@plt ()
(gdb) disassemble 
Dump of assembler code for function pthread_create@plt:
=> 0x080483c0 <+0>:     jmp    *0x804a00c
   0x080483c6 <+6>:     push   $0x18
   0x080483cb <+11>:    jmp    0x8048380
End of assembler dump.
(gdb) trace *0x080483c6
Tracepoint 2 at 0x80483c6
(gdb) tstart
(gdb) n
Single stepping until exit from function pthread_create@plt,
which has no line number information.
0x08048380 in ?? ()
(gdb) tstatus
Trace is running on the target.
Collected 1 trace frames.
Trace buffer has 5242874 bytes of 5242880 bytes free (0% full).
Trace will stop if GDB disconnects.
Not looking at any trace frame.
(gdb) tstop
(gdb) tfind
Found trace frame 0, tracepoint 2
Register 4 is not available
^^^^^^^^^^^^^^^^^^^^^^^^^^^

That is an error being thrown thrown from:

(top-gdb) bt
#0  throw_error (error=..., fmt=0x7fffffffee15 "") at ../../src/gdb/exceptions.c:420
#1  0x0000000000466705 in frame_unwind_register (frame=0xf20b30, regnum=4, buf=0x7fffffffd600 "") at ../../src/gdb/frame.c:919
#2  0x0000000000466b4d in frame_unwind_register_unsigned (frame=0xf20b30, regnum=4) at ../../src/gdb/frame.c:1025
#3  0x0000000000466b9e in get_frame_register_unsigned (frame=0xf20bf0, regnum=4) at ../../src/gdb/frame.c:1032
#4  0x000000000048325a in i386_plt_stub_frame_cache (this_frame=0xf20bf0, this_cache=0xf20c08) at ../../src/gdb/i386-tdep.c:2208
#5  0x00000000004832d6 in i386_plt_stub_frame_this_id (this_frame=0xf20bf0, this_cache=0xf20c08, this_id=0xf20c50) at ../../src/gdb/i386-tdep.c:2221
#6  0x00000000004653c4 in get_frame_id (fi=0xf20bf0) at ../../src/gdb/frame.c:339
#7  0x0000000000467ed1 in get_prev_frame_1 (this_frame=0xf20bf0) at ../../src/gdb/frame.c:1628
#8  0x00000000004687b7 in get_prev_frame (this_frame=0xf20bf0) at ../../src/gdb/frame.c:1950
#9  0x000000000059c991 in backtrace_command_1 (count_exp=0x0, show_locals=0, from_tty=1) at ../../src/gdb/stack.c:1374
#10 0x000000000059ca57 in backtrace_command_stub (data=0x7fffffffd9d0) at ../../src/gdb/stack.c:1421
#11 0x00000000005a2057 in catch_errors (func=0x59ca24 <backtrace_command_stub>, func_args=0x7fffffffd9d0, errstring=0x809fc8 "", mask=2)
    at ../../src/gdb/exceptions.c:506
#12 0x000000000059cc93 in backtrace_command (arg=0x0, from_tty=1) at ../../src/gdb/stack.c:1479

That is:

> +static struct i386_frame_cache *
> +i386_plt_stub_frame_cache (struct frame_info *this_frame, void **this_cache)
> +{
...
> +
> +  sp = get_frame_register_unsigned (this_frame, I386_ESP_REGNUM);
> +  cache->base = sp + cache->sp_offset;


You should wrap that in a TRY_CATCH for NOT_AVAILABLE_ERROR
like the other i386 unwinders, and

> +  cache->base_p = 1;

leave that false if the base wasn't computable,

> +static const struct frame_unwind i386_plt_stub_frame_unwind =
> +{
> +  NORMAL_FRAME,
> +  default_frame_unwind_stop_reason,

and here install a i386_plt_stub_frame_unwind_stop_reason
function that returns UNWIND_UNAVAILABLE if cache->base_p
is false, just like e.g., i386_epilogue_frame_unwind_stop_reason.

Might as well write that in patch form.  See below.  You
can merge it into yours, or have me apply it when yours goes
in, as you prefer.

Here's what it looks like with the patch applied.
Again, inspecting a tracepoint where only PC is available:

(gdb) tstatus
Trace is running on the target.
Collected 1 trace frames.
Trace buffer has 5242874 bytes of 5242880 bytes free (0% full).
Trace will stop if GDB disconnects.
Not looking at any trace frame.
(gdb) tstop
(gdb) tfind 
Found trace frame 0, tracepoint 2
#0  0x080483cb in pthread_create@plt ()
(gdb) bt
#0  0x080483cb in pthread_create@plt ()
Backtrace stopped: Not enough registers or memory available to unwind further
(gdb) info registers 
eax            *value not available*
ecx            *value not available*
edx            *value not available*
ebx            *value not available*
esp            *value not available*
ebp            *value not available*
esi            *value not available*
edi            *value not available*
eip            0x80483cb        0x80483cb <pthread_create@plt+11>
eflags         *value not available*
cs             *value not available*
ss             *value not available*
ds             *value not available*
es             *value not available*
fs             *value not available*
gs             *value not available*
(gdb) info frame 
Stack level 0, frame at 0x8:
 eip = 0x80483cb in pthread_create@plt; saved eip 0x80483cb
 Outermost frame: Not enough registers or memory available to unwind further
 Arglist at unknown address.
 Locals at unknown address,Register 9 is not available
(gdb) up
Initial frame selected; you cannot go up.


(that "Register 9 is not available" in info frame's output is a
common code bug I had missed before)

-- 
Pedro Alves

---
 gdb/i386-tdep.c |   31 +++++++++++++++++++++++++------
 1 file changed, 25 insertions(+), 6 deletions(-)

Index: src/gdb/i386-tdep.c
===================================================================
--- src.orig/gdb/i386-tdep.c	2011-06-14 12:05:30.000000000 +0100
+++ src/gdb/i386-tdep.c	2011-06-14 12:34:55.466490000 +0100
@@ -2125,6 +2125,7 @@ struct i386_insn i386_pic_plt_stub_insns
 static struct i386_frame_cache *
 i386_plt_stub_frame_cache (struct frame_info *this_frame, void **this_cache)
 {
+  volatile struct gdb_exception ex;
   struct i386_frame_cache *cache;
   struct i386_insn *insn;
   LONGEST sp_offset = -4;
@@ -2205,15 +2206,33 @@ i386_plt_stub_frame_cache (struct frame_
 
   cache->pc = pc;
 
-  sp = get_frame_register_unsigned (this_frame, I386_ESP_REGNUM);
-  cache->base = sp + cache->sp_offset;
-  cache->saved_sp = cache->base + 8;
-  cache->saved_regs[I386_EIP_REGNUM] = cache->base + 4;
+  TRY_CATCH (ex, RETURN_MASK_ERROR)
+    {
+      sp = get_frame_register_unsigned (this_frame, I386_ESP_REGNUM);
+      cache->base = sp + cache->sp_offset;
+      cache->saved_sp = cache->base + 8;
+      cache->saved_regs[I386_EIP_REGNUM] = cache->base + 4;
+      cache->base_p = 1;
+    }
+  if (ex.reason < 0 && ex.error != NOT_AVAILABLE_ERROR)
+    throw_exception (ex);
 
-  cache->base_p = 1;
   return cache;
 }
 
+static enum unwind_stop_reason
+i386_plt_stub_frame_unwind_stop_reason (struct frame_info *this_frame,
+					void **this_cache)
+{
+  struct i386_frame_cache *cache =
+    i386_plt_stub_frame_cache (this_frame, this_cache);
+
+  if (!cache->base_p)
+    return UNWIND_UNAVAILABLE;
+
+  return UNWIND_NO_REASON;
+}
+
 static void
 i386_plt_stub_frame_this_id (struct frame_info *this_frame, void **this_cache,
                             struct frame_id *this_id)
@@ -2256,7 +2275,7 @@ i386_plt_stub_frame_sniffer (const struc
 static const struct frame_unwind i386_plt_stub_frame_unwind =
 {
   NORMAL_FRAME,
-  default_frame_unwind_stop_reason,
+  i386_plt_stub_frame_unwind_stop_reason,
   i386_plt_stub_frame_this_id,
   i386_plt_stub_frame_prev_register,
   NULL,


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]