Lldb support code for mono (#4225)
authorZoltan Varga <vargaz@gmail.com>
Wed, 11 Jan 2017 17:12:32 +0000 (12:12 -0500)
committerGitHub <noreply@github.com>
Wed, 11 Jan 2017 17:12:32 +0000 (12:12 -0500)
* 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.

configure.ac
mono/mini/Makefile.am.in
mono/mini/lldb.c [new file with mode: 0644]
mono/mini/lldb.h [new file with mode: 0644]
mono/mini/mini-runtime.c
mono/mini/mini-trampolines.c
mono/mini/mini.c
mono/mini/mini.h
mono/tests/Makefile.am
mono/tests/test-lldb.cs [new file with mode: 0644]
mono/tests/test_lldb.py [new file with mode: 0644]

index 69e558b7f938a0ea2f1d4564539dd8aab58d8ee2..9b1f7fae5ba29bf3a5e62ab06bf780ff033758ec 100644 (file)
@@ -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.])
index 1ad86684ebd31c65ba8d638dcf7f9835e87146e4..2dd510670a4489e03a1c8224f34470b07d1d2d4d 100755 (executable)
@@ -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 (file)
index 0000000..83c4479
--- /dev/null
@@ -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 <mono/metadata/mono-debug.h>
+#include <mono/metadata/mono-debug-debugger.h>
+#include <mono/metadata/debug-mono-symfile.h>
+
+#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 (file)
index 0000000..6c60137
--- /dev/null
@@ -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
index 706a777bcc55bdac29c51e73782797bf0af5d4e4..cca68be1161ace33f3374e0fd61081cb316c0249 100644 (file)
@@ -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");
index 501e8a0ae9187b6030fe1c926992273a3ddc8c15..7d6712c958b5b2cbaa6e6455902f4b5790a75c17 100644 (file)
@@ -18,6 +18,7 @@
 #include <mono/utils/mono-threads-coop.h>
 
 #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
index fdbf7faea426de7822feae4f9b8ac97dbac522a0..88dc3a514c77e8bdcfe967f706e57563530e44bd 100644 (file)
@@ -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);
index 643d6c748d21909d0cc5524bbc2c96099c744321..f1ab4759122b03e3c5344d748619c3a6b411f902 100644 (file)
@@ -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);
index 5b5fec5acd3c926310a0ff4ee5141d118975aa0a..14d9edc4c526fddb4c75531473b4012ed096801f 100644 (file)
@@ -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 (file)
index 0000000..9a64016
--- /dev/null
@@ -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 (file)
index 0000000..eb55710
--- /dev/null
@@ -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 <mono executable>\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)
+