Skip to content

Commit

Permalink
Teach the BEAM loader to load debug information
Browse files Browse the repository at this point in the history
  • Loading branch information
bjorng committed Sep 5, 2024
1 parent 9ba6eb0 commit 607681c
Show file tree
Hide file tree
Showing 5 changed files with 303 additions and 1 deletion.
20 changes: 20 additions & 0 deletions erts/emulator/beam/beam_code.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
#define MD5_SIZE MD5_DIGEST_LENGTH

typedef struct BeamCodeLineTab_ BeamCodeLineTab;
typedef struct BeamDebugTab_ BeamDebugTab;

/*
* Header of code chunks which contains additional information
Expand Down Expand Up @@ -99,6 +100,11 @@ typedef struct beam_code_header {
Uint32 *loc_index_to_cover_id;
Uint line_coverage_len;

/*
* Debug information. debug->items are indexed directly by
* the index in each `executable_line` instruction.
*/
const BeamDebugTab *debug;
#endif

/*
Expand Down Expand Up @@ -137,6 +143,20 @@ struct BeamCodeLineTab_ {
const void** func_tab[1];
};

/*
* Layout of the debug information.
*/
typedef struct {
Sint32 frame_size;
Uint32 num_vars;
Eterm *first;
} BeamDebugItem;

struct BeamDebugTab_ {
Uint32 item_count;
BeamDebugItem *items;
};

/* Total code size in bytes */
extern Uint erts_total_code_size;

Expand Down
198 changes: 198 additions & 0 deletions erts/emulator/beam/beam_file.c
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,185 @@ static int parse_type_chunk(BeamFile *beam, IFF_Chunk *chunk) {
}
}

static int parse_debug_chunk_data(BeamFile *beam, BeamReader *p_reader) {
Sint32 count;
Sint32 total_num_vars;
int i;
BeamOpAllocator op_allocator;
BeamCodeReader *op_reader;
BeamOp* op = NULL;
BeamFile_DebugTable *debug = &beam->debug;
Eterm *tp;
byte *lp;

LoadAssert(beamreader_read_i32(p_reader, &count));
LoadAssert(beamreader_read_i32(p_reader, &total_num_vars));

beamopallocator_init(&op_allocator);

op_reader = erts_alloc(ERTS_ALC_T_PREPARED_CODE, sizeof(BeamCodeReader));

op_reader->allocator = &op_allocator;
op_reader->file = beam;
op_reader->pending = NULL;
op_reader->first = 1;
op_reader->reader = *p_reader;

if (count < 0 || total_num_vars < 0) {
goto error;
}

/* For convenience of indexing, we will add a dummy zeroth entry. */
count++;

debug->item_count = count;
debug->term_count = 2 * total_num_vars;
debug->items = erts_alloc(ERTS_ALC_T_PREPARED_CODE, count * sizeof(BeamFile_DebugItem));
debug->terms = erts_alloc(ERTS_ALC_T_PREPARED_CODE, 2 * total_num_vars * sizeof(Eterm));
debug->is_literal = erts_alloc(ERTS_ALC_T_PREPARED_CODE, 2 * total_num_vars * sizeof(Eterm));

tp = debug->terms;
lp = debug->is_literal;

/* Add dummy zeroth entry. */
debug->items[0].frame_size = -1;
debug->items[0].first = tp;
debug->items[0].num_vars = 0;

for (i = 1; i < count; i++) {
BeamOpArg *arg;
int extra_args;
Sint32 num_vars;

if (!beamcodereader_next(op_reader, &op)) {
goto error;
}
if (op->op != genop_call_2) {
goto error;
}

arg = op->a;

switch (arg->type) {
case TAG_n:
debug->items[i].frame_size = -1;
break;
case TAG_u:
debug->items[i].frame_size = op->a[0].val;
break;
default:
goto error;
}

arg++;

if (arg->type != TAG_u) {
goto error;
}
extra_args = arg->val;

arg++;

num_vars = extra_args / 2;
if (2 * num_vars != extra_args || num_vars > total_num_vars) {
goto error;
}
total_num_vars -= num_vars;

debug->items[i].num_vars = num_vars;
debug->items[i].first = tp;

while (extra_args > 0) {
Eterm var_name;

if (arg[0].type != TAG_q) {
goto error;
}

var_name = beamfile_get_literal(beam, arg[0].val);
if (!is_bitstring(var_name) || TAIL_BITS(bitstring_size(var_name))) {
goto error;
}
*tp++ = arg[0].val;
*lp++ = 1;

*lp = 0;
switch (arg[1].type) {
case TAG_i:
*tp = make_small(arg[1].val);
break;
case TAG_a:
*tp = arg[1].val;
break;
case TAG_x:
*tp = make_loader_x_reg(arg[1].val);
break;
case TAG_y:
*tp = make_loader_y_reg(arg[1].val);
break;
case TAG_q:
*tp = arg[1].val;
*lp = 1;
break;
default:
goto error;
}

tp++, lp++;
arg += 2;
extra_args -= 2;
}

beamopallocator_free_op(&op_allocator, op);
op = NULL;
}

if (total_num_vars != 0) {
goto error;
}

beamcodereader_close(op_reader);
beamopallocator_dtor(&op_allocator);

return 1;

error:
if (op != NULL) {
beamopallocator_free_op(&op_allocator, op);
}

beamcodereader_close(op_reader);
beamopallocator_dtor(&op_allocator);

if (debug->items) {
erts_free(ERTS_ALC_T_PREPARED_CODE, debug->items);
debug->items = NULL;
}

if (debug->terms) {
erts_free(ERTS_ALC_T_PREPARED_CODE, debug->terms);
debug->terms = NULL;
}

return 0;
}

