BeamFile - An interface to the BEAM file format and a decompiler

BeamFile is mainly a wrapper around the Erlang module :beam_lib.
BeamFile provides different views to the data in a BEAM file: BeamFile.elixir_code/2, BeamFile.erl_code/1, BeamFile.abstract_code/1 and BeamFile.byte_code/1. The reconstructed Elixir code is not the original code. In this code all macros and reference are resolved.

Example:

defmodule Say do
  def hello(name \\ "World"), do: name |> format() |> puts()

  def format(name), do: "Hello, #{name}!"

  defdelegate puts(item), to: IO
end
iex> {:ok, code} = BeamFile.elixir_code(Say)
iex> IO.puts(code)
defmodule Elixir.Say do
  def hello(name) do
    puts(format(name))
  end

  def hello do
    hello("World")
  end

  def format(name) do
    <<"Hello, ", String.Chars.to_string(name)::binary(), "!">>
  end

  def puts(item) do
    IO.puts(item)
  end
end
:ok
iex> {:ok, code} = BeamFile.erl_code(Say)
iex> IO.puts(code)
file("test/fixtures/default.ex", 7).

-module('Elixir.Say').

-compile([no_auto_import]).

-export(['__info__'/1,
         format/1,
         hello/0,
         hello/1,
         puts/1]).

-spec '__info__'(attributes |
                 compile |
                 functions |
                 macros |
                 md5 |
                 exports_md5 |
                 module |
                 deprecated) -> any().

'__info__'(module) -> 'Elixir.Say';
'__info__'(functions) ->
    [{format, 1}, {hello, 0}, {hello, 1}, {puts, 1}];
'__info__'(macros) -> [];
'__info__'(exports_md5) -> <<"VÑ>Qlf`zâ\202--*ÐP®">>;
'__info__'(Key = attributes) ->
    erlang:get_module_info('Elixir.Say', Key);
'__info__'(Key = compile) ->
    erlang:get_module_info('Elixir.Say', Key);
'__info__'(Key = md5) ->
    erlang:get_module_info('Elixir.Say', Key);
'__info__'(deprecated) -> [].

format(_name@1) ->
    <<"Hello, ",
      case _name@1 of
          _@1 when erlang:is_binary(_@1) -> _@1;
          _@1 -> 'Elixir.String.Chars':to_string(_@1)
      end/binary,
      "!">>.

hello() -> hello(<<"World">>).

hello(_name@1) -> puts(format(_name@1)).

puts(_item@1) -> 'Elixir.IO':puts(_item@1).
:ok
iex> {:ok, code} = BeamFile.byte_code(Say)
iex> IO.inspect(code)
{:beam_file, Say,
 [
   {:__info__, 1, 2},
   {:format, 1, 9},
   {:hello, 0, 13},
   {:hello, 1, 15},
   {:module_info, 0, 19},
   {:module_info, 1, 21},
   {:puts, 1, 17}
 ], [vsn: [328337295702536866380643469106329237584]],
 [
   version: '7.6.7',
   options: [:dialyzer, :no_spawn_compiler_process, :from_core,
    :no_core_prepare, :no_auto_import],
   source: '.../say.ex'
 ],
 [
   {:function, :__info__, 1, 2,
    [
      {:label, 1},
      {:line, 0},
      {:func_info, {:atom, Say}, {:atom, :__info__}, 1},
      {:label, 2},
      {:select_val, {:x, 0}, {:f, 1},
       {:list,
        [
          atom: :attributes,
          f: 7,
          atom: :compile,
          f: 7,
          atom: :deprecated,
          f: 6,
          atom: :exports_md5,
          f: 5,
          atom: :functions,
          f: 4,
          atom: :macros,
          f: 6,
          atom: :md5,
          f: 7,
          atom: :module,
          f: 3
        ]}},
      {:label, 3},
      {:move, {:atom, Say}, {:x, 0}},
      :return,
      {:label, 4},
      {:move, {:literal, [format: 1, hello: 0, hello: 1, puts: 1]}, {:x, 0}},
      :return,
      {:label, 5},
      {:move,
       {:literal,
        <<86, 209, 62, 81, 108, 102, 96, 122, 226, 130, 45, 45, 42, 208, 80,
          174>>}, {:x, 0}},
      :return,
      {:label, 6},
      {:move, nil, {:x, 0}},
      :return,
      {:label, 7},
      {:move, {:x, 0}, {:x, 1}},
      {:move, {:atom, Say}, {:x, 0}},
      {:line, 0},
      {:call_ext_only, 2, {:extfunc, :erlang, :get_module_info, 2}}
    ]},
   {:function, :format, 1, 9,
    [
      {:line, 1},
      {:label, 8},
      {:func_info, {:atom, Say}, {:atom, :format}, 1},
      {:label, 9},
      {:allocate, 1, 1},
      {:move, {:x, 0}, {:y, 0}},
      {:test, :is_binary, {:f, 10}, [x: 0]},
      {:move, {:y, 0}, {:x, 0}},
      {:jump, {:f, 11}},
      {:label, 10},
      {:init, {:y, 0}},
      {:line, 1},
      {:call_ext, 1, {:extfunc, String.Chars, :to_string, 1}},
      {:label, 11},
      {:line, 1},
      {:gc_bif, :byte_size, {:f, 0}, 1, [x: 0], {:x, 1}},
      {:bs_add, {:f, 0}, [{:x, 1}, {:integer, 8}, 1], {:x, 1}},
      {:line, 1},
      {:bs_init2, {:f, 0}, {:x, 1}, 0, 2, {:field_flags, 0}, {:x, 1}},
      {:bs_put_string, 7, {:string, 'Hello, '}},
      {:bs_put_binary, {:f, 0}, {:atom, :all}, 8, {:field_flags, 0}, {:x, 0}},
      {:bs_put_string, 1, {:string, '!'}},
      {:move, {:x, 1}, {:x, 0}},
      {:deallocate, 1},
      :return
    ]},
   {:function, :hello, 0, 13,
    [
      {:line, 2},
      {:label, 12},
      {:func_info, {:atom, Say}, {:atom, :hello}, 0},
      {:label, 13},
      {:move, {:literal, "World"}, {:x, 0}},
      {:call_only, 1, {Say, :hello, 1}}
    ]},
   {:function, :hello, 1, 15,
    [
      {:line, 2},
      {:label, 14},
      {:func_info, {:atom, Say}, {:atom, :hello}, 1},
      {:label, 15},
      {:allocate, 0, 1},
      {:line, 2},
      {:call, 1, {Say, :format, 1}},
      {:call_last, 1, {Say, :puts, 1}, 0}
    ]},
   {:function, :puts, 1, 17,
    [
      {:line, 3},
      {:label, 16},
      {:func_info, {:atom, Say}, {:atom, :puts}, 1},
      {:label, 17},
      {:line, 3},
      {:call_ext_only, 1, {:extfunc, IO, :puts, 1}}
    ]},
   {:function, :module_info, 0, 19,
    [
      {:line, 0},
      {:label, 18},
      {:func_info, {:atom, Say}, {:atom, :module_info}, 0},
      {:label, 19},
      {:move, {:atom, Say}, {:x, 0}},
      {:line, 0},
      {:call_ext_only, 1, {:extfunc, :erlang, :get_module_info, 1}}
    ]},
   {:function, :module_info, 1, 21,
    [
      {:line, 0},
      {:label, 20},
      {:func_info, {:atom, Say}, {:atom, :module_info}, 1},
      {:label, 21},
      {:move, {:x, 0}, {:x, 1}},
      {:move, {:atom, Say}, {:x, 0}},
      {:line, 0},
      {:call_ext_only, 2, {:extfunc, :erlang, :get_module_info, 2}}
    ]}
 ]}
