Skip to content

Commit

Permalink
Add support for string comparisons
Browse files Browse the repository at this point in the history
Signed-off-by: Eugene Loh <eugene.loh@oracle.com>
Reviewed-by: Kris Van Hees <kris.van.hees@oracle.com>
  • Loading branch information
euloh authored and kvanhees committed Oct 14, 2021
1 parent 9b337db commit 05f0212
Show file tree
Hide file tree
Showing 18 changed files with 499 additions and 22 deletions.
1 change: 1 addition & 0 deletions bpf/Build
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ bpf_dlib_SOURCES = \
get_tvar.c set_tvar.c \
probe_error.c \
speculation.c \
strcmp.S \
strjoin.S \
strlen.c \
substr.S
Expand Down
197 changes: 197 additions & 0 deletions bpf/strcmp.S
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
*/

#define DT_STRLEN_BYTES 2

#define BPF_FUNC_probe_read 4
#define BPF_FUNC_probe_read_str 45

.text

/*
* void dt_strcmp_xor(char *s, const char *t, uint64_t len) {
* for (r6 = 0; r6 < len; r6++)
* s[r6] ^= t[r6];
* }
* where len is a positive multiple of 8.
*/
.align 4
.global dt_strcmp_xor
.type dt_strcmp_xor, @function
dt_strcmp_xor :
mov %r6, 0
.Lxor:
ldxdw %r4, [%r1+0]
ldxdw %r5, [%r2+0]

xor %r4, %r5
stxdw [%r1+0], %r4

add %r1, 8
add %r2, 8
add %r6, 8
jlt %r6, %r3, .Lxor
exit
.size dt_strcmp_xor, .-dt_strcmp_xor

/*
* void dt_strcmp_not(char *s, uint64_t len) {
* for (r6 = 0; r6 < len; r6++)
* s[r6] = ! s[r6]
* }
*/
.align 4
.global dt_strcmp_not
.type dt_strcmp_not, @function
dt_strcmp_not :
mov %r6, 0
.Lnot:
ldxb %r3, [%r1+0]
and %r3, 0xff
sub %r3, 1
rsh %r3, 63
stxb [%r1+0], %r3
add %r1, 1
add %r6, 1
jlt %r6, %r2, .Lnot
exit
.size dt_strcmp_not, .-dt_strcmp_not

/*
* int dt_strcmp(char *s, char *t, char *tmp1, char *tmp2) {
*
* [%fp-8]=s
* [%fp-16]=t
* [%fp-24]=tmp1
* [%fp-32]=tmp2
*
* r8 = STRSZ
*
* // make temporary copies of strings
* r6 = bpf_probe_read_str(tmp1, STRSZ, s + DT_STRLEN_BYTES);
* r7 = bpf_probe_read_str(tmp2, STRSZ, t + DT_STRLEN_BYTES);
* tmp1[r6] = '\0';
* tmp2[r7] = '\0';
*
* // round r8 up to a multiple of 8
* r8 = (r8 + 7) & -8;
*
* // xor strings together, "not" bytes, and find first NULL
* // (this gives us the first byte that differs in the two strings)
* dt_strcmp_xor(tmp1, tmp2, r8);
* dt_strcmp_not(tmp1, r8);
* r0 = bpf_probe_read_str(tmp2, r8, tmp1);
* r0 -= 1;
*
* // based on this location, judge if the strings are >, <, or ==
* if (r0 > r6) goto Lsame;
* if (r0 > r7) goto Lsame;
* r0 += DT_STRLEN_BYTES;
* if (s[r0] > t[r0]) return +1;
* if (s[r0] < t[r0]) return +1;
*
* // if all chars are the same, break tie on string length
* Lsame:
* if (r6 > r7) return +1
* if (r6 < r7) return -1
* return 0;
* }
*/
.align 4
.global dt_strcmp
.type dt_strcmp, @function
dt_strcmp :

