btf_relocations.rs 13 KB

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