动态链接过程分析

Linux下库函数动态链接过程分析-结合glibc-2.11源码 Linux下程序库函数调用的动态链接过程是很常见的,其实刚学编程时写的helloworld程序调用的printf就牵涉到动态链接,只是我们那时没有去注意罢了。请看下面的helloworld程序反汇编代码int main(int argc, char **argv) { 80483e4: 55 push %ebp 80483e5: 89 e5 mov %esp,%ebp 80483e7 阅读详情


上文 静态链接过程分析 讲解了静态链接的过程,尤其强调了重定位的过程。本文将继续介绍动态链接的过程。

一、前言

静态链接得到的可执行程序,被操作系统加载之后就可以直接执行。

因为在链接的时候,链接器已经把所有目标文件中的代码、数据等 Section,都组装到可执行文件中了。并且把代码中所有使用的外部符号(变量、函数),都进行了重定位(即:把变量、函数的地址,都填写到代码段中需要重定位的地方),因此可执行程序在执行的时候,不依赖于其它的外部模块即可运行。

这里的前提是可执行文件是可写的。

而对于动态链接来说,在编译阶段,仅仅是在可执行文件或者动态库中记录了一些必要的信息。

真正的重定位过程,是在这个时间点来完成的:可执行程序、动态库被加载之后,调用可执行程序的入口函数之前。

只有当所有需要被重定位的符号被解决了之后,才能开始执行程序。既然也是重定位,与静态链接过程一样:也需要把符号的目标地址填写到代码段中需要重定位的地方。

但是对于动态链接,这里有个问题:对于内存的访问是有权限控制的,一般来说:

  • 代码段:可读、可执行
  • 数据段:可读、可写

如果进行符号重定位,就需要对代码进行修改(填写符号的地址),但是代码段又没有可写的权限,那该怎么办呢?

要解决这个问题,那就涉及到了 Linux 中的动态链接器的核心工作。

既然代码段在被加载到内存中之后不可写,但是数据段是可写的。在代码段中引用的外部符号,可以在数据段中增加一个跳板:让代码段先引用数据段中的内容,然后在重定位时,把外部符号的地址填写到数据段中对应的位置,如下图所示:

二、示例程序

下面还是来结合简单的示例来分析:

我们需要 3 个源文件来讨论动态链接中重定位的过程:main.c、a.c、b.c,其中的 a.c 和 b.c 被编译成动态库,然后 main.c 与这两个动态库一起动态链接成可执行程序。它们之间的依赖关系是:


代码如下:

// b.c
#include <stdio.h>

int b = 30;

void func_b(void)
{
    printf("in func_b. b = %d \n", b);
}

定义一个全局变量和一个全局函数,被 a.c 调用。

// a.c
#include <stdio.h>

// 内部定义【静态】全局变量
static int a1 = 10;

// 内部定义【非静态】全局变量
int a2 = 20;

// 声明外部变量
extern int b;

// 声明外部函数
extern void func_b(void);

// 内部定义的【静态】函数
static void func_a2(void)
{
    printf("in func_a2 \n");
}

// 内部定义的【非静态】函数
void func_a3(void)
{
    printf("in func_a3 \n");
}

// 被 main 调用
void func_a1(void)
{
    printf("in func_a1 \n");

    // 操作内部变量
    a1 = 11;
    a2 = 21;

    // 操作外部变量
    b  = 31;

    // 调用内部函数
    func_a2();
    func_a3();

    // 调用外部函数
    func_b();
}
  • 定义了 2 个全局变量:一个静态,一个非静态;
  • 定义了 3 个函数:func_a2 是静态函数,只能在本文件中调用;func_a1 和 func_a3 是全局函数,可以被外部调用
  • 在 main.c 中会调用 func_a1。
// main.c
#include <stdio.h>
#include <unistd.h>
#include <dlfcn.h>

// 声明外部变量
extern int a2;
extern void func_a1();

typedef void (*pfunc)(void);

int main(void)
{
    printf("in main \n");

    // 打印此进程的全局符号表
    void *handle = dlopen(0, RTLD_NOW);
    if (NULL == handle)
    {
        printf("dlopen failed! \n");
        return -1;
    }

    printf("\n------------ main ---------------\n");
    // 打印 main 中变量符号的地址
    pfunc addr_main = dlsym(handle, "main");
    if (NULL != addr_main)
        printf("addr_main = 0x%x \n", (unsigned int)addr_main);
    else
        printf("get address of main failed! \n");

    printf("\n------------ liba.so ---------------\n");
    // 打印 liba.so 中变量符号的地址
    unsigned int *addr_a1 = dlsym(handle, "a1");
    if (NULL != addr_a1)
        printf("addr_a1 = 0x%x \n", (unsigned int)addr_a1);
    else
        printf("get address of a1 failed! \n");

    unsigned int *addr_a2 = dlsym(handle, "a2");
    if (NULL != addr_a2)
        printf("addr_a2 = 0x%x \n", (unsigned int)addr_a2);
    else
        printf("get address of a2 failed! \n");

    // 打印 liba.so 中函数符号的地址
    pfunc addr_func_a1 = dlsym(handle, "func_a1");
    if (NULL != addr_func_a1)
        printf("addr_func_a1 = 0x%x \n", (unsigned int)addr_func_a1);
    else
        printf("get address of func_a1 failed! \n");

    pfunc addr_func_a2 = dlsym(handle, "func_a2");
    if (NULL != addr_func_a2)
        printf("addr_func_a2 = 0x%x \n", (unsigned int)addr_func_a2);
    else
        printf("get address of func_a2 failed! \n");

    pfunc addr_func_a3 = dlsym(handle, "func_a3");
    if (NULL != addr_func_a3)
        printf("addr_func_a3 = 0x%x \n", (unsigned int)addr_func_a3);
    else
        printf("get address of func_a3 failed! \n");


    printf("\n------------ libb.so ---------------\n");
    // 打印 libb.so 中变量符号的地址
    unsigned int *addr_b = dlsym(handle, "b");
    if (NULL != addr_b)
        printf("addr_b = 0x%x \n", *addr_b);
    else
        printf("get address of b failed! \n");

    // 打印 libb.so 中函数符号的地址
    pfunc addr_func_b = dlsym(handle, "func_b");
    if (NULL != addr_func_b)
        printf("addr_func_b = 0x%x \n", (unsigned int)addr_func_b);
    else
        printf("get address of func_b failed! \n");

    dlclose(handle);

    // 操作外部变量
    a2 = 100;

    // 调用外部函数
    func_a1();

    // 为了让进程不退出,方便查看虚拟空间中的地址信息
    while(1) sleep(5);
    return 0;
}
  • 利用 dlopen 函数(第一个参数传入 NULL),来打印此进程中的一些符号信息(变量和函数)
  • 赋值给 liba.so 中的变量 a2,然后调用 liba.so 中的 func_a1 函数

然后将这三个源文件编译成动态库和可执行文件:

$ gcc -m32 -fPIC --shared b.c -o libb.so
$ gcc -m32 -fPIC --shared a.c -o liba.so -lb -L./
$ gcc -m32 -fPIC main.c -o main -ldl -la -lb -L./

  1. -fPIC 参数意思是:生成位置无关代码(Position Independent Code),这也是动态链接中的关键
  2. 既然动态库是在运行时加载,那为什么在编译的时候还需要指明?因为在编译的时候,需要知道每一个动态库中提供了哪些符号。Windows 中的动态库的显性的导出和导入标识,更能体现这个概念(__declspec(dllexport), __declspec(dllimport))。

然后我们可以用 patchelf 工具来查看一个可执行文件或动态库依赖于哪个模块:

$ patchelf --print-needed xxx

三、动态库的加载过程

1、动态链接器加载动态库

当执行 main 程序的时候,操作系统首先把 main 加载到内存,然后通过 .interp 段信息来查看该文件依赖哪些动态库:

$ objdump -s main


上图中的字符串 /lib/ld-linux.so.2,就表示 main 依赖动态链接库。

ld-linux.so.2 也是一个动态链接库,在大部分情况下动态链接库已经被加载到内存中了(动态链接库就是为了共享),操作系统此时只需要把动态链接库所在的物理内存,映射到 main 进程的虚拟地址空间中就可以了,然后再把控制权交给动态链接器。

然后,动态链接器发现:main 依赖 liba.so,于是它就在虚拟地址空间中找一块能放得下 liba.so 的空闲空间,然后把 liba.so 中需要加载到内存中的代码段、数据段都加载进来。当然,在加载 liba.so 时,又会发现它依赖 libb.so,于是又把在虚拟地址空间中找一块能放得下libb.so 的空闲空间,把 libb.so 中的代码段、数据段等加载到内存中,示意图如下所示:

动态链接器自身也是一个动态库,而且是一个特殊的动态库:它不依赖于其他的任何动态库,因为当它被加载的时候,没有人帮它去加载依赖的动态库,否则就变成死循环了。

2、动态库的加载地址

一个进程在运行时的实际加载地址(或者说虚拟内存区域),可以通过指令读取出来:

$ cat /proc/[进程的 pid]/maps 

这里插个问题,当我运行 main 的时候,报了如下的错误:

然后,我用 ldd 命令检查了一下 main 的动态库依赖关系:

至于找不到的情况分为两种:

  • 系统里根本不存在 liba.so 库;
  • liba.so 库在系统中存在,但是动态链接器找不到,即 liba.so 库的位置没有告知动态链接器。

系统里当然找不到 liba.so,因为这是我们自己写的。所以问题出现的原因是第二条。

当然还要包含 libb.so

所以,只要把动态库 liba.so 的绝对路径添加到动态链接器的搜索路径中,那么动态链接器就可以获取到动态库 liba.so 的绝对路径,接着就可以找到动态库文件 liba.so,将动态库文件载入内存,然后就可以使用动态库里面的代码,最终可执行程序 main 就可以成功运行,不会报错。

动态链接器搜索动态库绝对路径的搜索顺序为:DT_RPATH 段→环境变量 LD_LIBRARY_PATH→/etc/ld.so.cache 文件列表→/lib/ 或 /usr/lib 目录。

因此,添加动态库绝对路径的方法也有很多种

接下来,我们就按照上述分析进行操作。

这里有两个方法,一个是临时的,一个是永久性的。

  1. 临时环境变量

输入命令(注意路径改成自己的动态库所在的路径):

$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/projectsauron/test/dynamic-link

然后可以 echo 这个变量检查一下:

现在 main 的链接正常了:

  1. 环境变量

在 ~/.bashrc 里输入:

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/projectsauron/test/dynamic-link

然后退出,在使能刷新一下:

$ source ~/.bashrc

一样可以,这里就不演示了。


再打开一个终端,找到 main 的进程号:
在这里插入图片描述

输入命令 cat /proc/80962/maps 可以看到当我的虚拟机中执行 main 程序时,看到的地址信息是:


另外,还可以看到 C 库(libc-2.31.so)、动态链接器(ld-2.31.so)以及动态加载库 libdl-2.31.so 的虚拟地址区域,布局如下:

代码段 rx,数据段 rw

可以看出 main 可执行程序是位于低地址,所有的动态库都位于 4G 内存空间的最后 1G 空间中。

还有另外一个指令也很好用 $ pmap [进程的 pid],也可以打印出每个模块的内存地址:

四、符号重定位

1、全局符号表

在上一篇文章的静态链接中学习过,链接器在扫描每一个目标文件(.o 文件)的时候,会把每个目标文件中的符号提取出来,构成一个全局符号表。

然后在第二遍扫描的时候,查看每个目标文件中需要重定位的符号,然后在全局符号表中查找该符号被安排在什么地址,然后把这个地址填写到引用的地方,这就是静态链接时的重定位。

但是动态链接过程中的重定位,与静态链接的处理方式差别就大很多了,因为每个符号的地址只有在运行的时候才能知道它们的地址。例如:liba.so 引用了 libb.so 中的变量和函数,而 libb.so 中的这两个符号被加载到什么位置,直到 main 程序准备执行的时候,才能被链接器加载到内存中的某个随机的位置。

也就是说:动态链接器知道每个动态库中的代码段、数据段被加载的内存地址,因此动态链接器也会维护一个全局符号表,其中存放着每一个动态库中导出的符号以及它们的内存地址信息。

在示例代码 main.c 函数中,我们通过 dlopen 返回的句柄来打印进程中的一些全局符号的地址信息,输出内容如下:

可以看到:在全局符号表中,没有找到 liba.so 中的变量 a1 和函数 func_a2 这两个符号,因为它俩都是 static 类型的,在编译成动态库的时候,没有导出到符号表中。

动态链接库中保护两个符号表:

  1. .dynsym:动态符号表: 表示模块中符号的导出、导入关系)
  2. .symtab:符号表: 表示模块中的所有符号


