计算机系统-基于RISC-V+Linux平台(精装版)课件 第五章 程序的链接_第1页
计算机系统-基于RISC-V+Linux平台(精装版)课件 第五章 程序的链接_第2页
计算机系统-基于RISC-V+Linux平台(精装版)课件 第五章 程序的链接_第3页
计算机系统-基于RISC-V+Linux平台(精装版)课件 第五章 程序的链接_第4页
计算机系统-基于RISC-V+Linux平台(精装版)课件 第五章 程序的链接_第5页
已阅读5页,还剩106页未读, 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第5章程序的链接

第1讲ELF目标文件格式

第2讲符号与符号解析

第3讲符号的重定位

第4讲共享库与动态链接可执行文件的链接生成主要教学目标使学生了解链接器是如何工作的,从而能够养成良好的程序设计习惯,并增加程序调试能力。通过了解可执行文件的存储器映像来进一步深入理解进程的虚拟地址空间的概念。包括以下内容链接和静态链接概念三种目标文件格式符号及符号表、符号解析使用静态库链接重定位信息及重定位过程可执行文件的存储器映像可执行文件的加载共享(动态)库链接程序的链接分以下三个部分介绍第一讲:ELF目标文件格式程序的链接概述、链接的意义与过程ELF目标文件、重定位目标文件格式、可执行目标文件格式第二讲:符号与符号解析符号和符号表、符号解析与静态库的链接第三讲:符号的重定位重定位信息、重定位过程可执行文件的加载第四讲:共享库和动态链接共享库、动态链接的特性、程序加载时的动态链接、程序运行时的动态链接、动态链接举例一个典型程序的转换处理过程#include<stdio.h>intmain(){printf("hello,world\n");}经典的“hello.c”C-源程序#include<sp><stdio.3510511099108117100101326011511610010511146h>\n\nint<sp>main()\n{1046210101051101163210997105110404110123\n<sp><sp><sp><sp>printf("hel10323232321121141051101161024034104101108lo,<sp>world\n");\n}10811144321191111141081009211034415910125hello.c的ASCII文本表示功能:输出“hello,world”预处理(cpp)编译(cc1)汇编(as)链接(ld)printf.o计算机不能直接执行hello.c!hello.c源程序(文本)hello.i源程序(文本)hello.s汇编语言程序(文本)hello.o可重定位目标程序(二进制)hello可执行目标程序(二进制)RISC-V汇编器汇编器:汇编表示→机器代码;反汇编:机器代码→汇编表示汇编器对.s文件处理,生成.o文件,采用ELF文件格式,其中包含许多不同的节,如.text节、.data节、.rodata节等汇编指示符:.s文件中用于指示汇编器如何处理的附加信息(带.),如:.text:指示代码节(.text)从此处开始.bss:指示未初始化数据节(.bss)从此处开始。.align2:指示代码从此处开始按22=4字节对齐。.balign4:指示数据从此处开始按4字节对齐。.globlmain:声明“main”是一个全局符号。.string"Hello,%s!\n":存储以null结尾的字符串"Hello,%s!\n"。.byte3,19,…,20:连续存储若干个8位数据3、19、…、20。.halfw1,w2,…,wn:连续存储n个16位数据w1、w2、…、wn。.wordw1,w2,…,wn:连续存储n个32位数据w1、w2、…、wn。.dwordw1,w2,…,wn:连续存储n个64位数据w1、w2、…、wn。.floatf1,f2,…,fn:连续存储n个32位单精度浮点数f1、f2、…、fn。.doubled1,d2,…,dn:连续存储n个64位双精度浮点数d1、d2、…、dn。.optionrvc:允许生成16位压缩指令。.optionrelax:允许采用链接器松弛技术。.optionpic:生成位置无关代码(positionindependentcode,PIC)。程序的转换、链接和加载不同的高级语言有不同的编译器,用于生成基于某种ISA架构的用汇编语言描述的机器级代码。每台机器的汇编程序功能一样,与高级语言无关目标文件中包含代码(指令)、数据及说明信息将多个过程(包括库例程)的目标文件链接成一个可执行文件。加载可执行文件到存储器,并启动开始执行。如何链接?如何分配代码和数据的地址?可执行文件要说明代码和数据区多大,并给出每条指令的机器码及其地址、各数据及其地址这里指虚拟地址!SKIP可能包含伪指令原始的链接概念早在高级编程语言出现之前就已存在最早程序员用机器语言编写程序,并记录在纸带或卡片上链接器的由来穿孔表示0,未穿孔为10:010101101:001001012:……3:……4:……5:011001116:……假设:0010-jmp若在第5条指令前加入指令,则程序员需重新计算jmp指令的目标地址(重定位),然后重新打孔。太原始了,无法忍受,咋办?用符号表示而不用0/1表示!用符号表示跳转位置和变量位置,是否简化了问题?汇编语言出现用助记符表示操作码用符号表示位置用助记符表示寄存器…..更高级编程语言出现程序越来越复杂,需多人开发不同的程序模块子程序(函数)起始地址和变量起始地址是符号定义(definition)调用子程序(函数或过程)和使用变量即是符号的引用(reference)一个模块定义的符号可以被另一个模块引用最终须链接(即合并),合并时须在符号引用处填入定义处的地址如上例,先确定L0的地址,再在jmp指令中填入L0的地址链接器的由来0:010101101:001001012:……3:……4:……5:011001116:……addBjmpL0

