From dc8b7cc2e8e81c5bebea180ce0cc094021cec734 Mon Sep 17 00:00:00 2001 From: rtoy <rtoy> Date: Fri, 30 Jul 2010 22:51:58 +0000 Subject: [PATCH] First cut at executable images on Darwin/x86. The resulting executable appears to work! bootfiles/20a/boot-2010-07-1.lisp: o Add :executable for darwin/x86. code/bsd-os.lisp: o Make :elf and :mach-o runtime features too. o Register :executable of :executable is defined. lisp/Config.x86_darwin: o Need mach-o.c o Add exec-final.c lisp/lisp.c: o Don't include elf.h if we're on Darwin since Darwin uses Mach-O, not ELF. o For Darwin, we get the initial function address from initial_function_addr, not from &initial_function_addr, like for Linux. lisp/save.c o Don't include libgen.h and elf.h on Darwin. tools/linker-x86.sh: o Update to support Darwin. tools/make-main-dist.sh: o Add support for executables on Darwin. lisp/mach-o.c: o Initial support for writing Mach-O files for the Lisp spaces. o Initial support for reading a Mach-O executable to find and map the Lisp spaces. --- bootfiles/20a/boot-2010-07-1.lisp | 2 +- code/bsd-os.lisp | 8 +- lisp/Config.x86_darwin | 5 +- lisp/lisp.c | 10 +- lisp/mach-o.c | 393 ++++++++++++++++++++++++++++++ lisp/save.c | 4 +- tools/linker-x86.sh | 46 +++- tools/make-main-dist.sh | 32 ++- 8 files changed, 468 insertions(+), 32 deletions(-) create mode 100644 lisp/mach-o.c diff --git a/bootfiles/20a/boot-2010-07-1.lisp b/bootfiles/20a/boot-2010-07-1.lisp index ce06e1ee8..ad368dae3 100644 --- a/bootfiles/20a/boot-2010-07-1.lisp +++ b/bootfiles/20a/boot-2010-07-1.lisp @@ -34,5 +34,5 @@ ) ;; Executable feature works on sparc! -#+sparc +#+(or sparc (and x86 darwin)) (pushnew :executable *features*) diff --git a/code/bsd-os.lisp b/code/bsd-os.lisp index bd06ad4ea..6eb8f5a18 100644 --- a/code/bsd-os.lisp +++ b/code/bsd-os.lisp @@ -5,7 +5,7 @@ ;;; Carnegie Mellon University, and has been placed in the public domain. ;;; (ext:file-comment - "$Header: /Volumes/share2/src/cmucl/cvs2git/cvsroot/src/code/bsd-os.lisp,v 1.16 2010/04/20 17:57:43 rtoy Exp $") + "$Header: /Volumes/share2/src/cmucl/cvs2git/cvsroot/src/code/bsd-os.lisp,v 1.17 2010/07/30 22:51:58 rtoy Rel $") ;;; ;;; ********************************************************************** ;;; @@ -35,11 +35,11 @@ #-(or freebsd NetBSD OpenBSD Darwin) :bsd) #+elf -(register-lisp-feature :elf) +(register-lisp-runtime-feature :elf) #+mach-o -(register-lisp-feature :mach-o) +(register-lisp-runtime-feature :mach-o) -#+freebsd +#+executable (register-lisp-runtime-feature :executable) (setq *software-type* #+OpenBSD "OpenBSD" diff --git a/lisp/Config.x86_darwin b/lisp/Config.x86_darwin index 154065c17..e4c95fd28 100644 --- a/lisp/Config.x86_darwin +++ b/lisp/Config.x86_darwin @@ -12,7 +12,10 @@ ASFLAGS += -g3 $(MIN_VER) UNDEFSYMPATTERN = -Xlinker -u -Xlinker & -OS_SRC += Darwin-os.c +OS_SRC += Darwin-os.c mach-o.c OS_LINK_FLAGS = $(MIN_VER) OS_LIBS = +EXEC_FINAL_OBJ = exec-final.o + + diff --git a/lisp/lisp.c b/lisp/lisp.c index 15e9dc0f6..ef1174f68 100644 --- a/lisp/lisp.c +++ b/lisp/lisp.c @@ -1,7 +1,7 @@ /* * main() entry point for a stand alone lisp image. * - * $Header: /Volumes/share2/src/cmucl/cvs2git/cvsroot/src/lisp/lisp.c,v 1.72 2010/07/30 20:26:11 rtoy Exp $ + * $Header: /Volumes/share2/src/cmucl/cvs2git/cvsroot/src/lisp/lisp.c,v 1.73 2010/07/30 22:51:58 rtoy Exp $ * */ @@ -32,9 +32,11 @@ #include "core.h" #include "save.h" #include "lispregs.h" -#if defined FEATURE_EXECUTABLE +#if defined(FEATURE_EXECUTABLE) +#if !defined(DARWIN) #include "elf.h" #endif +#endif /* SIGINT handler that invokes the monitor. */ @@ -437,8 +439,8 @@ main(int argc, const char *argv[], const char *envp[]) lispobj initial_function = 0; if (builtin_image_flag != 0) { -#if defined(i386) && defined(__linux__) - initial_function = initial_function_addr; +#if defined(i386) && (defined(__linux__) || defined(DARWIN)) + initial_function = (lispobj) initial_function_addr; #else initial_function = (lispobj) & initial_function_addr; #endif diff --git a/lisp/mach-o.c b/lisp/mach-o.c new file mode 100644 index 000000000..096be872b --- /dev/null +++ b/lisp/mach-o.c @@ -0,0 +1,393 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "os.h" +#include "core.h" +#include "internals.h" +#include "globals.h" +#include "validate.h" + +#include "mach-o/loader.h" + +#define LINKER_SCRIPT "linker-x86.sh" +#define C_COMPILER "cc" + +typedef struct mach_header MachO_hdr; + +/* Elf data structures. */ +static MachO_hdr eh; + +/* Names of the Lisp image ELF sections. These names must be the same as + the corresponding names found in the linker script. */ + +static char *section_names[] = {"CORDYN", "CORSTA", "CORRO"}; + +/* Note: write errors are not fatal. */ +static int +ewrite(int fd, const void *buf, size_t nbytes, const char *func) +{ + if (write(fd, buf, nbytes) < nbytes) { + perror(func); + return -1; /* Simple way to indicate error. */ + } + return 0; +} + +/* + Read errors are fatal, because these reads have to succeed for lisp to + get off the ground. + */ +static void +eread(int d, void *buf, size_t nbytes, const char *func) +{ + int res = read(d, buf, nbytes); + + if (res == -1) { + perror(func); + exit(-1); + } + + if (res < nbytes) { + fprintf(stderr, "Short read in %s!\n", func); + exit(-1); + } +} + +static void +elseek(int d, off_t o, int whence, const char *func) +{ + if (lseek(d, o, whence) == -1) { + perror(func); + exit(-1); + } +} + + +static int +create_elf_file (const char *dir, int id) +{ + char outfilename[FILENAME_MAX + 1]; + int out; + + /* Note: the space id will be either 1, 2 or 3. Subtract one to index + the name array. */ + snprintf(outfilename, FILENAME_MAX, "%s/%s.o", dir, section_names[id - 1]); + out = open(outfilename, O_WRONLY | O_CREAT | O_TRUNC, 0666); + + if(!out) { + perror("write_elf_object: can't open file"); + fprintf(stderr, "%s\n", outfilename); + } + + return out; +} + + +static int +write_elf_header(int fd) +{ + extern MachO_hdr eh; + + /* Ident array. */ + eh.magic = MH_MAGIC; + /* Support any kind x86. (Should we be more specific?) */ + eh.cputype = CPU_TYPE_I386; + eh.cpusubtype = CPU_SUBTYPE_I386_ALL; + + eh.filetype = MH_OBJECT; + + /* We only have 1 load command in our object */ + eh.ncmds = 1; + /* Size of 1 segment command plus size of 1 section */ + eh.sizeofcmds = sizeof(struct segment_command) + sizeof(struct section); + eh.flags = MH_NOUNDEFS | MH_NOMULTIDEFS; + + return ewrite(fd, &eh, sizeof(MachO_hdr), __func__); +} + +static int +write_load_command(int fd, char* name, int length, os_vm_address_t start) +{ + struct segment_command lc; + + lc.cmd = LC_SEGMENT; + /* Size is 1 segment command + 1 section command */ + lc.cmdsize = sizeof(lc) + sizeof(struct section); + strncpy(lc.segname, name, sizeof(lc.segname)); + lc.vmaddr = start; + lc.vmsize = length; + /* Offset where the data is. It's the header, the segment + * command, and one section */ + lc.fileoff = lc.cmdsize + sizeof(struct mach_header); + lc.filesize = length; + lc.maxprot = VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE; + lc.initprot = lc.maxprot; + lc.nsects = 1; + lc.flags = 0; + + return ewrite(fd, &lc, sizeof(lc), __func__); +} + +static int +write_section(int fd, int length, os_vm_address_t start, char* object_name) +{ + struct section sc; + + strncpy(sc.sectname, object_name, sizeof(sc.sectname)); + strncpy(sc.segname, object_name, sizeof(sc.segname)); + sc.addr = start; + sc.size = length; + /* Offset of the data. We have one header, one segment and one + * section */ + sc.offset = sizeof(struct mach_header) + sizeof(struct segment_command) + + sizeof(struct section); + sc.align = 12; /* Align on 2^12 = 4096 boundary */ + sc.reloff = 0; + sc.nreloc = 0; + sc.flags = 0; + sc.reserved1 = 0; + sc.reserved2 = 0; + + return ewrite(fd, &sc, sizeof(sc), __func__); +} + +static int +write_section_data(int fd, long length, os_vm_address_t real_addr) +{ + return ewrite(fd, (void *)real_addr, length, __func__); +} + + +int +write_elf_object(const char *dir, int id, os_vm_address_t start, os_vm_address_t end) +{ + int out = create_elf_file(dir, id); + int ret = 0; + /* The length should be a multiple of the page size. */ + size_t length = end - start + (os_vm_page_size - + ((end - start) % os_vm_page_size)); + + if(id < 1 || id > 3) { + fprintf(stderr, "Invalid space id in %s: %d\n", __func__, id); + fprintf(stderr, "Executable not built.\n"); + ret = -1; + } + + /* Make id be 0-based to match array. */ + id--; + + if ((write_elf_header(out) == -1) + || (write_load_command(out, section_names[id], length, start) == -1) + || (write_section(out, length, start, section_names[id]) == -1) + || (write_section_data(out, length, start) == -1)) { + fprintf(stderr, "Executable not built.\n"); + ret = -1; + } + + close(out); + return ret; +} + +void +elf_cleanup(const char *dirname) +{ + char filename[FILENAME_MAX + 1]; + int i; + + /* Get rid of lisp space files. */ + for(i = 0; i < 3; i++) { + /* Delete core space .o files. */ + sprintf(filename, "%s/%s.o", dirname, section_names[i]); + unlink(filename); + } +} + +int +elf_run_linker(long init_func_address, char *file) +{ + lispobj libstring = SymbolValue(CMUCL_LIB); /* Get library: */ + struct vector *vec = (struct vector *)PTR(libstring); + char *paths; + char command[FILENAME_MAX + 1]; + char command_line[FILENAME_MAX + FILENAME_MAX + 10]; + char *strptr; + struct stat st; + int ret; + extern int debug_lisp_search; +#ifndef UNICODE + paths = strdup((char *)vec->data); +#else + /* + * What should we do here with 16-bit characters? For now we just + * take the low 8-bits. + */ + paths = malloc(vec->length); + { + int k; + unsigned short *data; + data = (unsigned short*) vec->data; + + for (k = 0; k < vec->length; ++k) { + paths[k] = data[k] & 0xff; + } + } +#endif + strptr = strtok(paths, ":"); + + if (debug_lisp_search) { + printf("Searching for linker.sh script\n"); + } + + while(strptr != NULL) { + + sprintf(command, "%s/%s", strptr, LINKER_SCRIPT); + + if (debug_lisp_search) { + printf(" %s\n", command); + } + + if (stat(command, &st) == 0) { + extern int main(); + + free(paths); + printf("\t[%s: linking %s... \n", command, file); + fflush(stdout); + sprintf(command_line, "%s %s 0x%lx '%s' 0x%lx 0x%lx 0x%lx", command, + C_COMPILER, init_func_address, file, + (unsigned long) READ_ONLY_SPACE_START, + (unsigned long) STATIC_SPACE_START, + (unsigned long) DYNAMIC_0_SPACE_START); + ret = system(command_line); + if(ret == -1) { + perror("Can't run link script"); + } else { + printf("\tdone]\n"); + fflush(stdout); + } + return ret; + } + strptr = strtok(NULL, ":"); + } + + fprintf(stderr, + "Can't find %s script in CMUCL library directory list.\n", LINKER_SCRIPT); + free(paths); + return -1; +} + + +/* Read the ELF header from a file descriptor and stuff it into a + structure. Make sure it is really an elf header etc. */ +static void +read_elf_header(int fd, MachO_hdr *ehp) +{ + eread(fd, ehp, sizeof(MachO_hdr), __func__); + + if (ehp->magic != MH_MAGIC) { + fprintf(stderr, + "Bad ELF magic number --- not an elf file. Exiting in %s.\n", + __func__); + exit(-1); + } +} + + +/* + Map the built-in lisp core sections. + + NOTE! We need to do this without using malloc because the memory layout + is not set until some time after this is done. +*/ +void +map_core_sections(const char *exec_name) +{ + int exec_fd; + int sections_remaining = 3; + int i, j; + extern int image_dynamic_space_size; + extern int image_static_space_size; + extern int image_read_only_space_size; + + if (!(exec_fd = open(exec_name, O_RDONLY))) { + perror("Can't open executable!"); + exit(-1); + } + + read_elf_header(exec_fd, &eh); + + for (i = 0; i < eh.ncmds && sections_remaining > 0; i++) { + struct load_command lc; + struct segment_command sc; + + /* Read the load command and see if its segname matches one of + * our section names. If it does, save the file offset for + * later so we can map the data */ + + eread(exec_fd, &lc, sizeof(lc), __func__); + fprintf(stderr, "Load %d: cmd = %d, cmdsize = %d\n", i, lc.cmd, lc.cmdsize); + + if (lc.cmd == LC_SEGMENT) { + /* Read the segment command and save the file offset */ + fprintf(stderr, "Reading next %d bytes for SEGMENT\n", sizeof(sc) - sizeof(lc)); + eread(exec_fd, &sc.segname, sizeof(sc) - sizeof(lc), __func__); + fprintf(stderr, "LC_SEGMENT: name = %s\n", sc.segname); + + for (j = 0; j < 3; ++j) { + if (strncmp(sc.segname, section_names[j], sizeof(sc.segname)) == 0) { + /* Found a core segment. Map it! */ + fprintf(stderr, "Matched!\n"); + fprintf(stderr, " Fileoff = %ld\n", sc.fileoff); + fprintf(stderr, " vmaddr = 0x%lx\n", sc.vmaddr); + fprintf(stderr, " vmsize = 0x%lx\n", sc.vmsize); + + if ((os_vm_address_t) os_map(exec_fd, sc.fileoff, + (os_vm_address_t) sc.vmaddr, + sc.vmsize) + == (os_vm_address_t) -1) { + fprintf(stderr, "%s: Can't map section %s\n", __func__, section_names[j]); + exit(-1); + } + switch (j) { + case 0: + /* Dynamic space */ + image_dynamic_space_size = sc.vmsize; + break; + case 1: + /* Static space */ + image_static_space_size = sc.vmsize; + break; + case 2: + /* Read only */ + image_read_only_space_size = sc.vmsize; + break; + default: + /* Shouldn't happen! */ + abort(); + } + --sections_remaining; + break; + } + } + fprintf(stderr, "Reading %d remainder bytes left in command\n", + lc.cmdsize - sizeof(sc)); + elseek(exec_fd, lc.cmdsize - sizeof(sc), SEEK_CUR, __func__); + } else { + /* Seek to the next command */ + fprintf(stderr, "Seeking by %d bytes\n", lc.cmdsize - sizeof(lc)); + elseek(exec_fd, lc.cmdsize - sizeof(lc), SEEK_CUR, __func__); + } + } + + close(exec_fd); + + if (sections_remaining != 0) { + fprintf(stderr, "Couldn't map all core sections! Exiting!\n"); + exit(-1); + } +} + diff --git a/lisp/save.c b/lisp/save.c index 582505e6f..c7e66f067 100644 --- a/lisp/save.c +++ b/lisp/save.c @@ -1,6 +1,6 @@ /* - $Header: /Volumes/share2/src/cmucl/cvs2git/cvsroot/src/lisp/save.c,v 1.25 2010/07/30 20:26:11 rtoy Exp $ + $Header: /Volumes/share2/src/cmucl/cvs2git/cvsroot/src/lisp/save.c,v 1.26 2010/07/30 22:51:58 rtoy Exp $ This code was written as part of the CMU Common Lisp project at Carnegie Mellon University, and has been placed in the public domain. @@ -28,9 +28,11 @@ #endif #ifdef FEATURE_EXECUTABLE +#if !defined(DARWIN) #include <libgen.h> #include "elf.h" #endif +#endif extern int version; diff --git a/tools/linker-x86.sh b/tools/linker-x86.sh index 0767f69f0..641016daf 100644 --- a/tools/linker-x86.sh +++ b/tools/linker-x86.sh @@ -1,6 +1,6 @@ #!/bin/sh -# $Id: linker-x86.sh,v 1.2 2010/07/30 20:26:12 rtoy Exp $ +# $Id: linker-x86.sh,v 1.3 2010/07/30 22:51:58 rtoy Exp $ # This file written by Raymond Toy as part of CMU Common Lisp and is # placed in the public domain. @@ -19,23 +19,53 @@ fi CCOMPILER=$1 IFADDR=$2 EXEC=$3 -RO_ADDR="-Wl,--section-start=CORRO=$4" -STATIC_ADDR="-Wl,--section-start=CORSTA=$5" -DYN_ADDR="-Wl,--section-start=CORDYN=$6" OUTDIR=`dirname $EXEC` OUTNAME=`basename $EXEC` CMUCLLIB=`dirname $0` -#OPT_IFADDR="-Wl,--defsym -Wl,initial_function_addr=$IFADDR" -OPT_IFADDR="ifaddr.c" -OPT_ARCHIVE="-Wl,--whole-archive -Wl,$CMUCLLIB/lisp.a -Wl,--no-whole-archive" +# Name of file where we write the actual initial function address. +OPT_IFADDR="cmu-ifaddr-$$.c" +# Names of the core sections from Lisp. OPT_CORE="CORRO.o CORSTA.o CORDYN.o" +case `uname` in + Linux*) + # How to specify the starting address for each of the sections + RO_ADDR="-Wl,--section-start=CORRO=$4" + STATIC_ADDR="-Wl,--section-start=CORSTA=$5" + DYN_ADDR="-Wl,--section-start=CORDYN=$6" + + #OPT_IFADDR="-Wl,--defsym -Wl,initial_function_addr=$IFADDR" + + # Specify how to link the entire lisp.a library + OPT_ARCHIVE="-Wl,--whole-archive -Wl,$CMUCLLIB/lisp.a -Wl,--no-whole-archive" + # See Config.x86_linux + OS_LIBS=-ldl + ;; + Darwin*) + # How to specify the starting address for each of the sections + RO_ADDR="-segaddr CORRO $4" + STATIC_ADDR="-segaddr CORSTA $5" + DYN_ADDR="-segaddr CORDYN $6" + + # Specify how to link the entire lisp.a library + OPT_ARCHIVE="-all_load $CMUCLLIB/lisp.a" + + # Extra stuff. For some reason one __LINKEDIT segment is mapped + # just past the dynamic space. This messes things up, so we move it + # to another address. This seems to be free, at least on 10.5. + + OPT_EXTRA="-segaddr __LINKEDIT 0x99000000" + # See Config.x86_darwin + OS_LIBS= + ;; +esac + trap 'rm -f $OUTDIR/$OPT_IFADDR' 0 (cd $OUTDIR echo "long initial_function_addr = $IFADDR;" > $OPT_IFADDR -$CCOMPILER -m32 -o $OUTNAME -rdynamic $OPT_IFADDR $OPT_ARCHIVE $OPT_CORE $RO_ADDR $STATIC_ADDR $DYN_ADDR -ldl -lm) +$CCOMPILER -m32 -o $OUTNAME -rdynamic $OPT_IFADDR $OPT_ARCHIVE $OPT_CORE $RO_ADDR $STATIC_ADDR $DYN_ADDR $OPT_EXTRA $OS_LIBS -lm) diff --git a/tools/make-main-dist.sh b/tools/make-main-dist.sh index 57e4d9d68..387d41173 100755 --- a/tools/make-main-dist.sh +++ b/tools/make-main-dist.sh @@ -51,19 +51,24 @@ case $ARCH in esac case $OS in - FreeBSD*) - EXECUTABLE=true - SCRIPT=FreeBSD - ;; - linux*) - EXECUTABLE=true - SCRIPT=Linux - ;; - solaris*) - EXECUTABLE=true - SCRIPT=SunOS - ;; - *) EXECUTABLE="" ;; + FreeBSD*) + EXECUTABLE=true + SCRIPT=FreeBSD + ;; + linux*) + EXECUTABLE=true + SCRIPT=Linux + ;; + solaris*) + EXECUTABLE=true + SCRIPT=SunOS + ;; + darwin*) + EXECUTABLE=true + ;; + *) + EXECUTABLE="" + ;; esac # Frob PATH to use /usr/ucb/install for Solaris @@ -93,6 +98,7 @@ then install ${GROUP} ${OWNER} -m 0755 $TARGET/lisp/lisp.a $DESTDIR/lib/cmucl/lib/ install ${GROUP} ${OWNER} -m 0755 src/tools/linker.sh $DESTDIR/lib/cmucl/lib/ install ${GROUP} ${OWNER} -m 0755 src/tools/linker-x86.sh $DESTDIR/lib/cmucl/lib/ + install ${GROUP} ${OWNER} -m 0755 src/tools/linker-darwin.sh $DESTDIR/lib/cmucl/lib/ install ${GROUP} ${OWNER} -m 0755 src/tools/$SCRIPT-cmucl-linker-script $DESTDIR/lib/cmucl/lib/ fi for corefile in $TARGET/lisp/$CORE -- GitLab