4 ms·
My confusion about statements like this are understanding where the convention begins. Isn't it libc that sets up the stack? Couldn't it decide to set up the st
by eatonphil 6y ago
My confusion about statements like this are understanding where the convention begins. Isn't it libc that sets up the stack? Couldn't it decide to set up the stack starting from the bottom of memory and put heap allocations at the top? I guess instructions like PUSH/POP and derivatives wouldn't be useful anymore so you'd have to recreate them. So I guess that means that the convention starts at the processor? You could store memory in the opposite way but it would just be slower since you wouldn't be able to use built in operations. Do I have that right?
- ajross 6y ago> Isn't it libc that sets up the stack? In coordination with the kernel, yes. Different OSes do this differently, but many will set up the stack pointer for you. Linux historically has not, the new thread keeps the parent stack pointer and has to implement careful entry code to switch it without messing up the parent thread. > Couldn't it decide to set up the stack starting from the bottom of memory and put heap allocations at the top? In theory, but as you mention the architecture makes some clear demands here. On x86 CALL/RET and PUSH/POP both require a SP/ESP/RSP register with free space below it. Likewise x86 interrupts are handled on the stack and so you need to keep the stack pointer initialized for them (modern CPUs will automatically switch the stack for you from whatever user code is running, but they still need to switch it to a grows-down area you initialized for them). Broadly, sure, you can come up with a software abstraction that acted as a "stack", but it would have to be in addition to the CPU stack you already need. In effect you'd have to burn a register for this extra stack pointer, which has performance implications.
- eatonphil 6y agoThat's helpful! Then why is it that libc is the one setting up the stack if this is a convention basically required by the processor?
- ajross 6y agoBecause the runtime linker (e.g. ld-linux.so) is implemented in the C library, as is the user-callable implementation of pthread_create() or whatever[1]. It's definitely the application's job to decide on its own memory layout in any case. The kernel doesn't tell you where your stack should be, it's your address space. [1] vs. the clone() Linux syscall, which isn't really useful to regular code because of the complexity.
- monocasa 6y ago> Linux historically has not, the new thread keeps the parent stack pointer and has to implement careful entry code to switch it without messing up the parent thread. Well, for calls to fork(2) and clone(2), sure, but the kernel will setup a stack for you on exec(2). It has to have somewhere to stick the command line args, env, and some other extra bits of information like a random seed.
- astrobe_ 6y agoYes, you clearly see that when you implement a stack-based virtual machine (as in bytecode interpreter, not VMWare and the likes, although it is on the principle the same thing) : your bytecode (or whatever technique you are using except perhaps JIT) for push/pop/call/ret must agree on how to use the stack. It is particularly the case in a language like Forth, which has two stacks that can be manipulated directly by the user (actually the user has to if they want to get anything done...) : one for the parameters/arguments, and one for the return addresses. This deviates from the usual single hardware stack processors. Forth processors (there are still some in operation) fully support those two stacks. When you implement a Forth interpreter in assembler, you generally use the hardware stack for either the parameters or for the return addresses, while the other is managed manually (usually using another addressing-capable register such as EBP, ESI or EDI on x86). If for instance you dedicate one segment (either in the x86 sense or in the common meaning), you typically use the push down "hardware" stack that you initialize at the top of memory, while the return addresses stack is a "push up" software-managed stack and initialized at the bottom of the memory.
- toast0 6y ago> Likewise x86 interrupts are handled on the stack and so you need to keep the stack pointer initialized for them (modern CPUs will automatically switch the stack for you from whatever user code is running, but they still need to switch it to a grows-down area you initialized for them). If your kernel is re-entrant, you need to keep platform stack conventions in the kernel, or an interrupt (or exception) during the kernel will overwrite your backwards stack. I don't know for sure, but I think aignal handing in user processes runs on the user stack too (but I could easily be wrong). If you really wanted, you could use the platform stack for call/ret only and have a separate data stack with whatever conventions you like.
- ylyn 6y agoYes, more or less. I'm not sure if push and pop is significantly faster than a mov and add. But there are also instructions like leave and ret that follow this same fully descending stack convention. So if you deviate from that you can't use any of those. It's worth mentioning what fully descending means here. I'm surprised the article didn't mention it. "Fully" means that the stack pointer points to the last entry on the stack (the top). "Empty" means it points to the next entry just after the last (top) entry. That is, it points to where the next push would be written to. Anyway, you can have whatever calling convention you want, really. All you need is a way to pass arguments, return values, and to know where to return to. You could have a linked list of frames allocated by malloc() with the head pointer in EBP, for example. Say the return address and caller frame pointer is stored in the start of the frame. Then you would return by mov ebx, [ebp]; mov ebp, [ebp+4]; jmp ebx. Or something lile that. This is a pretty ridiculous calling convention though, but it'd work.
- AshamedCaptain 6y agoIn ARM you do have pop/push (and call/ret) instructions that can go in either direction, and most platforms I know still have a stack that grows to 0. TBH, dunno where the difficulty is.
- monocasa 6y agoSort of. The ARM stuff was allowed other uses because they were pretty generic load and store multiple instructions with a lot of increment/decrement options to make up for the original Acorn's lack of a DMA engine. On anything resembling a recent ARM core though, using the stack pointer register with anything other than a descending stack has you falling off the perf wagon as it's backed by a hardware stack engine.