[DTrace-devel] [PATCH 3/4] ERROR probe implementation

Kris Van Hees kris.van.hees at oracle.com
Fri Jan 22 14:04:34 PST 2021


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.

Whenever a fault occurs in one of our BPF programs, a call is made to
dt_probe_error() to record data associated with the fault, and the
clause is terminated with a non-zero return value.  The data items
that can be set are:

	- offset in the clause at which the fault occured
	- fault type
	- value

When a clause ends with a non-zero return value, execution is directed
to the ERROR probe clauses that are included in each probe program that
needs them.  These clauses are compiled as regular probe clauses, but
they are processed separately to resolve the EPID and PRID identifiers
according to the ERROR probe rather than the probe that the clauses are
included for.

Signed-off-by: Kris Van Hees <kris.van.hees at oracle.com>
---
 bpf/Build                                     |   3 +-
 bpf/get_bvar.c                                |  27 ++--
 bpf/probe_error.c                             |  38 +++++
 bpf/probe_error.h                             |  14 ++
 libdtrace/dt_as.c                             |  46 ++++++
 libdtrace/dt_bpf.c                            |  84 ++++++++--
 libdtrace/dt_cc.c                             |  90 ++++++++---
 libdtrace/dt_cg.c                             | 148 ++++++++++++++----
 libdtrace/dt_cg.h                             |   1 +
 libdtrace/dt_consume.c                        |   9 ++
 libdtrace/dt_dlibs.c                          |   3 +
 libdtrace/dt_dof.c                            |   4 +-
 libdtrace/dt_handle.c                         |  22 ++-
 libdtrace/dt_impl.h                           |   9 +-
 libdtrace/dt_map.c                            |  12 +-
 libdtrace/dt_pcb.h                            |   2 +-
 libdtrace/dt_probe.c                          |  79 +++++++++-
 libdtrace/dt_prov_dtrace.c                    |  26 ++-
 libdtrace/dt_work.c                           |  14 +-
 libdtrace/dtrace.h                            |   4 +-
 .../tst.DTRACEFLT_BADADDR.null_ptr_field.d    |  27 ++++
 .../tst.DTRACEFLT_BADADDR.null_ptr_field.r    |   3 +
 .../error/tst.clause_scope-begin-ended.d      |  34 ++++
 .../error/tst.clause_scope-begin-ended.r      |   5 +
 test/unittest/error/tst.clause_scope-begin.d  |  38 +++++
 test/unittest/error/tst.clause_scope-begin.r  |   5 +
 .../unittest/error/tst.clause_scope-regular.d |  32 ++++
 .../unittest/error/tst.clause_scope-regular.r |   5 +
 test/unittest/error/tst.error.d               |  11 +-
 test/unittest/error/tst.error.r               |   2 +-
 test/unittest/error/tst.errorend.d            |  11 +-
 test/unittest/error/tst.errorend.r            |   2 +-
 32 files changed, 664 insertions(+), 146 deletions(-)
 create mode 100644 bpf/probe_error.c
 create mode 100644 bpf/probe_error.h
 create mode 100644 test/unittest/error/tst.DTRACEFLT_BADADDR.null_ptr_field.d
 create mode 100644 test/unittest/error/tst.DTRACEFLT_BADADDR.null_ptr_field.r
 create mode 100644 test/unittest/error/tst.clause_scope-begin-ended.d
 create mode 100644 test/unittest/error/tst.clause_scope-begin-ended.r
 create mode 100644 test/unittest/error/tst.clause_scope-begin.d
 create mode 100644 test/unittest/error/tst.clause_scope-begin.r
 create mode 100644 test/unittest/error/tst.clause_scope-regular.d
 create mode 100644 test/unittest/error/tst.clause_scope-regular.r

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..7e7b25a3 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,8 +21,16 @@
 extern struct bpf_map_def cpuinfo;
 extern struct bpf_map_def state;
 
-noinline uint64_t dt_get_bvar(dt_mstate_t *mst, uint32_t id)
+#define error(dctx, fault, illval) \
+	({ \
+		dt_probe_error((dctx), -1, (fault), (illval)); \
+		-1; \
+	})
+
+noinline uint64_t dt_get_bvar(dt_dctx_t *dctx, uint32_t id)
 {
+	dt_mstate_t	*mst = dctx->mst;
+
 	switch (id) {
 	case DIF_VAR_CURTHREAD:
 		return bpf_get_current_task();
@@ -75,13 +86,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(dctx, DTRACEFLT_BADADDR, ptr);
 		if (bpf_probe_read((void *)&ptr, 8,
 		    (const void *)(ptr + *parent_off)))
-			return -1;
+			return error(dctx, DTRACEFLT_BADADDR, ptr + *parent_off);
 		if (bpf_probe_read((void *)&val, 4,
 		    (const void *)(ptr + *tgid_off)))
-			return -1;
+			return error(dctx, DTRACEFLT_BADADDR, ptr + *tgid_off);
 
 		return (uint64_t)val;
 	}
@@ -106,10 +117,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(dctx, DTRACEFLT_ILLOP, 0);
 	}
 }
