【问题标题】:How do I go about Counting the number of stack frames within my frameCount function?如何计算我的 frameCount 函数中的堆栈帧数?
【发布时间】:2020-11-26 19:52:08
【问题描述】:

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

getFP.s

  .globl getFP
getFP:
  movq %rbp, %rax
  ret

frameCount.c

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 时出现分段错误。

main.c

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

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

  return(0);
}

frameCount.c

#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;
}

输出

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: %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

【问题讨论】:

  • 您的最后一个程序是伪造的,因为您正在从NewFunc3 开始的%rbp 开始遍历堆栈,之后它已经返回。那时堆栈上的帧指针链可能已被覆盖。
  • 您的第一个更新版本适用于我,gcc -O0 并按预期返回计数 1。请注意,frameCount 并未将其自身计入调用链中。正如我所指出的,如果您使用优化进行编译,您应该期望它会因段错误或其他原因而失败。您使用了哪些编译器和选项?
  • @NateEldredge 是的,感谢您指出这一点。
  • 几个通用的 cmets,而我正在考虑它们:(1)不要将 .c 文件包含在彼此中。只写一个带有声明的标题,你包括在内;分别编译 .c 文件,并链接它们。 (2) C 中没有参数的函数声明为 int getFP(void);,而不是 int getFP();。后者声明了一个带有 unspecified 参数的函数。特别是,编译器认为它可能是可变参数,因此它必须跳过一些额外的循环来调用它(如果你想知道为什么在所有函数调用之前%rax 显然有不必要的归零,这就是原因。)
  • 无论如何,repl 中的代码似乎也在尝试循环,直到找到指向自身的帧指针。那不是正确的终止条件;正如我在回答中所说,在列表的末尾你会发现零。如果您不对此进行测试,您将取消对它的引用和段错误。

标签: c assembly x86-64 backtrace stack-frame


【解决方案1】:

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

但是,当使用堆栈帧时:您可以查看how a compiler sets them up

    pushq   %rbp
    movq    %rsp, %rbp

由于x86 push指令递减%rsp,然后将推送的值存储在%rsp指向的新地址,movq %rsp, %rbp离开%rbp,其中包含存储前一个%rbp的地址。堆栈帧的顶部有一个 %rbp 值为 0,所以你可以简单地做类似的事情

for (uint64_t *fp = getFP(); fp; fp = (uint64_t *)*fp) count++;

【讨论】:

  • 我运行了您的代码,但确实遇到了分段错误。我已经用更多详细信息更新了我的问题。
猜你喜欢
  • 2012-10-26
  • 2023-03-12
  • 1970-01-01
  • 1970-01-01
  • 2013-01-01
  • 2015-04-14
  • 2013-10-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多