AMD64(x86_64)架构abi文档:上

      System V Application Binary Interface

         AMD64 Architecture Processor Supplement
        (With LP64 and ILP32 Programming Models)

                                          Version 1.0


1 引言
2 软件安装
3 底层系统信息
3.1 机器接口
3.1.1 处理器架构
3.1.2 数据表示
3.2 函数调用序列
3.2.1 寄存器
3.2.2 堆栈框架
3.2.3 参数传递
3.3 操作系统接口
3.3.1 异常接口
3.3.2 虚拟地址空间
3.3.3 页面大小
3.3.4 虚拟地址分配
3.4 进程初始化
3.4.1 初始堆栈和寄存器状态
3.4.2 线程状态
3.4.3 辅助向量
3.5 编码示例
3.5.1 架构约束
3.5.2 约定
3.5.3 与位置无关的函数序言
3.5.4 数据对象
3.5.5 函数调用
3.5.6 分支
3.5.7 变量参数列表
3.6 DWARF定义
3.6.1 DWARF版本号
3.6.2 DWARF寄存器编号映射
3.7 堆栈展开算法


1 引言

  AMD64¹ 架构² 是x86架构的扩展。任何采用AMD64体系结构规范的处理器也将为以前的英特尔8086体系结构后代提供兼容模式,包括32位处理器,如英特尔386、英特尔奔腾和AMD K6-2处理器。操作系统符合

  AMD64 ABI可以支持执行设计为在这些兼容模式下执行的程序。AMD64 ABI不适用于此类计划;本文档仅适用于以AMD64体系结构提供的“长”模式运行的程序。

  使用AMD64指令集的二进制文件可以编程为32位模型,其中C数据类型int、long和所有指针类型都是32位对象(ILP32);或者对于64位模型,其中C int类型是32位,但C long类型和所有指针类型都是64位对象(LP64)。本规范涵盖LP64和ILP32编程模型。

  除非另有说明,否则AMD64体系结构ABI遵循Intel386 ABI中描述的约定。AMD64 ABI并不是复制Intel386 ABI的全部内容,而是仅表示对Intel386 ABI进行了更改的地方。

  尚未尝试为C以外的语言指定ABI。然而,假设许多编程语言希望与用C编写的代码相链接,因此此处记录的ABI规范也适用于那里。


¹ AMD64以前被称为x86-64。出于历史原因,许多地方使用后一个名称,而不是AMD64。

² 架构规范可在网站上获得,网址为http://www.x86-64.org/documentation。


2 软件安装

  本文档没有指定如何在AMD64架构机器上安装软件。


3 底层系统信息

3.1 机器接口

3.1.1 处理器架构

  任何程序都可以期望AMD64处理器实现表3.1中所述的基线特性。正如处理器手册中描述的那样,大多数特性名称对应于CPUID位。例外是OSFXSR和SCE,它们由%cr4寄存器和IA32_EFER MSR中的位控制。

  除了AMD64基准架构之外,还定义了几个由后来的CPU模块实现的微架构级别,从级别x86-64-v2开始。这些级别旨在支持在那些与它们兼容的系统上加载优化的实现(见下文)。从某种意义上来说,这些关卡是累积的,即之前关卡的功能会隐式地包含在之后的关卡中。

  x86-64-v3和x86-64-v4只有在完全启用相应特性的情况下才可用。这意味着系统必须通过处理器手册中对这些特性的完整检查序列,包括对使用xgetbv获得的XCR0特性标志的验证。

  微体系结构级别的建议使用表3.1中微体系结构级别的名称预计用作目录名称(动态链接器根据当前CPU支持的级别来搜索)和编译器来选择CPU特性组。发行版还可以指定它们需要某个级别的CPU支持。

Table 3.1: Micro-Architecture Levels
Level NameCPU FeatureExample instruction
(baseline)CMOVcmov
CX8cmpxchg8b
FPUfld
FXSRfxsave
MMXemms
OSFXSRfxsave
SCEsyscall
SSEcvtss2si
SSE2cvtpi2pd
x86-64-v2CMPXCHG16Bcmpxchg16b
LAHF-SAHFlahf
POPCNTpopcnt
SSE3addsubpd
SSE4_1blendpd
SSE4_2pcmpestri
SSSE3phaddd
x86-64-v3AVXvzeroall
AVX2vpermd
BMI1andn
BMI2bzhi
F16Cvcvtph2ps
FMAvfmadd132pd
LZCNTlzcnt
MOVBEmovbe
OSXSAVExgetbv
x86-64-v4AVX512Fkmovw
AVX512BWvdbpsadbw
AVX512CDvplzcntd
AVX512DQvpmullq
AVX512VLn/a

  例如,要选择第二个级别x86-64-v3,程序员将构建一个带有-march=x86-64-v3 GCC标志的共享对象。生成的共享对象需要安装到/usr/lib64/glibc-hwcaps/x86-64-v3或/usr/lib/x86_64-linux-gnu/glibc-hwcaps/x86-64-v3目录中(对于使用多arch文件系统布局的发行版)。为了支持只实现K8基线的系统,必须在默认位置/usr/lib64或/usr/lib/x66_64-linux/gnu中安装回退实现。它必须使用-march=x86-64(上游GCC默认值)构建。如果不遵循这条准则,那么在不支持优化共享对象所针对的级别的系统上加载库将会失败。

  安装在匹配的glibc-hwcaps子目录下的共享对象可以使用此级别和更早级别的CPU特性,而无需进一步的检测逻辑。

  仍然需要对本节中没有列出或只在后面的级别列出的其他CPU特性进行运行时检测(即使当前所有CPU一起实现了某些CPU特性)。

  如果某个发行版需要对某个级别的支持,它们会使用适当的-march=选项构建所有内容,并将构建好的二进制文件安装在默认文件系统tions中。当针对这些发行版时,程序员可以使用相同的-march=选项构建他们的二进制文件,并将它们安装到默认位置。为以后的级别优化的共享对象仍然可以安装到具有适当名称的子目录中。


3.1.2 数据表示

  在这个规范中,术语字节指的是8位对象,术语二字节指的是16位对象,术语四字节指的是32位对象,术语八字节指的是64位对象,术语十六字节指的是128位对象¹

基本类型

  图3.1显示了ISO C标量类型与处理器标量类型之间的对应关系。其中__int128、_Float16、__float80、__float128、__m64、__m128、__m256和__m512类型为可选的。
  __float128类型使用15位指数、113位尾数(如果icant位是隐式的,则高阶显著性)和指数偏差16383.²


