在 Objective-C/C/C++ 中,__attribute((used, section("__DATA,"#sectname" "))) 是一個(gè) 編譯器屬性(GCC/Clang 擴(kuò)展),用于將變量或函數(shù) 強(qiáng)制保留在指定的二進(jìn)制段(section)中,主要用于底層開發(fā)(如靜態(tài)數(shù)據(jù)注冊、二進(jìn)制分析、動態(tài)鏈接等場景)。
一、屬性拆解與作用
1. __attribute((...))
這是 GCC/Clang 編譯器提供的擴(kuò)展語法,用于向編譯器傳遞額外的編譯指令(如變量存儲位置、函數(shù)屬性等)。
2. used 子屬性
- 作用:強(qiáng)制編譯器保留該符號(變量 / 函數(shù)),即使它在代碼中沒有被顯式使用。
- 背景:編譯器默認(rèn)會優(yōu)化掉 “未使用的符號”(如未被引用的全局變量),used 可以阻止這種優(yōu)化,確保符號被保留在最終的二進(jìn)制文件中。
3. section("__DATA,"#sectname" ") 子屬性
- 作用:將符號存儲到二進(jìn)制文件的 指定段(section) 中。
- __DATA 是 Mach-O 二進(jìn)制格式(iOS/macOS 可執(zhí)行文件格式)中的一個(gè) 段(segment),用于存儲數(shù)據(jù)(與存儲代碼的 __TEXT 段對應(yīng))。
- sectname 是 宏字符串化操作,表示段內(nèi)的 “子段(section name)”,例如 #sectname 為 mysection 時(shí),最終段名是 __DATA,mysection。
- 段名格式必須嚴(yán)格遵循 __SEGMENT,section(注意逗號后無空格,末尾可帶空格但會被忽略),否則編譯器可能無法識別。
二、使用場景與示例
原理:通過在編譯時(shí)期將函數(shù)存儲到App二進(jìn)制的段(Segment)中的節(jié)(Section)中。在運(yùn)行時(shí)的某些時(shí)期(比如App啟動后、tabbarVC/VC初始化后等時(shí)機(jī))將函數(shù)從二進(jìn)制中取出并調(diào)用函數(shù)。以達(dá)到替換系統(tǒng)Load函數(shù)的效果。主要用于App的啟動優(yōu)化。
1、聲明
SegmentManager.h
#import <Foundation/Foundation.h>
typedef void (*LoadRegisterCallback)(void);
#define KLoadRegisterSegmentName "__DATA"
#define kLoadRegisterSectionName "__register_load"
#define KLoadRegister_Data __attribute((used, section(KLoadRegisterSegmentName "," kLoadRegisterSectionName )))
// 編譯保存Load
#define AppLoadRegister(loadName) \
static void LoadRegister##loadName();\
static LoadRegisterCallback varLoadRegister##loadName KLoadRegister_Data = LoadRegister##loadName;\
static void LoadRegister##loadName
NS_ASSUME_NONNULL_BEGIN
@interface SegmentManager : NSObject
+ (void)registerLoad;
@end
NS_ASSUME_NONNULL_END
SegmentManager.m
#import "SegmentManager.h"
#import <objc/runtime.h>
#import <objc/message.h>
#include <mach-o/getsect.h>
#include <mach-o/loader.h>
#include <mach-o/dyld.h>
#include <dlfcn.h>
static void LoadRegisterRun(const char * segmentName,const char *sectionName){
Dl_info info;
int ret = dladdr(LoadRegisterRun, &info);
if (ret == 0) {
// fatal error
}
#ifndef __LP64__
const struct mach_header *mhp = (struct mach_header*)info.dli_fbase;
unsigned long size = 0;
uint32_t *memory = (uint32_t*)getsectiondata(mhp, segmentName, sectionName, & size);
#else
const struct mach_header_64 *mhp = (struct mach_header_64*)info.dli_fbase;
unsigned long size = 0;
uint64_t *memory = (uint64_t*)getsectiondata(mhp, segmentName, sectionName, & size);
#endif
if(size == 0){
return;
}
for(int idx = 0; idx < size/sizeof(void*); ++idx){
LoadRegisterCallback func = (LoadRegisterCallback)memory[idx];
NSLog(@"2、獲取存儲在二進(jìn)制段segment中section名為“__register_load”中的函數(shù),并執(zhí)行函數(shù)");
func();
}
}
@implementation SegmentManager
+ (void)registerLoad {
LoadRegisterRun(KLoadRegisterSegmentName,kLoadRegisterSectionName);
}
@end
2、使用
ViewController.m
#import "ViewController.h"
#import "SegmentManager.h"
@interface ViewController ()
@end
@implementation ViewController
AppLoadRegister(ViewController)() {
// 類似于load方法
NSLog(@"3、在函數(shù)響應(yīng)方法中,加載需要放在load里的代碼");
}
- (void)viewDidLoad {
[super viewDidLoad];
// 注冊load方法
NSLog(@"1、在合適的時(shí)機(jī)注冊load方法");
[SegmentManager registerLoad];
}
@end
3、打印結(jié)果
1、在合適的時(shí)機(jī)注冊load方法
2、獲取存儲在二進(jìn)制段segment中section名為“__register_load”中的函數(shù),并執(zhí)行函數(shù)
3、在函數(shù)響應(yīng)方法中,加載需要放在load里的代碼
github 源碼:EXESegment
三、關(guān)鍵說明
- Mach-O 段與節(jié):在 iOS/macOS 的 Mach-O 格式中,二進(jìn)制文件由多個(gè) segment(段)組成,每個(gè)段包含多個(gè) section(節(jié))。__DATA 段用于存儲可讀寫數(shù)據(jù),__TEXT 段用于存儲代碼(只讀)。自定義段必須放在 __DATA 或其他可讀寫段中(若放 __TEXT 會因只讀導(dǎo)致無法修改)。
- used 的必要性:若變量未被顯式引用,編譯器會默認(rèn)將其優(yōu)化掉(即使指定了 section)。used 屬性強(qiáng)制保留變量,確保它被寫入目標(biāo)段。
- 運(yùn)行時(shí)訪問:可通過系統(tǒng) API(如 getsectbyname、getsectiondata)在運(yùn)行時(shí)獲取自定義段的地址和大小,從而遍歷段內(nèi)數(shù)據(jù)(如示例中的回調(diào)注冊)。
- 局限性:
僅適用于 GCC/Clang 編譯器(iOS/macOS 開發(fā)默認(rèn)使用 Clang),不兼容 MSVC 等其他編譯器。
自定義段中的數(shù)據(jù)必須是 編譯期可知的靜態(tài)數(shù)據(jù)(全局變量、靜態(tài)變量),無法動態(tài)添加。
四、常見用途
- 靜態(tài)注冊:如插件、回調(diào)、命令表等,無需手動調(diào)用注冊函數(shù),編譯時(shí)自動寫入段,運(yùn)行時(shí)遍歷初始化。
- 二進(jìn)制標(biāo)記:在二進(jìn)制中嵌入版本號、構(gòu)建信息等元數(shù)據(jù),可通過工具(如 otool)直接讀取。
- 底層鉤子:配合動態(tài)鏈接器(dyld)實(shí)現(xiàn)啟動時(shí)初始化(如 __mod_init_func 段的原理類似)。
五、總結(jié)
__attribute((used, section("__DATA,"#sectname"))) 是 iOS/macOS 底層開發(fā)中用于 靜態(tài)數(shù)據(jù)段管理 的強(qiáng)大工具,通過將數(shù)據(jù)強(qiáng)制存儲到自定義段,實(shí)現(xiàn)運(yùn)行時(shí)的批量訪問和初始化,常見于系統(tǒng)框架、插件化、靜態(tài)注冊等場景。