Skip to content

Commit 93314d7

Browse files
committed
Skip __ksym externs for standard BPF helpers
Signed-off-by: Cong Wang <cwang@multikernel.io>
1 parent dd5f6b7 commit 93314d7

4 files changed

Lines changed: 321 additions & 21 deletions

File tree

src/bpf_helpers.ml

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
(*
2+
* Copyright 2026 Multikernel Technologies, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*)
16+
17+
(** Standard BPF helper functions.
18+
19+
These are the legacy BPF helpers invoked through a fixed helper ID. libbpf's
20+
[<bpf/bpf_helpers.h>] (via the auto-generated [bpf_helper_defs.h]) already
21+
declares every one of them as a function pointer, so the eBPF C backend must
22+
NOT emit its own [extern ... __ksym;] declaration for them - doing so clashes
23+
with the libbpf declaration ("redefinition as different kind of symbol").
24+
25+
KernelScript uses the [extern] keyword for both kfuncs and helpers, so the
26+
backend consults this set to tell them apart: a name here is a helper (skip
27+
the __ksym declaration); anything else is a real kfunc (needs __ksym).
28+
29+
The list mirrors libbpf's [bpf_helper_defs.h], which is generated from the
30+
kernel's [__BPF_FUNC_MAPPER] and only ever grows. *)
31+
32+
let helper_names = [
33+
"bpf_bind";
34+
"bpf_bprm_opts_set";
35+
"bpf_btf_find_by_name_kind";
36+
"bpf_cgrp_storage_delete";
37+
"bpf_cgrp_storage_get";
38+
"bpf_check_mtu";
39+
"bpf_clone_redirect";
40+
"bpf_copy_from_user";
41+
"bpf_copy_from_user_task";
42+
"bpf_csum_diff";
43+
"bpf_csum_level";
44+
"bpf_csum_update";
45+
"bpf_current_task_under_cgroup";
46+
"bpf_d_path";
47+
"bpf_dynptr_data";
48+
"bpf_dynptr_from_mem";
49+
"bpf_dynptr_read";
50+
"bpf_dynptr_write";
51+
"bpf_fib_lookup";
52+
"bpf_find_vma";
53+
"bpf_for_each_map_elem";
54+
"bpf_get_attach_cookie";
55+
"bpf_get_branch_snapshot";
56+
"bpf_get_cgroup_classid";
57+
"bpf_get_current_ancestor_cgroup_id";
58+
"bpf_get_current_cgroup_id";
59+
"bpf_get_current_comm";
60+
"bpf_get_current_pid_tgid";
61+
"bpf_get_current_task";
62+
"bpf_get_current_task_btf";
63+
"bpf_get_current_uid_gid";
64+
"bpf_get_func_arg";
65+
"bpf_get_func_arg_cnt";
66+
"bpf_get_func_ip";
67+
"bpf_get_func_ret";
68+
"bpf_get_hash_recalc";
69+
"bpf_get_listener_sock";
70+
"bpf_get_local_storage";
71+
"bpf_get_netns_cookie";
72+
"bpf_get_ns_current_pid_tgid";
73+
"bpf_get_numa_node_id";
74+
"bpf_get_prandom_u32";
75+
"bpf_get_retval";
76+
"bpf_get_route_realm";
77+
"bpf_get_smp_processor_id";
78+
"bpf_get_socket_cookie";
79+
"bpf_get_socket_uid";
80+
"bpf_getsockopt";
81+
"bpf_get_stack";
82+
"bpf_get_stackid";
83+
"bpf_get_task_stack";
84+
"bpf_ima_file_hash";
85+
"bpf_ima_inode_hash";
86+
"bpf_inode_storage_delete";
87+
"bpf_inode_storage_get";
88+
"bpf_jiffies64";
89+
"bpf_kallsyms_lookup_name";
90+
"bpf_kptr_xchg";
91+
"bpf_ktime_get_boot_ns";
92+
"bpf_ktime_get_coarse_ns";
93+
"bpf_ktime_get_ns";
94+
"bpf_ktime_get_tai_ns";
95+
"bpf_l3_csum_replace";
96+
"bpf_l4_csum_replace";
97+
"bpf_load_hdr_opt";
98+
"bpf_loop";
99+
"bpf_lwt_push_encap";
100+
"bpf_lwt_seg6_action";
101+
"bpf_lwt_seg6_adjust_srh";
102+
"bpf_lwt_seg6_store_bytes";
103+
"bpf_map_delete_elem";
104+
"bpf_map_lookup_elem";
105+
"bpf_map_lookup_percpu_elem";
106+
"bpf_map_peek_elem";
107+
"bpf_map_pop_elem";
108+
"bpf_map_push_elem";
109+
"bpf_map_update_elem";
110+
"bpf_msg_apply_bytes";
111+
"bpf_msg_cork_bytes";
112+
"bpf_msg_pop_data";
113+
"bpf_msg_pull_data";
114+
"bpf_msg_push_data";
115+
"bpf_msg_redirect_hash";
116+
"bpf_msg_redirect_map";
117+
"bpf_override_return";
118+
"bpf_per_cpu_ptr";
119+
"bpf_perf_event_output";
120+
"bpf_perf_event_read";
121+
"bpf_perf_event_read_value";
122+
"bpf_perf_prog_read_value";
123+
"bpf_probe_read";
124+
"bpf_probe_read_kernel";
125+
"bpf_probe_read_kernel_str";
126+
"bpf_probe_read_str";
127+
"bpf_probe_read_user";
128+
"bpf_probe_read_user_str";
129+
"bpf_probe_write_user";
130+
"bpf_rc_keydown";
131+
"bpf_rc_pointer_rel";
132+
"bpf_rc_repeat";
133+
"bpf_read_branch_records";
134+
"bpf_redirect";
135+
"bpf_redirect_map";
136+
"bpf_redirect_neigh";
137+
"bpf_redirect_peer";
138+
"bpf_reserve_hdr_opt";
139+
"bpf_ringbuf_discard";
140+
"bpf_ringbuf_discard_dynptr";
141+
"bpf_ringbuf_output";
142+
"bpf_ringbuf_query";
143+
"bpf_ringbuf_reserve";
144+
"bpf_ringbuf_reserve_dynptr";
145+
"bpf_ringbuf_submit";
146+
"bpf_ringbuf_submit_dynptr";
147+
"bpf_send_signal";
148+
"bpf_send_signal_thread";
149+
"bpf_seq_printf";
150+
"bpf_seq_printf_btf";
151+
"bpf_seq_write";
152+
"bpf_set_hash";
153+
"bpf_set_hash_invalid";
154+
"bpf_set_retval";
155+
"bpf_setsockopt";
156+
"bpf_sk_ancestor_cgroup_id";
157+
"bpf_sk_assign";
158+
"bpf_skb_adjust_room";
159+
"bpf_skb_ancestor_cgroup_id";
160+
"bpf_skb_cgroup_classid";
161+
"bpf_skb_cgroup_id";
162+
"bpf_skb_change_head";
163+
"bpf_skb_change_proto";
164+
"bpf_skb_change_tail";
165+
"bpf_skb_change_type";
166+
"bpf_skb_ecn_set_ce";
167+
"bpf_skb_get_tunnel_key";
168+
"bpf_skb_get_tunnel_opt";
169+
"bpf_skb_get_xfrm_state";
170+
"bpf_skb_load_bytes";
171+
"bpf_skb_load_bytes_relative";
172+
"bpf_skb_output";
173+
"bpf_skb_pull_data";
174+
"bpf_skb_set_tstamp";
175+
"bpf_skb_set_tunnel_key";
176+
"bpf_skb_set_tunnel_opt";
177+
"bpf_skb_store_bytes";
178+
"bpf_skb_under_cgroup";
179+
"bpf_skb_vlan_pop";
180+
"bpf_skb_vlan_push";
181+
"bpf_sk_cgroup_id";
182+
"bpf_skc_lookup_tcp";
183+
"bpf_skc_to_mptcp_sock";
184+
"bpf_skc_to_tcp6_sock";
185+
"bpf_skc_to_tcp_request_sock";
186+
"bpf_skc_to_tcp_sock";
187+
"bpf_skc_to_tcp_timewait_sock";
188+
"bpf_skc_to_udp6_sock";
189+
"bpf_skc_to_unix_sock";
190+
"bpf_sk_fullsock";
191+
"bpf_sk_lookup_tcp";
192+
"bpf_sk_lookup_udp";
193+
"bpf_sk_redirect_hash";
194+
"bpf_sk_redirect_map";
195+
"bpf_sk_release";
196+
"bpf_sk_select_reuseport";
197+
"bpf_sk_storage_delete";
198+
"bpf_sk_storage_get";
199+
"bpf_snprintf";
200+
"bpf_snprintf_btf";
201+
"bpf_sock_from_file";
202+
"bpf_sock_hash_update";
203+
"bpf_sock_map_update";
204+
"bpf_sock_ops_cb_flags_set";
205+
"bpf_spin_lock";
206+
"bpf_spin_unlock";
207+
"bpf_store_hdr_opt";
208+
"bpf_strncmp";
209+
"bpf_strtol";
210+
"bpf_strtoul";
211+
"bpf_sys_bpf";
212+
"bpf_sys_close";
213+
"bpf_sysctl_get_current_value";
214+
"bpf_sysctl_get_name";
215+
"bpf_sysctl_get_new_value";
216+
"bpf_sysctl_set_new_value";
217+
"bpf_tail_call";
218+
"bpf_task_pt_regs";
219+
"bpf_task_storage_delete";
220+
"bpf_task_storage_get";
221+
"bpf_tcp_check_syncookie";
222+
"bpf_tcp_gen_syncookie";
223+
"bpf_tcp_raw_check_syncookie_ipv4";
224+
"bpf_tcp_raw_check_syncookie_ipv6";
225+
"bpf_tcp_raw_gen_syncookie_ipv4";
226+
"bpf_tcp_raw_gen_syncookie_ipv6";
227+
"bpf_tcp_send_ack";
228+
"bpf_tcp_sock";
229+
"bpf_this_cpu_ptr";
230+
"bpf_timer_cancel";
231+
"bpf_timer_init";
232+
"bpf_timer_set_callback";
233+
"bpf_timer_start";
234+
"bpf_trace_printk";
235+
"bpf_trace_vprintk";
236+
"bpf_user_ringbuf_drain";
237+
"bpf_xdp_adjust_head";
238+
"bpf_xdp_adjust_meta";
239+
"bpf_xdp_adjust_tail";
240+
"bpf_xdp_get_buff_len";
241+
"bpf_xdp_load_bytes";
242+
"bpf_xdp_output";
243+
"bpf_xdp_store_bytes";
244+
]
245+
246+
let helper_set =
247+
let tbl = Hashtbl.create 256 in
248+
List.iter (fun name -> Hashtbl.replace tbl name ()) helper_names;
249+
tbl
250+
251+
(** [is_bpf_helper name] is true if [name] is a standard BPF helper already
252+
declared by libbpf's bpf_helpers.h, and therefore must not be re-declared
253+
as a [__ksym] extern by the eBPF C backend. *)
254+
let is_bpf_helper name = Hashtbl.mem helper_set name

src/dune

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
(name kernelscript)
33
(modules ast parser lexer parse type_checker symbol_table maps map_assignment
44
map_operations ir ir_generator ir_analysis loop_analysis ir_function_system codegen_common
5+
bpf_helpers
56
multi_program_analyzer multi_program_ir_optimizer ebpf_c_codegen
67
userspace_codegen evaluator safety_checker stdlib test_codegen
78
tail_call_analyzer kernel_module_codegen dynptr_bridge

src/ebpf_c_codegen.ml

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3050,23 +3050,29 @@ let generate_declarations_in_source_order_unified ctx ir_multi_prog ~_btf_path _
30503050
| Ir.IRDeclKfuncDecl kfunc_decl ->
30513051
(* Emit a function-signature declaration so eBPF programs can call the kfunc.
30523052
Kernel-provided kfuncs need `extern ... __ksym;`; locally-defined @kfunc
3053-
functions just need a forward prototype. *)
3054-
let params_str = match kfunc_decl.Ir.ikfunc_params with
3055-
| [] -> "void"
3056-
| params -> String.concat ", " (List.map (fun (name, ir_type) ->
3057-
sprintf "%s %s" (kfunc_signature_type_to_c ir_type) name
3058-
) params)
3059-
in
3060-
let return_type_str = kfunc_signature_type_to_c kfunc_decl.Ir.ikfunc_return_type in
3061-
if kfunc_decl.Ir.ikfunc_is_extern then
3062-
emit_line ctx (sprintf "extern %s %s(%s) __ksym;"
3063-
return_type_str kfunc_decl.Ir.ikfunc_name params_str)
3053+
functions just need a forward prototype. Standard BPF helpers are skipped
3054+
entirely - libbpf's bpf_helpers.h already declares them, and a __ksym
3055+
extern would clash with that declaration. *)
3056+
let name = kfunc_decl.Ir.ikfunc_name in
3057+
if kfunc_decl.Ir.ikfunc_is_extern && Bpf_helpers.is_bpf_helper name then
3058+
()
30643059
else (
3065-
emit_line ctx "/* kfunc declaration */";
3066-
emit_line ctx (sprintf "%s %s(%s);"
3067-
return_type_str kfunc_decl.Ir.ikfunc_name params_str)
3068-
);
3069-
emit_blank_line ctx
3060+
let params_str = match kfunc_decl.Ir.ikfunc_params with
3061+
| [] -> "void"
3062+
| params -> String.concat ", " (List.map (fun (pname, ir_type) ->
3063+
sprintf "%s %s" (kfunc_signature_type_to_c ir_type) pname
3064+
) params)
3065+
in
3066+
let return_type_str = kfunc_signature_type_to_c kfunc_decl.Ir.ikfunc_return_type in
3067+
if kfunc_decl.Ir.ikfunc_is_extern then
3068+
emit_line ctx (sprintf "extern %s %s(%s) __ksym;"
3069+
return_type_str name params_str)
3070+
else (
3071+
emit_line ctx "/* kfunc declaration */";
3072+
emit_line ctx (sprintf "%s %s(%s);" return_type_str name params_str)
3073+
);
3074+
emit_blank_line ctx
3075+
)
30703076
) ir_multi_prog.Ir.source_declarations;
30713077

