init commit

This commit is contained in:
Faynot
2026-07-07 20:35:07 +03:00
commit 38cc34a6e9
33 changed files with 659 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
*.o
*.a
obj/
lib/
config.mak

40
Makefile Normal file
View File

@@ -0,0 +1,40 @@
CC ?= gcc
AR ?= ar
RANLIB ?= ranlib
CFLAGS ?= -Os -fno-builtin -ffreestanding -nostdinc
LDFLAGS ?= -nostdlib -static
-include config.mak
ARCH ?= x86_64
CFLAGS += -I. -I include
SRC_DIRS = src/string src/stdio src/stdlib src/internal
OBJ_DIR = obj
LIB_DIR = lib
SRCS := $(shell find $(SRC_DIRS) -name '*.c' 2>/dev/null)
OBJS := $(SRCS:%=$(OBJ_DIR)/%.o)
CRT_OBJ := crt/crt1.o
.PHONY: all clean distclean
all: $(LIB_DIR)/libc.a $(CRT_OBJ)
$(OBJ_DIR)/%.c.o: %.c
@mkdir -p $(dir $@)
$(CC) $(CFLAGS) -c -o $@ $<
$(LIB_DIR)/libc.a: $(OBJS)
@mkdir -p $(LIB_DIR)
$(AR) crs $@ $^
$(RANLIB) $@
crt/crt1.o: crt/crt1.c
$(CC) $(CFLAGS) -c -o $@ $<
clean:
rm -rf $(OBJ_DIR) $(LIB_DIR)
distclean: clean
rm -f config.mak

56
README.md Normal file
View File

@@ -0,0 +1,56 @@
# elibc — Standard C Library for Elyz
Minimal C library for the Elyz OS, based on musl's organisational principles
but adapted to Elyz's capability-based, syscall-less architecture.
## Structure
```
elibc/
├── arch/ # Architecture-specific code
│ ├── generic/ # Fallback implementation & abstract interfaces
│ │ ├── kcall.h # Kernel-call abstraction (no syscalls in Elyz)
│ │ ├── startup_info.h # Process-startup description
│ │ └── bits/ # Type-size definitions
│ └── x86_64/ # x86_64 port
│ ├── kcall.h # Kernel-call placeholder (ABI TBD)
│ ├── startup_info.h # x86_64 startup layout (ABI TBD)
│ ├── atomic_arch.h # Locked cmpxchg / atomic ops
│ └── bits/
├── include/ # Public headers (POSIX-like API)
├── src/ # Source implementations
│ ├── internal/ # Libc state, errno, bump allocator (temporary)
│ ├── string/ # Pure-C string ops (memcpy, strlen, etc.)
│ ├── stdio/ # (reserved)
│ └── stdlib/ # atoi, etc.
├── crt/ # C runtime entry point
├── Makefile
└── configure
```
## Key differences from a traditional libc (glibc/musl)
| Traditional (Linux) | Elyz / elibc |
|---|---|
| `syscall`/`int 0x80` | Capability invocation → PM Actor messaging |
| `brk`/`sbrk` | `ELYZ_OP_ALLOCATE` via `elyz_kcall` |
| `_exit` syscall | `ELYZ_OP_EXIT` via kcall |
| `errno` via TLS | `__errno_location` (plain global for now) |
## Porting to a new arch
1. Create `arch/<name>/` with:
- `kcall.h` — kernel-call mechanism
- `startup_info.h` — process-startup layout
- `arch.mak` — arch-specific compiler/linker flags
- `bits/alltypes.h.in` — type sizes
- `atomic_arch.h` — atomic ops (optional, fallback exists)
2. Add detection in `configure`
3. Build: `./configure && make`
## Building
```
./configure
make
```

3
arch/generic/arch.mak Normal file
View File

@@ -0,0 +1,3 @@
# Generic architecture flags — override per port
ARCH_CFLAGS :=
ARCH_LDFLAGS :=

View File

@@ -0,0 +1,7 @@
/* Generic type sizes — override per arch if different */
TYPEDEF int ptrdiff_t;
TYPEDEF unsigned long size_t;
TYPEDEF long ssize_t;
TYPEDEF unsigned long uintptr_t;
TYPEDEF long intptr_t;
TYPEDEF int wchar_t;

30
arch/generic/kcall.h Normal file
View File

