KangQingYu
Articles64
Tags21
Categories9
dyld/ObjC Runtime通过环境变量进行白盒调试

dyld/ObjC Runtime通过环境变量进行白盒调试

dyld/ObjC Runtime如何利用环境变量,进行白盒调试?
无需修改代码、无需重新编译,只需在启动参数里打开对应开关,就能在 console 里看到 runtime 内部的方法加载、替换、调用链等过程,环境变量是分析 Category 冲突、启动耗时、方法覆盖问题的利器。

一 dyld的升级

App启动速度分析与优化,以前可以通过DYLD_PRINT_STATISTICS拿到如下的T1阶段详细耗时

1
2
3
4
5
Total pre-main time: 1.2 seconds  
dylib loading time: 420 ms
rebase/binding time: 130 ms
ObjC setup time: 350 ms
initializer time: 270 ms

但是iOS17(2023年9月)之后,dyld无法再打印DYLD_PRINT_STATISTICS启动时间信息了。
DYLD_PRINT_APIS 设置之后,直接启动就崩了

1
2
3
4
5
dyld`dyld4::RuntimeState::setUpLogging:

-> 0x1bda45788 <+64>: str w8, [x19, #0x330]

Thread 1: EXC_BAD_ACCESS (code=2, address=0x10b3421e0)

2025年做AI Coding项目的时候,读书人物关系图,涉及到大量图论的理论,拓扑图的展示涉及大量算法,调试起来非常困难,用到了环境变量。

1
2
3
ProcessInfo.processInfo.environment

RBG_ROUTE_DEBUG_EDGE_IDS = 2B36FD13-D48C

因此本文总结一下环境变量的知识点。

二 查看源码

1
2
3
4
5
6
7
8
https://github.com/apple-oss-distributions/objc4/blob/main/runtime/NSObject-private.h

https://github.com/apple-oss-distributions/objc4/blob/main/runtime/objc-private.h



#define OBJC_PRINT_LOAD_METHODS
直接定义字符串。

定义已经不在如上两个文件中了。dyld3升级后,头文件迁移到了:

1
2
3
objc-private.h
objc-env.h
objc-env.mm

新版 runtime 用“表驱动”管理环境变量。统一注册。

1
2
3


OPTION( PrintLoading,                              Off, OBJC_PRINT_LOAD_METHODS,         "log calls to class and category +load methods")

runtime初始化的时候会先后调用:

1
2
3
4
void _objc_init(void)

void environ_init(void)

定义更丰富了,不仅仅是打开、关闭。

1
2
3
4
5
6
7
8
9
// Settings from environment variables
typedef enum {
    Off = 0,                // Disabled 禁用
    On = 1,                 // Enabled 启用打印log
    Fatal = 2,              // Terminate process if this triggers 触发则终止进程
    Fault = 3,              // Generate a fault (simulated crash) 模拟崩溃
    StochasticFault = 4     // Generate a fault with 10% probability 10% 概率触发 Fault
} option_value_t;

三 设置环境变量的底层逻辑

每次启动进程的时候,操作系统通过参数传入字符串数组envp

1
int main(int argc, char *argv[], char *envp[])

envp是拼接的字符串数组,xcode可以通过环境变量注入。
dyld启动之后,dyld和libobjc都从这张表中通过getenv() 取自己的key。

Objc环境变量比较多。dyld相关的有:

1
2
3
4
DYLD_PRINT_LIBRARIES          # 打印加载的动态库
DYLD_PRINT_INITIALIZERS # 打印初始化器调用
DYLD_INSERT_LIBRARIES # 注入动态库
DYLD_LIBRARY_PATH # 指定库搜索路径

DYLD_PRINT_LIBRARIES 打印加载的动态库

特别多,节选如下

1
2
3
4
5
6
7
8
9
10
dyld[9403]: <CB1A2757-E5BB-314D-B065-8BDE29E7F85D> /Library/Developer/CoreSimulator/Volumes/iOS_23E244/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 26.4.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/StorageData.framework/StorageData
dyld[9403]: <CEB1294F-F247-343C-84DD-9C21BCB421AB> /Library/Developer/CoreSimulator/Volumes/iOS_23E244/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 26.4.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/Fitness.framework/Fitness
dyld[9403]: <E9A742C4-C0BA-3886-B68C-36DC60FB7473> /Library/Developer/CoreSimulator/Volumes/iOS_23E244/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 26.4.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/HealthDaemonFoundation.framework/HealthDaemonFoundation

dyld[9403]: move delayed to loaded: _AVKit_SwiftUI
dyld[9403]: move delayed to loaded: AppSupportUI
dyld[9403]: move delayed to loaded: ContactsDonation
dyld[9403]: move delayed to loaded: FindMyLocate
dyld[9403]: move delayed to loaded: MonogramPoster

DYLD_PRINT_INITIALIZERS 打印初始化器调用

1
2
3
4
5
6
7
8
dyld[10471]: running initializer 0x1836b81bc in /Library/Developer/CoreSimulator/Volumes/iOS_23E244/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 26.4.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/Montreal.framework/Montreal
dyld[10471]: running initializer 0x104dd1140 in /Library/Developer/CoreSimulator/Volumes/iOS_23E244/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 26.4.simruntime/Contents/Resources/RuntimeRoot/usr/lib/libRPAC.dylib
dyld[10471]: running initializer 0x1858d98ec in /Library/Developer/CoreSimulator/Volumes/iOS_23E244/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 26.4.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/UIKitCore.framework/UIKitCore
dyld[10471]: running initializer 0x1a94ae61c in /Library/Developer/CoreSimulator/Volumes/iOS_23E244/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 26.4.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/GameController.framework/GameController
dyld[10471]: running initializer 0x1055ef3f4 in /Users/kqy/Library/Developer/CoreSimulator/Devices/F1A60EC1-04F0-4B59-B1A8-070FE2046EF1/data/Containers/Bundle/Application/6B25E34E-97EA-4CE7-96E7-247E52168901/KeepQuantizeYourself.app/Frameworks/AFNetworking.framework/AFNetworking
dyld[10471]: running initializer 0x104d2894c in /Users/kqy/Library/Developer/CoreSimulator/Devices/F1A60EC1-04F0-4B59-B1A8-070FE2046EF1/data/Containers/Bundle/Application/6B25E34E-97EA-4CE7-96E7-247E52168901/KeepQuantizeYourself.app/Frameworks/Masonry.framework/Masonry
dyld[10471]: running initializer 0x1057ecaa4 in /Users/kqy/Library/Developer/CoreSimulator/Devices/F1A60EC1-04F0-4B59-B1A8-070FE2046EF1/data/Containers/Bundle/Application/6B25E34E-97EA-4CE7-96E7-247E52168901/KeepQuantizeYourself.app/Frameworks/ReactiveObjC.framework/ReactiveObjC

四 Objc环境变量枚举

在 Xcode 中设置路径:Edit Scheme → Run → Arguments → Environment Variables,添加变量名,Value 填 YES(新版也支持 fatal/fault)。

通过设置OBJC_HELP = YES,可以看到很多选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
objc[3315]: Objective-C runtime debugging. Set variable=YES to enable.
objc[3315]: OBJC_HELP: describe available environment variables
objc[3315]: OBJC_PRINT_OPTIONS: list which options are set
objc[3315]: OBJC_PRINT_IMAGES: log image and library names as they are loaded
objc[3315]: OBJC_PRINT_IMAGE_TIMES: measure duration of image loading steps
objc[3315]: OBJC_PRINT_LOAD_METHODS: log calls to class and category +load methods
objc[3315]: OBJC_PRINT_INITIALIZE_METHODS: log calls to class +initialize methods
objc[3315]: OBJC_PRINT_RESOLVED_METHODS: log methods created by +resolveClassMethod: and +resolveInstanceMethod:
objc[3315]: OBJC_PRINT_CLASS_SETUP: log progress of class and category setup
objc[3315]: OBJC_PRINT_PROTOCOL_SETUP: log progress of protocol setup
objc[3315]: OBJC_PRINT_IVAR_SETUP: log processing of non-fragile ivars
objc[3315]: OBJC_PRINT_VTABLE_SETUP: log processing of class vtables
objc[3315]: OBJC_PRINT_VTABLE_IMAGES: print vtable images showing overridden methods
objc[3315]: OBJC_PRINT_CACHE_SETUP: log processing of method caches
objc[3315]: OBJC_PRINT_FUTURE_CLASSES: log use of future classes for toll-free bridging
objc[3315]: OBJC_PRINT_PREOPTIMIZATION: log preoptimization courtesy of dyld shared cache
objc[3315]: OBJC_PRINT_CXX_CTORS: log calls to C++ ctors and dtors for instance variables
objc[3315]: OBJC_PRINT_EXCEPTIONS: log exception handling
objc[3315]: OBJC_PRINT_EXCEPTION_THROW: log backtrace of every objc_exception_throw()
objc[3315]: OBJC_PRINT_ALT_HANDLERS: log processing of exception alt handlers
objc[3315]: OBJC_PRINT_REPLACED_METHODS: log methods replaced by category implementations
objc[3315]: OBJC_PRINT_DEPRECATION_WARNINGS: warn about calls to deprecated runtime functions
objc[3315]: OBJC_PRINT_POOL_HIGHWATER: log high-water marks for autorelease pools
objc[3315]: OBJC_PRINT_CUSTOM_CORE: log classes with custom core methods
objc[3315]: OBJC_PRINT_CUSTOM_RR: log classes with custom retain/release methods
objc[3315]: OBJC_PRINT_CUSTOM_AWZ: log classes with custom allocWithZone methods
objc[3315]: OBJC_PRINT_RAW_ISA: log classes that require raw pointer isa fields
objc[3315]: OBJC_DEBUG_UNLOAD: warn about poorly-behaving bundles when unloaded
objc[3315]: OBJC_DEBUG_FRAGILE_SUPERCLASSES: warn about subclasses that may have been broken by subsequent changes to superclasses
objc[3315]: OBJC_DEBUG_NIL_SYNC: warn about @synchronized(nil), which does no synchronization
objc[3315]: OBJC_DEBUG_NONFRAGILE_IVARS: capriciously rearrange non-fragile ivars
objc[3315]: OBJC_DEBUG_ALT_HANDLERS: record more info about bad alt handler use
objc[3315]: OBJC_DEBUG_MISSING_POOLS: warn about autorelease with no pool in place, which may be a leak
objc[3315]: OBJC_DEBUG_POOL_ALLOCATION: halt when autorelease pools are popped out of order, and allow heap debuggers to track autorelease pools
objc[3315]: OBJC_DEBUG_DUPLICATE_CLASSES: warn when multiple classes with the same name are present
objc[3315]: OBJC_DEBUG_DONT_CRASH: halt the process by exiting instead of crashing
objc[3315]: OBJC_DEBUG_POOL_DEPTH: log fault when at least a set number of autorelease pages has been allocated
objc[3315]: OBJC_DEBUG_SCRIBBLE_CACHES: scribble the IMPs in freed method caches
objc[3315]: OBJC_DEBUG_SCAN_WEAK_TABLES: scan the weak references table continuously in the background - set OBJC_DEBUG_SCAN_WEAK_TABLES_INTERVAL_NANOSECONDS to set scanning interval (default 1000000)
objc[3315]: OBJC_DISABLE_VTABLES: disable vtable dispatch
objc[3315]: OBJC_DISABLE_PREOPTIMIZATION: disable preoptimization courtesy of dyld shared cache
objc[3315]: OBJC_DISABLE_TAGGED_POINTERS: disable tagged pointer optimization of NSNumber et al.
objc[3315]: OBJC_DISABLE_TAG_OBFUSCATION: disable obfuscation of tagged pointers
objc[3315]: OBJC_DISABLE_NONPOINTER_ISA: disable non-pointer isa fields
objc[3315]: OBJC_DISABLE_INITIALIZE_FORK_SAFETY: disable safety checks for +initialize after fork
objc[3315]: OBJC_DISABLE_FAULTS: disable os faults
objc[3315]: OBJC_DISABLE_PREOPTIMIZED_CACHES: disable preoptimized caches
objc[3315]: OBJC_DISABLE_AUTORELEASE_COALESCING: disable coalescing of autorelease pool pointers
objc[3315]: OBJC_DISABLE_AUTORELEASE_COALESCING_LRU: disable coalescing of autorelease pool pointers using look back N strategy

这些环境变量本质上都是 ObjC runtime 的调试开关,启动时 environ_init() 读取并缓存,之后在运行时各个关键路径上判断是否打印 log 或触发防御行为。

功能分为如下几类:

1 镜像加载类

OBJC_PRINT_IMAGES dyld 每加载一个动态库/可执行文件时打印其路径。用于排查某个类/分类从哪个 framework 里加载进来的。

OBJC_PRINT_IMAGE_TIMES 打印每张镜像的加载耗时,用于分析启动性能瓶颈。


2 类与分类加载类

OBJC_PRINT_LOAD_METHODS 每次调用 +load 方法时打印类名/分类名。用来观察 +load 的调用顺序和来源,对 Category 调试价值很高。

美团这篇文章提到了。
https://tech.meituan.com/2015/03/03/diveintocategory.html

在Xcode中点击Edit Scheme,添加如下两个环境变量(可以在执行load方法以及加载category的时候打印log信息,更多的环境变量选项可参见objc-private.h):

不过这篇文章较早,已经删除环境变量选项OBJC_PRINT_LOAD_METHODS。

在自己项目中打开 OBJC_PRINT_LOAD_METHODS 可以看到调用顺序:

1
2
3
4
5
6
7
8
9
objc[3289]: LOAD: category 'NSObject(NSObject)' scheduled for +load
objc[3289]: LOAD: +[NSObject(NSObject) load]
objc[3289]: LOAD: class 'NSNotificationCenter' scheduled for +load
objc[3289]: LOAD: category 'NSObject(NSObject)' scheduled for +load
objc[3289]: LOAD: +[NSNotificationCenter load]
objc[3289]: LOAD: +[NSObject(NSObject) load]
objc[3289]: LOAD: class 'CLProjectRecord' scheduled for +load
objc[3289]: LOAD: class '_AFURLSessionTaskSwizzling' scheduled for +load
objc[3289]: LOAD: class 'CCProjectJsBridge' scheduled for +load

OBJC_PRINT_INITIALIZE_METHODS 每次调用 +initialize 时打印。可以发现意外的 +initialize 触发时机(比如某个类在不期望的时间点被首次使用)。

OBJC_PRINT_CLASS_SETUP 打印每个类被”methodize”(方法化)的过程,即类的方法列表、属性列表被挂载到 class_rw_t 的过程。

OBJC_PRINT_PROTOCOL_SETUP 打印协议的注册过程。


3 方法替换与冲突类

OBJC_PRINT_REPLACED_METHODS ,排查第三方库冲突。当一个 Category 的方法覆盖了原类(或另一个 Category)的同名方法时打印警告:

1
2
objc[1234]: REPLACED: -[NSString stringByAppendingString:] 
(from MyApp) by category from AnotherLib

可以直接发现”一个方法被某个第三方库的分类偷偷替换了”这类难以察觉的 bug。


4 消息发送与方法查找类

OBJC_PRINT_RESOLVED_METHODS 打印通过 +resolveInstanceMethod: / +resolveClassMethod: 动态创建的方法。

OBJC_HELP 启动时打印所有可用环境变量及说明,相当于内置文档。

OBJC_PRINT_OPTIONS 打印当前哪些选项被设置了(用来确认环境变量确实生效了)。


5 内存管理类

OBJC_PRINT_POOL_HIGHWATER 打印 Autorelease Pool 的水位高峰,帮助排查内存峰值问题。

OBJC_DEBUG_POOL_DEPTH 检查 pool 嵌套深度是否异常。


6 防御/安全类

新版 option_value_t 支持设成 fatalfault,这类变量不再只是打印 log,而是在检测到问题时主动让程序崩溃,方便在测试阶段暴露问题:

OBJC_DEBUG_DUPLICATE_CLASSES 检测到重复定义的类时报错(两个 framework 都定义了同名类)。

OBJC_DEBUG_MISSING_POOLS 在没有 Autorelease Pool 的线程上触发 autorelease 时报告。

OBJC_DEBUG_NONFRAGILE_IVARS 检查 ivar 的内存管理属性是否正确。
``


五 看不到日志的原因

设置了dead strip,某些动态库未加载,没被链接进 Mach-O。
我在项目中的Podfile进行了如下包体积优化的配置

1
config.build_settings['DEAD_CODE_STRIPPING'] = 'YES' 

线下分析的时候,注释这一行即可看到打印的日志信息。

Author:KangQingYu
Link:http://example.com/2025/08/16/20250816dyld%20ObjC%20Runtime%E9%80%9A%E8%BF%87%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F%E8%BF%9B%E8%A1%8C%E7%99%BD%E7%9B%92%E8%B0%83%E8%AF%95/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可
×