#!/usr/bin/env escript %% -*- erlang -*- %% Decompile .beam files into .erl source files when debug_info is available. %% Usage: escript decompile_beam.erl [more paths...] -mode(compile). main(Args) -> case Args of [] -> usage(); Paths -> lists:foreach(fun decompile_path/1, Paths) end. usage() -> io:format("Usage: escript decompile_beam.erl [more paths...]~n", []), io:format("Example: escript decompile_beam.erl module.beam~n", []), io:format(" escript decompile_beam.erl ./ebin~n", []), io:format("~nNote: readable source can only be generated when the .beam was compiled with debug_info.~n", []), io:format(" Example: compile:file(File, [debug_info])~n", []), halt(1). decompile_path(Path) -> case filelib:is_dir(Path) of true -> decompile_dir(Path); false -> case filelib:is_file(Path) of true -> case filename:extension(Path) of ".beam" -> decompile_beam(Path); _ -> io:format("Skip non-.beam file: ~s~n", [Path]) end; false -> io:format("Path does not exist: ~s~n", [Path]) end end. decompile_dir(Dir) -> case file:list_dir(Dir) of {ok, Files} -> Beams = [filename:join(Dir, F) || F <- Files, filename:extension(F) =:= ".beam"], io:format("Found ~p .beam files in ~s~n", [length(Beams), Dir]), lists:foreach(fun decompile_beam/1, Beams); {error, Reason} -> io:format("Failed to read directory ~s: ~p~n", [Dir, Reason]) end. decompile_beam(BeamPath) -> case beam_lib:chunks(BeamPath, [abstract_code]) of {ok, {_Module, [{abstract_code, {raw_abstract_v1, Forms}}]}} -> ErlPath = to_erl_path(BeamPath), case format_and_write(Forms, ErlPath) of ok -> io:format("Generated: ~s~n", [ErlPath]); {error, Reason} -> io:format("Write failed ~s: ~p~n", [ErlPath, Reason]) end; {ok, {_Module, _}} -> io:format("Skip ~s: no abstract_code chunk (likely built without debug_info)~n", [BeamPath]); {error, beam_lib, {missing_chunk, _, "Abst"}} -> io:format("Skip ~s: missing abstract code chunk, rebuild with debug_info~n", [BeamPath]); {error, beam_lib, Reason} -> io:format("Read failed ~s: ~p~n", [BeamPath, Reason]) end. to_erl_path(BeamPath) -> Dir = filename:dirname(BeamPath), Base = filename:basename(BeamPath, ".beam"), OutDir = case filename:basename(Dir) of "ebin" -> filename:join(filename:dirname(Dir), "src"); _ -> Dir end, filename:join(OutDir, Base ++ ".erl"). format_and_write(Forms, ErlPath) -> try Strings = lists:map(fun(F) -> erl_pp:form(F) end, Forms), Content = lists:flatten([S ++ ".\n\n" || S <- Strings]), ok = filelib:ensure_dir(ErlPath), file:write_file(ErlPath, Content, [write, {encoding, utf8}]) catch _:Reason -> {error, Reason} end.