¹ Intel386 ABI使用术语half - word表示16位对象,术语word表示32位对象,术语doubleword表示64位对象。但大多数IA-32处理器的特定文档将一个词定义为16位对象,一个双字作为32位对象,一个四字作为64位对象,一个双四字作为128位对象。

²AMD64架构的初始实现只希望通过软件模拟支持对__float128类型的操作。

Figure 3.1: Scalar Types
TypeCsizeofAlignment(bytes)AMD64
Integral_Bool†11boolean
char / signed char11signed byte
unsigned char11unsigned byte
signed short22signed twobyte
unsigned short22unsigned twobyte
signed int / enum†††44signed fourbyte
unsigned int44unsigned fourbyte
signed long (LP64)88signed eightbyte
unsigned long (LP64)88unsigned eightbyte
signed long (ILP32)44signed fourbyte
unsigned long (ILP32)44unsigned fourbyte
signed long long88††††signed eightbyte
unsigned long long88††††unsigned eightbyte
__int128†† / signed __int128††1616signed sixteenbyte
unsigned __int128††1616unsigned sixteenbyte
Pointerany-type * (LP64) / any-type (*)() (LP64)88unsigned eightbyte
any-type * (ILP32) / any-type (*)() (ILP32)44unsigned fourbyte
Floating-point_Float16††††††2216-bit (IEEE-754)
float44single (IEEE-754)
double88††††double (IEEE-754)
__float80†† / long double†††††161680-bit extended (IEEE-754)
__float128†† / long double†††††1616128-bit extended (IEEE-754)
Decimal-floating-point_Decimal324432bit BID (IEEE-754R)
_Decimal648864bit BID (IEEE-754R)
_Decimal1281616128bit BID (IEEE-754R)
Packed__m64††88MMX and 3DNow!
__m128††1616SSE and SSE-2
__m256††3232AVX
__m512††6464AVX-512

† 这种类型在c++中称为bool。

†† 这些类型是可选的。

††† c++和C的一些实现允许枚举大于int。基础类型按此顺序被碰撞为unsigned int、long int或unsigned long int。

†††† 在Intel386 ABI中,long long, signed long, unsigned long long和double类型有4字节对齐。

††††† 长双类型是128位的,与__float128类型相同,在Android™平台上。有关Android™平台的更多信息,请访问http://www.android.com/。

†††††† _Float16型,来自ISO/IEC TS 18661- 3:15,是可选的。



  长双类型使用15位指数,64位尾数,显式高阶有效位,指数偏差为16383 ³。虽然长双类型需要16字节存储,但只有前10字节是显著的。其余6个字节是尾部填充,这些字节的内容是未定义的。

  __int128类型在内存中以小端顺序存储,即64个低序位存储在比64个高序位更低的地址。

  _Alignof(max_align_t)的值为16。

  空指针(适用于所有类型)的值为零。

  对于LP64, size_t类型定义为unsigned long;对于ILP32, size_t类型定义为unsigned int。

  当存储在内存对象中时,布尔值存储为单字节对象,其值总是0 (false)或1 (true)。当存储在整数寄存器中(除了作为参数传递),寄存器的所有8个字节都是有效的;任何非零值都被认为是真值。

  与Intel386架构一样,AMD64架构通常不需要对所有数据访问进行正确对齐。非对齐的数据访问比对齐的数据访问慢,但在其他方面表现相同。唯一的例外是__m128, __m256和__m512必须始终正确对齐。

  Aggregates and Unions

  结构和联合假定对其最严格对齐的组件进行对齐。使用适当的对齐方式将每个成员分配到最低可用偏移量。任何对象的大小总是该对象对齐的倍数。

  数组使用与其元素相同的对齐方式,除了长度至少为16字节的局部或全局数组变量或C99变长数组变量总是至少为16字节⁴ 的对齐方式结构对象和联合对象可能需要填充以满足大小和对齐约束。任何填充的内容都是未定义的。

  Bit-Fields

  C结构体和联合定义可能包括位域,用于定义指定大小的整数值。

  ABI不允许位字段具有__m64、__m128、__m256或__m512类型。使用这些类型的位字段的程序是不可移植的。


³ 这种类型是x87双扩展精度数据类型。

⁴ 对齐要求允许在阵列上操作时使用SSE指令。编译器通常不能计算变长数组(VLA)的大小,但预计大多数VLA将需要至少16字节,因此要求VLA至少有16字节对齐是合乎逻辑的。


3.2 函数调用序列

  本节介绍标准函数调用顺序,包括栈帧布局、寄存器使用、参数传递等。

  标准的调用序列要求只适用于全局函数。不能从其他编译单元访问的本地函数可以使用不同的约定。尽管如此,建议所有函数在可能的情况下都使用标准调用序列。


3.2.1 寄存器

  AMD64架构提供16个通用64位寄存器。此外,该架构还提供16个SSE寄存器,每个128位宽,以及8个x87浮点寄存器,每个80位宽。每一个x87浮点寄存器都可以在MMX/3DNow!模式作为64位寄存器。所有这些寄存器对于给定线程的所有活动过程都是全局的。

  英特尔AVX(高级矢量扩展)提供16 256位宽的AVX寄存器(%ymm0 - %ymm15)。较低的128位%ymm0 - %ymm15别名为各自的128b-位SSE寄存器(%xmm0 - %xmm15)。Intel AVX-512提供32个512位宽的SIMD寄存器(%zmm0 - %zmm31)。较低的128位%zmm0 - %zmm31别名为各自的128bbit SSE寄存器(%xmm0 - %xmm31 ⁵)。较低的256位%zmm0 - %zmm31别名为各自的256位AVX寄存器(%ymm0 - %ymm31 ⁶)。为了传递参数和返回函数,%xmmN、%ymmN和%zmmN指向同一个寄存器。同一时间只能使用其中一个。我们使用矢量寄存器来表示SSE、AVX或AVX-512寄存器。此外,Intel AVX-512还提供8个矢量掩码寄存器(%k0 - %k7),每个64位宽。

  英特尔高级矩阵扩展(Intel Advanced Matrix Extensions, Intel AMX)是一种由两个组件组成的编程范式:一组二维寄存器(tiles),表示来自更大的二维内存图像的子数组,以及能够在tiles上操作的加速器。英特尔AMX实现的能力是通过调色板枚举的。支持两个调色板:调色板0表示初始化状态,而调色板1由8个多达1 KB大小的tile寄存器(%tmm0 - %tmm7)组成,这是由一个tile控制寄存器控制的。

  本小节讨论每个寄存器的用法。寄存器%rbp, %rbx和%r12到%r15“属于”调用函数,被调用函数需要保留它们的值。换句话说,被调用函数必须为其调用方保留这些寄存器的值。其余的寄存器“属于”被调用函数如果调用函数希望在整个函数调用中保存这样的寄存器值,它必须将该值保存在其本地堆栈帧中。

