【图片+代码】:GCC 链接过程中的【重定位】过程分析

2023-10-26

目录

  • 示例代码

  • sub.o 文件内容分析

    • 段信息

    • 符号表信息

  • main.o 文件分析

    • 段信息

    • 符号表信息

    • 绝对寻址

    • 相对寻址

    • 重定位表信息

  • 可执行程序 main

    • 段信息

    • 符号表信息

    • 绝对地址重定位

    • 相对地址重定位

  • 总结

别人的经验,我们的阶梯!

最近因为项目上的需要,利用动态链接库来实现了一个插件系统,顺便就复习了一下关于 Linux 中一些编译、链接的内容。

在链接过程中,符号重定位是比较麻烦的事情,特别是在动态链接的过程中,因为需要考虑到很多不同的情况。

这篇文章作为第一篇,先来聊一聊静态链接中的重定位过程

按照惯例,还是以一个简短的示例代码作为载体,看一看 GCC 在链接的过程中,是如何根据目标文件(.o文件)来进行重定位,生成最终的可执行文件的。

示例代码

示例代码很简单,一共有两个源文件:main.c 和 sub.c。

在 sub.c 中定义一个全局变量和一个全局函数,然后在 main.c 中使用这个全局变量和全局函数。代码如下:

sub.c

main.c

 在一般的开发过程中,都是使用 GCC 工具,直接把这两个源文件编译得到可执行文件。

但是,为了探究编译、链接过程中的一些内部情况,我们需要把编译、链接的过程拆开,从中间过程中产生的目标文件(.o 文件)中,来查看一些情况。

先把这两个源文件编译成目标文件 sub.o 和 main.o:

这样就得到了两个目标文件,先来初步看一下这两个目标文件中的一些信息。

以上这两个文件的编译过程都是独立的,虽然 main.o 中使用了两个符号(全局函数和全局变量),但是此时 main.o 并不知道这两个符号是在哪个文件中定义的。

当链接器把所有的 .o文件链接成可执行文件的过程中,才能确定这两个符号是在哪里。

Linux 系统中,目标文件(.o)可执行文件都是 ELF 格式,因此如何查看ELF格式文件的一些工具指令就非常有帮助。

