[DTrace-devel] [PATCH] WIP: ERROR probe implementation

Kris Van Hees kris.van.hees at oracle.com
Sat Jan 16 00:23:34 PST 2021


###
### This is a Work-In-Progress.  The code should be near complete (so feel
### free to check it out and comment).  The testsuite tests still need to
### be tweaked, and some follow-up patches are necessary to enable more of
### the error conditions that should trigger the ERROR probe.
###

The ERROR probe is special because it is not associated with a system
event.  Instead, it is a 'fake' probe that is reported whenever there
a fault occurs during probe execution.  This can be implemented in
BPF by including the ERROR probe clauses in every probe program that
needs them.

Signed-off-by: Kris Van Hees <kris.van.hees at oracle.com>
---
 bpf/Build                     |   3 +-
 bpf/get_bvar.c                |  23 +++--
 bpf/probe_error.c             |  36 ++++++++
 bpf/probe_error.h             |  14 ++++
 include/dtrace/difo_defines.h |   1 +
 libdtrace/dt_as.c             |  91 ++++++++++++++++++++
 libdtrace/dt_bpf.c            |   5 +-
 libdtrace/dt_cc.c             |   6 ++
 libdtrace/dt_cg.c             | 153 ++++++++++++++++++++++++++++++----
 libdtrace/dt_consume.c        |   9 ++
 libdtrace/dt_handle.c         |  12 +--
 libdtrace/dt_impl.h           |   4 +
 libdtrace/dt_map.c            |  12 ++-
 libdtrace/dt_pcb.h            |   3 +-
 libdtrace/dt_probe.c          | 125 +++++++++++++++++++++++++--
 libdtrace/dt_prov_dtrace.c    |   8 +-
 libdtrace/dtrace.h            |   4 +-
 17 files changed, 459 insertions(+), 50 deletions(-)
 create mode 100644 bpf/probe_error.c
 create mode 100644 bpf/probe_error.h

diff --git a/bpf/Build b/bpf/Build
index e9f2b1e2..36cf1fa0 100644
--- a/bpf/Build
+++ b/bpf/Build
@@ -1,5 +1,5 @@
 # Oracle Linux DTrace.
-# Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2020, 2021, 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.
 
@@ -26,6 +26,7 @@ bpf_dlib_SOURCES = \
 	get_bvar.c \
 	get_gvar.c set_gvar.c \
 	get_tvar.c set_tvar.c \
+	probe_error.c \
 	memcpy.c strnlen.c
 
 bpf-check: $(objdir)/include/.dir.stamp
diff --git a/bpf/get_bvar.c b/bpf/get_bvar.c
index 174626f9..8d941756 100644
--- a/bpf/get_bvar.c
+++ b/bpf/get_bvar.c
@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
 /*
- * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved.
  */
 #include <linux/bpf.h>
 #include <stddef.h>
@@ -8,9 +8,12 @@
 #include <bpf-helpers.h>
 #include <dtrace/conf.h>
 #include <dtrace/dif_defines.h>
+#include <dtrace/faults_defines.h>
 #include <dt_dctx.h>
 #include <dt_state.h>
 
+#include "probe_error.h"
+
 #ifndef noinline
 # define noinline	__attribute__((noinline))
 #endif
@@ -18,6 +21,12 @@
 extern struct bpf_map_def cpuinfo;
 extern struct bpf_map_def state;
 
+#define error(mst, fault, illval) \
+	({ \
+		dt_probe_error((mst), -1, (fault), (illval)); \
+		-1; \
+	})
+
 noinline uint64_t dt_get_bvar(dt_mstate_t *mst, uint32_t id)
 {
 	switch (id) {
@@ -75,13 +84,13 @@ noinline uint64_t dt_get_bvar(dt_mstate_t *mst, uint32_t id)
 		/* Chase pointers val = current->real_parent->tgid. */
 		ptr = bpf_get_current_task();
 		if (ptr == 0)
-			return -1;
+			return error(mst, DTRACEFLT_BADADDR, ptr);
 		if (bpf_probe_read((void *)&ptr, 8,
 		    (const void *)(ptr + *parent_off)))
-			return -1;
+			return error(mst, DTRACEFLT_BADADDR, ptr + *parent_off);
 		if (bpf_probe_read((void *)&val, 4,
 		    (const void *)(ptr + *tgid_off)))
-			return -1;
+			return error(mst, DTRACEFLT_BADADDR, ptr + *tgid_off);
 
 		return (uint64_t)val;
 	}
@@ -106,10 +115,6 @@ noinline uint64_t dt_get_bvar(dt_mstate_t *mst, uint32_t id)
 	}
 	default:
 		/* Not implemented yet. */
-#if 1
-		return (uint64_t)-1;
-#else
-		return (uint64_t)id;
-#endif
+		return error(mst, DTRACEFLT_ILLOP, 0);
 	}
 }
