查看源代码 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/1strip_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 加密是不可行的。因此,只要密钥保持安全且无法猜测,加密的调试信息应该对入侵者是安全的。

可以通过以下两种方式提供密钥:

  1. 使用编译器选项 {debug_info_key,Key},请参阅 compile 和函数 crypto_key_fun/1 以注册一个函数,该函数在 beam_lib 必须解密调试信息时返回密钥。

如果未注册此类函数,则 beam_lib 会改为搜索 .erlang.crypt 文件,请参阅下一节。

  1. 将密钥存储在名为 .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_cbcModule 可以是原子,在这种情况下,Key 仅用于模块 Module,也可以是 [],在这种情况下,Key 用于所有模块。Key 是非空的密钥字符串。

使用 ModeModule 都匹配的第一个元组中的 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) 定义。

类型

链接到此类型

abst_code()

查看源代码 (未导出)
-type abst_code() :: {AbstVersion :: atom(), forms()} | no_abstract_code.

不检查这些形式是否符合 AbstVersion 指示的抽象格式。no_abstract_code 表示存在块 "Abst",但是为空。

对于使用 OTP 20 及更高版本编译的模块,abst_code 块会自动从 debug_info 块计算。

-type attrib_entry() :: {Attribute :: atom(), [AttributeValue :: term()]}.
-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().
链接到此类型

chunkdata()

查看源代码 (未导出)
-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"

链接到此类型

chunkname()

查看源码 (未导出)
-type chunkname() ::
          abstract_code | debug_info | attributes | compile_info | exports | labeled_exports | imports |
          indexed_imports | locals | labeled_locals | atoms | documentation.
链接到此类型

chunkref()

查看源码 (未导出)
-type chunkref() :: chunkname() | chunkid().
-type cmp_rsn() ::
          {modules_different, module(), module()} |
          {chunks_different, chunkid()} |
          different_chunks |
          info_rsn().
-type compinfo_entry() :: {InfoKey :: atom(), term()}.
链接到此类型

crypto_fun()

查看源码 (未导出)
-type crypto_fun() :: fun((crypto_fun_arg()) -> term()).
链接到此类型

crypto_fun_arg()

查看源码 (未导出)
-type crypto_fun_arg() :: init | clear | {debug_info, mode(), module(), file:filename()}.
-type dataB() :: binary().
链接到此类型

debug_info()

查看源码 (未导出)
-type debug_info() :: {DbgiVersion :: atom(), Backend :: module(), Data :: term()} | no_debug_info.

存储在 debug_info 块中的格式。

要从后端检索特定的代码表示,必须调用Backend:debug_info(Format, Module, Data, Opts)Format 是一个原子,例如用于 Erlang 抽象格式的 erlang_v1 或用于 Core Erlang 的 core_v1Module 是由 beam 文件表示的模块,Data 是存储在调试信息块中的值。OptsBackend 支持的任何值列表。Backend:debug_info/4 必须返回 {ok, Code}{error, Term}

开发人员必须始终调用 debug_info/4 函数,并且永远不要依赖存储在 debug_info 块中的 Data,因为它是不透明的,并且可能随时更改。no_debug_info 表示块 "Dbgi" 存在,但为空。

-type docs() ::
          #docs_v1{anno :: term(),
                   beam_language :: term(),
                   format :: term(),
                   module_doc :: term(),
                   metadata :: term(),
                   docs :: term()}.

EEP-48 文档格式

-type forms() :: [erl_parse:abstract_form() | erl_parse:form_info()].
-type index() :: non_neg_integer().
链接到此类型

info_rsn()

查看源码 (未导出)
-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 labeled_entry() :: {Function :: atom(), arity(), label()}.
-type mode() :: des3_cbc.

函数

链接到此函数

all_chunks(File)

查看源码 (自 OTP 18.2 起)
-spec all_chunks(beam()) -> {ok, module(), [{chunkid(), dataB()}]} | {error, beam_lib, info_rsn()}.

读取所有块的块数据。

链接到此函数

build_module(Chunks)

查看源码 (自 OTP 18.2 起)
-spec build_module(Chunks) -> {ok, Binary} when Chunks :: [{chunkid(), dataB()}], Binary :: binary().

从块列表构建 BEAM 模块(作为二进制文件)。

链接到此函数

chunks(Beam, ChunkRefs)

查看源代码
-spec chunks(Beam, ChunkRefs) -> {ok, {module(), [chunkdata()]}} | {error, beam_lib, chnk_rsn()}
                when Beam :: beam(), ChunkRefs :: [chunkref()].

读取选定块引用的块数据。返回的块数据列表的顺序由块引用列表的顺序确定。

链接到此函数

chunks(Beam, ChunkRefs, Options)

查看源代码
-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 元组。

链接到此函数

clear_crypto_key_fun()

查看源代码
-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

-spec cmp(Beam1, Beam2) -> ok | {error, beam_lib, cmp_rsn()} when Beam1 :: beam(), Beam2 :: beam().

比较两个 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) 中。

链接到此函数

crypto_key_fun(CryptoKeyFun)

查看源代码
-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_infoabstract_code)将被删除。

链接到此函数

strip(Beam1, AdditionalChunks)

查看源码 (自 OTP 22.0 起)
-spec strip(Beam1, AdditionalChunks) -> {ok, {module(), Beam2}} | {error, beam_lib, info_rsn()}
               when Beam1 :: beam(), AdditionalChunks :: [chunkid()], Beam2 :: beam().

从 BEAM 文件中删除除加载程序使用或在 AdditionalChunks 中提及的块之外的所有块。

特别是,调试信息(块 debug_infoabstract_code)将被删除。

-spec strip_files(Files) -> {ok, [{module(), Beam}]} | {error, beam_lib, info_rsn()}
                     when Files :: [beam()], Beam :: beam().

Files 中删除除加载程序使用的块之外的所有块。

特别是,调试信息(块 debug_infoabstract_code)将被删除。返回的列表包含每个指定文件名的一个元素,其顺序与 Files 中的顺序相同。

链接到此函数

strip_files(Files, AdditionalChunks)

查看源码 (自 OTP 22.0 起)
-spec strip_files(Files, AdditionalChunks) -> {ok, [{module(), Beam}]} | {error, beam_lib, info_rsn()}
                     when Files :: [beam()], AdditionalChunks :: [chunkid()], Beam :: beam().

Files 中删除除加载程序使用或在 AdditionalChunks 中提及的块之外的所有块。

特别是,调试信息(块 debug_infoabstract_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 版本。

链接到此函数

strip_release(Dir, AdditionalChunks)

查看源码 (自 OTP 22.0 起)
-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]}}