#define _OVM_DEBUG_H
#include "bh.h"
+#include <semaphore.h>
+#include <stdbool.h>
typedef struct debug_loc_info_t {
u32 file_id;
u32 debug_op_offset;
} debug_func_info_t;
+typedef struct debug_file_info_t {
+ char *name;
+ u32 line_count;
+ i32 line_buffer_offset;
+} debug_file_info_t;
+
typedef struct debug_info_t {
bh_allocator alloc;
+ // func index -> func info
bh_arr(debug_func_info_t) funcs;
+
+ // reducer output -> line info
bh_arr(debug_loc_info_t) line_info;
+
+ // instruction index -> reducer output
bh_arr(u32) instruction_reducer;
- bh_arr(char *) file_names;
+ // line index -> instruction index
+ bh_arr(u32) line_to_instruction;
+
+ // file_id -> file info
+ bh_arr(debug_file_info_t) files;
} debug_info_t;
void debug_info_init(debug_info_t *);
void debug_info_import_file_info(debug_info_t *, u8 *data, u32 len);
void debug_info_import_func_info(debug_info_t *, u8 *data, u32 len);
+bool debug_info_lookup_location(debug_info_t *info, u32 instruction, debug_loc_info_t *out);
+bool debug_info_lookup_file(debug_info_t *info, u32 file_id, debug_file_info_t *out);
+bool debug_info_lookup_file_by_name(debug_info_t *info, char *name, debug_file_info_t *out);
+
//
// This builder is used in conjunction with code builder to output
// debug information for each instruction that is generated in OVM.
u32 current_file_id;
u32 current_line;
+ u32 next_file_line_offset;
bh_arr(char) symbol_scope_stack;
u32 remaining_reps;
-
+
b32 locked : 1;
} debug_info_builder_t;
void debug_info_builder_begin_func(debug_info_builder_t *, i32 func_idx);
void debug_info_builder_end_func(debug_info_builder_t *);
-#endif
\ No newline at end of file
+
+typedef enum debug_exec_state_t {
+ debug_state_starting,
+ debug_state_ready,
+ debug_state_running,
+ debug_state_paused
+} debug_exec_state_t;
+
+typedef struct debug_thread_state_t {
+ u32 id;
+
+ debug_exec_state_t state;
+ struct ovm_state_t *ovm_state;
+
+ i32 run_count;
+ sem_t wait_semaphore;
+
+ bh_arr(u32) breakpoints;
+} debug_thread_state_t;
+
+//
+// This represents known state of the debugger. There should only
+// be one state per running program, as it is tied to the ovm_engine_t.
+//
+typedef struct debug_state_t {
+ bh_allocator alloc;
+
+ debug_info_t *info;
+
+ bh_arr(debug_thread_state_t *) threads;
+ u32 next_thread_id;
+
+ pthread_t debug_thread;
+ bool debug_thread_running;
+
+ u32 listen_socket_fd;
+ u32 client_fd;
+} debug_state_t;
+
+void debug_host_init(debug_state_t *debug);
+void debug_host_start(debug_state_t *debug);
+void debug_host_stop(debug_state_t *debug);
+u32 debug_host_register_thread(debug_state_t *debug, struct ovm_state_t *ovm_state);
+debug_thread_state_t *debug_host_lookup_thread(debug_state_t *debug, u32 id);
+
+#endif
#define _ONYX_VM_H
#include "bh.h"
+#include "ovm_debug.h"
#include <stdbool.h>
#include <pthread.h>
i64 memory_size; // This is probably going to always be 4GiB.
void *memory;
+
+ debug_state_t *debug;
};
ovm_engine_t *ovm_engine_new(ovm_store_t *store);
// running instances of the program *could* have different
// native functions linked.
bh_arr(ovm_external_func_t) external_funcs;
+
+ debug_thread_state_t *debug;
};
ovm_state_t *ovm_state_new(ovm_engine_t *engine, ovm_program_t *program);
--- /dev/null
+
+#include "ovm_debug.h"
+#include "vm.h"
+
+void debug_host_init(debug_state_t *debug) {
+ memset(debug, 0, sizeof(*debug));
+ debug->alloc = bh_heap_allocator();
+
+ debug->info = NULL;
+
+ debug->threads = NULL;
+ debug->next_thread_id = 0;
+ bh_arr_new(debug->alloc, debug->threads, 4);
+
+ debug->listen_socket_fd = 0;
+ debug->client_fd = 0;
+}
+
+static void *debug_thread_entry(void *);
+
+void debug_host_start(debug_state_t *debug) {
+ if (debug->debug_thread_running) return;
+
+ pthread_create(&debug->debug_thread, NULL, debug_thread_entry, debug);
+}
+
+void debug_host_stop(debug_state_t *debug) {
+ debug->debug_thread_running = false;
+ pthread_join(debug->debug_thread, NULL);
+}
+
+u32 debug_host_register_thread(debug_state_t *debug, ovm_state_t *ovm_state) {
+ debug_thread_state_t *new_thread = bh_alloc(debug->alloc, sizeof(*new_thread));
+
+ new_thread->state = debug_state_starting;
+ new_thread->ovm_state = ovm_state;
+ new_thread->run_count = 0; // Start threads in stopped state.
+ sem_init(&new_thread->wait_semaphore, 0, 0);
+
+ new_thread->breakpoints = NULL;
+ bh_arr_new(debug->alloc, new_thread->breakpoints, 8);
+
+ u32 id = debug->next_thread_id++;
+ new_thread->id = id;
+
+ bh_arr_push(debug->threads, new_thread);
+ return id;
+}
+
+debug_thread_state_t *debug_host_lookup_thread(debug_state_t *debug, u32 id) {
+ bh_arr_each(debug_thread_state_t *, pthread, debug->threads) {
+ if ((*pthread)->id == id) return *pthread;
+ }
+ return NULL;
+}
+
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+static void *debug_thread_entry(void *data) {
+ debug_state_t *debug = data;
+ debug->debug_thread_running = true;
+
+ // Set up socket listener
+ // Wait for initial connection/handshake before entering loop?
+
+ debug->listen_socket_fd = socket(AF_UNIX, SOCK_STREAM, 0);
+
+ struct sockaddr_un local_addr, remote_addr;
+ local_addr.sun_family = AF_UNIX;
+ strcpy(local_addr.sun_path, "/tmp/ovm-debug.0000"); // TODO: Make this dynamic so mulitple servers can exist at a time.
+ unlink(local_addr.sun_path); // TODO: Remove this line for the same reason.
+ int len = strlen(local_addr.sun_path) + sizeof(local_addr.sun_family);
+ bind(debug->listen_socket_fd, (struct sockaddr *)&local_addr, len);
+
+ //
+ // Currently, there can only be 1 connected debugger instance at a time.
+ listen(debug->listen_socket_fd, 1);
+
+ len = sizeof(struct sockaddr_un);
+ debug->client_fd = accept(debug->listen_socket_fd, (void * restrict)&remote_addr, &len);
+
+ close(debug->listen_socket_fd);
+
+ printf("Client connected");
+
+ while (debug->debug_thread_running) {
+ // ...
+
+ }
+
+ close(debug->client_fd);
+
+ return NULL;
+}
bh_arr_new(info->alloc, info->funcs, 16);
bh_arr_new(info->alloc, info->line_info, 1024);
bh_arr_new(info->alloc, info->instruction_reducer, 4096);
- bh_arr_new(info->alloc, info->file_names, 16);
+ bh_arr_new(info->alloc, info->files, 16);
+ bh_arr_new(info->alloc, info->line_to_instruction, 1024);
}
void debug_info_free(debug_info_t *info) {
bh_arr_free(info->line_info);
bh_arr_free(info->instruction_reducer);
- bh_arr_each(char *, name, info->file_names) bh_free(info->alloc, *name);
- bh_arr_free(info->file_names);
+ bh_arr_each(debug_file_info_t, file, info->files) {
+ bh_free(info->alloc, file->name);
+ }
+ bh_arr_free(info->files);
}
void debug_info_import_file_info(debug_info_t *info, u8 *data, u32 len) {
i32 count = uleb128_to_uint(data, &offset);
fori (i, 0, (i32) count) {
+ debug_file_info_t file_info;
+ file_info.line_buffer_offset = -1;
+
u32 file_id = uleb128_to_uint(data, &offset);
+ file_info.line_count = uleb128_to_uint(data, &offset);
+
u32 name_length = uleb128_to_uint(data, &offset);
- char *name = bh_alloc_array(info->alloc, char, name_length + 1);
- memcpy(name, data + offset, name_length);
- name[name_length] = 0;
+ file_info.name = bh_alloc_array(info->alloc, char, name_length + 1);
+ memcpy(file_info.name, data + offset, name_length);
+ file_info.name[name_length] = 0;
offset += name_length;
- bh_arr_set_at(info->file_names, file_id, name);
+ bh_arr_set_at(info->files, file_id, file_info);
}
assert(offset == len);
assert(offset == len);
}
+
+bool debug_info_lookup_location(debug_info_t *info, u32 instruction, debug_loc_info_t *out) {
+ if (instruction > (u32) bh_arr_length(info->instruction_reducer)) return false;
+ *out = info->line_info[info->instruction_reducer[instruction]];
+ return true;
+}
+
+bool debug_info_lookup_file(debug_info_t *info, u32 file_id, debug_file_info_t *out) {
+ if (file_id > (u32) bh_arr_length(info->files)) return false;
+ *out = info->files[file_id];
+ return true;
+}
+
+//
+// For now, this is going to compare the strings exactly. In the future, it might be a good
+// to do a levenschtein distance or something, so the full path isn't needed.
+bool debug_info_lookup_file_by_name(debug_info_t *info, char *name, debug_file_info_t *out) {
+ bh_arr_each(debug_file_info_t, file, info->files) {
+ if (!strcmp(file->name, name)) {
+ *out = *file;
+ return true;
+ }
+ }
+
+ return false;
+}
builder->reader_offset = 0;
builder->current_file_id = 0;
builder->current_line = 0;
+ builder->next_file_line_offset = 0;
builder->remaining_reps = 0;
}
static void debug_info_builder_parse(debug_info_builder_t *builder) {
u32 count = 0;
- while (1) {
+ while (!builder->locked) {
u8 instr = builder->data[builder->reader_offset++];
switch (instr & 0b11000000) {
case 0b00000000:
instr &= 0b00111111;
switch (instr) {
- case 0: builder->locked = 1; break;
+ case 0: builder->locked = 1; break; // Early return!
case 1:
builder->current_file_id = uleb128_to_uint(builder->data, &builder->reader_offset);
builder->current_line = uleb128_to_uint(builder->data, &builder->reader_offset);
}
void debug_info_builder_step(debug_info_builder_t *builder) {
- while (builder->remaining_reps == 0) {
+ if (builder->data == NULL) return;
+
+ while (builder->remaining_reps == 0 && !builder->locked) {
debug_info_builder_parse(builder);
debug_loc_info_t info;
info.line = builder->current_line;
info.symbols = 0;
bh_arr_push(builder->info->line_info, info);
+
+ debug_file_info_t *file_info = &builder->info->files[info.file_id];
+ if (file_info->line_buffer_offset == -1) {
+ file_info->line_buffer_offset = builder->next_file_line_offset;
+ builder->next_file_line_offset += file_info->line_count;
+ }
+
+ u32 line_index = file_info->line_buffer_offset + builder->current_line;
+ u32 target = bh_arr_length(builder->info->instruction_reducer) - 1;
+ bh_arr_set_at(builder->info->line_to_instruction, line_index, target);
}
if (builder->locked) return;
+ assert(builder->remaining_reps);
builder->remaining_reps -= 1;
return;
}
}
void debug_info_builder_begin_func(debug_info_builder_t *builder, i32 func_idx) {
- assert(func_idx < bh_arr_length(builder->info->funcs));
+ if (func_idx >= bh_arr_length(builder->info->funcs)) return;
+
debug_func_info_t *func_info = &builder->info->funcs[func_idx];
builder->reader_offset = func_info->debug_op_offset;
- assert(builder->reader_offset < 20000);
+ assert(builder->data[builder->reader_offset] == 2);
+ assert(builder->data[builder->reader_offset+1] == 1);
+ builder->remaining_reps = 0;
builder->locked = 0;
}
void debug_info_builder_end_func(debug_info_builder_t *builder) {
+ assert(!builder->locked);
+ debug_info_builder_step(builder);
+ assert(builder->locked);
}
void ovm_program_modify_static_int(ovm_program_t *program, int arr, int idx, int new_value) {
if (arr >= bh_arr_length(program->static_data)) return;
-
+
ovm_static_integer_array_t array = program->static_data[arr];
if (idx >= array.len) return;
engine->memory = mmap(NULL, engine->memory_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
pthread_mutex_init(&engine->atomic_mutex, NULL);
+ engine->debug = NULL;
+
return engine;
}
state->external_funcs = NULL;
bh_arr_new(store->heap_allocator, state->external_funcs, 8);
+ if (engine->debug) {
+ u32 thread_id = debug_host_register_thread(engine->debug, state);
+ state->debug = debug_host_lookup_thread(engine->debug, thread_id);
+ }
+
#ifdef OVM_VERBOSE
ovm_program_print_instructions(program, 0, bh_arr_length(program->code));
-#endif
+#endif
return state;
}
bool release_mutex_at_end = false;
ovm_value_t tmp_val;
-
+
while (state->pc < bh_arr_length(program->code)) {
+ // This will become the line change detection
+ // debug_loc_info_t loc_info;
+ // debug_info_lookup_location(engine->debug->info, state->pc, &loc_info);
+ // if (loc_info.file_id != last_file || loc_info.line != last_line) {
+ // last_file = loc_info.file_id;
+ // last_line = loc_info.line;
+
+ // debug_file_info_t file_info;
+ // debug_info_lookup_file(engine->debug->info, last_file, &file_info);
+ //
+ // printf("(%d, %d) %s:%d\n", last_file, last_line, file_info.name, last_line);
+ // }
+
#ifdef OVM_VERBOSE
ovm_program_print_instructions(program, state->pc, 1);
#endif
VAL(instr.r).u64 = 0; \
VAL(instr.r).dtype = instr.stype; \
break;
-
+
OVM_IMM(OVM_TYPE_I8, i8, i)
OVM_IMM(OVM_TYPE_I16, i16, i)
OVM_IMM(OVM_TYPE_I32, i32, i)
case OVMI_RETURN: {
ovm_value_t val = VAL(instr.a);
- ovm_stack_frame_t frame = ovm__func_teardown_stack_frame(engine, state, program);
+ ovm_stack_frame_t frame = ovm__func_teardown_stack_frame(engine, state, program);
state->pc = frame.return_address;
if (bh_arr_length(state->stack_frames) == 0) {
}
#ifdef OVM_VERBOSE
- printf("Returning from %s to %s: ", frame.func->name, bh_arr_last(state->stack_frames).func->name);
+ printf("Returning from %s to %s: ", frame.func->name, bh_arr_last(state->stack_frames).func->name);
ovm_print_val(val);
printf("\n\n");
#endif
-
+
break;
}
case OVM_TYPED_INSTR(OVMI_CVT_I64_S, OVM_TYPE_I8): CVT(i64, i8, OVM_TYPE_I8, i8);
case OVM_TYPED_INSTR(OVMI_CVT_I64_S, OVM_TYPE_I16): CVT(i64, i16, OVM_TYPE_I16, i16);
case OVM_TYPED_INSTR(OVMI_CVT_I64_S, OVM_TYPE_I32): CVT(i64, i32, OVM_TYPE_I32, i32);
-
+
case OVM_TYPED_INSTR(OVMI_CVT_I64, OVM_TYPE_F32): CVT(u64, f32, OVM_TYPE_F32, f32);
case OVM_TYPED_INSTR(OVMI_CVT_I64_S, OVM_TYPE_F32): CVT(i64, f32, OVM_TYPE_F32, f32);
case OVM_TYPED_INSTR(OVMI_CVT_I64, OVM_TYPE_F64): CVT(u64, f64, OVM_TYPE_F64, f64);
return ((ovm_value_t) {0});
}
-
#include "vm.h"
wasm_engine_t *wasm_engine_new() {
+ wasm_engine_t *engine = wasm_engine_new_with_config(NULL);
+ return engine;
+}
+
+wasm_engine_t *wasm_engine_new_with_config(wasm_config_t *config) {
ovm_store_t *store = ovm_store_new();
wasm_engine_t *engine = bh_alloc_item(store->heap_allocator, wasm_engine_t);
- engine->config = NULL;
+ engine->config = config;
engine->store = store;
ovm_engine_t *ovm_engine = ovm_engine_new(store);
engine->engine = ovm_engine;
- return engine;
-}
-wasm_engine_t *wasm_engine_new_with_config(wasm_config_t *config) {
- wasm_engine_t *engine = wasm_engine_new();
- engine->config = config;
+ if (config && config->debug_enabled) {
+ // This should maybe be moved elsewhere?
+ debug_state_t *debug = bh_alloc_item(store->heap_allocator, debug_state_t);
+ engine->engine->debug = debug;
+
+ debug_host_init(engine->engine->debug);
+ debug_host_start(engine->engine->debug);
+ }
+
return engine;
}
void wasm_engine_delete(wasm_engine_t *engine) {
+ if (engine->engine->debug) {
+ debug_host_stop(engine->engine->debug);
+ }
+
ovm_store_t *store = engine->store;
ovm_engine_delete(engine->engine);
bh_free(store->heap_allocator, engine);
debug_info_init(&module->debug_info);
+ if (store->engine->engine->debug) {
+ assert(store->engine->engine->debug->info == NULL);
+ store->engine->engine->debug->info = &module->debug_info;
+ }
+
bool success = module_build(module, binary);
return module;
}
int func_table_arr_idx;
int next_external_func_idx;
+
+ debug_info_builder_t debug_builder;
// This will be set/reset for every code (function) entry.
ovm_code_builder_t builder;
- debug_info_builder_t debug_builder;
};
#define PEEK_BYTE(ctx) ((ctx)->binary.data[(ctx)->offset])
}
static void parse_instruction(build_context *ctx) {
+ debug_info_builder_step(&ctx->debug_builder);
+
unsigned char instr_byte = CONSUME_BYTE(ctx);
switch (instr_byte) {
case 0x00: break;
default: assert(("UNHANDLED INSTRUCTION", 0));
}
-
- debug_info_builder_step(&ctx->debug_builder);
}
static void parse_expression(build_context *ctx) {