Elixir is Erlang in a nutshell

Elixir leverages the Erlang VM […]
elixir-lang.org

What does mean “leverages”? It means Elixir is compiled into BEAMs, and the latter are executed in Erlang VM. Elixir brings a lot of goodness, like arguable better syntax and extensive macro support, but after all it’s the language on top of Erlang.

BEAMs are known to support decompilation back into Erlang code (providing they were compiled with debug_info compiler option on.)

So, let’s configure our project to support decompilation back into Erlang code in MIX_ENV=dev environment. That is relatively easy and might be helpful in some cases: both educational and “wtf–investigations.” To do so we’ll need three things:

Update your config/dev.exs to enable debug_info

Put this line in the very top of your config/dev.exs file:

# https://hexdocs.pm/elixir/Code.html#compiler_options/1
Code.compiler_options(debug_info: true)

According to the official documentation,

:debug_true option is set to true to retain debug information in the compiled module; this allows a developer to reconstruct the original source code, false by default.
[…]
These options are global since they are stored by Elixir’s Code Server.

Prepare the Elixir BEAMs to be available

Before we are to create the script itself, let’s discover what do we need to support Elixir code decompilation. Right: Elixir’s core compiled BEAMs themselves, otherwise we would hardly decompile Elixir’s own goodness, like comprehensions etc.

I have no idea whether it’s possible with a standard Elixir distribution, but lickily I nevertheless use trunk version (managed with exenv version manager.) So, install exenv unless you have already done it, download the source code of Elixir somewhere and compile it:

cd /usr/local/src && \
  git clone https://github.com/elixir-lang/elixir.git && \
  cd elixir && \
  make clean test

If everything went good, teach your exenv to use trunk version:

cd ~/.exenv/versions && \
  ln -s /usr/local/src/elixir trunk && \
  cd PATH_TO_MY_ELIXIR_PROJECT && \
  exenv local trunk

Check we are all set:

$ iex
Erlang/OTP 20 [erts-9.0] [source] [64-bit] [...]

Interactive Elixir (1.6.0-dev) - press Ctrl+C to exit (type h() ENTER for help)
iex|1 ▶ 

Create a script to unveil the code

Copy the following lines into /usr/local/bin/delixir:

#!/usr/bin/env escript
% -*- mode: erlang -*-

main([BeamFile]) ->
  Dir = "/usr/local/src/elixir/lib/elixir/ebin",
  code:add_patha(Dir),
  {ok,{_,[{abstract_code,{_,AC}}]}} = beam_lib:chunks(BeamFile,[abstract_code]),
  io:fwrite("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]).

Make the file executable with chmod +x /usr/local/bin/delixir and you are all set. Let’s check how it works:

delixir _build/dev/lib/my_app/ebin/Elixir.MyApp.beam

You should see something like:

-file("/home/user/proj/my_app/lib/"
      "my_app.ex",
      1).
-module('Elixir.MyApp').
-compile(no_auto_import).
-behaviour('Elixir.GenServer').
-export(['__info__'/1, ...]).
-spec '__info__'(attributes | compile | exports |
		 functions | macros | md5 | module) -> atom() |
						       [{atom(), any()} |
							{atom(), byte(),
							 integer()}].
'__info__'(functions) ->

etc. This is an Erlang code that is basically exactly what Elixir was “transpiled” into before compilation. Or, better to say, this is how Erlang virtual machine sees your Elixir code.