Skip to content

Commit

Permalink
initial
Browse files Browse the repository at this point in the history
  • Loading branch information
jokerYellow committed May 9, 2020
0 parents commit f4dfe72
Show file tree
Hide file tree
Showing 37 changed files with 445 additions and 0 deletions.
185 changes: 185 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
# linkDemo
该项目是对静态链接以及动态链接下,符号重复情况的一个验证。

## 执行
在每个目录下,都有 makefile 文件,包括静态库动态库的制作,以及链接、运行等过程。可以直接执行,缺什么就去 make 什么。

```sh
$ make command
```

## 概念

### 符号
符号用来方便的对变量以及函数进行引用。
* 全局符号,也称为外部符号,可供外部模块使用。如 global、f 为全局符号。
* 局部符号,带有 static 属性的为局部符号,不能被其他模块使用。如local,只在foo模块里可见,而f里的x只在f函数里可见。
```c
# foo.c
int global = 1;
static int local = 10;
int f(){
static int x = 0;
return x;
}
```

### 目标文件

每个代码文件都会被编译称为一个目标文件,如 main.c、foo.m 文件都会被编译成对应的 main.o、foo.o 文件。目标文件是可以重定位的。

```sh
$ gcc -c -o foo.o foo.c
$ gcc -c -lobjc foo.m -o foom.o
```

### 可执行文件

链接目标文件 main.o、foo.o 到标准库,成为可执行文件。

```sh
$ ld main.o foo.o -lc -o a.out
```
默认程序入口为 main 函数,链接时也可以指定程序入口。
可执行文件是可以直接运行的。

### 静态库

将 foo.o、foom.o 文件处理成 libfoo.a 文件,称为静态库。静态库其实是将 foo.o、foom.o 合并成了一个文件,其头部含有各个文件的偏移量以及大小。

```sh
$ ar rcs libfoo.a foo.o foom.o
```
如果 main.o 依赖 libfoo.a,在链接 main.o 成为可执行文件时,指定 libfoo.a 进行链接,libfoo.a 会被链接进最后的 a.out 文件里

```sh
$ gcc -o static.out main.c foo/libfoo.a ; ./static.out
```

### 动态库

将 foo.o 文件处理成 libfoo.so 文件,称为动态库

```sh
# -fPIC表示开启地址无关选项,只有地址无关才适合动态加载
$ gcc -g -fPIC -c foo.c
# -lobjc 表示编译objective-c文件
$ gcc -c -lobjc -g -fPIC foo.m -o foom.o
$ gcc -shared foo.o foom.o -framework Foundation -o libfoo.so
```

如果 main.o 依赖 libfoo.so,
在链接 main.o 成为可执行文件时,会在 a.out 添加 libfoo.so 的路径以及库名、重定位、符号表等信息,a.out 在启动时会寻找 libfoo.so 库进行加载。

```sh
$ gcc -g -c main.m
$ export DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:foo1 ;ld main.o -lc foo1/libfoo.so -framework Foundation
$ ./a.out
```

a.out 启动时会在 $DYLD_LIBRARY_PATH 环境变量寻找以及加载 libfoo.so 库。
同时也会加载 Foundation 库,用来支持 Objective-C 。
-lc 加载 C 的标准库。

动态库优势:

* 可以动态的更新程序,只需要替换 libfoo.so 文件就可以修改程序行为了。
* 多个可执行文件可以依赖同一个动态库。
* 节省内存,代码段在内存里只有一份。

## 静态库符号解析过程

链接器从左到右按照命令行上出现的顺序来扫描可重定位目标文件和存档文件。
* E:可重定位目标文件集合,这个集合里的文件会被合并起来形成可执行文件。
* U:未解析的符号集合,引用了但是尚未定义的符号,比如用 extern 引用,或者引入了 h 文件。
* D:一个在前面输入文件重已定义的符号集合。

对于命令行的每个输入文件 f:

1. f 为目标文件,链接器把 f 加到 E 集合,修改 U 和 D 来反映 f 中的符号定义和引用。
2. f 为静态库,链接器尝试匹配 U 中未解析的符号和静态库里的目标文件定义的符号。如静态库里的目标文件 m,定义了一个符号来解析 U 里面的一个引用,那么就把 m 加到 E 中,并且链接器修改 U 和 D 来反映 m 中的符号定义和引用。轮询完静态库里的所有的目标文件,直到 U 和 D 不再变化。此时静态库里不包含在 E 里的目标文件,会被丢弃。
3. 命令行里所有的文件处理完毕后,假如 U 不为空,链接器会报错。 U 为空则表示所有的引用符号都已经成功匹配,接下来链接器会将 E 集合里的所有目标文件进行合并以及重定位,构建可执行文件。

## 静态库符号重复

经过验证,静态库加载时,会默认选择首次加载的符号。U 集合里的符号被匹配之后,就会被从 U 集合里去掉,不再匹配。

```sh
$ make runstaticfoo1fisrtSameName
gcc -lobjc -o a.out main.m foo1/libfoo.a foo/libfoo.a -framework Foundation
./a.out
2020-05-08 18:40:31.028 a.out[19865:181170] [Foo name] is foo1

$ make runstaticfoofirstSameName
gcc -lobjc -o a.out main.m foo/libfoo.a foo1/libfoo.a -framework Foundation
./a.out
2020-05-08 18:41:45.210 a.out[19948:181814] [Foo name] is foo

$ make runstaticfoo1fisrtDifferentName
gcc -lobjc -o a.out main.m foo1/libfoo1.a foo/libfoo.a -framework Foundation
./a.out
2020-05-08 18:43:51.154 a.out[20164:183068] [Foo name] is foo1

$ make runstaticfoofirstDifferentName
gcc -lobjc -o a.out main.m foo/libfoo.a foo1/libfoo1.a -framework Foundation
./a.out
2020-05-08 18:44:25.282 a.out[20209:183371] [Foo name] is foo
```
## 动态库加载
动态链接器来处理动态链接,会维护一个**全局符号表**。动态链接器从可执行文件里提取需要查找的动态库,查找动态库以及将动态库里的符号添加到**全局符号表**。所有的动态库都装载完毕后,接下来会通过**全局符号表**对动态引用的符号进行重定向。

### 全局符号介入

当一个符号需要被加入到**全局符号表**时,如果相同的符号名已经存在,则后加入的符号忽略。
这会导致一个动态库里的符号被另一个动态库的同名符号所替代,这个叫**全局符号介入**

## 动态库符号重复

动态库名称重复,会认为是相同的库,默认加载最先找到的一个库

```sh
$ make rundynamicfoofirstSameLibraryName
gcc -g -c main.m
export DYLD_LIBRARY_PATH=YLD_LIBRARY_PATH:foo:foo1 ; ld main.o -lc foo/libfoo.so foo1/libfoo.so -framework Foundation ; ./a.out
2020-05-08 18:27:57.913 a.out[19150:174944] [Foo name] is foo

$ make rundynamicfoo1firstSameLibraryName
gcc -g -c main.m
export DYLD_LIBRARY_PATH=YLD_LIBRARY_PATH:foo:foo1 ;ld main.o -lc foo1/libfoo.so foo/libfoo.so -framework Foundation ; ./a.out
2020-05-08 18:28:06.088 a.out[19184:175091] [Foo name] is foo
```

动态库名称不重复符号重复的情况,默认会选择最早加载的库。如上**全局符号介入**

````sh
$ make rundynamicfoofirstDifferentLibraryName
gcc -g -c main.m
export DYLD_LIBRARY_PATH=YLD_LIBRARY_PATH:foo:foo1 ; ld main.o -lc foo/libfoo.so foo1/libfoo1.so -framework Foundation ; ./a.out
objc[19012]: Class Foo is implemented in both /Users/mi/Documents/code/linkdemo/foo1/libfoo1.so (0x1066450d8) and /Users/mi/Documents/code/linkdemo/foo/libfoo.so (0x1066410d8). One of the two will be used. Which one is undefined.
2020-05-08 18:27:15.759 a.out[19012:174315] [Foo name] is foo

