public inbox for systemtap@sourceware.org
 help / color / mirror / Atom feed
* [PATCH] Add new built-in tapset function quit()
@ 2018-08-24  1:51 Yichun Zhang (agentzh)
  2018-08-28  2:43 ` [PATCH v2] " Yichun Zhang (agentzh)
  0 siblings, 1 reply; 5+ messages in thread
From: Yichun Zhang (agentzh) @ 2018-08-24  1:51 UTC (permalink / raw)
  To: systemtap; +Cc: Yichun Zhang (agentzh)

The new built-in tapset function quit() is similar to exit(), but it
aborts the currently executing functions and probe handlers immediately.
Works in both the kernel and dyninst runtimes. The bpf runtime is not
yet supported.

fche thinks it is already too late to change the current behavior of
exit(), hence this new function.

Also added corresponding tests for both quit() and exit().
---
 runtime/dyninst/io.c                |   9 ++++
 runtime/linux/io.c                  |  13 +++++
 tapset/logging.stp                  |  20 +++++++
 testsuite/systemtap.base/exit.exp   | 101 ++++++++++++++++++++++++++++++++++++
 testsuite/systemtap.base/exit_1.stp |  11 ++++
 testsuite/systemtap.base/exit_2.stp |  17 ++++++
 testsuite/systemtap.base/exit_3.stp |   5 ++
 testsuite/systemtap.base/quit.exp   | 101 ++++++++++++++++++++++++++++++++++++
 testsuite/systemtap.base/quit_1.stp |  11 ++++
 testsuite/systemtap.base/quit_2.stp |  17 ++++++
 testsuite/systemtap.base/quit_3.stp |   5 ++
 translate.cxx                       |   4 +-
 12 files changed, 312 insertions(+), 2 deletions(-)
 create mode 100644 testsuite/systemtap.base/exit.exp
 create mode 100644 testsuite/systemtap.base/exit_1.stp
 create mode 100644 testsuite/systemtap.base/exit_2.stp
 create mode 100644 testsuite/systemtap.base/exit_3.stp
 create mode 100644 testsuite/systemtap.base/quit.exp
 create mode 100644 testsuite/systemtap.base/quit_1.stp
 create mode 100644 testsuite/systemtap.base/quit_2.stp
 create mode 100644 testsuite/systemtap.base/quit_3.stp

diff --git a/runtime/dyninst/io.c b/runtime/dyninst/io.c
index b8e9568a5..f08ca575d 100644
--- a/runtime/dyninst/io.c
+++ b/runtime/dyninst/io.c
@@ -19,6 +19,9 @@
 #define WARN_STRING "WARNING: "
 #define ERR_STRING "ERROR: "
 
+// a flag to signal the exiting state for stap function calls
+static int _stp_quitting = 0;
+
 enum code { INFO=0, WARN, ERROR, DBUG };
 
 static void _stp_vlog (enum code type, const char *func, int line,
@@ -165,4 +168,10 @@ static void _stp_exit (void)
 	_stp_dyninst_transport_request_exit();
 }
 
+static void _stp_quit (void)
+{
+	_stp_quitting = 1;
+	_stp_exit ();
+}
+
 #endif /* _STAPDYN_IO_C_ */
diff --git a/runtime/linux/io.c b/runtime/linux/io.c
index 74a032c52..4d2e35d10 100644
--- a/runtime/linux/io.c
+++ b/runtime/linux/io.c
@@ -24,6 +24,9 @@
 #error "STP_LOG_BUF_LEN is too short"
 #endif
 
+// a flag to signal the exiting state for stap function calls
+static int _stp_quitting = 0;
+
 enum code { INFO=0, WARN, ERROR, DBUG };
 
 static void _stp_vlog (enum code type, const char *func, int line, const char *fmt, va_list args)
@@ -105,6 +108,16 @@ static void _stp_exit (void)
 	_stp_exit_flag = 1;
 }
 
+/** Similar to _stp_exit(), but also sets the flag
+ * _stp_quitting.
+ */
+static void _stp_quit (void)
+{
+	_stp_quitting = 1;
+	_stp_exit ();
+}
+
+
 /** Prints error message and exits.
  * This function sends an error message immediately to staprun. It
  * will also be sent over the bulk transport (relayfs) if it is
diff --git a/tapset/logging.stp b/tapset/logging.stp
index d09b8eb80..ae91123c7 100644
--- a/tapset/logging.stp
+++ b/tapset/logging.stp
@@ -75,6 +75,26 @@ function exit ()
 
 
 /**
+ * sfunction quit - Immediately shutting down probing script.
+ *
+ * Description: This is similar to exit() but immediately stops
+ * the currently executing functions and probes.
+ */
+function quit ()
+%( runtime != "bpf" %?
+  %{ /* unprivileged */
+     atomic_set (session_state(), STAP_SESSION_STOPPING);
+     _stp_quit ();
+  %}
+%:
+  { /* unprivileged */ /* bpf */
+    _set_exit_status()
+    printf("ERROR: quit() not supported yet\n")
+    exit()  /* TODO: need to abort the execution flow immediately */
+  }
+%)
+
+/**
  * sfunction error - Send an error message
  *
  * @msg: The formatted message string
diff --git a/testsuite/systemtap.base/exit.exp b/testsuite/systemtap.base/exit.exp
new file mode 100644
index 000000000..525188c9c
--- /dev/null
+++ b/testsuite/systemtap.base/exit.exp
@@ -0,0 +1,101 @@
+set test "exit"
+set testpath "$srcdir/$subdir"
+
+if {! [installtest_p]} { untested "$test"; return }
+if {! [uretprobes_p]} { untested "$test"; return }
+
+# --- TEST 1 ---
+
+set subtest1 "TEST 1: exit() in the middle of a func body"
+foreach runtime [get_runtime_list] {
+    if {$runtime eq ""} { set runtime "kernel" }
+    set test_name "$test: $subtest1 ($runtime)"
+
+    set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_1.stp'"
+    send_log "executing: $cmd\n"
+    set pipe [open "| sh -c {$cmd}" r]
+    set out [read $pipe]
+    set failed 0
+    if {[catch {close $pipe} stderr] != 0} { set failed 1 }
+
+    set exp_out "enter f\nleave f\nexit\n"
+    regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+    if {$out eq $exp_out} {
+        pass "${test_name}: stdout matches \"$escaped_exp_out\""
+    } else {
+        fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+    }
+
+    if {$failed} {
+        fail "${test_name}: exit code not zero"
+    } else {
+        pass "${test_name}: exit code is zero"
+    }
+    if {$stderr ne ""} {
+        send_log "stderr:\n$stderr"
+    }
+}
+
+# --- TEST 2 ---
+
+set subtest2 "TEST 2: exit() in the middle of a func body in a deeper func call chain"
+foreach runtime [get_runtime_list] {
+    if {$runtime eq ""} { set runtime "kernel" }
+    set test_name "$test: $subtest2 ($runtime)"
+
+    set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_2.stp'"
+    send_log "executing: $cmd\n"
+    set pipe [open "| sh -c {$cmd}" r]
+    set out [read $pipe]
+    set failed 0
+    if {[catch {close $pipe} stderr] != 0} { set failed 1 }
+
+    set exp_out "enter g\nenter f\nleave f\nleave g\nexit\n"
+    regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+    if {$out eq $exp_out} {
+        pass "${test_name}: stdout matches \"$escaped_exp_out\""
+    } else {
+        fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+    }
+
+    if {$failed} {
+        fail "${test_name}: exit code not zero"
+    } else {
+        pass "${test_name}: exit code is zero"
+    }
+    if {$stderr ne ""} {
+        send_log "stderr:\n$stderr"
+    }
+}
+
+# --- TEST 3 ---
+
+set subtest3 "TEST 3: exit() in the middle of a probe handler body"
+foreach runtime [get_runtime_list] {
+    if {$runtime eq ""} { set runtime "kernel" }
+    set test_name "$test: $subtest3 ($runtime)"
+
+    set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_3.stp'"
+    send_log "executing: $cmd\n"
+    set pipe [open "| sh -c {$cmd}" r]
+    set out [read $pipe]
+    set failed 0
+    if {[catch {close $pipe} stderr] != 0} { set failed 1 }
+
+    set exp_out "enter probe\nleave probe\n"
+    regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+    if {$out eq $exp_out} {
+        pass "${test_name}: stdout matches \"$escaped_exp_out\""
+    } else {
+        fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+    }
+
+    if {$failed} {
+        fail "${test_name}: exit code not zero"
+    } else {
+        pass "${test_name}: exit code is zero"
+    }
+    if {$stderr ne ""} {
+        send_log "stderr:\n$stderr"
+    }
+}
diff --git a/testsuite/systemtap.base/exit_1.stp b/testsuite/systemtap.base/exit_1.stp
new file mode 100644
index 000000000..c2dd0141a
--- /dev/null
+++ b/testsuite/systemtap.base/exit_1.stp
@@ -0,0 +1,11 @@
+function f() {
+    println("enter f");
+    exit();
+    println("leave f");
+}
+
+probe begin {
+    f();
+    printf("exit\n");
+    exit();
+}
diff --git a/testsuite/systemtap.base/exit_2.stp b/testsuite/systemtap.base/exit_2.stp
new file mode 100644
index 000000000..b747ddc0c
--- /dev/null
+++ b/testsuite/systemtap.base/exit_2.stp
@@ -0,0 +1,17 @@
+function f() {
+    println("enter f");
+    exit();
+    println("leave f");
+}
+
+function g() {
+    println("enter g");
+    f();
+    println("leave g");
+}
+
+probe begin {
+    g();
+    printf("exit\n");
+    exit();
+}
diff --git a/testsuite/systemtap.base/exit_3.stp b/testsuite/systemtap.base/exit_3.stp
new file mode 100644
index 000000000..3f3b801d1
--- /dev/null
+++ b/testsuite/systemtap.base/exit_3.stp
@@ -0,0 +1,5 @@
+probe begin {
+    println("enter probe");
+    exit();
+    println("leave probe");
+}
diff --git a/testsuite/systemtap.base/quit.exp b/testsuite/systemtap.base/quit.exp
new file mode 100644
index 000000000..97c9acbc7
--- /dev/null
+++ b/testsuite/systemtap.base/quit.exp
@@ -0,0 +1,101 @@
+set test "quit"
+set testpath "$srcdir/$subdir"
+
+if {! [installtest_p]} { untested "$test"; return }
+if {! [uretprobes_p]} { untested "$test"; return }
+
+# --- TEST 1 ---
+
+set subtest1 "TEST 1: quit() in the middle of a func body"
+foreach runtime [get_runtime_list] {
+    if {$runtime eq ""} { set runtime "kernel" }
+    set test_name "$test: $subtest1 ($runtime)"
+
+    set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_1.stp'"
+    send_log "executing: $cmd\n"
+    set pipe [open "| sh -c {$cmd}" r]
+    set out [read $pipe]
+    set failed 0
+    if {[catch {close $pipe} stderr] != 0} { set failed 1 }
+
+    set exp_out "enter f\n"
+    regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+    if {$out eq $exp_out} {
+        pass "${test_name}: stdout matches \"$escaped_exp_out\""
+    } else {
+        fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+    }
+
+    if {$failed} {
+        fail "${test_name}: exit code not zero"
+    } else {
+        pass "${test_name}: exit code is zero"
+    }
+    if {$stderr ne ""} {
+        send_log "stderr:\n$stderr"
+    }
+}
+
+# --- TEST 2 ---
+
+set subtest2 "TEST 2: quit() in the middle of a func body in a deeper func call chain"
+foreach runtime [get_runtime_list] {
+    if {$runtime eq ""} { set runtime "kernel" }
+    set test_name "$test: $subtest2 ($runtime)"
+
+    set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_2.stp'"
+    send_log "executing: $cmd\n"
+    set pipe [open "| sh -c {$cmd}" r]
+    set out [read $pipe]
+    set failed 0
+    if {[catch {close $pipe} stderr] != 0} { set failed 1 }
+
+    set exp_out "enter g\nenter f\n"
+    regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+    if {$out eq $exp_out} {
+        pass "${test_name}: stdout matches \"$escaped_exp_out\""
+    } else {
+        fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+    }
+
+    if {$failed} {
+        fail "${test_name}: exit code not zero"
+    } else {
+        pass "${test_name}: exit code is zero"
+    }
+    if {$stderr ne ""} {
+        send_log "stderr:\n$stderr"
+    }
+}
+
+# --- TEST 3 ---
+
+set subtest3 "TEST 3: quit() in the middle of a probe handler body"
+foreach runtime [get_runtime_list] {
+    if {$runtime eq ""} { set runtime "kernel" }
+    set test_name "$test: $subtest3 ($runtime)"
+
+    set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_3.stp'"
+    send_log "executing: $cmd\n"
+    set pipe [open "| sh -c {$cmd}" r]
+    set out [read $pipe]
+    set failed 0
+    if {[catch {close $pipe} stderr] != 0} { set failed 1 }
+
+    set exp_out "enter probe\n"
+    regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+    if {$out eq $exp_out} {
+        pass "${test_name}: stdout matches \"$escaped_exp_out\""
+    } else {
+        fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+    }
+
+    if {$failed} {
+        fail "${test_name}: exit code not zero"
+    } else {
+        pass "${test_name}: exit code is zero"
+    }
+    if {$stderr ne ""} {
+        send_log "stderr:\n$stderr"
+    }
+}
diff --git a/testsuite/systemtap.base/quit_1.stp b/testsuite/systemtap.base/quit_1.stp
new file mode 100644
index 000000000..a60b3df1c
--- /dev/null
+++ b/testsuite/systemtap.base/quit_1.stp
@@ -0,0 +1,11 @@
+function f() {
+    println("enter f");
+    quit();
+    println("leave f");
+}
+
+probe begin {
+    f();
+    printf("quit\n");
+    quit();
+}
diff --git a/testsuite/systemtap.base/quit_2.stp b/testsuite/systemtap.base/quit_2.stp
new file mode 100644
index 000000000..2bc739f58
--- /dev/null
+++ b/testsuite/systemtap.base/quit_2.stp
@@ -0,0 +1,17 @@
+function f() {
+    println("enter f");
+    quit();
+    println("leave f");
+}
+
+function g() {
+    println("enter g");
+    f();
+    println("leave g");
+}
+
+probe begin {
+    g();
+    printf("quit\n");
+    quit();
+}
diff --git a/testsuite/systemtap.base/quit_3.stp b/testsuite/systemtap.base/quit_3.stp
new file mode 100644
index 000000000..a3b796f41
--- /dev/null
+++ b/testsuite/systemtap.base/quit_3.stp
@@ -0,0 +1,5 @@
+probe begin {
+    println("enter probe");
+    quit();
+    println("leave probe");
+}
diff --git a/translate.cxx b/translate.cxx
index c7e7aa53f..24e3167b7 100644
--- a/translate.cxx
+++ b/translate.cxx
@@ -5732,7 +5732,7 @@ c_unparser::visit_functioncall (functioncall* e)
 
       // call function
       o->newline() << c_funcname (r->name) << " (c);";
-      o->newline() << "if (unlikely(c->last_error)) goto out;";
+      o->newline() << "if (unlikely(c->last_error || _stp_quitting)) goto out;";
 
       if (!already_checked_action_count && !session->suppress_time_limits
           && !session->unoptimized)
@@ -6069,7 +6069,7 @@ c_unparser::visit_print_format (print_format* e)
 	}
       o->line() << ");";
       o->newline(-1) << "#endif // STP_LEGACY_PRINT";
-      o->newline() << "if (unlikely(c->last_error)) goto out;";
+      o->newline() << "if (unlikely(c->last_error || _stp_quitting)) goto out;";
       o->newline() << res.value() << ";";
     }
 }
-- 
2.11.0.295.gd7dffce

^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH v2] Add new built-in tapset function quit()
  2018-08-24  1:51 [PATCH] Add new built-in tapset function quit() Yichun Zhang (agentzh)
@ 2018-08-28  2:43 ` Yichun Zhang (agentzh)
  2018-08-28 19:54   ` [PATCH v3] Add new built-in tapset function abort() Yichun Zhang (agentzh)
  0 siblings, 1 reply; 5+ messages in thread
From: Yichun Zhang (agentzh) @ 2018-08-28  2:43 UTC (permalink / raw)
  To: systemtap; +Cc: Yichun Zhang (agentzh)

The new built-in tapset function quit() is similar to exit(), but it
aborts the currently executing functions and probe handlers immediately.
Works in both the kernel and dyninst runtimes. The bpf runtime is not
yet supported.

fche thinks it is already too late to change the current behavior of
exit(), hence this new function.

fche suggests the global variable name _stp_abort_flag is good since
it looks alarming and is consistent with the existing global variable
_stp_exit_flag.

Also added corresponding tests for both quit() and exit().

This new function can be disabled by the '--compatible 3.3' option. Also
added tests for this.

Updated NEWS for this new feature.
---
 NEWS                                |   6 +
 runtime/dyninst/io.c                |   9 ++
 runtime/linux/io.c                  |  13 +++
 tapset/logging.stp                  |  23 ++++
 testsuite/systemtap.base/exit.exp   | 112 +++++++++++++++++++
 testsuite/systemtap.base/exit_1.stp |  11 ++
 testsuite/systemtap.base/exit_2.stp |  17 +++
 testsuite/systemtap.base/exit_3.stp |   5 +
 testsuite/systemtap.base/quit.exp   | 214 ++++++++++++++++++++++++++++++++++++
 testsuite/systemtap.base/quit_1.stp |  11 ++
 testsuite/systemtap.base/quit_2.stp |  17 +++
 testsuite/systemtap.base/quit_3.stp |   5 +
 translate.cxx                       |   4 +-
 13 files changed, 445 insertions(+), 2 deletions(-)
 create mode 100644 testsuite/systemtap.base/exit.exp
 create mode 100644 testsuite/systemtap.base/exit_1.stp
 create mode 100644 testsuite/systemtap.base/exit_2.stp
 create mode 100644 testsuite/systemtap.base/exit_3.stp
 create mode 100644 testsuite/systemtap.base/quit.exp
 create mode 100644 testsuite/systemtap.base/quit_1.stp
 create mode 100644 testsuite/systemtap.base/quit_2.stp
 create mode 100644 testsuite/systemtap.base/quit_3.stp

diff --git a/NEWS b/NEWS
index 7ed02a99f..f1ba50921 100644
--- a/NEWS
+++ b/NEWS
@@ -64,6 +64,12 @@
   (The syscall tapsets are broken on kernel 4.17-rc, and will be fixed
   in a next release coming soon; PR23160.)
 
+- Add new built-in tapset function quit() which is similar to exit(),
+  but it aborts the currently executing functions and probe handlers
+  immediately instead of waiting. Works in both the kernel and dyninst
+  runtimes. This function can be disabled by the '--compatible 3.3'
+  option.
+
 * What's new in version 3.2, 2017-10-18
 
 - SystemTap now includes an extended Berkeley Packet Filter (eBPF)
diff --git a/runtime/dyninst/io.c b/runtime/dyninst/io.c
index b8e9568a5..6070597ac 100644
--- a/runtime/dyninst/io.c
+++ b/runtime/dyninst/io.c
@@ -19,6 +19,9 @@
 #define WARN_STRING "WARNING: "
 #define ERR_STRING "ERROR: "
 
+// a global flag to signal immediate probe/function execution abort from quit().
+static int _stp_abort_flag = 0;
+
 enum code { INFO=0, WARN, ERROR, DBUG };
 
 static void _stp_vlog (enum code type, const char *func, int line,
@@ -165,4 +168,10 @@ static void _stp_exit (void)
 	_stp_dyninst_transport_request_exit();
 }
 
+static void _stp_quit (void)
+{
+	_stp_abort_flag = 1;
+	_stp_exit ();
+}
+
 #endif /* _STAPDYN_IO_C_ */
diff --git a/runtime/linux/io.c b/runtime/linux/io.c
index 74a032c52..22245a666 100644
--- a/runtime/linux/io.c
+++ b/runtime/linux/io.c
@@ -24,6 +24,9 @@
 #error "STP_LOG_BUF_LEN is too short"
 #endif
 
+// a global flag to signal immediate probe/function execution abort from quit().
+static int _stp_abort_flag = 0;
+
 enum code { INFO=0, WARN, ERROR, DBUG };
 
 static void _stp_vlog (enum code type, const char *func, int line, const char *fmt, va_list args)
@@ -105,6 +108,16 @@ static void _stp_exit (void)
 	_stp_exit_flag = 1;
 }
 
+/** Similar to _stp_exit(), but also sets the flag
+ * _stp_abort_flag.
+ */
+static void _stp_quit (void)
+{
+	_stp_abort_flag = 1;
+	_stp_exit ();
+}
+
+
 /** Prints error message and exits.
  * This function sends an error message immediately to staprun. It
  * will also be sent over the bulk transport (relayfs) if it is
diff --git a/tapset/logging.stp b/tapset/logging.stp
index d09b8eb80..352f0fbe8 100644
--- a/tapset/logging.stp
+++ b/tapset/logging.stp
@@ -74,6 +74,29 @@ function exit ()
 %)
 
 
+%(systemtap_v >= "4.0" %?
+/**
+ * sfunction quit - Immediately shutting down probing script.
+ *
+ * Description: This is similar to exit() but immediately stops
+ * the currently executing functions and probes.
+ */
+function quit ()
+%( runtime != "bpf" %?
+  %{ /* unprivileged */
+     atomic_set (session_state(), STAP_SESSION_STOPPING);
+     _stp_quit ();
+  %}
+%:
+  { /* unprivileged */ /* bpf */
+    _set_exit_status()
+    printf("ERROR: quit() not supported yet\n")
+    exit()  /* TODO: need to abort the execution flow immediately */
+  }
+%)
+%)
+
+
 /**
  * sfunction error - Send an error message
  *
diff --git a/testsuite/systemtap.base/exit.exp b/testsuite/systemtap.base/exit.exp
new file mode 100644
index 000000000..9e91c30ec
--- /dev/null
+++ b/testsuite/systemtap.base/exit.exp
@@ -0,0 +1,112 @@
+set test "exit"
+set testpath "$srcdir/$subdir"
+
+if {! [installtest_p]} { untested "$test"; return }
+
+# --- TEST 1 ---
+
+set subtest1 "TEST 1: exit() in the middle of a func body"
+foreach runtime [get_runtime_list] {
+    if {$runtime eq ""} {
+        set runtime "kernel"
+    }
+    set test_name "$test: $subtest1 ($runtime)"
+
+    set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_1.stp'"
+    send_log "executing: $cmd\n"
+    set pipe [open "| sh -c {$cmd}" r]
+    set out [read $pipe]
+    set failed 0
+    if {[catch {close $pipe} stderr] != 0} {
+        set failed 1
+    }
+
+    set exp_out "enter f\nleave f\nexit\n"
+    regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+    if {$out eq $exp_out} {
+        pass "${test_name}: stdout matches \"$escaped_exp_out\""
+    } else {
+        fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+    }
+
+    if {$failed} {
+        fail "${test_name}: exit code not zero"
+    } else {
+        pass "${test_name}: exit code is zero"
+    }
+    if {$stderr ne ""} {
+        send_log "stderr:\n$stderr"
+    }
+}
+
+# --- TEST 2 ---
+
+set subtest2 "TEST 2: exit() in the middle of a func body in a deeper func call chain"
+foreach runtime [get_runtime_list] {
+    if {$runtime eq ""} {
+        set runtime "kernel"
+    }
+    set test_name "$test: $subtest2 ($runtime)"
+
+    set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_2.stp'"
+    send_log "executing: $cmd\n"
+    set pipe [open "| sh -c {$cmd}" r]
+    set out [read $pipe]
+    set failed 0
+    if {[catch {close $pipe} stderr] != 0} {
+        set failed 1
+    }
+
+    set exp_out "enter g\nenter f\nleave f\nleave g\nexit\n"
+    regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+    if {$out eq $exp_out} {
+        pass "${test_name}: stdout matches \"$escaped_exp_out\""
+    } else {
+        fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+    }
+
+    if {$failed} {
+        fail "${test_name}: exit code not zero"
+    } else {
+        pass "${test_name}: exit code is zero"
+    }
+    if {$stderr ne ""} {
+        send_log "stderr:\n$stderr"
+    }
+}
+
+# --- TEST 3 ---
+
+set subtest3 "TEST 3: exit() in the middle of a probe handler body"
+foreach runtime [get_runtime_list] {
+    if {$runtime eq ""} {
+        set runtime "kernel"
+    }
+    set test_name "$test: $subtest3 ($runtime)"
+
+    set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_3.stp'"
+    send_log "executing: $cmd\n"
+    set pipe [open "| sh -c {$cmd}" r]
+    set out [read $pipe]
+    set failed 0
+    if {[catch {close $pipe} stderr] != 0} {
+        set failed 1
+    }
+
+    set exp_out "enter probe\nleave probe\n"
+    regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+    if {$out eq $exp_out} {
+        pass "${test_name}: stdout matches \"$escaped_exp_out\""
+    } else {
+        fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+    }
+
+    if {$failed} {
+        fail "${test_name}: exit code not zero"
+    } else {
+        pass "${test_name}: exit code is zero"
+    }
+    if {$stderr ne ""} {
+        send_log "stderr:\n$stderr"
+    }
+}
diff --git a/testsuite/systemtap.base/exit_1.stp b/testsuite/systemtap.base/exit_1.stp
new file mode 100644
index 000000000..c2dd0141a
--- /dev/null
+++ b/testsuite/systemtap.base/exit_1.stp
@@ -0,0 +1,11 @@
+function f() {
+    println("enter f");
+    exit();
+    println("leave f");
+}
+
+probe begin {
+    f();
+    printf("exit\n");
+    exit();
+}
diff --git a/testsuite/systemtap.base/exit_2.stp b/testsuite/systemtap.base/exit_2.stp
new file mode 100644
index 000000000..b747ddc0c
--- /dev/null
+++ b/testsuite/systemtap.base/exit_2.stp
@@ -0,0 +1,17 @@
+function f() {
+    println("enter f");
+    exit();
+    println("leave f");
+}
+
+function g() {
+    println("enter g");
+    f();
+    println("leave g");
+}
+
+probe begin {
+    g();
+    printf("exit\n");
+    exit();
+}
diff --git a/testsuite/systemtap.base/exit_3.stp b/testsuite/systemtap.base/exit_3.stp
new file mode 100644
index 000000000..3f3b801d1
--- /dev/null
+++ b/testsuite/systemtap.base/exit_3.stp
@@ -0,0 +1,5 @@
+probe begin {
+    println("enter probe");
+    exit();
+    println("leave probe");
+}
diff --git a/testsuite/systemtap.base/quit.exp b/testsuite/systemtap.base/quit.exp
new file mode 100644
index 000000000..e02be5ad0
--- /dev/null
+++ b/testsuite/systemtap.base/quit.exp
@@ -0,0 +1,214 @@
+set test "quit"
+set testpath "$srcdir/$subdir"
+
+if {! [installtest_p]} { untested "$test"; return }
+
+# --- TEST 1 ---
+
+set subtest1 "TEST 1: quit() in the middle of a func body"
+foreach runtime [get_runtime_list] {
+    if {$runtime eq ""} {
+        set runtime "kernel"
+    }
+    set test_name "$test: $subtest1 ($runtime)"
+
+    set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_1.stp'"
+    send_log "executing: $cmd\n"
+    set pipe [open "| sh -c {$cmd}" r]
+    set out [read $pipe]
+    set failed 0
+    if {[catch {close $pipe} stderr] != 0} {
+        if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+            append stderr "\n"
+        }
+        global errorCode
+        if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+            set failed [lindex $errorCode 2]
+        }
+    }
+
+    set exp_out "enter f\n"
+    regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+    if {$out eq $exp_out} {
+        pass "${test_name}: stdout matches \"$escaped_exp_out\""
+    } else {
+        fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+    }
+
+    if {$failed} {
+        fail "${test_name}: exit code should be zero but is $failed"
+    } else {
+        pass "${test_name}: exit code is zero"
+    }
+    if {$stderr ne ""} {
+        send_log "stderr:\n$stderr"
+    }
+}
+
+# --- TEST 2 ---
+
+set subtest2 "TEST 2: quit() in the middle of a func body in a deeper func call chain"
+foreach runtime [get_runtime_list] {
+    if {$runtime eq ""} {
+        set runtime "kernel"
+    }
+    set test_name "$test: $subtest2 ($runtime)"
+
+    set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_2.stp'"
+    send_log "executing: $cmd\n"
+    set pipe [open "| sh -c {$cmd}" r]
+    set out [read $pipe]
+    set failed 0
+    if {[catch {close $pipe} stderr] != 0} {
+        if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+            append stderr "\n"
+        }
+        global errorCode
+        if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+            set failed [lindex $errorCode 2]
+        }
+    }
+
+    set exp_out "enter g\nenter f\n"
+    regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+    if {$out eq $exp_out} {
+        pass "${test_name}: stdout matches \"$escaped_exp_out\""
+    } else {
+        fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+    }
+
+    if {$failed} {
+        fail "${test_name}: exit code should be zero but is $failed"
+    } else {
+        pass "${test_name}: exit code is zero"
+    }
+    if {$stderr ne ""} {
+        send_log "stderr:\n$stderr"
+    }
+}
+
+# --- TEST 3 ---
+
+set subtest3 "TEST 3: quit() in the middle of a probe handler body"
+foreach runtime [get_runtime_list] {
+    if {$runtime eq ""} {
+        set runtime "kernel"
+    }
+    set test_name "$test: $subtest3 ($runtime)"
+
+    set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_3.stp'"
+    send_log "executing: $cmd\n"
+    set pipe [open "| sh -c {$cmd}" r]
+    set out [read $pipe]
+    set failed 0
+    if {[catch {close $pipe} stderr] != 0} {
+        if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+            append stderr "\n"
+        }
+        global errorCode
+        if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+            set failed [lindex $errorCode 2]
+        }
+    }
+
+    set exp_out "enter probe\n"
+    regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+    if {$out eq $exp_out} {
+        pass "${test_name}: stdout matches \"$escaped_exp_out\""
+    } else {
+        fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+    }
+
+    if {$failed} {
+        fail "${test_name}: exit code should be zero but is $failed"
+    } else {
+        pass "${test_name}: exit code is zero"
+    }
+    if {$stderr ne ""} {
+        send_log "stderr:\n$stderr"
+    }
+}
+
+# --- TEST 4 ---
+
+set subtest4 "TEST 4: quit() in the middle of a probe handler body (--compatible 4.0)"
+foreach runtime [get_runtime_list] {
+    if {$runtime eq ""} {
+        set runtime "kernel"
+    }
+    set test_name "$test: $subtest4 ($runtime)"
+
+    set cmd "stap --compatible 4.0 --runtime=$runtime '$srcdir/$subdir/${test}_3.stp'"
+    send_log "executing: $cmd\n"
+    set pipe [open "| sh -c {$cmd}" r]
+    set out [read $pipe]
+    set failed 0
+    if {[catch {close $pipe} stderr] != 0} {
+        if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+            append stderr "\n"
+        }
+        global errorCode
+        if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+            set failed [lindex $errorCode 2]
+        }
+    }
+
+    set exp_out "enter probe\n"
+    regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+    if {$out eq $exp_out} {
+        pass "${test_name}: stdout matches \"$escaped_exp_out\""
+    } else {
+        fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+    }
+
+    if {$failed} {
+        fail "${test_name}: exit code should be zero but is $failed"
+    } else {
+        pass "${test_name}: exit code is zero"
+    }
+    if {$stderr ne ""} {
+        send_log "stderr:\n$stderr"
+    }
+}
+
+# --- TEST 5 ---
+
+set subtest5 "TEST 5: quit() in the middle of a probe handler body (--compatible 3.3)"
+set test_name "$test: $subtest5"
+
+set cmd "stap --compatible 3.3 '$srcdir/$subdir/${test}_3.stp'"
+send_log "executing: $cmd\n"
+set pipe [open "| sh -c {$cmd}" r]
+set out [read $pipe]
+set failed 0
+if {[catch {close $pipe} stderr] != 0} {
+    if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+        append stderr "\n"
+    }
+    global errorCode
+    if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+        set failed [lindex $errorCode 2]
+    }
+}
+
+set exp_out ""
+regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+if {$out eq $exp_out} {
+    pass "${test_name}: stdout matches \"$escaped_exp_out\""
+} else {
+    fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+}
+
+if {$failed} {
+    pass "${test_name}: exit code should be non-zero"
+} else {
+    fail "${test_name}: exit code should be non-zero but is zero"
+}
+
+set stderr_pat "^semantic error: unresolved function \\(similar: \[^\\n\]*?\\): identifier 'quit' at \[^\\n\]*?\\.stp:3:5\\n"
+regsub -all -- {\n} $stderr_pat {\n} escaped_stderr_pat
+if {[regexp -linestop -lineanchor -- $stderr_pat $stderr]} {
+    pass "${test_name}: stderr matches \"$escaped_stderr_pat\""
+} else {
+    fail "${test_name}: stderr fails to match \"$escaped_stderr_pat\": got \"$stderr\""
+}
diff --git a/testsuite/systemtap.base/quit_1.stp b/testsuite/systemtap.base/quit_1.stp
new file mode 100644
index 000000000..f5903db45
--- /dev/null
+++ b/testsuite/systemtap.base/quit_1.stp
@@ -0,0 +1,11 @@
+function f() {
+    println("enter f");
+    quit();
+    println("leave f");
+}
+
+probe oneshot {
+    f();
+    printf("quit\n");
+    quit();
+}
diff --git a/testsuite/systemtap.base/quit_2.stp b/testsuite/systemtap.base/quit_2.stp
new file mode 100644
index 000000000..a4d0d7caf
--- /dev/null
+++ b/testsuite/systemtap.base/quit_2.stp
@@ -0,0 +1,17 @@
+function f() {
+    println("enter f");
+    quit();
+    println("leave f");
+}
+
+function g() {
+    println("enter g");
+    f();
+    println("leave g");
+}
+
+probe oneshot {
+    g();
+    printf("quit\n");
+    quit();
+}
diff --git a/testsuite/systemtap.base/quit_3.stp b/testsuite/systemtap.base/quit_3.stp
new file mode 100644
index 000000000..6782404ce
--- /dev/null
+++ b/testsuite/systemtap.base/quit_3.stp
@@ -0,0 +1,5 @@
+probe oneshot {
+    println("enter probe");
+    quit();
+    println("leave probe");
+}
diff --git a/translate.cxx b/translate.cxx
index c7e7aa53f..743d6cd0e 100644
--- a/translate.cxx
+++ b/translate.cxx
@@ -5732,7 +5732,7 @@ c_unparser::visit_functioncall (functioncall* e)
 
       // call function
       o->newline() << c_funcname (r->name) << " (c);";
-      o->newline() << "if (unlikely(c->last_error)) goto out;";
+      o->newline() << "if (unlikely(c->last_error || _stp_abort_flag)) goto out;";
 
       if (!already_checked_action_count && !session->suppress_time_limits
           && !session->unoptimized)
@@ -6069,7 +6069,7 @@ c_unparser::visit_print_format (print_format* e)
 	}
       o->line() << ");";
       o->newline(-1) << "#endif // STP_LEGACY_PRINT";
-      o->newline() << "if (unlikely(c->last_error)) goto out;";
+      o->newline() << "if (unlikely(c->last_error || _stp_abort_flag)) goto out;";
       o->newline() << res.value() << ";";
     }
 }
-- 
2.11.0.295.gd7dffce

^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH v3] Add new built-in tapset function abort()
  2018-08-28  2:43 ` [PATCH v2] " Yichun Zhang (agentzh)
