kmod-loader 0.2.0

Rust implementation of Linux kernel module loader for loadable kernel module development
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
use alloc::{
    boxed::Box,
    ffi::CString,
    string::{String, ToString},
    vec::Vec,
};
use core::{ffi::CStr, fmt::Display};

use bitflags::bitflags;
use goblin::elf::{Elf, SectionHeader};
use kmod_tools::Module;

use crate::{ModuleErr, Result, arch::ModuleArchSpecific, module::ModuleInfo};

bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    pub struct SectionPerm: u8 {
        const READ = 0b001;
        const WRITE = 0b010;
        const EXECUTE = 0b100;
    }
}

impl Display for SectionPerm {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        let mut perms = String::new();
        if self.contains(SectionPerm::READ) {
            perms.push('R');
        }
        if self.contains(SectionPerm::WRITE) {
            perms.push('W');
        }
        if self.contains(SectionPerm::EXECUTE) {
            perms.push('X');
        }
        write!(f, "{}", perms)
    }
}

impl SectionPerm {
    /// Create ModuleSectionPermissions from ELF section flags
    pub fn from_elf_flags(sh_flags: u64) -> Self {
        let mut perms = SectionPerm::empty();
        if (sh_flags & goblin::elf::section_header::SHF_ALLOC as u64) != 0 {
            perms |= SectionPerm::READ;
        }
        if (sh_flags & goblin::elf::section_header::SHF_WRITE as u64) != 0 {
            perms |= SectionPerm::WRITE;
        }
        if (sh_flags & goblin::elf::section_header::SHF_EXECINSTR as u64) != 0 {
            perms |= SectionPerm::EXECUTE;
        }
        perms
    }
}

/// Trait for accessing and manipulating memory for module sections
pub trait SectionMemOps: Send + Sync {
    fn as_ptr(&self) -> *const u8;
    fn as_mut_ptr(&mut self) -> *mut u8;
    /// Change the permissions of the memory region
    fn change_perms(&mut self, perms: SectionPerm) -> bool;
}

/// Trait for kernel module helper functions
pub trait KernelModuleHelper {
    /// Allocate virtual memory for module section
    fn vmalloc(size: usize) -> Box<dyn SectionMemOps>;
    /// Resolve symbol name to address
    fn resolve_symbol(name: &str) -> Option<usize>;
    /// Flush CPU cache for the given memory region
    fn flsuh_cache(_addr: usize, _size: usize) {
        // Default implementation does nothing
    }
}

pub struct ModuleLoader<'a, H: KernelModuleHelper> {
    elf: Elf<'a>,
    elf_data: &'a [u8],
    __helper: core::marker::PhantomData<H>,
}

struct SectionPages {
    name: String,
    addr: Box<dyn SectionMemOps>,
    size: usize,
    perms: SectionPerm,
}

pub struct ModuleOwner<H: KernelModuleHelper> {
    module_info: ModuleInfo,
    pages: Vec<SectionPages>,
    name: String,
    module: Module,
    #[allow(unused)]
    pub(crate) arch: ModuleArchSpecific,
    _helper: core::marker::PhantomData<H>,
}

impl<H: KernelModuleHelper> ModuleOwner<H> {
    /// Get the name of the module
    pub fn name(&self) -> &str {
        &self.name
    }

    pub(crate) fn set_name(&mut self, name: &str) {
        self.name = name.to_string();
    }

    /// Call the module's init function
    pub fn call_init(&mut self) -> Result<i32> {
        if let Some(init_fn) = self.module.take_init_fn() {
            let result = unsafe { init_fn() };
            Ok(result)
        } else {
            log::warn!("The init function can only be called once.");
            Err(ModuleErr::EINVAL)
        }
    }

    /// Call the module's exit function
    pub fn call_exit(&mut self) {
        if let Some(exit_fn) = self.module.take_exit_fn() {
            log::warn!("Calling module exit function...");
            unsafe {
                exit_fn();
            }
        } else {
            log::warn!("The exit function can only be called once.");
        }
    }
}

const fn align_up(addr: usize, align: usize) -> usize {
    (addr + align - 1) & !(align - 1)
}

