outputting debug info into WASM binary
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Tue, 26 Jul 2022 23:10:44 +0000 (18:10 -0500)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Tue, 26 Jul 2022 23:10:44 +0000 (18:10 -0500)
include/bh.h
include/wasm_emit.h
lib/linux_x86_64/lib/libovmwasm.so
lib/linux_x86_64/libovmwasm.so [deleted file]
src/wasm_emit.c
src/wasm_intrinsics.h
src/wasm_output.h

index 2d941eba9fcf86d27fc260bbe0fb30bb8716f09a..536ab38b7d83b2b43e821a40b7d848f01894201d 100644 (file)
@@ -509,6 +509,7 @@ typedef struct bh_buffer {
 
 void bh_buffer_init(bh_buffer* buffer, bh_allocator alloc, i32 length);
 void bh_buffer_free(bh_buffer* buffer);
+void bh_buffer_clear(bh_buffer* buffer);
 void bh_buffer_grow(bh_buffer* buffer, i32 length);
 void bh_buffer_append(bh_buffer* buffer, const void * data, i32 length);
 void bh_buffer_concat(bh_buffer* buffer, bh_buffer other);
@@ -2153,6 +2154,10 @@ void bh_buffer_free(bh_buffer* buffer) {
     buffer->capacity = 0;
 }
 
+void bh_buffer_clear(bh_buffer* buffer) {
+    buffer->length = 0;
+}
+
 void bh_buffer_grow(bh_buffer* buffer, i32 length) {
     if (buffer == NULL) return;
 
index d02bdea42f874466f2e87642525804a9c8171d1f..d2c6039137f45f4c3c527a9532fda477d4d60dce 100644 (file)
@@ -791,31 +791,41 @@ b32 onyx_run_wasm(bh_buffer code_buffer, int argc, char *argv[]);
 
 #ifdef ENABLE_DEBUG_INFO
 
-typedef struct DebugLocation {
-    u32 file_id;
-    u32 line;
-    u32 repeat;
-} DebugLocation;
+typedef enum DebugOpType {
+    DOT_END   = 0b00000000,
+    DOT_SET   = 0b00000001,
+    DOT_PUSHF = 0b00000010,
+    DOT_POPF  = 0b00000011,
+    DOT_SYM   = 0b00000100,
+
+    DOT_INC   = 0b01000000,
+    DOT_DEC   = 0b10000000,
+    DOT_REP   = 0b11000000,
+} DebugOpType;
 
 typedef struct DebugFuncContext {
     u32 func_index;
-    bh_arr_each(DebugLocation) locations;
+    u32 file_id;
+    u32 line;
+    u32 op_offset;
+    u32 stack_ptr_idx;
+
+    u32 name_length;
+    char *name;
 } DebugFuncContext;
 
 typedef struct DebugContext {
     bh_allocator allocator;
 
-    // file_names[file_ids["file"]] == "file"
-    // file_ids[file_name[123]] == 123
-    Table(u32)          file_ids;
-    bh_arr_each(char *) file_names;
+    Table(u32) file_ids;
+    u32 next_file_id;
 
-    bh_arr_each(DebugFuncContext *) 
+    bh_arr(DebugFuncContext) funcs;
+    bh_buffer                op_buffer;
 
     // Used during building the debug info
     OnyxToken *last_token;
-    DebugFucnContext *current_func;
-    
+    b32 last_op_was_rep : 1;
 } DebugContext;
 
 #endif
index 704e28d056897d172d459df21a5d76d45921770a..1a53a55a0f180bd02da557de307f60bc485d76ab 100755 (executable)
Binary files a/lib/linux_x86_64/lib/libovmwasm.so and b/lib/linux_x86_64/lib/libovmwasm.so differ
diff --git a/lib/linux_x86_64/libovmwasm.so b/lib/linux_x86_64/libovmwasm.so
deleted file mode 100755 (executable)
index d8250e7..0000000
Binary files a/lib/linux_x86_64/libovmwasm.so and /dev/null differ
index 36a872fedc745800a564a796f83d4721b43b045f..a6482f74d16de2546f81247f4eb0e596228a4aae 100644 (file)
@@ -219,26 +219,146 @@ static inline b32 should_emit_function(AstFunction* fd) {
 
 #ifdef ENABLE_DEBUG_INFO
 
+static u32 debug_get_file_id(OnyxWasmModule *mod, const char *name) {
+    assert(mod && mod->debug_context);
+
+    i32 index = shgeti(mod->debug_context->file_ids, name);
+    if (index == -1) {
+        u32 id = mod->debug_context->next_file_id++;
+        shput(mod->debug_context->file_ids, name, id);
+
+        return id;
+    }
+
+    return mod->debug_context->file_ids[index].value;
+}
+
 static void debug_set_position(OnyxWasmModule *mod, OnyxToken *token) {
+    i32 file_id = debug_get_file_id(mod, token->pos.filename);
+
+    bh_buffer_write_byte(&mod->debug_context->op_buffer, DOT_SET);
+    mod->debug_context->last_op_was_rep = 0;
+
+    u32 leb_len=0;
+    u8 *bytes = uint_to_uleb128(file_id, &leb_len);
+    bh_buffer_append(&mod->debug_context->op_buffer, bytes, leb_len);
+
+    bytes = uint_to_uleb128(token->pos.line, &leb_len);
+    bh_buffer_append(&mod->debug_context->op_buffer, bytes, leb_len);
+
     mod->debug_context->last_token = token;
 }
 
 // Called for every instruction being emitted
+// This has to emit either:
+//    - INC
+//    - DEC
+//    - REP
+//    - SET, REP 0
 static void debug_emit_instruction(OnyxWasmModule *mod, OnyxToken *token) {
     DebugContext *ctx = mod->debug_context; 
-    assert(ctx);
+    assert(ctx && ctx->last_token);
+
+    // Sanity check
+    if (ctx->last_op_was_rep) {
+        assert((ctx->op_buffer.data[ctx->op_buffer.length - 1] & DOT_REP) == DOT_REP);
+    }
+    i32 file_id, old_file_id;
+
+    b32 repeat_previous = 0;
+    if (!token || !token->pos.filename) {
+        repeat_previous = 1;
+
+    } else {
+        file_id = debug_get_file_id(mod, token->pos.filename);
+        old_file_id = debug_get_file_id(mod, ctx->last_token->pos.filename);
+        if (old_file_id == file_id && token->pos.line == ctx->last_token->pos.line) {
+            repeat_previous = 1;
+        }
+    }
+    
+    if (repeat_previous) {
+        // Output / increment REP instruction
+        if (ctx->last_op_was_rep) {
+            u8 rep_count = ctx->op_buffer.data[ctx->op_buffer.length - 1] & 0b00111111;
+            if (rep_count != 63) {
+                rep_count += 1;
+                ctx->op_buffer.data[ctx->op_buffer.length - 1] = DOT_REP | rep_count;
+                return;
+            }
+        }
 
-    DebugFuncContext *func_ctx = ctx->current_func;
-    assert(func_ctx);
+        bh_buffer_write_byte(&mod->debug_context->op_buffer, DOT_REP);
+        ctx->last_op_was_rep = 1;
+        return;
+    }
 
-    if ()
+    // At this point, token is definitely non-null.
+    ctx->last_op_was_rep = 0;
 
-    if (shgeti(ctx->file_ids, ctx->last_token->pos.filename) == -1) {
-        // File name has not been seen before, allocate a slot for it.
+    if (old_file_id == file_id) {
+        // We see if we can INC/DEC to get to the line number
+        if (ctx->last_token->pos.line < token->pos.line) {
+            u32 diff = token->pos.line - ctx->last_token->pos.line;
+            if (diff <= 64) {
+                bh_buffer_write_byte(&mod->debug_context->op_buffer, DOT_INC | (diff - 1));
+                goto done;
+            }
+        }
 
+        if (ctx->last_token->pos.line > token->pos.line) {
+            u32 diff = ctx->last_token->pos.line - token->pos.line;
+            if (diff <= 64) {
+                bh_buffer_write_byte(&mod->debug_context->op_buffer, DOT_DEC | (diff - 1));
+                goto done;
+            }
+        }
     }
+
+    // Otherwise, we need to output a SET, followed by a REP 0,
+    // which is what set_position does.
+    debug_set_position(mod, token);
+    bh_buffer_write_byte(&mod->debug_context->op_buffer, DOT_REP);
+    ctx->last_op_was_rep = 1;
+
+  done:
+    ctx->last_token = token;
 }
 
+static void debug_begin_function(OnyxWasmModule *mod, u32 func_idx, OnyxToken *token, char *name) {
+    u32 file_id = debug_get_file_id(mod, token->pos.filename);
+    u32 line    = token->pos.line;
+
+    assert(mod->debug_context);
+
+    DebugFuncContext func;
+    func.func_index = func_idx;
+    func.file_id = file_id;
+    func.line    = line;
+    func.op_offset = mod->debug_context->op_buffer.length;
+    func.stack_ptr_idx = 0;
+    func.name_length = strlen(name);
+    func.name = name;
+    bh_arr_push(mod->debug_context->funcs, func);
+
+    bh_buffer_write_byte(&mod->debug_context->op_buffer, DOT_PUSHF);
+    debug_set_position(mod, token);
+}
+
+static void debug_end_function(OnyxWasmModule *mod) {
+    bh_buffer_write_byte(&mod->debug_context->op_buffer, DOT_POPF);
+    bh_buffer_write_byte(&mod->debug_context->op_buffer, DOT_END);
+    mod->debug_context->last_op_was_rep = 0;
+}
+
+#else
+
+#define debug_get_file_id(mod, name) (void)0
+#define debug_set_position(mod, token) (void)0
+#define debug_emit_instruction(mod, token) (void)0
+#define debug_begin_function(mod, idx, token, name) (void)0
+#define debug_end_function(mod) (void)0
+
 #endif
 
 
@@ -487,7 +607,7 @@ EMIT_FUNC(statement, AstNode* stmt) {
     bh_arr(WasmInstruction) code = *pcode;
 
 #ifdef ENABLE_DEBUG_INFO
-    debug_set_position(stmt->token);
+    debug_set_position(mod, stmt->token);
 #endif
 
     switch (stmt->kind) {
@@ -889,19 +1009,19 @@ EMIT_FUNC(while, AstIfWhile* while_node) {
 
         if (!while_node->bottom_test) {
             emit_expression(mod, &code, while_node->cond);
-            WI(while_cond->token, WI_I32_EQZ);
-            WID(while_cond->token, WI_COND_JUMP, 0x01);
+            WI(NULL, WI_I32_EQZ);
+            WID(NULL, WI_COND_JUMP, 0x01);
         }
 
         emit_block(mod, &code, while_node->true_stmt, 0);
 
         if (while_node->bottom_test) {
             emit_expression(mod, &code, while_node->cond);
-            WID(while_node->cond, WI_COND_JUMP, 0x00);
+            WID(while_node->cond->token, WI_COND_JUMP, 0x00);
 
         } else {
             if (bh_arr_last(code).type != WI_JUMP)
-                WID(while_node->cond, WI_JUMP, 0x00);
+                WID(while_node->cond->token, WI_JUMP, 0x00);
         }
 
         emit_leave_structured_block(mod, &code);
@@ -1452,14 +1572,14 @@ EMIT_FUNC(remove_directive, AstDirectiveRemove* remove) {
 
     ForRemoveInfo remove_info = bh_arr_last(mod->for_remove_info);
 
-    WIL(remote->token, WI_LOCAL_GET, remove_info.iterator_remove_func);
-    WIL(remote->token, WI_I32_CONST, mod->null_proc_func_idx);
-    WI(remote->token, WI_I32_NE);
-    WID(remote->token, WI_IF_START, 0x40);
-    WIL(remote->token, WI_LOCAL_GET, remove_info.iterator_data_ptr);
-    WIL(remote->token, WI_LOCAL_GET, remove_info.iterator_remove_func);
-    WIL(remote->token, WI_CALL_INDIRECT, remove_info.remove_func_type_idx);
-    WI(remote->token, WI_IF_END);
+    WIL(remove->token, WI_LOCAL_GET, remove_info.iterator_remove_func);
+    WIL(remove->token, WI_I32_CONST, mod->null_proc_func_idx);
+    WI(remove->token, WI_I32_NE);
+    WID(remove->token, WI_IF_START, 0x40);
+    WIL(remove->token, WI_LOCAL_GET, remove_info.iterator_data_ptr);
+    WIL(remove->token, WI_LOCAL_GET, remove_info.iterator_remove_func);
+    WIL(remove->token, WI_CALL_INDIRECT, remove_info.remove_func_type_idx);
+    WI(remove->token, WI_IF_END);
 
     *pcode = code;
 }
@@ -3459,7 +3579,6 @@ static i32 get_element_idx(OnyxWasmModule* mod, AstFunction* func) {
     }
 }
 
-
 static void emit_function(OnyxWasmModule* mod, AstFunction* fd) {
     if (!should_emit_function(fd)) return;
 
@@ -3474,19 +3593,31 @@ static void emit_function(OnyxWasmModule* mod, AstFunction* fd) {
     i32 func_idx = (i32) bh_imap_get(&mod->index_map, (u64) fd);
     mod->current_func_idx = func_idx;
 
+    debug_begin_function(mod, func_idx, fd->token, get_function_name(fd));
+
     if (fd == builtin_initialize_data_segments && context.options->use_post_mvp_features) {
         emit_initialize_data_segments_body(mod, &wasm_func.code);
+
+        debug_emit_instruction(mod, NULL);
         bh_arr_push(wasm_func.code, ((WasmInstruction){ WI_BLOCK_END, 0x00 }));
+
         bh_arr_set_at(mod->funcs, func_idx - mod->foreign_function_count, wasm_func);
         mod->current_func_idx = -1;
+
+        debug_end_function(mod);
         return;
     }
 
     if (fd == builtin_run_init_procedures) {
         emit_run_init_procedures(mod, &wasm_func.code);
+
+        debug_emit_instruction(mod, NULL);
         bh_arr_push(wasm_func.code, ((WasmInstruction){ WI_BLOCK_END, 0x00 }));
+
         bh_arr_set_at(mod->funcs, func_idx - mod->foreign_function_count, wasm_func);
         mod->current_func_idx = -1;
+
+        debug_end_function(mod);
         return;
     }
 
@@ -3525,6 +3656,8 @@ static void emit_function(OnyxWasmModule* mod, AstFunction* fd) {
         bh_arr_insert_end(wasm_func.code, 5);
         fori (i, 0, 5) wasm_func.code[i] = (WasmInstruction) { WI_NOP, 0 };
 
+        // TODO: Emit debug info for the above instructions
+
         mod->stack_base_idx = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR);
 
         // Generate code
@@ -3535,6 +3668,9 @@ static void emit_function(OnyxWasmModule* mod, AstFunction* fd) {
 
             // Place all stack leaves in patch locations. These will (probably) all be
             // right before a "return" instruction.
+            debug_emit_instruction(mod, NULL);
+            debug_emit_instruction(mod, NULL);
+
             u64 stack_top_idx = bh_imap_get(&mod->index_map, (u64) &builtin_stack_top);
             bh_arr_push(wasm_func.code, ((WasmInstruction) { WI_LOCAL_GET,  { .l = mod->stack_base_idx } }));
             bh_arr_push(wasm_func.code, ((WasmInstruction) { WI_GLOBAL_SET, { .l = stack_top_idx } }));
@@ -3548,12 +3684,16 @@ static void emit_function(OnyxWasmModule* mod, AstFunction* fd) {
 
     WasmFuncType* ft = mod->types[type_idx];
     emit_zero_value(mod, &wasm_func.code, ft->return_type);
+
+    debug_emit_instruction(mod, NULL);
     bh_arr_push(wasm_func.code, ((WasmInstruction){ WI_BLOCK_END, 0x00 }));
 
     bh_imap_clear(&mod->local_map);
 
     bh_arr_set_at(mod->funcs, func_idx - mod->foreign_function_count, wasm_func);
     mod->current_func_idx = -1;
+
+    debug_end_function(mod);
 }
 
 static void emit_foreign_function(OnyxWasmModule* mod, AstFunction* fd) {
@@ -4050,6 +4190,18 @@ OnyxWasmModule onyx_wasm_module_create(bh_allocator alloc) {
     bh_arr_new(global_heap_allocator, module.procedures_with_tags, 4);
     bh_arr_new(global_heap_allocator, module.data_patches, 4);
 
+#ifdef ENABLE_DEBUG_INFO
+    module.debug_context = bh_alloc_item(context.ast_alloc, DebugContext);
+    module.debug_context->allocator = global_heap_allocator;
+    module.debug_context->next_file_id = 0;
+    module.debug_context->last_token = NULL;
+
+    sh_new_arena(module.debug_context->file_ids);
+    bh_arr_new(global_heap_allocator, module.debug_context->funcs, 16);
+
+    bh_buffer_init(&module.debug_context->op_buffer, global_heap_allocator, 1024);
+#endif
+
     return module;
 }
 
index 59633ffc8ad1819755e62d688ad3c993150a6f16..49dae8c0e628831b930993d4f04aa16b1cba218c 100644 (file)
@@ -444,6 +444,7 @@ EMIT_FUNC_NO_ARGS(run_init_procedures) {
 
     bh_arr_each(AstFunction *, func, init_procedures) {
         i32 func_idx = (i32) bh_imap_get(&mod->index_map, (u64) *func);
+        debug_emit_instruction(mod, NULL);
         bh_arr_push(code, ((WasmInstruction){ WI_CALL, func_idx }));
     }
 
index 99c19d92c2024f5b52d46a7b0841588166be8a5c..c7b4e7dd5316bd91d8133b114f7caf2bf5621a2a 100644 (file)
@@ -25,6 +25,20 @@ static const u8 ONYX_MAGIC_STRING[] = "ONYX";
 static const u8 WASM_MAGIC_STRING[] = { 0x00, 0x61, 0x73, 0x6D };
 static const u8 WASM_VERSION[] = { 0x01, 0x00, 0x00, 0x00 };
 
+static inline i32 output_unsigned_integer(u64 i, bh_buffer *buff) {
+    i32 leb_len;
+    u8* leb = uint_to_uleb128(i, &leb_len);
+    bh_buffer_append(buff, leb, leb_len);
+    return leb_len;
+}
+
+static inline i32 output_custom_section_name(char *name, bh_buffer *buff) {
+    u32 len = strlen(name);
+    i32 len_len = output_unsigned_integer(len, buff);
+    bh_buffer_append(buff, name, len);
+    return len_len + len;
+}
+
 static void output_instruction(WasmFunc* func, WasmInstruction* instr, bh_buffer* buff);
 
 static i32 output_vector(void** arr, i32 stride, i32 arrlen, vector_func elem, bh_buffer* vec_buff) {
@@ -414,7 +428,6 @@ static void output_instruction(WasmFunc* func, WasmInstruction* instr, bh_buffer
     i32 leb_len;
     u8* leb;
 
-    if (instr->type == WI_NOP) return;
     if (instr->type == WI_UNREACHABLE) assert(("EMITTING UNREACHABLE!!", 0));
 
     if (instr->type & SIMD_INSTR_MASK) {
@@ -677,38 +690,29 @@ static i32 output_onyx_libraries_section(OnyxWasmModule* module, bh_buffer* buff
     bh_buffer libs_buff;
     bh_buffer_init(&libs_buff, buff->allocator, 128);
 
-    const char *custom_name = "_onyx_libs";
-    i32 leb_len;
-    u8* leb = uint_to_uleb128(strlen(custom_name), &leb_len);
-    bh_buffer_append(&libs_buff, leb, leb_len);
-    bh_buffer_append(&libs_buff, custom_name, strlen(custom_name));
+    output_custom_section_name("_onyx_libs", &libs_buff);
 
-    leb = uint_to_uleb128((u64) bh_arr_length(module->library_paths), &leb_len);
-    bh_buffer_append(&libs_buff, leb, leb_len);
+    output_unsigned_integer(bh_arr_length(module->library_paths), &libs_buff);
 
     bh_arr_each(char *, lib, module->library_paths) {
         assert(*lib != NULL);
 
         u32 lib_len = strlen(*lib);
-        leb = uint_to_uleb128((u64) lib_len, &leb_len);
-        bh_buffer_append(&libs_buff, leb, leb_len);
+        output_unsigned_integer(lib_len, &libs_buff);
         bh_buffer_append(&libs_buff, *lib, lib_len);
     }
 
-    leb = uint_to_uleb128((u64) bh_arr_length(module->libraries), &leb_len);
-    bh_buffer_append(&libs_buff, leb, leb_len);
+    output_unsigned_integer(bh_arr_length(module->libraries), &libs_buff);
 
     bh_arr_each(char *, lib, module->libraries) {
         assert(*lib != NULL);
 
         u32 lib_len = strlen(*lib);
-        leb = uint_to_uleb128((u64) lib_len, &leb_len);
-        bh_buffer_append(&libs_buff, leb, leb_len);
+        output_unsigned_integer(lib_len, &libs_buff);
         bh_buffer_append(&libs_buff, *lib, lib_len);
     }
 
-    leb = uint_to_uleb128((u64) (libs_buff.length), &leb_len);
-    bh_buffer_append(buff, leb, leb_len);
+    output_unsigned_integer(libs_buff.length, buff);
 
     bh_buffer_concat(buff, libs_buff);
     bh_buffer_free(&libs_buff);
@@ -724,11 +728,7 @@ static i32 output_onyx_func_offset_section(OnyxWasmModule* module, bh_buffer* bu
     bh_buffer section_buff;
     bh_buffer_init(&section_buff, buff->allocator, 128);
 
-    const char *custom_name = "_onyx_func_offsets";
-    i32 leb_len;
-    u8* leb = uint_to_uleb128(strlen(custom_name), &leb_len);
-    bh_buffer_append(&section_buff, leb, leb_len);
-    bh_buffer_append(&section_buff, custom_name, strlen(custom_name));
+    output_custom_section_name("_onyx_func_offsets", &section_buff);
 
     i32 func_count = bh_arr_length(module->funcs) + module->foreign_function_count;
 
@@ -753,8 +753,7 @@ static i32 output_onyx_func_offset_section(OnyxWasmModule* module, bh_buffer* bu
 
     bh_buffer_concat(&section_buff, name_buff);
 
-    leb = uint_to_uleb128((u64) (section_buff.length), &leb_len);
-    bh_buffer_append(buff, leb, leb_len);
+    output_unsigned_integer(section_buff.length, buff);
 
     bh_buffer_concat(buff, section_buff);
     bh_buffer_free(&section_buff);
@@ -762,6 +761,81 @@ static i32 output_onyx_func_offset_section(OnyxWasmModule* module, bh_buffer* bu
     return buff->length - prev_len;
 }
 
+#ifdef ENABLE_DEBUG_INFO
+static i32 output_ovm_debug_sections(OnyxWasmModule* module, bh_buffer* buff) {
+    if (!module->debug_context) return 0;
+
+    DebugContext *ctx = module->debug_context;
+
+    bh_buffer section_buff;
+    bh_buffer_init(&section_buff, buff->allocator, 128);
+
+    {
+        // ovm_debug_files section
+        bh_buffer_clear(&section_buff);
+        bh_buffer_write_byte(buff, WASM_SECTION_ID_CUSTOM);
+
+        output_custom_section_name("ovm_debug_files", &section_buff);
+
+        i32 file_count = shlenu(ctx->file_ids);
+        output_unsigned_integer(file_count, &section_buff);
+
+        fori (i, 0, file_count) {
+            Table(u32) entry = (void *) &ctx->file_ids[i];
+            output_unsigned_integer(entry->value, &section_buff);
+            output_name(entry->key, strlen(entry->key), &section_buff);
+        }
+
+        output_unsigned_integer(section_buff.length, buff);
+
+        bh_buffer_concat(buff, section_buff);
+    }
+
+    {
+        // ovm_debug_funcs section
+        bh_buffer_clear(&section_buff);
+        bh_buffer_write_byte(buff, WASM_SECTION_ID_CUSTOM);
+
+        output_custom_section_name("ovm_debug_funcs", &section_buff);
+
+        i32 func_count = bh_arr_length(ctx->funcs);
+        output_unsigned_integer(func_count, &section_buff);
+
+        fori (i, 0, func_count) {
+            DebugFuncContext *func = &ctx->funcs[i];
+            output_unsigned_integer(func->func_index, &section_buff);
+            output_unsigned_integer(func->file_id, &section_buff);
+            output_unsigned_integer(func->line, &section_buff);
+            output_name(func->name, func->name_length, &section_buff);
+            output_unsigned_integer(1, &section_buff);
+            output_unsigned_integer(func->op_offset, &section_buff);
+            output_unsigned_integer(func->stack_ptr_idx, &section_buff);
+            output_unsigned_integer(0, &section_buff);
+        }
+
+        output_unsigned_integer(section_buff.length, buff);
+
+        bh_buffer_concat(buff, section_buff);
+    }
+
+    {
+        // ovm_debug_ops section
+        bh_buffer_clear(&section_buff);
+        bh_buffer_write_byte(buff, WASM_SECTION_ID_CUSTOM);
+
+        output_custom_section_name("ovm_debug_ops", &section_buff);
+        bh_buffer_concat(&section_buff, ctx->op_buffer);
+
+        output_unsigned_integer(section_buff.length, buff);
+
+        bh_buffer_concat(buff, section_buff);
+    }
+
+    bh_buffer_free(&section_buff);
+    return 0;
+}
+#endif
+
 void onyx_wasm_module_write_to_buffer(OnyxWasmModule* module, bh_buffer* buffer) {
     bh_buffer_init(buffer, global_heap_allocator, 128);
     if (context.options->runtime == Runtime_Onyx) {
@@ -771,6 +845,9 @@ void onyx_wasm_module_write_to_buffer(OnyxWasmModule* module, bh_buffer* buffer)
     }
     bh_buffer_append(buffer, WASM_VERSION, 4);
 
+#ifdef ENABLE_DEBUG_INFO
+    output_ovm_debug_sections(module, buffer);
+#endif
     output_typesection(module, buffer);
     output_importsection(module, buffer);
     output_funcsection(module, buffer);