本篇基于<<AMD64(x86_64)架构abi文档:中>>延伸章节。

10.3.2 静态线程局部变量
10.3.3 TLS链接器优化
10.4 内核支持
10.5 编码示例
10.5.1 间接分支
11 备用代码序列的安全性
11.1 没有PLT的代码序列
11.1.1 通过GOT槽间接呼叫
11.1.2 没有PLT的线程本地存储
12 英特尔MPX扩展
12.1 参数传递和返回值
12.1.1 归类
12.1.2 通过
12.1.3 返回值
12.1.4 变量参数列表
12.2 程序加载和动态链接
附录A
A.1 32位程序的执行
A.2 AMD64 Linux内核约定
A.2.1 调用约定
A.2.2 堆栈布局
A.2.3 其他评述
附录B
B.1 合并GOTPLT和GOT插槽
B.2 优化GOTPCRELX重新定位
目录预览

10.3.2 静态线程局部变量

对于静态线程局部变量x:

static __thread int x;

本地动态模型将x的地址加载到%rax中


Table 10.5: Local Dynamic Model Code Sequence With Lea

or

Table 10.6: Local Dynamic Model Code Sequence With Add


对于具有TLSDESC的代码序列,局部动态模型与一般动态模型相似。同样的编码要求也适用于法律指令。


Table 10.7: General Dynamic Model Code Sequence with TLSDESC


局部动态模型,加载值x到%edi


Table 10.8: Local Dynamic Model Code Sequence, II


本地Exec模型加载x到%rax的地址


Table 10.9: Local Exec Model Code Sequence With Lea


or


Table 10.10: Local Exec Model Code Sequence With Add


本地执行模型,II装载x到%edi的值


Table 10.11: Local Exec Model Code Sequence, II


本地执行模型,III装载x到%edi的值


Table 10.12: Local Exec Model Code Sequence, III


10.3.3 TLS链接器优化

General Dynamic To Initial Exec加载x到%rax的地址


Table 10.13: GD -> IE Code Transition



Table 10.14: GDesc -> IE Code Transition



Table 10.15: GD -> LE Code Transition



Table 10.16: GDesc -> LE Code Transition



Table 10.17: IE -> LE Code Transition With Lea


or


Table 10.18: IE -> LE Code Transition With Add


将x的值装入%edi。


Table 10.19: IE -> LE Code Transition, II


本地动态到本地Exec加载x的地址到%rax


Table 10.20: LD -> LE Code Transition With Lea


or


Table 10.21: LD -> LE Code Transition With Add


本地动态到本地执行,II加载x的值到%edi。


Table 10.22: LD -> LE Code Transition, II


10.4 内核支持

内核应该限制从系统调用返回的堆栈和地址在0x00000000到0xffffffff之间。

10.5 编码示例

尽管ILP32二进制文件以64位模式运行,但并不是所有64位指令都受支持。本节讨论与64位模式不同的基本操作示例代码序列。

10.5.1 间接分支

由于通过内存的间接分支在内存位置加载64位地址,因此在ILP32中不支持这种方式。应该改用通过寄存器的间接分支。内存中的32位地址被加载到寄存器的下32位,这将自动对寄存器的上32位进行零扩展。然后可以通过64位寄存器执行间接调用。


Table 10.23: Indirect Branch


11 备用代码序列的安全性

11.1 没有PLT的代码序列

PLT (Linkage Table)用于访问共享对象和support中定义的外部函数,具体操作请参见5.2节

懒惰的符号解析 函数地址只有在运行时第一次被调用时才会被解析。

规范函数地址 外部函数的PLT条目被用作它的地址,也就是函数指针。

PLT表项中的第一条指令是通过全局偏移表(GOT)的间接分支,详细信息参见5.2节,外部函数的表项,它的设置是这样的,当函数第一次被调用时,它将被更新到函数体的地址。由于GOT条目是可写的,所以任何地址都可能在运行时写入它,这是一个潜在的安全风险。

11.1.1 通过GOT槽间接呼叫

对于中小型机型,采用不同的代码序列来避免PLT:


Figure 11.1: Function Call without PLT (Small and Medium Models)


直接分支被通过GOT插槽的间接分支所取代,这类似于PLT插槽中的第一条指令。


Figure 11.2: Function Address without PLT (Small and Medium Models)


与使用PLT槽作为函数地址不同,函数地址是从GOT槽中检索的。

如果链接器确定该函数是在本地定义的,它将通过GOT插槽的间接分支转换为带有nop前缀的直接分支,并将通过GOT插槽的加载转换为立即或lea加载,详见B.2节。

动态连接器通过更新带有符号地址的GOT表项来解析所有符号后,就可以使GOT变为只读,覆盖GOT立即成为一个硬错误。由于PLT不再用于调用外部函数,延迟符号解析被禁用,函数只能在启动时符号解析期间插入。依赖于惰性符号解析的工具和特性将无法正常工作。然而,这样做也有一些好处:

没有额外的直接分支到PLT入口 由于间接分支的长度为6字节,而直接分支的长度为5字节,当使用通过GOT插槽的间接分支来调用本地函数时,每次调用的代码大小将增加一个字节。由于一个PLT槽有16个字节,当使用通过GOT槽的间接分支调用外部函数超过16次时,代码大小将会增加。

自定义调用约定 由于外部函数是通过GOT插槽直接调用的,所以在第一次调用时,不需要调用动态连接器来查找函数符号,传递参数的方式可以与本文中指定的不同。

11.1.2 没有PLT的线程本地存储

一般模型和本地动态模型的TLS代码序列可以通过更新来取代通过PLT条目直接调用__tls_get_addr,而通过GOT槽位间接调用__tls_get_addr,如图11.3所示。由于直接调用指令长度为4字节,间接调用指令长度为5字节,因此必须正确处理额外的一个字节。


Figure 11.3: __tls_get_addr Call


全局变量的通用动态模型

对于一般的动态模型,在调用指令前去掉一个0x66前缀,为间接调用腾出空间:

extern __thread int x;

下面的替代代码序列加载地址x到%rax没有PLT:


Table 11.1: General Dynamic Model Code Sequence (LP64)



Table 11.2: General Dynamic Model Code Sequence (ILP32)


静态局部变量

对于局部动态模型,使用间接调用代替直接调用:

static __thread int x;

下面的替代代码序列加载模块的TLS块的地址,其中包含变量x,到%rax没有PLT:


Table 11.3: Local Dynamic Model Code Sequence (LP64)

Table 11.4: Local Dynamic Model Code Sequence (ILP32)


TLS链接器优化

由于间接调用通用动态模型的代码序列与直接调用的代码序列长度相同,因此链接器只需要识别新的指令模式就可以将通用动态访问转换为初始exec或本地exec访问。

初始执行常规动态 将x地址加载到%rax中:


Table 11.5: GD -> IE Code Transition (LP64)



__________________________________________________________________ Table 11.6: GD -> IE Code Transition (ILP32)


本地执行常规动态 将x地址加载到%rax中:


Table 11.7: GD -> LE Code Transition (LP64)



Table 11.8: GD -> LE Code Transition (ILP32)


本地动态到本地执行 对于本地动态模型到本地执行模型的转换,linker在LP64的mov指令之前生成4个0x66前缀,而不是3个;在ILP32的mov指令之前生成5字节nop,而不是4字节nop。加载模块的TLS块的地址,其中包含变量x,到%rax没有PLT:


Table 11.9: LD -> LE Code Transition (LP64)



Table 11.10: LD -> LE Code Transition (ILP32)


12 英特尔MPX扩展

Intel MPX(内存保护扩展)提供4个128位宽边界寄存器(%bnd0 - %bnd3)。为了传递参数和返回函数,%bndN的下64位指定相应参数的下界,而上64位指定参数的上界。上界用补码的形式表示。

12.1 参数传递和返回值

