目录

  • 为什么要动态链接?
    • 地址无关代码
    • 共享模块的全局变量问题
  • 延迟绑定(PLT)
  • 动态链接相关结构
    • .interp段
    • .dynamic段
    • 动态符号表
    • 动态链接重定位表
    • 动态链接时进程堆栈初始化信息
    • 其他信息
  • 动态链接器实现步骤
    • 动态链接器自举
    • 装载共享对象
    • 重定位和初始化
    • Linux动态链接器是什么
  • 显示运行时链接
  • 共享库的创建和安装
    • 共享库的创建
    • 构造和析构函数
  • 总结

引言:最近受到了一位老师的启发(我不是CS专业的,我去找了信院里的操作系统的老师),又给我本狭隘的视野开拓了一些目光所及之处,交流了两个小时,倍感遗憾怎么没有早点认识这个老师,也倍感欣慰终于遇到了这种老师。

这篇文章可能有点晦涩难懂,我希望你有以下的前置基础知识(基础即可,我会尽量说清):

  • 操作系统(最好看过CSAPP
  • AT&T语法的汇编语言(Intel汇编也可以,但是可能有些地方不同得去查询指令集开发手册了)
  • CC++有一定的掌握
  • Linux操作熟练,对Linux内核和系统有一定的了解
  • GDB调试(主要是反汇编)有一定了解
  • 对编译原理、数据结构和计组有一定了解

相关代码实现部分我后续会补上。

其中关于GNU Glibc C语言库GNU LD手册Intel指令集开发手册以及其他里面资料大家可以私信我自取。

为了减少复杂性,后面的反汇编都是通过该源代码实现的

// t.c
#include <stdio.h>  // printf
#include <unistd.h>  // sleepint main(int argc, char *argv[]) {printf("Hello World!\n");  // 调用write(...)sleep(-1);  // 表示一直阻塞
}// gcc -o file t.c

为什么要动态链接?

静态链接使得不同的程序开发者能够相对独立的开发和测试自己的程序模块,但是模块更新困难、浪费内存和磁盘空间等问题也凸显出来。

动态链接基本思想:对组成程序的目标文件等到程序运行的时候才进行链接,也就是说,把链接这个过程推迟到了运行时再进行,这就是动态链接的基本思想。

目前主流的操作系统都支持动态链接,在Linux系统中,ELF动态链接文件被称为动态共享文件(DSO,dynamic shared objects),简称共享对象,一般以.so结尾。

全过程:把主程序进行虚拟内存映射、进入链接器的入口链接器执行重定位和其他操作、进入到主程序的入口执行程序。

此处有个地方需要进行认知:在静态链接时,整个程序最终只有一个可执行文件,他是一个不可以分割的整体,但是在动态链接下,一个程序被分成了若干个文件,有程序的主要部分,即可执行文件和程序所依赖的共享对象。很多时候我们也把这些部分称为模块,即动态链接下的可执行文件和共享对象都可以看做是程序的一个模块。

动态链接程序运行时地址空间分布

$ ./file &
[1] 748098
$ Hello World!
cat /proc/748098/maps
# 可执行文件虚拟空间映射
561c05d55000-561c05d56000 r--p 00000000 fc:01 1058139  /root/file
# 堆区虚拟空间映射
561c07aaa000-561c07acb000 rw-p 00000000 00:00 0        [heap]
# 动态链接器虚拟空间映射
7fd564277000-7fd564299000 r--p 00000000 fc:01 1705537  /usr/lib/x86_64-linux-gnu/libc-2.31.so
# 栈区虚拟空间映射
7ffcb0418000-7ffcb0439000 rw-p 00000000 00:00 0        [stack]

此处有必要提一句,共享对象的最终装载地址在编译时是不确定的。

地址无关代码

如何确定共享对象在进程虚拟地址空间中的位置?我们是否可以让共享对象在任意地址加载?或者说,共享对象在编译时不能假设自己在进程虚拟地址空间中的位置。

静态链接是链接时进行重定位(链接器是在多个模块合并成一个模块的时候进行重定位并生成重定位表,此时生成的是一个可执行文件),动态链接是在装载时进行重定位(可执行文件装载进内存)

