Clang插樁

配置Clang插樁

LLVM內(nèi)置了一個簡單的代碼覆蓋率檢測工具(SanitizerCoverage)。它在函數(shù)級基本塊級邊緣級上插入對用戶定義函數(shù)的調(diào)用,通過這種方式可以順利對OC方法C函數(shù)、Block、Swift的方法/函數(shù)進(jìn)行全面HOOK

Clang13的文檔 關(guān)于 Tracing PCs (跟蹤C(jī)PU執(zhí)行到的代碼),通過Clang插樁我們可以跟蹤到所有函數(shù)的執(zhí)行,包括APP啟動時刻所調(diào)用的。

Clang 插樁
  • 搭建測試項目,在Build Setting -> Other C Flags中,增加-fsanitize-coverage=trace-pc-guard的配置
image.png
  • 編譯工程有如下報錯
image.png

說明__sanitizer_cov_trace_pc_guard_init__sanitizer_cov_trace_pc_guard方法需要我們實現(xiàn),Clang13的官方文檔內(nèi)容如下

image.png
  • 按照文檔,在項目中加入如下代碼
#import "ViewController.h" 
#include <stdint.h> 
#include <stdio.h> 
#include <sanitizer/coverage_interface.h>
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
 }

void __sanitizer_cov_trace_pc_guard_init(uint32_t *start, uint32_t *stop) { 
    static uint64_t N; 
    if (start == stop || *start) return; 
    printf("INIT: %p %p\n", start, stop); 
    for (uint32_t *x = start; x < stop; x++) 
        *x = ++N; 
}

void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
    if (!*guard) return;
    void *PC = __builtin_return_address(0); 
    char PcDescr[1024]; 
    printf("guard: %p %x PC %s\n", guard, *guard, PcDescr); 
}

@end
__sanitizer_cov_trace_pc_guard_init函數(shù)

