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。
如此,根目录里的所有代码文件都会相应一起被映射。
