716 lines
16 KiB
C
716 lines
16 KiB
C
/**
|
|
* \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 <mono/metadata/debug-internals.h>
|
|
#include <mono/utils/mono-counters.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;
|
|
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
|