增加了一个POINTER类用于传递和返回指针类型,并对INTEGER类进行了如下更新:

POINTER 该类由指针类型组成。

INTEGER 该类由适合于通用寄存器之一的整型(指针类型除外)组成。

12.1.1 归类

指针和整数分为:

指针在POINTER类中。

类型(有符号和无符号)_Bool、char、short、int、long和long long的参数在INTEGER类中。

ILP32不支持MPX,因为MPX需要64位指针。


Figure 12.1: Bound Register Usage


聚合(结构和数组)和联合类型的分类更新如下:

1. 当c++对象通过不可见引用传递时,形参列表中的对象将被具有pointer类的指针替换。

2. 如果其中一个类是POINTER,结果就是POINTER。

12.1.2 通过

对于参数传递,如果类是INTEGER或POINTER,则使用序列%rdi、%rsi、%rdx、%rcx、%r8和%r9的下一个可用寄存器。

边界传递

Intel MPX提供了ISA扩展,允许为指针参数传递边界,指定可以通过解引用指针合法访问的内存区域。这一段描述如何将边界传递给被调用方。

下面描述中使用的几个函数定义如下:

BOUND_MAP_STORE(bnd, addr, ptr) 该函数执行Intel MPX bndstx指令。PTR参数用于初始化BNDSTX指令的内存操作数的索引字段,addr编码在内存操作数的基址和/或位移字段中,BND编码在寄存器操作数中。

BOUND_MAP_LOAD(addr, ptr) 该函数执行Intel MPX bndldx指令。PTR参数用于初始化BNDLDX指令的内存操作数的索引字段,addr编码在内存操作数的基字段和/或位移字段中。

下面的算法用来决定如何为每个8字节传递边界:

1. 如果类是INTEGER, 8字节将在通用寄存器中传递,并且被调用的函数使用可变参数或标准参数,那么类将转换为POINTER。为包含在8字节中的指针创建了允许访问所有内存的人工边界。

2. 如果类是POINTER,并且这八个字节是在通用寄存器中传递的,那么这八个字节中包含的指针关联的边界将被传递到下一个可用寄存器%bnd0, %bnd1, %bnd2和%bnd3中。如果参数的边界没有可用的绑定寄存器,那么边界将通过执行BOUND_MAP_STORE(bnd, addr, ptr)函数以CPU定义的方式传递,其中bnd是参数的当前边界,addr是被调用方返回地址位置之外的堆栈位置地址(将由相应的调用指令放在堆栈上),ptr是指针参数的实际值。对于每个调用,最多可以有两个这样的指针参数,第一个有它的边界与(<返回地址堆栈位置> - 8)地址相关联,第二个-与(<返回地址堆栈位置> - 16)相关联。

3. 如果类是POINTER并且在栈上传递八字节,或者类是MEMORY并且参数包含指针成员,那么八字节中包含的每个指针关联的边界将通过执行BOUND_MAP_STORE(bnd, addr, ptr)函数以CPU定义的方式传递,其中bnd是指针参数的当前边界,addr是指针参数堆栈位置的地址,ptr是指针参数的实际值。如果8字节可能包含部分重叠指针的部分,那么与指针相关的边界将被忽略,并为此类指针传递允许访问所有内存的特殊边界。

被调用方使用相同的算法对传入参数进行分类。如果使用BOUND_MAP_STORE将参数传递给被调用方,则被调用方使用BOUND_MAP_LOAD(addr, ptr)获取传递的边界,其中addr是传递给调用方中相应的BOUND_MAP_STORE的相同地址,ptr是被调用方从通用寄存器或堆栈位置获取的指针参数的实际值。

当向函数传递带边界的参数时,必须提供函数原型。否则,运行时行为是未定义的。

12.1.3 返回值

返回值已更新:

如果类是INTEGER或POINTER,则使用序列%rax, %rdx的下一个可用寄存器。

边界返回

返回边界的算法如下:

1. 使用分类算法对返回类型进行分类。

