1use anyhow::{anyhow, bail, Context, Result};
7use libbpf_rs::libbpf_sys::*;
8use libbpf_rs::{AsRawLibbpf, OpenProgramImpl, ProgramImpl};
9use log::{error, warn};
10use std::env;
11use std::ffi::c_void;
12use std::ffi::CStr;
13use std::ffi::CString;
14use std::io;
15use std::io::BufRead;
16use std::io::BufReader;
17use std::mem::size_of;
18use std::slice::from_raw_parts;
19
20const PROCFS_MOUNTS: &str = "/proc/mounts";
21const TRACEFS: &str = "tracefs";
22const DEBUGFS: &str = "debugfs";
23
24mod enums_abi {
25 include!("enums_abi.autogen.rs");
26}
27
28lazy_static::lazy_static! {
29 pub static ref SCX_OPS_KEEP_BUILTIN_IDLE: u64 =
30 read_enum("scx_ops_flags", "SCX_OPS_KEEP_BUILTIN_IDLE").unwrap_or(0);
31 pub static ref SCX_OPS_ENQ_LAST: u64 =
32 read_enum("scx_ops_flags", "SCX_OPS_ENQ_LAST").unwrap_or(0);
33 pub static ref SCX_OPS_ENQ_EXITING: u64 =
34 read_enum("scx_ops_flags", "SCX_OPS_ENQ_EXITING").unwrap_or(0);
35 pub static ref SCX_OPS_SWITCH_PARTIAL: u64 =
36 read_enum("scx_ops_flags", "SCX_OPS_SWITCH_PARTIAL").unwrap_or(0);
37 pub static ref SCX_OPS_ENQ_MIGRATION_DISABLED: u64 =
38 read_enum("scx_ops_flags", "SCX_OPS_ENQ_MIGRATION_DISABLED").unwrap_or(0);
39 pub static ref SCX_OPS_ALLOW_QUEUED_WAKEUP: u64 =
40 read_enum("scx_ops_flags", "SCX_OPS_ALLOW_QUEUED_WAKEUP").unwrap_or(0);
41 pub static ref SCX_OPS_BUILTIN_IDLE_PER_NODE: u64 =
42 read_enum("scx_ops_flags", "SCX_OPS_BUILTIN_IDLE_PER_NODE").unwrap_or(0);
43 pub static ref SCX_OPS_ALWAYS_ENQ_IMMED: u64 =
44 read_enum("scx_ops_flags", "SCX_OPS_ALWAYS_ENQ_IMMED").unwrap_or(0);
45
46 pub static ref SCX_PICK_IDLE_CORE: u64 =
47 read_enum("scx_pick_idle_cpu_flags", "SCX_PICK_IDLE_CORE").unwrap_or(0);
48 pub static ref SCX_PICK_IDLE_IN_NODE: u64 =
49 read_enum("scx_pick_idle_cpu_flags", "SCX_PICK_IDLE_IN_NODE").unwrap_or(0);
50
51 pub static ref ROOT_PREFIX: String =
52 env::var("SCX_SYSFS_PREFIX").unwrap_or("".to_string());
53}
54
55fn load_vmlinux_btf() -> &'static mut btf {
56 let btf = unsafe { btf__load_vmlinux_btf() };
57 if btf.is_null() {
58 panic!("btf__load_vmlinux_btf() returned NULL, was CONFIG_DEBUG_INFO_BTF enabled?")
59 }
60 unsafe { &mut *btf }
61}
62
63lazy_static::lazy_static! {
64 static ref VMLINUX_BTF: &'static mut btf = load_vmlinux_btf();
65}
66
67fn btf_kind(t: &btf_type) -> u32 {
68 (t.info >> 24) & 0x1f
69}
70
71fn btf_vlen(t: &btf_type) -> u32 {
72 t.info & 0xffff
73}
74
75fn btf_type_plus_1(t: &btf_type) -> *const c_void {
76 let ptr_val = t as *const btf_type as usize;
77 (ptr_val + size_of::<btf_type>()) as *const c_void
78}
79
80fn btf_enum(t: &btf_type) -> &[btf_enum] {
81 let ptr = btf_type_plus_1(t);
82 unsafe { from_raw_parts(ptr as *const btf_enum, btf_vlen(t) as usize) }
83}
84
85fn btf_enum64(t: &btf_type) -> &[btf_enum64] {
86 let ptr = btf_type_plus_1(t);
87 unsafe { from_raw_parts(ptr as *const btf_enum64, btf_vlen(t) as usize) }
88}
89
90fn btf_members(t: &btf_type) -> &[btf_member] {
91 let ptr = btf_type_plus_1(t);
92 unsafe { from_raw_parts(ptr as *const btf_member, btf_vlen(t) as usize) }
93}
94
95fn btf_params(t: &btf_type) -> &[btf_param] {
96 let ptr = btf_type_plus_1(t);
97 unsafe { from_raw_parts(ptr as *const btf_param, btf_vlen(t) as usize) }
98}
99
100fn btf_name_str_by_offset(btf: &btf, name_off: u32) -> Result<&str> {
101 let n = unsafe { btf__name_by_offset(btf, name_off) };
102 if n.is_null() {
103 bail!("btf__name_by_offset() returned NULL");
104 }
105 Ok(unsafe { CStr::from_ptr(n) }
106 .to_str()
107 .with_context(|| format!("Failed to convert {:?} to string", n))?)
108}
109
110fn recover_truncated_enum64_from(
137 table: &[(&str, &str, u64)],
138 type_name: &str,
139 name: &str,
140 lo32: u32,
141) -> Result<u64> {
142 static WARN_ONCE: std::sync::Once = std::sync::Once::new();
143
144 let Some(&(_, _, abi_val)) = table.iter().find(|(t, n, _)| *t == type_name && *n == name)
145 else {
146 let msg = format!(
150 "kernel BTF truncates 64-bit enum {}::{} to 0x{:x}; 64-bit \
151 variant not found in vmlinux.h",
152 type_name, name, lo32
153 );
154 error!("{}", msg);
155 bail!(msg);
156 };
157
158 if abi_val <= u32::MAX as u64 {
159 return Ok(lo32 as u64);
160 }
161
162 if abi_val as u32 != lo32 {
163 let msg = format!(
165 "kernel BTF value of {}::{} (0x{:x}) doesn't match the low 32 bits \
166 of the vmlinux.h value (0x{:x}); refusing to substitute",
167 type_name, name, lo32, abi_val
168 );
169 error!("{}", msg);
170 bail!(msg);
171 }
172
173 WARN_ONCE.call_once(|| {
174 warn!(
175 "kernel BTF lacks BTF_KIND_ENUM64 encoding (generated by \
176 pahole < 1.24 or with --skip_encoding_btf_enum64), so 64-bit \
177 scx enum values are truncated to their low 32 bits in kernel \
178 BTF. Substituting the full 64-bit values from the vmlinux.h \
179 this binary was built against, cross-checked against the low \
180 32 bits the kernel does provide. The high 32 bits cannot be \
181 verified: if the running kernel's actual values differ from \
182 the build-time vmlinux.h (e.g. an enum that moved in a newer \
183 kernel), the scheduler will operate on bogus values, such as \
184 dispatching to nonexistent DSQ ids, and can wildly malfunction."
185 );
186 });
187 Ok(abi_val)
188}
189
190fn recover_truncated_enum64(type_name: &str, name: &str, lo32: u32) -> Result<u64> {
191 recover_truncated_enum64_from(enums_abi::ENUM_ABI_VALUES, type_name, name, lo32)
192}
193
194pub fn read_enum(type_name: &str, name: &str) -> Result<u64> {
195 let btf: &btf = *VMLINUX_BTF;
196
197 let c_type_name = CString::new(type_name).unwrap();
198 let tid = unsafe { btf__find_by_name(btf, c_type_name.as_ptr()) };
199 if tid < 0 {
200 bail!("type {:?} doesn't exist, ret={}", type_name, tid);
201 }
202
203 let t = unsafe { btf__type_by_id(btf, tid as _) };
204 if t.is_null() {
205 bail!("btf__type_by_id({}) returned NULL", tid);
206 }
207 let t = unsafe { &*t };
208
209 match btf_kind(t) {
210 BTF_KIND_ENUM => {
211 for e in btf_enum(t).iter() {
212 if btf_name_str_by_offset(btf, e.name_off)? == name {
213 if unsafe { t.__bindgen_anon_1.size } == 8 && type_name.starts_with("scx_") {
218 return recover_truncated_enum64(type_name, name, e.val as u32);
219 }
220 return Ok(e.val as u64);
221 }
222 }
223 }
224 BTF_KIND_ENUM64 => {
225 for e in btf_enum64(t).iter() {
226 if btf_name_str_by_offset(btf, e.name_off)? == name {
227 return Ok(((e.val_hi32 as u64) << 32) | (e.val_lo32) as u64);
228 }
229 }
230 }
231 _ => (),
232 }
233
234 Err(anyhow!("{:?} doesn't exist in {:?}", name, type_name))
235}
236
237pub fn read_enum_any(type_names: &[&str], name: &str) -> Result<u64> {
239 let mut errors = Vec::new();
240
241 for type_name in type_names {
242 match read_enum(type_name, name) {
243 Ok(val) => return Ok(val),
244 Err(err) => errors.push(format!("{}: {:#}", type_name, err)),
245 }
246 }
247
248 bail!(
249 "{:?} doesn't exist in any of {:?}: {}",
250 name,
251 type_names,
252 errors.join("; ")
253 )
254}
255
256pub fn struct_has_field(type_name: &str, field: &str) -> Result<bool> {
257 let btf: &btf = *VMLINUX_BTF;
258
259 let c_type_name = CString::new(type_name).unwrap();
260 let tid = unsafe { btf__find_by_name_kind(btf, c_type_name.as_ptr(), BTF_KIND_STRUCT) };
261 if tid < 0 {
262 bail!("type {:?} doesn't exist, ret={}", type_name, tid);
263 }
264
265 let t = unsafe { btf__type_by_id(btf, tid as _) };
266 if t.is_null() {
267 bail!("btf__type_by_id({}) returned NULL", tid);
268 }
269 let t = unsafe { &*t };
270
271 for m in btf_members(t).iter() {
272 if btf_name_str_by_offset(btf, m.name_off)? == field {
273 return Ok(true);
274 }
275 }
276
277 Ok(false)
278}
279
280pub fn ksym_exists(ksym: &str) -> Result<bool> {
281 let btf: &btf = *VMLINUX_BTF;
282
283 let ksym_name = CString::new(ksym).unwrap();
284 let tid = unsafe { btf__find_by_name(btf, ksym_name.as_ptr()) };
285 Ok(tid >= 0)
286}
287
288pub fn malformed_scx_kfuncs() -> Vec<String> {
298 let btf: &btf = *VMLINUX_BTF;
299 let mut bad = Vec::new();
300 let cnt = unsafe { btf__type_cnt(btf) };
301
302 for id in 1..cnt {
303 let t = unsafe { btf__type_by_id(btf, id) };
304 if t.is_null() {
305 continue;
306 }
307 let t = unsafe { &*t };
308 if btf_kind(t) != BTF_KIND_FUNC {
309 continue;
310 }
311
312 let Ok(name) = btf_name_str_by_offset(btf, t.name_off) else {
313 continue;
314 };
315 if !(name.starts_with("scx_bpf_") || name.starts_with("__scx_bpf_")) {
316 continue;
317 }
318 if name.ends_with("_impl") {
324 continue;
325 }
326
327 let proto_id = unsafe { t.__bindgen_anon_1.type_ };
329 let pt = unsafe { btf__type_by_id(btf, proto_id) };
330 if pt.is_null() {
331 continue;
332 }
333 let pt = unsafe { &*pt };
334 if btf_kind(pt) != BTF_KIND_FUNC_PROTO {
335 continue;
336 }
337
338 if btf_params(pt).iter().any(|p| {
339 p.name_off != 0 && matches!(btf_name_str_by_offset(btf, p.name_off), Ok("aux"))
340 }) {
341 bad.push(name.to_string());
342 }
343 }
344
345 bad
346}
347
348pub fn in_kallsyms(ksym: &str) -> Result<bool> {
349 let file = std::fs::File::open("/proc/kallsyms")?;
350 let reader = std::io::BufReader::new(file);
351
352 for line in reader.lines() {
353 for sym in line.unwrap().split_whitespace() {
354 if ksym == sym {
355 return Ok(true);
356 }
357 }
358 }
359
360 Ok(false)
361}
362
363pub fn get_fs_mount(mount_type: &str) -> Result<Vec<std::path::PathBuf>> {
365 let proc_mounts_path = std::path::Path::new(PROCFS_MOUNTS);
366
367 let file = std::fs::File::open(proc_mounts_path)
368 .with_context(|| format!("Failed to open {}", proc_mounts_path.display()))?;
369
370 let reader = BufReader::new(file);
371
372 let mut mounts = Vec::new();
373 for line in reader.lines() {
374 let line = line.context("Failed to read line from /proc/mounts")?;
375 let mount_info: Vec<&str> = line.split_whitespace().collect();
376
377 if mount_info.len() > 3 && mount_info[2] == mount_type {
378 let mount_path = std::path::PathBuf::from(mount_info[1]);
379 mounts.push(mount_path);
380 }
381 }
382
383 Ok(mounts)
384}
385
386pub fn tracefs_mount() -> Result<std::path::PathBuf> {
388 let mounts = get_fs_mount(TRACEFS)?;
389 mounts.into_iter().next().context("No tracefs mount found")
390}
391
392pub fn debugfs_mount() -> Result<std::path::PathBuf> {
394 let mounts = get_fs_mount(DEBUGFS)?;
395 mounts.into_iter().next().context("No debugfs mount found")
396}
397
398pub fn tracer_available(tracer: &str) -> Result<bool> {
399 let base_path = tracefs_mount().unwrap_or_else(|_| debugfs_mount().unwrap().join("tracing"));
400 let file = match std::fs::File::open(base_path.join("available_tracers")) {
401 Ok(f) => f,
402 Err(_) => return Ok(false),
403 };
404 let reader = std::io::BufReader::new(file);
405
406 for line in reader.lines() {
407 for tc in line.unwrap().split_whitespace() {
408 if tracer == tc {
409 return Ok(true);
410 }
411 }
412 }
413
414 Ok(false)
415}
416
417pub fn tracepoint_exists(tracepoint: &str) -> Result<bool> {
418 let base_path = tracefs_mount().unwrap_or_else(|_| debugfs_mount().unwrap().join("tracing"));
419 let file = match std::fs::File::open(base_path.join("available_events")) {
420 Ok(f) => f,
421 Err(_) => return Ok(false),
422 };
423 let reader = std::io::BufReader::new(file);
424
425 for line in reader.lines() {
426 for tp in line.unwrap().split_whitespace() {
427 if tracepoint == tp {
428 return Ok(true);
429 }
430 }
431 }
432
433 Ok(false)
434}
435
436pub fn cond_kprobe_enable<T>(sym: &str, prog_ptr: &OpenProgramImpl<T>) -> Result<bool> {
437 if in_kallsyms(sym)? {
438 unsafe {
439 bpf_program__set_autoload(prog_ptr.as_libbpf_object().as_ptr(), true);
440 }
441 return Ok(true);
442 } else {
443 warn!("symbol {sym} is missing, kprobe not loaded");
444 }
445
446 Ok(false)
447}
448
449pub fn cond_kprobes_enable<T>(kprobes: Vec<(&str, &OpenProgramImpl<T>)>) -> Result<bool> {
450 for (sym, _) in kprobes.iter() {
452 if in_kallsyms(sym)? == false {
453 warn!("symbol {sym} is missing, kprobe not loaded");
454 return Ok(false);
455 }
456 }
457
458 for (_, ptr) in kprobes.iter() {
460 unsafe {
461 bpf_program__set_autoload(ptr.as_libbpf_object().as_ptr(), true);
462 }
463 }
464
465 Ok(true)
466}
467
468pub fn cond_kprobe_load<T>(sym: &str, prog_ptr: &OpenProgramImpl<T>) -> Result<bool> {
469 if in_kallsyms(sym)? {
470 unsafe {
471 bpf_program__set_autoload(prog_ptr.as_libbpf_object().as_ptr(), true);
472 bpf_program__set_autoattach(prog_ptr.as_libbpf_object().as_ptr(), false);
473 }
474 return Ok(true);
475 } else {
476 warn!("symbol {sym} is missing, kprobe not loaded");
477 }
478
479 Ok(false)
480}
481
482pub fn cond_kprobe_attach<T>(sym: &str, prog_ptr: &ProgramImpl<T>) -> Result<bool> {
483 if in_kallsyms(sym)? {
484 unsafe {
485 bpf_program__attach(prog_ptr.as_libbpf_object().as_ptr());
486 }
487 return Ok(true);
488 } else {
489 warn!("symbol {sym} is missing, kprobe not loaded");
490 }
491
492 Ok(false)
493}
494
495pub fn cond_tracepoint_enable<T>(tracepoint: &str, prog_ptr: &OpenProgramImpl<T>) -> Result<bool> {
496 if tracepoint_exists(tracepoint)? {
497 unsafe {
498 bpf_program__set_autoload(prog_ptr.as_libbpf_object().as_ptr(), true);
499 }
500 return Ok(true);
501 } else {
502 warn!("tracepoint {tracepoint} is missing, tracepoint not loaded");
503 }
504
505 Ok(false)
506}
507
508pub fn cond_tracepoints_enable<T>(tracepoints: Vec<(&str, &OpenProgramImpl<T>)>) -> Result<bool> {
509 for (tp, _) in tracepoints.iter() {
511 if tracepoint_exists(tp)? == false {
512 warn!("tracepoint {tp} is missing, tracepoint not loaded");
513 return Ok(false);
514 }
515 }
516
517 for (_, ptr) in tracepoints.iter() {
519 unsafe {
520 bpf_program__set_autoload(ptr.as_libbpf_object().as_ptr(), true);
521 }
522 }
523
524 Ok(true)
525}
526
527pub fn is_sched_ext_enabled() -> io::Result<bool> {
528 let content = std::fs::read_to_string("/sys/kernel/sched_ext/state")?;
529
530 match content.trim() {
531 "enabled" => Ok(true),
532 "disabled" => Ok(false),
533 _ => {
534 Err(io::Error::new(
536 io::ErrorKind::InvalidData,
537 "Unexpected content in /sys/kernel/sched_ext/state",
538 ))
539 }
540 }
541}
542
543#[macro_export]
544macro_rules! unwrap_or_break {
545 ($expr: expr, $label: lifetime) => {{
546 match $expr {
547 Ok(val) => val,
548 Err(e) => break $label Err(e),
549 }
550 }};
551}
552
553pub fn check_min_requirements() -> Result<()> {
554 if let Ok(false) | Err(_) = struct_has_field("sched_ext_ops", "dump") {
557 bail!("sched_ext_ops.dump() missing, kernel too old?");
558 }
559 Ok(())
560}
561
562#[rustfmt::skip]
567#[macro_export]
568macro_rules! scx_ops_open {
569 ($builder: expr, $obj_ref: expr, $ops: ident, $open_opts: expr) => { 'block: {
570 scx_utils::paste! {
571 scx_utils::unwrap_or_break!(scx_utils::compat::check_min_requirements(), 'block);
572 use ::anyhow::Context;
573 use ::libbpf_rs::skel::SkelBuilder;
574
575 let mut skel = match $open_opts {
576 Some(opts_ref) => { match $builder.open_opts(opts_ref, $obj_ref).context("Failed to open BPF program with options") {
578 Ok(val) => val,
579 Err(e) => break 'block Err(e),
580 }
581 }
582 None => {
583 match $builder.open($obj_ref).context("Failed to open BPF program") {
584 Ok(val) => val,
585 Err(e) => break 'block Err(e),
586 }
587 }
588 };
589
590 let ops = skel.struct_ops.[<$ops _mut>]();
591 let path = std::path::Path::new("/sys/kernel/sched_ext/hotplug_seq");
592
593 let val = match std::fs::read_to_string(&path) {
594 Ok(val) => val,
595 Err(_) => {
596 break 'block Err(anyhow::anyhow!("Failed to open or read file {:?}", path));
597 }
598 };
599
600 ops.hotplug_seq = match val.trim().parse::<u64>() {
601 Ok(parsed) => parsed,
602 Err(_) => {
603 break 'block Err(anyhow::anyhow!("Failed to parse hotplug seq {}", val));
604 }
605 };
606
607 if let Ok(s) = ::std::env::var("SCX_TIMEOUT_MS") {
608 skel.struct_ops.[<$ops _mut>]().timeout_ms = match s.parse::<u32>() {
609 Ok(ms) => {
610 ::scx_utils::info!("Setting timeout_ms to {} based on environment", ms);
611 ms
612 },
613 Err(e) => {
614 break 'block anyhow::Result::Err(e).context("SCX_TIMEOUT_MS has invalid value");
615 },
616 };
617 }
618
619 {
620 let ops = skel.struct_ops.[<$ops _mut>]();
621
622 let name_field = &mut ops.name;
623
624 let version_suffix = ::scx_utils::build_id::ops_version_suffix(env!("CARGO_PKG_VERSION"));
625 let bytes = version_suffix.as_bytes();
626 let mut i = 0;
627 let mut bytes_idx = 0;
628 let mut found_null = false;
629
630 while i < name_field.len() - 1 {
631 found_null |= name_field[i] == 0;
632 if !found_null {
633 i += 1;
634 continue;
635 }
636
637 if bytes_idx < bytes.len() {
638 name_field[i] = bytes[bytes_idx] as i8;
639 bytes_idx += 1;
640 } else {
641 break;
642 }
643 i += 1;
644 }
645 name_field[i] = 0;
646 }
647
648 $crate::import_enums!(skel);
649
650 let result = ::anyhow::Result::Ok(skel);
651
652 result
653 }
654 }};
655}
656
657#[rustfmt::skip]
662#[macro_export]
663macro_rules! scx_ops_load {
664 ($skel: expr, $ops: ident, $uei: ident) => { 'block: {
665 scx_utils::paste! {
666 use ::anyhow::Context;
667 use ::libbpf_rs::skel::OpenSkel;
668
669 {
670 let ops = $skel.struct_ops.[<$ops _mut>]();
671 if ops.sub_cgroup_id > 0 {
672 if let Ok(false) | Err(_) = scx_utils::compat::struct_has_field("sched_ext_ops", "sub_cgroup_id") {
673 ::scx_utils::warn!("kernel doesn't support ops.sub_cgroup_id");
674 ops.sub_cgroup_id = 0;
675 }
676 }
677 }
678
679 scx_utils::uei_set_size!($skel, $ops, $uei);
680 $skel.load().context("Failed to load BPF program").map_err(|e| {
681 let bad = scx_utils::compat::malformed_scx_kfuncs();
682 if bad.is_empty() {
683 e
684 } else {
685 e.context(format!(
686 "the running kernel's BTF has malformed scx kfunc prototype(s): {}.\n\
687 \n\
688 These kfuncs are KF_IMPLICIT_ARGS but their public BTF prototype\n\
689 still carries the implicit 'struct bpf_prog_aux *' argument, which\n\
690 makes BPF programs fail to load with 'func_proto incompatible with\n\
691 vmlinux'. This happens when the kernel was built with pahole < 1.26.\n\
692 \n\
693 Fix: boot a kernel whose BTF was generated with pahole >= 1.26.\n\
694 Affected distros include Ubuntu 24.04 LTS. See kernel commit\n\
695 9edd04c4189e (\"docs: Raise minimum pahole version to 1.26 for\n\
696 KF_IMPLICIT_ARGS kfuncs\").",
697 bad.join(", ")
698 ))
699 }
700 })
701 }
702 }};
703}
704
705#[rustfmt::skip]
707#[macro_export]
708macro_rules! scx_ops_attach {
709 ($skel: expr, $ops: ident) => {
710 scx_ops_attach!($skel, $ops, false)
711 };
712 ($skel: expr, $ops: ident, $is_subsched: expr) => { 'block: {
713 use ::anyhow::Context;
714 use ::libbpf_rs::skel::Skel;
715
716 if !$is_subsched && scx_utils::compat::is_sched_ext_enabled().unwrap_or(false) {
717 break 'block Err(anyhow::anyhow!(
718 "another sched_ext scheduler is already running"
719 ));
720 }
721 $skel
722 .attach()
723 .context("Failed to attach non-struct_ops BPF programs")
724 .and_then(|_| {
725 $skel
726 .maps
727 .$ops
728 .attach_struct_ops()
729 .context("Failed to attach struct_ops BPF programs")
730 })
731 }};
732}
733
734#[cfg(test)]
735mod tests {
736 #[test]
737 fn test_read_enum() {
738 assert_eq!(super::read_enum("pid_type", "PIDTYPE_TGID").unwrap(), 1);
739 }
740
741 #[test]
742 fn test_read_enum_any() {
743 assert_eq!(
744 super::read_enum_any(&["NO_SUCH_TYPE", "pid_type"], "PIDTYPE_TGID").unwrap(),
745 1
746 );
747 assert!(super::read_enum_any(&["NO_SUCH_TYPE", "pid_type"], "NO_SUCH_ENUM").is_err());
748 }
749
750 #[test]
751 fn test_recover_truncated_enum64() {
752 let table: &[(&str, &str, u64)] = &[
753 ("scx_dsq_id_flags", "SCX_DSQ_LOCAL", 0x8000000000000002),
754 ("scx_enq_flags", "SCX_ENQ_PREEMPT", 0x100000000),
755 ("scx_enq_flags", "SCX_ENQ_HEAD", 0x10000),
756 ];
757
758 assert_eq!(
760 super::recover_truncated_enum64_from(table, "scx_dsq_id_flags", "SCX_DSQ_LOCAL", 2)
761 .unwrap(),
762 0x8000000000000002
763 );
764 assert_eq!(
765 super::recover_truncated_enum64_from(table, "scx_enq_flags", "SCX_ENQ_PREEMPT", 0)
766 .unwrap(),
767 0x100000000
768 );
769 assert_eq!(
772 super::recover_truncated_enum64_from(table, "scx_enq_flags", "SCX_ENQ_HEAD", 0x20000)
773 .unwrap(),
774 0x20000
775 );
776 assert!(super::recover_truncated_enum64_from(
778 table,
779 "scx_dsq_id_flags",
780 "SCX_DSQ_LOCAL",
781 3
782 )
783 .is_err());
784 assert!(
786 super::recover_truncated_enum64_from(table, "scx_enq_flags", "SCX_ENQ_NEW", 7).is_err()
787 );
788 }
789
790 #[test]
791 fn test_enum_abi_table() {
792 let find = |t: &str, n: &str| {
795 super::enums_abi::ENUM_ABI_VALUES
796 .iter()
797 .find(|(ty, na, _)| *ty == t && *na == n)
798 .map(|&(_, _, v)| v)
799 };
800 assert_eq!(
801 find("scx_dsq_id_flags", "SCX_DSQ_FLAG_BUILTIN"),
802 Some(1 << 63)
803 );
804 assert_eq!(
805 find("scx_dsq_id_flags", "SCX_DSQ_LOCAL"),
806 Some((1 << 63) | 2)
807 );
808 assert_eq!(
809 find("scx_dsq_id_flags", "SCX_DSQ_LOCAL_ON"),
810 Some((1 << 63) | (1 << 62))
811 );
812 assert_eq!(find("scx_public_consts", "SCX_SLICE_INF"), Some(u64::MAX));
813 assert_eq!(find("scx_enq_flags", "SCX_ENQ_PREEMPT"), Some(1 << 32));
814 }
815
816 #[test]
817 fn test_struct_has_field() {
818 assert!(super::struct_has_field("task_struct", "flags").unwrap());
819 assert!(!super::struct_has_field("task_struct", "NO_SUCH_FIELD").unwrap());
820 assert!(super::struct_has_field("NO_SUCH_STRUCT", "NO_SUCH_FIELD").is_err());
821 }
822
823 #[test]
824 fn test_ksym_exists() {
825 assert!(super::ksym_exists("bpf_task_acquire").unwrap());
826 assert!(!super::ksym_exists("NO_SUCH_KFUNC").unwrap());
827 }
828
829 #[test]
830 fn test_malformed_scx_kfuncs() {
831 let bad = super::malformed_scx_kfuncs();
835 assert!(bad.iter().all(|n| !n.ends_with("_impl")));
836 }
837}