Beispiel #1
0
    def test_add_spent(self):
        """ Tests the utxocache:add utxocache:spent tracepoint API
            See https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#tracepoint-utxocacheadd
            and https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#tracepoint-utxocachespent
        """

        self.log.info(
            "test the utxocache:add and utxocache:spent tracepoint API")

        self.log.info("create an unconfirmed transaction")
        self.wallet.send_self_transfer(from_node=self.nodes[0])

        # We mine a block to trace changes (add/spent) to the active in-memory cache
        # of the UTXO set (see CoinsTip() of CCoinsViewCache). However, in some cases
        # temporary clones of the active cache are made. For example, during mining with
        # the generate RPC call, the block is first tested in TestBlockValidity(). There,
        # a clone of the active cache is modified during a test ConnectBlock() call.
        # These are implementation details we don't want to test here. Thus, after
        # mining, we invalidate the block, start the tracing, and then trace the cache
        # changes to the active utxo cache.
        self.log.info("mine and invalidate a block that is later reconsidered")
        block_hash = self.generate(self.wallet, 1)[0]
        self.nodes[0].invalidateblock(block_hash)

        self.log.info(
            "hook into the utxocache:add and utxocache:spent tracepoints")
        ctx = USDT(pid=self.nodes[0].process.pid)
        ctx.enable_probe(probe="utxocache:add", fn_name="trace_utxocache_add")
        ctx.enable_probe(probe="utxocache:spent",
                         fn_name="trace_utxocache_spent")
        bpf = BPF(text=utxocache_changes_program, usdt_contexts=[ctx], debug=0)

        # The handle_* function is a ctypes callback function called from C. When
        # we assert in the handle_* function, the AssertError doesn't propagate
        # back to Python. The exception is ignored. We manually count and assert
        # that the handle_* functions succeeded.
        EXPECTED_HANDLE_ADD_SUCCESS = 2
        EXPECTED_HANDLE_SPENT_SUCCESS = 1
        handle_add_succeeds = 0
        handle_spent_succeeds = 0

        expected_utxocache_spents = []
        expected_utxocache_adds = []

        def handle_utxocache_add(_, data, __):
            nonlocal handle_add_succeeds
            event = ctypes.cast(data, ctypes.POINTER(UTXOCacheChange)).contents
            self.log.info(f"handle_utxocache_add(): {event}")
            add = expected_utxocache_adds.pop(0)
            assert_equal(add["txid"], bytes(event.txid[::-1]).hex())
            assert_equal(add["index"], event.index)
            assert_equal(add["height"], event.height)
            assert_equal(add["value"], event.value)
            assert_equal(add["is_coinbase"], event.is_coinbase)
            handle_add_succeeds += 1

        def handle_utxocache_spent(_, data, __):
            nonlocal handle_spent_succeeds
            event = ctypes.cast(data, ctypes.POINTER(UTXOCacheChange)).contents
            self.log.info(f"handle_utxocache_spent(): {event}")
            spent = expected_utxocache_spents.pop(0)
            assert_equal(spent["txid"], bytes(event.txid[::-1]).hex())
            assert_equal(spent["index"], event.index)
            assert_equal(spent["height"], event.height)
            assert_equal(spent["value"], event.value)
            assert_equal(spent["is_coinbase"], event.is_coinbase)
            handle_spent_succeeds += 1

        bpf["utxocache_add"].open_perf_buffer(handle_utxocache_add)
        bpf["utxocache_spent"].open_perf_buffer(handle_utxocache_spent)

        # We trigger a block re-connection. This causes changes (add/spent)
        # to the UTXO-cache which in turn triggers the tracepoints.
        self.log.info("reconsider the previously invalidated block")
        self.nodes[0].reconsiderblock(block_hash)

        block = self.nodes[0].getblock(block_hash, 2)
        for (block_index, tx) in enumerate(block["tx"]):
            for vin in tx["vin"]:
                if "coinbase" not in vin:
                    prevout_tx = self.nodes[0].getrawtransaction(
                        vin["txid"], True)
                    prevout_tx_block = self.nodes[0].getblockheader(
                        prevout_tx["blockhash"])
                    spends_coinbase = "coinbase" in prevout_tx["vin"][0]
                    expected_utxocache_spents.append({
                        "txid":
                        vin["txid"],
                        "index":
                        vin["vout"],
                        "height":
                        prevout_tx_block["height"],
                        "value":
                        int(prevout_tx["vout"][vin["vout"]]["value"] * COIN),
                        "is_coinbase":
                        spends_coinbase,
                    })
            for (i, vout) in enumerate(tx["vout"]):
                if vout["scriptPubKey"]["type"] != "nulldata":
                    expected_utxocache_adds.append({
                        "txid":
                        tx["txid"],
                        "index":
                        i,
                        "height":
                        block["height"],
                        "value":
                        int(vout["value"] * COIN),
                        "is_coinbase":
                        block_index == 0,
                    })

        assert_equal(EXPECTED_HANDLE_ADD_SUCCESS, len(expected_utxocache_adds))
        assert_equal(EXPECTED_HANDLE_SPENT_SUCCESS,
                     len(expected_utxocache_spents))

        bpf.perf_buffer_poll(timeout=200)
        bpf.cleanup()

        self.log.info(
            f"check that we successfully traced {EXPECTED_HANDLE_ADD_SUCCESS} adds and {EXPECTED_HANDLE_SPENT_SUCCESS} spent"
        )
        assert_equal(0, len(expected_utxocache_adds))
        assert_equal(0, len(expected_utxocache_spents))
        assert_equal(EXPECTED_HANDLE_ADD_SUCCESS, handle_add_succeeds)
        assert_equal(EXPECTED_HANDLE_SPENT_SUCCESS, handle_spent_succeeds)
