// Simple emulator program #include #include #include #include #include #define SRVE_IMPLEMENTATION #include "srve.h" #include "../libc/elf.h" // Some defines for virtio emulation #define VIOE_UART0 0x10000000ULL #define VIOE_UART_SIZE 0x1000ULL #define VIOE_UART_HOLDING 0x0 #define VIOE_UART_LINESTATUS 0x5 #define VIOE_UART_LINESTATUS_RECEIVEABLE 1 #define VIOE_UART_LINESTATUS_TRANSMITTABLE (1<<5) // Some defines for new BIOS functionality #define VMRD_BASEADDRESS 0x0000000010001000UL #define VMRD_SIZE 0x1000ULL #define VMRD_REG_MAGIC 0 #define VMRD_REG_BLKSIZE 4 #define VMRD_REG_MEMADDR 8 #define VMRD_REG_BLKADDR 16 #define VMRD_REG_ACTION 24 // The magic number is BA5DB105, which drivers can use to distinguish // it from virtio. #define VMRD_MAGIC 0xBA5DB105 // This range needs to cover all I/O areas #define IORANGE_BEGIN 0x10000000ULL #define IORANGE_END 0x10002000ULL typedef struct vmrd vmrd_t; struct vmrd { uint64_t memoryaddress; uint64_t blockaddress; uint32_t blocksize; uint32_t action; FILE* file; uint64_t imagesize; }; srve_core64_t core; // Loads binary into memory at address, returning size or -1 on failure. int64_t loadf(unsigned char* memory, unsigned long long addr, const char* fname) { fprintf(stderr, "loadf(%p, 0x%llx, \"%s\");\n", memory, addr, fname); FILE* f = fopen(fname, "r"); if (!f) { fprintf(stderr, "Failed to open '%s'\n", fname); return -1; } unsigned long sz = 0; size_t nread; while ((nread = fread(memory + addr + sz, 1, 1024*1024, f)) > 0) { sz += (unsigned long) nread; } fclose(f); } int uart(int devnum, uint32_t offset, void* buffer, unsigned int storing, unsigned int size) { //fprintf(stderr, "uart(%i, 0x%x, %p, %d, %d);\n", devnum, offset, buffer, storing, size); switch (offset) { case VIOE_UART_HOLDING: if (storing) { fflush(stderr); //printf("char value is 0x%x\n", *((uint8_t*)buffer)); //putc('X',stdout); putc(*((uint8_t*)buffer), stdout); fflush(stdout); //exit(0); return 0; } else { return -1; } case VIOE_UART_LINESTATUS: if (storing) { return -1; } else { *((char*)buffer) = VIOE_UART_LINESTATUS_TRANSMITTABLE; return 0; } default: fprintf(stderr, "uart: bad offset 0x%x\n", offset); return 0; } } // Only allow one disk for now, but design as though there may be more. #define VMRD_MAX 1 vmrd_t* vmrd_array[VMRD_MAX]; uint32_t vmrd_performaction(void* memory, vmrd_t* device, uint32_t action) { if (memory == NULL || device == NULL || device->file == NULL || device->imagesize == 0) { return 'f'; } char* membytes = memory; switch (action) { case 't': core.tracing = 1; return 's'; case 'e': core.tracing = 0; return 's'; case 'r': if (fseek(device->file, device->blocksize*device->blockaddress, SEEK_SET) != 0) { return 'f'; } // TODO: Validate memory address... if (fread(membytes+device->memoryaddress, device->blocksize, 1, device->file) != 1) { return 'f'; } return 's'; default: return 'f'; } } int vmrd(void* memory, int devnum, uint32_t offset, void* buffer, unsigned int storing, unsigned int size) { vmrd_t* device = &vmrd_array[devnum]; switch (offset) { case VMRD_REG_MAGIC: if (!storing && size == 4) { *((uint32_t*)buffer) = VMRD_MAGIC; return 0; } else { goto fail; } case VMRD_REG_BLKADDR: if (storing && size == 8) { device->blockaddress = *((uint64_t*)buffer); return 0; } else if (!storing && size == 8) { *((uint64_t*)buffer) = device->blockaddress; return 0; } else { goto fail; } case VMRD_REG_MEMADDR: if (storing && size == 8) { device->memoryaddress = *((uint64_t*)buffer); return 0; } else if (!storing && size == 8) { *((uint64_t*)buffer) = device->memoryaddress; return 0; } else { goto fail; } case VMRD_REG_BLKSIZE: if (storing && size == 4) { device->blocksize = *((uint32_t*)buffer); return 0; } else if (!storing && size == 4) { *((uint32_t*)buffer) = device->blocksize; return 0; } else { goto fail; } case VMRD_REG_ACTION: if (storing && size == 4) { device->action = '*'; // Asterix action state indicates it's in use device->action = vmrd_performaction(memory, device, *((uint32_t*)buffer)); return 0; } else if (!storing && size == 4) { *((uint32_t*)buffer) = device->action; return 0; } else { goto fail; } default: fail: device->action = 'f'; fprintf(stderr, "vmrd: bad access at offset 0x%x\n", offset); return -1; } return 0; } int iofunc(void* memory, uint64_t address, void* buffer, unsigned int storing, unsigned int size) { if (address >= VIOE_UART0 && address+size <= VIOE_UART0+VIOE_UART_SIZE) { return uart(0, (uint32_t) (address - VIOE_UART0), buffer, storing, size); } if (address >= VMRD_BASEADDRESS && address+size <= VMRD_BASEADDRESS+VMRD_SIZE) { return vmrd(memory, 0, (uint32_t) (address - VMRD_BASEADDRESS), buffer, storing, size); } fprintf(stderr, "iofunc: Bad I/O address 0x%llx\n", address); return -1; } int memfunc(void* memory, uint64_t address, void* buffer, unsigned int storing, unsigned int size) { if (address >= IORANGE_BEGIN && address+size <= IORANGE_END) { return iofunc(memory, address, buffer, storing, size); } //if (address % size) { //fprintf(stderr, "memfunc(%p, 0x%lx, %p, %d, %d);\n", memory, address, buffer, storing, size); //exit(-1); //} //address = (address << 32) >> 32; //fprintf(stderr, "memfunc(%p, 0x%lx, %p, %d, %d);\n", memory, address, buffer, storing, size); unsigned char* bmemory = memory; if (size == 4) { uint32_t* ibuffer = buffer; uint32_t* istorage = (void*)(bmemory + address); if (storing) { *istorage = *ibuffer; } else { *ibuffer = *istorage; } return 0; } else if (size == 8) { uint64_t* ibuffer = buffer; uint64_t* istorage = (void*)(bmemory + address); if (storing) { *istorage = *ibuffer; } else { *ibuffer = *istorage; } return 0; } else if (size == 1) { uint8_t* ibuffer = buffer; uint8_t* istorage = (void*)(bmemory + address); if (storing) { *istorage = *ibuffer; } else { *ibuffer = *istorage; } return 0; } else if (size == 2) { uint16_t* ibuffer = buffer; uint16_t* istorage = (void*)(bmemory + address); if (storing) { *istorage = *ibuffer; } else { *ibuffer = *istorage; } return 0; } else { return -1; } } int hexvalue(char ch) { switch (ch) { case '0': return 0; case '1': return 1; case '2': return 2; case '3': return 3; case '4': return 4; case '5': return 5; case '6': return 6; case '7': return 7; case '8': return 8; case '9': return 9; case 'a': case 'A': return 10; case 'b': case 'B': return 11; case 'c': case 'C': return 12; case 'd': case 'D': return 13; case 'e': case 'E': return 14; case 'f': case 'F': return 15; default: return -1; } } char linebuf[1000]; int insertsymbol(srve_debugsymbol_t** symvar, uint64_t addr, char* namebuffer) { for (int i = 0; namebuffer[i] != 0; i++) { if (namebuffer[i] == '.') { return 0; // Skip adding entries for filenames and sections } } srve_debugsymbol_t* sym = malloc(sizeof(srve_debugsymbol_t)); if (!sym) { return -1; } sym->addr = addr; sym->name = strdup(namebuffer); sym->next = NULL; for (int i = 0; sym->name[i] != 0; i++) { if (sym->name[i] == '\r' || sym->name[i] == '\n') { sym->name[i] = 0; break; } } srve_debugsymbol_t* search = *symvar; if (search == NULL || addr < search->addr) { sym->next = search; *symvar = sym; return 0; } while (search->addr < addr) { if (search->next == NULL || search->next->addr >= addr) { sym->next = search->next; search->next = sym; return 0; } search = search->next; } sym->next = search->next; search->next = sym; return 0; } int loadsymbols(srve_debugsymbol_t** symvar, char* fname) { FILE* f = fopen(fname, "r"); while (fgets(linebuf, 1000, f)) { char* s = linebuf; uint64_t x = 0; int digit; while ((digit = hexvalue(*s)) >= 0) { x *= 0x10; x += digit; s++; } while (*s == ' ') { s++; } if (insertsymbol(symvar, x, s) != 0) { return -1; } } fclose(f); return 0; } int loaddiskimage(vmrd_t* device, char* fname) { FILE* f = fopen(fname, "r+"); if (f) { fseek(f, 0, SEEK_END); device->file = f; device->imagesize = ftell(f); device->action = 'i'; // Initial action state return 0; } else { device->file = NULL; device->imagesize = 0; device->action = 'f'; // Failed action state return -1; } } int main(int argc, char** argv) { fprintf(stderr, "SecureLang RV64/RV32 emulator, early testing version\n"); char* memory = calloc(4*1024*1024*1024ULL, 1); if (memory == NULL) { fprintf(stderr, "Setup failed!\n"); return -1; } uint64_t loadaddr = 0x8000000ULL; // TODO: Compiler bug prevents 0x80000000 as a constant, must be an integer conversion somewhere loadaddr *= 0x10; //printf("Loading at %dMB\n", loadaddr/(1024*1024)); char* kfname = argv[1]; char* sname = argv[2]; char* fsname = argv[3]; loadf(memory, loadaddr, argv[1]); srve_debugsymbol_t* syms = NULL; loadsymbols(&syms, sname); loaddiskimage(&vmrd_array[0], fsname); srve_debugsymbol_t* iter = syms; while (iter) { fprintf(stderr, "%lx %s\n", iter->addr, iter->name); iter = iter->next; } if (srve_core64_init(&core, &memfunc, memory, loadaddr) != 0) { return -1; } core.symbols = syms; core.tracing = 0; int64_t result; do { result = srve_core64_doinstr(&core); } while (result == 0); return 0; }