// const fn align_down(addr: usize, align: usize) -> usize {
//     addr & !(align - 1)
// }

const SKIP_SECTIONS: &[&str] = &[".note", ".modinfo", "__version"];

pub(crate) struct ModuleLoadInfo {
    pub(crate) syms: Vec<(goblin::elf::sym::Sym, String)>,
}

impl<'a, H: KernelModuleHelper> ModuleLoader<'a, H> {
    /// create a new ELF loader
    pub fn new(elf_data: &'a [u8]) -> Result<Self> {
        let elf = Elf::parse(elf_data).map_err(|_| ModuleErr::ENOEXEC)?;
        if !elf.is_64 {
            return Err(ModuleErr::ENOEXEC);
        }
        Ok(ModuleLoader {
            elf,
            elf_data,
            __helper: core::marker::PhantomData,
        })
    }

    /// Check module signature
    ///
    /// See <https://elixir.bootlin.com/linux/v6.6/source/kernel/module/signing.c#L70>
    fn module_sig_check(&self) -> bool {
        // TODO: implement module signature check
        true
    }

    /// Check userspace passed ELF module against our expectations, and cache
    /// useful variables for further processing as we go.
    ///
    /// See <https://elixir.bootlin.com/linux/v6.6/source/kernel/module/main.c#L1669>
    fn elf_validity_cache_copy(&self) -> Result<ModuleOwner<H>> {
        if self.elf.header.e_type != goblin::elf::header::ET_REL {
            log::error!(
                "Invalid ELF type: {}, expected ET_REL",
                self.elf.header.e_type
            );
            return Err(ModuleErr::ENOEXEC);
        }

        elf_check_arch(&self.elf)?;

        // Verify if the section name table index is valid.
        if self.elf.header.e_shstrndx == goblin::elf::section_header::SHN_UNDEF as _
            || self.elf.header.e_shstrndx as usize >= self.elf.section_headers.len()
        {
            log::error!(
                "Invalid ELF section name index: {} || e_shstrndx ({}) >= e_shnum ({})",
                self.elf.header.e_shstrndx,
                self.elf.header.e_shstrndx,
                self.elf.section_headers.len()
            );
            return Err(ModuleErr::ENOEXEC);
        }

        // The section name table must be NUL-terminated, as required
        // by the spec. This makes strcmp and pr_* calls that access
        // strings in the section safe.
        if self.elf.shdr_strtab.len() == 0 {
            log::error!("ELF section name string table is empty");
            return Err(ModuleErr::ENOEXEC);
        }

        // The code assumes that section 0 has a length of zero and
        // an addr of zero, so check for it.
        if self.elf.section_headers[0].sh_type != goblin::elf::section_header::SHT_NULL
            || self.elf.section_headers[0].sh_size != 0
            || self.elf.section_headers[0].sh_addr != 0
        {
            log::error!(
                "ELF Spec violation: section 0 type({})!=SH_NULL or non-zero len or addr",
                self.elf.section_headers[0].sh_type
            );
            return Err(ModuleErr::ENOEXEC);
        }

        let mut num_sym_secs = 0;
        let mut num_mod_secs = 0;
        let mut num_info_secs = 0;
        let mut info_idx = 0;
        let mut mod_idx = 0;
        for (idx, shdr) in self.elf.section_headers.iter().enumerate() {
            let ty = shdr.sh_type;
            match ty {
                goblin::elf::section_header::SHT_NULL | goblin::elf::section_header::SHT_NOBITS => {
                    continue;
                }
                goblin::elf::section_header::SHT_SYMTAB => {
                    if shdr.sh_link == goblin::elf::section_header::SHN_UNDEF
                        || shdr.sh_link as usize >= self.elf.section_headers.len()
                    {
                        log::error!(
                            "Invalid ELF sh_link!=SHN_UNDEF({}) or (sh_link({}) >= hdr->e_shnum({})",
                            shdr.sh_link,
                            shdr.sh_link,
                            self.elf.section_headers.len()
                        );
                        return Err(ModuleErr::ENOEXEC);
                    }
                    num_sym_secs += 1;
                }
                _ => {
                    let shdr_name = self
                        .elf
                        .shdr_strtab
                        .get_at(shdr.sh_name)
                        .ok_or(ModuleErr::ENOEXEC)?;
                    if shdr_name == ".gnu.linkonce.this_module" {
                        num_mod_secs += 1;
                        mod_idx = idx;
                    } else if shdr_name == ".modinfo" {
                        num_info_secs += 1;
                        info_idx = idx;
                    }

                    if shdr.sh_flags == goblin::elf::section_header::SHF_ALLOC as _ {
                        // Check that section names are valid
                        let _name = self
                            .elf
                            .shdr_strtab
                            .get_at(shdr.sh_name)
                            .ok_or(ModuleErr::ENOEXEC)?;
                    }
                }
            }
        }

        let mut owner = None;
        if num_info_secs > 1 {
            log::error!("Only one .modinfo section must exist.");
            return Err(ModuleErr::ENOEXEC);
        } else if num_info_secs == 1 {
            owner = Some(self.pre_read_modinfo(info_idx)?);
            if let Some(ref o) = owner {
                log::error!("Module({:?}) info: {:?}", o.name(), o.module_info);
            }
        }
        let mut owner = owner.ok_or(ModuleErr::ENOEXEC)?;
        let module_name = owner.name();

        if num_sym_secs != 1 {
            log::error!("{}: module has no symbols (stripped?)", module_name);
            return Err(ModuleErr::ENOEXEC);
        }
        /*
         * The ".gnu.linkonce.this_module" ELF section is special. It is
         * what modpost uses to refer to __this_module and let's use rely
         * on THIS_MODULE to point to &__this_module properly. The kernel's
         * modpost declares it on each modules's *.mod.c file. If the struct
         * module of the kernel changes a full kernel rebuild is required.
         *
         * We have a few expectaions for this special section, the following
         * code validates all this for us:
         *
         *   o Only one section must exist
         *   o We expect the kernel to always have to allocate it: SHF_ALLOC
         *   o The section size must match the kernel's run time's struct module
         *     size
         */
        if num_mod_secs != 1 {
            log::error!(
                "{}: Only one .gnu.linkonce.this_module section must exist.",
                module_name
            );
            return Err(ModuleErr::ENOEXEC);
        }

        let this_module_shdr = &self.elf.section_headers[mod_idx];
        if this_module_shdr.sh_type == goblin::elf::section_header::SHT_NOBITS {
            log::error!(
                "{}: .gnu.linkonce.this_module section must have a size set",
                module_name
            );
            return Err(ModuleErr::ENOEXEC);
        }

        if this_module_shdr.sh_flags & goblin::elf::section_header::SHF_ALLOC as u64 == 0 {
            log::error!(
                "{}: .gnu.linkonce.this_module section size must match the kernel's built struct module size at run time",
                module_name
            );
            return Err(ModuleErr::ENOEXEC);
        }
        // If we didn't load the .modinfo 'name' field earlier, fall back to
        // on-disk struct mod 'name' field.
        if owner.name().is_empty() {
            self.pre_read_this_module(mod_idx, &mut owner)?;
        }
        Ok(owner)
    }