……

…………L0:subC

B:……

C:……使用链接的好处链接带来的好处1:模块化(1)一个程序可以分成很多源程序文件(2)可构建公共函数库,如数学库,标准I/O库等链接带来的好处2:效率高(1)时间上,可分开编译只需重新编译被修改的源程序文件,然后重新链接(2)空间上,无需包含共享库所有代码

源文件中无需包含共享库函数的源码,只要直接调用即可可执行文件和运行时的内存中只需包含所调用函数的代码而不需要包含整个共享库一个C语言程序举例intbuf[2]={1,2};voidswap();intmain(){swap();return0;}main.cswap.cexternintbuf[];

int*bufp0=&buf[0];staticint*bufp1;voidswap(){inttemp;bufp1=&buf[1];temp=*bufp0;*bufp0=*bufp1;*bufp1=temp;}你能说出哪些是符号定义?哪些是符号的引用?局部变量temp分配在栈中,不会在过程外被引用,因此不是符号定义可执行文件的生成使用GCC编译器编译并链接生成可执行程序P:$gcc-O2-g-opmain.cswap.c$./p链接(ld)程序转换(cpp,cc1,as)main.cmain.o程序转换(cpp,cc1,as)swap.cswap.op源程序文件分别转换(预处理、编译、汇编)为可重定位目标文件完全可执行的目标文件GCC编译器的静态链接过程-O2:2级优化-g:生成调试信息-o:目标文件名链接过程的本质main()main.oint*bufp0=&buf[0]swap()swap.o系统代码int

buf[2]={1,2}系统数据可重定位目标文件可执行目标文件.text.data.text.data.text.dataintbuf[2]={1,2}Headersmain()swap()0int

*bufp0=&buf[0]更多系统代码系统数据.text.symtab.debug.dataint*bufp1.bss系统代码staticint*bufp1.bss链接本质:合并相同的“节”目标文件000000000000000<add>:

0:00b5053baddwa0,a0,a14:00000797auipca5,0x08:0007a783lwa5,0(a5)#4<add+0x4>c:00f5053baddwa0,a0,a510:00000797auipca5,0x014:0007a783lwa5,0(a5)#10<add+0x10>18:00f5053baddwa0,a0,a51c:0055051baddiwa0,a0,520:00008067ret

00000000000104a4<add>:104a4:00b5053baddwa0,a0,a1104a8:00002797auipca5,0x2104ac:b587a783lwa5,-1192(a5)#12000<a>104b0:00f5053baddwa0,a0,a5104b4:8341a783lwa5,-1996(gp)#12034<b>104b8:00f5053baddwa0,a0,a5104bc:0055051baddiwa0,a0,5104c0:00008067ret红字处计算变量a的地址(0x124a8+0xffb58=0x12000)并取出a送寄存器a5objdump-dtest

/*main.c*/intadd(int,int);intmain(){returnadd(20,13);}/*test.c*/inta=4,b=0;constintc=5;intadd(inti,intj){intx=i+j+a+b+c;returnx;}objdump-dtest.o

蓝字处计算变量b的地址(为0x12034)并取出b送寄存器a5。这里a和b的计算方式不同,why?可执行文件的生成使用GCC编译器编译并链接生成可执行程序P:$gcc-otestmain.ctest.c$./test链接(ld)程序转换(cpp,cc1,as)main.cmain.o程序转换(cpp,cc1,as)test.ctest.otest源程序文件分别转换(预处理、编译、汇编)为可重定位目标文件完全可执行的目标文件GCC编译器的静态链接过程RV64+Linux中可执行文件的存储器映像链接操作的步骤1)确定标号引用关系(符号解析)2)合并相关.o文件3)确定每个标号的地址4)在指令中填入新地址代码数据P0:addBjmpL0

……

call

P1……L0:subC……P1:addA

……

…………

subB……B:

10C:20A:

30P1:addA

……

…………

subB……A:

30P1.oP0:addBjmpL0

……

call

P1……L0:subC……B:

10C:20P0.o重定位链接操作的步骤Step1.符号解析(Symbolresolution)程序中有定义和引用的符号(包括变量和函数等)voidswap(){…}/*定义符号swap*/swap();/*引用符号swap*/int*xp=&x;/*定义符号xp,引用符号x*/编译器将定义的符号存放在一个符号表(symboltable)中.符号表是一个结构数组每个表项包含符号名、长度和位置(值)等信息链接器将每个符号的引用都与一个确定的符号定义建立关联Step2.重定位将多个代码段与数据段分别合并为一个单独的代码段和数据段计算每个定义的符号在虚拟地址空间中的绝对地址将可执行文件中符号引用处的地址修改为重定位后的地址信息addBjmpL0

