I can't find any clear "kernel stacks for dummies" articles on the web. It looks simple enough, but I can't confess to understanding the purpose of this in boilerplate ASM code for a Linux userland application (I'll get to the kernel stacks in a moment):
[SECTION .text]
global main
main:
push ebp
mov ebp, esp
push ebx
push esi
push edi
... stuff ...
pop edi
pop esi
pop ebx
mov esp, ebp
pop ebp
ret
....
This is apparently what the C library startup and control-returning code expects. You setup a stack frame in such a way and preserve the calling code's EBP, then make use of ESP through EBP. But why are EBX, ESI and EDI pushed onto the stack for a stack frame after EBP is saved? I thought everything was supposed to be between EBP and ESP, hence the name Base in Base Pointer. E.g.
EBP [mem] [mem] [mem] [mem] .... [mem] ESP
So is this a C library thing (why on earth? I'm not using the C library, however I will use Linux syscalls, which are written in C but with args passed mostly through registers... so wouldn't this mean normal C stack frame stuff isn't applicable?)
Getting to the kernel stack, what are the essential things to save when switching stacks between kernel, interrupt and user stacks? If applications used fp, wouldn't the fp registers need to be saved? Or are they already saved in a sense, since the fp registers are kind of stacky things?
I am thinking of using MicroC OS II as base code to build on, because it's small and beautiful. I can understand the code... I will be taking it and experimenting for educational purposes, e.g. adding paging, syscalls, that sort of do. Is this a good kernel to start from? Please check it out: http://www.ucos-ii.com. Please look at the Intel/AMD 32-bit protected mode flat model port.
I don't quite "get" kernel and interrupt stacks, I mean, I understand what they are for and so on, but the details of switching between them and what goes in a stack frame and the EBP...ESP business trips me up. Everything I have read so far seems vague. Is it vague because I can do whatever the hell I like with those stacks, as long as I store the essentials on them? (In other words, there are many ways to skin a cat or make a stack? No one way is strictly correct, excepting saving state in task switching?)
Now, Intel hardware has the TSS and provides hardware for task switching. I think I may as well use it. Will this make my job easier?
Now to interrupt stacks. What's the deal with these stacks, what MUST go there and what can optionally go there, and I need more examples of ISRs.... any links appreciated. (Again, is MicroC OS II a good starting point?)
Eesh, writing an OS from scratch is much harder than modifying the C sources of an existing OS... C is so.. so high level...
Many Thanks...
Kernel stacks, saving registers, etc.
- Pype.Clicker
- Member
- Posts: 5964
- Joined: Wed Oct 18, 2006 2:31 am
- Location: In a galaxy, far, far away
- Contact:
Re:Kernel stacks, saving registers, etc.
here are a few rules that i reverse-engineered from the C calling conventions:
- ebp is the base of the stack frame for a procedure. To clean up the stack, it should be enough for a function to issue
Code: Select all
mov esp, ebp pop ebp
- [ebp] is the pointer to the previous stack frame. always. this means that you can get the stack frame list from main() to the current function by just following a linked list:
Code: Select all
for(p=getCurrentEbpt(); p; p=*p) kprint ("frame start @ %x",p);
- [ebp+4] is the return address of the current function. This means that you can get a backtrace of the current thread by looking at base[1] in every stackframe you get. If you had push things before "push ebp; mov ebp, esp", this rule is broken and you can't tell what you're executing anymore.
Re:Kernel stacks, saving registers, etc.
Oh, thanks Pype.Clicker. I just figured out the rest of it for myself, sans the part you spoke about. Damn, nobody told me not to write aimlessly about questions at 4 AM. ::)
Re:Kernel stacks, saving registers, etc.
The issues of register saving in the function calling conventions are quite different from those in the kernel and interrupt stacks. Because interrupts and task switches are usually initiated by events outside of the running task, and cannot be predicted by it, it is necessary to save as much of the state of the halted task as possible; this means saving all the registers, and in some designs, additional task information as well. However, they do not usually use the ESP and EBP in any specific manner, as no parameters are being passed on the stack; indeed, in the case of task switches, the stack information is saved the same as the rest, and the kernel usually
Function calls are quite different, for the simple reason that they are internal to the task, not external to it; the calling function knows when they will happen, and some of the burden of saving state can be left to it rather than the called function. The gcc C calling convention requires that EBP, ESP, EBX, ESI, and EDI will be saved by the called function, that EAX and EDX will not be saved, and does not specify the condition of the other registers.
These choices are fairly easy to understand once you look at them. It means that the called function stores all of the crucial state information (EBP, ESP, and, implicitly in the CALL/RET operations, EIP) needed to restore the calling function's environment, as well as the registers most often used for indexing functions (EBX, ESI, EDI). It then uses the two registers most often used for temporary results (EAX and EDX) to pass the return values on (smaller values are held in EAX, while values of more than 32 bits are split between EAX and EDX, with the high bytes in EDX; if the variable is larger than 64 bits, it is implicitly passed as a pointer and automagically dereferenced and saved to a temporary value, IIUC). All the other registers are left to the calling function's discretion; since (AFAICT) gcc generated code uses ECX, et. al., rather sparsely, this is a reasonable compromise, by the lights of the compiler designers.
Function calls are quite different, for the simple reason that they are internal to the task, not external to it; the calling function knows when they will happen, and some of the burden of saving state can be left to it rather than the called function. The gcc C calling convention requires that EBP, ESP, EBX, ESI, and EDI will be saved by the called function, that EAX and EDX will not be saved, and does not specify the condition of the other registers.
These choices are fairly easy to understand once you look at them. It means that the called function stores all of the crucial state information (EBP, ESP, and, implicitly in the CALL/RET operations, EIP) needed to restore the calling function's environment, as well as the registers most often used for indexing functions (EBX, ESI, EDI). It then uses the two registers most often used for temporary results (EAX and EDX) to pass the return values on (smaller values are held in EAX, while values of more than 32 bits are split between EAX and EDX, with the high bytes in EDX; if the variable is larger than 64 bits, it is implicitly passed as a pointer and automagically dereferenced and saved to a temporary value, IIUC). All the other registers are left to the calling function's discretion; since (AFAICT) gcc generated code uses ECX, et. al., rather sparsely, this is a reasonable compromise, by the lights of the compiler designers.