在 Objective-C/C/C++ 中,__attribute((used, section("__DATA,"#sectname" "))) 是一个 编译器属性(GCC/Clang 扩展),用于将变量或函数 强制保留在指定的二进制段(section)中,主要用于底层开发(如静态数据注册、二进制分析、动态链接等场景)。
一、属性拆解与作用
1. __attribute((...))
这是 GCC/Clang 编译器提供的扩展语法,用于向编译器传递额外的编译指令(如变量存储位置、函数属性等)。
2. used 子属性
- 作用:强制编译器保留该符号(变量 / 函数),即使它在代码中没有被显式使用。
- 背景:编译器默认会优化掉 “未使用的符号”(如未被引用的全局变量),used 可以阻止这种优化,确保符号被保留在最终的二进制文件中。
3. section("__DATA,"#sectname" ") 子属性
- 作用:将符号存储到二进制文件的 指定段(section) 中。
- __DATA 是 Mach-O 二进制格式(iOS/macOS 可执行文件格式)中的一个 段(segment),用于存储数据(与存储代码的 __TEXT 段对应)。
- sectname 是 宏字符串化操作,表示段内的 “子段(section name)”,例如 #sectname 为 mysection 时,最终段名是 __DATA,mysection。
- 段名格式必须严格遵循 __SEGMENT,section(注意逗号后无空格,末尾可带空格但会被忽略),否则编译器可能无法识别。
二、使用场景与示例
原理:通过在编译时期将函数存储到App二进制的段(Segment)中的节(Section)中。在运行时的某些时期(比如App启动后、tabbarVC/VC初始化后等时机)将函数从二进制中取出并调用函数。以达到替换系统Load函数的效果。主要用于App的启动优化。
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、获取存储在二进制段segment中section名为“__register_load”中的函数,并执行函数");
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、在函数响应方法中,加载需要放在load里的代码");
}
- (void)viewDidLoad {
[super viewDidLoad];
// 注册load方法
NSLog(@"1、在合适的时机注册load方法");
[SegmentManager registerLoad];
}
@end
3、打印结果
1、在合适的时机注册load方法
2、获取存储在二进制段segment中section名为“__register_load”中的函数,并执行函数
3、在函数响应方法中,加载需要放在load里的代码
github 源码:EXESegment
三、关键说明
- Mach-O 段与节:在 iOS/macOS 的 Mach-O 格式中,二进制文件由多个 segment(段)组成,每个段包含多个 section(节)。__DATA 段用于存储可读写数据,__TEXT 段用于存储代码(只读)。自定义段必须放在 __DATA 或其他可读写段中(若放 __TEXT 会因只读导致无法修改)。
- used 的必要性:若变量未被显式引用,编译器会默认将其优化掉(即使指定了 section)。used 属性强制保留变量,确保它被写入目标段。
- 运行时访问:可通过系统 API(如 getsectbyname、getsectiondata)在运行时获取自定义段的地址和大小,从而遍历段内数据(如示例中的回调注册)。
- 局限性:
仅适用于 GCC/Clang 编译器(iOS/macOS 开发默认使用 Clang),不兼容 MSVC 等其他编译器。
自定义段中的数据必须是 编译期可知的静态数据(全局变量、静态变量),无法动态添加。
四、常见用途
- 静态注册:如插件、回调、命令表等,无需手动调用注册函数,编译时自动写入段,运行时遍历初始化。
- 二进制标记:在二进制中嵌入版本号、构建信息等元数据,可通过工具(如 otool)直接读取。
- 底层钩子:配合动态链接器(dyld)实现启动时初始化(如 __mod_init_func 段的原理类似)。
五、总结
__attribute((used, section("__DATA,"#sectname"))) 是 iOS/macOS 底层开发中用于 静态数据段管理 的强大工具,通过将数据强制存储到自定义段,实现运行时的批量访问和初始化,常见于系统框架、插件化、静态注册等场景。