| <html devsite><head> |
| <title>ABI 稳定性</title> |
| <meta name="project_path" value="/_project.yaml"/> |
| <meta name="book_path" value="/_book.yaml"/> |
| </head> |
| <body> |
| |
| <!-- |
| Copyright 2018 The Android Open Source Project |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| --> |
| |
| <p> |
| 应用二进制接口 (ABI) 稳定性是进行仅针对框架的更新的前提条件,因为供应商模块可能依赖于系统分区中的供应商原生开发套件 (VNDK) 共享库。新编译的 VNDK 共享库必须与之前发布的 VNDK 共享库保持 ABI 兼容性,以便供应商模块可以与这些库协同工作,而无需重新编译,也不会出现运行时错误。 |
| </p> |
| |
| <p> |
| 为了确保实现 ABI 兼容性,Android 9 中添加了一个标头 ABI 检查工具,下文会对该工具进行介绍。 |
| |
| </p><h2 id="about-vndk-abi-compliance">关于 VNDK 和 ABI 合规性</h2> |
| |
| <p> |
| VNDK 是供应商模块可以关联到的一组受限库,用于实现仅针对框架的更新。ABI 合规性是指较新版本的共享库能够按预期与动态关联到它的模块协同工作(即像较旧版本的共享库那样正常工作)。<em></em> |
| </p> |
| |
| <h3 id="about-exported-symbols">关于导出的符号</h3> |
| |
| <p> |
| 导出的符号(也称为全局符号)是指满足以下所有条件的符号: |
| <em></em><em></em></p> |
| |
| <ul> |
| <li>通过共享库的公开标头导出。<em></em></li> |
| <li>显示在与共享库对应的 <code>.so</code> 文件的 <code>.dynsym</code> 表中。</li> |
| <li>具有 WEAK 或 GLOBAL 绑定。</li> |
| <li>可见性为 DEFAULT 或 PROTECTED。</li> |
| <li>区块索引不是 UNDEFINED。</li> |
| <li>类型为 FUNC 或 OBJECT。</li> |
| </ul> |
| |
| <p> |
| 共享库的公开标头是指通过以下属性提供给其他库/二进制文件使用的标头<em></em>:与共享库对应的模块的 <code>Android.bp</code> 定义中的 <code>export_include_dirs</code>、<code>export_header_lib_headers</code>、<code>export_static_lib_headers</code>、<code>export_shared_lib_headers</code> 和 <code>export_generated_headers</code> 属性。 |
| </p> |
| |
| <h3 id="about-reachable-types">关于可到达类型</h3> |
| |
| <p> |
| 可到达类型是指可通过导出的符号直接或间接到达并且是通过公开标头导出的任何 C/C++ 内置类型或用户定义的类型。<em></em>例如,<code>libfoo.so</code> 具有函数 <code>Foo</code>,该函数是一个导出的符合,可在 <code>.dynsym</code> 表中找到。<code>libfoo.so</code> 库包含以下内容: |
| </p> |
| |
| <table> |
| <tbody><tr> |
| <th>foo_exported.h</th> |
| <th>foo.private.h</th> |
| </tr> |
| <tr> |
| <td> |
| <pre class="prettyprint"> |
| |
| typedef struct foo_private foo_private_t; |
| |
| typedef struct foo { |
| int m1; |
| int *m2; |
| foo_private_t *mPfoo; |
| } foo_t; |
| |
| typedef struct bar { |
| foo_t mfoo; |
| } bar_t; |
| |
| bool Foo(int id, bar_t *bar_ptr); |
| </pre> |
| </td> |
| |
| <td> |
| <pre class="prettyprint"> |
| |
| typedef struct foo_private { |
| int m1; |
| float mbar; |
| } foo_private_t; |
| </pre> |
| </td> |
| </tr> |
| </tbody></table> |
| |
| <table> |
| <tbody><tr> |
| <th>Android.bp</th> |
| </tr> |
| <tr> |
| <td> |
| <pre class="prettyprint"> |
| |
| cc_library { |
| name : libfoo, |
| vendor_available: true, |
| vndk { |
| enabled : true, |
| } |
| srcs : ["src/*.cpp"], |
| export_include_dirs : [ |
| "include" |
| ], |
| } |
| </pre> |
| </td> |
| </tr> |
| </tbody></table> |
| |
| <table> |
| <tbody><tr> |
| <th colspan="8">.dynsym 表</th> |
| </tr> |
| <tr> |
| <td><code>Num</code> |
| </td> |
| <td><code>Value</code> |
| </td> |
| <td><code>Size</code> |
| </td> |
| <td><code>Type</code> |
| </td> |
| <td><code>Bind</code> |
| </td> |
| <td><code>Vis</code> |
| </td> |
| <td><code>Ndx</code> |
| </td> |
| <td><code>Name</code> |
| </td> |
| </tr> |
| <tr> |
| <td><code>1</code> |
| </td> |
| <td><code>0</code> |
| </td> |
| <td><code>0</code> |
| </td> |
| <td><code>FUNC</code> |
| </td> |
| <td><code>GLOB</code> |
| </td> |
| <td><code>DEF</code> |
| </td> |
| <td><code>UND</code> |
| </td> |
| <td><code>dlerror@libc</code> |
| </td> |
| </tr> |
| <tr> |
| <td><code>2</code> |
| </td> |
| <td><code>1ce0</code> |
| </td> |
| <td><code>20</code> |
| </td> |
| <td><code>FUNC</code> |
| </td> |
| <td><code>GLOB</code> |
| </td> |
| <td><code>DEF</code> |
| </td> |
| <td><code>12</code> |
| </td> |
| <td><code>Foo</code> |
| </td> |
| </tr> |
| </tbody></table> |
| |
| <p> |
| 以 <code>Foo</code> 为例,直接/间接可到达类型包括: |
| </p> |
| |
| <table> |
| <tbody><tr> |
| <th>类型</th> |
| <th>说明</th> |
| </tr> |
| <tr> |
| <td><code>bool</code> |
| </td> |
| <td><code>Foo</code> 的返回值类型。 |
| </td> |
| </tr> |
| <tr> |
| <td><code>int</code> |
| </td> |
| <td>第一个 <code>Foo</code> 参数的类型。 |
| </td> |
| </tr> |
| <tr> |
| <td><code>bar_t *</code> |
| </td> |
| <td>第二个 Foo 参数的类型。<code>bar_t</code> 是经由 <code>bar_t *</code> 通过 <code>foo_exported.h</code> 导出的。 |
| <br /><br /> |
| <code>bar_t</code> 包含类型 <code>foo_t</code>(通过 <code>foo_exported.h</code> 导出)的一个成员 <code>mfoo</code>,这会导致导出更多类型: |
| |
| <ul> |
| <li><code>int :</code> 是 <code>m1</code> 的类型。</li> |
| <li><code>int * :</code> 是 <code>m2</code> 的类型。</li> |
| <li><code>foo_private_t * : </code>是 <code>mPfoo</code> 的类型。</li> |
| </ul> |
| <br /> |
| 不过,<code>foo_private_t</code> 不是可到达类型,因为它不是通过 <code>foo_exported.h</code> 导出的。(<code>foot_private_t *</code> 不透明,因此系统允许对 <code>foo_private_t</code> 进行更改)。 |
| </td> |
| </tr> |
| </tbody></table> |
| |
| <p> |
| 对于可通过基类指定符和模板参数到达的类型,也可给出类似解释。 |
| </p> |
| |
| <h2 id="ensuring-abi-compliance">确保 ABI 合规性</h2> |
| |
| <p> |
| 对于在对应的 <code>Android.bp</code> 文件中标有 <code>vendor_available: true</code> 和 <code>vndk.enabled: true</code> 的库,必须确保其 ABI 合规性。例如: |
| </p> |
| |
| <pre class="prettyprint"> |
| cc_library { |
| name: "libvndk_example", |
| vendor_available: true, |
| vndk: { |
| enabled: true, |
| } |
| } |
| </pre> |
| |
| <p> |
| 对于可通过导出的函数直接或间接到达的数据类型,对库进行以下更改会破坏 ABI 合规性: |
| </p> |
| |
| <table> |
| <tbody><tr> |
| <th>数据类型</th> |
| <th>说明</th> |
| </tr> |
| <tr> |
| <td>结构和类</td> |
| <td> |
| <ul> |
| <li>移除非静态数据成员。</li> |
| <li>会导致类/结构体大小发生变化的更改。</li> |
| <li>会导致虚表布局发生变化的更改。</li> |
| <li>添加/移除基类。</li> |
| <li>更改基类的顺序。</li> |
| <li>更改模板参数。</li> |
| <li>会导致数据成员的内存偏移发生变化的更改<sup>**</sup>。</li> |
| <li>更改成员的 const-volatile-restricted 限定符<sup>*</sup>。</li> |
| <li>对数据成员的访问权限指定符进行降级<sup>*</sup>。</li> |
| </ul> |
| </td> |
| </tr> |
| <tr> |
| <td>联合</td> |
| <td> |
| <ul> |
| <li>添加/移除字段。</li> |
| <li>会导致大小发生变化的更改。</li> |
| <li>更改字段顺序。</li> |
| <li>更改字段类型。</li> |
| </ul> |
| </td> |
| </tr> |
| <tr> |
| <td>枚举</td> |
| <td> |
| <ul> |
| <li>更改成员的值。</li> |
| <li>更改成员的名称。</li> |
| <li>更改基础类型。</li> |
| </ul> |
| </td> |
| </tr> |
| <tr> |
| <td>全局符号</td> |
| <td> |
| <ul> |
| <li>移除通过公开标头导出的符号。</li> |
| <li>对于类型 FUNC 的全局符号 |
| <ul> |
| <li>添加/移除参数。</li> |
| <li>以任何方式更改任何参数的类型。</li> |
| <li>以任何方式更改返回值类型。</li> |
| <li>对访问权限指定符进行降级<sup>*</sup>。</li> |
| </ul> |
| </li> |
| <li>对于类型 OBJECT 的全局符号 |
| <ul> |
| <li>以任何方式更改相应的 C/C++ 类型。</li> |
| <li>对访问权限指定符进行降级<sup>*</sup>。</li> |
| </ul> |
| </li> |
| </ul> |
| </td> |
| </tr> |
| </tbody></table> |
| |
| <p> |
| <strong><sup>**</sup></strong>不限于对公开字段的偏移进行更改(因为内联函数可以在内部使用不公开字段)。 |
| </p> |
| |
| <p> |
| <strong><sup>*</sup></strong>虽然这些并不代表对类型的内存布局进行更改,但它们是语义差异,可能导致库无法按预期正常运行。 |
| </p> |
| |
| <h2 id="using-abi-compliance-tools">使用 ABI 合规性工具</h2> |
| |
| <p> |
| 编译 VNDK 库时,系统会将其 ABI 与所编译 VNDK 的版本对应的 ABI 参考进行比较。参考 ABI 转储位于以下位置: |
| </p> |
| |
| <pre class="prettyprint"> |
| ${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/(v)ndk/<${PLATFORM_VNDK_VERSION}>/<BINDER_BITNESS>/<ARCH_ARCH-VARIANT>/source-based |
| </pre> |
| |
| <p> |
| 例如,在为 VNDK 的 API 级别 27 编译 <code>libfoo</code> 时,系统会将 <code>libfoo</code> 的推断 ABI 与其参考进行比较,该参考位于以下位置:</p> |
| |
| <pre class="prettyprint"> |
| ${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/(v)ndk/27/64/<ARCH_ARCH-VARIANT>/source-based/libfoo.so.lsdump |
| </pre> |
| |
| <h3 id="abit-breakage-error">ABI 损坏错误</h3> |
| |
| <p> |
| 当 ABI 损坏时,编译日志会显示警告,其中包含警告类型以及 abi-diff 报告所在的路径。例如,如果 <code>libbinder</code> 的 ABI 有不兼容的更改,则编译系统会抛出错误,并显示类似以下的消息: |
| </p> |
| |
| <pre> |
| ***************************************************** |
| error: VNDK library: libbinder.so's ABI has INCOMPATIBLE CHANGES |
| Please check compatibility report at: |
| out/soong/.intermediates/frameworks/native/libs/binder/libbinder/android_arm64_armv8-a_cortex-a73_vendor_shared/libbinder.so.abidiff |
| ****************************************************** |
| ---- Please update abi references by running |
| platform/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libbinder ---- |
| </pre> |
| |
| <h3 id="building-vndk-lib-abi-checks">编译 VNDK 库时进行的 ABI 检查</h3> |
| |
| <p> |
| 编译 VNDK 库时: |
| </p> |
| |
| <ol> |
| <li><code>header-abi-dumper</code> 会处理为了编译 VNDK 库(库本身的源文件以及通过静态传递依赖项沿用的源文件)而编译的源文件,以生成与各个源文件对应的 <code>.sdump</code> 文件。 |
| <br /> |
| <img src="../images/abi_check_sdump.png" alt="创建 sdump" title="创建 sdump"/> |
| <figcaption><strong>图 1.</strong> 创建 <code>.sdump</code> 文件</figcaption> |
| </li> |
| |
| <li>然后,<code>header-abi-linker</code> 会处理 <code>.sdump</code> 文件(使用提供给它的版本脚本或与共享库对应的 <code>.so</code> 文件),以生成 <code>.lsdump</code> 文件,该文件用于记录与共享库对应的所有 ABI 信息。 |
| <br /> |
| <img src="../images/abi_check_lsdump.png" alt="创建 lsdump" title="创建 lsdump"/> |
| <figcaption><strong>图 2.</strong> 创建 <code>.lsdump</code> 文件</figcaption> |
| </li> |
| |
| <li><code>header-abi-diff</code> 会将 <code>.lsdump</code> 文件与参考 <code>.lsdump</code> 文件进行比较,以生成差异报告,该报告中会简要说明两个库的 ABI 之间存在的差异。 |
| <br /> |
| <img src="../images/abi_check_abidiff.png" alt="创建 abi diff" title="创建 abi diff"/> |
| <figcaption><strong>图 3.</strong> 创建差异报告</figcaption> |
| </li> |
| </ol> |
| |
| <h3 id="header-abi-dumper">header-abi-dumper</h3> |
| |
| <p> |
| <code>header-abi-dumper</code> 工具会解析 C/C++ 源文件,并将从该源文件推断出的 ABI 转储到一个中间文件。编译系统会对所有已编译的源文件运行 <code>header-abi-dumper</code>,同时还会建立一个库,其中包含来自传递依赖项的源文件。 |
| </p> |
| |
| <p> |
| 目前,<code>.sdump</code> 文件采用 <a href="https://developers.google.com/protocol-buffers/docs/reference/java/com/google/protobuf/TextFormat" class="external">Protobuf TextFormatted</a> 格式,我们无法保证该格式在未来版本中仍保持稳定。因此,<code>.sdump</code> 文件格式化应被视为编译系统的实现细节。 |
| </p> |
| |
| <p> |
| 例如,<code>libfoo.so</code> 具有以下源文件 <strong><code>foo.cpp</code></strong>: |
| </p> |
| |
| <pre class="prettyprint"> |
| #include <stdio.h> |
| #include <foo_exported.h> |
| |
| bool Foo(int id, bar_t *bar_ptr) { |
| if (id > 0 && bar_ptr->mfoo.m1 > 0) { |
| return true; |
| } |
| return false; |
| }</pre> |
| |
| <p> |
| 您可以使用 <code>header-abi-dumper</code> 生成中间 <code>.sdump</code> 文件,该文件代表源文件使用以下命令提供的 ABI: |
| </p> |
| |
| <pre class="prettyprint"> |
| $ header-abi-dumper foo.cpp -I exported -o foo.sdump -- -x c++ |
| </pre> |
| |
| <p> |
| 该命令指示 <code>header-abi-dumper</code> 解析 <code>foo.cpp</code> 并发出 ABI 信息(显示在 <code>exported</code> 目录内的公开标头中)。下面是 <code>header-abi-dumper</code> 生成的 <strong><code>foo.sdump</code></strong> 中的一部分(并非完整内容): |
| </p> |
| |
| <pre class="prettyprint"> |
| record_types { |
| type_info { |
| name: "foo" |
| size: 12 |
| alignment: 4 |
| referenced_type: "type-1" |
| source_file: "foo/include/foo_exported.h" |
| linker_set_key: "foo" |
| self_type: "type-1" |
| } |
| fields { |
| referenced_type: "type-2" |
| field_offset: 0 |
| field_name: "m1" |
| access: public_access |
| } |
| fields { |
| referenced_type: "type-3" |
| field_offset: 32 |
| field_name: "m2" |
| access: public_access |
| } |
| fields { |
| referenced_type: "type-5" |
| field_offset: 64 |
| field_name: "mPfoo" |
| access: public_access |
| } |
| access: public_access |
| record_kind: struct_kind |
| tag_info { |
| unique_id: "_ZTS3foo" |
| } |
| } |
| record_types { |
| type_info { |
| name: "bar" |
| size: 12 |
| alignment: 4 |
| referenced_type: "type-6" |
| … |
| pointer_types { |
| type_info { |
| name: "bar *" |
| size: 4 |
| alignment: 4 |
| referenced_type: "type-6" |
| source_file: "foo/include/foo_exported.h" |
| linker_set_key: "bar *" |
| self_type: "type-8" |
| } |
| } |
| builtin_types { |
| type_info { |
| name: "int" |
| size: 4 |
| alignment: 4 |
| referenced_type: "type-2" |
| source_file: "" |
| linker_set_key: "int" |
| self_type: "type-2" |
| } |
| is_unsigned: false |
| is_integral: true |
| } |
| functions { |
| return_type: "type-7" |
| function_name: "Foo" |
| source_file: "foo/include/foo_exported.h" |
| parameters { |
| referenced_type: "type-2" |
| default_arg: false |
| } |
| parameters { |
| referenced_type: "type-8" |
| default_arg: false |
| } |
| linker_set_key: "_Z3FooiP3bar" |
| access: public_access |
| } |
| </pre> |
| |
| <p> |
| <code>foo.sdump</code> 包含源文件 <code>foo.cpp</code> 提供的 ABI 信息,例如: |
| </p> |
| |
| <ul> |
| <li><code>record_types</code>:指通过公开标头提供的结构、联合或类。每个记录类型都包含其字段、大小、访问权限指定符、所在标头文件等相关信息。</li> |
| <li><code>pointer_types</code>:指通过公开标头提供的记录/函数直接/间接引用的指针类型,以及指针指向的类型(通过 <code>type_info</code> 中的 <code>referenced_type</code> 字段)。对于限定类型、内置 C/C++ 类型、数组类型以及左值和右值参考类型(有关类型的此类记录信息允许递归差异),系统会在 <code>.sdump</code> 文件中记录类似信息。</li> |
| <li><code>functions</code>:表示通过公开标头提供的函数。它们还包含函数的重整名称、返回值类型、参数类型、访问权限指定符等相关信息。</li> |
| </ul> |
| |
| <aside class="tip"> |
| <strong>提示</strong>:要获取 <code>header-abi-dumper</code> 工具方面的帮助,请运行 <code>header-abi-dumper --help</code>。 |
| </aside> |
| |
| <h3 id="header-abi-linker">header-abi-linker</h3> |
| |
| <p> |
| <code>header-abi-linker</code> 工具会将 <code>header-abi-dumper</code> 生成的中间文件作为输入,然后关联以下文件: |
| </p> |
| |
| <table> |
| <tbody><tr> |
| <th>输入</th> |
| <td> |
| <ul> |
| <li><code>header-abi-dumper</code> 生成的中间文件</li> |
| <li>版本脚本/映射文件(可选)</li> |
| <li>共享库的 .so 文件</li> |
| </ul> |
| </td> |
| </tr> |
| <tr> |
| <th>输出</th> |
| <td>用于记录共享库 ABI 的文件(例如,<code>libfoo.so.lsdump </code>表示 <code>libfoo</code> 的 ABI)。 |
| </td> |
| </tr> |
| </tbody></table> |
| |
| <p> |
| 该工具会将收到的所有中间文件中的类型图合并在一起,并会将不同转换单元之间的单一定义(完全限定名称相同的不同转换单元中由用户定义的类型可能在语义上有所不同)差异考虑在内。然后,该工具会解析版本脚本或解析共享库的 <code>.dynsym</code> 表(<code>.so</code> 文件),以创建导出符号列表。 |
| </p> |
| |
| <p> |
| 例如,当 <code>libfoo</code> 将 <code>bar.cpp</code> 文件(用于提供 C 函数 <code>bar</code>)添加到其编译时,系统可能会调用 <code>header-abi-linker</code>,以创建 <code>libfoo</code> 的完整关联 ABI 转储,如下所示: |
| </p> |
| |
| <pre class="prettyprint"> |
| header-abi-linker -I exported foo.sdump bar.sdump \ |
| -o libfoo.so.lsdump \ |
| -so libfoo.so \ |
| -arch arm64 -api current |
| </pre> |
| |
| <p> |
| <strong><code>libfoo.so.lsdump</code></strong> 中的命令输出示例: |
| </p> |
| |
| <pre class="prettyprint"> |
| record_types { |
| type_info { |
| name: "foo" |
| size: 24 |
| alignment: 8 |
| referenced_type: "type-1" |
| source_file: "foo/include/foo_exported.h" |
| linker_set_key: "foo" |
| self_type: "type-1" |
| } |
| fields { |
| referenced_type: "type-2" |
| field_offset: 0 |
| field_name: "m1" |
| access: public_access |
| } |
| fields { |
| referenced_type: "type-3" |
| field_offset: 64 |
| field_name: "m2" |
| access: public_access |
| } |
| fields { |
| referenced_type: "type-4" |
| field_offset: 128 |
| field_name: "mPfoo" |
| access: public_access |
| } |
| access: public_access |
| record_kind: struct_kind |
| tag_info { |
| unique_id: "_ZTS3foo" |
| } |
| } |
| record_types { |
| type_info { |
| name: "bar" |
| size: 24 |
| alignment: 8 |
| ... |
| builtin_types { |
| type_info { |
| name: "void" |
| size: 0 |
| alignment: 0 |
| referenced_type: "type-6" |
| source_file: "" |
| linker_set_key: "void" |
| self_type: "type-6" |
| } |
| is_unsigned: false |
| is_integral: false |
| } |
| functions { |
| return_type: "type-19" |
| function_name: "Foo" |
| source_file: "foo/include/foo_exported.h" |
| parameters { |
| referenced_type: "type-2" |
| default_arg: false |
| } |
| parameters { |
| referenced_type: "type-20" |
| default_arg: false |
| } |
| linker_set_key: "_Z3FooiP3bar" |
| access: public_access |
| } |
| functions { |
| return_type: "type-6" |
| function_name: "FooBad" |
| source_file: "foo/include/foo_exported_bad.h" |
| parameters { |
| referenced_type: "type-2" |
| default_arg: false |
| } |
| parameters { |
| referenced_type: "type-7" |
| default_arg: false |
| } |
| linker_set_key: "_Z6FooBadiP3foo" |
| access: public_access |
| } |
| elf_functions { |
| name: "_Z3FooiP3bar" |
| } |
| elf_functions { |
| name: "_Z6FooBadiP3foo" |
| } |
| </pre> |
| |
| <p> |
| <code>header-abi-linker</code> 工具将执行以下操作: |
| </p> |
| |
| <ul> |
| <li>关联收到的 <code>.sdump</code> 文件(<code>foo.sdump</code> 和 <code>bar.sdump</code>),滤除位于 <code>exported</code> 目录的标头中不存在的 ABI 信息。</li> |
| <li>解析 <code>libfoo.so</code>,然后通过其 <code>.dynsym</code> 表收集通过库导出的符号的相关信息。</li> |
| <li>添加 <code>_Z3FooiP3bar</code> 和 <code>Bar</code>。</li> |
| </ul> |
| |
| <p> |
| <code>libfoo.so.lsdump</code> 是最终生成的 <code>libfoo.so</code> ABI 转储。 |
| </p> |
| |
| <aside class="tip"><strong>提示</strong>:要获取 <code>header-abi-linker</code> 工具方面的帮助,请运行 <code>header-abi-linker --help</code>。 |
| </aside> |
| |
| <h3 id="header-abi-diff">header-abi-diff</h3> |
| |
| <p> |
| <code>header-abi-diff</code> 工具会将代表两个库的 ABI 的两个 <code>.lsdump</code> 文件进行比较,并生成差异报告,其中会说明这两个 ABI 之间存在的差异。</p> |
| |
| <table> |
| <tbody><tr> |
| <th>输入</th> |
| <td> |
| <ul> |
| <li>表示旧共享库的 ABI 的 <code>.lsdump</code> 文件。</li> |
| <li>表示新共享库的 ABI 的 <code>.lsdump</code> 文件。 |
| </li> |
| </ul> |
| </td> |
| </tr> |
| <tr> |
| <th>输出</th> |
| <td>差异报告,其中会说明在比较两个共享库提供的 ABI 之后发现的差异。 |
| </td> |
| </tr> |
| </tbody></table> |
| |
| <p> |
| ABI 差异文件会尽可能详细且便于读懂。格式在未来版本中可能会发生变化。例如,您有两个版本的 <code>libfoo</code>:<code>libfoo_old.so</code> 和 <code>libfoo_new.so</code>。在 <code>libfoo_new.so</code> 中的 <code>bar_t</code> 内,您将 <code>mfoo</code> 的类型从 <code>foo_t</code> 更改为 <code>foo_t *</code>。由于 <code>bar_t</code> 是直接可到达类型,因此这应该由 <code>header-abi-diff</code> 标记为会破坏 ABI 的更改。 |
| </p> |
| |
| <p> |
| 要运行 <code>header-abi-diff</code>,请执行以下命令: |
| </p> |
| |
| <pre class="prettyprint"> |
| header-abi-diff -old libfoo_old.so.lsdump \ |
| -new libfoo_new.so.lsdump \ |
| -arch arm64 \ |
| -o libfoo.so.abidiff \ |
| -lib libfoo |
| </pre> |
| |
| <p> |
| <strong><code>libfoo.so.abidiff</code></strong> 中的命令输出示例: |
| </p> |
| |
| <pre class="prettyprint"> |
| lib_name: "libfoo" |
| arch: "arm64" |
| record_type_diffs { |
| name: "bar" |
| type_stack: "Foo-> bar *->bar " |
| type_info_diff { |
| old_type_info { |
| size: 24 |
| alignment: 8 |
| } |
| new_type_info { |
| size: 8 |
| alignment: 8 |
| } |
| } |
| fields_diff { |
| old_field { |
| referenced_type: "foo" |
| field_offset: 0 |
| field_name: "mfoo" |
| access: public_access |
| } |
| new_field { |
| referenced_type: "foo *" |
| field_offset: 0 |
| field_name: "mfoo" |
| access: public_access |
| } |
| } |
| }</pre> |
| |
| <p> |
| <code>libfoo.so.abidiff</code> 包含一个报告,其中会注明 <code>libfoo</code> 中所有会破坏 ABI 的更改。<code>record_type_diffs</code> 消息表示记录发生了更改,并会列出不兼容的更改,其中包括: |
| </p> |
| |
| <ul> |
| <li>记录大小从 <code>24</code> 个字节更改为 <code>8</code> 个字节。</li> |
| <li><code>mfoo</code> 的字段类型从 <code>foo</code> 更改为 <code>foo *</code>(去除了所有类型定义符)。</li> |
| </ul> |
| |
| <p> |
| <code>type_stack</code> 字段用于指示 <code>header-abi-diff</code> 如何到达已更改的类型 (<code>bar</code>)。该字段可作如下解释:<code>Foo</code> 是一个导出的函数,接受 <code>bar *</code> 作为参数,该参数指向已导出且发生变化的 <code>bar</code>。 |
| </p> |
| |
| <aside class="tip"> |
| <strong>提示</strong>:要获取 <code>header-abi-diff</code> 工具方面的帮助,请运行 <code>header-abi-diff --help</code>。您也可以参阅 <code>development/vndk/tools/header-checker/README.md</code>。 |
| </aside> |
| |
| <h2 id="enforcing-abi-api">强制执行 ABI/API</h2> |
| |
| <p> |
| 要强制执行 VNDK 和 LLNDK 共享库的 ABI/API,必须将 ABI 参考签入到 <code>${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/(v)ndk/</code> 中。要创建这些参考,请运行以下命令: |
| </p> |
| |
| <pre class="prettyprint"> |
| ${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py |
| </pre> |
| |
| <p> |
| 创建参考后,如果对源代码所做的任何更改导致 VNDK 或 LLNDK 库中出现不兼容的 ABI/API 更改,则这些更改现在会导致编译错误。 |
| </p> |
| |
| <p> |
| 要更新特定 VNDK 核心库的 ABI 参考,请运行以下命令: |
| </p> |
| |
| <pre class="prettyprint"> |
| ${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l <lib1> -l <lib2> |
| </pre> |
| |
| <p> |
| 例如,要更新 <code>libbinder</code> ABI 参考,请运行以下命令: |
| </p> |
| |
| <pre class="prettyprint"> |
| ${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libbinder |
| </pre> |
| |
| <p> |
| 要更新特定 LLNDK 库的 ABI 参考,请运行以下命令: |
| </p> |
| |
| <pre class="prettyprint"> |
| ${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l <lib1> -l <lib2> --llndk |
| </pre> |
| |
| <p> |
| 例如,要更新 <code>libm</code> ABI 参考,请运行以下命令: |
| </p> |
| |
| <pre class="prettyprint"> |
| ${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libm --llndk |
| </pre> |
| |
| </body></html> |