Skip to content

Instantly share code, notes, and snippets.

@rlipscombe
Created March 26, 2016 09:30
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save rlipscombe/770ce8fc75add11e16f1 to your computer and use it in GitHub Desktop.
Save rlipscombe/770ce8fc75add11e16f1 to your computer and use it in GitHub Desktop.
#!/usr/bin/env escript
%%% This script converts an arbitrary binary file to an Erlang module with a
%%% single exported 'bin/0' function that returns the original binary.
%%%
%%% See the end of the file for how I figured out the correct terms.
main(["+debug_info" | Files]) ->
io:format("Embedding binaries with debug_info...\n"),
lists:foreach(fun(X) -> embed_file_in_beam(X, [debug_info]) end, Files);
main(Files) ->
io:format("Embedding binaries...\n"),
lists:foreach(fun(X) -> embed_file_in_beam(X, []) end, Files).
embed_file_in_beam(InputPath, CompilerOptions) ->
% Read the source file.
{ok, Bytes} = file:read_file(InputPath),
% Work out what we're going to call the output.
ModuleName = get_module_name(filename:basename(InputPath)),
file:make_dir("ebin"),
OutputPath = filename:join("ebin", ModuleName ++ ".beam"),
io:format(" ~s -> ~s (~s)\n", [InputPath, ModuleName, OutputPath]),
% We'll export a function Mod:bin/0.
Mod = list_to_atom(ModuleName),
Fun = 'bin',
% An Erlang module is a list of forms; we need three:
Forms = [
module_form(Mod), % -module(foo).
export_form(Fun), % -export([bin/0]).
function_form(Fun, Bytes) % bin() -> <<...>>.
],
% Compile the forms into a binary.
{ok, Mod, Bin} = compile:forms(Forms, CompilerOptions),
% The binary _is_ the beam file, so write it out.
ok = file:write_file(OutputPath, Bin).
% Given, e.g., "foo_bar.baz", return "foo_bar_baz".
get_module_name(Filename) ->
re:replace(Filename, "\\.", "_", [global, {return, list}]).
module_form(Mod) ->
{attribute, 1, module, Mod}.
export_form(Fun) ->
{attribute, 1, export, [{Fun, 0}]}.
function_form(Fun, Binary) ->
% a function,
{function, 1, Fun, 0,
% with one clause,
[{clause, 1, [], [],
% which has one expression, a binary,
[{bin, 1, [
% made up of the stuff we first thought of.
{bin_element, 1, {integer, 1, Byte}, default, default}
|| <<Byte:8>> <= Binary
]}]}]}.
%%% How it works:
%%%
%%% See http://stackoverflow.com/a/2160696/8446, but...
%%%
%%% {ok, MTs, _} = erl_scan:string("-module(foo).").
%%% {ok, ETs, _} = erl_scan:string("-export([bin/0]).").
%%% % bin() -> <<"Hello">>.
%%% {ok, FTs, _} = erl_scan:string("bin() -> <<72, 101, 108, 108, 111>>.").
%%%
%%% {ok, MF} = erl_parse:parse_form(MTs).
%%% {ok, EF} = erl_parse:parse_form(ETs).
%%% {ok, FF} = erl_parse:parse_form(FTs).
%%%
%%% Forms = [MF, EF, FF].
%%%
%%% The above returns a readable AST, suitable for compile:forms/2.
%%%
%%% Debug Information
%%%
%%% By passing +debug_info to embed_binaries, you can persuade it to generate
%%% debug information. This can be verified by:
%%%
%%% beam_lib:chunks("foo.beam", [abstract_code]).
%%% % Without +debug_info:
%%% % {ok, {foo, [{abstract_code, no_abstract_code}]}}
%%% % With +debug_info:
%%% % {ok, {foo, [{abstract_code, {raw_abstract_v1, [{attribute, ....
%% vi: ft=erlang
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment