iOS工程化「八」符号

344 阅读6分钟

Mach-O

Mach-O(Mach Object)是macOSiOSiPadOS存储程序和库的文件格式。对应系统通过应用二进制接口(application binary interface,缩写为 ABI)来运行该格式的文件。

Mach-O格式用来替代BSD系统的a.out格式。Mach-O文件格式保存了在编译过程和链接过程中产生的机器代码和数据,从而为静态链接和动态链接的代码提供了单一文件格式。

Mach header:包含二进制信息的头文件。

创建一个macOS的命令行工程,先创建一个全局函数,这个函数作用域是整个exec可执行文件都可以访问这个函数:

void global_function(void) {
    printf("global_function");
}

除了global还有一种local符号,只会在当前使用它的文件。我们再定义一个local的函数:

static void staticFunc() {
    printf("staticFunc");
}

我们再新建一个空的.m文件,里面也声明staticFunc并且实现,在main.m中的main函数执行staticFunc();,编译不会报错。 我想现在看下它现在使用的符号怎么看呢?objdump这个命令,-t可以显示当前MachO文件的符号按globallocal给详细的展示出来。创建config文件,写入命令:

MACH_PATH=${BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/${PROJECT_NAME}

// objdump -t
CMD = objdump --macho -t ${MACH_PATH}
TTY=/dev/ttys000

编译,终端中就显示了符号表中的信息了:

image.png

l代表localg代表global。我们项目中写了两个staticFunc但是符号表中只有一个,中间d代表debug,中间那些是调试符号信息,如果想去掉这个干扰,给当前链接器传递一个参数OTHER_LDFLAGS = -Xlinker -S

这个含义就是当.o文件通过链接器生成exec可执行文件的时候,不会把.o文件的调试符号放到生成的可执行文件中。再重新编译:

image.png

发现确实只有一个staticFunc,因为local符号如果没有使用就不会放到exec中。我们写个代码去调用一个这个函数:

@interface StaticFunc : NSObject
@end
static void staticFunc() {
    printf("staticFunc");
}
@implementation StaticFunc
+ (void)load
{
    staticFunc();
}
@end

image.png

现在有两个staticFunc符号了,不同符号调用对应不同地址。 为什么static可以定义两个同名函数,global却不行呢?local可以通过不同地址调用到不同的实现中去,当前文件就近调用,先找到谁就用谁。global全局唯一的,同名符号local却可以定义,只要不在当前文件,就可以定义。

image.png

比如我们导入三方库,他们声明了同名全局函数该怎么办?能不能把golbal改成local或者把local改成golbal呢? 答案肯定是可以的,我们只需要给编译器传递参数:OTHER_LDFLAGS = -Xlinker -S -Xlinker -unexported_symbol -Xlinker _global_function,再编译就可以看到原来的golbal变成local了。

image.png

现在我把main.m文件不导入fundation#import <stdio.h>,这时候编译是成功的,但是我如果使用NSLog的话,就会报错,找不到这个声明。我们手动添加这个声明void NSLog(id format, ...);再编译,是否能成功呢?也能成功??!!我们把main.m改成main.mm文件再编译,就会报找不到这个符号,怀疑.mOC文件会自动把fundation导入进去。在编译main文件的时候是成功的,报错点在于链接生成exec文件的时候。

Foundation是动态库dylib,手机是存在手机,当前调试状态这个库是存在Mac里面。运行的时候才会找Foundation符号表里面的NSLog去调用。

链接器有个命令-U,把这个符号设为动态查找,也就是把这个符号查找退后到运行时查找所有链接的动态库里面查找。在xcconfig中设置:OTHER_LDFLAGS = -Xlinker -S -Xlinker -U -Xlinker _NSLog,进行编译发现还是报错?因为我们之前把main文件改为了.mm,是C++文件。 我们OC文件查找A B两个动态库的时候,如果他们有相同符号名称,调用的时候会有问题吗?答案是没有问题,针对动态查找机制,我们链接器做了两级命名空间,到底是执行哪个实现就是先找到谁就执行谁。 C++就没有这个二级机制,就需要告诉链接器直接去找这个符号,不要管它在哪里。把二级改成一级:OTHER_LDFLAGS = -Wl, -flat_namespace -Xlinker -undefined -Xlinker suppress,再去编译就可以成功了。

函数调用分为两步:1.在其他的.o文件能不能找到函数的实现,如果有直接实现。2.如果找不到,就看是不是在某个链接的动态库里面的符号,记录下在哪个库,运行的时候就去那个动态库动态的查找。

编译可以成功,我们command + R运行,发现崩溃了:

image.png

因为C++默认Foundation并没有在内存中,也没有去链接Foundation这个库,所以就找不到这个实现崩溃。


现在再创建个包含动态库的工程,需求很简单就是调用一下动态库代码。动态库下载地址。直接在工程链接一下动态库即可。本来调用顺序是导入头文件后再进行调用,但是现在我不想导入头文件想直接调用。

void SJDyFunc();
int main(int argc, const char * argv[]) {
    SJDyFunc();
    return 0;
}

进行编译运行,都可以成功。因为framework在同一个workspace中,编译的时候会一起编译,就会找到这个符号。那能不能在framework中使用工程中的符号呢?

main.m中定义一个全局函数:

void globalFunc() {
    NSLog(@"globalFunc");
}

在动态库中调用:

void globalFunc();
void SJDyFunc () {
    globalFunc();
    NSLog(@"SJDyFunc");
}

进行编译就会报错,所以我们要创建config文件来让编译器忽略掉这个符号的检测。

OTHER_LDFLAGS = -Xlinker -U -Xlinker _globalFunc

之后再编译就不会报错了。 App可以依赖动态库中符号,动态库也可以依赖App中的符号。链接器参数-upward_framework,这个参数只是会在mach-o文件中生成个标志,让别人看到这个标志时知道这个framework反向依赖App中符号。

我们创建个继承NSObject的类,看下符号:

image.png

可以看到,obj类符号都是global的,如果再创建一个同名类,报错原因就是有两个同名全局符号导致的。
如果开发中两个库包含了相同名称的类,并且不是动态库,静态库是.o文件的合集,静态库.o和应用程序的.o做合并的时候,两个静态库有同名类,就会报符号冲突了。
.m文件生成.o的过程中,除了生成一个符号表,还会有一个重定位符号表,标记哪些符号需要找具体的信息。当执行到这个重定位符号时,会去其他的.o文件找对应的符号实现,找到了替换,没找到就会报错。

如果有同名符号,我们可以针对一个.o里面的符号进行重命名,要进行符号重命名,要用到llvm-objcopy,这个和strip是一样的。修改符号名--redefine-sym=old=new或者--redefine-syms=filename