linux 动态连接

动态连接是现在操作系统中程序的默认使用方式,非常重要。但是搞懂动态连接你必须真正掌握静态连接。不然你是看不明白的

为什么要动态连接

在只有静态连接的世界里,所有的代码在运行之会被连接器做最后的加工,分配好运行地址。类似下面这样。

  4004ed:	55                   	push   %rbp
  4004ee:	48 89 e5             	mov    %rsp,%rbp
  4004f1:	48 83 ec 10          	sub    $0x10,%rsp
  4004f5:	c7 45 fc 01 00 00 00 	movl   $0x1,-0x4(%rbp)
  4004fc:	48 8d 45 fc          	lea    -0x4(%rbp),%rax
  400500:	48 89 c6             	mov    %rax,%rsi
  400503:	bf 2c 10 60 00       	mov    $0x60102c,%edi 
                                               ^------变量地址 被反汇编器翻译之后的地址
                ^------------指令+数据,因为x86是小端存储,所以绝对地址是反过来的存储的                            
  400508:	e8 07 00 00 00       	callq  400514 <swap>
                                               ^------函数地址,被反汇编器翻译之后的地址
               ^------------指令+数据,07开始的数据是相对地址
  40050d:	b8 00 00 00 00       	mov    $0x0,%eax
  400512:	c9                   	leaveq 
  400513:	c3                   	retq   

静态连接就是把上面绝对地址跳转和相对地址跳转全部写死,整个程序作为一台精密的机器在运行。

而动态连接目的就是无论A程序的代码加载到哪个地址。B,C,D…程序都能访问到它。(我们把A程序 叫做共享对象,B,C,D程序叫做可执行程序。而一堆共享对象的集合就叫共享库或者叫动态库。)为了完成这个目标。我们要解决2个问题。

  1. 共享库和可执行程序是分开的。他们如何找到彼此,可执行文件又是如何使用共享库的代码和数据的。
  2. 更进一步,共享库如何做到代码被共享,而不是影分身呢。

创建一个共享库

为了解决第一个问题我们,先创建一个共享库和访问它的可执行程序看看。

//lib.h
#ifndef LIB_H
#define LIB_H
int void fun(int i);
#endif

//lib.c
//#include
int fun(int i){
//      printf("this msg from lib.so %d\n",i);
        return i;
}

把上面的代码变成共享库。

gcc -shared -o lib.so lib.c

再创建一个访问它的可执行文件

#include"lib.h"
#include
int main(){
        printf("%d\n",fun(0));
        sleep(-1);
        return 0;
}

 gcc -o program program.c ./lib.so

运行./program 。一切正常。记住如果使用你自己的共享库一定要指定路径,不然gcc以为你用的是默认路径下的那些共享库。

接下来我们就要弄清楚程序是如何找到共享库的。回想静态连接也有一个找代码的过程那就重定位。而动态连接也是一样。但是细节上有所不同。

静态连接中我们可以这样按图索骥,elf 文件头->段表->符号表->重定位表,而动态连接也要先从段表找到相应的表才能完成动态连接的工作。我们也按顺序说。

.dynmic

如果一个elf 文件中有Dynamic段,那么它就需要动态连接。它就像目录一样。里面存着完成动态连接所需要的所有东西的地址。

(base) [root@10 桌面]# readelf  -d  program

Dynamic section at offset 0xe18 contains 25 entries:
  标记        类型                         名称/值
 0x0000000000000001 (NEEDED)             共享库:[./lib.so]
 0x0000000000000001 (NEEDED)             共享库:[libc.so.6]
 0x000000000000000c (INIT)               0x400518
 0x000000000000000d (FINI)               0x400744
 0x0000000000000019 (INIT_ARRAY)         0x600e00
 0x000000000000001b (INIT_ARRAYSZ)       8 (bytes)
 0x000000000000001a (FINI_ARRAY)         0x600e08
 0x000000000000001c (FINI_ARRAYSZ)       8 (bytes)
 0x000000006ffffef5 (GNU_HASH)           0x400298
 0x0000000000000005 (STRTAB)             0x4003d8
 0x0000000000000006 (SYMTAB)             0x4002d0
 0x000000000000000a (STRSZ)              118 (bytes)
 0x000000000000000b (SYMENT)             24 (bytes)
 0x0000000000000015 (DEBUG)              0x0
 0x0000000000000003 (PLTGOT)             0x601000
 0x0000000000000002 (PLTRELSZ)           120 (bytes)
 0x0000000000000014 (PLTREL)             RELA
 0x0000000000000017 (JMPREL)             0x4004a0
 0x0000000000000007 (RELA)               0x400488
 0x0000000000000008 (RELASZ)             24 (bytes)
 0x0000000000000009 (RELAENT)            24 (bytes)
 0x000000006ffffffe (VERNEED)            0x400468
 0x000000006fffffff (VERNEEDNUM)         1
 0x000000006ffffff0 (VERSYM)             0x40044e
 0x0000000000000000 (NULL)               0x0
 (base) [root@10 桌面]# readelf  -d  lib.so

Dynamic section at offset 0xe18 contains 24 entries:
  标记        类型                         名称/值
 0x0000000000000001 (NEEDED)             共享库:[libc.so.6]
 0x000000000000000c (INIT)               0x520
 0x000000000000000d (FINI)               0x664
 0x0000000000000019 (INIT_ARRAY)         0x200df8
 0x000000000000001b (INIT_ARRAYSZ)       8 (bytes)
 0x000000000000001a (FINI_ARRAY)         0x200e00
 0x000000000000001c (FINI_ARRAYSZ)       8 (bytes)
 0x000000006ffffef5 (GNU_HASH)           0x1f0
 0x0000000000000005 (STRTAB)             0x350
 0x0000000000000006 (SYMTAB)             0x230
 0x000000000000000a (STRSZ)              167 (bytes)
 0x000000000000000b (SYMENT)             24 (bytes)
 0x0000000000000003 (PLTGOT)             0x201000
 0x0000000000000002 (PLTRELSZ)           48 (bytes)
 0x0000000000000014 (PLTREL)             RELA
 0x0000000000000017 (JMPREL)             0x4f0
 0x0000000000000007 (RELA)               0x430
 0x0000000000000008 (RELASZ)             192 (bytes)
 0x0000000000000009 (RELAENT)            24 (bytes)
 0x000000006ffffffe (VERNEED)            0x410
 0x000000006fffffff (VERNEEDNUM)         1
 0x000000006ffffff0 (VERSYM)             0x3f8
 0x000000006ffffff9 (RELACOUNT)          3
 0x0000000000000000 (NULL)               0x0

