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 62
Be 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.