It's not easy.
First, decide on your object format. For my OS, I dynamically load in RDOFF2 objects (aka, the format developed by the Nasm developers). It may not be as extensible as ELF (although it comes surprisingly close), but it's a simple format, and that rates highly in my books
Second, research this object file format thoroughly. As you do so, the concept of dynamic linking will probably start to make more sense.
Essentially, all you do is load the object into memory, and look at the object's header. The header will contain information about the object, like where each of the sections (.text, .code) are, and how much space should be reserved for the .bss section.
The header will also contain (more importantly) offsets into these sections that need to be 'fixed' In other words, these are variables, function pointers, etc, that are contained elsewhere... in other objects... or in your kernel. You'll have to go through all of these, and replace them with the proper offsets of the other objects/your kernel.
And lastly, the header will also contain a list of all the globally available symbols in the object. If you need any of these, you should record them somewhere, and perhaps 'fix'/patch your kernel with these addresses, if it needs them.
Hope that makes sense. Like I said, my kernel does this with RDOFF2 modules... it might help to see it in code:
http://www.neuraldk.org (products -> ndk). The code's not beautiful, by any means... it's not done. But it works.
Cheers,
Jeff