Beispiel #2
0
    def load(self):
        ctx_name = "ctx"
        stack_trace = ""
        if self.user_stack:
            stack_trace += """
                    key.user_stack_id = stack_traces.get_stackid(
                      %s, BPF_F_USER_STACK
                    );""" % (ctx_name)
        else:
            stack_trace += "key.user_stack_id = -1;"
        if self.kernel_stack:
            stack_trace += """
                    key.kernel_stack_id = stack_traces.get_stackid(
                      %s, 0
                    );""" % (ctx_name)
        else:
            stack_trace += "key.kernel_stack_id = -1;"

        trace_count_text = """
int trace_count(void *ctx) {
    FILTER
    struct key_t key = {};
    key.tgid = GET_TGID;
    STORE_COMM
    %s
    counts.increment(key);
    return 0;
}
        """
        trace_count_text = trace_count_text % (stack_trace)

        bpf_text = """
#include <uapi/linux/ptrace.h>
#include <linux/sched.h>

//
struct key_t {
    // no pid (thread ID) so that we do not needlessly split this key
    u32 tgid;
    int kernel_stack_id;
    int user_stack_id;
    char name[TASK_COMM_LEN];
};

BPF_HASH(counts, struct key_t);
BPF_STACK_TRACE(stack_traces, 1024);
        """

        # filter text init
        filter_text = []
        # We really mean the tgid from the kernel's perspective, which is in
        # the top 32 bits of bpf_get_current_pid_tgid().
        if self.is_kernel_probe() and self.pid:
            # pid filter
            filter_text.append(
                'u32 pid; pid = bpf_get_current_pid_tgid() >> 32; ' +
                'if (pid != %d) { return 0; }' % self.pid)

        if self.is_kernel_probe() and self.cpu:
            # cpu filter
            filter_text.append(
                'struct task_struct *task; task = (struct task_struct*)bpf_get_current_task(); '
                + 'if (task->cpu != %d) { return 0; }' % self.cpu)

        #
        trace_count_text = trace_count_text.replace('FILTER',
                                                    '\n    '.join(filter_text))

        # Do per-pid statistics iff -P is provided
        if self.per_pid:
            # replace
            trace_count_text = trace_count_text.replace(
                'GET_TGID', 'bpf_get_current_pid_tgid() >> 32')
            trace_count_text = trace_count_text.replace(
                'STORE_COMM',
                'bpf_get_current_comm(&key.name, sizeof(key.name));')
        else:
            # skip splitting on PID so these aggregate
            # together, and don't store the process name.
            trace_count_text = trace_count_text.replace(
                'GET_TGID', '0xffffffff')
            trace_count_text = trace_count_text.replace('STORE_COMM', '')

        self.usdt = None

        # usdt
        if self.type == "u":
            self.usdt = USDT(path=self.library, pid=self.pid)

            # iterator all usdt of library above
            for probe in self.usdt.enumerate_probes():
                if not self.pid and (probe.bin_path != self.library):
                    continue
                # match pattern
                if re.match(self.pattern, probe.name):
                    # This hack is required because the bpf_usdt_readarg
                    # functions generated need different function names for
                    # each attached probe. If we just stick to trace_count,
                    # we'd get multiple bpf_usdt_readarg helpers with the same
                    # name when enabling more than one USDT probe.
                    new_func = "trace_count_%d" % self.matched
                    bpf_text += trace_count_text.replace(
                        "trace_count", new_func)
                    self.usdt.enable_probe(probe.name, new_func)
                    self.matched += 1
            if debug:
                print(self.usdt.get_text())
        else:
            bpf_text += trace_count_text

        if debug:
            print(bpf_text)

        # BPF
        self.bpf = BPF(text=bpf_text,
                       usdt_contexts=[self.usdt] if self.usdt else [])
Beispiel #3
0
                        default=1)
    parser.add_argument('-d',
                        type=int,
                        dest='duration_seconds',
                        help='Recording duration in seconds',
                        default=10)

    return parser


args = get_arg_parser().parse_args()

print(prog % (next_power_of_two(args.sampling_frequency) - 1))

pid = args.pid
usdt = USDT(path=args.lib_jvm_path, pid=args.pid)
usdt.enable_probe(probe="object__alloc", fn_name="trace_alloc")
bpf = BPF(text=prog % (next_power_of_two(args.sampling_frequency) - 1),
          usdt_contexts=[usdt])

time.sleep(float(str(args.duration_seconds)))

if len(bpf["tids"]) == 0:
    print("No data found - are DTrace probes enabled in running process?")

stack_traces = bpf["stack_traces"]
all_stacks = []
stack_counts = dict()

for k, v in bpf["counts"].iteritems():
    stack = []
Beispiel #4
0
    def test_attach1(self):
        # Enable USDT probe from given PID and verifier generated BPF programs.
        u = USDT(pid=int(self.app.pid))
        u.enable_probe(probe="probe_point_1", fn_name="do_trace1")
        u.enable_probe(probe="probe_point_2", fn_name="do_trace2")
        u2 = USDT(pid=int(self.app2.pid))
        u2.enable_probe(probe="probe_point_2", fn_name="do_trace2")
        u2.enable_probe(probe="probe_point_3", fn_name="do_trace3")
        self.bpf_text = self.bpf_text.replace("FILTER",
                                              "pid == %d" % self.app.pid)
        b = BPF(text=self.bpf_text, usdt_contexts=[u, u2])

        # Event states for each event:
        # 0 - probe not caught, 1 - probe caught with correct value,
        # 2 - probe caught with incorrect value
        self.evt_st_1 = 0
        self.evt_st_2 = 0
        self.evt_st_3 = 0
        self.evt_st_4 = 0
        self.evt_st_5 = 0
        self.evt_st_6 = 0

        def check_event_val(data, event_state, expected_val):
            result = ct.cast(data, ct.POINTER(ct.c_int)).contents
            if result.value == expected_val:
                if (event_state == 0 or event_state == 1):
                    return 1
            return 2

        def print_event1(cpu, data, size):
            self.evt_st_1 = check_event_val(data, self.evt_st_1, 1)

        def print_event2(cpu, data, size):
            self.evt_st_2 = check_event_val(data, self.evt_st_2, 2)

        def print_event3(cpu, data, size):
            self.evt_st_3 = check_event_val(data, self.evt_st_3, 3)

        def print_event4(cpu, data, size):
            self.evt_st_4 = check_event_val(data, self.evt_st_4, 11)

        def print_event5(cpu, data, size):
            self.evt_st_5 = check_event_val(data, self.evt_st_5, 12)

        def print_event6(cpu, data, size):
            self.evt_st_6 = check_event_val(data, self.evt_st_6, 13)

        # loop with callback to print_event
        b["event1"].open_perf_buffer(print_event1)
        b["event2"].open_perf_buffer(print_event2)
        b["event3"].open_perf_buffer(print_event3)
        b["event4"].open_perf_buffer(print_event4)
        b["event5"].open_perf_buffer(print_event5)
        b["event6"].open_perf_buffer(print_event6)

        # three iterations to make sure we get some probes and have time to process them
        for i in range(5):
            b.perf_buffer_poll()

        # note that event1 and event4 do not really fire, so their state should be 0
        # use separate asserts so that if test fails we know which one is the culprit
        self.assertTrue(self.evt_st_1 == 1)
        self.assertTrue(self.evt_st_2 == 1)
        self.assertTrue(self.evt_st_3 == 0)
        self.assertTrue(self.evt_st_4 == 0)
        self.assertTrue(self.evt_st_5 == 1)
        self.assertTrue(self.evt_st_6 == 1)