2、全局偏移表 GOT

在示例代码中,liba.so 是比较特殊的,它既被 main 可执行程序所依赖,又依赖于 libb.so。而且,在 liba.so 中,定义了静态、动态的全局变量和函数,可以很好的概况很多种情况,因此这部分内容就主要来分析 liba.so 这个动态库。

前文说过:代码重定位需要修改代码段中的符号引用,而代码段被加载到内存中又没有可写的权限,动态链接解决这个矛盾的方案是:增加一层间接性。

例如:liba.so 的代码中引用了 libb.so 中的变量 b,在 liba.so 的代码段,并不是在引用的地方直接指向 libb.so 数据段中变量 b 的地址,而是指向了 liba.so 自己的数据段中的某个位置,在重定位阶段,链接器再把 libb.so 中变量 b 的地址填写到这个位置。

因为 liba.so 自己的代码段和数据段位置是相对固定的,这样的话,liba.so 的代码段被加载到内存之后,就再也不用修改了。而数据段中这个间接跳转的位置,就称作:全局偏移表(GOT,Global Offset Table)。

liba.so 的代码段中引用了 libb.so 中的符号 b,既然 b 的地址需要在重定位时才能确定,那么就在数据段中开辟一块空间(GOT表),重定位时把 b 的地址填写到 GOT 表中。

