feat(cli_debugger): add commands for traversing stack frames

This commit is contained in:
Leonardo de Moura 2016-11-16 12:37:18 -08:00
parent b8e904094c
commit 91c8ff746f
5 changed files with 194 additions and 76 deletions

View file

@ -58,23 +58,25 @@ meta instance : monad vm_core :=
@[reducible] meta def vm (A : Type) : Type := optionT.{1 1} vm_core A
namespace vm
meta constant get_env : vm environment
meta constant get_decl : name → vm vm_decl
meta constant get_options : vm options
meta constant stack_size : vm nat
meta constant stack_obj : nat → vm vm_obj
meta constant stack_obj_info : nat → vm (name × option expr)
meta constant format_stack_obj : nat → vm format
meta constant call_stack_size : vm nat
meta constant call_stack_fn : nat → vm name
meta constant curr_fn : vm name
meta constant bp : vm nat
meta constant pc : vm nat
meta constant obj_to_string : vm_obj → vm string
meta constant put_str : string → vm unit
meta constant get_line : vm string
meta constant eof : vm bool
meta constant get_attribute : name → vm (list name)
meta constant get_env : vm environment
meta constant get_decl : name → vm vm_decl
meta constant get_options : vm options
meta constant stack_size : vm nat
meta constant stack_obj : nat → vm vm_obj
meta constant stack_obj_info : nat → vm (name × option expr)
meta constant pp_stack_obj : nat → vm format
meta constant pp_expr : expr → vm format
meta constant call_stack_size : vm nat
meta constant call_stack_fn : nat → vm name
meta constant call_stack_var_range : nat → vm (nat × nat)
meta constant curr_fn : vm name
meta constant bp : vm nat
meta constant pc : vm nat
meta constant obj_to_string : vm_obj → vm string
meta constant put_str : string → vm unit
meta constant get_line : vm string
meta constant eof : vm bool
meta constant get_attribute : name → vm (list name)
meta def trace {A : Type} [has_to_format A] (a : A) : vm unit :=
do fmt ← return $ to_fmt a,

View file

