Skip to content

Commit

Permalink
FATE Debugger (#4079)
Browse files Browse the repository at this point in the history
* Upgrade the version of aebytecode

* Add dbgloc fate op

* Add breakpoints to engine state

* Return break from step function on dbgloc op

* Fix typo

* Add skip_instructions

* Add dbg_def and dbg_undef

* Change breakpoints from set to list

* Rename dbgloc to dbg_loc

* Add specs for variables registers funs

* Remove column from DBG_LOC

* Delete var using reg

* Enable step and next

* Fix for next command + new finish command

* Refactor next and finish

* Add dbg_call_stack

* Ignore DBG_LOC when debugger is disabled

* Collect debug info in a single field in engine state

* Fix indention

* Remove breakpoint_stop debug field

* No instruction skip when debugger is disabled

* Resume debugger in a separate fun

* Push and pop debug call stack in a separate fun

* Move new_dbg to different export list

* Use different execute with DEBUG macro

* Enable debug ops only with DEBUG macro

* Rename DEBUG macro to DEBUG_INFO

* Create a macro for STEP

* Add instruction counter to debug_info

* Reset current op on end of block

* Make debug resume aware of stack size

* Fix bug in next debug resume command

* Specify types of keys and values of vars_registers

* Add DBG_CONTRACT and contract names functions

* Rename step, next and finish

* Move debugger related functions to aefa_debug

* Upgrade aebytecode dep

* Upgrade aebytecode dep

* Upgrade deps

* Upgrade the version of sophia used

* Fix the type of the debugger status

* Fix indention

* Fix error with old tests

* Add fate debugger tests

* Use ceres with debugger compiler version

* Refactor the tests

* Initialize debug_info when DEBUG_INFO is on

* Upgrade aebytecode version

* Upgrade sophia

* Upgrade sophia

* Fix debug_info type

* Upgrade sophia

* Fix dialyzer warnings

* Update the code generator

* Refactor code gen dispatch

* Upgrade aebytecode and aesophia

* Do not change dbginfo if bb ends with abort

* Upgrade sophia

* Upgrade aesophia

* Upgrade aesophia to fix a bug

* Add bytes functions to disable dialyzer warnings

* Upgrade aesophia to include ceres changes

* Upgrade sophia to fix event test fail

* Upgrade aesophia
  • Loading branch information
ghallak committed Jun 29, 2023
1 parent 8888bf2 commit 13edec3
Show file tree
Hide file tree
Showing 9 changed files with 640 additions and 34 deletions.
42 changes: 34 additions & 8 deletions apps/aefate/priv/aefa_gen_dispatch
Original file line number Diff line number Diff line change
Expand Up @@ -28,30 +28,56 @@ maybe_show_help(true) ->
maybe_show_help(false) ->
ok.

-define(IS_DEBUG_OP(Op),
(Op =:= dbg_def orelse
Op =:= dbg_undef orelse
Op =:= dbg_loc orelse
Op =:= dbg_contract)).

do_main(Filename) ->
{ok, File} = file:open(Filename, [write]),
Ops = aeb_fate_generate_ops:get_ops(),
Instructions = lists:flatten([gen_eval(Op)++"\n" || Op <- Ops]),
Instructions = lists:flatten([gen_eval(Op, eval) || Op <- Ops]),
DbgInstructions = lists:flatten([gen_eval(Op, eval_dbg) || Op <- Ops]),
io:format(File,
"%%\n%% This file is generated. Any modifications will be overwritten.\n%%\n"
"-module(aefa_fate_eval).\n\n"
"-export([eval/2]).\n\n"
"~s"
"eval(Op, _EngineState) ->\n"
" throw({error, unknown_op, Op}).\n\n"
"-ifdef(DEBUG_INFO).\n"
"~s"
"eval_dbg(Op, _EngineState) ->\n"
" throw({error, unknown_op, Op}).\n"
"-else.\n"
"eval_dbg(Op, _EngineState) ->\n"
" throw({error, unknown_op, Op}).\n"
, [Instructions]),
"-endif.\n"
, [Instructions, DbgInstructions]),
io:format(File, "\n", []),
file:close(File).

