├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── arch └── amd64 │ ├── entry.asm │ └── syscall.asm ├── include ├── stdarg.h ├── stddef.h ├── stdint.h ├── stdio.h ├── stdlib.h └── string.h ├── lib └── .gitignore ├── src ├── brk.c ├── brk.h ├── file.c ├── memory.c ├── string.c └── syscall.h └── tests └── brk └── test.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.swp 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Joseph Kogut 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC = clang 2 | LD = clang 3 | AS = nasm 4 | 5 | ARCH=amd64 6 | 7 | CFLAGS = -Wall -pipe -Iinclude/ -std=gnu11 -nostdlib -ffreestanding $(OPTIMIZATION) 8 | LFLAGS = $(CFLAGS) -Llib/ 9 | ASFLAGS = -f elf64 10 | 11 | OPTIMIZATION = -O2 12 | 13 | DEBUG = no 14 | ifeq ($(DEBUG), yes) 15 | CFLAGS += -g 16 | endif 17 | 18 | MLIBC_SOURCES = $(wildcard src/*.c) 19 | MLIBC_SOURCES += $(wildcard arch/${ARCH}/*.asm) 20 | 21 | TMP_OBJECTS = $(MLIBC_SOURCES:.c=.o) 22 | MLIBC_OBJECTS = $(TMP_OBJECTS:.asm=.o) 23 | 24 | LIB_DIR = lib 25 | 26 | mlibc: $(MLIBC_OBJECTS) 27 | ar rcs $(LIB_DIR)/libminimalc.a $(MLIBC_OBJECTS) 28 | 29 | %.o: %.c 30 | $(CC) $(CFLAGS) -c $< -o $@ 31 | 32 | %.o: %.asm 33 | $(AS) $(ASFLAGS) $< -o $@ 34 | 35 | clean: 36 | find . -type f \( -name "*.o" -o -name "*.a" \) -exec rm {} \; 37 | 38 | .PHONY: clean 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mlibc 2 | Minimal C Library 3 | 4 | This is a minimal and (intentionally) incomplete C library for Linux. The goal is simple, to provide enough of a subset of the C standard library to bootstrap other projects without linking to a full-fledged library. 5 | -------------------------------------------------------------------------------- /arch/amd64/entry.asm: -------------------------------------------------------------------------------- 1 | global _start 2 | 3 | section .text 4 | 5 | extern main 6 | 7 | _start: 8 | xor rbp, rbp 9 | pop rdi 10 | mov rsi, rsp 11 | call main 12 | 13 | mov rdi, rax ; Move the return value of main to the error code arg of sys_exit 14 | mov rax, 60 ; sys_exit 15 | syscall 16 | 17 | 18 | -------------------------------------------------------------------------------- /arch/amd64/syscall.asm: -------------------------------------------------------------------------------- 1 | global syscall 2 | 3 | section .text 4 | 5 | syscall: 6 | push rbp 7 | mov rbp, rsp 8 | 9 | mov rax, rdi ; syscall 10 | mov rdi, rsi 11 | mov rsi, rdx 12 | mov rdx, rcx 13 | mov r10, r8 14 | mov r8, r9 15 | mov r9, [rsp] ; Copy instead of pop 16 | ; removing an argument if it's not passed in will trash the stack 17 | syscall 18 | 19 | pop rbp 20 | ret 21 | 22 | section .data 23 | -------------------------------------------------------------------------------- /include/stdarg.h: -------------------------------------------------------------------------------- 1 | typedef unsigned char *va_list; 2 | 3 | #define va_start(list, param) (list = (va_list)¶m + sizeof(param)) 4 | #define va_arg(list, type) (* (type *)((list += sizeof(type)) - sizeof(type))) 5 | #define va_end(list) 6 | 7 | -------------------------------------------------------------------------------- /include/stddef.h: -------------------------------------------------------------------------------- 1 | #ifndef _STDDEF_H_ 2 | #define _STDDEF_H_ 1 3 | 4 | #define NULL 0 5 | 6 | typedef unsigned long size_t; 7 | typedef long ssize_t; 8 | typedef long intptr_t; 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /include/stdint.h: -------------------------------------------------------------------------------- 1 | #ifndef _STDINT_H_ 2 | #define _STDINT_H_ 3 | 4 | typedef unsigned char uint8_t; 5 | typedef unsigned short uint16_t; 6 | typedef unsigned long uint32_t; 7 | typedef unsigned long long uint64_t; 8 | 9 | typedef char int8_t; 10 | typedef short int16_t; 11 | typedef int int32_t; 12 | typedef long long int64_t; 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /include/stdio.h: -------------------------------------------------------------------------------- 1 | #ifndef _STDIO_H_ 2 | #define _STDIO_H_ 3 | 4 | #include "stddef.h" 5 | 6 | #define NULL 0 7 | 8 | #define EOF -1 9 | 10 | #define STDIN_FILENO 0 11 | #define STDOUT_FILENO 1 12 | #define STDERR_FILENO 2 13 | 14 | #define FOPEN_MAX 255 15 | #define FILENAME_MAX 255 16 | 17 | #define SEEK_SET 0 18 | #define SEEK_CURR 1 19 | #define SEEK_END 2 20 | 21 | typedef long fpos_t; 22 | 23 | typedef struct { 24 | int fd; 25 | fpos_t pos; 26 | 27 | char mode; 28 | char eof; 29 | } FILE; 30 | 31 | static const FILE stdout = { 32 | .fd = 0, 33 | .pos = 0, 34 | }; 35 | 36 | static const FILE stdin = { 37 | .fd = 1, 38 | .pos = 0, 39 | }; 40 | 41 | static const FILE stderr = { 42 | .fd = 2, 43 | .pos = 0, 44 | }; 45 | 46 | int printf(const char *restrict format, ...); 47 | char *fgets(char *str, int n, FILE *stream); 48 | 49 | FILE *fopen(const char *filename, const char *mode); 50 | int fclose(FILE *stream); 51 | 52 | int feof(FILE *stream); 53 | long int ftell(FILE *stream); 54 | int fseek(FILE *stream, long int offset, int whence); 55 | 56 | int fgetc(FILE *stream); 57 | 58 | void rewind(FILE *stream); 59 | 60 | #endif 61 | -------------------------------------------------------------------------------- /include/stdlib.h: -------------------------------------------------------------------------------- 1 | #ifndef _STDLIB_H_ 2 | #define _STDLIB_H_ 3 | 4 | #include "stddef.h" 5 | 6 | void *malloc(size_t size); 7 | void *calloc(size_t count, size_t size); 8 | void *realloc(void *p, size_t size); 9 | void free(void *p); 10 | 11 | unsigned long int strtoul(const char *nptr, char **endptr, int base); 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /include/string.h: -------------------------------------------------------------------------------- 1 | #ifndef _STRING_H_ 2 | #define _STRING_H_ 1 3 | 4 | #define NULL 0 5 | 6 | typedef unsigned long size_t; 7 | 8 | size_t strlen(const char *); 9 | void *memcpy(void *dest, const void *src, size_t n); 10 | void *memset(void *s, int c, size_t n); 11 | void *memmove(void *dest, const void *src, size_t n); 12 | 13 | char *strcat(char *dest, const char *src); 14 | char *strchr(const char *str, int c); 15 | int strcmp(const char *str1, const char *str2); 16 | char *strcpy(char *dest, const char *src); 17 | char *strncpy(char *dest, const char *src, size_t n); 18 | char *strstr(const char *haystack, const char *needle); 19 | char *strtok(char *str, const char *delim); 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /lib/.gitignore: -------------------------------------------------------------------------------- 1 | *.a 2 | -------------------------------------------------------------------------------- /src/brk.c: -------------------------------------------------------------------------------- 1 | #include "stdlib.h" 2 | 3 | #include "syscall.h" 4 | 5 | int brk(void *addr) 6 | { 7 | long ret = syscall(SYS_brk, addr); 8 | if (ret != (long)addr) 9 | return -1; 10 | 11 | return 0; 12 | } 13 | 14 | void *sbrk(size_t increment) 15 | { 16 | void *current_brk = (void *)syscall(SYS_brk, 0); 17 | if (!increment) return current_brk; 18 | 19 | if (brk(current_brk + increment) == 0) 20 | return current_brk; 21 | 22 | return (void *)(-1); 23 | } 24 | -------------------------------------------------------------------------------- /src/brk.h: -------------------------------------------------------------------------------- 1 | #ifndef _BRK_H_ 2 | #define _BRK_H_ 1 3 | 4 | int brk(void *addr); 5 | void *sbrk(intptr_t increment); 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /src/file.c: -------------------------------------------------------------------------------- 1 | #include "stdio.h" 2 | #include "string.h" 3 | #include "stdlib.h" 4 | 5 | #include "syscall.h" 6 | 7 | #include 8 | 9 | FILE *fopen(const char *path, const char *mode) 10 | { 11 | int o_flags = 0; 12 | 13 | size_t mode_size = strlen(mode); 14 | for (int i = 0; i < mode_size; i++) { 15 | switch(mode[i]) { 16 | case 'r': 17 | if (i < mode_size-1 && mode[i+1] == '+') 18 | o_flags = O_RDWR; 19 | else 20 | o_flags = O_RDONLY; 21 | break; 22 | case 'w': 23 | if (i < mode_size-1 && mode[i+1] == '+') 24 | o_flags = O_RDWR; 25 | else 26 | o_flags = O_WRONLY; 27 | 28 | o_flags |= O_TRUNC | O_CREAT; 29 | break; 30 | case 'a': 31 | if (i < mode_size-1 && mode[i+1] == '+') 32 | o_flags = O_WRONLY | O_APPEND; 33 | else 34 | o_flags = O_RDWR | O_APPEND; 35 | 36 | o_flags |= O_CREAT; 37 | break; 38 | }; 39 | } 40 | 41 | FILE *stream = malloc(sizeof(FILE)); 42 | stream->fd = syscall(SYS_open, path, o_flags); 43 | stream->eof = 0; 44 | 45 | return stream; 46 | } 47 | 48 | int fclose(FILE *stream) 49 | { 50 | syscall(SYS_close, stream->fd); 51 | free(stream); 52 | return 0; 53 | } 54 | 55 | int feof(FILE *stream) 56 | { 57 | return stream->eof; 58 | } 59 | 60 | long int ftell(FILE *stream) 61 | { 62 | return stream->pos; 63 | } 64 | 65 | int fseek(FILE *stream, long int offset, int whence) 66 | { 67 | if (whence == SEEK_SET) { 68 | syscall(SYS_lseek, stream->fd, offset, whence); 69 | stream->pos = offset; 70 | stream->eof = 0; 71 | } else { 72 | return -1; 73 | } 74 | // tinyvm doesn't need other origins 75 | return 0; 76 | } 77 | 78 | int fgetc(FILE *stream) 79 | { 80 | char buf; 81 | int read = syscall(SYS_read, stream->fd, &buf, 1); 82 | if (!read) 83 | stream->eof = 1; 84 | else 85 | stream->pos += read; 86 | return (int)buf; 87 | } 88 | 89 | char *fgets(char *str, int num, FILE *stream) 90 | { 91 | return NULL; 92 | } 93 | 94 | int fputs(const char *str, FILE *stream) 95 | { 96 | return 0; 97 | } 98 | -------------------------------------------------------------------------------- /src/memory.c: -------------------------------------------------------------------------------- 1 | #include "stdlib.h" 2 | #include "stdint.h" 3 | #include "string.h" 4 | 5 | #include "brk.h" 6 | 7 | #define ALLOC_INFO_SIZE sizeof(struct alloc_info) 8 | #define info_from_alloc(alloc) (struct alloc_info *)(alloc - sizeof(struct alloc_info)) 9 | 10 | struct alloc_info { 11 | size_t size; 12 | }; 13 | 14 | void *malloc(size_t size) 15 | { 16 | void *mem = sbrk(size + ALLOC_INFO_SIZE) + ALLOC_INFO_SIZE; 17 | if ((long)mem == -1) 18 | return NULL; 19 | 20 | struct alloc_info *header = info_from_alloc(mem); 21 | header->size = size; 22 | 23 | return (void *)mem; 24 | } 25 | 26 | 27 | void *calloc(size_t count, size_t size) 28 | { 29 | void *mem = malloc(count * size); 30 | if ((long)mem == -1) 31 | return NULL; 32 | 33 | memset(mem, 0, (count * size)); 34 | return mem; 35 | } 36 | 37 | void *realloc(void *p, size_t size) 38 | { 39 | void *buf = malloc(size); 40 | if (p != NULL) { 41 | struct alloc_info *header = info_from_alloc(p); 42 | memcpy(buf, p, header->size); 43 | } 44 | return buf; 45 | } 46 | 47 | 48 | void free(void *p) 49 | { 50 | } 51 | 52 | void *memcpy(void *dest, const void *src, size_t n) 53 | { 54 | for (int i = 0; i < n; i++) 55 | ((uint8_t *)dest)[i] = ((uint8_t *)src)[i]; 56 | 57 | return dest; 58 | } 59 | 60 | 61 | void *memset(void *s, int c, size_t n) 62 | { 63 | for (int i = 0; i < n; i++) 64 | ((uint8_t *)s)[i] = (uint8_t)c; 65 | 66 | return s; 67 | } 68 | 69 | 70 | void *memmove(void *dest, const void *src, size_t n) 71 | { 72 | char *buf = malloc(n); 73 | memcpy(buf, src, n); 74 | memcpy(dest, buf, n); 75 | free(buf); 76 | 77 | return dest; 78 | } 79 | 80 | -------------------------------------------------------------------------------- /src/string.c: -------------------------------------------------------------------------------- 1 | #include "stdlib.h" 2 | #include "stdarg.h" 3 | #include "stdio.h" 4 | #include "string.h" 5 | 6 | #include "syscall.h" 7 | 8 | int vsprintf(char *str, const char *format, va_list arg) 9 | { 10 | strcpy(str, format); 11 | return 0; 12 | } 13 | 14 | int printf(const char *format, ...) 15 | { 16 | char str[1024] = {0}; 17 | 18 | va_list args; 19 | va_start(args, format); 20 | vsprintf(str, format, args); 21 | va_end(args); 22 | 23 | syscall(SYS_write, stdout.fd, str, strlen(str-1)); 24 | return 0; 25 | } 26 | 27 | char *strcat(char *dest, const char *src) 28 | { 29 | char *dp = dest, *sp = (char *)src; 30 | while (*(dp+1)) dp++; 31 | while (*(sp+1)) *(dp++) = *(sp++); 32 | return dest; 33 | } 34 | 35 | char *strchr(const char *s, int c) 36 | { 37 | while (*s != (char)c) 38 | if (!*s++) return NULL; 39 | 40 | return (char *)s; 41 | } 42 | 43 | int strcmp(const char *a, const char *b) 44 | { 45 | int pos = 0; 46 | while (a[pos] != '\0') { 47 | if (b[pos] == '\0') return 1; 48 | else if (a[pos] < b[pos]) return -1; 49 | else if (a[pos] > b[pos]) return 1; 50 | pos++; 51 | } 52 | 53 | return 0; 54 | } 55 | 56 | char *strcpy(char *dest, const char *src) 57 | { 58 | int pos = 0; 59 | 60 | while (1) { 61 | dest[pos] = src[pos]; 62 | if (!dest[pos]) break; 63 | pos++; 64 | } 65 | 66 | return dest; 67 | } 68 | 69 | char *strncpy(char *dest, const char *src, size_t n) 70 | { 71 | int pos = 0, pad = 0; 72 | 73 | while (pos < n) { 74 | if (!pad) { 75 | dest[pos] = src[pos]; 76 | } else { 77 | dest[pos] = 0; 78 | } 79 | 80 | if (!dest[pos]) 81 | pad = 1; 82 | } 83 | 84 | return dest; 85 | } 86 | 87 | size_t strlen(const char *str) 88 | { 89 | size_t n = 0; 90 | while (str[n+1]) n++; 91 | return n; 92 | } 93 | 94 | char *strstr(const char *haystack, const char *needle) 95 | { 96 | char *hpos = (char *)haystack; 97 | 98 | if (!*needle) return (char *)haystack; 99 | while (*hpos && strlen(hpos) >= strlen(needle)) { 100 | char *a = hpos, *b = (char *)needle; 101 | while (*a && *b && *a == *b) { 102 | a++; 103 | b++; 104 | } 105 | 106 | if (*b == '\0') 107 | return hpos; 108 | 109 | hpos++; 110 | } 111 | 112 | return NULL; 113 | } 114 | 115 | char *strpbrk(const char *haystack, const char *needle) 116 | { 117 | const char *c1, *c2; 118 | for (c1 = haystack; *c1 != '\0'; ++c1) { 119 | for (c2 = needle; *c2 != '\0'; ++c2) { 120 | if (*c1 == *c2) { 121 | return (char *)c1; 122 | } 123 | } 124 | } 125 | return NULL; 126 | } 127 | 128 | static char *tok = NULL; 129 | char *strtok(char *str, const char *delim) 130 | { 131 | char *start, *end; 132 | start = (str != NULL ? str : tok); 133 | if (start == NULL) 134 | return NULL; 135 | 136 | end = strpbrk(start, delim); 137 | if (end) { 138 | *end = '\0'; 139 | tok = end + 1; 140 | } else { 141 | tok = NULL; 142 | } 143 | return start; 144 | } 145 | 146 | int ipow(int base, int exp) 147 | { 148 | int res = 1; 149 | for (int i = 0; i < exp; i++) 150 | res *= exp; 151 | 152 | return res; 153 | } 154 | 155 | unsigned long int strtoul(const char *nptr, char **endptr, int base) 156 | { 157 | unsigned long pos = 0, val = 0; 158 | 159 | if (!base) 160 | base = 10; 161 | 162 | char charset_start = '0', charset_end = '9'; 163 | 164 | for (const char *p = nptr; *p; p++) { 165 | if (*p == ' ' || *p == '\t') 166 | continue; 167 | 168 | if (!(charset_start <= *p <= charset_end)) 169 | return 0; 170 | 171 | val += (*p - charset_start) * ipow(base, pos); 172 | ++pos; 173 | } 174 | 175 | return val; 176 | } 177 | 178 | 179 | -------------------------------------------------------------------------------- /src/syscall.h: -------------------------------------------------------------------------------- 1 | #ifndef _SYSCALL_H_ 2 | #define _SYSCALL_H_ 3 | 4 | #define SYS_read 0 5 | #define SYS_write 1 6 | #define SYS_open 2 7 | #define SYS_close 3 8 | #define SYS_lseek 8 9 | 10 | #define SYS_mmap 9 11 | #define SYS_mprotect 10 12 | 13 | #define SYS_brk 12 14 | 15 | #define SYS_exit 60 16 | 17 | #define SYS_fsync 74 18 | #define SYS_fdatasync 75 19 | 20 | extern long syscall(); 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /tests/brk/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | int main() 7 | { 8 | void *res; 9 | void *current_brk, *old_brk; 10 | const size_t offset = 0x1000; 11 | 12 | printf("Checking brk(0) returns -1\n"); 13 | assert(brk(0) == -1); 14 | printf("PASSED\n\n"); 15 | 16 | printf("Checking sbrk(0) returns current break\n"); 17 | current_brk = (void *)(size_t)sbrk(NULL); 18 | assert(current_brk); 19 | printf("PASSED\n\n"); 20 | 21 | printf("Checking brk(current_brk + offset) returns 0\n"); 22 | res = (void *)(size_t)brk((void *)(current_brk + offset)); 23 | assert(res == 0); 24 | printf("PASSED\n\n"); 25 | 26 | printf("Checking program break was increased\n"); 27 | old_brk = current_brk; 28 | current_brk = (void *)(size_t)sbrk(NULL); 29 | printf("old_brk: %p current_brk: %p\n", old_brk, current_brk); 30 | assert((void *)(old_brk + offset) == current_brk); 31 | printf("PASSED\n\n"); 32 | 33 | printf("Checking sbrk(offset) returned previous break\n"); 34 | old_brk = current_brk; 35 | res = (void *)(size_t)sbrk(offset); 36 | assert(res == old_brk); 37 | printf("PASSED\n\n"); 38 | 39 | printf("Checking sbrk(offset) increased program break\n"); 40 | current_brk = (void *)(size_t)sbrk(NULL); 41 | printf("old_brk: %p, current_brk: %p\n", old_brk, current_brk); 42 | assert(current_brk >= (old_brk + offset)); 43 | printf("PASSED\n\n"); 44 | 45 | return 0; 46 | } 47 | --------------------------------------------------------------------------------