Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

libbpf: Fix USDT SIB argument handling causing unrecognized register error

On x86-64, USDT arguments can be specified using Scale-Index-Base (SIB)
addressing, e.g. "1@-96(%rbp,%rax,8)". The current USDT implementation
in libbpf cannot parse this format, causing `bpf_program__attach_usdt()`
to fail with -ENOENT (unrecognized register).

This patch fixes this by implementing the necessary changes:
- add correct handling for SIB-addressed arguments in `bpf_usdt_arg`.
- add adaptive support to `__bpf_usdt_arg_type` and
`__bpf_usdt_arg_spec` to represent SIB addressing parameters.

Signed-off-by: Jiawei Zhao <phoenix500526@163.com>
Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
Link: https://lore.kernel.org/bpf/20250827053128.1301287-2-phoenix500526@163.com

authored by

Jiawei Zhao and committed by
Andrii Nakryiko
758acb9c d3abefe8

+99 -7
+42 -2
tools/lib/bpf/usdt.bpf.h
··· 34 34 BPF_USDT_ARG_CONST, 35 35 BPF_USDT_ARG_REG, 36 36 BPF_USDT_ARG_REG_DEREF, 37 + BPF_USDT_ARG_SIB, 37 38 }; 38 39 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 + */ 39 45 struct __bpf_usdt_arg_spec { 40 46 /* u64 scalar interpreted depending on arg_type, see below */ 41 47 __u64 val_off; 48 + #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ 42 49 /* 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 44 63 /* offset of referenced register within struct pt_regs */ 45 64 short reg_off; 46 65 /* whether arg should be interpreted as signed value */ ··· 168 149 { 169 150 struct __bpf_usdt_spec *spec; 170 151 struct __bpf_usdt_arg_spec *arg_spec; 171 - unsigned long val; 152 + unsigned long val, idx; 172 153 int err, spec_id; 173 154 174 155 *res = 0; ··· 217 198 if (err) 218 199 return err; 219 200 err = bpf_probe_read_user(&val, sizeof(val), (void *)val + arg_spec->val_off); 201 + if (err) 202 + return err; 203 + #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ 204 + val >>= arg_spec->arg_bitshift; 205 + #endif 206 + break; 207 + case BPF_USDT_ARG_SIB: 208 + /* Arg is in memory addressed by SIB (Scale-Index-Base) mode 209 + * (e.g., "-1@-96(%rbp,%rax,8)" in USDT arg spec). We first 210 + * fetch the base register contents and the index register 211 + * contents from pt_regs. Then we calculate the final address 212 + * as base + (index * scale) + offset, and do a user-space 213 + * probe read to fetch the argument value. 214 + */ 215 + err = bpf_probe_read_kernel(&val, sizeof(val), (void *)ctx + arg_spec->reg_off); 216 + if (err) 217 + return err; 218 + err = bpf_probe_read_kernel(&idx, sizeof(idx), (void *)ctx + arg_spec->idx_reg_off); 219 + if (err) 220 + return err; 221 + err = bpf_probe_read_user(&val, sizeof(val), (void *)(val + (idx << arg_spec->scale_bitshift) + arg_spec->val_off)); 220 222 if (err) 221 223 return err; 222 224 #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+57 -5
tools/lib/bpf/usdt.c
··· 200 200 USDT_ARG_CONST, 201 201 USDT_ARG_REG, 202 202 USDT_ARG_REG_DEREF, 203 + USDT_ARG_SIB, 203 204 }; 204 205 205 206 /* should match exactly struct __bpf_usdt_arg_spec from usdt.bpf.h */ 206 207 struct usdt_arg_spec { 207 208 __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 209 220 short reg_off; 210 221 bool arg_signed; 211 222 char arg_bitshift; ··· 1294 1283 1295 1284 static int parse_usdt_arg(const char *arg_str, int arg_num, struct usdt_arg_spec *arg, int *arg_sz) 1296 1285 { 1297 - char reg_name[16]; 1298 - int len, reg_off; 1299 - long off; 1286 + char reg_name[16] = {0}, idx_reg_name[16] = {0}; 1287 + int len, reg_off, idx_reg_off, scale = 1; 1288 + long off = 0; 1300 1289 1301 - if (sscanf(arg_str, " %d @ %ld ( %%%15[^)] ) %n", arg_sz, &off, reg_name, &len) == 3) { 1290 + if (sscanf(arg_str, " %d @ %ld ( %%%15[^,] , %%%15[^,] , %d ) %n", 1291 + arg_sz, &off, reg_name, idx_reg_name, &scale, &len) == 5 || 1292 + sscanf(arg_str, " %d @ ( %%%15[^,] , %%%15[^,] , %d ) %n", 1293 + arg_sz, reg_name, idx_reg_name, &scale, &len) == 4 || 1294 + sscanf(arg_str, " %d @ %ld ( %%%15[^,] , %%%15[^)] ) %n", 1295 + arg_sz, &off, reg_name, idx_reg_name, &len) == 4 || 1296 + sscanf(arg_str, " %d @ ( %%%15[^,] , %%%15[^)] ) %n", 1297 + arg_sz, reg_name, idx_reg_name, &len) == 3 1298 + ) { 1299 + /* 1300 + * Scale Index Base case: 1301 + * 1@-96(%rbp,%rax,8) 1302 + * 1@(%rbp,%rax,8) 1303 + * 1@-96(%rbp,%rax) 1304 + * 1@(%rbp,%rax) 1305 + */ 1306 + arg->arg_type = USDT_ARG_SIB; 1307 + arg->val_off = off; 1308 + 1309 + reg_off = calc_pt_regs_off(reg_name); 1310 + if (reg_off < 0) 1311 + return reg_off; 1312 + arg->reg_off = reg_off; 1313 + 1314 + idx_reg_off = calc_pt_regs_off(idx_reg_name); 1315 + if (idx_reg_off < 0) 1316 + return idx_reg_off; 1317 + arg->idx_reg_off = idx_reg_off; 1318 + 1319 + /* validate scale factor and set fields directly */ 1320 + switch (scale) { 1321 + case 1: arg->scale_bitshift = 0; break; 1322 + case 2: arg->scale_bitshift = 1; break; 1323 + case 4: arg->scale_bitshift = 2; break; 1324 + case 8: arg->scale_bitshift = 3; break; 1325 + default: 1326 + pr_warn("usdt: invalid SIB scale %d, expected 1, 2, 4, 8\n", scale); 1327 + return -EINVAL; 1328 + } 1329 + } else if (sscanf(arg_str, " %d @ %ld ( %%%15[^)] ) %n", 1330 + arg_sz, &off, reg_name, &len) == 3) { 1302 1331 /* Memory dereference case, e.g., -4@-20(%rbp) */ 1303 1332 arg->arg_type = USDT_ARG_REG_DEREF; 1304 1333 arg->val_off = off; ··· 1357 1306 } else if (sscanf(arg_str, " %d @ %%%15s %n", arg_sz, reg_name, &len) == 2) { 1358 1307 /* Register read case, e.g., -4@%eax */ 1359 1308 arg->arg_type = USDT_ARG_REG; 1309 + /* register read has no memory offset */ 1360 1310 arg->val_off = 0; 1361 1311 1362 1312 reg_off = calc_pt_regs_off(reg_name);