@@ -0,0 +1,30 @@
#ifndef _ELIBC_KCALL_H
#define _ELIBC_KCALL_H
/* Elyz kernel-call abstraction.
*
* Elyz has NO syscall instruction. Communication with the kernel and PM
* actors happens through capability invocation — a mechanism defined by the
* kernel ABI (not finalised yet). This header describes the abstract
* interface; the arch-specific implementation (arch/<arch>/kcall.h) fills in
* the concrete mechanism.
*
* Current state: STUB — returns -ENOSYS until the Elyz kernel ABI is
* stabilised.
*/
#define ELYZ_OP_ALLOCATE 1
#define ELYZ_OP_FREE 2
#define ELYZ_OP_CARVE 3
#define ELYZ_OP_EXIT 4
struct elyz_kcall {
unsigned long opcode;
unsigned long arg1;
unsigned long arg2;
unsigned short channel_id;
};
long __kcall(struct elyz_kcall *msg);
#endif

View File

@@ -0,0 +1,22 @@
#ifndef _ELIBC_STARTUP_INFO_H
#define _ELIBC_STARTUP_INFO_H
/* Process-startup information block.
*
* The Elyz loader passes an opaque structure to every new process.
* Layout and content are arch-defined (see arch/<arch>/startup_info.h).
* At minimum the process receives:
* - an initial memory pool (capability / region)
* - argc / argv from the parent
* - a capability-wallet (CNode) root
*/
struct elyz_startup {
unsigned long initial_cnode_cap;
unsigned long initial_pool_addr;
unsigned long initial_pool_size;
int argc;
char **argv;
};
#endif

3
arch/x86_64/arch.mak Normal file
View File

@@ -0,0 +1,3 @@
# x86_64 architecture flags
ARCH_CFLAGS := -m64
ARCH_LDFLAGS := -m elf_x86_64

26
arch/x86_64/atomic_arch.h Normal file
View File

@@ -0,0 +1,26 @@
#ifndef _ELIBC_ATOMIC_ARCH_H
#define _ELIBC_ATOMIC_ARCH_H
/* x86_64: minimal atomic ops via locked cmpxchg (CPU-local, no syscall) */
static inline int a_cas(volatile int *p, int t, int s)
{
int old;
__asm__ volatile ("lock; cmpxchg %3, %1"
: "=a"(old), "=m"(*p)
: "a"(t), "r"(s), "m"(*p)
: "memory");
return old;
}
static inline void a_inc(volatile int *p)
{
__asm__ volatile ("lock; incl %0" : "=m"(*p) : "m"(*p) : "memory");
}
static inline void a_dec(volatile int *p)
{
__asm__ volatile ("lock; decl %0" : "=m"(*p) : "m"(*p) : "memory");
}
#endif

View File

@@ -0,0 +1,6 @@
TYPEDEF int ptrdiff_t;
TYPEDEF unsigned long size_t;
TYPEDEF long ssize_t;
TYPEDEF unsigned long uintptr_t;
TYPEDEF long intptr_t;
TYPEDEF int wchar_t;

21
arch/x86_64/kcall.h Normal file
View File

@@ -0,0 +1,21 @@
#ifndef _ELIBC_X86_64_KCALL_H
#define _ELIBC_X86_64_KCALL_H
#include <arch/generic/kcall.h>
/* x86_64: placeholder — Elyz kernel ABI not yet defined.
*
* When the ABI exists this will contain either:
* - a direct userspace→actor message-pipe write, or
* - a lightweight kernel entry that routes via the PMRouter.
*
* For now: return -ENOSYS and let the CPU halt.
*/
static inline long __kcall(struct elyz_kcall *msg)
{
(void)msg;
for (;;) __asm__ volatile("hlt");
return -38; /* -ENOSYS */
}
#endif

View File

@@ -0,0 +1,16 @@
#ifndef _ELIBC_X86_64_STARTUP_INFO_H
#define _ELIBC_X86_64_STARTUP_INFO_H
/* x86_64 process-startup ABI.
*
* The loader places an elyz_startup block at a known location (e.g. in a
* register or on a special stack page). The exact ABI depends on the
* kernel-to-userspace handoff which is not yet implemented.
*
* Current state: STUB — the process receives argc/argv on the stack as a
* fallback, and the initial memory pool comes from a fixed test region.
*/
#include <arch/generic/startup_info.h>
#endif

21
configure vendored Executable file
View File