參數(shù)一 start是一個指針,指向無符號int類型4個字節(jié),相當(dāng)于一個數(shù)組的起始位置,即符號的起始位置(是從高位往低位讀)
參數(shù)二 stop由于數(shù)據(jù)的地址是往下讀的(即從高往低讀,所以此時獲取的地址并不是stop真正的地址,而是標(biāo)記的最后的地址,讀取stop時由于stop占4個字節(jié),stop真實地址 = stop打印的地址-0x4

// 運行項目,打印以下內(nèi)容:
INIT: 0x10e838a0c 0x10e838aa0
  • 打印來自__sanitizer_cov_trace_pc_guard_init函數(shù)
  • 通過for循環(huán)代碼,發(fā)現(xiàn)從startstop的地址中,存儲的是uint32_t類型的值
  • 循環(huán)中xuint32_t指針類型,x++表示指針運算,步長+1會增加數(shù)據(jù)類型的長度
  • uint32_t占4字節(jié),所以循環(huán)中的代碼含義,每四字節(jié)記錄一個++N的值
lldb驗證

// 讀取start 
(lldb) x 0x10e838a0c
0x10e838a0c: 01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00  ................
0x10e838a1c: 05 00 00 00 06 00 00 00 07 00 00 00 08 00 00 00  ................
// 讀取stop 
(lldb) x 0x10e838aa0-4
0x10e838a9c: 25 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  %...............
0x10e838aac: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  • 讀取最后一個值,要在stop地址的基礎(chǔ)上減去4字節(jié)
  • startstop,讀出值為25(注意是16進(jìn)制)表示當(dāng)前項目中方法/函數(shù)/Block的符號個數(shù)。
__sanitizer_cov_trace_pc_guard函數(shù)

參數(shù)guard是一個哨兵,告訴我們是第幾個方法被調(diào)用的

  • __sanitizer_cov_trace_pc_guard函數(shù)中設(shè)置斷點,運行項目查看函數(shù)調(diào)用棧,由main函數(shù)調(diào)用
image.png
  • 繼續(xù)調(diào)試,進(jìn)入該函數(shù)的斷點,由didFinishLaunchingWithOptions函數(shù)調(diào)用
image.png

我們發(fā)現(xiàn)項目中每一個方法和函數(shù)的調(diào)用,都會觸發(fā)__sanitizer_cov_trace_pc_guard的斷點,并且由當(dāng)前執(zhí)行的方法/函數(shù)調(diào)用

測試__sanitizer_cov_trace_pc_guard方法

void __sanitizer_cov_trace_pc_guard(uint32_t *guard) { 
    NSLog(@"__sanitizer_cov_trace_pc_guard");
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { 
    NSLog(@"touchesBegan方法執(zhí)行"); 
    test(); 
}

void(^block)(void) = ^(void) { 
    NSLog(@"Block執(zhí)行");
};

void test() { 
    NSLog(@"test函數(shù)執(zhí)行");
    block();
}

// 控制臺打印
2021-10-20 20:57:20.938427+0800 TraceDemo[16078:2734157] __sanitizer_cov_trace_pc_guard
2021-10-20 20:57:20.940051+0800 TraceDemo[16078:2734157] touchesBegan方法執(zhí)行
2021-10-20 20:57:20.940299+0800 TraceDemo[16078:2734157] __sanitizer_cov_trace_pc_guard
2021-10-20 20:57:20.940499+0800 TraceDemo[16078:2734157] test函數(shù)執(zhí)行
2021-10-20 20:57:20.940675+0800 TraceDemo[16078:2734157] __sanitizer_cov_trace_pc_guard
2021-10-20 20:57:20.940861+0800 TraceDemo[16078:2734157] Block執(zhí)行
  • 從運行結(jié)果來看,方法和函數(shù)全部被HOOK
  • 被攔截的方法和函數(shù),僅限當(dāng)前項目中的符號。例如:NSLog等外部符號不會被HOOK
  • 二進(jìn)制重排的本意,就是將代碼實現(xiàn)的二進(jìn)制中方法/函數(shù)符號在啟動時刻按照順序排列在前面。外部符號的方法/函數(shù)實現(xiàn),并不在當(dāng)前項目中,所以它們的符號也不在重排的范圍之內(nèi)。

測試set方法能否HOOK到?

@interface ViewController ()
@property(nonatomic, assign) int age;
@end

- (void)viewDidLoad {
    [super viewDidLoad];
    self.age = 10;
}
image.png

set方法成功HOOK

Clang插樁的原理

__sanitizer_cov_trace_pc_guard方法添加斷點,點擊屏幕觸發(fā)touchesBegan方法進(jìn)行調(diào)試

image.png

在每一個方法/函數(shù)/Block內(nèi)容執(zhí)行前,都調(diào)用了__sanitizer_cov_trace_pc_guard函數(shù)

Clang插樁的實現(xiàn)原理:

  • 只要添加了Clang插樁的標(biāo)記,編譯器就會在當(dāng)前項目中所有方法、函數(shù)、Block的代碼實現(xiàn)邊緣,插入一句__sanitizer_cov_trace_pc_guard函數(shù)的調(diào)用代碼,達(dá)到方法/函數(shù)/Block的100%覆蓋
  • 相當(dāng)于編譯器在編譯時期,修改了當(dāng)前的二進(jìn)制文件
  • 修改時機(jī):有可能是語法分析之后生成IR中間代碼時進(jìn)行修改(未驗證)

獲取符號名稱

我們現(xiàn)在已經(jīng)能HOOK到所有的方法/函數(shù)/Block,現(xiàn)在要怎么獲取它們的符號,寫入order文件?

  • 查看__builtin_return_address方法
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
    if (!*guard) return;
    //獲取上一個函數(shù)的地址,通過這個地址就能拿到函數(shù)的符號名稱
    /*
     - PC 當(dāng)前函數(shù)返回上一個調(diào)用的地址
     - 0 當(dāng)前這個函數(shù)地址,即當(dāng)前函數(shù)的返回地址
     - 1 當(dāng)前函數(shù)調(diào)用者的地址,即上一個函數(shù)的返回地址
    */
    void *PC = __builtin_return_address(0); 
    char PcDescr[1024]; 
    printf("guard: %p %x PC %s\n", guard, *guard, PcDescr); 
}
  • 得到調(diào)用者的函數(shù)地址獲取符號名稱
#include <dlfcn.h> 

void __sanitizer_cov_trace_pc_guard(uint32_t *guard) { 
    if (!*guard) return; 
    
    void *PC = __builtin_return_address(0);
    Dl_info info; 
    dladdr(PC, &info);
    
    NSLog(@"%s", info.dli_fname); 
    NSLog(@"%p", info.dli_fbase);
    NSLog(@"%s", info.dli_sname);
    NSLog(@"%p", info.dli_saddr); 
}

使用dladdr函數(shù)傳入函數(shù)地址獲取基本信息,存入Dl_info結(jié)構(gòu)體

  • Dl_info結(jié)構(gòu)體的定義
typedef struct dl_info {
        const char      *dli_fname;     /* Pathname of shared object */
        void            *dli_fbase;     /* Base address of shared object */
        const char      *dli_sname;     /* Name of nearest symbol */
        void            *dli_saddr;     /* Address of nearest symbol */
} Dl_info;
  • dli_fname:當(dāng)前MachO路徑(文件的名字)
  • dli_fbase:當(dāng)前MachO起始地址(文件的地址)
  • dli_sname:函數(shù)名稱
  • dli_saddr:函數(shù)地址

運行項目查看打印結(jié)果,發(fā)現(xiàn)可以通過dli_sname得到函數(shù)名稱

/Users/wn/Library/Developer/CoreSimulator/Devices/C53887CF-B3AC-4677-B6FD-DD090CC6D346/data/Containers/Bundle/Application/E211EC24-FFD6-4745-8DFE-345A1DDDC07C/TraceDemo.app/TraceDemo
0x10fb8a000
-[ViewController touchesBegan:withEvent:]
0x10fb8dd20
  • 修改測試代碼運行項目
#import "ViewController.h"
#include <stdint.h>
#include <stdio.h>
#include <sanitizer/coverage_interface.h>
#include <dlfcn.h>

@interface ViewController ()
@end
@implementation ViewController

+ (void)load {
    // NSLog(@"load函數(shù)");
}

- (void)viewDidLoad {
    [super viewDidLoad];
}

void __sanitizer_cov_trace_pc_guard_init(uint32_t *start, uint32_t *stop) {
    static uint64_t N;
    if (start == stop || *start) return;
    
    for (uint32_t *x = start; x < stop; x++)
        *x = ++N;
}

void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
    void *PC = __builtin_return_address(0);
    Dl_info info;
    dladdr(PC, &info);
    NSLog(@"%s", info.dli_sname);
}

@end

// 控制臺打印
 +[ViewController load]
 main
 -[AppDelegate application:didFinishLaunchingWithOptions:]
 -[SceneDelegate window]
 -[SceneDelegate setWindow:]
 -[SceneDelegate window]
 -[SceneDelegate window]
 -[SceneDelegate scene:willConnectToSession:options:]
 -[SceneDelegate window]
 -[SceneDelegate window]
 -[SceneDelegate window]
 -[ViewController viewDidLoad]
 -[SceneDelegate sceneWillEnterForeground:]
 -[SceneDelegate sceneDidBecomeActive:]

獲取到啟動時刻所有被調(diào)用的方法、函數(shù)、Block的函數(shù)名稱。其中部分函數(shù)多次調(diào)用,出現(xiàn)了重復(fù)符號,還需要對其排重。

通過原子隊列保存符號

修改代碼,測試能否獲取到子線程的符號

- (void)viewDidLoad {
    [super viewDidLoad];
    [self performSelectorInBackground:@selector(testSleep) withObject:nil];
}

- (void)testSleep {
    sleep(3);
}

void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
    void *PC = __builtin_return_address(0);
    NSLog(@"%@", [NSThread currentThread]);
    Dl_info info;
    dladdr(PC, &info);
    NSLog(@"%s", info.dli_sname);
}

// 控制臺打印
 -[SceneDelegate sceneDidBecomeActive:]
 <NSThread: 0x6000038a7540>{number = 8, name = (null)}

通過日志可以確定能夠獲取子線程的符號,同時說明__sanitizer_cov_trace_pc_guard的回調(diào)是多線程的。所以當(dāng)我們通過回調(diào)收集函數(shù)名稱時也要保證線程安全。

  • 以下案例我們使用線程相對安全原子隊列進(jìn)行返回地址的收集
#import <libkern/OSAtomic.h>

//定義原子隊列 
static OSQueueHead symbolList = OS_ATOMIC_QUEUE_INIT;

//定義結(jié)構(gòu)體 
typedef struct { 
    void *pc; 
    void *next; 
} SYNode;