    /// Load the module into kernel space
    pub fn load_module(mut self, args: CString) -> Result<ModuleOwner<H>> {
        if !self.module_sig_check() {
            log::error!("Module signature check failed");
            return Err(ModuleErr::ENOEXEC);
        }
        // let arch = offset_of!(kmod::kbindings::module, arch);
        // log::error!("Offset of module.arch: {}", arch);
        let mut owner = self.elf_validity_cache_copy()?;

        self.layout_and_allocate(&mut owner)?;
        let load_info = self.simplify_symbols(&owner)?;
        self.apply_relocations(load_info, &mut owner)?;

        self.post_read_this_module(&mut owner)?;

        self.find_module_sections(&mut owner)?;

        self.complete_formation(&mut owner)?;

        self.parse_args(&mut owner, args)?;

        log::error!("Module({:?}) loaded successfully!", owner.name());
        Ok(owner)
    }

    /// Args looks like "foo=bar,bar2 baz=fuz wiz". Parse them and set module parameters.
    fn parse_args(&self, owner: &mut ModuleOwner<H>, args: CString) -> Result<()> {
        let name = owner.name().to_string();
        let kparams = owner.module.params_mut();
        let after_dashes = crate::param::parse_args(&name, args, kparams, i16::MIN, i16::MAX)?;
        if !after_dashes.is_empty() {
            log::warn!(
                "[{}]: parameters '{}' after '--' ignored",
                name,
                after_dashes.to_str().unwrap_or("<invalid UTF-8>")
            );
        }
        Ok(())
    }

