我如何在我的frameCount函数中计算堆栈帧的数量?
我有由 frameCount 调用的汇编代码,需要返回 frameCount,但不确定如何检索(然后导航)前一帧的指针引用。
获取FP.s
.globl getFP
getFP:
movq %rbp, %rax
ret
帧数
int frameCount() {
int count = 0;
uint64_t fp = getFP();
uint64_t *sp = &fp;
// how do I get the pointer/offset to pointer to the previous stack frame from here?
return count;
}
更新:
我已经更新了 frameCount 函数以包含一个遍历堆栈帧链接列表的循环,但是在调用 frameCount 时出现分段错误。
主文件
#include <stdio.h>
#include <inttypes.h>
#include "framecount.c"
int main() {
printf("Number of Frames: %dn", frameCount());
return(0);
}
帧数
#include <stdio.h>
#include <inttypes.h>
uint64_t* getFP();
int frameCount() {
uint64_t* fp = getFP();
uint64_t registerValue1 = *fp;
while (registerValue1 != 0) {
printf("current register value %" PRIx64 "n", registerValue1);
printf("next register value %" PRIx64 "n", *(volatile uint64_t *)registerValue1);
count++;
registerValue1 = *(volatile uint64_t *)registerValue1;
}
printf("count=%dn", count);
return count;
}
输出
current register value 7ffca7c147b0
next register value 401230
current register value 401230
next register value 8d4c5741fa1e0ff3
current register value 8d4c5741fa1e0ff3
Segmentation fault (core dumped)
但是,当我执行以下操作时,我没有收到分段错误,但计数似乎不正确:(更新:删除了虚假示例)
更新 2:
即使在使用选项-O0或-fno-omit-frame-pointer以下是初始第一次更新的程序集输出时,仍然会出现分段错误:
.file "lab7.c"
.text
.section .rodata
.LC0:
.string "%d"
.text
.globl frameCount
.type frameCount, @function
frameCount:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $32, %rsp
movl $0, -4(%rbp)
movl $0, %eax
call getFP
movq %rax, -24(%rbp)
movq -24(%rbp), %rax
movq (%rax), %rax
movq %rax, -16(%rbp)
jmp .L2
.L3:
addl $1, -4(%rbp)
movl -4(%rbp), %eax
movl %eax, %esi
movl $.LC0, %edi
movl $0, %eax
call printf
movq -16(%rbp), %rax
movq (%rax), %rax
movq %rax, -16(%rbp)
.L2:
cmpq $0, -16(%rbp)
jne .L3
movl -4(%rbp), %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size frameCount, .-frameCount
.section .rodata
.LC1:
.string "Number of Frames: %dn"
.text
.globl main
.type main, @function
main:
.LFB1:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $0, %eax
call frameCount
movl %eax, %esi
movl $.LC1, %edi
movl $0, %eax
call printf
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE1:
.size main, .-main
.ident "GCC: (GNU) 10.2.1 20200723 (Red Hat 10.2.1-1)"
.section .note.GNU-stack,"",@progbits
回答
一般来说,这种技术是行不通的。 只有当编译器实际使用它们时,才能像这样遍历堆栈帧。在 Linux 和类似操作系统下的 x86-64 上,这不是 ABI 所要求的,并且在启用优化时不是大多数编译器的默认设置,但在 GCC 和 clang 上,您可以使用-fno-omit-frame-pointer. 但是,如果调用链中的某些函数在调用%rbp下一个函数时用于其他用途,则存储的%rbp将不会指向前一个函数,并且您的程序可能会崩溃。有一种替代方法可以使用存储在内存其他地方的展开信息来遍历堆栈,但它很复杂,因此人们通常使用类似的库libbacktrace。
但是,当使用堆栈帧时:您可以查看编译器如何设置它们:
pushq %rbp
movq %rsp, %rbp
由于 x86push指令递减%rsp,然后将推送的值存储在%rsp指向的新地址处,因此movq %rsp, %rbp叶子%rbp包含%rbp存储前一个地址的地址。堆栈帧顶部的%rbp值为 0,因此您可以简单地执行以下操作
for (uint64_t *fp = getFP(); fp; fp = (uint64_t *)*fp) count++;