diff mbox series

[MINI-OS,04/12] kexec: analyze new kernel for kexec

Message ID 20250321092451.17309-5-jgross@suse.com (mailing list archive)
State New
Headers show
Series kexec: add kexec support to Mini-OS | expand

Commit Message

Jürgen Groß March 21, 2025, 9:24 a.m. UTC
Analyze the properties of the new kernel to be loaded by kexec. The
data needed is:

- upper boundary in final location
- copy and memory clear operations
- entry point and entry parameter

Signed-off-by: Juergen Gross <jgross@suse.com>
---
 arch/x86/kexec.c |  91 +++++++++++++++++++++++++++++++++++
 include/kexec.h  |  11 +++++
 kexec.c          | 120 ++++++++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 220 insertions(+), 2 deletions(-)
diff mbox series

Patch

diff --git a/arch/x86/kexec.c b/arch/x86/kexec.c
index bf247797..2069f3c6 100644
--- a/arch/x86/kexec.c
+++ b/arch/x86/kexec.c
@@ -28,8 +28,15 @@ 
 
 #include <mini-os/os.h>
 #include <mini-os/lib.h>
+#include <mini-os/e820.h>
+#include <mini-os/err.h>
 #include <mini-os/kexec.h>
 
+#include <xen/elfnote.h>
+#include <xen/arch-x86/hvm/start_info.h>
+
+static unsigned long kernel_entry = ~0UL;
+
 /*
  * Final stage of kexec. Copies all data to the final destinations, zeroes
  * .bss and activates new kernel.
@@ -106,4 +113,88 @@  void do_kexec(void *kexec_page)
         :"m" (final));
 }
 
+bool kexec_chk_arch(elf_ehdr *ehdr)
+{
+    return ehdr->e32.e_machine == EM_386 || ehdr->e32.e_machine == EM_X86_64;
+}
+
+static unsigned int note_data_sz(unsigned int sz)
+{
+    return (sz + 3) & ~3;
+}
+
+static void check_notes_entry(elf_ehdr *ehdr, void *start, unsigned int len)
+{
+    elf_note *note = start;
+    unsigned int off, note_len, namesz, descsz;
+    char *val;
+
+    for ( off = 0; off < len; off += note_len )
+    {
+        namesz = note_data_sz(note_val(ehdr, note, namesz));
+        descsz = note_data_sz(note_val(ehdr, note, descsz));
+        val = note_val(ehdr, note, data);
+        note_len = val - (char *)note + namesz + descsz;
+
+        if ( !strncmp(val, "Xen", namesz) &&
+             note_val(ehdr, note, type) == XEN_ELFNOTE_PHYS32_ENTRY )
+        {
+            val += namesz;
+            switch ( note_val(ehdr, note, descsz) )
+            {
+            case 1:
+                kernel_entry = *(uint8_t *)val;
+                return;
+            case 2:
+                kernel_entry = *(uint16_t *)val;
+                return;
+            case 4:
+                kernel_entry = *(uint32_t *)val;
+                return;
+            case 8:
+                kernel_entry = *(uint64_t *)val;
+                return;
+            default:
+                break;
+            }
+        }
+
+        note = elf_ptr_add(note, note_len);
+    }
+}
+
+int kexec_arch_analyze_phdr(elf_ehdr *ehdr, elf_phdr *phdr)
+{
+    void *notes_start;
+    unsigned int notes_len;
+
+    if ( phdr_val(ehdr, phdr, p_type) != PT_NOTE || kernel_entry != ~0UL )
+        return 0;
+
+    notes_start = elf_ptr_add(ehdr, phdr_val(ehdr, phdr, p_offset));
+    notes_len = phdr_val(ehdr, phdr, p_filesz);
+    check_notes_entry(ehdr, notes_start, notes_len);
+
+    return 0;
+}
+
+int kexec_arch_analyze_shdr(elf_ehdr *ehdr, elf_shdr *shdr)
+{
+    void *notes_start;
+    unsigned int notes_len;
+
+    if ( shdr_val(ehdr, shdr, sh_type) != SHT_NOTE || kernel_entry != ~0UL )
+        return 0;
+
+    notes_start = elf_ptr_add(ehdr, shdr_val(ehdr, shdr, sh_offset));
+    notes_len = shdr_val(ehdr, shdr, sh_size);
+    check_notes_entry(ehdr, notes_start, notes_len);
+
+    return 0;
+}
+
+bool kexec_arch_need_analyze_shdrs(void)
+{
+    return kernel_entry == ~0UL;
+}
 #endif /* CONFIG_KEXEC */
diff --git a/include/kexec.h b/include/kexec.h
index 722be456..f54cbb90 100644
--- a/include/kexec.h
+++ b/include/kexec.h
@@ -1,5 +1,6 @@ 
 #ifndef _KEXEC_H
 #define _KEXEC_H
