Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why would g++ compiled code write beyond stack pointer?

To better understand assembly, I compiled a simple C++ program using g++ and then used gdbgui to step through the execution. I made a graphic of the state of the stack and registers at several consecutive points in the program to help myself better understand exactly what was going on. It appears that the program writes/reads at memory beyond the stack pointer several times during execution. This surprised me. I was under the impression that a program should never write beyond the stack pointer. I understand how it works, the relative addressing is based off of the base pointer, but I would've expected the program to adjust the stack pointer in some way to encompass the memory it planned on using. Is this method of writing beyond the stack a common technique for compilers?

C++:

#include <iostream>

int square(int i) {
    i = i * i;
    return i;
}

int main() {
    int i = square(2);
}

Compiled on Ubuntu 19.10 with:

g++ -o square_2 square_2.cpp

g++ version:

g++ -v
Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/9/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none:hsa
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 9.2.1-9ubuntu2' --with-bugurl=file:///usr/share/doc/gcc-9/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++,gm2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-9 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-bootstrap --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-plugin --enable-default-pie --with-system-zlib --with-target-system-zlib=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none,hsa --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 9.2.1 20191008 (Ubuntu 9.2.1-9ubuntu2) 

Relevant assembly from gdb:

0x55555555516d     push rbp
0x55555555516e     mov rbp,rsp
0x555555555171     mov DWORD PTR [rbp-0x4],edi
0x555555555174     mov eax,DWORD PTR [rbp-0x4]
0x555555555177     imul eax,eax
0x55555555517a     mov DWORD PTR [rbp-0x4],eax
0x55555555517d     mov eax,DWORD PTR [rbp-0x4]
0x555555555180     pop rbp
0x555555555181     ret 

Execution Trace

I used yellow highlighting to indicate when a value was updated, and green highlighting to show which memory contents the stack pointer is currently pointing at.

gdbgui screenshot

like image 973
Ansari Rahman Avatar asked Apr 08 '20 23:04

Ansari Rahman


1 Answers

On x86-64 under the SysV ABI, it is legal for a function to write up to 128 bytes below the stack pointer; see Section 3.2.2. This area is known as the red zone. Any sort of asynchronous code that might use the same stack is required to leave those 128 bytes alone.

like image 76
Nate Eldredge Avatar answered Nov 05 '22 20:11

Nate Eldredge