Beispiel #5
0
    info->num_calls += 1;
    info->total_ns += bpf_ktime_get_ns() - e->timestamp;
    sysentry.delete(&pid);
    return 0;
}
#endif  // LATENCY
#endif  // SYSCALLS
""".replace("READ_CLASS", read_class) \
   .replace("READ_METHOD", read_method) \
   .replace("PID_FILTER", "if ((pid >> 32) != %d) { return 0; }" % args.pid) \
   .replace("DEFINE_NOLANG", "#define NOLANG" if not language else "") \
   .replace("DEFINE_LATENCY", "#define LATENCY" if args.latency else "") \
   .replace("DEFINE_SYSCALLS", "#define SYSCALLS" if args.syscalls else "")

if language:
    usdt = USDT(pid=args.pid)
    usdt.enable_probe_or_bail(entry_probe, "trace_entry")
    if args.latency:
        usdt.enable_probe_or_bail(return_probe, "trace_return")
else:
    usdt = None

if args.verbose:
    if usdt:
        print(usdt.get_text())
    print(program)

bpf = BPF(text=program, usdt_contexts=[usdt] if usdt else [])
if args.syscalls:
    syscall_regex = "^[Ss]y[Ss]_.*"
    bpf.attach_kprobe(event_re=syscall_regex, fn_name="syscall_entry")
Beispiel #6
0
                        help='pid of process emitting probes')
    args = parser.parse_args()
    bpfs = dict()

    # These probes do not currently exist on any branch, and were used solely for my own testing.
    # You can use any probes that pass through a BSONObj size and a BSONObj char* in that order.
    probes = []#["query1", "query", "query1", "query1R", "queryR", "query2", "query3"]

    # necessary to have multiple bpfs, one for each object,
    # due to restriction on number of insns per bpf object.
    # copying out long strings requires a large number of insns,
    # and duplicating that for each entrypt function is prohibitively
    # large in terms of number of isns.
    for probe in probes:
        print(probe)
        usdt = USDT(pid=args.pid[0])
        usdt.enable_probe(probe, fn_name="str_entry_{}".format(probe))
        bpfs[probe] = BPF(text=code.replace("#PROBE#", probe), usdt_contexts=[usdt])

    print(code)

    wrks = []
    for probe in probes:
        print(probe)
        bpf = bpfs[probe]
        bpf["str_out_{}".format(probe)].open_perf_buffer(gen_callback(probe, bpfs))
        wrks.append(WorkerThread(gen_work(probe, bpfs)))

    master = WorkerMaster(wrks)
    master.start_all()
    print("Ready")
Beispiel #7
0
int endQueryOp(struct pt_regs *ctx) {
    struct endQueryOutput out = {};
    void* summaryPtr = NULL;
    out.opCtx = NULL;
    bpf_usdt_readarg(1, ctx, &out.opCtx);
    if(!out.opCtx) return 0;
    bpf_usdt_readarg(2, ctx, &summaryPtr); 
    if(!summaryPtr) return 0;
    bpf_probe_read(&out.summaryStats, sizeof(out.summaryStats), summaryPtr);
    bpf_usdt_readarg(3, ctx, &out.numResults);
    endQuery.perf_submit(ctx, &out, sizeof(out));
    return 0;
}
"""

probe = USDT(int(sys.argv[1]))
probe.enable_probe(probe="findCmdPlan", fn_name="findCmdPlan")

toAgg = USDT(int(sys.argv[1]))
toAgg.enable_probe(probe="findToAgg", fn_name="findCmdToAgg")

# beginQuery = USDT(int(sys.argv[1]))
probe.enable_probe(probe="beginQueryOp", fn_name="beginQueryOp")
probe.enable_probe(probe="endQueryOp", fn_name="endQueryOp")


class BeginQuery(ct.Structure):
    _fields_ = [("nss", ct.c_char * 50), ("bson", ct.c_byte * 100),
                ("queryObjSz", ct.c_uint), ("nreturn", ct.c_longlong),
                ("nskip", ct.c_longlong)]
Beispiel #8
0
#!/usr/bin/env python3

from bcc import BPF, USDT
import sys

bpf_source = """
#include <uapi/linux/ptrace.h>

int trace_binary_exec(struct pt_regs *ctx) {
    u64 pid = bpf_get_current_pid_tgid();
    bpf_trace_printk("New process running with PID %d\\n", pid);
    return 0;
}
"""

usdt_ctx = USDT(pid=int(sys.argv[1]))
usdt_ctx.enable_probe(probe="probe-main", fn_name="trace_binary_exec")
bpf = BPF(text=bpf_source, usdt_contexts=[usdt_ctx], debug=4)
bpf.trace_print()
Beispiel #9
0
 def enable_usdt_probe(self, probe_name, fn_name):
     if not self.usdt_ctx:
         self.usdt_ctx = USDT(pid=self.args.pid)
     self.usdt_ctx.enable_probe(probe_name, fn_name)
Beispiel #10
0
from bcc import BPF, USDT

bpf_source = """
#include<uapi/linux/ptrace.h>
int trace_binary_exec(struct pt_regs *ctx) {
  u64 pid = bpf_get_current_pid_tgid();
  bpf_trace_printk("New hello-bpf process running with PID: %d\n", pid);
}
"""
usdt = USDT(path="./test")
usdt.enable_probe(probe="probe-main", fn_name="trace_binary_exec")

bpf = BPF(text= bpf_source, usdt_contexts= usdt)

bpf.trace_print()
Beispiel #11
0
def display_stats(commands, update_interval):
    previous_stats = {}

    while True:
        stats = commands.items()

        # :shrug:
        if stats.__str__() != previous_stats.__str__():
            commands.print_log2_hist("command strlen")

        time.sleep(update_interval)
        previous_stats = stats


if __name__ == '__main__':
    if len(sys.argv) < 2:
        print("usage: ... <memcached_pid> [<update_interval>]")
        sys.exit(1)

    memcached_pid = int(sys.argv[1])
    update_interval = float(sys.argv[2]) if len(sys.argv) >= 3 else 0.1

    u = USDT(pid=int(sys.argv[1]))
    u.enable_probe(probe='process__command__start', fn_name='command_start')

    b = BPF(text=bpf_text, usdt_contexts=[u])

    print('Waiting for commands...')
    display_stats(b.get_table('command_dist'), update_interval)