+#include <mini-os/elf.h>
 
 /* One element of kexec actions (last element must have action KEXEC_CALL): */
 struct kexec_action {
@@ -18,6 +19,8 @@  struct kexec_action {
 extern char _kexec_start[], _kexec_end[];
 extern struct kexec_action kexec_actions[KEXEC_MAX_ACTIONS];
 
+extern unsigned long kexec_last_addr;
+
 int kexec_add_action(int action, void *dest, void *src, unsigned int len);
 
 #define KEXEC_SECSIZE ((unsigned long)_kexec_end - (unsigned long)_kexec_start)
@@ -31,4 +34,12 @@  void do_kexec(void *kexec_page);
 /* Assembler code for switching off paging and passing execution to new OS. */
 void kexec_phys(void);
 
+/* Check kernel to match current architecture. */
+bool kexec_chk_arch(elf_ehdr *ehdr);
+
+/* Architecture specific ELF handling functions. */
+int kexec_arch_analyze_phdr(elf_ehdr *ehdr, elf_phdr *phdr);
+int kexec_arch_analyze_shdr(elf_ehdr *ehdr, elf_shdr *shdr);
+bool kexec_arch_need_analyze_shdrs(void);
+
 #endif /* _KEXEC_H */
diff --git a/kexec.c b/kexec.c
index 849a98e4..3ff4ea07 100644
--- a/kexec.c
+++ b/kexec.c
@@ -31,6 +31,9 @@ 
 #include <errno.h>
 #include <mini-os/os.h>
 #include <mini-os/lib.h>
+#include <mini-os/console.h>
+#include <mini-os/elf.h>
+#include <mini-os/err.h>
 #include <mini-os/kexec.h>
 
 /*
@@ -54,9 +57,122 @@ 
  * - The new kernel is activated.
  */
 
-int kexec(void *kernel, unsigned long kernel_size,
-          const char *cmdline)
+unsigned long kexec_last_addr;
+
+static int analyze_phdrs(elf_ehdr *ehdr)
+{
+    elf_phdr *phdr;
+    unsigned int n_hdr, i;
+    unsigned long paddr, offset, filesz, memsz;
+    int ret;
+
+    phdr = elf_ptr_add(ehdr, ehdr_val(ehdr, e_phoff));
+    n_hdr = ehdr_val(ehdr, e_phnum);
+    for ( i = 0; i < n_hdr; i++ )
+    {
+        ret = kexec_arch_analyze_phdr(ehdr, phdr);
+        if ( ret )
+            return ret;
+
+        if ( phdr_val(ehdr, phdr, p_type) == PT_LOAD &&
+             (phdr_val(ehdr, phdr, p_flags) & (PF_X | PF_W | PF_R)) )
+        {
+            paddr = phdr_val(ehdr, phdr, p_paddr);
+            offset = phdr_val(ehdr, phdr, p_offset);
+            filesz = phdr_val(ehdr, phdr, p_filesz);
+            memsz = phdr_val(ehdr, phdr, p_memsz);
+            if ( filesz > 0 )
+            {
+                ret = kexec_add_action(KEXEC_COPY, to_virt(paddr),
+                                       (char *)ehdr + offset, filesz);
+                if ( ret )
+                    return ret;
+            }
+            if ( memsz > filesz )
+            {
+                ret = kexec_add_action(KEXEC_ZERO, to_virt(paddr + filesz),
+                                       NULL, memsz - filesz);
+                if ( ret )
+                    return ret;
+            }
+            if ( paddr + memsz > kexec_last_addr )
+                kexec_last_addr = paddr + memsz;
+        }
+
+        phdr = elf_ptr_add(phdr, ehdr_val(ehdr, e_phentsize));
+    }
+
+    return 0;
+}
+
+static int analyze_shdrs(elf_ehdr *ehdr)
 {
+    elf_shdr *shdr;
+    unsigned int n_hdr, i;
+    int ret;
+
+    if ( !kexec_arch_need_analyze_shdrs() )
+        return 0;
+
+    shdr = elf_ptr_add(ehdr, ehdr_val(ehdr, e_shoff));
+    n_hdr = ehdr_val(ehdr, e_shnum);
+    for ( i = 0; i < n_hdr; i++ )
+    {
+        ret = kexec_arch_analyze_shdr(ehdr, shdr);
+        if ( ret )
+            return ret;
+
+        shdr = elf_ptr_add(shdr, ehdr_val(ehdr, e_shentsize));
+    }
+
+    return 0;
+}
+
+static int analyze_kernel(void *kernel, unsigned long size)
+{
+    elf_ehdr *ehdr = kernel;
+    int ret;
+
+    if ( !IS_ELF(ehdr->e32) )
+    {
+        printk("kexec: new kernel not an ELF file\n");
+        return ENOEXEC;
+    }
+    if ( ehdr->e32.e_ident[EI_DATA] != ELFDATA2LSB )
+    {
+        printk("kexec: ELF file of new kernel is big endian\n");
+        return ENOEXEC;
+    }
+    if ( !elf_is_32bit(ehdr) && !elf_is_64bit(ehdr) )
+    {
+        printk("kexec: ELF file of new kernel is neither 32 nor 64 bit\n");
+        return ENOEXEC;
+    }
+    if ( !kexec_chk_arch(ehdr) )
+    {
+        printk("kexec: ELF file of new kernel is not compatible with arch\n");
+        return ENOEXEC;
+    }
+
+    ret = analyze_phdrs(ehdr);
+    if ( ret )
+        return ret;
+
+    ret = analyze_shdrs(ehdr);
+    if ( ret )
+        return ret;
+
+    return 0;
+}
+
+int kexec(void *kernel, unsigned long kernel_size, const char *cmdline)
+{
+    int ret;
+
+    ret = analyze_kernel(kernel, kernel_size);
+    if ( ret )
+        return ret;
+
     return ENOSYS;
 }
 EXPORT_SYMBOL(kexec);