@ 2018-08-28 19:54   ` Yichun Zhang (agentzh)
  2018-08-28 23:22     ` [PATCH v4] " Yichun Zhang (agentzh)
  0 siblings, 1 reply; 5+ messages in thread
From: Yichun Zhang (agentzh) @ 2018-08-28 19:54 UTC (permalink / raw)
  To: systemtap; +Cc: Yichun Zhang (agentzh)

The new built-in tapset function abort() is similar to exit(), but it
aborts the currently executing functions and probe handlers immediately.
It works in both the kernel and dyninst runtimes. The bpf runtime is not
yet supported.

Unlike error(), abort() cannot be caught by try {...} catch {...}.

Similar to exit(), abort() yeilds the zero process exit code.

fche thinks it is already too late to change the current behavior of
exit(), hence this new function.

fche suggests the function name abort().

fche suggests the global variable name _stp_abort_flag because it looks
alarming and is consistent with the existing global variable
_stp_exit_flag.

Also added corresponding tests for both abort() and exit(), including
tests for probe timer.profile + abort(), as suggested by fche. The tests
cover both the kernel and dyninst runtimes wherever possible.

This new function can be disabled by the '--compatible 3.3' option. Also
added tests for this.

Updated NEWS for this new feature.
---
 NEWS                                 |   8 +
 runtime/dyninst/io.c                 |   9 +
 runtime/linux/io.c                   |  13 ++
 tapset/logging.stp                   |  25 +++
 testsuite/systemtap.base/abort.exp   | 335 +++++++++++++++++++++++++++++++++++
 testsuite/systemtap.base/abort_1.stp |  11 ++
 testsuite/systemtap.base/abort_2.stp |  17 ++
 testsuite/systemtap.base/abort_3.stp |   5 +
 testsuite/systemtap.base/abort_6.stp |   9 +
 testsuite/systemtap.base/abort_7.stp |   4 +
 testsuite/systemtap.base/abort_8.stp |  14 ++
 testsuite/systemtap.base/exit.exp    | 130 ++++++++++++++
 testsuite/systemtap.base/exit_1.stp  |  11 ++
 testsuite/systemtap.base/exit_2.stp  |  17 ++
 testsuite/systemtap.base/exit_3.stp  |   5 +
 translate.cxx                        |   4 +-
 16 files changed, 615 insertions(+), 2 deletions(-)
 create mode 100644 testsuite/systemtap.base/abort.exp
 create mode 100644 testsuite/systemtap.base/abort_1.stp
 create mode 100644 testsuite/systemtap.base/abort_2.stp
 create mode 100644 testsuite/systemtap.base/abort_3.stp
 create mode 100644 testsuite/systemtap.base/abort_6.stp
 create mode 100644 testsuite/systemtap.base/abort_7.stp
 create mode 100644 testsuite/systemtap.base/abort_8.stp
 create mode 100644 testsuite/systemtap.base/exit.exp
 create mode 100644 testsuite/systemtap.base/exit_1.stp
 create mode 100644 testsuite/systemtap.base/exit_2.stp
 create mode 100644 testsuite/systemtap.base/exit_3.stp