void __sanitizer_cov_trace_pc_guard(uint32_t *guard)  {
    void *PC = __builtin_return_address(0);
    //創(chuàng)建結(jié)構(gòu)體
    SYNode *node = malloc(sizeof(SYNode));
    *node = (SYNode){PC, NULL};
    //結(jié)構(gòu)體入棧
    //offsetof:參數(shù)1傳入類型,將下一個節(jié)點的地址返回給參數(shù)
    OSAtomicEnqueue(&symbolList, node, offsetof(SYNode, next));
}

// 生成order文件
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { 
    while (YES) {
        SYNode *node = OSAtomicDequeue(&symbolList, offsetof(SYNode, next)); 
        //取空則停止循環(huán) 
        if(node == NULL){ 
            break; 
        } 
        Dl_info info; 
        dladdr(node->pc, &info); 
        NSLog(@"%s", info.dli_sname);
    }
}

原子隊列保存符號步驟

  • 定義: 定義原子隊列、結(jié)構(gòu)體,pc存儲當(dāng)前返回地址,next存儲下一個節(jié)點地址
  • 收集:
    創(chuàng)建結(jié)構(gòu)體,對pc賦值,next設(shè)置為NULL
    結(jié)構(gòu)體入棧
    offsetof:宏,參數(shù)1傳入類型,將下一個節(jié)點的地址返回給參數(shù)2
  • 生成order文件
    循環(huán)讀取node,取空則停止循環(huán)
    將返回地址寫入Dl_info結(jié)構(gòu)體
    打印符號名稱

運行工程,點擊屏幕觸發(fā)touchesBegan方法產(chǎn)生死循環(huán)

// 控制臺打印
 -[ViewController touchesBegan:withEvent:]
 -[ViewController touchesBegan:withEvent:]
 -[ViewController touchesBegan:withEvent:]
 -[ViewController touchesBegan:withEvent:]
 -[ViewController touchesBegan:withEvent:]
......

解決循環(huán)引發(fā)的天坑

上面運行工程產(chǎn)生了死循環(huán),下面進(jìn)行調(diào)試

  • touchesBegan方法中設(shè)置斷點,運行項目查看匯編代碼,發(fā)現(xiàn)touchesBegan方法中插入了三次__sanitizer_cov_trace_pc_guard函數(shù)的調(diào)用
image.png

這就是循環(huán)引發(fā)的天坑,SanitizerCoverage不但攔截方法、函數(shù)Block,還會對循環(huán)進(jìn)行HOOK。
案例中while循環(huán)被HOOK,循環(huán)的執(zhí)行會進(jìn)入回調(diào)函數(shù)。回調(diào)函數(shù)中存入隊列的還是touchesBegan的函數(shù)地址,這會導(dǎo)致隊列中永遠(yuǎn)存在一個到兩個touchesBegan,next永遠(yuǎn)獲取不完。

解決辦法:
Build Setting -> Other C Flags中,將配置修改為-fsanitize-coverage=func,trace-pc-guard對其增加func參數(shù)

image.png
  • 再次運行項目,點擊屏幕,控制臺打印如下
 -[ViewController touchesBegan:withEvent:]
 -[SceneDelegate sceneDidBecomeActive:]
 -[SceneDelegate sceneWillEnterForeground:]
 -[ViewController viewDidLoad]
 -[SceneDelegate window]
 -[SceneDelegate window]
 -[SceneDelegate window]
 -[SceneDelegate scene:willConnectToSession:options:]
 -[SceneDelegate window]
 -[SceneDelegate window]
 -[SceneDelegate setWindow:]
 -[SceneDelegate window]
 -[AppDelegate application:didFinishLaunchingWithOptions:]
 main
 +[ViewController load]

修改配置項僅攔截方法的調(diào)用,成功解決循環(huán)引發(fā)的天坑。

取反&去重

還有幾個問題需要解決?

  • 過濾掉自身touchesBegan的函數(shù)名稱
  • 不是OC的函數(shù)Block等符號,需要在符號名稱之前增加_
  • 相同的函數(shù)符號,需要進(jìn)行去重
  • 隊列原則先進(jìn)后出,所以我們需要的符號順序需要反轉(zhuǎn)

