[PATCH] stepi/nexti: skip signal handler if "handle nostop" signal arrives

Pedro Alves
2014-10-14T17:48:35Z

I noticed that "si" behaves differently when a "handle nostop" signal
arrives while the step is in progress, depending on whether the
program was stopped at a breakpoint when "si" was entered.
Specifically, in case GDB needs to step off a breakpoint, the handler
is skipped and the program stops in the next "mainline" instruction.
Otherwise, the "si" stops in the first instruction of the signal
handler.

I was surprised the testsuite doesn't catch this difference. Turns
out gdb.base/sigstep.exp covers a bunch of cases related to stepping
and signal handlers, but does not test stepi nor nexti, only
step/next/continue.

My first reaction was that stopping in the signal handler was the
correct thing to do, as it's where the next user-visible instruction
that is executed is. I considered then "nexti" -- a signal handler
could be reasonably considered a subroutine call to step over, it'd
seem intuitive to me that "nexti" would skip it.

But then, I realized that signals that arrive while a plain/line
"step" is in progress _also_ have their handler skipped. A user might
well be excused for being confused by this, given:

(gdb) help step
Step program until it reaches a different source line.

And the signal handler's sources will be in different source lines,
after all.

I think that having to explain that "stepi" steps into handlers, (and
that "nexti" wouldn't according to my reasoning above), while "step"
does, is a sign of an awkward interface.

E.g., if a user truly is interested in stepping into signal handlers,
then it's odd that she has to either force the signal to "handle
stop", or recall to do "stepi" whenever such a signal might be
delivered. For that use case, it'd seem nicer to me if "step" also
stepped into handlers.

This suggests to me that we either need a global "step-into-handlers"
setting, or perhaps better, make "handle pass/nopass stop/nostop
print/noprint" have have an additional axis - "handle
stepinto/nostepinto", so that the user could configure whether
handlers for specific signals should be stepped into.

In any case, I think it's simpler (and thus better) for all step
commands to behave the same. This commit thus makes "si/ni" skip
handlers for "handle nostop" signals that arrive while the command was
already in progress, like step/next do.

To be clear, nothing changes if the program was stopped for a signal,
and the user enters a stepping command _then_ -- GDB still steps into
the handler. The change concerns signals that don't cause a stop and
that arrive while the step is in progress.

Tested on x86_64 Fedora 20, native and gdbserver.

gdb/
2014-10-14 Pedro Alves <palves@redhat.com>

* infrun.c (handle_signal_stop): Also skip handlers when a random
signal arrives while handling a "stepi" or a "nexti". Set the
thread's 'step_after_step_resume_breakpoint' flag.

gdb/doc/
2014-10-14 Pedro Alves <palves@redhat.com>

* gdb.texinfo (Signals): Clarify stepping and signal handlers.

gdb/testsuite/
2014-10-14 Pedro Alves <palves@redhat.com>

* gdb.base/sigstep.c (dummy): New global.
(main): Issue a couple writes to the new global.
* gdb.base/sigstep.exp (get_next_pc, test_skip_handler): New
procedures.
(skip_over_handler): Use test_skip_handler.
(top level): Call skip_over_handler for stepi and nexti too.
(breakpoint_over_handler): Use test_skip_handler.
(top level): Call breakpoint_over_handler for stepi and nexti too.
---
gdb/doc/gdb.texinfo | 9 +++++++
gdb/infrun.c | 4 ++-
gdb/testsuite/gdb.base/sigstep.c | 7 +++--
gdb/testsuite/gdb.base/sigstep.exp | 52 ++++++++++++++++++++++++++++----------
4 files changed, 56 insertions(+), 16 deletions(-)

diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 429c650..46c327e 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -5526,6 +5526,11 @@ Their full names are:
@value{GDBN} should not stop your program when this signal happens. It may
still print a message telling you that the signal has come in.

+If this signal arrives while a stepping command (e.g., @code{step}) is
+in progress, the signal's handler is skipped (though still executed if
+@code{pass} is in effect; see below). @value{GDBN} will still stop
+your program if the handler hits a breakpoint.
+
@item stop
@value{GDBN} should stop your program when this signal happens. This implies
the @code{print} keyword as well.
@@ -5558,6 +5563,10 @@ after @value{GDBN} reports a signal, you can use the @code{handle}
command with @code{pass} or @code{nopass} to control whether your
program sees that signal when you continue.

+If a stepping command is issued after the program stops for a signal,
+and @code{pass} is in effect for that signal, @value{GDBN} steps into
+the signal's handler (if the target supports it).
+
The default is set to @code{nostop}, @code{noprint}, @code{pass} for
non-erroneous signals such as @code{SIGALRM}, @code{SIGWINCH} and
@code{SIGCHLD}, and to @code{stop}, @code{print}, @code{pass} for the
diff --git a/gdb/infrun.c b/gdb/infrun.c
index d61cc12..3682765 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -4455,7 +4455,8 @@ handle_signal_stop (struct execution_control_state *ecs)