很久之前总结过这篇文章:《Linux系统中编译、链接的基石-ELF文件:扒开它的层层外衣,从字节码的粒度来探索》,里面详细总结了ELF文件的内部结构,以及一些相关的工具。(Linux系统中编译、链接的基石-ELF文件:扒开它的层层外衣,从字节码的粒度来探索_远近长安的博客-CSDN博客

sub.o 文件内容分析

段信息

首先来简单瞄一眼 sub.o 中的一些信息。

sub.o 中的段信息如下(指令:readelf -S sub.o):

我们主要关心框里面的代码段和数据段就可以了,可以看出:

1、代码段(.text):地址Addr是0x0000 0000(因为这是目标文件,不是可执行文件,所以不会安排地址),它在 sub.o 文件中的偏移(Off)是0x34,长度是 0x0C 字节;

2、数据段(.data):地址Addr是0x0000 0000,它在 sub.o 文件中的偏移量(Off)是0x40,长度是 0x04 字节;

简单算一下:sub.o 的开始部分是 ELF的 header,通过 readelf -h sub.o 指令可以看出来 header 部分是 52 字节(即:0x34),如下:

因此可以得到:

1、代码段(.text)是紧接在 header 之后,长度是 0x0C 个字节,在文件中占据着 0x34 ~ 0x3F 这部分空间(0x3F = 0x34 + 0x0C - 1);

2、数据段(.data)是进阶在代码段之后,在文件中占据着 0x40 ~ 0x43 这部分空间;

符号表信息

下面再来说说符号表的事情。

简单来说,符号表就是一个文件中定义的所有符号、引用的外部符号(在其它文件中定义),包括:变量名、函数名、段名等等,都属于符号。

当然了,在 ELF 文件中会详细的说明每一个符号的类型、大小、可见性等信息。

如果对 ELF 文件格式有过了解,那么一定知道每一条符号的信息,都是通过一个结构体来描述具体含义的,描述符号表的结构体如下:

// Symbol table entries for ELF32.
struct Elf32_Sym {
   Elf32_Word st_name;     // Symbol name (index into string table)
   Elf32_Addr st_value;    // Value or address associated with the symbol
   Elf32_Word st_size;     // Size of the symbol
   unsigned char st_info;  // Symbol's type and binding attributes
   unsigned char st_other; // Must be zero; reserved
   Elf32_Half st_shndx;    // Which section (header table index) it's defined in
};

再来看一下 sub.o 中的符号表,下面这张图(指令:readelf -s sub.o)

关注上图红框中的两个符号:SubData 和 SubFunc,很明显它们就是 sub.c 中定义的那两个全局变量和全局函数。

对于 SubData 符号来说:

1、Size = 4:长度是 4 个字节;

2、Type = OBJECT:说明这是一个数据对象;

3、Bind = CLOBAL:说明这个符号是全局可见的,也就是在其他文件中也可以使用;

4、Ndx = 2:说明这个符号是属于第2个段中,也就是数据段(.data);

同样的道理,对于 SubFunc 符号来说:

1、Size = 12:长度是 12 个字节;

2、Type = FUNC:说明这是一个函数;

3、Bind = CLOBAL:说明这个符号是全局可见的,也就是在其他文件中也可以调用;

4、Ndx = 1:说明这个符号是属于第1个段中,也就是代码段(.text);

main.o 文件分析

按照上面的步骤,把 main.o 也分析一下。

段信息

指令:readelf -S main.o

可以看出:

1、代码段(.text):地址Addr是0x0000 0000(因为这是目标文件,不是可执行文件,所以不会安排地址),它在 main.o 文件中的偏移(Off)是0x34,长度是 0x32 字节;

2、数据段(.data):地址Addr是0x0000 0000,它在 main.o 文件中的偏移量(Off)是0x66,长度是 0 字节,因为它没有定义变量;

在文件中的布局如下所示:

符号表信息

指令:readelf -s main.o

 重点看一下红框内的三个符号:

main 符号:

1、Size = 50:长度是 50 个字节,对应着代码段的长度 0x32;

2、Type = FUNC:说明这是一个函数;

3、Bind = CLOBAL:说明这个符号是全局可见的,也就是在其他文件中也可以调用;

4、Ndx = 1:说明这个符号是属于第1个段中,也就是代码段(.text);

下面两个符号 SubData 和 SubFunc,他们的 Ndx 都是 UND,表示这两个符号被 main.o 使用,但是定义在其他文件中

我们知道,当链接成可执行文件时,所有的符号都必须有确定的地址虚拟地址),所以链接器就需要在链接的过程中找到这两个符号在可执行文件中的地址,然后把这两个地址填写到 main 的代码段中。

可以先来看一下 main.o 的反汇编代码:

指令:objdump -d main.o

黄色框中是把数值 0 存储到 eax 寄存器中,然后把 eax 压到栈中,然后红色矩形框调用了一个函数。

从示例代码(.c文件)中可知:main 函数在调用 sub.c 中的 SubFunc 函数时,传入了变量 SubData。

黄色部分的 00 00 00 00 就应该是符号 SubData 的地址,只不过此时 main.o 还不知道这个符号将会被链接器安排到什么地址,所以只能空着(以 4 个字节的 00 来占位)。

红色部分的调用(call)地址为什么是 fc ff ff ff?

按照小端格式计算:0xff ff ff fc,十进制对应 -4,为什么设置成 -4 呢?

对于 x86 平台的 ELF 格式来说,对地址进行修正的方式有两种:绝对寻址相对寻址

绝对寻址

对于 SubData 符号就是绝对寻址,在链接成可执行文件时,这个地址在代码段中偏移 0x12 个字节(黄色矩形框指令码偏移 0x11 个字节,跨过一个字节的指令码 a1 就是 0x12 个字节),这个地方 4 个字节的当前值是 00 00 00 00。

链接器在修正的时候(就是链接成可执行文件的时候),会把这 4 个字节修改为 SubData 变量在可执行文件中的实际地址虚拟地址)。

相对寻址

