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"
		}
	}
}


拷贝以上脚本,在本地生成相应的文件,放在自己电脑里合适的位置,路径无要求,自己决定。

3d9b07a7-4d57-4090-a02c-731cf8d98044

指令功能项

gwdebug XXXXXXCenter
gwdebug center
gwdebug XXXXXX
gwdebug -a 0x10505fcec

组件名支持模糊匹配,忽略大小写

文件说明

  1. BinMapSource.py负责lldb调试时的指令调度,具体可以参考代码。 核心原理是基于lldb的路径映射能力
    settings set target.source-map  二进制编译时的代码全路径  二进制使用者电脑里代码路径
    
  2. sourceMap.json 将二进制文件编译时的文件路径 和 使用者电脑代码路径做一个映射,方便py脚本解析。

步骤2-配置lldb

在lldb初始化文件里自动加载调试脚本

文件路径

~/.lldbinit

461ef248-4f8d-43b4-a1fe-c3ec1071ed2c

如果没有这个文件,自行创建一个,普通文本格式。

打开lldbinit文件后,追加一行

command script import ~/Desktop/bindebug/BinMapSource.py

注意:BinMapSource.py的路径根据步骤1中提到的本地路径做相应调整。

如此,Xcode打开lldb调试时,会自动加载BinMapSource.py。

步骤3-调试验证

3.1 已知编译路径 & 本地代码路径

  1. gwdebug的命名可以根据个人习惯自行修改py代码。
  2. 组件名参数支持模糊匹配,忽略大小写。
  3. 映射关系在sourceMap.json 里,自行维护。
  4. 源码的当前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。

如此,根目录里的所有代码文件都会相应一起被映射。