diff --git a/bpf/probe_error.c b/bpf/probe_error.c
new file mode 100644
index 00000000..f697b38f
--- /dev/null
+++ b/bpf/probe_error.c
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
+ */
+#include <linux/bpf.h>
+#include <stdint.h>
+#include <bpf-helpers.h>
+#include <dt_dctx.h>
+
+#ifndef noinline
+# define noinline	__attribute__((noinline))
+#endif
+
+/*
+ * DTrace ERROR probes provide 6 arguments:
+ *	arg0 = always NULL (used to be kernel consumer state pointer)
+ *      arg1 = EPID of probe that triggered the fault
+ *      arg2 = clause index of code that triggered the fault
+ *      arg3 = BPF offset in the clause that triggered the fault (or -1)
+ *      arg4 = fault type
+ *      arg5 = fault-specific value (usually address being accessed or 0)
+ */
+noinline int64_t dt_probe_error(dt_mstate_t *mst, uint64_t pc, uint64_t fault,
+				uint64_t illval)
+{
+	mst->argv[0] = 0;
+	mst->argv[1] = mst->epid;
+	mst->argv[2] = mst->clid;
+	mst->argv[3] = pc;
+	mst->argv[4] = fault;
+	mst->argv[5] = illval;
+
+	mst->fault |= fault;
+
+	return fault;
+}
diff --git a/bpf/probe_error.h b/bpf/probe_error.h
new file mode 100644
index 00000000..8bd5ae29
--- /dev/null
+++ b/bpf/probe_error.h
@@ -0,0 +1,14 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
+ */
+#ifndef BPF_PROBE_ERRROR_H
+#define BPF_PROBE_ERRROR_H
+
+#include <stdint.h>
+#include <dt_dctx.h>
+
+extern int64_t dt_probe_error(dt_mstate_t *mst, uint64_t pc, uint64_t fault,
+			      uint64_t illval);
+
+#endif /* BPF_PROBE_ERRROR_H */
diff --git a/include/dtrace/difo_defines.h b/include/dtrace/difo_defines.h
index 1175a001..75e0b449 100644
--- a/include/dtrace/difo_defines.h
+++ b/include/dtrace/difo_defines.h
@@ -21,5 +21,6 @@ struct dtrace_difo;
  * DIFO flags.
  */
 #define DIFOFLG_DESTRUCTIVE		1	/* Uses destructive ops */
+#define DIFOFLG_ERROR			2	/* May trigger ERROR probe */
 
 #endif /* _DTRACE_DIFO_DEFINES_H */
diff --git a/libdtrace/dt_as.c b/libdtrace/dt_as.c
index 4ca2f420..db65bdc7 100644
--- a/libdtrace/dt_as.c
+++ b/libdtrace/dt_as.c
@@ -258,6 +258,13 @@ dt_as(dt_pcb_t *pcb)
 	if (labels == NULL)
 		longjmp(pcb->pcb_jmpbuf, EDT_NOMEM);
 
+	/*
+	 * Indicate whether this function contains code that may trigger the
+	 * ERROR probe.
+	 */
+	if (pcb->pcb_faultlbl > 0)
+		dp->dtdo_flags |= DIFOFLG_ERROR;
+
 	/*
 	 * Make an initial pass through the instruction list, filling in the
 	 * instruction buffer with valid instructions and skipping labeled nops.
@@ -605,3 +612,87 @@ fail:
 
 	return dp;
 }
+
+dtrace_difo_t *
+dt_difo_copy(dtrace_hdl_t *dtp, const dtrace_difo_t *odp)
+{
+	dtrace_difo_t	*dp;
+
+	dp = dt_zalloc(dtp, sizeof(dtrace_difo_t));
+	if (dp == NULL)
+		goto no_mem;
+
+	/* Copy the executable code. */
+	dp->dtdo_len = odp->dtdo_len;
+	dp->dtdo_buf = dt_calloc(dtp, dp->dtdo_len, sizeof(struct bpf_insn));
+	if (dp->dtdo_buf == NULL)
+		goto no_mem;
+
+	memcpy(dp->dtdo_buf, odp->dtdo_buf,
+	       dp->dtdo_len * sizeof(struct bpf_insn));
+
+	/* Copy the string table (if any). */
+	if (odp->dtdo_strlen > 0) {
+		dp->dtdo_strlen = odp->dtdo_strlen;
+		dp->dtdo_strtab = dt_zalloc(dtp, dp->dtdo_strlen);
+		if (dp->dtdo_strtab == NULL)
+			goto no_mem;
+
+		memcpy(dp->dtdo_strtab, odp->dtdo_strtab, dp->dtdo_strlen);
+	}
+
+	/* Copy the variable table (if any). */
+	if (odp->dtdo_varlen > 0) {
+		dp->dtdo_varlen = odp->dtdo_varlen;
+		dp->dtdo_vartab = dt_calloc(dtp, dp->dtdo_varlen,
+					    sizeof(dtrace_difv_t));
+		if (dp->dtdo_vartab == NULL)
+			goto no_mem;
+
+		memcpy(dp->dtdo_vartab, odp->dtdo_vartab,
+		       dp->dtdo_varlen * sizeof(dtrace_difv_t));
+	}
+
+	/* Copy the relocation tables (if any). */
+	if (odp->dtdo_brelen > 0) {
+		dp->dtdo_brelen = odp->dtdo_brelen;
+		dp->dtdo_breltab = dt_calloc(dtp, dp->dtdo_brelen,
+					    sizeof(dof_relodesc_t));
+		if (dp->dtdo_breltab == NULL)
+			goto no_mem;
+
+		memcpy(dp->dtdo_breltab, odp->dtdo_breltab,
+		       dp->dtdo_brelen * sizeof(dof_relodesc_t));
+	}
+
+	if (odp->dtdo_krelen > 0) {
+		dp->dtdo_krelen = odp->dtdo_krelen;
+		dp->dtdo_kreltab = dt_calloc(dtp, dp->dtdo_krelen,
+					    sizeof(dof_relodesc_t));
+		if (dp->dtdo_kreltab == NULL)
+			goto no_mem;
+
+		memcpy(dp->dtdo_kreltab, odp->dtdo_kreltab,
+		       dp->dtdo_krelen * sizeof(dof_relodesc_t));
+	}
+
+	if (odp->dtdo_urelen > 0) {
+		dp->dtdo_urelen = odp->dtdo_urelen;
+		dp->dtdo_ureltab = dt_calloc(dtp, dp->dtdo_urelen,
+					    sizeof(dof_relodesc_t));
+		if (dp->dtdo_ureltab == NULL)
+			goto no_mem;
+
+		memcpy(dp->dtdo_ureltab, odp->dtdo_ureltab,
+		       dp->dtdo_urelen * sizeof(dof_relodesc_t));
+	}
+
+	dp->dtdo_ddesc = dt_datadesc_hold(odp->dtdo_ddesc);
+	dp->dtdo_flags = odp->dtdo_flags;
+
+	return dp;
+
+no_mem:
+	dt_set_errno(dtp, EDT_NOMEM);
+	return NULL;
+}
diff --git a/libdtrace/dt_bpf.c b/libdtrace/dt_bpf.c
index 1705c990..06148789 100644
--- a/libdtrace/dt_bpf.c
+++ b/libdtrace/dt_bpf.c
@@ -1,6 +1,6 @@
 /*
  * Oracle Linux DTrace.
- * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2021, 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.
  */