14 Likes

Version 0.5.0 is released. I have added BeamFile.elixir_quoted/1 to get the AST from the beam file. All functions now accept {:module, Foo, <<...>>, []} as input. With this change experiments in the iex are simpler.

iex(1)> defmodule Foo do
...(1)>   def bar(x), do: if x == 0, do: false, else: true
...(1)> end |> BeamFile.elixir_code!() |> IO.puts()
defmodule Elixir.Foo do
  def bar(x) do
    case :erlang.==(x, 0) do
      false -> true
      true -> false
    end
  end
end
:ok
iex(2)> defmodule Foo do
...(2)>   def bar(x), do: if x == 0, do: false, else: true
...(2)> end |> BeamFile.elixir_quoted!()
{:defmodule, [context: Elixir, import: Kernel],
 [
   {:__aliases__, [alias: false], [Foo]},
   [
     do: {:__block__, [],
      [
        {:def, [line: 4],
         [
           {:bar, [], [{:x, [version: 0, line: 4], nil}]},
           [
             do: {:case, [line: 4, optimize_boolean: true],
              [
                {{:., [line: 4], [:erlang, :==]}, [line: 4],
                 [{:x, [version: 0, line: 4], nil}, 0]},
                [
                  do: [
                    {:->, [generated: true, line: 4], [[false], true]},
                    {:->, [generated: true, line: 4], [[true], false]}
                  ]
                ]
              ]}
           ]
         ]}
      ]}
   ]
 ]}

6 Likes

Version 0.6.0 has been released with some updates to work with Elixir 1.16.

The function BeamFileDocs now has some options for formatting and filtering the output:

iex(1)> BeamFile.docs(Date, since: "~> 1.12", format: :since)
{:ok,
 {[
    {{:function, :after?, 2}, [since: "1.15.0"]},
    {{:function, :before?, 2}, [since: "1.15.0"]},
    {{:function, :range, 3}, [since: "1.12.0"]}
  ], [since: nil]}}
iex(2)> BeamFile.docs(Enum, format: :info, deprecated: true)
{:ok,
 {[
    {{:function, :chunk, 2}, [since: nil, hidden: true, deprecated: true]},
    {{:function, :chunk, 3}, [since: nil, hidden: true, deprecated: true]},
    {{:function, :chunk, 4}, [since: nil, hidden: true, deprecated: true]},
    {{:function, :filter_map, 3}, [since: nil, hidden: true, deprecated: true]},
    {{:function, :partition, 2}, [since: nil, hidden: true, deprecated: true]},
    {{:function, :uniq, 2}, [since: nil, hidden: true, deprecated: true]}
  ], [since: nil, hidden: false, deprecated: false]}}
4 Likes