diff --git a/bpf/probe_error.c b/bpf/probe_error.c
new file mode 100644
index 00000000..b70a9c94
--- /dev/null
+++ b/bpf/probe_error.c
@@ -0,0 +1,38 @@
+// 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
+
+extern int64_t dt_error(dt_dctx_t *dctx);
+
+/*
+ * 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 void dt_probe_error(dt_dctx_t *dctx, uint64_t pc, uint64_t fault,
+			     uint64_t illval)
+{
+	dt_mstate_t	*mst = dctx->mst;
+
+	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;
+
+	dt_error(dctx);
+}
diff --git a/bpf/probe_error.h b/bpf/probe_error.h
new file mode 100644
index 00000000..d9e5e985
--- /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 void dt_probe_error(dt_dctx_t *dctx, uint64_t pc, uint64_t fault,
+			   uint64_t illval);
+
+#endif /* BPF_PROBE_ERRROR_H */
diff --git a/libdtrace/dt_as.c b/libdtrace/dt_as.c
index 4ca2f420..786fc22c 100644
--- a/libdtrace/dt_as.c
+++ b/libdtrace/dt_as.c
@@ -605,3 +605,49 @@ 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;
+
+#define DIFO_COPY_DATA(dtp, odp, dp, len, ptr) \
+	if ((odp)->len > 0) { \
+		size_t	tsiz = sizeof(typeof((dp)->ptr[0])); \
+		\
+		(dp)->len = (odp)->len; \
+		(dp)->ptr = dt_calloc((dtp), (dp)->len, tsiz); \
+		if ((dp)->ptr == NULL) \
+			goto no_mem; \
+		\
+		memcpy((dp)->ptr, (odp)->ptr, (dp)->len * tsiz); \
+	}
+
+	DIFO_COPY_DATA(dtp, odp, dp, dtdo_len, dtdo_buf);
+	DIFO_COPY_DATA(dtp, odp, dp, dtdo_strlen, dtdo_strtab);
+	DIFO_COPY_DATA(dtp, odp, dp, dtdo_varlen, dtdo_vartab);
+	DIFO_COPY_DATA(dtp, odp, dp, dtdo_brelen, dtdo_breltab);
+	DIFO_COPY_DATA(dtp, odp, dp, dtdo_krelen, dtdo_kreltab);
+	DIFO_COPY_DATA(dtp, odp, dp, dtdo_urelen, dtdo_ureltab);
+
+	dp->dtdo_ddesc = dt_datadesc_hold(odp->dtdo_ddesc);
+	dp->dtdo_flags = odp->dtdo_flags;
+
+	return dp;
+
+no_mem:
+	dt_free(dtp, dp->dtdo_buf);
+	dt_free(dtp, dp->dtdo_strtab);
+	dt_free(dtp, dp->dtdo_vartab);
+	dt_free(dtp, dp->dtdo_breltab);
+	dt_free(dtp, dp->dtdo_kreltab);
+	dt_free(dtp, dp->dtdo_ureltab);
+	dt_free(dtp, dp);
+
+	dt_set_errno(dtp, EDT_NOMEM);
+	return NULL;
+}
diff --git a/libdtrace/dt_bpf.c b/libdtrace/dt_bpf.c
index 1705c990..b313c6a9 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.
  */
@@ -274,19 +274,18 @@ dt_bpf_gmap_create(dtrace_hdl_t *dtp)
 }
 
 /*
- * Perform relocation processing on a program.
+ * Perform relocation processing for BPF maps in a program.
  */
 static void