Beispiel #12
0
    def run_test(self):
        # Tests the validation:block_connected tracepoint by generating blocks
        # and comparing the values passed in the tracepoint arguments with the
        # blocks.
        # See https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#tracepoint-validationblock_connected

        class Block(ctypes.Structure):
            _fields_ = [
                ("hash", ctypes.c_ubyte * 32),
                ("height", ctypes.c_int),
                ("transactions", ctypes.c_int64),
                ("inputs", ctypes.c_int),
                ("sigops", ctypes.c_int64),
                ("duration", ctypes.c_uint64),
            ]

            def __repr__(self):
                return "ConnectedBlock(hash=%s height=%d, transactions=%d, inputs=%d, sigops=%d, duration=%d)" % (
                    bytes(self.hash[::-1]).hex(), self.height,
                    self.transactions, self.inputs, self.sigops, self.duration)

        # The handle_* function is a ctypes callback function called from C. When
        # we assert in the handle_* function, the AssertError doesn't propagate
        # back to Python. The exception is ignored. We manually count and assert
        # that the handle_* functions succeeded.
        BLOCKS_EXPECTED = 2
        blocks_checked = 0
        expected_blocks = list()

        self.log.info("hook into the validation:block_connected tracepoint")
        ctx = USDT(path=str(self.options.bitcoind))
        ctx.enable_probe(probe="validation:block_connected",
                         fn_name="trace_block_connected")
        bpf = BPF(text=validation_blockconnected_program,
                  usdt_contexts=[ctx],
                  debug=0)

        def handle_blockconnected(_, data, __):
            nonlocal expected_blocks, blocks_checked
            event = ctypes.cast(data, ctypes.POINTER(Block)).contents
            self.log.info(f"handle_blockconnected(): {event}")
            block = expected_blocks.pop(0)
            assert_equal(block["hash"], bytes(event.hash[::-1]).hex())
            assert_equal(block["height"], event.height)
            assert_equal(len(block["tx"]), event.transactions)
            assert_equal(len([tx["vin"] for tx in block["tx"]]), event.inputs)
            assert_equal(0, event.sigops)  # no sigops in coinbase tx
            # only plausibility checks
            assert (event.duration > 0)

            blocks_checked += 1

        bpf["block_connected"].open_perf_buffer(handle_blockconnected)

        self.log.info(f"mine {BLOCKS_EXPECTED} blocks")
        block_hashes = self.generatetoaddress(self.nodes[0], BLOCKS_EXPECTED,
                                              ADDRESS_BCRT1_UNSPENDABLE)
        for block_hash in block_hashes:
            expected_blocks.append(self.nodes[0].getblock(block_hash, 2))

        bpf.perf_buffer_poll(timeout=200)
        bpf.cleanup()

        self.log.info(f"check that we traced {BLOCKS_EXPECTED} blocks")
        assert_equal(BLOCKS_EXPECTED, blocks_checked)
        assert_equal(0, len(expected_blocks))
Beispiel #13
0
        switch(type) {
            case READ_CMD:
                read_out.increment(delta);
                break;
            case WRITE_CMD:
                write_out.increment(delta);
                break;
            default:
                break;
        }
    }
    return 0;
}
"""

command_start = USDT(pid=pid)
command_start.enable_probe(probe="commandStart", fn_name="command_start")
command_end = USDT(pid=pid)
command_end.enable_probe(probe="commandEnd", fn_name="command_end")

b = BPF(text=text, usdt_contexts=[command_start, command_end])


def main():
    try:
        print("Read time histogram:")
        b["read_out"].print_linear_hist("Elapsed time in ms")
        print("Write time histogram:")
        b["write_out"].print_linear_hist("Elapsed time in ms")
    except KeyboardInterrupt:
        exit()
Beispiel #14
0
    def test_flush(self):
        """ Tests the utxocache:flush tracepoint API.
            See https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#tracepoint-utxocacheflush"""

        self.log.info("test the utxocache:flush tracepoint API")
        self.log.info("hook into the utxocache:flush tracepoint")
        ctx = USDT(pid=self.nodes[0].process.pid)
        ctx.enable_probe(probe="utxocache:flush",
                         fn_name="trace_utxocache_flush")
        bpf = BPF(text=utxocache_flushes_program, usdt_contexts=[ctx], debug=0)

        # The handle_* function is a ctypes callback function called from C. When
        # we assert in the handle_* function, the AssertError doesn't propagate
        # back to Python. The exception is ignored. We manually count and assert
        # that the handle_* functions succeeded.
        EXPECTED_HANDLE_FLUSH_SUCCESS = 3
        handle_flush_succeeds = 0
        expected_flushes = list()

        def handle_utxocache_flush(_, data, __):
            nonlocal handle_flush_succeeds
            event = ctypes.cast(data, ctypes.POINTER(UTXOCacheFlush)).contents
            self.log.info(f"handle_utxocache_flush(): {event}")
            expected_flushes.remove({
                "mode": FLUSHMODE_NAME[event.mode],
                "for_prune": event.for_prune,
                "size": event.size
            })
            # sanity checks only
            assert (event.memory > 0)
            assert (event.duration > 0)
            handle_flush_succeeds += 1

        bpf["utxocache_flush"].open_perf_buffer(handle_utxocache_flush)

        self.log.info("stop the node to flush the UTXO cache")
        UTXOS_IN_CACHE = 2  # might need to be changed if the eariler tests are modified
        # A node shutdown causes two flushes. One that flushes UTXOS_IN_CACHE
        # UTXOs and one that flushes 0 UTXOs. Normally the 0-UTXO-flush is the
        # second flush, however it can happen that the order changes.
        expected_flushes.append({
            "mode": "ALWAYS",
            "for_prune": False,
            "size": UTXOS_IN_CACHE
        })
        expected_flushes.append({
            "mode": "ALWAYS",
            "for_prune": False,
            "size": 0
        })
        self.stop_node(0)

        bpf.perf_buffer_poll(timeout=200)
        bpf.cleanup()

        self.log.info("check that we don't expect additional flushes")
        assert_equal(0, len(expected_flushes))

        self.log.info("restart the node with -prune")
        self.start_node(0, ["-fastprune=1", "-prune=1"])

        BLOCKS_TO_MINE = 350
        self.log.info(f"mine {BLOCKS_TO_MINE} blocks to be able to prune")
        self.generate(self.wallet, BLOCKS_TO_MINE)

        self.log.info("test the utxocache:flush tracepoint API with pruning")
        self.log.info("hook into the utxocache:flush tracepoint")
        ctx = USDT(pid=self.nodes[0].process.pid)
        ctx.enable_probe(probe="utxocache:flush",
                         fn_name="trace_utxocache_flush")
        bpf = BPF(text=utxocache_flushes_program, usdt_contexts=[ctx], debug=0)
        bpf["utxocache_flush"].open_perf_buffer(handle_utxocache_flush)

        self.log.info(f"prune blockchain to trigger a flush for pruning")
        expected_flushes.append({"mode": "NONE", "for_prune": True, "size": 0})
        self.nodes[0].pruneblockchain(315)

        bpf.perf_buffer_poll(timeout=500)
        bpf.cleanup()

        self.log.info(
            f"check that we don't expect additional flushes and that the handle_* function succeeded"
        )
        assert_equal(0, len(expected_flushes))
        assert_equal(EXPECTED_HANDLE_FLUSH_SUCCESS, handle_flush_succeeds)