……

…………L0:subC……三类目标文件

可重定位目标文件(.o)其代码和数据可和其他可重定位文件合并为可执行文件每个.o文件由对应的.c文件生成每个.o文件代码和数据地址都从0开始可执行目标文件(默认为a.out)包含的代码和数据可以被直接复制到内存并被执行代码和数据地址为虚拟地址空间中的地址共享的目标文件(.so)特殊的可重定位目标文件,能在装入或运行时被装入到内存并自动被链接,称为共享库文件Windows中称其为DynamicLinkLibraries(DLLs)

目标文件的格式目标代码(ObjectCode)指编译器和汇编器处理源代码后所生成的机器语言目标代码目标文件(ObjectFile)指包含目标代码的文件最早的目标文件格式是自有格式,非标准的标准的几种目标文件格式DOS操作系统(最简单):COM格式,文件中仅包含代码和数据,且被加载到固定位置SystemVUNIX早期版本:COFF格式,文件中不仅包含代码和数据,还包含重定位信息、调试信息、符号表等其他信息,由一组严格定义的数据结构序列组成Windows:PE格式(COFF的变种),称为可移植可执行(PortableExecutable,简称PE)Linux等类UNIX:ELF格式(COFF的变种),称为可执行可链接(ExecutableandLinkableFormat,简称ELF)ExecutableandLinkableFormat(ELF)两种视图链接视图(被链接):Relocatableobjectfiles执行视图(被执行):Executableobjectfiles节(section)是ELF文件中具有相同特征的最小可处理单位

.text节:代码.data节:数据.rodata:只读数据.bss:未初始化数据由不同的段(segment)组成,描述节如何映射到存储段中,可多个节映射到同一段,如:可合并.data节和.bss节,并映射到一个可读可写数据段中

链接视图执行视图链接视图—可重定位目标文件可被链接(合并)生成可执行文件或共享目标文件若干个可重定位目标文件可组成静态链接库文件包含代码、数据(已初始化.data和未初始化.bss)包含重定位信息(指出哪些符号引用处需要重定位)文件扩展名为.o(相当于Windows中的.obj文件)intx=100;inty=0;voidprn(intn){

printf(“%d\n”,n);}voidmain(){

staticinta=1;staticintb;inti=200,j;prn(x+a+i);}ELF的链接视图.text节.data节.bss节为了进行链接,还需要其他许多信息,如符号表、重定位信息等许多其他的节(Section)可重定位目标文件格式ELF头定义了ELF魔数、版本、小端/大端、操作系统平台、目标文件的类型、机器结构类型、节头表的起始位置和长度等.text节编译汇编后的代码部分.rodata节只读数据,如浮点常数、printf格式串、switch跳转表等.data节已初始化且初值不为0的全局/静态变量.bss节未初始化或初始化为0的全局/静态变量,仅是占位符,不占据任何实际磁盘空间。区分初始化和未初始化是为了空间效率ELF头.text节.rodata节.bss节.symtab节.rela.txt节.rela.data节.debug节Sectionheadertable(节头表)0.data节.strtab节.line节SKIP可重定位目标文件格式symtab.符号表(symboltable),定义的函数名和全局/静态变量名都属于符号.rela.text

节.text节相关的可重定位信息.rela.data节.data节相关的可重定位信息.debug节调试用符号表,使用带-g选项才生成.strtab

节字符串表,以null结尾的字符串序列,存放符号表表项中关联的符号名.line

节C源程序中的行号和.text节中机器指令之间的映射信息,使用带-g选项才生成ELF头.text节.rodata节.bss节.symtab节.rela.txt节.rela.data节.debug节Sectionheadertable(节头表)0.data节.strtab节.line节SKIP举例:hello程序.text #汇编指示:开始进入text节.align2 #汇编指示:代码按22=4字节对齐

.globlmain #汇编指示:声明全局符号mainmain: #标号main表明main符号的定义从此处开始

addisp,sp,-16 #分配main的栈帧,大小为16字节

swra,12(sp) #将ra中的返回地址压栈

luia0,%hi(string1)

#R[a0]的高20位←string1的地址高20位

addia0,a0,%lo(string1)

#R[a0]←R[a0]+string1的地址低12位luia1,%hi(string2)

#R[a1]的高20位←string2的地址高20位

addia1,a1,%lo(string2)

#R[a1]←R[a1]+string2的地址低12位

callprintf

#调用printf函数

lwra,12(sp) #恢复返回地址到ra中

addisp,sp,16 #释放main的栈帧空间

lia0,0 #R[a0]←返回值0ret #从mian函数返回到调用函数.section.rodata #汇编指示:开始进入只读数据区

