[DTrace-devel] [PATCH v2] pid: Support probe names specifying offsets
Kris Van Hees
kris.van.hees at oracle.com
Fri Jan 26 01:59:29 UTC 2024
On Thu, Nov 02, 2023 at 06:39:41PM -0400, eugene.loh at oracle.com wrote:
>
> In DTrace, a pid-provider probe name can be "entry" or "return".
> The probe name can also specify offsets, including wildcards (globs).
>
> Add support for "offset" probes.
>
> Note that offset used for the pid probe name is different from that used
> for the underlying uprobe. So introduce a new field pps_nameoff that
> allows us to track both offsets.
>
> Also, the "float" trigger when launched with "before" timing will
> presumably never fire the entry or return probes. So, tst.emptystack.d
> ought perhaps to be split into a entry/return test and an offset test.
>
> Signed-off-by: Eugene Loh <eugene.loh at oracle.com>
> ---
> dtrace.spec | 4 +
> include/dtrace/pid.h | 7 +-
> libcommon/uprobes.c | 2 +-
> libdtrace/Build | 15 +-
> libdtrace/dt_pid.c | 175 +++++++++++++++----
> libdtrace/dt_prov_uprobe.c | 6 +-
> test/unittest/pid/tst.emptystack.d | 1 -
> test/unittest/pid/tst.float.d | 17 +-
> test/unittest/pid/tst.offsets.r | 1 +
> test/unittest/pid/tst.offsets.sh | 270 +++++++++++++++++++++++++++++
> test/unittest/pid/tst.probefunc.d | 3 +-
> 11 files changed, 442 insertions(+), 59 deletions(-)
> create mode 100644 test/unittest/pid/tst.offsets.r
> create mode 100755 test/unittest/pid/tst.offsets.sh
>
> diff --git a/dtrace.spec b/dtrace.spec
> index 777dd28d..dd32c6dd 100644
> --- a/dtrace.spec
> +++ b/dtrace.spec
> @@ -74,6 +74,10 @@ BuildRequires: kernel%{variant}-devel = 5.15.0-0.16.2%{?dist}uek
> %endif
> BuildRequires: gcc-bpf-unknown-none
> BuildRequires: binutils-bpf-unknown-none
> +%ifnarch aarch64
> +Requires: binutils
> +BuildRequires: binutils-devel
> +%endif
> %if %{with_libctf}
> Requires: binutils >= 2.30-58.0.8
> BuildRequires: binutils-devel >= 2.30-58.0.8
> diff --git a/include/dtrace/pid.h b/include/dtrace/pid.h
> index 5e966825..8157e37c 100644
> --- a/include/dtrace/pid.h
> +++ b/include/dtrace/pid.h
> @@ -38,13 +38,10 @@ typedef struct pid_probespec {
> uint64_t pps_off; /* probe offset (in object) */
>
> /*
> - * Fields below this point do not apply to probes of type
> - * DTPPT_UNDERLYING.
> + * Fields below this point do not apply to underlying probes.
> */
> pid_t pps_pid; /* task PID */
> - uint64_t pps_vaddr; /* object base address */
> - uint64_t pps_size; /* function size (in bytes) */
> - char pps_gstr[1]; /* glob pattern string */
> + uint64_t pps_nameoff; /* offset to use for name */
> } pid_probespec_t;
>
> #endif /* _DTRACE_PID_H */
> diff --git a/libcommon/uprobes.c b/libcommon/uprobes.c
> index e910b9e6..dd61c748 100644
> --- a/libcommon/uprobes.c
> +++ b/libcommon/uprobes.c
> @@ -301,7 +301,7 @@ out:
> }
>
> /*
> - * Like uprobe_create, but do not specify the name of a corresponding DTrace
> + * Like uprobe_create_named, but do not specify the name of a corresponding DTrace
> * probe. (Used when the caller already knows what probe will be needed, and
> * there is no possibility of another DTrace having to pick it up from the
> * systemwide uprobe list.)
> diff --git a/libdtrace/Build b/libdtrace/Build
> index cc75d3c9..4fc6eb40 100644
> --- a/libdtrace/Build
> +++ b/libdtrace/Build
> @@ -78,16 +78,22 @@ SHLIBS += libdtrace
> libdtrace_DIR := $(current-dir)
> libdtrace_TARGET = libdtrace
> ifdef HAVE_LIBCTF
> -libdtrace_LIBS := -lctf -lelf -lz -lrt -lpcap -lpthread -ldl -lm -lpfm
> +libdtrace_LIBS := -lctf
> else
> -libdtrace_LIBS := -ldtrace-ctf -lelf -lz -lrt -lpcap -lpthread -ldl -lm -lpfm
> +libdtrace_LIBS := -ldtrace-ctf
> endif
> +libdtrace_LIBS += -lelf -lz -lrt -lpcap -lpthread -ldl -lm -lpfm
> libdtrace_VERSION := 2.0.0
> libdtrace_SONAME := libdtrace.so.2
> libdtrace_VERSCRIPT := libdtrace.ver
> libdtrace_LIBSOURCES := libdtrace-build libproc libport libcommon
> libdtrace_SECONDARY := libproc libport
>
> +# Required for dt_pid.c for varying instruction lengths on x86
> +ifeq ($(shell uname -m), x86_64)
> +libdtrace_LIBS += -lopcodes
> +endif
> +
> # Disable certain warnings for these files
> dt_consume.c_CFLAGS := -Wno-pedantic
> dt_debug.c_CFLAGS := -Wno-prio-ctor-dtor
> @@ -111,6 +117,11 @@ dt_prov_uprobe.c_CFLAGS := -Wno-pedantic
> dt_debug.c_CFLAGS := -Wno-prio-ctor-dtor
> drti.c_CFLAGS := -Wno-prio-ctor-dtor
>
> +# We call libopcodes's disassembler(). Its interface changed in binutils 2.29.
> +# So define macros for binutils major and minor version numbers.
> +dt_pid.c_CFLAGS += -DBINUTILSMAJ=$(shell ld -v | awk '{sub("\\..*", ""); sub("^.* ", ""); print}')
> +dt_pid.c_CFLAGS += -DBINUTILSMIN=$(shell ld -v | awk '{sub(".* [0-9]*\\.", ""); sub("[\\.-].*", ""); print}')
> +
> SHORTKERNELS := $(foreach kernel,$(KERNELS),$(shell printf %s $(kernel) | sed -e 's/\([0-9]*\)\.\([0-9]*\)\.\([0-9]*\).*$$/\1.\2.\3/'))
>
> -include libdtrace/$(ARCHINC)/Build
> diff --git a/libdtrace/dt_pid.c b/libdtrace/dt_pid.c
> index b0689aa8..9d136c76 100644
> --- a/libdtrace/dt_pid.c
> +++ b/libdtrace/dt_pid.c
> @@ -9,6 +9,7 @@
> #include <string.h>
> #include <stdlib.h>
> #include <stdio.h>
> +#include <stdbool.h>
> #include <errno.h>
> #include <ctype.h>
> #include <alloca.h>
> @@ -16,6 +17,9 @@
> #include <stddef.h>
> #include <sys/ioctl.h>
> #include <sys/sysmacros.h>
> +#if defined(__amd64)
> +#include <dis-asm.h>
> +#endif
>
> #include <mutex.h>
> #include <port.h>
> @@ -116,19 +120,15 @@ dt_pid_free_uprobespecs(dtrace_hdl_t *dtp)
> }
>
> static int
> -dt_pid_create_fbt_probe(struct ps_prochandle *P, dtrace_hdl_t *dtp,
> +dt_pid_create_one_probe(struct ps_prochandle *P, dtrace_hdl_t *dtp,
> pid_probespec_t *psp, const GElf_Sym *symp, pid_probetype_t type)
> {
> - const dt_provider_t *pvp;
> + const dt_provider_t *pvp = dtp->dt_prov_pid;
>
> - psp->pps_prv = "pid";
> psp->pps_type = type;
> - psp->pps_off = symp->st_value - psp->pps_vaddr;
> - psp->pps_size = (size_t)symp->st_size;
> - psp->pps_gstr[0] = '\0'; /* no glob pattern */
> + psp->pps_prv = "pid";
>
> /* Make sure we have a PID provider. */
> - pvp = dtp->dt_prov_pid;
> if (pvp == NULL) {
> pvp = dt_provider_lookup(dtp, psp->pps_prv);
> if (pvp == NULL)
> @@ -144,20 +144,13 @@ dt_pid_create_fbt_probe(struct ps_prochandle *P, dtrace_hdl_t *dtp,
> return pvp->impl->provide_probe(dtp, psp);
> }
>
> +#if defined(__amd64)
> static int
> -dt_pid_create_glob_offset_probes(struct ps_prochandle *P, dtrace_hdl_t *dtp,
> - pid_probespec_t *psp, const GElf_Sym *symp, const char *pattern)
> +my_callback(void *stream, const char *fmt, ...)
> {
> - psp->pps_type = DTPPT_OFFSETS;
> - psp->pps_off = symp->st_value - psp->pps_vaddr;
> - psp->pps_size = (size_t)symp->st_size;
> -
> - strcpy(psp->pps_gstr, pattern);
> -
> - /* Create a probe using 'psp'. */
> -
> - return 1;
> + return 0;
> }
> +#endif
>
> static int
> dt_pid_per_sym(dt_pid_probe_t *pp, const GElf_Sym *symp, const char *func)
> @@ -167,7 +160,6 @@ dt_pid_per_sym(dt_pid_probe_t *pp, const GElf_Sym *symp, const char *func)
> dt_proc_t *dpr = pp->dpp_dpr;
> pid_probespec_t *psp;
> uint64_t off;
> - char *end;
> uint_t nmatches = 0;
> ulong_t sz;
> int glob, rc = 0;
> @@ -183,7 +175,7 @@ dt_pid_per_sym(dt_pid_probe_t *pp, const GElf_Sym *symp, const char *func)
> dt_dprintf("creating probe pid%d:%s:%s:%s at %lx\n", (int)pid,
> pp->dpp_obj, func, pp->dpp_name, symp->st_value);
>
> - sz = sizeof(pid_probespec_t) + strlen(pp->dpp_name);
> + sz = sizeof(pid_probespec_t);
> psp = dt_zalloc(dtp, sz);
> if (psp == NULL) {
> dt_dprintf("proc_per_sym: dt_alloc(%lu) failed\n", sz);
> @@ -195,11 +187,12 @@ dt_pid_per_sym(dt_pid_probe_t *pp, const GElf_Sym *symp, const char *func)
> psp->pps_dev = pp->dpp_dev;
> psp->pps_inum = pp->dpp_inum;
> psp->pps_fn = strdup(pp->dpp_fname);
> - psp->pps_vaddr = pp->dpp_vaddr;
> psp->pps_fun = (char *) func;
> + psp->pps_nameoff = 0;
> + psp->pps_off = symp->st_value - pp->dpp_vaddr;
>
> if (!isdash && gmatch("return", pp->dpp_name)) {
> - if (dt_pid_create_fbt_probe(pp->dpp_pr, dtp, psp, symp,
> + if (dt_pid_create_one_probe(pp->dpp_pr, dtp, psp, symp,
> DTPPT_RETURN) < 0) {
> rc = dt_pid_error(
> dtp, pcb, dpr, D_PROC_CREATEFAIL,
> @@ -212,7 +205,7 @@ dt_pid_per_sym(dt_pid_probe_t *pp, const GElf_Sym *symp, const char *func)
> }
>
> if (!isdash && gmatch("entry", pp->dpp_name)) {
> - if (dt_pid_create_fbt_probe(pp->dpp_pr, dtp, psp, symp,
> + if (dt_pid_create_one_probe(pp->dpp_pr, dtp, psp, symp,
> DTPPT_ENTRY) < 0) {
> rc = dt_pid_error(
> dtp, pcb, dpr, D_PROC_CREATEFAIL,
> @@ -226,6 +219,8 @@ dt_pid_per_sym(dt_pid_probe_t *pp, const GElf_Sym *symp, const char *func)
>
> glob = strisglob(pp->dpp_name);
> if (!glob && nmatches == 0) {
> + char *end;
> +
> off = strtoull(pp->dpp_name, &end, 16);
> if (*end != '\0') {
> rc = dt_pid_error(dtp, pcb, dpr, D_PROC_NAME,
> @@ -242,8 +237,10 @@ dt_pid_per_sym(dt_pid_probe_t *pp, const GElf_Sym *symp, const char *func)
> goto out;
> }
>
> - if (dt_pid_create_glob_offset_probes(pp->dpp_pr, pp->dpp_dtp,
> - psp, symp, pp->dpp_name) < 0) {
> + psp->pps_nameoff = off;
> + psp->pps_off = symp->st_value - pp->dpp_vaddr + off;
> + if (dt_pid_create_one_probe(pp->dpp_pr, pp->dpp_dtp,
> + psp, symp, DTPPT_OFFSETS) < 0) {
> rc = dt_pid_error(
> dtp, pcb, dpr, D_PROC_CREATEFAIL,
> "failed to create probes at '%s+0x%llx': %s",
> @@ -254,16 +251,129 @@ dt_pid_per_sym(dt_pid_probe_t *pp, const GElf_Sym *symp, const char *func)
>
> nmatches++;
> } else if (glob && !isdash) {
> - if (dt_pid_create_glob_offset_probes(pp->dpp_pr, pp->dpp_dtp,
> - psp, symp, pp->dpp_name) < 0) {
> - rc = dt_pid_error(
> - dtp, pcb, dpr, D_PROC_CREATEFAIL,
> - "failed to create offset probes in '%s': %s",
> - func, dtrace_errmsg(dtp, dtrace_errno(dtp)));
> +#if defined(__amd64)
> + /*
> + * We need to step through the instructions to find their
> + * offsets. This is difficult on x86, which has variable
> + * instruction lengths. We invoke the disassembler in
> + * libopcodes.
> + *
> + * We look for the Elf pointer. It is already stored in
> + * file_elf in file_info_t, but getting it back over here
> + * means introducing new struct members, new arguments to
> + * functions, etc. So just call elf_begin() again here.
> + */
> + int fd, i;
> + Elf *elf;
> + Elf_Scn *scn = NULL;
> + GElf_Sym sym;
> + GElf_Shdr shdr;
> + Elf_Data *data;
> + size_t shstrndx, off;
> + disassembler_ftype disasm;
> +
> + /* Set things up. */
> + fd = open(pp->dpp_fname, O_RDONLY);
> + elf = elf_begin(fd, ELF_C_READ_MMAP, NULL); // ELF_C_READ ?
> + assert(elf_kind(elf) == ELF_K_ELF);
> + elf_getshdrstrndx(elf, &shstrndx);
> +
> + /* Look for the symbol table. */
> + while (1) {
> + scn = elf_nextscn(elf, scn);
> + if (scn == NULL)
> + goto out;
> + assert(gelf_getshdr(scn, &shdr) != NULL);
> + if (shdr.sh_type == SHT_SYMTAB)
> + break;
> + }
> +
> + /* Look for the symbol in the symbol table. */
> + data = elf_getdata(scn, NULL);
> + for (i = 0; i < data->d_size / sizeof(GElf_Sym); i++) {
> + if (!gelf_getsym(data, i, &sym))
> + continue;
> + if (GELF_ST_BIND(sym.st_info) != STB_GLOBAL)
> + continue;
> + if (strcmp(elf_strptr(elf, shdr.sh_link, sym.st_name), func) == 0)
> + break;
> + }
> + if (i >= data->d_size / sizeof(GElf_Sym))
> goto out;
> +
> + /* Get the section for our symbol. */
> + scn = elf_getscn(elf, sym.st_shndx);
> + assert(gelf_getshdr(scn, &shdr) != NULL);
> +
> + /* Check that the section is text. */
> + if (shdr.sh_type != SHT_PROGBITS ||
> + shdr.sh_size <= 0 ||
> + (shdr.sh_flags & SHF_EXECINSTR) == 0) {
> + assert(0);
> }
> + assert(strcmp(elf_strptr(elf, shstrndx, shdr.sh_name), ".text") == 0);
>
> - nmatches++;
> + /* Get the instructions. */
> + data = elf_getdata(scn, NULL);
> +
> + /*
> + * "Disassemble" instructions just to get the offsets.
> + *
> + * Unfortunately, libopcodes's disassembler() has a different
> + * interface in binutils versions before 2.29.
> + */
> +#if BINUTILSMAJ == 2 && BINUTILSMIN < 29
> + bfd *abfd;
> + struct disassemble_info disasm_info;
> +
> + bfd_init();
> + abfd = bfd_openr(pp->dpp_fname, NULL);
> + if (!bfd_check_format(abfd, bfd_object))
> + return 1;
> +
> + disasm = disassembler(abfd);
> +#else
> + disassemble_info disasm_info;
> +
> + disasm = disassembler(bfd_arch_i386, false, bfd_mach_x86_64, NULL);
> +#endif
> + init_disassemble_info(&disasm_info, NULL, my_callback);
At some point, libopcodes added a 4th argument to init_disassemble_info(), so
there will need to be some conditionals here to support both.
> + disasm_info.buffer = data->d_buf + (sym.st_value - shdr.sh_addr);
> + disasm_info.buffer_length = sym.st_size;
> +#else
> + /*
> + * The situation on aarch64 is much simpler: each instruction
> + * is 4 bytes.
> + */
> +#define disasm(x, y) 4
> +#endif
> +
> + for (off = 0; off < symp->st_size; off += disasm(off, &disasm_info)) {
> + char offstr[32];
> +
> + /* Newer kernels do not allow uprobes on "hlt" instructions. */
> + if ((unsigned int)disasm_info.buffer[off] == 0xf4)
> + continue;
> +
> + snprintf(offstr, sizeof(offstr), "%lx", off);
> + if (!gmatch(offstr, pp->dpp_name))
> + continue;
> +
> + psp->pps_nameoff = off;
> + psp->pps_off = symp->st_value - pp->dpp_vaddr + off;
> + if (dt_pid_create_one_probe(pp->dpp_pr, pp->dpp_dtp,
> + psp, symp, DTPPT_OFFSETS) >= 0)
> + nmatches++;
> + }
> +
> +#if defined(__amd64)
> + /* Shut things down. */
> + elf_end(elf);
> + close(fd);
> +#if BINUTILSMAJ == 2 && BINUTILSMIN < 29
> + bfd_close(abfd);
> +#endif
> +#endif
> }
>
> pp->dpp_nmatches += nmatches;
> @@ -711,6 +821,7 @@ dt_pid_scan_uprobes(dtrace_hdl_t *dtp, dt_pcb_t *pcb)
>
> psp = &dtp->dt_uprobespecs[i++];
> psp->pps_type = linep->is_enabled ? DTPPT_IS_ENABLED : DTPPT_OFFSETS;
> + psp->pps_nameoff = 0;
>
> /*
> * These components are only used for creation of an underlying
> diff --git a/libdtrace/dt_prov_uprobe.c b/libdtrace/dt_prov_uprobe.c
> index 5dc5c75c..7c3f1b64 100644
> --- a/libdtrace/dt_prov_uprobe.c
> +++ b/libdtrace/dt_prov_uprobe.c
> @@ -26,8 +26,6 @@
> static const char prvname[] = "uprobe";
> static const char prvname_is_enabled[] = "uprobe__is_enabled";
>
> -#define UPROBE_EVENTS TRACEFS "uprobe_events"
> -
> #define PP_IS_MINE 1
> #define PP_IS_RETURN 2
> #define PP_IS_FUNCALL 4
> @@ -309,7 +307,7 @@ static int provide_pid_probe(dtrace_hdl_t *dtp, const pid_probespec_t *psp)
> strcpy(prb, "return");
> break;
> case DTPPT_OFFSETS:
> - snprintf(prb, sizeof(prb), "%lx", psp->pps_off);
> + snprintf(prb, sizeof(prb), "%lx", psp->pps_nameoff);
> break;
> default:
> dt_dprintf("pid: unknown PID probe type %i\n", psp->pps_type);
> @@ -611,7 +609,7 @@ static int attach(dtrace_hdl_t *dtp, const dt_probe_t *prp, int bpf_fd)
>
> /*
> * If the uprobe creation failed, it is possible it already
> - * existed because someone else created it. Tey to access its
> + * existed because someone else created it. Try to access its
> * tracefs info and if that fail, we really failed.
> */
> }
> diff --git a/test/unittest/pid/tst.emptystack.d b/test/unittest/pid/tst.emptystack.d
> index 478557e4..25eb20bd 100644
> --- a/test/unittest/pid/tst.emptystack.d
> +++ b/test/unittest/pid/tst.emptystack.d
> @@ -4,7 +4,6 @@
> * Licensed under the Universal Permissive License v 1.0 as shown at
> * http://oss.oracle.com/licenses/upl.
> */
> -/* @@xfail: dtv2 */
> /* @@runtest-opts: $_pid */
> /* @@trigger-timing: before */
> /* @@trigger: pid-tst-float */
> diff --git a/test/unittest/pid/tst.float.d b/test/unittest/pid/tst.float.d
> index f5ec25ef..7ef037ae 100644
> --- a/test/unittest/pid/tst.float.d
> +++ b/test/unittest/pid/tst.float.d
> @@ -1,13 +1,13 @@
> /*
> * Oracle Linux DTrace.
> - * Copyright (c) 2006, 2021, Oracle and/or its affiliates. All rights reserved.
> + * Copyright (c) 2006, 2023, 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.
> */
>
> /* @@runtest-opts: $_pid */
> /* @@trigger: pid-tst-float */
> -/* @@trigger-timing: after */
> +/* @@trigger-timing: before */
>
> /*
> * ASSERTION: Make sure we can work on processes that use the FPU
> @@ -15,19 +15,12 @@
> * SECTION: pid provider
> */
>
> -BEGIN
> +pid$1:a.out:main:
> {
> - /*
> - * Let's just do this for 5 seconds.
> - */
> - timeout = timestamp + 5000000000;
> + @[probename] = count();
> }
>
> -pid$1:a.out:main:
> -{}
> -
> -profile:::tick-4
> -/timestamp > timeout/
> +profile:::tick-5s
> {
> exit(0);
> }
> diff --git a/test/unittest/pid/tst.offsets.r b/test/unittest/pid/tst.offsets.r
> new file mode 100644
> index 00000000..2e9ba477
> --- /dev/null
> +++ b/test/unittest/pid/tst.offsets.r
> @@ -0,0 +1 @@
> +success
> diff --git a/test/unittest/pid/tst.offsets.sh b/test/unittest/pid/tst.offsets.sh
> new file mode 100755
> index 00000000..239fd567
> --- /dev/null
> +++ b/test/unittest/pid/tst.offsets.sh
> @@ -0,0 +1,270 @@
> +#!/bin/bash
> +#
> +# Oracle Linux DTrace.
> +# Copyright (c) 2023, 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.
> +#
> +# This test verifies that a glob in a pid-provider probe name works.
> +#
> +
> +dtrace=$1
> +
> +DIR=$tmpdir/pid-offsets.$$
> +mkdir $DIR
> +cd $DIR
> +
> +nouter=10
> +ninner=1000
> +
> +# Some instructions are "hot" (executed $nouter*$ninner times).
> +# Some instructions are "cold" (executed only $nouter times).
> +# Function leaffunc() is hot.
> +# Function coldfunc() is cold.
> +# Function loopfunc() has instructions that are:
> +# hot (inside the function's loop)
> +# cold (outside the function's loop)
> +#
> +# Variable "i" is always manipulated after a function call to prevent
> +# tail-call optimization.
> +
> +cat > main.c << EOF
> +int leaffunc(int i) {
> + return 2 * i - 1;
> +}
> +
> +int loopfunc(int i) {
> + int j;
> + for (j = 0; j < $ninner; j++)
> + i = 2 * leaffunc(i) - 1;
> + return i;
> +}
> +
> +int coldfunc(int i) {
> + return (2 * loopfunc(i) - 1);
> +}
> +
> +int
> +main(int c, char **v)
> +{
> + int i = 1, j;
> + for (j = 0; j < $nouter; j++)
> + i = 2 * coldfunc(i) - 1;
> + return i;
> +}
> +EOF
> +
> +gcc main.c
> +if [ $? -ne 0 ]; then
> + echo ERROR in compiling
> + exit 1
> +fi
> +
> +#
> +# Now run DTrace.
> +# We are testing a glob in the pid$target:a.out:loopfunc: probe name.
> +#
> +
> +$dtrace -xstrsize=16 -qn '
> +pid$target:a.out:coldfunc:entry,
> +pid$target:a.out:loopfunc:,
> +pid$target:a.out:leaffunc:entry
> +{
> + @[probefunc, probename] = count();
> +}' -c ./a.out -o D.out
> +if [ $? -ne 0 ]; then
> + echo DTrace did not run
> + cat D.out
> + exit 1
> +fi
> +
> +#
> +# Use objdump and awk to extract the instruction PCs for function loopfunc().
> +# There should be a loop (jump to an earlier instruction), so also report
> +# the PCs for the jump and target instructions. This will help identify
> +# hot and cold instructions in the function.
> +#
> +
> +objdump -d a.out | awk '
> +BEGIN {
> + pc0 = 0; # First PC of loopfunc()
> + pcjump = 0; # PC of the jump
> + pctarget = 0; # PC of the target
> +}
> +
> +# Look for loopfunc()
> +!/^[0-9a-f]* <loopfunc>:$/ { next; }
> +
> +# Process instructions in the loopfunc() function
> +{
> + # Get the first PC
> + sub("^0*", "", $1);
> + pc0 = strtonum("0x"$1);
> +
> + # Walk instructions until a blank line indicates end of function
> + getline;
> + while (NF > 0) {
> + # Figure instruction offset and add to set of offsets
> + off = strtonum("0x"$1) - pc0;
> + offs[off] = 1;
> + printf(" %x", off);
> +
> + # Check for jump instruction to some <loopfunc+0x$target> target
> + if (match($NF, "^<loopfunc\\+0x[0-9a-f]*>$")) {
> + # Figure the offset of the target
> + sub("^.*<loopfunc\\+", "", $NF); # Get "0x$target>"
> + sub(">.*$", "", $NF); # Get "0x$target"
> + $NF = strtonum($NF); # Get $target
> +
> + # Use it if a back jump (an offset we have already seen)
> + if ($NF in offs) {
> + pctarget = $NF;
> + pcjump = off;
> + }
> + }
> +
> + # Next line
> + getline;
> + }
> +
> + # Report PCs for jump instruction and target
> + printf("\nfrom %x to %x\n", pcjump, pctarget);
> + exit(0);
> +}' > pcs.out
> +if [ $? -ne 0 ]; then
> + echo awk processing of objdump output did not run
> + cat D.out
> + cat pcs.out
> + exit 1
> +fi
> +
> +#
> +# Use files pcs.out and D.out to check results.
> +#
> +
> +awk '
> +BEGIN {
> + # Determine the expected counts for cold and hot instructions
> + ncold = '$nouter';
> + nhot = ncold * '$ninner';
> + nhot2 = nhot + '$nouter'; # An instruction within the loopfunc() loop may be executed an extra time
> +
> + # Track counts for a few of the pid probes specially
> + n_coldfunc_entry = -1;
> + n_loopfunc_return = -1;
> + n_leaffunc_entry = -1;
> +}
> +
> +#
> +# Process first file: pcs.out
> +# - which PCs are in loopfunc()
> +# - which PCs are hot and which are cold
> +#
> +
> +# Read the list of instruction PCs into array "offs"
> +NR == 1 { if (split($0, offs) < 4) {
> + # We do not know exactly how many instructions to expect,
> + # but perform a sanity check for some minimum.
> + print "ERROR: few PCs found";
> + exit(1)
> + };
> + next
> + }
> +
> +# Read the jump and target PCs and classify other PCs as hot and cold
> +NR == 2 { pcjump = strtonum("0x"$2);
> + pctarget = strtonum("0x"$4);
> + for (i in offs) {
> + off = strtonum("0x"offs[i]);
> + if (off >= pctarget && off <= pcjump) { hot[off] = 1 }
> + else { cold[off] = 1 }
> + }
> + next
> + }
> +
> +#
> +# Process second file: D.out
> +# - get DTrace output and check the counts for the various pid probes
> +#
> +
> +NF == 0 { next }
> +
> +NF != 3 { print "ERROR: line does not have 3 fields:", $0; exit(1) }
> +
> +# coldfunc:entry
> +$1 == "coldfunc" && $2 != "entry" { print "ERROR: coldfunc probe other than entry"; exit(1) }
> +$1 == "coldfunc" && n_coldfunc_entry != -1 { print "ERROR: second coldfunc:entry line"; exit(1) }
> +$1 == "coldfunc" { n_coldfunc_entry = strtonum($3); next }
> +
> +# leaffunc:entry
> +$1 == "leaffunc" && $2 != "entry" { print "ERROR: leaffunc probe other than entry"; exit(1) }
> +$1 == "leaffunc" && n_leaffunc_entry != -1 { print "ERROR: second leaffunc:entry line"; exit(1) }
> +$1 == "leaffunc" { n_leaffunc_entry = strtonum($3); next }
> +
> +# The rest should be loopfunc:*
> +$1 != "loopfunc" { print "ERROR: probe func is not recognized", $1; exit(1) }
> +
> +# loopfunc:return
> +$2 == "return" && n_loopfunc_return != -1 { print "ERROR: second loopfunc:return line"; exit(1) }
> +$2 == "return" { n_loopfunc_return = strtonum($3); next }
> +
> +# loopfunc:entry, which we handle as loopfunc:0
> +$2 == "entry" {
> + if (strtonum($3) != '$nouter') {
> + print "ERROR: loopfunc:entry mismatch";
> + exit(1);
> + }
> + delete cold[0];
> + next
> + }
> +
> +# loopfunc:$off
> +{
> + off = strtonum("0x"$2);
> + cnt = strtonum($3);
> +
> + if (off in hot) {
> + if (cnt != nhot && cnt != nhot2) {
> + printf("ERROR: loopfunc:$off mismatch hot: %x %d\n", off, cnt);
> + exit(1);
> + }
> + delete hot[off];
> + } else if (off in cold) {
> + if (cnt != ncold) {
> + printf("ERROR: loopfunc:$off mismatch cold: %x %d\n", off, cnt);
> + exit(1);
> + }
> + delete cold[off];
> + } else {
> + printf("ERROR: found unanticipated PC: %x\n", off);
> + exit(1);
> + }
> +}
> +
> +# Final checks
> +
> +END {
> + # Check coldfunc:entry, loopfunc:return, and leaffunc:entry counts (and that they were seen)
> + if (n_coldfunc_entry != '$nouter') { print "ERROR: coldfunc:entry mismatch"; exit(1) }
> + if (n_loopfunc_return != '$nouter') { print "ERROR: loopfunc:return mismatch"; exit(1) }
> + if (n_leaffunc_entry != nhot) { print "ERROR: leaffunc:entry mismatch"; exit(1) }
> +
> + # Report any remaining hot or cold PCs not found (if any)
> + num_not_found = 0;
> + for (off in hot) { print "ERROR: hot PC not found:", off; num_not_found++ }
> + for (off in cold) { print "ERROR: hot PC not found:", off; num_not_found++ }
> + if (num_not_found) exit(1);
> +
> + # Done
> + print "success";
> + exit(0);
> +}
> +' pcs.out D.out
> +if [ $? -ne 0 ]; then
> + cat D.out
> + cat pcs.out
> + echo ERROR: awk postprocess
> + exit 1
> +fi
> +
> +exit 0
> diff --git a/test/unittest/pid/tst.probefunc.d b/test/unittest/pid/tst.probefunc.d
> index 85b916ad..d84d4760 100644
> --- a/test/unittest/pid/tst.probefunc.d
> +++ b/test/unittest/pid/tst.probefunc.d
> @@ -1,12 +1,11 @@
> /*
> * Oracle Linux DTrace.
> - * Copyright (c) 2005, 2022, Oracle and/or its affiliates. All rights reserved.
> + * Copyright (c) 2005, 2023, 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.
> */
>
> /* @@runtest-opts: -c /bin/date */
> -/* @@trigger: none */
>
> pid$target:libc.so:malloc:entry
> {
> --
> 2.18.4
>
>
More information about the DTrace-devel
mailing list