Figure 3.3: Stack Frame with Base Pointer
PositionContentsFrame
8n+16(%rbp) / 16(%rbp)memory argument eightbyte n … memory argument eightbyte 0Previous
8(%rbp)return addressCurrent
0(%rbp)previous %rbp value
-8(%rbp) / 0(%rsp)unspecified … variable size
-128(%rsp)red zone

⁵ %xmm15 - %xmm31仅在Intel AVX-512中可用。

⁶ %ymm15 - %ymm31仅适用于英特尔AVX-512。


  进入某个功能时,CPU应处于x87模式。因此,每个使用MMX寄存器的函数都需要在使用MMX寄存器之后,在返回或调用另一个函数之前发出emms或femms指令。在函数入口和返回时,%rFLAGS寄存器中的方向标志DF必须是明确的(设置为“forward”方向)。其他用户标志在标准调用序列中没有指定的角色,并且在调用之间不保留。

  MXCSR寄存器的控制位是调用者保存的(跨调用保存),而状态位是调用者保存的(不保存)。x87状态字寄存器是调用者保存的,而x87控制字是被调用者保存的。


3.2.2 堆栈框架

  除了寄存器之外,每个函数在运行时堆栈上都有一帧。这个堆栈从高位地址开始向下增长。图3.3显示了栈的组织。

  输入参数区域的末端应按16(如果在堆栈上传递__m256或__m512,则为32或64)字节边界对齐。换句话说,在调用指令执行之前,堆栈需要有16(32或64)字节对齐。一旦控制转移到函数入口点,即在返回地址被推送之后,%rsp指向返回地址,(%rsp + 8)的值是16(32或64)的倍数。

  超出%rsp所指向的位置的128字节区域被认为是保留的,不能被信号或中断处理程序修改因此,函数可以将此区域用于跨函数调用不需要的临时数据。特别是,叶函数可能会在整个堆栈框架中使用这个区域,而不是在序言和尾声中调整堆栈指针。这个区域被称为红色区域。


3.2.3 参数传递

  在参数值计算完成后,它们要么被放置在寄存器中,要么被压入堆栈。下面几节将描述传递值的方式。

  我们首先定义一些类来对参数进行分类。类对应于AMD64寄存器类,定义为:

    INTEGER 该类由适合一个通用寄存器的整型组成。
    SSE 该类由适合向量寄存器的类型组成。
    SSEUP 该类由适合vector寄存器的类型组成,可以在其上层字节中传递和返回。
    X87, X87UP 这些类由将通过x87 FPU返回的类型组成。
    COMPLEX_X87 该类包含将通过x87 FPU返回的类型。
    NO_CLASS 该类在算法中用作初始化器。它将用于填充和空结构和工会。
    MEMORY 该类由将通过堆栈在内存中传递和返回的类型组成。

  Classification 每个参数的大小被四舍五入到8个字节。

    基本类型被分配给它们的自然类:

      类型(有符号和无符号)_Bool、char、short、int、long、long long和指针都属于INTEGER类。
      _Float16、float、double、_Decimal32、_Decimal64和__m64类型的参数在SSE类中。
      __float128、_Decimal128和__m128类型的实参被分为两部分。最不显著的属于SSE类,最显著的属于SSEUP类。
      __m256类型的参数被分成4个8字节的块。最不重要的一个属于SSE类,其他的都属于SSEUP类。
      __m512类型的参数被分成8个8字节的块。最不重要的一个属于SSE类,其他的都属于SSEUP类。
      long double类型参数的64位尾数属于X87类,16位指数加上6字节填充属于X87UP类。
      __int128类型的参数提供与整数相同的操作,但它们不能放入一个通用寄存器,而需要两个寄存器。为了分类的目的,__int128被当作它的实现方式来处理:

typedef struct {
	long low, high;
} __int128;

      __int128类型的实参存储在内存中必须按16字节边界对齐。

      complex T的实参,其中T是_Float16、float、double或__float128类型之一,它们被当作它们的实现方式来处理:

struct complexT {
	T real;
	T imag;
};

      complex long double类型的变量被分类为COMPLEX_X87类型。


    聚合(结构和数组)和联合类型的分类如下:
      1. 如果一个对象的大小大于8个字节,或者它包含未对齐的字段,它就有类MEMORY。
      2. 如果c++对象对于调用来说不是微不足道的,就像c++ ABI中指定的那样,它是通过不可见引用传递的(该对象在形参列表中被一个具有INTEGER类的指针替换)。
      3. 如果聚合的大小超过单个8字节,则分别对每个字节进行分类。每个8字节被初始化为类NO_CLASS。
      4. 对象的每个字段都是递归分类的,所以总是要考虑两个字段。结果类是根据8字节中字段的类来计算的:
        a. 如果两个类相等,则这是结果类。
        b. 如果其中一个类是NO_CLASS,则生成的类是另一个类。
        c. 如果其中一个类是MEMORY,结果就是MEMORY类。
        d. 如果其中一个类是INTEGER,结果就是INTEGER。
        e. 如果其中一个类是X87, X87UP, COMPLEX_X87类,内存被用作类。
        f. 否则将使用SSE类。

      5. 然后完成合并后的清理工作:
        a. 如果其中一个类是MEMORY,则整个参数在内存中传递。
        b. 如果X87UP前面没有X87,则整个参数在内存中传递。
        c. 如果聚合的大小超过两个8字节,并且前8字节不是SSE,或者其他任何8字节不是SSEUP,那么整个参数将在内存中传递。
        d. 如果SSEUP前面没有SSE或SSEUP,则将其转换为SSE。


  Passing 一旦参数被分类,寄存器将被分配(从左到右的顺序),传递如下:

    1. 如果类是MEMORY,则将实参传递到堆栈中一个地址,该地址与实参对齐有关(可能比其自然对齐更多)。
    2. 如果类是INTEGER,则使用序列%rdi、%rsi、%rdx、%rcx、%r8和%r9的下一个可用寄存器。
    3. 如果类是SSE,则使用下一个可用的向量寄存器,寄存器按%xmm0到%xmm7的顺序取。
    4. 如果类是SSEUP,则在最后使用的向量寄存器的下一个可用的八字节块中传递八字节。
    5. 如果类是X87、X87UP或COMPLEX_X87,则在内存中传递它。

    当在寄存器或堆栈中返回或传递_Bool类型的值时,第0位包含真值,第1到7位应为0。
    如果没有寄存器可用于参数的任何8字节,则整个参数将在堆栈上传递。如果已经为这样的参数分配了大约八个字节的寄存器,则会恢复赋值。

