Paging and Dynamic Loading/Relocation Questions
Posted: Wed Jun 21, 2017 7:08 am
I'm currently looking for the best way to initially make use of paging and given that I need to load modules dynamically into my kernel core, I think that I have come up with learning some details, but I don't know if it's a complete view on its own.
It seems that paging was never intended as a relocation mechanism. The very only thing paging does is mapping physical memory addresses to virtual addresses, but since you can only map a virtual address once in a process, you woldn't be able to map all of the dynamic loaded libraries to, say, address 0x100000 for the same process, so you would really have a limited number of libraries loaded simultaneously depending on the size of the memory.
It probably means that if a Linux shared library or a DLL is loaded, for example, at virtual address 0x100000, then no other code or data in the whole system could use that address in its own virtual address space, unless there's some API that allows to use that virtual address with the condition of becoming unable to use that library.
Maybe that's a reason for the higher-half kernel (to have a half for programs and some user libraries and another half for the kernel and system libraries), but a system could probably be loaded cleanly without that, specially seeing how now the half in 64-bit systems might not be practical, but I don't know enoug about this point.
I say that paging isn't really for relocation because we could recompile at run time, write position-independent code, use protected-mode or real-mode segmentation, instead of paging. We could even use the same physical address for all libraries, keep track of which one is currently loaded, and load back and forth the one we need, but obviously this last thing would produce a very unclean and confusing system.
It seems that if we want to relocate something, the only really intended thing to do so is to actually know which parts of the opcodes to relocate depending on where the program was actually loaded, by the time the code is loaded, and do so manually. Then we will have a relocation system portable to flat memory, real-mode, paging, protected-mode segmentation...
Shared libraries, dynamic link libraries, drivers, etc., are in reality like kernel modules, so they are only loaded once and mapped for any processes that want to use them. They need to be relocated first of course, except maybe for very few cases. The only detail is that all those libraries or drivers can be loaded with different privilege levels, but they are still basically kernel modules.
______________________________________________________________________
______________________________________________________________________
______________________________________________________________________
______________________________________________________________________
So, I'm figuring out that if I want to use paging for dynamically loading modules into my kernel and programs, I'd actually be better of by developing a relocation system instead, to begin with.
But for that it seems that I would need my relocator to know all of the x86 opcodes in existence for 16, 32 and 64-bit mode, then provide it with a table with the start address of each instruction, so that it relocates as indicated.
It seems to me that I cannot do this with paging alone. If I were to load dynamic libraries in their own virtual address spaces using multitasking capabilities for it, I would probably need some inter-process calling functions to make use of such libraries, but then I would need some way to distinguish between libraries, if I load them to the same virtual address in different virtual spaces. Then I would need to indirectly call those libraries via a position-independent mechanism like an interrupt that selected between them. But it seems too unclean of a code base.
It seems that whichever method I choose to use to implement dynamic loading, it has its own different yet equally complex details to implement, but a generic relocator for code and data seems to come before memory management. It seems to be portable to any memory system/layout.
The only other option would be to link modules statically into the kernel, but even in my OS, which is extremely basic, the need to dynamically load stuff at any address can no longer be escaped without becoming stuck with lack of system capabilities.
I thought that I could have the kernel load a binary, program or library, at any address, and then return me the base load address probably in EBP/WIDEBP, but then I would probably need to rewrite all of the code I've ever created to support this position independence, and if I load more than one library it would also become very difficult and very unclean to keep a base address initially in EBP for each one and switch between them, and being able to store and find it repeatedly.
Using relocations without paging, just flat memory, would probably be like having a purely single-tasking system. Paging only makes it easier and more secure to implement multiple process states but still is nothing more than a facility to disguise physical addresses with virtual ones, with all of the implicit protective effect that might have, but it's nothing more than that.
We could still implement a multitasking system with a good relocator where we could switch tasks and only use physical addresses ever, and we could still implement good memory protection, but then we would rely much much more on disk to swap out memory regions or purely physical pages that we need to use by another process mapped to the very same regions, which are taken up by a currently inactive task or set of data.
Probably implementing such system with paging-like functions, multitasking and memory swapping capabilities without actually use paging could prove useful to make for a more robust system that would be fundamentally independent from any hardware paging or special CPU protection, but which later could benefit from that as an additional module. We could debug a system in this way, and when all functions are mature, we could add actual paging as a trivial additional step given that we would have been managing things with the same structure but purely in software. The only thing finally enabling paging would achieve would be decreasing swapping memory from disk and provide additional hardware-based protection from the CPU, and to avoid unmanageable fragmentation as a plus side effect of virtual addressing mapping, but frankly apparently not so much more at all.
So how do you do with or without a relocator and with or without paging, to support dynamic loading of kernel module,
library code, and then program code?
It seems that paging was never intended as a relocation mechanism. The very only thing paging does is mapping physical memory addresses to virtual addresses, but since you can only map a virtual address once in a process, you woldn't be able to map all of the dynamic loaded libraries to, say, address 0x100000 for the same process, so you would really have a limited number of libraries loaded simultaneously depending on the size of the memory.
It probably means that if a Linux shared library or a DLL is loaded, for example, at virtual address 0x100000, then no other code or data in the whole system could use that address in its own virtual address space, unless there's some API that allows to use that virtual address with the condition of becoming unable to use that library.
Maybe that's a reason for the higher-half kernel (to have a half for programs and some user libraries and another half for the kernel and system libraries), but a system could probably be loaded cleanly without that, specially seeing how now the half in 64-bit systems might not be practical, but I don't know enoug about this point.
I say that paging isn't really for relocation because we could recompile at run time, write position-independent code, use protected-mode or real-mode segmentation, instead of paging. We could even use the same physical address for all libraries, keep track of which one is currently loaded, and load back and forth the one we need, but obviously this last thing would produce a very unclean and confusing system.
It seems that if we want to relocate something, the only really intended thing to do so is to actually know which parts of the opcodes to relocate depending on where the program was actually loaded, by the time the code is loaded, and do so manually. Then we will have a relocation system portable to flat memory, real-mode, paging, protected-mode segmentation...
Shared libraries, dynamic link libraries, drivers, etc., are in reality like kernel modules, so they are only loaded once and mapped for any processes that want to use them. They need to be relocated first of course, except maybe for very few cases. The only detail is that all those libraries or drivers can be loaded with different privilege levels, but they are still basically kernel modules.
______________________________________________________________________
______________________________________________________________________
______________________________________________________________________
______________________________________________________________________
So, I'm figuring out that if I want to use paging for dynamically loading modules into my kernel and programs, I'd actually be better of by developing a relocation system instead, to begin with.
But for that it seems that I would need my relocator to know all of the x86 opcodes in existence for 16, 32 and 64-bit mode, then provide it with a table with the start address of each instruction, so that it relocates as indicated.
It seems to me that I cannot do this with paging alone. If I were to load dynamic libraries in their own virtual address spaces using multitasking capabilities for it, I would probably need some inter-process calling functions to make use of such libraries, but then I would need some way to distinguish between libraries, if I load them to the same virtual address in different virtual spaces. Then I would need to indirectly call those libraries via a position-independent mechanism like an interrupt that selected between them. But it seems too unclean of a code base.
It seems that whichever method I choose to use to implement dynamic loading, it has its own different yet equally complex details to implement, but a generic relocator for code and data seems to come before memory management. It seems to be portable to any memory system/layout.
The only other option would be to link modules statically into the kernel, but even in my OS, which is extremely basic, the need to dynamically load stuff at any address can no longer be escaped without becoming stuck with lack of system capabilities.
I thought that I could have the kernel load a binary, program or library, at any address, and then return me the base load address probably in EBP/WIDEBP, but then I would probably need to rewrite all of the code I've ever created to support this position independence, and if I load more than one library it would also become very difficult and very unclean to keep a base address initially in EBP for each one and switch between them, and being able to store and find it repeatedly.
Using relocations without paging, just flat memory, would probably be like having a purely single-tasking system. Paging only makes it easier and more secure to implement multiple process states but still is nothing more than a facility to disguise physical addresses with virtual ones, with all of the implicit protective effect that might have, but it's nothing more than that.
We could still implement a multitasking system with a good relocator where we could switch tasks and only use physical addresses ever, and we could still implement good memory protection, but then we would rely much much more on disk to swap out memory regions or purely physical pages that we need to use by another process mapped to the very same regions, which are taken up by a currently inactive task or set of data.
Probably implementing such system with paging-like functions, multitasking and memory swapping capabilities without actually use paging could prove useful to make for a more robust system that would be fundamentally independent from any hardware paging or special CPU protection, but which later could benefit from that as an additional module. We could debug a system in this way, and when all functions are mature, we could add actual paging as a trivial additional step given that we would have been managing things with the same structure but purely in software. The only thing finally enabling paging would achieve would be decreasing swapping memory from disk and provide additional hardware-based protection from the CPU, and to avoid unmanageable fragmentation as a plus side effect of virtual addressing mapping, but frankly apparently not so much more at all.
So how do you do with or without a relocator and with or without paging, to support dynamic loading of kernel module,
library code, and then program code?