From 80addd948115579afd7dea524ba5f711a65157a7 Mon Sep 17 00:00:00 2001 From: Jonathan Lebon Date: Fri, 27 Jun 2014 14:57:02 -0400 Subject: [PATCH] uprobes_onthefly.exp: new testcase --- .../systemtap.onthefly/uprobes_onthefly.exp | 259 ++++++++++++++++++ 1 file changed, 259 insertions(+) create mode 100644 testsuite/systemtap.onthefly/uprobes_onthefly.exp diff --git a/testsuite/systemtap.onthefly/uprobes_onthefly.exp b/testsuite/systemtap.onthefly/uprobes_onthefly.exp new file mode 100644 index 000000000..188d3bd4f --- /dev/null +++ b/testsuite/systemtap.onthefly/uprobes_onthefly.exp @@ -0,0 +1,259 @@ +source $srcdir/$subdir/onthefly_common.tcl + +set test "uprobes_onthefly" +if {![installtest_p] || ![inode_uprobes_p]} { + untested "$test" + return +} + +# Goal: Ensure that inode-uprobes properly support on-the-fly arming/disarming. +# The script consists of a process.function and a process.function.return whose +# conditions are toggled by a timer probe. We then analyze the actual debug +# statements printed out to ensure the toggled probes are armed and disarmed as +# expected. + +# Runs stap on a script with the given arguments +proc run_stap {subtest args script} { + global test + + # Now start the application + set pid [exec ./$test.x &] + + # We always need the DEBUG and -g flags + set args "$args -D DEBUG_STP_ON_THE_FLY -g" + + # The script we use uses @entry(), which uses maps underneath. Since we will + # be stressing the system, we need to ensure we will have enough space for + # many map items. + set args "$args -D MAXMAPENTRIES=100000" + + # Run stap and catch any errors (we will rethrow it later) + set err [catch {eval exec stap -e {$script} $args} out] + + # Kill app + kill -INT $pid + + if {$err} {error $out} + return $out +} + +# Returns a script with some variables replaced. +# SUBTEST name of subtest +# ENABLED set to 1 to start the target probe as enabled, 0 as disabled +# MAXTOGGLES number of times we toggle the target probe condition +# TIMER if a number, then it's the period of the timer probe which +# toggles the target probes, if set to 'profile' then instead a +# timer.profile probe is used to toggle the target probe, if set +# to 'hardcore' then both a timer.profile probe and a +# kernel.trace("*") probe is used for toggling, if set to 'max' +# then a lot of various probe points are used for toggling +proc make_script {SUBTEST ENABLED MAXTOGGLES TIMER} { + global test + + set script { + + # We want these probes to fire as deterministically as possible so that + # their outputs can easily be predicted and compared. Unfortunately this + # is complicated by multiple facts, including + # (1) the timer might be so fast that the probes don't have time to + # fire and print something, + # (2) the uprobe might print something after it is re-enabled, but + # before the uretprobe is re-enabled, + # (3) disabling a uretprobe won't stop an already running handler. + # + # To get around these issues, we use a simple state machine. The states + # are as follow: + # + # 0 = cond disabled + # 1 = cond enabled, but uprobe && uretprobe not yet enabled + # 2 = cond enabled, uprobe enabled, uretprobe not yet enabled + # 3 = cond enabled, uprobe && uretprobe enabled, nothing printed yet + # 4 = cond enabled, 'hit' printed but not 'rethit' + # 5 = cond enabled, 'rethit' printed + + global state = ENABLED + global toggles = 0 + + probe process("./TEST.x").function("foo") if (state > 0) { + if (state == 3) { + println("hit") + state++; + } else if (state == 1) { + state++; + } + } + + probe process("./TEST.x").function("foo").return if (state > 0) { + # ensure that nothing changed during the vfs_read body + if (state != @entry(state)) + next + if (state == 4) { + println("rethit") + state++; + } else if (state == 2) { + state++; + } + } + + probe timer.us(TIMER) { + if (state != 0 && state != 5) + next # give probes more time to move through the states + toggles++ + if (toggles > MAXTOGGLES) + exit() + else { + println("toggling") + state = !state + } + } + + probe timer.s(360) { + println("timed out") + exit() + } + } + + # Set in values + set script [string map "TEST $test \ + SUBTEST $SUBTEST \ + ENABLED $ENABLED \ + MAXTOGGLES $MAXTOGGLES" $script] + if {[string match $TIMER "profile"]} { + set script [string map "timer.us(TIMER) timer.profile" $script] + } elseif {[string match $TIMER "hardcore"]} { + set script [string map "timer.us(TIMER) \ + {timer.profile, kernel.trace(\"*\")}" $script] + } elseif {[string match $TIMER "max"]} { + set script [string map "timer.us(TIMER) \ + {begin, end, error, kernel.function(\"*@workqueue.c\"), \ + process.begin, process.end, kernel.trace(\"*\"), \ + netfilter.pf(\"NFPROTO_IPV4\").hook(\"NF_INET_LOCAL_IN\")?, \ + netfilter.pf(\"NFPROTO_IPV4\").hook(\"NF_INET_LOCAL_OUT\")?, \ + perf.sw.cpu_clock.sample(1000000)?, timer.profile.tick?}" $script] + } else { + set script [string map "TIMER $TIMER" $script] + } + + return $script +} + +# Creates the pattern which will be used by the is_valid_output proc to +# determine if the output is valid. In other words, given the initial value of +# enabled, the maximum number of toggles, and whether on-the-fly is turned on or +# not, this proc determines what will be the output of stap. +proc make_pattern {subtest start_enabled maxtoggles} { + set pattern "" + + # If we're not starting as enabled, then the kprobes will be registered as + # disabled. + if {!$start_enabled} { + lappend pattern "* not registering (uretprobe) pidx ?" + lappend pattern "* not registering (uprobe) pidx ?" + lappend pattern "* not registering (uprobe) pidx ?" + } + + # For each toggle: + # - if we were enabled, then we would have had 'hit' and 'rethit' lines + # - if i == maxtoggles, then we'll exit so nothing more will be printed + # - if i < maxtoggles, then the timer probe will output 'toggling' followed + # by 'unregistering' or 'registering' lines if on-the-fly is turned on + for {set i 0} {$i <= $maxtoggles} {incr i} { + if {$start_enabled} { + lappend pattern "hit" "rethit" + } + if {$i < $maxtoggles} { + lappend pattern "toggling" + if {$start_enabled} { + set start_enabled 0 + lappend pattern "* unregistering (uretprobe) pidx ?" + lappend pattern "* unregistering (uprobe) pidx ?" + lappend pattern "* unregistering (uprobe) pidx ?" + } else { + set start_enabled 1 + if {$i < $maxtoggles} { + lappend pattern "* registering (uretprobe) pidx ?" + lappend pattern "* registering (uprobe) pidx ?" + lappend pattern "* registering (uprobe) pidx ?" + } + } + } + } + + return $pattern +} + +set c_program { + int foo(int i) { + int j = i; + return j+i*2; + } + int main(int argc, char **argv) { + while (1) { + argc = foo(argc); + } + return argc; + } +} + +# Compile program (feed program through stdin, -x c --> C code) +catch {exec gcc -g -o $test.x -x c - << $c_program} err +if {$err == "" && [file exists $test.x]} { + pass "$test (compilation)" +} else { + fail "$test (compilation)" + verbose -log "GCC output: $err" + return +} + +# Various on-the-fly tests + +# Toggle 0, 1, 2, 3, 4, and 5 times. This is important since we test if the +# probes finish well when disabled/enabled. + +run_subtest_valid "otf_finish_at_start_disabled" 0 0 500000 +run_subtest_valid "otf_finish_at_start_enabled" 1 0 500000 +run_subtest_valid "otf_start_disabled_iter_1" 0 1 500000 +run_subtest_valid "otf_start_enabled_iter_1" 1 1 500000 +run_subtest_valid "otf_start_disabled_iter_2" 0 2 500000 +run_subtest_valid "otf_start_enabled_iter_2" 1 2 500000 +run_subtest_valid "otf_start_disabled_iter_3" 0 3 500000 +run_subtest_valid "otf_start_enabled_iter_3" 1 3 500000 +run_subtest_valid "otf_start_disabled_iter_4" 0 4 500000 +run_subtest_valid "otf_start_enabled_iter_4" 1 4 500000 +run_subtest_valid "otf_start_disabled_iter_5" 0 5 500000 +run_subtest_valid "otf_start_enabled_iter_5" 1 5 500000 + +# Small interval tests + +run_subtest_valid "otf_timer_100ms" 1 5 100000 +run_subtest_valid "otf_timer_50ms" 1 5 50000 + +# NB: if we go any shorter, the kernel might not have a chance to run +# systemtap_module_refresh() before the next toggling occurs, throwing off the +# expected output. + +# Stress tests + +# These tests are not validated, i.e. their outputs are not checked against a +# pattern (see NB above why). Instead, they're just meant to stress the +# on-the-fly mechanism to ensure stap/the kernel doesn't die. So if stap is +# successfully run and terminated, we just default to a pass. + +run_subtest_stress "otf_stress_10ms_iter_50" 1 50 10000 +run_subtest_stress "otf_stress_5ms_iter_50" 1 50 5000 +run_subtest_stress "otf_stress_1ms_iter_50" 1 50 1000 +run_subtest_stress "otf_stress_500us_iter_50" 1 50 500 +run_subtest_stress "otf_stress_100us_iter_50" 1 50 100 + +run_subtest_stress "otf_stress_prof_iter_2000" 1 2000 profile + +# Use 100 us for interval +run_subtest_stress "otf_stress_hard_iter_2000" 1 2000 hardcore \ + -D STP_ON_THE_FLY_INTERVAL=100000 + +run_subtest_stress "otf_stress_max_iter_5000" 1 5000 max \ + -D STP_ON_THE_FLY_INTERVAL=100000 + +if {[file exists $test.x]} { + file delete $test.x +} -- 2.43.5