    /// Find section by name
    fn find_section(&self, name: &str) -> Result<&SectionHeader> {
        for shdr in &self.elf.section_headers {
            let sec_name = self
                .elf
                .shdr_strtab
                .get_at(shdr.sh_name)
                .ok_or(ModuleErr::ENOEXEC)?;

            if sec_name == name {
                return Ok(shdr);
            }
        }
        log::error!("Section '{}' not found", name);
        Err(ModuleErr::ENOEXEC)
    }

    fn pre_read_modinfo(&self, info_idx: usize) -> Result<ModuleOwner<H>> {
        let modinfo_shdr = &self.elf.section_headers[info_idx];
        let file_offset = modinfo_shdr.sh_offset as usize;
        let size = modinfo_shdr.sh_size as usize;

        let mut modinfo_data = &self.elf_data[file_offset..file_offset + size];
        let mut module_info = ModuleInfo::new();

        log::info!("Reading .modinfo section (size: {:#x})", size);

        // read the modinfo data
        // format is key=value\0key=value\0...
        loop {
            if modinfo_data.is_empty() {
                break;
            }
            let cstr = CStr::from_bytes_until_nul(modinfo_data)
                .map_err(|_| ModuleErr::EINVAL)
                .unwrap();
            let str_slice = cstr.to_str().map_err(|_| ModuleErr::EINVAL)?;
            modinfo_data = &modinfo_data[cstr.to_bytes_with_nul().len()..];

            let mut split = str_slice.splitn(2, '=');
            let key = split.next().ok_or(ModuleErr::EINVAL)?.to_string();
            let value = split.next().ok_or(ModuleErr::EINVAL)?.to_string();
            module_info.add_kv(key, value);
        }

        let name = module_info
            .get("name")
            .map(|s| s.to_string())
            .unwrap_or_else(|| "".to_string());

        Ok(ModuleOwner {
            name,
            module_info,
            pages: Vec::new(),
            module: Module::default(),
            arch: ModuleArchSpecific::default(),
            _helper: core::marker::PhantomData,
        })
    }

    /// Read the __this_module structure to get module name. If the name of owner
    /// is not set, set it here.
    fn pre_read_this_module(&self, idx: usize, owner: &mut ModuleOwner<H>) -> Result<()> {
        let this_module_shdr = &self.elf.section_headers[idx];
        let size = this_module_shdr.sh_size as usize;
        if size != core::mem::size_of::<Module>() {
            log::error!(
                "Invalid .gnu.linkonce.this_module section size: {}, expected: {}",
                size,
                core::mem::size_of::<Module>()
            );
            return Err(ModuleErr::ENOEXEC);
        }
        let modinfo_data = this_module_shdr.sh_addr as *mut u8;
        let module = unsafe { core::ptr::read(modinfo_data as *const Module) };
        let name = module.name();
        owner.set_name(name);
        Ok(())
    }

    /// After relocating, read the __this_module structure to get init and exit function pointers
    fn post_read_this_module(&mut self, owner: &mut ModuleOwner<H>) -> Result<()> {
        let this_module_shdr = self.find_section(".gnu.linkonce.this_module")?;
        // the data address is the allocated virtual address and it has been relocated
        let modinfo_data = this_module_shdr.sh_addr as *mut u8;
        let module = unsafe { core::ptr::read(modinfo_data as *const Module) };

        let init_fn = module.init_fn();
        let exit_fn = module.exit_fn();

        log::error!(
            "Module init_fn: {:?}, exit_fn: {:?}",
            init_fn.map(|f| f as *const ()),
            exit_fn.map(|f| f as *const ())
        );

        owner.module = module;
        Ok(())
    }

