Mercurial > hg > Members > anatofuz > MoarVM
view src/debug/debugserver.c @ 40:9b496a0c430a
merge
author | anatofuz |
---|---|
date | Tue, 27 Nov 2018 11:25:43 +0900 |
parents | 2cf249471370 |
children |
line wrap: on
line source
#include "moar.h" #include "platform/threads.h" #define DEBUGSERVER_MAJOR_PROTOCOL_VERSION 1 #define DEBUGSERVER_MINOR_PROTOCOL_VERSION 1 #define bool int #define true TRUE #define false FALSE #include "cmp.h" #ifdef _WIN32 #include <winsock2.h> #include <ws2tcpip.h> typedef SOCKET Socket; #define sa_family_t unsigned int #else #include "unistd.h" #include <sys/socket.h> #include <sys/un.h> typedef int Socket; #define closesocket close #endif typedef enum { MT_MessageTypeNotUnderstood, MT_ErrorProcessingMessage, MT_OperationSuccessful, MT_IsExecutionSuspendedRequest, MT_IsExecutionSuspendedResponse, MT_SuspendAll, MT_ResumeAll, MT_SuspendOne, MT_ResumeOne, MT_ThreadStarted, MT_ThreadEnded, MT_ThreadListRequest, MT_ThreadListResponse, MT_ThreadStackTraceRequest, MT_ThreadStackTraceResponse, MT_SetBreakpointRequest, MT_SetBreakpointConfirmation, MT_BreakpointNotification, MT_ClearBreakpoint, MT_ClearAllBreakpoints, MT_StepInto, MT_StepOver, MT_StepOut, MT_StepCompleted, MT_ReleaseHandles, MT_HandleResult, MT_ContextHandle, MT_ContextLexicalsRequest, MT_ContextLexicalsResponse, MT_OuterContextRequest, MT_CallerContextRequest, MT_CodeObjectHandle, MT_ObjectAttributesRequest, MT_ObjectAttributesResponse, MT_DecontainerizeHandle, MT_FindMethod, MT_Invoke, MT_InvokeResult, MT_UnhandledException, MT_OperationUnsuccessful, MT_ObjectMetadataRequest, MT_ObjectMetadataResponse, MT_ObjectPositionalsRequest, MT_ObjectPositionalsResponse, MT_ObjectAssociativesRequest, MT_ObjectAssociativesResponse, MT_HandleEquivalenceRequest, MT_HandleEquivalenceResponse, } message_type; typedef enum { ArgKind_Handle, ArgKind_Integer, ArgKind_Num, ArgKind_String, } argument_kind; typedef struct { MVMuint8 arg_kind; union { MVMint64 i; MVMnum64 n; char *s; MVMint64 o; } arg_u; } argument_data; typedef enum { FS_type = 1, FS_id = 2, FS_thread_id = 4, FS_file = 8, FS_line = 16, FS_suspend = 32, FS_stacktrace = 64, /* handle_count is just bookkeeping */ FS_handles = 128, FS_handle_id = 256, FS_frame_number = 512, FS_arguments = 1024, } fields_set; typedef struct { MVMuint16 type; MVMuint64 id; MVMuint32 thread_id; char *file; MVMuint32 line; MVMuint8 suspend; MVMuint8 stacktrace; MVMuint16 handle_count; MVMuint64 *handles; MVMuint64 handle_id; MVMuint32 frame_number; MVMuint32 argument_count; argument_data *arguments; MVMuint8 parse_fail; const char *parse_fail_message; fields_set fields_set; } request_data; static MVMint32 write_stacktrace_frames(MVMThreadContext *dtc, cmp_ctx_t *ctx, MVMThread *thread); static MVMint32 request_all_threads_suspend(MVMThreadContext *dtc, cmp_ctx_t *ctx, request_data *argument); static MVMuint64 allocate_handle(MVMThreadContext *dtc, MVMObject *target); /* Breakpoint stuff */ MVM_PUBLIC void MVM_debugserver_register_line(MVMThreadContext *tc, char *filename, MVMuint32 filename_len, MVMuint32 line_no, MVMuint32 *file_idx) { MVMDebugServerData *debugserver = tc->instance->debugserver; MVMDebugServerBreakpointTable *table = debugserver->breakpoints; MVMDebugServerBreakpointFileTable *found = NULL; MVMuint32 index = 0; char *open_paren_pos = (char *)memchr(filename, '(', filename_len); if (open_paren_pos) { if (open_paren_pos[-1] == ' ') { filename_len = open_paren_pos - filename - 1; } } uv_mutex_lock(&debugserver->mutex_breakpoints); if (*file_idx < table->files_used) { MVMDebugServerBreakpointFileTable *file = &table->files[*file_idx]; if (file->filename_length == filename_len && memcmp(file->filename, filename, filename_len) == 0) found = file; } if (!found) { for (index = 0; index < table->files_used; index++) { MVMDebugServerBreakpointFileTable *file = &table->files[index]; if (file->filename_length != filename_len) continue; if (memcmp(file->filename, filename, filename_len) != 0) continue; found = file; *file_idx = index; break; } } if (!found) { if (table->files_used++ >= table->files_alloc) { MVMuint32 old_alloc = table->files_alloc; table->files_alloc *= 2; table->files = MVM_fixed_size_realloc_at_safepoint(tc, tc->instance->fsa, table->files, old_alloc * sizeof(MVMDebugServerBreakpointFileTable), table->files_alloc * sizeof(MVMDebugServerBreakpointFileTable)); memset((char *)(table->files + old_alloc), 0, (table->files_alloc - old_alloc) * sizeof(MVMDebugServerBreakpointFileTable) - 1); if (tc->instance->debugserver->debugspam_protocol) fprintf(stderr, "table for files increased to %u slots\n", table->files_alloc); } found = &table->files[table->files_used - 1]; found->filename = MVM_calloc(filename_len + 1, sizeof(char)); strncpy(found->filename, filename, filename_len); if (tc->instance->debugserver->debugspam_protocol) fprintf(stderr, "created new file entry at %u for %s\n", table->files_used - 1, found->filename); found->filename_length = filename_len; found->lines_active_alloc = line_no + 32; found->lines_active = MVM_fixed_size_alloc_zeroed(tc, tc->instance->fsa, found->lines_active_alloc * sizeof(MVMuint8)); *file_idx = table->files_used - 1; found->breakpoints = NULL; found->breakpoints_alloc = 0; found->breakpoints_used = 0; } if (found->lines_active_alloc < line_no + 1) { MVMuint32 old_size = found->lines_active_alloc; found->lines_active_alloc *= 2; if (tc->instance->debugserver->debugspam_protocol) fprintf(stderr, "increasing line number table for %s from %u to %u slots\n", found->filename, old_size, found->lines_active_alloc); found->lines_active = MVM_fixed_size_realloc_at_safepoint(tc, tc->instance->fsa, found->lines_active, old_size, found->lines_active_alloc); memset((char *)found->lines_active + old_size, 0, found->lines_active_alloc - old_size - 1); } uv_mutex_unlock(&debugserver->mutex_breakpoints); } static void stop_point_hit(MVMThreadContext *tc) { while (1) { /* We're in total regular boring execution. Set ourselves to * interrupted for suspend reasons */ if (MVM_cas(&tc->gc_status, MVMGCStatus_NONE, MVMGCStatus_INTERRUPT | MVMSuspendState_SUSPEND_REQUEST) == MVMGCStatus_NONE) { break; } /* Looks like another thread just interrupted us; join in on GC and * then this loop will store the suspend request flag when we're back * to MVMGCStatus_NONE. */ if (MVM_load(&tc->gc_status) == MVMGCStatus_INTERRUPT) { MVM_gc_enter_from_interrupt(tc); } /* Perhaps the debugserver just asked us to suspend, too. It's not * important for our suspend request flag to survive or something. */ if (MVM_load(&tc->gc_status) == (MVMGCStatus_INTERRUPT | MVMSuspendState_SUSPEND_REQUEST)) { break; } } MVM_gc_enter_from_interrupt(tc); } static MVMuint8 breakpoint_hit(MVMThreadContext *tc, MVMDebugServerBreakpointFileTable *file, MVMuint32 line_no) { cmp_ctx_t *ctx = NULL; MVMDebugServerBreakpointInfo *info; MVMuint32 index; MVMuint8 must_suspend = 0; if (tc->instance->debugserver && tc->instance->debugserver->messagepack_data) { ctx = (cmp_ctx_t*)tc->instance->debugserver->messagepack_data; } for (index = 0; index < file->breakpoints_used; index++) { info = &file->breakpoints[index]; if (info->line_no == line_no) { if (tc->instance->debugserver->debugspam_protocol) fprintf(stderr, "hit a breakpoint\n"); if (ctx) { uv_mutex_lock(&tc->instance->debugserver->mutex_network_send); cmp_write_map(ctx, 4); cmp_write_str(ctx, "id", 2); cmp_write_integer(ctx, info->breakpoint_id); cmp_write_str(ctx, "type", 4); cmp_write_integer(ctx, MT_BreakpointNotification); cmp_write_str(ctx, "thread", 6); cmp_write_integer(ctx, tc->thread_id); cmp_write_str(ctx, "frames", 6); if (info->send_backtrace) { write_stacktrace_frames(tc, ctx, tc->thread_obj); } else { cmp_write_nil(ctx); } uv_mutex_unlock(&tc->instance->debugserver->mutex_network_send); } if (info->shall_suspend) { must_suspend = 1; } } } return must_suspend; } static void step_point_hit(MVMThreadContext *tc) { cmp_ctx_t *ctx = (cmp_ctx_t*)tc->instance->debugserver->messagepack_data; uv_mutex_lock(&tc->instance->debugserver->mutex_network_send); cmp_write_map(ctx, 4); cmp_write_str(ctx, "id", 2); cmp_write_integer(ctx, tc->step_message_id); cmp_write_str(ctx, "type", 4); cmp_write_integer(ctx, MT_StepCompleted); cmp_write_str(ctx, "thread", 6); cmp_write_integer(ctx, tc->thread_id); cmp_write_str(ctx, "frames", 6); write_stacktrace_frames(tc, ctx, tc->thread_obj); uv_mutex_unlock(&tc->instance->debugserver->mutex_network_send); tc->step_mode = MVMDebugSteppingMode_NONE; tc->step_mode_frame = NULL; } MVM_PUBLIC void MVM_debugserver_breakpoint_check(MVMThreadContext *tc, MVMuint32 file_idx, MVMuint32 line_no) { MVMDebugServerData *debugserver = tc->instance->debugserver; MVMuint8 shall_suspend = 0; tc->cur_line_no = line_no; tc->cur_file_idx = file_idx; if (debugserver->any_breakpoints_at_all) { MVMDebugServerBreakpointTable *table = debugserver->breakpoints; MVMDebugServerBreakpointFileTable *found = &table->files[file_idx]; if (debugserver->any_breakpoints_at_all && found->breakpoints_used && found->lines_active[line_no]) { shall_suspend |= breakpoint_hit(tc, found, line_no); } } if (tc->step_mode) { if (tc->step_mode == MVMDebugSteppingMode_STEP_OVER) { if (line_no != tc->step_mode_line_no && tc->step_mode_frame == tc->cur_frame) { if (tc->instance->debugserver->debugspam_protocol) fprintf(stderr, "hit a stepping point: step over; %u != %u, %p == %p\n", line_no, tc->step_mode_line_no, tc->step_mode_frame, tc->cur_frame); step_point_hit(tc); shall_suspend = 1; } } else if (tc->step_mode == MVMDebugSteppingMode_STEP_INTO) { if (line_no != tc->step_mode_line_no && tc->step_mode_frame == tc->cur_frame || tc->step_mode_frame != tc->cur_frame) { if (tc->instance->debugserver->debugspam_protocol) if (line_no != tc->step_mode_line_no && tc->step_mode_frame == tc->cur_frame) fprintf(stderr, "hit a stepping point: step into; %u != %u, %p == %p\n", line_no, tc->step_mode_line_no, tc->step_mode_frame, tc->cur_frame); else fprintf(stderr, "hit a stepping point: step into; %u, %u, %p != %p\n", line_no, tc->step_mode_line_no, tc->step_mode_frame, tc->cur_frame); step_point_hit(tc); shall_suspend = 1; } } /* Nothing to do for STEP_OUT. */ /* else if (tc->step_mode == MVMDebugSteppingMode_STEP_OUT) { } */ } if (shall_suspend) stop_point_hit(tc); } #define REQUIRE(field, message) do { if(!(data->fields_set & (field))) { data->parse_fail = 1; data->parse_fail_message = (message); return 0; }; accepted = accepted | (field); } while (0) MVMuint8 check_requirements(MVMThreadContext *tc, request_data *data) { fields_set accepted = FS_id | FS_type; REQUIRE(FS_id, "An id field is required"); REQUIRE(FS_type, "A type field is required"); switch (data->type) { case MT_IsExecutionSuspendedRequest: case MT_SuspendAll: case MT_ResumeAll: case MT_ThreadListRequest: case MT_ClearAllBreakpoints: /* All of these messages only take id and type */ break; case MT_SuspendOne: case MT_ResumeOne: case MT_ThreadStackTraceRequest: case MT_StepInto: case MT_StepOver: case MT_StepOut: REQUIRE(FS_thread_id, "A thread field is required"); break; case MT_SetBreakpointRequest: REQUIRE(FS_suspend, "A suspend field is required"); REQUIRE(FS_stacktrace, "A stacktrace field is required"); /* Fall-Through */ case MT_ClearBreakpoint: REQUIRE(FS_file, "A file field is required"); REQUIRE(FS_line, "A line field is required"); break; case MT_HandleEquivalenceRequest: case MT_ReleaseHandles: REQUIRE(FS_handles, "A handles field is required"); break; case MT_FindMethod: /* TODO we've got to have some name field or something */ /* Fall-Through */ case MT_DecontainerizeHandle: REQUIRE(FS_thread_id, "A thread field is required"); /* Fall-Through */ case MT_ContextLexicalsRequest: case MT_OuterContextRequest: case MT_CallerContextRequest: case MT_ObjectAttributesRequest: case MT_ObjectMetadataRequest: case MT_ObjectPositionalsRequest: case MT_ObjectAssociativesRequest: REQUIRE(FS_handle_id, "A handle field is required"); break; case MT_ContextHandle: case MT_CodeObjectHandle: REQUIRE(FS_thread_id, "A thread field is required"); REQUIRE(FS_frame_number, "A frame field is required"); break; case MT_Invoke: REQUIRE(FS_handle_id, "A handle field is required"); REQUIRE(FS_thread_id, "A thread field is required"); REQUIRE(FS_arguments, "An arguments field is required"); break; default: break; } if (data->fields_set != accepted) { if (tc->instance->debugserver->debugspam_protocol) fprintf(stderr, "debugserver: too many fields in message of type %d: accepted 0x%x, got 0x%x\n", data->type, accepted, data->fields_set); } } static MVMuint16 big_endian_16(MVMuint16 number) { #ifdef MVM_BIGENDIAN return number; #else char *bytes = (char *)&number; char tmp; tmp = bytes[1]; bytes[1] = bytes[0]; bytes[0] = tmp; return *((MVMuint16 *)bytes); #endif } static void send_greeting(Socket *sock) { char buffer[24] = "MOARVM-REMOTE-DEBUG\0"; MVMuint16 version = big_endian_16(DEBUGSERVER_MAJOR_PROTOCOL_VERSION); MVMuint16 *verptr = (MVMuint16 *)(&buffer[strlen("MOARVM-REMOTE-DEBUG") + 1]); *verptr = version; verptr++; version = big_endian_16(DEBUGSERVER_MINOR_PROTOCOL_VERSION); *verptr = version; send(*sock, buffer, 24, 0); } static int receive_greeting(Socket *sock) { const char *expected = "MOARVM-REMOTE-CLIENT-OK"; char buffer[24]; int received = 0; memset(buffer, 0, sizeof(buffer)); received = recv(*sock, buffer, sizeof(buffer), 0); if (received != sizeof(buffer)) { return 0; } if (memcmp(buffer, expected, sizeof(buffer)) == 0) { return 1; } return 0; } static void communicate_error(MVMThreadContext *tc, cmp_ctx_t *ctx, request_data *argument) { if (argument) { if (tc->instance->debugserver->debugspam_protocol) fprintf(stderr, "communicating an error\n"); cmp_write_map(ctx, 2); cmp_write_str(ctx, "id", 2); cmp_write_integer(ctx, argument->id); cmp_write_str(ctx, "type", 4); cmp_write_integer(ctx, MT_ErrorProcessingMessage); } } static void communicate_success(MVMThreadContext *tc, cmp_ctx_t *ctx, request_data *argument) { if (argument) { if (tc->instance->debugserver->debugspam_protocol) fprintf(stderr, "communicating success\n"); cmp_write_map(ctx, 2); cmp_write_str(ctx, "id", 2); cmp_write_integer(ctx, argument->id); cmp_write_str(ctx, "type", 4); cmp_write_integer(ctx, MT_OperationSuccessful); } } /* Send spontaneous events */ MVM_PUBLIC void MVM_debugserver_notify_thread_creation(MVMThreadContext *tc) { if (tc->instance->debugserver && tc->instance->debugserver->messagepack_data) { cmp_ctx_t *ctx = (cmp_ctx_t*)tc->instance->debugserver->messagepack_data; MVMuint64 event_id; uv_mutex_lock(&tc->instance->debugserver->mutex_network_send); event_id = tc->instance->debugserver->event_id; tc->instance->debugserver->event_id += 2; cmp_write_map(ctx, 5); cmp_write_str(ctx, "id", 2); cmp_write_integer(ctx, event_id); cmp_write_str(ctx, "type", 4); cmp_write_integer(ctx, MT_ThreadStarted); cmp_write_str(ctx, "thread", 6); cmp_write_integer(ctx, tc->thread_obj->body.thread_id); cmp_write_str(ctx, "native_id", 9); cmp_write_integer(ctx, tc->thread_obj->body.native_thread_id); cmp_write_str(ctx, "app_lifetime", 12); cmp_write_integer(ctx, tc->thread_obj->body.app_lifetime); uv_mutex_unlock(&tc->instance->debugserver->mutex_network_send); } } MVM_PUBLIC void MVM_debugserver_notify_thread_destruction(MVMThreadContext *tc) { if (tc->instance->debugserver && tc->instance->debugserver->messagepack_data) { cmp_ctx_t *ctx = (cmp_ctx_t*)tc->instance->debugserver->messagepack_data; MVMuint64 event_id; uv_mutex_lock(&tc->instance->debugserver->mutex_network_send); event_id = tc->instance->debugserver->event_id; tc->instance->debugserver->event_id += 2; cmp_write_map(ctx, 3); cmp_write_str(ctx, "id", 2); cmp_write_integer(ctx, event_id); cmp_write_str(ctx, "type", 4); cmp_write_integer(ctx, MT_ThreadEnded); cmp_write_str(ctx, "thread", 6); cmp_write_integer(ctx, tc->thread_obj->body.thread_id); uv_mutex_unlock(&tc->instance->debugserver->mutex_network_send); } } MVM_PUBLIC void MVM_debugserver_notify_unhandled_exception(MVMThreadContext *tc, MVMException *ex) { if (tc->instance->debugserver && tc->instance->debugserver->messagepack_data) { cmp_ctx_t *ctx = (cmp_ctx_t*)tc->instance->debugserver->messagepack_data; MVMuint64 event_id; uv_mutex_lock(&tc->instance->debugserver->mutex_network_send); request_all_threads_suspend(tc, ctx, NULL); event_id = tc->instance->debugserver->event_id; tc->instance->debugserver->event_id += 2; cmp_write_map(ctx, 5); cmp_write_str(ctx, "id", 2); cmp_write_integer(ctx, event_id); cmp_write_str(ctx, "type", 4); cmp_write_integer(ctx, MT_UnhandledException); cmp_write_str(ctx, "handle", 6); cmp_write_integer(ctx, allocate_handle(tc, (MVMObject *)ex)); cmp_write_str(ctx, "thread", 6); cmp_write_integer(ctx, tc->thread_obj->body.thread_id); cmp_write_str(ctx, "frames", 6); write_stacktrace_frames(tc, ctx, tc->thread_obj); uv_mutex_unlock(&tc->instance->debugserver->mutex_network_send); MVM_gc_enter_from_interrupt(tc); } } static MVMuint8 is_thread_id_eligible(MVMInstance *vm, MVMuint32 id) { if (id == vm->debugserver->thread_id || id == vm->speshworker_thread_id) { return 0; } return 1; } /* Send replies to requests send by the client */ static MVMThread *find_thread_by_id(MVMInstance *vm, MVMint32 id) { MVMThread *cur_thread = 0; if (!is_thread_id_eligible(vm, id)) { return NULL; } uv_mutex_lock(&vm->mutex_threads); cur_thread = vm->threads; while (cur_thread) { if (cur_thread->body.thread_id == id) { break; } cur_thread = cur_thread->body.next; } uv_mutex_unlock(&vm->mutex_threads); return cur_thread; } static MVMint32 request_thread_suspends(MVMThreadContext *dtc, cmp_ctx_t *ctx, request_data *argument, MVMThread *thread) { MVMThread *to_do = thread ? thread : find_thread_by_id(dtc->instance, argument->thread_id); MVMThreadContext *tc = to_do ? to_do->body.tc : NULL; if (!tc) return 1; MVM_gc_mark_thread_blocked(dtc); while (1) { /* Is the thread currently doing completely ordinary code execution? */ if (MVM_cas(&tc->gc_status, MVMGCStatus_NONE, MVMGCStatus_INTERRUPT | MVMSuspendState_SUSPEND_REQUEST) == MVMGCStatus_NONE) { break; } /* Is the thread in question currently blocked, i.e. spending time in * some long-running piece of C code, waiting for I/O, etc.? * If so, just store the suspend request bit so when it unblocks itself * it'll suspend execution. */ if (MVM_cas(&tc->gc_status, MVMGCStatus_UNABLE, MVMGCStatus_UNABLE | MVMSuspendState_SUSPEND_REQUEST) == MVMGCStatus_UNABLE) { break; } /* Was the thread faster than us? For example by running into * a breakpoint, completing a step, or encountering an * unhandled exception? If so, we're done here. */ if ((MVM_load(&tc->gc_status) & MVMSUSPENDSTATUS_MASK) == MVMSuspendState_SUSPEND_REQUEST) { break; } MVM_platform_thread_yield(); } if (argument && argument->type == MT_SuspendOne) communicate_success(tc, ctx, argument); MVM_gc_mark_thread_unblocked(dtc); if (tc->instance->debugserver->debugspam_protocol) fprintf(stderr, "thread %u successfully suspended\n", tc->thread_id); return 0; } static MVMint32 request_all_threads_suspend(MVMThreadContext *dtc, cmp_ctx_t *ctx, request_data *argument) { MVMInstance *vm = dtc->instance; MVMThread *cur_thread = 0; MVMuint32 success = 1; uv_mutex_lock(&vm->mutex_threads); /* TODO track which threads we successfully suspended so we can wake them * up again if an error occured */ cur_thread = vm->threads; while (cur_thread) { if (is_thread_id_eligible(vm, cur_thread->body.thread_id)) { AO_t current = MVM_load(&cur_thread->body.tc->gc_status); if (current == MVMGCStatus_NONE || current == MVMGCStatus_UNABLE) { MVMint32 result = request_thread_suspends(dtc, ctx, argument, cur_thread); if (result == 1) { success = 0; break; } } } cur_thread = cur_thread->body.next; } if (success) communicate_success(dtc, ctx, argument); else communicate_error(dtc, ctx, argument); uv_mutex_unlock(&vm->mutex_threads); } static MVMint32 request_thread_resumes(MVMThreadContext *dtc, cmp_ctx_t *ctx, request_data *argument, MVMThread *thread) { MVMInstance *vm = dtc->instance; MVMThread *to_do = thread ? thread : find_thread_by_id(vm, argument->thread_id); MVMThreadContext *tc = to_do ? to_do->body.tc : NULL; AO_t current; if (!tc) { return 1; } current = MVM_load(&tc->gc_status); if (current != (MVMGCStatus_UNABLE | MVMSuspendState_SUSPENDED) && (current & MVMSUSPENDSTATUS_MASK) != MVMSuspendState_SUSPEND_REQUEST) { fprintf(stderr, "wrong state to resume from: %lu\n", MVM_load(&tc->gc_status)); return 1; } MVM_gc_mark_thread_blocked(dtc); while(1) { current = MVM_cas(&tc->gc_status, MVMGCStatus_UNABLE | MVMSuspendState_SUSPENDED, MVMGCStatus_UNABLE); if (current == (MVMGCStatus_UNABLE | MVMSuspendState_SUSPENDED)) { /* Success! We signalled the thread and can now tell it to * mark itself unblocked, which takes care of any looming GC * and related business. */ uv_cond_broadcast(&vm->debugserver->tell_threads); break; } else if ((current & MVMGCSTATUS_MASK) == MVMGCStatus_STOLEN) { uv_mutex_lock(&tc->instance->mutex_gc_orchestrate); if (tc->instance->in_gc) { uv_cond_wait(&tc->instance->cond_blocked_can_continue, &tc->instance->mutex_gc_orchestrate); } uv_mutex_unlock(&tc->instance->mutex_gc_orchestrate); } else { if (current == (MVMGCStatus_UNABLE | MVMSuspendState_SUSPEND_REQUEST)) { if (MVM_cas(&tc->gc_status, current, MVMGCStatus_UNABLE) == current) { break; } } } } MVM_gc_mark_thread_unblocked(dtc); if (argument && argument->type == MT_ResumeOne) communicate_success(tc, ctx, argument); if (tc->instance->debugserver->debugspam_protocol) fprintf(stderr, "success resuming thread; its status is now %lu\n", MVM_load(&tc->gc_status)); return 0; } static MVMint32 request_all_threads_resume(MVMThreadContext *dtc, cmp_ctx_t *ctx, request_data *argument) { MVMInstance *vm = dtc->instance; MVMThread *cur_thread = 0; MVMuint8 success = 1; uv_mutex_lock(&vm->mutex_threads); cur_thread = vm->threads; while (cur_thread) { if (cur_thread != dtc->thread_obj) { AO_t current = MVM_load(&cur_thread->body.tc->gc_status); if (current == (MVMGCStatus_UNABLE | MVMSuspendState_SUSPENDED) || current == (MVMGCStatus_INTERRUPT | MVMSuspendState_SUSPEND_REQUEST) || current == (MVMGCStatus_STOLEN | MVMSuspendState_SUSPEND_REQUEST)) { if (request_thread_resumes(dtc, ctx, argument, cur_thread)) { if (vm->debugserver->debugspam_protocol) fprintf(stderr, "failure to resume thread %u\n", cur_thread->body.thread_id); success = 0; break; } } } cur_thread = cur_thread->body.next; } if (success) communicate_success(dtc, ctx, argument); else communicate_error(dtc, ctx, argument); uv_mutex_unlock(&vm->mutex_threads); return !success; } static MVMint32 write_stacktrace_frames(MVMThreadContext *dtc, cmp_ctx_t *ctx, MVMThread *thread) { MVMThreadContext *tc = thread->body.tc; MVMuint64 stack_size = 0; MVMFrame *cur_frame = tc->cur_frame; while (cur_frame != NULL) { stack_size++; cur_frame = cur_frame->caller; } if (tc->instance->debugserver->debugspam_protocol) fprintf(stderr, "dumping a stack trace of %lu frames\n", stack_size); cmp_write_array(ctx, stack_size); cur_frame = tc->cur_frame; stack_size = 0; /* To check if we've got the topmost frame or not */ while (cur_frame != NULL) { MVMString *bc_filename = cur_frame->static_info->body.cu->body.filename; MVMString *name = cur_frame->static_info->body.name; MVMuint8 *cur_op = stack_size != 0 ? cur_frame->return_address : *(tc->interp_cur_op); MVMuint32 offset = cur_op - MVM_frame_effective_bytecode(cur_frame); MVMBytecodeAnnotation *annot = MVM_bytecode_resolve_annotation(tc, &cur_frame->static_info->body, offset > 0 ? offset - 1 : 0); MVMint32 line_number = annot ? annot->line_number : 1; MVMint16 string_heap_index = annot ? annot->filename_string_heap_index : 1; char *tmp1 = annot && string_heap_index < cur_frame->static_info->body.cu->body.num_strings ? MVM_string_utf8_encode_C_string(tc, MVM_cu_string(tc, cur_frame->static_info->body.cu, string_heap_index)) : NULL; char *filename_c = bc_filename ? MVM_string_utf8_encode_C_string(tc, bc_filename) : NULL; char *name_c = name ? MVM_string_utf8_encode_C_string(tc, name) : NULL; MVMObject *code_ref = cur_frame->code_ref; MVMCode *code_obj = code_ref && REPR(code_ref)->ID == MVM_REPR_ID_MVMCode ? (MVMCode*)code_ref : NULL; char *debugname = code_obj && code_obj->body.code_object ? MVM_6model_get_debug_name(tc, code_obj->body.code_object) : ""; cmp_write_map(ctx, 5); cmp_write_str(ctx, "file", 4); cmp_write_str(ctx, tmp1, tmp1 ? strlen(tmp1) : 0); cmp_write_str(ctx, "line", 4); cmp_write_integer(ctx, line_number); cmp_write_str(ctx, "bytecode_file", 13); if (bc_filename) cmp_write_str(ctx, filename_c, strlen(filename_c)); else cmp_write_nil(ctx); cmp_write_str(ctx, "name", 4); cmp_write_str(ctx, name_c, name_c ? strlen(name_c) : 0); cmp_write_str(ctx, "type", 4); cmp_write_str(ctx, debugname, strlen(debugname)); if (bc_filename) MVM_free(filename_c); if (name) MVM_free(name_c); if (tmp1) MVM_free(tmp1); cur_frame = cur_frame->caller; stack_size++; } } static MVMint32 request_thread_stacktrace(MVMThreadContext *dtc, cmp_ctx_t *ctx, request_data *argument, MVMThread *thread) { MVMThread *to_do = thread ? thread : find_thread_by_id(dtc->instance, argument->thread_id); if (!to_do) return 1; if ((to_do->body.tc->gc_status & MVMGCSTATUS_MASK) != MVMGCStatus_UNABLE) { return 1; } cmp_write_map(ctx, 3); cmp_write_str(ctx, "id", 2); cmp_write_integer(ctx, argument->id); cmp_write_str(ctx, "type", 4); cmp_write_integer(ctx, MT_ThreadStackTraceResponse); cmp_write_str(ctx, "frames", 6); write_stacktrace_frames(dtc, ctx, to_do); return 0; } static void send_thread_info(MVMThreadContext *dtc, cmp_ctx_t *ctx, request_data *argument) { MVMInstance *vm = dtc->instance; MVMint32 threadcount = 0; MVMThread *cur_thread; char infobuf[32] = "THL"; uv_mutex_lock(&vm->mutex_threads); cur_thread = vm->threads; while (cur_thread) { threadcount++; cur_thread = cur_thread->body.next; } cmp_write_map(ctx, 3); cmp_write_str(ctx, "id", 2); cmp_write_integer(ctx, argument->id); cmp_write_str(ctx, "type", 4); cmp_write_integer(ctx, MT_ThreadListResponse); cmp_write_str(ctx, "threads", 7); cmp_write_array(ctx, threadcount); cur_thread = vm->threads; while (cur_thread) { cmp_write_map(ctx, 5); cmp_write_str(ctx, "thread", 6); cmp_write_integer(ctx, cur_thread->body.thread_id); cmp_write_str(ctx, "native_id", 9); cmp_write_integer(ctx, cur_thread->body.native_thread_id); cmp_write_str(ctx, "app_lifetime", 12); cmp_write_bool(ctx, cur_thread->body.app_lifetime); cmp_write_str(ctx, "suspended", 9); cmp_write_bool(ctx, (MVM_load(&cur_thread->body.tc->gc_status) & MVMSUSPENDSTATUS_MASK) != MVMSuspendState_NONE); cmp_write_str(ctx, "num_locks", 9); cmp_write_integer(ctx, MVM_thread_lock_count(dtc, (MVMObject *)cur_thread)); cur_thread = cur_thread->body.next; } uv_mutex_unlock(&vm->mutex_threads); } static MVMuint64 send_is_execution_suspended_info(MVMThreadContext *dtc, cmp_ctx_t *ctx, request_data *argument) { MVMInstance *vm = dtc->instance; MVMuint8 result = 1; MVMThread *cur_thread; uv_mutex_lock(&vm->mutex_threads); cur_thread = vm->threads; while (cur_thread) { if ((MVM_load(&cur_thread->body.tc->gc_status) & MVMSUSPENDSTATUS_MASK) != MVMSuspendState_SUSPENDED && cur_thread->body.thread_id != vm->debugserver->thread_id && cur_thread->body.thread_id != vm->speshworker_thread_id) { result = 0; break; } cur_thread = cur_thread->body.next; } uv_mutex_unlock(&vm->mutex_threads); cmp_write_map(ctx, 3); cmp_write_str(ctx, "id", 2); cmp_write_integer(ctx, argument->id); cmp_write_str(ctx, "type", 4); cmp_write_integer(ctx, MT_IsExecutionSuspendedResponse); cmp_write_str(ctx, "suspended", 9); cmp_write_bool(ctx, result); } MVMuint8 setup_step(MVMThreadContext *dtc, cmp_ctx_t *ctx, request_data *argument, MVMDebugSteppingMode mode, MVMThread *thread) { MVMThread *to_do = thread ? thread : find_thread_by_id(dtc->instance, argument->thread_id); MVMThreadContext *tc; if (!to_do) return 1; if ((to_do->body.tc->gc_status & MVMGCSTATUS_MASK) != MVMGCStatus_UNABLE) { return 1; } tc = to_do->body.tc; tc->step_mode_frame = tc->cur_frame; tc->step_message_id = argument->id; tc->step_mode_line_no = tc->cur_line_no; tc->step_mode_file_idx = tc->cur_file_idx; tc->step_mode = mode; request_thread_resumes(dtc, ctx, NULL, to_do); return 0; } void MVM_debugserver_add_breakpoint(MVMThreadContext *tc, cmp_ctx_t *ctx, request_data *argument) { MVMDebugServerData *debugserver = tc->instance->debugserver; MVMDebugServerBreakpointTable *table = debugserver->breakpoints; MVMDebugServerBreakpointFileTable *found = NULL; MVMDebugServerBreakpointInfo *bp_info = NULL; MVMuint32 index = 0; if (tc->instance->debugserver->debugspam_protocol) fprintf(stderr, "asked to set a breakpoint for file %s line %u to send id %lu\n", argument->file, argument->line, argument->id); MVM_debugserver_register_line(tc, argument->file, strlen(argument->file), argument->line, &index); uv_mutex_lock(&debugserver->mutex_breakpoints); found = &table->files[index]; /* Create breakpoint first so that if a thread breaks on the activated line * the breakpoint information already exists */ if (found->breakpoints_alloc == 0) { found->breakpoints_alloc = 4; found->breakpoints = MVM_fixed_size_alloc_zeroed(tc, tc->instance->fsa, found->breakpoints_alloc * sizeof(MVMDebugServerBreakpointInfo)); } if (found->breakpoints_used++ >= found->breakpoints_alloc) { MVMuint32 old_alloc = found->breakpoints_alloc; found->breakpoints_alloc *= 2; found->breakpoints = MVM_fixed_size_realloc_at_safepoint(tc, tc->instance->fsa, found->breakpoints, old_alloc * sizeof(MVMDebugServerBreakpointInfo), found->breakpoints_alloc * sizeof(MVMDebugServerBreakpointInfo)); if (tc->instance->debugserver->debugspam_protocol) fprintf(stderr, "table for breakpoints increased to %u slots\n", found->breakpoints_alloc); } bp_info = &found->breakpoints[found->breakpoints_used - 1]; bp_info->breakpoint_id = argument->id; bp_info->line_no = argument->line; bp_info->shall_suspend = argument->suspend; bp_info->send_backtrace = argument->stacktrace; debugserver->any_breakpoints_at_all++; if (tc->instance->debugserver->debugspam_protocol) fprintf(stderr, "breakpoint settings: index %u bpid %lu lineno %u suspend %u backtrace %u\n", found->breakpoints_used - 1, argument->id, argument->line, argument->suspend, argument->stacktrace); found->lines_active[argument->line] = 1; cmp_write_map(ctx, 3); cmp_write_str(ctx, "id", 2); cmp_write_integer(ctx, argument->id); cmp_write_str(ctx, "type", 4); cmp_write_integer(ctx, MT_SetBreakpointConfirmation); cmp_write_str(ctx, "line", 4); cmp_write_integer(ctx, argument->line); uv_mutex_unlock(&debugserver->mutex_breakpoints); } void MVM_debugserver_clear_all_breakpoints(MVMThreadContext *tc, cmp_ctx_t *ctx, request_data *argument) { MVMDebugServerData *debugserver = tc->instance->debugserver; MVMDebugServerBreakpointTable *table = debugserver->breakpoints; MVMDebugServerBreakpointFileTable *found = NULL; MVMuint32 index; uv_mutex_lock(&debugserver->mutex_breakpoints); for (index = 0; index < table->files_used; index++) { found = &table->files[index]; memset(found->lines_active, 0, found->lines_active_alloc * sizeof(MVMuint8)); found->breakpoints_used = 0; } debugserver->any_breakpoints_at_all = 0; uv_mutex_unlock(&debugserver->mutex_breakpoints); /* When a client disconnects, we clear all breakpoints but don't * send a confirmation. In this case ctx and argument will be NULL */ if (ctx && argument) communicate_success(tc, ctx, argument); } void MVM_debugserver_clear_breakpoint(MVMThreadContext *tc, cmp_ctx_t *ctx, request_data *argument) { MVMDebugServerData *debugserver = tc->instance->debugserver; MVMDebugServerBreakpointTable *table = debugserver->breakpoints; MVMDebugServerBreakpointFileTable *found = NULL; MVMuint32 index = 0; MVMuint32 bpidx = 0; MVMuint32 num_cleared = 0; MVM_debugserver_register_line(tc, argument->file, strlen(argument->file), argument->line, &index); if (tc->instance->debugserver->debugspam_protocol) fprintf(stderr, "asked to clear breakpoints for file %s line %u\n", argument->file, argument->line); uv_mutex_lock(&debugserver->mutex_breakpoints); found = &table->files[index]; if (tc->instance->debugserver->debugspam_protocol) { fprintf(stderr, "dumping all breakpoints\n"); for (bpidx = 0; bpidx < found->breakpoints_used; bpidx++) { MVMDebugServerBreakpointInfo *bp_info = &found->breakpoints[bpidx]; fprintf(stderr, "breakpoint index %u has id %lu, is at line %u\n", bpidx, bp_info->breakpoint_id, bp_info->line_no); } } if (tc->instance->debugserver->debugspam_protocol) fprintf(stderr, "trying to clear breakpoints\n\n"); for (bpidx = 0; bpidx < found->breakpoints_used; bpidx++) { MVMDebugServerBreakpointInfo *bp_info = &found->breakpoints[bpidx]; if (tc->instance->debugserver->debugspam_protocol) fprintf(stderr, "breakpoint index %u has id %lu, is at line %u\n", bpidx, bp_info->breakpoint_id, bp_info->line_no); if (bp_info->line_no == argument->line) { if (tc->instance->debugserver->debugspam_protocol) fprintf(stderr, "breakpoint with id %ld cleared\n", bp_info->breakpoint_id); found->breakpoints[bpidx] = found->breakpoints[--found->breakpoints_used]; num_cleared++; bpidx--; debugserver->any_breakpoints_at_all--; } } uv_mutex_unlock(&debugserver->mutex_breakpoints); if (num_cleared > 0) communicate_success(tc, ctx, argument); else communicate_error(tc, ctx, argument); } static void release_all_handles(MVMThreadContext *dtc) { MVMDebugServerHandleTable *dht = dtc->instance->debugserver->handle_table; dht->used = 0; dht->next_id = 1; } static MVMuint64 release_handles(MVMThreadContext *dtc, cmp_ctx_t *ctx, request_data *argument) { MVMDebugServerHandleTable *dht = dtc->instance->debugserver->handle_table; MVMuint16 handle_index = 0; MVMuint16 id_index = 0; MVMuint16 handles_cleared = 0; for (handle_index = 0; handle_index < dht->used; handle_index++) { for (id_index = 0; id_index < argument->handle_count; id_index++) { if (argument->handles[id_index] == dht->entries[handle_index].id) { dht->used--; dht->entries[handle_index].id = dht->entries[dht->used].id; dht->entries[handle_index].target = dht->entries[dht->used].target; handle_index--; handles_cleared++; break; } } } if (handles_cleared != argument->handle_count) { return 1; } else { return 0; } } static MVMuint64 allocate_handle(MVMThreadContext *dtc, MVMObject *target) { if (!target || MVM_is_null(dtc, target)) { return 0; } else { MVMDebugServerHandleTable *dht = dtc->instance->debugserver->handle_table; MVMuint64 id = dht->next_id++; if (dht->used + 1 > dht->allocated) { if (dht->allocated < 8192) dht->allocated *= 2; else dht->allocated += 1024; dht->entries = MVM_realloc(dht->entries, sizeof(MVMDebugServerHandleTableEntry) * dht->allocated); } dht->entries[dht->used].id = id; dht->entries[dht->used].target = target; dht->used++; return id; } } static MVMuint64 allocate_and_send_handle(MVMThreadContext *dtc, cmp_ctx_t *ctx, request_data *argument, MVMObject *target) { MVMuint64 id = allocate_handle(dtc, target); cmp_write_map(ctx, 3); cmp_write_str(ctx, "id", 2); cmp_write_integer(ctx, argument->id); cmp_write_str(ctx, "type", 4); cmp_write_integer(ctx, MT_HandleResult); cmp_write_str(ctx, "handle", 6); cmp_write_integer(ctx, id); return id; } static MVMObject *find_handle_target(MVMThreadContext *dtc, MVMuint64 id) { MVMDebugServerHandleTable *dht = dtc->instance->debugserver->handle_table; MVMuint32 index; for (index = 0; index < dht->used; index++) { if (dht->entries[index].id == id) return dht->entries[index].target; } return NULL; } static MVMuint64 find_representant(MVMint16 *representant, MVMuint64 index) { MVMuint64 last = index; while (representant[last] != last) { last = representant[last]; } return last; } static void send_handle_equivalence_classes(MVMThreadContext *dtc, cmp_ctx_t *ctx, request_data *argument) { MVMuint16 *representant = MVM_calloc(argument->handle_count, sizeof(MVMuint64)); MVMObject **objects = MVM_calloc(argument->handle_count, sizeof(MVMObject *)); MVMuint16 *counts = MVM_calloc(argument->handle_count, sizeof(MVMuint16)); MVMuint16 idx; MVMuint16 classes_count = 0; /* Set up our sentinel values. Any object that represents itself * is the end of a chain. At the beginning, everything represents * itself. * Critically, this allows us to use 0 as a valid representant. */ for (idx = 0; idx < argument->handle_count; idx++) { representant[idx] = idx; } for (idx = 0; idx < argument->handle_count; idx++) { MVMuint16 other_idx; objects[idx] = find_handle_target(dtc, argument->handles[idx]); for (other_idx = 0; other_idx < idx; other_idx++) { if (representant[other_idx] != other_idx) continue; if (objects[idx] == objects[other_idx]) { representant[other_idx] = idx; } } } /* First, we have to count how many distinct classes there are. * Whenever we hit 2, we know we've found a class. */ for (idx = 0; idx < argument->handle_count; idx++) { MVMuint16 the_repr = find_representant(representant, idx); counts[the_repr]++; if (counts[the_repr] == 2) classes_count++; } /* Send the header of the message */ cmp_write_map(ctx, 3); cmp_write_str(ctx, "id", 2); cmp_write_integer(ctx, argument->id); cmp_write_str(ctx, "type", 4); cmp_write_integer(ctx, MT_HandleEquivalenceResponse); cmp_write_str(ctx, "classes", 7); /* Now we can write out the classes by following the representant * chain until we find one whose representant is itself. */ cmp_write_array(ctx, classes_count); for (idx = 0; idx < argument->handle_count; idx++) { if (representant[idx] != idx) { MVMuint16 count = counts[find_representant(representant, idx)]; MVMuint16 pointer = idx; cmp_write_array(ctx, count); do { MVMuint16 current_representant = representant[pointer]; representant[pointer] = pointer; cmp_write_integer(ctx, argument->handles[pointer]); pointer = current_representant; } while (representant[pointer] != pointer); cmp_write_integer(ctx, argument->handles[pointer]); } } MVM_free(representant); MVM_free(objects); } static MVMint32 create_context_or_code_obj_debug_handle(MVMThreadContext *dtc, cmp_ctx_t *ctx, request_data *argument, MVMThread *thread) { MVMInstance *vm = dtc->instance; MVMThread *to_do = thread ? thread : find_thread_by_id(vm, argument->thread_id); if (!to_do) { if (dtc->instance->debugserver->debugspam_protocol) fprintf(stderr, "no thread found for context/code obj handle (or thread not eligible)\n"); return 1; } if ((to_do->body.tc->gc_status & MVMGCSTATUS_MASK) != MVMGCStatus_UNABLE) { if (dtc->instance->debugserver->debugspam_protocol) fprintf(stderr, "can only retrieve a context or code obj handle if thread is 'UNABLE' (is %lu)\n", to_do->body.tc->gc_status); return 1; } { MVMFrame *cur_frame = to_do->body.tc->cur_frame; MVMuint32 frame_idx; for (frame_idx = 0; cur_frame && frame_idx < argument->frame_number; frame_idx++, cur_frame = cur_frame->caller) { } if (!cur_frame) { if (dtc->instance->debugserver->debugspam_protocol) fprintf(stderr, "couldn't create context/coderef handle: no such frame %d\n", argument->frame_number); return 1; } if (argument->type == MT_ContextHandle) { MVMROOT(dtc, cur_frame, { if (MVM_FRAME_IS_ON_CALLSTACK(dtc, cur_frame)) { cur_frame = MVM_frame_debugserver_move_to_heap(dtc, to_do->body.tc, cur_frame); } allocate_and_send_handle(dtc, ctx, argument, MVM_frame_context_wrapper(dtc, cur_frame)); }); } else if (argument->type == MT_CodeObjectHandle) { allocate_and_send_handle(dtc, ctx, argument, cur_frame->code_ref); } else { if (dtc->instance->debugserver->debugspam_protocol) fprintf(stderr, "Did not expect to see create_context_or_code_obj_debug_handle called with a %d type\n", argument->type); return 1; } } return 0; } static MVMint32 create_caller_or_outer_context_debug_handle(MVMThreadContext *dtc, cmp_ctx_t *ctx, request_data *argument, MVMThread *thread) { MVMObject *this_ctx = argument->handle_id ? find_handle_target(dtc, argument->handle_id) : dtc->instance->VMNull; MVMFrame *frame; if (!IS_CONCRETE(this_ctx) || REPR(this_ctx)->ID != MVM_REPR_ID_MVMContext) { if (dtc->instance->debugserver->debugspam_protocol) fprintf(stderr, "outer/caller context handle must refer to a definite MVMContext object\n"); return 1; } if (argument->type == MT_OuterContextRequest) { if ((frame = ((MVMContext *)this_ctx)->body.context->outer)) this_ctx = MVM_frame_context_wrapper(dtc, frame); } else if (argument->type == MT_CallerContextRequest) { if ((frame = ((MVMContext *)this_ctx)->body.context->caller)) this_ctx = MVM_frame_context_wrapper(dtc, frame); } allocate_and_send_handle(dtc, ctx, argument, this_ctx); return 0; } static MVMint32 request_context_lexicals(MVMThreadContext *dtc, cmp_ctx_t *ctx, request_data *argument) { MVMObject *this_ctx = argument->handle_id ? find_handle_target(dtc, argument->handle_id) : dtc->instance->VMNull; MVMStaticFrame *static_info; MVMLexicalRegistry *lexical_names; MVMFrame *frame; if (MVM_is_null(dtc, this_ctx) || !IS_CONCRETE(this_ctx) || REPR(this_ctx)->ID != MVM_REPR_ID_MVMContext) { if (dtc->instance->debugserver->debugspam_protocol) fprintf(stderr, "getting lexicals: context handle must refer to a definite MVMContext object\n"); return 1; } if (!(frame = ((MVMContext *)this_ctx)->body.context)) { if (dtc->instance->debugserver->debugspam_protocol) fprintf(stderr, "context doesn't have a frame?!\n"); return 1; } static_info = frame->static_info; lexical_names = static_info->body.lexical_names; if (lexical_names) { MVMLexicalRegistry *entry, *tmp; unsigned bucket_tmp; MVMuint64 lexcount = HASH_CNT(hash_handle, lexical_names); MVMuint64 lexical_index = 0; cmp_write_map(ctx, 3); cmp_write_str(ctx, "id", 2); cmp_write_integer(ctx, argument->id); cmp_write_str(ctx, "type", 4); cmp_write_integer(ctx, MT_ContextLexicalsResponse); cmp_write_str(ctx, "lexicals", 8); cmp_write_map(ctx, lexcount); if (dtc->instance->debugserver->debugspam_protocol) fprintf(stderr, "will write %lu lexicals\n", lexcount); HASH_ITER(hash_handle, lexical_names, entry, tmp, bucket_tmp) { MVMuint16 lextype = static_info->body.lexical_types[entry->value]; MVMRegister *result = &frame->env[entry->value]; char *c_key_name; if (entry->key && IS_CONCRETE(entry->key)) c_key_name = MVM_string_utf8_encode_C_string(dtc, entry->key); else { c_key_name = MVM_malloc(12 + 16); sprintf(c_key_name, "<lexical %lu>", lexical_index); } cmp_write_str(ctx, c_key_name, strlen(c_key_name)); MVM_free(c_key_name); if (lextype == MVM_reg_obj) { /* Object */ char *debugname; if (!result->o) { /* XXX this can't allocate? */ MVM_frame_vivify_lexical(dtc, frame, entry->value); } cmp_write_map(ctx, 5); cmp_write_str(ctx, "kind", 4); cmp_write_str(ctx, "obj", 3); cmp_write_str(ctx, "handle", 6); cmp_write_integer(ctx, allocate_handle(dtc, result->o)); debugname = MVM_6model_get_debug_name(dtc, result->o); cmp_write_str(ctx, "type", 4); cmp_write_str(ctx, debugname, strlen(debugname)); cmp_write_str(ctx, "concrete", 8); cmp_write_bool(ctx, IS_CONCRETE(result->o)); cmp_write_str(ctx, "container", 9); cmp_write_bool(ctx, STABLE(result->o)->container_spec == NULL ? 0 : 1); } else { cmp_write_map(ctx, 2); cmp_write_str(ctx, "kind", 4); cmp_write_str(ctx, lextype == MVM_reg_int64 ? "int" : lextype == MVM_reg_num32 ? "num" : lextype == MVM_reg_str ? "str" : "???", 3); cmp_write_str(ctx, "value", 5); if (lextype == MVM_reg_int64) { cmp_write_integer(ctx, result->i64); } else if (lextype == MVM_reg_num64) { cmp_write_double(ctx, result->n64); } else if (lextype == MVM_reg_str) { if (result->s && IS_CONCRETE(result->s)) { char *c_value = MVM_string_utf8_encode_C_string(dtc, result->s); cmp_write_str(ctx, c_value, strlen(c_value)); MVM_free(c_value); } else { cmp_write_nil(ctx); } } else { if (dtc->instance->debugserver->debugspam_protocol) fprintf(stderr, "what lexical type is %d supposed to be?\n", lextype); cmp_write_nil(ctx); } } if (dtc->instance->debugserver->debugspam_protocol) fprintf(stderr, "wrote a lexical\n"); lexical_index++; } } else { cmp_write_map(ctx, 3); cmp_write_str(ctx, "id", 2); cmp_write_integer(ctx, argument->id); cmp_write_str(ctx, "type", 4); cmp_write_integer(ctx, MT_ContextLexicalsResponse); cmp_write_str(ctx, "lexicals", 8); cmp_write_map(ctx, 0); } if (dtc->instance->debugserver->debugspam_protocol) fprintf(stderr, "done writing lexicals\n"); return 0; } MVM_STATIC_INLINE MVMObject * get_obj_at_offset(void *data, MVMint64 offset) { void *location = (char *)data + offset; return *((MVMObject **)location); } static MVMint32 request_object_attributes(MVMThreadContext *dtc, cmp_ctx_t *ctx, request_data *argument) { MVMInstance *vm = dtc->instance; MVMObject *target = argument->handle_id ? find_handle_target(dtc, argument->handle_id) : dtc->instance->VMNull; if (MVM_is_null(dtc, target)) { if (vm->debugserver->debugspam_protocol) fprintf(stderr, "target of attributes request is null\n"); return 1; } if (!IS_CONCRETE(target)) { if (vm->debugserver->debugspam_protocol) fprintf(stderr, "target of attributes request is not concrete\n"); return 1; } if (dtc->instance->debugserver->debugspam_protocol) fprintf(stderr, "writing attributes of a %s\n", MVM_6model_get_debug_name(dtc, target)); cmp_write_map(ctx, 3); cmp_write_str(ctx, "id", 2); cmp_write_integer(ctx, argument->id); cmp_write_str(ctx, "type", 4); cmp_write_integer(ctx, MT_ObjectAttributesResponse); cmp_write_str(ctx, "attributes", 10); if (REPR(target)->ID == MVM_REPR_ID_P6opaque) { MVMP6opaqueREPRData *repr_data = (MVMP6opaqueREPRData *)STABLE(target)->REPR_data; MVMP6opaqueBody *data = MVM_p6opaque_real_data(dtc, OBJECT_BODY(target)); if (repr_data) { MVMint16 num_attributes = 0; MVMP6opaqueNameMap * name_to_index_mapping = repr_data->name_to_index_mapping; while (name_to_index_mapping != NULL && name_to_index_mapping->class_key != NULL) { num_attributes += name_to_index_mapping->num_attrs; name_to_index_mapping++; } name_to_index_mapping = repr_data->name_to_index_mapping; cmp_write_array(ctx, num_attributes); if (vm->debugserver->debugspam_protocol) fprintf(stderr, "going to write out %d attributes\n", num_attributes); if (name_to_index_mapping != NULL) { MVMint16 i; MVMP6opaqueNameMap *cur_map_entry = name_to_index_mapping; while (cur_map_entry->class_key != NULL) { MVMint16 i; MVMint64 slot; char *class_name = MVM_6model_get_stable_debug_name(dtc, cur_map_entry->class_key->st); if (vm->debugserver->debugspam_protocol) fprintf(stderr, "class %s has %d attributes\n", class_name, cur_map_entry->num_attrs); for (i = 0; i < cur_map_entry->num_attrs; i++) { char * name = MVM_string_utf8_encode_C_string(dtc, cur_map_entry->names[i]); slot = cur_map_entry->slots[i]; if (slot >= 0) { MVMuint16 const offset = repr_data->attribute_offsets[slot]; MVMSTable * const attr_st = repr_data->flattened_stables[slot]; if (attr_st == NULL) { MVMObject *value = get_obj_at_offset(data, offset); char *value_debug_name = value ? MVM_6model_get_debug_name(dtc, value) : "VMNull"; if (vm->debugserver->debugspam_protocol) fprintf(stderr, "Writing an object attribute\n"); cmp_write_map(ctx, 7); cmp_write_str(ctx, "name", 4); cmp_write_str(ctx, name, strlen(name)); cmp_write_str(ctx, "class", 5); cmp_write_str(ctx, class_name, strlen(class_name)); cmp_write_str(ctx, "kind", 4); cmp_write_str(ctx, "obj", 3); cmp_write_str(ctx, "handle", 6); cmp_write_integer(ctx, allocate_handle(dtc, value)); cmp_write_str(ctx, "type", 4); cmp_write_str(ctx, value_debug_name, strlen(value_debug_name)); cmp_write_str(ctx, "concrete", 8); cmp_write_bool(ctx, !MVM_is_null(dtc, value) && IS_CONCRETE(value)); cmp_write_str(ctx, "container", 9); if (MVM_is_null(dtc, value)) cmp_write_bool(ctx, 0); else cmp_write_bool(ctx, STABLE(value)->container_spec == NULL ? 0 : 1); } else { const MVMStorageSpec *attr_storage_spec = attr_st->REPR->get_storage_spec(dtc, attr_st); if (vm->debugserver->debugspam_protocol) fprintf(stderr, "Writing a native attribute\n"); cmp_write_map(ctx, 4); cmp_write_str(ctx, "name", 4); cmp_write_str(ctx, name, strlen(name)); cmp_write_str(ctx, "class", 5); cmp_write_str(ctx, class_name, strlen(class_name)); cmp_write_str(ctx, "kind", 4); switch (attr_storage_spec->boxed_primitive) { case MVM_STORAGE_SPEC_BP_INT: cmp_write_str(ctx, "int", 3); cmp_write_str(ctx, "value", 5); cmp_write_integer(ctx, attr_st->REPR->box_funcs.get_int(dtc, attr_st, target, (char *)data + offset)); break; case MVM_STORAGE_SPEC_BP_NUM: cmp_write_str(ctx, "num", 3); cmp_write_str(ctx, "value", 5); cmp_write_double(ctx, attr_st->REPR->box_funcs.get_num(dtc, attr_st, target, (char *)data + offset)); break; case MVM_STORAGE_SPEC_BP_STR: { MVMString * const s = attr_st->REPR->box_funcs.get_str(dtc, attr_st, target, (char *)data + offset); char * str; if (s) str = MVM_string_utf8_encode_C_string(dtc, s); cmp_write_str(ctx, "str", 3); cmp_write_str(ctx, "value", 5); if (s) { cmp_write_str(ctx, str, strlen(str)); MVM_free(str); } else { cmp_write_nil(ctx); } break; } default: cmp_write_str(ctx, "error", 5); cmp_write_str(ctx, "value", 5); cmp_write_str(ctx, "error", 5); break; } } } MVM_free(name); } cur_map_entry++; } } return 0; } else { if (vm->debugserver->debugspam_protocol) fprintf(stderr, "This class isn't composed yet!\n"); cmp_write_str(ctx, "error: not composed yet", 22); return 0; } } else { cmp_write_array(ctx, 0); return 0; } return 1; } static void write_object_features(MVMThreadContext *tc, cmp_ctx_t *ctx, MVMuint8 attributes, MVMuint8 positional, MVMuint8 associative) { cmp_write_str(ctx, "attr_features", 13); cmp_write_bool(ctx, attributes); cmp_write_str(ctx, "pos_features", 12); cmp_write_bool(ctx, positional); cmp_write_str(ctx, "ass_features", 12); cmp_write_bool(ctx, associative); } static void write_vmarray_slot_type(MVMThreadContext *tc, cmp_ctx_t *ctx, MVMuint8 slot_type) { char *text = "unknown"; switch (slot_type) { case MVM_ARRAY_OBJ: text = "obj"; break; case MVM_ARRAY_STR: text = "str"; break; case MVM_ARRAY_I64: text = "i64"; break; case MVM_ARRAY_I32: text = "i32"; break; case MVM_ARRAY_I16: text = "i16"; break; case MVM_ARRAY_I8: text = "i8"; break; case MVM_ARRAY_N64: text = "n64"; break; case MVM_ARRAY_N32: text = "n32"; break; case MVM_ARRAY_U64: text = "u64"; break; case MVM_ARRAY_U32: text = "u32"; break; case MVM_ARRAY_U16: text = "u16"; break; case MVM_ARRAY_U8: text = "u8"; break; case MVM_ARRAY_U4: text = "u4"; break; case MVM_ARRAY_U2: text = "u2"; break; case MVM_ARRAY_U1: text = "u1"; break; case MVM_ARRAY_I4: text = "i4"; break; case MVM_ARRAY_I2: text = "i2"; break; case MVM_ARRAY_I1: text = "i1"; break; } cmp_write_str(ctx, text, strlen(text)); } static MVMuint16 write_vmarray_slot_kind(MVMThreadContext *tc, cmp_ctx_t *ctx, MVMuint8 slot_type) { char *text = "unknown"; MVMuint16 kind = 0; switch (slot_type) { case MVM_ARRAY_OBJ: text = "obj"; kind = MVM_reg_obj; break; case MVM_ARRAY_STR: text = "str"; kind = MVM_reg_str; break; case MVM_ARRAY_I64: case MVM_ARRAY_I32: case MVM_ARRAY_I16: case MVM_ARRAY_I8: text = "int"; kind = MVM_reg_int64; break; case MVM_ARRAY_N64: case MVM_ARRAY_N32: text = "num"; kind = MVM_reg_num64; break; case MVM_ARRAY_U64: case MVM_ARRAY_U32: case MVM_ARRAY_U16: case MVM_ARRAY_U8: case MVM_ARRAY_U4: case MVM_ARRAY_U2: case MVM_ARRAY_U1: case MVM_ARRAY_I4: case MVM_ARRAY_I2: case MVM_ARRAY_I1: text = "int"; kind = MVM_reg_int64; break; } cmp_write_str(ctx, text, strlen(text)); return kind; } static MVMint32 request_object_metadata(MVMThreadContext *dtc, cmp_ctx_t *ctx, request_data *argument) { MVMInstance *vm = dtc->instance; MVMObject *target = argument->handle_id ? find_handle_target(dtc, argument->handle_id) : dtc->instance->VMNull; MVMint64 slots = 2; /* Always have the repr name and debug name */ MVMuint32 repr_id; if (MVM_is_null(dtc, target)) { return 1; } cmp_write_map(ctx, 3); cmp_write_str(ctx, "id", 2); cmp_write_integer(ctx, argument->id); cmp_write_str(ctx, "type", 4); cmp_write_integer(ctx, MT_ObjectMetadataResponse); cmp_write_str(ctx, "metadata", 8); if (IS_CONCRETE(target)) { slots++; if (REPR(target)->unmanaged_size) slots++; } repr_id = REPR(target)->ID; if (repr_id == MVM_REPR_ID_P6opaque) { MVMP6opaqueREPRData *repr_data = (MVMP6opaqueREPRData*)(STABLE(target)->REPR_data); MVMP6opaqueBody *body = &((MVMP6opaque *)target)->body; if (IS_CONCRETE(target)) { slots += 1; /* Replaced? */ } slots += 5; /* pos/ass del slots, int/num/str unbox slots */ /*slots++; [> storage spec <]*/ slots += 3; /* features */ cmp_write_map(ctx, slots); cmp_write_str(ctx, "p6opaque_pos_delegate_slot", 21); cmp_write_int(ctx, repr_data->pos_del_slot); cmp_write_str(ctx, "p6opaque_ass_delegate_slot", 21); cmp_write_int(ctx, repr_data->ass_del_slot); cmp_write_str(ctx, "p6opaque_unbox_int_slot", 23); cmp_write_int(ctx, repr_data->unbox_int_slot); cmp_write_str(ctx, "p6opaque_unbox_num_slot", 23); cmp_write_int(ctx, repr_data->unbox_num_slot); cmp_write_str(ctx, "p6opaque_unbox_str_slot", 23); cmp_write_int(ctx, repr_data->unbox_str_slot); if (IS_CONCRETE(target)) { cmp_write_str(ctx, "p6opaque_body_replaced", 22); cmp_write_bool(ctx, !!body->replaced); } write_object_features(dtc, ctx, 1, 0, 0); /* TODO maybe output additional unbox slots, too? */ /*cmp_write_str(ctx, "storage_spec", 12);*/ /*write_storage_spec(dtc, ctx, repr_data->storage_spec);*/ } else if (repr_id == MVM_REPR_ID_VMArray) { MVMArrayREPRData *repr_data = (MVMArrayREPRData *)(STABLE(target)->REPR_data); char *debugname = repr_data->elem_type ? MVM_6model_get_debug_name(dtc, repr_data->elem_type) : NULL; if (IS_CONCRETE(target)) { slots += 3; /* slots allocated / used, storage size */ } slots += 3; slots += 3; /* features */ cmp_write_map(ctx, slots); cmp_write_str(ctx, "vmarray_elem_size", 17); cmp_write_int(ctx, repr_data->elem_size); cmp_write_str(ctx, "vmarray_slot_type", 17); write_vmarray_slot_type(dtc, ctx, repr_data->slot_type); cmp_write_str(ctx, "vmarray_elem_type", 17); if (debugname) cmp_write_str(ctx, debugname, strlen(debugname)); else cmp_write_nil(ctx); if (IS_CONCRETE(target)) { MVMArrayBody *body = (MVMArrayBody *)OBJECT_BODY(target); cmp_write_str(ctx, "positional_elems", 16); cmp_write_int(ctx, body->elems); cmp_write_str(ctx, "vmarray_start", 13); cmp_write_int(ctx, body->start); cmp_write_str(ctx, "vmarray_ssize", 13); cmp_write_int(ctx, body->ssize); } write_object_features(dtc, ctx, 0, 1, 0); } else if (repr_id == MVM_REPR_ID_MVMHash) { if (IS_CONCRETE(target)) { slots += 4; /* num_buckets, num_items, nonideal_items, ineff_expands */ } slots += 3; /* features */ cmp_write_map(ctx, slots); if (IS_CONCRETE(target)) { MVMHashBody *body = (MVMHashBody *)OBJECT_BODY(target); MVMHashEntry *handle = body->hash_head; UT_hash_table *tbl = handle ? body->hash_head->hash_handle.tbl : 0; if (tbl) { cmp_write_str(ctx, "mvmhash_num_buckets", 19); cmp_write_int(ctx, tbl->num_buckets); cmp_write_str(ctx, "mvmhash_num_items", 17); cmp_write_int(ctx, tbl->num_items); cmp_write_str(ctx, "mvmhash_nonideal_items", 22); cmp_write_int(ctx, tbl->nonideal_items); cmp_write_str(ctx, "mvmhash_ineff_expands", 21); cmp_write_int(ctx, tbl->ineff_expands); } else { cmp_write_str(ctx, "mvmhash_num_buckets", 19); cmp_write_int(ctx, 0); cmp_write_str(ctx, "mvmhash_num_items", 17); cmp_write_int(ctx, 0); cmp_write_str(ctx, "mvmhash_nonideal_items", 22); cmp_write_int(ctx, 0); cmp_write_str(ctx, "mvmhash_ineff_expands", 21); cmp_write_int(ctx, 0); } } write_object_features(dtc, ctx, 0, 0, 1); } else if (repr_id == MVM_REPR_ID_MVMContext) { MVMFrame *frame = ((MVMContext *)target)->body.context; MVMArgProcContext *argctx = &frame->params; MVMCallsite *cs = argctx->callsite; MVMCallsiteEntry *cse = argctx->arg_flags ? argctx->arg_flags : cs->arg_flags; MVMuint16 flag_idx; MVMuint16 flag_count = argctx->arg_flags ? argctx->flag_count : cs->flag_count; char *name = MVM_string_utf8_encode_C_string(dtc, frame->static_info->body.name); char *cuuid = MVM_string_utf8_encode_C_string(dtc, frame->static_info->body.cuuid); slots += 8; slots += 3; /* features */ cmp_write_map(ctx, slots); cmp_write_str(ctx, "frame_on_heap", 13); cmp_write_bool(ctx, !MVM_FRAME_IS_ON_CALLSTACK(dtc, frame)); cmp_write_str(ctx, "frame_work_size", 15); cmp_write_int(ctx, frame->allocd_work); cmp_write_str(ctx, "frame_env_size", 15); cmp_write_int(ctx, frame->allocd_env); cmp_write_str(ctx, "frame_name", 10); cmp_write_str(ctx, name, strlen(name)); cmp_write_str(ctx, "frame_cuuid", 11); cmp_write_str(ctx, cuuid, strlen(cuuid)); cmp_write_str(ctx, "frame_num_locals", 16); cmp_write_int(ctx, frame->static_info->body.num_locals); cmp_write_str(ctx, "frame_num_lexicals", 18); cmp_write_int(ctx, frame->static_info->body.num_lexicals); cmp_write_str(ctx, "callsite_flags", 14); cmp_write_array(ctx, flag_count); for (flag_idx = 0; flag_idx < flag_count; flag_idx++) { MVMCallsiteEntry entry = cse[flag_idx]; MVMuint8 entry_count = !!(entry & MVM_CALLSITE_ARG_OBJ) + !!(entry & MVM_CALLSITE_ARG_INT) + !!(entry & MVM_CALLSITE_ARG_NUM) + !!(entry & MVM_CALLSITE_ARG_STR) + !!(entry & MVM_CALLSITE_ARG_NAMED) + !!(entry & MVM_CALLSITE_ARG_FLAT) + !!(entry & MVM_CALLSITE_ARG_FLAT_NAMED); cmp_write_array(ctx, entry_count ? entry_count : 0); if (entry & MVM_CALLSITE_ARG_OBJ) cmp_write_str(ctx, "obj", 3); if (entry & MVM_CALLSITE_ARG_INT) cmp_write_str(ctx, "int", 3); if (entry & MVM_CALLSITE_ARG_NUM) cmp_write_str(ctx, "num", 3); if (entry & MVM_CALLSITE_ARG_STR) cmp_write_str(ctx, "str", 3); if (entry & MVM_CALLSITE_ARG_NAMED) cmp_write_str(ctx, "named", 5); if (entry & MVM_CALLSITE_ARG_FLAT) cmp_write_str(ctx, "flat", 4); if (entry & MVM_CALLSITE_ARG_FLAT_NAMED) cmp_write_str(ctx, "flat&named", 10); if (!entry_count) cmp_write_str(ctx, "nothing", 7); } MVM_free(name); MVM_free(cuuid); write_object_features(dtc, ctx, 0, 0, 0); } else if (repr_id == MVM_REPR_ID_P6str && IS_CONCRETE(target)) { MVMP6strBody *body = (MVMP6strBody *)OBJECT_BODY(target); MVMString *string = body->value; char *value = MVM_string_utf8_encode_C_string(dtc, string); slots += 2; slots += 3; /* features */ if (string->body.storage_type == MVM_STRING_STRAND) slots += 5; cmp_write_map(ctx, slots); cmp_write_str(ctx, "string_value", 12); cmp_write_str(ctx, value, strlen(value)); cmp_write_str(ctx, "string_storage_kind", 19); switch (string->body.storage_type) { case MVM_STRING_GRAPHEME_32: cmp_write_str(ctx, "grapheme32", 10); break; case MVM_STRING_GRAPHEME_ASCII: cmp_write_str(ctx, "graphemeASCII", 13); break; case MVM_STRING_GRAPHEME_8: cmp_write_str(ctx, "grapheme8", 9); break; case MVM_STRING_STRAND: cmp_write_str(ctx, "strands", 7); break; default: cmp_write_str(ctx, "???", 3); } if (string->body.storage_type == MVM_STRING_STRAND) { MVMuint16 num_strands = string->body.num_strands; MVMuint16 strand_idx; cmp_write_str(ctx, "string_strand_count", 19); cmp_write_int(ctx, num_strands); cmp_write_str(ctx, "string_strand_starts", 20); cmp_write_array(ctx, num_strands); for (strand_idx = 0; strand_idx < num_strands; strand_idx++) { cmp_write_int(ctx, string->body.storage.strands[strand_idx].start); } cmp_write_str(ctx, "string_strand_ends", 18); cmp_write_array(ctx, num_strands); for (strand_idx = 0; strand_idx < num_strands; strand_idx++) { cmp_write_int(ctx, string->body.storage.strands[strand_idx].end); } cmp_write_str(ctx, "string_strand_repetitions", 25); cmp_write_array(ctx, num_strands); for (strand_idx = 0; strand_idx < num_strands; strand_idx++) { cmp_write_int(ctx, string->body.storage.strands[strand_idx].repetitions); } cmp_write_str(ctx, "string_strand_target_length", 27); cmp_write_array(ctx, num_strands); for (strand_idx = 0; strand_idx < num_strands; strand_idx++) { cmp_write_int(ctx, string->body.storage.strands[strand_idx].blob_string->body.num_graphs); } } write_object_features(dtc, ctx, 0, 0, 0); MVM_free(value); } else if (repr_id == MVM_REPR_ID_ReentrantMutex && IS_CONCRETE(target)) { MVMReentrantMutexBody *body = (MVMReentrantMutexBody *)OBJECT_BODY(target); slots += 3; slots += 3; /* features */ cmp_write_map(ctx, slots); cmp_write_str(ctx, "mutex_identity", 14); cmp_write_int(ctx, (uintptr_t)body->mutex); cmp_write_str(ctx, "mutex_holder", 12); cmp_write_int(ctx, MVM_load(&body->holder_id)); cmp_write_str(ctx, "mutex_lock_count", 16); cmp_write_int(ctx, MVM_load(&body->lock_count)); write_object_features(dtc, ctx, 0, 0, 0); } else if (repr_id == MVM_REPR_ID_Semaphore && IS_CONCRETE(target)) { MVMSemaphoreBody *body = (MVMSemaphoreBody *)OBJECT_BODY(target); slots += 1; slots += 3; /* features */ cmp_write_map(ctx, slots); cmp_write_str(ctx, "semaphore_identity", 14); cmp_write_int(ctx, (uintptr_t)body->sem); write_object_features(dtc, ctx, 0, 0, 0); } else { cmp_write_map(ctx, slots); } if (REPR(target)->unmanaged_size && IS_CONCRETE(target)) { cmp_write_str(ctx, "unmanaged_size", 14); cmp_write_int(ctx, REPR(target)->unmanaged_size(dtc, STABLE(target), OBJECT_BODY(target))); } if (IS_CONCRETE(target)) { cmp_write_str(ctx, "size", 4); cmp_write_int(ctx, target->header.size); } cmp_write_str(ctx, "repr_name", 9); cmp_write_str(ctx, REPR(target)->name, strlen(REPR(target)->name)); { char *debug_name = MVM_6model_get_debug_name(dtc, target); cmp_write_str(ctx, "debug_name", 10); cmp_write_str(ctx, debug_name, strlen(debug_name)); } return 0; } static MVMint32 request_object_positionals(MVMThreadContext *dtc, cmp_ctx_t *ctx, request_data *argument) { MVMObject *target = argument->handle_id ? find_handle_target(dtc, argument->handle_id) : dtc->instance->VMNull; MVMint64 slots; if (MVM_is_null(dtc, target)) { return 1; } if (REPR(target)->ID == MVM_REPR_ID_VMArray) { MVMArrayBody *body = (MVMArrayBody *)OBJECT_BODY(target); MVMArrayREPRData *repr_data = (MVMArrayREPRData *)STABLE(target)->REPR_data; MVMuint16 kind; MVMint64 index; cmp_write_map(ctx, 5); cmp_write_str(ctx, "id", 2); cmp_write_integer(ctx, argument->id); cmp_write_str(ctx, "type", 4); cmp_write_integer(ctx, MT_ObjectPositionalsResponse); cmp_write_str(ctx, "kind", 4); kind = write_vmarray_slot_kind(dtc, ctx, repr_data->slot_type); cmp_write_str(ctx, "start", 5); cmp_write_int(ctx, 0); cmp_write_str(ctx, "contents", 8); cmp_write_array(ctx, body->elems); for (index = 0; index < body->elems; index++) { MVMRegister target_reg; REPR(target)->pos_funcs.at_pos(dtc, STABLE(target), target, body, index, &target_reg, kind); switch (kind) { case MVM_reg_obj: { MVMObject *value = target_reg.o; char *value_debug_name = value ? MVM_6model_get_debug_name(dtc, value) : "VMNull"; cmp_write_map(ctx, 4); cmp_write_str(ctx, "handle", 6); cmp_write_integer(ctx, allocate_handle(dtc, value)); cmp_write_str(ctx, "type", 4); cmp_write_str(ctx, value_debug_name, strlen(value_debug_name)); cmp_write_str(ctx, "concrete", 8); cmp_write_bool(ctx, !MVM_is_null(dtc, value) && IS_CONCRETE(value)); cmp_write_str(ctx, "container", 9); if (MVM_is_null(dtc, value)) cmp_write_bool(ctx, 0); else cmp_write_bool(ctx, STABLE(value)->container_spec == NULL ? 0 : 1); break; } case MVM_reg_int64: { cmp_write_int(ctx, target_reg.i64); break; } case MVM_reg_num64: cmp_write_double(ctx, target_reg.n64); break; case MVM_reg_str: { char *c_value = MVM_string_utf8_encode_C_string(dtc, target_reg.s); cmp_write_str(ctx, c_value, strlen(c_value)); MVM_free(c_value); break; } default: cmp_write_nil(ctx); } } return 0; } return 1; } static MVMint32 request_object_associatives(MVMThreadContext *dtc, cmp_ctx_t *ctx, request_data *argument) { MVMObject *target = argument->handle_id ? find_handle_target(dtc, argument->handle_id) : dtc->instance->VMNull; if (MVM_is_null(dtc, target)) { return 1; } if (!IS_CONCRETE(target)) { return 1; } if (REPR(target)->ID == MVM_REPR_ID_MVMHash) { MVMHashBody *body = (MVMHashBody *)OBJECT_BODY(target); MVMuint64 count = HASH_CNT(hash_handle, body->hash_head); MVMHashEntry *entry = NULL; MVMHashEntry *tmp = NULL; unsigned bucket_tmp; cmp_write_map(ctx, 4); cmp_write_str(ctx, "id", 2); cmp_write_integer(ctx, argument->id); cmp_write_str(ctx, "type", 4); cmp_write_integer(ctx, MT_ObjectAssociativesResponse); cmp_write_str(ctx, "kind", 4); cmp_write_str(ctx, "obj", 3); cmp_write_str(ctx, "contents", 8); cmp_write_map(ctx, count); HASH_ITER(hash_handle, body->hash_head, entry, tmp, bucket_tmp) { char *key = MVM_string_utf8_encode_C_string(dtc, entry->hash_handle.key); MVMObject *value = entry->value; char *value_debug_name = value ? MVM_6model_get_debug_name(dtc, value) : "VMNull"; cmp_write_str(ctx, key, strlen(key)); cmp_write_map(ctx, 4); cmp_write_str(ctx, "handle", 6); cmp_write_integer(ctx, allocate_handle(dtc, value)); cmp_write_str(ctx, "type", 4); cmp_write_str(ctx, value_debug_name, strlen(value_debug_name)); cmp_write_str(ctx, "concrete", 8); cmp_write_bool(ctx, !MVM_is_null(dtc, value) && IS_CONCRETE(value)); cmp_write_str(ctx, "container", 9); if (MVM_is_null(dtc, value)) cmp_write_bool(ctx, 0); else cmp_write_bool(ctx, STABLE(value)->container_spec == NULL ? 0 : 1); MVM_free(key); } } } MVMuint8 debugspam_network; static bool socket_reader(cmp_ctx_t *ctx, void *data, size_t limit) { size_t idx; size_t total_read = 0; size_t read; MVMuint8 *orig_data = (MVMuint8 *)data; if (debugspam_network) fprintf(stderr, "asked to read %lu bytes\n", limit); while (total_read < limit) { if ((read = recv(*((Socket*)ctx->buf), data, limit, 0)) == -1) { if (debugspam_network) fprintf(stderr, "minus one\n"); return 0; } else if (read == 0) { if (debugspam_network) fprintf(stderr, "end of file - socket probably closed\nignore warnings about parse errors\n"); return 0; } if (debugspam_network) fprintf(stderr, "%lu ", read); data = (void *)(((MVMuint8*)data) + read); total_read += read; } if (debugspam_network) { fprintf(stderr, "... recv received %lu bytes\n", total_read); fprintf(stderr, "cmp read: "); for (idx = 0; idx < limit; idx++) { fprintf(stderr, "%x ", orig_data[idx]); } fprintf(stderr, "\n"); } return 1; } static size_t socket_writer(cmp_ctx_t *ctx, const void *data, size_t limit) { size_t idx; size_t total_sent = 0; size_t sent; MVMuint8 *orig_data = (MVMuint8 *)data; if (debugspam_network) fprintf(stderr, "asked to send %3lu bytes: ", limit); while (total_sent < limit) { if ((sent = send(*(Socket*)ctx->buf, data, limit, 0)) == -1) { if (debugspam_network) fprintf(stderr, "but couldn't (socket disconnected?)\n"); return 0; } else if (sent == 0) { if (debugspam_network) fprintf(stderr, "send encountered end of file\n"); return 0; } if (debugspam_network) fprintf(stderr, "%2lu ", sent); data = (void *)(((MVMuint8*)data) + sent); total_sent += sent; } if (debugspam_network) fprintf(stderr, "... send sent %3lu bytes\n", total_sent); return 1; } static bool is_valid_int(cmp_object_t *obj, MVMint64 *result) { switch (obj->type) { case CMP_TYPE_POSITIVE_FIXNUM: case CMP_TYPE_UINT8: *result = obj->as.u8; break; case CMP_TYPE_UINT16: *result = obj->as.u16; break; case CMP_TYPE_UINT32: *result = obj->as.u32; break; case CMP_TYPE_UINT64: *result = obj->as.u64; break; case CMP_TYPE_NEGATIVE_FIXNUM: case CMP_TYPE_SINT8: *result = obj->as.s8; break; case CMP_TYPE_SINT16: *result = obj->as.s16; break; case CMP_TYPE_SINT32: *result = obj->as.s32; break; case CMP_TYPE_SINT64: *result = obj->as.s64; break; case CMP_TYPE_BOOLEAN: *result = obj->as.boolean; break; default: return 0; } return 1; } #define CHECK(operation, message) do { if(!(operation)) { data->parse_fail = 1; data->parse_fail_message = (message); if (tc->instance->debugserver->debugspam_protocol) fprintf(stderr, "CMP error: %s; %s\n", cmp_strerror(ctx), message); return 0; } } while(0) #define FIELD_FOUND(field, duplicated_message) do { if(data->fields_set & (field)) { data->parse_fail = 1; data->parse_fail_message = duplicated_message; return 0; }; field_to_set = (field); } while (0) MVMint8 skip_all_read_data(cmp_ctx_t *ctx, MVMuint32 size) { char dump[1024]; while (size > 1024) { if (!socket_reader(ctx, dump, 1024)) { return 0; } size -= 1024; } if (!socket_reader(ctx, dump, size)) { return 0; } return 1; } MVMint8 skip_whole_object(MVMThreadContext *tc, cmp_ctx_t *ctx, request_data *data) { cmp_object_t obj; MVMuint32 obj_size = 0; MVMuint32 index; CHECK(cmp_read_object(ctx, &obj), "couldn't skip object from unknown key"); switch (obj.type) { case CMP_TYPE_FIXMAP: case CMP_TYPE_MAP16: case CMP_TYPE_MAP32: obj_size = obj.as.map_size * 2; for (index = 0; index < obj_size; index++) { if (!skip_whole_object(tc, ctx, data)) { return 0; } } break; case CMP_TYPE_FIXARRAY: case CMP_TYPE_ARRAY16: case CMP_TYPE_ARRAY32: obj_size = obj.as.array_size; for (index = 0; index < obj_size; index++) { if (!skip_whole_object(tc, ctx, data)) { return 0; } } break; case CMP_TYPE_FIXSTR: case CMP_TYPE_STR8: case CMP_TYPE_STR16: case CMP_TYPE_STR32: obj_size = obj.as.str_size; CHECK(skip_all_read_data(ctx, obj_size), "could not skip string data"); break; case CMP_TYPE_BIN8: case CMP_TYPE_BIN16: case CMP_TYPE_BIN32: obj_size = obj.as.bin_size; CHECK(skip_all_read_data(ctx, obj_size), "could not skip string data"); break; case CMP_TYPE_EXT8: case CMP_TYPE_EXT16: case CMP_TYPE_EXT32: case CMP_TYPE_FIXEXT1: case CMP_TYPE_FIXEXT2: case CMP_TYPE_FIXEXT4: case CMP_TYPE_FIXEXT8: case CMP_TYPE_FIXEXT16: obj_size = obj.as.ext.size; CHECK(skip_all_read_data(ctx, obj_size), "could not skip string data"); break; case CMP_TYPE_POSITIVE_FIXNUM: case CMP_TYPE_NIL: case CMP_TYPE_BOOLEAN: case CMP_TYPE_FLOAT: case CMP_TYPE_DOUBLE: case CMP_TYPE_NEGATIVE_FIXNUM: case CMP_TYPE_UINT8: case CMP_TYPE_UINT16: case CMP_TYPE_UINT32: case CMP_TYPE_UINT64: case CMP_TYPE_SINT8: case CMP_TYPE_SINT16: case CMP_TYPE_SINT32: case CMP_TYPE_SINT64: break; default: CHECK(0, "could not skip object: unhandled type"); } return 1; } MVMint32 parse_message_map(MVMThreadContext *tc, cmp_ctx_t *ctx, request_data *data) { MVMuint32 map_size = 0; MVMuint32 i; cmp_object_t obj; memset(data, 0, sizeof(request_data)); CHECK(cmp_read_object(ctx, &obj), "couldn't read envelope object!"); switch (obj.type) { case CMP_TYPE_FIXMAP: case CMP_TYPE_MAP16: case CMP_TYPE_MAP32: map_size = obj.as.map_size; break; default: if (tc->instance->debugserver->debugspam_protocol) fprintf(stderr, "expected a map, but got %d\n", obj.type); data->parse_fail = 1; data->parse_fail_message = "expected a map as the envelope"; return 0; } for (i = 0; i < map_size; i++) { char key_str[16]; MVMuint32 str_size = 16; fields_set field_to_set = 0; MVMint32 type_to_parse = 0; CHECK(cmp_read_str(ctx, key_str, &str_size), "Couldn't read string key"); if (strncmp(key_str, "type", 15) == 0) { FIELD_FOUND(FS_type, "type field duplicated"); type_to_parse = 1; } else if (strncmp(key_str, "id", 15) == 0) { FIELD_FOUND(FS_id, "id field duplicated"); type_to_parse = 1; } else if (strncmp(key_str, "thread", 15) == 0) { FIELD_FOUND(FS_thread_id, "thread field duplicated"); type_to_parse = 1; } else if (strncmp(key_str, "frame", 15) == 0) { FIELD_FOUND(FS_frame_number, "frame number field duplicated"); type_to_parse = 1; } else if (strncmp(key_str, "handle", 15) == 0) { FIELD_FOUND(FS_handle_id, "handle field duplicated"); type_to_parse = 1; } else if (strncmp(key_str, "line", 15) == 0) { FIELD_FOUND(FS_line, "line field duplicated"); type_to_parse = 1; } else if (strncmp(key_str, "suspend", 15) == 0) { FIELD_FOUND(FS_suspend, "suspend field duplicated"); type_to_parse = 1; } else if (strncmp(key_str, "stacktrace", 15) == 0) { FIELD_FOUND(FS_stacktrace, "stacktrace field duplicated"); type_to_parse = 1; } else if (strncmp(key_str, "file", 15) == 0) { FIELD_FOUND(FS_file, "file field duplicated"); type_to_parse = 2; } else if (strncmp(key_str, "handles", 15) == 0) { FIELD_FOUND(FS_handles, "handles field duplicated"); type_to_parse = 3; } else { if (tc->instance->debugserver->debugspam_protocol) fprintf(stderr, "the hell is a %s?\n", key_str); type_to_parse = -1; } if (type_to_parse == 1) { cmp_object_t object; MVMuint64 result; CHECK(cmp_read_object(ctx, &object), "Couldn't read value for a key"); CHECK(is_valid_int(&object, &result), "Couldn't read integer value for a key"); switch (field_to_set) { case FS_type: data->type = result; break; case FS_id: data->id = result; break; case FS_thread_id: data->thread_id = result; break; case FS_frame_number: data->frame_number = result; break; case FS_handle_id: data->handle_id = result; break; case FS_line: data->line = result; break; case FS_suspend: data->suspend = result; break; case FS_stacktrace: data->stacktrace = result; break; default: data->parse_fail = 1; data->parse_fail_message = "Int field to set NYI"; return 0; } data->fields_set = data->fields_set | field_to_set; } else if (type_to_parse == 2) { uint32_t strsize = 1024; char *string = MVM_calloc(strsize, sizeof(char)); if (tc->instance->debugserver->debugspam_protocol) fprintf(stderr, "reading a string for %s\n", key_str); CHECK(cmp_read_str(ctx, string, &strsize), "Couldn't read string for a key"); switch (field_to_set) { case FS_file: data->file = string; break; default: data->parse_fail = 1; data->parse_fail_message = "Str field to set NYI"; return 0; } data->fields_set = data->fields_set | field_to_set; } else if (type_to_parse == 3) { uint32_t arraysize = 0; uint32_t index; CHECK(cmp_read_array(ctx, &arraysize), "Couldn't read array for a key"); data->handle_count = arraysize; data->handles = MVM_malloc(arraysize * sizeof(MVMuint64)); for (index = 0; index < arraysize; index++) { cmp_object_t object; MVMuint64 result; CHECK(cmp_read_object(ctx, &object), "Couldn't read value for a key"); CHECK(is_valid_int(&object, &result), "Couldn't read integer value for a key"); data->handles[index] = result; } data->fields_set = data->fields_set | field_to_set; } else if (type_to_parse == -1) { skip_whole_object(tc, ctx, data); } } return check_requirements(tc, data); } #define COMMUNICATE_RESULT(operation) do { if((operation)) { communicate_error(tc, &ctx, &argument); } else { communicate_success(tc, &ctx, &argument); } } while (0) #define COMMUNICATE_ERROR(operation) do { if((operation)) { communicate_error(tc, &ctx, &argument); } } while (0) static void debugserver_worker(MVMThreadContext *tc, MVMCallsite *callsite, MVMRegister *args) { int continue_running = 1; MVMint32 command_serial; Socket listensocket; MVMInstance *vm = tc->instance; MVMuint64 port = vm->debugserver->port; vm->debugserver->thread_id = tc->thread_obj->body.thread_id; { char portstr[16]; struct addrinfo *res; int error; snprintf(portstr, 16, "%lu", port); getaddrinfo("localhost", portstr, NULL, &res); listensocket = socket(res->ai_family, SOCK_STREAM, 0); #ifndef _WIN32 { int one = 1; setsockopt(listensocket, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); } #endif if (bind(listensocket, res->ai_addr, res->ai_addrlen) == -1) { MVM_panic(1, "Could not bind to socket: %s", strerror(errno)); } freeaddrinfo(res); if (listen(listensocket, 1) == -1) { MVM_panic(1, "Could not listen on socket: %s", strerror(errno)); } } while(continue_running) { Socket clientsocket; int len; char *buffer[32]; cmp_ctx_t ctx; MVM_gc_mark_thread_blocked(tc); clientsocket = accept(listensocket, NULL, NULL); MVM_gc_mark_thread_unblocked(tc); send_greeting(&clientsocket); if (!receive_greeting(&clientsocket)) { if (tc->instance->debugserver->debugspam_protocol) fprintf(stderr, "did not receive greeting properly\n"); close(clientsocket); continue; } cmp_init(&ctx, &clientsocket, socket_reader, NULL, socket_writer); vm->debugserver->messagepack_data = (void*)&ctx; while (clientsocket) { request_data argument; MVM_gc_mark_thread_blocked(tc); parse_message_map(tc, &ctx, &argument); MVM_gc_mark_thread_unblocked(tc); uv_mutex_lock(&vm->debugserver->mutex_network_send); if (argument.parse_fail) { if (tc->instance->debugserver->debugspam_protocol) fprintf(stderr, "failed to parse this message: %s\n", argument.parse_fail_message); cmp_write_map(&ctx, 3); cmp_write_str(&ctx, "id", 2); cmp_write_integer(&ctx, argument.id); cmp_write_str(&ctx, "type", 4); cmp_write_integer(&ctx, 1); cmp_write_str(&ctx, "reason", 6); cmp_write_str(&ctx, argument.parse_fail_message, strlen(argument.parse_fail_message)); close(clientsocket); uv_mutex_unlock(&vm->debugserver->mutex_network_send); break; } if (vm->debugserver->debugspam_protocol) fprintf(stderr, "debugserver received packet %lu, command %u\n", argument.id, argument.type); switch (argument.type) { case MT_IsExecutionSuspendedRequest: send_is_execution_suspended_info(tc, &ctx, &argument); break; case MT_SuspendAll: COMMUNICATE_ERROR(request_all_threads_suspend(tc, &ctx, &argument)); break; case MT_ResumeAll: COMMUNICATE_ERROR(request_all_threads_resume(tc, &ctx, &argument)); break; case MT_SuspendOne: COMMUNICATE_ERROR(request_thread_suspends(tc, &ctx, &argument, NULL)); break; case MT_ResumeOne: COMMUNICATE_ERROR(request_thread_resumes(tc, &ctx, &argument, NULL)); break; case MT_ThreadListRequest: send_thread_info(tc, &ctx, &argument); break; case MT_ThreadStackTraceRequest: if (request_thread_stacktrace(tc, &ctx, &argument, NULL)) { communicate_error(tc, &ctx, &argument); } break; case MT_SetBreakpointRequest: MVM_debugserver_add_breakpoint(tc, &ctx, &argument); break; case MT_ClearBreakpoint: MVM_debugserver_clear_breakpoint(tc, &ctx, &argument); break; case MT_ClearAllBreakpoints: MVM_debugserver_clear_all_breakpoints(tc, &ctx, &argument); break; case MT_StepInto: COMMUNICATE_RESULT(setup_step(tc, &ctx, &argument, MVMDebugSteppingMode_STEP_INTO, NULL)); break; case MT_StepOver: COMMUNICATE_RESULT(setup_step(tc, &ctx, &argument, MVMDebugSteppingMode_STEP_OVER, NULL)); break; case MT_StepOut: COMMUNICATE_RESULT(setup_step(tc, &ctx, &argument, MVMDebugSteppingMode_STEP_OUT, NULL)); break; case MT_ObjectAttributesRequest: if (request_object_attributes(tc, &ctx, &argument)) { communicate_error(tc, &ctx, &argument); } break; case MT_ReleaseHandles: COMMUNICATE_RESULT(release_handles(tc, &ctx, &argument)); MVM_free(argument.handles); break; case MT_ContextHandle: case MT_CodeObjectHandle: if (create_context_or_code_obj_debug_handle(tc, &ctx, &argument, NULL)) { communicate_error(tc, &ctx, &argument); } break; case MT_CallerContextRequest: case MT_OuterContextRequest: if (create_caller_or_outer_context_debug_handle(tc, &ctx, &argument, NULL)) { communicate_error(tc, &ctx, &argument); } break; case MT_ContextLexicalsRequest: if (request_context_lexicals(tc, &ctx, &argument)) { communicate_error(tc, &ctx, &argument); } break; case MT_ObjectMetadataRequest: if (request_object_metadata(tc, &ctx, &argument)) { communicate_error(tc, &ctx, &argument); } break; case MT_ObjectPositionalsRequest: if (request_object_positionals(tc, &ctx, &argument)) { communicate_error(tc, &ctx, &argument); } break; case MT_ObjectAssociativesRequest: if (request_object_associatives(tc, &ctx, &argument)) { communicate_error(tc, &ctx, &argument); } break; case MT_HandleEquivalenceRequest: send_handle_equivalence_classes(tc, &ctx, &argument); break; default: /* Unknown command or NYI */ if (tc->instance->debugserver->debugspam_protocol) fprintf(stderr, "unknown command type (or NYI)\n"); cmp_write_map(&ctx, 2); cmp_write_str(&ctx, "id", 2); cmp_write_integer(&ctx, argument.id); cmp_write_str(&ctx, "type", 4); cmp_write_integer(&ctx, 0); break; } uv_mutex_unlock(&vm->debugserver->mutex_network_send); } MVM_debugserver_clear_all_breakpoints(tc, NULL, NULL); release_all_handles(tc); vm->debugserver->messagepack_data = NULL; } } /* XXX stolen verbatim from src/moar.c; maybe put into a header somewhere */ #define init_mutex(loc, name) do { \ if ((init_stat = uv_mutex_init(&loc)) < 0) { \ fprintf(stderr, "MoarVM: Initialization of " name " mutex failed\n %s\n", \ uv_strerror(init_stat)); \ exit(1); \ } \ } while (0) #define init_cond(loc, name) do { \ if ((init_stat = uv_cond_init(&loc)) < 0) { \ fprintf(stderr, "MoarVM: Initialization of " name " condition variable failed\n %s\n", \ uv_strerror(init_stat)); \ exit(1); \ } \ } while (0) MVM_PUBLIC void MVM_debugserver_init(MVMThreadContext *tc, MVMuint32 port) { MVMInstance *vm = tc->instance; MVMDebugServerData *debugserver = MVM_calloc(1, sizeof(MVMDebugServerData)); MVMObject *worker_entry_point; int threadCreateError; int init_stat; init_mutex(debugserver->mutex_cond, "debug server orchestration"); init_mutex(debugserver->mutex_network_send, "debug server network socket lock"); init_mutex(debugserver->mutex_request_list, "debug server request list lock"); init_mutex(debugserver->mutex_breakpoints, "debug server breakpoint management lock"); init_cond(debugserver->tell_threads, "debugserver signals threads"); init_cond(debugserver->tell_worker, "threads signal debugserver"); debugserver->handle_table = MVM_malloc(sizeof(MVMDebugServerHandleTable)); debugserver->handle_table->allocated = 32; debugserver->handle_table->used = 0; debugserver->handle_table->next_id = 1; debugserver->handle_table->entries = MVM_calloc(debugserver->handle_table->allocated, sizeof(MVMDebugServerHandleTableEntry)); debugserver->breakpoints = MVM_malloc(sizeof(MVMDebugServerBreakpointTable)); debugserver->breakpoints->files_alloc = 32; debugserver->breakpoints->files_used = 0; debugserver->breakpoints->files = MVM_fixed_size_alloc_zeroed(tc, vm->fsa, debugserver->breakpoints->files_alloc * sizeof(MVMDebugServerBreakpointFileTable)); debugserver->event_id = 2; debugserver->port = port; if (getenv("MDS_NETWORK")) { debugspam_network = 1; debugserver->debugspam_network = 1; } else { debugspam_network = 0; } if (getenv("MDS_PROTOCOL")) { debugserver->debugspam_protocol = 1; } vm->debugserver = debugserver; worker_entry_point = MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTCCode); ((MVMCFunction *)worker_entry_point)->body.func = debugserver_worker; MVM_thread_run(tc, MVM_thread_new(tc, worker_entry_point, 1)); } MVM_PUBLIC void MVM_debugserver_mark_handles(MVMThreadContext *tc, MVMGCWorklist *worklist, MVMHeapSnapshotState *snapshot) { MVMInstance *vm = tc->instance; if (vm->debugserver) { MVMDebugServerHandleTable *table = vm->debugserver->handle_table; MVMuint32 idx; if (table == NULL) return; for (idx = 0; idx < table->used; idx++) { if (worklist) MVM_gc_worklist_add(tc, worklist, &(table->entries[idx].target)); else MVM_profile_heap_add_collectable_rel_const_cstr(tc, snapshot, (MVMCollectable *)table->entries[idx].target, "Debug Handle"); } } }