/** * \file * Mono support for LLDB. * * Author: * Zoltan Varga (vargaz@gmail.com) * * Copyright 2016 Xamarin, Inc (http://www.xamarin.com) */ #include "config.h" #include "mini.h" #include "lldb.h" #include "seq-points.h" #include #include #if !defined(DISABLE_JIT) && !defined(DISABLE_LLDB) typedef enum { ENTRY_CODE_REGION = 1, ENTRY_METHOD = 2, ENTRY_TRAMPOLINE = 3, ENTRY_UNLOAD_CODE_REGION = 4 } EntryType; /* * Need to make sure these structures have the same size and alignment on * all platforms. */ /* One data packet sent from the runtime to the debugger */ typedef struct { /* Pointer to the next entry */ guint64 next_addr; /* The type of data pointed to by ADDR */ /* One of the ENTRY_ constants */ guint32 type; /* Align */ guint32 dummy; guint64 size; guint64 addr; } DebugEntry; typedef struct { /* (MAJOR << 16) | MINOR */ guint32 version; /* Align */ guint32 dummy; DebugEntry *entry; /* List of all entries */ /* Keep this as a pointer so accessing it is atomic */ DebugEntry *all_entries; /* The current entry embedded here to reduce the amount of roundtrips */ guint32 type; guint32 dummy2; guint64 size; guint64 addr; } JitDescriptor; /* * Represents a memory region used for code. */ typedef struct { /* * OBJFILE_MAGIC. This is needed to make it easier for lldb to * create object files from this packet. */ char magic [32]; guint64 start; guint32 size; int id; } CodeRegionEntry; typedef struct { int id; } UnloadCodeRegionEntry; /* * Represents a managed method */ typedef struct { guint64 code; int id; /* The id of the codegen region which contains CODE */ int region_id; int code_size; /* Align */ guint32 dummy; /* Followed by variable size data */ } MethodEntry; /* * Represents a trampoline */ typedef struct { guint64 code; int id; /* The id of the codegen region which contains CODE */ int region_id; int code_size; /* Align */ guint32 dummy; /* Followed by variable size data */ } TrampolineEntry; #define MAJOR_VERSION 1 #define MINOR_VERSION 0 static const char* OBJFILE_MAGIC = { "MONO_JIT_OBJECT_FILE" }; JitDescriptor __mono_jit_debug_descriptor = { (MAJOR_VERSION << 16) | MINOR_VERSION }; static gboolean enabled; static int id_generator; static GHashTable *codegen_regions; static DebugEntry *last_entry; static mono_mutex_t mutex; static GHashTable *dyn_codegen_regions; static gdouble register_time; static int num_entries; #define lldb_lock() mono_os_mutex_lock (&mutex) #define lldb_unlock() mono_os_mutex_unlock (&mutex) void MONO_NEVER_INLINE __mono_jit_debug_register_code (void); /* The native debugger puts a breakpoint in this function. */ void MONO_NEVER_INLINE __mono_jit_debug_register_code (void) { /* Make sure that even compilers that ignore __noinline__ don't inline this */ #if defined(__GNUC__) asm (""); #endif } /* * Functions to encode protocol data */ typedef struct { guint8 *buf, *p, *end; } Buffer; static inline void buffer_init (Buffer *buf, int size) { buf->buf = (guint8 *)g_malloc (size); buf->p = buf->buf; buf->end = buf->buf + size; } static inline int buffer_len (Buffer *buf) { return buf->p - buf->buf; } static inline void buffer_make_room (Buffer *buf, int size) { if (buf->end - buf->p < size) { int new_size = buf->end - buf->buf + size + 32; guint8 *p = (guint8 *)g_realloc (buf->buf, new_size); size = buf->p - buf->buf; buf->buf = p; buf->p = p + size; buf->end = buf->buf + new_size; } } static inline void buffer_add_byte (Buffer *buf, guint8 val) { buffer_make_room (buf, 1); buf->p [0] = val; buf->p++; } static inline void buffer_add_short (Buffer *buf, guint32 val) { buffer_make_room (buf, 2); buf->p [0] = (val >> 8) & 0xff; buf->p [1] = (val >> 0) & 0xff; buf->p += 2; } static inline void buffer_add_int (Buffer *buf, guint32 val) { buffer_make_room (buf, 4); buf->p [0] = (val >> 24) & 0xff; buf->p [1] = (val >> 16) & 0xff; buf->p [2] = (val >> 8) & 0xff; buf->p [3] = (val >> 0) & 0xff; buf->p += 4; } static inline void buffer_add_long (Buffer *buf, guint64 l) { buffer_add_int (buf, (l >> 32) & 0xffffffff); buffer_add_int (buf, (l >> 0) & 0xffffffff); } static inline void buffer_add_id (Buffer *buf, int id) { buffer_add_int (buf, (guint64)id); } static inline void buffer_add_data (Buffer *buf, guint8 *data, int len) { buffer_make_room (buf, len); memcpy (buf->p, data, len); buf->p += len; } static inline void buffer_add_string (Buffer *buf, const char *str) { int len; if (str == NULL) { buffer_add_int (buf, 0); } else { len = strlen (str); buffer_add_int (buf, len); buffer_add_data (buf, (guint8*)str, len); } } static inline void buffer_add_buffer (Buffer *buf, Buffer *data) { buffer_add_data (buf, data->buf, buffer_len (data)); } static inline void buffer_free (Buffer *buf) { g_free (buf->buf); } typedef struct { gpointer code; gpointer region_start; guint32 region_size; gboolean found; } UserData; static int find_code_region (void *data, int csize, int size, void *user_data) { UserData *ud = user_data; if ((char*)ud->code >= (char*)data && (char*)ud->code < (char*)data + csize) { ud->region_start = data; ud->region_size = csize; ud->found = TRUE; return 1; } return 0; } static void add_entry (EntryType type, Buffer *buf) { DebugEntry *entry; guint8 *data; int size = buffer_len (buf); data = g_malloc (size); memcpy (data, buf->buf, size); entry = g_malloc0 (sizeof (DebugEntry)); entry->type = type; entry->addr = (guint64)(gsize)data; entry->size = size; mono_memory_barrier (); lldb_lock (); /* The debugger can read the list of entries asynchronously, so this has to be async safe */ // FIXME: Make sure this is async safe if (last_entry) { last_entry->next_addr = (guint64)(gsize) (entry); last_entry = entry; } else { last_entry = entry; __mono_jit_debug_descriptor.all_entries = entry; } __mono_jit_debug_descriptor.entry = entry; __mono_jit_debug_descriptor.type = entry->type; __mono_jit_debug_descriptor.size = entry->size; __mono_jit_debug_descriptor.addr = entry->addr; mono_memory_barrier (); GTimer *timer = mono_time_track_start (); __mono_jit_debug_register_code (); mono_time_track_end (®ister_time, timer); num_entries ++; //printf ("%lf %d %d\n", register_time, num_entries, entry->type); lldb_unlock (); } /* * register_codegen_region: * * Register a codegen region with the debugger if needed. * Return a region id. */ static int register_codegen_region (gpointer region_start, int region_size, gboolean dynamic) { CodeRegionEntry *region_entry; int id; Buffer tmp_buf; Buffer *buf = &tmp_buf; if (dynamic) { lldb_lock (); id = ++id_generator; lldb_unlock (); } else { lldb_lock (); if (!codegen_regions) codegen_regions = g_hash_table_new (NULL, NULL); id = GPOINTER_TO_INT (g_hash_table_lookup (codegen_regions, region_start)); if (id) { lldb_unlock (); return id; } id = ++id_generator; g_hash_table_insert (codegen_regions, region_start, GINT_TO_POINTER (id)); lldb_unlock (); } buffer_init (buf, 128); region_entry = (CodeRegionEntry*)buf->p; buf->p += sizeof (CodeRegionEntry); memset (region_entry, 0, sizeof (CodeRegionEntry)); strcpy (region_entry->magic, OBJFILE_MAGIC); region_entry->id = id; region_entry->start = (gsize)region_start; region_entry->size = (gsize)region_size; add_entry (ENTRY_CODE_REGION, buf); buffer_free (buf); return id; } static void emit_unwind_info (GSList *unwind_ops, Buffer *buf) { int ret_reg; int nunwind_ops; GSList *l; ret_reg = mono_unwind_get_dwarf_pc_reg (); g_assert (ret_reg < 256); /* We use the unencoded version of the unwind info to make it easier to decode */ nunwind_ops = 0; for (l = unwind_ops; l; l = l->next) { MonoUnwindOp *op = l->data; /* lldb can't handle these */ if (op->op == DW_CFA_mono_advance_loc) break; nunwind_ops ++; } buffer_add_byte (buf, ret_reg); buffer_add_int (buf, nunwind_ops); for (l = unwind_ops; l; l = l->next) { MonoUnwindOp *op = l->data; if (op->op == DW_CFA_mono_advance_loc) break; buffer_add_int (buf, op->op); buffer_add_int (buf, op->when); int dreg; #if TARGET_X86 // LLDB doesn't see to use the switched esp/ebp if (op->reg == X86_ESP) dreg = X86_ESP; else if (op->reg == X86_EBP) dreg = X86_EBP; else dreg = mono_hw_reg_to_dwarf_reg (op->reg); #else dreg = mono_hw_reg_to_dwarf_reg (op->reg); #endif buffer_add_int (buf, dreg); buffer_add_int (buf, op->val); } } void mono_lldb_init (const char *options) { enabled = TRUE; mono_os_mutex_init_recursive (&mutex); mono_counters_register ("Time spent in LLDB", MONO_COUNTER_JIT | MONO_COUNTER_DOUBLE, ®ister_time); } typedef struct { MonoSymSeqPoint sp; int native_offset; } FullSeqPoint; static int compare_by_addr (const void *arg1, const void *arg2) { const FullSeqPoint *sp1 = arg1; const FullSeqPoint *sp2 = arg2; return sp1->native_offset - sp2->native_offset; } void mono_lldb_save_method_info (MonoCompile *cfg) { MethodEntry *entry; UserData udata; int region_id; Buffer tmpbuf; Buffer *buf = &tmpbuf; MonoDebugMethodInfo *minfo; int i, j, n_il_offsets; int *source_files; GPtrArray *source_file_list; MonoSymSeqPoint *sym_seq_points; FullSeqPoint *locs; if (!enabled) return; /* Find the codegen region which contains the code */ memset (&udata, 0, sizeof (udata)); udata.code = cfg->native_code; if (cfg->method->dynamic) { mono_code_manager_foreach (cfg->dynamic_info->code_mp, find_code_region, &udata); g_assert (udata.found); region_id = register_codegen_region (udata.region_start, udata.region_size, TRUE); lldb_lock (); if (!dyn_codegen_regions) dyn_codegen_regions = g_hash_table_new (NULL, NULL); g_hash_table_insert (dyn_codegen_regions, cfg->method, GINT_TO_POINTER (region_id)); lldb_unlock (); } else { mono_domain_code_foreach (cfg->domain, find_code_region, &udata); g_assert (udata.found); region_id = register_codegen_region (udata.region_start, udata.region_size, FALSE); } buffer_init (buf, 256); entry = (MethodEntry*)buf->p; buf->p += sizeof (MethodEntry); entry->id = ++id_generator; entry->region_id = region_id; entry->code = (gsize)cfg->native_code; entry->code_size = cfg->code_size; emit_unwind_info (cfg->unwind_ops, buf); char *s = mono_method_full_name (cfg->method, TRUE); buffer_add_string (buf, s); g_free (s); minfo = mono_debug_lookup_method (cfg->method); MonoSeqPointInfo *seq_points = cfg->seq_point_info; if (minfo && seq_points) { mono_debug_get_seq_points (minfo, NULL, &source_file_list, &source_files, &sym_seq_points, &n_il_offsets); buffer_add_int (buf, source_file_list->len); for (i = 0; i < source_file_list->len; ++i) { MonoDebugSourceInfo *sinfo = (MonoDebugSourceInfo *)g_ptr_array_index (source_file_list, i); buffer_add_string (buf, sinfo->source_file); for (j = 0; j < 16; ++j) buffer_add_byte (buf, sinfo->hash [j]); } // The sym seq points are ordered by il offset, need to order them by address int skipped = 0; locs = g_new0 (FullSeqPoint, n_il_offsets); for (i = 0; i < n_il_offsets; ++i) { locs [i].sp = sym_seq_points [i]; // FIXME: O(n^2) SeqPoint seq_point; if (mono_seq_point_find_by_il_offset (seq_points, sym_seq_points [i].il_offset, &seq_point)) { locs [i].native_offset = seq_point.native_offset; } else { locs [i].native_offset = 0xffffff; skipped ++; } } qsort (locs, n_il_offsets, sizeof (FullSeqPoint), compare_by_addr); n_il_offsets -= skipped; buffer_add_int (buf, n_il_offsets); for (i = 0; i < n_il_offsets; ++i) { MonoSymSeqPoint *sp = &locs [i].sp; const char *srcfile = ""; if (source_files [i] != -1) { MonoDebugSourceInfo *sinfo = (MonoDebugSourceInfo *)g_ptr_array_index (source_file_list, source_files [i]); srcfile = sinfo->source_file; } //printf ("%s %x %d %d\n", cfg->method->name, locs [i].native_offset, sp->il_offset, sp->line); buffer_add_int (buf, locs [i].native_offset); buffer_add_int (buf, sp->il_offset); buffer_add_int (buf, sp->line); buffer_add_int (buf, source_files [i]); buffer_add_int (buf, sp->column); buffer_add_int (buf, sp->end_line); buffer_add_int (buf, sp->end_column); } g_free (locs); g_free (source_files); g_free (sym_seq_points); g_ptr_array_free (source_file_list, TRUE); } else { buffer_add_int (buf, 0); buffer_add_int (buf, 0); } add_entry (ENTRY_METHOD, buf); buffer_free (buf); } void mono_lldb_remove_method (MonoDomain *domain, MonoMethod *method, MonoJitDynamicMethodInfo *info) { int region_id; UnloadCodeRegionEntry *entry; Buffer tmpbuf; Buffer *buf = &tmpbuf; if (!enabled) return; g_assert (method->dynamic); lldb_lock (); region_id = GPOINTER_TO_INT (g_hash_table_lookup (dyn_codegen_regions, method)); g_hash_table_remove (dyn_codegen_regions, method); lldb_unlock (); buffer_init (buf, 256); entry = (UnloadCodeRegionEntry*)buf->p; buf->p += sizeof (UnloadCodeRegionEntry); entry->id = region_id; add_entry (ENTRY_UNLOAD_CODE_REGION, buf); buffer_free (buf); /* The method is associated with the code region, so it doesn't have to be unloaded */ } void mono_lldb_save_trampoline_info (MonoTrampInfo *info) { TrampolineEntry *entry; UserData udata; int region_id; Buffer tmpbuf; Buffer *buf = &tmpbuf; if (!enabled) return; /* Find the codegen region which contains the code */ memset (&udata, 0, sizeof (udata)); udata.code = info->code; mono_global_codeman_foreach (find_code_region, &udata); if (!udata.found) mono_domain_code_foreach (mono_get_root_domain (), find_code_region, &udata); if (!udata.found) /* Can happen with AOT */ return; region_id = register_codegen_region (udata.region_start, udata.region_size, FALSE); buffer_init (buf, 1024); entry = (TrampolineEntry*)buf->p; buf->p += sizeof (TrampolineEntry); entry->id = ++id_generator; entry->region_id = region_id; entry->code = (gsize)info->code; entry->code_size = info->code_size; emit_unwind_info (info->unwind_ops, buf); buffer_add_string (buf, info->name); add_entry (ENTRY_TRAMPOLINE, buf); buffer_free (buf); } void mono_lldb_save_specific_trampoline_info (gpointer arg1, MonoTrampolineType tramp_type, MonoDomain *domain, gpointer code, guint32 code_len) { /* * Avoid emitting these for now, * they slow down execution too much, and they are * only needed during single stepping which doesn't * work anyway. */ #if 0 TrampolineEntry *entry; UserData udata; int region_id; Buffer tmpbuf; Buffer *buf = &tmpbuf; if (!enabled) return; /* Find the codegen region which contains the code */ memset (&udata, 0, sizeof (udata)); udata.code = code; mono_global_codeman_foreach (find_code_region, &udata); if (!udata.found) mono_domain_code_foreach (mono_get_root_domain (), find_code_region, &udata); g_assert (udata.found); region_id = register_codegen_region (udata.region_start, udata.region_size, FALSE); buffer_init (buf, 1024); entry = (TrampolineEntry*)buf->p; buf->p += sizeof (TrampolineEntry); entry->id = ++id_generator; entry->region_id = region_id; entry->code = (gsize)code; entry->code_size = code_len; GSList *unwind_ops = mono_unwind_get_cie_program (); emit_unwind_info (unwind_ops, buf); buffer_add_string (buf, ""); add_entry (ENTRY_TRAMPOLINE, buf); buffer_free (buf); #endif } /* DESIGN: Communication: Similar to the gdb jit interface. The runtime communicates with a plugin running inside lldb. - The runtime allocates a data packet, points a symbol with a well known name at it. - It calls a dummy function with a well known name. - The plugin sets a breakpoint at this function, causing the runtime to be suspended. - The plugin reads the data pointed to by the other symbol and processes it. The data packets are kept in a list, so lldb can read all of them after attaching. Lldb will associate an object file with each mono codegen region. Packet design: - use a flat byte array so the whole data can be read in one operation. - use 64 bit ints for pointers. */ #else void mono_lldb_init (const char *options) { g_error ("lldb support has been disabled at configure time."); } void mono_lldb_save_method_info (MonoCompile *cfg) { } void mono_lldb_save_trampoline_info (MonoTrampInfo *info) { } void mono_lldb_remove_method (MonoDomain *domain, MonoMethod *method, MonoJitDynamicMethodInfo *info) { } void mono_lldb_save_specific_trampoline_info (gpointer arg1, MonoTrampolineType tramp_type, MonoDomain *domain, gpointer code, guint32 code_len) { } #endif