(NEEDED) <—依赖的共享库名称
(INIT) <—初始化代码偏移量
(FINI) <—结束代码偏移量
(GNU_HASH) <—符号hash表偏移量。加快符号查找过程
(STRTAB) <—动态符号字符串表偏移量
(SYMTAB) <—指向动态连接的符号表偏移量.dynsym ,是.symtab的子集,只包含动态连接的符号。
(STRSZ) <—动态连接字符串大小
(SYMENT) <—单个动态连接符号大小
(RELA) <—动态连接重定位表偏移量
(RELASZ) <—动态连接重定位表大小
(RELAENT) <—单个动态连接重定位表元素大小

通过.dynsym我们可以找到动态符号表和动态重定位表

.dynsym

通过.dynmic表我们可以找到.dynsym,它是动态连接的符号表。这些符号同时也在.symtab(静态连接的符号表)里面。

(base) [root@10 桌面]# readelf  -sD  lib.so  <----只显示自己已经定义的符号,

Symbol table of `.gnu.hash' for image:
  Num Buc:    Value          Size   Type   Bind Vis      Ndx Name
    6   0: 0000000000201028     0 NOTYPE  GLOBAL DEFAULT  21 _edata
    7   0: 0000000000201030     0 NOTYPE  GLOBAL DEFAULT  22 _end
    8   1: 0000000000201028     0 NOTYPE  GLOBAL DEFAULT  22 __bss_start
    9   1: 0000000000000520     0 FUNC    GLOBAL DEFAULT   9 _init
   10   2: 0000000000000664     0 FUNC    GLOBAL DEFAULT  12 _fini   <---这些global函数,可以被别人使用。也叫导出函数
   11   2: 0000000000000655    12 FUNC    GLOBAL DEFAULT  11 fun
(base) [root@10 桌面]# readelf  --dyn-sym  lib.so <---显示所有定义或者引用的符号

Symbol table '.dynsym' contains 12 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterTMCloneTab
     2: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
     3: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _Jv_RegisterClasses
     4: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMCloneTable
     5: 0000000000000000     0 FUNC    WEAK   DEFAULT  UND __cxa_finalize@GLIBC_2.2.5 (2)   
     6: 0000000000201028     0 NOTYPE  GLOBAL DEFAULT   21 _edata
     7: 0000000000201030     0 NOTYPE  GLOBAL DEFAULT   22 _end
     8: 0000000000201028     0 NOTYPE  GLOBAL DEFAULT   22 __bss_start
     9: 0000000000000520     0 FUNC    GLOBAL DEFAULT    9 _init
    10: 0000000000000664     0 FUNC    GLOBAL DEFAULT   12 _fini
    11: 0000000000000655    12 FUNC    GLOBAL DEFAULT   11 fun

(base) [root@10 桌面]# readelf  --dyn-sym  program

Symbol table '.dynsym' contains 11 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND fun
     2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND printf@GLIBC_2.2.5 (2)
     3: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.2.5 (2)
     4: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
     5: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND sleep@GLIBC_2.2.5 (2)  <---这些引用别人的函数。也叫导入函数
     6: 0000000000601044     0 NOTYPE  GLOBAL DEFAULT   24 _edata
     7: 0000000000601048     0 NOTYPE  GLOBAL DEFAULT   25 _end
     8: 0000000000601044     0 NOTYPE  GLOBAL DEFAULT   25 __bss_start
     9: 0000000000400518     0 FUNC    GLOBAL DEFAULT   11 _init
    10: 0000000000400744     0 FUNC    GLOBAL DEFAULT   14 _fini

动态连接的符号表结构和静态连接时一样的,所以各个列的含义都一样,这里就不多赘述了。但是有一点要搞明白。就是上面的value值。

  • 目标文件:value就是符号所在段的偏移量。
  • 可执行文件: 因为能运行,所以value 就是符号所在的虚拟地址(也可以叫逻辑地址)

共享库可以直接运行

不管是共享库还是引用共享库的可执行文件其实他们都是可以直接运行的,对你没听错,其实共享库和可执行文件对于操作系统来说都是一样的。比如我们可以直接运行下面的so

 /lib64/ld-linux-x86-64.so.2 

你可能会问为啥不运行我们自己的llib.so呢,那当然运行会报错才不运行啦,不信你看下面。

(base) [root@10 桌面]# ./lib.so 
段错误(吐核)

为啥会这样呢,答案是2方面

  1. 共享库和可执行文件一样也有程序头,会被系统加载到内存中。我们可以看一下程序头和内存映射
(base) [root@10 桌面]# readelf -l lib.so

Elf 文件类型为 DYN (共享目标文件)
入口点 0x570
共有 7 个程序头,开始于偏移量64

程序头:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000
  									     ^-----逻辑地址都是0 。因为代码段和数据段内容的相对位置不会变,所以系统会把程序整体平移到内存的某个地方。这个过程叫做基址重置(bebasing)。
  LOAD           0x0000000000000df8 0x0000000000200df8 0x0000000000200df8
                 0x0000000000000230 0x0000000000000238  RW     200000
........

 Section to Segment mapping:
  Segment Sections...
   00     .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .eh_frame_hdr .eh_frame 
   01     .init_array .fini_array .jcr .data.rel.ro .dynamic .got .got.plt .bss 
   02     .dynamic 
   03     .note.gnu.build-id 
   04     .eh_frame_hdr 
   05     
   06     .init_array .fini_array .jcr .data.rel.ro .dynamic .got 

。。。。。。

[root@paas-controller-2:/home/ubuntu]$ cat /proc/19830/maps
555555554000-555555555000 r-xp 00000000 fd:00 67113449                   /home/ubuntu/wen/lib.so
	^-----------------可以看见装载到这了。对于进程来说0附近地址根本就不在自己的虚拟地址中,而我们的动态库中的函数都没链接,所以地址都是0,是不能访问的。
555555754000-555555756000 rw-p 00000000 fd:00 67113449                   /home/ubuntu/wen/lib.so
	^-----------------也可以看见数据段是紧接着代码段,所以代码和数据的相对位置是不变的
7ffff7ffd000-7ffff7fff000 r-xp 00000000 00:00 0                          [vdso]
7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0                          [stack]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
[root@paas-controller-2:/home/ubuntu]$ 

  1. 通过第一点我们知道,共享库运行时地址是会变的(这个过程叫做基址重置,bebasing),但是其中所有内容的相对位置是不变的,也就说动态链接生成的文件里面只能用相对地址。。我们来分析一下我们程序的运行逻辑。
(base) [root@10 桌面]# objdump -d  lib.so
。。。。。。。。。。。
Disassembly of section .text:
//可以 readelf -l 看程序的入口点
0000000000000570 <deregister_tm_clones>:                               <------程序入口
 570:   48 8d 05 b8 0a 20 00    lea    0x200ab8(%rip),%rax        # 20102f <_edata+0x7>
 577:   48 8d 3d aa 0a 20 00    lea    0x200aaa(%rip),%rdi        # 201028 <_edata>
 57e:   55                      push   %rbp
 57f:   48 29 f8                sub    %rdi,%rax                       <------%rax=%rax-%rdi=0x4
 582:   48 89 e5                mov    %rsp,%rbp
 585:   48 83 f8 0e             cmp    $0xe,%rax
 589:   77 02                   ja     58d <deregister_tm_clones+0x1d> <----- 因为$0xe>%rax 不跳转
 58b:   5d                      pop    %rbp
 58c:   c3                      retq                                   <----运行到这返回,返回退出
 58d:   48 8b 05 44 0a 20 00    mov    0x200a44(%rip),%rax        # 200fd8 <_ITM_deregisterTMCloneTable>
 594:   48 85 c0                test   %rax,%rax
 597:   74 f2                   je     58b <deregister_tm_clones+0x1b>
 599:   5d                      pop    %rbp
 59a:   ff e0                   jmpq   *%rax
 59c:   0f 1f 40 00             nopl   0x0(%rax)
。。。。。。
#现在来看返回到哪了
[root@paas-controller-2:/home/ubuntu/wen]$ gdb ./lib.so            <----使用调试器看看
###进图调试器后
(gdb) b *deregister_tm_clones                                      <----打断点
Breakpoint 1 at 0x570
(gdb) r                                                            <----r/run 运行
(gdb) info frame                                                   <----查看当前堆栈
Stack level 0, frame at 0x7fffffffe438:
 rip = 0x555555554570 in deregister_tm_clones; saved rip 0x1       <----函数的返回地址是0x1,访问不了,所以运行报错
 Arglist at 0x7fffffffe428, args: 
 Locals at 0x7fffffffe428, Previous frame's sp is 0x7fffffffe438
 Saved registers:
  rip at 0x7fffffffe430

#我们在看看寄存器和内存证实一下是不是对的,
(gdb) i registers  
。。。。
rbp            0x0      0x0                                         <---- 上一个栈帧的栈底,因为停在了进程初始化之后运行的第一个函数。所以上一个栈帧理论上是没有的。用一个非法地址填充。
rsp            0x7fffffffe430   0x7fffffffe430                      <---- 上一个栈帧的栈顶
。。。。。
rip            0x555555554570   0x555555554570 <deregister_tm_clones>
。。。。

#因为函数停在栈帧初始化之前所以这个rsp表示上个栈的栈顶。
(gdb) x  /1xg $sp
0x7fffffffe430: 0x0000000000000001                                  <---- 非法地址
 ........

而像 /lib64/ld-linux-x86-64.so.2 ,./lib64/ibc-2.17.so 这种能运行的共享库就不会像我们写的共享库那样返回到不存在的栈帧。他们最后都是走系统调用,让内核把自己整个进程销毁掉。那我们的为啥就不能向他们那样调用内核的函数呢?如果你想要和他们一样退出的有2种方法(1)手写汇编(直接触发中断),(2)调用别人的函数(系统调用)。

(1) 我们手写下面汇编代码就可以运行正常退出了,汇编解释可以看这个链接

//lib.c

int fun(int i){
	asm(
		"movl $1, %eax \n\t"  
		"movl $40, %ebx \n\t"
		"int $0x80 "
	);
    return i;
}

#编译的时候记得一定要用 -e (entry) 指定入口到哦
gcc -shared -o lib.so -e fun lib.c

发现可以完美结束。但是这个太底层了,太原始了,如果你有资源打开了的话,你还要关闭这些资源。所以不推荐这样。

(2)我们试试调用别人写的函数看看。

#include
int fun(int i){
        exit(0);
        return i;
}

你会发现按照直接的方法编译不通过,提示让你用-fPIC.

(base) [root@10 桌面]# gcc -shared -o lib.so  -e fun --save-temps lib.c 
                                                            ^--------------可以保存中间过程,下面会分析 
/bin/ld: lib.o: relocation R_X86_64_PC32 against symbol `exit@@GLIBC_2.2.5' can not be used when making a shared object; recompile with -fPIC
/bin/ld: 最后的链结失败: 错误的值
collect2: 错误:ld 返回 1
(base) [root@10 桌面]# 