而 liba.so 的代码段中,把 GOT 表的地址填写到引用 b 的地方,因为 GOT 表在编译阶段是可以确定的,使用的是相对地址。这样,就可以在不修改 liba.so 代码段的前提下,动态地对符号 b 进行了重定位!

其实,在一个动态库中存在 2 个 GOT 表,分别用于重定位变量符号(section 名称:.got)和函数符号(section 名称:.got.plt)。也就是说:

  • 所有变量类型的符号重定位信息都位于 .got 中
  • 所有函数类型的符号重定位信息都位于 .got.plt 中

并且,在一个动态库文件中,有两个特殊的段(.rel.dyn 和 .rel.plt)来告诉链接器:.got 和 .got.plt 这两个表中,有哪些符号需要进行重定位。

3、liba.so 动态库文件的布局

为了更深刻的理解 .got 和 .got.plt 这两个表,有必要来拆解一下 liba.so 动态库文件的内部结构。

通过 readelf -S liba.so 指令来看一下这个 ELF 文件中都有哪些 section:

dynamic 段

在静态链接,elf文件有一个文件头,里面记录了一些静态链接所需要的信息,比如比如需要的符号表,重定位表等。
而在共享对象中,需要动态链接的变量和函数也需要相应的信息,为了方便动态链接器的执行,在共享对象中,有一个专门的 dynamic 段,汇总了和动态链接有关的段的信息,方便动态链接器使用。

