Before we start, we’re going to apply a few restrictions to our program loader. These are things you can easily add later, but they only serve to complicate the process.
For a program to be compatible with our loader:
In the previous chapter we looked at the details of loading program headers, but we glossed over a lot of the high level details of loading a program. Assuming we want to start running a new program (we’re ignoring fork() and exec() for the moment), we’ll need to do a few things. Most of this was covered in previous chapters, and now it’s a matter of putting it all together.
PT_LOAD, we’ll need those in a moment.memsz and filesz.e_entry field in the ELF header. This field is the start function of the program. You’ll also need to create a stack in the memory space of this program for the thread to use, if this wasnt already done as part of our thread creation.If all of the above are done, then the program is ready to run! We now should be able to enqueue the main thread in the scheduler and let it run.
When veryfying an ELF file there are few things we need to check in order to decide if an executable is valid, the fields to validate are at different points in the ELF header. Some can be found in the e_ident field, like the following:
ELFMAG part. It is expected to be the following values:| Value | Byte |
|---|---|
0x7f |
0 |
E |
1 |
L |
2 |
F |
3 |
x86_64 architecture endiannes is LSB, then the value is expected to be 1. This field is in the byte 5.Then from the other fields that need validation (that area not in the e_ident field) are:
e_type: this identifies the type of elf, for our purpose the one to be considered valid this value should be 2 that indicates an Executable File (ET_EXEC) there are other values that in the future we could support, for example the ET_DYN type that is used for position independent code or shared object, but they require more work to be done.e_machine: this indicates the required architecture for the executable, the value depends on the architectures we are supporting. For example the value for the AMD64 architecture is 62Be aware that most of the variables and their values have a specific naming convention, for more information refer to the ELF specs.
Beware that some compilers when generating a simple executable are not using the ET_EXEC value, but it could be of the type ET_REL (value 1), to obtain an executable we need to link it using a linker. For example if we generated the executable: example.elf with ET_REL type, we can use ld (or another equivalent linker):
ld -o example.o example.elf
For basic executables, we most likely don’t need to include any linker script.
If we want to know the type of an elf, we can use the readelf command, if we are on a unix-like os:
readelf -e example.elf
Will print out all the executable information, including the type.
As we can already see from the above restrictions there is plenty of room for improvement. There are also some other things to keep in mind:
x86_64 these are called rings (ring 0 = kernel, ring 3 = user), other platforms may use different names. See the userspace chapter for more detail.