diff --git a/.gitignore b/.gitignore index 2e92c1a..0d13d72 100644 --- a/.gitignore +++ b/.gitignore @@ -87,3 +87,8 @@ bin/* !bin/pcmake.exe !bin/libpcc.dll !bin/libpcc1.a +!bin/pcc-arm64.exe +!bin/pcc-riscv64.exe +!bin/pcc-asm.exe +!bin/pcc-get.exe +!bin/pcc-impdef.exe diff --git a/bin/pcc-arm64.exe b/bin/pcc-arm64.exe new file mode 100644 index 0000000..16baf03 Binary files /dev/null and b/bin/pcc-arm64.exe differ diff --git a/bin/pcc-asm.exe b/bin/pcc-asm.exe new file mode 100644 index 0000000..7c075cf Binary files /dev/null and b/bin/pcc-asm.exe differ diff --git a/bin/pcc-get.exe b/bin/pcc-get.exe new file mode 100644 index 0000000..b2168c3 Binary files /dev/null and b/bin/pcc-get.exe differ diff --git a/bin/pcc-impdef.exe b/bin/pcc-impdef.exe new file mode 100644 index 0000000..11e0f41 Binary files /dev/null and b/bin/pcc-impdef.exe differ diff --git a/bin/pcc-riscv64.exe b/bin/pcc-riscv64.exe new file mode 100644 index 0000000..cbce010 Binary files /dev/null and b/bin/pcc-riscv64.exe differ diff --git a/tools/pcc-asm.c b/tools/pcc-asm.c new file mode 100644 index 0000000..3e7c3a4 --- /dev/null +++ b/tools/pcc-asm.c @@ -0,0 +1,109 @@ +/* + * pcc-asm.c - disassemble a PCC-produced binary + * + * Shows the x86-64 assembly of a compiled program. + * Uses llvm-objdump if available, else objdump. + * + * Usage: + * pcc-asm program.exe disassemble all code + * pcc-asm program.exe main disassemble, filter by symbol + * + * License: MIT + */ + +#include +#include +#include + +#ifdef _WIN32 +# include +#endif + +static int file_exists(const char *f) +{ + FILE *p = fopen(f, "rb"); + if (p) { fclose(p); return 1; } + return 0; +} + +static const char *find_objdump(void) +{ + /* common locations, first match wins */ + static const char *cands[] = { + "llvm-objdump.exe", + "objdump.exe", + "C:\\Program Files\\LLVM\\bin\\llvm-objdump.exe", + "C:\\Clang+LLVM-22.1.7\\bin\\llvm-objdump.exe", + "D:\\Clang+LLVM-22.1.7\\bin\\llvm-objdump.exe", + "/usr/bin/objdump", + "/usr/bin/llvm-objdump", + NULL + }; + int i; + for (i = 0; cands[i]; i++) + if (file_exists(cands[i])) + return cands[i]; + return "objdump"; /* hope it's in PATH */ +} + +int main(int argc, char **argv) +{ + const char *objdump = find_objdump(); + const char *file; + const char *filter = NULL; + char cmd[1024]; + int len; + + if (argc < 2) { + fprintf(stderr, + "pcc-asm - disassemble a PCC binary\n" + "\n" + "Usage:\n" + " pcc-asm program.exe disassemble all code\n" + " pcc-asm program.exe main disassemble, filter by symbol\n" + "\n" + "Uses %s\n", objdump); + return 1; + } + + file = argv[1]; + if (argc > 2) + filter = argv[2]; + + if (!file_exists(file)) { + fprintf(stderr, "pcc-asm: cannot open '%s'\n", file); + return 1; + } + +#ifdef _WIN32 + len = snprintf(cmd, sizeof cmd, "\"%s\" -d \"%s\"", objdump, file); +#else + len = snprintf(cmd, sizeof cmd, "%s -d \"%s\"", objdump, file); +#endif + + if (filter) { + /* use objdump's symbol filter if supported, else grep-style */ + len += snprintf(cmd + len, sizeof cmd - len, " | findstr /c:\"%s\"", filter); + } + +#ifdef _WIN32 + { + STARTUPINFOA si; + PROCESS_INFORMATION pi; + char *cmdline = (char*)malloc(strlen(cmd) + 1); + if (!cmdline) return 1; + strcpy(cmdline, cmd); + memset(&si, 0, sizeof si); + si.cb = sizeof si; + if (CreateProcessA(NULL, cmdline, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) { + WaitForSingleObject(pi.hProcess, INFINITE); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } + free(cmdline); + } +#else + system(cmd); +#endif + return 0; +} diff --git a/tools/pcc-get.c b/tools/pcc-get.c new file mode 100644 index 0000000..1a0c4f3 --- /dev/null +++ b/tools/pcc-get.c @@ -0,0 +1,350 @@ +/* + * pcc-get.c - package manager for PCC + * + * Manages C libraries: install headers/libs, remove, list. + * + * Usage: + * pcc-get install [url] install a package + * is a name; if a directory of that name exists + * in the current dir or the packages/ dir, its files are + * copied. If [url] is given, it is downloaded first + * (zip/tar not extracted; use a flat file layout). + * pcc-get list list installed packages + * pcc-get remove remove a package + * pcc-get root print the install root + * + * A package is a directory containing: + * include/xxx.h -> /include/xxx.h + * lib/libxxx.a -> /lib/libxxx.a + * (files may also be placed directly: foo.h -> include/, + * libfoo.a -> lib/) + * + * License: MIT + */ + +#include +#include +#include +#include + +#ifdef _WIN32 +# include +# include +# define mkdir_p(p) _mkdir(p) +#else +# include +# include +# define mkdir_p(p) mkdir(p, 0755) +#endif +/* URLDownloadToFile from urlmon.dll (declared manually) */ +#ifdef _WIN32 +typedef long (__stdcall *URLDownloadToFileA_t)(void*, const char*, const char*, long, void*); +static URLDownloadToFileA_t g_url_dl = NULL; +static int http_download(const char *url, const char *dest) +{ + HMODULE h = LoadLibraryA("urlmon.dll"); + if (!h) return -1; + g_url_dl = (URLDownloadToFileA_t)GetProcAddress(h, "URLDownloadToFileA"); + if (!g_url_dl) return -1; + return g_url_dl(NULL, url, dest, 0, NULL) == 0 ? 0 : -1; +} +#else +static int http_download(const char *url, const char *dest) +{ + (void)url; (void)dest; + return -1; /* no download on non-Windows */ +} +#endif + +static void die(const char *msg) +{ + fprintf(stderr, "pcc-get: %s\n", msg); + exit(1); +} + +/* find install root: dir of pcc-get.exe, else cwd */ +static void get_root(char *root, int size) +{ +#ifdef _WIN32 + GetModuleFileNameA(NULL, root, size); + { + char *s = strrchr(root, '\\'); + if (!s) s = strrchr(root, '/'); + if (s) *s = 0; + else strcpy(root, "."); + } +#else + getcwd(root, size); +#endif +} + +static int file_exists(const char *f) +{ + FILE *p = fopen(f, "rb"); + if (p) { fclose(p); return 1; } + return 0; +} + +static int dir_exists(const char *d) +{ +#ifdef _WIN32 + DWORD attr = GetFileAttributesA(d); + return (attr != INVALID_FILE_ATTRIBUTES) && (attr & FILE_ATTRIBUTE_DIRECTORY); +#else + struct stat st; + return stat(d, &st) == 0 && S_ISDIR(st.st_mode); +#endif +} + +/* copy a file */ +static int copy_file(const char *src, const char *dst) +{ + FILE *in = fopen(src, "rb"); + FILE *out; + char buf[8192]; + size_t n; + if (!in) return -1; + out = fopen(dst, "wb"); + if (!out) { fclose(in); return -1; } + while ((n = fread(buf, 1, sizeof buf, in)) > 0) + fwrite(buf, 1, n, out); + fclose(in); + fclose(out); + return 0; +} + +/* case-insensitive compare of extensions */ +static int has_ext(const char *name, const char *ext) +{ + const char *dot = strrchr(name, '.'); + if (!dot) return 0; + return strcmp(dot + 1, ext) == 0 + || strcmp(dot + 1, ext + 1) == 0 /* ".a" vs "a" */ + || strcmp(dot, ext) == 0; +} + +static void ensure_dir(const char *path) +{ + mkdir_p(path); +} + +/* install a package directory */ +static int install_dir(const char *pkgdir, const char *root) +{ + char pattern[1024]; + char incdir[1024], libdir[1024]; + char src[1200], dst[1200]; + WIN32_FIND_DATAA fd; + HANDLE h; + int n = 0; + + snprintf(incdir, sizeof incdir, "%s\\include", root); + snprintf(libdir, sizeof libdir, "%s\\lib", root); + ensure_dir(incdir); + ensure_dir(libdir); + + /* copy *.h to include/ */ + snprintf(pattern, sizeof pattern, "%s\\*.h", pkgdir); + h = FindFirstFileA(pattern, &fd); + if (h != INVALID_HANDLE_VALUE) { + do { + if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { + snprintf(src, sizeof src, "%s\\%s", pkgdir, fd.cFileName); + snprintf(dst, sizeof dst, "%s\\%s", incdir, fd.cFileName); + if (copy_file(src, dst) == 0) { + printf(" install %s -> include/\n", fd.cFileName); + n++; + } + } + } while (FindNextFileA(h, &fd)); + FindClose(h); + } + + /* copy *.a and *.def to lib/ */ + snprintf(pattern, sizeof pattern, "%s\\*.a", pkgdir); + h = FindFirstFileA(pattern, &fd); + if (h != INVALID_HANDLE_VALUE) { + do { + if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { + snprintf(src, sizeof src, "%s\\%s", pkgdir, fd.cFileName); + snprintf(dst, sizeof dst, "%s\\%s", libdir, fd.cFileName); + if (copy_file(src, dst) == 0) { + printf(" install %s -> lib/\n", fd.cFileName); + n++; + } + } + } while (FindNextFileA(h, &fd)); + FindClose(h); + } + snprintf(pattern, sizeof pattern, "%s\\*.def", pkgdir); + h = FindFirstFileA(pattern, &fd); + if (h != INVALID_HANDLE_VALUE) { + do { + if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { + snprintf(src, sizeof src, "%s\\%s", pkgdir, fd.cFileName); + snprintf(dst, sizeof dst, "%s\\%s", libdir, fd.cFileName); + if (copy_file(src, dst) == 0) { + printf(" install %s -> lib/\n", fd.cFileName); + n++; + } + } + } while (FindNextFileA(h, &fd)); + FindClose(h); + } + return n; +} + +int main(int argc, char **argv) +{ + char root[1024]; + char pkgdir[1200]; + char listfile[1200]; + const char *cmd, *pkg = NULL, *url = NULL; + + get_root(root, sizeof root); + + if (argc < 2) { + fprintf(stderr, + "pcc-get - PCC package manager\n" + "\n" + "Usage:\n" + " pcc-get install [url] install a package\n" + " pcc-get list list installed packages\n" + " pcc-get remove remove a package\n" + " pcc-get root print install root\n" + "\n" + "Install root: %s\n", root); + return 1; + } + + cmd = argv[1]; + if (strcmp(cmd, "root") == 0) { + printf("%s\n", root); + return 0; + } + + if (strcmp(cmd, "list") == 0) { + char pattern[1024]; + WIN32_FIND_DATAA fd; + HANDLE h; + int n = 0; + snprintf(pattern, sizeof pattern, "%s\\lib\\*.a", root); + h = FindFirstFileA(pattern, &fd); + printf("Installed packages:\n"); + if (h != INVALID_HANDLE_VALUE) { + do { + if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { + char name[512]; + strncpy(name, fd.cFileName, sizeof name - 1); + name[sizeof name - 1] = 0; + /* strip lib prefix and .a suffix */ + if (strncmp(name, "lib", 3) == 0) { + char *dot = strrchr(name, '.'); + if (dot) *dot = 0; + printf(" %s\n", name + 3); + n++; + } + } + } while (FindNextFileA(h, &fd)); + FindClose(h); + } + if (n == 0) printf(" (none)\n"); + return 0; + } + + if (strcmp(cmd, "install") == 0) { + int n; + if (argc < 3) die("usage: pcc-get install [url]"); + pkg = argv[2]; + if (argc > 3) url = argv[3]; + + /* if url given, download to temp then use as package dir */ + if (url) { + char tmp[1200]; + snprintf(tmp, sizeof tmp, "%s\\%s.tmp", root, pkg); + printf("pcc-get: downloading %s ...\n", url); + if (http_download(url, tmp) != 0) { + fprintf(stderr, "pcc-get: download failed\n"); + return 1; + } + printf("pcc-get: downloaded %s\n", tmp); + /* treat the downloaded file as a single library file: + move to lib/ based on extension */ + { + char dst[1200]; + char name[512]; + const char *base = strrchr(url, '/'); + base = base ? base + 1 : pkg; + strncpy(name, base, sizeof name - 1); + name[sizeof name - 1] = 0; + if (has_ext(name, ".a") || has_ext(name, "a")) { + snprintf(dst, sizeof dst, "%s\\lib\\lib%s.a", root, pkg); + ensure_dir(root); + { + char libd[1024]; + snprintf(libd, sizeof libd, "%s\\lib", root); + ensure_dir(libd); + } + copy_file(tmp, dst); + printf("pcc-get: installed lib%s.a\n", pkg); + remove(tmp); + return 0; + } else if (has_ext(name, ".h") || has_ext(name, "h")) { + char incd[1024]; + snprintf(incd, sizeof incd, "%s\\include", root); + ensure_dir(incd); + snprintf(dst, sizeof dst, "%s\\include\\%s.h", root, pkg); + copy_file(tmp, dst); + printf("pcc-get: installed %s.h\n", pkg); + remove(tmp); + return 0; + } else { + fprintf(stderr, "pcc-get: cannot determine type of %s\n", name); + remove(tmp); + return 1; + } + } + } + + /* local package: look for dir in cwd, then packages/, then root */ + snprintf(pkgdir, sizeof pkgdir, "%s", pkg); + if (!dir_exists(pkgdir)) { + snprintf(pkgdir, sizeof pkgdir, "packages\\%s", pkg); + if (!dir_exists(pkgdir)) { + snprintf(pkgdir, sizeof pkgdir, "%s\\packages\\%s", root, pkg); + if (!dir_exists(pkgdir)) { + fprintf(stderr, "pcc-get: package '%s' not found (looked in ., packages/, %s\\packages)\n", + pkg, root); + return 1; + } + } + } + n = install_dir(pkgdir, root); + printf("pcc-get: installed %s (%d files)\n", pkg, n); + return n > 0 ? 0 : 1; + } + + if (strcmp(cmd, "remove") == 0) { + char pattern[1024]; + WIN32_FIND_DATAA fd; + HANDLE h; + int n = 0; + if (argc < 3) die("usage: pcc-get remove "); + pkg = argv[2]; + + /* remove lib/lib.a and include/.h */ + { + char path[1200]; + snprintf(path, sizeof path, "%s\\lib\\lib%s.a", root, pkg); + if (file_exists(path)) { remove(path); printf(" removed %s\n", path); n++; } + snprintf(path, sizeof path, "%s\\include\\%s.h", root, pkg); + if (file_exists(path)) { remove(path); printf(" removed %s\n", path); n++; } + } + if (n == 0) + printf("pcc-get: package '%s' not installed\n", pkg); + return 0; + } + + fprintf(stderr, "pcc-get: unknown command '%s'\n", cmd); + return 1; +} diff --git a/tools/pcc-impdef.c b/tools/pcc-impdef.c new file mode 100644 index 0000000..7b6cd4c --- /dev/null +++ b/tools/pcc-impdef.c @@ -0,0 +1,190 @@ +/* + * pcc-impdef.c - create a .def file from a Windows DLL + * + * Parses the PE export directory of a DLL and writes an + * import definition (.def) file. + * + * Usage: + * pcc-impdef library.dll [-o output.def] [-v] + * + * License: MIT + */ + +#include +#include +#include + +typedef unsigned char u8; +typedef unsigned short u16; +typedef unsigned int u32; + +/* read u32 from file data at offset (little-endian) */ +static u32 rd32(const u8 *d, u32 off) { + return (u32)d[off] | ((u32)d[off+1] << 8) | ((u32)d[off+2] << 16) | ((u32)d[off+3] << 24); +} + +static u16 rd16(const u8 *d, u32 off) { + return (u16)(d[off] | (d[off+1] << 8)); +} + +int main(int argc, char **argv) +{ + const char *infile = NULL; + const char *outfile = NULL; + int verbose = 0; + int i; + FILE *f; + long fsize; + u8 *data; + FILE *out = NULL; + u32 pe_off, opt_off, num_sections, sec_off; + u32 export_rva, export_size, names_rva, n_names; + u32 s; + int found_sec = 0; + u32 sec_rva = 0, sec_raw = 0, sec_vsize = 0; + + /* --- parse args --- */ + for (i = 1; i < argc; ++i) { + const char *a = argv[i]; + if (a[0] == '-') { + if (strcmp(a, "-v") == 0) + verbose = 1; + else if (strcmp(a, "-o") == 0 && i + 1 < argc) + outfile = argv[++i]; + else { + fprintf(stderr, "usage: pcc-impdef library.dll [-v] [-o outputfile]\n"); + return 1; + } + } else if (!infile) { + infile = a; + } else { + fprintf(stderr, "usage: pcc-impdef library.dll [-v] [-o outputfile]\n"); + return 1; + } + } + if (!infile) { + fprintf(stderr, "usage: pcc-impdef library.dll [-v] [-o outputfile]\n"); + return 1; + } + + /* --- read file --- */ + f = fopen(infile, "rb"); + if (!f) { fprintf(stderr, "pcc-impdef: cannot open '%s'\n", infile); return 1; } + fseek(f, 0, SEEK_END); + fsize = ftell(f); + fseek(f, 0, SEEK_SET); + data = (u8 *)malloc((size_t)fsize); + if (!data) { fprintf(stderr, "pcc-impdef: out of memory\n"); fclose(f); return 1; } + if (fread(data, 1, (size_t)fsize, f) != (size_t)fsize) { + fprintf(stderr, "pcc-impdef: read error\n"); fclose(f); free(data); return 1; + } + fclose(f); + + /* --- PE header --- */ + if (fsize < 0x40 || data[0] != 'M' || data[1] != 'Z') { + fprintf(stderr, "pcc-impdef: '%s' is not a PE file\n", infile); + free(data); return 1; + } + pe_off = rd32(data, 0x3C); + if (pe_off + 0x18 > (u32)fsize || rd32(data, pe_off) != 0x00004550) { + fprintf(stderr, "pcc-impdef: invalid PE header\n"); + free(data); return 1; + } + opt_off = pe_off + 24; + num_sections = rd16(data, pe_off + 6); + sec_off = opt_off + 96; /* standard 32-byte opt header, but use size field */ + + /* optional header size tells where sections start */ + sec_off = opt_off + rd16(data, pe_off + 20); + + /* data directory (export is entry 0) - located after opt header fixed part */ + { + u16 magic = rd16(data, opt_off); + u32 dd_off; + if (magic == 0x20B) /* PE32+ */ + dd_off = opt_off + 112; + else /* PE32 */ + dd_off = opt_off + 96; + export_rva = rd32(data, dd_off + 0); + export_size = rd32(data, dd_off + 4); + } + if (verbose) + printf("Export RVA: 0x%X size: 0x%X\n", export_rva, export_size); + + if (export_rva == 0 || export_size == 0) { + fprintf(stderr, "pcc-impdef: no exports in '%s'\n", infile); + free(data); return 1; + } + + /* find section containing the export RVA */ + for (s = 0; s < num_sections; s++) { + u32 hdr = sec_off + s * 40; + u32 va = rd32(data, hdr + 12); + u32 vs = rd32(data, hdr + 8); + u32 rs = rd32(data, hdr + 16); + u32 rr = rd32(data, hdr + 20); + if (export_rva >= va && export_rva < va + vs) { + sec_rva = va; sec_raw = rr; sec_vsize = vs; + found_sec = 1; + break; + } + } + if (!found_sec) { + fprintf(stderr, "pcc-impdef: export RVA not in any section\n"); + free(data); return 1; + } + + /* export directory */ + { + u32 ed = sec_raw + (export_rva - sec_rva); + if (ed + 40 > (u32)fsize) { + fprintf(stderr, "pcc-impdef: bad export directory\n"); + free(data); return 1; + } + n_names = rd32(data, ed + 24); + names_rva = rd32(data, ed + 32); + + /* names array: another RVA in same section */ + { + u32 names_off = sec_raw + (names_rva - sec_rva); + u32 k; + const char *base = strrchr(infile, '\\'); + if (!base) base = strrchr(infile, '/'); + base = base ? base + 1 : infile; + + if (!outfile) { + static char obuf[512]; + char *dot; + strncpy(obuf, base, sizeof obuf - 1); + obuf[sizeof obuf - 1] = 0; + dot = strrchr(obuf, '.'); + if (dot) *dot = 0; + strncat(obuf, ".def", sizeof obuf - strlen(obuf) - 1); + outfile = obuf; + } + out = fopen(outfile, "w"); + if (!out) { + fprintf(stderr, "pcc-impdef: cannot create '%s'\n", outfile); + free(data); return 1; + } + + fprintf(out, "LIBRARY %s\n\nEXPORTS\n", base); + for (k = 0; k < n_names; k++) { + u32 name_rva = rd32(data, names_off + k * 4); + u32 name_off; + if (name_rva >= sec_rva && name_rva < sec_rva + sec_vsize) + name_off = sec_raw + (name_rva - sec_rva); + else + name_off = name_rva; /* fallback */ + if (name_off < (u32)fsize) { + fprintf(out, "%s\n", data + name_off); + if (verbose) printf(" export: %s\n", data + name_off); + } + } + fclose(out); + printf("pcc-impdef: wrote %s (%u exports)\n", outfile, n_names); + } + } + free(data); + return 0; +}