@@ -0,0 +1,21 @@
#!/bin/sh
# Minimal configure for elibc — detects target architecture
echo "Configuring elibc..."
# Detect architecture
case "$(uname -m)" in
x86_64|amd64)
ARCH=x86_64
;;
aarch64|arm64)
ARCH=aarch64
;;
*)
echo "Warning: unknown arch $(uname -m), falling back to x86_64"
ARCH=x86_64
;;
esac
echo "ARCH := $ARCH" > config.mak
echo "Configured for $ARCH"

32
crt/crt1.c Normal file
View File

@@ -0,0 +1,32 @@
/* Minimal C runtime — entry point before main.
*
* Elyz process startup:
* the loader transfers control here with an initial capability wallet
* (CNode) and a pool of untyped memory. No traditional syscalls exist;
* all kernel interaction goes through capability invocation.
*
* Current state: STUB — the kernel ABI is under construction.
* - argc/argv passed on stack (convention inherited from limine boot)
* - initial memory pool not yet wired; __libc_init sets up a tiny bump
* arena for development.
*/
void _start(void)
{
extern int main(int, char **, char **);
extern void __libc_init(void);
register long *args __asm__("rsp");
int argc = (int)args[0];
char **argv = (char **)(args + 1);
char **envp = argv + argc + 1;
__libc_init();
int ret = main(argc, argv, envp);
/* No _exit syscall in Elyz — send an Exit message to the PM actor.
* For now: infinite halt. */
for (;;) __asm__ volatile("hlt");
(void)ret;
}

42
include/errno.h Normal file
View File

@@ -0,0 +1,42 @@
#ifndef _ELIBC_ERRNO_H
#define _ELIBC_ERRNO_H
#define EPERM 1
#define ENOENT 2
#define ESRCH 3
#define EINTR 4
#define EIO 5
#define ENXIO 6
#define E2BIG 7
#define ENOEXEC 8
#define EBADF 9
#define ECHILD 10
#define EAGAIN 11
#define ENOMEM 12
#define EACCES 13
#define EFAULT 14
#define EBUSY 16
#define EEXIST 17
#define EXDEV 18
#define ENODEV 19
#define ENOTDIR 20
#define EISDIR 21
#define EINVAL 22
#define ENFILE 23
#define EMFILE 24
#define ENOTTY 25
#define EFBIG 27
#define ENOSPC 28
#define ESPIPE 29
#define EROFS 30
#define EMLINK 31
#define EPIPE 32
#define EDOM 33
#define ERANGE 34
#define EDEADLK 35
#define ENOSYS 38
extern int *__errno_location(void);
#define errno (*__errno_location())
#endif

26
include/limits.h Normal file
View File

@@ -0,0 +1,26 @@
#ifndef _ELIBC_LIMITS_H
#define _ELIBC_LIMITS_H
#define CHAR_BIT 8
#define SCHAR_MIN (-128)
#define SCHAR_MAX 127
#define UCHAR_MAX 255
#define SHRT_MIN (-32768)
#define SHRT_MAX 32767
#define USHRT_MAX 65535
#define INT_MIN (-2147483647 - 1)
#define INT_MAX 2147483647
#define UINT_MAX 4294967295U
#define LONG_MIN (-9223372036854775807L - 1)
#define LONG_MAX 9223372036854775807L
#define ULONG_MAX 18446744073709551615UL
#define LLONG_MIN (-9223372036854775807LL - 1)
#define LLONG_MAX 9223372036854775807LL
#define ULLONG_MAX 18446744073709551615ULL
#endif

10
include/stdarg.h Normal file
View File

@@ -0,0 +1,10 @@
#ifndef _ELIBC_STDARG_H
#define _ELIBC_STDARG_H
typedef __builtin_va_list va_list;
#define va_start(v, l) __builtin_va_start(v, l)
#define va_end(v) __builtin_va_end(v)
#define va_arg(v, t) __builtin_va_arg(v, t)
#define va_copy(d, s) __builtin_va_copy(d, s)
#endif

9
include/stdbool.h Normal file
View File

@@ -0,0 +1,9 @@
#ifndef _ELIBC_STDBOOL_H
#define _ELIBC_STDBOOL_H
#define bool _Bool
#define true 1
#define false 0
#define __bool_true_false_are_defined 1
#endif

11
include/stddef.h Normal file
View File