$ make rundynamicfoo1firstDifferentLibraryName
gcc -g -c main.m
export DYLD_LIBRARY_PATH=YLD_LIBRARY_PATH:foo:foo1 ;ld main.o -lc foo1/libfoo1.so foo/libfoo.so -framework Foundation ; ./a.out
objc[40702]: Class Foo is implemented in both /Users/pipasese/Documents/miCode/linkdemo/foo/libfoo.so (0x1069c50d8) and /Users/pipasese/Documents/miCode/linkdemo/foo1/libfoo1.so (0x1069bf0d8). One of the two will be used. Which one is undefined.
2020-05-09 10:00:44.785 a.out[40702:1267961] [Foo name] is foo1
````

## 动态库与静态库重复

会使用静态库的代码,静态库链接时已经写入可执行文件,动态库启动时链接会忽略。

````sh
$ make runStaticAndDynamicMultipleSymbols
gcc -g -c main.m
export DYLD_LIBRARY_PATH=YLD_LIBRARY_PATH:foo ; ld main.o foo1/libfoo.a -lc foo/libfoo.so -framework Foundation ; ./a.out
objc[40771]: Class Foo is implemented in both /Users/pipasese/Documents/miCode/linkdemo/foo/libfoo.so (0x104c690d8) and /Users/pipasese/Documents/miCode/linkdemo/./a.out (0x104c5e0f8). One of the two will be used. Which one is undefined.
2020-05-09 10:01:05.218 a.out[40771:1268288] [Foo name] is foo1
````

## 结论
不论是动态库还是静态库的场景,默认都是使用最先加载的符号。

## 参考资料
* 《深入理解计算机系统》链接
* 《程序员的自我修养》动态链接
19 changes: 19 additions & 0 deletions foo.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// Foo.h
// Test
//
// Created by mi on 2020/5/8.
// Copyright © 2020 mi. All rights reserved.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Foo : NSObject

-(NSString*)name;

@end

NS_ASSUME_NONNULL_END
5 changes: 5 additions & 0 deletions foo/foo.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#include "stdio.h"

char* name(){
return "this message is from foofoofoofoofoo!\n";
}
19 changes: 19 additions & 0 deletions foo/foo.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// Foo.h
// Test
//
// Created by mi on 2020/5/8.
// Copyright © 2020 mi. All rights reserved.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Foo : NSObject

-(NSString*)name;

@end

NS_ASSUME_NONNULL_END
9 changes: 9 additions & 0 deletions foo/foo.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#import "Foo.h"

@implementation Foo

-(NSString*)name{
return @"[Foo name] is foo";
}

@end
Binary file added foo/foo.o
Binary file not shown.
Binary file added foo/foom.o
Binary file not shown.
Binary file added foo/libfoo.a
Binary file not shown.
Binary file added foo/libfoo.so
Binary file not shown.
12 changes: 12 additions & 0 deletions foo/makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
libfoo.a:foo.c
gcc -c -o foo.o foo.c
gcc -c -lobjc foo.m -o foom.o
ar rcs libfoo.a foo.o foom.o

libfoo.so:foo.c
gcc -g -fPIC -c foo.c
gcc -c -lobjc -g -fPIC foo.m -o foom.o
gcc -shared foo.o foom.o -framework Foundation -o libfoo.so

clean:
rm -f foo.o libfoo.a libfoo.so
5 changes: 5 additions & 0 deletions foo1/foo.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#include "stdio.h"

char* name(){
return "this message is from foo1foo1foo1foo1foo1!\n";
}
19 changes: 19 additions & 0 deletions foo1/foo.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// Foo.h
// Test
//
// Created by mi on 2020/5/8.
// Copyright © 2020 mi. All rights reserved.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Foo : NSObject

-(NSString*)name;

@end

NS_ASSUME_NONNULL_END
9 changes: 9 additions & 0 deletions foo1/foo.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#import "Foo.h"

@implementation Foo

-(NSString*)name{
return @"[Foo name] is foo1";
}