static int parse_debug_chunk(BeamFile *beam, IFF_Chunk *chunk) {
BeamReader reader;
Sint32 version;

beamreader_init(chunk->data, chunk->size, &reader);

LoadAssert(beamreader_read_i32(&reader, &version));

if (version == 0) {
return parse_debug_chunk_data(beam, &reader);
} else {
/* Silently ignore chunk of wrong version. */
return 1;
}
}

static ErlHeapFragment *new_literal_fragment(Uint size)
{
ErlHeapFragment *bp;
Expand Down Expand Up @@ -898,6 +1077,7 @@ beamfile_read(const byte *data, size_t size, BeamFile *beam) {
MakeIffId('A', 't', 'o', 'm'), /* 11 */
MakeIffId('T', 'y', 'p', 'e'), /* 12 */
MakeIffId('M', 'e', 't', 'a'), /* 13 */
MakeIffId('D', 'b', 'g', 'B'), /* 14 */
};

static const int UTF8_ATOM_CHUNK = 0;
Expand All @@ -916,6 +1096,7 @@ beamfile_read(const byte *data, size_t size, BeamFile *beam) {
static const int OBSOLETE_ATOM_CHUNK = 11;
static const int TYPE_CHUNK = 12;
static const int META_CHUNK = 13;
static const int DEBUG_CHUNK = 14;

static const int NUM_CHUNKS = sizeof(chunk_iffs) / sizeof(chunk_iffs[0]);

Expand Down Expand Up @@ -1013,6 +1194,13 @@ beamfile_read(const byte *data, size_t size, BeamFile *beam) {
init_fallback_type_table(beam);
}

if (chunks[DEBUG_CHUNK].size > 0) {
if (!parse_debug_chunk(beam, &chunks[DEBUG_CHUNK])) {
error = BEAMFILE_READ_CORRUPT_DEBUG_TABLE;
goto error;
}
}

beam->strings.data = chunks[STR_CHUNK].data;
beam->strings.size = chunks[STR_CHUNK].size;

Expand Down Expand Up @@ -1153,6 +1341,16 @@ void beamfile_free(BeamFile *beam) {
beam->types.entries = NULL;
}

if (beam->debug.items) {
erts_free(ERTS_ALC_T_PREPARED_CODE, beam->debug.items);
erts_free(ERTS_ALC_T_PREPARED_CODE, beam->debug.terms);
erts_free(ERTS_ALC_T_PREPARED_CODE, beam->debug.is_literal);

beam->debug.items = NULL;
beam->debug.terms = NULL;
beam->debug.is_literal = NULL;
}

if (beam->static_literals.entries) {
beamfile_literal_dtor(&beam->static_literals);
}
Expand Down
18 changes: 17 additions & 1 deletion erts/emulator/beam/beam_file.h
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,20 @@ typedef struct {
BeamType *entries;
} BeamFile_TypeTable;

typedef struct {
Sint32 frame_size;
Sint32 num_vars;
Eterm *first;
} BeamFile_DebugItem;

typedef struct {
Sint32 item_count;
Sint32 term_count;
BeamFile_DebugItem *items;
Eterm *terms;
byte *is_literal;
} BeamFile_DebugTable;

typedef struct {
IFF_File iff;

Expand All @@ -166,6 +180,7 @@ typedef struct {
BeamFile_LambdaTable lambdas;
BeamFile_LineTable lines;
BeamFile_TypeTable types;
BeamFile_DebugTable debug;

/* Static literals are those defined in the file, and dynamic literals are
* those created when loading. The former is positively indexed starting
Expand Down Expand Up @@ -206,7 +221,8 @@ enum beamfile_read_result {
BEAMFILE_READ_CORRUPT_LAMBDA_TABLE,
BEAMFILE_READ_CORRUPT_LINE_TABLE,
BEAMFILE_READ_CORRUPT_LITERAL_TABLE,
BEAMFILE_READ_CORRUPT_TYPE_TABLE
BEAMFILE_READ_CORRUPT_TYPE_TABLE,
BEAMFILE_READ_CORRUPT_DEBUG_TABLE
};

typedef struct {
Expand Down
2 changes: 2 additions & 0 deletions erts/emulator/beam/beam_load.c
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ erts_prepare_loading(Binary* magic, Process *c_p, Eterm group_leader,
BeamLoadError0(stp, "corrupt locals table");
case BEAMFILE_READ_CORRUPT_TYPE_TABLE:
BeamLoadError0(stp, "corrupt type table");
case BEAMFILE_READ_CORRUPT_DEBUG_TABLE:
BeamLoadError0(stp, "corrupt BEAM debug information table");
case BEAMFILE_READ_SUCCESS:
break;
}
Expand Down
Loading

0 comments on commit 607681c

Please sign in to comment.