How do I go about Counting the number of stack frames within my frameCount function?

Viewed 397

I have assembly code, called by frameCount, and need to return frameCount, but unsure how to retrieve (then navigate) previous frame's pointer reference.

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

Update:

I've updated the frameCount function to include a loop that traverses the linked list of stack frames, but I'm getting a segmentation fault when calling 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;
}

Output

current register value 7ffca7c147b0
next register value 401230
current register value 401230
next register value 8d4c5741fa1e0ff3
current register value 8d4c5741fa1e0ff3
Segmentation fault (core dumped)

However, when I do the following I don't get a segmentation fault, but the count seems incorrect: (update: removed bogus example)

Update 2:

Still getting a segmentation fault even when running with option -O0 or -fno-omit-frame-pointer Here's the assembly output from initial first update:

    .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
1 Answers

In general this technique will not work. Walking stack frames like this is only possible if the compiler actually uses them. On x86-64 under Linux and similar OSes, this is not required by the ABI, and is not the default for most compilers when optimization is enabled, though on GCC and clang you can request it with -fno-omit-frame-pointer. But if some of the functions in the call chain were using %rbp for something else when they called the next function, the stored %rbp will not point to the previous one and your program will probably crash. There is an alternative way to walk the stack using unwind info stored elsewhere in memory, but it is complicated, so people often use a library like libbacktrace instead.

However, when stack frames are in use: you can look at how a compiler sets them up:

    pushq   %rbp
    movq    %rsp, %rbp

Since the x86 push instruction decrements %rsp and then stores the pushed value at the new address where %rsp points, the movq %rsp, %rbp leaves %rbp containing the address where the previous %rbp is stored. The top of the stack frame has an %rbp value of 0, so you can simply do something like

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