fp equal to sp at startup but when copied onto enlarged stack changes to zero - why?

0

I'm learning x32 ARM assembly on RaspberryPi with Raspbian. I wrote the following code:

@ Define my Raspberry Pi
    .cpu    cortex-a53
    .fpu    neon-fp-armv8
    .syntax unified         @ modern syntax

.text
    .align  2
    .global main
    .type   main, %function

main:
    mov     r0, 1           @ line added only for breakpoint purposes
    sub     sp, sp, 8       @ space for fp, lr
    str     fp, [sp, 0]     @ save fp
    str     lr, [sp, 4]     @   and lr
    add     fp, sp, 4       @ set our frame pointer

Build with gcc:

gcc -g test.s -o test

Use gdb to check values of fp and sp in lines 13 and 16 and dereference them:

$ gdb ./test
(gdb) break 13
Breakpoint 3 at 0x103d4: file test.s, line 13.
(gdb) break 16
Breakpoint 4 at 0x103e0: file test.s, line 16.
(gdb) run
Starting program: /home/pi/assembly/nine/bob/test

Breakpoint 3, main () at test.s:13
13              sub     sp, sp, 8       @ space for fp, lr
(gdb) print {$sp, $fp}
$1 = {0x7efffae8, 0x7efffae8}
(gdb) x $sp
0x7efffae8:     0x76f9e000
(gdb) x $fp
0x7efffae8:     0x76f9e000
(gdb) continue
Continuing.

Breakpoint 4, main () at test.s:16
16              add     fp, sp, 4       @ set our frame pointer
(gdb) print {$sp, $fp}
$2 = {0x7efffae0, 0x7efffae0}
(gdb) x $sp
0x7efffae0:     0x00000000
(gdb) x $fp
0x7efffae0:     0x00000000

As you see fp is equal to sp at startup and non-zero:

(gdb) print {$sp, $fp}
$1 = {0x7efffae8, 0x7efffae8}
(gdb) x $sp
0x7efffae8:     0x76f9e000
(gdb) x $fp
0x7efffae8:     0x76f9e000

but when copied onto the enlarged stack it changes to zero

(gdb) x $fp
0x7efffae0:     0x00000000

Why does it change to zero? Why does it change value at all? Is underlying implementation somehow linking values of fp and sp so that when sp is moved down to the initialized memory that might be all zeroes fp is changed as well? I only found this:

fp

Is the frame pointer register. In the obsolete APCS variants that
use fp, this register contains either zero, or a pointer to the
most recently created stack backtrace data structure. As with the
stack pointer, the frame pointer must be preserved, but in
handwritten code it does not need to be available at every
instant. However, it must be valid whenever any strictly
conforming function is called. fp must always be preserved.

This comment says that lr is stored as the first element on the stack but it's definitely not - it stays the same and is not zero:

(gdb) print {$sp, $fp, $lr}
$1 = {0x7efffae8, 0x7efffae8, 0x76e6b718 <__libc_start_main+268>}

and after sp changes:

(gdb) x/2xw $sp
0x7efffae0:     0x00000000      0x76e6b718
linux
assembly
arm
asked on Stack Overflow Apr 20, 2020 by user1042840 • edited Apr 20, 2020 by user1042840

1 Answer

0

Ok, I'm answering myself - this happens in gdb/arm-tdep.c in GDB source code:

  /* The frame size is just the distance from the frame register
 to the original stack pointer.  */
  if (pv_is_register (regs[ARM_FP_REGNUM], ARM_SP_REGNUM))
{
  /* Frame pointer is fp.  */
  framereg = ARM_FP_REGNUM;
  framesize = -regs[ARM_FP_REGNUM].k;
}
  else
{
  /* Try the stack pointer... this is a bit desperate.  */
  framereg = ARM_SP_REGNUM;
  framesize = -regs[ARM_SP_REGNUM].k;
}
answered on Stack Overflow Apr 20, 2020 by user1042840 • edited Apr 20, 2020 by user1042840

User contributions licensed under CC BY-SA 3.0