.balign4 #汇编指示:数据节按4字节对齐string1: #标号string1表明string1符号的定义从此处开始

.string“Hello,%s!\n” #汇编指示:数据为以null结尾的字符串string2: #标号string2表明string2符号的定义从此处开始

.string“world” #汇编指示:数据为以null结尾的字符#include<stdio.h>intmain(){printf("Hello,%s\n",

"world");…汇编语言程序文件hello.s

switch-case语句举例intsw_test(inta,intb,intc){intresult;switch(a){case15:c=b&0x0f;case10:result=c+50;break;case12:case17:result=b+50;break;case14:result=bbreak;default:result=a;}returnresult;}跳转表在只读代码节按4字节边界对齐R[a5]=a-10=iif(a-10)>7转L6取.L4+4*i

处内容。加.L4转.L7/.L6…/.L3处执行1011121314151617a=a在10和17之间switch_test:addiwa5,a0,-10sext.wa3,a5lia4,7bgtua3,a4,.L6sllia4,a5,32srlia5,a4,30llaa4,.L4adda5,a5,a4lw a5,0(a5)add a5,a5,a4jr a5

.section.rodata.align 2.L4:.word.L7-.L4.word.L6-.L4.word.L3-.L4.word.L6-.L4.word.L8-.L4.word.L5-.L4.word.L6-.L4.word.L3-.L4.text.L5:andi a2,a1,15.L7:addiwa0,a2,50ret.L3:addiwa0,a1,50ret.L8:mv a0,a1.L6:retBACKELF头(ELFHeader)ELF头位于ELF文件开始,包含文件结构说明信息。分32位系统对应结构和64位系统对应结构(32位版本、64位版本)以下是32位系统对应的数据结构#defineEI_NIDENT16typedefstruct{unsignedchar e_ident[EI_NIDENT];Elf32_Half e_type;Elf32_Half e_machine;Elf32_Word e_version;Elf32_Addr e_entry;Elf32_Off e_phoff;Elf32_Off e_shoff;Elf32_Word e_flags;Elf32_Half e_ehsize;Elf32_Half e_phentsize;Elf32_Half e_phnum;Elf32_Half e_shentsize;Elf32_Half e_shnum;Elf32_Half e_shstrndx;}Elf32_Ehdr;定义了ELF魔数、版本、小端/大端、操作系统平台、目标文件的类型、机器结构类型、程序执行的入口地址、程序头表(段头表)的起始位置和长度、节头表的起始位置和长度等魔数:文件开头几个字节通常用来确定文件的类型或格式a.out的魔数:01H07HPE格式魔数:4DH5AH加载或读取文件时,可用魔数确认文件类型是否正确ELF头信息举例$readelf-hmain.o

ELFHeader:Magic:7f454c46010101000000000000000000Class:ELF32Data:2'scomplement,littleendianVersion:1(current)OS/ABI:UNIX-SystemVABIVersion:0Type:REL(Relocatablefile)Machine:Intel80386Version:0x1Entrypointaddress:0x0Startofprogramheaders:0(bytesintofile)Startofsectionheaders:516(bytesintofile)Flags:0x0Sizeofthisheader:52(bytes)Sizeofprogramheaders:0(bytes)Numberofprogramheaders:0Sizeofsectionheaders:40(bytes)Numberofsectionheaders:15Sectionheaderstringtableindex:12ELF头.text节.rodata节.bss节.symtab节.rela.txt节.rela.data节.debug节Sectionheader(节头表)0.data节.strtab节.line节可重定位目标文件的ELF头没有程序头表15x40B.strtab在节头表中的索引ELF文件的魔数ABI:应用程序二进制接口节头表(SectionHeaderTable)除ELF头之外,节头表是ELF可重定位目标文件中最重要的部分内容描述每个节的节名、在文件中的偏移、大小、访问属性、对齐方式等以下是32位系统对应的数据结构(每个表项占40B)typedefstruct{Elf32_Word sh_name;Elf32_Word sh_type;Elf32_Word sh_flags;Elf32_Addr sh_addr;Elf32_Off sh_offset;Elf32_Word sh_size;Elf32_Word sh_link;Elf32_Word sh_info;Elf32_Word sh_addralign;Elf32_Word sh_entsize;

}Elf32_Shdr;节名字符串在.strtab中的偏移节类型:无效/代码或数据/符号/字符串/…节标志:该节在虚拟空间中的访问属性虚拟地址:若可被加载,则对应虚拟地址在文件中的偏移地址,对.bss节而言则无意义节在文件中所占的长度sh_link和sh_info用于与链接相关的节(如.rel.text节、.rel.data节、.symtab节等)节的对齐要求节中每个表项的长度,0表示无固定长度表项节头表信息举例$readelf-Stest.o

Thereare11sectionheaders,startingatoffset0x120:

SectionHeaders:

[Nr]Name

Type

Addr

Off

Size

ESFlgLkInfAl

[0]

NULL

0000000000000000000000

0

0

0

[1].text

