diff --git a/collects/compiler/embed-unit.rkt b/collects/compiler/embed-unit.rkt index 84a95ee717..c6785fe74c 100644 --- a/collects/compiler/embed-unit.rkt +++ b/collects/compiler/embed-unit.rkt @@ -15,6 +15,7 @@ "private/winsubsys.ss" "private/macfw.ss" "private/mach-o.ss" + "private/elf.ss" "private/windlldir.ss" "private/collects-path.ss" "find-exe.rkt") @@ -1168,8 +1169,44 @@ compiler expand-namespace src-filter - get-extra-imports))]) - (let-values ([(start end) + get-extra-imports))] + [make-full-cmdline + (lambda (start end) + (let ([start-s (number->string start)] + [end-s (number->string end)]) + (append (if launcher? + (if (and (eq? 'windows (system-type)) + keep-exe?) + ;; argv[0] replacement: + (list (path->string + (if relative? + (relativize exe dest-exe values) + exe))) + ;; No argv[0]: + null) + (list "-k" start-s end-s)) + cmdline)))] + [make-starter-cmdline + (lambda (full-cmdline) + (apply bytes-append + (map (lambda (s) + (bytes-append + (cond + [(path? s) (path->bytes s)] + [else (string->bytes/locale s)]) + #"\0")) + (append + (list (if relative? + (relativize exe dest-exe values) + exe) + (let ([dir (find-dll-dir)]) + (if dir + (if relative? + (relativize dir dest-exe values) + dir) + ""))) + full-cmdline))))]) + (let-values ([(start end cmdline-end) (if (and (eq? (system-type) 'macosx) (not unix-starter?)) ;; For Mach-O, we know how to add a proper segment @@ -1178,29 +1215,37 @@ (let ([s (get-output-bytes s)]) (let ([start (add-plt-segment dest-exe s)]) (values start - (+ start (bytes-length s)))))) - ;; Other platforms: just add to the end of the file: - (let ([start (file-size dest-exe)]) - (call-with-output-file* dest-exe write-module - #:exists 'append) - (values start (file-size dest-exe))))]) + (+ start (bytes-length s)) + #f)))) + ;; Unix starter: Maybe ELF, in which case we + ;; can add a proper section + (let-values ([(s e p) + (if unix-starter? + (add-racket-section + orig-exe + dest-exe + (if launcher? #".rackcmdl" #".rackprog") + (lambda (start) + (let ([s (open-output-bytes)]) + (write-module s) + (let ([p (file-position s)]) + (display (make-starter-cmdline + (make-full-cmdline start (+ start p))) + s) + (values (get-output-bytes s) p))))) + (values #f #f #f))]) + (if (and s e) + ;; ELF succeeded: + (values s (+ s p) e) + ;; Otherwise, just add to the end of the file: + (let ([start (file-size dest-exe)]) + (call-with-output-file* dest-exe write-module + #:exists 'append) + (values start (file-size dest-exe) #f)))))]) (when verbose? (fprintf (current-error-port) "Setting command line\n")) - (let ([start-s (number->string start)] - [end-s (number->string end)]) - (let ([full-cmdline (append - (if launcher? - (if (and (eq? 'windows (system-type)) - keep-exe?) - ;; argv[0] replacement: - (list (path->string - (if relative? - (relativize exe dest-exe values) - exe))) - ;; No argv[0]: - null) - (list "-k" start-s end-s)) - cmdline)]) + (let () + (let ([full-cmdline (make-full-cmdline start end)]) (when collects-path-bytes (when verbose? (fprintf (current-error-port) "Setting collection path\n")) @@ -1218,27 +1263,12 @@ (lambda () (find-cmdline "exeuctable type" #"bINARy tYPe:"))))] - [cmdline - (apply bytes-append - (map (lambda (s) - (bytes-append - (cond - [(path? s) (path->bytes s)] - [else (string->bytes/locale s)]) - #"\0")) - (append - (list (if relative? - (relativize exe dest-exe values) - exe) - (let ([dir (find-dll-dir)]) - (if dir - (if relative? - (relativize dir dest-exe values) - dir) - ""))) - full-cmdline)))] + [cmdline (if cmdline-end + #f + (make-starter-cmdline full-cmdline))] [out (open-output-file dest-exe #:exists 'update)]) - (let ([cmdline-end (+ end (bytes-length cmdline))] + (let ([old-cmdline-end cmdline-end] + [cmdline-end (or cmdline-end (+ end (bytes-length cmdline)))] [write-num (lambda (n) (write-bytes (integer->integer-bytes n 4 #t #f) out))]) (dynamic-wind @@ -1260,9 +1290,10 @@ (write-num (length full-cmdline)) (write-num (if mred? 1 0)) (flush-output out) - (file-position out end) - (write-bytes cmdline out) - (flush-output out)) + (unless old-cmdline-end + (file-position out end) + (write-bytes cmdline out) + (flush-output out))) (lambda () (close-output-port out)))))] [else diff --git a/collects/compiler/private/elf.rkt b/collects/compiler/private/elf.rkt new file mode 100644 index 0000000000..2decf07ba1 --- /dev/null +++ b/collects/compiler/private/elf.rkt @@ -0,0 +1,316 @@ +#lang racket/base + +(provide add-racket-section) + +(define 32BIT 1) +(define 64BIT 2) + +(define LITTLEEND 1) +(define BIGEND 2) + +(define SECTION-ALIGN 16) ; conservative? + +(define SHT_PROGBITS 1) + +(struct elf (ph-offset ph-esize ph-ecount + sh-offset sh-esize sh-ecount + class format sh-str-index) + #:transparent) +(struct section (name-offset offset size) + #:transparent) +(struct program (offset size) + #:transparent) + +(define (copy-port-bytes amt in out) + (let ([get (min amt 4096)]) + (let ([s (read-bytes get in)]) + (unless (and (bytes? s) + (= get (bytes-length s))) + (error 'add-elf-section "file data copy failed")) + (write-bytes s out)) + (unless (= get amt) + (copy-port-bytes (- amt get) in out)))) + +(define (write-n-bytes amt out) + (let ([put (min amt 4096)]) + (write-bytes (make-bytes put 0) out) + (unless (= put amt) + (write-n-bytes (- amt put) out)))) + +(define (round-up v align) + (let ([d (modulo v align)]) + (if (zero? d) + v + (+ v (- align d))))) + +(define (read-elf p fail-k k #:dump? [dump? #f]) + (define (stop) (raise "unexpected input")) + (define (expect b) + (eq? b (read-byte p))) + (define (skip n) + (for ([i (in-range n)]) + (when (eof-object? (read-byte p)) + (stop)))) + (define (read-a-byte) + (let ([v (read-byte p)]) + (when (eof-object? v) + (stop)) + v)) + (define (skip-half) (skip 2)) + (define (skip-word) (skip 4)) + (define (show v) (displayln v) v) + ;; Read ELF identification --------------- + (if (not (and + (expect #x7F) + (expect (char->integer #\E)) + (expect (char->integer #\L)) + (expect (char->integer #\F)))) + ;; Not an ELF binary + (fail-k) + ;; Is an ELF binary: + (let ([class (read-byte p)]) + (unless (or (= class 32BIT) + (= class 64BIT)) + (stop)) + (let ([format (read-byte p)]) + (unless (or (= format LITTLEEND) + (= format BIGEND)) + (stop)) + ;; Set up multi-byte reading --------------- + (let* ([read-word + (lambda () + (let ([a (read-a-byte)] + [b (read-a-byte)] + [c (read-a-byte)] + [d (read-a-byte)]) + (cond + [(= format LITTLEEND) + (bitwise-ior a + (arithmetic-shift b 8) + (arithmetic-shift c 16) + (arithmetic-shift d 24))] + [else + (bitwise-ior d + (arithmetic-shift c 8) + (arithmetic-shift b 16) + (arithmetic-shift a 24))])))] + [read-xword + (lambda () + (if (= class 32BIT) + (read-word) + (let ([b (read-bytes 8 p)]) + (if (and (bytes? b) (= 8 (bytes-length b))) + (integer-bytes->integer b #f (= format BIGEND)) + (stop)))))] + [read-half + (lambda () + (let ([a (read-a-byte)] + [b (read-a-byte)]) + (cond + [(= format LITTLEEND) + (bitwise-ior a + (arithmetic-shift b 8))] + [else + (bitwise-ior b + (arithmetic-shift a 8))])))] + [skip-addr (lambda () + (skip (if (= class 32BIT) 4 8)))] + [read-addr (lambda () (read-xword))] + [read-off (lambda () (read-xword))]) + (skip 1) ; version + (skip 9) ; padding + (skip-half) ; type + (skip-half) ; machine + (skip-word) ; version + (skip-addr) ; entry + ;; Read rest of ELF header ----------------- + (let ([ph-offset (read-off)] + [sh-offset (read-off)] + [flags (read-word)] + [eh-size (read-half)] + [ph-esize (read-half)] + [ph-ecount (read-half)] + [sh-esize (read-half)] + [sh-ecount (read-half)] + [sh-str-index (read-half)]) + ;; Read sections ------------------------ + (let ([sections + (for/list ([i (in-range sh-ecount)]) + (file-position p (+ sh-offset (* i sh-esize))) + (let ([name-offset (read-word)] + [type (read-word)] + [flags (read-xword)] + [addr (read-addr)] + [offset (read-off)] + [size (read-xword)] + [link (read-word)] + [info (read-word)] + [align (read-xword)] + [esize (read-xword)]) + (section name-offset offset size)))]) + ;; Read program headers ------------------------ + (let ([programs + (for/list ([i (in-range ph-ecount)]) + (file-position p (+ ph-offset (* i ph-esize))) + (let ([type (read-word)] + [flags (if (= class 32BIT) + 0 + (read-word))] + [offset (read-off)] + [vaddr (read-addr)] + [paddr (read-addr)] + [file-size (read-xword)]) + (program offset file-size)))]) + ;; Load strings from string section ------------------------ + (let* ([str-section (list-ref sections sh-str-index)] + [strs (begin + (file-position p (section-offset str-section)) + (read-bytes (section-size str-section) p))]) + (when dump? + (for ([s (in-list sections)]) + (printf "~s ~x ~x\n" + (regexp-match #rx#"[^\0]*" strs (min (section-name-offset s) + (bytes-length strs))) + (section-offset s) + (section-size s)))) + (k (elf ph-offset ph-esize ph-ecount + sh-offset sh-esize sh-ecount + class format sh-str-index) + sections programs + str-section strs)))))))))) + +(define (add-racket-section src-file dest-file section-name get-data) + (call-with-input-file* + src-file + (lambda (in) + (read-elf + in + (lambda () (values #f #f #f)) + (lambda (elf sections programs str-section strs) + (let ([new-sec-pos (+ (elf-sh-offset elf) + (* (elf-sh-esize elf) (elf-sh-ecount elf)))] + [new-sec-delta (round-up (elf-sh-ecount elf) SECTION-ALIGN)] + [new-str-pos (+ (section-offset str-section) + (section-size str-section))] + [new-str-delta (round-up (add1 (bytes-length section-name)) + SECTION-ALIGN)] + [total-size (file-size src-file)] + [class (elf-class elf)] + [format (elf-format elf)]) + (let-values ([(a-pos a-delta b-pos b-delta) + (if (new-sec-pos . < . new-str-pos) + (values new-sec-pos new-sec-delta + new-str-pos new-str-delta) + (values new-str-pos new-str-delta + new-sec-pos new-sec-delta))] + [(data mid) (get-data (+ total-size new-str-delta new-sec-delta))]) + (call-with-output-file* + dest-file + #:exists 'truncate + (lambda (out) + (let* ([write-word + (lambda (v) + (if (= format LITTLEEND) + (begin + (write-byte (bitwise-and v #xFF) out) + (write-byte (bitwise-and (arithmetic-shift v -8) #xFF) out) + (write-byte (bitwise-and (arithmetic-shift v -16) #xFF) out) + (write-byte (bitwise-and (arithmetic-shift v -24) #xFF) out)) + (begin + (write-byte (bitwise-and (arithmetic-shift v -24) #xFF) out) + (write-byte (bitwise-and (arithmetic-shift v -16) #xFF) out) + (write-byte (bitwise-and (arithmetic-shift v -8) #xFF) out) + (write-byte (bitwise-and v #xFF) out))))] + [write-xword + (lambda (v) + (if (= class 32BIT) + (write-word v) + (display (integer->integer-bytes v 8 #f (= format BIGEND)) + out)))] + [write-addr (lambda (v) (write-xword v))] + [write-off (lambda (v) (write-xword v))] + [write-half + (lambda (v) + (if (= format LITTLEEND) + (begin + (write-byte (bitwise-and v #xFF) out) + (write-byte (bitwise-and (arithmetic-shift v -8) #xFF) out)) + (begin + (write-byte (bitwise-and (arithmetic-shift v -8) #xFF) out) + (write-byte (bitwise-and v #xFF) out))))] + [adjust (lambda (offset) + (if (offset . >= . a-pos) + (if (offset . >= . b-pos) + (+ offset a-delta b-delta) + (+ offset a-delta)) + offset))] + [adjust* (lambda (offset) + (add1 (adjust (sub1 offset))))] + [at-class (lambda (a b) (if (= class 32BIT) a b))]) + + (file-position in 0) + (copy-port-bytes a-pos in out) + (write-n-bytes a-delta out) + (copy-port-bytes (- b-pos a-pos) in out) + (write-n-bytes b-delta out) + (copy-port-bytes (- total-size b-pos) in out) + + ;; Write new section: + (file-position out (adjust* new-sec-pos)) + (write-word (section-size str-section)) + (write-word SHT_PROGBITS) + (write-xword 0) ; flags + (write-addr 0) ; addr + (write-off (+ total-size new-sec-delta new-str-delta)) + (write-xword (bytes-length data)) + (write-word 0) ; link + (write-word 0) ; info + (write-xword 1) ; align + (write-xword 0) ; esize + + ;; Write new string: + (file-position out (adjust* new-str-pos)) + (write-bytes section-name out) + + ;; Fix section-header and program-header offsets: + (file-position out (at-class 28 32)) + (write-off (adjust (elf-ph-offset elf))) + (write-off (adjust (elf-sh-offset elf))) + + ;; Increment section count: + (file-position out (at-class 48 60)) + (write-half (add1 (length sections))) + + ;; Increment string section size: + (file-position out (adjust (+ (elf-sh-offset elf) + (* (elf-sh-str-index elf) + (elf-sh-esize elf)) + (at-class 20 32)))) + (write-xword (+ (section-size str-section) new-str-delta)) + + ;; Fix up section offsets: + (for ([s (in-list sections)] + [i (in-naturals)]) + (let ([offset (section-offset s)]) + (when (offset . > . a-pos) + (file-position out (adjust (+ (elf-sh-offset elf) + (* i (elf-sh-esize elf)) + (at-class 16 24)))) + (write-off (adjust offset))))) + + ;; Fix up program offsets: + (for ([p (in-list programs)] + [i (in-naturals)]) + (let ([offset (program-offset p)]) + (when (offset . > . a-pos) + (file-position out (adjust (+ (elf-ph-offset elf) + (* i (elf-ph-esize elf)) + (at-class 4 8)))) + (write-off (adjust offset))))) + + ;; Write new section data: + (let ([dest (+ total-size new-sec-delta new-str-delta)]) + (file-position out dest) + (write-bytes data out) + + (values dest (+ dest (bytes-length data)) mid)))))))))))) diff --git a/src/racket/dynsrc/ustart.c b/src/racket/dynsrc/ustart.c index 9232163a72..5f2209d2eb 100644 --- a/src/racket/dynsrc/ustart.c +++ b/src/racket/dynsrc/ustart.c @@ -24,6 +24,13 @@ exe_path - program to start (relative is w.r.t. executable) dll_path - DLL directory if non-empty (relative is w.r.t. executable) cmdline_arg ... + + For ELF binaries, the absolute values of `start', `prog_end', and + `end' are ignored if a ".rackcmdl" (starter) or ".rackprog" + (embedding) section is found. The `start' value is set to match the + section offset, and `prog_end' and `end' are correspondingly + adjusted. Using a seciton offset allows linking tools (such as + `strip') to move the data in the executable. */ char *config = "cOnFiG:[***************************"; @@ -101,8 +108,6 @@ static int write_str(int fd, char *s) return write(fd, s, strlen(s)); } -#if 0 -/* Useful for debugging: */ static char *num_to_string(int n) { if (!n) @@ -118,7 +123,6 @@ static char *num_to_string(int n) return d; } } -#endif static char *string_append(char *s1, char *s2) { @@ -225,12 +229,100 @@ static char *next_string(char *s) return s + strlen(s) + 1; } +typedef unsigned short ELF__Half; +typedef unsigned int ELF__Word; +typedef unsigned long ELF__Xword; +typedef unsigned long ELF__Addr; +typedef unsigned long ELF__Off; + +typedef struct { + unsigned char e_ident[16]; + ELF__Half e_type; + ELF__Half e_machine; + ELF__Word e_version; + ELF__Addr e_entry; + ELF__Off e_phoff; + ELF__Off e_shoff; + ELF__Word e_flags; + ELF__Half e_ehsize; + ELF__Half e_phentsize; + ELF__Half e_phnum; + ELF__Half e_shentsize; + ELF__Half e_shnum; + ELF__Half e_shstrndx; +} ELF__Header; + +typedef struct +{ + ELF__Word sh_name; + ELF__Word sh_type; + ELF__Xword sh_flags; + ELF__Addr sh_addr; + ELF__Off sh_offset; + ELF__Xword sh_size; + ELF__Word sh_link; + ELF__Word sh_info; + ELF__Xword sh_addralign; + ELF__Xword sh_entsize; +} Elf__Shdr; + +static int try_elf_section(const char *me, int *_start, int *_prog_end, int *_end) +{ + int fd, i; + ELF__Header e; + Elf__Shdr s; + char *strs; + + fd = open(me, O_RDONLY, 0); + if (fd == -1) return 0; + + if (read(fd, &e, sizeof(e)) == sizeof(e)) { + if ((e.e_ident[0] == 0x7F) + && (e.e_ident[1] == 'E') + && (e.e_ident[2] == 'L') + && (e.e_ident[3] == 'F')) { + + lseek(fd, e.e_shoff + (e.e_shstrndx * e.e_shentsize), SEEK_SET); + if (read(fd, &s, sizeof(s)) != sizeof(s)) { + close(fd); + return 0; + } + + strs = (char *)malloc(s.sh_size); + lseek(fd, s.sh_offset, SEEK_SET); + if (read(fd, strs, s.sh_size) != s.sh_size) { + close(fd); + return 0; + } + + for (i = 0; i < e.e_shnum; i++) { + lseek(fd, e.e_shoff + (i * e.e_shentsize), SEEK_SET); + if (read(fd, &s, sizeof(s)) != sizeof(s)) { + close(fd); + return 0; + } + if (!strcmp(strs + s.sh_name, ".rackcmdl") + || !strcmp(strs + s.sh_name, ".rackprog")) { + *_prog_end = (*_prog_end - *_start) + s.sh_offset; + *_start = s.sh_offset; + *_end = s.sh_offset + s.sh_size; + close(fd); + return !strcmp(strs + s.sh_name, ".rackprog"); + } + } + } + } + + close(fd); + return 0; +} + int main(int argc, char **argv) { char *me = argv[0], *data, **new_argv; char *exe_path, *lib_path, *dll_path; int start, prog_end, end, count, fd, v, en, x11; - int argpos, inpos, collcount = 1; + int argpos, inpos, collcount = 1, fix_argv; if (config[7] == '[') { write_str(2, argv[0]); @@ -314,6 +406,8 @@ int main(int argc, char **argv) count = as_int(config + 20); x11 = as_int(config + 24); + fix_argv = try_elf_section(me, &start, &prog_end, &end); + { int offset, len; offset = _coldir_offset; @@ -407,6 +501,12 @@ int main(int argc, char **argv) } } + if (fix_argv) { + /* next three args are "-k" and numbers; fix + the numbers to match start and prog_end */ + fix_argv = argpos + 1; + } + /* Add built-in flags: */ while (count--) { new_argv[argpos++] = data; @@ -420,6 +520,11 @@ int main(int argc, char **argv) new_argv[argpos] = NULL; + if (fix_argv) { + new_argv[fix_argv] = num_to_string(start); + new_argv[fix_argv+1] = num_to_string(prog_end); + } + /* Execute the original binary: */ v = execv(exe_path, new_argv);