initial commit

This commit is contained in:
IoIxD 2026-01-15 20:10:55 -07:00
commit 7accc34991
10 changed files with 498 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
msh

25
Makefile Normal file
View file

@ -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

5
README.md Normal file
View file

@ -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).

39
src/main.c Normal file
View file

@ -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);
}
}

138
src/system/linux/alloc.c Normal file
View file

@ -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);
}

75
src/system/linux/i386.s Normal file
View file

@ -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

41
src/system/linux/linux.c Normal file
View file

@ -0,0 +1,41 @@
#include "../system.h"
#include "syscall.h"
#include <signal.h>
#include <sys/wait.h>
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;
};

View file

@ -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 <sys/syscall.h>
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

86
src/system/linux/x86_64.s Normal file
View file

@ -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

50
src/system/system.h Normal file
View file

@ -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