attribute 编译指令的使用

3,952 阅读5分钟

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文件的数据分布:

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];
    }

BeeHive

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));

3、文章引用

attribute used section iOS简单应用

__attribute__详解及应用

dyld编码

mach-o文件讲解