mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-07-25 20:10:29 +00:00
Introduce new linker for fat ape binaries
This commit is contained in:
parent
e3c456d23a
commit
0105e3e2b6
44 changed files with 3140 additions and 867 deletions
2164
tool/build/apelink.c
Normal file
2164
tool/build/apelink.c
Normal file
File diff suppressed because it is too large
Load diff
|
@ -20,13 +20,16 @@
|
|||
#include "libc/calls/calls.h"
|
||||
#include "libc/calls/struct/stat.h"
|
||||
#include "libc/dce.h"
|
||||
#include "libc/elf/def.h"
|
||||
#include "libc/elf/struct/ehdr.h"
|
||||
#include "libc/fmt/conv.h"
|
||||
#include "libc/intrin/bits.h"
|
||||
#include "libc/intrin/kprintf.h"
|
||||
#include "libc/log/check.h"
|
||||
#include "libc/limits.h"
|
||||
#include "libc/macho.internal.h"
|
||||
#include "libc/macros.internal.h"
|
||||
#include "libc/runtime/runtime.h"
|
||||
#include "libc/stdckdint.h"
|
||||
#include "libc/stdio/stdio.h"
|
||||
#include "libc/str/str.h"
|
||||
#include "libc/sysv/consts/map.h"
|
||||
#include "libc/sysv/consts/msync.h"
|
||||
|
@ -35,85 +38,129 @@
|
|||
#include "third_party/getopt/getopt.internal.h"
|
||||
#include "third_party/regex/regex.h"
|
||||
|
||||
__static_yoink("strerror_wr");
|
||||
#define VERSION \
|
||||
"actually portable executable assimilate v1.6\n" \
|
||||
"copyright 2023 justine alexandra roberts tunney\n"
|
||||
|
||||
// options used: fhem
|
||||
// letters not used: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdgijklnopqrstuvwxyz
|
||||
// digits not used: 0123456789
|
||||
// puncts not used: !"#$%&'()*+,-./;<=>@[\]^_`{|}~
|
||||
// letters duplicated: none
|
||||
#define GETOPTS "fem"
|
||||
#define USAGE \
|
||||
"usage: assimilate.com [-hvfem] COMFILE...\n" \
|
||||
" -h show help\n" \
|
||||
" -v show version\n" \
|
||||
" -f ignore soft errors\n" \
|
||||
" -e convert to elf regardless of host os\n" \
|
||||
" -m convert to macho regardless of host os\n" \
|
||||
" -x convert to amd64 regardless of host cpu\n" \
|
||||
" -a convert to arm64 regardless of host cpu\n"
|
||||
|
||||
#define USAGE \
|
||||
"\
|
||||
Usage: assimilate.com [-hfem] COMFILE...\n\
|
||||
-h show help\n\
|
||||
-e force elf\n\
|
||||
-m force macho\n\
|
||||
-f continue on soft errors\n\
|
||||
\n\
|
||||
αcτµαlly pδrταblε εxεcµταblε assimilate v1.o\n\
|
||||
copyright 2022 justine alexandra roberts tunney\n\
|
||||
https://twitter.com/justinetunney\n\
|
||||
https://linkedin.com/in/jtunney\n\
|
||||
https://justine.lol/ape.html\n\
|
||||
https://github.com/jart\n\
|
||||
\n\
|
||||
This program converts Actually Portable Executable files so they're\n\
|
||||
in the platform-local executable format, rather than the multiplatform\n\
|
||||
APE shell script format. This is useful on UNIX operating systems when\n\
|
||||
you want to use your APE programs as script interpreter or for setuid.\n\
|
||||
"
|
||||
#define ARCH_NATIVE 0
|
||||
#define ARCH_AMD64 1
|
||||
#define ARCH_ARM64 2
|
||||
|
||||
#define MODE_NATIVE 0
|
||||
#define MODE_ELF 1
|
||||
#define MODE_MACHO 2
|
||||
#define MODE_PE 3
|
||||
#define FORMAT_NATIVE 0
|
||||
#define FORMAT_ELF 1
|
||||
#define FORMAT_MACHO 2
|
||||
#define FORMAT_PE 3
|
||||
|
||||
int g_mode;
|
||||
bool g_force;
|
||||
int exitcode;
|
||||
const char *prog;
|
||||
char errstr[128];
|
||||
static int g_arch;
|
||||
static int g_format;
|
||||
static bool g_force;
|
||||
static const char *prog;
|
||||
static const char *path;
|
||||
static char errstr[128];
|
||||
static bool got_format_flag;
|
||||
|
||||
void GetOpts(int argc, char *argv[]) {
|
||||
static wontreturn void Die(const char *thing, const char *reason) {
|
||||
const char *native_explainer;
|
||||
if (got_format_flag) {
|
||||
native_explainer = "";
|
||||
} else if (IsXnu()) {
|
||||
native_explainer = " (the host os uses macho natively)";
|
||||
} else if (IsLinux() || IsFreebsd() || IsNetbsd() || IsOpenbsd()) {
|
||||
native_explainer = " (the host os uses elf natively)";
|
||||
} else {
|
||||
native_explainer = " (the host os uses pe natively)";
|
||||
}
|
||||
tinyprint(2, thing, ": ", reason, native_explainer, "\n", NULL);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static wontreturn void DieSys(const char *thing) {
|
||||
perror(thing);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static int Atoi(const char *s) {
|
||||
int x;
|
||||
if ((x = atoi(s)) == INT_MAX) {
|
||||
Die(path, "integer overflow parsing ape macho dd argument");
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
static void GetOpts(int argc, char *argv[]) {
|
||||
int opt;
|
||||
while ((opt = getopt(argc, argv, GETOPTS)) != -1) {
|
||||
while ((opt = getopt(argc, argv, "hvfemxa")) != -1) {
|
||||
switch (opt) {
|
||||
case 'f':
|
||||
g_force = true;
|
||||
break;
|
||||
case 'e':
|
||||
g_mode = MODE_ELF;
|
||||
g_format = FORMAT_ELF;
|
||||
got_format_flag = true;
|
||||
break;
|
||||
case 'm':
|
||||
g_mode = MODE_MACHO;
|
||||
g_format = FORMAT_MACHO;
|
||||
got_format_flag = true;
|
||||
break;
|
||||
case 'x':
|
||||
g_arch = ARCH_AMD64;
|
||||
break;
|
||||
case 'a':
|
||||
g_arch = ARCH_ARM64;
|
||||
break;
|
||||
case 'v':
|
||||
tinyprint(1, VERSION, NULL);
|
||||
exit(0);
|
||||
case 'h':
|
||||
write(1, USAGE, sizeof(USAGE) - 1);
|
||||
tinyprint(1, VERSION, USAGE, NULL);
|
||||
exit(0);
|
||||
default:
|
||||
write(2, USAGE, sizeof(USAGE) - 1);
|
||||
exit(64);
|
||||
tinyprint(2, VERSION, USAGE, NULL);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
if (g_mode == MODE_NATIVE) {
|
||||
if (optind == argc) {
|
||||
Die(prog, "missing operand");
|
||||
}
|
||||
if (g_format == FORMAT_NATIVE) {
|
||||
if (IsXnu()) {
|
||||
g_mode = MODE_MACHO;
|
||||
g_format = FORMAT_MACHO;
|
||||
} else if (IsLinux() || IsFreebsd() || IsNetbsd() || IsOpenbsd()) {
|
||||
g_mode = MODE_ELF;
|
||||
g_format = FORMAT_ELF;
|
||||
} else {
|
||||
g_mode = MODE_PE;
|
||||
g_format = FORMAT_PE;
|
||||
}
|
||||
}
|
||||
if (g_arch == ARCH_NATIVE) {
|
||||
#ifdef __aarch64__
|
||||
g_arch = ARCH_ARM64;
|
||||
#else
|
||||
g_arch = ARCH_AMD64;
|
||||
#endif
|
||||
}
|
||||
if (g_format == FORMAT_PE && g_arch == ARCH_ARM64) {
|
||||
Die(prog, "native arm64 on windows not supported yet");
|
||||
}
|
||||
}
|
||||
|
||||
void GetElfHeader(char ehdr[hasatleast 64], const char *image, size_t n) {
|
||||
char *p;
|
||||
static void GetElfHeader(char ehdr[hasatleast 64], const char *image,
|
||||
size_t n) {
|
||||
int c, i;
|
||||
for (p = image; p < image + MIN(n, 4096); ++p) {
|
||||
char *p, *e;
|
||||
for (p = image, e = p + MIN(n, 8192); p < e; ++p) {
|
||||
TryAgain:
|
||||
if (READ64LE(p) != READ64LE("printf '")) continue;
|
||||
for (i = 0, p += 8; p + 3 < image + MIN(n, 4096) && (c = *p++) != '\'';) {
|
||||
for (i = 0, p += 8; p + 3 < e && (c = *p++) != '\'';) {
|
||||
if (c == '\\') {
|
||||
if ('0' <= *p && *p <= '7') {
|
||||
c = *p++ - '0';
|
||||
|
@ -130,29 +177,35 @@ void GetElfHeader(char ehdr[hasatleast 64], const char *image, size_t n) {
|
|||
if (i < 64) {
|
||||
ehdr[i++] = c;
|
||||
} else {
|
||||
kprintf("%s: ape printf elf header too long\n", prog);
|
||||
exit(1);
|
||||
goto TryAgain;
|
||||
}
|
||||
}
|
||||
if (i != 64) {
|
||||
kprintf("%s: ape printf elf header too short\n", prog);
|
||||
exit(2);
|
||||
}
|
||||
if (READ32LE(ehdr) != READ32LE("\177ELF")) {
|
||||
kprintf("%s: ape printf elf header didn't have elf magic\n", prog);
|
||||
exit(3);
|
||||
if (i != 64 || //
|
||||
READ32LE(ehdr) != READ32LE("\177ELF") || //
|
||||
ehdr[EI_CLASS] == ELFCLASS32 || //
|
||||
READ16LE(ehdr + 18) !=
|
||||
(g_arch == ARCH_AMD64 ? EM_NEXGEN32E : EM_AARCH64)) {
|
||||
goto TryAgain;
|
||||
}
|
||||
return;
|
||||
}
|
||||
kprintf("%s: printf statement not found in first 4096 bytes\n", prog);
|
||||
exit(4);
|
||||
switch (g_arch) {
|
||||
case ARCH_AMD64:
|
||||
Die(path, "printf statement not found in first 8192 bytes of image "
|
||||
"containing elf64 ehdr for amd64");
|
||||
case ARCH_ARM64:
|
||||
Die(path, "printf statement not found in first 8192 bytes of image "
|
||||
"containing elf64 ehdr for arm64");
|
||||
default:
|
||||
__builtin_unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
void GetMachoPayload(const char *image, size_t imagesize, int *out_offset,
|
||||
int *out_size) {
|
||||
static void GetMachoPayload(const char *image, size_t imagesize,
|
||||
int *out_offset, int *out_size) {
|
||||
regex_t rx;
|
||||
const char *script;
|
||||
regmatch_t rm[1 + 3] = {0};
|
||||
regmatch_t rm[1 + 13] = {0};
|
||||
int rc, skip, count, bs, offset, size;
|
||||
|
||||
if ((script = memmem(image, imagesize, "'\n#'\"\n", 6))) {
|
||||
|
@ -160,147 +213,239 @@ void GetMachoPayload(const char *image, size_t imagesize, int *out_offset,
|
|||
} else if ((script = memmem(image, imagesize, "#'\"\n", 4))) {
|
||||
script += 4;
|
||||
} else {
|
||||
kprintf("%s: ape shell script not found\n", prog);
|
||||
exit(5);
|
||||
Die(path, "ape shell script not found");
|
||||
}
|
||||
|
||||
DCHECK_EQ(REG_OK, regcomp(&rx,
|
||||
"bs=\"?\\$?(*([ [:digit:]]+))*\"? "
|
||||
"skip=\"?\\$?(*([ [:digit:]]+))*\"? "
|
||||
"count=\"?\\$?(*([ [:digit:]]+))*\"?",
|
||||
REG_EXTENDED));
|
||||
rc = regexec(&rx, script, 4, rm, 0);
|
||||
// the ape shell script has always historically used `dd` to
|
||||
// assimilate binaries to the mach-o file format but we have
|
||||
// formatted the arguments in a variety of different ways eg
|
||||
//
|
||||
// - `arg=" 9293"` is how we originally had ape do it
|
||||
// - `arg=$(( 9293))` b/c busybox sh disliked quoted space
|
||||
// - `arg=9293 ` is generated by modern apelink.com program
|
||||
//
|
||||
unassert(regcomp(&rx,
|
||||
"bs=" // dd block size arg
|
||||
"(['\"] *)?" // #1 optional quote w/ space
|
||||
"(\\$\\(\\( *)?" // #2 optional math w/ space
|
||||
"([[:digit:]]+)" // #3
|
||||
"( *\\)\\))?" // #4 optional math w/ space
|
||||
"( *['\"])?" // #5 optional quote w/ space
|
||||
" +" //
|
||||
"skip=" // dd skip arg
|
||||
"(['\"] *)?" // #6 optional quote w/ space
|
||||
"(\\$\\(\\( *)?" // #7 optional math w/ space
|
||||
"([[:digit:]]+)" // #8
|
||||
"( *\\)\\))?" // #9 optional math w/ space
|
||||
"( *['\"])?" // #10 optional quote w/ space
|
||||
" +" //
|
||||
"count=" // dd count arg
|
||||
"(['\"] *)?" // #11 optional quote w/ space
|
||||
"(\\$\\(\\( *)?" // #12 optional math w/ space
|
||||
"([[:digit:]]+)", // #13
|
||||
REG_EXTENDED) == REG_OK);
|
||||
|
||||
int i = 0;
|
||||
TryAgain:
|
||||
rc = regexec(&rx, script + i, 1 + 13, rm, 0);
|
||||
if (rc != REG_OK) {
|
||||
if (rc == REG_NOMATCH) {
|
||||
kprintf("%s: ape macho dd command not found\n", prog);
|
||||
exit(6);
|
||||
unassert(rc == REG_NOMATCH);
|
||||
switch (g_arch) {
|
||||
case ARCH_AMD64:
|
||||
Die(path, "ape macho dd command for amd64 not found");
|
||||
case ARCH_ARM64:
|
||||
Die(path, "ape macho dd command for arm64 not found");
|
||||
default:
|
||||
__builtin_unreachable();
|
||||
}
|
||||
regerror(rc, &rx, errstr, sizeof(errstr));
|
||||
kprintf("%s: ape macho dd regex failed: %s\n", prog, errstr);
|
||||
exit(7);
|
||||
}
|
||||
bs = atoi(script + rm[1].rm_so);
|
||||
skip = atoi(script + rm[2].rm_so);
|
||||
count = atoi(script + rm[3].rm_so);
|
||||
if (ckd_mul(&offset, skip, bs) || ckd_mul(&size, count, bs)) {
|
||||
kprintf("%s: integer overflow parsing macho\n");
|
||||
exit(8);
|
||||
i += rm[13].rm_eo;
|
||||
|
||||
bs = Atoi(script + rm[3].rm_so);
|
||||
skip = Atoi(script + rm[8].rm_so);
|
||||
count = Atoi(script + rm[13].rm_so);
|
||||
|
||||
if (ckd_mul(&offset, skip, bs)) {
|
||||
Die(path, "integer overflow computing ape macho dd offset");
|
||||
}
|
||||
if (ckd_mul(&size, count, bs)) {
|
||||
Die(path, "integer overflow computing ape macho dd size");
|
||||
}
|
||||
|
||||
if (offset < 64) {
|
||||
kprintf("%s: ape macho dd offset should be ≥64: %d\n", prog, offset);
|
||||
exit(9);
|
||||
Die(path, "ape macho dd offset must be at least 64");
|
||||
}
|
||||
if (offset >= imagesize) {
|
||||
kprintf("%s: ape macho dd offset is outside file: %d\n", prog, offset);
|
||||
exit(10);
|
||||
Die(path, "ape macho dd offset points outside image");
|
||||
}
|
||||
if (size < 32) {
|
||||
kprintf("%s: ape macho dd size should be ≥32: %d\n", prog, size);
|
||||
exit(11);
|
||||
Die(path, "ape macho dd size must be at least 32");
|
||||
}
|
||||
if (size > imagesize - offset) {
|
||||
kprintf("%s: ape macho dd size is outside file: %d\n", prog, size);
|
||||
exit(12);
|
||||
Die(path, "ape macho dd size overlaps end of image");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (READ32LE(image + offset) != 0xFEEDFACE + 1 ||
|
||||
READ32LE(image + offset + 4) !=
|
||||
(g_arch == ARCH_AMD64 ? MAC_CPU_NEXGEN32E : MAC_CPU_ARM64)) {
|
||||
goto TryAgain;
|
||||
}
|
||||
|
||||
*out_offset = offset;
|
||||
*out_size = size;
|
||||
regfree(&rx);
|
||||
}
|
||||
|
||||
void AssimilateElf(char *p, size_t n) {
|
||||
static void AssimilateElf(char *p, size_t n) {
|
||||
char ehdr[64];
|
||||
GetElfHeader(ehdr, p, n);
|
||||
memcpy(p, ehdr, 64);
|
||||
msync(p, 4096, MS_SYNC);
|
||||
}
|
||||
|
||||
void AssimilateMacho(char *p, size_t n) {
|
||||
static void AssimilateMacho(char *p, size_t n) {
|
||||
int offset, size;
|
||||
GetMachoPayload(p, n, &offset, &size);
|
||||
memmove(p, p + offset, size);
|
||||
msync(p, n, MS_SYNC);
|
||||
}
|
||||
|
||||
void Assimilate(void) {
|
||||
static void Assimilate(void) {
|
||||
int fd;
|
||||
char *p;
|
||||
struct stat st;
|
||||
if ((fd = open(prog, O_RDWR)) == -1) {
|
||||
kprintf("%s: open(O_RDWR) failed: %m\n", prog);
|
||||
exit(13);
|
||||
}
|
||||
if (fstat(fd, &st) == -1) {
|
||||
kprintf("%s: fstat() failed: %m\n", prog);
|
||||
exit(14);
|
||||
}
|
||||
if (st.st_size < 64) {
|
||||
kprintf("%s: ape binaries must be at least 64 bytes\n", prog);
|
||||
exit(15);
|
||||
}
|
||||
if ((p = mmap(0, st.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) ==
|
||||
MAP_FAILED) {
|
||||
kprintf("%s: mmap failed: %m\n", prog);
|
||||
exit(16);
|
||||
}
|
||||
if (g_mode == MODE_PE) {
|
||||
if (READ16LE(p) == READ16LE("MZ")) {
|
||||
if (!g_force) {
|
||||
kprintf("%s: program is already an elf binary\n", prog);
|
||||
if (g_mode != MODE_ELF) {
|
||||
exitcode = 1;
|
||||
}
|
||||
}
|
||||
goto Finish;
|
||||
} else {
|
||||
kprintf("%s: currently cannot back-convert to pe\n", prog);
|
||||
exit(17);
|
||||
}
|
||||
}
|
||||
ssize_t size;
|
||||
if ((fd = open(path, O_RDWR)) == -1) DieSys(path);
|
||||
if ((size = lseek(fd, 0, SEEK_END)) == -1) DieSys(path);
|
||||
if (size < 64) Die(path, "ape executables must be at least 64 bytes");
|
||||
p = mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
if (p == MAP_FAILED) DieSys(path);
|
||||
|
||||
if (READ32LE(p) == READ32LE("\177ELF")) {
|
||||
if (!g_force) {
|
||||
kprintf("%s: program is already an elf binary\n", prog);
|
||||
if (g_mode != MODE_ELF) {
|
||||
exitcode = 1;
|
||||
}
|
||||
Elf64_Ehdr *ehdr;
|
||||
switch (g_format) {
|
||||
case FORMAT_ELF:
|
||||
ehdr = (Elf64_Ehdr *)p;
|
||||
if (ehdr->e_ident[EI_CLASS] == ELFCLASS32) {
|
||||
Die(path, "32-bit elf not supported");
|
||||
}
|
||||
switch (g_arch) {
|
||||
case ARCH_AMD64:
|
||||
switch (ehdr->e_machine) {
|
||||
case EM_NEXGEN32E:
|
||||
if (g_force) {
|
||||
exit(0);
|
||||
} else {
|
||||
Die(path, "already an elf amd64 executable");
|
||||
}
|
||||
case EM_AARCH64:
|
||||
Die(path, "can't assimilate elf arm64 to elf amd64");
|
||||
default:
|
||||
Die(path, "elf has unsupported architecture");
|
||||
}
|
||||
case ARCH_ARM64:
|
||||
switch (ehdr->e_machine) {
|
||||
case EM_AARCH64:
|
||||
if (g_force) {
|
||||
exit(0);
|
||||
} else {
|
||||
Die(path, "already an elf arm64 executable");
|
||||
}
|
||||
case EM_NEXGEN32E:
|
||||
Die(path, "can't assimilate elf amd64 to elf arm64");
|
||||
default:
|
||||
Die(path, "elf has unsupported architecture");
|
||||
}
|
||||
default:
|
||||
__builtin_unreachable();
|
||||
}
|
||||
case FORMAT_MACHO:
|
||||
Die(path, "can't assimilate elf to macho");
|
||||
case FORMAT_PE:
|
||||
Die(path, "can't assimilate elf to pe (try elf2pe.com)");
|
||||
default:
|
||||
__builtin_unreachable();
|
||||
}
|
||||
goto Finish;
|
||||
}
|
||||
|
||||
if (READ32LE(p) == 0xFEEDFACE + 1) {
|
||||
if (!g_force) {
|
||||
kprintf("%s: program is already a mach-o binary\n", prog);
|
||||
if (g_mode != MODE_MACHO) {
|
||||
exitcode = 1;
|
||||
}
|
||||
struct MachoHeader *macho;
|
||||
switch (g_format) {
|
||||
case FORMAT_MACHO:
|
||||
macho = (struct MachoHeader *)p;
|
||||
switch (g_arch) {
|
||||
case ARCH_AMD64:
|
||||
switch (macho->arch) {
|
||||
case MAC_CPU_NEXGEN32E:
|
||||
if (g_force) {
|
||||
exit(0);
|
||||
} else {
|
||||
Die(path, "already a macho amd64 executable");
|
||||
}
|
||||
case MAC_CPU_ARM64:
|
||||
Die(path, "can't assimilate macho arm64 to macho amd64");
|
||||
default:
|
||||
Die(path, "macho has unsupported architecture");
|
||||
}
|
||||
case ARCH_ARM64:
|
||||
switch (macho->arch) {
|
||||
case MAC_CPU_ARM64:
|
||||
if (g_force) {
|
||||
exit(0);
|
||||
} else {
|
||||
Die(path, "already a macho arm64 executable");
|
||||
}
|
||||
case MAC_CPU_NEXGEN32E:
|
||||
Die(path, "can't assimilate macho amd64 to macho arm64");
|
||||
default:
|
||||
Die(path, "macho has unsupported architecture");
|
||||
}
|
||||
default:
|
||||
__builtin_unreachable();
|
||||
}
|
||||
case FORMAT_ELF:
|
||||
Die(path, "can't assimilate macho to elf");
|
||||
case FORMAT_PE:
|
||||
Die(path, "can't assimilate macho to pe");
|
||||
default:
|
||||
__builtin_unreachable();
|
||||
}
|
||||
goto Finish;
|
||||
}
|
||||
if (READ64LE(p) != READ64LE("MZqFpD='")) {
|
||||
kprintf("%s: this file is not an actually portable executable\n", prog);
|
||||
exit(17);
|
||||
|
||||
if (READ64LE(p) != READ64LE("MZqFpD='") && //
|
||||
READ64LE(p) != READ64LE("jartsr='") && //
|
||||
READ64LE(p) != READ64LE("APEDBG='")) {
|
||||
Die(path, "not an actually portable executable");
|
||||
}
|
||||
if (g_mode == MODE_ELF) {
|
||||
AssimilateElf(p, st.st_size);
|
||||
} else if (g_mode == MODE_MACHO) {
|
||||
AssimilateMacho(p, st.st_size);
|
||||
|
||||
if (g_format == FORMAT_PE) {
|
||||
if (READ16LE(p) == READ16LE("MZ")) {
|
||||
if (g_force) {
|
||||
exit(0);
|
||||
} else {
|
||||
Die(path, "this ape file is already a pe file");
|
||||
}
|
||||
} else {
|
||||
Die(path, "this ape file was built without pe support");
|
||||
}
|
||||
}
|
||||
Finish:
|
||||
if (munmap(p, st.st_size) == -1) {
|
||||
kprintf("%s: munmap() failed: %m\n", prog);
|
||||
exit(18);
|
||||
|
||||
if (g_format == FORMAT_ELF) {
|
||||
AssimilateElf(p, size);
|
||||
} else if (g_format == FORMAT_MACHO) {
|
||||
AssimilateMacho(p, size);
|
||||
}
|
||||
|
||||
if (munmap(p, size)) DieSys(path);
|
||||
if (close(fd)) DieSys(path);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
int i;
|
||||
prog = argv[0];
|
||||
if (!prog) prog = "assimilate";
|
||||
GetOpts(argc, argv);
|
||||
if (optind == argc) {
|
||||
kprintf("error: need at least one program path to assimilate\n");
|
||||
write(2, USAGE, sizeof(USAGE) - 1);
|
||||
exit(64);
|
||||
}
|
||||
for (i = optind; i < argc; ++i) {
|
||||
prog = argv[i];
|
||||
for (int i = optind; i < argc; ++i) {
|
||||
path = argv[i];
|
||||
Assimilate();
|
||||
}
|
||||
return exitcode;
|
||||
}
|
||||
|
|
|
@ -47,8 +47,8 @@
|
|||
#include "libc/sysv/consts/prot.h"
|
||||
#include "third_party/getopt/getopt.internal.h"
|
||||
|
||||
// see tool/hello/hello.c for an example program this can link
|
||||
// make -j8 m=tiny o/tiny/tool/hello/hello.com
|
||||
// see tool/hello/hello-pe.c for an example program this can link
|
||||
// make -j8 m=tiny o/tiny/tool/hello/hello-pe.com
|
||||
|
||||
#define VERSION \
|
||||
"elf2pe v0.1\n" \
|
||||
|
@ -571,6 +571,7 @@ static struct Section *LoadSection(struct Elf *elf, int index,
|
|||
static void LoadSectionsIntoSegments(struct Elf *elf) {
|
||||
int i;
|
||||
Elf64_Shdr *shdr;
|
||||
bool hasdataseg = false;
|
||||
struct Segment *segment = 0;
|
||||
for (i = 0; i < elf->ehdr->e_shnum; ++i) {
|
||||
if ((shdr = GetElfSectionHeaderAddress(elf->ehdr, elf->size, i)) &&
|
||||
|
@ -590,6 +591,7 @@ static void LoadSectionsIntoSegments(struct Elf *elf) {
|
|||
segment->vaddr_min = section->shdr->sh_addr;
|
||||
if (shdr->sh_type == SHT_PROGBITS)
|
||||
segment->offset_min = section->shdr->sh_offset;
|
||||
hasdataseg |= segment->prot == (PROT_READ | PROT_WRITE);
|
||||
}
|
||||
segment->hasnobits |= shdr->sh_type == SHT_NOBITS;
|
||||
segment->hasprogbits |= shdr->sh_type == SHT_PROGBITS;
|
||||
|
@ -604,6 +606,16 @@ static void LoadSectionsIntoSegments(struct Elf *elf) {
|
|||
if (segment) {
|
||||
dll_make_last(&elf->segments, &segment->elem);
|
||||
}
|
||||
if (elf->imports && !hasdataseg) {
|
||||
// if the program we're linking is really tiny and it doesn't have
|
||||
// either a .data or .bss section but it does import function from
|
||||
// libraries, then create a synthetic .data segment for the pe iat
|
||||
segment = NewSegment();
|
||||
segment->align = 8;
|
||||
segment->hasprogbits = true;
|
||||
segment->prot = PROT_READ | PROT_WRITE;
|
||||
dll_make_last(&elf->segments, &segment->elem);
|
||||
}
|
||||
}
|
||||
|
||||
static bool ParseDllImportSymbol(const char *symbol_name,
|
||||
|
@ -678,8 +690,8 @@ static struct Elf *OpenElf(const char *path) {
|
|||
if (!elf->strtab) Die(path, "elf doesn't have string table");
|
||||
elf->secstrs = GetElfSectionNameStringTable(elf->ehdr, elf->size);
|
||||
if (!elf->strtab) Die(path, "elf doesn't have section string table");
|
||||
LoadSectionsIntoSegments(elf);
|
||||
LoadDllImports(elf);
|
||||
LoadSectionsIntoSegments(elf);
|
||||
close(fd);
|
||||
return elf;
|
||||
}
|
||||
|
@ -745,11 +757,20 @@ static void PickPeSectionName(char *p, struct Elf *elf,
|
|||
|
||||
static uint32_t GetPeSectionCharacteristics(struct Segment *s) {
|
||||
uint32_t x = 0;
|
||||
if (s->prot & PROT_EXEC) x |= kNtPeSectionCntCode | kNtPeSectionMemExecute;
|
||||
if (s->prot & PROT_READ) x |= kNtPeSectionMemRead;
|
||||
if (s->prot & PROT_WRITE) x |= kNtPeSectionMemWrite;
|
||||
if (s->hasnobits) x |= kNtPeSectionCntUninitializedData;
|
||||
if (s->hasprogbits) x |= kNtPeSectionCntInitializedData;
|
||||
if (s->prot & PROT_EXEC) {
|
||||
x |= kNtPeSectionCntCode | kNtPeSectionMemExecute;
|
||||
} else if (s->hasprogbits) {
|
||||
x |= kNtPeSectionCntInitializedData;
|
||||
}
|
||||
if (s->prot & PROT_READ) {
|
||||
x |= kNtPeSectionMemRead;
|
||||
}
|
||||
if (s->prot & PROT_WRITE) {
|
||||
x |= kNtPeSectionMemWrite;
|
||||
}
|
||||
if (s->hasnobits) {
|
||||
x |= kNtPeSectionCntUninitializedData;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
|
@ -780,9 +801,6 @@ static struct ImagePointer GeneratePe(struct Elf *elf, char *fp, int64_t vp) {
|
|||
mzhdr = (struct NtImageDosHeader *)fp;
|
||||
fp += sizeof(struct NtImageDosHeader);
|
||||
memcpy(mzhdr, "MZ", 2);
|
||||
/* memcpy(mzhdr, "MZqFpD='\n\n", 10); */
|
||||
/* mzhdr->e_oemid = 'J' | 'T' << 8; */
|
||||
/* memcpy(mzhdr->e_res2, "' <<'@'\n", 8); */
|
||||
|
||||
// embed the ms-dos stub and/or bios bootloader
|
||||
if (stubpath) {
|
||||
|
@ -797,10 +815,6 @@ static struct ImagePointer GeneratePe(struct Elf *elf, char *fp, int64_t vp) {
|
|||
if (close(fd)) DieSys(stubpath);
|
||||
}
|
||||
|
||||
// begin the shell script
|
||||
/* fp = stpcpy(fp, "\n@\n" */
|
||||
/* "#'\"\n"); */
|
||||
|
||||
// output portable executable magic
|
||||
fp = ALIGN_FILE(fp, 8);
|
||||
mzhdr->e_lfanew = fp - (char *)mzhdr;
|
||||
|
@ -956,6 +970,7 @@ static struct ImagePointer GeneratePe(struct Elf *elf, char *fp, int64_t vp) {
|
|||
struct Library *library = LIBRARY_CONTAINER(e);
|
||||
library->idt->ImportAddressTable = vp - opthdr->ImageBase;
|
||||
fp = mempcpy(fp, library->ilt, library->iltbytes);
|
||||
segment->hasprogbits = true;
|
||||
for (struct Dll *g = dll_first(library->funcs); g;
|
||||
g = dll_next(library->funcs, g)) {
|
||||
struct Func *func = FUNC_CONTAINER(g);
|
||||
|
@ -1055,19 +1070,17 @@ int main(int argc, char *argv[]) {
|
|||
#ifndef NDEBUG
|
||||
ShowCrashReports();
|
||||
#endif
|
||||
|
||||
// get program name
|
||||
prog = argv[0];
|
||||
if (!prog) prog = "elf2pe";
|
||||
|
||||
// process flags
|
||||
GetOpts(argc, argv);
|
||||
|
||||
// translate executable
|
||||
struct Elf *elf = OpenElf(argv[optind]);
|
||||
char *buf = memalign(MAX_ALIGN, INT_MAX);
|
||||
char *buf = memalign(MAX_ALIGN, 134217728);
|
||||
struct ImagePointer ip = GeneratePe(elf, buf, 0x00400000);
|
||||
if (creat(outpath, 0755) == -1) DieSys(elf->path);
|
||||
Pwrite(3, buf, ip.fp - buf, 0);
|
||||
if (close(3)) DieSys(elf->path);
|
||||
// PrintElf(elf);
|
||||
}
|
||||
|
|
8
tool/build/elf2pe.h
Normal file
8
tool/build/elf2pe.h
Normal file
|
@ -0,0 +1,8 @@
|
|||
#ifndef COSMOPOLITAN_TOOL_BUILD_ELF2PE_H_
|
||||
#define COSMOPOLITAN_TOOL_BUILD_ELF2PE_H_
|
||||
|
||||
#define __dll_import(DLL, RET, FUNC, ARGS) \
|
||||
extern RET(*const __attribute__((__ms_abi__, __weak__)) FUNC) \
|
||||
ARGS __asm__("\"dll$" DLL "$" #FUNC "\"")
|
||||
|
||||
#endif /* COSMOPOLITAN_TOOL_BUILD_ELF2PE_H_ */
|
10
tool/build/lib/lib.h
Normal file
10
tool/build/lib/lib.h
Normal file
|
@ -0,0 +1,10 @@
|
|||
#ifndef COSMOPOLITAN_TOOL_BUILD_LIB_LIB_H_
|
||||
#define COSMOPOLITAN_TOOL_BUILD_LIB_LIB_H_
|
||||
#if !(__ASSEMBLER__ + __LINKER__ + 0)
|
||||
COSMOPOLITAN_C_START_
|
||||
|
||||
bool ParseSupportVector(char *, int *);
|
||||
|
||||
COSMOPOLITAN_C_END_
|
||||
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
|
||||
#endif /* COSMOPOLITAN_TOOL_BUILD_LIB_LIB_H_ */
|
75
tool/build/lib/parsesupportvector.c
Normal file
75
tool/build/lib/parsesupportvector.c
Normal file
|
@ -0,0 +1,75 @@
|
|||
/*-*- 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 2023 Justine Alexandra Roberts Tunney │
|
||||
│ │
|
||||
│ Permission to use, copy, modify, and/or distribute this software for │
|
||||
│ any purpose with or without fee is hereby granted, provided that the │
|
||||
│ above copyright notice and this permission notice appear in all copies. │
|
||||
│ │
|
||||
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
|
||||
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
|
||||
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
|
||||
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
|
||||
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
|
||||
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
|
||||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/dce.h"
|
||||
#include "libc/fmt/conv.h"
|
||||
#include "libc/str/str.h"
|
||||
#include "tool/build/lib/lib.h"
|
||||
|
||||
bool ParseSupportVector(char *str, int *out_bits) {
|
||||
// you can supply a number, e.g. 123, 0x123, etc.
|
||||
char *endptr;
|
||||
int bits = strtol(str, &endptr, 0);
|
||||
if (!*endptr) {
|
||||
*out_bits = bits;
|
||||
return true;
|
||||
}
|
||||
// you can supply a string, e.g. -s linux+mac+bsd
|
||||
bits = 0;
|
||||
char *tok, *state;
|
||||
const char *sep = " ,+:/|";
|
||||
while ((tok = strtok_r(str, sep, &state))) {
|
||||
if (_startswithi(tok, "_HOST")) {
|
||||
tok += 5;
|
||||
}
|
||||
if (!strcasecmp(tok, "linux")) {
|
||||
bits |= _HOSTLINUX;
|
||||
} else if (!strcasecmp(tok, "metal")) {
|
||||
bits |= _HOSTMETAL;
|
||||
} else if (!strcasecmp(tok, "windows") || //
|
||||
!strcasecmp(tok, "win") || //
|
||||
!strcasecmp(tok, "nt") || //
|
||||
!strcasecmp(tok, "pe")) {
|
||||
bits |= _HOSTWINDOWS;
|
||||
} else if (!strcasecmp(tok, "xnu") || //
|
||||
!strcasecmp(tok, "mac") || //
|
||||
!strcasecmp(tok, "macos") || //
|
||||
!strcasecmp(tok, "macho") || //
|
||||
!strcasecmp(tok, "darwin")) {
|
||||
bits |= _HOSTXNU;
|
||||
} else if (!strcasecmp(tok, "freebsd")) {
|
||||
bits |= _HOSTFREEBSD;
|
||||
} else if (!strcasecmp(tok, "openbsd")) {
|
||||
bits |= _HOSTOPENBSD;
|
||||
} else if (!strcasecmp(tok, "netbsd")) {
|
||||
bits |= _HOSTNETBSD;
|
||||
} else if (!strcasecmp(tok, "bsd")) {
|
||||
bits |= _HOSTFREEBSD | _HOSTOPENBSD | _HOSTNETBSD;
|
||||
} else if (!strcasecmp(tok, "elf")) {
|
||||
bits |=
|
||||
_HOSTMETAL | _HOSTLINUX | _HOSTFREEBSD | _HOSTOPENBSD | _HOSTNETBSD;
|
||||
} else if (!strcasecmp(tok, "unix")) {
|
||||
bits |= _HOSTLINUX | _HOSTFREEBSD | _HOSTOPENBSD | _HOSTNETBSD | _HOSTXNU;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
str = 0;
|
||||
}
|
||||
*out_bits = bits;
|
||||
return true;
|
||||
}
|
|
@ -18,6 +18,7 @@
|
|||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/calls/calls.h"
|
||||
#include "libc/intrin/bits.h"
|
||||
#include "libc/intrin/kprintf.h"
|
||||
#include "libc/limits.h"
|
||||
#include "libc/nt/struct/imageimportbyname.internal.h"
|
||||
#include "libc/nt/struct/imageimportdescriptor.internal.h"
|
||||
|
@ -27,10 +28,29 @@
|
|||
#include "libc/runtime/runtime.h"
|
||||
#include "libc/stdckdint.h"
|
||||
#include "libc/stdio/stdio.h"
|
||||
#include "libc/str/str.h"
|
||||
#include "libc/sysv/consts/map.h"
|
||||
#include "libc/sysv/consts/o.h"
|
||||
#include "libc/sysv/consts/prot.h"
|
||||
|
||||
/**
|
||||
* @fileoverview Linter for PE static executable files.
|
||||
*
|
||||
* Generating PE files from scratch is tricky. There's numerous things
|
||||
* that can go wrong, and operating systems don't explain what's wrong
|
||||
* when they refuse to run a program. This program can help illuminate
|
||||
* any issues with your generated binaries, with better error messages
|
||||
*/
|
||||
|
||||
struct Exe {
|
||||
char *map;
|
||||
size_t size;
|
||||
const char *path;
|
||||
struct NtImageNtHeaders *pe;
|
||||
struct NtImageSectionHeader *sections;
|
||||
uint32_t section_count;
|
||||
};
|
||||
|
||||
static wontreturn void Die(const char *thing, const char *reason) {
|
||||
tinyprint(2, thing, ": ", reason, "\n", NULL);
|
||||
exit(1);
|
||||
|
@ -41,6 +61,68 @@ static wontreturn void DieSys(const char *thing) {
|
|||
exit(1);
|
||||
}
|
||||
|
||||
static void LogPeSections(FILE *f, struct NtImageSectionHeader *p, size_t n) {
|
||||
size_t i;
|
||||
fprintf(f, "Name Offset RelativeVirtAddr FileSiz MemSiz Flg\n");
|
||||
for (i = 0; i < n; ++i) {
|
||||
fprintf(f, "%-8.8s 0x%06lx 0x%016lx 0x%06lx 0x%06lx %c%c%c\n", p[i].Name,
|
||||
p[i].PointerToRawData, p[i].VirtualAddress, p[i].SizeOfRawData,
|
||||
p[i].Misc.VirtualSize,
|
||||
p[i].Characteristics & kNtPeSectionMemRead ? 'R' : ' ',
|
||||
p[i].Characteristics & kNtPeSectionMemWrite ? 'W' : ' ',
|
||||
p[i].Characteristics & kNtPeSectionMemExecute ? 'E' : ' ');
|
||||
}
|
||||
}
|
||||
|
||||
// resolves relative virtual address
|
||||
//
|
||||
// this is a trivial process when an executable has been loaded properly
|
||||
// i.e. a separate mmap() call was made for each individual section; but
|
||||
// we've only mapped the executable file itself into memory; thus, we'll
|
||||
// need to remap a virtual address into a file offset to get the pointer
|
||||
//
|
||||
// returns pointer to image data, or null on error
|
||||
static void *GetRva(struct Exe *exe, uint32_t rva, uint32_t size) {
|
||||
int i;
|
||||
for (i = 0; i < exe->section_count; ++i) {
|
||||
if (exe->sections[i].VirtualAddress <= rva &&
|
||||
rva < exe->sections[i].VirtualAddress +
|
||||
exe->sections[i].Misc.VirtualSize) {
|
||||
if (rva + size <=
|
||||
exe->sections[i].VirtualAddress + exe->sections[i].Misc.VirtualSize) {
|
||||
return exe->map + exe->sections[i].PointerToRawData +
|
||||
(rva - exe->sections[i].VirtualAddress);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool HasControlCodes(const char *s) {
|
||||
int c;
|
||||
while ((c = *s++)) {
|
||||
if (isascii(c) && iscntrl(c)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void CheckPeImportByName(struct Exe *exe, uint32_t rva) {
|
||||
struct NtImageImportByName *hintname;
|
||||
if (rva & 1)
|
||||
Die(exe->path, "PE IMAGE_IMPORT_BY_NAME (hint name) structures must "
|
||||
"be 2-byte aligned");
|
||||
if (!(hintname = GetRva(exe, rva, sizeof(struct NtImageImportByName))))
|
||||
Die(exe->path, "PE import table RVA entry didn't reslove");
|
||||
if (!*hintname->Name)
|
||||
Die(exe->path, "PE imported function name is empty string");
|
||||
if (HasControlCodes(hintname->Name))
|
||||
Die(exe->path, "PE imported function name contains ascii control codes");
|
||||
}
|
||||
|
||||
static void CheckPe(const char *path, char *map, size_t size) {
|
||||
|
||||
int pagesz = 4096;
|
||||
|
@ -53,6 +135,8 @@ static void CheckPe(const char *path, char *map, size_t size) {
|
|||
uint32_t pe_offset;
|
||||
if ((pe_offset = READ32LE(map + 60)) >= size)
|
||||
Die(path, "PE header offset points past end of image");
|
||||
if (pe_offset & 7)
|
||||
Die(path, "PE header offset must possess an 8-byte alignment");
|
||||
if (pe_offset + sizeof(struct NtImageNtHeaders) > size)
|
||||
Die(path, "PE mandatory headers overlap end of image");
|
||||
struct NtImageNtHeaders *pe = (struct NtImageNtHeaders *)(map + pe_offset);
|
||||
|
@ -93,12 +177,14 @@ static void CheckPe(const char *path, char *map, size_t size) {
|
|||
Die(path, "PE FileHeader.Characteristics needs "
|
||||
"IMAGE_FILE_LARGE_ADDRESS_AWARE if ImageBase > INT_MAX");
|
||||
|
||||
// fixup pe header
|
||||
// validate the size of the pe optional headers
|
||||
int len;
|
||||
if (ckd_mul(&len, pe->OptionalHeader.NumberOfRvaAndSizes, 8) ||
|
||||
ckd_add(&len, len, sizeof(struct NtImageOptionalHeader)) ||
|
||||
pe->FileHeader.SizeOfOptionalHeader < len)
|
||||
Die(path, "PE SizeOfOptionalHeader too small");
|
||||
if (ckd_mul(&len, pe->OptionalHeader.NumberOfRvaAndSizes,
|
||||
sizeof(struct NtImageDataDirectory)) ||
|
||||
ckd_add(&len, len, sizeof(struct NtImageOptionalHeader)))
|
||||
Die(path, "encountered overflow computing PE SizeOfOptionalHeader");
|
||||
if (pe->FileHeader.SizeOfOptionalHeader != len)
|
||||
Die(path, "PE SizeOfOptionalHeader had incorrect value");
|
||||
if (len > size || (char *)&pe->OptionalHeader + len > map + size)
|
||||
Die(path, "PE OptionalHeader overflows image");
|
||||
|
||||
|
@ -167,34 +253,60 @@ static void CheckPe(const char *path, char *map, size_t size) {
|
|||
}
|
||||
}
|
||||
|
||||
#if 0 // broken
|
||||
// create an object for our portable executable
|
||||
struct Exe exe[1] = {{
|
||||
.pe = pe,
|
||||
.path = path,
|
||||
.map = map,
|
||||
.size = size,
|
||||
.sections = sections,
|
||||
.section_count = pe->FileHeader.NumberOfSections,
|
||||
}};
|
||||
|
||||
// validate dll imports
|
||||
if (pe->OptionalHeader.NumberOfRvaAndSizes >= 2 &&
|
||||
pe->OptionalHeader.DataDirectory[kNtImageDirectoryEntryImport]
|
||||
.VirtualAddress) {
|
||||
struct NtImageImportDescriptor *idt =
|
||||
(struct NtImageImportDescriptor
|
||||
*)(map +
|
||||
pe->OptionalHeader.DataDirectory[kNtImageDirectoryEntryImport]
|
||||
.VirtualAddress);
|
||||
for (int i = 0;; ++i) {
|
||||
if ((char *)(idt + i + sizeof(*idt)) > map + size)
|
||||
Die(path, "PE IMAGE_DIRECTORY_ENTRY_IMPORT points outside image");
|
||||
if (!idt[i].ImportLookupTable) break;
|
||||
uint64_t *ilt = (uint64_t *)(map + idt[i].ImportLookupTable);
|
||||
for (int j = 0;; ++j) {
|
||||
if ((char *)(ilt + j + sizeof(*ilt)) > map + size)
|
||||
Die(path, "PE ImportLookupTable points outside image");
|
||||
if (!ilt[j]) break;
|
||||
struct NtImageImportByName *func =
|
||||
(struct NtImageImportByName *)(map + ilt[j]);
|
||||
struct NtImageDataDirectory *ddImports =
|
||||
exe->pe->OptionalHeader.DataDirectory + kNtImageDirectoryEntryImport;
|
||||
if (exe->pe->OptionalHeader.NumberOfRvaAndSizes >= 2 && ddImports->Size) {
|
||||
if (ddImports->Size % sizeof(struct NtImageImportDescriptor) != 0)
|
||||
Die(exe->path, "PE Imports data directory entry Size should be a "
|
||||
"multiple of sizeof(IMAGE_IMPORT_DESCRIPTOR)");
|
||||
if (ddImports->VirtualAddress & 3)
|
||||
Die(exe->path, "PE IMAGE_IMPORT_DESCRIPTOR table must be 4-byte aligned");
|
||||
struct NtImageImportDescriptor *idt;
|
||||
if (!(idt = GetRva(exe, ddImports->VirtualAddress, ddImports->Size)))
|
||||
Die(exe->path, "couldn't resolve VirtualAddress/Size RVA of PE Import "
|
||||
"Directory Table to within a defined PE section");
|
||||
if (idt->ImportLookupTable >= exe->size)
|
||||
Die(exe->path, "Import Directory Table VirtualAddress/Size RVA resolved "
|
||||
"to dense unrelated binary content");
|
||||
for (int i = 0; idt->ImportLookupTable; ++i, ++idt) {
|
||||
char *dllname;
|
||||
if (!(dllname = GetRva(exe, idt->DllNameRva, 2)))
|
||||
Die(exe->path, "PE DllNameRva doesn't resolve to a PE section");
|
||||
if (!*dllname)
|
||||
Die(exe->path, "PE import DllNameRva pointed to empty string");
|
||||
if (HasControlCodes(dllname))
|
||||
Die(exe->path, "PE import DllNameRva contained ascii control codes");
|
||||
if (idt->ImportLookupTable & 7)
|
||||
Die(exe->path, "PE ImportLookupTable must be 8-byte aligned");
|
||||
if (idt->ImportAddressTable & 7)
|
||||
Die(exe->path, "PE ImportAddressTable must be 8-byte aligned");
|
||||
uint64_t *ilt, *iat;
|
||||
if (!(ilt = GetRva(exe, idt->ImportLookupTable, 8)))
|
||||
Die(exe->path, "PE ImportLookupTable RVA didn't resolve to a section");
|
||||
if (!(iat = GetRva(exe, idt->ImportAddressTable, 8)))
|
||||
Die(exe->path, "PE ImportAddressTable RVA didn't resolve to a section");
|
||||
for (int j = 0;; ++j, ++ilt, ++iat) {
|
||||
if (*ilt != *iat) {
|
||||
kprintf("i=%d j=%d ilt=%#x iat=%#x\n", i, j, *ilt, *iat);
|
||||
Die(exe->path, "PE ImportLookupTable and ImportAddressTable should "
|
||||
"have identical content");
|
||||
}
|
||||
if (!*ilt) break;
|
||||
CheckPeImportByName(exe, *ilt);
|
||||
}
|
||||
uint64_t *iat = (uint64_t *)(map + idt[i].ImportAddressTable);
|
||||
if ((char *)(iat + sizeof(*iat)) > map + size)
|
||||
Die(path, "PE ImportAddressTable points outside image");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
|
@ -202,6 +314,9 @@ int main(int argc, char *argv[]) {
|
|||
void *map;
|
||||
ssize_t size;
|
||||
const char *path;
|
||||
#ifndef NDEBUG
|
||||
ShowCrashReports();
|
||||
#endif
|
||||
for (i = 1; i < argc; ++i) {
|
||||
path = argv[i];
|
||||
if ((fd = open(path, O_RDONLY)) == -1) DieSys(path);
|
||||
|
|
|
@ -483,8 +483,8 @@ void HandleClient(void) {
|
|||
goto TerminateJob;
|
||||
}
|
||||
if (received > 0) {
|
||||
WARNF("%s client sent %d unexpected bytes so killing job", exename,
|
||||
received);
|
||||
WARNF("%s client sent %d bytes unexpected bytes so killing job",
|
||||
exename, received);
|
||||
goto TerminateJob;
|
||||
}
|
||||
if (received != MBEDTLS_ERR_SSL_WANT_READ) {
|
||||
|
|
|
@ -226,13 +226,14 @@ static void showpeoptionalheader(struct NtImageOptionalHeader *opt) {
|
|||
}
|
||||
}
|
||||
|
||||
static void ShowIlt(int64_t *ilt) {
|
||||
printf("\n");
|
||||
showtitle(basename(path), "windows", "import lookup table (ilt)", 0, 0);
|
||||
static void ShowIlt(uint32_t rva) {
|
||||
int64_t *ilt, *ilt0;
|
||||
ilt = ilt0 = GetRva(rva);
|
||||
do {
|
||||
printf("\n");
|
||||
show(".quad", format(b1, "%#lx", *ilt),
|
||||
_gc(xasprintf("@%#lx", (intptr_t)ilt - (intptr_t)mz)));
|
||||
_gc(xasprintf("rva=%#lx off=%#lx", (char *)ilt - (char *)ilt0 + rva,
|
||||
(intptr_t)ilt - (intptr_t)mz)));
|
||||
if (*ilt) {
|
||||
char *hint = GetRva(*ilt);
|
||||
printf("/\t.short\t%d\t\t\t# @%#lx\n", READ16LE(hint),
|
||||
|
@ -244,11 +245,11 @@ static void ShowIlt(int64_t *ilt) {
|
|||
} while (*ilt++);
|
||||
}
|
||||
|
||||
static void ShowIat(char *iat, size_t size) {
|
||||
static void ShowIdt(char *idt, size_t size) {
|
||||
char *p, *e;
|
||||
printf("\n");
|
||||
showtitle(basename(path), "windows", "import address table (iat)", 0, 0);
|
||||
for (p = iat, e = iat + size; p + 20 <= e; p += 20) {
|
||||
showtitle(basename(path), "windows", "import descriptor table (idt)", 0, 0);
|
||||
for (p = idt, e = idt + size; p + 20 <= e; p += 20) {
|
||||
printf("\n");
|
||||
show(".long", format(b1, "%#x", READ32LE(p)),
|
||||
_gc(xasprintf("ImportLookupTable RVA @%#lx",
|
||||
|
@ -262,9 +263,18 @@ static void ShowIat(char *iat, size_t size) {
|
|||
show(".long", format(b1, "%#x", READ32LE(p + 16)),
|
||||
"ImportAddressTable RVA");
|
||||
}
|
||||
for (p = iat, e = iat + size; p + 20 <= e; p += 20) {
|
||||
for (p = idt, e = idt + size; p + 20 <= e; p += 20) {
|
||||
if (READ32LE(p)) {
|
||||
ShowIlt(GetRva(READ32LE(p)));
|
||||
printf("\n");
|
||||
showtitle(basename(path), "windows", "import lookup table (ilt)", 0, 0);
|
||||
ShowIlt(READ32LE(p));
|
||||
}
|
||||
}
|
||||
for (p = idt, e = idt + size; p + 20 <= e; p += 20) {
|
||||
if (READ32LE(p)) {
|
||||
printf("\n");
|
||||
showtitle(basename(path), "windows", "import address table (iat)", 0, 0);
|
||||
ShowIlt(READ32LE(p + 16));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -342,7 +352,7 @@ static void showpeheader(struct NtImageNtHeaders *pe) {
|
|||
pe->FileHeader.NumberOfSections *
|
||||
sizeof(struct NtImageSectionHeader)),
|
||||
pe->FileHeader.NumberOfSections);
|
||||
ShowIat(GetRva(pe->OptionalHeader.DataDirectory[kNtImageDirectoryEntryImport]
|
||||
ShowIdt(GetRva(pe->OptionalHeader.DataDirectory[kNtImageDirectoryEntryImport]
|
||||
.VirtualAddress),
|
||||
pe->OptionalHeader.DataDirectory[kNtImageDirectoryEntryImport].Size);
|
||||
}
|
||||
|
|
|
@ -206,6 +206,7 @@
|
|||
"__conceal"
|
||||
"__expropriate"
|
||||
"__yoink"
|
||||
"__dll_import"
|
||||
"__static_yoink"
|
||||
"PYTHON_YOINK"
|
||||
"PYTHON_PROVIDE"
|
||||
|
|
21
tool/hello/hello-pe.c
Normal file
21
tool/hello/hello-pe.c
Normal file
|
@ -0,0 +1,21 @@
|
|||
#if 0
|
||||
/*─────────────────────────────────────────────────────────────────╗
|
||||
│ To the extent possible under law, Justine Tunney has waived │
|
||||
│ all copyright and related or neighboring rights to this file, │
|
||||
│ as it is written in the following disclaimers: │
|
||||
│ • http://unlicense.org/ │
|
||||
│ • http://creativecommons.org/publicdomain/zero/1.0/ │
|
||||
╚─────────────────────────────────────────────────────────────────*/
|
||||
#endif
|
||||
#include "tool/build/elf2pe.h"
|
||||
|
||||
#define STD_OUTPUT_HANDLE -11u
|
||||
|
||||
__dll_import("kernel32.dll", long, GetStdHandle, (unsigned));
|
||||
__dll_import("kernel32.dll", int, WriteFile,
|
||||
(long, const void *, unsigned, unsigned *, void *));
|
||||
|
||||
__attribute__((__ms_abi__)) long WinMain(void) {
|
||||
WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), "hello world\n", 12, 0, 0);
|
||||
return 0;
|
||||
}
|
|
@ -1,433 +1,18 @@
|
|||
/*-*- 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 2023 Justine Alexandra Roberts Tunney │
|
||||
│ │
|
||||
│ Permission to use, copy, modify, and/or distribute this software for │
|
||||
│ any purpose with or without fee is hereby granted, provided that the │
|
||||
│ above copyright notice and this permission notice appear in all copies. │
|
||||
│ │
|
||||
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
|
||||
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
|
||||
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
|
||||
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
|
||||
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
|
||||
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
|
||||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#if 0
|
||||
/*─────────────────────────────────────────────────────────────────╗
|
||||
│ To the extent possible under law, Justine Tunney has waived │
|
||||
│ all copyright and related or neighboring rights to this file, │
|
||||
│ as it is written in the following disclaimers: │
|
||||
│ • http://unlicense.org/ │
|
||||
│ • http://creativecommons.org/publicdomain/zero/1.0/ │
|
||||
╚─────────────────────────────────────────────────────────────────*/
|
||||
#endif
|
||||
#include "libc/calls/calls.h"
|
||||
|
||||
#define _HOSTLINUX 1
|
||||
#define _HOSTWINDOWS 4
|
||||
#define _HOSTXNU 8
|
||||
#define _HOSTOPENBSD 16
|
||||
#define _HOSTFREEBSD 32
|
||||
#define _HOSTNETBSD 64
|
||||
|
||||
#ifndef SUPPORT_VECTOR
|
||||
#define SUPPORT_VECTOR -1
|
||||
#ifndef TINY
|
||||
__static_yoink("zipos"); // so apelink embeds symbol table
|
||||
#endif
|
||||
|
||||
#ifdef __aarch64__
|
||||
#define IsAarch64() 1
|
||||
#else
|
||||
#define IsAarch64() 0
|
||||
#endif
|
||||
|
||||
#define SupportsLinux() (SUPPORT_VECTOR & _HOSTLINUX)
|
||||
#define SupportsXnu() (SUPPORT_VECTOR & _HOSTXNU)
|
||||
#define SupportsWindows() (SUPPORT_VECTOR & _HOSTWINDOWS)
|
||||
#define SupportsFreebsd() (SUPPORT_VECTOR & _HOSTFREEBSD)
|
||||
#define SupportsOpenbsd() (SUPPORT_VECTOR & _HOSTOPENBSD)
|
||||
#define SupportsNetbsd() (SUPPORT_VECTOR & _HOSTNETBSD)
|
||||
|
||||
#define IsLinux() (SupportsLinux() && __crt.os == _HOSTLINUX)
|
||||
#define IsXnu() (SupportsXnu() && __crt.os == _HOSTXNU)
|
||||
#define IsWindows() (SupportsWindows() && __crt.os == _HOSTWINDOWS)
|
||||
#define IsFreebsd() (SupportsFreebsd() && __crt.os == _HOSTFREEBSD)
|
||||
#define IsOpenbsd() (SupportsOpenbsd() && __crt.os == _HOSTOPENBSD)
|
||||
#define IsNetbsd() (SupportsNetbsd() && __crt.os == _HOSTNETBSD)
|
||||
|
||||
#define O_RDONLY 0
|
||||
#define PROT_NONE 0
|
||||
#define PROT_READ 1
|
||||
#define PROT_WRITE 2
|
||||
#define PROT_EXEC 4
|
||||
#define MAP_SHARED 1
|
||||
#define MAP_PRIVATE 2
|
||||
#define MAP_FIXED 16
|
||||
#define MAP_ANONYMOUS 32
|
||||
#define MAP_EXECUTABLE 4096
|
||||
#define MAP_NORESERVE 16384
|
||||
#define ELFCLASS32 1
|
||||
#define ELFDATA2LSB 1
|
||||
#define EM_NEXGEN32E 62
|
||||
#define EM_AARCH64 183
|
||||
#define ET_EXEC 2
|
||||
#define ET_DYN 3
|
||||
#define PT_LOAD 1
|
||||
#define PT_DYNAMIC 2
|
||||
#define PT_INTERP 3
|
||||
#define EI_CLASS 4
|
||||
#define EI_DATA 5
|
||||
#define PF_X 1
|
||||
#define PF_W 2
|
||||
#define PF_R 4
|
||||
#define AT_PHDR 3
|
||||
#define AT_PHENT 4
|
||||
#define AT_PHNUM 5
|
||||
#define AT_PAGESZ 6
|
||||
#define AT_EXECFN_LINUX 31
|
||||
#define AT_EXECFN_NETBSD 2014
|
||||
#define X_OK 1
|
||||
#define XCR0_SSE 2
|
||||
#define XCR0_AVX 4
|
||||
#define PR_SET_MM 35
|
||||
#define PR_SET_MM_EXE_FILE 13
|
||||
|
||||
#define EIO 5
|
||||
#define EBADF 9
|
||||
|
||||
#define kNtInvalidHandleValue -1L
|
||||
#define kNtStdInputHandle -10u
|
||||
#define kNtStdOutputHandle -11u
|
||||
#define kNtStdErrorHandle -12u
|
||||
|
||||
#define kNtFileTypeUnknown 0x0000
|
||||
#define kNtFileTypeDisk 0x0001
|
||||
#define kNtFileTypeChar 0x0002
|
||||
#define kNtFileTypePipe 0x0003
|
||||
#define kNtFileTypeRemote 0x8000
|
||||
|
||||
#define kNtGenericRead 0x80000000u
|
||||
#define kNtGenericWrite 0x40000000u
|
||||
|
||||
#define kNtFileShareRead 0x00000001u
|
||||
#define kNtFileShareWrite 0x00000002u
|
||||
#define kNtFileShareDelete 0x00000004u
|
||||
|
||||
#define kNtCreateNew 1
|
||||
#define kNtCreateAlways 2
|
||||
#define kNtOpenExisting 3
|
||||
#define kNtOpenAlways 4
|
||||
#define kNtTruncateExisting 5
|
||||
|
||||
#define kNtFileFlagOverlapped 0x40000000u
|
||||
#define kNtFileAttributeNotContentIndexed 0x00002000u
|
||||
#define kNtFileFlagBackupSemantics 0x02000000u
|
||||
#define kNtFileFlagOpenReparsePoint 0x00200000u
|
||||
#define kNtFileAttributeCompressed 0x00000800u
|
||||
#define kNtFileAttributeTemporary 0x00000100u
|
||||
#define kNtFileAttributeDirectory 0x00000010u
|
||||
|
||||
struct NtOverlapped {
|
||||
unsigned long Internal;
|
||||
unsigned long InternalHigh;
|
||||
union {
|
||||
struct {
|
||||
unsigned Offset;
|
||||
unsigned OffsetHigh;
|
||||
};
|
||||
void *Pointer;
|
||||
};
|
||||
long hEvent;
|
||||
};
|
||||
|
||||
#define __dll_import(DLL, RET, FUNC, ARGS) \
|
||||
extern RET(*__attribute__((__ms_abi__, __weak__)) FUNC) \
|
||||
ARGS __asm__("dll$" DLL ".dll$" #FUNC)
|
||||
|
||||
__dll_import("kernel32", void, ExitProcess, (unsigned));
|
||||
__dll_import("kernel32", int, CloseHandle, (long));
|
||||
__dll_import("kernel32", long, GetStdHandle, (unsigned));
|
||||
__dll_import("kernel32", int, ReadFile,
|
||||
(long, void *, unsigned, unsigned *, struct NtOverlapped *));
|
||||
__dll_import("kernel32", int, WriteFile,
|
||||
(long, const void *, unsigned, unsigned *, struct NtOverlapped *));
|
||||
|
||||
struct WinCrt {
|
||||
long fd2handle[3];
|
||||
};
|
||||
|
||||
struct Crt {
|
||||
int os;
|
||||
int argc;
|
||||
char **argv;
|
||||
char **envp;
|
||||
long *auxv;
|
||||
int pagesz;
|
||||
int gransz;
|
||||
struct WinCrt *wincrt;
|
||||
} __crt;
|
||||
|
||||
long SystemCall(long, long, long, long, long, long, long, int);
|
||||
long CallSystem(long a, long b, long c, long d, long e, long f, long g, int x) {
|
||||
if (IsXnu() && !IsAarch64()) x |= 0x2000000;
|
||||
return SystemCall(a, b, c, d, e, f, g, x);
|
||||
}
|
||||
|
||||
wontreturn void _Exit(int rc) {
|
||||
int numba;
|
||||
if (!IsWindows()) {
|
||||
if (IsLinux()) {
|
||||
if (IsAarch64()) {
|
||||
numba = 94;
|
||||
} else {
|
||||
numba = 60;
|
||||
}
|
||||
} else {
|
||||
numba = 1;
|
||||
}
|
||||
CallSystem(rc, 0, 0, 0, 0, 0, 0, numba);
|
||||
} else {
|
||||
ExitProcess((unsigned)rc << 8);
|
||||
}
|
||||
__builtin_unreachable();
|
||||
}
|
||||
|
||||
static int ConvertFdToWin32Handle(int fd, long *out_handle) {
|
||||
if (fd < 0 || fd > 2) return -EBADF;
|
||||
*out_handle = __crt.wincrt->fd2handle[fd];
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sys_close(int fd) {
|
||||
if (!IsWindows()) {
|
||||
int numba;
|
||||
if (IsLinux()) {
|
||||
if (IsAarch64()) {
|
||||
numba = 57;
|
||||
} else {
|
||||
numba = 3;
|
||||
}
|
||||
} else {
|
||||
numba = 6;
|
||||
}
|
||||
return CallSystem(fd, 0, 0, 0, 0, 0, 0, numba);
|
||||
} else {
|
||||
int rc;
|
||||
long handle;
|
||||
if (!(rc = ConvertFdToWin32Handle(fd, &handle))) {
|
||||
CloseHandle(handle);
|
||||
return 0;
|
||||
} else {
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ssize_t sys_write(int fd, const void *data, size_t size) {
|
||||
if (!IsWindows()) {
|
||||
int numba;
|
||||
if (IsLinux()) {
|
||||
if (IsAarch64()) {
|
||||
numba = 64;
|
||||
} else {
|
||||
numba = 1;
|
||||
}
|
||||
} else {
|
||||
numba = 4;
|
||||
}
|
||||
return CallSystem(fd, (long)data, size, 0, 0, 0, 0, numba);
|
||||
} else {
|
||||
ssize_t rc;
|
||||
long handle;
|
||||
uint32_t got;
|
||||
if (!(rc = ConvertFdToWin32Handle(fd, &handle))) {
|
||||
if (WriteFile(handle, data, size, &got, 0)) {
|
||||
return got;
|
||||
} else {
|
||||
return -EIO;
|
||||
}
|
||||
} else {
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ssize_t sys_pread(int fd, void *data, size_t size, long off) {
|
||||
int numba;
|
||||
if (IsLinux()) {
|
||||
if (IsAarch64()) {
|
||||
numba = 0x043;
|
||||
} else {
|
||||
numba = 0x011;
|
||||
}
|
||||
} else if (IsXnu()) {
|
||||
numba = 0x099;
|
||||
} else if (IsFreebsd()) {
|
||||
numba = 0x1db;
|
||||
} else if (IsOpenbsd()) {
|
||||
numba = 0x0a9;
|
||||
} else if (IsNetbsd()) {
|
||||
numba = 0x0ad;
|
||||
} else {
|
||||
__builtin_unreachable();
|
||||
}
|
||||
return SystemCall(fd, (long)data, size, off, off, 0, 0, numba);
|
||||
}
|
||||
|
||||
int sys_access(const char *path, int mode) {
|
||||
if (IsLinux() && IsAarch64()) {
|
||||
return SystemCall(-100, (long)path, mode, 0, 0, 0, 0, 48);
|
||||
} else {
|
||||
return CallSystem((long)path, mode, 0, 0, 0, 0, 0, IsLinux() ? 21 : 33);
|
||||
}
|
||||
}
|
||||
|
||||
int sys_open(const char *path, int flags, int mode) {
|
||||
if (IsLinux() && IsAarch64()) {
|
||||
return SystemCall(-100, (long)path, flags, mode, 0, 0, 0, 56);
|
||||
} else {
|
||||
return CallSystem((long)path, flags, mode, 0, 0, 0, 0, IsLinux() ? 2 : 5);
|
||||
}
|
||||
}
|
||||
|
||||
int sys_mprotect(void *addr, size_t size, int prot) {
|
||||
int numba;
|
||||
// all unix operating systems define the same values for prot
|
||||
if (IsLinux()) {
|
||||
if (IsAarch64()) {
|
||||
numba = 226;
|
||||
} else {
|
||||
numba = 10;
|
||||
}
|
||||
} else {
|
||||
numba = 74;
|
||||
}
|
||||
return CallSystem((long)addr, size, prot, 0, 0, 0, 0, numba);
|
||||
}
|
||||
|
||||
long sys_mmap(void *addr, size_t size, int prot, int flags, int fd, long off) {
|
||||
long numba;
|
||||
if (IsLinux()) {
|
||||
if (IsAarch64()) {
|
||||
numba = 222;
|
||||
} else {
|
||||
numba = 9;
|
||||
}
|
||||
} else {
|
||||
// this flag isn't supported on non-Linux systems. since it's just
|
||||
// hinting the kernel, it should be inconsequential to just ignore
|
||||
flags &= ~MAP_NORESERVE;
|
||||
// this flag is ignored by Linux and it overlaps with bsd map_anon
|
||||
flags &= ~MAP_EXECUTABLE;
|
||||
if (flags & MAP_ANONYMOUS) {
|
||||
// all bsd-style operating systems share the same mag_anon magic
|
||||
flags &= ~MAP_ANONYMOUS;
|
||||
flags |= 4096;
|
||||
}
|
||||
if (IsFreebsd()) {
|
||||
numba = 477;
|
||||
} else if (IsOpenbsd()) {
|
||||
numba = 49;
|
||||
} else {
|
||||
numba = 197; // xnu, netbsd
|
||||
}
|
||||
}
|
||||
return CallSystem((long)addr, size, prot, flags, fd, off, off, numba);
|
||||
}
|
||||
|
||||
wontreturn void __unix_start(long di, long *sp, char os) {
|
||||
|
||||
// detect freebsd
|
||||
if (SupportsXnu() && os == _HOSTXNU) {
|
||||
os = _HOSTXNU;
|
||||
} else if (SupportsFreebsd() && di) {
|
||||
os = _HOSTFREEBSD;
|
||||
sp = (long *)di;
|
||||
}
|
||||
|
||||
// extract arguments
|
||||
__crt.argc = *sp;
|
||||
__crt.argv = (char **)(sp + 1);
|
||||
__crt.envp = (char **)(sp + 1 + __crt.argc + 1);
|
||||
__crt.auxv = (long *)(sp + 1 + __crt.argc + 1);
|
||||
for (;;) {
|
||||
if (!*__crt.auxv++) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// detect openbsd
|
||||
if (SupportsOpenbsd() && !os && !__crt.auxv[0]) {
|
||||
os = _HOSTOPENBSD;
|
||||
}
|
||||
|
||||
// detect netbsd and find end of words
|
||||
__crt.pagesz = IsAarch64() ? 16384 : 4096;
|
||||
for (long *ap = __crt.auxv; ap[0]; ap += 2) {
|
||||
if (ap[0] == AT_PAGESZ) {
|
||||
__crt.pagesz = ap[1];
|
||||
} else if (SupportsNetbsd() && !os && ap[0] == AT_EXECFN_NETBSD) {
|
||||
os = _HOSTNETBSD;
|
||||
}
|
||||
}
|
||||
if (!os) {
|
||||
os = _HOSTLINUX;
|
||||
}
|
||||
__crt.gransz = __crt.pagesz;
|
||||
__crt.os = os;
|
||||
|
||||
// call startup routines
|
||||
typedef int init_f(int, char **, char **, long *);
|
||||
extern init_f *__init_array_start[] __attribute__((__weak__));
|
||||
extern init_f *__init_array_end[] __attribute__((__weak__));
|
||||
for (init_f **fp = __init_array_end; fp-- > __init_array_start;) {
|
||||
(*fp)(__crt.argc, __crt.argv, __crt.envp, __crt.auxv);
|
||||
}
|
||||
|
||||
// call program
|
||||
int main(int, char **, char **);
|
||||
_Exit(main(__crt.argc, __crt.argv, __crt.envp));
|
||||
}
|
||||
|
||||
wontreturn void __win32_start(void) {
|
||||
long sp[] = {
|
||||
0, // argc
|
||||
0, // empty argv
|
||||
0, // empty environ
|
||||
0, // empty auxv
|
||||
};
|
||||
__crt.wincrt = &(struct WinCrt){
|
||||
.fd2handle =
|
||||
{
|
||||
GetStdHandle(kNtStdInputHandle),
|
||||
GetStdHandle(kNtStdOutputHandle),
|
||||
GetStdHandle(kNtStdErrorHandle),
|
||||
},
|
||||
};
|
||||
__unix_start(0, sp, _HOSTWINDOWS);
|
||||
}
|
||||
|
||||
ssize_t print(int fd, const char *s, ...) {
|
||||
int c;
|
||||
unsigned n;
|
||||
va_list va;
|
||||
char b[512];
|
||||
va_start(va, s);
|
||||
for (n = 0; s; s = va_arg(va, const char *)) {
|
||||
while ((c = *s++)) {
|
||||
if (n < sizeof(b)) {
|
||||
b[n++] = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
va_end(va);
|
||||
return sys_write(fd, b, n);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
char data[10] = "sup";
|
||||
char bss[0xf9];
|
||||
_Thread_local char tdata[10] = "hello";
|
||||
_Thread_local char tbss[0xf9];
|
||||
|
||||
_Section(".love") int main(int argc, char **argv, char **envp) {
|
||||
if (argc == 666) {
|
||||
bss[0] = data[0];
|
||||
tbss[0] = tdata[0];
|
||||
}
|
||||
print(1, "hello world\n", NULL);
|
||||
int main(int argc, char *argv[]) {
|
||||
write(2, "hello world\n", 12);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
#-*-mode:makefile-gmake;indent-tabs-mode:t;tab-width:8;coding:utf-8-*-┐
|
||||
#───vi: set et ft=make ts=8 tw=8 fenc=utf-8 :vi───────────────────────┘
|
||||
|
||||
# qemu-user execve() is broken so we need to build/bootstrap/ commands
|
||||
ifeq ($(ARCH), x86_64)
|
||||
|
||||
PKGS += TOOL_HELLO
|
||||
|
||||
TOOL_HELLO_FILES := $(wildcard tool/hello/*)
|
||||
|
@ -9,20 +12,77 @@ TOOL_HELLO_SRCS_C = $(filter %.c,$(TOOL_HELLO_FILES))
|
|||
TOOL_HELLO_SRCS_S = $(filter %.S,$(TOOL_HELLO_FILES))
|
||||
TOOL_HELLO_SRCS = $(TOOL_HELLO_SRCS_C) $(TOOL_HELLO_SRCS_S)
|
||||
TOOL_HELLO_OBJS = $(TOOL_HELLO_SRCS_C:%.c=o/$(MODE)/%.o) $(TOOL_HELLO_SRCS_S:%.S=o/$(MODE)/%.o)
|
||||
TOOL_HELLO_BINS = o/$(MODE)/tool/hello/hello.com.dbg
|
||||
TOOL_HELLO_BINS = $(TOOL_HELLO_COMS) $(TOOL_HELLO_COMS:%=%.dbg)
|
||||
|
||||
o/$(MODE)/tool/hello/hello.com.dbg: \
|
||||
o/$(MODE)/tool/hello/systemcall.o \
|
||||
o/$(MODE)/tool/hello/hello.o \
|
||||
o/$(MODE)/tool/hello/start.o
|
||||
@$(COMPILE) -ALINK.elf $(LINK) $(LINKARGS) $(OUTPUT_OPTION) -q -zmax-page-size=4096
|
||||
TOOL_HELLO_COMS = \
|
||||
o/$(MODE)/tool/hello/hello.com \
|
||||
o/$(MODE)/tool/hello/hello-pe.com \
|
||||
o/$(MODE)/tool/hello/hello-elf.com \
|
||||
o/$(MODE)/tool/hello/hello-unix.com
|
||||
|
||||
o/$(MODE)/tool/hello/hello.com: \
|
||||
o/$(MODE)/tool/hello/hello.com.dbg \
|
||||
TOOL_HELLO_DIRECTDEPS = \
|
||||
LIBC_CALLS \
|
||||
LIBC_RUNTIME \
|
||||
LIBC_ZIPOS
|
||||
|
||||
TOOL_HELLO_DEPS := \
|
||||
$(call uniq,$(foreach x,$(TOOL_HELLO_DIRECTDEPS),$($(x))))
|
||||
|
||||
o/$(MODE)/tool/hello/hello.pkg: \
|
||||
$(TOOL_HELLO_OBJS) \
|
||||
$(foreach x,$(TOOL_HELLO_DIRECTDEPS),$($(x)_A).pkg)
|
||||
|
||||
# generates debuggable executable using gcc
|
||||
o/$(MODE)/tool/hello/hello.com.dbg: \
|
||||
$(TOOL_HELLO_DEPS) \
|
||||
o/$(MODE)/tool/hello/hello.o \
|
||||
o/$(MODE)/tool/hello/hello.pkg \
|
||||
$(CRT) \
|
||||
$(APE)
|
||||
@$(APELINK)
|
||||
|
||||
# uses apelink to turn it into an ape executable
|
||||
# support vector is set to all operating systems
|
||||
o/$(MODE)/tool/hello/hello.com: \
|
||||
o/$(MODE)/tool/hello/hello.com.dbg \
|
||||
o/$(MODE)/tool/build/apelink.com \
|
||||
o/$(MODE)/tool/build/pecheck.com \
|
||||
o/$(MODE)/ape/ape.elf
|
||||
@$(COMPILE) -ALINK.ape o/$(MODE)/tool/build/apelink.com -o $@ -l o/$(MODE)/ape/ape.elf $<
|
||||
@$(COMPILE) -APECHECK -wT$@ o/$(MODE)/tool/build/pecheck.com $@
|
||||
|
||||
# uses apelink to generate elf-only executable
|
||||
# support vector = linux/freebsd/openbsd/netbsd/metal
|
||||
o/$(MODE)/tool/hello/hello-elf.com: \
|
||||
o/$(MODE)/tool/hello/hello.com.dbg \
|
||||
o/$(MODE)/tool/build/apelink.com \
|
||||
o/$(MODE)/ape/ape.elf
|
||||
@$(COMPILE) -ALINK.ape o/$(MODE)/tool/build/apelink.com -s elf -o $@ -l o/$(MODE)/ape/ape.elf $<
|
||||
|
||||
# uses apelink to generate non-pe ape executable
|
||||
# support vector = macos/linux/freebsd/openbsd/netbsd
|
||||
# - great way to avoid attention from bad virus scanners
|
||||
# - creates tinier executable by reducing alignment requirement
|
||||
o/$(MODE)/tool/hello/hello-unix.com: \
|
||||
o/$(MODE)/tool/hello/hello.com.dbg \
|
||||
o/$(MODE)/tool/build/apelink.com \
|
||||
o/$(MODE)/ape/ape.elf
|
||||
@$(COMPILE) -ALINK.ape o/$(MODE)/tool/build/apelink.com -s unix -o $@ -l o/$(MODE)/ape/ape.elf $<
|
||||
|
||||
# elf2pe generates optimal pe binaries
|
||||
# windows is the only platform supported
|
||||
# doesn't depend on ape or the cosmopolitan c library
|
||||
o/$(MODE)/tool/hello/hello-pe.com.dbg: \
|
||||
o/$(MODE)/tool/hello/hello-pe.o
|
||||
@$(COMPILE) -ALINK.elf $(LINK) $(LINKARGS) $(OUTPUT_OPTION) -q -e WinMain
|
||||
o/$(MODE)/tool/hello/hello-pe.com: \
|
||||
o/$(MODE)/tool/hello/hello-pe.com.dbg \
|
||||
o/$(MODE)/tool/build/elf2pe.com
|
||||
o/$(MODE)/tool/build/elf2pe.com -o $@ $<
|
||||
@$(COMPILE) -AELF2PE o/$(MODE)/tool/build/elf2pe.com -o $@ $<
|
||||
|
||||
$(TOOL_HELLO_OBJS): tool/hello/hello.mk
|
||||
|
||||
.PHONY: o/$(MODE)/tool/hello
|
||||
o/$(MODE)/tool/hello: $(TOOL_HELLO_BINS)
|
||||
|
||||
endif
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│
|
||||
│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2023 Justine Alexandra Roberts Tunney │
|
||||
│ │
|
||||
│ Permission to use, copy, modify, and/or distribute this software for │
|
||||
│ any purpose with or without fee is hereby granted, provided that the │
|
||||
│ above copyright notice and this permission notice appear in all copies. │
|
||||
│ │
|
||||
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
|
||||
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
|
||||
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
|
||||
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
|
||||
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
|
||||
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
|
||||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/macros.internal.h"
|
||||
|
||||
_apple: mov $8,%dl
|
||||
_start: mov %rsp,%rsi
|
||||
call __unix_start
|
||||
call __win32_start // prevent --gc-sections
|
||||
.weak __win32_start
|
||||
.endfn _start,globl
|
||||
.endfn _apple,globl
|
|
@ -1,57 +0,0 @@
|
|||
/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│
|
||||
│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2023 Justine Alexandra Roberts Tunney │
|
||||
│ │
|
||||
│ Permission to use, copy, modify, and/or distribute this software for │
|
||||
│ any purpose with or without fee is hereby granted, provided that the │
|
||||
│ above copyright notice and this permission notice appear in all copies. │
|
||||
│ │
|
||||
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
|
||||
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
|
||||
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
|
||||
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
|
||||
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
|
||||
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
|
||||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/macros.internal.h"
|
||||
|
||||
// Invokes system call.
|
||||
//
|
||||
// This function has eight parameters. The first seven are for
|
||||
// arguments passed along to the system call. The eight is for
|
||||
// the magic number that indicates which system call is called
|
||||
//
|
||||
// The return value follows the Linux kernel convention, where
|
||||
// errors are returned as `-errno`. BSD systems are normalized
|
||||
// to follow this convention automatically.
|
||||
//
|
||||
// It's important to use a function call wrapper when invoking
|
||||
// syscall, because BSD kernels will unpredictably clobber any
|
||||
// volatile registers (unlike Linux). There's no overhead with
|
||||
// the extra call since a system call takes like a microsecond
|
||||
//
|
||||
// @return negative errno above -4096ul on error
|
||||
SystemCall:
|
||||
#ifdef __aarch64__
|
||||
mov x8,x7
|
||||
mov x16,x7
|
||||
mov x9,0
|
||||
adds x9,x9,0
|
||||
svc 0
|
||||
bcs 1f
|
||||
ret
|
||||
1: neg x0,x0
|
||||
ret
|
||||
#else
|
||||
mov %rcx,%r10
|
||||
mov 16(%rsp),%eax
|
||||
clc
|
||||
syscall
|
||||
jnc 1f
|
||||
neg %rax
|
||||
1: ret
|
||||
#endif
|
||||
.endfn SystemCall,globl
|
Loading…
Add table
Add a link
Reference in a new issue