红色矩形框中的函数调用(SubFunc 符号),就是相对寻址,就是说:当 CPU 执行到这条指令的时候,把 PC 寄存中的值加上这个偏移地址,就是被调用对象的实际地址。

链接器在重定位的时候,目的就是计算出相对地址,然后替换掉 fc ff ff ff 这四个字节。

PC 寄存器中的值是确定的,当 call 指令这条指令被 CPU 取到之后,PC 寄存器被自动增加,指向 下一条指令的开始地址(偏移 0x1f 地址处)。

实际地址 = PC值 + xxxx_xxxx,所以:xxxx_xxxx = 实际地址 - PC值。

而 PC 值与 xxxx_xxxx 所在的地址之间是有关系的:PC值 + (-4) 就得到 xxxx_xxxx 所在的地址,因此在 main.o 中预先在这个地址处填 fc ff ff ff(-4)。

(PS:注意是 xxxx_xxxx 所在的地址)

问题来了,链接器怎么知道 main.o 中代码段的这两个地方,需要进行地址修正?

这就是下面介绍的重定位表的作用了!

重定位表信息

指令:objdump -r main.o

重定位表就表示:该目标文件中,有哪些符号需要在链接的时候进行地址重定位。

从上图框中可以看到:main.o 中代码段(.text)的 SubData SubFunc 这两个符号都需要链接器对它进行重定位。

TYPE列R_386_32 表示绝对寻址R_386_PC32 表示相对寻址

OFFSET列:表示需要重定位的符号在 main.o 文件代码段中的偏移位置。

刚才已经看了 main.o 的反汇编代码,可以看到偏移 0x12 0x1b 的地方,就是需要进行地址重定位的两个符号。

可执行程序 main

有了两个目标文件:main.o 和 sub.o,就可以链接得到可执行文件了:

ld -m elf_i386 main.o sub.o -e main -o main

段信息

使用 readelf 工具来看一下 main 可执行文件中的段信息(指令:readelf -S main):

1、红色矩形框是代码段(.text),链接器把它放在虚拟地址 0x0804 8094;

2、黄色矩形框是数据段(.data),链接器把它放在虚拟地址 0x0804 9138;

从段信息可以看到 main 文件中代码段数据段的布局如下:

 可执行文件 main 是由 main.o 和 sub.o 这两个目标文件组成的,所以 main 中的代码段是由 main.o 中的代码段和 sub.o 中的代码段组合得到的;对于数据段,由于 main.o 中数据段的长度为0,所以 main 中的数据段就是 sub.o 中的数据段(长度为4),如下图所示:

符号表信息

指令:readelf -s main

黄色矩形框中的 SubData 属于数据段,长度是 4 个字节,虚拟地址是 0x0804 9138,与段信息中的值是一致的。

红色矩形框中的 SubFunc 属于代码段,长度是 12 个字节,虚拟地址是 0x0804 80c6.

因为 main 中的代码段包括两部分内容:

1、main.o 中的代码段 main 函数;

2、sub.o 中的代码段 SubFunc 函数;

所以,可执行文件 main 中的代码段,先存放的是 main 函数,虚拟地址是 0x0804 8094,长度是 0x32(50个字节);

紧接着存放的是 SubFunc 函数,虚拟地址:0x0804 80c6,长度是 0x0c(12个字节)。

如下图所示:

链接器在第一遍扫描所有的目标文件时,把所有相同类型的段进行合并,安排到相应的虚拟地址,如上图所示。

所谓的安排虚拟地址,就是指定这块内容被加载到虚拟内存的什么地方

当可执行文件被执行的时候,加载器就把每一块内容复制虚拟内存相应的地址处

同时,链接器还会建立一个全局符号表,把每一个目标文件中的符号信息都复制到这个全局符号表中

对于我们的示例程序,全局符号表包括:

SubData: 属于 sub.o 文件,数据段,安排在虚拟地址 0x0804_9138;

SubFunc: 属于 sub.o 文件,代码段,安排在虚拟地址 0x0804_80c6;

其它符号信息...

绝对地址重定位

然后,链接器第二遍扫描所有的目标文件,检查哪些目标文件中的符号需要进行重定位。