@@ -0,0 +1,11 @@
#ifndef _ELIBC_STDDEF_H
#define _ELIBC_STDDEF_H
typedef __SIZE_TYPE__ size_t;
typedef __PTRDIFF_TYPE__ ptrdiff_t;
typedef __WCHAR_TYPE__ wchar_t;
#define NULL ((void *)0)
#define offsetof(type, member) __builtin_offsetof(type, member)
#endif

18
include/stdint.h Normal file
View File

@@ -0,0 +1,18 @@
#ifndef _ELIBC_STDINT_H
#define _ELIBC_STDINT_H
typedef signed char int8_t;
typedef short int16_t;
typedef int int32_t;
typedef long long int64_t;
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef unsigned long long uint64_t;
typedef __SIZE_TYPE__ size_t;
typedef __UINTPTR_TYPE__ uintptr_t;
typedef __INTPTR_TYPE__ intptr_t;
#endif

18
include/stdlib.h Normal file
View File

@@ -0,0 +1,18 @@
#ifndef _ELIBC_STDLIB_H
#define _ELIBC_STDLIB_H
#include <stddef.h>
void *malloc(size_t);
void *calloc(size_t, size_t);
void *realloc(void *, size_t);
void free(void *);
void exit(int) __attribute__((noreturn));
void abort(void) __attribute__((noreturn));
int atoi(const char *);
long atol(const char *);
long long atoll(const char *);
#endif

21
include/string.h Normal file
View File

@@ -0,0 +1,21 @@
#ifndef _ELIBC_STRING_H
#define _ELIBC_STRING_H
#include <stddef.h>
void *memcpy(void *restrict, const void *restrict, size_t);
void *memmove(void *, const void *, size_t);
void *memset(void *, int, size_t);
int memcmp(const void *, const void *, size_t);
void *memchr(const void *, int, size_t);
size_t strlen(const char *);
int strcmp(const char *, const char *);
int strncmp(const char *, const char *, size_t);
char *strcpy(char *restrict, const char *restrict);
char *strncpy(char *restrict, const char *restrict, size_t);
char *strcat(char *restrict, const char *restrict);
char *strchr(const char *, int);
char *strrchr(const char *, int);
#endif

7
src/internal/errno.c Normal file
View File

@@ -0,0 +1,7 @@
#include <errno.h>
#include "libc.h"
int *__errno_location(void)
{
return &__errno;
}

23
src/internal/libc.c Normal file
View File

@@ -0,0 +1,23 @@
#include "libc.h"
/* Initial memory pool — small bump arena for early development.
* When the PM-actor path is wired this will request memory via
* elyz_kcall with ELYZ_OP_ALLOCATE.
*/
#define EARLY_HEAP_SIZE (64UL * 1024 * 1024)
static unsigned char __early_heap[EARLY_HEAP_SIZE];
struct __libc __libc = {
.heap_base = (unsigned long)__early_heap,
.heap_size = EARLY_HEAP_SIZE,
.heap_used = 0,
.threaded = 0,
.progname = 0,
};
int __errno = 0;
void __libc_init(void)
{
/* Future: read startup_info from loader, wire capabilities */
}

28
src/internal/libc.h Normal file
View File

@@ -0,0 +1,28 @@
#ifndef _ELIBC_INTERNAL_LIBC_H
#define _ELIBC_INTERNAL_LIBC_H
/* Internal libc state.
*
* This structure is the single global anchor for elibc. Fields are added
* as the kernel ABI stabilises (e.g. CNode root, PM router channel pool,
* initial capability tokens).
*/
#include <arch/generic/startup_info.h>
struct __libc {
/* Initial pool — bump-allocated until the PM-actor path is wired */
unsigned long heap_base;
unsigned long heap_size;
unsigned long heap_used;
int threaded;
char *progname;
};
extern struct __libc __libc;
extern int __errno;
void __libc_init(void);
#endif

52
src/internal/malloc.c Normal file
View File

