I want to see what happens when elixir gets transformed into beam files. Is there any way to print in console or in a file how it is translated? I want to know what would this module look like in erlang.
I was thinking if there is a debug mode of elixir, which would output any of the above.
More specifically I have this example:
defmodule Test do
def t1(a), do: a
def t1(a, b \\ 2), do: a + b
end
The above code raises a warning, which is understanable considering what I've done. Basically I want to understand a bit more what's happening.
First, you need to compile the Elixir module to a .beam file:
$ cat test.ex
defmodule Test do
def t1(a), do: a
def t1(a, b \\ 2), do: a + b
end
$ elixirc test.ex
warning: this clause cannot match because a previous clause at line 2 always matches
test.ex:3
This will generate Elixir.Test.beam
. Then, you can decompile this .beam to Erlang source using the following escript (I copied this from some answer here on Stackoverflow a while ago. Unfortunately I can't seem to locate the exact source but this code is in many answers here including this one.):
$ cat decompile.erl
#!/usr/bin/env escript
main([BeamFile]) ->
{ok,{_,[{abstract_code,{_,AC}}]}} = beam_lib:chunks(BeamFile,[abstract_code]),
io:fwrite("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]).
Then run it:
$ escript decompile.erl Elixir.Test.beam
-compile(no_auto_import).
-file("test.ex", 1).
-module('Elixir.Test').
-export(['__info__'/1, t1/1, t1/2]).
-spec '__info__'(attributes | compile | exports |
functions | macros | md5 | module |
native_addresses) -> atom() |
[{atom(), any()} |
{atom(), byte(), integer()}].
'__info__'(functions) -> [{t1, 1}, {t1, 2}];
'__info__'(macros) -> [];
'__info__'(info) ->
erlang:get_module_info('Elixir.Test', info).
t1(a@1) -> a@1;
t1(x0@1) -> t1(x0@1, 2).
t1(a@1, b@1) -> a@1 + b@1.
This probably should be more of a comment to @Dogbert’s answer above, but I would post it as a separate answer for the sake of formatting.
One does not need to create .ex
files and invoke the compiler on them to produce beams:
{:module, _, bytecode, _} =
defmodule Elixir.Test do
def t1(a), do: a
def t1(a, b \\ 2), do: a + b
end
# File.write!("Elixir.Test.beam", bytecode)
now you might have had a beam file written (we have it stored in the bytecode
variable by the way.)
NB: beam_lib:chunks/2
works if and only the beam contains unencrypted debug information (elixir beams by default do.)
Also, you don’t need to write decompiled erlang code, you might simply pass a binary there, directly in Elixir
:
:beam_lib.chunks(bytecode, [:abstract_code])
To extract the code itself:
{:ok,{_,[abstract_code: {_, code}]}} =
bytecode |> :beam_lib.chunks([:abstract_code])
Now code
contains the code, it should be enough to examine it, but you still are free to use erlang build-ins:
code |> :erl_syntax.form_list
or:
code |> :erl_syntax.form_list |> :erl_prettypr.format
The latter will give you the binary charlist, containing erlang code, exactly as in @Dogbert’s answer. Use IO.puts
to output it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With