1、概念
attribute是一个编译器指令,可以对一些变量、函数、类型进行特定的制定。
2、attribute 使用
2.1 在main函数调用完之后进行调用
__attribute__((destructor))
static void afterMain(void) {
NSLog(@"main 函数执行完执行");
}
2.2在main函数调用之前进行调用,但是在+load方法之后
//在mian函数之前执行
__attribute__((constructor))
static void preMain(void) {
NSLog(@"我在main函数之前执行了");
}
2.3设置变量销毁的时候会调方法
///clean up 的方法 参数类型必须和变量的类型一致
static void cleanUp(NSString **obj) {
NSLog(@"变量销毁了 %@", *obj);
}
{
//cleanUp方法执行的方法是先进后出。先执行name2对应的cleanUp
NSString *name __attribute__((cleanup(cleanUp))) = @"zhaoyan";
NSString *name2 __attribute__((cleanup(cleanUp))) = @"hhhadasf";
name2 = @"12312312";
// NSObject *obj __attribute__((cleanup(cleanUp))) = NSObject.new;
}
2.4 used 、section 使用方式
2.4.1 attriubte(used) 用法
告诉编译器该**静态变量**要在改对象文件中保留(尽管该变量没有被引用)
2.4.2 attribute((section)) 用法
mach-o文件的数据分布:

