Make terminal ui binaries work well everywhere

Here's some screenshots of an emulator tui program that was compiled on
Linux, then scp'd it to Windows, Mac, and FreeBSD.

https://justine.storage.googleapis.com/blinkenlights-cmdexe.png
https://justine.storage.googleapis.com/blinkenlights-imac.png
https://justine.storage.googleapis.com/blinkenlights-freebsd.png
https://justine.storage.googleapis.com/blinkenlights-lisp.png

How is this even possible that we have a nontrivial ui binary that just
works on Mac, Windows, Linux, and BSD? Surely a first ever achievement.

Fixed many bugs. Bootstrapped John McCarthy's metacircular evaluator on
bare metal in half the size of Altair BASIC (about 2.5kb) and ran it in
emulator for fun and profit.
This commit is contained in:
Justine Tunney 2020-10-10 21:18:53 -07:00
parent 680daf1210
commit 9e3e985ae5
276 changed files with 7026 additions and 3790 deletions

View file

@ -25,9 +25,6 @@
/ Terminates process, ignoring destructors and atexit() handlers.
/
/ Normally exit() or quick_exit() is better. This won't even flush
/ stdio streams. Sometimes that makes sense, like after fork().
/
/ @param edi is exit code ∈ [0,256)
/ @note _exit() is same thing
/ @asyncsignalsafe

View file