对于我们的示例程序,首先来看一下 main.o 中使用的外部变量 SubData 的重定位。

从 main.o 的重定位表中可知:SubData 符号需要进行重定位,需要把这个符号在执行时刻的绝对地址(虚拟地址),写入到可执行文件 main 的代码段中偏移 0x12 字节的位置。

也就是说,需要解决两个问题:

  1. 需要计算出在执行文件 main 中的什么位置来填写绝对地址(虚拟地址);
  2. 填写的绝对地址(虚拟地址)的值是多少;

首先来解决第一个问题。

从可执行文件的段表中可以看出:目标文件 main.o sub.o 中的代码段被存放到可执行文件 main 中代码段的开始位置,先放 main.o 代码段,再放 sub.o 代码段。

代码段的开始地址距离文件开始的偏移量是 0x94,再加上偏移量 0x12,结果就是 0xa6

也就是说:需要在 main 文件中偏移 0xa6 处填入 SubData 在执行时刻的绝对地址(虚拟地址)。

再来解决第二个问题。

链接器从全局符号表中发现:SubData 符号属于 sub.o 文件,已经被安排在虚拟地址 0x0804 9138 处,因此只需要把 0x0804 9138 填写到可执行文件 main 中偏移 0xa6 的地方。

我们来读取 main 文件,验证一下这个位置处的虚拟地址是否正确:

指令:od -Ax -t x1 -j 166 -N 4 main

-Ax: 显示地址的时候,用十六进制来表示。如果使用 -Ad,意思就是用十进制来显示地址;

-t -x1: 显示字节码内容的时候,使用十六进制(x),每次显示一个字节(1);

-j 166: 跨过 166 个字节(十六进制 0xa6);

-N 4:只需要读取 4 个字节;

 注意:显示的是小端格式(低地址存放低位字节)。

相对地址重定位

从上面描述的重定位表中看出:main.o 代码段中的 SubFunc 符号也需要重定位,而且是相对寻址

链接器需要把 SunFunc 符号在执行时刻的绝对地址(虚拟地址),减去 call 指令的下一条指令(PC 寄存器) 之后的差值,填写到执行文件 main 中的 main.o 代码段偏移 0x1b 的地方。

同样的道理,需要解决2个问题

  1. 需要计算出在执行文件 main 中的什么位置来填写相对地址;

  2. 填写的相对地址的值是多少;

首先来解决第一个问题。

main.o 的重定位表中可知:需要修正的位置距离 main.o 中代码段的偏移量是 0x1b 字节。

可执行文件 main 中代码段的开始地址距离文件开始的偏移量是 0x94,再加上偏移量 0x1b 就是0xaf

也就是说:需要在 main 文件中 0xaf 偏移处填入一个相对地址,这个相对地址的值就是 SubFunc 在执行时刻的绝对地址(虚拟地址)、距离 call 指令的下一条指令的偏移量。

再来解决第二个问题。

链接器在第一遍扫描的时候,已经把 sub.o 中的符号 SubFunc 记录到全局符号表中了,知道SubFunc 函数被安排在虚拟地址 0x0804 80c6 的地方。

但是不能直接把这个绝对地址填进去,因为 call 指令需要的是相对地址(偏移地址)。

链接器把 main 代码段起始位置安排在 0x0804 8094,那么偏移 0x1b 处的虚拟地址就是:0x0804 80af,然后还需要再跨过 4 个字节(因为执行call指令时,PC的值自动增加到下一条指令的开始地址)才是此刻PC寄存器的值,即:0x0804 80b3,如下图中红色部分:

两个虚拟地址都知道了,计算一下差值就可以了:0x0804_80c6 - 0x0804_80b3 = 0x13

也就是说:在可执行文件 main 中偏移为 0xaf 的地方,填入相对地址 0x0000_0013 就完成了SubFunc 符号的重定位。

还是用 od 指令来读取 main 文件的内容来验证一下:

指令:od -Ax -t x1 -j 175 -N 4 main

总结

经过以上两个重定位操作,main.c 中使用的两个外部符号就解决了地址重定位问题。

