"Linking" drivers to a binary kernel
"Linking" drivers to a binary kernel
I'm at the stage where I've started to flesh out a common driver interface for my OS. Seems to work well but the driver code is compiled directly into the kernel. I am using a flat-binary kernel just 'cause I like to make things harder
I'm using GCC/LD toolchains.
Now, I'd like to place driver code in relocatable external modules and simply give the drivers direct access to the kernel's fixed functions, etc... Thanks to my lack of understanding of the linker, I don't really know how to go about this. Is it possible to output a second copy of the kernel as a shared object (.so), link the driver against that during compile-time and then use the driver with the flat-binary kernel that is loaded by the boot-loader? Would the so's offsets match those of the binary kernel?
Is there a better way to do this? I'm sure there are several different ways to approach it.
The reason, by the way, that I insist on a flat-binary kernel is to force myself to deal with exactly this sort of situation. I won't learn a damn thing about anything if the tools do absolutely everything for me
Thanks.
I'm using GCC/LD toolchains.
Now, I'd like to place driver code in relocatable external modules and simply give the drivers direct access to the kernel's fixed functions, etc... Thanks to my lack of understanding of the linker, I don't really know how to go about this. Is it possible to output a second copy of the kernel as a shared object (.so), link the driver against that during compile-time and then use the driver with the flat-binary kernel that is loaded by the boot-loader? Would the so's offsets match those of the binary kernel?
Is there a better way to do this? I'm sure there are several different ways to approach it.
The reason, by the way, that I insist on a flat-binary kernel is to force myself to deal with exactly this sort of situation. I won't learn a damn thing about anything if the tools do absolutely everything for me
Thanks.
- NickJohnson
- Member
- Posts: 1249
- Joined: Tue Mar 24, 2009 8:11 pm
- Location: Sunnyvale, California
Re: "Linking" drivers to a binary kernel
If you have to use a flat binary kernel, you can't do anything with linking symbols and such. However, if you fix the address of kernel structures, it is possible to do driver/module loading. I would recommend having a table at a fixed address in the kernel that contains pointers to the functions that make up the driver interface. Drivers would just jump to the addresses at offsets in that table. Alternatively, you could have the drivers use a system call interface through interrupts just like user programs do, except that the state wouldn't be saved and it would act just like a function call with some extra return information - that would be slower and more complex, but possibly more flexible.
Re: "Linking" drivers to a binary kernel
When I link the kernel, could I have the linker create a set of "stubs" in another file that would act as a lookup table for linking the drivers against? All the drives need to know is that it has a function it's allowed to use e.g. kmalloc(). That function would be at a fixed address in the kernel.
Basically the "stub" file would just have entries like:
The drivers could be statically linked to that stub file (.a format maybe - which is just archived .o files).
Basically I want the drivers to think that they're inside the kernel (so I can just use kernel headers in the drivers) but I obviously don't want the entire kernel to be compiled into each driver
Basically the "stub" file would just have entries like:
Code: Select all
kmalloc:
jmp 0xC******* (whatever the kmalloc address in the kernel is).
kprintf:
jmp 0xC*******
etc...
Basically I want the drivers to think that they're inside the kernel (so I can just use kernel headers in the drivers) but I obviously don't want the entire kernel to be compiled into each driver
Re: "Linking" drivers to a binary kernel
Hi,
Cheers,
Adam
AFAIK, you won't get the linker to do this for flat binary. What you can do, is create the pointer file yourself, include it in the kernel binary and relocate it to a fixed address, which is the first option suggested by NickJohnson. Another thought crosses my mind - could you link as ELF, pull the symbols and the objcopy to flat binary? I don't know if objcopy preserves offsets, but you could try...When I link the kernel, could I have the linker create a set of "stubs" in another file that would act as a lookup table for linking the drivers against?
If you get the linker to extract symbols, it's doing as much of the work for you as if you just create an ELF in the first place. Using ELF, the static linker still does not help with dynamic linking, so you'll still need to have a dynamic linker in your kernel. This means that you'll understand just as much about the whole linking process whether you use ELF or flat binaries.The reason, by the way, that I insist on a flat-binary kernel is to force myself to deal with exactly this sort of situation. I won't learn a damn thing about anything if the tools do absolutely everything for me
Cheers,
Adam
Re: "Linking" drivers to a binary kernel
That would mean the driver's dynamically linked to the kernel, wouldn't it?, which isn't really what I'm trying to do. The driver should be statically linked to code in the kernel but the driver code should be re-locatable - the driver provides a set of entries to the kernel when it's loaded. I know I'm doing it a dumb way and people might ask "why?" and I respond with: "That's the way I want to do it"AJ wrote:What you can do, is create the pointer file yourself, include it in the kernel binary and relocate it to a fixed address, which is the first option suggested by NickJohnson
An ld map file basically provides the information needed:
Code: Select all
.text 0x00000000c0102650 0x232 build/Release/GNU-Linux-x86/src/kheap.o
0x00000000c0102650 kfree
0x00000000c01026e5 morecore
0x00000000c01027cc kmalloc
Thanks, guys, for the input.
Re: "Linking" drivers to a binary kernel
Ok, I won't argue with how you are doing this, but how about another suggestion?
How about you create your table as we described above and place it right at the end of your kernel binary:
Now, you compile your kernel as usual, but use your normal command-line tools to strip out the last x bytes of your flat binary. This stripped out section can then be dumped as a separate file. Your drivers can then be statically linked with this function pointer array and as long as you keep your exported functions in the same order across kernel versions, your drivers should automatically be forwards compatible too!
Cheers,
Adam
How about you create your table as we described above and place it right at the end of your kernel binary:
Code: Select all
__kmalloc_link:
_kmalloc ; address of kmalloc
__kfree_link:
_kfree ; address of kfree
times n db 0 ; pad table to a known size (x).
Cheers,
Adam
Re: "Linking" drivers to a binary kernel
Ok, that might be a step in the direction I'm thinking of going. At least now I'm thinking of things I hadn't considered before.
Thanks.
Thanks.
Re: "Linking" drivers to a binary kernel
I think this could be part of what I was looking for:
From the ld man page:
From the ld man page:
This won't work against the flat-binary but, if I have a second "intermediary" kernel in elf format, I could link against that and still use the binary one to boot. This of course, will only work if the offsets in the elf file are the same as in the binary. Yeah, not the most elegant way but... I don't care. It's not like anyone else will ever use this OS-R filename
--just-symbols=filename
Read symbol names and their addresses from filename, but do not relocate it or include it in the output. This
allows your output file to refer symbolically to absolute locations of memory defined in other programs. You may
use this option more than once.
For compatibility with other ELF linkers, if the -R option is followed by a directory name, rather than a file name,
it is treated as the -rpath option.
- Firestryke31
- Member
- Posts: 550
- Joined: Sat Nov 29, 2008 1:07 pm
- Location: Throw a dart at central Texas
- Contact:
Re: "Linking" drivers to a binary kernel
I think it would be easier and more flexible to go with the interrupt interface. That way you don't have to recompile every driver ever written for your OS just because you changed a string from "appications" to "applications" or something similarly small but potentially binary-breaking. Drivers just push all of the arguments to the stack, store the call ID in, say, eax, then call an interrupt, perhaps int 255. On the kernel side, it gets all of the parameters from the stack and calls the appropriate function. If the kernel changes, it's far less likely you will need to recompile every driver because they all use a standardized interface that doesn't change between kernel versions. It also (theoretically) makes it easier to push drivers to user mode should you choose to go that direction.
Owner of Fawkes Software.
Wierd Al wrote: You think your Commodore 64 is really neato,
What kind of chip you got in there, a Dorito?
Re: "Linking" drivers to a binary kernel
I'd make a table pointing to everything, and either use relocation entries in the driver to redirect calls to the right address, or copy the table to an address stored in each driver, copying only as many DWORDs as the driver requires (so that newer versions of the kernel won't overwrite things past the end of the table).
If each driver is linked against specific addresses of kernel functions during linking, they won't be compatible with newer versions of the kernel, which is a bad idea.
If each driver is linked against specific addresses of kernel functions during linking, they won't be compatible with newer versions of the kernel, which is a bad idea.
Re: "Linking" drivers to a binary kernel
True. The only issue would be that I'd have to create a new set of functions to invoke those interrupts. So, I'd need a new malloc(), (or whatever function) that invokes an interrupt that calls the kernel's malloc() rather than just calling the kernel's malloc() directly from the driver. Although, I guess one really shouldn't let drivers just do whatever they want.... and I know having to recompile the drivers every time would be a pain (which is one of the things I really don't like about Linux actually).Firestryke31 wrote:I think it would be easier and more flexible to go with the interrupt interface. That way you don't have to recompile every driver ever written for your OS just because you changed a string from "appications" to "applications" or something similarly small but potentially binary-breaking. Drivers just push all of the arguments to the stack, store the call ID in, say, eax, then call an interrupt, perhaps int 255. On the kernel side, it gets all of the parameters from the stack and calls the appropriate function. If the kernel changes, it's far less likely you will need to recompile every driver because they all use a standardized interface that doesn't change between kernel versions. It also (theoretically) makes it easier to push drivers to user mode should you choose to go that direction.
Re: "Linking" drivers to a binary kernel
I agree. But I'm just at a point where I don't have to take a billion other things into consideration just to get one thing done. I'm already handling a billion other things to even get to the point I'm at nowGigasoft wrote:If each driver is linked against specific addresses of kernel functions during linking, they won't be compatible with newer versions of the kernel, which is a bad idea.
- Owen
- Member
- Posts: 1700
- Joined: Fri Jun 13, 2008 3:21 pm
- Location: Cambridge, United Kingdom
- Contact:
Re: "Linking" drivers to a binary kernel
Create a file that looks something like
Don't put header guards in it.
Create a header file, say, kapistatic.h, for when linking statically. do something like
Create another header file, say, kapidynamic.h, for when linking dynamically. do something like
In the kernel, create a source file somewhere similar to the following, which defines and fills your structure and builds your stubs:
Finally, create a stub file to be linked with the modules in order to configure them, and contain the function stubs:
Make _start the entry point of your module some how. When the kernel finds it, it should call it with it's own value of the kernel interface pointer. The module should then get a pointer to the structure containing all the user functions. We also define little assembly stubs which load the function address from the structure we defined and jump to it.
The only hurdle then is loading the drivers
Warning: The above code is untested
Code: Select all
DECLARE_FUNCTION(void, name, args)
DECLARE_FUNCTION(int, name2, args2)
DECLARE_FUNCTION(char, name3, arga, argb, arcg)
Create a header file, say, kapistatic.h, for when linking statically. do something like
Code: Select all
#define DECLARE_FUNCTION(_type, _name, ...) \
extern _type _name(__VA_ARGS__);
#include "functionFile.h"
Code: Select all
#define DECLARE_FUNCTION(_type, _name, ...) \
_type (*_name)(__VA_ARGS__),
struct KernelInterface {
size_t numEntries;
#include "functionFile.h"
};
extern struct KernelInterface *ki;
#define DECLARE_FUNCTION(_type, _name, ...) \
extern _type _name(__VA_ARGS__); \
#include "functionFile.h"
Code: Select all
KernelInterface ki = {
#define DECLARE_FUNCTION(_type, _name, ...) \
._name = _name,
#include "functionFile.h"
};
Code: Select all
KernelInterface* ki;
void _start(KernelInterface* pki) {
ki = pki;
moduleMain();
}
asm(".extern ki");
#define STRIFY(_val) #_val
#define DECLARE_FUNCTION(_type, _name, ...) \
asm(\
".global " STRIFY(_name) ";" \
STRIFY(_name) ":" \
"movq ki, %rax;" \
"movq " STRIFY(offsetof(KernelInterface, _name)) "(%rax), %rax;" \
"jmpq *%rax" \
);
#include "functionFile.h"
The only hurdle then is loading the drivers
Warning: The above code is untested
Re: "Linking" drivers to a binary kernel
I started the same way as you. I made a table with a specific offset, then each driver could grab it's location. I had it working completely, but it was a mess, adding new functions, keeping track of which function was what offset, etc. Finally, I switched over to using a format that stored symbols (COFF, but i'll probably change again due to lack of 64-bit support). My life is so much easier now, I can dynamically link files in memory, and one driver can rely on another driver as well, not just the kernel. Any driver or kernel can be recompiled, functions moved, etc without breaking stuff. In the end, I wish I would have just done linking in the first place, it ended up being much simpler than trying to avoid it, and 10x more flexible. Testing my linker was simple as well, I simply compiled to a COFF file, then to flat binary at a specific location. I tested my linker against the coff file and made sure it matched the flat binary. Once they matched up in all cases, I added it into my kernel and good to go. I don't recommend coff files, it was a pain to get working properly and there are better formats available, but it was pretty simple to get going.
Re: "Linking" drivers to a binary kernel
m32: If you have an independent mind, write your own compiler/linker. This may seem like a daunting task, but balanced against life-time wasted trying to make other people's software do what they never imagined, this is time well-spent.
Start in a small way, maybe write a program which converts a text file containing your ideas into a format that can be read by your linker.
In effect, this is what Ready4Dis has already done
Start in a small way, maybe write a program which converts a text file containing your ideas into a format that can be read by your linker.
In effect, this is what Ready4Dis has already done