    /// Get number of objects and starting address of a section.
    fn section_objs(&self, name: &str, object_size: usize) -> Result<(usize, *const u8)> {
        let section = self
            .find_section(name)
            .unwrap_or(&self.elf.section_headers[0]); // Section 0 has sh_addr 0 and sh_size 0.
        let num = section.sh_size as usize / object_size;
        let addr = section.sh_addr as *const u8;
        Ok((num, addr))
    }

    fn find_module_sections(&self, owner: &mut ModuleOwner<H>) -> Result<()> {
        let (num_kparams, kparam_addr) =
            self.section_objs("__param", size_of::<kmod_tools::kernel_param>())?;
        let raw_module = owner.module.raw_mod();
        raw_module.kp = kparam_addr as *mut kmod_tools::kernel_param;
        raw_module.num_kp = num_kparams as _;

        // TODO: implement finding other sections:
        // __ksymtab
        // __kcrctab
        // __ksymtab_gpl
        // __kcrctab_gpl
        Ok(())
    }

    /// Finally it's fully formed, ready to start executing.
    fn complete_formation(&self, owner: &mut ModuleOwner<H>) -> Result<()> {
        for page in &mut owner.pages {
            if !page.addr.change_perms(page.perms) {
                log::error!(
                    "Failed to change permissions of section '{}' to {}",
                    page.name,
                    page.perms
                );
                return Err(ModuleErr::EINVAL);
            }
            H::flsuh_cache(page.addr.as_ptr() as usize, page.size);
        }
        Ok(())
    }

    /// Layout sections and allocate memory
    /// See <https://elixir.bootlin.com/linux/v6.6/source/kernel/module/main.c#L2363>
    fn layout_and_allocate(&mut self, owner: &mut ModuleOwner<H>) -> Result<()> {
        // Allow arches to frob section contents and sizes
        #[cfg(feature = "module-sections")]
        crate::arch::module_frob_arch_sections(&mut self.elf, owner)?;
        for shdr in self.elf.section_headers.iter_mut() {
            let sec_name = self
                .elf
                .shdr_strtab
                .get_at(shdr.sh_name)
                .unwrap_or("<unknown>");

            // Skip non-allocatable sections
            if (shdr.sh_flags & goblin::elf::section_header::SHF_ALLOC as u64) == 0 {
                log::debug!("Skipping non-allocatable section '{}'", sec_name);
                continue;
            }

            // Skip sections in the skip list
            if SKIP_SECTIONS.iter().any(|&s| sec_name.starts_with(s)) {
                log::warn!("Skipping section '{}' in skip list", sec_name);
                continue;
            }

            let file_offset = shdr.sh_offset as usize;
            let size = shdr.sh_size as usize;

            let perms = SectionPerm::from_elf_flags(shdr.sh_flags);

            if size == 0 {
                log::error!("Skipping zero-size section '{}'", sec_name);
                continue;
            }

            let aligned_size = align_up(size, 4096);

            // Allocate memory for the section
            let mut addr = H::vmalloc(aligned_size);
            if addr.as_ptr().is_null() {
                return Err(ModuleErr::ENOSPC);
            }

            let raw_addr = addr.as_ptr() as u64;

            // Copy section data from ELF to allocated memory
            // For SHT_NOBITS sections (like .bss), memory is already zeroed by vmalloc
            if shdr.sh_type != goblin::elf::section_header::SHT_NOBITS {
                let section_data = &self.elf_data[file_offset..file_offset + size];
                unsafe {
                    core::ptr::copy_nonoverlapping(section_data.as_ptr(), addr.as_mut_ptr(), size);
                }
            }

            // Store the allocated page info
            owner.pages.push(SectionPages {
                name: sec_name.to_string(),
                addr,
                size: aligned_size,
                perms,
            });

            // update section address
            // Note: In a real loader, we would update the section header's sh_addr field
            // to reflect the new virtual address.
            shdr.sh_addr = raw_addr;
        }

        for page in &owner.pages {
            log::error!(
                "Allocated section '{:>26}' at {:p} [{}] ({:8<#x})",
                page.name,
                page.addr.as_ptr(),
                page.perms,
                page.size
            );
        }

        Ok(())
    }

