1use anyhow::{Context, Result, anyhow, bail};
7use libbpf_rs::libbpf_sys::*;
8use libbpf_rs::{AsRawLibbpf, OpenObject, OpenProgramImpl, ProgramImpl};
9use log::{debug, error, info, warn};
10use std::env;
11use std::ffi::CStr;
12use std::ffi::CString;
13use std::ffi::OsStr;
14use std::ffi::c_void;
15use std::io;
16use std::io::BufRead;
17use std::io::BufReader;
18use std::mem::size_of;
19use std::os::fd::AsFd;
20use std::os::fd::AsRawFd;
21use std::os::fd::BorrowedFd;
22use std::os::fd::FromRawFd;
23use std::os::fd::OwnedFd;
24use std::slice::from_raw_parts;
25
26const PROCFS_MOUNTS: &str = "/proc/mounts";
27const TRACEFS: &str = "tracefs";
28const DEBUGFS: &str = "debugfs";
29
30mod enums_abi {
31 include!("enums_abi.autogen.rs");
32}
33
34lazy_static::lazy_static! {
35 pub static ref SCX_OPS_KEEP_BUILTIN_IDLE: u64 =
36 read_enum("scx_ops_flags", "SCX_OPS_KEEP_BUILTIN_IDLE").unwrap_or(0);
37 pub static ref SCX_OPS_ENQ_LAST: u64 =
38 read_enum("scx_ops_flags", "SCX_OPS_ENQ_LAST").unwrap_or(0);
39 pub static ref SCX_OPS_ENQ_EXITING: u64 =
40 read_enum("scx_ops_flags", "SCX_OPS_ENQ_EXITING").unwrap_or(0);
41 pub static ref SCX_OPS_SWITCH_PARTIAL: u64 =
42 read_enum("scx_ops_flags", "SCX_OPS_SWITCH_PARTIAL").unwrap_or(0);
43 pub static ref SCX_OPS_ENQ_MIGRATION_DISABLED: u64 =
44 read_enum("scx_ops_flags", "SCX_OPS_ENQ_MIGRATION_DISABLED").unwrap_or(0);
45 pub static ref SCX_OPS_ALLOW_QUEUED_WAKEUP: u64 =
46 read_enum("scx_ops_flags", "SCX_OPS_ALLOW_QUEUED_WAKEUP").unwrap_or(0);
47 pub static ref SCX_OPS_BUILTIN_IDLE_PER_NODE: u64 =
48 read_enum("scx_ops_flags", "SCX_OPS_BUILTIN_IDLE_PER_NODE").unwrap_or(0);
49 pub static ref SCX_OPS_ALWAYS_ENQ_IMMED: u64 =
50 read_enum("scx_ops_flags", "SCX_OPS_ALWAYS_ENQ_IMMED").unwrap_or(0);
51 pub static ref SCX_OPS_TID_TO_TASK: u64 =
52 read_enum("scx_ops_flags", "SCX_OPS_TID_TO_TASK").unwrap_or(0);
53 pub static ref SCX_OPS_ENQ_BLOCKED: u64 =
54 read_enum("scx_ops_flags", "SCX_OPS_ENQ_BLOCKED").unwrap_or(0);
55
56 pub static ref SCX_PICK_IDLE_CORE: u64 =
57 read_enum("scx_pick_idle_cpu_flags", "SCX_PICK_IDLE_CORE").unwrap_or(0);
58 pub static ref SCX_PICK_IDLE_IN_NODE: u64 =
59 read_enum("scx_pick_idle_cpu_flags", "SCX_PICK_IDLE_IN_NODE").unwrap_or(0);
60
61 pub static ref ROOT_PREFIX: String =
62 env::var("SCX_SYSFS_PREFIX").unwrap_or("".to_string());
63}
64
65fn load_vmlinux_btf() -> &'static mut btf {
66 let btf = unsafe { btf__load_vmlinux_btf() };
67 if btf.is_null() {
68 panic!("btf__load_vmlinux_btf() returned NULL, was CONFIG_DEBUG_INFO_BTF enabled?")
69 }
70 unsafe { &mut *btf }
71}
72
73lazy_static::lazy_static! {
74 static ref VMLINUX_BTF: &'static mut btf = load_vmlinux_btf();
75}
76
77fn btf_kind(t: &btf_type) -> u32 {
78 (t.info >> 24) & 0x1f
79}
80
81fn btf_vlen(t: &btf_type) -> u32 {
82 t.info & 0xffff
83}
84
85fn btf_type_plus_1(t: &btf_type) -> *const c_void {
86 let ptr_val = t as *const btf_type as usize;
87 (ptr_val + size_of::<btf_type>()) as *const c_void
88}
89
90fn btf_enum(t: &btf_type) -> &[btf_enum] {
91 let ptr = btf_type_plus_1(t);
92 unsafe { from_raw_parts(ptr as *const btf_enum, btf_vlen(t) as usize) }
93}
94
95fn btf_enum64(t: &btf_type) -> &[btf_enum64] {
96 let ptr = btf_type_plus_1(t);
97 unsafe { from_raw_parts(ptr as *const btf_enum64, btf_vlen(t) as usize) }
98}
99
100fn btf_members(t: &btf_type) -> &[btf_member] {
101 let ptr = btf_type_plus_1(t);
102 unsafe { from_raw_parts(ptr as *const btf_member, btf_vlen(t) as usize) }
103}
104
105fn btf_params(t: &btf_type) -> &[btf_param] {
106 let ptr = btf_type_plus_1(t);
107 unsafe { from_raw_parts(ptr as *const btf_param, btf_vlen(t) as usize) }
108}
109
110fn btf_name_str_by_offset(btf: &btf, name_off: u32) -> Result<&str> {
111 let n = unsafe { btf__name_by_offset(btf, name_off) };
112 if n.is_null() {
113 bail!("btf__name_by_offset() returned NULL");
114 }
115 unsafe { CStr::from_ptr(n) }
116 .to_str()
117 .with_context(|| format!("Failed to convert {:?} to string", n))
118}
119
120fn recover_truncated_enum64_from(
147 table: &[(&str, &str, u64)],
148 type_name: &str,
149 name: &str,
150 lo32: u32,
151) -> Result<u64> {
152 static WARN_ONCE: std::sync::Once = std::sync::Once::new();
153
154 let Some(&(_, _, abi_val)) = table.iter().find(|(t, n, _)| *t == type_name && *n == name)
155 else {
156 let msg = format!(
160 "kernel BTF truncates 64-bit enum {}::{} to 0x{:x}; 64-bit \
161 variant not found in vmlinux.h",
162 type_name, name, lo32
163 );
164 error!("{}", msg);
165 bail!(msg);
166 };
167
168 if abi_val <= u32::MAX as u64 {
169 return Ok(lo32 as u64);
170 }
171
172 if abi_val as u32 != lo32 {
173 let msg = format!(
175 "kernel BTF value of {}::{} (0x{:x}) doesn't match the low 32 bits \
176 of the vmlinux.h value (0x{:x}); refusing to substitute",
177 type_name, name, lo32, abi_val
178 );
179 error!("{}", msg);
180 bail!(msg);
181 }
182
183 WARN_ONCE.call_once(|| {
184 warn!(
185 "kernel BTF lacks BTF_KIND_ENUM64 encoding (generated by \
186 pahole < 1.24 or with --skip_encoding_btf_enum64), so 64-bit \
187 scx enum values are truncated to their low 32 bits in kernel \
188 BTF. Substituting the full 64-bit values from the vmlinux.h \
189 this binary was built against, cross-checked against the low \
190 32 bits the kernel does provide. The high 32 bits cannot be \
191 verified: if the running kernel's actual values differ from \
192 the build-time vmlinux.h (e.g. an enum that moved in a newer \
193 kernel), the scheduler will operate on bogus values, such as \
194 dispatching to nonexistent DSQ ids, and can wildly malfunction."
195 );
196 });
197 Ok(abi_val)
198}
199
200fn recover_truncated_enum64(type_name: &str, name: &str, lo32: u32) -> Result<u64> {
201 recover_truncated_enum64_from(enums_abi::ENUM_ABI_VALUES, type_name, name, lo32)
202}
203
204pub fn read_enum(type_name: &str, name: &str) -> Result<u64> {
205 let btf: &btf = *VMLINUX_BTF;
206
207 let c_type_name = CString::new(type_name).unwrap();
208 let tid = unsafe { btf__find_by_name(btf, c_type_name.as_ptr()) };
209 if tid < 0 {
210 bail!("type {:?} doesn't exist, ret={}", type_name, tid);
211 }
212
213 let t = unsafe { btf__type_by_id(btf, tid as _) };
214 if t.is_null() {
215 bail!("btf__type_by_id({}) returned NULL", tid);
216 }
217 let t = unsafe { &*t };
218
219 match btf_kind(t) {
220 BTF_KIND_ENUM => {
221 for e in btf_enum(t).iter() {
222 if btf_name_str_by_offset(btf, e.name_off)? == name {
223 if unsafe { t.__bindgen_anon_1.size } == 8 && type_name.starts_with("scx_") {
228 return recover_truncated_enum64(type_name, name, e.val as u32);
229 }
230 return Ok(e.val as u64);
231 }
232 }
233 }
234 BTF_KIND_ENUM64 => {
235 for e in btf_enum64(t).iter() {
236 if btf_name_str_by_offset(btf, e.name_off)? == name {
237 return Ok(((e.val_hi32 as u64) << 32) | (e.val_lo32) as u64);
238 }
239 }
240 }
241 _ => (),
242 }
243
244 Err(anyhow!("{:?} doesn't exist in {:?}", name, type_name))
245}
246
247pub fn read_enum_any(type_names: &[&str], name: &str) -> Result<u64> {
249 let mut errors = Vec::new();
250
251 for type_name in type_names {
252 match read_enum(type_name, name) {
253 Ok(val) => return Ok(val),
254 Err(err) => errors.push(format!("{}: {:#}", type_name, err)),
255 }
256 }
257
258 bail!(
259 "{:?} doesn't exist in any of {:?}: {}",
260 name,
261 type_names,
262 errors.join("; ")
263 )
264}
265
266pub fn struct_has_field(type_name: &str, field: &str) -> Result<bool> {
267 let btf: &btf = *VMLINUX_BTF;
268
269 let c_type_name = CString::new(type_name).unwrap();
270 let tid = unsafe { btf__find_by_name_kind(btf, c_type_name.as_ptr(), BTF_KIND_STRUCT) };
271 if tid < 0 {
272 bail!("type {:?} doesn't exist, ret={}", type_name, tid);
273 }
274
275 let t = unsafe { btf__type_by_id(btf, tid as _) };
276 if t.is_null() {
277 bail!("btf__type_by_id({}) returned NULL", tid);
278 }
279 let t = unsafe { &*t };
280
281 for m in btf_members(t).iter() {
282 if btf_name_str_by_offset(btf, m.name_off)? == field {
283 return Ok(true);
284 }
285 }
286
287 Ok(false)
288}
289
290pub fn ksym_exists(ksym: &str) -> Result<bool> {
291 let btf: &btf = *VMLINUX_BTF;
292
293 let ksym_name = CString::new(ksym).unwrap();
294 let tid = unsafe { btf__find_by_name(btf, ksym_name.as_ptr()) };
295 Ok(tid >= 0)
296}
297
298#[repr(C)]
301#[derive(Clone, Copy)]
302struct ProbeInsn {
303 code: u8,
304 regs: u8,
305 off: i16,
306 imm: i32,
307}
308
309const fn probe_insn(code: u8, dst: u8, src: u8, off: i16, imm: i32) -> ProbeInsn {
310 #[cfg(target_endian = "little")]
311 let regs = (src << 4) | dst;
312 #[cfg(target_endian = "big")]
313 let regs = (dst << 4) | src;
314 ProbeInsn {
315 code,
316 regs,
317 off,
318 imm,
319 }
320}
321
322const PROBE_LD_IMM64: u8 = 0x18; const PROBE_MOV64_REG: u8 = 0xbf; const PROBE_MOV64_IMM: u8 = 0xb7; const PROBE_ATOMIC_DW: u8 = 0xdb; const PROBE_EXIT: u8 = 0x95; const PROBE_ATOMIC_OR_FETCH: i32 = 0x41; const PROBE_ADDR_SPACE_CAST: i16 = 1;
330
331fn probe_prog_load(
333 prog_type: bpf_prog_type,
334 flags: u32,
335 insns: &[ProbeInsn],
336) -> io::Result<OwnedFd> {
337 let mut opts = bpf_prog_load_opts {
338 sz: size_of::<bpf_prog_load_opts>() as _,
339 prog_flags: flags,
340 ..Default::default()
341 };
342 let fd = unsafe {
343 bpf_prog_load(
344 prog_type,
345 std::ptr::null(),
346 c"GPL".as_ptr(),
347 insns.as_ptr() as *const bpf_insn,
348 insns.len() as _,
349 &mut opts,
350 )
351 };
352 if fd < 0 {
353 return Err(io::Error::from_raw_os_error(-fd));
354 }
355 Ok(unsafe { OwnedFd::from_raw_fd(fd) })
356}
357
358fn probe_arena_fetch_bitops() -> bool {
363 let opts = bpf_map_create_opts {
364 sz: size_of::<bpf_map_create_opts>() as _,
365 map_flags: BPF_F_MMAPABLE,
366 ..Default::default()
367 };
368 let map_fd = unsafe { bpf_map_create(BPF_MAP_TYPE_ARENA, std::ptr::null(), 0, 0, 1, &opts) };
369 if map_fd < 0 {
370 return false;
371 }
372 let map = unsafe { OwnedFd::from_raw_fd(map_fd) };
373 let page = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize;
374 let addr = unsafe {
375 libc::mmap(
376 std::ptr::null_mut(),
377 page,
378 libc::PROT_READ | libc::PROT_WRITE,
379 libc::MAP_SHARED,
380 map.as_raw_fd(),
381 0,
382 )
383 };
384 if addr == libc::MAP_FAILED {
385 return false;
386 }
387
388 let insns = [
389 probe_insn(
390 PROBE_LD_IMM64,
391 1,
392 BPF_PSEUDO_MAP_VALUE as u8,
393 0,
394 map.as_raw_fd(),
395 ),
396 probe_insn(0, 0, 0, 0, 0),
397 probe_insn(PROBE_MOV64_REG, 1, 1, PROBE_ADDR_SPACE_CAST, 1),
398 probe_insn(PROBE_MOV64_IMM, 2, 0, 0, 1),
399 probe_insn(PROBE_ATOMIC_DW, 1, 2, 0, PROBE_ATOMIC_OR_FETCH),
400 probe_insn(PROBE_MOV64_IMM, 0, 0, 0, 0),
401 probe_insn(PROBE_EXIT, 0, 0, 0, 0),
402 ];
403 let loaded = probe_prog_load(BPF_PROG_TYPE_SYSCALL, BPF_F_SLEEPABLE, &insns).is_ok();
404 unsafe { libc::munmap(addr, page) };
405 loaded
406}
407
408const BPF_F_STREAM_NONBLOCK: u32 = 1;
412
413lazy_static::lazy_static! {
414 static ref BPF_PROG_STREAM_OPEN: Option<u64> =
415 read_enum("bpf_cmd", "BPF_PROG_STREAM_OPEN").ok();
416}
417
418#[repr(C)]
419struct ProgStreamOpenAttr {
420 prog_fd: u32,
421 stream_id: u32,
422 flags: u32,
423}
424
425pub fn prog_stream_open(
431 prog: BorrowedFd<'_>,
432 stream_id: u32,
433 nonblock: bool,
434) -> io::Result<OwnedFd> {
435 let attr = ProgStreamOpenAttr {
436 prog_fd: prog.as_raw_fd() as u32,
437 stream_id,
438 flags: if nonblock { BPF_F_STREAM_NONBLOCK } else { 0 },
439 };
440 let Some(cmd) = *BPF_PROG_STREAM_OPEN else {
441 return Err(io::Error::from_raw_os_error(libc::EINVAL));
442 };
443 let fd = unsafe {
444 libc::syscall(
445 libc::SYS_bpf,
446 cmd as libc::c_long,
447 &attr as *const ProgStreamOpenAttr as *const c_void,
448 size_of::<ProgStreamOpenAttr>(),
449 )
450 };
451 if fd < 0 {
452 return Err(io::Error::last_os_error());
453 }
454 Ok(unsafe { OwnedFd::from_raw_fd(fd as i32) })
455}
456
457fn probe_prog_stream_open() -> bool {
458 let insns = [
459 probe_insn(PROBE_MOV64_IMM, 0, 0, 0, 0),
460 probe_insn(PROBE_EXIT, 0, 0, 0, 0),
461 ];
462 let Ok(prog) = probe_prog_load(BPF_PROG_TYPE_SYSCALL, BPF_F_SLEEPABLE, &insns) else {
463 return false;
464 };
465 prog_stream_open(prog.as_fd(), BPF_STREAM_STDOUT, true).is_ok()
466}
467
468pub const SCX_LIB_FEAT_ARENA_FETCH_BITOPS: u64 = 1 << 0;
471
472lazy_static::lazy_static! {
473 pub static ref ARENA_FETCH_BITOPS: bool = probe_arena_fetch_bitops();
475
476 pub static ref PROG_STREAM_OPEN_SUPPORTED: bool = probe_prog_stream_open();
478
479 pub static ref LIB_FEATURES: Vec<(&'static str, u64, bool)> = vec![
483 ("arena_fetch_bitops", SCX_LIB_FEAT_ARENA_FETCH_BITOPS, *ARENA_FETCH_BITOPS),
484 ("prog_stream_fds", 0, *PROG_STREAM_OPEN_SUPPORTED),
485 ];
486}
487
488fn rodata_var(obj: &OpenObject, name: &str) -> Result<Option<(usize, usize)>> {
491 let raw = unsafe { &*obj.as_libbpf_object().as_ptr() };
492 let Some(btf) = libbpf_rs::btf::Btf::from_bpf_object(raw)? else {
493 return Ok(None);
494 };
495 let mut var_off = None;
496 for sec in btf.type_by_kind::<libbpf_rs::btf::types::DataSec<'_>>() {
497 if sec.name() != Some(OsStr::new(".rodata")) {
498 continue;
499 }
500 for var in sec.iter() {
501 let ty: libbpf_rs::btf::types::Var<'_> = btf
502 .type_by_id(var.ty)
503 .ok_or_else(|| anyhow!("BTF datasec entry {:?} is not a variable", var.ty))?;
504 if ty.name() == Some(OsStr::new(name)) {
505 var_off = Some((var.offset as usize, var.size));
506 }
507 }
508 }
509 Ok(var_off)
510}
511
512pub fn has_rodata_var(obj: &OpenObject, name: &str) -> Result<bool> {
514 Ok(rodata_var(obj, name)?.is_some())
515}
516
517pub fn set_rodata_var(obj: &mut OpenObject, name: &str, value: &[u8]) -> Result<bool> {
520 let Some((off, size)) = rodata_var(obj, name)? else {
521 return Ok(false);
522 };
523 if size != value.len() {
524 bail!(
525 "rodata variable {name} is {size} bytes, not {}",
526 value.len()
527 );
528 }
529 for mut map in obj.maps_mut() {
530 if !map.name().to_string_lossy().ends_with(".rodata") {
531 continue;
532 }
533 let data = map
534 .initial_value_mut()
535 .ok_or_else(|| anyhow!("rodata map has no initial value"))?;
536 let Some(slot) = data.get_mut(off..off + size) else {
537 bail!(
538 "rodata variable {name} lies outside the {} byte map",
539 data.len()
540 );
541 };
542 slot.copy_from_slice(value);
543 return Ok(true);
544 }
545 bail!("object has no .rodata map for {name}")
546}
547
548pub fn set_lib_features(obj: &mut OpenObject) -> Result<()> {
551 let mut bits = 0;
552 let mut missing = false;
553 let mut line = String::new();
554 for (name, bit, present) in LIB_FEATURES.iter() {
555 if *present {
556 bits |= bit;
557 } else {
558 missing = true;
559 }
560 line.push_str(&format!(" {name}={}", if *present { "yes" } else { "no" }));
561 }
562 set_rodata_var(obj, "scx_lib_features", &bits.to_ne_bytes())?;
563 if missing {
564 info!("kernel features:{line}");
565 } else {
566 debug!("kernel features:{line}");
567 }
568 Ok(())
569}
570
571pub fn malformed_scx_kfuncs() -> Vec<String> {
581 let btf: &btf = *VMLINUX_BTF;
582 let mut bad = Vec::new();
583 let cnt = unsafe { btf__type_cnt(btf) };
584
585 for id in 1..cnt {
586 let t = unsafe { btf__type_by_id(btf, id) };
587 if t.is_null() {
588 continue;
589 }
590 let t = unsafe { &*t };
591 if btf_kind(t) != BTF_KIND_FUNC {
592 continue;
593 }
594
595 let Ok(name) = btf_name_str_by_offset(btf, t.name_off) else {
596 continue;
597 };
598 if !(name.starts_with("scx_bpf_") || name.starts_with("__scx_bpf_")) {
599 continue;
600 }
601 if name.ends_with("_impl") {
607 continue;
608 }
609
610 let proto_id = unsafe { t.__bindgen_anon_1.type_ };
612 let pt = unsafe { btf__type_by_id(btf, proto_id) };
613 if pt.is_null() {
614 continue;
615 }
616 let pt = unsafe { &*pt };
617 if btf_kind(pt) != BTF_KIND_FUNC_PROTO {
618 continue;
619 }
620
621 if btf_params(pt).iter().any(|p| {
622 p.name_off != 0 && matches!(btf_name_str_by_offset(btf, p.name_off), Ok("aux"))
623 }) {
624 bad.push(name.to_string());
625 }
626 }
627
628 bad
629}
630
631pub fn in_kallsyms(ksym: &str) -> Result<bool> {
632 let file = std::fs::File::open("/proc/kallsyms")?;
633 let reader = std::io::BufReader::new(file);
634
635 for line in reader.lines() {
636 for sym in line.unwrap().split_whitespace() {
637 if ksym == sym {
638 return Ok(true);
639 }
640 }
641 }
642
643 Ok(false)
644}
645
646pub fn get_fs_mount(mount_type: &str) -> Result<Vec<std::path::PathBuf>> {
648 let proc_mounts_path = std::path::Path::new(PROCFS_MOUNTS);
649
650 let file = std::fs::File::open(proc_mounts_path)
651 .with_context(|| format!("Failed to open {}", proc_mounts_path.display()))?;
652
653 let reader = BufReader::new(file);
654
655 let mut mounts = Vec::new();
656 for line in reader.lines() {
657 let line = line.context("Failed to read line from /proc/mounts")?;
658 let mount_info: Vec<&str> = line.split_whitespace().collect();
659
660 if mount_info.len() > 3 && mount_info[2] == mount_type {
661 let mount_path = std::path::PathBuf::from(mount_info[1]);
662 mounts.push(mount_path);
663 }
664 }
665
666 Ok(mounts)
667}
668
669pub fn tracefs_mount() -> Result<std::path::PathBuf> {
671 let mounts = get_fs_mount(TRACEFS)?;
672 mounts.into_iter().next().context("No tracefs mount found")
673}
674
675pub fn debugfs_mount() -> Result<std::path::PathBuf> {
677 let mounts = get_fs_mount(DEBUGFS)?;
678 mounts.into_iter().next().context("No debugfs mount found")
679}
680
681pub fn tracer_available(tracer: &str) -> Result<bool> {
682 let base_path = tracefs_mount().unwrap_or_else(|_| debugfs_mount().unwrap().join("tracing"));
683 let file = match std::fs::File::open(base_path.join("available_tracers")) {
684 Ok(f) => f,
685 Err(_) => return Ok(false),
686 };
687 let reader = std::io::BufReader::new(file);
688
689 for line in reader.lines() {
690 for tc in line.unwrap().split_whitespace() {
691 if tracer == tc {
692 return Ok(true);
693 }
694 }
695 }
696
697 Ok(false)
698}
699
700pub fn tracepoint_exists(tracepoint: &str) -> Result<bool> {
701 let base_path = tracefs_mount().unwrap_or_else(|_| debugfs_mount().unwrap().join("tracing"));
702 let file = match std::fs::File::open(base_path.join("available_events")) {
703 Ok(f) => f,
704 Err(_) => return Ok(false),
705 };
706 let reader = std::io::BufReader::new(file);
707
708 for line in reader.lines() {
709 for tp in line.unwrap().split_whitespace() {
710 if tracepoint == tp {
711 return Ok(true);
712 }
713 }
714 }
715
716 Ok(false)
717}
718
719pub fn function_tracer_available() -> bool {
724 std::path::Path::new("/proc/sys/kernel/ftrace_enabled").exists()
725}
726
727pub fn cond_kprobe_enable<T>(sym: &str, prog_ptr: &OpenProgramImpl<T>) -> Result<bool> {
728 if in_kallsyms(sym)? {
729 unsafe {
730 bpf_program__set_autoload(prog_ptr.as_libbpf_object().as_ptr(), true);
731 }
732 return Ok(true);
733 } else {
734 warn!("symbol {sym} is missing, kprobe not loaded");
735 }
736
737 Ok(false)
738}
739
740pub fn cond_kprobes_enable<T>(kprobes: Vec<(&str, &OpenProgramImpl<T>)>) -> Result<bool> {
741 for (sym, _) in kprobes.iter() {
743 if !in_kallsyms(sym)? {
744 warn!("symbol {sym} is missing, kprobe not loaded");
745 return Ok(false);
746 }
747 }
748
749 for (_, ptr) in kprobes.iter() {
751 unsafe {
752 bpf_program__set_autoload(ptr.as_libbpf_object().as_ptr(), true);
753 }
754 }
755
756 Ok(true)
757}
758
759pub fn cond_kprobe_load<T>(sym: &str, prog_ptr: &OpenProgramImpl<T>) -> Result<bool> {
760 if in_kallsyms(sym)? {
761 unsafe {
762 bpf_program__set_autoload(prog_ptr.as_libbpf_object().as_ptr(), true);
763 bpf_program__set_autoattach(prog_ptr.as_libbpf_object().as_ptr(), false);
764 }
765 return Ok(true);
766 } else {
767 warn!("symbol {sym} is missing, kprobe not loaded");
768 }
769
770 Ok(false)
771}
772
773pub fn cond_kprobe_attach<T>(sym: &str, prog_ptr: &ProgramImpl<T>) -> Result<bool> {
774 if in_kallsyms(sym)? {
775 unsafe {
776 bpf_program__attach(prog_ptr.as_libbpf_object().as_ptr());
777 }
778 return Ok(true);
779 } else {
780 warn!("symbol {sym} is missing, kprobe not loaded");
781 }
782
783 Ok(false)
784}
785
786pub fn cond_tracepoint_enable<T>(tracepoint: &str, prog_ptr: &OpenProgramImpl<T>) -> Result<bool> {
787 if tracepoint_exists(tracepoint)? {
788 unsafe {
789 bpf_program__set_autoload(prog_ptr.as_libbpf_object().as_ptr(), true);
790 }
791 return Ok(true);
792 } else {
793 warn!("tracepoint {tracepoint} is missing, tracepoint not loaded");
794 }
795
796 Ok(false)
797}
798
799pub fn cond_tracepoints_enable<T>(tracepoints: Vec<(&str, &OpenProgramImpl<T>)>) -> Result<bool> {
800 for (tp, _) in tracepoints.iter() {
802 if !tracepoint_exists(tp)? {
803 warn!("tracepoint {tp} is missing, tracepoint not loaded");
804 return Ok(false);
805 }
806 }
807
808 for (_, ptr) in tracepoints.iter() {
810 unsafe {
811 bpf_program__set_autoload(ptr.as_libbpf_object().as_ptr(), true);
812 }
813 }
814
815 Ok(true)
816}
817
818pub fn is_sched_ext_enabled() -> io::Result<bool> {
819 let content = std::fs::read_to_string("/sys/kernel/sched_ext/state")?;
820
821 match content.trim() {
822 "enabled" => Ok(true),
823 "disabled" => Ok(false),
824 _ => {
825 Err(io::Error::new(
827 io::ErrorKind::InvalidData,
828 "Unexpected content in /sys/kernel/sched_ext/state",
829 ))
830 }
831 }
832}
833
834#[macro_export]
835macro_rules! unwrap_or_break {
836 ($expr: expr, $label: lifetime) => {{
837 match $expr {
838 Ok(val) => val,
839 Err(e) => break $label Err(e),
840 }
841 }};
842}
843
844pub fn check_min_requirements(ops_struct: &str) -> Result<()> {
845 if let Ok(false) | Err(_) = struct_has_field(ops_struct, "dump") {
848 bail!("{}.dump() missing, kernel too old?", ops_struct);
849 }
850 Ok(())
851}
852
853#[doc(hidden)]
854#[rustfmt::skip]
855#[macro_export]
856macro_rules! __scx_ops_open {
857 ($builder: expr, $obj_ref: expr, $ops: ident, $ops_struct: literal, $open_opts: expr) => { 'block: {
858 scx_utils::paste! {
859 scx_utils::unwrap_or_break!(scx_utils::compat::check_min_requirements($ops_struct), 'block);
860 use ::anyhow::Context;
861 use ::libbpf_rs::skel::OpenSkel;
862 use ::libbpf_rs::skel::SkelBuilder;
863
864 let mut skel = match $open_opts {
865 Some(opts_ref) => { match $builder.open_opts(opts_ref, $obj_ref).context("Failed to open BPF program with options") {
867 Ok(val) => val,
868 Err(e) => break 'block Err(e),
869 }
870 }
871 None => {
872 match $builder.open($obj_ref).context("Failed to open BPF program") {
873 Ok(val) => val,
874 Err(e) => break 'block Err(e),
875 }
876 }
877 };
878
879 if let Err(e) = scx_utils::compat::set_lib_features(skel.open_object_mut()) {
880 break 'block Err(e);
881 }
882
883 let ops = skel.struct_ops.[<$ops _mut>]();
884 let path = std::path::Path::new("/sys/kernel/sched_ext/hotplug_seq");
885
886 let val = match std::fs::read_to_string(&path) {
887 Ok(val) => val,
888 Err(_) => {
889 break 'block Err(anyhow::anyhow!("Failed to open or read file {:?}", path));
890 }
891 };
892
893 ops.hotplug_seq = match val.trim().parse::<u64>() {
894 Ok(parsed) => parsed,
895 Err(_) => {
896 break 'block Err(anyhow::anyhow!("Failed to parse hotplug seq {}", val));
897 }
898 };
899
900 if let Ok(s) = ::std::env::var("SCX_TIMEOUT_MS") {
901 skel.struct_ops.[<$ops _mut>]().timeout_ms = match s.parse::<u32>() {
902 Ok(ms) => {
903 ::scx_utils::info!("Setting timeout_ms to {} based on environment", ms);
904 ms
905 },
906 Err(e) => {
907 break 'block anyhow::Result::Err(e).context("SCX_TIMEOUT_MS has invalid value");
908 },
909 };
910 }
911
912 {
913 let ops = skel.struct_ops.[<$ops _mut>]();
914
915 let name_field = &mut ops.name;
916
917 let version_suffix = ::scx_utils::build_id::ops_version_suffix(env!("CARGO_PKG_VERSION"));
918 let bytes = version_suffix.as_bytes();
919 let mut i = 0;
920 let mut bytes_idx = 0;
921 let mut found_null = false;
922
923 while i < name_field.len() - 1 {
924 found_null |= name_field[i] == 0;
925 if !found_null {
926 i += 1;
927 continue;
928 }
929
930 if bytes_idx < bytes.len() {
931 name_field[i] = bytes[bytes_idx] as i8;
932 bytes_idx += 1;
933 } else {
934 break;
935 }
936 i += 1;
937 }
938 name_field[i] = 0;
939 }
940
941 $crate::import_enums!(skel);
942
943 let result = ::anyhow::Result::Ok(skel);
944
945 result
946 }
947 }};
948}
949
950#[macro_export]
955macro_rules! scx_ops_open {
956 ($builder: expr, $obj_ref: expr, $ops: ident, $open_opts: expr) => {
957 $crate::__scx_ops_open!($builder, $obj_ref, $ops, "sched_ext_ops", $open_opts)
958 };
959}
960
961pub const BPF_F_ARENA_SCALAR: u32 = 1 << 8;
965
966#[macro_export]
980macro_rules! scx_ops_cid_open {
981 ($builder: expr, $obj_ref: expr, $ops: ident, $open_opts: expr) => {
982 $crate::__scx_ops_open!($builder, $obj_ref, $ops, "sched_ext_ops_cid", $open_opts).and_then(
983 |mut skel| {
984 skel.progs
985 .scx_lib_init_probe
986 .set_attach_target(0, Some("bpf_scx_reg_cid".to_string()))?;
987 if $crate::compat::has_rodata_var(
988 ::libbpf_rs::skel::OpenSkel::open_object(&skel),
989 "__SCX_MARKER_arena_scalar",
990 )? {
991 for mut prog in
992 ::libbpf_rs::skel::OpenSkel::open_object_mut(&mut skel).progs_mut()
993 {
994 let ptr = ::libbpf_rs::AsRawLibbpf::as_libbpf_object(&prog).as_ptr();
996 let flags = unsafe { ::libbpf_rs::libbpf_sys::bpf_program__flags(ptr) };
997 prog.set_flags(flags | $crate::compat::BPF_F_ARENA_SCALAR);
998 }
999 }
1000 ::anyhow::Result::Ok(skel)
1001 },
1002 )
1003 };
1004}
1005
1006#[doc(hidden)]
1016#[rustfmt::skip]
1017#[macro_export]
1018macro_rules! __scx_ops_form_fixups {
1019 (cpu, $ops_ref: expr) => {
1020 if !$ops_ref.cgroup_set_bandwidth.is_null() {
1021 if let Ok(false) | Err(_) = scx_utils::compat::struct_has_field("sched_ext_ops", "cgroup_set_bandwidth") {
1022 ::scx_utils::warn!("kernel doesn't support ops.cgroup_set_bandwidth()");
1023 $ops_ref.cgroup_set_bandwidth = std::ptr::null_mut();
1024 }
1025 }
1026 if !$ops_ref.cgroup_set_idle.is_null() {
1027 if let Ok(false) | Err(_) = scx_utils::compat::struct_has_field("sched_ext_ops", "cgroup_set_idle") {
1028 ::scx_utils::warn!("kernel doesn't support ops.cgroup_set_idle()");
1029 $ops_ref.cgroup_set_idle = std::ptr::null_mut();
1030 }
1031 }
1032 };
1033 (cid, $ops_ref: expr) => {};
1034}
1035
1036#[doc(hidden)]
1037#[rustfmt::skip]
1038#[macro_export]
1039macro_rules! __scx_ops_load {
1040 ($skel: expr, $ops: ident, $uei: ident, $form: ident) => { 'block: {
1041 scx_utils::paste! {
1042 use ::anyhow::Context;
1043 use ::libbpf_rs::skel::OpenSkel;
1044
1045 {
1046 let ops = $skel.struct_ops.[<$ops _mut>]();
1047 $crate::__scx_ops_form_fixups!($form, ops);
1048 if !ops.sub_attach.is_null() {
1049 if let Ok(false) | Err(_) = scx_utils::compat::struct_has_field("sched_ext_ops", "sub_attach") {
1050 ::scx_utils::warn!("kernel doesn't support ops.sub_attach()");
1051 ops.sub_attach = std::ptr::null_mut();
1052 }
1053 }
1054 if !ops.sub_detach.is_null() {
1055 if let Ok(false) | Err(_) = scx_utils::compat::struct_has_field("sched_ext_ops", "sub_detach") {
1056 ::scx_utils::warn!("kernel doesn't support ops.sub_detach()");
1057 ops.sub_detach = std::ptr::null_mut();
1058 }
1059 }
1060 if ops.sub_cgroup_id > 0 {
1061 if let Ok(false) | Err(_) = scx_utils::compat::struct_has_field("sched_ext_ops", "sub_cgroup_id") {
1062 ::scx_utils::warn!("kernel doesn't support ops.sub_cgroup_id");
1063 ops.sub_cgroup_id = 0;
1064 }
1065 }
1066 if ops.rescue_bandwidth_ppt > 0 {
1067 if let Ok(false) | Err(_) = scx_utils::compat::struct_has_field("sched_ext_ops", "rescue_bandwidth_ppt") {
1068 ::scx_utils::warn!("kernel doesn't support ops.rescue_bandwidth_ppt");
1069 ops.rescue_bandwidth_ppt = 0;
1070 }
1071 }
1072 if ops.rescue_quantum_us > 0 {
1073 if let Ok(false) | Err(_) = scx_utils::compat::struct_has_field("sched_ext_ops", "rescue_quantum_us") {
1074 ::scx_utils::warn!("kernel doesn't support ops.rescue_quantum_us");
1075 ops.rescue_quantum_us = 0;
1076 }
1077 }
1078 }
1079
1080 if !scx_utils::compat::function_tracer_available() {
1081 break 'block Err(anyhow::anyhow!("kernel is missing CONFIG_FUNCTION_TRACER"));
1082 }
1083
1084 scx_utils::uei_set_size!($skel, $ops, $uei);
1085 $skel.load().context("Failed to load BPF program").map_err(|e| {
1086 let bad = scx_utils::compat::malformed_scx_kfuncs();
1087 if bad.is_empty() {
1088 e
1089 } else {
1090 e.context(format!(
1091 "the running kernel's BTF has malformed scx kfunc prototype(s): {}.\n\
1092 \n\
1093 These kfuncs are KF_IMPLICIT_ARGS but their public BTF prototype\n\
1094 still carries the implicit 'struct bpf_prog_aux *' argument, which\n\
1095 makes BPF programs fail to load with 'func_proto incompatible with\n\
1096 vmlinux'. This happens when the kernel was built with pahole < 1.26.\n\
1097 \n\
1098 Fix: boot a kernel whose BTF was generated with pahole >= 1.26.\n\
1099 Affected distros include Ubuntu 24.04 LTS. See kernel commit\n\
1100 9edd04c4189e (\"docs: Raise minimum pahole version to 1.26 for\n\
1101 KF_IMPLICIT_ARGS kfuncs\").",
1102 bad.join(", ")
1103 ))
1104 }
1105 }).inspect(|skel| {
1106 use ::libbpf_rs::skel::Skel;
1107 use ::libbpf_rs::AsRawLibbpf;
1108
1109 if scx_utils::compat::read_enum("bpf_cmd", "BPF_PROG_ASSOC_STRUCT_OPS").is_ok() {
1119 let map_ptr = skel.maps.$ops.as_libbpf_object().as_ptr();
1120 for prog in skel.object().progs() {
1121 if prog.prog_type() == ::libbpf_rs::ProgramType::StructOps || !prog.autoload() {
1122 continue;
1123 }
1124 let ret = unsafe {
1125 ::libbpf_rs::libbpf_sys::bpf_program__assoc_struct_ops(
1126 prog.as_libbpf_object().as_ptr(), map_ptr, std::ptr::null_mut())
1127 };
1128 if ret < 0 {
1129 ::scx_utils::warn!("Failed to associate {} with {}: {}",
1130 prog.name().to_string_lossy(), stringify!($ops), ret);
1131 break;
1132 }
1133 }
1134 }
1135 })
1136 }
1137 }};
1138}
1139
1140#[macro_export]
1145macro_rules! scx_ops_load {
1146 ($skel: expr, $ops: ident, $uei: ident) => {
1147 $crate::__scx_ops_load!($skel, $ops, $uei, cpu)
1148 };
1149}
1150
1151#[macro_export]
1156macro_rules! scx_ops_cid_load {
1157 ($skel: expr, $ops: ident, $uei: ident) => {
1158 $crate::__scx_ops_load!($skel, $ops, $uei, cid)
1159 };
1160}
1161
1162#[rustfmt::skip]
1164#[macro_export]
1165macro_rules! scx_ops_attach {
1166 ($skel: expr, $ops: ident) => {
1167 scx_ops_attach!($skel, $ops, false)
1168 };
1169 ($skel: expr, $ops: ident, $is_subsched: expr) => { 'block: {
1170 use ::anyhow::Context;
1171 use ::libbpf_rs::skel::Skel;
1172
1173 if !$is_subsched && scx_utils::compat::is_sched_ext_enabled().unwrap_or(false) {
1174 break 'block Err(anyhow::anyhow!(
1175 "another sched_ext scheduler is already running"
1176 ));
1177 }
1178 $skel
1179 .attach()
1180 .context("Failed to attach non-struct_ops BPF programs")
1181 .and_then(|_| {
1182 $skel
1183 .maps
1184 .$ops
1185 .attach_struct_ops()
1186 .context("Failed to attach struct_ops BPF programs")
1187 })
1188 }};
1189}
1190
1191#[cfg(test)]
1192mod tests {
1193 #[test]
1194 fn test_read_enum() {
1195 assert_eq!(super::read_enum("pid_type", "PIDTYPE_TGID").unwrap(), 1);
1196 }
1197
1198 #[test]
1199 fn test_read_enum_any() {
1200 assert_eq!(
1201 super::read_enum_any(&["NO_SUCH_TYPE", "pid_type"], "PIDTYPE_TGID").unwrap(),
1202 1
1203 );
1204 assert!(super::read_enum_any(&["NO_SUCH_TYPE", "pid_type"], "NO_SUCH_ENUM").is_err());
1205 }
1206
1207 #[test]
1208 fn test_recover_truncated_enum64() {
1209 let table: &[(&str, &str, u64)] = &[
1210 ("scx_dsq_id_flags", "SCX_DSQ_LOCAL", 0x8000000000000002),
1211 ("scx_enq_flags", "SCX_ENQ_PREEMPT", 0x100000000),
1212 ("scx_enq_flags", "SCX_ENQ_HEAD", 0x10000),
1213 ];
1214
1215 assert_eq!(
1217 super::recover_truncated_enum64_from(table, "scx_dsq_id_flags", "SCX_DSQ_LOCAL", 2)
1218 .unwrap(),
1219 0x8000000000000002
1220 );
1221 assert_eq!(
1222 super::recover_truncated_enum64_from(table, "scx_enq_flags", "SCX_ENQ_PREEMPT", 0)
1223 .unwrap(),
1224 0x100000000
1225 );
1226 assert_eq!(
1229 super::recover_truncated_enum64_from(table, "scx_enq_flags", "SCX_ENQ_HEAD", 0x20000)
1230 .unwrap(),
1231 0x20000
1232 );
1233 assert!(
1235 super::recover_truncated_enum64_from(table, "scx_dsq_id_flags", "SCX_DSQ_LOCAL", 3)
1236 .is_err()
1237 );
1238 assert!(
1240 super::recover_truncated_enum64_from(table, "scx_enq_flags", "SCX_ENQ_NEW", 7).is_err()
1241 );
1242 }
1243
1244 #[test]
1245 fn test_enum_abi_table() {
1246 let find = |t: &str, n: &str| {
1249 super::enums_abi::ENUM_ABI_VALUES
1250 .iter()
1251 .find(|(ty, na, _)| *ty == t && *na == n)
1252 .map(|&(_, _, v)| v)
1253 };
1254 assert_eq!(
1255 find("scx_dsq_id_flags", "SCX_DSQ_FLAG_BUILTIN"),
1256 Some(1 << 63)
1257 );
1258 assert_eq!(
1259 find("scx_dsq_id_flags", "SCX_DSQ_LOCAL"),
1260 Some((1 << 63) | 2)
1261 );
1262 assert_eq!(
1263 find("scx_dsq_id_flags", "SCX_DSQ_LOCAL_ON"),
1264 Some((1 << 63) | (1 << 62))
1265 );
1266 assert_eq!(find("scx_public_consts", "SCX_SLICE_INF"), Some(u64::MAX));
1267 assert_eq!(find("scx_enq_flags", "SCX_ENQ_PREEMPT"), Some(1 << 32));
1268 }
1269
1270 #[test]
1271 fn test_struct_has_field() {
1272 assert!(super::struct_has_field("task_struct", "flags").unwrap());
1273 assert!(!super::struct_has_field("task_struct", "NO_SUCH_FIELD").unwrap());
1274 assert!(super::struct_has_field("NO_SUCH_STRUCT", "NO_SUCH_FIELD").is_err());
1275 }
1276
1277 #[test]
1278 fn test_ksym_exists() {
1279 assert!(super::ksym_exists("bpf_task_acquire").unwrap());
1280 assert!(!super::ksym_exists("NO_SUCH_KFUNC").unwrap());
1281 }
1282
1283 #[test]
1284 fn test_malformed_scx_kfuncs() {
1285 let bad = super::malformed_scx_kfuncs();
1289 assert!(bad.iter().all(|n| !n.ends_with("_impl")));
1290 }
1291}