我如何在我的 frameCount 函数中计算堆栈帧的数量?

yib*_*ibo 3 c assembly x86-64 backtrace stack-frame

我有由 frameCount 调用的汇编代码,需要返回 frameCount,但不确定如何检索(然后导航)前一帧的指针引用。

获取FP.s

  .globl getFP
getFP:
  movq %rbp, %rax
  ret
Run Code Online (Sandbox Code Playgroud)

帧数

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;
}
Run Code Online (Sandbox Code Playgroud)

更新:

我已经更新了 frameCount 函数以包含一个遍历堆栈帧链接列表的循环,但是在调用 frameCount 时出现分段错误。

主文件

#include <stdio.h>
#include <inttypes.h>
#include "framecount.c"

int main() {
  printf("Number of Frames: %d\n", frameCount());

  return(0);
}
Run Code Online (Sandbox Code Playgroud)

帧数

#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=%d\n", count);

  return count;
}

Run Code Online (Sandbox Code Playgroud)

输出

current register value 7ffca7c147b0
next register value 401230
current register value 401230
next register value 8d4c5741fa1e0ff3
current register value 8d4c5741fa1e0ff3
Segmentation fault (core dumped)
Run Code Online (Sandbox Code Playgroud)

但是,当我执行以下操作时,我没有收到分段错误,但计数似乎不正确:(更新:删除了虚假示例)

更新 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: %d\n"
    .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
Run Code Online (Sandbox Code Playgroud)

Nat*_*dge 7

一般来说,这种技术是行不通的。 只有当编译器实际使用它们时,才能像这样遍历堆栈帧。在 Linux 和类似操作系统下的 x86-64 上,这不是 ABI 所要求的,并且在启用优化时不是大多数编译器的默认设置,但在 GCC 和 clang 上,您可以使用-fno-omit-frame-pointer. 但是,如果调用链中的某些函数在调用%rbp下一个函数时用于其他用途,则存储的%rbp将不会指向前一个函数,并且您的程序可能会崩溃。有一种替代方法可以使用存储在内存其他地方的展开信息来遍历堆栈,但它很复杂,因此人们通常使用类似的库libbacktrace

但是,当使用堆栈帧时:您可以查看编译器如何设置它们

    pushq   %rbp
    movq    %rsp, %rbp
Run Code Online (Sandbox Code Playgroud)

由于 x86push指令递减%rsp,然后将推送的值存储在%rsp指向的新地址处,因此movq %rsp, %rbp叶子%rbp包含%rbp存储前一个地址的地址。堆栈帧顶部的%rbp值为 0,因此您可以简单地执行以下操作

for (uint64_t *fp = getFP(); fp; fp = (uint64_t *)*fp) count++;
Run Code Online (Sandbox Code Playgroud)