From fde9ce230f11b6a7ca2586be434ee5f2a142d9e2 Mon Sep 17 00:00:00 2001 From: ChinYikMing Date: Thu, 1 Feb 2024 13:52:26 +0800 Subject: [PATCH] Refine the API in the public header The following should be included in an emulator's simple and clear public API: 1. create/init core 2. run emulation 3. delete/destroy core Other components, including as memory, file systems, program data, etc., should be abstracted from the user, as a result, setting a configuration value (vm_attr_t) is sufficient. The user should manage about memory (state_t) and elf stuff before this PR. The user may just construct a core, run it, and shut it down after this PR, so they won't need to worry about them anymore. For stdio remapping, rv_remap_stdstream function is introduced. The vm_attr_t has multiple fields and they are commented clearly in the code. elf_t is reopened in run_and_trace and dump_test_signature because elf_t is allocated inside rv_create and they cannot access them. It is acceptable to reopen elf_t since they are only for testing and debugging. PRINT_EXIT_CODE build macro is introduced to enable syscall_exit to print exit code to console only during testing since the actual usage of exit code is really depending on applications. The io interface is not changed in this PR because it could maybe reused with semu in some way, still need to be investigated. Also, Logging feature and system emulator integration are not implemented yet. related: #310 --- Makefile | 9 +- src/elf.c | 6 +- src/elf.h | 2 +- src/emulate.c | 6 +- src/io.c | 24 ++---- src/io.h | 2 +- src/jit.c | 4 +- src/main.c | 67 +++++++-------- src/riscv.c | 177 +++++++++++++++++++++++++++----------- src/riscv.h | 119 ++++++++++++++++++++----- src/riscv_private.h | 3 +- src/syscall.c | 83 ++++++++++-------- src/syscall_sdl.c | 30 ++++--- tools/gen-jit-template.py | 2 +- 14 files changed, 343 insertions(+), 191 deletions(-) diff --git a/Makefile b/Makefile index cda3c5027..61dcdde40 100644 --- a/Makefile +++ b/Makefile @@ -11,11 +11,6 @@ CFLAGS = -std=gnu99 -O2 -Wall -Wextra CFLAGS += -Wno-unused-label CFLAGS += -include src/common.h -# Set the default stack pointer -CFLAGS += -D DEFAULT_STACK_ADDR=0xFFFFE000 -# Set the default args starting address -CFLAGS += -D DEFAULT_ARGS_ADDR=0xFFFFF000 - # Enable link-time optimization (LTO) ENABLE_LTO ?= 1 ifeq ($(call has, LTO), 1) @@ -121,7 +116,7 @@ endif ENABLE_JIT ?= 0 $(call set-feature, JIT) ifeq ($(call has, JIT), 1) -OBJS_EXT += jit.o +OBJS_EXT += jit.o ifneq ($(processor),$(filter $(processor),x86_64 aarch64 arm64)) $(error JIT mode only supports for x64 and arm64 target currently.) endif @@ -202,6 +197,7 @@ EXPECTED_puzzle = success in 2005 trials EXPECTED_fcalc = Performed 12 tests, 0 failures, 100% success rate. EXPECTED_pi = 3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067982148086 +check: CFLAGS += -DPRINT_EXIT_CODE check: $(BIN) $(Q)$(foreach e,$(CHECK_ELF_FILES),\ $(PRINTF) "Running $(e).elf ... "; \ @@ -214,6 +210,7 @@ check: $(BIN) ) EXPECTED_aes_sha1 = 1242a6757c8aef23e50b5264f5941a2f4b4a347e - +misalign: CFLAGS += -DPRINT_EXIT_CODE misalign: $(BIN) $(Q)$(PRINTF) "Running aes.elf ... "; $(Q)if [ "$(shell $(BIN) -m $(OUT)/aes.elf | $(SHA1SUM))" = "$(EXPECTED_aes_sha1)" ]; then \ diff --git a/src/elf.c b/src/elf.c index 7d4414020..4f3f4e48a 100644 --- a/src/elf.c +++ b/src/elf.c @@ -259,12 +259,8 @@ bool elf_get_data_section_range(elf_t *e, uint32_t *start, uint32_t *end) * Finding data for section headers: * File start + section_header.offset -> section Data */ -bool elf_load(elf_t *e, riscv_t *rv, memory_t *mem) +bool elf_load(elf_t *e, memory_t *mem) { - /* set the entry point */ - if (!rv_set_pc(rv, e->hdr->e_entry)) - return false; - /* loop over all of the program headers */ for (int p = 0; p < e->hdr->e_phnum; ++p) { /* find next program header */ diff --git a/src/elf.h b/src/elf.h index e43caf9a0..6ebb80723 100644 --- a/src/elf.h +++ b/src/elf.h @@ -145,7 +145,7 @@ const char *elf_find_symbol(elf_t *e, uint32_t addr); bool elf_get_data_section_range(elf_t *e, uint32_t *start, uint32_t *end); /* Load the ELF file into a memory abstraction */ -bool elf_load(elf_t *e, riscv_t *rv, memory_t *mem); +bool elf_load(elf_t *e, memory_t *mem); /* get the ELF header */ struct Elf32_Ehdr *get_elf_header(elf_t *e); diff --git a/src/emulate.c b/src/emulate.c index ada47350a..dd3886ce7 100644 --- a/src/emulate.c +++ b/src/emulate.c @@ -125,7 +125,7 @@ RV_EXCEPTION_LIST */ #define RV_EXC_MISALIGN_HANDLER(mask_or_pc, type, compress, IO) \ IIF(IO) \ - (if (!rv->io.allow_misalign && unlikely(addr & (mask_or_pc))), \ + (if (!PRIV(rv)->allow_misalign && unlikely(addr & (mask_or_pc))), \ if (unlikely(insn_is_misaligned(PC)))) \ { \ rv->compressed = compress; \ @@ -1182,7 +1182,7 @@ void ecall_handler(riscv_t *rv) void memset_handler(riscv_t *rv) { - memory_t *m = ((state_t *) rv->userdata)->mem; + memory_t *m = PRIV(rv)->mem; memset((char *) m->mem_base + rv->X[rv_reg_a0], rv->X[rv_reg_a1], rv->X[rv_reg_a2]); rv->PC = rv->X[rv_reg_ra] & ~1U; @@ -1190,7 +1190,7 @@ void memset_handler(riscv_t *rv) void memcpy_handler(riscv_t *rv) { - memory_t *m = ((state_t *) rv->userdata)->mem; + memory_t *m = PRIV(rv)->mem; memcpy((char *) m->mem_base + rv->X[rv_reg_a0], (char *) m->mem_base + rv->X[rv_reg_a1], rv->X[rv_reg_a2]); rv->PC = rv->X[rv_reg_ra] & ~1U; diff --git a/src/io.c b/src/io.c index ce66ce465..6da5d284c 100644 --- a/src/io.c +++ b/src/io.c @@ -17,44 +17,36 @@ static uint8_t *data_memory_base; -/* - * set memory size to 2^32 - 1 bytes - * - * The memory size is set to 2^32 - 1 bytes in order to make this emulator - * portable for both 32-bit and 64-bit platforms. As a result, it can access - * any segment of the memory on either platform. Furthermore, it is safe - * because most of the test cases' data memory usage will not exceed this - * memory size. - */ -#define MEM_SIZE 0xFFFFFFFFULL - -memory_t *memory_new(void) +memory_t *memory_new(uint32_t size) { + if (!size) + return NULL; + memory_t *mem = malloc(sizeof(memory_t)); assert(mem); #if HAVE_MMAP - data_memory_base = mmap(NULL, MEM_SIZE, PROT_READ | PROT_WRITE, + data_memory_base = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); if (data_memory_base == MAP_FAILED) { free(mem); return NULL; } #else - data_memory_base = malloc(MEM_SIZE); + data_memory_base = malloc(size); if (!data_memory_base) { free(mem); return NULL; } #endif mem->mem_base = data_memory_base; - mem->mem_size = MEM_SIZE; + mem->mem_size = size; return mem; } void memory_delete(memory_t *mem) { #if HAVE_MMAP - munmap(mem->mem_base, MEM_SIZE); + munmap(mem->mem_base, mem->mem_size); #else free(mem->mem_base); #endif diff --git a/src/io.h b/src/io.h index cc93008f0..9a6741796 100644 --- a/src/io.h +++ b/src/io.h @@ -17,7 +17,7 @@ typedef struct { uint64_t mem_size; } memory_t; -memory_t *memory_new(void); +memory_t *memory_new(uint32_t size); void memory_delete(memory_t *m); /* read a C-style string from memory */ diff --git a/src/jit.c b/src/jit.c index a24648dae..8c72658d6 100644 --- a/src/jit.c +++ b/src/jit.c @@ -1284,7 +1284,7 @@ static void do_fuse2(struct jit_state *state, riscv_t *rv UNUSED, rv_insn_t *ir) static void do_fuse3(struct jit_state *state, riscv_t *rv, rv_insn_t *ir) { - memory_t *m = ((state_t *) rv->userdata)->mem; + memory_t *m = PRIV(rv)->mem; opcode_fuse_t *fuse = ir->fuse; for (int i = 0; i < ir->imm2; i++) { emit_load(state, S32, parameter_reg[0], temp_reg[0], @@ -1300,7 +1300,7 @@ static void do_fuse3(struct jit_state *state, riscv_t *rv, rv_insn_t *ir) static void do_fuse4(struct jit_state *state, riscv_t *rv, rv_insn_t *ir) { - memory_t *m = ((state_t *) rv->userdata)->mem; + memory_t *m = ((vm_attr_t *) rv->userdata)->mem; opcode_fuse_t *fuse = ir->fuse; for (int i = 0; i < ir->imm2; i++) { emit_load(state, S32, parameter_reg[0], temp_reg[0], diff --git a/src/main.c b/src/main.c index 71d900b71..3edde6c93 100644 --- a/src/main.c +++ b/src/main.c @@ -75,8 +75,10 @@ IO_HANDLER_IMPL(byte, write_b, W) #undef W /* run: printing out an instruction trace */ -static void run_and_trace(riscv_t *rv, elf_t *elf) +static void run_and_trace(riscv_t *rv, const char *prog_name) { + elf_t *elf = elf_new(); + assert(elf && elf_open(elf, prog_name)); const uint32_t cycles_per_step = 1; for (; !rv_has_halted(rv);) { /* run until the flag is done */ @@ -88,15 +90,8 @@ static void run_and_trace(riscv_t *rv, elf_t *elf) /* step instructions */ rv_step(rv, cycles_per_step); } -} -static void run(riscv_t *rv) -{ - const uint32_t cycles_per_step = 100; - for (; !rv_has_halted(rv);) { /* run until the flag is done */ - /* step instructions */ - rv_step(rv, cycles_per_step); - } + elf_delete(elf); } static void print_usage(const char *filename) @@ -188,8 +183,11 @@ static bool parse_args(int argc, char **args) return true; } -static void dump_test_signature(elf_t *elf) +static void dump_test_signature(const char *prog_name) { + elf_t *elf = elf_new(); + assert(elf && elf_open(elf, prog_name)); + uint32_t start = 0, end = 0; const struct Elf32_Sym *sym; FILE *f = fopen(signature_out_file, "w"); @@ -212,8 +210,13 @@ static void dump_test_signature(elf_t *elf) fprintf(f, "%08x\n", memory_read_w(addr)); fclose(f); + elf_delete(elf); } +#define MEM_SIZE 0xFFFFFFFFULL /* 2^32 - 1 */ +#define STACK_SIZE 0x1000 /* 4096 */ +#define ARGS_OFFSET_SIZE 0x1000 /* 4096 */ + int main(int argc, char **args) { if (argc == 1 || !parse_args(argc, args)) { @@ -221,12 +224,19 @@ int main(int argc, char **args) return 1; } - /* open the ELF file from the file system */ - elf_t *elf = elf_new(); - if (!elf_open(elf, opt_prog_name)) { - fprintf(stderr, "Unable to open ELF file '%s'\n", opt_prog_name); - return 1; - } + vm_attr_t attr = { + .mem_size = MEM_SIZE, + .stack_size = STACK_SIZE, + .args_offset_size = ARGS_OFFSET_SIZE, + .argc = prog_argc, + .argv = prog_args, + .logging_level = 0, + .emu_data.vm_user = malloc(sizeof(vm_user_t)), + .cycle_per_step = 100, + .allow_misalign = opt_misaligned, + }; + assert(attr.emu_data.vm_user); + attr.emu_data.vm_user->elf_program = opt_prog_name; /* install the I/O handlers for the RISC-V runtime */ const riscv_io_t io = { @@ -249,30 +259,16 @@ int main(int argc, char **args) .allow_misalign = opt_misaligned, }; - state_t *state = state_new(); - - /* find the start of the heap */ - const struct Elf32_Sym *end; - if ((end = elf_get_symbol(elf, "_end"))) - state->break_addr = end->st_value; - /* create the RISC-V runtime */ - riscv_t *rv = - rv_create(&io, state, prog_argc, prog_args, !opt_quiet_outputs); + riscv_t *rv = rv_create(&io, &attr); if (!rv) { fprintf(stderr, "Unable to create riscv emulator\n"); return 1; } - /* load the ELF file into the memory abstraction */ - if (!elf_load(elf, rv, state->mem)) { - fprintf(stderr, "Unable to load ELF file '%s'\n", args[1]); - return 1; - } - /* run based on the specified mode */ if (opt_trace) { - run_and_trace(rv, elf); + run_and_trace(rv, opt_prog_name); } #if RV32_HAS(GDBSTUB) else if (opt_gdbstub) { @@ -288,15 +284,14 @@ int main(int argc, char **args) dump_registers(rv, registers_out_file); /* dump test result in test mode */ - if (opt_arch_test) - dump_test_signature(elf); + if (opt_arch_test) { + dump_test_signature(opt_prog_name); + } if (opt_prof_data) rv_profile(rv, prof_out_file); /* finalize the RISC-V runtime */ - elf_delete(elf); rv_delete(rv); - state_delete(state); return 0; } diff --git a/src/riscv.c b/src/riscv.c index 1399ff6bf..f835e2550 100644 --- a/src/riscv.c +++ b/src/riscv.c @@ -8,6 +8,7 @@ #include #include +#include "elf.h" #include "mpool.h" #include "riscv.h" #include "riscv_private.h" @@ -105,11 +106,65 @@ riscv_word_t rv_get_reg(riscv_t *rv, uint32_t reg) return ~0U; } -riscv_t *rv_create(const riscv_io_t *io, - riscv_user_t userdata, - int argc, - char **args, - bool output_exit_code) +/* + * Remap stdio + * + * @rv: riscv + * @fsp: a list of pair of mapping from fd to FILE * + * @fsp_size: list size + * + * Note: fd inside fsp should be 0 or 1 or 2 only + * + */ +void rv_remap_stdstream(riscv_t *rv, fd_stream_pair_t *fsp, uint32_t fsp_size) +{ + if (!rv) + return; + + vm_attr_t *attr = PRIV(rv); + if (!attr) + return; + if (!attr->fd_map) + return; + + for (uint32_t i = 0; i < fsp_size; i++) { + int fd = fsp[i].fd; + FILE *file = fsp[i].file; + if (fd != 0 && fd != 1 && fd != 2) + continue; + if (!file) + continue; + + /* + * check if stdio_fd exists or not + */ + map_iter_t it; + map_find(attr->fd_map, &it, &fd); + if (it.node) /* found, remove first */ + map_erase(attr->fd_map, &it); + map_insert(attr->fd_map, &fd, &file); + + /* + * store new fd to make the vm_attr_t consistent + */ + int new_fd = fileno(file); + switch (fd) { + case 0: + attr->stdin_fd = new_fd; + break; + case 1: + attr->stdout_fd = new_fd; + break; + case 2: + attr->stderr_fd = new_fd; + break; + default: + break; + } + } +} + +riscv_t *rv_create(const riscv_io_t *io, riscv_user_t userdata) { assert(io); @@ -122,7 +177,45 @@ riscv_t *rv_create(const riscv_io_t *io, /* copy over the userdata */ rv->userdata = userdata; - rv->output_exit_code = output_exit_code; + vm_attr_t *attr = PRIV(rv); + attr->mem = memory_new(attr->mem_size); + assert(attr->mem); + + /* reset */ + rv_reset(rv, 0U); + + if (attr->emu_data.vm_user) { + elf_t *elf = elf_new(); + assert(elf && elf_open(elf, (attr->emu_data.vm_user)->elf_program)); + + const struct Elf32_Sym *end; + if ((end = elf_get_symbol(elf, "_end"))) + attr->break_addr = end->st_value; + + assert(elf_load(elf, attr->mem)); + + /* set the entry pc */ + const struct Elf32_Ehdr *hdr = get_elf_header(elf); + assert(rv_set_pc(rv, hdr->e_entry)); + + elf_delete(elf); + } else { + /* TODO: system emulator */ + } + + /* + * default stdio + * + * rv_remap_stdstream can be called to overwrite them + */ + attr->fd_map = map_init(int, FILE *, map_cmp_int); + rv_remap_stdstream(rv, + (fd_stream_pair_t[]){ + {0, stdin}, + {1, stdout}, + {2, stderr}, + }, + 3); /* create block and IRs memory pool */ rv->block_mp = mpool_create(sizeof(block_t) << BLOCK_MAP_CAPACITY_BITS, @@ -141,12 +234,20 @@ riscv_t *rv_create(const riscv_io_t *io, rv->block_cache = cache_create(BLOCK_MAP_CAPACITY_BITS); assert(rv->block_cache); #endif - /* reset */ - rv_reset(rv, 0U, argc, args); return rv; } +void run(riscv_t *rv) +{ + vm_attr_t *attr = PRIV(rv); + + for (; !rv_has_halted(rv);) { /* run until the flag is done */ + /* step instructions */ + rv_step(rv, attr->cycle_per_step); + } +} + void rv_halt(riscv_t *rv) { rv->halt = true; @@ -157,15 +258,13 @@ bool rv_has_halted(riscv_t *rv) return rv->halt; } -bool rv_enables_to_output_exit_code(riscv_t *rv) -{ - return rv->output_exit_code; -} - void rv_delete(riscv_t *rv) { assert(rv); #if !RV32_HAS(JIT) + vm_attr_t *attr = PRIV(rv); + map_delete(attr->fd_map); + memory_delete(attr->mem); block_map_destroy(rv); #else mpool_destroy(rv->chain_entry_mp); @@ -175,16 +274,22 @@ void rv_delete(riscv_t *rv) free(rv); } -void rv_reset(riscv_t *rv, riscv_word_t pc, int argc, char **args) +void rv_reset(riscv_t *rv, riscv_word_t pc) { assert(rv); memset(rv->X, 0, sizeof(uint32_t) * N_RV_REGS); + vm_attr_t *attr = PRIV(rv); + int argc = attr->argc; + char **args = attr->argv; + memory_t *mem = attr->mem; + /* set the reset address */ rv->PC = pc; /* set the default stack pointer */ - rv->X[rv_reg_sp] = DEFAULT_STACK_ADDR; + rv->X[rv_reg_sp] = + attr->mem_size - attr->stack_size - attr->args_offset_size; /* Store 'argc' and 'args' of the target program in 'state->mem'. Thus, * we can use an offset trick to emulate 32/64-bit target programs on @@ -218,17 +323,15 @@ void rv_reset(riscv_t *rv, riscv_word_t pc, int argc, char **args) * TODO: access to envp */ - state_t *s = rv_userdata(rv); - /* copy args to RAM */ uintptr_t args_size = (1 + argc + 1) * sizeof(uint32_t); - uintptr_t args_bottom = DEFAULT_ARGS_ADDR; + uintptr_t args_bottom = attr->mem_size - attr->stack_size; uintptr_t args_top = args_bottom - args_size; args_top &= 16; /* argc */ uintptr_t *args_p = (uintptr_t *) args_top; - memory_write(s->mem, (uintptr_t) args_p, (void *) &argc, sizeof(int)); + memory_write(mem, (uintptr_t) args_p, (void *) &argc, sizeof(int)); args_p++; /* args */ @@ -240,7 +343,7 @@ void rv_reset(riscv_t *rv, riscv_word_t pc, int argc, char **args) for (int i = 0; i < argc; i++) { const char *arg = args[i]; args_len = strlen(arg); - memory_write(s->mem, (uintptr_t) args_p, (void *) arg, + memory_write(mem, (uintptr_t) args_p, (void *) arg, (args_len + 1) * sizeof(uint8_t)); args_space[args_space_idx++] = args_len + 1; args_p = (uintptr_t *) ((uintptr_t) args_p + args_len + 1); @@ -257,8 +360,8 @@ void rv_reset(riscv_t *rv, riscv_word_t pc, int argc, char **args) /* argc */ uintptr_t *sp = (uintptr_t *) stack_top; - memory_write(s->mem, (uintptr_t) sp, - (void *) (s->mem->mem_base + (uintptr_t) args_p), sizeof(int)); + memory_write(mem, (uintptr_t) sp, + (void *) (mem->mem_base + (uintptr_t) args_p), sizeof(int)); args_p++; /* keep argc and args[0] within one word due to RV32 ABI */ sp = (uintptr_t *) ((uint32_t *) sp + 1); @@ -266,12 +369,11 @@ void rv_reset(riscv_t *rv, riscv_word_t pc, int argc, char **args) /* args */ for (int i = 0; i < argc; i++) { uintptr_t offset = (uintptr_t) args_p; - memory_write(s->mem, (uintptr_t) sp, (void *) &offset, - sizeof(uintptr_t)); + memory_write(mem, (uintptr_t) sp, (void *) &offset, sizeof(uintptr_t)); args_p = (uintptr_t *) ((uintptr_t) args_p + args_space[i]); sp = (uintptr_t *) ((uint32_t *) sp + 1); } - memory_fill(s->mem, (uintptr_t) sp, sizeof(uint32_t), 0); + memory_fill(mem, (uintptr_t) sp, sizeof(uint32_t), 0); /* reset sp pointing to argc */ rv->X[rv_reg_sp] = stack_top; @@ -301,31 +403,6 @@ void rv_reset(riscv_t *rv, riscv_word_t pc, int argc, char **args) rv->halt = false; } -state_t *state_new(void) -{ - state_t *s = malloc(sizeof(state_t)); - assert(s); - s->mem = memory_new(); - s->break_addr = 0; - - s->fd_map = map_init(int, FILE *, map_cmp_int); - FILE *files[] = {stdin, stdout, stderr}; - for (size_t i = 0; i < ARRAYS_SIZE(files); i++) - map_insert(s->fd_map, &i, &files[i]); - - return s; -} - -void state_delete(state_t *s) -{ - if (!s) - return; - - map_delete(s->fd_map); - memory_delete(s->mem); - free(s); -} - static const char *insn_name_table[] = { #define _(inst, can_branch, insn_len, translatable, reg_mask) \ [rv_insn_##inst] = #inst, diff --git a/src/riscv.h b/src/riscv.h index b109a12ab..44aa8ed3a 100644 --- a/src/riscv.h +++ b/src/riscv.h @@ -7,6 +7,7 @@ #include #include +#include #include "io.h" #include "map.h" @@ -97,6 +98,8 @@ enum { #define BLOCK_MAP_CAPACITY_BITS 10 +#define PRIV(x) ((vm_attr_t *) x->userdata) + /* forward declaration for internal structure */ typedef struct riscv_internal riscv_t; typedef void *riscv_user_t; @@ -143,22 +146,21 @@ typedef struct { riscv_on_ebreak on_ebreak; riscv_on_memset on_memset; riscv_on_memcpy on_memcpy; - /* enable misaligned memory access */ + bool allow_misalign; } riscv_io_t; +/* run emulation */ +void run(riscv_t *rv); + /* create a RISC-V emulator */ -riscv_t *rv_create(const riscv_io_t *io, - riscv_user_t user_data, - int argc, - char **args, - bool output_exit_code); +riscv_t *rv_create(const riscv_io_t *io, riscv_user_t user_data); /* delete a RISC-V emulator */ void rv_delete(riscv_t *rv); /* reset the RISC-V processor */ -void rv_reset(riscv_t *rv, riscv_word_t pc, int argc, char **args); +void rv_reset(riscv_t *rv, riscv_word_t pc); #if RV32_HAS(GDBSTUB) /* Run the RISC-V emulator as gdbstub */ @@ -168,7 +170,19 @@ void rv_debug(riscv_t *rv); /* step the RISC-V emulator */ void rv_step(riscv_t *rv, int32_t cycles); -/* get RISC-V user data bound to an emulator */ +/* + * @rv: RICV-V + * + * get RISC-V user data bound to an emulator + * + * A structure named "vm_attr_t" must first be set up by the user + * before a riscv core can be created. This structure then be passed + * to the "rv_create" function. + * + * The "vm_attr_t" that the user supplied is therefore regarded as user data. + * Thus, "vm_attr_t" will be returned by calling "rv_userdata". + * + */ riscv_user_t rv_userdata(riscv_t *rv); /* set the program counter of a RISC-V emulator */ @@ -180,6 +194,14 @@ riscv_word_t rv_get_pc(riscv_t *rv); /* set a register of the RISC-V emulator */ void rv_set_reg(riscv_t *rv, uint32_t reg, riscv_word_t in); +typedef struct { + int fd; + FILE *file; +} fd_stream_pair_t; + +/* remap stdio stream */ +void rv_remap_stdstream(riscv_t *rv, fd_stream_pair_t *fsp, uint32_t fsp_size); + /* get a register of the RISC-V emulator */ riscv_word_t rv_get_reg(riscv_t *rv, uint32_t reg); @@ -207,25 +229,82 @@ void rv_halt(riscv_t *rv); /* return the halt state */ bool rv_has_halted(riscv_t *rv); -/* return the flag of outputting exit code */ -bool rv_enables_to_output_exit_code(riscv_t *rv); +typedef struct { + char *elf_program; +} vm_user_t; + +typedef struct { + char *kernel_img; + char *dtb; + char *rootfs_img; +} vm_sys_t; + +typedef union { + vm_user_t *vm_user; + vm_sys_t *vm_sys; +} vm_emu_data_t; -/* state structure passed to the runtime */ typedef struct { + /* vm memory object */ memory_t *mem; - /* the data segment break address */ - riscv_word_t break_addr; + /* + * max memory size is 2^32 - 1 bytes + * + * it is for portable on both 32-bit and 64-bit platforms. In this way, + * emulator can access any segment of the memory on either platform. + */ + uint32_t mem_size; - /* file descriptor map: int -> (FILE *) */ - map_t fd_map; -} state_t; + /* vm main stack size */ + uint32_t stack_size; + + /* + * To deal with the RV32 ABI for accessing args list, + * offset of args data have to be saved. + * + * args_offset_size is the memory size to store the offset + */ + uint32_t args_offset_size; + + /* arguments of emulation program */ + int argc; + char **argv; + /* FIXME: rv32emu cannot access envp yet */ + + /* emulation program exit code */ + int exit_code; + + /* emulation program error code */ + int error; + + /* TODO: for logging feature */ + int logging_level; -/* create a state */ -state_t *state_new(void); + /* userspace or system emulation data */ + vm_emu_data_t emu_data; -/* delete a state */ -void state_delete(state_t *s); + /* number of cycle(instruction) in a rv_step call*/ + int cycle_per_step; + + /* allow misaligned memory access */ + bool allow_misalign; + + /* + * default stdin, stdout, stderr, set by rv_create + * + * use rv_remap_stdstream to overwrite them + */ + int stdin_fd; + int stdout_fd; + int stderr_fd; + + /* vm file descriptor map: int -> (FILE *) */ + map_t fd_map; + + /* the data segment break address */ + riscv_word_t break_addr; +} vm_attr_t; void rv_profile(riscv_t *rv, char *out_file_path); diff --git a/src/riscv_private.h b/src/riscv_private.h index 0ec29bd4a..c1d2f9883 100644 --- a/src/riscv_private.h +++ b/src/riscv_private.h @@ -129,8 +129,7 @@ struct riscv_internal { struct mpool *chain_entry_mp; #endif struct mpool *block_mp, *block_ir_mp; - /* print exit code on syscall_exit */ - bool output_exit_code; + void *jit_state; #if RV32_HAS(GDBSTUB) /* gdbstub instance */ diff --git a/src/syscall.c b/src/syscall.c index 8c2e867fa..d4d693218 100644 --- a/src/syscall.c +++ b/src/syscall.c @@ -53,12 +53,12 @@ enum { O_ACCMODE = 3, }; -static int find_free_fd(state_t *s) +static int find_free_fd(vm_attr_t *attr) { for (int i = 3;; ++i) { map_iter_t it; - map_find(s->fd_map, &it, &i); - if (map_at_end(s->fd_map, &it)) + map_find(attr->fd_map, &it, &i); + if (map_at_end(attr->fd_map, &it)) return i; } } @@ -80,7 +80,7 @@ static const char *get_mode_str(uint32_t flags, uint32_t mode UNUSED) static uint8_t tmp[PREALLOC_SIZE]; static void syscall_write(riscv_t *rv) { - state_t *s = rv_userdata(rv); /* access userdata */ + vm_attr_t *attr = rv_userdata(rv); /* access userdata */ /* _write(fde, buffer, count) */ riscv_word_t fd = rv_get_reg(rv, rv_reg_a0); @@ -89,12 +89,12 @@ static void syscall_write(riscv_t *rv) /* lookup the file descriptor */ map_iter_t it; - map_find(s->fd_map, &it, &fd); + map_find(attr->fd_map, &it, &fd); uint32_t total_write = 0; while (count > PREALLOC_SIZE) { - memory_read(s->mem, tmp, buffer + total_write, PREALLOC_SIZE); - if (!map_at_end(s->fd_map, &it)) { + memory_read(attr->mem, tmp, buffer + total_write, PREALLOC_SIZE); + if (!map_at_end(attr->fd_map, &it)) { /* write out the data */ FILE *handle = map_iter_value(&it, FILE *); size_t written = fwrite(tmp, 1, PREALLOC_SIZE, handle); @@ -105,8 +105,8 @@ static void syscall_write(riscv_t *rv) } else goto error_handler; } - memory_read(s->mem, tmp, buffer + total_write, count); - if (!map_at_end(s->fd_map, &it)) { + memory_read(attr->mem, tmp, buffer + total_write, count); + if (!map_at_end(attr->fd_map, &it)) { /* write out the data */ FILE *handle = map_iter_value(&it, FILE *); size_t written = fwrite(tmp, 1, count, handle); @@ -130,11 +130,18 @@ static void syscall_exit(riscv_t *rv) { rv_halt(rv); - /* To avoid mixing with JSON output */ - if (rv_enables_to_output_exit_code(rv)) { - riscv_word_t code = rv_get_reg(rv, rv_reg_a0); - fprintf(stdout, "inferior exit code %d\n", (int) code); - } + /* + * simply halt cpu and save exit code + * + * the application decides the usage of exit code + */ + vm_attr_t *attr = rv_userdata(rv); /* access userdata */ + attr->exit_code = rv_get_reg(rv, rv_reg_a0); + +#if defined(PRINT_EXIT_CODE) + riscv_word_t code = rv_get_reg(rv, rv_reg_a0); + fprintf(stdout, "inferior exit code %d\n", (int) code); +#endif } /* brk(increment) @@ -144,15 +151,15 @@ static void syscall_exit(riscv_t *rv) */ static void syscall_brk(riscv_t *rv) { - state_t *s = rv_userdata(rv); /* access userdata */ + vm_attr_t *attr = rv_userdata(rv); /* access userdata */ /* get the increment parameter */ riscv_word_t increment = rv_get_reg(rv, rv_reg_a0); if (increment) - s->break_addr = increment; + attr->break_addr = increment; /* return new break address */ - rv_set_reg(rv, rv_reg_a0, s->break_addr); + rv_set_reg(rv, rv_reg_a0, attr->break_addr); } static void syscall_gettimeofday(riscv_t *rv) @@ -207,21 +214,21 @@ static void syscall_clock_gettime(riscv_t *rv) static void syscall_close(riscv_t *rv) { - state_t *s = rv_userdata(rv); /* access userdata */ + vm_attr_t *attr = rv_userdata(rv); /* access userdata */ /* _close(fd); */ uint32_t fd = rv_get_reg(rv, rv_reg_a0); if (fd >= 3) { /* lookup the file descriptor */ map_iter_t it; - map_find(s->fd_map, &it, &fd); - if (!map_at_end(s->fd_map, &it)) { + map_find(attr->fd_map, &it, &fd); + if (!map_at_end(attr->fd_map, &it)) { if (fclose(map_iter_value(&it, FILE *))) { /* error */ rv_set_reg(rv, rv_reg_a0, -1); return; } - map_erase(s->fd_map, &it); + map_erase(attr->fd_map, &it); /* success */ rv_set_reg(rv, rv_reg_a0, 0); @@ -238,7 +245,7 @@ static void syscall_close(riscv_t *rv) */ static void syscall_lseek(riscv_t *rv) { - state_t *s = rv_userdata(rv); /* access userdata */ + vm_attr_t *attr = rv_userdata(rv); /* access userdata */ /* _lseek(fd, offset, whence); */ uint32_t fd = rv_get_reg(rv, rv_reg_a0); @@ -247,8 +254,8 @@ static void syscall_lseek(riscv_t *rv) /* find the file descriptor */ map_iter_t it; - map_find(s->fd_map, &it, &fd); - if (map_at_end(s->fd_map, &it)) { + map_find(attr->fd_map, &it, &fd); + if (map_at_end(attr->fd_map, &it)) { /* error */ rv_set_reg(rv, rv_reg_a0, -1); return; @@ -267,7 +274,7 @@ static void syscall_lseek(riscv_t *rv) static void syscall_read(riscv_t *rv) { - state_t *s = rv_userdata(rv); /* access userdata */ + vm_attr_t *attr = rv_userdata(rv); /* access userdata */ /* _read(fd, buf, count); */ uint32_t fd = rv_get_reg(rv, rv_reg_a0); @@ -276,8 +283,8 @@ static void syscall_read(riscv_t *rv) /* lookup the file */ map_iter_t it; - map_find(s->fd_map, &it, &fd); - if (map_at_end(s->fd_map, &it)) { + map_find(attr->fd_map, &it, &fd); + if (map_at_end(attr->fd_map, &it)) { /* error */ rv_set_reg(rv, rv_reg_a0, -1); return; @@ -289,14 +296,14 @@ static void syscall_read(riscv_t *rv) while (count > PREALLOC_SIZE) { size_t r = fread(tmp, 1, PREALLOC_SIZE, handle); - memory_write(s->mem, buf + total_read, tmp, r); + memory_write(attr->mem, buf + total_read, tmp, r); count -= r; total_read += r; if (r != PREALLOC_SIZE) break; } size_t r = fread(tmp, 1, count, handle); - memory_write(s->mem, buf + total_read, tmp, r); + memory_write(attr->mem, buf + total_read, tmp, r); total_read += r; if (total_read != rv_get_reg(rv, rv_reg_a2) && ferror(handle)) { /* error */ @@ -314,7 +321,7 @@ static void syscall_fstat(riscv_t *rv UNUSED) static void syscall_open(riscv_t *rv) { - state_t *s = rv_userdata(rv); /* access userdata */ + vm_attr_t *attr = rv_userdata(rv); /* access userdata */ /* _open(name, flags, mode); */ uint32_t name = rv_get_reg(rv, rv_reg_a0); @@ -323,8 +330,8 @@ static void syscall_open(riscv_t *rv) /* read name from runtime memory */ char name_str[256] = {'\0'}; - uint32_t read = - memory_read_str(s->mem, (uint8_t *) name_str, name, sizeof(name_str)); + uint32_t read = memory_read_str(attr->mem, (uint8_t *) name_str, name, + sizeof(name_str)); if (read > sizeof(name_str)) { rv_set_reg(rv, rv_reg_a0, -1); return; @@ -343,10 +350,10 @@ static void syscall_open(riscv_t *rv) return; } - const int fd = find_free_fd(s); /* find a free file descriptor */ + const int fd = find_free_fd(attr); /* find a free file descriptor */ /* insert into the file descriptor map */ - map_insert(s->fd_map, (void *) &fd, &handle); + map_insert(attr->fd_map, (void *) &fd, &handle); /* return the file descriptor */ rv_set_reg(rv, rv_reg_a0, fd); @@ -376,4 +383,12 @@ void syscall_handler(riscv_t *rv) fprintf(stderr, "unknown syscall %d\n", (int) syscall); break; } + + /* + * save return code + * + * the application decides the usage of the return code + */ + vm_attr_t *attr = rv_userdata(rv); /* access userdata */ + attr->error = rv_get_reg(rv, rv_reg_a0); } diff --git a/src/syscall_sdl.c b/src/syscall_sdl.c index 6bd7e2166..a78ab1959 100644 --- a/src/syscall_sdl.c +++ b/src/syscall_sdl.c @@ -149,10 +149,10 @@ static submission_queue_t submission_queue = { static submission_t submission_pop(riscv_t *rv) { - state_t *s = rv_userdata(rv); + vm_attr_t *attr = rv_userdata(rv); submission_t submission; memory_read( - s->mem, (void *) &submission, + attr->mem, (void *) &submission, submission_queue.base + submission_queue.start * sizeof(submission_t), sizeof(submission_t)); ++submission_queue.start; @@ -162,16 +162,17 @@ static submission_t submission_pop(riscv_t *rv) static void event_push(riscv_t *rv, event_t event) { - state_t *s = rv_userdata(rv); - memory_write(s->mem, event_queue.base + event_queue.end * sizeof(event_t), + vm_attr_t *attr = rv_userdata(rv); + memory_write(attr->mem, + event_queue.base + event_queue.end * sizeof(event_t), (void *) &event, sizeof(event_t)); ++event_queue.end; event_queue.end &= queues_capacity - 1; uint32_t count; - memory_read(s->mem, (void *) &count, event_count, sizeof(uint32_t)); + memory_read(attr->mem, (void *) &count, event_count, sizeof(uint32_t)); count += 1; - memory_write(s->mem, event_count, (void *) &count, sizeof(uint32_t)); + memory_write(attr->mem, event_count, (void *) &count, sizeof(uint32_t)); } static inline uint32_t round_pow2(uint32_t x) @@ -279,7 +280,7 @@ static bool check_sdl(riscv_t *rv, int width, int height) void syscall_draw_frame(riscv_t *rv) { - state_t *s = rv_userdata(rv); /* access userdata */ + vm_attr_t *attr = rv_userdata(rv); /* access userdata */ /* draw_frame(base, width, height) */ const uint32_t screen = rv_get_reg(rv, rv_reg_a0); @@ -294,7 +295,7 @@ void syscall_draw_frame(riscv_t *rv) void *pixels_ptr; if (SDL_LockTexture(texture, NULL, &pixels_ptr, &pitch)) exit(-1); - memory_read(s->mem, pixels_ptr, screen, width * height * 4); + memory_read(attr->mem, pixels_ptr, screen, width * height * 4); SDL_UnlockTexture(texture); int actual_width, actual_height; @@ -677,13 +678,14 @@ static void *music_handler(void *arg) static void play_sfx(riscv_t *rv) { - state_t *s = rv_userdata(rv); /* access userdata */ + vm_attr_t *attr = rv_userdata(rv); /* access userdata */ const uint32_t sfxinfo_addr = (uint32_t) rv_get_reg(rv, rv_reg_a1); int volume = rv_get_reg(rv, rv_reg_a2); sfxinfo_t sfxinfo; - memory_read(s->mem, (uint8_t *) &sfxinfo, sfxinfo_addr, sizeof(sfxinfo_t)); + memory_read(attr->mem, (uint8_t *) &sfxinfo, sfxinfo_addr, + sizeof(sfxinfo_t)); /* The data and size in the application must be positioned in the first two * fields of the structure. This ensures emulator compatibility with @@ -692,7 +694,7 @@ static void play_sfx(riscv_t *rv) uint32_t sfx_data_offset = *((uint32_t *) &sfxinfo); uint32_t sfx_data_size = *(uint32_t *) ((uint32_t *) &sfxinfo + 1); uint8_t sfx_data[SFX_SAMPLE_SIZE]; - memory_read(s->mem, sfx_data, sfx_data_offset, + memory_read(attr->mem, sfx_data, sfx_data_offset, sizeof(uint8_t) * sfx_data_size); sound_t sfx = { @@ -705,14 +707,14 @@ static void play_sfx(riscv_t *rv) static void play_music(riscv_t *rv) { - state_t *s = rv_userdata(rv); /* access userdata */ + vm_attr_t *attr = rv_userdata(rv); /* access userdata */ const uint32_t musicinfo_addr = (uint32_t) rv_get_reg(rv, rv_reg_a1); int volume = rv_get_reg(rv, rv_reg_a2); int looping = rv_get_reg(rv, rv_reg_a3); musicinfo_t musicinfo; - memory_read(s->mem, (uint8_t *) &musicinfo, musicinfo_addr, + memory_read(attr->mem, (uint8_t *) &musicinfo, musicinfo_addr, sizeof(musicinfo_t)); /* The data and size in the application must be positioned in the first two @@ -722,7 +724,7 @@ static void play_music(riscv_t *rv) uint32_t music_data_offset = *((uint32_t *) &musicinfo); uint32_t music_data_size = *(uint32_t *) ((uint32_t *) &musicinfo + 1); uint8_t music_data[MUSIC_MAX_SIZE]; - memory_read(s->mem, music_data, music_data_offset, music_data_size); + memory_read(attr->mem, music_data, music_data_offset, music_data_size); sound_t music = { .data = music_data, diff --git a/tools/gen-jit-template.py b/tools/gen-jit-template.py index 02c55f586..5199525b7 100755 --- a/tools/gen-jit-template.py +++ b/tools/gen-jit-template.py @@ -219,7 +219,7 @@ def parse_argv(EXT_LIST, SKIP_LIST): elif items[0] == "jmp_off": asm = "emit_jump_target_offset(state, JUMP_LOC, state->offset);" elif items[0] == "mem": - asm = "memory_t *m = ((state_t *) rv->userdata)->mem;" + asm = "memory_t *m = PRIV(rv)->mem;" elif items[0] == "call": asm = "emit_call(state, (intptr_t) rv->io.on_{});".format( items[1])