Skip to content

hrzndhrn/beam_file

Repository files navigation

BeamFile

Hex.pm: version GitHub: CI status License: MIT

A little fun project to get a peek into BEAM files. For now, this project is in an early beta state.

BeamFile is mainly a wrapper around the Erlang module :beam_lib.

BeamFile provides different views to the data in a BEAM file:

  • abstract code
  • byte code
  • Erlang code
  • Elixir code.

The reconstructed Elixir code is not the original code. In this code all macros and reference are resolved.

A mix task is also provided for decompiling modules.

$ mix beam.file MyModule --elixir
$ mix beam.file MyModule --erlang
$ mix beam.file MyModule --byte-code

Example

Assume we have an BEAM file compiled form the following source.

defmodule Example.Math do
  @moduledoc "Math is Fun"

  def add(number_a, number_b), do: number_a + number_b

  def odd_or_even(a) do
    if rem(a, 2) == 0 do
      :even
    else
      :odd
    end
  end
end

The module must be compiled and loaded so that it can be found by name, see BeamFile.which/1 and :erlang.which/1. To give BaemFile in iex a try see "Example (iex)" in the next section.

Than we can reconstruct Elixir code:

iex> {:ok, code} = BeamFile.elixir_code(Example.Math, docs: true)
iex> IO.puts(code)
defmodule Elixir.Example.Math do
  @moduledoc "Math is Fun"

  def add(number_a, number_b) do
    :erlang.+(number_a, number_b)
  end

  def odd_or_even(a) do
    case(:erlang.==(:erlang.rem(a, 2), 0)) do
      false ->
        :odd

      true ->
        :even
    end
  end
end

Take a look on the Erlang code:

iex> {:ok, code} = BeamFile.erl_code(Example.Math)
iex> IO.puts(code)
-file("test/fixtures/example/math.ex", 1).

-module('Elixir.Example.Math').

-compile([no_auto_import]).

-export(['__info__'/1, add/2, odd_or_even/1]).

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

'__info__'(module) -> 'Elixir.Example.Math';
'__info__'(functions) -> [{add, 2}, {odd_or_even, 1}];
'__info__'(macros) -> [];
'__info__'(Key = attributes) ->
    erlang:get_module_info('Elixir.Example.Math', Key);
'__info__'(Key = compile) ->
    erlang:get_module_info('Elixir.Example.Math', Key);
'__info__'(Key = md5) ->
    erlang:get_module_info('Elixir.Example.Math', Key);
'__info__'(deprecated) -> [].

add(_number_a@1, _number_b@1) ->
    _number_a@1 + _number_b@1.

odd_or_even(_a@1) ->
    case _a@1 rem 2 == 0 of
      false -> odd;
      true -> even
    end.

Or byte code:

