配置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的配置

- 編譯工程有如下報錯

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

- 按照文檔,在項目中加入如下代碼
#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)從start至stop的地址中,存儲的是uint32_t類型的值 - 循環(huán)中
x為uint32_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é) - 從
start至stop,讀出值為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)用

- 繼續(xù)調(diào)試,進(jìn)入該函數(shù)的斷點,由
didFinishLaunchingWithOptions函數(shù)調(diào)用

我們發(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;
}

set方法成功HOOK
Clang插樁的原理
__sanitizer_cov_trace_pc_guard方法添加斷點,點擊屏幕觸發(fā)touchesBegan方法進(jìn)行調(diào)試

在每一個方法/函數(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)用

這就是循環(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ù)

- 再次運行項目,點擊屏幕,控制臺打印如下
-[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...

- 選中
案例App點擊Downlad Container...,如下圖

- 選擇
存放路徑下載.xcappdata文件,右鍵顯示包內(nèi)容,在AppData/tmp目錄下找到.order文件 - 將
.order文件拷貝到工程根目錄,在Build Setting->Order File進(jìn)行配置

- 在
Build Settings->Write Link Map File設(shè)置為YES

-
編譯項目打開LinkMap文件查看,發(fā)現(xiàn)配置生效二進(jìn)制重排成功

Swift符號覆蓋
- 創(chuàng)建
SwiftTest.swift文件代碼如下
import UIKit
class SwiftTest: NSObject {
@objc class public func swiftTest(){
print("Swift Test ...")
}
}
- 在
ViewController的load方法中分別調(diào)用Block和swiftTest方法
+(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兩項

- 運行項目,點擊屏幕,查看控制臺輸出內(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ù)符號