地址无关代码技术:指令部分保持不变,需要进行修改的部分和数据部分(数据段)放在一起。

地址无关代码主要有以下四个方式

模块内部的函数调用,跳转等

因为被调用的函数与调用者都处于同一个模块,所以这种情况不需要进行重定位,不作概述

模块内部的数据访问

与上同理

模块外部的数据访问

因为模块间的数据访问模板地址要等到装载时才能决定,所以根据地址无关性,把跟地址相关的部分放到数据段里面。

ELF通常的做法是在数据段里面建立一个指向这些变量的指针数组(里面存放的是指针),也被称为全局偏移表(GOT,global offset table,当代码需要引用这个全局变量时,可以通过GOT中相对应的项间接引用)
链接器在装载模块的时候会查找每个变量的地址,然后填充GOT中的各个项,以确保每个指针所执行的地址正确

查看GOT表的位置

$ objdump -h file.got   00000050  0000000000003fb0  0000000000003fb0  00002fb0  2**3CONTENTS, ALLOC, LOAD, DATA

查看需要重定位的项

$ objdump -R fileDYNAMIC RELOCATION RECORDS
OFFSET           TYPE              VALUE
0000000000003fc8 R_X86_64_JUMP_SLOT  puts@GLIBC_2.2.5
0000000000003fd0 R_X86_64_JUMP_SLOT  sleep@GLIBC_2.2.5

模块外部的函数调用,跳转等

对于模块间的跳转和调用,也可以采用GOT表的项保存目标函数的地址来实现,当模块需要调用目标函数时,可以通过GOT中的项进行间接跳转。

注意静态链接是在编译可执行文件的时候形成VMA的时候就将虚拟内存地址放入了相对的地址,是在链接的时候形成的。

如何区分一个DSO是不是PIC

readelf -d xxx.so | grep TEXTREL

共享模块的全局变量问题

当一个模块引用了一个定义在共享对象的全局变量的时候,比如一个共享对象定义了一个全局对象global,而模块module.c中是这么引用的

extern int global;int foo() {global = 1;
}

则可以通过所有的使用这个变量的指令都执行位于可执行文件中的那个副本,ELF共享库在编译时,默认都把定义在模块内部的全局变量当做定义在其他模块的全局变量,也就是通过GOT表进行访问。

当共享模块被装载时,如果某个全局变量在可执行文件中拥有副本,那么动态链接器就会把GOT中的相应地址指向该副本,这样该变量在运行时实际上就只有一个实例(如果变量在共享模块中被初始化,那么动态链接器还需要将该初始化值赋值到程序主模块的变量副本,如果该全局变量在程序主模块中没有副本,那么GOT中相应的地址就指向模块内部的该变量副本)

和线程私有存储有点类似(thread local storage)

延迟绑定(PLT)

动态链接的缺点:

  • 动态链接对于模块的调用以及全局和静态的数据访问都要进行GOT定位
  • 在程序加载的时候,动态链接器会装载所有的共享对象,然后进行符号查找地址重定位等工作。

延迟绑定:在动态链接的时,程序模块之间包含了大量的阿含糊引用,所以程序在刚开始执行前,必然会消耗大量的时候去重定位,但是有时候有些函数程序几乎不会用到,比如错误处理函数,这很浪费!因此ELF采用了延迟绑定,基本的是

当函数第一次被用到时才进行绑定(符号查找、重定位等)。
如果没有用到则不进行绑定,此时程序开始执行的时候,模块间的函数调用都没有进行绑定,而是需要用到的时才由动态链接器进行绑定,大大加快了程序的启动速度。

ELF使用PLT(procedure linkage table)的方式来实现,在ld-xxx-xxx.so链接器调用函数_dl_runtime_resovlve(module, function)来实现他,这个函数保存在PLT表中,所以可以直接调用,其中module指的是共享对象的名称,function值得是共享对象中的某个函数。

PLT在直接通过GOT找到响应的项增加了一层跳转,调用函数并不直接通过GOT跳转,而是通过一个PLT的项的结构进行跳转,每个外部函数在PLT中都有一个相应的项,如function()函数叫做function@plt