可以看到:一共有 29 个 section,其中的 22、23 就是两个 GOT 表。

另外,从装载的角度来看,装载器并不是把这些 sections 分开来处理,而是根据不同的读写属性,把多个 section 看做一个segment(ELF 的运行视图)。再次通过指令 readelf -l liba.so,来查看一下 segment 信息:

注意看上面两张图红圈部分的地址信息

由上面两张图可以看出:

  • section 0~18 都是可读、可执行权限,被当做一个 segment
  • section 19 ~ 25 都是可读、可写的权限,被当作另一个 segment

再来重点看一下 .got 和 .got.plt 这两个 section,可见:

  • .got 和 .got.plt 与数据段一样,都是可读、可写的,所以被当做同一个 segment 被加载到内存中。

通过以上这 2 张图(红色矩形框),可以得到 liba.so 动态库文件的内部结构如下:

4、liba.so 动态库的虚拟地址

来继续观察 liba.so 文件 segment 信息中的 VirtAddr 列,它表示的是被加载到虚拟内存中的地址:

因为编译动态库时,使用了代码位置无关参数(-fPIC),这里的虚拟地址从 0x0000 0000 开始。

当 liba.so 的代码段、数据段被加载到内存中时,动态链接器找到一块空闲空间,这个空间的开始地址,就相当于一个基地址。

liba.so 中的代码段和数据段中所有的虚拟地址信息,只要加上这个基地址,就得到了实际虚拟地址。所以结合前面的信息可得:

5、GOT 表的内部结构

现在,我们已经知道了 liba.so 库的文件布局,也知道了它的虚拟地址,此时就可以来进一步的看一下 .got 和 .got.plt 这两个表的内部结构了。从刚才的图片中看出:

  • .got 表的长度是 0x18,说明有 6 个表项(每个表项占 4 个字节)
  • .got.plt 表的长度是 0x18,说明有 6 个表项

上文已经说过,这两个表是用来重定位所有的变量和函数等符号的。那么:liba.so 通过什么方式来告诉动态链接器需要对 .got 和 .got.plt 这两个表中的表项进行地址重定位呢?

  • 在静态链接的时候,目标文件是通过两个重定位表 .rel.text 和 .rel.data 这两个段信息来告诉链接器的。
  • 对于动态链接来说,也是通过两个重定位表来传递需要重定位的符号信息的,只不过名字有些不同:.rel.dyn 和 .rel.plt。

