Current section
Files
Jump to
Current section
Files
lib/int.ex
defmodule Int do
@moduledoc """
Implements functions from the erlang debugger interface module.
Since elixir has poor access to the erlang debugger, this provides
an easy interface to work with the debugger from the repl.
Erlang Documentation is provided in the comments.
Unecessary function calls are passed through to the erlang :int module.
==Erlang Interpreter================================================
int
---
Interface module.
i
-
Interface module to int, retained for backwards compatibility only.
dbg_debugged
------------
Contains the message loops for a debugged process and is the main
entry point from the breakpoint handler in the error_handler module
(via the int module).
When a process is debugged, most code is executed in another
process, called the meta process. When the meta process is
interpreting code, the process being debugged just waits in a
receive loop in dbg_debugged. However the debugged process itself
calls any BIFs that must execute in the correct process (such as
link/1 and spawn_link/1), and external code which is not
interpreted.
dbg_icmd, dbg_ieval
-------------------
Code for the meta process.
dbg_iserver
-----------
Interpreter main process, keeping and distributing information
about interpreted modules and debugged processes.
dbg_idb
-------
ETS wrapper, allowing transparent access to tables at a remote node.
dbg_iload
---------
Code for interpreting a module.
====================================================================
"""
# %%====================================================================
# %% External exports
# %%====================================================================
#
@doc """
--------------------------------------------------------------------
i(AbsMods) -> {module,Mod} | error | ok
ni(AbsMods) -> {module,Mod} | error | ok
AbsMods = AbsMod | [AbsMod]
AbsMod = atom() | string()
Mod = atom()
Options = term() ignored
--------------------------------------------------------------------
## Examples
iex> Int.i(Int)
{:module, Int}
"""
def i(absMods), do: i2(absMods, :local, :ok)
def i(absMods, _options), do: i2(absMods, :local, :ok)
def ni(absMods), do: i2(absMods, :distributed, :ok)
def ni(absMods, _options), do: i2(absMods, :distributed, :ok)
defp i2([absMod|absMods], dist, acc)
when is_atom(absMod) or is_list(absMod) or is_tuple(absMod) do
res = int_mod(absMod, dist)
case acc do
:error ->
i2(absMods, dist, acc)
_ ->
i2(absMods, dist, res)
end
end
defp i2([], _dist, acc), do: acc
defp i2(absMod, dist, _acc)
when is_atom(absMod) or is_list(absMod) or is_tuple(absMod) do
int_mod(absMod, dist)
end
@doc """
--------------------------------------------------------------------
n(AbsMods) -> ok
nn(AbsMods) -> ok
--------------------------------------------------------------------
## Examples
"""
def n(absMods), do: :int.n(absMods)
def nn(absMods), do: :int.nn(absMods)
defp n2([absMod|absMods], dist) when is_atom(absMod) or is_list(absMod) do
:int.n2(absMod <> absMods, dist)
end
defp n2([absMod], dist) when is_atom(absMod) or is_list(absMod), do: :int.del_mod(absMod, dist)
defp n2([], _dist), do: :int.n2([], _dist)
defp n2(absMod, dist) when is_atom(absMod) or is_list(absMod), do: :int.del_mod(absMod, dist)
@doc """
--------------------------------------------------------------------
interpreted() -> [Mod]
--------------------------------------------------------------------
"""
def interpreted(), do: :int.interpreted()
@doc """
--------------------------------------------------------------------
file(Mod) -> File | {error, not_loaded}
Mod = atom()
File = string()
--------------------------------------------------------------------
"""
def file(mod) when is_atom(mod), do: :int.file(mod)
@doc """
--------------------------------------------------------------------
interpretable(AbsMod) -> true | {error, Reason}
AbsMod = Mod | File
Reason = no_src | no_beam | no_debug_info | badarg | {app, App}
--------------------------------------------------------------------
"""
def interpretable(absMod) do
case check(absMod) do
{:ok, _Res} -> true
error -> error
end
end
@doc """
--------------------------------------------------------------------
auto_attach() -> false | {Flags, Function}
auto_attach(false)
auto_attach(false|Flags, Function)
Flags = Flag | [Flag]
Flag = init | break | exit
Function = {Mod, Func} | {Mod, Func, Args}
Will result in calling:
spawn(Mod, Func, [Dist, Pid, Meta | Args]) (living process) or
spawn(Mod, Func, [Dist, Pid, Reason, Info | Args]) (dead process)
--------------------------------------------------------------------
"""
def auto_attach(), do: :int.auto_attach()
def auto_attach(false), do: :int.auto_attach(false)
def auto_attach([], _function), do: :int.auto_attach([], _function)
def auto_attach(flags, {mod, func}), do: :int.auto_attach(flags, {mod, func})
def auto_attach(flags, {mod, func, args}) when is_atom(mod) and is_atom(func) and is_list(args) do
:int.auto_attach(flags, {mod, func, args})
end
@doc """
--------------------------------------------------------------------
stack_trace() -> Flag
stack_trace(Flag)
Flag = all | true | no_tail | false
--------------------------------------------------------------------
"""
def stack_trace(), do: :int.stack_trace()
def stack_trace(true), do: :int.stack_trace(true)
def stack_trace(flag), do: :int.stack_trace(flag)
@doc """
--------------------------------------------------------------------
break(Mod, Line) -> ok | {error, break_exists}
delete_break(Mod, Line) -> ok
break_in(Mod, Func, Arity) -> ok | {error, function_not_found}
del_break_in(Mod, Function, Arity) -> ok | {error, function_not_found}
no_break()
no_break(Mod)
disable_break(Mod, Line) -> ok
enable_break(Mod, Line) -> ok
action_at_break(Mod, Line, Action) -> ok
test_at_break(Mod, Line, Function) -> ok
get_binding(Var, Bindings) -> {value, Value} | unbound
all_breaks() -> [Break]
all_breaks(Mod) -> [Break]
Mod = atom()
Line = integer()
Func = atom() function name
Arity = integer()
Action = enable | disable | delete
Function = {Mod, Func} must have arity 1 (Bindings)
Var = atom()
Bindings = Value = term()
Break = {Point, Options}
Point = {Mod, Line}
Options = [Status, Action, null, Cond]
Status = active | inactive
Cond = null | Function
--------------------------------------------------------------------
"""
def break(mod, line) when is_atom(mod) and is_integer(line) do
:int.break(mod, line)
end
def delete_break(mod, line) when is_atom(mod) and is_integer(line) do
:int.delete_break(mod, line)
end
def break_in(mod, func, arity) when is_atom(mod) and is_atom(func) and is_integer(arity) do
:int.del_break_in(mod, func,arity)
end
def del_break_in(mod, func, arity) when is_atom(mod) and is_atom(func) and is_integer(arity) do
:int.del_break_in(mod, func, arity)
end
def no_break(), do: :int.no_break()
def no_break(mod) when is_atom(mod), do: :int.no_break(mod)
def disable_break(mod, line) when is_atom(mod) and is_integer(line) do
:int.disable_break(mod, line)
end
def enable_break(mod, line) when is_atom(mod) and is_integer(line) do
:int.enable_break(mod, line)
end
def action_at_break(mod, line, action) when is_atom(mod) and is_integer(line) do
:int.action_at_break(mod, line, action)
end
def test_at_break(mod, line, function) when is_atom(mod) and is_integer(line) do
:int.test_at_break(mod, line, function)
end
def get_binding(var, bs), do: :int.get_binding(var, bs)
def all_breaks(), do: :int.all_breaks()
def all_breaks(mod) when is_atom(mod), do: :int.all_breaks(mod)
@doc """
--------------------------------------------------------------------
snapshot() -> [{Pid, Init, Status, Info}]
Pid = pid()
Init = atom() First interpreted function
Status = idle | running | waiting | break | exit
Info = {} | {Mod, Line} | ExitReason
Mod = atom()
Line = integer()
ExitReason = term()
--------------------------------------------------------------------
"""
def snapshot(), do: :int.snapshot()
@doc """
--------------------------------------------------------------------
clear()
--------------------------------------------------------------------
"""
def clear(), do: :int.clear()
@doc """
--------------------------------------------------------------------
continue(Pid) -> ok | {error, not_interpreted}
continue(X, Y, Z) -> ok | {error, not_interpreted}
--------------------------------------------------------------------
"""
def continue(pid) when is_pid(pid), do: :int.continue(pid)
def continue(x, y, z) when is_integer(x) and is_integer(y) and is_integer(z), do: :int.continue(x, y, z)
# %%====================================================================
# %% External exports only to be used by Debugger
# %%====================================================================
@doc """
--------------------------------------------------------------------
start()
stop()
Functions for starting and stopping dbg_iserver explicitly.
--------------------------------------------------------------------
"""
def start(), do: :int.start()
def stop(), do: :int.stop()
@doc """
--------------------------------------------------------------------
subscribe()
Subscribe to information from dbg_iserver. The process calling this
function will receive the following messages:
{int, {interpret, Mod}}
{int, {no_interpret, Mod}}
{int, {new_process, Pid, Function, Status, Info}}
{int, {new_status, Pid, Status, Info}}
{int, {new_break, {Point, Options}}}
{int, {delete_break, Point}}
{int, {break_options, {Point, Options}}}
{int, no_break}
{int, {no_break, Mod}}
{int, {auto_attach, false|{Flags, Function}}}
{int, {stack_trace, Flag}}
--------------------------------------------------------------------
"""
def subscribe(), do: :int.subscribe()
@doc """
--------------------------------------------------------------------
attach(Pid, Function)
Pid = pid()
Function = {Mod, Func} | {Mod, Func, Args} (see auto_attach/2)
Tell dbg_iserver to attach to Pid using Function. Will result in:
spawn(Mod, Func, [Pid, Status | Args])
--------------------------------------------------------------------
"""
def attach(pid, {mod, func}), do: :int.attach(pid, {mod, func})
def attach(pid, function), do: :int.attach(pid, function)
@doc """
--------------------------------------------------------------------
step(Pid)
next(Pid)
(continue(Pid))
finish(Pid)
--------------------------------------------------------------------
"""
def step(pid), do: :int.step(pid)
def next(pid), do: :int.next(pid)
def finish(pid), do: :int.finish(pid)
# %%====================================================================
# %% External exports only to be used by an attached process
# %%====================================================================
@doc """
--------------------------------------------------------------------
attached(Pid) -> {ok, Meta} | error
Pid = Meta = pid()
Tell dbg_iserver that I have attached to Pid. dbg_iserver informs
the meta process and returns its pid. dbg_iserver may also refuse,
if there already is a process attached to Pid.
--------------------------------------------------------------------
"""
def attached(pid), do: :int.attached(pid)
@doc """
--------------------------------------------------------------------
meta(Meta, Cmd)
Meta = pid()
Cmd = step | next | continue | finish | skip | timeout | stop
Cmd = messages => [Message]
meta(Meta, Cmd, Arg)
Cmd = trace, Arg = bool()
Cmd = stack_trace Arg = all | notail | false
Cmd = stack_frame Arg = {up|down, Sp}
=> {Sp, Mod, Line} | top | bottom
Cmd = backtrace Arg = integer()
=> {Sp, Mod, {Func, Arity}, Line}
Cmd = eval Arg = {Cm, Cmd} | {Cm, Cmd, Sp}
--------------------------------------------------------------------
"""
def meta(meta, :step), do: :int.meta(meta, :step)
def meta(meta, :next), do: :int.meta(meta, :next)
def meta(meta, :continue), do: :int.meta(meta, :continue)
def meta(meta, :finish), do: :int.meta(meta, :finish)
def meta(meta, :skip), do: :int.meta(meta, :skip)
def meta(meta, :timeout), do: :int.meta(meta, :timeout)
def meta(meta, :stop), do: :int.meta(meta, :stop)
def meta(meta, :messages), do: :int.meta(meta, :messages)
def meta(meta, :trace, trace), do: :int.meta(meta, :trace, trace)
def meta(meta, :stack_trace, flag), do: :int.meta(meta, :stack_trace, flag)
def meta(meta, :bindings, stack), do: :int.meta(meta, :bindings, stack)
def meta(meta, :stack_frame, arg), do: :int.meta(meta, :stack_frame, arg)
def meta(meta, :backtrace, n), do: :int.meta(meta, :backtrace, n)
def meta(meta, :eval, arg), do: :int.meta(meta, :eval, arg)
@doc """
--------------------------------------------------------------------
contents(Mod, Pid) -> string()
Mod = atom()
Pid = pid() | any
Return the contents of an interpreted module.
--------------------------------------------------------------------
"""
def contents(mod, pid) do
:int.contents(mod, pid)
end
@doc """
--------------------------------------------------------------------
functions(Mod) -> [[Name, Arity]]
Mod = Name = atom()
Arity = integer()
--------------------------------------------------------------------
"""
def functions(mod) do
:int.functions(mod)
end
# %%====================================================================
# %% External exports only to be used by error_handler
# %%====================================================================
def eval(mod, func, args) do
:int.eval(mod, func, args)
end
# %%====================================================================
# %% Internal functions
# %%====================================================================
defp int_mod({mod, src, beam, beamBin}, dist)
when is_atom(mod) and is_list(src) and is_list(beam) and is_binary(beamBin) do
try do
case is_file(src) do
true ->
check_application(src)
case check_beam(beamBin) do
{:ok, exp, abst, _beamBin} ->
load({mod, src, beam, beamBin, exp, abst}, dist)
:error ->
:error
end
false ->
:error
end
catch
reason ->
reason
end
end
defp int_mod(absMod, dist) when is_atom(absMod) or is_list(absMod) do
case check(absMod) do
{:ok, res} ->
load(res, dist)
{:error, {:app, app}} ->
:io.format("** Cannot interpret ~p module: ~p~n", [app, absMod])
:error
_error ->
:io.format("** Invalid beam file or no abstract code: ~tp\n", [absMod])
:error
end
end
defp check(mod) when is_atom(mod) do
try do
check_module(mod)
catch
x -> x
end
end
defp check(file) when is_list(file) do
try do
check_file(file)
catch
x -> x
end
end
defp load({mod, src, beam, beamBin, exp, abst}, dist) do
everywhere(dist,
fn () ->
:code.purge(mod)
:erts_debug.breakpoint({mod,:_,:_}, false)
{:module, mod} = :code.load_binary(mod, beam, beamBin)
end)
case :erl_prim_loader.get_file(:filename.absname(src)) do
{:ok, srcBin, _} ->
md5 = :code.module_md5(beamBin)
bin = :erlang.term_to_binary({:interpreter_module, exp, abst,srcBin,md5})
{:module, mod} = :dbg_iserver.safe_call({:load, mod, src, bin})
everywhere(dist,
fn () ->
true = :erts_debug.breakpoint({mod,:_,:_}, true) > 0
end)
{:module, mod};
:error ->
:error
end
end
defp check_module(mod) do
case :code.which(mod) do # Gets the path to the beam file
beam when is_list(beam) -> # Yes it'll be a char list
case find_src(mod, beam) do # We need to get the source path
src when is_list(src) ->
check_application(src)
case check_beam(beam) do
{:ok, exp, abst, beamBin} ->
{:ok, {mod, src, beam, beamBin, exp, abst}}
:error ->
{:error, :no_debug_info}
end
:error ->
{:error, :no_src}
end
_ ->
{:error, :badarg}
end
end
defp check_file(name0) do
src =
case is_file(name0) do
true -> name0
false ->
name = name0 <> ".erl"
case is_file(name) do
true -> name
false ->
name = name0 <> ".ex"
case is_file(name) do
true -> name
false -> :error
end
end
end
cond do
is_list(src) ->
check_application(src)
mod = :int.scan_module_name(src)
case :int.find_beam(mod, src) do
beam when is_list(beam) ->
case check_beam(Beam) do
{:ok, exp, abst, beamBin} ->
{:ok, {mod, src, beam, beamBin, exp, abst}}
:error ->
{:error, :no_debug_info}
end
:error ->
{:error, :no_beam}
end
true ->
{:error, :badarg}
end
end
# Try to avoid interpreting a kernel, stdlib, gs or debugger module.
defp check_application(src) do
case :lists.reverse(:filename.split(:filename.absname(src))) do
[_mod, "src", appS|_] ->
check_application2(appS)
_ -> :ok
end
end
defp check_application2("kernel-" <> _), do: throw({:error,{:app,:kernel}})
defp check_application2("stdlib-" <> _), do: throw({:error,{:app,:stdlib}})
defp check_application2("erts-" <> _), do: throw({:error,{:app,:erts}})
defp check_application2("gs-" <> _), do: throw({:error,{:app,:gs}})
defp check_application2("debugger-" <> _), do: throw({:error,{:app,:debugger}})
defp check_application2(_), do: :ok
defp find_src(mod, beam) do
src0 = :filename.rootname(List.to_string(beam)) <> ".erl"
case is_file(src0) do
true -> src0
false ->
ebinDir = :filename.dirname(beam)
src = :filename.join([:filename.dirname(ebinDir), "src", :filename.basename(src0)])
case is_file(src) do
true -> src
false -> # Check if it could be from elixir
src0 = mod.__info__(:compile)[:source]
# src0 = :filename.rootname(List.to_string(beam)) <> ".ex"
case is_file(src0) do
true -> src0
false ->
ebinDir = :filename.dirname(beam)
src = :filename.join([:filename.dirname(ebinDir), "src", :filename.basename(src0)])
case is_file(src) do
true -> src
false -> :error
end
end
end
end
end
defp check_beam(beamBin) when is_binary(beamBin) do
case :beam_lib.chunks(beamBin, [:abstract_code, :exports]) do
{:ok,{_mod,[{:abstract_code,:no_abstract_code}|_]}} ->
:error
{:ok,{_mod,[{:abstract_code,abst},{:exports,exp}]}} ->
{:ok, exp, abst, beamBin}
_ ->
:error
end
end
defp check_beam(beam) when is_list(beam) do
{:ok, bin, _fullPath} = :erl_prim_loader.get_file(:filename.absname(beam))
check_beam(bin)
end
defp is_file(name) do
:filelib.is_regular(:filename.absname(name)) #, :erl_prim_loader)
end
defp everywhere(:distributed, fun) do
case Process.alive?(self()) do
true -> :rpc.multicall(:erlang, :apply, [fun,[]])
false -> fun.()
end
end
defp everywhere(:local, fun), do: fun.()
defp scan_module_name(file) do
try do
{:ok, bin, _fullPath} = :erl_prim_loader.get_file(:filename.absname(file))
scan_module_name_1([], <<>>, bin, enc(bin))
catch
_, _ ->
throw({:error, :no_beam})
end
end
defp scan_module_name_1(cont0, b0, bin0, enc) do
n = min(100, byte_size(bin0))
{bin1, bin} = :erlang.split_binary(bin0, n)
{chars, b1} =
case :unicode.characters_to_list(:erlang.list_to_binary([b0, bin1]), enc) do
{:incomplete, list, binary} -> {list, binary}
list when is_list(list) and list != [] -> {list, <<>>}
end
scan_module_name_2(cont0, chars, b1, bin, enc)
end
defp scan_module_name_2(cont0, chars, b1, bin, enc) do
case :erl_scan.tokens(cont0, chars, _anyLine \\ 1) do
{:done, {:ok, ts, _}, rest} ->
scan_module_name_3(ts, rest, b1, bin, enc)
{:more, cont} ->
scan_module_name_1(cont, b1, bin, enc)
end
end
defp scan_module_name_3([{'-',_},{:atom,_,:module},{'(',_} | _]=ts,
_chars, _b1, _bin, _enc) do
scan_module_name_4(ts)
end
defp scan_module_name_3([{'-',_},{:atom,_,_} | _], chars, b1, bin, enc) do
scan_module_name_2("", chars, b1, bin, enc)
end
defp scan_module_name_4(ts) do
{:ok, {:attribute,_,:module,m}} = :erl_parse.parse_form(ts)
true = is_atom(m)
m
end
defp enc(bin) do
case :epp.read_encoding_from_binary(bin) do
:none -> :epp.default_encoding()
encoding -> encoding
end
end
end