-dt_bpf_reloc_prog(dtrace_hdl_t *dtp, const dt_probe_t *prp,
-		  const dtrace_difo_t *dp)
+dt_bpf_reloc_prog(dtrace_hdl_t *dtp, const dtrace_difo_t *dp)
 {
 	int			len = dp->dtdo_brelen;
 	const dof_relodesc_t	*rp = dp->dtdo_breltab;
+	struct bpf_insn		*text = dp->dtdo_buf;
 
 	for (; len != 0; len--, rp++) {
 		char		*name = &dp->dtdo_strtab[rp->dofr_name];
 		dt_ident_t	*idp = dt_idhash_lookup(dtp->dt_bpfsyms, name);
-		struct bpf_insn	*text = dp->dtdo_buf;
 		int		ioff = rp->dofr_offset /
 					sizeof(struct bpf_insn);
 		uint32_t	val = 0;
@@ -324,15 +323,12 @@ dt_bpf_load_prog(dtrace_hdl_t *dtp, const dt_probe_t *prp,
 	int				rc;
 
 	/*
-	 * Check whether there are any probe-specific relocations to be
-	 * performed.  If so, we need to modify the executable code.  This can
-	 * be done in-place since program loading is serialized.
-	 *
-	 * Relocations that are probe independent were already done at an
-	 * earlier time so we can ignore those.
+	 * Check whether there are any BPF specific relocations that may need to
+	 * be performed.  If so, we need to modify the executable code.  This
+	 * can be done in-place since program loading is serialized.
 	 */
 	if (dp->dtdo_brelen)
-		dt_bpf_reloc_prog(dtp, prp, dp);
+		dt_bpf_reloc_prog(dtp, dp);
 
 	memset(&attr, 0, sizeof(struct bpf_load_program_attr));
 
@@ -376,17 +372,77 @@ dt_bpf_load_prog(dtrace_hdl_t *dtp, const dt_probe_t *prp,
 	return rc;
 }
 
+/*
+ * Perform relocation processing on the ERROR probe program.
+ */
+static void
+dt_bpf_reloc_error_prog(dtrace_hdl_t *dtp, dtrace_difo_t *dp)
+{
+	int			len = dp->dtdo_brelen;
+	const dof_relodesc_t	*rp = dp->dtdo_breltab;
+	dof_relodesc_t		*nrp = dp->dtdo_breltab;
+	struct bpf_insn		*text = dp->dtdo_buf;
+
+	for (; len != 0; len--, rp++) {
+		char		*name = &dp->dtdo_strtab[rp->dofr_name];
+		dt_ident_t	*idp = dt_idhash_lookup(dtp->dt_bpfsyms, name);
+		int		ioff = rp->dofr_offset /
+					sizeof(struct bpf_insn);
+
+		/*
+		 * We need to turns "call dt_error" into a NOP.  We also remove
+		 * the relocation record because it is obsolete.
+		 */
+		if (idp != NULL && idp->di_kind == DT_IDENT_SYMBOL &&
+		    strcmp(idp->di_name, "dt_error") == 0) {
+			text[ioff] = BPF_NOP();
+			continue;
+		}
+
+		if (nrp != rp)
+			*nrp = *rp;
+
+		nrp++;
+	}
+
+	dp->dtdo_brelen -= rp - nrp;
+}
+
 int
 dt_bpf_load_progs(dtrace_hdl_t *dtp, uint_t cflags)
 {
 	dt_probe_t	*prp;
+	dtrace_difo_t	*dp;
+	dt_ident_t	*idp = dt_dlib_get_func(dtp, "dt_error");
+
+	assert(idp != NULL);
+
+	/*
+	 * First construct the ERROR probe program (to be included in probe
+	 * programs that may trigger a fault).
+	 *
+	 * After constructing the program, we need to patch up any calls to
+	 * dt_error because DTrace cannot handle faults in ERROR itself.
+	 */
+	dp = dt_program_construct(dtp, dtp->dt_error, cflags, idp);
+	if (dp == NULL)
+		return -1;
 
+	idp->di_flags |= DT_IDFLG_CGREG;	/* mark it as inline-ready */
+	dt_bpf_reloc_error_prog(dtp, dp);
+
+	/*
+	 * Now construct all the other programs.
+	 */
 	for (prp = dt_list_next(&dtp->dt_enablings); prp != NULL;
 	     prp = dt_list_next(prp)) {
-		dtrace_difo_t	*dp;
 		int		fd, rc;
 
-		dp = dt_program_construct(dtp, prp, cflags);
+		/* Already done. */
+		if (prp == dtp->dt_error)
+			continue;
+
+		dp = dt_program_construct(dtp, prp, cflags, NULL);
 		if (dp == NULL)
 			return -1;
 
diff --git a/libdtrace/dt_cc.c b/libdtrace/dt_cc.c
index 0ee1c381..e5ef9861 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.
 	 */
@@ -2205,7 +2211,7 @@ out:
 }
 
 static dtrace_difo_t *
-dt_construct(dtrace_hdl_t *dtp, dt_probe_t *prp, uint_t cflags)
+dt_construct(dtrace_hdl_t *dtp, dt_probe_t *prp, uint_t cflags, dt_ident_t *idp)
 {
 	dt_pcb_t	pcb;
 	dt_node_t	*tnp;
@@ -2266,6 +2272,12 @@ dt_construct(dtrace_hdl_t *dtp, dt_probe_t *prp, uint_t cflags)
 	dt_cg(yypcb, tnp);
 	dp = dt_as(yypcb);
 
+	/*
+	 * If we were called with an identifier, assign the DIFO to it.
+	 */
+	if (idp != NULL)
+		dt_ident_set_data(idp, dp);
+
 out:
 	if (dtp->dt_cdefs_fd != -1 &&
 	    (ftruncate(dtp->dt_cdefs_fd, 0) == -1 ||
@@ -2287,32 +2299,41 @@ out:
 
 static int
 dt_link_layout(dtrace_hdl_t *dtp, const dtrace_difo_t *dp, uint_t *pcp,
-	       uint_t *rcp, uint_t *vcp)
+	       uint_t *rcp, uint_t *vcp, dt_ident_t *idp)
 {
 	uint_t			pc = *pcp;
 	uint_t			len = dp->dtdo_brelen;
 	const dof_relodesc_t	*rp = dp->dtdo_breltab;
+	int			no_deps = 0;
+
+	if (idp != NULL) {
+		idp->di_flags |= DT_IDFLG_REF;
+		if (idp->di_flags & DT_IDFLG_CGREG)
+				no_deps = 1;
+	}
 
 	(*pcp) += dp->dtdo_len;
 	(*rcp) += len;
 	(*vcp) += dp->dtdo_varlen;
+
+	if (no_deps)
+		return pc;
+
 	for (; len != 0; len--, rp++) {
 		char            *name = &dp->dtdo_strtab[rp->dofr_name];
-		dt_ident_t      *idp = dt_dlib_get_func(dtp, name);
 		dtrace_difo_t   *rdp;
 		int		ipc;
 
+		idp = dt_dlib_get_func(dtp, name);
 		if (idp == NULL ||			/* not found */
 		    idp->di_kind != DT_IDENT_SYMBOL ||	/* not external sym */
 		    idp->di_flags & DT_IDFLG_REF)       /* already seen */
 			continue;
 
-		idp->di_flags |= DT_IDFLG_REF;
-
 		rdp = dt_dlib_get_func_difo(dtp, idp);
 		if (rdp == NULL)
 			return -1;
-		ipc = dt_link_layout(dtp, rdp, pcp, rcp, vcp);
+		ipc = dt_link_layout(dtp, rdp, pcp, rcp, vcp, idp);
 		if (ipc == -1)
 			return -1;
 		idp->di_id = ipc;
@@ -2323,7 +2344,7 @@ dt_link_layout(dtrace_hdl_t *dtp, const dtrace_difo_t *dp, uint_t *pcp,
 
 static int
 dt_link_construct(dtrace_hdl_t *dtp, const dt_probe_t *prp, dtrace_difo_t *dp,
-		  const dtrace_difo_t *sdp, dt_strtab_t *stab,
+		  dt_ident_t *idp, const dtrace_difo_t *sdp, dt_strtab_t *stab,
 		  uint_t *pcp, uint_t *rcp, uint_t *vcp, dtrace_epid_t epid,
 		  uint_t clid)
 {
@@ -2336,6 +2357,13 @@ dt_link_construct(dtrace_hdl_t *dtp, const dt_probe_t *prp, dtrace_difo_t *dp,
 	uint_t			len = sdp->dtdo_brelen;
 	const dof_relodesc_t	*rp = sdp->dtdo_breltab;
 	dof_relodesc_t		*nrp = &dp->dtdo_breltab[rc];
+	int			no_deps = 0;
+
+	if (idp != NULL) {
+		idp->di_flags |= DT_IDFLG_REF;
+		if (idp->di_flags & DT_IDFLG_CGREG)
+				no_deps = 1;
+	}
 
 	/*
 	 * Make sure there is enough room in the destination instruction buffer
@@ -2396,37 +2424,45 @@ dt_link_construct(dtrace_hdl_t *dtp, const dt_probe_t *prp, dtrace_difo_t *dp,
 	nrp = &dp->dtdo_breltab[rc];
 	for (; len != 0; len--, rp++, nrp++) {
 		const char	*name = &sdp->dtdo_strtab[rp->dofr_name];
-		dt_ident_t	*idp = dt_dlib_get_sym(dtp, name);
 		dtrace_difo_t	*rdp;
 		dtrace_epid_t	nepid;
 		int		ipc;
 
+		idp = dt_dlib_get_sym(dtp, name);
 		if (idp == NULL)			/* not found */
 			continue;
 
 		switch (idp->di_kind) {
 		case DT_IDENT_SCALAR:			/* constant */
+			if (no_deps) {
+				nrp->dofr_type = R_BPF_NONE;
+				continue;
+			}
+
 			switch (idp->di_id) {
 			case DT_CONST_EPID:
 				nrp->dofr_data = epid;
-				break;
+				continue;
 			case DT_CONST_PRID:
 				nrp->dofr_data = prp->desc->id;
-				break;
+				continue;
 			case DT_CONST_CLID:
 				nrp->dofr_data = clid;
-				break;
+				continue;
 			case DT_CONST_ARGC:
 				nrp->dofr_data = 0;	/* FIXME */
-				break;
+				continue;
 			}
 
-			break;
+			continue;
 		case DT_IDENT_SYMBOL:			/* BPF function */
-			if (idp->di_flags & DT_IDFLG_REF)
+			if (no_deps) {
+				nrp->dofr_data = rp->dofr_data + pc;
 				continue;
+			}
 
-			idp->di_flags |= DT_IDFLG_REF;
+			if (idp->di_flags & DT_IDFLG_REF)
+				continue;
 
 			rdp = dt_dlib_get_func_difo(dtp, idp);
 			if (rdp == NULL)
@@ -2437,7 +2473,7 @@ dt_link_construct(dtrace_hdl_t *dtp, const dt_probe_t *prp, dtrace_difo_t *dp,
 				clid++;
 			} else
 				nepid = 0;
-			ipc = dt_link_construct(dtp, prp, dp, rdp, stab,
+			ipc = dt_link_construct(dtp, prp, dp, idp, rdp, stab,
 						pcp, rcp, vcp, nepid, clid);
 			if (ipc == -1)
 				return -1;
@@ -2445,7 +2481,7 @@ dt_link_construct(dtrace_hdl_t *dtp, const dt_probe_t *prp, dtrace_difo_t *dp,
 			idp->di_id = ipc;
 			nrp->dofr_data = idp->di_id;	/* set value */
 
-			break;
+			continue;
 		default:
 			continue;
 		}
@@ -2497,7 +2533,8 @@ dt_link_resolve(dtrace_hdl_t *dtp, dtrace_difo_t *dp)
 }
 
 static int
-dt_link(dtrace_hdl_t *dtp, const dt_probe_t *prp, dtrace_difo_t *dp)
+dt_link(dtrace_hdl_t *dtp, const dt_probe_t *prp, dtrace_difo_t *dp,
+	dt_ident_t *idp)
 {
 	uint_t		insc = 0;
 	uint_t		relc = 0;
@@ -2510,7 +2547,7 @@ dt_link(dtrace_hdl_t *dtp, const dt_probe_t *prp, dtrace_difo_t *dp)
 	 * Determine the layout of the final (linked) DIFO, and calculate the
 	 * total instruction, relocation record, and variable table counts.
 	 */
-	rc = dt_link_layout(dtp, dp, &insc, &relc, &varc);
+	rc = dt_link_layout(dtp, dp, &insc, &relc, &varc, idp);
 	dt_dlib_reset(dtp, B_TRUE);
 	if (rc == -1)
 		goto fail;
@@ -2548,8 +2585,8 @@ dt_link(dtrace_hdl_t *dtp, const dt_probe_t *prp, dtrace_difo_t *dp)
 	if (stab == NULL)
 		goto nomem;
 
-	rc = dt_link_construct(dtp, prp, fdp, dp, stab, &insc, &relc, &varc,
-			       0, 0);
+	rc = dt_link_construct(dtp, prp, fdp, idp, dp, stab, &insc, &relc,
+			       &varc, 0, 0);
 	dt_dlib_reset(dtp, B_FALSE);
 	if (rc == -1)
 		goto fail;
@@ -2609,26 +2646,27 @@ nomem:
 }
 
 dtrace_difo_t *
-dt_program_construct(dtrace_hdl_t *dtp, dt_probe_t *prp, uint_t cflags)
+dt_program_construct(dtrace_hdl_t *dtp, dt_probe_t *prp, uint_t cflags,
+		     dt_ident_t *idp)
 {
 	dtrace_difo_t *dp;
 
 	assert(prp != NULL);
 
-	dp = dt_construct(dtp, prp, cflags);
+	dp = dt_construct(dtp, prp, cflags, idp);
 	if (dp == NULL)
 		return NULL;
 
 	if (cflags & DTRACE_C_DIFV && DT_DISASM(dtp, 2))
-		dt_dis_difo(dp, stderr, NULL);
+		dt_dis_difo(dp, stderr, idp);
 
-	if (dt_link(dtp, prp, dp) != 0) {
+	if (dt_link(dtp, prp, dp, idp) != 0) {
 		dt_difo_free(dtp, dp);
 		return NULL;
 	}
 
 	if (cflags & DTRACE_C_DIFV && DT_DISASM(dtp, 3))
-		dt_dis_difo(dp, stderr, NULL);
+		dt_dis_difo(dp, stderr, idp);
 
 	return dp;
 }
diff --git a/libdtrace/dt_cg.c b/libdtrace/dt_cg.c
index 858eb5a5..33ddb9f7 100644
--- a/libdtrace/dt_cg.c
+++ b/libdtrace/dt_cg.c
@@ -172,16 +172,20 @@ dt_cg_call_clause(dtrace_hdl_t *dtp, dt_ident_t *idp, dt_clause_arg_t *arg)
 	dt_irlist_t	*dlp = arg->dlp;
 
 	/*
-	 *	if (*dctx.act != act)	// ldw %r0, [%fp + DCTX_FP(DCTX_ACT)]
+	 *	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
-	 *				// 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));
+
+	/*
+	 *	dt_clause(dctx);	// mov %r1, %fp
+	 *				// add %r1, DCTX_FP(0)
+	 *				// call dt_program
+	 */
 	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);
@@ -190,12 +194,12 @@ dt_cg_call_clause(dtrace_hdl_t *dtp, dt_ident_t *idp, dt_clause_arg_t *arg)
 }
 
 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)
 {
 	dt_irlist_t	*dlp = &pcb->pcb_ir;
 	dt_clause_arg_t	arg = { dlp, act, pcb->pcb_exitlbl };
 
-	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 +222,7 @@ 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_cg_tramp_call_clauses(pcb, pcb->pcb_probe, DT_ACTIVITY_ACTIVE);
 	dt_cg_tramp_return(pcb);
 }
 
@@ -227,7 +231,7 @@ 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);
 
 	/*
 	 *	(*dctx.act)++;		// lddw %r0, [%fp + DCTX_FP(DCTX_ACT)]
@@ -241,6 +245,54 @@ dt_cg_tramp_epilogue_advance(dt_pcb_t *pcb, dt_activity_t act)
 	dt_cg_tramp_return(pcb);
 }
 
+static int
+dt_cg_tramp_error_call_clause(dtrace_hdl_t *dtp, dt_ident_t *idp,
+			      dt_irlist_t *dlp)
+{
+	/*
+	 *	dt_error_#(dctx);	// mov %r1, %r9
+	 *				// call dt_error_#
+	 */
+	emit(dlp,  BPF_MOV_REG(BPF_REG_1, BPF_REG_9));
+	emite(dlp, BPF_CALL_FUNC(idp->di_id), idp);
+
+	return 0;
+}
+
+/*
+ * Generate the trampoline BPF program for the ERROR probe.
+ *
+ * The trampoline BPF program for the ERROR probe is implemented as a function
+ * that calls the ERROR probe clauses one by one.  It must satisfy the
+ * signature:
+ *
+ *	int dt_error(dt_dctx_t *dctx)
+ */
+void
+dt_cg_tramp_error(dt_pcb_t *pcb)
+{
+	dtrace_hdl_t	*dtp = pcb->pcb_hdl;
+	dt_irlist_t	*dlp = &pcb->pcb_ir;
+
+	/*
+	 * int dt_error(dt_dctx_t *dctx)
+	 *				//     (%r1 = pointer to dt_dctx_t)
+	 * {
+	 *	int	rc;
+	 *				//     (%r9 = reserved reg for dctx)
+	 *				// mov %r9, %r1
+	 */
+	TRACE_REGSET("Trampoline: Begin");
+	emit(dlp, BPF_MOV_REG(BPF_REG_9, BPF_REG_1));
+
+	dt_probe_clause_iter(dtp, dtp->dt_error,
+			     (dt_clause_f *)dt_cg_tramp_error_call_clause, dlp);
+
+	emit(dlp, BPF_MOV_IMM(BPF_REG_0, 0));
+	emit(dlp, BPF_RETURN());
+	TRACE_REGSET("Trampoline: Begin");
+}
+
 /*
  * Generate the function prologue.
  *
@@ -339,7 +391,7 @@ dt_cg_prologue(dt_pcb_t *pcb, dt_node_t *pred)
 
 /*
  * Generate the function epilogue:
- *	4. If a fault was flagged, return 0.
+ *	4. If a fault was flagged, return the fault code.
  *	5. Submit the buffer to the perf event output buffer for the current
  *	   cpu.
  *	6. Return 0
@@ -363,19 +415,8 @@ dt_cg_epilogue(dt_pcb_t *pcb)
 		assert(buffers != NULL);
 
 		/*
-		 *	rc = dctx->mst->fault;	// lddw %r0, [%fp + DT_STK_DCTX]
-		 *				// lddw %r0, [%r0 + DCTX_MST]
-		 *				// lddw %r0, [%r0 + DMST_FAULT]
-		 *	if (rc != 0)
-		 *	    goto exit;		// jne %r0, 0, pcb->pcb_exitlbl
-		 */
-		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));
-
-		/*
-		 *	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]
@@ -384,7 +425,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
 		 *
 		 */
@@ -411,6 +452,43 @@ 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);
+
+	/*
+	 *	return dt_probe_error(
+	 *		dctx,		// lddw %r1, %fp, DT_STK_DCT
+	 *		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_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
@@ -1368,7 +1446,6 @@ dt_cg_load_var(dt_node_t *dst, dt_irlist_t *dlp, dt_regset_t *drp)
 			longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
 		if (idp->di_id < DIF_VAR_OTHER_UBASE) {	/* built-in var */
 			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, idp->di_id));
 			idp = dt_dlib_get_func(yypcb->pcb_hdl, "dt_get_bvar");
 		} else {
@@ -1383,7 +1460,8 @@ dt_cg_load_var(dt_node_t *dst, dt_irlist_t *dlp, dt_regset_t *drp)
 		if ((dst->dn_reg = dt_regset_alloc(drp)) == -1)
 			longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
 
-		emit(dlp, BPF_MOV_REG(dst->dn_reg, BPF_REG_0));
+		emit(dlp,  BPF_MOV_REG(dst->dn_reg, BPF_REG_0));
+
 		dt_regset_free(drp, BPF_REG_0);
 	}
 }
@@ -2811,14 +2889,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,
@@ -2900,6 +2991,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)
diff --git a/libdtrace/dt_cg.h b/libdtrace/dt_cg.h
index bf8a3f1d..daeaa9a1 100644
--- a/libdtrace/dt_cg.h
+++ b/libdtrace/dt_cg.h
@@ -23,6 +23,7 @@ extern void dt_cg_tramp_prologue_act(dt_pcb_t *pcb, dt_activity_t act);
 extern void dt_cg_tramp_prologue(dt_pcb_t *pcb);
 extern void dt_cg_tramp_epilogue(dt_pcb_t *pcb);
 extern void dt_cg_tramp_epilogue_advance(dt_pcb_t *pcb, dt_activity_t act);
