"Linking" drivers to a binary kernel

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
User avatar
-m32
Member
Member
Posts: 120
Joined: Thu Feb 21, 2008 5:59 am
Location: Ottawa, Canada

"Linking" drivers to a binary kernel

Post by -m32 »

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.
User avatar
NickJohnson
Member
Member
Posts: 1249
Joined: Tue Mar 24, 2009 8:11 pm
Location: Sunnyvale, California

Re: "Linking" drivers to a binary kernel

Post by NickJohnson »

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.
User avatar
-m32
Member
Member
Posts: 120
Joined: Thu Feb 21, 2008 5:59 am
Location: Ottawa, Canada

Re: "Linking" drivers to a binary kernel

Post by -m32 »

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:

Code: Select all

kmalloc:
   jmp 0xC******* (whatever the kmalloc address in the kernel is).
kprintf:
   jmp 0xC*******

etc...
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 :)
User avatar
AJ
Member
Member
Posts: 2646
Joined: Sun Oct 22, 2006 7:01 am
Location: Devon, UK
Contact:

Re: "Linking" drivers to a binary kernel

Post by AJ »

Hi,
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?
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...
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
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.

Cheers,
Adam
User avatar
-m32
Member
Member
Posts: 120
Joined: Thu Feb 21, 2008 5:59 am
Location: Ottawa, Canada

Re: "Linking" drivers to a binary kernel

Post by -m32 »

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
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" ;)

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
Obviously not the right format for linking code against.... but maybe I can work with it somehow.

Thanks, guys, for the input.
User avatar
AJ
Member
Member
Posts: 2646
Joined: Sun Oct 22, 2006 7:01 am
Location: Devon, UK
Contact:

Re: "Linking" drivers to a binary kernel

Post by AJ »

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:

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).
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
User avatar
-m32
Member
Member
Posts: 120
Joined: Thu Feb 21, 2008 5:59 am
Location: Ottawa, Canada

Re: "Linking" drivers to a binary kernel

Post by -m32 »

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.
User avatar
-m32
Member
Member
Posts: 120
Joined: Thu Feb 21, 2008 5:59 am
Location: Ottawa, Canada

Re: "Linking" drivers to a binary kernel

Post by -m32 »

I think this could be part of what I was looking for:

From the ld man page:
-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.
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 ;)
User avatar
Firestryke31
Member
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

Post by Firestryke31 »

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?
Gigasoft
Member
Member
Posts: 856
Joined: Sat Nov 21, 2009 5:11 pm

Re: "Linking" drivers to a binary kernel

Post by Gigasoft »

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.
User avatar
-m32
Member
Member
Posts: 120
Joined: Thu Feb 21, 2008 5:59 am
Location: Ottawa, Canada

Re: "Linking" drivers to a binary kernel

Post by -m32 »

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.
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).
User avatar
-m32
Member
Member
Posts: 120
Joined: Thu Feb 21, 2008 5:59 am
Location: Ottawa, Canada

Re: "Linking" drivers to a binary kernel

Post by -m32 »

Gigasoft 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.
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 now :)
User avatar
Owen
Member
Member
Posts: 1700
Joined: Fri Jun 13, 2008 3:21 pm
Location: Cambridge, United Kingdom
Contact:

Re: "Linking" drivers to a binary kernel

Post by Owen »

Create a file that looks something like

Code: Select all

DECLARE_FUNCTION(void, name, args)
DECLARE_FUNCTION(int, name2, args2)
DECLARE_FUNCTION(char, name3, arga, argb, arcg)
Don't put header guards in it.

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"
Create another header file, say, kapidynamic.h, for when linking dynamically. do something like

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"
In the kernel, create a source file somewhere similar to the following, which defines and fills your structure and builds your stubs:

Code: Select all

KernelInterface ki = {
#define DECLARE_FUNCTION(_type, _name, ...) \
  ._name = _name,
#include "functionFile.h"
};
Finally, create a stub file to be linked with the modules in order to configure them, and contain the function stubs:

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"
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
Ready4Dis
Member
Member
Posts: 571
Joined: Sat Nov 18, 2006 9:11 am

Re: "Linking" drivers to a binary kernel

Post by Ready4Dis »

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.
grobdj
Posts: 1
Joined: Sat Jan 02, 2010 5:21 pm

Re: "Linking" drivers to a binary kernel

Post by grobdj »

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
Post Reply