Long/64-Bit Mode switch from protected mode questions
Posted: Sun Feb 21, 2021 2:41 am
...
The Place to Start for Operating System Developers
http://f.osdev.org/
No, but it does mean linking the 32-bit C with the rest of your kernel will be difficult. Setting up the initial page tables is very simple, so you can do it using only assembly, and it's not as difficult to link 32-bit assembly with the rest of your kernel. Another option is to link the bootstrap code into a separate executable, and implement a simple ELF loader to handle the 64-bit executable. Another option is to use a different bootloader that will put the CPU in 64-bit mode for you.ngx wrote:1. Does this mean that I could only use assembly for bootstrap(as 32 bit C can't be linked to the 32+64bit assembly compiled as elf64 and 64-Bit C)?
If you tell the assembler to output elf64, you'll be able to link your 32-bit assembly with the rest of your kernel. You may need to look out for certain types of relocations, since the linker is only prepared to handle 64-bit code.ngx wrote:2. Would it would work always or only depending on the assembler target(e.g. -f elf32), what should assembler output and compiler target be?
Probably, but the specifics depend on exactly how you map your kernel after you enable paging. There's an example here.ngx wrote:3. Should there be any changes made in the linker-script for it to work?
Nope. GRUB2 can load it.ngx wrote:4. Should something be changed in GRUB2 to load the 64-bit ELF?
Maybe you had a typo somewhere? There's no "xi386" or "x86-64" targets.ngx wrote:5. This is not so related but for a day I was struggling because the clang code for --target=x86_64-elf was not working in grub and same was before with code with --target=xi386-elf, the fix was to change to x86-64-elf and i686-elf accordingly, what could the problem be?
Assembly routines and C functions only work if you put the CPU in the correct mode before calling them, and you must use assembly to switch modes. Better stick to just one mode switch, when you enable 64-bit mode for the first time. (Or use a bootloader that can do it for you.)ngx wrote:5. Do the assembly routines and C functions and variables declared during protected mode operation still work/can be called/accessed in 64-Bit mode?
The red zone is disabled when you tell the compiler to disable the red zone. The compiler may link against a runtime library, which must also be compiled without the red zone. (We have instructions on the wiki to set it up with GCC. I've never tried with Clang so I don't know if any special configuration is necessary.)ngx wrote:6. How is it possible to check that red-zone was disabled and long mode was enabled?
You don't need to prevent it.ngx wrote:7. Long mode requires paging to be enabled, but I am not really professional in this topic - so could it happen that some data or function get's split between two pages(like few bytes on end of one and few bytes on start of another page), then how do I prevent it?
Anywhere is fine, as long as it's memory not already in use for some other purpose. For switching to long mode, you might dedicate some space in the .bss section for enough page tables to get your kernel started, and then figure out where to put the rest once you're in 64-bit mode.ngx wrote:8. Where should the paging structures be stored as with their amount increasing, their storage requirements also increase?
For all of these, I recommend getting a second loader kernel. I have two loader kernels, one for UEFI and one for GRUB. The one for GRUB is a 32-bit kernel using GRUB's module mechanism to load the main kernel. It is written mostly in C, with obvious assembler stubs. The UEFI one is all written in C, and is 64-bit mode. Both will setup the environment the main kernel expects, transfer whatever start parameters are needed to the new kernel's stack in a unified format, and then just transfer control to the main kernel.ngx wrote:1. Does this mean that I could only use assembly for bootstrap(as 32 bit C can't be linked to the 32+64bit assembly compiled as elf64 and 64-Bit C)?
2. Would it would work always or only depending on the assembler target(e.g. -f elf32), what should assembler output and compiler target be?
3. Should there be any changes made in the linker-script for it to work?
Better to make the loader kernel for GRUB 32-bit. That way it will also work with GRUB legacy.ngx wrote:4. Should something be changed in GRUB2 to load the 64-bit ELF?
In theory, you can access protected mode data from long mode; those are just addresses, after all. 32-bit code will not work in 64-bit mode.ngx wrote:5. Do the assembly routines and C functions and variables declared during protected mode operation still work/can be called/accessed in 64-Bit mode?
Red zone has to be disabled in the compiler. You can't really check, except to make sure that "-mno-red-zone" is part of the compiler command line for all source files. You could read through the disassembly of the kernel to check manually, but that is probably not feasible for a kernel of any complexity.ngx wrote:6. How is it possible to check that red-zone was disabled and long mode was enabled?
You don't. It is not an issue. If you map the memory linearly, nothing bad will happen there. Most of the time you want to load the kernel as a blob to some place, and then map that place in one block to somewhere in virtual memory. Then it doesn't matter if a function crosses a page boundary, the addresses, both physical and virtual, will still be consecutive.ngx wrote:7. Long mode requires paging to be enabled, but I am not really professional in this topic - so could it happen that some data or function get's split between two pages(like few bytes on end of one and few bytes on start of another page), then how do I prevent it?
Wherever is free. Having a decent physical memory allocator is half the rent, here. I don't think having them contiguous in memory would aid anything. Just place them down wherever.ngx wrote:8. Where should the paging structures be stored as with their amount increasing, their storage requirements also increase?
I assume you mean a byte (or bit) map to track free frames? Unless you're doing something particularly fancy, you'll know at boot-time how many total frames the system has, and how many are usable (vs. reserved, ACPI, MMIO, etc.), and set the map accordingly. It shouldn't need to increase in size after that.ngx wrote:And also I have a question that is pretty close to this one - where should I place the byte-map of my page frame allocator and how can it dynamically increase?
If i allocate the page frame byte/bit map at boot time it could then be too large if there is a lot of RAM - how can i solve this?sj95126 wrote:I assume you mean a byte (or bit) map to track free frames? Unless you're doing something particularly fancy, you'll know at boot-time how many total frames the system has, and how many are usable (vs. reserved, ACPI, MMIO, etc.), and set the map accordingly. It shouldn't need to increase in size after that.ngx wrote:And also I have a question that is pretty close to this one - where should I place the byte-map of my page frame allocator and how can it dynamically increase?
I went with a different approach. I track the free frames as a linked list, where the pointer to the next frame is stored in the frame itself. So, I don't need any extra memory to track them, and finding a free frame is fast, because there's no searching - just take the first frame off the list. With paging, there's no need for the physical frames to be contiguous, so allocating multiple frames as a block isn't necessary.
Thanks for your reply, but what do you mean by placing them down - like in the physical or in the virtual memory and then what should i do if the table increases to high sizes, should i move other data forwards(as it increases) to fit it?nullplan wrote:For all of these, I recommend getting a second loader kernel. I have two loader kernels, one for UEFI and one for GRUB. The one for GRUB is a 32-bit kernel using GRUB's module mechanism to load the main kernel. It is written mostly in C, with obvious assembler stubs. The UEFI one is all written in C, and is 64-bit mode. Both will setup the environment the main kernel expects, transfer whatever start parameters are needed to the new kernel's stack in a unified format, and then just transfer control to the main kernel.ngx wrote:1. Does this mean that I could only use assembly for bootstrap(as 32 bit C can't be linked to the 32+64bit assembly compiled as elf64 and 64-Bit C)?
2. Would it would work always or only depending on the assembler target(e.g. -f elf32), what should assembler output and compiler target be?
3. Should there be any changes made in the linker-script for it to work?
Better to make the loader kernel for GRUB 32-bit. That way it will also work with GRUB legacy.ngx wrote:4. Should something be changed in GRUB2 to load the 64-bit ELF?In theory, you can access protected mode data from long mode; those are just addresses, after all. 32-bit code will not work in 64-bit mode.ngx wrote:5. Do the assembly routines and C functions and variables declared during protected mode operation still work/can be called/accessed in 64-Bit mode?Red zone has to be disabled in the compiler. You can't really check, except to make sure that "-mno-red-zone" is part of the compiler command line for all source files. You could read through the disassembly of the kernel to check manually, but that is probably not feasible for a kernel of any complexity.ngx wrote:6. How is it possible to check that red-zone was disabled and long mode was enabled?
As for long mode being active, you can test the LMA bit in EFER. That code should be mode agnostic.You don't. It is not an issue. If you map the memory linearly, nothing bad will happen there. Most of the time you want to load the kernel as a blob to some place, and then map that place in one block to somewhere in virtual memory. Then it doesn't matter if a function crosses a page boundary, the addresses, both physical and virtual, will still be consecutive.ngx wrote:7. Long mode requires paging to be enabled, but I am not really professional in this topic - so could it happen that some data or function get's split between two pages(like few bytes on end of one and few bytes on start of another page), then how do I prevent it?Wherever is free. Having a decent physical memory allocator is half the rent, here. I don't think having them contiguous in memory would aid anything. Just place them down wherever.ngx wrote:8. Where should the paging structures be stored as with their amount increasing, their storage requirements also increase?
Thanks for answering,Octocontrabass wrote:No, but it does mean linking the 32-bit C with the rest of your kernel will be difficult. Setting up the initial page tables is very simple, so you can do it using only assembly, and it's not as difficult to link 32-bit assembly with the rest of your kernel. Another option is to link the bootstrap code into a separate executable, and implement a simple ELF loader to handle the 64-bit executable. Another option is to use a different bootloader that will put the CPU in 64-bit mode for you.ngx wrote:1. Does this mean that I could only use assembly for bootstrap(as 32 bit C can't be linked to the 32+64bit assembly compiled as elf64 and 64-Bit C)?
If you tell the assembler to output elf64, you'll be able to link your 32-bit assembly with the rest of your kernel. You may need to look out for certain types of relocations, since the linker is only prepared to handle 64-bit code.ngx wrote:2. Would it would work always or only depending on the assembler target(e.g. -f elf32), what should assembler output and compiler target be?
Probably, but the specifics depend on exactly how you map your kernel after you enable paging. There's an example here.ngx wrote:3. Should there be any changes made in the linker-script for it to work?
Nope. GRUB2 can load it.ngx wrote:4. Should something be changed in GRUB2 to load the 64-bit ELF?
Maybe you had a typo somewhere? There's no "xi386" or "x86-64" targets.ngx wrote:5. This is not so related but for a day I was struggling because the clang code for --target=x86_64-elf was not working in grub and same was before with code with --target=xi386-elf, the fix was to change to x86-64-elf and i686-elf accordingly, what could the problem be?
Assembly routines and C functions only work if you put the CPU in the correct mode before calling them, and you must use assembly to switch modes. Better stick to just one mode switch, when you enable 64-bit mode for the first time. (Or use a bootloader that can do it for you.)ngx wrote:5. Do the assembly routines and C functions and variables declared during protected mode operation still work/can be called/accessed in 64-Bit mode?
Variables... may be accessible, if you link everything into a single executable, but I've never tried it and I'm sure there are ways it can break. Better avoid this too.
The red zone is disabled when you tell the compiler to disable the red zone. The compiler may link against a runtime library, which must also be compiled without the red zone. (We have instructions on the wiki to set it up with GCC. I've never tried with Clang so I don't know if any special configuration is necessary.)ngx wrote:6. How is it possible to check that red-zone was disabled and long mode was enabled?
You can check if long mode is active by looking at EFER.LMA (bit 10).
You don't need to prevent it.ngx wrote:7. Long mode requires paging to be enabled, but I am not really professional in this topic - so could it happen that some data or function get's split between two pages(like few bytes on end of one and few bytes on start of another page), then how do I prevent it?
Anywhere is fine, as long as it's memory not already in use for some other purpose. For switching to long mode, you might dedicate some space in the .bss section for enough page tables to get your kernel started, and then figure out where to put the rest once you're in 64-bit mode.ngx wrote:8. Where should the paging structures be stored as with their amount increasing, their storage requirements also increase?
What's considered too large? If you use a page frame bit map, where each bit represents a 4K frame, then you'll only need a fairly small amount of memory to manage it. If your system has 32GB of RAM, you'd only need 1MB to hold the bitmaps to track whether frames are allocated or not.ngx wrote:If i allocate the page frame byte/bit map at boot time it could then be too large if there is a lot of RAM - how can i solve this?sj95126 wrote:I assume you mean a byte (or bit) map to track free frames? Unless you're doing something particularly fancy, you'll know at boot-time how many total frames the system has, and how many are usable (vs. reserved, ACPI, MMIO, etc.), and set the map accordingly. It shouldn't need to increase in size after that.ngx wrote:And also I have a question that is pretty close to this one - where should I place the byte-map of my page frame allocator and how can it dynamically increase?
I went with a different approach. I track the free frames as a linked list, where the pointer to the next frame is stored in the frame itself. So, I don't need any extra memory to track them, and finding a free frame is fast, because there's no searching - just take the first frame off the list. With paging, there's no need for the physical frames to be contiguous, so allocating multiple frames as a block isn't necessary.
For physical memory: Your boot environment (i.e. BIOS or UEFI) will tell you about the layout of the physical memory. You can initialize your physical memory manager with the knowledge of these areas existing, then reserve the memory you need to preserve (so wherever your kernel is, and where your stack is, and whatever other memory you are already using). Whatever remains is physical memory free to use. So you can place your paging structures on an as-needed basis wherever is space when the time comes to allocate it. Paging structures never need to be allocated in any unit other than one page at a time.ngx wrote:Thanks for your reply, but what do you mean by placing them down - like in the physical or in the virtual memory and then what should i do if the table increases to high sizes, should i move other data forwards(as it increases) to fit it?
Why would the map need to dynamically increase? From the moment you boot, you know how much memory is in the system, so you know how big your byte-map is ever going to get, so you can just allocate the maximum size and never worry about it again.ngx wrote:And also I have a question that is pretty close to this one - where should I place the byte-map of my page frame allocator and how can it dynamically increase?
A bit of late reply, but I just remembered about what you said about linked list of frames while I was implementing my PMM. I wanted to ask how are you doing this because if you have the pointer to next frame inside another frame then there are only 4096-8 bytes available, how do you handle it so the stuff which uses the page does not overwrite the place where address is stored?sj95126 wrote:I assume you mean a byte (or bit) map to track free frames? Unless you're doing something particularly fancy, you'll know at boot-time how many total frames the system has, and how many are usable (vs. reserved, ACPI, MMIO, etc.), and set the map accordingly. It shouldn't need to increase in size after that.ngx wrote:And also I have a question that is pretty close to this one - where should I place the byte-map of my page frame allocator and how can it dynamically increase?
I went with a different approach. I track the free frames as a linked list, where the pointer to the next frame is stored in the frame itself. So, I don't need any extra memory to track them, and finding a free frame is fast, because there's no searching - just take the first frame off the list. With paging, there's no need for the physical frames to be contiguous, so allocating multiple frames as a block isn't necessary.
I can't really understand the algorithm here, can you please explain how should the linked list of frames work:Octocontrabass wrote:You don't. When the frame is part of the list, it's not being used by anything else, so nothing will overwrite the pointer. When the frame isn't part of the list, there's no pointer.