+extern void dt_cg_tramp_error(dt_pcb_t *pcb);
 
 #ifdef	__cplusplus
 }
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_dlibs.c b/libdtrace/dt_dlibs.c
index b85c1962..8536704d 100644
--- a/libdtrace/dt_dlibs.c
+++ b/libdtrace/dt_dlibs.c
@@ -54,6 +54,7 @@ static const dt_ident_t		dt_bpf_symbols[] = {
 	/* BPF library (external) functions */
 	DT_BPF_SYMBOL(dt_agg_lqbin, DT_IDENT_SYMBOL),
 	DT_BPF_SYMBOL(dt_agg_qbin, DT_IDENT_SYMBOL),
+	DT_BPF_SYMBOL(dt_error, DT_IDENT_SYMBOL),
 	DT_BPF_SYMBOL(dt_get_bvar, DT_IDENT_SYMBOL),
 	DT_BPF_SYMBOL(dt_get_gvar, DT_IDENT_SYMBOL),
 	DT_BPF_SYMBOL(dt_get_string, DT_IDENT_SYMBOL),
@@ -394,6 +395,8 @@ get_symbols(dtrace_hdl_t *dtp, Elf *elf, int syms_idx, int strs_idx,
 			 * will get rid of these fake function symbols.
 			 */
 			idp = dt_dlib_get_map(dtp, name);
+			if (idp == NULL)
+				idp = dt_dlib_get_func(dtp, name);
 			if (idp == NULL) {
 				dt_dlib_error(dtp, D_IDENT_UNDEF,
 					      "undefined symbol %s in BPF dlib",
diff --git a/libdtrace/dt_dof.c b/libdtrace/dt_dof.c
index c78e38a3..6827464a 100644
--- a/libdtrace/dt_dof.c
+++ b/libdtrace/dt_dof.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.
  */
@@ -891,8 +891,10 @@ dtrace_getopt_dof(dtrace_hdl_t *dtp)
 void *
 dtrace_geterr_dof(dtrace_hdl_t *dtp)
 {
+#if 0
 	if (dtp->dt_errprog != NULL)
 		return dtrace_dof_create(dtp, dtp->dt_errprog, 0);
+#endif
 
 	dt_set_errno(dtp, EDT_BADERROR);
 	return NULL;
diff --git a/libdtrace/dt_handle.c b/libdtrace/dt_handle.c
index 52ff27a8..5d450084 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,23 +49,21 @@ 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));
 
-	stp = dt_list_next(&pgp->dp_stmts);
-	assert(stp != NULL);
-
-	edp = stp->ds_desc->dtsd_ecbdesc;
-	assert(edp != NULL);
-	edp->dted_uarg = DT_ECB_ERROR;
-#endif
+	if (dtrace_program_exec(dtp, pgp, NULL) == -1)
+		return -1;		/* errno already set */
 
 out:
 	dtp->dt_errhdlr = hdlr;
 	dtp->dt_errarg = arg;
+#if 0
 	dtp->dt_errprog = pgp;
+#endif
 
 	return 0;
 }
diff --git a/libdtrace/dt_impl.h b/libdtrace/dt_impl.h
index e1aab88d..8780e7c7 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 */
@@ -350,7 +352,9 @@ struct dtrace_hdl {
 	int dt_aggmap_fd;	/* file descriptor for the 'aggs' BPF map */
 	dtrace_handle_err_f *dt_errhdlr; /* error handler, if any */
 	void *dt_errarg;	/* error handler argument */
+#if 0
 	dtrace_prog_t *dt_errprog; /* error handler program, if any */
+#endif
 	dtrace_handle_drop_f *dt_drophdlr; /* drop handler, if any */
 	void *dt_droparg;	/* drop handler argument */
 	dtrace_handle_proc_f *dt_prochdlr; /* proc handler, if any */
@@ -723,11 +727,13 @@ extern void *dt_compile(dtrace_hdl_t *dtp, int context,
 			dtrace_probespec_t pspec, void *arg, uint_t cflags,
 			int argc, char *const argv[], FILE *fp, const char *s);
 extern dtrace_difo_t *dt_program_construct(dtrace_hdl_t *dtp,
-					   struct dt_probe *prp, uint_t cflags);
+					   struct dt_probe *prp, uint_t cflags,
+					   dt_ident_t *idp);
 
 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,
 			const dt_ident_t *idp);
@@ -736,6 +742,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..28d824b3 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.
  */
diff --git a/libdtrace/dt_probe.c b/libdtrace/dt_probe.c
index 23ee49ad..0190975e 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,84 @@ 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)
+ */
+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;
+
+	/*
+	 * Copy the DIFO.
+	 */
+	ndp = dt_difo_copy(dtp, dp);
+	if (ndp == NULL)
+		goto no_mem;
+
+	/*
+	 * 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 + strlen("dt_clause_"));
+
+	/*
+	 * 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..fcd91400 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;
 }
@@ -74,10 +80,20 @@ static void trampoline(dt_pcb_t *pcb)
 {
 	int		i;
 	dt_irlist_t	*dlp = &pcb->pcb_ir;
-	dt_activity_t	act;
+	dt_activity_t	act = DT_ACTIVITY_ACTIVE;
 	int		adv_act;
 	uint32_t	key = 0;
 
+	/*
+	 * The ERROR probe isn't really a trace event that a BPF program is
+	 * attached to.  Its entire trampoline program is provided by the code
+	 * generator.
+	 */
+	if (strcmp(pcb->pcb_probe->desc->prb, "ERROR") == 0) {
+		dt_cg_tramp_error(pcb);
+		return;
+	}
+
 	/*
 	 * The BEGIN probe should only run when the activity state is INACTIVE.
 	 * At the end of the trampoline (after executing any clauses), the
@@ -95,9 +111,6 @@ static void trampoline(dt_pcb_t *pcb)
 	 * When the END probe is triggered, we need to record the CPU it runs
 	 * on in state[DT_STATE_ENDEDON] to ensure that we know which trace
 	 * data buffer to process last.
-	 *
-	 * Any other probe requires the state to be ACTIVE, and does not change
-	 * the state.
 	 */
 	if (strcmp(pcb->pcb_probe->desc->prb, "BEGIN") == 0) {
 		act = DT_ACTIVITY_INACTIVE;
@@ -107,9 +120,6 @@ static void trampoline(dt_pcb_t *pcb)
 		act = DT_ACTIVITY_DRAINING;
 		adv_act = 1;
 		key = DT_STATE_ENDEDON;
-	} else {
-		act = DT_ACTIVITY_ACTIVE;
-		adv_act = 0;
 	}
 
 	dt_cg_tramp_prologue_act(pcb, act);