Beispiel #15
0
 def _find_usdt_probe(self):
     self.usdt = USDT(path=self.library, pid=Probe.pid)
     for probe in self.usdt.enumerate_probes():
         if probe.name == self.usdt_name:
             return  # Found it, will enable later
     self._bail("unrecognized USDT probe %s" % self.usdt_name)
Beispiel #16
0
    if (delta >= """ + str(min_ns) + """) {
        // populate and emit data struct
        struct data_t data = {.pid = pid, .ts = sp->ts, .delta = delta};
        bpf_probe_read(&data.query, sizeof(data.query), (void *)sp->query);
        events.perf_submit(ctx, &data, sizeof(data));
    }

    start_tmp.delete(&pid);

    return 0;
};

"""

# enable USDT probe from given PID
u = USDT(pid=pid)
u.enable_probe(probe="query__start", fn_name="do_start")
u.enable_probe(probe="query__done", fn_name="do_done")
if debug:
    print(u.get_text())
    print(bpf_text)

# initialize BPF
b = BPF(text=bpf_text, usdt_contexts=[u])

# header
print("Tracing MySQL server queries for PID %d slower than %s ms..." %
      (pid, min_ms_text))
print("%-14s %-6s %8s %s" % ("TIME(s)", "PID", "MS", "QUERY"))

Beispiel #17
0
    return 0;
}
"""

bpf_text = bpf_text.replace("MAX_SAMPLE_SIZE", str(this_maxsamplesize))
bpf_text = bpf_text.replace("FILTER_STRING", this_filter)
if this_filter:
    bpf_text = bpf_text.replace(
        "FILTER_STATEMENT", "if (!filter(start_data.input)) { return 0; }")
else:
    bpf_text = bpf_text.replace("FILTER_STATEMENT", "")

# Create USDT context
print("lat_avg.py - Attaching probes to pid: %d; filter: %s" %
      (this_pid, this_filter))
usdt_ctx = USDT(pid=this_pid)

if args.sdt:
    usdt_ctx.enable_probe(probe="usdt_sample_lib1_sdt:operation_start_sdt",
                          fn_name="trace_operation_start")
    usdt_ctx.enable_probe(probe="usdt_sample_lib1_sdt:operation_end_sdt",
                          fn_name="trace_operation_end")
else:
    usdt_ctx.enable_probe(probe="usdt_sample_lib1:operation_start",
                          fn_name="trace_operation_start")
    usdt_ctx.enable_probe(probe="usdt_sample_lib1:operation_end",
                          fn_name="trace_operation_end")

# Create BPF context, load BPF program
bpf_ctx = BPF(text=bpf_text, usdt_contexts=[usdt_ctx], debug=debugLevel)
Beispiel #18
0
from bcc import BPF, USDT

bpf_source = """
#include <uapi/linux/ptrace.h>
int trace_binary_exec(struct pt_regs *ctx) {
  u64 pid = bpf_get_current_pid_tgid();
  bpf_trace_printk("New hello_usdt process running with PID: %d", pid);
}
"""

usdt = USDT(path="./ha")
usdt.enable_probe(probe='probe-main', fn_name="trace_binary_exec")

bpf = BPF(text=bpf_source, usdt=usdt)
bpf.trace_print()
Beispiel #19
0
    uint64_t addr;
    char query[128];
    /*
     * Read the first argument from the query-start probe, which is the query.
     * The format of this probe is:
     * query-start(query, connectionid, database, user, host)
     * see: https://dev.mysql.com/doc/refman/5.7/en/dba-dtrace-ref-query.html
     */
    bpf_usdt_readarg(1, ctx, &addr);
    bpf_trace_printk("%s\\n", addr);
    return 0;
};
"""

# enable USDT probe from given PID
u = USDT(pid=int(pid))
u.enable_probe(probe="query__start", fn_name="do_trace")
if debug:
    print(u.get_text())
    print(bpf_text)

# initialize BPF
b = BPF(text=bpf_text, usdt_contexts=[u])

# header
print("%-18s %-16s %-6s %s" % ("TIME(s)", "COMM", "PID", "QUERY"))

# format output
while 1:
    try:
        (task, pid, cpu, flags, ts, msg) = b.trace_fields()
Beispiel #20
0
    def load(self):
        ctx_name = "ctx"
        stack_trace = ""
        if self.user_stack:
            stack_trace += """
                    key.user_stack_id = stack_traces.get_stackid(
                      %s, BPF_F_REUSE_STACKID | BPF_F_USER_STACK
                    );""" % (ctx_name)
        else:
            stack_trace += "key.user_stack_id = -1;"
        if self.kernel_stack:
            stack_trace += """
                    key.kernel_stack_id = stack_traces.get_stackid(
                      %s, BPF_F_REUSE_STACKID
                    );""" % (ctx_name)
        else:
            stack_trace += "key.kernel_stack_id = -1;"

        trace_count_text = """
int trace_count(void *ctx) {
    FILTER
    struct key_t key = {};
    key.tgid = GET_TGID;
    STORE_COMM
    %s
    u64 zero = 0;
    u64 *val = counts.lookup_or_init(&key, &zero);
    (*val)++;
    return 0;
}
        """
        trace_count_text = trace_count_text % (stack_trace)

        bpf_text = """#include <uapi/linux/ptrace.h>
#include <linux/sched.h>

struct key_t {
    // no pid (thread ID) so that we do not needlessly split this key
    u32 tgid;
    int kernel_stack_id;
    int user_stack_id;
    char name[TASK_COMM_LEN];
};

