PgrAm wrote:
there seems to be more than one kind of "relocation" when it comes to ELF
Not really many kind; it's only one that has addend, and one that hasn't. Besides of that there's a record for code (functions), and a record for data. That's 4. You can ask the linker to only produce some of these. You don't have to support all, only the ones that your modules actually use (which in turn depends on your linker script).
PgrAm wrote:
I'm interested in how you chose to implement kernel modules if you have them?
You can do that in many different ways. The simplest I think is to compile the modules as relocatable shared libraries, then designate an area for them in your kernel address-space. In your kernel you should have a list which keeps track of that area, aka which module loaded at which address and what is the next free address. Once the module is loaded, you have two options:
- use specific function names which you can add to a hook list in your kernel (like "irqhandler" or "opendevice" for example)
- call the entry point in each module which in turn calls register hooks calls to add its functions to the kernel's list.
PgrAm wrote:
And if they should dynamic libraries can someone refer me to some resources on how to load/relocate these?
Our
wiki page has some information on this, but it mainly focuses on loading shared libraries in user-space using the "needed" records in ELF.
In addition to that, this is what you need:
- you must create a list with addresses that you want to export to the modules. If you only allow your modules to call kernel functions, then this list can be static. If you allow your modules to call each other's functions (like usb-storage calling a function in usb-roothub), then you have to maintain this list dynamically as you load and unload the modules. When you add to this list, you'll save the module load address + offset in the module plus the symbol.
- on module load, you have to locate the relocation table(s). Some might have one table for both data and code, others might have two separated tables (depends on your linker script).
- iterate through that table, and replace the offset in the module with the offset in your export list + addend. So *(offset in table + load address) = offset in export list + addend in table.
- (optional) if you want dynamic export list, then iterate through the dynamic segment and add offsets of the newly loaded module to the list.
Here's a implementation to look at:
-
locating RELA table-
replacing plt offsets-
replacing symbol offsetsThis code does the aforementioned list with addresses the other way around: the relas array contains pairs of symbol and offset, but this is not the offset of the symbol, rather the offset where it's referenced. So when I load an ELF with an exported symbol, and I know where it's loaded in the memory, I iterate through this list and fill its references. This is faster, although care must be taken to load the ELF binaries in a specific order (this code loads shared libraries for an address-space, so I can know the order in advance; for you, as you might load and unload the modules in any order, so you'll need to keep a list of symbol and offset where is located rather than referenced.)
Another thing, this code assumes there's only one relocation table with both jump and data entries, because I wrote the linker script that way. It was due to a compatibility issue with gcc and Clang, this is the exception rather than the rule. You could use data relocation entries in a separate table too, this only depends on how you write your linker script.
(The code has comments about GNU ld and LLVM lld. Those are not relevant any more since I've convinced the LLVM developers to implement RELA tables the same way as GNU ld, and they did.)
Cheers,
bzt