@@ -386,6 +386,9 @@ dt_bpf_load_progs(dtrace_hdl_t *dtp, uint_t cflags)
 		dtrace_difo_t	*dp;
 		int		fd, rc;
 
+		if (prp == dtp->dt_error)
+			continue;
+
 		dp = dt_program_construct(dtp, prp, cflags);
 		if (dp == NULL)
 			return -1;
diff --git a/libdtrace/dt_cc.c b/libdtrace/dt_cc.c
index 691c31f2..12f60601 100644
--- a/libdtrace/dt_cc.c
+++ b/libdtrace/dt_cc.c
@@ -1585,6 +1585,12 @@ dt_clause_create(dtrace_hdl_t *dtp, dtrace_difo_t *dp)
 	dp->dtdo_ddesc = yypcb->pcb_ddesc;
 	yypcb->pcb_ddesc = NULL;
 
+	/*
+	 * Special case: ERROR probe default clause.
+	 */
+	if (yypcb->pcb_cflags & DTRACE_C_EPROBE)
+		dp->dtdo_ddesc->dtdd_uarg = DT_ECB_ERROR;
+
 	/*
 	 * Generate a symbol name.
 	 */
diff --git a/libdtrace/dt_cg.c b/libdtrace/dt_cg.c
index f55fd6b6..70ba69f8 100644
--- a/libdtrace/dt_cg.c
+++ b/libdtrace/dt_cg.c
@@ -164,38 +164,75 @@ typedef struct {
 	dt_irlist_t	*dlp;
 	dt_activity_t	act;
 	uint_t		lbl_exit;
+	int		is_reg;
 } dt_clause_arg_t;
 
 static int
 dt_cg_call_clause(dtrace_hdl_t *dtp, dt_ident_t *idp, dt_clause_arg_t *arg)
 {
 	dt_irlist_t	*dlp = arg->dlp;
+	dtrace_difo_t	*dp = dt_dlib_get_func_difo(dtp, idp);
 
 	/*
-	 *	if (*dctx.act != act)	// ldw %r0, [%fp + DCTX_FP(DCTX_ACT)]
-	 *		goto exit;	// ldw %r0, [%r0 + 0]
-	 *				// jne %r0, act, lbl_exit
-	 *	dt_clause(dctx);	// mov %r1, %fp
+	 * Regular clause (not ERROR probe clause), so activity state checking
+	 * must be done in case we should skip any furthe processing.
+	 */
+	if (arg->is_reg) {
+		/*
+		 *	if (*dctx.act != act)	// ldw %r0, [%fp +
+		 *				//	     DCTX_FP(DCTX_ACT)]
+		 *		goto exit;	// ldw %r0, [%r0 + 0]
+		 *				// jne %r0, act, lbl_exit
+		 */
+		emit(dlp,  BPF_LOAD(BPF_DW, BPF_REG_0, BPF_REG_FP,
+				    DCTX_FP(DCTX_ACT)));
+		emit(dlp,  BPF_LOAD(BPF_W, BPF_REG_0, BPF_REG_0, 0));
+		emit(dlp,  BPF_BRANCH_IMM(BPF_JNE, BPF_REG_0, arg->act,
+					  arg->lbl_exit));
+
+		/*
+		 * If this identifier is the first one to possibly trigger the
+		 * ERROR probe, we need to allocate a fault label.
+		 */
+		if ((dp->dtdo_flags & DIFOFLG_ERROR) &&
+		    yypcb->pcb_faultlbl == 0)
+			yypcb->pcb_faultlbl = dt_irlist_label(dlp);
+	}
+
+	/*
+	 *	rc = dt_clause(dctx);	// mov %r1, %fp
 	 *				// add %r1, DCTX_FP(0)
 	 *				// call dt_program
 	 */
-	emit(dlp,  BPF_LOAD(BPF_DW, BPF_REG_0, BPF_REG_FP, DCTX_FP(DCTX_ACT)));
-	emit(dlp,  BPF_LOAD(BPF_W, BPF_REG_0, BPF_REG_0, 0));
-	emit(dlp,  BPF_BRANCH_IMM(BPF_JNE, BPF_REG_0, arg->act, arg->lbl_exit));
 	emit(dlp,  BPF_MOV_REG(BPF_REG_1, BPF_REG_FP));
 	emit(dlp,  BPF_ALU64_IMM(BPF_ADD, BPF_REG_1, DCTX_FP(0)));
 	emite(dlp, BPF_CALL_FUNC(idp->di_id), idp);
 
+	/*
+	 * Regular clause (not ERROR probe clause) so if the clause may trigger
+	 * the ERROR probe, we must jump to the ERROR probe handling if a
+	 * non-zero value is returned.
+	 */
+	if (arg->is_reg && (dp->dtdo_flags & DIFOFLG_ERROR)) {
+		/*
+		 *	if (rc != 0)		// jne %r0, 0, lbl_fault
+		 *		goto fault;
+		 */
+		emit(dlp,  BPF_BRANCH_IMM(BPF_JNE, BPF_REG_0, 0,
+					  yypcb->pcb_faultlbl));
+	}
+
 	return 0;
 }
 
 static void