if (ecs->event_thread->control.step_range_end != 0
&& ecs->event_thread->suspend.stop_signal != GDB_SIGNAL_0
- && pc_in_thread_step_range (stop_pc, ecs->event_thread)
+ && (pc_in_thread_step_range (stop_pc, ecs->event_thread)
+ || ecs->event_thread->control.step_range_end == 1)
&& frame_id_eq (get_stack_frame_id (frame),
ecs->event_thread->control.step_stack_frame_id)
&& ecs->event_thread->control.step_resume_breakpoint == NULL)
@@ -4475,6 +4476,7 @@ handle_signal_stop (struct execution_control_state *ecs)
"single-step range\n");

insert_hp_step_resume_breakpoint_at_frame (frame);
+ ecs->event_thread->step_after_step_resume_breakpoint = 1;
/* Reset trap_expected to ensure breakpoints are re-inserted. */
ecs->event_thread->control.trap_expected = 0;
keep_going (ecs);
diff --git a/gdb/testsuite/gdb.base/sigstep.c b/gdb/testsuite/gdb.base/sigstep.c
index aa2384a..25a4647 100644
--- a/gdb/testsuite/gdb.base/sigstep.c
+++ b/gdb/testsuite/gdb.base/sigstep.c
@@ -24,6 +24,7 @@
#include <errno.h>

static volatile int done;
+static volatile int dummy;

static void
handler (int sig)
@@ -74,8 +75,10 @@ main ()
return 1;
}
}
- /* Wait. */
- while (!done);
+ /* Wait. Issue a couple writes to a dummy volatile var to be
+ reasonably sure our simple "get-next-pc" logic doesn't
+ stumble on branches. */
+ dummy = 0; dummy = 1; while (!done);
done = 0;
}
return 0;
diff --git a/gdb/testsuite/gdb.base/sigstep.exp b/gdb/testsuite/gdb.base/sigstep.exp
index 184d46e..b589e12 100644
--- a/gdb/testsuite/gdb.base/sigstep.exp
+++ b/gdb/testsuite/gdb.base/sigstep.exp
@@ -269,9 +269,35 @@ proc skip_to_handler_entry { i } {
gdb_test "clear *handler" ".*" "$prefix; clear handler"
}

-skip_to_handler_entry step
-skip_to_handler_entry next
-skip_to_handler_entry continue
+foreach cmd {stepi nexti step next continue} {
+ skip_to_handler_entry $cmd
+}
+
+# Get the address of where a single-step should land.
+proc get_next_pc {test} {
+ global gdb_prompt
+ global hex
+
+ set next ""
+ gdb_test_multiple "x/2i \$pc" $test {
+ -re "$hex .*:\[^\r\n\]+\r\n\[ \]+($hex).*\.\r\n$gdb_prompt $" {
+ set next $expect_out(1,string)
+ pass $test
+ }
+ }
+
+ return $next
+}
+
+proc test_skip_handler {prefix i} {
+ if {$i == "stepi" || $i == "nexti"} {
+ set next_pc [get_next_pc "$prefix; get next PC"]
+ gdb_test "$i" "dummy = 0.*" "$prefix; performing $i"
+ gdb_test "p /x \$pc" " = $next_pc" "$prefix; advanced"
+ } else {
+ gdb_test "$i" "done = 0.*" "$prefix; performing $i"
+ }
+}

# Try stepping when there's a signal pending but no breakpoints.
# Should skip the handler advancing to the next line.
@@ -295,13 +321,13 @@ proc skip_over_handler { i } {

# Make the signal pending
sleep 1
-
- gdb_test "$i" "done = 0.*" "$prefix; performing $i"
+
+ test_skip_handler $prefix $i
}

-skip_over_handler step
-skip_over_handler next
-skip_over_handler continue
+foreach cmd {stepi nexti step next continue} {
+ skip_over_handler $cmd
+}

# Try stepping when there's a signal pending, a pre-existing
# breakpoint at the current instruction, and a breakpoint in the
@@ -385,7 +411,7 @@ breakpoint_to_handler_entry continue

# Try stepping when there's a signal pending, and a pre-existing
# breakpoint at the current instruction, and no breakpoint in the
-# handler. Should advance to the next line.
+# handler. Should advance to the next line/instruction.

proc breakpoint_over_handler { i } {
global gdb_prompt
@@ -409,10 +435,10 @@ proc breakpoint_over_handler { i } {
# Make the signal pending
sleep 1

- gdb_test "$i" "done = 0.*" "$prefix; performing $i"
+ test_skip_handler $prefix $i
gdb_test "clear $infinite_loop" ".*" "$prefix; clear infinite loop"
}

-breakpoint_over_handler step
-breakpoint_over_handler next
-breakpoint_over_handler continue
+foreach cmd {stepi nexti step next continue} {
+ breakpoint_over_handler $cmd
+}
--
1.9.3