* longjmp handling vs. glibc LD_POINTER_GUARD problems
@ 2008-05-14 18:24 Ulrich Weigand
2008-05-14 19:14 ` Daniel Jacobowitz
` (2 more replies)
0 siblings, 3 replies; 17+ messages in thread
From: Ulrich Weigand @ 2008-05-14 18:24 UTC (permalink / raw)
To: gdb-patches
Hello,
since the recent "stepping over longjmp" patches went in,
I'm seeing test suite failures in longjmp.exp on s390, spu,
and powerpc -- because none of those platforms actually
implement get_longjmp_target.
While I was trying to implement the missing routines, I ran
into a problem: on current glibcs, the return address as
stored in the jmp_buf is actually "mangled", i.e. XORed
with a magic "pointer guard" value. This is apparently
intended to provide some protection against buffer overflow
attacks ...
To implement implement get_longjmp_target I'd have to retrieve
that guard value and demangle the pointers. This is of course
possible in principle -- but this assumes that the details of
where to find the guard value (typically somewhere in the
thread control block header) remain fixed across glibc versions.
I'm not sure we can actually rely on that. I couldn't find any
exported glibc mechanism to retrieve this value in a supported
way either ...
I'm now wondering how we should handle this. Should be
implement an ad-hoc solution to retrieve the guard, which
may break in the future if glibc changes? Should we require
use of LD_POINTER_GUARD=0 (which switches off the pointer
guard mechanism) to enable debugging? Am I overlooking some
defined interface to get at the value?
Why are we using the get_longjmp_target mechanism instead of
just stepping through longjmp until we see where we come out?
I'd appreciate your thoughts on this ...
Thanks,
Ulrich
--
Dr. Ulrich Weigand
GNU Toolchain for Linux on System z and Cell BE
Ulrich.Weigand@de.ibm.com
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: longjmp handling vs. glibc LD_POINTER_GUARD problems
2008-05-14 18:24 longjmp handling vs. glibc LD_POINTER_GUARD problems Ulrich Weigand
@ 2008-05-14 19:14 ` Daniel Jacobowitz
2008-05-14 22:01 ` Ulrich Weigand
2008-05-14 19:17 ` Pedro Alves
2008-05-14 23:03 ` David Miller
2 siblings, 1 reply; 17+ messages in thread
From: Daniel Jacobowitz @ 2008-05-14 19:14 UTC (permalink / raw)
To: Ulrich Weigand; +Cc: gdb-patches
On Wed, May 14, 2008 at 08:00:18PM +0200, Ulrich Weigand wrote:
> To implement implement get_longjmp_target I'd have to retrieve
> that guard value and demangle the pointers. This is of course
> possible in principle -- but this assumes that the details of
> where to find the guard value (typically somewhere in the
> thread control block header) remain fixed across glibc versions.
> I'm not sure we can actually rely on that. I couldn't find any
> exported glibc mechanism to retrieve this value in a supported
> way either ...
Indeed, there isn't such a mechanism, and the mangling algorithm has
changed at least once in the past.
> I'm now wondering how we should handle this. Should be
> implement an ad-hoc solution to retrieve the guard, which
> may break in the future if glibc changes? Should we require
> use of LD_POINTER_GUARD=0 (which switches off the pointer
> guard mechanism) to enable debugging? Am I overlooking some
> defined interface to get at the value?
>
> Why are we using the get_longjmp_target mechanism instead of
> just stepping through longjmp until we see where we come out?
Bingo. I discussed this with Pedro, in followups to one of his nine
patches... ah, here it is.
http://sourceware.org/ml/gdb-patches/2008-04/msg00252.html
And stepping on platforms that don't provide a fetch routine, plus not
providing fetch routines on platforms which mangle the pointer, is my
best idea so far. Maybe dropping the fetcher entirely? Will that be
too slow?
--
Daniel Jacobowitz
CodeSourcery
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: longjmp handling vs. glibc LD_POINTER_GUARD problems
2008-05-14 18:24 longjmp handling vs. glibc LD_POINTER_GUARD problems Ulrich Weigand
2008-05-14 19:14 ` Daniel Jacobowitz
@ 2008-05-14 19:17 ` Pedro Alves
2008-05-17 14:00 ` Pedro Alves
2008-05-14 23:03 ` David Miller
2 siblings, 1 reply; 17+ messages in thread
From: Pedro Alves @ 2008-05-14 19:17 UTC (permalink / raw)
To: gdb-patches; +Cc: Ulrich Weigand
A Wednesday 14 May 2008 19:00:18, Ulrich Weigand wrote:
> I'm now wondering how we should handle this. Should be
> implement an ad-hoc solution to retrieve the guard, which
> may break in the future if glibc changes? Should we require
> use of LD_POINTER_GUARD=0 (which switches off the pointer
> guard mechanism) to enable debugging? Am I overlooking some
> defined interface to get at the value?
No, you're not. There is none. And still
LD_POINTER_GUARD=0 doesn't help when attaching to an already
running target.
> Why are we using the get_longjmp_target mechanism instead of
> just stepping through longjmp until we see where we come out?
You tell me. :-) I had assumed there was a reason. Perhaps
to support longjumping to a different stack, but that's hardly
a portable and frequent use case. This seems to be the path
to go.
--
Pedro Alves
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: longjmp handling vs. glibc LD_POINTER_GUARD problems
2008-05-14 19:14 ` Daniel Jacobowitz
@ 2008-05-14 22:01 ` Ulrich Weigand
0 siblings, 0 replies; 17+ messages in thread
From: Ulrich Weigand @ 2008-05-14 22:01 UTC (permalink / raw)
To: Daniel Jacobowitz; +Cc: gdb-patches
Daniel Jacobowitz wrote:
> Bingo. I discussed this with Pedro, in followups to one of his nine
> patches... ah, here it is.
>
> http://sourceware.org/ml/gdb-patches/2008-04/msg00252.html
Huh ... I completely missed that, thanks.
> And stepping on platforms that don't provide a fetch routine, plus not
> providing fetch routines on platforms which mangle the pointer, is my
> best idea so far. Maybe dropping the fetcher entirely? Will that be
> too slow?
longjmp generally shouldn't very be long, so I don't think this matters.
However, Pedro mentioned that there are problems implementing this
in the current infrun.c setup, so there maybe some more work required ...
Bye,
Ulrich
--
Dr. Ulrich Weigand
GNU Toolchain for Linux on System z and Cell BE
Ulrich.Weigand@de.ibm.com
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: longjmp handling vs. glibc LD_POINTER_GUARD problems
2008-05-14 18:24 longjmp handling vs. glibc LD_POINTER_GUARD problems Ulrich Weigand
2008-05-14 19:14 ` Daniel Jacobowitz
2008-05-14 19:17 ` Pedro Alves
@ 2008-05-14 23:03 ` David Miller
2008-05-15 0:39 ` Daniel Jacobowitz
2 siblings, 1 reply; 17+ messages in thread
From: David Miller @ 2008-05-14 23:03 UTC (permalink / raw)
To: uweigand; +Cc: gdb-patches
From: "Ulrich Weigand" <uweigand@de.ibm.com>
Date: Wed, 14 May 2008 20:00:18 +0200 (CEST)
> To implement implement get_longjmp_target I'd have to retrieve
> that guard value and demangle the pointers. This is of course
> possible in principle -- but this assumes that the details of
> where to find the guard value (typically somewhere in the
> thread control block header) remain fixed across glibc versions.
> I'm not sure we can actually rely on that. I couldn't find any
> exported glibc mechanism to retrieve this value in a supported
> way either ...
I think you can treat this the same way we treat the signal frame
layout. It's something undocumented but effectively fixed in stone.
If glibc ever changed the offset within the thread struct for this
cookie, so many binaries would break. So it is very likely the value
will stay the same for the forseeable future.
Thanks for pointing out this issue, I think sparc has the same
problem and thus needs the same longjmp hooks.
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: longjmp handling vs. glibc LD_POINTER_GUARD problems
2008-05-14 23:03 ` David Miller
@ 2008-05-15 0:39 ` Daniel Jacobowitz
0 siblings, 0 replies; 17+ messages in thread
From: Daniel Jacobowitz @ 2008-05-15 0:39 UTC (permalink / raw)
To: David Miller; +Cc: uweigand, gdb-patches
On Wed, May 14, 2008 at 02:26:58PM -0700, David Miller wrote:
> I think you can treat this the same way we treat the signal frame
> layout. It's something undocumented but effectively fixed in stone.
>
> If glibc ever changed the offset within the thread struct for this
> cookie, so many binaries would break. So it is very likely the value
> will stay the same for the forseeable future.
I don't think that's true, is it? There's two cookies, only one of
them is exposed to GCC, and glibc uses internal macros to protect the
contents of a jmp_buf which are not necessarily the same as those used
by GCC.
e.g. gcc uses xor with %fs:0x14 on i386-linux. That's
__stack_chk_guard and jmp_buf uses PTR_MANGLE -> __pointer_chk_guard.
Many architectures don't expose either variable.
--
Daniel Jacobowitz
CodeSourcery
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: longjmp handling vs. glibc LD_POINTER_GUARD problems
2008-05-14 19:17 ` Pedro Alves
@ 2008-05-17 14:00 ` Pedro Alves
2008-05-21 4:20 ` [patch] " Pedro Alves
0 siblings, 1 reply; 17+ messages in thread
From: Pedro Alves @ 2008-05-17 14:00 UTC (permalink / raw)
To: gdb-patches; +Cc: Ulrich Weigand, Daniel Jacobowitz
[-- Attachment #1: Type: text/plain, Size: 4749 bytes --]
A Wednesday 14 May 2008 19:16:23, Pedro Alves wrote:
> A Wednesday 14 May 2008 19:00:18, Ulrich Weigand wrote:
> > I'm now wondering how we should handle this. Should be
> > implement an ad-hoc solution to retrieve the guard, which
> > may break in the future if glibc changes? Should we require
> > use of LD_POINTER_GUARD=0 (which switches off the pointer
> > guard mechanism) to enable debugging? Am I overlooking some
> > defined interface to get at the value?
>
> No, you're not. There is none. And still
> LD_POINTER_GUARD=0 doesn't help when attaching to an already
> running target.
>
> > Why are we using the get_longjmp_target mechanism instead of
> > just stepping through longjmp until we see where we come out?
>
> You tell me. :-) I had assumed there was a reason. Perhaps
> to support longjumping to a different stack, but that's hardly
> a portable and frequent use case. This seems to be the path
> to go.
Alright, here is a quick hack at it. If this is the path to go,
we can remove a bunch of gdbarch_get_longjmp_target implementations,
and the code around longjmp_resume breakpoints.
The patch implements the "keep stepping if going through a longjmp",
and also, if landing somewhere inner than the current step-resume, we
keep stepping, which is an alternative and simple implementation
of what I proposed here:
http://sourceware.org/ml/gdb-patches/2008-04/msg00162.html
Tested on x86_64-unknown-linux-gnu. The longjmp.exp passes all ok,
and we become immune to pointer mangling.
I've also simplified a bit the test, removing stuff that wasn't
adding any value.
One thing, why do I need to call get_frame_type before calling
frame_unwind_id (first thing after frame cache invalidation)? If I
don't do that, I hit this gdb_assert:
static void
frame_cleanup_after_sniffer (void *arg)
{
...
/* No sniffer should extend the frame chain; sniff based on what is
already certain. */
gdb_assert (!frame->prev_p);
#0 internal_error (file=0x7558a1 "../../src/gdb/frame.c", line=1808,
string=0x755946 "%s: Assertion `%s' failed.")
at ../../src/gdb/utils.c:779
#1 0x00000000005b5b6a in frame_cleanup_after_sniffer (arg=0xafbd40)
at ../../src/gdb/frame.c:1808
#2 0x00000000004570ed in do_my_cleanups (pmy_chain=0xa9b900,
old_chain=0xb13d10) at ../../src/gdb/utils.c:320
#3 0x000000000045709d in do_cleanups (old_chain=0xb13d10)
at ../../src/gdb/utils.c:303
#4 0x00000000005b62d3 in frame_unwind_find_by_frame (this_frame=0xafbd40,
this_cache=0xafbd48)
at ../../src/gdb/frame-unwind.c:105
#5 0x00000000005b2e7f in get_frame_id (fi=0xafbd40)
at ../../src/gdb/frame.c:258
#6 0x00000000005b4c6a in get_prev_frame_1 (this_frame=0xafbd40)
at ../../src/gdb/frame.c:1188
#7 0x00000000005b2f72 in frame_unwind_id (next_frame=0xafbd40)
at ../../src/gdb/frame.c:279
#8 0x0000000000503b00 in handle_inferior_event (ecs=0x7fffd6bca510)
at ../../src/gdb/infrun.c:2605
#9 0x00000000005016cb in wait_for_inferior (treat_exec_as_sigtrap=0)
at ../../src/gdb/infrun.c:1482
#10 0x000000000050142d in proceed (addr=18446744073709551615,
siggnal=TARGET_SIGNAL_DEFAULT, step=1)
at ../../src/gdb/infrun.c:1273
#11 0x00000000004fd64f in step_1 (skip_subroutines=1, single_inst=0,
count_string=0x0) at ../../src/gdb/infcmd.c:784
#12 0x00000000004fd32e in next_command (count_string=0x0, from_tty=1)
at ../../src/gdb/infcmd.c:678
...
The first call to frame_unwind_id a bit below happens to have a get_frame_type
call before, by chance,
if (step_range_end != 1
&& (step_over_calls == STEP_OVER_UNDEBUGGABLE
|| step_over_calls == STEP_OVER_ALL)
>>> && get_frame_type (get_current_frame ()) == SIGTRAMP_FRAME)
{
if (debug_infrun)
fprintf_unfiltered (gdb_stdlog, "infrun: stepped into signal trampoline\n");
/* The inferior, while doing a "step" or "next", has ended up in
a signal trampoline (either by a signal being delivered or by
the signal handler returning). Just single-step until the
inferior leaves the trampoline (either by calling the handler
or returning). */
keep_going (ecs);
return;
}
/* Check for subroutine calls. The check for the current frame
equalling the step ID is not necessary - the check of the
previous frame's ID is sufficient - but it is a common case and
cheaper than checking the previous frame's ID.
NOTE: frame_id_eq will never report two invalid frame IDs as
being equal, so to get into this block, both the current and
previous frame must have valid frame IDs. */
if (!frame_id_eq (get_frame_id (get_current_frame ()), step_frame_id)
>>> && frame_id_eq (frame_unwind_id (get_current_frame ()), step_frame_id))
{
--
Pedro Alves
[-- Attachment #2: longjmp.diff --]
[-- Type: text/x-diff, Size: 9914 bytes --]
2008-05-17 Pedro Alves <pedro@codesourcery.com>
* infrun.c (struct execution_control_state): Add
stepping_through_longjmp and longjmp_caller_frame.
(init_execution_control_state): Clear them.
(context_switch): Context switch them.
(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 is no longer needed.
(currently_stepping): Return true if stepping through longjmp.
* gdbthread.h (struct thread_info): Add stepping_through_longjmp
and longjmp_caller_frame.
(save_infrun_state, load_infrun_state): Context switch them.
* thread.c (save_infrun_state, load_infrun_state): Ditto.
---
gdb/gdbthread.h | 14 +++++++-
gdb/infrun.c | 95 +++++++++++++++++++++++++++++++++++++++++---------------
gdb/thread.c | 12 +++++--
3 files changed, 93 insertions(+), 28 deletions(-)
Index: src/gdb/infrun.c
===================================================================
--- src.orig/gdb/infrun.c 2008-05-17 02:38:39.000000000 +0100
+++ src/gdb/infrun.c 2008-05-17 02:49:21.000000000 +0100
@@ -1386,6 +1386,13 @@ struct execution_control_state
int step_after_step_resume_breakpoint;
int stepping_through_solib_after_catch;
bpstat stepping_through_solib_catchpoints;
+
+ /* True if a longjmp call was detected while stepping, and we're
+ single-stepping until the other end. */
+ int stepping_through_longjmp;
+ /* The frame that called longjmp. */
+ struct frame_id longjmp_caller_frame;
+
int new_thread_event;
struct target_waitstatus tmpstatus;
enum infwait_states infwait_state;
@@ -1556,6 +1563,7 @@ init_execution_control_state (struct exe
ecs->infwait_state = infwait_normal_state;
ecs->waiton_ptid = pid_to_ptid (-1);
ecs->wp = &(ecs->ws);
+ ecs->stepping_through_longjmp = 0;
}
/* Return the cached copy of the last pid/waitstatus returned by
@@ -1605,7 +1613,9 @@ context_switch (struct execution_control
ecs->stepping_over_breakpoint,
ecs->stepping_through_solib_after_catch,
ecs->stepping_through_solib_catchpoints,
- ecs->current_line, ecs->current_symtab);
+ ecs->current_line, ecs->current_symtab,
+ ecs->stepping_through_longjmp,
+ ecs->longjmp_caller_frame);
/* Load infrun state for the new thread. */
load_infrun_state (ecs->ptid, &prev_pc,
@@ -1615,7 +1625,9 @@ context_switch (struct execution_control
&ecs->stepping_over_breakpoint,
&ecs->stepping_through_solib_after_catch,
&ecs->stepping_through_solib_catchpoints,
- &ecs->current_line, &ecs->current_symtab);
+ &ecs->current_line, &ecs->current_symtab,
+ &ecs->stepping_through_longjmp,
+ &ecs->longjmp_caller_frame);
}
switch_to_thread (ecs->ptid);
@@ -2449,7 +2461,8 @@ handle_inferior_event (struct execution_
ecs->random_signal
= !(bpstat_explains_signal (stop_bpstat)
|| stepping_over_breakpoint
- || (step_range_end && step_resume_breakpoint == NULL));
+ || (step_range_end && step_resume_breakpoint == NULL)
+ || ecs->stepping_through_longjmp);
else
{
ecs->random_signal = !bpstat_explains_signal (stop_bpstat);
@@ -2575,35 +2588,24 @@ 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->stepping_over_breakpoint = 1;
- if (!gdbarch_get_longjmp_target_p (current_gdbarch)
- || !gdbarch_get_longjmp_target (current_gdbarch,
- get_current_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. */
- if (step_resume_breakpoint != NULL)
- delete_step_resume_breakpoint (&step_resume_breakpoint);
+ /* Why do I need to call this? */
+ get_frame_type (get_current_frame ());
- /* Insert a breakpoint at resume address. */
- insert_longjmp_resume_breakpoint (jmp_buf_pc);
+ /* Store the frame of the caller. This is used to compare
+ with any step-resume step set. */
+ ecs->longjmp_caller_frame = frame_unwind_id (get_current_frame ());
+ /* See you on the other side! */
+ ecs->stepping_through_longjmp = 1;
keep_going (ecs);
return;
@@ -2819,6 +2821,50 @@ infrun: BPSTAT_WHAT_SET_LONGJMP_RESUME (
return;
}
+ if (ecs->stepping_through_longjmp)
+ {
+ struct frame_info *frame = get_current_frame ();
+ struct frame_id current_frame = get_frame_id (frame);
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog,
+ "infrun: stepping through longjmp\n");
+ if (frame_id_inner (get_frame_arch (frame),
+ current_frame, ecs->longjmp_caller_frame))
+ {
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog,
+ "infrun: still in longjmp\n");
+ keep_going (ecs);
+ return;
+ }
+
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog,
+ "infrun: got out of longjmp\n");
+ ecs->stepping_through_longjmp = 0;
+
+ /* Now decide if we should proceed to the step-resume
+ breakpoint, or if we already went passed it. */
+
+ if (step_resume_breakpoint)
+ {
+ if (frame_id_inner (get_frame_arch (frame),
+ current_frame, step_resume_breakpoint->frame_id))
+ {
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog,
+ "infrun: longjmp-resume is inner than step-resume\n");
+ }
+ else
+ {
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog,
+ "infrun: step-resume overran by longjmp\n");
+ delete_step_resume_breakpoint (&step_resume_breakpoint);
+ }
+ }
+ }
+
if (step_resume_breakpoint)
{
if (debug_infrun)
@@ -3183,6 +3229,7 @@ currently_stepping (struct execution_con
{
return (((step_range_end && step_resume_breakpoint == NULL)
|| stepping_over_breakpoint)
+ || ecs->stepping_through_longjmp
|| ecs->stepping_through_solib_after_catch
|| bpstat_should_step ());
}
Index: src/gdb/gdbthread.h
===================================================================
--- src.orig/gdb/gdbthread.h 2008-05-17 02:39:51.000000000 +0100
+++ src/gdb/gdbthread.h 2008-05-17 02:45:44.000000000 +0100
@@ -63,6 +63,12 @@ struct thread_info
when we finally do stop stepping. */
bpstat stepping_through_solib_catchpoints;
+ /* True if a longjmp call was detected while stepping, and we're
+ single-stepping until the other end. */
+ int stepping_through_longjmp;
+ /* The frame that called longjmp. */
+ struct frame_id longjmp_caller_frame;
+
/* Private data used by the target vector implementation. */
struct private_thread_info *private;
};
@@ -126,7 +132,9 @@ extern void save_infrun_state (ptid_t pt
int stepping_through_solib_after_catch,
bpstat stepping_through_solib_catchpoints,
int current_line,
- struct symtab *current_symtab);
+ struct symtab *current_symtab,
+ int stepping_through_longjmp,
+ struct frame_id longjmp_caller_frame);
/* infrun context switch: load the debugger state previously saved
for the given thread. */
@@ -141,7 +149,9 @@ extern void load_infrun_state (ptid_t pt
int *stepping_through_solib_affter_catch,
bpstat *stepping_through_solib_catchpoints,
int *current_line,
- struct symtab **current_symtab);
+ struct symtab **current_symtab,
+ int *stepping_through_longjmp,
+ struct frame_id *longjmp_caller_frame);
/* Switch from one thread to another. */
extern void switch_to_thread (ptid_t ptid);
Index: src/gdb/thread.c
===================================================================
--- src.orig/gdb/thread.c 2008-05-17 02:39:54.000000000 +0100
+++ src/gdb/thread.c 2008-05-17 02:47:18.000000000 +0100
@@ -323,7 +323,9 @@ load_infrun_state (ptid_t ptid,
int *stepping_through_solib_after_catch,
bpstat *stepping_through_solib_catchpoints,
int *current_line,
- struct symtab **current_symtab)
+ struct symtab **current_symtab,
+ int *stepping_through_longjmp,
+ struct frame_id *longjmp_caller_frame)
{
struct thread_info *tp;
@@ -346,6 +348,8 @@ load_infrun_state (ptid_t ptid,
tp->stepping_through_solib_catchpoints;
*current_line = tp->current_line;
*current_symtab = tp->current_symtab;
+ *stepping_through_longjmp = tp->stepping_through_longjmp;
+ *longjmp_caller_frame = tp->longjmp_caller_frame;
}
/* Save infrun state for the thread PID. */
@@ -362,7 +366,9 @@ save_infrun_state (ptid_t ptid,
int stepping_through_solib_after_catch,
bpstat stepping_through_solib_catchpoints,
int current_line,
- struct symtab *current_symtab)
+ struct symtab *current_symtab,
+ int stepping_through_longjmp,
+ struct frame_id longjmp_caller_frame)
{
struct thread_info *tp;
@@ -383,6 +389,8 @@ save_infrun_state (ptid_t ptid,
tp->stepping_through_solib_catchpoints = stepping_through_solib_catchpoints;
tp->current_line = current_line;
tp->current_symtab = current_symtab;
+ tp->stepping_through_longjmp = stepping_through_longjmp;
+ tp->longjmp_caller_frame = longjmp_caller_frame;
}
/* Return true if TP is an active thread. */
[-- Attachment #3: longjmp_tests_outer.diff --]
[-- Type: text/x-diff, Size: 7818 bytes --]
2008-05-17 Pedro Alves <pedro@codesourcery.com>
* longjmp.c, longjmp.exp: Add tests to test ignoring inner
longjmp resumes while stepping, and update current tests.
---
gdb/testsuite/gdb.base/longjmp.c | 103 ++++++++++++++++++++++++++++++++-----
gdb/testsuite/gdb.base/longjmp.exp | 96 ++++++++++++++++++----------------
2 files changed, 142 insertions(+), 57 deletions(-)
Index: src/gdb/testsuite/gdb.base/longjmp.c
===================================================================
--- src.orig/gdb/testsuite/gdb.base/longjmp.c 2008-05-04 20:43:31.000000000 +0100
+++ src/gdb/testsuite/gdb.base/longjmp.c 2008-05-17 02:36:56.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,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 2008-05-04 20:43:31.000000000 +0100
+++ src/gdb/testsuite/gdb.base/longjmp.exp 2008-05-17 02:30:18.000000000 +0100
@@ -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.*" {
- 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.*" {
- 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.*" {
- 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.*" {
- 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"
^ permalink raw reply [flat|nested] 17+ messages in thread
* [patch] Re: longjmp handling vs. glibc LD_POINTER_GUARD problems
2008-05-17 14:00 ` Pedro Alves
@ 2008-05-21 4:20 ` Pedro Alves
2008-05-22 0:11 ` Ulrich Weigand
0 siblings, 1 reply; 17+ messages in thread
From: Pedro Alves @ 2008-05-21 4:20 UTC (permalink / raw)
To: gdb-patches; +Cc: Ulrich Weigand, Daniel Jacobowitz
[-- Attachment #1: Type: text/plain, Size: 1685 bytes --]
A Saturday 17 May 2008 03:17:17, Pedro Alves wrote:
> A Wednesday 14 May 2008 19:16:23, Pedro Alves wrote:
> > A Wednesday 14 May 2008 19:00:18, Ulrich Weigand wrote:
> > > Why are we using the get_longjmp_target mechanism instead of
> > > just stepping through longjmp until we see where we come out?
> >
> > You tell me. :-) I had assumed there was a reason. Perhaps
> > to support longjumping to a different stack, but that's hardly
> > a portable and frequent use case. This seems to be the path
> > to go.
>
> Alright, here is a quick hack at it. If this is the path to go,
> we can remove a bunch of gdbarch_get_longjmp_target implementations,
> and the code around longjmp_resume breakpoints.
>
> The patch implements the "keep stepping if going through a longjmp",
> and also, if landing somewhere inner than the current step-resume, we
> keep stepping, which is an alternative and simple implementation
> of what I proposed here:
>
> http://sourceware.org/ml/gdb-patches/2008-04/msg00162.html
>
> Tested on x86_64-unknown-linux-gnu. The longjmp.exp passes all ok,
> and we become immune to pointer mangling.
>
> I've also simplified a bit the test, removing stuff that wasn't
> adding any value.
>
> One thing, why do I need to call get_frame_type before calling
> frame_unwind_id (first thing after frame cache invalidation)? If I
> don't do that, I hit this gdb_assert:
Since this is now fixed, :
http://sourceware.org/ml/gdb-patches/2008-05/msg00605.html
... here's an updated patch. The tests are the same as before. Tested on
x86_86-unknown-linux-gnu, and confirmed longjmp.exp also passes
cleanly on x86-pc-linux-gnu.
What do you think?
--
Pedro Alves
[-- Attachment #2: longjmp.diff --]
[-- Type: text/x-diff, Size: 9945 bytes --]
2008-05-20 Pedro Alves <pedro@codesourcery.com>
* infrun.c (struct execution_control_state): Add
stepping_through_longjmp and longjmp_caller_frame.
(init_execution_control_state): Clear them.
(context_switch): Context switch them.
(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.
(currently_stepping): Return true if stepping through longjmp.
* gdbthread.h (struct thread_info): Add stepping_through_longjmp
and longjmp_caller_frame.
(save_infrun_state, load_infrun_state): Context switch them.
* thread.c (save_infrun_state, load_infrun_state): Ditto.
---
gdb/gdbthread.h | 14 +++++++-
gdb/infrun.c | 96 +++++++++++++++++++++++++++++++++++++++++---------------
gdb/thread.c | 12 +++++--
3 files changed, 93 insertions(+), 29 deletions(-)
Index: src/gdb/infrun.c
===================================================================
--- src.orig/gdb/infrun.c 2008-05-20 22:41:40.000000000 +0100
+++ src/gdb/infrun.c 2008-05-20 23:41:17.000000000 +0100
@@ -1386,6 +1386,13 @@ struct execution_control_state
int step_after_step_resume_breakpoint;
int stepping_through_solib_after_catch;
bpstat stepping_through_solib_catchpoints;
+
+ /* True if a longjmp call was detected while stepping, and we're
+ single-stepping until the other end. */
+ int stepping_through_longjmp;
+ /* The frame that called longjmp. */
+ struct frame_id longjmp_caller_frame;
+
int new_thread_event;
struct target_waitstatus tmpstatus;
enum infwait_states infwait_state;
@@ -1556,6 +1563,7 @@ init_execution_control_state (struct exe
ecs->infwait_state = infwait_normal_state;
ecs->waiton_ptid = pid_to_ptid (-1);
ecs->wp = &(ecs->ws);
+ ecs->stepping_through_longjmp = 0;
}
/* Return the cached copy of the last pid/waitstatus returned by
@@ -1605,7 +1613,9 @@ context_switch (struct execution_control
ecs->stepping_over_breakpoint,
ecs->stepping_through_solib_after_catch,
ecs->stepping_through_solib_catchpoints,
- ecs->current_line, ecs->current_symtab);
+ ecs->current_line, ecs->current_symtab,
+ ecs->stepping_through_longjmp,
+ ecs->longjmp_caller_frame);
/* Load infrun state for the new thread. */
load_infrun_state (ecs->ptid, &prev_pc,
@@ -1615,7 +1625,9 @@ context_switch (struct execution_control
&ecs->stepping_over_breakpoint,
&ecs->stepping_through_solib_after_catch,
&ecs->stepping_through_solib_catchpoints,
- &ecs->current_line, &ecs->current_symtab);
+ &ecs->current_line, &ecs->current_symtab,
+ &ecs->stepping_through_longjmp,
+ &ecs->longjmp_caller_frame);
}
switch_to_thread (ecs->ptid);
@@ -2449,7 +2461,8 @@ handle_inferior_event (struct execution_
ecs->random_signal
= !(bpstat_explains_signal (stop_bpstat)
|| stepping_over_breakpoint
- || (step_range_end && step_resume_breakpoint == NULL));
+ || (step_range_end && step_resume_breakpoint == NULL)
+ || ecs->stepping_through_longjmp);
else
{
ecs->random_signal = !bpstat_explains_signal (stop_bpstat);
@@ -2575,35 +2588,21 @@ 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->stepping_over_breakpoint = 1;
- if (!gdbarch_get_longjmp_target_p (current_gdbarch)
- || !gdbarch_get_longjmp_target (current_gdbarch,
- get_current_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. */
- if (step_resume_breakpoint != NULL)
- delete_step_resume_breakpoint (&step_resume_breakpoint);
-
- /* Insert a breakpoint at resume address. */
- insert_longjmp_resume_breakpoint (jmp_buf_pc);
+ /* Store the frame id of the caller. This is used to compare
+ with any step-resume breakpoint set. */
+ ecs->longjmp_caller_frame = frame_unwind_id (get_current_frame ());
+ /* See you on the other side! */
+ ecs->stepping_through_longjmp = 1;
keep_going (ecs);
return;
@@ -2819,6 +2818,52 @@ infrun: BPSTAT_WHAT_SET_LONGJMP_RESUME (
return;
}
+ if (ecs->stepping_through_longjmp)
+ {
+ struct frame_info *frame = get_current_frame ();
+ struct frame_id frame_id = get_frame_id (frame);
+ struct gdbarch *gdbarch = get_frame_arch (frame);
+
+ if (frame_id_inner (gdbarch, frame_id, ecs->longjmp_caller_frame))
+ {
+ 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->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 (step_resume_breakpoint)
+ {
+ if (frame_id_inner (gdbarch, frame_id,
+ 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 (&step_resume_breakpoint);
+ }
+ }
+ }
+
if (step_resume_breakpoint)
{
if (debug_infrun)
@@ -3183,6 +3228,7 @@ currently_stepping (struct execution_con
{
return (((step_range_end && step_resume_breakpoint == NULL)
|| stepping_over_breakpoint)
+ || ecs->stepping_through_longjmp
|| ecs->stepping_through_solib_after_catch
|| bpstat_should_step ());
}
Index: src/gdb/gdbthread.h
===================================================================
--- src.orig/gdb/gdbthread.h 2008-05-20 22:41:40.000000000 +0100
+++ src/gdb/gdbthread.h 2008-05-20 23:25:02.000000000 +0100
@@ -63,6 +63,12 @@ struct thread_info
when we finally do stop stepping. */
bpstat stepping_through_solib_catchpoints;
+ /* True if a longjmp call was detected while stepping, and we're
+ single-stepping until the other end. */
+ int stepping_through_longjmp;
+ /* The frame that called longjmp. */
+ struct frame_id longjmp_caller_frame;
+
/* Private data used by the target vector implementation. */
struct private_thread_info *private;
};
@@ -126,7 +132,9 @@ extern void save_infrun_state (ptid_t pt
int stepping_through_solib_after_catch,
bpstat stepping_through_solib_catchpoints,
int current_line,
- struct symtab *current_symtab);
+ struct symtab *current_symtab,
+ int stepping_through_longjmp,
+ struct frame_id longjmp_caller_frame);
/* infrun context switch: load the debugger state previously saved
for the given thread. */
@@ -141,7 +149,9 @@ extern void load_infrun_state (ptid_t pt
int *stepping_through_solib_affter_catch,
bpstat *stepping_through_solib_catchpoints,
int *current_line,
- struct symtab **current_symtab);
+ struct symtab **current_symtab,
+ int *stepping_through_longjmp,
+ struct frame_id *longjmp_caller_frame);
/* Switch from one thread to another. */
extern void switch_to_thread (ptid_t ptid);
Index: src/gdb/thread.c
===================================================================
--- src.orig/gdb/thread.c 2008-05-20 23:18:13.000000000 +0100
+++ src/gdb/thread.c 2008-05-20 23:25:02.000000000 +0100
@@ -325,7 +325,9 @@ load_infrun_state (ptid_t ptid,
int *stepping_through_solib_after_catch,
bpstat *stepping_through_solib_catchpoints,
int *current_line,
- struct symtab **current_symtab)
+ struct symtab **current_symtab,
+ int *stepping_through_longjmp,
+ struct frame_id *longjmp_caller_frame)
{
struct thread_info *tp;
@@ -348,6 +350,8 @@ load_infrun_state (ptid_t ptid,
tp->stepping_through_solib_catchpoints;
*current_line = tp->current_line;
*current_symtab = tp->current_symtab;
+ *stepping_through_longjmp = tp->stepping_through_longjmp;
+ *longjmp_caller_frame = tp->longjmp_caller_frame;
}
/* Save infrun state for the thread PID. */
@@ -364,7 +368,9 @@ save_infrun_state (ptid_t ptid,
int stepping_through_solib_after_catch,
bpstat stepping_through_solib_catchpoints,
int current_line,
- struct symtab *current_symtab)
+ struct symtab *current_symtab,
+ int stepping_through_longjmp,
+ struct frame_id longjmp_caller_frame)
{
struct thread_info *tp;
@@ -385,6 +391,8 @@ save_infrun_state (ptid_t ptid,
tp->stepping_through_solib_catchpoints = stepping_through_solib_catchpoints;
tp->current_line = current_line;
tp->current_symtab = current_symtab;
+ tp->stepping_through_longjmp = stepping_through_longjmp;
+ tp->longjmp_caller_frame = longjmp_caller_frame;
}
/* Return true if TP is an active thread. */
[-- Attachment #3: longjmp_tests_outer.diff --]
[-- Type: text/x-diff, Size: 7818 bytes --]
2008-05-17 Pedro Alves <pedro@codesourcery.com>
* longjmp.c, longjmp.exp: Add tests to test ignoring inner
longjmp resumes while stepping, and update current tests.
---
gdb/testsuite/gdb.base/longjmp.c | 103 ++++++++++++++++++++++++++++++++-----
gdb/testsuite/gdb.base/longjmp.exp | 96 ++++++++++++++++++----------------
2 files changed, 142 insertions(+), 57 deletions(-)
Index: src/gdb/testsuite/gdb.base/longjmp.c
===================================================================
--- src.orig/gdb/testsuite/gdb.base/longjmp.c 2008-05-04 20:43:31.000000000 +0100
+++ src/gdb/testsuite/gdb.base/longjmp.c 2008-05-17 02:36:56.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,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 2008-05-04 20:43:31.000000000 +0100
+++ src/gdb/testsuite/gdb.base/longjmp.exp 2008-05-17 02:30:18.000000000 +0100
@@ -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.*" {
- 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.*" {
- 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.*" {
- 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.*" {
- 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"
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [patch] Re: longjmp handling vs. glibc LD_POINTER_GUARD problems
2008-05-21 4:20 ` [patch] " Pedro Alves
@ 2008-05-22 0:11 ` Ulrich Weigand
2008-05-22 0:14 ` Pedro Alves
2008-05-22 3:14 ` Daniel Jacobowitz
0 siblings, 2 replies; 17+ messages in thread
From: Ulrich Weigand @ 2008-05-22 0:11 UTC (permalink / raw)
To: Pedro Alves; +Cc: gdb-patches, Daniel Jacobowitz
Pedro Alves wrote:
> ... here's an updated patch. The tests are the same as before. Tested on
> x86_86-unknown-linux-gnu, and confirmed longjmp.exp also passes
> cleanly on x86-pc-linux-gnu.
>
> What do you think?
Looks like the right way to go for me. Unfortunately, I doesn't quite
work yet on the platforms I've tried it (s390, s390x, powerpc, powerpc64,
and spu) -- the "next" over
110 call_longjmp (&env); /* patt2 */
always causes the program to run to its end. I didn't get the chance yet
to debug this problem ...
Another issue with your patch is the use of frame_id_inner ... I'd rather
get rid of this function instead of adding new uses, because this really
requires that it is possible to compare two stack (frame) addresses
along a linear order. This breaks for me in multi-architecture scenarios,
but even on existing targets it may not always work OK (e.g. if signal
handlers run on a different frame, or if the code uses some sort of
user-level threading or coroutine library ...). Maybe instead of
comparing frame_ids, it would be better to check whether or not a
frame with the given ID still exists in the current backtrace?
Bye,
Ulrich
--
Dr. Ulrich Weigand
GNU Toolchain for Linux on System z and Cell BE
Ulrich.Weigand@de.ibm.com
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [patch] Re: longjmp handling vs. glibc LD_POINTER_GUARD problems
2008-05-22 0:11 ` Ulrich Weigand
@ 2008-05-22 0:14 ` Pedro Alves
2008-05-22 15:20 ` Pedro Alves
2008-05-22 16:29 ` [patch] Re: longjmp handling vs. glibc LD_POINTER_GUARD problems Ulrich Weigand
2008-05-22 3:14 ` Daniel Jacobowitz
1 sibling, 2 replies; 17+ messages in thread
From: Pedro Alves @ 2008-05-22 0:14 UTC (permalink / raw)
To: Ulrich Weigand; +Cc: gdb-patches, Daniel Jacobowitz
A Wednesday 21 May 2008 20:20:19, Ulrich Weigand wrote:
> Pedro Alves wrote:
> > ... here's an updated patch. The tests are the same as before. Tested
> > on x86_86-unknown-linux-gnu, and confirmed longjmp.exp also passes
> > cleanly on x86-pc-linux-gnu.
> >
> > What do you think?
>
> Looks like the right way to go for me. Unfortunately, I doesn't quite
> work yet on the platforms I've tried it (s390, s390x, powerpc, powerpc64,
> and spu) -- the "next" over
> 110 call_longjmp (&env); /* patt2 */
> always causes the program to run to its end. I didn't get the chance yet
> to debug this problem ...
I had forgotten to that the longjmp breakpoints are only inserted
when there's a gdbarch_get_longjmp_target implementation, and none
of those target implements it ...
If you're willing, could you try changing
breakpoint.c:set_longjmp_breakpoint like so?
void
set_longjmp_breakpoint (void)
{
struct breakpoint *b;
- if (gdbarch_get_longjmp_target_p (current_gdbarch))
- {
create_longjmp_breakpoint ("longjmp");
create_longjmp_breakpoint ("_longjmp");
create_longjmp_breakpoint ("siglongjmp");
create_longjmp_breakpoint ("_siglongjmp");
- }
}
>
> Another issue with your patch is the use of frame_id_inner ... I'd rather
> get rid of this function instead of adding new uses, because this really
> requires that it is possible to compare two stack (frame) addresses
> along a linear order. This breaks for me in multi-architecture scenarios,
> but even on existing targets it may not always work OK (e.g. if signal
> handlers run on a different frame, or if the code uses some sort of
> user-level threading or coroutine library ...). Maybe instead of
> comparing frame_ids, it would be better to check whether or not a
> frame with the given ID still exists in the current backtrace?
Hmm, coroutines and different stacks, ... I had mentioned in the
other threads it wouldn't work on those cases. ;-)
OK. That may work too. I'll give it a try.
--
Pedro Alves
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [patch] Re: longjmp handling vs. glibc LD_POINTER_GUARD problems
2008-05-22 0:11 ` Ulrich Weigand
2008-05-22 0:14 ` Pedro Alves
@ 2008-05-22 3:14 ` Daniel Jacobowitz
1 sibling, 0 replies; 17+ messages in thread
From: Daniel Jacobowitz @ 2008-05-22 3:14 UTC (permalink / raw)
To: Ulrich Weigand; +Cc: Pedro Alves, gdb-patches
On Wed, May 21, 2008 at 09:20:19PM +0200, Ulrich Weigand wrote:
> Another issue with your patch is the use of frame_id_inner ... I'd rather
> get rid of this function instead of adding new uses, because this really
> requires that it is possible to compare two stack (frame) addresses
> along a linear order. This breaks for me in multi-architecture scenarios,
> but even on existing targets it may not always work OK (e.g. if signal
> handlers run on a different frame, or if the code uses some sort of
> user-level threading or coroutine library ...). Maybe instead of
> comparing frame_ids, it would be better to check whether or not a
> frame with the given ID still exists in the current backtrace?
Let's be careful, if doing that, that we don't search too far up the
stack chain. Infinite stacks are a not uncommon failure mode when
something's gone wrong in GDB...
--
Daniel Jacobowitz
CodeSourcery
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [patch] Re: longjmp handling vs. glibc LD_POINTER_GUARD problems
2008-05-22 0:14 ` Pedro Alves
@ 2008-05-22 15:20 ` Pedro Alves
2008-05-22 15:34 ` Daniel Jacobowitz
2008-05-22 16:29 ` [patch] Re: longjmp handling vs. glibc LD_POINTER_GUARD problems Ulrich Weigand
1 sibling, 1 reply; 17+ messages in thread
From: Pedro Alves @ 2008-05-22 15:20 UTC (permalink / raw)
To: gdb-patches; +Cc: Ulrich Weigand, Daniel Jacobowitz
[-- Attachment #1: Type: text/plain, Size: 4027 bytes --]
A Wednesday 21 May 2008 20:38:02, Pedro Alves wrote:
> A Wednesday 21 May 2008 20:20:19, Ulrich Weigand wrote:
>
> > Another issue with your patch is the use of frame_id_inner ... I'd rather
> > get rid of this function instead of adding new uses, because this really
> > requires that it is possible to compare two stack (frame) addresses
> > along a linear order. This breaks for me in multi-architecture
> > scenarios, but even on existing targets it may not always work OK (e.g.
> > if signal handlers run on a different frame, or if the code uses some
> > sort of user-level threading or coroutine library ...). Maybe instead of
> > comparing frame_ids, it would be better to check whether or not a frame
> > with the given ID still exists in the current backtrace?
> OK. That may work too. I'll give it a try.
Hmmm, I got blocked getting that to work. It always stops stepping
before getting to the other side. It looks like going out of
longjmp, the frame id of the longjmp's caller starts
being computed differently, or badly...
Using the longjmp.c testcase, on x86_64:
(gdb) n
99 longjmp (env, 1); /* patt1 */
(gdb) n
(about thirty or so single-steps go ok, but then it stops,)
0x00007f0480e4af33 in ?? () from /lib/libc.so.6
(gdb) bt
#0 0x00007f413d0dff33 in ?? () from /lib/libc.so.6
#1 0x000000000040067d in main ()
at ../../../src/gdb/testsuite/gdb.base/longjmp.c:96
(gdb) disassemble ($pc-20) ($pc + 28)
Dump of assembler code from 0x7f26b04fbf1f to 0x7f26b04fbf4f:
0x00007f26b04fbf1f: xor 0x30,%ecx
0x00007f26b04fbf26: ror $0x11,%rdx << look, pointer unmangling!
0x00007f26b04fbf2a: xor %fs:0x30,%rdx << " "
0x00007f26b04fbf33: mov (%rdi),%rbx << here, things go bad...
0x00007f26b04fbf36: mov 0x10(%rdi),%r12
0x00007f26b04fbf3a: mov 0x18(%rdi),%r13
0x00007f26b04fbf3e: mov 0x20(%rdi),%r14
0x00007f26b04fbf42: mov 0x28(%rdi),%r15
0x00007f26b04fbf46: mov %esi,%eax
0x00007f26b04fbf48: mov %r8,%rsp
0x00007f26b04fbf4b: mov %r9,%rbp
0x00007f26b04fbf4e: jmpq *%rdx
(gdb) i
(top-gdb) c
Continuing.
(gdb) up
#1 0x000000000040067d in main ()
at ../../../src/gdb/testsuite/gdb.base/longjmp.c:96
96 if (setjmp (env) == 0)
(gdb) i
(top-gdb) p /x get_frame_id (selected_frame)
$3 = {stack_addr = 0xe706f6f457b7fe24, code_addr = 0x400664, special_addr =
0x0, stack_addr_p = 0x1,
code_addr_p = 0x1, special_addr_p = 0x0}
When the longjmp breakpoint was hit it looked like:
Breakpoint 3, handle_inferior_event (ecs=0x7fffcdfb48f0)
at ../../src/gdb/infrun.c:2605
2605 ecs->stepping_through_longjmp = 1;
(top-gdb) p /x ecs->longjmp_caller_frame
$1 = {stack_addr = 0x7fff087e7b20, code_addr = 0x400664, special_addr = 0x0,
stack_addr_p = 0x1, code_addr_p = 0x1,
special_addr_p = 0x0}
Notice that stack_addr is now different of the original 0x7fff087e7b20,
although, the $sp is unwinds ok,
(top-gdb) c
Continuing.
(gdb) p $sp
$1 = (void *) 0x7fff087e7b00
x86 is even worse:
99 longjmp (env, 1); /* patt1 */
(gdb) n
0xf7da41c3 in ?? () from /lib32/libc.so.6
(gdb) bt
#0 0xf7da41c3 in ?? () from /lib32/libc.so.6
#1 0xffb327a8 in ?? ()
#2 0xf7da413c in siglongjmp () from /lib32/libc.so.6
Backtrace stopped: frame did not save the PC
(gdb) disassemble ($pc-10) ($pc+20)
Dump of assembler code from 0xf7da41b9 to 0xf7da41d7:
0xf7da41b9: nop
0xf7da41ba: nop
0xf7da41bb: nop
0xf7da41bc: nop
0xf7da41bd: nop
0xf7da41be: nop
0xf7da41bf: nop
0xf7da41c0: push %ebp
0xf7da41c1: mov %esp,%ebp
0xf7da41c3: push %ebx <<< fails here, just while
<<< setting up a new frame...
0xf7da41c4: call 0xf7d8f290 <_Unwind_Find_FDE@plt+120>
0xf7da41c9: add $0x11fe2b,%ebx
0xf7da41cf: sub $0x8,%esp
0xf7da41d2: mov 0x340c(%ebx),%eax
End of assembler dump.
(gdb)
Any ideas?
--
Pedro Alves
[-- Attachment #2: longjmp.diff --]
[-- Type: text/x-diff, Size: 11535 bytes --]
2008-05-21 Pedro Alves <pedro@codesourcery.com>
* infrun.c (struct execution_control_state): Add
stepping_through_longjmp and longjmp_caller_frame.
(init_execution_control_state): Clear them.
(context_switch): Context switch them.
(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.
(currently_stepping): Return true if stepping through longjmp.
* breakpoint.c (set_longjmp_breakpoint): Always set longjmp
breakpoints.
* gdbthread.h (struct thread_info): Add stepping_through_longjmp
and longjmp_caller_frame.
(save_infrun_state, load_infrun_state): Context switch them.
* thread.c (save_infrun_state, load_infrun_state): Ditto.
---
gdb/breakpoint.c | 13 ++----
gdb/gdbthread.h | 14 ++++++-
gdb/infrun.c | 106 +++++++++++++++++++++++++++++++++++++++++--------------
gdb/thread.c | 12 +++++-
4 files changed, 106 insertions(+), 39 deletions(-)
Index: src/gdb/infrun.c
===================================================================
--- src.orig/gdb/infrun.c 2008-05-21 20:27:11.000000000 +0100
+++ src/gdb/infrun.c 2008-05-21 22:31:15.000000000 +0100
@@ -1386,6 +1386,13 @@ struct execution_control_state
int step_after_step_resume_breakpoint;
int stepping_through_solib_after_catch;
bpstat stepping_through_solib_catchpoints;
+
+ /* True if a longjmp call was detected while stepping, and we're
+ single-stepping until the other end. */
+ int stepping_through_longjmp;
+ /* The frame that called longjmp. */
+ struct frame_id longjmp_caller_frame;
+
int new_thread_event;
struct target_waitstatus tmpstatus;
enum infwait_states infwait_state;
@@ -1556,6 +1563,8 @@ init_execution_control_state (struct exe
ecs->infwait_state = infwait_normal_state;
ecs->waiton_ptid = pid_to_ptid (-1);
ecs->wp = &(ecs->ws);
+ ecs->stepping_through_longjmp = 0;
+ ecs->longjmp_caller_frame = null_frame_id;
}
/* Return the cached copy of the last pid/waitstatus returned by
@@ -1605,7 +1614,9 @@ context_switch (struct execution_control
ecs->stepping_over_breakpoint,
ecs->stepping_through_solib_after_catch,
ecs->stepping_through_solib_catchpoints,
- ecs->current_line, ecs->current_symtab);
+ ecs->current_line, ecs->current_symtab,
+ ecs->stepping_through_longjmp,
+ ecs->longjmp_caller_frame);
/* Load infrun state for the new thread. */
load_infrun_state (ecs->ptid, &prev_pc,
@@ -1615,7 +1626,9 @@ context_switch (struct execution_control
&ecs->stepping_over_breakpoint,
&ecs->stepping_through_solib_after_catch,
&ecs->stepping_through_solib_catchpoints,
- &ecs->current_line, &ecs->current_symtab);
+ &ecs->current_line, &ecs->current_symtab,
+ &ecs->stepping_through_longjmp,
+ &ecs->longjmp_caller_frame);
}
switch_to_thread (ecs->ptid);
@@ -2449,7 +2462,8 @@ handle_inferior_event (struct execution_
ecs->random_signal
= !(bpstat_explains_signal (stop_bpstat)
|| stepping_over_breakpoint
- || (step_range_end && step_resume_breakpoint == NULL));
+ || (step_range_end && step_resume_breakpoint == NULL)
+ || ecs->stepping_through_longjmp);
else
{
ecs->random_signal = !bpstat_explains_signal (stop_bpstat);
@@ -2562,7 +2576,6 @@ process_event_stop_test:
/* Handle cases caused by hitting a breakpoint. */
{
- CORE_ADDR jmp_buf_pc;
struct bpstat_what what;
what = bpstat_what (stop_bpstat);
@@ -2575,35 +2588,21 @@ 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->stepping_over_breakpoint = 1;
- if (!gdbarch_get_longjmp_target_p (current_gdbarch)
- || !gdbarch_get_longjmp_target (current_gdbarch,
- get_current_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. */
- if (step_resume_breakpoint != NULL)
- delete_step_resume_breakpoint (&step_resume_breakpoint);
-
- /* Insert a breakpoint at resume address. */
- insert_longjmp_resume_breakpoint (jmp_buf_pc);
+ /* Store the frame id of the caller. We'll single-step until
+ we detect an outer frame becoming current. */
+ ecs->longjmp_caller_frame = frame_unwind_id (get_current_frame ());
+ /* See you on the other side! */
+ ecs->stepping_through_longjmp = 1;
keep_going (ecs);
return;
@@ -2819,6 +2818,60 @@ infrun: BPSTAT_WHAT_SET_LONGJMP_RESUME (
return;
}
+ if (ecs->stepping_through_longjmp)
+ {
+ struct frame_info *frame = get_current_frame ();
+ struct frame_id frame_id = get_frame_id (frame);
+ struct gdbarch *gdbarch = get_frame_arch (frame);
+
+ /* If the current frame is the longjmp caller's, then we're
+ done. We're also done if the longjmp caller's frame is not
+ found on the frame stack. */
+ if (!frame_id_eq (frame_id, ecs->longjmp_caller_frame)
+ && frame_find_by_id (ecs->longjmp_caller_frame) != NULL)
+ {
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog,
+ "infrun: stepping through longjmp\n");
+#if 0
+ /* REMOVE ME: For debugging purposes only. */
+ print_stack_frame (frame, 0, SRC_AND_LOC);
+#endif
+ /* Still not there. */
+ keep_going (ecs);
+ return;
+ }
+
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog, "infrun: got out of longjmp\n");
+
+ /* We made it. */
+ ecs->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 (step_resume_breakpoint)
+ {
+ if (!frame_id_eq (frame_id, step_resume_breakpoint->frame_id)
+ && frame_find_by_id (step_resume_breakpoint->frame_id) != NULL)
+ {
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog,
+ "\
+infrun: step-resume frame found on the stack\n");
+ }
+ else
+ {
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog,
+ "infrun: step-resume overran by longjmp\n");
+ delete_step_resume_breakpoint (&step_resume_breakpoint);
+ }
+ }
+ }
+
if (step_resume_breakpoint)
{
if (debug_infrun)
@@ -3183,6 +3236,7 @@ currently_stepping (struct execution_con
{
return (((step_range_end && step_resume_breakpoint == NULL)
|| stepping_over_breakpoint)
+ || ecs->stepping_through_longjmp
|| ecs->stepping_through_solib_after_catch
|| bpstat_should_step ());
}
Index: src/gdb/gdbthread.h
===================================================================
--- src.orig/gdb/gdbthread.h 2008-05-21 20:27:11.000000000 +0100
+++ src/gdb/gdbthread.h 2008-05-21 20:42:06.000000000 +0100
@@ -63,6 +63,12 @@ struct thread_info
when we finally do stop stepping. */
bpstat stepping_through_solib_catchpoints;
+ /* True if a longjmp call was detected while stepping, and we're
+ single-stepping until the other end. */
+ int stepping_through_longjmp;
+ /* The frame that called longjmp. */
+ struct frame_id longjmp_caller_frame;
+
/* Private data used by the target vector implementation. */
struct private_thread_info *private;
};
@@ -126,7 +132,9 @@ extern void save_infrun_state (ptid_t pt
int stepping_through_solib_after_catch,
bpstat stepping_through_solib_catchpoints,
int current_line,
- struct symtab *current_symtab);
+ struct symtab *current_symtab,
+ int stepping_through_longjmp,
+ struct frame_id longjmp_caller_frame);
/* infrun context switch: load the debugger state previously saved
for the given thread. */
@@ -141,7 +149,9 @@ extern void load_infrun_state (ptid_t pt
int *stepping_through_solib_affter_catch,
bpstat *stepping_through_solib_catchpoints,
int *current_line,
- struct symtab **current_symtab);
+ struct symtab **current_symtab,
+ int *stepping_through_longjmp,
+ struct frame_id *longjmp_caller_frame);
/* Switch from one thread to another. */
extern void switch_to_thread (ptid_t ptid);
Index: src/gdb/thread.c
===================================================================
--- src.orig/gdb/thread.c 2008-05-21 20:27:11.000000000 +0100
+++ src/gdb/thread.c 2008-05-21 20:42:06.000000000 +0100
@@ -325,7 +325,9 @@ load_infrun_state (ptid_t ptid,
int *stepping_through_solib_after_catch,
bpstat *stepping_through_solib_catchpoints,
int *current_line,
- struct symtab **current_symtab)
+ struct symtab **current_symtab,
+ int *stepping_through_longjmp,
+ struct frame_id *longjmp_caller_frame)
{
struct thread_info *tp;
@@ -348,6 +350,8 @@ load_infrun_state (ptid_t ptid,
tp->stepping_through_solib_catchpoints;
*current_line = tp->current_line;
*current_symtab = tp->current_symtab;
+ *stepping_through_longjmp = tp->stepping_through_longjmp;
+ *longjmp_caller_frame = tp->longjmp_caller_frame;
}
/* Save infrun state for the thread PID. */
@@ -364,7 +368,9 @@ save_infrun_state (ptid_t ptid,
int stepping_through_solib_after_catch,
bpstat stepping_through_solib_catchpoints,
int current_line,
- struct symtab *current_symtab)
+ struct symtab *current_symtab,
+ int stepping_through_longjmp,
+ struct frame_id longjmp_caller_frame)
{
struct thread_info *tp;
@@ -385,6 +391,8 @@ save_infrun_state (ptid_t ptid,
tp->stepping_through_solib_catchpoints = stepping_through_solib_catchpoints;
tp->current_line = current_line;
tp->current_symtab = current_symtab;
+ tp->stepping_through_longjmp = stepping_through_longjmp;
+ tp->longjmp_caller_frame = longjmp_caller_frame;
}
/* Return true if TP is an active thread. */
Index: src/gdb/breakpoint.c
===================================================================
--- src.orig/gdb/breakpoint.c 2008-05-21 20:27:11.000000000 +0100
+++ src/gdb/breakpoint.c 2008-05-21 20:42:06.000000000 +0100
@@ -4470,15 +4470,10 @@ create_longjmp_breakpoint (char *func_na
void
set_longjmp_breakpoint (void)
{
- struct breakpoint *b;
-
- if (gdbarch_get_longjmp_target_p (current_gdbarch))
- {
- create_longjmp_breakpoint ("longjmp");
- create_longjmp_breakpoint ("_longjmp");
- create_longjmp_breakpoint ("siglongjmp");
- create_longjmp_breakpoint ("_siglongjmp");
- }
+ create_longjmp_breakpoint ("longjmp");
+ create_longjmp_breakpoint ("_longjmp");
+ create_longjmp_breakpoint ("siglongjmp");
+ create_longjmp_breakpoint ("_siglongjmp");
}
/* Delete all longjmp breakpoints from THREAD. */
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [patch] Re: longjmp handling vs. glibc LD_POINTER_GUARD problems
2008-05-22 15:20 ` Pedro Alves
@ 2008-05-22 15:34 ` Daniel Jacobowitz
2008-05-22 16:17 ` Pedro Alves
0 siblings, 1 reply; 17+ messages in thread
From: Daniel Jacobowitz @ 2008-05-22 15:34 UTC (permalink / raw)
To: Pedro Alves; +Cc: gdb-patches, Ulrich Weigand
On Wed, May 21, 2008 at 11:01:25PM +0100, Pedro Alves wrote:
> Hmmm, I got blocked getting that to work. It always stops stepping
> before getting to the other side. It looks like going out of
> longjmp, the frame id of the longjmp's caller starts
> being computed differently, or badly...
This is the same problem as software watchpoints... I just don't think
we're going to be able to get it to work. I certainly had to do
horrible things on powerpc-linux when I wanted to be able to backtrace
from epilogues without a symbol table.
Which frame IDs are we comparing here? I think we can assume that
longjmp is not going to change stacks until it's about to return,
although the return might be on a different stack entirely.
I suspect we'll be prone to stopping in the last instruction or two of
longjmp, instead of returning the compiler.
--
Daniel Jacobowitz
CodeSourcery
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [patch] Re: longjmp handling vs. glibc LD_POINTER_GUARD problems
2008-05-22 15:34 ` Daniel Jacobowitz
@ 2008-05-22 16:17 ` Pedro Alves
2008-05-22 16:38 ` Ulrich Weigand
0 siblings, 1 reply; 17+ messages in thread
From: Pedro Alves @ 2008-05-22 16:17 UTC (permalink / raw)
To: gdb-patches; +Cc: Daniel Jacobowitz, Ulrich Weigand
A Wednesday 21 May 2008 23:15:48, Daniel Jacobowitz wrote:
>> This is the same problem as software watchpoints... I just don't think
> we're going to be able to get it to work. I certainly had to do
> horrible things on powerpc-linux when I wanted to be able to backtrace
> from epilogues without a symbol table.
> Which frame IDs are we comparing here?
I tried comparing the frame id of longjmp itself with with the
current frame first:
- record the longjmp frame when the longjmp breakpoint is hit.
- single-step until that frame is gone from the frame stack
Then (the logs I posted) tried comparing the longjmp's caller with
the the current frame while single stepping.
- record the longjmp's caller frame when the longjmp breakpoint
is hit.
- single-step until that frame is either current, or gone from the
frame stack.
Both gave the same results.
> I think we can assume that longjmp is not going to change
> stacks until it's about to return,
> although the return might be on a different stack entirely.
> I suspect we'll be prone to stopping in the last instruction or two of
> longjmp, instead of returning the compiler.
The point where the stepping stops is exactly the point
where the stacks change on x86_64.
(gdb) up
#1 0x00007fab4b544ee3 in siglongjmp () from /lib/libc.so.6
(gdb)
#2 0x000000000040069f in main ()
at ../../../src/gdb/testsuite/gdb.base/longjmp.c:99
99 longjmp (env, 1); /* patt1 */
(gdb) si
0x00007fab4b544f33 in ?? () from /lib/libc.so.6
(gdb) up
#1 0x000000000040067d in main ()
at ../../../src/gdb/testsuite/gdb.base/longjmp.c:96
96 if (setjmp (env) == 0)
With the patch installed, it's stopping exactly at
0x00007fab4b544f33.
Seeing this, I was thinking of:
- recording the longjmp frame when the longjmp breakpoint is hit
- single-step until the longjmp frame is gone (going to return to setjmp --
SP/FP changing)
- single-step until this new current frame is gone.
But, x86 doesn't show any promise on that... The first time
we stop seeing the longjmp frame on the frame stack is much
earlier than the exit of longjmp:
#0 0xf7e201d8 in ?? () from /lib32/libc.so.6
#1 0x00000001 in ?? ()
#2 0xf7e201c9 in ?? () from /lib32/libc.so.6
#3 0xf7f3fff4 in ?? () from /lib32/libc.so.6
#4 0xfff7bbe8 in ?? ()
#5 0xf7e2013c in siglongjmp () from /lib32/libc.so.6
Backtrace stopped: frame did not save the PC
Looks like a dead end.
--
Pedro Alves
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [patch] Re: longjmp handling vs. glibc LD_POINTER_GUARD problems
2008-05-22 0:14 ` Pedro Alves
2008-05-22 15:20 ` Pedro Alves
@ 2008-05-22 16:29 ` Ulrich Weigand
1 sibling, 0 replies; 17+ messages in thread
From: Ulrich Weigand @ 2008-05-22 16:29 UTC (permalink / raw)
To: Pedro Alves; +Cc: gdb-patches, Daniel Jacobowitz
Pedro Alves wrote:
> If you're willing, could you try changing
> breakpoint.c:set_longjmp_breakpoint like so?
>
> void
> set_longjmp_breakpoint (void)
> {
> struct breakpoint *b;
>
> - if (gdbarch_get_longjmp_target_p (current_gdbarch))
> - {
> create_longjmp_breakpoint ("longjmp");
> create_longjmp_breakpoint ("_longjmp");
> create_longjmp_breakpoint ("siglongjmp");
> create_longjmp_breakpoint ("_siglongjmp");
> - }
> }
Unfortunately this still doesn't help on s390. I think I'm running
into the same type of problem you're having with you new implementation.
When I get to here:
107 /* Pattern 2 - longjmp from an inner function. */
108 if (setjmp (env) == 0)
109 {
110 call_longjmp (&env); /* patt2 */
111 }
and want to "next" over the patt2 line, GDB steps into the function
and sets a step-resume breakpoint on the return address.
At this point the stack pointer is
(gdb) print/x $r15
$1 = 0x7fd87930
and the frame address is
(gdb) info frame 0
Stack frame at 0x7fd879f8:
pc = 0x400720 in main (/home/uweigand/fsf/gdb-head/gdb/testsuite/gdb.base/longjmp.c:110);
saved pc 0x77df8762
source language c.
Arglist at 0x7fd879f8, args:
Locals at 0x7fd879f8, Previous frame's sp at 0x7fd879d4
Saved registers:
r11 at 0x7fd879c4, r12 at 0x7fd879c8, r13 at 0x7fd879cc, r14 at 0x7fd879d0, pc at 0x7fd879d0
(Note that the frame address is the CFA, which is computed as
stack pointer + frame size + stack bias (96 bytes) on s390.)
It then catches the longjmp breakpoint, and starts stepping through.
However, even with your original version using the frame_id_inner
check, stepping stops at the point the stack pointer is reloaded,
before we actually jump to the longjmp target:
infrun: stepping through longjmp
infrun: resume (step=1, signal=0), stepping_over_breakpoint=0
infrun: prepare_to_wait
infrun: infwait_normal_state
infrun: TARGET_WAITKIND_STOPPED
infrun: stop_pc = 0x77e0dfda
infrun: got out of longjmp
infrun: longjmp-resume inner than step-resume
infrun: step-resume breakpoint is inserted
(gdb) disass 0x77e0dfda
Dump of assembler code for function __longjmp:
0x77e0dfb0 <__longjmp+0>: lr %r1,%r2
0x77e0dfb2 <__longjmp+2>: ltr %r2,%r3
0x77e0dfb4 <__longjmp+4>: jne 0x77e0dfbc <__longjmp+12>
0x77e0dfb8 <__longjmp+8>: lhi %r2,1
0x77e0dfbc <__longjmp+12>: ear %r4,%a0
0x77e0dfc0 <__longjmp+16>: l %r3,20(%r4)
0x77e0dfc4 <__longjmp+20>: ld %f6,48(%r1)
0x77e0dfc8 <__longjmp+24>: ld %f4,40(%r1)
0x77e0dfcc <__longjmp+28>: lm %r6,%r13,0(%r1)
0x77e0dfd0 <__longjmp+32>: lm %r4,%r5,32(%r1)
0x77e0dfd4 <__longjmp+36>: xr %r4,%r3
0x77e0dfd6 <__longjmp+38>: xr %r5,%r3
0x77e0dfd8 <__longjmp+40>: lr %r15,%r5 <<- sets the stack pointer
0x77e0dfda <__longjmp+42>: br %r4
0x77e0dfdc <__longjmp+44>: j 0x77e0dfdc <__longjmp+44>
However, at this point unwinding is broken, because the value
of the stack pointer doesn't fit the status of __longjmp as
frameless function as detected by prologue analysis.
Breakpoint 2, 0x77e0dfda in __longjmp () from /lib/libc.so.6
(gdb) bt
#0 0x77e0dfda in __longjmp () from /lib/libc.so.6
#1 0x77e0df84 in siglongjmp () from /lib/libc.so.6
#2 0x77f31000 in ?? () from /lib/libc.so.6
Cannot access memory at address 0xa7faff7c
Now, even though the stack pointer was restored to the value
it will have in main:
(gdb) print/x $r15
$1 = 0x7fd87930
the frame address is computed under the assumption that the
current function is frameless:
(gdb) info frame 0
Stack frame at 0x7fd87990:
pc = 0x77e0dfda in __longjmp; saved pc 0x77e0df84
called by frame at 0x7fd879f0
Arglist at 0x7fd87990, args:
Locals at 0x7fd87990, Previous frame's sp in r15
Now even with the frame_id_inner check, this address counts
as strictly inner to the step-resume breakpoint's frame, and
thus GDB will keep going until that breakpoint is hit.
Unfortunately, we have in fact bypassed that step-resume
target location by the longjmp, and thus it will never hit ...
Bye,
Ulrich
--
Dr. Ulrich Weigand
GNU Toolchain for Linux on System z and Cell BE
Ulrich.Weigand@de.ibm.com
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [patch] Re: longjmp handling vs. glibc LD_POINTER_GUARD problems
2008-05-22 16:17 ` Pedro Alves
@ 2008-05-22 16:38 ` Ulrich Weigand
2008-05-22 17:03 ` [patch] Re: longjmp handling vs. glibc LD_POINTER_GUARD ?problems Daniel Jacobowitz
0 siblings, 1 reply; 17+ messages in thread
From: Ulrich Weigand @ 2008-05-22 16:38 UTC (permalink / raw)
To: Pedro Alves; +Cc: gdb-patches, Daniel Jacobowitz
Pedro Alves wrote:
> Seeing this, I was thinking of:
> - recording the longjmp frame when the longjmp breakpoint is hit
> - single-step until the longjmp frame is gone (going to return to setjmp --
> SP/FP changing)
> - single-step until this new current frame is gone.
During the time longjmp reloads the registers, I now don't think
we can trust the frame at all; this is even worse that during
regular function epilogues.
I think one heuristics might be that as soon as we notice odd
things to happen to the frame, we step until we reach the end
of the current *function* (i.e. look only at the PC).
> But, x86 doesn't show any promise on that... The first time
> we stop seeing the longjmp frame on the frame stack is much
> earlier than the exit of longjmp:
>
> #0 0xf7e201d8 in ?? () from /lib32/libc.so.6
> #1 0x00000001 in ?? ()
So what's happening there? Is this some unrelated unwinder
failure?
Bye,
Ulrich
--
Dr. Ulrich Weigand
GNU Toolchain for Linux on System z and Cell BE
Ulrich.Weigand@de.ibm.com
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [patch] Re: longjmp handling vs. glibc LD_POINTER_GUARD ?problems
2008-05-22 16:38 ` Ulrich Weigand
@ 2008-05-22 17:03 ` Daniel Jacobowitz
0 siblings, 0 replies; 17+ messages in thread
From: Daniel Jacobowitz @ 2008-05-22 17:03 UTC (permalink / raw)
To: Ulrich Weigand; +Cc: Pedro Alves, gdb-patches
On Thu, May 22, 2008 at 02:14:25AM +0200, Ulrich Weigand wrote:
> > But, x86 doesn't show any promise on that... The first time
> > we stop seeing the longjmp frame on the frame stack is much
> > earlier than the exit of longjmp:
> >
> > #0 0xf7e201d8 in ?? () from /lib32/libc.so.6
> > #1 0x00000001 in ?? ()
>
> So what's happening there? Is this some unrelated unwinder
> failure?
My guess: longjmp tail called to __siglongmp or some similarly named
not-exported function. So we can't find whatever frame setup there
was.
--
Daniel Jacobowitz
CodeSourcery
^ permalink raw reply [flat|nested] 17+ messages in thread
end of thread, other threads:[~2008-05-22 3:14 UTC | newest]
Thread overview: 17+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2008-05-14 18:24 longjmp handling vs. glibc LD_POINTER_GUARD problems Ulrich Weigand
2008-05-14 19:14 ` Daniel Jacobowitz
2008-05-14 22:01 ` Ulrich Weigand
2008-05-14 19:17 ` Pedro Alves
2008-05-17 14:00 ` Pedro Alves
2008-05-21 4:20 ` [patch] " Pedro Alves
2008-05-22 0:11 ` Ulrich Weigand
2008-05-22 0:14 ` Pedro Alves
2008-05-22 15:20 ` Pedro Alves
2008-05-22 15:34 ` Daniel Jacobowitz
2008-05-22 16:17 ` Pedro Alves
2008-05-22 16:38 ` Ulrich Weigand
2008-05-22 17:03 ` [patch] Re: longjmp handling vs. glibc LD_POINTER_GUARD ?problems Daniel Jacobowitz
2008-05-22 16:29 ` [patch] Re: longjmp handling vs. glibc LD_POINTER_GUARD problems Ulrich Weigand
2008-05-22 3:14 ` Daniel Jacobowitz
2008-05-14 23:03 ` David Miller
2008-05-15 0:39 ` Daniel Jacobowitz
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox