]> git.saurik.com Git - apple/ld64.git/blobdiff - src/ld/Snapshot.cpp
ld64-133.3.tar.gz
[apple/ld64.git] / src / ld / Snapshot.cpp
diff --git a/src/ld/Snapshot.cpp b/src/ld/Snapshot.cpp
new file mode 100644 (file)
index 0000000..27ce370
--- /dev/null
@@ -0,0 +1,538 @@
+//
+//  Snapshot.cpp
+//  ld64
+//
+//  Created by Josh Behnke on 8/25/11.
+//  Copyright (c) 2011 Apple Inc. All rights reserved.
+//
+
+#include <string.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <limits.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <sys/stat.h>
+#include <libgen.h>
+#include <time.h>
+#include <Block.h>
+
+#include "Snapshot.h"
+#include "Options.h"
+
+#include "compile_stubs.h"
+
+//#define STORE_PID_IN_SNAPSHOT 1
+
+// Well known snapshot file/directory names. These appear in the root of the snapshot.
+// They are collected together here to make managing the namespace easier.
+static const char *frameworksString         = "frameworks";         // directory containing framework stubs (mach-o files)
+static const char *dylibsString             = "dylibs";             // directory containing dylib stubs (mach-o files)
+static const char *archiveFilesString       = "archive_files";      // directory containing .a files
+static const char *origCommandLineString    = "orig_command_line";  // text file containing the original command line
+static const char *linkCommandString        = "link_command";       // text file containing the snapshot equivalent command line
+static const char *dataFilesString          = "data_files";         // arbitrary data files referenced on the command line
+static const char *objectsString            = "objects";            // directory containing object files
+static const char *frameworkStubsString     = "framework_stubs";    // directory containing framework stub info (text files)
+static const char *dylibStubsString         = "dylib_stubs";        // directory containing dylib stub info (text files)
+static const char *assertFileString         = "assert_info";        // text file containing assertion failure logs
+static const char *compileFileString        = "compile_stubs";      // text file containing compile_stubs script
+
+Snapshot *Snapshot::globalSnapshot = NULL;
+
+Snapshot::Snapshot() : fRecordArgs(false), fRecordObjects(false), fRecordDylibSymbols(false), fRecordArchiveFiles(false), fRecordUmbrellaFiles(false), fRecordDataFiles(false), fFrameworkArgAdded(false), fSnapshotLocation(NULL), fSnapshotName(NULL), fRootDir(NULL), fFilelistFile(-1), fCopiedArchives(NULL) 
+{
+    if (globalSnapshot != NULL)
+        throw "only one snapshot supported";
+    globalSnapshot = this;
+}
+
+
+Snapshot::~Snapshot() 
+{
+    // Lots of things leak under the assumption the linker is about to exit.
+}
+
+
+void Snapshot::setSnapshotPath(const char *path) 
+{
+    if (fRootDir == NULL) {
+        fSnapshotLocation = strdup(path);
+    }
+}
+
+
+void Snapshot::setSnapshotMode(SnapshotMode mode) 
+{
+    if (fRootDir == NULL) {
+        fRecordArgs = false;
+        fRecordObjects = false;
+        fRecordDylibSymbols = false;
+        fRecordArchiveFiles = false;
+        fRecordUmbrellaFiles = false;
+        fRecordDataFiles = false;
+        
+        switch (mode) {
+            case SNAPSHOT_DISABLED:
+                break;
+            case SNAPSHOT_DEBUG:
+                fRecordArgs = fRecordObjects = fRecordDylibSymbols = fRecordArchiveFiles = fRecordUmbrellaFiles = fRecordDataFiles = true;
+                break;
+            default:
+                break;
+        }
+    }
+}
+
+void Snapshot::setSnapshotName(const char *path)
+{
+    if (fRootDir == NULL) {
+        const char *base = basename((char *)path);
+        time_t now = time(NULL);
+        struct tm t;
+        localtime_r(&now, &t);
+        char buf[PATH_MAX];
+        snprintf(buf, sizeof(buf)-1, "%s-%4.4d-%2.2d-%2.2d-%2.2d%2.2d%2.2d.ld-snapshot", base, t.tm_year+1900, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec);
+        fSnapshotName = strdup(buf);
+    }
+}
+
+
+// Construct a path string in the snapshot.
+// subdir - an optional subdirectory name
+// file - the file name
+void Snapshot::buildPath(char *buf, const char *subdir, const char *file) 
+{
+    if (fRootDir == NULL)
+        throw "snapshot not created";
+    
+    strcpy(buf, fRootDir);
+    strcat(buf, "/");
+    if (subdir) {
+        strcat(buf, subdir);
+        // implicitly create the subdirectory
+        mkdir(buf, S_IRUSR|S_IWUSR|S_IXUSR);
+        strcat(buf, "/");
+    }
+    if (file != NULL)
+        strcat(buf, basename((char *)file));
+}
+
+
+// Construct a unique path string in the snapshot. If a path collision is detected then uniquing
+// is accomplished by appending a counter to the path until there is no preexisting file.
+// subdir - an optional subdirectory name
+// file - the file name
+void Snapshot::buildUniquePath(char *buf, const char *subdir, const char *file) 
+{
+    buildPath(buf, subdir, file);
+    struct stat st;
+    if (stat(buf, &st)==0) {
+        // make it unique
+        int counter=1;
+        char *number = strrchr(buf, 0);
+        number[0]='-';
+        number++;
+        do {
+            sprintf(number, "%d", counter++);
+        } while (stat(buf, &st) == 0);
+    }
+}
+
+
+// Copy a file to the snapshot.
+// sourcePath is the original file
+// subdir is an optional subdirectory in the snapshot
+// path is an optional out parameter containing the final uniqued path in the snapshot
+// where the file was copied
+void Snapshot::copyFileToSnapshot(const char *sourcePath, const char *subdir, char *path) 
+{
+    const int copyBufSize=(1<<14); // 16kb buffer
+    static void *copyBuf = NULL;
+    if (copyBuf == NULL)
+        copyBuf = malloc(copyBufSize);
+    
+    char *file=basename((char *)sourcePath);
+    char buf[PATH_MAX];
+    if (path == NULL) path = buf;
+    buildUniquePath(path, subdir, file);
+    int out_fd = open(path, O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR);
+    int in_fd = open(sourcePath, O_RDONLY);
+    int len;
+    if (out_fd != -1 && in_fd != -1) {
+        do {
+            len = read(in_fd, copyBuf, copyBufSize);
+            if (len > 0) write(out_fd, copyBuf, len);
+        } while (len == copyBufSize);
+    }
+    close(in_fd);
+    close(out_fd);
+}
+
+
+// Create the snapshot root directory.
+void Snapshot::createSnapshot()
+{
+    if (fRootDir == NULL) {
+        // provide default name and location
+        if (fSnapshotLocation == NULL)
+            fSnapshotLocation = "/tmp";        
+        if (fSnapshotName == NULL) {
+            setSnapshotName("ld_snapshot");
+        }
+        
+        char buf[PATH_MAX];
+        fRootDir = (char *)fSnapshotLocation;
+        buildUniquePath(buf, NULL, fSnapshotName);
+        fRootDir = strdup(buf);
+        if (mkdir(fRootDir, S_IRUSR|S_IWUSR|S_IXUSR)!=0) {
+            warning("unable to create link snapshot directory: %s", fRootDir);
+            fRootDir = NULL;
+            setSnapshotMode(SNAPSHOT_DISABLED); // don't try to write anything if we can't create snapshot dir
+        }
+        
+        buildPath(buf, NULL, compileFileString);
+        int compileScript = open(buf, O_WRONLY|O_CREAT|O_TRUNC, S_IXUSR|S_IRUSR|S_IWUSR);
+        write(compileScript, compile_stubs, strlen(compile_stubs));
+        close(compileScript);
+
+        SnapshotLog::iterator it;
+        for (it = fLog.begin(); it != fLog.end(); it++) {
+            void (^logItem)(void) = *it;
+            logItem();
+            Block_release(logItem);
+        }
+        fLog.erase(fLog.begin(), fLog.end());
+        
+        if (fRecordArgs) {
+            writeCommandLine(fRawArgs, origCommandLineString, true);
+            writeCommandLine(fArgs);
+        }
+        
+#if STORE_PID_IN_SNAPSHOT
+        char path[PATH_MAX];
+        buildUniquePath(path, NULL, pidString);
+        int pidfile = open(path, O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR);
+        char pid_buf[32];
+        sprintf(pid_buf, "%lu\n", (long unsigned)getpid());
+        write(pidfile, pid_buf, strlen(pid_buf));
+        write(pidfile, "\n", 1);
+        close(pidfile);    
+#endif
+        
+    }
+}
+
+
+// Write the current command line vector to filename.
+void Snapshot::writeCommandLine(StringVector &args, const char *filename, bool includeCWD) 
+{
+    if (!isLazy() && fRecordArgs) {
+        // Figure out the file name and open it.
+        if (filename == NULL)
+            filename = linkCommandString;
+        char path[PATH_MAX];
+        buildPath(path, NULL, filename);
+        int argsFile = open(path, O_WRONLY|O_CREAT|O_TRUNC, S_IXUSR|S_IRUSR|S_IWUSR);
+        FILE *argsStream = fdopen(argsFile, "w");
+        
+        if (includeCWD)
+            fprintf(argsStream, "cd %s\n", getcwd(path, sizeof(path)));
+
+        // iterate to write args, quoting as needed
+        StringVector::iterator it;
+        for (it = args.begin(); it != args.end(); it++) {
+            const char *arg = *it;
+            bool needQuotes = false;
+            for (const char *c = arg; *c != 0 && !needQuotes; c++) {
+                if (isspace(*c))
+                    needQuotes = true;
+            }
+            if (it != args.begin()) fprintf(argsStream, " ");
+            if (needQuotes) fprintf(argsStream, "\"");
+            fprintf(argsStream, "%s", arg);
+            if (needQuotes) fprintf(argsStream, "\"");
+        }
+        fprintf(argsStream, "\n");
+        fclose(argsStream);
+    }
+}
+
+
+// Store the command line args in the snapshot.
+void Snapshot::recordRawArgs(int argc, const char *argv[])
+{
+    // first store the original command line as-is
+    for (int i=0; i<argc; i++) {
+        fRawArgs.push_back(argv[i]);
+    }
+    fArgs.insert(fArgs.begin(), argv[0]);
+    fArgs.insert(fArgs.begin()+1, "-Z"); // don't search standard paths when running in the snapshot
+}
+
+
+// Adds one or more args to the snapshot link command.
+// argIndex is the index in the original raw args vector to start adding args
+// argCount is the count of args to copy from the raw args vector
+// fileArg is the index relative to argIndex of a file arg. The file is copied into the
+// snapshot and the path is fixed up in the snapshot link command. (skipped if fileArg==-1)
+void Snapshot::addSnapshotLinkArg(int argIndex, int argCount, int fileArg)
+{
+    if (fRootDir == NULL) {
+        fLog.push_back(Block_copy(^{ this->addSnapshotLinkArg(argIndex, argCount, fileArg); }));
+    } else {
+        char buf[PATH_MAX];
+        const char *subdir = dataFilesString;
+        for (int i=0, arg=argIndex; i<argCount && argIndex+1<(int)fRawArgs.size(); i++, arg++) {
+            if (i != fileArg) {
+                fArgs.push_back(fRawArgs[arg]);
+            } else {
+                if (fRecordDataFiles) {
+                    copyFileToSnapshot(fRawArgs[arg], subdir, buf);
+                    fArgs.push_back(strdup(snapshotRelativePath(buf)));
+                } else {
+                    // if we don't copy the file then just record the original path
+                    fArgs.push_back(strdup(fRawArgs[arg]));
+                }
+            }
+        }
+    }
+}
+
+// Record the -arch string
+void Snapshot::recordArch(const char *arch)
+{
+    // must be called after recordRawArgs()
+    if (fRawArgs.size() == 0)
+        throw "raw args not set";
+
+    // only need to store the arch explicitly if it is not mentioned on the command line
+    bool archInArgs = false;
+    StringVector::iterator it;
+    for (it = fRawArgs.begin(); it != fRawArgs.end() && !archInArgs; it++) {
+        const char *arg = *it;
+        if (strcmp(arg, "-arch") == 0)
+            archInArgs = true;
+    }
+    
+    if (!archInArgs) {
+        if (fRootDir == NULL) {
+            fLog.push_back(Block_copy(^{ this->recordArch(arch); }));
+        } else {
+            char path_buf[PATH_MAX];
+            buildUniquePath(path_buf, NULL, "arch");
+            int fd=open(path_buf, O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR);
+            write(fd, arch, strlen(arch));
+            close(fd);
+        }
+    }
+}
+
+// Record an object file in the snapshot.
+// path - the object file's path
+// fileContent - a pointer to the object file content
+// fileLength - the buffer size of fileContent
+void Snapshot::recordObjectFile(const char *path) 
+{
+    if (fRootDir == NULL) {
+        fLog.push_back(Block_copy(^{ this->recordObjectFile(path); }));
+    } else {
+        if (fRecordObjects) {
+                       char path_buf[PATH_MAX];
+                       copyFileToSnapshot(path, objectsString, path_buf);
+            
+            // lazily open the filelist file
+            if (fFilelistFile == -1) {
+                char filelist_path[PATH_MAX];
+                buildUniquePath(filelist_path, objectsString, "filelist");
+                fFilelistFile = open(filelist_path, O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR);
+                fArgs.push_back("-filelist");
+                fArgs.push_back(strdup(snapshotRelativePath(filelist_path)));
+                writeCommandLine(fArgs);
+            }
+            
+            // record the snapshot path in the filelist
+            const char *relative_path = snapshotRelativePath(path_buf);
+            write(fFilelistFile, relative_path, strlen(relative_path));
+            write(fFilelistFile, "\n", 1);
+        }
+    }
+}
+
+void Snapshot::addFrameworkArg(const char *framework)
+{
+    bool found=false;
+    for (unsigned i=0; i<fArgs.size()-1; i++) {
+        if (strcmp(fArgs[i], "-framework") == 0 && strcmp(fArgs[i+1], framework) == 0)
+            found = true;
+    }
+    if (!found) {
+        if (!fFrameworkArgAdded) {
+            fFrameworkArgAdded = true;
+            fArgs.push_back("-Fframeworks");
+        }
+        fArgs.push_back("-framework");
+        fArgs.push_back(strdup(framework));
+        writeCommandLine(fArgs);
+    }
+}
+
+void Snapshot::addDylibArg(const char *dylib)
+{
+    bool found=false;
+    for (unsigned i=0; i<fArgs.size()-1; i++) {
+        if (strcmp(fArgs[i], dylib) == 0)
+            found = true;
+    }
+    if (!found) {
+        char buf[ARG_MAX];
+        sprintf(buf, "%s/%s", dylibsString, dylib);
+        fArgs.push_back(strdup(buf));
+        writeCommandLine(fArgs);
+    }
+}
+
+// Record a dylib symbol reference in the snapshot.
+// (References are not written to the snapshot until writeStubDylibs() is called.)
+void Snapshot::recordDylibSymbol(ld::dylib::File* dylibFile, const char *name)
+{
+    if (fRootDir == NULL) {
+        fLog.push_back(Block_copy(^{ this->recordDylibSymbol(dylibFile, name); }));
+    } else {
+        if (fRecordDylibSymbols) {
+            // find the dylib in the table
+            DylibMap::iterator it;
+            const char *dylibPath = dylibFile->path();
+            it = fDylibSymbols.find(dylibPath);
+            bool isFramework = (strstr(dylibPath, "framework") != NULL);
+            int dylibFd;
+            if (it == fDylibSymbols.end()) {
+                // Didn't find a file descriptor for this dylib. Create one and add it to the dylib map.
+                char path_buf[PATH_MAX];
+                buildUniquePath(path_buf, isFramework ? frameworkStubsString : dylibStubsString, dylibPath);
+                dylibFd = open(path_buf, O_WRONLY|O_APPEND|O_CREAT, S_IRUSR|S_IWUSR);
+                fDylibSymbols.insert(std::pair<const char *, int>(dylibPath, dylibFd));
+                char *base_name = strdup(basename(path_buf));
+                if (isFramework) {
+                    addFrameworkArg(base_name);
+                } else {
+                    addDylibArg(base_name);
+                }
+                writeCommandLine(fArgs);
+            } else {
+                dylibFd = it->second;
+            }
+            // Record the symbol.
+            
+            bool isIdentifier = (name[0] == '_');
+            for (const char *c = name; *c != 0 && isIdentifier; c++)
+                if (!isalnum(*c) && *c!='_')
+                    isIdentifier = false;
+            const char *prefix = "void ";
+            const char *weakAttr = "__attribute__ ((weak)) ";
+            const char *suffix = "(void){}\n";
+            if (isIdentifier) {
+                write(dylibFd, prefix, strlen(prefix));
+                if (dylibFile->hasWeakExternals() && dylibFile->hasWeakDefinition(name))
+                    write(dylibFd, weakAttr, strlen(weakAttr));
+                if (*name == '_') name++;
+                write(dylibFd, name, strlen(name));
+                write(dylibFd, suffix, strlen(suffix));
+            } else {
+                static int symbolCounter = 0;
+                char buf[64+strlen(name)];
+                sprintf(buf, "void s_%5.5d(void) __asm(\"%s\");\nvoid s_%5.5d(){}\n", symbolCounter, name, symbolCounter);
+                write(dylibFd, buf, strlen(buf));
+                symbolCounter++;
+            }
+        }                
+    }
+}
+
+
+// Record a .a archive in the snapshot.
+void Snapshot::recordArchive(const char *archiveFile)
+{
+    if (fRootDir == NULL) {
+        const char *copy = strdup(archiveFile);
+        fLog.push_back(Block_copy(^{ this->recordArchive(archiveFile); ::free((void *)copy); }));
+    } else {
+        if (fRecordArchiveFiles) {
+            // lazily create a vector of .a files that have been added
+            if (fCopiedArchives == NULL) {
+                fCopiedArchives = new StringVector;
+            }
+            
+            // See if we have already added this .a
+            StringVector::iterator it;
+            bool found = false;
+            for (it = fCopiedArchives->begin(); it != fCopiedArchives->end() && !found; it++) {
+                if (strcmp(archiveFile, *it) == 0)
+                    found = true;
+            }
+            
+            // If this is a new .a then copy it to the snapshot and add it to the snapshot link command.
+            if (!found) {
+                char path[PATH_MAX];
+                fCopiedArchives->push_back(archiveFile);
+                copyFileToSnapshot(archiveFile, archiveFilesString, path);
+                fArgs.push_back(strdup(snapshotRelativePath(path)));
+                writeCommandLine(fArgs);
+            }
+        }
+    }
+}
+
+void Snapshot::recordSubUmbrella(const char *frameworkPath)
+{
+    if (fRootDir == NULL) {
+        const char *copy = strdup(frameworkPath);
+        fLog.push_back(Block_copy(^{ this->recordSubUmbrella(copy); ::free((void *)copy); }));
+    } else {
+        if (fRecordUmbrellaFiles) {
+            const char *framework = basename((char *)frameworkPath);
+            char buf[PATH_MAX], wrapper[PATH_MAX];
+            strcpy(wrapper, frameworksString);
+            buildPath(buf, wrapper, NULL); // ensure the frameworks directory exists
+            strcat(wrapper, "/");
+            strcat(wrapper, framework);
+            strcat(wrapper, ".framework");
+            copyFileToSnapshot(frameworkPath, wrapper);
+            addFrameworkArg(framework);
+        }
+    }
+}
+
+void Snapshot::recordSubLibrary(const char *dylibPath)
+{
+    if (fRootDir == NULL) {
+        const char *copy = strdup(dylibPath);
+        fLog.push_back(Block_copy(^{ this->recordSubLibrary(copy); ::free((void *)copy); }));
+    } else {
+        if (fRecordUmbrellaFiles) {
+            copyFileToSnapshot(dylibPath, dylibsString);
+            addDylibArg(basename((char *)dylibPath));
+        }
+    }
+}
+
+void Snapshot::recordAssertionMessage(const char *fmt, ...)
+{
+    char *msg;
+    va_list args;
+    va_start(args, fmt);
+    vasprintf(&msg, fmt, args);
+    va_end(args);
+    if (msg != NULL) {
+        if (fRootDir == NULL) {
+            fLog.push_back(Block_copy(^{ this->recordAssertionMessage("%s", msg); free(msg); }));
+        } else {
+            char path[PATH_MAX];
+            buildPath(path, NULL, assertFileString);
+            int log = open(path, O_WRONLY|O_APPEND|O_CREAT, S_IRUSR|S_IWUSR);
+            write(log, msg, strlen(msg));
+            close(log);
+            free(msg);
+        }    
+    }
+}