PROGBITS

0000000000003400005b00

AX

0

0

4

[2].rel.text

REL

0000000000049800002808

9

1

4

[3].data

PROGBITS

0000000000009000000c00

WA

0

0

4

[4].bss

NOBITS

0000000000009c00000c00

WA

0

0

4

[5].rodata

PROGBITS

0000000000009c00000400

A

0

0

1

[6].comment

PROGBITS

000000000000a000002e00

0

0

1

[7].note.GNU-stack

PROGBITS

000000000000ce00000000

0

0

1

[8].shstrtab

STRTAB

000000000000ce00005100

0

0

1

[9].symtab

SYMTAB

000000000002d800012010

1013

4

[10].strtab

STRTAB

000000000003f800009e00

0

0

1KeytoFlags:

W(write),A(alloc),X(execute),M(merge),S(strings)

I(info),L(linkorder),G(group),x(unknown)

O(extraOSprocessingrequired)o(OSspecific),p(processorspecific)ELF头.text节.rodata节.bss节.symtab节.rel.txt节.rela.data节.debug节Sectionheader(节头表)0.data节.strtab节.line节可重定位目标文件中,每个可装入节的起始地址总是0节头表信息举例$readelf-Stest.o

Thereare11sectionheaders,startingatoffset0x120:

SectionHeaders:

[Nr]Name

Off

Size

ESFlgLkInfAl

[0]

00000000000000

0

0

0

[1].text

00003400005b00

AX

0

0

4

[2].rel.text

00049800002808

9

1

4

[3].data

00009000000c00

WA

0

0

4

[4].bss

00009c00000c00

WA

0

0

4

[5].rodata

00009c00000400

A

0

0

1

[6].comment

0000a000002e00

0

0

1

[7].note.GNU-stack

0000ce00000000

0

0

1

[8].shstrtab

0000ce00005100

0

0

1

[9].symtab

0002d800012010

1013

4

[10].strtab

0003f800009e00

0

0

1KeytoFlags:

W(write),A(alloc),X(execute),M(merge),S(strings)

I(info),L(linkorder),G(group),x(unknown)

………..有4个节分配(A)空间.text:可执行.data和.bss:可读写.rodata:只读ELF头e_shoff=0x120.text.data.rodata.comment.shstrtab节头表.symtab.strtab.rel.text00000000003400009000009c0000a00000ce.bss0001200002d80003f800049800011f5b0c040c2e511b81209e2800008f000496可重定位目标文件test.o的结构shstrtab:节名表strtab:符号名表symtab:符号表执行视图—可执行目标文件包含代码、数据(已初始化.data和未初始化.bss)定义的所有变量和函数已有确定地址(虚拟地址空间中的地址)符号引用处已被重定位,以指向所引用的定义符号没有文件扩展名或默认为a.out(相当于Windows中的.exe文件)可被CPU直接执行,指令地址和指令给出的操作数地址都是虚拟地址为了能执行,还需将具相同访问属性的节合并成段(Segment),并说明每个段的属性,如:在可执行文件中的位移、大小、在虚拟空间中的位置、对齐方式、访问属性等intx=100;inty=0;voidprn(intn){

printf(“%d\n”,n);}voidmain(){

staticinta=1;staticintb;inti=200,j;prn(x+a+i);}ELF的执行视图.text节.data节.bss节程序头表用来说明段信息,也称段头表可执行目标文件格式ELF头.text节.rodata节.bss节.symtab节程序头表.init节.debug节Sectionheadertable(节头表).data节.strtab节.line节只读(代码)段读写(数据)段无需装入到存储空间的信息与可重定位文件稍有不同:ELF头中字段e_entry给出执行程序时第一条指令的地址,而在可重定位文件中,此字段为0多一个程序头表,也称段头表(segmentheadertable),是一个结构数组多一个.init节,用于定义_init函数,该函数用来进行可执行目标文件开始执行时的初始化工作少两个.rela节(无需重定位)ELF头信息举例可重定位目标文件的ELF头ELF头.text节.rodata节.bss节.symtab节程序头表.init节.debug节Sectionheadertable(节头表).data节.strtab节.line节11x40B0x0BELFHeader:Magic:7f454c46020101000000000000000000Class:ELF64Data:2'scomplement,littleendianVersion:1(current)OS/ABI:UNIX–SystemVABIVersion:0Type:REL(Relocatablefile)Machine:RISC-VVersion: 0x1Entrypointaddress:0x0Startofprogramheaders:0(bytesintofile)Startofsectionheaders:624(bytesintofile)Flags:0x4,double-floatABISizeofthisheader:64(bytes)Sizeofprogramheaders:0(bytes)Numberofprogramheaders:0Sizeofsectionheaders:64(bytes)Numberofsectionheaders:11Sectionheaderstringtableindex:10ELF头信息举例$readelf-hmain