实现过程

function@plt:# 如果GOT已经存在该函数地址,则直接跳转,否则该项中的地址是该指令的下一条指令,即push njmp *(function@GOT)# 这个数字是该符号在.rel.plt中的下标push n# 模块id入栈push moduleIDjump _dl_runtime_resolve

来看看源码的反汇编

0000000000001169 <main>:...1178:   e8 e3 fe ff ff          callq  1060 <puts@plt>...
0000000000001060 <puts@plt>:1064:    f2 ff 25 5d 2f 00 00    bnd jmpq *0x2f5d(%rip)        106b: 0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)

一般情况下,GOT表被分成了.got.got.plt.got表用来保存全局变量引用的的地址,.got.plt用来保存函数引用的地址。

PLT表的前三项分别是

  • .dynamic段的地址
  • 本模块的ID
  • _dl_runtime_resolve()的地址。

PLTELF文件中以独立的段存放,段名通常叫做.plt,因为他本身是一些地址无关代码,所以可以跟代码段等一起合并成同一个可读可执行的segmane被装载入内存。

动态链接相关结构

在动态链接情况下,操作系统在映射完可执行文件之后,会先启动一个动态链接器(dynamic linker),操作系统同样以映射的方式将他加载到进程的地址空间中,然后将控制权交给动态链接器的入口地址,当链接工作完成后,动态链接器将控制权转交给可执行文件的入口地址。

.interp段

.interp段中保存的就是一个字符串,这个字符串就是可执行文件所需要动态链接器的路径。

查看.interp

Contents of section .interp:0318 2f6c6962 36342f6c 642d6c69 6e75782d  /lib64/ld-linux-0328 7838362d 36342e73 6f2e3200           x86-64.so.2.

查看使用动态链接器类型

readelf -l a.out | grep interpreter[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]

.dynamic段

保存了动态链接器所需要的基本信息,比如依赖于哪些共享对象,动态符号表的位置,动态链接重定位表的位置等等。

/* Dynamic section entry.  */typedef struct
{Elf32_Sword   d_tag;                  /* Dynamic entry type */union{Elf32_Word d_val;                 /* Integer value */Elf32_Addr d_ptr;                 /* Address value */} d_un;
} Elf32_Dyn;

查看.dynamic段信息

$ readelf -d fileDynamic section at offset 0x2dc0 contains 27 entries:Tag        Type                         Name/Value0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]0x000000000000000c (INIT)               0x10000x000000000000000d (FINI)               0x12080x0000000000000019 (INIT_ARRAY)         0x3db00x000000000000001b (INIT_ARRAYSZ)       8 (bytes)0x000000000000001a (FINI_ARRAY)         0x3db80x000000000000001c (FINI_ARRAYSZ)       8 (bytes)0x000000006ffffef5 (GNU_HASH)           0x3a00x0000000000000005 (STRTAB)             0x4880x0000000000000006 (SYMTAB)             0x3c80x000000000000000a (STRSZ)              136 (bytes)0x000000000000000b (SYMENT)             24 (bytes)0x0000000000000015 (DEBUG)              0x00x0000000000000003 (PLTGOT)             0x3fb00x0000000000000002 (PLTRELSZ)           48 (bytes)0x0000000000000014 (PLTREL)             RELA0x0000000000000017 (JMPREL)             0x6000x0000000000000007 (RELA)               0x5400x0000000000000008 (RELASZ)             192 (bytes)0x0000000000000009 (RELAENT)            24 (bytes)0x000000000000001e (FLAGS)              BIND_NOW0x000000006ffffffb (FLAGS_1)            Flags: NOW PIE0x000000006ffffffe (VERNEED)            0x5200x000000006fffffff (VERNEEDNUM)         10x000000006ffffff0 (VERSYM)             0x5100x000000006ffffff9 (RELACOUNT)          30x0000000000000000 (NULL)               0x0

查看一个程序依赖于哪些模块或共享库

$ ldd file# 系统调用库linux-vdso.so.1 (0x00007ffd11337000)# GNU C库libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fcc4edc3000)# 动态链接库/lib64/ld-linux-x86-64.so.2 (0x00007fcc4efc4000)

