bpf_sys/
type_gen.rs

1// Copyright 2021 Junyeong Jeong <rhdxmr@gmail.com>
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// http://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7/*!
8A module for generating C source code (i.e. vmlinux.h) that defines all structs
9and enums of the Linux kernel.
10
11This module is responsible for generating C source code using BTF of
12vmlinux. All data structures even not exported to the Linux kernel headers can
13be found in `vmlinux.h`.
14
15*NOTE* BTF does not record macro constants that are defined by `#define`
16syntax. So macro constants can not be generated from vmlinux image. But
17`type_gen` provides common macro constants by including some C header files in
18system.
19*/
20
21use super::{
22    btf, btf__free, btf__get_nr_types, btf__name_by_offset, btf__parse_elf, btf__parse_raw,
23    btf__type_by_id, btf_dump, btf_dump__dump_type, btf_dump__free, btf_dump__new, btf_dump_opts,
24    libbpf_find_kernel_btf, vdprintf,
25};
26use libc::{c_char, c_void};
27use regex::RegexSet;
28use std::env;
29use std::ffi::{CStr, CString};
30use std::fs::File;
31use std::io::{self, Write};
32use std::mem::{self, MaybeUninit};
33use std::os::unix::io::{FromRawFd, IntoRawFd, RawFd};
34use std::path::Path;
35use std::path::PathBuf;
36use std::ptr;
37pub const ENV_VMLINUX_PATH: &'static str = "REDBPF_VMLINUX";
38
39// only used for RAII
40struct RawFdWrapper(RawFd);
41impl Drop for RawFdWrapper {
42    fn drop(&mut self) {
43        unsafe {
44            File::from_raw_fd(self.0);
45        }
46    }
47}
48
49impl From<File> for RawFdWrapper {
50    fn from(fobj: File) -> RawFdWrapper {
51        RawFdWrapper(fobj.into_raw_fd())
52    }
53}
54
55impl From<RawFdWrapper> for File {
56    fn from(rawfd: RawFdWrapper) -> File {
57        let fobj = unsafe { File::from_raw_fd(rawfd.0) };
58        mem::forget(rawfd);
59        fobj
60    }
61}
62
63// only used for RAII
64struct BtfDumpWrapper(*mut btf_dump);
65impl Drop for BtfDumpWrapper {
66    fn drop(&mut self) {
67        unsafe {
68            btf_dump__free(self.0);
69        }
70    }
71}
72
73/// An error that occurred during parsing vmlinux and generating C source code
74/// from BTF.
75#[derive(Debug)]
76pub enum TypeGenError {
77    /// error on parsing vmlinux
78    VmlinuxParsingError,
79    /// vmlinux not found
80    VmlinuxNotFound,
81    /// path contains invalid utf-8
82    InvalidPath,
83    /// IO error
84    IO(io::Error),
85    /// invalid regex
86    RegexError,
87    DumpError,
88}
89
90type Result<T> = std::result::Result<T, TypeGenError>;
91
92/// Load vmlinux from file and generate `vmlinux.h`
93pub struct VmlinuxBtfDump {
94    allowlist: Option<Vec<String>>,
95    btfptr: *mut btf,
96}
97
98impl VmlinuxBtfDump {
99    /// Probe few well-known locations for vmlinux kernel image and try to load
100    /// BTF data out of it
101    pub fn with_system_default() -> Result<Self> {
102        let btfptr = unsafe { libbpf_find_kernel_btf() };
103        if (btfptr as isize) < 0 {
104            return Err(TypeGenError::VmlinuxNotFound);
105        }
106
107        Ok(VmlinuxBtfDump {
108            allowlist: None,
109            btfptr,
110        })
111    }
112
113    /// Read the ELF file and parse BTF data out of the given ELF file
114    pub fn with_elf_file(elf_file: impl AsRef<Path>) -> Result<Self> {
115        if !elf_file.as_ref().exists() {
116            return Err(TypeGenError::VmlinuxNotFound);
117        }
118
119        let elf_str = elf_file
120            .as_ref()
121            .to_str()
122            .ok_or(TypeGenError::InvalidPath)?;
123
124        let cvmlinux_path = CString::new(elf_str).unwrap();
125        let btfptr = unsafe { btf__parse_elf(cvmlinux_path.as_ptr(), ptr::null_mut()) };
126        if (btfptr as isize) < 0 {
127            return Err(TypeGenError::VmlinuxParsingError);
128        }
129
130        Ok(VmlinuxBtfDump {
131            allowlist: None,
132            btfptr,
133        })
134    }
135
136    /// Parse BTF data from a file containing raw BTF data
137    pub fn with_raw_file(raw: impl AsRef<Path>) -> Result<Self> {
138        if !raw.as_ref().exists() {
139            return Err(TypeGenError::VmlinuxNotFound);
140        }
141
142        let raw_str = raw.as_ref().to_str().ok_or(TypeGenError::InvalidPath)?;
143
144        let cpath = CString::new(raw_str).unwrap();
145        let btfptr = unsafe { btf__parse_raw(cpath.as_ptr()) };
146        if (btfptr as isize) < 0 {
147            return Err(TypeGenError::VmlinuxParsingError);
148        }
149
150        Ok(VmlinuxBtfDump {
151            allowlist: None,
152            btfptr,
153        })
154    }
155
156    /// Add regex `pattern` into allowlist of BTF types
157    pub fn allowlist(mut self, pattern: &str) -> Self {
158        let allowlist: &mut Vec<String> = if let Some(allowlist) = &mut self.allowlist {
159            allowlist
160        } else {
161            self.allowlist = Some(vec![]);
162            self.allowlist.as_mut().unwrap()
163        };
164        allowlist.push(pattern.to_string());
165        self
166    }
167
168    /// Dump BTF types as C source code into `outfile` including all the
169    /// necessary dependent types.
170    pub fn generate(self, outfile: impl AsRef<Path>) -> Result<()> {
171        let mut fobj = File::create(&outfile).or_else(|e| Err(TypeGenError::IO(e)))?;
172        let header_name = outfile.as_ref().file_name().unwrap();
173        let guard_name = header_name
174            .to_str()
175            .unwrap()
176            .to_uppercase()
177            .chars()
178            .map(|c| match c {
179                'A'..='Z' => c,
180                _ => '_',
181            })
182            .collect::<String>();
183        let guardstr = format!(
184            "#ifndef __{guard_name}__\n#define __{guard_name}__\n\n",
185            guard_name = guard_name
186        );
187        fobj.write_all(guardstr.as_bytes())
188            .or_else(|e| Err(TypeGenError::IO(e)))?;
189        fobj.flush().or_else(|e| Err(TypeGenError::IO(e)))?;
190        let mut rawfd: RawFdWrapper = fobj.into();
191        let white_re = if let Some(ref allowlist) = self.allowlist {
192            Some(RegexSet::new(allowlist).or(Err(TypeGenError::RegexError))?)
193        } else {
194            None
195        };
196        unsafe {
197            let dump_opts = {
198                let mut uninit = MaybeUninit::<btf_dump_opts>::zeroed();
199                (*uninit.as_mut_ptr()).ctx = &mut rawfd as *mut _ as *mut _;
200                uninit.assume_init()
201            };
202            let dumpptr = btf_dump__new(
203                self.btfptr,
204                ptr::null(),
205                &dump_opts as *const _,
206                Some(vdprintf_wrapper),
207            );
208            if (dumpptr as isize) < 0 {
209                return Err(TypeGenError::DumpError);
210            }
211            let dumpptr = BtfDumpWrapper(dumpptr);
212            for type_id in 1..=btf__get_nr_types(self.btfptr) {
213                let btftypeptr = btf__type_by_id(self.btfptr, type_id);
214                let nameptr = btf__name_by_offset(self.btfptr, (*btftypeptr).name_off);
215                if nameptr.is_null() {
216                    continue;
217                }
218
219                let cname = CStr::from_ptr(nameptr);
220                let namestr = cname.to_str().or(Err(TypeGenError::DumpError))?;
221                if let Some(wre) = &white_re {
222                    if !wre.is_match(namestr) {
223                        continue;
224                    }
225                }
226                if btf_dump__dump_type(dumpptr.0, type_id) < 0 {
227                    return Err(TypeGenError::DumpError);
228                }
229            }
230        }
231
232        let mut fobj: File = rawfd.into();
233        fobj.write_all(b"#endif\n")
234            .or_else(|e| Err(TypeGenError::IO(e)))?;
235
236        Ok(())
237    }
238}
239
240impl Drop for VmlinuxBtfDump {
241    fn drop(&mut self) {
242        unsafe {
243            btf__free(self.btfptr);
244        }
245    }
246}
247
248// wrapping vdprintf to get rid of return type
249#[cfg(not(any(target_arch = "arm", target_arch = "aarch64")))]
250unsafe extern "C" fn vdprintf_wrapper(
251    ctx: *mut c_void,
252    format: *const c_char,
253    va_list: *mut super::__va_list_tag,
254) {
255    let rawfd_wrapper = &*(ctx as *mut RawFdWrapper);
256    vdprintf(rawfd_wrapper.0, format, va_list);
257}
258
259// wrapping vdprintf to get rid of return type
260#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
261unsafe extern "C" fn vdprintf_wrapper(
262    ctx: *mut c_void,
263    format: *const c_char,
264    #[cfg(target_env = "musl")] va_list: super::__isoc_va_list,
265    #[cfg(not(target_env = "musl"))] va_list: super::__gnuc_va_list,
266) {
267    let rawfd_wrapper = &*(ctx as *mut RawFdWrapper);
268    vdprintf(rawfd_wrapper.0, format, va_list);
269}
270
271pub fn get_custom_vmlinux_path() -> Option<PathBuf> {
272    Some(PathBuf::from(env::var(ENV_VMLINUX_PATH).ok()?))
273}
274
275/// Find a source of vmlinux BTF and parse it
276///
277/// Using the returned `VmlinuxBtfDump`, BTF of the Linux kernel can be dumped
278/// into `vmlinux.h`.
279pub fn vmlinux_btf_dump() -> Result<VmlinuxBtfDump> {
280    if let Some(path) = get_custom_vmlinux_path() {
281        if path.to_str().unwrap() == "system" {
282            VmlinuxBtfDump::with_system_default()
283        } else {
284            VmlinuxBtfDump::with_raw_file(&path).or_else(|_| VmlinuxBtfDump::with_elf_file(&path))
285        }
286    } else {
287        VmlinuxBtfDump::with_system_default()
288    }
289}