iex> {:ok, code} = BeamFile.byte_code(Example.Math)
iex> IO.puts(code)
{:ok,
 {:beam_file, Example.Math,
  [
    {:__info__, 1, 2},
    {:add, 2, 8},
    {:module_info, 0, 13},
    {:module_info, 1, 15},
    {:odd_or_even, 1, 10}
  ], [vsn: [213009173131107396303781325243687557035]],
  [
    version: '7.5.3',
    options: [:no_spawn_compiler_process, :from_core,
     :no_auto_import],
    source: '/Users/kruse/Projects/beam_file/test/fixtures/example/math.ex'
  ],
  [
    {:function, :__info__, 1, 2,
     [
       {:label, 1},
       {:line, 0},
       {:func_info, {:atom, Example.Math}, {:atom, :__info__}, 1},
       {:label, 2},
       {:select_val, {:x, 0}, {:f, 1},
        {:list,
         [
           atom: :compile,
           f: 6,
           atom: :md5,
           f: 6,
           atom: :attributes,
           f: 6,
           atom: :functions,
           f: 5,
           atom: :macros,
           f: 4,
           atom: :deprecated,
           f: 4,
           atom: :module,
           f: 3
         ]}},
       {:label, 3},
       {:move, {:atom, Example.Math}, {:x, 0}},
       :return,
       {:label, 4},
       {:move, nil, {:x, 0}},
       :return,
       {:label, 5},
       {:move, {:literal, [add: 2, odd_or_even: 1]}, {:x, 0}},
       :return,
       {:label, 6},
       {:move, {:x, 0}, {:x, 1}},
       {:move, {:atom, Example.Math}, {:x, 0}},
       {:line, 0},
       {:call_ext_only, 2,
        {:extfunc, :erlang, :get_module_info, 2}}
     ]},
    {:function, :add, 2, 8,
     [
       {:line, 1},
       {:label, 7},
       {:func_info, {:atom, Example.Math}, {:atom, :add}, 2},
       {:label, 8},
       {:line, 1},
       {:gc_bif, :+, {:f, 0}, 2, [x: 0, x: 1], {:x, 0}},
       :return
     ]},
    {:function, :odd_or_even, 1, 10,
     [
       {:line, 2},
       {:label, 9},
       {:func_info, {:atom, Example.Math}, {:atom, :odd_or_even},
        1},
       {:label, 10},
       {:line, 3},
       {:gc_bif, :rem, {:f, 0}, 1, [x: 0, integer: 2], {:x, 0}},
       {:test, :is_eq_exact, {:f, 11}, [x: 0, integer: 0]},
       {:move, {:atom, :even}, {:x, 0}},
       :return,
       {:label, 11},
       {:move, {:atom, :odd}, {:x, 0}},
       :return
     ]},
    {:function, :module_info, 0, 13,
     [
       {:line, 0},
       {:label, 12},
       {:func_info, {:atom, Example.Math}, {:atom, :module_info},
        0},
       {:label, 13},
       {:move, {:atom, Example.Math}, {:x, 0}},
       {:line, 0},
       {:call_ext_only, 1,
        {:extfunc, :erlang, :get_module_info, 1}}
     ]},
    {:function, :module_info, 1, 15,
     [
       {:line, 0},
       {:label, 14},
       {:func_info, {:atom, Example.Math}, {:atom, :module_info},
        1},
       {:label, 15},
       {:move, {:x, 0}, {:x, 1}},
       {:move, {:atom, Example.Math}, {:x, 0}},
       {:line, 0},
       {:call_ext_only, 2,
        {:extfunc, :erlang, :get_module_info, 2}}
     ]}
  ]}}

Example (iex)

Disasamble some Elixir code:

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

Take a look to the AST:

iex(1)> defmodule Foo do
...(1)>   def bar(x), do: if x == 0, do: false, else: true
...(1)> 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]}
                  ]
                ]
              ]}
           ]
         ]}
      ]}
   ]
 ]}

Use of BeamFile.erl_code/1 with binary:

iex(1)> {:module, Foo, binary, _} = defmodule Foo do
...(1)>   def bar, do: :bar
...(1)> end
iex(2)> BeamFile.erl_code!(binary) |> IO.puts()
-file("iex", 1).

-module('Elixir.Foo').

-compile([no_auto_import]).

-export(['__info__'/1, bar/0]).

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

'__info__'(module) -> 'Elixir.Foo';
'__info__'(functions) -> [{bar, 0}];
'__info__'(macros) -> [];
'__info__'(exports_md5) ->
    <<\r¦c\004í.©¾uHÀ¯\217,">>;
'__info__'(Key = attributes) ->
    erlang:get_module_info('Elixir.Foo', Key);
'__info__'(Key = compile) ->
    erlang:get_module_info('Elixir.Foo', Key);
'__info__'(Key = md5) ->
    erlang:get_module_info('Elixir.Foo', Key);
'__info__'(deprecated) -> [].

bar() -> bar.
:ok

Resources

More information about BEAM files: