【Unity3D杂谈】使用NDK命令行工具翻译Android Vitals上的内存堆栈
在Android游戏开发中,崩溃日志的分析至关重要,尤其是在Unity3D项目中,libunity.so
等动态库中的堆栈信息往往难以直接解读。本文介绍如何使用NDK提供的 addr2line
工具解析 Android Vitals 上的崩溃堆栈,以便快速定位问题。
1. addr2line
工具介绍
addr2line
是NDK提供的一个命令行工具,可将崩溃日志中的地址转换为具体的代码位置。
1.1 addr2line
命令示例
addr2line -f -e libunity.so 0x00000000001ea00c
-
-f
:显示函数名称。 -
-e
:指定符号文件(libunity.so
)。 -
0x00000000001ea00c
:崩溃日志中的内存地址。
1.2 使用 addr2line
工具
如果 addr2line
没有在环境变量中配置,可以直接使用完整路径:
D:\Program Files\Unity\Editor\Unity 2021.3.44f1\Editor\Data\PlaybackEngines\AndroidPlayer\NDK\toolchains\aarch64-linux-android-4.9\prebuilt\windows-x86_64\bin\aarch64-linux-android-addr2line.exe
其中 aarch64
代表 ARM64
架构。
2. Unity符号库 so
文件的获取
要正确解析崩溃堆栈,需要符号化的 .so
文件。这要求在打包的时候Build Setting的Create symbol.zip开关打开。或者打包代码实现:
EditorUserBuildSettings.androidCreateSymbolsZip = true
将zip解压后会获得一些so符号库,比如:
-
Crash/arm64-v8a/libunity.so
-
Crash/armeabi-v7a/libunity.so
3. 堆栈解析示例
3.1 崩溃堆栈
#00 pc 0x000000000038858c /data/app/com.demo.examples/lib/arm64/libunity.so
#01 pc 0x00000000003885c4 /data/app/com.demo.examples/lib/arm64/libunity.so
#02 pc 0x000000000078c044 /data/app/com.demo.examples/lib/arm64/libunity.so
#03 pc 0x0000000000786dc4 /data/app/com.demo.examples/lib/arm64/libunity.so
at com.unity3d.player.UnityPlayer.nativeDone (Native method)
at com.unity3d.player.UnityPlayer.shutdown (SourceFile)
3.2 addr2line
解析结果
_ZN14ConstantString7cleanupEv
_ZN14ConstantStringaSERKS_
_ZN13sorted_vectorINSt6__ndk14pairI14ConstantStringP11AssetBundleEE9erase_oneIS2_EEmRKT_
_ZN18AssetBundleManager23UnloadAssetBundleAtPathEP11AssetBundle
4. Python脚本实现自动解析
为了简化 addr2line
解析过程,可以编写一个 Python 脚本逐行解析崩溃堆栈。
4.1 解析崩溃堆栈的Python脚本
import os
import re
original_stack = """
000000038858c /data/app/com.demo.examples/lib/arm64/libunity.so
#01 pc 0x00000000003885c4 /data/app/com.demo.examples/lib/arm64/libunity.so
#02 pc 0x000000000078c044 /data/app/com.demo.examples/lib/arm64/libunity.so
#03 pc 0x0000000000786dc4 /data/app/com.demo.examples/lib/arm64/libunity.so
at com.unity3d.player.UnityPlayer.nativeDone (Native method)
at com.unity3d.player.UnityPlayer.shutdown (SourceFile)
"""
valid_so_list = ["libil2cpp.so", "libunity.so", "libmain.so"]
class StackInfo:
def __init__(self, address, arch, lib_name):
self.address = address
self.arch = arch
self.libName = lib_name
def parse_stack_info():
stackInfoList = []
for line in original_stack.splitlines():
if "pc" in line:
address = re.findall(r"0x[0-9a-fA-F]+", line)[0]
cpu_arch = "arm64" if "arm64" in line else "arm"
lib_name = re.findall(r"lib[\w]+\.so", line)[0]
if lib_name in valid_so_list:
stackInfoList.append(StackInfo(address, cpu_arch, lib_name))
return stackInfoList
def translate_stack(current_dir, address, cpu_arch, lib_name):
addr2line_path = "D:/Program Files/Unity/Editor/Unity 2021.3.44f1/Editor/Data/PlaybackEngines/AndroidPlayer/NDK/toolchains/aarch64-linux-android-4.9/prebuilt/windows-x86_64/bin"
addr2line_name = "./aarch64-linux-android-addr2line.exe"
current_dir = current_dir + ("/Crash/armeabi-v7a/" if cpu_arch == "arm" else "/Crash/arm64-v8a/")
lib_name = f'"{current_dir}{lib_name}"'
cmd = f'{addr2line_name} -f -e {lib_name} {address}'
os.chdir(addr2line_path)
result = os.popen(cmd).read()
return result.splitlines()[0]
if __name__ == '__main__':
current_dir = os.getcwd()
stack_info_list = parse_stack_info()
translated_stack = "".join(translate_stack(current_dir, s.address, s.arch, s.libName) for s in stack_info_list)
print(f"原始堆栈:\n{original_stack}\n\n翻译后:\n{translated_stack}")
4.2 脚本解析结果示例
原始堆栈:
#00 pc 0x0000000000373714 /data/app/com.demo.examples/lib/arm64/libunity.so
#01 pc 0x0000000000370848 /data/app/com.demo.examples/lib/arm64/libunity.so
翻译后:
_ZN14ConstantString7cleanupEv
_ZN14ConstantStringaSERKS_
5. 结论
通过 addr2line
工具结合符号库 .so
,可以有效解析 Android Vitals 上的崩溃日志,帮助开发者快速定位问题。此外,使用 Python 自动化脚本可以批量解析堆栈,提高调试效率。
只要具备完整的符号表 (so
文件),这种方法适用于大多数崩溃日志的解析,极大提升了问题排查速度。