]> xenbits.xensource.com Git - unikraft/unikraft.git/commitdiff
support/scripts: Add script to generate arm64 linux boot header
authorMichalis Pappas <michalis@unikraft.io>
Thu, 20 Jul 2023 04:31:51 +0000 (06:31 +0200)
committerUnikraft <monkey@unikraft.io>
Fri, 11 Aug 2023 19:49:38 +0000 (19:49 +0000)
The Linux arm64 boot protocol [1] prepends the image with a modified
MZ header:

  u32 code0;                    /* Executable code */
  u32 code1;                    /* Executable code */
  u64 text_offset;              /* Image load offset, little endian */
  u64 image_size;               /* Effective Image size, little endian */
  u64 flags;                    /* kernel flags, little endian */
  u64 res2      = 0;            /* reserved */
  u64 res3      = 0;            /* reserved */
  u64 res4      = 0;            /* reserved */
  u32 magic     = 0x644d5241;   /* Magic number, little endian,"ARM\x64" */
  u32 res5;                     /* reserved (used for PE COFF offset) */

Introduce mklinux.py that generates the arm64 linux header when
CONFIG_LXBOOT is enabled on arm64.

[1] https://www.kernel.org/doc/Documentation/arm64/booting.txt

Signed-off-by: Michalis Pappas <michalis@unikraft.io>
Reviewed-by: Sergiu Moga <sergiu@unikraft.io>
Approved-by: Razvan Deaconescu <razvand@unikraft.io>
Tested-by: Unikraft CI <monkey@unikraft.io>
GitHub-Closes: #988

support/scripts/mklinux.py [new file with mode: 0755]

diff --git a/support/scripts/mklinux.py b/support/scripts/mklinux.py
new file mode 100755 (executable)
index 0000000..d84ce14
--- /dev/null
@@ -0,0 +1,105 @@
+#!/usr/bin/env python3
+
+import argparse
+import os
+import re
+import subprocess
+import sys
+
+# Linux arm64 boot header
+# https://www.kernel.org/doc/Documentation/arm64/booting.txt
+LINUX_ARM64_HDR = {
+    "CODE0"                   : (0x0000000091005a4d, 0x04),  # Offset   0
+    "CODE1"                   : [0x0000000000000000, 0x04],  # Offset   4
+    "LOAD_OFFS"               : [0x0000000000000000, 0x08],  # Offset   8
+    "IMAGE_SIZE"              : [0x0000000000000000, 0x08],  # Offset  16
+    "KERNEL_FLAGS"            : [0x0000000000000000, 0x08],  # Offset  24
+    "RES2"                    : (0x0000000000000000, 0x08),  # Offset  32
+    "RES3"                    : (0x0000000000000000, 0x08),  # Offset  40
+    "RES4"                    : (0x0000000000000000, 0x08),  # Offset  48
+    "MAGIC"                   : (0x00000000644d5241, 0x04),  # Offset  56
+    "RES5"                    : (0x0000000000000040, 0x04),  # Offset  60
+}
+LINUX_ARM64_HDR_SIZE = 64
+
+# surely there's a pythonic way for that
+def align_up(v, a):
+    return (((v) + (a)-1) & ~((a)-1))
+
+def autoint(x):
+    return int(x, 0)
+
+# Get the absolute value of symbol, as seen through `nm`
+def get_sym_val(elf, sym):
+    exp = r'^\s*' + r'([a-f0-9]+)' + r'\s+[A-Za-z]\s+' + sym + r'$'
+    out = subprocess.check_output(['nm', elf])
+
+    re_out = re.findall(exp, out.decode('ASCII'), re.MULTILINE)
+    if len(re_out) != 1:
+        raise Exception('Found no ' + sym + ' symbol.')
+
+    return int(re_out[0], 16)
+
+def main():
+    parser = argparse.ArgumentParser()
+    description='Prepends image with arm64 linux boot header.'
+    parser.add_argument('bin', help='Raw binary to prepend header with.')
+    parser.add_argument('elf', help='The ELF image the raw binary was derived from. '
+                        'Required for resolving symbols used in the header.')
+    opt = parser.parse_args()
+
+    ram_base = get_sym_val(opt.elf, r'_start_ram_addr')
+    img_base = get_sym_val(opt.elf, r'_base_addr')
+    entry = get_sym_val(opt.elf, r'_libkvmplat_entry')
+
+    # code1
+    #
+    # This contains an unconditional branch to the entry point.
+    # The encoding of the branch instruction according to the
+    # Arm ARM (DDI0487I.a) is:
+    #
+    # 31           25                            0
+    # ┌───────────┬───────────────────────────────┐
+    # │0 0 0 1 0 1│           imm26               │
+    # └───────────┴───────────────────────────────┘
+    #
+    # b <offs>
+    #
+    # where offs is encoded as "imm26" times 4, relatively
+    # to the branch instruction
+    #
+    pc = img_base - LINUX_ARM64_HDR_SIZE + 4
+    offs = entry - pc
+
+    # offset must be <= +128M. We don't accept negative offsets
+    # here as the header always prepends the image.
+    assert((offs) <= ((1 << 26) / 2 - 1))
+
+    LINUX_ARM64_HDR["CODE1"][0] = ((0b101 << 26) | (int(offs / 4)))
+
+    # load_offset
+    #
+    # This includes the header, so we subtract the header size
+    LINUX_ARM64_HDR["LOAD_OFFS"][0] = (img_base - ram_base) - LINUX_ARM64_HDR_SIZE
+
+    # kernel_flags
+    #
+    # For now assume little-endian, 4KiB pages, image anywhere in memory
+    LINUX_ARM64_HDR["KERNEL_FLAGS"][0] = 0xa
+
+    # image_size
+    #
+    # We arbitrarily set this to header size + image size aligned up to 2MiB
+    total_size = os.path.getsize(opt.bin) + LINUX_ARM64_HDR_SIZE
+    LINUX_ARM64_HDR["IMAGE_SIZE"][0] = align_up(total_size, 2 ** 21)
+
+    # Create final image
+    with open(opt.bin, 'r+b') as f:
+        img = f.read()
+        f.seek(0)
+        for field in [k for k in LINUX_ARM64_HDR.keys()]:
+            f.write(LINUX_ARM64_HDR[field][0].to_bytes(LINUX_ARM64_HDR[field][1], 'little'))
+        f.write(img)
+
+if __name__ == '__main__':
+    main()