btf_relocations.rs 12 KB

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