    /// Change all symbols so that st_value encodes the pointer directly.
    ///
    /// See <https://elixir.bootlin.com/linux/v6.6/source/kernel/module/main.c#L1367>
    fn simplify_symbols(&self, owner: &ModuleOwner<H>) -> Result<ModuleLoadInfo> {
        let mut loadinfo = ModuleLoadInfo { syms: Vec::new() };

        // Skip the first symbol (index 0), which is always the undefined symbol
        for (idx, sym) in self.elf.syms.iter().enumerate() {
            if idx == 0 {
                loadinfo.syms.push((sym, "".to_string()));
                // Symbol 0 is always SHN_UNDEF and should be skipped
                continue;
            }

            let sym_name = self
                .elf
                .strtab
                .get_at(sym.st_name)
                .unwrap_or("<unknown>")
                .to_string();

            let sym_value = sym.st_value;
            let sym_size = sym.st_size;

            // For debugging purposes, print symbol info
            log::debug!(
                "Symbol: ('{}') [{}] Value: 0x{:016x} Size: {}",
                sym_name,
                sym_section_to_str(sym.st_shndx as _),
                sym_value,
                sym_size
            );

            // Create a mutable copy for potential updates
            let mut updated_sym = sym;

            match sym.st_shndx as _ {
                goblin::elf::section_header::SHN_UNDEF => {
                    // Undefined symbol
                    let sym_address = H::resolve_symbol(&sym_name);
                    // Ok if resolved.
                    if let Some(addr) = sym_address {
                        log::error!(
                            "  -> Resolved undefined symbol '{}' ({}) to address 0x{:016x}",
                            sym_name,
                            sym_bind_to_str(sym.st_bind()),
                            addr
                        );
                        // Update the symbol table entry's st_value to the resolved address
                        updated_sym.st_value = addr as u64;
                    } else {
                        // Ok if weak or ignored.
                        if sym.st_bind() == goblin::elf::sym::STB_WEAK {
                            log::warn!(
                                "  -> Unresolved weak symbol '{}' ({})",
                                sym_name,
                                sym_bind_to_str(sym.st_bind())
                            );
                        } else {
                            log::warn!(
                                "  -> Unresolved symbol '{}' ({})",
                                sym_name,
                                sym_bind_to_str(sym.st_bind())
                            );
                            return Err(ModuleErr::ENOENT);
                        }
                    }
                }
                goblin::elf::section_header::SHN_ABS => {
                    // Don't need to do anything
                    log::debug!("Absolute symbol: {} 0x{:x}", sym_name, sym_value);
                }
                goblin::elf::section_header::SHN_COMMON => {
                    // Ignore common symbols
                    // We compiled with -fno-common. These are not supposed to happen.
                    log::debug!("Common symbol: {}", sym_name);
                    log::warn!("{:?}: please compile with -fno-common", owner.name());
                    return Err(ModuleErr::ENOEXEC);
                }
                ty => {
                    /* Divert to percpu allocation if a percpu var. */
                    // if (sym[i].st_shndx == info->index.pcpu)
                    //     secbase = (unsigned long)mod_percpu(mod);
                    // else
                    //     secbase = info->sechdrs[sym[i].st_shndx].sh_addr;
                    // sym[i].st_value += secbase;

                    // TODO: Handle special sections like percpu
                    // Normal symbol defined in a section
                    // Add section base address to symbol's offset within the section
                    let secbase = self.elf.section_headers[ty as usize].sh_addr;
                    updated_sym.st_value = sym.st_value.wrapping_add(secbase);
                    log::trace!(
                        "  -> Defined symbol '{}' in section {} at address 0x{:016x} (base: 0x{:016x} + offset: 0x{:016x})",
                        sym_name,
                        ty,
                        updated_sym.st_value,
                        secbase,
                        sym.st_value
                    );
                }
            }

            // Push the updated symbol to the list
            loadinfo.syms.push((updated_sym, sym_name));
        }

        Ok(loadinfo)
    }

