[DTrace-devel] [PATCH 4/4] uprobe: Implement PID-specific uprobes
Eugene Loh
eugene.loh at oracle.com
Tue Nov 11 23:19:26 UTC 2025
First, I had been under the impression there might be some test suite
changes. New tests? Something like that?
On 11/10/25 10:27, Kris Van Hees via DTrace-devel wrote:
> The mechanism to create uprobes by writing to $TRACEFS/uprobe_events
> caused probes to be placed in the dev/inode based mapping. This means
> that all tasks that use that mapping are be subject to the probes
s/be//?
> firing.
>
> The kernel supports placing uprobes for a specific task (by PID), which
> avoids impacting all other tasks that share the same code but are not
> the target of the tracing.
>
> This new mechanism places uprobes using the perf_event_open interface.
> Perf event attribute configuration data is read from
> /sys/bus/event_source/devices/uprobe/ as needed (and cached to ease
> repeated use). Underlying probes are now organized by PID-specific
> providers (uprobe$PID), and attach/detach no longer depends on the
> generic tracepoint support.
>
> The usdt_prids BPF map is no longer needed because USDT BPF programs
> are now task-specific. The trampoline generation for USDT Probes
> discovered after tracing started can now perform a simple loop over
> all compiled clauses, adding those that match the probe description
> to the program.
Should we remove:
libdtrace/dt_dlibs.c: DT_BPF_SYMBOL(usdt_prids, DT_IDENT_PTR),
> diff --git a/libdtrace/dt_bpf.c b/libdtrace/dt_bpf.c
> @@ -974,19 +974,12 @@ gmap_create_probes(dtrace_hdl_t *dtp)
> }
>
> /*
> - * Create the 'usdt_names' and 'usdt_prids' BPF maps.
> + * Create the 'usdt_names' BPF maps.
s/maps/map/
> *
> * 'usdt_names': a global hash map indexed by PRID and whose value has probe
> * name elements at fixed offsets within the value. This map
> * is used for get_bvar() to look up probe name elements for
> * any prid that was created after dtrace_go().
> - *
> - * 'usdt_prids': a global hash map indexed by (pid, underlying probe ID).
> - * The value is a probe ID for the overlying USDT probe and
> - * a bit mask indicating which clauses to execute for this pid.
> - *
> - * For a given (pid, PRID) key, there can be at most one
> - * overlying USDT probe.
> */
> static int
> gmap_create_usdt(dtrace_hdl_t *dtp)
> diff --git a/libdtrace/dt_program.c b/libdtrace/dt_program.c
> @@ -20,21 +20,6 @@
> #include <dt_probe.h>
> #include <dt_bpf.h>
>
> -int
> -dt_stmt_clsflag_set(dtrace_stmtdesc_t *stp, int flags) {
> - stp->dtsd_clauseflags |= flags;
> -
> - return 0;
> -}
> -
> -int
> -dt_stmt_clsflag_test(dtrace_stmtdesc_t *stp, int flags) {
> - if (stp->dtsd_clauseflags & flags)
> - return 1;
> -
> - return 0;
> -}
> -
Okay, but then in dt_program.h get rid of
dt_program.h:extern int dt_stmt_clsflag_set(dtrace_stmtdesc_t *stp, int
flags);
dt_program.h:extern int dt_stmt_clsflag_test(dtrace_stmtdesc_t *stp, int
flags);
> dtrace_prog_t *
> dt_program_create(dtrace_hdl_t *dtp)
> {
> diff --git a/libdtrace/dt_prov_uprobe.c b/libdtrace/dt_prov_uprobe.c
> @@ -316,11 +319,71 @@ dt_provimpl_t dt_pid;
> static int populate(dtrace_hdl_t *dtp)
> {
> + uprobe_data_t *udp = dt_alloc(dtp, sizeof(uprobe_data_t));
> +
> + udp->perf_type = -1; /* not initialized */
> + udp->ret_flag = -1; /* not initialized */
> + udp->ref_shift = -1; /* not initialized */
> +
> if (dt_provider_create(dtp, dt_uprobe.name, &dt_uprobe, &pattr,
> - NULL) == NULL ||
> - dt_provider_create(dtp, dt_pid.name, &dt_pid, &pattr,
> + udp) == NULL)
> + return -1;
> +
> + if (dt_provider_create(dtp, dt_pid.name, &dt_pid, &pattr,
> NULL) == NULL ||
> dt_provider_create(dtp, dt_stapsdt.name, &dt_stapsdt, &pattr,
> NULL) == NULL)
Why is create(dt_uprobe) being split off the other two create()s? This
makes both the delta and the resulting code (a tiny bit) more complex.
> @@ -401,182 +474,57 @@ static void probe_disable(dtrace_hdl_t *dtp, dt_probe_t *prp)
> -/*
> - * Judge whether clause "n" could ever be called as a USDT probe
> - * for this underlying probe. We can pass uprp==NULL to see if
> - * the clause can be excluded for every probe.
> - */
> static int
> -ignore_clause(dtrace_hdl_t *dtp, int n, const dt_probe_t *uprp)
> +clean_usdt_probes(dtrace_hdl_t *dtp)
> {
> - dtrace_stmtdesc_t *stp = dtp->dt_stmts[n];
> - dtrace_probedesc_t *pdp = &stp->dtsd_ecbdesc->dted_probe;
> + int fdnames = dtp->dt_usdt_namesmap_fd;
> + uint32_t key, nxt;
> + del_list_t dlist = { 0, };
> + del_list_t *del, *ndel;
> + dt_probe_t *prp;
>
> - if (stp == NULL)
> - return 1;
> + /* Initialize key to a probe id that cannot be found. */
> + key = DTRACE_IDNONE;
>
> - /*
> - * Some clauses could never be called for a USDT probe,
> - * regardless of the underlying probe uprp. Cache this
> - * status in the clause flags for dt_stmts[n].
> - */
> - if (dt_stmt_clsflag_test(stp, DT_CLSFLAG_USDT_INCLUDE | DT_CLSFLAG_USDT_EXCLUDE) == 0) {
> - size_t len = strlen(pdp->prv);
> + /* Loop over usdt_names entries. */
> + while (dt_bpf_map_next_key(fdnames, &key, &nxt) == 0) {
> + dtrace_probedesc_t pd = { 0, };
>
> - /*
> - * If the last char in the provider description is
> - * neither '*' nor a digit, it cannot be a USDT probe.
> - */
> - if (len > 1) {
> - char lastchar = (pdp->prv[0] != '\0' ? pdp->prv[len - 1] : '*');
> -
> - if (lastchar != '*' && !isdigit(lastchar)) {
> - dt_stmt_clsflag_set(stp, DT_CLSFLAG_USDT_EXCLUDE);
> - return 1;
> - }
> - }
> + key = nxt;
> + pd.id = key;
>
> /*
> - * If the provider description is "pid[0-9]*", it
> - * is a pid probe, not USDT.
> + * If the probe exists (as it should), and the process exists,
> + * we should keep it.
> */
> - if (strncmp(pdp->prv, "pid", 3) == 0) {
> - int i, l = strlen(pdp->prv);
> -
> - for (i = 3; i < l; i++)
> - if (!isdigit((pdp->prv[i])))
> - break;
> + prp = dt_probe_lookup(dtp, &pd);
> + if (prp != NULL) {
> + list_probe_t *pup = prp->prv_data;
> + dt_uprobe_t *upp = pup->probe->prv_data;
>
> - if (i == l) {
> - dt_stmt_clsflag_set(stp, DT_CLSFLAG_USDT_EXCLUDE);
> - return 1;
> - }
> + if (Pexists(upp->pid))
> + continue;
> }
>
> - /* Otherwise, it is possibly a USDT probe. */
> - dt_stmt_clsflag_set(stp, DT_CLSFLAG_USDT_INCLUDE);
> + /* Add the key and probe to the delete list. */
> + del = dt_zalloc(dtp, sizeof(del_list_t));
> + del->probe = prp;
> + dt_list_append((dt_list_t *)&dlist, del);
> }
> - if (dt_stmt_clsflag_test(stp, DT_CLSFLAG_USDT_EXCLUDE) == 1)
> - return 1;
> - if (uprp == NULL)
> - return 0;
>
> - /*
> - * If we cannot ignore this statement, try to use uprp.
> - */
> -
> - /* We know what function we're in. It must match the probe description (unless "-"). */
> - if (strcmp(pdp->fun, "-") != 0) {
> - dt_uprobe_t *upp = uprp->prv_data;
> + /* Really delete entries from usdt_names. */
> + for (del = dt_list_next(&dlist); del != NULL; del = ndel) {
> + ndel = dt_list_next(del);
> + prp = del->probe;
>
> - assert(upp->func); // never a return probe
> - if (!dt_gmatch(upp->func, pdp->fun))
> - return 1;
> + dt_bpf_map_delete(fdnames, &prp->desc->id);
> + probe_disable(dtp, prp);
> + free(del);
Okay. Just out of curiosity, under what conditions does one use dt_free()?
> }
>
> return 0;
> @@ -1508,95 +1400,73 @@ static int trampoline(dt_pcb_t *pcb, uint_t exitlbl)
> /*
> * USDT.
> */
>
> - /* In some cases, we know there are no USDT probes. */ // FIXME: add more checks
> - if (upp->flags & PP_IS_RETURN)
> - goto out;
> -
> + /*
> + * First check whether the USDT probe is active, i.e. its probe ID is
> + * in the usdt_names BPF map. If not, ignore it for now.
> + */
> + emit(dlp, BPF_STORE_IMM(BPF_W, BPF_REG_FP, DT_TRAMP_SP_SLOT(0), usdtp->desc->id));
> + dt_cg_xsetx(dlp, usdt_names, DT_LBL_NONE, BPF_REG_1, usdt_names->di_id);
> + emit(dlp, BPF_MOV_REG(BPF_REG_2, BPF_REG_FP));
> + emit(dlp, BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, DT_TRAMP_SP_SLOT(0)));
> + emit(dlp, BPF_CALL_HELPER(BPF_FUNC_map_lookup_elem));
> + emit(dlp, BPF_BRANCH_IMM(BPF_JEQ, BPF_REG_0, 0, pcb->pcb_exitlbl));
> +
> + /* Set up probe arguments. */
> if (upp->sargc)
> copy_args(pcb, upp);
> else
> dt_cg_tramp_copy_args_from_regs(pcb, 0);
>
> - /*
> - * 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));
> -
> - /*
> - * Look up in the BPF 'usdt_prids' map. The key should fit into
> - * trampoline stack slot 0.
> - */
> - assert(sizeof(usdt_prids_map_key_t) <= DT_STK_SLOT_SZ);
> - emit(dlp, BPF_STORE(BPF_W, BPF_REG_FP, DT_TRAMP_SP_SLOT(0), BPF_REG_0));
> - emit(dlp, BPF_STORE_IMM(BPF_W, BPF_REG_FP, DT_TRAMP_SP_SLOT(0) + (int)sizeof(pid_t), uprp->desc->id));
> - dt_cg_xsetx(dlp, usdt_prids, DT_LBL_NONE, BPF_REG_1, usdt_prids->di_id);
> - emit(dlp, BPF_MOV_REG(BPF_REG_2, BPF_REG_FP));
> - emit(dlp, BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, DT_TRAMP_SP_SLOT(0)));
> - emit(dlp, BPF_CALL_HELPER(BPF_FUNC_map_lookup_elem));
> - emit(dlp, BPF_BRANCH_IMM(BPF_JEQ, BPF_REG_0, 0, lbl_exit));
> -
> if (upp->flags & PP_IS_ENABLED) {
> /*
> - * Generate a BPF trampoline for an is-enabled probe. The is-enabled probe
> - * prototype looks like:
> + * Generate a BPF trampoline for an is-enabled probe. The
> + * is-enabled probe * prototype looks like:
s/* prototype/prototype/
> *
> * int is_enabled(int *arg)
> *
> - * The trampoline writes 1 into the location pointed to by the passed-in arg.
> + * The trampoline writes 1 into the location pointed to by the
> + * passed-in arg.
> */
> emit(dlp, BPF_STORE_IMM(BPF_W, BPF_REG_FP, DT_TRAMP_SP_SLOT(0), 1));
> emit(dlp, BPF_LOAD(BPF_DW, BPF_REG_1, BPF_REG_7, DMST_ARG(0)));
More information about the DTrace-devel
mailing list