链接

Posted ourfor

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了链接相关的知识,希望对你有一定的参考价值。

安装工具

我的机器环境是:macOS Mojave 10.14.4 18E226 x86_64,开始之前,得在机器上面安装一些工具:

  • gcc
  • binutils (readelf,objdump)

因为我在自己的机器上面安装了 brew这个包管理工具,以及zsh这个 Shell,所以我就通过brew install gcc binutils就安装好了gccobjdumpreadelf这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上面的包管理工具有yumdnf,为了方便,我还是安装下gccbinutils以及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.csum.c这两个文件

gcc -Og -o prog main.c sum.c 

它实际上经过了下面??几个过程:

 
main.o
 
sum.o
链接
main.c
翻译器
(cpp,ccl,as)
链接器
(ld)
sum.c
翻译器
(cpp,ccl,as)
prog
完全链接的可执行目标文件

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.osum.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.oswap.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 

因为swapmain是全局函数,由于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 

在这里,我们可以看到bufp1Ndx显示为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 

可以看到这里面bufswap都可以正确显示.

符号解析

  1. 不允许有多个同名的强符号.
  2. 如果有一个强符号和多个弱符号同名,那么选择弱符号.
  3. 如果有多个弱符号同名,那么从这些弱符号中任意选择一个.
技术图片

以上是关于链接的主要内容,如果未能解决你的问题,请参考以下文章

Linux动态链接和静态链接简析

软链接和硬链接详解

linux命令:ln 链接文件--硬链接,符号链接(软链接)

Linux中有两种不同的文件链接类别。 符号链接(又称软链接) 硬链接

软链接和硬链接

Linux软链接和硬链接