    /// See <https://elixir.bootlin.com/linux/v6.6/source/kernel/module/main.c#L1438>
    fn apply_relocations(
        &self,
        load_info: ModuleLoadInfo,
        owner: &mut ModuleOwner<H>,
    ) -> Result<()> {
        for shdr in self.elf.section_headers.iter() {
            let infosec = shdr.sh_info;

            let sec_name = self
                .elf
                .shdr_strtab
                .get_at(shdr.sh_name)
                .ok_or(ModuleErr::ENOEXEC)?;

            // Not a valid relocation section?
            if infosec >= self.elf.section_headers.len() as u32 {
                continue;
            }
            // Don't bother with non-allocated sections
            if self.elf.section_headers[infosec as usize].sh_flags
                & goblin::elf::section_header::SHF_ALLOC as u64
                == 0
            {
                continue;
            }

            // Skip non-relocation sections
            if shdr.sh_type != goblin::elf::section_header::SHT_RELA {
                continue;
            }

            let to_section = &self.elf.section_headers[infosec as usize];
            let to_sec_name = self
                .elf
                .shdr_strtab
                .get_at(to_section.sh_name)
                .ok_or(ModuleErr::ENOEXEC)?;

            let rela_entries = shdr.sh_size as usize / shdr.sh_entsize as usize;
            log::error!(
                "Applying relocations for section '{}' to '{}', {} entries",
                sec_name,
                to_sec_name,
                rela_entries
            );

            let offset = shdr.sh_offset as usize;
            // Size of Elf64_Rela
            debug_assert!(shdr.sh_entsize == 24);

            let data_buf = &self.elf_data[offset..offset + shdr.sh_size as usize];
            let rela_list = unsafe {
                goblin::elf64::reloc::from_raw_rela(data_buf.as_ptr() as _, shdr.sh_size as usize)
            };

            crate::arch::ArchRelocate::apply_relocate_add(
                rela_list,
                shdr,
                &self.elf.section_headers,
                &load_info,
                owner,
            )?;
        }
        Ok(())
    }
}

const fn sym_bind_to_str(bind: u8) -> &'static str {
    match bind {
        goblin::elf::sym::STB_LOCAL => "LOCAL",
        goblin::elf::sym::STB_GLOBAL => "GLOBAL",
        goblin::elf::sym::STB_WEAK => "WEAK",
        _ => "UNKNOWN",
    }
}

const fn sym_section_to_str(shndx: u32) -> &'static str {
    match shndx {
        goblin::elf::section_header::SHN_UNDEF => "UNDEF(0)",
        goblin::elf::section_header::SHN_LORESERVE => "LORESERVE(0xff00)",
        // goblin::elf::section_header::SHN_LOPROC => "LOPROC(0xff00)",
        goblin::elf::section_header::SHN_HIPROC => "HIPROC(0xff1f)",
        goblin::elf::section_header::SHN_ABS => "ABS(0xfff1)",
        goblin::elf::section_header::SHN_COMMON => "COMMON(0xfff2)",
        goblin::elf::section_header::SHN_HIRESERVE => "HIRESERVE(0xffff)",
        _ => "OTHER",
    }
}

// #define SHN_LIVEPATCH	0xff20

/// Check if the ELF file is for a supported architecture
fn elf_check_arch(elf: &goblin::elf::Elf) -> Result<()> {
    if elf.header.e_machine != goblin::elf::header::EM_AARCH64
        && elf.header.e_machine != goblin::elf::header::EM_X86_64
        && elf.header.e_machine != goblin::elf::header::EM_RISCV
        && elf.header.e_machine != goblin::elf::header::EM_LOONGARCH
    {
        log::error!(
            "Invalid ELF machine: {}, expected AARCH64({}), X86_64({}), RISC-V({}), LOONGARCH({})",
            elf.header.e_machine,
            goblin::elf::header::EM_AARCH64,
            goblin::elf::header::EM_X86_64,
            goblin::elf::header::EM_RISCV,
            goblin::elf::header::EM_LOONGARCH
        );
        return Err(ModuleErr::ENOEXEC);
    }
    Ok(())
}