再来看一下可执行文件 main 的反汇编代码:

指令:objdump -d main.o

 从黄色和红色的矩形框可以看出,二进制指令中的地址值与上面的分析是一致的。

 以上就是静态链接过程中地址重定位的基本过程,与动态链接相比,静态链接还是相对简单很多。

以后有机会的话,我们再继续聊一下动态链接中的一些操作,谢谢!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【图片+代码】:GCC 链接过程中的【重定位】过程分析 的相关文章

  • 在 Kali (Debian) 中安装 mono-devel 时,软件包具有未满足的依赖关系

    我尝试安装 mono devel 并输入sudo apt get mono devel在终端中 但失败了 得到以下结果 apt get install mono devel Reading package lists Done Buildi
  • 在我的 Linux 机器上安装 lisp

    我使用 Vim 作为我的编辑器 Practical common Lisp 建议安装 Lispbox 我不知道如何使用 emacs 不知道如何用那个 T T 运行 lisp 代码 之后我找到了一个名为 limp vim 的 vim lisp
  • 获取后台进程的退出代码

    我有一个从我的主 bourne shell 脚本中调用的命令 CMD 该命令需要很长时间 我想修改脚本如下 作为后台进程并行运行命令 CMD CMD 在主脚本中 有一个循环每隔几秒监视生成的命令 该循环还向标准输出回显一些消息 指示脚本的进
  • Linux下对多个文件进行排序

    我有多个 很多 文件 每个都非常大 file0 txt file1 txt file2 txt 我不想将它们合并到一个文件中 因为生成的文件将超过 10 场演出 每个文件中的每一行都包含一个 40 字节的字符串 现在字符串的排序相当好 大约
  • Java Linux 非阻塞套接字超时行为

    我有一个 Java 非阻塞服务器 它跟踪选择器中的所有套接字通道 然后我与服务器建立 500 个连接并定期发送数据 服务器接收到的每条数据都会回显给客户端 问题来了 测试工作了几个小时 然后突然逐渐地 服务器管理的所有套接字在尝试读取数据时
  • 打印堆栈指针的值

    如何在 Linux Debian 和 Ubuntu 中用 C 打印堆栈指针的当前值 我尝试谷歌但没有找到结果 一个技巧是简单地将本地地址作为指针打印出来 但它不可移植 甚至无法保证有效 void print stack pointer vo
  • 如果specfile中的某些条件不满足,如何中止rpm包的安装?

    还有一些事情Requires标签不满足 所以我写了一个脚本来验证这些东西 但是我把它们放在哪里呢 如果没有找到 那么我想退出安装 提示用户在尝试再次安装此 rpm 之前执行这些步骤 writing exit 1 in installtag
  • 健全性检查 SSH 公钥? [关闭]

    Closed 这个问题是无关 help closed questions 目前不接受答案 我已要求用户提供他们的公共 id rsa pub ssh 密钥 然后将其放入 home theiraccount ssh authorized key
  • 将尾部输出重定向到程序中

    我想使用 tail 作为标准输入向程序发送文本文件中的最新行 首先 我向程序回显一些每次都相同的输入 然后从输入文件发送尾部输入 该输入文件应首先通过 sed 处理 以下是我期望工作的命令行 但是当程序运行时 它只接收回显输入 而不接收尾部
  • 获取当前时间(以小时和分钟为单位)

    我正在尝试从系统收集信息 并且需要获取当前时间 以小时和分钟为单位 目前我有 date awk print 4 输出如下 16 18 54 怎样才能把秒数去掉呢 提供格式字符串 date H M Running man date将给出所有格
  • Linux 中不使用 C++ 的 C 异常处理

    Linux 是否提供了 C 语言的异常处理而不求助于 C 或者 实现此类异常处理的最佳方法是什么 目标是避免检查每个调用的函数的返回码 而是执行类似于 C 的线程安全且易于移植的操作 您可以通过为其编写信号处理程序来处理信号 GNU 记录的
  • Ubuntu 上的 Docker 无法连接到本地主机,但可以连接到其 IP

    我运行的是 Ubuntu 18 04 uname r 5 3 0 46 generic 我已经安装了docker docker version Docker version 19 03 8 build afacb8b7f0 我有一个简单的
  • 为什么 SDL 在 Mac 上比 Linux 上慢得多?

    我正在研究使用 SDL2 渲染的单线程图形程序 https github com TurkeyMcMac intergrid 请参阅末尾的较小示例 它既可以在旧的 Linux 机器上运行 也可以在不太旧的 Mac 上运行 Linux 计算机
  • 用于列出用户和组的 Python 脚本

    我正在尝试编写一个脚本 在自己的行上输出每个用户及其组 如下所示 user1 group1 user2 group1 user3 group2 user10 group6 etc 我正在为此用 python 编写一个脚本 但想知道如何做到这
  • 在 Ubuntu 上运行独立的 ASP.NET Core 应用程序

    我已经发布了一个 ASP NET Core 应用程序作为针对 Ubuntu 的独立应用程序 发布似乎工作正常 我已将这些文件复制到一台漂亮的 Ubuntu 机器上 现在 我如何运行我的应用程序 我的理解是 因为它是一个独立的 NET Cor
  • 如何在汇编语言中换行打印多个字符串

    我试图在汇编中的不同行上打印多个字符串 但使用我的代码 它只打印最后一个字符串 我对汇编语言非常陌生 所以请耐心等待 section text global start start mov edx len mov edx len1 mov
  • 当模式在范围内时使用 sed 打印范围?

    我有一个充满查询的日志文件 我只想查看有错误的查询 日志条目类似于 path to file executing query QUERY SIZE ROWS MSG DURATION 我想打印所有这些东西 但只有当MSG 包含一些有趣的内容
  • 在 UNIX 时间戳 Shell/Bash 中将日期与时区转换

    我需要将日期从格式为 yyyy mm dd hh mm ss TZ 的字符串转换为 UNIX 时间 TZ 时区 到目前为止我所做的是将没有时区的 yyyy mm dd hh mm ss 格式的日期转换为时间戳 dateYMD 2019 2
  • 使用请求和多处理时的奇怪问题

    请检查这个Python代码 usr bin env python import requests import multiprocessing from time import sleep time from requests import
  • 使用正在运行的进程的共享内存收集核心转储

    核心转储仅收集进程空间 而不收集为进程间通信创建的共享内存 如何使核心转储也包含正在运行的进程的共享内存 设置核心文件过滤器 proc PID coredump filter per http man7 org linux man page

