设为首页 加入收藏

TOP

字节跳动 DanceCC 工具链系列之Xcode LLDB耗时监控统计方案(一)
2023-07-23 13:26:36 】 浏览:293
Tags:DanceCC Xcode LLDB 时监控 计方案

作者:李卓立 仲凯宁

背景介绍

《字节跳动 DanceCC 工具链系列之Swift 调试性能的优化方案》[1]一文中,我们介绍了如何使用自定义的工具链,来针对性优化调试器的性能,解决大型Swift项目的调试痛点。

在经过内部项目的接入以及一段时间的试用之后,为了精确测量经过优化后的LLDB调试Xcode项目效率提升效果,衡量项目收益,需要开发一套能够同时获取Xcode官方工具链与DanceCC工具链调试耗时的耗时监控方案。

一般来说,LLDB内置的工作耗时,可以通过输入log timers dump来获取粗略的累计耗时,但是这个耗时只包括了源代码中插入了LLDB_SCOPED_TIMER()宏的函数,并不代表完整的真实耗时。并且这个耗时统计需要用户手动触发,如果要单独获取某次操作的耗时还需要先进行reset操作清空之前的耗时记录;对于我们目前的需求而言不够精确也不够自动。

因此DanceCC提出了一套专门的方案。方案原理基于LLDB Plugin[2],利用Fishhook[3],从LLDB的Script Bridge API[4]层面拦截Xcode对LLDB调用,以此来进行耗时监控统计。

注:LLDB论坛也有贡献者,讨论另一套内置的LLDB metries方案[5],但是目标侧重点和我们略有不同,并且截至发稿日未有完整的结论,因此仅在引用链接提及供读者延伸阅读。

方案原理

LLDB Plugin

Apple在其LLDB和早期Xcode集成中,为了不侵入一些容易改动的上层逻辑,引入了LLDB Plugin的设计和支持。

每个Plugin是一个动态链接库,需要实现特定的C++/C入口函数,由LLDB主进程在运行时通过dladdr找到函数入口并加载进内存。目前有两种Plugin的接口形式(网上常见第一种)

  • 新Plugin接口:
namespace lldb {
bool PluginInitialize(SBDebugger debugger);
}

这种Plugin,需要用户在脚本中手动按需加载,并常驻在内存中:

plugin load /path/to/plugin.dylib
  • 老Plugin接口:
extern "C" bool LLDBPluginInitialize(void);
extern "C" void LLDBPluginTerminate(void);

将编译的动态库放入以下两个目录,即可自动被加载,无法手动控制时机,在当前调试Session结束时卸载:

/path/to/LLDB.framework/Resources/Plugins
~/Library/Application Support/LLDB/PlugIns

注入动态库

图片

正常流程中,Xcode开始调试时会启动一个lldb-rpc-server的进程,这个进程会加载Xcode默认工具链,或指定工具链中的LLDB.framework,并且通过这个动态库中暴露出的Script Bridge API调用LLDB的各功能。

图片

监控流程中,我们向lldbinit文件中添加了command script import ~/.dancecc/dancecc_lldb.py,用于在LLDB启动时加载脚本,脚本内会执行plugin load ~/.dancecc/libLLDBStatistics.dylib,加载监控动态库。

监控动态库在被加载时,因为被加载的动态库和LLDB.framework不在一个MachO Image中,我们能够通过Fishhook方案,对LLDB.framework暴露出的我们关心的Script Bridge API进行hook。

hook成功之后,每次Xcode对Script Bridge API进行调用都会先进入我们的监控逻辑。此时我们记录时间戳来计时,然后再进入LLDB.framework中的逻辑,获取结果后返回给lldb-rpc-server,并在Xcode的GUI中展示。

Hook SB API

Hook SB API时,需要一份含有要部署的LLDB.framework的头文件(Xcode并未内置)。由于上述的流程使用了动态链接的LLDB.framework,我们选择了Swift 5.6的产物,并tbd化避免仓库膨胀。

由于LLDB Script Bridge API相对稳定,因此可以使用一个动态库实现,通过运行时来应对不同版本的API变化(极少出现,截止发文调研5.5~5.7之间Xcode并没有改变调用接口)。

对于hook C++函数的方式,这里借用了Fishhook进行替换。原C++的函数地址,可通过dlsym调用得到。注意C++函数名使用mangled后的名称(在tbd文件中可找到)。

///
/// Hook a SB API using the stub method defined with the macros above
///
#define LLDB_HOOK_METHOD(MANGLED, CLASS, METHOD) \
Logger::Log("Hook "#CLASS"::"#METHOD" started!"); \
ptr_##MANGLED.pvoid = dlsym(RTLD_DEFAULT, #MANGLED); \
if (!ptr_##MANGLED.pvoid) { \
    Logger::Log(dlerror()); \
    return; \
} \
if (rebind_symbols((struct rebinding[1]){{#MANGLED, (void *) hook_##MANGLED, (void **) & ptr_##MANGLED.pvoid }}, 1) < 0) { \
    Logger::Log(dlerror()); \
    return; \
} \
Logger::Log("Hook "#CLASS"::"#METHOD" succeed!");

C++的成员函数的函数指针第一个应该是thi

首页 上一页 1 2 3 下一页 尾页 1/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇本文相关主要记录一下使用Hbuilde.. 下一篇Flash开发iOS应用全攻略(五)—..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目