diff options
Diffstat (limited to 'src/test.c')
| -rw-r--r-- | src/test.c | 417 |
1 files changed, 417 insertions, 0 deletions
diff --git a/src/test.c b/src/test.c new file mode 100644 index 00000000..012fc13d --- /dev/null +++ b/src/test.c @@ -0,0 +1,417 @@ +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stdbool.h> +#include <assert.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include "box64context.h" +#include "debug.h" +#include "core.h" +#include "fileutils.h" +#include "json.h" +#include "elfloader.h" +#include "x64emu.h" +#include "box64cpu.h" +#include "box64cpu_util.h" +#include "x64trace.h" +#include "build_info.h" + +#define NASM "nasm" +#define X86_64_LD "x86_64-linux-gnu-ld" + +extern box64context_t* my_context; +extern FILE* ftrace; + +uint64_t regs[16] = { 0 }; +uint64_t ymmregs[16][4] = { { 0 } }; + +bool check_regs[16] = { 0 }; +bool check_xmmregs[16] = { 0 }; +bool check_ymmregs[16] = { 0 }; + +const char* regname[] = { "RAX", "RCX", "RDX", "RBX", "RSP", "RBP", "RSI", "RDI", "R8", "R9", "R10", "R11", "R12", "R13", "R14", "R15" }; + +#define MAX_MEMORY_REGIONS 32 +#define MAX_MEMORY_DATA 32 +#define MAX_MEMORY_DATA_SIZE 256 + +struct { + uint64_t start; + uint64_t size; +} memory_regions[MAX_MEMORY_REGIONS] = { { 0 } }; + +struct { + uint64_t start; + uint64_t size; + uint8_t data[MAX_MEMORY_DATA_SIZE]; +} memory_data[MAX_MEMORY_DATA] = { { 0 } }; + +inline uint64_t fromstr(const char* str) +{ + int base = strlen(str) > 2 && (str[1] == 'x' || str[1] == 'X') ? 16 : 10; + return strtoull(str, NULL, base); +} + +static struct json_value_s* json_find(struct json_object_s* object, const char* key) +{ + struct json_object_element_s *element = object->start; + while (element) { + if (strcmp(element->name->string, key) == 0) { + return element->value; + } + element = element->next; + } + return NULL; +} + +static void json_fill_array(struct json_array_s* array, uint64_t* values) +{ + struct json_array_element_s* element = array->start; + int i = 0; + while (element) { + if (element->value->type == json_type_string) { + struct json_string_s* string = (struct json_string_s*)element->value->payload; + values[i] = fromstr(string->string); + i++; + } + element = element->next; + } +} + +static void loadTest(const char** filepath, const char* include_path) +{ + FILE* file = fopen(*filepath, "r"); + if (!file) { + printf_log(LOG_NONE, "Failed to open test file: %s\n", *filepath); + exit(1); + } + + // read file line by line + char line[1024]; + char json[4096] = { 0 }; + bool in_config = false; + while (fgets(line, sizeof(line), file)) { + if (!strcmp(line, "%ifdef CONFIG\n")) { + in_config = true; + continue; + } else if (!strcmp(line, "%endif\n")) { + in_config = false; + break; + } + if (in_config) { + strncat(json, line, sizeof(json) - strlen(json) - 1); + } + } + + fclose(file); + struct json_value_s *config = json_parse(json, strlen(json)); + if (!config || config->type != json_type_object) { + printf_log(LOG_NONE, "Failed to parse JSON configuration.\n"); + exit(1); + } + + + struct json_value_s* regdata = json_find(config->payload, "RegData"); + int i = 0; + +#define REG(name) \ + if (regdata && regdata->type == json_type_object) { \ + struct json_value_s* r##name = json_find(regdata->payload, #name); \ + if (r##name && r##name->type == json_type_string) { \ + struct json_string_s* string = (struct json_string_s*)r##name->payload; \ + regs[i] = fromstr(string->string); \ + check_regs[i] = true; \ + } \ + i++; \ + } + + REG(RAX); + REG(RCX); + REG(RDX); + REG(RBX); + REG(RSP); + REG(RBP); + REG(RSI); + REG(RDI); + REG(R8); + REG(R9); + REG(R10); + REG(R11); + REG(R12); + REG(R13); + REG(R14); + REG(R15); +#undef REG + + i = 0; + +#define REG(name) \ + if (regdata && regdata->type == json_type_object) { \ + struct json_value_s* r##name = json_find(regdata->payload, #name); \ + if (r##name && r##name->type == json_type_array) { \ + struct json_array_s* array = (struct json_array_s*)r##name->payload; \ + json_fill_array(array, ymmregs[i]); \ + check_xmmregs[i] = true; \ + if (array->length == 4) check_ymmregs[i] = true; \ + } \ + i++; \ + } + + REG(XMM0); + REG(XMM1); + REG(XMM2); + REG(XMM3); + REG(XMM4); + REG(XMM5); + REG(XMM6); + REG(XMM7); + REG(XMM8); + REG(XMM9); + REG(XMM10); + REG(XMM11); + REG(XMM12); + REG(XMM13); + REG(XMM14); + REG(XMM15); +#undef REG + + struct json_value_s* mode = json_find(config->payload, "Mode"); + if (mode && mode->type == json_type_string && !strcmp(((struct json_string_s*)mode->payload)->string, "32BIT")) { + box64_is32bits = true; + printf_log(LOG_INFO, "Test is in 32bits mode\n"); + } + + struct json_value_s* json_memory_regions = json_find(config->payload, "MemoryRegions"); + if (json_memory_regions && json_memory_regions->type == json_type_object) { + struct json_object_s* object = (struct json_object_s*)json_memory_regions->payload; + struct json_object_element_s* element = object->start; + i = 0; + while (element && i < MAX_MEMORY_REGIONS) { + struct json_string_s* element_key = element->name; + struct json_value_s* value = element->value; + assert(value->type == json_type_string); + struct json_string_s* element_value = (struct json_string_s*)value->payload; + memory_regions[i].start = fromstr(element_key->string); + memory_regions[i].size = fromstr(element_value->string); + i++; + element = element->next; + } + } + + struct json_value_s* json_memory_data = json_find(config->payload, "MemoryData"); + if (json_memory_data && json_memory_data->type == json_type_object) { + struct json_object_s* object = (struct json_object_s*)json_memory_data->payload; + struct json_object_element_s* element = object->start; + i = 0; + while (element && i < MAX_MEMORY_DATA) { + struct json_string_s* element_key = element->name; + struct json_value_s* value = element->value; + assert(value->type == json_type_string); + struct json_string_s* element_value = (struct json_string_s*)value->payload; + memory_data[i].start = fromstr(element_key->string); + uint8_t* data = (uint8_t*)element_value->string; + while (*data) { + if (*data == ' ') { + ++data; + continue; + } + if (*data == '0' && (*(data + 1) == 'x' || *(data + 1) == 'X')) { + data += 2; + } + uint8_t* lastbyte = data; + while (*lastbyte && *lastbyte != ' ' && *(lastbyte + 1) && *(lastbyte + 1) != ' ') { + lastbyte += 2; + } + size_t len = lastbyte - data; + if (len < 2) { + printf_log(LOG_NONE, "Invalid MemoryData item %s\n", element_key->string); + break; + } + lastbyte -= 2; + static uint8_t byte_str[3] = { 0 }; + while (lastbyte >= data) { + byte_str[0] = *lastbyte; + byte_str[1] = *(lastbyte + 1); + memory_data[i].data[memory_data[i].size] = (uint8_t)strtol((const char*)byte_str, NULL, 16); + memory_data[i].size++; + if (memory_data[i].size >= MAX_MEMORY_DATA_SIZE) { + printf_log(LOG_NONE, "MemoryData item %s too big (max=%d bytes)\n", element_key->string, MAX_MEMORY_DATA_SIZE); + break; + } + lastbyte -= 2; + } + data += len; + } + i++; + element = element->next; + } + } + +#define BINNAME "/tmp/binfileXXXXXX" + char* binname = box_malloc(strlen(BINNAME) + 1); + memcpy(binname, BINNAME, strlen(BINNAME) + 1); +#undef BINNAME + char objname[] = "/tmp/objfileXXXXXX"; + + int fd; + fd = mkstemp(objname); + close(fd); + + const char* nasm_cmd[] = { NASM, *filepath, box64_is32bits ? "-felf32" : "-felf64", "-o", objname, + include_path ? "-i" : NULL, include_path ? include_path : NULL, NULL }; + pid_t fork_result = fork(); + if (fork_result == 0) { + execvp(nasm_cmd[0], (char* const*)nasm_cmd); + exit(1); + } else { + int status; + waitpid(fork_result, &status, 0); + if (WIFEXITED(status) && WEXITSTATUS(status) != 0) { + printf_log(LOG_NONE, NASM " failed with exit code: %d\n", WEXITSTATUS(status)); + exit(1); + } + } + + close(mkstemp(binname)); + const char* ld_cmd[] = { X86_64_LD, objname, "-Ttext=0x10000", "-w", "-m", box64_is32bits ? "elf_i386" : "elf_x86_64", "-o", binname, NULL }; + + fork_result = fork(); + if (fork_result == 0) { + execvp(ld_cmd[0], (char* const*)ld_cmd); + exit(1); + } else { + int status; + waitpid(fork_result, &status, 0); + if (WIFEXITED(status) && WEXITSTATUS(status) != 0) { + printf_log(LOG_NONE, X86_64_LD " failed with exit code: %d\n", WEXITSTATUS(status)); + exit(1); + } + } + + printf_log(LOG_DEBUG, "Test binary compiled as %s\n", binname); + *filepath = binname; + + unlink(objname); + free(config); +} + +static void setupZydis(box64context_t* context) +{ +#ifdef HAVE_TRACE + if ((BOX64ENV(trace_init) && strcmp(BOX64ENV(trace_init), "0")) || (BOX64ENV(trace) && strcmp(BOX64ENV(trace), "0"))) { + context->x64trace = 1; + } + if (context->x64trace) { + printf_log(LOG_INFO, "Initializing Zydis lib\n"); + if (InitX64Trace(context)) { + printf_log(LOG_INFO, "Zydis init failed. No x86 trace activated\n"); + context->x64trace = 0; + } + } +#endif +} + +int unittest(int argc, const char** argv) +{ + if (argc < 3 || (strcmp(argv[1], "--test") && strcmp(argv[1], "-t"))) { + printf_log(LOG_NONE, "Usage: %s -t <filepath> [-i <include>]\n", argv[0]); + return 0; + } + + const char* include_path = NULL; + if (argc > 4 && (!strcmp(argv[3], "-i") || !strcmp(argv[3], "--include"))) { + include_path = argv[4]; + } + + box64_pagesize = 4096; + LoadEnvVariables(); + ftrace = stdout; + if (!BOX64ENV(nobanner)) PrintBox64Version(1); + +#ifdef DYNAREC + if (DetectHostCpuFeatures()) + PrintHostCpuFeatures(); + else { + printf_log(LOG_INFO, "Minimum CPU requirements not met, disabling DynaRec\n"); + SET_BOX64ENV(dynarec, 0); + } +#endif + + PrintEnvVariables(&box64env, LOG_INFO); + my_context = NewBox64Context(argc - 1); + + loadTest(&argv[2], include_path); // will modify argv[2] to point to the binary file + my_context->fullpath = box_strdup(argv[2]); + + FILE* f = fopen(my_context->fullpath, "rb"); + unlink(my_context->fullpath); + + elfheader_t* elf_header = LoadAndCheckElfHeader(f, my_context->fullpath, 1); + AddElfHeader(my_context, elf_header); + CalcLoadAddr(elf_header); + AllocLoadElfMemory(my_context, elf_header, 1); + + setupZydis(my_context); + my_context->ep = GetEntryPoint(my_context->maplib, elf_header); + + my_context->stack = (void*)0xC0000000; + my_context->stacksz = 4096; + mmap((void*)my_context->stack, my_context->stacksz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); + mmap((void*)0xE0000000, 16 * 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); + mmap((void*)0xE800F000, 2 * 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); + + for (int i = 0; i < MAX_MEMORY_REGIONS && memory_regions[i].size; ++i) { + if (!memory_regions[i].start) break; + mmap((void*)memory_regions[i].start, memory_regions[i].size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); + } + + for (int i = 0; i < MAX_MEMORY_DATA && memory_data[i].size; ++i) { + if (!memory_data[i].start) break; + if (!memory_data[i].size) continue; + memcpy((void*)memory_data[i].start, memory_data[i].data, memory_data[i].size); + } + + x64emu_t* emu = NewX64Emu(my_context, my_context->ep, + (uintptr_t)my_context->stack, my_context->stacksz, 0); + + ResetFlags(emu); + SetRIP(emu, my_context->ep); + DynaRun(emu); + + int retcode = 0; + for (int i = 0; i < 16; ++i) { + if (check_regs[i]) { + if (regs[i] != emu->regs[_RAX + i].q[0]) { + printf_log(LOG_NONE, "%-5s: expected %016zx, got %016zx\n", regname[i], regs[i], emu->regs[_RAX + i].q[0]); + retcode += 1; + } + } + } + + for (int i = 0; i < 16; ++i) { + if (check_xmmregs[i]) { + if (ymmregs[i][0] != emu->xmm[i].q[0] || ymmregs[i][1] != emu->xmm[i].q[1]) { + printf_log(LOG_NONE, "XMM%-2d: expected %016zx-%016zx, got %016zx-%016zx\n", i, ymmregs[i][1], ymmregs[i][0], emu->xmm[i].q[1], emu->xmm[i].q[0]); + retcode += 1; + } + } + if (check_ymmregs[i]) { + if (ymmregs[i][2] != emu->ymm[i].q[0] || ymmregs[i][3] != emu->ymm[i].q[1]) { + printf_log(LOG_NONE, "YMM%-2d: expected %016zx-%016zx, got %016zx-%016zx\n", i, ymmregs[i][3], ymmregs[i][2], emu->ymm[i].q[1], emu->ymm[i].q[0]); + retcode += 1; + } + } + } + + if (retcode == 0) + printf_log(LOG_INFO, "Passed\n"); + else + printf_log(LOG_NONE, "Failed with %d errors\n", retcode); + + + return retcode; +} |