30723078
(* Emit callbacks at the end if no functions were found (fallback) *)

tests/test_extern.ml

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -158,13 +158,13 @@ let test_extern_kfunc_string_representation () =
158158
(** Test extern kfunc declarations are emitted into generated eBPF C with __ksym *)
159159
let test_extern_kfunc_ebpf_codegen () =
160160
let program = {|
161-
extern bpf_ktime_get_ns() -> u64
161+
extern scx_bpf_select_cpu_dfl(p: *u8, prev_cpu: i32) -> u64
162162
extern scx_bpf_dsq_insert(p: *u8, dsq_id: u64, slice: u64, enq_flags: u64) -> void
163163

164164
@xdp
165165
fn test_program(ctx: *xdp_md) -> xdp_action {
166-
var ts = bpf_ktime_get_ns()
167-
scx_bpf_dsq_insert(null, 0, ts, 0)
166+
var cpu = scx_bpf_select_cpu_dfl(null, 0)
167+
scx_bpf_dsq_insert(null, 0, cpu, 0)
168168
return 2
169169
}
170170

@@ -186,11 +186,49 @@ let test_extern_kfunc_ebpf_codegen () =
186186
try ignore (Str.search_forward (Str.regexp_string substr) generated_code 0); true
187187
with Not_found -> false
188188
in
189-
check bool "Contains bpf_ktime_get_ns __ksym extern" true
190-
(contains "extern __u64 bpf_ktime_get_ns(void) __ksym;");
189+
check bool "Contains scx_bpf_select_cpu_dfl __ksym extern" true
190+
(contains "extern __u64 scx_bpf_select_cpu_dfl(__u8* p, __s32 prev_cpu) __ksym;");
191191
check bool "Contains scx_bpf_dsq_insert __ksym extern" true
192192
(contains "extern void scx_bpf_dsq_insert(__u8* p, __u64 dsq_id, __u64 slice, __u64 enq_flags) __ksym;")
193193

194+
(** Test that extern declarations naming standard BPF helpers are NOT re-declared
195+
as __ksym externs (libbpf's bpf_helpers.h already declares them; a __ksym extern
196+
would clash with the helper pointer definition). Real kfuncs still get __ksym. *)
197+
let test_extern_bpf_helper_not_redeclared () =
198+
let program = {|
199+
extern bpf_ktime_get_ns() -> u64
200+
extern my_real_kfunc(x: u64) -> i32
201+
202+
@xdp
203+
fn test_program(ctx: *xdp_md) -> xdp_action {
204+
var ts = bpf_ktime_get_ns()
205+
var r = my_real_kfunc(ts)
206+
if (r > 0) {
207+
return 1
208+
}
209+
return 2
210+
}
211+
212+
fn main() -> i32 {
213+
return 0
214+
}
215+
|} in
216+
217+
let ast = Parse.parse_string program in
218+
let symbol_table = Symbol_table.build_symbol_table ast in
219+
let (typed_ast, _) = Type_checker.type_check_and_annotate_ast ast in
220+
let ir = Ir_generator.generate_ir typed_ast symbol_table "test" in
221+
let (generated_code, _) = Ebpf_c_codegen.compile_multi_to_c_with_analysis ir in
222+
223+
let contains substr =
224+
try ignore (Str.search_forward (Str.regexp_string substr) generated_code 0); true
225+
with Not_found -> false
226+
in
227+
check bool "BPF helper bpf_ktime_get_ns is not re-declared as __ksym extern" false
228+
(contains "bpf_ktime_get_ns(void) __ksym;");
229+
check bool "Real kfunc my_real_kfunc is declared as __ksym extern" true
230+
(contains "extern __s32 my_real_kfunc(__u64 x) __ksym;")
231+
194232
(** Test extern keyword cannot be used in function definitions *)
195233
let test_extern_in_function_definition_fails () =
196234
let program = {|
@@ -270,6 +308,7 @@ let tests = [
270308
"extern kfunc userspace restriction", `Quick, test_extern_kfunc_userspace_restriction;
271309
"extern kfunc string representation", `Quick, test_extern_kfunc_string_representation;
272310
"extern kfunc ebpf codegen", `Quick, test_extern_kfunc_ebpf_codegen;
311+
"extern bpf helper not redeclared", `Quick, test_extern_bpf_helper_not_redeclared;
273312
"extern in function definition fails", `Quick, test_extern_in_function_definition_fails;
274313
"extern with body fails", `Quick, test_extern_with_body_fails;
275314
"extern mixed keywords fails", `Quick, test_extern_mixed_keywords_fails;

0 commit comments

Comments
 (0)