Unwind 栈回溯详解

二月 05, 2024 [Linux 与系统] #Linux #obsidian #编译链接 #调试

Unwind 栈回溯详解

1. 历史背景

1.1 frame pointers

在调试的时候经常需要进行堆栈回溯。最简单的方式是使用一个独立的寄存器(ebp)来保存每层函数调用的堆栈栈顶(frame pointer):

pushl%ebp
movl%esp,%ebp
...
popl%ebp
ret

在这里插入图片描述

在这里插入图片描述

这种方式在堆栈回溯时非常方便快捷。但是这种方法也有自己的不足:

1.2 .debug_frame (DWARF)

调试信息标准DWARF(Debugging With Attributed Record Formats)定义了一个.debug_frame section用来解决上述的难题。

这种机制也有其不足:

1.3 .eh_frame (LSB)

现代Linux操作系统在LSB(Linux Standard Base)标准中定义了一个.eh_frame section来解决上述的难题。这个section和.debug_frame非常类似,但是它解决了上述难题:

但是.debug_frame和.eh_frame同时面临一个难题:怎么样生成堆栈信息表?

1.4 CFI directives

为了解决上述难题,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。

2. .debug_frame (DWARF) 详解

在DWARF6.4 Call Frame Information一节详细的描述了调用栈帧的定义。

2.1 Call Frame Table

抽象的说,整个栈帧计算机制的核心是一张大表:

在这里插入图片描述

可以使用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指令的解析。

2.2 Call Frame Instructions

上一节说明栈帧回溯的核心是一张大表,而这个表的行(Row)和列(Column)最后都是靠CIE和FDE中的指令填充起来的。

这些指令主要分为以下几大类:

本类的指令会确定一个新的Location(PC)值,相当于在表中创建新的一行(Row)。