也可以使用otool -lV excufilepath 来查看可执行文件的命令加载指令
//第一个参数指定的segment名称、第二个参数指定的是section名称
__attribute__((section("__Text,new_section")))
static void nocall(void) {
NSLog(@"12312313");
}
注意事项:对工程执行下clean,在进行 build。 通过
otool命令可以查看new_section
Load command 4
cmd LC_SEGMENT_64
cmdsize 152
segname __Text
vmaddr 0x0000000100007000
vmsize 0x0000000000001000
fileoff 28672
filesize 4096
maxprot rw-
initprot rw-
nsects 1
flags (none)
Section
sectname new_section
segname __Text
addr 0x0000000100007000
size 0x0000000000000017
offset 28672
align 2^4 (16)
reloff 0
nreloc 0
type S_REGULAR
attributes (none)
reserved1 0
reserved2 0
__attribute__((section("__Text,new_section")))该语句中__Text是程序编译的时候现有的的segment name。当然我们也可以指定自己的section的name例如这样__attribute__((section("__Zhaoyan,zhao"))),通过otool命令我们可以查看到改segement
Load command 4
cmd LC_SEGMENT_64
cmdsize 152
segname __Zhaoyan
vmaddr 0x0000000100007000
vmsize 0x0000000000001000
fileoff 28672
filesize 4096
maxprot rw-
initprot rw-
nsects 1
flags (none)
Section
sectname zhao
segname __Zhaoyan
addr 0x0000000100007000
size 0x0000000000000017
offset 28672
align 2^4 (16)
reloff 0
nreloc 0
type S_REGULAR
attributes (none)
reserved1 0
reserved2 0
2.4.3 如何操作动态库
_dyld_register_func_for_add_image: image load 的回调方式,会返回image 对应的mach-header指针。
dladdr(void *add, DL_info)//BSD定义的接口:查找地址范围包含add指针的image文件
ypedef 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 : image的path dli_fbase : image 的首地址,也就是mach-header 地址。 dli_sname:离add地址最近的symbol dli_saddr: symbol的地址
#import <mach-o/dyld.h>
_dyld_register_func_for_add_image(imageAdd);
_dyld_register_func_for_remove_image(imageRemove);
//判断mach_header 是64位还是32位,因为不同的架构mach_header的size不一样
static bool imageMachHeaderIs64(struct mach_header *mh) {
if (mh->magic == MH_MAGIC_64 || mh->magic ==MH_CIGAM_64) {
return YES;
}
return NO;
}
//返回mach_header 根据不同的架构返回大小
static size_t imageMachHeaderSize(const struct mach_header *mh) {
if (imageMachHeaderIs64(mh)) {
return sizeof(struct mach_header_64);
}
return sizeof(struct mach_header);
}
//根据上面mach-o文件的结构图,我们知道mach-o文件是有mach-header、loadcommad和数据构成。所以mach_header之后的地址就是loadcommand的地址。
static void imageVisitLoadCommand(const struct mach_header *mh, void(^visitor)(struct load_command *lc, bool *stop)) {
assert(visitor);
uintptr_t lcCursor = (uintptr_t)mh + imageMachHeaderSize(mh);
bool stop = false;
for (int i = 0 ; i < mh->ncmds; i ++) {
struct load_command * lc = (struct load_command *)lcCursor;
visitor(lc, &stop);
if (stop) {
break;
}
lcCursor += lc->cmdsize;
}
}
// load command 的结构体都是在 mach-o/load.h
static void imageRetrieveUUID(const struct mach_header *mh) {
__block const struct uuid_command *UUIDCmd = NULL;
imageVisitLoadCommand(mh, ^(struct load_command *lc, bool *stop) {
if (lc == NULL) {
return ;
}
if (lc->cmd == LC_UUID) {
UUIDCmd = (const struct uuid_command *)lc;
*stop = YES;
}
});
NSLog(@"uuid: %s", UUIDCmd->uuid);
}
// 计算__TEXT段的大小
static void imageTextSegmentSize(const struct mach_header *mh) {
const char * textSegment = "__TEXT";
__block uint64_t textSegmentSize = 0;
imageVisitLoadCommand(mh, ^(struct load_command *lc, bool *stop) {
if (lc == NULL) {
return ;
}
if (lc->cmd == LC_SEGMENT) {
struct segment_command * segmentCommand = (struct segment_command *)lc;
if(strcmp(segmentCommand->segname, textSegment) == 0) {
textSegmentSize = segmentCommand->vmsize;
}
}
if (lc->cmd == LC_SEGMENT_64) {
struct segment_command_64 * segmentCommand = (struct segment_command_64 *)lc;
if(strcmp(segmentCommand->segname, textSegment) == 0) {
textSegmentSize = segmentCommand->vmsize;
}
}
});
NSLog(@"text segment size : %llu", textSegmentSize);
}
static void imageAdd(const struct mach_header *mh, intptr_t intp) {
Dl_info dlInfo;
void * add = (void *)(mh + 1);
int result = dladdr(add, &dlInfo);
if (result == 0) {
NSLog(@"无法获取动态库的信息");
return;
}
imageRetrieveUUID(mh);
imageTextSegmentSize(mh);
}
上面的代码只是说明了mach_header 和 loadcommand。但是并没有涉及到segment 和 section。获取数据苹果给我们提供给了很好的API ,API定义在<mach-o/getsect.h>。
#ifndef __LP64__
uintptr_t *memory = (uintptr_t*)getsectiondata(mhp, SEG_DATA, sectionName, &size);
#else
const struct mach_header_64 *mhp64 = (const struct mach_header_64 *)mhp;
uintptr_t *memory = (uintptr_t*)getsectiondata(mhp64, SEG_DATA, sectionName, &size);
#endif
unsigned long counter = size/sizeof(void*);
for(int idx = 0; idx < counter; ++idx){
char *string = (char*)memory[idx];
NSString *str = [NSString stringWithUTF8String:string];
if(!str)continue;
NSLog(@"config = %@", str);
if(str) [configs addObject:str];
}
2.5 nonull
参数不能为空指针
-
1、参数的个数是从1,开始计算的。name是1,j是2,a是3,如果nonnull的参数设置成0,那么就会报错“参数超出范围”
-
2、nonnull只能设定形参类型是指针类型的。例如下面的例子__attribute__((nonnull(3, 2))) 3是 int a,但是a并不是一个指针类型的形参,会报错
- (void)test:(NSString *)name and:(NSString *)j and:(int)a __attribute__((nonnull(3, 2))){
NSLog(@"%@%@%d",name, j, a);
}
2.6 objc_runtime_name
更改runtime的类名或者协议名
- objc_rutime_name 只能用的类的声明处,不能使用在分类或者扩展的声明处
__attribute__((objc_runtime_name("zhaoyanclass")))
@interface Person : NSObject
@end
@implementation Person
@end
// zhaoyanclass
NSLog(@"--%@", NSStringFromClass(Person.class));