Skip to content

Commit

Permalink
Implement strlen() based on bpf_probe_read_str()
Browse files Browse the repository at this point in the history
The bpf_probe_read_str() 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 new strlen() 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@oracle.com>
Reviewed-by: Eugene Loh <eugene.loh@oracle.com>
  • Loading branch information
kvanhees committed Feb 8, 2022
1 parent 7f40cd2 commit e042483
Show file tree
Hide file tree
Showing 14 changed files with 248 additions and 64 deletions.
19 changes: 15 additions & 4 deletions bpf/strlen.c
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
// 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

#ifndef noinline
# 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;
Expand All @@ -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 */
}
26 changes: 17 additions & 9 deletions bpf/substr.S
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -43,17 +43,26 @@ dt_substr :

/*
* Get the source string length and validate it.
* If the return value of probe_read_str) is less than 0, an error
* occured, and the result will be the empty string.
* If the length is 0, the result is the empty string.
* If the length is less than the maximum string length (STRSZ), use
* 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 %r2, 1
call BPF_FUNC_probe_read_str /*
* len = probe_read_str(
* dst, STRSZ + 1,
* &src[DT_STRLEN_BYTES]);
*/
jslt %r0, 1, .Lempty /* if (len < 1) goto Lempty; */
sub %r0, 1 /* len--; */
mov %r4, %r8 /* %r4 = STRSZ (previously in %r8) */
mov %r8, %r0 /* %r8 = len */
.Llen_set:

jsge %r6, 0, .Lcheck_idx /* if (idx s>= 0) goto Lcheck_idx; */

Expand Down Expand Up @@ -110,9 +119,8 @@ dt_substr :
mov %r1, %r9
add %r1, DT_STRLEN_BYTES
mov %r2, %r7
lddw %r0, STRSZ
jle %r2, %r0, .Lcnt_ok /* if (cnt <= STRSZ) goto Lcnt_ok; */
mov %r2, %r0 /* cnt = STRSZ */
jle %r2, %r4, .Lcnt_ok /* if (cnt <= STRSZ) goto Lcnt_ok; */
mov %r2, %r4 /* cnt = STRSZ */
.Lcnt_ok:
add %r2, 1
ldxdw %r3, [%fp+-8]
Expand Down
22 changes: 19 additions & 3 deletions include/bpf-lib.h
Original file line number Diff line number Diff line change
@@ -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.
*/
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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 */
8 changes: 4 additions & 4 deletions libdtrace/dt_bpf.c
Original file line number Diff line number Diff line change
@@ -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.
*/
Expand Down Expand Up @@ -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);
Expand Down
63 changes: 27 additions & 36 deletions libdtrace/dt_cg.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -3762,10 +3729,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 ");
}

Expand Down
18 changes: 18 additions & 0 deletions test/unittest/funcs/strlen/err.D_PROTO_ARG.non_string.d
Original file line number Diff line number Diff line change
@@ -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);
}
18 changes: 18 additions & 0 deletions test/unittest/funcs/strlen/err.D_PROTO_LEN.missing_arg.d
Original file line number Diff line number Diff line change
@@ -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);
}
18 changes: 18 additions & 0 deletions test/unittest/funcs/strlen/err.D_PROTO_LEN.too_many_args.d
Original file line number Diff line number Diff line change
@@ -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);
}
37 changes: 37 additions & 0 deletions test/unittest/funcs/strlen/tst.basic.d
Original file line number Diff line number Diff line change
@@ -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);
}
15 changes: 15 additions & 0 deletions test/unittest/funcs/strlen/tst.basic.r
Original file line number Diff line number Diff line change
@@ -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

25 changes: 25 additions & 0 deletions test/unittest/funcs/strlen/tst.capped-sizw.d
Original file line number Diff line number Diff line change
@@ -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);
}

0 comments on commit e042483

Please sign in to comment.