commit 296361e67f879675a2e8f178cf6f9850a1a3e5ce
parent a8052633f6775e1cba26a4b7a6db19f237e98c22
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Thu, 4 Jun 2026 10:49:40 -0700
test: add freestanding qemu-system smoke
Diffstat:
5 files changed, 485 insertions(+), 30 deletions(-)
diff --git a/mk/test.mk b/mk/test.mk
@@ -894,6 +894,14 @@ test-smoke-rv64:
test-smoke-rv32:
bash test/smoke/rv32.sh
+# test-smoke-freestanding-system: whole-toolchain bare-metal smoke for
+# aarch64/x86_64/riscv64/riscv32 under qemu-system. Each lane compiles with kit,
+# assembles its reset stub with kit, links with kit ld -T, then boots the image
+# under the matching qemu-system binary. rv32 may need compiler-rt-style helpers
+# for 64-bit operations, so build that freestanding runtime variant with kit too.
+test-smoke-freestanding-system: bin rt-riscv32-elf-hardfloat
+ bash test/smoke/freestanding_system.sh
+
# test-parse-rv64-wide: end-to-end coverage of the rv64 128-bit scalar types
# — __int128 (i128_*) and IEEE-754 binary128 long double (ldbl128_*) — built
# with kit and run on riscv64. Exercises the soft-float / i128 lowering to
diff --git a/src/asm/asm.c b/src/asm/asm.c
@@ -142,22 +142,25 @@ static int starts_with(AsmDriver* d, Sym s, const char* prefix) {
/* ---- section management ---- */
-static ObjSecId ensure_section(AsmDriver* d, Sym name, SecKind kind, u16 flags,
- u32 align) {
+static ObjSecId ensure_section_ex(AsmDriver* d, Sym name, SecKind kind, u16 sem,
+ u16 flags, u32 align) {
ObjSecId* hit = SymSecMap_get(&d->sec_map, name);
ObjSecId id;
if (hit) return *hit;
+ id = obj_section_ex(d->ob, name, kind, sem, flags, align, 0, OBJ_SEC_NONE, 0);
+ SymSecMap_set(&d->sec_map, name, id);
+ return id;
+}
+
+static ObjSecId ensure_section(AsmDriver* d, Sym name, SecKind kind, u16 flags,
+ u32 align) {
/* A .bss section is NOBITS: it stores no bytes, only a size. Create it that
* way (codegen does the same via obj_section_ex) so the ELF emitter writes
* SHT_NOBITS and `.zero`/labels track bss_size, not a byte buffer — matching
* `cc -c` so the round-tripped object isn't a writable-but-loaded .bss. */
- if (kind == SEC_BSS)
- id = obj_section_ex(d->ob, name, kind, SSEM_NOBITS, flags, align, 0,
- OBJ_SEC_NONE, 0);
- else
- id = obj_section(d->ob, name, kind, flags, align);
- SymSecMap_set(&d->sec_map, name, id);
- return id;
+ return ensure_section_ex(d, name, kind,
+ kind == SEC_BSS ? SSEM_NOBITS : SSEM_PROGBITS, flags,
+ align);
}
static void set_section(AsmDriver* d, Sym name, SecKind kind, u16 flags,
@@ -746,6 +749,7 @@ static void do_directive(AsmDriver* d, Sym name) {
d_panicf(d, "asm: .section: expected name");
}
SecKind kind = SEC_OTHER;
+ u16 sem = SSEM_PROGBITS;
u16 flags = 0;
u32 entsize = 0;
int have_flags = 0;
@@ -834,11 +838,26 @@ static void do_directive(AsmDriver* d, Sym name) {
(void)d_next(d);
if (asm_driver_eat_comma(d)) {
AsmTok ty = d_peek(d);
+ Sym tag = 0;
if (tok_is_punct(ty, '@')) {
(void)d_next(d);
- (void)d_next(d); /* the @type ident (progbits/nobits) */
+ AsmTok ti = d_next(d); /* the @type ident (progbits/nobits/note) */
+ if (ti.kind == ASM_TOK_IDENT) tag = ti.v.ident;
} else if (ty.kind == ASM_TOK_IDENT) {
- (void)d_next(d);
+ tag = d_next(d).v.ident;
+ }
+ if (tag) {
+ if (sym_eq(d, tag, "note")) {
+ sem = SSEM_NOTE;
+ } else if (sym_eq(d, tag, "nobits")) {
+ sem = SSEM_NOBITS;
+ } else if (sym_eq(d, tag, "init_array")) {
+ sem = SSEM_INIT_ARRAY;
+ } else if (sym_eq(d, tag, "fini_array")) {
+ sem = SSEM_FINI_ARRAY;
+ } else if (sym_eq(d, tag, "preinit_array")) {
+ sem = SSEM_PREINIT_ARRAY;
+ }
}
if (asm_driver_eat_comma(d)) {
AsmTok es = d_peek(d);
@@ -911,11 +930,13 @@ static void do_directive(AsmDriver* d, Sym name) {
}
}
}
+ if (kind == SEC_BSS) sem = SSEM_NOBITS;
+ if (sem == SSEM_NOTE) kind = SEC_OTHER;
/* Consume any remaining operands (e.g. ,unique,N or group fields). */
d_skip_to_eol(d);
{
- ObjSecId sid = ensure_section(d, sname, kind, flags, 1);
+ ObjSecId sid = ensure_section_ex(d, sname, kind, sem, flags, 1);
if (entsize) obj_section_set_entsize(d->ob, sid, entsize);
d->cur_sec = sid;
d->mc->set_section(d->mc, sid);
diff --git a/src/link/link_layout.c b/src/link/link_layout.c
@@ -46,7 +46,8 @@ int link_section_kept(const Section* s) {
* symtab/strtab, group, and note sections are dropped — none of
* them participate in a static ET_EXEC layout. */
if (!(s->flags & SF_ALLOC)) return 0;
- if (s->sem == SSEM_PROGBITS || s->sem == SSEM_NOBITS) return 1;
+ if (s->sem == SSEM_PROGBITS || s->sem == SSEM_NOBITS || s->sem == SSEM_NOTE)
+ return 1;
if (s->sem == SSEM_INIT_ARRAY || s->sem == SSEM_FINI_ARRAY) return 1;
return 0;
}
diff --git a/src/obj/elf/link.c b/src/obj/elf/link.c
@@ -839,7 +839,8 @@ void link_emit_elf(LinkImage* img, Writer* w) {
int pie = img->pie;
int scripted = img->scripted;
/* Static ET_EXEC base: a `kit ld -Ttext ADDR` override (e.g. 0x80000000 for a
- * qemu `virt` image) wins over IMAGE_BASE_STATIC; PIE/scripted keep base 0. */
+ * qemu `virt` image) wins over IMAGE_BASE_STATIC; PIE/scripted keep base 0.
+ */
u64 img_base = (pie || scripted) ? 0ULL
: img->text_base_set ? img->text_base
: IMAGE_BASE_STATIC;
@@ -853,11 +854,21 @@ void link_emit_elf(LinkImage* img, Writer* w) {
* Scripted images skip the headers PT_LOAD and PT_NOTE: phdrs are
* just the per-segment PT_LOADs. */
u32 has_tls = img->tls_memsz ? 1u : 0u;
+ u32 nphdr_section_notes = 0;
+ {
+ u32 i;
+ for (i = 0; i < img->nsections; ++i) {
+ const LinkSection* s = &img->sections[i];
+ if (!s->file_only && s->sem == SSEM_NOTE && s->size) {
+ nphdr_section_notes++;
+ }
+ }
+ }
u32 nphdr_extra_dyn = pie ? 4u : 0u;
u32 nphdr_headers = scripted ? 0u : 1u;
u32 nphdr_buildid = scripted ? 0u : 1u;
- u32 nphdr_total = nphdr_headers + img->nsegments + nphdr_buildid + has_tls +
- nphdr_extra_dyn;
+ u32 nphdr_total = nphdr_headers + img->nsegments + nphdr_buildid +
+ nphdr_section_notes + has_tls + nphdr_extra_dyn;
u64 build_id_note_bytes = scripted ? 0ULL : BUILD_ID_NOTE_BYTES;
/* Class-selected on-disk header sizes (ELF32: 52/32/40, ELF64: 64/56/64). */
u64 ehdr_sz = elf_ehdr_sz(class32);
@@ -1057,7 +1068,8 @@ void link_emit_elf(LinkImage* img, Writer* w) {
u32 outshdr_cap = img->nsections + 1u;
outshdrs = (OutShdr*)heap->alloc(heap, sizeof(*outshdrs) * outshdr_cap,
_Alignof(OutShdr));
- if (!outshdrs) compiler_panic(c, SRCLOC_NONE, "link_emit_elf: oom on outshdrs");
+ if (!outshdrs)
+ compiler_panic(c, SRCLOC_NONE, "link_emit_elf: oom on outshdrs");
memset(outshdrs, 0, sizeof(*outshdrs) * outshdr_cap);
{
/* Build a sort index over LinkSection ids by (segment_id, vaddr). */
@@ -1116,7 +1128,8 @@ void link_emit_elf(LinkImage* img, Writer* w) {
u32 sh_name_symtab = strb_add_cstr(&shstrtab, ".symtab");
u32 sh_name_strtab = strb_add_cstr(&shstrtab, ".strtab");
u32 sh_name_shstrtab = strb_add_cstr(&shstrtab, ".shstrtab");
- u32 sh_name_buildid = strb_add_cstr(&shstrtab, ".note.gnu.build-id");
+ u32 sh_name_buildid =
+ scripted ? 0u : strb_add_cstr(&shstrtab, ".note.gnu.build-id");
/* Per-output-shdr names — interned strings from input section names. */
u32* outshdr_name_off =
(u32*)heap->alloc(heap, sizeof(u32) * (noutshdr + 1u), _Alignof(u32));
@@ -1138,9 +1151,9 @@ void link_emit_elf(LinkImage* img, Writer* w) {
}
}
- u32 nshdr = 1u + noutshdr + 4u;
- u32 shndx_buildid = 1u + noutshdr;
- u32 shndx_symtab = shndx_buildid + 1u;
+ u32 nbuildid_shdr = scripted ? 0u : 1u;
+ u32 nshdr = 1u + noutshdr + nbuildid_shdr + 3u;
+ u32 shndx_symtab = 1u + noutshdr + nbuildid_shdr;
u32 shndx_strtab = shndx_symtab + 1u;
u32 shndx_shstrtab = shndx_strtab + 1u;
@@ -1317,6 +1330,23 @@ void link_emit_elf(LinkImage* img, Writer* w) {
p->p_memsz = (seg->flags & SF_TLS) ? seg->file_size : seg->mem_size;
p->p_align = seg->align ? seg->align : PAGE_SIZE;
}
+ /* Allocatable SHT_NOTE sections can also be addressed by PT_NOTE. QEMU's
+ * x86 PVH loader uses this for XEN_ELFNOTE_PHYS32_ENTRY while the bytes
+ * still live in the normal read-only PT_LOAD. */
+ for (i = 0; i < img->nsections; ++i) {
+ const LinkSection* s = &img->sections[i];
+ Phdr64* p;
+ if (s->file_only || s->sem != SSEM_NOTE || s->size == 0) continue;
+ p = &phdrs[pi++];
+ p->p_type = PT_NOTE;
+ p->p_flags = PF_R;
+ p->p_offset = s->file_offset;
+ p->p_vaddr = img_base + s->vaddr;
+ p->p_paddr = p->p_vaddr;
+ p->p_filesz = s->size;
+ p->p_memsz = s->size;
+ p->p_align = s->align ? s->align : 4;
+ }
/* PT_NOTE for build-id. Scripted images skip the build-id entirely. */
if (!scripted) {
phdrs[pi].p_type = PT_NOTE;
@@ -1632,15 +1662,17 @@ void link_emit_elf(LinkImage* img, Writer* w) {
write_shdr(w, &sh, class32);
}
/* shdr: .note.gnu.build-id (allocatable; in headers PT_LOAD) */
- memset(&sh, 0, sizeof(sh));
- sh.sh_name = sh_name_buildid;
- sh.sh_type = SHT_NOTE;
- sh.sh_flags = SHF_ALLOC;
- sh.sh_addr = build_id_addr;
- sh.sh_offset = build_id_off;
- sh.sh_size = BUILD_ID_NOTE_BYTES;
- sh.sh_addralign = 4;
- write_shdr(w, &sh, class32);
+ if (!scripted) {
+ memset(&sh, 0, sizeof(sh));
+ sh.sh_name = sh_name_buildid;
+ sh.sh_type = SHT_NOTE;
+ sh.sh_flags = SHF_ALLOC;
+ sh.sh_addr = build_id_addr;
+ sh.sh_offset = build_id_off;
+ sh.sh_size = BUILD_ID_NOTE_BYTES;
+ sh.sh_addralign = 4;
+ write_shdr(w, &sh, class32);
+ }
/* shdr: .symtab */
memset(&sh, 0, sizeof(sh));
sh.sh_name = sh_name_symtab;
diff --git a/test/smoke/freestanding_system.sh b/test/smoke/freestanding_system.sh
@@ -0,0 +1,393 @@
+#!/usr/bin/env bash
+# test/smoke/freestanding_system.sh - qemu-system bare-metal smoke for
+# kit-compiled aarch64/x86_64/riscv64/riscv32 freestanding ELF images.
+#
+# Each lane builds the same small C payload with `kit cc -target *-none-elf`,
+# assembles an arch-specific reset stub with `kit as`, links a static image with
+# `kit ld -T`, then boots it under the matching qemu-system binary. This is a
+# whole-toolchain smoke: no clang, ld.lld, hosted libc, or qemu-user path is in
+# the image build.
+
+set -u
+
+ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
+BUILD_DIR="$ROOT/build/test/freestanding-system"
+mkdir -p "$BUILD_DIR"
+
+KIT_KIT_DIR="$ROOT/test/lib"
+# shellcheck source=../lib/kit_sh_kit.sh
+. "$ROOT/test/lib/kit_sh_kit.sh"
+kit_report_init
+KIT_SKIP_IS_FAILURE=1
+
+KIT="${KIT:-$ROOT/build/kit}"
+TIMEOUT="${TIMEOUT:-timeout}"
+if ! command -v "$TIMEOUT" >/dev/null 2>&1; then
+ if command -v gtimeout >/dev/null 2>&1; then
+ TIMEOUT="gtimeout"
+ fi
+fi
+
+if [ ! -x "$KIT" ]; then
+ skip_test "kit" "kit binary not found at $KIT"
+ kit_summary test-smoke-freestanding-system
+ kit_exit
+fi
+
+if ! command -v "$TIMEOUT" >/dev/null 2>&1; then
+ skip_test "timeout" "timeout command unavailable"
+ kit_summary test-smoke-freestanding-system
+ kit_exit
+fi
+
+cat > "$BUILD_DIR/app.c" <<'EOF'
+static int calc(void) {
+ volatile unsigned long long a = 0x1122334455667788ull;
+ volatile unsigned long long b = 0x0102030405060708ull;
+ unsigned long long c = (a ^ b) + 0x10ull;
+ if (c != ((0x1122334455667788ull ^ 0x0102030405060708ull) + 0x10ull))
+ return 2;
+
+ int sum = 0;
+ for (int i = 1; i <= 10; ++i) sum += i;
+ if (sum != 55) return 3;
+
+ volatile long long sx = -7;
+ if ((sx >> 1) != -4) return 4;
+
+ unsigned x = 0x12345678u;
+ x = (x << 3) | (x >> 29);
+ if (x != 0x91a2b3c0u) return 5;
+
+ return 0;
+}
+
+int cmain(void) { return calc(); }
+EOF
+
+have_qemu() {
+ command -v "$1" >/dev/null 2>&1
+}
+
+compile_obj() { # <arch-label> <target> <extra...>
+ local label="$1" target="$2"
+ shift 2
+ local dir="$BUILD_DIR/$label"
+ mkdir -p "$dir"
+ "$KIT" cc -target "$target" "$@" -O1 -ffreestanding -c "$BUILD_DIR/app.c" \
+ -o "$dir/app.o" 2>"$dir/cc.err"
+}
+
+assemble_obj() { # <label> <target> <src> <extra...>
+ local label="$1" target="$2" src="$3"
+ shift 3
+ "$KIT" as -target "$target" "$@" -o "$BUILD_DIR/$label/start.o" "$src" \
+ 2>"$BUILD_DIR/$label/as.err"
+}
+
+link_elf() { # <label>
+ local label="$1" dir="$BUILD_DIR/$1"
+ "$KIT" ld -T "$dir/link.ld" -e _start "$dir/start.o" "$dir/app.o" \
+ -o "$dir/kernel.elf" 2>"$dir/ld.err"
+}
+
+record_build_fail() { # <name> <log>
+ local name="$1" log="$2"
+ if [ -s "$log" ]; then
+ not_ok "$name" "$log"
+ else
+ not_ok "$name" "command failed with no stderr"
+ fi
+}
+
+run_aa64() {
+ local label="aa64" dir="$BUILD_DIR/aa64"
+ if ! have_qemu qemu-system-aarch64; then
+ skip_test "$label" "qemu-system-aarch64 unavailable"
+ return
+ fi
+ mkdir -p "$dir"
+ cat > "$dir/start.S" <<'EOF'
+.section .text.start,"ax",@progbits
+.globl _start
+_start:
+ adrp x0, stack_top
+ add x0, x0, :lo12:stack_top
+ mov sp, x0
+ bl cmain
+ adrp x1, semihost_args
+ add x1, x1, :lo12:semihost_args
+ str x0, [x1, #8]
+ mov x0, #0x20
+ hlt #0xf000
+.Lhang:
+ b .Lhang
+
+.section .data.semihost,"aw",@progbits
+.balign 8
+semihost_args:
+ .quad 0x20026
+ .quad 0
+
+.section .bss.stack,"aw",@nobits
+.balign 16
+stack_bottom:
+ .zero 65536
+stack_top:
+EOF
+ cat > "$dir/link.ld" <<'EOF'
+ENTRY(_start)
+SECTIONS {
+ . = 0x40080000;
+ .text : { *(.text.start) *(.text*) }
+ .rodata : { *(.rodata*) }
+ .data : { *(.data*) }
+ .bss : { *(.bss*) *(COMMON) }
+ /DISCARD/ : { *(.comment) }
+}
+EOF
+ if ! compile_obj "$label" aarch64-none-elf; then
+ record_build_fail "$label cc" "$dir/cc.err"; return
+ fi
+ if ! assemble_obj "$label" aarch64-none-elf "$dir/start.S"; then
+ record_build_fail "$label as" "$dir/as.err"; return
+ fi
+ if ! link_elf "$label"; then
+ record_build_fail "$label ld" "$dir/ld.err"; return
+ fi
+
+ local rc=0
+ "$TIMEOUT" 20 qemu-system-aarch64 -machine virt -cpu cortex-a53 \
+ -kernel "$dir/kernel.elf" -display none -serial none -monitor none \
+ -semihosting-config enable=on,target=native -no-reboot \
+ >"$dir/qemu.out" 2>"$dir/qemu.err" || rc=$?
+ if [ "$rc" -eq 0 ]; then
+ ok "$label qemu-system (rc=0)"
+ else
+ printf 'expected qemu rc 0, got %s; see %s\n' "$rc" "$dir/qemu.err" \
+ > "$dir/qemu.diag"
+ not_ok "$label qemu-system" "$dir/qemu.diag"
+ fi
+}
+
+run_riscv() { # <label> <qemu> <target> <march> <mabi> <stack>
+ local label="$1" qemu="$2" target="$3" march="$4" mabi="$5" stack="$6"
+ local dir="$BUILD_DIR/$label"
+ if ! have_qemu "$qemu"; then
+ skip_test "$label" "$qemu unavailable"
+ return
+ fi
+ mkdir -p "$dir"
+ cat > "$dir/start.S" <<EOF
+.section .text.start,"ax",@progbits
+.globl _start
+_start:
+ li sp, $stack
+ li t0, 0x2000
+ csrs mstatus, t0
+ call cmain
+ li t0, 0x100000
+ beqz a0, .Lpass
+ slli a0, a0, 16
+ li t1, 0x3333
+ or a0, a0, t1
+ sw a0, 0(t0)
+.Lhang:
+ j .Lhang
+.Lpass:
+ li t1, 0x5555
+ sw t1, 0(t0)
+ j .Lhang
+EOF
+ cat > "$dir/link.ld" <<'EOF'
+ENTRY(_start)
+SECTIONS {
+ . = 0x80000000;
+ .text : { *(.text.start) *(.text*) }
+ .rodata : { *(.rodata*) }
+ .data : { *(.data*) }
+ .bss : { *(.bss*) *(COMMON) }
+ /DISCARD/ : { *(.riscv.attributes) *(.comment) }
+}
+EOF
+ if ! compile_obj "$label" "$target" -march="$march" -mabi="$mabi"; then
+ record_build_fail "$label cc" "$dir/cc.err"; return
+ fi
+ if ! assemble_obj "$label" "$target" "$dir/start.S" -march="$march" -mabi="$mabi"; then
+ record_build_fail "$label as" "$dir/as.err"; return
+ fi
+ if ! link_elf "$label"; then
+ record_build_fail "$label ld" "$dir/ld.err"; return
+ fi
+
+ local rc=0
+ "$TIMEOUT" 20 "$qemu" -machine virt -bios none \
+ -kernel "$dir/kernel.elf" -nographic -no-reboot \
+ >"$dir/qemu.out" 2>"$dir/qemu.err" || rc=$?
+ if [ "$rc" -eq 0 ]; then
+ ok "$label qemu-system (rc=0)"
+ else
+ printf 'expected qemu rc 0, got %s; see %s\n' "$rc" "$dir/qemu.err" \
+ > "$dir/qemu.diag"
+ not_ok "$label qemu-system" "$dir/qemu.diag"
+ fi
+}
+
+run_rv64() {
+ run_riscv rv64 qemu-system-riscv64 riscv64-none-elf \
+ rv64imafd_zicsr_zifencei lp64 0x81000000
+}
+
+run_rv32() {
+ run_riscv rv32 qemu-system-riscv32 riscv32-none-elf \
+ rv32imafc_zicsr_zifencei ilp32f 0x80100000
+}
+
+emit_x64_start() {
+ local out="$1"
+ cat > "$out" <<'EOF'
+.section .multiboot,"a",@progbits
+.balign 4
+.long 0x1badb002
+.long 0
+.long 0xe4524ffe
+
+.section .note.Xen,"a",@note
+.balign 4
+.long 4
+.long 4
+.long 18
+.ascii "Xen"
+.byte 0
+.long _start
+
+.section .text.start,"ax",@progbits
+.globl _start
+_start:
+ .byte 0xfa /* cli */
+ .byte 0xbc /* mov $stack_top, %esp */
+ .long stack_top
+ .byte 0xb8 /* mov $pml4, %eax */
+ .long pml4
+ .byte 0x0f, 0x22, 0xd8 /* mov %eax, %cr3 */
+ .byte 0x0f, 0x20, 0xe0 /* mov %cr4, %eax */
+ .byte 0x83, 0xc8, 0x20 /* or $CR4_PAE, %eax */
+ .byte 0x0f, 0x22, 0xe0 /* mov %eax, %cr4 */
+ .byte 0xb9, 0x80, 0x00, 0x00, 0xc0 /* mov $EFER, %ecx */
+ .byte 0x0f, 0x32 /* rdmsr */
+ .byte 0x0d, 0x00, 0x01, 0x00, 0x00 /* or $EFER_LME, %eax */
+ .byte 0x0f, 0x30 /* wrmsr */
+ .byte 0x0f, 0x20, 0xc0 /* mov %cr0, %eax */
+ .byte 0x0d, 0x00, 0x00, 0x00, 0x80 /* or $CR0_PG, %eax */
+ .byte 0x0f, 0x22, 0xc0 /* mov %eax, %cr0 */
+ .byte 0x0f, 0x01, 0x15 /* lgdt gdt_desc */
+ .long gdt_desc
+ .byte 0xea /* ljmp $0x08,$long_entry */
+ .long long_entry
+ .hword 0x08
+
+long_entry:
+ .byte 0x66, 0xb8, 0x10, 0x00 /* mov $0x10, %ax */
+ .byte 0x8e, 0xd8 /* mov %ax, %ds */
+ .byte 0x8e, 0xc0 /* mov %ax, %es */
+ .byte 0x8e, 0xd0 /* mov %ax, %ss */
+ .byte 0x48, 0xbc /* movabs $stack_top, %rsp */
+ .quad stack_top
+ call cmain
+ .byte 0x66, 0xba, 0x01, 0x05 /* mov $0x501, %dx */
+ .byte 0x66, 0xef /* outw %ax, %dx */
+.Lhang:
+ .byte 0xf4 /* hlt */
+ jmp .Lhang
+
+.section .data.boot,"aw",@progbits
+.balign 8
+gdt:
+ .quad 0
+ .quad 0x00af9a000000ffff
+ .quad 0x00af92000000ffff
+gdt_desc:
+ .hword 23
+ .long gdt
+
+.balign 4096
+pml4:
+ .quad pdpt + 0x3
+EOF
+ for _ in $(seq 1 511); do printf ' .quad 0\n' >> "$out"; done
+ cat >> "$out" <<'EOF'
+.balign 4096
+pdpt:
+ .quad pd + 0x3
+EOF
+ for _ in $(seq 1 511); do printf ' .quad 0\n' >> "$out"; done
+ printf '.balign 4096\npd:\n' >> "$out"
+ local i=0
+ while [ "$i" -lt 512 ]; do
+ printf ' .quad 0x%016x\n' $((i * 0x200000 + 0x83)) >> "$out"
+ i=$((i + 1))
+ done
+ cat >> "$out" <<'EOF'
+
+.section .bss.stack,"aw",@nobits
+.balign 16
+stack_bottom:
+ .zero 65536
+stack_top:
+EOF
+}
+
+run_x64() {
+ local label="x64" dir="$BUILD_DIR/x64"
+ if ! have_qemu qemu-system-x86_64; then
+ skip_test "$label" "qemu-system-x86_64 unavailable"
+ return
+ fi
+ mkdir -p "$dir"
+ emit_x64_start "$dir/start.S"
+ cat > "$dir/link.ld" <<'EOF'
+ENTRY(_start)
+SECTIONS {
+ . = 0x100000;
+ .multiboot : { *(.multiboot) }
+ .note.Xen : { *(.note.Xen) }
+ .text : { *(.text.start) *(.text*) }
+ .rodata : { *(.rodata*) }
+ .data.boot : ALIGN(4096) { *(.data.boot) }
+ .data : { *(.data*) }
+ .bss.stack : ALIGN(16) { *(.bss.stack) }
+ .bss : { *(.bss*) *(COMMON) }
+ /DISCARD/ : { *(.comment) }
+}
+EOF
+ if ! compile_obj "$label" x86_64-none-elf; then
+ record_build_fail "$label cc" "$dir/cc.err"; return
+ fi
+ if ! assemble_obj "$label" x86_64-none-elf "$dir/start.S"; then
+ record_build_fail "$label as" "$dir/as.err"; return
+ fi
+ if ! link_elf "$label"; then
+ record_build_fail "$label ld" "$dir/ld.err"; return
+ fi
+
+ local rc=0 want=1
+ "$TIMEOUT" 20 qemu-system-x86_64 -kernel "$dir/kernel.elf" \
+ -device isa-debug-exit,iobase=0x501,iosize=0x02 \
+ -display none -serial none -monitor none -no-reboot \
+ >"$dir/qemu.out" 2>"$dir/qemu.err" || rc=$?
+ if [ "$rc" -eq "$want" ] && ! grep -q 'Error loading' "$dir/qemu.err"; then
+ ok "$label qemu-system (guest rc=0)"
+ else
+ printf 'expected qemu rc %s (guest rc=0), got %s; see %s\n' \
+ "$want" "$rc" "$dir/qemu.err" > "$dir/qemu.diag"
+ not_ok "$label qemu-system" "$dir/qemu.diag"
+ fi
+}
+
+run_aa64
+run_x64
+run_rv64
+run_rv32
+
+kit_summary test-smoke-freestanding-system
+kit_exit