Figure 3.4: Register Usage
RegisterUsagecallee saved
%rax临时登记;通过可变参数传递有关所用向量寄存器数量的信息;第一个返回寄存器No
%rbxcallee-saved注册Yes
%rcx用于向函数传递第四个整数参数No
%rdx用于向函数传递第三个参数;第二个返回寄存器没有%rsp堆栈指针No
%rsp堆栈指针Yes
%rbpcallee-saved登记;可选用作帧指针Yes
%rsi用于向函数传递第二个参数No
%rdi用于向函数传递第一个参数No
%r8用于向函数传递第5个参数No
%r9用于向函数传递第6个参数No
%r10临时寄存器,用于传递函数的静态链指针No
%r11临时登记;暂时寄存器No
%r12-r14callee-saved寄存器Yes
%r15callee-saved登记;可选地用作GOT基指针Yes
%xmm0–%xmm1用于传递和返回浮点参数No
%xmm2–%xmm7用于传递浮点参数No
%xmm8–%xmm15临时登记;暂时寄存器No
%tmm0–%tmm7临时登记;暂时寄存器No
%k0–%k7临时登记;暂时寄存器No
%st0,%st1临时寄存器,用于返回长双参数No
%st2–%st7临时登记;暂时寄存器No
%fs线程的指针Yes
mxcsrSSE2控制和状态字(word)partial
x87 SWx87状态字No
x87 CWx87控制字Yes
tilecfig平铺控制寄存器No

    一旦寄存器赋值,在内存中传递的参数就会以相反的(从右到左)顺序压入堆栈。
    对于可能调用使用可变参数或标准参数的函数的调用(无原型调用或对包含省略号的函数的调用(…)在声明中)%al19被用作隐藏参数,用于指定所使用的向量寄存器的数量。%al的内容不需要完全匹配寄存器的数量,但必须是所使用的向量寄存器数量的上限,并且在0-8的范围内。
    当向使用可变参数或标准参数的函数传递__m256或__m512参数时,必须提供函数原型。否则,运行时行为是未定义的。


  Returning of Values 返回值的算法如下:

    1. 使用分类算法对返回类型进行分类。
    2. 如果该类型具有MEMORY类,则调用者为返回值提供空间,并将该存储地址作为函数的第一个参数传递到%rdi中。实际上,这个地址变成了一个“隐藏”的第一个参数。此存储不能与通过除此参数外的其他名称对被调用方可见的任何数据重叠。
       返回时%rax将包含在%rdi中被调用者传入的地址。
    3. 如果类是INTEGER,则使用序列%rax, %rdx的下一个可用寄存器。
    4. 如果类是SSE,则使用序列%xmm0, %xmm1的下一个可用向量寄存器。
    5. 如果类是SSEUP,则在最后使用的向量寄存器的下一个可用的八字节块中返回八字节。
    6. 如果类是X87,在%st0的X87堆栈上返回80位的X87数字。
    7. 如果类是X87UP,返回的值与之前的X87值在%st0中一起。
    8. 如果类是COMPLEX_X87,值的实部返回%st0,虚部返回%st1。

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

Figure 3.5: Parameter Passing Example
typedef struct {
	int a, b;
	double d;
} structparm;
structparm s;
int e, f, g, h, i, j, k;
long double ld;
double m, n;
__m256 y;
__m512 z;

extern void func (int e, int f,
					structparm s, int g, int h,
					long double ld, double m,
					__m256 y,
					__m512 z,
					double n, int i, int j, int k);
					
func (e, f, s, g, h, ld, m, y, z, n, i, j, k);

Figure 3.6: Register Allocation Example
通用寄存器浮点寄存器堆栈帧偏移量
%rdi: e%xmm0: s.d0: ld
%rsi: f%xmm1: m16: j
%rdx: s.a,s.b%ymm2: y24: k
%rcx: g%zmm3: z
%r8: h%xmm4: n
%r9: i

3.3 操作系统接口

3.3.1 异常接口

  正如AMD64手册所描述的,处理器改变模式来处理异常,可能是同步的、浮点/协处理器的或异步的。由指令执行引起的同步异常和浮点/协处理器异常可以由进程显式地生成。因此,本节指定那些具有已定义行为的异常类型。AMD64架构将异常分为故障、告警和中止。请参阅Intel386 ABI以获得更多关于它们区别的信息。

  硬件异常类型

    操作系统定义了硬件异常与signal (BA_OS)指定信号的对应关系,如表3.2所示。与i386架构相反,AMD64没有定义任何在长模式下生成边界检查错误的指令。


3.3.2 虚拟地址空间

  虽然AMD64架构使用64位指针,但实现只需要处理48位地址。因此,符合进程只能使用0x00000000 00000000到0x00007fff ffffff20的地址。


3.3.3 页面大小

  系统允许使用4KB到64KB(包括64KB)之间的任何2次方的页面大小。


3.3.4 虚拟地址分配

  从概念上讲,进程拥有可用的全部地址空间。然而,在实践中,有几个因素限制了流程的大小。

    系统会保留与配置相关的虚拟空间。

Table 3.2: Hardware Exceptions and Signals
编号异常名称信号
0divide error fault / 除法误差错误SIGFPE
1single step trap/fault / 单步陷阱/错误SIGTRAP
2non-maskable interrupt / 非屏蔽中断none
3breakpoint trap / 断点陷阱SIGTRAP
4overflow trap / 溢位陷阱SIGSEGV
5(reserved) / (保留)
6invalid opcode fault / 无效指令码错误SIGILL
7no coprocessor fault / 无协处理器错误SIGFPE
8double fault abort / 双重错误中止none
9coprocessor overrun abort / 协处理器溢出中止SIGSEGV
10invalid TSS fault / TSS错误无效none
11segment no present fault / 段不存在none
12stack exception fault / 堆栈异常错误SIGSEGV
13general protection fault/abort / 一般保护错误/中止SIGSEGV
14page fault / 页错误SIGSEGV
15(reserved) / (保留)
16coprocessor error fault / 协处理器错误SIGFPE
other(unspecified) / (未指定)SIGILL