diff --git a/NEWS b/NEWS
index ab20986bf..60e4c108e 100644
--- a/NEWS
+++ b/NEWS
@@ -69,6 +69,14 @@
   (The syscall tapsets are broken on kernel 4.17-rc, and will be fixed
   in a next release coming soon; PR23160.)
 
+- Add new built-in tapset function abort() which is similar to exit(),
+  but it aborts the currently executing functions and probe handlers
+  immediately instead of waiting. Unlike error(), abort() cannot be
+  caught by try {...} catch {...}. Similar to exit(), abort() yeilds
+  the zero process exit code. It works in both the kernel and dyninst
+  runtimes. This function can be disabled by the '--compatible 3.3'
+  option.
+
 * What's new in version 3.2, 2017-10-18
 
 - SystemTap now includes an extended Berkeley Packet Filter (eBPF)
diff --git a/runtime/dyninst/io.c b/runtime/dyninst/io.c
index b8e9568a5..39094cec2 100644
--- a/runtime/dyninst/io.c
+++ b/runtime/dyninst/io.c
@@ -19,6 +19,9 @@
 #define WARN_STRING "WARNING: "
 #define ERR_STRING "ERROR: "
 
+// a global flag to signal immediate probe/function execution abort from abort().
+static int _stp_abort_flag = 0;
+
 enum code { INFO=0, WARN, ERROR, DBUG };
 
 static void _stp_vlog (enum code type, const char *func, int line,
@@ -165,4 +168,10 @@ static void _stp_exit (void)
 	_stp_dyninst_transport_request_exit();
 }
 