动态符号表

动态符号表,保存了与动态链接相关的符号,对于模块内部的符号,比如模块私有变量则不保存,这些一般保存在.symtab中。所以一般情况下都有这两个表。

动态符号也需要一些辅助表,比如保存符号名的字符串表,如动态符号字符串表.dynstr;由于动态链接下,我们需要在程序运行时查找符号,为了加快符号查找的过程,往往还是辅助的符号哈希表.hash

查看动态符号表和哈希表

Symbol table '.symtab' contains 66 entries:Num:    Value          Size Type    Bind   Vis      Ndx Name# puts函数,是一个UND(undefined)状态49: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@@GLIBC_2.2.561: 0000000000001169    37 FUNC    GLOBAL DEFAULT   16 main64: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND sleep@@GLIBC_2.2.5Symbol table of '.gnu.hash' for image:Num Buc:    Value          Size   Type   Bind Vis      Ndx Name7   0: 0000000000000000     0 FUNC    WEAK   DEFAULT UND __cxa_finalize

动态链接重定位表

对于PIC技术的可执行文件或共享对象来说,虽然他们的代码不需要重定位(因为地址无关),但是数据段还包含了绝对地址的引用,因为代码段中绝对地址相关的部分被分离了出来(PIC地址无关技术),变成了GOT,而GOT实际上是数据段的一部分。

主要有两个段,一个是.rel.dyn是对数据引用的修正,他所修正的位置位于.got,一个是.rel.plt是对函数引用的修正,他所修正的位置位于.got.plt

查看重定位表

$ readelf -r fileRelocation section '.rela.dyn' at offset 0x540 contains 8 entries:Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000003db0  000000000008 R_X86_64_RELATIVE                    1160
000000003db8  000000000008 R_X86_64_RELATIVE                    1120
000000004008  000000000008 R_X86_64_RELATIVE                    4008
000000003fd8  000100000006 R_X86_64_GLOB_DAT 0000000000000000 _ITM_deregisterTMClone + 0
000000003fe0  000300000006 R_X86_64_GLOB_DAT 0000000000000000 __libc_start_main@GLIBC_2.2.5 + 0
000000003fe8  000400000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0
000000003ff0  000500000006 R_X86_64_GLOB_DAT 0000000000000000 _ITM_registerTMCloneTa + 0
000000003ff8  000700000006 R_X86_64_GLOB_DAT 0000000000000000 __cxa_finalize@GLIBC_2.2.5 + 0Relocation section '.rela.plt' at offset 0x600 contains 2 entries:Offset          Info           Type           Sym. Value    Sym. Name + Addend
# 两个共享对象函数引用
000000003fc8  000200000007 R_X86_64_JUMP_SLO 0000000000000000 puts@GLIBC_2.2.5 + 0
000000003fd0  000600000007 R_X86_64_JUMP_SLO 0000000000000000 sleep@GLIBC_2.2.5 + 0

当动态链接器需要进行重定位时,他首先查找printf的位置,printf位于libc-2.6.1.so,假设链接器在全局符号表里面找到printf的地址为0x08801234,那么链接器就会将这个地址填入到.got.plt中偏移为000000003fc8的位置中去,从而实现了地址的重定位,例如在前面调用puts@plt中反汇编代码为

0000000000001060 <puts@plt>:1064: f2 ff 25 5d 2f 00 00    bnd jmpq *0x2f5d(%rip) # 3fc8 <puts@GLIBC_2.2.5>      106b:  0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)

动态链接时进程堆栈初始化信息

进程在初始化的时候,堆栈里面保存了动态链接器所需要的一些辅助信息数组(auxiliary vector)

typedef struct
{uint32_t a_type;              /* Entry type */union{uint32_t a_val;           /* Integer value *//* We use to have pointer elements added here.  We cannot do that,though, since it does not work when using 32-bit definitionson 64-bit platforms and vice versa.  */} a_un;
} Elf32_auxv_t;

其他信息

特殊符号

