* RFC: Longjmp vs LD_POINTER_GUARD revisited
@ 2009-11-15 17:35 Daniel Jacobowitz
2009-11-15 18:06 ` Eli Zaretskii
` (2 more replies)
0 siblings, 3 replies; 18+ messages in thread
From: Daniel Jacobowitz @ 2009-11-15 17:35 UTC (permalink / raw)
To: gdb-patches; +Cc: Pedro Alves, Ulrich Weigand
This is an update of Pedro's patches from this thread:
http://sourceware.org/ml/gdb-patches/2008-05/msg00427.html
To recap, the problem is that GDB's current support for stepping over
longjmp relies on reading the jmp_buf and extracting the target PC.
There are two cases where this doesn't work:
* Where the layout of the jmp_buf changes. It's not generally part of
the platform ABI; I know that the ARM EABI makes it explicitly opaque.
This is not a fatal flaw, since GDB is happy to grub around in
undocumented bits, but it's not great either.
* Where the jmp_buf contents are obfuscated, as done by glibc, which
"encrypts" them using a canary stored in the thread TCB.
Someone, I forget precisely who, suggested that we step through
longjmp instead of setting a breakpoint at the longjmp target. Pedro
implemented that, but we ran into trouble with frame IDs; it's like
the bad debug info that GCC emits for epilogues, but even worse. In
the middle of longjmp you're pretty much guaranteed to have the stack
or frame pointer clobbered in a way GDB can't recognize.
This patch updates Pedro's work to current trunk, and adds a pragmatic
hack. If we recognize the name of the current function as definitely
related to longjmp, then we know it won't return normally, so we
should continue stepping. For x86_64 glibc, the magic name is
"__longjmp". Otherwise, we do a frame check as before.
This does not work without debug symbols. If I remove the Debian
libc6-dbg package (simulated, by runtest GDBFLAGS='-ex "set
debug-file-directory /nowhere"'), the longjmp tests all stop in the
middle of __longjmp. There's an obvious spot in this patch where
someone sufficiently motivated could add code recognition if
stop_func_name == NULL.
Any comments? One open question with this approach is whether we
think there are platforms where this won't work (or is sufficiently
less optimal than the gdbarch_get_longjmp_target approach) that we
should preserve both methods. Then, as Pedro suggested, we'd just
remove the Linux implementations.
I added some documentation to gdbint.texinfo about the algorithm.
If approved, there are two followup patches: remove bp_longjmp_resume
breakpoints and remove the gdbarch get_longjmp_target method.
Tested on x86_64-linux, where it fixes all of longjmp.exp with no
regressions.
--
Daniel Jacobowitz
CodeSourcery
2009-11-15 Pedro Alves <pedro@codesourcery.com>
Daniel Jacobowitz <dan@codesourcery.com>
* infrun.c (insert_longjmp_resume_breakpoint): Delete.
(init_thread_stepping_state): Clear stepping_through_longjmp.
(still_in_longjmp_frame_p): New function.
(handle_inferior_event): If stepping through longjmp, a SIGTRAP is
not random. When a longjmp breakpoint is hit, prepare to step
until the other end. Keep stepping while in an inner frame
relative to the longjmp caller. When coming out on the other end,
check if the step-resume breakpoint frame is not needed anymore.
Remove BPSTAT_WHAT_CLEAR_LONGJMP_RESUME case.
(currently_stepping): Return true if stepping through longjmp.
(currently_stepping_or_nexting_callback): Likewise.
* gdbthread.h (struct thread_info): Add stepping_through_longjmp
and longjmp_frame.
* breakpoint.c (create_longjmp_master_breakpoint): Remove
gdbarch_get_longjmp_target_p check.
* breakpoint.h (BPSTAT_WHAT_SET_LONGJMP_RESUME): Rename to
BPSTAT_WHAT_SKIP_LONGJMP. All uses changed.
* NEWS: Document improved longjmp support.
2009-11-15 Daniel Jacobowitz <dan@codesourcery.com>
* gdbint.texinfo (Algorithms): Update longjmp support section.
2009-11-15 Pedro Alves <pedro@codesourcery.com>
* longjmp.c, longjmp.exp: Add tests to test ignoring inner
longjmp resumes while stepping, and update current tests.
Index: src/gdb/testsuite/gdb.base/longjmp.c
===================================================================
--- src.orig/gdb/testsuite/gdb.base/longjmp.c 2009-01-14 08:52:48.000000000 -0500
+++ src/gdb/testsuite/gdb.base/longjmp.c 2009-11-14 23:32:17.000000000 -0500
@@ -19,6 +19,7 @@
#include <setjmp.h>
jmp_buf env;
+jmp_buf env2;
volatile int longjmps = 0;
volatile int resumes = 0;
@@ -33,7 +34,7 @@ call_longjmp (jmp_buf *buf)
void
hidden_longjmp (void)
{
- if (setjmp (env) == 0) /* longjmp caught */
+ if (setjmp (env) == 0)
{
call_longjmp (&env);
}
@@ -41,41 +42,117 @@ hidden_longjmp (void)
resumes++;
}
+void
+hidden_longjmp_2 (void)
+{
+ if (setjmp (env) == 0)
+ {
+ if (setjmp (env2) == 0)
+ {
+ longjmps++;
+ longjmp (env2, 1);
+ }
+ else
+ {
+ resumes++;
+ longjmps++;
+ longjmp (env, 1);
+ }
+ }
+ else
+ {
+ resumes++;
+ }
+}
+
+void
+hidden_longjmp_3_1 (void)
+{
+ if (setjmp (env2) == 0)
+ {
+ longjmps++;
+ longjmp (env2, 1);
+ }
+ else
+ {
+ resumes++;
+ longjmps++;
+ longjmp (env, 1);
+ }
+}
+
+void
+hidden_longjmp_3 (void)
+{
+ hidden_longjmp_3_1 ();
+}
+
int
main ()
{
volatile int i = 0;
/* Pattern 1 - simple longjmp. */
- if (setjmp (env) == 0) /* patt1 */
+ if (setjmp (env) == 0)
{
longjmps++;
- longjmp (env, 1);
+ longjmp (env, 1); /* patt1 */
}
else
{
- resumes++;
+ resumes++; /* resume1 */
}
- i = 1; /* miss_step_1 */
-
/* Pattern 2 - longjmp from an inner function. */
- if (setjmp (env) == 0) /* patt2 */
+ if (setjmp (env) == 0)
{
- call_longjmp (&env);
+ call_longjmp (&env); /* patt2 */
}
else
{
- resumes++;
+ resumes++; /* resume2 */
}
- i = 2; /* miss_step_2 */
- /* Pattern 3 - setjmp/longjmp inside stepped-over function. */
- hidden_longjmp (); /* patt3 */
+ /* Pattern 3 - prefer longjmp resume.
+
+ This tests if GDB chooses the longjmp-resume over the step-resume
+ breakpoint. If GDB chooses wrongly, a step over the
+ hidden_longjmp_3 function will resume the inferior and pass
+ straight the else clause without stopping to step. */
+ if (setjmp (env) == 0)
+ hidden_longjmp_3 (); /* patt3 */
+ else
+ resumes++; /* resume3 */
+
+
+ /* Pattern 4 - prefer longjmp resume after step.
+
+ Quite similar, but in this case, we step into hidden_longjmp_3
+ before next'ing over the longjmp. In this case, the step-resume
+ breakpoint will be set in an inner stack. Check if GDB still
+ detects that the longjmp-resume address is prefered. */
+ if (setjmp (env) == 0)
+ hidden_longjmp_3 (); /* patt4 */
+ else
+ resumes++; /* resume4 */
+
+
+ /* Pattern 5 - setjmp/longjmp handled inside stepped-over function.
+
+ Test that we don't miss-handle internal setjmp/longjmp sequences.
+ A next over this should not stop at the longjmp resume
+ address. */
+ hidden_longjmp (); /* patt5 */
+ i = 5; /* patt_end5. */
+
+
+ /* Pattern 6 - nested setjmp/longjmp handled inside stepped-over
+ function. */
+ hidden_longjmp_2 (); /* patt6 */
+ i = 6; /* patt_end6. */
- i = 3; /* patt_end3. */
return 0;
}
Index: src/gdb/testsuite/gdb.base/longjmp.exp
===================================================================
--- src.orig/gdb/testsuite/gdb.base/longjmp.exp 2009-01-14 08:52:48.000000000 -0500
+++ src/gdb/testsuite/gdb.base/longjmp.exp 2009-11-14 23:33:47.000000000 -0500
@@ -47,12 +47,12 @@ if ![runto_main] then {
return 0
}
-set bp_miss_step_1 [gdb_get_line_number "miss_step_1"]
-set bp_miss_step_2 [gdb_get_line_number "miss_step_2"]
-
set bp_start_test_1 [gdb_get_line_number "patt1"]
set bp_start_test_2 [gdb_get_line_number "patt2"]
set bp_start_test_3 [gdb_get_line_number "patt3"]
+set bp_start_test_4 [gdb_get_line_number "patt4"]
+set bp_start_test_5 [gdb_get_line_number "patt5"]
+set bp_start_test_6 [gdb_get_line_number "patt6"]
#
# Pattern 1 - simple longjmp.
@@ -65,25 +65,7 @@ gdb_test "break $bp_start_test_1" \
"breakpoint at pattern 1 start"
gdb_test "continue" "patt1.*" "continue to breakpoint at pattern 1 start"
-# set safe-net break
-gdb_test "break $bp_miss_step_1" \
- "Breakpoint.*at.* file .*$srcfile, line.*$bp_miss_step_1.*" \
- "breakpoint at miss_step_1"
-
-gdb_test "next" "longjmps\\+\\+;.*" "next over setjmp (1)"
-gdb_test "next" "longjmp \\(env, 1\\);.*" "next to longjmp (1)"
-
-set msg "next over longjmp(1)"
-gdb_test_multiple "next" $msg {
- -re ".*patt1.*$gdb_prompt $" {
- pass $msg
- gdb_test "next" "resumes\\+\\+.*" "next into else block (1)"
- gdb_test "next" "miss_step_1.*" "next into safety net (1)"
- }
- -re "miss_step_1.*$gdb_prompt $" {
- fail $msg
- }
-}
+gdb_test "next" ".*resume1.*" "next over longjmp(1)"
#
# Pattern 2 - longjmp from an inner function.
@@ -96,28 +78,10 @@ gdb_test "break $bp_start_test_2" \
"breakpoint at pattern 2 start"
gdb_test "continue" "patt2.*" "continue to breakpoint at pattern 2 start"
-# set safe-net break
-gdb_test "break $bp_miss_step_2" \
- "Breakpoint.*at.* file .*$srcfile, line.*$bp_miss_step_2.*" \
- "breakpoint at miss_step_2"
-
-gdb_test "next" "call_longjmp.*" "next over setjmp (2)"
-
-set msg "next over call_longjmp (2)"
-gdb_test_multiple "next" $msg {
- -re ".*patt2.*$gdb_prompt $" {
- pass $msg
-
- gdb_test "next" "resumes\\+\\+.*" "next into else block (2)"
- gdb_test "next" "miss_step_2.*" "next into safety net (2)"
- }
- -re "miss_step_2.*$gdb_prompt $" {
- fail $msg
- }
-}
+gdb_test "next" ".*resume2.*" "next over call_longjmp (2)"
#
-# Pattern 3 - setjmp/longjmp inside stepped-over function.
+# Pattern 3 - prefer longjmp resume
#
delete_breakpoints
@@ -125,6 +89,50 @@ delete_breakpoints
gdb_test "break $bp_start_test_3" \
"Breakpoint.*at.* file .*$srcfile, line.*$bp_start_test_3.*" \
"breakpoint at pattern 3 start"
-gdb_test "continue" "patt3.*" "continue to breakpoint at pattern 3 start"
+gdb_test "continue" "patt3.*" \
+ "continue to breakpoint at pattern 3 start"
+
+gdb_test "next" "resume3.*" "next over hidden_longjmp_3 (3)"
+
+
+#
+# Pattern 4 - prefer longjmp resume after step
+#
+
+delete_breakpoints
+
+gdb_test "break $bp_start_test_4" \
+ "Breakpoint.*at.* file .*$srcfile, line.*$bp_start_test_4.*" \
+ "breakpoint at pattern 4 start"
+gdb_test "continue" "patt4.*" "continue to breakpoint at pattern 4 start"
+
+gdb_test "step" "hidden_longjmp_3_1 \\(\\).*" "step into hidden_longjmp_3 (4)"
+
+gdb_test "next" "resume4.*" "next over hidden_longjmp_3_1 (4)"
+
+#
+# Pattern 5 - setjmp/longjmp handled inside stepped-over function.
+#
+
+delete_breakpoints
+
+gdb_test "break $bp_start_test_5" \
+ "Breakpoint.*at.* file .*$srcfile, line.*$bp_start_test_5.*" \
+ "breakpoint at pattern 5 start"
+gdb_test "continue" "patt5.*" "continue to breakpoint at pattern 5 start"
+
+gdb_test "next" "patt_end5.*" "next over patt5"
+
+#
+# Pattern 6 - nested setjmp/longjmp handled inside stepped-over
+# function.
+#
+
+delete_breakpoints
+
+gdb_test "break $bp_start_test_6" \
+ "Breakpoint.*at.* file .*$srcfile, line.*$bp_start_test_6.*" \
+ "breakpoint at pattern 6 start"
+gdb_test "continue" "patt6.*" "continue to breakpoint at pattern 6 start"
-gdb_test "next" "longjmp caught.*" "next over patt3"
+gdb_test "next" "patt_end6.*" "next over patt6"
Index: src/gdb/infrun.c
===================================================================
--- src.orig/gdb/infrun.c 2009-11-13 16:59:53.000000000 -0500
+++ src/gdb/infrun.c 2009-11-15 11:25:42.000000000 -0500
@@ -2012,7 +2012,6 @@ static void insert_step_resume_breakpoin
static void insert_step_resume_breakpoint_at_sal (struct gdbarch *gdbarch,
struct symtab_and_line sr_sal,
struct frame_id sr_id);
-static void insert_longjmp_resume_breakpoint (struct gdbarch *, CORE_ADDR);
static void stop_stepping (struct execution_control_state *ecs);
static void prepare_to_wait (struct execution_control_state *ecs);
@@ -2421,6 +2420,7 @@ init_thread_stepping_state (struct threa
tss->step_after_step_resume_breakpoint = 0;
tss->stepping_through_solib_after_catch = 0;
tss->stepping_through_solib_catchpoints = NULL;
+ tss->stepping_through_longjmp = 0;
}
/* Return the cached copy of the last pid/waitstatus returned by
@@ -2616,6 +2616,43 @@ stepped_in_from (struct frame_info *fram
return 0;
}
+/* This function is called while we step through a call to longjmp.
+ In most cases we can detect that we are inside the call to longjmp
+ by searching for a frame ID that we saved at the start of the call;
+ if that is still on the stack, we are still stepping through the
+ call to longjmp. However, towards the end of the call, longjmp
+ has to restore the saved stack pointer. At this point we can
+ not trust the frame ID. So this function must recognize the
+ affected functions by other means. Currently we do so by
+ name; an architecture method may be needed also.
+
+ Return 1 if we are clearly inside a function which will not
+ return normally, and we should continue stepping. Return 0
+ otherwise. */
+
+static int
+still_in_longjmp_frame_p (const char *func)
+{
+ if (func == NULL)
+ return 0;
+
+ /* The functions we set a longjmp breakpoint on. */
+ if (strcmp (func, "longjmp") == 0)
+ return 1;
+ if (strcmp (func, "_longjmp") == 0)
+ return 1;
+ if (strcmp (func, "siglongjmp") == 0)
+ return 1;
+ if (strcmp (func, "_longjmp") == 0)
+ return 1;
+
+ /* x86_64 glibc calls this function internally. */
+ if (strcmp (func, "__longjmp") == 0)
+ return 1;
+
+ return 0;
+}
+
/* Auxiliary function that handles syscall entry/return events.
It returns 1 if the inferior should keep going (and GDB
should ignore the event), or 0 if the event deserves to be
@@ -3608,7 +3645,8 @@ targets should add new threads to the th
= !(bpstat_explains_signal (ecs->event_thread->stop_bpstat)
|| ecs->event_thread->trap_expected
|| (ecs->event_thread->step_range_end
- && ecs->event_thread->step_resume_breakpoint == NULL));
+ && ecs->event_thread->step_resume_breakpoint == NULL)
+ || ecs->event_thread->stepping_through_longjmp);
else
{
ecs->random_signal = !bpstat_explains_signal (ecs->event_thread->stop_bpstat);
@@ -3746,50 +3784,27 @@ process_event_stop_test:
switch (what.main_action)
{
- case BPSTAT_WHAT_SET_LONGJMP_RESUME:
- /* If we hit the breakpoint at longjmp while stepping, we
- install a momentary breakpoint at the target of the
- jmp_buf. */
+ case BPSTAT_WHAT_SKIP_LONGJMP:
+ /* If we hit the breakpoint at longjmp while stepping, prepare
+ to step all the way through it. */
if (debug_infrun)
fprintf_unfiltered (gdb_stdlog,
- "infrun: BPSTAT_WHAT_SET_LONGJMP_RESUME\n");
+ "infrun: BPSTAT_WHAT_SKIP_LONGJMP\n");
+ /* Step over this longjmp breakpoint. */
ecs->event_thread->stepping_over_breakpoint = 1;
- if (!gdbarch_get_longjmp_target_p (gdbarch)
- || !gdbarch_get_longjmp_target (gdbarch, frame, &jmp_buf_pc))
- {
- if (debug_infrun)
- fprintf_unfiltered (gdb_stdlog, "\
-infrun: BPSTAT_WHAT_SET_LONGJMP_RESUME (!gdbarch_get_longjmp_target)\n");
- keep_going (ecs);
- return;
- }
-
- /* We're going to replace the current step-resume breakpoint
- with a longjmp-resume breakpoint. */
- delete_step_resume_breakpoint (ecs->event_thread);
-
- /* Insert a breakpoint at resume address. */
- insert_longjmp_resume_breakpoint (gdbarch, jmp_buf_pc);
+ /* Store the current frame id. This is used to check whether we
+ are still inside the call to longjmp. */
+ ecs->event_thread->longjmp_frame
+ = get_stack_frame_id (get_current_frame ());
+ /* See you on the other side! */
+ ecs->event_thread->stepping_through_longjmp = 1;
keep_going (ecs);
return;
- case BPSTAT_WHAT_CLEAR_LONGJMP_RESUME:
- if (debug_infrun)
- fprintf_unfiltered (gdb_stdlog,
- "infrun: BPSTAT_WHAT_CLEAR_LONGJMP_RESUME\n");
-
- gdb_assert (ecs->event_thread->step_resume_breakpoint != NULL);
- delete_step_resume_breakpoint (ecs->event_thread);
-
- ecs->event_thread->stop_step = 1;
- print_stop_reason (END_STEPPING_RANGE, 0);
- stop_stepping (ecs);
- return;
-
case BPSTAT_WHAT_SINGLE:
if (debug_infrun)
fprintf_unfiltered (gdb_stdlog, "infrun: BPSTAT_WHAT_SINGLE\n");
@@ -4017,6 +4032,54 @@ infrun: not switching back to stepped th
return;
}
+ if (ecs->event_thread->stepping_through_longjmp)
+ {
+ struct frame_id frame_id = get_stack_frame_id (get_current_frame ());
+
+ if (still_in_longjmp_frame_p (ecs->stop_func_name)
+ || frame_find_by_id (ecs->event_thread->longjmp_frame) != NULL)
+ {
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog,
+ "infrun: stepping through longjmp\n");
+ /* Still not there. */
+ keep_going (ecs);
+ return;
+ }
+
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog, "infrun: got out of longjmp\n");
+
+ /* We made it. */
+ ecs->event_thread->stepping_through_longjmp = 0;
+
+ /* If there's a step-resume breakpoint set, decide if we should
+ keep stepping to the step-resume breakpoint, or if the
+ longjmp took us outermost already, hence the step-resume
+ breakpoint will never be hit, and we should stop now. */
+ if (ecs->event_thread->step_resume_breakpoint)
+ {
+ struct frame_id resume_frame_id;
+
+ resume_frame_id = ecs->event_thread->step_resume_breakpoint->frame_id;
+ if (! frame_id_eq (resume_frame_id, frame_id)
+ && frame_find_by_id (resume_frame_id) != NULL)
+ {
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog,
+ "\
+infrun: longjmp-resume inner than step-resume\n");
+ }
+ else
+ {
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog,
+ "infrun: step-resume overran by longjmp\n");
+ delete_step_resume_breakpoint (ecs->event_thread);
+ }
+ }
+ }
+
if (ecs->event_thread->step_resume_breakpoint)
{
if (debug_infrun)
@@ -4559,6 +4622,7 @@ currently_stepping (struct thread_info *
{
return ((tp->step_range_end && tp->step_resume_breakpoint == NULL)
|| tp->trap_expected
+ || tp->stepping_through_longjmp
|| tp->stepping_through_solib_after_catch
|| bpstat_should_step ());
}
@@ -4574,6 +4638,7 @@ currently_stepping_or_nexting_callback (
return (tp->step_range_end
|| tp->trap_expected
+ || tp->stepping_through_longjmp
|| tp->stepping_through_solib_after_catch);
}
@@ -4777,28 +4842,6 @@ insert_step_resume_breakpoint_at_caller
frame_unwind_caller_id (next_frame));
}
-/* Insert a "longjmp-resume" breakpoint at PC. This is used to set a
- new breakpoint at the target of a jmp_buf. The handling of
- longjmp-resume uses the same mechanisms used for handling
- "step-resume" breakpoints. */
-
-static void
-insert_longjmp_resume_breakpoint (struct gdbarch *gdbarch, CORE_ADDR pc)
-{
- /* There should never be more than one step-resume or longjmp-resume
- breakpoint per thread, so we should never be setting a new
- longjmp_resume_breakpoint when one is already active. */
- gdb_assert (inferior_thread ()->step_resume_breakpoint == NULL);
-
- if (debug_infrun)
- fprintf_unfiltered (gdb_stdlog,
- "infrun: inserting longjmp-resume breakpoint at %s\n",
- paddress (gdbarch, pc));
-
- inferior_thread ()->step_resume_breakpoint =
- set_momentary_breakpoint_at_pc (gdbarch, pc, bp_longjmp_resume);
-}
-
static void
stop_stepping (struct execution_control_state *ecs)
{
Index: src/gdb/gdbthread.h
===================================================================
--- src.orig/gdb/gdbthread.h 2009-10-28 16:14:54.000000000 -0400
+++ src/gdb/gdbthread.h 2009-11-15 10:55:10.000000000 -0500
@@ -185,6 +185,13 @@ struct thread_info
/* True if this thread has been explicitly requested to stop. */
int stop_requested;
+ /* True if a longjmp call was detected while stepping, and we're
+ single-stepping until the other end. */
+ int stepping_through_longjmp;
+
+ /* The frame where a longjmp breakpoint was hit. */
+ struct frame_id longjmp_frame;
+
/* Private data used by the target vector implementation. */
struct private_thread_info *private;
};
Index: src/gdb/NEWS
===================================================================
--- src.orig/gdb/NEWS 2009-11-15 11:12:57.000000000 -0500
+++ src/gdb/NEWS 2009-11-15 11:17:33.000000000 -0500
@@ -3,6 +3,10 @@
*** Changes since GDB 7.0
+* Support for stepping and nexting over longjmp has been improved. It now
+works independently of the architecture and supports recent versions
+of GLIBC.
+
* New targets
Xilinx MicroBlaze microblaze-*-*
Index: src/gdb/breakpoint.c
===================================================================
--- src.orig/gdb/breakpoint.c 2009-11-15 00:19:06.000000000 -0500
+++ src/gdb/breakpoint.c 2009-11-15 11:01:01.000000000 -0500
@@ -1774,9 +1774,6 @@ create_longjmp_master_breakpoint (char *
struct breakpoint *b;
struct minimal_symbol *m;
- if (!gdbarch_get_longjmp_target_p (get_objfile_arch (objfile)))
- continue;
-
set_current_program_space (pspace);
m = lookup_minimal_symbol_text (func_name, objfile);
@@ -3625,7 +3622,7 @@ bpstat_what (bpstat bs)
#define ss BPSTAT_WHAT_STOP_SILENT
#define sn BPSTAT_WHAT_STOP_NOISY
#define sgl BPSTAT_WHAT_SINGLE
-#define slr BPSTAT_WHAT_SET_LONGJMP_RESUME
+#define slr BPSTAT_WHAT_SKIP_LONGJMP
#define clr BPSTAT_WHAT_CLEAR_LONGJMP_RESUME
#define sr BPSTAT_WHAT_STEP_RESUME
#define shl BPSTAT_WHAT_CHECK_SHLIBS
Index: src/gdb/breakpoint.h
===================================================================
--- src.orig/gdb/breakpoint.h 2009-11-15 11:00:06.000000000 -0500
+++ src/gdb/breakpoint.h 2009-11-15 11:01:27.000000000 -0500
@@ -561,11 +561,8 @@ enum bpstat_what_main_action
cleanly handle BPSTAT_WHAT_CLEAR_LONGJMP_RESUME_SINGLE. */
BPSTAT_WHAT_SINGLE,
- /* Set longjmp_resume breakpoint, remove all other breakpoints,
- and continue. The "remove all other breakpoints" part is required
- if we are also stepping over another breakpoint as well as doing
- the longjmp handling. */
- BPSTAT_WHAT_SET_LONGJMP_RESUME,
+ /* Step until we reach the target of a longjmp call. */
+ BPSTAT_WHAT_SKIP_LONGJMP,
/* Clear longjmp_resume breakpoint, then handle as
BPSTAT_WHAT_KEEP_CHECKING. */
Index: src/gdb/doc/gdbint.texinfo
===================================================================
--- src.orig/gdb/doc/gdbint.texinfo 2009-11-15 11:17:50.000000000 -0500
+++ src/gdb/doc/gdbint.texinfo 2009-11-15 11:25:35.000000000 -0500
@@ -609,15 +609,20 @@ stepping. This is done with a few speci
which are visible in the output of the @samp{maint info breakpoint}
command.
-@findex gdbarch_get_longjmp_target
-To make this work, you need to define a function called
-@code{gdbarch_get_longjmp_target}, which will examine the
-@code{jmp_buf} structure and extract the @code{longjmp} target address.
-Since @code{jmp_buf} is target specific and typically defined in a
-target header not available to @value{GDBN}, you will need to
-determine the offset of the PC manually and return that; many targets
-define a @code{jb_pc_offset} field in the tdep structure to save the
-value once calculated.
+When @value{GDBN} detects a call to @code{longjmp}, it begins
+stepping the program. As long as the program is still inside
+the call to @code{longjmp} (as determined by either the current
+function name or a stack frame search), @value{GDBN} continues
+stepping. Once the program has left @code{longjmp}, @value{GDBN}
+determines whether to stop or to resume an earlier @code{next}
+opertion.
+
+In many cases you do not need any architecture-specific support
+for this feature. You may need to augment @code{still_in_longjmp_frame_p}
+in @file{infrun.c} to recognize any functions called by @code{longjmp}
+which make unusual changes to the stack. It can recognize
+functions by name, and could recognize additional cases
+by instruction scanning to support a stripped C library.
@section Watchpoints
@cindex watchpoints
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: RFC: Longjmp vs LD_POINTER_GUARD revisited
2009-11-15 17:35 RFC: Longjmp vs LD_POINTER_GUARD revisited Daniel Jacobowitz
@ 2009-11-15 18:06 ` Eli Zaretskii
2009-11-15 18:30 ` Paul Pluzhnikov
2009-11-15 18:39 ` Joseph S. Myers
2 siblings, 0 replies; 18+ messages in thread
From: Eli Zaretskii @ 2009-11-15 18:06 UTC (permalink / raw)
To: Daniel Jacobowitz; +Cc: gdb-patches, pedro, uweigand
> Date: Sun, 15 Nov 2009 12:34:29 -0500
> From: Daniel Jacobowitz <drow@false.org>
> Cc: Pedro Alves <pedro@codesourcery.com>, Ulrich Weigand <uweigand@de.ibm.com>
>
> This patch updates Pedro's work to current trunk, and adds a pragmatic
> hack. If we recognize the name of the current function as definitely
> related to longjmp, then we know it won't return normally, so we
> should continue stepping. For x86_64 glibc, the magic name is
> "__longjmp". Otherwise, we do a frame check as before.
Thanks.
> + /* The functions we set a longjmp breakpoint on. */
> + if (strcmp (func, "longjmp") == 0)
> + return 1;
> + if (strcmp (func, "_longjmp") == 0)
> + return 1;
> + if (strcmp (func, "siglongjmp") == 0)
> + return 1;
> + if (strcmp (func, "_longjmp") == 0)
> + return 1;
Did you really mean to have _longjmp twice here?
> Index: src/gdb/NEWS
> ===================================================================
> --- src.orig/gdb/NEWS 2009-11-15 11:12:57.000000000 -0500
> +++ src/gdb/NEWS 2009-11-15 11:17:33.000000000 -0500
> @@ -3,6 +3,10 @@
>
> *** Changes since GDB 7.0
>
> +* Support for stepping and nexting over longjmp has been improved. It now
> +works independently of the architecture and supports recent versions
> +of GLIBC.
> +
> * New targets
This part is fine.
> Index: src/gdb/doc/gdbint.texinfo
> ===================================================================
> --- src.orig/gdb/doc/gdbint.texinfo 2009-11-15 11:17:50.000000000 -0500
> +++ src/gdb/doc/gdbint.texinfo 2009-11-15 11:25:35.000000000 -0500
> @@ -609,15 +609,20 @@ stepping. This is done with a few speci
> which are visible in the output of the @samp{maint info breakpoint}
> command.
>
> -@findex gdbarch_get_longjmp_target
> -To make this work, you need to define a function called
> -@code{gdbarch_get_longjmp_target}, which will examine the
> -@code{jmp_buf} structure and extract the @code{longjmp} target address.
> -Since @code{jmp_buf} is target specific and typically defined in a
> -target header not available to @value{GDBN}, you will need to
> -determine the offset of the PC manually and return that; many targets
> -define a @code{jb_pc_offset} field in the tdep structure to save the
> -value once calculated.
> +When @value{GDBN} detects a call to @code{longjmp}, it begins
> +stepping the program. As long as the program is still inside
> +the call to @code{longjmp} (as determined by either the current
> +function name or a stack frame search), @value{GDBN} continues
> +stepping. Once the program has left @code{longjmp}, @value{GDBN}
> +determines whether to stop or to resume an earlier @code{next}
> +opertion.
> +
> +In many cases you do not need any architecture-specific support
> +for this feature. You may need to augment @code{still_in_longjmp_frame_p}
> +in @file{infrun.c} to recognize any functions called by @code{longjmp}
> +which make unusual changes to the stack. It can recognize
> +functions by name, and could recognize additional cases
> +by instruction scanning to support a stripped C library.
This is also OK, but I'd suggest to add an index entry for
still_in_longjmp_frame_p.
Thanks.
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: RFC: Longjmp vs LD_POINTER_GUARD revisited
2009-11-15 17:35 RFC: Longjmp vs LD_POINTER_GUARD revisited Daniel Jacobowitz
2009-11-15 18:06 ` Eli Zaretskii
@ 2009-11-15 18:30 ` Paul Pluzhnikov
2009-11-15 22:36 ` Daniel Jacobowitz
2009-11-15 18:39 ` Joseph S. Myers
2 siblings, 1 reply; 18+ messages in thread
From: Paul Pluzhnikov @ 2009-11-15 18:30 UTC (permalink / raw)
To: gdb-patches, Pedro Alves, Ulrich Weigand
On Sun, Nov 15, 2009 at 9:34 AM, Daniel Jacobowitz <drow@false.org> wrote:
> * Where the jmp_buf contents are obfuscated, as done by glibc, which
> "encrypts" them using a canary stored in the thread TCB.
FWIW, I never understood the point of this obfuscation: the program
(and any rogue code injected into it) can trivially discover the value
of canary:
call sigsetjmp
a_label:
... canary = sigjmp_buf.__jmpbuf[JB_PC] - &a_label
I think GDB could also perform such discovery, and that would allow it
work with non-debug glibc (debug glibc (I believe) is exception rather
the rule outside of {gdb,glibc,gcc} developers).
--
Paul Pluzhnikov
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: RFC: Longjmp vs LD_POINTER_GUARD revisited
2009-11-15 17:35 RFC: Longjmp vs LD_POINTER_GUARD revisited Daniel Jacobowitz
2009-11-15 18:06 ` Eli Zaretskii
2009-11-15 18:30 ` Paul Pluzhnikov
@ 2009-11-15 18:39 ` Joseph S. Myers
2009-11-15 21:52 ` Mark Kettenis
2 siblings, 1 reply; 18+ messages in thread
From: Joseph S. Myers @ 2009-11-15 18:39 UTC (permalink / raw)
To: Daniel Jacobowitz; +Cc: gdb-patches, Pedro Alves, Ulrich Weigand
On Sun, 15 Nov 2009, Daniel Jacobowitz wrote:
> should continue stepping. For x86_64 glibc, the magic name is
> "__longjmp". Otherwise, we do a frame check as before.
Nowadays you may need to handle ____longjmp_chk for glibc 2.11 as well.
Because distribution compilers may or may not enable _FORTIFY_SOURCE by
default, I suppose in principle the tests should be run explicitly with
different settings.
--
Joseph S. Myers
joseph@codesourcery.com
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: RFC: Longjmp vs LD_POINTER_GUARD revisited
2009-11-15 18:39 ` Joseph S. Myers
@ 2009-11-15 21:52 ` Mark Kettenis
2009-11-15 22:37 ` Daniel Jacobowitz
2009-11-16 15:15 ` Frank Ch. Eigler
0 siblings, 2 replies; 18+ messages in thread
From: Mark Kettenis @ 2009-11-15 21:52 UTC (permalink / raw)
To: joseph; +Cc: drow, gdb-patches, pedro, uweigand
> Date: Sun, 15 Nov 2009 18:38:19 +0000 (UTC)
> From: "Joseph S. Myers" <joseph@codesourcery.com>
>
> On Sun, 15 Nov 2009, Daniel Jacobowitz wrote:
>
> > should continue stepping. For x86_64 glibc, the magic name is
> > "__longjmp". Otherwise, we do a frame check as before.
>
> Nowadays you may need to handle ____longjmp_chk for glibc 2.11 as well.
> Because distribution compilers may or may not enable _FORTIFY_SOURCE by
> default, I suppose in principle the tests should be run explicitly with
> different settings.
Isn't it a bit ridiculous that glibc, which is a GNU project, makes
the life of GDB, another GNU project, so difficult?
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: RFC: Longjmp vs LD_POINTER_GUARD revisited
2009-11-15 18:30 ` Paul Pluzhnikov
@ 2009-11-15 22:36 ` Daniel Jacobowitz
2009-11-15 23:06 ` Paul Pluzhnikov
0 siblings, 1 reply; 18+ messages in thread
From: Daniel Jacobowitz @ 2009-11-15 22:36 UTC (permalink / raw)
To: Paul Pluzhnikov; +Cc: gdb-patches, Pedro Alves, Ulrich Weigand
On Sun, Nov 15, 2009 at 10:29:33AM -0800, Paul Pluzhnikov wrote:
> On Sun, Nov 15, 2009 at 9:34 AM, Daniel Jacobowitz <drow@false.org> wrote:
>
> > * Where the jmp_buf contents are obfuscated, as done by glibc, which
> > "encrypts" them using a canary stored in the thread TCB.
>
> FWIW, I never understood the point of this obfuscation: the program
> (and any rogue code injected into it) can trivially discover the value
> of canary:
>
> call sigsetjmp
> a_label:
> ... canary = sigjmp_buf.__jmpbuf[JB_PC] - &a_label
>
> I think GDB could also perform such discovery, and that would allow it
> work with non-debug glibc (debug glibc (I believe) is exception rather
> the rule outside of {gdb,glibc,gcc} developers).
There's a rotate and an xor involved; I don't believe this would work
as written... sure, we could "discover" it from disassembling key
functions automatically...
--
Daniel Jacobowitz
CodeSourcery
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: RFC: Longjmp vs LD_POINTER_GUARD revisited
2009-11-15 21:52 ` Mark Kettenis
@ 2009-11-15 22:37 ` Daniel Jacobowitz
2009-11-16 15:15 ` Frank Ch. Eigler
1 sibling, 0 replies; 18+ messages in thread
From: Daniel Jacobowitz @ 2009-11-15 22:37 UTC (permalink / raw)
To: Mark Kettenis; +Cc: joseph, gdb-patches, pedro, uweigand
On Sun, Nov 15, 2009 at 10:48:34PM +0100, Mark Kettenis wrote:
> Isn't it a bit ridiculous that glibc, which is a GNU project, makes
> the life of GDB, another GNU project, so difficult?
Yes. However, even if we could get some help from the libc developers
- signs so far, very grim - this mess has now been deployed for years :-(
--
Daniel Jacobowitz
CodeSourcery
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: RFC: Longjmp vs LD_POINTER_GUARD revisited
2009-11-15 22:36 ` Daniel Jacobowitz
@ 2009-11-15 23:06 ` Paul Pluzhnikov
2009-11-16 14:37 ` Daniel Jacobowitz
0 siblings, 1 reply; 18+ messages in thread
From: Paul Pluzhnikov @ 2009-11-15 23:06 UTC (permalink / raw)
To: Paul Pluzhnikov, gdb-patches, Pedro Alves, Ulrich Weigand
On Sun, Nov 15, 2009 at 2:35 PM, Daniel Jacobowitz <drow@false.org> wrote:
> There's a rotate and an xor involved; I don't believe this would work
> as written... sure, we could "discover" it from disassembling key
> functions automatically...
Oh, right. There was "plain XOR" in FC6, and shift-by-9 added in FC7.
Still it's trivial to discover the canary without disassembling
anything (disassembling requires symbols, which may be stripped):
there are only 3 different algorithms I've seen (no canary, XOR,
XOR+shift-by-9). Hmm, looks like x86_64 has XOR+shift-by-17 now, but
ia64, SPARC and PPC all have just "plain XOR".
Still I think this may be a more robust then requiring debuginfo or
non-stripped glibc.
--
Paul Pluzhnikov
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: RFC: Longjmp vs LD_POINTER_GUARD revisited
2009-11-15 23:06 ` Paul Pluzhnikov
@ 2009-11-16 14:37 ` Daniel Jacobowitz
2009-11-16 14:55 ` Pedro Alves
0 siblings, 1 reply; 18+ messages in thread
From: Daniel Jacobowitz @ 2009-11-16 14:37 UTC (permalink / raw)
To: Paul Pluzhnikov; +Cc: gdb-patches, Pedro Alves, Ulrich Weigand
On Sun, Nov 15, 2009 at 03:05:33PM -0800, Paul Pluzhnikov wrote:
> Still it's trivial to discover the canary without disassembling
> anything (disassembling requires symbols, which may be stripped):
> there are only 3 different algorithms I've seen (no canary, XOR,
> XOR+shift-by-9). Hmm, looks like x86_64 has XOR+shift-by-17 now, but
> ia64, SPARC and PPC all have just "plain XOR".
I don't know about "trivial" - could you explain how you would do this
without disassembling? I think that at the least we'd have to call
setjmp in the inferior, which has risks with signals / multiple
threads / etc. I don't think we call functions in the inferior for
discovery otherwise.
(Also I'm not entirely comfortable having GDB call functions
silently. We do this for malloc, yes - IMO there ought to be an
option to turn that off. This is a nasty gotcha that folks using GDB
for software forensics may not consider...)
IMO the debug info is quite commonly available now, but I wonder what
other folks see. Doesn't Red Hat's GDB suggest debuginfo RPMs that
you should install?
--
Daniel Jacobowitz
CodeSourcery
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: RFC: Longjmp vs LD_POINTER_GUARD revisited
2009-11-16 14:37 ` Daniel Jacobowitz
@ 2009-11-16 14:55 ` Pedro Alves
2009-11-16 14:56 ` Pedro Alves
` (2 more replies)
0 siblings, 3 replies; 18+ messages in thread
From: Pedro Alves @ 2009-11-16 14:55 UTC (permalink / raw)
To: Daniel Jacobowitz; +Cc: Paul Pluzhnikov, gdb-patches, Ulrich Weigand
On Monday 16 November 2009 14:36:13, Daniel Jacobowitz wrote:
> On Sun, Nov 15, 2009 at 03:05:33PM -0800, Paul Pluzhnikov wrote:
> > Still it's trivial to discover the canary without disassembling
> > anything (disassembling requires symbols, which may be stripped):
> > there are only 3 different algorithms I've seen (no canary, XOR,
> > XOR+shift-by-9). Hmm, looks like x86_64 has XOR+shift-by-17 now, but
> > ia64, SPARC and PPC all have just "plain XOR".
>
> I don't know about "trivial" - could you explain how you would do this
> without disassembling? I think that at the least we'd have to call
> setjmp in the inferior, which has risks with signals / multiple
> threads / etc. I don't think we call functions in the inferior for
> discovery otherwise.
I've had this patch below laying here for ages, that does
what Paul is suggesting, while trying to avoid infcalls.
It's quite ugly... I've been meaning to post this for ages,
but I'm a bit embarrassed by the hack :-) It doesn't work in
every possible case.
There are two issues that made me try this route:
- single-stepping all the way through longjmp turned out
to be slow. Did you not perceive that too? Maybe we
could limit the amount of single-stepping by not placing
the longjmp breakpoint at 'longjmp', but at one of its callees
that's closer to the real long jump.
- I've tried a similar hack as yours and found that I had
to add several more "functions still within longjmp" special
cases. I had tried it on netbsd and Windows too. NetBSD
was also bad at needing special casing. I'll post the relevant
bits of the patch in a bit, when I find it :-)
I guess we could make use-infcalls-for-discovery safer by forcing
scheduler-locking, and, by deferring asynchronous signals the
inferior catches while running the infcall. I've got a heavily
modified gdbserver that defers signals similarly (places signals
on a queue until safe to deliver, and lets the inferior run a bit
more), which works quite well, but it's not a quick hack to
implement it, and gdb is quite different from gdbserver.
> (Also I'm not entirely comfortable having GDB call functions
> silently. We do this for malloc, yes - IMO there ought to be an
> option to turn that off. This is a nasty gotcha that folks using GDB
> for software forensics may not consider...)
Yeah, calling malloc in the inferior can also deadlock it (if done at
a time that e.g., the inferior was already holding a malloc mutex).
--
Pedro Alves
Index: src/gdb/i386-tdep.c
===================================================================
--- src.orig/gdb/i386-tdep.c 2009-06-12 22:46:40.000000000 +0100
+++ src/gdb/i386-tdep.c 2009-06-12 22:58:41.000000000 +0100
@@ -1605,7 +1605,7 @@ i386_dummy_id (struct gdbarch *gdbarch,
This address is copied into PC. This routine returns non-zero on
success. */
-static int
+int
i386_get_longjmp_target (struct frame_info *frame, CORE_ADDR *pc)
{
gdb_byte buf[4];
Index: src/gdb/i386-linux-nat.c
===================================================================
--- src.orig/gdb/i386-linux-nat.c 2009-06-12 22:46:40.000000000 +0100
+++ src/gdb/i386-linux-nat.c 2009-06-13 00:24:33.000000000 +0100
@@ -716,6 +716,31 @@ ps_get_thread_area (const struct ps_proc
*(int *)base = desc[1];
return PS_OK;
}
+
+static int
+i386_linux_get_thread_area (struct target_ops *ops, CORE_ADDR *addr)
+{
+ int len = TYPE_LENGTH (builtin_type (target_gdbarch)->builtin_func_ptr);
+ struct regcache *regcache = get_thread_regcache (inferior_ptid);
+ void *tcbhead;
+ ULONGEST gs;
+ const int reg_thread_area = 3; /* bits to scale down register value. */
+ int idx;
+ ps_err_e ps_err;
+ struct ps_prochandle prochandle;
+
+ regcache_cooked_read_unsigned (regcache, I386_GS_REGNUM, &gs);
+
+ idx = gs >> reg_thread_area;
+ prochandle.pid = ptid_get_pid (inferior_ptid);
+ ps_err = ps_get_thread_area (&prochandle, prochandle.pid, idx, &tcbhead);
+ if (ps_err != PS_OK)
+ return 0;
+
+ *addr = (CORE_ADDR) tcbhead;
+ return 1;
+}
+
\f
/* The instruction for a GNU/Linux system call is:
@@ -843,6 +868,8 @@ _initialize_i386_linux_nat (void)
t->to_fetch_registers = i386_linux_fetch_inferior_registers;
t->to_store_registers = i386_linux_store_inferior_registers;
+ t->to_get_thread_area = i386_linux_get_thread_area;
+
/* Register the target. */
linux_nat_add_target (t);
linux_nat_set_new_thread (t, i386_linux_new_thread);
Index: src/gdb/i386-linux-tdep.c
===================================================================
--- src.orig/gdb/i386-linux-tdep.c 2009-06-12 22:46:40.000000000 +0100
+++ src/gdb/i386-linux-tdep.c 2009-06-13 00:23:58.000000000 +0100
@@ -447,6 +447,138 @@ static int i386_linux_sc_reg_offset[] =
0 * 4 /* %gs */
};
+static char *setjmp_names[] =
+ {
+ "setjmp",
+ "setjmp@plt",
+ "_setjmp",
+ "_setjmp@plt",
+ "__sigsetjmp",
+ "__sigsetjmp@plt"
+ };
+
+static int
+setjmp_p (CORE_ADDR pc)
+{
+ struct minimal_symbol *msym;
+ int i;
+ const char *symname;
+
+ msym = lookup_minimal_symbol_by_pc (pc);
+ if (msym == NULL)
+ return 0;
+
+ symname = SYMBOL_LINKAGE_NAME (msym);
+ if (symname == NULL)
+ return 0;
+
+ for (i = 0; i < ARRAY_SIZE (setjmp_names); i++)
+ if (strcmp (symname, setjmp_names[i]) == 0)
+ return 1;
+
+ return 0;
+}
+
+static int
+is_setjmp_return_p (CORE_ADDR address)
+{
+ gdb_byte buf[8];
+ if (target_read_memory (address - 5, buf, 5) == 0)
+ {
+ /* callq <32-bit offset> */
+ if (buf[0] == 0xe8)
+ {
+ /* eip-relative address */
+ int32_t disp = extract_signed_integer (&buf[1], sizeof (int32_t));
+ if (setjmp_p (address + disp))
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/* Rotate right X by N bits. */
+#define ror32(x, n) (((x) >> ((int) (n))) | ((x) << (32 - (int) (n))))
+
+static int
+i386_linux_get_longjmp_target (struct frame_info *frame, CORE_ADDR *pc)
+{
+ CORE_ADDR tcbhead;
+ CORE_ADDR pointer_guard;
+ CORE_ADDR address;
+ gdb_byte buf[4];
+ int status;
+ int ptr_guard_offset;
+
+ /* glibc pointer mangles the PC in jmp_buf. Unfortunately, there is
+ no blessed public API we can use to unmangle it, and, the
+ mangling algorithm itself has changed throughout glibc releases.
+ Some versions xor the address with a canary stored in the thread
+ control block, others apply an extra rotate on top for extra
+ mangling.
+
+ Users are supposed to be able to disable this mangling by
+ exporting LD_POINTER_GUARD=0, but, some versions got that wrong:
+ some got it backwards when set and require `LD_POINTER_GUARD=1';
+ others disable the xor, but still do the rotate.
+
+ What we do is, access glibc's private data, and try to reverse
+ the mangling using the different possible algorithms. We know
+ we've succeeded when the longjmp's target PC points at an
+ instruction right after a call to setjmp -- that is, that
+ setjmp's return. If all unmangling attempts fail, we assume that
+ the pristine PC extracted out of the jmp_buf is the best
+ choice. */
+
+ if (!i386_get_longjmp_target (frame, pc))
+ return 0;
+
+ address = *pc;
+
+ /* Check if we already have a valid return address. */
+ if (is_setjmp_return_p (address))
+ return 1;
+
+ /* The pointer guard is stored in tcbhead, at
+ offsetof (tcbhead_t, pointer_guard). */
+
+ if (!target_get_thread_area (&tcbhead))
+ return 1; /* Nothing more we can do. */
+
+ /* Wear sunglasses, please. This is private data. */
+
+ ptr_guard_offset = 0x18; /* offsetof (tcbhead_t, pointer_guard) */
+ status = target_read_memory (tcbhead + ptr_guard_offset, buf, 4);
+ if (status != 0)
+ /* Nothing more we can do. */
+ return 1;
+
+ pointer_guard = extract_unsigned_integer (buf, 4);
+
+ address = ror32 (address, 9);
+
+ /* On some versions of glibc, exporting LD_POINTER_GUARD=0 disables
+ the xor, but still does the rol. Check if we're there
+ already. */
+ if (is_setjmp_return_p (address))
+ {
+ *pc = address;
+ return 1;
+ }
+
+ address ^= pointer_guard;
+
+ /* Check if we successfully unmangled the address. */
+ if (is_setjmp_return_p (address))
+ {
+ *pc = address;
+ return 1;
+ }
+
+ return 1;
+}
+
static void
i386_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
{
@@ -468,6 +600,7 @@ i386_linux_init_abi (struct gdbarch_info
tdep->sizeof_gregset = 17 * 4;
tdep->jb_pc_offset = 20; /* From <bits/setjmp.h>. */
+ set_gdbarch_get_longjmp_target (gdbarch, i386_linux_get_longjmp_target);
tdep->sigtramp_p = i386_linux_sigtramp_p;
tdep->sigcontext_addr = i386_linux_sigcontext_addr;
Index: src/gdb/i386-tdep.h
===================================================================
--- src.orig/gdb/i386-tdep.h 2009-06-12 22:46:40.000000000 +0100
+++ src/gdb/i386-tdep.h 2009-06-12 22:59:23.000000000 +0100
@@ -221,6 +221,8 @@ extern void i386_elf_init_abi (struct gd
/* Initialize a SVR4 architecture variant. */
extern void i386_svr4_init_abi (struct gdbarch_info, struct gdbarch *);
+extern int i386_get_longjmp_target (struct frame_info *, CORE_ADDR *);
+
extern int i386_process_record (struct gdbarch *gdbarch,
struct regcache *regcache, CORE_ADDR addr);
\f
Index: src/gdb/target.h
===================================================================
--- src.orig/gdb/target.h 2009-06-12 22:46:47.000000000 +0100
+++ src/gdb/target.h 2009-06-13 00:23:44.000000000 +0100
@@ -445,6 +445,9 @@ struct target_ops
CORE_ADDR load_module_addr,
CORE_ADDR offset);
+ /* Returns true on success. */
+ int (*to_get_thread_area) (struct target_ops *ops, CORE_ADDR *addr);
+
/* Request that OPS transfer up to LEN 8-bit bytes of the target's
OBJECT. The OFFSET, for a seekable object, specifies the
starting point. The ANNEX can be used to provide additional
@@ -1090,6 +1093,8 @@ extern char *normal_pid_to_str (ptid_t p
#define target_region_ok_for_hw_watchpoint(addr, len) \
(*current_target.to_region_ok_for_hw_watchpoint) (addr, len)
+/* TCB. */
+extern int target_get_thread_area (CORE_ADDR *addr);
/* Set/clear a hardware watchpoint starting at ADDR, for LEN bytes. TYPE is 0
for write, 1 for read, and 2 for read/write accesses. Returns 0 for
Index: src/gdb/target.c
===================================================================
--- src.orig/gdb/target.c 2009-06-12 22:46:47.000000000 +0100
+++ src/gdb/target.c 2009-06-13 00:23:27.000000000 +0100
@@ -628,6 +628,7 @@ update_current_target (void)
INHERIT (to_find_memory_regions, t);
INHERIT (to_make_corefile_notes, t);
/* Do not inherit to_get_thread_local_address. */
+ /* Do not inherit to_get_thread_area. */
INHERIT (to_can_execute_reverse, t);
/* Do not inherit to_read_description. */
INHERIT (to_get_ada_task_ptid, t);
@@ -922,6 +923,20 @@ pop_all_targets (int quitting)
pop_all_targets_above (dummy_stratum, quitting);
}
+int
+target_get_thread_area (CORE_ADDR *addr)
+{
+ struct target_ops *target;
+
+ for (target = current_target.beneath;
+ target != NULL;
+ target = target->beneath)
+ if (target->to_get_thread_area != NULL)
+ return (*target->to_get_thread_area) (target, addr);
+
+ return 0;
+}
+
/* Using the objfile specified in OBJFILE, find the address for the
current thread's thread-local storage with offset OFFSET. */
CORE_ADDR
Index: src/gdb/amd64-linux-nat.c
===================================================================
--- src.orig/gdb/amd64-linux-nat.c 2009-06-12 22:46:35.000000000 +0100
+++ src/gdb/amd64-linux-nat.c 2009-06-13 00:24:17.000000000 +0100
@@ -391,6 +391,26 @@ ps_get_thread_area (const struct ps_proc
}
return PS_ERR; /* ptrace failed. */
}
+
+static int
+amd64_linux_get_thread_area (struct target_ops *ops, CORE_ADDR *addr)
+{
+ int len = TYPE_LENGTH (builtin_type (target_gdbarch)->builtin_func_ptr);
+ void *tcbhead;
+ ps_err_e ps_err;
+ struct ps_prochandle prochandle;
+ int idx;
+
+ idx = FS;
+ prochandle.ptid = inferior_ptid;
+ ps_err = ps_get_thread_area (&prochandle, GET_LWP (prochandle.ptid),
+ idx, &tcbhead);
+ if (ps_err != PS_OK)
+ return 0;
+
+ *addr = (CORE_ADDR) tcbhead;
+ return 1;
+}
\f
static void (*super_post_startup_inferior) (ptid_t ptid);
@@ -682,6 +702,8 @@ _initialize_amd64_linux_nat (void)
t->to_fetch_registers = amd64_linux_fetch_inferior_registers;
t->to_store_registers = amd64_linux_store_inferior_registers;
+ t->to_get_thread_area = amd64_linux_get_thread_area;
+
/* Register the target. */
linux_nat_add_target (t);
linux_nat_set_new_thread (t, amd64_linux_new_thread);
Index: src/gdb/amd64-linux-tdep.c
===================================================================
--- src.orig/gdb/amd64-linux-tdep.c 2009-06-12 22:46:02.000000000 +0100
+++ src/gdb/amd64-linux-tdep.c 2009-06-13 00:22:03.000000000 +0100
@@ -36,6 +36,9 @@
#include "amd64-tdep.h"
#include "solib-svr4.h"
+#include "target.h"
+#include "inferior.h"
+
/* Mapping between the general-purpose registers in `struct user'
format and GDB's register cache layout. */
@@ -258,6 +261,139 @@ amd64_linux_write_pc (struct regcache *r
regcache_cooked_write_unsigned (regcache, AMD64_LINUX_ORIG_RAX_REGNUM, -1);
}
+static char *setjmp_names[] =
+ {
+ "setjmp",
+ "setjmp@plt",
+ "_setjmp",
+ "_setjmp@plt",
+ "__sigsetjmp",
+ "__sigsetjmp@plt"
+ };
+
+static int
+setjmp_p (CORE_ADDR pc)
+{
+ struct minimal_symbol *msym;
+ int i;
+ const char *symname;
+
+ msym = lookup_minimal_symbol_by_pc (pc);
+ if (msym == NULL)
+ return 0;
+
+ symname = SYMBOL_LINKAGE_NAME (msym);
+ if (symname == NULL)
+ return 0;
+
+ for (i = 0; i < ARRAY_SIZE (setjmp_names); i++)
+ if (strcmp (symname, setjmp_names[i]) == 0)
+ return 1;
+
+ return 0;
+}
+
+static int
+is_setjmp_return_p (CORE_ADDR address)
+{
+ gdb_byte buf[8];
+ if (target_read_memory (address - 5, buf, 5) == 0)
+ {
+ /* callq <32-bit offset> */
+ if (buf[0] == 0xe8)
+ {
+ /* rip-relative address */
+ int32_t disp = extract_signed_integer (&buf[1], sizeof (int32_t));
+ if (setjmp_p (address + disp))
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/* Rotate right X by N bits. */
+#define ror64(x, n) (((x) >> ((int) (n))) | ((x) << (64 - (int) (n))))
+
+static int
+amd64_linux_get_longjmp_target (struct frame_info *frame, CORE_ADDR *pc)
+{
+ CORE_ADDR tcbhead;
+ CORE_ADDR pointer_guard;
+ CORE_ADDR address;
+ gdb_byte buf[8];
+ int status;
+ int offset;
+ int ptr_guard_offset;
+
+ /* glibc pointer mangles the PC in jmp_buf. Unfortunately, there is
+ no blessed public API we can use to unmangle it, and, the
+ mangling algorithm itself has changed throughout glibc releases.
+ Some versions xor the address with a canary stored in the thread
+ control block, others apply an extra rotate on top for extra
+ mangling.
+
+ Users are supposed to be able to disable this mangling by
+ exporting LD_POINTER_GUARD=0, but, some versions got that wrong:
+ some got it backwards when set and require `LD_POINTER_GUARD=1';
+ others disable the xor, but still do the rotate.
+
+ What we do is, access glibc's private data, and try to reverse
+ the mangling using the different possible algorithms. We know
+ we've succeeded when the longjmp's target PC points at an
+ instruction right after a call to setjmp -- that is, that
+ setjmp's return. If all unmangling attempts fail, we assume that
+ the pristine PC extracted out of the jmp_buf is the best
+ choice. */
+
+ /* Extract the PC from the jmp_buf. */
+ if (!amd64_get_longjmp_target (frame, pc))
+ return 0;
+
+ address = *pc;
+
+ /* Check if we already have a valid return address. */
+ if (is_setjmp_return_p (address))
+ return 1;
+
+ /* The pointer guard is stored in tcbhead, at
+ offsetof (tcbhead_t, pointer_guard). */
+
+ if (!target_get_thread_area (&tcbhead))
+ return 1; /* Nothing more we can do. */
+
+ /* Wear sunglasses, please. This is private data. */
+ ptr_guard_offset = 0x30; /* offsetof (tcbhead_t, pointer_guard) */
+ status = target_read_memory (tcbhead + ptr_guard_offset, buf, 8);
+ if (status != 0)
+ /* Nothing more we can do. */
+ return 1;
+
+ pointer_guard = extract_unsigned_integer (buf, 8);
+
+ address = ror64 (address, 17);
+
+ /* On some versions of glibc, exporting LD_POINTER_GUARD=0 disables
+ the xor, but still does the rol. Check if we're there
+ already. */
+ if (is_setjmp_return_p (address))
+ {
+ *pc = address;
+ return 1;
+ }
+
+ address ^= pointer_guard;
+
+ /* Check if we successfully unmangled the address. */
+ if (is_setjmp_return_p (address))
+ {
+ *pc = address;
+ return 1;
+ }
+
+ return 1;
+}
+
static void
amd64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
{
@@ -274,6 +410,9 @@ amd64_linux_init_abi (struct gdbarch_inf
tdep->sc_reg_offset = amd64_linux_sc_reg_offset;
tdep->sc_num_regs = ARRAY_SIZE (amd64_linux_sc_reg_offset);
+ tdep->jb_pc_offset = 7 * 8;
+ set_gdbarch_get_longjmp_target (gdbarch, amd64_linux_get_longjmp_target);
+
/* GNU/Linux uses SVR4-style shared libraries. */
set_solib_svr4_fetch_link_map_offsets
(gdbarch, svr4_lp64_fetch_link_map_offsets);
Index: src/gdb/amd64-tdep.h
===================================================================
--- src.orig/gdb/amd64-tdep.h 2009-06-12 22:46:02.000000000 +0100
+++ src/gdb/amd64-tdep.h 2009-06-12 22:58:41.000000000 +0100
@@ -98,6 +98,8 @@ extern void amd64_supply_fxsave (struct
extern void amd64_collect_fxsave (const struct regcache *regcache, int regnum,
void *fxsave);
+
+extern int amd64_get_longjmp_target (struct frame_info *frame, CORE_ADDR *pc);
\f
/* Variables exported from amd64nbsd-tdep.c. */
Index: src/gdb/amd64-tdep.c
===================================================================
--- src.orig/gdb/amd64-tdep.c 2009-06-12 22:46:35.000000000 +0100
+++ src/gdb/amd64-tdep.c 2009-06-12 22:58:41.000000000 +0100
@@ -1950,7 +1950,7 @@ amd64_regset_from_core_section (struct g
address is copied into PC. This routine returns non-zero on
success. */
-static int
+int
amd64_get_longjmp_target (struct frame_info *frame, CORE_ADDR *pc)
{
gdb_byte buf[8];
Index: src/gdb/testsuite/gdb.base/longjmp.c
===================================================================
--- src.orig/gdb/testsuite/gdb.base/longjmp.c 2009-02-13 19:04:32.000000000 +0000
+++ src/gdb/testsuite/gdb.base/longjmp.c 2009-06-13 00:25:57.000000000 +0100
@@ -19,6 +19,7 @@
#include <setjmp.h>
jmp_buf env;
+jmp_buf env2;
volatile int longjmps = 0;
volatile int resumes = 0;
@@ -33,7 +34,7 @@ call_longjmp (jmp_buf *buf)
void
hidden_longjmp (void)
{
- if (setjmp (env) == 0) /* longjmp caught */
+ if (setjmp (env) == 0)
{
call_longjmp (&env);
}
@@ -41,6 +42,51 @@ hidden_longjmp (void)
resumes++;
}
+void
+hidden_longjmp_2 (void)
+{
+ if (setjmp (env) == 0)
+ {
+ if (setjmp (env2) == 0)
+ {
+ longjmps++;
+ longjmp (env2, 1);
+ }
+ else
+ {
+ resumes++;
+ longjmps++;
+ longjmp (env, 1);
+ }
+ }
+ else
+ {
+ resumes++;
+ }
+}
+
+void
+hidden_longjmp_3_1 (void)
+{
+ if (setjmp (env2) == 0)
+ {
+ longjmps++;
+ longjmp (env2, 1);
+ }
+ else
+ {
+ resumes++;
+ longjmps++;
+ longjmp (env, 1);
+ }
+}
+
+void
+hidden_longjmp_3 (void)
+{
+ hidden_longjmp_3_1 ();
+}
+
int
main ()
{
@@ -72,10 +118,43 @@ main ()
i = 2; /* miss_step_2 */
- /* Pattern 3 - setjmp/longjmp inside stepped-over function. */
- hidden_longjmp (); /* patt3 */
+ /* This tests if GDB chooses the longjmp-resume over the
+ step-resume breakpoint. If GDB chooses wrongly, a step over the
+ hidden_longjmp_3 function will resume the inferior and pass
+ straight the else clause without stopping to step. */
+ if (setjmp (env) == 0) /* patt3 */
+ hidden_longjmp_3 ();
+ else
+ resumes++;
+
+ i = 3; /* miss_step_3 */
+
+
+ /* Quite similar, but in this case, we step into hidden_longjmp_3
+ before next'ing over the longjmp. In this case, the step-resume
+ breakpoint will be set in an inner stack. Check if GDB still
+ detects that the longjmp-resume address is prefered. */
+ if (setjmp (env) == 0) /* patt4 */
+ hidden_longjmp_3 ();
+ else
+ resumes++;
+
+ i = 4; /* miss_step_4 */
+
+
+ /* Test that we don't miss-handle internal setjmp/longjmp
+ sequences. */
+
+ /* setjmp/longjmp handled completelly inside the function. A next
+ over this should not stop at the longjmp resume address. */
+ hidden_longjmp (); /* patt5 */
+
+ i = 5; /* patt_end5. */
+
+ /* Same, but with a nested setjmp/longjmp sequence. */
+ hidden_longjmp_2 (); /* patt6 */
- i = 3; /* patt_end3. */
+ i = 6; /* patt_end6. */
return 0;
}
Index: src/gdb/testsuite/gdb.base/longjmp.exp
===================================================================
--- src.orig/gdb/testsuite/gdb.base/longjmp.exp 2009-02-10 01:28:04.000000000 +0000
+++ src/gdb/testsuite/gdb.base/longjmp.exp 2009-06-14 20:31:19.000000000 +0100
@@ -49,10 +49,15 @@ if ![runto_main] then {
set bp_miss_step_1 [gdb_get_line_number "miss_step_1"]
set bp_miss_step_2 [gdb_get_line_number "miss_step_2"]
+set bp_miss_step_3 [gdb_get_line_number "miss_step_3"]
+set bp_miss_step_4 [gdb_get_line_number "miss_step_4"]
set bp_start_test_1 [gdb_get_line_number "patt1"]
set bp_start_test_2 [gdb_get_line_number "patt2"]
set bp_start_test_3 [gdb_get_line_number "patt3"]
+set bp_start_test_4 [gdb_get_line_number "patt4"]
+set bp_start_test_5 [gdb_get_line_number "patt5"]
+set bp_start_test_6 [gdb_get_line_number "patt6"]
#
# Pattern 1 - simple longjmp.
@@ -117,7 +122,7 @@ gdb_test_multiple "next" $msg {
}
#
-# Pattern 3 - setjmp/longjmp inside stepped-over function.
+# Pattern 3 - prefer longjmp-resume
#
delete_breakpoints
@@ -125,6 +130,87 @@ delete_breakpoints
gdb_test "break $bp_start_test_3" \
"Breakpoint.*at.* file .*$srcfile, line.*$bp_start_test_3.*" \
"breakpoint at pattern 3 start"
-gdb_test "continue" "patt3.*" "continue to breakpoint at pattern 3 start"
+gdb_test "continue" "patt3.*" \
+ "continue to breakpoint at pattern 3 start"
+
+
+# set safe-net break
+gdb_test "break $bp_miss_step_3" \
+ "Breakpoint.*at.* file .*$srcfile, line.*$bp_miss_step_3.*" \
+ "breakpoint at miss_step_3"
+
+gdb_test "next" "hidden_longjmp_3 \\(\\).*" "next over setjmp (3)"
+
+set msg "next over hidden_longjmp_3 (3)"
+gdb_test_multiple "next" $msg {
+ -re "patt3.*$gdb_prompt $" {
+ pass $msg
+
+ gdb_test "next" "resumes\\+\\+.*" "next into else block (3)"
+ gdb_test "next" "miss_step_3.*" "next into safety net (3)"
+ }
+ -re "miss_step_3.*$gdb_prompt $" {
+ fail $msg
+ }
+}
+
+
+#
+# Pattern 4 - prefer longjmp-resume after step
+#
+
+delete_breakpoints
+
+gdb_test "break $bp_start_test_4" \
+ "Breakpoint.*at.* file .*$srcfile, line.*$bp_start_test_4.*" \
+ "breakpoint at pattern 4 start"
+gdb_test "continue" "patt4.*" "continue to breakpoint at pattern 4 start"
+
+# set safe-net break
+gdb_test "break $bp_miss_step_4" \
+ "Breakpoint.*at.* file .*$srcfile, line.*$bp_miss_step_4.*" \
+ "breakpoint at miss_step_4"
+
+gdb_test "next" "hidden_longjmp_3 \\(\\).*" "next over setjmp (4)"
+gdb_test "step" "hidden_longjmp_3_1 \\(\\).*" "step into hidden_longjmp_3 (4)"
+
+set msg "next over hidden_longjmp_3_1 (4)"
+gdb_test_multiple "next" $msg {
+ -re "patt4.*$gdb_prompt $" {
+ pass $msg
+
+ gdb_test "next" "resumes\\+\\+.*" "next into else block (4)"
+ gdb_test "next" "miss_step_4.*" "next safety net (4)"
+ }
+ -re "miss_step_4.*$gdb_prompt $" {
+ fail $msg
+ }
+}
+
+#
+# Pattern 5 - setjmp/longjmp handled inside stepped-over function.
+#
+
+delete_breakpoints
+
+gdb_test "break $bp_start_test_5" \
+ "Breakpoint.*at.* file .*$srcfile, line.*$bp_start_test_5.*" \
+ "breakpoint at pattern 5 start"
+gdb_test "continue" "patt5.*" "continue to breakpoint at pattern 5 start"
+
+gdb_test "next" "patt_end5.*" "next over patt5"
+
+#
+# Pattern 6 - nested setjmp/longjmp handled inside stepped-over
+# function.
+#
+
+delete_breakpoints
+
+gdb_test "break $bp_start_test_6" \
+ "Breakpoint.*at.* file .*$srcfile, line.*$bp_start_test_6.*" \
+ "breakpoint at pattern 6 start"
+gdb_test "continue" "patt6.*" "continue to breakpoint at pattern 6 start"
+
+gdb_test "next" "patt_end6.*" "next over patt6"
-gdb_test "next" "longjmp caught.*" "next over patt3"
Index: src/gdb/gdbthread.h
===================================================================
--- src.orig/gdb/gdbthread.h 2009-06-14 20:29:54.000000000 +0100
+++ src/gdb/gdbthread.h 2009-06-14 20:31:26.000000000 +0100
@@ -63,9 +63,12 @@ struct thread_info
/* User/external stepping state. */
- /* Step-resume or longjmp-resume breakpoint. */
+ /* Step-resume breakpoint. */
struct breakpoint *step_resume_breakpoint;
+ /* longjmp-resume breakpoint. */
+ struct breakpoint *longjmp_resume_breakpoint;
+
/* Range to single step within.
If this is nonzero, respond to a single-step signal by continuing
@@ -212,6 +215,9 @@ extern void delete_thread_silent (ptid_t
/* Delete a step_resume_breakpoint from the thread database. */
extern void delete_step_resume_breakpoint (struct thread_info *);
+/* Delete a longjmp_resume_breakpoint from the thread database. */
+extern void delete_longjmp_resume_breakpoint (struct thread_info *);
+
/* Translate the integer thread id (GDB's homegrown id, not the system's)
into a "pid" (which may be overloaded with extra thread information). */
extern ptid_t thread_id_to_pid (int);
Index: src/gdb/infrun.c
===================================================================
--- src.orig/gdb/infrun.c 2009-06-14 20:29:54.000000000 +0100
+++ src/gdb/infrun.c 2009-06-14 21:27:24.000000000 +0100
@@ -495,6 +495,9 @@ follow_exec (ptid_t pid, char *execd_pat
th->step_range_start = 0;
th->step_range_end = 0;
+ /* Neither can we follow a longjmp through an exec. */
+ th->longjmp_resume_breakpoint = NULL;
+
/* The target reports the exec event to the main thread, even if
some other thread does the exec, and even if the main thread was
already stopped --- if debugging in non-stop mode, it's possible
@@ -1873,21 +1876,22 @@ infrun_thread_thread_exit (struct thread
/* Callback for iterate_over_threads. */
static int
-delete_step_resume_breakpoint_callback (struct thread_info *info, void *data)
+delete_momentary_breakpoints_callback (struct thread_info *info, void *data)
{
if (is_exited (info->ptid))
return 0;
delete_step_resume_breakpoint (info);
+ delete_longjmp_resume_breakpoint (info);
return 0;
}
-/* In all-stop, delete the step resume breakpoint of any thread that
- had one. In non-stop, delete the step resume breakpoint of the
+/* In all-stop, delete the step and longjmp resume breakpoints of any
+ thread that had any. In non-stop, delete the breakpoint of the
thread that just stopped. */
static void
-delete_step_thread_step_resume_breakpoint (void)
+delete_momentary_breakpoints (void)
{
if (!target_has_execution
|| ptid_equal (inferior_ptid, null_ptid))
@@ -1900,21 +1904,20 @@ delete_step_thread_step_resume_breakpoin
/* If in non-stop mode, only delete the step-resume or
longjmp-resume breakpoint of the thread that just stopped
stepping. */
- struct thread_info *tp = inferior_thread ();
- delete_step_resume_breakpoint (tp);
+ delete_momentary_breakpoints_callback (inferior_thread (), NULL);
}
else
/* In all-stop mode, delete all step-resume and longjmp-resume
- breakpoints of any thread that had them. */
- iterate_over_threads (delete_step_resume_breakpoint_callback, NULL);
+ breakpoints of all threads. */
+ iterate_over_threads (delete_momentary_breakpoints_callback, NULL);
}
/* A cleanup wrapper. */
static void
-delete_step_thread_step_resume_breakpoint_cleanup (void *arg)
+delete_momentary_breakpoints_cleanup (void *arg)
{
- delete_step_thread_step_resume_breakpoint ();
+ delete_momentary_breakpoints ();
}
/* Pretty print the results of target_wait, for debugging purposes. */
@@ -1981,8 +1984,9 @@ wait_for_inferior (int treat_exec_as_sig
(gdb_stdlog, "infrun: wait_for_inferior (treat_exec_as_sigtrap=%d)\n",
treat_exec_as_sigtrap);
- old_cleanups =
- make_cleanup (delete_step_thread_step_resume_breakpoint_cleanup, NULL);
+ /* Be sure to delete step-resume and similar breakpoints if
+ something goes wrong. */
+ old_cleanups = make_cleanup (delete_momentary_breakpoints_cleanup, NULL);
ecs = &ecss;
memset (ecs, 0, sizeof (*ecs));
@@ -2114,7 +2118,7 @@ fetch_inferior_event (void *client_data)
{
struct inferior *inf = find_inferior_pid (ptid_get_pid (ecs->ptid));
- delete_step_thread_step_resume_breakpoint ();
+ delete_momentary_breakpoints (); /* step-resume, etc */
/* We may not find an inferior if this was a process exit. */
if (inf == NULL || inf->stop_soon == NO_STOP_QUIETLY)
@@ -3363,10 +3367,6 @@ infrun: BPSTAT_WHAT_SET_LONGJMP_RESUME (
return;
}
- /* We're going to replace the current step-resume breakpoint
- with a longjmp-resume breakpoint. */
- delete_step_resume_breakpoint (ecs->event_thread);
-
/* Insert a breakpoint at resume address. */
insert_longjmp_resume_breakpoint (jmp_buf_pc);
@@ -3374,17 +3374,67 @@ infrun: BPSTAT_WHAT_SET_LONGJMP_RESUME (
return;
case BPSTAT_WHAT_CLEAR_LONGJMP_RESUME:
- if (debug_infrun)
- fprintf_unfiltered (gdb_stdlog,
- "infrun: BPSTAT_WHAT_CLEAR_LONGJMP_RESUME\n");
+ {
+ struct thread_info *tp = ecs->event_thread;
+ struct frame_info *frame = get_current_frame ();
+ struct gdbarch *gdbarch = get_frame_arch (frame);
- gdb_assert (ecs->event_thread->step_resume_breakpoint != NULL);
- delete_step_resume_breakpoint (ecs->event_thread);
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog,
+ "infrun: BPSTAT_WHAT_CLEAR_LONGJMP_RESUME\n");
- ecs->event_thread->stop_step = 1;
- print_stop_reason (END_STEPPING_RANGE, 0);
- stop_stepping (ecs);
- return;
+ gdb_assert (tp->longjmp_resume_breakpoint != NULL);
+ delete_longjmp_resume_breakpoint (tp);
+
+ /* Ignore long jumps that land somewhere inner to what we
+ were nexting. Otherwise, if the longjmp took us to an
+ outer frame compared to the nexting frame, the
+ step-resume breakpoint location will never be reached, so
+ stop now. */
+ if (tp->step_resume_breakpoint)
+ {
+ /* If we can still see the frame where we placed the
+ step-resume breakpoint in the frame chain, then we're
+ now somewhere inner. If not, then one of two things
+ happened:
+
+ - The longjmp took us somewhere outer to the nexting
+ frame --- stop now.
+
+ - For some reason we couldn't unwind all the way to
+ the nexting frame (e.g., bad unwinding data for some
+ frame in between). Ideally, we never see this
+ happen. However, if it does happen, we get a
+ spurious stop (that is what would happen if we always
+ stopped at longjmp-resumes anyway), which is much
+ better than having the process run away. */
+ if (!frame_id_eq (get_frame_id (frame),
+ tp->step_resume_breakpoint->frame_id)
+ && frame_find_by_id (tp->step_resume_breakpoint->frame_id))
+ {
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog, "\
+infrun: longjmp-resume inner than step-resume\n");
+
+ keep_going (ecs);
+ return;
+ }
+ else
+ {
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog, "\
+infrun: step-resume overran by longjmp\n");
+
+ /* No longer need this. */
+ delete_step_resume_breakpoint (tp);
+ }
+ }
+
+ ecs->event_thread->stop_step = 1;
+ print_stop_reason (END_STEPPING_RANGE, 0);
+ stop_stepping (ecs);
+ return;
+ }
case BPSTAT_WHAT_SINGLE:
if (debug_infrun)
@@ -3610,6 +3660,19 @@ infrun: not switching back to stepped th
return;
}
+ if (ecs->event_thread->longjmp_resume_breakpoint)
+ {
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog,
+ "infrun: longjmp-resume breakpoint is inserted\n");
+
+ /* Having a longjmp-resume breakpoint overrides anything else
+ having to do with stepping commands until that breakpoint is
+ reached. */
+ keep_going (ecs);
+ return;
+ }
+
if (ecs->event_thread->step_range_end == 0)
{
if (debug_infrun)
@@ -3988,7 +4051,9 @@ infrun: not switching back to stepped th
static int
currently_stepping (struct thread_info *tp)
{
- return ((tp->step_range_end && tp->step_resume_breakpoint == NULL)
+ return ((tp->step_range_end
+ && tp->step_resume_breakpoint == NULL
+ && tp->longjmp_resume_breakpoint == NULL)
|| tp->trap_expected
|| tp->stepping_through_solib_after_catch
|| bpstat_should_step ());
@@ -4205,17 +4270,17 @@ insert_step_resume_breakpoint_at_caller
static void
insert_longjmp_resume_breakpoint (CORE_ADDR pc)
{
- /* There should never be more than one step-resume or longjmp-resume
- breakpoint per thread, so we should never be setting a new
+ /* There should never be more than one longjmp-resume breakpoint per
+ thread, so we should never be setting a new
longjmp_resume_breakpoint when one is already active. */
- gdb_assert (inferior_thread ()->step_resume_breakpoint == NULL);
+ gdb_assert (inferior_thread ()->longjmp_resume_breakpoint == NULL);
if (debug_infrun)
fprintf_unfiltered (gdb_stdlog,
"infrun: inserting longjmp-resume breakpoint at 0x%s\n",
paddr_nz (pc));
- inferior_thread ()->step_resume_breakpoint =
+ inferior_thread ()->longjmp_resume_breakpoint =
set_momentary_breakpoint_at_pc (pc, bp_longjmp_resume);
}
Index: src/gdb/thread.c
===================================================================
--- src.orig/gdb/thread.c 2009-06-14 20:29:54.000000000 +0100
+++ src/gdb/thread.c 2009-06-14 20:31:26.000000000 +0100
@@ -90,6 +90,16 @@ delete_step_resume_breakpoint (struct th
}
}
+void
+delete_longjmp_resume_breakpoint (struct thread_info *tp)
+{
+ if (tp && tp->longjmp_resume_breakpoint)
+ {
+ delete_breakpoint (tp->longjmp_resume_breakpoint);
+ tp->longjmp_resume_breakpoint = NULL;
+ }
+}
+
static void
clear_thread_inferior_resources (struct thread_info *tp)
{
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: RFC: Longjmp vs LD_POINTER_GUARD revisited
2009-11-16 14:55 ` Pedro Alves
@ 2009-11-16 14:56 ` Pedro Alves
2009-11-16 15:05 ` Pedro Alves
2009-11-16 17:50 ` Daniel Jacobowitz
2 siblings, 0 replies; 18+ messages in thread
From: Pedro Alves @ 2009-11-16 14:56 UTC (permalink / raw)
To: Daniel Jacobowitz; +Cc: Paul Pluzhnikov, gdb-patches, Ulrich Weigand
On Monday 16 November 2009 14:53:50, Pedro Alves wrote:
> cases. I had tried it on netbsd and Windows too. NetBSD
Err, thinko. I meant OpenBSD...
--
Pedro Alves
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: RFC: Longjmp vs LD_POINTER_GUARD revisited
2009-11-16 14:55 ` Pedro Alves
2009-11-16 14:56 ` Pedro Alves
@ 2009-11-16 15:05 ` Pedro Alves
2009-11-16 17:50 ` Daniel Jacobowitz
2 siblings, 0 replies; 18+ messages in thread
From: Pedro Alves @ 2009-11-16 15:05 UTC (permalink / raw)
To: gdb-patches; +Cc: Daniel Jacobowitz, Paul Pluzhnikov, Ulrich Weigand
On Monday 16 November 2009 14:53:50, Pedro Alves wrote:
> - I've tried a similar hack as yours and found that I had
> to add several more "functions still within longjmp" special
> cases. I had tried it on netbsd and Windows too. NetBSD
> was also bad at needing special casing. I'll post the relevant
> bits of the patch in a bit, when I find it :-)
>
Here they are...
--
Pedro Alves
Index: src/gdb/infrun.c
===================================================================
--- src.orig/gdb/infrun.c 2009-07-25 16:47:07.000000000 +0100
+++ src/gdb/infrun.c 2009-07-25 18:28:50.000000000 +0100
@@ -1330,6 +1330,9 @@ clear_proceed_status_thread (struct thre
tp->proceed_to_finish = 0;
+ tp->stepping_through_longjmp = 0;
+ tp->longjmp_frame = null_frame_id;
+
/* Discard any remaining commands or status from previous stop. */
bpstat_clear (&tp->stop_bpstat);
}
@@ -3248,7 +3251,8 @@ targets should add new threads to the th
= !(bpstat_explains_signal (ecs->event_thread->stop_bpstat)
|| ecs->event_thread->trap_expected
|| (ecs->event_thread->step_range_end
- && ecs->event_thread->step_resume_breakpoint == NULL));
+ && ecs->event_thread->step_resume_breakpoint == NULL)
+ || ecs->event_thread->stepping_through_longjmp);
else
{
ecs->random_signal = !bpstat_explains_signal (ecs->event_thread->stop_bpstat);
@@ -3374,7 +3378,6 @@ process_event_stop_test:
/* Handle cases caused by hitting a breakpoint. */
{
- CORE_ADDR jmp_buf_pc;
struct bpstat_what what;
what = bpstat_what (ecs->event_thread->stop_bpstat);
@@ -3387,33 +3390,19 @@ process_event_stop_test:
switch (what.main_action)
{
case BPSTAT_WHAT_SET_LONGJMP_RESUME:
- /* If we hit the breakpoint at longjmp while stepping, we
- install a momentary breakpoint at the target of the
- jmp_buf. */
-
+ /* If we hit the breakpoint at longjmp while stepping, prepare
+ to step all the way through it. */
if (debug_infrun)
fprintf_unfiltered (gdb_stdlog,
"infrun: BPSTAT_WHAT_SET_LONGJMP_RESUME\n");
+ /* Step over this longjmp breakpoint. */
ecs->event_thread->stepping_over_breakpoint = 1;
- if (!gdbarch_get_longjmp_target_p (gdbarch)
- || !gdbarch_get_longjmp_target (gdbarch, frame, &jmp_buf_pc))
- {
- if (debug_infrun)
- fprintf_unfiltered (gdb_stdlog, "\
-infrun: BPSTAT_WHAT_SET_LONGJMP_RESUME (!gdbarch_get_longjmp_target)\n");
- keep_going (ecs);
- return;
- }
-
- /* We're going to replace the current step-resume breakpoint
- with a longjmp-resume breakpoint. */
- delete_step_resume_breakpoint (ecs->event_thread);
-
- /* Insert a breakpoint at resume address. */
- insert_longjmp_resume_breakpoint (gdbarch, jmp_buf_pc);
-
+ /* Store longjmp's frame id. We'll single-step until an outer
+ frame becomes current. */
+ ecs->event_thread->longjmp_frame = get_frame_id (get_current_frame ());
+ ecs->event_thread->stepping_through_longjmp = 1;
keep_going (ecs);
return;
@@ -3641,6 +3630,135 @@ infrun: not switching back to stepped th
return;
}
+ if (ecs->event_thread->stepping_through_longjmp)
+ {
+ struct thread_info *tp = ecs->event_thread;
+ struct frame_info *frame = get_current_frame ();
+ struct gdbarch *gdbarch = get_frame_arch (frame);
+ struct minimal_symbol *msymbol;
+
+ /* While we're still inside longjmp, in most implementations,
+ matching by frame id won't work, unfortunately, either
+ because unwind info is missing the correct annotations, or
+ because the frame parsers have a hard time with the PC, SP,
+ and FP juggling going on inside longjmp. Libc
+ implementations that apply some form of mangling to
+ jmp_buf's are the worse offenders. There's another caveat
+ here: if we find ourselves in code that has no symbol info
+ whatsoever, we don't really know if we're still inside
+ longjmp, or, if we've reached a setjmp landing site that had
+ no debug info. We're assuming the former. */
+
+ if (in_solib_dynsym_resolve_code (get_frame_pc (frame)))
+ {
+ CORE_ADDR pc_after_resolver =
+ gdbarch_skip_solib_resolver (get_frame_arch (frame), get_frame_pc (frame));
+
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog, "infrun: stepped into dynsym resolve code\n");
+
+ if (pc_after_resolver)
+ {
+ /* Set up a step-resume breakpoint at the address
+ indicated by SKIP_SOLIB_RESOLVER. */
+ struct symtab_and_line sr_sal;
+ init_sal (&sr_sal);
+ sr_sal.pc = pc_after_resolver;
+
+ insert_step_resume_breakpoint_at_sal (gdbarch,
+ sr_sal, null_frame_id);
+ }
+
+ keep_going (ecs);
+ return;
+ }
+
+ msymbol = lookup_minimal_symbol_by_pc (get_frame_pc (frame));
+ if (msymbol == NULL)
+ {
+ /* We're still supposedly in longjmp, keep going. Should
+ perhaps check if we're still in the same module (that
+ is, catch going out of libc.so?) */
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog,
+ "infrun: stepping through longjmp (in ??\?)\n");
+ keep_going (ecs);
+ return;
+ }
+ else if (strcmp (SYMBOL_LINKAGE_NAME (msymbol), "longjmp") == 0
+ || strcmp (SYMBOL_LINKAGE_NAME (msymbol), "_longjmp") == 0
+ || strcmp (SYMBOL_LINKAGE_NAME (msymbol), "siglongjmp") == 0
+ || strcmp (SYMBOL_LINKAGE_NAME (msymbol), "_siglongjmp") == 0)
+ {
+ /* Update the recorded frame id, as it is likely to not be
+ stable (in a perfect world, it would be, though). */
+ tp->longjmp_frame = get_frame_id (frame);
+
+ /* We're still in longjmp, keep going. */
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog,
+ "infrun: stepping through longjmp (in longjmp)\n");
+ keep_going (ecs);
+ return;
+ }
+ /* Make this an uglier than ugly architecture specific callback
+ "un-unwindable functions that are called by longjmp, and
+ can't be longjmp landing sites" list? Sigh... */
+ else if (strcmp (SYMBOL_LINKAGE_NAME (msymbol),
+ "__pthread_cleanup_upto") == 0 /* linux */
+ /* openbsd */
+ || strcmp (SYMBOL_LINKAGE_NAME (msymbol), "sigsetmask") == 0)
+ {
+ /* We're still waiting for the longjmp, keep going. */
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog,
+ "infrun: stepping through longjmp (in callee)\n");
+ keep_going (ecs);
+ return;
+ }
+ else if (frame_find_by_id (tp->longjmp_frame) != NULL)
+ {
+ /* We still have the longjmp frame in the frame chain, keep
+ going. In theory, we would only need this check. */
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog,
+ "infrun: stepping through longjmp (in frame chain)\n");
+ keep_going (ecs);
+ return;
+ }
+
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog, "infrun: got out of longjmp\n");
+
+ /* We made it. */
+ tp->stepping_through_longjmp = 0;
+
+ /* If there's a step-resume breakpoint set, decide if we should
+ keep stepping to the step-resume breakpoint, or if the
+ longjmp took us outermost already, hence the step-resume
+ breakpoint will never be hit, and we should stop now. */
+ if (ecs->event_thread->step_resume_breakpoint)
+ {
+ if (!frame_id_eq (get_frame_id (frame),
+ tp->step_resume_breakpoint->frame_id)
+ && frame_find_by_id (tp->step_resume_breakpoint->frame_id))
+ {
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog, "\
+infrun: longjmp-resume inner than step-resume\n");
+ }
+ else
+ {
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog,
+ "infrun: step-resume overran by longjmp\n");
+ delete_step_resume_breakpoint (ecs->event_thread);
+ stop_stepping (ecs);
+ return;
+ }
+ }
+ }
+
if (ecs->event_thread->step_resume_breakpoint)
{
if (debug_infrun)
@@ -4165,7 +4283,8 @@ currently_stepping (struct thread_info *
return ((tp->step_range_end && tp->step_resume_breakpoint == NULL)
|| tp->trap_expected
|| tp->stepping_through_solib_after_catch
- || bpstat_should_step ());
+ || bpstat_should_step ()
+ || tp->stepping_through_longjmp);
}
/* Returns true if any thread *but* the one passed in "data" is in the
@@ -4179,7 +4298,8 @@ currently_stepping_or_nexting_callback (
return (tp->step_range_end
|| tp->trap_expected
- || tp->stepping_through_solib_after_catch);
+ || tp->stepping_through_solib_after_catch
+ || tp->stepping_through_longjmp);
}
/* Inferior has stepped into a subroutine call with source code that
Index: src/gdb/gdbthread.h
===================================================================
--- src.orig/gdb/gdbthread.h 2009-07-25 16:47:07.000000000 +0100
+++ src/gdb/gdbthread.h 2009-07-25 17:06:40.000000000 +0100
@@ -185,6 +185,12 @@ struct thread_info
/* True if this thread has been explicitly requested to stop. */
int stop_requested;
+ /* True if a longjmp call was detected while stepping, and we're
+ single-stepping until we reach the other end. */
+ int stepping_through_longjmp;
+ /* The frame of the longjmp we're currently handling. */
+ struct frame_id longjmp_frame;
+
/* Private data used by the target vector implementation. */
struct private_thread_info *private;
};
Index: src/gdb/breakpoint.c
===================================================================
--- src.orig/gdb/breakpoint.c 2009-07-25 16:47:07.000000000 +0100
+++ src/gdb/breakpoint.c 2009-07-25 17:06:40.000000000 +0100
@@ -1532,9 +1532,6 @@ create_longjmp_master_breakpoint (char *
struct breakpoint *b;
struct minimal_symbol *m;
- if (!gdbarch_get_longjmp_target_p (get_objfile_arch (objfile)))
- continue;
-
m = lookup_minimal_symbol_text (func_name, objfile);
if (m == NULL)
continue;
Index: src/gdb/testsuite/gdb.base/longjmp.exp
===================================================================
--- src.orig/gdb/testsuite/gdb.base/longjmp.exp 2009-07-25 16:47:07.000000000 +0100
+++ src/gdb/testsuite/gdb.base/longjmp.exp 2009-07-25 17:06:40.000000000 +0100
@@ -127,4 +127,4 @@ gdb_test "break $bp_start_test_3" \
"breakpoint at pattern 3 start"
gdb_test "continue" "patt3.*" "continue to breakpoint at pattern 3 start"
-gdb_test "next" "longjmp caught.*" "next over patt3"
+gdb_test "next" "patt_end3.*" "next over patt3"
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: RFC: Longjmp vs LD_POINTER_GUARD revisited
2009-11-15 21:52 ` Mark Kettenis
2009-11-15 22:37 ` Daniel Jacobowitz
@ 2009-11-16 15:15 ` Frank Ch. Eigler
2009-11-16 15:40 ` Paul Pluzhnikov
2009-11-16 15:59 ` Mark Kettenis
1 sibling, 2 replies; 18+ messages in thread
From: Frank Ch. Eigler @ 2009-11-16 15:15 UTC (permalink / raw)
To: Mark Kettenis; +Cc: joseph, drow, gdb-patches, pedro, uweigand
Mark Kettenis <mark.kettenis@xs4all.nl> writes:
>> [...]
> Isn't it a bit ridiculous that glibc, which is a GNU project, makes
> the life of GDB, another GNU project, so difficult?
Well, it's nothing personal. If glibc made it trivial decrypt this
stuff on demand, it'd be just as easy for an attacker. Maybe this is
a case for something akin to libthread_db.
- FChE
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: RFC: Longjmp vs LD_POINTER_GUARD revisited
2009-11-16 15:15 ` Frank Ch. Eigler
@ 2009-11-16 15:40 ` Paul Pluzhnikov
2009-11-16 15:43 ` Paul Pluzhnikov
2009-11-16 16:19 ` Mark Kettenis
2009-11-16 15:59 ` Mark Kettenis
1 sibling, 2 replies; 18+ messages in thread
From: Paul Pluzhnikov @ 2009-11-16 15:40 UTC (permalink / raw)
To: Frank Ch. Eigler
Cc: Mark Kettenis, joseph, drow, gdb-patches, pedro, uweigand
On Mon, Nov 16, 2009 at 7:13 AM, Frank Ch. Eigler <fche@redhat.com> wrote:
> Well, it's nothing personal. If glibc made it trivial decrypt this
> stuff on demand, it'd be just as easy for an attacker.
That's exactly my point: the process itself can trivially discover the
problem by executing two setjmps with known resume addresses (an
implementation I did in my previous job (for a Valgrind-like checker)
took less than 20 lines of assembly), so I wonder how much of a
deterrent this really is.
> Maybe this is a case for something akin to libthread_db.
Hmm, libc_db to subsume libthread_db, and answer all kinds of
questions about glibc internals; wouldn't GDB's life be easier! OTOH,
if the sysadmin is not careful to remove libc_db from a production
system, then the attacker could just dlopen libc_db and hack away.
--
Paul Pluzhnikov
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: RFC: Longjmp vs LD_POINTER_GUARD revisited
2009-11-16 15:40 ` Paul Pluzhnikov
@ 2009-11-16 15:43 ` Paul Pluzhnikov
2009-11-16 16:19 ` Mark Kettenis
1 sibling, 0 replies; 18+ messages in thread
From: Paul Pluzhnikov @ 2009-11-16 15:43 UTC (permalink / raw)
To: Frank Ch. Eigler
Cc: Mark Kettenis, joseph, drow, gdb-patches, pedro, uweigand
On Mon, Nov 16, 2009 at 7:39 AM, Paul Pluzhnikov <ppluzhnikov@google.com> wrote:
> process itself can trivially discover the problem
I meant: "can trivially discover the canary and the algorithm used to
obfuscate the return address".
--
Paul Pluzhnikov
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: RFC: Longjmp vs LD_POINTER_GUARD revisited
2009-11-16 15:15 ` Frank Ch. Eigler
2009-11-16 15:40 ` Paul Pluzhnikov
@ 2009-11-16 15:59 ` Mark Kettenis
1 sibling, 0 replies; 18+ messages in thread
From: Mark Kettenis @ 2009-11-16 15:59 UTC (permalink / raw)
To: fche; +Cc: joseph, drow, gdb-patches, pedro, uweigand
> From: fche@redhat.com (Frank Ch. Eigler)
> Date: Mon, 16 Nov 2009 10:13:49 -0500
>
> Mark Kettenis <mark.kettenis@xs4all.nl> writes:
>
> >> [...]
> > Isn't it a bit ridiculous that glibc, which is a GNU project, makes
> > the life of GDB, another GNU project, so difficult?
>
> Well, it's nothing personal. If glibc made it trivial decrypt this
> stuff on demand, it'd be just as easy for an attacker. Maybe this is
> a case for something akin to libthread_db.
That's not quite the point I wanted to make. I merely wanted to point
out that glibc implemented this security measure (and apparently keeps
changing it) without considering the debugability of processes.
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: RFC: Longjmp vs LD_POINTER_GUARD revisited
2009-11-16 15:40 ` Paul Pluzhnikov
2009-11-16 15:43 ` Paul Pluzhnikov
@ 2009-11-16 16:19 ` Mark Kettenis
1 sibling, 0 replies; 18+ messages in thread
From: Mark Kettenis @ 2009-11-16 16:19 UTC (permalink / raw)
To: ppluzhnikov
Cc: fche, mark.kettenis, joseph, drow, gdb-patches, pedro, uweigand
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain, Size: 1911 bytes --]
> Date: Mon, 16 Nov 2009 07:39:45 -0800
> From: Paul Pluzhnikov <ppluzhnikov@google.com>
>
> On Mon, Nov 16, 2009 at 7:13 AM, Frank Ch. Eigler <fche@redhat.com> wrote:
>
> > Well, it's nothing personal. If glibc made it trivial decrypt this
> > stuff on demand, it'd be just as easy for an attacker.
>
> That's exactly my point: the process itself can trivially discover the
> problem by executing two setjmps with known resume addresses (an
> implementation I did in my previous job (for a Valgrind-like checker)
> took less than 20 lines of assembly), so I wonder how much of a
> deterrent this really is.
Well, the whole purpose of encrypting the setjmp buffer this way is to
prevent an attacker from longjmp-ing to "shellcode" by overwriting the
setjmp buffer by exploiting some sort of buffer overflow. The fact
that an attacker can trivially write some shellcode to figure out the
cookie doesn't really help him because he'll still have to find a way
to execute that shellcode.
At the same time, this means that there's little benefit from coming
up with clever encrypting algorithms (a simple XOR is probably good
enough) and going through extreme lengths to hide the cookie. So I
don't think an agreed upon interface between glibc and GDB, on how to
find the cookie and which encrypting algorithm is used would have a
significant impact on security.
> > Maybe this is a case for something akin to libthread_db.
>
> Hmm, libc_db to subsume libthread_db, and answer all kinds of
> questions about glibc internals; wouldn't GDB's life be easier! OTOH,
> if the sysadmin is not careful to remove libc_db from a production
> system, then the attacker could just dlopen libc_db and hack away.
I'm not sure something similar to libthread_db is such a terribly goo
idea; it tends to be a bitch for cross-debugging. As to the security
implications of having such a libc_db, see my argument above.
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: RFC: Longjmp vs LD_POINTER_GUARD revisited
2009-11-16 14:55 ` Pedro Alves
2009-11-16 14:56 ` Pedro Alves
2009-11-16 15:05 ` Pedro Alves
@ 2009-11-16 17:50 ` Daniel Jacobowitz
2 siblings, 0 replies; 18+ messages in thread
From: Daniel Jacobowitz @ 2009-11-16 17:50 UTC (permalink / raw)
To: Pedro Alves; +Cc: Paul Pluzhnikov, gdb-patches, Ulrich Weigand
On Mon, Nov 16, 2009 at 02:53:50PM +0000, Pedro Alves wrote:
> On Monday 16 November 2009 14:36:13, Daniel Jacobowitz wrote:
> > On Sun, Nov 15, 2009 at 03:05:33PM -0800, Paul Pluzhnikov wrote:
> > > Still it's trivial to discover the canary without disassembling
> > > anything (disassembling requires symbols, which may be stripped):
> > > there are only 3 different algorithms I've seen (no canary, XOR,
> > > XOR+shift-by-9). Hmm, looks like x86_64 has XOR+shift-by-17 now, but
> > > ia64, SPARC and PPC all have just "plain XOR".
> >
> > I don't know about "trivial" - could you explain how you would do this
> > without disassembling? I think that at the least we'd have to call
> > setjmp in the inferior, which has risks with signals / multiple
> > threads / etc. I don't think we call functions in the inferior for
> > discovery otherwise.
>
> I've had this patch below laying here for ages, that does
> what Paul is suggesting, while trying to avoid infcalls.
> It's quite ugly... I've been meaning to post this for ages,
> but I'm a bit embarrassed by the hack :-) It doesn't work in
> every possible case.
So rather than disassembling setjmp to see what it is, you try various
algorithms and then scan backwards looking for a call to setjmp. If
you find one, you've succeeded. Sounds like it will work in an
acceptable set of cases.
> There are two issues that made me try this route:
>
> - single-stepping all the way through longjmp turned out
> to be slow. Did you not perceive that too? Maybe we
> could limit the amount of single-stepping by not placing
> the longjmp breakpoint at 'longjmp', but at one of its callees
> that's closer to the real long jump.
I've only been testing on x86_64-linux native so far, where the speed
is not as measurable. It didn't seem to be too many instructions.
> - I've tried a similar hack as yours and found that I had
> to add several more "functions still within longjmp" special
> cases. I had tried it on netbsd and Windows too. NetBSD
> was also bad at needing special casing. I'll post the relevant
> bits of the patch in a bit, when I find it :-)
I don't quite follow your patch... I'd have to sit down and stare at
it for a while.
So we have three strategies:
* Step. Various hacks, reconcile your patch and mine (probably mostly
by taking yours), test on a lot of platforms.
* Infcall setjmp to examine its behavior. Risky in that infcalls are
always risky. I feel like I don't have good insight into all the ways
this could go wrong.
* Try multiple algorithms in the target on platforms with jmp_buf
encryption.
You've explored these more than I have. Which one do you like best?
If it's the last one, then I should pick up your attached patch... I
don't think it's too awful. The only thing I don't care for is
the target method for the thread area. I'd rather make the value
available through the regcache. IIRC we were going to call this
%fs_base at some point.
--
Daniel Jacobowitz
CodeSourcery
^ permalink raw reply [flat|nested] 18+ messages in thread
end of thread, other threads:[~2009-11-16 17:50 UTC | newest]
Thread overview: 18+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2009-11-15 17:35 RFC: Longjmp vs LD_POINTER_GUARD revisited Daniel Jacobowitz
2009-11-15 18:06 ` Eli Zaretskii
2009-11-15 18:30 ` Paul Pluzhnikov
2009-11-15 22:36 ` Daniel Jacobowitz
2009-11-15 23:06 ` Paul Pluzhnikov
2009-11-16 14:37 ` Daniel Jacobowitz
2009-11-16 14:55 ` Pedro Alves
2009-11-16 14:56 ` Pedro Alves
2009-11-16 15:05 ` Pedro Alves
2009-11-16 17:50 ` Daniel Jacobowitz
2009-11-15 18:39 ` Joseph S. Myers
2009-11-15 21:52 ` Mark Kettenis
2009-11-15 22:37 ` Daniel Jacobowitz
2009-11-16 15:15 ` Frank Ch. Eigler
2009-11-16 15:40 ` Paul Pluzhnikov
2009-11-16 15:43 ` Paul Pluzhnikov
2009-11-16 16:19 ` Mark Kettenis
2009-11-16 15:59 ` Mark Kettenis
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox