[DTrace-devel] [PATCH 1/4] Implement strlen() based on bpf_probe_read_str()

Kris Van Hees kris.van.hees at oracle.com
Wed Jan 26 16:28:55 UTC 2022


The bpf_probe_read_atr() BPF helper returns the number of characters
it copies (incl. the terminating NUL byte), up to the maximum number
passed as one of the arguments.  This can be used to determine the
length of a string.

This implementation makes use of the unused space at the end of the
string constant table, which is sized so that it can contain the
largest string possible.

The implementation of substr) is adjusted to reflect this change as
well.  Rather than calling the strlen() function, it uses the BPF
helper directly.

This patch also introduces tests for the strlen() function.

Signed-off-by: Kris Van Hees <kris.van.hees at oracle.com>
---
 bpf/strlen.c                                  | 19 ++++--
 bpf/substr.S                                  | 17 +++--
 include/bpf-lib.h                             | 22 ++++++-
 libdtrace/dt_bpf.c                            |  8 +--
 libdtrace/dt_cg.c                             | 63 ++++++++-----------
 .../funcs/strlen/err.D_PROTO_ARG.non_string.d | 18 ++++++
 .../strlen/err.D_PROTO_LEN.missing_arg.d      | 18 ++++++
 .../strlen/err.D_PROTO_LEN.too_many_args.d    | 18 ++++++
 test/unittest/funcs/strlen/tst.basic.d        | 37 +++++++++++
 test/unittest/funcs/strlen/tst.basic.r        | 15 +++++
 test/unittest/funcs/strlen/tst.capped-sizw.d  | 25 ++++++++
 .../strlen/tst.empty.d}                       | 15 +++--
 test/unittest/funcs/strlen/tst.null.d         | 25 ++++++++
 test/unittest/funcs/strlen/tst.null.r         |  3 +
 14 files changed, 243 insertions(+), 60 deletions(-)
 create mode 100644 test/unittest/funcs/strlen/err.D_PROTO_ARG.non_string.d
 create mode 100644 test/unittest/funcs/strlen/err.D_PROTO_LEN.missing_arg.d
 create mode 100644 test/unittest/funcs/strlen/err.D_PROTO_LEN.too_many_args.d
 create mode 100644 test/unittest/funcs/strlen/tst.basic.d
 create mode 100644 test/unittest/funcs/strlen/tst.basic.r
 create mode 100644 test/unittest/funcs/strlen/tst.capped-sizw.d
 rename test/unittest/{strlen/tst.strlen1.d => funcs/strlen/tst.empty.d} (50%)
 create mode 100644 test/unittest/funcs/strlen/tst.null.d
 create mode 100644 test/unittest/funcs/strlen/tst.null.r

diff --git a/bpf/strlen.c b/bpf/strlen.c
index b2a8c740..889b2949 100644
--- a/bpf/strlen.c
+++ b/bpf/strlen.c
@@ -1,8 +1,12 @@
 // SPDX-License-Identifier: GPL-2.0
 /*
- * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved.
  */
+#include <linux/bpf.h>
+#include <bpf-helpers.h>
 #include <stdint.h>
+#include <dt_dctx.h>
+#include <bpf-lib.h>
 
 #define DT_STRLEN_BYTES	2
 
@@ -10,6 +14,9 @@
 # define noinline	__attribute__((noinline))
 #endif
 
+extern uint64_t STBSZ;
+extern uint64_t STRSZ;
+
 noinline void dt_strlen_store(uint64_t val, char *str)
 {
 	uint8_t		*buf = (uint8_t *)str;
@@ -18,9 +25,13 @@ noinline void dt_strlen_store(uint64_t val, char *str)
 	buf[1] = (uint8_t)(val & 0xff);
 }
 
-noinline uint64_t dt_strlen(const char *str)
+noinline uint64_t dt_strlen(const dt_dctx_t *dctx, const char *str)
 {
-	const uint8_t	*buf = (const uint8_t *)str;
+	char	*tmp = dctx->strtab + (uint64_t)&STBSZ;
+	int64_t	len;
+
+	len = bpf_probe_read_str(tmp, (uint64_t)&STRSZ + 1, str + DT_STRLEN_BYTES);
+	set_not_neg_bound(len);
 
-	return ((uint64_t)buf[0] << 8) + (uint64_t)buf[1];
+	return len - 1;		/* bpf_probe_read_str() never returns 0 */
 }
diff --git a/bpf/substr.S b/bpf/substr.S
index bc6133c9..a6cf5a57 100644
--- a/bpf/substr.S
+++ b/bpf/substr.S
@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
 /*
- * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved.
  */
 
 #define DT_STRLEN_BYTES		2
@@ -48,10 +48,17 @@ dt_substr :
 	 * the length we got.  (The length is initialized in %r8 as the default
 	 * string length.)
 	 */
-	ldxdw	%r1, [%fp+-8]
-	call	dt_strlen		/* len = dt_strlen(src) */
-	jeq	%r0, 0, .Lempty		/* if (len == 0) goto Lempty; */
-	jge	%r0, %r8, .Llen_set	/* if (len >= STRSZ) goto Llen_set; */
+	mov	%r3, %r2
+	add	%r3, DT_STRLEN_BYTES
+	mov	%r2, %r8
+	add	%r8, 1
+	call	BPF_FUNC_probe_read_str	/*
+					 * len = probe_read_str(
+					 *	   dst, STRSZ + 1,
+					 *	   &src[DT_STRLEN_BYTES]);
+					 */
+	jslt	%r0, 0, .Lempty		/* if (len < 0) goto Lempty; */
+	sub	%r0, 1			/* len--; */
 	mov	%r8, %r0		/* %r8 = len */
 .Llen_set:
 
diff --git a/include/bpf-lib.h b/include/bpf-lib.h
index f7885d2a..d7078da5 100644
--- a/include/bpf-lib.h
+++ b/include/bpf-lib.h
@@ -1,6 +1,6 @@
 /*
  * Oracle Linux DTrace.
- * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2021, 2022, 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.
  */
@@ -9,7 +9,7 @@
 #define BPF_LIB_H
 
 /*
- * Explicit inline assembler to implement an upper bound check:
+ * Explicit inline assembler to implement a dynamic upper bound check:
  *
  *	if (var > bnd)
  *		var = bnd;
@@ -28,7 +28,7 @@
 	);
 
 /*
- * Explicit inline assembler to implement a lower bound check:
+ * Explicit inline assembler to implement a dynamic lower bound check:
  *
  *	if (var < bnd)
  *		var = bnd;
@@ -46,4 +46,20 @@
 		: /* no clobbers */ \
 	);
 
+/*
+ * Explicit inline assembler to implement a non-negative bound check:
+ *
+ *	if (var < 0)
+ *		var = 0;
+ */
+#define set_not_neg_bound(var) \
+	asm ("jsge %0, 0, 1f\n\t" \
+             "mov %0, 0\n\t" \
+             "1:" \
+                : "+r" (var) \
+                : /* no inputs */ \
+                : /* no clobbers */ \
+        );
+
+
 #endif /* BPF_LIB_H */
diff --git a/libdtrace/dt_bpf.c b/libdtrace/dt_bpf.c
index 50343057..cfd7c6d7 100644
--- a/libdtrace/dt_bpf.c
+++ b/libdtrace/dt_bpf.c
@@ -1,6 +1,6 @@
 /*
  * Oracle Linux DTrace.
- * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2022, 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.
  */
@@ -313,11 +313,11 @@ dt_bpf_gmap_create(dtrace_hdl_t *dtp)
 	/*
 	 * We need to create the global (consolidated) string table.  We store
 	 * the actual length (for in-code BPF validation purposes) but augment
-	 * it by the maximum string size to determine the size of the BPF map
-	 * value that is used to store the strtab.
+	 * it by the maximum string storage size to determine the size of the
+	 * BPF map value that is used to store the strtab.
 	 */
 	dtp->dt_strlen = dt_strtab_size(dtp->dt_ccstab);
-	stabsz = dtp->dt_strlen + strsize;
+	stabsz = dtp->dt_strlen + strsize + 1;
 	strtab = dt_zalloc(dtp, stabsz);
 	if (strtab == NULL)
 		return dt_set_errno(dtp, EDT_NOMEM);
diff --git a/libdtrace/dt_cg.c b/libdtrace/dt_cg.c
index e233e757..0225832d 100644
--- a/libdtrace/dt_cg.c
+++ b/libdtrace/dt_cg.c
@@ -755,39 +755,6 @@ dt_cg_memcpy(dt_irlist_t *dlp, dt_regset_t *drp, int dst, int src, size_t size)
 	dt_regset_free(drp, BPF_REG_0);
 }
 
-static void
-dt_cg_strlen(dt_irlist_t *dlp, dt_regset_t *drp, int dst, int src)
-{
-	dt_ident_t	*idp = dt_dlib_get_func(yypcb->pcb_hdl, "dt_strlen");
-	size_t		size = yypcb->pcb_hdl->dt_options[DTRACEOPT_STRSIZE];
-	uint_t		lbl_ok = dt_irlist_label(dlp);
-
-	TRACE_REGSET("        strlen:Begin");
-
-	assert(idp != NULL);
-	if (dt_regset_xalloc_args(drp) == -1)
-		longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
-
-	if (src != BPF_REG_1)
-		emit(dlp,  BPF_MOV_REG(BPF_REG_1, src));
-	if (dst != BPF_REG_0)
-		dt_regset_xalloc(drp, BPF_REG_0);
-
-	emite(dlp, BPF_CALL_FUNC(idp->di_id), idp);
-	dt_regset_free_args(drp);
-	emit(dlp,  BPF_BRANCH_IMM(BPF_JLE, BPF_REG_0, size, lbl_ok));
-	emit(dlp,  BPF_MOV_IMM(BPF_REG_0, size));
-	emitl(dlp, lbl_ok,
-		   BPF_NOP());
-
-	if (dst != BPF_REG_0) {
-		emit(dlp, BPF_MOV_REG(dst, BPF_REG_0));
-		dt_regset_free(drp, BPF_REG_0);
-	}
-
-	TRACE_REGSET("        strlen:End  ");
-}
-
 static void
 dt_cg_spill_store(int reg)
 {
@@ -3763,10 +3730,34 @@ dt_cg_subr_strrchr(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp)
 static void
 dt_cg_subr_strlen(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp)
 {
+	dt_ident_t	*idp = dt_dlib_get_func(yypcb->pcb_hdl, "dt_strlen");
+	dt_node_t	*str = dnp->dn_args;
+
+	assert(idp != NULL);
+
 	TRACE_REGSET("    subr-strlen:Begin");
-	dt_cg_node(dnp->dn_args, dlp, drp);
-	dnp->dn_reg = dnp->dn_args->dn_reg;
-	dt_cg_strlen(dlp, drp, dnp->dn_reg, dnp->dn_args->dn_reg);
+
+	dt_cg_node(str, dlp, drp);
+	dt_cg_check_notnull(dlp, drp, str->dn_reg);
+	dnp->dn_reg = str->dn_reg;		/* re-use register */
+
+	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_REG(BPF_REG_2, str->dn_reg));
+	dt_regset_free(drp, str->dn_reg);
+	dt_cg_tstring_free(yypcb, str);
+	dt_regset_xalloc(drp, BPF_REG_0);
+	emite(dlp,BPF_CALL_FUNC(idp->di_id), idp);
+
+	dt_regset_free_args(drp);
+	dnp->dn_reg = dt_regset_alloc(drp);
+	if (dnp->dn_reg == -1)
+		longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
+	emit(dlp, BPF_MOV_REG(dnp->dn_reg, BPF_REG_0));
+	dt_regset_free(drp, BPF_REG_0);
+
 	TRACE_REGSET("    subr-strlen:End  ");
 }
 
diff --git a/test/unittest/funcs/strlen/err.D_PROTO_ARG.non_string.d b/test/unittest/funcs/strlen/err.D_PROTO_ARG.non_string.d
new file mode 100644
index 00000000..a9f2a38c
--- /dev/null
+++ b/test/unittest/funcs/strlen/err.D_PROTO_ARG.non_string.d
@@ -0,0 +1,18 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2022, 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: The argument to strlen() should be a string.
+ *
+ * SECTION: Actions and Subroutines/strlen()
+ */
+
+BEGIN
+{
+	strlen(12);
+	exit(0);
+}
diff --git a/test/unittest/funcs/strlen/err.D_PROTO_LEN.missing_arg.d b/test/unittest/funcs/strlen/err.D_PROTO_LEN.missing_arg.d
new file mode 100644
index 00000000..9467240d
--- /dev/null
+++ b/test/unittest/funcs/strlen/err.D_PROTO_LEN.missing_arg.d
@@ -0,0 +1,18 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2022, 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: strlen() requires an argument
+ *
+ * SECTION: Actions and Subroutines/strlen()
+ */
+
+BEGIN
+{
+	strlen();
+	exit(0);
+}
diff --git a/test/unittest/funcs/strlen/err.D_PROTO_LEN.too_many_args.d b/test/unittest/funcs/strlen/err.D_PROTO_LEN.too_many_args.d
new file mode 100644
index 00000000..c0e8fd13
--- /dev/null
+++ b/test/unittest/funcs/strlen/err.D_PROTO_LEN.too_many_args.d
@@ -0,0 +1,18 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2022, 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: strlen() accepts exactly one argument
+ *
+ * SECTION: Actions and Subroutines/strlen()
+ */
+
+BEGIN
+{
+	strlen("", 1);
+	exit(0);
+}
diff --git a/test/unittest/funcs/strlen/tst.basic.d b/test/unittest/funcs/strlen/tst.basic.d
new file mode 100644
index 00000000..e2ae275b
--- /dev/null
+++ b/test/unittest/funcs/strlen/tst.basic.d
@@ -0,0 +1,37 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2022, 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: The strlen() subroutine returns the correct result.
+ *
+ * SECTION: Actions and Subroutines/strlen()
+ */
+
+/* @@runtest-opts: -C */
+
+#pragma D option quiet
+
+#define TEST(s)	printf("%60s %d\n", (s), strlen(s))
+
+BEGIN
+{
+	TEST("1");
+	TEST("12");
+	TEST("123");
+	TEST("12345");
+	TEST("123456");
+	TEST("1234567");
+	TEST("12345678");
+	TEST("123456789");
+	TEST("1234567890");
+	TEST("12345678901234567890");
+	TEST("123456789012345678901234567890");
+	TEST("1234567890123456789012345678901234567890");
+	TEST("12345678901234567890123456789012345678901234567890");
+	TEST("123456789012345678901234567890123456789012345678901234567890");
+	exit(0);
+}
diff --git a/test/unittest/funcs/strlen/tst.basic.r b/test/unittest/funcs/strlen/tst.basic.r
new file mode 100644
index 00000000..822e67a6
--- /dev/null
+++ b/test/unittest/funcs/strlen/tst.basic.r
@@ -0,0 +1,15 @@
+                                                           1 1
+                                                          12 2
+                                                         123 3
+                                                       12345 5
+                                                      123456 6
+                                                     1234567 7
+                                                    12345678 8
+                                                   123456789 9
+                                                  1234567890 10
+                                        12345678901234567890 20
+                              123456789012345678901234567890 30
+                    1234567890123456789012345678901234567890 40
+          12345678901234567890123456789012345678901234567890 50
+123456789012345678901234567890123456789012345678901234567890 60
+
diff --git a/test/unittest/funcs/strlen/tst.capped-sizw.d b/test/unittest/funcs/strlen/tst.capped-sizw.d
new file mode 100644
index 00000000..4e473236
--- /dev/null
+++ b/test/unittest/funcs/strlen/tst.capped-sizw.d
@@ -0,0 +1,25 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2022, 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: strlen() obeys the maximum string size setting
+ *
+ * SECTION: Actions and Subroutines/strlen()
+ */
+
+#pragma D option strsize=5
+#pragma D option quiet
+
+BEGIN
+{
+	exit(strlen("123456789") == 5 ? 0 : 1);
+}
+
+ERROR
+{
+	exit(1);
+}
diff --git a/test/unittest/strlen/tst.strlen1.d b/test/unittest/funcs/strlen/tst.empty.d
similarity index 50%
rename from test/unittest/strlen/tst.strlen1.d
rename to test/unittest/funcs/strlen/tst.empty.d
index aed5623c..ede41c36 100644
--- a/test/unittest/strlen/tst.strlen1.d
+++ b/test/unittest/funcs/strlen/tst.empty.d
@@ -1,25 +1,24 @@
 /*
  * Oracle Linux DTrace.
- * Copyright (c) 2006, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2022, 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 the strlen() subroutine.
+ * ASSERTION: strlen("") is 0
  *
  * SECTION: Actions and Subroutines/strlen()
- *
  */
 
+#pragma D option quiet
+
 BEGIN
-/strlen("I like DTrace") == 13/
 {
-	correct = 1;
+	exit(strlen(""));
 }
 
-BEGIN
+ERROR
 {
-	exit(correct ? 0 : 1);
+	exit(1);
 }
diff --git a/test/unittest/funcs/strlen/tst.null.d b/test/unittest/funcs/strlen/tst.null.d
new file mode 100644
index 00000000..d2bff0f6
--- /dev/null
+++ b/test/unittest/funcs/strlen/tst.null.d
@@ -0,0 +1,25 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2022, 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: Passing NULL (0) as argument to strlen() triggers an error.
+ *
+ * SECTION: Actions and Subroutines/strlen()
+ */
+
+#pragma D option quiet
+
+BEGIN
+{
+	strlen(0);
+	exit(1);
+}
+
+ERROR
+{
+	exit(0);
+}
diff --git a/test/unittest/funcs/strlen/tst.null.r b/test/unittest/funcs/strlen/tst.null.r
new file mode 100644
index 00000000..ef424aed
--- /dev/null
+++ b/test/unittest/funcs/strlen/tst.null.r
@@ -0,0 +1,3 @@
+
+-- @@stderr --
+dtrace: error on enabled probe ID 3 (ID 1: dtrace:::BEGIN): invalid address ({ptr}) in action #1
-- 
2.34.1




More information about the DTrace-devel mailing list