链接
Posted ourfor
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了链接相关的知识,希望对你有一定的参考价值。
安装工具
我的机器环境是:macOS Mojave 10.14.4 18E226 x86_64
,开始之前,得在机器上面安装一些工具:
- gcc
- binutils (readelf,objdump)
因为我在自己的机器上面安装了 brew这个包管理工具,以及zsh
这个 Shell,所以我就通过brew install gcc binutils
就安装好了gcc
、objdump
和readelf
这3个命令,值得注意的是,由于macOS上面也提供了和binutils
相同功能的工具,我们就需要手动将这两个命令的路径添加到环境变量里面:
echo ‘export PATH="/usr/local/opt/binutils/bin:$PATH"‘ >> ~/.zshrc #使用bash的话,就添加到.bashrc里面 exec $SHELL #刷新下环境变量
如果需要让编译器找到这些命令,还需要额外添加:
export LDFLAGS="-L/usr/local/opt/binutils/lib" export CPPFLAGS="-I/usr/local/opt/binutils/include"
因为macOS并没有使用elf
作为可执行文件的格式,所以我得在linux下面编译文件,以前学jsp的使用写了个fedora
的镜像构建脚本,打开了ssh,这样编译好的文件就可以通过scp
来传输到宿主机器。不过为了方便我还是挂载了一个目录到fedora。
docker pull ourfor/tomcat docker run --privileged --name asm -d \ -v /sys/fs/cgroup:/sys/fs/cgroup:ro \ -v $PWD:/root:rw \ -h docker.server -p 4040:8080 -p 2020:22 \ -p 9906:3306 \ -t ourfor/tomcat
创建一个名为asm
的容器,同时将当前目录挂载到/root
目录
fedora上面的包管理工具有yum
和dnf
,为了方便,我还是安装下gcc
和binutils
以及vim
dnf install gcc binutils vim -y
在fedora里面编译好,再打开一个Terminal,到挂载的共享目录就可以查看编译好的文件
这个结果和fedora里面用readelf
看到的结果是一样的:
ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2‘s complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: EXEC (Executable file) Machine: Advanced Micro Devices X86-64 Version: 0x1 Entry point address: 0x401020 Start of program headers: 64 (bytes into file) Start of section headers: 16360 (bytes into file) Flags: 0x0 Size of this header: 64 (bytes) Size of program headers: 56 (bytes) Number of program headers: 11 Size of section headers: 64 (bytes) Number of section headers: 28 Section header string table index: 27
要用到的工具我们都安装完了。
链接
比如我们在Shell
下面输入下面的命令来编译main.c
和sum.c
这两个文件
gcc -Og -o prog main.c sum.c
它实际上经过了下面??几个过程:
(cpp,ccl,as)
(ld)
(cpp,ccl,as)
完全链接的可执行目标文件
sum.c源码:
int sum(int *a,int n) int i, s = 0; for(i=0;i<n;i++) s += a[i]; return s;
main.c源码:
int sum(int *a,int n); int array[2] = 1,2; int main() int val = sum(array,2); return val;
接下来使用gcc编译这两个文件为可重定位文件:
gcc -c main.c sum.c
得到main.o
和sum.o
这两个可重定位文件,使用 -S 可以得到汇编文件(-masm=intel可以得到intel格式汇编,见118页),比如下面的main.c
得到的汇编代码:
.section __TEXT,__text,regular,pure_instructions .build_version macos, 10, 14 sdk_version 10, 14 .intel_syntax noprefix .globl _main ## -- Begin function main .p2align 4, 0x90 _main: ## @main .cfi_startproc ## %bb.0: push rbp .cfi_def_cfa_offset 16 .cfi_offset rbp, -16 mov rbp, rsp .cfi_def_cfa_register rbp sub rsp, 16 mov dword ptr [rbp - 4], 0 mov al, 0 call _swap xor eax, eax add rsp, 16 pop rbp ret .cfi_endproc ## -- End function .section __DATA,__data .globl _buf ## @buf .p2align 2 _buf: .long 1 ## 0x1 .long 2 ## 0x2 .subsections_via_symbols
目标文件
目标文件有三种格式:
- 可重定位目标文件。 包含二进制数据,其形式可以在编译时与其他可重定位目标文件合并起来,创建一个可执行的目标文件
- 可执行目标文件。包含二进制数据,其形式可以被直接复制到内存并执行
- 共享目标文件。一种特殊类型的可重定位目标文件,可以在加载或者运行时被动态的加载进内存并链接
以前在编译httpd
的时候就了解了这三种文件,比如Apache的模块就是共享目标文件
,Apache连接tomcat的mod_jk.so就是这种类型的,可执行文件就是编译好可以直接运行的文件,在使用make命令编译的时候,如果遇到库丢失,安装好依赖后,不会再重新编译,而是在编译好的.o
文件的基础上面继续编译其它没有编译的源文件。
可重定位目标文件
一个典型的ELF可重定位目标文件的格式如下表所示。ELF头以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。
节 | ELF头 |
.text | |
.rodata | |
.data | |
.bss | |
.symtab | |
.rel.text | |
.rel.data | |
.debug | |
.line | |
.strtab | |
描述目标文件的节 | 节头部表 |
在Computer Systems A Programmer‘s Perspective Third Edition这本书的练习题7.1里面有这样两个源文件:
m.c
void swap(); int buf[2] = 1,2; int main() swap(); return 0;
swap.c
extern int buf[]; int *bufp0 = &buf[0]; int *bufp1; void swap() int temp; bufp1 = &buf[1]; temp = *bufp0; *bufp0 = *bufp1; *bufp1 = temp;
使用命令:gcc -c m.c swap.c
得到两个可重定位的目标文件,分别是m.o
和swap.o
,接下来用readelf
来查看m.o
的符号表:
Symbol table ‘.symtab‘ contains 11 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 FILE LOCAL DEFAULT ABS m.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 6 6: 0000000000000000 0 SECTION LOCAL DEFAULT 7 7: 0000000000000000 0 SECTION LOCAL DEFAULT 5 8: 0000000000000000 8 OBJECT GLOBAL DEFAULT 3 buf 9: 0000000000000000 21 FUNC GLOBAL DEFAULT 1 main 10: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND swap
因为swap
和main
是全局函数,由于m.c调用了swap.c里面定义的函数,还没有将这两个文件编译成一个可执行文件,所以在这里swap
显示UND
(表示未定义的符号),保存在.data
这一节里面,buf
是在 m.c里面初始化的全局变量,也是保存在.data
里面.
同样的我们来查看下swap.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 swap.c 2: 0000000000000000 0 SECTION LOCAL DEFAULT 1 3: 0000000000000000 0 SECTION LOCAL DEFAULT 3 4: 0000000000000000 0 SECTION LOCAL DEFAULT 5 5: 0000000000000000 0 SECTION LOCAL DEFAULT 7 6: 0000000000000000 0 SECTION LOCAL DEFAULT 8 7: 0000000000000000 0 SECTION LOCAL DEFAULT 6 8: 0000000000000000 8 OBJECT GLOBAL DEFAULT 3 bufp0 9: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND buf 10: 0000000000000008 8 OBJECT GLOBAL DEFAULT COM bufp1 11: 0000000000000000 60 FUNC GLOBAL DEFAULT 1 swap
在这里,我们可以看到bufp1
的Ndx
显示为COM
(表示未初始化的全局变量),buf是在m.c
里面定义的全局变量,在swap.c
里面声明时用关键字extern
指出这是一个外部符号,所以它Ndx
这一项显示UND
。
将这两个重定位文件编译成一个可执行的目标文件gcc -o prog m.o swap.o
,在使用readelf
查看prog
的符号表:
.... 72: 0000000000404020 8 OBJECT GLOBAL DEFAULT 21 bufp0 73: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__ 74: 0000000000402008 0 OBJECT GLOBAL HIDDEN 13 __dso_handle 75: 0000000000402000 4 OBJECT GLOBAL DEFAULT 13 _IO_stdin_used 76: 0000000000401160 101 FUNC GLOBAL DEFAULT 11 __libc_csu_init 77: 0000000000404040 0 NOTYPE GLOBAL DEFAULT 22 _end 78: 0000000000401050 5 FUNC GLOBAL HIDDEN 11 _dl_relocate_static_pie 79: 0000000000401020 47 FUNC GLOBAL DEFAULT 11 _start 80: 0000000000404028 8 OBJECT GLOBAL DEFAULT 21 buf 81: 0000000000404030 0 NOTYPE GLOBAL DEFAULT 22 __bss_start 82: 0000000000401142 21 FUNC GLOBAL DEFAULT 11 main 83: 0000000000404030 0 OBJECT GLOBAL HIDDEN 21 __TMC_END__ 84: 0000000000401106 60 FUNC GLOBAL DEFAULT 11 swap 85: 0000000000401000 0 FUNC GLOBAL HIDDEN 10 _init 86: 0000000000404038 8 OBJECT GLOBAL DEFAULT 22 bufp1
可以看到这里面buf
和swap
都可以正确显示.
符号解析
- 不允许有多个同名的强符号.
- 如果有一个强符号和多个弱符号同名,那么选择弱符号.
- 如果有多个弱符号同名,那么从这些弱符号中任意选择一个.
以上是关于链接的主要内容,如果未能解决你的问题,请参考以下文章
linux命令:ln 链接文件--硬链接,符号链接(软链接)