@@ -0,0 +1,52 @@
/* Bump allocator on the initial memory pool.
*
* Temporary — until the PM-actor malloc path is implemented.
* Thread-safe: no — __libc.heap_used is guarded by nothing yet.
*/
#include <stddef.h>
#include "libc.h"
void *malloc(size_t size)
{
if (size == 0) size = 1;
unsigned long heap = __libc.heap_base;
unsigned long used = __libc.heap_used;
if (used + size > __libc.heap_size)
return 0;
void *ptr = (void *)(heap + used);
__libc.heap_used = used + size;
return ptr;
}
void free(void *ptr)
{
(void)ptr;
}
void *calloc(size_t nmemb, size_t size)
{
size_t total = nmemb * size;
void *p = malloc(total);
if (!p) return 0;
for (size_t i = 0; i < total; i++)
((unsigned char *)p)[i] = 0;
return p;
}
void *realloc(void *ptr, size_t size)
{
if (!ptr) return malloc(size);
if (size == 0) { free(ptr); return 0; }
/* Bump allocator cannot shrink/grow in place — always allocate + copy */
void *newp = malloc(size);
if (!newp) return 0;
unsigned long old = __libc.heap_used - size;
if (old > __libc.heap_used) old = __libc.heap_used;
for (size_t i = 0; i < old; i++)
((unsigned char *)newp)[i] = ((unsigned char *)ptr)[i];
return newp;
}

11
src/string/memcmp.c Normal file
View File

@@ -0,0 +1,11 @@
#include <string.h>
int memcmp(const void *vl, const void *vr, size_t n)
{
const unsigned char *l = vl, *r = vr;
for (size_t i = 0; i < n; i++) {
if (l[i] != r[i])
return l[i] - r[i];
}
return 0;
}

10
src/string/memcpy.c Normal file
View File

@@ -0,0 +1,10 @@
#include <string.h>
void *memcpy(void *restrict dest, const void *restrict src, size_t n)
{
unsigned char *d = dest;
const unsigned char *s = src;
for (size_t i = 0; i < n; i++)
d[i] = s[i];
return dest;
}

9
src/string/memset.c Normal file
View File

@@ -0,0 +1,9 @@
#include <string.h>
void *memset(void *dest, int c, size_t n)
{
unsigned char *d = dest;
for (size_t i = 0; i < n; i++)
d[i] = (unsigned char)c;
return dest;
}

7
src/string/strcmp.c Normal file
View File

@@ -0,0 +1,7 @@
#include <string.h>
int strcmp(const char *l, const char *r)
{
while (*l && *l == *r) l++, r++;
return *(unsigned char *)l - *(unsigned char *)r;
}

8
src/string/strlen.c Normal file
View File

@@ -0,0 +1,8 @@
#include <string.h>
size_t strlen(const char *s)
{
const char *p = s;
while (*p) p++;
return p - s;
}

33
tests/test_basic.c Normal file
View File

@@ -0,0 +1,33 @@
#include <assert.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
int main(void) {
/* memcpy */
char src[] = "hello world";
char dst[64];
memcpy(dst, src, 12);
dst[12] = 0;
assert(strcmp(dst, "hello world") == 0);
/* strlen */
assert(strlen("") == 0);
assert(strlen("abc") == 3);
/* memset */
char buf[10];
memset(buf, 0xAB, 10);
for (int i = 0; i < 10; i++) assert((unsigned char)buf[i] == 0xAB);
/* memcmp */
assert(memcmp("abc", "abc", 3) == 0);
assert(memcmp("abc", "abd", 3) < 0);
/* strcmp */
assert(strcmp("hello", "hello") == 0);
assert(strcmp("abc", "def") < 0);
printf("ALL STRING TESTS PASSED\n");
return 0;
}

28
tests/test_string.c Normal file
View File

@@ -0,0 +1,28 @@
typedef unsigned long size_t;
void *memcpy(void *restrict d, const void *restrict s, size_t n);
void *memset(void *d, int c, size_t n);
int memcmp(const void *l, const void *r, size_t n);
size_t strlen(const char *s);
int strcmp(const char *l, const char *r);
void _start(void)
{
int code = 0;
char src[] = "hello world", dst[64];
memcpy(dst, src, 12); dst[12] = 0;
if (strcmp(dst, "hello world") != 0) code = 1;
else if (strlen("") != 0) code = 2;
else if (strlen("abc") != 3) code = 3;
else {
char buf[10];
memset(buf, 0xAB, 10);
for (int i = 0; i < 10; i++)
if ((unsigned char)buf[i] != 0xAB) { code = 4; break; }
}
if (code == 0 && memcmp("abc", "abc", 3) != 0) code = 5;
if (code == 0 && memcmp("abc", "abd", 3) >= 0) code = 6;
if (code == 0 && strcmp("hello", "hello") != 0) code = 7;
if (code == 0 && strcmp("abc", "def") >= 0) code = 8;
__asm__ volatile ("mov $60, %%eax\n syscall" : : "D"(code) : "rax");
}