[DTrace-devel] Long-standing bug in DTrace discovered

Kris Van Hees kris.van.hees at oracle.com
Thu Sep 24 12:48:44 PDT 2020


In the midst of fixing the semantics of the BEGIN probe, the END probe, the
exit() action, and user-initiated termination we tried various scenarios of
valid D clauses to make sure everything was working as expected.  An unusual
discrepancy was found...

dtrace -n BEGIN'{ trace(123); exit(1); trace(321); exit(2); }' -n END

yields the following output on DTrace based on BPF:

    dtrace: description 'BEGIN' matched 1 probe
    dtrace: description 'END' matched 1 probe
    CPU     ID                    FUNCTION:NAME
     11      1                           :BEGIN         123        321
     11      2                             :END 

and the return code of the dtrace command is 2.

When trying this on the legacy implementation of DTrace on Linux we get:

    dtrace: description 'BEGIN ' matched 1 probe
    dtrace: description 'END' matched 1 probe
     CPU     ID                    FUNCTION:NAME
       2      2                             :END 

and the return code of the dtrace command is 0.  That can't be right!

So, we tried this on an Oracle Solaris system and got exactly the same output
(and rc) as the legacy DTrace on Linux version!

Now, if you drop the 2nd exit action, things are very different:

    dtrace: description 'BEGIN' matched 1 probe
    dtrace: description 'END' matched 1 probe
    CPU     ID                    FUNCTION:NAME
     11      1                           :BEGIN         123        321
     11      2                             :END 

and rc = 1.  And this is the output and rc) for all three versions.

When you start digging into the implementation of DTrace on Linux (legacy) and
Oracle Solaris, the problem can be found in dtrace_probe().  When doing action
specific processing in a switch on act->dta_kind, special handling is done for
DTRACEACT_EXIT.  If the current activity state is DTRACE_ACTIVITY_COOLDOWN, we
have nothing to do (we are already processing a STOP operation issued by the
consumer).  Then, if the current activity state is not DTRACE_ACTIVITY_WARMUP,
we force the expected state to be DTRACE_ACTIVITY_ACTIVE.  Finally, the code
will set the activity state to be DTRACE_ACTIVITY_DRAINING if and only if the
current state is DTRACE_ACTIVITY_ACTIVE.

If the expected state is not DTRACE_ACTIVITY_ACTIVE, the CPU_DTRACE_DROP flag
is set which will drop all trace data recorded for the current ECB.

So, the problem is that when a clause contains two exit() actions, the first
one will move the activity state to DTRACE_ACTIVITY_DRAINING, and when the
second one gets processed, an invalid state is found (DTRACE_ACTIVITY_DRAINING
when it should be DTRACE_ACTIVITY_ACTIVE), and so the trace data for the ECB
gets dropped.  But since the state is DTRACE_ACTIVITY_DRAINING, tracing does
terminate as usual.  The trace data of the BEGIN clause (incl. the exit
value) is lost though.

So, the good news is that DTrace based on BPF is doing the right thing.  The
bad news is that pretty much all existing versions of DTrace have this bug.

Granted, I cannot really think of a real use case scenario where this action
sequence makes sense, but it is a nice example of valid code that actually
triggers a bug in the implementation.

	Kris



More information about the DTrace-devel mailing list