Table 3.3: Floating-Point Exceptions
编码原因
FPE_FLTDIVfloating-point divide by zero / 浮点数除以零
FPE_FLTOVFfloating-point overflow / 浮点数溢出
FPE_FLTUNDfloating-point underflow / 浮点下溢
FPE_FLTRESfloating-point inexact result / 浮点不精确结果
FPE_FLTINVinvalid floating-point operation / 浮点操作无效

    系统为每个进程保留了与配置相关的空间量。
    进程的大小超过系统可用的物理内存和辅助存储的总和将无法运行。虽然必须有一些物理内存才能运行任何进程,但是系统可以执行比物理内存大的进程,并在二级存储之间对它们进行分页。尽管如此,物理内存和二级存储都是共享资源。每次程序执行时,系统负载都会有所不同,这会影响可用的数量。

  间接引用空指针的程序是错误的,进程不应期望0x0是有效地址。



Figure 3.7: Virtual Address Configuration
地址区域描述
0xffffffffffffffffReserved system area / 预留系统区域End of memory
0x80000000000Dynamic segments / 动态段
0Process segments / 进程段Beginning of memory

  虽然应用程序可以控制其内存分配,但典型的排列如图3.8所示。


Figure 3.8: Conventional Segment Arrangements
地址区域
Dynamic segments / 动态段
0x80000000000Stack segment / 堆栈段
Data segments / 数据段
0x400000Text segments / 文本段
0Unmapped / 未映射

3.4 进程初始化

3.4.1 初始堆栈和寄存器状态

  Special Registers

    AMD64体系结构定义了浮点指令。在进程启动时,两个浮点单元SSE2和x87都清除了所有浮点异常状态标志。控制字的状态如表3.4和表3.5所定义。


Table 3.4: x87 Floating-Point Control Word
标志注释
RC0Round to nearest / 就近舍入
PC11Double extended precision / 双扩展精度
PM1Precision masked / 精确屏蔽
UM1Underflow masked / 底流屏蔽
OM1Overflow masked / 溢出屏蔽
ZM1Zero divide masked / 零除掩码
DM1De-normal operand masked / 非规范化操作数掩码
IM1Invalid operation masked / 屏蔽的操作无效



Table 3.5: MXCSR Status Bits
标志注释
FZ0Do not flush to zero / 不清除为0
RC0Round to nearest / 就近舍入
PM1Precision masked / 精确屏蔽
UM1Underflow masked / 底流屏蔽
OM1Overflow masked / 溢出屏蔽
ZM1Zero divide masked / 零除掩码
DM1De-normal operand masked / 非规范化操作数掩码
IM1Invalid operation masked / 屏蔽的操作无效


    rFLAGS寄存器包含系统标志,如方向标志和进位标志。应用软件可以访问rFLAGS的低16位(FLAGS部分)。它们在进程初始化时的状态如表3.6所示。


Table 3.6: rFLAGS Bits
标志注释
DF0Direction forward / 正向
CF0No carry / 无进位
PF0Even parity / 偶数奇偶校验
AF0No auxiliary carry / 无辅助进位
ZF0No zero result / 无零结果
SF0Unsigned result / 无符号结果
OF0No overflow occurred / No overflow occurred

  Stack State

    本节描述exec (BA_OS)为新进程创建的机器状态。各种语言实现将这种初始程序状态转换为语言标准所需的状态。
    例如,一个C程序执行一个名为main的函数,声明为:

extern int main ( int argc , char *argv[ ] , char* envp[ ] );

      argc 非负参数计数。
      argv 参数字符串数组。
      envp 环境字符串数组,以空指针结束。

      当main()返回其值时,将其传递给exit(),如果该值已被重写并返回,则返回_exit()(必须不受用户干预)。
      进程堆栈的初始状态,即调用_start时的状态如图3.9所示。


Figure 3.9: Initial Process Stack
标志起始地址长度
Information block, including argument strings, environment strings, auxiliary information …varies
Unspecified
Null auxiliary vector entry每个八字节
Auxiliary vector entries …每个十六字节
0八字节
Environment pointers …每个八字节
08+8*argc+%rsp八字节
Argument pointers8+%rspargc 八字节
Argument count%rsp八字节
UndefinedLow Addresses


      参数字符串、环境字符串和辅助信息在信息块中没有特定的顺序,它们不需要被紧凑地分配。

      只有下面列出的寄存器在进程入口有指定的值:

        %rbp 这个寄存器的内容在进程初始化时是不指定的,但是用户代码应该通过将帧指针设置为零来标记堆栈最深处的帧。

        %rsp 堆栈指针保存着最低地址的字节的地址,该字节是堆栈的一部分。它保证在进程入口是16字节对齐的。

        %rdx 应用程序应该用atexit (BA_OS)注册的函数指针。

        数据和堆栈段是否初始映射有执行权限是不确定的。需要在堆栈或数据段上执行代码的应用程序应该采取适当的预防措施,例如调用mprotect()。


3.4.2 线程状态

  新线程继承父线程的浮点状态,并且这个状态在以后是线程私有的。


3.4.3 辅助向量

  辅助向量是一个由以下结构组成的数组(参见图3.10),根据a_type成员解释。


Figure 3.10: auxv_t Type Definition
typedef struct
{
	int a_type;
	union {
		long a_val;
		void *a_ptr;
		void (*a_fnc)();
	} a_un;
} auxv_t;

  AMD64 ABI使用图3.11中定义的辅助向量类型。


