From 3b2db289cd9f334516604020a67fe81b442d92e4 Mon Sep 17 00:00:00 2001 From: Gordon Guthrie Date: Fri, 29 Dec 2017 11:57:50 +0000 Subject: [PATCH 1/2] Now offers a summary of the beam file and not the full thing See changes to the README for details --- README.md | 39 +++++++++++++++++- lib/decompilerl.ex | 89 +++++++++++++++++++++++++++++++++++++++++- lib/decompilerl/cli.ex | 38 ++++++++++++------ 3 files changed, 153 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 591f234..7b8b338 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,41 @@ new() -> There we go! `struct` is an Erlang map with a special key `__struct__ => ?MODULE` that allows pattern matching further down the line. Noice! +## Summary View + +Sometimes you don't want to fish through all the source code so there is now a summary view. + +This can be called as so: + +```elixir +Decompilerl.summarise('_build/dev/lib/myapp/ebin/Elixir.MyApp.AuthController.beam') +``` + +It returns a summary of the beam file: +``` +File : web/controllers/auth_controller.ex +Module : Elixir.MyApp.AuthController +Behaviours : Elixir.Plug +Exported Fns : __info__/1 + action/2 + call/2 + callback/2 + fake/2 + index/2 + init/1 +Private Fns : action (overridable 2)/2 + authorize_url!/1 + call/2 + callback/2 + fake/2 + get_token!/2 + get_user!/2 + index/2 + init/1 + maybe_create/5 + phoenix_controller_pipeline/2 +``` + ## Command-line interface You can build `Decompilerl` as a standalone executable (escript). @@ -73,7 +108,7 @@ $ ./decompilerl Decompilerl -usage: decompierl [-o | --output=] +usage: decompierl [-o | --output= | -s | --sumary] ``` ## Usage @@ -81,6 +116,8 @@ usage: decompierl [-o | --output=] By default, `Decompilerl.decompile` spits the Erlang abstract code to stdout. When provided with a second (optional) argument, it'll dump it to a file. +adding the option `-s` or `--summary` produces the summary and not the source code + ``` $ iex -S mix diff --git a/lib/decompilerl.ex b/lib/decompilerl.ex index 6a18f94..733bc51 100644 --- a/lib/decompilerl.ex +++ b/lib/decompilerl.ex @@ -1,11 +1,98 @@ defmodule Decompilerl do + def summarise(module, device \\ :stdio) do + obtain_beam(module) + |> make_summary + |> format_summary + |> write_to(device) + end + def decompile(module, device \\ :stdio) do obtain_beam(module) |> do_decompile |> write_to(device) end + defp format_summary(map) do + %{:file => file, + :module => module, + :behaviours => behaviours, + :exports => exports, + :functions => functions} = map + lines = [ + "File : " <> file, + "Module : " <> module, + format(behaviours, "Behaviours : "), + format(exports, "Exported Fns : "), + get_private_functions(functions, exports) + |>format("Private Fns : ") + ] + Enum.join(List.flatten(lines), "\n") + end + + + defp format([], _prefix) do + [] + end + defp format(behaviours, prefix) do + [h | t] = behaviours + lines = for x <- t, do: " " <> x + [prefix <> h] ++ lines + end + + defp get_private_functions(functions, exports) do + Enum.drop_while(functions, fn(x) -> Enum.member?(exports, x) end) + end + + defp make_summary(beam_code) do + {:ok, {_, [abstract_code: {_, ac}]}} = + :beam_lib.chunks(beam_code, [:abstract_code]) + {:tree, _, _, ast} = :erl_syntax.form_list(ac) + map = %{:file => [], + :module => [], + :behaviours => [], + :exports => [], + :functions => []} + Enum.reduce(ast, map, &process_ast/2) + end + + defp process_ast({:attribute, _, :module, module}, map) do + Map.put(map, :module, Atom.to_string(module)) + end + + defp process_ast({:attribute, _, :file, {file, _}}, map) do + Map.put(map, :file, to_string(file)) + end + + defp process_ast({:attribute, _, :export, exports}, map) do + formatted_exports = for {func, arity} <- exports do + make_fn_declaration(func, arity) + end + append_value(map, :exports, formatted_exports) + end + + defp process_ast({:attribute, _, :behaviour, behaviour}, map) do + append_value(map, :behaviours, [Atom.to_string(behaviour)]) + end + + defp process_ast({:function, _, func, arity, _body}, map) do + append_value(map, :functions, [make_fn_declaration(func, arity)]) + end + + ## dump the rest + defp process_ast(_clause, map) do + map + end + + defp make_fn_declaration(func, arity) do + Atom.to_string(func) <> "/" <> Integer.to_string(arity) + end + + defp append_value(map, key, valuelist) when is_list(valuelist) do + %{^key => values} = map + Map.put(map, key, values ++ valuelist) + end + defp obtain_beam(module) when is_atom(module) do {^module, beam, _file} = :code.get_object_code(module) beam @@ -18,13 +105,13 @@ defmodule Decompilerl do defp do_decompile(beam_code) do {:ok, {_, [abstract_code: {_, ac}]}} = :beam_lib.chunks(beam_code, [:abstract_code]) - :erl_prettypr.format(:erl_syntax.form_list(ac)) end defp write_to(code, :stdio) do IO.puts code end + defp write_to(code, file_name) when is_binary(file_name) do {:ok, result} = File.open(file_name, [:write], fn(file) -> diff --git a/lib/decompilerl/cli.ex b/lib/decompilerl/cli.ex index 3ced7ad..d66158d 100644 --- a/lib/decompilerl/cli.ex +++ b/lib/decompilerl/cli.ex @@ -1,6 +1,6 @@ defmodule Decompilerl.CLI do - @switches help: :boolean, output: :string - @aliases h: :help, o: :output + @switches help: :boolean, output: :string, summary: :boolean + @aliases h: :help, o: :output, s: :summary def main(argv) do argv @@ -8,26 +8,42 @@ defmodule Decompilerl.CLI do |> process end - def parse_args(args) do - opts = + defp parse_args(args) do + opts = OptionParser.parse(args, switches: @switches, aliases: @aliases) - case opts do - {[], [name], _} -> {name, :stdio} - {[output: output], [name], _} -> {name, output} - _ -> :help + {parsed, args, errors} = opts + case errors do + [] -> + case {Enum.sort(parsed), args} do + {[], [name]} -> + {:decompile, {name, :stdio}} + {[output: output], [name]} -> + {:decompile, {name, output}} + {[output: output, summary: true], [name]} -> + {:summarise, {name, output}} + {[summary: true], [name]} -> + {:summarise, {name, :stdio}} + end + _ -> + :help end end - def process(:help) do + defp process(:help) do IO.puts """ Decompilerl - usage: decompierl [-o | --output=] + usage: decompierl [-o | --output= | -s | --summary] """ end - def process({name, device}) do + defp process({:decompile, {name, device}}) do Decompilerl.decompile(name, device) end + + defp process({:summarise, {name, device}}) do + Decompilerl.summarise(name, device) + end + end From 968f5dd4387271fc2f2d962647179e7c8bd49335 Mon Sep 17 00:00:00 2001 From: Gordon Guthrie Date: Fri, 29 Dec 2017 12:01:59 +0000 Subject: [PATCH 2/2] arg, typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7b8b338..f279b06 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,7 @@ $ ./decompilerl Decompilerl -usage: decompierl [-o | --output= | -s | --sumary] +usage: decompierl [-o | --output= | -s | --summary] ``` ## Usage