InstructionsDescriptDescript
DW_CFA_set_locLocation = AddressDW_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_locLocation += (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_loc1Location += (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_loc2Location += (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_loc4Location += (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计算规则。

InstructionsDescriptDescript
DW_CFA_def_cfaregister = new register num
offset = new offset
CFA = register + offsetDW_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_sfregister = new register num
offset = (factored_offset * data_alignment_factor)
CFA = register + offsetDW_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_registerregister = new register number
CFA = register + old offsetDW_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_offsetoffset = new offset
CFA = old register + offsetDW_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_sfoffset = (factored_offset * data_alignment_factor)
CFA = old register + offsetDW_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_expressionCFA = DWARF expressionDW_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)寄存器的恢复规则。

寄存器规则包含以下:

RuleDescriptDescript
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.

具体包含以下指令:

InstructionsRuleDescriptDescript
DW_CFA_undefinedundefinedreg num = undefinedDW_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_valuesame valuereg num = same valueDW_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_offsetoffset(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_extendedoffset(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_sfoffset(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_offsetval_offset(N)offset = (factored offset * data_alignment_factor)
reg num = CFA + offsetDW_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_sfval_offset(N)offset = (factored offset * data_alignment_factor)
reg num = CFA + offsetDW_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_registerregister(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_expressionexpression(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_expressionval_expression(E)reg num = DWARF expressionDW_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_restoreinitial_instructions in the CIEreg num = initial_instructions in the CIEDW_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_extendedinitial_instructions in the CIEreg num = initial_instructions in the CIEDW_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)完整寄存器状态的能力。

InstructionsDescriptDescript
DW_CFA_remember_stateDW_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_stateDW_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.
InstructionsDescriptDescript
DW_CFA_nopDW_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.
InstructionsDescriptDescript
DW_CFA_GNU_args_sizeDW_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_extendedoffset = (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.

2.3 Instructions Opcode

各个指令具体的指令码编码如下:

InstructionHigh 2BitsLow 6 BitsOperand 1Operand 2
DW_CFA_advance_loc0x1delta
DW_CFA_offset0x2registerULEB128 offset
DW_CFA_restore0x3register
DW_CFA_nop00
DW_CFA_set_loc00x01address
DW_CFA_advance_loc100x021-byte delta
DW_CFA_advance_loc200x032-byte delta
DW_CFA_advance_loc400x044-byte delta
DW_CFA_offset_extended00x05ULEB128 registerULEB128 offset
DW_CFA_restore_extended00x06ULEB128 register
DW_CFA_undefined00x07ULEB128 register
DW_CFA_same_value00x08ULEB128 register
DW_CFA_register00x09ULEB128 registerULEB128 register
DW_CFA_remember_state00x0a
DW_CFA_restore_state00x0b
DW_CFA_def_cfa00x0cULEB128 registerULEB128 offset
DW_CFA_def_cfa_register00x0dULEB128 register
DW_CFA_def_cfa_offset00x0eULEB128 offset
DW_CFA_def_cfa_expression00x0fBLOCK
DW_CFA_expression00x10ULEB128 registerBLOCK
DW_CFA_offset_extended_sf00x11ULEB128 registerSLEB128 offset
DW_CFA_def_cfa_sf00x12ULEB128 registerSLEB128 offset
DW_CFA_def_cfa_offset_sf00x13SLEB128 offset
DW_CFA_val_offset00x14ULEB128ULEB128
DW_CFA_val_offset_sf00x15ULEB128SLEB128
DW_CFA_val_expression00x16ULEB128BLOCK
DW_CFA_lo_user00x1c
DW_CFA_GNU_args_size00x2eULEB128
DW_CFA_GNU_negative_offset_extended00x2fULEB128ULEB128
DW_CFA_hi_user00x3f

2.4 DWARF expression

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容易成为攻击的后门。

2.5 Call Frame Instruction Usage

CFA相关指令的解析过程如下。

首先为了给虚拟展开规则集设置一个确定位置(L1),人们在FDE标头中进行搜索,查看initial_location和address_range值以查看L1是否包含在FDE中。 如果存在,则:

2.6 Call Frame Calling Address

展开帧时,使用者经常希望获得调用子例程的指令的地址。并非总是提供此信息。但是,通常虚拟展开表中的寄存器之一是返回地址。

如果在虚拟展开表中定义了返回地址寄存器,并且未定义其规则(例如,通过DW_CFA_undefined定义),则没有返回地址(return address),也没有调用地址(call address),并且堆栈激活的虚拟展开已完成。

在大多数情况下,返回地址与调用地址在同一上下文中,但不必如此,特别是如果生产者以某种方式知道该调用永不返回。 “返回地址”的上下文可能在不同的行中,在不同的词法块中或在调用子例程的末尾。如果使用者假定它与呼叫地址处于同一上下文中,则展开可能会失败。

对于具有恒定长度指令的体系结构,其中返回地址紧跟在调用指令之后,一种简单的解决方案是从返回地址中减去指令的长度以获得调用指令。对于具有可变长度指令的体系结构(例如x86),这是不可能的。但是,从返回地址减去1尽管不能保证提供准确的调用地址,但通常会在与调用地址相同的上下文中生成一个地址,通常就足够了。

2.7 CFI Example

以下示例使用摩托罗拉88000风格的假想RISC机器。它有以下特性:

•存储器是按字节寻址的。
•指令均为4个字节,字对齐。
•指令操作数通常采用以下形式:<destination.reg>,<source.reg>,<constant>
•加载和存储指令的地址是通过将源寄存器的内容与常量相加得出的。
•有8个4字节寄存器:
	R0 始终为0
	R1 保存调用时的返回地址
	R2-R3 临时寄存器(在调用时不保护)
	R4-R6 在调用时保护
	R7 堆栈指针
•堆栈向负方向增长。
•体系结构ABI委员会指定堆栈指针(R7)与CFA相同

以下是来自名为foo的子例程的两个代码片段,该子例程使用帧指针(除了堆栈指针之外)。 第一列的值是字节地址。 ``表示以字节为单位的堆栈帧大小,即12。

		;; 保存现场,保护 R1/R6/R4
		;; start prologue
foo 	sub R7, R7, <fs> ; Allocate frame
foo+4 	store R1, R7, (<fs>-4) ; Save the return address
foo+8 	store R6, R7, (<fs>-8) ; Save R6
foo+12 	add R6, R7, 0 ; R6 is now the Frame ptr
foo+16 	store R4, R6, (<fs>-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, (<fs>-12) ; Restore R4
foo+68 	load R6, R7, (<fs>-8) ; Restore R6
foo+72 	load R1, R7, (<fs>-4) ; Restore return address
foo+76 	add R7, R7, <fs> ; 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. <fs> = frame size
2. <caf> = code alignment factor
3. <daf> = data alignment factor

3. .eh_frame 详解

使用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。

3.1 .eh_frame 格式

在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对应一个函数。

3.1.1 CIE 格式

FieldDescription
LengthRequired
Extended LengthOptional
CIE IDRequired
VersionRequired
Augmentation StringRequired
Code Alignment FactorRequired
Data Alignment FactorRequired
Return Address RegisterRequired
Augmentation Data LengthOptional
Augmentation DataOptional
Initial InstructionsRequired
Padding

具体解析如下:

3.1.2 FDE 格式

FieldDescription
LengthRequired
Extended LengthOptional
CIE PointerRequired
PC BeginRequired
PC RangeRequired
Augmentation Data LengthOptional
Augmentation DataOptional
Call Frame InstructionsRequired
Padding

具体解析如下:

3.1.3 .eh_frame_hdr 格式

EncodingField
unsigned byteversion
unsigned byteeh_frame_ptr_enc
unsigned bytefde_count_enc
unsigned bytetable_enc
encodedeh_frame_ptr
encodedfde_count
binary search table

.eh_frame_hdr section包含.eh_frame的额外信息。

具体解析如下:

3.1.4 DWARF Exception Header Encoding

DWARF异常标头编码用于描述.eh_frame和.eh_frame_hdr部分中使用的数据类型。 高4位指示如何应用该值, 低4位表示数据格式。

NameValueMeaningDescript
DW_EH_PE_absptr0x00Pointer (long)The Value is a literal pointer whose size is determined by the architecture.
DW_EH_PE_uleb1280x01Unsigned LEB128Unsigned value is encoded using the Little Endian Base 128 (LEB128) as defined by DWARF Debugging Information Format, Revision 2.0.0.
DW_EH_PE_udata20x02A 2 bytes unsigned value.
DW_EH_PE_udata40x03A 4 bytes unsigned value.
DW_EH_PE_udata80x04An 8 bytes unsigned value.
DW_EH_PE_sleb1280x09Signed LEB128Signed value is encoded using the Little Endian Base 128 (LEB128) as defined by DWARF Debugging Information Format, Revision 2.0.0.
DW_EH_PE_sdata20x0AA 2 bytes signed value.
DW_EH_PE_sdata40x0BA 4 bytes signed value.
DW_EH_PE_sdata80x0CAn 8 bytes signed value.
NameValueMeaningDescript
DW_EH_PE_pcrel0x10Value is relative to the current program counter.值是相对于当前程序计数器的。
DW_EH_PE_textrel0x20Value is relative to the beginning of the .text section.值是相对于.text section的。
DW_EH_PE_datarel0x30Value is relative to the beginning of the .got or .eh_frame_hdr section.值是相对于.got或者.eh_frame_hdr section的。
DW_EH_PE_funcrel0x40Value is relative to the beginning of the function.值是相对于当前函数的。
DW_EH_PE_aligned0x50Value is aligned to an address unit sized boundary.值与地址单元大小的边界对齐。
NameValueMeaningDescript
DW_EH_PE_omit0xFFindicate that no value is present

3.2 .eh_frame 实例

一段简单的c语言代码:

$ cat test.c
#include <stdio.h>

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

3.2.1 解析信息

可以使用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来进行栈回溯:

3.2.2 原始信息

也可以使用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命令解析了这些信息,我们初步看看对应关系:

在这里插入图片描述

4. CFI directives 详解

4.1 CFI 详解

在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 ABI3.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。

更多的指令请查看手册,或者在实际用例中理解。

4.2 CFI 实例

如果我们直接编写汇编代码,需要自己手工添加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

5. kernel unwind 实现

在内核中已经有现成的代码实现了kernel代码的unwind方式栈回溯。

5.1 .eh_frame的加载

内核vmlinux也是一个elf文件,它编译完成后默认也生成了.eh_frame和.eh_frame_hdrsection。这两个段运行的时候被一起加载到内存,运行的时候需要有方法能找到它们。

内核在链接脚本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_hdrsection当成一张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链表。

5.2 unwind解析

通常我们调用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 != &not_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 == &not_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));
}

6. 用户态常见取栈方法

6.1 gcc取栈

gcc提供了__builtin_return_address() 宏来做栈的回溯:

#include <stdio.h>

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

6.2 glibc取栈

glibc提供了一对函数backtrace()和backtrace_symbols()来回溯栈信息:

#include <stdio.h>
#include <execinfo.h>

#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选项:

6.3 libunwind取栈

安装libunwind:

// suse11sp4
sudo zypper install libunwind libunwind-devel

// ubuntu18.04
sudo apt-get install libunwind-dev

例程:

#include <stdio.h>
#define UNW_LOCAL_ONLY
#include <libunwind.h>

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