-dt_cg_tramp_call_clauses(dt_pcb_t *pcb, dt_activity_t act)
+dt_cg_tramp_call_clauses(dt_pcb_t *pcb, dt_probe_t *prp, dt_activity_t act,
+			 int is_reg)
 {
 	dt_irlist_t	*dlp = &pcb->pcb_ir;
-	dt_clause_arg_t	arg = { dlp, act, pcb->pcb_exitlbl };
+	dt_clause_arg_t	arg = { dlp, act, pcb->pcb_exitlbl, is_reg };
 
-	dt_probe_clause_iter(pcb->pcb_hdl, pcb->pcb_probe,
+	dt_probe_clause_iter(pcb->pcb_hdl, prp,
 			     (dt_clause_f *)dt_cg_call_clause, &arg);
 }
 
@@ -218,7 +255,20 @@ dt_cg_tramp_return(dt_pcb_t *pcb)
 void
 dt_cg_tramp_epilogue(dt_pcb_t *pcb)
 {
-	dt_cg_tramp_call_clauses(pcb, DT_ACTIVITY_ACTIVE);
+	dt_irlist_t	*dlp = &pcb->pcb_ir;
+
+	dt_cg_tramp_call_clauses(pcb, pcb->pcb_probe, DT_ACTIVITY_ACTIVE, 1);
+
+	/*
+	 * If there are clauses that may trigger the ERROR probe, set the
+	 * fault label and add calls to the ERROR probe clauses.
+	 */
+	if (pcb->pcb_faultlbl > 0) {
+		emitl(dlp, pcb->pcb_faultlbl,
+			   BPF_NOP());
+		dt_cg_tramp_call_clauses(pcb, pcb->pcb_hdl->dt_error, 0, 0);
+	}
+
 	dt_cg_tramp_return(pcb);
 }
 
@@ -227,7 +277,17 @@ dt_cg_tramp_epilogue_advance(dt_pcb_t *pcb, dt_activity_t act)
 {
 	dt_irlist_t	*dlp = &pcb->pcb_ir;
 
-	dt_cg_tramp_call_clauses(pcb, act);
+	dt_cg_tramp_call_clauses(pcb, pcb->pcb_probe, act, 1);
+
+	/*
+	 * If there are clauses that may trigger the ERROR probe, set the
+	 * fault label and add calls to the ERROR probe clauses.
+	 */
+	if (pcb->pcb_faultlbl > 0) {
+		emitl(dlp, pcb->pcb_faultlbl,
+			   BPF_NOP());
+		dt_cg_tramp_call_clauses(pcb, pcb->pcb_hdl->dt_error, 0, 0);
+	}
 
 	/*
 	 *	(*dctx.act)++;		// lddw %r0, [%fp + DCTX_FP(DCTX_ACT)]
@@ -368,13 +428,16 @@ dt_cg_epilogue(dt_pcb_t *pcb)
 		 *	if (rc != 0)
 		 *	    goto exit;		// jne %r0, 0, pcb->pcb_exitlbl
 		 */
+#if 0
 		emit(dlp, BPF_LOAD(BPF_DW, BPF_REG_0, BPF_REG_FP, DT_STK_DCTX));
 		emit(dlp, BPF_LOAD(BPF_DW, BPF_REG_0, BPF_REG_0, DCTX_MST));
 		emit(dlp, BPF_LOAD(BPF_DW, BPF_REG_0, BPF_REG_0, DMST_FAULT));
 		emit(dlp, BPF_BRANCH_IMM(BPF_JNE, BPF_REG_0, 0, pcb->pcb_exitlbl));
+#endif
 
 		/*
-		 *	bpf_perf_event_output(dctx->ctx, &buffers, BPF_F_CURRENT_CPU,
+		 *	bpf_perf_event_output(dctx->ctx, &buffers,
+		 *			      BPF_F_CURRENT_CPU,
 		 *			      buf - 4, bufoff + 4);
 		 *				// lddw %r1, [%fp + DT_STK_DCTX]
 		 *				// lddw %r1, [%r1 + DCTX_CTX]
@@ -383,7 +446,7 @@ dt_cg_epilogue(dt_pcb_t *pcb)
 		 *				// mov %r4, %r9
 		 *				// add %r4, -4
 		 *				// mov %r5, pcb->pcb_bufoff
-		 *				// add %r4, 4
+		 *				// add %r5, 4
 		 *				// call bpf_perf_event_output
 		 *
 		 */
@@ -410,6 +473,47 @@ dt_cg_epilogue(dt_pcb_t *pcb)
 	TRACE_REGSET("Epilogue: End  ");
 }
 
+/*
+ * Generate code for a fault condition.  A call is made to dt_probe_error() to
+ * set the fault information, and then a non-zero return from the function is
+ * performed.
+ */
+static void
+dt_cg_probe_error(dt_pcb_t *pcb, uint32_t off, uint32_t fault, uint64_t illval)
+{
+	dt_irlist_t	*dlp = &pcb->pcb_ir;
+	dt_regset_t	*drp = pcb->pcb_regs;
+	dt_ident_t	*idp = dt_dlib_get_func(yypcb->pcb_hdl,
+						"dt_probe_error");
+
+	assert(idp != NULL);
+
+	if (pcb->pcb_faultlbl == 0)
+		pcb->pcb_faultlbl = dt_irlist_label(dlp);
+
+	/*
+	 *	return dt_probe_error(	// lddw %r1, %fp, DT_STK_DCTX
+	 *		dctx->mst,	// lddw %r1, %r1, DCTX_MST
+	 *		off,		// mov %r2, off
+	 *		fault,		// mov %r3, fault
+	 *		illval);	// mov %r4, illval
+	 *				// call dt_probe_error
+	 *				// exit
+	 */
+	if (dt_regset_xalloc_args(drp) == -1)
+		longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
+	emit(dlp,  BPF_LOAD(BPF_DW, BPF_REG_1, BPF_REG_FP, DT_STK_DCTX));
+	emit(dlp,  BPF_LOAD(BPF_DW, BPF_REG_1, BPF_REG_1, DCTX_MST));
+	emit(dlp,  BPF_MOV_IMM(BPF_REG_2, off));
+	emit(dlp,  BPF_MOV_IMM(BPF_REG_3, fault));
+	emit(dlp,  BPF_MOV_IMM(BPF_REG_4, illval));
+	dt_regset_xalloc(drp, BPF_REG_0);
+	emite(dlp, BPF_CALL_FUNC(idp->di_id), idp);
+	dt_regset_free_args(drp);
+	dt_regset_free(drp, BPF_REG_0);
+	emit(dlp,  BPF_RETURN());
+}
+
 /*
  * Generate an instruction sequence to fill a gap in the output buffer with 0
  * values.  This is used to ensure that there are no uninitialized bytes in the
@@ -2810,14 +2914,27 @@ dt_cg_node(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp)
 		break;
 
 	case DT_TOK_PTR:
-	case DT_TOK_DOT:
+	case DT_TOK_DOT: {
+		uint_t	lbl_valid = dt_irlist_label(dlp);
+
 		assert(dnp->dn_right->dn_kind == DT_NODE_IDENT);
 		dt_cg_node(dnp->dn_left, dlp, drp);
 
 		/*
-		 * Ensure that the lvalue is not the NULL pointer.
+		 * If the lvalue is the NULL pointer, we must report a BADARR
+		 * fault.
+		 *
+		 *	if (left != 0)		// jne %lreg, 0, valid
+		 *		goto valid;
+		 *				//     (report BADADDR fault)
+		 * valid:
 		 */
-		emit(dlp, BPF_BRANCH_IMM(BPF_JEQ, dnp->dn_left->dn_reg, 0, yypcb->pcb_exitlbl));
+		lbl_valid = dt_irlist_label(dlp);
+		emit(dlp,  BPF_BRANCH_IMM(BPF_JNE, dnp->dn_left->dn_reg, 0,
+					  lbl_valid));
+		dt_cg_probe_error(yypcb, -1, DTRACEFLT_BADADDR, 0);
+		emitl(dlp, lbl_valid,
+			   BPF_NOP());
 
 		/*
 		 * If the left-hand side of PTR or DOT is a dynamic variable,
@@ -2899,6 +3016,7 @@ dt_cg_node(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp)
 
 		dnp->dn_reg = dnp->dn_left->dn_reg;
 		break;
+	}
 
 	case DT_TOK_STRING:
 		if ((dnp->dn_reg = dt_regset_alloc(drp)) == -1)
@@ -4252,6 +4370,7 @@ dt_cg(dt_pcb_t *pcb, dt_node_t *dnp)
 	dt_irlist_destroy(&pcb->pcb_ir);
 	dt_irlist_create(&pcb->pcb_ir);
 	pcb->pcb_exitlbl = dt_irlist_label(&pcb->pcb_ir);
+	pcb->pcb_faultlbl = 0;
 
 	pcb->pcb_bufoff = 0;
 
diff --git a/libdtrace/dt_consume.c b/libdtrace/dt_consume.c
index 1a562983..a034ee8e 100644
--- a/libdtrace/dt_consume.c
+++ b/libdtrace/dt_consume.c
@@ -1979,6 +1979,15 @@ dt_consume_one(dtrace_hdl_t *dtp, FILE *fp, char *buf,
 		if (rval != 0)
 			return dt_set_errno(dtp, EDT_BADEPID);
 
+		if (pdat->dtpda_ddesc->dtdd_uarg != DT_ECB_DEFAULT) {
+			rval = dt_handle(dtp, pdat);
+
+			if (rval == DTRACE_CONSUME_NEXT)
+				return DTRACE_WORKSTATUS_OKAY;
+			if (rval == DTRACE_CONSUME_ERROR)
+				return DTRACE_WORKSTATUS_ERROR;
+		}
+
 		if (flow)
 			dt_flowindent(dtp, pdat, *last, DTRACE_EPIDNONE);
 
diff --git a/libdtrace/dt_handle.c b/libdtrace/dt_handle.c
index 52ff27a8..758b6e10 100644
--- a/libdtrace/dt_handle.c
+++ b/libdtrace/dt_handle.c
@@ -1,6 +1,6 @@
 /*
  * Oracle Linux DTrace.
- * Copyright (c) 2007, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2007, 2021, 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.
  */
@@ -16,7 +16,6 @@
 #include <dt_impl.h>
 #include <dt_program.h>
 
-#ifdef FIXME
 static const char _dt_errprog[] =
 "dtrace:::ERROR"
 "{"
@@ -26,7 +25,6 @@ static const char _dt_errprog[] =
 "	trace(arg4);"
 "	trace(arg5);"
 "}";
-#endif
 
 int
 dtrace_handle_err(dtrace_hdl_t *dtp, dtrace_handle_err_f *hdlr, void *arg)
@@ -51,11 +49,13 @@ dtrace_handle_err(dtrace_hdl_t *dtp, dtrace_handle_err_f *hdlr, void *arg)
 	if (dtp->dt_options[DTRACEOPT_GRABANON] != DTRACEOPT_UNSET)
 		goto out;
 
-#if 0
-	if ((pgp = dtrace_program_strcompile(dtp, _dt_errprog,
-	    DTRACE_PROBESPEC_NAME, DTRACE_C_ZDEFS, 0, NULL)) == NULL)
+	pgp = dtrace_program_strcompile(dtp, _dt_errprog, DTRACE_PROBESPEC_NAME,
+					DTRACE_C_ZDEFS | DTRACE_C_EPROBE, 0,
+					NULL);
+	if (pgp == NULL)
 		return dt_set_errno(dtp, dtrace_errno(dtp));
 
+#if 0
 	stp = dt_list_next(&pgp->dp_stmts);
 	assert(stp != NULL);
 
diff --git a/libdtrace/dt_impl.h b/libdtrace/dt_impl.h
index bc996929..9e69f448 100644
--- a/libdtrace/dt_impl.h
+++ b/libdtrace/dt_impl.h
@@ -287,6 +287,8 @@ struct dtrace_hdl {
 	uint32_t dt_probes_sz;	/* size of array of probes */
 	uint32_t dt_probe_id;	/* next available probe id */
 
+	struct dt_probe *dt_error; /* ERROR probe */
+
 	dt_list_t dt_provlist;	/* linked list of dt_provider_t's */
 	struct dt_provider **dt_provs; /* hash table of dt_provider_t's */
 	uint_t dt_provbuckets;	/* number of provider hash buckets */
@@ -728,6 +730,7 @@ extern dtrace_difo_t *dt_program_construct(dtrace_hdl_t *dtp,
 extern void dt_pragma(dt_node_t *);
 extern int dt_reduce(dtrace_hdl_t *, dt_version_t);
 extern dtrace_difo_t *dt_as(dt_pcb_t *);
+extern dtrace_difo_t *dt_difo_copy(dtrace_hdl_t *dtp, const dtrace_difo_t *odp);
 extern void dt_dis_program(dtrace_hdl_t *dtp, dtrace_prog_t *pgp, FILE *fp);
 extern void dt_dis_difo(const dtrace_difo_t *dp, FILE *fp);
 
@@ -735,6 +738,7 @@ extern int dt_aggregate_go(dtrace_hdl_t *);
 extern int dt_aggregate_init(dtrace_hdl_t *);
 extern void dt_aggregate_destroy(dtrace_hdl_t *);
 
+extern dtrace_datadesc_t *dt_datadesc_hold(dtrace_datadesc_t *ddp);
 extern void dt_datadesc_release(dtrace_hdl_t *, dtrace_datadesc_t *);
 extern dtrace_datadesc_t *dt_datadesc_create(dtrace_hdl_t *);
 extern int dt_datadesc_finalize(dtrace_hdl_t *, dtrace_datadesc_t *);
diff --git a/libdtrace/dt_map.c b/libdtrace/dt_map.c
index 6197d1d6..b7f067fb 100644
--- a/libdtrace/dt_map.c
+++ b/libdtrace/dt_map.c
@@ -1,6 +1,6 @@
 /*
  * Oracle Linux DTrace.
- * Copyright (c) 2006, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2006, 2021, 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.
  */
@@ -15,10 +15,11 @@
 #include <dt_probe.h>
 #include <dt_printf.h>
 
-static void
+dtrace_datadesc_t *
 dt_datadesc_hold(dtrace_datadesc_t *ddp)
 {
 	ddp->dtdd_refcnt++;
+	return ddp;
 }
 
 void
@@ -49,9 +50,7 @@ dt_datadesc_create(dtrace_hdl_t *dtp)
 		return NULL;
 	}
 
-	dt_datadesc_hold(ddp);
-
-	return ddp;
+	return dt_datadesc_hold(ddp);
 }
 
 int
@@ -128,8 +127,7 @@ dt_epid_add(dtrace_hdl_t *dtp, dtrace_datadesc_t *ddp, dtrace_id_t prid)
 	if (dtp->dt_ddesc[epid] != NULL)
 		return epid;
 
-	dt_datadesc_hold(ddp);
-	dtp->dt_ddesc[epid] = ddp;
+	dtp->dt_ddesc[epid] = dt_datadesc_hold(ddp);
 	dtp->dt_pdesc[epid] = (dtrace_probedesc_t *)dtp->dt_probes[prid]->desc;
 
 	return epid;
diff --git a/libdtrace/dt_pcb.h b/libdtrace/dt_pcb.h
index cc39a0f3..0813c089 100644
--- a/libdtrace/dt_pcb.h
+++ b/libdtrace/dt_pcb.h
@@ -1,6 +1,6 @@
 /*
  * Oracle Linux DTrace.
- * Copyright (c) 2005, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 2021, 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.
  */
@@ -44,6 +44,7 @@ typedef struct dt_pcb {
 	uint32_t pcb_bufoff;	/* output buffer offset (for DFUNCs) */
 	dt_irlist_t pcb_ir;	/* list of unrelocated IR instructions */
 	uint_t pcb_exitlbl;	/* label for exit of program */
+	uint_t pcb_faultlbl;	/* label for fault handling */
 	uint_t pcb_asvidx;	/* assembler vartab index (see dt_as.c) */
 	ulong_t **pcb_asxrefs;	/* assembler imported xlators (see dt_as.c) */
 	uint_t pcb_asxreflen;	/* assembler xlator map length (see dt_as.c) */
diff --git a/libdtrace/dt_probe.c b/libdtrace/dt_probe.c
index 23ee49ad..ad3ffb4e 100644
--- a/libdtrace/dt_probe.c
+++ b/libdtrace/dt_probe.c
@@ -1,6 +1,6 @@
 /*
  * Oracle Linux DTrace.
- * Copyright (c) 2006, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2006, 2021, 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.
  */
@@ -22,6 +22,7 @@
 #include <dt_string.h>
 #include <dt_htab.h>
 #include <dt_list.h>
+#include <dt_bpf.h>
 
 typedef struct dt_probeclause {
 	dt_list_t	list;
@@ -1268,18 +1269,130 @@ dtrace_probe_iter(dtrace_hdl_t *dtp, const dtrace_probedesc_t *pdp,
 	return dt_probe_iter(dtp, pdp, NULL, func, arg);
 }
 
+/*
+ * Create an ERROR-probe specific copy of a given clause.
+ *
+ * A modified copy of the clause is necessary because the ERROR probe may share
+ * some clauses with other probes, and yet it needs to be handled differently.
+ *
+ * The following modifications are made in the copy of the clause:
+ *
+ *	- it is named dt_error_N where N is taken from the original clause
+ *	  dt_clause_N (which also guarantees uniqueness)
+ *	- all references to the EPID identifier in the DIFO are resolved
+ *	  in-place by assigning a new EPID for the clause in the ERROR probe
+ *	- all references to the PRID identifier in the DIFO are resolved
+ *	  in-place with the probe id of the ERROR probe
+ *	- EPID and PRID records are stripped from the relocation records
+ */
+dt_ident_t *
+dt_probe_error_clause(dtrace_hdl_t *dtp, dt_ident_t *idp)
+{
+	char		*name;
+	int		len;
+	dtrace_difo_t	*dp = dt_dlib_get_func_difo(dtp, idp);
+	dt_ident_t	*nidp = NULL;
+	dtrace_difo_t	*ndp;
+	dof_relodesc_t	*rp, *nrp;
+	dtrace_epid_t	epid = dt_epid_add(dtp, dp->dtdo_ddesc,
+					   dtp->dt_error->desc->id);;
+
+	/*
+	 * Copy the DIFO.
+	 */
+	ndp = dt_difo_copy(dtp, dp);
+	if (ndp == NULL)
+		goto no_mem;
+
+	/*
+	 * Resolve EPID and PRID references.
+	 */
+	len = ndp->dtdo_brelen;
+	rp = nrp = ndp->dtdo_breltab;
+	for (; len != 0; len--, rp++) {
+		const char	*name = &ndp->dtdo_strtab[rp->dofr_name];
+		dt_ident_t	*ridp = dt_dlib_get_sym(dtp, name);
+		uint32_t	val = 0;
+
+		if (ridp != NULL && ridp->di_kind == DT_IDENT_SCALAR) {
+			if (ridp->di_id == DT_CONST_EPID)
+				val = epid;
+			else if (ridp->di_id == DT_CONST_PRID)
+				val = dtp->dt_error->desc->id;
+
+			if (val > 0) {
+				uint_t	ioff = rp->dofr_offset /
+					       sizeof(struct bpf_insn);
+
+				if (rp->dofr_type == R_BPF_64_64) {
+					ndp->dtdo_buf[ioff].imm = val;
+					ndp->dtdo_buf[ioff + 1].imm = 0;
+				} else if (rp->dofr_type == R_BPF_64_32)
+					ndp->dtdo_buf[ioff].imm = val;
+
+				continue;
+			}
+		}
+
+		if (nrp != rp)
+			*nrp = *rp;
+
+		nrp++;
+	}
+
+	ndp->dtdo_brelen -= rp - nrp;
+
+	/*
+	 * Generate a new symbol name from the given identifier.
+	 */
+	len = strlen(idp->di_name);
+	name = dt_alloc(dtp, len);
+	if (name == NULL)
+		goto no_mem;
+
+	snprintf(name, len, "dt_error_%s", idp->di_name + 10);
+
+	/*
+	 * Add the new symbol to the BPF identifier table and associate the
+	 * modified copy of the DIFO with the symbol.
+	 */
+	nidp = dt_dlib_add_func(dtp, name);
+	dt_free(dtp, name);
+	if (nidp == NULL)
+		goto no_mem;
+
+	dt_ident_set_data(nidp, ndp);
+
+	return nidp;
+
+no_mem:
+	if (ndp != NULL)
+		dt_difo_free(dtp, ndp);
+	if (nidp != NULL)
+		dt_ident_destroy(nidp);
+
+	dt_set_errno(dtp, EDT_NOMEM);
+	return NULL;
+}
+
 int
 dt_probe_add_clause(dtrace_hdl_t *dtp, dt_probe_t *prp, dt_ident_t *idp)
 {
 	dt_probeclause_t	*pcp;
 
 	pcp = dt_zalloc(dtp, sizeof(dt_probeclause_t));;
-	if (pcp == NULL) {
-		dt_set_errno(dtp, EDT_NOMEM);
-		return -1;
-	}
+	if (pcp == NULL)
+		return dt_set_errno(dtp, EDT_NOMEM);
+
+	if (prp == dtp->dt_error) {
+		pcp->clause = dt_probe_error_clause(dtp, idp);
+		if (pcp->clause == NULL) {
+			dt_free(dtp, pcp);
+			return 0;
+		}
+	} else
+		pcp->clause = idp;
 
-	pcp->clause = idp;
 	dt_list_append(&prp->clauses, pcp);
 
 	return 0;
diff --git a/libdtrace/dt_prov_dtrace.c b/libdtrace/dt_prov_dtrace.c
index b28f212d..b9929c35 100644
--- a/libdtrace/dt_prov_dtrace.c
+++ b/libdtrace/dt_prov_dtrace.c
@@ -48,13 +48,19 @@ static int populate(dtrace_hdl_t *dtp)
 		n++;
 		dt_list_append(&dtp->dt_enablings, prp);
 	}
+
 	prp = tp_probe_insert(dtp, prv, prvname, modname, funname, "END");
 	if (prp) {
 		n++;
 		dt_list_append(&dtp->dt_enablings, prp);
 	}
-	if (tp_probe_insert(dtp, prv, prvname, modname, funname, "ERROR"))
+
+	prp = tp_probe_insert(dtp, prv, prvname, modname, funname, "ERROR");
+	if (prp) {
 		n++;
+		dt_list_append(&dtp->dt_enablings, prp);
+		dtp->dt_error = prp;
+	}
 
 	return n;
 }
diff --git a/libdtrace/dtrace.h b/libdtrace/dtrace.h
index 4cf7c998..f9650e6e 100644
--- a/libdtrace/dtrace.h
+++ b/libdtrace/dtrace.h
@@ -101,7 +101,8 @@ typedef struct dtrace_proginfo {
 #define	DTRACE_C_DEFARG	0x0800	/* Use 0/"" as value for unspecified args */
 #define	DTRACE_C_NOLIBS	0x1000	/* Do not process D system libraries */
 #define	DTRACE_C_CTL	0x2000	/* Only process control directives */
-#define	DTRACE_C_MASK	0x3bff	/* mask of all valid flags to dtrace_*compile */
+#define	DTRACE_C_EPROBE	0x8000	/* Compiling default ERROR probe clause */
+#define	DTRACE_C_MASK	0xbbff	/* mask of all valid flags to dtrace_*compile */
 
 extern dtrace_prog_t *dtrace_program_strcompile(dtrace_hdl_t *dtp, const char *s,
     dtrace_probespec_t spec, uint_t cflags, int argc, char *const argv[]);
@@ -150,6 +151,7 @@ typedef struct dtrace_stmtdesc {
 #define DT_CLSFLAG_COMMIT	4	/* commit */
 #define DT_CLSFLAG_EXIT		8	/* exit */
 #define DT_CLSFLAG_DESTRUCT	16	/* destructive */
+#define DT_CLSFLAG_ERROR	32	/* may throw an error */
 
 typedef int dtrace_stmt_f(dtrace_hdl_t *dtp, dtrace_prog_t *pgp,
     dtrace_stmtdesc_t *sdp, void *data);
-- 
2.28.0




More information about the DTrace-devel mailing list