@end
Binary file added foo1/foo.o
Binary file not shown.
Binary file added foo1/foom.o
Binary file not shown.
Binary file added foo1/libfoo.a
Binary file not shown.
Binary file added foo1/libfoo.so
Binary file not shown.
Binary file added foo1/libfoo1.a
Binary file not shown.
Binary file added foo1/libfoo1.so
Binary file not shown.
22 changes: 22 additions & 0 deletions foo1/makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
libfoo.a:foo.c
gcc -c -o foo.o foo.c
gcc -c -lobjc foo.m -o foom.o
ar rcs libfoo.a foo.o foom.o

libfoo.so:foo.c
gcc -g -fPIC -c foo.c
gcc -c -lobjc -g -fPIC foo.m -o foom.o
gcc -shared foo.o foom.o -framework Foundation -o libfoo.so

libfoo1.a:foo.c
gcc -c -o foo.o foo.c
gcc -c -lobjc foo.m -o foom.o
ar rcs libfoo1.a foo.o foom.o

libfoo1.so:foo.c
gcc -g -fPIC -c foo.c
gcc -c -lobjc -g -fPIC foo.m -o foom.o
gcc -shared foo.o foom.o -framework Foundation -o libfoo1.so

clean:
rm -f libfoo.a libfoo.so foo.o foom.o libfoo1.so libfoo1.a
6 changes: 6 additions & 0 deletions includefoo/car.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

extern char* name();

char* fooname(){
return name();
}
Binary file added includefoo/car.o
Binary file not shown.
Binary file added includefoo/libUseFooButIncludeFoo.a
Binary file not shown.
Binary file added includefoo/libUseFooButNotIncludeFoo.a
Binary file not shown.
11 changes: 11 additions & 0 deletions includefoo/makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
libUseFooButNotIncludeFoo.a:car.c
gcc -c -o car.o car.c
ar rcs libUseFooButNotIncludeFoo.a car.o

libUseFooButIncludeFoo.a:car.c
gcc -c -o car.o car.c
ar rcs tmp.a car.o
libtool -static tmp.a ../foo/libfoo.a -o libUseFooButIncludeFoo.a

clean:
rm -f libUseFooButNotIncludeFoo.a libUseFooButIncludeFoo.a car.o tmp.a
Binary file added includefoo/tmp.a
Binary file not shown.
6 changes: 6 additions & 0 deletions includefoo1/car.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

extern char* name();

char* fooname(){
return name();
}
Binary file added includefoo1/car.o
Binary file not shown.
Binary file added includefoo1/libUseFooButIncludeFoo1.a
Binary file not shown.
11 changes: 11 additions & 0 deletions includefoo1/makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
libUseFooButNotIncludeFoo.a:car.c
gcc -c -o car.o car.c
ar rcs libUseFooButNotIncludeFoo.a car.o

libUseFooButIncludeFoo1.a:car.c
gcc -c -o car.o car.c
ar rcs tmp.a car.o
libtool -static tmp.a ../foo1/libfoo1.a -o libUseFooButIncludeFoo1.a

clean:
rm -f libUseFooButNotIncludeFoo1.a libUseFooButIncludeFoo1.a car.o tmp.a
Binary file added includefoo1/tmp.a
Binary file not shown.
Binary file added includefooMain/a.out
Binary file not shown.
9 changes: 9 additions & 0 deletions includefooMain/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#include "stdio.h"

extern char* fooname();

int main(int argc, char const *argv[])
{
printf("%s",fooname());
return 0;
}
Binary file added includefooMain/main.o
Binary file not shown.
22 changes: 22 additions & 0 deletions includefooMain/makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
run:
gcc -c main.c;
ld main.o ../includefoo/libUseFooButNotIncludeFoo.a ../foo/libfoo.a ../foo1/libfoo.a -lc
./a.out

run1:
gcc -c main.c;
ld main.o ../includefoo/libUseFooButIncludeFoo.a -lc
./a.out

run2:
gcc -c main.c;
ld main.o ../includefoo/libUseFooButIncludeFoo.a ../includefoo1/libUseFooButIncludeFoo1.a -lc
./a.out

run3:
gcc -c main.c;
ld main.o ../includefoo1/libUseFooButIncludeFoo1.a ../includefoo/libUseFooButIncludeFoo.a -lc
./a.out

clean:
rm a.out main.o
Loading

0 comments on commit f4dfe72

Please sign in to comment.