Skip to content

Commit

Permalink
Refine the API in the public header
Browse files Browse the repository at this point in the history
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.

The vm_attr_t has multiple fields and they are commented clearly
in the code. As you can see in "main", there are various mode to run the
emulator such as "run_and_trace", "gdbstub", and "profiling". Thus,
a field call "run_flag" is introduced in vm_attr_t.

For standard stream remapping, rv_remap_stdstream function is
introduced. The emulator can remap default standard stream to required
streams after creating the emulator by calling the rv_remap_stdstream
function.

rv_userdata has been dropped since PRIV macro is sufficient for
internal implemntation. Also, application will not need to direct
access it.

elf is reopened in dump_test_signature because elf is allocated during
rv_create. It is acceptable to reopen elf since it is only for testing.

Print inferior exit code to console inside main instead of syscall_exit
because the actual usage of exit code depends on applications of using
riscv public API.

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. A validator for validating the user-defined vm_attr_t might need to
be introduced.

related: sysprog21#310
  • Loading branch information
ChinYikMing committed Feb 2, 2024
1 parent 176e121 commit fcfebe1
Show file tree
Hide file tree
Showing 14 changed files with 407 additions and 230 deletions.
7 changes: 1 addition & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
6 changes: 1 addition & 5 deletions src/elf.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
2 changes: 1 addition & 1 deletion src/elf.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
6 changes: 3 additions & 3 deletions src/emulate.c
Original file line number Diff line number Diff line change
Expand Up @@ -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; \
Expand Down Expand Up @@ -1182,15 +1182,15 @@ 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;
}

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;
Expand Down
24 changes: 8 additions & 16 deletions src/io.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/io.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
4 changes: 2 additions & 2 deletions src/jit.c
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand All @@ -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],
Expand Down
102 changes: 36 additions & 66 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -74,35 +74,10 @@ IO_HANDLER_IMPL(byte, write_b, W)
#undef R
#undef W

/* run: printing out an instruction trace */
static void run_and_trace(riscv_t *rv, elf_t *elf)
{
const uint32_t cycles_per_step = 1;

for (; !rv_has_halted(rv);) { /* run until the flag is done */
/* trace execution */
uint32_t pc = rv_get_pc(rv);
const char *sym = elf_find_symbol(elf, pc);
printf("%08x %s\n", pc, (sym ? sym : ""));

/* 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);
}
}

static void print_usage(const char *filename)
{
fprintf(stderr,
"RV32I[MA] Emulator which loads an ELF file to execute.\n"
"RV32I[MACF] Emulator which loads an ELF file to execute.\n"
"Usage: %s [options] [filename] [arguments]\n"
"Options:\n"
" -t : print executable trace\n"
Expand Down Expand Up @@ -188,8 +163,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");
Expand All @@ -212,21 +190,42 @@ 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)) {
print_usage(args[0]);
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;
}
int run_flag = 0;
run_flag |= opt_trace;
#if RV32_HAS(GDBSTUB)
run_flag |= opt_gdbstub << 1;
#endif
run_flag |= opt_prof_data << 2;

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,
.run_flag = run_flag,
.profile_output_file = prof_out_file,
.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 = {
Expand All @@ -246,57 +245,28 @@ int main(int argc, char **args)
.on_ebreak = ebreak_handler,
.on_memcpy = memcpy_handler,
.on_memset = memset_handler,
.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);
}
#if RV32_HAS(GDBSTUB)
else if (opt_gdbstub) {
rv_debug(rv);
}
#endif
else {
run(rv);
}
rv_run(rv);

/* dump registers as JSON */
if (opt_dump_regs)
dump_registers(rv, registers_out_file);

/* dump test result in test mode */
if (opt_arch_test)
dump_test_signature(elf);
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);

printf("inferior exit code %d\n", attr.exit_code);
return 0;
}
Loading

0 comments on commit fcfebe1

Please sign in to comment.