TL;DR After a Multiboot loader transfers control to your code - to use the stack you only need to set ESP to a usable memory address, you do not need to set SS. SS (and all the other segment registers) will still have flat memory model characteristics in their associated descriptor cache entries until a time when the segments registers are reloaded. If you don't reload a segment register there is no problem.
---
bzt wrote:HI,
I see you had a bad day. Please calm, no-one is attacking you.
Who said anyone was attacking me? I'm dealing with someone who makes incorrect statements and pushes misinformation to people.
You clearly never read what I said in my original post, or you decided to read it and see it say something else. You made the claim that:
Calling a function requires accessing SS as well as ESP, neither is at a known state, you should not rely on them.
This is statement is FALSE. ESP doesn't have any guaranteed value (which I said in my original comment and was a point I agreed with you on), but SS is guaranteed to have a descriptor cache with a flat memory model. SS isn't guaranteed to be any particular selector value either. What also isn't guaranteed is for you to reload SS with a selector (even the same one) without your own GDT and corresponding GDT Record. I stated in other words that
AS LONG AS YOU DO NOT LOAD A SEGMENT REGISTER EITHER DIRECTLY OR INDIRECTLY, YOU DO NOT NEED ANYTHING OTHER THAN WHAT A MULTIBOOT LOADER PROVIDED.
This means that in order to use an instruction that references the stack in 32-bit protected mode you only need to set ESP. You DO NOT have to set SS. It will just work. Period, end of story. Anything else is nonsense unless you have a CPU with a bug. The access info, base, limit etc come from the descriptor cache which were loaded when the Multiboot loader created a GDT and set up all the segment registers initially (flat model) - THAT is guaranteed by the spec. After that the descriptor cache is referenced for all memory operands. The GDT Record (or LDT record) is not referenced (nor is the GDT or LDT itself) when you use a memory operand - period.
Things change when you want to LOAD a segment register with a selector. That is when the GDT (or the LDT) will be accessed (via the GDT record which Multiboot may leave in an invalid state), and it as that point that things are undefined. It may or may not work, but you can't make any assumptions about it. Loading a selector into a segment register requires the CPU looking at the GDT or LDT to get the descriptor information. That requires a GDT (or LDT) record loaded into the CPU that points at a GDT/LDT that has valid selectors in it. The Multiboot spec makes it clear that the GDT record (and what it points at) may be invalid and can't be relied on. What is in the descriptor cache is valid, as Multiboot guarantees that the entries in the descriptor cache is set as a flat data and code segment memory model.
With that being said it is very safe to set ESP to an appropriate memory address. The value in ESP isn't guaranteed to be usable (by the Multiboot spec) so you do need to set it before using the stack (like pushing parameters on the stack and calling into a kernel entry point). The kernel code can then do as it pleases until it needs to load a segment register (directly or indirectly). If you want interrupts you'll need a GDT. The kernel can set that up when it needs it. That would require reloading the segment registers with appropriate selectors.
bzt wrote:
You can find a minimal PoC on this very forum. In order to use the BIOS, my code switched back to real mode, and some shadow registers left unchanged causing faults in the BIOS routines, producing exactly the same error messages in bochs.
Again, this isn't what I am referring to. To enter real mode your code needs to load new values into the selector registers. That is where you need to have a GDT that is in a known good state and a GDT record (or LGDT) that points at it. If you load the SS register you of course can run into problems because you are loading the segment registers and without a valid GDT record it may not work as expected (if at all).
I'm looking for an example that fails when you NEVER modify any of the segment registers (directly or indirectly). What you are talking about is not what I am talking about. The multiboot spec doesn't guarantee that a particular selector is the code or segment selector. Those values can change between implementations (real GRUB vs QEMU `-kernel` option behave differently). Beyond a CPU bug or buggy Multiboot compliant bootloader all the segment registers and descriptor cache are loaded with required flat model values per the Multiboot spec.
I'm getting the general impression you don't understand how segment selectors, segment registers, descriptors, and the descriptor cache work at the architectural level. If you understood these things, and understood what the Multiboot spec actually says you would know what is safe and what isn't and why.