stxdw [%fp+-8], %r1 /* Spill s */
stxdw [%fp+-16], %r2 /* Spill t */
stxdw [%fp+-24], %r3 /* Spill tmp1 */
stxdw [%fp+-32], %r4 /* Spill tmp2 */

lddw %r8, STRSZ /* r8 = STRSZ */

ldxdw %r1, [%fp+-24]
mov %r2, %r8
ldxdw %r3, [%fp+-8]
add %r3, DT_STRLEN_BYTES
call BPF_FUNC_probe_read_str /* r6 = bpf_probe_read_str(tmp1, STRSZ, s + DT_STRLEN_BYTES) */
mov %r6, %r0
jle %r6, %r8, 1
mov %r6, %r8

ldxdw %r1, [%fp+-32]
mov %r2, %r8
ldxdw %r3, [%fp+-16]
add %r3, DT_STRLEN_BYTES
call BPF_FUNC_probe_read_str /* r7 = bpf_probe_read_str(tmp2, STRSZ, t + DT_STRLEN_BYTES) */
mov %r7, %r0
jle %r7, %r8, 1
mov %r7, %r8

mov %r2, 0
ldxdw %r1, [%fp+-24]
add %r1, %r6
stxb [%r1+0], %r2 /* tmp1[r6] = '\0' */
ldxdw %r1, [%fp+-32]
add %r1, %r7
stxb [%r1+0], %r2 /* tmp2[r7] = '\0' */

add %r8, 7 /* round r8 up to a multiple of 8 */
and %r8, -8

ldxdw %r1, [%fp+-24]
ldxdw %r2, [%fp+-32]
mov %r3, %r8
call dt_strcmp_xor /* dt_strcmp_xor(tmp1, tmp2, r8) */
ldxdw %r1, [%fp+-24]
mov %r2, %r8
call dt_strcmp_not /* dt_strcmp_not(tmp1, r8) */

ldxdw %r1, [%fp+-32]
mov %r2, %r8
ldxdw %r3, [%fp+-24]
call BPF_FUNC_probe_read_str /* r0 = bpf_probe_read_str(tmp2, r8, tmp1) */

jsle %r0, 0, .L0 /* help the BPF verifier */
lddw %r8, STRSZ
sub %r8, DT_STRLEN_BYTES
jlt %r0, %r8, 1
mov %r0, %r8

sub %r0, 1 /* r0 -= 1 */

jgt %r0, %r6, .Lsame /* if (r0 > r6) goto Lsame */
jgt %r0, %r8, .Lsame /* if (r0 > r8) goto Lsame */

add %r0, DT_STRLEN_BYTES /* r0 += DT_STRLEN_BYTES */

ldxdw %r4, [%fp+-8]
add %r4, %r0
ldxb %r4, [%r4+0] /* s[r0] */
and %r4, 0xff

ldxdw %r5, [%fp+-16]
add %r5, %r0
ldxb %r5, [%r5+0] /* t[r0] */
and %r5, 0xff

jle %r4, %r5, 2 /* if (s[r0] > t[r0]) return +1 */
mov %r0, 1
exit
jge %r4, %r5, 2 /* if (s[r0] < t[r0]) return +1 */
mov %r0, -1
exit

.Lsame:
jle %r6, %r7, 2 /* if (r6 > r7) return +1 */
mov %r0, 1
exit
jge %r6, %r7, 2 /* if (r6 < r7) return -1 */
mov %r0, -1
exit

.L0:
mov %r0, 0 /* return 0 */
exit
.size dt_strcmp, .-dt_strcmp
6 changes: 4 additions & 2 deletions libdtrace/dt_bpf.c
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,8 @@ dt_bpf_gmap_create(dtrace_hdl_t *dtp)
* multiple of 8
* - the greater of:
* + the maximum stack trace size
* + four times the maximum string size (incl. length)
* + four times the maximum string size (incl. length
* and allowing round up to multiple of 8)
* plus the maximum string size (to accomodate the BPF
* verifier)
*/
Expand All @@ -308,7 +309,8 @@ dt_bpf_gmap_create(dtrace_hdl_t *dtp)
roundup(dtp->dt_maxreclen, 8) +
MAX(sizeof(uint64_t) * dtp->dt_options[DTRACEOPT_MAXFRAMES],
DT_TSTRING_SLOTS *
(DT_STRLEN_BYTES + dtp->dt_options[DTRACEOPT_STRSIZE]) +
roundup(DT_STRLEN_BYTES +
dtp->dt_options[DTRACEOPT_STRSIZE], 8) +
dtp->dt_options[DTRACEOPT_STRSIZE] + 1
);
if (create_gmap(dtp, "mem", BPF_MAP_TYPE_PERCPU_ARRAY,
Expand Down
66 changes: 59 additions & 7 deletions libdtrace/dt_cg.c
Original file line number Diff line number Diff line change
Expand Up @@ -815,8 +815,9 @@ dt_cg_tstring_reset(dtrace_hdl_t *dtp)

ts = dtp->dt_tstrings;
for (i = 0; i < DT_TSTRING_SLOTS; i++, ts++)
ts->offset = i * (DT_STRLEN_BYTES +
dtp->dt_options[DTRACEOPT_STRSIZE]);
ts->offset = i *
roundup(DT_STRLEN_BYTES +
dtp->dt_options[DTRACEOPT_STRSIZE], 8);
}

ts = dtp->dt_tstrings;
Expand Down Expand Up @@ -2700,7 +2701,11 @@ dt_cg_compare_signed(dt_node_t *dnp)

if (dt_node_is_string(dnp->dn_left) ||
dt_node_is_string(dnp->dn_right))
return 1; /* strings always compare signed */
/*
* String comparison looks at unsigned bytes, and
* "string op NULL" comparisons examine unsigned pointers.
*/
return 0;
else if (!dt_node_is_arith(dnp->dn_left) ||
!dt_node_is_arith(dnp->dn_right))
return 0; /* non-arithmetic types always compare unsigned */
Expand All @@ -2719,10 +2724,57 @@ dt_cg_compare_op(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp, uint_t op)
dt_cg_node(dnp->dn_left, dlp, drp);
dt_cg_node(dnp->dn_right, dlp, drp);

/* FIXME: No support for string comparison yet */
if (dt_node_is_string(dnp->dn_left) || dt_node_is_string(dnp->dn_right))
xyerror(D_UNKNOWN, "internal error -- no support for "
"string comparison yet\n");
/* by now, we have checked and both args are strings or neither is */
if (dt_node_is_string(dnp->dn_left) ||
dt_node_is_string(dnp->dn_right)) {
dt_ident_t *idp;
uint_t Lcomp = dt_irlist_label(dlp);
uint64_t off1, off2;

/* if either pointer is NULL, just compare pointers */
emit(dlp, BPF_BRANCH_IMM(BPF_JEQ, dnp->dn_left->dn_reg, 0, Lcomp));
emit(dlp, BPF_BRANCH_IMM(BPF_JEQ, dnp->dn_right->dn_reg, 0, Lcomp));

/*
* Otherwise, replace pointers with relative string values.
* Specifically, replace left with strcmp(left,right)+1 and
* right with 1, so we can use unsigned comparisons.
*/

off1 = dt_cg_tstring_xalloc(yypcb);
off2 = dt_cg_tstring_xalloc(yypcb);

if (dt_regset_xalloc_args(drp) == -1)
longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
emit(dlp, BPF_MOV_REG(BPF_REG_1, dnp->dn_left->dn_reg));
emit(dlp, BPF_MOV_REG(BPF_REG_2, dnp->dn_right->dn_reg));
emit(dlp, BPF_LOAD(BPF_DW, BPF_REG_3, BPF_REG_FP, DT_STK_DCTX));
emit(dlp, BPF_LOAD(BPF_DW, BPF_REG_3, BPF_REG_3, DCTX_MEM));
emit(dlp, BPF_MOV_REG(BPF_REG_4, BPF_REG_3));
emit(dlp, BPF_ALU64_IMM(BPF_ADD, BPF_REG_3, off1));
emit(dlp, BPF_ALU64_IMM(BPF_ADD, BPF_REG_4, off2));
idp = dt_dlib_get_func(yypcb->pcb_hdl, "dt_strcmp");
assert(idp != NULL);
dt_regset_xalloc(drp, BPF_REG_0);
emite(dlp, BPF_CALL_FUNC(idp->di_id), idp);
dt_regset_free_args(drp);
emit(dlp, BPF_ALU64_IMM(BPF_ADD, BPF_REG_0, 1));
emit(dlp, BPF_MOV_REG(dnp->dn_left->dn_reg, BPF_REG_0));
dt_regset_free(drp, BPF_REG_0);
emit(dlp, BPF_MOV_IMM(dnp->dn_right->dn_reg, 1));

dt_cg_tstring_xfree(yypcb, off1);
dt_cg_tstring_xfree(yypcb, off2);

if (dnp->dn_left->dn_tstring)
dt_cg_tstring_free(yypcb, dnp->dn_left);
if (dnp->dn_right->dn_tstring)
dt_cg_tstring_free(yypcb, dnp->dn_right);

/* proceed with the comparison */
emitl(dlp, Lcomp,
BPF_NOP());
}

emit(dlp, BPF_BRANCH_REG(op, dnp->dn_left->dn_reg, dnp->dn_right->dn_reg, lbl_true));
dt_regset_free(drp, dnp->dn_right->dn_reg);
Expand Down
1 change: 1 addition & 0 deletions libdtrace/dt_dlibs.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ static const dt_ident_t dt_bpf_symbols[] = {
DT_BPF_SYMBOL(dt_get_string, DT_IDENT_SYMBOL),
DT_BPF_SYMBOL(dt_get_tvar, DT_IDENT_SYMBOL),
DT_BPF_SYMBOL(dt_set_tvar, DT_IDENT_SYMBOL),
DT_BPF_SYMBOL(dt_strcmp, DT_IDENT_SYMBOL),
DT_BPF_SYMBOL(dt_strjoin, DT_IDENT_SYMBOL),
DT_BPF_SYMBOL(dt_substr, DT_IDENT_SYMBOL),
DT_BPF_SYMBOL(dt_speculation, DT_IDENT_SYMBOL),
Expand Down
1 change: 0 additions & 1 deletion test/demo/builtin/probename.d
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* @@xfail: dtv2 */
BEGIN {
exit(probename == "BEGIN" ? 0 : 1);
}
1 change: 0 additions & 1 deletion test/demo/builtin/probeprov.d
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* @@xfail: dtv2 */
BEGIN {
exit(probeprov == "dtrace" ? 0 : 1);
}
3 changes: 1 addition & 2 deletions test/demo/fbt/xioctl.d
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
/*
* 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.
*/
/* @@xfail: dtv2 */

/*
* To make the output more readable, we want to indent every function entry
Expand Down
1 change: 0 additions & 1 deletion test/unittest/dif/strncmp.d
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* @@xfail: dtv2 */
BEGIN
{
rc = probename == "BEGIN" ? 0 : 1;
Expand Down
3 changes: 1 addition & 2 deletions test/unittest/dtrace-util/tst.ListProbesFunc.sh
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
#!/bin/bash
#
# Oracle Linux DTrace.
# Copyright (c) 2013, 2020, Oracle and/or its affiliates. All rights reserved.
# Copyright (c) 2013, 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

##
#
Expand Down
3 changes: 1 addition & 2 deletions test/unittest/dtrace-util/tst.ListProbesName.sh
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
#!/bin/bash
#
# Oracle Linux DTrace.
# Copyright (c) 2013, 2020, Oracle and/or its affiliates. All rights reserved.
# Copyright (c) 2013, 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

# @@timeout: 20

Expand Down

0 comments on commit 05f0212

Please sign in to comment.