随机推荐

  • python制作词云图

    准备基础模块 matplotlib 数据可视化模块 numpy 数值计算模块 jieba 分词模块 wordcloud 词云模块 Pillow PIL 图像处理模块 同时准备遮罩图和文本信息 实现代码 导入matplotlib模块pyplo
  • 数据挖掘学习之路二:数据预处理方法概述

    主要是将数据中缺失的数据补充完整 消除噪声数据 识别和删除离群点并解决不一致性 主要达到的目标是 将数据格式标准化 异常数据清除 错误纠正 重复数据清除 A 异常数据处理 分析异常数据 1 使用统计值进行判断 最大值 最小值 平均值等判断是
  • C++中的++i 与 i++详解

    一 区别 i 与 i 的主要区别有两个 1 i 返回原来的值 i 返回加1后的值 2 i 不能作为左值 而 i 可以 二 原理 毫无疑问大家都知道第一点 我们重点说下第二点 首先解释下什么是左值与右值 通俗地说 以赋值符号 为界 左边的就是
  • CUDA小白 - NPP(6) 图像处理 Geometry Transforms (2)

    cuda小白 原始API链接 NPP GPU架构近些年也有不少的变化 具体的可以参考别的博主的介绍 都比较详细 还有一些cuda中的专有名词的含义 可以参考 详解CUDA的Context Stream Warp SM SP Kernel B
  • npm包的发布和删除

    登录npm发布包 1 终端 输入npm login 可以登录npm账号 依次输入用户名 密码 密码盲打 邮箱 2 输入nrm use npm通过命令更改npm服务 需将npm切换为npm官方服务 不能使用taobao镜像 注 可以通过npm
  • 基于虚拟机的集群冗余简化

    为了实现高可用性 企业使用中间软件例如微软和 Veritas 的集群软件 把两台服务器绑定在一个热备环境 即使运行在服务器上的应用程序有集群 感知能力 万一主服务器遭遇硬件或软件错误 这样的安排仍然会导致非应 用程序当机 冗余能消除单点失败
  • 面经——嵌入式软件工程师ARM体系与架构相关

    参考 嵌入式软件工程师笔试面试指南 ARM体系与架构 作者 嵌入式与Linux那些事 发布时间 2021 04 28 15 22 06 网址 https blog csdn net qq 16933601 article details 1
  • Windows下配置nginx+php(wnmp)

    Windows下配置nginx php wnmp waynewuzhenbo 博客园 http www cnblogs com wuzhenbo p 3493518 html Windows下配置nginx php wnmp 第一部分 准备
  • GraphPad Prism 9 for mac/win 安装教程

    GraphPad Prism集生物统计 化学统计 以及科技绘图于一身 其中医学所能用到的绘图需要它几乎都能满足 Prism 现在被各种生物学家以及社会和物理科学家广泛使用 超过110个国家的超过20万名科学家依靠 Prism 来分析 绘制和
  • 删除节点后从新加入的错误

    ERR Node 172 168 63 202 7001 is not empty Either the nodealready knows other nodes check with CLUSTER NODES or contains
  • Qt中绘制直线

    绘制多条直线 直接上代码 绘制直线的部分 QPen pen Qt lightGray 1 pen setStyle Qt DashDotDotLine pen setWidth 1 painter setPen pen painter tr
  • JVM【八股文】

    JVM 八股文 JVM内存区域划分 程序计数器 栈 堆 方法区 一块大的区域 需要根据功能 来划分不同的小区域 JVM内存是从操作系统里申请来的 之后堆这部分区域进行了划分 1 程序计数器 内存中最小的区域 保存了下一条要执行指令的地址 指
  • Spring 如何使用注解装配Bean呢?

    转自 Spring 如何使用注解装配Bean呢 我们都知道在Spring中 可以使用xml可实现 Bean状态操作 但是如果有非常多的Bean时 就会出现大量的xml 这样就会导致配置文件非常的大 并且容易出错及难维护 Java 从JDK5
  • PHP取整,四舍五入取整、向上取整、向下取整、小数截取。

    PHP取整数函数常用的四种方法 1 直接取整 舍弃小数 保留整数 intval 2 四舍五入取整 round 3 向上取整 有小数就加1 ceil 4 向下取整 floor 一 intval 对变数转成整数型态 intval如果是字符型的会
  • 迭代器iterator

    能进行算术运算的迭代器只有随即访问迭代器 要求容器元素存储在连续内存空间里 vector string deque的迭代器是有加减法的 但是map set multimap multiset的迭代器是没有加减法的 list也不可以
  • minio老版本集成到k8s的yaml

    apiVersion apps v1 kind StatefulSet metadata name minio spec replicas 1 serviceName minio selector matchLabels name mini
  • Android WebView使用详解及注意事项

    未经本人授权 不得转载 否则必将维权到底 目前很多公司的 App 就只使用一个 WebView 作为整体框架 App 中的所有内容全部使用 HTML5 进行展示 这样只需要写一次 HTML5 代码 就可以在 Android 和 iOS 平台
  • Android textAppearance的属性设置及TextView属性详解

    http blog csdn net jaycee110905 article details 8762238 textAppearance的属性设置 android textAppearance android attr textAppe
  • html实现蜂窝菜单

    效果图 CSS样式 keyframes fade in mkmxd 1 0 filter blur 20px opacity 0 to filter none opacity 1 keyframes drop in mkmxd 1 0 tr
  • 【图片+代码】:GCC 链接过程中的【重定位】过程分析

    目录 示例代码 sub o 文件内容分析 段信息 符号表信息 main o 文件分析 段信息 符号表信息 绝对寻址 相对寻址 重定位表信息 可执行程序 main 段信息 符号表信息 绝对地址重定位 相对地址重定位 总结 别人的经验 我们的阶