修改touchesBegan方法,解決上述問題

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // 定義數(shù)組
    NSMutableArray<NSString *> *symbolNames = [NSMutableArray array]; 
    while (YES) { 
        SYNode *node = OSAtomicDequeue(&symbolList, offsetof(SYNode, next)); 
        if(node == NULL){
             break; 
        } 
        Dl_info info; 
        dladdr(node->pc, &info); 
        // 轉(zhuǎn)字符串
        NSString *name = @(info.dli_sname); 
        // 不是OC函數(shù)名稱添加_,獲取符號名稱,如果不是+[和-[開頭,視為函數(shù)或Block,前面加_
        BOOL isObjc = [name hasPrefix:@"+["] || [name hasPrefix:@"-["];
        NSString * symbolName = isObjc ? name : [@"_" stringByAppendingString:name];
        [symbleNames addObject:symbolName];
    } 
    // 反向遍歷數(shù)組
    symbolNames = (NSMutableArray<NSString *> *)[[symbolNames reverseObjectEnumerator] allObjects]; 
    NSLog(@"%@",symbleNames); 
}

// 運行工程,控制臺打印
 TraceDemo[47155:640473] (
    "+[ViewController load]",
    "_main",
    "-[AppDelegate application:didFinishLaunchingWithOptions:]",
    "-[SceneDelegate window]",
    "-[SceneDelegate setWindow:]",
    "-[SceneDelegate window]",
    "-[SceneDelegate window]",
    "-[SceneDelegate scene:willConnectToSession:options:]",
    "-[SceneDelegate window]",
    "-[SceneDelegate window]",
    "-[SceneDelegate window]",
    "-[ViewController viewDidLoad]",
    "-[ViewController setAge:]",
    "-[SceneDelegate sceneWillEnterForeground:]",
    "-[SceneDelegate sceneDidBecomeActive:]",
    "-[ViewController touchesBegan:withEvent:]"
)

相同符號去重

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // 定義數(shù)組
    NSMutableArray<NSString *> *symbolNames = [NSMutableArray array]; 
    while (YES) { 
        SYNode *node = OSAtomicDequeue(&symbolList, offsetof(SYNode, next)); 
        if(node == NULL){
             break; 
        } 
        Dl_info info; 
        dladdr(node->pc, &info); 
        // 轉(zhuǎn)字符串
        NSString *name = @(info.dli_sname); 
        // 給OC函數(shù)名稱添加_
        BOOL isObjc = [name hasPrefix:@"+["] || [name hasPrefix:@"-["];
        NSString * symbolName = isObjc ? name : [@"_" stringByAppendingString:name];
        [symbleNames addObject:symbolName];
    } 
    // 反向遍歷數(shù)組
    NSEnumerator * em = [symbleNames reverseObjectEnumerator];
    NSMutableArray * funcs = [NSMutableArray arrayWithCapacity:symbleNames.count];
    NSString * name;
    while (name = [em nextObject]) {
        // 如果符號名稱不在數(shù)組中,添加到數(shù)組
        if (![funcs containsObject:name]) {//去重:數(shù)組沒有name
            [funcs addObject:name];
        }
    }
    //去掉當(dāng)前函數(shù)名稱touchesBegan
    [funcs removeObject:[NSString stringWithFormat:@"%s",__func__]];
    
    NSLog(@"%@",funcs);
}

// 運行工程,控制臺打印
TraceDemo[47196:643780] (
    "+[ViewController load]",
    "_main",
    "-[AppDelegate application:didFinishLaunchingWithOptions:]",
    "-[SceneDelegate window]",
    "-[SceneDelegate setWindow:]",
    "-[SceneDelegate scene:willConnectToSession:options:]",
    "-[ViewController viewDidLoad]",
    "-[ViewController setAge:]",
    "-[SceneDelegate sceneWillEnterForeground:]",
    "-[SceneDelegate sceneDidBecomeActive:]"
)

生成order文件

  • 修改touchesBegan方法,將符號列表寫入.order文件
// 添加load方法與block
+(void)load {
    block();
}