通过指令 readelf -r liba.so来查看重定位信息:

由上图可以看出:

  • liba.so 引用了外部符号 b,类型是 R_386_GLOB_DAT,这个符号的重定位描述信息在 .rel.dyn 段中
  • liba.so 引用了外部符号 func_b, 类型是 R_386_JUMP_SLOT,这个符号的重定位描述信息在 .rel.plt 段中


由上图可以看出:

  • liba.so 的代码中在操作变量 b 的时候,就到 .got 表中的 0x0000 3fec 这个地址处来获取变量 b 的真正地址
  • liba.so 的代码中在调用 func_b 函数的时候,就到 .got.plt 表中的 0x0000 400c 这个地址处来获取函数的真正地址

6、反汇编 liba.so 代码

下面就来反汇编一下 liba.so,看一下指令码中是如何对这两个表项进行寻址的。

执行反汇编指令:$ objdump -d liba.so,这里只贴出 func_a1 函数的反汇编代码:

call 10b0 <__x86.get_pc_thunk.bx> 的功能是:把下一条指令(add $0x2de5,%ebx)的地址存储到 %ebx 中,也就是:

%ebx = 0x121b

然后执行: add $0x2de5,%ebx,让 %ebx 加上 0x2de5,结果就是:%ebx = 0x4000。

看下前面的图,0x4000 正是 .got.plt 表的开始地址!

紧接着看第二个红框。

  • mov -0x8(%ebx),%eax:先用 %ebx 减去 0x8 的结果,存储到 %eax 中,结果是:%eax = 0x3fec,这个地址正是变量 b 在 .got 表中的虚拟地址。
  • movl $0x1f,(%eax):在把 0x1f(十进制就是 31),存储到 0x3fec 表项中存储的地址所对应的内存单元中(libb.so的数据段中的某个位置)。

因此,当链接器进行重定位之后,0x3fec 表项中存储的就是变量 b 的真正地址,而上面这两步操作,就把数值 31 赋值给变量 b 了。

第 3 个红色矩形框,是调用函数 func_b,稍微复杂一些,跳转到符号 func_b@plt 的地方,看一下反汇编代码:


jmp 指令调用了 %ebx + 0xc(得到 0x400c)处的那个函数指针,从上面的 .got.plt 布局图中可以看出,重定位之后这个表项中存储的正是 func_b 函数的地址(libb.so 中代码段的某个位置),所以就正确的跳转到该函数中了。

五、补充

1、延迟绑定 plt

实际应用中,共享对象可能会访问大量的外部函数,也就是说,有一个庞大的 got.plt 表。

当加载该共享对象时,理论上,动态链接器就要将该共享对象涉及到的外部模块全部加载并链接,这可能会耗费大量时间,而且,很多外部函数,也许在整个进程生命周期内,都不会被实际调用一次,加载消耗的时间就浪费了。

为了优化这一点,引入延迟绑定(lazy binding)技术。具体办法是,调用外部函数的指令不直接从 got.plt 中取函数地址,而是新建一个plt段,从这个里面取函数的地址。

假设,某个共享对象 a 访问共享对象 b 中的 bar 函数,那么,在 got.plt 和 plt 都有一个 bar 函数的项。

plt 中的 bar 函数的项的内容是:

jmp *(bar@got.plt)
push n
push moduleID
jump _dl_runtime_resolve

我们来分析一下这几句话。

我们假设一个场景,即共享对象在实际执行时,第一次实际调用 bar 函数,这个时机正是体现延迟绑定技术的时候。

jmp *(bar@got.plt) 这句话是说跳转到 got.plt 中 bar 函数的地址,我们知道,因为采用了延迟绑定, 此时这里的地址并不是 bar 的地址。那是什么地址呢?答案是链接器在初始化时,已经帮我们填好了,就是下一条 push 指令的地址。

于是,跳转到了下一条 push 语句,这个语句的 n 又是什么呢?

答案是,为了实现延迟绑定,还建了一个新的段,rel.plt。这个段也是一个重定位表,记录了 got.plt 中的 bar 的位置,告诉链接器,这个 bar 的位置要进行重定位。n 就是 bar 函数在 rel.plt 中的位置。我们可以将其称作 bar 函数的 id。

接下来,push moduleID,是把 bar 所在的模块的 id 入栈。