当使用ld作为链接器来连接生成可执行文件时,他会为完美定义很多特殊的符号,这些符号在我们的程序中并没有定义,但是外面可以直接声明并且引用他,我们称之为特殊符号,其实这些符号是定义在ld脚本文件中的,有几个常见的

  • __executable_start:该符号为程序起始地址,不是入口地址,是程序的最开始地址
  • __etext_etextetext:代码段结束地址
  • _edataedata:数据段结束地址
  • _endend:程序结束地址

以上部分都为程序被装载时的虚拟地址

ld默认链接脚本中

PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); . = SEGMENT_START("text-segment", 0x400000) + SIZEOF_HEADERS;
PROVIDE (__etext = .);
PROVIDE (_etext = .);
PROVIDE (etext = .);
_edata = .; PROVIDE (edata = .);
_end = .; PROVIDE (end = .);

在某些情况下, 一个符号被引用到的时候只在连接脚本中定义,而不在任何一个被连接进来的目标文件中定义

验证并打印地址

#include <stdio.h>extern char __executable_start[];
extern char __etext[];
extern char _edata[];
extern char _end[];int main(int argc, char *argv[]) {printf("%X.\n", __executable_start);printf("%X.\n", __etext);printf("%X.\n", _edata);printf("%X.\n", _end);
}/*B0926000.B0927255.B092A010.B092A018.
*/

动态链接器实现步骤

动态链接器自举

自举(bootstrap):动态链接器本身不可以依赖其他任何共享对象;动态链接器本身的所需要的全局和静态变量的重定位工作由他本身完成。

动态链接器入口地址即是自举代码的入口,当操作系统将进程控制权交给动态链接器的时,动态链接器的自举代码开始执行,自举代码首先首先找到自己的GOT表,而GOT表的第一个地址是.dynamic段,通过该信息,自举代码便可以获得动态链接器本身的重定位表和符号表等,从而得到动态链接器的重定位入口,先将他们全局重定位。

装载共享对象

完成自举之后,动态链接器将可执行文件和链接器本身的符号表都合并到一个符号表当中,成为全局符号表(global symbol table),然后链接器开始寻找可执行文件所依赖的共享对象

此处执行的算法是BFS。

重定位和初始化

当上面的步骤完成之后,链接器便开始重新遍历可执行文件和每个共享对象的重定位表,将页面的GOTPLT中的每个需要重定位的位置进行修正。

如果某个共享对象有.init段,那么链接器执行该段,实现共享对象特有的初始化过程,当执行.final段时,进程会退出

Linux动态链接器是什么

Linux链接器本身也是一个共享对象,是个特殊的共享对象,是个可执行的程序。

显示运行时链接

这一章不是必要的,但又是必要的,很矛盾就是说。

支持动态链接的系统往往都支持一种更加灵活的模块架子啊方式,即显示运行时链接,也叫做运行时加载,也就是让程序自己在运行时控制加载指定的模块,并且在不需要该模块时将其卸载,这种共享对象被佳作动态装载库(dynamic loading library)。

共享对象和动态装载库其实没什么区别,只是角度不同罢了。

他和之前的区别是,动态库的加载通过一系列由动态连接器提供的API,具有有四个函数:dlopen打开动态库,dlsym查找符号,dlerror错误处理,dlclose关闭动态库。

dlopen()

该函数用来打开一个动态库,并将其加载到进程的地址空间,完成初始化过程

// filename 被加载动态库的路径
// flag 符号的解析方式
void *dlopen(const char *filename, int flags);

dlsym()

通过该函数找到所需要的符号

// handle 句柄
// symbol 函数名
void *dlsym(void *handle, const char *symbol);

dlerror()

每次调用dlopen()dlsym()dlclose()之后,都可以调用dlerror()来判断上一次调用是否成功

char *dlerror(void);

dlclose()

将一个已经加载的模块卸载,系统会维护一个加载引用计数器,每次使用dlopen()加载某模块时,相应的计数器加一,每次使用dlclose()卸载某个模块时,相应计数器减一。

当减为0的时候,模块才真正被卸载,卸载的过程相反,先执行.final段的代码,然后将相应的符号从符号表中取出,取消进程空间跟模块和映射关系,然后关闭模块文件。