BPF_HASH(counts, struct key_t);
BPF_STACK_TRACE(stack_traces, 1024);
        """

        # We really mean the tgid from the kernel's perspective, which is in
        # the top 32 bits of bpf_get_current_pid_tgid().
        if self.is_kernel_probe() and self.pid:
            trace_count_text = trace_count_text.replace(
                'FILTER',
                ('u32 pid; pid = bpf_get_current_pid_tgid() >> 32; ' +
                 'if (pid != %d) { return 0; }') % (self.pid))
        else:
            trace_count_text = trace_count_text.replace('FILTER', '')

        # We need per-pid statistics when tracing a user-space process, because
        # the meaning of the symbols depends on the pid. We also need them if
        # per-pid statistics were requested with -P, or for user stacks.
        if self.per_pid or not self.is_kernel_probe() or self.user_stack:
            trace_count_text = trace_count_text.replace(
                'GET_TGID', 'bpf_get_current_pid_tgid() >> 32')
            trace_count_text = trace_count_text.replace(
                'STORE_COMM',
                'bpf_get_current_comm(&key.name, sizeof(key.name));')
        else:
            # kernel stacks only. skip splitting on PID so these aggregate
            # together, and don't store the process name.
            trace_count_text = trace_count_text.replace(
                'GET_TGID', '0xffffffff')
            trace_count_text = trace_count_text.replace('STORE_COMM', '')

        self.usdt = None
        if self.type == "u":
            self.usdt = USDT(path=self.library, pid=self.pid)
            for probe in self.usdt.enumerate_probes():
                if not self.pid and (probe.bin_path != self.library):
                    continue
                if re.match(self.pattern, probe.name):
                    # This hack is required because the bpf_usdt_readarg
                    # functions generated need different function names for
                    # each attached probe. If we just stick to trace_count,
                    # we'd get multiple bpf_usdt_readarg helpers with the same
                    # name when enabling more than one USDT probe.
                    new_func = "trace_count_%d" % self.matched
                    bpf_text += trace_count_text.replace(
                        "trace_count", new_func)
                    self.usdt.enable_probe(probe.name, new_func)
                    self.matched += 1
            if debug:
                print(self.usdt.get_text())
        else:
            bpf_text += trace_count_text

        if debug:
            print(bpf_text)
        self.bpf = BPF(text=bpf_text,
                       usdt_contexts=[self.usdt] if self.usdt else [])
Beispiel #21
0
    u64 delta = bpf_ktime_get_ns() - tempp->timestamp;
    if (delta >= """ + str(threshold_ns) + """) {
        struct data_t data = {};
        data.pid = pid >> 32;   // only process id
        data.timestamp = tempp->timestamp;
        data.duration = delta;
        bpf_probe_read(&data.query, sizeof(data.query), tempp->query);
        events.perf_submit(ctx, &data, sizeof(data));
    }
    temp.delete(&pid);
    return 0;
}
"""

usdts = map(lambda pid: USDT(pid=pid), args.pids)
for usdt in usdts:
    usdt.enable_probe("query__start", "probe_start")
    usdt.enable_probe("query__done", "probe_end")

bpf = BPF(text=program, usdt_contexts=usdts)
if args.verbose:
    print('\n'.join(map(lambda u: u.get_text(), usdts)))
    print(program)


class Data(ct.Structure):
    _fields_ = [("pid", ct.c_ulonglong), ("timestamp", ct.c_ulonglong),
                ("delta", ct.c_ulonglong), ("query", ct.c_char * 256)]

Beispiel #22
0
    def load(self):
        ctx_name = "ctx"
        stack_trace = ""
        if self.user_stack:
            stack_trace += """
            key.user_stack_id = stack_traces.get_stackid(
            %s, BPF_F_USER_STACK
            );""" % (ctx_name)
        else:
            stack_trace += "key.user_stack_id = -1;"
        if self.kernel_stack:
            stack_trace += """
            key.kernel_stack_id = stack_traces.get_stackid(
            %s, 0
            );""" % (ctx_name)
        else:
            stack_trace += "key.kernel_stack_id = -1;"

        trace_count_text = """
int trace_count(void *ctx) {
    FILTER
    struct key_t key = {};
    key.tgid = GET_TGID;
    STORE_COMM
    %s
    counts.increment(key);
    return 0;
}
        """
        trace_count_text = trace_count_text % (stack_trace)

        bpf_text = """#include <uapi/linux/ptrace.h>
#include <linux/sched.h>

struct key_t {
    // no pid (thread ID) so that we do not needlessly split this key
    u32 tgid;
    int kernel_stack_id;
    int user_stack_id;
    char name[TASK_COMM_LEN];
};

BPF_HASH(counts, struct key_t);
BPF_STACK_TRACE(stack_traces, 1024);
        """

        filter_text = []
        trace_count_text = trace_count_text.replace('FILTER',
                                                    '\n    '.join(filter_text))

        # Do per-pid statistics iff -P is provided
        if self.per_pid:
            trace_count_text = trace_count_text.replace(
                'GET_TGID', 'bpf_get_current_pid_tgid() >> 32')
            trace_count_text = trace_count_text.replace(
                'STORE_COMM',
                'bpf_get_current_comm(&key.name, sizeof(key.name));')
        else:
            # skip splitting on PID so these aggregate
            # together, and don't store the process name.
            trace_count_text = trace_count_text.replace(
                'GET_TGID', '0xffffffff')
            trace_count_text = trace_count_text.replace('STORE_COMM', '')

        if self.type == "u":
            self.usdt = USDT(path=self.library, pid=self.pid)
            for probe in self.usdt.enumerate_probes():
                if not self.pid and (str(probe.bin_path, 'ascii') !=
                                     self.library):
                    continue
                parts = self.pattern.split(":")
                if len(parts) == 1:
                    provider_name = None
                    usdt_name = parts[0].encode("ascii")
                else:
                    provider_name = parts[0]
                    usdt_name = parts[1]
                if (str(probe.name, 'ascii') == usdt_name
                        and str(probe.provider, 'ascii') == provider_name):
                    # This hack is required because the bpf_usdt_readarg
                    # functions generated need different function names for
                    # each attached probe. If we just stick to trace_count,
                    # we'd get multiple bpf_usdt_readarg helpers with the same
                    # name when enabling more than one USDT probe.
                    new_func = "trace_count_%d" % self.matched
                    bpf_text += trace_count_text.replace(
                        "trace_count", new_func)
                    self.usdt.enable_probe(str(probe.name, 'ascii'), new_func)
                    self.matched += 1
            if DEBUG:
                print(self.usdt.get_text())
        else:
            bpf_text += trace_count_text

        if DEBUG:
            print(bpf_text)
        self.bpf = BPF(text=bpf_text,
                       usdt_contexts=[self.usdt] if self.usdt else [])
