[DTrace-devel] [PATCH v3 1/7] libdtrace: Support multiple DTrace handles per process

Alan Maguire alan.maguire at oracle.com
Thu Jul 23 12:33:05 UTC 2026


On 22/07/2026 22:09, Kris Van Hees wrote:
> On Tue, Jul 21, 2026 at 04:24:45PM +0100, Alan Maguire wrote:
>> For use cases like PCP we require multiple DTrace handles
>> in a process; to fix this ensure we tie global map creation
>> to the handle and also allow BEGIN and END probes to already
>> exist.  Later work can capitalize on efficiences like sharing
>> kernel type info, but for now this is enough to make multiple
>> instances work.
>>
>> Signed-off-by: Alan Maguire <alan.maguire at oracle.com>
>> ---
>>  libdtrace/dt_bpf.c         | 6 ++----
>>  libdtrace/dt_impl.h        | 1 +
>>  libdtrace/dt_prov_dtrace.c | 6 +++++-
>>  3 files changed, 8 insertions(+), 5 deletions(-)
>>
>> diff --git a/libdtrace/dt_bpf.c b/libdtrace/dt_bpf.c
>> index 146a66e3..be8a4229 100644
>> --- a/libdtrace/dt_bpf.c
>> +++ b/libdtrace/dt_bpf.c
>> @@ -25,8 +25,6 @@
>>  #include <dt_btf.h>
>>  #include <port.h>
>>
>> -static boolean_t       dt_gmap_done = 0;
>> -
>>  #define BPF_CG_LICENSE "GPL";
>>
>>  int
>> @@ -1076,11 +1074,11 @@ int
>>  dt_bpf_gmap_create(dtrace_hdl_t *dtp)
>>  {
>>         /* If we already created the global maps, return success. */
>> -       if (dt_gmap_done)
>> +       if (dtp->dt_gmap_done)
>>                 return 0;
>>
>>         /* Mark global maps creation as completed. */
>> -       dt_gmap_done = 1;
>> +       dtp->dt_gmap_done = 1;
> 
> ACK.
> 
>>
>>  #define CREATE_MAP(name) \
>>         if (gmap_create_##name(dtp) == -1) \
>> diff --git a/libdtrace/dt_impl.h b/libdtrace/dt_impl.h
>> index 5282efbd..7db2ba93 100644
>> --- a/libdtrace/dt_impl.h
>> +++ b/libdtrace/dt_impl.h
>> @@ -433,6 +433,7 @@ struct dtrace_hdl {
>>         dt_list_t dt_lib_dep_sorted;    /* dependency sorted library list */
>>         dt_global_pcap_t dt_pcap; /* global tshark/pcap state */
>>         char *dt_freopen_filename; /* filename for freopen() action */
>> +       boolean_t dt_gmap_done; /* global map created */
> 
> ACK.
> 
>>  };
>>
>>  /*
>> diff --git a/libdtrace/dt_prov_dtrace.c b/libdtrace/dt_prov_dtrace.c
>> index 4b788507..8118ab23 100644
>> --- a/libdtrace/dt_prov_dtrace.c
>> +++ b/libdtrace/dt_prov_dtrace.c
>> @@ -243,7 +243,11 @@ static int attach(dtrace_hdl_t *dtp, const dt_probe_t *prp, int bpf_fd)
>>                         close(fd);
>>                 }
>>                 free(spec);
>> -               if (rc == -1)
>> +               /*
>> +                * Multiple handles in a process mean we may have BEGIN/END
>> +                * uprobe events already.
>> +                * */
>> +               if (rc == -1 && errno != EEXIST)
>>                         return -ENOENT;
> 
> ACK, but I do wonder whether the use case of multiple handles in a process
> may cause trouble when one of those handles tries to remove the uprobe for
> BEGIN/END?  This really should have a test case that shows that two handles

I believe we are okay here. 

All handles use the same per-process event name (dt_<pid>_dtrace/BEGIN|END)
and accept EEXIST when it already exists. Each handle then opens its own
tracepoint perf-event fd and attaches its own BPF program. On close, it first
closes only its own fd, then requests deletion of the shared uprobe. The
kernel refuses that deletion with EBUSY while another handle still has a perf-
event reference open; the last handle to close can remove it. So rather than
relying on maintaining a local refcount the kernel helps us here.

> can be operative at the same time, and also a test that shows that closing one
> while keeping the other open does not stop the second one from seeing its own
> END probe firing.  Ideally, it would be two test cases:
> 
>  - one that creates handles A and B, and then closes A and checks B still works
>  - one that creates handles A and B, and then closes B and checks A still works
> 

Sure, below implements this and passes; I'll add it for the next iteration:

#!/bin/bash
#
# Oracle Linux DTrace.
# Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
# Licensed under the Universal Permissive License v 1.0 as shown at
# http://oss.oracle.com/licenses/upl.
#
# Ensure END probes remain attached when another DTrace session in the same
# process closes.

if [ $# -ne 1 ]; then
    echo "usage: $0 <dtrace>" >&2
    exit 2
fi

tmpdir=$(mktemp -d)
cleanup() {
    rm -rf "$tmpdir"
}
trap cleanup EXIT

cat >"$tmpdir/script.d" <<'EOF'
#!/usr/bin/env python3
from dtrace import DTraceSession


program = r'''
END
{
    @ended = count();
}
'''


def start_session():
    session = DTraceSession()
    program_handle = session.compile(program)
    session.enable(program_handle)
    session.go()
    return session


def assert_end_fired(session, survivor):
    session.stop()
    session.agg_snap()
    rows = session.agg_walk()

    for row in rows:
        if row["name"] == "ended" and int(row["value"]) == 1:
            return

    raise SystemExit(f"END probe did not fire for surviving handle {survivor}")


# Closing A must not remove B's shared END uprobe.
handle_a = start_session()
handle_b = start_session()
try:
    handle_a.close()
    assert_end_fired(handle_b, "B")
finally:
    handle_b.close()

# Closing B must not remove A's shared END uprobe.
handle_a = start_session()
handle_b = start_session()
try:
    handle_b.close()
    assert_end_fired(handle_a, "A")
finally:
    handle_a.close()
EOF

chmod +x "$tmpdir/script.d"


"$tmpdir/script.d"




More information about the DTrace-devel mailing list