added elf loader

This commit is contained in:
Luxferre
2026-06-19 07:45:35 +03:00
parent ce40bb9a28
commit bd27989a03
3 changed files with 78 additions and 7 deletions
+11 -4
View File
@@ -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..."
+16 -2
View File
@@ -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):
+51 -1
View File
@@ -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) {