Figure 3.11: Auxiliary Vector Types
名称a_un
AT_NULL0ignored
AT_IGNORE1ignored
AT_EXECFD2a_val
AT_PHDR3a_ptr
AT_PHENT4a_val
AT_PHNUM5a_val
AT_PAGESZ6a_val
AT_BASE7a_ptr
AT_FLAGS8a_val
AT_ENTRY9a_ptr
AT_NOTELF10a_val
AT_UID11a_val
AT_EUID12a_val
AT_GID13a_val
AT_EGID14a_val
AT_PLATFORM15a_ptr
AT_HWCAP16a_val
AT_CLKTCK17a_val
AT_SECURE23a_val
AT_BASE_PLATFORM24a_ptr
AT_RANDOM25a_ptr
AT_HWCAP226a_val
AT_EXECFN31a_ptr


    AT_NULL 辅助向量没有固定长度;相反,它最后一个条目的a_type成员有这个值。

    AT_IGNORE 该类型表示该表项没有意义。a_un对应的值未定义。

    AT_EXECFD 在进程创建时,系统可以将控制权传递给解释器程序。当发生这种情况时,系统在辅助向量中放置AT_EXECFD类型的条目或AT_PHDR类型的条目。AT_EXECFD类型的条目使用a_val成员包含一个文件描述符,用于读取应用程序的目标文件。

    AT_PHDR 在将控制传递给解释器程序之前,系统可以创建应用程序的内存映像。当发生这种情况时,AT_PHDR条目的a_ptr成员告诉解释器在内存映像中的哪里找到程序头表。

    AT_PHENT 该条目的a_val成员保存着程序头表中AT_PHDR条目所指向的一个条目的大小(以字节为单位)。

    AT_PHNUM 该条目的a_val成员保存着程序头表中AT_PHDR条目所指向的条目数。

    AT_PAGESZ 如果存在,这个条目的a_val成员将给出系统页面的大小,以字节为单位。

    AT_BASE 该条目的 a_ptr 成员保存解释程序加载到内存中的基地址。 有关基地址的更多信息,请参见 System V ABI 中的“程序头”。

    AT_FLAGS 如果存在,则该条目的a_val成员持有一位标志。具有未定义语义的位被设为零。

    AT_ENTRY 该条目的a_ptr成员保存着应用程序的入口点,解释器程序应该将控制权转移到该入口点。

    AT_NOTELF 如果程序的格式不是ELF,则该条目的a_val成员非零。

    AT_UID 该条目的a_val成员持有进程的真实用户id。

    AT_EUID 该条目的a_val成员持有进程的有效用户id。

    AT_GID 该条目的a_val成员持有进程的真实组id。

    AT_EGID 该条目的a_val成员持有进程的有效组id。

    AT_PLATFORM 该条目的a_ptr成员指向一个包含平台名的字符串。

    AT_HWCAP 这个表项的a_val成员包含一个CPU特性的位掩码。掩码为CPUID 1.EDX返回的值。

    AT_CLKTCK 该条目的a_val成员包含times()递增的频率。

    AT_SECURE 如果程序处于安全模式(例如以suid开头),则该条目的a_val成员包含一个。否则为零。

    AT_BASE_PLATFORM 该条目的a_ptr成员指向一个标识基本体系结构平台(可能与平台不同)的字符串。

    AT_RANDOM 该条目的a_ptr成员指向安全生成的16个随机字节。

    AT_HWCAP2 该条目的a_val成员包含扩展硬件特性掩码。目前是0,但将来可能包含额外的特性位。

    AT_EXECFN 该条目的a_ptr成员是指向被执行程序文件名的指针。


3.5 编码示例

  本节讨论基本操作的示例代码序列,例如调用函数、访问静态对象以及将控制从程序的一部分转移到另一部分。 与以前的材料不同,该材料不是规范的。

3.5.1 架构约束

  AMD64体系结构通常不允许指令将任意64位常量编码为直接操作数。大多数指令接受32位直接指令,这些指令被符号扩展为64位指令。此外,具有寄存器目标的32位操作隐式执行零扩展,使上半部分设置为0的64位即时加载更便宜。
  此外,分支指令接受符号扩展的32位立即数操作数,并用于调整指令指针。类似地,对于具有等效限制的数据访问,存在指令指针相对寻址模式。
  为了提高性能和减少代码大小,需要根据需求使用不同的代码模型。
  代码模型为符号值定义约束,使编译器能够生成更好的代码。基本上,代码模型在寻址(绝对与位置无关)、代码大小、数据大小和地址范围方面有所不同。我们只定义了少数普遍感兴趣的代码模型:

    Small code model 所执行代码的虚拟地址在链接时已知。此外,已知所有符号都位于虚拟地址中,范围从0到 2^31 - 2^24−1或从0x00000000到0x7effffff。
      这允许编译器在符号扩展的即时操作数中对符号引用进行编码,其偏移量范围为 −(2^31) 到 2^24或0x80000000到0x01000000,其偏移量范围为0到 2^31 − 2^24或0x00000000到0x7f000000,并对偏移量范围为 −(2^24) 到 2^24或0xff000000到0x01000000的符号使用指令指针相对寻址。
      这是最快的代码模型,我们希望它适用于绝大多数程序。

    Kernel code model 操作系统的内核通常相当小,但运行在负的一半地址空间。因此,我们定义所有符号的范围为2^64 − 2^31 到 2^64 − 2^24或0xffffffff80000000到0xffffffff000000。
      这个代码模型具有与小模型相似的优点,但是只允许对从 2^31到 2^31 + 2^24 或从0x80000000到0x81000000的偏移量进行零扩展符号引用编码。符号扩展引用的范围偏移量变化为0到 2^31 + 2^24或0x00000000到0x81000000。

    Medium code model 在中型模型中,数据部分被分成两部分——数据部分仍然以与小代码模型相同的方式受到限制,而大数据部分除了可用的寻址空间之外没有限制。 程序布局的设置方式必须使大数据部分(.ldata、.lrodata、.lbss)位于文本和数据部分之后。
      该模型要求编译器使用movabs指令来访问大型静态数据并将地址加载到寄存器中,但是保留了小代码模型在小数据和文本部分操作地址的优点(对于分支来说特别需要)。
      默认情况下,只将大于65535字节的数据放置在大数据段中。

    Large code model 大型代码模型没有对节的地址和大小进行假设。
      编译器需要使用movabs指令,就像在媒体代码模型中一样,即使是处理文本部分中的地址。此外,当分支到与当前指令指针的偏移量未知的地址时,需要间接分支。
      在中小型模型中,可以通过将程序分解为多个共享库来避免对文本部分的限制,因此只有当单个函数的文本超过了中型模型所允许的范围时,才严格要求使用该模型。

    Small position independent code model (PIC) 与之前的模型不同,指令和数据的虚拟地址直到动态链接时间才被知道。所以所有的地址都是相对于指令指针的。
      另外,一个符号到指令结束的最大距离被限制在 2^31 − 2^24−1 或0x7effffff,允许编译器对每一个偏移量在 −(2^24) 到 2^24 或0xff000000到0x01000000范围内的符号使用指令指针相对分支和硬件支持的寻址模式。

    Medium position independent code model (PIC) 这个模型与前一个模型类似,但与中等静态模型相似,它在对象文件的末尾添加了大型数据段。
      在中等PIC模型中,指令指针相对寻址不能直接用于访问大型静态数据,因为偏移量可能会超过指令中位移场大小的限制。相反,需要使用由movabs、lea和add组成的展开序列。

    Large position independent code model (PIC) 这个模型和之前的模型一样,但是没有对符号的距离做任何假设。
      在静态数据寻址方面,大型PIC模型具有与中型PIC模型相同的局限性。此外,对全局偏移表、过程链接表和分支目的地的引用需要以类似的方式计算。此外,文本段的大小允许不超过16EB,因此类似的限制适用于所有对文本段的地址引用,包括分支。

  ILP32二进制文件只使用小代码模型和小位置独立代码模型(PIC)。


