Add libc implementation stubs
Real string core and process teardown; thin read/write/close/getpid/_exit syscall wrappers; the rest follows the stub convention (documented error return, errno = ENOSYS, TODO). syscall.c is the only kernel ABI surface; crt0.S is the static entry point, kept out of libc.a. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <[email protected]>
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* crt0.S — process entry point for statically linked nulsl-libc binaries.
|
||||
*
|
||||
* The kernel hands control to _start with the stack already set up:
|
||||
*
|
||||
* rsp -> argc
|
||||
* rsp + 8 -> argv[0], argv[1], ..., argv[argc-1], NULL
|
||||
* after NULL -> envp[0], ... (we compute it, the libc does not use
|
||||
* it yet, but a correct entry point is cheap)
|
||||
*
|
||||
* We set up a valid frame, call main(argc, argv), and hand the exit code
|
||||
* to exit(). No dynamic linker is involved anywhere: this object + libc.a
|
||||
* is the entire runtime (see docs/architecture.md).
|
||||
*
|
||||
* This file is assembled with `gcc -c`, so it passes through the C
|
||||
* preprocessor and can carry the same arch guards as the C sources.
|
||||
*/
|
||||
|
||||
#if defined(__x86_64__)
|
||||
|
||||
.section .text
|
||||
.globl _start
|
||||
.type _start, @function
|
||||
_start:
|
||||
xor %ebp, %ebp /* outermost frame: ebp = 0 */
|
||||
mov (%rsp), %edi /* argc */
|
||||
lea 8(%rsp), %rsi /* argv */
|
||||
lea 16(%rsp,%rdi,8), %rdx /* envp = &argv[argc + 1] */
|
||||
and $-16, %rsp /* ABI: 16-byte stack alignment */
|
||||
call main
|
||||
mov %eax, %edi /* exit(status) */
|
||||
call exit
|
||||
1: jmp 1b /* exit() is noreturn; never reached */
|
||||
.size _start, .-_start
|
||||
|
||||
.section .note.GNU-stack,"",@progbits
|
||||
|
||||
#else
|
||||
#error "nulsl-libc: no crt0 for this architecture yet — see src/arch/"
|
||||
#endif
|
||||
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* errno.c — the errno global.
|
||||
*
|
||||
* Single-threaded for now; see include/errno.h for the threading note.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
int errno = 0;
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* internal.h — private declarations shared between nulsl-libc translation
|
||||
* units. NOT installed; src/ only.
|
||||
*/
|
||||
|
||||
#ifndef _NULSL_INTERNAL_H
|
||||
#define _NULSL_INTERNAL_H
|
||||
|
||||
#include <sys/syscall.h>
|
||||
|
||||
/*
|
||||
* The FILE type: a file descriptor until a real buffering layer lands.
|
||||
* Defined here (not in the public stdio.h) so the layout stays private.
|
||||
*/
|
||||
struct nulsl_file {
|
||||
int fd;
|
||||
};
|
||||
#endif /* _NULSL_INTERNAL_H */
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* stdio.c — standard I/O.
|
||||
*
|
||||
* What is real today: unbuffered character/string output to a descriptor.
|
||||
* What is stubbed: formatting (printf), and anything that would need a
|
||||
* buffer or the open() path. All stubs follow the same convention —
|
||||
* return the documented error value and set errno = ENOSYS.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
FILE *stdin = &(struct nulsl_file){0};
|
||||
FILE *stdout = &(struct nulsl_file){1};
|
||||
FILE *stderr = &(struct nulsl_file){2};
|
||||
|
||||
int puts(const char *s)
|
||||
{
|
||||
if (write(STDOUT_FILENO, s, strlen(s)) < 0)
|
||||
return EOF;
|
||||
if (write(STDOUT_FILENO, "\n", 1) < 0)
|
||||
return EOF;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int putchar(int c)
|
||||
{
|
||||
unsigned char b = (unsigned char)c;
|
||||
return write(STDOUT_FILENO, &b, 1) == 1 ? c : EOF;
|
||||
}
|
||||
|
||||
/* Stub: formatting engine is on the roadmap. */
|
||||
int printf(const char *fmt, ...)
|
||||
{
|
||||
(void)fmt;
|
||||
errno = ENOSYS;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Stub: needs open() and a buffering layer. */
|
||||
FILE *fopen(const char *path, const char *mode)
|
||||
{
|
||||
(void)path;
|
||||
(void)mode;
|
||||
errno = ENOSYS;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int fclose(FILE *f)
|
||||
{
|
||||
(void)f;
|
||||
errno = ENOSYS;
|
||||
return EOF;
|
||||
}
|
||||
|
||||
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *f)
|
||||
{
|
||||
(void)ptr;
|
||||
(void)size;
|
||||
(void)nmemb;
|
||||
(void)f;
|
||||
errno = ENOSYS;
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *f)
|
||||
{
|
||||
(void)ptr;
|
||||
(void)size;
|
||||
(void)nmemb;
|
||||
(void)f;
|
||||
errno = ENOSYS;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Trivially correct: nothing buffers yet, so there is nothing to flush. */
|
||||
int fflush(FILE *f)
|
||||
{
|
||||
(void)f;
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* stdlib.c — general utilities.
|
||||
*
|
||||
* Real: process teardown (exit/_exit/abort) and atoi.
|
||||
* Stubs: the allocator (malloc & friends wait for a brk/mmap allocator)
|
||||
* and strtol (waits for proper errno/base handling).
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
void exit(int status)
|
||||
{
|
||||
/* TODO: run atexit() handlers and flush stdio once buffering exists. */
|
||||
_exit(status);
|
||||
for (;;)
|
||||
; /* unreachable */
|
||||
}
|
||||
|
||||
void abort(void)
|
||||
{
|
||||
/* TODO: raise SIGABRT once signals exist; 128+6 mirrors the shell
|
||||
* convention for "killed by signal 6" without any signal machinery. */
|
||||
_exit(134);
|
||||
for (;;)
|
||||
; /* unreachable */
|
||||
}
|
||||
|
||||
int atoi(const char *s)
|
||||
{
|
||||
int sign = 1;
|
||||
int v = 0;
|
||||
|
||||
while (*s == ' ' || (*s >= '\t' && *s <= '\r'))
|
||||
s++;
|
||||
if (*s == '-' || *s == '+') {
|
||||
if (*s == '-')
|
||||
sign = -1;
|
||||
s++;
|
||||
}
|
||||
while (*s >= '0' && *s <= '9')
|
||||
v = v * 10 + (*s++ - '0');
|
||||
return sign * v;
|
||||
}
|
||||
|
||||
/* Stub: brk()/mmap() allocator is on the roadmap. */
|
||||
void *malloc(size_t size)
|
||||
{
|
||||
(void)size;
|
||||
errno = ENOSYS;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void *calloc(size_t nmemb, size_t size)
|
||||
{
|
||||
(void)nmemb;
|
||||
(void)size;
|
||||
errno = ENOSYS;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void *realloc(void *ptr, size_t size)
|
||||
{
|
||||
(void)ptr;
|
||||
(void)size;
|
||||
errno = ENOSYS;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void free(void *ptr)
|
||||
{
|
||||
(void)ptr;
|
||||
/* Nothing to do until the allocator exists. */
|
||||
}
|
||||
|
||||
/* Stub: needs errno/base handling (ERANGE, EINVAL, 0x/0o prefixes). */
|
||||
long strtol(const char *s, char **endptr, int base)
|
||||
{
|
||||
(void)s;
|
||||
(void)endptr;
|
||||
(void)base;
|
||||
errno = ENOSYS;
|
||||
return 0;
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* string.c — real, minimal implementations of the memory/string core.
|
||||
*
|
||||
* Byte-at-a-time on purpose: for the sizes Null Linux deals with, and for
|
||||
* the goal of keeping the code obviously correct, word-at-a-time tricks
|
||||
* are not worth the branch soup. If profiling ever says otherwise, the
|
||||
* benchmark suite will say so (see benchmarks/).
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
size_t strlen(const char *s)
|
||||
{
|
||||
const char *p = s;
|
||||
while (*p)
|
||||
p++;
|
||||
return (size_t)(p - s);
|
||||
}
|
||||
|
||||
int strcmp(const char *a, const char *b)
|
||||
{
|
||||
while (*a && *a == *b) {
|
||||
a++;
|
||||
b++;
|
||||
}
|
||||
return (unsigned char)*a - (unsigned char)*b;
|
||||
}
|
||||
|
||||
int strncmp(const char *a, const char *b, size_t n)
|
||||
{
|
||||
for (; n && *a && *a == *b; n--, a++, b++)
|
||||
;
|
||||
if (n == 0)
|
||||
return 0;
|
||||
return (unsigned char)*a - (unsigned char)*b;
|
||||
}
|
||||
|
||||
char *strcpy(char *dst, const char *src)
|
||||
{
|
||||
char *d = dst;
|
||||
while ((*d++ = *src++))
|
||||
;
|
||||
return dst;
|
||||
}
|
||||
|
||||
char *strncpy(char *dst, const char *src, size_t n)
|
||||
{
|
||||
char *d = dst;
|
||||
while (n && *src) {
|
||||
*d++ = *src++;
|
||||
n--;
|
||||
}
|
||||
while (n--)
|
||||
*d++ = '\0';
|
||||
return dst;
|
||||
}
|
||||
|
||||
void *memcpy(void *restrict dst, const void *restrict src, size_t n)
|
||||
{
|
||||
unsigned char *d = dst;
|
||||
const unsigned char *s = src;
|
||||
while (n--)
|
||||
*d++ = *s++;
|
||||
return dst;
|
||||
}
|
||||
|
||||
void *memmove(void *dst, const void *src, size_t n)
|
||||
{
|
||||
unsigned char *d = dst;
|
||||
const unsigned char *s = src;
|
||||
|
||||
if ((size_t)(d - s) >= n) {
|
||||
/* No overlap (or exact): copy forward. */
|
||||
while (n--)
|
||||
*d++ = *s++;
|
||||
} else {
|
||||
/* Overlap: copy backward. */
|
||||
d += n;
|
||||
s += n;
|
||||
while (n--)
|
||||
*--d = *--s;
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
int memcmp(const void *a, const void *b, size_t n)
|
||||
{
|
||||
const unsigned char *x = a;
|
||||
const unsigned char *y = b;
|
||||
while (n--) {
|
||||
if (*x != *y)
|
||||
return *x - *y;
|
||||
x++;
|
||||
y++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void *memset(void *dst, int c, size_t n)
|
||||
{
|
||||
unsigned char *d = dst;
|
||||
while (n--)
|
||||
*d++ = (unsigned char)c;
|
||||
return dst;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* syscall.c — the raw syscall entry point.
|
||||
*
|
||||
* This is the ONLY file in nulsl-libc that executes the `syscall`
|
||||
* instruction. Every other function that reaches the kernel goes
|
||||
* through here (see docs/syscalls.md).
|
||||
*
|
||||
* ABI: on error the kernel returns -errno in rax. We follow the glibc
|
||||
* convention for syscall(): return -1 and set errno. (The -4095 bound is
|
||||
* the documented Linux range for negative errno values.)
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdarg.h>
|
||||
#include <sys/syscall.h>
|
||||
|
||||
long syscall(long number, ...)
|
||||
{
|
||||
va_list ap;
|
||||
long a1, a2, a3, a4, a5, a6;
|
||||
long ret;
|
||||
|
||||
va_start(ap, number);
|
||||
a1 = va_arg(ap, long);
|
||||
a2 = va_arg(ap, long);
|
||||
a3 = va_arg(ap, long);
|
||||
a4 = va_arg(ap, long);
|
||||
a5 = va_arg(ap, long);
|
||||
a6 = va_arg(ap, long);
|
||||
va_end(ap);
|
||||
|
||||
#if defined(__x86_64__)
|
||||
/* System V AMD64 ABI: number in rax, args in rdi rsi rdx r10 r8 r9. */
|
||||
register long r10 __asm__("r10") = a4;
|
||||
register long r8 __asm__("r8") = a5;
|
||||
register long r9 __asm__("r9") = a6;
|
||||
__asm__ volatile("syscall"
|
||||
: "=a"(ret)
|
||||
: "a"(number), "D"(a1), "S"(a2), "d"(a3), "r"(r10),
|
||||
"r"(r8), "r"(r9)
|
||||
: "rcx", "r11", "memory");
|
||||
#else
|
||||
#error "nulsl-libc: no syscall ABI for this architecture yet — see src/arch/"
|
||||
#endif
|
||||
|
||||
if (ret < 0 && ret >= -4095) {
|
||||
errno = (int)-ret;
|
||||
return -1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* unistd.c — thin wrappers around raw syscalls.
|
||||
*
|
||||
* Each function here is a one-liner on purpose: the kernel is the API
|
||||
* (project guideline #6), and a wrapper that does more than translate
|
||||
* arguments is a wrapper that can lie. Error translation (kernel -errno
|
||||
* -> errno) happens inside syscall() itself.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <unistd.h>
|
||||
|
||||
ssize_t read(int fd, void *buf, size_t count)
|
||||
{
|
||||
return (ssize_t)syscall(SYS_read, fd, buf, count);
|
||||
}
|
||||
|
||||
ssize_t write(int fd, const void *buf, size_t count)
|
||||
{
|
||||
return (ssize_t)syscall(SYS_write, fd, buf, count);
|
||||
}
|
||||
|
||||
int close(int fd)
|
||||
{
|
||||
return (int)syscall(SYS_close, fd);
|
||||
}
|
||||
|
||||
pid_t getpid(void)
|
||||
{
|
||||
return (pid_t)syscall(SYS_getpid);
|
||||
}
|
||||
|
||||
void _exit(int status)
|
||||
{
|
||||
syscall(SYS_exit, status);
|
||||
for (;;)
|
||||
; /* unreachable */
|
||||
}
|
||||
|
||||
/* Stub: trivially a one-liner once wanted (SYS_unlink = 87 on x86_64). */
|
||||
int unlink(const char *path)
|
||||
{
|
||||
(void)path;
|
||||
errno = ENOSYS;
|
||||
return -1;
|
||||
}
|
||||
Reference in New Issue
Block a user