From 7accc34991753dce4ea01fe6d83a4a1e2c6a8aa1 Mon Sep 17 00:00:00 2001 From: IoIxD Date: Thu, 15 Jan 2026 20:10:55 -0700 Subject: [PATCH] initial commit --- .gitignore | 1 + Makefile | 25 +++++++ README.md | 5 ++ src/main.c | 39 +++++++++++ src/system/linux/alloc.c | 138 +++++++++++++++++++++++++++++++++++++ src/system/linux/i386.s | 75 ++++++++++++++++++++ src/system/linux/linux.c | 41 +++++++++++ src/system/linux/syscall.h | 38 ++++++++++ src/system/linux/x86_64.s | 86 +++++++++++++++++++++++ src/system/system.h | 50 ++++++++++++++ 10 files changed, 498 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md create mode 100644 src/main.c create mode 100644 src/system/linux/alloc.c create mode 100644 src/system/linux/i386.s create mode 100644 src/system/linux/linux.c create mode 100644 src/system/linux/syscall.h create mode 100644 src/system/linux/x86_64.s create mode 100644 src/system/system.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4ad1aae --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +msh diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..811f93f --- /dev/null +++ b/Makefile @@ -0,0 +1,25 @@ +SOURCES := src/main.c +CFLAGS := -g -O0 -nostdlib + +UNAME := $(shell uname) +ARCH := $(shell uname -m) + +ifeq ($(UNAME), Linux) +SOURCES += src/system/linux/linux.c src/system/linux/alloc.c +CFLAGS += -fno-stack-protector +CFLAGS += -DMSH_ENTRY_POINT="void _start()" +ifeq ($(ARCH),i386) +SOURCES += src/system/linux/i386.s +CFLAGS += -DARCH_32 +endif +ifeq ($(ARCH),x86_64) +SOURCES += src/system/linux/x86_64.s +CFLAGS += -DARCH_64 +endif +endif + +all: $(SOURCES) + $(CC) $(SOURCES) $(CFLAGS) -o msh + +clean: + rm -f *.o msh diff --git a/README.md b/README.md new file mode 100644 index 0000000..b8b08b9 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# msh + +msh is a WIP shell with the goal of being portable to older operating systems, namely classic Windows and classic Mac OS. While the former already has a shell, the aim for both is to provide an emulation of bash that's more sane to use in the modern day then whatever is built in. + +It's written without the C standard library. Ports instead must implement `src/system/system.h` accordingly. It's also written in C89/C99 (mostly the former as it's needed for some compilers, but if C99 is supported by a platform's backend then it'll probably be used). diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..2a8924a --- /dev/null +++ b/src/main.c @@ -0,0 +1,39 @@ +#include "system/system.h" + +unsigned char prefix[] = "%> "; + +/* Makefile-defined system entry point. */ +MSH_ENTRY_POINT { + while (1) { + int out; + unsigned char *buf = msh_callocate(2048, sizeof(unsigned char)); + unsigned char **args = msh_callocate(2048 * 2, sizeof(unsigned char)); + int i = 0, n = 0, last = 0; + unsigned long long sz = 0; + + msh_write(prefix, sizeof(prefix)); + + while ((out = msh_read_input(buf, &sz, 2048)), !out) { + } + + /* args */ + for (i = 0; i < sz; i++) { + if (buf[i] == ' ') { + *(buf + i) = '\0'; + if (i != sz - 1) { + args[n++] = buf + last; + last = i + 1; + if (n >= 2048) { + break; + } + } + } + } + + args[n++] = buf + last; + + msh_execute(buf, args); + + msh_deallocate(buf); + } +} diff --git a/src/system/linux/alloc.c b/src/system/linux/alloc.c new file mode 100644 index 0000000..5338f07 --- /dev/null +++ b/src/system/linux/alloc.c @@ -0,0 +1,138 @@ +#include "../system.h" +#include "syscall.h" + +typedef struct msh_linux_block_t msh_linux_block; + +#ifdef ARCH_32 +#define PTR_ALIGN(x) (((((x) - 1) >> 2) << 2) + 4) +#else +#define PTR_ALIGN(x) (((((x) - 1) >> 3) << 3) + 8) +#endif + +struct msh_linux_block_t { + SIZE_TYPE size; + msh_linux_block *prev; + msh_linux_block *next; + int free; + char data[1]; +}; + +#define MSH_BLOCK_SIZE 28 + +static msh_linux_block *msh_linux_base = 0; + +static msh_linux_block *find_block(msh_linux_block **last, SIZE_TYPE size) { + msh_linux_block *b = msh_linux_base; + while (b && !(b->free && b->size >= size)) { + *last = b; + b = b->next; + } + return b; +} +static msh_linux_block *extend_heap(msh_linux_block *last, SIZE_TYPE size) { + msh_linux_block *sb = (msh_linux_block *)SYSCALL(brk, 0); + + if (SYSCALL(brk, sb + (MSH_BLOCK_SIZE) + size) < 0) { + return (void *)0; + }; + + sb->size = size; + sb->next = (msh_linux_block *)0; + sb->prev = last; + + if (last) { + last->next = sb; + } + sb->free = 0; + + return (void *)sb; +} + +static void split_block(msh_linux_block *b, SIZE_TYPE size) { + msh_linux_block *new; + new = (msh_linux_block *)b->data + size; + new->size = b->size - size - MSH_BLOCK_SIZE; + new->next = b->next; + new->free = 1; + b->size = size; + b->next = new; + if (new->next) { + new->next->prev = new; + } +} + +static msh_linux_block *get_metadata_block(void *p) { + return p - MSH_BLOCK_SIZE; +} + +static msh_linux_block *fuse_with_next_block(msh_linux_block *b) { + if (b->next && b->next->free) { + b->size += MSH_BLOCK_SIZE + b->next->size; + b->next = b->next->next; + if (b->next) { + b->next->prev = b; + } + } + return b; +} + +void *msh_allocate(SIZE_TYPE size) { + msh_linux_block *b, *last = 0; + SIZE_TYPE s = PTR_ALIGN(size); + + if (msh_linux_base) { + /* first find a block */ + last = msh_linux_base; + b = find_block(&last, s); + if (b) { + /* if we can split, do so */ + if ((b->size - s) >= MSH_BLOCK_SIZE + 4) { + split_block(b, s); + } + b->free = 0; + } else { + b = extend_heap(last, s); + if (!b) { + return (msh_linux_block *)0; + } + } + } else { + b = extend_heap(last, s); + if (!b) { + return (msh_linux_block *)0; + } + msh_linux_base = b; + } + return b->data; +}; + +void msh_deallocate(void *ptr) { + msh_linux_block *block = get_metadata_block(ptr); + block->free = 1; + if (block->prev && block->prev->free) { + block = fuse_with_next_block(block->prev); + } + if (block->next) { + fuse_with_next_block(block); + } else { + /* free the end of the heap */ + if (block->prev) { + block->prev->next = 0; + } else { + /* no more block */ + msh_linux_base = 0; + } + SYSCALL(brk, block); + } +}; + +void *msh_callocate(SIZE_TYPE number, SIZE_TYPE size) { + SIZE_TYPE *new, i, s4; + new = msh_allocate(number * size); + if (new) { + for (i = 0; i < (number * size); i++) { + new[i] = 0; + } + } + return (new); +} diff --git a/src/system/linux/i386.s b/src/system/linux/i386.s new file mode 100644 index 0000000..aa40fe5 --- /dev/null +++ b/src/system/linux/i386.s @@ -0,0 +1,75 @@ +.globl syscall_0 +.globl syscall_1 +.globl syscall_2 +.globl syscall_3 +.globl syscall_4 +.globl syscall_5 +.globl syscall_6 +syscall_0: + push %ebx + mov 0x4(%esp), %eax + int $0x80 + pop %ebx + ret +syscall_1: + push %ebx + mov 0x8(%esp), %eax + mov 0xc(%esp), %ebx + int $0x80 + pop %ebx + ret + +syscall_2: + push %ebx + mov 0x8(%esp), %eax + mov 0xc(%esp), %ebx + mov 0x10(%esp), %ecx + int $0x80 + pop %ebx + ret + +syscall_3: + push %ebx + mov 0x8(%esp), %eax + mov 0xc(%esp), %ebx + mov 0x10(%esp), %ecx + mov 0x14(%esp), %edx + int $0x80 + pop %ebx + ret + +syscall_4: + push %ebx + mov 0x8(%esp), %eax + mov 0xc(%esp), %ebx + mov 0x10(%esp), %ecx + mov 0x14(%esp), %edx + mov 0x18(%esp), %esi + int $0x80 + pop %ebx + ret + +syscall_5: + push %ebx + mov 0x8(%esp), %eax + mov 0xc(%esp), %ebx + mov 0x10(%esp), %ecx + mov 0x14(%esp), %edx + mov 0x18(%esp), %esi + int $0x80 + pop %ebx + ret + +syscall_6: + push %ebx + mov 0x8(%esp), %eax + mov 0xc(%esp), %ebx + mov 0x10(%esp), %ecx + mov 0x14(%esp), %edx + mov 0x18(%esp), %esi + mov 0x1c(%esp), %edi + mov 0x20(%esp), %ebp + int $0x80 + pop %ebx + + ret diff --git a/src/system/linux/linux.c b/src/system/linux/linux.c new file mode 100644 index 0000000..c005007 --- /dev/null +++ b/src/system/linux/linux.c @@ -0,0 +1,41 @@ +#include "../system.h" +#include "syscall.h" + +#include +#include + +int msh_read_input(unsigned char *ptr, SIZE_TYPE *size, SIZE_TYPE n) { + char ch; + SIZE_TYPE sz; + if (*size >= n) { + return 2; + } + sz = (SIZE_TYPE)SYSCALL(read, 0, &ch, 1); + if (ch == '\n') { + *(ptr + *size) = '\0'; + return 1; + } else { + *(ptr + *size) = ch; + *size += sz; + } + return 0; +}; + +void msh_write(unsigned char *st, SIZE_TYPE size) { + SYSCALL(write, 1, st, size); +} + +int msh_execute(unsigned char *cmd, unsigned char **args) { + SIZE_TYPE fork = (SIZE_TYPE)SYSCALL(fork, 0); + if (fork == 0) { + SYSCALL(execve, cmd, args, 0); + msh_deallocate(args); + SYSCALL(kill, fork, SIGKILL); + return 0; + } else { + siginfo_t info; + SYSCALL(waitid, P_ALL, 0, &info, WEXITED); + return info.si_code; + } + return 0; +}; diff --git a/src/system/linux/syscall.h b/src/system/linux/syscall.h new file mode 100644 index 0000000..0c22887 --- /dev/null +++ b/src/system/linux/syscall.h @@ -0,0 +1,38 @@ +#ifndef __MSH_SYSTEM_LINUX_SYSCALL +#define __MSH_SYSTEM_LINUX_SYSCALL + +#define NARGS_SEQ(_1, _2, _3, _4, _5, _6, _7, _8, _9, N, ...) N +#define NARGS(...) NARGS_SEQ(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1) + +#include + +void *syscall_0(void *number); +void *syscall_1(void *number, void *arg1); +void *syscall_2(void *number, void *arg1, void *arg2); +void *syscall_3(void *number, void *arg1, void *arg2, void *arg3); +void *syscall_4(void *number, void *arg1, void *arg2, void *arg3, void *arg4); +void *syscall_5(void *number, void *arg1, void *arg2, void *arg3, void *arg4, + void *arg5); +void *syscall_6(void *number, void *arg1, void *arg2, void *arg3, void *arg4, + void *arg5, void *arg6); + +#define _syscall_0(a) syscall_0((void *)(a)) +#define _syscall_1(a, b) syscall_1((void *)(a), (void *)(b)) +#define _syscall_2(a, b, c) syscall_2((void *)(a), (void *)(b), (void *)(c)) +#define _syscall_3(a, b, c, d) \ + syscall_3((void *)(a), (void *)(b), (void *)(c), (void *)(d)) +#define _syscall_4(a, b, c, d, e) \ + syscall_4((void *)(a), (void *)(b), (void *)(c), (void *)(d), (void *)(e)) +#define _syscall_5(a, b, c, d, e, f) \ + syscall_5((void *)(a), (void *)(b), (void *)(c), (void *)(d), (void *)(e), \ + (void *)(f)) +#define _syscall_6(a, b, c, d, e, f, g) \ + syscall_6((void *)(a), (void *)(b), (void *)(c), (void *)(d), (void *)(e), \ + (void *)(f), (void *)(g)) +#define PASTER(x, y) x##_##y +#define EVALUATOR(x, y) PASTER(x, y) +#define VARIABLE(...) NARGS(__VA_ARGS__) +#define FUNC(...) EVALUATOR(_syscall, VARIABLE(__VA_ARGS__)) + +#define SYSCALL(name, ...) FUNC(__VA_ARGS__)(__NR_##name, __VA_ARGS__) +#endif diff --git a/src/system/linux/x86_64.s b/src/system/linux/x86_64.s new file mode 100644 index 0000000..74345dc --- /dev/null +++ b/src/system/linux/x86_64.s @@ -0,0 +1,86 @@ +# Copyright 2024 (c) Jonathan M. Wilbur. Released under an MIT License. + +.globl syscall_0 +.globl syscall_1 +.globl syscall_2 +.globl syscall_3 +.globl syscall_4 +.globl syscall_5 +.globl syscall_6 + +syscall_0: + mov %rdi, %rax + syscall + retq + +syscall_1: + push %rbp + mov %rsp, %rbp + + push %rdi + + mov %rdi, %rax + mov %rsi, %rdi + syscall + + mov %rdi, %rsi + pop %rdi + + leaveq + retq + +syscall_2: + mov %rdi, %rax + mov %rsi, %rdi + mov %rdx, %rsi + syscall + retq + +syscall_3: + push %rbp + mov %rsp, %rbp + push %rdi + + mov %rdi, %rax + mov %rsi, %rdi + mov %rdx, %rsi + mov %rcx, %rdx + syscall + + mov %rdx, %rcx + mov %rsi, %rdx + mov %rdi, %rsi + pop %rdi + + leaveq + retq + +syscall_4: + mov %rdi, %rax + mov %rsi, %rdi + mov %rdx, %rsi + mov %rcx, %rdx + mov %r8, %r10 + syscall + retq + +syscall_5: + mov %rdi, %rax + mov %rsi, %rdi + mov %rdx, %rsi + mov %rcx, %rdx + mov %r8, %r10 + mov %r9, %r8 + syscall + retq + +syscall_6: + mov %rdi, %rax + mov %rsi, %rdi + mov %rdx, %rsi + mov %rcx, %rdx + mov %r8, %r10 + mov %r9, %r8 + mov 0x8(%rsp), %r9 + syscall + retq diff --git a/src/system/system.h b/src/system/system.h new file mode 100644 index 0000000..b2d8833 --- /dev/null +++ b/src/system/system.h @@ -0,0 +1,50 @@ +#ifndef __MSH_SYSTEM_H +#define __MSH_SYSTEM_H + +#ifndef SIZE_TYPE +#ifdef ARCH_32 +#define SIZE_TYPE unsigned long +#else +#define SIZE_TYPE unsigned long long +#endif +#endif + +/* Allocate memory */ +void *msh_allocate(SIZE_TYPE size); + +/* Allocates memory for an array of num objects of size */ +void *msh_callocate(SIZE_TYPE number, SIZE_TYPE size); + +/* Deallocate memory previously allocated by msh_allocate */ +void msh_deallocate(void *ptr); + +/* + * Read user input into ptr, stopping if it has reached n characters. + * returns 1 if finished reading, 2 if it ran out of space. + */ +int msh_read_input(unsigned char *ptr, SIZE_TYPE *size, SIZE_TYPE n); + +/* Write characters to terminal. */ +void msh_write(unsigned char *st, SIZE_TYPE size); + +/* + * Execute given command with args. + * + * cmd is self explanatory, args is a null terminated array of strings based on + * the arguments the user provided. + * + * The function is also responsible for searching the user's PATH to execute the + * first command. + * + * Returns exit status (or 0 on platforms where that isn't a thing). + */ +int msh_execute(unsigned char *cmd, unsigned char **args); + +#ifndef MSH_ENTRY_POINT +/* + * default to int main() to make language servers happy. + */ +#define MSH_ENTRY_POINT int main() +#endif + +#endif