@ -302,7 +302,7 @@ static format default_format(vm_state const & vm, unsigned idx) {
return format(out.str());
}
vm_obj vm_format_stack_obj(vm_obj const & i, vm_obj const & /*s*/) {
vm_obj vm_pp_stack_obj(vm_obj const & i, vm_obj const & /*s*/) {
auto const & vm = get_vm_state_being_debugged();
unsigned idx = force_to_unsigned(i, std::numeric_limits<unsigned>::max());
if (idx >= vm.stack_size()) return mk_vm_failure();
@ -339,6 +339,30 @@ vm_obj vm_call_stack_fn(vm_obj const & i, vm_obj const & /*s*/) {
return mk_vm_success(to_obj(vm.call_stack_fn(idx)));
}
vm_obj vm_call_stack_var_range(vm_obj const & i, vm_obj const & /*s*/) {
auto const & vm = get_vm_state_being_debugged();
unsigned idx = force_to_unsigned(i, std::numeric_limits<unsigned>::max());
unsigned csz = vm.call_stack_size();
if (idx >= csz) {
return mk_vm_failure();
} else {
lean_assert(csz > 0);
unsigned start, end;
if (idx == csz - 1) {
start = vm.bp();
end = vm.stack_size();
} else if (idx == csz - 2) {
start = vm.call_stack_bp(csz - 1);
end = vm.bp();
} else {
lean_assert(idx < csz - 2);
start = vm.call_stack_bp(idx + 1);
end = vm.call_stack_bp(idx + 2);
}
return mk_vm_success(mk_vm_pair(mk_vm_nat(start), mk_vm_nat(end)));
}
}
vm_obj vm_bp(vm_obj const & /*s*/) {
return mk_vm_success(mk_vm_nat(get_vm_state_being_debugged().bp()));
}
@ -396,47 +420,63 @@ vm_obj vm_get_attribute(vm_obj const & vm_n, vm_obj const &) {
}
}
vm_obj vm_pp_expr(vm_obj const & e, vm_obj const &) {
auto const & vm = get_vm_state_being_debugged();
formatter_factory const & fmtf = get_global_ios().get_formatter_factory();
type_context ctx(vm.env());
formatter fmt = fmtf(vm.env(), vm.get_options(), ctx);
try {
return mk_vm_success(to_obj(fmt(to_expr(e))));
} catch (exception &) {
std::ostringstream out;
out << to_expr(e);
return mk_vm_success(to_obj(format(out.str())));
}
}
void initialize_vm_monitor() {
DECLARE_VM_BUILTIN(name({"vm_monitor", "register"}), _vm_monitor_register);
DECLARE_VM_BUILTIN(name({"vm_core", "map"}), vm_core_map);
DECLARE_VM_BUILTIN(name({"vm_core", "ret"}), vm_core_ret);
DECLARE_VM_BUILTIN(name({"vm_core", "bind"}), vm_core_bind);
DECLARE_VM_BUILTIN(name({"vm_obj", "kind"}), _vm_obj_kind);
DECLARE_VM_BUILTIN(name({"vm_obj", "cidx"}), vm_obj_cidx);
DECLARE_VM_BUILTIN(name({"vm_obj", "fn_idx"}), vm_obj_fn_idx);
DECLARE_VM_BUILTIN(name({"vm_obj", "fields"}), vm_obj_fields);
DECLARE_VM_BUILTIN(name({"vm_obj", "to_nat"}), vm_obj_to_nat);
DECLARE_VM_BUILTIN(name({"vm_obj", "to_name"}), vm_obj_to_name);
DECLARE_VM_BUILTIN(name({"vm_obj", "to_level"}), vm_obj_to_level);
DECLARE_VM_BUILTIN(name({"vm_obj", "to_expr"}), vm_obj_to_expr);
DECLARE_VM_BUILTIN(name({"vm_obj", "to_declaration"}), vm_obj_to_declaration);
DECLARE_VM_BUILTIN(name({"vm_obj", "to_environment"}), vm_obj_to_environment);
DECLARE_VM_BUILTIN(name({"vm_obj", "to_tactic_state"}), vm_obj_to_tactic_state);
DECLARE_VM_BUILTIN(name({"vm_obj", "to_format"}), vm_obj_to_format);
DECLARE_VM_BUILTIN(name({"vm_decl", "kind"}), _vm_decl_kind);
DECLARE_VM_BUILTIN(name({"vm_decl", "to_name"}), vm_decl_to_name);
DECLARE_VM_BUILTIN(name({"vm_decl", "idx"}), vm_decl_idx);
DECLARE_VM_BUILTIN(name({"vm_decl", "arity"}), vm_decl_arity);
DECLARE_VM_BUILTIN(name({"vm_decl", "pos"}), vm_decl_pos);
DECLARE_VM_BUILTIN(name({"vm_decl", "olean"}), vm_decl_olean);
DECLARE_VM_BUILTIN(name({"vm_decl", "args_info"}), vm_decl_args_info);
DECLARE_VM_BUILTIN(name({"vm", "get_env"}), vm_get_env);
DECLARE_VM_BUILTIN(name({"vm", "get_decl"}), vm_get_decl);
DECLARE_VM_BUILTIN(name({"vm", "stack_size"}), vm_stack_size);
DECLARE_VM_BUILTIN(name({"vm", "stack_obj"}), vm_stack_obj);
DECLARE_VM_BUILTIN(name({"vm", "stack_obj_info"}), vm_stack_obj_info);
DECLARE_VM_BUILTIN(name({"vm", "call_stack_size"}), vm_call_stack_size);
DECLARE_VM_BUILTIN(name({"vm", "call_stack_fn"}), vm_call_stack_fn);
DECLARE_VM_BUILTIN(name({"vm", "bp"}), vm_bp);
DECLARE_VM_BUILTIN(name({"vm", "pc"}), vm_pc);
DECLARE_VM_BUILTIN(name({"vm", "curr_fn"}), vm_curr_fn);
DECLARE_VM_BUILTIN(name({"vm", "get_options"}), vm_get_options);
DECLARE_VM_BUILTIN(name({"vm", "obj_to_string"}), vm_obj_to_string);
DECLARE_VM_BUILTIN(name({"vm", "format_stack_obj"}), vm_format_stack_obj);
DECLARE_VM_BUILTIN(name({"vm", "put_str"}), vm_put_str);
DECLARE_VM_BUILTIN(name({"vm", "get_line"}), vm_get_line);
DECLARE_VM_BUILTIN(name({"vm", "eof"}), vm_eof);
DECLARE_VM_BUILTIN(name({"vm", "get_attribute"}), vm_get_attribute);
DECLARE_VM_BUILTIN(name({"vm_monitor", "register"}), _vm_monitor_register);
DECLARE_VM_BUILTIN(name({"vm_core", "map"}), vm_core_map);
DECLARE_VM_BUILTIN(name({"vm_core", "ret"}), vm_core_ret);
DECLARE_VM_BUILTIN(name({"vm_core", "bind"}), vm_core_bind);
DECLARE_VM_BUILTIN(name({"vm_obj", "kind"}), _vm_obj_kind);
DECLARE_VM_BUILTIN(name({"vm_obj", "cidx"}), vm_obj_cidx);
DECLARE_VM_BUILTIN(name({"vm_obj", "fn_idx"}), vm_obj_fn_idx);
DECLARE_VM_BUILTIN(name({"vm_obj", "fields"}), vm_obj_fields);
DECLARE_VM_BUILTIN(name({"vm_obj", "to_nat"}), vm_obj_to_nat);
DECLARE_VM_BUILTIN(name({"vm_obj", "to_name"}), vm_obj_to_name);
DECLARE_VM_BUILTIN(name({"vm_obj", "to_level"}), vm_obj_to_level);
DECLARE_VM_BUILTIN(name({"vm_obj", "to_expr"}), vm_obj_to_expr);
DECLARE_VM_BUILTIN(name({"vm_obj", "to_declaration"}), vm_obj_to_declaration);
DECLARE_VM_BUILTIN(name({"vm_obj", "to_environment"}), vm_obj_to_environment);
DECLARE_VM_BUILTIN(name({"vm_obj", "to_tactic_state"}), vm_obj_to_tactic_state);
DECLARE_VM_BUILTIN(name({"vm_obj", "to_format"}), vm_obj_to_format);
DECLARE_VM_BUILTIN(name({"vm_decl", "kind"}), _vm_decl_kind);
DECLARE_VM_BUILTIN(name({"vm_decl", "to_name"}), vm_decl_to_name);
DECLARE_VM_BUILTIN(name({"vm_decl", "idx"}), vm_decl_idx);
DECLARE_VM_BUILTIN(name({"vm_decl", "arity"}), vm_decl_arity);
DECLARE_VM_BUILTIN(name({"vm_decl", "pos"}), vm_decl_pos);
DECLARE_VM_BUILTIN(name({"vm_decl", "olean"}), vm_decl_olean);
DECLARE_VM_BUILTIN(name({"vm_decl", "args_info"}), vm_decl_args_info);
DECLARE_VM_BUILTIN(name({"vm", "get_env"}), vm_get_env);
DECLARE_VM_BUILTIN(name({"vm", "get_decl"}), vm_get_decl);
DECLARE_VM_BUILTIN(name({"vm", "stack_size"}), vm_stack_size);
DECLARE_VM_BUILTIN(name({"vm", "stack_obj"}), vm_stack_obj);
DECLARE_VM_BUILTIN(name({"vm", "stack_obj_info"}), vm_stack_obj_info);
DECLARE_VM_BUILTIN(name({"vm", "call_stack_size"}), vm_call_stack_size);
DECLARE_VM_BUILTIN(name({"vm", "call_stack_fn"}), vm_call_stack_fn);
DECLARE_VM_BUILTIN(name({"vm", "call_stack_var_range"}), vm_call_stack_var_range);
DECLARE_VM_BUILTIN(name({"vm", "bp"}), vm_bp);
DECLARE_VM_BUILTIN(name({"vm", "pc"}), vm_pc);
DECLARE_VM_BUILTIN(name({"vm", "curr_fn"}), vm_curr_fn);
DECLARE_VM_BUILTIN(name({"vm", "get_options"}), vm_get_options);
DECLARE_VM_BUILTIN(name({"vm", "obj_to_string"}), vm_obj_to_string);
DECLARE_VM_BUILTIN(name({"vm", "pp_stack_obj"}), vm_pp_stack_obj);
DECLARE_VM_BUILTIN(name({"vm", "pp_expr"}), vm_pp_expr);
DECLARE_VM_BUILTIN(name({"vm", "put_str"}), vm_put_str);
DECLARE_VM_BUILTIN(name({"vm", "get_line"}), vm_get_line);
DECLARE_VM_BUILTIN(name({"vm", "eof"}), vm_eof);
DECLARE_VM_BUILTIN(name({"vm", "get_attribute"}), vm_get_attribute);
}
void finalize_vm_monitor() {

View file

@ -2655,6 +2655,9 @@ void initialize_vm() {
#endif
g_debugger = new name{"debugger"};
register_bool_option(*g_debugger, false, "(debugger) debug code using VM monitors");
/* TODO(Leo): move to .lean after we add primitives for creating new options on .lean files */
register_bool_option(name({"debugger", "autorun"}), false,
"(debugger) skip debugger startup messages and initial prompt");
}
void finalize_vm() {

View file

@ -624,6 +624,11 @@ public:
return m_decl_map.find(m_call_stack[idx].m_curr_fn_idx)->get_name();
}
unsigned call_stack_bp(unsigned idx) const {
lean_assert(idx < m_call_stack.size());
return m_call_stack[idx].m_bp;
}
unsigned bp() const { return m_bp; }
unsigned pc() const { return m_pc; }

View file

@ -49,7 +49,7 @@ do {
return n
}
<|>
return "<curr file>"
return "[current file]"
meta def pos_info (fn : name) : vm string :=
do {
@ -61,11 +61,14 @@ do {
<|>
return "<position not available>"
meta def display_fn (header : string) (fn : name) (frame : nat) : vm unit :=
do pos ← pos_info fn,
vm.put_str ("[" ++ frame^.to_string ++ "] " ++ header ++ " " ++ fn^.to_string ++ " at " ++ pos ++ "\n")
meta def display_curr_fn (header : string) : vm unit :=
do fn ← vm.curr_fn,
pos ← pos_info fn,
sz ← vm.call_stack_size,
vm.put_str ("[" ++ sz^.to_string ++ "] " ++ header ++ " " ++ fn^.to_string ++ " at " ++ pos ++ "\n")
display_fn header fn (sz-1)
meta def display_help : vm unit :=
do
@ -73,6 +76,10 @@ do
vm.put_str "help - display this message\n",
vm.put_str "run - continue execution\n",
vm.put_str "step - execute until another function in on the top of the stack\n",
vm.put_str "stack trace\n",
vm.put_str " up - move up in the stack trace\n",
vm.put_str " down - move down in the stack trace\n",
vm.put_str " vars - display variables in the current stack frame\n",
vm.put_str "breakpoints\n",
vm.put_str " break fn - add breakpoint for fn\n",
vm.put_str " rbreak fn - remove breakpoint\n",
@ -118,8 +125,58 @@ meta def show_breakpoints : list name → vm unit
vm.put_str "\n",
show_breakpoints fns
meta def cmd_loop : state → list string → vm state
| s default_cmd := do
meta def show_frame (frame : nat) : vm unit :=
do sz ← vm.call_stack_size,
fn ← if frame >= sz then vm.curr_fn else vm.call_stack_fn frame,
display_fn "frame" fn frame
meta def up (frame : nat) : vm nat :=
if frame = 0 then return 0
else show_frame (frame - 1) >> return (frame - 1)
meta def down (frame : nat) : vm nat :=
do sz ← vm.call_stack_size,
if frame >= sz - 1 then return frame
else show_frame (frame + 1) >> return (frame + 1)
meta def type_to_string : option expr → nat → vm string
| none i := do
o ← vm.stack_obj i,
match o^.kind with
| vm_obj_kind.simple := return "[tagged value]"
| vm_obj_kind.constructor := return "[constructor]"
| vm_obj_kind.closure := return "[closure]"
| vm_obj_kind.mpz := return "[big num]"
| vm_obj_kind.name := return "name"
| vm_obj_kind.level := return "level"
| vm_obj_kind.expr := return "expr"
| vm_obj_kind.declaration := return "declaration"
| vm_obj_kind.environment := return "environment"
| vm_obj_kind.tactic_state := return "tactic_state"
| vm_obj_kind.format := return "format"
| vm_obj_kind.options := return "options"
| vm_obj_kind.other := return "[other]"
end
| (some type) i := do
fmt ← vm.pp_expr type,
opts ← vm.get_options,
return (fmt^.to_string opts)
meta def show_vars_core : nat → nat → nat → vm unit
| c i e :=
if i = e then return ()
else do
(n, type) ← vm.stack_obj_info i,
type_str ← type_to_string type i,
vm.put_str $ "#" ++ c^.to_string ++ " " ++ n^.to_string ++ " : " ++ type_str ++ "\n",
show_vars_core (c+1) (i+1) e
meta def show_vars (frame : nat) : vm unit :=
do (s, e) ← vm.call_stack_var_range frame,
show_vars_core 0 s e
meta def cmd_loop_core : state → nat → list string → vm state
| s frame default_cmd := do
is_eof ← vm.eof,
if is_eof then do
vm.put_str "stopping debugger... 'end of file' has been found\n",
@ -130,21 +187,28 @@ meta def cmd_loop : state → list string → vm state
tks ← return $ split l,
tks ← return $ if tks = [] then default_cmd else tks,
match tks with
| [] := cmd_loop s default_cmd
| [] := cmd_loop_core s frame default_cmd
| (cmd::args) :=
if cmd = "help" then display_help >> cmd_loop s default_cmd
if cmd = "help" then display_help >> cmd_loop_core s frame default_cmd
else if cmd = "exit" then return {s with md := mode.done }
else if cmd = "run" cmd = "r" then return {s with md := mode.run }
else if cmd = "step" cmd = "s" then return {s with md := mode.step }
else if cmd = "break" cmd = "b" then do new_s ← add_breakpoint s args, cmd_loop new_s default_cmd
else if cmd = "rbreak" then do new_s ← remove_breakpoint s args, cmd_loop new_s default_cmd
else if cmd = "break" cmd = "b" then do new_s ← add_breakpoint s args, cmd_loop_core new_s frame default_cmd
else if cmd = "rbreak" then do new_s ← remove_breakpoint s args, cmd_loop_core new_s frame default_cmd
else if cmd = "bs" then do
vm.put_str "breakpoints\n",
show_breakpoints s^.fn_bps,
cmd_loop s default_cmd
else do vm.put_str "unknown command, type 'help' for help\n", cmd_loop s default_cmd
cmd_loop_core s frame default_cmd
else if cmd = "up" cmd = "u" then do frame ← up frame, cmd_loop_core s frame ["u"]
else if cmd = "down" cmd = "d" then do frame ← down frame, cmd_loop_core s frame ["d"]
else if cmd = "vars" cmd = "v" then do show_vars frame, cmd_loop_core s frame ["vars"]
else do vm.put_str "unknown command, type 'help' for help\n", cmd_loop_core s frame default_cmd
end
meta def cmd_loop (s : state) (default_cmd : list string) : vm state :=
do csz ← vm.call_stack_size,
cmd_loop_core s (csz - 1) default_cmd
def prune_active_bps_core (csz : nat) : list (nat × name) → list (nat × name)
| [] := []
| ((csz', n)::ls) := if csz < csz' then prune_active_bps_core ls else ((csz',n)::ls)
@ -163,10 +227,13 @@ do opts ← vm.get_options,
else do
bps ← vm.get_attribute `breakpoint,
new_s ← return {s with fn_bps := bps},
vm.put_str "Lean debugger\n",
display_curr_fn "debugging",
vm.put_str "type 'help' for help\n",
cmd_loop new_s []
if opts^.get_bool `debugger.autorun ff then
return {new_s with md := mode.run}
else do
vm.put_str "Lean debugger\n",
display_curr_fn "debugging",
vm.put_str "type 'help' for help\n",
cmd_loop new_s []
meta def step_transition (s : state) : vm state :=
do
@ -219,12 +286,13 @@ run_command vm_monitor.register `debugger.monitor
run_command attribute.register `debugger.attr
set_option debugger true
set_option debugger.autorun true
def g (a : nat) := a + 1
def g (c : nat) := c + 1
def h (a : nat) := g a + 1
def h (a : nat) (b : nat) := g b + a
def s (a : nat) := h a + h a
def s (a : nat) := h 2 a + h 3 a
local attribute [breakpoint] h