3.5.2 约定

  在本文档的编码示例和讨论中使用了一些特殊的汇编器符号。它们是:

    name@GOT: 指定符号名与GOT基值的偏移量。

    name@GOTOFF: 指定从GOT的底部到符号名位置的偏移量。

    name@GOTPCREL: 指定符号名的GOT条目与当前代码位置的偏移量。

    name@PLT: 指定从当前代码位置到符号名的PLT条目的偏移量。

    name@PLTOFF: 指定符号名的PLT条目与GOT的基数的偏移量。

    GLOBAL_OFFSET_TABLE: 指定从当前代码位置到GOT基的偏移量。


3.5.3 与位置无关的函数序言

  在这个小代码模型中,所有地址(包括GOT条目)都可以通过AMD64架构提供的IP相对寻址访问。因此不需要显式的GOT指针,因此也不需要函数序言来设置它。
  在中型和大型代码模型中,必须分配一个寄存器来保存位置无关对象中GOT的地址,因为AMD64 ISA不支持大于32位的即时位移。
  由于%r15在函数调用中被保留,它在函数序言中被初始化,以保存通过PLT调用其他函数的非叶函数的GOT地址22。其他函数可以自由使用任何其他寄存器。在整个文档中,%r15将用于示例中。


Figure 3.12: Position-Independent Function Prolog Code

  medium model:

leaq _GLOBAL_OFFSET_TABLE_(%rip),%r15 # GOTPC32 reloc

  large model:

pushq %r15 # save %r15
leaq 1f(%rip),%r11 # absolute %rip
1: movabs $_GLOBAL_OFFSET_TABLE_,%r15 # offset to the GOT (R_X86_64_GOTPC64)
leaq (%r11,%r15),%r15 # absolute address of the GOT

  对于中等模型,GOT指针被直接加载,对于大模型,%rip的绝对值被添加到GOT基底的相对偏移量中,以获得它的绝对地址(见图3.12)。


3.5.4 数据对象

  本节只介绍静态存储的对象。堆栈驻留对象被排除在外,因为程序总是计算它们相对于堆栈或框架指针的虚拟地址。
  因为只有movabs指令直接使用64位地址,所以根据代码模型,必须使用%rip相对寻址或在寄存器中构建地址并通过寄存器访问内存。
  对于绝对地址,%rip相对编码可以在小模型中使用。在媒介模型中,必须使用movabs指令来访问地址。
  位置无关的代码不能包含绝对地址。要访问全局符号,必须从全局偏移表中加载该符号的地址。在小模型中,GOT表项的地址可以用%rip-relative指令获取。


  Small models


Figure 3.13: Absolute Load and Store (Small Model)

在这里插入图片描述




Figure 3.14: Position-Independent Load and Store (Small PIC Model)

在这里插入图片描述



  Medium models


Figure 3.15: Absolute Load and Store (Medium Model)

在这里插入图片描述




Figure 3.16: Position-Independent Load and Store (Medium PIC Model)

在这里插入图片描述




Figure 3.17: Position-Independent Load and Store (Medium PIC Model), continued

在这里插入图片描述



  Large Models

    同样,为了访问64位寻址空间中任意位置的数据,需要显式地计算地址,这与媒介代码模型类似。


Figure 3.18: Absolute Global Data Load and Store

在这里插入图片描述




Figure 3.19: Faster Absolute Global Data Load and Store

在这里插入图片描述



    对于位置无关的代码访问静态和外部全局数据,假设GOT地址存储在专用寄存器中。在这些例子中,我们假设它位于%r15中(参见函数序言):


Figure 3.20: Position-Independent Global Data Load and Store

在这里插入图片描述




Figure 3.21: Faster Position-Independent Global Data Load and Store

在这里插入图片描述



3.5.5 函数调用

  Small and Medium Models


Figure 3.22: Position-Independent Direct Function Call (Small and Medium Model)

在这里插入图片描述




Figure 3.23: Position-Independent Indirect Function Call

在这里插入图片描述



  Large models

    一般情况下,不能假设函数的大小在2GB以内。因此,有必要显式地计算到达整个64位地址空间的期望地址。


Figure 3.24: Absolute Direct and Indirect Function Call

在这里插入图片描述

    对于与位置无关的对象:


Figure 3.25: Position-Independent Direct and Indirect Function Call

在这里插入图片描述

  Implementation advice

    如果在代码生成时,确定了某些条件,就有可能按照大型模型通常需要的方式生成更快或更小的代码序列。当:

    函数调用的(绝对)目标在2GB内,可以使用直接调用或%rip相对寻址:

在这里插入图片描述

    (PIC) GOT的基数在2GB以内,对GOT条目的间接调用可以这样实现:

在这里插入图片描述

    (PIC) PLT的基数在2GB以内,PLT条目可以相对%rip引用:

在这里插入图片描述
    (PIC)函数调用的目标在2GB范围内,并且不是全局的或局部绑定的,可以使用对符号的直接调用,也可以相对于%rip进行引用:

在这里插入图片描述


3.5.6 分支

  Small and Medium Models

    由于所有标签都在2GB范围内,因此在实现分支时无需特别注意。完整的AMD64 ISA可用。

  Large Models

    由于函数理论上最长可达16EB,AMD64 ISA中条件和无条件分支的最大32位位移不足以解决分支目标。因此,显式计算分支目标地址。对于绝对对象:


Figure 3.26: Absolute Branching Code

在这里插入图片描述




Figure 3.27: Implicit Calculation of Target Address

在这里插入图片描述

    对于位置无关的对象:




Figure 3.28: Position-Independent Branching Code

在这里插入图片描述

    对于绝对对象,switch语句的实现是:




Figure 3.29: Absolute Switch Code

在这里插入图片描述

    在构建与位置无关的对象时,switch 语句实现更改为:




Figure 3.30: Position-Independent Switch Code

在这里插入图片描述

3.5.7 变量参数列表

  一些可移植的C程序依赖于参数传递模式,隐式地假设所有参数都在堆栈上传递,并且参数在堆栈上以递增的顺序出现。做出这些假设的程序从来都不是可移植的,但是它们在许多实现上都工作过。但是,它们在AMD64架构上不起作用,因为有些参数是在寄存器中传递的。可移植的C程序必须使用头文件来处理变量参数列表。
  调用带有变量参数的函数时,必须将%al设置为向量寄存器中传递给该函数的浮点参数总数。


    跳转表在不同的部分发出,这样就会占用没有指令字节的缓存行,从而避免了独占缓存子系统的抖动。
    当调用带有变量参数列表的函数时,%al的唯一合法值是0 ~ 8。


  当__m256或__m512作为变量参数传递时,它应该始终在堆栈上传递。只有命名为__m256和__m512的参数才能像3.2.3节中指定的那样在寄存器中传递。


