When does stack grow? How does OS know when to grow stack?
Asked Answered
G

1

7

Note: This question is about x86_64 architecture and Linux ABI.

When the program is launched, some space is allocated for stack. Later on, during program execution the stack area can get resized (when more space is required) up to some maximum specified by OS.

Let's take for instance simple program:

int main() {
    char bytes[7 * 1024 * 1024];
}

Let's run it under gdb and set breakpoints: before main and after declaring an array.

gdb> b *main
gdb> b main
gdb> r
gdb> info proc mapping // breakpoint before pushing stack
          Start Addr           End Addr       Size     Offset objfile
      0x7ffffffde000     0x7ffffffff000    0x21000        0x0 [stack]
gdb> c
gdb> info proc mapping // breakpoint after pushing stack
          Start Addr           End Addr       Size     Offset objfile
      0x7fffff8fe000     0x7ffffffff000   0x701000        0x0 [stack]

So we can see the stack actually got resized.

The question is how does the OS know when the stack has to be resized?. Some internet resources say OS handles page fault exception and if the accessed address is within possible stack address range, it gets resized.

But when I was debugging the program step by step, it turned out, the stack got resized just after following instruction:

0x40129d <main+4>     sub    rsp, 0x700010

So (as far as I know), there is no page fault yet since we are not accessing the addresses actually. We only change rsp register. So how is it possible OS handles it? Or maybe there is a page fault exception after changing rsp?

Goldenseal answered 3/3, 2020 at 11:49 Comment(7)
The code doesn't really "grow" the stack. Instead the stack-pointer is adjusted so you can access local variables (including arrays) from a negative offset from it. It's all done inside the generated code itself, it's not handled by the OS at all.Shoelace
@Someprogrammerdude But then, how to interpret the "info proc mapping" output?Gerger
The internet resources were right, the OS handles the fault. Just moving rsp around does nothing until you actually perform a suitable memory access. You did not show full disassembly. I have checked and stack doesn't grow here.Unconcern
@Unconcern You mean you have checked info proc mappings output or some other way?Goldenseal
Yes, I used info proc mappings. Note, gdb may cause the stack to grow as well, e.g. if you do x/a $rsp or have something set up that does so.Unconcern
As @Unconcern already says the OS handle the whole process. The stack memory, as any other process memory, is allocated by the kernel mapping system physical memory onto process virtual space. The process is divided in 2 phases: memory allocation, up to maximum size allowed on system, and committment. When an access is made outside of actually committed memory the OS remaps virtual memory to honour the request. This mechanism is used to give any process virtually all memory, and in the same time preserve physical memory to be used where it is really required.Nonetheless
This article may be exactly what you’re looking for. It’s focused on 32-bit x86, but the concepts should be largely the same.Openmouthed
N
2

But when I was debugging the program step by step, it turned out, the stack got resized just after following instruction:

0x40129d <main+4>     sub    rsp, 0x700010

That's not correct. If you step the program you have shown (even if compiled without optimizations), there is no increase in the mapping at all since there are no writes to the stack pages.

But even if you step a less trivial program, you will also see the mapping does not change on the sub instruction, but rather on every ever-increasing access to the stack.

For instance, if you do:

bytes[0] = 42;

you will see it increases by 0x70000, since you are writing into the top. But if you instead do something like:

bytes[3 * 1024 * 1024] = 42;

You will see it increases up only by 0x40000.

Note that you may be getting confused if you are hitting an instruction that happens to push into the stack, like a call.

Nilotic answered 3/3, 2020 at 13:39 Comment(5)
Ok, I have recorded a gdb debugging session: streamable.com/b8kyz for me it looks like the stack gets resized. Where am I wrong?Goldenseal
As I said in my comment: "Note, gdb may cause the stack to grow as well, e.g. if you do x/a $rsp or have something set up that does so." You have that fancy display set up which shows the stack at each stop. That is accessing the memory hence the stack grows. If you use plain stock gdb, that doesn't happen.Unconcern
@Unconcern Oh, now I get it, I have completely missed the fact I am using pwndbg which actually may access those addresses. You were first to say that, I can accept your answer if you put one :) Thanks for help!Goldenseal
@Andy: Something this answer didn't mention explicitly: the OS knows to grow the stack mapping when the CPU raises a #PF page-fault exception. (And the address happens to be below the current stack mapping, but above RSP, and ulimit -s is large enough.) There are some SO Q&As about this level of detail for Linux main-thread stacks. (thread stacks can't safely grow dynamically on Linux)Finegrain
@PeterCordes Indeed, some of them most likely written by you ;-DNilotic

© 2022 - 2024 — McMap. All rights reserved.