Beispiel #23
0
    timestampp = temp.lookup(&pid);
    if (!timestampp)
        return 0;

    u64 delta = bpf_ktime_get_ns() - *timestampp;
    if (delta/1000000 < %d)
        return 0;

    delta /= %d;
    latency.increment(bpf_log2l(delta));
    temp.delete(&pid);
    return 0;
}
""" % (args.threshold, 1000000 if args.milliseconds else 1000)

usdt = USDT(pid=int(dbpid))
usdt.enable_probe("query__start", "probe_start")
usdt.enable_probe("query__done", "probe_end")

bpf = BPF(text=program, usdt_contexts=[usdt])
if args.verbose:
    print(usdt.get_text())
    print(program)

print(
    "Tracing database queries slower than %dms for PID %d... Ctrl+C to quit." %
    (args.threshold, dbpid))

try:
    sleep(999999999999)
except KeyboardInterrupt:
Beispiel #24
0
    bpf = BPF(text=program)
    bpf.attach_uprobe(name=args.path, sym=mysql_func_name,
                      fn_name="query_start")
    bpf.attach_uretprobe(name=args.path, sym=mysql_func_name,
                         fn_name="query_end")
else:
    # USDT mode
    if not args.pids or len(args.pids) == 0:
        if args.db == "mysql":
            args.pids = map(int, subprocess.check_output(
                                            "pidof mysqld".split()).split())
        elif args.db == "postgres":
            args.pids = map(int, subprocess.check_output(
                                            "pidof postgres".split()).split())

    usdts = list(map(lambda pid: USDT(pid=pid), args.pids))
    for usdt in usdts:
        usdt.enable_probe("query__start", "query_start")
        usdt.enable_probe("query__done", "query_end")
    if args.verbose:
        print('\n'.join(map(lambda u: u.get_text(), usdts)))

    bpf = BPF(text=program, usdt_contexts=usdts)

if args.verbose or args.ebpf:
    print(program)
    if args.ebpf:
        exit()

start = BPF.monotonic_time()
Beispiel #25
0
    def test_attach1(self):
        # enable USDT probe from given PID and verifier generated BPF programs
        u = USDT(pid=int(self.app.pid))
        u.enable_probe(probe="probe_point_1", fn_name="do_trace1")
        u.enable_probe(probe="probe_point_2", fn_name="do_trace2")
        u.enable_probe(probe="probe_point_3", fn_name="do_trace3")
        u.enable_probe(probe="probe_point_4", fn_name="do_trace4")
        u.enable_probe(probe="probe_point_5", fn_name="do_trace5")
        b = BPF(text=self.bpf_text, usdt_contexts=[u], debug=4)

        # Event states for each event:
        # 0 - probe not caught, 1 - probe caught with correct value,
        # 2 - probe caught with incorrect value
        self.evt_st_1 = 0
        self.evt_st_2 = 0
        self.evt_st_3 = 0

        # define output data structure in Python
        class Data1(ct.Structure):
            _fields_ = [("v1", ct.c_char), ("v2", ct.c_int)]

        class Data2(ct.Structure):
            _fields_ = [("v1", ct.c_int), ("v2", ct.c_char)]

        class Data3(ct.Structure):
            _fields_ = [("v1", ct.c_int), ("v2", ct.c_int)]

        class Data4(ct.Structure):
            _fields_ = [("v1", ct.c_ulonglong), ("v2", ct.c_char * 64)]

        class Data5(ct.Structure):
            _fields_ = [("v1", ct.c_char * 64), ("v2", ct.c_ulonglong)]

        def check_event_val(event, event_state, v1, v2, v3, v4):
            if ((event.v1 == v1 and event.v2 == v2)
                    or (event.v1 == v3 and event.v2 == v4)):
                if (event_state == 0 or event_state == 1):
                    return 1
            return 2

        def print_event1(cpu, data, size):
            event = ct.cast(data, ct.POINTER(Data1)).contents
            self.evt_st_1 = check_event_val(event, self.evt_st_1, b'\x0d', 40,
                                            b'\x08', 200)

        def print_event2(cpu, data, size):
            event = ct.cast(data, ct.POINTER(Data2)).contents
            # pretend we have two identical probe points to simplify the code
            self.evt_st_2 = check_event_val(event, self.evt_st_2, 5, b'\x04',
                                            5, b'\x04')

        def print_event3(cpu, data, size):
            event = ct.cast(data, ct.POINTER(Data3)).contents
            self.evt_st_3 = check_event_val(event, self.evt_st_3, 200, 40, 8,
                                            13)

        def print_event4(cpu, data, size):
            event = ct.cast(data, ct.POINTER(Data4)).contents
            print("%s" % event.v2)

        def print_event5(cpu, data, size):
            event = ct.cast(data, ct.POINTER(Data5)).contents
            print("%s" % event.v1)

        # loop with callback to print_event
        b["event1"].open_perf_buffer(print_event1)
        b["event2"].open_perf_buffer(print_event2)
        b["event3"].open_perf_buffer(print_event3)
        b["event4"].open_perf_buffer(print_event4)
        b["event5"].open_perf_buffer(print_event5)

        # three iterations to make sure we get some probes and have time to process them
        for i in range(3):
            b.perf_buffer_poll()
        self.assertTrue(self.evt_st_1 == 1 and self.evt_st_2 == 1
                        and self.evt_st_3 == 1)
Beispiel #26
0
    def run_test(self):
        # Tests the net:inbound_message and net:outbound_message tracepoints
        # See https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#context-net

        class P2PMessage(ctypes.Structure):
            _fields_ = [
                ("peer_id", ctypes.c_uint64),
                ("peer_addr", ctypes.c_char * MAX_PEER_ADDR_LENGTH),
                ("peer_conn_type", ctypes.c_char * MAX_PEER_CONN_TYPE_LENGTH),
                ("msg_type", ctypes.c_char * MAX_MSG_TYPE_LENGTH),
                ("msg_size", ctypes.c_uint64),
                ("msg", ctypes.c_ubyte * MAX_MSG_DATA_LENGTH),
            ]

            def __repr__(self):
                return f"P2PMessage(peer={self.peer_id}, addr={self.peer_addr.decode('utf-8')}, conn_type={self.peer_conn_type.decode('utf-8')}, msg_type={self.msg_type.decode('utf-8')}, msg_size={self.msg_size})"

        self.log.info(
            "hook into the net:inbound_message and net:outbound_message tracepoints"
        )
        ctx = USDT(path=str(self.options.bitcoind))
        ctx.enable_probe(probe="net:inbound_message",
                         fn_name="trace_inbound_message")
        ctx.enable_probe(probe="net:outbound_message",
                         fn_name="trace_outbound_message")
        bpf = BPF(text=net_tracepoints_program, usdt_contexts=[ctx], debug=0)

        # The handle_* function is a ctypes callback function called from C. When
        # we assert in the handle_* function, the AssertError doesn't propagate
        # back to Python. The exception is ignored. We manually count and assert
        # that the handle_* functions succeeded.
        EXPECTED_INOUTBOUND_VERSION_MSG = 1
        checked_inbound_version_msg = 0
        checked_outbound_version_msg = 0

        def check_p2p_message(event, inbound):
            nonlocal checked_inbound_version_msg, checked_outbound_version_msg
            if event.msg_type.decode("utf-8") == "version":
                self.log.info(
                    f"check_p2p_message(): {'inbound' if inbound else 'outbound'} {event}"
                )
                peer = self.nodes[0].getpeerinfo()[0]
                msg = msg_version()
                msg.deserialize(BytesIO(bytes(event.msg[:event.msg_size])))
                assert_equal(peer["id"], event.peer_id, peer["id"])
                assert_equal(peer["addr"], event.peer_addr.decode("utf-8"))
                assert_equal(peer["connection_type"],
                             event.peer_conn_type.decode("utf-8"))
                if inbound:
                    checked_inbound_version_msg += 1
                else:
                    checked_outbound_version_msg += 1

        def handle_inbound(_, data, __):
            event = ctypes.cast(data, ctypes.POINTER(P2PMessage)).contents
            check_p2p_message(event, True)

        def handle_outbound(_, data, __):
            event = ctypes.cast(data, ctypes.POINTER(P2PMessage)).contents
            check_p2p_message(event, False)

        bpf["inbound_messages"].open_perf_buffer(handle_inbound)
        bpf["outbound_messages"].open_perf_buffer(handle_outbound)

        self.log.info("connect a P2P test node to our bitcoind node")
        test_node = P2PInterface()
        self.nodes[0].add_p2p_connection(test_node)
        bpf.perf_buffer_poll(timeout=200)

        self.log.info(
            "check that we got both an inbound and outbound version message")
        assert_equal(EXPECTED_INOUTBOUND_VERSION_MSG,
                     checked_inbound_version_msg)
        assert_equal(EXPECTED_INOUTBOUND_VERSION_MSG,
                     checked_outbound_version_msg)

        bpf.cleanup()
Beispiel #27
0
    "-DHHVM_TRACING_MAX_STACK_FRAMES={}".format(200),
    "-DHHVM_TRACING_FILE_NAME_MAX={}".format(128),
    "-DHHVM_TRACING_CLASS_NAME_MAX={}".format(128),
    "-DHHVM_TRACING_FUNCTION_MAX={}".format(128),
]

bpf_text = ""
with open("../strobelight_hhvm_structs.h", "r") as structs_file:
    bpf_text += structs_file.read() + "\n"

with open("./strobelight_hhvm_signal_latency.c", "r") as latency_file:
    bpf_text += latency_file.read()

usdts = []
for pid in args.pids:
    usdt = USDT(pid=pid)
    usdt.enable_probe("hhvm_surprise", "on_hhvm_signal_handler")
    print("Enabled tracing on {}\n".format(pid))
    usdts.append(usdt)

bpf = BPF(text=bpf_text,
          usdt_contexts=usdts,
          cflags=cflags)

hhvm_pids = bpf.get_table("hhvm_pids")
for pid in args.pids:
    hhvm_pids[c_int(pid)] = c_int(1)

# A generic perf event for driving the signal
bpf.attach_perf_event(
    ev_type=PerfType.HARDWARE,
Beispiel #28
0
import os
from bcc import USDT
import ctypes as ct
from ctypes.util import find_library
from typing import get_type_hints, List, Any

from ebph import defs
from ebph.logger import get_logger

logger = get_logger()

libebph = ct.CDLL(defs.LIBEBPH, use_errno=True)

usdt_context = USDT(pid=os.getpid())


def command(func):
    """
    A decorator that allows a function to provide an interface into a libebph
    command of the same name. Types are determined using Python type hints.
    """
    name = func.__name__
    th = get_type_hints(func)
    argtypes = [v for k, v in th.items() if k != 'return']
    try:
        restype = th['return']
    except KeyError:
        restype = None

    @staticmethod
    def wrapper(*args, **kwargs):
Beispiel #29
0
 def _enable_usdt_probe(self):
         self.usdt_ctx = USDT(path=self.library, pid=self.pid)
         self.usdt_ctx.enable_probe(
                 self.function, self.probe_func_name)
Beispiel #30
0
    def test_uncache(self):
        """ Tests the utxocache:uncache tracepoint API.
        https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#tracepoint-utxocacheuncache
        """
        # To trigger an UTXO uncache from the cache, we create an invalid transaction
        # spending a not-cached, but existing UTXO. During transaction validation, this
        # the UTXO is added to the utxo cache, but as the transaction is invalid, it's
        # uncached again.
        self.log.info("testing the utxocache:uncache tracepoint API")

        # Retrieve the txid for the UTXO created in the first block. This UTXO is not
        # in our UTXO cache.
        EARLY_BLOCK_HEIGHT = 1
        block_1_hash = self.nodes[0].getblockhash(EARLY_BLOCK_HEIGHT)
        block_1 = self.nodes[0].getblock(block_1_hash)
        block_1_coinbase_txid = block_1["tx"][0]

        # Create a transaction and invalidate it by changing the txid of the previous
        # output to the coinbase txid of the block at height 1.
        invalid_tx = self.wallet.create_self_transfer()["tx"]
        invalid_tx.vin[0].prevout.hash = int(block_1_coinbase_txid, 16)

        self.log.info("hooking into the utxocache:uncache tracepoint")
        ctx = USDT(pid=self.nodes[0].process.pid)
        ctx.enable_probe(probe="utxocache:uncache",
                         fn_name="trace_utxocache_uncache")
        bpf = BPF(text=utxocache_changes_program, usdt_contexts=[ctx], debug=0)

        # The handle_* function is a ctypes callback function called from C. When
        # we assert in the handle_* function, the AssertError doesn't propagate
        # back to Python. The exception is ignored. We manually count and assert
        # that the handle_* functions succeeded.
        EXPECTED_HANDLE_UNCACHE_SUCCESS = 1
        handle_uncache_succeeds = 0

        def handle_utxocache_uncache(_, data, __):
            nonlocal handle_uncache_succeeds
            event = ctypes.cast(data, ctypes.POINTER(UTXOCacheChange)).contents
            self.log.info(f"handle_utxocache_uncache(): {event}")
            assert_equal(block_1_coinbase_txid, bytes(event.txid[::-1]).hex())
            assert_equal(0, event.index)  # prevout index
            assert_equal(EARLY_BLOCK_HEIGHT, event.height)
            assert_equal(50 * COIN, event.value)
            assert_equal(True, event.is_coinbase)

            handle_uncache_succeeds += 1

        bpf["utxocache_uncache"].open_perf_buffer(handle_utxocache_uncache)

        self.log.info(
            "testmempoolaccept the invalid transaction to trigger an UTXO-cache uncache"
        )
        result = self.nodes[0].testmempoolaccept(
            [invalid_tx.serialize().hex()])[0]
        assert_equal(result["allowed"], False)

        bpf.perf_buffer_poll(timeout=100)
        bpf.cleanup()

        self.log.info(
            f"check that we successfully traced {EXPECTED_HANDLE_UNCACHE_SUCCESS} uncaches"
        )
        assert_equal(EXPECTED_HANDLE_UNCACHE_SUCCESS, handle_uncache_succeeds)