Linux笔记---动静态库(原理篇)
1. ELF文件格式
动静态库文件的构成是什么样的呢?或者说二者的内容是什么?
实际上,可执行文件,目标文件,静态库文件,动态库文件都是使用ELF文件格式进行组织的。
ELF(Executable and Linkable Format)文件格式是Unix系统及其衍生系统中广泛使用的可执行文件、共享库和核心转储的二进制文件格式。
主要包括以下四种文件*
- 可重定位文件(Relocatable File):包含适合于与其他目标文件链接来创建可执行文件或者共享目标文件的代码和数据,如xxx.o文件。
- 可执行文件(Executable File):包含适合于执行的一个程序,规定了exec()如何创建一个程序的进程映像,如a.out文件。
- 共享目标文件(Shared Object File):包含可在两种上下文中链接的代码和数据。首先链接编辑器可以将它和其它可重定位文件和共享目标文件一起处理,生成另外一个目标文件;其次,动态链接器可能将它与某个可执行文件以及其它共享目标一起组合,创建进程映像,如xxx.so文件。
- 内核转储(core dumps):存放当前进程的执行上下文,用于dump信号触发。
结构组成
ELF头(ELF header):位于文件的开始位置,主要目的是定位文件的其他部分。
程序头表(Program header table):列举了所有有效的段(segments)和它们的属性。
节(Section):文件的最小逻辑组织单元,用于存储程序在编译、链接或执行过程中需要的特定类型数据。每个Section都有明确的用途,例如存储代码、全局变量、符号表或调试信息等。
节头表(Section header table):包含对节(sections)的描述。
总结来说,除了节以外的三个部分实际上都是帮助定位的辅助信息,程序的核心信息在节部分。
我们可以通过 readelf 工具来查看一个可执行程序的各个部分,下面我们以ls为例展示各个部分。
1.1 ELF header
位于文件的开始位置,主要目的是定位文件的其他部分。包含文件类型、目标结构、ELF文件格式的版本、程序入口地址、程序头表的文件偏移、节头表的文件偏移等重要信息。
readelf -h /usr/bin/ls
shishen@hcss-ecs-b8e6:~$ readelf -h /usr/bin/ls
ELF Header:Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64Data: 2's complement, little endianVersion: 1 (current)OS/ABI: UNIX - System VABI Version: 0Type: DYN (Position-Independent Executable file)Machine: Advanced Micro Devices X86-64Version: 0x1Entry point address: 0x6aa0Start of program headers: 64 (bytes into file)Start of section headers: 136232 (bytes into file)Flags: 0x0Size of this header: 64 (bytes)Size of program headers: 56 (bytes)Number of program headers: 13Size of section headers: 64 (bytes)Number of section headers: 31Section header string table index: 30
Magic: 标识ELF文件的魔数字节序列。
Class: 文件位数(32位或64位)。
Data: 数据存储的字节序(小端或大端)。
Version: ELF格式版本号。
OS/ABI: 目标操作系统和应用二进制接口(ABI)。
ABI Version: ABI的版本号。
Type: 文件类型(可执行、共享库等)。
Machine: 目标CPU架构。
Version: ELF版本(通常为1)。
Entry point address: 程序执行的起始地址。
Start of program headers: 程序头表在文件中的偏移量。
Start of section headers: 节头表在文件中的偏移量。
Flags: 处理器特定的标志位。
Size of this header: ELF头的大小(字节)。
Size of program headers: 单个程序头条目的大小。
Number of program headers: 程序头条目数量。
Size of section headers: 单个节头条目的大小。
Number of section headers: 节头条目数量。
Section header string table index:节名称字符串表的索引号。
1.2 Section header table
ELF文件的链接视图(Linking view),包含对节(sections)的描述。即一个ELF文件中到底有哪些具体的sections,以及这些sections的属性信息。
简单来说,这部分包含了一个个的section header,它们与section一一对应,指示各个section的属性信息,这与文件系统中的inode table的设计思路相同。
readelf -S /usr/bin/ls
shishen@hcss-ecs-b8e6:~$ readelf -S /usr/bin/ls
There are 31 section headers, starting at offset 0x21428:Section Headers:[Nr] Name Type Address OffsetSize EntSize Flags Link Info Align[ 0] NULL 0000000000000000 000000000000000000000000 0000000000000000 0 0 0[ 1] .interp PROGBITS 0000000000000318 00000318000000000000001c 0000000000000000 A 0 0 1[ 2] .note.gnu.pr[...] NOTE 0000000000000338 000003380000000000000030 0000000000000000 A 0 0 8[ 3] .note.gnu.bu[...] NOTE 0000000000000368 000003680000000000000024 0000000000000000 A 0 0 4[ 4] .note.ABI-tag NOTE 000000000000038c 0000038c0000000000000020 0000000000000000 A 0 0 4[ 5] .gnu.hash GNU_HASH 00000000000003b0 000003b0000000000000004c 0000000000000000 A 6 0 8[ 6] .dynsym DYNSYM 0000000000000400 000004000000000000000b88 0000000000000018 A 7 1 8[ 7] .dynstr STRTAB 0000000000000f88 00000f8800000000000005a6 0000000000000000 A 0 0 1[ 8] .gnu.version VERSYM 000000000000152e 0000152e00000000000000f6 0000000000000002 A 6 0 2[ 9] .gnu.version_r VERNEED 0000000000001628 0000162800000000000000c0 0000000000000000 A 7 2 8[10] .rela.dyn RELA 00000000000016e8 000016e80000000000001410 0000000000000018 A 6 0 8[11] .rela.plt RELA 0000000000002af8 00002af80000000000000960 0000000000000018 AI 6 25 8[12] .init PROGBITS 0000000000004000 00004000000000000000001b 0000000000000000 AX 0 0 4[13] .plt PROGBITS 0000000000004020 000040200000000000000650 0000000000000010 AX 0 0 16[14] .plt.got PROGBITS 0000000000004670 000046700000000000000030 0000000000000010 AX 0 0 16[15] .plt.sec PROGBITS 00000000000046a0 000046a00000000000000640 0000000000000010 AX 0 0 16[16] .text PROGBITS 0000000000004ce0 00004ce000000000000123a2 0000000000000000 AX 0 0 16[17] .fini PROGBITS 0000000000017084 00017084000000000000000d 0000000000000000 AX 0 0 4[18] .rodata PROGBITS 0000000000018000 000180000000000000004dcc 0000000000000000 A 0 0 32[19] .eh_frame_hdr PROGBITS 000000000001cdcc 0001cdcc000000000000056c 0000000000000000 A 0 0 4[20] .eh_frame PROGBITS 000000000001d338 0001d3380000000000002120 0000000000000000 A 0 0 8[21] .init_array INIT_ARRAY 0000000000020fd0 0001ffd00000000000000008 0000000000000008 WA 0 0 8[22] .fini_array FINI_ARRAY 0000000000020fd8 0001ffd80000000000000008 0000000000000008 WA 0 0 8[23] .data.rel.ro PROGBITS 0000000000020fe0 0001ffe00000000000000a78 0000000000000000 WA 0 0 32[24] .dynamic DYNAMIC 0000000000021a58 00020a580000000000000200 0000000000000010 WA 7 0 8[25] .got PROGBITS 0000000000021c58 00020c5800000000000003a0 0000000000000008 WA 0 0 8[26] .data PROGBITS 0000000000022000 000210000000000000000278 0000000000000000 WA 0 0 32[27] .bss NOBITS 0000000000022280 0002127800000000000012c0 0000000000000000 WA 0 0 32[28] .gnu_debugaltlink PROGBITS 0000000000000000 000212780000000000000049 0000000000000000 0 0 1[29] .gnu_debuglink PROGBITS 0000000000000000 000212c40000000000000034 0000000000000000 0 0 4[30] .shstrtab STRTAB 0000000000000000 000212f8000000000000012f 0000000000000000 0 0 1
Key to Flags:W (write), A (alloc), X (execute), M (merge), S (strings), I (info),L (link order), O (extra OS processing required), G (group), T (TLS),C (compressed), x (unknown), o (OS specific), E (exclude),D (mbind), l (large), p (processor specific)// 注:
// [Nr] 节的索引号(从 1 开始),0 表示无效节。
// Name 节的名称(如 .text、.data、.rodata)。
// Type 节的类型(如 PROGBITS 表示程序数据,SYMTAB 表示符号表)。
// Address 节加载到内存时的虚拟地址(未加载时为 0)。
// Offset 节在文件中的起始偏移量(字节)。
// Size 节的大小(字节)。
// EntSize 如果节是表格(如符号表),表示每个条目的大小;否则为 0。
// Flags 节的属性标志(如 A 可分配,X 可执行,W 可写)。
// Link 链接到其他节的索引(如符号表会链接到字符串表)。
// Info 节的附加信息(如符号表的局部符号起始索引)。
// Align 节的对齐要求(如 16 表示按 16 字节对齐)。
链接视图的含义是:目标文件以及动静态库在进行链接形成可执行程序时,是以section为单位进行的,即各个section各自进行合并。
1.3 Program header table
ELF文件的执行视图(execution view),列举了所有有效的段(segments)和它们的属性。
同样地,这部分包含了一个个的program header(或者说segment header),它们与segment一一对应,指示各个segment的属性信息。
可执行程序在被加载到内存当中时,多个节会合并成一个段(合并原则:相同属性,比如可读,可写,可执行,需要加载时申请空间等)。所以,Program header table其实就是对中间部分的一个重新划分。
readelf -l /usr/bin/ls
shishen@hcss-ecs-b8e6:~$ readelf -l /usr/bin/lsElf file type is DYN (Position-Independent Executable file)
Entry point 0x6aa0
There are 13 program headers, starting at offset 64Program Headers:Type Offset VirtAddr PhysAddrFileSiz MemSiz Flags AlignPHDR 0x0000000000000040 0x0000000000000040 0x00000000000000400x00000000000002d8 0x00000000000002d8 R 0x8INTERP 0x0000000000000318 0x0000000000000318 0x00000000000003180x000000000000001c 0x000000000000001c R 0x1[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]LOAD 0x0000000000000000 0x0000000000000000 0x00000000000000000x0000000000003458 0x0000000000003458 R 0x1000LOAD 0x0000000000004000 0x0000000000004000 0x00000000000040000x0000000000013091 0x0000000000013091 R E 0x1000LOAD 0x0000000000018000 0x0000000000018000 0x00000000000180000x0000000000007458 0x0000000000007458 R 0x1000LOAD 0x000000000001ffd0 0x0000000000020fd0 0x0000000000020fd00x00000000000012a8 0x0000000000002570 RW 0x1000DYNAMIC 0x0000000000020a58 0x0000000000021a58 0x0000000000021a580x0000000000000200 0x0000000000000200 RW 0x8NOTE 0x0000000000000338 0x0000000000000338 0x00000000000003380x0000000000000030 0x0000000000000030 R 0x8NOTE 0x0000000000000368 0x0000000000000368 0x00000000000003680x0000000000000044 0x0000000000000044 R 0x4GNU_PROPERTY 0x0000000000000338 0x0000000000000338 0x00000000000003380x0000000000000030 0x0000000000000030 R 0x8GNU_EH_FRAME 0x000000000001cdcc 0x000000000001cdcc 0x000000000001cdcc0x000000000000056c 0x000000000000056c R 0x4GNU_STACK 0x0000000000000000 0x0000000000000000 0x00000000000000000x0000000000000000 0x0000000000000000 RW 0x10GNU_RELRO 0x000000000001ffd0 0x0000000000020fd0 0x0000000000020fd00x0000000000001030 0x0000000000001030 R 0x1Section to Segment mapping:Segment Sections...00 01 .interp 02 .interp .note.gnu.property .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt 03 .init .plt .plt.got .plt.sec .text .fini 04 .rodata .eh_frame_hdr .eh_frame 05 .init_array .fini_array .data.rel.ro .dynamic .got .data .bss 06 .dynamic 07 .note.gnu.property 08 .note.gnu.build-id .note.ABI-tag 09 .note.gnu.property 10 .eh_frame_hdr 11 12 .init_array .fini_array .data.rel.ro .dynamic .got // 注:
// Type 段的类型(如 LOAD 表示可加载段,DYNAMIC 表示动态链接信息)。
// Offset 段在文件中的起始偏移量(字节)。
// VirtAddr 段加载到内存时的虚拟地址(程序运行时访问的地址)。
// PhysAddr 段加载到内存时的物理地址(通常与 VirtAddr 相同,现代系统忽略)。
// FileSiz 段在文件中的大小(字节)。
// MemSiz 段在内存中的大小(字节,可能大于 FileSiz,如 .bss 节会填充零)。
// Flags 段的权限标志(R=读,W=写,X=执行)。
// Align 段在内存和文件中的对齐要求(如 0x1000 表示按 4KB 对齐)。
Section to Segment mapping部分就显示了各个segment包含了哪些section。
执行视图的含义是:该部分负责告诉操作系统,如何加载可执行文件,完成进程内存的初始化。一个可执行程序的格式中,一定有 program header table。
节(Section) 是链接时的逻辑划分(如 .text、.data),数量多且大小不一。
段(Segment) 是执行时的物理加载单元(如代码段、数据段),合并相同权限的节后,操作系统只需按段映射内存,减少内存碎片和系统调用次数。
加载到内存时将节合并为段的原因
- 符合操作系统的内存页管理:操作系统以 页(Page) 为单位管理内存(如4KB)。段会按页对齐,避免跨页的节导致内存浪费或权限冲突。如果不进行合并,假设页面大小为4096字节(内存块基本大小,加载,管理的基本单位),如果.text部分为4097字节,.init部分为512字节,那么它们将占用3个页面,而合并后,它们只需2个页面。
- 统一内存访问权限:每个段有明确的权限(读/写/执行),而节可能分散且权限不同。
2. 静态链接原理
2.1 地址重定位
我们知道,静态库实际上就是一系列目标文件的集合。所以,要理解静态链接,我们只需要知道目标文件在进行链接时发生了什么即可。
将各个节的数据分别合并到一起是必然的,但是除此之外呢?
以如下代码为例:
// hello.c
#include<stdio.h>void run();int main() {printf("hello world!\n");run();return 0;
} // code.c
#include<stdio.h>void run() {printf("running...\n");
}
我们将这两个原文件进行编译得到目标文件:
shishen@hcss-ecs-b8e6:~/113code/linux-c/动静态库/display$ gcc -c *.c
shishen@hcss-ecs-b8e6:~/113code/linux-c/动静态库/display$ ls
code.c code.o hello.c hello.o
这里,我们需要用到一个指令objdump -d:将代码段(.text)进行反汇编查看。
shishen@hcss-ecs-b8e6:~/113code/linux-c/动静态库/display$ objdump -d hello.o > hello.s
shishen@hcss-ecs-b8e6:~/113code/linux-c/动静态库/display$ objdump -d code.o > code.s
shishen@hcss-ecs-b8e6:~/113code/linux-c/动静态库/display$ ls
code.c code.o code.s hello.c hello.o hello.s
这里的call很明显就是函数调用,call指令的编码为e8。
对比源文件可以看出,无论是printf函数还是run函数,e8跳转到的地址都为0。这是因为,在完成链接之前,编译器并不知道这些外部函数的实现与定义,无法为其分配地址(逻辑地址),就以0代替其地址。
所以,在链接时,编译器还需要将代码段中这些为0的地址修改为实际为这些函数分配的地址。
要完成这项工作,编译器还需要符号表的帮助。每个目标文件都有自己的符号表,在进行链接时,大家相互对照符号表,就能找到外部函数或变量是在哪一个文件当中声明的了,进而就能为其分配地址。
readelf -s # 读取目标文件的符号表
shishen@hcss-ecs-b8e6:~/113code/linux-c/动静态库/display$ readelf -s hello.oSymbol table '.symtab' contains 7 entries:Num: Value Size Type Bind Vis Ndx Name0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 FILE LOCAL DEFAULT ABS hello.c2: 0000000000000000 0 SECTION LOCAL DEFAULT 1 .text3: 0000000000000000 0 SECTION LOCAL DEFAULT 5 .rodata4: 0000000000000000 40 FUNC GLOBAL DEFAULT 1 main5: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND puts6: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND run
shishen@hcss-ecs-b8e6:~/113code/linux-c/动静态库/display$ readelf -s code.oSymbol table '.symtab' contains 6 entries:Num: Value Size Type Bind Vis Ndx Name0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 FILE LOCAL DEFAULT ABS code.c2: 0000000000000000 0 SECTION LOCAL DEFAULT 1 .text3: 0000000000000000 0 SECTION LOCAL DEFAULT 5 .rodata4: 0000000000000000 26 FUNC GLOBAL DEFAULT 1 run5: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND puts// 注:printf底层就是puts
其中,每一行就代表一个符号,我们主要关注Ndx列和Name列。
- ABS:该符号的值是绝对地址,不依赖于任何节(Section),通常是文件名或特殊定义的全局符号。
- UND:该符号在当前目标文件中未定义,需要在链接时从其他目标文件或库中解析(如外部函数或全局变量)。
数字:表示符号定义在对应索引号的节中。例如:Ndx=1:符号属于 .text 节(代码段)。Ndx=5:符号属于 .rodata 节(只读数据段)。
可以看到,在hello.o中,run和puts都是未定义,而code.o中,run有定义但puts依然未定义。
当我们将二者进行链接之后:
shishen@hcss-ecs-b8e6:~/113code/linux-c/动静态库/display$ gcc -o main hello.o code.o
shishen@hcss-ecs-b8e6:~/113code/linux-c/动静态库/display$ objdump -d main > main.s
puts我们暂时也不关心,因为puts使用的是动态链接。可以看到,run函数的地址为1171,该地址也标注在run函数的名称之前。
这就说明编译器在链接时,找到了run函数,并为其分配了地址。
shishen@hcss-ecs-b8e6:~/113code/linux-c/动静态库/display$ readelf -s mainSymbol table '.dynsym' contains 7 entries:Num: Value Size Type Bind Vis Ndx Name0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _[...]@GLIBC_2.34 (2)2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterT[...]3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5 (3)4: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__5: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMC[...]6: 0000000000000000 0 FUNC WEAK DEFAULT UND [...]@GLIBC_2.2.5 (3)Symbol table '.symtab' contains 38 entries:Num: Value Size Type Bind Vis Ndx Name0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 FILE LOCAL DEFAULT ABS Scrt1.o2: 000000000000038c 32 OBJECT LOCAL DEFAULT 4 __abi_tag3: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c4: 0000000000001090 0 FUNC LOCAL DEFAULT 16 deregister_tm_clones5: 00000000000010c0 0 FUNC LOCAL DEFAULT 16 register_tm_clones6: 0000000000001100 0 FUNC LOCAL DEFAULT 16 __do_global_dtors_aux7: 0000000000004010 1 OBJECT LOCAL DEFAULT 26 completed.08: 0000000000003dc0 0 OBJECT LOCAL DEFAULT 22 __do_global_dtor[...]9: 0000000000001140 0 FUNC LOCAL DEFAULT 16 frame_dummy10: 0000000000003db8 0 OBJECT LOCAL DEFAULT 21 __frame_dummy_in[...]11: 0000000000000000 0 FILE LOCAL DEFAULT ABS hello.c12: 0000000000000000 0 FILE LOCAL DEFAULT ABS code.c13: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c14: 0000000000002120 0 OBJECT LOCAL DEFAULT 20 __FRAME_END__15: 0000000000000000 0 FILE LOCAL DEFAULT ABS 16: 0000000000003dc8 0 OBJECT LOCAL DEFAULT 23 _DYNAMIC17: 000000000000201c 0 NOTYPE LOCAL DEFAULT 19 __GNU_EH_FRAME_HDR18: 0000000000003fb8 0 OBJECT LOCAL DEFAULT 24 _GLOBAL_OFFSET_TABLE_19: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_mai[...]20: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterT[...]21: 0000000000004000 0 NOTYPE WEAK DEFAULT 25 data_start22: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.523: 0000000000004010 0 NOTYPE GLOBAL DEFAULT 25 _edata24: 0000000000001171 26 FUNC GLOBAL DEFAULT 16 run25: 000000000000118c 0 FUNC GLOBAL HIDDEN 17 _fini26: 0000000000004000 0 NOTYPE GLOBAL DEFAULT 25 __data_start27: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__28: 0000000000004008 0 OBJECT GLOBAL HIDDEN 25 __dso_handle29: 0000000000002000 4 OBJECT GLOBAL DEFAULT 18 _IO_stdin_used30: 0000000000004018 0 NOTYPE GLOBAL DEFAULT 26 _end31: 0000000000001060 38 FUNC GLOBAL DEFAULT 16 _start32: 0000000000004010 0 NOTYPE GLOBAL DEFAULT 26 __bss_start33: 0000000000001149 40 FUNC GLOBAL DEFAULT 16 main34: 0000000000004010 0 OBJECT GLOBAL HIDDEN 25 __TMC_END__35: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMC[...]36: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@G[...]37: 0000000000001000 0 FUNC GLOBAL HIDDEN 12 _init
从上面的结果中可以看到run函数对应的section的编号为16,这意味着run函数在编号为16的节中。
readelf -S main
从main.s中可以得到验证(只关注开头的描述以及main函数和run函数即可):
Disassembly of section .text:0000000000001060 <_start>:1060: f3 0f 1e fa endbr64 1064: 31 ed xor %ebp,%ebp1066: 49 89 d1 mov %rdx,%r91069: 5e pop %rsi106a: 48 89 e2 mov %rsp,%rdx106d: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp1071: 50 push %rax1072: 54 push %rsp1073: 45 31 c0 xor %r8d,%r8d1076: 31 c9 xor %ecx,%ecx1078: 48 8d 3d ca 00 00 00 lea 0xca(%rip),%rdi # 1149 <main>107f: ff 15 53 2f 00 00 call *0x2f53(%rip) # 3fd8 <__libc_start_main@GLIBC_2.34>1085: f4 hlt 1086: 66 2e 0f 1f 84 00 00 cs nopw 0x0(%rax,%rax,1)108d: 00 00 00 0000000000001090 <deregister_tm_clones>:1090: 48 8d 3d 79 2f 00 00 lea 0x2f79(%rip),%rdi # 4010 <__TMC_END__>1097: 48 8d 05 72 2f 00 00 lea 0x2f72(%rip),%rax # 4010 <__TMC_END__>109e: 48 39 f8 cmp %rdi,%rax10a1: 74 15 je 10b8 <deregister_tm_clones+0x28>10a3: 48 8b 05 36 2f 00 00 mov 0x2f36(%rip),%rax # 3fe0 <_ITM_deregisterTMCloneTable@Base>10aa: 48 85 c0 test %rax,%rax10ad: 74 09 je 10b8 <deregister_tm_clones+0x28>10af: ff e0 jmp *%rax10b1: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)10b8: c3 ret 10b9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)00000000000010c0 <register_tm_clones>:10c0: 48 8d 3d 49 2f 00 00 lea 0x2f49(%rip),%rdi # 4010 <__TMC_END__>10c7: 48 8d 35 42 2f 00 00 lea 0x2f42(%rip),%rsi # 4010 <__TMC_END__>10ce: 48 29 fe sub %rdi,%rsi10d1: 48 89 f0 mov %rsi,%rax10d4: 48 c1 ee 3f shr $0x3f,%rsi10d8: 48 c1 f8 03 sar $0x3,%rax10dc: 48 01 c6 add %rax,%rsi10df: 48 d1 fe sar %rsi10e2: 74 14 je 10f8 <register_tm_clones+0x38>10e4: 48 8b 05 05 2f 00 00 mov 0x2f05(%rip),%rax # 3ff0 <_ITM_registerTMCloneTable@Base>10eb: 48 85 c0 test %rax,%rax10ee: 74 08 je 10f8 <register_tm_clones+0x38>10f0: ff e0 jmp *%rax10f2: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)10f8: c3 ret 10f9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)0000000000001100 <__do_global_dtors_aux>:1100: f3 0f 1e fa endbr64 1104: 80 3d 05 2f 00 00 00 cmpb $0x0,0x2f05(%rip) # 4010 <__TMC_END__>110b: 75 2b jne 1138 <__do_global_dtors_aux+0x38>110d: 55 push %rbp110e: 48 83 3d e2 2e 00 00 cmpq $0x0,0x2ee2(%rip) # 3ff8 <__cxa_finalize@GLIBC_2.2.5>1115: 00 1116: 48 89 e5 mov %rsp,%rbp1119: 74 0c je 1127 <__do_global_dtors_aux+0x27>111b: 48 8b 3d e6 2e 00 00 mov 0x2ee6(%rip),%rdi # 4008 <__dso_handle>1122: e8 19 ff ff ff call 1040 <__cxa_finalize@plt>1127: e8 64 ff ff ff call 1090 <deregister_tm_clones>112c: c6 05 dd 2e 00 00 01 movb $0x1,0x2edd(%rip) # 4010 <__TMC_END__>1133: 5d pop %rbp1134: c3 ret 1135: 0f 1f 00 nopl (%rax)1138: c3 ret 1139: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)0000000000001140 <frame_dummy>:1140: f3 0f 1e fa endbr64 1144: e9 77 ff ff ff jmp 10c0 <register_tm_clones>0000000000001149 <main>:1149: f3 0f 1e fa endbr64 114d: 55 push %rbp114e: 48 89 e5 mov %rsp,%rbp1151: 48 8d 05 ac 0e 00 00 lea 0xeac(%rip),%rax # 2004 <_IO_stdin_used+0x4>1158: 48 89 c7 mov %rax,%rdi115b: e8 f0 fe ff ff call 1050 <puts@plt>1160: b8 00 00 00 00 mov $0x0,%eax1165: e8 07 00 00 00 call 1171 <run>116a: b8 00 00 00 00 mov $0x0,%eax116f: 5d pop %rbp1170: c3 ret 0000000000001171 <run>:1171: f3 0f 1e fa endbr64 1175: 55 push %rbp1176: 48 89 e5 mov %rsp,%rbp1179: 48 8d 05 91 0e 00 00 lea 0xe91(%rip),%rax # 2011 <_IO_stdin_used+0x11>1180: 48 89 c7 mov %rax,%rdi1183: e8 c8 fe ff ff call 1050 <puts@plt>1188: 90 nop1189: 5d pop %rbp118a: c3 ret Disassembly of section .fini:
目标文件又叫可重定位文件,这里的重定位就是指的为这些外部函数或变量重新分配地址的过程。
总结来说,静态链接的原理就是将各个目标文件的对应节分别合并,并对照符号表完成对外部函数或变量的重定位。
2.1 虚拟地址空间补充
我们前面在main.s中看到,run函数的地址是1171,但实际上这个说法并不准确。准确的说法是:1171是run函数在代码段的地址,也即run函数在代码段的偏移量(各个段内部从0开始编址)。
run函数最终被加载到内存当中的虚拟地址应该是代码段的地址+偏移量。
其中代码段的地址是在每次程序被加载到内存当中时随机分配的。
我们修改一下hello.c的代码:
#include<stdio.h>void run();int main() {printf("hello world!\n");run();printf("%p\n", &run);return 0;
}
重新编译链接之后,run函数的地址变为:
00000000000011af <run>:
运行./main可以看到结果:
shishen@hcss-ecs-b8e6:~/113code/linux-c/动静态库/display$ ./main
hello world!
running...
0x55b1458801af
shishen@hcss-ecs-b8e6:~/113code/linux-c/动静态库/display$ ./main
hello world!
running...
0x55ca012441af
shishen@hcss-ecs-b8e6:~/113code/linux-c/动静态库/display$ ./main
hello world!
running...
0x56372f8ed1af
很明显,run函数的虚拟地址是极富规律且与其偏移量强相关的。
ELF文件是地址空间初始化的基础,但完整的内存布局是内核、ELF、动态链接器共同作用的结果。
3. 动态链接的原理
进程是如何跳转到动态库并共享动态库的代码的呢?
概括来说很简单,将动态库函数的逻辑地址映射到物理地址空间中动态库代码所在位置即可。
即库的起始虚拟地址 + 方法偏移量 ---> 库的起始物理地址 + 方法偏移量。
但实际上其中的细节与机制并不简单。
3.1 链接的时机
将动态库函数的逻辑地址映射到物理地址空间中共享库代码所在位置,这一过程显然是在程序被加载到内存时完成的。也就是说动态链接的时机就是程序被加载到内存时。
对于这一点,我们还可以更加详细一点。
上文当中我们说到,目标文件完成链接之后,程序当中多了许多库当中的函数。
除了main和run以外的函数。来自于库 /lib64/ld-linux-x86-64.so.2 ,用于程序初始化:
在C/C++程序中,当程序开始执行时,它首先并不会直接跳转到 main 函数。实际上,程序的入口点是 _start ,这是一个由C运行时库(通常是glibc)或链接器(如ld)提供的特殊函数。
从main.s中可以看到,_start的地址位1060,而ELF header中指明的程序入口地址就是1060:
shishen@hcss-ecs-b8e6:~/113code/linux-c/动静态库/display$ readelf -h main
ELF Header:Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64Data: 2's complement, little endianVersion: 1 (current)OS/ABI: UNIX - System VABI Version: 0Type: DYN (Position-Independent Executable file)Machine: Advanced Micro Devices X86-64Version: 0x1Entry point address: 0x1060 # 程序入口地址Start of program headers: 64 (bytes into file)Start of section headers: 14032 (bytes into file)Flags: 0x0Size of this header: 64 (bytes)Size of program headers: 56 (bytes)Number of program headers: 13Size of section headers: 64 (bytes)Number of section headers: 31Section header string table index: 30
在 _start 函数中,会执行一系列初始化操作,这些操作包括:
- 设置堆栈:为程序创建一个初始的堆栈环境。
- 初始化数据段:将程序的数据段(如全局变量和静态变量)从初始化数据段复制到相应的内存位置,并清零未初始化的数据段。
- 动态链接:这是关键的一步, _start 函数会调用动态链接器的代码来解析和加载程序所依赖的动态库(shared libraries)。动态链接器会处理所有的符号解析和重定位,确保程序中的函数调用和变量访问能够正确地映射到动态库中的实际地址。
- 调用 __libc_start_main :一旦动态链接完成, _start 函数会调用__libc_start_main (这是glibc提供的⼀个函数)。 __libc_start_main 函数负责执行一些额外的初始化工作,比如设置信号处理函数、初始化线程库(如果使用了线程)等。
- 调用 main 函数:最后, __libc_start_main 函数会调用程序的 main 函数,此时程序的执行控制权才正式交给用户编写的代码。
- 处理 main 函数的返回值:当 main 函数返回时, __libc_start_main 会负责处理这个返回值,并最终调用 _exit 函数来终止程序。
动态链接器:
- 动态链接器(如ld-linux.so)负责在程序运行时加载动态库。
- 当程序启动时,动态链接器会解析程序中的动态库依赖,并加载这些库到内存中。环境变量和配置文件:
- Linux系统通过环境变量(如LD_LIBRARY_PATH)和配置文件(如/etc/ld.so.conf及其子配置文件)来指定动态库的搜索路径。
- 这些路径会被动态链接器在加载动态库时搜索。
- 缓存文件:为了提高动态库的加载效率,Linux系统会维护一个名为/etc/ld.so.cache的缓存文件。该文件包含了系统中所有已知动态库的路径和相关信息,动态链接器在加载动态库时会首先搜索这个缓存文件。
上述过程描述了C/C++程序在 main 函数之前执行的一系列操作,但这些操作对于大多数程序员来说是透明的。程序员通常只需要关注 main 函数中的代码,而不需要关心底层的初始化过程。然而,了解这些底层细节有助于更好地理解程序的执行流程和调试问题。
3.2 全局偏移量表GOT
- 动态库的代码可能是会变化的,所以在编译链接时可执行程序当中是无法完成动态库的重定位的。
- 于是,我们只能在程序被加载到内存之后,找到被加载到内存中的动态库,再进行重定位。即,加载地址重定位。
- 但代码段的权限为只读,我们无法对代码段进行修改。为了实现这一点,动态链接需要一种机制来在运行时查找和绑定符号的地址,这就是GOT的作用。
全局偏移量表(GOT,Global Offset Table)是Linux系统下ELF格式可执行文件中用于定位全局变量和函数的表,主要用于动态链接。
其实际上就是在专门预留的一片用来存放函数的跳转地址的区域:.got节。
[24] .got PROGBITS 0000000000003fb0 00002fb00000000000000050 0000000000000008 WA 0 0 8
由于.got所在的段是可读可写的,所以就可以实现在运行当中动态地完成重定位。
3.3 过程链接表PLT
我们在main.s中可以看到,动态链接库函数的函数名之后都会跟着PLT:
PLT(Procedure Linkage Table,过程链接表)是程序动态链接中的关键机制,主要用于延迟绑定(Lazy Binding)动态库中的函数地址。
延迟绑定:程序启动时不会立即解析所有动态库函数的地址,而是在首次调用时才通过PLT解析并缓存地址,减少启动时间。
3.3.1 GOT与PLT的作用
存储全局变量和函数地址:GOT存储了程序中使用的外部函数和全局变量的实际地址,使得程序在运行时能够正确地访问这些外部符号。
支持动态链接:在动态链接过程中,GOT允许程序在运行时解析和绑定外部符号,而不需要在编译时就确定所有符号的地址。
实现延迟绑定:通过GOT和PLT(过程链接表)的配合,实现了函数的延迟绑定,即函数在第一次被调用时才进行地址绑定,提高了程序的启动速度。
3.3.2 工作原理
第一次调用:当程序第一次调用某个外部函数时,会通过PLT跳转到GOT,由于GOT中此时没有该函数的地址,会再次跳转回PLT,PLT会将函数的ID压入栈中,然后调用_dl_runtime_resolve函数进行符号查找和重定位,找到函数地址后,将其填充到GOT中,之后再跳转到该函数地址执行。
后续调用:当再次调用该函数时,PLT会直接跳转到GOT中存储的函数地址,无需再次进行符号查找和重定位。
3.4 地址无关代码PIC
动态库被加载到内存当中之后,其内部函数的虚拟地址就都是确定了的。当我们使用这些共享代码时,在我们进程的虚拟地址空间当中也应当为其分配对应的虚拟地址,否则代码与其地址就对应不起来了(汇编代码中,每条代码都有自己的地址)。
这就会导致一个问题:两个动态库要求的地址发生冲突。
为了解决这个问题,我们希望动态库中的代码被加载到任意位置都能运行,这就是地址无关代码。
PIC(Position Independent Code)地址无关代码是一种编程技术,它使得代码不依赖于特定的内存地址。
所以我们在编译动态库对应的目标文件时,需要加上-fPIC选项:
gcc -fPIC -c
3.4.1 原理
相对寻址:PIC代码通过使用相对寻址方式来访问数据和代码,而不是使用绝对地址。这意味着代码可以在内存中的任何位置加载和执行,而不需要进行重定位。
全局偏移表(GOT):在PIC中,全局变量和函数的地址是通过全局偏移表(GOT)来访问的。GOT是一个数据结构,用于存储全局变量和函数的实际地址。当代码需要访问这些全局符号时,它会通过GOT中的相应项来间接引用。
相关文章:
Linux笔记---动静态库(原理篇)
1. ELF文件格式 动静态库文件的构成是什么样的呢?或者说二者的内容是什么? 实际上,可执行文件,目标文件,静态库文件,动态库文件都是使用ELF文件格式进行组织的。 ELF(Executable and Linkable…...
SpringBoot整合Logback日志框架深度实践
一、依赖与默认集成机制 SpringBoot从2.x版本开始默认集成Logback日志框架,无需手动添加额外依赖。当项目引入spring-boot-starter-web时,该组件已包含spring-boot-starter-logging,其底层实现基于LogbackSLF4J组合。这种设计使得开发者只需…...
Spring Boot中接入DeepSeek的流式输出
第一步,添加依赖: <!-- WebFlux 响应式支持 --> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId> </dependency> 第二步,配置We…...
路由交换网络专题 | 第四章 | 生成树 | VRRP | 边缘端口
拓扑图 (1)SW1、SW2、SW3 三台交换机之间存在环路问题,需要通过生成树协议破环,请简述二层环路可能导致的问题。 因为交换机在收到一个广播帧之后,会对非接收端口进行转发。每台交换机都转发的话,就行形成一…...
SFOS2:常用容器(布局)介绍
一、前言 最近在进行sailfish os的开发,由于在此之前并没有从事过QT开发的工作,所以对这一套颇为生疏,以此记录一下。以下内容不一定完全准确,开发所使用的是Qt Quick 2.6与Sailfish.Silica 1.0两个库。 二、布局 1.Qt Quick 2.…...
VS qt 联合开发环境下的多国语言翻译
添加Linguist 文件方法,如同添加类文件的方式,那样: 其他跟QT的一样的流程,另外在main函数里要注册一下, QTextCodec::setCodecForLocale(textCodec); QTranslator translator5; QString trans5 fi…...
基于 Python 的 ROS2 应用开发全解析
引言 在机器人操作系统(ROS)不断发展的进程中,ROS2 作为新一代的机器人框架,带来了诸多显著的改进与新特性。Python 作为一种简洁、高效且具有强大数据处理能力的编程语言,在 ROS2 应用开发中占据着重要地位。本文将深…...
AI分析师
01 实操 人工 公司需要开发了一个XX系统,在文件夹中包含了XX.csv,其中每一行表示一个XX样本,最后一列为每个样本的标签,现需要设计模型与系统,请按照以下要求完成算法测试。根据要求完成以下任务,将完成的…...
Redis核心数据类型在实际项目中的典型应用场景解析
精心整理了最新的面试资料和简历模板,有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 Redis作为高性能的键值存储系统,在现代软件开发中扮演着重要角色。其多样化的数据结构为开发者提供了灵活的解决方案,本文将通过真实项…...
LLamaIndex中经常使用的三个模块
from aiostream import stream from fastapi import Request from fastapi.responses import StreamingResponse from llama_index.core.chat_engine.types import StreamingAgentChatResponse这四个模块每一个都很实用,在实际开发中经常用到,下面我就详…...
Idea集成AI:CodeGeeX开发
当入职新公司,或者调到新项目组进行开发时,需要快速熟悉项目代码 而新的项目代码,可能有很多模块,很多的接口,很复杂的业务逻辑,更加有与之前自己的代码风格不一致的现有复杂代码 更别提很多人写代码不喜…...
软考 中级软件设计师 考点知识点笔记总结 day12 计算机网络基础知识
文章目录 计算机网络基础5.1、计算机网络基础知识5.1.1 计算机网络分类5.1.2 七层网络体系结构5.1.3 网络标准5.1.4 TCP/IP协议族5.1.5 IP地址和IPv6简介5.1.6 Internet服务 计算机网络基础 要求掌握以下内容 5.1、计算机网络基础知识 网络体系结构 传输介质 传输技术 传输…...
【扩散模型(十三)】Break-A-Scene 可控生成,原理与代码详解(中)Cross Attn Loss 代码篇
系列文章目录 【扩散模型(一)】中介绍了 Stable Diffusion 可以被理解为重建分支(reconstruction branch)和条件分支(condition branch)【扩散模型(二)】IP-Adapter 从条件分支的视…...
C语言数字图像处理---2.31统计滤波器
本文介绍空域滤波器中的一种:统计滤波器 [定义与算法] 统计滤波(Statistic Filter)定义:基于图像处理中的邻域统计方法,对邻域内的像素信息进行统计,如基于均值和方差的信息,用于平滑或去噪图像,同时保留边缘信息。 算法步骤如下: 统计滤波器的优点和缺点主要包…...
流程设计实战:流程架构设计六步法
目录 简介 1、梳理业务模式及场景 2、甄别核心业务能力 3、搭建差异化的业务流程框架 4、定义L4流程能力 5、L4流程串联 6、展开L5业务流程 作者简介 简介 以往在设计流程的时候,我多数都是采用的自下而上的方式,从具体场景、具体问题出发去做流…...
SDK游戏盾如何接入?复杂吗?
接入SDK游戏盾(通常指游戏安全防护类SDK,如防DDoS攻击、防作弊、防外挂等功能)的流程和复杂度取决于具体的服务商(如腾讯云、上海云盾等)以及游戏类型和技术架构。以下是一般性的接入步骤、复杂度评估及注意事项&#…...
STM32F103C8T6 单片机入门基础知识及点亮第一个 LED 灯
目录 一、引言 二、STM32F103C8T6 基本特性 1. 内核与性能 2. 存储器 3. 时钟系统 4. GPIO(通用输入输出) 5. 外设 三、开发环境搭建 1. 硬件准备 2. 软件安装 四、点亮第一个 LED 灯 1. 硬件连接 2. 软件实现 (1)创…...
JavaScript Worker池实现教程
JavaScript Worker池实现教程 Worker池是一种管理和复用Web Workers的有效方法,可以在不频繁创建和销毁Worker的情况下,充分利用多线程能力提升应用性能。下面我将详细介绍如何在JavaScript中实现一个功能完善的Worker池。 为什么需要Worker池…...
【统信UOS操作系统】python3.11安装numpy库及导入问题解决
一、安装Python3.11.4 首先来安装Python3.11.4。所用操作系统:统信UOS 前提是准备好Python3.11.4的安装包(可从官网下载(链接)),并解压到本地: 右键,选择“在终端中打开”ÿ…...
Navicat导入JSON数据到MySQL表
🤟致敬读者 🟩感谢阅读🟦笑口常开🟪生日快乐⬛早点睡觉 📘博主相关 🟧博主信息🟨博客首页🟫专栏推荐🟥活动信息 文章目录 Navicat导入JSON数据到MySQL表1. 导入入口2.…...
体育比分小程序怎么提示日活
要提高体育比分小程序的日活跃用户(DAU),您可以考虑以下几个方面的策略: 一、核心功能优化 1.实时推送:确保比分更新真正实时,延迟不超过2秒,推荐接入熊猫比分API体育数据,比分实时更新 2.个性化订阅&am…...
【星海随笔】Python-JSON数据的处理
JSON 是一种轻量级的数据交换格式,主要用于在客户端和服务器之间传输数据。 JSON 在 python 里是一个标准库 https://www.jyshare.com/compile/9/ import json data {name: Alice, age: 30, city: New York} json_string json.dumps(data) print(json_string)js…...
Tomcat与Servlet
目录 1 Tomcat 1.1 目录结构 1.2 启动服务器 1.3 部署 2 Servlet 2.1 创建项目 (1)创建Maven项目 (2)目录结构 (3)引入依赖 (4)创建必要的目录结构 (5…...
MySQL MVCC工作流程详解
MySQL MVCC工作流程详解 1. 基础概念 MVCC(多版本并发控制)是通过在每行记录后面保存多个版本来实现并发控制的技术,主要用于提供并发事务访问数据库时的读一致性。 2. 核心要素 2.1 事务ID(DB_TRX_ID) 每个事务都…...
unityTEngine 框架学习记录1
目前项目再用QF框架其中的UI部分,突然有天想学习一下其他好用的框架UI,根据我多年网友胖菊大佬的推荐TE映入眼帘,网上找了一下发现学习教程没有几个,不太适合啥都不会的小白,然后我就加入了ET官方群,里面人长得又帅又有…...
算法的时间复杂度
整理了下算法的时间复杂度,跟大家一起分享下。 时间复杂度O是表示算法运行时间与输入数据规模(通常用 n 表示)之间的关系。算法执行时间随输入数据规模增长的变化趋势。 1、O(1) — 常数时间 无论输入数据多大,执行时间固定不变…...
深度学习 从入门到精通 day_01
Pytorch安装 torch安装 python版本3.9.0 在官方文档里面找到适合你设备的PyTorch版本及对应的安装指令执行即可:https://pytorch.org/get-started/previous-versions/ 针对我的网络及设备情况,我复制了如下指令完成了Torch的安装: …...
AutoToM:让AI像人类一样“读心”的突破性方法
引言:AI如何理解人类的“内心世界”? 如何让AI像人类一样理解他人的意图、情感和动机?这一问题的核心是心智理论(Theory of Mind, ToM),即通过观察行为推断心理状态的能力。近日,约翰霍普金斯大…...
Java实现Redis
String类型 代码 package com.whop.changyuan2.redisTest;import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.redis.cor…...
DAY09:【pytorch】nn网络层
1、卷积层 1.1 Convolution 1.1.1 卷积操作 卷积运算:卷积核在输入信号(图像)上滑动,相应位置上进行乘加卷积核:又称为滤波器、过滤器,可认为是某种模式、某种特征 1.1.2 卷积维度 一般情况下…...
河南普瑞维升企业案例:日事清SOP流程与目标模块实现客户自主简报功能落地
公司简介: 河南普瑞维升企业管理咨询有限公司成立于2017年,目前公司主营业务是为加油站提供全方面咨询管理服务,目前公司成功运营打造河南成品油,运营站点15座,会员数量已达几十万,在加油站周边辐射区域内…...
LeetCode面试热题150中19-22题学习笔记(用Java语言描述)
Day 04 19、最后一个单词的长度 需求:给你一个字符串 s,由若干单词组成,单词前后用一些空格字符隔开。返回字符串中 最后一个 单词的长度。 单词 是指仅由字母组成、不包含任何空格字符的最大子字符串。 代码表示 public class Q19_1 {p…...
车载刷写架构 --- 刷写流程中重复擦除同一地址的问题分析
我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 周末洗了一个澡,换了一身衣服,出了门却不知道去哪儿,不知道去找谁,漫无目的走着,大概这就是成年人最深的孤独吧! 旧人不知我近况,新人不知我过…...
一个测试GPU可用的测试实例
一个测试GPU可用的测试实例: import torch import torch.nn as nn import torch.optim as optim import time import gc import numpy as np from torch.cuda.amp import autocast, GradScalerclass LargeNN(nn.Module):def __init__(self, use_attentionTrue):sup…...
chili3d调试笔记2+添加web ui按钮
onclick 查找 打个断点看看 挺可疑的,打个断点看看 挺可疑的,打个断点看看 打到事件监听上了 加ui了 加入成功 新建弹窗-------------------------------------- 可以模仿这个文件,写弹窗 然后在这里注册一下,外部就能调用了 对了…...
Go-zero:JWT鉴权方式
1.简述 用于记录在go-zero的后端项目中如何添加jwt中间件鉴权 2.流程 配置api.yaml Auth:AccessSecret: "secret_key"AccessExpire: 604800config中添加Auth结构体 Auth struct {AccessSecret stringAccessExpire int64 }types定义jwt token的自定义数据结构&#…...
MySQL的MVCC机制详解
1. 什么是MVCC? MVCC(Multi-Version Concurrency Control,多版本并发控制)是数据库系统中用于实现并发控制的一种技术。它通过保存数据在某个时间点的快照来实现,使得在同一个数据行上可以同时存在多个版本࿰…...
Postman做自动化测试
Postman也可以实现接口自动化 1.在Scripts写断言,图中红框处。不会写可以偷懒使用蓝框处会自动填写 2.单个运行调试,结果显示在TestResults 3.多个接口都写好断言并调通后,在包揽这些接口的文件夹下运行,图示以两个接口为例&…...
Meltdown原理介绍:用户空间读取内核内存
摘要 计算机系统的安全性从根本上依赖内存隔离,如,内核地址范围被标记为不可访问并受到保护,以防用户非法访问。本文介绍了Meltdown。 利用现代处理器上乱序执行,来读取内核任意的内存位置,包括个人数据和密码。乱序执行是必不可少的用来提升性能的手段,并在现代处理器中…...
数据结构和算法(七)--树
一、树 树是我们计算机中非常重要的一种数据结构,同时使用树这种数据结构,可以描述现实生活中的很多事物,例如家谱、单位的组织架构、等等。 树是由n(n>1)个有限结点组成一个具有层次关系的集合。把它叫做"树"是因为它看起来像一…...
UDP猜数字游戏与TCP文件传输案例解析
目录 案例一:UDP协议实现的猜数字游戏 游戏概述 服务器端代码 客户端代码 (udp_client.py) 游戏特点 案例二:TCP协议实现的文件传输工具 工具概述 服务器端代码 客户端代码 工具特点 总结对比 案例一:UDP协议实现的猜数字游戏 游…...
WPF View 与ViewModel注入对象
View 和ViewModel中使用同一个类型的类,注入的对象在主机中通过在服务中添加 AddTransient 获取的不是同一个对象,在 View 绑定了在ViewModel 中是取不到的,应该在View 中注入ViewModel 对象,使用View中的ViewModel对象里面的参数…...
如何下载免费地图数据?
按照以下步骤下载免费地图数据。 1、安装GIS地图下载器 从GeoSaaS(.COM)官网下载“GIS地图下载器”软件:,安装完成后桌面上出现”GIS地图下载器“图标。 双击桌面图标打开”GIS地图下载器“ 2、下载地图数据 点击主界面底部的“…...
B端可视化方案,如何助力企业精准决策,抢占市场先机
在当今竞争激烈的商业环境中,企业需要快速、准确地做出决策以抢占市场先机。B端可视化方案通过将复杂的企业数据转化为直观的图表和仪表盘,帮助企业管理层和业务人员快速理解数据背后的业务逻辑,从而做出精准决策。本文将深入探讨B端可视化方…...
IAR打包生成的hex和.a文件的区别
IAR打包生成的hex和.a文件的区别 在使用IAR Embedded Workbench进行嵌入式开发时,项目生成的文件中常见的两种文件类型是HEX文件和.a文件。它们在项目开发和部署过程中扮演着不同的角色。 HEX文件 定义与用途 HEX文件是一种十六进制表示的二进制文件格式…...
黑马点评:Redis消息队列【学习笔记】
目录 当前业务存在的问题 认识消息队列 List PubSub (publish subscribe) Stream 单消费模式 消费者组模式 对比 异步秒杀优化 当前业务存在的问题 JVM内存限制:当前使用的是JDK提供的阻塞队列,使用的是JVM的内存,如果不加以限制&…...
thinkphp:部署完整项目到本地phpstudy
一、准备工作 首先准备一个thinkphp的项目文件;准备mysql数据库 二、小皮初步搭建 1、建立网站 在小皮界面,网站->创建网站->输入域名,选择PHP版本等 注:确保端口未被占用 2、将项目文件放入根目录 网站->管理->…...
关于链接库
在 C# 中,链接库主要分为两种类型:托管链接库和非托管链接库,以下为你详细介绍它们的特点和导入方式: 托管链接库 特点 托管链接库通常是用 .NET 兼容的语言(如 C#、VB.NET 等)编写的,运行在…...
小程序返回按钮,兼容所有机型的高度办法
现象 在使用返回按钮的时候在不同机型上返回按钮小图标位置总是不一样,一会高一会低。 原因 因为手机的状态栏一般是不一样的,导致设置固定高度的时候就随时在改变。 解决办法 直接获取胶囊按钮的top值和height值将返回按钮的top值设置为一样的&…...
Docker镜像迁移指南:从Windows构建到Ubuntu运行
Docker镜像迁移指南:从Windows构建到Ubuntu运行 本文档详细介绍如何在Windows系统中构建SVM分类服务的Docker镜像,并将其迁移到Ubuntu系统中运行。 项目概述 本项目是一个使用FastAPI构建的SVM图像分类服务,可以将上传的图像分类为五种不同…...