gen_eval(#{ constructor := Constructor, gas := Gas } = Op) ->
gen_eval(#{ constructor := Constructor, gas := Gas } = Op, EvalKind) ->
check_descending(Constructor, Gas),
Cmd = io_lib:format("aefa_fate_op:~w(~saefa_engine_state:spend_gas(~w, EngineState))",
[Constructor, gen_cargs(Op), Gas]),
Body = [gen_allowed_offchain(Op),
gen_in_auth(Op, gen_next_cmd(Op, Cmd))
],
io_lib:format("eval(~s, EngineState) ->\n~s;\n",
[gen_op_w_args(Op), Body]).
Body =
case Constructor of
C when ?IS_DEBUG_OP(C) andalso EvalKind =:= eval ->
io_lib:format(" eval_dbg(~s, EngineState)", [gen_op_w_args(Op)]);
_ ->
[ gen_allowed_offchain(Op)
, gen_in_auth(Op, gen_next_cmd(Op, Cmd)) ]
end,
PrintFun = EvalKind =:= eval orelse ?IS_DEBUG_OP(Constructor),
case PrintFun of
true ->
io_lib:format("~s(~s, EngineState) ->\n~s;\n\n",
[atom_to_list(EvalKind), gen_op_w_args(Op), Body]);
false ->
""
end.

gen_next_cmd(#{ end_bb := true }, Cmd) -> Cmd;
gen_next_cmd(#{ end_bb := false }, Cmd) -> io_lib:format("{next, ~s}", [Cmd]).
Expand Down
152 changes: 152 additions & 0 deletions apps/aefate/src/aefa_debug.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
-module(aefa_debug).

%% Debug info getters
-export([ breakpoints/1
, current_instruction/1
, debugger_status/1
, debugger_location/1
, call_stack/1
]).

%% Debug info setters
-export([ set_breakpoints/2
, set_debugger_status/2
, set_debugger_location/2
]).

%% Debug info functions
-export([ new/0
, add_variable_register/3
, del_variable_register/3
, get_variable_register/2
, inc_current_instruction/1
, reset_current_instruction/1
, debugger_resume/2
, contract_name/2
, set_contract_name/3
, push_call_stack/1
, pop_call_stack/1
]).

-export_type([info/0]).

-type debugger_status() :: break
| continue
| stepin
| {stepover, any()} %% TODO: better type here
| {stepout, any()}. %% TODO: better type here

-type debugger_location() :: none | {string(), integer()}.
-type pubkey() :: <<_:256>>.

-record(dbginf, { status = continue :: debugger_status()
, location = none :: debugger_location()
, breakpoints = [] :: list()
, current_instruction = 0 :: integer()
, vars_registers = #{} :: #{string() => list(tuple())}
, call_stack = [] :: [{string(), pos_integer()}]
, contract_names = #{} :: #{pubkey() => string()}
}).

-opaque info() :: #dbginf{}.


-spec new() -> info().
new() ->
#dbginf{}.


-spec push_call_stack(info()) -> info().
push_call_stack(Info = #dbginf{location = Loc, call_stack = Stack}) ->
Info#dbginf{call_stack = [Loc | Stack]}.

-spec pop_call_stack(info()) -> info().
pop_call_stack(Info = #dbginf{call_stack = []}) ->
Info;
pop_call_stack(Info = #dbginf{call_stack = [_ | Rest]}) ->
Info#dbginf{call_stack = Rest}.


-spec breakpoints(info()) -> list().
breakpoints(#dbginf{breakpoints = Breakpoints}) ->
Breakpoints.

-spec set_breakpoints(list(), info()) -> info().
set_breakpoints(BPs, Info) ->
Info#dbginf{breakpoints = BPs}.


-spec current_instruction(info()) -> integer().
current_instruction(#dbginf{current_instruction = Current}) ->
Current.

-spec inc_current_instruction(info()) -> info().
inc_current_instruction(Info = #dbginf{current_instruction = Current}) ->
Info#dbginf{current_instruction = Current + 1}.

-spec reset_current_instruction(info()) -> info().
reset_current_instruction(Info) ->
Info#dbginf{current_instruction = 0}.


-spec debugger_status(info()) -> debugger_status().
debugger_status(#dbginf{status = Status}) ->
Status.

-spec set_debugger_status(debugger_status(), info()) -> info().
set_debugger_status(Status, Info) ->
Info#dbginf{status = Status}.


-spec debugger_location(info()) -> debugger_location().
debugger_location(#dbginf{location = Location}) ->
Location.

-spec set_debugger_location(debugger_location(), info()) -> info().
set_debugger_location(Location, Info) ->
Info#dbginf{location = Location}.


-spec call_stack(info()) -> [{string(), pos_integer()}].
call_stack(#dbginf{call_stack = Stack}) ->
Stack.


-spec add_variable_register(string(), tuple(), info()) -> info().
add_variable_register(Var, Reg, Info = #dbginf{vars_registers = VarsRegs}) ->
Old = maps:get(Var, VarsRegs, []),
New = [Reg | Old],
Info#dbginf{vars_registers = VarsRegs#{Var => New}}.

-spec del_variable_register(string(), tuple(), info()) -> info().
del_variable_register(Var, Reg, Info = #dbginf{vars_registers = VarsRegs}) ->
New = lists:delete(Reg, maps:get(Var, VarsRegs, [])),
Info#dbginf{vars_registers = VarsRegs#{Var => New}}.

-spec get_variable_register(string(), info()) -> tuple().
get_variable_register(Var, #dbginf{vars_registers = VarsRegs}) ->
case maps:get(Var, VarsRegs, [undefined]) of
[] -> undefined;
[Reg | _] -> Reg
end.


-spec debugger_resume([tuple()], info()) -> info().
debugger_resume(CallStack, Info = #dbginf{status = {Step, Stack}})
when Step == stepover andalso length(CallStack) =< length(Stack);
Step == stepout andalso length(CallStack) < length(Stack) ->
Info#dbginf{status = break};
debugger_resume(_CallStack, Info = #dbginf{status = stepin}) ->
Info#dbginf{status = break};
debugger_resume(_CallStack, Info) ->
Info.


-spec contract_name(pubkey(), info()) -> string() | pubkey().
contract_name(ContractPK, #dbginf{contract_names = ContractNames}) ->
maps:get(ContractPK, ContractNames, ContractPK).

-spec set_contract_name(pubkey(), string(), info()) -> info().
set_contract_name(ContractPK, ContractName, Info = #dbginf{contract_names = OldNames}) ->
NewNames = OldNames#{ContractPK => ContractName},
Info#dbginf{contract_names = NewNames}.
41 changes: 39 additions & 2 deletions apps/aefate/src/aefa_engine_state.erl
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@
]).
-endif.

-ifdef(DEBUG_INFO).
-export([ debug_info/1
, set_debug_info/2
]).
-endif.

-define(FIX_CONTRACT_CHECK_WINDOW_LOWER_LIMIT, 237000).
-define(FIX_CONTRACT_CHECK_WINDOW_UPPER_LIMIT, 245000).

Expand All @@ -103,6 +109,12 @@
-type void_or_fate() :: ?FATE_VOID | aeb_fate_data:fate_type().
-type pubkey() :: <<_:256>>.

-ifdef(DEBUG_INFO).
-type debug_info() :: aefa_debug:info().
-else.
-type debug_info() :: disabled.
-endif.

-record(es, { accumulator :: void_or_fate()
, accumulator_stack :: [aeb_fate_data:fate_type()]
, bbs :: map()
Expand All @@ -126,6 +138,7 @@
, stores :: aefa_stores:store()
, trace :: list()
, vm_version :: non_neg_integer()
, debug_info :: debug_info()
}).

-opaque state() :: #es{}.
Expand Down Expand Up @@ -158,6 +171,7 @@ new(Gas, Value, Spec, Stores, APIState, CodeCache, VMVersion) ->
, stores = Stores
, trace = []
, vm_version = VMVersion
, debug_info = disabled
}.

aefa_stores(#es{ chain_api = APIState }) ->
Expand Down Expand Up @@ -269,6 +283,14 @@ push_arguments([], Acc, Stack, ES) ->
push_arguments([A|As], Acc, Stack, ES) ->
push_arguments(As, A, [Acc | Stack], ES).

-ifdef(DEBUG_INFO).
-define(PUSH_DEBUG_CALL_STACK(Info), aefa_debug:push_call_stack(Info)).
-define(POP_DEBUG_CALL_STACK(Info), aefa_debug:pop_call_stack(Info)).
-else.
-define(PUSH_DEBUG_CALL_STACK(Info), Info).
-define(POP_DEBUG_CALL_STACK(Info), Info).
-endif.

-spec push_call_stack(state()) -> state().
push_call_stack(#es{ current_bb = BB
, current_function = Function
Expand All @@ -280,11 +302,13 @@ push_call_stack(#es{ current_bb = BB
, call_stack = Stack
, call_value = Value
, caller = Caller
, memory = Mem} = ES) ->
, memory = Mem
, debug_info = DbgInfo} = ES) ->
AccS1 = [Acc || Acc /= void] ++ AccS,
ES#es{accumulator = void,
accumulator_stack = [],
call_stack = [{Caller, Contract, VmVersion, Function, TVars, BB + 1, AccS1, Mem, Value}|Stack]}.
call_stack = [{Caller, Contract, VmVersion, Function, TVars, BB + 1, AccS1, Mem, Value}|Stack],
debug_info = ?PUSH_DEBUG_CALL_STACK(DbgInfo)}.

%% TODO: Make better types for all these things
-spec pop_call_stack(state()) ->
Expand All @@ -297,6 +321,7 @@ push_call_stack(#es{ current_bb = BB
_, map(), non_neg_integer(), state()}.
pop_call_stack(#es{accumulator = ReturnValue,
call_stack = Stack,
debug_info = DbgInfo,
current_contract = Current} = ES) ->
case Stack of
[] -> {empty, ES};
Expand All @@ -316,6 +341,7 @@ pop_call_stack(#es{accumulator = ReturnValue,
, accumulator_stack = AccS
, memory = Mem
, call_stack = Rest
, debug_info = ?POP_DEBUG_CALL_STACK(DbgInfo)
}};
[{Caller, Pubkey, VmVersion, Function, TVars, BB, AccS, Mem, Value}| Rest] ->
Seen = pop_seen_contracts(Pubkey, ES),
Expand All @@ -336,6 +362,7 @@ pop_call_stack(#es{accumulator = ReturnValue,
, seen_contracts = Seen
, current_contract = NewCurrent
, vm_version = VmVersion
, debug_info = ?POP_DEBUG_CALL_STACK(DbgInfo)
}}
end.

Expand Down Expand Up @@ -863,3 +890,13 @@ vm_version(#es{vm_version = X}) ->
consensus_version(#es{chain_api = Api}) ->
TxEnv = aefa_chain_api:tx_env(Api),
aetx_env:consensus_version(TxEnv).

%%%------------------

-ifdef(DEBUG_INFO).
debug_info(#es{debug_info = Info}) ->
Info.

set_debug_info(Info, ES) ->
ES#es{debug_info = Info}.
-endif.

0 comments on commit 13edec3

Please sign in to comment.