[DTrace-devel] [PATCH 16/16] PID provider implementation

Eugene Loh eugene.loh at oracle.com
Mon Mar 22 19:52:36 PDT 2021


Sorry for the slow replies.  It's taking me a while to refamiliarize 
myself with providers.  I'm hopping around the patches in this patch 
set.  Let me start with a few nits and then with a bigger question.

On 3/19/21 12:55 AM, Kris Van Hees wrote:
> This patch provides an implementation of the pid provider.  This is
> a provider that makes it possible to do function boundary tracing
> at the userspace level (in shared libraries and executables).
>
> The implementation of userspace probes is built on the uprobes
> support in the kernel tracing infrastructure.  The uprobes are
> inode based and therefore not process specific whereas pid probes in
> DTrace are process specific.  This means that there is a n-to-1

either "a" -> "an" or even better "n-to-1" to "many-to-one"

> relation between pid probes and uprobes.  It is also worth noting
> that a pid probe together with its underlying uprobe provides
> equivalent functionality as any other non-pid DTrace probes.  The
> (process specific) pid probe represents the named probe and has
> D clauses associated eith it.  The uprobe represents the underlying

eith -> with

> system probe (not process specific) that our program will be attached
> to.
>
> When we want to trace the main() functon in a process with PID 1234,
> we can specify this probes as pid1234:a.out:main:entry.  A lookup is
> performed to see if we already have a pid:<ino>:main:entry probe to
> associate the new pid probe with.  If not, it is created.  It is this
> pid:<ino>:main:entry probe that will be added to the enablings list of
> probes to which we need to attach a BPF program.
>
> Any clauses for pid1234:a.out:main:entry will be associated with that
> specific probe.
>
> When the final BPF program is constructed for a pid related enaling,

enaling -> enabling

> we construct the program for probe pid:<ino>:main:entry.  The program
> (trampoline) will comprise basic environment setup, copying of any
> possible probe arguments, and a sequence of code blocks following the
> pattern:
>
> 	if (pid == <some-pid>) {
> 		casee dt_clause_0(dctx);
> 		casee dt_clause_1(dctx);

casee -> case: ?
(two instances)

> 		<...>
> 	}
>
> In other words, although the uprobe will fire for any process that
> reaches the probe point, we check the PID of the process that caused
> the probe to fire against the registered probes and we only execute
> the clauses for the matching PID.
>
> Future patches will augment the functionality provided here with
> arbitrary instruction tracing at the userspace level.
>
> Signee-off-by: Kris Van Hees <kris.van.hees at oracle.com>

Signee -> Signed

> diff --git a/libdtrace/dt_prov_pid.c b/libdtrace/dt_prov_pid.c
> +static void trampoline(dt_pcb_t *pcb)
> +{
> +	dt_irlist_t		*dlp = &pcb->pcb_ir;
> +	const dt_probe_t	*prp = pcb->pcb_probe;
> +	const dt_probe_t	*pprp;
> +	const pid_probe_t	*pp = prp->prv_data;
> +
> +	dt_cg_tramp_prologue(pcb);
> +
> +	dt_cg_tramp_copy_args_from_regs(pcb, BPF_REG_8);
> +
> +	/*
> +	 * Retrieve the PID of the process that caused the probe to fire.
> +	 */
> +	emit(dlp,  BPF_CALL_HELPER(BPF_FUNC_get_current_pid_tgid));
> +	emit(dlp,  BPF_ALU64_IMM(BPF_RSH, BPF_REG_0, 32));

I would prefer a blank line between the get_current_pid_tgid() code and 
the following for(;;) loop since the comment refers only to the former 
and not to the loop.

> +	for (pprp = dt_list_next(&pp->probes); pprp != NULL;
> +	     pprp = dt_list_next(pprp)) {
> +		uint_t	lbl_next = dt_irlist_label(dlp);
> +		pid_t	pid = strtoul(pprp->desc->prv + 3, NULL, 10);
> +
> +		/*
> +		 * Check whether this pid-provider probe serves the current
> +		 * process.
> +		 */
> +		emit(dlp,  BPF_BRANCH_IMM(BPF_JNE, BPF_REG_0, pid, lbl_next));
> +		dt_cg_tramp_call_clauses(pcb, pprp, DT_ACTIVITY_ACTIVE);
> +		emitl(dlp, lbl_next,
> +			   BPF_NOP());
> +	}
> +
> +	dt_cg_tramp_return(pcb);
> +}

The main reason for this email is a question I had about the pid 
trampoline.  We enter that loop with %r0 holding the firing pid.  We 
then loop over probes, comparing %r0 with the probe pid.  That's great, 
but if ever the firing and probe pids match, then we call the code 
that's generated in dt_cg_tramp_call_clauses().  That generated BPF code 
taints %r0, which will no longer contain the firing pid. Then, the next 
%r0!= test will not be doing what we want it to do.

If at most one probe pid in the loop can match the firing pid, maybe the 
thing to do is hoist the lbl_next=... and the emitl(lbl_next,BPF_NOP()) 
out of the loop.

Maybe we're okay since a clause call will, I guess, finish with a %r0=0 
and so the remaining pid checks will all fail, but I would think it 
makes more sense to clean this thing up rather than relying on such 
intricacies.



More information about the DTrace-devel mailing list