raco exe: make Windows exes as proper PE32 images

Instead of simply tacking bytecode onto the end of an executable,
generate a proper PE32 image.
This commit is contained in:
Matthew Flatt 2015-08-24 13:29:48 -06:00
parent d16c5c08b6
commit 3d452fdba6
3 changed files with 207 additions and 62 deletions

View File

@ -177,7 +177,8 @@ flags:
embedded in the executable from file position @nonterm{n} to
@nonterm{m} and from @nonterm{m} to @nonterm{p}. (On Mac OS X,
@nonterm{n}, @nonterm{m}, and @nonterm{p} are relative to a
@tt{__PLTSCHEME} segment in the executable.) The first range
@tt{__PLTSCHEME} segment in the executable. On Windows,
they are relative to a resource of type 257 and ID 1.) The first range
is loaded in every new @tech{place}, and any modules declared
in that range are considered predefined in the sense of
@racket[module-predefined?]. This option is normally embedded

View File

@ -17,6 +17,7 @@
"private/mach-o.rkt"
"private/elf.rkt"
"private/windlldir.rkt"
"private/pe-rsrc.rkt"
"private/collects-path.rkt"
"private/configdir.rkt"
"find-exe.rkt")
@ -1582,54 +1583,76 @@
full-cmdline)
(display "\0\0\0\0" out))])
(let-values ([(start decl-end end cmdline-end)
(if (and (eq? (system-type) 'macosx)
(not unix-starter?))
;; For Mach-O, we know how to add a proper segment
(let ([s (open-output-bytes)])
(define decl-len (write-module s))
(let* ([s (get-output-bytes s)]
[cl (let ([o (open-output-bytes)])
;; position is relative to __PLTSCHEME:
(write-cmdline (make-full-cmdline 0 decl-len (bytes-length s)) o)
(get-output-bytes o))])
(let ([start (add-plt-segment
dest-exe
(bytes-append
s
cl))])
(let ([start 0]) ; i.e., relative to __PLTSCHEME
(values start
(+ start decl-len)
(+ start (bytes-length s))
(+ start (bytes-length s) (bytes-length cl)))))))
;; Unix starter: Maybe ELF, in which case we
;; can add a proper section
(let-values ([(s e dl p)
(if unix-starter?
(add-racket-section
orig-exe
dest-exe
(if launcher? #".rackcmdl" #".rackprog")
(lambda (start)
(let ([s (open-output-bytes)])
(define decl-len (write-module s))
(let ([p (file-position s)])
(display (make-starter-cmdline
(make-full-cmdline start
(+ start decl-len)
(+ start p)))
s)
(values (get-output-bytes s) decl-len p)))))
(values #f #f #f #f))])
(if (and s e)
;; ELF succeeded:
(values s (+ s dl) (+ s p) e)
;; Otherwise, just add to the end of the file:
(let ([start (file-size dest-exe)])
(define decl-end
(call-with-output-file* dest-exe write-module
#:exists 'append))
(values start decl-end (file-size dest-exe) #f)))))])
(cond
[(eq? (system-type) 'windows)
;; Add as a resource
(define o (open-output-bytes))
(define decl-len (write-module o))
(define init-len (bytes-length (get-output-bytes o)))
(write-cmdline (make-full-cmdline 0 decl-len init-len) o)
(define bstr (get-output-bytes o))
(define cmdline-len (- (bytes-length bstr) init-len))
(define-values (pe rsrcs) (call-with-input-file*
dest-exe
read-pe+resources))
(define new-rsrcs (resource-set rsrcs
;; Racket's "user-defined" type for excutable
;; plus command line:
257
1
1033 ; U.S. English
bstr))
(update-resources dest-exe pe new-rsrcs)
(values 0 decl-len init-len (+ init-len cmdline-len))]
[(and (eq? (system-type) 'macosx)
(not unix-starter?))
;; For Mach-O, we know how to add a proper segment
(define s (open-output-bytes))
(define decl-len (write-module s))
(let* ([s (get-output-bytes s)]
[cl (let ([o (open-output-bytes)])
;; position is relative to __PLTSCHEME:
(write-cmdline (make-full-cmdline 0 decl-len (bytes-length s)) o)
(get-output-bytes o))])
(let ([start (add-plt-segment
dest-exe
(bytes-append
s
cl))])
(let ([start 0]) ; i.e., relative to __PLTSCHEME
(values start
(+ start decl-len)
(+ start (bytes-length s))
(+ start (bytes-length s) (bytes-length cl))))))]
[else
;; Unix starter: Maybe ELF, in which case we
;; can add a proper section
(define-values (s e dl p)
(if unix-starter?
(add-racket-section
orig-exe
dest-exe
(if launcher? #".rackcmdl" #".rackprog")
(lambda (start)
(let ([s (open-output-bytes)])
(define decl-len (write-module s))
(let ([p (file-position s)])
(display (make-starter-cmdline
(make-full-cmdline start
(+ start decl-len)
(+ start p)))
s)
(values (get-output-bytes s) decl-len p)))))
(values #f #f #f #f)))
(if (and s e)
;; ELF succeeded:
(values s (+ s dl) (+ s p) e)
;; Otherwise, just add to the end of the file:
(let ([start (file-size dest-exe)])
(define decl-end
(call-with-output-file* dest-exe write-module
#:exists 'append))
(values start decl-end (file-size dest-exe) #f)))])])
(when unix-starter?
(adjust-config-dir))
(when verbose?
@ -1729,7 +1752,7 @@
(unless cmdline-done?
(write-cmdline full-cmdline out))
(when long-cmdline?
;; cmdline written at the end;
;; cmdline written at the end, in a resource, etc.;
;; now put forwarding information at the normal cmdline pos
(let ([new-end (or cmdline-end
(file-position out))])

View File

@ -136,6 +136,126 @@ static long get_segment_offset()
}
#endif
#ifdef DOS_FILE_SYSTEM
wchar_t *get_self_executable_path()
{
wchar_t *path;
DWORD r, sz = 1024;
while (1) {
path = (wchar_t *)malloc(sz * sizeof(wchar_t));
r = GetModuleFileNameW(NULL, path, sz);
if ((r == sz)
&& (GetLastError() == ERROR_INSUFFICIENT_BUFFER)) {
free(path);
sz = 2 * sz;
} else
break;
}
return path;
}
static DWORD find_by_id(HANDLE fd, DWORD rsrcs, DWORD pos, int id)
{
DWORD got, val;
WORD name_count, id_count, i;
SetFilePointer(fd, pos + 12, 0, FILE_BEGIN);
ReadFile(fd, &name_count, 2, &got, NULL);
ReadFile(fd, &id_count, 2, &got, NULL);
pos += 16 + (name_count * 8);
while (id_count--) {
ReadFile(fd, &val, 4, &got, NULL);
if (val == id) {
ReadFile(fd, &val, 4, &got, NULL);
return rsrcs + (val & 0x7FFFFFF);
} else {
ReadFile(fd, &val, 4, &got, NULL);
}
}
return 0;
}
static long get_segment_offset()
{
/* Find the resource of type 257 */
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);
if (fd == INVALID_HANDLE_VALUE)
return 0;
else {
DWORD val, got, sec_pos, virtual_addr, rsrcs, pos;
WORD num_sections, head_size;
char name[8];
SetFilePointer(fd, 60, 0, FILE_BEGIN);
ReadFile(fd, &val, 4, &got, NULL);
SetFilePointer(fd, val+4+2, 0, FILE_BEGIN); /* Skip "PE\0\0" tag and machine */
ReadFile(fd, &num_sections, 2, &got, NULL);
SetFilePointer(fd, 12, 0, FILE_CURRENT); /* time stamp + symbol table */
ReadFile(fd, &head_size, 2, &got, NULL);
sec_pos = val+4+20+head_size;
while (num_sections--) {
SetFilePointer(fd, sec_pos, 0, FILE_BEGIN);
ReadFile(fd, &name, 8, &got, NULL);
if ((name[0] == '.')
&& (name[1] == 'r')
&& (name[2] == 's')
&& (name[3] == 'r')
&& (name[4] == 'c')
&& (name[5] == 0)) {
SetFilePointer(fd, 4, 0, FILE_CURRENT); /* skip virtual size */
ReadFile(fd, &virtual_addr, 4, &got, NULL);
SetFilePointer(fd, 4, 0, FILE_CURRENT); /* skip file size */
ReadFile(fd, &rsrcs, 4, &got, NULL);
SetFilePointer(fd, rsrcs, 0, FILE_BEGIN);
/* We're at the resource table; step through 3 layers */
pos = find_by_id(fd, rsrcs, rsrcs, 257);
if (pos) {
pos = find_by_id(fd, rsrcs, pos, 1);
if (pos) {
pos = find_by_id(fd, rsrcs, pos, 1033);
if (pos) {
/* pos is the reource data entry */
SetFilePointer(fd, pos, 0, FILE_BEGIN);
ReadFile(fd, &val, 4, &got, NULL);
pos = val - virtual_addr + rsrcs;
CloseHandle(fd);
return pos;
}
}
}
break;
}
sec_pos += 40;
}
/* something went wrong */
CloseHandle(fd);
return 0;
}
}
#endif
#ifndef DONT_PARSE_COMMAND_LINE
static int is_number_arg(const char *s)
{
@ -159,7 +279,7 @@ static int is_number_arg(const char *s)
return 1;
}
#ifdef OS_X
#if defined(OS_X) || defined(DOS_FILE_SYSTEM)
char *add_to_str(const char *addr, long amt)
{
long addr_v;
@ -175,7 +295,7 @@ static char *make_embedded_load(const char *start, const char *end)
char *s;
int slen, elen;
#ifdef OS_X
#if defined(OS_X) || defined(DOS_FILE_SYSTEM)
{
long fileoff;
fileoff = get_segment_offset();
@ -844,17 +964,16 @@ static int run_from_cmd_line(int argc, char *_argv[],
#ifdef DOS_FILE_SYSTEM
if ((scheme_cmdline_exe_hack[0] == '?')
|| (scheme_cmdline_exe_hack[0] == '*')) {
/* This is how we make launchers in Windows.
The cmdline is appended to the end of the binary.
The long integer at scheme_cmdline_exe_hack[4] says
where the old end was, and scheme_cmdline_exe_hack[8]
says how long the cmdline string is. It might
be relative to the executable. */
wchar_t *path;
/* This is how we make launchers in Windows. The cmdline is
added as a resource of type 257. The long integer at
scheme_cmdline_exe_hack[4] says where the command line starts
with the source, and scheme_cmdline_exe_hack[8] says how long
the cmdline string is. It might be relative to the
executable. */
HANDLE fd;
wchar_t *path;
path = (wchar_t *)malloc(1024 * sizeof(wchar_t));
GetModuleFileNameW(NULL, path, 1024);
path = get_self_executable_path();
fd = CreateFileW(path, GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
@ -868,6 +987,7 @@ static int run_from_cmd_line(int argc, char *_argv[],
DWORD got;
start = *(long *)&scheme_cmdline_exe_hack[4];
len = *(long *)&scheme_cmdline_exe_hack[8];
start += get_segment_offset();
p = (unsigned char *)malloc(len);
SetFilePointer(fd, start, 0, FILE_BEGIN);
ReadFile(fd, p, len, &got, NULL);
@ -916,9 +1036,10 @@ static int run_from_cmd_line(int argc, char *_argv[],
+ 4);
}
}
free(path);
}
#endif
#ifdef OS_X
#if defined(OS_X)
if (scheme_cmdline_exe_hack[0] == '?') {
long fileoff, cmdoff, cmdlen;
int fd;