回顾上面两个 push,我们看到,入栈了模块的 id,以及要使用该模块的函数 bar 的 id n。

然后调用 _dl_runtime_resolve,该函数就帮我们加载并链接要使用的外部模块,并在 got.plt 中更新 bar 函数的地址。该函数会使用到我们刚刚 push 的两个值,这是它领受的任务。

一旦这个过程完成,再次通过 plt 调用 bar 函数时,就会跳转到真正的 bar 函数了。

总结一下,为了实现延迟绑定,又引入了两个新的段,plt 和 rel.plt。

记一次 APK 逆向动静调试 + so 动态链接库分析 好久没有做过安卓逆向了,最近重新系统地学习了安卓逆向技术。找到了一道较为典型的逆向分析题来练手,以锻炼动静态分析和动态链接库分析的基本能力。在这里记录基本的分析流程手法。通过 APK 逆向和 so 动态链接库分析,了解应用的内部逻辑和行为。静态分析帮助我们初步理解代码结构和关键点,而动态调试则允许我们在运行时获取更详细的信息。两者结合,可以有效地进行应用的逆向工程和安全分析。 阅读详情

相关推荐

C语言 | 什么是动态链接与静态链接?

什么是链接? 对于初学C语言的朋友,可能对链接这个概念有点陌生,这里简单介绍一下。我们的C代码编译生成可执行程序会经过如下过程: 链接就是把目标文件与一些库文件生成可执行文件的一个过程。关于更详细的编译过程,可查阅往期笔记:C语言的编译过程 静态、动态链接? 1、什么是静态链接? 静态链接是由链接器在链接时将库的内容加入到可执行程序中的做法。链接器是一个独立程序,将一个或多个库或目标文件(先前由...

嵌入式大杂烩 4381

彻底搞懂程序链接过程之动态链接

通过静态链接,可以生成一个可执行文件,这个可执行文件既可以是完全链接的也可以是部分链接的,对于部分链接的可执行文件,有些符号引用需要等到可执行文件加载时甚至是运行时才会进行符号解析和重定位。 动态链接与静态链接一样包括符号解析和重定位两个任务,静态链接和动态链接的区别之一就是符号解析和重定位的时机,动态链接分为加载时动态链接和运行时动态链接,本篇文章将拆分成3个部分阐述: 1.可执行文件的结构和加载过程。 2.加载时动态链接。 3.运行时动态链接。 可执行文件的结构和加载过程 可执行文件的结构

一口Linux的专栏 9210

pi/4QPSK调制解调原理

文章目录1. π/4\pi/4π/4QPSK 信号的调制原理2. 匹配滤波器与成形滤波器 1. π/4\pi/4π/4QPSK 信号的调制原理 减小相位突变量的QPSK由偏置键控QPSK(OK-QPSK)和 π/4\pi/4π/4偏置的QPSK( π/4\pi/4π/4QPSK)。由于 π/4\pi/4π/4QPSK是用差分相位编码产生的(因此也叫做 π/4\pi/4π/4DQPSK),能有效地进行差分解调和鉴频器解调,在一些不易提取相干载波的场合很有用。 在常规的QPSK中,输入数据经过串/并变换后,分成

gemengxia的博客 2万+

动态链接的整个过程

1.静态链接的缺点 (1)同一个模块被多个模块链接时,那么这个模块在磁盘和内存中都有多个副本,导致很大一部分空间被浪费了。 (2)当程序的任意一个模块发生更新时,整个程序都要重新链接、发布给用户。 2.动态链接的基本思想 动态就是不对那些组成程序的目标文件进行链接,等到程序要运行时才进行链接。也就是说,把链接这个过程推迟到了运行时再进行,这就是动态链接(Dynamic Linking)的基

人生智慧的博客 1万+

VS2019+EasyX图形库实战:手把手教你用C语言打造消消乐小游戏(附完整源码)

本文详细介绍了如何使用VS2019和EasyX图形库从零开始构建一个C语言消消乐小游戏。通过环境配置、数据结构设计、界面绘制、交互实现到游戏逻辑完善的完整流程,帮助开发者掌握图形编程和游戏开发的核心技巧,并附有完整源码供参考。

bush的博客 186

Multisim14.0仿真实战:从零搭建RS/JK触发器电路(附真值表+波形图)

本文通过Multisim 14.0软件,提供了从零搭建RS触发器和JK触发器电路的详细仿真实战教程。内容涵盖核心概念解析、电路搭建步骤、动态波形观测及真值表分析,重点探讨了RS触发器的非法状态问题以及JK触发器的四种工作模式,帮助读者直观理解时序逻辑电路的“记忆”功能与设计要点。

weixin_29175469的博客 272

静态链接与动态链接(详解)

静态链接与动态链接(详解)

wkshwbw的博客 2573

EMC案例:解决千兆网口RTL8211F EMI问题

摘要:本文分析了Amlogic S812平台OEM项目中RTL8211F-CG千兆网卡EMI辐射超标问题。发现百兆测试时25MHz时钟有3dB余量,但千兆125MHz时钟导致超标。问题根源包括:未关闭PHY芯片悬空CLKOUT引脚、TX/RXCLK端RC位置错误、包地不完整及参考地缺失。解决方案涉及软件关闭无用时钟、优化RC布局、加强包地与打孔、调整共模电感位置,并在HDMI 3.3V加磁珠。关键结论指出千兆网125MHz时钟需严格包地打孔,且RC取值需平衡EMI与通信性能。

电子绿洲的博客 1275

动态库静态库的链接过程

1、预备知识 1.1、地址概念 连接地址<>运行地址 存储地址<>加载地址 加载时地址就是程序放置的地址 运行地址就是程序定位的绝对地址,也即在编译连接时定位的地址。 first 0x30000000 : AT(0){main.o},表示运行地址为0x30000000,加载地址为0 如果程序是在flash里运行,则运行地址和加载地址是相同的。 如果程序是在ram里运行,但程序是存储在flash里,则运行地址指向ram,而加载地址是指向flash 1.2、位置代码 位置无关码:指令只使

卢平光的博客 5118

QST QMC5883L —— 经典磁传感器的生存指南与替代预案

本文聚焦于QST旗下经典三轴磁力计 QMC5883L,解析其技术渊源、核心特性及当前面临的停产风险。结合真实应用场景提供续命方案与选型建议,为依赖该芯片的开发团队争取过渡时间窗口期。文章包含实测数据对比、生命周期管理策略及潜在替代方案拆解。存续价值判断✔️ 现有产品设计无需大幅改动即可延续生命;❌ 新项目采用将面临更高的维护成本;⚠️ 建议在6个月内完成技术路线切换。#磁传感器 #嵌入式开发 #供应链管理 #QST #QMC5883L。

2501_93066786的博客 997

Python 爬虫实战:华尔街见闻精选文章爬取(反反爬 + 投资情绪分析)

Python 爬虫实战:华尔街见闻精选文章爬取(反反爬 + 投资情绪分析)

yansideyucsdn的博客 969

位置无关码PIC详解:原理、动态链接库、代码重定位

本文解释了什么是位置无关代码,以及它如何帮助创建具有可共享只读文本段的共享库。位置无关代码(PIC)通过引入全局偏移表(GOT)和过程链接表(PLT)实现,解决了共享库加载时的重定位问题。GOT提供了数据和函数的间接引用,PLT实现了懒绑定,推迟函数地址的解析。当然这也伴随额外的内存加载和寄存器使用成本,但在权衡之下,现代的编译器都更倾向于使用PIC。

主要分享嵌入式软件、人工智能部分知识 7785

【系统架构设计师】预测试卷一:综合知识(75道选择题)

软件架构风格是描述某一特定应用领域中系统组织方式的惯用模式,架构风格反映领域中众多系统所共有的结构和 (48),强调对架构(49)的重用。 (48) A. 语义特性 B. 功能需求 C. 质量属性 D. 业务规则 (49) A. 分析 B. 设计 C.实现

数据知道的博客 7766

Linux下库函数动态链接过程分析(转)

Linux下库函数动态链接过程分析(转) 作者: renyuan000 和linux相关   新浪微博QQ空间QQ微博百度搜藏腾讯朋友QQ收藏百度空间人人网开心网0 Linux下库函数动态链接过程分析-结合glibc-2.11源码(转) 原文地址:http://blog.youkuaiyun.com/lzshlzsh/article/details/6066

linuxheik的专栏 989

使用R语言中的NbClust包确定最佳聚类簇数

聚类分析是一种常用的数据分析技术,它将相似的数据点分组成不同的簇。NbClust包是R语言中一个强大的工具,它提供了多种方法来帮助我们确定最佳的聚类簇数。综上所述,使用R语言中的NbClust包可以帮助我们确定最佳的聚类簇数。通过选择适当的聚类算法和评估指标,我们可以更好地理解数据集的结构和模式,并根据需要进行进一步的分析和解释。此外,NbClust包还提供了其他函数和参数,用于执行不同的聚类算法、选择不同的评估指标等。安装完毕后,我们需要加载NbClust包,以便使用其中的函数。函数来确定最佳聚类簇数。

2301_79326559的博客 2278

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

Linux下库函数动态链接过程分析

太精辟了 http://blog.youkuaiyun.com/lzshlzsh/article/details/6066628 http://www.ibm.com/developerworks/cn/linux/l-elf/part1/ http://www.ibm.com/developerworks/cn/linux/l-elf/part2/ 转载于:https://www.cnblogs.co...

weixin_34082177的博客 93

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

带你轻松理解linux动态链接

用图文实例带你轻松理解动态链接的机制

qq_43707773的博客 1018

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

linux libc源码,Linux下库函数动态链接过程分析-结合glibc-2.11源码

请看下面的helloworld程序反汇编代码intmain(intargc,char**argv){80483e4:55push%ebp80483e5:89e5mov%esp,%ebp80483e7:83e4f0and...

weixin_42292183的博客 768

gcc 编译一个C文件的过程分析,动态链接生成

#gcc hello.c 该命令将hello.c直接生成最终二进制可执行程序a.out 这条命令隐含执行了(1)预处理、(2)汇编、(3)编译并(4)链接形成最终的二进制可执行程序。这里未指定输出文件,默认输出为a.out。   从上面我们知道GCC编译源代码生成最终可执行的二进制程序,GCC后台隐含执行了四个阶段步骤。 GCC编译C源码有四个步骤: 预处理-----> 编译 ----

tanglanting12的专栏 978

vs 连接过程报错 dll 分析 ------- DLL动态链接库

1:编译成功,说明代码没有问题了2:连接报错,说明 exe 在查找dll 的入口地址过程,找不到合适的信息,这些信息保存在 dll 对应的 *.lib 文件里面 说明:exe如果生成成功了lib 这个中间产物就不需要了!!!,这是关键点!!!3:.lib文件和.h 是在visual studio 里面需要开发者自己设置的,这是让exe 正确找到DLL动态链接库调用函数的地址信息!!!,使用者不...

weixin_34185560的博客 240

i386 Linux下 ELF 动态链接分析 (一)

导读: Aug 29i386 Linux下 ELF 动态链接分析 (一)work Add commentsIan Lance Taylor正在写连载文章介绍linkers。一直想了解dynamic linking的过程,于是正好就学习了一下。program loader、program linker和dynamic linker的具体工作过程暂不在讨论范围。首先,随便写个小程

xu的blog 2702

gcc/g++使用格式+各种选项,程序编译过程 -- 预处理/编译(分析树,符号解析,函数名修饰,编译优化,生成目标代码)/汇编/链接过程,函数库(动态链接过程)

gcc/g++使用格式+各种选项,程序编译过程 -- 预处理/编译(分析树,符号解析,函数名修饰,编译优化,生成目标代码)/汇编/链接过程,函数库(动态链接过程)

m0_74206992的博客 622

深入Os--动态链接

动态链接,加载时动态链接,运行中动态链接

x13262608581的博客 1207

Intel平台下Linux中ELF文件动态链接的加载、解析及实例分析(一):

动态链接,一个经常被人提起的话题。但在这方面很少有文章来阐明这个重要的软件运行机制,只有一些关于动态链接库编程的文章。本系列文章就是要从源代码的层次来探讨这个问题。当然从文章的题目就可以看出,intel平台下的linux ELF文件的动态链接。一则是因为这一方面的资料查找比较方便,二则也是这个讨论的意思比其它的动态链接要更为重要(毕竟现在是intel的天下)。当然,有了这么一个例子,其它的平台

vrix的专栏 988

glibc动态链接器dl_runtime_resolve简要分析

dl_runtime_resolve简要分析 资料 glibc 2.9 source linux elf 手册 &各种百度搜索 基于32位elf,64位一些结构会略有不同,新手学习,如果有理解错误,请师傅们帮忙指出。 elf执行时动态绑定简要分析 动态链接的过程 动态链接中起到核心作用的是got节和plt节,链接器为所有共享目标文件(shared object)中未定义的(undef)外部...

Hello World.c 3516
上一篇: 静态链接过程分析
下一篇: STM32 调试之栈回溯和 CmBacktrace 的使用
Projectsauron
博客等级 码龄4年 2587粉丝 100原创
评论 5
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值