ELFHeader:Magic:7f454c46010101000000000000000000Class:ELF32Data:2'scomplement,littleendianVersion:1(current)OS/ABI:UNIX-SystemVABIVersion:0Type:EXEC(Executablefile)Machine:Intel80386Version:0x1Entrypointaddress:x8048580Startofprogramheaders:52(bytesintofile)Startofsectionheaders:3232(bytesintofile)Flags:0x0Sizeofthisheader:52(bytes)Sizeofprogramheaders:32(bytes)Numberofprogramheaders:8Sizeofsectionheaders:40(bytes)Numberofsectionheaders:29Sectionheaderstringtableindex:26

可执行目标文件的ELF头ELF头.text节.rodata节.bss节.symtab节程序头表.init节.debug节Sectionheadertable(节头表).data节.strtab节.line节29x40B8x32Bi386SystemVABI规定的存储器映像0%esp(栈顶)brk0xC000000000x08048000内核虚存区共享库区域堆(heap)(由malloc动态生成)用户栈(Userstack)动态生成未使用0读写数据段(.data,.bss)只读代码段(.init,.text,.rodata)从可执行文件装入程序头表描述如何映射ELF头程序(段)头表.text节.data节.bss节.symtab节.debug节.rodata节.line节.init节.strtab节可执行文件与虚拟地址空间间的存储器映像(memorymapping)由ABI规范定义可执行文件中的程序头表typedefstruct{Elf32_Wordp_type;Elf32_Offp_offset;Elf32_Addrp_vaddr;Elf32_Addrp_paddr;Elf32_Wordp_filesz;Elf32_Wordp_memsz;Elf32_Wordp_flags;Elf32_Wordp_align;}Elf32_Phdr;程序头表描述可执行文件中的节与虚拟空间中的存储段之间的映射关系一个表项(32B)说明虚拟地址空间中一个连续的段或一个特殊的节

以下是某可执行目标文件程序头表信息有8个表项,其中两个为可装入段(即Type=LOAD)$readelf–lmain可执行文件中的程序头表第一可装入段:第0x0~0x50b字节(包括ELF头、程序头表、.init、.text和.rodata节),映射到虚拟地址0x10000开始长度为0x50c字节的区域,按0x1000=212=4KB对齐,具有只读/执行权限(Flg=RE),是只读代码段。第二可装入段:第0xe18开始长度为0x218字节的.data节,映射到虚拟地址0x11e18开始长度为0x220字节的存储区域,在0x220=544B存储区中,前0x218=536B用.data节内容初始化,后面544-536=8B对应.bss节,初始化为0,按0x1000=4KB对齐,具有可读可写权限(Flg=RW),是可读写数据段。SKIP可执行文件的存储器映像你会实现自己的readelf(-h/-S/-l)吗?objdump呢?程序的链接分以下三个部分介绍第一讲:ELF目标文件格式程序的链接概述、链接的意义与过程ELF目标文件、重定位目标文件格式、可执行目标文件格式第二讲:符号与符号解析符号和符号表、符号解析与静态库的链接第三讲:符号的重定位重定位信息、重定位过程可执行文件的加载第四讲:共享库和动态链接共享库、动态链接的特性、程序加载时的动态链接、程序运行时的动态链接、动态链接举例符号和符号解析

每个可重定位目标模块m都有一个符号表,它包含了在m中定义的符号。有三种链接器符号:Globalsymbols(本模块内定义的全局符号)由模块m定义并可被其他模块引用的全局符号。例如,非static函数和非static的全局变量(指不带static的全局变量)

如,main.c中的全局变量名bufExternalsymbols(外部定义的全局符号)由其他模块定义并可被模块m引用的全局符号

如,main.c中的函数名swapLocalsymbols(本模块的局部符号)由模块m定义和引用的本地符号。如在模块m中定义的带static的函数和静态变量。同一模块中不同函数内可定义同名局部(本地)符号如,swap.c中的static变量名bufp1注意:链接器局部(本地)符号不是指程序中的局部变量

局部变量(auto型变量)是分配在栈中的临时性变量符号和符号解析intbuf[2]={1,2};voidswap();intmain(){swap();return0;}main.cexternintbuf[];

