允许在 DWARF 表达式栈上使用位置描述¶
1. 扩展¶
在 DWARF 5 中,表达式使用类型化值栈、单独的位置区域和独立的 loclist 机制进行求值。此扩展将所有这三种机制统一到一个通用的 DWARF 表达式求值模型中,该模型允许在求值栈上操作类型化值和位置描述。栈上同时支持单个和多个位置描述。此外,调用帧信息 (CFI) 被扩展以支持位置描述的完整通用性。这是以一种向后兼容 DWARF 5 的方式完成的。此扩展涉及对 DWARF 5 第 2.5 节(第 26-38 页)、第 2.6 节(第 38-45 页)和第 6.4 节(第 171-182 页)的更改。
此扩展允许以增量、一致且可组合的方式对位置描述执行操作。它允许定义少量操作来满足异构设备的需求,并为非异构设备带来好处。它充当一个基础,为已提出的其他问题提供支持,这些问题将使所有设备受益。
还探索了其他方法,包括添加专门的操作和规则。然而,这些方法导致需要更多不可组合的操作。它还导致了具有上下文敏感语义和必须定义的极端情况的操作。观察结果是,对于生产者和消费者而言,许多专门的上下文敏感操作比少量具有一致语义且与上下文无关的通用可组合操作更难。
首先,2. 异构计算设备 节描述了异构设备及其具有的 DWARF 5 未解决的功能。然后,3. DWARF5 节简要概述了 DWARF 5 表达式求值模型,重点介绍了支持异构功能的困难。接下来,4. 扩展解决方案 节概述了该提案,使用简化的示例来说明它如何解决异构设备的问题,以及如何使非异构设备受益。然后在 5. 结论 节中涵盖了总体结论。A. 对 DWARF 调试信息格式版本 5 的更改 附录给出了相对于 DWARF 版本 5 标准的更改。最后,B. 更多信息 附录提供了更多信息的参考。
2. 异构计算设备¶
GPU 和其他异构计算设备具有 CPU 计算设备不常见的功能。
这些设备通常比 CPU 具有更多的寄存器。这有助于减少内存访问,由于并发执行的线程数量多得多,内存访问往往比 CPU 上更昂贵。除了 CPU 的传统标量寄存器外,这些设备通常还具有许多宽向量寄存器。
它们可能支持掩码向量指令,编译器使用这些指令将高级语言线程映射到向量寄存器的通道上。因此,当向量指令执行时,多个语言线程以锁步方式执行。这被称为单指令多线程 (SIMT) 执行。
除了 CPU 的单个全局内存地址空间外,GPU 还可以具有多个内存地址空间。这些额外的地址空间使用不同的指令进行访问,并且通常是特定线程或线程组的本地地址空间。
例如,GPU 可能具有每个线程块的地址空间,该地址空间实现为暂存器内存,并具有显式硬件支持,以将部分隔离到创建为单个线程块的特定线程组。
GPU 也可能以非线性的方式使用全局内存。例如,为了有效地支持提供 SIMT 每个通道的地址空间,可能存在支持交错访问的指令。
通过优化,源变量可以位于这些不同的存储类型中。SIMT 执行要求位置能够表达运行时定义的向量寄存器片段的选择。对于更复杂的位置,能够分解它们的计算是有益的,这要求统一支持所有位置类型,否则就需要重复。
3. DWARF 5¶
在提出支持异构设备的建议解决方案之前,将简要概述 DWARF 5 表达式求值模型,以突出显示此扩展所解决的方面。
3.1 DWARF 如何将源语言映射到硬件¶
DWARF 是一种标准化的方式来指定调试信息。它描述了源语言实体,例如编译单元、函数、类型、变量等。它可以直接嵌入到代码对象可执行文件的节中,也可以拆分到它们引用的单独文件中。
DWARF 在源程序语言实体及其硬件表示之间进行映射。例如
它将硬件指令程序计数器映射到源语言程序行,反之亦然。
它将源语言函数映射到其入口点的硬件指令程序计数器。
它将源语言变量映射到特定程序计数器处的硬件位置。
它提供信息以允许对源语言函数调用堆栈的硬件寄存器进行虚拟展开。
此外,它还提供了有关源语言程序的许多其他信息。
特别地,源语言实体映射到硬件位置的方式存在很大差异。位置可能涉及运行时值。例如,源语言变量位置可以是
在寄存器中。
在内存地址处。
相对于当前堆栈指针的偏移量。
已优化掉,但具有已知的编译时值。
已优化掉,但具有未知值,例如未使用变量的情况。
分布在上述类型的多种位置组合中。
在内存地址处,但也瞬时加载到寄存器中。
为了支持这一点,DWARF 5 定义了一种丰富的表达式语言,包括 loclist 表达式和操作表达式。Loclist 表达式允许结果根据 PC 而变化。操作表达式由一系列在简单栈机器上求值的操作组成。
DWARF 表达式可以用作不同调试信息条目 (DIE) 的不同属性的值。DWARF 表达式也可以用作调用帧信息 (CFI) 条目操作的参数。表达式在由其使用位置决定的上下文中求值。上下文可能包括
表达式需要生成值还是实体的位置。
当前执行点,包括进程、线程、PC 和堆栈帧。
某些表达式在栈初始化为特定值或使用 DW_OP_push_object_address 操作可用的基本对象位置的情况下求值。
3.2 示例¶
以下示例说明了如何在 DWARF 5 中求值涉及操作的 DWARF 表达式。DWARF 还具有涉及位置列表的表达式,这些表达式在这些示例中未涵盖。
3.2.1 动态数组大小¶
第一个示例用于与 DIE 属性关联的操作表达式,该属性提供动态数组类型中的元素数量。此类属性指示表达式必须在提供值结果类型的上下文中求值。
在这个假设的示例中,编译器已在内存中分配了一个数组描述符,并将描述符的地址放置在架构寄存器 SGPR0 中。数组描述符的第一个位置是数组的运行时大小。
检索数组动态大小的可能表达式是
DW_OP_regval_type SGPR0 Generic
DW_OP_deref
表达式一次求值一个操作。操作具有操作数,并且可以在栈上弹出和推入条目。
表达式求值从第一个 DW_OP_regval_type 操作开始。此操作读取由其第一个操作数(SGPR0)指定的架构寄存器的当前值。第二个操作数指定要读取的数据的大小。读取的值被推入栈。每个栈元素都是一个值及其关联的类型。
类型必须是 DWARF 基本类型。它指定类型的编码、字节顺序和大小。DWARF 定义每个架构都有一个默认通用类型:它是架构特定的整数编码和字节顺序,即架构全局内存地址的大小。
DW_OP_deref 操作从栈中弹出一个值,将其视为全局内存地址,并使用通用类型读取该位置的内容。它将读取的值作为值及其关联的通用类型推入栈。
当求值到达表达式末尾时停止。在值结果类型上下文中求值的表达式的结果是栈的顶部元素,它提供值及其类型。
3.2.2 寄存器中的变量位置¶
此示例用于与 DIE 属性关联的操作表达式,该属性提供源语言变量的位置。此类属性指示表达式必须在提供位置结果类型的上下文中求值。
DWARF 根据位置描述定义对象的位置。
在此示例中,编译器已在架构寄存器 SGPR0 中分配了一个源语言变量。
指定变量位置的可能表达式是
DW_OP_regx SGPR0
DW_OP_regx 操作创建一个位置描述,该描述指定由操作数(SGPR0)指定的架构寄存器的位置。与值不同,位置描述不会被推入栈。相反,它们在概念上被放置在位置区域中。与值不同,位置描述没有关联的类型,它们仅表示对象基址的位置。
同样,当求值到达表达式末尾时停止。在位置结果类型上下文中求值的表达式的结果是位置区域中的位置描述。
3.2.3 内存中的变量位置¶
下一个示例用于与 DIE 属性关联的操作表达式,该属性提供在堆栈帧中分配的源语言变量的位置。编译器已将堆栈帧指针放置在架构寄存器 SGPR0 中,并将变量分配在相对于堆栈帧基址 0x10 的偏移量处。堆栈帧在全局内存中分配,因此 SGPR0 包含全局内存地址。
指定变量位置的可能表达式是
DW_OP_regval_type SGPR0 Generic
DW_OP_plus_uconst 0x10
与前面的示例一样,DW_OP_regval_type 操作将堆栈帧指针全局内存地址推入栈。通用类型是全局内存地址的大小。
DW_OP_plus_uconst 操作从栈中弹出一个值,该值必须具有整数编码的类型,加上其操作数的值,并将结果以相同的关联类型推回栈。在此示例中,它计算源语言变量的全局内存地址。
当求值到达表达式末尾时停止。如果求值的表达式具有位置结果类型上下文,并且位置区域为空,则栈顶元素必须是具有通用类型的值。该值被隐式地从栈中弹出,并被视为全局内存地址以创建全局内存位置描述,该描述被放置在位置区域中。表达式的结果是位置区域中的位置描述。
3.2.4 变量分布在不同的位置¶
此示例用于部分在寄存器中、部分未定义且部分在内存中的源变量。
DWARF 定义了可以具有一个或多个部分的复合位置描述。每个部分指定一个位置描述以及从中使用的字节数。以下操作表达式创建一个复合位置描述。
DW_OP_regx SGPR3
DW_OP_piece 4
DW_OP_piece 2
DW_OP_bregx SGPR0 0x10
DW_OP_piece 2
DW_OP_regx 操作在位置区域中创建一个寄存器位置描述。
第一个 DW_OP_piece 操作在位置区域中创建一个具有单个部分的不完整复合位置描述。位置区域中的位置描述用于定义该部分的开始,其大小由操作数指定,即 4 个字节。
后续的 DW_OP_piece 将新部分添加到位置区域中已有的不完整复合位置描述。这些部分形成一组连续的字节。如果位置区域中没有其他位置描述,并且栈上没有值,则该部分隐式地使用未定义的位置描述。同样,操作数指定该部分的大小(以字节为单位)。未定义的位置描述可用于指示已优化的部分。在本例中,为 2 个字节的未定义值。
DW_OP_bregx 操作读取由第一个操作数 (SGPR0) 指定的架构寄存器作为通用类型,加上第二个操作数 (0x10) 的值,并将该值推入栈。
下一个 DW_OP_piece 操作将另一个部分添加到已创建的不完整复合位置。
如果位置区域中没有其他位置,但栈上有值,则新部分是内存位置描述。使用的内存地址从栈中弹出。在本例中,操作数 2 表示来自内存的 2 个字节。
当求值到达表达式末尾时停止。如果求值的表达式具有位置结果类型上下文,并且位置区域具有不完整复合位置描述,则不完整复合位置描述将隐式转换为完整复合位置描述。表达式的结果是位置区域中的位置描述。
3.2.5 偏移复合位置¶
此示例尝试扩展前面的示例以偏移其创建的复合位置描述。3.2.3 内存中的变量位置 示例方便地使用了 DW_OP_plus 操作来偏移内存地址。
DW_OP_regx SGPR3
DW_OP_piece 4
DW_OP_piece 2
DW_OP_bregx SGPR0 0x10
DW_OP_piece 2
DW_OP_plus_uconst 5
但是,DW_OP_plus 不能用于偏移复合位置。它仅在栈上操作。
要偏移复合位置描述,编译器需要创建一个不同的复合位置描述,从与偏移量对应的部分开始。例如
DW_OP_piece 1
DW_OP_bregx SGPR0 0x10
DW_OP_piece 2
这说明栈值上的操作与位置描述上的操作不可组合。
3.2.6 指向成员的指针¶
注意:在不失一般性的前提下,本示例中使用的是 DWARF 4,因为所使用的工具版本中没有完全支持 DWARF 5。DWARF 5 的任何功能都不能解决此问题。
此示例突出显示了 DWARF 5 无法描述 C++ 指向成员的指针的使用语义。
DWARF 5 为描述指向成员的指针的使用提供的机制是 DW_AT_use_location
,它被定义为编码位置描述,该位置描述计算指向成员的指针所指向的成员的地址,给定指向成员的指针对象和包含对象的地址。
也就是说,当调试代理希望求值指向成员的指针访问操作时,它首先将两个值推入 DWARF 表达式栈
指向成员的指针对象
包含对象的地址
然后,它求值与指向成员的指针类型的 DW_AT_use_location
关联的位置描述,并将结果解释为指向成员的指针所指向的成员的地址。
考虑以下 C++ 源文件 s.cc
struct s {
int m;
int n;
};
int s::* p;
当使用 GCC 编译并使用 dwarfdump 检查时
$ g++ -gdwarf-5 -O3 -c s.cc
$ dwarfdump s.o
< 1><0x0000001e> DW_TAG_structure_type
DW_AT_name s
DW_AT_byte_size 0x00000008
DW_AT_sibling <0x0000003c>
< 2><0x00000029> DW_TAG_member
DW_AT_name m
DW_AT_type <0x0000003c>
DW_AT_data_member_location 0
< 2><0x00000032> DW_TAG_member
DW_AT_name n
DW_AT_type <0x0000003c>
DW_AT_data_member_location 4
< 1><0x0000003c> DW_TAG_base_type
DW_AT_byte_size 0x00000004
DW_AT_encoding DW_ATE_signed
DW_AT_name int
< 1><0x00000043> DW_TAG_ptr_to_member_type
DW_AT_containing_type <0x0000001e>
DW_AT_type <0x0000003c>
DW_AT_use_location len 0x0001: 22: DW_OP_plus
< 1><0x0000004e> DW_TAG_variable
DW_AT_name p
DW_AT_type <0x00000043>
DW_AT_external yes(1)
DW_AT_location len 0x0009: 030000000000000000: DW_OP_addr 0x00000000
请注意,DW_AT_use_location
的位置描述是 DW_OP_plus
,这反映了 GCC 将指向成员的指针实现为包含对象内的整数字节偏移量。例如,在此实现中,&s::m
的值为 offsetof(s, m)
,&s::n
的值为 offsetof(s, n)
struct s {
int m; // offsetof(s, m) == 0
int n; // offsetof(s, n) == 4
} o; // &o == 0xff00
int s::* p;
int *i;
p = &s::m; // p == 0
i = &(o.*p); // i == 0xff00 + 0
p = &s::n; // p == 4
i = &(o.*p); // i == 0xff00 + 4
只要整个包含对象驻留在默认地址空间中的内存中,表达式 DW_OP_plus
就可以准确地描述此实现。
但是,如果包含对象或指向的成员不在任何默认地址空间地址中,该怎么办?
编译器可以将包含对象存储在任何地址空间的内存中、寄存器中,在每次使用时重新计算其值,或者以任意方式组合这些方式。
现有 DWARF 5 表达式语言的丰富性反映了影响程序中对象位置的可能实现策略和优化选择的多样性,并且(模地址空间)它可以描述变量的所有这些位置。但是,当我们查看指向成员的指针的使用时,我们仅限于驻留在默认地址空间中连续内存块中的对象。
为了演示这个问题,考虑一个程序,GCC 选择对其进行优化,以便包含对象根本不在内存中
ptm.h
struct s {
int m;
int n;
};
void i(int);
extern int t;
void f(s x, int s::* p);
ptm.cc
#include "ptm.h"
void f(s x, int s::* p) {
for (int a = 0; a < t; ++a) {
x.m += a + x.n;
i(x.*p);
}
}
main.cc
#include "ptm.h"
int t = 100;
void i(int) {}
int main(int argc, char *argv[]) {
s x = { 0, 1 };
f(x, &s::m);
}
当在 GDB 下编译和运行时
$ g++-9 -gdwarf-4 -O3 -c main.cc -o main.o
$ g++-9 -gdwarf-4 -O3 -c ptm.cc -o ptm.o
$ g++-9 main.o ptm.o -o use_location.out
$ gdb ./use_location.out
(gdb) maint set dwarf always-disassemble
(gdb) b ptm.cc:5
Breakpoint 1 at 0x119e: file ptm.cc, line 5.
(gdb) r
Breakpoint 1, f (x=..., p=<optimized out>) at ptm.cc:5
5 i(x.*p);
请注意,编译器已将整个对象 x
提升到循环体内的寄存器 rdi
中
(gdb) info addr x
Symbol "x" is multi-location:
Range 0x555555555160-0x5555555551af: a complex DWARF expression:
0: DW_OP_reg5 [$rdi]
Range 0x5555555551af-0x5555555551ba: a complex DWARF expression:
0: DW_OP_fbreg -56
.
(gdb) p $pc
$1 = (void (*)(void)) 0x55555555519e <f(s, int s::*)+62>
因此,在这种情况下无法解释 DW_OP_use_location
(gdb) p x.*p
Address requested for identifier "x" which is in register $rdi
通过在栈上使用位置描述,可以通过将每个“地址”实例替换为“位置描述”来修改 DW_OP_use_location
的定义,如 类型条目 中所述。
为了实现此属性的完全通用版本,GCC 只需要将表达式从 DW_OP_plus
更改为 DW_OP_swap, DW_OP_LLVM_offset
。
3.2.7 虚基类¶
注意:在不失一般性的前提下,本示例中使用的是 DWARF 4,因为所使用的工具版本中没有完全支持 DWARF 5。DWARF 5 的任何功能都不能解决此问题。
此示例突出显示了 DWARF 5 无法描述 C++ 虚拟继承语义。
DWARF 5 为描述继承子对象的位置提供的机制是 DW_AT_data_member_location
。此属性被重载以描述数据成员位置和继承子对象位置,并且在每种情况下都有多种可能的格式
如果是整数常量形式,则它编码从派生对象到数据成员或子对象的字节偏移量。
否则,它编码位置描述以计算数据成员或子对象的地址,给定派生对象的地址。
此处仅考虑描述子对象的属性,以及仅位置描述形式。
在这种情况下,当调试代理希望定位子对象时,它首先将派生对象的地址推入 DWARF 表达式栈。然后,它求值与对应于继承子对象的 DW_TAG_inheritence
DIE 的 DW_AT_data_member_location
关联的位置描述。
考虑以下 C++ 源文件 ab.cc
class A {
public:
char x;
};
class B
: public virtual A {} o;
当使用 GCC 编译并使用 dwarfdump 检查时
$ g++ -gdwarf-5 -O3 -c ab.cc
$ dwarfdump ab.o
< 1><0x0000002a> DW_TAG_class_type
DW_AT_name A
DW_AT_byte_size 0x00000001
DW_AT_sibling <0x00000042>
< 1><0x00000049> DW_TAG_class_type
DW_AT_name B
DW_AT_byte_size 0x00000010
DW_AT_containing_type <0x00000049>
DW_AT_sibling <0x000000f9>
< 2><0x00000058> DW_TAG_inheritance
DW_AT_type <0x0000002a>
DW_AT_data_member_location len 0x0006: 1206481c0622:
DW_OP_dup DW_OP_deref DW_OP_lit24 DW_OP_minus DW_OP_deref DW_OP_plus
DW_AT_virtuality DW_VIRTUALITY_virtual
DW_AT_accessibility DW_ACCESS_public
此 DW_AT_data_member_location
表达式根据 Itanium ABI 描述了定位 B
中的 A
子对象的动态过程。类 B
的逻辑布局图如下:
0: class B
0: vptr B
8: class A
8: A::x
也就是说,类 B
的对象的地址等效于 B
的 vtable
指针的地址。由于 B
没有其他直接数据成员,因此类 A
的主基类子对象紧随其后,并且由于子对象对齐要求已满足,因此没有插入填充。
B
的 vtable
指针包含每个虚基类的条目 vbase_offset
。在这种情况下,该表的布局为
-24: vbase_offset[A]=8
-16: offset_to_top=0
-8: B RTTI
0: <vtable for B>
也就是说,为了找到 B
中的 A
子对象的 vbase_offset
,vptr B
的地址偏移了静态确定的值 -24
。
因此,为了实现 B
中的 A
的 DW_AT_data_member_location
,表达式需要通过 vptr B
索引到静态已知的字节偏移量 -24
,以查找 B
中的 A
的 vbase_offset
。然后,它必须将 B
的位置偏移 vbase_offset
的动态值(在本例中为 8
),以到达继承子对象的位置。
此定义与示例 3.2.6 共享相同的问题,因为它依赖于派生对象和继承子对象的地址,而不能保证它们中的任何一个或两个都具有任何地址。
为了演示这个问题,考虑一个程序,GCC 选择对其进行优化,以便派生对象根本不在内存中
f.h
class A {
public:
char x;
};
class B
: public virtual A {};
void f(B b);
f.cc
#include "f.h"
void f(B b) {}
main.cc
#include "f.h"
int main(int argc, char *argv[]) {
B b;
b.x = 42;
f(b);
return b.x;
}
当在 GDB 下编译和运行时
$ g++-9 -gdwarf-4 -O3 -c main.cc -o main.o
$ g++-9 -gdwarf-4 -O3 -c f.cc -o f.o
$ g++-9 main.o f.o -o cpp-vbase.out
(gdb) maint set dwarf always-disassemble
(gdb) b main.cc:6
Breakpoint 1 at 0x1090: file main.cc, line 6.
(gdb) r
Breakpoint 1, main (argc=<optimized out>, argv=<optimized out>) at main.cc:6
6 return b.x;
请注意,编译器已在 main()
的主体中省略了整个对象 x
的存储
(gdb) info addr b
Symbol "b" is multi-location:
Range 0x555555555078-0x5555555550af: a complex DWARF expression:
0: DW_OP_piece 8 (bytes)
2: DW_OP_const1u 42
4: DW_OP_stack_value
5: DW_OP_piece 1 (bytes)
7: DW_OP_piece 7 (bytes)
.
(gdb) p $pc
$1 = (void (*)(void)) 0x555555555090 <main(int, char**)+48>
因此,在这种情况下无法解释 DW_OP_data_member_location
(gdb) p b
$2 = {<A> = <invalid address>, _vptr.B = <optimized out>}
注意:
vptr B
应该占据对象b
的前 8 个字节,在 DWARF 中是未定义的,但可以被编译器描述为隐式值。此更改是微不足道的,并且会直接暴露此处描述的 DWARF 5 中的问题。
通过在栈上使用位置描述,可以通过将每个“地址”实例替换为“位置描述”来修改 DW_OP_data_member_location
的定义,如 A.5 类型条目 中所述。
为了实现此属性的完全通用版本,GCC 只需要将表达式中的最后一个操作从 DW_OP_plus
更改为 DW_OP_LLVM_offset
。
3.3 局限性¶
DWARF 5 无法描述运行时索引寄存器部分中的变量。这是描述位于 SIMT 向量寄存器通道中的源变量所必需的。
某些功能仅在位于全局内存中时才有效。类型属性表达式需要一个基本对象,该对象可以位于任何类型的位置。
DWARF 过程只能接受全局内存地址参数。这限制了分解涉及其他位置类型的地址创建的能力。
没有向量基本类型。这是描述向量寄存器所必需的。
没有操作可以在非全局地址空间中创建内存位置。只有解引用操作支持提供地址空间。
CFI 位置表达式不允许复合位置或非全局地址空间内存位置。在具有向量寄存器和地址空间的设备的优化代码中,这两者都是必需的。
位字段偏移量仅以有限的方式支持寄存器位置。以统一的方式为所有位置类型支持它们是支持具有位大小实体的语言所必需的。
4. 扩展解决方案¶
本节概述了对 DWARF 表达式求值模型的扩展,以允许在堆栈上操作位置描述。它提供了一些简化的示例,以演示其优势以及该扩展如何解决异构设备的问题。它介绍了如何以向后兼容 DWARF 5 的方式完成此操作。
4.1 位置描述¶
为了拥有对位置描述进行操作的一致且可组合的操作,此扩展定义了一种统一的方式来处理所有位置类型。这包括内存、寄存器、隐式、隐式指针、未定义和复合位置描述。
每种位置描述在概念上都是存储块中基于零的偏移量。存储块是具有一定字节数的连续线性组织(有关如何扩展以支持位大小存储,请参见下文)。
对于全局内存,存储块是体系结构地址大小的字节线性流。
对于每个独立的体系结构寄存器,存储块是该特定寄存器大小的字节线性流。
对于隐式位置,存储块是使用值的基本类型表示值时的字节线性流,该基本类型指定编码、大小和字节顺序。
对于未定义位置,存储块是无限大小的线性流,其中每个字节都未定义。
对于复合位置,存储块是由复合位置的各个部分定义的字节线性流。
4.2 堆栈位置描述操作¶
DWARF 表达式堆栈被扩展为允许每个堆栈条目是值或位置描述。
定义了求值规则以隐式地将作为值的堆栈元素转换为位置描述,反之亦然,以便所有 DWARF 5 表达式继续具有相同的语义。这反映了内存地址被有效地用作内存位置描述的代理。
对于允许指定 DWARF 表达式的每个位置,都定义了表达式是要作为值还是位置描述进行求值。
用于对内存地址进行操作的现有 DWARF 表达式操作被推广为可对任何位置描述类型进行操作。例如,DW_OP_deref 操作从堆栈中弹出位置描述而不是内存地址值,并从位置描述的偏移量开始读取与位置类型关联的存储。
创建位置描述的现有 DWARF 表达式操作被更改为在堆栈上弹出和推送位置描述。例如,DW_OP_value、DW_OP_regx、DW_OP_implicit_value、DW_OP_implicit_pointer、DW_OP_stack_value 和 DW_OP_piece。
可以添加对位置描述进行操作的新操作。例如,DW_OP_offset 操作可以修改堆栈顶部的位置描述的偏移量。与仅适用于内存地址的 DW_OP_plus 操作不同,DW_OP_offset 操作可以与任何位置类型一起使用。
为了允许增量和嵌套地创建复合位置描述,可以定义 DW_OP_piece_end 来显式指示复合位置的最后一部分。目前,创建复合位置必须始终是表达式的最后一个操作。
可以定义 DW_OP_undefined 操作,该操作显式创建未定义的位置描述。目前,这仅在堆栈为空时作为复合位置的一部分才有可能。
4.3 示例¶
本节提供了一些激励性示例,以说明允许堆栈上的位置描述所带来的好处。
4.3.1 源语言变量溢出到向量寄存器的一部分¶
为 GPU 生成代码的编译器可能会分配一个源语言变量,该变量经证明对于 SIMT 线程的每个通道都具有相同的值(在标量寄存器中)。然后,它可能需要溢出该标量寄存器。为了避免溢出到内存的高昂成本,它可以溢出到众多向量寄存器之一的固定通道。
以下表达式定义了源语言变量的位置,编译器将该变量分配在标量寄存器中,但在此代码点必须溢出到向量寄存器的通道 5。
DW_OP_regx VGPR0
DW_OP_offset_uconst 20
DW_OP_regx 在堆栈上推送寄存器位置描述。寄存器的存储是向量寄存器的大小。寄存器位置描述在概念上引用该存储,初始偏移量为 0。体系结构定义了寄存器的字节顺序。
DW_OP_offset_uconst 从堆栈中弹出一个位置描述,将其操作数值添加到偏移量,并将更新后的位置描述推回堆栈。在本例中,源语言变量被溢出到通道 5,每个通道的组件为 32 位(4 字节),因此偏移量为 5*4=20。
表达式求值的结果是堆栈顶部的位置描述。
另一种方法可能是目标为每个向量寄存器的每个部分定义不同的寄存器名称。但是,对于 GPU 来说,这并不实用,因为必须定义的寄存器数量非常庞大。它也不允许像下一个示例中所示的那样使用运行时索引到整个寄存器的一部分。
4.3.2 源语言变量分布在多个向量寄存器中¶
编译器可以为 GPU 生成 SIMT 代码。每个源语言执行线程都映射到 GPU 线程的单个通道。映射到寄存器的源语言变量映射到向量寄存器的通道组件,该向量寄存器对应于源语言的执行线程。
因此,必须在聚焦的源语言执行线程的上下文中执行此类变量的位置表达式。可以定义 DW_OP_push_lane 操作,以推送当前聚焦的源语言执行线程的通道的值。要使用的值将由 DWARF 的使用者在评估位置表达式时提供。
如果源语言变量大于向量寄存器通道组件的大小,则使用多个向量寄存器。每个源语言执行线程将仅使用与其关联的通道的向量寄存器组件。
以下表达式定义了必须占用两个向量寄存器的源语言变量的位置。创建了一个复合位置描述,该描述组合了两个部分。无论哪个通道对应于用户聚焦的源语言执行线程,它都将给出正确的结果。
DW_OP_regx VGPR0
DW_OP_push_lane
DW_OP_uconst 4
DW_OP_mul
DW_OP_offset
DW_OP_piece 4
DW_OP_regx VGPR1
DW_OP_push_lane
DW_OP_uconst 4
DW_OP_mul
DW_OP_offset
DW_OP_piece 4
DW_OP_regx VGPR0 推送第一个寄存器的位置描述。
DW_OP_push_lane; DW_OP_uconst 4; DW_OP_mul 计算聚焦通道的向量寄存器组件的偏移量,即通道号的 4 倍。
DW_OP_offset 将寄存器位置描述的偏移量调整为运行时计算的值。
DW_OP_piece 要么创建一个新的复合位置描述,要么将新部分添加到现有的不完整复合位置描述中。它弹出要用于新部分的位置描述。然后,如果下一个堆栈元素是不完整的复合位置描述,则弹出该元素;否则,它将创建一个没有部分的新不完整复合位置描述。最后,在添加新部分后,它会推送不完整的复合位置描述。
在本例中,寄存器位置描述被添加到新的不完整复合位置描述中。DW_OP_piece 的 4 指定了构成该部分的寄存器存储的大小。请注意,4 个字节从计算出的寄存器偏移量开始。
为了向后兼容,如果堆栈为空或顶部堆栈元素是不完整的复合位置,则未定义的位置描述将用于该部分。如果顶部堆栈元素是通用基本类型值,则将其隐式转换为全局内存位置描述,偏移量等于该值。
表达式的其余部分对 VGPR1 执行相同的操作。但是,当评估 DW_OP_piece 时,堆栈上存在一个不完整的复合位置。因此,VGPR1 寄存器位置描述将作为第二部分添加。
在表达式的末尾,如果顶部堆栈元素是不完整的复合位置描述,则将其转换为完整的位置描述并作为结果返回。
4.3.3 源语言变量分布在多种位置类型中¶
此示例与上一个示例相同,不同之处在于第二个向量寄存器的前 2 个字节已溢出到内存,最后 2 个字节已被证明是常量并被优化掉。
DW_OP_regx VGPR0
DW_OP_push_lane
DW_OP_uconst 4
DW_OP_mul
DW_OP_offset
DW_OP_piece 4
DW_OP_addr 0xbeef
DW_OP_piece 2
DW_OP_uconst 0xf00d
DW_OP_stack_value
DW_OP_piece 2
DW_OP_piece_end
前 6 个操作相同。
DW_OP_addr 操作在堆栈上推送全局内存位置描述,偏移量等于地址。
下一个 DW_OP_piece 将全局内存位置描述添加为复合位置的下一个 2 字节部分。
DW_OP_uconst 0xf00d; DW_OP_stack_value 在堆栈上推送隐式位置描述。隐式位置描述的存储是使用通用基本类型的编码、大小和字节顺序表示值 0xf00d。
最后的 DW_OP_piece 将隐式位置描述的 2 个字节添加为复合位置描述的第三部分。
DW_OP_piece_end 操作显式地将不完整的复合位置描述转换为完整的位置描述。这允许在堆栈上创建完整的复合位置描述,该描述可以用作后续操作的位置描述。例如,DW_OP_offset 可以应用于它。更实际的是,它允许在堆栈上创建多个复合位置描述,这些描述可用于使用 DW_OP_call* 操作将参数传递给 DWARF 过程。这可能有利于分解位置描述的增量创建。
4.3.4 地址空间¶
异构设备可以具有多个硬件支持的地址空间,这些地址空间使用特定的硬件指令来访问它们。
例如,使用 SIMT 执行的 GPU 可以提供硬件支持来访问内存,以便每个通道都可以看到线性内存视图,而实际的后备内存是以交错方式访问的,以便每个通道的第 N 个双字的位置是连续的。这最大限度地减少了 SIMT 执行读取的缓存行。
以下表达式定义了在当前子程序的堆栈帧中偏移量为 0x10 处分配的源语言变量的位置。子程序堆栈帧是按通道划分的,并且驻留在交错地址空间中。
DW_OP_regval_type SGPR0 Generic
DW_OP_uconst 1
DW_OP_form_aspace_address
DW_OP_offset 0x10
DW_OP_regval_type 操作将 SGPR0 的内容作为通用值推送。这是保存当前堆栈帧地址的寄存器。
DW_OP_uconst 操作推送地址空间编号。每个体系结构都在 DWARF 中定义了它使用的编号。在本例中,地址空间 1 用作按通道划分的内存。
DW_OP_form_aspace_address 操作弹出一个值和一个地址空间编号。每个地址空间都与一个单独的存储关联。推送一个内存位置描述,该描述引用地址空间的存储,偏移量为弹出的值。
所有对位置描述进行操作的操作都适用于内存位置,而与其地址空间无关。
每个体系结构都将地址空间 0 定义为默认的全局内存地址空间。
将内存位置描述推广为包括地址空间组件,避免了必须创建专门的操作来处理地址空间。
源变量在堆栈帧中的偏移量为 0x10。DW_OP_offset 操作适用于具有地址空间的内存位置描述,就像适用于任何其他类型的位置描述一样。
DWARF 5 中唯一采用地址空间的操作是 DW_OP_xderef*。它们将值视为指定地址空间中的地址,并读取其内容。没有操作可以实际创建引用地址空间的位置描述。没有办法将地址空间内存位置包含在复合位置的各个部分中。
由于 DW_OP_piece 现在可以采用任何类型的位置描述作为其部分,因此复合位置的各个部分现在可以涉及不同地址空间中的位置。例如,当分配在寄存器中的源变量的各个部分溢出到驻留在非全局地址空间中的堆栈帧时,可能会发生这种情况。
4.3.5 位偏移量¶
通过对堆栈上的位置描述进行推广,可以定义 DW_OP_bit_offset 操作,该操作以位而不是字节为单位调整任何类型位置的偏移量。偏移量可以是运行时计算的值。这通常对于任何支持位大小实体的源语言以及对于字节数不是整数的寄存器都很有用。
DWARF 5 仅在使用 DW_OP_bit_piece 的复合位置中支持位字段。它不支持位字段打包数组可能发生的运行时计算偏移量。它也不具有一般可组合性,因为它必须是表达式的最后一部分。
以下示例定义了源变量的位置描述,该源变量分配在寄存器的位 20 处开始的位置。如果源变量位于内存或特定地址空间内的位偏移量处,或者偏移量是运行时值,则可以使用类似的表达式。
DW_OP_regx SGPR3
DW_OP_uconst 20
DW_OP_bit_offset
DW_OP_bit_offset 操作从堆栈中弹出一个值和位置描述。它推送位置描述,并在使用该值作为位计数更新其偏移量后。
字节内的位顺序与字节顺序一样,由目标体系结构定义。可以扩展基本类型以指定位顺序以及字节顺序。
4.4 调用帧信息 (CFI)¶
DWARF 定义了调用帧信息 (CFI),该信息可用于虚拟地展开子程序调用堆栈。这涉及确定寄存器值溢出的位置。DWARF 5 将这些位置限制为寄存器或全局内存。如前面的示例所示,异构设备可能会将寄存器溢出到其他寄存器的各个部分、非全局内存地址空间,甚至是由不同位置类型组成的复合位置。
因此,此扩展扩展了 CFI 规则,以支持任何类型的位置描述,以及在地址空间中创建位置的操作。
4.5 不在字节对齐全局内存中的对象¶
DWARF 5 仅通过使用全局内存地址作为内存位置描述的代理,有效地支持堆栈上的字节对齐内存位置。对于定义 DWARF 表达式的属性来说,这是一个问题,这些属性需要不在字节对齐全局内存中分配的某些源语言实体的位置。
例如,DW_AT_data_member_location 属性的 DWARF 表达式在求值时,其初始堆栈包含类型实例对象的位置。该对象可能位于寄存器中、非全局内存地址空间中,由复合位置描述描述,甚至可能是隐式位置描述。
对于使用 DW_OP_push_object_address 操作的 DWARF 表达式,也存在类似的问题。此操作推送与定义表达式的属性关联的程序对象的位置。
允许堆栈上的任何类型的位置描述都允许使用 DW_OP_call* 操作来分解位置描述的创建。调用的输入和输出在堆栈上传递。例如,在 GPU 上,可以定义一个表达式来描述 SIMT 执行的非活动通道的有效 PC。这自然是通过组合每个嵌套控制流区域的表达式的结果来完成的。这可以通过使每个控制流区域都有自己的 DWARF 过程,然后从嵌套控制流区域的表达式中调用它来完成。另一种方法是使每个控制流区域都具有完整的表达式,这会导致更大的 DWARF,并且生成起来不太方便。
GPU 编译器努力将对象分配到更多数量的寄存器中以减少内存访问,它们必须使用不同的内存地址空间,并且它们执行优化,从而产生这些对象的复合位置。允许操作使用任何类型的位置描述可以创建支持所有这些的表达式。
对位字段和隐式位置的完全通用支持有利于任何目标上的优化。
4.6 高阶操作¶
推广允许以优雅的方式添加高阶操作,这些操作以通用的可组合方式从其他位置描述中创建位置描述。
例如,DW_OP_extend 操作可以从位置描述、元素大小和元素计数中创建复合位置描述。生成的复合位置将有效地成为元素计数个元素的向量,每个元素都是指定位大小的相同位置描述。
DW_OP_select_bit_piece 操作可以从两个位置描述、位掩码值和元素大小中创建复合位置描述。生成的复合位置将有效地成为元素向量,根据位掩码从两个输入位置之一中选择。
这些可以用于计算 SIMT 执行通道的有效 PC 的属性的表达式中。向量结果有效地一次计算每个 SIMT 通道的 PC。掩码可以是硬件执行掩码寄存器,该寄存器控制哪些 SIMT 通道正在执行。对于活动的散发通道,向量元素将是当前 PC,对于非活动的散发通道,PC 将对应于通道在逻辑上定位的源语言行。
类似地,可以定义 DW_OP_overlay_piece 操作,该操作从两个位置描述、偏移量值和大小中创建复合位置描述。生成的复合位置将由等效于其中一个位置描述的部分组成,但另一个位置描述替换了由偏移量和大小定义的切片。这可以用于有效地表达源语言数组,该数组在以 SIMD 方式执行一组循环迭代时已将一组元素提升为向量寄存器。
4.7 多个位置中的对象¶
编译器可以将源变量分配在堆栈帧内存中,但在某些代码范围内可能会将其提升为寄存器。如果生成的代码未更改寄存器值,则无需将其保存回内存。实际上,在该范围内,源变量同时位于内存和寄存器中。如果使用者(例如调试器)允许用户在该 PC 范围内更改源变量的值,则它需要更改这两个位置。
DWARF 5 支持 loclist,它可以指定源语言实体在不同 PC 位置位于不同的位置。它还可以表达源语言实体同时位于多个位置。
DWARF 5 分别定义了操作表达式和 loclist。一般来说,这是足够的,因为非内存位置描述只能作为表达式求值的最后一步来计算。
但是,允许堆栈上的位置描述允许在表达式求值中间使用非内存位置描述。例如,DW_OP_call* 和 DW_OP_implicit_pointer 操作可能会导致评估 DIE 的 DW_AT_location 属性的表达式。DW_AT_location 属性允许 loclist 形式。因此,结果可能包括多个位置描述。
类似地,与诸如 DW_AT_data_member_location 之类的属性关联的 DWARF 表达式(这些表达式在求值时,其初始堆栈包含位置描述)或使用 DW_OP_push_object_address 操作的 DWARF 操作表达式可能希望对另一个返回涉及多个位置的位置描述的表达式的结果进行操作。
因此,此扩展需要定义使用这些结果的表达式操作将如何表现。此扩展通过推广表达式堆栈来允许条目是一个或多个单个位置描述来做到这一点。通过这样做,它以自然的方式统一了 DWARF 操作表达式和 loclist 表达式的定义。
所有对位置描述进行操作的操作都扩展为可对多个单个位置描述进行操作。例如,DW_OP_offset 操作将偏移量添加到每个单个位置描述。DW_OP_deref* 操作仅读取其中一个单个位置描述的存储,因为多个单个位置描述必须都保存相同的值。类似地,如果 DWARF 表达式的求值导致多个单个位置描述,则使用者可以确保对所有位置描述执行任何更新,并且任何读取都可以使用其中任何一个位置描述。
5. 结论¶
DWARF 的优势在于,它通常寻求提供通用的可组合解决方案来解决许多问题,而不是仅解决一次性问题的解决方案。此扩展尝试遵循这一传统,通过定义向后兼容的可组合推广,可以解决一系列重要问题。它解决了异构计算设备的特定问题,为非异构设备提供了好处,并可以帮助解决许多其他先前报告的问题。
A. 对 DWARF 调试信息格式版本 5 的更改¶
注意:此附录提供了相对于 DWARF 版本 5 的更改。它被定义为向后兼容 DWARF 版本 5。非规范性文本以斜体显示。除非另有说明,否则节号通常与 DWARF 版本 5 标准中的节号相对应。给出定义是为了阐明现有表达式操作、CFI 操作和属性在支持多个位置的广义位置描述方面的行为方式。
注意:包含注释以描述如何将更改应用于 DWARF 版本 5 标准。它们还描述了可能需要进一步考虑的原理和问题。
A.2 一般描述¶
A.2.5 DWARF 表达式¶
注意:本节及其嵌套节取代了 DWARF 版本 5 的第 2.5 节和第 2.6 节。它基于现有 DWARF 版本 5 标准的文本。
DWARF 表达式描述了如何计算值或指定位置。
DWARF 表达式的求值可以提供对象的位置、数组边界的值、动态字符串的长度、所需的值本身等等。
如果 DWARF 表达式的求值没有遇到错误,则它可以产生值(请参阅2.5.2 DWARF 表达式值)或位置描述(请参阅2.5.3 DWARF 位置描述)。当评估 DWARF 表达式时,可以指定是否需要值或位置描述作为结果类型。
如果指定了结果类型,并且评估的结果与指定的结果类型不匹配,则如果有效,则执行2.5.4.4.3 内存位置描述操作中描述的隐式转换。否则,DWARF 表达式格式不正确。
如果 DWARF 表达式的求值遇到求值错误,则结果为求值错误。
注意:决定定义求值错误的概念。另一种方法是以类似于位置描述具有未定义位置描述的方式引入未定义的值基本类型。然后,遇到求值错误的操作可以返回未定义的位置描述或具有未定义基本类型的值。
所有对值进行操作的操作,如果给定未定义的值,都将返回未定义的实体。然后,表达式将始终评估完成,并且可以进行测试以确定它是否是未定义的实体。
但是,这将增加相当大的额外复杂性,并且与 GDB 在发生这些求值错误时抛出异常的情况不符。
如果 DWARF 表达式格式不正确,则结果未定义。
以下各节详细说明了 DWARF 表达式何时格式不正确或导致求值错误的规则。
DWARF 表达式可以编码为操作表达式(请参阅2.5.4 DWARF 操作表达式),也可以编码为位置列表表达式(请参阅2.5.5 DWARF 位置列表表达式)。
A.2.5.1 DWARF 表达式求值上下文¶
DWARF 表达式在上下文中求值,该上下文可以包括多个上下文元素。如果指定了多个上下文元素,则它们必须是自洽的,否则求值结果未定义。可以指定的上下文元素是
当前结果类型
DWARF 表达式求值所需的结果类型。如果指定,则可以是位置描述或值。
当前线程
用户呈现的表达式当前正在为其求值的源程序执行线程的目标体系结构线程标识符。
对于与目标体系结构线程相关的操作是必需的。
例如,
DW_OP_regval_type
操作。当前调用帧
目标体系结构调用帧标识符。它标识与当前线程中子程序的活动调用相对应的调用帧。它由其在调用堆栈上的地址标识。该地址称为规范帧地址 (CFA)。调用帧信息用于确定当前线程调用堆栈的调用帧的 CFA(请参阅6.4 调用帧信息)。
对于指定目标体系结构寄存器以支持调用堆栈的虚拟展开的操作是必需的。
例如,
DW_OP_*reg*
操作。如果指定,则它必须是当前线程中的活动调用帧。否则,结果未定义。
如果是当前正在执行的调用帧,则称为顶部调用帧。
当前程序位置
与当前线程的当前调用帧相对应的目标体系结构程序位置。
顶部调用帧的程序位置是当前线程的目标体系结构程序计数器。调用帧信息用于获取返回地址寄存器的值,以确定其他调用帧的程序位置(请参阅6.4 调用帧信息)。
对于评估位置列表表达式以在多个程序位置范围之间进行选择是必需的。对于指定目标体系结构寄存器以支持调用堆栈的虚拟展开的操作是必需的(请参阅6.4 调用帧信息)。
如果指定
如果当前调用帧是顶部调用帧,则它必须是当前目标体系结构程序位置。
如果当前调用帧 F 不是顶部调用帧,则它必须是与当前调用方帧 F 中调用被调用方帧的调用站点关联的程序位置。
否则,结果未定义。
当前编译单元
包含正在评估的 DWARF 表达式的编译单元调试信息条目。
对于引用与同一编译单元关联的调试信息的操作是必需的,包括指示此类引用是否使用 32 位或 64 位 DWARF 格式。如果未指定当前目标体系结构,它还可以提供默认地址空间地址大小。
例如,
DW_OP_constx
和DW_OP_addrx
操作。请注意,此编译单元可能与从与当前程序位置相对应的已加载代码对象确定的编译单元不同。例如,与
DW_OP_call*
操作的操作数的调试信息条目的DW_AT_location
属性关联的表达式 E 的评估使用包含 E 的编译单元进行评估,而不是包含DW_OP_call*
操作表达式的编译单元。当前目标体系结构
目标体系结构。
对于指定目标体系结构特定实体的操作是必需的。
例如,目标体系结构特定实体包括 DWARF 寄存器标识符、DWARF 地址空间标识符、默认地址空间和地址空间地址大小。
如果指定
如果指定了当前帧,则当前目标体系结构必须与当前帧的目标体系结构相同。
如果指定了当前帧并且是顶层帧,并且如果指定了当前线程,则当前目标架构必须与当前线程的目标架构相同。
如果指定了当前编译单元,则当前目标架构默认地址空间地址大小必须与当前编译单元头中的
address_size
字段以及.debug_aranges
节中任何关联条目中的字段相同。如果指定了当前程序位置,则当前目标架构必须与对应于当前程序位置的任何行号信息条目(参见 6.2 行号信息)的目标架构相同。
如果指定了当前程序位置,则当前目标架构默认地址空间地址大小必须与
.debug_addr
、.debug_line
、.debug_rnglists
、.debug_rnglists.dwo
、.debug_loclists
和.debug_loclists.dwo
节中对应于当前程序位置的任何条目头中的address_size
字段相同。否则,结果未定义。
当前对象
程序对象的位置描述。
对于
DW_OP_push_object_address
操作是必需的。例如,类型调试信息条目上的
DW_AT_data_location
属性在评估其关联表达式时,将对应于运行时描述符的程序对象指定为当前对象。如果位置描述无效(参见 2.5.3 DWARF 位置描述),则结果未定义。
初始栈
这是值或位置描述的列表,这些值或位置描述将在操作表达式评估开始之前,按照提供的顺序被压入操作表达式评估栈中。
某些调试器信息条目具有使用初始栈条目评估其 DWARF 表达式值的属性。在所有其他情况下,初始栈为空。
如果任何位置描述无效(参见 2.5.3 DWARF 位置描述),则结果未定义。
如果评估需要未指定的上下文元素,则评估结果为错误。
位置描述的 DWARF 表达式可能能够在没有线程、调用帧、程序位置或架构上下文的情况下进行评估。例如,全局变量的位置可能能够在没有此类上下文的情况下进行评估。如果表达式评估结果为错误,则可能表明变量已被优化,因此需要更多上下文。
调用帧信息(参见 6.4 调用帧信息)操作的 DWARF 表达式被限制为那些不需要指定编译单元上下文的操作。
如果与任何给定程序位置对应的 .debug_info
、.debug_addr
、.debug_line
、.debug_rnglists
、.debug_rnglists.dwo
、.debug_loclists
和 .debug_loclists.dwo
节中所有条目的头中的所有 address_size
字段不匹配,则 DWARF 格式不正确。
A.2.5.2 DWARF 表达式值¶
一个值具有类型和字面值。它可以表示目标架构任何受支持的基本类型的字面值。基本类型指定字面值的大小、编码和字节序。
注意:可能需要添加隐式指针基本类型编码。它将用于当
DW_OP_deref*
操作检索由DW_OP_implicit_pointer
操作创建的隐式指针位置存储的完整内容时生成的值的类型。字面值将记录由关联的DW_OP_implicit_pointer
操作指定的调试信息条目和字节位移。
存在一种称为通用类型的特殊基本类型,它是一种整数类型,其大小为目标架构默认地址空间中的地址大小,目标架构定义的字节序和未指定的符号。
通用类型与 DWARF 版本 4 及之前版本中定义的栈操作使用的未指定类型相同。
整数类型是一种基本类型,其编码为 DW_ATE_signed
、DW_ATE_signed_char
、DW_ATE_unsigned
、DW_ATE_unsigned_char
、DW_ATE_boolean
或任何目标架构定义的整数编码,范围包括 DW_ATE_lo_user
到 DW_ATE_hi_user
。
注意:目前尚不清楚
DW_ATE_address
是否为整数类型。GDB 似乎不认为它是整数类型。
A.2.5.3 DWARF 位置描述¶
调试信息必须为使用者提供一种方法来查找程序变量的位置,确定动态数组和字符串的边界,并可能找到子程序的调用帧的基地址或子程序的返回地址。此外,为了满足最新计算机架构和优化技术的需求,调试信息必须能够描述对象位置在其生命周期内发生变化的对象的位置,并且可能在对象生命周期的一部分时间内同时驻留在多个位置。
有关程序对象位置的信息由位置描述提供。
位置描述可以由一个或多个单位置描述组成。
单位置描述指定保存程序对象的位置存储以及程序对象开始的位置存储内的位置。位置存储内的位置表示为相对于位置存储起点的位偏移。
位置存储是可以保存值的线性位流。每个位置存储都有一个以位为单位的大小,并且可以使用基于零的位偏移进行访问。位置存储中位的顺序使用适用于目标架构上当前语言的位编号和方向约定。
位置存储有五种类型
内存位置存储
对应于目标架构内存地址空间。
寄存器位置存储
对应于目标架构寄存器。
隐式位置存储
对应于只能读取的固定值。
未定义位置存储
表示没有可用值,因此无法读取或写入。
复合位置存储
允许这些类型的混合,其中某些位来自一个位置存储,而另一些位来自另一个位置存储,或来自同一位置存储的不相交部分。
注意:最好添加
DW_OP_implicit_pointer
操作使用的隐式指针位置存储类型。它将指定操作提供的调试器信息条目和字节偏移。
位置描述是寻址规则的语言无关表示。
它们可以是评估调试信息条目属性的结果,该属性指定任意复杂度的操作表达式。在这种用法中,只要对象在其生命周期内是静态的,或者与其拥有的词法块(参见 3.5 词法块条目)的生命周期相同,并且在其生命周期内不移动,它们就可以描述对象的位置。
它们可以是评估调试信息条目属性的结果,该属性指定位置列表表达式。在这种用法中,它们可以描述生命周期有限、在其生命周期内更改其位置或在其部分或全部生命周期内具有多个位置的对象的位置。
如果位置描述具有多个单位置描述,则如果每个单位置描述的位置存储内的位置中保存的对象值与值不相同(未初始化的值部分除外),则 DWARF 表达式格式不正确。
具有多个单位置描述的位置描述只能由具有重叠程序位置范围的位置列表表达式或对具有多个单位置描述的位置描述执行操作的某些表达式操作创建。没有可以直接创建具有多个单位置描述的位置描述的操作表达式操作。
具有多个单位置描述的位置描述可用于描述同时驻留在多个存储块中的对象。由于优化,对象可能具有多个位置。例如,仅读取的值可以从内存提升到寄存器以用于某些代码区域,但稍后的代码可能会恢复从内存读取该值,因为寄存器可能用于其他目的。对于值在寄存器中的代码区域,对对象值的任何更改都必须在寄存器和内存中进行,以便两个代码区域都将读取更新后的值。
具有多个单位置描述的位置描述的使用者可以从任何单位置描述中读取对象的值(因为它们都引用具有相同值的位置存储),但必须将任何更改后的值写入所有单位置描述。
通过位偏移 B 更新位置描述 L 定义为将 B 的值添加到 L 的每个单位置描述 SL 的位偏移。如果任何 SL 的更新位偏移小于 0 或大于或等于 SL 指定的位置存储的大小,则会发生评估错误。
表达式的评估可能需要上下文元素来创建位置描述。如果访问此类位置描述,则其表示的存储是与创建位置描述时指定的上下文元素值关联的存储,这可能与访问时的上下文不同。
例如,创建寄存器位置描述需要线程上下文:位置存储用于该线程的指定寄存器。为地址空间创建内存位置描述可能需要线程上下文:位置存储是与该线程关联的内存。
如果创建位置描述所需的任何上下文元素发生更改,则位置描述将变为无效,并且访问它将是未定义的。
可能使位置描述无效的上下文示例包括
需要线程上下文,并且执行导致线程终止。
需要调用帧上下文,并且进一步执行导致调用帧返回到调用帧。
需要程序位置,并且线程的进一步执行发生。这可能会更改适用的位置列表条目或调用帧信息条目。
操作使用调用帧信息
虚拟调用帧展开中使用的任何帧返回。
使用顶层调用帧,程序位置用于选择调用帧信息条目,并且线程的进一步执行发生。
DWARF 表达式可用于计算对象的位置描述。后续的 DWARF 表达式评估可以将对象位置描述作为对象上下文或初始栈上下文来计算对象的组件。如果对象位置描述在两次表达式评估之间变为无效,则最终结果未定义。
线程的程序位置的更改可能不会使位置描述无效,但仍可能使其不再有意义。访问此类位置描述,或将其用作表达式评估的对象上下文或初始栈上下文,可能会产生未定义的结果。
例如,位置描述可能指定一个寄存器,该寄存器在程序位置更改后不再保存预期的程序对象。避免此类问题的一种方法是在线程的程序位置更改时重新计算与线程关联的位置描述。
A.2.5.4 DWARF 操作表达式¶
操作表达式由一系列操作组成,每个操作都包含一个操作码,后跟零个或多个操作数。操作数的数量由操作码暗示。
操作表示简单栈机器上的后缀操作。每个栈条目可以保存值或位置描述。操作可以作用于栈上的条目,包括添加条目和删除条目。如果栈条目的类型与操作所需的类型不匹配,并且不能隐式转换为所需的类型(参见 2.5.4.4.3 内存位置描述操作),则 DWARF 操作表达式格式不正确。
操作表达式的评估从空栈开始,初始栈提供的条目按照提供的顺序被压入空栈。然后评估操作,从流的第一个操作开始。评估持续进行,直到操作发生评估错误,或直到到达流的最后一个操作之后的一个位置。
评估结果是
如果操作发生评估错误,或者操作评估的表达式发生评估错误,则结果为评估错误。
如果当前结果类型指定位置描述,则
如果栈为空,则结果是具有一个未定义位置描述的位置描述。
此规则是为了向后兼容 DWARF 版本 5,后者为此目的使用空操作表达式。
如果顶层栈条目是位置描述,或者可以转换为位置描述(参见 2.5.4.4.3 内存位置描述操作),则结果是该位置描述(可能已转换)。栈上的任何其他条目都将被丢弃。
否则,DWARF 表达式格式不正确。
注意:可以将这种情况定义为返回隐式位置描述,就像执行了
DW_OP_implicit
操作一样。
如果当前结果类型指定值,则
如果顶层栈条目是值,或者可以转换为值(参见 2.5.4.4.3 内存位置描述操作),则结果是该值(可能已转换)。栈上的任何其他条目都将被丢弃。
否则,DWARF 表达式格式不正确。
如果未指定当前结果类型,则
如果栈为空,则结果是具有一个未定义位置描述的位置描述。
此规则是为了向后兼容 DWARF 版本 5,后者为此目的使用空操作表达式。
注意:此规则与请求位置描述时的上述规则一致。但是,GDB 似乎将此报告为错误,并且没有 GDB 测试似乎会导致这种情况下的空栈。
否则,返回顶层栈条目。栈上的任何其他条目都将被丢弃。
操作表达式编码为字节块,带有某种形式的前缀,用于指定字节计数。它可以用于
作为使用
exprloc
类(参见 7.5.5 类和形式)编码的调试信息条目属性的值,作为某些操作表达式操作的操作数,
作为某些调用帧信息操作的操作数(参见 6.4 调用帧信息),
以及在位置列表条目中(参见 2.5.5 DWARF 位置列表表达式)。
A.2.5.4.1 栈操作¶
注意:本节取代了 DWARF 版本 5 第 2.5.1.3 节。
以下操作操作 DWARF 栈。索引栈的操作假定栈顶(最近添加的条目)的索引为 0。它们允许栈条目为值或位置描述。
如果栈操作访问的任何栈条目是不完整的复合位置描述(参见 [2.5.4.4.6 复合位置描述操作] (#composite-location-description-operations)),则 DWARF 表达式格式不正确。
注意:这些操作现在支持作为值和位置描述的栈条目。
注意:如果也希望使它们与不完整的复合位置描述一起使用,则需要定义,当推送副本时,不完整的复合位置描述指定的复合位置存储也被复制。这确保了不完整的复合位置描述的每个副本都可以独立更新它们指定的复合位置存储。
DW_OP_dup
DW_OP_dup
复制栈顶的栈条目。DW_OP_drop
DW_OP_drop
弹出栈顶的栈条目并将其丢弃。DW_OP_pick
DW_OP_pick
具有单个无符号 1 字节操作数,该操作数表示索引 I。索引为 I 的栈条目的副本被压入栈。DW_OP_over
DW_OP_over
推送索引为 1 的条目的副本。这等效于
DW_OP_pick 1
操作。DW_OP_swap
DW_OP_swap
交换栈顶的两个栈条目。栈顶的条目变为第二个栈条目,第二个栈条目变为栈顶。DW_OP_rot
DW_OP_rot
旋转前三个栈条目。栈顶的条目变为第三个栈条目,第二个条目变为栈顶,第三个条目变为第二个条目。
说明许多这些栈操作的示例在附录 D.1.2 第 289 页中找到。
A.2.5.4.2 控制流操作¶
注意:本节取代了 DWARF 版本 5 第 2.5.1.5 节。
以下操作提供对 DWARF 操作表达式流的简单控制。
DW_OP_nop
DW_OP_nop
是占位符。它对 DWARF 栈条目没有影响。DW_OP_le
,DW_OP_ge
,DW_OP_eq
,DW_OP_lt
,DW_OP_gt
,DW_OP_ne
注意:与 DWARF 版本 5 第 2.5.1.5 节中相同。
DW_OP_skip
DW_OP_skip
是无条件分支。它的单个操作数是 2 字节有符号整数常量。2 字节常量是要从当前操作向前或向后跳过的 DWARF 表达式的字节数,从 2 字节常量之后开始。如果更新后的位置位于最后一个操作之后的一个位置,则操作表达式评估完成。
否则,如果更新后的操作位置不在第一个到最后一个操作(包括两者)的范围内,或者不在操作的开头,则 DWARF 表达式格式不正确。
DW_OP_bra
DW_OP_bra
是条件分支。它的单个操作数是 2 字节有符号整数常量。此操作弹出栈顶。如果弹出的值不是常量 0,则 2 字节常量操作数是要从当前操作向前或向后跳过的 DWARF 操作表达式的字节数,从 2 字节常量之后开始。如果更新后的位置位于最后一个操作之后的一个位置,则操作表达式评估完成。
否则,如果更新后的操作位置不在第一个到最后一个操作(包括两者)的范围内,或者不在操作的开头,则 DWARF 表达式格式不正确。
DW_OP_call2, DW_OP_call4, DW_OP_call_ref
DW_OP_call2
、DW_OP_call4
和DW_OP_call_ref
在 DWARF 操作表达式的评估期间执行 DWARF 过程调用。DW_OP_call2
和DW_OP_call4
具有一个操作数,分别是 2 字节或 4 字节无符号偏移 DR,表示调试信息条目 D 相对于当前编译单元开头的字节偏移。DW_OP_call_ref
具有一个操作数,该操作数是以 32 位 DWARF 格式的 4 字节无符号值或以 64 位 DWARF 格式的 8 字节无符号值,表示调试信息条目 D 相对于包含当前编译单元的.debug_info
节开头的字节偏移 DR。D 可能不在当前编译单元中。注意:DWARF 版本 5 指出 DR 可以是
.debug_info
节中的偏移量,而不是包含当前编译单元的节。它指出,从一个可执行文件或共享对象文件到另一个可执行文件或共享对象文件的引用重定位必须由使用者执行。但鉴于 DR 被定义为.debug_info
节中的偏移量,这似乎是不可能的。如果 DR 被定义为实现定义的值,则使用者可以选择以实现定义的方式解释该值,以引用另一个可执行文件或共享对象中的调试信息。在 ELF 中,
.debug_info
节位于非PT_LOAD
段中,因此无法使用标准动态重定位。但是,即使它们是加载的段并且使用了动态重定位,DR 也需要是 D 的地址,而不是.debug_info
节中的偏移量。这也需要 DR 是全局地址的大小。因此,在 64 位全局地址空间中不可能使用 32 位 DWARF 格式。此外,使用者需要确定重定位地址位于哪个可执行文件或共享对象中,以便它可以确定包含的编译单元。GDB 仅将 DR 解释为包含当前编译单元的
.debug_info
节中的偏移量。此注释也适用于
DW_OP_implicit_pointer
。DW_OP_call2
、DW_OP_call4
和DW_OP_call_ref
的操作数解释与DW_FORM_ref2
、DW_FORM_ref4
和DW_FORM_ref_addr
的操作数解释完全相同。调用操作通过以下方式评估:
如果 D 具有
DW_AT_location
属性,该属性编码为exprloc
,指定操作表达式 E,则当前操作表达式的执行从 E 的第一个操作继续。执行持续进行,直到到达 E 的最后一个操作之后的一个位置,此时执行继续执行调用操作之后的下一个操作。E 的操作在相同的当前上下文中评估,但当前编译单元是包含 D 的编译单元,并且栈与调用操作正在使用的栈相同。因此,在评估调用操作之后,栈与 E 的操作评估后留下的栈相同。由于 E 在与调用操作相同的栈上评估,因此 E 可以使用和/或删除栈上已有的条目,并且可以向栈添加新条目。调用时栈上的值可以用作被调用表达式的参数,而被调用表达式留在栈上的值可以用作调用表达式之间的先前约定的返回值。
如果 D 具有
DW_AT_location
属性,该属性编码为loclist
或loclistsptr
,则评估指定的位置列表表达式 E。E 的评估使用当前上下文,但结果类型是位置描述,编译单元是包含 D 的编译单元,初始栈为空。位置描述结果被压入栈。注意:此规则避免了在存在多个匹配项时,必须定义如何在与调用相同的栈上执行匹配的位置列表条目操作表达式。但是,它允许调用获取变量或形式参数的位置描述,这些变量或形式参数可以使用位置列表表达式。
另一种方法是将 D 具有
DW_AT_location
属性(该属性编码为loclist
或loclistsptr
)的情况,以及指定的位置列表表达式 E' 匹配具有操作表达式 E 的单个位置列表条目的情况,与exprloc
情况相同对待,并在相同的栈上进行评估。但这并不吸引人,因为如果属性是用于恰好以非单例栈结尾的变量,它将不会简单地将位置描述放在栈上。据推测,在变量或形式参数调试信息条目上使用
DW_OP_call*
的目的是仅将一个位置描述压入栈。该位置描述可能具有多个单位置描述。先前针对
exprloc
的规则也存在相同的问题,因为通常变量或形式参数位置表达式可能会在栈上留下多个条目,并且仅返回顶层条目。GDB 通过始终在相同的栈上执行 E 来实现
DW_OP_call*
。如果位置列表具有多个匹配的条目,它只会选择第一个并忽略其余的。这似乎从根本上与支持变量的多个位置的愿望相悖。因此,感觉
DW_OP_call*
应该既支持将变量或形式参数的位置描述压入栈,又支持能够在相同的栈上执行操作表达式。能够为不同的程序位置指定不同的操作表达式似乎是一个值得保留的特性。解决此问题的一种方法是为
DW_TAG_dwarf_procedure
调试信息条目提供一个不同的DW_AT_proc
属性。然后,DW_AT_location
属性表达式始终单独执行并推送位置描述(可能具有多个单位置描述),并且DW_AT_proc
属性表达式始终在相同的栈上执行,并且可以在栈上留下任何内容。DW_AT_proc
属性可以具有新的类exprproc
、loclistproc
和loclistsptrproc
,以指示表达式在相同的栈上执行。exprproc
的编码与exprloc
相同。loclistproc
和loclistsptrproc
的编码与其非proc
对等项相同,但如果位置列表与正好一个位置列表条目不匹配并且需要默认条目,则 DWARF 格式不正确。这些形式明确指示匹配的单个操作表达式必须在相同的栈上执行。这比loclistproc
和loclistsptrproc
的临时特殊规则更好,这些规则当前明确定义为始终返回位置描述。然后,生产者通过属性类明确指示意图。这样的更改对于 GDB 如何实现
DW_OP_call*
将是一个重大更改。但是,实际中是否真的发生了重大更改案例?GDB 可以为 DWARF 版本 5 实现当前方法,并为 DWARF 版本 6 实现新语义,这已为某些其他功能完成。另一种选择是将执行限制为仅在
DW_TAG_dwarf_procedure
调试信息条目的DW_AT_location
属性值的表达式 E 的评估中在相同的栈上进行。如果 E 是与正好一个位置列表条目不匹配的位置列表表达式,则 DWARF 格式将不正确。在所有其他情况下,作为DW_AT_location
属性值的表达式 E 的评估将在当前上下文中评估 E,但结果类型是位置描述,编译单元是包含 D 的编译单元,初始栈为空。位置描述结果被压入栈。如果 D 具有带有值 V 的
DW_AT_const_value
属性,则就好像执行了DW_OP_implicit_value V
操作一样。这允许使用调用操作来计算任何变量或形式参数的位置描述,无论生产者是否已将其优化为常量。这与
DW_OP_implicit_pointer
操作一致。注意:或者,可以弃用对作为常量的
DW_TAG_variable
和DW_TAG_formal_parameter
调试信息条目使用DW_AT_const_value
,而是使用DW_AT_location
以及导致具有一个隐式位置描述的位置描述的操作表达式。那么将不需要此规则。否则,不会产生任何影响,并且栈不会发生任何更改。
注意:在 DWARF 版本 5 中,如果 D 没有
DW_AT_location
,则DW_OP_call*
被定义为无效。目前尚不清楚这是否是正确的定义,因为生产者应该能够依赖使用DW_OP_call*
来获取任何非DW_TAG_dwarf_procedure
调试信息条目的位置描述。此外,生产者不应创建 DWARF,其中DW_OP_call*
指向不具有DW_AT_location
属性的DW_TAG_dwarf_procedure
。那么,这种情况是否应该定义为格式错误的 DWARF 表达式?
可以使用
DW_TAG_dwarf_procedure
调试信息条目来定义可以调用的 DWARF 过程。
A.2.5.4.3 值操作¶
本节描述将值压入堆栈的操作。
每个值堆栈条目都有一个类型和一个字面值。它可以表示目标架构任何受支持的基本类型的字面值。基本类型指定字面值的大小、编码和字节序。
值堆栈条目的基本类型可以是区分的通用类型。
A.2.5.4.3.1 字面值操作¶
注意:本节替换了 DWARF 版本 5 第 2.5.1.1 节。
以下操作都会将字面值压入 DWARF 堆栈。
除了 DW_OP_const_type
之外的操作会将具有通用类型的值 V 压入堆栈。如果 V 大于通用类型,则 V 将被截断为通用类型大小,并使用低位。
DW_OP_lit0
、DW_OP_lit1
、…、DW_OP_lit31
DW_OP_lit<N>
操作编码一个从 0 到 31(包括 0 和 31)的无符号字面值 N。它们将值 N 与通用类型一起压入堆栈。DW_OP_const1u
、DW_OP_const2u
、DW_OP_const4u
、DW_OP_const8u
DW_OP_const<N>u
操作具有单个操作数,该操作数分别是 1、2、4 或 8 字节的无符号整数常量 U。它们将值 U 与通用类型一起压入堆栈。DW_OP_const1s
、DW_OP_const2s
、DW_OP_const4s
、DW_OP_const8s
DW_OP_const<N>s
操作具有单个操作数,该操作数分别是 1、2、4 或 8 字节的有符号整数常量 S。它们将值 S 与通用类型一起压入堆栈。DW_OP_constu
DW_OP_constu
具有单个无符号 LEB128 整数操作数 N。它将值 N 与通用类型一起压入堆栈。DW_OP_consts
DW_OP_consts
具有单个有符号 LEB128 整数操作数 N。它将值 N 与通用类型一起压入堆栈。DW_OP_constx
DW_OP_constx
具有单个无符号 LEB128 整数操作数,该操作数表示相对于关联编译单元的DW_AT_addr_base
属性值,在.debug_addr
节中的从零开始的索引。.debug_addr
节中的值 N 具有通用类型的大小。它将值 N 与通用类型一起压入堆栈。DW_OP_constx
操作是为需要链接时重定位的常量提供的,但不应被消费者解释为可重定位地址(例如,线程本地存储的偏移量)。DW_OP_const_type
DW_OP_const_type
具有三个操作数。第一个是无符号 LEB128 整数 DR,表示相对于当前编译单元起始处的调试信息条目 D 的字节偏移量,该条目 D 提供常量值 T 的类型。第二个是 1 字节无符号整数常量 S。第三个是字节块 B,其长度等于 S。TS 是类型 T 的位大小。B 的最低有效 TS 位被解释为类型 D 的值 V。它将值 V 与类型 D 一起压入堆栈。
如果 D 不是当前编译单元中的
DW_TAG_base_type
调试信息条目,或者如果 TS 除以 8(字节大小)并向上舍入为整数不等于 S,则 DWARF 格式错误。虽然字节块 B 的大小可以从类型 D 定义中推断出来,但它被显式编码到操作中,以便可以轻松解析操作,而无需引用
.debug_info
节。
A.2.5.4.3.2 算术和逻辑运算¶
注意:本节与 DWARF 版本 5 第 2.5.1.4 节相同。
A.2.5.4.3.3 类型转换操作¶
注意:本节与 DWARF 版本 5 第 2.5.1.6 节相同。
A.2.5.4.3.4 特殊值操作¶
注意:本节替换了 DWARF 版本 5 第 2.5.1.2 节、2.5.1.3 节和 2.5.1.7 节的部分内容。
当前定义了以下特殊值操作
DW_OP_regval_type
DW_OP_regval_type
具有两个操作数。第一个是无符号 LEB128 整数,表示寄存器号 R。第二个是无符号 LEB128 整数 DR,表示相对于当前编译单元起始处的调试信息条目 D 的字节偏移量,该条目 D 提供寄存器值 T 的类型。该操作等效于执行
DW_OP_regx R; DW_OP_deref_type DR
。注意:DWARF 是否应该允许类型 T 大于寄存器 R 的大小?限制更大的位大小可以避免任何转换问题,因为寄存器的(可能被截断的)位内容只是被解释为 T 的值。如果需要转换,可以使用
DW_OP_convert
操作显式完成。GDB 具有每个寄存器的钩子,允许在每个寄存器的基础上进行目标特定的转换。它默认为截断较大的寄存器。删除目标钩子的使用不会导致常见架构中的任何测试失败。如果目标架构的编译器确实需要某种形式的转换,包括更大的结果类型,它始终可以显式使用
DW_OP_convert
操作。如果 T 是比寄存器大小更大的类型,则默认的 GDB 寄存器钩子从下一个寄存器读取字节(或者对于最后一个寄存器,则超出范围读取!)。删除目标钩子的使用不会导致常见架构中的任何测试失败(除了非法的手写汇编测试)。如果目标架构需要此行为,则这些扩展允许使用复合位置描述来组合多个寄存器。
DW_OP_deref
S 是通用类型的位大小除以 8(字节大小)并向上舍入为整数。DR 是当前编译单元中假设的调试信息条目 D 的偏移量,用于通用类型的基本类型。
该操作等效于执行
DW_OP_deref_type S, DR
。DW_OP_deref_size
DW_OP_deref_size
具有单个 1 字节无符号整数常量,表示字节结果大小 S。TS 是通用类型位大小和 S 乘以 8(字节大小)中的较小值。如果 TS 小于通用类型位大小,则 T 是位大小为 TS 的无符号整数类型,否则 T 是通用类型。DR 是当前编译单元中假设的调试信息条目 D 的偏移量,用于基本类型 T。
注意:当 S 大于通用类型时,截断值与 GDB 的做法相符。这允许通用类型大小不是整数字节大小。它确实允许 S 任意大。S 是否应该限制为通用类型的大小,并向上舍入为 8 的倍数?
该操作等效于执行
DW_OP_deref_type S, DR
,除非 T 不是通用类型,否则推送的值 V 将零扩展到通用类型位大小,并且其类型更改为通用类型。DW_OP_deref_type
DW_OP_deref_type
具有两个操作数。第一个是 1 字节无符号整数常量 S。第二个是无符号 LEB128 整数 DR,表示相对于当前编译单元起始处的调试信息条目 D 的字节偏移量,该条目 D 提供结果值 T 的类型。TS 是类型 T 的位大小。
虽然推送的值 V 的大小可以从类型 T 推断出来,但它被显式编码为操作数 S,以便可以轻松解析操作,而无需引用
.debug_info
节。注意:目前尚不清楚为什么需要操作数 S。与
DW_OP_const_type
不同,解析不需要大小。任何评估都需要获取基本类型 T 以与值一起推送,以了解其编码和位大小。它弹出一个堆栈条目,该条目必须是位置描述 L。
从 L 的单个位置描述 SL 之一指定的位置存储 LS 中检索 TS 位的值 V。
如果 L 或作为 L 子组件的任何复合位置描述部分的位置描述具有多个单个位置描述,则可以选择其中任何一个,因为它们都需要具有相同的值。对于任何单个位置描述 SL,位从关联的存储位置检索,从 SL 指定的位偏移量开始。对于复合位置描述,检索到的位是从每个复合位置部分 PL 连接的 N 位,其中 N 限制为 PL 的大小。
V 与类型 T 一起压入堆栈。
注意:如果 L 是寄存器位置描述,并且寄存器存储中剩余的位数少于 TS 位,则此定义使其成为评估错误。特别是由于这些扩展扩展了位置描述以具有位偏移量,因此将其定义为基于类型执行符号扩展或依赖于目标架构将是很奇怪的,因为剩余位数的数量可以是任意数字。这与 GDB 对
DW_OP_deref_type
的实现相符。这些扩展根据
DW_OP_regval_type
定义了DW_OP_*breg*
。DW_OP_regval_type
根据DW_OP_regx
(使用 0 位偏移量)和DW_OP_deref_type
定义。因此,它要求寄存器大小大于或等于地址空间的地址大小。这与 GDB 对DW_OP_*breg*
的实现相符。如果 D 不在当前编译单元中,D 不是
DW_TAG_base_type
调试信息条目,或者如果 TS 除以 8(字节大小)并向上舍入为整数不等于 S,则 DWARF 格式错误。注意:此定义允许基本类型为位大小,因为似乎没有理由限制它。
如果从未定义的存储位置检索到值的任何位,或者任何位的偏移量超过 L 的任何单个位置描述 SL 指定的位置存储 LS 的大小,则这是一个评估错误。
有关
DW_OP_implicit_pointer
操作创建的隐式位置描述的特殊规则,请参阅 2.5.4.4.5 隐式位置描述操作。DW_OP_xderef
DW_OP_xderef
弹出两个堆栈条目。第一个必须是表示地址 A 的整数类型值。第二个必须是表示目标架构特定地址空间标识符 AS 的整数类型值。地址大小 S 定义为与 AS 对应的目标架构特定地址空间的地址位大小。
A 通过必要时的零扩展调整为 S 位,然后将最低有效 S 位视为无符号值 A'。
它创建一个位置描述 L,其中包含一个内存位置描述 SL。SL 指定与 AS 对应的内存位置存储 LS,其位偏移量等于 A' 乘以 8(字节大小)。
如果 AS 是特定于上下文元素的地址空间,则 LS 对应于与当前上下文关联的位置存储。
例如,如果 AS 用于每个线程的存储,则 LS 是当前线程的位置存储。因此,如果 L 被操作访问,则访问在创建位置描述时选择的位置存储,而不是与访问操作的当前上下文关联的位置存储。
如果 AS 不是目标架构特定
DW_ASPACE_*
值定义的其中一个值,则 DWARF 表达式格式错误。该操作等效于弹出 A 和 AS,压入 L,然后执行
DW_OP_deref
。检索到的值 V 保留在堆栈上,类型为通用类型。DW_OP_xderef_size
DW_OP_xderef_size
具有单个 1 字节无符号整数常量,表示字节结果大小 S。它弹出两个堆栈条目。第一个必须是表示地址 A 的整数类型值。第二个必须是表示目标架构特定地址空间标识符 AS 的整数类型值。
它创建一个位置描述 L,如
DW_OP_xderef
所述。该操作等效于弹出 A 和 AS,压入 L,然后执行
DW_OP_deref_size S
。零扩展值 V 保留在堆栈上,类型为通用类型。DW_OP_xderef_type
DW_OP_xderef_type
具有两个操作数。第一个是 1 字节无符号整数常量 S。第二个操作数是无符号 LEB128 整数 DR,表示相对于当前编译单元起始处的调试信息条目 D 的字节偏移量,该条目 D 提供结果值 T 的类型。它弹出两个堆栈条目。第一个必须是表示地址 A 的整数类型值。第二个必须是表示目标架构特定地址空间标识符 AS 的整数类型值。
它创建一个位置描述 L,如
DW_OP_xderef
所述。该操作等效于弹出 A 和 AS,压入 L,然后执行
DW_OP_deref_type DR
。检索到的值 V 保留在堆栈上,类型为类型 T。DW_OP_entry_value
已弃用DW_OP_entry_value
将在调用帧的上下文中评估的表达式的值压入堆栈。它可用于确定进入当前调用帧时参数的值,前提是这些参数未被破坏。
它具有两个操作数。第一个是无符号 LEB128 整数 S。第二个是字节块,长度等于 S,解释为 DWARF 操作表达式 E。
E 在当前上下文中评估,但结果类型未指定,调用帧是调用当前帧的帧,程序位置是调用帧中的调用站点,对象未指定,初始堆栈为空。调用帧信息通过使用调用帧信息虚拟展开当前调用帧来获得(请参阅 6.4 调用帧信息)。
如果 E 的结果是位置描述 L(请参阅 2.5.4.4.4 寄存器位置描述操作),并且 E 执行的最后一个操作是寄存器 R 的
DW_OP_reg*
,其目标架构特定基本类型为 T,则检索寄存器的内容,就像执行了DW_OP_deref_type DR
操作一样,其中 DR 是当前编译单元中类型 T 的假设调试信息条目的偏移量。结果值 V 被压入堆栈。使用
DW_OP_reg*
为子程序入口处的值位于寄存器中的情况提供更紧凑的形式。注意:目前尚不清楚这如何提供更紧凑的表达式,因为可以使用
DW_OP_regval_type
,它只是略大一些。如果 E 的结果是值 V,则 V 被压入堆栈。
否则,DWARF 表达式格式错误。
DW_OP_entry_value
操作已弃用,因为它的主要用途已通过其他方式提供。DWARF 版本 5 为调用站点添加了DW_TAG_call_site_parameter
调试器信息条目,该条目具有DW_AT_call_value
、DW_AT_call_data_location
和DW_AT_call_data_value
属性,这些属性提供 DWARF 表达式来计算调用时的实际参数值,并要求生产者确保表达式即使在虚拟展开时也有效。注意:GDB 仅在 E 完全是
DW_OP_reg*
或DW_OP_breg*; DW_OP_deref*
时才实现DW_OP_entry_value
。
A.2.5.4.4 位置描述操作¶
本节描述将位置描述压入堆栈的操作。
A.2.5.4.4.1 通用位置描述操作¶
注意:本节替换了 DWARF 版本 5 第 2.5.1.3 节的部分内容。
DW_OP_push_object_address
DW_OP_push_object_address
将当前对象的位置描述 L 压入堆栈。此对象可能对应于作为正在评估的用户呈现表达式的一部分的独立变量。对象位置描述可以从变量自身的调试信息条目中确定,或者它可能是数组、结构或类的组件,其地址已在用户表达式评估期间的早期步骤中动态确定。
此操作提供了显式功能(特别是对于涉及描述符的数组),类似于在评估
DW_AT_data_member_location
以访问结构的数据成员之前,隐式压入结构的基本位置描述。注意:可以删除此操作,并将对象位置描述指定为初始堆栈,如
DW_AT_data_member_location
中所示。或者可以使用此操作,而不是需要指定初始堆栈。后一种方法更具可组合性,因为可能需要在表达式的任何点访问对象,并且将其作为初始堆栈传递需要整个表达式都知道它在堆栈上的位置。如果这样做,
DW_AT_use_location
将需要DW_OP_push_object2_address
操作来处理第二个对象。或者,更通用的方法是在表达式上下文中传入任意数量的参数和获取第 N 个参数的操作,例如
DW_OP_arg N
。然后,参数向量将在表达式上下文中传递,而不是初始堆栈。这也可以通过允许指定传入和返回的特定数量的参数来解决DW_OP_call*
的问题。然后,DW_OP_call*
操作始终可以在单独的堆栈上执行:参数的数量将在新的调用操作中指定,并从调用者的堆栈中获取,类似地,返回结果的数量将被指定,并在调用的表达式完成时从被调用堆栈复制回调用者堆栈。唯一指定当前对象的属性是
DW_AT_data_location
,因此非规范性文本似乎夸大了它的使用方式。或者,是否还有其他属性需要声明它们传递对象?
A.2.5.4.4.2 未定义位置描述操作¶
注意:本节替换了 DWARF 版本 5 第 2.6.1.1.1 节。
未定义的位置存储表示源代码中存在但在目标代码中不存在的对象的一部分或全部(可能是由于优化)。读取或写入未定义的位置存储都没有意义。
未定义的位置描述指定了未定义的位置存储。未定义的位置存储没有大小的概念,未定义的位置描述也没有位偏移量的概念。 DW_OP_*piece
操作可以隐式指定未定义的位置描述,允许指定任何大小和偏移量,并导致一个包含所有未定义位的部件。
A.2.5.4.4.3 内存位置描述操作¶
注意:本节替换了 DWARF 版本 5 第 2.5.1.1 节、2.5.1.2 节、2.5.1.3 节和 2.6.1.1.2 节的部分内容。
每个目标架构特定的地址空间都有一个对应的内存位置存储,它表示该地址空间的线性可寻址内存。每个内存位置存储的大小对应于相应地址空间中地址的范围。
目标架构定义了地址空间位置存储如何映射到目标架构物理内存。例如,它们可能是独立的内存,或者多个位置存储可能以不同的偏移量和不同的交错方式别名相同的物理内存。映射也可能由源语言地址类决定。
内存位置描述指定内存位置存储。位偏移量对应于内存字节内的位位置。使用内存位置描述访问的位,访问从位偏移量指定的字节内的位位置开始的相应目标架构内存。
位偏移量是 8(字节大小)的倍数的内存位置描述被定义为字节地址内存位置描述。它具有内存字节地址 A,该地址等于位偏移量除以 8。
位偏移量不是 8(字节大小)的倍数的内存位置描述被定义为位字段内存位置描述。它具有位位置 B,等于位偏移量模 8,以及内存字节地址 A,等于位偏移量减去 B,然后除以 8。
内存位置描述的地址空间 AS 被定义为与内存位置描述关联的内存位置存储对应的地址空间。
由一个字节地址内存位置描述 SL 组成的位置描述被定义为内存字节地址位置描述。它具有字节地址,等于 A,地址空间,等于对应 SL 的 AS。
DW_ASPACE_none
被定义为目标架构默认地址空间。
如果堆栈条目需要是位置描述,但它是一个具有通用类型的值 V,则它会被隐式转换为位置描述 L,其中包含一个内存位置描述 SL。SL 指定与目标架构默认地址空间对应的内存位置存储,其位偏移量等于 V 乘以 8(字节大小)。
注意:如果希望允许任何整数类型值隐式转换为目标架构默认地址空间中的内存位置描述
如果堆栈条目需要是位置描述,但它是一个具有整数类型的值 V,则它会被隐式转换为位置描述 L,其中包含一个内存位置描述 SL。如果 V 的类型大小小于通用类型大小,则值 V 将零扩展到通用类型的大小。最低有效通用类型大小位被视为无符号值,用作地址 A。SL 指定与目标架构默认地址空间对应的内存位置存储,其位偏移量等于 A 乘以 8(字节大小)。
隐式转换也可以定义为目标架构特定的。例如,GDB 检查 V 是否为整数类型。如果不是,则会给出错误。否则,GDB 将 V 零扩展到 64 位。如果 GDB 目标定义了钩子函数,则会调用它。目标特定的钩子函数可以修改 64 位值,可能基于原始值类型进行符号扩展。最后,GDB 将 64 位值 V 视为内存位置地址。
如果堆栈条目需要是位置描述,但它是一个具有目标架构默认地址空间的隐式指针值 IPV,则它会被隐式转换为位置描述,其中包含一个由 IPV 指定的单个位置描述。请参阅 2.5.4.4.5 隐式位置描述操作。
如果堆栈条目需要是值,但它是一个位置描述 L,其中包含一个内存位置描述 SL,该 SL 位于目标架构默认地址空间中,且位偏移量 B 是 8 的倍数,则它会被隐式转换为一个值,该值等于 B 除以 8(字节大小),并且具有通用类型。
DW_OP_addr
DW_OP_addr
具有一个单字节常量值操作数,其大小为通用类型的大小,表示地址 A。它在堆栈上推送一个位置描述 L,其中包含一个内存位置描述 SL。SL 指定与目标架构默认地址空间对应的内存位置存储,其位偏移量等于 A 乘以 8(字节大小)。
如果 DWARF 是代码对象的一部分,则 A 可能需要重定位。例如,在 ELF 代码对象格式中,A 必须通过 ELF 段虚拟地址与段加载时的虚拟地址之间的差值进行调整。
DW_OP_addrx
DW_OP_addrx
具有一个无符号 LEB128 整数操作数,该操作数表示相对于关联编译单元的DW_AT_addr_base
属性值的.debug_addr
section 的从零开始的索引。.debug_addr
section 中的地址值 A 的大小为通用类型的大小。它在堆栈上推送一个位置描述 L,其中包含一个内存位置描述 SL。SL 指定与目标架构默认地址空间对应的内存位置存储,其位偏移量等于 A 乘以 8(字节大小)。
如果 DWARF 是代码对象的一部分,则 A 可能需要重定位。例如,在 ELF 代码对象格式中,A 必须通过 ELF 段虚拟地址与段加载时的虚拟地址之间的差值进行调整。
DW_OP_form_tls_address
DW_OP_form_tls_address
弹出堆栈中的一个条目,该条目必须是整数类型值,并将其视为线程本地存储地址 TA。它在堆栈上推送一个位置描述 L,其中包含一个内存位置描述 SL。SL 是特定于目标架构的内存位置描述,它对应于线程本地存储地址 TA。
线程本地存储地址 TA 的含义由运行时环境定义。如果运行时环境支持单个线程的多个线程本地存储块,则使用与包含此 DWARF 表达式的可执行文件或共享库相对应的块。
C、C++、Fortran 和其他语言的一些实现支持线程本地存储类。具有此存储类的变量在不同的线程中具有不同的值和地址,就像自动变量在每个子程序调用中具有不同的值和地址一样。通常,有一个存储块包含在主可执行文件中声明的所有线程本地变量,以及每个共享库中声明的变量的单独块。然后可以使用标识符在其块中访问每个线程本地变量。此标识符通常是块中的字节偏移量,并在
DW_OP_form_tls_address
操作之前,通过DW_OP_const*
操作之一推送到 DWARF 堆栈上。计算适当块的地址可能很复杂(在某些情况下,编译器会发出函数调用来执行此操作),并且很难使用普通的 DWARF 位置描述来描述。与其将复杂的线程本地存储计算强制放入 DWARF 表达式中,不如使用DW_OP_form_tls_address
允许使用者根据特定于目标架构的运行时环境执行计算。DW_OP_call_frame_cfa
DW_OP_call_frame_cfa
推送当前子程序的规范帧地址 (CFA) 的位置描述 L,该地址从堆栈上的调用帧信息中获得。请参阅 6.4 调用帧信息。尽管可以使用位置列表表达式计算与当前子程序对应的调试信息条目的
DW_AT_frame_base
属性的值,但在某些情况下,这将需要广泛的位置列表,因为用于计算 CFA 的寄存器的值在子程序执行期间会发生变化。如果调用帧信息存在,则它已经编码了这些更改,并且使用DW_OP_call_frame_cfa
操作引用它是节省空间的。DW_OP_fbreg
DW_OP_fbreg
具有一个有符号 LEB128 整数操作数,该操作数表示字节位移 B。当前子程序的帧基址的位置描述 L 从与当前子程序对应的调试信息条目的
DW_AT_frame_base
属性中获得,如 3.3.5 低级信息 中所述。位置描述 L 通过位偏移量 B 乘以 8(字节大小)进行更新,并推送到堆栈上。
DW_OP_breg0
、DW_OP_breg1
、 …、DW_OP_breg31
DW_OP_breg<N>
操作编码最多 32 个寄存器的编号,编号从 0 到 31(包括 0 和 31)。寄存器号 R 对应于操作名称中的 N。它们具有一个有符号 LEB128 整数操作数,该操作数表示字节位移 B。
地址空间标识符 AS 定义为与目标架构特定默认地址空间对应的标识符。
地址大小 S 定义为与 AS 对应的目标架构特定地址空间的地址位大小。
检索由 R 指定的寄存器的内容,就好像执行了
DW_OP_regval_type R, DR
操作一样,其中 DR 是当前编译单元中无符号整数基本类型(大小为 S 位)的假设调试信息条目的偏移量。B 被加上,并且最低有效 S 位被视为要用作地址 A 的无符号值。它们在堆栈上推送一个位置描述 L,该位置描述 L 包含一个内存位置描述 LS。LS 指定与 AS 对应的内存位置存储,其位偏移量等于 A 乘以 8(字节大小)。
DW_OP_bregx
DW_OP_bregx
具有两个操作数。第一个是无符号 LEB128 整数,表示寄存器号 R。第二个是有符号 LEB128 整数,表示字节位移 B。该操作与
DW_OP_breg<N>
的操作相同,不同之处在于 R 用作寄存器号,B 用作字节位移。
A.2.5.4.4.4 寄存器位置描述操作¶
注意:本节替换 DWARF 版本 5 第 2.6.1.1.3 节。
存在一个寄存器位置存储,它对应于每个目标架构寄存器。每个寄存器位置存储的大小对应于相应目标架构寄存器的大小。
寄存器位置描述指定寄存器位置存储。位偏移量对应于寄存器内的位位置。使用寄存器位置描述访问的位访问从指定的位偏移量开始的相应目标架构寄存器。
DW_OP_reg0
、DW_OP_reg1
、 …、DW_OP_reg31
DW_OP_reg<N>
操作编码最多 32 个寄存器的编号,编号从 0 到 31(包括 0 和 31)。目标架构寄存器号 R 对应于操作名称中的 N。该操作等效于执行
DW_OP_regx R
。DW_OP_regx
DW_OP_regx
具有一个无符号 LEB128 整数操作数,该操作数表示目标架构寄存器号 R。如果当前调用帧是顶层调用帧,则它会推送一个位置描述 L,该位置描述 L 指定堆栈上的一个寄存器位置描述 SL。SL 指定对应于 R 的寄存器位置存储,当前线程的位偏移量为 0。
如果当前调用帧不是顶层调用帧,则使用调用帧信息(请参阅 6.4 调用帧信息)来确定保存当前调用帧和当前线程的当前程序位置的寄存器的位置描述。推送生成的位置描述 L。
请注意,如果使用调用帧信息,则生成的位置描述可能是寄存器、内存或未定义的。
实现可以立即评估调用帧信息,也可以延迟评估,直到操作访问 L 为止。如果延迟评估,则可以将 R 和当前上下文记录在 L 中。访问时,将使用记录的上下文来评估调用帧信息,而不是访问操作的当前上下文。
这些操作获取寄存器位置。要获取寄存器的内容,必须使用 DW_OP_regval_type
,使用 DW_OP_breg*
基于寄存器的寻址操作之一,或者在寄存器位置描述上使用 DW_OP_deref*
。
A.2.5.4.4.5 隐式位置描述操作¶
注意:本节替换 DWARF 版本 5 第 2.6.1.1.4 节。
隐式位置存储表示对象的一部分或全部,该对象在程序中没有实际位置,但其内容仍然是已知的,可以是常量,也可以从程序中的其他位置和值计算得出。
隐式位置描述指定隐式位置存储。位偏移量对应于隐式位置存储中的位位置。使用隐式位置描述访问的位访问从位偏移量开始的相应隐式存储值。
DW_OP_implicit_value
DW_OP_implicit_value
具有两个操作数。第一个是无符号 LEB128 整数,表示字节大小 S。第二个是字节块,其长度等于 S,被视为字面值 V。创建一个隐式位置存储 LS,其字面值 V 和大小为 S。
它在堆栈上推送位置描述 L,其中包含一个隐式位置描述 SL。SL 指定位偏移量为 0 的 LS。
DW_OP_stack_value
DW_OP_stack_value
弹出堆栈中的一个条目,该条目必须是值 V。创建一个隐式位置存储 LS,其字面值 V 使用 V 的基本类型指定的大小、编码和字节序。
它在堆栈上推送位置描述 L,其中包含一个隐式位置描述 SL。SL 指定位偏移量为 0 的 LS。
DW_OP_stack_value
操作指定对象在内存中不存在,但其值仍然是已知的。在这种形式中,位置描述指定对象的实际值,而不是指定保存该值的内存或寄存器存储。有关由
DW_OP_implicit_pointer
操作创建的隐式位置描述取消引用产生的隐式指针值的特殊规则,请参阅DW_OP_implicit_pointer
(以下)。注意:由于位置描述允许在堆栈上,因此
DW_OP_stack_value
操作不再像 DWARF 版本 5 中那样终止 DWARF 操作表达式的执行。DW_OP_implicit_pointer
优化编译器可能会消除指针,同时仍然保留指针寻址的值。
DW_OP_implicit_pointer
允许生产者描述此值。DW_OP_implicit_pointer
指定对象是指向目标架构默认地址空间的指针,即使它指向的值可以描述,也不能表示为真实指针。在这种形式中,位置描述指定一个调试信息条目,该条目表示指针将指向的对象的实际位置描述。因此,调试信息的消费者将能够访问取消引用的指针,即使它无法访问指针本身。DW_OP_implicit_pointer
具有两个操作数。第一个操作数是在 32 位 DWARF 格式中为 4 字节无符号值,或在 64 位 DWARF 格式中为 8 字节无符号值,表示相对于包含当前编译单元的.debug_info
section 的开始处的调试信息条目 D 的字节偏移量 DR。第二个操作数是有符号 LEB128 整数,表示字节位移 B。请注意,D 可能不在当前编译单元中。
第一个操作数的解释与
DW_FORM_ref_addr
的解释完全相同。地址空间标识符 AS 定义为与目标架构特定默认地址空间对应的标识符。
地址大小 S 定义为与 AS 对应的目标架构特定地址空间的地址位大小。
创建一个隐式位置存储 LS,其调试信息条目为 D,地址空间为 AS,大小为 S。
它在堆栈上推送位置描述 L,该位置描述 L 包含一个隐式位置描述 SL。SL 指定位偏移量为 0 的 LS。
如果
DW_OP_deref*
操作弹出一个位置描述 L' 并检索 S 位,使得任何检索到的位都来自与 LS 相同的隐式位置存储,则这是一个评估错误,除非满足以下两个条件所有检索到的位都来自一个隐式位置描述,该描述引用与 LS 相同的隐式位置存储。
请注意,所有位不必来自相同的隐式位置描述,因为 L' 可能涉及复合位置描述。
这些位来自其各自隐式位置存储中的连续递增偏移量。
这些规则等效于检索 LS 的完整内容。
如果满足上述两个条件,则
DW_OP_deref*
操作推送的值 V 是一个隐式指针值 IPV,其目标架构特定地址空间为 AS,调试信息条目为 D,基本类型为 T。如果 AS 是目标架构默认地址空间,则 T 是通用类型。否则,T 是目标架构特定整数类型,其位大小等于 S。如果 IPV 被隐式转换为位置描述(仅当 AS 是目标架构默认地址空间时才执行),则生成的位置描述 RL 为
如果 D 具有
DW_AT_location
属性,则DW_AT_location
属性中的 DWARF 表达式 E 将在当前上下文中进行评估,但结果类型是位置描述,编译单元是包含 D 的编译单元,对象未指定,初始堆栈为空。RL 是表达式结果。请注意,E 是在访问 IPV 的表达式的上下文中评估的,而不是在包含创建 L 的
DW_OP_implicit_pointer
操作的表达式的上下文中评估的。如果 D 具有
DW_AT_const_value
属性,则从DW_AT_const_value
属性的值创建一个隐式位置存储 RLS,其大小与DW_AT_const_value
属性的值的大小匹配。RL 包含一个隐式位置描述 SRL。SRL 指定位偏移量为 0 的 RLS。注意:如果使用
DW_AT_const_value
用于变量和形式参数已被弃用,而是使用DW_AT_location
与隐式位置描述,则不需要此规则。否则,这是一个评估错误。
位置描述 RL 通过位偏移量 B 乘以 8(字节大小)进行更新。
如果
DW_OP_stack_value
操作弹出一个与 IPV 相同的值,则它会推送与 L 相同的位置描述。以任何其他方式访问 LS 或 IPV 都是评估错误。
对
DW_OP_implicit_pointer
创建的隐式指针位置描述的使用方式的限制是为了简化 DWARF 消费者。对于由DW_OP_deref*
和DW_OP_stack_value
创建的隐式指针值也是如此。
通常,DW_OP_implicit_pointer
操作用于 DW_TAG_variable
或 DW_TAG_formal_parameter
调试信息条目 D1 的 DW_AT_location
属性的 DWARF 表达式 E1 中。DW_OP_implicit_pointer
操作引用的调试信息条目通常是 DW_TAG_variable
或 DW_TAG_formal_parameter
调试信息条目 D2 本身,其 DW_AT_location
属性给出第二个 DWARF 表达式 E2。
D1 和 E1 描述指针类型对象的位置。D2 和 E2 描述该指针对象指向的对象的位置。
但是,D2 可以是任何包含 DW_AT_location
或 DW_AT_const_value
属性的调试信息条目(例如,DW_TAG_dwarf_procedure
)。通过使用 E2,当要求使用者取消引用 E1 描述的指针(包含 DW_OP_implicit_pointer
操作)时,使用者可以重建对象的值。
A.2.5.4.4.6 复合位置描述操作¶
注意:本节替换 DWARF 版本 5 第 2.6.1.2 节。
复合位置存储表示对象或值,该对象或值可能包含在另一个位置存储的一部分中,或包含在多个位置存储的部分中。
每个部分都有一个部分位置描述 L 和一个部分位大小 S。L 可以有一个或多个单个位置描述 SL。如果有多个 SL,则表示该部分位于多个位置。每个部分的位置的位包含来自 SL 指定的位置存储 LS 的 S 个连续位,从 SL 指定的位偏移量开始。所有位都必须在 LS 的大小范围内,否则 DWARF 表达式格式不正确。
复合位置存储可以有零个或多个部分。这些部分是连续的,因此从零开始的位置存储位索引将在每个部分上范围内,它们之间没有间隙。因此,复合位置存储的大小是其各部分大小的总和。如果连续位置存储的大小大于与最大目标架构特定地址空间对应的内存位置存储的大小,则 DWARF 表达式格式不正确。
复合位置描述指定复合位置存储。位偏移量对应于复合位置存储中的位位置。
有一些操作可以创建复合位置存储。
还有其他操作允许逐步创建复合位置存储。每个部分由单独的操作创建。可能有一个或多个操作来创建最终的复合位置存储。一系列这样的操作描述了复合位置存储的各个部分,这些部分按照关联部分操作的执行顺序排列。
为了支持逐步创建,复合位置存储可以处于不完整状态。当增量操作作用于不完整的复合位置存储时,它会添加一个新的部分。
指定不完整复合位置存储的复合位置描述称为不完整复合位置描述。指定完整复合位置存储的复合位置描述称为完整复合位置描述。
如果在操作表达式执行完成后,顶层堆栈条目是一个位置描述,该位置描述具有一个不完整的复合位置描述 SL,则 SL 将转换为完整的复合位置描述。
请注意,在由 DW_OP_call*
操作在同一堆栈上评估的操作表达式完成后,不会发生此转换。此类执行不是操作表达式的单独评估,而是包含 DW_OP_call*
操作的同一操作表达式的持续评估。
如果堆栈条目需要是位置描述 L,但 L 具有不完整的复合位置描述,则 DWARF 表达式格式不正确。例外情况是用于逐步创建复合位置描述的操作,如下所述。
请注意,DWARF 操作表达式可以任意组合来自任何其他位置描述的复合位置描述,包括那些具有多个单个位置描述的位置描述,以及那些具有复合位置描述的位置描述。
增量复合位置描述操作被定义为与 DWARF 版本 5 中的定义兼容。
DW_OP_piece
DW_OP_piece
具有一个无符号 LEB128 整数,表示字节大小 S。该操作基于上下文:
如果堆栈为空,则在堆栈上推送一个位置描述 L,该位置描述 L 由一个不完整的复合位置描述 SL 组成。
创建一个不完整的复合位置存储 LS,其中包含一个部分 P。P 指定位置描述 PL,并且位大小为 S 乘以 8(字节大小)。PL 由一个未定义的位置描述 PSL 组成。
SL 指定位偏移量为 0 的 LS。
否则,如果顶层堆栈条目是一个位置描述 L,该位置描述 L 由一个不完整的复合位置描述 SL 组成,则 SL 指定的不完整复合位置存储 LS 将被更新以附加一个新部分 P。P 指定位置描述 PL,并且位大小为 S 乘以 8(字节大小)。PL 由一个未定义的位置描述 PSL 组成。L 留在堆栈上。
否则,如果顶层堆栈条目是一个位置描述或可以转换为位置描述,则将其弹出并视为部分位置描述 PL。然后:
如果顶层堆栈条目(在弹出 PL 之后)是一个位置描述 L,该位置描述 L 由一个不完整的复合位置描述 SL 组成,则 SL 指定的不完整的复合位置存储 LS 将被更新以附加一个新部分 P。P 指定位置描述 PL,并且位大小为 S 乘以 8(字节大小)。L 留在堆栈上。
否则,在堆栈上推送一个位置描述 L,该位置描述 L 由一个不完整的复合位置描述 SL 组成。
创建一个不完整的复合位置存储 LS,其中包含一个部分 P。P 指定位置描述 PL,并且位大小为 S 乘以 8(字节大小)。
SL 指定位偏移量为 0 的 LS。
否则,DWARF 表达式格式不正确
许多编译器将单个变量存储在寄存器集中,或者将变量部分存储在内存中,部分存储在寄存器中。
DW_OP_piece
提供了一种描述变量部分位置的方法。DW_OP_piece
操作的评估规则使其与 DWARF 版本 5 的定义兼容。注意:由于这些扩展允许位置描述作为堆栈上的条目,因此可以定义一个更简单的操作来创建复合位置描述。例如,仅一个操作,用于指定多少个部分,并弹出堆栈条目对以表示部分大小和位置描述。这不仅是一个更简单的操作,并避免了不完整复合位置描述的复杂性,而且在实践中也可能具有更小的编码。但是,与 DWARF 版本 5 兼容的愿望可能是一个更强的考虑因素。
DW_OP_bit_piece
DW_OP_bit_piece
具有两个操作数。第一个是无符号 LEB128 整数,表示部分位大小 S。第二个是无符号 LEB128 整数,表示位位移 B。该操作与
DW_OP_piece
的操作相同,不同之处在于创建的任何部分都具有位大小 S,并且任何创建的部分的位置描述 PL 都通过位偏移量 B 进行更新。当要组装的部分不是字节大小或不在部分位置描述的开头时,使用
DW_OP_bit_piece
而不是DW_OP_piece
。
A.2.5.5 DWARF 位置列表表达式¶
注意:本节替换 DWARF 版本 5 第 2.6.2 节。
为了满足最新计算机架构和优化技术的需要,调试信息必须能够描述对象位置在其生命周期内发生变化的对象的位置,并且在对象生命周期的某些部分可能位于多个位置。当要描述其位置的对象具有这些要求时,将使用位置列表表达式来代替操作表达式。
位置列表表达式由一系列位置列表条目组成。每个位置列表条目都是以下类型之一:
有界位置描述
这种位置列表条目提供一个操作表达式,该表达式评估为在由起始地址和结束地址限定的生命周期内有效的对象的位置描述。起始地址是位置有效的地址范围的最低地址。结束地址是超出地址范围最高地址的第一个位置的地址。
当当前程序位置在给定范围内时,位置列表条目匹配。
有几种类型的有界位置描述条目,它们在指定起始地址和结束地址的方式上有所不同。
默认位置描述
这种位置列表条目提供一个操作表达式,该表达式评估为在没有有界位置描述条目适用时有效的对象的位置描述。
当当前程序位置不在任何有界位置描述条目的范围内时,位置列表条目匹配。
基地址
这种位置列表条目提供一个地址,该地址将用作在某些类型的有界位置描述条目中给出的起始地址和结束地址偏移量的基地址。有界位置描述条目的适用基地址是同一位置列表中最接近的前一个基地址条目指定的地址。如果没有前一个基地址条目,则适用基地址默认为编译单元的基地址(请参阅 DWARF 版本 5 第 3.1.1 节)。
在所有机器代码都包含在单个连续 section 中的编译单元的情况下,不需要基地址条目。
列表结束
这种位置列表条目标记位置列表表达式的结束。
位置列表表达式的有界位置描述条目定义的地址范围可能会重叠。当它们重叠时,它们描述的是一个对象同时存在于多个位置的情况。
如果给定的位置列表表达式中的所有地址范围没有共同覆盖目标对象定义的整个范围,并且没有后续的默认位置描述条目,则假定该对象在未覆盖的范围部分不可用。
DWARF 位置列表表达式的求值结果是
如果未指定当前程序位置,则这是一个求值错误。
注意:如果位置列表只有一个默认条目,那么在没有程序位置的情况下,是否应该将其视为匹配?如果存在非默认条目,那么当没有程序位置时,似乎必须是一个求值错误,因为这表明位置取决于未知的程序位置。
如果没有匹配的位置列表条目,则结果是一个包含一个未定义位置描述的位置描述。
否则,每个匹配的位置列表条目的操作表达式 E 将在当前上下文中求值,但结果类型是位置描述,对象未指定,并且初始堆栈为空。位置列表条目结果是由 E 的求值返回的位置描述。
结果是一个位置描述,它由每个匹配的位置列表条目的位置描述结果的单个位置描述的联合组成。
位置列表表达式只能用作使用 class loclist
或 loclistsptr
编码的调试器信息条目属性的值(参见 7.5.5 类和形式)。属性的值提供了一个索引,指向一个名为 .debug_loclists
或 .debug_loclists.dwo
(对于拆分 DWARF 对象文件)的单独对象文件节,该节包含位置列表条目。
DW_OP_call*
和 DW_OP_implicit_pointer
操作可用于指定具有位置列表表达式的调试器信息条目属性。一些调试器信息条目属性允许 DWARF 表达式,这些表达式在求值时使用包含位置描述的初始堆栈,该位置描述可能源自位置列表表达式的求值。
此位置列表表示、loclist
和 loclistsptr
类以及相关的 DW_AT_loclists_base
属性是 DWARF 版本 5 中的新增功能。它们共同消除了之前位置列表表达式所需的大部分或全部代码对象重定位。
注意:本节的其余部分与 DWARF 版本 5 第 2.6.2 节相同。
A.3 程序作用域条目¶
注意:本节提供了对现有调试器信息条目属性的更改。这些更改将纳入相应的 DWARF 版本 5 第 3 章。
A.3.3 子例程和入口点条目¶
A.3.3.5 底层信息¶
DW_TAG_subprogram
、DW_TAG_inlined_subroutine
或DW_TAG_entry_point
调试器信息条目可能具有DW_AT_return_addr
属性,其值是 DWARF 表达式 E。属性的结果是通过在以下上下文中求值 E 获得的:结果类型为位置描述,对象未指定,包含 E 的编译单元,空的初始堆栈,以及其他与用户关注的源语言执行线程相对应的上下文元素(如果有)。求值的结果是位置描述 L,它描述了当前调用帧的子例程或入口点的返回地址存储的位置。
如果 L 不是由目标架构特定地址空间之一的一个内存位置描述组成,则 DWARF 格式不正确。
注意:目前尚不清楚为什么
DW_TAG_inlined_subroutine
具有DW_AT_return_addr
属性,但没有DW_AT_frame_base
或DW_AT_static_link
属性。似乎它要么全部都有,要么全部都没有。由于内联子程序没有调用帧,因此似乎它们应该都没有这些属性。DW_TAG_subprogram
或DW_TAG_entry_point
调试器信息条目可能具有DW_AT_frame_base
属性,其值是 DWARF 表达式 E。属性的结果是通过在以下上下文中求值 E 获得的:结果类型为位置描述,对象未指定,包含 E 的编译单元,空的初始堆栈,以及其他与用户关注的源语言执行线程相对应的上下文元素(如果有)。
如果 E 包含
DW_OP_fbreg
操作,或者结果位置描述 L 不是由单个位置描述 SL 组成,则 DWARF 格式不正确。如果 SL 是寄存器 R 的寄存器位置描述,则 L 将被替换为求值
DW_OP_bregx R, 0
操作的结果。这计算目标架构默认地址空间中的帧基内存位置描述。这允许使用更紧凑的
DW_OP_reg*
代替DW_OP_breg* 0
。注意:可以删除此规则,并要求生产者直接使用
DW_OP_call_frame_cfa
或DW_OP_breg*
创建所需的位置描述。这也将允许目标在大型寄存器中实现调用帧。否则,如果 SL 不是任何目标架构特定地址空间中的内存位置描述,则 DWARF 格式不正确。
结果 L 是子例程或入口点的帧基。
通常,E 将使用
DW_OP_call_frame_cfa
操作,或者是一个堆栈指针寄存器加上或减去某个偏移量。如果
DW_TAG_subprogram
或DW_TAG_entry_point
调试器信息条目是词法嵌套的,则它可能具有DW_AT_static_link
属性,其值是 DWARF 表达式 E。属性的结果是通过在以下上下文中求值 E 获得的:结果类型为位置描述,对象未指定,包含 E 的编译单元,空的初始堆栈,以及其他与用户关注的源语言执行线程相对应的上下文元素(如果有)。求值的结果是位置描述 L,它描述了紧邻当前调用帧的子例程或入口点词法包围的子例程实例的相关调用帧的规范帧地址(参见 6.4 调用帧信息)。
如果 L 不是由目标架构特定地址空间之一的一个内存位置描述组成,则 DWARF 格式不正确。
A.3.4 调用站点条目和参数¶
A.3.4.2 调用站点参数¶
DW_TAG_call_site_parameter
调试器信息条目可能具有DW_AT_call_value
属性,其值是 DWARF 操作表达式 E1。DW_AT_call_value
属性的结果是通过在以下上下文中求值 E1 获得的:结果类型为值,对象未指定,包含 E 的编译单元,空的初始堆栈,以及其他与用户关注的源语言执行线程相对应的上下文元素(如果有)。结果值 V1 是调用站点进行调用时参数的值。对于按引用传递的参数(代码传递指向包含参数的位置的指针)或引用类型参数,
DW_TAG_call_site_parameter
调试器信息条目也可能具有DW_AT_call_data_location
属性(其值是 DWARF 操作表达式 E2)和DW_AT_call_data_value
属性(其值是 DWARF 操作表达式 E3)。DW_AT_call_data_location
属性的值是通过在以下上下文中求值 E2 获得的:结果类型为位置描述,对象未指定,包含 E 的编译单元,空的初始堆栈,以及其他与用户关注的源语言执行线程相对应的上下文元素(如果有)。结果位置描述 L2 是被引用参数在调用站点进行调用期间所处的位置。如果 E2 只是DW_OP_push_object_address
,则可以省略DW_AT_call_data_location
属性。注意:DWARF 版本 5 隐含
DW_OP_push_object_address
可以使用,但未说明上下文中必须指定什么对象。要么DW_OP_push_object_address
不能使用,要么必须定义上下文中要传递的对象。DW_AT_call_data_value
属性的值是通过在以下上下文中求值 E3 获得的:结果类型为值,对象未指定,包含 E 的编译单元,空的初始堆栈,以及其他与用户关注的源语言执行线程相对应的上下文元素(如果有)。结果值 V3 是调用站点进行调用时 L2 中的值。如果当前调用帧不是包含
DW_TAG_call_site_parameter
调试器信息条目的子例程的调用帧,或者当前程序位置不是当前调用帧中包含DW_TAG_call_site_parameter
调试器信息条目的调用站点的程序位置,则这些属性的结果未定义。消费者可能必须虚拟地回溯到调用站点(参见 6.4 调用帧信息),以便求值这些属性。这将确保用户关注的源语言执行线程与求值表达式所需的调用站点相对应。
如果无法避免这些属性的表达式访问可能被调用站点调用的子例程覆盖的寄存器或内存位置,则不应提供关联的属性。
限制的原因是可能需要在被调用者的执行期间访问参数。消费者可以从被调用的子例程虚拟地回溯到调用者,然后求值属性表达式。调用帧信息(参见 6.4 调用帧信息)将无法恢复已被覆盖的寄存器,并且被覆盖的内存将不再具有调用时的值。
A.3.5 词法块条目¶
注意:本节与 DWARF 版本 5 第 3.5 节相同。
A.4 数据对象和对象列表条目¶
注意:本节提供了对现有调试器信息条目属性的更改。这些更改将纳入相应的 DWARF 版本 5 第 4 章。
A.4.1 数据对象条目¶
程序变量、形式参数和常量分别由带有标签 DW_TAG_variable
、DW_TAG_formal_parameter
和 DW_TAG_constant
的调试信息条目表示。
标签 DW_TAG_constant
用于具有真正常量名称的语言。
程序变量、形式参数或常量的调试信息条目可能具有以下属性
DW_AT_location
属性,其值是 DWARF 表达式 E,它描述了变量或参数在运行时的位置。属性的结果是通过在以下上下文中求值 E 获得的:结果类型为位置描述,对象未指定,包含 E 的编译单元,空的初始堆栈,以及其他与用户关注的源语言执行线程相对应的上下文元素(如果有)。求值的结果是数据对象基址的位置描述。
有关
DW_OP_call*
操作使用的特殊求值规则,请参见 2.5.4.2 控制流操作。注意:删除关于
DW_OP_call*
操作如何求值DW_AT_location
属性的描述,因为这现在在操作中描述。注意:请参见关于
DW_AT_location
属性在DW_OP_call*
操作中的讨论。让每个属性只有一个目的和单一的执行语义似乎是可取的。这使得消费者更容易,不再需要跟踪上下文。这也使得生产者更容易,因为它可以依赖于每个属性的单一语义。因此,将
DW_AT_location
属性限制为仅支持求值对象的位置描述,并对同一操作表达式堆栈上的 DWARF 表达式过程的求值使用不同的属性和编码类,似乎是可取的。DW_AT_const_value
注意:可以弃用对已优化为常量的
DW_TAG_variable
或DW_TAG_formal_parameter
调试器信息条目使用DW_AT_const_value
属性。相反,DW_AT_location
可以与产生隐式位置描述的 DWARF 表达式一起使用,因为现在任何位置描述都可以在 DWARF 表达式中使用。这允许使用DW_OP_call*
操作来推送任何变量的位置描述,而不管其如何优化。
A.4.2 公共块条目¶
公共块条目也具有 DW_AT_location 属性,其值是 DWARF 表达式 E,它描述了公共块在运行时的位置。属性的结果是通过在以下上下文中求值 E 获得的:结果类型为位置描述,对象未指定,包含 E 的编译单元,空的初始堆栈,以及其他与用户关注的源语言执行线程相对应的上下文元素(如果有)。求值的结果是公共块基址的位置描述。有关 DW_OP_call* 操作使用的特殊求值规则,请参见 2.5.4.2 控制流操作。
A.5 类型条目¶
注意:本节提供了对现有调试器信息条目属性的更改。这些更改将纳入相应的 DWARF 版本 5 第 5 章。
A.5.7 结构、联合、类和接口类型条目¶
A.5.7.3 派生或扩展的结构、类和接口¶
对于
DW_AT_data_member_location
属性,有两种情况如果属性是整数常量 B,则它提供从包含实体的开头起的字节偏移量。
属性的结果是通过将包含实体的开头的位置描述的位偏移量更新为 B 乘以 8(字节大小)来获得的。结果是成员条目基址的位置描述。
如果包含实体的开头不是字节对齐的,则成员条目的开头在字节内具有相同的位移。
否则,属性必须是 DWARF 表达式 E,它在以下上下文中求值:结果类型为位置描述,对象未指定,包含 E 的编译单元,初始堆栈包含包含实体开头的位置描述,以及其他与用户关注的源语言执行线程相对应的上下文元素(如果有)。求值的结果是成员条目基址的位置描述。
注意:包含实体的开头现在可以是任何位置描述,包括具有多个单个位置描述的位置描述,以及具有任何类型和任何位偏移量的单个位置描述的位置描述。
A.5.7.8 成员函数条目¶
虚拟函数的条目也具有
DW_AT_vtable_elem_location
属性,其值是 DWARF 表达式 E。属性的结果是通过在以下上下文中求值 E 获得的:结果类型为位置描述,对象未指定,包含 E 的编译单元,初始堆栈包含封闭类型的对象的位置描述,以及其他与用户关注的源语言执行线程相对应的上下文元素(如果有)。求值的结果是封闭类的虚函数表中函数槽的位置描述。
A.5.14 指向成员类型的指针条目¶
DW_TAG_ptr_to_member_type
调试信息条目具有DW_AT_use_location
属性,其值是 DWARF 表达式 E。它用于计算指针指向的类成员的位置描述。用于查找给定类、结构或联合的成员的位置描述的方法对于该类、结构或联合的任何实例以及指向成员类型的指针类型的任何实例都是通用的。因此,该方法与指向成员类型的指针类型相关联,而不是与具有指向成员类型的指针类型的每个对象相关联。
DW_AT_use_location
DWARF 表达式与给定指向成员类型的指针类型的特定对象以及特定结构或类实例的位置描述结合使用。属性的结果是通过在以下上下文中求值 E 获得的:结果类型为位置描述,对象未指定,包含 E 的编译单元,初始堆栈包含两个条目,以及其他与用户关注的源语言执行线程相对应的上下文元素(如果有)。第一个堆栈条目是指向成员对象的指针对象本身的值。第二个堆栈条目是包含要计算其位置的成员的整个类、结构或联合实例的基址的位置描述。求值的结果是指针指向的类成员的位置描述。
A.5.18 类型的动态属性¶
A.5.18.1 数据位置¶
DW_AT_data_location
属性可以与任何提供一个或多个级别的隐藏间接寻址和/或运行时参数的表示形式的类型一起使用。它的值是一个 DWARF 操作表达式 E,它计算对象的数据的位置描述。当省略此属性时,数据的位置描述与对象的位置描述相同。属性的结果是通过在以下上下文中求值 E 获得的:结果类型为位置描述,对象是数据描述符的位置描述,包含 E 的编译单元,空的初始堆栈,以及其他与用户关注的源语言执行线程相对应的上下文元素(如果有)。求值的结果是成员条目基址的位置描述。
E 通常将涉及一个操作表达式,该表达式以
DW_OP_push_object_address
操作开始,该操作加载对象的位置描述,然后可以将其用作后续计算中的描述符。注意:由于
DW_AT_data_member_location
、DW_AT_use_location
和DW_AT_vtable_elem_location
都允许操作表达式和位置列表表达式,为什么DW_AT_data_location
不允许两者都允许?在所有情况下,它们都适用于数据对象,因此优化不太可能导致不同程序位置范围的不同操作表达式。但如果支持某些,则应支持所有。似乎很奇怪,此属性与
DW_AT_data_member_location
不同,后者具有包含对象位置描述的初始堆栈,因为表达式必须需要它。
A.6 其他调试信息¶
注意:本节提供了对现有调试器信息条目属性的更改。这些更改将纳入相应的 DWARF 版本 5 第 6 章。
A.6.2 行号信息¶
注意:本节与 DWARF 版本 5 第 6.2 节相同。
A.6.4 调用帧信息¶
注意:本节提供了对 DWARF 版本 5 第 6.4 节的更改。寄存器展开 DWARF 表达式被推广为允许任何位置描述,包括具有复合和隐式位置描述的位置描述。
A.6.4.1 调用帧信息的结构¶
寄存器规则是
未定义
具有此规则的寄存器在前一帧中没有可恢复的值。此寄存器的先前值是未定义的位置描述(参见 2.5.4.4.2 未定义位置描述操作)。
按照惯例,寄存器不会被被调用者保留。
相同的值
此寄存器自先前的调用者帧以来未被修改。
如果当前帧是顶层帧,则此寄存器的先前值是位置描述 L,它指定一个寄存器位置描述 SL。SL 指定与当前线程的位偏移量为 0 的寄存器相对应的寄存器位置存储。
如果当前帧不是顶层帧,则此寄存器的先前值是使用被当前调用者帧调用的被调用者帧和被调用者程序位置的调用帧信息为同一寄存器获得的位置描述。
按照惯例,寄存器会被被调用者保留,但被调用者没有修改它。
offset(N)
N 是一个有符号字节偏移量。此寄存器的先前值保存在位置描述 L 处。其中 L 是当前 CFA 的位置描述(参见 2.5.4 DWARF 操作表达式),并使用位偏移量 N 乘以 8(字节大小)进行更新。
val_offset(N)
N 是一个有符号字节偏移量。此寄存器的先前值是位置描述 L 的内存字节地址。其中 L 是当前 CFA 的位置描述(参见 2.5.4 DWARF 操作表达式),并使用位偏移量 N 乘以 8(字节大小)进行更新。
如果 CFA 位置描述不是内存字节地址位置描述,或者寄存器大小与目标架构默认地址空间中的地址大小不匹配,则 DWARF 格式不正确。
由于 CFA 位置描述必须是内存字节地址位置描述,因此 val_offset(N) 的值也将是内存字节地址位置描述,因为它通过 N 字节偏移了 CFA 位置描述。此外,val_offset(N) 的值将是目标架构默认地址空间中的内存字节地址。
注意:DWARF 是否应该允许地址大小与寄存器大小不同?要求它们具有相同的位大小可以避免任何转换问题,因为寄存器的位内容只是被解释为地址的值。
GDB 具有每个寄存器的钩子,允许在每个寄存器的基础上进行目标特定的转换。它默认为截断较大的寄存器,以及实际从下一个寄存器读取字节(或读取超出最后一个寄存器的范围)以用于较小的寄存器。没有 GDB 测试读取超出范围的寄存器(除非是非法的手写汇编测试)。
register(R)
此寄存器已存储在编号为 R 的另一个寄存器中。
此寄存器的先前值是使用当前帧和当前程序位置的调用帧信息为寄存器 R 获得的位置描述。
如果此寄存器的大小与寄存器 R 的大小不匹配,或者调用帧信息中存在循环依赖关系,则 DWARF 格式不正确。
注意:这是否也应该允许 R 大于此寄存器?如果是这样,值是否存储在低位,而额外的上位中存储的内容是否未定义?
expression(E)
此寄存器的先前值位于通过求值 DWARF 操作表达式 E(参见 2.5.4 DWARF 操作表达式)产生的位置描述处。
E 使用当前上下文进行求值,但结果类型为位置描述,编译单元未指定,对象未指定,以及包含当前 CFA 位置描述的初始堆栈(参见 2.5.4 DWARF 操作表达式)。
val_expression(E)
此寄存器先前的值位于通过求值 DWARF 操作表达式 E 生成的值创建的隐式位置描述中(参见 2.5.4 DWARF 操作表达式)。
E 使用当前上下文进行求值,但结果类型为值,编译单元未指定,对象未指定,以及包含当前 CFA 位置描述的初始堆栈(参见 2.5.4 DWARF 操作表达式)。
如果结果值类型大小与寄存器大小不匹配,则 DWARF 格式不正确。
注:由于 DWARF 表达式 E 只能生成最大为通用类型大小的值,因此这种用法用途有限。这是因为不允许任何在 CFI 操作表达式中指定类型的操作。这使得它对于大于通用类型的寄存器不可用。但是,expression(E) 可以用于创建任何大小的隐式位置描述。
架构相关的
此规则由增强器在规范外部定义。
通用信息条目 (CIE) 包含在多个帧描述条目 (FDE) 之间共享的信息。每个非空的 .debug_frame
section 中至少有一个 CIE。CIE 包含以下字段,按顺序排列
length
(初始长度)一个常量,给出 CIE 结构的字节数,不包括 length 字段本身。length 字段的大小加上 length 的值必须是
address_size
字段中指定的地址大小的整数倍。CIE_id
(4 或 8 字节,参见 7.4 32 位和 64 位 DWARF 格式)一个常量,用于区分 CIE 和 FDE。
在 32 位 DWARF 格式中,CIE 标头中的 CIE id 值为 0xffffffff;在 64 位 DWARF 格式中,该值为 0xffffffffffffffff。
version
(ubyte)版本号。此号码特定于调用帧信息,并且独立于 DWARF 版本号。
CIE 版本号的值为 4。
注:为了反映这些扩展中的更改,这个值会增加到 5 吗?
augmentation
(UTF-8 字符序列)一个以 null 结尾的 UTF-8 字符串,用于标识对此 CIE 或使用它的 FDE 的增强。如果读取器遇到意外的增强字符串,则只能读取以下字段
CIE:length、CIE_id、version、augmentation
FDE:length、CIE_pointer、initial_location、address_range
如果没有增强,则此值为零字节。
增强字符串允许用户指示在 CIE 或 FDE 中存在额外的供应商和目标架构特定的信息,这些信息是虚拟展开堆栈帧所必需的。例如,这可能是关于动态分配数据的信息,这些数据需要在例程退出时释放。
由于
.debug_frame
section 独立于任何.debug_info
section 也很有用,因此增强字符串始终使用 UTF-8 编码。address_size
(ubyte)此 CIE 和使用它的任何 FDE 中目标地址的大小,以字节为单位。如果此帧存在编译单元,则其地址大小必须与此处的地址大小匹配。
segment_selector_size
(ubyte)此 CIE 和使用它的任何 FDE 中段选择器的大小,以字节为单位。
code_alignment_factor
(无符号 LEB128)一个常量,从所有 advance location 指令中分解出来(参见 6.4.2.1 行创建指令)。结果值为
(operand * code_alignment_factor)
。data_alignment_factor
(有符号 LEB128)一个常量,从某些 offset 指令中分解出来(参见 6.4.2.2 CFA 定义指令 和 6.4.2.3 寄存器规则指令)。结果值为
(operand * data_alignment_factor)
。return_address_register
(无符号 LEB128)一个无符号 LEB128 常量,指示规则表中哪一列表示子程序的返回地址。请注意,此列可能不对应于实际的机器寄存器。
返回地址寄存器的值用于确定调用者帧的程序位置。顶层帧的程序位置是当前线程的目标架构程序计数器值。
initial_instructions
(ubyte 数组)一系列规则,被解释为创建表中每一列的初始设置。
在解释初始指令之前,所有列的默认规则是未定义规则。但是,ABI 编写机构或编译系统编写机构可以为任何或所有列指定备用默认值。
padding
(ubyte 数组)足够的
DW_CFA_nop
指令,使此条目的大小与上面的 length 值匹配。
FDE 包含以下字段,按顺序排列
length
(初始长度)一个常量,给出此子程序的标头和指令流的字节数,不包括 length 字段本身。length 字段的大小加上 length 的值必须是地址大小的整数倍。
CIE_pointer
(4 或 8 字节,参见 7.4 32 位和 64 位 DWARF 格式)指向
.debug_frame
section 的常量偏移量,表示与此 FDE 关联的 CIE。initial_location
(段选择器和目标地址)与此表条目关联的第一个位置的地址。如果此 FDE 的 CIE 的 segment_selector_size 字段为非零,则初始位置前面会有一个给定长度的段选择器。
address_range
(目标地址)此条目描述的程序指令的字节数。
instructions
(ubyte 数组)一系列表定义指令,在 6.4.2 调用帧指令 中描述。
padding
(ubyte 数组)足够的
DW_CFA_nop
指令,使此条目的大小与上面的 length 值匹配。
A.6.4.2 调用帧指令¶
一些调用帧指令的操作数被编码为 DWARF 操作表达式 E(参见 2.5.4 DWARF 操作表达式)。可在 E 中使用的 DWARF 操作具有以下限制
DW_OP_addrx
、DW_OP_call2
、DW_OP_call4
、DW_OP_call_ref
、DW_OP_const_type
、DW_OP_constx
、DW_OP_convert
、DW_OP_deref_type
、DW_OP_fbreg
、DW_OP_implicit_pointer
、DW_OP_regval_type
、DW_OP_reinterpret
和DW_OP_xderef_type
操作是不允许的,因为调用帧信息不得依赖于其他调试 section。DW_OP_push_object_address
是不允许的,因为没有对象上下文来提供要推送的值。DW_OP_call_frame_cfa
和DW_OP_entry_value
是不允许的,因为它们的使用将是循环的。
应用这些限制的调用帧指令包括 DW_CFA_def_cfa_expression
、DW_CFA_expression
和 DW_CFA_val_expression
。
A.6.4.2.1 行创建指令¶
注:这些指令与 DWARF 版本 5 第 6.4.2.1 节中的指令相同。
A.6.4.2.2 CFA 定义指令¶
DW_CFA_def_cfa
DW_CFA_def_cfa
指令接受两个无符号 LEB128 操作数,分别表示寄存器号 R 和(非分解的)字节位移 B。所需的操作是将当前 CFA 规则定义为等效于求值 DWARF 操作表达式DW_OP_bregx R, B
作为位置描述的结果。DW_CFA_def_cfa_sf
DW_CFA_def_cfa_sf
指令接受两个操作数:一个无符号 LEB128 值,表示寄存器号 R,以及一个有符号 LEB128 分解字节位移 B。所需的操作是将当前 CFA 规则定义为等效于求值 DWARF 操作表达式DW_OP_bregx R, B * data_alignment_factor
作为位置描述的结果。该操作与
DW_CFA_def_cfa
相同,不同之处在于第二个操作数是有符号且分解的。DW_CFA_def_cfa_register
DW_CFA_def_cfa_register
指令接受单个无符号 LEB128 操作数,表示寄存器号 R。所需的操作是将当前 CFA 规则定义为等效于求值 DWARF 操作表达式DW_OP_bregx R, B
作为位置描述的结果。B 是旧的 CFA 字节位移。如果子程序没有当前的 CFA 规则,或者该规则是由
DW_CFA_def_cfa_expression
指令定义的,则 DWARF 格式不正确。DW_CFA_def_cfa_offset
DW_CFA_def_cfa_offset
指令接受单个无符号 LEB128 操作数,表示(非分解的)字节位移 B。所需的操作是将当前 CFA 规则定义为等效于求值 DWARF 操作表达式DW_OP_bregx R, B
作为位置描述的结果。R 是旧的 CFA 寄存器号。如果子程序没有当前的 CFA 规则,或者该规则是由
DW_CFA_def_cfa_expression
指令定义的,则 DWARF 格式不正确。DW_CFA_def_cfa_offset_sf
DW_CFA_def_cfa_offset_sf
指令接受一个有符号 LEB128 操作数,表示分解字节位移 B。所需的操作是将当前 CFA 规则定义为等效于求值 DWARF 操作表达式DW_OP_bregx R, B * data_alignment_factor
作为位置描述的结果。R 是旧的 CFA 寄存器号。如果子程序没有当前的 CFA 规则,或者该规则是由
DW_CFA_def_cfa_expression
指令定义的,则 DWARF 格式不正确。该操作与
DW_CFA_def_cfa_offset
相同,不同之处在于操作数是有符号且分解的。DW_CFA_def_cfa_expression
DW_CFA_def_cfa_expression
指令接受单个操作数,该操作数被编码为DW_FORM_exprloc
值,表示 DWARF 操作表达式 E。所需的操作是将当前 CFA 规则定义为等效于使用当前上下文求值 E 的结果,但结果类型为位置描述,编译单元未指定,对象未指定,以及一个空初始堆栈。关于可在 E 中使用的 DWARF 表达式操作的限制,请参见 6.4.2 调用帧指令。
如果求值 E 的结果不是内存字节地址位置描述,则 DWARF 格式不正确。
A.6.4.2.3 寄存器规则指令¶
DW_CFA_undefined
DW_CFA_undefined
指令接受单个无符号 LEB128 操作数,表示寄存器号 R。所需的操作是将 R 指定的寄存器的规则设置为undefined
(未定义)。DW_CFA_same_value
DW_CFA_same_value
指令接受单个无符号 LEB128 操作数,表示寄存器号 R。所需的操作是将 R 指定的寄存器的规则设置为same value
(相同值)。DW_CFA_offset
DW_CFA_offset
指令接受两个操作数:寄存器号 R(使用操作码编码)和一个无符号 LEB128 常量,表示分解位移 B。所需的操作是将 R 指定的寄存器的规则更改为 offset(B * data_alignment_factor) 规则。注:似乎应该命名为
DW_CFA_offset_uf
,因为偏移量是无符号分解的。DW_CFA_offset_extended
DW_CFA_offset_extended
指令接受两个无符号 LEB128 操作数,分别表示寄存器号 R 和分解位移 B。此指令与DW_CFA_offset
相同,除了寄存器操作数的编码和大小。注:似乎应该命名为
DW_CFA_offset_extended_uf
,因为位移是无符号分解的。DW_CFA_offset_extended_sf
DW_CFA_offset_extended_sf
指令接受两个操作数:一个无符号 LEB128 值,表示寄存器号 R,以及一个有符号 LEB128 分解位移 B。此指令与DW_CFA_offset_extended
相同,不同之处在于 B 是有符号的。DW_CFA_val_offset
DW_CFA_val_offset
指令接受两个无符号 LEB128 操作数,分别表示寄存器号 R 和分解位移 B。所需的操作是将 R 指示的寄存器的规则更改为 val_offset(B * data_alignment_factor) 规则。注:似乎应该命名为
DW_CFA_val_offset_uf
,因为位移是无符号分解的。DW_CFA_val_offset_sf
DW_CFA_val_offset_sf
指令接受两个操作数:一个无符号 LEB128 值,表示寄存器号 R,以及一个有符号 LEB128 分解位移 B。此指令与DW_CFA_val_offset
相同,不同之处在于 B 是有符号的。DW_CFA_register
DW_CFA_register
指令接受两个无符号 LEB128 操作数,分别表示寄存器号 R1 和 R2。所需的操作是将 R1 指定的寄存器的规则设置为 register(R2) 规则。DW_CFA_expression
DW_CFA_expression
指令接受两个操作数:一个无符号 LEB128 值,表示寄存器号 R,以及一个DW_FORM_block
值,表示 DWARF 操作表达式 E。所需的操作是将 R 指定的寄存器的规则更改为 expression(E) 规则。也就是说,E 计算可以检索寄存器值的位置描述。
关于可在 E 中使用的 DWARF 表达式操作的限制,请参见 6.4.2 调用帧指令。
DW_CFA_val_expression
DW_CFA_val_expression
指令接受两个操作数:一个无符号 LEB128 值,表示寄存器号 R,以及一个DW_FORM_block
值,表示 DWARF 操作表达式 E。所需的操作是将 R 指定的寄存器的规则更改为 val_expression(E) 规则。也就是说,E 计算寄存器 R 的值。
关于可在 E 中使用的 DWARF 表达式操作的限制,请参见 6.4.2 调用帧指令。
如果求值 E 的结果不是具有与寄存器大小匹配的基本类型大小的值,则 DWARF 格式不正确。
DW_CFA_restore
DW_CFA_restore
指令接受单个操作数(使用操作码编码),表示寄存器号 R。所需的操作是将 R 指定的寄存器的规则更改为 CIE 中initial_instructions
分配给它的规则。DW_CFA_restore_extended
DW_CFA_restore_extended
指令接受单个无符号 LEB128 操作数,表示寄存器号 R。此指令与DW_CFA_restore
相同,除了寄存器操作数的编码和大小。
A.6.4.2.4 行状态指令¶
注:这些指令与 DWARF 版本 5 第 6.4.2.4 节中的指令相同。
A.6.4.2.5 填充指令¶
注:这些指令与 DWARF 版本 5 第 6.4.2.5 节中的指令相同。
A.6.4.3 调用帧指令用法¶
注:与 DWARF 版本 5 第 6.4.3 节中的相同。
A.6.4.4 调用帧调用地址¶
注:与 DWARF 版本 5 第 6.4.4 节中的相同。
A.7 数据表示¶
注:本节提供了对现有调试信息条目属性的更改。这些更改将合并到相应的 DWARF 版本 5 第 7 章 section 中。
A.7.4 32 位和 64 位 DWARF 格式¶
注:本节增强了 DWARF 版本 5 第 7.4 节列表项 3 的表格。
Form Role
------------------------ --------------------------------------
DW_OP_implicit_pointer offset in `.debug_info`
A.7.5 调试信息格式¶
A.7.5.5 类和形式¶
注:与 DWARF 版本 5 第 7.5.5 节中的相同。
A.7.7 DWARF 表达式¶
注:重命名 DWARF 版本 5 第 7.7 节,以反映位置描述统一到 DWARF 表达式中。
A.7.7.1 操作表达式¶
注:重命名 DWARF 版本 5 第 7.7.1 节并删除第 7.7.2 节,以反映位置描述统一到 DWARF 表达式中。
A.7.7.3 位置列表表达式¶
注:重命名 DWARF 版本 5 第 7.7.3 节,以反映位置列表是 DWARF 表达式的一种。
B. 更多信息¶
以下参考文献提供了关于此扩展的更多信息。
提供了对 DWARF 标准的引用。
此扩展的格式化版本可在 LLVM 网站上找到。它包含许多图表,有助于说明文本描述,特别是示例 DWARF 表达式求值。
Linux Plumbers Conference 2021 上关于此扩展的演示文稿的幻灯片和视频可用。
LLVM 编译器扩展包括动机示例中提到的操作。它还涵盖了异构设备所需的其他扩展。
用于优化的 SIMT/SIMD (GPU) 调试的 DWARF 扩展 - Linux Plumbers Conference 2021