From c917083554d4aa61c5778658df351537712e3100 Mon Sep 17 00:00:00 2001 From: xinyangli Date: Wed, 13 Mar 2024 16:54:00 +0800 Subject: [PATCH 01/10] pa2.2: add ITRACE buffer --- nemu/Kconfig | 4 ++++ nemu/include/cpu/decode.h | 1 - nemu/include/debug.h | 1 + nemu/include/utils.h | 3 +++ nemu/src/cpu/cpu-exec.c | 23 +++++++++++++++-------- nemu/src/isa/riscv32/inst.c | 2 +- nemu/src/utils/log.c | 15 +++++++++++++++ 7 files changed, 39 insertions(+), 10 deletions(-) diff --git a/nemu/Kconfig b/nemu/Kconfig index a1ed68e..20f7705 100644 --- a/nemu/Kconfig +++ b/nemu/Kconfig @@ -151,6 +151,10 @@ config ITRACE_COND string "Only trace instructions when the condition is true" default "true" +config ITRACE_BUFFER + depends on ITRACE + int "Buffer size for intruction trace (unit: number of instructions)" + default 10 config DIFFTEST depends on TARGET_NATIVE_ELF diff --git a/nemu/include/cpu/decode.h b/nemu/include/cpu/decode.h index 915bcf2..a17c888 100644 --- a/nemu/include/cpu/decode.h +++ b/nemu/include/cpu/decode.h @@ -23,7 +23,6 @@ typedef struct Decode { vaddr_t snpc; // static next pc vaddr_t dnpc; // dynamic next pc ISADecodeInfo isa; - IFDEF(CONFIG_ITRACE, char logbuf[128]); } Decode; // --- pattern matching mechanism --- diff --git a/nemu/include/debug.h b/nemu/include/debug.h index 087da4d..df88556 100644 --- a/nemu/include/debug.h +++ b/nemu/include/debug.h @@ -38,6 +38,7 @@ MUXDEF(CONFIG_TARGET_AM, printf(ANSI_FMT(format, ANSI_FG_RED) "\n", ## __VA_ARGS__), \ (fflush(stdout), fprintf(stderr, ANSI_FMT(format, ANSI_FG_RED) "\n", ## __VA_ARGS__))); \ IFNDEF(CONFIG_TARGET_AM, extern FILE* log_fp; fflush(log_fp)); \ + IFDEF(CONFIG_ITRACE, log_itrace_print()); \ extern void assert_fail_msg(); \ assert_fail_msg(); \ assert(cond); \ diff --git a/nemu/include/utils.h b/nemu/include/utils.h index 2cd1561..59bc9df 100644 --- a/nemu/include/utils.h +++ b/nemu/include/utils.h @@ -74,4 +74,7 @@ uint64_t get_time(); } while (0) +IFDEF(CONFIG_ITRACE, void log_itrace_print()); + + #endif diff --git a/nemu/src/cpu/cpu-exec.c b/nemu/src/cpu/cpu-exec.c index 1f2940f..72e1265 100644 --- a/nemu/src/cpu/cpu-exec.c +++ b/nemu/src/cpu/cpu-exec.c @@ -13,6 +13,7 @@ * See the Mulan PSL v2 for more details. ***************************************************************************************/ +#include "utils.h" #include #include #include @@ -29,15 +30,17 @@ CPU_state cpu = {}; uint64_t g_nr_guest_inst = 0; static uint64_t g_timer = 0; // unit: us static bool g_print_step = false; +IFDEF(CONFIG_ITRACE, extern char logbuf[CONFIG_ITRACE_BUFFER][128]); +IFDEF(CONFIG_ITRACE, extern int logbuf_rear); void device_update(); bool wp_eval_all(); static void trace_and_difftest(Decode *_this, vaddr_t dnpc) { #ifdef CONFIG_ITRACE_COND - if (ITRACE_COND) { log_write("%s\n", _this->logbuf); } + if (ITRACE_COND) { log_write("%s\n", logbuf[logbuf_rear]); } #endif - if (g_print_step) { IFDEF(CONFIG_ITRACE, puts(_this->logbuf)); } + if (g_print_step) { IFDEF(CONFIG_ITRACE, puts(logbuf[logbuf_rear])); } IFDEF(CONFIG_DIFFTEST, difftest_step(_this->pc, dnpc)); } @@ -47,8 +50,9 @@ static void exec_once(Decode *s, vaddr_t pc) { isa_exec_once(s); cpu.pc = s->dnpc; #ifdef CONFIG_ITRACE - char *p = s->logbuf; - p += snprintf(p, sizeof(s->logbuf), FMT_WORD ":", s->pc); + logbuf_rear = (logbuf_rear + 1) % CONFIG_ITRACE_BUFFER; + char *p = logbuf[logbuf_rear]; + p += snprintf(p, sizeof(logbuf), FMT_WORD ":", s->pc); int ilen = s->snpc - s->pc; int i; uint8_t *inst = (uint8_t *)&s->isa.inst.val; @@ -64,7 +68,7 @@ static void exec_once(Decode *s, vaddr_t pc) { #ifndef CONFIG_ISA_loongarch32r void disassemble(char *str, int size, uint64_t pc, uint8_t *code, int nbyte); - disassemble(p, s->logbuf + sizeof(s->logbuf) - p, + disassemble(p, logbuf[logbuf_rear] + sizeof(logbuf[logbuf_rear]) - p, MUXDEF(CONFIG_ISA_x86, s->snpc, s->pc), (uint8_t *)&s->isa.inst.val, ilen); #else p[0] = '\0'; // the upstream llvm does not support loongarch32r @@ -79,7 +83,7 @@ static void execute(uint64_t n) { g_nr_guest_inst ++; trace_and_difftest(&s, cpu.pc); if (wp_eval_all()) { - puts(s.logbuf); + puts(logbuf[logbuf_rear]); break; } if (nemu_state.state != NEMU_RUNNING) break; @@ -121,13 +125,16 @@ void cpu_exec(uint64_t n) { switch (nemu_state.state) { case NEMU_RUNNING: nemu_state.state = NEMU_STOP; break; - case NEMU_END: case NEMU_ABORT: + case NEMU_END: case NEMU_ABORT: { Log("nemu: %s at pc = " FMT_WORD, (nemu_state.state == NEMU_ABORT ? ANSI_FMT("ABORT", ANSI_FG_RED) : (nemu_state.halt_ret == 0 ? ANSI_FMT("HIT GOOD TRAP", ANSI_FG_GREEN) : ANSI_FMT("HIT BAD TRAP", ANSI_FG_RED))), nemu_state.halt_pc); - // fall through + if(nemu_state.halt_ret != 0) { + log_itrace_print(); + } + } // fall through case NEMU_QUIT: statistic(); } } diff --git a/nemu/src/isa/riscv32/inst.c b/nemu/src/isa/riscv32/inst.c index 9c86937..b7aeac5 100644 --- a/nemu/src/isa/riscv32/inst.c +++ b/nemu/src/isa/riscv32/inst.c @@ -54,7 +54,7 @@ static void decode_operand(Decode *s, int *rd, word_t *src1, word_t *src2, static void do_branch(Decode *s, bool condition, word_t offset) { if (condition) { - puts(s->logbuf); + // puts(s->logbuf[s->logbuf_rear]); s->dnpc = s->pc + offset; } } diff --git a/nemu/src/utils/log.c b/nemu/src/utils/log.c index a9bb9a7..7939d42 100644 --- a/nemu/src/utils/log.c +++ b/nemu/src/utils/log.c @@ -35,3 +35,18 @@ bool log_enable() { (g_nr_guest_inst <= CONFIG_TRACE_END), false); } #endif + +IFDEF(CONFIG_ITRACE, char logbuf[CONFIG_ITRACE_BUFFER][128]); +IFDEF(CONFIG_ITRACE, int logbuf_rear); + +#ifdef CONFIG_ITRACE +void log_itrace_print() { + puts("ITRACE buffer:"); + for (int i = (logbuf_rear + 1) % CONFIG_ITRACE_BUFFER; i != logbuf_rear; i = (i + 1) % CONFIG_ITRACE_BUFFER) { + if (logbuf[i][0] == '\0') continue; + puts(logbuf[i]); + } + puts("Current command:"); + puts(logbuf[logbuf_rear]); +} +#endif From 0f7c6fd508ac81de10b99b276d23371c72903008 Mon Sep 17 00:00:00 2001 From: xinyangli Date: Wed, 13 Mar 2024 18:14:17 +0800 Subject: [PATCH 02/10] pa2.2: add memory tracer --- nemu/Kconfig | 18 ++++++++++++++++ nemu/include/debug.h | 3 +++ nemu/src/memory/paddr.c | 48 ++++++++++++++++++++++++++++++++++++++--- 3 files changed, 66 insertions(+), 3 deletions(-) diff --git a/nemu/Kconfig b/nemu/Kconfig index 20f7705..9243aba 100644 --- a/nemu/Kconfig +++ b/nemu/Kconfig @@ -156,6 +156,24 @@ config ITRACE_BUFFER int "Buffer size for intruction trace (unit: number of instructions)" default 10 +config MTRACE + depends on TRACE && TARGET_NATIVE_ELF && ENGINE_INTERPRETER + bool "Enable memory tracer" + + +config MTRACE_RANGE + depends on MTRACE + string "Memory trace active range" + default "0x0-0xfffffff" + help + Memory tracer will only print memory access in these ranges. + Use comma to seperate between ranges. + +config MTRACE_RANGE_MAX + depends on MTRACE + int "Max range count in MTRACE_RANGE" + default 10 + config DIFFTEST depends on TARGET_NATIVE_ELF bool "Enable differential testing" diff --git a/nemu/include/debug.h b/nemu/include/debug.h index df88556..057f8bf 100644 --- a/nemu/include/debug.h +++ b/nemu/include/debug.h @@ -20,6 +20,9 @@ #include #include +#define Trace(format, ...) \ + _Log("[TRACE] " format "\n", ## __VA_ARGS__) + #define Log(format, ...) \ _Log(ANSI_FMT("[INFO] %s:%d %s() ", ANSI_FG_BLUE) format "\n", \ __FILE__, __LINE__, __func__, ## __VA_ARGS__) diff --git a/nemu/src/memory/paddr.c b/nemu/src/memory/paddr.c index ee30e70..437debd 100644 --- a/nemu/src/memory/paddr.c +++ b/nemu/src/memory/paddr.c @@ -13,6 +13,8 @@ * See the Mulan PSL v2 for more details. ***************************************************************************************/ +#include "common.h" +#include "debug.h" #include #include #include @@ -23,6 +25,11 @@ static uint8_t *pmem = NULL; #else // CONFIG_PMEM_GARRAY static uint8_t pmem[CONFIG_MSIZE] PG_ALIGN = {}; #endif +#ifdef CONFIG_MTRACE +static word_t mtrace_start[CONFIG_MTRACE_RANGE_MAX] = {0}; +static word_t mtrace_end[CONFIG_MTRACE_RANGE_MAX] = {0}; +static int range_count = 0; +#endif uint8_t* guest_to_host(paddr_t paddr) { return pmem + paddr - CONFIG_MBASE; } paddr_t host_to_guest(uint8_t *haddr) { return haddr - pmem + CONFIG_MBASE; } @@ -41,23 +48,58 @@ static void out_of_bound(paddr_t addr) { addr, PMEM_LEFT, PMEM_RIGHT, cpu.pc); } +#ifdef CONFIG_MTRACE +static void mtrace_print(char type, word_t addr, int len, word_t data) { + for (int i = 0; i < range_count; i++) + if (addr <= mtrace_end[i] && addr >= mtrace_start[i] ) { + Trace("Mem %c " FMT_PADDR "%d D " FMT_PADDR, type, addr, len, data); + break; + } +} +#endif + void init_mem() { #if defined(CONFIG_PMEM_MALLOC) pmem = malloc(CONFIG_MSIZE); assert(pmem); +#endif +#ifdef CONFIG_MTRACE + char range[sizeof(CONFIG_MTRACE_RANGE)] = CONFIG_MTRACE_RANGE; + char *saveptr, *ptr; + ptr = strtok_r(range, ",", &saveptr); + for (range_count = 0; range_count < CONFIG_MTRACE_RANGE_MAX; ) { + word_t start, end; + Assert(sscanf(ptr, FMT_PADDR "-" FMT_PADDR, &start, &end) == 2, "Config option MTRACE_RANGE has wrong format"); + mtrace_start[range_count] = start; + mtrace_end[range_count] = end; + + range_count++; + ptr = strtok_r(NULL, ",", &saveptr); + if (!ptr) break; + } + Trace("MTRACE ranges: "); + for (int i = 0; i < range_count; i++) { + Trace("[0x%x, 0x%x]", mtrace_start[i], mtrace_end[i]); + } #endif IFDEF(CONFIG_MEM_RANDOM, memset(pmem, rand(), CONFIG_MSIZE)); Log("physical memory area [" FMT_PADDR ", " FMT_PADDR "]", PMEM_LEFT, PMEM_RIGHT); } word_t paddr_read(paddr_t addr, int len) { - if (likely(in_pmem(addr))) return pmem_read(addr, len); - IFDEF(CONFIG_DEVICE, return mmio_read(addr, len)); + word_t result = 0; + if (likely(in_pmem(addr))) { result = pmem_read(addr, len); goto mtrace;} + IFDEF(CONFIG_DEVICE, result = mmio_read(addr, len); goto mtrace) out_of_bound(addr); - return 0; + +mtrace: + IFDEF(CONFIG_MTRACE, mtrace_print('R', addr, len, result)); + + return result; } void paddr_write(paddr_t addr, int len, word_t data) { + IFDEF(CONFIG_MTRACE, mtrace_print('W', addr, len, data)); if (likely(in_pmem(addr))) { pmem_write(addr, len, data); return; } IFDEF(CONFIG_DEVICE, mmio_write(addr, len, data); return); out_of_bound(addr); From 9229e4318ea17018d6cc8ba01716e26955e77365 Mon Sep 17 00:00:00 2001 From: xinyangli Date: Wed, 20 Mar 2024 19:46:54 +0800 Subject: [PATCH 03/10] pa2.2: add ftrace --- flake.nix | 1 + nemu/.result.tmp | 0 nemu/Kconfig | 24 ++++++- nemu/include/ftrace.h | 18 ++++++ nemu/include/macro.h | 2 + nemu/src/isa/riscv32/inst.c | 15 ++++- nemu/src/monitor/monitor.c | 10 +++ nemu/src/utils/ftrace.c | 125 ++++++++++++++++++++++++++++++++++++ 8 files changed, 190 insertions(+), 5 deletions(-) create mode 100644 nemu/.result.tmp create mode 100644 nemu/include/ftrace.h create mode 100644 nemu/src/utils/ftrace.c diff --git a/flake.nix b/flake.nix index 6656094..4492e28 100644 --- a/flake.nix +++ b/flake.nix @@ -65,6 +65,7 @@ devShells.nemu = pkgs.mkShell { packages = with pkgs; [ clang-tools + gdb ]; inputsFrom = [ self.packages.${system}.nemu diff --git a/nemu/.result.tmp b/nemu/.result.tmp new file mode 100644 index 0000000..e69de29 diff --git a/nemu/Kconfig b/nemu/Kconfig index 9243aba..ae1921f 100644 --- a/nemu/Kconfig +++ b/nemu/Kconfig @@ -143,8 +143,11 @@ config TRACE_END config ITRACE depends on TRACE && TARGET_NATIVE_ELF && ENGINE_INTERPRETER - bool "Enable instruction tracer" + bool "Enable instruction tracing" default y + help + Instraction tracing will log past instructions into a ring buffer + and print them when NEMU exit unexpectedly. config ITRACE_COND depends on ITRACE @@ -158,8 +161,8 @@ config ITRACE_BUFFER config MTRACE depends on TRACE && TARGET_NATIVE_ELF && ENGINE_INTERPRETER - bool "Enable memory tracer" - + bool "Enable memory tracing" + default n config MTRACE_RANGE depends on MTRACE @@ -174,6 +177,21 @@ config MTRACE_RANGE_MAX int "Max range count in MTRACE_RANGE" default 10 +config FTRACE + depends on TRACE && TARGET_NATIVE_ELF && ENGINE_INTERPRETER + bool "Enable function tracing" + default y + +config FTRACE_STACK_SIZE + depends on FTRACE + int "Max function track stack size" + default 100 + +config FTRACE_LOG + depends on FTRACE + bool "Print log when entering a funciton" + default n + config DIFFTEST depends on TARGET_NATIVE_ELF bool "Enable differential testing" diff --git a/nemu/include/ftrace.h b/nemu/include/ftrace.h new file mode 100644 index 0000000..9fcf28a --- /dev/null +++ b/nemu/include/ftrace.h @@ -0,0 +1,18 @@ +#ifndef __FUNC_DEF_H__ +#define __FUNC_DEF_H__ +#include + +#ifdef CONFIG_FTRACE +typedef struct { + vaddr_t start; + vaddr_t len; + char * name; +} func_t; + +extern func_t *func_table; +void ftrace_call(vaddr_t, vaddr_t); +void ftrace_return(vaddr_t, vaddr_t); +// const char *get_func_name(vaddr_t addr); +#endif + +#endif \ No newline at end of file diff --git a/nemu/include/macro.h b/nemu/include/macro.h index 8aa38f8..47f11b0 100644 --- a/nemu/include/macro.h +++ b/nemu/include/macro.h @@ -92,6 +92,8 @@ #define PG_ALIGN __attribute((aligned(4096))) +#define FAILED_GOTO(tag, exp) do {if((exp)) goto tag;} while(0) + #if !defined(likely) #define likely(cond) __builtin_expect(cond, 1) #define unlikely(cond) __builtin_expect(cond, 0) diff --git a/nemu/src/isa/riscv32/inst.c b/nemu/src/isa/riscv32/inst.c index b7aeac5..54b9044 100644 --- a/nemu/src/isa/riscv32/inst.c +++ b/nemu/src/isa/riscv32/inst.c @@ -18,6 +18,7 @@ #include #include #include +#include #define R(i) gpr(i) #define Mr vaddr_read @@ -59,6 +60,16 @@ static void do_branch(Decode *s, bool condition, word_t offset) { } } +static void ftrace(Decode *s, int rd, vaddr_t dst) { + uint32_t i = s->isa.inst.val; + int rs1 = BITS(i, 19, 15); + if(rs1 == 1 && rd == 0) { + ftrace_return(s->pc, dst); + } else { + ftrace_call(s->pc, dst); + } +} + static int decode_exec(Decode *s) { int rd = 0; word_t src1 = 0, src2 = 0, imm = 0; @@ -74,8 +85,8 @@ static int decode_exec(Decode *s) { INSTPAT("??????? ????? ????? ??? ????? 01101 11", lui , U, R(rd) = imm); INSTPAT("??????? ????? ????? ??? ????? 00101 11", auipc , U, R(rd) = s->pc + imm); - INSTPAT("??????? ????? ????? ??? ????? 11011 11", jal , J, do {s->dnpc = s->pc + imm; R(rd) = s->pc + 4; } while(0)); - INSTPAT("??????? ????? ????? ??? ????? 11001 11", jalr , I, do {s->dnpc = src1 + imm; R(rd) = s->pc + 4; } while(0)); + INSTPAT("??????? ????? ????? ??? ????? 11011 11", jal , J, do {s->dnpc = s->pc + imm; R(rd) = s->pc + 4; ftrace_call(s->pc, s->pc + imm); } while(0)); + INSTPAT("??????? ????? ????? ??? ????? 11001 11", jalr , I, do {s->dnpc = src1 + imm; R(rd) = s->pc + 4; ftrace(s, rd, src1 + imm); } while(0)); INSTPAT("??????? ????? ????? 000 ????? 11000 11", beq , B, do_branch(s, src1 == src2, imm)); INSTPAT("??????? ????? ????? 001 ????? 11000 11", bne , B, do_branch(s, src1 != src2, imm)); INSTPAT("??????? ????? ????? 100 ????? 11000 11", blt , B, do_branch(s, (sword_t)src1 < (sword_t)src2, imm)); diff --git a/nemu/src/monitor/monitor.c b/nemu/src/monitor/monitor.c index 2279ca0..1920ae4 100644 --- a/nemu/src/monitor/monitor.c +++ b/nemu/src/monitor/monitor.c @@ -40,6 +40,7 @@ static void welcome() { void sdb_set_batch_mode(); static char *log_file = NULL; +static char *elf_file = NULL; static char *diff_so_file = NULL; static char *img_file = NULL; static int difftest_port = 1234; @@ -72,6 +73,7 @@ static int parse_args(int argc, char *argv[]) { {"log" , required_argument, NULL, 'l'}, {"diff" , required_argument, NULL, 'd'}, {"port" , required_argument, NULL, 'p'}, + {"elf" , required_argument, NULL, 'f'}, {"help" , no_argument , NULL, 'h'}, {0 , 0 , NULL, 0 }, }; @@ -82,6 +84,7 @@ static int parse_args(int argc, char *argv[]) { case 'p': sscanf(optarg, "%d", &difftest_port); break; case 'l': log_file = optarg; break; case 'd': diff_so_file = optarg; break; + case 'f': elf_file = optarg; break; case 1: img_file = optarg; return 0; default: printf("Usage: %s [OPTION...] IMAGE [args]\n\n", argv[0]); @@ -89,6 +92,7 @@ static int parse_args(int argc, char *argv[]) { printf("\t-l,--log=FILE output log to FILE\n"); printf("\t-d,--diff=REF_SO run DiffTest with reference REF_SO\n"); printf("\t-p,--port=PORT run DiffTest with port PORT\n"); + printf("\t-f,--elf=FILE elf file with debug info\n"); printf("\n"); exit(0); } @@ -126,6 +130,12 @@ void init_monitor(int argc, char *argv[]) { /* Initialize the simple debugger. */ init_sdb(); + // printf("elf_file: %s\n", elf_file); + if(elf_file != NULL) { + void init_elf(const char *path); + init_elf(elf_file); + } + #ifndef CONFIG_ISA_loongarch32r IFDEF(CONFIG_ITRACE, init_disasm( MUXDEF(CONFIG_ISA_x86, "i686", diff --git a/nemu/src/utils/ftrace.c b/nemu/src/utils/ftrace.c new file mode 100644 index 0000000..25f097a --- /dev/null +++ b/nemu/src/utils/ftrace.c @@ -0,0 +1,125 @@ +#include "debug.h" +#include "macro.h" +#include +#include +#include +#include + +// Put this into another file +#ifdef CONFIG_FTRACE +static vaddr_t ftrace_stack[CONFIG_FTRACE_STACK_SIZE] = {0}; +static vaddr_t ftrace_stack_len = 0; +func_t *func_table = NULL; +int func_table_len = 0, func_table_size = 8; +#endif + +static int cmp_func_t(const void *a, const void *b) { + return ((func_t *)a)->start > ((func_t *)b)->start; +} + +static func_t *get_func(vaddr_t addr) { + int l = 0, r = func_table_len - 1; + while(l <= r) { + int mid = (l + r) / 2; + if(func_table[mid].start <= addr) l = mid + 1; + else r = mid - 1; + } + return l == 0 ? NULL : &func_table[l - 1]; +} + +void init_elf(const char *path) { + FILE *elf_file = fopen(path, "rb"); + Elf32_Ehdr header; + Elf32_Shdr section_header[200], *psh; + + func_table = (func_t *)calloc(func_table_size, sizeof(func_t)); + assert(func_table); + + FAILED_GOTO(failed_header, fread(&header, sizeof(Elf32_Ehdr), 1, elf_file) <= 0); + FAILED_GOTO(failed_header, fseek(elf_file, header.e_shoff, SEEK_SET) != 0); + FAILED_GOTO(failed_header, fread(section_header, header.e_shentsize, header.e_shnum, elf_file) <= 0); + + char *shstrtab = calloc(1, section_header[header.e_shstrndx].sh_size); + FAILED_GOTO(failed_shstrtab, fseek(elf_file, section_header[header.e_shstrndx].sh_offset, SEEK_SET) != 0); + FAILED_GOTO(failed_shstrtab, fread(shstrtab, section_header[header.e_shstrndx].sh_size, 1, elf_file) <= 0); + + Elf32_Shdr *symtab = NULL, *strtab = NULL; + for(int i = 0; i < header.e_shnum; i++) { + psh = section_header + i; + if (psh->sh_type == SHT_SYMTAB) { + symtab = psh; + } else if (psh->sh_type == SHT_STRTAB && strncmp(shstrtab + psh->sh_name, ".strtab", 8) == 0) { + strtab = psh; + } + } + + int sym_length = symtab->sh_size / sizeof(Elf32_Sym); + Elf32_Sym *sym = calloc(sym_length, sizeof(Elf32_Sym)); + assert(sym); + FAILED_GOTO(failed_funcname, fseek(elf_file, symtab->sh_offset, SEEK_SET) != 0); + FAILED_GOTO(failed_funcname, fread(sym, sizeof(Elf32_Sym), sym_length, elf_file) <= 0); + + for(int j = 0; j < sym_length; j++) { + if(ELF32_ST_TYPE(sym[j].st_info) != STT_FUNC) continue; + // Only read function type symbol + func_t *f = &func_table[func_table_len]; + char *func = (char *)malloc(30); + FAILED_GOTO(failed_funcname, fseek(elf_file, strtab->sh_offset + sym[j].st_name, SEEK_SET) != 0); + FAILED_GOTO(failed_funcname, fgets(func, 30, elf_file) <= 0); + f->start = sym[j].st_value; + f->len = sym[j].st_size; + f->name = func; + ++func_table_len; + if(func_table_len >= func_table_size) { + Assert(func_table_size * 2 > func_table_size, "Function table exceed memory limit"); + func_table_size *= 2; + func_table = realloc(func_table, func_table_size * sizeof(func_t)); + Assert(func_table, "Function table exceed memory limit"); + } + } + qsort(func_table, func_table_len, sizeof(func_t), cmp_func_t); + goto success; + +success: + free(sym); + free(shstrtab); + return; + +failed_funcname: + free(sym); +failed_shstrtab: + free(shstrtab); +failed_header: + for(int i = 0; i < func_table_len; i++) { + func_t *f = &func_table[i]; + if(f->name) { free(f->name); } + } + free(func_table); + Error("Failed reading elf file"); + return; +} + +void ftrace_call(vaddr_t pc, vaddr_t addr) { + func_t *f = get_func(addr); + Assert(ftrace_stack_len < CONFIG_FTRACE_STACK_SIZE, + "Ftrace stack exceed size limit, consider turn off ftrace or increase " + "FTRACE_STACK_SIZE."); + ftrace_stack[ftrace_stack_len] = pc + 4; + Trace("%*s0x%x call 0x%x <%s+0x%x>", ftrace_stack_len, "", pc, addr, + f == NULL ? "???" : f->name, addr - f->start); + ftrace_stack_len++; +} + +void ftrace_return(vaddr_t pc, vaddr_t addr) { + --ftrace_stack_len; + for (; addr != ftrace_stack[ftrace_stack_len] && ftrace_stack_len >= 0; + ftrace_stack_len--) { + vaddr_t tco_addr = ftrace_stack[ftrace_stack_len]; + func_t *f = get_func(tco_addr); + Trace("%*s0x%x ret 0x%x <%s+0x%x> (TCO)", ftrace_stack_len, "", pc, tco_addr, + f == NULL ? "???" : f->name, tco_addr - f->start); + } + func_t *f = get_func(addr); + Trace("%*s0x%x ret 0x%x <%s+0x%x>", ftrace_stack_len, "", pc, addr, + f == NULL ? "???" : f->name, addr - f->start); +} From a62a132587b8afd8c7e8c14ae6ca9d59be213289 Mon Sep 17 00:00:00 2001 From: xinyangli Date: Wed, 20 Mar 2024 20:07:28 +0800 Subject: [PATCH 04/10] pa2.2: cleanup includes --- nemu/include/common.h | 19 +------------------ nemu/include/debug.h | 3 ++- nemu/include/types.h | 21 +++++++++++++++++++++ nemu/include/utils.h | 5 +---- nemu/src/cpu/cpu-exec.c | 2 +- nemu/src/isa/riscv32/inst.c | 3 ++- nemu/src/monitor/monitor.c | 1 + nemu/src/monitor/sdb/addrexp.y | 1 + nemu/src/utils/ftrace.c | 3 +-- nemu/src/utils/log.c | 1 + 10 files changed, 32 insertions(+), 27 deletions(-) create mode 100644 nemu/include/types.h diff --git a/nemu/include/common.h b/nemu/include/common.h index fbffaa5..08a46e5 100644 --- a/nemu/include/common.h +++ b/nemu/include/common.h @@ -17,12 +17,12 @@ #define __COMMON_H__ #include -#include #include #include #include #include +#include #ifdef CONFIG_TARGET_AM #include @@ -31,23 +31,6 @@ #include #endif -#if CONFIG_MBASE + CONFIG_MSIZE > 0x100000000ul -#define PMEM64 1 -#endif - -typedef MUXDEF(CONFIG_ISA64, uint64_t, uint32_t) word_t; -typedef MUXDEF(CONFIG_ISA64, int64_t, int32_t) sword_t; -static const word_t WORD_T_MAX = MUXDEF(CONFIG_ISA64, UINT64_MAX, UINT32_MAX); -static const sword_t SWORD_T_MAX = MUXDEF(CONFIG_ISA64, INT64_MAX, INT32_MAX); -static const sword_t SWORD_T_MIN = MUXDEF(CONFIG_ISA64, INT64_MIN, INT32_MIN); -#define WORD_BYTES MUXDEF(CONFIG_ISA64, 8, 4) -#define FMT_WORD MUXDEF(CONFIG_ISA64, "0x%016" PRIx64, "0x%08" PRIx32) - -typedef word_t vaddr_t; -typedef MUXDEF(PMEM64, uint64_t, uint32_t) paddr_t; -#define FMT_PADDR MUXDEF(PMEM64, "0x%016" PRIx64, "0x%08" PRIx32) -typedef uint16_t ioaddr_t; - #include #endif diff --git a/nemu/include/debug.h b/nemu/include/debug.h index 057f8bf..3aae781 100644 --- a/nemu/include/debug.h +++ b/nemu/include/debug.h @@ -16,10 +16,11 @@ #ifndef __DEBUG_H__ #define __DEBUG_H__ -#include #include #include +IFDEF(CONFIG_ITRACE, void log_itrace_print()); + #define Trace(format, ...) \ _Log("[TRACE] " format "\n", ## __VA_ARGS__) diff --git a/nemu/include/types.h b/nemu/include/types.h new file mode 100644 index 0000000..364f2ed --- /dev/null +++ b/nemu/include/types.h @@ -0,0 +1,21 @@ +#ifndef __TYPES_H__ +#define __TYPES_H__ +#include +#include +#if CONFIG_MBASE + CONFIG_MSIZE > 0x100000000ul +#define PMEM64 1 +#endif + +typedef MUXDEF(CONFIG_ISA64, uint64_t, uint32_t) word_t; +typedef MUXDEF(CONFIG_ISA64, int64_t, int32_t) sword_t; +static const word_t WORD_T_MAX = MUXDEF(CONFIG_ISA64, UINT64_MAX, UINT32_MAX); +static const sword_t SWORD_T_MAX = MUXDEF(CONFIG_ISA64, INT64_MAX, INT32_MAX); +static const sword_t SWORD_T_MIN = MUXDEF(CONFIG_ISA64, INT64_MIN, INT32_MIN); +#define WORD_BYTES MUXDEF(CONFIG_ISA64, 8, 4) +#define FMT_WORD MUXDEF(CONFIG_ISA64, "0x%016" PRIx64, "0x%08" PRIx32) + +typedef word_t vaddr_t; +typedef MUXDEF(PMEM64, uint64_t, uint32_t) paddr_t; +#define FMT_PADDR MUXDEF(PMEM64, "0x%016" PRIx64, "0x%08" PRIx32) +typedef uint16_t ioaddr_t; +#endif \ No newline at end of file diff --git a/nemu/include/utils.h b/nemu/include/utils.h index 59bc9df..f974584 100644 --- a/nemu/include/utils.h +++ b/nemu/include/utils.h @@ -16,7 +16,7 @@ #ifndef __UTILS_H__ #define __UTILS_H__ -#include +#include // ----------- state ----------- @@ -74,7 +74,4 @@ uint64_t get_time(); } while (0) -IFDEF(CONFIG_ITRACE, void log_itrace_print()); - - #endif diff --git a/nemu/src/cpu/cpu-exec.c b/nemu/src/cpu/cpu-exec.c index 72e1265..1e402ab 100644 --- a/nemu/src/cpu/cpu-exec.c +++ b/nemu/src/cpu/cpu-exec.c @@ -13,7 +13,7 @@ * See the Mulan PSL v2 for more details. ***************************************************************************************/ -#include "utils.h" +#include #include #include #include diff --git a/nemu/src/isa/riscv32/inst.c b/nemu/src/isa/riscv32/inst.c index 54b9044..c4d824f 100644 --- a/nemu/src/isa/riscv32/inst.c +++ b/nemu/src/isa/riscv32/inst.c @@ -13,12 +13,13 @@ * See the Mulan PSL v2 for more details. ***************************************************************************************/ -#include "common.h" +#include #include "local-include/reg.h" #include #include #include #include +#include #define R(i) gpr(i) #define Mr vaddr_read diff --git a/nemu/src/monitor/monitor.c b/nemu/src/monitor/monitor.c index 1920ae4..6755edf 100644 --- a/nemu/src/monitor/monitor.c +++ b/nemu/src/monitor/monitor.c @@ -15,6 +15,7 @@ #include #include +#include void init_rand(); void init_log(const char *log_file); diff --git a/nemu/src/monitor/sdb/addrexp.y b/nemu/src/monitor/sdb/addrexp.y index 8e7df9a..4094e0b 100644 --- a/nemu/src/monitor/sdb/addrexp.y +++ b/nemu/src/monitor/sdb/addrexp.y @@ -7,6 +7,7 @@ } %{ #include + #include #include #include #include diff --git a/nemu/src/utils/ftrace.c b/nemu/src/utils/ftrace.c index 25f097a..ec668c0 100644 --- a/nemu/src/utils/ftrace.c +++ b/nemu/src/utils/ftrace.c @@ -1,9 +1,8 @@ -#include "debug.h" -#include "macro.h" #include #include #include #include +#include // Put this into another file #ifdef CONFIG_FTRACE diff --git a/nemu/src/utils/log.c b/nemu/src/utils/log.c index 7939d42..a041a2d 100644 --- a/nemu/src/utils/log.c +++ b/nemu/src/utils/log.c @@ -14,6 +14,7 @@ ***************************************************************************************/ #include +#include extern uint64_t g_nr_guest_inst; From 18db852763b86e78c2209ebba94bcc3f1fec7a37 Mon Sep 17 00:00:00 2001 From: xinyangli Date: Wed, 20 Mar 2024 20:11:21 +0800 Subject: [PATCH 05/10] pa2.2: fix ftrace switch --- nemu/src/isa/riscv32/inst.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/nemu/src/isa/riscv32/inst.c b/nemu/src/isa/riscv32/inst.c index c4d824f..41c2098 100644 --- a/nemu/src/isa/riscv32/inst.c +++ b/nemu/src/isa/riscv32/inst.c @@ -15,6 +15,7 @@ #include #include "local-include/reg.h" +#include "macro.h" #include #include #include @@ -61,7 +62,7 @@ static void do_branch(Decode *s, bool condition, word_t offset) { } } -static void ftrace(Decode *s, int rd, vaddr_t dst) { +static void ftrace_jalr(Decode *s, int rd, vaddr_t dst) { uint32_t i = s->isa.inst.val; int rs1 = BITS(i, 19, 15); if(rs1 == 1 && rd == 0) { @@ -86,8 +87,12 @@ static int decode_exec(Decode *s) { INSTPAT("??????? ????? ????? ??? ????? 01101 11", lui , U, R(rd) = imm); INSTPAT("??????? ????? ????? ??? ????? 00101 11", auipc , U, R(rd) = s->pc + imm); - INSTPAT("??????? ????? ????? ??? ????? 11011 11", jal , J, do {s->dnpc = s->pc + imm; R(rd) = s->pc + 4; ftrace_call(s->pc, s->pc + imm); } while(0)); - INSTPAT("??????? ????? ????? ??? ????? 11001 11", jalr , I, do {s->dnpc = src1 + imm; R(rd) = s->pc + 4; ftrace(s, rd, src1 + imm); } while(0)); + INSTPAT("??????? ????? ????? ??? ????? 11011 11", jal , J, do { + s->dnpc = s->pc + imm; R(rd) = s->pc + 4; + IFDEF(CONFIG_FTRACE, ftrace_call(s->pc, s->pc + imm)); } while(0)); + INSTPAT("??????? ????? ????? ??? ????? 11001 11", jalr , I, do { + s->dnpc = src1 + imm; R(rd) = s->pc + 4; + IFDEF(CONFIG_FTRACE, ftrace_jalr(s, rd, src1 + imm)); } while(0)); INSTPAT("??????? ????? ????? 000 ????? 11000 11", beq , B, do_branch(s, src1 == src2, imm)); INSTPAT("??????? ????? ????? 001 ????? 11000 11", bne , B, do_branch(s, src1 != src2, imm)); INSTPAT("??????? ????? ????? 100 ????? 11000 11", blt , B, do_branch(s, (sword_t)src1 < (sword_t)src2, imm)); From da0c42422d853f528fc1e21d3482c7da1fb6c18c Mon Sep 17 00:00:00 2001 From: xinyangli Date: Mon, 25 Mar 2024 16:56:16 +0800 Subject: [PATCH 06/10] build: init cmake build system for am --- .gitmodules | 3 + abstract-machine/.gitignore | 25 ++--- abstract-machine/CMakeLists.txt | 87 +++++++++++++++++ abstract-machine/CMakePresets.json | 29 ++++++ abstract-machine/am/CMakeLists.txt | 10 ++ abstract-machine/am/src/CMakeLists.txt | 53 ++++++++++ abstract-machine/am/src/native/CMakeLists.txt | 26 +++++ .../am/src/riscv/nemu/CMakeLists.txt | 34 +++++++ abstract-machine/cmake/am-config.cmake.in | 9 ++ abstract-machine/cmake/klib-config.cmake.in | 6 ++ abstract-machine/cmake/nemu-settings.cmake | 11 +++ abstract-machine/cmake/riscv-settings.cmake | 2 + abstract-machine/klib/CMakeLists.txt | 12 +++ abstract-machine/klib/include/klib.h | 1 + abstract-machine/klib/src/CMakeLists.txt | 33 +++++++ abstract-machine/klib/src/stdio.c | 14 ++- abstract-machine/klib/src/string.c | 92 ++++++++++++++++-- abstract-machine/klib/tests/CMakeLists.txt | 17 ++++ abstract-machine/klib/tests/stdio.c | 5 + abstract-machine/klib/tests/string.c | 75 ++++++++++++++ abstract-machine/out/install/lib/libklib.a | Bin 0 -> 87486 bytes am-kernels | 1 + 22 files changed, 515 insertions(+), 30 deletions(-) create mode 100644 .gitmodules create mode 100644 abstract-machine/CMakeLists.txt create mode 100644 abstract-machine/CMakePresets.json create mode 100644 abstract-machine/am/CMakeLists.txt create mode 100644 abstract-machine/am/src/CMakeLists.txt create mode 100644 abstract-machine/am/src/native/CMakeLists.txt create mode 100644 abstract-machine/am/src/riscv/nemu/CMakeLists.txt create mode 100644 abstract-machine/cmake/am-config.cmake.in create mode 100644 abstract-machine/cmake/klib-config.cmake.in create mode 100644 abstract-machine/cmake/nemu-settings.cmake create mode 100644 abstract-machine/cmake/riscv-settings.cmake create mode 100644 abstract-machine/klib/CMakeLists.txt create mode 100644 abstract-machine/klib/src/CMakeLists.txt create mode 100644 abstract-machine/klib/tests/CMakeLists.txt create mode 100644 abstract-machine/klib/tests/stdio.c create mode 100644 abstract-machine/klib/tests/string.c create mode 100644 abstract-machine/out/install/lib/libklib.a create mode 160000 am-kernels diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..d7bc671 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "am-kernels"] + path = am-kernels + url = ./am-kernels/ diff --git a/abstract-machine/.gitignore b/abstract-machine/.gitignore index 84c3ed2..bcba0ab 100644 --- a/abstract-machine/.gitignore +++ b/abstract-machine/.gitignore @@ -1,19 +1,6 @@ -* -!*/ -!*.h -!*.c -!*.cc -!*.S -!*.ld -!*.sh -!*.py -!*.mk -!Makefile -!README -!LICENSE -.* -_* -*~ -build/ -!.gitignore -.vscode \ No newline at end of file +**/.direnv/ +**/build/ +**/.envrc +**/.cache +.vscode +compile_commands.json diff --git a/abstract-machine/CMakeLists.txt b/abstract-machine/CMakeLists.txt new file mode 100644 index 0000000..508bc68 --- /dev/null +++ b/abstract-machine/CMakeLists.txt @@ -0,0 +1,87 @@ +cmake_minimum_required(VERSION 3.22) + +project(abstract-machine) +enable_language(CXX C ASM) +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 11) + +include(CMakeDependentOption) +include(CMakePackageConfigHelpers) # Used to find libcheck +include(CTest) + +# -- General options +set(ISA CACHE STRING "Target ISA") +set_property(CACHE ISA PROPERTY STRINGS "riscv" "x86" "x86_64" "native") +string(TOUPPER ${ISA} ISA_UPPER) + +cmake_dependent_option( + __PLATFORM_NEMU__ "Run on NEMU" + ON "ISA MATCHES \"(riscv | x86)\"" OFF) +cmake_dependent_option( + __PLATFORM_NATIVE__ "Run on native" + ON "ISA MATCHES native" OFF) + +# -- Set PLATFORM according to options +set(MATCH_PLATFORM_PATTERN "^__PLATFORM_([A-Z]*)__") +get_cmake_property(CACHE_VARS CACHE_VARIABLES) + +message(STATUS "ISA: ${ISA}") +foreach(VAR IN LISTS CACHE_VARS) + if(VAR MATCHES ${MATCH_PLATFORM_PATTERN}) + # Retrieve the value of the cache variable + get_property(VAR_VALUE CACHE ${VAR} PROPERTY VALUE) + set(PLATFORM_UPPER ${CMAKE_MATCH_1}) + string(TOLOWER ${PLATFORM_UPPER} PLATFORM) + message(STATUS "Variable: ${VAR}=${VAR_VALUE}, Platform: ${PLATFORM}") + endif() +endforeach() + +if(${PLATFORM} MATCHES "native") +set(ARCH "native") +else() +set(ARCH ${ISA}-${PLATFORM}) +endif() +string(TOUPPER ${ARCH} ARCH_UPPER) + +# -- Target specific options +cmake_dependent_option( + NATIVE_USE_KLIB "Use Klib even if on native" + ON "NOT __ISA_NATIVE__" OFF) + +# -- Add compile definitions based on options +add_compile_definitions( + $ + __ISA_${ISA_UPPER}__ + __PLATFORM_${PLATFORM_UPPER}__ +) + +add_compile_definitions( + $<$:__NATIVE_USE_KLIB__> +) + +# -- Required compiler flags +add_compile_options( + # -Werror + -Wno-main + -fno-asynchronous-unwind-tables + -fno-builtin + -fno-stack-protector + -U_FORTIFY_SOURCE + $<$:-fno-exceptions> + $<$:-ffreestanding> + $<$:-fno-rtti>) + +add_link_options( + -znoexecstack +) + +# -- Include linker script here. Use this linker script at link time if INCLUDE_LINKER_SCRIPT is set to true +set(LINKER_SCRIPT linker.ld) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") + +add_compile_options(-march=rv32if -mabi=ilp32) +add_link_options(-march=rv32if -mabi=ilp32) + +add_subdirectory(klib) +add_subdirectory(am) diff --git a/abstract-machine/CMakePresets.json b/abstract-machine/CMakePresets.json new file mode 100644 index 0000000..d14c0b6 --- /dev/null +++ b/abstract-machine/CMakePresets.json @@ -0,0 +1,29 @@ +{ + "version": 6, + "configurePresets": [ + { + "name": "native", + "displayName": "Native", + "generator": "Unix Makefiles", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "ISA": "native", + "__PLATFORM_NATIVE__": true, + "NATIVE_USE_KLIB": true + } + }, + { + "name": "riscv-nemu", + "displayName": "Riscv32 NEMU", + "generator": "Unix Makefiles", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "installDir": "/home/xin/repo/ysyx-workbench/abstract-machine/out/install", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "ISA": "riscv", + "__PLATFORM_NEMU__": true + } + } + ] +} \ No newline at end of file diff --git a/abstract-machine/am/CMakeLists.txt b/abstract-machine/am/CMakeLists.txt new file mode 100644 index 0000000..b0462e4 --- /dev/null +++ b/abstract-machine/am/CMakeLists.txt @@ -0,0 +1,10 @@ +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) + +add_library(am_interface INTERFACE) +target_include_directories(am_interface INTERFACE + $ + $) + +add_subdirectory(src) + +install(DIRECTORY include/ DESTINATION include/abstract-machine) diff --git a/abstract-machine/am/src/CMakeLists.txt b/abstract-machine/am/src/CMakeLists.txt new file mode 100644 index 0000000..533dd3b --- /dev/null +++ b/abstract-machine/am/src/CMakeLists.txt @@ -0,0 +1,53 @@ +if(ISA MATCHES "native") +set(SOURCEDIR "./${PLATFORM}") +else() +set(SOURCEDIR "./${ISA}/${PLATFORM}") +endif() + +add_subdirectory(${SOURCEDIR}) + +target_include_directories(am-${ARCH} + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} + PUBLIC + $ + $) +target_link_libraries(am-${ARCH} + PUBLIC klib_interface + INTERFACE m) + +# TODO: Check +target_link_options(am-${ARCH} INTERFACE + $ + $) + +# Interface compile flags +target_link_options(am-${ARCH} INTERFACE + -znoexecstack) + +target_compile_options(am-${ARCH} INTERFACE + -fno-asynchronous-unwind-tables + -fno-builtin + -fno-stack-protector + -U_FORTIFY_SOURCE + $<$:-fno-exceptions> + $<$:-ffreestanding> + $<$:-fno-rtti>) + +install(TARGETS am-${ARCH} klib_interface am_interface + EXPORT amTargets + LIBRARY DESTINATION lib) + +install(EXPORT amTargets + FILE amTargets.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/am-${ARCH}) + +configure_package_config_file(${CMAKE_SOURCE_DIR}/cmake/am-config.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/am-${ARCH}-config.cmake + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/am-${ARCH}) + +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/am-${ARCH}-config.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/am-${ARCH}) + +# TODO: check +install(FILES ${CMAKE_SOURCE_DIR}/scripts/${LINKER_SCRIPT} + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/am-${ARCH}) diff --git a/abstract-machine/am/src/native/CMakeLists.txt b/abstract-machine/am/src/native/CMakeLists.txt new file mode 100644 index 0000000..e3c9303 --- /dev/null +++ b/abstract-machine/am/src/native/CMakeLists.txt @@ -0,0 +1,26 @@ +include(CheckPIESupported) +check_pie_supported() + +set(SOURCES + trap.S + cte.c + ioe.c + mpe.c + platform.c + trm.c + vme.c + ioe/audio.c + ioe/disk.c + ioe/gpu.c + ioe/input.c + ioe/timer.c +) +add_library(am-native ${SOURCES}) + +# FIXME: get free(): invalid address when user program compiled without pie +set_target_properties(am-native PROPERTIES + POSITION_INDEPENDENT_CODE TRUE + INTERFACE_POSITION_INDEPENDENT_CODE TRUE) + +find_package(SDL2 REQUIRED) +target_link_libraries(am-${ARCH} PUBLIC SDL2::SDL2) diff --git a/abstract-machine/am/src/riscv/nemu/CMakeLists.txt b/abstract-machine/am/src/riscv/nemu/CMakeLists.txt new file mode 100644 index 0000000..a6992db --- /dev/null +++ b/abstract-machine/am/src/riscv/nemu/CMakeLists.txt @@ -0,0 +1,34 @@ +include(nemu-settings) +include(riscv-settings) + +add_library(am-${ISA}-nemu + cte.c + start.S + trap.S + vme.c + ${NEMU_SOURCES} +) + +target_compile_options(am-${ISA}-nemu PRIVATE + ${NEMU_COMPILE_OPTIONS} + ${RISCV_COMPILE_OPTIONS}) +target_link_options(am-${ISA}-nemu PRIVATE + ${NEMU_LINK_OPITIONS} + ${RISCV_LINK_OPTIONS}) +target_include_directories(am-${ISA}-nemu PRIVATE + ${NEMU_INCLUDE_DIRECTORIES}) +target_link_options(am-${ISA}-nemu INTERFACE + LINKER:--defsym=_pmem_start=0x80000000 + LINKER:--defsym=_entry_offset=0x0 + LINKER:--gc-sections + LINKER:-e _start + -nostartfiles) + +target_compile_definitions(am-${ISA}-nemu PUBLIC + ARCH_H="arch/riscv.h") +target_compile_definitions(am-${ISA}-nemu PRIVATE + ISA_H="riscv/riscv.h") + +set_target_properties(am-${ISA}-nemu PROPERTIES + POSITION_INDEPENDENT_CODE OFF + INTERFACE_POSITION_INDEPENDENT_CODE OFF) diff --git a/abstract-machine/cmake/am-config.cmake.in b/abstract-machine/cmake/am-config.cmake.in new file mode 100644 index 0000000..f2fbb32 --- /dev/null +++ b/abstract-machine/cmake/am-config.cmake.in @@ -0,0 +1,9 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) +if(${ARCH} MATCHES "native") +find_dependency(SDL2 REQUIRED) +endif() + +# Include the targets file +include("${CMAKE_CURRENT_LIST_DIR}/amTargets.cmake") diff --git a/abstract-machine/cmake/klib-config.cmake.in b/abstract-machine/cmake/klib-config.cmake.in new file mode 100644 index 0000000..6b57e7f --- /dev/null +++ b/abstract-machine/cmake/klib-config.cmake.in @@ -0,0 +1,6 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) + +# Include the targets file +include("${CMAKE_CURRENT_LIST_DIR}/klibTargets.cmake") diff --git a/abstract-machine/cmake/nemu-settings.cmake b/abstract-machine/cmake/nemu-settings.cmake new file mode 100644 index 0000000..910cdcf --- /dev/null +++ b/abstract-machine/cmake/nemu-settings.cmake @@ -0,0 +1,11 @@ +set(NEMU_COMPILE_OPTIONS -fdata-sections -ffunction-sections) +set(NEMU_LINK_OPTIONS + --defsym=_pmem_start=0x80000000 + --defsym=_entry_offset=0x0 + --gc-sections + -e _start) +set(NEMU_INCLUDE_DIRECTORIES + ${CMAKE_SOURCE_DIR}/am/src/platform/nemu/include) +file(GLOB_RECURSE NEMU_SOURCES + ${CMAKE_SOURCE_DIR}/am/src/platform/nemu/*.[cS]) +set(INCLUDE_LINKER_SCRIPT ON) diff --git a/abstract-machine/cmake/riscv-settings.cmake b/abstract-machine/cmake/riscv-settings.cmake new file mode 100644 index 0000000..1286e4c --- /dev/null +++ b/abstract-machine/cmake/riscv-settings.cmake @@ -0,0 +1,2 @@ +set(RISCV_COMPILE_OPTIONS) +set(RISCV_LINK_OPTIONS) diff --git a/abstract-machine/klib/CMakeLists.txt b/abstract-machine/klib/CMakeLists.txt new file mode 100644 index 0000000..2cf4a78 --- /dev/null +++ b/abstract-machine/klib/CMakeLists.txt @@ -0,0 +1,12 @@ +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) + +add_library(klib_interface INTERFACE) +target_include_directories(klib_interface + INTERFACE + $ + $) + +add_subdirectory(src) +# add_subdirectory(tests) + +install(DIRECTORY include/ DESTINATION include/abstract-machine) diff --git a/abstract-machine/klib/include/klib.h b/abstract-machine/klib/include/klib.h index ecb24c8..48d63e9 100644 --- a/abstract-machine/klib/include/klib.h +++ b/abstract-machine/klib/include/klib.h @@ -35,6 +35,7 @@ int atoi (const char *nptr); int printf (const char *format, ...); int sprintf (char *str, const char *format, ...); int snprintf (char *str, size_t size, const char *format, ...); +int vprintf (const char *format, va_list ap); int vsprintf (char *str, const char *format, va_list ap); int vsnprintf (char *str, size_t size, const char *format, va_list ap); diff --git a/abstract-machine/klib/src/CMakeLists.txt b/abstract-machine/klib/src/CMakeLists.txt new file mode 100644 index 0000000..bf7e136 --- /dev/null +++ b/abstract-machine/klib/src/CMakeLists.txt @@ -0,0 +1,33 @@ +# find_package(FLEX) +# find_package(BISON) + +# FLEX_TARGET(fmt_scanner fmt_scanner.l fmt_scanner.c) + +set(SOURCES + cpp.c + int64.c + stdio.c + stdlib.c + string.c + # ${FLEX_fmt_scanner_OUTPUTS} +) + +add_library(klib ${SOURCES}) +target_include_directories(klib PUBLIC $) +target_compile_definitions(klib PUBLIC $) + +install(TARGETS klib + EXPORT klibTargets + LIBRARY DESTINATION lib) + +install(EXPORT klibTargets + FILE klibTargets.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/klib) + +configure_package_config_file(${CMAKE_SOURCE_DIR}/cmake/klib-config.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/klib-config.cmake + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/klib) + +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/klib-config.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/klib) + diff --git a/abstract-machine/klib/src/stdio.c b/abstract-machine/klib/src/stdio.c index 1b19953..fec63bc 100644 --- a/abstract-machine/klib/src/stdio.c +++ b/abstract-machine/klib/src/stdio.c @@ -5,8 +5,20 @@ #if !defined(__ISA_NATIVE__) || defined(__NATIVE_USE_KLIB__) +int vprintf(const char *fmt, va_list ap) { + const char *p = fmt; + while(*p != '\0') { + putch(*p); + } + return 0; +} + int printf(const char *fmt, ...) { - panic("Not implemented"); + va_list args; + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); + return 0; } int vsprintf(char *out, const char *fmt, va_list ap) { diff --git a/abstract-machine/klib/src/string.c b/abstract-machine/klib/src/string.c index f1a1f22..931e7dd 100644 --- a/abstract-machine/klib/src/string.c +++ b/abstract-machine/klib/src/string.c @@ -5,43 +5,115 @@ #if !defined(__ISA_NATIVE__) || defined(__NATIVE_USE_KLIB__) size_t strlen(const char *s) { - panic("Not implemented"); + const char *p = s; + size_t len = 0; + while(*(p++) != '\0') len++; + return len; } char *strcpy(char *dst, const char *src) { - panic("Not implemented"); + char *p_dst = dst; + const char *p_src = src; + for(; *p_src != '\0'; p_src++, p_dst++) { + *p_dst = *p_src; + } + *p_dst = '\0'; + return dst; } char *strncpy(char *dst, const char *src, size_t n) { - panic("Not implemented"); + int i = 0; + for(; i < n && src[i] != '\0'; i++) { + dst[i] = src[i]; + } + for(; i < n; i++) { + dst[i] = '\0'; + } + return dst; } char *strcat(char *dst, const char *src) { - panic("Not implemented"); + char *p_dst = dst; + const char *p_src = src; + while(*p_dst != '\0') p_dst++; + for(; *p_src != '\0'; p_src++, p_dst++) { + *p_dst = *p_src; + } + *p_dst = '\0'; + return dst; } int strcmp(const char *s1, const char *s2) { - panic("Not implemented"); + const char *p_s1 = s1, *p_s2 = s2; + for(; *p_s1 == *p_s2; p_s1++, p_s2++) { + if(*p_s1 == '\0' || *p_s2 == '\0') { + break; + } + } + return *p_s1 - *p_s2; } int strncmp(const char *s1, const char *s2, size_t n) { - panic("Not implemented"); + const char *p_s1 = s1, *p_s2 = s2; + int i = 0; + for(i = 0; i < n - 1; i++) { + if(s1[i] == '\0' || s2[i] == '\0') + break; + } + return s1[i] - s2[i]; } void *memset(void *s, int c, size_t n) { - panic("Not implemented"); + uint8_t *p = s; + for(int i = 0; i < n; i++) { + p[i] = c; + } + return s; } void *memmove(void *dst, const void *src, size_t n) { - panic("Not implemented"); + if (src + n > dst && src < dst) { + size_t len = dst - src; + void *p_dst = (void *)src + n; + const void *p_src = src + n - len; + while(p_dst >= dst) { + memcpy(p_dst, p_src, len); + p_src -= len; + p_dst -= len; + } + if(n % len) memcpy(dst, src, n % len); + } else if (dst < src && dst + n > src) { + size_t len = src - dst; + void *p_dst = dst; + const void *p_src = src; + while(p_src < src + n) { + memcpy(p_dst, p_src, len); + p_src += len; + p_dst += len; + } + if(n % len) memcpy(p_dst, p_src, n % len); + } else { + memcpy(dst, src, n); + } + + return dst; } void *memcpy(void *out, const void *in, size_t n) { - panic("Not implemented"); + for (size_t i = 0 ; i < n ; i++) { + *(uint8_t *)(out + i) = *(uint8_t *)(in + i); + } + return out; } int memcmp(const void *s1, const void *s2, size_t n) { - panic("Not implemented"); + const uint8_t *p1 = s1, *p2 = s2; + for (int i = 0; i < n; i++) { + if(*p1 != *p2) + return p1 - p2; + p1++; p2++; + } + return 0; } #endif diff --git a/abstract-machine/klib/tests/CMakeLists.txt b/abstract-machine/klib/tests/CMakeLists.txt new file mode 100644 index 0000000..f72c555 --- /dev/null +++ b/abstract-machine/klib/tests/CMakeLists.txt @@ -0,0 +1,17 @@ +set(TEST_SOURCES + stdio + string +) + +foreach(TEST IN LISTS TEST_SOURCES) + add_executable(${TEST} ${TEST}.c) + target_link_libraries(${TEST} am-${ARCH} klib m) + target_include_directories(${TEST} + PRIVATE $ + PRIVATE $ + ) + # TODO: Run tests in other configurations + if(__PLATFORM_NATIVE__) + add_test(NAME ${TEST} COMMAND ${TEST}) + endif() +endforeach() diff --git a/abstract-machine/klib/tests/stdio.c b/abstract-machine/klib/tests/stdio.c new file mode 100644 index 0000000..7287e83 --- /dev/null +++ b/abstract-machine/klib/tests/stdio.c @@ -0,0 +1,5 @@ +#include + +int main(void) { + return 0; +} \ No newline at end of file diff --git a/abstract-machine/klib/tests/string.c b/abstract-machine/klib/tests/string.c new file mode 100644 index 0000000..640f2d6 --- /dev/null +++ b/abstract-machine/klib/tests/string.c @@ -0,0 +1,75 @@ +#include +#include +#include + +void test_strcpy() { + char b[32]; + char *s; + b[16]='a'; b[17]='b'; b[18]='c'; b[19]=0; + panic_on((s = strcpy(b, b+16)) != b, "strcpy wrong return value"); + panic_on(strcmp(s, "abc") != 0, "strcpy gave incorrect string"); + panic_on((s = strcpy(b+1, b+16)) != b+1, "strcpy wrong return value"); + panic_on(strcmp(s, "abc") != 0, "strcpy gave incorrect string"); + + panic_on((s = strcpy(b+1, b+17)) != b+1, "strcpy wrong return value"); + panic_on(strcmp(s, "bc") != 0, "strcpy gave incorrect string"); +} + +void test_strncpy() { + char b[32]; + char *s; + int i; + b[3] = 'x'; b[4] = 0; + panic_on((s = strncpy(b, "abc", 3)) != b, "strncpy wrong return value"); + panic_on(b[2] != 'c', "strncpy fails to copy last byte"); + panic_on(b[3] != 'x', "strncpy overruns buffer to null-terminate"); +} + +void test_strncmp() { + panic_on(strncmp("abcd", "abce", 3) != 0, "strncmp compares past n"); + panic_on(strncmp("abc", "abd", 3) == 0, "strncmp fails to compare n-1st byte"); +} + +void test_memset() { + uint8_t arr[128]; + arr[120] = 0xd; + panic_on(memset(arr, 0xf, 120) != arr, "memset wrong return value"); + panic_on(arr[7] != 0xf, "memset fails to set value in range"); + panic_on(arr[120] != 0xd, "memset set value past n"); +} + +void test_memcpy() { + const uint8_t src[] = { 0x0, 0x0, 0x1, 0x2, 0x3, 0x4, 0x0, 0x0 }; + uint8_t dst[8] = {0}; + memcpy(dst, src, 8); + panic_on(memcmp(dst, src, 8) != 0, "memcpy fails to copy memory"); +} + +void test_memmove() { + const uint8_t ref[] = { 0x0, 0x0, 0x1, 0x2, 0x3, 0x4, 0x0, 0x0 }; + uint8_t dst[8] = {0}; + const uint8_t ans1[] = { 0x1, 0x2, 0x3, 0x4, 0x3, 0x4, 0x0, 0x0 }; + const uint8_t ans2[] = { 0x1, 0x2, 0x2, 0x3, 0x4, 0x3, 0x0, 0x0 }; + const uint8_t ans3[] = { 0x1, 0x2, 0x2, 0x1, 0x2, 0x2, 0x3, 0x4 }; + memmove(dst, ref, 8); + panic_on(memcmp(dst, ref, 8) != 0, "memmove fails to copy non-overlapping memory"); + + memmove(dst, dst + 2, 4); + panic_on(memcmp(dst, ans1, 8) != 0, "memmove fails to copy overlapping memory (dst < src)"); + + memmove(dst + 2, dst + 1, 4); + panic_on(memcmp(dst, ans2, 8) != 0, "memmove fails to copy overlapping memory (src < dst)"); + + memmove(dst + 3, dst, 5); + panic_on(memcmp(dst, ans3, 8) != 0, "memmove fails to copy overlapping memory (src < dst)"); +} + +int main(void) { + test_strcpy(); + test_strncpy(); + test_strncmp(); + test_memset(); + test_memcpy(); + test_memmove(); + return 0; +} diff --git a/abstract-machine/out/install/lib/libklib.a b/abstract-machine/out/install/lib/libklib.a new file mode 100644 index 0000000000000000000000000000000000000000..5023a30456119001268273724f42950c73659d18 GIT binary patch literal 87486 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 Lv`GTe22Te7Y4qh2 literal 0 HcmV?d00001 diff --git a/am-kernels b/am-kernels new file mode 160000 index 0000000..2f55982 --- /dev/null +++ b/am-kernels @@ -0,0 +1 @@ +Subproject commit 2f559823a63cf6909d5a9e32dee47d6891caf553 From 8545a92c1f7be75df075ef7b9266e5560efb0638 Mon Sep 17 00:00:00 2001 From: xinyangli Date: Mon, 25 Mar 2024 17:02:12 +0800 Subject: [PATCH 07/10] pa2.2: nemu small fixes --- nemu/.result.tmp | 0 nemu/Makefile | 2 +- nemu/default.nix | 4 ++-- nemu/include/debug.h | 1 + nemu/src/isa/riscv32/inst.c | 2 ++ nemu/src/monitor/monitor.c | 4 ++++ nemu/src/utils/ftrace.c | 2 +- 7 files changed, 11 insertions(+), 4 deletions(-) delete mode 100644 nemu/.result.tmp diff --git a/nemu/.result.tmp b/nemu/.result.tmp deleted file mode 100644 index e69de29..0000000 diff --git a/nemu/Makefile b/nemu/Makefile index e5e37c3..e5e5838 100644 --- a/nemu/Makefile +++ b/nemu/Makefile @@ -94,4 +94,4 @@ integration-tests: $(IMAGES) test: unit-tests integration-tests -.PHONY: test unit-tests integration-tests \ No newline at end of file +.PHONY: test unit-tests integration-tests diff --git a/nemu/default.nix b/nemu/default.nix index a886b79..e746a7c 100644 --- a/nemu/default.nix +++ b/nemu/default.nix @@ -38,7 +38,7 @@ stdenv.mkDerivation rec { doCheck = true; checkPhase = '' - export IMAGES_PATH=${am-kernels}/share/images + export IMAGES_PATH=${am-kernels}/share/binary make test ''; @@ -49,7 +49,7 @@ stdenv.mkDerivation rec { shellHook = '' export NEMU_HOME=$(pwd) - export IMAGES_PATH=${am-kernels}/share/images + export IMAGES_PATH=${am-kernels}/share/binary ''; meta = with lib; { diff --git a/nemu/include/debug.h b/nemu/include/debug.h index 3aae781..329c64a 100644 --- a/nemu/include/debug.h +++ b/nemu/include/debug.h @@ -18,6 +18,7 @@ #include #include +#include IFDEF(CONFIG_ITRACE, void log_itrace_print()); diff --git a/nemu/src/isa/riscv32/inst.c b/nemu/src/isa/riscv32/inst.c index 41c2098..1c41c63 100644 --- a/nemu/src/isa/riscv32/inst.c +++ b/nemu/src/isa/riscv32/inst.c @@ -62,6 +62,7 @@ static void do_branch(Decode *s, bool condition, word_t offset) { } } +#ifdef CONFIG_FTRACE static void ftrace_jalr(Decode *s, int rd, vaddr_t dst) { uint32_t i = s->isa.inst.val; int rs1 = BITS(i, 19, 15); @@ -71,6 +72,7 @@ static void ftrace_jalr(Decode *s, int rd, vaddr_t dst) { ftrace_call(s->pc, dst); } } +#endif static int decode_exec(Decode *s) { int rd = 0; diff --git a/nemu/src/monitor/monitor.c b/nemu/src/monitor/monitor.c index 6755edf..0154208 100644 --- a/nemu/src/monitor/monitor.c +++ b/nemu/src/monitor/monitor.c @@ -133,8 +133,12 @@ void init_monitor(int argc, char *argv[]) { // printf("elf_file: %s\n", elf_file); if(elf_file != NULL) { +#ifdef CONFIG_FTRACE void init_elf(const char *path); init_elf(elf_file); +#else + Warning("Elf file provided, but ftrace not turned on. Ignoring elf file."); +#endif } #ifndef CONFIG_ISA_loongarch32r diff --git a/nemu/src/utils/ftrace.c b/nemu/src/utils/ftrace.c index ec668c0..ea2f2b6 100644 --- a/nemu/src/utils/ftrace.c +++ b/nemu/src/utils/ftrace.c @@ -10,7 +10,6 @@ static vaddr_t ftrace_stack[CONFIG_FTRACE_STACK_SIZE] = {0}; static vaddr_t ftrace_stack_len = 0; func_t *func_table = NULL; int func_table_len = 0, func_table_size = 8; -#endif static int cmp_func_t(const void *a, const void *b) { return ((func_t *)a)->start > ((func_t *)b)->start; @@ -122,3 +121,4 @@ void ftrace_return(vaddr_t pc, vaddr_t addr) { Trace("%*s0x%x ret 0x%x <%s+0x%x>", ftrace_stack_len, "", pc, addr, f == NULL ? "???" : f->name, addr - f->start); } +#endif From 2cc992a1818ae2c18943f0be51a55b3cfa126dff Mon Sep 17 00:00:00 2001 From: xinyangli Date: Mon, 25 Mar 2024 17:29:46 +0800 Subject: [PATCH 08/10] am: build with nix --- .gitignore | 3 +- .gitmodules | 2 +- abstract-machine/default.nix | 26 ++++++++++++++++ flake.nix | 60 +++++++++++++----------------------- 4 files changed, 50 insertions(+), 41 deletions(-) create mode 100644 abstract-machine/default.nix diff --git a/.gitignore b/.gitignore index 4ed3ed8..44a51ce 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ !init.sh /fceux-am /nvboard -/am-kernels +**/.cache +**/result diff --git a/.gitmodules b/.gitmodules index d7bc671..3d834b3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "am-kernels"] path = am-kernels - url = ./am-kernels/ + url = https://git.xinyang.life/xin/am-kernels.git diff --git a/abstract-machine/default.nix b/abstract-machine/default.nix new file mode 100644 index 0000000..1f1f67d --- /dev/null +++ b/abstract-machine/default.nix @@ -0,0 +1,26 @@ +{ stdenv, + lib, + cmake, + SDL2, + isa ? "native", + platform ? "NEMU" +}: +stdenv.mkDerivation { + pname = "abstract-machine"; + version = "2024.02.18"; + + src = ./.; + + cmakeFlags = [ + (lib.cmakeFeature "ISA" isa) + (lib.cmakeBool "__PLATFORM_${lib.strings.toUpper platform}__" true) + ]; + + nativeBuildInputs = [ + cmake + ]; + + buildInputs = [ + + ] ++ (if platform=="native" then [ SDL2 ] else [ ]); +} diff --git a/flake.nix b/flake.nix index 4492e28..42dccac 100644 --- a/flake.nix +++ b/flake.nix @@ -12,54 +12,37 @@ localSystem = system; crossSystem = { config = "riscv32-none-elf"; - abi = "ilp32"; + gcc = { + abi = "ilp32"; + arch = "rv32if"; + }; }; }; in { - packages.nemu = pkgs.callPackage ./nemu { am-kernels = self.packages.${system}.am-kernels; }; + packages.nemu = pkgs.callPackage ./nemu { am-kernels = self.packages.${system}.am-kernels-cmake; }; + packages.abstract-machine = crossPkgs.callPackage ./abstract-machine { isa = "riscv"; platform = "nemu"; }; - packages.am-kernels = crossPkgs.stdenv.mkDerivation rec { - pname = "am-kernels"; + packages.am-kernels-cmake = crossPkgs.stdenv.mkDerivation rec { + pname = "am-kernels-cmake"; version = "2024.02.18"; - src = pkgs.fetchFromGitHub { - owner = "NJU-ProjectN"; - repo = "am-kernels"; - rev = "bb725d6f8223dd7de831c3b692e8c4531e9d01af"; - hash = "sha256-ZHdrw28TN8cMvhhzM469OV7cp0Yp+8yao855HP4+P4A="; - }; + src = ./am-kernels; - AM_HOME = pkgs.fetchFromGitHub { - owner = "xinyangli"; - repo = "abstract-machine"; - rev = "788595aac61c6b2f3b78ca8aa7d08dc33911bca4"; - hash = "sha256-YvWHIBP9tz3HL2TyibftvvQrpkWUDPnviCF4oyLmdjg="; - }; + nativeBuildInputs = [ + pkgs.cmake + ]; - ARCH = "riscv32-nemu"; + cmakeFlags = [ + (pkgs.lib.cmakeFeature "ISA" "riscv") + (pkgs.lib.cmakeFeature "PLATFORM" "nemu") + (pkgs.lib.cmakeFeature "CMAKE_INSTALL_DATADIR" "share") + ]; - patchPhase = '' - sed -i 's/\/bin\/echo/echo/' tests/cpu-tests/Makefile - ''; - - buildPhase = '' - AS=$CC make -C tests/cpu-tests BUILD_DIR=$(pwd)/build ARCH=$ARCH - ''; - - installPhase = '' - mkdir -p $out/share/images $out/share/dump - cp build/riscv32-nemu/*.bin $out/share/images - cp build/riscv32-nemu/*.txt $out/share/dump - ''; - - dontFixup = true; - }; - - devShells.default = pkgs.mkShell { - packages = with pkgs; [ - gdb - ] ++ builtins.attrValues self.packages.${system}; + buildInputs = [ + # SDL2 + self.packages.${system}.abstract-machine + ]; }; devShells.nemu = pkgs.mkShell { @@ -74,4 +57,3 @@ } ); } - From d5521806d922216fe76f21161beb67b9612a4483 Mon Sep 17 00:00:00 2001 From: xinyangli Date: Mon, 25 Mar 2024 20:46:13 +0800 Subject: [PATCH 09/10] ci: init --- .gitea/workflows/abstract-machine-build.yml | 22 +++++++++++++++++++++ flake.nix | 4 ++-- 2 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 .gitea/workflows/abstract-machine-build.yml diff --git a/.gitea/workflows/abstract-machine-build.yml b/.gitea/workflows/abstract-machine-build.yml new file mode 100644 index 0000000..ded6e88 --- /dev/null +++ b/.gitea/workflows/abstract-machine-build.yml @@ -0,0 +1,22 @@ +name: Build abstract machine with nix +on: [push] + +jobs: + build-abstract-machine: + runs-on: nix + steps: + - uses: https://github.com/cachix/cachix-action@v14 + with: + name: ysyx + signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}' + - uses: actions/checkout@v4 + sparse-checkout: | + flake.nix + abstract-machine + - name: Build abstract-machine + run: | + nix build .#abstract-machine + - name: Build nemu + run: | + nix build .#nemu + diff --git a/flake.nix b/flake.nix index 42dccac..3a07d79 100644 --- a/flake.nix +++ b/flake.nix @@ -20,10 +20,10 @@ }; in { - packages.nemu = pkgs.callPackage ./nemu { am-kernels = self.packages.${system}.am-kernels-cmake; }; + packages.nemu = pkgs.callPackage ./nemu { am-kernels = self.packages.${system}.am-kernels; }; packages.abstract-machine = crossPkgs.callPackage ./abstract-machine { isa = "riscv"; platform = "nemu"; }; - packages.am-kernels-cmake = crossPkgs.stdenv.mkDerivation rec { + packages.am-kernels = crossPkgs.stdenv.mkDerivation rec { pname = "am-kernels-cmake"; version = "2024.02.18"; From d5feb71b50b5d7c87ddb0ac9f8fc9378d701900a Mon Sep 17 00:00:00 2001 From: xinyangli Date: Tue, 26 Mar 2024 01:53:17 +0800 Subject: [PATCH 10/10] nemu: try difftest --- .gitea/workflows/abstract-machine-build.yml | 5 ++--- nemu/default.nix | 4 +++- nemu/src/isa/riscv32/difftest/dut.c | 5 ++++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.gitea/workflows/abstract-machine-build.yml b/.gitea/workflows/abstract-machine-build.yml index ded6e88..af10d43 100644 --- a/.gitea/workflows/abstract-machine-build.yml +++ b/.gitea/workflows/abstract-machine-build.yml @@ -10,9 +10,8 @@ jobs: name: ysyx signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}' - uses: actions/checkout@v4 - sparse-checkout: | - flake.nix - abstract-machine + with: + submodules: true - name: Build abstract-machine run: | nix build .#abstract-machine diff --git a/nemu/default.nix b/nemu/default.nix index e746a7c..d3d5a70 100644 --- a/nemu/default.nix +++ b/nemu/default.nix @@ -1,7 +1,8 @@ { pkgs, lib, stdenv, - am-kernels + am-kernels, + dtc }: stdenv.mkDerivation rec { @@ -15,6 +16,7 @@ stdenv.mkDerivation rec { pkg-config flex bison + dtc ]; buildInputs = with pkgs; [ diff --git a/nemu/src/isa/riscv32/difftest/dut.c b/nemu/src/isa/riscv32/difftest/dut.c index c5ebf13..06748ce 100644 --- a/nemu/src/isa/riscv32/difftest/dut.c +++ b/nemu/src/isa/riscv32/difftest/dut.c @@ -18,7 +18,10 @@ #include "../local-include/reg.h" bool isa_difftest_checkregs(CPU_state *ref_r, vaddr_t pc) { - return false; + for(int i = 0; i < MUXDEF(CONFIG_RVE, 16, 32); i++) { + if(!difftest_check_reg(reg_name(i), pc, ref_r->gpr[i], gpr(i))) return false; + } + return true; } void isa_difftest_attach() {