diff --git a/libdtrace/dt_work.c b/libdtrace/dt_work.c
index c9142114..7eb13da2 100644
--- a/libdtrace/dt_work.c
+++ b/libdtrace/dt_work.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.
  */
@@ -149,18 +149,6 @@ dtrace_go(dtrace_hdl_t *dtp, uint_t cflags)
 	if (dtp->dt_active)
 		return dt_set_errno(dtp, EINVAL);
 
-	/*
-	 * If a dtrace:::ERROR program and callback are registered, enable the
-	 * program before we start tracing.  If this fails for a vector open
-	 * with ENOTTY, we permit dtrace_go() to succeed so that vector clients
-	 * such as mdb's dtrace module can execute the rest of dtrace_go() even
-	 * though they do not provide support for the DTRACEIOC_ENABLE ioctl.
-	 */
-	if (dtp->dt_errprog != NULL &&
-	    dtrace_program_exec(dtp, dtp->dt_errprog, NULL) == -1 && (
-	    dtp->dt_errno != ENOTTY || dtp->dt_vector == NULL))
-		return -1;		/* dt_errno has been set for us */
-
 	/*
 	 * Create the global BPF maps.  This is done only once regardless of
 	 * how many programs there are.
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);
diff --git a/test/unittest/error/tst.DTRACEFLT_BADADDR.null_ptr_field.d b/test/unittest/error/tst.DTRACEFLT_BADADDR.null_ptr_field.d
new file mode 100644
index 00000000..f764e84c
--- /dev/null
+++ b/test/unittest/error/tst.DTRACEFLT_BADADDR.null_ptr_field.d
@@ -0,0 +1,27 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 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.
+ */
+
+/*
+ * ASSERTION: Test DTRACEFLT_BADADDR error reporting.
+ *
+ * SECTION: dtrace Provider
+ */
+
+#pragma D option quiet
+
+BEGIN
+{
+	myepid = epid;
+	trace(((struct task_struct *)NULL)->pid);
+	exit(1);
+}
+
+ERROR
+{
+	exit(arg1 != myepid || arg2 != 1 || arg3 != -1 ||
+	     arg4 != DTRACEFLT_BADADDR || arg5 != 0);
+}
diff --git a/test/unittest/error/tst.DTRACEFLT_BADADDR.null_ptr_field.r b/test/unittest/error/tst.DTRACEFLT_BADADDR.null_ptr_field.r
new file mode 100644
index 00000000..ef424aed
--- /dev/null
+++ b/test/unittest/error/tst.DTRACEFLT_BADADDR.null_ptr_field.r
@@ -0,0 +1,3 @@
+
+-- @@stderr --
+dtrace: error on enabled probe ID 3 (ID 1: dtrace:::BEGIN): invalid address ({ptr}) in action #1
diff --git a/test/unittest/error/tst.clause_scope-begin-ended.d b/test/unittest/error/tst.clause_scope-begin-ended.d
new file mode 100644
index 00000000..29dd0aef
--- /dev/null
+++ b/test/unittest/error/tst.clause_scope-begin-ended.d
@@ -0,0 +1,34 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 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.
+ */
+/* @@xfail: dtv2 */
+/*
+ * ASSERTION: A fault that triggers the ERROR probe terminates the execution of
+ *            the current clause, but other clauses for the same probe should
+ *            still be executed.  This tests the special case where the fault
+ *            occurs in the BEGIN probe and probing ends before we process the
+ *            probe data.
+ *
+ * SECTION: dtrace Provider
+ */
+
+#pragma D option quiet
+
+ERROR
+{
+	printf("Error fired\n");
+}
+
+BEGIN
+{
+	trace(((struct task_struct *)NULL)->pid);
+}
+
+BEGIN
+{
+	printf("Clause executed\n");
+	exit(0);
+}
diff --git a/test/unittest/error/tst.clause_scope-begin-ended.r b/test/unittest/error/tst.clause_scope-begin-ended.r
new file mode 100644
index 00000000..839d9004
--- /dev/null
+++ b/test/unittest/error/tst.clause_scope-begin-ended.r
@@ -0,0 +1,5 @@
+Error fired
+Clause executed
+
+-- @@stderr --
+dtrace: error on enabled probe ID 3 (ID 1: dtrace:::BEGIN): invalid address ({ptr}) in action #1
diff --git a/test/unittest/error/tst.clause_scope-begin.d b/test/unittest/error/tst.clause_scope-begin.d
new file mode 100644
index 00000000..c1195854
--- /dev/null
+++ b/test/unittest/error/tst.clause_scope-begin.d
@@ -0,0 +1,38 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 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.
+ */
+
+/*
+ * ASSERTION: A fault that triggers the ERROR probe terminates the execution of
+ *            the current clause, but other clauses for the same probe should
+ *            still be executed.  This tests the special case where the fault
+ *            occurs in the BEGIN probe and the probe data is processed before
+ *            probing ends.
+ *
+ * SECTION: dtrace Provider
+ */
+
+#pragma D option quiet
+
+ERROR
+{
+	printf("Error fired\n");
+}
+
+BEGIN
+{
+	trace(((struct task_struct *)NULL)->pid);
+}
+
+BEGIN
+{
+	printf("Clause executed\n");
+}
+
+tick-1s
+{
+	exit(0);
+}
diff --git a/test/unittest/error/tst.clause_scope-begin.r b/test/unittest/error/tst.clause_scope-begin.r
new file mode 100644
index 00000000..839d9004
--- /dev/null
+++ b/test/unittest/error/tst.clause_scope-begin.r
@@ -0,0 +1,5 @@
+Error fired
+Clause executed
+
+-- @@stderr --
+dtrace: error on enabled probe ID 3 (ID 1: dtrace:::BEGIN): invalid address ({ptr}) in action #1
diff --git a/test/unittest/error/tst.clause_scope-regular.d b/test/unittest/error/tst.clause_scope-regular.d
new file mode 100644
index 00000000..cda9f70a
--- /dev/null
+++ b/test/unittest/error/tst.clause_scope-regular.d
@@ -0,0 +1,32 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 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.
+ */
+
+/*
+ * ASSERTION: A fault that triggers the ERROR probe terminates the execution of
+ *            the current clause, but other clauses for the same probe should
+ *            still be executed.
+ *
+ * SECTION: dtrace Provider
+ */
+
+#pragma D option quiet
+
+ERROR
+{
+	printf("Error fired\n");
+}
+
+tick-10ms
+{
+	trace(((struct task_struct *)NULL)->pid);
+}
+
+tick-10ms
+{
+	printf("Clause executed\n");
+	exit(0);
+}
diff --git a/test/unittest/error/tst.clause_scope-regular.r b/test/unittest/error/tst.clause_scope-regular.r
new file mode 100644
index 00000000..6e558412
--- /dev/null
+++ b/test/unittest/error/tst.clause_scope-regular.r
@@ -0,0 +1,5 @@
+Error fired
+Clause executed
+
+-- @@stderr --
+dtrace: error on enabled probe ID 3 (ID 72958: profile:::tick-10ms): invalid address ({ptr}) in action #1
diff --git a/test/unittest/error/tst.error.d b/test/unittest/error/tst.error.d
index 1d0b87c5..6117a41b 100644
--- a/test/unittest/error/tst.error.d
+++ b/test/unittest/error/tst.error.d
@@ -1,20 +1,16 @@
 /*
  * 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.
  */
