[DTrace-devel] [PATCH v2] pid: Support probe names specifying offsets

eugene.loh at oracle.com eugene.loh at oracle.com
Thu Nov 2 22:39:41 UTC 2023


From: Eugene Loh <eugene.loh at oracle.com>

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);
+		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