Skip to content

Commit

Permalink
Move file loading functions into '/loader'
Browse files Browse the repository at this point in the history
  • Loading branch information
taikiy committed Jan 3, 2024
1 parent 1b7453b commit 0a3b438
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 62 deletions.
2 changes: 1 addition & 1 deletion doc/14_accessing_keyboard_in_protected_mode.md.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,4 @@ We can access the keyboard buffer from the user program by making a syscall. The

---

[Previous](./13_calling_kernel_space_routines_from_user_space.md) | [Next]() | [Home](../README.md)
[Previous](./13_calling_kernel_space_routines_from_user_space.md) | [Next](./15_elf_files.md) | [Home](../README.md)
24 changes: 24 additions & 0 deletions doc/15_elf_files.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# What is an ELF file?

https://wiki.osdev.org/ELF

ELF is a format for storing programs or fragments of programs on disk, created as a result of compiling and linking. An ELF file is divided into sections. For an executable program, these are the _text_ section for the code, the _data_ section for global variables and the _rodata_ section which usually contains constant strings. The ELF file contains headers that describe how these sections should be stored in memory. Because of this, multiple ELF files can be loaded and dynamically linked together at runtime into a single program in memory.

The kernel's ELF file loader is responsible for finding the symbols that are required by the program, resolving any conflicts, and loading them into memory. Linking is performed in memory at runtime.

## Loading ELF files