void(^block)(void) = ^(void){
    NSLog(@"block函數(shù)執(zhí)行!");
};

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // 定義數(shù)組
    NSMutableArray<NSString *> *symbolNames = [NSMutableArray array]; 
    while (YES) { 
        SYNode *node = OSAtomicDequeue(&symbolList, offsetof(SYNode, next)); 
        if(node == NULL){
             break; 
        } 
        Dl_info info; 
        dladdr(node->pc, &info); 
        // 轉(zhuǎn)字符串
        NSString *name = @(info.dli_sname); 
        // 給OC函數(shù)名稱添加_
        BOOL isObjc = [name hasPrefix:@"+["] || [name hasPrefix:@"-["];
        NSString * symbolName = isObjc ? name : [@"_" stringByAppendingString:name];
        [symbleNames addObject:symbolName];
    } 
    // 反向遍歷數(shù)組
    NSEnumerator * em = [symbleNames reverseObjectEnumerator];
    NSMutableArray * funcs = [NSMutableArray arrayWithCapacity:symbleNames.count];
    NSString * name;
    while (name = [em nextObject]) {
        // 如果符號名稱不在數(shù)組中,添加到數(shù)組
        if (![funcs containsObject:name]) {//去重:數(shù)組沒有name
            [funcs addObject:name];
        }
    }
    //去掉當(dāng)前函數(shù)名稱touchesBegan
    [funcs removeObject:[NSString stringWithFormat:@"%s",__func__]];
    
    //寫入文件
    //1.編程字符串
    NSString * funcStr = [funcs componentsJoinedByString:@"\n"];
    NSString * filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"hank.order"];
    NSData * file = [funcStr dataUsingEncoding:NSUTF8StringEncoding];
    [[NSFileManager defaultManager] createFileAtPath:filePath contents:file attributes:nil];
    
    NSLog(@"%@",funcStr);
}

// 運行工程,控制臺打印
+[ViewController load]
_block_block_invoke
_main
-[AppDelegate application:didFinishLaunchingWithOptions:]
-[SceneDelegate window]
-[SceneDelegate setWindow:]
-[SceneDelegate scene:willConnectToSession:options:]
-[ViewController viewDidLoad]
-[ViewController setAge:]
-[SceneDelegate sceneWillEnterForeground:]
-[SceneDelegate sceneDidBecomeActive:]
  • 拿到.order文件選擇Add Additional Simulators...
image.png
  • 選中案例App點擊Downlad Container...,如下圖
image.png
  • 選擇存放路徑下載.xcappdata文件,右鍵顯示包內(nèi)容,在AppData/tmp目錄下找到.order文件
  • .order文件拷貝到工程根目錄,在Build Setting -> Order File進(jìn)行配置
image.png
  • Build Settings -> Write Link Map File設(shè)置為YES
image.png
  • 編譯項目打開LinkMap文件查看,發(fā)現(xiàn)配置生效二進(jìn)制重排成功
image.png

Swift符號覆蓋

  • 創(chuàng)建SwiftTest.swift文件代碼如下
import UIKit

class SwiftTest: NSObject {
    @objc class public func swiftTest(){
        print("Swift Test ...")
    }
}
  • ViewControllerload方法中分別調(diào)用BlockswiftTest方法
+(void)load
{
    [SwiftTest swiftTest];
    block();
}

void(^block)(void) = ^(void){
    NSLog(@"block函數(shù)執(zhí)行!");
};
  • Other C Flags中的配置僅對Clang編譯器生效。而Swift使用swiftc編譯器,要想獲得swift函數(shù)符號,需要對Other Swift Flags添加-sanitize-coverage=func、-sanitize=undefined兩項
image.png
  • 運行項目,點擊屏幕,查看控制臺輸出內(nèi)容
+[ViewController load]
_$s9TraceDemo9SwiftTestC05swiftD0yyFZTo
_$s9TraceDemo9SwiftTestC05swiftD0yyFZ
_$ss5print_9separator10terminatoryypd_S2StFfA0_
_$ss5print_9separator10terminatoryypd_S2StFfA1_
_block_block_invoke
_main
-[AppDelegate application:didFinishLaunchingWithOptions:]
-[SceneDelegate window]
-[SceneDelegate setWindow:]
-[SceneDelegate scene:willConnectToSession:options:]
-[ViewController viewDidLoad]
-[ViewController setAge:]
-[SceneDelegate sceneWillEnterForeground:]
-[SceneDelegate sceneDidBecomeActive:]

OC和Swift的混編工程中,成功得到Swift函數(shù)符號

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容