面向NDK开发者的Android 7.0变更(面向开发者的ios)
haoteby 2025-07-27 20:05 25 浏览
订阅 Google 官方微信公众号:谷歌开发者。与谷歌一起创造未来!
受 Android 平台其他改进的影响,为了方便加载本机代码,Android M 和 N 中的动态链接器对编写整洁且跨平台兼容的本机代码提出了更严格的要求。为了确保平滑过渡到近期发布的 Android 版本,应用的本机代码必须遵循这些规则和建议。
我们在下面详细说明了与加载本机代码有关的每一项变更及其影响,以及您可以采取哪些措施来避免出现问题。所需工具:在 NDK 中,每一个架构都有一个 <arch>-linux-android-readelf 二进制文件(例如
arm-linux-androideabi-readelf 或
i686-linux-android-readelf)(位于 toolchains/ 下),但您可以将 readelf 用于任何架构,因为我们只进行基本检查。在 Linux 上,您需要为 readelf 安装 binutils 程序包,为 scanelf 安装 pax-utils 程序包。
1. 私有 API(从 API 24 开始执行)
原生库必须仅使用公共 API,且不可以链接到非 NDK 平台库。该规则从 API 24 开始强制执行,自此以后,应用无法加载非 NDK 平台库。该规则由动态链接器执行,所以无论代码使用何种方式加载,都无法访问非公共库:System.loadLibrary(...)、DT_NEEDED 条目和直接调用 dlopen(...) 都将以完全相同的方式失败。
在更新期间用户应获得一致的应用体验,开发者应不需要进行紧急应用更新来处理平台变更。因此,我们不推荐使用私有 C/C++ 符号。所有 Android 设备都必须通过的兼容性测试套件 (CTS) 不包含对私有符号进行的测试。它们可能不存在,或可能行为方式不同。因此,使用私有符号的应用很可能在某些设备上或在将来发布的版本中无法使用,在 Android 6.0 Marshmallow 从 OpenSSL 切换到 BoringSSL 时,很多开发者就发现了这种问题。
为了减少过渡期间对用户的影响,我们确定了在 Google Play 上安装最多的应用中颇为常用且我们在短期内仍可提供支持的一些库(包括 libandroid_runtime.so、libcutils.so、libcrypto.so 和 libssl.so)。为了给您更多的时间进行过渡,我们将暂时支持这些库;所以,如果您看到警告提示,这就意味着您的代码在将来发布的版本中将无法使用,请立即对其进行修复!
潜在问题:从 API 24 开始,动态链接器将不再加载私有库,并会阻止应用加载。
解决方案:重新编写本机代码,以便仅依赖公共 API。作为短期的变通方案,可以将没有复杂依赖关系的平台库 (libcutils.so) 复制到项目。作为长期的解决方案,必须将相关代码复制到项目树。SSL/Media/JNI 内部/binder API 不可以从本机代码访问。在必要时,本机代码应调用合适的公共 Java API 方法。
NDK 的
platforms/android-API/usr/lib 下提供完整的公共库列表。注:SSL/crypto 是特例,应用不得直接使用平台 libcrypto 和 libssl 库,即使在较早版本的平台上也不可以。所有应用都应使用 GMS 安全提供程序,以确保应用免遭已知漏洞攻击。
2. 缺少节标题(从 API 24 开始执行)
每个 ELF 文件的节标题中都包含额外的信息。现在这些标题必须存在,因为动态链接器使用它们进行健全性检查。有些开发者尝试通过删除标题使二进制文件难以理解,防止遭到反向工程。(这样做事实上没有作用,因为可以利用随处可得的工具重建被删除的信息。)
解决方案:在您的版本中取消删除节标题的步骤。
3. 文本重定位(从 API 23 开始执行)
从 API 23 开始,共享对象将不得包含文本重定位。也就是说,代码必须按原样加载,不得对其进行修改。这种方法减少了加载时间,并提高了安全性。
文本重定位的常见原因是使用了非定位独立手写汇编程序。这种情况并不常见。使用我们的文档中所述的 scanelf 工具进行进一步诊断:
$ scanelf -qT libTextRel.so
libTextRel.so: (memory/data?) [0x15E0E2] in (optimized out: previous simd_broken_op1) [0x15E0E0]
libTextRel.so: (memory/data?) [0x15E3B2] in (optimized out: previous simd_broken_op2) [0x15E3B0]
[skipped the rest]
如果您没有可用的 scanelf 工具,您可以使用 readelf 进行基本检查,查找 TEXTREL 条目或 TEXTREL 标志。查找其中之一便已足够。(对应于 TEXTREL 条目的值无关紧要,且其通常为 0,存在 TEXTREL 条目即可表明 .so 包含文本重定位)。本例同时存在两个指标:
$ readelf --dynamic libTextRel.so | grep TEXTREL
0x00000016 (TEXTREL) 0x0
0x0000001e (FLAGS) SYMBOLIC TEXTREL BIND_NOW
$
注:从技术上讲,是有可能存在带有 TEXTREL 条目/标志但没有任何实际文本重定位的共享对象。NDK 不会发生这种情况,但如果您自己生成 ELF 文件,请确保不要生成声明包含文本重定位的 ELF 文件,因为 Android 动态链接器信任该条目/标志。
潜在问题:重定位强制使代码页可写入,增加了内存中的脏页数量,因此非常浪费内存。从 Android K (API 19) 开始,动态链接器发布了有关文本重定位的警告,但在 API 23 及更高版本中,其拒绝加载带有文本重定位的代码。
解决方案:重新写入与位置无关的汇编程序,确保不需要文本重定位。查看 Gentoo 文档,了解相关详细信息:
wiki.gentoo.org/wiki/Hardened/Textrels_Guide
4. 无效的 DT_NEEDED 条目(从 API 23 开始执行)
虽然库依赖项(ELF 标题中的 DT_NEEDED 条目)可以是绝对路径,但这在 Android 平台上没有任何意义,因为您无法控制系统将在何处安装库。DT_NEEDED 条目应与所需库的 SONAME 相同,将在运行时查找库的任务留给动态链接器。在 API 23 之前,Android 的动态链接器在查找所需库时会忽略完整路径,仅使用基名(最后一个 ‘/’ 之后的部分)。从 API 23 开始,运行时链接器将完全服从 DT_NEEDED,所以,如果设备的特定位置不存在库,其将无法加载库。
更糟糕的是,有些编译系统存在漏洞,这会导致它们插入指向构建主机上的文件的 DT_NEEDED 条目,而在设备上将无法找到这种文件。
$ readelf --dynamic libSample.so | grep NEEDED
0x00000001 (NEEDED) Shared library: [libm.so]
0x00000001 (NEEDED) Shared library: [libc.so]
0x00000001 (NEEDED) Shared library: [libdl.so]
0x00000001 (NEEDED) Shared library:
[C:\Users\build\Android\ci\jni\libBroken.so]
$
潜在问题:在 API 23 之前使用 DT_NEEDED 条目的基名,但从 API 23 开始,Android 运行时将尝试使用指定路径加载库,而该路径在设备上将不存在。有些损坏的第三方工具链/编译系统使用构建主机而非 SONAME 上的路径。
解决方案:确保所有所需的库仅通过 SONAME 引用。最好让运行时链接器查找和加载这些库,因为库的位置可能在不同的设备上有所不同。
5. 缺少 SONAME(从 API 23 开始使用)
每个 ELF 共享对象(“原生库”)必须有一个 SONAME(共享对象名称)属性。NDK 工具链会默认添加此属性,若无此属性,则表明备用工具链或编译系统存在配置错误。缺少 SONAME 可能导致运行时问题,例如加载错误的库:缺少此属性时将使用文件名。
$ readelf --dynamic libWithSoName.so | grep SONAME
0x0000000e (SONAME) Library soname: [libWithSoName.so]
$
潜在问题:命名空间冲突可能导致在运行时加载错误的库,进而导致在未找到所需符号时或您尝试使用非预期的 ABI 不兼容库时系统崩溃。
解决方案:当前 NDK 默认情况下生成正确的 SONAME。确保使用最新版 NDK,且未将编译系统配置为生成不正确的 SONAME 条目(使用 -soname 链接器选项)。
请记住,使用最新版 NDK 构建的整洁的跨平台代码在 Android N 上应没有问题。我们建议您修改本机代码的构建,以便生成正确的二进制文件。
发布人:谷歌开发顾问 Dmitry Malykhanov
翻译:Google 官方微信公众号:谷歌开发者
相关推荐
- 一日一技:用Python程序将十进制转换为二进制
-
用Python程序将十进制转换为二进制通过将数字连续除以2并以相反顺序打印其余部分,将十进制数转换为二进制。在下面的程序中,我们将学习使用递归函数将十进制数转换为二进制数,代码如下:...
- 十进制转化成二进制你会吗?#数学思维
-
六年级奥赛起跑线:抽屉原理揭秘。同学们好,我是你们的奥耀老师。今天一起来学习奥赛起跑线第三讲二进制计数法。例一:把十进制五十三化成二进制数是多少?首先十进制就是满十进一,二进制就是满二进一。二进制每个...
- 二进制、十进制、八进制和十六进制,它们之间是如何转换的?
-
在学习进制时总会遇到多种进制转换的时候,学会它们之间的转换方法也是必须的,这里分享一下几种进制之间转换的方法,也分享两个好用的转换工具,使用它们能够大幅度的提升你的办公和学习效率,感兴趣的小伙伴记得点...
- c语言-2进制转10进制_c语言 二进制转十进制
-
#include<stdio.h>intmain(){charch;inta=0;...
- 二进制、八进制、十进制和十六进制数制转换
-
一、数制1、什么是数制数制是计数进位的简称。也就是由低位向高位进位计数的方法。2、常用数制计算机中常用的数制有二进制、八进制、十进制和十六进制。...
- 二进制、十进制、八进制、十六进制间的相互转换函数
-
二进制、十进制、八进制、十六进制间的相互转换函数1、输入任意一个十进制的整数,将其分别转换为二进制、八进制、十六进制。2、程序代码如下:#include<iostream>usingna...
- 二进制、八进制、十进制和十六进制等常用数制及其相互转换
-
从大学开始系统的接触计算机专业,到现在已经过去十几年了,今天整理一下基础的进制转换,希望给还在上高中的表妹一个入门的引导,早日熟悉这个行业。一、二进制、八进制、十进制和十六进制是如何定义的?二进制是B...
- 二进制如何转换成十进制?_二进制如何转换成十进制例子图解
-
随着社会的发展,电器维修由继电器时代逐渐被PLC,变频器,触摸屏等工控时代所替代,特别是plc编程,其数据逻辑往往涉及到数制二进制,那么二进制到底是什么呢?它和十进制又有什么区别和联系呢?下面和朋友们...
- 二进制与十进制的相互转换_二进制和十进制之间转换
-
很多同学在刚开始接触计算机语言的时候,都会了解计算机的世界里面大多都是二进制来表达现实世界的任何事物的。当然现实世界的事务有很多很多,就拿最简单的数字,我们经常看到的数字大多都是十进制的形式,例如:我...
- 十进制如何转换为二进制,二进制如何转换为十进制
-
用十进制除以2,除的断的,商用0表示;除不断的,商用1表示余0时结束假如十进制用X表示,用十进制除以2,即x/2除以2后为整数的(除的断的),商用0表示;除以2除不断的,商用1表示除完后的商0或1...
- 十进制数如何转换为二进制数_十进制数如何转换为二进制数举例说明
-
我们经常听到十进制数和二进制数,电脑中也经常使用二进制数来进行计算,但是很多人却不清楚十进制数和二进制数是怎样进行转换的,下面就来看看,十进制数转换为二进制数的方法。正整数转二进制...
- 二进制转化为十进制,你会做吗?一起来试试吧
-
今天孩子问把二进制表示的110101改写成十进制数怎么做呀?,“二进制”简单来说就是“满二进一”,只用0和1共两个数字表示,同理我们平常接触到的“十进制”是“满十进一”,只用0-9共十个数字表示。如果...
- Mac终于能正常打游戏了!苹果正逐渐淘汰Rosetta转译
-
Mac玩家苦转译久矣!WWDC2025苹果正式宣判Rosetta死刑,原生游戏时代终于杀到。Metal4光追和AI插帧技术直接掀桌,连Steam都连夜扛着ARM架构投诚了。看到《赛博朋克2077》...
- 怎么把视频的声音提出来转为音频?音频提取,11款工具实测搞定
-
想把视频里的声音单独保存为音频文件(MP3/AAC/WAV/FLAC)用于配音、播客、听课或二次剪辑?本文挑出10款常用工具,给出实测可复现的操作步骤、优缺点和场景推荐。1)转换猫mp3转换器(操作门...
- 6个mp4格式转换器测评:转换速度与质量并存!
-
MP4视频格式具有兼容性强、视频画质高清、文件体积较小、支持多种编码等特点,适用于网络媒体传播。如果大家想要将非MP4格式的视频转换成MP4的视频格式的话,可以使用MP4格式转换器更换格式。本文分别从...