Skip to content

Commit

Permalink
Add code:get_debug_info/1
Browse files Browse the repository at this point in the history
Provided that module Module contains debug information,
code:get_debug_info(Module) will read out a symbolic representation
of it.

It is unclear how useful this BIF is in practice, but its source code
shows how to access the debug information from inside the runtime
system.

The following example has been annotated with the index operand for the
`executable_line` instruction preceding each line in the source code:

    foobar(A) ->
        case A of                        % 1
            0 ->
                B = 1,                   % 2
                io:format("~p\n", [B]);  % 3
            1 ->
                C = [1,2,3],    % 4
                io:format("~p\n", [C])   % 4
        end,
        A.                               % 6

Here follows the dump of the debug information:

    1> code:get_debug_info(t).
    [{1,{none,[{<<"A">>,{x,0}}]}},
     {2,{1,[{<<"A">>,{y,0}}]}},
     {3,{1,[{<<"B">>,1},{<<"A">>,{y,0}}]}},
     {4,{1,[{<<"A">>,{y,0}}]}},
     {5,{1,[{<<"C">>,[1,2,3]},{<<"A">>,{y,0}}]}},
     {6,{1,[{<<"A">>,{y,0}}]}}]
  • Loading branch information
bjorng committed Sep 6, 2024
1 parent 007eb9e commit f45a14c
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 6 deletions.
8 changes: 5 additions & 3 deletions erts/emulator/beam/atom.names
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -486,6 +488,7 @@ atom new_processes
atom new_ports
atom new_uniq
atom newline
atom nifs
atom no
atom nomatch
atom none
Expand Down Expand Up @@ -763,10 +766,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
97 changes: 97 additions & 0 deletions erts/emulator/beam/beam_bif_load.c
Original file line number Diff line number Diff line change
Expand Up @@ -1251,6 +1251,103 @@ 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 BeamDebugTab* debug;
Uint 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);
}

debug = hdr->debug;
if (debug == NULL) {
return am_none;
}

alloc_size = 0;

for (i = 1; 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];
Sint32 frame_size = items->frame_size;
Uint num_vars = items->num_vars;
Eterm *tp = items->first + 2 * num_vars - 2;
Eterm frame_size_term;
Eterm var_list = NIL;
Eterm tmp;

if (frame_size < 0) {
frame_size_term = am_none;
} else {
frame_size_term = make_small(frame_size);
}

while (num_vars-- != 0) {
Eterm val;

if (_is_loader_x_reg(tp[1])) {
Uint xreg = loader_x_reg_index(tp[1]);
val = TUPLE2(hp, am_x, make_small(xreg));
hp += 3;
} else if (_is_loader_y_reg(tp[1])) {
Uint yreg = loader_y_reg_index(tp[1]);
val = TUPLE2(hp, am_y, make_small(yreg));
hp += 3;
} else {
val = tp[1];
}
tmp = TUPLE2(hp, tp[0], val);
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(i), 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...
*
Expand Down
5 changes: 5 additions & 0 deletions erts/emulator/beam/bif.tab
Original file line number Diff line number Diff line change
Expand Up @@ -805,3 +805,8 @@ bif erts_internal:trace_pattern/4
bif erts_internal:trace_info/3
bif erts_trace_cleaner:check/0
bif erts_trace_cleaner:send_trace_clean_signal/1

#
# New in 28.
#
bif code:get_debug_info/1
49 changes: 47 additions & 2 deletions lib/compiler/test/beam_debug_info_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
-module(beam_debug_info_SUITE).
-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]).
Expand Down Expand Up @@ -49,6 +51,8 @@ end_per_group(_GroupName, Config) ->
Config.

smoke(_Config) ->
{ok, Peer, Node} = ?CT_PEER(#{}),

TestBeams0 = get_unique_beam_files(),
TestBeams = compiler_beams() ++ TestBeams0,

Expand All @@ -62,12 +66,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]),
Expand All @@ -81,7 +92,20 @@ do_smoke(Beam) ->
[beam_debug_info,dexp,binary,report_errors]),
SrcVars = source_variables(Abstr),
IndexToFunctionMap = abstr_executable_lines(Abstr),

%% Retrieve the debug information in two different ways.
DebugInfo = get_debug_info(Mod, Code),
DebugInfoBif = load_get_debug_info(Node, Mod, Code),

if
DebugInfo =:= DebugInfoBif ->
ok;
true ->
io:format("~p\n", [DebugInfo]),
io:format("~p\n", [DebugInfoBif]),
error(inconsistent_debug_info)
end,

{DbgVars,DbgLiterals} = debug_info_vars(DebugInfo, IndexToFunctionMap),

%% The debug information must only contain variables that are
Expand Down Expand Up @@ -187,6 +211,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
Expand Down
10 changes: 9 additions & 1 deletion lib/kernel/src/code.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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"}).
Expand Down Expand Up @@ -2321,3 +2322,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).

0 comments on commit f45a14c

Please sign in to comment.