Figure 3.31: Parameter Passing Example with Variable-Argument List

在这里插入图片描述




Figure 3.32: Register Allocation Example for Variable-Argument List

在这里插入图片描述



  The Register Save Area

    接受变量参数列表并调用宏va_start的函数的序言将把参数寄存器保存到寄存器保存区。每个参数寄存器在寄存器保存区域中都有一个固定的偏移量,如图3.33所示。
    只有可能用于传递参数的寄存器需要保存。其他寄存器不被访问,可以用于其他目的。如果知道一个函数永远不会接受传入寄存器的参数,那么寄存器保存区可能会被完全省略。
    序言应该使用%al来避免不必要地保存XMM寄存器。对于只使用整数的程序来说,防止XMM单元的初始化特别重要。


      这个事实可以通过探索 va_arg 宏使用的类型来确定,或者通过命名参数已经完全用尽参数寄存器这一事实来确定。



Figure 3.33: Register Save Area

在这里插入图片描述



  The va_list Type

    va_list类型是一个数组,其中包含一个结构的单个元素,该结构包含实现va_arg宏所需的信息。图3.34给出了va_list类型的C定义。


Figure 3.34: va_list Type Declaration

在这里插入图片描述



  The va_start Macro

    va_start宏初始化结构如下:

      reg_save_area 元素指向寄存器保存区域的开始。

      overflow_arg_area 这个指针用于获取传递给堆栈的参数。它用传递给堆栈的第一个参数(如果有的话)的地址初始化,然后总是更新为指向堆栈上下一个参数的起点。

      gp_offset 元素保存了从reg_save_area到保存下一个可用的通用参数寄存器的位置的字节偏移量。如果所有的参数寄存器都耗尽了,它就被设为48(6 ∗ 8)。

      fp_offset 元素保存了从reg_save_area到保存下一个可用浮点参数寄存器的位置的字节偏移量。如果所有的参数寄存器都耗尽了,那么它的值就被设为304(6 * 8 + 16 * 16)。


  The va_arg Macro

    泛型va_arg(l, type)实现的算法定义如下:

      1. 确定type是否可以在寄存器中传递。如果不是,请执行步骤7。

      2. 计算num_gp来保存传递type所需的通用寄存器的数量,计算num_fp来保存所需的浮点寄存器的数量。

      3. 验证参数是否适合寄存器。案例:

           l->gp_offset > 48 − num_gp ∗ 8
        or
           l->fp_offset > 304 − num_fp ∗ 16

        执行步骤7。

      4. 从l->reg_save_area中获取类型,偏移量为l->gp_offset和/或l->fp_offset。如果参数在不同的寄存器类中传递,这可能需要将其复制到一个临时位置,或者需要对一般用途寄存器的比对大于8,对XMM寄存器的比对大于16。

      5. Set:

           l->gp_offset = l->gp_offset + num_gp ∗ 8

           l->fp_offset = l->fp_offset + num_fp ∗ 16

      6. 返回获取的类型。

      7. 如果类型所需的对齐超过8字节边界,则向上对齐l->overflow_arg_area至16字节边界。

      8. 从l->overflow_arg_area获取类型。

      9. 设置 l - > overflow_arg_area:

           l->overflow_arg_area + sizeof(type)

      10. 将l->overflow_arg_area向上对齐到8字节的边界。

      11. 返回获取的类型。

        va_arg宏通常作为编译器内置实现,并针对每个特定类型以简化形式展开。图3.35是va_arg宏的示例实现。


Figure 3.35: Sample Implementation of va_arg(l, int)

在这里插入图片描述



3.6 DWARF定义

  本节为AMD64处理器系列定义了带有任意记录格式的调试(DWARF)调试格式。AMD64 ABI没有定义调试格式。然而,所有在AMD64上实现DWARF的系统应使用以下定义。
  DWARF是一个用于符号级、源代码级调试的规范。调试信息格式不适合任何编译器或调试器的设计。有关DWARF的更多信息,请参见DWARF调试格式标准,可在以下网站获得:http://www.dwarfstd.org/。

3.6.1 DWARF版本号

  DWARF定义需要一些特定于机器的定义。需要为AMD64寄存器指定寄存器号映射。此外,从version3开始,DWARF规范要求定义特定于处理器的地址类代码。


3.6.2 DWARF寄存器编号映射

  表3.36概述了AMD64处理器家族的寄存器号映射。


3.7 堆栈展开算法

  堆栈框架不是自描述的,在需要堆栈展开的地方(例如用于异常处理),需要生成额外的展开信息。信息存储在一个可分配的section .eh_frame中,该section的格式与DWARF调试信息标准定义的.debug_frame相同,参见DWARF调试信息格式,扩展名如下:

    位置无关性 为了避免位置无关代码的加载时间重定位,FDE CIE偏移指针应该相对于CIE表项的开始位置存储。使用DWARF扩展的帧必须设置CIE标识符标签为1。

    传出参数区域增量 为了维护堆栈末端临时分配的输出参数区域的大小(在使用push指令时),可以使用操作GNU_ARGS_SIZE (0x2e)。该操作接受一个指定当前大小的uleb128参数。此信息用于在解开堆栈帧后跳转到函数的异常处理程序时调整堆栈帧。此外,CIE扩充文件应包含所使用编码的确切规范。建议尽可能使用PC相对编码,并根据所使用的代码模型调整大小。

    CIE增强 扩充字段是根据存储在CIE头中的扩充字段格式化字符串来格式化的。


      表定义Return Address有一个寄存器号,尽管地址存储在0(%rsp)中,而不是存储在物理寄存器中。
      本文档没有定义特权寄存器的映射。


    该字符串可以包含以下字符:


Figure 3.36: DWARF Register Number Mapping

在这里插入图片描述




Figure 3.37: Pointer Encoding Specification Byte

在这里插入图片描述



    z 指示存在一个uleb128来决定增加部分的大小。

    L 指示在FDE扩充中对LSDA指针的编码(以及存在)。
      数据字段由指定指针编码方式的单字节组成。它是表3.37中指定的值的掩码。
      缺省的DWARF指针编码(直接的4字节绝对指针)由值0表示。

    R 指示FDE代码指针的非默认指针编码。格式化由单个字节表示,方式与’ L '命令相同。

    P 表示CIE扩充中语言个性例程的存在和编码。编码由单个字节表示,其方式与’ L '命令后跟一个指向由指定编码编码的人格函数的指针相同。

    当增加出现时,第一个命令必须总是’ z ',以允许轻松跳过信息。

    为了简化对展开表的操作,运行时库提供了更高级别的栈展开机制API,详细信息请参见6.2节。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

坤昱

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值