-/* @@xfail: dtv2 */
 
 /*
- * ASSERTION:
- *	To fire ERROR probe
+ * ASSERTION: Test ERROR probe firing.
  *
  * SECTION: dtrace Provider
- *
  */
 
-
 #pragma D option quiet
 
 ERROR
@@ -25,5 +21,6 @@ ERROR
 
 BEGIN
 {
-	*(char *)NULL;
+	trace(((struct task_struct *)NULL)->pid);
+	exit(1);
 }
diff --git a/test/unittest/error/tst.error.r b/test/unittest/error/tst.error.r
index 190551ee..b2ceedd6 100644
--- a/test/unittest/error/tst.error.r
+++ b/test/unittest/error/tst.error.r
@@ -1,4 +1,4 @@
 Error fired
 
 -- @@stderr --
-dtrace: error on enabled probe ID 2 (ID 1: dtrace:::BEGIN): invalid address ({ptr}) in action #1 at DIF offset 16
+dtrace: error on enabled probe ID 3 (ID 1: dtrace:::BEGIN): invalid address ({ptr}) in action #1
diff --git a/test/unittest/error/tst.errorend.d b/test/unittest/error/tst.errorend.d
index 07171752..fe6dcaf6 100644
--- a/test/unittest/error/tst.errorend.d
+++ b/test/unittest/error/tst.errorend.d
@@ -1,20 +1,16 @@
 /*
  * 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.
  */
-/* @@xfail: dtv2 */
 
 /*
- * ASSERTION:
- *	Flow of ERROR probe
+ * ASSERTION: Ensure the END probe still fires after an ERROR was reported.
  *
  * SECTION: dtrace Provider
- *
  */
 
-
 #pragma D option quiet
 
 ERROR
@@ -30,5 +26,6 @@ END
 
 BEGIN
 {
-	*(char *)NULL;
+	trace(((struct task_struct *)NULL)->pid);
+	exit(1);
 }
diff --git a/test/unittest/error/tst.errorend.r b/test/unittest/error/tst.errorend.r
index 7a4184e4..ea8f1c36 100644
--- a/test/unittest/error/tst.errorend.r
+++ b/test/unittest/error/tst.errorend.r
@@ -2,4 +2,4 @@ Error fired
 End fired after exit
 
 -- @@stderr --
-dtrace: error on enabled probe ID 3 (ID 1: dtrace:::BEGIN): invalid address ({ptr}) in action #1 at DIF offset 16
+dtrace: error on enabled probe ID 3 (ID 1: dtrace:::BEGIN): invalid address ({ptr}) in action #1
-- 
2.28.0




More information about the DTrace-devel mailing list