btf_relocations.rs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. use anyhow::{bail, Context as _, Result};
  2. use procfs::KernelVersion;
  3. use std::{path::PathBuf, process::Command, thread::sleep, time::Duration};
  4. use tempfile::TempDir;
  5. use aya::{maps::Array, programs::TracePoint, BpfLoader, Btf, Endianness};
  6. // In the tests below we often use values like 0xAAAAAAAA or -0x7AAAAAAA. Those values have no
  7. // special meaning, they just have "nice" bit patterns that can be helpful while debugging.
  8. #[test]
  9. fn relocate_field() {
  10. let test = RelocationTest {
  11. local_definition: r#"
  12. struct foo {
  13. __u8 a;
  14. __u8 b;
  15. __u8 c;
  16. __u8 d;
  17. };
  18. "#,
  19. target_btf: r#"
  20. struct foo {
  21. __u8 a;
  22. __u8 c;
  23. __u8 b;
  24. __u8 d;
  25. } s1;
  26. "#,
  27. relocation_code: r#"
  28. __u8 memory[] = {1, 2, 3, 4};
  29. struct foo *ptr = (struct foo *) &memory;
  30. value = __builtin_preserve_access_index(ptr->c);
  31. "#,
  32. }
  33. .build()
  34. .unwrap();
  35. assert_eq!(test.run().unwrap(), 2);
  36. assert_eq!(test.run_no_btf().unwrap(), 3);
  37. }
  38. #[test]
  39. fn relocate_enum() {
  40. let test = RelocationTest {
  41. local_definition: r#"
  42. enum foo { D = 0xAAAAAAAA };
  43. "#,
  44. target_btf: r#"
  45. enum foo { D = 0xBBBBBBBB } e1;
  46. "#,
  47. relocation_code: r#"
  48. #define BPF_ENUMVAL_VALUE 1
  49. value = __builtin_preserve_enum_value(*(typeof(enum foo) *)D, BPF_ENUMVAL_VALUE);
  50. "#,
  51. }
  52. .build()
  53. .unwrap();
  54. assert_eq!(test.run().unwrap(), 0xBBBBBBBB);
  55. assert_eq!(test.run_no_btf().unwrap(), 0xAAAAAAAA);
  56. }
  57. #[test]
  58. fn relocate_enum_signed() {
  59. let kernel_version = KernelVersion::current().unwrap();
  60. if kernel_version < KernelVersion::new(6, 0, 0) {
  61. eprintln!("skipping test on kernel {kernel_version:?}, support for signed enum was added in 6.0.0; see https://github.com/torvalds/linux/commit/6089fb3");
  62. return;
  63. }
  64. let test = RelocationTest {
  65. local_definition: r#"
  66. enum foo { D = -0x7AAAAAAA };
  67. "#,
  68. target_btf: r#"
  69. enum foo { D = -0x7BBBBBBB } e1;
  70. "#,
  71. relocation_code: r#"
  72. #define BPF_ENUMVAL_VALUE 1
  73. value = __builtin_preserve_enum_value(*(typeof(enum foo) *)D, BPF_ENUMVAL_VALUE);
  74. "#,
  75. }
  76. .build()
  77. .unwrap();
  78. assert_eq!(test.run().unwrap() as i64, -0x7BBBBBBBi64);
  79. assert_eq!(test.run_no_btf().unwrap() as i64, -0x7AAAAAAAi64);
  80. }
  81. #[test]
  82. fn relocate_enum64() {
  83. let kernel_version = KernelVersion::current().unwrap();
  84. if kernel_version < KernelVersion::new(6, 0, 0) {
  85. eprintln!("skipping test on kernel {kernel_version:?}, support for enum64 was added in 6.0.0; see https://github.com/torvalds/linux/commit/6089fb3");
  86. return;
  87. }
  88. let test = RelocationTest {
  89. local_definition: r#"
  90. enum foo { D = 0xAAAAAAAABBBBBBBB };
  91. "#,
  92. target_btf: r#"
  93. enum foo { D = 0xCCCCCCCCDDDDDDDD } e1;
  94. "#,
  95. relocation_code: r#"
  96. #define BPF_ENUMVAL_VALUE 1
  97. value = __builtin_preserve_enum_value(*(typeof(enum foo) *)D, BPF_ENUMVAL_VALUE);
  98. "#,
  99. }
  100. .build()
  101. .unwrap();
  102. assert_eq!(test.run().unwrap(), 0xCCCCCCCCDDDDDDDD);
  103. assert_eq!(test.run_no_btf().unwrap(), 0xAAAAAAAABBBBBBBB);
  104. }
  105. #[test]
  106. fn relocate_enum64_signed() {
  107. let kernel_version = KernelVersion::current().unwrap();
  108. if kernel_version < KernelVersion::new(6, 0, 0) {
  109. eprintln!("skipping test on kernel {kernel_version:?}, support for enum64 was added in 6.0.0; see https://github.com/torvalds/linux/commit/6089fb3");
  110. return;
  111. }
  112. let test = RelocationTest {
  113. local_definition: r#"
  114. enum foo { D = -0xAAAAAAABBBBBBBB };
  115. "#,
  116. target_btf: r#"
  117. enum foo { D = -0xCCCCCCCDDDDDDDD } e1;
  118. "#,
  119. relocation_code: r#"
  120. #define BPF_ENUMVAL_VALUE 1
  121. value = __builtin_preserve_enum_value(*(typeof(enum foo) *)D, BPF_ENUMVAL_VALUE);
  122. "#,
  123. }
  124. .build()
  125. .unwrap();
  126. assert_eq!(test.run().unwrap() as i64, -0xCCCCCCCDDDDDDDDi64);
  127. assert_eq!(test.run_no_btf().unwrap() as i64, -0xAAAAAAABBBBBBBBi64);
  128. }
  129. #[test]
  130. fn relocate_pointer() {
  131. let test = RelocationTest {
  132. local_definition: r#"
  133. struct foo {};
  134. struct bar { struct foo *f; };
  135. "#,
  136. target_btf: r#"
  137. struct foo {};
  138. struct bar { struct foo *f; };
  139. "#,
  140. relocation_code: r#"
  141. __u8 memory[] = {42, 0, 0, 0, 0, 0, 0, 0};
  142. struct bar* ptr = (struct bar *) &memory;
  143. value = (__u64) __builtin_preserve_access_index(ptr->f);
  144. "#,
  145. }
  146. .build()
  147. .unwrap();
  148. assert_eq!(test.run().unwrap(), 42);
  149. assert_eq!(test.run_no_btf().unwrap(), 42);
  150. }
  151. #[test]
  152. fn relocate_struct_flavors() {
  153. let definition = r#"
  154. struct foo {};
  155. struct bar { struct foo *f; };
  156. struct bar___cafe { struct foo *e; struct foo *f; };
  157. "#;
  158. let relocation_code = r#"
  159. __u8 memory[] = {42, 0, 0, 0, 0, 0, 0, 0, 21, 0, 0, 0, 0, 0, 0, 0};
  160. struct bar* ptr = (struct bar *) &memory;
  161. if (__builtin_preserve_field_info((((typeof(struct bar___cafe) *)0)->e), 2)) {
  162. value = (__u64) __builtin_preserve_access_index(((struct bar___cafe *)ptr)->e);
  163. } else {
  164. value = (__u64) __builtin_preserve_access_index(ptr->f);
  165. }
  166. "#;
  167. let test_no_flavor = RelocationTest {
  168. local_definition: definition,
  169. target_btf: definition,
  170. relocation_code,
  171. }
  172. .build()
  173. .unwrap();
  174. assert_eq!(test_no_flavor.run_no_btf().unwrap(), 42);
  175. }
  176. /// Utility code for running relocation tests:
  177. /// - Generates the eBPF program using probided local definition and relocation code
  178. /// - Generates the BTF from the target btf code
  179. struct RelocationTest {
  180. /// Data structure definition, local to the eBPF program and embedded in the eBPF bytecode
  181. local_definition: &'static str,
  182. /// Target data structure definition. What the vmlinux would actually contain.
  183. target_btf: &'static str,
  184. /// Code executed by the eBPF program to test the relocation.
  185. /// The format should be:
  186. // __u8 memory[] = { ... };
  187. // __u32 value = BPF_CORE_READ((struct foo *)&memory, ...);
  188. //
  189. // The generated code will be executed by attaching a tracepoint to sched_switch
  190. // and emitting `__u32 value` an a map. See the code template below for more details.
  191. relocation_code: &'static str,
  192. }
  193. impl RelocationTest {
  194. /// Build a RelocationTestRunner
  195. fn build(&self) -> Result<RelocationTestRunner> {
  196. Ok(RelocationTestRunner {
  197. ebpf: self.build_ebpf()?,
  198. btf: self.build_btf()?,
  199. })
  200. }
  201. /// - Generate the source eBPF filling a template
  202. /// - Compile it with clang
  203. fn build_ebpf(&self) -> Result<Vec<u8>> {
  204. let local_definition = self.local_definition;
  205. let relocation_code = self.relocation_code;
  206. let (_tmp_dir, compiled_file) = compile(&format!(
  207. r#"
  208. #include <linux/bpf.h>
  209. static long (*bpf_map_update_elem)(void *map, const void *key, const void *value, __u64 flags) = (void *) 2;
  210. {local_definition}
  211. struct {{
  212. int (*type)[BPF_MAP_TYPE_ARRAY];
  213. __u32 *key;
  214. __u64 *value;
  215. int (*max_entries)[1];
  216. }} output_map
  217. __attribute__((section(".maps"), used));
  218. __attribute__ ((noinline)) int bpf_func() {{
  219. __u32 key = 0;
  220. __u64 value = 0;
  221. {relocation_code}
  222. bpf_map_update_elem(&output_map, &key, &value, BPF_ANY);
  223. return 0;
  224. }}
  225. __attribute__((section("tracepoint/bpf_prog"), used))
  226. int bpf_prog(void *ctx) {{
  227. bpf_func();
  228. return 0;
  229. }}
  230. char _license[] __attribute__((section("license"), used)) = "GPL";
  231. "#
  232. ))
  233. .context("Failed to compile eBPF program")?;
  234. let bytecode =
  235. std::fs::read(compiled_file).context("Error reading compiled eBPF program")?;
  236. Ok(bytecode)
  237. }
  238. /// - Generate the target BTF source with a mock main()
  239. /// - Compile it with clang
  240. /// - Extract the BTF with llvm-objcopy
  241. fn build_btf(&self) -> Result<Btf> {
  242. let target_btf = self.target_btf;
  243. let relocation_code = self.relocation_code;
  244. // BTF files can be generated and inspected with these commands:
  245. // $ clang -c -g -O2 -target bpf target.c
  246. // $ pahole --btf_encode_detached=target.btf -V target.o
  247. // $ bpftool btf dump file ./target.btf format c
  248. let (tmp_dir, compiled_file) = compile(&format!(
  249. r#"
  250. #include <linux/bpf.h>
  251. {target_btf}
  252. int main() {{
  253. __u64 value = 0;
  254. // This is needed to make sure to emit BTF for the defined types,
  255. // it could be dead code eliminated if we don't.
  256. {relocation_code};
  257. return value;
  258. }}
  259. "#
  260. ))
  261. .context("Failed to compile BTF")?;
  262. let mut cmd = Command::new("llvm-objcopy");
  263. cmd.current_dir(tmp_dir.path())
  264. .args(["--dump-section", ".BTF=target.btf"])
  265. .arg(compiled_file);
  266. let status = cmd
  267. .status()
  268. .with_context(|| format!("Failed to run {cmd:?}"))?;
  269. match status.code() {
  270. Some(code) => match code {
  271. 0 => {}
  272. code => bail!("{cmd:?} exited with code {code}"),
  273. },
  274. None => bail!("{cmd:?} terminated by signal"),
  275. }
  276. let btf = Btf::parse_file(tmp_dir.path().join("target.btf"), Endianness::default())
  277. .context("Error parsing generated BTF")?;
  278. Ok(btf)
  279. }
  280. }
  281. /// Compile an eBPF program and return the path of the compiled object.
  282. /// Also returns a TempDir handler, dropping it will clear the created dicretory.
  283. fn compile(source_code: &str) -> Result<(TempDir, PathBuf)> {
  284. let tmp_dir = tempfile::tempdir().context("Error making temp dir")?;
  285. let source = tmp_dir.path().join("source.c");
  286. std::fs::write(&source, source_code).context("Writing bpf program failed")?;
  287. let mut cmd = Command::new("clang");
  288. cmd.current_dir(&tmp_dir)
  289. .args(["-c", "-g", "-O2", "-target", "bpf"])
  290. .arg(&source);
  291. let status = cmd
  292. .status()
  293. .with_context(|| format!("Failed to run {cmd:?}"))?;
  294. match status.code() {
  295. Some(code) => match code {
  296. 0 => {}
  297. code => bail!("{cmd:?} exited with code {code}"),
  298. },
  299. None => bail!("{cmd:?} terminated by signal"),
  300. }
  301. Ok((tmp_dir, source.with_extension("o")))
  302. }
  303. struct RelocationTestRunner {
  304. ebpf: Vec<u8>,
  305. btf: Btf,
  306. }
  307. impl RelocationTestRunner {
  308. /// Run test and return the output value
  309. fn run(&self) -> Result<u64> {
  310. self.run_internal(true).context("Error running with BTF")
  311. }
  312. /// Run without loading btf
  313. fn run_no_btf(&self) -> Result<u64> {
  314. self.run_internal(false)
  315. .context("Error running without BTF")
  316. }
  317. fn run_internal(&self, with_relocations: bool) -> Result<u64> {
  318. let mut loader = BpfLoader::new();
  319. if with_relocations {
  320. loader.btf(Some(&self.btf));
  321. } else {
  322. loader.btf(None);
  323. }
  324. let mut bpf = loader.load(&self.ebpf).context("Loading eBPF failed")?;
  325. let program: &mut TracePoint = bpf
  326. .program_mut("bpf_prog")
  327. .context("bpf_prog not found")?
  328. .try_into()
  329. .context("program not a tracepoint")?;
  330. program.load().context("Loading tracepoint failed")?;
  331. // Attach to sched_switch and wait some time to make sure it executed at least once
  332. program
  333. .attach("sched", "sched_switch")
  334. .context("attach failed")?;
  335. sleep(Duration::from_millis(1000));
  336. // To inspect the loaded eBPF bytecode, increse the timeout and run:
  337. // $ sudo bpftool prog dump xlated name bpf_prog
  338. let output_map: Array<_, u64> = bpf.take_map("output_map").unwrap().try_into().unwrap();
  339. let key = 0;
  340. output_map.get(&key, 0).context("Getting key 0 failed")
  341. }
  342. }