diff --git a/erts/emulator/beam/atom.names b/erts/emulator/beam/atom.names index b017300f4a5e..62d0313b4087 100644 --- a/erts/emulator/beam/atom.names +++ b/erts/emulator/beam/atom.names @@ -111,6 +111,7 @@ atom asynchronous atom atom atom atom_used atom attributes +atom auto atom auto_connect atom await_exit atom await_microstate_accounting_modifications @@ -217,6 +218,7 @@ atom debug_flags atom decentralized_counters atom decimals atom default +atom debug_hash_fixed_number_of_locks atom delay_trap atom demonitor atom deterministic @@ -486,6 +488,7 @@ atom new_processes atom new_ports atom new_uniq atom newline +atom nifs atom no atom nomatch atom none @@ -766,10 +769,9 @@ atom warning atom warning_msg atom wordsize atom write_concurrency +atom x atom xor atom x86 +atom y atom yes atom yield -atom nifs -atom auto -atom debug_hash_fixed_number_of_locks diff --git a/erts/emulator/beam/beam_bif_load.c b/erts/emulator/beam/beam_bif_load.c index 59bb67ebc6a4..221498b7d32b 100644 --- a/erts/emulator/beam/beam_bif_load.c +++ b/erts/emulator/beam/beam_bif_load.c @@ -1251,6 +1251,125 @@ any_heap_refs(Eterm* start, Eterm* end, char* mod_start, Uint mod_size) return 0; } +BIF_RETTYPE code_get_debug_info_1(BIF_ALIST_1) +{ +#ifdef BEAMASM + ErtsCodeIndex code_ix; + Module* modp; + const BeamCodeHeader* hdr; + const BeamCodeLineTab* lt; + const BeamDebugTab* debug; + Sint i; + Uint alloc_size; + Eterm result = NIL; + Eterm* hp; + Eterm* hend; + + if (is_not_atom(BIF_ARG_1)) { + BIF_ERROR(BIF_P, BADARG); + } + code_ix = erts_active_code_ix(); + modp = erts_get_module(BIF_ARG_1, code_ix); + if (modp == NULL) { + BIF_ERROR(BIF_P, BADARG); + } + hdr = modp->curr.code_hdr; + if (hdr == NULL) { + BIF_ERROR(BIF_P, BADARG); + } + + lt = hdr->line_table; + + debug = hdr->debug; + if (debug == NULL) { + return am_none; + } + + alloc_size = 0; + + for (i = 0; i < debug->item_count; i++) { + /* [ {Index, {FrameSize,[{Name,Value}]} ] */ + alloc_size += 2 + 3 + 3 + debug->items[i].num_vars * (2 + 3 + 3); + } + + hp = HAlloc(BIF_P, alloc_size); + hend = hp + alloc_size; + + for (i = debug->item_count-1; i >= 0; i--) { + BeamDebugItem* items = &debug->items[i]; + Uint32 location_index; + Sint frame_size = items->frame_size; + Uint32 location; + Uint num_vars = items->num_vars; + Eterm *tp = items->first + 2 * num_vars - 2; + Eterm frame_size_term; + Eterm var_list = NIL; + Eterm tmp; + + location_index = items->location_index; + + if (location_index == (Uint32)-1) { + continue; + } + if (lt->loc_size == 2) { + location = lt->loc_tab.p2[location_index]; + } else { + ASSERT(lt->loc_size == 4); + location = lt->loc_tab.p4[location_index]; + } + + if (frame_size < 0) { + frame_size_term = am_none; + } else { + frame_size_term = make_small(frame_size); + } + + while (num_vars-- != 0) { + Eterm val; + Eterm tag; + + if (_is_loader_x_reg(tp[1])) { + Uint xreg = loader_x_reg_index(tp[1]); + tag = am_x; + val = make_small(xreg); + } else if (_is_loader_y_reg(tp[1])) { + Uint yreg = loader_y_reg_index(tp[1]); + tag = am_y; + val = make_small(yreg); + } else { + tag = am_value; + val = tp[1]; + } + tmp = TUPLE2(hp, tag, val); + hp += 3; + + tmp = TUPLE2(hp, tp[0], tmp); + hp += 3; + + tp -= 2; + + var_list = CONS(hp, tmp, var_list); + hp += 2; + } + + tmp = TUPLE2(hp, frame_size_term, var_list); + hp += 3; + + tmp = TUPLE2(hp, make_small(LOC_LINE(location)), tmp); + hp += 3; + + result = CONS(hp, tmp, result); + hp += 2; + } + + ASSERT(hp <= hend); + HRelease(BIF_P, hend, hp); + return result; +#endif + + BIF_ERROR(BIF_P, BADARG); +} + /* * Release of literal areas... * diff --git a/erts/emulator/beam/beam_code.h b/erts/emulator/beam/beam_code.h index 456d3cf2e840..f9a77b4b01ba 100644 --- a/erts/emulator/beam/beam_code.h +++ b/erts/emulator/beam/beam_code.h @@ -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 @@ -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 `debug_line` instruction. + */ + const BeamDebugTab *debug; #endif /* @@ -137,6 +143,21 @@ struct BeamCodeLineTab_ { const void** func_tab[1]; }; +/* + * Layout of the debug information. + */ +typedef struct { + Uint32 location_index; + Sint16 frame_size; + Uint16 num_vars; + Eterm *first; +} BeamDebugItem; + +struct BeamDebugTab_ { + Uint32 item_count; + BeamDebugItem *items; +}; + /* Total code size in bytes */ extern Uint erts_total_code_size; diff --git a/erts/emulator/beam/beam_file.c b/erts/emulator/beam/beam_file.c index 998391651ea6..2616321e41dc 100644 --- a/erts/emulator/beam/beam_file.c +++ b/erts/emulator/beam/beam_file.c @@ -668,6 +668,186 @@ 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; + } + + 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; + + for (i = 0; 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; + } + + debug->items[i].location_index = -1; + + arg = op->a; + + /* Process frame size. */ + switch (arg->type) { + case TAG_n: + debug->items[i].frame_size = -1; + break; + case TAG_u: + debug->items[i].frame_size = arg[0].val; + break; + default: + goto error; + } + + arg++; + + /* Get and check the number of extra arguments. */ + if (arg->type != TAG_u) { + goto error; + } + extra_args = arg->val; + + arg++; + + /* Process the list of variable mappings. */ + + 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_n: + *tp = NIL; + 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; @@ -921,6 +1101,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; @@ -939,6 +1120,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]); @@ -1036,6 +1218,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; @@ -1176,6 +1365,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); } diff --git a/erts/emulator/beam/beam_file.h b/erts/emulator/beam/beam_file.h index f32f9db2670e..38840d0906e0 100644 --- a/erts/emulator/beam/beam_file.h +++ b/erts/emulator/beam/beam_file.h @@ -150,6 +150,21 @@ typedef struct { BeamType *entries; } BeamFile_TypeTable; +typedef struct { + Uint32 location_index; + 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; @@ -166,6 +181,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 @@ -206,7 +222,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 { diff --git a/erts/emulator/beam/beam_load.c b/erts/emulator/beam/beam_load.c index 76fbf19a19d0..e0abed77a52a 100644 --- a/erts/emulator/beam/beam_load.c +++ b/erts/emulator/beam/beam_load.c @@ -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; } diff --git a/erts/emulator/beam/bif.tab b/erts/emulator/beam/bif.tab index 41998c51e17f..a1d916266b09 100644 --- a/erts/emulator/beam/bif.tab +++ b/erts/emulator/beam/bif.tab @@ -803,7 +803,8 @@ bif erts_trace_cleaner:check/0 bif erts_trace_cleaner:send_trace_clean_signal/1 # -# New in 28 +# New in 28. # bif erts_internal:system_monitor/1 bif erts_internal:system_monitor/3 +bif code:get_debug_info/1 diff --git a/erts/emulator/beam/jit/arm/instr_common.cpp b/erts/emulator/beam/jit/arm/instr_common.cpp index 0f9299fd59dd..c07e48bf7edf 100644 --- a/erts/emulator/beam/jit/arm/instr_common.cpp +++ b/erts/emulator/beam/jit/arm/instr_common.cpp @@ -181,7 +181,7 @@ void BeamModuleAssembler::emit_validate(const ArgWord &Arity) { # ifdef JIT_HARD_DEBUG emit_enter_runtime_frame(); - for (unsigned i = 0; i < arity.get(); i++) { + for (unsigned i = 0; i < Arity.get(); i++) { mov_arg(ARG1, ArgVal(ArgVal::XReg, i)); emit_enter_runtime(); @@ -3180,3 +3180,9 @@ void BeamModuleAssembler::emit_coverage(void *coverage, Uint index, Uint size) { ASSERT(0); } } + +void BeamModuleAssembler::emit_debug_line(const ArgWord &Loc, + const ArgWord &Index, + const ArgWord &Live) { + emit_validate(Live); +} diff --git a/erts/emulator/beam/jit/arm/ops.tab b/erts/emulator/beam/jit/arm/ops.tab index 24562b7727cb..78ea867675ef 100644 --- a/erts/emulator/beam/jit/arm/ops.tab +++ b/erts/emulator/beam/jit/arm/ops.tab @@ -88,7 +88,7 @@ line I executable_line I I -debug_line u u u => _ +debug_line I I t allocate t t allocate_heap t I t diff --git a/erts/emulator/beam/jit/asm_load.c b/erts/emulator/beam/jit/asm_load.c index 88f471a3dd10..c370ec6ca9ed 100644 --- a/erts/emulator/beam/jit/asm_load.c +++ b/erts/emulator/beam/jit/asm_load.c @@ -705,6 +705,20 @@ int beam_load_emit_op(LoaderState *stp, BeamOp *tmp_op) { } break; } + case op_debug_line_IIt: { + BeamFile_DebugItem *items = stp->beam.debug.items; + Uint location_index = tmp_op->a[0].val; + Sint index = tmp_op->a[1].val - 1; + + if (add_line_entry(stp, location_index, 1)) { + goto load_error; + } + + ASSERT(items[index].location_index == -1); + items[index].location_index = stp->current_li - 1; + + break; + } case op_int_code_end: /* End of code found. */ if (stp->function_number != stp->beam.code.function_count) { @@ -858,6 +872,58 @@ static const BeamCodeLineTab *finish_line_table(LoaderState *stp, return line_tab_ro; } +static const BeamDebugTab *finish_debug_table(LoaderState *stp, + char *module_base, + size_t module_size) { + BeamFile_DebugTable *debug = &stp->beam.debug; + const BeamDebugTab *debug_tab_ro; + byte *debug_tab_rw_base; + BeamDebugTab *debug_tab_top; + Eterm *debug_tab_terms; + BeamDebugItem *debug_tab_items; + Uint item_count = (Uint)debug->item_count; + Uint term_count = (Uint)debug->term_count; + Uint i; + + if (item_count == 0) { + return NULL; + } + + debug_tab_ro = (const BeamDebugTab *)beamasm_get_rodata(stp->ba, "debug"); + debug_tab_rw_base = get_writable_ptr(stp->executable_region, + stp->writable_region, + debug_tab_ro); + debug_tab_top = (BeamDebugTab *)debug_tab_rw_base; + debug_tab_terms = (Eterm *)(debug_tab_top + 1); + debug_tab_items = (BeamDebugItem *)(debug_tab_terms + term_count); + + debug_tab_top->item_count = debug->item_count; + debug_tab_top->items = debug_tab_items; + + for (i = 0; i < term_count; i++) { + if (debug->is_literal[i]) { + ASSERT(debug->is_literal[i] == 1); + debug_tab_terms[i] = + beamfile_get_literal(&stp->beam, debug->terms[i]); + } else { + ASSERT(debug->is_literal[i] == 0); + debug_tab_terms[i] = debug->terms[i]; + } + } + + for (i = 0; i < item_count; i++) { + Uint num_vars = (Uint)debug->items[i].num_vars; + + debug_tab_items[i].location_index = debug->items[i].location_index; + debug_tab_items[i].frame_size = debug->items[i].frame_size; + debug_tab_items[i].num_vars = num_vars; + debug_tab_items[i].first = debug_tab_terms; + debug_tab_terms += 2 * num_vars; + } + + return debug_tab_ro; +} + int beam_load_finish_emit(LoaderState *stp) { const BeamCodeHeader *code_hdr_ro = NULL; BeamCodeHeader *code_hdr_rw = NULL; @@ -887,6 +953,17 @@ int beam_load_finish_emit(LoaderState *stp) { beamasm_embed_bss(stp->ba, "line", line_size); } + /* Calculate size of the load BEAM debug information. */ + if (stp->beam.debug.item_count > 0) { + BeamFile_DebugTable *debug = &stp->beam.debug; + Uint debug_size; + + debug_size = sizeof(BeamDebugTab); + debug_size += (Uint)debug->item_count * sizeof(BeamFile_DebugItem); + debug_size += (Uint)debug->term_count * sizeof(Eterm); + beamasm_embed_bss(stp->ba, "debug", debug_size); + } + /* Place the string table and, optionally, attributes here. */ beamasm_embed_rodata(stp->ba, "str", @@ -977,6 +1054,10 @@ int beam_load_finish_emit(LoaderState *stp) { * names are literal lists. */ code_hdr_rw->line_table = finish_line_table(stp, module_base, module_size); + /* Debug information must be added after moving literals, since literals + * are used extensively. */ + code_hdr_rw->debug = finish_debug_table(stp, module_base, module_size); + if (stp->beam.attributes.size) { const byte *attr = beamasm_get_rodata(stp->ba, "attr"); diff --git a/erts/emulator/beam/jit/x86/instr_common.cpp b/erts/emulator/beam/jit/x86/instr_common.cpp index d722a19d3730..9c4483ce2570 100644 --- a/erts/emulator/beam/jit/x86/instr_common.cpp +++ b/erts/emulator/beam/jit/x86/instr_common.cpp @@ -3316,3 +3316,9 @@ void BeamModuleAssembler::emit_coverage(void *coverage, Uint index, Uint size) { ASSERT(0); } } + +void BeamModuleAssembler::emit_debug_line(const ArgWord &Loc, + const ArgWord &Index, + const ArgWord &Live) { + emit_validate(Live); +} diff --git a/erts/emulator/beam/jit/x86/ops.tab b/erts/emulator/beam/jit/x86/ops.tab index 0a1f871b968b..a618c56e7c36 100644 --- a/erts/emulator/beam/jit/x86/ops.tab +++ b/erts/emulator/beam/jit/x86/ops.tab @@ -88,7 +88,7 @@ line I executable_line I I -debug_line u u u => _ +debug_line I I t allocate t t allocate_heap t I t diff --git a/lib/compiler/test/beam_debug_info_SUITE.erl b/lib/compiler/test/beam_debug_info_SUITE.erl index 578afe90b63d..799764c1d177 100644 --- a/lib/compiler/test/beam_debug_info_SUITE.erl +++ b/lib/compiler/test/beam_debug_info_SUITE.erl @@ -26,6 +26,8 @@ -include("beam_opcodes.hrl"). +-include_lib("common_test/include/ct.hrl"). + -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, init_per_group/2,end_per_group/2, smoke/1, @@ -60,6 +62,8 @@ end_per_group(_GroupName, Config) -> Config. smoke(_Config) -> + {ok, Peer, Node} = ?CT_PEER(#{}), + TestBeams0 = get_unique_beam_files(), TestBeams = compiler_beams() ++ TestBeams0, @@ -76,12 +80,19 @@ smoke(_Config) -> """, io:put_chars(S), - test_lib:p_run(fun do_smoke/1, TestBeams). + test_lib:p_run(fun(Beam) -> + do_smoke(Beam, Node) + end, TestBeams), + + peer:stop(Peer), + + ok. + compiler_beams() -> filelib:wildcard(filename:join([code:lib_dir(compiler), "ebin", "*.beam"])). -do_smoke(Beam) -> +do_smoke(Beam, Node) -> try {ok,{Mod,[{abstract_code,{raw_abstract_v1,Abstr0}}]}} = beam_lib:chunks(Beam, [abstract_code]), @@ -95,7 +106,33 @@ do_smoke(Beam) -> [beam_debug_info,dexp,binary,report_errors]), SrcVars = source_variables(Abstr), IndexToFunctionMap = abstr_debug_lines(Abstr), - DebugInfo = get_debug_info(Mod, Code), + + %% Retrieve the debug information in two different ways. + {DebugInfo,CookedDebugInfo} = get_debug_info(Mod, Code), + DebugInfoBif = lists:sort(load_get_debug_info(Node, Mod, Code)), + CookedDebugInfoSorted = lists:sort(CookedDebugInfo), + + if + CookedDebugInfoSorted =:= DebugInfoBif -> + ok; + true -> + Z0 = lists:zip(CookedDebugInfoSorted, DebugInfoBif, + {pad, {short,short}}), + Z = lists:dropwhile(fun({A,B}) -> A =:= B end, Z0), + io:format("~p\n", [Z]), + io:format("~p\n", [CookedDebugInfoSorted]), + io:format("~p\n", [DebugInfoBif]), + + error(inconsistent_debug_info) + end, + + case Mod of + ?MODULE -> + %% This module has been compiled with `beam_debug_info`. + CookedDebugInfoSorted = lists:sort(code:get_debug_info(Mod)); + _ -> + ok + end, {DbgVars,DbgLiterals} = debug_info_vars(DebugInfo, IndexToFunctionMap), @@ -200,6 +237,27 @@ family_difference(F0, F1) -> S3 = sofs:family_specification(SpecFun, S2), sofs:to_external(S3). +%% Load a module on a remote node and retrieve debug information. +load_get_debug_info(Node, Mod, Beam) -> + erpc:call(Node, + fun() -> + {module,Mod} = code:load_binary(Mod, "", Beam), + DebugInfo = code:get_debug_info(Mod), + + case Mod of + ?MODULE -> + %% Don't purge the module that this fun + %% is located in. + ok; + _ -> + %% Smoke test of purging a module with + %% debug information. + _ = code:delete(Mod), + _ = code:purge(Mod) + end, + DebugInfo + end). + %% %% Extract variables mentioned in the source code. Try to remove %% variables that will never show up in the debug information; for @@ -449,7 +507,8 @@ abstr_extract_debug_lines(_, Acc) -> Acc. %%% get_debug_info(Mod, Beam) -> {ok,{Mod,[{"DbgB",DebugInfo0}, - {atoms,Atoms0}]}} = beam_lib:chunks(Beam, ["DbgB",atoms]), + {atoms,Atoms0}, + {"Line",Lines0}]}} = beam_lib:chunks(Beam, ["DbgB",atoms,"Line"]), Atoms = maps:from_list(Atoms0), Literals = case beam_lib:chunks(Beam, ["LitT"]) of {ok,{Mod,[{"LitT",Literals0}]}} -> @@ -463,8 +522,39 @@ get_debug_info(Mod, Beam) -> _NumVars:32, DebugInfo1/binary>> = DebugInfo0, 0 = Version, - DebugInfo = decode_debug_info(DebugInfo1, Literals, Atoms, Op), - lists:zip(lists:seq(1, length(DebugInfo)), DebugInfo). + RawDebugInfo0 = decode_debug_info(DebugInfo1, Literals, Atoms, Op), + RawDebugInfo = lists:zip(lists:seq(1, length(RawDebugInfo0)), RawDebugInfo0), + + %% The cooked debug info has line numbers instead of indices. + Lines = decode_line_table(Lines0, Literals, Atoms), + {beam_file,Mod,_Exp,_Attr,_Opts,Fs} = beam_disasm:file(Beam), + DebugMap = #{Index => LocationIndex || + {function,_Name,_Arity,_Entry,Is} <:- Fs, + {debug_line,LocationIndex,Index,_Live} <- Is}, + CookedDebugInfo = + [{map_get(map_get(Index, DebugMap), Lines),Info} || + {Index,Info} <:- RawDebugInfo, + is_map_key(Index, DebugMap)], + + {RawDebugInfo,CookedDebugInfo}. + +decode_line_table(<<0:32,_Bits:32,_NumIs:32,NumLines:32, + _NumFnames:32, Lines0/binary>>, + Literals, Atoms) -> + Lines = decode_line_tab_1(Lines0, Literals, Atoms, NumLines), + #{K => V || {K,V} <:- lists:zip(lists:seq(1, length(Lines)), Lines)}. + +decode_line_tab_1(_Lines, _Literals, _Atoms, 0) -> + []; +decode_line_tab_1(Lines0, Literals, Atoms, N) -> + case decode_arg(Lines0, Literals, Atoms) of + {{atom,_},Lines1} -> + decode_line_tab_1(Lines1, Literals, Atoms, N); + {{integer,Line},Lines1} -> + [Line|decode_line_tab_1(Lines1, Literals, Atoms, N - 1)]; + {nil,Lines1} -> + decode_line_tab_1(Lines1, Literals, Atoms, N) + end. decode_literal_table(<<0:32,N:32,Tab/binary>>) -> #{Index => binary_to_term(Literal) || diff --git a/lib/kernel/src/code.erl b/lib/kernel/src/code.erl index 172f4d5b280a..5d65e5d2709a 100644 --- a/lib/kernel/src/code.erl +++ b/lib/kernel/src/code.erl @@ -401,7 +401,8 @@ common reasons. module_status/0, module_status/1, modified_modules/0, - get_mode/0]). + get_mode/0, + get_debug_info/1]). -removed({rehash,0,"the code path cache feature has been removed"}). -removed({is_module_native,1,"HiPE has been removed"}). @@ -2322,3 +2323,10 @@ _See also:_ [Native Coverage Support](#module-native-coverage-support) Supported :: boolean(). coverage_support() -> erlang:nif_error(undefined). + +-doc(#{since => <<"OTP 28.0">>}). +-spec get_debug_info(Module) -> Mode when + Module :: module(), + Mode :: coverage_mode(). +get_debug_info(_Module) -> + erlang:nif_error(undefined).