From bd27989a03ed2bc2bd8899f61e6df64b04367029 Mon Sep 17 00:00:00 2001 From: Luxferre Date: Fri, 19 Jun 2026 07:45:35 +0300 Subject: [PATCH] added elf loader --- Makefile | 15 +++++++++++---- README.md | 18 ++++++++++++++++-- amach.awk | 52 +++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 78 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 15f2cd6..56e043a 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ EMULATOR = ./amach ASFLAGS = -march=rv32imac -mabi=ilp32 -nostdlib -s -static -fno-pic -fno-pie -mno-relax LDFLAGS = -Wl,-Ttext=0x100b0 -Wl,--build-id=none -all: rv32imac_tests.bin rv32imac_syscall_tests.bin +all: rv32imac_tests.bin rv32imac_syscall_tests.bin rv32imac_tests.elf rv32imac_syscall_tests.elf rv32imac_tests.elf: rv32imac_tests.s $(CC) $(ASFLAGS) $(LDFLAGS) rv32imac_tests.s -o rv32imac_tests.elf @@ -19,12 +19,19 @@ rv32imac_syscall_tests.elf: rv32imac_syscall_tests.s rv32imac_syscall_tests.bin: rv32imac_syscall_tests.elf $(OBJCOPY) -O binary rv32imac_syscall_tests.elf rv32imac_syscall_tests.bin -run: rv32imac_tests.bin rv32imac_syscall_tests.bin - @echo "Running CPU instruction tests..." +run: rv32imac_tests.bin rv32imac_syscall_tests.bin rv32imac_tests.elf rv32imac_syscall_tests.elf + @echo "Running CPU instruction tests (binary)..." $(EMULATOR) rv32imac_tests.bin - @echo "Running syscall tests..." + @echo "Running CPU instruction tests (ELF)..." + $(EMULATOR) rv32imac_tests.elf + @echo "Running syscall tests (binary)..." + rm -rf test_dir test_link.txt test_output.txt test_output_new.txt ln -sf test_output_new.txt test_link.txt $(EMULATOR) rv32imac_syscall_tests.bin + @echo "Running syscall tests (ELF)..." + rm -rf test_dir test_link.txt test_output.txt test_output_new.txt + ln -sf test_output_new.txt test_link.txt + $(EMULATOR) rv32imac_syscall_tests.elf @echo "Testing standard ebreak..." @echo "115 0 16 0" | awk -f amach.awk 2>&1 | grep -q "Fatal: EBREAK" @echo "Testing compressed c.ebreak..." diff --git a/README.md b/README.md index a323cb5..cc9bcb7 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ A-machine is a simple and straightforward implementation of the RV32I specification with M (multiplication/division), A (atomic operations), and C (compressed instructions) extensions. It consists of two files: -- [amach.awk](amach.awk), the emulator core itself that supports .dec files, -- and [amach](amach) shell script which is a two-liner that uses POSIX `od` to convert .bin files into .dec files on the fly and then passes them to the emulator core. +- [amach.awk](amach.awk), the emulator core itself that supports both `.dec` files and RV32-compatible ELF executables, +- and [amach](amach) shell script which is a wrapper that uses POSIX `od` to convert `.bin` or `.elf` files into `.dec` formats on the fly and then passes them to the emulator core. Additionally, A-machine is shipped with a reference test suite that assumes that you have a full RISC-V toolchain installed under the `riscv64-linux-gnu-` prefix (adjustable in the `Makefile`). Just run `make run` to build and run all tests. @@ -13,6 +13,8 @@ The project is to be considered highly experimental and not production-ready in ## Usage +### Raw Binary files (`.bin`) + First, decide where your program needs to be loaded in memory. The default LVA (loading virtual address) is 0x100b0. In most AWK implementations, you need to convert this value to decimal (the default is 65712). Then, most probably, you'll want to use the provided wrapper: @@ -30,6 +32,18 @@ But in case you already have a program in the `.dec` format, you can pass it dir POSIXLY_CORRECT=1 awk -f amach.awk [-v LVA=...] -- path/to/program.dec ``` +### ELF Executables + +`amach.awk` now also features an ELF file loader that auto-detects RV32-compatible ELF headers (32-bit, little-endian, RISC-V). If detected, it: +* Maps the loadable segments (`PT_LOAD`) to their target virtual addresses in memory. +* Zero-initializes any remaining space in those segments (e.g., `.bss`). +* Overrides `LVA` to the entry point (`e_entry`) specified inside the ELF file automatically, meaning no manual `-v LVA=...` parameter is needed. + +You can launch ELF files directly using the wrapper or via the same pipe pipeline: +```sh +./amach path/to/program.elf +``` + ## Support Currently, A-machine only supports the **unprivileged** RV32IMAC instruction set with an extended subset of POSIX/Linux-compatible `ecall` system calls (using standard `asm-generic` numbers): diff --git a/amach.awk b/amach.awk index bc4087b..ab3443b 100644 --- a/amach.awk +++ b/amach.awk @@ -723,7 +723,7 @@ BEGIN { LVA = int(LVA) pc = LVA NEXT_FD = 3 - LAST_SEC = -1 + LAST_SEC = -1 VIRT_NSEC = 0 srand() } @@ -733,6 +733,56 @@ BEGIN { # Execution section END { + # ELF detection and parsing + # The file has been loaded into MEM starting from original LVA + orig_LVA = LVA + elf_size = pc - orig_LVA + # Verify if it starts with ELF magic (0x7F, 'E', 'L', 'F') and is large enough to contain a header + if(elf_size >= 52 && MEM[LVA] == 127 && MEM[LVA+1] == 69 && MEM[LVA+2] == 76 && MEM[LVA+3] == 70) { + # Verify class is ELF32, data is LSB (little endian), and machine is RISC-V (243 / 0xf3) + if(MEM[LVA+4] != 1 || MEM[LVA+5] != 1 || (MEM[LVA+18] + MEM[LVA+19] * 256) != 243) + trapout("Incompatible ELF file (must be RV32 little-endian)") + + # Read entry point (4 bytes at offset 24) + e_entry = MEM[LVA+24] + MEM[LVA+25] * 256 + MEM[LVA+26] * 65536 + MEM[LVA+27] * 16777216 + # Read program header table offset (4 bytes at offset 28) + e_phoff = MEM[LVA+28] + MEM[LVA+29] * 256 + MEM[LVA+30] * 65536 + MEM[LVA+31] * 16777216 + # Read number of program headers (2 bytes at offset 44) + e_phnum = MEM[LVA+44] + MEM[LVA+45] * 256 + # Read size of program header entry (2 bytes at offset 42) + e_phentsize = MEM[LVA+42] + MEM[LVA+43] * 256 + + # Back up the entire ELF image to ELF_DATA array and clear MEM in the loaded range + for(i = 0; i < elf_size; i++) { + ELF_DATA[i] = MEM[orig_LVA + i] + delete MEM[orig_LVA + i] + } + + # Load segment data into memory + for(p = 0; p < e_phnum; p++) { + ph_addr = e_phoff + p * e_phentsize + p_type = ELF_DATA[ph_addr] + ELF_DATA[ph_addr+1] * 256 + ELF_DATA[ph_addr+2] * 65536 + ELF_DATA[ph_addr+3] * 16777216 + + # We only load PT_LOAD (p_type == 1) + if(p_type == 1) { + p_offset = ELF_DATA[ph_addr+4] + ELF_DATA[ph_addr+5] * 256 + ELF_DATA[ph_addr+6] * 65536 + ELF_DATA[ph_addr+7] * 16777216 + p_vaddr = ELF_DATA[ph_addr+8] + ELF_DATA[ph_addr+9] * 256 + ELF_DATA[ph_addr+10] * 65536 + ELF_DATA[ph_addr+11] * 16777216 + p_filesz = ELF_DATA[ph_addr+16] + ELF_DATA[ph_addr+17] * 256 + ELF_DATA[ph_addr+18] * 65536 + ELF_DATA[ph_addr+19] * 16777216 + p_memsz = ELF_DATA[ph_addr+20] + ELF_DATA[ph_addr+21] * 256 + ELF_DATA[ph_addr+22] * 65536 + ELF_DATA[ph_addr+23] * 16777216 + + # Copy p_filesz bytes from ELF_DATA to MEM + for(i = 0; i < p_filesz; i++) + MEM[p_vaddr + i] = ELF_DATA[p_offset + i] + # Zero-initialize the remaining p_memsz - p_filesz bytes + for(i = p_filesz; i < p_memsz; i++) + MEM[p_vaddr + i] = 0 + } + } + + # Override LVA to the entry point (actual memory start value inside the ELF file) + LVA = e_entry + } + memsize = pc - LVA pc = LVA while(pc > -1) {