int*bufp0=&buf[0];staticint*bufp1;voidswap(){inttemp;bufp1=&buf[1];temp=*bufp0;*bufp0=*bufp1;*bufp1=temp;}swap.c你能说出哪些是全局符号?哪些是外部符号?哪些是局部(本地)符号?同一模块内的同名本地符号intfunc1(){staticintx=0;returnx;}intfunc2(){

staticintx=12;returnx;}p1.cx在不同的函数内各有1次作为静态局部变量定义,编译器会为这两个变量在该模块对应可重定位目标文件的.data节或.bss节分配空间,并作为本地符号记录在该模块的符号表中以下程序会发生链接出错吗?目标文件中的符号表符号表(symtab)中每个条目的结构如下:typedefstruct{uint32_tst_name;/*符号对应字符串在strtab节中的偏移量*/Elf32_Addrst_value;/*在对应节的偏移量,可执行文件中是虚拟地址*/uint32_tst_size;;/*符号对应目标所占字节数*/unsignedcharst_info;/*符号类型(Type)和绑定属性(Bind)*/unsignedcharst_other;/*符号的可见性(Vis)

*/uint16_t st_shndx;/*符号所在节在节头表中的索引,有三种特殊伪节*/}Elf32_Sym;.symtab节记录符号表信息,是一个结构数组函数名在text节中变量名在data节或bss节中函数大小或变量长度#defineELF64_ST_BIND(info)((info)>>4)#defineELF64_ST_TYPE(info)((info)&0xf)符号类型:未指定-Notype、变量-Object、函数-Func、节-Section等绑定属性:本地-LOCAL、全局-GLOBAL、弱-WEAK等WEAK属性指用属性指示符__attribute__((weak))指定的符号目标文件中的符号表符号表(symtab)中每个条目的结构如下:typedefstruct{uint32_tst_name;/*符号对应字符串在strtab节中的偏移量*/Elf32_Addrst_value;/*在对应节的偏移量,可执行文件中是虚拟地址*/uint32_tst_size;;/*符号对应目标所占字节数*/unsignedcharst_info;/*符号类型(Type)和绑定属性(Bind)*/unsignedcharst_other;/*符号的可见性(Vis)*/uint16_t st_shndx;/*符号所在节在节头表中的索引,有三种特殊伪节*/}Elf32_Sym;.symtab节记录符号表信息,是一个结构数组函数大小或变量长度伪节在节头表中无相应表项,无法给出其索引值(Ndx)ABS:表示对应符号不会被重定位UNDEF:未定义符号,即在本模块引用的外部符号(属于未指定符号类型)COMMON:未初始化全局变量,称COMMON符号,此时,st_value给出对齐要求,st_size给出最小长度。符号和符号解析intbuf[2]={1,2};voidswap(void);intmain(){swap();return0;}main.cexternintbuf[];

int*bufp0=&buf[0];int*bufp1;voidswap(){inttemp;bufp1=&buf[1];temp=*bufp0;*bufp0=*bufp1;*bufp1=temp;}swap.c哪些是全局符号?哪些是外部符号?哪些是局部(本地)符号?哪些是局部变量?哪些是未定义符号(未指定类型)?哪些是未初始化符号(COMMON符号)?RV64中目标文件中的符号表main.o中的符号表中最后三个条目(共11个)Num: value Size Type Bind Vis Ndx Name9: 0 32 Func Global DEFAULT1 main10: 0 0 Notype Global DEFAULTUND swap11: 0 8 Object Global DEFAULT3 bufswap.o中的符号表中最后4个条目(共17个)Num: value Size Type Bind Vis NdxName14: 0 56 Func Global DEFAULT1 swap15: 8 8 Object Global DEFAULTCOMbufp116: 0 0 NotypeGlobalDEFAULTUNDbuf17: 0 8 Object GlobalDEFAULT5 bufp0main是第1节(.text)偏移为0的符号,是全局函数,占32B;swap是main.o中未定义全局(在其他模块定义)符号,类型和大小未知buf是main.o中第3节(.data节)偏移为0的符号,是全局变量,占8B;bufp1是未分配地址的未初始化全局变量(Ndx=COM),按8B对齐且至少占8Bbufp0位于节头表中第5个表项对应的.data.rel节中偏移量为0处,占8B符号解析(SymbolResolution)目的:将每个模块中引用的符号与某个目标模块中的定义符号建立关联。每个定义符号在代码段或数据段中都被分配了存储空间,将引用符号与定义符号建立关联后,就可在重定位时将引用符号的地址重定位为相关联的定义符号的地址。局部(本地)符号在本模块定义并引用,其解析较简单,只要与本模块内唯一的定义符号关联即可。全局符号(外部定义的、内部定义的)的解析涉及多个模块,故较复杂

“符号的定义”其实质是什么?指被分配了存储空间。为函数名时,指代码所在区;为变量名时,指所占的静态数据区。addBjmpL0

……L0:sub23……B:……确定L0的地址,再在jmp指令中填入L0的地址所有定义符号的值就是其目标所在的首地址符号解析也称符号绑定全局符号的符号解析全局符号的强/弱特性强符号:函数名、.data节中已初始化的全局变量名、.bss节中初始化为0的全局变量弱符号

:绑定属性为WEAK的符号COMMON符号:未初始化全局变量名

intvar=0;p1(){……}intvar;p2(){……}p1.cp2.c以下符号哪些是强符号?哪些是弱符号或COMMON符号?有时将这两种符号统称为弱符号全局符号的符号解析intbuf[2]={1,2};voidswap();intmain(){swap();return0;}main.cexternintbuf[];

