initial commit

This commit is contained in:
2026-04-14 00:02:09 +02:00
commit 8d6775c51d
12 changed files with 562 additions and 0 deletions

5
src/config.h Normal file
View File

@@ -0,0 +1,5 @@
#pragma once
#define EMU_MEM_SIZE 4096u
#define EMU_PROGRAM_PATH "build/emulated.riscv.elf"
#define EMU_ENTRY_SYMBOL "entry"

346
src/elf.c Normal file
View File

@@ -0,0 +1,346 @@
#include "elf.h"
#include "config.h"
#include <stdint.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
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;
}

41
src/elf.h Normal file
View File

@@ -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 <stdint.h>
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);

View File

@@ -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; \
})

13
src/emulated/function.c Normal file
View File

@@ -0,0 +1,13 @@
#include "function.h"
#include "custom_instructions.h"
#include <stdint.h>
int32_t operation(int32_t a) {
return trunc_xor(a, 3);
}

6
src/emulated/function.h Normal file
View File

@@ -0,0 +1,6 @@
#pragma once
#include <stdint.h>
int32_t operation(int32_t a);

10
src/emulated/program.c Normal file
View File

@@ -0,0 +1,10 @@
#include <stdint.h>
#include "function.h"
#include "custom_instructions.h"
uint8_t entry(uint8_t a) {
return operation(a);
}

75
src/main.c Normal file
View File

@@ -0,0 +1,75 @@
#define TINYRISCV_M
#include "tinyriscv.h"
#include "elf.h"
#include "config.h"
#include <errno.h>
#include <stdio.h>
#include <string.h>
#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;
}