@ -25,7 +25,7 @@
* Frees symbol table.
* @return 0 on success or -1 on system error
*/
int closesymboltable(struct SymbolTable **table) {
int CloseSymbolTable(struct SymbolTable **table) {
int rc;
struct SymbolTable *t;
rc = 0;

View file

@ -17,6 +17,7 @@
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA
*/
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/macros.h"
#include "libc/nt/memory.h"

View file

@ -57,42 +57,14 @@ _executive:
ud2
#ifdef __PG__
/ Enables plaintext function tracing if --ftrace flag passed.
/
/ The --ftrace CLI arg is removed before main() is called. This
/ code is intended for diagnostic purposes and assumes binaries
/ are trustworthy and stack isn't corrupted. Logging plain text
/ allows program structure to easily be visualized and hotspots
/ identified w/ sed | sort | uniq -c | sort. A compressed trace
/ can be made by appending --ftrace 2>&1 | gzip -4 >trace.gz to
/ the CLI arguments. Have fun.
/
/ @see libc/runtime/ftrace.greg.c
/ @see libc/crt/crt.S
.init.start 800,_init_ftrace
push %rdi
push %rsi
xor %edx,%edx
loadstr "--ftrace",di
xor %ecx,%ecx
0: inc %ecx
mov (%r13,%rcx,8),%rsi
test %edx,%edx
jz 1f
mov %rsi,-8(%r13,%rcx,8)
1: test %rsi,%rsi
jz 2f
test %edx,%edx
jnz 0b
call tinystrcmp
test %eax,%eax
setz %dl
jmp 0b
2: sub %rdx,%r12
test %edx,%edx
jz 2f
mov %r12d,%edi
mov %r13,%rsi
call ftrace_init
2: pop %rsi
mov %eax,%r12d
pop %rsi
pop %rdi
.init.end 800,_init_ftrace
#endif /* -pg */
#endif

View file

@ -28,25 +28,25 @@ struct FindComBinary {
char buf[PATH_MAX];
};
static struct FindComBinary findcombinary_;
static struct FindComBinary g_findcombinary;
/**
* Returns path of binary without debug information, or null.
*
* @return path to non-debug binary, or -1 w/ errno
*/
const char *findcombinary(void) {
const char *FindComBinary(void) {
size_t len;
const char *p;
if (!findcombinary_.once) {
findcombinary_.once = true;
if (!g_findcombinary.once) {
g_findcombinary.once = true;
if ((p = (const char *)getauxval(AT_EXECFN)) &&
(len = strlen(p)) < ARRAYLEN(findcombinary_.buf)) {
findcombinary_.res = memcpy(findcombinary_.buf, p, len + 1);
if (len > 4 && memcmp(&findcombinary_.buf[len - 4], ".dbg", 4) == 0) {
findcombinary_.buf[len - 4] = '\0';
(len = strlen(p)) < ARRAYLEN(g_findcombinary.buf)) {
g_findcombinary.res = memcpy(g_findcombinary.buf, p, len + 1);
if (len > 4 && memcmp(&g_findcombinary.buf[len - 4], ".dbg", 4) == 0) {
g_findcombinary.buf[len - 4] = '\0';
}
}
}
return findcombinary_.res;
return g_findcombinary.res;
}

View file

@ -31,7 +31,7 @@
*
* @return path to debug binary, or -1 w/ errno
*/
const char *finddebugbinary(void) {
const char *FindDebugBinary(void) {
static char buf[PATH_MAX];
if (buf[0]) return &buf[0];
const char *const trybins[] = {program_invocation_name,

View file

@ -24,6 +24,7 @@
#include "libc/calls/internal.h"
#include "libc/calls/struct/sigset.h"
#include "libc/dce.h"
#include "libc/intrin/repmovsb.h"
#include "libc/macros.h"
#include "libc/nexgen32e/stackframe.h"
#include "libc/nt/files.h"
@ -50,7 +51,7 @@ static char g_buf[512];
static const char *g_lastsymbol;
static struct SymbolTable *g_symbols;
forceinline int getnestinglevel(struct StackFrame *frame) {
forceinline int GetNestingLevel(struct StackFrame *frame) {
int nesting = -2;
while (frame) {
++nesting;
@ -81,7 +82,7 @@ privileged interruptfn void ftrace_hook(void) {
frame->addr - g_symbols->addr_base)]
.name_rva];
if (symbol != g_lastsymbol &&
(nesting = getnestinglevel(frame)) * 2 < ARRAYLEN(g_buf) - 3) {
(nesting = GetNestingLevel(frame)) * 2 < ARRAYLEN(g_buf) - 4) {
i = 2;
j = 0;
while (nesting--) {
@ -91,6 +92,7 @@ privileged interruptfn void ftrace_hook(void) {
while (i < ARRAYLEN(g_buf) - 2 && symbol[j]) {
g_buf[i++] = symbol[j++];
}
g_buf[i++] = '\r';
g_buf[i++] = '\n';
__print(g_buf, i);
}
@ -100,13 +102,42 @@ privileged interruptfn void ftrace_hook(void) {
}
/**
* Installs plaintext function tracer. Do not call.
* Enables plaintext function tracing if --ftrace flag passed.
*
* The --ftrace CLI arg is removed before main() is called. This
* code is intended for diagnostic purposes and assumes binaries
* are trustworthy and stack isn't corrupted. Logging plain text
* allows program structure to easily be visualized and hotspots
* identified w/ sed | sort | uniq -c | sort. A compressed trace
* can be made by appending --ftrace 2>&1 | gzip -4 >trace.gz to
* the CLI arguments. Have fun.
*
* @see libc/runtime/_init.S for documentation
*/
textstartup void ftrace_init(void) {
g_buf[0] = '+';
g_buf[1] = ' ';
if ((g_symbols = opensymboltable(finddebugbinary()))) {
__hook(ftrace_hook, g_symbols);
textstartup int ftrace_init(int argc, char *argv[]) {
int i;
bool foundflag;
foundflag = false;
for (i = 1; i <= argc; ++i) {
if (!foundflag) {
if (argv[i]) {
if (strcmp(argv[i], "--ftrace") == 0) {
foundflag = true;
} else if (strcmp(argv[i], "----ftrace") == 0) {
strcpy(argv[i], "--ftrace");
}
}
} else {
argv[i - 1] = argv[i];
}
}
if (foundflag) {
--argc;
g_buf[0] = '+';
g_buf[1] = ' ';
if ((g_symbols = OpenSymbolTable(FindDebugBinary()))) {
__hook(ftrace_hook, g_symbols);
}
}
return argc;
}

View file

@ -25,6 +25,7 @@
#include "libc/str/appendchar.h"
#include "libc/str/str.h"
#include "libc/str/tpenc.h"
#include "libc/str/utf16.h"
/* TODO(jart): Make early-stage data structures happen. */
#undef isspace
@ -39,11 +40,7 @@ struct DosArgv {
wint_t wc;
};
static textwindows void decodedosargv(struct DosArgv *st) {
st->s += getutf16(st->s, &st->wc);
}
static textwindows void appenddosargv(struct DosArgv *st, wint_t wc) {
static textwindows void AppendDosArgv(struct DosArgv *st, wint_t wc) {
AppendChar(&st->p, st->pe, wc);
}
@ -66,7 +63,7 @@ static textwindows void appenddosargv(struct DosArgv *st, wint_t wc) {
* @see libc/runtime/ntspawn.c
* @note kudos to Simon Tatham for figuring out quoting behavior
*/
textwindows int getdosargv(const char16_t *cmdline, char *buf, size_t size,
textwindows int GetDosArgv(const char16_t *cmdline, char *buf, size_t size,
char **argv, size_t max) {
bool inquote;
size_t i, argc, slashes, quotes;
@ -75,9 +72,9 @@ textwindows int getdosargv(const char16_t *cmdline, char *buf, size_t size,
st.p = buf;
st.pe = buf + size;
argc = 0;
decodedosargv(&st);
st.wc = DecodeNtsUtf16(&st.s);
while (st.wc) {
while (st.wc && iswspace(st.wc)) decodedosargv(&st);
while (st.wc && iswspace(st.wc)) st.wc = DecodeNtsUtf16(&st.s);
if (!st.wc) break;
if (++argc < max) {
argv[argc - 1] = st.p < st.pe ? st.p : NULL;
@ -88,27 +85,27 @@ textwindows int getdosargv(const char16_t *cmdline, char *buf, size_t size,
if (st.wc == '"' || st.wc == '\\') {
slashes = 0;
quotes = 0;
while (st.wc == '\\') decodedosargv(&st), slashes++;
while (st.wc == '"') decodedosargv(&st), quotes++;
while (st.wc == '\\') st.wc = DecodeNtsUtf16(&st.s), slashes++;
while (st.wc == '"') st.wc = DecodeNtsUtf16(&st.s), quotes++;
if (!quotes) {
while (slashes--) appenddosargv(&st, '\\');
while (slashes--) AppendDosArgv(&st, '\\');
} else {
while (slashes >= 2) appenddosargv(&st, '\\'), slashes -= 2;
if (slashes) appenddosargv(&st, '"'), quotes--;
while (slashes >= 2) AppendDosArgv(&st, '\\'), slashes -= 2;
if (slashes) AppendDosArgv(&st, '"'), quotes--;
if (quotes > 0) {
if (!inquote) quotes--;
for (i = 3; i <= quotes + 1; i += 3) appenddosargv(&st, '"');
for (i = 3; i <= quotes + 1; i += 3) AppendDosArgv(&st, '"');
inquote = (quotes % 3 == 0);
}
}
} else {
appenddosargv(&st, st.wc);
decodedosargv(&st);
AppendDosArgv(&st, st.wc);
st.wc = DecodeNtsUtf16(&st.s);
}
}
appenddosargv(&st, '\0');
AppendDosArgv(&st, '\0');
}
appenddosargv(&st, '\0');
AppendDosArgv(&st, '\0');
if (size) buf[min(st.p - buf, size - 1)] = '\0';
if (max) argv[min(argc, max - 1)] = NULL;
return argc;

View file

@ -22,6 +22,7 @@
#ifndef __STRICT_ANSI__
#include "libc/bits/safemacros.h"
#include "libc/str/appendchar.h"
#include "libc/str/str.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
/**
@ -34,25 +35,28 @@
* @param max is the pointer count capacity of envp
* @return number of variables decoded, excluding NULL-terminator
*/
static inline int getdosenviron(const char16_t *env, char *buf, size_t size,
static inline int GetDosEnviron(const char16_t *env, char *buf, size_t size,
char **envp, size_t max) {
const char16_t *s = env;
size_t envc = 0;
wint_t wc;
size_t envc;
char *p, *pe;
bool endstring;
const char16_t *s;
s = env;
envc = 0;
if (size) {
wint_t wc;
char *p = buf;
char *pe = buf + size - 1;
p = buf;
pe = buf + size - 1;
if (p < pe) {
s += getutf16(s, &wc);
wc = DecodeNtsUtf16(&s);
while (wc) {
if (++envc < max) {
envp[envc - 1] = p < pe ? p : NULL;
}
bool endstring;
do {
AppendChar(&p, pe, wc);
endstring = !wc;
s += getutf16(s, &wc);
wc = DecodeNtsUtf16(&s);
} while (!endstring);
buf[min(p - buf, size - 2)] = u'\0';
}

View file

@ -48,6 +48,7 @@ privileged interruptfn void GOT_HERE(long num) {
msg[len++] = 'e';
msg[len++] = ' ';
len += int64toarray_radix10(num, &msg[len]);
msg[len++] = '\r';
msg[len++] = '\n';
msg[len] = '\0';
__print(msg, len);

View file

@ -25,7 +25,7 @@ void *__cxa_finalize(void *) hidden;
void _executive(int, char **, char **, long (*)[2]) hidden noreturn;
void __stack_chk_fail(void) noreturn relegated;
void __stack_chk_fail_local(void) noreturn relegated hidden;
int getdosargv(const char16_t *, char *, size_t, char **, size_t) hidden;
int GetDosArgv(const char16_t *, char *, size_t, char **, size_t) hidden;
forceinline void AssertNeverCalledWhileTerminating(void) {
if (!NoDebug() && (g_runstate & RUNSTATE_TERMINATE)) {

View file

@ -17,6 +17,7 @@
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA
*/
#include "libc/macros.h"
#include "libc/runtime/memtrack.h"
#include "libc/runtime/runtime.h"
@ -28,12 +29,13 @@
*/
bool isheap(void *p) {
int x, i;
#if 1
register intptr_t rsp asm("rsp");
if ((intptr_t)p >= rsp) return false;
#endif
if ((intptr_t)p <= (intptr_t)_end) return false;
x = (intptr_t)p >> 16;
i = FindMemoryInterval(&_mmi, x);
return i < _mmi.i && x >= _mmi.p[i].x && x <= _mmi.p[i].y;
register uintptr_t rsp asm("rsp");
if (ROUNDDOWN(rsp, STACKSIZE) == ROUNDDOWN((intptr_t)p, STACKSIZE)) {
return false;
} else {
if ((intptr_t)p <= (intptr_t)_end) return false;
x = (intptr_t)p >> 16;
i = FindMemoryInterval(&_mmi, x);
return i < _mmi.i && x >= _mmi.p[i].x && x <= _mmi.p[i].y;
}
}

View file

@ -24,5 +24,5 @@
void *mapanon(size_t mapsize) {
return mmap(NULL, mapsize, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_NONBLOCK | MAP_ANONYMOUS, -1, 0);
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
}

View file

@ -1,10 +1,12 @@
#ifndef COSMOPOLITAN_LIBC_RUNTIME_MEMTRACK_H_
#define COSMOPOLITAN_LIBC_RUNTIME_MEMTRACK_H_
#include "libc/macros.h"
#include "libc/runtime/runtime.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
#define kAutomapStart 0x0000100000000000
#define kAutomapSize 0x0000100000000000
#define kAutomapStart 0x0000100080000000 // asan can't spread its poison here
#define kAutomapSize 0x00000fff80000000
#define kFixedmapStart 0x0000200000000000
struct MemoryIntervals {
@ -12,8 +14,8 @@ struct MemoryIntervals {
struct MemoryInterval {
int x;
int y;
} p[64];
long h[64];
} p[128];
long h[128];
};
extern struct MemoryIntervals _mmi;

View file

@ -30,9 +30,9 @@
/**
* Maps debuggable binary into memory and indexes symbol addresses.
*
* @return object freeable with closesymboltable(), or NULL w/ errno
* @return object freeable with CloseSymbolTable(), or NULL w/ errno
*/
struct SymbolTable *opensymboltable(const char *filename) {
struct SymbolTable *OpenSymbolTable(const char *filename) {
unsigned i, j;
struct SymbolTable *t;
const Elf64_Sym *symtab, *sym;
@ -56,7 +56,7 @@ struct SymbolTable *opensymboltable(const char *filename) {
t->count = j;
carsort1000(t->count, (void *)t->symbols);
} else {
closesymboltable(&t);
CloseSymbolTable(&t);
}
return t == MAP_FAILED ? NULL : t;
}

View file

@ -28,18 +28,18 @@ void PrintMemoryIntervals(int fd, const struct MemoryIntervals *mm) {
int i, frames, maptally, gaptally;
maptally = 0;
gaptally = 0;
(dprintf)(fd, "%s%zd%s\n", "mm->i == ", mm->i, ";");
(dprintf)(fd, "%s%zd%s\r\n", "mm->i == ", mm->i, ";");
for (i = 0; i < mm->i; ++i) {
if (i && mm->p[i].x != mm->p[i - 1].y + 1) {
frames = mm->p[i].x - mm->p[i - 1].y - 1;
gaptally += frames;
(dprintf)(fd, "%s%,zd%s\n", "/* ", frames, " */");
(dprintf)(fd, "%s%,zd%s\r\n", "/* ", frames, " */");
}
frames = mm->p[i].y + 1 - mm->p[i].x;
maptally += frames;
(dprintf)(fd, "%s%3u%s0x%08x,0x%08x%s%,zd%s\n", "mm->p[", i, "]=={",
(dprintf)(fd, "%s%3u%s0x%08x,0x%08x%s%,zd%s\r\n", "mm->p[", i, "]=={",
mm->p[i].x, mm->p[i].y, "}; /* ", frames, " */");
}
(dprintf)(fd, "%s%,zd%s%,zd%s\n\n", "/* ", maptally, " frames mapped w/ ",
(dprintf)(fd, "%s%,zd%s%,zd%s\r\n\r\n", "/* ", maptally, " frames mapped w/ ",
gaptally, " frames gapped */");
}

View file

@ -1,34 +0,0 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2020 Justine Alexandra Roberts Tunney
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA
*/
#include "libc/bits/weaken.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/stdio.h"
/**
* Terminates process normally, running minimal cleanup.
* @noreturn
*/
noreturn textexit void quick_exit(int rc) {
if (weaken(fflush)) {
if (weaken(stdout)) weaken(fflush)(*weaken(stdout));
if (weaken(stderr)) weaken(fflush)(*weaken(stderr));
}
_Exit(rc);
}

View file

@ -41,7 +41,6 @@ void *mapanon(size_t) vallocesque attributeallocsize((1));
int setjmp(jmp_buf) libcesque returnstwice paramsnonnull();
void longjmp(jmp_buf, int) libcesque noreturn paramsnonnull();
void exit(int) noreturn;
void quick_exit(int) noreturn;
void _exit(int) libcesque noreturn;
void _Exit(int) libcesque noreturn;
long _setstack(void *, void *, ...);

View file

@ -1,27 +1,8 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=8 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2020 Justine Alexandra Roberts Tunney
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA
*/
#ifndef COSMOPOLITAN_LIBC_SYMBOLS_H_
#define COSMOPOLITAN_LIBC_SYMBOLS_H_
#if !(__ASSEMBLER__ + __LINKER__ + 0)
#include "libc/elf/elf.h"
#include "libc/runtime/ezmap.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
struct Symbol {
@ -46,13 +27,11 @@ struct SymbolTable {
struct Symbol symbols[];
};
struct SymbolTable *getsymboltable(void);
const char *findcombinary(void);
const char *finddebugbinary(void);
struct SymbolTable *opensymboltable(const char *) nodiscard;
int closesymboltable(struct SymbolTable **);
const struct Symbol *bisectsymbol(struct SymbolTable *, intptr_t, int64_t *);
const char *getsymbolname(struct SymbolTable *, const struct Symbol *);
struct SymbolTable *GetSymbolTable(void);
const char *FindComBinary(void);
const char *FindDebugBinary(void);
struct SymbolTable *OpenSymbolTable(const char *) nodiscard;
int CloseSymbolTable(struct SymbolTable **);
void __hook(void (*)(void), struct SymbolTable *);
COSMOPOLITAN_C_END_

View file

@ -38,8 +38,8 @@
static struct CmdExe {
bool result;
struct OldNtConsole {
unsigned codepage;
unsigned mode;
uint32_t codepage;
uint32_t mode;
int64_t handle;
} oldin, oldout;
} g_cmdexe;
@ -82,6 +82,8 @@ static textwindows void NormalizeCmdExe(void) {
SetConsoleCP(kNtCpUtf8);
GetConsoleMode(handle, &g_cmdexe.oldin.mode);
SetConsoleMode(handle, g_cmdexe.oldin.mode | kNtEnableProcessedInput |
kNtEnableEchoInput | kNtEnableLineInput |
kNtEnableWindowInput |
kNtEnableVirtualTerminalInput);
}
if (GetFileType((handle = hstdout)) == kNtFileTypeChar ||
@ -92,7 +94,10 @@ static textwindows void NormalizeCmdExe(void) {
SetConsoleOutputCP(kNtCpUtf8);
GetConsoleMode(handle, &g_cmdexe.oldout.mode);
SetConsoleMode(handle, g_cmdexe.oldout.mode | kNtEnableProcessedOutput |
kNtEnableVirtualTerminalProcessing);
kNtEnableWrapAtEolOutput |
(NtGetVersion() >= kNtVersionWindows10
? kNtEnableVirtualTerminalProcessing
: 0));
}
}
}
@ -136,11 +141,11 @@ textwindows int WinMain(void *hInstance, void *hPrevInstance,
*(/*unconst*/ int *)&hostos = WINDOWS;
cmd16 = GetCommandLine();
env16 = GetEnvironmentStrings();
count = getdosargv(cmd16, argblock, ARG_MAX, argarray, 512);
count = GetDosArgv(cmd16, argblock, ARG_MAX, argarray, 512);
for (i = 0; argarray[0][i]; ++i) {
if (argarray[0][i] == '\\') argarray[0][i] = '/';
}
getdosenviron(env16, envblock, ENV_MAX, envarray, 512);
GetDosEnviron(env16, envblock, ENV_MAX, envarray, 512);
FreeEnvironmentStrings(env16);
_executive(count, argarray, envarray, auxarray);
}