diff --git a/Makefile b/Makefile
index a5fb9a4..631447b 100644
--- a/Makefile
+++ b/Makefile
@@ -1,3 +1,5 @@
+USER_PROGRAMS := pedicel.raw apex.raw
+USER_FILES := wow.txt
BUILDDIR := build
ELFFILE := pepperk
@@ -21,10 +23,6 @@ CC_FLAGS=-Wall -Wextra -std=gnu99 -nostdlib -ffreestanding -fstack-protector -fn
LD := x86_64-elf-ld
$(ELFFILE): $(BUILDDIR) $(OBJFILES)
- nasm -f bin user/hello.S -o $(BUILDDIR)/hello
- nasm -f bin user/pedicel.S -o $(BUILDDIR)/pedicel
- tar cvf $(BUILDDIR)/initfs.tar -C $(BUILDDIR) hello pedicel
-
nasm -f elf64 src/arch/x86/idt.S -o $(BUILDDIR)/idt_stub.o
$(LD) -o $(ELFFILE) -T linker.ld $(OBJFILES) $(BUILDDIR)/idt_stub.o
# Get the symbols for debugging
@@ -45,7 +43,10 @@ limine/limine:
git clone https://github.com/limine-bootloader/limine.git --branch=v9.x-binary --depth=1
$(MAKE) -C limine
-initfs:
+.PHONY: user
+user:
+ $(MAKE) -C user
+ tar cvf $(BUILDDIR)/initfs.tar -C $(BUILDDIR) $(USER_PROGRAMS) -C ../user $(USER_FILES)
build-iso: limine/limine $(ELFFILE)
rm -rf iso_root
diff --git a/README.md b/README.md
index 33254bb..7d75366 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
#
pepperOS: "will never be done"
-
+
## Description
PepperOS is a 64-bit freely-licensed monolithic kernel for x86 processors, with round-robin preemptive scheduling and 4-level paging. See the [manual](docs/MANUAL.md) for more.
@@ -23,8 +23,9 @@ CC := gcc
LD := ld
```
-Then, to compile the kernel and make an ISO image file, run: `make build-iso`
-To run it with QEMU, do: `make run`
+Then, to compile the kernel and make an ISO image file, run: `make`.
+To build the user programs and the initial filesystem, do `make user`.
+To run it with QEMU, do: `make run`.
## Trying the kernel on real hardware
@@ -48,31 +49,9 @@ These features can be activated by setting them to "true" at the end of the make
make UBSAN=true
```
-## TODO
+## Writing software for PepperOS
-The basics that I'm targeting are:
-
-### Basic utility of what we call a "kernel"
-
-- Implement tasks, and task switching + context switching and spinlock acquire/release
-- Load an executable
-- Filesystem (TAR for read-only initfs, then maybe read-write using FAT12/16/32 or easier fs) w/ VFS layer
-- Getting to userspace (ring 3 switching, syscall interface)
-- Porting musl libc or equivalent
-
-### Scalability/maintenance/expansion features
-
-- Documentation
-- SOME error handling in functions
-- Unit tests
-- Good error codes (like Linux kernel: ENOMEM, ENOENT, ...)
-
-### Optional features
-
-In the future, maybe?
-- SMP support (Limine provides functionality to make this easier)
-- Parsing the ACPI tables and using them for something
-- Replacing the PIT timer with APIC
+If you want to write software for PepperOS, take a look at the [Software Developer's guide](docs/SOFTWARE.md).
## Thanks
diff --git a/docs/MANUAL.md b/docs/MANUAL.md
index c55e67a..b8dcda2 100644
--- a/docs/MANUAL.md
+++ b/docs/MANUAL.md
@@ -41,8 +41,12 @@ The recommended hardware to run PepperOS is the following:
## III. Syscall table
The syscall interface in the Pepper kernel uses the System V ABI convention for argument order.
+It vaguely mimics Unix-like systems.
Name | Number (%rax) | arg0 (%rdi) | arg1 (%rsi) | arg2 (%rdx) |
|---|---|---|---|---|
-| sys_write | 1 | unsigned int fd | const char* buf | size_t count | |
-| sys_exit | 60 | int error_code | | | |
\ No newline at end of file
+| sys_read | 0 | unsigned int fd | char* buf | size_t count |
+| sys_write | 1 | unsigned int fd | const char* buf | size_t count |
+| sys_open | 2 | const char* filename | int flags | |
+| sys_close | 3 | unsigned int fd | | |
+| sys_exit | 60 | int error_code | | |
\ No newline at end of file
diff --git a/docs/SOFTWARE.md b/docs/SOFTWARE.md
new file mode 100644
index 0000000..c7e57a0
--- /dev/null
+++ b/docs/SOFTWARE.md
@@ -0,0 +1,57 @@
+# Writing software for PepperOS
+
+## Why would you want to do that?
+
+Honestly I have no idea. Maybe you have too much free time.
+Keep in mind that the Pepper kernel is a personal project and it's full of bugs, inconsistencies, weird ways of doing things (and I don't care because it's my toy).
+Now if you still want to write something for this OS, thank you. Follow along.
+
+## 1. Write the source code
+
+PepperOS is able to run programs written in x86 assembly, and C programs.
+
+### x86 Assembly
+
+Start your assembly file with the `bits 64` instruction, to emit 64-bit code.
+You can add sections `.text`, `.data`, `.bss` as you need.
+The three things to take in consideration here are:
+- PepperOS does not use the `syscall` instruction, instead it uses the old-fashioned `int 0x80` to trigger a system call.
+- The entry point should be labelled as `_start`.
+- At the end of the file, there should be an exit system call followed by a loop, like so:
+
+```nasm
+.end:
+ mov rax, 0x3C
+ mov rdi, 0x0
+ int 0x80
+
+.loop:
+ jmp .loop
+```
+
+For an example, look at the file [pedicel.S](../user/pedicel.S).
+
+### C program
+
+You will find relevant headers in the `libc` directory. They contain system call wrappers, utility functions, and more. See what's implemented there and what's not.
+To invoke a system call you can use the functions defined in `libc/syscall.h`.
+
+## 2. Add the Makefile rule and variable
+
+Now that your code is complete, add a Makefile rule to `user/Makefile` with your program name. You can just copy-paste the rule that applies to you (either from an Assembly source or C source) and change the name of the files (.raw, .elf, etc...) in the rule.
+For clarity, raw binaries have the `.raw` extension, and ELF ones have `.elf`.
+You also now have to add the name of the executable to the `USER_PROGRAMS` variable at the top of the global Makefile.
+Finally, do `make user` to compile your program.
+
+## 3. Run your program
+
+You can now boot up PepperOS, in a VM or on real hardware, and use the kernel's shell to `list` files in the filesystem (to see if your executable was properly added), and then, run it with the `load` command. Congratulations, you made a program for a random hobby OS!
+
+## 4. (Optional) debugging
+
+Use GDB with the `make debug` rule!
+For your information, user programs are loaded at `0x400000`. Can be good to know to set breakpoints.
+
+## 5. (Optional) contribute!
+
+If you like what you've done and you think it could be nice to add it to PepperOS, send it to me by e-mail: `xamidev (at) riseup (dot) net`. It may or may not be added in a future release... who knows?
\ No newline at end of file
diff --git a/docs/STYLE.md b/docs/STYLE.md
index 6321955..f43e30b 100644
--- a/docs/STYLE.md
+++ b/docs/STYLE.md
@@ -2,6 +2,14 @@
This document describes the coding style for the Pepper kernel. It is used as a guideline across all source files.
+## Setting up a language server (optional)
+
+Before you do anything you might want to setup a language server with your editor. This will save you lots of time correcting errors and stuff. I use `clangd`, and generate my `compile_commands.json` like so:
+
+```
+bear -- make
+```
+
## Indentation
Indentations should be 4 characters long.
diff --git a/include/config.h b/include/config.h
index 4b187da..11c6fe9 100644
--- a/include/config.h
+++ b/include/config.h
@@ -63,4 +63,8 @@
/* ssp */
#define STACK_CHK_GUARD 0x7ABA5C007ABA5C00
+/* fs */
+#define FDT_MAX 8 // Maximum amount of file descriptors per process
+
+
#endif
diff --git a/include/fs/initfs.h b/include/fs/initfs.h
index c47d517..f7a3ca8 100644
--- a/include/fs/initfs.h
+++ b/include/fs/initfs.h
@@ -11,5 +11,8 @@
int initfs_init(struct limine_file* tar_file);
int tar_lookup(unsigned char* archive, char* filename, char** out);
+int tar_exists(const char* filename);
+int tar_read(char* filename, char* out, int count, int offset);
+void tar_list();
#endif
\ No newline at end of file
diff --git a/include/kernel.h b/include/kernel.h
index 01cfb42..3d8d0b7 100644
--- a/include/kernel.h
+++ b/include/kernel.h
@@ -8,10 +8,14 @@
#define KERNEL_H
#include "limine.h"
+
enum ErrorCodes {
- ENOMEM,
- EIO,
- ENOENT
+ ENOMEM, // No memory
+ EIO, // Input/output error
+ ENOENT, // No entry
+ EBADFD, // Bad file descriptor
+ EMFILE, // Too many open files
+ EINVAL // Invalid argument
};
#define CLEAR_INTERRUPTS __asm__ volatile("cli")
diff --git a/include/sched/process.h b/include/sched/process.h
index af36cb6..bb10c58 100644
--- a/include/sched/process.h
+++ b/include/sched/process.h
@@ -11,6 +11,7 @@
#include
#include
#include
+#include
typedef enum {
READY,
@@ -18,6 +19,13 @@ typedef enum {
DEAD
} status_t;
+struct fd {
+ int fd;
+ char filename[PROCESS_NAME_MAX]; // File opened
+ uint64_t cursor; // Cursor position in file
+ bool open;
+};
+
struct process {
size_t pid;
char name[PROCESS_NAME_MAX];
@@ -26,6 +34,10 @@ struct process {
struct cpu_status* context;
void* root_page_table; // Process PML4 (should contain kernel PML4 in higher half [256-511]
void* kernel_stack; // Used for interrupts (syscall: int 0x80), defines the TSS RSP0
+
+ struct fd fdt[FDT_MAX]; // File Descriptor Table
+ size_t next_free_fd;
+
struct process* next;
};
@@ -38,7 +50,7 @@ void process_exit(void);
void process_display_list(struct process* processes_list);
-void process_create_user(struct limine_file* file, char* name);
+
void process_create_user_raw(char* file, int size, char* name);
#endif
diff --git a/src/arch/x86/syscall.c b/src/arch/x86/syscall.c
index 839969f..15f9128 100644
--- a/src/arch/x86/syscall.c
+++ b/src/arch/x86/syscall.c
@@ -4,36 +4,105 @@
* @license GPL-3.0-only
*/
+#include "config.h"
#include "sched/scheduler.h"
#include
#include
#include
#include
#include
+#include
+#include
+#include
extern struct process* current_process;
-void sys_write(unsigned int fd, const char* buf, size_t count)
+// Return fd on success, -errno on error
+int sys_open(const char* filename, int flags)
+{
+ if (tar_exists(filename) < 0) {
+ return -ENOENT; // file doesn't exist..
+ }
+ // file exists here!
+ if (current_process->next_free_fd >= FDT_MAX) {
+ return -EMFILE;
+ }
+ int fd = current_process->next_free_fd++;
+ current_process->fdt[fd].fd = fd;
+ current_process->fdt[fd].open = true;
+ current_process->fdt[fd].cursor = 0;
+ strncpy(current_process->fdt[fd].filename, filename, PROCESS_NAME_MAX - 1);
+ return fd;
+}
+
+// Return 0 on success, -EBADFD if invalid FD
+int sys_close(int fd)
+{
+ if (fd < 0 || fd >= FDT_MAX) {
+ return -EBADFD;
+ }
+
+ if (!current_process->fdt[fd].open) {
+ return -EBADFD; // FD not opened in the first place
+ }
+
+ current_process->fdt[fd].open = false;
+ current_process->fdt[fd].filename[0] = '\0';
+ current_process->fdt[fd].cursor = 0;
+ return 0;
+}
+
+// Should return the number of bytes read
+int sys_read(unsigned int fd, char* buf, size_t count)
+{
+ size_t i;
+ switch (fd) {
+ case 0: //read from stdin (keyboard)
+ for (i=0; ifdt[fd].open == false) {
+ return -EBADFD; // File descriptor wasn't open
+ }
+ // Here fd refers to a valid opened file..
+ int sz = tar_read(current_process->fdt[fd].filename, buf, count,
+ current_process->fdt[fd].cursor);
+ if (sz == 0) {
+ return -ENOENT;
+ } else {
+ current_process->fdt[fd].cursor += sz;
+ return sz;
+ }
+ }
+
+ return -EBADFD;
+}
+
+// TODO: Should have a return value: number of bytes written on success, -1 on error (errno set)
+int sys_write(unsigned int fd, const char* buf, size_t count)
{
switch (fd) {
case 1: //stdout
- for (size_t i=0; istatus = DEAD;
- DEBUG("exiting process PID=%u name=%s", current_process->pid, current_process->name);
+ DEBUG("(pid=%u, name=%s)", current_process->pid, current_process->name);
+ return error_code;
}
/*
@@ -56,22 +125,36 @@ void sys_exit(int error_code)
*/
struct cpu_status* syscall_handler(struct cpu_status* regs)
{
- DEBUG("Syscall %lx with (arg0=%lx arg1=%lx)", regs->rax, regs->rdi, regs->rsi);
-
switch (regs->rax)
{
- case 0: //sys_read
+ case 0:
+ DEBUG("sys_read(fd=%u, buf=%p, count=%u)", regs->rdi, regs->rsi, regs->rdx);
+ regs->rax = sys_read(regs->rdi, (char*)regs->rsi, regs->rdx);
break;
- case 1: //sys_write
- sys_write(regs->rdi, (char*)regs->rsi, regs->rdx);
+ case 1:
+ DEBUG("sys_write(fd=%u, buf=%p, count=%u)", regs->rdi, regs->rsi, regs->rdx);
+ regs->rax = sys_write(regs->rdi, (char*)regs->rsi, regs->rdx);
break;
- case 60: //sys_exit
- sys_exit(regs->rdi);
+ case 2:
+ DEBUG("sys_open(filename=%s, flags=%u)", regs->rdi, regs->rsi);
+ regs->rax = sys_open((const char*)regs->rdi, regs->rsi);
+ break;
+ case 3:
+ DEBUG("sys_close(fd=%u)", regs->rdi);
+ regs->rax = sys_close(regs->rdi);
+ break;
+ case 60:
+ DEBUG("sys_exit(error_code=%d)", regs->rdi);
+ regs->rax = sys_exit(regs->rdi);
break;
default:
+ DEBUG("Bad syscall! (rax=%p, rdi=%p, rsi=%p, rdx=%p)",
+ regs->rax, regs->rdi, regs->rsi, regs->rdx);
regs->rax = 0xbad515ca11;
break;
}
+ DEBUG("returned rax=%p (%u)", regs->rax, regs->rax);
+
return regs;
}
\ No newline at end of file
diff --git a/src/fs/initfs.c b/src/fs/initfs.c
index ddeff2b..50c34dd 100644
--- a/src/fs/initfs.c
+++ b/src/fs/initfs.c
@@ -42,7 +42,7 @@ int tar_oct2bin(unsigned char* str, int size)
*
* Return:
* $filesize - size of the file, if found
- * $0 - file not found
+ * $-ENOENT - file not found
*/
int tar_lookup(unsigned char* archive, char* filename, char** out)
{
@@ -56,9 +56,80 @@ int tar_lookup(unsigned char* archive, char* filename, char** out)
}
ptr += (((filesize + 511) / 512) + 1) * 512;
}
- return 0;
+ return -ENOENT;
}
+/*
+ * tar_list - list all files present in archive
+ */
+void tar_list()
+{
+ printf("++ Contents of initial filesystem ++\r\n\r\n");
+ unsigned char *ptr = archive_start_addr;
+
+ while (!memcmp(ptr + 257, "ustar", 5)) {
+ int filesize = tar_oct2bin(ptr + 0x7c, 11);
+ char* filename = (char*)ptr;
+ printf("file: %s\r\n", filename);
+ ptr += (((filesize + 511) / 512) + 1) * 512;
+ }
+}
+
+/*
+ * tar_read - read a file in the TAR archive
+ * @filename: file to read (absolute path)
+ * @out: where to store file data if found
+ * @count: amount of bytes to read
+ * @offset: read from byte offset (0 for none)
+ *
+ * Return:
+ * $filesize - size of the file, if found
+ * $-ENOENT - file not found
+ */
+int tar_read(char* filename, char* out, int count, int offset)
+{
+ char* file_data;
+ int filesize = tar_lookup(archive_start_addr, filename, &file_data);
+
+ if (filesize <= 0) {
+ return filesize;
+ }
+
+ if (offset >= filesize) {
+ return -EINVAL;
+ }
+
+ int remaining = filesize - offset;
+ int to_read = remaining < count ? remaining : count;
+
+ memcpy(out, file_data + offset, to_read);
+
+ return to_read;
+}
+
+/*
+ * tar_exists - check if a file exists in the TAR archive
+ * @filename: file to check (absolute path)
+ *
+ * Return:
+ * $filesize - size of the file, if found
+ * $-ENOENT - file not found
+ */
+int tar_exists(const char* filename)
+{
+ unsigned char* ptr = archive_start_addr;
+
+ while (!memcmp(ptr + 257, "ustar", 5)) {
+ int filesize = tar_oct2bin(ptr + 0x7c, 11);
+ if (!memcmp(ptr, filename, strlen(filename) + 1)) {
+ return filesize;
+ }
+ ptr += (((filesize + 511) / 512) + 1) * 512;
+ }
+ return -ENOENT;
+}
+
+
/*
* initfs_init - initialize the TAR initial filesystem
* @tar_file: pointer to the Limine-loaded archive
diff --git a/src/kapps/kshell.c b/src/kapps/kshell.c
index 46cd6dc..fa44b51 100644
--- a/src/kapps/kshell.c
+++ b/src/kapps/kshell.c
@@ -4,6 +4,7 @@
* @license GPL-3.0-only
*/
+#include "fs/initfs.h"
#include
#include
#include
@@ -42,17 +43,16 @@ void pedicel_main(void* arg)
keyboard_getline(input_buf, PEDICEL_INPUT_SIZE);
if (strncmp(input_buf, "help", 4) == 0) {
- printf("\r\nYou are currently running the test kernel shell. This is not\r\n"
- "a fully-fledged shell like you'd find in a complete operating system,\r\n"
- "but rather a toy to play around in the meantime.\r\n\r\n"
- "clear - clear the screen\r\n"
- "panic - trigger a test panic\r\n"
- "syscall - trigger int 0x80\r\n"
- "pf - trigger a page fault\r\n"
- "now - get current date\r\n"
- "smash - smash the stack\r\n"
- "mem - get used heap info\r\n"
- "load - load an user executable\r\n");
+ printf("++ shell builtins ++\r\n\r\n"
+ "\tclear - clear the screen\r\n"
+ "\tpanic - trigger a test panic\r\n"
+ "\tsyscall - trigger int 0x80\r\n"
+ "\tpf - trigger a page fault\r\n"
+ "\tnow - get current date\r\n"
+ "\tsmash - smash the stack\r\n"
+ "\tmem - get used heap info\r\n"
+ "\tload - load an user executable\r\n"
+ "\tlist - list initfs.tar contents\r\n");
continue;
}
@@ -102,6 +102,11 @@ void pedicel_main(void* arg)
continue;
}
+ if (strncmp(input_buf, "list", 4) == 0) {
+ tar_list();
+ continue;
+ }
+
printf("%s: command not found\r\n", input_buf);
}
}
\ No newline at end of file
diff --git a/src/kmain.c b/src/kmain.c
index c0ec88b..33ee671 100644
--- a/src/kmain.c
+++ b/src/kmain.c
@@ -78,14 +78,6 @@ void idle_main(void* arg)
}
}
-void thing_main(void* arg)
-{
- printf("What's your name, pal? ");
- char name[10];
- keyboard_getline(name, 10);
- printf("\r\n{%s} is such a nice name!\r\n", name);
-}
-
extern uintptr_t kheap_start;
/*
diff --git a/src/sched/process.c b/src/sched/process.c
index e7d72d6..633555e 100644
--- a/src/sched/process.c
+++ b/src/sched/process.c
@@ -225,59 +225,15 @@ void process_jump_to_user(uintptr_t stack_top, uintptr_t user_code)
extern struct tss tss;
/*
- * process_create_user - Create a new user process
- * @file: pointer to Limine file structure
+ * process_create_user_raw - Create a new user process from raw binary
+ * @file: pointer to beginning of binary
+ * @size: size of the binary
* @name: name for the new process
*
- * This function takes a loaded Limine executable
- * module, and maps its code, a user stack, sets the
- * TSS RSP0 for interrupts, and finally jumps to the
- * user code.
+ * This function takes an executable loaded in memory
+ * and maps its code, a user stack, sets the TSS RSP0
+ * for interrupts, and finally jumps to the user code.
*/
-void process_create_user(struct limine_file* file, char* name)
-{
- CLEAR_INTERRUPTS;
- struct process* proc = (struct process*)kmalloc(sizeof(struct process));
- struct cpu_status* ctx = (struct cpu_status*)kmalloc(sizeof(struct cpu_status));
-
- if (!proc || !ctx) panic(NULL, "out of memory while creating user process");
-
- strncpy(proc->name, name, PROCESS_NAME_MAX);
- memset(ctx, 0, sizeof(struct cpu_status)); // set GP registers to zero
- proc->pid = next_free_pid++;
- proc->status = READY;
- proc->next = 0;
- proc->context = ctx;
- proc->context->iret_ss = USER_DATA_SEGMENT | 3;
- proc->context->iret_cs = USER_CODE_SEGMENT | 3;
- proc->context->iret_flags = 0x202; // Interrupt Flag set
-
- void* exec_addr = file->address;
- uint64_t exec_size = file->size;
-
- uint64_t* user_pml4 = vmm_create_address_space();
- if (!user_pml4) panic(NULL, "failed to create user address space");
- proc->root_page_table = user_pml4;
-
- uintptr_t stack_top = vmm_alloc_user_stack(user_pml4);
- uint64_t code = vmm_alloc_user_code(user_pml4, exec_addr, exec_size);
-
- proc->context->iret_rsp = stack_top;
- proc->context->iret_rip = code;
- proc->kernel_stack = kalloc_stack();
- if (!proc->kernel_stack) panic(NULL, "failed to allocate kernel stack");
-
- // Copy code into user pages; for that we need to temporarily switch to the user pml4
- load_cr3(VIRT_TO_PHYS((uint64_t)user_pml4));
- memcpy((uint64_t*)code, exec_addr, exec_size);
- load_cr3(VIRT_TO_PHYS((uint64_t)kernel_pml4));
-
- process_add(&processes_list, proc);
- DEBUG("user process '%s' (pid=%u) enqueued for scheduling", name, proc->pid);
- SET_INTERRUPTS;
-}
-
-// Same as above but for a raw data pointer (pointing to raw binary, no ELF)
void process_create_user_raw(char* file, int size, char* name)
{
CLEAR_INTERRUPTS;
@@ -286,8 +242,10 @@ void process_create_user_raw(char* file, int size, char* name)
if (!proc || !ctx) panic(NULL, "out of memory while creating user process");
+ memset(proc, 0, sizeof(struct process));
+ memset(ctx, 0, sizeof(struct cpu_status));
+
strncpy(proc->name, name, PROCESS_NAME_MAX);
- memset(ctx, 0, sizeof(struct cpu_status)); // set GP registers to zero
proc->pid = next_free_pid++;
proc->status = READY;
proc->next = 0;
@@ -296,6 +254,24 @@ void process_create_user_raw(char* file, int size, char* name)
proc->context->iret_cs = USER_CODE_SEGMENT | 3;
proc->context->iret_flags = 0x202; // Interrupt Flag set
+ /* Set basic entries for the process's File Descriptor Table */
+ proc->fdt[0].fd = 0;
+ proc->fdt[0].open = true;
+ proc->fdt[0].cursor = 0;
+ strncpy(proc->fdt[0].filename, "stdin", PROCESS_NAME_MAX - 1);
+
+ proc->fdt[1].fd = 1;
+ proc->fdt[1].open = true;
+ proc->fdt[1].cursor = 0;
+ strncpy(proc->fdt[1].filename, "stdout", PROCESS_NAME_MAX - 1);
+
+ proc->fdt[2].fd = 2;
+ proc->fdt[2].open = true;
+ proc->fdt[2].cursor = 0;
+ strncpy(proc->fdt[2].filename, "stderr", PROCESS_NAME_MAX - 1);
+
+ proc->next_free_fd = 3; // file descriptors are also bump-allocated
+
void* exec_addr = (void*)file;
uint64_t exec_size = size;
diff --git a/user/Makefile b/user/Makefile
new file mode 100644
index 0000000..5844db3
--- /dev/null
+++ b/user/Makefile
@@ -0,0 +1,18 @@
+CC := x86_64-elf-gcc
+CC_FLAGS := -ffreestanding -nostdlib -fno-pic -mno-red-zone -Ilibc
+
+LD := x86_64-elf-ld
+
+BUILDDIR := ../build
+LIBDIR := libc
+
+all: pedicel apex
+
+pedicel:
+ nasm -f bin pedicel.S -o $(BUILDDIR)/pedicel.raw
+
+apex:
+ $(CC) $(CC_FLAGS) -c apex.c -o $(BUILDDIR)/apex.o
+ nasm -f elf64 $(LIBDIR)/crt0.S -o $(BUILDDIR)/crt0.o
+ $(LD) -T $(LIBDIR)/linker.ld $(BUILDDIR)/crt0.o $(BUILDDIR)/apex.o -o $(BUILDDIR)/apex.elf
+ objcopy -O binary $(BUILDDIR)/apex.elf $(BUILDDIR)/apex.raw
\ No newline at end of file
diff --git a/user/apex.c b/user/apex.c
new file mode 100644
index 0000000..158b571
--- /dev/null
+++ b/user/apex.c
@@ -0,0 +1,7 @@
+#include
+
+int main() {
+ const char* msg = "hi from C userland\r\n";
+ write(1, msg, 21);
+ return 42;
+}
\ No newline at end of file
diff --git a/user/hello.S b/user/hello.S
deleted file mode 100644
index 3f063c5..0000000
--- a/user/hello.S
+++ /dev/null
@@ -1,21 +0,0 @@
-bits 64
-
-section .data
- hi db "hi from userland :) we did it man", 0x0A, 0x0d, 0
-
-section .text
-
-hello:
- mov rax, 0x1 ;sys_write
- mov rdi, 0x1 ;stdout
- lea rsi, [rel hi] ;char* buf
- mov rdx, 35 ;count
- int 0x80
-
-.end:
- mov rax, 0x3C ;sys_exit
- mov rdi, 0x0 ;error_code
- int 0x80
-
-.loop:
- jmp .loop
\ No newline at end of file
diff --git a/user/libc/crt0.S b/user/libc/crt0.S
new file mode 100644
index 0000000..1679f5a
--- /dev/null
+++ b/user/libc/crt0.S
@@ -0,0 +1,18 @@
+bits 64
+global _start
+extern main
+
+section .text
+
+; Begin the program with main() function
+_start:
+ call main
+
+; Exit the program by exit() syscall
+.exit:
+ mov rdi, rax ; put the value of "return X;" (rax) as arg1 (error_code)
+ mov rax, 60 ; sys_exit
+ int 0x80
+
+.loop:
+ jmp .loop
\ No newline at end of file
diff --git a/user/libc/linker.ld b/user/libc/linker.ld
new file mode 100644
index 0000000..0aa08d6
--- /dev/null
+++ b/user/libc/linker.ld
@@ -0,0 +1,22 @@
+ENTRY(_start)
+
+SECTIONS
+{
+ . = 0x400000;
+
+ .text : {
+ *(.text*)
+ }
+
+ .rodata : {
+ *(.rodata*)
+ }
+
+ .data : {
+ *(.data*)
+ }
+
+ .bss : {
+ *(.bss*)
+ }
+}
\ No newline at end of file
diff --git a/user/libc/syscall.h b/user/libc/syscall.h
new file mode 100644
index 0000000..21861ae
--- /dev/null
+++ b/user/libc/syscall.h
@@ -0,0 +1,30 @@
+#pragma once
+
+// 3 because 3 arguments to the call, get it??
+static inline long syscall3(long n, long a, long b, long c) {
+ long ret;
+
+ __asm__ volatile (
+ "int $0x80"
+ : "=a"(ret)
+ : "a"(n), "D"(a), "S"(b), "d"(c)
+ : "memory"
+ );
+
+ return ret;
+}
+
+static inline void write(int fd, const char* buf, long len) {
+ syscall3(1, fd, (long)buf, len);
+}
+
+static inline void exit(int code) {
+ __asm__ volatile (
+ "int $0x80"
+ :
+ : "a"(60), "D"(code)
+ : "memory"
+ );
+
+ for (;;);
+}
\ No newline at end of file
diff --git a/user/pedicel.S b/user/pedicel.S
index e1b92ef..28bab89 100644
--- a/user/pedicel.S
+++ b/user/pedicel.S
@@ -1,7 +1,8 @@
bits 64
section .data
-hello db 0x0A, 0x0D, "User program 2 speaking", 0x0A, 0x0D, 0
+hello db 0x0A, 0x0D, "TEST PROGRAM...", 0x0A, 0x0D, 0
+filename db "wow.txt", 0
section .text
@@ -9,7 +10,29 @@ _start:
mov rax, 0x1 ;sys_write
mov rdi, 0x1 ;stdout
lea rsi, [rel hello]
- mov rdx, 27 ;count
+ mov rdx, 19 ;count
+ int 0x80
+
+ ; Open a file
+ mov rax, 0x2 ;sys_open
+ lea rdi, [rel filename] ;filename
+ mov rsi, 0x0 ;flags
+ int 0x80
+
+ mov rdi, rax ;fd
+ mov rax, 0x0 ;sys_read
+ lea rsi, [rel buf] ;buf
+ mov rdx, 33 ;count
+ int 0x80
+
+ mov rax, 0x1 ;sys_write
+ mov rdi, 0x1 ;stdout
+ lea rsi, [rel buf] ;buf
+ mov rdx, 33 ;count
+ int 0x80
+
+ mov rax, 0x3 ;sys_close
+ mov rdi, 0x3 ;fd
int 0x80
; when we are ready to have an os specific toolchain,
@@ -22,4 +45,7 @@ _start:
int 0x80
.loop:
- jmp .loop
\ No newline at end of file
+ jmp .loop
+
+section .bss
+buf resb 10
\ No newline at end of file
diff --git a/user/wow.txt b/user/wow.txt
new file mode 100644
index 0000000..ed7191d
--- /dev/null
+++ b/user/wow.txt
@@ -0,0 +1 @@
+hi from a file opened in usermode
\ No newline at end of file