int*bufp0=&buf[0];staticint*bufp1;voidswap(){inttemp;bufp1=&buf[1];temp=*bufp0;*bufp0=*bufp1;*bufp1=temp;}swap.c此处为引用本地局部符号局部变量以下符号哪些是强符号?哪些是弱符号或COMMON符号?外部符号没有强弱??COMMON符号只可能是变量名全局符号的符号解析全局符号的解析规则规则1:强符号不能多次定义,否则链接错误。规则2:若出现一次强符号定义和多次COMMON符号或弱符号定义,则按强符号定义为准。规则3:若同时出现COMMON符号定义和弱符号定义,则按COMMON符号定义为准。规则4:若一个COMMON符号出现多次定义,则以其中占空间最大的一个为准。符号表中仅记录COMMON符号的最小长度,因此在链接器确定多重COMMON符号的唯一定义时,以最小长度(st_size字段)中的最大值为准进行符号解析,保证满足所有同名COMMON符号的空间要求。规则5:若使用编译选项-fno-common,则将COMMON符号作为强符号处理。符号解析时只能有一个确定的定义(即每个符号仅占一处存储空间)多重定义符号的解析举例intx=10;intp1(void);intmain(){x=p1();returnx;}main.cintx=0;intp1(){returnx;}p1.cmain为强符号p1为强符号x有两次强定义,所以,链接器将输出一条出错信息

以下程序会发生链接出错吗?多重定义符号的解析举例p1.cy一次强定义,一次COM定义z两次COM定义p1一次强定义main一次强定义#include<stdio.h>inty=100;intz;voidp1(void);intmain(){z=1000;p1();printf(“y=%d,z=%d\n”,y,z);return0;}main.cinty;shortz;voidp1(){y=200;z=2000;}问题:打印结果是什么?y=200,z=2000以下程序会发生链接出错吗?据规则2,符号y以main.o中的y为准据规则4,符号z以main.o中占更大空间的z为准该例说明:在两个不同模块定义相同变量名,很可能发生意想不到的结果!多重定义符号的解析举例p1.c该例说明:两个重复定义的变量具有不同类型时,更容易出现难以理解的结果!

main.c问题:打印结果是什么?d=0,x=1072693248

以下程序会发生链接出错吗?1#include<stdio.h>2intd=100;3intx=200;4voidp1(void);5intmain()6{7p1();8printf(“d=%d,x=%d\n”,d,x);9return0;10}1doubled;23voidp1()4{5d=1.0;6}p1执行后d和x处内容是什么?FLD1FSTPl&d1.0:0011111111110…0B=3FF0000000000000H低高IA-32是小端方式多重定义符号的解析举例打印结果:d=0,x=1072693248Why?

1doubled;23voidp1()4{5d=1.0;6}…….1intd=100;2intx=200;3intmain()4{5p1();6printf(“d=%d,x=%d\n”,d,x);7return0;8}main.cp1.c3FF00000H=230-1-(220-1)=230-220=1024*1024*1023=1072693248可通过-fno-common告知编译器将COMMON符号作为强符号处理,再利用规则1,让链接器在遇到多重定义的全局符号时报告错误,以尽早暴露错误发生源多重定义全局符号的问题尽量避免使用全局变量一定需要用的话,就按以下规则使用尽量使用本地变量(加static)全局变量要赋初值(变成强符号)外部全局变量要使用extern多重定义全局变量会造成一些意想不到的错误,而且是默默发生的,编译系统不会警告,并会在程序执行很久后才能表现出来,且远离错误引发处。特别是在一个具有几百个模块的大型软件中,这类错误很难修正。大部分程序员并不了解链接器如何工作,因而养成良好的编程习惯是非常重要的。如何划分模块?许多函数无需自己写,可使用共享库函数如数学库,输入/输出库,存储管理库,字符串处理等避免以下两种极端做法将所有函数都放在一个源文件中修改一个函数需要对所有函数重新编译时间和空间两方面的效率都不高一个源文件中仅包含一个函数需要程序员显式地进行链接效率高,但模块太多,故太繁琐静态共享库静态库(.aarchivefiles)将所有相关的目标模块(.o)打包为一个单独的库文件(.a),称为静态库文件,也称存档文件(archive)增强了链接器功能,使其能通过查找一个或多个库文件中的符号来解析符号在构建可执行文件时只需指定库文件名,链接器会自动到库中寻找那些应用程序用到的目标模块,并且只把用到的模块从库中拷贝出来在gcc命令行中无需明显指定C标准库libc.a(默认库)静态库的创建转换(cpp,cc1,as)atoi.catoi.o转换(cpp,cc1,as)printf.cprintf.olibc.aArchiver(ar)...random.crandom.o$arrcslibc.a\atoi.oprintf.o…random.oC标准静态库Archiver(归档器)允许增量更新,只要重新编译需修改的源码并将其.o文件替换到静态库中。转换(cpp,cc1,as)在gcc命令行中无需明显指定C标准库libc.a(默认库)常用静态库libc.a(C标准库)1392个目标文件(大约8MB)包含I/O、存储分配、信号处理、字符串处理、时间和日期、随机

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论