Skip to content

Commit 4c229f3

Browse files
committed
Merge branch 'libbpf-fix-usdt-sib-argument-handling-causing-unrecognized-register-error'
Jiawei Zhao says: ==================== libbpf: fix USDT SIB argument handling causing unrecognized register error When using GCC on x86-64 to compile an usdt prog with -O1 or higher optimization, the compiler will generate SIB addressing mode for global array, e.g. "1@-96(%rbp,%rax,8)". The current USDT implementation in libbpf cannot parse these two formats, causing `bpf_program__attach_usdt()` to fail with -ENOENT (unrecognized register). This patch series adds support for SIB addressing mode in USDT probes. The main changes include: - add correct handling logic for SIB-addressed arguments in `parse_usdt_arg`. - add an usdt_o2 test case to cover SIB addressing mode. Testing shows that the SIB probe correctly generates 8@(%rcx,%rax,8) argument spec and passes all validation checks. The modification history of this patch series: Change since v1: - refactor the code to make it more readable - modify the commit message to explain why and how Change since v2: - fix the `scale` uninitialized error Change since v3: - force -O2 optimization for usdt.test.o to generate SIB addressing usdt and pass all test cases. Change since v4: - split the patch into two parts, one for the fix and the other for the test Change since v5: - Only enable optimization for x86 architecture to generate SIB addressing usdt argument spec. Change since v6: - Add an usdt_o2 test case to cover SIB addressing mode. - Reinstate the usdt.c test case. Change since v7: - Refactor modifications to __bpf_usdt_arg_spec to avoid increasing its size, achieving better compatibility - Fix some minor code style issues - Refactor the usdt_o2 test case, removing semaphore and adding GCC attribute to force -O2 optimization Change since v8: - Refactor the usdt_o2 test case, using assembly to force SIB addressing mode. Change since v9: - Only enable the usdt_o2 test case on x86_64 and i386 architectures since the SIB addressing mode is only supported on x86_64 and i386. Change since v10: - Replace `__attribute__((optimize("O2")))` with `#pragma GCC optimize("O1")` to fix the issue where the optimized compilation condition works improperly. - Renamed test case usdt_o2 and relevant files name to usdt_o1 in that O1 level optimization is enough to generate SIB addressing usdt argument spec. Change since v11: - Replace `STAP_PROBE1` with `STAP_PROBE_ASM` - Use bit fields instead of bit shifting operations - Merge the usdt_o1 test case into the usdt test case Change since v12: - This patch is same with the v12 but with a new version number. Change since v13(resolve some review comments): - https://lore.kernel.org/bpf/CAEf4BzZWd2zUC=U6uGJFF3EMZ7zWGLweQAG3CJWTeHy-5yFEPw@mail.gmail.com/ - https://lore.kernel.org/bpf/CAEf4Bzbs3hV_Q47+d93tTX13WkrpkpOb4=U04mZCjHyZg4aVdw@mail.gmail.com/ Change since v14: - fix a typo in __bpf_usdt_arg_spec Change since v15(resolve some review comments): - https://lore.kernel.org/bpf/CAEf4BzaxuYijEfQMDFZ+CQdjxLuDZiesUXNA-SiopS+5+VxRaA@mail.gmail.com/ - https://lore.kernel.org/bpf/CAEf4BzaHi5kpuJ6OVvDU62LT5g0qHbWYMfb_FBQ3iuvvUF9fag@mail.gmail.com/ - https://lore.kernel.org/bpf/d438bf3a-a9c9-4d34-b814-63f2e9bb3a85@linux.dev/ ==================== Link: https://patch.msgid.link/20250827053128.1301287-1-phoenix500526@163.com Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
2 parents d3abefe + 6942409 commit 4c229f3

File tree

4 files changed

+211
-9
lines changed

4 files changed

+211
-9
lines changed

tools/lib/bpf/usdt.bpf.h

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,32 @@ enum __bpf_usdt_arg_type {
3434
BPF_USDT_ARG_CONST,
3535
BPF_USDT_ARG_REG,
3636
BPF_USDT_ARG_REG_DEREF,
37+
BPF_USDT_ARG_SIB,
3738
};
3839

