commit d60e3e32916334532d527101871f8f429019786a
parent c612ee4b1ae6980f51ef14c0a4d990c6580c59aa
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Wed, 6 May 2026 12:55:41 -0700
AT.2: native PT_NOTE for PVH boot; retire elf-pvh-note shim
Three tcc patches replace the post-link elf-pvh-note.c host tool that
rewrote kernel.elf so QEMU's PVH `-kernel` path on amd64 could find
the Xen 18 entry note:
- note-section-sht-note: find_section() creates `.note*` sections as
SHT_NOTE.
- pt-note-phdr: elf_output_file() bumps phnum and emits one PT_NOTE
covering the SHT_NOTE+SHF_ALLOC range. Gated on actual presence,
so aarch64/riscv64 (no .note sections) keep their existing phnum
and produce byte-identical output.
- load-obj-accept-sht-note: tcc_load_object_file() accepts SHT_NOTE
in its section-type filter. Strict pair with the find_section
change — without it, kernel-asm.o's .note.Xen is silently dropped
during link and the subsequent .rela.note.Xen merge segfaults the
linker on a NULL sm_table[].s.
Validation: aarch64 boot6/Image is byte-identical to pre-patch
(sha256 matches). amd64 boot6/kernel.elf has .note.Xen as NOTE,
exactly one PT_NOTE phdr at the right offset/size, and the Xen 18
note pointing at the kernel base.
Diffstat:
13 files changed, 250 insertions(+), 160 deletions(-)
diff --git a/docs/PLAN.md b/docs/PLAN.md
@@ -247,20 +247,32 @@ land" wording); audit found that wrong (AT.4 is a pure refactor).
`scripts/simple-patches/tcc-0.9.26/arm64-svcul-no-truncate*`; track
separately.
-- **AT.2. `.note.*` SHT_NOTE / PT_NOTE.** tcc emits `.note.*` sections
- as `SHT_PROGBITS` and never writes a `PT_NOTE` phdr, forcing the
- post-link `seed-kernel/scripts/elf-pvh-note.c` tool to rewrite the
- ELF for amd64 PVH boot. Two patches:
- - `note-section-sht-note.{before,after}` — in `tccelf.c:251`
+- **AT.2. `.note.*` SHT_NOTE / PT_NOTE.** [done] tcc emitted
+ `.note.*` sections as `SHT_PROGBITS` and never wrote a `PT_NOTE`
+ phdr, forcing the post-link `seed-kernel/scripts/elf-pvh-note.c`
+ tool to rewrite the ELF for amd64 PVH boot. Three patches landed in
+ `scripts/simple-patches/tcc-0.9.26/`:
+ - `note-section-sht-note.{before,after}` — in `tccelf.c`
`find_section()`, name-prefix-check `.note*` → create as `SHT_NOTE`.
- - `pt-note-phdr.{before,after}` — in `tccelf.c:elf_output_file`
- (~2202), gate `phnum++` on "any `SHT_NOTE+SHF_ALLOC` section
- exists" (else aarch64/riscv64 phnum perturbs and breaks the A5
- reproducibility target); compute `min(sh_offset)` /
- `max(sh_offset+sh_size)` over those sections; emit one `PT_NOTE`.
- Then delete `seed-kernel/scripts/elf-pvh-note.c`,
- `scripts/prep-src.sh:121–123`, `scripts/boot6.sh:80–89`,
- `scripts/boot6-gen-runscm.sh:99–117`. `seed-kernel/arch/amd64/kernel.lds`
+ - `pt-note-phdr.{before,after}` — in `tccelf.c:elf_output_file`,
+ gate `phnum++` on "any `SHT_NOTE+SHF_ALLOC` section exists" (else
+ aarch64/riscv64 phnum perturbs and breaks the A5 reproducibility
+ target); compute `min(sh_offset)` / `max(sh_offset+sh_size)`
+ over those sections; fill the leftover `PT_NULL` slot with one
+ `PT_NOTE` after `fill_unloadable_phdr` runs.
+ - `load-obj-accept-sht-note.{before,after}` — in
+ `tccelf.c:tcc_load_object_file`, add `SHT_NOTE` to the
+ accepted-section-type list. Strict pair with the find_section
+ change: without it, .o files emitted by the patched compiler
+ drop their `.note*` sections during link, then the subsequent
+ `.rela.note.*` merge derefs `sm_table[].s == NULL` and segfaults
+ the linker. (Found during validation: aarch64 was byte-identical,
+ amd64 boot6 link crashed under QEMU until this third patch.)
+ Then deleted `seed-kernel/scripts/elf-pvh-note.c`, the
+ `cp seed-kernel/scripts/elf-pvh-note.c` block in
+ `scripts/prep-src.sh`, the `if [ "$ARCH" = amd64 ]` block in
+ `scripts/boot6.sh`, and the amd64 PVH-fixup block in
+ `scripts/boot6-gen-runscm.sh`. `seed-kernel/arch/amd64/kernel.lds`
is unchanged (consumed by clang+ld.lld, not tcc).
- **AT.3. riscv64 inline-asm — audit + memory update only, no patch.**
@@ -308,10 +320,10 @@ land" wording); audit found that wrong (AT.4 is a pure refactor).
- `vendor/mes-libc/mes/abtol.c` (AT.1 preferred fix) **or** new patch
`scripts/simple-patches/tcc-0.9.26/tccasm-asm-expr-parse-number.*`
(AT.1 alternative).
-- `scripts/simple-patches/tcc-0.9.26/note-section-sht-note.*` and
- `pt-note-phdr.*` (AT.2). The patch directory is
- `scripts/simple-patches/tcc-0.9.26/` (not `seed-kernel/simple-patches/`,
- which doesn't exist).
+- `scripts/simple-patches/tcc-0.9.26/note-section-sht-note.*`,
+ `pt-note-phdr.*`, and `load-obj-accept-sht-note.*` (AT.2). The
+ patch directory is `scripts/simple-patches/tcc-0.9.26/` (not
+ `seed-kernel/simple-patches/`, which doesn't exist).
- `scripts/stage1-flatten.sh`: list any newly-added patches in
`apply_our_patch` (around line ~223); regenerate the flattened tree.
- `seed-kernel/arch/amd64/kernel.S`: lines 406–413 (`.quad` GDT
diff --git a/docs/TCC.md b/docs/TCC.md
@@ -521,3 +521,44 @@ narrow.
Until this is fixed, tcc1 is the "shake-out" stage and tcc2 is the
canonical compiler.
+
+## AT-series patches (post-bootstrap uniformity)
+
+These patches go beyond the bootstrap-stub patches in
+`scripts/simple-patches/tcc-0.9.26/` and exist to remove per-arch
+workarounds in seed-kernel and the build pipeline. They live in the
+same patch directory and are listed in `scripts/stage1-flatten.sh`'s
+`apply_our_patch` block.
+
+### AT.2 — native PT_NOTE for PVH boot
+
+Stock tcc 0.9.26 tags every assembler-created section as
+`SHT_PROGBITS` and emits only `PT_LOAD` phdrs for static EXEs.
+QEMU's PVH `-kernel` path on amd64 scans `PT_NOTE` phdrs for the
+Xen 18 note that names the 32-bit entry; without one it errors out
+("Error loading uncompressed kernel without PVH ELF Note"). The
+old workaround was a post-link host tool, `elf-pvh-note.c`, that
+rewrote the ELF after tcc finished. AT.2 replaces it with two
+patches in `tccelf.c`:
+
+- `note-section-sht-note` — `find_section()` creates `.note*`
+ sections as `SHT_NOTE` instead of `SHT_PROGBITS`.
+- `pt-note-phdr` — `elf_output_file()` bumps `phnum` by one when at
+ least one `SHT_NOTE+SHF_ALLOC` section exists, then fills the
+ reserved phdr slot with a `PT_NOTE` covering
+ `[min(sh_offset), max(sh_offset+sh_size))` over those sections.
+ The bump is gated on actual presence so arches with no note
+ sections (aarch64, riscv64) keep the same phnum and produce
+ byte-identical output.
+- `load-obj-accept-sht-note` — `tcc_load_object_file()`'s
+ accepted-section-type list adds `SHT_NOTE`. Without this, .o
+ files emitted by the patched `find_section` (which now produces
+ `SHT_NOTE` for `.note*`) get silently skipped during the link;
+ the subsequent `.rela.note.*` merge then derefs a NULL
+ `sm_table[].s` for the (skipped) note section. Strict pair with
+ `note-section-sht-note`.
+
+After AT.2 the post-link `elf-pvh-note.c` tool, the amd64-only
+branch in `boot6.sh`, and the amd64 fixup block in
+`boot6-gen-runscm.sh` are all gone. The amd64 kernel is emitted
+ready-to-boot by tcc3.
diff --git a/scripts/boot6-gen-runscm.sh b/scripts/boot6-gen-runscm.sh
@@ -94,29 +94,6 @@ cat > "$OUT" <<EOF
"-o" "out/$OUT_FILE"
"out/kernel-asm.o" "out/kernel.o" "out/mmu.o" "out/mem.o")
"link $OUT_FILE")
-EOF
-
-if [ "$ARCH" = amd64 ]; then
- cat >> "$OUT" <<'EOF'
-
-;; amd64: tcc3 doesn't emit PT_NOTE phdrs, so QEMU's PVH `-kernel`
-;; path doesn't find the Xen 18 note we placed in `.note.Xen`.
-;; Build the fixup tool with tcc3 + boot4's libc/crt1/libtcc1 and
-;; rewrite the ELF in place.
-(write-string stdout "boot6: tcc3 build elf-pvh-note\n")
-(must (run "in/tcc3" "-nostdlib"
- "in/crt1.o" "in/elf-pvh-note.c"
- "in/libc.a" "in/libtcc1.a" "in/libc.a"
- "-o" "out/elf-pvh-note")
- "build elf-pvh-note")
-
-(write-string stdout "boot6: amd64 PVH note fixup\n")
-(must (run "out/elf-pvh-note" "out/kernel.elf")
- "elf-pvh-note kernel.elf")
-EOF
-fi
-
-cat >> "$OUT" <<EOF
(write-string stdout "boot6: ALL-OK\n")
(exit 0)
diff --git a/scripts/boot6.sh b/scripts/boot6.sh
@@ -15,8 +15,6 @@
## — DTB parse, MMU bring-up, syscalls,
## virtio-blk, tmpfs, ELF loader
## build/$ARCH/src/src/tcc-cc/mem.c memcpy/memset/memmove/memcmp
-## build/$ARCH/src/src/kernel/scripts/elf-pvh-note.c
-## — amd64-only post-link PT_NOTE fixup
##
## ─── Tools ────────────────────────────────────────────────────────────
## In container: scratch + busybox (boot2-empty:$ARCH).
@@ -77,17 +75,6 @@ runscm_input_from_src src "kernel/arch/$ARCH/arch.h"
runscm_input_from_src src "kernel/arch/$ARCH/mmu.c"
runscm_input_from_src src tcc-cc/mem.c
-# amd64 needs a post-link fixup — tcc3 doesn't emit PT_NOTE phdrs, so
-# QEMU's PVH `-kernel` path can't find the Xen 18 note that names the
-# 32-bit entry. The fixup is a hosted C tool we build inside the same
-# run.scm with tcc3 + boot4's libc/crt1/libtcc1, then run on kernel.elf.
-if [ "$ARCH" = amd64 ]; then
- runscm_input_from_src src kernel/scripts/elf-pvh-note.c
- runscm_input crt1.o "$BOOT4/crt1.o"
- runscm_input libc.a "$BOOT4/libc.a"
- runscm_input libtcc1.a "$BOOT4/libtcc1.a"
-fi
-
runscm_export "$OUT_FILE"
runscm_run "${BOOT6_TIMEOUT:-1200}"
diff --git a/scripts/prep-src.sh b/scripts/prep-src.sh
@@ -108,7 +108,7 @@ mkdir -p "$DST_SRC/test-fixtures"
cp scripts/boot-hello.c "$DST_SRC/test-fixtures/boot-hello.c"
# ── (3) seed-kernel sources for this arch ─────────────────────────────
-mkdir -p "$DST_SRC/kernel/arch/$ARCH" "$DST_SRC/kernel/user" "$DST_SRC/kernel/scripts"
+mkdir -p "$DST_SRC/kernel/arch/$ARCH" "$DST_SRC/kernel/user"
cp seed-kernel/kernel.c "$DST_SRC/kernel/kernel.c"
for f in seed-kernel/arch/$ARCH/*; do
[ -f "$f" ] || continue
@@ -118,9 +118,6 @@ for f in seed-kernel/user/*; do
[ -f "$f" ] || continue
cp "$f" "$DST_SRC/kernel/user/$(basename "$f")"
done
-# elf-pvh-note.c is consumed by boot6 on amd64; keep it in the canonical
-# tree on every arch so the layout is uniform.
-cp seed-kernel/scripts/elf-pvh-note.c "$DST_SRC/kernel/scripts/elf-pvh-note.c"
# ── (4) tcc flatten ───────────────────────────────────────────────────
# stage1-flatten.sh writes to build/<arch>/vendor/tcc/. Run it (it's
diff --git a/scripts/simple-patches/tcc-0.9.26/load-obj-accept-sht-note.after b/scripts/simple-patches/tcc-0.9.26/load-obj-accept-sht-note.after
@@ -0,0 +1,18 @@
+ /* ignore sections types we do not handle. AT.2: SHT_NOTE is
+ * accepted so .o files emitted by find_section()'s .note*
+ * branch are merged in. Without this, kernel.elf links skip
+ * .note.Xen and the subsequent .rela.note.Xen merge derefs
+ * a NULL sm_table[].s for the (skipped) note section. */
+ if (sh->sh_type != SHT_PROGBITS &&
+ sh->sh_type != SHT_RELX &&
+#ifdef TCC_ARM_EABI
+ sh->sh_type != SHT_ARM_EXIDX &&
+#endif
+ sh->sh_type != SHT_NOBITS &&
+ sh->sh_type != SHT_NOTE &&
+ sh->sh_type != SHT_PREINIT_ARRAY &&
+ sh->sh_type != SHT_INIT_ARRAY &&
+ sh->sh_type != SHT_FINI_ARRAY &&
+ strcmp(sh_name, ".stabstr")
+ )
+ continue;
diff --git a/scripts/simple-patches/tcc-0.9.26/load-obj-accept-sht-note.before b/scripts/simple-patches/tcc-0.9.26/load-obj-accept-sht-note.before
@@ -0,0 +1,13 @@
+ /* ignore sections types we do not handle */
+ if (sh->sh_type != SHT_PROGBITS &&
+ sh->sh_type != SHT_RELX &&
+#ifdef TCC_ARM_EABI
+ sh->sh_type != SHT_ARM_EXIDX &&
+#endif
+ sh->sh_type != SHT_NOBITS &&
+ sh->sh_type != SHT_PREINIT_ARRAY &&
+ sh->sh_type != SHT_INIT_ARRAY &&
+ sh->sh_type != SHT_FINI_ARRAY &&
+ strcmp(sh_name, ".stabstr")
+ )
+ continue;
diff --git a/scripts/simple-patches/tcc-0.9.26/note-section-sht-note.after b/scripts/simple-patches/tcc-0.9.26/note-section-sht-note.after
@@ -0,0 +1,6 @@
+ /* AT.2: .note* sections must have SHT_NOTE so a PT_NOTE phdr can
+ * point at them; PVH boot scans .note.Xen for the 18 entry. All
+ * other implicitly-created sections stay PROGBITS. */
+ if (!strncmp(name, ".note", 5))
+ return new_section(s1, name, SHT_NOTE, SHF_ALLOC);
+ return new_section(s1, name, SHT_PROGBITS, SHF_ALLOC);
diff --git a/scripts/simple-patches/tcc-0.9.26/note-section-sht-note.before b/scripts/simple-patches/tcc-0.9.26/note-section-sht-note.before
@@ -0,0 +1,2 @@
+ /* sections are created as PROGBITS */
+ return new_section(s1, name, SHT_PROGBITS, SHF_ALLOC);
diff --git a/scripts/simple-patches/tcc-0.9.26/pt-note-phdr.after b/scripts/simple-patches/tcc-0.9.26/pt-note-phdr.after
@@ -0,0 +1,94 @@
+ /* compute number of program headers */
+ switch(file_type) {
+ default:
+ case TCC_OUTPUT_OBJ:
+ phnum = 0;
+ break;
+ case TCC_OUTPUT_EXE:
+ if (!s1->static_link)
+ phnum = 4 + HAVE_PHDR;
+ else
+ phnum = 2;
+ break;
+ case TCC_OUTPUT_DLL:
+ phnum = 3;
+ break;
+ }
+
+ /* AT.2: bump phnum by 1 when an SHT_NOTE+SHF_ALLOC section is
+ * present, so the layout has room for one PT_NOTE phdr covering
+ * the .note* group. Gating on actual presence keeps the phnum
+ * unchanged for builds with no note sections (aarch64/riscv64
+ * stay byte-identical). */
+ if (phnum > 0) {
+ for (i = 1; i < s1->nb_sections; i++) {
+ Section *ns = s1->sections[i];
+ if (ns->sh_type == SHT_NOTE && (ns->sh_flags & SHF_ALLOC)) {
+ phnum++;
+ break;
+ }
+ }
+ }
+
+ /* Allocate strings for section names */
+ alloc_sec_names(s1, file_type, strsec);
+
+ /* allocate program segment headers */
+ phdr = tcc_mallocz(phnum * sizeof(ElfW(Phdr)));
+
+ /* compute section to program header mapping */
+ file_offset = layout_sections(s1, phdr, phnum, interp, strsec, &dyninf,
+ sec_order);
+
+ /* Fill remaining program header and finalize relocation related to dynamic
+ linking. */
+ if (phnum > 0) {
+ fill_unloadable_phdr(phdr, phnum, interp, dynamic);
+ /* AT.2: fill the PT_NOTE phdr in the leftover PT_NULL slot.
+ * layout_sections fills the PT_LOAD slots; fill_unloadable_phdr
+ * fills PT_PHDR/PT_INTERP/PT_DYNAMIC. The extra slot reserved by
+ * the bump above is left as PT_NULL by tcc_mallocz, so the first
+ * PT_NULL we find is ours. Range = [min sh_offset, max
+ * sh_offset+sh_size) over SHT_NOTE+SHF_ALLOC sections. */
+ {
+ addr_t note_off = 0, note_end = 0, note_addr = 0;
+ int note_found = 0;
+ for (i = 1; i < s1->nb_sections; i++) {
+ Section *ns = s1->sections[i];
+ if (ns->sh_type != SHT_NOTE || !(ns->sh_flags & SHF_ALLOC))
+ continue;
+ if (!note_found) {
+ note_off = ns->sh_offset;
+ note_end = ns->sh_offset + ns->sh_size;
+ note_addr = ns->sh_addr;
+ note_found = 1;
+ } else {
+ if (ns->sh_offset < note_off)
+ note_off = ns->sh_offset;
+ if (ns->sh_offset + ns->sh_size > note_end)
+ note_end = ns->sh_offset + ns->sh_size;
+ if (ns->sh_addr < note_addr)
+ note_addr = ns->sh_addr;
+ }
+ }
+ if (note_found) {
+ ElfW(Phdr) *nph = NULL;
+ int j;
+ for (j = 0; j < phnum; j++) {
+ if (phdr[j].p_type == PT_NULL) {
+ nph = &phdr[j];
+ break;
+ }
+ }
+ if (nph) {
+ nph->p_type = PT_NOTE;
+ nph->p_flags = PF_R;
+ nph->p_offset = note_off;
+ nph->p_vaddr = note_addr;
+ nph->p_paddr = note_addr;
+ nph->p_filesz = note_end - note_off;
+ nph->p_memsz = note_end - note_off;
+ nph->p_align = 4;
+ }
+ }
+ }
diff --git a/scripts/simple-patches/tcc-0.9.26/pt-note-phdr.before b/scripts/simple-patches/tcc-0.9.26/pt-note-phdr.before
@@ -0,0 +1,31 @@
+ /* compute number of program headers */
+ switch(file_type) {
+ default:
+ case TCC_OUTPUT_OBJ:
+ phnum = 0;
+ break;
+ case TCC_OUTPUT_EXE:
+ if (!s1->static_link)
+ phnum = 4 + HAVE_PHDR;
+ else
+ phnum = 2;
+ break;
+ case TCC_OUTPUT_DLL:
+ phnum = 3;
+ break;
+ }
+
+ /* Allocate strings for section names */
+ alloc_sec_names(s1, file_type, strsec);
+
+ /* allocate program segment headers */
+ phdr = tcc_mallocz(phnum * sizeof(ElfW(Phdr)));
+
+ /* compute section to program header mapping */
+ file_offset = layout_sections(s1, phdr, phnum, interp, strsec, &dyninf,
+ sec_order);
+
+ /* Fill remaining program header and finalize relocation related to dynamic
+ linking. */
+ if (phnum > 0) {
+ fill_unloadable_phdr(phdr, phnum, interp, dynamic);
diff --git a/scripts/stage1-flatten.sh b/scripts/stage1-flatten.sh
@@ -164,6 +164,21 @@ apply_our_patch bss-start-symbol "$SRC/tccelf.c"
# other arches: the block is gated `#ifdef TCC_TARGET_X86_64`.
apply_our_patch x86_64-static-plt32 "$SRC/tccelf.c"
+# AT.2: native PT_NOTE for PVH boot. Stock tcc tags `.note.*` sections
+# as SHT_PROGBITS and never emits a PT_NOTE phdr, so QEMU's PVH
+# `-kernel` path on amd64 (which scans PT_NOTE for the Xen 18 entry)
+# rejects the kernel. Three patches: (1) retype implicitly-created
+# `.note*` sections to SHT_NOTE; (2) allocate a PT_NOTE phdr covering
+# every SHT_NOTE+SHF_ALLOC section; (3) accept SHT_NOTE in the .o
+# loader so kernel-asm.o's .note.Xen merges into the link output (else
+# the subsequent .rela.note.Xen merge derefs sm_table[].s == NULL).
+# The phnum bump in (2) is gated on actual presence so aarch64/riscv64
+# (no .note sections) keep their existing phdr count and stay
+# byte-identical to pre-patch output.
+apply_our_patch note-section-sht-note "$SRC/tccelf.c"
+apply_our_patch pt-note-phdr "$SRC/tccelf.c"
+apply_our_patch load-obj-accept-sht-note "$SRC/tccelf.c"
+
# x86_64 va_list runtime: tcc's lib/va_list.c declares `extern void
# abort(void)` and calls it in an unreachable default branch of the
# arg-type switch. Under -nostdlib that abort() symbol is unresolved
diff --git a/seed-kernel/scripts/elf-pvh-note.c b/seed-kernel/scripts/elf-pvh-note.c
@@ -1,103 +0,0 @@
-/* elf-pvh-note — append a PT_NOTE program header pointing at the
- * existing `.note.Xen` section, and retype that section as SHT_NOTE.
- *
- * Why: tcc 0.9.26's linker emits only PT_LOAD program headers and
- * marks every assembler-created section as SHT_PROGBITS, so QEMU's
- * PVH `-kernel` path (which scans PT_NOTE phdrs for the Xen 18 note
- * to find the 32-bit entry) refuses the kernel with "Error loading
- * uncompressed kernel without PVH ELF Note". Patching the linker is
- * a much bigger change than rewriting six fields after the fact;
- * tcc reserves the post-Ehdr program-header area only large enough
- * for its declared phnum, but the next section content lives at
- * 0x200000 (s_align), so writing one extra Phdr at phoff+phnum*phentsize
- * fits in the gap.
- *
- * Usage: elf-pvh-note <elf-path>
- */
-
-extern int open(const char *, int, ...);
-extern long lseek(int, long, int);
-extern long read(int, void *, unsigned long);
-extern long write(int, const void *, unsigned long);
-extern int close(int);
-extern void *malloc(unsigned long);
-extern int strcmp(const char *, const char *);
-extern int printf(const char *, ...);
-
-#define O_RDWR 2
-#define SEEK_SET 0
-#define SEEK_END 2
-
-#define PT_NOTE 4
-#define PF_R 4
-#define SHT_NOTE 7
-
-typedef unsigned long u64;
-typedef unsigned int u32;
-typedef unsigned short u16;
-typedef unsigned char u8;
-
-static u16 r16(u8 *p) { return (u16)p[0] | ((u16)p[1] << 8); }
-static u32 r32(u8 *p) {
- return (u32)p[0] | ((u32)p[1] << 8) | ((u32)p[2] << 16) | ((u32)p[3] << 24);
-}
-static u64 r64(u8 *p) {
- return (u64)r32(p) | ((u64)r32(p + 4) << 32);
-}
-static void w16(u8 *p, u16 v) { p[0] = v; p[1] = v >> 8; }
-static void w32(u8 *p, u32 v) {
- p[0] = v; p[1] = v >> 8; p[2] = v >> 16; p[3] = v >> 24;
-}
-static void w64(u8 *p, u64 v) { w32(p, (u32)v); w32(p + 4, (u32)(v >> 32)); }
-
-int main(int argc, char **argv) {
- if (argc != 2) { printf("usage: elf-pvh-note <elf>\n"); return 2; }
- int fd = open(argv[1], O_RDWR);
- if (fd < 0) { printf("elf-pvh-note: open failed\n"); return 1; }
- long size = lseek(fd, 0, SEEK_END);
- lseek(fd, 0, SEEK_SET);
- u8 *buf = (u8 *)malloc(size);
- if (read(fd, buf, size) != size) { printf("elf-pvh-note: short read\n"); return 1; }
-
- u64 phoff = r64(buf + 0x20);
- u16 phentsize = r16(buf + 0x36);
- u16 phnum = r16(buf + 0x38);
- u64 shoff = r64(buf + 0x28);
- u16 shentsize = r16(buf + 0x3a);
- u16 shnum = r16(buf + 0x3c);
- u16 shstrndx = r16(buf + 0x3e);
-
- u64 shstrtab_off = r64(buf + shoff + shstrndx * shentsize + 0x18);
-
- u64 note_off = 0, note_size = 0, note_addr = 0;
- int found = 0;
- for (int i = 0; i < shnum; i++) {
- u8 *sh = buf + shoff + i * shentsize;
- u32 name_off = r32(sh);
- if (strcmp((char *)(buf + shstrtab_off + name_off), ".note.Xen") == 0) {
- note_addr = r64(sh + 0x10);
- note_off = r64(sh + 0x18);
- note_size = r64(sh + 0x20);
- w32(sh + 4, SHT_NOTE);
- found = 1;
- break;
- }
- }
- if (!found) { printf("elf-pvh-note: no .note.Xen section\n"); return 1; }
-
- u8 *ph = buf + phoff + phnum * phentsize;
- w32(ph + 0, PT_NOTE);
- w32(ph + 4, PF_R);
- w64(ph + 8, note_off);
- w64(ph + 16, note_addr);
- w64(ph + 24, note_addr);
- w64(ph + 32, note_size);
- w64(ph + 40, note_size);
- w64(ph + 48, 4);
- w16(buf + 0x38, phnum + 1);
-
- lseek(fd, 0, SEEK_SET);
- if (write(fd, buf, size) != size) { printf("elf-pvh-note: short write\n"); return 1; }
- close(fd);
- return 0;
-}