mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-01-31 19:43:32 +00:00
f4f4caab0e
I wanted a tiny scriptable meltdown proof way to run userspace programs and visualize how program execution impacts memory. It helps to explain how things like Actually Portable Executable works. It can show you how the GCC generated code is going about manipulating matrices and more. I didn't feel fully comfortable with Qemu and Bochs because I'm not smart enough to understand them. I wanted something like gVisor but with much stronger levels of assurances. I wanted a single binary that'll run, on all major operating systems with an embedded GPL barrier ZIP filesystem that is tiny enough to transpile to JavaScript and run in browsers too. https://justine.storage.googleapis.com/emulator625.mp4
317 lines
10 KiB
C
317 lines
10 KiB
C
/*-*- 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/alg/alg.h"
|
|
#include "libc/alg/arraylist.h"
|
|
#include "libc/alg/arraylist2.h"
|
|
#include "libc/alg/bisectcarleft.h"
|
|
#include "libc/assert.h"
|
|
#include "libc/bits/bits.h"
|
|
#include "libc/bits/safemacros.h"
|
|
#include "libc/calls/calls.h"
|
|
#include "libc/errno.h"
|
|
#include "libc/fmt/fmt.h"
|
|
#include "libc/log/check.h"
|
|
#include "libc/log/log.h"
|
|
#include "libc/macros.h"
|
|
#include "libc/nexgen32e/crc32.h"
|
|
#include "libc/runtime/ezmap.h"
|
|
#include "libc/runtime/gc.h"
|
|
#include "libc/runtime/runtime.h"
|
|
#include "libc/stdio/stdio.h"
|
|
#include "libc/str/knuthmultiplicativehash.h"
|
|
#include "libc/str/str.h"
|
|
#include "libc/sysv/consts/madv.h"
|
|
#include "libc/sysv/consts/map.h"
|
|
#include "libc/sysv/consts/prot.h"
|
|
#include "libc/x/x.h"
|
|
#include "third_party/getopt/getopt.h"
|
|
|
|
/**
|
|
* @fileoverview Make dependency generator.
|
|
*
|
|
* This program generates Makefile code saying which sources include
|
|
* which headers, thus improving build invalidation.
|
|
*
|
|
* The same thing can be accomplished using GCC's -M flag. This tool is
|
|
* much faster. It's designed to process over 9,000 sources to generate
|
|
* 50k+ lines of make code in ~80ms using one core and a meg of ram.
|
|
*/
|
|
|
|
static const char kSourceExts[][5] = {".s", ".S", ".c", ".cc", ".cpp"};
|
|
static alignas(16) const char kIncludePrefix[] = "include \"";
|
|
|
|
static const char *const kModelessPackages[] = {
|
|
"libc/nt/",
|
|
"libc/stubs/",
|
|
"libc/sysv/",
|
|
};
|
|
|
|
struct Strings {
|
|
size_t i, n;
|
|
char *p;
|
|
};
|
|
|
|
struct Source {
|
|
uint32_t hash; /* 0 means empty w/ triangle probe */
|
|
uint32_t name; /* strings.p[name] w/ interning */
|
|
uint32_t id; /* rehashing changes indexes */
|
|
};
|
|
|
|
struct Edge {
|
|
int32_t from; /* sources.p[from.id] */
|
|
int32_t to; /* sources.p[to.id] */
|
|
};
|
|
|
|
struct Sources {
|
|
size_t i, n; /* phase 1: hashmap: popcnt(n)==1 if n */
|
|
struct Source *p; /* phase 2: arraylist sorted by id */
|
|
};
|
|
|
|
struct Edges {
|
|
size_t i, n;
|
|
struct Edge *p;
|
|
};
|
|
|
|
int g_sourceid;
|
|
struct Strings strings;
|
|
struct Sources sources;
|
|
struct Edges edges;
|
|
const char *buildroot;
|
|
int *visited;
|
|
char *out;
|
|
FILE *fout;
|
|
|
|
static int CompareSourcesById(struct Source *a, struct Source *b) {
|
|
return a->id > b->id ? 1 : a->id < b->id ? -1 : 0;
|
|
}
|
|
|
|
static int CompareEdgesByFrom(struct Edge *a, struct Edge *b) {
|
|
return a->from > b->from ? 1 : a->from < b->from ? -1 : 0;
|
|
}
|
|
|
|
static uint32_t Hash(const void *s, size_t l) {
|
|
return max(1, crc32c(0, s, l));
|
|
}
|
|
|
|
void Crunch(void) {
|
|
size_t i, j;
|
|
for (i = 0, j = 0; j < sources.n; ++j) {
|
|
if (!sources.p[j].hash) continue;
|
|
if (i != j) memcpy(&sources.p[i], &sources.p[j], sizeof(sources.p[j]));
|
|
i++;
|
|
}
|
|
sources.i = i;
|
|
qsort(sources.p, sources.i, sizeof(*sources.p), (void *)CompareSourcesById);
|
|
qsort(edges.p, edges.i, sizeof(*edges.p), (void *)CompareEdgesByFrom);
|
|
}
|
|
|
|
void Rehash(void) {
|
|
size_t i, j, step;
|
|
struct Sources old;
|
|
memcpy(&old, &sources, sizeof(sources));
|
|
sources.n = sources.n ? sources.n << 1 : 16;
|
|
sources.p = calloc(sources.n, sizeof(struct Source));
|
|
for (i = 0; i < old.n; ++i) {
|
|
if (!old.p[i].hash) continue;
|
|
step = 0;
|
|
do {
|
|
j = (old.p[i].hash + step * (step + 1) / 2) & (sources.n - 1);
|
|
step++;
|
|
} while (sources.p[j].hash);
|
|
memcpy(&sources.p[j], &old.p[i], sizeof(old.p[i]));
|
|
}
|
|
free(old.p);
|
|
}
|
|
|
|
uint32_t GetSourceId(const char *name, size_t len) {
|
|
size_t i, step;
|
|
uint32_t hash;
|
|
i = 0;
|
|
hash = Hash(name, len);
|
|
if (sources.n) {
|
|
step = 0;
|
|
do {
|
|
i = (hash + step * (step + 1) / 2) & (sources.n - 1);
|
|
if (sources.p[i].hash == hash &&
|
|
memcmp(name, &strings.p[sources.p[i].name], len) == 0) {
|
|
return sources.p[i].id;
|
|
}
|
|
step++;
|
|
} while (sources.p[i].hash);
|
|
}
|
|
if (++sources.i >= (sources.n >> 1)) {
|
|
Rehash();
|
|
step = 0;
|
|
do {
|
|
i = (hash + step * (step + 1) / 2) & (sources.n - 1);
|
|
step++;
|
|
} while (sources.p[i].hash);
|
|
}
|
|
sources.p[i].hash = hash;
|
|
sources.p[i].name = CONCAT(&strings.p, &strings.i, &strings.n, name, len);
|
|
strings.p[strings.i++] = '\0';
|
|
return (sources.p[i].id = g_sourceid++);
|
|
}
|
|
|
|
void LoadRelationships(int argc, char *argv[]) {
|
|
struct MappedFile mf;
|
|
const char *p, *pe;
|
|
size_t i, linecap = 0;
|
|
char *line = NULL;
|
|
FILE *finpaths;
|
|
for (i = optind; i < argc; ++i) {
|
|
CHECK_NOTNULL((finpaths = fopen(argv[i], "r")));
|
|
while (getline(&line, &linecap, finpaths) != -1) {
|
|
if (mapfileread(chomp(line), &mf) == -1) {
|
|
CHECK_EQ(ENOENT, errno, "%s", line);
|
|
/*
|
|
* This code helps GNU Make automatically fix itself when we
|
|
* delete a source file. It removes o/.../srcs.txt or
|
|
* o/.../hdrs.txt and exits nonzero. Since we use hyphen
|
|
* notation on mkdeps related rules, the build will
|
|
* automatically restart itself.
|
|
*/
|
|
fprintf(stderr, "%s %s...\n", "Refreshing", argv[i]);
|
|
unlink(argv[i]);
|
|
exit(1);
|
|
}
|
|
if (mf.size) {
|
|
if (mf.size > PAGESIZE) {
|
|
madvise(mf.addr, mf.size, MADV_WILLNEED | MADV_SEQUENTIAL);
|
|
}
|
|
uint32_t sauce = GetSourceId(line, strlen(line));
|
|
size_t inclen = strlen(kIncludePrefix);
|
|
p = mf.addr;
|
|
pe = p + mf.size;
|
|
while ((p = strstr(p, kIncludePrefix))) {
|
|
const char *path = p + inclen;
|
|
const char *pathend = memchr(path, '"', pe - path);
|
|
if (pathend && (intptr_t)p > (intptr_t)mf.addr &&
|
|
(p[-1] == '#' || p[-1] == '.') &&
|
|
(p - 1 == mf.addr || p[-2] == '\n')) {
|
|
uint32_t dependency = GetSourceId(path, pathend - path);
|
|
struct Edge edge;
|
|
edge.from = sauce;
|
|
edge.to = dependency;
|
|
append(&edges, &edge);
|
|
}
|
|
p = path;
|
|
}
|
|
}
|
|
CHECK_NE(-1, unmapfile(&mf));
|
|
}
|
|
CHECK_NE(-1, fclose(finpaths));
|
|
}
|
|
free_s(&line);
|
|
}
|
|
|
|
void GetOpts(int argc, char *argv[]) {
|
|
int opt;
|
|
while ((opt = getopt(argc, argv, "ho:r:")) != -1) {
|
|
switch (opt) {
|
|
case 'o':
|
|
out = optarg;
|
|
break;
|
|
case 'r':
|
|
buildroot = optarg;
|
|
break;
|
|
default:
|
|
fprintf(stderr, "%s: %s [-r %s] [-o %s] [%s...]\n", "Usage", argv[0],
|
|
"BUILDROOT", "OUTPUT", "PATHSFILE");
|
|
exit(1);
|
|
}
|
|
}
|
|
if (isempty(out)) fprintf(stderr, "need -o FILE"), exit(1);
|
|
if (isempty(buildroot)) fprintf(stderr, "need -r o/$(MODE)"), exit(1);
|
|
}
|
|
|
|
const char *StripExt(const char *s) {
|
|
static bool once;
|
|
static size_t i, n;
|
|
static char *p, *dot;
|
|
if (!once) {
|
|
once = true;
|
|
__cxa_atexit(free_s, &p, NULL);
|
|
}
|
|
i = 0;
|
|
CONCAT(&p, &i, &n, s, strlen(s) + 1);
|
|
dot = strrchr(p, '.');
|
|
if (dot) *dot = '\0';
|
|
return p;
|
|
}
|
|
|
|
bool IsObjectSource(const char *name) {
|
|
for (size_t i = 0; i < ARRAYLEN(kSourceExts); ++i) {
|
|
if (endswith(name, kSourceExts[i])) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Dive(uint32_t sauce) {
|
|
for (uint32_t i = bisectcarleft((const int32_t(*)[2])edges.p, edges.i, sauce);
|
|
edges.p[i].from == sauce; ++i) {
|
|
int32_t dep = edges.p[i].to;
|
|
if (bts(visited, dep)) continue;
|
|
fputs(" \\\n\t", fout);
|
|
fputs(&strings.p[sources.p[dep].name], fout);
|
|
Dive(dep);
|
|
}
|
|
}
|
|
|
|
bool IsModeless(const char *path) {
|
|
size_t i;
|
|
for (i = 0; i < ARRAYLEN(kModelessPackages); ++i) {
|
|
if (startswith(path, kModelessPackages[i])) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
out = "/dev/stdout";
|
|
GetOpts(argc, argv);
|
|
char *tmp =
|
|
!fileexists(out) || isregularfile(out) ? xasprintf("%s.tmp", out) : NULL;
|
|
CHECK_NOTNULL((fout = fopen(tmp ? tmp : out, "w")));
|
|
LoadRelationships(argc, argv);
|
|
Crunch();
|
|
size_t bitmaplen = roundup((sources.i + 8) / 8, 4);
|
|
visited = malloc(bitmaplen);
|
|
for (size_t i = 0; i < sources.i; ++i) {
|
|
const char *path = &strings.p[sources.p[i].name];
|
|
if (!IsObjectSource(path)) continue;
|
|
bool needprefix = !startswith(path, "o/");
|
|
const char *prefix = !needprefix ? "" : IsModeless(path) ? "o/" : buildroot;
|
|
fprintf(fout, "\n%s%s.o: \\\n\t%s", prefix, StripExt(path), path);
|
|
memset(visited, 0, bitmaplen);
|
|
bts(visited, i);
|
|
Dive(i);
|
|
fprintf(fout, "\n");
|
|
}
|
|
if (fclose(fout) == -1) perror(out), exit(1);
|
|
if (tmp) {
|
|
if (rename(tmp, out) == -1) perror(out), exit(1);
|
|
}
|
|
free_s(&strings.p);
|
|
free_s(&sources.p);
|
|
free_s(&edges.p);
|
|
free_s(&visited);
|
|
free_s(&tmp);
|
|
return 0;
|
|
}
|