2. 如果类型有MEMORY类,返回时%bnd0必须包含调用者在%rdi中传入的“隐藏”第一个参数的边界。

3. 如果类是POINTER,序列%bnd0, %bnd1, %bnd2, %bnd3的下一个可用寄存器用于返回包含在8字节中的指针的边界。

作为绑定传递约定的一个例子,考虑图12.2中所示的声明和函数调用。图12.3给出了相应的绑定寄存器分配,给出的堆栈帧偏移显示了调用函数前的帧。


Figure 12.2: Bounds Passing Example



Figure 12.3: Bounds Allocation Example


12.1.4 变量参数列表

传递变量参数的方式更新如下:

寄存器保存区

如果带变量参数列表的函数是为Intel MPX编译的,那么通过执行BOUND_MAP_STORE(bnd, addr, ptr)函数(12.1.2),将保存到寄存器保存区的参数寄存器传递的边界保存到prolog中的每个参数寄存器,其中bnd是指针参数的当前边界,addr是参数寄存器在寄存器保存区的位置的地址,ptr是参数寄存器的实际值。

va_arg宏

va_arg(l, type)的实现更新如下:

1. 从l->reg_save_area中获取类型,偏移量为l->gp_offset和/或l->fp_offset。如果参数在不同的寄存器类中传递,这可能需要将其复制到一个临时位置,或者需要对一般用途寄存器的比对大于8,对XMM寄存器的比对大于16。如果type指定了一个指针,那么被获取的参数的边界通过执行BOUND_MAP_LOAD(l->reg_save_area + l->gp_offset, ptr)(12.1.2)来加载,其中ptr是从l->reg_save_area中获取的实际值,偏移量为l->gp_offset。

12.2 程序加载和动态链接

为了在带有BND (0xf2)前缀的分支指令中保留用于符号查找的绑定寄存器,链接器应该生成BND过程链接表(见图12.4)和一个附加的过程链接表(见图12.5)。

在调用func()之前,调用的返回地址还没有放在堆栈上,因此偏移-16帐户将由调用指令进行返回地址的推送。


Figure 12.4: BND Procedure Linkage Table (small and medium models)



Figure 12.5: Additional Procedure Linkage Table (small and medium models)


为了支持具有BND (0xf2)前缀的间接分支(见图12.6),所有BND过程联动表项中的分支都必须具有BND (0xf2)前缀。


Figure 12.6: Indirect branch


当使用BND过程联动表时,外部函数的全局偏移表项的初始值为附加过程联动表对应表项的地址。

附录A

Linux约定

本章描述了一些只与GNU/Linux系统和Linux内核相关的细节。

A.1 32位程序的执行

AMD64处理器能够执行64位AMD64和32位ia32程序。符合Intel386 ABI的库将位于/lib、/usr/lib和/usr/bin等正常位置。AMD64之后的库将使用lib64子目录作为库,例如/lib64和/usr/lib64。符合Intel386 ABI和AMD64 ABI的程序将共享像/usr/bin这样的目录。特别是,没有/bin64目录。

A.2 AMD64 Linux内核约定

本节仅供参考。

A.2.1 调用约定

Linux AMD64内核内部使用与用户级应用程序相同的调用约定(详见3.2.3节)。喜欢调用系统调用的用户级应用程序应该使用C库中的函数。C库与Linux内核的接口与用户级应用的接口相同,不同之处在于:

1. 用户级应用程序使用整数寄存器来传递序列%rdi、%rsi、%rdx、%rcx、%r8和%r9。内核接口使用%rdi、%rsi、%rdx、%r10、%r8和%r9。

2. 系统调用是通过系统调用指令完成的。内核销毁寄存器%rcx和%r11。

3. 系统调用的编号必须在寄存器%rax中传递。

4. 系统调用限制为6个参数,没有参数直接传递到堆栈上。

5. 从系统调用返回,寄存器%rax包含系统调用的结果。-4095 ~ -1表示错误,取值为-errno。

6. 只有类INTEGER或类MEMORY的值被传递给内核。

A.2.2 堆栈布局

Linux内核可能会将输入参数区域的末尾对齐到8字节边界,而不是16字节边界。它不属于红色区域(参见3.2.2节),因此内核代码不允许使用这个区域。内核代码应该由GCC使用-mno-red-zone选项进行编译。

A.2.3 其他评述

Linux内核代码不允许更改x87和SSE单元。如果它们被内核代码更改了,那么必须在休眠或离开内核之前正确地恢复它们。在先发制人的内核上也可能需要更多的预防措施。

附录B

链接器优化

本章描述了可由链接器执行的优化。

B.1 合并GOTPLT和GOT插槽

在中小型模型中,当对同一个函数符号同时有PLT和GOT引用时,通常连接器会为PLT条目创建一个GOTPLT槽,为GOT引用创建一个GOT槽。创建一个运行时JUMP_SLOT重定位来更新GOTPLT槽,并创建一个运行时GLOB_DAT重定位来更新GOT槽。在运行时,JUMP_SLOT和GLOB_DAT重定位分别对GOTPLT和GOT槽位应用相同的符号值。

作为一种优化,链接器可以将GOTPLT和GOT槽合并到单个GOT槽中,并删除运行时的JUMP_SLOT重定位。它取代了常规的PLT条目:


Figure B.1: Procedure Linkage Table Entry Via GOTPLT Slot


通过GOT插槽间接跳跃的GOT PLT条目:


Figure B.2: Procedure Linkage Table Entry Via GOT Slot


并将PLT引用解析为GOT PLT条目。间接jmp是一个5字节指令。nop可以被编码为3字节指令或11字节指令,用于8字节或16字节的PLT插槽。一个带有8字节槽的单独PLT可用于此优化。

这种优化不适用于STT_GNU_IFUNC符号,因为它们的GOTPLT槽被解析为所选的实现,它们的GOT槽被解析为它们的PLT条目。

如果需要指针相等,则必须避免这种优化,因为在这种情况下,符号值不会被清除,动态连接器也不会更新GOT槽。否则,生成的二进制文件将在运行时进入无限循环。

B.2 优化GOTPCRELX重新定位

AMD64指令编码支持使用R_X86_64_GOTPCRELX或R_X86_64_REX_GOTPCRELX对符号foo的重定位将内存操作数上的某些指令转换为另一种形式的即时操作数(如果foo是在本地定义的)。

Convert call and jmp 将call和jmp的内存操作数转换为即时操作数。


Table B.1: Call and Jmp Conversion


Convert mov 将mov的内存操作数转换为即时操作数。当位置无关代码被禁用,foo在本地低32位地址空间中定义时,mov中的内存操作数可以转换为即时操作数。否则,必须将mov改为lea。


Table B.2: Mov Conversion


Convert Test and Binop 将test和binop的内存操作数转换为即时操作数,其中binop是adc, add, and, cmp, or, sbb, sub, xor指令中的一个,当位置无关代码被禁用时。

__________________________________________________________________ Table B.3: Test and Binop Conversion


目录预览

<<AMD64(x86_64)架构abi文档:上>>
<<AMD64(x86_64)架构abi文档:中>>

