4
0

btf_relocations.rs 11 KB

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