Skip to main content

kmod_loader/
loader.rs

1use alloc::{
2    boxed::Box,
3    ffi::CString,
4    string::{String, ToString},
5    vec::Vec,
6};
7use core::{ffi::CStr, fmt::Display};
8
9use bitflags::bitflags;
10use goblin::elf::{Elf, SectionHeader};
11use kmod_tools::Module;
12
13use crate::{ModuleErr, Result, arch::ModuleArchSpecific, module::ModuleInfo};
14
15bitflags! {
16    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
17    pub struct SectionPerm: u8 {
18        const READ = 0b001;
19        const WRITE = 0b010;
20        const EXECUTE = 0b100;
21    }
22}
23
24impl Display for SectionPerm {
25    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
26        let mut perms = String::new();
27        if self.contains(SectionPerm::READ) {
28            perms.push('R');
29        }
30        if self.contains(SectionPerm::WRITE) {
31            perms.push('W');
32        }
33        if self.contains(SectionPerm::EXECUTE) {
34            perms.push('X');
35        }
36        write!(f, "{}", perms)
37    }
38}
39
40impl SectionPerm {
41    /// Create ModuleSectionPermissions from ELF section flags
42    pub fn from_elf_flags(sh_flags: u64) -> Self {
43        let mut perms = SectionPerm::empty();
44        if (sh_flags & goblin::elf::section_header::SHF_ALLOC as u64) != 0 {
45            perms |= SectionPerm::READ;
46        }
47        if (sh_flags & goblin::elf::section_header::SHF_WRITE as u64) != 0 {
48            perms |= SectionPerm::WRITE;
49        }
50        if (sh_flags & goblin::elf::section_header::SHF_EXECINSTR as u64) != 0 {
51            perms |= SectionPerm::EXECUTE;
52        }
53        perms
54    }
55}
56
57/// Trait for accessing and manipulating memory for module sections
58pub trait SectionMemOps: Send + Sync {
59    fn as_ptr(&self) -> *const u8;
60    fn as_mut_ptr(&mut self) -> *mut u8;
61    /// Change the permissions of the memory region
62    fn change_perms(&mut self, perms: SectionPerm) -> bool;
63}
64
65/// Trait for kernel module helper functions
66pub trait KernelModuleHelper {
67    /// Allocate virtual memory for module section
68    fn vmalloc(size: usize) -> Box<dyn SectionMemOps>;
69    /// Resolve symbol name to address
70    fn resolve_symbol(name: &str) -> Option<usize>;
71    /// Flush CPU cache for the given memory region
72    fn flsuh_cache(_addr: usize, _size: usize) {
73        // Default implementation does nothing
74    }
75}
76
77pub struct ModuleLoader<'a, H: KernelModuleHelper> {
78    elf: Elf<'a>,
79    elf_data: &'a [u8],
80    __helper: core::marker::PhantomData<H>,
81}
82
83struct SectionPages {
84    name: String,
85    addr: Box<dyn SectionMemOps>,
86    size: usize,
87    perms: SectionPerm,
88}
89
90pub struct ModuleOwner<H: KernelModuleHelper> {
91    module_info: ModuleInfo,
92    pages: Vec<SectionPages>,
93    name: String,
94    module: Module,
95    #[allow(unused)]
96    pub(crate) arch: ModuleArchSpecific,
97    _helper: core::marker::PhantomData<H>,
98}
99
100impl<H: KernelModuleHelper> ModuleOwner<H> {
101    /// Get the name of the module
102    pub fn name(&self) -> &str {
103        &self.name
104    }
105
106    pub(crate) fn set_name(&mut self, name: &str) {
107        self.name = name.to_string();
108    }
109
110    /// Call the module's init function
111    pub fn call_init(&mut self) -> Result<i32> {
112        if let Some(init_fn) = self.module.take_init_fn() {
113            let result = unsafe { init_fn() };
114            Ok(result)
115        } else {
116            log::warn!("The init function can only be called once.");
117            Err(ModuleErr::EINVAL)
118        }
119    }
120
121    /// Call the module's exit function
122    pub fn call_exit(&mut self) {
123        if let Some(exit_fn) = self.module.take_exit_fn() {
124            log::warn!("Calling module exit function...");
125            unsafe {
126                exit_fn();
127            }
128        } else {
129            log::warn!("The exit function can only be called once.");
130        }
131    }
132}
133
134const fn align_up(addr: usize, align: usize) -> usize {
135    (addr + align - 1) & !(align - 1)
136}
137
138// const fn align_down(addr: usize, align: usize) -> usize {
139//     addr & !(align - 1)
140// }
141
142const SKIP_SECTIONS: &[&str] = &[".note", ".modinfo", "__version"];
143
144pub(crate) struct ModuleLoadInfo {
145    pub(crate) syms: Vec<(goblin::elf::sym::Sym, String)>,
146}
147
148impl<'a, H: KernelModuleHelper> ModuleLoader<'a, H> {
149    /// create a new ELF loader
150    pub fn new(elf_data: &'a [u8]) -> Result<Self> {
151        let elf = Elf::parse(elf_data).map_err(|_| ModuleErr::ENOEXEC)?;
152        if !elf.is_64 {
153            return Err(ModuleErr::ENOEXEC);
154        }
155        Ok(ModuleLoader {
156            elf,
157            elf_data,
158            __helper: core::marker::PhantomData,
159        })
160    }
161
162    /// Check module signature
163    ///
164    /// See <https://elixir.bootlin.com/linux/v6.6/source/kernel/module/signing.c#L70>
165    fn module_sig_check(&self) -> bool {
166        // TODO: implement module signature check
167        true
168    }
169
170    /// Check userspace passed ELF module against our expectations, and cache
171    /// useful variables for further processing as we go.
172    ///
173    /// See <https://elixir.bootlin.com/linux/v6.6/source/kernel/module/main.c#L1669>
174    fn elf_validity_cache_copy(&self) -> Result<ModuleOwner<H>> {
175        if self.elf.header.e_type != goblin::elf::header::ET_REL {
176            log::error!(
177                "Invalid ELF type: {}, expected ET_REL",
178                self.elf.header.e_type
179            );
180            return Err(ModuleErr::ENOEXEC);
181        }
182
183        elf_check_arch(&self.elf)?;
184
185        // Verify if the section name table index is valid.
186        if self.elf.header.e_shstrndx == goblin::elf::section_header::SHN_UNDEF as _
187            || self.elf.header.e_shstrndx as usize >= self.elf.section_headers.len()
188        {
189            log::error!(
190                "Invalid ELF section name index: {} || e_shstrndx ({}) >= e_shnum ({})",
191                self.elf.header.e_shstrndx,
192                self.elf.header.e_shstrndx,
193                self.elf.section_headers.len()
194            );
195            return Err(ModuleErr::ENOEXEC);
196        }
197
198        // The section name table must be NUL-terminated, as required
199        // by the spec. This makes strcmp and pr_* calls that access
200        // strings in the section safe.
201        if self.elf.shdr_strtab.len() == 0 {
202            log::error!("ELF section name string table is empty");
203            return Err(ModuleErr::ENOEXEC);
204        }
205
206        // The code assumes that section 0 has a length of zero and
207        // an addr of zero, so check for it.
208        if self.elf.section_headers[0].sh_type != goblin::elf::section_header::SHT_NULL
209            || self.elf.section_headers[0].sh_size != 0
210            || self.elf.section_headers[0].sh_addr != 0
211        {
212            log::error!(
213                "ELF Spec violation: section 0 type({})!=SH_NULL or non-zero len or addr",
214                self.elf.section_headers[0].sh_type
215            );
216            return Err(ModuleErr::ENOEXEC);
217        }
218
219        let mut num_sym_secs = 0;
220        let mut num_mod_secs = 0;
221        let mut num_info_secs = 0;
222        let mut info_idx = 0;
223        let mut mod_idx = 0;
224        for (idx, shdr) in self.elf.section_headers.iter().enumerate() {
225            let ty = shdr.sh_type;
226            match ty {
227                goblin::elf::section_header::SHT_NULL | goblin::elf::section_header::SHT_NOBITS => {
228                    continue;
229                }
230                goblin::elf::section_header::SHT_SYMTAB => {
231                    if shdr.sh_link == goblin::elf::section_header::SHN_UNDEF
232                        || shdr.sh_link as usize >= self.elf.section_headers.len()
233                    {
234                        log::error!(
235                            "Invalid ELF sh_link!=SHN_UNDEF({}) or (sh_link({}) >= hdr->e_shnum({})",
236                            shdr.sh_link,
237                            shdr.sh_link,
238                            self.elf.section_headers.len()
239                        );
240                        return Err(ModuleErr::ENOEXEC);
241                    }
242                    num_sym_secs += 1;
243                }
244                _ => {
245                    let shdr_name = self
246                        .elf
247                        .shdr_strtab
248                        .get_at(shdr.sh_name)
249                        .ok_or(ModuleErr::ENOEXEC)?;
250                    if shdr_name == ".gnu.linkonce.this_module" {
251                        num_mod_secs += 1;
252                        mod_idx = idx;
253                    } else if shdr_name == ".modinfo" {
254                        num_info_secs += 1;
255                        info_idx = idx;
256                    }
257
258                    if shdr.sh_flags == goblin::elf::section_header::SHF_ALLOC as _ {
259                        // Check that section names are valid
260                        let _name = self
261                            .elf
262                            .shdr_strtab
263                            .get_at(shdr.sh_name)
264                            .ok_or(ModuleErr::ENOEXEC)?;
265                    }
266                }
267            }
268        }
269
270        let mut owner = None;
271        if num_info_secs > 1 {
272            log::error!("Only one .modinfo section must exist.");
273            return Err(ModuleErr::ENOEXEC);
274        } else if num_info_secs == 1 {
275            owner = Some(self.pre_read_modinfo(info_idx)?);
276            if let Some(ref o) = owner {
277                log::error!("Module({:?}) info: {:?}", o.name(), o.module_info);
278            }
279        }
280        let mut owner = owner.ok_or(ModuleErr::ENOEXEC)?;
281        let module_name = owner.name();
282
283        if num_sym_secs != 1 {
284            log::error!("{}: module has no symbols (stripped?)", module_name);
285            return Err(ModuleErr::ENOEXEC);
286        }
287        /*
288         * The ".gnu.linkonce.this_module" ELF section is special. It is
289         * what modpost uses to refer to __this_module and let's use rely
290         * on THIS_MODULE to point to &__this_module properly. The kernel's
291         * modpost declares it on each modules's *.mod.c file. If the struct
292         * module of the kernel changes a full kernel rebuild is required.
293         *
294         * We have a few expectaions for this special section, the following
295         * code validates all this for us:
296         *
297         *   o Only one section must exist
298         *   o We expect the kernel to always have to allocate it: SHF_ALLOC
299         *   o The section size must match the kernel's run time's struct module
300         *     size
301         */
302        if num_mod_secs != 1 {
303            log::error!(
304                "{}: Only one .gnu.linkonce.this_module section must exist.",
305                module_name
306            );
307            return Err(ModuleErr::ENOEXEC);
308        }
309
310        let this_module_shdr = &self.elf.section_headers[mod_idx];
311        if this_module_shdr.sh_type == goblin::elf::section_header::SHT_NOBITS {
312            log::error!(
313                "{}: .gnu.linkonce.this_module section must have a size set",
314                module_name
315            );
316            return Err(ModuleErr::ENOEXEC);
317        }
318
319        if this_module_shdr.sh_flags & goblin::elf::section_header::SHF_ALLOC as u64 == 0 {
320            log::error!(
321                "{}: .gnu.linkonce.this_module section size must match the kernel's built struct module size at run time",
322                module_name
323            );
324            return Err(ModuleErr::ENOEXEC);
325        }
326        // If we didn't load the .modinfo 'name' field earlier, fall back to
327        // on-disk struct mod 'name' field.
328        if owner.name().is_empty() {
329            self.pre_read_this_module(mod_idx, &mut owner)?;
330        }
331        Ok(owner)
332    }
333
334    /// Load the module into kernel space
335    pub fn load_module(mut self, args: CString) -> Result<ModuleOwner<H>> {
336        if !self.module_sig_check() {
337            log::error!("Module signature check failed");
338            return Err(ModuleErr::ENOEXEC);
339        }
340        // let arch = offset_of!(kmod::kbindings::module, arch);
341        // log::error!("Offset of module.arch: {}", arch);
342        let mut owner = self.elf_validity_cache_copy()?;
343
344        self.layout_and_allocate(&mut owner)?;
345        let load_info = self.simplify_symbols(&owner)?;
346        self.apply_relocations(load_info, &mut owner)?;
347
348        self.post_read_this_module(&mut owner)?;
349
350        self.find_module_sections(&mut owner)?;
351
352        self.complete_formation(&mut owner)?;
353
354        self.parse_args(&mut owner, args)?;
355
356        log::error!("Module({:?}) loaded successfully!", owner.name());
357        Ok(owner)
358    }
359
360    /// Args looks like "foo=bar,bar2 baz=fuz wiz". Parse them and set module parameters.
361    fn parse_args(&self, owner: &mut ModuleOwner<H>, args: CString) -> Result<()> {
362        let name = owner.name().to_string();
363        let kparams = owner.module.params_mut();
364        let after_dashes = crate::param::parse_args(&name, args, kparams, i16::MIN, i16::MAX)?;
365        if !after_dashes.is_empty() {
366            log::warn!(
367                "[{}]: parameters '{}' after '--' ignored",
368                name,
369                after_dashes.to_str().unwrap_or("<invalid UTF-8>")
370            );
371        }
372        Ok(())
373    }
374
375    /// Find section by name
376    fn find_section(&self, name: &str) -> Result<&SectionHeader> {
377        for shdr in &self.elf.section_headers {
378            let sec_name = self
379                .elf
380                .shdr_strtab
381                .get_at(shdr.sh_name)
382                .ok_or(ModuleErr::ENOEXEC)?;
383
384            if sec_name == name {
385                return Ok(shdr);
386            }
387        }
388        log::error!("Section '{}' not found", name);
389        Err(ModuleErr::ENOEXEC)
390    }
391
392    fn pre_read_modinfo(&self, info_idx: usize) -> Result<ModuleOwner<H>> {
393        let modinfo_shdr = &self.elf.section_headers[info_idx];
394        let file_offset = modinfo_shdr.sh_offset as usize;
395        let size = modinfo_shdr.sh_size as usize;
396
397        let mut modinfo_data = &self.elf_data[file_offset..file_offset + size];
398        let mut module_info = ModuleInfo::new();
399
400        log::info!("Reading .modinfo section (size: {:#x})", size);
401
402        // read the modinfo data
403        // format is key=value\0key=value\0...
404        loop {
405            if modinfo_data.is_empty() {
406                break;
407            }
408            let cstr = CStr::from_bytes_until_nul(modinfo_data)
409                .map_err(|_| ModuleErr::EINVAL)
410                .unwrap();
411            let str_slice = cstr.to_str().map_err(|_| ModuleErr::EINVAL)?;
412            modinfo_data = &modinfo_data[cstr.to_bytes_with_nul().len()..];
413
414            let mut split = str_slice.splitn(2, '=');
415            let key = split.next().ok_or(ModuleErr::EINVAL)?.to_string();
416            let value = split.next().ok_or(ModuleErr::EINVAL)?.to_string();
417            module_info.add_kv(key, value);
418        }
419
420        let name = module_info
421            .get("name")
422            .map(|s| s.to_string())
423            .unwrap_or_else(|| "".to_string());
424
425        Ok(ModuleOwner {
426            name,
427            module_info,
428            pages: Vec::new(),
429            module: Module::default(),
430            arch: ModuleArchSpecific::default(),
431            _helper: core::marker::PhantomData,
432        })
433    }
434
435    /// Read the __this_module structure to get module name. If the name of owner
436    /// is not set, set it here.
437    fn pre_read_this_module(&self, idx: usize, owner: &mut ModuleOwner<H>) -> Result<()> {
438        let this_module_shdr = &self.elf.section_headers[idx];
439        let size = this_module_shdr.sh_size as usize;
440        if size != core::mem::size_of::<Module>() {
441            log::error!(
442                "Invalid .gnu.linkonce.this_module section size: {}, expected: {}",
443                size,
444                core::mem::size_of::<Module>()
445            );
446            return Err(ModuleErr::ENOEXEC);
447        }
448        let modinfo_data = this_module_shdr.sh_addr as *mut u8;
449        let module = unsafe { core::ptr::read(modinfo_data as *const Module) };
450        let name = module.name();
451        owner.set_name(name);
452        Ok(())
453    }
454
455    /// After relocating, read the __this_module structure to get init and exit function pointers
456    fn post_read_this_module(&mut self, owner: &mut ModuleOwner<H>) -> Result<()> {
457        let this_module_shdr = self.find_section(".gnu.linkonce.this_module")?;
458        // the data address is the allocated virtual address and it has been relocated
459        let modinfo_data = this_module_shdr.sh_addr as *mut u8;
460        let module = unsafe { core::ptr::read(modinfo_data as *const Module) };
461
462        let init_fn = module.init_fn();
463        let exit_fn = module.exit_fn();
464
465        log::error!(
466            "Module init_fn: {:?}, exit_fn: {:?}",
467            init_fn.map(|f| f as *const ()),
468            exit_fn.map(|f| f as *const ())
469        );
470
471        owner.module = module;
472        Ok(())
473    }
474
475    /// Get number of objects and starting address of a section.
476    fn section_objs(&self, name: &str, object_size: usize) -> Result<(usize, *const u8)> {
477        let section = self
478            .find_section(name)
479            .unwrap_or(&self.elf.section_headers[0]); // Section 0 has sh_addr 0 and sh_size 0.
480        let num = section.sh_size as usize / object_size;
481        let addr = section.sh_addr as *const u8;
482        Ok((num, addr))
483    }
484
485    fn find_module_sections(&self, owner: &mut ModuleOwner<H>) -> Result<()> {
486        let (num_kparams, kparam_addr) =
487            self.section_objs("__param", size_of::<kmod_tools::kernel_param>())?;
488        let raw_module = owner.module.raw_mod();
489        raw_module.kp = kparam_addr as *mut kmod_tools::kernel_param;
490        raw_module.num_kp = num_kparams as _;
491
492        // TODO: implement finding other sections:
493        // __ksymtab
494        // __kcrctab
495        // __ksymtab_gpl
496        // __kcrctab_gpl
497        Ok(())
498    }
499
500    /// Finally it's fully formed, ready to start executing.
501    fn complete_formation(&self, owner: &mut ModuleOwner<H>) -> Result<()> {
502        for page in &mut owner.pages {
503            if !page.addr.change_perms(page.perms) {
504                log::error!(
505                    "Failed to change permissions of section '{}' to {}",
506                    page.name,
507                    page.perms
508                );
509                return Err(ModuleErr::EINVAL);
510            }
511            H::flsuh_cache(page.addr.as_ptr() as usize, page.size);
512        }
513        Ok(())
514    }
515
516    /// Layout sections and allocate memory
517    /// See <https://elixir.bootlin.com/linux/v6.6/source/kernel/module/main.c#L2363>
518    fn layout_and_allocate(&mut self, owner: &mut ModuleOwner<H>) -> Result<()> {
519        // Allow arches to frob section contents and sizes
520        #[cfg(feature = "module-sections")]
521        crate::arch::module_frob_arch_sections(&mut self.elf, owner)?;
522        for shdr in self.elf.section_headers.iter_mut() {
523            let sec_name = self
524                .elf
525                .shdr_strtab
526                .get_at(shdr.sh_name)
527                .unwrap_or("<unknown>");
528
529            // Skip non-allocatable sections
530            if (shdr.sh_flags & goblin::elf::section_header::SHF_ALLOC as u64) == 0 {
531                log::debug!("Skipping non-allocatable section '{}'", sec_name);
532                continue;
533            }
534
535            // Skip sections in the skip list
536            if SKIP_SECTIONS.iter().any(|&s| sec_name.starts_with(s)) {
537                log::warn!("Skipping section '{}' in skip list", sec_name);
538                continue;
539            }
540
541            let file_offset = shdr.sh_offset as usize;
542            let size = shdr.sh_size as usize;
543
544            let perms = SectionPerm::from_elf_flags(shdr.sh_flags);
545
546            if size == 0 {
547                log::error!("Skipping zero-size section '{}'", sec_name);
548                continue;
549            }
550
551            let aligned_size = align_up(size, 4096);
552
553            // Allocate memory for the section
554            let mut addr = H::vmalloc(aligned_size);
555            if addr.as_ptr().is_null() {
556                return Err(ModuleErr::ENOSPC);
557            }
558
559            let raw_addr = addr.as_ptr() as u64;
560
561            // Copy section data from ELF to allocated memory
562            // For SHT_NOBITS sections (like .bss), memory is already zeroed by vmalloc
563            if shdr.sh_type != goblin::elf::section_header::SHT_NOBITS {
564                let section_data = &self.elf_data[file_offset..file_offset + size];
565                unsafe {
566                    core::ptr::copy_nonoverlapping(section_data.as_ptr(), addr.as_mut_ptr(), size);
567                }
568            }
569
570            // Store the allocated page info
571            owner.pages.push(SectionPages {
572                name: sec_name.to_string(),
573                addr,
574                size: aligned_size,
575                perms,
576            });
577
578            // update section address
579            // Note: In a real loader, we would update the section header's sh_addr field
580            // to reflect the new virtual address.
581            shdr.sh_addr = raw_addr;
582        }
583
584        for page in &owner.pages {
585            log::error!(
586                "Allocated section '{:>26}' at {:p} [{}] ({:8<#x})",
587                page.name,
588                page.addr.as_ptr(),
589                page.perms,
590                page.size
591            );
592        }
593
594        Ok(())
595    }
596
597    /// Change all symbols so that st_value encodes the pointer directly.
598    ///
599    /// See <https://elixir.bootlin.com/linux/v6.6/source/kernel/module/main.c#L1367>
600    fn simplify_symbols(&self, owner: &ModuleOwner<H>) -> Result<ModuleLoadInfo> {
601        let mut loadinfo = ModuleLoadInfo { syms: Vec::new() };
602
603        // Skip the first symbol (index 0), which is always the undefined symbol
604        for (idx, sym) in self.elf.syms.iter().enumerate() {
605            if idx == 0 {
606                loadinfo.syms.push((sym, "".to_string()));
607                // Symbol 0 is always SHN_UNDEF and should be skipped
608                continue;
609            }
610
611            let sym_name = self
612                .elf
613                .strtab
614                .get_at(sym.st_name)
615                .unwrap_or("<unknown>")
616                .to_string();
617
618            let sym_value = sym.st_value;
619            let sym_size = sym.st_size;
620
621            // For debugging purposes, print symbol info
622            log::debug!(
623                "Symbol: ('{}') [{}] Value: 0x{:016x} Size: {}",
624                sym_name,
625                sym_section_to_str(sym.st_shndx as _),
626                sym_value,
627                sym_size
628            );
629
630            // Create a mutable copy for potential updates
631            let mut updated_sym = sym;
632
633            match sym.st_shndx as _ {
634                goblin::elf::section_header::SHN_UNDEF => {
635                    // Undefined symbol
636                    let sym_address = H::resolve_symbol(&sym_name);
637                    // Ok if resolved.
638                    if let Some(addr) = sym_address {
639                        log::error!(
640                            "  -> Resolved undefined symbol '{}' ({}) to address 0x{:016x}",
641                            sym_name,
642                            sym_bind_to_str(sym.st_bind()),
643                            addr
644                        );
645                        // Update the symbol table entry's st_value to the resolved address
646                        updated_sym.st_value = addr as u64;
647                    } else {
648                        // Ok if weak or ignored.
649                        if sym.st_bind() == goblin::elf::sym::STB_WEAK {
650                            log::warn!(
651                                "  -> Unresolved weak symbol '{}' ({})",
652                                sym_name,
653                                sym_bind_to_str(sym.st_bind())
654                            );
655                        } else {
656                            log::warn!(
657                                "  -> Unresolved symbol '{}' ({})",
658                                sym_name,
659                                sym_bind_to_str(sym.st_bind())
660                            );
661                            return Err(ModuleErr::ENOENT);
662                        }
663                    }
664                }
665                goblin::elf::section_header::SHN_ABS => {
666                    // Don't need to do anything
667                    log::debug!("Absolute symbol: {} 0x{:x}", sym_name, sym_value);
668                }
669                goblin::elf::section_header::SHN_COMMON => {
670                    // Ignore common symbols
671                    // We compiled with -fno-common. These are not supposed to happen.
672                    log::debug!("Common symbol: {}", sym_name);
673                    log::warn!("{:?}: please compile with -fno-common", owner.name());
674                    return Err(ModuleErr::ENOEXEC);
675                }
676                ty => {
677                    /* Divert to percpu allocation if a percpu var. */
678                    // if (sym[i].st_shndx == info->index.pcpu)
679                    //     secbase = (unsigned long)mod_percpu(mod);
680                    // else
681                    //     secbase = info->sechdrs[sym[i].st_shndx].sh_addr;
682                    // sym[i].st_value += secbase;
683
684                    // TODO: Handle special sections like percpu
685                    // Normal symbol defined in a section
686                    // Add section base address to symbol's offset within the section
687                    let secbase = self.elf.section_headers[ty as usize].sh_addr;
688                    updated_sym.st_value = sym.st_value.wrapping_add(secbase);
689                    log::trace!(
690                        "  -> Defined symbol '{}' in section {} at address 0x{:016x} (base: 0x{:016x} + offset: 0x{:016x})",
691                        sym_name,
692                        ty,
693                        updated_sym.st_value,
694                        secbase,
695                        sym.st_value
696                    );
697                }
698            }
699
700            // Push the updated symbol to the list
701            loadinfo.syms.push((updated_sym, sym_name));
702        }
703
704        Ok(loadinfo)
705    }
706
707    /// See <https://elixir.bootlin.com/linux/v6.6/source/kernel/module/main.c#L1438>
708    fn apply_relocations(
709        &self,
710        load_info: ModuleLoadInfo,
711        owner: &mut ModuleOwner<H>,
712    ) -> Result<()> {
713        for shdr in self.elf.section_headers.iter() {
714            let infosec = shdr.sh_info;
715
716            let sec_name = self
717                .elf
718                .shdr_strtab
719                .get_at(shdr.sh_name)
720                .ok_or(ModuleErr::ENOEXEC)?;
721
722            // Not a valid relocation section?
723            if infosec >= self.elf.section_headers.len() as u32 {
724                continue;
725            }
726            // Don't bother with non-allocated sections
727            if self.elf.section_headers[infosec as usize].sh_flags
728                & goblin::elf::section_header::SHF_ALLOC as u64
729                == 0
730            {
731                continue;
732            }
733
734            // Skip non-relocation sections
735            if shdr.sh_type != goblin::elf::section_header::SHT_RELA {
736                continue;
737            }
738
739            let to_section = &self.elf.section_headers[infosec as usize];
740            let to_sec_name = self
741                .elf
742                .shdr_strtab
743                .get_at(to_section.sh_name)
744                .ok_or(ModuleErr::ENOEXEC)?;
745
746            let rela_entries = shdr.sh_size as usize / shdr.sh_entsize as usize;
747            log::error!(
748                "Applying relocations for section '{}' to '{}', {} entries",
749                sec_name,
750                to_sec_name,
751                rela_entries
752            );
753
754            let offset = shdr.sh_offset as usize;
755            // Size of Elf64_Rela
756            debug_assert!(shdr.sh_entsize == 24);
757
758            let data_buf = &self.elf_data[offset..offset + shdr.sh_size as usize];
759            let rela_list = unsafe {
760                goblin::elf64::reloc::from_raw_rela(data_buf.as_ptr() as _, shdr.sh_size as usize)
761            };
762
763            crate::arch::ArchRelocate::apply_relocate_add(
764                rela_list,
765                shdr,
766                &self.elf.section_headers,
767                &load_info,
768                owner,
769            )?;
770        }
771        Ok(())
772    }
773}
774
775const fn sym_bind_to_str(bind: u8) -> &'static str {
776    match bind {
777        goblin::elf::sym::STB_LOCAL => "LOCAL",
778        goblin::elf::sym::STB_GLOBAL => "GLOBAL",
779        goblin::elf::sym::STB_WEAK => "WEAK",
780        _ => "UNKNOWN",
781    }
782}
783
784const fn sym_section_to_str(shndx: u32) -> &'static str {
785    match shndx {
786        goblin::elf::section_header::SHN_UNDEF => "UNDEF(0)",
787        goblin::elf::section_header::SHN_LORESERVE => "LORESERVE(0xff00)",
788        // goblin::elf::section_header::SHN_LOPROC => "LOPROC(0xff00)",
789        goblin::elf::section_header::SHN_HIPROC => "HIPROC(0xff1f)",
790        goblin::elf::section_header::SHN_ABS => "ABS(0xfff1)",
791        goblin::elf::section_header::SHN_COMMON => "COMMON(0xfff2)",
792        goblin::elf::section_header::SHN_HIRESERVE => "HIRESERVE(0xffff)",
793        _ => "OTHER",
794    }
795}
796
797// #define SHN_LIVEPATCH	0xff20
798
799/// Check if the ELF file is for a supported architecture
800fn elf_check_arch(elf: &goblin::elf::Elf) -> Result<()> {
801    if elf.header.e_machine != goblin::elf::header::EM_AARCH64
802        && elf.header.e_machine != goblin::elf::header::EM_X86_64
803        && elf.header.e_machine != goblin::elf::header::EM_RISCV
804        && elf.header.e_machine != goblin::elf::header::EM_LOONGARCH
805    {
806        log::error!(
807            "Invalid ELF machine: {}, expected AARCH64({}), X86_64({}), RISC-V({}), LOONGARCH({})",
808            elf.header.e_machine,
809            goblin::elf::header::EM_AARCH64,
810            goblin::elf::header::EM_X86_64,
811            goblin::elf::header::EM_RISCV,
812            goblin::elf::header::EM_LOONGARCH
813        );
814        return Err(ModuleErr::ENOEXEC);
815    }
816    Ok(())
817}