diff --git a/build.py b/build.py index 359d093..67ed216 100755 --- a/build.py +++ b/build.py @@ -7,10 +7,6 @@ import os import sys import pyjson5 -dependencies = [ - 'nasm', - 'llvm-ar' -] # Parse command line arguments arg_parser = argparse.ArgumentParser('build.py') @@ -23,17 +19,25 @@ arg_parser.add_argument('--cflags', nargs='*', default=[], help='Pass additional args = arg_parser.parse_args() compiler = args.compiler -print('==> Performing basic checks') # Perform cleaning if required +def rm(path): + if os.path.exists(path): + os.remove(path) if args.clean: - shutil.rmtree('bin') - shutil.rmtree('lib') - os.remove(os.path.join('src', 'ciabatta.c')) - os.remove('a') + if os.path.exists('bin'): + shutil.rmtree('bin') + if os.path.exists('lib'): + shutil.rmtree('lib') + rm(os.path.join('src', 'ciabatta.c')) + rm('a') + rm('a.exe') + rm('a.ilk') + rm('a.pdb') sys.exit(0) # Check host OS +print('==> Performing basic checks') target = args.target if target is None: host_os = platform.system().lower() @@ -44,8 +48,31 @@ if target is None: target = host_os # Add compiler to dependencies +dependencies = [ + 'nasm', + 'llvm-ar' +] dependencies.append(args.compiler) + +# Figure out the flags +includes = ['include'] +cc = args.compiler +cc_defines = [] +cc_flags = ['-nostdlib'] +crt_file = 'crt.lib' +lib_file = 'cia.lib' +if args.mode == 'release': + cc_flags.append('-O2') + cc_defines.append('NDEBUG') +else: # 'debug' + cc_flags.append('-g') + cc_flags.append('-O0') + cc_defines.append('DEBUG') +if target != 'windows': + cc_flags.append('-fpic') +cc_defines.append(f'_CIA_OS_{target.upper()}') + # Check dependencies print('==> Checking dependencies') for dependency in dependencies: @@ -54,24 +81,6 @@ for dependency in dependencies: sys.exit(1) print(' -> Everything OK') -# Figure out the flags -includes = ['include'] -cc = '' -cc_defines = [] -cc_flags = ['-nostdlib'] -crt_file = 'crt.lib' -lib_file = 'cia.lib' - -cc = args.compiler -if args.mode == 'release': - cc_flags.append('-O2') - cc_defines.append('NDEBUG') -else: # 'debug' - cc_flags.append('-g') - cc_flags.append('-O0') - cc_defines.append('DEBUG') -cc_defines.append(f'_CIA_OS_{target.upper()}') - # Generate TinyRT headers for the target platform print(f"==> Generating TinyRT header for {target}") tinyrt_config_path = os.path.join('src', target, 'tinyrt.json') @@ -133,8 +142,8 @@ try: if platform_config is None: print(f" -> [ERROR] library config doesn't contain configuration for platform {target}") for include in platform_config['includes']: - include_path = os.path.join(platform_config['path'], include) - ciabatta_header.write(f'#include "{include_path}"\n') + include_path = platform_config['path'] + ciabatta_header.write(f'#include "{include_path}/{include}"\n') ciabatta_header.write(f'#include "{target}/tinyrt-iface.h"\n') ciabatta_header.write(f'#include \n') for tinyrt_source in platform_config['tinyrt']: @@ -193,7 +202,7 @@ def assemble(src, out): def compile(srcs, out, extra_flags = ''): flags = cc_flags_str + ' ' + extra_flags + ' '.join(args.cflags) inputs = ' '.join(map(quote, srcs)) - cmdline = f'{compiler} {flags} {inputs} -o {quote(out)}' + cmdline = f'{cc} {flags} {inputs} -o {quote(out)}' print(' >', cmdline) code = os.system(cmdline) if code != 0: @@ -207,20 +216,34 @@ def archive(srcs, out): if code != 0: sys.exit(code) - # Ciabatta build spec if not os.path.exists('lib'): os.mkdir('lib') if not os.path.exists('bin'): os.mkdir('bin') -p = os.path.join +def p(path): + l = path.split('/') + return os.path.join(*l) -assemble(p('src', 'linux', 'crt-entry.asm'), p('bin', 'crt-entry.o')) -compile([p('src', 'linux', 'crt-ctors.c')], p('bin', 'crt-ctors.o'), '-fpic -c') -compile([p('src', 'ciabatta.c')], p('bin', 'ciabatta.o'), '-fpic -c') -archive([p('bin', 'crt-ctors.o'), p('bin', 'crt-entry.o')], p('lib', crt_file)) -archive([p('bin', 'ciabatta.o'), ], p('lib', lib_file)) +cia_lib = p(f'lib/{lib_file}') +crt_lib = p(f'lib/{crt_file}') +ciabatta_c = p('src/ciabatta.c') +ciabatta_o = p('bin/ciabatta.o') + +if target == 'linux': + assemble(p('src/linux/crt-entry.asm'), p('bin/crt-entry.o')) + compile([p('src/linux/crt-ctors.c')], p('bin/crt-ctors.o'), '-c') + archive([p('bin/crt-ctors.o'), p('bin/crt-entry.o')], p('lib', crt_file)) +elif target == 'windows': + assemble(p('src/windows/chkstk.asm'), p('bin/chkstk.o')) + compile([p('src/windows/crt-entry.c')], p('bin/crt-entry.o'), '-c') + archive([p('bin/crt-entry.o')], crt_lib) +compile([ciabatta_c], ciabatta_o, '-c') +archive([ciabatta_o], cia_lib) if args.test: - compile([args.test, p('lib', lib_file), p('lib', crt_file)], 'a', '-pie') + if target == 'linux': + compile([args.test, cia_lib, crt_lib], 'a', '-pie') + elif target == 'windows': + compile([args.test, cia_lib, crt_lib], 'a.exe', '-lkernel32') diff --git a/include/cia-def.h b/include/cia-def.h index a4d2be9..0f81d92 100644 --- a/include/cia-def.h +++ b/include/cia-def.h @@ -1,9 +1,16 @@ #pragma once +// Since we're re-defining noreturn below, this would mess +// with __declspec(noreturn) in windows headers, which +// is hidden behind a define. Good thing, because now we +// can override that define over here. +#if defined(_CIA_OS_WINDOWS) + #define DECLSPEC_NORETURN __declspec("noreturn") +#endif + // Pre-C23 keyword macros and stddef #define static_assert _Static_assert -#define noreturn _Noreturn #define NULL ((void *)0) // Assert commonly-accepted platform-invariant sizes diff --git a/include/stdlib.h b/include/stdlib.h index a0a5ac5..97e1ac3 100644 --- a/include/stdlib.h +++ b/include/stdlib.h @@ -5,7 +5,7 @@ int atexit(void (*func)(void)); int at_quick_exit(void (*func)(void)); -noreturn void abort(void); -noreturn void exit(int code); -noreturn void _Exit(int code); -noreturn void quick_exit(int code); \ No newline at end of file +[[noreturn]] void abort(void); +[[noreturn]] void exit(int code); +[[noreturn]] void _Exit(int code); +[[noreturn]] void quick_exit(int code); \ No newline at end of file diff --git a/include/tinyrt.h b/include/tinyrt.h index 6099e32..5f22601 100644 --- a/include/tinyrt.h +++ b/include/tinyrt.h @@ -41,7 +41,7 @@ static _RT_Status _rt_deinit(); // Program API #if _RT_API_PROGRAM == 1 - static noreturn void _rt_program_exit(int code); + [[noreturn]] static void _rt_program_exit(int code); #endif #if _RT_API_ENVIRONMENT == 1 diff --git a/src/impl/stdlib-program/program.c b/src/impl/stdlib-program/program.c index 97aa470..8bfe326 100644 --- a/src/impl/stdlib-program/program.c +++ b/src/impl/stdlib-program/program.c @@ -25,12 +25,13 @@ int at_quick_exit(void (*func)(void)) { return 0; } -noreturn void abort(void) { +[[noreturn]] void abort(void) { // TODO: Ideally do a debug trap if the process is being debugged - _Exit(-1); + _rt_program_exit(1); + __builtin_unreachable(); } -noreturn void exit(int code) { +[[noreturn]] void exit(int code) { for(u64 i = n_atexit_handlers-1; i-- != 0; ) { void (*handler)(void) = atexit_handlers[i]; handler(); @@ -38,17 +39,20 @@ noreturn void exit(int code) { // TODO(bumbread): flush all the unflushed file streams // TODO(bumbread): close all file streams and delete temporary files _rt_program_exit(code); + __builtin_unreachable(); } -noreturn void _Exit(int code) { +[[noreturn]] void _Exit(int code) { _rt_program_exit(code); + __builtin_unreachable(); } -noreturn void quick_exit(int code) { +[[noreturn]] void quick_exit(int code) { for(u64 i = n_at_quick_exit_handlers-1; i-- != 0; ) { void (*handler)(void) = at_quick_exit_handlers[i]; handler(); } _rt_program_exit(code); + __builtin_unreachable(); } diff --git a/src/library.json b/src/library.json index ac2fab8..6c459a8 100644 --- a/src/library.json +++ b/src/library.json @@ -44,8 +44,13 @@ platforms: [ { name: "windows", path: "windows", - includes: [], - tinyrt: [], + includes: [ + "windows.c" + ], + tinyrt: [ + "tinyrt.c", + "cia-init.c" + ], }, ], diff --git a/src/linux/tinyrt.c b/src/linux/tinyrt.c index ec0b7ca..9916061 100644 --- a/src/linux/tinyrt.c +++ b/src/linux/tinyrt.c @@ -1,9 +1,6 @@ // See src/tinyrt.h file for the interface this file implements -static _RT_File _rt_file_stdin; -static _RT_File _rt_file_stderr; - static _RT_Status _rt_file_std_handles_init() { _rt_file_stdin.fd = 0; _rt_file_stdin.flags = _RT_FILE_READ; @@ -68,6 +65,6 @@ static _RT_Status _rt_file_close(_RT_File *file) { return _RT_STATUS_OK; } -static noreturn void _rt_program_exit(int code) { +[[noreturn]] static void _rt_program_exit(int code) { syscall_exit(code); } diff --git a/src/windows/chkstk.asm b/src/windows/chkstk.asm new file mode 100644 index 0000000..d3dfb6d --- /dev/null +++ b/src/windows/chkstk.asm @@ -0,0 +1,26 @@ +bits 64 + +segment .text +global __chkstk +__chkstk: + sub rsp, 0x10 + mov [rsp], r10 + mov [rsp+0x8], r11 + xor r11, r11 + lea r10, [rsp+0x18] + sub r10, rax + cmovb r10, r11 + mov r11, gs:[0x10] + cmp r10, r11 + jnb .end + and r10w, 0xf000 +.loop: + lea r11, [r11-0x1000] + mov byte [r11], 0x0 + cmp r10, r11 + jnz .loop +.end: + mov r10, [rsp] + mov r11, [rsp+0x8] + add rsp, 0x10 + ret \ No newline at end of file diff --git a/src/windows/cia-init.c b/src/windows/cia-init.c new file mode 100644 index 0000000..da0f549 --- /dev/null +++ b/src/windows/cia-init.c @@ -0,0 +1,6 @@ + +static void _fileapi_init(); + +void _cia_init() { + _fileapi_init(); +} diff --git a/src/windows/crt-entry.c b/src/windows/crt-entry.c new file mode 100644 index 0000000..a3ae248 --- /dev/null +++ b/src/windows/crt-entry.c @@ -0,0 +1,62 @@ + +#include +#include "windows.c" + +extern int main(int argc, char **argv, char **envp); +extern void _cia_init(); + +void mainCRTStartup() { + _cia_init(); + __security_init_cookie(); + main(0, NULL, NULL); + ExitProcess(0); +} + +void wmainCRTStartup() { + ExitProcess(0); +} + +void _WinMainCRTStartup() { + ExitProcess(0); +} + +void wWinMainCRTStartup() { + ExitProcess(0); +} + +BOOL _DllMainCRTStartup(HINSTANCE instance, DWORD reason, void *_reserved) { + switch(reason) { + case DLL_PROCESS_ATTACH: { + // Initialize once for each new process. + // Return FALSE to fail DLL load. + } break; + case DLL_THREAD_ATTACH: { + // Do thread-specific initialization. + } break; + case DLL_THREAD_DETACH: { + // Do thread-specific cleanup. + } break; + case DLL_PROCESS_DETACH: { + // Perform any necessary cleanup. + } break; + } + return TRUE; +} + +u64 __security_cookie; + +void __security_init_cookie() { + // They say it's a random number so I generated + // one using numbergenerator.org + __security_cookie = 0xb26e04cc62ba48aULL; +} + +void __security_check_cookie(u64 retrieved) { + if(__security_cookie != retrieved) { + char buf[] = "Buffer overrun detected!\n"; + HANDLE stdout = GetStdHandle(STD_OUTPUT_HANDLE); + WriteFile(stdout, buf, sizeof buf, NULL, NULL); + // TODO: abort-like behaviour here + ExitProcess(1); + } +} diff --git a/src/windows/tinyrt.c b/src/windows/tinyrt.c new file mode 100644 index 0000000..adc7680 --- /dev/null +++ b/src/windows/tinyrt.c @@ -0,0 +1,76 @@ + +// See src/tinyrt.h file for the interface this file implements + +static _RT_Status _rt_file_std_handles_init() { + _rt_file_stdin.handle = GetStdHandle(STD_INPUT_HANDLE); + _rt_file_stdin.flags = _RT_FILE_READ; + _rt_file_stdout.handle = GetStdHandle(STD_OUTPUT_HANDLE); + _rt_file_stdout.flags = _RT_FILE_WRITE; + _rt_file_stderr.handle = GetStdHandle(STD_ERROR_HANDLE); + _rt_file_stdout.flags = _RT_FILE_WRITE; + return _RT_STATUS_OK; +} + +static _RT_Status _rt_file_open(_RT_File *file, char const *name, int _rt_flags) { + u32 access = 0; + if(_rt_flags & _RT_FILE_READ) access |= GENERIC_READ; + if(_rt_flags & _RT_FILE_WRITE) access |= GENERIC_WRITE; + u32 share = 0; + if((_rt_flags & _RT_FILE_READ) == 0) share |= FILE_SHARE_READ; + u32 creation = 0; + if(_rt_flags & _RT_FILE_TRUNCATE) { + creation = TRUNCATE_EXISTING; + } + else if(_rt_flags & _RT_FILE_CREATE) { + if(_rt_flags & _RT_FILE_EXCLUSIVE) creation = CREATE_ALWAYS; + else creation = CREATE_NEW; + } + else { + if(_rt_flags & _RT_FILE_EXCLUSIVE) creation = OPEN_ALWAYS; + else creation = OPEN_EXISTING; + } + HANDLE handle = CreateFileA(name, access, share, NULL, creation, FILE_ATTRIBUTE_NORMAL, NULL); + if(file == INVALID_HANDLE_VALUE) { + // ERROR_FILE_NOT_FOUND + // ERROR_PATH_NOT_FOUND + // ERROR_ACCESS_DENIED + // ERROR_NOT_ENOUGH_MEMORY + return _RT_STATUS_FILE_IO_ERROR; + + } + file->handle = handle; + file->flags = _rt_flags; + return _RT_STATUS_OK; +} + +static _RT_Status _rt_file_read(u64 size, void *buffer, _RT_File *from, u64 *out_bytes_read) { + *out_bytes_read = 0; + BOOL ok = ReadFile(from->handle, buffer, size, (unsigned long *)out_bytes_read, NULL); + if(!ok) { + return _RT_STATUS_FILE_IO_ERROR; + } + return _RT_STATUS_OK; +} + +static _RT_Status _rt_file_write(_RT_File *to, u64 size, void *buffer, u64 *out_bytes_written) { + *out_bytes_written = 0; + BOOL ok = WriteFile(to->handle, buffer, size, (unsigned long *)out_bytes_written, NULL); + if(!ok) { + return _RT_STATUS_FILE_IO_ERROR; + } + return _RT_STATUS_OK; +} + +static _RT_Status _rt_file_close(_RT_File *file) { + BOOL ok = CloseHandle(file->handle); + if(!ok) { + return _RT_STATUS_FILE_BAD_FILE; + } + return _RT_STATUS_OK; +} + +[[noreturn]] static void _rt_program_exit(int code) { + ExitProcess(code); + __builtin_unreachable(); +} + diff --git a/src/windows/tinyrt.json b/src/windows/tinyrt.json new file mode 100644 index 0000000..f82eb3f --- /dev/null +++ b/src/windows/tinyrt.json @@ -0,0 +1,12 @@ +{ + +// This file contains TinyRT API subsets that are exported +// for this operating system, one name per line. This file +// is used in a build script to generate tinyrt_macro file +// and to resolve higher-level API dependencies + +rt_api_file: true, +rt_api_program: true, +rt_api_shell: false, + +} \ No newline at end of file diff --git a/src/windows/tinyrt.mf b/src/windows/tinyrt.mf deleted file mode 100644 index e69de29..0000000 diff --git a/src/windows/windows.c b/src/windows/windows.c new file mode 100644 index 0000000..0517779 --- /dev/null +++ b/src/windows/windows.c @@ -0,0 +1,6 @@ + +#define NOGDI +#define NOUSER +#define NOMINMAX +#define WIN32_LEAN_AND_MEAN +#include diff --git a/tests/empty.c b/tests/empty.c index 3de02c0..3f4d186 100644 --- a/tests/empty.c +++ b/tests/empty.c @@ -1,9 +1,4 @@ - -#include -#include - -int main(int argc, char **argv, char **envp) { - char string[] = "Hello, world!\n"; - fwrite(string, 1, sizeof string-1, stdout); - return 0; -} + +int main(int argc, char **argv) { + return 0; +} diff --git a/tests/hello.c b/tests/hello.c new file mode 100644 index 0000000..3de02c0 --- /dev/null +++ b/tests/hello.c @@ -0,0 +1,9 @@ + +#include +#include + +int main(int argc, char **argv, char **envp) { + char string[] = "Hello, world!\n"; + fwrite(string, 1, sizeof string-1, stdout); + return 0; +}