+static void _stp_abort (void)
+{
+	_stp_abort_flag = 1;
+	_stp_exit ();
+}
+
 #endif /* _STAPDYN_IO_C_ */
diff --git a/runtime/linux/io.c b/runtime/linux/io.c
index 74a032c52..86a17792e 100644
--- a/runtime/linux/io.c
+++ b/runtime/linux/io.c
@@ -24,6 +24,9 @@
 #error "STP_LOG_BUF_LEN is too short"
 #endif
 
+// a global flag to signal immediate probe/function execution abort from abort().
+static int _stp_abort_flag = 0;
+
 enum code { INFO=0, WARN, ERROR, DBUG };
 
 static void _stp_vlog (enum code type, const char *func, int line, const char *fmt, va_list args)
@@ -105,6 +108,16 @@ static void _stp_exit (void)
 	_stp_exit_flag = 1;
 }
 
+/** Similar to _stp_exit(), but also sets the flag
+ * _stp_abort_flag.
+ */
+static void _stp_abort (void)
+{
+	_stp_abort_flag = 1;
+	_stp_exit ();
+}
+
+
 /** Prints error message and exits.
  * This function sends an error message immediately to staprun. It
  * will also be sent over the bulk transport (relayfs) if it is
diff --git a/tapset/logging.stp b/tapset/logging.stp
index d09b8eb80..17edac6da 100644
--- a/tapset/logging.stp
+++ b/tapset/logging.stp
@@ -74,6 +74,31 @@ function exit ()
 %)
 
 
+%(systemtap_v >= "4.0" %?
+/**
+ * sfunction abort - Immediately shutting down probing script.
+ *
+ * Description: This is similar to exit() but immediately stops
+ * the currently executing functions and probes. Unlike error(),
+ * abort() cannot be caught by 'try ... catch'. Similar to exit(),
+ * it yields the zero process exit code.
+ */
+function abort ()
+%( runtime != "bpf" %?
+  %{ /* unprivileged */
+     atomic_set (session_state(), STAP_SESSION_STOPPING);
+     _stp_abort ();
+  %}
+%:
+  { /* unprivileged */ /* bpf */
+    _set_exit_status()
+    printf("ERROR: abort() not supported yet\n")
+    exit()  /* TODO: need to abort the execution flow immediately */
+  }
+%)
+%)
+
+
 /**
  * sfunction error - Send an error message
  *
diff --git a/testsuite/systemtap.base/abort.exp b/testsuite/systemtap.base/abort.exp
new file mode 100644
index 000000000..906a77962
--- /dev/null
+++ b/testsuite/systemtap.base/abort.exp
@@ -0,0 +1,335 @@
+set test "abort"
+set testpath "$srcdir/$subdir"
+
+if {! [installtest_p]} { untested "$test"; return }
+
+# --- TEST 1 ---
+
+set subtest1 "TEST 1: abort() in the middle of a func body"
+foreach runtime [get_runtime_list] {
+    if {$runtime eq ""} {
+        set runtime "kernel"
+    }
+    set test_name "$test: $subtest1 ($runtime)"
+
+    set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_1.stp'"
+    send_log "executing: $cmd\n"
+    set pipe [open "| sh -c {$cmd}" r]
+    set out [read $pipe]
+    set failed 0
+    if {[catch {close $pipe} stderr] != 0} {
+        if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+            append stderr "\n"
+        }
+        global errorCode
+        if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+            set failed [lindex $errorCode 2]
+        }
+    }
+
+    set exp_out "enter f\n"
+    regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+    if {$out eq $exp_out} {
+        pass "${test_name}: stdout matches \"$escaped_exp_out\""
+    } else {
+        fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+    }
+
+    if {$failed} {
+        fail "${test_name}: exit code should be zero but is $failed"
+    } else {
+        pass "${test_name}: exit code is zero"
+    }
+    if {$stderr ne ""} {
+        send_log "stderr:\n$stderr"
+    }
+}
+
+# --- TEST 2 ---
+
+set subtest2 "TEST 2: abort() in the middle of a func body in a deeper func call chain"
+foreach runtime [get_runtime_list] {
+    if {$runtime eq ""} {
+        set runtime "kernel"
+    }
+    set test_name "$test: $subtest2 ($runtime)"
+
+    set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_2.stp'"
+    send_log "executing: $cmd\n"
+    set pipe [open "| sh -c {$cmd}" r]
+    set out [read $pipe]
+    set failed 0
+    if {[catch {close $pipe} stderr] != 0} {
+        if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+            append stderr "\n"
+        }
+        global errorCode
+        if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+            set failed [lindex $errorCode 2]
+        }
+    }
+
+    set exp_out "enter g\nenter f\n"
+    regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+    if {$out eq $exp_out} {
+        pass "${test_name}: stdout matches \"$escaped_exp_out\""
+    } else {
+        fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+    }
+
+    if {$failed} {
+        fail "${test_name}: exit code should be zero but is $failed"
+    } else {
+        pass "${test_name}: exit code is zero"
+    }
+    if {$stderr ne ""} {
+        send_log "stderr:\n$stderr"
+    }
+}
+
+# --- TEST 3 ---
+
+set subtest3 "TEST 3: abort() in the middle of a probe handler body"
+foreach runtime [get_runtime_list] {
+    if {$runtime eq ""} {
+        set runtime "kernel"
+    }
+    set test_name "$test: $subtest3 ($runtime)"
+
+    set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_3.stp'"
+    send_log "executing: $cmd\n"
+    set pipe [open "| sh -c {$cmd}" r]
+    set out [read $pipe]
+    set failed 0
+    if {[catch {close $pipe} stderr] != 0} {
+        if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+            append stderr "\n"
+        }
+        global errorCode
+        if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+            set failed [lindex $errorCode 2]
+        }
+    }
+
+    set exp_out "enter probe\n"
+    regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+    if {$out eq $exp_out} {
+        pass "${test_name}: stdout matches \"$escaped_exp_out\""
+    } else {
+        fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+    }
+
+    if {$failed} {
+        fail "${test_name}: exit code should be zero but is $failed"
+    } else {
+        pass "${test_name}: exit code is zero"
+    }
+    if {$stderr ne ""} {
+        send_log "stderr:\n$stderr"
+    }
+}
+
+# --- TEST 4 ---
+
+set subtest4 "TEST 4: abort() in the middle of a probe handler body (--compatible 4.0)"
+foreach runtime [get_runtime_list] {
+    if {$runtime eq ""} {
+        set runtime "kernel"
+    }
+    set test_name "$test: $subtest4 ($runtime)"
+
+    set cmd "stap --compatible 4.0 --runtime=$runtime '$srcdir/$subdir/${test}_3.stp'"
+    send_log "executing: $cmd\n"
+    set pipe [open "| sh -c {$cmd}" r]
+    set out [read $pipe]
+    set failed 0
+    if {[catch {close $pipe} stderr] != 0} {
+        if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+            append stderr "\n"
+        }
+        global errorCode
+        if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+            set failed [lindex $errorCode 2]
+        }
+    }
+
+    set exp_out "enter probe\n"
+    regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+    if {$out eq $exp_out} {
+        pass "${test_name}: stdout matches \"$escaped_exp_out\""
+    } else {
+        fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+    }
+
+    if {$failed} {
+        fail "${test_name}: exit code should be zero but is $failed"
+    } else {
+        pass "${test_name}: exit code is zero"
+    }
+    if {$stderr ne ""} {
+        send_log "stderr:\n$stderr"
+    }
+}
+
+# --- TEST 5 ---
+
+set subtest5 "TEST 5: abort() in the middle of a probe handler body (--compatible 3.3)"
+set test_name "$test: $subtest5"
+
+set cmd "stap --compatible 3.3 '$srcdir/$subdir/${test}_3.stp'"
+send_log "executing: $cmd\n"
+set pipe [open "| sh -c {$cmd}" r]
+set out [read $pipe]
+set failed 0
+if {[catch {close $pipe} stderr] != 0} {
+    if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+        append stderr "\n"
+    }
+    global errorCode
+    if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+        set failed [lindex $errorCode 2]
+    }
+}
+
+set exp_out ""
+regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+if {$out eq $exp_out} {
+    pass "${test_name}: stdout matches \"$escaped_exp_out\""
+} else {
+    fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+}
+
+if {$failed} {
+    pass "${test_name}: exit code should be non-zero"
+} else {
+    fail "${test_name}: exit code should be non-zero but is zero"
+}
+
+set stderr_pat "^semantic error: unresolved function \\(similar: \[^\\n\]*?\\): identifier 'abort' at \[^\\n\]*?\\.stp:3:5\\n"
+regsub -all -- {\n} $stderr_pat {\n} escaped_stderr_pat
+if {[regexp -linestop -lineanchor -- $stderr_pat $stderr]} {
+    pass "${test_name}: stderr matches \"$escaped_stderr_pat\""
+} else {
+    fail "${test_name}: stderr fails to match \"$escaped_stderr_pat\": got \"$stderr\""
+}
+
+# --- TEST 6 ---
+
+set subtest6 "TEST 6: abort() in timer.profile (using globals)"
+set test_name "$test: $subtest6"
+
+set cmd "stap '$srcdir/$subdir/${test}_6.stp'"
+send_log "executing: $cmd\n"
+set pipe [open "| sh -c {$cmd}" r]
+set out [read $pipe]
+set failed 0
+if {[catch {close $pipe} stderr] != 0} {
+    if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+        append stderr "\n"
+    }
+    global errorCode
+    if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+        set failed [lindex $errorCode 2]
+    }
+}
+
+set exp_out "fire 3!\nfire 2!\nfire 1!\n"
+regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+if {$out eq $exp_out} {
+    pass "${test_name}: stdout matches \"$escaped_exp_out\""
+} else {
+    fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+}
+
+if {$failed} {
+    fail "${test_name}: exit code should be zero but is $failed"
+} else {
+    pass "${test_name}: exit code is zero"
+}
+if {$stderr ne ""} {
+    send_log "stderr:\n$stderr"
+}
+
+# --- TEST 7 ---
+
+set subtest7 "TEST 7: abort() in timer.profile (more concurrency and no globals)"
+set test_name "$test: $subtest7"
+
+set cmd "stap '$srcdir/$subdir/${test}_7.stp'"
+send_log "executing: $cmd\n"
+set pipe [open "| sh -c {$cmd}" r]
+set out [read $pipe]
+set failed 0
+if {[catch {close $pipe} stderr] != 0} {
+    if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+        append stderr "\n"
+    }
+    global errorCode
+    if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+        set failed [lindex $errorCode 2]
+    }
+}
+
+set exp_out ""
+regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+if {$out eq $exp_out} {
+    pass "${test_name}: stdout matches \"$escaped_exp_out\""
+} else {
+    fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+}
+
+set exp_stderr ""
+regsub -all -- {\n} $exp_stderr {\n} escaped_exp_stderr
+if {$stderr eq $exp_stderr} {
+    pass "${test_name}: stderr matches \"$escaped_exp_stderr\""
+} else {
+    fail "${test_name}: stderr fails to match \"$escaped_exp_stderr\": got \"$stderr\""
+}
+
+if {$failed} {
+    fail "${test_name}: exit code should be zero but is $failed"
+} else {
+    pass "${test_name}: exit code is zero"
+}
+
+# --- TEST 8 ---
+
+set subtest8 "TEST 8: abort() in the middle of a func body - abort() cannot be caught"
+foreach runtime [get_runtime_list] {
+    if {$runtime eq ""} {
+        set runtime "kernel"
+    }
+    set test_name "$test: $subtest8 ($runtime)"
+
+    set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_8.stp'"
+    send_log "executing: $cmd\n"
+    set pipe [open "| sh -c {$cmd}" r]
+    set out [read $pipe]
+    set failed 0
+    if {[catch {close $pipe} stderr] != 0} {
+        if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+            append stderr "\n"
+        }
+        global errorCode
+        if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+            set failed [lindex $errorCode 2]
+        }
+    }
+
+    set exp_out "enter f\n"
+    regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+    if {$out eq $exp_out} {
+        pass "${test_name}: stdout matches \"$escaped_exp_out\""
+    } else {
+        fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+    }
+
+    if {$failed} {
+        fail "${test_name}: exit code should be zero but is $failed"
+    } else {
+        pass "${test_name}: exit code is zero"
+    }
+    if {$stderr ne ""} {
+        send_log "stderr:\n$stderr"
+    }
+}
diff --git a/testsuite/systemtap.base/abort_1.stp b/testsuite/systemtap.base/abort_1.stp
new file mode 100644
index 000000000..633a49616
--- /dev/null
+++ b/testsuite/systemtap.base/abort_1.stp
@@ -0,0 +1,11 @@
+function f() {
+    println("enter f");
+    abort();
+    println("leave f");
+}
+
+probe oneshot {
+    f();
+    printf("abort\n");
+    abort();
+}
diff --git a/testsuite/systemtap.base/abort_2.stp b/testsuite/systemtap.base/abort_2.stp
new file mode 100644
index 000000000..b8f864d06
--- /dev/null
+++ b/testsuite/systemtap.base/abort_2.stp
@@ -0,0 +1,17 @@
+function f() {
+    println("enter f");
+    abort();
+    println("leave f");
+}
+
+function g() {
+    println("enter g");
+    f();
+    println("leave g");
+}
+
+probe oneshot {
+    g();
+    printf("abort\n");
+    abort();
+}
diff --git a/testsuite/systemtap.base/abort_3.stp b/testsuite/systemtap.base/abort_3.stp
new file mode 100644
index 000000000..48182f1a3
--- /dev/null
+++ b/testsuite/systemtap.base/abort_3.stp
@@ -0,0 +1,5 @@
+probe oneshot {
+    println("enter probe");
+    abort();
+    println("leave probe");
+}
diff --git a/testsuite/systemtap.base/abort_6.stp b/testsuite/systemtap.base/abort_6.stp
new file mode 100644
index 000000000..2b42ec3f7
--- /dev/null
+++ b/testsuite/systemtap.base/abort_6.stp
@@ -0,0 +1,9 @@
+global rest = 3
+
+probe timer.profile {
+    id = rest--;
+    if (id <= 0) {
+        abort();
+    }
+    printf("fire %d!\n", id);
+}
diff --git a/testsuite/systemtap.base/abort_7.stp b/testsuite/systemtap.base/abort_7.stp
new file mode 100644
index 000000000..972ba365d
--- /dev/null
+++ b/testsuite/systemtap.base/abort_7.stp
@@ -0,0 +1,4 @@
+probe timer.profile {
+    abort();
+    error("fire after abort!\n")
+}
diff --git a/testsuite/systemtap.base/abort_8.stp b/testsuite/systemtap.base/abort_8.stp
new file mode 100644
index 000000000..304e788e6
--- /dev/null
+++ b/testsuite/systemtap.base/abort_8.stp
@@ -0,0 +1,14 @@
+function f() {
+    println("enter f");
+    try {
+        abort();
+    } catch {
+        println("caught abort!")
+    }
+    println("leave f");
+}
+
+probe oneshot {
+    f();
+    printf("abort\n");
+}
diff --git a/testsuite/systemtap.base/exit.exp b/testsuite/systemtap.base/exit.exp
new file mode 100644
index 000000000..9296590e2
--- /dev/null
+++ b/testsuite/systemtap.base/exit.exp
@@ -0,0 +1,130 @@
+set test "exit"
+set testpath "$srcdir/$subdir"
+
+if {! [installtest_p]} { untested "$test"; return }
+
+# --- TEST 1 ---
+
+set subtest1 "TEST 1: exit() in the middle of a func body"
+foreach runtime [get_runtime_list] {
+    if {$runtime eq ""} {
+        set runtime "kernel"
+    }
+    set test_name "$test: $subtest1 ($runtime)"
+
+    set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_1.stp'"
+    send_log "executing: $cmd\n"
+    set pipe [open "| sh -c {$cmd}" r]
+    set out [read $pipe]
+    set failed 0
+    if {[catch {close $pipe} stderr] != 0} {
+        if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+            append stderr "\n"
+        }
+        global errorCode
+        if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+            set failed [lindex $errorCode 2]
+        }
+    }
+
+    set exp_out "enter f\nleave f\nexit\n"
+    regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+    if {$out eq $exp_out} {
+        pass "${test_name}: stdout matches \"$escaped_exp_out\""
+    } else {
+        fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+    }
+
+    if {$failed} {
+        fail "${test_name}: exit code should be zero but is $failed"
+    } else {
+        pass "${test_name}: exit code is zero"
+    }
+    if {$stderr ne ""} {
+        send_log "stderr:\n$stderr"
+    }
+}
+
+# --- TEST 2 ---
+
+set subtest2 "TEST 2: exit() in the middle of a func body in a deeper func call chain"
+foreach runtime [get_runtime_list] {
+    if {$runtime eq ""} {
+        set runtime "kernel"
+    }
+    set test_name "$test: $subtest2 ($runtime)"
+
+    set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_2.stp'"
+    send_log "executing: $cmd\n"
+    set pipe [open "| sh -c {$cmd}" r]
+    set out [read $pipe]
+    set failed 0
+    if {[catch {close $pipe} stderr] != 0} {
+        if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+            append stderr "\n"
+        }
+        global errorCode
+        if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+            set failed [lindex $errorCode 2]
+        }
+    }
+
+    set exp_out "enter g\nenter f\nleave f\nleave g\nexit\n"
+    regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+    if {$out eq $exp_out} {
+        pass "${test_name}: stdout matches \"$escaped_exp_out\""
+    } else {
+        fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+    }
+
+    if {$failed} {
+        fail "${test_name}: exit code should be zero but is $failed"
+    } else {
+        pass "${test_name}: exit code is zero"
+    }
+    if {$stderr ne ""} {
+        send_log "stderr:\n$stderr"
+    }
+}
+
+# --- TEST 3 ---
+
+set subtest3 "TEST 3: exit() in the middle of a probe handler body"
+foreach runtime [get_runtime_list] {
+    if {$runtime eq ""} {
+        set runtime "kernel"
+    }
+    set test_name "$test: $subtest3 ($runtime)"
+
+    set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_3.stp'"
+    send_log "executing: $cmd\n"
+    set pipe [open "| sh -c {$cmd}" r]
+    set out [read $pipe]
+    set failed 0
+    if {[catch {close $pipe} stderr] != 0} {
+        if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+            append stderr "\n"
+        }
+        global errorCode
+        if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+            set failed [lindex $errorCode 2]
+        }
+    }
+
+    set exp_out "enter probe\nleave probe\n"
+    regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+    if {$out eq $exp_out} {
+        pass "${test_name}: stdout matches \"$escaped_exp_out\""
+    } else {
+        fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+    }
+
+    if {$failed} {
+        fail "${test_name}: exit code should be zero but is $failed"
+    } else {
+        pass "${test_name}: exit code is zero"
+    }
+    if {$stderr ne ""} {
+        send_log "stderr:\n$stderr"
+    }
+}
diff --git a/testsuite/systemtap.base/exit_1.stp b/testsuite/systemtap.base/exit_1.stp
new file mode 100644
index 000000000..c2dd0141a
--- /dev/null
+++ b/testsuite/systemtap.base/exit_1.stp
@@ -0,0 +1,11 @@
+function f() {
+    println("enter f");
+    exit();
+    println("leave f");
+}
+
+probe begin {
+    f();
+    printf("exit\n");
+    exit();
+}
diff --git a/testsuite/systemtap.base/exit_2.stp b/testsuite/systemtap.base/exit_2.stp
new file mode 100644
index 000000000..b747ddc0c
--- /dev/null
+++ b/testsuite/systemtap.base/exit_2.stp
@@ -0,0 +1,17 @@
+function f() {
+    println("enter f");
+    exit();
+    println("leave f");
+}
+
+function g() {
+    println("enter g");
+    f();
+    println("leave g");
+}
+
+probe begin {
+    g();
+    printf("exit\n");
+    exit();
+}
diff --git a/testsuite/systemtap.base/exit_3.stp b/testsuite/systemtap.base/exit_3.stp
new file mode 100644
index 000000000..3f3b801d1
--- /dev/null
+++ b/testsuite/systemtap.base/exit_3.stp
@@ -0,0 +1,5 @@
+probe begin {
+    println("enter probe");
+    exit();
+    println("leave probe");
+}
diff --git a/translate.cxx b/translate.cxx
index c7e7aa53f..743d6cd0e 100644
--- a/translate.cxx
+++ b/translate.cxx
@@ -5732,7 +5732,7 @@ c_unparser::visit_functioncall (functioncall* e)
 
       // call function
       o->newline() << c_funcname (r->name) << " (c);";
-      o->newline() << "if (unlikely(c->last_error)) goto out;";
+      o->newline() << "if (unlikely(c->last_error || _stp_abort_flag)) goto out;";
 
       if (!already_checked_action_count && !session->suppress_time_limits
           && !session->unoptimized)
@@ -6069,7 +6069,7 @@ c_unparser::visit_print_format (print_format* e)
 	}
       o->line() << ");";
       o->newline(-1) << "#endif // STP_LEGACY_PRINT";
-      o->newline() << "if (unlikely(c->last_error)) goto out;";
+      o->newline() << "if (unlikely(c->last_error || _stp_abort_flag)) goto out;";
       o->newline() << res.value() << ";";
     }
 }
-- 
2.11.0.295.gd7dffce

^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH v4] Add new built-in tapset function abort()
  2018-08-28 19:54   ` [PATCH v3] Add new built-in tapset function abort() Yichun Zhang (agentzh)
@ 2018-08-28 23:22     ` Yichun Zhang (agentzh)
  2018-08-31 21:40       ` Yichun Zhang
  0 siblings, 1 reply; 5+ messages in thread
From: Yichun Zhang (agentzh) @ 2018-08-28 23:22 UTC (permalink / raw)
  To: systemtap; +Cc: Yichun Zhang (agentzh)

The new built-in tapset function abort() is similar to exit(), but it
aborts the current probe handler (and any function calls in it) immediately.
It works with both the kernel and dyninst runtimes. The bpf runtime is not
yet supported.

Unlike error(), abort() cannot be caught by try {...} catch {...}.

Similar to exit(), abort() yeilds the zero process exit code.

fche thinks it is already too late to change the current behavior of
exit(), hence this new function. And he suggests the function name abort().

Also added corresponding tests for both abort() and exit(), including
tests for probe timer.profile + abort(), as suggested by fche. The tests
cover both the kernel and dyninst runtimes wherever possible.

This new function can be disabled by the '--compatible 3.3' option. Also
added tests for this.

Updated NEWS for this new feature.
---
 NEWS                                 |   9 +
 runtime/common_probe_context.h       |   6 +
 tapset/logging.stp                   |  28 +++
 tapsets.cxx                          |   1 +
 testsuite/systemtap.base/abort.exp   | 335 +++++++++++++++++++++++++++++++++++
 testsuite/systemtap.base/abort_1.stp |  11 ++
 testsuite/systemtap.base/abort_2.stp |  17 ++
 testsuite/systemtap.base/abort_3.stp |   5 +
 testsuite/systemtap.base/abort_6.stp |   9 +
 testsuite/systemtap.base/abort_7.stp |   4 +
 testsuite/systemtap.base/abort_8.stp |  14 ++
 testsuite/systemtap.base/exit.exp    | 130 ++++++++++++++
 testsuite/systemtap.base/exit_1.stp  |  11 ++
 testsuite/systemtap.base/exit_2.stp  |  17 ++
 testsuite/systemtap.base/exit_3.stp  |   5 +
 translate.cxx                        |   4 +-
 16 files changed, 604 insertions(+), 2 deletions(-)
 create mode 100644 testsuite/systemtap.base/abort.exp
 create mode 100644 testsuite/systemtap.base/abort_1.stp
 create mode 100644 testsuite/systemtap.base/abort_2.stp
 create mode 100644 testsuite/systemtap.base/abort_3.stp
 create mode 100644 testsuite/systemtap.base/abort_6.stp
 create mode 100644 testsuite/systemtap.base/abort_7.stp
 create mode 100644 testsuite/systemtap.base/abort_8.stp
 create mode 100644 testsuite/systemtap.base/exit.exp
 create mode 100644 testsuite/systemtap.base/exit_1.stp
 create mode 100644 testsuite/systemtap.base/exit_2.stp
 create mode 100644 testsuite/systemtap.base/exit_3.stp

diff --git a/NEWS b/NEWS
index ab20986bf..273b791fc 100644
--- a/NEWS
+++ b/NEWS
@@ -69,6 +69,15 @@
   (The syscall tapsets are broken on kernel 4.17-rc, and will be fixed
   in a next release coming soon; PR23160.)
 
+- Add new built-in tapset function abort() which is similar to exit(),
+  but it aborts the current probe handler (and any function calls in
+  it) immediately instead of waiting for its completion. Probe handlers
+  already running on *other* CPU cores, however, will still continue
+  to their completion. Unlike error(), abort() cannot be caught by
+  try {...} catch {...}. Similar to exit(), abort() yeilds the zero
+  process exit code. It works with both the kernel and dyninst runtimes.
+  This function can be disabled by the '--compatible 3.3' option.
+
 * What's new in version 3.2, 2017-10-18
 
 - SystemTap now includes an extended Berkeley Packet Filter (eBPF)
diff --git a/runtime/common_probe_context.h b/runtime/common_probe_context.h
index 5ab917781..7af0b78ef 100644
--- a/runtime/common_probe_context.h
+++ b/runtime/common_probe_context.h
@@ -70,6 +70,12 @@ int nesting;
  * if no more alternatives are to be executed. */
 int next;
 
