iOS 二进制切换源码调试
前置条件
假设主工程集成了framework、xcframework等二进制库,形式不限。
需要Python3
演示示例:
XXXXXXCenter.xcframework
XXXXNetWork.xcframework
文中用XXXX做了库名脱敏,请自行修改。
步骤1-准备脚本
BinMapSource.py
#!/usr/bin/env python3
#encoding=utf-8
import lldb
import re
import os
import json
def exchangePath(debugger, command, result, internal_dict):
if command == "":
print('')
print('✗ missing parameters')
print('')
print('Example:')
print('')
print('gwdebug XXXXXXCenter')
print('gwdebug center')
print('gwdebug XXXXXX')
print('gwdebug -a 0x10505fcec')
print('')
exit(1)
print('====================================')
print('')
print('debugger: ' + str(debugger))
print('command: ' + command)
sourceMapFilePath = "~/Desktop/bindebug/sourceMap.json"
sourceMapFilePath = os.path.expanduser(sourceMapFilePath)
print('sourceMapFilePath: ' + sourceMapFilePath)
print('current python file path:' + os.path.abspath(__file__))
print('')
print('====================================')
interpreter = lldb.debugger.GetCommandInterpreter()
returnObject = lldb.SBCommandReturnObject()
if '-a' in command:
address = command.replace("-a ", "")
interpreter.HandleCommand('image lookup -v --address ' + address, returnObject)
output = returnObject.GetOutput()
fileMatchs = re.match(r'(.|\n)*file = "(.*?)".*', output,re.M)
if fileMatchs is not None:
print('✔ bin compileSourcePath = ' + fileMatchs.group(2))
print('')
else:
print('✗ bin compileSourcePath not found, maybe the memory address is mistyped')
print('')
exit(1)
print('')
print('✔ map path task begin')
print('')
with open(sourceMapFilePath, 'r') as f:
jsonObj = json.load(f)
customConfig = jsonObj["customConfig"]
customItems = customConfig.items()
nomatch = True
for itemName, itemInfo in customItems:
if command.lower() in itemName.lower():
nomatch = False
compileSourcePath = itemInfo['compileSourcePath']
compileSourcePath = os.path.expanduser(compileSourcePath)
localSourcePath = itemInfo['localSourcePath']
localSourcePath = os.path.expanduser(localSourcePath)
print("- " + itemName)
print(" - compileSourcePath = " + compileSourcePath)
print(" - localSourcePath = " + localSourcePath)
interpreter.HandleCommand('settings set target.source-map ' + compileSourcePath + ' ' + localSourcePath, returnObject)
output = returnObject.GetOutput()
if output != "":
print('✓ output: ' + output)
break
if nomatch:
print('✗ nomatch')
print('')
print('✔ map path task done')
print('')
# 添加一个 扩展命令 gwdebug
# 在 lldb 输入 gwdebug 时,会执行 BinMapSource.py 文件的 exchangePath 方法
def __lldb_init_module(debugger, internal_dict):
debugger.HandleCommand('command script add gwdebug -f BinMapSource.exchangePath')
sourceMap.json
{
"defaultConfig" : {
"compileSourcePath": "",
"localSourcePath": ""
},
"customConfig" : {
"XXXXNetWork" : {
"compileSourcePath": "~/Documents/work/iOS/XXXX",
"localSourcePath": "~/Desktop/XXXXX"
},
"XXXXXXCenter" : {
"compileSourcePath": "~/Documents/work/iOS/XXXXXX",
"localSourcePath": "~/Desktop/XXXXXX"
}
}
}
拷贝以上脚本,在本地生成相应的文件,放在自己电脑里合适的位置,路径无要求,自己决定。
指令功能项
gwdebug XXXXXXCenter
gwdebug center
gwdebug XXXXXX
gwdebug -a 0x10505fcec
组件名支持模糊匹配,忽略大小写
文件说明
- BinMapSource.py负责lldb调试时的指令调度,具体可以参考代码。
核心原理是基于lldb的路径映射能力
settings set target.source-map 二进制编译时的代码全路径 二进制使用者电脑里代码路径
- sourceMap.json 将二进制文件编译时的文件路径 和 使用者电脑代码路径做一个映射,方便py脚本解析。
步骤2-配置lldb
在lldb初始化文件里自动加载调试脚本
文件路径
~/.lldbinit
如果没有这个文件,自行创建一个,普通文本格式。
打开lldbinit文件后,追加一行
command script import ~/Desktop/bindebug/BinMapSource.py
注意:BinMapSource.py的路径根据步骤1中提到的本地路径做相应调整。
如此,Xcode打开lldb调试时,会自动加载BinMapSource.py。
步骤3-调试验证
3.1 已知编译路径 & 本地代码路径
- gwdebug的命名可以根据个人习惯自行修改py代码。
- 组件名参数支持模糊匹配,忽略大小写。
- 映射关系在sourceMap.json 里,自行维护。
- 源码的当前git commit 要跟 framework 编译时的commit严格对应,否则影响debug预期。
3.2 不知道二进制编译时的路径怎么办
3.2.1 如果是动态库
可以通过指令获取
gwdebug -a 内存地址
3.2.2 如果是静态库
在控制台里执行以下指令,可直接获取到相应的路径
cd YOUR PATH OF XXXXXXCenter.framework
dwarfdump ./XXXXXXCenter | grep 'XXXXXXCenter'
得到地址后,在sourceMap.json 里维护更新。
建议路径填写到代码的根目录
,比如 ~/Desktop/XXXXNetWork。
如此,根目录里的所有代码文件都会相应一起被映射。