AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/06/27 15:13 flow patch-triage
3m Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [
    "bpf_sdt_link_attach",
    "bpf_sdt_link_release",
    "bpf_sdt_link_dealloc",
    "check_sdt_probe",
    "bpf_insn_array_get_sdt_probe_by_name",
    "insn_array_alloc",
    "insn_array_alloc_check"
  ],
  "Reasoning": "The patch introduces BPF SDT (Statically Defined Tracing) probes, allowing BPF observer programs to attach to statically defined probe points in other BPF programs. This adds new BPF attach types, link types, map flags, and verifier logic to check probe arguments, which is a significant functional change to the core BPF subsystem and should be fuzzed.",
  "WorthFuzzing": true
}

1/1 2026/06/27 15:13 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 835a2cce00842492f316111c0c1e2ee26f5fd475\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Sat Jun 27 15:13:22 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c\nindex 054e043ffcd22d..262f1bd74d53f0 100644\n--- a/arch/x86/net/bpf_jit_comp.c\n+++ b/arch/x86/net/bpf_jit_comp.c\n@@ -2797,6 +2797,16 @@ st:\t\t\tinsn_off = insn-\u003eoff;\n \t\t\t}\n \n \t\t\tif (!jmp_offset) {\n+\t\t\t\tint index = bpf_prog-\u003eaux-\u003esubprog_start + i - 1;\n+\n+\t\t\t\t/*\n+\t\t\t\t * Emit a 5-byte NOP for SDT probe site, so it can be patched to\n+\t\t\t\t * a CALL instruction when observer program is attached.\n+\t\t\t\t */\n+\t\t\t\tif (env-\u003einsn_aux_data[index].sdt_entry) {\n+\t\t\t\t\temit_nops(\u0026prog, X86_PATCH_SIZE);\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n \t\t\t\t/*\n \t\t\t\t * If jmp_padding is enabled, the extra nops will\n \t\t\t\t * be inserted. Otherwise, optimize out nop jumps.\n@@ -3059,9 +3069,13 @@ static void save_args(const struct btf_func_model *m, u8 **prog,\n \n \t\t\t/* copy the arguments from regs into stack */\n \t\t\tfor (j = 0; j \u003c arg_regs; j++) {\n+\t\t\t\tu8 reg = (flags \u0026 BPF_TRAMP_F_SDT_PROBE)\n+\t\t\t\t\t? m-\u003earg_regs[nr_regs]\n+\t\t\t\t\t: (nr_regs == 5 ? X86_REG_R9\n+\t\t\t\t\t   : BPF_REG_1 + nr_regs);\n+\n \t\t\t\temit_stx(prog, BPF_DW, BPF_REG_FP,\n-\t\t\t\t\t nr_regs == 5 ? X86_REG_R9 : BPF_REG_1 + nr_regs,\n-\t\t\t\t\t -stack_size);\n+\t\t\t\t\t reg, -stack_size);\n \t\t\t\tstack_size -= 8;\n \t\t\t\tnr_regs++;\n \t\t\t}\n@@ -3072,7 +3086,7 @@ static void save_args(const struct btf_func_model *m, u8 **prog,\n }\n \n static void restore_regs(const struct btf_func_model *m, u8 **prog,\n-\t\t\t int stack_size)\n+\t\t\t int stack_size, u32 flags)\n {\n \tint i, j, arg_regs, nr_regs = 0;\n \n@@ -3087,9 +3101,12 @@ static void restore_regs(const struct btf_func_model *m, u8 **prog,\n \t\targ_regs = (m-\u003earg_size[i] + 7) / 8;\n \t\tif (nr_regs + arg_regs \u003c= 6) {\n \t\t\tfor (j = 0; j \u003c arg_regs; j++) {\n-\t\t\t\temit_ldx(prog, BPF_DW,\n-\t\t\t\t\t nr_regs == 5 ? X86_REG_R9 : BPF_REG_1 + nr_regs,\n-\t\t\t\t\t BPF_REG_FP,\n+\t\t\t\tu8 reg = (flags \u0026 BPF_TRAMP_F_SDT_PROBE)\n+\t\t\t\t\t? m-\u003earg_regs[nr_regs]\n+\t\t\t\t\t: (nr_regs == 5 ? X86_REG_R9\n+\t\t\t\t\t   : BPF_REG_1 + nr_regs);\n+\n+\t\t\t\temit_ldx(prog, BPF_DW, reg, BPF_REG_FP,\n \t\t\t\t\t -stack_size);\n \t\t\t\tstack_size -= 8;\n \t\t\t\tnr_regs++;\n@@ -3547,7 +3564,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im\n \t}\n \n \tif (flags \u0026 BPF_TRAMP_F_CALL_ORIG) {\n-\t\trestore_regs(m, \u0026prog, regs_off);\n+\t\trestore_regs(m, \u0026prog, regs_off, flags);\n \t\tsave_args(m, \u0026prog, arg_stack_off, true, flags);\n \n \t\tif (flags \u0026 BPF_TRAMP_F_TAIL_CALL_CTX) {\n@@ -3603,7 +3620,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im\n \t}\n \n \tif (flags \u0026 BPF_TRAMP_F_RESTORE_REGS)\n-\t\trestore_regs(m, \u0026prog, regs_off);\n+\t\trestore_regs(m, \u0026prog, regs_off, flags);\n \n \t/* This needs to be done regardless. If there were fmod_ret programs,\n \t * the return value is only updated on the stack and still needs to be\n@@ -4220,3 +4237,8 @@ bool bpf_jit_supports_fsession(void)\n {\n \treturn true;\n }\n+\n+bool bpf_jit_supports_sdt_probe(void)\n+{\n+\treturn true;\n+}\ndiff --git a/include/linux/bpf.h b/include/linux/bpf.h\nindex 7719f652844560..6548a2dd76da6f 100644\n--- a/include/linux/bpf.h\n+++ b/include/linux/bpf.h\n@@ -1220,6 +1220,8 @@ struct btf_func_model {\n \tu8 nr_args;\n \tu8 arg_size[MAX_BPF_FUNC_ARGS];\n \tu8 arg_flags[MAX_BPF_FUNC_ARGS];\n+\t/* argument registers for bpf SDT probe site */\n+\tu8 arg_regs[MAX_BPF_FUNC_REG_ARGS];\n };\n \n /* Restore arguments before returning from trampoline to let original function\n@@ -1268,6 +1270,12 @@ struct btf_func_model {\n  */\n #define BPF_TRAMP_F_INDIRECT\t\tBIT(8)\n \n+/* Trampoline is for an SDT probe site: arguments arrive in\n+ * BPF registers recorded by the SDT macro at build time,\n+ * described by btf_func_model.arg_regs[] instead of ABI order.\n+ */\n+#define BPF_TRAMP_F_SDT_PROBE\t\tBIT(9)\n+\n /* Each call __bpf_prog_enter + call bpf_func + call __bpf_prog_exit is ~50\n  * bytes on x86.\n  */\n@@ -1391,7 +1399,8 @@ struct bpf_trampoline {\n \tstruct {\n \t\tstruct btf_func_model model;\n \t\tvoid *addr;\n-\t\tbool ftrace_managed;\n+\t\tu8 ftrace_managed:1;\n+\t\tu8 sdt_probe_site:1;\n \t} func;\n \t/* if !NULL this is BPF_PROG_TYPE_EXT program that extends another BPF\n \t * program by replacing one of its functions. func.addr is the address\n@@ -1419,6 +1428,15 @@ struct bpf_attach_target_info {\n \tconst struct btf_type *tgt_type;\n };\n \n+/*\n+ * cached in prog-\u003eaux, used by the verifier to type the observer's context\n+ * arguments, and by link_create to build the trampoline without re-resolving\n+ */\n+struct bpf_sdt_probe_info {\n+\tstruct bpf_insn_array_value val;\n+\tunsigned long probe_ip;\n+};\n+\n #define BPF_DISPATCHER_MAX 48 /* Fits in 2048B */\n \n struct bpf_dispatcher_prog {\n@@ -1762,6 +1780,7 @@ struct bpf_prog_aux {\n \tvoid __percpu *priv_stack_ptr;\n \tstruct mutex dst_mutex; /* protects dst_* pointers below, *after* prog becomes visible */\n \tstruct bpf_prog *dst_prog;\n+\tstruct bpf_sdt_probe_info *sdt_probe;\n \tstruct bpf_trampoline *dst_trampoline;\n \tenum bpf_prog_type saved_dst_prog_type;\n \tenum bpf_attach_type saved_dst_attach_type;\n@@ -4154,11 +4173,14 @@ int bpf_prog_get_file_line(struct bpf_prog *prog, unsigned long ip, const char *\n \t\t\t   const char **linep, int *nump);\n struct bpf_prog *bpf_prog_find_from_stack(void);\n \n-int bpf_insn_array_init(struct bpf_map *map, const struct bpf_prog *prog);\n+int bpf_insn_array_init(struct bpf_map *map, struct bpf_verifier_env *env);\n int bpf_insn_array_ready(struct bpf_map *map);\n void bpf_insn_array_release(struct bpf_map *map);\n void bpf_insn_array_adjust(struct bpf_map *map, u32 off, u32 len);\n void bpf_insn_array_adjust_after_remove(struct bpf_map *map, u32 off, u32 len);\n+int bpf_insn_array_get_sdt_probe_by_name(struct bpf_prog *prog, const char *name,\n+\t\t\t\t\t struct bpf_insn_array_value *val,\n+\t\t\t\t\t unsigned long *ip);\n \n #ifdef CONFIG_BPF_SYSCALL\n void bpf_prog_update_insn_ptrs(struct bpf_prog *prog, u32 *offsets, void *image);\ndiff --git a/include/linux/bpf_types.h b/include/linux/bpf_types.h\nindex e5906829aa6f37..1aac6bc71c6fbb 100644\n--- a/include/linux/bpf_types.h\n+++ b/include/linux/bpf_types.h\n@@ -157,3 +157,4 @@ BPF_LINK_TYPE(BPF_LINK_TYPE_KPROBE_MULTI, kprobe_multi)\n BPF_LINK_TYPE(BPF_LINK_TYPE_STRUCT_OPS, struct_ops)\n BPF_LINK_TYPE(BPF_LINK_TYPE_UPROBE_MULTI, uprobe_multi)\n BPF_LINK_TYPE(BPF_LINK_TYPE_TRACING_MULTI, tracing_multi)\n+BPF_LINK_TYPE(BPF_LINK_TYPE_SDT, sdt)\ndiff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h\nindex 76b8b7627a1085..e824b7b6469040 100644\n--- a/include/linux/bpf_verifier.h\n+++ b/include/linux/bpf_verifier.h\n@@ -691,6 +691,9 @@ struct bpf_insn_aux_data {\n \tu8 fastcall_spills_num:3;\n \tu8 arg_prog:4;\n \n+\t/* set when the instruction is a SDT probe site */\n+\tstruct bpf_insn_array_value *sdt_entry;\n+\n \t/* below fields are initialized once */\n \tunsigned int orig_idx; /* original instruction index */\n \tu32 jmp_point:1;\ndiff --git a/include/linux/filter.h b/include/linux/filter.h\nindex 67d337ede91b8e..ba4025160a6f33 100644\n--- a/include/linux/filter.h\n+++ b/include/linux/filter.h\n@@ -1190,6 +1190,7 @@ bool bpf_jit_supports_insn(struct bpf_insn *insn, bool in_arena);\n bool bpf_jit_supports_private_stack(void);\n bool bpf_jit_supports_timed_may_goto(void);\n bool bpf_jit_supports_fsession(void);\n+bool bpf_jit_supports_sdt_probe(void);\n u64 bpf_arch_uaddress_limit(void);\n void arch_bpf_stack_walk(bool (*consume_fn)(void *cookie, u64 ip, u64 sp, u64 bp), void *cookie);\n u64 arch_bpf_timed_may_goto(void);\ndiff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h\nindex c91b5a4bda03cb..6490eb5dfb68ee 100644\n--- a/include/uapi/linux/bpf.h\n+++ b/include/uapi/linux/bpf.h\n@@ -1159,6 +1159,7 @@ enum bpf_attach_type {\n \tBPF_TRACE_FENTRY_MULTI,\n \tBPF_TRACE_FEXIT_MULTI,\n \tBPF_TRACE_FSESSION_MULTI,\n+\tBPF_TRACE_SDT,\n \t__MAX_BPF_ATTACH_TYPE\n };\n \n@@ -1184,6 +1185,7 @@ enum bpf_link_type {\n \tBPF_LINK_TYPE_NETKIT = 13,\n \tBPF_LINK_TYPE_SOCKMAP = 14,\n \tBPF_LINK_TYPE_TRACING_MULTI = 15,\n+\tBPF_LINK_TYPE_SDT,\n \t__MAX_BPF_LINK_TYPE,\n };\n \n@@ -1466,6 +1468,9 @@ enum {\n \n /* Enable BPF ringbuf overwrite mode */\n \tBPF_F_RB_OVERWRITE\t= (1U \u003c\u003c 19),\n+\n+/* insn_array map is used for bpf SDT probe */\n+\tBPF_F_INSN_ARRAY_SDT\t= (1U \u003c\u003c 20),\n };\n \n /* Flags for BPF_PROG_QUERY. */\n@@ -1518,6 +1523,7 @@ struct bpf_common_attr {\n };\n \n #define BPF_OBJ_NAME_LEN 16U\n+#define BPF_SDT_MAX_NAME_LEN 64\n \n enum {\n \tBPF_STREAM_STDOUT = 1,\n@@ -1669,6 +1675,15 @@ union bpf_attr {\n \t\t * verification.\n \t\t */\n \t\t__s32\t\tkeyring_id;\n+\t\t/* fd of the BPF_MAP_TYPE_INSN_ARRAY map created with\n+\t\t * BPF_F_INSN_ARRAY_SDT, used for SDT probe\n+\t\t */\n+\t\t__u32\t\tsdt_map_fd;\n+\t\t/* Kernel looks up the probe using target prog and probe name */\n+\t\tstruct {\n+\t\t\t__u32\ttarget_prog_fd;\n+\t\t\tchar\tname[BPF_SDT_MAX_NAME_LEN]; /* probe name */\n+\t\t} sdt;\n \t};\n \n \tstruct { /* anonymous struct used by BPF_OBJ_* commands */\n@@ -7732,7 +7747,11 @@ struct bpf_insn_array_value {\n \t__u32 orig_off;\n \t__u32 xlated_off;\n \t__u32 jitted_off;\n-\t__u32 :32;\n+\t__u32 btf_id;\t\t/* BTF FUNC_PROTO type ID (0 if none) */\n+\t__u8  nargs;\t\t/* argument count (0..5) */\n+\t__u8  arg_reg[5];\t/* BPF register for each argument */\n+\t__u8  pad[2];\n+\tchar  name[BPF_SDT_MAX_NAME_LEN]; /* probe name */\n };\n \n #endif /* _UAPI__LINUX_BPF_H__ */\ndiff --git a/kernel/bpf/bpf_insn_array.c b/kernel/bpf/bpf_insn_array.c\nindex a2f84afe6f7c65..067cc1b817d5f7 100644\n--- a/kernel/bpf/bpf_insn_array.c\n+++ b/kernel/bpf/bpf_insn_array.c\n@@ -2,11 +2,14 @@\n /* Copyright (c) 2025 Isovalent */\n \n #include \u003clinux/bpf.h\u003e\n+#include \u003clinux/bpf_verifier.h\u003e\n+#include \u003clinux/string.h\u003e\n \n struct bpf_insn_array {\n \tstruct bpf_map map;\n \tatomic_t used;\n \tlong *ips;\n+\tint subtype;\n \tDECLARE_FLEX_ARRAY(struct bpf_insn_array_value, values);\n };\n \n@@ -15,6 +18,11 @@ struct bpf_insn_array {\n \n #define INSN_DELETED ((u32)-1)\n \n+enum bpf_insn_array_subtype {\n+\tBPF_INSN_ARRAY_SUBTYPE_JUMP_TABLE = 0,\n+\tBPF_INSN_ARRAY_SUBTYPE_SDT = 1,\n+};\n+\n static inline u64 insn_array_alloc_size(u32 max_entries)\n {\n \tconst u64 base_size = sizeof(struct bpf_insn_array);\n@@ -28,7 +36,8 @@ static int insn_array_alloc_check(union bpf_attr *attr)\n \tu32 value_size = sizeof(struct bpf_insn_array_value);\n \n \tif (attr-\u003emax_entries == 0 || attr-\u003ekey_size != 4 ||\n-\t    attr-\u003evalue_size != value_size || attr-\u003emap_flags != 0)\n+\t    attr-\u003evalue_size != value_size ||\n+\t    attr-\u003emap_flags \u0026 ~BPF_F_INSN_ARRAY_SDT)\n \t\treturn -EINVAL;\n \n \treturn 0;\n@@ -55,6 +64,11 @@ static struct bpf_map *insn_array_alloc(union bpf_attr *attr)\n \n \tbpf_map_init_from_attr(\u0026insn_array-\u003emap, attr);\n \n+\tif (attr-\u003emap_flags \u0026 BPF_F_INSN_ARRAY_SDT)\n+\t\tinsn_array-\u003esubtype = BPF_INSN_ARRAY_SUBTYPE_SDT;\n+\telse\n+\t\tinsn_array-\u003esubtype = BPF_INSN_ARRAY_SUBTYPE_JUMP_TABLE;\n+\n \t/* BPF programs aren't allowed to write to the map */\n \tinsn_array-\u003emap.map_flags |= BPF_F_RDONLY_PROG;\n \n@@ -90,6 +104,13 @@ static long insn_array_update_elem(struct bpf_map *map, void *key, void *value,\n \n \tinsn_array-\u003evalues[index].orig_off = val.orig_off;\n \n+\tif (insn_array-\u003esubtype == BPF_INSN_ARRAY_SUBTYPE_SDT) {\n+\t\tinsn_array-\u003evalues[index].btf_id = val.btf_id;\n+\t\tinsn_array-\u003evalues[index].nargs = val.nargs;\n+\t\tmemcpy(insn_array-\u003evalues[index].arg_reg, val.arg_reg, sizeof(val.arg_reg));\n+\t\tstrscpy(insn_array-\u003evalues[index].name, val.name, sizeof(val.name));\n+\t}\n+\n \treturn 0;\n }\n \n@@ -180,10 +201,12 @@ static inline bool valid_offsets(const struct bpf_insn_array *insn_array,\n \treturn true;\n }\n \n-int bpf_insn_array_init(struct bpf_map *map, const struct bpf_prog *prog)\n+int bpf_insn_array_init(struct bpf_map *map, struct bpf_verifier_env *env)\n {\n \tstruct bpf_insn_array *insn_array = cast_insn_array(map);\n \tstruct bpf_insn_array_value *values = insn_array-\u003evalues;\n+\tconst struct bpf_prog *prog = env-\u003eprog;\n+\tconst struct bpf_insn *insn;\n \tint i;\n \n \tif (!is_frozen(map))\n@@ -206,6 +229,15 @@ int bpf_insn_array_init(struct bpf_map *map, const struct bpf_prog *prog)\n \tfor (i = 0; i \u003c map-\u003emax_entries; i++)\n \t\tvalues[i].xlated_off = values[i].orig_off;\n \n+\tif (insn_array-\u003esubtype == BPF_INSN_ARRAY_SUBTYPE_SDT) {\n+\t\tfor (i = 0; i \u003c map-\u003emax_entries; i++) {\n+\t\t\tinsn = \u0026prog-\u003einsnsi[values[i].orig_off];\n+\t\t\tif (insn-\u003ecode != (BPF_JMP | BPF_JA) || insn-\u003eoff != 0)\n+\t\t\t\treturn -EINVAL;\n+\t\t\tenv-\u003einsn_aux_data[values[i].orig_off].sdt_entry = \u0026values[i];\n+\t\t}\n+\t}\n+\n \treturn 0;\n }\n \n@@ -231,6 +263,34 @@ void bpf_insn_array_release(struct bpf_map *map)\n \tatomic_set(\u0026insn_array-\u003eused, 0);\n }\n \n+int bpf_insn_array_get_sdt_probe_by_name(struct bpf_prog *prog, const char *name,\n+\t\t\t\t\t struct bpf_insn_array_value *val,\n+\t\t\t\t\t unsigned long *ip)\n+{\n+\tint i, j;\n+\tstruct bpf_map *map;\n+\tstruct bpf_insn_array *insn_array;\n+\n+\tfor (i = 0; i \u003c prog-\u003eaux-\u003eused_map_cnt; i++) {\n+\t\tmap = prog-\u003eaux-\u003eused_maps[i];\n+\t\tif (map-\u003emap_type != BPF_MAP_TYPE_INSN_ARRAY ||\n+\t\t    !(map-\u003emap_flags \u0026 BPF_F_INSN_ARRAY_SDT))\n+\t\t\tcontinue;\n+\t\tinsn_array = cast_insn_array(map);\n+\t\tfor (j = 0; j \u003c map-\u003emax_entries; j++) {\n+\t\t\tif (insn_array-\u003evalues[j].xlated_off == INSN_DELETED)\n+\t\t\t\tcontinue;\n+\t\t\tif (!strcmp(insn_array-\u003evalues[j].name, name)) {\n+\t\t\t\t*val = insn_array-\u003evalues[j];\n+\t\t\t\t*ip = (unsigned long)insn_array-\u003eips[j];\n+\t\t\t\treturn 0;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\treturn -EEXIST;\n+}\n+\n void bpf_insn_array_adjust(struct bpf_map *map, u32 off, u32 len)\n {\n \tstruct bpf_insn_array *insn_array = cast_insn_array(map);\ndiff --git a/kernel/bpf/cfg.c b/kernel/bpf/cfg.c\nindex 26d37066465f38..1b8734ee4bf3d3 100644\n--- a/kernel/bpf/cfg.c\n+++ b/kernel/bpf/cfg.c\n@@ -251,6 +251,9 @@ static struct bpf_iarray *jt_from_map(struct bpf_map *map)\n \tint err;\n \tint n;\n \n+\tif (map-\u003emap_flags \u0026 BPF_F_INSN_ARRAY_SDT)\n+\t\treturn ERR_PTR(-EINVAL);\n+\n \tjt = bpf_iarray_realloc(NULL, map-\u003emax_entries);\n \tif (!jt)\n \t\treturn ERR_PTR(-ENOMEM);\ndiff --git a/kernel/bpf/core.c b/kernel/bpf/core.c\nindex 649cce41e13f3b..78c7ad3b4e2fc4 100644\n--- a/kernel/bpf/core.c\n+++ b/kernel/bpf/core.c\n@@ -3263,6 +3263,11 @@ bool __weak bpf_jit_supports_fsession(void)\n \treturn false;\n }\n \n+bool __weak bpf_jit_supports_sdt_probe(void)\n+{\n+\treturn false;\n+}\n+\n u64 __weak bpf_arch_uaddress_limit(void)\n {\n #if defined(CONFIG_64BIT) \u0026\u0026 defined(CONFIG_ARCH_HAS_NON_OVERLAPPING_ADDRESS_SPACE)\ndiff --git a/kernel/bpf/fixups.c b/kernel/bpf/fixups.c\nindex 3cf2cc6e3ab66a..4281c71cde4c66 100644\n--- a/kernel/bpf/fixups.c\n+++ b/kernel/bpf/fixups.c\n@@ -568,6 +568,10 @@ int bpf_opt_remove_nops(struct bpf_verifier_env *env)\n \t\tif (!is_may_goto_0 \u0026\u0026 !is_ja)\n \t\t\tcontinue;\n \n+\t\t/* SDT probes are NOPs kept for text_poke at attach time. */\n+\t\tif (env-\u003einsn_aux_data[i].sdt_entry)\n+\t\t\tcontinue;\n+\n \t\terr = verifier_remove_insns(env, i, 1);\n \t\tif (err)\n \t\t\treturn err;\ndiff --git a/kernel/bpf/liveness.c b/kernel/bpf/liveness.c\nindex 0aadfbae0acc5a..50ab8c29c0ebd1 100644\n--- a/kernel/bpf/liveness.c\n+++ b/kernel/bpf/liveness.c\n@@ -2062,6 +2062,7 @@ struct insn_live_regs {\n /* Compute info-\u003e{use,def} fields for the instruction */\n static void compute_insn_live_regs(struct bpf_verifier_env *env,\n \t\t\t\t   struct bpf_insn *insn,\n+\t\t\t\t   int insn_idx,\n \t\t\t\t   struct insn_live_regs *info)\n {\n \tstruct bpf_call_summary cs;\n@@ -2163,10 +2164,27 @@ static void compute_insn_live_regs(struct bpf_verifier_env *env,\n \t\tswitch (code) {\n \t\tcase BPF_JA:\n \t\t\tdef = 0;\n-\t\t\tif (BPF_SRC(insn-\u003ecode) == BPF_X)\n+\t\t\tif (BPF_SRC(insn-\u003ecode) == BPF_X) {\n \t\t\t\tuse = dst;\n-\t\t\telse\n+\t\t\t} else if (env-\u003einsn_aux_data[insn_idx].sdt_entry) {\n+\t\t\t\tstruct bpf_insn_array_value *sdt;\n+\t\t\t\tint i;\n+\n+\t\t\t\t/*\n+\t\t\t\t * Without marking the argument registers arg_reg[]\n+\t\t\t\t * as live, the liveness pass would clear them before\n+\t\t\t\t * the probe site, causing check_sdt_probe() to reject\n+\t\t\t\t * the prog with \"arg is uninitialized\".\n+\t\t\t\t */\n \t\t\t\tuse = 0;\n+\t\t\t\tsdt = env-\u003einsn_aux_data[insn_idx].sdt_entry;\n+\t\t\t\tfor (i = 0; i \u003c sdt-\u003enargs; i++) {\n+\t\t\t\t\tif (sdt-\u003earg_reg[i] \u003c BPF_REG_FP)\n+\t\t\t\t\t\tuse |= BIT(sdt-\u003earg_reg[i]);\n+\t\t\t\t}\n+\t\t\t} else {\n+\t\t\t\tuse = 0;\n+\t\t\t}\n \t\t\tbreak;\n \t\tcase BPF_JCOND:\n \t\t\tdef = 0;\n@@ -2238,7 +2256,7 @@ int bpf_compute_live_registers(struct bpf_verifier_env *env)\n \t}\n \n \tfor (i = 0; i \u003c insn_cnt; ++i)\n-\t\tcompute_insn_live_regs(env, \u0026insns[i], \u0026state[i]);\n+\t\tcompute_insn_live_regs(env, \u0026insns[i], i, \u0026state[i]);\n \n \t/* Forward pass: resolve stack access through FP-derived pointers */\n \terr = bpf_compute_subprog_arg_access(env);\ndiff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c\nindex 6db306d23b479f..53897ff1100faf 100644\n--- a/kernel/bpf/syscall.c\n+++ b/kernel/bpf/syscall.c\n@@ -2454,6 +2454,7 @@ static void __bpf_prog_put_noref(struct bpf_prog *prog, bool deferred)\n \tkvfree(prog-\u003eaux-\u003elinfo);\n \tkfree(prog-\u003eaux-\u003ekfunc_tab);\n \tkfree(prog-\u003eaux-\u003ectx_arg_info);\n+\tkfree(prog-\u003eaux-\u003esdt_probe);\n \tif (prog-\u003eaux-\u003eattach_btf)\n \t\tbtf_put(prog-\u003eaux-\u003eattach_btf);\n \n@@ -2967,13 +2968,15 @@ int __init __used bpf_multi_func(void) { return 0; }\n BTF_ID_LIST_GLOBAL_SINGLE(bpf_multi_func_btf_id, func, bpf_multi_func)\n \n /* last field in 'union bpf_attr' used by this command */\n-#define BPF_PROG_LOAD_LAST_FIELD keyring_id\n+#define BPF_PROG_LOAD_LAST_FIELD sdt.name\n \n static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, struct bpf_log_attr *attr_log)\n {\n \tenum bpf_prog_type type = attr-\u003eprog_type;\n+\tstruct bpf_sdt_probe_info *prog_sdt_probe = NULL;\n \tstruct bpf_prog *prog, *dst_prog = NULL;\n \tstruct btf *attach_btf = NULL;\n+\tu32 attach_btf_id = 0;\n \tstruct bpf_token *token = NULL;\n \tbool bpf_cap;\n \tint err;\n@@ -3082,33 +3085,75 @@ static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, struct bpf_log_at\n \t\tbtf_get(attach_btf);\n \t}\n \n+\t/*\n+\t * find the probe site in the target program at load time of the\n+\t * observer program, so the verifier can check the observer's context\n+\t * arguments from the probe's FUNC_PROTO.\n+\t */\n+\tif (attr-\u003eexpected_attach_type == BPF_TRACE_SDT) {\n+\t\tstruct bpf_insn_array_value sdt_val;\n+\t\tstruct bpf_sdt_probe_info *sdt_probe;\n+\t\tunsigned long probe_ip;\n+\n+\t\tif (!bpf_jit_supports_sdt_probe()) {\n+\t\t\terr = -EOPNOTSUPP;\n+\t\t\tgoto put_token;\n+\t\t}\n+\t\tif (!attr-\u003esdt.target_prog_fd) {\n+\t\t\terr = -EINVAL;\n+\t\t\tgoto put_token;\n+\t\t}\n+\t\tdst_prog = bpf_prog_get(attr-\u003esdt.target_prog_fd);\n+\t\tif (IS_ERR(dst_prog)) {\n+\t\t\terr = PTR_ERR(dst_prog);\n+\t\t\tdst_prog = NULL;\n+\t\t\tgoto put_token;\n+\t\t}\n+\t\terr = bpf_insn_array_get_sdt_probe_by_name(dst_prog, attr-\u003esdt.name,\n+\t\t\t\t\t\t\t   \u0026sdt_val, \u0026probe_ip);\n+\t\tif (err)\n+\t\t\tgoto put_sdt;\n+\t\tsdt_probe = kzalloc_obj(struct bpf_sdt_probe_info, GFP_USER);\n+\t\tif (!sdt_probe) {\n+\t\t\terr = -ENOMEM;\n+\t\t\tgoto put_sdt;\n+\t\t}\n+\t\tsdt_probe-\u003eval = sdt_val;\n+\t\tsdt_probe-\u003eprobe_ip = probe_ip;\n+\t\tprog_sdt_probe = sdt_probe;\n+\t\tattach_btf_id = sdt_val.btf_id;\n+\t}\n+\n+\n \tif (bpf_prog_load_check_attach(type, attr-\u003eexpected_attach_type,\n-\t\t\t\t       attach_btf, attr-\u003eattach_btf_id,\n+\t\t\t\t       attach_btf,\n+\t\t\t\t       prog_sdt_probe ? attach_btf_id : attr-\u003eattach_btf_id,\n \t\t\t\t       dst_prog, multi_func)) {\n-\t\tif (dst_prog)\n-\t\t\tbpf_prog_put(dst_prog);\n-\t\tif (attach_btf)\n-\t\t\tbtf_put(attach_btf);\n \t\terr = -EINVAL;\n-\t\tgoto put_token;\n+\t\tgoto put_sdt;\n \t}\n \n \t/* plain bpf_prog allocation */\n \tprog = bpf_prog_alloc(bpf_prog_size(attr-\u003einsn_cnt), GFP_USER);\n \tif (!prog) {\n-\t\tif (dst_prog)\n-\t\t\tbpf_prog_put(dst_prog);\n-\t\tif (attach_btf)\n-\t\t\tbtf_put(attach_btf);\n \t\terr = -EINVAL;\n-\t\tgoto put_token;\n+\t\tgoto put_sdt;\n \t}\n \n \tprog-\u003eexpected_attach_type = attr-\u003eexpected_attach_type;\n \tprog-\u003esleepable = !!(attr-\u003eprog_flags \u0026 BPF_F_SLEEPABLE);\n \tprog-\u003eaux-\u003eattach_btf = attach_btf;\n-\tprog-\u003eaux-\u003eattach_btf_id = multi_func ? bpf_multi_func_btf_id[0] : attr-\u003eattach_btf_id;\n+\tif (prog_sdt_probe)\n+\t\tprog-\u003eaux-\u003eattach_btf_id = attach_btf_id;\n+\telse\n+\t\tprog-\u003eaux-\u003eattach_btf_id =\n+\t\t\tmulti_func ? bpf_multi_func_btf_id[0] : attr-\u003eattach_btf_id;\n \tprog-\u003eaux-\u003edst_prog = dst_prog;\n+\tprog-\u003eaux-\u003esdt_probe = prog_sdt_probe;\n+\t/* ownership of dst_prog/attach_btf/prog_sdt_probe moved to prog-\u003eaux */\n+\tdst_prog = NULL;\n+\tattach_btf = NULL;\n+\tprog_sdt_probe = NULL;\n \tprog-\u003eaux-\u003edev_bound = !!attr-\u003eprog_ifindex;\n \tprog-\u003eaux-\u003exdp_has_frags = attr-\u003eprog_flags \u0026 BPF_F_XDP_HAS_FRAGS;\n \n@@ -3242,6 +3287,12 @@ static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, struct bpf_log_at\n \tif (prog-\u003eaux-\u003eattach_btf)\n \t\tbtf_put(prog-\u003eaux-\u003eattach_btf);\n \tbpf_prog_free(prog);\n+put_sdt:\n+\tkfree(prog_sdt_probe);\n+\tif (attach_btf)\n+\t\tbtf_put(attach_btf);\n+\tif (dst_prog)\n+\t\tbpf_prog_put(dst_prog);\n put_token:\n \tbpf_token_put(token);\n \treturn err;\n@@ -3686,6 +3737,89 @@ static const struct bpf_link_ops bpf_tracing_link_lops = {\n \t.fill_link_info = bpf_tracing_link_fill_link_info,\n };\n \n+struct bpf_sdt_link {\n+\tstruct bpf_tramp_link link;\n+\tstruct bpf_trampoline *trampoline;\n+};\n+\n+static void bpf_sdt_link_release(struct bpf_link *link)\n+{\n+\tstruct bpf_sdt_link *sdt_link = container_of(link, struct bpf_sdt_link, link.link);\n+\tstruct bpf_prog *prog = sdt_link-\u003elink.link.prog;\n+\tstruct bpf_prog *tgt_prog = prog-\u003eaux-\u003edst_prog;\n+\n+\tWARN_ON_ONCE(bpf_trampoline_unlink_prog(\u0026sdt_link-\u003elink.node, sdt_link-\u003etrampoline,\n+\t\t\t\t\t\ttgt_prog));\n+\tbpf_trampoline_put(sdt_link-\u003etrampoline);\n+}\n+\n+static void bpf_sdt_link_dealloc(struct bpf_link *link)\n+{\n+\tstruct bpf_sdt_link *sdt_link = container_of(link, struct bpf_sdt_link, link.link);\n+\n+\tkfree(sdt_link);\n+}\n+\n+static const struct bpf_link_ops bpf_sdt_link_lops = {\n+\t.release = bpf_sdt_link_release,\n+\t.dealloc = bpf_sdt_link_dealloc,\n+};\n+\n+static int bpf_sdt_link_attach(struct bpf_prog *prog)\n+{\n+\tstruct bpf_sdt_probe_info *sdt = prog-\u003eaux-\u003esdt_probe;\n+\tstruct bpf_attach_target_info tgt_info = {};\n+\tstruct bpf_link_primer primer;\n+\tstruct bpf_sdt_link *sdt_link;\n+\tstruct bpf_prog *tgt_prog = prog-\u003eaux-\u003edst_prog;\n+\tu64 tr_key;\n+\tint i, err;\n+\n+\tif (!sdt || !tgt_prog)\n+\t\treturn -EINVAL;\n+\n+\tsdt_link = kzalloc_obj(struct bpf_sdt_link, GFP_USER);\n+\tif (!sdt_link)\n+\t\treturn -ENOMEM;\n+\n+\tbpf_tramp_link_init(\u0026sdt_link-\u003elink, BPF_LINK_TYPE_SDT, \u0026bpf_sdt_link_lops, prog,\n+\t\t\t    BPF_TRACE_SDT, 0);\n+\terr = bpf_link_prime(\u0026sdt_link-\u003elink.link, \u0026primer);\n+\tif (err) {\n+\t\tkfree(sdt_link);\n+\t\treturn err;\n+\t}\n+\n+\t/* use orig_off to compute the key as orig_off is unique for each probe */\n+\ttr_key = bpf_trampoline_compute_key(tgt_prog, NULL, sdt-\u003eval.orig_off);\n+\ttgt_info.tgt_addr = sdt-\u003eprobe_ip;\n+\ttgt_info.tgt_name = sdt-\u003eval.name;\n+\ttgt_info.fmodel.nr_args = sdt-\u003eval.nargs;\n+\tfor (i = 0; i \u003c sdt-\u003eval.nargs; i++) {\n+\t\ttgt_info.fmodel.arg_size[i] = 8;\n+\t\ttgt_info.fmodel.arg_regs[i] = sdt-\u003eval.arg_reg[i];\n+\t}\n+\n+\tsdt_link-\u003etrampoline = bpf_trampoline_get(tr_key, \u0026tgt_info);\n+\tif (!sdt_link-\u003etrampoline) {\n+\t\terr = -ENOMEM;\n+\t\tgoto err_cleanup;\n+\t}\n+\tsdt_link-\u003etrampoline-\u003efunc.sdt_probe_site = 1;\n+\n+\terr = bpf_trampoline_link_prog(\u0026sdt_link-\u003elink.node, sdt_link-\u003etrampoline, tgt_prog);\n+\tif (err)\n+\t\tgoto err_put_tramp;\n+\n+\treturn bpf_link_settle(\u0026primer);\n+\n+err_put_tramp:\n+\tbpf_trampoline_put(sdt_link-\u003etrampoline);\n+err_cleanup:\n+\tbpf_link_cleanup(\u0026primer);\n+\treturn err;\n+}\n+\n static int bpf_tracing_prog_attach(struct bpf_prog *prog,\n \t\t\t\t   int tgt_prog_fd,\n \t\t\t\t   u32 btf_id,\n@@ -4507,6 +4641,7 @@ attach_type_to_prog_type(enum bpf_attach_type attach_type)\n \tcase BPF_TRACE_FENTRY_MULTI:\n \tcase BPF_TRACE_FEXIT_MULTI:\n \tcase BPF_MODIFY_RETURN:\n+\tcase BPF_TRACE_SDT:\n \t\treturn BPF_PROG_TYPE_TRACING;\n \tcase BPF_LSM_MAC:\n \t\treturn BPF_PROG_TYPE_LSM;\n@@ -5893,6 +6028,8 @@ static int link_create(union bpf_attr *attr, bpfptr_t uattr)\n \t\t\tret = cgroup_bpf_link_attach(attr, prog);\n \t\telse if (is_tracing_multi(prog-\u003eexpected_attach_type))\n \t\t\tret = bpf_tracing_multi_attach(prog, attr);\n+\t\telse if (prog-\u003eexpected_attach_type == BPF_TRACE_SDT)\n+\t\t\tret = bpf_sdt_link_attach(prog);\n \t\telse\n \t\t\tret = bpf_tracing_prog_attach(prog,\n \t\t\t\t\t\t      attr-\u003elink_create.target_fd,\ndiff --git a/kernel/bpf/trampoline.c b/kernel/bpf/trampoline.c\nindex 1a721fc4bef562..68f87e7b901f91 100644\n--- a/kernel/bpf/trampoline.c\n+++ b/kernel/bpf/trampoline.c\n@@ -691,6 +691,9 @@ static int bpf_trampoline_update(struct bpf_trampoline *tr, bool lock_direct_mut\n \t/* clear all bits except SHARE_IPMODIFY and TAIL_CALL_CTX */\n \ttr-\u003eflags \u0026= (BPF_TRAMP_F_SHARE_IPMODIFY | BPF_TRAMP_F_TAIL_CALL_CTX);\n \n+\tif (tr-\u003efunc.sdt_probe_site)\n+\t\ttr-\u003eflags |= BPF_TRAMP_F_SDT_PROBE;\n+\n \tif (tnodes[BPF_TRAMP_FEXIT].nr_nodes ||\n \t    tnodes[BPF_TRAMP_MODIFY_RETURN].nr_nodes) {\n \t\t/* NOTE: BPF_TRAMP_F_RESTORE_REGS and BPF_TRAMP_F_SKIP_FRAME\n@@ -784,6 +787,7 @@ static enum bpf_tramp_prog_type bpf_attach_type_to_tramp(struct bpf_prog *prog)\n \tswitch (prog-\u003eexpected_attach_type) {\n \tcase BPF_TRACE_FENTRY:\n \tcase BPF_TRACE_FENTRY_MULTI:\n+\tcase BPF_TRACE_SDT:\n \t\treturn BPF_TRAMP_FENTRY;\n \tcase BPF_MODIFY_RETURN:\n \t\treturn BPF_TRAMP_MODIFY_RETURN;\ndiff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c\nindex 25aea4271cd089..a875d64a095071 100644\n--- a/kernel/bpf/verifier.c\n+++ b/kernel/bpf/verifier.c\n@@ -16451,6 +16451,7 @@ static bool return_retval_range(struct bpf_verifier_env *env, struct bpf_retval_\n \t\t\tbreak;\n \t\tcase BPF_TRACE_RAW_TP:\n \t\tcase BPF_MODIFY_RETURN:\n+\t\tcase BPF_TRACE_SDT:\n \t\t\treturn false;\n \t\tcase BPF_TRACE_ITER:\n \t\tdefault:\n@@ -17242,6 +17243,133 @@ static int check_indirect_jump(struct bpf_verifier_env *env, struct bpf_insn *in\n \treturn INSN_IDX_UPDATED;\n }\n \n+static int check_sdt_probe(struct bpf_verifier_env *env, int insn_idx)\n+{\n+\tenum bpf_prog_type prog_type = resolve_prog_type(env-\u003eprog);\n+\tstruct bpf_insn_array_value *val;\n+\tstruct bpf_func_state *frame;\n+\tstruct bpf_reg_state *regs;\n+\tconst struct btf *btf;\n+\tconst struct btf_type *proto;\n+\tconst struct btf_param *args;\n+\tint i, nargs;\n+\n+\tval = env-\u003einsn_aux_data[insn_idx].sdt_entry;\n+\tif (!val-\u003enargs)\n+\t\treturn 0;\n+\n+\tif (val-\u003enargs \u003e MAX_BPF_FUNC_REG_ARGS) {\n+\t\tverbose(env, \"SDT probe nargs %u \u003e 5\\n\", val-\u003enargs);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tframe = env-\u003ecur_state-\u003eframe[env-\u003ecur_state-\u003ecurframe];\n+\tregs = frame-\u003eregs;\n+\n+\tfor (i = 0; i \u003c val-\u003enargs; i++) {\n+\t\tu8 reg = val-\u003earg_reg[i];\n+\t\tstruct bpf_reg_state *rs = \u0026regs[reg];\n+\n+\t\tif (rs-\u003etype == NOT_INIT) {\n+\t\t\tverbose(env, \"SDT arg%d (r%d) is uninitialized\\n\", i, reg);\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\t}\n+\n+\tbtf = env-\u003eprog-\u003eaux-\u003ebtf;\n+\tif (!btf || !val-\u003ebtf_id) {\n+\t\tverbose(env, \"BTF is required for SDT probe with %u arguments\\n\", val-\u003enargs);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tproto = btf_type_by_id(btf, val-\u003ebtf_id);\n+\tif (!proto || !btf_type_is_func_proto(proto)) {\n+\t\tverbose(env, \"SDT btf_id %u is not a FUNC_PROTO\\n\", val-\u003ebtf_id);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tnargs = btf_type_vlen(proto);\n+\tif (nargs != val-\u003enargs) {\n+\t\tverbose(env, \"SDT nargs %u != BTF FUNC_PROTO nargs %d\\n\",\n+\t\t\tval-\u003enargs, nargs);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\targs = (const struct btf_param *)(proto + 1);\n+\tfor (i = 0; i \u003c nargs; i++) {\n+\t\tu8 reg = val-\u003earg_reg[i];\n+\t\tstruct bpf_reg_state *rs = \u0026regs[reg];\n+\t\tconst struct btf_type *t;\n+\t\tu32 arg_btf_id;\n+\t\tu32 t_size;\n+\n+\t\tt = btf_type_skip_modifiers(btf, args[i].type, NULL);\n+\t\tt_size = t ? t-\u003esize : 0;\n+\n+\t\tif (btf_type_is_scalar(t)) {\n+\t\t\tif (base_type(rs-\u003etype) != SCALAR_VALUE) {\n+\t\t\t\tverbose(env, \"SDT arg%d (r%d) type %s expected scalar\\n\",\n+\t\t\t\t\ti, reg, reg_type_str(env, rs-\u003etype));\n+\t\t\t\treturn -EACCES;\n+\t\t\t}\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\t/*\n+\t\t * Small structs/unions (\u003c= 8 bytes) are passed by value in\n+\t\t * a register as a SCALAR_VALUE carrying the raw bytes.  The\n+\t\t * BPF_SDT_PROBE\u003cN\u003e macro enforces sizeof(arg) \u003c= 8 at build\n+\t\t * time, so the FUNC_PROTO parameter type is the struct itself\n+\t\t * (not a pointer) and the verifier sees SCALAR_VALUE.\n+\t\t */\n+\t\tif (btf_type_is_struct(t) \u0026\u0026 t_size \u003c= 8) {\n+\t\t\tif (base_type(rs-\u003etype) != SCALAR_VALUE) {\n+\t\t\t\tverbose(env,\n+\t\t\t\t\"SDT arg%d (r%d) type %s expected scalar (small struct by value)\\n\",\n+\t\t\t\t\ti, reg, reg_type_str(env, rs-\u003etype));\n+\t\t\t\treturn -EACCES;\n+\t\t\t}\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (!btf_type_is_ptr(t)) {\n+\t\t\tverbose(env, \"SDT arg%d (r%d) unsupported BTF parameter kind\\n\",\n+\t\t\t\ti, reg);\n+\t\t\treturn -EACCES;\n+\t\t}\n+\n+\t\t/*\n+\t\t * When the probe argument is the target program's context\n+\t\t * type (e.g. struct xdp_md * for XDP), the register at the\n+\t\t * probe site is PTR_TO_CTX, not PTR_TO_BTF_ID.\n+\t\t */\n+\t\tif (base_type(rs-\u003etype) == PTR_TO_CTX \u0026\u0026\n+\t\t    btf_is_prog_ctx_type(\u0026env-\u003elog, btf, t, prog_type, i))\n+\t\t\tcontinue;\n+\n+\t\tt = btf_type_skip_modifiers(btf, t-\u003etype, \u0026arg_btf_id);\n+\t\tif (!btf_type_is_struct(t)) {\n+\t\t\tverbose(env, \"SDT arg%d (r%d) unsupported BTF pointer target\\n\",\n+\t\t\t\ti, reg);\n+\t\t\treturn -EACCES;\n+\t\t}\n+\n+\t\tif (base_type(rs-\u003etype) != PTR_TO_BTF_ID) {\n+\t\t\tverbose(env, \"SDT arg%d (r%d) type %s expected PTR_TO_BTF_ID\\n\",\n+\t\t\t\ti, reg, reg_type_str(env, rs-\u003etype));\n+\t\t\treturn -EACCES;\n+\t\t}\n+\n+\t\tif (!btf_struct_ids_match(\u0026env-\u003elog, rs-\u003ebtf, rs-\u003ebtf_id,\n+\t\t\t\t\t  rs-\u003evar_off.value, btf, arg_btf_id, false)) {\n+\t\t\tverbose(env, \"SDT arg%d (r%d) btf_id %u does not match expected %u\\n\",\n+\t\t\t\ti, reg, rs-\u003ebtf_id, arg_btf_id);\n+\t\t\treturn -EACCES;\n+\t\t}\n+\t}\n+\treturn 0;\n+}\n+\n static int do_check_insn(struct bpf_verifier_env *env, bool *do_print_state)\n {\n \tint err;\n@@ -17317,6 +17445,9 @@ static int do_check_insn(struct bpf_verifier_env *env, bool *do_print_state)\n \t\t\tif (BPF_SRC(insn-\u003ecode) == BPF_X)\n \t\t\t\treturn check_indirect_jump(env, insn);\n \n+\t\t\tif (env-\u003einsn_aux_data[env-\u003einsn_idx].sdt_entry)\n+\t\t\t\treturn check_sdt_probe(env, env-\u003einsn_idx);\n+\n \t\t\tif (class == BPF_JMP)\n \t\t\t\tenv-\u003einsn_idx += insn-\u003eoff + 1;\n \t\t\telse\n@@ -17882,7 +18013,7 @@ static int __add_used_map(struct bpf_verifier_env *env, struct bpf_map *map)\n \tenv-\u003eused_maps[env-\u003eused_map_cnt++] = map;\n \n \tif (map-\u003emap_type == BPF_MAP_TYPE_INSN_ARRAY) {\n-\t\terr = bpf_insn_array_init(map, env-\u003eprog);\n+\t\terr = bpf_insn_array_init(map, env);\n \t\tif (err) {\n \t\t\tverbose(env, \"Failed to properly initialize insn array\\n\");\n \t\t\treturn err;\n@@ -19328,6 +19459,24 @@ static int check_attach_btf_id(struct bpf_verifier_env *env)\n \t    prog-\u003etype != BPF_PROG_TYPE_EXT)\n \t\treturn 0;\n \n+\tif (prog-\u003eexpected_attach_type == BPF_TRACE_SDT) {\n+\t\tconst struct btf_type *sdt_t;\n+\t\tstruct btf *sdt_btf = tgt_prog ? tgt_prog-\u003eaux-\u003ebtf : NULL;\n+\n+\t\tif (!sdt_btf) {\n+\t\t\tverbose(env, \"SDT observer requires a target program with BTF\\n\");\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\t\tsdt_t = btf_type_by_id(sdt_btf, btf_id);\n+\t\tif (!sdt_t || !btf_type_is_func_proto(sdt_t)) {\n+\t\t\tverbose(env, \"SDT attach_btf_id %u is not a FUNC_PROTO\\n\", btf_id);\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\t\tprog-\u003eaux-\u003eattach_func_proto = sdt_t;\n+\t\tprog-\u003eaux-\u003eattach_func_name = \"sdt_probe\";\n+\t\treturn 0;\n+\t}\n+\n \tret = bpf_check_attach_target(\u0026env-\u003elog, prog, tgt_prog, btf_id, \u0026tgt_info);\n \tif (ret)\n \t\treturn ret;\n@@ -19800,6 +19949,29 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,\n \tif (ret)\n \t\tgoto skip_full_check;\n \n+\tif (attr-\u003esdt_map_fd) {\n+\t\tCLASS(fd, f)(attr-\u003esdt_map_fd);\n+\t\tstruct bpf_map *sdt_map = __bpf_map_get(f);\n+\n+\t\tif (IS_ERR(sdt_map)) {\n+\t\t\tverbose(env, \"sdt_map_fd %d is not a valid bpf_map\\n\",\n+\t\t\t\tattr-\u003esdt_map_fd);\n+\t\t\tret = PTR_ERR(sdt_map);\n+\t\t\tgoto skip_full_check;\n+\t\t}\n+\t\tif (sdt_map-\u003emap_type != BPF_MAP_TYPE_INSN_ARRAY) {\n+\t\t\tverbose(env, \"sdt_map_fd %d is not an INSN_ARRAY map\\n\",\n+\t\t\t\tattr-\u003esdt_map_fd);\n+\t\t\tret = -EINVAL;\n+\t\t\tgoto skip_full_check;\n+\t\t}\n+\t\tret = __add_used_map(env, sdt_map);\n+\t\tif (ret \u003c 0) {\n+\t\t\tverbose(env, \"failed to bind SDT map to program: %d\\n\", ret);\n+\t\t\tgoto skip_full_check;\n+\t\t}\n+\t}\n+\n \tmark_verifier_state_clean(env);\n \n \tif (IS_ERR(btf_vmlinux)) {\ndiff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h\nindex c91b5a4bda03cb..6490eb5dfb68ee 100644\n--- a/tools/include/uapi/linux/bpf.h\n+++ b/tools/include/uapi/linux/bpf.h\n@@ -1159,6 +1159,7 @@ enum bpf_attach_type {\n \tBPF_TRACE_FENTRY_MULTI,\n \tBPF_TRACE_FEXIT_MULTI,\n \tBPF_TRACE_FSESSION_MULTI,\n+\tBPF_TRACE_SDT,\n \t__MAX_BPF_ATTACH_TYPE\n };\n \n@@ -1184,6 +1185,7 @@ enum bpf_link_type {\n \tBPF_LINK_TYPE_NETKIT = 13,\n \tBPF_LINK_TYPE_SOCKMAP = 14,\n \tBPF_LINK_TYPE_TRACING_MULTI = 15,\n+\tBPF_LINK_TYPE_SDT,\n \t__MAX_BPF_LINK_TYPE,\n };\n \n@@ -1466,6 +1468,9 @@ enum {\n \n /* Enable BPF ringbuf overwrite mode */\n \tBPF_F_RB_OVERWRITE\t= (1U \u003c\u003c 19),\n+\n+/* insn_array map is used for bpf SDT probe */\n+\tBPF_F_INSN_ARRAY_SDT\t= (1U \u003c\u003c 20),\n };\n \n /* Flags for BPF_PROG_QUERY. */\n@@ -1518,6 +1523,7 @@ struct bpf_common_attr {\n };\n \n #define BPF_OBJ_NAME_LEN 16U\n+#define BPF_SDT_MAX_NAME_LEN 64\n \n enum {\n \tBPF_STREAM_STDOUT = 1,\n@@ -1669,6 +1675,15 @@ union bpf_attr {\n \t\t * verification.\n \t\t */\n \t\t__s32\t\tkeyring_id;\n+\t\t/* fd of the BPF_MAP_TYPE_INSN_ARRAY map created with\n+\t\t * BPF_F_INSN_ARRAY_SDT, used for SDT probe\n+\t\t */\n+\t\t__u32\t\tsdt_map_fd;\n+\t\t/* Kernel looks up the probe using target prog and probe name */\n+\t\tstruct {\n+\t\t\t__u32\ttarget_prog_fd;\n+\t\t\tchar\tname[BPF_SDT_MAX_NAME_LEN]; /* probe name */\n+\t\t} sdt;\n \t};\n \n \tstruct { /* anonymous struct used by BPF_OBJ_* commands */\n@@ -7732,7 +7747,11 @@ struct bpf_insn_array_value {\n \t__u32 orig_off;\n \t__u32 xlated_off;\n \t__u32 jitted_off;\n-\t__u32 :32;\n+\t__u32 btf_id;\t\t/* BTF FUNC_PROTO type ID (0 if none) */\n+\t__u8  nargs;\t\t/* argument count (0..5) */\n+\t__u8  arg_reg[5];\t/* BPF register for each argument */\n+\t__u8  pad[2];\n+\tchar  name[BPF_SDT_MAX_NAME_LEN]; /* probe name */\n };\n \n #endif /* _UAPI__LINUX_BPF_H__ */\ndiff --git a/tools/lib/bpf/Makefile b/tools/lib/bpf/Makefile\nindex eca584fb061e16..1f6f027a327b66 100644\n--- a/tools/lib/bpf/Makefile\n+++ b/tools/lib/bpf/Makefile\n@@ -235,7 +235,7 @@ install_lib: all_cmd\n \n SRC_HDRS := bpf.h libbpf.h btf.h libbpf_common.h libbpf_legacy.h\t     \\\n \t    bpf_helpers.h bpf_tracing.h bpf_endian.h bpf_core_read.h\t     \\\n-\t    skel_internal.h libbpf_version.h usdt.bpf.h\n+\t    skel_internal.h libbpf_version.h usdt.bpf.h bpf_sdt.h\n GEN_HDRS := $(BPF_GENERATED)\n \n INSTALL_PFX := $(DESTDIR)$(prefix)/include/bpf\ndiff --git a/tools/lib/bpf/bpf.c b/tools/lib/bpf/bpf.c\nindex 96819c082c772d..adf951df6a3ec7 100644\n--- a/tools/lib/bpf/bpf.c\n+++ b/tools/lib/bpf/bpf.c\n@@ -295,7 +295,7 @@ int bpf_prog_load(enum bpf_prog_type prog_type,\n \t\t  const struct bpf_insn *insns, size_t insn_cnt,\n \t\t  struct bpf_prog_load_opts *opts)\n {\n-\tconst size_t attr_sz = offsetofend(union bpf_attr, keyring_id);\n+\tconst size_t attr_sz = offsetofend(union bpf_attr, sdt.name);\n \tvoid *finfo = NULL, *linfo = NULL;\n \tconst char *func_info, *line_info;\n \t__u32 log_size, log_level, attach_prog_fd, attach_btf_obj_fd;\n@@ -303,6 +303,7 @@ int bpf_prog_load(enum bpf_prog_type prog_type,\n \tint fd, attempts;\n \tunion bpf_attr attr;\n \tchar *log_buf;\n+\tconst char *sdt_name;\n \n \tbump_rlimit_memlock();\n \n@@ -369,6 +370,12 @@ int bpf_prog_load(enum bpf_prog_type prog_type,\n \n \tattr.fd_array = ptr_to_u64(OPTS_GET(opts, fd_array, NULL));\n \tattr.fd_array_cnt = OPTS_GET(opts, fd_array_cnt, 0);\n+\tattr.sdt_map_fd = OPTS_GET(opts, sdt_map_fd, 0);\n+\tattr.sdt.target_prog_fd = OPTS_GET(opts, sdt.target_prog_fd, 0);\n+\n+\tsdt_name = OPTS_GET(opts, sdt.name, NULL);\n+\tif (sdt_name)\n+\t\tsnprintf(attr.sdt.name, sizeof(attr.sdt.name), \"%s\", sdt_name);\n \n \tif (log_level) {\n \t\tattr.log_buf = ptr_to_u64(log_buf);\n@@ -861,6 +868,7 @@ int bpf_link_create(int prog_fd, int target_fd,\n \tcase BPF_MODIFY_RETURN:\n \tcase BPF_TRACE_FSESSION:\n \tcase BPF_LSM_MAC:\n+\tcase BPF_TRACE_SDT:\n \t\tattr.link_create.tracing.cookie = OPTS_GET(opts, tracing.cookie, 0);\n \t\tif (!OPTS_ZEROED(opts, tracing))\n \t\t\treturn libbpf_err(-EINVAL);\ndiff --git a/tools/lib/bpf/bpf.h b/tools/lib/bpf/bpf.h\nindex 7534a593edae21..bf3665789420a8 100644\n--- a/tools/lib/bpf/bpf.h\n+++ b/tools/lib/bpf/bpf.h\n@@ -128,9 +128,16 @@ struct bpf_prog_load_opts {\n \n \t/* if set, provides the length of fd_array */\n \t__u32 fd_array_cnt;\n+\t/* if set, FD of the program's BPF_MAP_TYPE_INSN_ARRAY SDT map */\n+\t__u32 sdt_map_fd;\n+\t/* target program fd and probe name for SDT observer program */\n+\tstruct {\n+\t\t__u32 target_prog_fd;\n+\t\tconst char *name;\n+\t} sdt;\n \tsize_t :0;\n };\n-#define bpf_prog_load_opts__last_field fd_array_cnt\n+#define bpf_prog_load_opts__last_field sdt.name\n \n LIBBPF_API int bpf_prog_load(enum bpf_prog_type prog_type,\n \t\t\t     const char *prog_name, const char *license,\ndiff --git a/tools/lib/bpf/bpf_sdt.h b/tools/lib/bpf/bpf_sdt.h\nnew file mode 100644\nindex 00000000000000..23f84460aa3685\n--- /dev/null\n+++ b/tools/lib/bpf/bpf_sdt.h\n@@ -0,0 +1,346 @@\n+/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */\n+\n+/*\n+ * USDT-like probe for BPF programs.\n+ *\n+ * 1. Example\n+ *\n+ * A probe in the target program can be declared and defined like:\n+ *\n+ *   BPF_SDT_DECLARE2(my_trace, int, int);\n+ *\n+ *   SEC(\"xdp\")\n+ *   int xdp_prog(struct xdp_md *ctx)\n+ *   {\n+ *       int len = ctx-\u003edata_end - ctx-\u003edata;\n+ *       int ret = XDP_DROP;\n+ *       ...\n+ *       BPF_SDT_PROBE2(my_trace, len, ret);\n+ *       ...\n+ *   }\n+ *\n+ * An observer would be like:\n+ *\n+ *   SEC(\"bpf_sdt\")\n+ *   int BPF_PROG(observer_prog, int len, int ret)\n+ *   {\n+ *       bpf_printk(\"len=%d ret=%d\\n\", len, ret);\n+ *       return 0;\n+ *   }\n+ *\n+ * The target program and probe site is set at attach time dynamically.\n+ *\n+ * 2. Design\n+ *\n+ * 2.1 declaration macro\n+ *\n+ * BPF_SDT_DECLARE\u003cN\u003e(name, types...) generates:\n+ *\n+ *  - A BTF function-pointer variable in a .debug_* section:\n+ *\n+ *       static void (*const __sdt_type_##name)(types...);\n+ *\n+ *   which produces a BTF VAR -\u003e PTR -\u003e FUNC_PROTO(types...) chain\n+ *   tagged with btf_decl_tag(\"bpf_sdt:\u003cname\u003e:\u003cnargs\u003e\"). The BPF\n+ *   linker discards the .debug_* section but preserves the BTF\n+ *   entries, so the argument types survive into the final object.\n+ *   The type will be passed to the kernel for the verifier to check\n+ *   if it matches the real value type in the argument register.\n+ *\n+ *  - One dummy static variable per argument:\n+ *\n+ *       __attribute__((section(\".debug_bpf_sdt_chk\")))\n+ *       typeof(declared_type) __sdt_chk_##name##_N;\n+ *\n+ *   The dummy variable carries the declared type. At the\n+ *   BPF_SDT_PROBE call site, _Static_assert with\n+ *   __builtin_types_compatible_p() compares the caller's actual\n+ *   argument type against the dummy variable's type:\n+ *\n+ *       _Static_assert(__builtin_types_compatible_p(\n+ *                       typeof(caller_arg),\n+ *                       typeof(__sdt_chk_##name##_N)),\n+ *                       \"type mismatch\");\n+ *\n+ *   For example, if the caller passes an int where the declaration\n+ *   said char *, the compiler fails with a message naming the\n+ *   mismatched argument.\n+ *\n+ * 2.2 probe macro\n+ *\n+ * BPF_SDT_PROBE\u003cN\u003e is an inline assembly sequence that emits into two\n+ * ELF sections simultaneously. A goto+0 instruction is inserted at the\n+ * call site in the bpf prog code section. It works as a NOP, and will be\n+ * patched to a jump instruction when an observer is attached. The call\n+ * site address and argument information are recorded into a separate\n+ * section .bpf_sdt_notes. This section is not executed, it is used as\n+ * metadata. libbpf will convert the metadata to an insn_array map at\n+ * load time.\n+ *\n+ * An interesting note is that the argument registers are recorded in\n+ * BPF move instructions, like r1 = %[arg_reg0]. The instruction is not\n+ * executed, it is only used by the linker to extract the argument\n+ * register from the src_reg.\n+ *\n+ * For BPF_SDT_PROBE2(my_trace, len, ret) the compiler produces:\n+ *\n+ * [code section, e.g. xdp]\n+ *     goto +0                  // NOP, patched to CALL at attach time\n+ *\n+ * [.bpf_sdt_notes]\n+ *     ___sdt_jt_my_trace:      // symbol marking this entry's boundary\n+ *     .quad  0b                // 8 bytes: offset of the NOP in the code\n+ *                              // section (resolved by the linker via\n+ *                              // R_BPF_64_ABS64 relocation)\n+ *     r1 = %[arg1_reg]         // 8 bytes per argument: BPF move instruction\n+ *                              // whose src_reg field encodes the BPF\n+ *                              // register holding each probe argument\n+ *     r2 = %[arg2_reg]\n+ *\n+ *     ....\n+ *\n+ *  [.BTF]\n+ *     FUNC_PROTO (int, int) -\u003e void   // from BPF_SDT_DECLARE2 stub\n+ *     DECL_TAG \"bpf_sdt:my_trace:2\"   // keyed by name + nargs\n+ */\n+\n+#ifndef __BPF_SDT_PROBE_H\n+#define __BPF_SDT_PROBE_H\n+\n+#define ___sdt_decl_attr(name, nargs)\t\t\t\t\t\\\n+\t__attribute__((used, section(\".debug_bpf_sdt_types\"),\t\t\\\n+\t\t\tbtf_decl_tag(\"bpf_sdt:\" #name \":\" #nargs)))\n+\n+#define ___sdt_chk_attr\t\t\t\t\t\t\t\\\n+\t __attribute__((unused, section(\".debug_bpf_sdt_chk\")))\n+\n+#define BPF_SDT_DECLARE(name)\t\t\t\t\t\t\\\n+\t___sdt_decl_attr(name, 0)\t\t\t\t\t\\\n+\tstatic void (*const __sdt_type_##name)(void)\n+\n+#define BPF_SDT_DECLARE1(name, t0)\t\t\t\t\t\\\n+\t___sdt_decl_attr(name, 1)\t\t\t\t\t\\\n+\tstatic void (*const __sdt_type_##name)(t0);\t\t\t\\\n+\t___sdt_chk_attr t0 __sdt_chk_##name##_0\n+\n+#define BPF_SDT_DECLARE2(name, t0, t1)\t\t\t\t\t\\\n+\t___sdt_decl_attr(name, 2)\t\t\t\t\t\\\n+\tstatic void (*const __sdt_type_##name)(t0, t1);\t\t\t\\\n+\t___sdt_chk_attr t0 __sdt_chk_##name##_0;\t\t\t\\\n+\t___sdt_chk_attr t1 __sdt_chk_##name##_1\n+\n+#define BPF_SDT_DECLARE3(name, t0, t1, t2)\t\t\t\t\\\n+\t___sdt_decl_attr(name, 3)\t\t\t\t\t\\\n+\tstatic void (*const __sdt_type_##name)(t0, t1, t2);\t\t\\\n+\t___sdt_chk_attr t0 __sdt_chk_##name##_0;\t\t\t\\\n+\t___sdt_chk_attr t1 __sdt_chk_##name##_1;\t\t\t\\\n+\t___sdt_chk_attr t2 __sdt_chk_##name##_2\n+\n+#define BPF_SDT_DECLARE4(name, t0, t1, t2, t3)\t\t\t\t\\\n+\t___sdt_decl_attr(name, 4)\t\t\t\t\t\\\n+\tstatic void (*const __sdt_type_##name)(t0, t1, t2, t3);\t\t\\\n+\t___sdt_chk_attr t0 __sdt_chk_##name##_0;\t\t\t\\\n+\t___sdt_chk_attr t1 __sdt_chk_##name##_1;\t\t\t\\\n+\t___sdt_chk_attr t2 __sdt_chk_##name##_2;\t\t\t\\\n+\t___sdt_chk_attr t3 __sdt_chk_##name##_3\n+\n+#define BPF_SDT_DECLARE5(name, t0, t1, t2, t3, t4)\t\t\t\\\n+\t___sdt_decl_attr(name, 5)\t\t\t\t\t\\\n+\tstatic void (*const __sdt_type_##name)(t0, t1, t2, t3, t4);\t\\\n+\t___sdt_chk_attr t0 __sdt_chk_##name##_0;\t\t\t\\\n+\t___sdt_chk_attr t1 __sdt_chk_##name##_1;\t\t\t\\\n+\t___sdt_chk_attr t2 __sdt_chk_##name##_2;\t\t\t\\\n+\t___sdt_chk_attr t3 __sdt_chk_##name##_3;\t\t\t\\\n+\t___sdt_chk_attr t4 __sdt_chk_##name##_4\n+\n+/* probe macros, 0-5 arguments\n+ *\n+ * Each probe emits a NOP (goto +0) in the program stream plus a\n+ * .bpf_sdt_notes entry.  The entry begins with the ___sdt_jt_\u003cname\u003e\n+ * label so that libbpf can locate each entry boundary at load time.\n+ * One BPF_SDT_PROBE\u003cN\u003e invocation per probe name is required; invoking\n+ * the same name twice produces a duplicate symbol.\n+ */\n+#define BPF_SDT_PROBE(name)\t\t\t\t\t\t\\\n+({\t\t\t\t\t\t\t\t\t\\\n+\tasm volatile(\t\t\t\t\t\t\t\\\n+\t\t\"0:\\n\"\t\t\t\t\t\t\t\\\n+\t\t\"goto +0\\n\"\t\t\t\t\t\t\\\n+\t\t\".pushsection .bpf_sdt_notes,\\\"\\\",@progbits\\n\"\t\t\\\n+\t\t\"___sdt_jt_\" #name \":\\n\"\t\t\t\t\\\n+\t\t\".quad 0b\\n\"\t\t\t\t\t\t\\\n+\t\t\".popsection\\n\"\t\t\t\t\t\t\\\n+\t);\t\t\t\t\t\t\t\t\\\n+})\n+\n+#define BPF_SDT_PROBE1(name, a)\t\t\t\t\t\t\\\n+({\t\t\t\t\t\t\t\t\t\\\n+\t_Static_assert(__builtin_types_compatible_p(typeof(a),\t\t\\\n+\t\t\ttypeof(__sdt_chk_##name##_0)),\t\t\t\\\n+\t\t\t\"BPF_SDT_PROBE1: arg0 type mismatch\");\t\t\\\n+\t_Static_assert(sizeof(typeof(a)) \u003c= 8,\t\t\t\t\\\n+\t\t\t\"BPF_SDT_PROBE1: arg0 too large, use pointer\");\t\\\n+\ttypeof(a) __a0 = (a);\t\t\t\t\t\t\\\n+\tasm volatile(\t\t\t\t\t\t\t\\\n+\t\t\"0:\\n\"\t\t\t\t\t\t\t\\\n+\t\t\"goto +0\\n\"\t\t\t\t\t\t\\\n+\t\t\".pushsection .bpf_sdt_notes,\\\"\\\",@progbits\\n\"\t\t\\\n+\t\t\"___sdt_jt_\" #name \":\\n\"\t\t\t\t\\\n+\t\t\".quad 0b\\n\"\t\t\t\t\t\t\\\n+\t\t\"r1 = %0\\n\"\t\t\t\t\t\t\\\n+\t\t\".popsection\\n\"\t\t\t\t\t\t\\\n+\t\t:: \"r\"(__a0)\t\t\t\t\t\t\\\n+\t);\t\t\t\t\t\t\t\t\\\n+})\n+\n+#define BPF_SDT_PROBE2(name, a, b)\t\t\t\t\t\\\n+({\t\t\t\t\t\t\t\t\t\\\n+\t_Static_assert(__builtin_types_compatible_p(typeof(a),\t\t\\\n+\t\t\ttypeof(__sdt_chk_##name##_0)),\t\t\t\\\n+\t\t\t\"BPF_SDT_PROBE2: arg0 type mismatch\");\t\t\\\n+\t_Static_assert(sizeof(typeof(a)) \u003c= 8,\t\t\t\t\\\n+\t\t\t\"BPF_SDT_PROBE2: arg0 too large, use pointer\");\t\\\n+\t_Static_assert(__builtin_types_compatible_p(typeof(b),\t\t\\\n+\t\t\ttypeof(__sdt_chk_##name##_1)),\t\t\t\\\n+\t\t\t\"BPF_SDT_PROBE2: arg1 type mismatch\");\t\t\\\n+\t_Static_assert(sizeof(typeof(b)) \u003c= 8,\t\t\t\t\\\n+\t\t\t\"BPF_SDT_PROBE2: arg1 too large, use pointer\");\t\\\n+\ttypeof(a) __a0 = (a);\t\t\t\t\t\t\\\n+\ttypeof(b) __a1 = (b);\t\t\t\t\t\t\\\n+\tasm volatile(\t\t\t\t\t\t\t\\\n+\t\t\"0:\\n\"\t\t\t\t\t\t\t\\\n+\t\t\"goto +0\\n\"\t\t\t\t\t\t\\\n+\t\t\".pushsection .bpf_sdt_notes,\\\"\\\",@progbits\\n\"\t\t\\\n+\t\t\"___sdt_jt_\" #name \":\\n\"\t\t\t\t\\\n+\t\t\".quad 0b\\n\"\t\t\t\t\t\t\\\n+\t\t\"r1 = %0\\n\"\t\t\t\t\t\t\\\n+\t\t\"r2 = %1\\n\"\t\t\t\t\t\t\\\n+\t\t\".popsection\\n\"\t\t\t\t\t\t\\\n+\t\t:: \"r\"(__a0), \"r\"(__a1)\t\t\t\t\t\\\n+\t);\t\t\t\t\t\t\t\t\\\n+})\n+\n+#define BPF_SDT_PROBE3(name, a, b, c)\t\t\t\t\t\\\n+({\t\t\t\t\t\t\t\t\t\\\n+\t_Static_assert(__builtin_types_compatible_p(typeof(a),\t\t\\\n+\t\t\ttypeof(__sdt_chk_##name##_0)),\t\t\t\\\n+\t\t\t\"BPF_SDT_PROBE3: arg0 type mismatch\");\t\t\\\n+\t_Static_assert(sizeof(typeof(a)) \u003c= 8,\t\t\t\t\\\n+\t\t\t\"BPF_SDT_PROBE3: arg0 too large, use pointer\");\t\\\n+\t_Static_assert(__builtin_types_compatible_p(typeof(b),\t\t\\\n+\t\t\ttypeof(__sdt_chk_##name##_1)),\t\t\t\\\n+\t\t\t\"BPF_SDT_PROBE3: arg1 type mismatch\");\t\t\\\n+\t_Static_assert(sizeof(typeof(b)) \u003c= 8,\t\t\t\t\\\n+\t\t\t\"BPF_SDT_PROBE3: arg1 too large, use pointer\");\t\\\n+\t_Static_assert(__builtin_types_compatible_p(typeof(c),\t\t\\\n+\t\t\ttypeof(__sdt_chk_##name##_2)),\t\t\t\\\n+\t\t\t\"BPF_SDT_PROBE3: arg2 type mismatch\");\t\t\\\n+\t_Static_assert(sizeof(typeof(c)) \u003c= 8,\t\t\t\t\\\n+\t\t\t\"BPF_SDT_PROBE3: arg2 too large, use pointer\");\t\\\n+\ttypeof(a) __a0 = (a);\t\t\t\t\t\t\\\n+\ttypeof(b) __a1 = (b);\t\t\t\t\t\t\\\n+\ttypeof(c) __a2 = (c);\t\t\t\t\t\t\\\n+\tasm volatile(\t\t\t\t\t\t\t\\\n+\t\t\"0:\\n\"\t\t\t\t\t\t\t\\\n+\t\t\"goto +0\\n\"\t\t\t\t\t\t\\\n+\t\t\".pushsection .bpf_sdt_notes,\\\"\\\",@progbits\\n\"\t\t\\\n+\t\t\"___sdt_jt_\" #name \":\\n\"\t\t\t\t\\\n+\t\t\".quad 0b\\n\"\t\t\t\t\t\t\\\n+\t\t\"r1 = %0\\n\"\t\t\t\t\t\t\\\n+\t\t\"r2 = %1\\n\"\t\t\t\t\t\t\\\n+\t\t\"r3 = %2\\n\"\t\t\t\t\t\t\\\n+\t\t\".popsection\\n\"\t\t\t\t\t\t\\\n+\t\t:: \"r\"(__a0), \"r\"(__a1), \"r\"(__a2)\t\t\t\\\n+\t);\t\t\t\t\t\t\t\t\\\n+})\n+\n+#define BPF_SDT_PROBE4(name, a, b, c, d)\t\t\t\t\\\n+({\t\t\t\t\t\t\t\t\t\\\n+\t_Static_assert(__builtin_types_compatible_p(typeof(a),\t\t\\\n+\t\t\ttypeof(__sdt_chk_##name##_0)),\t\t\t\\\n+\t\t\t\"BPF_SDT_PROBE4: arg0 type mismatch\");\t\t\\\n+\t_Static_assert(sizeof(typeof(a)) \u003c= 8,\t\t\t\t\\\n+\t\t\t\"BPF_SDT_PROBE4: arg0 too large, use pointer\");\t\\\n+\t_Static_assert(__builtin_types_compatible_p(typeof(b),\t\t\\\n+\t\t\ttypeof(__sdt_chk_##name##_1)),\t\t\t\\\n+\t\t\t\"BPF_SDT_PROBE4: arg1 type mismatch\");\t\t\\\n+\t_Static_assert(sizeof(typeof(b)) \u003c= 8,\t\t\t\t\\\n+\t\t\t\"BPF_SDT_PROBE4: arg1 too large, use pointer\");\t\\\n+\t_Static_assert(__builtin_types_compatible_p(typeof(c),\t\t\\\n+\t\t\ttypeof(__sdt_chk_##name##_2)),\t\t\t\\\n+\t\t\t\"BPF_SDT_PROBE4: arg2 type mismatch\");\t\t\\\n+\t_Static_assert(sizeof(typeof(c)) \u003c= 8,\t\t\t\t\\\n+\t\t\t\"BPF_SDT_PROBE4: arg2 too large, use pointer\");\t\\\n+\t_Static_assert(__builtin_types_compatible_p(typeof(d),\t\t\\\n+\t\t\ttypeof(__sdt_chk_##name##_3)),\t\t\t\\\n+\t\t\t\"BPF_SDT_PROBE4: arg3 type mismatch\");\t\t\\\n+\t_Static_assert(sizeof(typeof(d)) \u003c= 8,\t\t\t\t\\\n+\t\t\t\"BPF_SDT_PROBE4: arg3 too large, use pointer\");\t\\\n+\ttypeof(a) __a0 = (a);\t\t\t\t\t\t\\\n+\ttypeof(b) __a1 = (b);\t\t\t\t\t\t\\\n+\ttypeof(c) __a2 = (c);\t\t\t\t\t\t\\\n+\ttypeof(d) __a3 = (d);\t\t\t\t\t\t\\\n+\tasm volatile(\t\t\t\t\t\t\t\\\n+\t\t\"0:\\n\"\t\t\t\t\t\t\t\\\n+\t\t\"goto +0\\n\"\t\t\t\t\t\t\\\n+\t\t\".pushsection .bpf_sdt_notes,\\\"\\\",@progbits\\n\"\t\t\\\n+\t\t\"___sdt_jt_\" #name \":\\n\"\t\t\t\t\\\n+\t\t\".quad 0b\\n\"\t\t\t\t\t\t\\\n+\t\t\"r1 = %0\\n\"\t\t\t\t\t\t\\\n+\t\t\"r2 = %1\\n\"\t\t\t\t\t\t\\\n+\t\t\"r3 = %2\\n\"\t\t\t\t\t\t\\\n+\t\t\"r4 = %3\\n\"\t\t\t\t\t\t\\\n+\t\t\".popsection\\n\"\t\t\t\t\t\t\\\n+\t\t:: \"r\"(__a0), \"r\"(__a1), \"r\"(__a2), \"r\"(__a3)\t\t\\\n+\t);\t\t\t\t\t\t\t\t\\\n+})\n+\n+#define BPF_SDT_PROBE5(name, a, b, c, d, e)\t\t\t\t\\\n+({\t\t\t\t\t\t\t\t\t\\\n+\t_Static_assert(__builtin_types_compatible_p(typeof(a),\t\t\\\n+\t\t\ttypeof(__sdt_chk_##name##_0)),\t\t\t\\\n+\t\t\t\"BPF_SDT_PROBE5: arg0 type mismatch\");\t\t\\\n+\t_Static_assert(sizeof(typeof(a)) \u003c= 8,\t\t\t\t\\\n+\t\t\t\"BPF_SDT_PROBE5: arg0 too large, use pointer\");\t\\\n+\t_Static_assert(__builtin_types_compatible_p(typeof(b),\t\t\\\n+\t\t\ttypeof(__sdt_chk_##name##_1)),\t\t\t\\\n+\t\t\t\"BPF_SDT_PROBE5: arg1 type mismatch\");\t\t\\\n+\t_Static_assert(sizeof(typeof(b)) \u003c= 8,\t\t\t\t\\\n+\t\t\t\"BPF_SDT_PROBE5: arg1 too large, use pointer\");\t\\\n+\t_Static_assert(__builtin_types_compatible_p(typeof(c),\t\t\\\n+\t\t\ttypeof(__sdt_chk_##name##_2)),\t\t\t\\\n+\t\t\t\"BPF_SDT_PROBE5: arg2 type mismatch\");\t\t\\\n+\t_Static_assert(sizeof(typeof(c)) \u003c= 8,\t\t\t\t\\\n+\t\t\t\"BPF_SDT_PROBE5: arg2 too large, use pointer\");\t\\\n+\t_Static_assert(__builtin_types_compatible_p(typeof(d),\t\t\\\n+\t\t\ttypeof(__sdt_chk_##name##_3)),\t\t\t\\\n+\t\t\t\"BPF_SDT_PROBE5: arg3 type mismatch\");\t\t\\\n+\t_Static_assert(sizeof(typeof(d)) \u003c= 8,\t\t\t\t\\\n+\t\t\t\"BPF_SDT_PROBE5: arg3 too large, use pointer\");\t\\\n+\t_Static_assert(__builtin_types_compatible_p(typeof(e),\t\t\\\n+\t\t\ttypeof(__sdt_chk_##name##_4)),\t\t\t\\\n+\t\t\t\"BPF_SDT_PROBE5: arg4 type mismatch\");\t\t\\\n+\t_Static_assert(sizeof(typeof(e)) \u003c= 8,\t\t\t\t\\\n+\t\t\t\"BPF_SDT_PROBE5: arg4 too large, use pointer\");\t\\\n+\ttypeof(a) __a0 = (a);\t\t\t\t\t\t\\\n+\ttypeof(b) __a1 = (b);\t\t\t\t\t\t\\\n+\ttypeof(c) __a2 = (c);\t\t\t\t\t\t\\\n+\ttypeof(d) __a3 = (d);\t\t\t\t\t\t\\\n+\ttypeof(e) __a4 = (e);\t\t\t\t\t\t\\\n+\tasm volatile(\t\t\t\t\t\t\t\\\n+\t\t\"0:\\n\"\t\t\t\t\t\t\t\\\n+\t\t\"goto +0\\n\"\t\t\t\t\t\t\\\n+\t\t\".pushsection .bpf_sdt_notes,\\\"\\\",@progbits\\n\"\t\t\\\n+\t\t\"___sdt_jt_\" #name \":\\n\"\t\t\t\t\\\n+\t\t\".quad 0b\\n\"\t\t\t\t\t\t\\\n+\t\t\"r1 = %0\\n\"\t\t\t\t\t\t\\\n+\t\t\"r2 = %1\\n\"\t\t\t\t\t\t\\\n+\t\t\"r3 = %2\\n\"\t\t\t\t\t\t\\\n+\t\t\"r4 = %3\\n\"\t\t\t\t\t\t\\\n+\t\t\"r5 = %4\\n\"\t\t\t\t\t\t\\\n+\t\t\".popsection\\n\"\t\t\t\t\t\t\\\n+\t\t:: \"r\"(__a0), \"r\"(__a1), \"r\"(__a2), \"r\"(__a3), \"r\"(__a4)\\\n+\t);\t\t\t\t\t\t\t\t\\\n+})\n+\n+#endif /* __BPF_SDT_PROBE_H */\ndiff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c\nindex 7162146280a80e..273991b80f8f79 100644\n--- a/tools/lib/bpf/libbpf.c\n+++ b/tools/lib/bpf/libbpf.c\n@@ -139,6 +139,7 @@ static const char * const attach_type_name[] = {\n \t[BPF_TRACE_FENTRY_MULTI]\t= \"trace_fentry_multi\",\n \t[BPF_TRACE_FEXIT_MULTI]\t\t= \"trace_fexit_multi\",\n \t[BPF_TRACE_FSESSION_MULTI]\t= \"trace_fsession_multi\",\n+\t[BPF_TRACE_SDT]\t\t\t= \"trace_sdt\",\n };\n \n static const char * const link_type_name[] = {\n@@ -158,6 +159,7 @@ static const char * const link_type_name[] = {\n \t[BPF_LINK_TYPE_NETKIT]\t\t\t= \"netkit\",\n \t[BPF_LINK_TYPE_SOCKMAP]\t\t\t= \"sockmap\",\n \t[BPF_LINK_TYPE_TRACING_MULTI]\t\t= \"tracing_multi\",\n+\t[BPF_LINK_TYPE_SDT]\t\t\t= \"sdt\",\n };\n \n static const char * const map_type_name[] = {\n@@ -494,6 +496,7 @@ struct bpf_program {\n \tstruct bpf_object *obj;\n \n \tint fd;\n+\tint sdt_map_fd;\n \tbool autoload;\n \tbool autoattach;\n \tbool sym_global;\n@@ -506,6 +509,7 @@ struct bpf_program {\n \t__u32 attach_btf_obj_fd;\n \t__u32 attach_btf_id;\n \t__u32 attach_prog_fd;\n+\tchar *sdt_probe_name;\n \n \tvoid *func_info;\n \t__u32 func_info_rec_size;\n@@ -519,6 +523,9 @@ struct bpf_program {\n \n \tstruct bpf_light_subprog *subprogs;\n \t__u32 subprog_cnt;\n+\n+\t/* index of the main program that absorbed this subprog */\n+\tint absorbed_by;\n };\n \n struct bpf_struct_ops {\n@@ -693,6 +700,7 @@ struct elf_state {\n \tbool has_st_ops;\n \tint arena_data_shndx;\n \tint jumptables_data_shndx;\n+\tint sdt_notes_shndx;\n };\n \n struct usdt_manager;\n@@ -703,6 +711,16 @@ enum bpf_object_state {\n \tOBJ_LOADED,\n };\n \n+struct sdt_entry {\n+\tchar *name;      /* probe name */\n+\t__u16 prog_idx;  /* resolved in bpf_object__resolve_sdt_progs */\n+\t__u16 sec_idx;   /* ELF section index of the probe site (from reloc) */\n+\t__u64 insn_idx;  /* insn index of probe site within its ELF section */\n+\t__u8 nargs;      /* how many arguments */\n+\t__u8 arg_reg[5]; /* which register the argument locates in */\n+\t__u32 btf_id;    /* BTF FUNC_PROTO type ID from decl tag */\n+};\n+\n struct bpf_object {\n \tchar name[BPF_OBJ_NAME_LEN];\n \tchar license[64];\n@@ -768,6 +786,13 @@ struct bpf_object {\n \tvoid *jumptables_data;\n \tsize_t jumptables_data_sz;\n \n+\tvoid *sdt_notes_data;\n+\tsize_t sdt_notes_data_sz;\n+\n+\tstruct sdt_entry *sdt_entries;\n+\tsize_t sdt_entry_cnt;\n+\tsize_t sdt_entry_cap;\n+\n \tstruct {\n \t\tstruct bpf_program *prog;\n \t\tunsigned int sym_off;\n@@ -814,6 +839,7 @@ static void bpf_program__exit(struct bpf_program *prog)\n \tzfree(\u0026prog-\u003esec_name);\n \tzfree(\u0026prog-\u003einsns);\n \tzfree(\u0026prog-\u003ereloc_desc);\n+\tzfree(\u0026prog-\u003esdt_probe_name);\n \n \tprog-\u003enr_reloc = 0;\n \tprog-\u003einsns_cnt = 0;\n@@ -862,7 +888,9 @@ bpf_object__init_prog(struct bpf_object *obj, struct bpf_program *prog,\n \n \tprog-\u003etype = BPF_PROG_TYPE_UNSPEC;\n \tprog-\u003efd = -1;\n+\tprog-\u003esdt_map_fd = -1;\n \tprog-\u003eexception_cb_idx = -1;\n+\tprog-\u003eabsorbed_by = -1;\n \n \t/* libbpf's convention for SEC(\"?abc...\") is that it's just like\n \t * SEC(\"abc...\") but the corresponding bpf_program starts out with\n@@ -3892,6 +3920,260 @@ static int cmp_progs(const void *_a, const void *_b)\n \treturn a-\u003esec_insn_off \u003c b-\u003esec_insn_off ? -1 : 1;\n }\n \n+static struct bpf_program *find_prog_by_sec_insn(const struct bpf_object *obj,\n+\t\t\t\t\t\t size_t sec_idx, size_t insn_idx);\n+\n+struct sdt_sym {\n+\tconst char *name;\n+\tsize_t off; /* offset in the sdt entry section */\n+};\n+\n+static int sdt_sym_cmp(const void *a, const void *b)\n+{\n+\tconst struct sdt_sym *sym1 = a, *sym2 = b;\n+\n+\tif (sym1-\u003eoff == sym2-\u003eoff)\n+\t\treturn 0;\n+\n+\treturn sym1-\u003eoff \u003c sym2-\u003eoff ? -1 : 1;\n+}\n+\n+static Elf_Data *sdt_find_relo(struct bpf_object *obj)\n+{\n+\tGElf_Shdr shdr;\n+\tElf_Scn *scn = NULL;\n+\n+\twhile ((scn = elf_nextscn(obj-\u003eefile.elf, scn)) != NULL) {\n+\t\tif (!gelf_getshdr(scn, \u0026shdr) || shdr.sh_type != SHT_REL)\n+\t\t\tcontinue;\n+\n+\t\tif (shdr.sh_info == obj-\u003eefile.sdt_notes_shndx)\n+\t\t\treturn elf_getdata(scn, NULL);\n+\t}\n+\n+\treturn NULL;\n+}\n+\n+static int sdt_collect_syms(struct bpf_object *obj, struct sdt_sym **sdt_syms)\n+{\n+\tint i, err, cnt = 0;\n+\tsize_t nr_syms;\n+\tsize_t sym_cap = 0;\n+\tconst char *name;\n+\tElf64_Sym *sym, *syms;\n+\tstruct sdt_sym *ssyms = NULL;\n+\n+\tsyms = obj-\u003eefile.symbols-\u003ed_buf;\n+\tnr_syms = obj-\u003eefile.symbols-\u003ed_size / sizeof(Elf64_Sym);\n+\n+\tfor (i = 0; i \u003c nr_syms; i++) {\n+\t\tsym = \u0026syms[i];\n+\t\tif (sym-\u003est_shndx != obj-\u003eefile.sdt_notes_shndx)\n+\t\t\tcontinue;\n+\n+\t\tif (ELF64_ST_TYPE(sym-\u003est_info) != STT_NOTYPE)\n+\t\t\tcontinue;\n+\n+\t\tname = elf_sym_str(obj, sym-\u003est_name);\n+\t\tif (!name || strncmp(name, \"___sdt_jt_\", 10))\n+\t\t\tcontinue;\n+\n+\t\terr = libbpf_ensure_mem((void **)\u0026ssyms, \u0026sym_cap, sizeof(*ssyms), cnt + 1);\n+\t\tif (err) {\n+\t\t\tfree(ssyms);\n+\t\t\treturn -ENOMEM;\n+\t\t}\n+\n+\t\tssyms[cnt].name = name;\n+\t\tssyms[cnt].off = sym-\u003est_value;\n+\t\tcnt++;\n+\t}\n+\n+\tif (cnt)\n+\t\tqsort(ssyms, cnt, sizeof(*ssyms), sdt_sym_cmp);\n+\n+\t*sdt_syms = ssyms;\n+\n+\treturn cnt;\n+}\n+\n+/*\n+ * Layout of SDT entry:\n+ *\n+ *   off + 0:  64 bits of the NOP instruction offset\n+ *   off + 8:  r1 = %[arg0_reg]  //  bpf move instruction for arg0\n+ *   off + 16: r2 = %[arg1_reg]  //  bpf move instruction for arg1\n+ *       ...\n+ *   off + 8 * N: rN = %[argN_reg]  //  bpf move instruction for argN\n+ *\n+ *   next_off + 0: // start of the next entry\n+ *\n+ *   so argument number N = (next_entry_off - off - 8) / sizeof(bpf_insn).\n+ */\n+static int sdt_calc_nargs(struct sdt_sym *syms, int sym_idx, size_t sym_cnt, size_t last_off)\n+{\n+\tsize_t off = syms[sym_idx].off;\n+\tsize_t next_off = (sym_idx + 1 \u003c sym_cnt) ? syms[sym_idx + 1].off : last_off;\n+\n+\tif (next_off \u003e last_off)\n+\t\treturn -EINVAL;\n+\n+\tif (off + 8 \u003e next_off)\n+\t\treturn -EINVAL;\n+\n+\tif ((next_off - off - 8) % sizeof(struct bpf_insn))\n+\t\treturn -EINVAL;\n+\n+\tif ((next_off - off - 8) / sizeof(struct bpf_insn) \u003e 5)\n+\t\treturn -EINVAL;\n+\n+\treturn (next_off - off - 8) / sizeof(struct bpf_insn);\n+}\n+\n+/*\n+ * Look up the ELF section index of the code section containing the probe\n+ * site, via the R_BPF_64_ABS64 relocation on this entry's .quad.  The\n+ * target symbol's st_shndx is the code section; this is stored in the\n+ * sdt_entry and used later (after subprogram linking) to resolve prog_idx.\n+ *\n+ * Returns the section index on success, -1 on failure.\n+ */\n+static int sdt_find_sec_idx(struct bpf_object *obj, Elf_Data *sdt_relo,\n+\t\t\t    size_t sdt_entry_off)\n+{\n+\tint i;\n+\tElf64_Sym *tgt;\n+\tElf64_Rel *rel = sdt_relo-\u003ed_buf;\n+\tElf64_Sym *elf_syms = obj-\u003eefile.symbols-\u003ed_buf;\n+\n+\tfor (i = 0; i \u003c sdt_relo-\u003ed_size / sizeof(Elf64_Rel); i++, rel++) {\n+\t\tif (rel-\u003er_offset != sdt_entry_off)\n+\t\t\tcontinue;\n+\t\tif (ELF64_R_TYPE(rel-\u003er_info) != R_BPF_64_ABS64)\n+\t\t\tcontinue;\n+\n+\t\ttgt = \u0026elf_syms[ELF64_R_SYM(rel-\u003er_info)];\n+\t\treturn tgt-\u003est_shndx;\n+\t}\n+\n+\treturn -1;\n+}\n+\n+static int sdt_parse_arg(const void *data, int nargs, struct sdt_entry *e)\n+{\n+\tint i;\n+\tconst struct bpf_insn *insn;\n+\n+\tinsn = (const struct bpf_insn *)data;\n+\tfor (i = 0; i \u003c nargs; i++, insn++) {\n+\t\tif (insn-\u003ecode != (BPF_ALU64 | BPF_MOV | BPF_X))\n+\t\t\treturn -EINVAL;\n+\t\te-\u003earg_reg[i] = insn-\u003esrc_reg;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+\n+/*\n+ * Parse .bpf_sdt_notes into per-probe SDT entries.\n+ *\n+ * Each entry starts at a ___sdt_jt_\u003cname\u003e label and contains .quad nop_off\n+ * (8 bytes, with R_BPF_64_ABS64 reloc) followed by one 8-byte BPF_MOV\n+ * instruction per argument whose src_reg field encodes the register.\n+ *\n+ * nargs is derived from the gap between consecutive labels: the assembler\n+ * lays entries out sequentially, so nargs = (next_off - off - 8) / 8.\n+ */\n+static int bpf_object__collect_sdt_notes(struct bpf_object *obj)\n+{\n+\tint i, sym_cnt, err = 0;\n+\tstruct sdt_sym *sdt_syms = NULL;\n+\tElf_Data *sdt_relo = NULL;\n+\tconst void *data = obj-\u003esdt_notes_data;\n+\n+\tif (!obj-\u003esdt_notes_data || !obj-\u003esdt_notes_data_sz)\n+\t\treturn 0;\n+\n+\tsdt_relo = sdt_find_relo(obj);\n+\tif (!sdt_relo)\n+\t\treturn -EINVAL;\n+\n+\tsym_cnt = sdt_collect_syms(obj, \u0026sdt_syms);\n+\tif (sym_cnt \u003c 0)\n+\t\treturn sym_cnt;\n+\n+\tfor (i = 0; i \u003c sym_cnt; i++) {\n+\t\t__u64 nop_idx;\n+\t\tint nargs, sec_idx;\n+\t\tstruct sdt_entry *e;\n+\t\tsize_t off = sdt_syms[i].off;\n+\n+\t\tnargs = sdt_calc_nargs(sdt_syms, i, sym_cnt, obj-\u003esdt_notes_data_sz);\n+\t\tif (nargs \u003c 0) {\n+\t\t\terr = -EINVAL;\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tmemcpy(\u0026nop_idx, data + off, sizeof(__u64));\n+\t\tnop_idx = nop_idx / sizeof(struct bpf_insn);\n+\n+\t\t/*\n+\t\t * Record the ELF section index of the code section containing\n+\t\t * the probe site.  prog_idx is resolved later in\n+\t\t * bpf_object__resolve_sdt_progs(), after static subprograms\n+\t\t * in .text have been absorbed into their calling main program;\n+\t\t * resolving here would return the subprogram's own entry, not\n+\t\t * the main program that ends up owning the probe site.\n+\t\t */\n+\t\tsec_idx = sdt_find_sec_idx(obj, sdt_relo, off);\n+\t\tif (sec_idx \u003c 0) {\n+\t\t\terr = -EINVAL;\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\t/* Read arg_reg[] from cold instructions */\n+\t\terr = libbpf_ensure_mem((void **)\u0026obj-\u003esdt_entries, \u0026obj-\u003esdt_entry_cap,\n+\t\t\t\t\tsizeof(*obj-\u003esdt_entries), i + 1);\n+\t\tif (err) {\n+\t\t\terr = -ENOMEM;\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\te = \u0026obj-\u003esdt_entries[i];\n+\t\terr = sdt_parse_arg(data + off + 8, nargs, e);\n+\t\tif (err \u003c 0)\n+\t\t\tgoto out;\n+\n+\t\te-\u003ename = strdup(sdt_syms[i].name + 10); /* skip \"___sdt_jt_\" */\n+\t\tif (!e-\u003ename) {\n+\t\t\terr = -ENOMEM;\n+\t\t\tgoto out;\n+\t\t}\n+\t\tif (strlen(e-\u003ename) \u003e= BPF_SDT_MAX_NAME_LEN) {\n+\t\t\tpr_warn(\"sdt: probe name '%s' too long (max %d chars)\\n\",\n+\t\t\t\te-\u003ename, BPF_SDT_MAX_NAME_LEN - 1);\n+\t\t\terr = -EINVAL;\n+\t\t\tgoto out;\n+\t\t}\n+\t\te-\u003esec_idx = sec_idx;\n+\t\te-\u003einsn_idx = nop_idx;\n+\t\te-\u003enargs = nargs;\n+\t\tobj-\u003esdt_entry_cnt++;\n+\t}\n+\n+out:\n+\tif (err) {\n+\t\tfor (i = 0; i \u003c obj-\u003esdt_entry_cnt; i++)\n+\t\t\tfree(obj-\u003esdt_entries[i].name);\n+\t\tzfree(\u0026obj-\u003esdt_entries);\n+\t\tobj-\u003esdt_entry_cnt = obj-\u003esdt_entry_cap = 0;\n+\t}\n+\n+\tfree(sdt_syms);\n+\treturn err;\n+}\n+\n static int bpf_object__elf_collect(struct bpf_object *obj)\n {\n \tstruct elf_sec_desc *sec_desc;\n@@ -4034,6 +4316,13 @@ static int bpf_object__elf_collect(struct bpf_object *obj)\n \t\t\t\tmemcpy(obj-\u003ejumptables_data, data-\u003ed_buf, data-\u003ed_size);\n \t\t\t\tobj-\u003ejumptables_data_sz = data-\u003ed_size;\n \t\t\t\tobj-\u003eefile.jumptables_data_shndx = idx;\n+\t\t\t} else if (strcmp(name, SDT_NOTES_SEC) == 0) {\n+\t\t\t\tobj-\u003esdt_notes_data = malloc(data-\u003ed_size);\n+\t\t\t\tif (!obj-\u003esdt_notes_data)\n+\t\t\t\t\treturn -ENOMEM;\n+\t\t\t\tmemcpy(obj-\u003esdt_notes_data, data-\u003ed_buf, data-\u003ed_size);\n+\t\t\t\tobj-\u003esdt_notes_data_sz = data-\u003ed_size;\n+\t\t\t\tobj-\u003eefile.sdt_notes_shndx = idx;\n \t\t\t} else {\n \t\t\t\tpr_info(\"elf: skipping unrecognized data section(%d) %s\\n\",\n \t\t\t\t\tidx, name);\n@@ -4087,6 +4376,13 @@ static int bpf_object__elf_collect(struct bpf_object *obj)\n \tif (obj-\u003enr_programs)\n \t\tqsort(obj-\u003eprograms, obj-\u003enr_programs, sizeof(*obj-\u003eprograms), cmp_progs);\n \n+\terr = bpf_object__collect_sdt_notes(obj);\n+\tif (err) {\n+\t\tzfree(\u0026obj-\u003esdt_notes_data);\n+\t\tobj-\u003esdt_notes_data_sz = 0;\n+\t\treturn err;\n+\t}\n+\n \treturn bpf_object__init_btf(obj, btf_data, btf_ext_data);\n }\n \n@@ -6407,6 +6703,186 @@ static int create_jt_map(struct bpf_object *obj, struct bpf_program *prog, struc\n \treturn err;\n }\n \n+static void sdt_collect_btf_ids(struct bpf_object *obj)\n+{\n+\tint n = btf__type_cnt(obj-\u003ebtf);\n+\tconst struct btf_type *t;\n+\tint i;\n+\n+\tfor (i = 1; i \u003c n; i++) {\n+\t\tconst char *tag, *colon;\n+\t\tsize_t name_len;\n+\t\t__u32 func_proto_id;\n+\t\tint j;\n+\n+\t\tt = btf_type_by_id(obj-\u003ebtf, i);\n+\t\tif (!btf_is_decl_tag(t) || btf_decl_tag(t)-\u003ecomponent_idx != -1)\n+\t\t\tcontinue;\n+\n+\t\ttag = btf__str_by_offset(obj-\u003ebtf, t-\u003ename_off);\n+\t\tif (strncmp(tag, \"bpf_sdt:\", 8) != 0)\n+\t\t\tcontinue;\n+\n+\t\t/* tag format: \"bpf_sdt:\u003cname\u003e:\u003cnargs\u003e\"; \u003cname\u003e has no colons */\n+\t\tcolon = strchr(tag + 8, ':');\n+\t\tif (!colon)\n+\t\t\tcontinue;\n+\t\tname_len = colon - tag - 8;\n+\n+\t\t/* type chain: DECL_TAG -\u003e VAR -\u003e (CONST) -\u003e PTR -\u003e FUNC_PROTO */\n+\t\tt = btf_type_by_id(obj-\u003ebtf, t-\u003etype);\n+\t\tif (!btf_is_var(t))\n+\t\t\tcontinue;\n+\t\tt = btf_type_by_id(obj-\u003ebtf, t-\u003etype);\n+\t\tif (btf_kind(t) == BTF_KIND_CONST)\n+\t\t\tt = btf_type_by_id(obj-\u003ebtf, t-\u003etype);\n+\t\tif (!btf_is_ptr(t))\n+\t\t\tcontinue;\n+\t\tfunc_proto_id = t-\u003etype;\n+\t\tt = btf_type_by_id(obj-\u003ebtf, func_proto_id);\n+\t\tif (!btf_is_func_proto(t))\n+\t\t\tcontinue;\n+\n+\t\t/* match by name (tag format: \"bpf_sdt:\u003cname\u003e:\u003cnargs\u003e\") */\n+\t\tfor (j = 0; j \u003c obj-\u003esdt_entry_cnt; j++) {\n+\t\t\tstruct sdt_entry *e = \u0026obj-\u003esdt_entries[j];\n+\n+\t\t\tif (!e-\u003ebtf_id \u0026\u0026 strlen(e-\u003ename) == name_len \u0026\u0026\n+\t\t\t    strncmp(tag + 8, e-\u003ename, name_len) == 0)\n+\t\t\t\te-\u003ebtf_id = func_proto_id;\n+\t\t}\n+\t}\n+}\n+\n+static int bpf_object__resolve_sdt_progs(struct bpf_object *obj)\n+{\n+\tint i, j;\n+\tstruct bpf_light_subprog *sp;\n+\n+\tfor (i = 0; i \u003c obj-\u003esdt_entry_cnt; i++) {\n+\t\tstruct sdt_entry *e = \u0026obj-\u003esdt_entries[i];\n+\t\tstruct bpf_program *prog, *subprog;\n+\n+\t\tprog = find_prog_by_sec_insn(obj, e-\u003esec_idx, e-\u003einsn_idx);\n+\t\tif (!prog) {\n+\t\t\tpr_warn(\"sdt: probe '%s' at sec %u insn %llu not found in any program\\n\",\n+\t\t\t\te-\u003ename, (unsigned)e-\u003esec_idx, (unsigned long long)e-\u003einsn_idx);\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\n+\t\t/* resolve subprog probe in the main prog that absorbed the subprog */\n+\t\tif (prog_is_subprog(obj, prog) \u0026\u0026 prog-\u003eabsorbed_by != -1) {\n+\t\t\tsubprog = prog;\n+\t\t\tprog = \u0026obj-\u003eprograms[prog-\u003eabsorbed_by];\n+\t\t\tfor (j = 0; j \u003c prog-\u003esubprog_cnt; j++) {\n+\t\t\t\tsp = \u0026prog-\u003esubprogs[j];\n+\t\t\t\tif (sp-\u003esec_insn_off == subprog-\u003esec_insn_off) {\n+\t\t\t\t\te-\u003einsn_idx = sp-\u003esub_insn_off +\n+\t\t\t\t\t\t       (e-\u003einsn_idx - sp-\u003esec_insn_off);\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tif (j \u003e= prog-\u003esubprog_cnt) {\n+\t\t\t\tpr_warn(\"sdt: subprog probe '%s' not found\\n\", e-\u003ename);\n+\t\t\t\treturn -EINVAL;\n+\t\t\t}\n+\t\t}\n+\n+\t\te-\u003eprog_idx = prog - obj-\u003eprograms;\n+\t}\n+\treturn 0;\n+}\n+\n+static int bpf_object__create_sdt_maps(struct bpf_object *obj)\n+{\n+\tconst __u32 value_size = sizeof(struct bpf_insn_array_value);\n+\tstruct bpf_insn_array_value val = {};\n+\tstruct bpf_program *prog;\n+\tstruct {\n+\t\t__u32 sdt_cnt;\n+\t\t__u32 next_key;\n+\t} *prog_sdt;\n+\tint i, err = 0;\n+\n+\tif (!obj-\u003esdt_entry_cnt)\n+\t\treturn 0;\n+\n+\terr = bpf_object__resolve_sdt_progs(obj);\n+\tif (err)\n+\t\treturn err;\n+\n+\tsdt_collect_btf_ids(obj);\n+\n+\tprog_sdt = calloc(obj-\u003enr_programs, sizeof(*prog_sdt));\n+\tif (!prog_sdt)\n+\t\treturn -ENOMEM;\n+\n+\t/* count entries per program */\n+\tfor (i = 0; i \u003c obj-\u003esdt_entry_cnt; i++)\n+\t\tprog_sdt[obj-\u003esdt_entries[i].prog_idx].sdt_cnt++;\n+\n+\t/* create insn_array maps per program and populate entries */\n+\tfor (i = 0; i \u003c obj-\u003esdt_entry_cnt; i++) {\n+\t\t__u32 key;\n+\t\tstruct sdt_entry *e = \u0026obj-\u003esdt_entries[i];\n+\t\t__u32 sdt_cnt = prog_sdt[e-\u003eprog_idx].sdt_cnt;\n+\n+\t\tif (!sdt_cnt)\n+\t\t\tcontinue;\n+\n+\t\tprog = \u0026obj-\u003eprograms[e-\u003eprog_idx];\n+\n+\t\tif (prog-\u003esdt_map_fd \u003c 0) {\n+\t\t\tint map_fd;\n+\t\t\tLIBBPF_OPTS(bpf_map_create_opts, map_opts);\n+\n+\t\t\tmap_opts.map_flags = BPF_F_INSN_ARRAY_SDT;\n+\t\t\tmap_fd = bpf_map_create(BPF_MAP_TYPE_INSN_ARRAY, \".bpf_sdt_notes\",\n+\t\t\t\t\t\tsizeof(key), value_size, sdt_cnt, \u0026map_opts);\n+\t\t\tif (map_fd \u003c 0) {\n+\t\t\t\terr = map_fd;\n+\t\t\t\tgoto out_free;\n+\t\t\t}\n+\t\t\tprog-\u003esdt_map_fd = map_fd;\n+\t\t}\n+\n+\t\tkey = prog_sdt[e-\u003eprog_idx].next_key++;\n+\n+\t\tmemset(\u0026val, 0, sizeof(val));\n+\t\tval.btf_id = e-\u003ebtf_id;\n+\t\tval.nargs = e-\u003enargs;\n+\t\tval.orig_off = e-\u003einsn_idx - prog-\u003esec_insn_off;\n+\t\tmemcpy(val.arg_reg, e-\u003earg_reg, sizeof(val.arg_reg));\n+\t\tstrncpy(val.name, e-\u003ename, sizeof(val.name) - 1);\n+\n+\t\terr = bpf_map_update_elem(prog-\u003esdt_map_fd, \u0026key, \u0026val, 0);\n+\t\tif (err)\n+\t\t\tgoto out_free;\n+\t}\n+\n+\t/* freeze maps */\n+\tfor (i = 0; i \u003c obj-\u003enr_programs; i++) {\n+\t\tprog = \u0026obj-\u003eprograms[i];\n+\t\tif (prog-\u003esdt_map_fd \u003e= 0) {\n+\t\t\terr = bpf_map_freeze(prog-\u003esdt_map_fd);\n+\t\t\tif (err)\n+\t\t\t\tgoto out_free;\n+\t\t}\n+\t}\n+\n+out_free:\n+\tfree(prog_sdt);\n+\tif (err) {\n+\t\tfor (i = 0; i \u003c obj-\u003enr_programs; i++) {\n+\t\t\tif (obj-\u003eprograms[i].sdt_map_fd \u003e= 0) {\n+\t\t\t\tclose(obj-\u003eprograms[i].sdt_map_fd);\n+\t\t\t\tobj-\u003eprograms[i].sdt_map_fd = -1;\n+\t\t\t}\n+\t\t}\n+\t}\n+\treturn err;\n+}\n+\n /* Relocate data references within program code:\n  *  - map references;\n  *  - global variable references;\n@@ -6856,6 +7332,8 @@ bpf_object__reloc_code(struct bpf_object *obj, struct bpf_program *main_prog,\n \t\t\terr = bpf_object__append_subprog_code(obj, main_prog, subprog);\n \t\t\tif (err)\n \t\t\t\treturn err;\n+\t\t\tif (subprog-\u003eabsorbed_by == -1)\n+\t\t\t\tsubprog-\u003eabsorbed_by = main_prog - obj-\u003eprograms;\n \t\t\terr = bpf_object__reloc_code(obj, main_prog, subprog);\n \t\t\tif (err)\n \t\t\t\treturn err;\n@@ -7975,6 +8453,12 @@ static int bpf_object_load_prog(struct bpf_object *obj, struct bpf_program *prog\n \tload_attr.log_level = log_level;\n \tload_attr.prog_flags = prog-\u003eprog_flags;\n \tload_attr.fd_array = obj-\u003efd_array;\n+\tif (prog-\u003esdt_map_fd \u003e= 0)\n+\t\tload_attr.sdt_map_fd = prog-\u003esdt_map_fd;\n+\tif (prog-\u003eexpected_attach_type == BPF_TRACE_SDT \u0026\u0026 prog-\u003esdt_probe_name) {\n+\t\tload_attr.sdt.target_prog_fd = prog-\u003eattach_prog_fd;\n+\t\tload_attr.sdt.name = prog-\u003esdt_probe_name;\n+\t}\n \n \tload_attr.token_fd = obj-\u003etoken_fd;\n \tif (obj-\u003etoken_fd)\n@@ -9064,6 +9548,7 @@ static int bpf_object_prepare(struct bpf_object *obj, const char *target_btf_pat\n \terr = err ? : bpf_object__relocate(obj, obj-\u003ebtf_custom_path ? : target_btf_path);\n \terr = err ? : bpf_object__sanitize_and_load_btf(obj);\n \terr = err ? : bpf_object__create_maps(obj);\n+\terr = err ? : bpf_object__create_sdt_maps(obj);\n \terr = err ? : bpf_object_prepare_progs(obj);\n \n \tif (err) {\n@@ -9614,8 +10099,11 @@ void bpf_object__close(struct bpf_object *obj)\n \tobj-\u003enr_maps = 0;\n \n \tif (obj-\u003eprograms \u0026\u0026 obj-\u003enr_programs) {\n-\t\tfor (i = 0; i \u003c obj-\u003enr_programs; i++)\n+\t\tfor (i = 0; i \u003c obj-\u003enr_programs; i++) {\n+\t\t\tif (obj-\u003eprograms[i].sdt_map_fd \u003e= 0)\n+\t\t\t\tclose(obj-\u003eprograms[i].sdt_map_fd);\n \t\t\tbpf_program__exit(\u0026obj-\u003eprograms[i]);\n+\t\t}\n \t}\n \tzfree(\u0026obj-\u003eprograms);\n \n@@ -9629,6 +10117,17 @@ void bpf_object__close(struct bpf_object *obj)\n \tzfree(\u0026obj-\u003ejumptables_data);\n \tobj-\u003ejumptables_data_sz = 0;\n \n+\tzfree(\u0026obj-\u003esdt_notes_data);\n+\tobj-\u003esdt_notes_data_sz = 0;\n+\n+\tif (obj-\u003esdt_entries) {\n+\t\tfor (i = 0; i \u003c obj-\u003esdt_entry_cnt; i++)\n+\t\t\tfree(obj-\u003esdt_entries[i].name);\n+\t\tzfree(\u0026obj-\u003esdt_entries);\n+\t\tobj-\u003esdt_entry_cnt = 0;\n+\t\tobj-\u003esdt_entry_cap = 0;\n+\t}\n+\n \tfor (i = 0; i \u003c obj-\u003ejumptable_map_cnt; i++)\n \t\tclose(obj-\u003ejumptable_maps[i].fd);\n \tzfree(\u0026obj-\u003ejumptable_maps);\n@@ -10096,6 +10595,7 @@ static const struct bpf_sec_def section_defs[] = {\n \tSEC_DEF(\"kretsyscall+\",\t\tKPROBE, 0, SEC_NONE, attach_ksyscall),\n \tSEC_DEF(\"usdt+\",\t\tKPROBE,\t0, SEC_USDT, attach_usdt),\n \tSEC_DEF(\"usdt.s+\",\t\tKPROBE,\t0, SEC_USDT | SEC_SLEEPABLE, attach_usdt),\n+\tSEC_DEF(\"bpf_sdt\",\t\tTRACING, BPF_TRACE_SDT, SEC_NONE),\n \tSEC_DEF(\"tc/ingress\",\t\tSCHED_CLS, BPF_TCX_INGRESS, SEC_NONE), /* alias for tcx */\n \tSEC_DEF(\"tc/egress\",\t\tSCHED_CLS, BPF_TCX_EGRESS, SEC_NONE),  /* alias for tcx */\n \tSEC_DEF(\"tcx/ingress\",\t\tSCHED_CLS, BPF_TCX_INGRESS, SEC_NONE),\n@@ -14712,6 +15212,17 @@ int bpf_program__set_attach_target(struct bpf_program *prog,\n \tif (prog-\u003eobj-\u003estate \u003e= OBJ_LOADED)\n \t\treturn libbpf_err(-EINVAL);\n \n+\tif (prog-\u003eexpected_attach_type == BPF_TRACE_SDT) {\n+\t\tif (!attach_func_name)\n+\t\t\treturn libbpf_err(-EINVAL);\n+\t\tfree(prog-\u003esdt_probe_name);\n+\t\tprog-\u003esdt_probe_name = strdup(attach_func_name);\n+\t\tif (!prog-\u003esdt_probe_name)\n+\t\t\treturn libbpf_err(-ENOMEM);\n+\t\tprog-\u003eattach_prog_fd = attach_prog_fd;\n+\t\treturn 0;\n+\t}\n+\n \tif (attach_prog_fd \u0026\u0026 !attach_func_name) {\n \t\t/* Store attach_prog_fd. The BTF ID will be resolved later during\n \t\t * the normal object/program load phase.\ndiff --git a/tools/lib/bpf/libbpf_internal.h b/tools/lib/bpf/libbpf_internal.h\nindex 04cd303fb5a879..ed7a587a61ca4e 100644\n--- a/tools/lib/bpf/libbpf_internal.h\n+++ b/tools/lib/bpf/libbpf_internal.h\n@@ -75,6 +75,7 @@\n #endif\n \n #define JUMPTABLES_SEC \".jumptables\"\n+#define SDT_NOTES_SEC \".bpf_sdt_notes\"\n \n #define BTF_INFO_ENC(kind, kind_flag, vlen) \\\n \t((!!(kind_flag) \u003c\u003c 31) | ((kind) \u003c\u003c 24) | ((vlen) \u0026 BTF_MAX_VLEN))\ndiff --git a/tools/lib/bpf/linker.c b/tools/lib/bpf/linker.c\nindex 78f92c39290af5..3a72276244c725 100644\n--- a/tools/lib/bpf/linker.c\n+++ b/tools/lib/bpf/linker.c\n@@ -2028,6 +2028,8 @@ static int linker_append_elf_sym(struct bpf_linker *linker, struct src_obj *obj,\n \n \t\tif (strcmp(src_sec-\u003esec_name, JUMPTABLES_SEC) == 0)\n \t\t\tgoto add_sym;\n+\t\tif (strcmp(src_sec-\u003esec_name, SDT_NOTES_SEC) == 0)\n+\t\t\tgoto add_sym;\n \t}\n \n \tif (sym_bind == STB_LOCAL)\n@@ -2274,6 +2276,13 @@ static int linker_append_elf_relos(struct bpf_linker *linker, struct src_obj *ob\n \t\t\t\t\t\tinsn-\u003eimm += sec-\u003edst_off / sizeof(struct bpf_insn);\n \t\t\t\t\telse\n \t\t\t\t\t\tinsn-\u003eimm += sec-\u003edst_off;\n+\t\t\t\t} else if (strcmp(src_linked_sec-\u003esec_name, SDT_NOTES_SEC) == 0) {\n+\t\t\t\t\t/* .bpf_sdt_notes contains R_BPF_64_ABS64 relocations\n+\t\t\t\t\t * referencing code section symbols. Adjust the 64-bit\n+\t\t\t\t\t * address by dst_off.\n+\t\t\t\t\t */\n+\t\t\t\t\t__u64 *addr = dst_linked_sec-\u003eraw_data + dst_rel-\u003er_offset;\n+\t\t\t\t\t*addr += sec-\u003edst_off;\n \t\t\t\t} else {\n \t\t\t\t\tpr_warn(\"relocation against STT_SECTION in non-exec section is not supported!\\n\");\n \t\t\t\t\treturn -EINVAL;\ndiff --git a/tools/testing/selftests/bpf/prog_tests/test_bpf_sdt.c b/tools/testing/selftests/bpf/prog_tests/test_bpf_sdt.c\nnew file mode 100644\nindex 00000000000000..b2752e7b2b0925\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/prog_tests/test_bpf_sdt.c\n@@ -0,0 +1,151 @@\n+// SPDX-License-Identifier: GPL-2.0\n+#include \u003ctest_progs.h\u003e\n+#include \u003cbpf/bpf.h\u003e\n+#include \"bpf_sdt_observer.skel.h\"\n+#include \"bpf_sdt_target.skel.h\"\n+\n+static int sdt_attach(struct bpf_program *obs)\n+{\n+\tLIBBPF_OPTS(bpf_link_create_opts, opts);\n+\n+\treturn bpf_link_create(bpf_program__fd(obs), 0, BPF_TRACE_SDT, \u0026opts);\n+}\n+\n+static void read_args(void)\n+{\n+\tLIBBPF_OPTS(bpf_test_run_opts, topts);\n+\tstruct bpf_sdt_target *tgt_skel;\n+\tstruct bpf_sdt_observer *skel;\n+\tchar pkt[64] = {};\n+\tint link, err;\n+\n+\ttgt_skel = bpf_sdt_target__open_and_load();\n+\tif (!ASSERT_OK_PTR(tgt_skel, \"target open_and_load\"))\n+\t\treturn;\n+\n+\tskel = bpf_sdt_observer__open();\n+\tif (!ASSERT_OK_PTR(skel, \"observer open\"))\n+\t\tgoto out_tgt;\n+\n+\tbpf_program__set_autoload(skel-\u003eprogs.tc_trace_prog, false);\n+\tbpf_program__set_autoload(skel-\u003eprogs.subprog_trace_prog, false);\n+\tbpf_program__set_attach_target(skel-\u003eprogs.xdp_trace_prog,\n+\t\t\t\t       bpf_program__fd(tgt_skel-\u003eprogs.xdp_prog),\n+\t\t\t\t       \"xdp_probe_len_ret\");\n+\terr = bpf_sdt_observer__load(skel);\n+\tif (!ASSERT_OK(err, \"observer load\"))\n+\t\tgoto out_obs;\n+\n+\tlink = sdt_attach(skel-\u003eprogs.xdp_trace_prog);\n+\tif (!ASSERT_GE(link, 0, \"attach xdp_probe_len_ret\"))\n+\t\tgoto out_obs;\n+\n+\ttopts.data_in = pkt;\n+\ttopts.data_size_in = sizeof(pkt);\n+\ttopts.data_size_out = sizeof(pkt);\n+\terr = bpf_prog_test_run_opts(bpf_program__fd(tgt_skel-\u003eprogs.xdp_prog), \u0026topts);\n+\tif (!ASSERT_OK(err, \"prog_test_run\"))\n+\t\tgoto out_link;\n+\n+\tASSERT_EQ(skel-\u003ebss-\u003exdp_len, sizeof(pkt), \"xdp_len\");\n+\tASSERT_EQ(skel-\u003ebss-\u003exdp_ret, XDP_PASS, \"xdp_ret\");\n+\n+out_link:\n+\tclose(link);\n+out_obs:\n+\tbpf_sdt_observer__destroy(skel);\n+out_tgt:\n+\tbpf_sdt_target__destroy(tgt_skel);\n+}\n+\n+static void multi_prog(void)\n+{\n+\tstruct bpf_sdt_target *tgt_skel;\n+\tstruct bpf_sdt_observer *skel;\n+\tint link1, link2, err;\n+\n+\ttgt_skel = bpf_sdt_target__open_and_load();\n+\tif (!ASSERT_OK_PTR(tgt_skel, \"target open_and_load\"))\n+\t\treturn;\n+\n+\tskel = bpf_sdt_observer__open();\n+\tif (!ASSERT_OK_PTR(skel, \"observer open\"))\n+\t\tgoto out_tgt;\n+\n+\tbpf_program__set_autoload(skel-\u003eprogs.subprog_trace_prog, false);\n+\tbpf_program__set_attach_target(skel-\u003eprogs.xdp_trace_prog,\n+\t\t\t\t       bpf_program__fd(tgt_skel-\u003eprogs.xdp_prog),\n+\t\t\t\t       \"xdp_probe_len_ret\");\n+\tbpf_program__set_attach_target(skel-\u003eprogs.tc_trace_prog,\n+\t\t\t\t       bpf_program__fd(tgt_skel-\u003eprogs.tc_prog),\n+\t\t\t\t       \"tc_probe\");\n+\terr = bpf_sdt_observer__load(skel);\n+\tif (!ASSERT_OK(err, \"observer load\"))\n+\t\tgoto out_obs;\n+\n+\tlink1 = sdt_attach(skel-\u003eprogs.xdp_trace_prog);\n+\tASSERT_GE(link1, 0, \"attach xdp xdp_probe_len_ret\");\n+\n+\tlink2 = sdt_attach(skel-\u003eprogs.tc_trace_prog);\n+\tASSERT_GE(link2, 0, \"attach tc_probe\");\n+\n+\tclose(link1);\n+\tclose(link2);\n+out_obs:\n+\tbpf_sdt_observer__destroy(skel);\n+out_tgt:\n+\tbpf_sdt_target__destroy(tgt_skel);\n+}\n+\n+static void subprog_probe(void)\n+{\n+\tLIBBPF_OPTS(bpf_test_run_opts, topts);\n+\tstruct bpf_sdt_target *tgt_skel;\n+\tstruct bpf_sdt_observer *skel;\n+\tchar pkt[64] = {0, 1, 2, 3};\n+\tint link, err;\n+\n+\ttgt_skel = bpf_sdt_target__open_and_load();\n+\tif (!ASSERT_OK_PTR(tgt_skel, \"target open_and_load\"))\n+\t\treturn;\n+\n+\tskel = bpf_sdt_observer__open();\n+\tif (!ASSERT_OK_PTR(skel, \"observer open\"))\n+\t\tgoto out_tgt;\n+\n+\tbpf_program__set_autoload(skel-\u003eprogs.tc_trace_prog, false);\n+\tbpf_program__set_autoload(skel-\u003eprogs.xdp_trace_prog, false);\n+\tbpf_program__set_attach_target(skel-\u003eprogs.subprog_trace_prog,\n+\t\t\t\t       bpf_program__fd(tgt_skel-\u003eprogs.xdp_prog),\n+\t\t\t\t       \"xdp_probe_ctx\");\n+\terr = bpf_sdt_observer__load(skel);\n+\tif (!ASSERT_OK(err, \"observer load\"))\n+\t\tgoto out_obs;\n+\n+\tlink = sdt_attach(skel-\u003eprogs.subprog_trace_prog);\n+\tif (!ASSERT_GE(link, 0, \"attach xdp_probe_ctx\"))\n+\t\tgoto out_obs;\n+\n+\ttopts.data_in = pkt;\n+\ttopts.data_size_in = sizeof(pkt);\n+\ttopts.data_size_out = sizeof(pkt);\n+\terr = bpf_prog_test_run_opts(bpf_program__fd(tgt_skel-\u003eprogs.xdp_prog), \u0026topts);\n+\tASSERT_OK(err, \"prog_test_run\");\n+\tASSERT_EQ(skel-\u003ebss-\u003exdp_len, sizeof(pkt), \"xdp_len\");\n+\n+\tclose(link);\n+out_obs:\n+\tbpf_sdt_observer__destroy(skel);\n+out_tgt:\n+\tbpf_sdt_target__destroy(tgt_skel);\n+}\n+\n+void test_bpf_sdt(void)\n+{\n+\tif (test__start_subtest(\"read_args\"))\n+\t\tread_args();\n+\tif (test__start_subtest(\"multi_prog\"))\n+\t\tmulti_prog();\n+\tif (test__start_subtest(\"subprog_probe\"))\n+\t\tsubprog_probe();\n+}\ndiff --git a/tools/testing/selftests/bpf/progs/bpf_sdt_observer.c b/tools/testing/selftests/bpf/progs/bpf_sdt_observer.c\nnew file mode 100644\nindex 00000000000000..ec059e86b15465\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/progs/bpf_sdt_observer.c\n@@ -0,0 +1,30 @@\n+// SPDX-License-Identifier: GPL-2.0\n+#include \"vmlinux.h\"\n+#include \u003cbpf/bpf_helpers.h\u003e\n+#include \u003cbpf/bpf_tracing.h\u003e\n+\n+volatile int xdp_len;\n+volatile int xdp_ret;\n+\n+SEC(\"bpf_sdt\")\n+int BPF_PROG(tc_trace_prog)\n+{\n+\treturn 0;\n+}\n+\n+SEC(\"bpf_sdt\")\n+int BPF_PROG(xdp_trace_prog, int len, int ret)\n+{\n+\txdp_len = len;\n+\txdp_ret = ret;\n+\treturn 0;\n+}\n+\n+SEC(\"bpf_sdt\")\n+int BPF_PROG(subprog_trace_prog, struct xdp_md *xdp_ctx)\n+{\n+\txdp_len = bpf_xdp_get_buff_len((struct xdp_md *)xdp_ctx);\n+\treturn 0;\n+}\n+\n+char LICENSE[] SEC(\"license\") = \"GPL\";\ndiff --git a/tools/testing/selftests/bpf/progs/bpf_sdt_target.c b/tools/testing/selftests/bpf/progs/bpf_sdt_target.c\nnew file mode 100644\nindex 00000000000000..e1c14b9d7e9a28\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/progs/bpf_sdt_target.c\n@@ -0,0 +1,34 @@\n+// SPDX-License-Identifier: GPL-2.0\n+#include \"vmlinux.h\"\n+#include \u003cbpf/bpf_helpers.h\u003e\n+#include \u003cbpf/bpf_tracing.h\u003e\n+#include \"../../../../tools/lib/bpf/bpf_sdt.h\"\n+\n+BPF_SDT_DECLARE(tc_probe);\n+BPF_SDT_DECLARE1(xdp_probe_ctx, struct xdp_md *);\n+BPF_SDT_DECLARE2(xdp_probe_len_ret, int, int);\n+\n+static __noinline int xdp_process(struct xdp_md *ctx)\n+{\n+\tBPF_SDT_PROBE1(xdp_probe_ctx, ctx);\n+\treturn XDP_PASS;\n+}\n+\n+SEC(\"xdp\")\n+int xdp_prog(struct xdp_md *ctx)\n+{\n+\tint len = (int)(ctx-\u003edata_end - ctx-\u003edata);\n+\n+\tBPF_SDT_PROBE2(xdp_probe_len_ret, len, XDP_PASS);\n+\n+\treturn xdp_process(ctx);\n+}\n+\n+SEC(\"tc\")\n+int tc_prog(struct __sk_buff *skb)\n+{\n+\tBPF_SDT_PROBE(tc_probe);\n+\treturn 0;\n+}\n+\n+char LICENSE[] SEC(\"license\") = \"GPL\";\n"
}

2/1 2026/06/27 15:13 agent patch-evaluator
3m Model:
gemini-3.1-pro-preview

Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [
    "bpf_sdt_link_attach",
    "bpf_sdt_link_release",
    "bpf_sdt_link_dealloc",
    "check_sdt_probe",
    "bpf_insn_array_get_sdt_probe_by_name",
    "insn_array_alloc",
    "insn_array_alloc_check"
  ],
  "Reasoning": "The patch introduces BPF SDT (Statically Defined Tracing) probes, allowing BPF observer programs to attach to statically defined probe points in other BPF programs. This adds new BPF attach types, link types, map flags, and verifier logic to check probe arguments, which is a significant functional change to the core BPF subsystem and should be fuzzed.",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer.
Your job is to review a provided patch series and determine
if it makes functional changes to the kernel that should be fuzzed.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively
use your code access tools to examine the actual source code and confirm any assumptions.

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).

If it modifies reachable core kernel logic, drivers, or architectures, use your code search
tools to verify the code can be executed, then return WorthFuzzing=true.

When returning WorthFuzzing=true, you MUST ALSO:
1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols.
   Avoid listing generic hot-path functions to prevent skewed test distributions.
2. Identify any specific CONFIG_ options required to properly test this new/modified feature.
   Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions.
   Do not list too generic configs (we already have them enabled). Only list those that
   specifically cover the modified code. List them in the EnableConfigs output array,
   and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit 835a2cce00842492f316111c0c1e2ee26f5fd475
Author: syz-cluster <triage@syzkaller.com>
Date:   Sat Jun 27 15:13:22 2026 +0000

    syz-cluster: applied patch under review

diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
index 054e043ffcd22d..262f1bd74d53f0 100644
--- a/arch/x86/net/bpf_jit_comp.c
+++ b/arch/x86/net/bpf_jit_comp.c
@@ -2797,6 +2797,16 @@ st:			insn_off = insn->off;
 			}
 
 			if (!jmp_offset) {
+				int index = bpf_prog->aux->subprog_start + i - 1;
+
+				/*
+				 * Emit a 5-byte NOP for SDT probe site, so it can be patched to
+				 * a CALL instruction when observer program is attached.
+				 */
+				if (env->insn_aux_data[index].sdt_entry) {
+					emit_nops(&prog, X86_PATCH_SIZE);
+					break;
+				}
 				/*
 				 * If jmp_padding is enabled, the extra nops will
 				 * be inserted. Otherwise, optimize out nop jumps.
@@ -3059,9 +3069,13 @@ static void save_args(const struct btf_func_model *m, u8 **prog,
 
 			/* copy the arguments from regs into stack */
 			for (j = 0; j < arg_regs; j++) {
+				u8 reg = (flags & BPF_TRAMP_F_SDT_PROBE)
+					? m->arg_regs[nr_regs]
+					: (nr_regs == 5 ? X86_REG_R9
+					   : BPF_REG_1 + nr_regs);
+
 				emit_stx(prog, BPF_DW, BPF_REG_FP,
-					 nr_regs == 5 ? X86_REG_R9 : BPF_REG_1 + nr_regs,
-					 -stack_size);
+					 reg, -stack_size);
 				stack_size -= 8;
 				nr_regs++;
 			}
@@ -3072,7 +3086,7 @@ static void save_args(const struct btf_func_model *m, u8 **prog,
 }
 
 static void restore_regs(const struct btf_func_model *m, u8 **prog,
-			 int stack_size)
+			 int stack_size, u32 flags)
 {
 	int i, j, arg_regs, nr_regs = 0;
 
@@ -3087,9 +3101,12 @@ static void restore_regs(const struct btf_func_model *m, u8 **prog,
 		arg_regs = (m->arg_size[i] + 7) / 8;
 		if (nr_regs + arg_regs <= 6) {
 			for (j = 0; j < arg_regs; j++) {
-				emit_ldx(prog, BPF_DW,
-					 nr_regs == 5 ? X86_REG_R9 : BPF_REG_1 + nr_regs,
-					 BPF_REG_FP,
+				u8 reg = (flags & BPF_TRAMP_F_SDT_PROBE)
+					? m->arg_regs[nr_regs]
+					: (nr_regs == 5 ? X86_REG_R9
+					   : BPF_REG_1 + nr_regs);
+
+				emit_ldx(prog, BPF_DW, reg, BPF_REG_FP,
 					 -stack_size);
 				stack_size -= 8;
 				nr_regs++;
@@ -3547,7 +3564,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
 	}
 
 	if (flags & BPF_TRAMP_F_CALL_ORIG) {
-		restore_regs(m, &prog, regs_off);
+		restore_regs(m, &prog, regs_off, flags);
 		save_args(m, &prog, arg_stack_off, true, flags);
 
 		if (flags & BPF_TRAMP_F_TAIL_CALL_CTX) {
@@ -3603,7 +3620,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
 	}
 
 	if (flags & BPF_TRAMP_F_RESTORE_REGS)
-		restore_regs(m, &prog, regs_off);
+		restore_regs(m, &prog, regs_off, flags);
 
 	/* This needs to be done regardless. If there were fmod_ret programs,
 	 * the return value is only updated on the stack and still needs to be
@@ -4220,3 +4237,8 @@ bool bpf_jit_supports_fsession(void)
 {
 	return true;
 }
+
+bool bpf_jit_supports_sdt_probe(void)
+{
+	return true;
+}
diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index 7719f652844560..6548a2dd76da6f 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -1220,6 +1220,8 @@ struct btf_func_model {
 	u8 nr_args;
 	u8 arg_size[MAX_BPF_FUNC_ARGS];
 	u8 arg_flags[MAX_BPF_FUNC_ARGS];
+	/* argument registers for bpf SDT probe site */
+	u8 arg_regs[MAX_BPF_FUNC_REG_ARGS];
 };
 
 /* Restore arguments before returning from trampoline to let original function
@@ -1268,6 +1270,12 @@ struct btf_func_model {
  */
 #define BPF_TRAMP_F_INDIRECT		BIT(8)
 
+/* Trampoline is for an SDT probe site: arguments arrive in
+ * BPF registers recorded by the SDT macro at build time,
+ * described by btf_func_model.arg_regs[] instead of ABI order.
+ */
+#define BPF_TRAMP_F_SDT_PROBE		BIT(9)
+
 /* Each call __bpf_prog_enter + call bpf_func + call __bpf_prog_exit is ~50
  * bytes on x86.
  */
@@ -1391,7 +1399,8 @@ struct bpf_trampoline {
 	struct {
 		struct btf_func_model model;
 		void *addr;
-		bool ftrace_managed;
+		u8 ftrace_managed:1;
+		u8 sdt_probe_site:1;
 	} func;
 	/* if !NULL this is BPF_PROG_TYPE_EXT program that extends another BPF
 	 * program by replacing one of its functions. func.addr is the address
@@ -1419,6 +1428,15 @@ struct bpf_attach_target_info {
 	const struct btf_type *tgt_type;
 };
 
+/*
+ * cached in prog->aux, used by the verifier to type the observer's context
+ * arguments, and by link_create to build the trampoline without re-resolving
+ */
+struct bpf_sdt_probe_info {
+	struct bpf_insn_array_value val;
+	unsigned long probe_ip;
+};
+
 #define BPF_DISPATCHER_MAX 48 /* Fits in 2048B */
 
 struct bpf_dispatcher_prog {
@@ -1762,6 +1780,7 @@ struct bpf_prog_aux {
 	void __percpu *priv_stack_ptr;
 	struct mutex dst_mutex; /* protects dst_* pointers below, *after* prog becomes visible */
 	struct bpf_prog *dst_prog;
+	struct bpf_sdt_probe_info *sdt_probe;
 	struct bpf_trampoline *dst_trampoline;
 	enum bpf_prog_type saved_dst_prog_type;
 	enum bpf_attach_type saved_dst_attach_type;
@@ -4154,11 +4173,14 @@ int bpf_prog_get_file_line(struct bpf_prog *prog, unsigned long ip, const char *
 			   const char **linep, int *nump);
 struct bpf_prog *bpf_prog_find_from_stack(void);
 
-int bpf_insn_array_init(struct bpf_map *map, const struct bpf_prog *prog);
+int bpf_insn_array_init(struct bpf_map *map, struct bpf_verifier_env *env);
 int bpf_insn_array_ready(struct bpf_map *map);
 void bpf_insn_array_release(struct bpf_map *map);
 void bpf_insn_array_adjust(struct bpf_map *map, u32 off, u32 len);
 void bpf_insn_array_adjust_after_remove(struct bpf_map *map, u32 off, u32 len);
+int bpf_insn_array_get_sdt_probe_by_name(struct bpf_prog *prog, const char *name,
+					 struct bpf_insn_array_value *val,
+					 unsigned long *ip);
 
 #ifdef CONFIG_BPF_SYSCALL
 void bpf_prog_update_insn_ptrs(struct bpf_prog *prog, u32 *offsets, void *image);
diff --git a/include/linux/bpf_types.h b/include/linux/bpf_types.h
index e5906829aa6f37..1aac6bc71c6fbb 100644
--- a/include/linux/bpf_types.h
+++ b/include/linux/bpf_types.h
@@ -157,3 +157,4 @@ BPF_LINK_TYPE(BPF_LINK_TYPE_KPROBE_MULTI, kprobe_multi)
 BPF_LINK_TYPE(BPF_LINK_TYPE_STRUCT_OPS, struct_ops)
 BPF_LINK_TYPE(BPF_LINK_TYPE_UPROBE_MULTI, uprobe_multi)
 BPF_LINK_TYPE(BPF_LINK_TYPE_TRACING_MULTI, tracing_multi)
+BPF_LINK_TYPE(BPF_LINK_TYPE_SDT, sdt)
diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index 76b8b7627a1085..e824b7b6469040 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -691,6 +691,9 @@ struct bpf_insn_aux_data {
 	u8 fastcall_spills_num:3;
 	u8 arg_prog:4;
 
+	/* set when the instruction is a SDT probe site */
+	struct bpf_insn_array_value *sdt_entry;
+
 	/* below fields are initialized once */
 	unsigned int orig_idx; /* original instruction index */
 	u32 jmp_point:1;
diff --git a/include/linux/filter.h b/include/linux/filter.h
index 67d337ede91b8e..ba4025160a6f33 100644
--- a/include/linux/filter.h
+++ b/include/linux/filter.h
@@ -1190,6 +1190,7 @@ bool bpf_jit_supports_insn(struct bpf_insn *insn, bool in_arena);
 bool bpf_jit_supports_private_stack(void);
 bool bpf_jit_supports_timed_may_goto(void);
 bool bpf_jit_supports_fsession(void);
+bool bpf_jit_supports_sdt_probe(void);
 u64 bpf_arch_uaddress_limit(void);
 void arch_bpf_stack_walk(bool (*consume_fn)(void *cookie, u64 ip, u64 sp, u64 bp), void *cookie);
 u64 arch_bpf_timed_may_goto(void);
diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
index c91b5a4bda03cb..6490eb5dfb68ee 100644
--- a/include/uapi/linux/bpf.h
+++ b/include/uapi/linux/bpf.h
@@ -1159,6 +1159,7 @@ enum bpf_attach_type {
 	BPF_TRACE_FENTRY_MULTI,
 	BPF_TRACE_FEXIT_MULTI,
 	BPF_TRACE_FSESSION_MULTI,
+	BPF_TRACE_SDT,
 	__MAX_BPF_ATTACH_TYPE
 };
 
@@ -1184,6 +1185,7 @@ enum bpf_link_type {
 	BPF_LINK_TYPE_NETKIT = 13,
 	BPF_LINK_TYPE_SOCKMAP = 14,
 	BPF_LINK_TYPE_TRACING_MULTI = 15,
+	BPF_LINK_TYPE_SDT,
 	__MAX_BPF_LINK_TYPE,
 };
 
@@ -1466,6 +1468,9 @@ enum {
 
 /* Enable BPF ringbuf overwrite mode */
 	BPF_F_RB_OVERWRITE	= (1U << 19),
+
+/* insn_array map is used for bpf SDT probe */
+	BPF_F_INSN_ARRAY_SDT	= (1U << 20),
 };
 
 /* Flags for BPF_PROG_QUERY. */
@@ -1518,6 +1523,7 @@ struct bpf_common_attr {
 };
 
 #define BPF_OBJ_NAME_LEN 16U
+#define BPF_SDT_MAX_NAME_LEN 64
 
 enum {
 	BPF_STREAM_STDOUT = 1,
@@ -1669,6 +1675,15 @@ union bpf_attr {
 		 * verification.
 		 */
 		__s32		keyring_id;
+		/* fd of the BPF_MAP_TYPE_INSN_ARRAY map created with
+		 * BPF_F_INSN_ARRAY_SDT, used for SDT probe
+		 */
+		__u32		sdt_map_fd;
+		/* Kernel looks up the probe using target prog and probe name */
+		struct {
+			__u32	target_prog_fd;
+			char	name[BPF_SDT_MAX_NAME_LEN]; /* probe name */
+		} sdt;
 	};
 
 	struct { /* anonymous struct used by BPF_OBJ_* commands */
@@ -7732,7 +7747,11 @@ struct bpf_insn_array_value {
 	__u32 orig_off;
 	__u32 xlated_off;
 	__u32 jitted_off;
-	__u32 :32;
+	__u32 btf_id;		/* BTF FUNC_PROTO type ID (0 if none) */
+	__u8  nargs;		/* argument count (0..5) */
+	__u8  arg_reg[5];	/* BPF register for each argument */
+	__u8  pad[2];
+	char  name[BPF_SDT_MAX_NAME_LEN]; /* probe name */
 };
 
 #endif /* _UAPI__LINUX_BPF_H__ */
diff --git a/kernel/bpf/bpf_insn_array.c b/kernel/bpf/bpf_insn_array.c
index a2f84afe6f7c65..067cc1b817d5f7 100644
--- a/kernel/bpf/bpf_insn_array.c
+++ b/kernel/bpf/bpf_insn_array.c
@@ -2,11 +2,14 @@
 /* Copyright (c) 2025 Isovalent */
 
 #include <linux/bpf.h>
+#include <linux/bpf_verifier.h>
+#include <linux/string.h>
 
 struct bpf_insn_array {
 	struct bpf_map map;
 	atomic_t used;
 	long *ips;
+	int subtype;
 	DECLARE_FLEX_ARRAY(struct bpf_insn_array_value, values);
 };
 
@@ -15,6 +18,11 @@ struct bpf_insn_array {
 
 #define INSN_DELETED ((u32)-1)
 
+enum bpf_insn_array_subtype {
+	BPF_INSN_ARRAY_SUBTYPE_JUMP_TABLE = 0,
+	BPF_INSN_ARRAY_SUBTYPE_SDT = 1,
+};
+
 static inline u64 insn_array_alloc_size(u32 max_entries)
 {
 	const u64 base_size = sizeof(struct bpf_insn_array);
@@ -28,7 +36,8 @@ static int insn_array_alloc_check(union bpf_attr *attr)
 	u32 value_size = sizeof(struct bpf_insn_array_value);
 
 	if (attr->max_entries == 0 || attr->key_size != 4 ||
-	    attr->value_size != value_size || attr->map_flags != 0)
+	    attr->value_size != value_size ||
+	    attr->map_flags & ~BPF_F_INSN_ARRAY_SDT)
 		return -EINVAL;
 
 	return 0;
@@ -55,6 +64,11 @@ static struct bpf_map *insn_array_alloc(union bpf_attr *attr)
 
 	bpf_map_init_from_attr(&insn_array->map, attr);
 
+	if (attr->map_flags & BPF_F_INSN_ARRAY_SDT)
+		insn_array->subtype = BPF_INSN_ARRAY_SUBTYPE_SDT;
+	else
+		insn_array->subtype = BPF_INSN_ARRAY_SUBTYPE_JUMP_TABLE;
+
 	/* BPF programs aren't allowed to write to the map */
 	insn_array->map.map_flags |= BPF_F_RDONLY_PROG;
 
@@ -90,6 +104,13 @@ static long insn_array_update_elem(struct bpf_map *map, void *key, void *value,
 
 	insn_array->values[index].orig_off = val.orig_off;
 
+	if (insn_array->subtype == BPF_INSN_ARRAY_SUBTYPE_SDT) {
+		insn_array->values[index].btf_id = val.btf_id;
+		insn_array->values[index].nargs = val.nargs;
+		memcpy(insn_array->values[index].arg_reg, val.arg_reg, sizeof(val.arg_reg));
+		strscpy(insn_array->values[index].name, val.name, sizeof(val.name));
+	}
+
 	return 0;
 }
 
@@ -180,10 +201,12 @@ static inline bool valid_offsets(const struct bpf_insn_array *insn_array,
 	return true;
 }
 
-int bpf_insn_array_init(struct bpf_map *map, const struct bpf_prog *prog)
+int bpf_insn_array_init(struct bpf_map *map, struct bpf_verifier_env *env)
 {
 	struct bpf_insn_array *insn_array = cast_insn_array(map);
 	struct bpf_insn_array_value *values = insn_array->values;
+	const struct bpf_prog *prog = env->prog;
+	const struct bpf_insn *insn;
 	int i;
 
 	if (!is_frozen(map))
@@ -206,6 +229,15 @@ int bpf_insn_array_init(struct bpf_map *map, const struct bpf_prog *prog)
 	for (i = 0; i < map->max_entries; i++)
 		values[i].xlated_off = values[i].orig_off;
 
+	if (insn_array->subtype == BPF_INSN_ARRAY_SUBTYPE_SDT) {
+		for (i = 0; i < map->max_entries; i++) {
+			insn = &prog->insnsi[values[i].orig_off];
+			if (insn->code != (BPF_JMP | BPF_JA) || insn->off != 0)
+				return -EINVAL;
+			env->insn_aux_data[values[i].orig_off].sdt_entry = &values[i];
+		}
+	}
+
 	return 0;
 }
 
@@ -231,6 +263,34 @@ void bpf_insn_array_release(struct bpf_map *map)
 	atomic_set(&insn_array->used, 0);
 }
 
+int bpf_insn_array_get_sdt_probe_by_name(struct bpf_prog *prog, const char *name,
+					 struct bpf_insn_array_value *val,
+					 unsigned long *ip)
+{
+	int i, j;
+	struct bpf_map *map;
+	struct bpf_insn_array *insn_array;
+
+	for (i = 0; i < prog->aux->used_map_cnt; i++) {
+		map = prog->aux->used_maps[i];
+		if (map->map_type != BPF_MAP_TYPE_INSN_ARRAY ||
+		    !(map->map_flags & BPF_F_INSN_ARRAY_SDT))
+			continue;
+		insn_array = cast_insn_array(map);
+		for (j = 0; j < map->max_entries; j++) {
+			if (insn_array->values[j].xlated_off == INSN_DELETED)
+				continue;
+			if (!strcmp(insn_array->values[j].name, name)) {
+				*val = insn_array->values[j];
+				*ip = (unsigned long)insn_array->ips[j];
+				return 0;
+			}
+		}
+	}
+
+	return -EEXIST;
+}
+
 void bpf_insn_array_adjust(struct bpf_map *map, u32 off, u32 len)
 {
 	struct bpf_insn_array *insn_array = cast_insn_array(map);
diff --git a/kernel/bpf/cfg.c b/kernel/bpf/cfg.c
index 26d37066465f38..1b8734ee4bf3d3 100644
--- a/kernel/bpf/cfg.c
+++ b/kernel/bpf/cfg.c
@@ -251,6 +251,9 @@ static struct bpf_iarray *jt_from_map(struct bpf_map *map)
 	int err;
 	int n;
 
+	if (map->map_flags & BPF_F_INSN_ARRAY_SDT)
+		return ERR_PTR(-EINVAL);
+
 	jt = bpf_iarray_realloc(NULL, map->max_entries);
 	if (!jt)
 		return ERR_PTR(-ENOMEM);
diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index 649cce41e13f3b..78c7ad3b4e2fc4 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -3263,6 +3263,11 @@ bool __weak bpf_jit_supports_fsession(void)
 	return false;
 }
 
+bool __weak bpf_jit_supports_sdt_probe(void)
+{
+	return false;
+}
+
 u64 __weak bpf_arch_uaddress_limit(void)
 {
 #if defined(CONFIG_64BIT) && defined(CONFIG_ARCH_HAS_NON_OVERLAPPING_ADDRESS_SPACE)
diff --git a/kernel/bpf/fixups.c b/kernel/bpf/fixups.c
index 3cf2cc6e3ab66a..4281c71cde4c66 100644
--- a/kernel/bpf/fixups.c
+++ b/kernel/bpf/fixups.c
@@ -568,6 +568,10 @@ int bpf_opt_remove_nops(struct bpf_verifier_env *env)
 		if (!is_may_goto_0 && !is_ja)
 			continue;
 
+		/* SDT probes are NOPs kept for text_poke at attach time. */
+		if (env->insn_aux_data[i].sdt_entry)
+			continue;
+
 		err = verifier_remove_insns(env, i, 1);
 		if (err)
 			return err;
diff --git a/kernel/bpf/liveness.c b/kernel/bpf/liveness.c
index 0aadfbae0acc5a..50ab8c29c0ebd1 100644
--- a/kernel/bpf/liveness.c
+++ b/kernel/bpf/liveness.c
@@ -2062,6 +2062,7 @@ struct insn_live_regs {
 /* Compute info->{use,def} fields for the instruction */
 static void compute_insn_live_regs(struct bpf_verifier_env *env,
 				   struct bpf_insn *insn,
+				   int insn_idx,
 				   struct insn_live_regs *info)
 {
 	struct bpf_call_summary cs;
@@ -2163,10 +2164,27 @@ static void compute_insn_live_regs(struct bpf_verifier_env *env,
 		switch (code) {
 		case BPF_JA:
 			def = 0;
-			if (BPF_SRC(insn->code) == BPF_X)
+			if (BPF_SRC(insn->code) == BPF_X) {
 				use = dst;
-			else
+			} else if (env->insn_aux_data[insn_idx].sdt_entry) {
+				struct bpf_insn_array_value *sdt;
+				int i;
+
+				/*
+				 * Without marking the argument registers arg_reg[]
+				 * as live, the liveness pass would clear them before
+				 * the probe site, causing check_sdt_probe() to reject
+				 * the prog with "arg is uninitialized".
+				 */
 				use = 0;
+				sdt = env->insn_aux_data[insn_idx].sdt_entry;
+				for (i = 0; i < sdt->nargs; i++) {
+					if (sdt->arg_reg[i] < BPF_REG_FP)
+						use |= BIT(sdt->arg_reg[i]);
+				}
+			} else {
+				use = 0;
+			}
 			break;
 		case BPF_JCOND:
 			def = 0;
@@ -2238,7 +2256,7 @@ int bpf_compute_live_registers(struct bpf_verifier_env *env)
 	}
 
 	for (i = 0; i < insn_cnt; ++i)
-		compute_insn_live_regs(env, &insns[i], &state[i]);
+		compute_insn_live_regs(env, &insns[i], i, &state[i]);
 
 	/* Forward pass: resolve stack access through FP-derived pointers */
 	err = bpf_compute_subprog_arg_access(env);
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index 6db306d23b479f..53897ff1100faf 100644
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -2454,6 +2454,7 @@ static void __bpf_prog_put_noref(struct bpf_prog *prog, bool deferred)
 	kvfree(prog->aux->linfo);
 	kfree(prog->aux->kfunc_tab);
 	kfree(prog->aux->ctx_arg_info);
+	kfree(prog->aux->sdt_probe);
 	if (prog->aux->attach_btf)
 		btf_put(prog->aux->attach_btf);
 
@@ -2967,13 +2968,15 @@ int __init __used bpf_multi_func(void) { return 0; }
 BTF_ID_LIST_GLOBAL_SINGLE(bpf_multi_func_btf_id, func, bpf_multi_func)
 
 /* last field in 'union bpf_attr' used by this command */
-#define BPF_PROG_LOAD_LAST_FIELD keyring_id
+#define BPF_PROG_LOAD_LAST_FIELD sdt.name
 
 static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, struct bpf_log_attr *attr_log)
 {
 	enum bpf_prog_type type = attr->prog_type;
+	struct bpf_sdt_probe_info *prog_sdt_probe = NULL;
 	struct bpf_prog *prog, *dst_prog = NULL;
 	struct btf *attach_btf = NULL;
+	u32 attach_btf_id = 0;
 	struct bpf_token *token = NULL;
 	bool bpf_cap;
 	int err;
@@ -3082,33 +3085,75 @@ static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, struct bpf_log_at
 		btf_get(attach_btf);
 	}
 
+	/*
+	 * find the probe site in the target program at load time of the
+	 * observer program, so the verifier can check the observer's context
+	 * arguments from the probe's FUNC_PROTO.
+	 */
+	if (attr->expected_attach_type == BPF_TRACE_SDT) {
+		struct bpf_insn_array_value sdt_val;
+		struct bpf_sdt_probe_info *sdt_probe;
+		unsigned long probe_ip;
+
+		if (!bpf_jit_supports_sdt_probe()) {
+			err = -EOPNOTSUPP;
+			goto put_token;
+		}
+		if (!attr->sdt.target_prog_fd) {
+			err = -EINVAL;
+			goto put_token;
+		}
+		dst_prog = bpf_prog_get(attr->sdt.target_prog_fd);
+		if (IS_ERR(dst_prog)) {
+			err = PTR_ERR(dst_prog);
+			dst_prog = NULL;
+			goto put_token;
+		}
+		err = bpf_insn_array_get_sdt_probe_by_name(dst_prog, attr->sdt.name,
+							   &sdt_val, &probe_ip);
+		if (err)
+			goto put_sdt;
+		sdt_probe = kzalloc_obj(struct bpf_sdt_probe_info, GFP_USER);
+		if (!sdt_probe) {
+			err = -ENOMEM;
+			goto put_sdt;
+		}
+		sdt_probe->val = sdt_val;
+		sdt_probe->probe_ip = probe_ip;
+		prog_sdt_probe = sdt_probe;
+		attach_btf_id = sdt_val.btf_id;
+	}
+
+
 	if (bpf_prog_load_check_attach(type, attr->expected_attach_type,
-				       attach_btf, attr->attach_btf_id,
+				       attach_btf,
+				       prog_sdt_probe ? attach_btf_id : attr->attach_btf_id,
 				       dst_prog, multi_func)) {
-		if (dst_prog)
-			bpf_prog_put(dst_prog);
-		if (attach_btf)
-			btf_put(attach_btf);
 		err = -EINVAL;
-		goto put_token;
+		goto put_sdt;
 	}
 
 	/* plain bpf_prog allocation */
 	prog = bpf_prog_alloc(bpf_prog_size(attr->insn_cnt), GFP_USER);
 	if (!prog) {
-		if (dst_prog)
-			bpf_prog_put(dst_prog);
-		if (attach_btf)
-			btf_put(attach_btf);
 		err = -EINVAL;
-		goto put_token;
+		goto put_sdt;
 	}
 
 	prog->expected_attach_type = attr->expected_attach_type;
 	prog->sleepable = !!(attr->prog_flags & BPF_F_SLEEPABLE);
 	prog->aux->attach_btf = attach_btf;
-	prog->aux->attach_btf_id = multi_func ? bpf_multi_func_btf_id[0] : attr->attach_btf_id;
+	if (prog_sdt_probe)
+		prog->aux->attach_btf_id = attach_btf_id;
+	else
+		prog->aux->attach_btf_id =
+			multi_func ? bpf_multi_func_btf_id[0] : attr->attach_btf_id;
 	prog->aux->dst_prog = dst_prog;
+	prog->aux->sdt_probe = prog_sdt_probe;
+	/* ownership of dst_prog/attach_btf/prog_sdt_probe moved to prog->aux */
+	dst_prog = NULL;
+	attach_btf = NULL;
+	prog_sdt_probe = NULL;
 	prog->aux->dev_bound = !!attr->prog_ifindex;
 	prog->aux->xdp_has_frags = attr->prog_flags & BPF_F_XDP_HAS_FRAGS;
 
@@ -3242,6 +3287,12 @@ static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, struct bpf_log_at
 	if (prog->aux->attach_btf)
 		btf_put(prog->aux->attach_btf);
 	bpf_prog_free(prog);
+put_sdt:
+	kfree(prog_sdt_probe);
+	if (attach_btf)
+		btf_put(attach_btf);
+	if (dst_prog)
+		bpf_prog_put(dst_prog);
 put_token:
 	bpf_token_put(token);
 	return err;
@@ -3686,6 +3737,89 @@ static const struct bpf_link_ops bpf_tracing_link_lops = {
 	.fill_link_info = bpf_tracing_link_fill_link_info,
 };
 
+struct bpf_sdt_link {
+	struct bpf_tramp_link link;
+	struct bpf_trampoline *trampoline;
+};
+
+static void bpf_sdt_link_release(struct bpf_link *link)
+{
+	struct bpf_sdt_link *sdt_link = container_of(link, struct bpf_sdt_link, link.link);
+	struct bpf_prog *prog = sdt_link->link.link.prog;
+	struct bpf_prog *tgt_prog = prog->aux->dst_prog;
+
+	WARN_ON_ONCE(bpf_trampoline_unlink_prog(&sdt_link->link.node, sdt_link->trampoline,
+						tgt_prog));
+	bpf_trampoline_put(sdt_link->trampoline);
+}
+
+static void bpf_sdt_link_dealloc(struct bpf_link *link)
+{
+	struct bpf_sdt_link *sdt_link = container_of(link, struct bpf_sdt_link, link.link);
+
+	kfree(sdt_link);
+}
+
+static const struct bpf_link_ops bpf_sdt_link_lops = {
+	.release = bpf_sdt_link_release,
+	.dealloc = bpf_sdt_link_dealloc,
+};
+
+static int bpf_sdt_link_attach(struct bpf_prog *prog)
+{
+	struct bpf_sdt_probe_info *sdt = prog->aux->sdt_probe;
+	struct bpf_attach_target_info tgt_info = {};
+	struct bpf_link_primer primer;
+	struct bpf_sdt_link *sdt_link;
+	struct bpf_prog *tgt_prog = prog->aux->dst_prog;
+	u64 tr_key;
+	int i, err;
+
+	if (!sdt || !tgt_prog)
+		return -EINVAL;
+
+	sdt_link = kzalloc_obj(struct bpf_sdt_link, GFP_USER);
+	if (!sdt_link)
+		return -ENOMEM;
+
+	bpf_tramp_link_init(&sdt_link->link, BPF_LINK_TYPE_SDT, &bpf_sdt_link_lops, prog,
+			    BPF_TRACE_SDT, 0);
+	err = bpf_link_prime(&sdt_link->link.link, &primer);
+	if (err) {
+		kfree(sdt_link);
+		return err;
+	}
+
+	/* use orig_off to compute the key as orig_off is unique for each probe */
+	tr_key = bpf_trampoline_compute_key(tgt_prog, NULL, sdt->val.orig_off);
+	tgt_info.tgt_addr = sdt->probe_ip;
+	tgt_info.tgt_name = sdt->val.name;
+	tgt_info.fmodel.nr_args = sdt->val.nargs;
+	for (i = 0; i < sdt->val.nargs; i++) {
+		tgt_info.fmodel.arg_size[i] = 8;
+		tgt_info.fmodel.arg_regs[i] = sdt->val.arg_reg[i];
+	}
+
+	sdt_link->trampoline = bpf_trampoline_get(tr_key, &tgt_info);
+	if (!sdt_link->trampoline) {
+		err = -ENOMEM;
+		goto err_cleanup;
+	}
+	sdt_link->trampoline->func.sdt_probe_site = 1;
+
+	err = bpf_trampoline_link_prog(&sdt_link->link.node, sdt_link->trampoline, tgt_prog);
+	if (err)
+		goto err_put_tramp;
+
+	return bpf_link_settle(&primer);
+
+err_put_tramp:
+	bpf_trampoline_put(sdt_link->trampoline);
+err_cleanup:
+	bpf_link_cleanup(&primer);
+	return err;
+}
+
 static int bpf_tracing_prog_attach(struct bpf_prog *prog,
 				   int tgt_prog_fd,
 				   u32 btf_id,
@@ -4507,6 +4641,7 @@ attach_type_to_prog_type(enum bpf_attach_type attach_type)
 	case BPF_TRACE_FENTRY_MULTI:
 	case BPF_TRACE_FEXIT_MULTI:
 	case BPF_MODIFY_RETURN:
+	case BPF_TRACE_SDT:
 		return BPF_PROG_TYPE_TRACING;
 	case BPF_LSM_MAC:
 		return BPF_PROG_TYPE_LSM;
@@ -5893,6 +6028,8 @@ static int link_create(union bpf_attr *attr, bpfptr_t uattr)
 			ret = cgroup_bpf_link_attach(attr, prog);
 		else if (is_tracing_multi(prog->expected_attach_type))
 			ret = bpf_tracing_multi_attach(prog, attr);
+		else if (prog->expected_attach_type == BPF_TRACE_SDT)
+			ret = bpf_sdt_link_attach(prog);
 		else
 			ret = bpf_tracing_prog_attach(prog,
 						      attr->link_create.target_fd,
diff --git a/kernel/bpf/trampoline.c b/kernel/bpf/trampoline.c
index 1a721fc4bef562..68f87e7b901f91 100644
--- a/kernel/bpf/trampoline.c
+++ b/kernel/bpf/trampoline.c
@@ -691,6 +691,9 @@ static int bpf_trampoline_update(struct bpf_trampoline *tr, bool lock_direct_mut
 	/* clear all bits except SHARE_IPMODIFY and TAIL_CALL_CTX */
 	tr->flags &= (BPF_TRAMP_F_SHARE_IPMODIFY | BPF_TRAMP_F_TAIL_CALL_CTX);
 
+	if (tr->func.sdt_probe_site)
+		tr->flags |= BPF_TRAMP_F_SDT_PROBE;
+
 	if (tnodes[BPF_TRAMP_FEXIT].nr_nodes ||
 	    tnodes[BPF_TRAMP_MODIFY_RETURN].nr_nodes) {
 		/* NOTE: BPF_TRAMP_F_RESTORE_REGS and BPF_TRAMP_F_SKIP_FRAME
@@ -784,6 +787,7 @@ static enum bpf_tramp_prog_type bpf_attach_type_to_tramp(struct bpf_prog *prog)
 	switch (prog->expected_attach_type) {
 	case BPF_TRACE_FENTRY:
 	case BPF_TRACE_FENTRY_MULTI:
+	case BPF_TRACE_SDT:
 		return BPF_TRAMP_FENTRY;
 	case BPF_MODIFY_RETURN:
 		return BPF_TRAMP_MODIFY_RETURN;
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 25aea4271cd089..a875d64a095071 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -16451,6 +16451,7 @@ static bool return_retval_range(struct bpf_verifier_env *env, struct bpf_retval_
 			break;
 		case BPF_TRACE_RAW_TP:
 		case BPF_MODIFY_RETURN:
+		case BPF_TRACE_SDT:
 			return false;
 		case BPF_TRACE_ITER:
 		default:
@@ -17242,6 +17243,133 @@ static int check_indirect_jump(struct bpf_verifier_env *env, struct bpf_insn *in
 	return INSN_IDX_UPDATED;
 }
 
+static int check_sdt_probe(struct bpf_verifier_env *env, int insn_idx)
+{
+	enum bpf_prog_type prog_type = resolve_prog_type(env->prog);
+	struct bpf_insn_array_value *val;
+	struct bpf_func_state *frame;
+	struct bpf_reg_state *regs;
+	const struct btf *btf;
+	const struct btf_type *proto;
+	const struct btf_param *args;
+	int i, nargs;
+
+	val = env->insn_aux_data[insn_idx].sdt_entry;
+	if (!val->nargs)
+		return 0;
+
+	if (val->nargs > MAX_BPF_FUNC_REG_ARGS) {
+		verbose(env, "SDT probe nargs %u > 5\n", val->nargs);
+		return -EINVAL;
+	}
+
+	frame = env->cur_state->frame[env->cur_state->curframe];
+	regs = frame->regs;
+
+	for (i = 0; i < val->nargs; i++) {
+		u8 reg = val->arg_reg[i];
+		struct bpf_reg_state *rs = &regs[reg];
+
+		if (rs->type == NOT_INIT) {
+			verbose(env, "SDT arg%d (r%d) is uninitialized\n", i, reg);
+			return -EINVAL;
+		}
+	}
+
+	btf = env->prog->aux->btf;
+	if (!btf || !val->btf_id) {
+		verbose(env, "BTF is required for SDT probe with %u arguments\n", val->nargs);
+		return -EINVAL;
+	}
+
+	proto = btf_type_by_id(btf, val->btf_id);
+	if (!proto || !btf_type_is_func_proto(proto)) {
+		verbose(env, "SDT btf_id %u is not a FUNC_PROTO\n", val->btf_id);
+		return -EINVAL;
+	}
+
+	nargs = btf_type_vlen(proto);
+	if (nargs != val->nargs) {
+		verbose(env, "SDT nargs %u != BTF FUNC_PROTO nargs %d\n",
+			val->nargs, nargs);
+		return -EINVAL;
+	}
+
+	args = (const struct btf_param *)(proto + 1);
+	for (i = 0; i < nargs; i++) {
+		u8 reg = val->arg_reg[i];
+		struct bpf_reg_state *rs = &regs[reg];
+		const struct btf_type *t;
+		u32 arg_btf_id;
+		u32 t_size;
+
+		t = btf_type_skip_modifiers(btf, args[i].type, NULL);
+		t_size = t ? t->size : 0;
+
+		if (btf_type_is_scalar(t)) {
+			if (base_type(rs->type) != SCALAR_VALUE) {
+				verbose(env, "SDT arg%d (r%d) type %s expected scalar\n",
+					i, reg, reg_type_str(env, rs->type));
+				return -EACCES;
+			}
+			continue;
+		}
+
+		/*
+		 * Small structs/unions (<= 8 bytes) are passed by value in
+		 * a register as a SCALAR_VALUE carrying the raw bytes.  The
+		 * BPF_SDT_PROBE<N> macro enforces sizeof(arg) <= 8 at build
+		 * time, so the FUNC_PROTO parameter type is the struct itself
+		 * (not a pointer) and the verifier sees SCALAR_VALUE.
+		 */
+		if (btf_type_is_struct(t) && t_size <= 8) {
+			if (base_type(rs->type) != SCALAR_VALUE) {
+				verbose(env,
+				"SDT arg%d (r%d) type %s expected scalar (small struct by value)\n",
+					i, reg, reg_type_str(env, rs->type));
+				return -EACCES;
+			}
+			continue;
+		}
+
+		if (!btf_type_is_ptr(t)) {
+			verbose(env, "SDT arg%d (r%d) unsupported BTF parameter kind\n",
+				i, reg);
+			return -EACCES;
+		}
+
+		/*
+		 * When the probe argument is the target program's context
+		 * type (e.g. struct xdp_md * for XDP), the register at the
+		 * probe site is PTR_TO_CTX, not PTR_TO_BTF_ID.
+		 */
+		if (base_type(rs->type) == PTR_TO_CTX &&
+		    btf_is_prog_ctx_type(&env->log, btf, t, prog_type, i))
+			continue;
+
+		t = btf_type_skip_modifiers(btf, t->type, &arg_btf_id);
+		if (!btf_type_is_struct(t)) {
+			verbose(env, "SDT arg%d (r%d) unsupported BTF pointer target\n",
+				i, reg);
+			return -EACCES;
+		}
+
+		if (base_type(rs->type) != PTR_TO_BTF_ID) {
+			verbose(env, "SDT arg%d (r%d) type %s expected PTR_TO_BTF_ID\n",
+				i, reg, reg_type_str(env, rs->type));
+			return -EACCES;
+		}
+
+		if (!btf_struct_ids_match(&env->log, rs->btf, rs->btf_id,
+					  rs->var_off.value, btf, arg_btf_id, false)) {
+			verbose(env, "SDT arg%d (r%d) btf_id %u does not match expected %u\n",
+				i, reg, rs->btf_id, arg_btf_id);
+			return -EACCES;
+		}
+	}
+	return 0;
+}
+
 static int do_check_insn(struct bpf_verifier_env *env, bool *do_print_state)
 {
 	int err;
@@ -17317,6 +17445,9 @@ static int do_check_insn(struct bpf_verifier_env *env, bool *do_print_state)
 			if (BPF_SRC(insn->code) == BPF_X)
 				return check_indirect_jump(env, insn);
 
+			if (env->insn_aux_data[env->insn_idx].sdt_entry)
+				return check_sdt_probe(env, env->insn_idx);
+
 			if (class == BPF_JMP)
 				env->insn_idx += insn->off + 1;
 			else
@@ -17882,7 +18013,7 @@ static int __add_used_map(struct bpf_verifier_env *env, struct bpf_map *map)
 	env->used_maps[env->used_map_cnt++] = map;
 
 	if (map->map_type == BPF_MAP_TYPE_INSN_ARRAY) {
-		err = bpf_insn_array_init(map, env->prog);
+		err = bpf_insn_array_init(map, env);
 		if (err) {
 			verbose(env, "Failed to properly initialize insn array\n");
 			return err;
@@ -19328,6 +19459,24 @@ static int check_attach_btf_id(struct bpf_verifier_env *env)
 	    prog->type != BPF_PROG_TYPE_EXT)
 		return 0;
 
+	if (prog->expected_attach_type == BPF_TRACE_SDT) {
+		const struct btf_type *sdt_t;
+		struct btf *sdt_btf = tgt_prog ? tgt_prog->aux->btf : NULL;
+
+		if (!sdt_btf) {
+			verbose(env, "SDT observer requires a target program with BTF\n");
+			return -EINVAL;
+		}
+		sdt_t = btf_type_by_id(sdt_btf, btf_id);
+		if (!sdt_t || !btf_type_is_func_proto(sdt_t)) {
+			verbose(env, "SDT attach_btf_id %u is not a FUNC_PROTO\n", btf_id);
+			return -EINVAL;
+		}
+		prog->aux->attach_func_proto = sdt_t;
+		prog->aux->attach_func_name = "sdt_probe";
+		return 0;
+	}
+
 	ret = bpf_check_attach_target(&env->log, prog, tgt_prog, btf_id, &tgt_info);
 	if (ret)
 		return ret;
@@ -19800,6 +19949,29 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
 	if (ret)
 		goto skip_full_check;
 
+	if (attr->sdt_map_fd) {
+		CLASS(fd, f)(attr->sdt_map_fd);
+		struct bpf_map *sdt_map = __bpf_map_get(f);
+
+		if (IS_ERR(sdt_map)) {
+			verbose(env, "sdt_map_fd %d is not a valid bpf_map\n",
+				attr->sdt_map_fd);
+			ret = PTR_ERR(sdt_map);
+			goto skip_full_check;
+		}
+		if (sdt_map->map_type != BPF_MAP_TYPE_INSN_ARRAY) {
+			verbose(env, "sdt_map_fd %d is not an INSN_ARRAY map\n",
+				attr->sdt_map_fd);
+			ret = -EINVAL;
+			goto skip_full_check;
+		}
+		ret = __add_used_map(env, sdt_map);
+		if (ret < 0) {
+			verbose(env, "failed to bind SDT map to program: %d\n", ret);
+			goto skip_full_check;
+		}
+	}
+
 	mark_verifier_state_clean(env);
 
 	if (IS_ERR(btf_vmlinux)) {
diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h
index c91b5a4bda03cb..6490eb5dfb68ee 100644
--- a/tools/include/uapi/linux/bpf.h
+++ b/tools/include/uapi/linux/bpf.h
@@ -1159,6 +1159,7 @@ enum bpf_attach_type {
 	BPF_TRACE_FENTRY_MULTI,
 	BPF_TRACE_FEXIT_MULTI,
 	BPF_TRACE_FSESSION_MULTI,
+	BPF_TRACE_SDT,
 	__MAX_BPF_ATTACH_TYPE
 };
 
@@ -1184,6 +1185,7 @@ enum bpf_link_type {
 	BPF_LINK_TYPE_NETKIT = 13,
 	BPF_LINK_TYPE_SOCKMAP = 14,
 	BPF_LINK_TYPE_TRACING_MULTI = 15,
+	BPF_LINK_TYPE_SDT,
 	__MAX_BPF_LINK_TYPE,
 };
 
@@ -1466,6 +1468,9 @@ enum {
 
 /* Enable BPF ringbuf overwrite mode */
 	BPF_F_RB_OVERWRITE	= (1U << 19),
+
+/* insn_array map is used for bpf SDT probe */
+	BPF_F_INSN_ARRAY_SDT	= (1U << 20),
 };
 
 /* Flags for BPF_PROG_QUERY. */
@@ -1518,6 +1523,7 @@ struct bpf_common_attr {
 };
 
 #define BPF_OBJ_NAME_LEN 16U
+#define BPF_SDT_MAX_NAME_LEN 64
 
 enum {
 	BPF_STREAM_STDOUT = 1,
@@ -1669,6 +1675,15 @@ union bpf_attr {
 		 * verification.
 		 */
 		__s32		keyring_id;
+		/* fd of the BPF_MAP_TYPE_INSN_ARRAY map created with
+		 * BPF_F_INSN_ARRAY_SDT, used for SDT probe
+		 */
+		__u32		sdt_map_fd;
+		/* Kernel looks up the probe using target prog and probe name */
+		struct {
+			__u32	target_prog_fd;
+			char	name[BPF_SDT_MAX_NAME_LEN]; /* probe name */
+		} sdt;
 	};
 
 	struct { /* anonymous struct used by BPF_OBJ_* commands */
@@ -7732,7 +7747,11 @@ struct bpf_insn_array_value {
 	__u32 orig_off;
 	__u32 xlated_off;
 	__u32 jitted_off;
-	__u32 :32;
+	__u32 btf_id;		/* BTF FUNC_PROTO type ID (0 if none) */
+	__u8  nargs;		/* argument count (0..5) */
+	__u8  arg_reg[5];	/* BPF register for each argument */
+	__u8  pad[2];
+	char  name[BPF_SDT_MAX_NAME_LEN]; /* probe name */
 };
 
 #endif /* _UAPI__LINUX_BPF_H__ */
diff --git a/tools/lib/bpf/Makefile b/tools/lib/bpf/Makefile
index eca584fb061e16..1f6f027a327b66 100644
--- a/tools/lib/bpf/Makefile
+++ b/tools/lib/bpf/Makefile
@@ -235,7 +235,7 @@ install_lib: all_cmd
 
 SRC_HDRS := bpf.h libbpf.h btf.h libbpf_common.h libbpf_legacy.h	     \
 	    bpf_helpers.h bpf_tracing.h bpf_endian.h bpf_core_read.h	     \
-	    skel_internal.h libbpf_version.h usdt.bpf.h
+	    skel_internal.h libbpf_version.h usdt.bpf.h bpf_sdt.h
 GEN_HDRS := $(BPF_GENERATED)
 
 INSTALL_PFX := $(DESTDIR)$(prefix)/include/bpf
diff --git a/tools/lib/bpf/bpf.c b/tools/lib/bpf/bpf.c
index 96819c082c772d..adf951df6a3ec7 100644
--- a/tools/lib/bpf/bpf.c
+++ b/tools/lib/bpf/bpf.c
@@ -295,7 +295,7 @@ int bpf_prog_load(enum bpf_prog_type prog_type,
 		  const struct bpf_insn *insns, size_t insn_cnt,
 		  struct bpf_prog_load_opts *opts)
 {
-	const size_t attr_sz = offsetofend(union bpf_attr, keyring_id);
+	const size_t attr_sz = offsetofend(union bpf_attr, sdt.name);
 	void *finfo = NULL, *linfo = NULL;
 	const char *func_info, *line_info;
 	__u32 log_size, log_level, attach_prog_fd, attach_btf_obj_fd;
@@ -303,6 +303,7 @@ int bpf_prog_load(enum bpf_prog_type prog_type,
 	int fd, attempts;
 	union bpf_attr attr;
 	char *log_buf;
+	const char *sdt_name;
 
 	bump_rlimit_memlock();
 
@@ -369,6 +370,12 @@ int bpf_prog_load(enum bpf_prog_type prog_type,
 
 	attr.fd_array = ptr_to_u64(OPTS_GET(opts, fd_array, NULL));
 	attr.fd_array_cnt = OPTS_GET(opts, fd_array_cnt, 0);
+	attr.sdt_map_fd = OPTS_GET(opts, sdt_map_fd, 0);
+	attr.sdt.target_prog_fd = OPTS_GET(opts, sdt.target_prog_fd, 0);
+
+	sdt_name = OPTS_GET(opts, sdt.name, NULL);
+	if (sdt_name)
+		snprintf(attr.sdt.name, sizeof(attr.sdt.name), "%s", sdt_name);
 
 	if (log_level) {
 		attr.log_buf = ptr_to_u64(log_buf);
@@ -861,6 +868,7 @@ int bpf_link_create(int prog_fd, int target_fd,
 	case BPF_MODIFY_RETURN:
 	case BPF_TRACE_FSESSION:
 	case BPF_LSM_MAC:
+	case BPF_TRACE_SDT:
 		attr.link_create.tracing.cookie = OPTS_GET(opts, tracing.cookie, 0);
 		if (!OPTS_ZEROED(opts, tracing))
 			return libbpf_err(-EINVAL);
diff --git a/tools/lib/bpf/bpf.h b/tools/lib/bpf/bpf.h
index 7534a593edae21..bf3665789420a8 100644
--- a/tools/lib/bpf/bpf.h
+++ b/tools/lib/bpf/bpf.h
@@ -128,9 +128,16 @@ struct bpf_prog_load_opts {
 
 	/* if set, provides the length of fd_array */
 	__u32 fd_array_cnt;
+	/* if set, FD of the program's BPF_MAP_TYPE_INSN_ARRAY SDT map */
+	__u32 sdt_map_fd;
+	/* target program fd and probe name for SDT observer program */
+	struct {
+		__u32 target_prog_fd;
+		const char *name;
+	} sdt;
 	size_t :0;
 };
-#define bpf_prog_load_opts__last_field fd_array_cnt
+#define bpf_prog_load_opts__last_field sdt.name
 
 LIBBPF_API int bpf_prog_load(enum bpf_prog_type prog_type,
 			     const char *prog_name, const char *license,
diff --git a/tools/lib/bpf/bpf_sdt.h b/tools/lib/bpf/bpf_sdt.h
new file mode 100644
index 00000000000000..23f84460aa3685
--- /dev/null
+++ b/tools/lib/bpf/bpf_sdt.h
@@ -0,0 +1,346 @@
+/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
+
+/*
+ * USDT-like probe for BPF programs.
+ *
+ * 1. Example
+ *
+ * A probe in the target program can be declared and defined like:
+ *
+ *   BPF_SDT_DECLARE2(my_trace, int, int);
+ *
+ *   SEC("xdp")
+ *   int xdp_prog(struct xdp_md *ctx)
+ *   {
+ *       int len = ctx->data_end - ctx->data;
+ *       int ret = XDP_DROP;
+ *       ...
+ *       BPF_SDT_PROBE2(my_trace, len, ret);
+ *       ...
+ *   }
+ *
+ * An observer would be like:
+ *
+ *   SEC("bpf_sdt")
+ *   int BPF_PROG(observer_prog, int len, int ret)
+ *   {
+ *       bpf_printk("len=%d ret=%d\n", len, ret);
+ *       return 0;
+ *   }
+ *
+ * The target program and probe site is set at attach time dynamically.
+ *
+ * 2. Design
+ *
+ * 2.1 declaration macro
+ *
+ * BPF_SDT_DECLARE<N>(name, types...) generates:
+ *
+ *  - A BTF function-pointer variable in a .debug_* section:
+ *
+ *       static void (*const __sdt_type_##name)(types...);
+ *
+ *   which produces a BTF VAR -> PTR -> FUNC_PROTO(types...) chain
+ *   tagged with btf_decl_tag("bpf_sdt:<name>:<nargs>"). The BPF
+ *   linker discards the .debug_* section but preserves the BTF
+ *   entries, so the argument types survive into the final object.
+ *   The type will be passed to the kernel for the verifier to check
+ *   if it matches the real value type in the argument register.
+ *
+ *  - One dummy static variable per argument:
+ *
+ *       __attribute__((section(".debug_bpf_sdt_chk")))
+ *       typeof(declared_type) __sdt_chk_##name##_N;
+ *
+ *   The dummy variable carries the declared type. At the
+ *   BPF_SDT_PROBE call site, _Static_assert with
+ *   __builtin_types_compatible_p() compares the caller's actual
+ *   argument type against the dummy variable's type:
+ *
+ *       _Static_assert(__builtin_types_compatible_p(
+ *                       typeof(caller_arg),
+ *                       typeof(__sdt_chk_##name##_N)),
+ *                       "type mismatch");
+ *
+ *   For example, if the caller passes an int where the declaration
+ *   said char *, the compiler fails with a message naming the
+ *   mismatched argument.
+ *
+ * 2.2 probe macro
+ *
+ * BPF_SDT_PROBE<N> is an inline assembly sequence that emits into two
+ * ELF sections simultaneously. A goto+0 instruction is inserted at the
+ * call site in the bpf prog code section. It works as a NOP, and will be
+ * patched to a jump instruction when an observer is attached. The call
+ * site address and argument information are recorded into a separate
+ * section .bpf_sdt_notes. This section is not executed, it is used as
+ * metadata. libbpf will convert the metadata to an insn_array map at
+ * load time.
+ *
+ * An interesting note is that the argument registers are recorded in
+ * BPF move instructions, like r1 = %[arg_reg0]. The instruction is not
+ * executed, it is only used by the linker to extract the argument
+ * register from the src_reg.
+ *
+ * For BPF_SDT_PROBE2(my_trace, len, ret) the compiler produces:
+ *
+ * [code section, e.g. xdp]
+ *     goto +0                  // NOP, patched to CALL at attach time
+ *
+ * [.bpf_sdt_notes]
+ *     ___sdt_jt_my_trace:      // symbol marking this entry's boundary
+ *     .quad  0b                // 8 bytes: offset of the NOP in the code
+ *                              // section (resolved by the linker via
+ *                              // R_BPF_64_ABS64 relocation)
+ *     r1 = %[arg1_reg]         // 8 bytes per argument: BPF move instruction
+ *                              // whose src_reg field encodes the BPF
+ *                              // register holding each probe argument
+ *     r2 = %[arg2_reg]
+ *
+ *     ....
+ *
+ *  [.BTF]
+ *     FUNC_PROTO (int, int) -> void   // from BPF_SDT_DECLARE2 stub
+ *     DECL_TAG "bpf_sdt:my_trace:2"   // keyed by name + nargs
+ */
+
+#ifndef __BPF_SDT_PROBE_H
+#define __BPF_SDT_PROBE_H
+
+#define ___sdt_decl_attr(name, nargs)					\
+	__attribute__((used, section(".debug_bpf_sdt_types"),		\
+			btf_decl_tag("bpf_sdt:" #name ":" #nargs)))
+
+#define ___sdt_chk_attr							\
+	 __attribute__((unused, section(".debug_bpf_sdt_chk")))
+
+#define BPF_SDT_DECLARE(name)						\
+	___sdt_decl_attr(name, 0)					\
+	static void (*const __sdt_type_##name)(void)
+
+#define BPF_SDT_DECLARE1(name, t0)					\
+	___sdt_decl_attr(name, 1)					\
+	static void (*const __sdt_type_##name)(t0);			\
+	___sdt_chk_attr t0 __sdt_chk_##name##_0
+
+#define BPF_SDT_DECLARE2(name, t0, t1)					\
+	___sdt_decl_attr(name, 2)					\
+	static void (*const __sdt_type_##name)(t0, t1);			\
+	___sdt_chk_attr t0 __sdt_chk_##name##_0;			\
+	___sdt_chk_attr t1 __sdt_chk_##name##_1
+
+#define BPF_SDT_DECLARE3(name, t0, t1, t2)				\
+	___sdt_decl_attr(name, 3)					\
+	static void (*const __sdt_type_##name)(t0, t1, t2);		\
+	___sdt_chk_attr t0 __sdt_chk_##name##_0;			\
+	___sdt_chk_attr t1 __sdt_chk_##name##_1;			\
+	___sdt_chk_attr t2 __sdt_chk_##name##_2
+
+#define BPF_SDT_DECLARE4(name, t0, t1, t2, t3)				\
+	___sdt_decl_attr(name, 4)					\
+	static void (*const __sdt_type_##name)(t0, t1, t2, t3);		\
+	___sdt_chk_attr t0 __sdt_chk_##name##_0;			\
+	___sdt_chk_attr t1 __sdt_chk_##name##_1;			\
+	___sdt_chk_attr t2 __sdt_chk_##name##_2;			\
+	___sdt_chk_attr t3 __sdt_chk_##name##_3
+
+#define BPF_SDT_DECLARE5(name, t0, t1, t2, t3, t4)			\
+	___sdt_decl_attr(name, 5)					\
+	static void (*const __sdt_type_##name)(t0, t1, t2, t3, t4);	\
+	___sdt_chk_attr t0 __sdt_chk_##name##_0;			\
+	___sdt_chk_attr t1 __sdt_chk_##name##_1;			\
+	___sdt_chk_attr t2 __sdt_chk_##name##_2;			\
+	___sdt_chk_attr t3 __sdt_chk_##name##_3;			\
+	___sdt_chk_attr t4 __sdt_chk_##name##_4
+
+/* probe macros, 0-5 arguments
+ *
+ * Each probe emits a NOP (goto +0) in the program stream plus a
+ * .bpf_sdt_notes entry.  The entry begins with the ___sdt_jt_<name>
+ * label so that libbpf can locate each entry boundary at load time.
+ * One BPF_SDT_PROBE<N> invocation per probe name is required; invoking
+ * the same name twice produces a duplicate symbol.
+ */
+#define BPF_SDT_PROBE(name)						\
+({									\
+	asm volatile(							\
+		"0:\n"							\
+		"goto +0\n"						\
+		".pushsection .bpf_sdt_notes,\"\",@progbits\n"		\
+		"___sdt_jt_" #name ":\n"				\
+		".quad 0b\n"						\
+		".popsection\n"						\
+	);								\
+})
+
+#define BPF_SDT_PROBE1(name, a)						\
+({									\
+	_Static_assert(__builtin_types_compatible_p(typeof(a),		\
+			typeof(__sdt_chk_##name##_0)),			\
+			"BPF_SDT_PROBE1: arg0 type mismatch");		\
+	_Static_assert(sizeof(typeof(a)) <= 8,				\
+			"BPF_SDT_PROBE1: arg0 too large, use pointer");	\
+	typeof(a) __a0 = (a);						\
+	asm volatile(							\
+		"0:\n"							\
+		"goto +0\n"						\
+		".pushsection .bpf_sdt_notes,\"\",@progbits\n"		\
+		"___sdt_jt_" #name ":\n"				\
+		".quad 0b\n"						\
+		"r1 = %0\n"						\
+		".popsection\n"						\
+		:: "r"(__a0)						\
+	);								\
+})
+
+#define BPF_SDT_PROBE2(name, a, b)					\
+({									\
+	_Static_assert(__builtin_types_compatible_p(typeof(a),		\
+			typeof(__sdt_chk_##name##_0)),			\
+			"BPF_SDT_PROBE2: arg0 type mismatch");		\
+	_Static_assert(sizeof(typeof(a)) <= 8,				\
+			"BPF_SDT_PROBE2: arg0 too large, use pointer");	\
+	_Static_assert(__builtin_types_compatible_p(typeof(b),		\
+			typeof(__sdt_chk_##name##_1)),			\
+			"BPF_SDT_PROBE2: arg1 type mismatch");		\
+	_Static_assert(sizeof(typeof(b)) <= 8,				\
+			"BPF_SDT_PROBE2: arg1 too large, use pointer");	\
+	typeof(a) __a0 = (a);						\
+	typeof(b) __a1 = (b);						\
+	asm volatile(							\
+		"0:\n"							\
+		"goto +0\n"						\
+		".pushsection .bpf_sdt_notes,\"\",@progbits\n"		\
+		"___sdt_jt_" #name ":\n"				\
+		".quad 0b\n"						\
+		"r1 = %0\n"						\
+		"r2 = %1\n"						\
+		".popsection\n"						\
+		:: "r"(__a0), "r"(__a1)					\
+	);								\
+})
+
+#define BPF_SDT_PROBE3(name, a, b, c)					\
+({									\
+	_Static_assert(__builtin_types_compatible_p(typeof(a),		\
+			typeof(__sdt_chk_##name##_0)),			\
+			"BPF_SDT_PROBE3: arg0 type mismatch");		\
+	_Static_assert(sizeof(typeof(a)) <= 8,				\
+			"BPF_SDT_PROBE3: arg0 too large, use pointer");	\
+	_Static_assert(__builtin_types_compatible_p(typeof(b),		\
+			typeof(__sdt_chk_##name##_1)),			\
+			"BPF_SDT_PROBE3: arg1 type mismatch");		\
+	_Static_assert(sizeof(typeof(b)) <= 8,				\
+			"BPF_SDT_PROBE3: arg1 too large, use pointer");	\
+	_Static_assert(__builtin_types_compatible_p(typeof(c),		\
+			typeof(__sdt_chk_##name##_2)),			\
+			"BPF_SDT_PROBE3: arg2 type mismatch");		\
+	_Static_assert(sizeof(typeof(c)) <= 8,				\
+			"BPF_SDT_PROBE3: arg2 too large, use pointer");	\
+	typeof(a) __a0 = (a);						\
+	typeof(b) __a1 = (b);						\
+	typeof(c) __a2 = (c);						\
+	asm volatile(							\
+		"0:\n"							\
+		"goto +0\n"						\
+		".pushsection .bpf_sdt_notes,\"\",@progbits\n"		\
+		"___sdt_jt_" #name ":\n"				\
+		".quad 0b\n"						\
+		"r1 = %0\n"						\
+		"r2 = %1\n"						\
+		"r3 = %2\n"						\
+		".popsection\n"						\
+		:: "r"(__a0), "r"(__a1), "r"(__a2)			\
+	);								\
+})
+
+#define BPF_SDT_PROBE4(name, a, b, c, d)				\
+({									\
+	_Static_assert(__builtin_types_compatible_p(typeof(a),		\
+			typeof(__sdt_chk_##name##_0)),			\
+			"BPF_SDT_PROBE4: arg0 type mismatch");		\
+	_Static_assert(sizeof(typeof(a)) <= 8,				\
+			"BPF_SDT_PROBE4: arg0 too large, use pointer");	\
+	_Static_assert(__builtin_types_compatible_p(typeof(b),		\
+			typeof(__sdt_chk_##name##_1)),			\
+			"BPF_SDT_PROBE4: arg1 type mismatch");		\
+	_Static_assert(sizeof(typeof(b)) <= 8,				\
+			"BPF_SDT_PROBE4: arg1 too large, use pointer");	\
+	_Static_assert(__builtin_types_compatible_p(typeof(c),		\
+			typeof(__sdt_chk_##name##_2)),			\
+			"BPF_SDT_PROBE4: arg2 type mismatch");		\
+	_Static_assert(sizeof(typeof(c)) <= 8,				\
+			"BPF_SDT_PROBE4: arg2 too large, use pointer");	\
+	_Static_assert(__builtin_types_compatible_p(typeof(d),		\
+			typeof(__sdt_chk_##name##_3)),			\
+			"BPF_SDT_PROBE4: arg3 type mismatch");		\
+	_Static_assert(sizeof(typeof(d)) <= 8,				\
+			"BPF_SDT_PROBE4: arg3 too large, use pointer");	\
+	typeof(a) __a0 = (a);						\
+	typeof(b) __a1 = (b);						\
+	typeof(c) __a2 = (c);						\
+	typeof(d) __a3 = (d);						\
+	asm volatile(							\
+		"0:\n"							\
+		"goto +0\n"						\
+		".pushsection .bpf_sdt_notes,\"\",@progbits\n"		\
+		"___sdt_jt_" #name ":\n"				\
+		".quad 0b\n"						\
+		"r1 = %0\n"						\
+		"r2 = %1\n"						\
+		"r3 = %2\n"						\
+		"r4 = %3\n"						\
+		".popsection\n"						\
+		:: "r"(__a0), "r"(__a1), "r"(__a2), "r"(__a3)		\
+	);								\
+})
+
+#define BPF_SDT_PROBE5(name, a, b, c, d, e)				\
+({									\
+	_Static_assert(__builtin_types_compatible_p(typeof(a),		\
+			typeof(__sdt_chk_##name##_0)),			\
+			"BPF_SDT_PROBE5: arg0 type mismatch");		\
+	_Static_assert(sizeof(typeof(a)) <= 8,				\
+			"BPF_SDT_PROBE5: arg0 too large, use pointer");	\
+	_Static_assert(__builtin_types_compatible_p(typeof(b),		\
+			typeof(__sdt_chk_##name##_1)),			\
+			"BPF_SDT_PROBE5: arg1 type mismatch");		\
+	_Static_assert(sizeof(typeof(b)) <= 8,				\
+			"BPF_SDT_PROBE5: arg1 too large, use pointer");	\
+	_Static_assert(__builtin_types_compatible_p(typeof(c),		\
+			typeof(__sdt_chk_##name##_2)),			\
+			"BPF_SDT_PROBE5: arg2 type mismatch");		\
+	_Static_assert(sizeof(typeof(c)) <= 8,				\
+			"BPF_SDT_PROBE5: arg2 too large, use pointer");	\
+	_Static_assert(__builtin_types_compatible_p(typeof(d),		\
+			typeof(__sdt_chk_##name##_3)),			\
+			"BPF_SDT_PROBE5: arg3 type mismatch");		\
+	_Static_assert(sizeof(typeof(d)) <= 8,				\
+			"BPF_SDT_PROBE5: arg3 too large, use pointer");	\
+	_Static_assert(__builtin_types_compatible_p(typeof(e),		\
+			typeof(__sdt_chk_##name##_4)),			\
+			"BPF_SDT_PROBE5: arg4 type mismatch");		\
+	_Static_assert(sizeof(typeof(e)) <= 8,				\
+			"BPF_SDT_PROBE5: arg4 too large, use pointer");	\
+	typeof(a) __a0 = (a);						\
+	typeof(b) __a1 = (b);						\
+	typeof(c) __a2 = (c);						\
+	typeof(d) __a3 = (d);						\
+	typeof(e) __a4 = (e);						\
+	asm volatile(							\
+		"0:\n"							\
+		"goto +0\n"						\
+		".pushsection .bpf_sdt_notes,\"\",@progbits\n"		\
+		"___sdt_jt_" #name ":\n"				\
+		".quad 0b\n"						\
+		"r1 = %0\n"						\
+		"r2 = %1\n"						\
+		"r3 = %2\n"						\
+		"r4 = %3\n"						\
+		"r5 = %4\n"						\
+		".popsection\n"						\
+		:: "r"(__a0), "r"(__a1), "r"(__a2), "r"(__a3), "r"(__a4)\
+	);								\
+})
+
+#endif /* __BPF_SDT_PROBE_H */
diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
index 7162146280a80e..273991b80f8f79 100644
--- a/tools/lib/bpf/libbpf.c
+++ b/tools/lib/bpf/libbpf.c
@@ -139,6 +139,7 @@ static const char * const attach_type_name[] = {
 	[BPF_TRACE_FENTRY_MULTI]	= "trace_fentry_multi",
 	[BPF_TRACE_FEXIT_MULTI]		= "trace_fexit_multi",
 	[BPF_TRACE_FSESSION_MULTI]	= "trace_fsession_multi",
+	[BPF_TRACE_SDT]			= "trace_sdt",
 };
 
 static const char * const link_type_name[] = {
@@ -158,6 +159,7 @@ static const char * const link_type_name[] = {
 	[BPF_LINK_TYPE_NETKIT]			= "netkit",
 	[BPF_LINK_TYPE_SOCKMAP]			= "sockmap",
 	[BPF_LINK_TYPE_TRACING_MULTI]		= "tracing_multi",
+	[BPF_LINK_TYPE_SDT]			= "sdt",
 };
 
 static const char * const map_type_name[] = {
@@ -494,6 +496,7 @@ struct bpf_program {
 	struct bpf_object *obj;
 
 	int fd;
+	int sdt_map_fd;
 	bool autoload;
 	bool autoattach;
 	bool sym_global;
@@ -506,6 +509,7 @@ struct bpf_program {
 	__u32 attach_btf_obj_fd;
 	__u32 attach_btf_id;
 	__u32 attach_prog_fd;
+	char *sdt_probe_name;
 
 	void *func_info;
 	__u32 func_info_rec_size;
@@ -519,6 +523,9 @@ struct bpf_program {
 
 	struct bpf_light_subprog *subprogs;
 	__u32 subprog_cnt;
+
+	/* index of the main program that absorbed this subprog */
+	int absorbed_by;
 };
 
 struct bpf_struct_ops {
@@ -693,6 +700,7 @@ struct elf_state {
 	bool has_st_ops;
 	int arena_data_shndx;
 	int jumptables_data_shndx;
+	int sdt_notes_shndx;
 };
 
 struct usdt_manager;
@@ -703,6 +711,16 @@ enum bpf_object_state {
 	OBJ_LOADED,
 };
 
+struct sdt_entry {
+	char *name;      /* probe name */
+	__u16 prog_idx;  /* resolved in bpf_object__resolve_sdt_progs */
+	__u16 sec_idx;   /* ELF section index of the probe site (from reloc) */
+	__u64 insn_idx;  /* insn index of probe site within its ELF section */
+	__u8 nargs;      /* how many arguments */
+	__u8 arg_reg[5]; /* which register the argument locates in */
+	__u32 btf_id;    /* BTF FUNC_PROTO type ID from decl tag */
+};
+
 struct bpf_object {
 	char name[BPF_OBJ_NAME_LEN];
 	char license[64];
@@ -768,6 +786,13 @@ struct bpf_object {
 	void *jumptables_data;
 	size_t jumptables_data_sz;
 
+	void *sdt_notes_data;
+	size_t sdt_notes_data_sz;
+
+	struct sdt_entry *sdt_entries;
+	size_t sdt_entry_cnt;
+	size_t sdt_entry_cap;
+
 	struct {
 		struct bpf_program *prog;
 		unsigned int sym_off;
@@ -814,6 +839,7 @@ static void bpf_program__exit(struct bpf_program *prog)
 	zfree(&prog->sec_name);
 	zfree(&prog->insns);
 	zfree(&prog->reloc_desc);
+	zfree(&prog->sdt_probe_name);
 
 	prog->nr_reloc = 0;
 	prog->insns_cnt = 0;
@@ -862,7 +888,9 @@ bpf_object__init_prog(struct bpf_object *obj, struct bpf_program *prog,
 
 	prog->type = BPF_PROG_TYPE_UNSPEC;
 	prog->fd = -1;
+	prog->sdt_map_fd = -1;
 	prog->exception_cb_idx = -1;
+	prog->absorbed_by = -1;
 
 	/* libbpf's convention for SEC("?abc...") is that it's just like
 	 * SEC("abc...") but the corresponding bpf_program starts out with
@@ -3892,6 +3920,260 @@ static int cmp_progs(const void *_a, const void *_b)
 	return a->sec_insn_off < b->sec_insn_off ? -1 : 1;
 }
 
+static struct bpf_program *find_prog_by_sec_insn(const struct bpf_object *obj,
+						 size_t sec_idx, size_t insn_idx);
+
+struct sdt_sym {
+	const char *name;
+	size_t off; /* offset in the sdt entry section */
+};
+
+static int sdt_sym_cmp(const void *a, const void *b)
+{
+	const struct sdt_sym *sym1 = a, *sym2 = b;
+
+	if (sym1->off == sym2->off)
+		return 0;
+
+	return sym1->off < sym2->off ? -1 : 1;
+}
+
+static Elf_Data *sdt_find_relo(struct bpf_object *obj)
+{
+	GElf_Shdr shdr;
+	Elf_Scn *scn = NULL;
+
+	while ((scn = elf_nextscn(obj->efile.elf, scn)) != NULL) {
+		if (!gelf_getshdr(scn, &shdr) || shdr.sh_type != SHT_REL)
+			continue;
+
+		if (shdr.sh_info == obj->efile.sdt_notes_shndx)
+			return elf_getdata(scn, NULL);
+	}
+
+	return NULL;
+}
+
+static int sdt_collect_syms(struct bpf_object *obj, struct sdt_sym **sdt_syms)
+{
+	int i, err, cnt = 0;
+	size_t nr_syms;
+	size_t sym_cap = 0;
+	const char *name;
+	Elf64_Sym *sym, *syms;
+	struct sdt_sym *ssyms = NULL;
+
+	syms = obj->efile.symbols->d_buf;
+	nr_syms = obj->efile.symbols->d_size / sizeof(Elf64_Sym);
+
+	for (i = 0; i < nr_syms; i++) {
+		sym = &syms[i];
+		if (sym->st_shndx != obj->efile.sdt_notes_shndx)
+			continue;
+
+		if (ELF64_ST_TYPE(sym->st_info) != STT_NOTYPE)
+			continue;
+
+		name = elf_sym_str(obj, sym->st_name);
+		if (!name || strncmp(name, "___sdt_jt_", 10))
+			continue;
+
+		err = libbpf_ensure_mem((void **)&ssyms, &sym_cap, sizeof(*ssyms), cnt + 1);
+		if (err) {
+			free(ssyms);
+			return -ENOMEM;
+		}
+
+		ssyms[cnt].name = name;
+		ssyms[cnt].off = sym->st_value;
+		cnt++;
+	}
+
+	if (cnt)
+		qsort(ssyms, cnt, sizeof(*ssyms), sdt_sym_cmp);
+
+	*sdt_syms = ssyms;
+
+	return cnt;
+}
+
+/*
+ * Layout of SDT entry:
+ *
+ *   off + 0:  64 bits of the NOP instruction offset
+ *   off + 8:  r1 = %[arg0_reg]  //  bpf move instruction for arg0
+ *   off + 16: r2 = %[arg1_reg]  //  bpf move instruction for arg1
+ *       ...
+ *   off + 8 * N: rN = %[argN_reg]  //  bpf move instruction for argN
+ *
+ *   next_off + 0: // start of the next entry
+ *
+ *   so argument number N = (next_entry_off - off - 8) / sizeof(bpf_insn).
+ */
+static int sdt_calc_nargs(struct sdt_sym *syms, int sym_idx, size_t sym_cnt, size_t last_off)
+{
+	size_t off = syms[sym_idx].off;
+	size_t next_off = (sym_idx + 1 < sym_cnt) ? syms[sym_idx + 1].off : last_off;
+
+	if (next_off > last_off)
+		return -EINVAL;
+
+	if (off + 8 > next_off)
+		return -EINVAL;
+
+	if ((next_off - off - 8) % sizeof(struct bpf_insn))
+		return -EINVAL;
+
+	if ((next_off - off - 8) / sizeof(struct bpf_insn) > 5)
+		return -EINVAL;
+
+	return (next_off - off - 8) / sizeof(struct bpf_insn);
+}
+
+/*
+ * Look up the ELF section index of the code section containing the probe
+ * site, via the R_BPF_64_ABS64 relocation on this entry's .quad.  The
+ * target symbol's st_shndx is the code section; this is stored in the
+ * sdt_entry and used later (after subprogram linking) to resolve prog_idx.
+ *
+ * Returns the section index on success, -1 on failure.
+ */
+static int sdt_find_sec_idx(struct bpf_object *obj, Elf_Data *sdt_relo,
+			    size_t sdt_entry_off)
+{
+	int i;
+	Elf64_Sym *tgt;
+	Elf64_Rel *rel = sdt_relo->d_buf;
+	Elf64_Sym *elf_syms = obj->efile.symbols->d_buf;
+
+	for (i = 0; i < sdt_relo->d_size / sizeof(Elf64_Rel); i++, rel++) {
+		if (rel->r_offset != sdt_entry_off)
+			continue;
+		if (ELF64_R_TYPE(rel->r_info) != R_BPF_64_ABS64)
+			continue;
+
+		tgt = &elf_syms[ELF64_R_SYM(rel->r_info)];
+		return tgt->st_shndx;
+	}
+
+	return -1;
+}
+
+static int sdt_parse_arg(const void *data, int nargs, struct sdt_entry *e)
+{
+	int i;
+	const struct bpf_insn *insn;
+
+	insn = (const struct bpf_insn *)data;
+	for (i = 0; i < nargs; i++, insn++) {
+		if (insn->code != (BPF_ALU64 | BPF_MOV | BPF_X))
+			return -EINVAL;
+		e->arg_reg[i] = insn->src_reg;
+	}
+
+	return 0;
+}
+
+
+/*
+ * Parse .bpf_sdt_notes into per-probe SDT entries.
+ *
+ * Each entry starts at a ___sdt_jt_<name> label and contains .quad nop_off
+ * (8 bytes, with R_BPF_64_ABS64 reloc) followed by one 8-byte BPF_MOV
+ * instruction per argument whose src_reg field encodes the register.
+ *
+ * nargs is derived from the gap between consecutive labels: the assembler
+ * lays entries out sequentially, so nargs = (next_off - off - 8) / 8.
+ */
+static int bpf_object__collect_sdt_notes(struct bpf_object *obj)
+{
+	int i, sym_cnt, err = 0;
+	struct sdt_sym *sdt_syms = NULL;
+	Elf_Data *sdt_relo = NULL;
+	const void *data = obj->sdt_notes_data;
+
+	if (!obj->sdt_notes_data || !obj->sdt_notes_data_sz)
+		return 0;
+
+	sdt_relo = sdt_find_relo(obj);
+	if (!sdt_relo)
+		return -EINVAL;
+
+	sym_cnt = sdt_collect_syms(obj, &sdt_syms);
+	if (sym_cnt < 0)
+		return sym_cnt;
+
+	for (i = 0; i < sym_cnt; i++) {
+		__u64 nop_idx;
+		int nargs, sec_idx;
+		struct sdt_entry *e;
+		size_t off = sdt_syms[i].off;
+
+		nargs = sdt_calc_nargs(sdt_syms, i, sym_cnt, obj->sdt_notes_data_sz);
+		if (nargs < 0) {
+			err = -EINVAL;
+			goto out;
+		}
+
+		memcpy(&nop_idx, data + off, sizeof(__u64));
+		nop_idx = nop_idx / sizeof(struct bpf_insn);
+
+		/*
+		 * Record the ELF section index of the code section containing
+		 * the probe site.  prog_idx is resolved later in
+		 * bpf_object__resolve_sdt_progs(), after static subprograms
+		 * in .text have been absorbed into their calling main program;
+		 * resolving here would return the subprogram's own entry, not
+		 * the main program that ends up owning the probe site.
+		 */
+		sec_idx = sdt_find_sec_idx(obj, sdt_relo, off);
+		if (sec_idx < 0) {
+			err = -EINVAL;
+			goto out;
+		}
+
+		/* Read arg_reg[] from cold instructions */
+		err = libbpf_ensure_mem((void **)&obj->sdt_entries, &obj->sdt_entry_cap,
+					sizeof(*obj->sdt_entries), i + 1);
+		if (err) {
+			err = -ENOMEM;
+			goto out;
+		}
+
+		e = &obj->sdt_entries[i];
+		err = sdt_parse_arg(data + off + 8, nargs, e);
+		if (err < 0)
+			goto out;
+
+		e->name = strdup(sdt_syms[i].name + 10); /* skip "___sdt_jt_" */
+		if (!e->name) {
+			err = -ENOMEM;
+			goto out;
+		}
+		if (strlen(e->name) >= BPF_SDT_MAX_NAME_LEN) {
+			pr_warn("sdt: probe name '%s' too long (max %d chars)\n",
+				e->name, BPF_SDT_MAX_NAME_LEN - 1);
+			err = -EINVAL;
+			goto out;
+		}
+		e->sec_idx = sec_idx;
+		e->insn_idx = nop_idx;
+		e->nargs = nargs;
+		obj->sdt_entry_cnt++;
+	}
+
+out:
+	if (err) {
+		for (i = 0; i < obj->sdt_entry_cnt; i++)
+			free(obj->sdt_entries[i].name);
+		zfree(&obj->sdt_entries);
+		obj->sdt_entry_cnt = obj->sdt_entry_cap = 0;
+	}
+
+	free(sdt_syms);
+	return err;
+}
+
 static int bpf_object__elf_collect(struct bpf_object *obj)
 {
 	struct elf_sec_desc *sec_desc;
@@ -4034,6 +4316,13 @@ static int bpf_object__elf_collect(struct bpf_object *obj)
 				memcpy(obj->jumptables_data, data->d_buf, data->d_size);
 				obj->jumptables_data_sz = data->d_size;
 				obj->efile.jumptables_data_shndx = idx;
+			} else if (strcmp(name, SDT_NOTES_SEC) == 0) {
+				obj->sdt_notes_data = malloc(data->d_size);
+				if (!obj->sdt_notes_data)
+					return -ENOMEM;
+				memcpy(obj->sdt_notes_data, data->d_buf, data->d_size);
+				obj->sdt_notes_data_sz = data->d_size;
+				obj->efile.sdt_notes_shndx = idx;
 			} else {
 				pr_info("elf: skipping unrecognized data section(%d) %s\n",
 					idx, name);
@@ -4087,6 +4376,13 @@ static int bpf_object__elf_collect(struct bpf_object *obj)
 	if (obj->nr_programs)
 		qsort(obj->programs, obj->nr_programs, sizeof(*obj->programs), cmp_progs);
 
+	err = bpf_object__collect_sdt_notes(obj);
+	if (err) {
+		zfree(&obj->sdt_notes_data);
+		obj->sdt_notes_data_sz = 0;
+		return err;
+	}
+
 	return bpf_object__init_btf(obj, btf_data, btf_ext_data);
 }
 
@@ -6407,6 +6703,186 @@ static int create_jt_map(struct bpf_object *obj, struct bpf_program *prog, struc
 	return err;
 }
 
+static void sdt_collect_btf_ids(struct bpf_object *obj)
+{
+	int n = btf__type_cnt(obj->btf);
+	const struct btf_type *t;
+	int i;
+
+	for (i = 1; i < n; i++) {
+		const char *tag, *colon;
+		size_t name_len;
+		__u32 func_proto_id;
+		int j;
+
+		t = btf_type_by_id(obj->btf, i);
+		if (!btf_is_decl_tag(t) || btf_decl_tag(t)->component_idx != -1)
+			continue;
+
+		tag = btf__str_by_offset(obj->btf, t->name_off);
+		if (strncmp(tag, "bpf_sdt:", 8) != 0)
+			continue;
+
+		/* tag format: "bpf_sdt:<name>:<nargs>"; <name> has no colons */
+		colon = strchr(tag + 8, ':');
+		if (!colon)
+			continue;
+		name_len = colon - tag - 8;
+
+		/* type chain: DECL_TAG -> VAR -> (CONST) -> PTR -> FUNC_PROTO */
+		t = btf_type_by_id(obj->btf, t->type);
+		if (!btf_is_var(t))
+			continue;
+		t = btf_type_by_id(obj->btf, t->type);
+		if (btf_kind(t) == BTF_KIND_CONST)
+			t = btf_type_by_id(obj->btf, t->type);
+		if (!btf_is_ptr(t))
+			continue;
+		func_proto_id = t->type;
+		t = btf_type_by_id(obj->btf, func_proto_id);
+		if (!btf_is_func_proto(t))
+			continue;
+
+		/* match by name (tag format: "bpf_sdt:<name>:<nargs>") */
+		for (j = 0; j < obj->sdt_entry_cnt; j++) {
+			struct sdt_entry *e = &obj->sdt_entries[j];
+
+			if (!e->btf_id && strlen(e->name) == name_len &&
+			    strncmp(tag + 8, e->name, name_len) == 0)
+				e->btf_id = func_proto_id;
+		}
+	}
+}
+
+static int bpf_object__resolve_sdt_progs(struct bpf_object *obj)
+{
+	int i, j;
+	struct bpf_light_subprog *sp;
+
+	for (i = 0; i < obj->sdt_entry_cnt; i++) {
+		struct sdt_entry *e = &obj->sdt_entries[i];
+		struct bpf_program *prog, *subprog;
+
+		prog = find_prog_by_sec_insn(obj, e->sec_idx, e->insn_idx);
+		if (!prog) {
+			pr_warn("sdt: probe '%s' at sec %u insn %llu not found in any program\n",
+				e->name, (unsigned)e->sec_idx, (unsigned long long)e->insn_idx);
+			return -EINVAL;
+		}
+
+		/* resolve subprog probe in the main prog that absorbed the subprog */
+		if (prog_is_subprog(obj, prog) && prog->absorbed_by != -1) {
+			subprog = prog;
+			prog = &obj->programs[prog->absorbed_by];
+			for (j = 0; j < prog->subprog_cnt; j++) {
+				sp = &prog->subprogs[j];
+				if (sp->sec_insn_off == subprog->sec_insn_off) {
+					e->insn_idx = sp->sub_insn_off +
+						       (e->insn_idx - sp->sec_insn_off);
+					break;
+				}
+			}
+			if (j >= prog->subprog_cnt) {
+				pr_warn("sdt: subprog probe '%s' not found\n", e->name);
+				return -EINVAL;
+			}
+		}
+
+		e->prog_idx = prog - obj->programs;
+	}
+	return 0;
+}
+
+static int bpf_object__create_sdt_maps(struct bpf_object *obj)
+{
+	const __u32 value_size = sizeof(struct bpf_insn_array_value);
+	struct bpf_insn_array_value val = {};
+	struct bpf_program *prog;
+	struct {
+		__u32 sdt_cnt;
+		__u32 next_key;
+	} *prog_sdt;
+	int i, err = 0;
+
+	if (!obj->sdt_entry_cnt)
+		return 0;
+
+	err = bpf_object__resolve_sdt_progs(obj);
+	if (err)
+		return err;
+
+	sdt_collect_btf_ids(obj);
+
+	prog_sdt = calloc(obj->nr_programs, sizeof(*prog_sdt));
+	if (!prog_sdt)
+		return -ENOMEM;
+
+	/* count entries per program */
+	for (i = 0; i < obj->sdt_entry_cnt; i++)
+		prog_sdt[obj->sdt_entries[i].prog_idx].sdt_cnt++;
+
+	/* create insn_array maps per program and populate entries */
+	for (i = 0; i < obj->sdt_entry_cnt; i++) {
+		__u32 key;
+		struct sdt_entry *e = &obj->sdt_entries[i];
+		__u32 sdt_cnt = prog_sdt[e->prog_idx].sdt_cnt;
+
+		if (!sdt_cnt)
+			continue;
+
+		prog = &obj->programs[e->prog_idx];
+
+		if (prog->sdt_map_fd < 0) {
+			int map_fd;
+			LIBBPF_OPTS(bpf_map_create_opts, map_opts);
+
+			map_opts.map_flags = BPF_F_INSN_ARRAY_SDT;
+			map_fd = bpf_map_create(BPF_MAP_TYPE_INSN_ARRAY, ".bpf_sdt_notes",
+						sizeof(key), value_size, sdt_cnt, &map_opts);
+			if (map_fd < 0) {
+				err = map_fd;
+				goto out_free;
+			}
+			prog->sdt_map_fd = map_fd;
+		}
+
+		key = prog_sdt[e->prog_idx].next_key++;
+
+		memset(&val, 0, sizeof(val));
+		val.btf_id = e->btf_id;
+		val.nargs = e->nargs;
+		val.orig_off = e->insn_idx - prog->sec_insn_off;
+		memcpy(val.arg_reg, e->arg_reg, sizeof(val.arg_reg));
+		strncpy(val.name, e->name, sizeof(val.name) - 1);
+
+		err = bpf_map_update_elem(prog->sdt_map_fd, &key, &val, 0);
+		if (err)
+			goto out_free;
+	}
+
+	/* freeze maps */
+	for (i = 0; i < obj->nr_programs; i++) {
+		prog = &obj->programs[i];
+		if (prog->sdt_map_fd >= 0) {
+			err = bpf_map_freeze(prog->sdt_map_fd);
+			if (err)
+				goto out_free;
+		}
+	}
+
+out_free:
+	free(prog_sdt);
+	if (err) {
+		for (i = 0; i < obj->nr_programs; i++) {
+			if (obj->programs[i].sdt_map_fd >= 0) {
+				close(obj->programs[i].sdt_map_fd);
+				obj->programs[i].sdt_map_fd = -1;
+			}
+		}
+	}
+	return err;
+}
+
 /* Relocate data references within program code:
  *  - map references;
  *  - global variable references;
@@ -6856,6 +7332,8 @@ bpf_object__reloc_code(struct bpf_object *obj, struct bpf_program *main_prog,
 			err = bpf_object__append_subprog_code(obj, main_prog, subprog);
 			if (err)
 				return err;
+			if (subprog->absorbed_by == -1)
+				subprog->absorbed_by = main_prog - obj->programs;
 			err = bpf_object__reloc_code(obj, main_prog, subprog);
 			if (err)
 				return err;
@@ -7975,6 +8453,12 @@ static int bpf_object_load_prog(struct bpf_object *obj, struct bpf_program *prog
 	load_attr.log_level = log_level;
 	load_attr.prog_flags = prog->prog_flags;
 	load_attr.fd_array = obj->fd_array;
+	if (prog->sdt_map_fd >= 0)
+		load_attr.sdt_map_fd = prog->sdt_map_fd;
+	if (prog->expected_attach_type == BPF_TRACE_SDT && prog->sdt_probe_name) {
+		load_attr.sdt.target_prog_fd = prog->attach_prog_fd;
+		load_attr.sdt.name = prog->sdt_probe_name;
+	}
 
 	load_attr.token_fd = obj->token_fd;
 	if (obj->token_fd)
@@ -9064,6 +9548,7 @@ static int bpf_object_prepare(struct bpf_object *obj, const char *target_btf_pat
 	err = err ? : bpf_object__relocate(obj, obj->btf_custom_path ? : target_btf_path);
 	err = err ? : bpf_object__sanitize_and_load_btf(obj);
 	err = err ? : bpf_object__create_maps(obj);
+	err = err ? : bpf_object__create_sdt_maps(obj);
 	err = err ? : bpf_object_prepare_progs(obj);
 
 	if (err) {
@@ -9614,8 +10099,11 @@ void bpf_object__close(struct bpf_object *obj)
 	obj->nr_maps = 0;
 
 	if (obj->programs && obj->nr_programs) {
-		for (i = 0; i < obj->nr_programs; i++)
+		for (i = 0; i < obj->nr_programs; i++) {
+			if (obj->programs[i].sdt_map_fd >= 0)
+				close(obj->programs[i].sdt_map_fd);
 			bpf_program__exit(&obj->programs[i]);
+		}
 	}
 	zfree(&obj->programs);
 
@@ -9629,6 +10117,17 @@ void bpf_object__close(struct bpf_object *obj)
 	zfree(&obj->jumptables_data);
 	obj->jumptables_data_sz = 0;
 
+	zfree(&obj->sdt_notes_data);
+	obj->sdt_notes_data_sz = 0;
+
+	if (obj->sdt_entries) {
+		for (i = 0; i < obj->sdt_entry_cnt; i++)
+			free(obj->sdt_entries[i].name);
+		zfree(&obj->sdt_entries);
+		obj->sdt_entry_cnt = 0;
+		obj->sdt_entry_cap = 0;
+	}
+
 	for (i = 0; i < obj->jumptable_map_cnt; i++)
 		close(obj->jumptable_maps[i].fd);
 	zfree(&obj->jumptable_maps);
@@ -10096,6 +10595,7 @@ static const struct bpf_sec_def section_defs[] = {
 	SEC_DEF("kretsyscall+",		KPROBE, 0, SEC_NONE, attach_ksyscall),
 	SEC_DEF("usdt+",		KPROBE,	0, SEC_USDT, attach_usdt),
 	SEC_DEF("usdt.s+",		KPROBE,	0, SEC_USDT | SEC_SLEEPABLE, attach_usdt),
+	SEC_DEF("bpf_sdt",		TRACING, BPF_TRACE_SDT, SEC_NONE),
 	SEC_DEF("tc/ingress",		SCHED_CLS, BPF_TCX_INGRESS, SEC_NONE), /* alias for tcx */
 	SEC_DEF("tc/egress",		SCHED_CLS, BPF_TCX_EGRESS, SEC_NONE),  /* alias for tcx */
 	SEC_DEF("tcx/ingress",		SCHED_CLS, BPF_TCX_INGRESS, SEC_NONE),
@@ -14712,6 +15212,17 @@ int bpf_program__set_attach_target(struct bpf_program *prog,
 	if (prog->obj->state >= OBJ_LOADED)
 		return libbpf_err(-EINVAL);
 
+	if (prog->expected_attach_type == BPF_TRACE_SDT) {
+		if (!attach_func_name)
+			return libbpf_err(-EINVAL);
+		free(prog->sdt_probe_name);
+		prog->sdt_probe_name = strdup(attach_func_name);
+		if (!prog->sdt_probe_name)
+			return libbpf_err(-ENOMEM);
+		prog->attach_prog_fd = attach_prog_fd;
+		return 0;
+	}
+
 	if (attach_prog_fd && !attach_func_name) {
 		/* Store attach_prog_fd. The BTF ID will be resolved later during
 		 * the normal object/program load phase.
diff --git a/tools/lib/bpf/libbpf_internal.h b/tools/lib/bpf/libbpf_internal.h
index 04cd303fb5a879..ed7a587a61ca4e 100644
--- a/tools/lib/bpf/libbpf_internal.h
+++ b/tools/lib/bpf/libbpf_internal.h
@@ -75,6 +75,7 @@
 #endif
 
 #define JUMPTABLES_SEC ".jumptables"
+#define SDT_NOTES_SEC ".bpf_sdt_notes"
 
 #define BTF_INFO_ENC(kind, kind_flag, vlen) \
 	((!!(kind_flag) << 31) | ((kind) << 24) | ((vlen) & BTF_MAX_VLEN))
diff --git a/tools/lib/bpf/linker.c b/tools/lib/bpf/linker.c
index 78f92c39290af5..3a72276244c725 100644
--- a/tools/lib/bpf/linker.c
+++ b/tools/lib/bpf/linker.c
@@ -2028,6 +2028,8 @@ static int linker_append_elf_sym(struct bpf_linker *linker, struct src_obj *obj,
 
 		if (strcmp(src_sec->sec_name, JUMPTABLES_SEC) == 0)
 			goto add_sym;
+		if (strcmp(src_sec->sec_name, SDT_NOTES_SEC) == 0)
+			goto add_sym;
 	}
 
 	if (sym_bind == STB_LOCAL)
@@ -2274,6 +2276,13 @@ static int linker_append_elf_relos(struct bpf_linker *linker, struct src_obj *ob
 						insn->imm += sec->dst_off / sizeof(struct bpf_insn);
 					else
 						insn->imm += sec->dst_off;
+				} else if (strcmp(src_linked_sec->sec_name, SDT_NOTES_SEC) == 0) {
+					/* .bpf_sdt_notes contains R_BPF_64_ABS64 relocations
+					 * referencing code section symbols. Adjust the 64-bit
+					 * address by dst_off.
+					 */
+					__u64 *addr = dst_linked_sec->raw_data + dst_rel->r_offset;
+					*addr += sec->dst_off;
 				} else {
 					pr_warn("relocation against STT_SECTION in non-exec section is not supported!\n");
 					return -EINVAL;
diff --git a/tools/testing/selftests/bpf/prog_tests/test_bpf_sdt.c b/tools/testing/selftests/bpf/prog_tests/test_bpf_sdt.c
new file mode 100644
index 00000000000000..b2752e7b2b0925
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/test_bpf_sdt.c
@@ -0,0 +1,151 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <test_progs.h>
+#include <bpf/bpf.h>
+#include "bpf_sdt_observer.skel.h"
+#include "bpf_sdt_target.skel.h"
+
+static int sdt_attach(struct bpf_program *obs)
+{
+	LIBBPF_OPTS(bpf_link_create_opts, opts);
+
+	return bpf_link_create(bpf_program__fd(obs), 0, BPF_TRACE_SDT, &opts);
+}
+
+static void read_args(void)
+{
+	LIBBPF_OPTS(bpf_test_run_opts, topts);
+	struct bpf_sdt_target *tgt_skel;
+	struct bpf_sdt_observer *skel;
+	char pkt[64] = {};
+	int link, err;
+
+	tgt_skel = bpf_sdt_target__open_and_load();
+	if (!ASSERT_OK_PTR(tgt_skel, "target open_and_load"))
+		return;
+
+	skel = bpf_sdt_observer__open();
+	if (!ASSERT_OK_PTR(skel, "observer open"))
+		goto out_tgt;
+
+	bpf_program__set_autoload(skel->progs.tc_trace_prog, false);
+	bpf_program__set_autoload(skel->progs.subprog_trace_prog, false);
+	bpf_program__set_attach_target(skel->progs.xdp_trace_prog,
+				       bpf_program__fd(tgt_skel->progs.xdp_prog),
+				       "xdp_probe_len_ret");
+	err = bpf_sdt_observer__load(skel);
+	if (!ASSERT_OK(err, "observer load"))
+		goto out_obs;
+
+	link = sdt_attach(skel->progs.xdp_trace_prog);
+	if (!ASSERT_GE(link, 0, "attach xdp_probe_len_ret"))
+		goto out_obs;
+
+	topts.data_in = pkt;
+	topts.data_size_in = sizeof(pkt);
+	topts.data_size_out = sizeof(pkt);
+	err = bpf_prog_test_run_opts(bpf_program__fd(tgt_skel->progs.xdp_prog), &topts);
+	if (!ASSERT_OK(err, "prog_test_run"))
+		goto out_link;
+
+	ASSERT_EQ(skel->bss->xdp_len, sizeof(pkt), "xdp_len");
+	ASSERT_EQ(skel->bss->xdp_ret, XDP_PASS, "xdp_ret");
+
+out_link:
+	close(link);
+out_obs:
+	bpf_sdt_observer__destroy(skel);
+out_tgt:
+	bpf_sdt_target__destroy(tgt_skel);
+}
+
+static void multi_prog(void)
+{
+	struct bpf_sdt_target *tgt_skel;
+	struct bpf_sdt_observer *skel;
+	int link1, link2, err;
+
+	tgt_skel = bpf_sdt_target__open_and_load();
+	if (!ASSERT_OK_PTR(tgt_skel, "target open_and_load"))
+		return;
+
+	skel = bpf_sdt_observer__open();
+	if (!ASSERT_OK_PTR(skel, "observer open"))
+		goto out_tgt;
+
+	bpf_program__set_autoload(skel->progs.subprog_trace_prog, false);
+	bpf_program__set_attach_target(skel->progs.xdp_trace_prog,
+				       bpf_program__fd(tgt_skel->progs.xdp_prog),
+				       "xdp_probe_len_ret");
+	bpf_program__set_attach_target(skel->progs.tc_trace_prog,
+				       bpf_program__fd(tgt_skel->progs.tc_prog),
+				       "tc_probe");
+	err = bpf_sdt_observer__load(skel);
+	if (!ASSERT_OK(err, "observer load"))
+		goto out_obs;
+
+	link1 = sdt_attach(skel->progs.xdp_trace_prog);
+	ASSERT_GE(link1, 0, "attach xdp xdp_probe_len_ret");
+
+	link2 = sdt_attach(skel->progs.tc_trace_prog);
+	ASSERT_GE(link2, 0, "attach tc_probe");
+
+	close(link1);
+	close(link2);
+out_obs:
+	bpf_sdt_observer__destroy(skel);
+out_tgt:
+	bpf_sdt_target__destroy(tgt_skel);
+}
+
+static void subprog_probe(void)
+{
+	LIBBPF_OPTS(bpf_test_run_opts, topts);
+	struct bpf_sdt_target *tgt_skel;
+	struct bpf_sdt_observer *skel;
+	char pkt[64] = {0, 1, 2, 3};
+	int link, err;
+
+	tgt_skel = bpf_sdt_target__open_and_load();
+	if (!ASSERT_OK_PTR(tgt_skel, "target open_and_load"))
+		return;
+
+	skel = bpf_sdt_observer__open();
+	if (!ASSERT_OK_PTR(skel, "observer open"))
+		goto out_tgt;
+
+	bpf_program__set_autoload(skel->progs.tc_trace_prog, false);
+	bpf_program__set_autoload(skel->progs.xdp_trace_prog, false);
+	bpf_program__set_attach_target(skel->progs.subprog_trace_prog,
+				       bpf_program__fd(tgt_skel->progs.xdp_prog),
+				       "xdp_probe_ctx");
+	err = bpf_sdt_observer__load(skel);
+	if (!ASSERT_OK(err, "observer load"))
+		goto out_obs;
+
+	link = sdt_attach(skel->progs.subprog_trace_prog);
+	if (!ASSERT_GE(link, 0, "attach xdp_probe_ctx"))
+		goto out_obs;
+
+	topts.data_in = pkt;
+	topts.data_size_in = sizeof(pkt);
+	topts.data_size_out = sizeof(pkt);
+	err = bpf_prog_test_run_opts(bpf_program__fd(tgt_skel->progs.xdp_prog), &topts);
+	ASSERT_OK(err, "prog_test_run");
+	ASSERT_EQ(skel->bss->xdp_len, sizeof(pkt), "xdp_len");
+
+	close(link);
+out_obs:
+	bpf_sdt_observer__destroy(skel);
+out_tgt:
+	bpf_sdt_target__destroy(tgt_skel);
+}
+
+void test_bpf_sdt(void)
+{
+	if (test__start_subtest("read_args"))
+		read_args();
+	if (test__start_subtest("multi_prog"))
+		multi_prog();
+	if (test__start_subtest("subprog_probe"))
+		subprog_probe();
+}
diff --git a/tools/testing/selftests/bpf/progs/bpf_sdt_observer.c b/tools/testing/selftests/bpf/progs/bpf_sdt_observer.c
new file mode 100644
index 00000000000000..ec059e86b15465
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/bpf_sdt_observer.c
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+volatile int xdp_len;
+volatile int xdp_ret;
+
+SEC("bpf_sdt")
+int BPF_PROG(tc_trace_prog)
+{
+	return 0;
+}
+
+SEC("bpf_sdt")
+int BPF_PROG(xdp_trace_prog, int len, int ret)
+{
+	xdp_len = len;
+	xdp_ret = ret;
+	return 0;
+}
+
+SEC("bpf_sdt")
+int BPF_PROG(subprog_trace_prog, struct xdp_md *xdp_ctx)
+{
+	xdp_len = bpf_xdp_get_buff_len((struct xdp_md *)xdp_ctx);
+	return 0;
+}
+
+char LICENSE[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/bpf_sdt_target.c b/tools/testing/selftests/bpf/progs/bpf_sdt_target.c
new file mode 100644
index 00000000000000..e1c14b9d7e9a28
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/bpf_sdt_target.c
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "../../../../tools/lib/bpf/bpf_sdt.h"
+
+BPF_SDT_DECLARE(tc_probe);
+BPF_SDT_DECLARE1(xdp_probe_ctx, struct xdp_md *);
+BPF_SDT_DECLARE2(xdp_probe_len_ret, int, int);
+
+static __noinline int xdp_process(struct xdp_md *ctx)
+{
+	BPF_SDT_PROBE1(xdp_probe_ctx, ctx);
+	return XDP_PASS;
+}
+
+SEC("xdp")
+int xdp_prog(struct xdp_md *ctx)
+{
+	int len = (int)(ctx->data_end - ctx->data);
+
+	BPF_SDT_PROBE2(xdp_probe_len_ret, len, XDP_PASS);
+
+	return xdp_process(ctx);
+}
+
+SEC("tc")
+int tc_prog(struct __sk_buff *skb)
+{
+	BPF_SDT_PROBE(tc_probe);
+	return 0;
+}
+
+char LICENSE[] SEC("license") = "GPL";

3/2 2026/06/27 15:13 llm patch-evaluator
1m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 35850
							output: 23
							thoughts: 669
						

Thoughts:
**BPF SDT Probe Implementation Thoughts**

Okay, so I'm looking at this patch series, and it's pretty clear: we're adding support for BPF Statically Defined Tracing (SDT) probes. This is a substantial functional enhancement to the BPF infrastructure, a core piece of the tracing and instrumentation capabilities.

Essentially, it looks like a whole suite of changes are required. The changes encompass the JIT compiler, the kernel BPF core, and the user-space libraries. I see modifications to `arch/x86/net/bpf_jit_comp.c`, which is the JIT compiler, specifically to handle the insertion of NOPs, and argument handling for SDT probes. On top of that, new types, flags, and fields have been added in a number of the header files, including `bpf.h`, `bpf_types.h`, `bpf_verifier.h`, `filter.h`, and the UAPI headers. The `bpf_insn_array.c` file is being updated to support SDT subtypes, and a raft of other BPF core files (`cfg.c`, `core.c`, `fixups.c`, `liveness.c`, `syscall.c`, `trampoline.c`, `verifier.c`) have been modified to incorporate the verification, loading, and attachment mechanisms for these SDT probes. We have corresponding changes in `tools/lib/bpf/*` and `tools/testing/selftests/bpf/*` to ensure proper user-space interaction and testing.

A new attach type, `BPF_TRACE_SDT`, a new link type, `BPF_LINK_TYPE_SDT`, and a new map flag, `BPF_F_INSN_ARRAY_SDT`, are included. That indicates the breadth of this change.

Now, let me quickly review the config requirements. Obviously, `CONFIG_BPF_SYSCALL` will be needed for the BPF subsystem to even be enabled. Since the JIT compiler is getting touched (`arch/x86/net/bpf_jit_comp.c`), `CONFIG_BPF_JIT` is also a given. I don't see any new specific Kconfig options introduced here, which is interesting. It seems like the feature is conditionally enabled through the use of `bpf_jit_supports_sdt_probe()`. That's likely x86 specific for now.

Let's quickly identify the key symbols in this patch. I see new functions like `bpf_insn_array_get_sdt_probe_by_name`, `bpf_sdt_link_attach`, `check_sdt_probe`, and `bpf_jit_supports_sdt_probe`. Also, a number of the existing BPF infrastructure is being modified: `bpf_insn_array_init`, `bpf_prog_load`, `bpf_trampoline_update`, and `compute_insn_live_regs`.

I'll do a quick `grep` for `bpf_sdt_link_attach` and `check_sdt_probe` to get a better sense of how those functions fit into the overall picture. This looks like a big change, so let me delve further into the code...



4/2 2026/06/27 15:14 tool grepper
0m Args:
{
  "Expression": "bpf_sdt_link_attach"
}

Results:
{
  "Output": "kernel/bpf/syscall.c=3763=static const struct bpf_link_ops bpf_sdt_link_lops = {\n--\nkernel/bpf/syscall.c-3767-\nkernel/bpf/syscall.c:3768:static int bpf_sdt_link_attach(struct bpf_prog *prog)\nkernel/bpf/syscall.c-3769-{\n--\nkernel/bpf/syscall.c=5979=static int link_create(union bpf_attr *attr, bpfptr_t uattr)\n--\nkernel/bpf/syscall.c-6031-\t\telse if (prog-\u003eexpected_attach_type == BPF_TRACE_SDT)\nkernel/bpf/syscall.c:6032:\t\t\tret = bpf_sdt_link_attach(prog);\nkernel/bpf/syscall.c-6033-\t\telse\n"
}

5/2 2026/06/27 15:14 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 37495
							output: 20
							thoughts: 12
						

Thoughts:
Okay, here's my interpretation of that thought process, assuming I'm an expert in the field and considering how I'd approach it:

**Alright, Time to Get to Work**

Right, let's dive in. The task at hand likely requires verifying something related to the `check_sdt_probe` function. "Check" implies a thorough evaluation or validation. "SDT" probably stands for something specific within my domain, perhaps an acronym for a subsystem, data type, or testing methodology. And "probe" suggests we are investigating. My immediate reaction is to start by recalling all the relevant details I know about `check_sdt_probe`. My knowledge of the code base and the underlying principles will be vital. What exactly is this probe designed to do? What are its inputs and outputs? Does it perform any specific checks on data, configurations or device state? Is there recent work or documentation I need to reference? My next step is to mentally compile all the scenarios where this function could be used, or the problems it could solve, and decide on a strategy to ensure this code is performing its job accurately and reliably. I’ll start with the likely edge cases and potential failure modes. I’ll make a list of tests I will need to develop, or verify are already in place.



6/2 2026/06/27 15:15 tool grepper
0m Args:
{
  "Expression": "check_sdt_probe"
}

Results:
{
  "Output": "kernel/bpf/liveness.c=2063=static void compute_insn_live_regs(struct bpf_verifier_env *env,\n--\nkernel/bpf/liveness.c-2175-\t\t\t\t * as live, the liveness pass would clear them before\nkernel/bpf/liveness.c:2176:\t\t\t\t * the probe site, causing check_sdt_probe() to reject\nkernel/bpf/liveness.c-2177-\t\t\t\t * the prog with \"arg is uninitialized\".\n--\nkernel/bpf/verifier.c=17188=static int check_indirect_jump(struct bpf_verifier_env *env, struct bpf_insn *insn)\n--\nkernel/bpf/verifier.c-17245-\nkernel/bpf/verifier.c:17246:static int check_sdt_probe(struct bpf_verifier_env *env, int insn_idx)\nkernel/bpf/verifier.c-17247-{\n--\nkernel/bpf/verifier.c=17373=static int do_check_insn(struct bpf_verifier_env *env, bool *do_print_state)\n--\nkernel/bpf/verifier.c-17448-\t\t\tif (env-\u003einsn_aux_data[env-\u003einsn_idx].sdt_entry)\nkernel/bpf/verifier.c:17449:\t\t\t\treturn check_sdt_probe(env, env-\u003einsn_idx);\nkernel/bpf/verifier.c-17450-\n"
}

7/2 2026/06/27 15:15 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 38195
							output: 32
							thoughts: 24
						

Thoughts:
Okay, here's my summary, presented as a stream of thought:

**Checking `bpf_insn_array_get_sdt_probe_by_name`**

Alright, time to dive in and take a look at `bpf_insn_array_get_sdt_probe_by_name`. I need to refresh my understanding of what this function does, particularly in the context of systemtap probes, since this is related to instrumenting BPF programs. I'm assuming it handles retrieving an SDT (Statically Defined Tracepoint) probe based on its name. The "insn_array" suggests that we're dealing with an array of BPF instructions, which probably means this function is used to locate the appropriate probe within a BPF program or a set of BPF programs. This function's retrieval is probably based on a name string. It is important to remember what SDT is used for and its relationships with BPF. I need to understand its specific role in hooking into the kernel and how it's integrated with BPF execution. I'll need to figure out the inputs it expects – likely the BPF instruction array or a pointer to it, and the probe name. I'll also check the return type to figure out what it gives back, is it a pointer, or an index, or some other identifier for the SDT probe? The details here are critical, I must know how the retrieved information is used further on. I'll need to investigate its implementation to grasp the intricacies of this retrieval process. I'm really curious about any error handling it might have. Understanding how this function interfaces with other parts of the BPF framework is key. Let's see how it works!



8/2 2026/06/27 15:15 tool grepper
0m Args:
{
  "Expression": "bpf_insn_array_get_sdt_probe_by_name"
}

Results:
{
  "Output": "include/linux/bpf.h=4180=void bpf_insn_array_adjust_after_remove(struct bpf_map *map, u32 off, u32 len);\ninclude/linux/bpf.h:4181:int bpf_insn_array_get_sdt_probe_by_name(struct bpf_prog *prog, const char *name,\ninclude/linux/bpf.h-4182-\t\t\t\t\t struct bpf_insn_array_value *val,\n--\nkernel/bpf/bpf_insn_array.c=259=void bpf_insn_array_release(struct bpf_map *map)\n--\nkernel/bpf/bpf_insn_array.c-265-\nkernel/bpf/bpf_insn_array.c:266:int bpf_insn_array_get_sdt_probe_by_name(struct bpf_prog *prog, const char *name,\nkernel/bpf/bpf_insn_array.c-267-\t\t\t\t\t struct bpf_insn_array_value *val,\n--\nkernel/bpf/syscall.c=2973=static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, struct bpf_log_attr *attr_log)\n--\nkernel/bpf/syscall.c-3111-\t\t}\nkernel/bpf/syscall.c:3112:\t\terr = bpf_insn_array_get_sdt_probe_by_name(dst_prog, attr-\u003esdt.name,\nkernel/bpf/syscall.c-3113-\t\t\t\t\t\t\t   \u0026sdt_val, \u0026probe_ip);\n"
}

9/2 2026/06/27 15:16 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 39050
							output: 23
							thoughts: 146
						

Thoughts:
**Fuzzing SDT Probe Attachment**

Okay, so this patch introduces a new feature – seems like a prime target for fuzzing. I need to make a mental note of the key symbols involved so I can guide the fuzzing process effectively. Specifically, I'm thinking about: `bpf_sdt_link_attach`, `check_sdt_probe`, `bpf_insn_array_get_sdt_probe_by_name`, and `bpf_insn_array_init`. The last one is particularly interesting because it now handles the SDT subtype.

Next, I should quickly verify if there are any new configuration options introduced. Let's see... `CONFIG_BPF_SYSCALL` and `CONFIG_BPF_JIT` are standard, so nothing new on that front. Good.

Time to revisit `bpf_insn_array_init` and make sure it's behaving as expected.



10/2 2026/06/27 15:16 tool grepper
0m Args:
{
  "Expression": "bpf_insn_array_init"
}

Results:
{
  "Output": "include/linux/bpf.h=4174=struct bpf_prog *bpf_prog_find_from_stack(void);\ninclude/linux/bpf.h-4175-\ninclude/linux/bpf.h:4176:int bpf_insn_array_init(struct bpf_map *map, struct bpf_verifier_env *env);\ninclude/linux/bpf.h-4177-int bpf_insn_array_ready(struct bpf_map *map);\n--\nkernel/bpf/bpf_insn_array.c=183=static inline bool valid_offsets(const struct bpf_insn_array *insn_array,\n--\nkernel/bpf/bpf_insn_array.c-203-\nkernel/bpf/bpf_insn_array.c:204:int bpf_insn_array_init(struct bpf_map *map, struct bpf_verifier_env *env)\nkernel/bpf/bpf_insn_array.c-205-{\n--\nkernel/bpf/verifier.c=17984=static int __add_used_map(struct bpf_verifier_env *env, struct bpf_map *map)\n--\nkernel/bpf/verifier.c-18015-\tif (map-\u003emap_type == BPF_MAP_TYPE_INSN_ARRAY) {\nkernel/bpf/verifier.c:18016:\t\terr = bpf_insn_array_init(map, env);\nkernel/bpf/verifier.c-18017-\t\tif (err) {\n"
}

11/2 2026/06/27 15:16 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 39837
							output: 20
							thoughts: 120
						

Thoughts:
**Considering the Impact of These Changes**

Okay, so I'm putting together a concise overview of the relevant modifications, specifically focusing on the eBPF side of things. I need to include these functions: `bpf_sdt_link_attach`, `check_sdt_probe`, and `bpf_insn_array_get_sdt_probe_by_name`. Those are the core components directly tied to adding support for SDT probes within the eBPF framework. I should make sure I haven't missed any other critical pieces, though.

I also see that `bpf_prog_load` is being modified. While I need to acknowledge that and the added complexity, I'm a bit concerned about performance implications, since it's a hot path. Similarly, `bpf_trampoline_update` is on my radar as a potential bottleneck, being a frequent operation, and I'll need to analyze the specific changes there carefully. Finally, I've noted that `bpf_insn_array_alloc` is being adjusted to support `BPF_F_INSN_ARRAY_SDT`, so that is clearly a core component in the new workflow.



12/2 2026/06/27 15:16 tool grepper
0m Args:
{
  "Expression": "insn_array_alloc"
}

Results:
{
  "Output": "kernel/bpf/bpf_insn_array.c=21=enum bpf_insn_array_subtype {\n--\nkernel/bpf/bpf_insn_array.c-25-\nkernel/bpf/bpf_insn_array.c:26:static inline u64 insn_array_alloc_size(u32 max_entries)\nkernel/bpf/bpf_insn_array.c-27-{\n--\nkernel/bpf/bpf_insn_array.c-33-\nkernel/bpf/bpf_insn_array.c:34:static int insn_array_alloc_check(union bpf_attr *attr)\nkernel/bpf/bpf_insn_array.c-35-{\n--\nkernel/bpf/bpf_insn_array.c=46=static void insn_array_free(struct bpf_map *map)\n--\nkernel/bpf/bpf_insn_array.c-52-\nkernel/bpf/bpf_insn_array.c:53:static struct bpf_map *insn_array_alloc(union bpf_attr *attr)\nkernel/bpf/bpf_insn_array.c-54-{\nkernel/bpf/bpf_insn_array.c:55:\tu64 size = insn_array_alloc_size(attr-\u003emax_entries);\nkernel/bpf/bpf_insn_array.c-56-\tstruct bpf_insn_array *insn_array;\n--\nkernel/bpf/bpf_insn_array.c=136=static u64 insn_array_mem_usage(const struct bpf_map *map)\nkernel/bpf/bpf_insn_array.c-137-{\nkernel/bpf/bpf_insn_array.c:138:\treturn insn_array_alloc_size(map-\u003emax_entries);\nkernel/bpf/bpf_insn_array.c-139-}\n--\nkernel/bpf/bpf_insn_array.c=157=const struct bpf_map_ops insn_array_map_ops = {\nkernel/bpf/bpf_insn_array.c:158:\t.map_alloc_check = insn_array_alloc_check,\nkernel/bpf/bpf_insn_array.c:159:\t.map_alloc = insn_array_alloc,\nkernel/bpf/bpf_insn_array.c-160-\t.map_free = insn_array_free,\n"
}

13/2 2026/06/27 15:16 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 40888
							output: 23
							thoughts: 84
						

Thoughts:
Here's my take on those thoughts:

**Handling the New Map Flag in Instruction Array Allocation**

Okay, so I need to make sure the changes for this new map flag are properly handled. I'm thinking I'll include both `insn_array_alloc` and `insn_array_alloc_check` in this context, since they're responsible for the initial allocation and any checks related to it. That seems logical.

Hmm, wait a second... `insn_array_alloc` is defined as a static function. That could normally be an issue, but since we're working within the syzkaller context, that shouldn't pose any immediate problems. We can work with that.

Next, I need to turn my attention to the `bpf_sdt_link_release` and `bpf_sdt_link_dealloc` functions. I need to make sure that these are adapted to consider this flag, especially in how the links and underlying resources are managed during release and deallocation. I need to review those carefully.



14/2 2026/06/27 15:16 tool grepper
0m Args:
{
  "Expression": "bpf_sdt_link_release"
}

Results:
{
  "Output": "kernel/bpf/syscall.c=3740=struct bpf_sdt_link {\n--\nkernel/bpf/syscall.c-3744-\nkernel/bpf/syscall.c:3745:static void bpf_sdt_link_release(struct bpf_link *link)\nkernel/bpf/syscall.c-3746-{\n--\nkernel/bpf/syscall.c=3763=static const struct bpf_link_ops bpf_sdt_link_lops = {\nkernel/bpf/syscall.c:3764:\t.release = bpf_sdt_link_release,\nkernel/bpf/syscall.c-3765-\t.dealloc = bpf_sdt_link_dealloc,\n"
}

15/2 2026/06/27 15:16 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 41421
							output: 193
							thoughts: 319
						

Thoughts:
**My BPF SDT Probe Implementation Review**

Okay, so I'm focusing on the new BPF SDT probes. I need to ensure I've got the key symbols identified for my review, especially for fuzzing and verifying the memory safety and correctness of the changes. The core functions I'm concentrating on are: `bpf_sdt_link_attach`, `bpf_sdt_link_release`, `bpf_sdt_link_dealloc`, `check_sdt_probe`, `bpf_insn_array_get_sdt_probe_by_name`, `insn_array_alloc`, and `insn_array_alloc_check`. These are the key entry points and supporting functions that will drive the new SDT probe functionality.

As for configurations to highlight, I recall the prompt wants me to be specific. Since `BPF_SYSCALL` and `BPF_JIT` are pretty fundamental and likely already enabled, listing them would be redundant. They don't specifically cover the *modified* code. So, the `EnableConfigs` list will remain empty.

Just to recap my thinking: This patch brings in BPF SDT probes, which is a major addition to allow BPF programs to observe statically defined probe points within other BPF programs. This involves new attach types, link types, map flags, and, critically, verifier logic to analyze probe arguments. This represents a significant shift in core BPF capabilities. Thus, extensive fuzzing is essential to validate the memory safety and proper behavior of these new features. It's a critical component.

Now, let's call `set-results`.



16/2 2026/06/27 15:16 tool set-results
0m Args:
{
  "EnableConfigs": [],
  "FocusSymbols": [
    "bpf_sdt_link_attach",
    "bpf_sdt_link_release",
    "bpf_sdt_link_dealloc",
    "check_sdt_probe",
    "bpf_insn_array_get_sdt_probe_by_name",
    "insn_array_alloc",
    "insn_array_alloc_check"
  ],
  "Reasoning": "The patch introduces BPF SDT (Statically Defined Tracing) probes, allowing BPF observer programs to attach to statically defined probe points in other BPF programs. This adds new BPF attach types, link types, map flags, and verifier logic to check probe arguments, which is a significant functional change to the core BPF subsystem and should be fuzzed.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [
    "bpf_sdt_link_attach",
    "bpf_sdt_link_release",
    "bpf_sdt_link_dealloc",
    "check_sdt_probe",
    "bpf_insn_array_get_sdt_probe_by_name",
    "insn_array_alloc",
    "insn_array_alloc_check"
  ],
  "Reasoning": "The patch introduces BPF SDT (Statically Defined Tracing) probes, allowing BPF observer programs to attach to statically defined probe points in other BPF programs. This adds new BPF attach types, link types, map flags, and verifier logic to check probe arguments, which is a significant functional change to the core BPF subsystem and should be fuzzed.",
  "WorthFuzzing": true
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)