Contents
  1. 1. compiler driver
  2. 2. 库函数
  3. 3. 动态链接
  4. 4. 小结

  刚刚看完了CSAPP的linking一章,试着简单回顾一下。

  C语言允许将工程分为多个文件单独管理和编译,这对模块化编程是很好地支持。Linking则将多个代码与数据片断连接起来成为一个完整的可很执行程序。

  需要注意多个文件中定义同名(数据类型可同可不同)全局变量是允许的,只要它们最多只有一个进行了初始化,这其实是一个很容易导致运行时才发现错误的特性,所以有时候可以使用gcc参数-warn-common来提示这些同名的全局变量。

compiler driver

  假设一个工程包含两个文件swap.cmain.c,我们可以将它们一起编译为一个可执行文件

1
gcc main.c swap.c -o myapp

  这其中其它包含了预处理、汇编、编译和链接的过程,我们可以将它们分开执行。

1
cpp main.c tmp/main.i

  如果包含了标准库,一般会生成一个很大的文本文件。

1
2
3
4
5
6
7
8
9
835 extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
836 # 943 "/usr/include/stdio.h" 3 4
837
838 # 3 "main.c" 2
839
840 void swap();
841
842 int buf[2] = {1, 2};
843 int main(){

  可以将此预处理后的文件转换为汇编文件。

1
gcc -S main.c

  这将自动生成一个名为main.s的文本文件

1
2
3
4
5
6
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movl $1, -8(%rbp)
movl $3, -4(%rbp)
leaq -4(%rbp), %rdx

  可以将其编译为目标文件

1
as main.s -o main.o

  这已经是一个二进制文件,可以使用readelf,objdump查看它的相关信息.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
##反编译汇编代码
p$ objdump -d main.o
main.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <main>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 48 83 ec 10 sub $0x10,%rsp
8: c7 45 f8 01 00 00 00 movl $0x1,-0x8(%rbp)
f: c7 45 fc 03 00 00 00 movl $0x3,-0x4(%rbp)
16: 48 8d 55 fc lea -0x4(%rbp),%rdx
1a: 48 8d 45 f8 lea -0x8(%rbp),%rax
1e: 48 89 d6 mov %rdx,%rsi
21: 48 89 c7 mov %rax,%rdi
24: b8 00 00 00 00 mov $0x0,%eax
29: e8 00 00 00 00 callq 2e <main+0x2e>
2e: 8b 55 fc mov -0x4(%rbp),%edx
31: 8b 45 f8 mov -0x8(%rbp),%eax
34: 89 c6 mov %eax,%esi
36: bf 00 00 00 00 mov $0x0,%edi
3b: b8 00 00 00 00 mov $0x0,%eax
40: e8 00 00 00 00 callq 45 <main+0x45>
45: c9 leaveq
46: c3 retq
##查看elf格式相关段,以下为symtab段信息
$ readelf -s main.o
Symbol table '.symtab' contains 12 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS main.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 3
4: 0000000000000000 0 SECTION LOCAL DEFAULT 4
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000000 0 SECTION LOCAL DEFAULT 7
7: 0000000000000000 0 SECTION LOCAL DEFAULT 8
8: 0000000000000000 0 SECTION LOCAL DEFAULT 6
9: 0000000000000000 71 FUNC GLOBAL DEFAULT 1 main
10: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND swap
11: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND printf

  使用ld可以将多个目标文件链接成一个可执行文件

1
gcc main.o swap.o -o p

库函数

  C语言中将许多常用的函数的目标文件用archive格式进行组织,形成一个个函数库,方便开发。例如默认包含的libc.a(其中包含printf等函数的目标文件),用于数学计算的libm.a等。
  我们也可以生成自己的函数库。

1
2
##将两个relocatable目标文件打包成库文件archive
ar rcs libvector.a addvec.o multvec.o

  使用该库进行编译,static意为生成一个不依赖任何外部目标文件的可执行文件

1
2
gcc -static main.c libvector.a
##一般将库放在后面,否则易出错,同一文件可重复出现在参数中

动态链接

  动态链接可在运行时加载指定代码,多个进程可共享同一内存片段,具有相当多的优点。我们可以将自己所写方法生成动态函数库。

1
$ gcc -fPIC -shared swap2.c -o libswap.so

使用动态库,需要用到dlfcn.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
main(){
int buf[] = {3, 5};
void (*swap)(int*, int*);
char* error;
void* handle = dlopen("./libswap.so",RTLD_LAZY);
if(!handle){
fprintf(stderr,"%s\n",dlerror());
exit(1);
}
swap = dlsym(handle, "swap");
if( (error = dlerror()) != NULL){
fprintf(stderr,"%s\n",dlerror());
exit(1);
}
swap(buf,buf+1);
printf("buf = [%d, %d]\n",buf[0],buf[1]);
if(0>dlclose(handle)){
fprintf(stderr,"%s\n",dlerror());
exit(1);
}
}

  使用以下命令进行编译运行,注意-rdynamic, main2.c-ldl的位置关系。

1
2
3
$gcc -rdynamic -O2 main2.c -ldl -o p
$ ./p
buf = [5, 3]

小结

  CSAPP的这一节中其实重点在讲relocatable目标文件的组织结构,以及如何从源文件生成及它又如何转换为executable目标文件,但几乎每一句都是重要内容所以不方便摘录,推荐阅读原文。这里只是记录了一些实际操作相关。

Contents
  1. 1. compiler driver
  2. 2. 库函数
  3. 3. 动态链接
  4. 4. 小结