commit 8d6775c51d4826912a5c2c92d403f3395b48f0b8 Author: Michael <20937441+KMikeeU@users.noreply.github.com> Date: Tue Apr 14 00:02:09 2026 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..9bffda0 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "tinyriscv"] + path = tinyriscv + url = git@github.com:inixyz/tinyriscv.git diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..08d7be8 --- /dev/null +++ b/Makefile @@ -0,0 +1,46 @@ +RISCV_CC ?= riscv64-unknown-elf-gcc +RISCV_ARCH_FLAGS ?= -march=rv32im -mabi=ilp32 +HOST_CC ?= gcc +HOST_CFLAGS ?= -Wall -Wextra -O3 -g +HOST_LDFLAGS ?= -Wl,-s + +EMU_SRC_FILES := $(wildcard src/emulated/*.c) +EMU_OBJ_FILES := $(EMU_SRC_FILES:src/emulated/%.c=build/%.riscv.o) +EMU_OBJ := build/emulated.riscv.elf + +HOST_SRC := src/main.c src/elf.c +HOST_EXE := build/main.exe + +# Disassembly +RISCV_OBJDUMP ?= $(patsubst %-gcc,%-objdump,$(RISCV_CC)) +_DISAS_TARGETS := $(filter disas-%,$(MAKECMDGOALS)) +_DISAS_FILES := $(if $(_DISAS_TARGETS),$(filter %.elf %.o,$(MAKECMDGOALS))) + + + +.PHONY: all emulated-riscv host clean disas-risc + +all: $(HOST_EXE) $(EMU_OBJ) + +emulated-riscv: $(EMU_OBJ) + +host: $(HOST_EXE) + +$(HOST_EXE): $(HOST_SRC) tinyriscv/src/tinyriscv.h src/config.h src/elf.h | build + $(HOST_CC) $(HOST_CFLAGS) $(HOST_LDFLAGS) -I src -I tinyriscv/src $(HOST_SRC) -o $@ + +build/%.riscv.o: src/emulated/%.c | build + $(RISCV_CC) $(RISCV_ARCH_FLAGS) -c $< -o $@ + +$(EMU_OBJ): $(EMU_OBJ_FILES) | build + $(RISCV_CC) $(RISCV_ARCH_FLAGS) -nostdlib -Wl,-e,entry -Wl,-Ttext=0 $(EMU_OBJ_FILES) -o $@ + +build: + mkdir -p $@ + +clean: + rm -rf build + +disas-risc: $(_DISAS_FILES) + $(RISCV_OBJDUMP) -d $^ + diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..8a8ac5e --- /dev/null +++ b/src/config.h @@ -0,0 +1,5 @@ +#pragma once + +#define EMU_MEM_SIZE 4096u +#define EMU_PROGRAM_PATH "build/emulated.riscv.elf" +#define EMU_ENTRY_SYMBOL "entry" diff --git a/src/elf.c b/src/elf.c new file mode 100644 index 0000000..41c1fce --- /dev/null +++ b/src/elf.c @@ -0,0 +1,346 @@ +#include "elf.h" +#include "config.h" + +#include +#include +#include +#include +#include + + +static int read_file_bytes(const char* path, uint8_t** out_buf, size_t* out_size) { + FILE* f = fopen(path, "rb"); + if (!f) { + fprintf(stderr, "Failed to open object file: %s (%s)\n", path, strerror(errno)); + return LOAD_ERR_OPEN; + } + + if (fseek(f, 0, SEEK_END) != 0) { + fclose(f); + fprintf(stderr, "Failed to seek object file: %s\n", path); + return LOAD_ERR_SMALL_FILE; + } + + long file_size = ftell(f); + if (file_size < 0) { + fclose(f); + fprintf(stderr, "Failed to read object file size: %s\n", path); + return LOAD_ERR_SMALL_FILE; + } + + if (fseek(f, 0, SEEK_SET) != 0) { + fclose(f); + fprintf(stderr, "Failed to rewind object file: %s\n", path); + return LOAD_ERR_SMALL_FILE; + } + + uint8_t* file_buf = (uint8_t*)malloc((size_t)file_size); + if (!file_buf) { + fclose(f); + fprintf(stderr, "Failed to allocate %ld bytes for object file\n", file_size); + return LOAD_ERR_OOM; + } + + size_t read_count = fread(file_buf, 1, (size_t)file_size, f); + fclose(f); + + if (read_count != (size_t)file_size) { + free(file_buf); + fprintf(stderr, "Object file too small or unreadable: %s\n", path); + return LOAD_ERR_SMALL_FILE; + } + + *out_buf = file_buf; + *out_size = read_count; + return LOAD_OK; +} + + +uint16_t read_u16_le(const uint8_t* data) { + return (uint16_t)data[0] | ((uint16_t)data[1] << 8); +} + +uint32_t read_u32_le(const uint8_t* data) { + return (uint32_t)data[0] + | ((uint32_t)data[1] << 8) + | ((uint32_t)data[2] << 16) + | ((uint32_t)data[3] << 24); +} + +uint32_t align_up(uint32_t value, uint32_t align) { + if (align == 0u) { + return value; + } + return (value + align - 1u) & ~(align - 1u); +} + +int load_elf_object_sections(const char* path, uint8_t* memory, uint32_t mem_size) { + uint8_t* file_buf = NULL; + size_t bytes = 0u; + int file_status = read_file_bytes(path, &file_buf, &bytes); + if (file_status != LOAD_OK) { + return file_status; + } + + if (bytes < 52u) { + fprintf(stderr, "Object file too small or unreadable: %s\n", path); + free(file_buf); + return LOAD_ERR_SMALL_FILE; + } + + if (file_buf[0] != ELF_MAGIC_0 || file_buf[1] != ELF_MAGIC_1 + || file_buf[2] != ELF_MAGIC_2 || file_buf[3] != ELF_MAGIC_3) { + fprintf(stderr, "Not an ELF file: %s\n", path); + free(file_buf); + return LOAD_ERR_NOT_ELF; + } + + if (file_buf[4] != ELFCLASS32 || file_buf[5] != ELFDATA2LSB) { + fprintf(stderr, "Unsupported ELF format in %s (need 32-bit little-endian)\n", path); + free(file_buf); + return LOAD_ERR_UNSUPPORTED_FORMAT; + } + + uint32_t e_shoff = read_u32_le(file_buf + 32); + uint16_t e_shentsize = read_u16_le(file_buf + 46); + uint16_t e_shnum = read_u16_le(file_buf + 48); + + if (e_shoff >= bytes || e_shentsize == 0u) { + fprintf(stderr, "Invalid section header table in %s\n", path); + free(file_buf); + return LOAD_ERR_INVALID_SHT; + } + + if ((uint32_t)e_shoff + (uint32_t)e_shentsize * (uint32_t)e_shnum > bytes) { + fprintf(stderr, "Section headers out of file bounds in %s\n", path); + free(file_buf); + return LOAD_ERR_SHT_OUT_OF_BOUNDS; + } + + uint32_t cursor = 0u; + + for (uint16_t i = 0; i < e_shnum; ++i) { + const uint8_t* sh = file_buf + e_shoff + (uint32_t)i * e_shentsize; + + uint32_t sh_type = read_u32_le(sh + 4); + uint32_t sh_flags = read_u32_le(sh + 8); + uint32_t sh_addr = read_u32_le(sh + 12); + uint32_t sh_offset = read_u32_le(sh + 16); + uint32_t sh_size = read_u32_le(sh + 20); + uint32_t sh_addralign = read_u32_le(sh + 32); + + if ((sh_flags & SHF_ALLOC) == 0u || sh_size == 0u) { + continue; + } + + uint32_t dst = (sh_addr != 0u) ? sh_addr : align_up(cursor, sh_addralign); + + if (dst >= mem_size || sh_size > mem_size - dst) { + fprintf(stderr, "Section %u does not fit into emulator memory\n", (unsigned)i); + free(file_buf); + return LOAD_ERR_SECTION_TOO_LARGE; + } + + if (sh_type == SHT_NOBITS) { + memset(memory + dst, 0, sh_size); + } else { + if (sh_offset >= bytes || sh_size > (uint32_t)bytes - sh_offset) { + fprintf(stderr, "Section %u exceeds object file bounds\n", (unsigned)i); + free(file_buf); + return LOAD_ERR_SECTION_FILE_BOUNDS; + } + memcpy(memory + dst, file_buf + sh_offset, sh_size); + } + + if (sh_addr == 0u) { + cursor = dst + sh_size; + } + } + + free(file_buf); + return LOAD_OK; +} + +int resolve_elf_symbol_memory_offset(const char* path, const char* symbol_name, + uint32_t mem_size, uint32_t* out_offset) { + uint8_t* file_buf = NULL; + size_t bytes = 0u; + int file_status = read_file_bytes(path, &file_buf, &bytes); + if (file_status != LOAD_OK) { + return file_status; + } + + if (bytes < 52u) { + free(file_buf); + return LOAD_ERR_SMALL_FILE; + } + + if (file_buf[0] != ELF_MAGIC_0 || file_buf[1] != ELF_MAGIC_1 + || file_buf[2] != ELF_MAGIC_2 || file_buf[3] != ELF_MAGIC_3) { + free(file_buf); + return LOAD_ERR_NOT_ELF; + } + + if (file_buf[4] != ELFCLASS32 || file_buf[5] != ELFDATA2LSB) { + free(file_buf); + return LOAD_ERR_UNSUPPORTED_FORMAT; + } + + uint32_t e_shoff = read_u32_le(file_buf + 32); + uint16_t e_shentsize = read_u16_le(file_buf + 46); + uint16_t e_shnum = read_u16_le(file_buf + 48); + + if (e_shoff >= bytes || e_shentsize == 0u) { + free(file_buf); + return LOAD_ERR_INVALID_SHT; + } + + if ((uint32_t)e_shoff + (uint32_t)e_shentsize * (uint32_t)e_shnum > bytes) { + free(file_buf); + return LOAD_ERR_SHT_OUT_OF_BOUNDS; + } + + uint32_t* section_runtime = (uint32_t*)calloc(e_shnum, sizeof(uint32_t)); + uint32_t* section_addr = (uint32_t*)calloc(e_shnum, sizeof(uint32_t)); + uint8_t* section_loaded = (uint8_t*)calloc(e_shnum, sizeof(uint8_t)); + if (!section_runtime || !section_addr || !section_loaded) { + free(section_runtime); + free(section_addr); + free(section_loaded); + free(file_buf); + return LOAD_ERR_OOM; + } + + uint32_t cursor = 0u; + for (uint16_t i = 0; i < e_shnum; ++i) { + const uint8_t* sh = file_buf + e_shoff + (uint32_t)i * e_shentsize; + uint32_t sh_flags = read_u32_le(sh + 8); + uint32_t sh_addr = read_u32_le(sh + 12); + uint32_t sh_size = read_u32_le(sh + 20); + uint32_t sh_addralign = read_u32_le(sh + 32); + + if ((sh_flags & SHF_ALLOC) == 0u || sh_size == 0u) { + continue; + } + + uint32_t dst = (sh_addr != 0u) ? sh_addr : align_up(cursor, sh_addralign); + if (dst >= mem_size || sh_size > mem_size - dst) { + free(section_runtime); + free(section_addr); + free(section_loaded); + free(file_buf); + return LOAD_ERR_SECTION_TOO_LARGE; + } + + section_runtime[i] = dst; + section_addr[i] = sh_addr; + section_loaded[i] = 1u; + + if (sh_addr == 0u) { + cursor = dst + sh_size; + } + } + + int found_symtab = 0; + int status = LOAD_ERR_SYMBOL_NOT_FOUND; + + for (uint16_t i = 0; i < e_shnum; ++i) { + const uint8_t* sh = file_buf + e_shoff + (uint32_t)i * e_shentsize; + uint32_t sh_type = read_u32_le(sh + 4); + uint32_t sh_offset = read_u32_le(sh + 16); + uint32_t sh_size = read_u32_le(sh + 20); + uint32_t sh_link = read_u32_le(sh + 24); + uint32_t sh_entsize = read_u32_le(sh + 36); + + if (sh_type != SHT_SYMTAB) { + continue; + } + + found_symtab = 1; + + if (sh_link >= e_shnum || sh_offset >= bytes || sh_size > (uint32_t)bytes - sh_offset) { + status = LOAD_ERR_STRTAB_INVALID; + break; + } + + const uint8_t* strtab_sh = file_buf + e_shoff + sh_link * (uint32_t)e_shentsize; + uint32_t strtab_offset = read_u32_le(strtab_sh + 16); + uint32_t strtab_size = read_u32_le(strtab_sh + 20); + + if (strtab_offset >= bytes || strtab_size > (uint32_t)bytes - strtab_offset) { + status = LOAD_ERR_STRTAB_INVALID; + break; + } + + if (sh_entsize == 0u) { + sh_entsize = 16u; + } + + if (sh_entsize < 16u) { + status = LOAD_ERR_STRTAB_INVALID; + break; + } + + uint32_t count = sh_size / sh_entsize; + const uint8_t* symtab = file_buf + sh_offset; + const char* strtab = (const char*)(file_buf + strtab_offset); + + for (uint32_t s = 0u; s < count; ++s) { + const uint8_t* sym = symtab + s * sh_entsize; + uint32_t st_name = read_u32_le(sym + 0); + uint32_t st_value = read_u32_le(sym + 4); + uint16_t st_shndx = read_u16_le(sym + 14); + + if (st_name >= strtab_size) { + continue; + } + + const char* name = strtab + st_name; + if (strcmp(name, symbol_name) != 0) { + continue; + } + + if (st_shndx == 0u) { + continue; + } + + if (st_shndx >= e_shnum || !section_loaded[st_shndx]) { + status = LOAD_ERR_SYMBOL_INVALID_SECTION; + break; + } + + uint32_t section_base_runtime = section_runtime[st_shndx]; + uint32_t section_base_elf = section_addr[st_shndx]; + if (st_value < section_base_elf) { + status = LOAD_ERR_SYMBOL_INVALID_SECTION; + break; + } + + uint32_t symbol_offset = section_base_runtime + (st_value - section_base_elf); + if (symbol_offset >= mem_size) { + status = LOAD_ERR_SYMBOL_OUT_OF_RANGE; + break; + } + + *out_offset = symbol_offset; + status = LOAD_OK; + break; + } + + if (status == LOAD_OK || status == LOAD_ERR_SYMBOL_INVALID_SECTION + || status == LOAD_ERR_SYMBOL_OUT_OF_RANGE) { + break; + } + } + + if (!found_symtab) { + status = LOAD_ERR_SYMTAB_MISSING; + } + + free(section_runtime); + free(section_addr); + free(section_loaded); + free(file_buf); + return status; +} + diff --git a/src/elf.h b/src/elf.h new file mode 100644 index 0000000..b5e4336 --- /dev/null +++ b/src/elf.h @@ -0,0 +1,41 @@ +#pragma once + +#define ELF_MAGIC_0 0x7f +#define ELF_MAGIC_1 'E' +#define ELF_MAGIC_2 'L' +#define ELF_MAGIC_3 'F' + +#define ELFCLASS32 1 +#define ELFDATA2LSB 1 + +#define SHT_NOBITS 8u +#define SHT_SYMTAB 2u +#define SHF_ALLOC 0x2u + +#include + +enum { + LOAD_OK = 1, + LOAD_ERR_OPEN = -1, + LOAD_ERR_SMALL_FILE = -2, + LOAD_ERR_NOT_ELF = -3, + LOAD_ERR_UNSUPPORTED_FORMAT = -4, + LOAD_ERR_INVALID_SHT = -5, + LOAD_ERR_SHT_OUT_OF_BOUNDS = -6, + LOAD_ERR_SECTION_TOO_LARGE = -7, + LOAD_ERR_SECTION_FILE_BOUNDS = -8, + LOAD_ERR_OOM = -9, + LOAD_ERR_SYMTAB_MISSING = -10, + LOAD_ERR_STRTAB_INVALID = -11, + LOAD_ERR_SYMBOL_NOT_FOUND = -12, + LOAD_ERR_SYMBOL_INVALID_SECTION = -13, + LOAD_ERR_SYMBOL_OUT_OF_RANGE = -14 +}; + +uint16_t read_u16_le(const uint8_t* data); +uint32_t read_u32_le(const uint8_t* data); +uint32_t align_up(uint32_t value, uint32_t align); +int load_elf_object_sections(const char* path, uint8_t* memory, uint32_t mem_size); +int resolve_elf_symbol_memory_offset(const char* path, const char* symbol_name, + uint32_t mem_size, uint32_t* out_offset); + diff --git a/src/emulated/custom_instructions.h b/src/emulated/custom_instructions.h new file mode 100644 index 0000000..d7c40b8 --- /dev/null +++ b/src/emulated/custom_instructions.h @@ -0,0 +1,15 @@ +#pragma once + + +#define trunc_xor(a, imm12) \ + __extension__ ({ \ + int32_t _result; \ + asm volatile ( \ + ".insn i 0x0B, 0x00, %0, %1, %2" \ + : "=r" (_result) \ + : "r" ((int32_t)(a)), \ + "I" (imm12) \ + ); \ + _result; \ + }) + diff --git a/src/emulated/function.c b/src/emulated/function.c new file mode 100644 index 0000000..f7ab9a6 --- /dev/null +++ b/src/emulated/function.c @@ -0,0 +1,13 @@ + +#include "function.h" + +#include "custom_instructions.h" +#include + + + +int32_t operation(int32_t a) { + return trunc_xor(a, 3); +} + + diff --git a/src/emulated/function.h b/src/emulated/function.h new file mode 100644 index 0000000..b167114 --- /dev/null +++ b/src/emulated/function.h @@ -0,0 +1,6 @@ +#pragma once + +#include + +int32_t operation(int32_t a); + diff --git a/src/emulated/program.c b/src/emulated/program.c new file mode 100644 index 0000000..decd3eb --- /dev/null +++ b/src/emulated/program.c @@ -0,0 +1,10 @@ +#include + +#include "function.h" +#include "custom_instructions.h" + + +uint8_t entry(uint8_t a) { + return operation(a); +} + diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..6a8b397 --- /dev/null +++ b/src/main.c @@ -0,0 +1,75 @@ +#define TINYRISCV_M + +#include "tinyriscv.h" +#include "elf.h" +#include "config.h" + +#include +#include +#include + + +#define TEST_A 5u +#define TEST_B 7u +#define MAX_STEPS 1024u + + +int main(){ + uint8_t memory[EMU_MEM_SIZE]; //4kb of internal memory + tinyriscv_core cpu; //create the emulation core + memset(&cpu, 0, sizeof(cpu)); + cpu.mem = memory; //use the uint8_t array as the memory + cpu.mem_size = EMU_MEM_SIZE; + memset(memory, 0, sizeof(memory)); + + int load_status = load_elf_object_sections(EMU_PROGRAM_PATH, memory, cpu.mem_size); + if (load_status != LOAD_OK) { + fprintf(stderr, "ELF load failed with code %d for '%s'\n", load_status, EMU_PROGRAM_PATH); + fflush(stderr); + printf("ELF load failed with code %d\n", load_status); + fflush(stdout); + return 1; + } + + uint32_t entry_offset = 0u; + int symbol_status = resolve_elf_symbol_memory_offset( + EMU_PROGRAM_PATH, EMU_ENTRY_SYMBOL, cpu.mem_size, &entry_offset); + if (symbol_status != LOAD_OK) { + fprintf(stderr, "Entry symbol '%s' resolve failed with code %d for '%s'\n", + EMU_ENTRY_SYMBOL, symbol_status, EMU_PROGRAM_PATH); + return 2; + } + + tinyriscv_init(&cpu); //initialise core for execution + cpu.pc = tinyriscv_MEM_OFFSET + entry_offset; + + // Call operation(a, b) using RV32 calling convention: + // a0=x10, return value in a0, return address in ra=x1. + cpu.x[10] = TEST_A; + cpu.x[1] = tinyriscv_MEM_OFFSET + cpu.mem_size; + + uint32_t steps = 0u; + while (tinyriscv_valid_step(&cpu) && steps < MAX_STEPS) { + tinyriscv_step(&cpu); + ++steps; + } + + if (steps == MAX_STEPS) { + fprintf(stderr, "Execution did not halt within %u steps\n", MAX_STEPS); + return 2; + } + + uint32_t actual = (uint32_t)cpu.x[10]; + uint32_t expected = ((TEST_A << 3) ^ 0xFF) >> 3; + printf("operation(%u) -> %u (expected %u)\n", + (unsigned)TEST_A, + (unsigned)actual, (unsigned)expected); + + if (actual != expected) { + fprintf(stderr, "Verification failed\n"); + return 3; + } + + return 0; +} + diff --git a/tinyriscv b/tinyriscv new file mode 160000 index 0000000..2ca19e3 --- /dev/null +++ b/tinyriscv @@ -0,0 +1 @@ +Subproject commit 2ca19e3f78712d8d6e2b0f025d74cc020d9e7f90