查看源代码 beam_lib (stdlib v6.2)
此模块提供 BEAM 编译器(“BEAM 文件”)创建的文件的接口。
使用的格式是“EA IFF 1985”标准交换格式文件的一种变体,将数据分为多个块。
块数据可以作为二进制数据或复合项返回。当通过名称(原子)而不是标识符(字符串)引用块时,将返回复合项。可识别的名称和相应的标识符如下所示:
atoms ("Atom")
attributes ("Attr")
compile_info ("CInf")
debug_info ("Dbgi")
exports ("ExpT")
imports ("ImpT")
indexed_imports ("ImpT")
labeled_exports ("ExpT")
labeled_locals ("LocT")
locals ("LocT")
documentation ("Docs")
调试信息/抽象代码
可以为编译器指定 debug_info
选项(请参阅 compile
),以将调试信息(例如 Erlang 抽象格式)存储在 debug_info
块中。调试器和 Xref 等工具需要包含调试信息。
警告
可以从调试信息中重建源代码。为防止这种情况,请使用加密的调试信息(见下文)。
也可以使用 strip/1
、 strip_files/1
和/或 strip_release/1
从 BEAM 文件中删除调试信息。
重建源代码
以下示例展示了如何从 BEAM 文件 Beam
中的调试信息重建 Erlang 源代码
{ok,{_,[{abstract_code,{_,AC}}]}} = beam_lib:chunks(Beam,[abstract_code]).
io:fwrite("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]).
加密的调试信息
可以加密调试信息以保持源代码的秘密,但仍然能够使用调试器或 Xref 等工具。
要使用加密的调试信息,必须向编译器和 beam_lib
提供密钥。密钥指定为字符串。建议字符串至少包含 32 个字符,并使用大小写字母以及数字和特殊字符。
加密算法的默认类型(目前也是唯一类型)是 des3_cbc
,三轮 DES。密钥字符串使用 erlang:md5/1
进行加扰,以生成用于 des3_cbc
的密钥。
注意
就我们撰写本文时的了解,在不知道密钥的情况下,破解
des3_cbc
加密是不可行的。因此,只要密钥保持安全且无法猜测,加密的调试信息应该对入侵者是安全的。
可以通过以下两种方式提供密钥:
- 使用编译器选项
{debug_info_key,Key}
,请参阅compile
和函数crypto_key_fun/1
以注册一个函数,该函数在beam_lib
必须解密调试信息时返回密钥。
如果未注册此类函数,则 beam_lib
会改为搜索 .erlang.crypt
文件,请参阅下一节。
- 将密钥存储在名为
.erlang.crypt
的文本文件中。
在这种情况下,可以使用编译器选项 encrypt_debug_info
,请参阅 compile
。
.erlang.crypt
beam_lib
在当前目录中搜索 .erlang.crypt
,然后搜索 用户的 home 目录,然后搜索 filename:basedir(user_config, "erlang")
。如果找到该文件并且包含密钥,则 beam_lib
会隐式创建一个加密密钥函数并注册它。
文件 .erlang.crypt
包含一个元组列表
{debug_info, Mode, Module, Key}
Mode
是加密算法的类型;目前,唯一允许的值是 des3_cbc
。Module
可以是原子,在这种情况下,Key
仅用于模块 Module
,也可以是 []
,在这种情况下,Key
用于所有模块。Key
是非空的密钥字符串。
使用 Mode
和 Module
都匹配的第一个元组中的 Key
。
以下是 .erlang.crypt
文件的一个示例,该文件为所有模块返回相同的密钥
[{debug_info, des3_cbc, [], "%>7}|pc/DM6Cga*68$Mw]L#&_Gejr]G^"}].
以下是 .erlang.crypt
的一个稍微复杂的示例,该示例为模块 t
提供一个密钥,为所有其他模块提供另一个密钥
[{debug_info, des3_cbc, t, "My KEY"},
{debug_info, des3_cbc, [], "%>7}|pc/DM6Cga*68$Mw]L#&_Gejr]G^"}].
注意
请勿使用这些示例中的任何密钥。请使用您自己的密钥。
摘要
类型
不检查这些形式是否符合 AbstVersion
指示的抽象格式。no_abstract_code
表示存在块 "Abst"
,但是为空。
下面描述的每个函数都接受文件名(作为字符串)或包含 BEAM 模块的二进制文件。
属性列表按 Attribute
(在 attrib_entry/0
中)排序,并且每个属性名称在列表中出现一次。属性值的顺序与文件中的顺序相同。函数列表也已排序。
"Attr" | "CInf" | "Dbgi" | "ExpT" | "ImpT" | "LocT" | "AtU8" | "Docs"
存储在 debug_info
块中的格式。
函数
读取所有块的块数据。
从块列表构建 BEAM 模块(作为二进制文件)。
读取选定块引用的块数据。返回的块数据列表的顺序由块引用列表的顺序确定。
读取选定块引用的块数据。返回的块数据列表的顺序由块引用列表的顺序确定。
取消注册加密密钥函数,并终止持有它的进程,该进程由 crypto_key_fun/1
启动。
比较两个 BEAM 文件的内容。
比较两个目录中的 BEAM 文件。
注册一个一元函数,如果 beam_lib
必须读取已加密的 debug_info
块,则会调用该函数。该函数保存在由该函数启动的进程中。
像 cmp_dirs/2
一样比较两个目录中的 BEAM 文件,但是仅在一个目录中存在或不同的文件的名称会显示在标准输出上。
对于此模块中任何函数返回的指定错误,此函数返回用英语描述该错误的字符串。对于文件错误,应调用函数 file:format_error(Posix)
。
以元组 {Item, Info}
的形式返回包含有关 BEAM 文件的一些信息的列表
计算模块代码的 MD5 冗余校验(不包括编译日期和其他属性)。
从 BEAM 文件中删除除加载程序使用的块之外的所有块。
从 BEAM 文件中删除除加载程序使用或在 AdditionalChunks
中提及的块之外的所有块。
从 Files
中删除除加载程序使用的块之外的所有块。
从 Files
中删除除加载程序使用或在 AdditionalChunks
中提及的块之外的所有块。
从发布的 BEAM 文件中删除除加载程序使用的块之外的所有块。
删除除了加载程序使用或 AdditionalChunks
中提到的块之外的所有块。
返回模块版本。版本由模块属性 -vsn(Vsn)
定义。
类型
不检查这些形式是否符合 AbstVersion
指示的抽象格式。no_abstract_code
表示存在块 "Abst"
,但是为空。
对于使用 OTP 20 及更高版本编译的模块,abst_code
块会自动从 debug_info
块计算。
-type beam() :: file:filename() | binary().
下面描述的每个函数都接受文件名(作为字符串)或包含 BEAM 模块的二进制文件。
-type chnk_rsn() :: {unknown_chunk, file:filename(), atom()} | {key_missing_or_invalid, file:filename(), abstract_code | debug_info} | {missing_backend, file:filename(), module()} | info_rsn().
-type chunkdata() :: {chunkid(), dataB()} | {abstract_code, abst_code()} | {debug_info, debug_info()} | {attributes, [attrib_entry()]} | {compile_info, [compinfo_entry()]} | {exports, [{atom(), arity()}]} | {labeled_exports, [labeled_entry()]} | {imports, [mfa()]} | {indexed_imports, [{index(), module(), Function :: atom(), arity()}]} | {locals, [{atom(), arity()}]} | {labeled_locals, [labeled_entry()]} | {atoms, [{integer(), atom()}]} | {documentation, docs()}.
属性列表按 Attribute
(在 attrib_entry/0
中)排序,并且每个属性名称在列表中出现一次。属性值的顺序与文件中的顺序相同。函数列表也已排序。
-type chunkid() :: nonempty_string().
"Attr" | "CInf" | "Dbgi" | "ExpT" | "ImpT" | "LocT" | "AtU8" | "Docs"
-type chunkname() ::
abstract_code | debug_info | attributes | compile_info | exports | labeled_exports | imports |
indexed_imports | locals | labeled_locals | atoms | documentation.
-type crypto_fun() :: fun((crypto_fun_arg()) -> term()).
-type crypto_fun_arg() :: init | clear | {debug_info, mode(), module(), file:filename()}.
-type dataB() :: binary().
存储在 debug_info
块中的格式。
要从后端检索特定的代码表示,必须调用Backend:debug_info(Format, Module, Data, Opts)
。Format
是一个原子,例如用于 Erlang 抽象格式的 erlang_v1
或用于 Core Erlang 的 core_v1
。Module
是由 beam 文件表示的模块,Data
是存储在调试信息块中的值。Opts
是 Backend
支持的任何值列表。Backend:debug_info/4
必须返回 {ok, Code}
或 {error, Term}
。
开发人员必须始终调用 debug_info/4
函数,并且永远不要依赖存储在 debug_info
块中的 Data
,因为它是不透明的,并且可能随时更改。no_debug_info
表示块 "Dbgi"
存在,但为空。
-type forms() :: [erl_parse:abstract_form() | erl_parse:form_info()].
-type index() :: non_neg_integer().
-type info_rsn() :: {chunk_too_big, file:filename(), chunkid(), ChunkSize :: non_neg_integer(), FileSize :: non_neg_integer()} | {invalid_beam_file, file:filename(), Position :: non_neg_integer()} | {invalid_chunk, file:filename(), chunkid()} | {missing_chunk, file:filename(), chunkid()} | {not_a_beam_file, file:filename()} | {file_error, file:filename(), file:posix()}.
-type label() :: integer().
-type mode() :: des3_cbc.
函数
读取所有块的块数据。
-spec build_module(Chunks) -> {ok, Binary} when Chunks :: [{chunkid(), dataB()}], Binary :: binary().
从块列表构建 BEAM 模块(作为二进制文件)。
-spec chunks(Beam, ChunkRefs) -> {ok, {module(), [chunkdata()]}} | {error, beam_lib, chnk_rsn()} when Beam :: beam(), ChunkRefs :: [chunkref()].
读取选定块引用的块数据。返回的块数据列表的顺序由块引用列表的顺序确定。
-spec chunks(Beam, ChunkRefs, Options) -> {ok, {module(), [ChunkResult]}} | {error, beam_lib, chnk_rsn()} when Beam :: beam(), ChunkRefs :: [chunkref()], Options :: [allow_missing_chunks], ChunkResult :: chunkdata() | {ChunkRef :: chunkref(), missing_chunk}.
读取选定块引用的块数据。返回的块数据列表的顺序由块引用列表的顺序确定。
默认情况下,如果 Beam
中缺少任何请求的块,则返回一个 error
元组。但是,如果指定了选项 allow_missing_chunks
,即使缺少块也会返回结果。在结果列表中,任何缺少的块都表示为 {ChunkRef,missing_chunk}
。但是请注意,如果缺少块 "Atom"
,则会被视为致命错误,并且返回值是一个 error
元组。
-spec clear_crypto_key_fun() -> undefined | {ok, Result} when Result :: undefined | term().
取消注册加密密钥函数,并终止持有它的进程,该进程由 crypto_key_fun/1
启动。
如果没有注册加密密钥函数,则返回 {ok, undefined}
,否则返回 {ok, Term}
,其中 Term
是来自 CryptoKeyFun(clear)
的返回值,请参见 crypto_key_fun/1
。
比较两个 BEAM 文件的内容。
如果模块名称相同,并且除了块 "CInf"
(包含由 Module:module_info(compile)
返回的编译信息的块)之外的所有块在两个文件中都具有相同的内容,则返回 ok
。否则,返回错误消息。
-spec cmp_dirs(Dir1, Dir2) -> {Only1, Only2, Different} | {error, beam_lib, Reason} when Dir1 :: atom() | file:filename(), Dir2 :: atom() | file:filename(), Only1 :: [file:filename()], Only2 :: [file:filename()], Different :: [{Filename1 :: file:filename(), Filename2 :: file:filename()}], Reason :: {not_a_directory, term()} | info_rsn().
比较两个目录中的 BEAM 文件。
仅比较扩展名为 ".beam"
的文件。仅存在于目录 Dir1
(Dir2
) 中的 BEAM 文件在 Only1
(Only2
) 中返回。存在于两个目录中,但被 cmp/2
视为不同的 BEAM 文件作为对 {Filename1, Filename2}
返回,其中 Filename1
(Filename2
) 存在于目录 Dir1
(Dir2
) 中。
-spec crypto_key_fun(CryptoKeyFun) -> ok | {error, Reason} when CryptoKeyFun :: crypto_fun(), Reason :: badfun | exists | term().
注册一个一元函数,如果 beam_lib
必须读取已加密的 debug_info
块,则会调用该函数。该函数保存在由该函数启动的进程中。
如果在尝试注册函数时已注册函数,则返回 {error, exists}
。
该函数必须处理以下参数
CryptoKeyFun(init) -> ok | {ok, NewCryptoKeyFun} | {error, Term}
当该函数在持有该函数的进程中注册时调用。在这里,加密密钥函数可以执行任何必要的初始化。如果返回 {ok, NewCryptoKeyFun}
,则注册 NewCryptoKeyFun
而不是 CryptoKeyFun
。如果返回 {error, Term}
,则中止注册,并且 crypto_key_fun/1
也会返回 {error, Term}
。
CryptoKeyFun({debug_info, Mode, Module, Filename}) -> Key
当在名为 Filename
的文件中,模块 Module
需要密钥时调用。Mode
是加密算法的类型;目前,唯一可能的值是 des3_cbc
。如果没有密钥可用,则调用将失败(引发异常)。
CryptoKeyFun(clear) -> term()
在取消注册函数之前调用。这里可以完成任何清理工作。返回值并不重要,但是作为其返回值的一部分,将传递回 clear_crypto_key_fun/0
的调用者。
-spec diff_dirs(Dir1, Dir2) -> ok | {error, beam_lib, Reason} when Dir1 :: atom() | file:filename(), Dir2 :: atom() | file:filename(), Reason :: {not_a_directory, term()} | info_rsn().
像 cmp_dirs/2
一样比较两个目录中的 BEAM 文件,但是仅在一个目录中存在或不同的文件的名称会显示在标准输出上。
-spec format_error(Reason) -> io_lib:chars() when Reason :: term().
对于此模块中任何函数返回的指定错误,此函数返回用英语描述该错误的字符串。对于文件错误,应调用函数 file:format_error(Posix)
。
-spec info(Beam) -> [InfoPair] | {error, beam_lib, info_rsn()} when Beam :: beam(), InfoPair :: {file, Filename :: file:filename()} | {binary, Binary :: binary()} | {module, Module :: module()} | {chunks, [{ChunkId :: chunkid(), Pos :: non_neg_integer(), Size :: non_neg_integer()}]}.
以元组 {Item, Info}
的形式返回包含有关 BEAM 文件的一些信息的列表
{file, Filename} | {binary, Binary}
- BEAM 文件的名称(字符串)或从中提取信息的二进制文件。{module, Module}
- 模块的名称(原子)。{chunks, [{ChunkId, Pos, Size}]}
- 对于每个块,标识符(字符串)以及块数据的位置和大小(以字节为单位)。
-spec md5(Beam) -> {ok, {module(), MD5}} | {error, beam_lib, chnk_rsn()} when Beam :: beam(), MD5 :: binary().
计算模块代码的 MD5 冗余校验(不包括编译日期和其他属性)。
-spec strip(Beam1) -> {ok, {module(), Beam2}} | {error, beam_lib, info_rsn()} when Beam1 :: beam(), Beam2 :: beam().
从 BEAM 文件中删除除加载程序使用的块之外的所有块。
特别是,调试信息(块 debug_info
和 abstract_code
)将被删除。
-spec strip(Beam1, AdditionalChunks) -> {ok, {module(), Beam2}} | {error, beam_lib, info_rsn()} when Beam1 :: beam(), AdditionalChunks :: [chunkid()], Beam2 :: beam().
从 BEAM 文件中删除除加载程序使用或在 AdditionalChunks
中提及的块之外的所有块。
特别是,调试信息(块 debug_info
和 abstract_code
)将被删除。
-spec strip_files(Files) -> {ok, [{module(), Beam}]} | {error, beam_lib, info_rsn()} when Files :: [beam()], Beam :: beam().
从 Files
中删除除加载程序使用的块之外的所有块。
特别是,调试信息(块 debug_info
和 abstract_code
)将被删除。返回的列表包含每个指定文件名的一个元素,其顺序与 Files
中的顺序相同。
-spec strip_files(Files, AdditionalChunks) -> {ok, [{module(), Beam}]} | {error, beam_lib, info_rsn()} when Files :: [beam()], AdditionalChunks :: [chunkid()], Beam :: beam().
从 Files
中删除除加载程序使用或在 AdditionalChunks
中提及的块之外的所有块。
特别是,调试信息(块 debug_info
和 abstract_code
)将被删除。返回的列表包含每个指定文件名的一个元素,其顺序与 Files
中的顺序相同。
-spec strip_release(Dir) -> {ok, [{module(), file:filename()}]} | {error, beam_lib, Reason} when Dir :: atom() | file:filename(), Reason :: {not_a_directory, term()} | info_rsn().
从发布的 BEAM 文件中删除除加载程序使用的块之外的所有块。
Dir
应该是安装根目录。例如,可以使用调用 beam_lib:strip_release(code:root_dir())
来剥离当前的 OTP 版本。
-spec strip_release(Dir, AdditionalChunks) -> {ok, [{module(), file:filename()}]} | {error, beam_lib, Reason} when Dir :: atom() | file:filename(), AdditionalChunks :: [chunkid()], Reason :: {not_a_directory, term()} | info_rsn().
删除除了加载程序使用或 AdditionalChunks
中提到的块之外的所有块。
Dir
应该是安装根目录。例如,可以使用调用 beam_lib:strip_release(code:root_dir(),[documentation])
来剥离当前的 OTP 版本。
-spec version(Beam) -> {ok, {module(), [Version :: term()]}} | {error, beam_lib, chnk_rsn()} when Beam :: beam().
返回模块版本。版本由模块属性 -vsn(Vsn)
定义。
如果未指定此属性,则版本默认为模块的校验和。请注意,如果版本 Vsn
不是列表,则将其转换为列表,即返回 {ok,{Module,[Vsn]}}
。如果有多个 -vsn
模块属性,则结果是串联的版本列表。
示例
1> beam_lib:version(a). % -vsn(1).
{ok,{a,[1]}}
2> beam_lib:version(b). % -vsn([1]).
{ok,{b,[1]}}
3> beam_lib:version(c). % -vsn([1]). -vsn(2).
{ok,{c,[1,2]}}
4> beam_lib:version(d). % no -vsn attribute
{ok,{d,[275613208176997377698094100858909383631]}}