40+
/*
41+
* This struct layout is designed specifically to be backwards/forward
42+
* compatible between libbpf versions for ARG_CONST, ARG_REG, and
43+
* ARG_REG_DEREF modes. ARG_SIB requires libbpf v1.7+.
44+
*/
3945
struct __bpf_usdt_arg_spec {
4046
/* u64 scalar interpreted depending on arg_type, see below */
4147
__u64 val_off;
48+
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
4249
/* arg location case, see bpf_usdt_arg() for details */
43-
enum __bpf_usdt_arg_type arg_type;
50+
enum __bpf_usdt_arg_type arg_type: 8;
51+
/* index register offset within struct pt_regs */
52+
__u16 idx_reg_off: 12;
53+
/* scale factor for index register (1, 2, 4, or 8) */
54+
__u16 scale_bitshift: 4;
55+
/* reserved for future use, keeps reg_off offset stable */
56+
__u8 __reserved: 8;
57+
#else
58+
__u8 __reserved: 8;
59+
__u16 idx_reg_off: 12;
60+
__u16 scale_bitshift: 4;
61+
enum __bpf_usdt_arg_type arg_type: 8;
62+
#endif
4463
/* offset of referenced register within struct pt_regs */
4564
short reg_off;
4665
/* whether arg should be interpreted as signed value */
@@ -149,7 +168,7 @@ int bpf_usdt_arg(struct pt_regs *ctx, __u64 arg_num, long *res)
149168
{
150169
struct __bpf_usdt_spec *spec;
151170
struct __bpf_usdt_arg_spec *arg_spec;
152-
unsigned long val;
171+
unsigned long val, idx;
153172
int err, spec_id;
154173

155174
*res = 0;
@@ -202,6 +221,27 @@ int bpf_usdt_arg(struct pt_regs *ctx, __u64 arg_num, long *res)
202221
return err;
203222
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
204223
val >>= arg_spec->arg_bitshift;
224+
#endif
225+
break;
226+
case BPF_USDT_ARG_SIB:
227+
/* Arg is in memory addressed by SIB (Scale-Index-Base) mode
228+
* (e.g., "-1@-96(%rbp,%rax,8)" in USDT arg spec). We first
229+
* fetch the base register contents and the index register
230+
* contents from pt_regs. Then we calculate the final address
231+
* as base + (index * scale) + offset, and do a user-space
232+
* probe read to fetch the argument value.
233+
*/
234+
err = bpf_probe_read_kernel(&val, sizeof(val), (void *)ctx + arg_spec->reg_off);
235+
if (err)
236+
return err;
237+
err = bpf_probe_read_kernel(&idx, sizeof(idx), (void *)ctx + arg_spec->idx_reg_off);
238+
if (err)
239+
return err;
240+
err = bpf_probe_read_user(&val, sizeof(val), (void *)(val + (idx << arg_spec->scale_bitshift) + arg_spec->val_off));
241+
if (err)
242+
return err;
243+
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
244+
val >>= arg_spec->arg_bitshift;
205245
#endif
206246
break;
207247
default:

tools/lib/bpf/usdt.c

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -200,12 +200,23 @@ enum usdt_arg_type {
200200
USDT_ARG_CONST,
201201
USDT_ARG_REG,
202202
USDT_ARG_REG_DEREF,
203+
USDT_ARG_SIB,
203204
};
204205

205206
/* should match exactly struct __bpf_usdt_arg_spec from usdt.bpf.h */
206207
struct usdt_arg_spec {
207208
__u64 val_off;
208-
enum usdt_arg_type arg_type;
209+
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
210+
enum usdt_arg_type arg_type: 8;
211+
__u16 idx_reg_off: 12;
212+
__u16 scale_bitshift: 4;
213+
__u8 __reserved: 8; /* keep reg_off offset stable */
214+
#else
215+
__u8 __reserved: 8; /* keep reg_off offset stable */
216+
__u16 idx_reg_off: 12;
217+
__u16 scale_bitshift: 4;
218+
enum usdt_arg_type arg_type: 8;
219+
#endif
209220
short reg_off;
210221
bool arg_signed;
211222
char arg_bitshift;
@@ -1283,11 +1294,51 @@ static int calc_pt_regs_off(const char *reg_name)
12831294

12841295
static int parse_usdt_arg(const char *arg_str, int arg_num, struct usdt_arg_spec *arg, int *arg_sz)
12851296
{
1286-
char reg_name[16];
1287-
int len, reg_off;
1288-
long off;
1297+
char reg_name[16] = {0}, idx_reg_name[16] = {0};
1298+
int len, reg_off, idx_reg_off, scale = 1;
1299+
long off = 0;
1300+
1301+
if (sscanf(arg_str, " %d @ %ld ( %%%15[^,] , %%%15[^,] , %d ) %n",
1302+
arg_sz, &off, reg_name, idx_reg_name, &scale, &len) == 5 ||
1303+
sscanf(arg_str, " %d @ ( %%%15[^,] , %%%15[^,] , %d ) %n",
1304+
arg_sz, reg_name, idx_reg_name, &scale, &len) == 4 ||
1305+
sscanf(arg_str, " %d @ %ld ( %%%15[^,] , %%%15[^)] ) %n",
1306+
arg_sz, &off, reg_name, idx_reg_name, &len) == 4 ||
1307+
sscanf(arg_str, " %d @ ( %%%15[^,] , %%%15[^)] ) %n",
1308+
arg_sz, reg_name, idx_reg_name, &len) == 3
1309+
) {
1310+
/*
1311+
* Scale Index Base case:
1312+
* 1@-96(%rbp,%rax,8)
1313+
* 1@(%rbp,%rax,8)
1314+
* 1@-96(%rbp,%rax)
1315+
* 1@(%rbp,%rax)
1316+
*/
1317+
arg->arg_type = USDT_ARG_SIB;
1318+
arg->val_off = off;
12891319

1290-
if (sscanf(arg_str, " %d @ %ld ( %%%15[^)] ) %n", arg_sz, &off, reg_name, &len) == 3) {
1320+
reg_off = calc_pt_regs_off(reg_name);
1321+
if (reg_off < 0)
1322+
return reg_off;
1323+
arg->reg_off = reg_off;
1324+
1325+
idx_reg_off = calc_pt_regs_off(idx_reg_name);
1326+
if (idx_reg_off < 0)
1327+
return idx_reg_off;
1328+
arg->idx_reg_off = idx_reg_off;
1329+
1330+
/* validate scale factor and set fields directly */
1331+
switch (scale) {
1332+
case 1: arg->scale_bitshift = 0; break;
1333+
case 2: arg->scale_bitshift = 1; break;
1334+
case 4: arg->scale_bitshift = 2; break;
1335+
case 8: arg->scale_bitshift = 3; break;
1336+
default:
1337+
pr_warn("usdt: invalid SIB scale %d, expected 1, 2, 4, 8\n", scale);
1338+
return -EINVAL;
1339+
}
1340+
} else if (sscanf(arg_str, " %d @ %ld ( %%%15[^)] ) %n",
1341+
arg_sz, &off, reg_name, &len) == 3) {
12911342
/* Memory dereference case, e.g., -4@-20(%rbp) */
12921343
arg->arg_type = USDT_ARG_REG_DEREF;
12931344
arg->val_off = off;
@@ -1306,6 +1357,7 @@ static int parse_usdt_arg(const char *arg_str, int arg_num, struct usdt_arg_spec
13061357
} else if (sscanf(arg_str, " %d @ %%%15s %n", arg_sz, reg_name, &len) == 2) {
13071358
/* Register read case, e.g., -4@%eax */
13081359
arg->arg_type = USDT_ARG_REG;
1360+
/* register read has no memory offset */
13091361
arg->val_off = 0;
13101362

13111363
reg_off = calc_pt_regs_off(reg_name);

tools/testing/selftests/bpf/prog_tests/usdt.c

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,72 @@ static void __always_inline trigger_func(int x) {
4040
}
4141
}
4242

43+
#if defined(__x86_64__) || defined(__i386__)
44+
/*
45+
* SIB (Scale-Index-Base) addressing format: "size@(base_reg, index_reg, scale)"
46+
* - 'size' is the size in bytes of the array element, and its sign indicates
47+
* whether the type is signed (negative) or unsigned (positive).
48+
* - 'base_reg' is the register holding the base address, normally rdx or edx
49+
* - 'index_reg' is the register holding the index, normally rax or eax
50+
* - 'scale' is the scaling factor (typically 1, 2, 4, or 8), which matches the
51+
* size of the element type.
52+
*
53+
* For example, for an array of 'short' (signed 2-byte elements), the SIB spec would be:
54+
* - size: -2 (negative because 'short' is signed)
55+
* - scale: 2 (since sizeof(short) == 2)
56+
*
57+
* The resulting SIB format: "-2@(%%rdx,%%rax,2)" for x86_64, "-2@(%%edx,%%eax,2)" for i386
58+
*/
59+
static volatile short array[] = {-1, -2, -3, -4};
60+
61+
#if defined(__x86_64__)
62+
#define USDT_SIB_ARG_SPEC -2@(%%rdx,%%rax,2)
63+
#else
64+
#define USDT_SIB_ARG_SPEC -2@(%%edx,%%eax,2)
65+
#endif
66+
67+
unsigned short test_usdt_sib_semaphore SEC(".probes");
68+
69+
static void trigger_sib_spec(void)
70+
{
71+
/*
72+
* Force SIB addressing with inline assembly.
73+
*
74+
* You must compile with -std=gnu99 or -std=c99 to use the
75+
* STAP_PROBE_ASM macro.
76+
*
77+
* The STAP_PROBE_ASM macro generates a quoted string that gets
78+
* inserted between the surrounding assembly instructions. In this
79+
* case, USDT_SIB_ARG_SPEC is embedded directly into the instruction
80+
* stream, creating a probe point between the asm statement boundaries.
81+
* It works fine with gcc/clang.
82+
*
83+
* Register constraints:
84+
* - "d"(array): Binds the 'array' variable to %rdx or %edx register
85+
* - "a"(0): Binds the constant 0 to %rax or %eax register
86+
* These ensure that when USDT_SIB_ARG_SPEC references %%rdx(%edx) and
87+
* %%rax(%eax), they contain the expected values for SIB addressing.
88+
*
89+
* The "memory" clobber prevents the compiler from reordering memory
90+
* accesses around the probe point, ensuring that the probe behavior
91+
* is predictable and consistent.
92+
*/
93+
asm volatile(
94+
STAP_PROBE_ASM(test, usdt_sib, USDT_SIB_ARG_SPEC)
95+
:
96+
: "d"(array), "a"(0)
97+
: "memory"
98+
);
99+
}
100+
#endif
101+
43102
static void subtest_basic_usdt(void)
44103
{
45104
LIBBPF_OPTS(bpf_usdt_opts, opts);
46105
struct test_usdt *skel;
47106
struct test_usdt__bss *bss;
48107
int err, i;
108+
const __u64 expected_cookie = 0xcafedeadbeeffeed;
49109

50110
skel = test_usdt__open_and_load();
51111
if (!ASSERT_OK_PTR(skel, "skel_open"))
@@ -59,20 +119,29 @@ static void subtest_basic_usdt(void)
59119
goto cleanup;
60120

61121
/* usdt0 won't be auto-attached */
62-
opts.usdt_cookie = 0xcafedeadbeeffeed;
122+
opts.usdt_cookie = expected_cookie;
63123
skel->links.usdt0 = bpf_program__attach_usdt(skel->progs.usdt0,
64124
0 /*self*/, "/proc/self/exe",
65125
"test", "usdt0", &opts);
66126
if (!ASSERT_OK_PTR(skel->links.usdt0, "usdt0_link"))
67127
goto cleanup;
68128

129+
#if defined(__x86_64__) || defined(__i386__)
130+
opts.usdt_cookie = expected_cookie;
131+
skel->links.usdt_sib = bpf_program__attach_usdt(skel->progs.usdt_sib,
132+
0 /*self*/, "/proc/self/exe",
133+
"test", "usdt_sib", &opts);
134+
if (!ASSERT_OK_PTR(skel->links.usdt_sib, "usdt_sib_link"))
135+
goto cleanup;
136+
#endif
137+
69138
trigger_func(1);
70139

71140
ASSERT_EQ(bss->usdt0_called, 1, "usdt0_called");
72141
ASSERT_EQ(bss->usdt3_called, 1, "usdt3_called");
73142
ASSERT_EQ(bss->usdt12_called, 1, "usdt12_called");
74143

75-
ASSERT_EQ(bss->usdt0_cookie, 0xcafedeadbeeffeed, "usdt0_cookie");
144+
ASSERT_EQ(bss->usdt0_cookie, expected_cookie, "usdt0_cookie");
76145
ASSERT_EQ(bss->usdt0_arg_cnt, 0, "usdt0_arg_cnt");
77146
ASSERT_EQ(bss->usdt0_arg_ret, -ENOENT, "usdt0_arg_ret");
78147
ASSERT_EQ(bss->usdt0_arg_size, -ENOENT, "usdt0_arg_size");
@@ -156,6 +225,16 @@ static void subtest_basic_usdt(void)
156225
ASSERT_EQ(bss->usdt3_args[1], 42, "usdt3_arg2");
157226
ASSERT_EQ(bss->usdt3_args[2], (uintptr_t)&bla, "usdt3_arg3");
158227

228+
#if defined(__x86_64__) || defined(__i386__)
229+
trigger_sib_spec();
230+
ASSERT_EQ(bss->usdt_sib_called, 1, "usdt_sib_called");
231+
ASSERT_EQ(bss->usdt_sib_cookie, expected_cookie, "usdt_sib_cookie");
232+
ASSERT_EQ(bss->usdt_sib_arg_cnt, 1, "usdt_sib_arg_cnt");
233+
ASSERT_EQ(bss->usdt_sib_arg, nums[0], "usdt_sib_arg");
234+
ASSERT_EQ(bss->usdt_sib_arg_ret, 0, "usdt_sib_arg_ret");
235+
ASSERT_EQ(bss->usdt_sib_arg_size, sizeof(nums[0]), "usdt_sib_arg_size");
236+
#endif
237+
159238
cleanup:
160239
test_usdt__destroy(skel);
161240
}

tools/testing/selftests/bpf/progs/test_usdt.c

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,35 @@ int BPF_USDT(usdt12, int a1, int a2, long a3, long a4, unsigned a5,
107107
return 0;
108108
}
109109

110+
int usdt_sib_called;
111+
u64 usdt_sib_cookie;
112+
int usdt_sib_arg_cnt;
113+
int usdt_sib_arg_ret;
114+
short usdt_sib_arg;
115+
int usdt_sib_arg_size;
116+
117+
/*
118+
* usdt_sib is only tested on x86-related architectures, so it requires
119+
* manual attach since auto-attach will panic tests under other architectures
120+
*/
121+
SEC("usdt")
122+
int usdt_sib(struct pt_regs *ctx)
123+
{
124+
long tmp;
125+
126+
if (my_pid != (bpf_get_current_pid_tgid() >> 32))
127+
return 0;
128+
129+
__sync_fetch_and_add(&usdt_sib_called, 1);
130+
131+
usdt_sib_cookie = bpf_usdt_cookie(ctx);
132+
usdt_sib_arg_cnt = bpf_usdt_arg_cnt(ctx);
133+
134+
usdt_sib_arg_ret = bpf_usdt_arg(ctx, 0, &tmp);
135+
usdt_sib_arg = (short)tmp;
136+
usdt_sib_arg_size = bpf_usdt_arg_size(ctx, 0);
137+
138+
return 0;
139+
}
140+
110141
char _license[] SEC("license") = "GPL";

0 commit comments

Comments
 (0)