那我们加上试试呢

(base) [root@10 桌面]# gcc -shared -o lib.so  -e fun  -fPIC  lib.c 
#可以编译通过了。但是运行起来还是报错
(base) [root@10 桌面]# ./lib.so
段错误(吐核)

发现还是不行。好了我们来写一个可以运行成功代码把。

#include
const char ldpath[] __attribute__ ((section (".interp")))  = "/lib64/ld-linux-x86-64.so.2";
int fun(int i){
        exit(0);
        return i;

(base) [root@10 桌面]# gcc -shared -o lib.so  -e fun  -fPIC  lib.c 
(base) [root@10 桌面]# ./lib.so

终于可以正常运行了,那么为啥要加上-fPIC和.interp就能运行呢?这就涉及到动态连接的核心思想了。还记得我们上面说的动态连接的文件里面只能使用相对地址。所以-fPIC和.interp就是在这个前提下让你用上未知地址的函数的方法。我们会在下面的章节详细解释-fPIC和.interp

fPIC

PIC全称position-indepenent code(地址无关代码),为了搞清楚这个,我们看一下上节编译失败时保存的目标文件。

(base) [root@10 桌面]# readelf -r lib.o

重定位节 '.rela.text' 位于偏移量 0x220 含有 1 个条目:
  偏移量          信息           类型           符号值        符号名称 + 加数
000000000011  000b00000002 R_X86_64_PC32     0000000000000000 exit - 4
                                ^-------报错说R_X86_64_PC32类型的exit 函数在共享对象中不能用。除非你用fPIC来编译。

重定位节 '.rela.eh_frame' 位于偏移量 0x238 含有 1 个条目:
  偏移量          信息           类型           符号值        符号名称 + 加数
000000000020  000200000002 R_X86_64_PC32     0000000000000000 .text + 0

我们加上发PIC看看会 怎么样

(base) [root@10 桌面]# gcc -shared -o lib.so  -e fun --save-temps -fPIC lib.c  

(base) [root@10 桌面]# readelf -r lib.o

重定位节 '.rela.text' 位于偏移量 0x250 含有 1 个条目:
  偏移量          信息           类型           符号值        符号名称 + 加数
000000000011  000c00000004 R_X86_64_PLT32    0000000000000000 exit - 4
                                  ^------变成这个了

重定位节 '.rela.eh_frame' 位于偏移量 0x268 含有 1 个条目:
  偏移量          信息           类型           符号值        符号名称 + 加数
000000000020  000200000002 R_X86_64_PC32     0000000000000000 .text + 0
(base) [root@10 桌面]# 

要想搞清楚这些类型干啥用的,你就要理解地址无关代码什么意思。我们lib.so加点代码

#include
const char ldpath[] __attribute__ ((section (".interp")))  = "/lib64/ld-linux-x86-64.so.2";
static int static_var; 
extern int inter_var;//可能在共享库外。也可能是在共享库的其他目标文件内
void inner_fun(){    
}

int fun(int i){
//      printf("this msg from lib.so %d\n",i);
        static_var=1;
        inter_var=100;
        inner_fun();
        exit(0);//定义在stdlib.h里面,是extern的
        return i;
}

(base) [root@10 桌面]# gcc -shared -o lib.so  -e fun --save-temps -fPIC lib.c  

我们定义了各种类型的变量和函数,有模块内的也有模块外的,我们来看它们是如何寻址的


。。。。。。
00000000000006e0 <fun>:
 6e0:   55                      push   %rbp
 6e1:   48 89 e5                mov    %rsp,%rbp
 6e4:   48 83 ec 10             sub    $0x10,%rsp
 6e8:   89 7d fc                mov    %edi,-0x4(%rbp)
 6eb:   c7 05 3f 09 20 00 01    movl   $0x1,0x20093f(%rip)        # 201034 
 		          ^----- x86是小端存储3f 09 20 00  其实就是0x20093f,此时rip=0x6f5 ,这条命令的意思就是给0x201034处的static_var变量赋值1
 6f2:   00 00 00 
 6f5:   48 8b 05 e4 08 20 00    mov    0x2008e4(%rip),%rax        # 200fe0 
                                             ^-----------------------和上面一样获得inter_var的地址
 6fc:   c7 00 64 00 00 00       movl   $0x64,(%rax)
 702:   b8 00 00 00 00          mov    $0x0,%eax
 707:   e8 e4 fe ff ff          callq  5f0 <inner_fun@plt>
 				^----------------------------------------------------跳转到 下一条指令 0x70c+0xfffffee4=0x5f0 处
               
 70c:   bf 00 00 00 00          mov    $0x0,%edi
 711:   e8 ea fe ff ff          callq  600 <exit@plt>
 				^------------------------------------------------------跳转到 下一条指令 0x716+0xfffffeea=0x600 处

。。。。
00000000000005f0 <inner_fun@plt>: <------这种plt结尾的函数是为了延迟绑定设计的。主要是为了提升动态加载的性能.延迟绑定的时候细说
 5f0:   ff 25 22 0a 20 00       jmpq   *0x200a22(%rip)        # 201018 
                                                                    ^-------------跳转到这个地址。
 5f6:   68 00 00 00 00          pushq  $0x0
 5fb:   e9 e0 ff ff ff          jmpq   5e0 <.plt>                    <----进行动态绑定的函数

0000000000000600 <exit@plt>:
 600:   ff 25 1a 0a 20 00       jmpq   *0x200a1a(%rip)        # 201020 
 606:   68 01 00 00 00          pushq  $0x1
 60b:   e9 d0 ff ff ff          jmpq   5e0 <.plt>

 。。。。。。。

#我们在看看它们在哪个分段
。。。。。。

[20] .got              PROGBITS         0000000000200fd8  00000fd8  <------外部变量在这
       0000000000000028  0000000000000008  WA       0     0     8
[21] .got.plt          PROGBITS         0000000000201000  00001000  <------引用的函数最终跳转到这里面了。
       0000000000000030  0000000000000008  WA       0     0     8
[22] .bss              NOBITS           0000000000201030  00001030  <------- 未初始化的静态变量在这
       0000000000000008  0000000000000000  WA       0     0     4

 。。。。。


写了怎么多,就是想说明动态链接中是不能用绝对地址的。而R_X86_64_PC32虽然是相对寻址,但是如果这个类型在目标文件中,就必须在编译的时候重定位,也就是必须在编译的时候确定地址在哪。但是exit是c运行库的代码,根本就不在我们的符号表中。所以编译器直接报错了。如果你自己定义个同样类型的exit(就像我解决方案一),就能让编译器重定位到。自己写一个c库的核心函数,和整个底层打交道,想想就头疼不现实。

从上面分析的结果中。你会发现。所有引用的函数都在一个叫.got.plt 的section中。我们接着往下往下捋

可以使用下面命令看动态链接文件是不是PIC编译的
bash readelf -d /lib64/libc-2.17.so | grep TEXTREL

.got和.got.plt

全局偏移表.got和.got.plt (global offset table,procedure linkage table),他是我们程序和外面程序交互的中转站。你所有对外部的访问都是通过它,更夸张的是,我们引用共享对象内自己定义的函数都要通过它。当你访问外部数据和函数时,操作系统中的一个程序(动态连接器)会在这个结构中填上外部对象的地址。所以只需读取.got和.got.plt中的值就能得到对应对象的地址。


##你会发现.got.plt 里面已经有值了。我们来看看这些地址都指向哪
(base) [6092003521@zte.intra@LIN-107F2060E3C cc]$ readelf -x 21 lib.so

“.got.plt”节的十六进制输出:
 NOTE: This section has relocations against it, but these have NOT been applied to this dump.
  0x00201000 180e2000 00000000 00000000 00000000 .. .............
  				^------64位是8字节一组,且都是小端存储0x208e18,指向.dynamic  section
  0x00201010 00000000 00000000 f6050000 00000000 ................
                                    ^------0x5f6,指向<inner_fun@plt>的第二条指令
  0x00201020 06060000 00000000 16060000 00000000 ................
                ^                   ^------0x616,指向<__cxa_finalize@plt>的第二条指令
                ^------0x606,指向<exit@plt>的第二条指令

(base) [6092003521@zte.intra@LIN-107F2060E3C cc]$ readelf -x 20 lib.so

“.got”节的十六进制输出:
  0x00200fd8 00000000 00000000 00000000 00000000 ................
  0x00200fe8 00000000 00000000 00000000 00000000 ................
  0x00200ff8 00000000 00000000                   ........

(base) [6092003521@zte.intra@LIN-107F2060E3C cc]$ readelf -S lib.so
。。。。。。。
  [19] .dynamic          DYNAMIC          0000000000200e18  00000e18
                                                     ^-------.got.plt第一个元素
       00000000000001c0  0000000000000010  WA       4     0     8
。。。。。。。

(base) [6092003521@zte.intra@LIN-107F2060E3C cc]$ objdump  -d  lib.so

。。。。。。。

Disassembly of section .plt:

00000000000005e0 <.plt>:                                                       
 5e0:   ff 35 22 0a 20 00       pushq  0x200a22(%rip)        # 201008 <_GLOBAL_OFFSET_TABLE_+0x8>
                                                                               ^-------.got.plt第二个元素,存着模块ID,之后延迟绑定细说
 5e6:   ff 25 24 0a 20 00       jmpq   *0x200a24(%rip)        # 201010 <_GLOBAL_OFFSET_TABLE_+0x10>
                                                                                ^-------.got.plt第三个元素,存着绑定函数地址。延迟绑定细说
 5ec:   0f 1f 40 00             nopl   0x0(%rax)

00000000000005f0 <inner_fun@plt>:
 5f0:   ff 25 22 0a 20 00       jmpq   *0x200a22(%rip)        # 201018 
 5f6:   68 00 00 00 00          pushq  $0x0
  ^-------.got.plt第四个元素
 5fb:   e9 e0 ff ff ff          jmpq   5e0 <.plt>

0000000000000600 <exit@plt>:
 600:   ff 25 1a 0a 20 00       jmpq   *0x200a1a(%rip)        # 201020 
 606:   68 01 00 00 00          pushq  $0x1
  ^-------.got.plt第五个元素
 60b:   e9 d0 ff ff ff          jmpq   5e0 <.plt>

0000000000000610 <__cxa_finalize@plt>:
 610:   ff 25 12 0a 20 00       jmpq   *0x200a12(%rip)        # 201028 <__cxa_finalize@GLIBC_2.2.5>
 616:   68 02 00 00 00          pushq  $0x2
 ^-------.got.plt第六个元素
 61b:   e9 c0 ff ff ff          jmpq   5e0 <.plt>

经过上面的梳理你应该可以感受到got的作用。我们在之前的章节中多次强调,共享库的装载地址是不确定的,共享库中的代码只能用相对地址,而代码段和数据段是相对位置是固定的。所以可以用got来存储外部的绝对地址,用相对寻址来找到got。这样就可以解决共享库各个代码相互寻找的问题

我们简单总结一下。.got中存着外部数据的地址,.got.plt存着外部函数的地址,这些结构中的地址会在代码跑起来之前被操作系统中的一个程序(动态连接器)填充。

延迟绑定

我们接着说一下延迟绑定。从上面的分析我们得到一个顺序。当在动态链接中引用一个函数xxxx,它指向xxxx@plt这个延迟绑定函数,xxxx@plt中的第一条指令就是跳转到.got.plt中存的地址。而.got.plt中 存的地址就是xxxx@plt的第二条指令地址,入栈函数id。xxxx@plt的第三条指令调用<.plt>函数。<.plt>函数会把函数id,模块id,传给绑定函数。我上面说过操作系统中有一个程序叫做动态连接器。它能填充.got和.got.plt表。而这个填充工作就是绑定函数做的。所以只要我们调用一次函数,就会触发这个绑定函数。如果不调用动态连接器就不会绑定。可以减少动态连接器一开始的工作,提升性能。

那么动态连接器是如何进行绑定的呢?

.rel.dyn和 .rel.plt

程序跑起来之前。动态链接会把所有用到的共享库装载到程序的进程中。然后获取所有的符号表。这样就知道各个符号的定义地址了。这样哪里引用了对应的函数和数据就把地址填充到那。而引用的位置就存在.rel.dyn和 .rel.plt。在静态链接中对应的是.rel.data和 .rel.text 。而这个填充的过程就叫做重定位。


(base) [6092003521@zte.intra@LIN-107F2060E3C cc]$ readelf -r lib.so

重定位节 '.rela.dyn' at offset 0x4b8 contains 8 entries:
  偏移量          信息           类型           符号值        符号名称 + 加数
000000200e00  000000000008 R_X86_64_RELATIVE                    6d0
000000200e08  000000000008 R_X86_64_RELATIVE                    690
000000200e10  000000000008 R_X86_64_RELATIVE                    200e10
000000200fd8  000100000006 R_X86_64_GLOB_DAT 0000000000000000 _ITM_deregisterTMClone + 0 
	^---------------从这开始重定位地方都在.pot表中,这里面都应该被填充外部数据的地址

000000200fe0  000200000006 R_X86_64_GLOB_DAT 0000000000000000 inter_var + 0
000000200fe8  000300000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0
000000200ff0  000500000006 R_X86_64_GLOB_DAT 0000000000000000 _ITM_registerTMCloneTa + 0
000000200ff8  000600000006 R_X86_64_GLOB_DAT 0000000000000000 __cxa_finalize@GLIBC_2.2.5 + 0

重定位节 '.rela.plt' at offset 0x578 contains 3 entries:
  偏移量          信息           类型           符号值        符号名称 + 加数
000000201018  000b00000007 R_X86_64_JUMP_SLO 00000000000006d9 inner_fun + 0
      ^---------------从这开始重定位地方都在.pot.plt表中,这里面都应该被填充外部函数的地址
000000201020  000400000007 R_X86_64_JUMP_SLO 0000000000000000 exit@GLIBC_2.2.5 + 0
000000201028  000600000007 R_X86_64_JUMP_SLO 0000000000000000 __cxa_finalize@GLIBC_2.2.5 + 0
(base) [6092003521@zte.intra@LIN-107F2060E3C cc]$ readelf -S lib.so

(base) [6092003521@zte.intra@LIN-107F2060E3C cc]$ readelf  --dyn-sym  lib.so

Symbol table '.dynsym' contains 13 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
                                                        ^------------- UND表示目标文件中引用的对象
     1: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterTMCloneTab
     2: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND inter_var
     3: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
     4: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND exit@GLIBC_2.2.5 (2)
     5: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMCloneTable
     6: 0000000000000000     0 FUNC    WEAK   DEFAULT  UND __cxa_finalize@GLIBC_2.2.5 (2)
     7: 0000000000000730    28 OBJECT  GLOBAL DEFAULT   13 ldpath 
                                                         ^------------- 数字表示目标文件中定义的对象
     8: 0000000000201030     0 NOTYPE  GLOBAL DEFAULT   21 _edata
     9: 0000000000201038     0 NOTYPE  GLOBAL DEFAULT   22 _end
    10: 0000000000201030     0 NOTYPE  GLOBAL DEFAULT   22 __bss_start
    11: 00000000000006d9     7 FUNC    GLOBAL DEFAULT   11 inner_fun
    12: 00000000000006e0    54 FUNC    GLOBAL DEFAULT   11 fun
(base) [6092003521@zte.intra@LIN-107F2060E3C cc]$ 

.rel.dyn和 .rel.plt,又名动态重定位表。你可能注意到R_X86_64_RELATIVE ,R_X86_64_GLOB_DAT,R_X86_64_JUMP_SLO这些类型。这些类型的作用就是告诉动态连接器如何填写地址。R_X86_64_GLOB_DAT,R_X86_64_JUMP_SLO 类型,符号地址是多少就填多少,而R_X86_64_RELATIVE的填地址方式叫做基址重置(bebasing),就是程序加载到的地址A加上符号在程序中的偏移量B。例如上面的前三个重定位符号都是在数据段中引用了代码段或数据段的地址。因为这些被引用代码段和数据段地址在装载后是会变的,但是他们的相对位置是不会变的。所以要用基址重置

.interp

我们上面介绍了动态连接器会帮完成引用外部对象的工作。而这个动态连接器必须要在.interp section中指定。


#看有没有.interp段
readelf -S | grep interp
#程序头也能看连接器
readelf -l /lib64/libc-2.17.so  | grep interpret
#看依赖的连接库
ldd /lib64/libc-2.17.so

动态连接器

elf文件运行是从execve(用户态)->sys_execve(内核)->do_execve->search_binary_handle->load_elf_binary

  • sys_execve, 检查函数参数
  • do_execve ,检查elf文件路径,并且取出来文件类型的唯一标识(开头前几个字节)
  • search_binary_handle,根据唯一标识找到对应的解析函数
  • load_elf_binary ,elf的解析函数,这个函数进行文件和内存的映射,初始化进程环境(例如寄存器的值),动态连接的话还要从interp中找对应的动态连接器,设置共享库搜索路径。最后修改sys_execve结束后cpu运行的地方(设置程序开始运行的地方)。
  • 上面结束后,静态连接的可执行文件会从e_entry(程序入口点)处运行。而动态连接的文件会从动态连接器的e_entry处运行。

共享库的搜索路径优先级 LD_PRELOAD环境变量>LD_LIBRAR_PATH环境变量>/etc/ld.so.config配置文件>/>/usr/local/lib64>/usr/lib64>lib64

动态连接器会在我们程序运行之前运行,这时候就像在宇宙大爆炸之前,什么都没有,不能调用任何函数和外部数据,它必须要重定位自己。然后装载所有依赖的共享库,重定位和初始化。

相同的代码的重复利用

我们花了上万字解释了外部代码是如何被引用的。现在来看看多个程序引用同一个外部函数,它们的地址是不是相同的。

//建2个一样的文件分别叫做program1.c和program2.c
#include
int main(){
        printf("program ");
}
#编译
 gcc -fno-builtin -o program1 program1.c 
 gcc -fno-builtin -o program2 program2.c 
 #2个文件的重定位表是一样的,这里以 program1举例子了
[root@paas-controller1:/home/ubuntu/wen]$ readelf -r program1

重定位节 '.rela.dyn' 位于偏移量 0x380 含有 1 个条目:
  偏移量          信息           类型           符号值        符号名称 + 加数
000000600ff8  000300000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0

重定位节 '.rela.plt' 位于偏移量 0x398 含有 3 个条目:
  偏移量          信息           类型           符号值        符号名称 + 加数
000000601018  000100000007 R_X86_64_JUMP_SLO 0000000000000000 printf@GLIBC_2.2.5 + 0
     ^---------------------可执行文件中这个值就是逻辑地址
000000601020  000200000007 R_X86_64_JUMP_SLO 0000000000000000 __libc_start_main@GLIBC_2.2.5 + 0
000000601028  000300000007 R_X86_64_JUMP_SLO 0000000000000000 __gmon_start__ + 0

[root@paas-controller1:/home/ubuntu/wen]$ gdb ./program1  <-------调试这个程序
(gdb) b *main              <-------------------------------------打断点
(gdb) r                    <-------------------------------------运行
(gdb) display /20i $pc  <----------------------------------------输出提示显示更多的汇编指令
1: x/20i $pc
=> 0x40052d <main>:     push   %rbp
   0x40052e <main+1>:   mov    %rsp,%rbp
   0x400531 <main+4>:   mov    $0x4005e0,%edi
   0x400536 <main+9>:   mov    $0x0,%eax
   0x40053b <main+14>:  callq  0x400410 <printf@plt>
   0x400540 <main+19>:  pop    %rbp
   0x400541 <main+20>:  retq   
   0x400542:    nopw   %cs:0x0(%rax,%rax,1)
   0x40054c:    nopl   0x0(%rax)
(gdb) b *main+20             <-------------------------------------我们要让延迟绑定触发不然看不到print函数地址。
(gdb) n                       <-------------------------------------接着运行
(gdb) x /1xg 0x601018
0x601018:       0x00007ffff7a604a0     <----------------------------这个就是printf的地址,内核空间

#用上面同样的方法你会发现program2中的printf 也是这个地址

通过动态链接不仅可以引用共享库中的代码。而且比静态链接省内存。至此我们解答了一开始提出的问题二。

进程栈的初始化

我们来看看一个进程栈中最早入栈的都是啥。

//lib.c
void fun(){
//建一个超级简单的可运行共享库
}
#编译成直接运行的共享库程序
 gcc -shared -o lib.so  -e fun --save-temps  lib.c  


#用gdb调试器调试一下
[root@paas-controller1:/home/ubuntu/wen]$ gdb ./lib.so
(gdb) b *fun         <------------------------------------------入口打一个断点                          
Breakpoint 1 at 0x655
(gdb) r              <-------------------------------------------让程序跑起来
Starting program: /home/ubuntu/wen/./lib.so 
(gdb) i f              <-------------------------------------------看一下栈帧
Stack level 0, frame at 0x7fffffffe558:
 rip = 0x555555554655 in fun; saved rip 0x1
 Arglist at 0x7fffffffe548, args: 
 Locals at 0x7fffffffe548, Previous frame's sp is 0x7fffffffe558    <---上一个栈帧的栈顶指针
 Saved registers:
  rip at 0x7fffffffe550


(gdb) x /550xg  0x7fffffffe550                                     <--------用16进制显示栈上面550个8字节内存单元
0x7fffffffe550: 0x0000000000000001      0x00007fffffffe7a7         <--------文件名地址,指向下面的那些字符串
0x7fffffffe560: 0x0000000000000000      0x00007fffffffe7bf         <---------环境变量存储的地址
                     ^-------------------------全是零表示分隔区域
0x7fffffffe570: 0x00007fffffffe7d2      0x00007fffffffe7ec
0x7fffffffe580: 0x00007fffffffe7f7      0x00007fffffffe807
0x7fffffffe590: 0x00007fffffffe815      0x00007fffffffe81f
0x7fffffffe5a0: 0x00007fffffffedbb      0x00007fffffffedcc
0x7fffffffe5b0: 0x00007fffffffedda      0x00007fffffffede8
0x7fffffffe5c0: 0x00007fffffffedf4      0x00007fffffffee10
0x7fffffffe5d0: 0x00007fffffffee33      0x00007fffffffee3e
0x7fffffffe5e0: 0x00007fffffffee53      0x00007fffffffee64
0x7fffffffe5f0: 0x00007fffffffee6d      0x00007fffffffeea0
0x7fffffffe600: 0x00007fffffffeeab      0x00007fffffffeec0
0x7fffffffe610: 0x00007fffffffeec8      0x00007fffffffeed5
0x7fffffffe620: 0x00007fffffffeef8      0x00007fffffffefb7
0x7fffffffe630: 0x00007fffffffefc5      0x0000000000000000          <---------分隔区域
0x7fffffffe640: 0x0000000000000021      0x00007ffff7ffd000          <--------- auxiliary vector 开始的地方,每一个元素由2个子元素组成。类型和值。
					^--- auxiliary vector 类型  ^--- auxiliary vector 值,下面同理
0x7fffffffe650: 0x0000000000000010      0x00000000bfebfbff
0x7fffffffe660: 0x0000000000000006      0x0000000000001000
0x7fffffffe670: 0x0000000000000011      0x0000000000000064
0x7fffffffe680: 0x0000000000000003      0x0000555555554040            <------AT_PHDR,程序头的地址。
0x7fffffffe690: 0x0000000000000004      0x0000000000000038             <------AT_PHENT,程序头中元素的大小(字节)。
0x7fffffffe6a0: 0x0000000000000005      0x0000000000000007              <------AT_PHENT,程序头中元素数量 。
0x7fffffffe6b0: 0x0000000000000007      0x0000000000000000             <------AT_BASE,动态连接器的地址
0x7fffffffe6c0: 0x0000000000000008      0x0000000000000000
0x7fffffffe6d0: 0x0000000000000009      0x0000555555554655             <------AT_ENTRY ,入口地址,我们关注的
0x7fffffffe6e0: 0x000000000000000b      0x0000000000000000
0x7fffffffe6f0: 0x000000000000000c      0x0000000000000000
0x7fffffffe700: 0x000000000000000d      0x0000000000000000
0x7fffffffe710: 0x000000000000000e      0x0000000000000000
0x7fffffffe720: 0x0000000000000017      0x0000000000000000
---Type <return> to continue, or q <return> to quit---
0x7fffffffe730: 0x0000000000000019      0x00007fffffffe789
0x7fffffffe740: 0x000000000000001a      0x0000000000000000
0x7fffffffe750: 0x000000000000001f      0x00007fffffffefe0
0x7fffffffe760: 0x000000000000000f      0x00007fffffffe799
0x7fffffffe770: 0x0000000000000000      0x0000000000000000       <--------- auxiliary vector 结束的的地方,AT_NULL
0x7fffffffe780: 0x0000000000000000      0x74477c3edae94600       <---------UNspecified 的地方,未定义的地方
                      ^---------分隔区域
0x7fffffffe790: 0x5b4dfd1f55163060      0x0034365f36387853
0x7fffffffe7a0: 0x2f00000000000000      0x7562752f656d6f68      <---------information block 的地方。环境变量在这里面。我们用字符串来显示他们
0x7fffffffe7b0: 0x2f6e65772f75746e      0x58006f732e62696c

。。。。。。。。。....。.。。。。。。。。。。。。。。。
(gdb) x /550sg  0x7fffffffe550                                 <--用字符串显示栈上面550个8字节内存单元
。。。。。。。。。....。.。。。。。。。。。。。。。。。

0x7fffffffe786: ""
0x7fffffffe787: ""
0x7fffffffe788: ""
0x7fffffffe789: "F\351\332>|Gt`0\026U\037\375M[Sx86_64"
0x7fffffffe7a0: ""
0x7fffffffe7a1: ""
0x7fffffffe7a2: ""
0x7fffffffe7a3: ""
0x7fffffffe7a4: ""
0x7fffffffe7a5: ""
0x7fffffffe7a6: ""
0x7fffffffe7a7: "/home/ubuntu/wen/lib.so"
0x7fffffffe7bf: "XDG_SESSION_ID=298"
0x7fffffffe7d2: "HOSTNAME=paas-controller1"
0x7fffffffe7ec: "TERM=xterm"
0x7fffffffe7f7: "SHELL=/bin/bash"
0x7fffffffe807: "HISTSIZE=1000"
0x7fffffffe815: "USER=root"
0x7fffffffe81f: "LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31---Type  to continue, or q  to quit---
:*.tgz=01"...
0x7fffffffe8e7: ";31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;"...
0x7fffffffe9af: "31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01"...
0x7fffffffea77: ";31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.ti"...
0x7fffffffeb3f: "ff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=0"...
0x7fffffffec07: "1;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf="...
0x7fffffffeccf: "01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*."...
0x7fffffffed97: "oga=01;36:*.spx=01;36:*.xspf=01;36:"
0x7fffffffedbb: "SUDO_USER=ubuntu"
0x7fffffffedcc: "SUDO_UID=1000"
0x7fffffffedda: "USERNAME=root"
0x7fffffffede8: "COLUMNS=208"
0x7fffffffedf4: "MAIL=/var/spool/mail/ubuntu"
0x7fffffffee10: "PATH=/sbin:/bin:/usr/sbin:/usr/bin"
0x7fffffffee33: "_=/bin/gdb"
0x7fffffffee3e: "PWD=/home/ubuntu/wen"
0x7fffffffee53: "LANG=zh_CN.UTF-8"
0x7fffffffee64: "LINES=31"
0x7fffffffee6d: "HISTIGNORE=*adduser*--user=*-p=*:*openssl*passwd *"
0x7fffffffeea0: "HOME=/root"
0x7fffffffeeab: "SUDO_COMMAND=/bin/su"
0x7fffffffeec0: "SHLVL=1"
0x7fffffffeec8: "LOGNAME=root"
0x7fffffffeed5: "LESSOPEN=||/usr/bin/lesspipe.sh %s"
---Type <return> to continue, or q <return> to quit---
0x7fffffffeef8: "PROMPT_COMMAND={ msg=$(history 1 | { read x y; echo $x $y | grep -v -E \"password|passwd|\\-\\-email|\\-\\-description\"; });logger -p local6.notice \"[euid=$(whoami)]\":$(who am i):[`pwd`]\"$msg\"; }"
0x7fffffffefb7: "SUDO_GID=1000"
0x7fffffffefc5: "HISTTIMEFORMAT=%F %T root "
0x7fffffffefe0: "/home/ubuntu/wen/lib.so"
0x7fffffffeff8: ""
0x7fffffffeff9: ""
0x7fffffffeffa: ""

通过上面的分析我们知道。进程的栈空间会在运行之前塞入一下东西。其中auxiliary vector 就像目录一样。其中AT_ENTRY是程序的入口

SO-NAME 和ldconfig

so-name是共享库的命令方式
ldconfig是共享库的安装程序。可以建立so的索引,减少so的搜索时间

[参考]
https://refspecs.linuxfoundation.org/ELF/zSeries/lzsabi0_zSeries/x895.html 【linux foundation process Process initialization

https://ftp.gnu.org/old-gnu/Manuals/bfd-2.9.1/html_mono/bfd.html 【linux bfd 】
https://www.gnu.org/software/binutils/ 【linux binutils】

你可能感兴趣的:(操作系统,linux,运维,服务器)