From: Zoltan Varga Date: Wed, 11 Jan 2017 17:12:32 +0000 (-0500) Subject: Lldb support code for mono (#4225) X-Git-Url: http://wien.tomnetworks.com/gitweb/?a=commitdiff_plain;h=6e72e0ec9502cd4046fbe8ce166106f96b66bb6e;p=mono.git Lldb support code for mono (#4225) * Initial version of lldb support code. * [lldb] Keep all the debug entries in a linked list so lldb can read it after attaching to the process. * [lldb] Add an --enable-minimal=lldb option to disable the code. Add locking. * [lldb] Add support for dynamic methods by adding a new protocol entry for unloading code regions. * [lldb] Add beginnings of a test suite using the lldb python bindings. * [lldb] Emit line number info for methods, not used yet. * [lldb] Add dummy fields to structures to make sure 64 bit fields are 64 bit aligned. * [lldb] Finish emission of line number info. * [lldb] Use mono_method_full_name () for computing method names. * [lldb] Emit info for specific trampolines as well. * [lldb] Add tests for line number info. * [lldb] Fix unwind info encoding on x86. * [lldb] Skip seq points with no native offset. * [lldb] Add tests for breakpoints. --- diff --git a/configure.ac b/configure.ac index 69e558b7f93..9b1f7fae5ba 100644 --- a/configure.ac +++ b/configure.ac @@ -992,7 +992,7 @@ fi AC_ARG_ENABLE(minimal, [ --enable-minimal=LIST drop support for LIST subsystems. LIST is a comma-separated list from: aot, profiler, decimal, pinvoke, debug, appdomains, verifier, reflection_emit, reflection_emit_save, large_code, logging, com, ssa, generics, attach, jit, simd, soft_debug, perfcounters, normalization, assembly_remapping, shared_perfcounters, remoting, - security, sgen_remset, sgen_marksweep_par, sgen_marksweep_fixed, sgen_marksweep_fixed_par, sgen_copying.], + security, lldb, sgen_remset, sgen_marksweep_par, sgen_marksweep_fixed, sgen_marksweep_fixed_par, sgen_copying.], [ for feature in `echo "$enable_minimal" | sed -e "s/,/ /g"`; do eval "mono_feature_disable_$feature='yes'" @@ -1137,6 +1137,11 @@ if test "x$mono_feature_disable_security" = "xyes"; then AC_MSG_NOTICE([Disabled CAS/CoreCLR security manager (used e.g. for Moonlight)]) fi +if test "x$mono_feature_disable_lldb" = "xyes"; then + AC_DEFINE(DISABLE_LLDB, 1, [Disable support code for the LLDB plugin.]) + AC_MSG_NOTICE([Disabled LLDB plugin support code.]) +fi + if test "x$mono_feature_disable_sgen_remset" = "xyes"; then AC_DEFINE(DISABLE_SGEN_REMSET, 1, [Disable wbarrier=remset support in SGEN.]) AC_MSG_NOTICE([Disabled wbarrier=remset support in SGEN.]) diff --git a/mono/mini/Makefile.am.in b/mono/mini/Makefile.am.in index 1ad86684ebd..2dd510670a4 100755 --- a/mono/mini/Makefile.am.in +++ b/mono/mini/Makefile.am.in @@ -464,7 +464,9 @@ common_sources = \ mini-cross-helpers.c \ arch-stubs.c \ llvm-runtime.h \ - type-checking.c + type-checking.c \ + lldb.h \ + lldb.c test_sources = \ basic-calls.cs \ diff --git a/mono/mini/lldb.c b/mono/mini/lldb.c new file mode 100644 index 00000000000..83c447969e3 --- /dev/null +++ b/mono/mini/lldb.c @@ -0,0 +1,683 @@ +/* + * lldb.c: 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 +#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; + /* Keep these as pointers so accessing them is atomic */ + DebugEntry *entry; + /* List of all entries */ + DebugEntry *all_entries; +} 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; + +#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_register_code (); + + 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); +} + +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; + + 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); + 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)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) +{ + 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_arch_get_cie_program (); + emit_unwind_info (unwind_ops, buf); + + buffer_add_string (buf, ""); + + add_entry (ENTRY_TRAMPOLINE, buf); + buffer_free (buf); +} + +/* +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 diff --git a/mono/mini/lldb.h b/mono/mini/lldb.h new file mode 100644 index 00000000000..6c601376c39 --- /dev/null +++ b/mono/mini/lldb.h @@ -0,0 +1,17 @@ +#ifndef __MONO_XDEBUG_LLDB_H__ +#define __MONO_XDEBUG_LLDB_H__ + +#include "config.h" +#include "mini.h" + +void mono_lldb_init (const char *options); + +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 diff --git a/mono/mini/mini-runtime.c b/mono/mini/mini-runtime.c index 706a777bcc5..cca68be1161 100644 --- a/mono/mini/mini-runtime.c +++ b/mono/mini/mini-runtime.c @@ -81,6 +81,7 @@ #include "mini-gc.h" #include "mini-llvm.h" #include "debugger-agent.h" +#include "lldb.h" #ifdef MONO_ARCH_LLVM_SUPPORTED #ifdef ENABLE_LLVM @@ -353,6 +354,15 @@ void *mono_global_codeman_reserve (int size) } } +/* The callback shouldn't take any locks */ +void +mono_global_codeman_foreach (MonoCodeManagerFunc func, void *user_data) +{ + mono_jit_lock (); + mono_code_manager_foreach (global_codeman, func, user_data); + mono_jit_unlock (); +} + #if defined(__native_client_codegen__) && defined(__native_client__) void mono_nacl_gc() @@ -481,6 +491,7 @@ mono_tramp_info_register (MonoTrampInfo *info, MonoDomain *domain) mono_jit_unlock (); mono_save_trampoline_xdebug_info (info); + mono_lldb_save_trampoline_info (info); /* Only register trampolines that have unwind infos */ if (mono_get_root_domain () && copy->uw_info) @@ -1940,6 +1951,7 @@ mono_jit_free_method (MonoDomain *domain, MonoMethod *method) return; mono_debug_remove_method (method, domain); + mono_lldb_remove_method (domain, method, ji); mono_domain_lock (domain); g_hash_table_remove (domain_jit_info (domain)->dynamic_code_hash, method); @@ -3121,6 +3133,8 @@ mini_parse_debug_option (const char *option) debug_options.dyn_runtime_invoke = TRUE; else if (!strcmp (option, "gdb")) debug_options.gdb = TRUE; + else if (!strcmp (option, "lldb")) + debug_options.lldb = TRUE; else if (!strcmp (option, "explicit-null-checks")) debug_options.explicit_null_checks = TRUE; else if (!strcmp (option, "gen-seq-points")) @@ -3568,6 +3582,11 @@ mini_init (const char *filename, const char *runtime_version) mono_unwind_init (); + if (mini_get_debug_options ()->lldb || g_getenv ("MONO_LLDB")) { + mono_lldb_init (""); + mono_dont_free_domains = TRUE; + } + #ifdef XDEBUG_ENABLED if (g_getenv ("MONO_XDEBUG")) { const char *xdebug_opts = g_getenv ("MONO_XDEBUG"); diff --git a/mono/mini/mini-trampolines.c b/mono/mini/mini-trampolines.c index 501e8a0ae91..7d6712c958b 100644 --- a/mono/mini/mini-trampolines.c +++ b/mono/mini/mini-trampolines.c @@ -18,6 +18,7 @@ #include #include "mini.h" +#include "lldb.h" /* * Address of the trampoline code. This is used by the debugger to check @@ -1436,10 +1437,17 @@ mono_get_trampoline_code (MonoTrampolineType tramp_type) gpointer mono_create_specific_trampoline (gpointer arg1, MonoTrampolineType tramp_type, MonoDomain *domain, guint32 *code_len) { + gpointer code; + guint32 len; + if (mono_aot_only) - return mono_aot_create_specific_trampoline (mono_defaults.corlib, arg1, tramp_type, domain, code_len); + code = mono_aot_create_specific_trampoline (mono_defaults.corlib, arg1, tramp_type, domain, &len); else - return mono_arch_create_specific_trampoline (arg1, tramp_type, domain, code_len); + code = mono_arch_create_specific_trampoline (arg1, tramp_type, domain, &len); + mono_lldb_save_specific_trampoline_info (arg1, tramp_type, domain, code, len); + if (code_len) + *code_len = len; + return code; } gpointer diff --git a/mono/mini/mini.c b/mono/mini/mini.c index fdbf7faea42..88dc3a514c7 100644 --- a/mono/mini/mini.c +++ b/mono/mini/mini.c @@ -77,6 +77,7 @@ #include "debugger-agent.h" #include "llvm-runtime.h" #include "mini-llvm.h" +#include "lldb.h" MonoTraceSpec *mono_jit_trace_calls; MonoMethodDesc *mono_inject_async_exc_method; @@ -3880,13 +3881,14 @@ mini_method_compile (MonoMethod *method, guint32 opts, MonoDomain *domain, JitFl } #endif - if (!cfg->compile_aot) - mono_save_xdebug_info (cfg); - MONO_TIME_TRACK (mono_jit_stats.jit_gc_create_gc_map, mini_gc_create_gc_map (cfg)); - MONO_TIME_TRACK (mono_jit_stats.jit_save_seq_point_info, mono_save_seq_point_info (cfg)); + if (!cfg->compile_aot) { + mono_save_xdebug_info (cfg); + mono_lldb_save_method_info (cfg); + } + if (cfg->verbose_level >= 2) { char *id = mono_method_full_name (cfg->method, FALSE); mono_disassemble_code (cfg, cfg->native_code, cfg->code_len, id + 3); diff --git a/mono/mini/mini.h b/mono/mini/mini.h index 643d6c748d2..f1ab4759122 100644 --- a/mono/mini/mini.h +++ b/mono/mini/mini.h @@ -2136,6 +2136,7 @@ typedef struct { gboolean suspend_on_unhandled; gboolean dyn_runtime_invoke; gboolean gdb; + gboolean lldb; gboolean use_fallback_tls; /* * Whenever data such as next sequence points and flags is required. @@ -2607,6 +2608,7 @@ MonoFtnDesc *mini_create_llvmonly_ftndesc (MonoDomain *domain, gpointer add gboolean mono_running_on_valgrind (void); void* mono_global_codeman_reserve (int size); +void mono_global_codeman_foreach (MonoCodeManagerFunc func, void *user_data); const char *mono_regname_full (int reg, int bank); gint32* mono_allocate_stack_slots (MonoCompile *cfg, gboolean backward, guint32 *stack_size, guint32 *stack_align); void mono_local_regalloc (MonoCompile *cfg, MonoBasicBlock *bb); diff --git a/mono/tests/Makefile.am b/mono/tests/Makefile.am index 5b5fec5acd3..14d9edc4c52 100644 --- a/mono/tests/Makefile.am +++ b/mono/tests/Makefile.am @@ -1794,6 +1794,11 @@ test-process-stress: $(PROCESS_STRESS_TESTS) test-runner.exe coreclr-gcstress: $(MAKE) -C $(mono_build_root)/acceptance-tests coreclr-gcstress +# Tests for the Mono lldb plugin +EXTRA_DIST += test_lldb.py test-lldb.cs +test-lldb: test-lldb.exe + python test_lldb.py $(JITTEST_PROG) + noinst_LTLIBRARIES = libtest.la AM_CPPFLAGS = $(GLIB_CFLAGS) diff --git a/mono/tests/test-lldb.cs b/mono/tests/test-lldb.cs new file mode 100644 index 00000000000..9a64016e752 --- /dev/null +++ b/mono/tests/test-lldb.cs @@ -0,0 +1,12 @@ +using System; +using System.Threading; +using System.Runtime.CompilerServices; + +public class Tests +{ + public static void Main () { + // Keep this at test-lldb.cs:9 + Console.WriteLine ("Hello"); + Thread.Sleep (100000); + } +} diff --git a/mono/tests/test_lldb.py b/mono/tests/test_lldb.py new file mode 100644 index 00000000000..eb557108a6f --- /dev/null +++ b/mono/tests/test_lldb.py @@ -0,0 +1,87 @@ +#!/usr/bin/python + +import os +import sys + +import unittest +import lldb + +if len (sys.argv) == 1: + sys.stderr.write ('Usage: test_lldb.py \n') + sys.exit (1) +mono_exe = sys.argv [1] +test_exe = 'test-lldb.exe' + +class TestLldb(unittest.TestCase): + + def setUp (self): + self.dbg = lldb.SBDebugger.Create () + + self.dbg.SetAsync (False) + self.target = self.dbg.CreateTargetWithFileAndArch (mono_exe, lldb.LLDB_ARCH_DEFAULT) + self.process = None + #self.dbg.HandleCommand ('log enable lldb jit') + + def tearDown (self): + if self.process != None: + self.process.Kill () + + def test_stacktraces (self): + bp = self.target.BreakpointCreateByName ('ves_icall_System_Threading_Thread_Sleep_internal') + self.assertEqual (bp.GetNumLocations (), 1) + + process = self.target.LaunchSimple (['--debug', test_exe], ['MONO_LLDB=1'], os.getcwd()) + self.process = process + self.assertNotEqual (process, None) + + state = process.GetState () + self.assertEqual (state, lldb.eStateStopped) + + # Stopped in the Sleep icall + findex = 0 + thread = process.GetThreadAtIndex (0) + frame = thread.GetFrameAtIndex (findex) + name = frame.GetSymbol().GetName () + self.assertEqual (name, 'ves_icall_System_Threading_Thread_Sleep_internal') + findex += 1 + + frame = thread.GetFrameAtIndex (findex) + name = frame.GetSymbol().GetName () + if name == 'ves_icall_System_Threading_Thread_Sleep_internal': + # inlined + findex += 1 + frame = thread.GetFrameAtIndex (findex) + name = frame.GetSymbol().GetName () + self.assertEqual (name, '(wrapper managed-to-native) System.Threading.Thread:SleepInternal (int)') + + findex += 1 + frame = thread.GetFrameAtIndex (findex) + name = frame.GetSymbol().GetName () + self.assertEqual (name, 'System.Threading.Thread:Sleep (int)') + self.assertTrue (str (frame.GetLineEntry ().GetFileSpec()).find ('thread.cs') != -1) + findex += 1 + + frame = thread.GetFrameAtIndex (findex) + name = frame.GetSymbol().GetName () + self.assertEqual (name, 'Tests:Main ()') + self.assertTrue (str (frame.GetLineEntry ().GetFileSpec()).find ('test-lldb.cs') != -1) + + def test_breakpoints (self): + bp = self.target.BreakpointCreateByLocation ('test-lldb.cs', 9) + + process = self.target.LaunchSimple (['--debug', test_exe], ['MONO_LLDB=1'], os.getcwd()) + self.process = process + self.assertNotEqual (process, None) + + state = process.GetState () + self.assertEqual (state, lldb.eStateStopped) + # Stopped in foo () + thread = process.GetThreadAtIndex (0) + frame = thread.GetFrameAtIndex (0) + name = frame.GetSymbol().GetName () + self.assertEqual (name, 'Tests:Main ()') + +if __name__ == '__main__': + suite = unittest.TestLoader().loadTestsFromTestCase(TestLldb) + unittest.TextTestRunner(verbosity=2).run(suite) +