在调试的时候经常需要进行堆栈回溯。最简单的方式是使用一个独立的寄存器(ebp)来保存每层函数调用的堆栈栈顶(frame pointer):
pushl%ebp
movl%esp,%ebp
...
popl%ebp
ret
这种方式在堆栈回溯时非常方便快捷。但是这种方法也有自己的不足:
调试信息标准DWARF(Debugging With Attributed Record Formats)定义了一个.debug_frame
section用来解决上述的难题。
带外
的,不消耗任何指令周期,没有任何性能开销。这种机制也有其不足:
现代Linux操作系统在LSB(Linux Standard Base)标准中定义了一个.eh_frame
section来解决上述的难题。这个section和.debug_frame
非常类似,但是它解决了上述难题:
但是.debug_frame
和.eh_frame
同时面临一个难题:怎么样生成堆栈信息表?
为了解决上述难题,GAS(GCC Assembler)汇编编译器定义了一组伪指令来协助生成调用栈信息CFI(Call Frame Information)。
CFI directives
伪指令是一组生成CFI调试信息的高级语言,它的形式类似于:
f:
.cfi_startproc
pushl%ebp
.cfi_def_cfa_offset 8
.cfi_offset ebp,-8
关于汇编器利用这些伪指令来生成.debug_frame
还是.debug_frame
,在.cfi_sections
指令中定义。如果只是调试需求可以生成.debug_frame
,如果需要在运行时调用需要生成.eh_frame
。
在DWARF6.4 Call Frame Information
一节详细的描述了调用栈帧的定义。
抽象的说,整个栈帧计算机制的核心是一张大表:
可以使用readelf -wF xxx
命令来查看通过elf文件中的.eh_frame
解析出来的这张表:
$ readelf -wF a.out
...
LOC CFA rbx rbp r12 r13 r14 r15 ra
00000000000006b0 rsp+8 u u u u u u c-8
00000000000006b2 rsp+16 u u u u u c-16 c-8
00000000000006b4 rsp+24 u u u u c-24 c-16 c-8
00000000000006b9 rsp+32 u u u c-32 c-24 c-16 c-8
00000000000006bb rsp+40 u u c-40 c-32 c-24 c-16 c-8
00000000000006c3 rsp+48 u c-48 c-40 c-32 c-24 c-16 c-8
00000000000006cb rsp+56 c-56 c-48 c-40 c-32 c-24 c-16 c-8
00000000000006d8 rsp+64 c-56 c-48 c-40 c-32 c-24 c-16 c-8
000000000000070a rsp+56 c-56 c-48 c-40 c-32 c-24 c-16 c-8
000000000000070b rsp+48 c-56 c-48 c-40 c-32 c-24 c-16 c-8
000000000000070c rsp+40 c-56 c-48 c-40 c-32 c-24 c-16 c-8
000000000000070e rsp+32 c-56 c-48 c-40 c-32 c-24 c-16 c-8
0000000000000710 rsp+24 c-56 c-48 c-40 c-32 c-24 c-16 c-8
0000000000000712 rsp+16 c-56 c-48 c-40 c-32 c-24 c-16 c-8
0000000000000714 rsp+8 c-56 c-48 c-40 c-32 c-24 c-16 c-8
这张表的结构设计如下:
如果按上述说明实际构建,此表将非常大。 该表中任何位置的大多数条目与它们上方的条目相同。通过仅记录从程序中每个子例程的起始地址开始的差异,可以非常紧凑地表示整个表。
虚拟展开信息被编码在称为.debug_frame
的独立部分中。 .debug_frame
节中的条目相对于该节的开头按地址大小的倍数对齐,并以两种形式出现:公共信息条目(CIE)和帧描述条目(FDE)。
如果某个函数的代码地址范围不连续,则可能有多个CIE和FDE与该功能的各个部分相对应。
CIE和FDE的定义在.eh_frame
一节会详细描述,这里就不展开。本节重点关注CFA指令的解析。
上一节说明栈帧回溯的核心是一张大表,而这个表的行(Row)
和列(Column)
最后都是靠CIE和FDE中的指令填充起来的。
这些指令主要分为以下几大类:
本类的指令会确定一个新的Location(PC)值,相当于在表中创建新的一行(Row)
。
Instructions | Descript | Descript |
---|---|---|
DW_CFA_set_loc | Location = Address DW_CFA_set_loc指令采用单个操作数代表目标地址。 所需的操作是使用指定位置的地址来创建新的表行。 新行中的所有其他值最初都与当前行相同。 新位置值始终大于当前位置值。 如果此FDE的CIE的segment_size字段不为零,则在初始位置之前是给定长度的段选择器。 |
The DW_CFA_set_loc instruction takes a single operand that represents a target address. The required action is to create a new table row using the specified address as the location. All other values in the new row are initially identical to the current row. The new location value is always greater than the current one. If the segment_size field of this FDE's CIE is non-zero, the initial location is preceded by a segment selector of the given length. |
DW_CFA_advance_loc | Location += (delta * code_alignment_factor) DW_CFA_advance指令采用单个操作数(和操作码一起编码),该操作数表示常数增量。 所需的操作是使用位置值创建一个新表行,该位置值是通过获取当前条目的位置值并加上“ delta * code_alignment_factor”的值来计算的。 新行中的所有其他值最初都与当前行相同。 |
The DW_CFA_advance instruction takes a single operand (encoded with the opcode) that represents a constant delta. The required action is to create a new table row with a location value that is computed by taking the current entry’s location value and adding the value of `delta * code_alignment_factor`. All other values in the new row are initially identical to the current row. |
DW_CFA_advance_loc1 | Location += (delta * code_alignment_factor) DW_CFA_advance_loc1指令采用一个表示常量增量的单个`ubyte`操作数。 除了增量操作数的编码和大小外,该指令与DW_CFA_advance_loc相同。 |
The DW_CFA_advance_loc1 instruction takes a single ubyte operand that represents a constant delta. This instruction is identical to DW_CFA_advance_loc except for the encoding and size of the delta operand. |
DW_CFA_advance_loc2 | Location += (delta * code_alignment_factor) DW_CFA_advance_loc2指令采用单个`uhalf`操作数表示常数增量。 除了增量操作数的编码和大小外,该指令与DW_CFA_advance_loc相同。 |
The DW_CFA_advance_loc2 instruction takes a single uhalf operand that represents a constant delta. This instruction is identical to DW_CFA_advance_loc except for the encoding and size of the delta operand. |
DW_CFA_advance_loc4 | Location += (delta * code_alignment_factor) DW_CFA_advance_loc4指令采用单个`uword`操作数来表示恒定增量。 除了增量操作数的编码和大小外,该指令与DW_CFA_advance_loc相同。 |
The DW_CFA_advance_loc4 instruction takes a single uword operand that represents a constant delta. This instruction is identical to DW_CFA_advance_loc except for the encoding and size of the delta operand. |
在Location相关指令创建一个新行(Row)
以后,本节相关指令来定义这一行的CFA计算规则。
Instructions | Descript | Descript |
---|---|---|
DW_CFA_def_cfa | register = new register num offset = new offset CFA = register + offset DW_CFA_def_cfa指令采用两个无符号的LEB128操作数,它们代表`寄存器号`和(非因数)`偏移量`。 所需的操作是定义当前的CFA规则以使用提供的寄存器和偏移量。 |
The DW_CFA_def_cfa instruction takes two unsigned LEB128 operands representing a register number and a (non-factored) offset. The required action is to define the current CFA rule to use the provided register and offset. |
DW_CFA_def_cfa_sf | register = new register num offset = (factored_offset * data_alignment_factor) CFA = register + offset DW_CFA_def_cfa_sf指令采用两个操作数:代表寄存器号的无符号LEB128值和有符号LEB128因数偏移量。 该指令与DW_CFA_def_cfa相同,不同之处在于第二个操作数是有符号并且乘以因数。 结果偏移量为factored_offset * data_alignment_factor。 |
The DW_CFA_def_cfa_sf instruction takes two operands: an unsigned LEB128 value representing a register number and a signed LEB128 factored offset. This instruction is identical to DW_CFA_def_cfa except that the second operand is signed and factored. The resulting offset is factored_offset * data_alignment_factor. |
DW_CFA_def_cfa_register | register = new register number CFA = register + old offset DW_CFA_def_cfa_register指令采用单个无符号的LEB128操作数表示寄存器号。 所需的操作是定义当前的CFA规则以使用提供的寄存器(但保留旧的偏移量)。 仅当当前CFA规则定义为使用寄存器和偏移量时,此操作才有效。 |
The DW_CFA_def_cfa_register instruction takes a single unsigned LEB128 operand representing a register number. The required action is to define the current CFA rule to use the provided register (but to keep the old offset). This operation is valid only if the current CFA rule is defined to use a register and offset. |
DW_CFA_def_cfa_offset | offset = new offset CFA = old register + offset DW_CFA_def_cfa_offset指令采用单个无符号LEB128操作数表示一个(未分解的)偏移量。 所需的操作是定义当前的CFA规则以使用提供的偏移量(但保留旧寄存器)。 仅当当前CFA规则定义为使用寄存器和偏移量时,此操作才有效。 |
The DW_CFA_def_cfa_offset instruction takes a single unsigned LEB128 operand representing a (non-factored) offset. The required action is to define the current CFA rule to use the provided offset (but to keep the old register). This operation is valid only if the current CFA rule is defined to use a register and offset. |
DW_CFA_def_cfa_offset_sf | offset = (factored_offset * data_alignment_factor) CFA = old register + offset DW_CFA_def_cfa_offset_sf指令采用一个带符号的LEB128操作数,代表一个因数偏移量。 该指令与DW_CFA_def_cfa_offset相同,除了操作数是有符号并且乘以因数。 结果偏移量为factored_offset * data_alignment_factor。 仅当当前CFA规则定义为使用寄存器和偏移量时,此操作才有效。 |
The DW_CFA_def_cfa_offset_sf instruction takes a signed LEB128 operand representing a factored offset. This instruction is identical to DW_CFA_def_cfa_offset except that the operand is signed and factored. The resulting offset is factored_offset * data_alignment_factor. This operation is valid only if the current CFA rule is defined to use a register and offset. |
DW_CFA_def_cfa_expression | CFA = DWARF expression DW_CFA_def_cfa_expression指令采用单个操作数编码为代表DWARF表达式的DW_FORM_exprloc值。 所需的操作是建立该表达式作为计算当前CFA的方式。 |
The DW_CFA_def_cfa_expression instruction takes a single operand encoded as a DW_FORM_exprloc value representing a DWARF expression. The required action is to establish that expression as the means by which the current CFA is computed. |
本节指令确定一行(Row)
寄存器的恢复规则。
寄存器规则包含以下:
Rule | Descript | Descript |
---|---|---|
undefined | 具有此规则的寄存器在前一帧中没有可恢复的值。(按照惯例,被调用方不会保留它。) | A register that has this rule has no recoverable value in the previous frame. (By convention, it is not preserved by a callee.) |
same value | 没有从前一帧修改此寄存器。(按照惯例,它是由被调用方保留的,但是被调用方尚未对其进行修改。) | This register has not been modified from the previous frame. (By convention, it is preserved by the callee, but the callee has not modified it.) |
offset(N) | 该寄存器的先前值保存在地址CFA + N中,其中CFA是当前CFA值,N是有符号偏移量。 | The previous value of this register is saved at the address CFA+N where CFA is the current CFA value and N is a signed offset. |
val_offset(N) | 该寄存器的前一个值为CFA + N,其中CFA为当前CFA值,N为有符号偏移量。 | The previous value of this register is the value CFA+N where CFA is the current CFA value and N is a signed offset. |
register(R) | 该寄存器的先前值存储在另一个编号为R的寄存器中。 | The previous value of this register is stored in another register numbered R. |
expression(E) | 该寄存器的先前值位于通过执行DWARF表达式E产生的地址。 | The previous value of this register is located at the address produced by executing the DWARF expression E. |
val_expression(E) | 该寄存器的先前值是通过执行DWARF表达式E产生的值。 | The previous value of this register is the value produced by executing the DWARF expression E. |
architectural | 该规则由增强器在此规范的外部定义。 | The rule is defined externally to this specification by the augmenter. |
具体包含以下指令:
Instructions | Rule | Descript | Descript |
---|---|---|---|
DW_CFA_undefined | undefined | reg num = undefined DW_CFA_undefined指令采用单个无符号的LEB128操作数来表示寄存器号。 所需的操作是将指定寄存器的规则设置为“未定义”。 |
The DW_CFA_undefined instruction takes a single unsigned LEB128 operand that represents a register number. The required action is to set the rule for the specified register to “undefined.” |
DW_CFA_same_value | same value | reg num = same value DW_CFA_same_value指令采用单个无符号LEB128操作数来表示寄存器号。 所需的操作是将指定寄存器的规则设置为“相同值”。 |
The DW_CFA_same_value instruction takes a single unsigned LEB128 operand that represents a register number. The required action is to set the rule for the specified register to “same value.” |
DW_CFA_offset | offset(N) | offset = (factored offset * data_alignment_factor) reg num = *(CFA + offset) DW_CFA_offset指令采用两个操作数:一个寄存器号(用操作码编码)和一个无符号的LEB128常量,表示因数偏移量。 所需的操作是将由寄存器编号指示的寄存器的规则更改为offset(N)规则,其中N的值是`factored offset* data_alignment_factor`。 |
The DW_CFA_offset instruction takes two operands: a register number (encoded with the opcode) and an unsigned LEB128 constant representing a factored offset. The required action is to change the rule for the register indicated by the register number to be an offset(N) rule where the value of N is factored offset * data_alignment_factor. |
DW_CFA_offset_extended | offset(N) | offset = (factored offset * data_alignment_factor) reg num = *(CFA + offset) DW_CFA_offset_extended指令采用两个无符号的LEB128操作数,它们代表寄存器号和因数偏移量。 该指令与DW_CFA_offset相同,不同之处在于寄存器操作数的编码和大小。 |
The DW_CFA_offset_extended instruction takes two unsigned LEB128 operands representing a register number and a factored offset. This instruction is identical to DW_CFA_offset except for the encoding and size of the register operand. |
DW_CFA_offset_extended_sf | offset(N) | offset = (factored offset * data_alignment_factor) reg num = *(CFA + offset) DW_CFA_offset_extended_sf指令采用两个操作数:代表寄存器号的无符号LEB128值和有符号LEB128因数偏移量。 该指令与DW_CFA_offset_extended相同,不同之处在于第二个操作数有符号且乘以因数。 结果偏移量为factored_offset * data_alignment_factor。 |
The DW_CFA_offset_extended_sf instruction takes two operands: an unsigned LEB128 value representing a register number and a signed LEB128 factored offset. This instruction is identical to DW_CFA_offset_extended except that the second operand is signed and factored. The resulting offset is factored_offset * data_alignment_factor. |
DW_CFA_val_offset | val_offset(N) | offset = (factored offset * data_alignment_factor) reg num = CFA + offset DW_CFA_val_offset指令采用两个无符号的LEB128操作数,它们代表寄存器号和因数偏移量。 所需的操作是将寄存器编号指示的寄存器规则更改为val_offset(N)规则,其中N的值是factored_offset * data_alignment_factor。 |
The DW_CFA_val_offset instruction takes two unsigned LEB128 operands representing a register number and a factored offset. The required action is to change the rule for the register indicated by the register number to be a val_offset(N) rule where the value of N is factored_offset * data_alignment_factor. |
DW_CFA_val_offset_sf | val_offset(N) | offset = (factored offset * data_alignment_factor) reg num = CFA + offset DW_CFA_val_offset_sf指令采用两个操作数:代表寄存器号的无符号LEB128值和有符号LEB128因数偏移量。 该指令与DW_CFA_val_offset相同,不同之处在于第二个操作数有符号且乘以因数。 结果偏移量为factored_offset * data_alignment_factor。 |
The DW_CFA_val_offset_sf instruction takes two operands: an unsigned LEB128 value representing a register number and a signed LEB128 factored offset. This instruction is identical to DW_CFA_val_offset except that the second operand is signed and factored. The resulting offset is factored_offset * data_alignment_factor. |
DW_CFA_register | register(R) | reg num = R (second operands) DW_CFA_register指令采用两个无符号的LEB128操作数表示寄存器编号。 所需的操作是将第一个寄存器的规则设置为register(R),其中R是第二个寄存器。 |
The DW_CFA_register instruction takes two unsigned LEB128 operands representing register numbers. The required action is to set the rule for the first register to be register(R) where R is the second register. |
DW_CFA_expression | expression(E) | reg num = *(DWARF expression) DW_CFA_expression指令采用两个操作数:代表寄存器号的无符号LEB128值和代表DWARF表达式的DW_FORM_block值。 所需的操作是将由寄存器编号指示的寄存器的规则更改为expression(E)规则,其中E是DWARF表达式。 即,DWARF表达式计算地址。 在执行DWARF表达式之前,将CFA的值压入DWARF评估堆栈。 |
The DW_CFA_expression instruction takes two operands: an unsigned LEB128 value representing a register number, and a DW_FORM_block value representing a DWARF expression. The required action is to change the rule for the register indicated by the register number to be an expression(E) rule where E is the DWARF expression. That is, the DWARF expression computes the address. The value of the CFA is pushed on the DWARF evaluation stack prior to execution of the DWARF expression. |
DW_CFA_val_expression | val_expression(E) | reg num = DWARF expression DW_CFA_val_expression指令采用两个操作数:代表寄存器号的无符号LEB128值和代表DWARF表达式的DW_FORM_block值。 所需的操作是将由寄存器号指示的寄存器的规则更改为val_expression(E)规则,其中E是DWARF表达式。 也就是说,DWARF表达式计算给定寄存器的值。 在执行DWARF表达式之前,将CFA的值压入DWARF评估堆栈。 |
The DW_CFA_val_expression instruction takes two operands: an unsigned LEB128 value representing a register number, and a DW_FORM_block value representing a DWARF expression. The required action is to change the rule for the register indicated by the register number to be a val_expression(E) rule where E is the DWARF expression. That is, the DWARF expression computes the value of the given register. The value of the CFA is pushed on the DWARF evaluation stack prior to execution of the DWARF expression. |
DW_CFA_restore | initial_instructions in the CIE | reg num = initial_instructions in the CIE DW_CFA_restore指令采用单个操作数(用操作码编码),该操作数代表寄存器号。 所需的操作是将指示寄存器的规则更改为CIE中initial_instructions为其分配的规则。 |
The DW_CFA_restore instruction takes a single operand (encoded with the opcode) that represents a register number. The required action is to change the rule for the indicated register to the rule assigned it by the initial_instructions in the CIE. |
DW_CFA_restore_extended | initial_instructions in the CIE | reg num = initial_instructions in the CIE DW_CFA_restore_extended指令采用单个无符号LEB128操作数来表示寄存器号。 该指令与DW_CFA_restore相同,不同之处在于寄存器操作数的编码和大小。 |
The DW_CFA_restore_extended instruction takes a single unsigned LEB128 operand that represents a register number. This instruction is identical to DW_CFA_restore except for the encoding and size of the register operand. |
接下来的两条指令提供了备份(stack)和恢复(retrieve)完整寄存器状态的能力。
Instructions | Descript | Descript |
---|---|---|
DW_CFA_remember_state | DW_CFA_remember_state指令不接受任何操作数。 所需的操作是将每个寄存器的规则集压入隐式堆栈。 |
The DW_CFA_remember_state instruction takes no operands. The required action is to push the set of rules for every register onto an implicit stack. |
DW_CFA_restore_state | DW_CFA_restore_state指令不接受任何操作数。 所需的操作是将规则集从隐式堆栈中弹出,并将其放置在当前行(row)中。 |
The DW_CFA_restore_state instruction takes no operands. The required action is to pop the set of rules off the implicit stack and place them in the current row. |
Instructions | Descript | Descript |
---|---|---|
DW_CFA_nop | DW_CFA_nop指令没有操作数,也没有必需的操作。 它用作填充以使CIE或FDE大小合适。 | The DW_CFA_nop instruction has no operands and no required actions. It is used as padding to make a CIE or FDE an appropriate size. |
Instructions | Descript | Descript |
---|---|---|
DW_CFA_GNU_args_size | DW_CFA_GNU_args_size指令采用表示参数大小的无符号LEB128操作数。 该指令指定已推入堆栈的参数的总大小。 | The DW_CFA_GNU_args_size instruction takes an unsigned LEB128 operand representing an argument size. This instruction specifies the total of the size of the arguments which have been pushed onto the stack. |
DW_CFA_GNU_negative_offset_extended | offset = (factored offset * data_alignment_factor) reg num = *(CFA + offset) DW_CFA_def_cfa_sf指令采用两个操作数:代表寄存器号的无符号LEB128值和代表偏移量的无符号LEB128。 该指令与DW_CFA_offset_extended_sf相同,除了减去操作数以产生偏移量。 该指令已被DW_CFA_offset_extended_sf废弃。 |
The DW_CFA_def_cfa_sf instruction takes two operands: an unsigned LEB128 value representing a register number and an unsigned LEB128 which represents the magnitude of the offset. This instruction is identical to DW_CFA_offset_extended_sf except that the operand is subtracted to produce the offset. This instructions is obsoleted by DW_CFA_offset_extended_sf. |
各个指令具体的指令码编码如下:
Instruction | High 2Bits | Low 6 Bits | Operand 1 | Operand 2 |
---|---|---|---|---|
DW_CFA_advance_loc | 0x1 | delta | ||
DW_CFA_offset | 0x2 | register | ULEB128 offset | |
DW_CFA_restore | 0x3 | register | ||
DW_CFA_nop | 0 | 0 | ||
DW_CFA_set_loc | 0 | 0x01 | address | |
DW_CFA_advance_loc1 | 0 | 0x02 | 1-byte delta | |
DW_CFA_advance_loc2 | 0 | 0x03 | 2-byte delta | |
DW_CFA_advance_loc4 | 0 | 0x04 | 4-byte delta | |
DW_CFA_offset_extended | 0 | 0x05 | ULEB128 register | ULEB128 offset |
DW_CFA_restore_extended | 0 | 0x06 | ULEB128 register | |
DW_CFA_undefined | 0 | 0x07 | ULEB128 register | |
DW_CFA_same_value | 0 | 0x08 | ULEB128 register | |
DW_CFA_register | 0 | 0x09 | ULEB128 register | ULEB128 register |
DW_CFA_remember_state | 0 | 0x0a | ||
DW_CFA_restore_state | 0 | 0x0b | ||
DW_CFA_def_cfa | 0 | 0x0c | ULEB128 register | ULEB128 offset |
DW_CFA_def_cfa_register | 0 | 0x0d | ULEB128 register | |
DW_CFA_def_cfa_offset | 0 | 0x0e | ULEB128 offset | |
DW_CFA_def_cfa_expression | 0 | 0x0f | BLOCK | |
DW_CFA_expression | 0 | 0x10 | ULEB128 register | BLOCK |
DW_CFA_offset_extended_sf | 0 | 0x11 | ULEB128 register | SLEB128 offset |
DW_CFA_def_cfa_sf | 0 | 0x12 | ULEB128 register | SLEB128 offset |
DW_CFA_def_cfa_offset_sf | 0 | 0x13 | SLEB128 offset | |
DW_CFA_val_offset | 0 | 0x14 | ULEB128 | ULEB128 |
DW_CFA_val_offset_sf | 0 | 0x15 | ULEB128 | SLEB128 |
DW_CFA_val_expression | 0 | 0x16 | ULEB128 | BLOCK |
DW_CFA_lo_user | 0 | 0x1c | ||
DW_CFA_GNU_args_size | 0 | 0x2e | ULEB128 | |
DW_CFA_GNU_negative_offset_extended | 0 | 0x2f | ULEB128 | ULEB128 |
DW_CFA_hi_user | 0 | 0x3f |
DWARF表达式描述了在程序调试期间如何计算值或命名位置。 它们以对一堆值进行操作的DWARF操作表示。
所有DWARF操作都被编码为流,每个操作码后跟零个或多个文字操作数。 操作数的数量由操作码决定。
具体DWARF表达式的运算指令在这里就不展开,感兴趣可以参考DWARF2.5 DWARF Expressions
一节。
DW_CFA_def_cfa_expression
、DW_CFA_expression
、 DW_CFA_val_expression
这几条指令在计算时用到了DWARF表达式。同时它也有限制,以下DWARF运算符不能在此类操作数中使用:
因为DWARF expression
拥有完备的图灵计算的能力,所以eh_frame
容易成为攻击的后门。
CFA相关指令的解析过程如下。
首先为了给虚拟展开规则集设置一个确定位置(L1),人们在FDE标头中进行搜索,查看initial_location
和address_range
值以查看L1是否包含在FDE中。 如果存在,则:
展开帧时,使用者经常希望获得调用子例程的指令的地址。并非总是提供此信息。但是,通常虚拟展开表中的寄存器之一是返回地址。
如果在虚拟展开表中定义了返回地址寄存器,并且未定义其规则(例如,通过DW_CFA_undefined定义),则没有返回地址(return address),也没有调用地址(call address),并且堆栈激活的虚拟展开已完成。
在大多数情况下,返回地址与调用地址在同一上下文中,但不必如此,特别是如果生产者以某种方式知道该调用永不返回。 “返回地址”的上下文可能在不同的行中,在不同的词法块中或在调用子例程的末尾。如果使用者假定它与呼叫地址处于同一上下文中,则展开可能会失败。
对于具有恒定长度指令的体系结构,其中返回地址紧跟在调用指令之后,一种简单的解决方案是从返回地址中减去指令的长度以获得调用指令。对于具有可变长度指令的体系结构(例如x86),这是不可能的。但是,从返回地址减去1尽管不能保证提供准确的调用地址,但通常会在与调用地址相同的上下文中生成一个地址,通常就足够了。
以下示例使用摩托罗拉88000风格的假想RISC机器。它有以下特性:
•存储器是按字节寻址的。
•指令均为4个字节,字对齐。
•指令操作数通常采用以下形式:,,
•加载和存储指令的地址是通过将源寄存器的内容与常量相加得出的。
•有8个4字节寄存器:
R0 始终为0
R1 保存调用时的返回地址
R2-R3 临时寄存器(在调用时不保护)
R4-R6 在调用时保护
R7 堆栈指针
•堆栈向负方向增长。
•体系结构ABI委员会指定堆栈指针(R7)与CFA相同
以下是来自名为foo的子例程的两个代码片段,该子例程使用帧指针(除了堆栈指针之外)。 第一列的值是字节地址。
表示以字节为单位的堆栈帧大小,即12。
;; 保存现场,保护 R1/R6/R4
;; start prologue
foo sub R7, R7, ; Allocate frame
foo+4 store R1, R7, (-4) ; Save the return address
foo+8 store R6, R7, (-8) ; Save R6
foo+12 add R6, R7, 0 ; R6 is now the Frame ptr
foo+16 store R4, R6, (-12) ; Save a preserved reg
;; R5 在子函数中没有调用,所以没有保护
;; This subroutine does not change R5
...
;; 恢复现场,恢复 R4/R6/R1
;; Start epilogue (R7 is returned to entry value)
foo+64 load R4, R6, (-12) ; Restore R4
foo+68 load R6, R7, (-8) ; Restore R6
foo+72 load R1, R7, (-4) ; Restore return address
foo+76 add R7, R7, ; Deallocate frame
foo+80 jump R1 ; Return
foo+84
以上代码最终生成的CFI Table如下:
图中的注释如下:
1. R8 is the return address
2. s = same_value rule
3. u = undefined rule
4. rN = register(N) rule
5. cN = offset(N) rule
6. a = architectural rule
图中的注释如下:
1. = frame size
2. = code alignment factor
3. = data alignment factor
使用gcc -g
生成的DWARF信息存储在debug_*
类型的section,我们可以使用readelf -wi xxx
查看debug_info
段,或者dwarfdump xxx
查看debug信息。
无论是否有-g
选项,gcc默认都会生成.eh_frame
和.eh_frame_hdr
section。-fno-asynchronous-unwind-tables
选项可以禁止生成.eh_frame
和.eh_frame_hdr
section。
在LSB(Linux Standard Base)中对.eh_frame格式有详细的描述。
.eh_frame
section 包含一个或者多个CFI(Call Frame Information)记录。每个CFI包含一个CIE(Common Information Entry Record)记录,每个CIE包含一个或者多个FDE(Frame Description Entry)记录。
通常情况下,CIE对应一个文件,FDE对应一个函数。
Field | Description |
---|---|
Length | Required |
Extended Length | Optional |
CIE ID | Required |
Version | Required |
Augmentation String | Required |
Code Alignment Factor | Required |
Data Alignment Factor | Required |
Return Address Register | Required |
Augmentation Data Length | Optional |
Augmentation Data | Optional |
Initial Instructions | Required |
Padding |
具体解析如下:
1、Length。读取4个字节。如果它们不是0xffffffff,则它们是CIE或FDE记录的长度。否则,接下来的64位将保留长度,这是64位DWARF格式。就像.debug_frame。
2、ID。一个4字节无符号值,对于CIE,它是0。对于FDE,它是从该字段到与该FDE关联的CIE开头的字节偏移。字节偏移量到达CIE的长度记录。正值向后退;也就是说,您必须从当前字节位置减去ID字段的值才能获得CIE位置。这与.debug_frame不同,因为偏移是相对的,而不是.debug_frame节中的偏移。
3、Version。1字节的CIE版本。在撰写本文时,该值为1或3。
4、Augmentation String。扩充参数字符串,以NULL结尾。这是一个字符序列。非常老版本的gcc在这里使用字符串“ eh”,但我不会对此进行记录。这将在下面进一步描述。
5、Code Alignment Factor。代码对齐因子,无符号LEB128(LEB128是数字的DWARF编码,在此不再赘述)。对于.eh_frame,该值应始终为1。
6、Data Alignment Factor。数据对齐因子,带符号的LEB128。如.debug_frame中所示,这是偏移指令之外的常数。
7、Return Address Register。返回地址寄存器。在CIE版本1中,这是一个字节。在CIE版本3中,这是未签名的LEB128。这表明框架表中的哪一列代表返回地址。
8、下面的字段含义,取决于Augmentation String
的定义:
'z’可以作为字符串的第一个字符出现。如果存在,则应显示Augmentation Data
字段。 扩展数据的内容应根据扩展字符串中的其他字符来解释。
如果扩展字符串以’z’开头,下一个字段是一个无符号的LEB128即扩展数据的长度Augmentation Data Length
,将其补齐以使CIE在地址边界处结束。如果看到无法识别的扩充字符,则用于跳到扩充数据augmentation data
的末尾。
字符串的第一个字符之后的任何位置都可能存在’L’。 仅当’z’是字符串的第一个字符时,才可以显示该字符。
如果存在,则表示CIE的扩展数据Augmentation Data
中存在一个参数argument
,而FDE的扩展数据Augmentation Data
中也存在相应的参数argument
。
CIE的扩展数据中的参数为1字节,表示用于FDE的扩展数据中的参数的指针编码。这些编码是DW_EH_PE_xxx值(稍后描述),默认值为DW_EH_PE_absptr。
FDE的扩展数据是特定于语言的数据区(LSDA)的地址,LSDA指针的大小由所使用的指针编码指定。
字符串的第一个字符之后的任何位置都可以出现’R’。 仅当’z’是字符串的第一个字符时,才可以显示该字符。
如果存在,则CIE的扩展数据应包括一个1字节的参数,该参数表示FDE中使用的地址指针的指针编码。这些是DW_EH_PE_xxx值。默认值为DW_EH_PE_absptr。
扩充字符串中的字符’S’表示此CIE表示用于调用信号处理程序的堆栈帧。展开堆栈时,信号堆栈帧的处理方式略有不同:指令指针被假定为在下一条要执行的指令之前而不是在其之后。
字符串的第一个字符之后的任何位置都可能存在“ P”。 仅当“ z”是字符串的第一个字符时,才可以显示该字符。
如果存在,则表示在CIE的扩展数据中存在两个参数。
第一个参数为1字节,代表用于第二个参数的指针编码(DW_EH_PE_xxx)。
第二个参数是个性例程处理程序的地址。
个性例程用于处理语言和特定于供应商的任务。 系统展开库接口通过指向个性例程的指针访问特定于语言的异常处理语义。 个性例程没有特定于ABI的名称。 个性例程指针的大小由所使用的指针编码指定。
9、Initial Instructions。其余字节是DW_CFA_xxx操作码数组,它们定义帧表的初始值。然后,根据需要跟在DW_CFA_nop填充字节之后,以匹配CIE的总长度。
Field | Description |
---|---|
Length | Required |
Extended Length | Optional |
CIE Pointer | Required |
PC Begin | Required |
PC Range | Required |
Augmentation Data Length | Optional |
Augmentation Data | Optional |
Call Frame Instructions | Required |
Padding |
具体解析如下:
Augmentation Data
中包含指向LSDA的指针,该指针由CIE指定编码(LSDA encoding)。Encoding | Field |
---|---|
unsigned byte | version |
unsigned byte | eh_frame_ptr_enc |
unsigned byte | fde_count_enc |
unsigned byte | table_enc |
encoded | eh_frame_ptr |
encoded | fde_count |
binary search table |
.eh_frame_hdr
section包含.eh_frame
的额外信息。
具体解析如下:
.eh_frame
section开始指针。DWARF异常标头编码用于描述.eh_frame和.eh_frame_hdr部分中使用的数据类型。 高4位指示如何应用该值, 低4位表示数据格式。
Name | Value | Meaning | Descript |
---|---|---|---|
DW_EH_PE_absptr | 0x00 | Pointer (long) | The Value is a literal pointer whose size is determined by the architecture. |
DW_EH_PE_uleb128 | 0x01 | Unsigned LEB128 | Unsigned value is encoded using the Little Endian Base 128 (LEB128) as defined by DWARF Debugging Information Format, Revision 2.0.0. |
DW_EH_PE_udata2 | 0x02 | A 2 bytes unsigned value. | |
DW_EH_PE_udata4 | 0x03 | A 4 bytes unsigned value. | |
DW_EH_PE_udata8 | 0x04 | An 8 bytes unsigned value. | |
DW_EH_PE_sleb128 | 0x09 | Signed LEB128 | Signed value is encoded using the Little Endian Base 128 (LEB128) as defined by DWARF Debugging Information Format, Revision 2.0.0. |
DW_EH_PE_sdata2 | 0x0A | A 2 bytes signed value. | |
DW_EH_PE_sdata4 | 0x0B | A 4 bytes signed value. | |
DW_EH_PE_sdata8 | 0x0C | An 8 bytes signed value. |
Name | Value | Meaning | Descript |
---|---|---|---|
DW_EH_PE_pcrel | 0x10 | Value is relative to the current program counter. | 值是相对于当前程序计数器的。 |
DW_EH_PE_textrel | 0x20 | Value is relative to the beginning of the .text section. | 值是相对于.text section的。 |
DW_EH_PE_datarel | 0x30 | Value is relative to the beginning of the .got or .eh_frame_hdr section. | 值是相对于.got或者.eh_frame_hdr section的。 |
DW_EH_PE_funcrel | 0x40 | Value is relative to the beginning of the function. | 值是相对于当前函数的。 |
DW_EH_PE_aligned | 0x50 | Value is aligned to an address unit sized boundary. | 值与地址单元大小的边界对齐。 |
Name | Value | Meaning | Descript |
---|---|---|---|
DW_EH_PE_omit | 0xFF | indicate that no value is present |
一段简单的c语言代码:
$ cat test.c
#include
int test(int x)
{
int c =10;
return x*c;
}
void main()
{
int a,b;
a = 10;
b = 11;
printf("hello test~, %d\n", a+b);
a = test(a+b);
}
$ gcc test.c
可以使用readelf -wF xxx
命令来查看elf文件中的.eh_frame
解析信息:
$ readelf -wF a.out
Contents of the .eh_frame section:
00000000 0000000000000014 00000000 CIE "zR" cf=1 df=-8 ra=16
LOC CFA ra
0000000000000000 rsp+8 u
...
000000c8 0000000000000044 0000009c FDE cie=00000030 pc=00000000000006b0..0000000000000715
LOC CFA rbx rbp r12 r13 r14 r15 ra
00000000000006b0 rsp+8 u u u u u u c-8
00000000000006b2 rsp+16 u u u u u c-16 c-8
00000000000006b4 rsp+24 u u u u c-24 c-16 c-8
00000000000006b9 rsp+32 u u u c-32 c-24 c-16 c-8
00000000000006bb rsp+40 u u c-40 c-32 c-24 c-16 c-8
00000000000006c3 rsp+48 u c-48 c-40 c-32 c-24 c-16 c-8
00000000000006cb rsp+56 c-56 c-48 c-40 c-32 c-24 c-16 c-8
00000000000006d8 rsp+64 c-56 c-48 c-40 c-32 c-24 c-16 c-8
000000000000070a rsp+56 c-56 c-48 c-40 c-32 c-24 c-16 c-8
000000000000070b rsp+48 c-56 c-48 c-40 c-32 c-24 c-16 c-8
000000000000070c rsp+40 c-56 c-48 c-40 c-32 c-24 c-16 c-8
000000000000070e rsp+32 c-56 c-48 c-40 c-32 c-24 c-16 c-8
0000000000000710 rsp+24 c-56 c-48 c-40 c-32 c-24 c-16 c-8
0000000000000712 rsp+16 c-56 c-48 c-40 c-32 c-24 c-16 c-8
0000000000000714 rsp+8 c-56 c-48 c-40 c-32 c-24 c-16 c-8
可以看到.eh_frame
总体架构就是由CIE
和FDE
组成的。其中最核心的就是FDE的组织,读懂它条目的所有字段基本就理解了unwind的含义:
CFA (Canonical Frame Address, which is the address of %rsp in the caller frame),CFA就是上一级调用者的堆栈指针。
上图详细说明了怎么样利用.eh_frame
来进行栈回溯:
.eh_frame
中找到对应的条目,根据条目提供的各种偏移计算其他信息。CFA = rsp+4
,把当前rsp+4得到CFA的值。再根据CFA的值计算出通用寄存器和返回地址在堆栈中的位置。也可以使用readelf -wf xxx
命令来查看elf文件中的.eh_frame
原始信息:
$ readelf -wf a.out
...
000000c8 0000000000000044 0000009c FDE cie=00000030 pc=00000000000006b0..0000000000000715
DW_CFA_advance_loc: 2 to 00000000000006b2
DW_CFA_def_cfa_offset: 16
DW_CFA_offset: r15 (r15) at cfa-16
DW_CFA_advance_loc: 2 to 00000000000006b4
DW_CFA_def_cfa_offset: 24
DW_CFA_offset: r14 (r14) at cfa-24
DW_CFA_advance_loc: 5 to 00000000000006b9
DW_CFA_def_cfa_offset: 32
DW_CFA_offset: r13 (r13) at cfa-32
DW_CFA_advance_loc: 2 to 00000000000006bb
DW_CFA_def_cfa_offset: 40
DW_CFA_offset: r12 (r12) at cfa-40
DW_CFA_advance_loc: 8 to 00000000000006c3
DW_CFA_def_cfa_offset: 48
DW_CFA_offset: r6 (rbp) at cfa-48
DW_CFA_advance_loc: 8 to 00000000000006cb
DW_CFA_def_cfa_offset: 56
DW_CFA_offset: r3 (rbx) at cfa-56
DW_CFA_advance_loc: 13 to 00000000000006d8
DW_CFA_def_cfa_offset: 64
DW_CFA_advance_loc: 50 to 000000000000070a
DW_CFA_def_cfa_offset: 56
DW_CFA_advance_loc: 1 to 000000000000070b
DW_CFA_def_cfa_offset: 48
DW_CFA_advance_loc: 1 to 000000000000070c
DW_CFA_def_cfa_offset: 40
DW_CFA_advance_loc: 2 to 000000000000070e
DW_CFA_def_cfa_offset: 32
DW_CFA_advance_loc: 2 to 0000000000000710
DW_CFA_def_cfa_offset: 24
DW_CFA_advance_loc: 2 to 0000000000000712
DW_CFA_def_cfa_offset: 16
DW_CFA_advance_loc: 2 to 0000000000000714
DW_CFA_def_cfa_offset: 8
DW_CFA_nop
这些信息就是.eh_frame
的原始格式,是GAS(GCC Assembler)汇编编译器搜集汇编代码中所有的CFI伪指令汇总而成。
其中CIE
、FDE
的格式在第一节中已经介绍,不好理解的是DW_CFA_*
开头的这些指令,这些指令的具体含义可以查看DWARF6.4.2 Call Frame Instructions
一节或者CFI(Call Frame Information)/ARM CFI的定义。
而上一节使用readelf -wF xxx
命令解析了这些信息,我们初步看看对应关系:
在GAS(GCC Assembler)汇编编译器CFI(Call Frame Information)/ARM CFI文档中,对所有CFI伪指令的含义有详细描述。或者查看查看DWARF6.4.2 Call Frame Instructions
一节。
我们介绍其中的一些重点指令:
.cfi_sections
用来描述产生的目标是.eh_frame section
and/or .debug_frame section
。
如果section_list
为.eh_frame,.eh_frame则产生,如果section_list为.debug_frame,.debug_frame则产生。两者同时产生.eh_frame, .debug_frame
。如果不使用此指令,则默认值为.cfi_sections .eh_frame。
.cfi_startproc
用在每个函数的入口处。
.cfi_endproc
用在函数的结束处,和.cfi_startproc
对应。
用来定义CFA的计算规则:
CFA = register + offset
默认基址寄存器register = rsp。
x86_64的register编号从0-15对应下表。rbp
的register编号为6,rsp
的register编号为7。
%rax,%rbx,%rcx,%rdx,%esi,%edi,%rbp,%rsp,%r8,%r9,%r10,%r11,%r12,%r13,%r14,%r15
完整x86_64的DWARF Register Number Mapping可以参考x86_64 ABI 3.7 Stack Unwind Algorithm
一节。
用来修改修改CFA计算规则,基址寄存器从rsp
转移到新的register
。
register = new register
用来修改修改CFA计算规则,基址寄存器不变,offset变化:
CFA = register + offset(new)
用来修改修改CFA计算规则,在上一个offset的基础上加上相对的offset:
CFA = register + pre_offset + offset(new)
寄存器register上一次值保存在CFA偏移offset的堆栈中:
*(CFA + offset) = register(pre_value)
基址寄存器恢复成函数开始时的默认寄存器,默认是rsp
。
更多的指令请查看手册,或者在实际用例中理解。
如果我们直接编写汇编代码,需要自己手工添加CFI伪指令,否者堆栈回溯信息会出错。例如:
kernel\arch\x86\kernel\entry_64.S:
ENTRY(system_call)
CFI_STARTPROC simple
CFI_SIGNAL_FRAME
CFI_DEF_CFA rsp,KERNEL_STACK_OFFSET
CFI_REGISTER rip,rcx
/*CFI_REGISTER rflags,r11*/
SWAPGS_UNSAFE_STACK
#define CFI_STARTPROC .cfi_startproc
#define CFI_ENDPROC .cfi_endproc
#define CFI_DEF_CFA .cfi_def_cfa
#define CFI_DEF_CFA_REGISTER .cfi_def_cfa_register
#define CFI_DEF_CFA_OFFSET .cfi_def_cfa_offset
#define CFI_ADJUST_CFA_OFFSET .cfi_adjust_cfa_offset
#define CFI_OFFSET .cfi_offset
#define CFI_REL_OFFSET .cfi_rel_offset
#define CFI_REGISTER .cfi_register
#define CFI_RESTORE .cfi_restore
#define CFI_REMEMBER_STATE .cfi_remember_state
#define CFI_RESTORE_STATE .cfi_restore_state
#define CFI_UNDEFINED .cfi_undefined
#define CFI_SAME_VALUE .cfi_same_value
我们使用c语言编写时,gcc会自动帮我们产生CFI伪指令:
gcc -S test.c // c语言生成汇编代码
vim test.s // 查看汇编代码
我们对汇编指令中的CFI伪指令做一个解析:
使用readelf -wF xxx
读出对应FDE的信息:
000000a8 000000000000001c 0000007c FDE cie=00000030 pc=0000000000000661..00000000000006a7
LOC CFA rbp ra
0000000000000661 rsp+8 u c-8
0000000000000662 rsp+16 c-16 c-8
0000000000000665 rbp+16 c-16 c-8
00000000000006a6 rsp+8 c-16 c-8
在内核中已经有现成的代码实现了kernel代码的unwind方式栈回溯。
.eh_frame
的加载内核vmlinux也是一个elf文件,它编译完成后默认也生成了.eh_frame
和.eh_frame_hdr
section。这两个段运行的时候被一起加载到内存,运行的时候需要有方法能找到它们。
内核在链接脚本vmlinux.lds.h
中制定了定义:__start_unwind_hdr
和__end_unwind_hdr
变量用来标识.eh_frame_hdr
的位置,__start_unwind
和__end_unwind
变量用来标识.eh_frame
的位置。
kernel\include\asm-generic\vmlinux.lds.h
#ifdef CONFIG_STACK_UNWIND
#define EH_FRAME \
/* Unwind data binary search table */ \
. = ALIGN(8); \
.eh_frame_hdr : AT(ADDR(.eh_frame_hdr) - LOAD_OFFSET) { \
VMLINUX_SYMBOL(__start_unwind_hdr) = .; \
*(.eh_frame_hdr) \
VMLINUX_SYMBOL(__end_unwind_hdr) = .; \
} \
/* Unwind data */ \
. = ALIGN(8); \
.eh_frame : AT(ADDR(.eh_frame) - LOAD_OFFSET) { \
VMLINUX_SYMBOL(__start_unwind) = .; \
*(.eh_frame) \
VMLINUX_SYMBOL(__end_unwind) = .; \
}
#else
#define EH_FRAME
#endif
在系统启动时,把内核的.eh_frame
和.eh_frame_hdr
section当成一张table
管理起来:
start_kernel()
↓
void __init unwind_init(void)
{
init_unwind_table(&root_table, "kernel",
_text, _end - _text,
NULL, 0,
__start_unwind, __end_unwind - __start_unwind,
__start_unwind_hdr, __end_unwind_hdr - __start_unwind_hdr);
}
↓
static void init_unwind_table(struct unwind_table *table,
const char *name,
const void *core_start,
unsigned long core_size,
const void *init_start,
unsigned long init_size,
const void *table_start,
unsigned long table_size,
const u8 *header_start,
unsigned long header_size)
{
const u8 *ptr = header_start + 4;
const u8 *end = header_start + header_size;
table->core.pc = (unsigned long)core_start;
table->core.range = core_size;
table->init.pc = (unsigned long)init_start;
table->init.range = init_size;
table->address = table_start;
table->size = table_size;
/* See if the linker provided table looks valid. */
if (header_size <= 4
|| header_start[0] != 1
|| (void *)read_pointer(&ptr, end, header_start[1], 0, 0)
!= table_start
|| !read_pointer(&ptr, end, header_start[2], 0, 0)
|| !read_pointer(&ptr, end, header_start[3], 0,
(unsigned long)header_start)
|| !read_pointer(&ptr, end, header_start[3], 0,
(unsigned long)header_start))
header_start = NULL;
table->hdrsz = header_size;
smp_wmb();
table->header = header_start;
table->link = NULL;
table->name = name;
}
驱动模块在加载的时候也需要把自己的.eh_frame
加进来,接口函数unwind_add_table()
,这样就形成了一张unwind信息table链表。
通常我们调用dump_stack()来打印出内核的当前调用栈,其具体实现如下:
dump_stack()
↓
show_trace()
↓
void
show_trace_log_lvl(struct task_struct *task, struct pt_regs *regs,
unsigned long *stack, unsigned long bp, char *log_lvl)
{
printk("%sCall Trace:\n", log_lvl);
/* 指定了ops为print_trace_ops */
dump_trace(task, regs, stack, bp, &print_trace_ops, log_lvl);
}
↓
dump_trace()
↓
try_stack_unwind()
↓
kernel\arch\x86\kernel\entry_64.S:
#ifdef CONFIG_STACK_UNWIND
/*
%rdi 参数1:unwind_frame_info
%rsi 参数2:unwind_callback_fn = dump_trace_unwind()
%rdx 参数3:stacktrace_ops
%rcx 参数4:void *data = NULL
*/
ENTRY(arch_unwind_init_running)
CFI_STARTPROC
movq %r15, R15(%rdi) /* 给参数1赋值:构造本进程的寄存器值 unwind_frame_info->regs */
movq %r14, R14(%rdi)
xchgq %rsi, %rdx
movq %r13, R13(%rdi)
movq %r12, R12(%rdi)
xorl %eax, %eax
movq %rbp, RBP(%rdi)
movq %rbx, RBX(%rdi)
movq (%rsp), %r9
xchgq %rdx, %rcx /* 交换参数3和参数4 */
movq %rax, R11(%rdi)
movq %rax, R10(%rdi)
movq %rax, R9(%rdi)
movq %rax, R8(%rdi)
movq %rax, RAX(%rdi)
movq %rax, RCX(%rdi)
movq %rax, RDX(%rdi)
movq %rax, RSI(%rdi)
movq %rax, RDI(%rdi)
movq %rax, ORIG_RAX(%rdi)
movq %r9, RIP(%rdi)
leaq 8(%rsp), %r9
movq $__KERNEL_CS, CS(%rdi)
movq %rax, EFLAGS(%rdi)
movq %r9, RSP(%rdi)
movq $__KERNEL_DS, SS(%rdi)
jmpq *%rcx /* 使用构造好的参数来调用dump_trace_unwind() */
CFI_ENDPROC
END(arch_unwind_init_running)
#endif
↓
int asmlinkage dump_trace_unwind(struct unwind_frame_info *info,
const struct stacktrace_ops *ops, void *data)
{
int n = 0;
#ifdef CONFIG_STACK_UNWIND
unsigned long sp = UNW_SP(info);
if (arch_unw_user_mode(info))
return -1;
/* (1) 循环调用unwind()函数
获取每一层堆栈的寄存器信息 info->regs
*/
while (unwind(info) == 0 && UNW_PC(info)) {
n++;
/* (1.1) 调用ops->address来处理每一层的pc值 */
ops->address(data, UNW_PC(info), 1);
/* (1.2) 是否是用户态判断 */
if (arch_unw_user_mode(info))
break;
/* (1.3) sp合法性判断 */
if ((sp & ~(PAGE_SIZE - 1)) == (UNW_SP(info) & ~(PAGE_SIZE - 1))
&& sp > UNW_SP(info))
break;
sp = UNW_SP(info);
}
#endif
return n;
}
最核心的函数unwind(),所有unwind的具体算法都在其中:
int unwind(struct unwind_frame_info *frame)
{
#define FRAME_REG(r, t) (((t *)frame)[reg_info[r].offs])
const u32 *fde = NULL, *cie = NULL;
const u8 *ptr = NULL, *end = NULL;
unsigned long pc = UNW_PC(frame) - frame->call_frame, sp;
unsigned long startLoc = 0, endLoc = 0, cfa;
unsigned i;
signed ptrType = -1;
uleb128_t retAddrReg = 0;
const struct unwind_table *table;
struct unwind_state state;
if (UNW_PC(frame) == 0)
return -EINVAL;
/* (1) 根据pc找到对应的unwind table
查找PC对应的FDE
*/
if ((table = find_table(pc)) != NULL
&& !(table->size & (sizeof(*fde) - 1))) {
const u8 *hdr = table->header;
unsigned long tableSize;
smp_rmb();
/* (1.1) 根据`.eh_frame_hdr`查找到PC对应的FDE
`.eh_frame_hdr`的格式
--------------------------------------------------------------------
Field | expression
--------------------------------------------------------------------
version | hdr[0]
eh_frame_ptr_enc | hdr[1]
fde_count_enc | hdr[2]
table_enc | hdr[3]
eh_frame_ptr | read_pointer(&ptr, end, hdr[1], 0, 0)
fde_count | read_pointer(&ptr, end, hdr[2], 0, 0))
binary search table | (2 * tableSize)*fde_count ,(location, address)
---------------------------------------------------------------------
`binary search table`是一张FDE的查询表:
每张表有fde_count个条目
每个条目有两个元素(location, address),每个元素的大小为tableSize
表以降序来排序
*/
if (hdr && hdr[0] == 1) {
/* 根据`table_enc`计算出tableSize大小 */
switch (hdr[3] & DW_EH_PE_FORM) {
case DW_EH_PE_native: tableSize = sizeof(unsigned long); break;
case DW_EH_PE_data2: tableSize = 2; break;
case DW_EH_PE_data4: tableSize = 4; break;
case DW_EH_PE_data8: tableSize = 8; break;
default: tableSize = 0; break;
}
ptr = hdr + 4;
end = hdr + table->hdrsz;
if (tableSize
/* 确认`eh_frame_ptr`指向的是`.eh_frame` */
&& read_pointer(&ptr, end, hdr[1], 0, 0)
== (unsigned long)table->address
/* 计算`fde_count`存储到i变量,并且进行合法性判断 */
&& (i = read_pointer(&ptr, end, hdr[2], 0, 0)) > 0
&& i == (end - ptr) / (2 * tableSize)
&& !((end - ptr) % (2 * tableSize))) {
/* 使用二分法在`binary search table`入口表中查询,
根据PC找到对应的FDE地址
*/
do {
const u8 *cur = ptr + (i / 2) * (2 * tableSize);
startLoc = read_pointer(&cur,
cur + tableSize,
hdr[3], 0,
(unsigned long)hdr);
if (pc < startLoc)
i /= 2;
else {
ptr = cur - tableSize;
i = (i + 1) / 2;
}
} while (startLoc && i > 1);
if (i == 1
&& (startLoc = read_pointer(&ptr,
ptr + tableSize,
hdr[3], 0,
(unsigned long)hdr)) != 0
&& pc >= startLoc)
fde = (void *)read_pointer(&ptr,
ptr + tableSize,
hdr[3], 0,
(unsigned long)hdr);
}
}
if (hdr && !fde)
dprintk(3, "Binary lookup for %lx failed.", pc);
/* (1.2) 解析`FDE`,确认PC值是否在FDE的范围中
--------------------------------------------------------------------
Field | expression
--------------------------|------------------------------------------
Length | fde
CIE Pointer | (fde + 1)
PC Begin | ptr = (const u8 *)(fde + 2);
| read_pointer(&ptr, (const u8 *)(fde + 1) + *fde, ptrType, 0, 0)
PC Range | read_pointer(&ptr, (const u8 *)(fde + 1) + *fde, ptrType, 0, 0)
Augmentation Data Length |
Augmentation Data |
Call Frame Instructions |
Padding |
---------------------------------------------------------------------
*/
if (fde != NULL) {
/* 根据fde中的指针找到对应cie */
cie = cie_for_fde(fde, table);
ptr = (const u8 *)(fde + 2);
if (cie != NULL
&& cie != &bad_cie
&& cie != ¬_fde
/* 得到cie中的指针类型 */
&& (ptrType = fde_pointer_type(cie)) >= 0
/* 读出`PC Begin`,确认`.eh_frame_hdr`中的PC基址和FDE中的PC基址相等 */
&& read_pointer(&ptr,
(const u8 *)(fde + 1) + *fde,
ptrType, 0, 0) == startLoc) {
if (!(ptrType & DW_EH_PE_indirect))
ptrType &= DW_EH_PE_FORM|DW_EH_PE_signed;
/* 读出`PC Range`,得到FDE的结束PC值 */
endLoc = startLoc
+ read_pointer(&ptr,
(const u8 *)(fde + 1) + *fde,
ptrType, 0, 0);
if (pc >= endLoc)
fde = NULL;
} else
fde = NULL;
if (!fde)
dprintk(1, "Binary lookup result for %lx discarded.", pc);
}
/* (1.3) 如果从`.eh_frame_hdr`中查找PC对应的FDE失败 (快路径)
尝试直接从`.eh_frame`中查找PC对应的FDE (慢路径)
*/
if (fde == NULL) {
for (fde = table->address, tableSize = table->size;
cie = NULL, tableSize > sizeof(*fde)
&& tableSize - sizeof(*fde) >= *fde;
tableSize -= sizeof(*fde) + *fde,
fde += 1 + *fde / sizeof(*fde)) {
cie = cie_for_fde(fde, table);
if (cie == &bad_cie) {
cie = NULL;
break;
}
if (cie == NULL
|| cie == ¬_fde
|| (ptrType = fde_pointer_type(cie)) < 0)
continue;
ptr = (const u8 *)(fde + 2);
startLoc = read_pointer(&ptr,
(const u8 *)(fde + 1) + *fde,
ptrType, 0, 0);
if (!startLoc)
continue;
if (!(ptrType & DW_EH_PE_indirect))
ptrType &= DW_EH_PE_FORM|DW_EH_PE_signed;
endLoc = startLoc
+ read_pointer(&ptr,
(const u8 *)(fde + 1) + *fde,
ptrType, 0, 0);
if (pc >= startLoc && pc < endLoc)
break;
}
if (!fde)
dprintk(3, "Linear lookup for %lx failed.", pc);
}
}
/* (2) 解析出`CIE`各字段
--------------------------------------------------------------------
Field | expression
--------------------------|------------------------------------------
Length | cie
CIE ID | (cie + 1)
Version | ptr = (const u8 *)(cie + 2)
Augmentation String |
Code Alignment Factor |
Data Alignment Factor |
Return Address Register |
Augmentation Data Length |
Augmentation Data |
Initial Instructions |
Padding |
---------------------------------------------------------------------
(2.1) 解析出CIE中的`Length`、`CIE ID`、`Version`、`Augmentation String`这几个字段
*/
if (cie != NULL) {
memset(&state, 0, sizeof(state));
state.cieEnd = ptr; /* keep here temporarily */
ptr = (const u8 *)(cie + 2);
end = (const u8 *)(cie + 1) + *cie;
frame->call_frame = 1;
/* 解析出`Version` */
if ((state.version = *ptr) != 1)
cie = NULL; /* unsupported version */
/* 解析出`Augmentation String` */
else if (*++ptr) {
/* check if augmentation size is first (and thus present) */
if (*ptr == 'z') {
while (++ptr < end && *ptr) {
switch (*ptr) {
/* check for ignorable (or already handled)
* nul-terminated augmentation string */
case 'L':
case 'P':
case 'R':
continue;
case 'S':
frame->call_frame = 0;
continue;
default:
break;
}
break;
}
}
if (ptr >= end || *ptr)
cie = NULL;
}
if (!cie)
dprintk(1, "CIE unusable (%p,%p).", ptr, end);
++ptr;
}
/* (2.2) 解析出CIE中的`Code Alignment Factor`、`Data Alignment Factor`、
`Return Address Register`、`Augmentation Data Length`、`Augmentation Data`这几个字段
*/
if (cie != NULL) {
/* get code aligment factor */
/* 解析出`Code Alignment Factor` */
state.codeAlign = get_uleb128(&ptr, end);
/* get data aligment factor */
/* 解析出`Data Alignment Factor` */
state.dataAlign = get_sleb128(&ptr, end);
if (state.codeAlign == 0 || state.dataAlign == 0 || ptr >= end)
cie = NULL;
else if (UNW_PC(frame) % state.codeAlign
|| UNW_SP(frame) % sleb128abs(state.dataAlign)) {
dprintk(1, "Input pointer(s) misaligned (%lx,%lx).",
UNW_PC(frame), UNW_SP(frame));
return -EPERM;
} else {
/* 解析出`Return Address Register` */
retAddrReg = state.version <= 1 ? *ptr++ : get_uleb128(&ptr, end);
/* skip augmentation */
/* 解析出`Augmentation Data Length` */
if (((const char *)(cie + 2))[1] == 'z') {
uleb128_t augSize = get_uleb128(&ptr, end);
ptr += augSize;
}
if (ptr > end
|| retAddrReg >= ARRAY_SIZE(reg_info)
|| REG_INVALID(retAddrReg)
|| reg_info[retAddrReg].width != sizeof(unsigned long))
cie = NULL;
}
if (!cie)
dprintk(1, "CIE validation failed (%p,%p).", ptr, end);
}
/* (2.3) 解析出CIE中的`Initial Instructions`位置
和FDE中的`Call Frame Instructions`位置
*/
if (cie != NULL) {
state.cieStart = ptr; /* state.cieStart: CIE的`Initial Instructions`开始 */
ptr = state.cieEnd;
state.cieEnd = end; /* state.cieEnd: CIE的`Initial Instructions`结束 */
end = (const u8 *)(fde + 1) + *fde; /* end: FDE的`Call Frame Instructions`结束 */
/* skip augmentation */
if (((const char *)(cie + 2))[1] == 'z') {
uleb128_t augSize = get_uleb128(&ptr, end);
if ((ptr += augSize) > end) /* ptr: FDE的`Call Frame Instructions`开始 */
fde = NULL;
}
if (!fde)
dprintk(1, "FDE validation failed (%p,%p).", ptr, end);
}
/* (3) 如果CIE和FDE任一为空,出错返回 */
if (cie == NULL || fde == NULL) {
#ifdef CONFIG_FRAME_POINTER
unsigned long top = TSK_STACK_TOP(frame->task);
unsigned long bottom = STACK_BOTTOM(frame->task);
unsigned long fp = UNW_FP(frame);
unsigned long sp = UNW_SP(frame);
unsigned long link;
if ((sp | fp) & (sizeof(unsigned long) - 1))
return -EPERM;
# if FRAME_RETADDR_OFFSET < 0
if (!(sp < top && fp <= sp && bottom < fp))
# else
if (!(sp > top && fp >= sp && bottom > fp))
# endif
return -ENXIO;
if (probe_kernel_address(fp + FRAME_LINK_OFFSET, link))
return -ENXIO;
# if FRAME_RETADDR_OFFSET < 0
if (!(link > bottom && link < fp))
# else
if (!(link < bottom && link > fp))
# endif
return -ENXIO;
if (link & (sizeof(link) - 1))
return -ENXIO;
fp += FRAME_RETADDR_OFFSET;
if (probe_kernel_address(fp, UNW_PC(frame)))
return -ENXIO;
/* Ok, we can use it */
# if FRAME_RETADDR_OFFSET < 0
UNW_SP(frame) = fp - sizeof(UNW_PC(frame));
# else
UNW_SP(frame) = fp + sizeof(UNW_PC(frame));
# endif
UNW_FP(frame) = link;
return 0;
#else
return -ENXIO;
#endif
}
/* (4) 解析CIE的`Initial Instructions`中的cfa指令 和
FDE的`Call Frame Instructions`中的cfa指令
解析结果放到state数据结构中
*/
state.org = startLoc;
memcpy(&state.cfa, &badCFA, sizeof(state.cfa));
/* process instructions */
if (!processCFI(ptr, end, pc, ptrType, &state)
|| state.loc > endLoc
|| state.regs[retAddrReg].where == Nowhere
|| state.cfa.reg >= ARRAY_SIZE(reg_info)
|| reg_info[state.cfa.reg].width != sizeof(unsigned long)
|| FRAME_REG(state.cfa.reg, unsigned long) % sizeof(unsigned long)
|| state.cfa.offs % sizeof(unsigned long)) {
dprintk(1, "Unusable unwind info (%p,%p).", ptr, end);
return -EIO;
}
/* update frame */
/* (5) 信号相关的帧处理 */
#ifndef CONFIG_AS_CFI_SIGNAL_FRAME
if (frame->call_frame
&& !UNW_DEFAULT_RA(state.regs[retAddrReg], state.dataAlign))
frame->call_frame = 0;
#endif
/* (6) 首先根据指令解析结果,计算cfa的值:
CFA = register + offset
*/
cfa = FRAME_REG(state.cfa.reg, unsigned long) + state.cfa.offs;
startLoc = min((unsigned long)UNW_SP(frame), cfa);
endLoc = max((unsigned long)UNW_SP(frame), cfa);
if (STACK_LIMIT(startLoc) != STACK_LIMIT(endLoc)) {
startLoc = min(STACK_LIMIT(cfa), cfa);
endLoc = max(STACK_LIMIT(cfa), cfa);
}
#ifndef CONFIG_64BIT
# define CASES CASE(8); CASE(16); CASE(32)
#else
# define CASES CASE(8); CASE(16); CASE(32); CASE(64)
#endif
pc = UNW_PC(frame);
sp = UNW_SP(frame);
/* (7) 再根据指令解析结果更新寄存器集的值
(7.1) 第1遍轮询解析
Register类型:state.regs[i].value = FRAME_REG(state.regs[i].value)
*/
for (i = 0; i < ARRAY_SIZE(state.regs); ++i) {
if (REG_INVALID(i)) {
if (state.regs[i].where == Nowhere)
continue;
dprintk(1, "Cannot restore register %u (%d).",
i, state.regs[i].where);
return -EIO;
}
switch (state.regs[i].where) {
default:
break;
case Register:
if (state.regs[i].value >= ARRAY_SIZE(reg_info)
|| REG_INVALID(state.regs[i].value)
|| reg_info[i].width > reg_info[state.regs[i].value].width) {
dprintk(1, "Cannot restore register %u from register %lu.",
i, state.regs[i].value);
return -EIO;
}
switch (reg_info[state.regs[i].value].width) {
#define CASE(n) \
case sizeof(u##n): \
state.regs[i].value = FRAME_REG(state.regs[i].value, \
const u##n); \
break
CASES;
#undef CASE
default:
dprintk(1, "Unsupported register size %u (%lu).",
reg_info[state.regs[i].value].width,
state.regs[i].value);
return -EIO;
}
break;
}
}
/* (7.2) 第2遍轮询解析
*/
for (i = 0; i < ARRAY_SIZE(state.regs); ++i) {
if (REG_INVALID(i))
continue;
switch (state.regs[i].where) {
/* Nowhere类型:UNW_SP(frame) = cfa; */
case Nowhere:
if (reg_info[i].width != sizeof(UNW_SP(frame))
|| &FRAME_REG(i, __typeof__(UNW_SP(frame)))
!= &UNW_SP(frame))
continue;
UNW_SP(frame) = cfa;
break;
/* Register类型:把第一轮计算出来的寄存器的值state.regs[i].value,
更新完到FRAME_REG(i)结构中
*/
case Register:
switch (reg_info[i].width) {
#define CASE(n) case sizeof(u##n): \
FRAME_REG(i, u##n) = state.regs[i].value; \
break
CASES;
#undef CASE
default:
dprintk(1, "Unsupported register size %u (%u).",
reg_info[i].width, i);
return -EIO;
}
break;
/* Value类型:
reg = CFA + (factored offset * data_alignment_factor)
*/
case Value:
if (reg_info[i].width != sizeof(unsigned long)) {
dprintk(1, "Unsupported value size %u (%u).",
reg_info[i].width, i);
return -EIO;
}
FRAME_REG(i, unsigned long) = cfa + state.regs[i].value
* state.dataAlign;
break;
/* Memory类型:
reg = *(CFA + (factored offset * data_alignment_factor))
*/
case Memory: {
unsigned long addr = cfa + state.regs[i].value
* state.dataAlign;
if ((state.regs[i].value * state.dataAlign)
% sizeof(unsigned long)
|| addr < startLoc
|| addr + sizeof(unsigned long) < addr
|| addr + sizeof(unsigned long) > endLoc) {
dprintk(1, "Bad memory location %lx (%lx).",
addr, state.regs[i].value);
return -EIO;
}
switch (reg_info[i].width) {
#define CASE(n) case sizeof(u##n): \
if (probe_kernel_address(addr, \
FRAME_REG(i, u##n))) \
return -EFAULT; \
break
CASES;
#undef CASE
default:
dprintk(1, "Unsupported memory size %u (%u).",
reg_info[i].width, i);
return -EIO;
}
}
break;
}
}
if (UNW_PC(frame) % state.codeAlign
|| UNW_SP(frame) % sleb128abs(state.dataAlign)) {
dprintk(1, "Output pointer(s) misaligned (%lx,%lx).",
UNW_PC(frame), UNW_SP(frame));
return -EIO;
}
if (pc == UNW_PC(frame) && sp == UNW_SP(frame)) {
dprintk(1, "No progress (%lx,%lx).", pc, sp);
return -EIO;
}
return 0;
#undef CASES
#undef FRAME_REG
}
↓
static int processCFI(const u8 *start,
const u8 *end,
unsigned long targetLoc,
signed ptrType,
struct unwind_state *state)
{
union {
const u8 *p8;
const u16 *p16;
const u32 *p32;
} ptr;
int result = 1;
/* (4.1) 先递归的解析CIE中的cfa指令 */
if (start != state->cieStart) {
state->loc = state->org;
result = processCFI(state->cieStart, state->cieEnd, 0, ptrType, state);
if (targetLoc == 0 && state->label == NULL)
return result;
}
/* (4.2) 再解析FDE中的cfa指令
这段指令解析请参考本文的`2.2 Call Frame Instructions`一节,就非常好理解了
*/
for (ptr.p8 = start; result && ptr.p8 < end; ) {
switch (*ptr.p8 >> 6) {
uleb128_t value;
case 0:
switch (*ptr.p8++) {
/* (4.2.1) 更新PC位置到:state->loc */
case DW_CFA_nop:
break;
case DW_CFA_set_loc:
/* 计算:Location = Address */
state->loc = read_pointer(&ptr.p8, end, ptrType, 0, 0);
if (state->loc == 0)
result = 0;
break;
case DW_CFA_advance_loc1:
/* 计算:Location += (delta * code_alignment_factor) */
result = ptr.p8 < end && advance_loc(*ptr.p8++, state);
break;
case DW_CFA_advance_loc2:
/* 计算:Location += (delta * code_alignment_factor) */
result = ptr.p8 <= end + 2
&& advance_loc(*ptr.p16++, state);
break;
case DW_CFA_advance_loc4:
/* 计算:Location += (delta * code_alignment_factor) */
result = ptr.p8 <= end + 4
&& advance_loc(*ptr.p32++, state);
break;
/* (4.2.2) 更新通用寄存器的值:state->regs[reg].where/value */
case DW_CFA_offset_extended:
/* 计算 rule:offset(N)
reg num = *(CFA + (factored offset * data_alignment_factor))
*/
value = get_uleb128(&ptr.p8, end);
set_rule(value, Memory, get_uleb128(&ptr.p8, end), state);
break;
case DW_CFA_val_offset:
/* 计算 rule:val_offset(N)
reg num = CFA + (factored offset * data_alignment_factor)
*/
value = get_uleb128(&ptr.p8, end);
set_rule(value, Value, get_uleb128(&ptr.p8, end), state);
break;
case DW_CFA_offset_extended_sf:
/* 计算 rule:offset(N)
reg num = *(CFA + (factored offset * data_alignment_factor))
*/
value = get_uleb128(&ptr.p8, end);
set_rule(value, Memory, get_sleb128(&ptr.p8, end), state);
break;
case DW_CFA_val_offset_sf:
/* 计算 rule:val_offset(N)
reg num = CFA + (factored offset * data_alignment_factor)
*/
value = get_uleb128(&ptr.p8, end);
set_rule(value, Value, get_sleb128(&ptr.p8, end), state);
break;
case DW_CFA_restore_extended:
case DW_CFA_undefined:
case DW_CFA_same_value:
set_rule(get_uleb128(&ptr.p8, end), Nowhere, 0, state);
break;
case DW_CFA_register:
/* 计算 rule:register(R)
reg num = R (second operands)
*/
value = get_uleb128(&ptr.p8, end);
set_rule(value,
Register,
get_uleb128(&ptr.p8, end), state);
break;
/* (4.2.3) 寄存器集的存栈和恢复 */
case DW_CFA_remember_state:
/* 本次是寄存器集恢复动作:重新解析指令一直恢复到label处 */
if (ptr.p8 == state->label) {
state->label = NULL;
return 1;
}
if (state->stackDepth >= MAX_STACK_DEPTH) {
dprintk(1, "State stack overflow (%p,%p).", ptr.p8, end);
return 0;
}
/* 本次是寄存器集存栈动作:把当前location存入到堆栈中 */
state->stack[state->stackDepth++] = ptr.p8;
break;
case DW_CFA_restore_state:
if (state->stackDepth) {
const uleb128_t loc = state->loc;
const u8 *label = state->label;
/* 碰到需要恢复寄存器的情况:
1、从堆栈中弹出需要恢复到哪一步的label
2、把cfa和regs寄存器集的值都设置成初始状态
3、从头重新开始解析,直到label处为止
4、寄存器集就恢复到了之前`DW_CFA_remember_state`指令保存的状态
*/
state->label = state->stack[state->stackDepth - 1];
memcpy(&state->cfa, &badCFA, sizeof(state->cfa));
memset(state->regs, 0, sizeof(state->regs));
state->stackDepth = 0;
result = processCFI(start, end, 0, ptrType, state);
state->loc = loc;
state->label = label;
} else {
dprintk(1, "State stack underflow (%p,%p).", ptr.p8, end);
return 0;
}
break;
/* (4.2.4) cfa值的更新:state->cfa
更新cfa计算公式的两个变量register和offset
通常情况下:CFA = regitser + offset
*/
case DW_CFA_def_cfa:
/* 同时更新register和offset:
register = new register
offset = new offset
*/
state->cfa.reg = get_uleb128(&ptr.p8, end);
/*nobreak*/
case DW_CFA_def_cfa_offset:
/* 只更新offset:
offset = new offset
*/
state->cfa.offs = get_uleb128(&ptr.p8, end);
break;
case DW_CFA_def_cfa_sf:
/* 同时更新register和offset:
register = new register
offset = (new offset * data_alignment_factor)
*/
state->cfa.reg = get_uleb128(&ptr.p8, end);
/*nobreak*/
case DW_CFA_def_cfa_offset_sf:
/* 只更新offset:
offset = (new offset * data_alignment_factor)
*/
state->cfa.offs = get_sleb128(&ptr.p8, end)
* state->dataAlign;
break;
case DW_CFA_def_cfa_register:
/* 只更新register:
register = new register
*/
state->cfa.reg = get_uleb128(&ptr.p8, end);
break;
/* (4.2.5) 对 DWARF expression的处理,没看明白 */
/*todo case DW_CFA_def_cfa_expression: */
/*todo case DW_CFA_expression: */
/*todo case DW_CFA_val_expression: */
case DW_CFA_GNU_args_size:
get_uleb128(&ptr.p8, end);
break;
case DW_CFA_GNU_negative_offset_extended:
value = get_uleb128(&ptr.p8, end);
set_rule(value,
Memory,
(uleb128_t)0 - get_uleb128(&ptr.p8, end), state);
break;
case DW_CFA_GNU_window_save:
default:
dprintk(1, "Unrecognized CFI op %02X (%p,%p).", ptr.p8[-1], ptr.p8 - 1, end);
result = 0;
break;
}
break;
/* DW_CFA_advance_loc */
case 1:
/* 计算:Location += (delta * code_alignment_factor) */
result = advance_loc(*ptr.p8++ & 0x3f, state);
break;
/* DW_CFA_offset */
case 2:
/* 计算 rule:offset(N)
reg num = *(CFA + (factored offset * data_alignment_factor))
*/
value = *ptr.p8++ & 0x3f;
set_rule(value, Memory, get_uleb128(&ptr.p8, end), state);
break;
/* DW_CFA_restore */
case 3:
/* 计算 rule:initial_instructions in the CIE
reg num = initial_instructions in the CIE
*/
set_rule(*ptr.p8++ & 0x3f, Nowhere, 0, state);
break;
}
if (ptr.p8 > end) {
dprintk(1, "Data overrun (%p,%p).", ptr.p8, end);
result = 0;
}
/* (4.2.6) 读取fde的条目,直到大于PC值的位置 */
if (result && targetLoc != 0 && targetLoc < state->loc)
return 1;
}
if (result && ptr.p8 < end)
dprintk(1, "Data underrun (%p,%p).", ptr.p8, end);
return result
&& ptr.p8 == end
&& (targetLoc == 0
|| (/*todo While in theory this should apply, gcc in practice omits
everything past the function prolog, and hence the location
never reaches the end of the function.
targetLoc < state->loc &&*/ state->label == NULL));
}
gcc提供了__builtin_return_address() 宏来做栈的回溯:
#include
void do_backtrace()
{
void *pc0 = __builtin_return_address(0);
void *pc1 = __builtin_return_address(1);
//void *pc2 = __builtin_return_address(2);
//void *pc3 = __builtin_return_address(3);
printf("Frame 0: PC=%p\n", pc0);
printf("Frame 1: PC=%p\n", pc1);
//printf("Frame 2: PC=%p\n", pc2);
//printf("Frame 3: PC=%p\n", pc3);
}
int main()
{
do_backtrace();
}
编译并运行:
> gcc gcc_backtrace.c
> ./a.out
Frame 0: PC=0x400590
Frame 1: PC=0x7f4052ab8c36
glibc提供了一对函数backtrace()和backtrace_symbols()来回溯栈信息:
#include
#include
#define BACKTRACE_SIZ 64
void do_backtrace()
{
void *array[BACKTRACE_SIZ];
size_t size, i;
char **strings;
size = backtrace(array, BACKTRACE_SIZ);
strings = backtrace_symbols(array, size);
for (i = 0; i < size; i++) {
printf("%p : %s\n", array[i], strings[i]);
}
free(strings); // malloced by backtrace_symbols
}
int main()
{
do_backtrace();
return 0;
}
编译并运行:
> gcc glibc_backtrace.c
> ./a.out
0x400646 : ./a.out() [0x400646]
0x4006c0 : ./a.out() [0x4006c0]
0x7fd564f97c36 : /lib64/libc.so.6(__libc_start_main+0xe6) [0x7fd564f97c36]
0x400569 : ./a.out() [0x400569]
编译时使用-rdynamic
把调试信息链接进文件,运行会打印出更详细的符号信息:
> gcc -g -rdynamic glibc_backtrace.c
> ./a.out
0x4008d6 : ./a.out(do_backtrace+0x1c) [0x4008d6]
0x400950 : ./a.out(main+0xe) [0x400950]
0x7f9ed1f6bc36 : /lib64/libc.so.6(__libc_start_main+0xe6) [0x7f9ed1f6bc36]
0x4007f9 : ./a.out() [0x4007f9]
gcc的-g
和rdynamic
选项:
-g
,应该没有人不知道它是一个调试选项,因此在一般需要进行程序调试的场景下,我们都会加上该选项,并且根据调试工具的不同,还能直接选择更有针对性的说明,比如-ggdb。-g是一个编译选项
,即在源代码编译的过程中起作用,让gcc把更多调试信息(也就包括符号信息)收集起来并将存放到最终的可执行文件内。-rdynamic
却是一个连接选项
,它将指示连接器把所有符号(而不仅仅只是程序已使用到的外部符号,但不包括静态符号,比如被static修饰的函数)都添加到动态符号表(即.dynsym表)里,以便那些通过dlopen()或backtrace()(这一系列函数使用.dynsym表内符号)这样的函数使用。安装libunwind:
// suse11sp4
sudo zypper install libunwind libunwind-devel
// ubuntu18.04
sudo apt-get install libunwind-dev
例程:
#include
#define UNW_LOCAL_ONLY
#include
void do_backtrace()
{
unw_cursor_t cursor;
unw_context_t context;
unw_getcontext(&context);
unw_init_local(&cursor, &context);
while (unw_step(&cursor) > 0) {
unw_word_t offset, pc;
char fname[64];
unw_get_reg(&cursor, UNW_REG_IP, &pc);
fname[0] = '\0';
(void) unw_get_proc_name(&cursor, fname, sizeof(fname), &offset);
printf ("%p : (%s+0x%x) [%p]\n", pc, fname, offset, pc);
}
}
int main()
{
do_backtrace();
}
编译并运行:
$ gcc -g libunwind_backtrace.c -lunwind
$ ./a.out
0x55e6635819cb : (main+0xe) [0x55e6635819cb]
0x7f8f4e88db97 : (__libc_start_main+0xe7) [0x7f8f4e88db97]
0x55e6635817fa : (_start+0x2a) [0x55e6635817fa]
libunwind提供了更多能力来检查每帧的程序状态。 例如,可以打印保存的寄存器值:
void do_backtrace2()
{
unw_cursor_t cursor;
unw_context_t context;
unw_getcontext(&context);
unw_init_local(&cursor, &context);
while (unw_step(&cursor) > 0) {
unw_word_t offset;
unw_word_t pc, eax, ebx, ecx, edx;
char fname[64];
unw_get_reg(&cursor, UNW_REG_IP, &pc);
unw_get_reg(&cursor, UNW_X86_64_RAX, &eax);
unw_get_reg(&cursor, UNW_X86_64_RDX, &edx);
unw_get_reg(&cursor, UNW_X86_64_RCX, &ecx);
unw_get_reg(&cursor, UNW_X86_64_RBX, &ebx);
fname[0] = '\0';
unw_get_proc_name(&cursor, fname, sizeof(fname), &offset);
printf ("%p : (%s+0x%x) [%p]\n", pc, fname, offset, pc);
printf("\tEAX=0x%08x EDX=0x%08x ECX=0x%08x EBX=0x%08x\n",
eax, edx, ecx, ebx);
}
}
编译并运行:
$ gcc libunwind_bt.c -lunwind
$ ./a.out
0x55e24c1f7b50 : (main+0xe) [0x55e24c1f7b50]
EAX=0xe8a1c4e0 EDX=0x7f6977ca ECX=0x4c1f7b60 EBX=0x00000000
0x7f027f095b97 : (__libc_start_main+0xe7) [0x7f027f095b97]
EAX=0xe8a1c4e0 EDX=0x7f6977ca ECX=0x4c1f7b60 EBX=0x00000000
0x55e24c1f77fa : (_start+0x2a) [0x55e24c1f77fa]
EAX=0xe8a1c4e0 EDX=0x7f6977ca ECX=0x4c1f7b60 EBX=0x00000000
相关资源:libunwind project、libunwind with Android ARM support
1.CFI directives introduce
2..eh_frame section
3.DWARF Version 4
4..eh_frame
5.ARM-Unwinding-Tutorial
6.ARM-Directives
7.CFI-directives
8.linux c 及 c++打印调用者函数caller function的方法,包括arm c平台
9.gcc选项-g与-rdynamic的异同
10.X86系列CPU标准寄存器
11.x86-64 下函数调用及栈帧原理
12.Exploiting the Hard-Working DWARF
13.ELF文件装载链接过程及hook原理
14.Linux ELF文件和VMA间的关系
15.unread
16.DWARF Extensions