+/* A flat to signal aborting the currently running probe
+   handler.
+   While it's 0, execution continues
+   When it's 1, probe code unwnds. */
+int aborted;
+
 /* A place to format error messages into if some error occurs, last_error
    will then be pointed here.  */
 string_t error_buffer;
diff --git a/tapset/logging.stp b/tapset/logging.stp
index d09b8eb80..6b824c1b6 100644
--- a/tapset/logging.stp
+++ b/tapset/logging.stp
@@ -74,6 +74,34 @@ function exit ()
 %)
 
 
+%(systemtap_v >= "4.0" %?
+/**
+ * sfunction abort - Immediately shutting down probing script.
+ *
+ * Description: This is similar to exit() but immediately aborts
+ * the current probe handler instead of waiting for its
+ * completion. Probe handlers already running on *other* CPU cores,
+ * however, will still continue to their completion. Unlike error(),
+ * this function call cannot be caught by 'try ... catch'.
+ */
+function abort ()
+%( runtime != "bpf" %?
+  %{ /* unprivileged */
+     atomic_set (session_state(), STAP_SESSION_STOPPING);
+     _stp_exit ();
+     CONTEXT->aborted = 1;
+     CONTEXT->last_stmt = NULL;
+  %}
+%:
+  { /* unprivileged */ /* bpf */
+    _set_exit_status()
+    printf("ERROR: abort() not supported yet\n")
+    exit()  /* TODO: need to abort the execution flow immediately */
+  }
+%)
+%)
+
+
 /**
  * sfunction error - Send an error message
  *
diff --git a/tapsets.cxx b/tapsets.cxx
index ae6cfd83f..509e45748 100644
--- a/tapsets.cxx
+++ b/tapsets.cxx
@@ -181,6 +181,7 @@ common_probe_entryfn_prologue (systemtap_session& s,
   s.op->newline(-1) << "}";
 
   s.op->newline();
+  s.op->newline() << "c->aborted = 0;";
   s.op->newline() << "c->last_stmt = 0;";
   s.op->newline() << "c->last_error = 0;";
   s.op->newline() << "c->nesting = -1;"; // NB: PR10516 packs locals[] tighter
diff --git a/testsuite/systemtap.base/abort.exp b/testsuite/systemtap.base/abort.exp
new file mode 100644
index 000000000..906a77962
--- /dev/null
+++ b/testsuite/systemtap.base/abort.exp
@@ -0,0 +1,335 @@
+set test "abort"
+set testpath "$srcdir/$subdir"
+
+if {! [installtest_p]} { untested "$test"; return }
+
+# --- TEST 1 ---
+
+set subtest1 "TEST 1: abort() in the middle of a func body"
+foreach runtime [get_runtime_list] {
+    if {$runtime eq ""} {
+        set runtime "kernel"
+    }
+    set test_name "$test: $subtest1 ($runtime)"
+
+    set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_1.stp'"
+    send_log "executing: $cmd\n"
+    set pipe [open "| sh -c {$cmd}" r]
+    set out [read $pipe]
+    set failed 0
+    if {[catch {close $pipe} stderr] != 0} {
+        if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+            append stderr "\n"
+        }
+        global errorCode
+        if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+            set failed [lindex $errorCode 2]
+        }
+    }
+
+    set exp_out "enter f\n"
+    regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+    if {$out eq $exp_out} {
+        pass "${test_name}: stdout matches \"$escaped_exp_out\""
+    } else {
+        fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+    }
+
+    if {$failed} {
+        fail "${test_name}: exit code should be zero but is $failed"
+    } else {
+        pass "${test_name}: exit code is zero"
+    }
+    if {$stderr ne ""} {
+        send_log "stderr:\n$stderr"
+    }
+}
+
+# --- TEST 2 ---
+
+set subtest2 "TEST 2: abort() in the middle of a func body in a deeper func call chain"
+foreach runtime [get_runtime_list] {
+    if {$runtime eq ""} {
+        set runtime "kernel"
+    }
+    set test_name "$test: $subtest2 ($runtime)"
+
+    set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_2.stp'"
+    send_log "executing: $cmd\n"
+    set pipe [open "| sh -c {$cmd}" r]
+    set out [read $pipe]
+    set failed 0
+    if {[catch {close $pipe} stderr] != 0} {
+        if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+            append stderr "\n"
+        }
+        global errorCode
+        if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+            set failed [lindex $errorCode 2]
+        }
+    }
+
+    set exp_out "enter g\nenter f\n"
+    regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+    if {$out eq $exp_out} {
+        pass "${test_name}: stdout matches \"$escaped_exp_out\""
+    } else {
+        fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+    }
+
+    if {$failed} {
+        fail "${test_name}: exit code should be zero but is $failed"
+    } else {
+        pass "${test_name}: exit code is zero"
+    }
+    if {$stderr ne ""} {
+        send_log "stderr:\n$stderr"
+    }
+}
+
+# --- TEST 3 ---
+
+set subtest3 "TEST 3: abort() in the middle of a probe handler body"
+foreach runtime [get_runtime_list] {
+    if {$runtime eq ""} {
+        set runtime "kernel"
+    }
+    set test_name "$test: $subtest3 ($runtime)"
+
+    set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_3.stp'"
+    send_log "executing: $cmd\n"
+    set pipe [open "| sh -c {$cmd}" r]
+    set out [read $pipe]
+    set failed 0
+    if {[catch {close $pipe} stderr] != 0} {
+        if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+            append stderr "\n"
+        }
+        global errorCode
+        if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+            set failed [lindex $errorCode 2]
+        }
+    }
+
+    set exp_out "enter probe\n"
+    regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+    if {$out eq $exp_out} {
+        pass "${test_name}: stdout matches \"$escaped_exp_out\""
+    } else {
+        fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+    }
+
+    if {$failed} {
+        fail "${test_name}: exit code should be zero but is $failed"
+    } else {
+        pass "${test_name}: exit code is zero"
+    }
+    if {$stderr ne ""} {
+        send_log "stderr:\n$stderr"
+    }
+}
+
+# --- TEST 4 ---
+
+set subtest4 "TEST 4: abort() in the middle of a probe handler body (--compatible 4.0)"
+foreach runtime [get_runtime_list] {
+    if {$runtime eq ""} {
+        set runtime "kernel"
+    }
+    set test_name "$test: $subtest4 ($runtime)"
+
+    set cmd "stap --compatible 4.0 --runtime=$runtime '$srcdir/$subdir/${test}_3.stp'"
+    send_log "executing: $cmd\n"
+    set pipe [open "| sh -c {$cmd}" r]
+    set out [read $pipe]
+    set failed 0
+    if {[catch {close $pipe} stderr] != 0} {
+        if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+            append stderr "\n"
+        }
+        global errorCode
+        if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+            set failed [lindex $errorCode 2]
+        }
+    }
+
+    set exp_out "enter probe\n"
+    regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+    if {$out eq $exp_out} {
+        pass "${test_name}: stdout matches \"$escaped_exp_out\""
+    } else {
+        fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+    }
+
+    if {$failed} {
+        fail "${test_name}: exit code should be zero but is $failed"
+    } else {
+        pass "${test_name}: exit code is zero"
+    }
+    if {$stderr ne ""} {
+        send_log "stderr:\n$stderr"
+    }
+}
+
+# --- TEST 5 ---
+
+set subtest5 "TEST 5: abort() in the middle of a probe handler body (--compatible 3.3)"
+set test_name "$test: $subtest5"
+
+set cmd "stap --compatible 3.3 '$srcdir/$subdir/${test}_3.stp'"
+send_log "executing: $cmd\n"
+set pipe [open "| sh -c {$cmd}" r]
+set out [read $pipe]
+set failed 0
+if {[catch {close $pipe} stderr] != 0} {
+    if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+        append stderr "\n"
+    }
+    global errorCode
+    if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+        set failed [lindex $errorCode 2]
+    }
+}
+
+set exp_out ""
+regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+if {$out eq $exp_out} {
+    pass "${test_name}: stdout matches \"$escaped_exp_out\""
+} else {
+    fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+}
+
+if {$failed} {
+    pass "${test_name}: exit code should be non-zero"
+} else {
+    fail "${test_name}: exit code should be non-zero but is zero"
+}
+
+set stderr_pat "^semantic error: unresolved function \\(similar: \[^\\n\]*?\\): identifier 'abort' at \[^\\n\]*?\\.stp:3:5\\n"
+regsub -all -- {\n} $stderr_pat {\n} escaped_stderr_pat
+if {[regexp -linestop -lineanchor -- $stderr_pat $stderr]} {
+    pass "${test_name}: stderr matches \"$escaped_stderr_pat\""
+} else {
+    fail "${test_name}: stderr fails to match \"$escaped_stderr_pat\": got \"$stderr\""
+}
+
+# --- TEST 6 ---
+
+set subtest6 "TEST 6: abort() in timer.profile (using globals)"
+set test_name "$test: $subtest6"
+
+set cmd "stap '$srcdir/$subdir/${test}_6.stp'"
+send_log "executing: $cmd\n"
+set pipe [open "| sh -c {$cmd}" r]
+set out [read $pipe]
+set failed 0
+if {[catch {close $pipe} stderr] != 0} {
+    if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+        append stderr "\n"
+    }
+    global errorCode
+    if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+        set failed [lindex $errorCode 2]
+    }
+}
+
+set exp_out "fire 3!\nfire 2!\nfire 1!\n"
+regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+if {$out eq $exp_out} {
+    pass "${test_name}: stdout matches \"$escaped_exp_out\""
+} else {
+    fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+}
+
+if {$failed} {
+    fail "${test_name}: exit code should be zero but is $failed"
+} else {
+    pass "${test_name}: exit code is zero"
+}
+if {$stderr ne ""} {
+    send_log "stderr:\n$stderr"
+}
+
+# --- TEST 7 ---
+
+set subtest7 "TEST 7: abort() in timer.profile (more concurrency and no globals)"
+set test_name "$test: $subtest7"
+
+set cmd "stap '$srcdir/$subdir/${test}_7.stp'"
+send_log "executing: $cmd\n"
+set pipe [open "| sh -c {$cmd}" r]
+set out [read $pipe]
+set failed 0
+if {[catch {close $pipe} stderr] != 0} {
+    if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+        append stderr "\n"
+    }
+    global errorCode
+    if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+        set failed [lindex $errorCode 2]
+    }
+}
+
+set exp_out ""
+regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+if {$out eq $exp_out} {
+    pass "${test_name}: stdout matches \"$escaped_exp_out\""
+} else {
+    fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+}
+
+set exp_stderr ""
+regsub -all -- {\n} $exp_stderr {\n} escaped_exp_stderr
+if {$stderr eq $exp_stderr} {
+    pass "${test_name}: stderr matches \"$escaped_exp_stderr\""
+} else {
+    fail "${test_name}: stderr fails to match \"$escaped_exp_stderr\": got \"$stderr\""
+}
+
+if {$failed} {
+    fail "${test_name}: exit code should be zero but is $failed"
+} else {
+    pass "${test_name}: exit code is zero"
+}
+
+# --- TEST 8 ---
+
+set subtest8 "TEST 8: abort() in the middle of a func body - abort() cannot be caught"
+foreach runtime [get_runtime_list] {
+    if {$runtime eq ""} {
+        set runtime "kernel"
+    }
+    set test_name "$test: $subtest8 ($runtime)"
+
+    set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_8.stp'"
+    send_log "executing: $cmd\n"
+    set pipe [open "| sh -c {$cmd}" r]
+    set out [read $pipe]
+    set failed 0
+    if {[catch {close $pipe} stderr] != 0} {
+        if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+            append stderr "\n"
+        }
+        global errorCode
+        if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+            set failed [lindex $errorCode 2]
+        }
+    }
+
+    set exp_out "enter f\n"
+    regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+    if {$out eq $exp_out} {
+        pass "${test_name}: stdout matches \"$escaped_exp_out\""
+    } else {
+        fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+    }
+
+    if {$failed} {
+        fail "${test_name}: exit code should be zero but is $failed"
+    } else {
+        pass "${test_name}: exit code is zero"
+    }
+    if {$stderr ne ""} {
+        send_log "stderr:\n$stderr"
+    }
+}
diff --git a/testsuite/systemtap.base/abort_1.stp b/testsuite/systemtap.base/abort_1.stp
new file mode 100644
index 000000000..633a49616
--- /dev/null
+++ b/testsuite/systemtap.base/abort_1.stp
@@ -0,0 +1,11 @@
+function f() {
+    println("enter f");
+    abort();
+    println("leave f");
+}
+
+probe oneshot {
+    f();
+    printf("abort\n");
+    abort();
+}
diff --git a/testsuite/systemtap.base/abort_2.stp b/testsuite/systemtap.base/abort_2.stp
new file mode 100644
index 000000000..b8f864d06
--- /dev/null
+++ b/testsuite/systemtap.base/abort_2.stp
@@ -0,0 +1,17 @@
+function f() {
+    println("enter f");
+    abort();
+    println("leave f");
+}
+
+function g() {
+    println("enter g");
+    f();
+    println("leave g");
+}
+
+probe oneshot {
+    g();
+    printf("abort\n");
+    abort();
+}
diff --git a/testsuite/systemtap.base/abort_3.stp b/testsuite/systemtap.base/abort_3.stp
new file mode 100644
index 000000000..48182f1a3
--- /dev/null
+++ b/testsuite/systemtap.base/abort_3.stp
@@ -0,0 +1,5 @@
+probe oneshot {
+    println("enter probe");
+    abort();
+    println("leave probe");
+}
diff --git a/testsuite/systemtap.base/abort_6.stp b/testsuite/systemtap.base/abort_6.stp
new file mode 100644
index 000000000..2b42ec3f7
--- /dev/null
+++ b/testsuite/systemtap.base/abort_6.stp
@@ -0,0 +1,9 @@
+global rest = 3
+
+probe timer.profile {
+    id = rest--;
+    if (id <= 0) {
+        abort();
+    }
+    printf("fire %d!\n", id);
+}
diff --git a/testsuite/systemtap.base/abort_7.stp b/testsuite/systemtap.base/abort_7.stp
new file mode 100644
index 000000000..972ba365d
--- /dev/null
+++ b/testsuite/systemtap.base/abort_7.stp
@@ -0,0 +1,4 @@
+probe timer.profile {
+    abort();
+    error("fire after abort!\n")
+}
diff --git a/testsuite/systemtap.base/abort_8.stp b/testsuite/systemtap.base/abort_8.stp
new file mode 100644
index 000000000..304e788e6
--- /dev/null
+++ b/testsuite/systemtap.base/abort_8.stp
@@ -0,0 +1,14 @@
+function f() {
+    println("enter f");
+    try {
+        abort();
+    } catch {
+        println("caught abort!")
+    }
+    println("leave f");
+}
+
+probe oneshot {
+    f();
+    printf("abort\n");
+}
diff --git a/testsuite/systemtap.base/exit.exp b/testsuite/systemtap.base/exit.exp
new file mode 100644
index 000000000..9296590e2
--- /dev/null
+++ b/testsuite/systemtap.base/exit.exp
@@ -0,0 +1,130 @@
+set test "exit"
+set testpath "$srcdir/$subdir"
+
+if {! [installtest_p]} { untested "$test"; return }
+
+# --- TEST 1 ---
+
+set subtest1 "TEST 1: exit() in the middle of a func body"
+foreach runtime [get_runtime_list] {
+    if {$runtime eq ""} {
+        set runtime "kernel"
+    }
+    set test_name "$test: $subtest1 ($runtime)"
+
+    set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_1.stp'"
+    send_log "executing: $cmd\n"
+    set pipe [open "| sh -c {$cmd}" r]
+    set out [read $pipe]
+    set failed 0
+    if {[catch {close $pipe} stderr] != 0} {
+        if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+            append stderr "\n"
+        }
+        global errorCode
+        if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+            set failed [lindex $errorCode 2]
+        }
+    }
+
+    set exp_out "enter f\nleave f\nexit\n"
+    regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+    if {$out eq $exp_out} {
+        pass "${test_name}: stdout matches \"$escaped_exp_out\""
+    } else {
+        fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+    }
+
+    if {$failed} {
+        fail "${test_name}: exit code should be zero but is $failed"
+    } else {
+        pass "${test_name}: exit code is zero"
+    }
+    if {$stderr ne ""} {
+        send_log "stderr:\n$stderr"
+    }
+}
+
+# --- TEST 2 ---
+
+set subtest2 "TEST 2: exit() in the middle of a func body in a deeper func call chain"
+foreach runtime [get_runtime_list] {
+    if {$runtime eq ""} {
+        set runtime "kernel"
+    }
+    set test_name "$test: $subtest2 ($runtime)"
+
+    set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_2.stp'"
+    send_log "executing: $cmd\n"
+    set pipe [open "| sh -c {$cmd}" r]
+    set out [read $pipe]
+    set failed 0
+    if {[catch {close $pipe} stderr] != 0} {
+        if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+            append stderr "\n"
+        }
+        global errorCode
+        if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+            set failed [lindex $errorCode 2]
+        }
+    }
+
+    set exp_out "enter g\nenter f\nleave f\nleave g\nexit\n"
+    regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+    if {$out eq $exp_out} {
+        pass "${test_name}: stdout matches \"$escaped_exp_out\""
+    } else {
+        fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+    }
+
+    if {$failed} {
+        fail "${test_name}: exit code should be zero but is $failed"
+    } else {
+        pass "${test_name}: exit code is zero"
+    }
+    if {$stderr ne ""} {
+        send_log "stderr:\n$stderr"
+    }
+}
+
+# --- TEST 3 ---
+
+set subtest3 "TEST 3: exit() in the middle of a probe handler body"
+foreach runtime [get_runtime_list] {
+    if {$runtime eq ""} {
+        set runtime "kernel"
+    }
+    set test_name "$test: $subtest3 ($runtime)"
+
+    set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_3.stp'"
+    send_log "executing: $cmd\n"
+    set pipe [open "| sh -c {$cmd}" r]
+    set out [read $pipe]
+    set failed 0
+    if {[catch {close $pipe} stderr] != 0} {
+        if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+            append stderr "\n"
+        }
+        global errorCode
+        if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+            set failed [lindex $errorCode 2]
+        }
+    }
+
+    set exp_out "enter probe\nleave probe\n"
+    regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+    if {$out eq $exp_out} {
+        pass "${test_name}: stdout matches \"$escaped_exp_out\""
+    } else {
+        fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+    }
+
+    if {$failed} {
+        fail "${test_name}: exit code should be zero but is $failed"
+    } else {
+        pass "${test_name}: exit code is zero"
+    }
+    if {$stderr ne ""} {
+        send_log "stderr:\n$stderr"
+    }
+}
diff --git a/testsuite/systemtap.base/exit_1.stp b/testsuite/systemtap.base/exit_1.stp
new file mode 100644
index 000000000..c2dd0141a
--- /dev/null
+++ b/testsuite/systemtap.base/exit_1.stp
@@ -0,0 +1,11 @@
+function f() {
+    println("enter f");
+    exit();
+    println("leave f");
+}
+
+probe begin {
+    f();
+    printf("exit\n");
+    exit();
+}
diff --git a/testsuite/systemtap.base/exit_2.stp b/testsuite/systemtap.base/exit_2.stp
new file mode 100644
index 000000000..b747ddc0c
--- /dev/null
+++ b/testsuite/systemtap.base/exit_2.stp
@@ -0,0 +1,17 @@
+function f() {
+    println("enter f");
+    exit();
+    println("leave f");
+}
+
+function g() {
+    println("enter g");
+    f();
+    println("leave g");
+}
+
+probe begin {
+    g();
+    printf("exit\n");
+    exit();
+}
diff --git a/testsuite/systemtap.base/exit_3.stp b/testsuite/systemtap.base/exit_3.stp
new file mode 100644
index 000000000..3f3b801d1
--- /dev/null
+++ b/testsuite/systemtap.base/exit_3.stp
@@ -0,0 +1,5 @@
+probe begin {
+    println("enter probe");
+    exit();
+    println("leave probe");
+}
diff --git a/translate.cxx b/translate.cxx
index c7e7aa53f..a1b25d9b3 100644
--- a/translate.cxx
+++ b/translate.cxx
@@ -5732,7 +5732,7 @@ c_unparser::visit_functioncall (functioncall* e)
 
       // call function
       o->newline() << c_funcname (r->name) << " (c);";
-      o->newline() << "if (unlikely(c->last_error)) goto out;";
+      o->newline() << "if (unlikely(c->last_error || c->aborted)) goto out;";
 
       if (!already_checked_action_count && !session->suppress_time_limits
           && !session->unoptimized)
@@ -6069,7 +6069,7 @@ c_unparser::visit_print_format (print_format* e)
 	}
       o->line() << ");";
       o->newline(-1) << "#endif // STP_LEGACY_PRINT";
-      o->newline() << "if (unlikely(c->last_error)) goto out;";
+      o->newline() << "if (unlikely(c->last_error || c->aborted)) goto out;";
       o->newline() << res.value() << ";";
     }
 }
-- 
2.11.0.295.gd7dffce

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH v4] Add new built-in tapset function abort()
  2018-08-28 23:22     ` [PATCH v4] " Yichun Zhang (agentzh)
@ 2018-08-31 21:40       ` Yichun Zhang
  0 siblings, 0 replies; 5+ messages in thread
From: Yichun Zhang @ 2018-08-31 21:40 UTC (permalink / raw)
  To: systemtap

Hello!

On Tue, Aug 28, 2018 at 4:22 PM Yichun Zhang (agentzh) wrote:
> The new built-in tapset function abort() is similar to exit(), but it
> aborts the current probe handler (and any function calls in it) immediately.
> It works with both the kernel and dyninst runtimes. The bpf runtime is not
> yet supported.
>

Just committed to master with a typo fix ("flat" => "flag") found by fche.

Best,
Yichun

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2018-08-31 21:40 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-08-24  1:51 [PATCH] Add new built-in tapset function quit() Yichun Zhang (agentzh)
2018-08-28  2:43 ` [PATCH v2] " Yichun Zhang (agentzh)
2018-08-28 19:54   ` [PATCH v3] Add new built-in tapset function abort() Yichun Zhang (agentzh)
2018-08-28 23:22     ` [PATCH v4] " Yichun Zhang (agentzh)
2018-08-31 21:40       ` Yichun Zhang

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).