Add static binary build infrastructure
FreeBSD User <Jaime Fournier>
2f08031aefad29cadd21fddd72d3286a78a9d25e
--- a/.gitignore +++ b/.gitignore @@ -4,3 +4,11 @@ # CDB data files *.cdb + +# Static build artifacts +static/*.boot +static/*.wp.so +static/*.h +static/jdns +static/jdns-data +static/jdns-debug new file mode 100644 --- /dev/null +++ b/static/build-boot.ss @@ -0,0 +1,44 @@ +#!chezscheme +;;; Build compiled program for static embedding. +;;; +;;; Usage: scheme --libdirs "lib:$JERBOA" --script static/build-boot.ss <entry.ss> <output.so> +;;; +;;; This compiles the entry point with whole-program optimization, +;;; producing a single .so file that can be loaded via Sscheme_program. + +(import (chezscheme)) + +(unless (>= (length (command-line)) 3) + (display "Usage: build-boot.ss <entry.ss> <output.so>\n" (current-error-port)) + (exit 1)) + +(define (string-suffix? str suffix) + (let ([slen (string-length str)] + [xlen (string-length suffix)]) + (and (>= slen xlen) + (string=? (substring str (- slen xlen) slen) suffix)))) + +(let ([entry-file (list-ref (command-line) 1)] + [output-so (list-ref (command-line) 2)]) + + ;; Step 1: Compile with WPO enabled + (compile-imported-libraries #t) + (generate-wpo-files #t) + + ;; Chez strips the .ss extension for output: bin/jdns.ss -> bin/jdns.wpo + (let* ([base (if (or (string-suffix? entry-file ".ss") + (string-suffix? entry-file ".scm")) + (substring entry-file 0 + (- (string-length entry-file) + (if (string-suffix? entry-file ".ss") 3 4))) + entry-file)] + [wpo-file (string-append base ".wpo")]) + + (display (format "Compiling ~a...\n" entry-file) (current-error-port)) + (compile-program entry-file) + + ;; Step 2: Whole-program optimization — bundle all libraries into one .so + (display (format "Whole-program optimization -> ~a\n" output-so) (current-error-port)) + (compile-whole-program wpo-file output-so) + + (display "Done.\n" (current-error-port)))) new file mode 100755 --- /dev/null +++ b/static/build-static.sh @@ -0,0 +1,105 @@ +#!/bin/sh +# +# Build static jdns binaries. +# +# Produces: +# static/jdns — DNS server (single static binary) +# static/jdns-data — Zone compiler (single static binary) +# +# Each binary embeds the Chez Scheme runtime + boot files + compiled program. +# No external .boot files or shared libraries needed. + +set -e + +SCHEME="${SCHEME:-scheme}" +JERBOA="${JERBOA:-$HOME/jerboa/lib}" +LIBDIRS="lib:${JERBOA}" +# Find Chez Scheme installation directory +CSVDIR="" +for d in /usr/local/lib/csv*/"$($SCHEME -q <<'EOF' +(display (machine-type)) (exit) +EOF +)" /usr/lib/csv*/"$($SCHEME -q <<'EOF' +(display (machine-type)) (exit) +EOF +)"; do + if [ -f "$d/libkernel.a" ] 2>/dev/null; then + CSVDIR="$d" + break + fi +done + +if [ ! -f "$CSVDIR/libkernel.a" ]; then + echo "Error: Cannot find Chez Scheme installation (libkernel.a)" >&2 + echo " Searched: $CSVDIR" >&2 + exit 1 +fi + +echo "Chez Scheme: $CSVDIR" +echo "Jerboa: $JERBOA" + +STATIC_DIR="$(dirname "$0")" +cd "$(dirname "$0")/.." + +# Step 1: Build libraries (ensure .wpo files exist) +echo "==> Compiling libraries..." +$SCHEME --libdirs "$LIBDIRS" --compile-imported-libraries --script build.ss + +# Step 2: Build each entry point +build_binary() { + local name="$1" + local entry="$2" + local prog_so="static/${name}.wp.so" + + echo "==> Building $name..." + + # Compile with WPO into a single .so + $SCHEME --libdirs "$LIBDIRS" --script static/build-boot.ss "$entry" "$prog_so" + + # Convert boot files and program to C headers + echo " Converting to C headers..." + convert_to_header "$CSVDIR/petite.boot" "static/petite_boot" + convert_to_header "$CSVDIR/scheme.boot" "static/scheme_boot" + convert_to_header "$prog_so" "static/program_boot" + + # Compile and link static binary + echo " Linking static binary..." + cc -DSCHEME_STATIC \ + -I"$CSVDIR" \ + -o "static/${name}" \ + static/main.c \ + "$CSVDIR/libkernel.a" \ + "$CSVDIR/liblz4.a" \ + "$CSVDIR/libz.a" \ + -L/usr/local/lib \ + -lm -lthr -liconv -lncursesw \ + -static + + strip "static/${name}" + + echo " static/${name}: $(ls -lh "static/${name}" | awk '{print $5}')" +} + +convert_to_header() { + local input="$1" + local stem="$2" + local varname="$(basename "$stem")_data" + local sizename="$(basename "$stem")_size" + local header="${stem}.h" + + # Convert binary file to C unsigned char array using od + printf "static const unsigned char %s[] = {\n" "$varname" > "$header" + od -An -tx1 -v "$input" | sed 's/^ *//;s/ *$//;s/ */ /g;s/ /,0x/g;s/^/0x/;s/$/,/' >> "$header" + printf "};\n" >> "$header" + printf "static const unsigned int %s = sizeof(%s);\n" "$sizename" "$varname" >> "$header" +} + +build_binary "jdns" "bin/jdns.ss" +build_binary "jdns-data" "bin/jdns-data.ss" + +echo "" +echo "Static binaries:" +ls -lh static/jdns static/jdns-data +echo "" +echo "Verify (should show 'statically linked'):" +file static/jdns static/jdns-data new file mode 100644 --- /dev/null +++ b/static/main.c @@ -0,0 +1,134 @@ +/* + * jdns static binary entry point + * + * Embeds Chez Scheme boot files (petite.boot, scheme.boot) and + * the compiled program (.so) as binary blobs. + * Statically linked — no shared library dependencies. + * + * The program .so is written to a temp file and loaded via + * Sscheme_program, which sets command-line before executing. + */ + +#include "scheme.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <dlfcn.h> + +/* Embedded boot file data — generated by build-static.sh */ +#include "petite_boot.h" +#include "scheme_boot.h" +#include "program_boot.h" + +/* + * Override dlopen/dlsym for static binary. + * Chez's load-shared-object calls dlopen, which may fail in a fully + * static binary. We return a dummy handle so it doesn't error out. + * All actual symbol resolution happens via Sforeign_symbol. + */ +static int _static_dummy; + +void *dlopen(const char *path, int mode) { + (void)path; + (void)mode; + return &_static_dummy; +} + +void *dlsym(void *handle, const char *name) { + (void)handle; + (void)name; + return NULL; +} + +int dlclose(void *handle) { + (void)handle; + return 0; +} + +char *dlerror(void) { + return NULL; +} + +/* Wrapper functions for htons/ntohs which may be macros */ +static unsigned short wrap_htons(unsigned short x) { return htons(x); } +static unsigned short wrap_ntohs(unsigned short x) { return ntohs(x); } + +/* + * Called by Sbuild_heap after the Scheme heap is initialized + * but before boot files execute. Register FFI symbols here. + */ +static void custom_init(void) { + Sforeign_symbol("socket", (void *)socket); + Sforeign_symbol("bind", (void *)bind); + Sforeign_symbol("close", (void *)close); + Sforeign_symbol("recvfrom", (void *)recvfrom); + Sforeign_symbol("sendto", (void *)sendto); + Sforeign_symbol("setsockopt", (void *)setsockopt); + Sforeign_symbol("htons", (void *)wrap_htons); + Sforeign_symbol("ntohs", (void *)wrap_ntohs); + Sforeign_symbol("inet_pton", (void *)inet_pton); + Sforeign_symbol("inet_ntop", (void *)inet_ntop); + Sforeign_symbol("setuid", (void *)setuid); + Sforeign_symbol("setgid", (void *)setgid); + Sforeign_symbol("chdir", (void *)chdir); + Sforeign_symbol("chroot", (void *)chroot); +#ifdef __FreeBSD__ + { + extern int cap_rights_limit(int, const void *); + Sforeign_symbol("cap_rights_limit", (void *)cap_rights_limit); + } +#endif +} + +/* + * Write embedded program data to a temp file so Sscheme_program + * can load it. This lets Chez set command-line before execution. + */ +static const char *write_program_tempfile(void) { + static char path[] = "/tmp/jdns-prog-XXXXXX"; + int fd; + ssize_t written; + + fd = mkstemp(path); + if (fd < 0) { + perror("mkstemp"); + exit(1); + } + + written = write(fd, program_boot_data, program_boot_size); + if (written != (ssize_t)program_boot_size) { + perror("write"); + close(fd); + unlink(path); + exit(1); + } + + close(fd); + return path; +} + +int main(int argc, const char *argv[]) { + const char *prog_path; + + Sscheme_init(NULL); + + /* Register boot files (petite + scheme only — not the program) */ + Sregister_boot_file_bytes("petite", (void *)petite_boot_data, petite_boot_size); + Sregister_boot_file_bytes("scheme", (void *)scheme_boot_data, scheme_boot_size); + + /* Build heap with FFI symbols registered in custom_init */ + Sbuild_heap(NULL, custom_init); + + /* Write compiled program to temp file, load via Sscheme_program + * which sets (command-line) before executing */ + prog_path = write_program_tempfile(); + Sscheme_program(prog_path, argc, argv); + unlink(prog_path); + + Sscheme_deinit(); + return 0; +}