AMD64(x86_64)架构abi文档:相关推荐

  1. AMD64(x86_64)架构abi文档:上

    System V Application Binary Interface AMD64 Architecture Processor Supplement (With LP64 and ILP32 P ...

  2. 使用语义分割架构的文档扫描仪 DeepLabV3

    0 介绍 地址:https://learnopencv.com/deep-learning-based-document-segmentation-using-semantic-segmentatio ...

  3. 玩转MFC文档视图架构编程1——深入浅出MFC文档/视图架构之基本概念深入浅出MFC文档/视图架构之文档

    原创地址: 深入浅出MFC文档/视图架构之基本概念 http://iis.xrtvu.com/Tech/ShowArticle.asp?ArticleID=276 深入浅出MFC文档/视图架构之文档模 ...

  4. 架构设计文档模板参考

    目录 备选方案模板 需求介绍 需求分析 5W 1H 8C 复杂度分析 高可用 高性能 可扩展 备选方案 备选方案评估 架构设计模板 总体方案 架构总览 核心流程 详细设计 高可用设计 高性能设计 可扩 ...

  5. 架构实战:架构设计文档模板

    在前面的专栏里,有同学留言说想看看具体的架构设计文档.由于信息安全的原因,再加上稍微复杂的系统,设计文档都是几十页,因此专栏无法直接给出详细的文档案例.但我认为提供一个架构设计文档模板还是很有必要的, ...

  6. 深入浅出MFC文档/视图架构之文档

    1.文档类CDocument 在"文档/视图"架构的MFC程序中,文档是一个CDocument派生对象,它负责存储应用程序的数据,并把这些信息提供给应用程序的其余部分.CDocum ...

  7. 深入浅出MFC文档/视图架构之文档模板

    在"文档/视图"架构的MFC程序中,提供了文档模板管理者类CDocManager,由它管理应用程序所包含的文档模板.我们先看看这个类的声明: / // CDocTemplate m ...

  8. 微服务 前台调用后台的慢的原因_20年IT农民工分享SpringCloud微服务架构实战文档...

    前言 越来越多的企业使用 SpringCloud 实现微服务架构设计.我们可以看到这样一种现象:不管是全新开发,还是系统重构,大家似乎都在争先恐后地使用微服务.对于一个Java开发人员来说,学习微服务 ...

  9. 服务器架构设计文档,架构设计文档

    很多同学问做架构设计,怎么才能写出比较好的文档.其实很简单,都是有套路的,今天刚好借这个机会,和大家分享下一般做架构设计该怎么写文档. 背景 首先介绍下项目背景.基于什么原因需要需求. 如果是新产品, ...

最新文章

  1. 谷歌AutoML鼻祖Quoc Le新作AutoML-Zero:从零开始构建机器学习算法
  2. mysql 占比函数_MySQL中你必须了解的函数
  3. linux dns 攻击,DNSlog攻击技巧 | CN-SEC 中文网
  4. .Net深入学习序列化和反序列化 (转)
  5. Matplotlib 中文用户指南 3.7 变换教程
  6. 【Linux】Linux用户和权限管理
  7. 【Computer Organization笔记10】单周期CPU设计:基于7条MIPS指令的数据通路
  8. java jar 是什么_java中的jar文件是什么
  9. 实用工具系列 - Xshell安装下载与使用
  10. 美国与中国互联网电视发展状况比较分析研究
  11. LMS自适应滤波的MATLAB实现——实例仿真
  12. 国内刊物(杂志社,编辑部)联系方式列表 zt
  13. android build.prop 修改,修改android的build.prop文件真的能够提高android设备性能?!...
  14. python123第三单元测试卷_第三单元测试卷(带答案)
  15. 使用Kotlin配合RxJava网络请求
  16. php开源记账,php记账
  17. stm32——手动移植HAL库以及错误解决方案(以STM32F103ZE为例)
  18. listview 的首行固定内容标题且加粗显示(类似于表格的首行)的实现方法
  19. 安卓开发(一)快速搭建Android开发环境
  20. SearchView基本功能用法

热门文章

  1. OCR之论文笔记TrOCR
  2. 优达学城自动驾驶汽车-Project2 Traffic_Sign_Classifier
  3. 文件上传,服务器文件名中文乱码
  4. VLOOKUP函数具体操作及注意事项
  5. 三种视频播放标签(video,embed,iframe)-------笔记
  6. 51fe 漫画下载助手 release091008
  7. 【Luat-air105】1.air105资料整理及点灯
  8. C++:no type named ‘type‘ in ‘class std::result_of<void (*())(HWND__*)>‘
  9. 基于MATLAB软件GUI界面的可编程电音合成器软件
  10. day016类的成员(变量、方法、属性)、私有