如下

#include <stdio.h>
#include <dlfcn.h>int main(int argc, char *argv[]) {void *handle;double (*func)(double);char *error;handle = dlopen(argv[1], RTLD_NOW);if (handle == NULL) {printf("open library %s error: %s.\n", argv[1], dlerror());return -1;}func = dlsym(handle, "sin");if ((error = dlerror()) != NULL) {printf("symbal sin not found: %s.\n", error);return -1;// goto exit_runso;}printf("%f.\n", func(3.1415926 / 2));// exit_runso;dlclose(handle);
}

共享库的创建和安装

为什么会产生共享库?大量的程序开始使用动态链接机制,导致系统里面存在数量极为庞大的共享对象,如果没有很好的方法将这些共享对象组织起来,整个系统的共享对象文件则会散落在各个目录下,造成巨大困难,所以操作系统一般会对共享对象的目录组织和使用方法有一定的规则,即共享库。

共享库的创建

与创建共享对象过程一致,需要两个参数-shared表示输出结果是共享库类型的,-fPIC表示是地址无关代码技术来产生输出文件,

构造和析构函数

很多时候在共享库被加载的时候能够进行一些初始化工作,比如打开文件,网络连接等等,使得共享库里面的函数接口能够正常工作,GCC提供了一种共享库的构造函数,只要在函数声明时加上__attribute__((constructor))的属性,即指定该函数为共享库构造函数,拥有这种属性的函数会在共享库加载时被执行,即在程序的main函数之前执行。

如果我们使用dlopen()打开共享库,共享库构造函数会在dlopen()返回之前被执行。

与共享库构造函数相对于的是析构函数__attribute__((destructor))的属性,这种函数会在main()函数执行完毕之后执行(或者是程序调用exit()时执行)

如果共享库是运行时加载的,那么使用dlclose()来卸载共享库时,析构函数将会在他返回之前执行。

void __attribute__((constructor)) init_function(void);
void __attribute__((constructor)) fini_function(void);

总结

写了那么多,知识点貌似有点分散,但是每一个部分都有他的作用,先来口语简略的说一下不在显示运行时链接的程序执行过程(显示链接无法就是把主动权交给了程序,被动转为 了主动,差别不大)。

源程序(.c)经过预处理、编译(.s)、汇编(.o)形成对象文件(或者叫目标文件),接着进行链接(在.interp段中指定链接器地址,生成重定位表.rel.dyn.rel.plt,生成字符表。symtab,生成.got(直接存放绝对地址)和.got.plt段(存放假地址),PLT存放所有函数的假地址,初始化堆栈信息,此时都是虚存),执行文件,链接器进行自举和重定位,然后进入进程地址空间,读取进程堆栈信息,合并一个全局符号表,因为是PIC技术,所以暂时不加载函数引用。当进行函数引用的时候,寻找相关函数位置,进程代码进入到GOT表中寻找,无法找到,则跳转到PLT表的地址,PLT通过dl_runtime_close()函数找到地址并放入下标为n的位置,并填入GOT中,代码直接从GOT表中即可得到该函数地址,并执行该函数。

完美!

后续开始看Linux-0.12源码版本

