__attribute((used, section("__DATA,"#sectname" ")))

在 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)注冊等場景。

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

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