From e01a21db0c6dc97fe4f98844205a65c7799ae2dc Mon Sep 17 00:00:00 2001 From: Matthew Flatt Date: Sat, 24 Mar 2018 17:02:34 -0600 Subject: [PATCH] raco exe: add `--embed-dlls` for Windows Creating an executable with embedded DLLs means that the executable can be truly stand-alone, instead of needing to be kept with its DLLs in a relative subdirectory. DLL embedding works by bypassing the OS's LoadLibrary and GetProcAddress functions, and instead maps the DLL into memory and performs relocations explicitly. Joachim Bauch's MemoryModule (Mozilla license) implements those steps. --- pkgs/compiler-lib/compiler/commands/exe.rkt | 2 + pkgs/compiler-test/info.rkt | 4 +- .../tests/compiler/embed/test.rkt | 33 +- .../racket-doc/scribblings/raco/exe-api.scrbl | 12 +- pkgs/racket-doc/scribblings/raco/exe.scrbl | 13 +- racket/collects/compiler/distribute.rkt | 85 +- racket/collects/compiler/embed.rkt | 109 +- .../compiler/private/win-dll-list.rkt | 56 + .../collects/compiler/private/windlldir.rkt | 10 +- racket/collects/compiler/private/xform.rkt | 6 + racket/src/gracket/Makefile.in | 4 +- racket/src/gracket/gc2/Makefile.in | 6 +- racket/src/racket/Makefile.in | 14 +- racket/src/racket/gc2/Makefile.in | 4 +- racket/src/racket/include/scheme.h | 3 + racket/src/racket/include/schthread.h | 8 + racket/src/racket/main.c | 11 + racket/src/racket/src/file.c | 33 + racket/src/racket/src/longdouble/longdouble.c | 6 +- racket/src/racket/src/salloc.c | 17 + racket/src/racket/src/schpriv.h | 5 + racket/src/racket/win_tls.inc | 15 +- racket/src/rktio/rktio.h | 6 + racket/src/rktio/rktio_convert.c | 48 +- racket/src/rktio/rktio_dll.c | 68 +- racket/src/rktio/rktio_private.h | 4 + racket/src/rktio/rktio_process.c | 9 +- racket/src/start/MemoryModule.c | 1207 +++++++++++++++++ racket/src/start/MemoryModule.h | 168 +++ racket/src/start/config.inc | 16 +- racket/src/start/embedded_dll.inc | 243 ++++ racket/src/worksp/gc2/make.rkt | 6 +- racket/src/worksp/gracket/gracket.vcproj | 4 + racket/src/worksp/gracket/gracket.vcxproj | 1 + racket/src/worksp/racket/racket.vcproj | 4 + racket/src/worksp/racket/racket.vcxproj | 1 + 36 files changed, 2101 insertions(+), 140 deletions(-) create mode 100644 racket/collects/compiler/private/win-dll-list.rkt create mode 100644 racket/src/start/MemoryModule.c create mode 100644 racket/src/start/MemoryModule.h create mode 100644 racket/src/start/embedded_dll.inc diff --git a/pkgs/compiler-lib/compiler/commands/exe.rkt b/pkgs/compiler-lib/compiler/commands/exe.rkt index 5470f289eb..15257278f2 100644 --- a/pkgs/compiler-lib/compiler/commands/exe.rkt +++ b/pkgs/compiler-lib/compiler/commands/exe.rkt @@ -41,6 +41,8 @@ (list "-A" (path->string (find-system-path 'addon-dir))) (remove "-U" (exe-embedded-flags))))) (launcher #t)] + [("--embed-dlls") "On Windows, embed DLLs in the executable" + (exe-aux (cons (cons 'embed-dlls? #t) (exe-aux)))] [("--config-path") path "Set as configuration directory for executable" (exe-embedded-config-path path)] [("--collects-path") path "Set as main collects for executable" diff --git a/pkgs/compiler-test/info.rkt b/pkgs/compiler-test/info.rkt index 220c2971a1..ef3f2b2788 100644 --- a/pkgs/compiler-test/info.rkt +++ b/pkgs/compiler-test/info.rkt @@ -16,5 +16,7 @@ "gui-lib" "htdp-lib" "plai-lib" - "rackunit-lib")) + "rackunit-lib" + "dynext-lib" + "mzscheme-lib")) (define update-implies '("compiler-lib")) diff --git a/pkgs/compiler-test/tests/compiler/embed/test.rkt b/pkgs/compiler-test/tests/compiler/embed/test.rkt index 5c5d3f9ae2..0d58da0c69 100644 --- a/pkgs/compiler-test/tests/compiler/embed/test.rkt +++ b/pkgs/compiler-test/tests/compiler/embed/test.rkt @@ -297,6 +297,27 @@ `("-l" "tests/compiler/embed/embed-me5.rkt")) (try-exe mr-dest "This is 5: #\n" #t))) +(define (try-embedded-dlls) + (prepare mz-dest "embed-me1.rkt") + (make-embedding-executable + mz-dest #f #f + `((#t (lib "embed-me1.rkt" "tests" "compiler" "embed"))) + '() + #f + `("-l" "tests/compiler/embed/embed-me1.rkt") + '((embed-dlls? . #t))) + (try-exe mz-dest "This is 1\n" #t) + + (prepare mr-dest "embed-me5.rkt") + (make-embedding-executable + mr-dest #t #f + `((#t (lib "embed-me5.rkt" "tests" "compiler" "embed"))) + '() + #f + `("-l" "tests/compiler/embed/embed-me5.rkt") + '((embed-dlls? . #t))) + (try-exe mr-dest "This is 5: #\n" #t)) + ;; Try the raco interface: (require setup/dirs mzlib/file) @@ -309,7 +330,8 @@ (define (system+ . args) (printf "> ~a\n" (car (reverse args))) - (apply system* args)) + (unless (apply system* args) + (error 'system+ "command failed ~s" args))) (define (short-mzc-tests mred?) (parameterize ([current-directory (find-system-path 'temp-dir)]) @@ -399,6 +421,7 @@ (try-exe (mk-dest mred?) "Hello from a place!\n" mred?) ;; raco exe --launcher + (printf ">>launcher\n") (system+ raco "exe" "--launcher" @@ -409,6 +432,7 @@ ;; the rest use mzc... + (printf ">>mzc\n") (system+ mzc (if mred? "--gui-exe" "--exe") (path->string (mk-dest mred?)) @@ -470,7 +494,7 @@ (try-exe (mk-dest mred?) "This is 6\n#t\n" mred? void "cts") ; <- cts copied to distribution (delete-directory/files "cts") (parameterize ([current-error-port (open-output-nowhere)]) - (test #f system+ (mk-dest mred?)))) + (test #f system* (mk-dest mred?)))) (check-collection-path "embed-me6b.rkt" "racket/fixnum.rkt" #t) (check-collection-path "embed-me6.rkt" "mzlib/etc.rkt" ;; "mzlib" is found via the "collects" path @@ -702,12 +726,15 @@ (try-basic) (try-mzc) -(try-extension) +(unless (eq? 'windows (system-type)) + (try-extension)) (try-gracket) (try-reader) (try-planet) (try-*sl) (try-source) +(when (eq? 'windows (system-type)) + (try-embedded-dlls)) ;; ---------------------------------------- ;; Make sure that embedding does not break future module declarations diff --git a/pkgs/racket-doc/scribblings/raco/exe-api.scrbl b/pkgs/racket-doc/scribblings/raco/exe-api.scrbl index 318a252f67..ae5fa58b05 100644 --- a/pkgs/racket-doc/scribblings/raco/exe-api.scrbl +++ b/pkgs/racket-doc/scribblings/raco/exe-api.scrbl @@ -251,6 +251,13 @@ currently supported keys are as follows: original executable's path to DLLs is converted to an absolute path if it was relative.} + @item{@racket['embed-dlls?] (Windows) : A boolean indicating whether + to copy DLLs into the executable, where the default value is + @racket[#f]. Embedded DLLs are instantiated by an internal + linking step that bypasses some operating system facilities, + so it will not work for all Windows DLLs, but typical DLLs + will work as embedded.} + @item{@racket['subsystem] (Windows) : A symbol, either @racket['console] for a console application or @racket['windows] for a consoleless application; the default @@ -368,7 +375,10 @@ reader extensions needed to parse a module that will be included as source, as long as the reader is referenced through an absolute module path. Each path given to @racket[extras-proc] corresponds to the actual file name (e.g., @filepath{.ss}/@filepath{.rkt} conversions -have been applied as needed to refer to the existing file).} +have been applied as needed to refer to the existing file). + +@history[#:changed "6.90.0.23" @elem{Added @racket[embed-dlls?] as an + @racket[#:aux] key.}]} @defproc[(make-embedding-executable [dest path-string?] diff --git a/pkgs/racket-doc/scribblings/raco/exe.scrbl b/pkgs/racket-doc/scribblings/raco/exe.scrbl index 168b8b6a8d..01d9c8809b 100644 --- a/pkgs/racket-doc/scribblings/raco/exe.scrbl +++ b/pkgs/racket-doc/scribblings/raco/exe.scrbl @@ -68,7 +68,7 @@ interface to the embedding mechanism. A stand-alone executable is ``stand-alone'' in the sense that you can run it without starting @exec{racket}, @exec{gracket}, or -DrRacket. However, the executable depends on Racket shared libraries, +DrRacket. However, the executable may depend on Racket shared libraries and possibly other run-time files declared via @racket[define-runtime-path]. The executable can be packaged with support libraries to create a distribution using @exec{raco @@ -95,6 +95,14 @@ The @exec{raco exe} command accepts the following command-line flags: are installed in user scope; use @exec{--exf -U} to enable access to user-scope packages from the launcher.} + @item{@DFlag{embed-dlls} --- On Windows, for a stand-alone executable, + copies any needed DLLs into the executable. Embedding DLLs makes + the resulting executable truly stand-alone if it does not depend on + other external files. Not all DLLs work with embedding, and + limitations are mostly related to thread-local storage and + resources, but all DLLs within the main Racket distribution work + with @DFlag{embed-dlls}.} + @item{@DFlag{config-path} @nonterm{path} --- set @nonterm{path} within the executable as the path to the @tech{configuration directory}; if the path is relative, it will be treated as relative @@ -184,7 +192,8 @@ The @exec{raco exe} command accepts the following command-line flags: ] @history[#:changed "6.3.0.11" @elem{Added support for - @racketidfont{declare-preserve-for-embedding}.}] + @racketidfont{declare-preserve-for-embedding}.} + #:changed "6.90.0.23" @elem{Added @DFlag{embed-dlls}.}] @; ---------------------------------------------------------------------- diff --git a/racket/collects/compiler/distribute.rkt b/racket/collects/compiler/distribute.rkt index eba3572873..cbe5ebf262 100644 --- a/racket/collects/compiler/distribute.rkt +++ b/racket/collects/compiler/distribute.rkt @@ -7,12 +7,12 @@ setup/cross-system pkg/path setup/main-collects - dynext/filename-version "private/macfw.rkt" "private/windlldir.rkt" "private/elf.rkt" "private/collects-path.rkt" - "private/write-perm.rkt") + "private/write-perm.rkt" + "private/win-dll-list.rkt") (provide assemble-distribution) @@ -106,12 +106,16 @@ relative-collects-dir (build-path dest-dir specific-lib-dir "exts") (build-path specific-lib-dir "exts"))))]) - (make-directory* lib-dir) - (make-directory* collects-dir) - (make-directory* exts-dir) ;; Copy libs into place - (install-libs lib-dir types (not executables?)) + (install-libs lib-dir types + #:extras-only? (not executables?) + #:no-dlls? (and (eq? 'windows (cross-system-type)) + ;; If all executables have "" the the + ;; DLL dir, then no base DLLS are needed + (for/and ([f (in-list orig-binaries)]) + (current-no-dlls? f)))) ;; Copy collections into place + (unless (null? copy-collects) (make-directory* collects-dir)) (for-each (lambda (dir) (for-each (lambda (f) (copy-directory/files* @@ -161,38 +165,18 @@ ;; Done! (void)))))) - (define (install-libs lib-dir types extras-only?) + (define (install-libs lib-dir types + #:extras-only? extras-only? + #:no-dlls? no-dlls?) (case (cross-system-type) [(windows) - (let ([copy-dll (lambda (name) - (copy-file* (search-dll (find-cross-dll-dir) name) - (build-path lib-dir name)))] - [versionize (lambda (template) - (let ([f (search-dll (find-cross-dll-dir) - (format template filename-version-part))]) - (if (file-exists? f) - (format template filename-version-part) - (format template "xxxxxxx"))))]) - (map copy-dll (list - "libiconv-2.dll" - "longdouble.dll")) - (unless extras-only? - (when (or (memq 'racketcgc types) - (memq 'gracketcgc types)) - (map copy-dll - (list - (versionize "libracket~a.dll") - (versionize "libmzgc~a.dll")))) - (when (or (memq 'racket3m types) - (memq 'gracket3m types)) - (map copy-dll - (list - (versionize "libracket3m~a.dll")))) - (when (or (memq 'racketcs types) - (memq 'gracketcs types)) - (map copy-dll - (list - (versionize "libracketcs~a.dll"))))))] + (if no-dlls? + '() + (let ([copy-dll (lambda (name) + (make-directory* lib-dir) + (copy-file* (search-dll name) + (build-path lib-dir name)))]) + (map copy-dll (get-racket-dlls types #:extras-only? extras-only?))))] [(macosx) (unless extras-only? (when (or (memq 'racketcgc types) @@ -207,10 +191,9 @@ [(unix) (unless extras-only? (let ([lib-plt-dir (build-path lib-dir "plt")]) - (unless (directory-exists? lib-plt-dir) - (make-directory lib-plt-dir)) (let ([copy-bin (lambda (name variant gr?) + (make-directory* lib-plt-dir) (copy-file* (build-path (if gr? (find-lib-dir) (find-console-bin-dir)) @@ -241,28 +224,6 @@ (memq 'gracketcs types)) (copy-shared-lib "racketcs" lib-dir)))))])) - (define (search-dll dll-dir dll) - (if dll-dir - (build-path dll-dir dll) - (let* ([exe-dir - (let ([exec (path->complete-path - (find-executable-path (find-system-path 'exec-file)) - (find-system-path 'orig-dir))]) - (let-values ([(base name dir?) (split-path exec)]) - base))] - [paths (cons - exe-dir - (path-list-string->path-list - (or (getenv "PATH") "") - (list (find-system-path 'sys-dir))))]) - (or (ormap (lambda (p) - (let ([p (build-path p dll)]) - (and (file-exists? p) - p))) - paths) - ;; Can't find it, so just use executable's dir: - (build-path exe-dir dll))))) - (define (copy-framework name variant lib-dir) (let* ([fw-name (format "~a.framework" name)] [sub-dir (build-path fw-name "Versions" @@ -298,6 +259,7 @@ (define avail-lib-files #f) (define (copy-shared-lib name lib-dir) + (make-directory* lib-dir) (unless avail-lib-files (set! avail-lib-files (directory-list (find-cross-dll-dir)))) (let* ([rx (byte-regexp (string->bytes/latin-1 @@ -320,7 +282,8 @@ (case (cross-system-type) [(windows) (for-each (lambda (b) - (update-dll-dir b "lib")) + (unless (current-no-dlls? b) + (update-dll-dir b "lib"))) binaries)] [(macosx) (if (and (= 1 (length types)) diff --git a/racket/collects/compiler/embed.rkt b/racket/collects/compiler/embed.rkt index f24e0cda4c..53aade8836 100644 --- a/racket/collects/compiler/embed.rkt +++ b/racket/collects/compiler/embed.rkt @@ -22,9 +22,9 @@ "private/collects-path.rkt" "private/configdir.rkt" "private/write-perm.rkt" + "private/win-dll-list.rkt" "find-exe.rkt") - (provide/contract [make-embedding-executable (->* (path-string? any/c @@ -558,7 +558,7 @@ [_else (error 'create-empbedding-executable "expansion mismatch when getting external paths: ~e" (syntax->datum e))]))))] - + [extra-runtime-paths (filter values (map (lambda (p) @@ -1116,7 +1116,8 @@ early-literal-expressions config? literal-files literal-expressions collects-dest on-extension program-name compiler expand-namespace - src-filter get-extra-imports on-decls-done) + src-filter get-extra-imports on-decls-done + embedded-dlls-box) (let* ([program-name-bytes (if program-name (path->bytes program-name) #"?")] @@ -1248,7 +1249,15 @@ p))) (let ([p (cond [(bytes? p) (bytes->path p)] - [(so-spec? p) (so-find p)] + [(so-spec? p) + (define path (so-find p)) + (cond + [(and path embedded-dlls-box) + (set-box! embedded-dlls-box (cons path (unbox embedded-dlls-box))) + ;; Don't record the path in the executable since we'll + ;; record the whole DLL in the executable + #f] + [else path])] [(and (list? p) (eq? 'lib (car p))) (let ([p (if (null? (cddr p)) @@ -1356,7 +1365,8 @@ #f ; program-name compiler expand-namespace src-filter get-extra-imports - void)) + void + #f)) ; don't accumulate embedded DLLs ;; The old interface: @@ -1501,20 +1511,28 @@ "/") dest mred?)))))) + (define embed-dlls? (and (eq? 'windows (cross-system-type)) + (let ([m (assq 'embed-dlls? aux)]) + (and m (cdr m))))) + (define embedded-dlls-box (and embed-dlls? (box null))) (when (eq? 'windows (cross-system-type)) - (let ([m (or (assq 'dll-dir aux) - (and relative? '(dll-dir . #f)))]) - (if m - (if (cdr m) - (update-dll-dir dest (cdr m)) - ;; adjust relative path, since exe directory can change: - (update-dll-dir dest (find-relative-path* dest (find-cross-dll-dir)))) - ;; Check whether we need an absolute path to DLLs: - (let ([dir (get-current-dll-dir dest)]) - (when (relative-path? dir) - (let-values ([(orig-dir name dir?) (split-path - (path->complete-path orig-exe))]) - (update-dll-dir dest (build-path orig-dir dir)))))))) + (cond + [embed-dlls? + (update-dll-dir dest #t)] + [else + (let ([m (or (assq 'dll-dir aux) + (and relative? '(dll-dir . #f)))]) + (if m + (if (cdr m) + (update-dll-dir dest (cdr m)) + ;; adjust relative path, since exe directory can change: + (update-dll-dir dest (find-relative-path* dest (find-cross-dll-dir)))) + ;; Check whether we need an absolute path to DLLs: + (let ([dir (get-current-dll-dir dest)]) + (when (relative-path? dir) + (let-values ([(orig-dir name dir?) (split-path + (path->complete-path orig-exe))]) + (update-dll-dir dest (build-path orig-dir dir)))))))])) (define (adjust-config-dir) (let ([m (or (assq 'config-dir aux) (and relative? '(config-dir . #f)))] @@ -1567,7 +1585,8 @@ expand-namespace src-filter get-extra-imports - (lambda (outp) (set! pos (file-position outp)))) + (lambda (outp) (set! pos (file-position outp))) + embedded-dlls-box) pos)] [make-full-cmdline (lambda (start decl-end end) @@ -1638,7 +1657,24 @@ 1 1033 ; U.S. English bstr)) - (update-resources dest-exe pe new-rsrcs) + (define new+dll-rsrcs + (if embed-dlls? + (resource-set new-rsrcs + ;; Racket's "user-defined" type for embedded DLLs: + 258 + 1 + 1033 ; U.S. English + (pack-embedded-dlls + (append + (get-racket-dlls + (list + (case (cross-system-type 'gc) + [(3m) (if mred? 'gracket3m 'racket3m)] + [(cgc) (if mred? 'gracketcgc 'racketcgc)] + [(cs) (if mred? 'gracketcs 'racketcs)]))) + (unbox embedded-dlls-box)))) + new-rsrcs)) + (update-resources dest-exe pe new+dll-rsrcs) (values 0 decl-len init-len (+ init-len cmdline-len))] [(and (eq? (cross-system-type) 'macosx) (not unix-starter?)) @@ -1828,3 +1864,36 @@ (define (find-relative-path* wrt-exe p) (define-values (wrt base name) (split-path (path->complete-path wrt-exe))) (find-relative-path (simplify-path wrt) (simplify-path p))) + +;; To embed DLLs in the executable as resource ID 258: +(define (pack-embedded-dlls name-or-paths) + (define bstrs (for/list ([p (in-list name-or-paths)]) + (file->bytes (if (string? p) + (search-dll p) + p)))) + (define names (for/list ([p (in-list name-or-paths)]) + (if (string? p) + p + (let-values ([(base name dir) (split-path p)]) + (path-element->string name))))) + (define start-pos (+ 4 ; count + ;; name array: + (for/sum ([p (in-list names)]) + (+ 2 (bytes-length (string->bytes/utf-8 p)))) + ;; starting-position array: + (* 4 (add1 (length names))))) + (define-values (rev-offsets total) + (for/fold ([rev-offsets null] [total start-pos]) ([bstr (in-list bstrs)]) + (values (cons total rev-offsets) + (+ total (bytes-length bstr))))) + (apply + bytes-append + (integer->integer-bytes (length names) 4 #t #f) + (append + (for/list ([p (in-list names)]) + (define bstr (string->bytes/utf-8 p)) + (bytes-append (integer->integer-bytes (bytes-length bstr) 2 #t #f) bstr)) + (for/list ([offset (in-list (reverse rev-offsets))]) + (integer->integer-bytes offset 4 #t #f)) + (list (integer->integer-bytes total 4 #t #f)) + bstrs))) diff --git a/racket/collects/compiler/private/win-dll-list.rkt b/racket/collects/compiler/private/win-dll-list.rkt new file mode 100644 index 0000000000..0296bac182 --- /dev/null +++ b/racket/collects/compiler/private/win-dll-list.rkt @@ -0,0 +1,56 @@ +#lang racket/base +(require setup/dirs + dynext/filename-version) + +(provide get-racket-dlls + search-dll) + +(define (get-racket-dlls types #:extras-only? [extras-only? #f]) + (define (versionize template) + (let ([f (search-dll (format template filename-version-part))]) + (if (file-exists? f) + (format template filename-version-part) + (format template "xxxxxxx")))) + (append + (list + "libiconv-2.dll" + "longdouble.dll") + (if extras-only? + '() + (cond + [(or (memq 'racketcgc types) + (memq 'gracketcgc types)) + (list + (versionize "libracket~a.dll") + (versionize "libmzgc~a.dll"))] + [(or (memq 'racket3m types) + (memq 'gracket3m types)) + (list + (versionize "libracket3m~a.dll"))] + [(or (memq 'racketcs types) + (memq 'gracketcs types)) + (list + (versionize "libracketcs~a.dll"))])))) + +(define (search-dll dll) + (define dll-dir (find-cross-dll-dir)) + (if dll-dir + (build-path dll-dir dll) + (let* ([exe-dir + (let ([exec (path->complete-path + (find-executable-path (find-system-path 'exec-file)) + (find-system-path 'orig-dir))]) + (let-values ([(base name dir?) (split-path exec)]) + base))] + [paths (cons + exe-dir + (path-list-string->path-list + (or (getenv "PATH") "") + (list (find-system-path 'sys-dir))))]) + (or (ormap (lambda (p) + (let ([p (build-path p dll)]) + (and (file-exists? p) + p))) + paths) + ;; Can't find it, so just use executable's dir: + (build-path exe-dir dll))))) diff --git a/racket/collects/compiler/private/windlldir.rkt b/racket/collects/compiler/private/windlldir.rkt index 5493d381d1..d0b8333353 100644 --- a/racket/collects/compiler/private/windlldir.rkt +++ b/racket/collects/compiler/private/windlldir.rkt @@ -4,7 +4,8 @@ "winutf16.rkt") (provide update-dll-dir - get-current-dll-dir) + get-current-dll-dir + current-no-dlls?) (define label (delay/sync (byte-regexp (bytes->utf-16-bytes #"dLl dIRECTORy:")))) (define max-dir-len (* 512 2)) ; sizeof(wchar_t) is 2 @@ -27,7 +28,7 @@ (write-bytes path-bytes) (write-byte 0)) #:exists 'update)))) - + (define (get-current-dll-dir dest) (with-input-from-file dest (lambda () @@ -35,4 +36,7 @@ (error 'get-current-dll-dir "cannot find DLL path in file: ~e" dest)) (let ([p (make-limited-input-port (current-input-port) max-dir-len)]) (let ([m (regexp-match #rx#"(?:[^\0].|.[^\0])*" p)]) - (bytes->path (utf-16-bytes->bytes (car m))))))))) + (bytes->path (utf-16-bytes->bytes (car m)))))))) + + (define (current-no-dlls? dest) + (regexp-match? #rx#"^<" (get-current-dll-dir dest)))) diff --git a/racket/collects/compiler/private/xform.rkt b/racket/collects/compiler/private/xform.rkt index 89ae848f9c..bb637213c8 100644 --- a/racket/collects/compiler/private/xform.rkt +++ b/racket/collects/compiler/private/xform.rkt @@ -620,6 +620,9 @@ [(and (tok? e) (eq? (tok-n e) 'XFORM_GC_VARIABLE_STACK_THROUGH_FUNCTION)) 'function] + [(and (tok? e) + (eq? (tok-n e) 'XFORM_GC_VARIABLE_STACK_THROUGH_DELTA)) + 'delta] [(and (tok? e) (eq? (tok-n e) 'XFORM_GC_VARIABLE_STACK_THROUGH_DIRECT_FUNCTION)) 'direct-function] @@ -643,6 +646,8 @@ "#define GC_VARIABLE_STACK ((scheme_get_thread_local_variables())->GC_variable_stack_)\n"] [(thread-local) "#define GC_VARIABLE_STACK ((&scheme_thread_locals)->GC_variable_stack_)\n"] + [(delta) + "#define GC_VARIABLE_STACK (((Thread_Local_Variables *)((char *)&scheme_thread_locals_space + scheme_tls_delta))->GC_variable_stack_)\n"] [else "#define GC_VARIABLE_STACK GC_variable_stack\n"])) (if (or gc-variable-stack-through-funcs? @@ -1713,6 +1718,7 @@ (or (eq? 'XFORM_GC_VARIABLE_STACK_THROUGH_GETSPECIFIC (tok-n (car e))) (eq? 'XFORM_GC_VARIABLE_STACK_THROUGH_FUNCTION (tok-n (car e))) (eq? 'XFORM_GC_VARIABLE_STACK_THROUGH_DIRECT_FUNCTION (tok-n (car e))) + (eq? 'XFORM_GC_VARIABLE_STACK_THROUGH_DELTA (tok-n (car e))) (eq? 'XFORM_GC_VARIABLE_STACK_THROUGH_THREAD_LOCAL (tok-n (car e)))))) (define (access-modifier? e) diff --git a/racket/src/gracket/Makefile.in b/racket/src/gracket/Makefile.in index 44f82f6e1e..7b767a1a14 100644 --- a/racket/src/gracket/Makefile.in +++ b/racket/src/gracket/Makefile.in @@ -69,8 +69,8 @@ GRACKETLDFLAGS = $(LDFLAGS) -L../racket GRACKETRES@NOT_MINGW@ = GRACKETRESDEP@NOT_MINGW@ = -GRACKETRES@MINGW@ = -mwindows gres.o -GRACKETRESDEP@MINGW@ = gres.o +GRACKETRES@MINGW@ = -mwindows gres.o ../racket/MemoryModule.@LTO@ +GRACKETRESDEP@MINGW@ = gres.o ../racket/MemoryModule.@LTO@ LOCALFLAGS_wx_xt = @WX_MMD_FLAG@ LOCALFLAGS_wx_mac = -I$(srcdir)/../mac/racket -MMD -DWX_CARBON diff --git a/racket/src/gracket/gc2/Makefile.in b/racket/src/gracket/gc2/Makefile.in index cb4af63efd..95cbe7fd30 100644 --- a/racket/src/gracket/gc2/Makefile.in +++ b/racket/src/gracket/gc2/Makefile.in @@ -56,7 +56,7 @@ MZMMM_wx_xt = @RUN_RACKET_MMM@ MZMMM_wx_mac = @RUN_RACKET_MMM@ MZMMM = $(MZMMM_@WXVARIANT@) -SETUP_BOOT = -l- setup @BOOT_MODE@ $(srcdir)/../../setup-go.rkt ../../compiled +SETUP_BOOT = -O "info@compiler/cm error" -l- setup @BOOT_MODE@ $(srcdir)/../../setup-go.rkt ../../compiled XFORM_CMD = $(MZMMM) $(SELF_RACKET_FLAGS) $(SETUP_BOOT) --tag ++out $(srcdir)/../../racket/gc2/xform-mod.rkt XFORM_CPP_ARGS = -I$(srcdir)/../../racket/gc2 $(NOGCINC) $(OPTIONS) @PREFLAGS@ $(XFORM_INC_@WXVARIANT@) @@ -105,8 +105,8 @@ GRACKETMZOBJS_la = ../gracket@MMM@@NOT_OSX@@NOT_MINGW@: grmain.@LTO@ ../../racket/libracket3m.@LIBSFX@ $(LIBRKTIO_@LIBSFX@) $(GRACKETLINKER) $(GRACKETLDFLAGS) -o ../gracket@MMM@ grmain.@LTO@ ../../racket/libracket3m.@LIBSFX@ $(GRACKETMZOBJS_@LIBSFX@) $(GRACKETLIBS_@LIBSFX@) -../gracket@MMM@@MINGW@: grmain.@LTO@ ../../racket/gc2/libracket3m.dll.a ../gres.o - $(GRACKETLINKER) -mwindows $(GRACKETLDFLAGS) -o ../gracket@MMM@ grmain.@LTO@ ../gres.o ../../racket/gc2/libracket3m.dll.a $(GRACKETMZOBJS_@LIBSFX@) $(GRACKETLIBS_@LIBSFX@) -l delayimp -static-libgcc +../gracket@MMM@@MINGW@: grmain.@LTO@ ../../racket/MemoryModule.@LTO@ ../../racket/gc2/libracket3m.dll.a ../gres.o + $(GRACKETLINKER) -mwindows $(GRACKETLDFLAGS) -o ../gracket@MMM@ grmain.@LTO@ ../../racket/MemoryModule.@LTO@ ../gres.o ../../racket/gc2/libracket3m.dll.a $(GRACKETMZOBJS_@LIBSFX@) $(GRACKETLIBS_@LIBSFX@) -l delayimp -static-libgcc MZFW = ../../racket/Racket.framework/Versions/$(FWVERSION)_3m/Racket MRAPPSKEL = ../GRacket@MMM@.app/Contents/Info.plist diff --git a/racket/src/racket/Makefile.in b/racket/src/racket/Makefile.in index a5a2ce8bdb..0ac6b59cba 100644 --- a/racket/src/racket/Makefile.in +++ b/racket/src/racket/Makefile.in @@ -242,15 +242,18 @@ libracket.dll.a: lib/libracketxxxxxxx.dll libmzgc.dll.a: lib/libmzgcxxxxxxx.dll @DLLTOOL@ --def libmzgc.def -D libmzgcxxxxxxx.dll --output-lib libmzgcxxxxxxx.lib --output-exp libmzgcxxxxxxx.lib --output-delaylib libmzgc.dll.a -rres.o : $(srcdir)/../worksp/racket/racket.rc +rres.o: $(srcdir)/../worksp/racket/racket.rc @WINDRES@ -i $(srcdir)/../worksp/racket/racket.rc -o rres.o MW_RACKET_LIBS = libracket.dll.a libmzgc.dll.a @LDFLAGS@ @LIBS@ -ldelayimp -static-libgcc -racket@CGC@@MINGW@: libracket.dll.a libmzgc.dll.a main.@LTO@ $(SPECIALIZINGOBJECTS) rres.o - @MZLINKER@ -o racket@CGC@ main.@LTO@ rres.o $(SPECIALIZINGOBJECTS) $(MW_RACKET_LIBS) +racket@CGC@@MINGW@: libracket.dll.a libmzgc.dll.a main.@LTO@ MemoryModule.@LTO@ $(SPECIALIZINGOBJECTS) rres.o + @MZLINKER@ -o racket@CGC@ main.@LTO@ MemoryModule.@LTO@ rres.o $(SPECIALIZINGOBJECTS) $(MW_RACKET_LIBS) -mingw-other@MINGW@: mzsj86g.o rres.o comres.o com_glue.@LTO@ +MemoryModule.@LTO@: $(srcdir)/../start/MemoryModule.c $(srcdir)/../start/MemoryModule.h + $(CC) -c -I $(srcdir)/../start -o MemoryModule.@LTO@ $(srcdir)/../start/MemoryModule.c + +mingw-other@MINGW@: mzsj86g.o MemoryModule.@LTO@ rres.o comres.o com_glue.@LTO@ $(NOOP) mingw-other@NOT_MINGW@: @@ -270,7 +273,8 @@ DEF_C_DIRS = $(DEF_COLLECTS_DIR) $(DEF_CONFIG_DIR) MAIN_HEADER_DEPS = $(srcdir)/include/scheme.h $(srcdir)/include/schthread.h $(srcdir)/sconfig.h \ $(srcdir)/src/stypes.h $(srcdir)/cmdline.inc $(srcdir)/parse_cmdl.inc \ - $(srcdir)/../start/config.inc $(srcdir)/../start/delayed.inc $(srcdir)/parse_cmdl.inc + $(srcdir)/../start/config.inc $(srcdir)/../start/delayed.inc $(srcdir)/parse_cmdl.inc \ + $(srcdir)/../start/embedded_dll.inc main.@LTO@: $(srcdir)/main.c $(MAIN_HEADER_DEPS) $(CC) -I$(builddir) -I$(srcdir)/include $(CFLAGS) $(CPPFLAGS) @OPTIONS@ @MZOPTIONS@ $(DEF_C_DIRS) -c $(srcdir)/main.c -o main.@LTO@ diff --git a/racket/src/racket/gc2/Makefile.in b/racket/src/racket/gc2/Makefile.in index 654b315972..3583aa1eb0 100644 --- a/racket/src/racket/gc2/Makefile.in +++ b/racket/src/racket/gc2/Makefile.in @@ -617,8 +617,8 @@ libracket3m.dll.a: ../lib/libracket3mxxxxxxx.dll MW_RACKET_LIBS = gc2/libracket3m.dll.a @LDFLAGS@ @LIBS@ -ldelayimp -static-libgcc -../racket@MMM@@MINGW@: libracket3m.dll.a main.@LTO@ ../rres.o $(SPECIALIZINGOBJECTS) - cd ..; @MZLINKER@ -o racket@MMM@ gc2/main.@LTO@ rres.o $(SPECIALIZINGOBJECTS) $(MW_RACKET_LIBS) +../racket@MMM@@MINGW@: libracket3m.dll.a main.@LTO@ ../MemoryModule.@LTO@ ../rres.o $(SPECIALIZINGOBJECTS) + cd ..; @MZLINKER@ -o racket@MMM@ gc2/main.@LTO@ MemoryModule.@LTO@ rres.o $(SPECIALIZINGOBJECTS) $(MW_RACKET_LIBS) ../mzcom@MMM@@NOT_MINGW@: $(NOOP) diff --git a/racket/src/racket/include/scheme.h b/racket/src/racket/include/scheme.h index aee08f2ab6..b5f9956b25 100644 --- a/racket/src/racket/include/scheme.h +++ b/racket/src/racket/include/scheme.h @@ -1921,6 +1921,9 @@ MZ_EXTERN void scheme_set_compiled_file_paths(Scheme_Object *list); MZ_EXTERN void scheme_set_compiled_file_roots(Scheme_Object *list); #ifdef DOS_FILE_SYSTEM MZ_EXTERN void scheme_set_dll_path(wchar_t *s); +typedef void *(*scheme_dll_open_proc)(const char *name, int as_global); +typedef void *(*scheme_dll_find_object_proc)(void *h, const char *name); +MZ_EXTERN void scheme_set_dll_procs(scheme_dll_open_proc, scheme_dll_find_object_proc); #endif MZ_EXTERN void scheme_init_collection_paths(Scheme_Env *global_env, Scheme_Object *extra_dirs); diff --git a/racket/src/racket/include/schthread.h b/racket/src/racket/include/schthread.h index 7069f90c4f..6c1e54da15 100644 --- a/racket/src/racket/include/schthread.h +++ b/racket/src/racket/include/schthread.h @@ -35,6 +35,7 @@ extern "C" { # else # define THREAD_LOCAL __declspec(thread) # define MZ_THREAD_EXTERN extern +# define IMPLEMENT_THREAD_LOCAL_VIA_OFFSET # define IMPLEMENT_THREAD_LOCAL_EXTERNALLY_VIA_PROC # endif # else @@ -486,6 +487,13 @@ static __inline Thread_Local_Variables *scheme_get_thread_local_variables(void) END_XFORM_SKIP; XFORM_GC_VARIABLE_STACK_THROUGH_FUNCTION; # endif +# elif defined (IMPLEMENT_THREAD_LOCAL_VIA_OFFSET) +MZ_THREAD_EXTERN THREAD_LOCAL Thread_Local_Variables scheme_thread_locals_space; +extern int scheme_tls_delta; +# define scheme_get_thread_local_variables() ((Thread_Local_Variables *)((char *)&scheme_thread_locals_space + scheme_tls_delta)) +# ifdef MZ_XFORM +XFORM_GC_VARIABLE_STACK_THROUGH_DELTA; +# endif # else MZ_THREAD_EXTERN THREAD_LOCAL Thread_Local_Variables scheme_thread_locals; # define scheme_get_thread_local_variables() (&scheme_thread_locals) diff --git a/racket/src/racket/main.c b/racket/src/racket/main.c index 0ecb01826d..a6af61f9b7 100644 --- a/racket/src/racket/main.c +++ b/racket/src/racket/main.c @@ -311,13 +311,22 @@ START_XFORM_SKIP; #ifdef DOS_FILE_SYSTEM # include "win_tls.inc" +# include "../start/embedded_dll.inc" #endif #ifdef DOS_FILE_SYSTEM +static int load_delayed_done; + void load_delayed() { + if (load_delayed_done) + return; + load_delayed_done = 1; + (void)SetErrorMode(SEM_FAILCRITICALERRORS); + parse_embedded_dlls(); + # ifndef MZ_NO_LIBRACKET_DLL /* Order matters: load dependencies first */ # ifndef MZ_PRECISE_GC @@ -327,6 +336,8 @@ void load_delayed() # endif record_dll_path(); + register_embedded_dll_hooks(); + register_win_tls(); } #endif diff --git a/racket/src/racket/src/file.c b/racket/src/racket/src/file.c index ec8297731b..4d80274352 100644 --- a/racket/src/racket/src/file.c +++ b/racket/src/racket/src/file.c @@ -5098,6 +5098,9 @@ void scheme_set_addon_dir(Scheme_Object *p) #ifdef DOS_FILE_SYSTEM +static scheme_dll_open_proc alt_dll_open; +static scheme_dll_find_object_proc alt_find_obj; + void scheme_set_dll_path(wchar_t *p) { rktio_set_dll_path(p); @@ -5117,4 +5120,34 @@ wchar_t *scheme_get_dll_path(wchar_t *p) return r2; } +void scheme_set_dll_procs(scheme_dll_open_proc dll_open, scheme_dll_find_object_proc find_obj) +{ + rktio_set_dll_procs(dll_open, find_obj); + alt_dll_open = dll_open; + alt_find_obj = find_obj; +} + +HANDLE scheme_dll_load_library(const char *s, const wchar_t *ws, int *_mode) +{ + if (alt_dll_open) { + void *h; + h = alt_dll_open(s, 0); + if (h) { + *_mode = 1; + return (HANDLE)h; + } + } + + *_mode = 0; + return LoadLibraryW(ws); +} + +void *scheme_dll_get_proc_address(HANDLE m, const char *name, int dll_mode) +{ + if (dll_mode) + return alt_find_obj((void *)m, name); + else + return GetProcAddress(m, name); +} + #endif diff --git a/racket/src/racket/src/longdouble/longdouble.c b/racket/src/racket/src/longdouble/longdouble.c index 067597f5f3..e525793d2f 100644 --- a/racket/src/racket/src/longdouble/longdouble.c +++ b/racket/src/racket/src/longdouble/longdouble.c @@ -737,12 +737,14 @@ static void fail_sprint(char* buffer, int digits, long_double ld) void scheme_load_long_double_dll() { HANDLE m; - m = LoadLibraryW(scheme_get_dll_path(L"longdouble.dll")); + int dll_mode; + + m = scheme_dll_load_library("longdouble.dll", L"longdouble.dll", &dll_mode); if (m) long_double_dll_available = 1; # define EXTRACT_LDBL(name, fail) \ - _imp_ ## name = (name ##_t)(m ? GetProcAddress(m, # name) : NULL); \ + _imp_ ## name = (name ##_t)(m ? scheme_dll_get_proc_address(m, # name, dll_mode) : NULL); \ if (!(_imp_ ## name)) _imp_ ## name = (name ##_t)fail; EXTRACT_LDBL(get_long_double_infinity_val, fail_long_double_infinity); diff --git a/racket/src/racket/src/salloc.c b/racket/src/racket/src/salloc.c index 9bb8bc2943..4f45b0fe18 100644 --- a/racket/src/racket/src/salloc.c +++ b/racket/src/racket/src/salloc.c @@ -74,6 +74,8 @@ uintptr_t scheme_tls_delta; int scheme_tls_index; # elif defined(IMPLEMENT_THREAD_LOCAL_VIA_WIN_TLS_FUNC) DWORD scheme_thread_local_key; +# elif defined(IMPLEMENT_THREAD_LOCAL_VIA_OFFSET) +SHARED_OK THREAD_LOCAL Thread_Local_Variables scheme_thread_locals_space; # else SHARED_OK THREAD_LOCAL Thread_Local_Variables scheme_thread_locals; # endif @@ -193,6 +195,8 @@ int scheme_main_setup(int no_auto_statics, Scheme_Env_Main _main, int argc, char return scheme_main_stack_setup(no_auto_statics, call_with_basic, &d); } +extern int _tls_index; + static int do_main_stack_setup(int no_auto_statics, Scheme_Nested_Main _main, void *data) { void *stack_start; @@ -232,6 +236,19 @@ Thread_Local_Variables *scheme_external_get_thread_local_variables() XFORM_SKIP_ { return scheme_get_thread_local_variables(); } +#elif defined(IMPLEMENT_THREAD_LOCAL_VIA_OFFSET) +int scheme_tls_delta; +extern int _tls_index; +void scheme_register_tls_space(void *tls_space, int tls_index) XFORM_SKIP_PROC +{ + if (_tls_index == 0) { + /* The Racket DLL didn't get its own index, which means that it's + being instantiated in-memory instead of loaded from a ".dll" file. + Use space reserved by the application for thread-local variables. */ + scheme_tls_delta = ((char *)tls_space - (char *)&scheme_thread_locals_space); + } else + scheme_tls_delta = 0; +} #else void scheme_register_tls_space(void *tls_space, int tls_index) XFORM_SKIP_PROC { diff --git a/racket/src/racket/src/schpriv.h b/racket/src/racket/src/schpriv.h index 8f3e1b2498..4b3240f044 100644 --- a/racket/src/racket/src/schpriv.h +++ b/racket/src/racket/src/schpriv.h @@ -3987,4 +3987,9 @@ void scheme_process_global_unlock(void); Scheme_Object *scheme_expander_syntax_to_datum(Scheme_Object *v); int scheme_is_syntax(Scheme_Object *v); +#ifdef DOS_FILE_SYSTEM +HANDLE scheme_dll_load_library(const char *s, const wchar_t *ws, int *_mode); +void *scheme_dll_get_proc_address(HANDLE m, const char *name, int dll_mode); +#endif + #endif /* __mzscheme_private__ */ diff --git a/racket/src/racket/win_tls.inc b/racket/src/racket/win_tls.inc index deace2adcb..07aeb77be9 100644 --- a/racket/src/racket/win_tls.inc +++ b/racket/src/racket/win_tls.inc @@ -35,5 +35,18 @@ static void register_win_tls() } #else -static void register_win_tls() {} + +/* For emebdded-DLL mode, make sure there's a thread-local space to + be taken ovver by the Racket DLL */ +# ifdef USE_THREAD_LOCAL + +static __declspec(thread) Thread_Local_Variables tls_space; +static void register_win_tls() { + scheme_register_tls_space(&tls_space, 0); +} + +# else +static void register_win_tls() { } +# endif + #endif diff --git a/racket/src/rktio/rktio.h b/racket/src/rktio/rktio.h index c30cac1a33..a867ca3068 100644 --- a/racket/src/rktio/rktio.h +++ b/racket/src/rktio/rktio.h @@ -1167,6 +1167,12 @@ RKTIO_EXTERN char *rktio_dll_get_error(rktio_t *rktio); if no error string is available or has already been returned. See `rktio_dll_open` for more information. */ +typedef void *(*dll_open_proc)(rktio_const_string_t name, rktio_bool_t as_global); +typedef void *(*dll_find_object_proc)(void *h, rktio_const_string_t name); +RKTIO_EXTERN void rktio_set_dll_procs(dll_open_proc dll_open, dll_find_object_proc dll_find_object); +/* Installs procedures that are tried before native mechanisms, + currently only supported for Windows. */ + /*************************************************/ /* Errors */ diff --git a/racket/src/rktio/rktio_convert.c b/racket/src/rktio/rktio_convert.c index 2defb2da85..dcb9a8099e 100644 --- a/racket/src/rktio/rktio_convert.c +++ b/racket/src/rktio/rktio_convert.c @@ -70,16 +70,24 @@ static int iconv_ready = 0; static void init_iconv() { - HMODULE m; + HMODULE m = NULL; wchar_t *p; - - p = rktio_get_dll_path(L"iconv.dll"); - if (p) { - m = LoadLibraryW(p); - free(p); - } else - m = NULL; - + int hook_handle = 0; + + /* Try embedded "libiconv-2.dll", first: */ + m = rktio_load_library("libiconv-2.dll"); + if (m) + hook_handle = 1; + + if (!m) { + p = rktio_get_dll_path(L"iconv.dll"); + if (p) { + m = LoadLibraryW(p); + free(p); + } else + m = NULL; + } + if (!m) { p = rktio_get_dll_path(L"libiconv.dll"); if (p) { @@ -106,10 +114,17 @@ static void init_iconv() m = LoadLibraryW(L"libiconv-2.dll"); if (m) { - iconv = (iconv_proc_t)GetProcAddress(m, "libiconv"); - iconv_open = (iconv_open_proc_t)GetProcAddress(m, "libiconv_open"); - iconv_close = (iconv_close_proc_t)GetProcAddress(m, "libiconv_close"); - locale_charset = (locale_charset_proc_t)GetProcAddress(m, "locale_charset"); + if (hook_handle) { + iconv = (iconv_proc_t)rktio_get_proc_address(m, "libiconv"); + iconv_open = (iconv_open_proc_t)rktio_get_proc_address(m, "libiconv_open"); + iconv_close = (iconv_close_proc_t)rktio_get_proc_address(m, "libiconv_close"); + locale_charset = (locale_charset_proc_t)rktio_get_proc_address(m, "locale_charset"); + } else { + iconv = (iconv_proc_t)GetProcAddress(m, "libiconv"); + iconv_open = (iconv_open_proc_t)GetProcAddress(m, "libiconv_open"); + iconv_close = (iconv_close_proc_t)GetProcAddress(m, "libiconv_close"); + locale_charset = (locale_charset_proc_t)GetProcAddress(m, "locale_charset"); + } /* Make sure we have all of them or none: */ if (!iconv || !iconv_open || !iconv_close) { iconv = NULL; @@ -119,7 +134,10 @@ static void init_iconv() } if (iconv) { - iconv_errno = (errno_proc_t)GetProcAddress(m, "_errno"); + if (hook_handle) + iconv_errno = (errno_proc_t)rktio_get_proc_address(m, "_errno"); + else + iconv_errno = (errno_proc_t)GetProcAddress(m, "_errno"); if (!iconv_errno) { /* The iconv.dll distributed with Racket links to msvcrt.dll. It's a slighly dangerous assumption that whatever iconv we @@ -349,7 +367,7 @@ char *rktio_system_language_country(rktio_t *rktio) llen = GetLocaleInfoW(l, LOCALE_SENGLANGUAGE, NULL, 0); lang = malloc(llen * sizeof(wchar_t)); - GetLocaleInfo(l, LOCALE_SENGLANGUAGE, lang, llen); + GetLocaleInfoW(l, LOCALE_SENGLANGUAGE, lang, llen); if (llen) llen -= 1; /* drop nul terminator */ diff --git a/racket/src/rktio/rktio_dll.c b/racket/src/rktio/rktio_dll.c index 17605a5b44..a45fa1225e 100644 --- a/racket/src/rktio/rktio_dll.c +++ b/racket/src/rktio/rktio_dll.c @@ -20,10 +20,15 @@ typedef void *dll_handle_t; #ifdef RKTIO_SYSTEM_WINDOWS typedef HANDLE dll_handle_t; +static dll_open_proc LoadLibraryHook; +static dll_find_object_proc GetProcAddressHook; #endif struct rktio_dll_t { void *handle; +#ifdef RKTIO_SYSTEM_WINDOWS + int hook_handle; +#endif char *name; rktio_hash_t *objects_by_name; rktio_dll_object_t *all_objects; @@ -38,7 +43,10 @@ rktio_dll_t *rktio_dll_open(rktio_t *rktio, rktio_const_string_t name, rktio_boo intptr_t key; dll_handle_t handle; int null_ok = 0; - +#ifdef RKTIO_SYSTEM_WINDOWS + int hook_handle = 0; +#endif + if (!rktio->dlls_by_name) rktio->dlls_by_name = rktio_hash_new(); @@ -76,13 +84,21 @@ rktio_dll_t *rktio_dll_open(rktio_t *rktio, rktio_const_string_t name, rktio_boo #ifdef RKTIO_SYSTEM_WINDOWS if (!name) { - /* openning the executable is marked by a NULL handle */ + /* opening the executable is marked by a NULL handle */ handle = NULL; null_ok = 1; } else { - handle = LoadLibraryW(WIDE_PATH_temp(name)); - if (!handle) - get_windows_error(); + if (LoadLibraryHook) + handle = LoadLibraryHook(name, as_global); + else + handle = NULL; + if (handle) { + hook_handle = 1; + } else { + handle = LoadLibraryW(WIDE_PATH_temp(name)); + if (!handle) + get_windows_error(); + } } #endif @@ -91,6 +107,9 @@ rktio_dll_t *rktio_dll_open(rktio_t *rktio, rktio_const_string_t name, rktio_boo dll = malloc(sizeof(rktio_dll_t)); dll->handle = handle; +#ifdef RKTIO_SYSTEM_WINDOWS + dll->hook_handle = hook_handle; +#endif dll->name = (name ? MSC_IZE(strdup)(name) : NULL); dll->objects_by_name = rktio_hash_new(); dll->all_objects = NULL; @@ -236,9 +255,12 @@ void *rktio_dll_find_object(rktio_t *rktio, rktio_dll_t *dll, rktio_const_string #endif #ifdef RKTIO_SYSTEM_WINDOWS - if (dll->handle) - address = GetProcAddress(dll->handle, name); - else { + if (dll->handle) { + if (dll->hook_handle) + address = GetProcAddressHook(dll->handle, name); + else + address = GetProcAddress(dll->handle, name); + } else { /* this is for the executable-open case, which was marked by a NULL * handle; deal with it by searching all current modules */ # define NUM_QUICK_MODS 16 @@ -266,6 +288,8 @@ void *rktio_dll_find_object(rktio_t *rktio, rktio_dll_t *dll, rktio_const_string address = NULL; if (mods != quick_mods) free(mods); + if (!address && GetProcAddressHook) + address = GetProcAddressHook(NULL, name); } if (!address) { @@ -319,6 +343,34 @@ RKTIO_EXTERN char *rktio_dll_get_error(rktio_t *rktio) #endif } +/*========================================================================*/ +/* Windows hooks */ +/*========================================================================*/ + +/* Support in-memory DLLs and similar by allowing the application to + install replacements for LoadLibrary and GetProcAddress. */ + +void rktio_set_dll_procs(dll_open_proc dll_open, dll_find_object_proc dll_find_object) +{ +#ifdef RKTIO_SYSTEM_WINDOWS + LoadLibraryHook = dll_open; + GetProcAddressHook = dll_find_object; +#endif +} + +#ifdef RKTIO_SYSTEM_WINDOWS +HANDLE rktio_load_library(rktio_const_string_t name) +{ + if (!LoadLibraryHook) return NULL; + return (HANDLE)LoadLibraryHook(name, 1); +} + +void *rktio_get_proc_address(HANDLE m, rktio_const_string_t name) +{ + return GetProcAddressHook((void *)m, name); +} +#endif + /*========================================================================*/ /* Clean up */ /*========================================================================*/ diff --git a/racket/src/rktio/rktio_private.h b/racket/src/rktio/rktio_private.h index 54cc9fa38d..6ad662900b 100644 --- a/racket/src/rktio/rktio_private.h +++ b/racket/src/rktio/rktio_private.h @@ -298,6 +298,10 @@ void rktio_set_windows_error(rktio_t *rktio, int errid); void rktio_error_clean(rktio_t *rktio); void rktio_dll_clean(rktio_t *rktio); +#ifdef RKTIO_SYSTEM_WINDOWS +HANDLE rktio_load_library(rktio_const_string_t name); +void *rktio_get_proc_address(HANDLE m, rktio_const_string_t name); +#endif #if defined(USE_FNDELAY_O_NONBLOCK) # define RKTIO_NONBLOCKING FNDELAY diff --git a/racket/src/rktio/rktio_process.c b/racket/src/rktio/rktio_process.c index 6875a27f1f..e3d5977925 100644 --- a/racket/src/rktio/rktio_process.c +++ b/racket/src/rktio/rktio_process.c @@ -820,7 +820,8 @@ rktio_status_t *rktio_process_status(rktio_t *rktio, rktio_process_t *sp) get_windows_error(); return NULL; } - } + } else + status = -1; # endif #endif @@ -986,7 +987,7 @@ void rktio_process_deinit(rktio_t *rktio) /*========================================================================*/ #ifdef RKTIO_SYSTEM_WINDOWS -static char *cmdline_protect(char *s) +static char *cmdline_protect(const char *s) { char *naya; int ds; @@ -1292,7 +1293,7 @@ rktio_process_result_t *rktio_process(rktio_t *rktio, for (i = 0; i < argc; i++) { new_argv[i] = cmdline_protect(argv[i]); } - argv = new_argv; + argv = (rktio_const_string_t *)new_argv; } pid = 0; @@ -1310,7 +1311,7 @@ rktio_process_result_t *rktio_process(rktio_t *rktio, if (!windows_exact_cmdline) { for (i = 0; i < argc; i++) { - free(argv[i]); + free((char *)argv[i]); } free(argv); } diff --git a/racket/src/start/MemoryModule.c b/racket/src/start/MemoryModule.c new file mode 100644 index 0000000000..d3925bf7bf --- /dev/null +++ b/racket/src/start/MemoryModule.c @@ -0,0 +1,1207 @@ +/* + * Memory DLL loading code + * Version 0.0.4 + * + * Copyright (c) 2004-2015 by Joachim Bauch / mail@joachim-bauch.de + * http://www.joachim-bauch.de + * + * The contents of this file are subject to the Mozilla Public License Version + * 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is MemoryModule.c + * + * The Initial Developer of the Original Code is Joachim Bauch. + * + * Portions created by Joachim Bauch are Copyright (C) 2004-2015 + * Joachim Bauch. All Rights Reserved. + * + * + * THeller: Added binary search in MemoryGetProcAddress function + * (#define USE_BINARY_SEARCH to enable it). This gives a very large + * speedup for libraries that exports lots of functions. + * + * These portions are Copyright (C) 2013 Thomas Heller. + */ + +#include +#include +#include +#include +#ifdef DEBUG_OUTPUT +#include +#endif + +#if _MSC_VER +// Disable warning about data -> function pointer conversion +#pragma warning(disable:4055) + // C4244: conversion from 'uintptr_t' to 'DWORD', possible loss of data. +#pragma warning(error: 4244) +// C4267: conversion from 'size_t' to 'int', possible loss of data. +#pragma warning(error: 4267) + +#define inline __inline +#endif + +#ifndef IMAGE_SIZEOF_BASE_RELOCATION +// Vista SDKs no longer define IMAGE_SIZEOF_BASE_RELOCATION!? +#define IMAGE_SIZEOF_BASE_RELOCATION (sizeof(IMAGE_BASE_RELOCATION)) +#endif + +#ifdef _WIN64 +#define HOST_MACHINE IMAGE_FILE_MACHINE_AMD64 +#else +#define HOST_MACHINE IMAGE_FILE_MACHINE_I386 +#endif + +#include "MemoryModule.h" + +struct ExportNameEntry { + LPCSTR name; + WORD idx; +}; + +typedef BOOL (WINAPI *DllEntryProc)(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved); +typedef int (WINAPI *ExeEntryProc)(void); + +#ifdef _WIN64 +typedef struct POINTER_LIST { + struct POINTER_LIST *next; + void *address; +} POINTER_LIST; +#endif + +typedef struct { + PIMAGE_NT_HEADERS headers; + unsigned char *codeBase; + HCUSTOMMODULE *modules; + int numModules; + BOOL initialized; + BOOL isDLL; + BOOL isRelocated; + CustomAllocFunc alloc; + CustomFreeFunc free; + CustomLoadLibraryFunc loadLibrary; + CustomGetProcAddressFunc getProcAddress; + CustomFreeLibraryFunc freeLibrary; + struct ExportNameEntry *nameExportsTable; + void *userdata; + ExeEntryProc exeEntry; + DWORD pageSize; +#ifdef _WIN64 + POINTER_LIST *blockedMemory; +#endif +} MEMORYMODULE, *PMEMORYMODULE; + +typedef struct { + LPVOID address; + LPVOID alignedAddress; + SIZE_T size; + DWORD characteristics; + BOOL last; +} SECTIONFINALIZEDATA, *PSECTIONFINALIZEDATA; + +#define GET_HEADER_DICTIONARY(module, idx) &(module)->headers->OptionalHeader.DataDirectory[idx] + +static inline uintptr_t +AlignValueDown(uintptr_t value, uintptr_t alignment) { + return value & ~(alignment - 1); +} + +static inline LPVOID +AlignAddressDown(LPVOID address, uintptr_t alignment) { + return (LPVOID) AlignValueDown((uintptr_t) address, alignment); +} + +static inline size_t +AlignValueUp(size_t value, size_t alignment) { + return (value + alignment - 1) & ~(alignment - 1); +} + +static inline void* +OffsetPointer(void* data, ptrdiff_t offset) { + return (void*) ((uintptr_t) data + offset); +} + +static inline void +OutputLastError(const char *msg) +{ +#ifndef DEBUG_OUTPUT + UNREFERENCED_PARAMETER(msg); +#else + LPVOID tmp; + char *tmpmsg; + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&tmp, 0, NULL); + tmpmsg = (char *)LocalAlloc(LPTR, strlen(msg) + strlen(tmp) + 3); + sprintf(tmpmsg, "%s: %s", msg, tmp); + OutputDebugString(tmpmsg); + LocalFree(tmpmsg); + LocalFree(tmp); +#endif +} + +#ifdef _WIN64 +static void +FreePointerList(POINTER_LIST *head, CustomFreeFunc freeMemory, void *userdata) +{ + POINTER_LIST *node = head; + while (node) { + POINTER_LIST *next; + freeMemory(node->address, 0, MEM_RELEASE, userdata); + next = node->next; + free(node); + node = next; + } +} +#endif + +static BOOL +CheckSize(size_t size, size_t expected) { + if (size < expected) { + SetLastError(ERROR_INVALID_DATA); + return FALSE; + } + + return TRUE; +} + +static BOOL +CopySections(const unsigned char *data, size_t size, PIMAGE_NT_HEADERS old_headers, PMEMORYMODULE module) +{ + int i, section_size; + unsigned char *codeBase = module->codeBase; + unsigned char *dest; + PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(module->headers); + for (i=0; iheaders->FileHeader.NumberOfSections; i++, section++) { + if (section->SizeOfRawData == 0) { + // section doesn't contain data in the dll itself, but may define + // uninitialized data + section_size = old_headers->OptionalHeader.SectionAlignment; + if (section_size > 0) { + dest = (unsigned char *)module->alloc(codeBase + section->VirtualAddress, + section_size, + MEM_COMMIT, + PAGE_READWRITE, + module->userdata); + if (dest == NULL) { + return FALSE; + } + + // Always use position from file to support alignments smaller + // than page size (allocation above will align to page size). + dest = codeBase + section->VirtualAddress; + // NOTE: On 64bit systems we truncate to 32bit here but expand + // again later when "PhysicalAddress" is used. + section->Misc.PhysicalAddress = (DWORD) ((uintptr_t) dest & 0xffffffff); + memset(dest, 0, section_size); + } + + // section is empty + continue; + } + + if (!CheckSize(size, section->PointerToRawData + section->SizeOfRawData)) { + return FALSE; + } + + // commit memory block and copy data from dll + dest = (unsigned char *)module->alloc(codeBase + section->VirtualAddress, + section->SizeOfRawData, + MEM_COMMIT, + PAGE_READWRITE, + module->userdata); + if (dest == NULL) { + return FALSE; + } + + // Always use position from file to support alignments smaller + // than page size (allocation above will align to page size). + dest = codeBase + section->VirtualAddress; + memcpy(dest, data + section->PointerToRawData, section->SizeOfRawData); + // NOTE: On 64bit systems we truncate to 32bit here but expand + // again later when "PhysicalAddress" is used. + section->Misc.PhysicalAddress = (DWORD) ((uintptr_t) dest & 0xffffffff); + } + + return TRUE; +} + +// Protection flags for memory pages (Executable, Readable, Writeable) +static int ProtectionFlags[2][2][2] = { + { + // not executable + {PAGE_NOACCESS, PAGE_WRITECOPY}, + {PAGE_READONLY, PAGE_READWRITE}, + }, { + // executable + {PAGE_EXECUTE, PAGE_EXECUTE_WRITECOPY}, + {PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE}, + }, +}; + +static SIZE_T +GetRealSectionSize(PMEMORYMODULE module, PIMAGE_SECTION_HEADER section) { + DWORD size = section->SizeOfRawData; + if (size == 0) { + if (section->Characteristics & IMAGE_SCN_CNT_INITIALIZED_DATA) { + size = module->headers->OptionalHeader.SizeOfInitializedData; + } else if (section->Characteristics & IMAGE_SCN_CNT_UNINITIALIZED_DATA) { + size = module->headers->OptionalHeader.SizeOfUninitializedData; + } + } + return (SIZE_T) size; +} + +static BOOL +FinalizeSection(PMEMORYMODULE module, PSECTIONFINALIZEDATA sectionData) { + DWORD protect, oldProtect; + BOOL executable; + BOOL readable; + BOOL writeable; + + if (sectionData->size == 0) { + return TRUE; + } + + if (sectionData->characteristics & IMAGE_SCN_MEM_DISCARDABLE) { + // section is not needed any more and can safely be freed + if (sectionData->address == sectionData->alignedAddress && + (sectionData->last || + module->headers->OptionalHeader.SectionAlignment == module->pageSize || + (sectionData->size % module->pageSize) == 0) + ) { + // Only allowed to decommit whole pages + module->free(sectionData->address, sectionData->size, MEM_DECOMMIT, module->userdata); + } + return TRUE; + } + + // determine protection flags based on characteristics + executable = (sectionData->characteristics & IMAGE_SCN_MEM_EXECUTE) != 0; + readable = (sectionData->characteristics & IMAGE_SCN_MEM_READ) != 0; + writeable = (sectionData->characteristics & IMAGE_SCN_MEM_WRITE) != 0; + protect = ProtectionFlags[executable][readable][writeable]; + if (sectionData->characteristics & IMAGE_SCN_MEM_NOT_CACHED) { + protect |= PAGE_NOCACHE; + } + + // change memory access flags + if (VirtualProtect(sectionData->address, sectionData->size, protect, &oldProtect) == 0) { + OutputLastError("Error protecting memory page"); + return FALSE; + } + + return TRUE; +} + +static BOOL +FinalizeSections(PMEMORYMODULE module) +{ + int i; + PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(module->headers); +#ifdef _WIN64 + // "PhysicalAddress" might have been truncated to 32bit above, expand to + // 64bits again. + uintptr_t imageOffset = ((uintptr_t) module->headers->OptionalHeader.ImageBase & 0xffffffff00000000); +#else + static const uintptr_t imageOffset = 0; +#endif + SECTIONFINALIZEDATA sectionData; + sectionData.address = (LPVOID)((uintptr_t)section->Misc.PhysicalAddress | imageOffset); + sectionData.alignedAddress = AlignAddressDown(sectionData.address, module->pageSize); + sectionData.size = GetRealSectionSize(module, section); + sectionData.characteristics = section->Characteristics; + sectionData.last = FALSE; + section++; + + // loop through all sections and change access flags + for (i=1; iheaders->FileHeader.NumberOfSections; i++, section++) { + LPVOID sectionAddress = (LPVOID)((uintptr_t)section->Misc.PhysicalAddress | imageOffset); + LPVOID alignedAddress = AlignAddressDown(sectionAddress, module->pageSize); + SIZE_T sectionSize = GetRealSectionSize(module, section); + // Combine access flags of all sections that share a page + // TODO(fancycode): We currently share flags of a trailing large section + // with the page of a first small section. This should be optimized. + if (sectionData.alignedAddress == alignedAddress || (uintptr_t) sectionData.address + sectionData.size > (uintptr_t) alignedAddress) { + // Section shares page with previous + if ((section->Characteristics & IMAGE_SCN_MEM_DISCARDABLE) == 0 || (sectionData.characteristics & IMAGE_SCN_MEM_DISCARDABLE) == 0) { + sectionData.characteristics = (sectionData.characteristics | section->Characteristics) & ~IMAGE_SCN_MEM_DISCARDABLE; + } else { + sectionData.characteristics |= section->Characteristics; + } + sectionData.size = (((uintptr_t)sectionAddress) + ((uintptr_t) sectionSize)) - (uintptr_t) sectionData.address; + continue; + } + + if (!FinalizeSection(module, §ionData)) { + return FALSE; + } + sectionData.address = sectionAddress; + sectionData.alignedAddress = alignedAddress; + sectionData.size = sectionSize; + sectionData.characteristics = section->Characteristics; + } + sectionData.last = TRUE; + if (!FinalizeSection(module, §ionData)) { + return FALSE; + } + return TRUE; +} + +static BOOL +ExecuteTLS(PMEMORYMODULE module) +{ + unsigned char *codeBase = module->codeBase; + PIMAGE_TLS_DIRECTORY tls; + PIMAGE_TLS_CALLBACK* callback; + + PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY(module, IMAGE_DIRECTORY_ENTRY_TLS); + if (directory->VirtualAddress == 0) { + return TRUE; + } + + tls = (PIMAGE_TLS_DIRECTORY) (codeBase + directory->VirtualAddress); + callback = (PIMAGE_TLS_CALLBACK *) tls->AddressOfCallBacks; + if (callback) { + while (*callback) { + (*callback)((LPVOID) codeBase, DLL_PROCESS_ATTACH, NULL); + callback++; + } + } + return TRUE; +} + +static BOOL +PerformBaseRelocation(PMEMORYMODULE module, ptrdiff_t delta) +{ + unsigned char *codeBase = module->codeBase; + PIMAGE_BASE_RELOCATION relocation; + + PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY(module, IMAGE_DIRECTORY_ENTRY_BASERELOC); + if (directory->Size == 0) { + return (delta == 0); + } + + relocation = (PIMAGE_BASE_RELOCATION) (codeBase + directory->VirtualAddress); + for (; relocation->VirtualAddress > 0; ) { + DWORD i; + unsigned char *dest = codeBase + relocation->VirtualAddress; + unsigned short *relInfo = (unsigned short*) OffsetPointer(relocation, IMAGE_SIZEOF_BASE_RELOCATION); + for (i=0; i<((relocation->SizeOfBlock-IMAGE_SIZEOF_BASE_RELOCATION) / 2); i++, relInfo++) { + // the upper 4 bits define the type of relocation + int type = *relInfo >> 12; + // the lower 12 bits define the offset + int offset = *relInfo & 0xfff; + + switch (type) + { + case IMAGE_REL_BASED_ABSOLUTE: + // skip relocation + break; + + case IMAGE_REL_BASED_HIGHLOW: + // change complete 32 bit address + { + DWORD *patchAddrHL = (DWORD *) (dest + offset); + *patchAddrHL += (DWORD) delta; + } + break; + +#ifdef _WIN64 + case IMAGE_REL_BASED_DIR64: + { + ULONGLONG *patchAddr64 = (ULONGLONG *) (dest + offset); + *patchAddr64 += (ULONGLONG) delta; + } + break; +#endif + + default: + //printf("Unknown relocation: %d\n", type); + break; + } + } + + // advance to next relocation block + relocation = (PIMAGE_BASE_RELOCATION) OffsetPointer(relocation, relocation->SizeOfBlock); + } + return TRUE; +} + +static BOOL +BuildImportTable(PMEMORYMODULE module) +{ + unsigned char *codeBase = module->codeBase; + PIMAGE_IMPORT_DESCRIPTOR importDesc; + BOOL result = TRUE; + + PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY(module, IMAGE_DIRECTORY_ENTRY_IMPORT); + if (directory->Size == 0) { + return TRUE; + } + + importDesc = (PIMAGE_IMPORT_DESCRIPTOR) (codeBase + directory->VirtualAddress); + for (; !IsBadReadPtr(importDesc, sizeof(IMAGE_IMPORT_DESCRIPTOR)) && importDesc->Name; importDesc++) { + uintptr_t *thunkRef; + FARPROC *funcRef; + HCUSTOMMODULE *tmp; + HCUSTOMMODULE handle = module->loadLibrary((LPCSTR) (codeBase + importDesc->Name), module->userdata); + if (handle == NULL) { + SetLastError(ERROR_MOD_NOT_FOUND); + result = FALSE; + break; + } + + tmp = (HCUSTOMMODULE *) realloc(module->modules, (module->numModules+1)*(sizeof(HCUSTOMMODULE))); + if (tmp == NULL) { + module->freeLibrary(handle, module->userdata); + SetLastError(ERROR_OUTOFMEMORY); + result = FALSE; + break; + } + module->modules = tmp; + + module->modules[module->numModules++] = handle; + if (importDesc->OriginalFirstThunk) { + thunkRef = (uintptr_t *) (codeBase + importDesc->OriginalFirstThunk); + funcRef = (FARPROC *) (codeBase + importDesc->FirstThunk); + } else { + // no hint table + thunkRef = (uintptr_t *) (codeBase + importDesc->FirstThunk); + funcRef = (FARPROC *) (codeBase + importDesc->FirstThunk); + } + for (; *thunkRef; thunkRef++, funcRef++) { + if (IMAGE_SNAP_BY_ORDINAL(*thunkRef)) { + *funcRef = module->getProcAddress(handle, (LPCSTR)IMAGE_ORDINAL(*thunkRef), module->userdata); + } else { + PIMAGE_IMPORT_BY_NAME thunkData = (PIMAGE_IMPORT_BY_NAME) (codeBase + (*thunkRef)); + *funcRef = module->getProcAddress(handle, (LPCSTR)&thunkData->Name, module->userdata); + } + if (*funcRef == 0) { + result = FALSE; + break; + } + } + + if (!result) { + module->freeLibrary(handle, module->userdata); + SetLastError(ERROR_PROC_NOT_FOUND); + break; + } + } + + return result; +} + +LPVOID MemoryDefaultAlloc(LPVOID address, SIZE_T size, DWORD allocationType, DWORD protect, void* userdata) +{ + UNREFERENCED_PARAMETER(userdata); + return VirtualAlloc(address, size, allocationType, protect); +} + +BOOL MemoryDefaultFree(LPVOID lpAddress, SIZE_T dwSize, DWORD dwFreeType, void* userdata) +{ + UNREFERENCED_PARAMETER(userdata); + return VirtualFree(lpAddress, dwSize, dwFreeType); +} + +HCUSTOMMODULE MemoryDefaultLoadLibrary(LPCSTR filename, void *userdata) +{ + HMODULE result; + UNREFERENCED_PARAMETER(userdata); + result = LoadLibraryA(filename); + if (result == NULL) { + return NULL; + } + + return (HCUSTOMMODULE) result; +} + +FARPROC MemoryDefaultGetProcAddress(HCUSTOMMODULE module, LPCSTR name, void *userdata) +{ + UNREFERENCED_PARAMETER(userdata); + return (FARPROC) GetProcAddress((HMODULE) module, name); +} + +void MemoryDefaultFreeLibrary(HCUSTOMMODULE module, void *userdata) +{ + UNREFERENCED_PARAMETER(userdata); + FreeLibrary((HMODULE) module); +} + +HMEMORYMODULE MemoryLoadLibrary(const void *data, size_t size) +{ + return MemoryLoadLibraryEx(data, size, MemoryDefaultAlloc, MemoryDefaultFree, MemoryDefaultLoadLibrary, MemoryDefaultGetProcAddress, MemoryDefaultFreeLibrary, NULL); +} + +HMEMORYMODULE MemoryLoadLibraryEx(const void *data, size_t size, + CustomAllocFunc allocMemory, + CustomFreeFunc freeMemory, + CustomLoadLibraryFunc loadLibrary, + CustomGetProcAddressFunc getProcAddress, + CustomFreeLibraryFunc freeLibrary, + void *userdata) +{ + PMEMORYMODULE result = NULL; + PIMAGE_DOS_HEADER dos_header; + PIMAGE_NT_HEADERS old_header; + unsigned char *code, *headers; + ptrdiff_t locationDelta; + SYSTEM_INFO sysInfo; + PIMAGE_SECTION_HEADER section; + DWORD i; + size_t optionalSectionSize; + size_t lastSectionEnd = 0; + size_t alignedImageSize; +#ifdef _WIN64 + POINTER_LIST *blockedMemory = NULL; +#endif + + if (!CheckSize(size, sizeof(IMAGE_DOS_HEADER))) { + return NULL; + } + dos_header = (PIMAGE_DOS_HEADER)data; + if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) { + SetLastError(ERROR_BAD_EXE_FORMAT); + return NULL; + } + + if (!CheckSize(size, dos_header->e_lfanew + sizeof(IMAGE_NT_HEADERS))) { + return NULL; + } + old_header = (PIMAGE_NT_HEADERS)&((const unsigned char *)(data))[dos_header->e_lfanew]; + if (old_header->Signature != IMAGE_NT_SIGNATURE) { + SetLastError(ERROR_BAD_EXE_FORMAT); + return NULL; + } + + if (old_header->FileHeader.Machine != HOST_MACHINE) { + SetLastError(ERROR_BAD_EXE_FORMAT); + return NULL; + } + + if (old_header->OptionalHeader.SectionAlignment & 1) { + // Only support section alignments that are a multiple of 2 + SetLastError(ERROR_BAD_EXE_FORMAT); + return NULL; + } + + section = IMAGE_FIRST_SECTION(old_header); + optionalSectionSize = old_header->OptionalHeader.SectionAlignment; + for (i=0; iFileHeader.NumberOfSections; i++, section++) { + size_t endOfSection; + if (section->SizeOfRawData == 0) { + // Section without data in the DLL + endOfSection = section->VirtualAddress + optionalSectionSize; + } else { + endOfSection = section->VirtualAddress + section->SizeOfRawData; + } + + if (endOfSection > lastSectionEnd) { + lastSectionEnd = endOfSection; + } + } + + GetNativeSystemInfo(&sysInfo); + alignedImageSize = AlignValueUp(old_header->OptionalHeader.SizeOfImage, sysInfo.dwPageSize); + if (alignedImageSize != AlignValueUp(lastSectionEnd, sysInfo.dwPageSize)) { + SetLastError(ERROR_BAD_EXE_FORMAT); + return NULL; + } + + // reserve memory for image of library + // XXX: is it correct to commit the complete memory region at once? + // calling DllEntry raises an exception if we don't... + code = (unsigned char *)allocMemory((LPVOID)(old_header->OptionalHeader.ImageBase), + alignedImageSize, + MEM_RESERVE | MEM_COMMIT, + PAGE_READWRITE, + userdata); + + if (code == NULL) { + // try to allocate memory at arbitrary position + code = (unsigned char *)allocMemory(NULL, + alignedImageSize, + MEM_RESERVE | MEM_COMMIT, + PAGE_READWRITE, + userdata); + if (code == NULL) { + SetLastError(ERROR_OUTOFMEMORY); + return NULL; + } + } + +#ifdef _WIN64 + // Memory block may not span 4 GB boundaries. + while ((((uintptr_t) code) >> 32) < (((uintptr_t) (code + alignedImageSize)) >> 32)) { + POINTER_LIST *node = (POINTER_LIST*) malloc(sizeof(POINTER_LIST)); + if (!node) { + freeMemory(code, 0, MEM_RELEASE, userdata); + FreePointerList(blockedMemory, freeMemory, userdata); + SetLastError(ERROR_OUTOFMEMORY); + return NULL; + } + + node->next = blockedMemory; + node->address = code; + blockedMemory = node; + + code = (unsigned char *)allocMemory(NULL, + alignedImageSize, + MEM_RESERVE | MEM_COMMIT, + PAGE_READWRITE, + userdata); + if (code == NULL) { + FreePointerList(blockedMemory, freeMemory, userdata); + SetLastError(ERROR_OUTOFMEMORY); + return NULL; + } + } +#endif + + result = (PMEMORYMODULE)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(MEMORYMODULE)); + if (result == NULL) { + freeMemory(code, 0, MEM_RELEASE, userdata); +#ifdef _WIN64 + FreePointerList(blockedMemory, freeMemory, userdata); +#endif + SetLastError(ERROR_OUTOFMEMORY); + return NULL; + } + + result->codeBase = code; + result->isDLL = (old_header->FileHeader.Characteristics & IMAGE_FILE_DLL) != 0; + result->alloc = allocMemory; + result->free = freeMemory; + result->loadLibrary = loadLibrary; + result->getProcAddress = getProcAddress; + result->freeLibrary = freeLibrary; + result->userdata = userdata; + result->pageSize = sysInfo.dwPageSize; +#ifdef _WIN64 + result->blockedMemory = blockedMemory; +#endif + + if (!CheckSize(size, old_header->OptionalHeader.SizeOfHeaders)) { + goto error; + } + + // commit memory for headers + headers = (unsigned char *)allocMemory(code, + old_header->OptionalHeader.SizeOfHeaders, + MEM_COMMIT, + PAGE_READWRITE, + userdata); + + // copy PE header to code + memcpy(headers, dos_header, old_header->OptionalHeader.SizeOfHeaders); + result->headers = (PIMAGE_NT_HEADERS)&((const unsigned char *)(headers))[dos_header->e_lfanew]; + + // update position + result->headers->OptionalHeader.ImageBase = (uintptr_t)code; + + // copy sections from DLL file block to new memory location + if (!CopySections((const unsigned char *) data, size, old_header, result)) { + goto error; + } + + // adjust base address of imported data + locationDelta = (ptrdiff_t)(result->headers->OptionalHeader.ImageBase - old_header->OptionalHeader.ImageBase); + if (locationDelta != 0) { + result->isRelocated = PerformBaseRelocation(result, locationDelta); + } else { + result->isRelocated = TRUE; + } + + // load required dlls and adjust function table of imports + if (!BuildImportTable(result)) { + goto error; + } + + // mark memory pages depending on section headers and release + // sections that are marked as "discardable" + if (!FinalizeSections(result)) { + goto error; + } + + // TLS callbacks are executed BEFORE the main loading + if (!ExecuteTLS(result)) { + goto error; + } + + // get entry point of loaded library + if (result->headers->OptionalHeader.AddressOfEntryPoint != 0) { + if (result->isDLL) { + DllEntryProc DllEntry = (DllEntryProc)(LPVOID)(code + result->headers->OptionalHeader.AddressOfEntryPoint); + // notify library about attaching to process + BOOL successfull = (*DllEntry)((HINSTANCE)code, DLL_PROCESS_ATTACH, 0); + if (!successfull) { + // RACKET: try again with NULL as the instanec handle. That allows + // libiconv to work, since it passes the handle to GetModuleName + successfull = (*DllEntry)((HINSTANCE)NULL, DLL_PROCESS_ATTACH, 0); + if (!successfull) { + SetLastError(ERROR_DLL_INIT_FAILED); + goto error; + } + } + result->initialized = TRUE; + } else { + result->exeEntry = (ExeEntryProc)(LPVOID)(code + result->headers->OptionalHeader.AddressOfEntryPoint); + } + } else { + result->exeEntry = NULL; + } + + return (HMEMORYMODULE)result; + +error: + // cleanup + MemoryFreeLibrary(result); + return NULL; +} + +static int _compare(const void *a, const void *b) +{ + const struct ExportNameEntry *p1 = (const struct ExportNameEntry*) a; + const struct ExportNameEntry *p2 = (const struct ExportNameEntry*) b; + return strcmp(p1->name, p2->name); +} + +static int _find(const void *a, const void *b) +{ + LPCSTR *name = (LPCSTR *) a; + const struct ExportNameEntry *p = (const struct ExportNameEntry*) b; + return strcmp(*name, p->name); +} + +FARPROC MemoryGetProcAddress(HMEMORYMODULE mod, LPCSTR name) +{ + PMEMORYMODULE module = (PMEMORYMODULE)mod; + unsigned char *codeBase = module->codeBase; + DWORD idx = 0; + PIMAGE_EXPORT_DIRECTORY exports; + PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY(module, IMAGE_DIRECTORY_ENTRY_EXPORT); + if (directory->Size == 0) { + // no export table found + SetLastError(ERROR_PROC_NOT_FOUND); + return NULL; + } + + exports = (PIMAGE_EXPORT_DIRECTORY) (codeBase + directory->VirtualAddress); + if (exports->NumberOfNames == 0 || exports->NumberOfFunctions == 0) { + // DLL doesn't export anything + SetLastError(ERROR_PROC_NOT_FOUND); + return NULL; + } + + if (HIWORD(name) == 0) { + // load function by ordinal value + if (LOWORD(name) < exports->Base) { + SetLastError(ERROR_PROC_NOT_FOUND); + return NULL; + } + + idx = LOWORD(name) - exports->Base; + } else if (!exports->NumberOfNames) { + SetLastError(ERROR_PROC_NOT_FOUND); + return NULL; + } else { + const struct ExportNameEntry *found; + + // Lazily build name table and sort it by names + if (!module->nameExportsTable) { + DWORD i; + DWORD *nameRef = (DWORD *) (codeBase + exports->AddressOfNames); + WORD *ordinal = (WORD *) (codeBase + exports->AddressOfNameOrdinals); + struct ExportNameEntry *entry = (struct ExportNameEntry*) malloc(exports->NumberOfNames * sizeof(struct ExportNameEntry)); + module->nameExportsTable = entry; + if (!entry) { + SetLastError(ERROR_OUTOFMEMORY); + return NULL; + } + for (i=0; iNumberOfNames; i++, nameRef++, ordinal++, entry++) { + entry->name = (const char *) (codeBase + (*nameRef)); + entry->idx = *ordinal; + } + qsort(module->nameExportsTable, + exports->NumberOfNames, + sizeof(struct ExportNameEntry), _compare); + } + + // search function name in list of exported names with binary search + found = (const struct ExportNameEntry*) bsearch(&name, + module->nameExportsTable, + exports->NumberOfNames, + sizeof(struct ExportNameEntry), _find); + if (!found) { + // exported symbol not found + SetLastError(ERROR_PROC_NOT_FOUND); + return NULL; + } + + idx = found->idx; + } + + if (idx > exports->NumberOfFunctions) { + // name <-> ordinal number don't match + SetLastError(ERROR_PROC_NOT_FOUND); + return NULL; + } + + // AddressOfFunctions contains the RVAs to the "real" functions + return (FARPROC)(LPVOID)(codeBase + (*(DWORD *) (codeBase + exports->AddressOfFunctions + (idx*4)))); +} + +void MemoryFreeLibrary(HMEMORYMODULE mod) +{ + PMEMORYMODULE module = (PMEMORYMODULE)mod; + + if (module == NULL) { + return; + } + if (module->initialized) { + // notify library about detaching from process + DllEntryProc DllEntry = (DllEntryProc)(LPVOID)(module->codeBase + module->headers->OptionalHeader.AddressOfEntryPoint); + (*DllEntry)((HINSTANCE)module->codeBase, DLL_PROCESS_DETACH, 0); + } + + free(module->nameExportsTable); + if (module->modules != NULL) { + // free previously opened libraries + int i; + for (i=0; inumModules; i++) { + if (module->modules[i] != NULL) { + module->freeLibrary(module->modules[i], module->userdata); + } + } + + free(module->modules); + } + + if (module->codeBase != NULL) { + // release memory of library + module->free(module->codeBase, 0, MEM_RELEASE, module->userdata); + } + +#ifdef _WIN64 + FreePointerList(module->blockedMemory, module->free, module->userdata); +#endif + HeapFree(GetProcessHeap(), 0, module); +} + +int MemoryCallEntryPoint(HMEMORYMODULE mod) +{ + PMEMORYMODULE module = (PMEMORYMODULE)mod; + + if (module == NULL || module->isDLL || module->exeEntry == NULL || !module->isRelocated) { + return -1; + } + + return module->exeEntry(); +} + +#define DEFAULT_LANGUAGE MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL) + +HMEMORYRSRC MemoryFindResource(HMEMORYMODULE module, LPCTSTR name, LPCTSTR type) +{ + return MemoryFindResourceEx(module, name, type, DEFAULT_LANGUAGE); +} + +static PIMAGE_RESOURCE_DIRECTORY_ENTRY _MemorySearchResourceEntry( + void *root, + PIMAGE_RESOURCE_DIRECTORY resources, + LPCTSTR key) +{ + PIMAGE_RESOURCE_DIRECTORY_ENTRY entries = (PIMAGE_RESOURCE_DIRECTORY_ENTRY) (resources + 1); + PIMAGE_RESOURCE_DIRECTORY_ENTRY result = NULL; + DWORD start; + DWORD end; + DWORD middle; + + if (!IS_INTRESOURCE(key) && key[0] == TEXT('#')) { + // special case: resource id given as string + TCHAR *endpos = NULL; + long int tmpkey = (WORD) _tcstol((TCHAR *) &key[1], &endpos, 10); + if (tmpkey <= 0xffff && lstrlen(endpos) == 0) { + key = MAKEINTRESOURCE(tmpkey); + } + } + + // entries are stored as ordered list of named entries, + // followed by an ordered list of id entries - we can do + // a binary search to find faster... + if (IS_INTRESOURCE(key)) { + WORD check = (WORD) (uintptr_t) key; + start = resources->NumberOfNamedEntries; + end = start + resources->NumberOfIdEntries; + + while (end > start) { + WORD entryName; + middle = (start + end) >> 1; + entryName = (WORD) entries[middle].Name; + if (check < entryName) { + end = (end != middle ? middle : middle-1); + } else if (check > entryName) { + start = (start != middle ? middle : middle+1); + } else { + result = &entries[middle]; + break; + } + } + } else { + LPCWSTR searchKey; + size_t searchKeyLen = _tcslen(key); +#if defined(UNICODE) + searchKey = key; +#else + // Resource names are always stored using 16bit characters, need to + // convert string we search for. +#define MAX_LOCAL_KEY_LENGTH 2048 + // In most cases resource names are short, so optimize for that by + // using a pre-allocated array. + wchar_t _searchKeySpace[MAX_LOCAL_KEY_LENGTH+1]; + LPWSTR _searchKey; + if (searchKeyLen > MAX_LOCAL_KEY_LENGTH) { + size_t _searchKeySize = (searchKeyLen + 1) * sizeof(wchar_t); + _searchKey = (LPWSTR) malloc(_searchKeySize); + if (_searchKey == NULL) { + SetLastError(ERROR_OUTOFMEMORY); + return NULL; + } + } else { + _searchKey = &_searchKeySpace[0]; + } + + mbstowcs(_searchKey, key, searchKeyLen); + _searchKey[searchKeyLen] = 0; + searchKey = _searchKey; +#endif + start = 0; + end = resources->NumberOfNamedEntries; + while (end > start) { + int cmp; + PIMAGE_RESOURCE_DIR_STRING_U resourceString; + middle = (start + end) >> 1; + resourceString = (PIMAGE_RESOURCE_DIR_STRING_U) OffsetPointer(root, entries[middle].Name & 0x7FFFFFFF); + cmp = _wcsnicmp(searchKey, resourceString->NameString, resourceString->Length); + if (cmp == 0) { + // Handle partial match + if (searchKeyLen > resourceString->Length) { + cmp = 1; + } else if (searchKeyLen < resourceString->Length) { + cmp = -1; + } + } + if (cmp < 0) { + end = (middle != end ? middle : middle-1); + } else if (cmp > 0) { + start = (middle != start ? middle : middle+1); + } else { + result = &entries[middle]; + break; + } + } +#if !defined(UNICODE) + if (searchKeyLen > MAX_LOCAL_KEY_LENGTH) { + free(_searchKey); + } +#undef MAX_LOCAL_KEY_LENGTH +#endif + } + + return result; +} + +HMEMORYRSRC MemoryFindResourceEx(HMEMORYMODULE module, LPCTSTR name, LPCTSTR type, WORD language) +{ + unsigned char *codeBase = ((PMEMORYMODULE) module)->codeBase; + PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY((PMEMORYMODULE) module, IMAGE_DIRECTORY_ENTRY_RESOURCE); + PIMAGE_RESOURCE_DIRECTORY rootResources; + PIMAGE_RESOURCE_DIRECTORY nameResources; + PIMAGE_RESOURCE_DIRECTORY typeResources; + PIMAGE_RESOURCE_DIRECTORY_ENTRY foundType; + PIMAGE_RESOURCE_DIRECTORY_ENTRY foundName; + PIMAGE_RESOURCE_DIRECTORY_ENTRY foundLanguage; + if (directory->Size == 0) { + // no resource table found + SetLastError(ERROR_RESOURCE_DATA_NOT_FOUND); + return NULL; + } + + if (language == DEFAULT_LANGUAGE) { + // use language from current thread + language = LANGIDFROMLCID(GetThreadLocale()); + } + + // resources are stored as three-level tree + // - first node is the type + // - second node is the name + // - third node is the language + rootResources = (PIMAGE_RESOURCE_DIRECTORY) (codeBase + directory->VirtualAddress); + foundType = _MemorySearchResourceEntry(rootResources, rootResources, type); + if (foundType == NULL) { + SetLastError(ERROR_RESOURCE_TYPE_NOT_FOUND); + return NULL; + } + + typeResources = (PIMAGE_RESOURCE_DIRECTORY) (codeBase + directory->VirtualAddress + (foundType->OffsetToData & 0x7fffffff)); + foundName = _MemorySearchResourceEntry(rootResources, typeResources, name); + if (foundName == NULL) { + SetLastError(ERROR_RESOURCE_NAME_NOT_FOUND); + return NULL; + } + + nameResources = (PIMAGE_RESOURCE_DIRECTORY) (codeBase + directory->VirtualAddress + (foundName->OffsetToData & 0x7fffffff)); + foundLanguage = _MemorySearchResourceEntry(rootResources, nameResources, (LPCTSTR) (uintptr_t) language); + if (foundLanguage == NULL) { + // requested language not found, use first available + if (nameResources->NumberOfIdEntries == 0) { + SetLastError(ERROR_RESOURCE_LANG_NOT_FOUND); + return NULL; + } + + foundLanguage = (PIMAGE_RESOURCE_DIRECTORY_ENTRY) (nameResources + 1); + } + + return (codeBase + directory->VirtualAddress + (foundLanguage->OffsetToData & 0x7fffffff)); +} + +DWORD MemorySizeofResource(HMEMORYMODULE module, HMEMORYRSRC resource) +{ + PIMAGE_RESOURCE_DATA_ENTRY entry; + UNREFERENCED_PARAMETER(module); + entry = (PIMAGE_RESOURCE_DATA_ENTRY) resource; + if (entry == NULL) { + return 0; + } + + return entry->Size; +} + +LPVOID MemoryLoadResource(HMEMORYMODULE module, HMEMORYRSRC resource) +{ + unsigned char *codeBase = ((PMEMORYMODULE) module)->codeBase; + PIMAGE_RESOURCE_DATA_ENTRY entry = (PIMAGE_RESOURCE_DATA_ENTRY) resource; + if (entry == NULL) { + return NULL; + } + + return codeBase + entry->OffsetToData; +} + +int +MemoryLoadString(HMEMORYMODULE module, UINT id, LPTSTR buffer, int maxsize) +{ + return MemoryLoadStringEx(module, id, buffer, maxsize, DEFAULT_LANGUAGE); +} + +int +MemoryLoadStringEx(HMEMORYMODULE module, UINT id, LPTSTR buffer, int maxsize, WORD language) +{ + HMEMORYRSRC resource; + PIMAGE_RESOURCE_DIR_STRING_U data; + DWORD size; + if (maxsize == 0) { + return 0; + } + + resource = MemoryFindResourceEx(module, MAKEINTRESOURCE((id >> 4) + 1), RT_STRING, language); + if (resource == NULL) { + buffer[0] = 0; + return 0; + } + + data = (PIMAGE_RESOURCE_DIR_STRING_U) MemoryLoadResource(module, resource); + id = id & 0x0f; + while (id--) { + data = (PIMAGE_RESOURCE_DIR_STRING_U) OffsetPointer(data, (data->Length + 1) * sizeof(WCHAR)); + } + if (data->Length == 0) { + SetLastError(ERROR_RESOURCE_NAME_NOT_FOUND); + buffer[0] = 0; + return 0; + } + + size = data->Length; + if (size >= (DWORD) maxsize) { + size = maxsize; + } else { + buffer[size] = 0; + } +#if defined(UNICODE) + wcsncpy(buffer, data->NameString, size); +#else + wcstombs(buffer, data->NameString, size); +#endif + return size; +} + +#ifdef TESTSUITE +#include + +#ifndef PRIxPTR +#ifdef _WIN64 +#define PRIxPTR "I64x" +#else +#define PRIxPTR "x" +#endif +#endif + +static const uintptr_t AlignValueDownTests[][3] = { + {16, 16, 16}, + {17, 16, 16}, + {32, 16, 32}, + {33, 16, 32}, +#ifdef _WIN64 + {0x12345678abcd1000, 0x1000, 0x12345678abcd1000}, + {0x12345678abcd101f, 0x1000, 0x12345678abcd1000}, +#endif + {0, 0, 0}, +}; + +static const uintptr_t AlignValueUpTests[][3] = { + {16, 16, 16}, + {17, 16, 32}, + {32, 16, 32}, + {33, 16, 48}, +#ifdef _WIN64 + {0x12345678abcd1000, 0x1000, 0x12345678abcd1000}, + {0x12345678abcd101f, 0x1000, 0x12345678abcd2000}, +#endif + {0, 0, 0}, +}; + +BOOL MemoryModuleTestsuite() { + BOOL success = TRUE; + size_t idx; + for (idx = 0; AlignValueDownTests[idx][0]; ++idx) { + const uintptr_t* tests = AlignValueDownTests[idx]; + uintptr_t value = AlignValueDown(tests[0], tests[1]); + if (value != tests[2]) { + printf("AlignValueDown failed for 0x%" PRIxPTR "/0x%" PRIxPTR ": expected 0x%" PRIxPTR ", got 0x%" PRIxPTR "\n", + tests[0], tests[1], tests[2], value); + success = FALSE; + } + } + for (idx = 0; AlignValueDownTests[idx][0]; ++idx) { + const uintptr_t* tests = AlignValueUpTests[idx]; + uintptr_t value = AlignValueUp(tests[0], tests[1]); + if (value != tests[2]) { + printf("AlignValueUp failed for 0x%" PRIxPTR "/0x%" PRIxPTR ": expected 0x%" PRIxPTR ", got 0x%" PRIxPTR "\n", + tests[0], tests[1], tests[2], value); + success = FALSE; + } + } + if (success) { + printf("OK\n"); + } + return success; +} +#endif diff --git a/racket/src/start/MemoryModule.h b/racket/src/start/MemoryModule.h new file mode 100644 index 0000000000..a728f6b141 --- /dev/null +++ b/racket/src/start/MemoryModule.h @@ -0,0 +1,168 @@ +/* + * Memory DLL loading code + * Version 0.0.4 + * + * Copyright (c) 2004-2015 by Joachim Bauch / mail@joachim-bauch.de + * http://www.joachim-bauch.de + * + * The contents of this file are subject to the Mozilla Public License Version + * 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is MemoryModule.h + * + * The Initial Developer of the Original Code is Joachim Bauch. + * + * Portions created by Joachim Bauch are Copyright (C) 2004-2015 + * Joachim Bauch. All Rights Reserved. + * + */ + +#ifndef __MEMORY_MODULE_HEADER +#define __MEMORY_MODULE_HEADER + +#include + +typedef void *HMEMORYMODULE; + +typedef void *HMEMORYRSRC; + +typedef void *HCUSTOMMODULE; + +#ifdef __cplusplus +extern "C" { +#endif + +typedef LPVOID (*CustomAllocFunc)(LPVOID, SIZE_T, DWORD, DWORD, void*); +typedef BOOL (*CustomFreeFunc)(LPVOID, SIZE_T, DWORD, void*); +typedef HCUSTOMMODULE (*CustomLoadLibraryFunc)(LPCSTR, void *); +typedef FARPROC (*CustomGetProcAddressFunc)(HCUSTOMMODULE, LPCSTR, void *); +typedef void (*CustomFreeLibraryFunc)(HCUSTOMMODULE, void *); + +/** + * Load EXE/DLL from memory location with the given size. + * + * All dependencies are resolved using default LoadLibrary/GetProcAddress + * calls through the Windows API. + */ +HMEMORYMODULE MemoryLoadLibrary(const void *, size_t); + +/** + * Load EXE/DLL from memory location with the given size using custom dependency + * resolvers. + * + * Dependencies will be resolved using passed callback methods. + */ +HMEMORYMODULE MemoryLoadLibraryEx(const void *, size_t, + CustomAllocFunc, + CustomFreeFunc, + CustomLoadLibraryFunc, + CustomGetProcAddressFunc, + CustomFreeLibraryFunc, + void *); + +/** + * Get address of exported method. Supports loading both by name and by + * ordinal value. + */ +FARPROC MemoryGetProcAddress(HMEMORYMODULE, LPCSTR); + +/** + * Free previously loaded EXE/DLL. + */ +void MemoryFreeLibrary(HMEMORYMODULE); + +/** + * Execute entry point (EXE only). The entry point can only be executed + * if the EXE has been loaded to the correct base address or it could + * be relocated (i.e. relocation information have not been stripped by + * the linker). + * + * Important: calling this function will not return, i.e. once the loaded + * EXE finished running, the process will terminate. + * + * Returns a negative value if the entry point could not be executed. + */ +int MemoryCallEntryPoint(HMEMORYMODULE); + +/** + * Find the location of a resource with the specified type and name. + */ +HMEMORYRSRC MemoryFindResource(HMEMORYMODULE, LPCTSTR, LPCTSTR); + +/** + * Find the location of a resource with the specified type, name and language. + */ +HMEMORYRSRC MemoryFindResourceEx(HMEMORYMODULE, LPCTSTR, LPCTSTR, WORD); + +/** + * Get the size of the resource in bytes. + */ +DWORD MemorySizeofResource(HMEMORYMODULE, HMEMORYRSRC); + +/** + * Get a pointer to the contents of the resource. + */ +LPVOID MemoryLoadResource(HMEMORYMODULE, HMEMORYRSRC); + +/** + * Load a string resource. + */ +int MemoryLoadString(HMEMORYMODULE, UINT, LPTSTR, int); + +/** + * Load a string resource with a given language. + */ +int MemoryLoadStringEx(HMEMORYMODULE, UINT, LPTSTR, int, WORD); + +/** +* Default implementation of CustomAllocFunc that calls VirtualAlloc +* internally to allocate memory for a library +* +* This is the default as used by MemoryLoadLibrary. +*/ +LPVOID MemoryDefaultAlloc(LPVOID, SIZE_T, DWORD, DWORD, void *); + +/** +* Default implementation of CustomFreeFunc that calls VirtualFree +* internally to free the memory used by a library +* +* This is the default as used by MemoryLoadLibrary. +*/ +BOOL MemoryDefaultFree(LPVOID, SIZE_T, DWORD, void *); + +/** + * Default implementation of CustomLoadLibraryFunc that calls LoadLibraryA + * internally to load an additional libary. + * + * This is the default as used by MemoryLoadLibrary. + */ +HCUSTOMMODULE MemoryDefaultLoadLibrary(LPCSTR, void *); + +/** + * Default implementation of CustomGetProcAddressFunc that calls GetProcAddress + * internally to get the address of an exported function. + * + * This is the default as used by MemoryLoadLibrary. + */ +FARPROC MemoryDefaultGetProcAddress(HCUSTOMMODULE, LPCSTR, void *); + +/** + * Default implementation of CustomFreeLibraryFunc that calls FreeLibrary + * internally to release an additional libary. + * + * This is the default as used by MemoryLoadLibrary. + */ +void MemoryDefaultFreeLibrary(HCUSTOMMODULE, void *); + +#ifdef __cplusplus +} +#endif + +#endif // __MEMORY_MODULE_HEADER diff --git a/racket/src/start/config.inc b/racket/src/start/config.inc index d59010dcfb..0a5b631326 100644 --- a/racket/src/start/config.inc +++ b/racket/src/start/config.inc @@ -133,7 +133,7 @@ static long get_segment_offset() #endif #ifdef DOS_FILE_SYSTEM -wchar_t *get_self_executable_path() +wchar_t *get_self_executable_path() XFORM_SKIP_PROC { wchar_t *path; DWORD r, sz = 1024; @@ -152,7 +152,7 @@ wchar_t *get_self_executable_path() return path; } -static DWORD find_by_id(HANDLE fd, DWORD rsrcs, DWORD pos, int id) +static DWORD find_by_id(HANDLE fd, DWORD rsrcs, DWORD pos, int id) XFORM_SKIP_PROC { DWORD got, val; WORD name_count, id_count; @@ -175,9 +175,9 @@ static DWORD find_by_id(HANDLE fd, DWORD rsrcs, DWORD pos, int id) return 0; } -static long get_segment_offset() +static long find_resource_offset(int id) XFORM_SKIP_PROC { - /* Find the resource of type 257 */ + /* Find the resource of type `id` */ wchar_t *path; HANDLE fd; @@ -221,7 +221,7 @@ static long get_segment_offset() SetFilePointer(fd, rsrcs, 0, FILE_BEGIN); /* We're at the resource table; step through 3 layers */ - pos = find_by_id(fd, rsrcs, rsrcs, 257); + pos = find_by_id(fd, rsrcs, rsrcs, id); if (pos) { pos = find_by_id(fd, rsrcs, pos, 1); if (pos) { @@ -250,6 +250,12 @@ static long get_segment_offset() return 0; } } + +static long get_segment_offset() XFORM_SKIP_PROC +{ + return find_resource_offset(257); +} + #endif static void extract_built_in_arguments(char **_prog, char **_sprog, int *_argc, char ***_argv) diff --git a/racket/src/start/embedded_dll.inc b/racket/src/start/embedded_dll.inc new file mode 100644 index 0000000000..a68c576efc --- /dev/null +++ b/racket/src/start/embedded_dll.inc @@ -0,0 +1,243 @@ +#include "MemoryModule.h" + +#define noisy_embedded 0 + +typedef struct embedded_dll_entry_t { + char *name; + long pos; + HMEMORYMODULE loaded_h; +} embedded_dll_entry_t; + +static embedded_dll_entry_t *embedded_dlls; + +extern void *__pfnDliNotifyHook2; +static FARPROC WINAPI delayHook(unsigned dliNotify, void *pdli); +static HCUSTOMMODULE LoadLibraryHook(LPCSTR, void *); +static FARPROC GetProcAddressHook(HCUSTOMMODULE, LPCSTR, void *); + +static HANDLE open_self() +{ + wchar_t *path; + HANDLE fd; + + path = get_self_executable_path(); + + fd = CreateFileW(path, GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + 0, + NULL); + + free(path); + + return fd; +} + +static void parse_embedded_dlls() +{ + long rsrc_pos; + + rsrc_pos = find_resource_offset(258); + if (rsrc_pos) { + HANDLE fd = open_self(); + + if (fd != INVALID_HANDLE_VALUE) { + long pos; + DWORD count, got, i; + short name_len; + char *name; + + SetFilePointer(fd, rsrc_pos, 0, FILE_BEGIN); + ReadFile(fd, &count, sizeof(DWORD), &got, NULL); + + embedded_dlls = malloc(sizeof(embedded_dll_entry_t) * (count + 1)); + for (i = 0; i < count; i++) { + ReadFile(fd, &name_len, sizeof(short), &got, NULL); + name = malloc(name_len + 1); + ReadFile(fd, name, name_len, &got, NULL); + name[name_len] = 0; + embedded_dlls[i].name = name; + if (noisy_embedded) + fprintf(stderr, "embedded %s\n", name); + } + embedded_dlls[count].name = NULL; + + for (i = 0; i < count+1; i++) { + ReadFile(fd, &pos, sizeof(pos), &got, NULL); + embedded_dlls[i].pos = pos + rsrc_pos; + embedded_dlls[i].loaded_h = NULL; + } + + CloseHandle(fd); + + __pfnDliNotifyHook2 = delayHook; + } + } +} + +static void *in_memory_open(const char *name, int as_global) +{ + int i; + + for (i = 0; embedded_dlls[i].name; i++) { + if (!_stricmp(embedded_dlls[i].name, name)) { + HMEMORYMODULE loaded_h = (void *)embedded_dlls[i].loaded_h; + if (!loaded_h) { + HANDLE fd = open_self(); + + if (fd != INVALID_HANDLE_VALUE) { + void *p; + DWORD got; + long len = embedded_dlls[i+1].pos - embedded_dlls[i].pos; + + SetFilePointer(fd, embedded_dlls[i].pos, 0, FILE_BEGIN); + p = malloc(len); + ReadFile(fd, p, len, &got, NULL); + CloseHandle(fd); + + if (got != len) + fprintf(stderr, "partial load %d vs %ld\n", got, len); + + loaded_h = MemoryLoadLibraryEx(p, len, + MemoryDefaultAlloc, MemoryDefaultFree, + LoadLibraryHook, GetProcAddressHook, + MemoryDefaultFreeLibrary, NULL); + if (noisy_embedded) { + if (!loaded_h) { + fprintf(stderr, "failed %s %d\n", name, GetLastError()); + } else + fprintf(stderr, "ok %s\n", name); + } + + free(p); + + embedded_dlls[i].loaded_h = loaded_h; + } + } + return (void *)loaded_h; + } + } + + return NULL; +} + +static void *in_memory_find_object(void *h, const char *name) +{ + if (h) + return MemoryGetProcAddress((HMEMORYMODULE)h, name); + else { + /* Search all loaded DLLs */ + int i; + for (i = 0; embedded_dlls[i].name; i++) { + if (embedded_dlls[i].loaded_h) { + void *v = MemoryGetProcAddress((HMEMORYMODULE)embedded_dlls[i].loaded_h, name); + if (v) + return v; + } + } + return NULL; + } +} + +static void register_embedded_dll_hooks() +{ + if (embedded_dlls) { + scheme_set_dll_procs(in_memory_open, in_memory_find_object); + } +} + +/**************************************************************/ + +typedef struct custom_module_t { + int hooked; + void *h; +} custom_module_t; + +static HCUSTOMMODULE LoadLibraryHook(LPCSTR name, void *data) +{ + void *h; + int hooked = 1; + custom_module_t *m; + + h = (HANDLE)in_memory_open(name, 0); + if (h) + hooked = 1; + else { + h = MemoryDefaultLoadLibrary(name, data); + hooked = 0; + } + + if (!h) + return NULL; + + m = malloc(sizeof(custom_module_t)); + m->hooked = hooked; + m->h = h; + + return (HCUSTOMMODULE)m; +} + +static FARPROC GetProcAddressHook(HCUSTOMMODULE _m, LPCSTR name, void *data) +{ + custom_module_t *m = (custom_module_t *)_m; + + if (m->hooked) + return in_memory_find_object(m->h, name); + else + return MemoryDefaultGetProcAddress(m->h, name, data); +} + +/*************************************************************/ + +/* Set a delay-load hook to potentially redirect to an embedded DLL */ + +/* From Microsoft docs: */ + +typedef struct mz_DelayLoadProc { + BOOL fImportByName; + union { + LPCSTR szProcName; + DWORD dwOrdinal; + }; +} mz_DelayLoadProc; + +typedef struct Mz_DelayLoadInfo { + DWORD cb; // size of structure + void *pidd; // raw form of data (everything is there) + FARPROC * ppfn; // points to address of function to load + LPCSTR szDll; // name of dll + mz_DelayLoadProc dlp; // name or ordinal of procedure + HMODULE hmodCur; // the hInstance of the library we have loaded + FARPROC pfnCur; // the actual function that will be called + DWORD dwLastError;// error received (if an error notification) +} mz_DelayLoadInfo; + +# define mz_dliNotePreLoadLibrary 1 +# define mz_dliNotePreGetProcAddress 2 + +FARPROC WINAPI delayHook(unsigned dliNotify, void *_dli) +{ + mz_DelayLoadInfo *dli = (mz_DelayLoadInfo *)_dli; + + switch (dliNotify) { + case mz_dliNotePreLoadLibrary: + return in_memory_open(dli->szDll, 0); + break; + case mz_dliNotePreGetProcAddress: + { + void *h; + h = in_memory_open(dli->szDll, 0); + if (h) { + if (dli->dlp.fImportByName) + return in_memory_find_object(h, dli->dlp.szProcName); + else + return in_memory_find_object(h, (char *)(intptr_t)dli->dlp.dwOrdinal); + } + } + default: + break; + } + + return NULL; +} diff --git a/racket/src/worksp/gc2/make.rkt b/racket/src/worksp/gc2/make.rkt index 4910d04773..fac83168c0 100644 --- a/racket/src/worksp/gc2/make.rkt +++ b/racket/src/worksp/gc2/make.rkt @@ -363,7 +363,8 @@ (link-exe (list "racket.res" - "xsrc/main.obj") + "xsrc/main.obj" + (find-build-file "racket" "MemoryModule.obj")) null exe "") @@ -394,7 +395,8 @@ (link-exe (list "gracket.res" - "xsrc/grmain.obj") + "xsrc/grmain.obj" + (find-build-file "racket" "MemoryModule.obj")) '("advapi32.lib") "../../../lib/GRacket.exe" " /subsystem:windows") diff --git a/racket/src/worksp/gracket/gracket.vcproj b/racket/src/worksp/gracket/gracket.vcproj index 89c1343b8a..0fc7b7ad48 100644 --- a/racket/src/worksp/gracket/gracket.vcproj +++ b/racket/src/worksp/gracket/gracket.vcproj @@ -473,6 +473,10 @@ RelativePath="..\..\gracket\grmain.c" > + + + diff --git a/racket/src/worksp/racket/racket.vcproj b/racket/src/worksp/racket/racket.vcproj index 1311c9cad9..9fdf9fb6fb 100644 --- a/racket/src/worksp/racket/racket.vcproj +++ b/racket/src/worksp/racket/racket.vcproj @@ -466,6 +466,10 @@ RelativePath="..\..\racket\main.c" > + + diff --git a/racket/src/worksp/racket/racket.vcxproj b/racket/src/worksp/racket/racket.vcxproj index cacff47628..5bd92bf958 100644 --- a/racket/src/worksp/racket/racket.vcxproj +++ b/racket/src/worksp/racket/racket.vcxproj @@ -191,6 +191,7 @@ addman.bat +