一篇长文带你深析Linux动态链接的全过程相关推荐

  1. python3进阶篇(二)——深析函数装饰器

    python3进阶篇(二)--深析函数装饰器 前言: 阅读这篇文章我能学到什么?   装饰器可以算python3中一个较难理解的概念了,这篇文章由浅入深带你理解函数装饰器,请阅读它. --如果您觉得这 ...

  2. Linux 动态链接和静态链接简析(库名与库文件名)

    原文请见 Linux动态链接和静态链接简析 0. 库名与真正的库文件名 就拿数学库来说,他的库名是 m,他的库文件名是libm.so,很容易看出,把库文件名的头 lib 和尾.so去掉就是库名.(gc ...

  3. linux got分析,聊聊Linux动态链接中的PLT和GOT(3)——公共GOT表项

    前文(聊聊Linux动态链接中的PLT和GOT(2)--延迟重定位)提到所有动态库函数的plt指令最终都跳进公共plt执行,那么公共plt指令里面的地址是什么鬼? 把test可执行文的共公plt贴出来 ...

  4. 聊聊Linux动态链接中的PLT和GOT(3)——公共GOT表项

    前文(聊聊Linux动态链接中的PLT和GOT(2)--延迟重定位)提到所有动态库函数的plt指令最终都跳进公共plt执行,那么公共plt指令里面的地址是什么鬼? 把test可执行文的共公plt贴出来 ...

  5. 万字长文带你 搞定 linux BT 宝塔面板 之外网上快速搭建苹果CMS电影网站

    文章目录 万字长文带你搞定宝塔面板 一.本地搭建宝塔面板及安装ecshop 1.1前言 1.2面板特色功能 1.3安装环境说明 1.4安装BT面板 1.5常用管理命令 1.6 BT面板一键安装LAMP ...

  6. 汇编语言使用C库函数和Linux动态链接

    使用printf 代码 #cpuid2.s -- Using C labrary calls .section .data output: .asciz "The processor Ven ...

  7. 聊聊Linux动态链接中的PLT和GOT(1)——何谓PLT与GOT

    在介绍PLT和GOT出场之前,先以一个简单的例子引入两个主角,各位请看以下代码: #include <stdio.h>void print_banner() {printf("W ...

  8. 一篇长文带你在python里玩转Json数据

    Json简介 Json(JavaScript Object Notation) 很多网站都会用到Json格式来进行数据的传输和交换. 这因为Json是一种轻量级的数据交换格式,具有数据格式简单,读写方 ...

  9. hutol json null值没了_一篇长文带你在python里玩转Json数据

    前言 文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者: 朱小五/凹凸玩数据 PS:如有需要Python学习资料的小伙伴可以加 ...

最新文章

  1. ​采访了14位技术公司的创始人,他们如何看待2020年的AI行业?
  2. python3 redis操作 错误 cannot import name 'StrictRedis' from 'redis'
  3. 最长回文子串—leetcode5
  4. 利用Math.random做背景图像随机切换【前端开发技能必备系列】
  5. 关于centos6升级python3.6无法使用pip的问题
  6. jzoj4226-A【图论】
  7. 页面某个模块的文字内容是动态的,可能是几个字,也可能是一句话。然 后,希望文字少的时候居中显示,文字超过一行的时候居左显示。该如何实现?...
  8. Pycharm 提示:this license * has been cancelled - Python零基础入门教程
  9. clion IDEA 2019 Activation Code
  10. Ubuntu中配置虚拟专用网络***
  11. linux查看usb设备名称,Linux系统下查看USB设备名及使用USB设备
  12. 23种设计模式的深入浅出(更新中)
  13. Python敏感词过滤DFA算法+免费附带敏感词库
  14. 计算机硬件故障诊断的原则,计算机硬件常见故障诊断和维护.doc
  15. oracle 判断是否复数,第 14 章 使用复数运算库
  16. springboot毕设项目基于SpringBoot的个人理财系统ibx9h(java+VUE+Mybatis+Maven+Mysql)
  17. SQL2019 用户sa‘登录失败(错误18456)
  18. 用友u8系统管理服务器,用友U8服务软件建立新账套的教程
  19. 泰拉瑞亚试图加载不正确的_泰拉瑞亚Switch中文版将在12月19日发售|宝可梦 剑/盾大量细节公布 自动存档可关经验平均分配等...
  20. Java修行——DAY12

热门文章

  1. 解决问题:import torch失败和torch.cuda.is_available()返回false
  2. HyperLPR车牌识别库代码分析总结(15)
  3. 2bc-gskew:De-aliased hybrid branch predictors(1999)
  4. 三星 android 4.4.4,我的手机是三星4.4.4安卓系统,怎么升级到6.1版本
  5. 首席新媒体运营教程:电商UGC社区运营全攻略电商
  6. appium连接vivo手机,启动APP后就不动了--其它手机正常
  7. 苹果 iOS 15 正式发布
  8. Markdown添加目录(自定义目录)
  9. C# 上位机倒数计时器
  10. antdprotable defaultExpandAllRows巨坑的坑