relocations.rs 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. use anyhow::{Context, Result};
  2. use std::{process::Command, thread::sleep, time::Duration};
  3. use aya::{maps::Array, programs::TracePoint, BpfLoader, Btf, Endianness};
  4. use super::{integration_test, IntegrationTest};
  5. #[integration_test]
  6. fn relocate_field() {
  7. let test = RelocationTest {
  8. local_definition: r#"
  9. struct foo {
  10. __u8 a;
  11. __u8 b;
  12. __u8 c;
  13. __u8 d;
  14. } __attribute__((preserve_access_index));
  15. "#,
  16. target_btf: r#"
  17. struct foo {
  18. __u8 a;
  19. __u8 c;
  20. __u8 b;
  21. __u8 d;
  22. } s1;
  23. "#,
  24. relocation_code: r#"
  25. __u8 memory[] = {1, 2, 3, 4};
  26. __u32 value = BPF_CORE_READ((struct foo *)&memory, c);
  27. "#,
  28. }
  29. .build()
  30. .unwrap();
  31. assert_eq!(test.run().unwrap(), 2);
  32. assert_eq!(test.run_no_btf().unwrap(), 3);
  33. }
  34. #[integration_test]
  35. fn relocate_enum() {
  36. let test = RelocationTest {
  37. local_definition: r#"
  38. enum foo { D = 1 };
  39. "#,
  40. target_btf: r#"
  41. enum foo { D = 4 } e1;
  42. "#,
  43. relocation_code: r#"
  44. __u32 value = bpf_core_enum_value(enum foo, D);
  45. "#,
  46. }
  47. .build()
  48. .unwrap();
  49. assert_eq!(test.run().unwrap(), 4);
  50. assert_eq!(test.run_no_btf().unwrap(), 0);
  51. }
  52. /// Utility code for running relocation tests:
  53. /// - Generates the eBPF program using probided local definition and relocation code
  54. /// - Generates the BTF from the target btf code
  55. struct RelocationTest {
  56. /// Data structure definition, local to the eBPF program and embedded in the eBPF bytecode
  57. local_definition: &'static str,
  58. /// Target data structure definition. What the vmlinux would actually contain.
  59. target_btf: &'static str,
  60. /// Code executed by the eBPF program to test the relocation.
  61. /// The format should be:
  62. // __u8 memory[] = { ... };
  63. // __u32 value = BPF_CORE_READ((struct foo *)&memory, ...);
  64. //
  65. // The generated code will be executed by attaching a tracepoint to sched_switch
  66. // and emitting `__u32 value` an a map. See the code template below for more details.
  67. relocation_code: &'static str,
  68. }
  69. impl RelocationTest {
  70. /// Build a RelocationTestRunner
  71. fn build(&self) -> Result<RelocationTestRunner> {
  72. Ok(RelocationTestRunner {
  73. ebpf: self.build_ebpf()?,
  74. btf: self.build_btf()?,
  75. })
  76. }
  77. /// - Generate the source eBPF filling a template
  78. /// - Compile it with clang
  79. fn build_ebpf(&self) -> Result<Vec<u8>> {
  80. let local_definition = self.local_definition;
  81. let relocation_code = self.relocation_code;
  82. let tmp_dir = tempfile::tempdir().context("Error making temp dir")?;
  83. let ebpf_file = tmp_dir.path().join("ebpf_program.c");
  84. std::fs::write(
  85. &ebpf_file,
  86. format!(
  87. r#"
  88. #include <linux/bpf.h>
  89. #include <bpf/bpf_core_read.h>
  90. #include <bpf/bpf_helpers.h>
  91. #include <bpf/bpf_tracing.h>
  92. {local_definition}
  93. struct {{
  94. __uint(type, BPF_MAP_TYPE_ARRAY);
  95. __type(key, __u32);
  96. __type(value, __u32);
  97. __uint(max_entries, 1);
  98. }} output_map SEC(".maps");
  99. SEC("tracepoint/bpf_prog") int bpf_prog(void *ctx) {{
  100. __u32 key = 0;
  101. {relocation_code}
  102. bpf_map_update_elem(&output_map, &key, &value, BPF_ANY);
  103. return 0;
  104. }}
  105. char _license[] SEC("license") = "GPL";
  106. "#
  107. ),
  108. )
  109. .context("Writing bpf program failed")?;
  110. Command::new("clang")
  111. .current_dir(tmp_dir.path())
  112. .args(["-c", "-g", "-O2", "-target", "bpf"])
  113. .args([
  114. "-I",
  115. &std::env::var("LIBBPF_INCLUDE").context("LIBBPF_INCLUDE not set")?,
  116. ])
  117. .arg(&ebpf_file)
  118. .status()
  119. .context("Failed to run clang")?
  120. .success()
  121. .then_some(())
  122. .context("Failed to compile eBPF program")?;
  123. let ebpf = std::fs::read(ebpf_file.with_extension("o"))
  124. .context("Error reading compiled eBPF program")?;
  125. Ok(ebpf)
  126. }
  127. /// - Generate the target BTF source with a mock main()
  128. /// - Compile it with clang
  129. /// - Extract the BTF with pahole
  130. fn build_btf(&self) -> Result<Btf> {
  131. let target_btf = self.target_btf;
  132. let tmp_dir = tempfile::tempdir().context("Error making temp dir")?;
  133. // BTF files can be generated and inspected with these commands:
  134. // $ clang -c -g -O2 -target bpf target.c
  135. // $ pahole --btf_encode_detached=target.btf -V target.o
  136. // $ bpftool btf dump file ./target.btf format c
  137. let btf_file = tmp_dir.path().join("target_btf.c");
  138. std::fs::write(
  139. &btf_file,
  140. format!(
  141. r#"
  142. #include <linux/types.h>
  143. {target_btf}
  144. int main() {{ return 0; }}
  145. "#
  146. ),
  147. )
  148. .context("Writing bpf program failed")?;
  149. Command::new("clang")
  150. .current_dir(tmp_dir.path())
  151. .args(["-c", "-g", "-O2", "-target", "bpf"])
  152. .arg(&btf_file)
  153. .status()
  154. .context("Failed to run clang")?
  155. .success()
  156. .then_some(())
  157. .context("Failed to compile BTF")?;
  158. Command::new("pahole")
  159. .current_dir(tmp_dir.path())
  160. .arg("--btf_encode_detached=target.btf")
  161. .arg(btf_file.with_extension("o"))
  162. .status()
  163. .context("Failed to run pahole")?
  164. .success()
  165. .then_some(())
  166. .context("Failed to extract BTF")?;
  167. let btf = Btf::parse_file(tmp_dir.path().join("target.btf"), Endianness::default())
  168. .context("Error parsing generated BTF")?;
  169. Ok(btf)
  170. }
  171. }
  172. struct RelocationTestRunner {
  173. ebpf: Vec<u8>,
  174. btf: Btf,
  175. }
  176. impl RelocationTestRunner {
  177. /// Run test and return the output value
  178. fn run(&self) -> Result<u32> {
  179. self.run_internal(true)
  180. }
  181. /// Run without loading btf
  182. fn run_no_btf(&self) -> Result<u32> {
  183. self.run_internal(false)
  184. }
  185. fn run_internal(&self, with_relocations: bool) -> Result<u32> {
  186. let mut loader = BpfLoader::new();
  187. if with_relocations {
  188. loader.btf(Some(&self.btf));
  189. }
  190. let mut bpf = loader.load(&self.ebpf).context("Loading eBPF failed")?;
  191. let program: &mut TracePoint = bpf
  192. .program_mut("bpf_prog")
  193. .context("bpf_prog not found")?
  194. .try_into()
  195. .context("program not a tracepoint")?;
  196. program.load().context("Loading tracepoint failed")?;
  197. // Attach to sched_switch and wait some time to make sure it executed at least once
  198. program
  199. .attach("sched", "sched_switch")
  200. .context("attach failed")?;
  201. sleep(Duration::from_millis(1000));
  202. // To inspect the loaded eBPF bytecode, increse the timeout and run:
  203. // $ sudo bpftool prog dump xlated name bpf_prog
  204. let output_map: Array<_, u32> = bpf.take_map("output_map").unwrap().try_into().unwrap();
  205. let key = 0;
  206. output_map.get(&key, 0).context("Getting key 0 failed")
  207. }
  208. }