The ELF header contains all of the relevant information required to load an ELF executable. The format of this header is described in the [ELF Specification](http://www.skyfree.org/linux/references/ELF_Format.pdf). The most relevant sections for this purpose are 1.1 to 1.4 and 2.1 to 2.7. Instructions on loading an executable are contained within section 2.7.

The following is a rough outline of the steps that an ELF executable loader must perform:

1. Verify that the file starts with the ELF magic number (4 bytes) as described in Figure 1-4 (and subsequent table) on page 11 in the ELF specification.
2. Read the ELF Header. The ELF header is always located at the very beginning of an ELF file. The ELF header contains information about how the rest of the file is laid out. An executable loader is only concerned with the program headers.
3. Read the ELF executable's program headers. These specify where in the file the program segments are located, and where they need to be loaded into memory.
4. Parse the program headers to determine the number of program segments that must be loaded. Each program header has an associated type, as described in Figure 2-2 of the ELF specification. Only headers with a type of PT_LOAD describe a loadable segment.
5. Load each of the loadable segments. This is performed as follows:
1. Allocate virtual memory for each segment, at the address specified by the p_vaddr member in the program header. The size of the segment in memory is specified by the p_memsz member.
2. Copy the segment data from the file offset specified by the p_offset member to the virtual memory address specified by the p_vaddr member. The size of the segment in the file is contained in the p_filesz member. This can be zero.
3. The p_memsz member specifies the size the segment occupies in memory. This can be zero. If the p_filesz and p_memsz members differ, this indicates that the segment is padded with zeros. All bytes in memory between the ending offset of the file size, and the segment's virtual memory size are to be cleared with zeros.
6. Read the executable's entry point from the ELF header.
7. Jump to the executable's entry point in the newly loaded memory.
2 changes: 1 addition & 1 deletion doc/3_32-bit_kernel.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## 1. Cross-Compiler

Installing the i686-elf toolchain on M1 Mac is very easy.
Since we are using Apple Silicon Mac, we need to use a cross-compiler to compile files into x86 binaries. Installing the GNU _binutils_ toolchain for x86 architecture on M1 Mac is very easy.

```shell
> brew install i686-elf-binutils i686-elf-gcc i386-elf-gdb
Expand Down
44 changes: 44 additions & 0 deletions src/loader/format/binary.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#include "binary.h"
#include "fs/file.h"
#include "memory/heap/kheap.h"
#include <stdint.h>

status_t
load_binary_executable_file(int fd, void** out_ptr, size_t* out_size)
{
status_t result = ALL_OK;

if (fd <= 0) {
return ERROR(EINVARG);
}

struct file_stat stat;
result = fstat(fd, &stat);
if (result != ALL_OK) {
return ERROR(EIO);
}

uint32_t file_size = stat.size;
void* program_data_ptr = kzalloc(file_size);
if (!program_data_ptr) {
return ERROR(ENOMEM);
}

// TODO: Read the file in chunks
size_t read_items = fread(program_data_ptr, file_size, 1, fd);
if (read_items != 1) {
result = ERROR(EIO);
goto out;
}

*out_ptr = program_data_ptr;
*out_size = file_size;

out:
if (result != ALL_OK) {
if (program_data_ptr) {
kfree(program_data_ptr);
}
}
return result;
}
9 changes: 9 additions & 0 deletions src/loader/format/binary.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#ifndef FORMAT_BINARY_H
#define FORMAT_BINARY_H

#include "status.h"
#include <stddef.h>

status_t load_binary_executable_file(int fd, void** out_ptr, size_t* out_size);

#endif
32 changes: 32 additions & 0 deletions src/loader/loader.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#include "loader.h"
#include "format/binary.h"
#include "fs/file.h"
#include "memory/heap/kheap.h"
#include <stdint.h>

status_t
load_file(const char* file_path, void** out_ptr, size_t* out_size)
{
status_t result = ALL_OK;

if (!file_path) {
return ERROR(EINVARG);
}

int fd = fopen(file_path, "r");
if (!fd) {
return ERROR(EIO);
}

// TODO: Check if the file is binary, ELF, etc.

result = load_binary_executable_file(fd, out_ptr, out_size);
if (result != ALL_OK) {
goto out;
}

out:
// We can safely call fclose() even if fd is 0.
fclose(fd);
return result;
}
9 changes: 9 additions & 0 deletions src/loader/loader.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#ifndef LOADER_H
#define LOADER_H

#include "status.h"
#include <stddef.h>

status_t load_file(const char* file_path, void** out_ptr, size_t* out_size);

#endif
64 changes: 4 additions & 60 deletions src/task/process.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@

#include "config.h"
#include "fs/file.h"
#include "loader/loader.h"
#include "memory/heap/kheap.h"
#include "memory/memory.h"
#include "memory/paging/paging.h"
#include "string/string.h"
#include "task.h"

struct process* current_process = 0;
static struct process* current_process = 0;
static struct process* processes[MAX_PROCESSES] = {};

static void
Expand All @@ -17,80 +18,23 @@ initialize_process(struct process* process)
memset(process, 0, sizeof(struct process));
}

static status_t
load_binary_data(int fd, struct process* process)
{
status_t result = ALL_OK;

if (fd <= 0) {
return ERROR(EINVARG);
}

if (!process) {
return ERROR(EINVARG);
}

struct file_stat stat;
result = fstat(fd, &stat);
if (result != ALL_OK) {
return ERROR(EIO);
}

uint32_t file_size = stat.size;
void* program_data_ptr = kzalloc(file_size);
if (!program_data_ptr) {
return ERROR(ENOMEM);
}

// TODO: Read the file in chunks
size_t read_items = fread(program_data_ptr, file_size, 1, fd);
if (read_items != 1) {
result = ERROR(EIO);
goto out;
}

process->data = program_data_ptr;
process->size = file_size;

out:
if (result != ALL_OK) {
if (program_data_ptr) {
kfree(program_data_ptr);
}
}
return result;
}

static status_t
load_data_for_process(const char* file_path, struct process* process)
{
status_t result = ALL_OK;

if (!file_path) {
return ERROR(EINVARG);
}

if (!process) {
return ERROR(EINVARG);
}

int fd = fopen(file_path, "r");
if (!fd) {
return ERROR(EIO);
}

// TODO: Check if the file is executable, binary, ELF, etc.

result = load_binary_data(fd, process);
result = load_file(file_path, &process->data, &process->size);
if (result != ALL_OK) {
goto out;
}

strncpy(process->file_path, file_path, sizeof(process->file_path) - 1);

out:
// We can safely call fclose() even if fd is 0.
fclose(fd);
return result;
}

Expand Down Expand Up @@ -231,7 +175,7 @@ load_process_to_slot(const char* file_path, struct process** process, int slot)
// assign the process ID
new_process->id = slot;
processes[slot] = new_process;
*process = new_process;
*process = new_process;

out:
if (result != ALL_OK) {
Expand Down

0 comments on commit 0a3b438

Please sign in to comment.