/* $Id: RTDbgSymCache.cpp $ */ /** @file * IPRT - Debug Symbol Cache Utility. */ /* * Copyright (C) 2013-2019 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; * you can redistribute it and/or modify it under the terms of the GNU * General Public License (GPL) as published by the Free Software * Foundation, in version 2 as it comes in the "COPYING" file of the * VirtualBox OSE distribution. VirtualBox OSE is distributed in the * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. * * The contents of this file may alternatively be used under the terms * of the Common Development and Distribution License Version 1.0 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the * VirtualBox OSE distribution, in which case the provisions of the * CDDL are applicable instead of those of the GPL. * * You may elect to license modified versions of this file under the * terms and conditions of either the GPL or the CDDL or both. */ /********************************************************************************************************************************* * Header Files * *********************************************************************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /********************************************************************************************************************************* * Structures and Typedefs * *********************************************************************************************************************************/ /** * Cache file type. */ typedef enum RTDBGSYMCACHEFILETYPE { RTDBGSYMCACHEFILETYPE_INVALID, RTDBGSYMCACHEFILETYPE_DIR, RTDBGSYMCACHEFILETYPE_DIR_FILTER, RTDBGSYMCACHEFILETYPE_DEBUG_FILE, RTDBGSYMCACHEFILETYPE_IMAGE_FILE, RTDBGSYMCACHEFILETYPE_DEBUG_BUNDLE, RTDBGSYMCACHEFILETYPE_IMAGE_BUNDLE, RTDBGSYMCACHEFILETYPE_IGNORE } RTDBGSYMCACHEFILETYPE; /** * Configuration for the 'add' command. */ typedef struct RTDBGSYMCACHEADDCFG { bool fRecursive; bool fOverwriteOnConflict; const char *pszFilter; const char *pszCache; } RTDBGSYMCACHEADDCFG; /** Pointer to a read only 'add' config. */ typedef RTDBGSYMCACHEADDCFG const *PCRTDBGSYMCACHEADDCFG; /********************************************************************************************************************************* * Global Variables * *********************************************************************************************************************************/ /** Bundle suffixes. */ static const char * const g_apszBundleSuffixes[] = { ".kext", ".app", ".framework", /** @todo framework is different. */ ".component", ".action", ".caction", ".bundle", ".sourcebundle", ".plugin", ".ppp", ".menu", ".monitorpanel", ".scripting", ".prefPane", ".qlgenerator", ".brailledriver", ".saver", ".SpeechVoice", ".SpeechRecognizer", ".SpeechSynthesizer", ".mdimporter", ".spreporter", ".xpc", NULL }; /** Debug bundle suffixes. (Same as above + .dSYM) */ static const char * const g_apszDSymBundleSuffixes[] = { ".kext.dSYM", ".app.dSYM", ".framework.dSYM", ".component.dSYM", ".action.dSYM", ".caction.dSYM", ".bundle.dSYM", ".sourcebundle.dSYM", ".menu.dSYM", ".plugin.dSYM", ".ppp.dSYM", ".monitorpanel.dSYM", ".scripting.dSYM", ".prefPane.dSYM", ".qlgenerator.dSYM", ".brailledriver.dSYM", ".saver.dSYM", ".SpeechVoice.dSYM", ".SpeechRecognizer.dSYM", ".SpeechSynthesizer.dSYM", ".mdimporter.dSYM", ".spreporter.dSYM", ".xpc.dSYM", ".dSYM", NULL }; /********************************************************************************************************************************* * Internal Functions * *********************************************************************************************************************************/ static int rtDbgSymCacheAddDirWorker(char *pszPath, size_t cchPath, PRTDIRENTRYEX pDirEntry, PCRTDBGSYMCACHEADDCFG pCfg); /** * Display the version of the cache program. * * @returns exit code. */ static RTEXITCODE rtDbgSymCacheVersion(void) { RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision()); return RTEXITCODE_SUCCESS; } /** * Shows the usage of the cache program. * * @returns Exit code. * @param pszArg0 Program name. * @param pszCommand Command selector, NULL if all. */ static RTEXITCODE rtDbgSymCacheUsage(const char *pszArg0, const char *pszCommand) { if (!pszCommand || !strcmp(pszCommand, "add")) RTPrintf("Usage: %s add [-Rno] [fileN..]\n", pszArg0); return RTEXITCODE_SUCCESS; } /** * Creates a UUID mapping for the file. * * @returns IPRT status code. * @param pszCacheFile The path to the file in the cache. * @param pFileUuid The UUID of the file. * @param pszUuidMapDir The UUID map subdirectory in the cache, if this is * wanted, otherwise NULL. * @param pCfg The configuration. */ static int rtDbgSymCacheAddCreateUuidMapping(const char *pszCacheFile, PRTUUID pFileUuid, const char *pszUuidMapDir, PCRTDBGSYMCACHEADDCFG pCfg) { /* * Create the UUID map entry first, deep. */ char szMapPath[RTPATH_MAX]; int rc = RTPathJoin(szMapPath, sizeof(szMapPath) - sizeof("/xxxx/yyyy/xxxx/yyyy/xxxx/zzzzzzzzzzzz") + 1, pCfg->pszCache, pszUuidMapDir); if (RT_FAILURE(rc)) return RTMsgErrorRc(rc, "Error constructing UUID map path (RTPathJoin): %Rrc", rc); size_t cch = strlen(szMapPath); szMapPath[cch] = '-'; rc = RTUuidToStr(pFileUuid, &szMapPath[cch + 2], sizeof(szMapPath) - cch); if (RT_FAILURE(rc)) return RTMsgErrorRc(rc, "Error constructing UUID map path (RTUuidToStr): %Rrc", rc); /* Uppercase the whole lot. */ RTStrToUpper(&szMapPath[cch + 2]); /* Split the first dword in two. */ szMapPath[cch + 1] = szMapPath[cch + 2]; szMapPath[cch + 2] = szMapPath[cch + 3]; szMapPath[cch + 3] = szMapPath[cch + 4]; szMapPath[cch + 4] = szMapPath[cch + 5]; szMapPath[cch + 5] = '-'; /* * Create the directories in the path. */ for (unsigned i = 0; i < 6; i++, cch += 5) { Assert(szMapPath[cch] == '-'); szMapPath[cch] = '\0'; if (!RTDirExists(szMapPath)) { rc = RTDirCreate(szMapPath, 0755, RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL); if (RT_FAILURE(rc)) return RTMsgErrorRc(rc, "RTDirCreate failed on '%s' (UUID map path): %Rrc", szMapPath, rc); } szMapPath[cch] = RTPATH_SLASH; } cch -= 5; /* * Calculate a relative path from there to the actual file. */ char szLinkTarget[RTPATH_MAX]; //szMapPath[cch] = '\0'; rc = RTPathCalcRelative(szLinkTarget, sizeof(szLinkTarget), szMapPath, false /*fFromFile*/, pszCacheFile); //szMapPath[cch] = RTPATH_SLASH; if (RT_FAILURE(rc)) return RTMsgErrorRc(rc, "Failed to calculate relative path from '%s' to '%s': %Rrc", szMapPath, pszCacheFile, rc); /* * If there is already a link there, check if it matches or whether * perhaps it's target doesn't exist. */ RTFSOBJINFO ObjInfo; rc = RTPathQueryInfoEx(szMapPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK); if (RT_SUCCESS(rc)) { if (RTFS_IS_SYMLINK(ObjInfo.Attr.fMode)) { rc = RTPathQueryInfoEx(szMapPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK); if (RT_SUCCESS(rc)) { char *pszCurTarget = NULL; rc = RTSymlinkReadA(szMapPath, &pszCurTarget); if (RT_FAILURE(rc)) return RTMsgErrorRc(rc, "UUID map: failed to read existing symlink '%s': %Rrc", szMapPath, rc); if (RTPathCompare(pszCurTarget, szLinkTarget) == 0) RTMsgInfo("UUID map: existing link '%s' has the same target ('%s').", szMapPath, pszCurTarget); else { RTMsgError("UUID map: Existing mapping '%s' pointing to '%s' insted of '%s'", szMapPath, pszCurTarget, szLinkTarget); rc = VERR_ALREADY_EXISTS; } RTStrFree(pszCurTarget); return rc; } else RTMsgInfo("UUID map: replacing dangling link '%s'", szMapPath); RTSymlinkDelete(szMapPath, 0 /*fFlags*/); } else if (RTFS_IS_FILE(ObjInfo.Attr.fMode)) return RTMsgErrorRc(VERR_IS_A_FILE, "UUID map: found file at '%s', expect symbolic link or nothing.", szMapPath); else if (RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode)) return RTMsgErrorRc(VERR_IS_A_DIRECTORY, "UUID map: found directory at '%s', expect symbolic link or nothing.", szMapPath); else return RTMsgErrorRc(VERR_NOT_SYMLINK, "UUID map: Expected symbolic link or nothing at '%s', found: fMode=%#x", szMapPath, ObjInfo.Attr.fMode); } /* * Create the symbolic link. */ rc = RTSymlinkCreate(szMapPath, szLinkTarget, RTSYMLINKTYPE_FILE, 0); if (RT_FAILURE(rc)) return RTMsgErrorRc(rc, "Failed to create UUID map symlink '%s' to '%s': %Rrc", szMapPath, szLinkTarget, rc); RTMsgInfo("UUID map: %s => %s", szMapPath, szLinkTarget); return VINF_SUCCESS; } /** * Adds a file to the cache. * * @returns IPRT status code. * @param pszSrcPath Path to the source file. * @param pszDstName The name of the destionation file (no path stuff). * @param pszExtraSuff Optional extra suffix. Mach-O dSYM hack. * @param pszDstSubDir The subdirectory to file it under. This is the * stringification of a relatively unique identifier of * the file in question. * @param pAddToUuidMap Optional file UUID that is used to create a UUID map * entry. * @param pszUuidMapDir The UUID map subdirectory in the cache, if this is * wanted, otherwise NULL. * @param pCfg The configuration. */ static int rtDbgSymCacheAddOneFile(const char *pszSrcPath, const char *pszDstName, const char *pszExtraStuff, const char *pszDstSubDir, PRTUUID pAddToUuidMap, const char *pszUuidMapDir, PCRTDBGSYMCACHEADDCFG pCfg) { /* * Build and create the destination path, step by step. */ char szDstPath[RTPATH_MAX]; int rc = RTPathJoin(szDstPath, sizeof(szDstPath), pCfg->pszCache, pszDstName); if (RT_FAILURE(rc)) return RTMsgErrorRc(rc, "Error constructing cache path for '%s': %Rrc", pszSrcPath, rc); if (!RTDirExists(szDstPath)) { rc = RTDirCreate(szDstPath, 0755, RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL); if (RT_FAILURE(rc)) return RTMsgErrorRc(rc, "Error creating '%s': %Rrc", szDstPath, rc); } rc = RTPathAppend(szDstPath, sizeof(szDstPath), pszDstSubDir); if (RT_FAILURE(rc)) return RTMsgErrorRc(rc, "Error constructing cache path for '%s': %Rrc", pszSrcPath, rc); if (!RTDirExists(szDstPath)) { rc = RTDirCreate(szDstPath, 0755, RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL); if (RT_FAILURE(rc)) return RTMsgErrorRc(rc, "Error creating '%s': %Rrc", szDstPath, rc); } rc = RTPathAppend(szDstPath, sizeof(szDstPath), pszDstName); if (RT_FAILURE(rc)) return RTMsgErrorRc(rc, "Error constructing cache path for '%s': %Rrc", pszSrcPath, rc); if (pszExtraStuff) { rc = RTStrCat(szDstPath, sizeof(szDstPath), pszExtraStuff); if (RT_FAILURE(rc)) return RTMsgErrorRc(rc, "Error constructing cache path for '%s': %Rrc", pszSrcPath, rc); } /* * If the file exists, we compare the two and throws an error if the doesn't match. */ if (RTPathExists(szDstPath)) { rc = RTFileCompare(pszSrcPath, szDstPath); if (RT_SUCCESS(rc)) { RTMsgInfo("%s is already in the cache.", pszSrcPath); if (pAddToUuidMap && pszUuidMapDir) return rtDbgSymCacheAddCreateUuidMapping(szDstPath, pAddToUuidMap, pszUuidMapDir, pCfg); return VINF_SUCCESS; } if (rc == VERR_NOT_EQUAL) RTMsgInfo("Cache conflict with existing entry '%s' when inserting '%s'.", szDstPath, pszSrcPath); else RTMsgInfo("Error comparing '%s' with '%s': %Rrc", pszSrcPath, szDstPath, rc); if (!pCfg->fOverwriteOnConflict) return rc; } /* * The file doesn't exist or we should overwrite it, */ RTMsgInfo("Copying '%s' to '%s'...", pszSrcPath, szDstPath); rc = RTFileCopy(pszSrcPath, szDstPath); if (RT_FAILURE(rc)) return RTMsgErrorRc(rc, "Error copying '%s' to '%s': %Rrc", pszSrcPath, szDstPath, rc); if (pAddToUuidMap && pszUuidMapDir) return rtDbgSymCacheAddCreateUuidMapping(szDstPath, pAddToUuidMap, pszUuidMapDir, pCfg); return VINF_SUCCESS; } /** * Worker that add the image file to the right place. * * @returns IPRT status code. * @param pszPath Path to the image file. * @param pCfg Configuration data. * @param hLdrMod Image handle. * @param pszExtraSuff Optional extra suffix. Mach-O dSYM hack. * @param pszUuidMapDir Optional UUID map cache directory if the image * should be mapped by UUID. * The map is a Mac OS X debug feature supported by * the two native debuggers gdb and lldb. Look for * descriptions of DBGFileMappedPaths in the * com.apple.DebugSymbols in the user defaults. */ static int rtDbgSymCacheAddImageFileWorker(const char *pszPath, PCRTDBGSYMCACHEADDCFG pCfg, RTLDRMOD hLdrMod, const char *pszExtrSuff, const char *pszUuidMapDir) { /* * Determine which subdirectory to put the files in. */ RTUUID Uuid; PRTUUID pUuid = NULL; int rc; char szSubDir[48]; RTLDRFMT enmFmt = RTLdrGetFormat(hLdrMod); switch (enmFmt) { case RTLDRFMT_MACHO: { rc = RTLdrQueryProp(hLdrMod, RTLDRPROP_UUID, &Uuid, sizeof(Uuid)); if (RT_FAILURE(rc)) return RTMsgErrorRc(rc, "Error quering image UUID from image '%s': %Rrc", pszPath, rc); rc = RTUuidToStr(&Uuid, szSubDir, sizeof(szSubDir)); if (RT_FAILURE(rc)) return RTMsgErrorRc(rc, "Error convering UUID for image '%s' to string: %Rrc", pszPath, rc); pUuid = &Uuid; break; } case RTLDRFMT_PE: { uint32_t uTimestamp; rc = RTLdrQueryProp(hLdrMod, RTLDRPROP_TIMESTAMP_SECONDS, &uTimestamp, sizeof(uTimestamp)); if (RT_FAILURE(rc)) return RTMsgErrorRc(rc, "Error quering timestamp from image '%s': %Rrc", pszPath, rc); size_t cbImage = RTLdrSize(hLdrMod); if (cbImage == ~(size_t)0) return RTMsgErrorRc(rc, "Error quering size of image '%s': %Rrc", pszPath, rc); RTStrPrintf(szSubDir, sizeof(szSubDir), "%08X%x", uTimestamp, cbImage); break; } case RTLDRFMT_AOUT: return RTMsgErrorRc(VERR_NOT_SUPPORTED, "Caching of a.out image has not yet been implemented: %s", pszPath); case RTLDRFMT_ELF: return RTMsgErrorRc(VERR_NOT_SUPPORTED, "Caching of ELF image has not yet been implemented: %s", pszPath); case RTLDRFMT_LX: return RTMsgErrorRc(VERR_NOT_SUPPORTED, "Caching of LX image has not yet been implemented: %s", pszPath); default: return RTMsgErrorRc(VERR_NOT_SUPPORTED, "Unknown loader format for '%s': %d", pszPath, enmFmt); } /* * Now add it. */ return rtDbgSymCacheAddOneFile(pszPath, RTPathFilename(pszPath), pszExtrSuff, szSubDir, pUuid, pszUuidMapDir, pCfg); } /** * Adds what we think is an image file to the cache. * * @returns IPRT status code. * @param pszPath Path to the image file. * @param pszExtraSuff Optional extra suffix. Mach-O dSYM hack. * @param pszUuidMapDir The UUID map subdirectory in the cache, if this is * wanted, otherwise NULL. * @param pCfg Configuration data. */ static int rtDbgSymCacheAddImageFile(const char *pszPath, const char *pszExtraSuff, const char *pszUuidMapDir, PCRTDBGSYMCACHEADDCFG pCfg) { /* * Use the loader to open the alleged image file. We need to open it with * arch set to amd64 and x86_32 in order to handle FAT images from the mac * guys (we should actually enumerate archs, but that's currently not * implemented nor necessary for our current use). */ /* Open it as AMD64. */ RTLDRMOD hLdrMod64; int rc = RTLdrOpen(pszPath, RTLDR_O_FOR_DEBUG, RTLDRARCH_AMD64, &hLdrMod64); if (RT_FAILURE(rc)) { if (rc != VERR_LDR_ARCH_MISMATCH) { if (rc != VERR_INVALID_EXE_SIGNATURE) return RTMsgErrorRc(rc, "RTLdrOpen failed opening '%s' [arch=amd64]: %Rrc", pszPath, rc); RTMsgInfo("Skipping '%s', no a recognizable image file...", pszPath); return VINF_SUCCESS; } hLdrMod64 = NIL_RTLDRMOD; } /* Open it as X86. */ RTLDRMOD hLdrMod32; rc = RTLdrOpen(pszPath, RTLDR_O_FOR_DEBUG, RTLDRARCH_X86_32, &hLdrMod32); if (RT_FAILURE(rc)) { if (rc != VERR_LDR_ARCH_MISMATCH) { RTLdrClose(hLdrMod32); return RTMsgErrorRc(rc, "RTLdrOpen failed opening '%s' [arch=x86]: %Rrc", pszPath, rc); } hLdrMod32 = NIL_RTLDRMOD; } /* * Add the file. */ if (hLdrMod32 == NIL_RTLDRMOD) rc = rtDbgSymCacheAddImageFileWorker(pszPath, pCfg, hLdrMod64, pszExtraSuff, pszUuidMapDir); else if (hLdrMod64 == NIL_RTLDRMOD) rc = rtDbgSymCacheAddImageFileWorker(pszPath, pCfg, hLdrMod32, pszExtraSuff, pszUuidMapDir); else { /* * Do we need to add it once or twice? */ RTLDRFMT enmFmt = RTLdrGetFormat(hLdrMod32); bool fSame = enmFmt == RTLdrGetFormat(hLdrMod64); if (fSame && enmFmt == RTLDRFMT_MACHO) { RTUUID Uuid32, Uuid64; int rc32 = RTLdrQueryProp(hLdrMod32, RTLDRPROP_UUID, &Uuid32, sizeof(Uuid32)); int rc64 = RTLdrQueryProp(hLdrMod64, RTLDRPROP_UUID, &Uuid64, sizeof(Uuid64)); fSame = RT_SUCCESS(rc32) == RT_SUCCESS(rc64); if (fSame && RT_SUCCESS(rc32)) fSame = RTUuidCompare(&Uuid32, &Uuid64) == 0; } else if (fSame && enmFmt == RTLDRFMT_PE) { fSame = RTLdrSize(hLdrMod32) == RTLdrSize(hLdrMod64); if (fSame) { uint32_t uTimestamp32, uTimestamp64; int rc32 = RTLdrQueryProp(hLdrMod32, RTLDRPROP_TIMESTAMP_SECONDS, &uTimestamp32, sizeof(uTimestamp32)); int rc64 = RTLdrQueryProp(hLdrMod64, RTLDRPROP_TIMESTAMP_SECONDS, &uTimestamp64, sizeof(uTimestamp64)); fSame = RT_SUCCESS(rc32) == RT_SUCCESS(rc64); if (fSame && RT_SUCCESS(rc32)) fSame = uTimestamp32 == uTimestamp64; } } rc = rtDbgSymCacheAddImageFileWorker(pszPath, pCfg, hLdrMod64, pszExtraSuff, pszUuidMapDir); if (!fSame) { /** @todo should symlink or hardlink this second copy. */ int rc2 = rtDbgSymCacheAddImageFileWorker(pszPath, pCfg, hLdrMod32, pszExtraSuff, pszUuidMapDir); if (RT_FAILURE(rc2) && RT_SUCCESS(rc)) rc = rc2; } } RTLdrClose(hLdrMod32); RTLdrClose(hLdrMod64); return VINF_SUCCESS; } /** * Worker for rtDbgSymCacheAddDebugFile that adds a Mach-O debug file to the * cache. * * @returns IPRT status code * @param pszPath The path to the PDB file. * @param pCfg The configuration. * @param hFile Handle to the file. */ static int rtDbgSymCacheAddDebugMachO(const char *pszPath, PCRTDBGSYMCACHEADDCFG pCfg) { /* This shouldn't happen, figure out what to do if it does. */ RT_NOREF_PV(pCfg); return RTMsgErrorRc(VERR_NOT_IMPLEMENTED, "'%s' is an OS X image file, did you point me to a file inside a .dSYM or .sym file?", pszPath); } /** * Worker for rtDbgSymCacheAddDebugFile that adds PDBs to the cace. * * @returns IPRT status code * @param pszPath The path to the PDB file. * @param pCfg The configuration. * @param hFile Handle to the file. */ static int rtDbgSymCacheAddDebugPdb(const char *pszPath, PCRTDBGSYMCACHEADDCFG pCfg, RTFILE hFile) { RT_NOREF2(pCfg, hFile); return RTMsgErrorRc(VERR_NOT_IMPLEMENTED, "PDB support not implemented: '%s'", pszPath); } /** * Adds a debug file to the cache. * * @returns IPRT status code * @param pszPath The path to the debug file in question. * @param pCfg The configuration. */ static int rtDbgSymCacheAddDebugFile(const char *pszPath, PCRTDBGSYMCACHEADDCFG pCfg) { /* * Need to extract an identifier of sorts here in order to put them in * the right place in the cache. Currently only implemnted for Mach-O * files since these use executable containers. * * We take a look at the file header in hope to figure out what to do * with the file. */ RTFILE hFile; int rc = RTFileOpen(&hFile, pszPath, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE); if (RT_FAILURE(rc)) return RTMsgErrorRc(rc, "Error opening '%s': %Rrc", pszPath, rc); union { uint64_t au64[16]; uint32_t au32[16]; uint16_t au16[32]; uint8_t ab[64]; } uBuf; rc = RTFileRead(hFile, &uBuf, sizeof(uBuf), NULL); if (RT_SUCCESS(rc)) { /* * Look for magics and call workers. */ if (!memcmp(uBuf.ab, RT_STR_TUPLE("Microsoft C/C++ MSF 7.00"))) rc = rtDbgSymCacheAddDebugPdb(pszPath, pCfg, hFile); else if ( uBuf.au32[0] == IMAGE_FAT_SIGNATURE || uBuf.au32[0] == IMAGE_FAT_SIGNATURE_OE || uBuf.au32[0] == IMAGE_MACHO32_SIGNATURE || uBuf.au32[0] == IMAGE_MACHO64_SIGNATURE || uBuf.au32[0] == IMAGE_MACHO32_SIGNATURE_OE || uBuf.au32[0] == IMAGE_MACHO64_SIGNATURE_OE) rc = rtDbgSymCacheAddDebugMachO(pszPath, pCfg); else rc = RTMsgErrorRc(VERR_INVALID_MAGIC, "Unsupported debug file '%s' magic: %#010x", pszPath, uBuf.au32[0]); } else rc = RTMsgErrorRc(rc, "Error reading '%s': %Rrc", pszPath, rc); /* close the file. */ int rc2 = RTFileClose(hFile); if (RT_FAILURE(rc2)) { RTMsgError("Error closing '%s': %Rrc", pszPath, rc2); if (RT_SUCCESS(rc)) rc = rc2; } return rc; } /** * Constructs the path to the file instide the bundle that we're keen on. * * @returns IPRT status code. * @param pszPath Path to the bundle on input, on successful * return it's the path to the desired file. This * a RTPATH_MAX size buffer. * @param cchPath The length of the path up to the bundle name. * @param cchName The length of the bundle name. * @param pszSubDir The bundle subdirectory the file lives in. * @param papszSuffixes Pointer to an array of bundle suffixes. */ static int rtDbgSymCacheConstructBundlePath(char *pszPath, size_t cchPath, size_t cchName, const char *pszSubDir, const char * const *papszSuffixes) { /* * Calc the name without the bundle extension. */ size_t const cchOrgName = cchName; const char *pszEnd = &pszPath[cchPath + cchName]; for (unsigned i = 0; papszSuffixes[i]; i++) { Assert(papszSuffixes[i][0] == '.'); size_t cchSuff = strlen(papszSuffixes[i]); if ( cchSuff < cchName && !memcmp(&pszEnd[-(ssize_t)cchSuff], papszSuffixes[i], cchSuff)) { cchName -= cchSuff; break; } } /* * Check the immediate directory first, in case it's layed out like * IOPCIFamily.kext. */ int rc = RTPathAppendEx(pszPath, RTPATH_MAX, &pszPath[cchPath], cchName); if (RT_FAILURE(rc) || !RTFileExists(pszPath)) { /* * Not there, ok then try the given subdirectory + name. */ pszPath[cchPath + cchOrgName] = '\0'; rc = RTPathAppend(pszPath, RTPATH_MAX, pszSubDir); if (RT_SUCCESS(rc)) rc = RTPathAppendEx(pszPath, RTPATH_MAX, &pszPath[cchPath], cchName); if (RT_FAILURE(rc)) { pszPath[cchPath + cchOrgName] = '\0'; return RTMsgErrorRc(rc, "Error constructing image bundle path for '%s': %Rrc", pszPath, rc); } } return VINF_SUCCESS; } /** * Adds a image bundle of some sort. * * @returns IPRT status code. * @param pszPath Path to the bundle. This a RTPATH_MAX size * buffer that we can write to when creating the * path to the file inside the bundle that we're * interested in. * @param cchPath The length of the path up to the bundle name. * @param cchName The length of the bundle name. * @param pDirEntry The directory entry buffer, for handling bundle * within bundle recursion. * @param pCfg The configuration. */ static int rtDbgSymCacheAddImageBundle(char *pszPath, size_t cchPath, size_t cchName, PRTDIRENTRYEX pDirEntry, PCRTDBGSYMCACHEADDCFG pCfg) { /* * Assuming these are kexts or simple applications, we only add the image * file itself to the cache. No Info.plist or other files. */ /** @todo consider looking for Frameworks and handling framework bundles. */ int rc = rtDbgSymCacheConstructBundlePath(pszPath, cchPath, cchName, "Contents/MacOS/", g_apszBundleSuffixes); if (RT_SUCCESS(rc)) rc = rtDbgSymCacheAddImageFile(pszPath, NULL, RTDBG_CACHE_UUID_MAP_DIR_IMAGES, pCfg); /* * Look for plugins and other sub-bundles. */ if (pCfg->fRecursive) { static char const * const s_apszSubBundleDirs[] = { "Contents/Plugins/", /** @todo Frameworks ++ */ }; for (uint32_t i = 0; i < RT_ELEMENTS(s_apszSubBundleDirs); i++) { pszPath[cchPath + cchName] = '\0'; int rc2 = RTPathAppend(pszPath, RTPATH_MAX - 1, s_apszSubBundleDirs[i]); if (RT_SUCCESS(rc2)) { if (RTDirExists(pszPath)) { size_t cchPath2 = strlen(pszPath); if (!RTPATH_IS_SLASH(pszPath[cchPath2 - 1])) { pszPath[cchPath2++] = RTPATH_SLASH; pszPath[cchPath2] = '\0'; } rc2 = rtDbgSymCacheAddDirWorker(pszPath, cchPath2, pDirEntry, pCfg); } } else { pszPath[cchPath + cchName] = '\0'; RTMsgError("Error constructing bundle subdir path for '%s' + '%s': %Rrc", pszPath, s_apszSubBundleDirs[i], rc); } if (RT_FAILURE(rc2) && RT_SUCCESS(rc)) rc = rc2; } } return rc; } /** * Adds a debug bundle. * * @returns IPRT status code. * @param pszPath Path to the bundle. This a RTPATH_MAX size * buffer that we can write to when creating the * path to the file inside the bundle that we're * interested in. * @param cchPath The length of the path up to the bundle name. * @param cchName The length of the bundle name. * @param pCfg The configuration. */ static int rtDbgSymCacheAddDebugBundle(char *pszPath, size_t cchPath, size_t cchName, PCRTDBGSYMCACHEADDCFG pCfg) { /* * The current policy is not to add the whole .dSYM (or .sym) bundle, but * rather just the dwarf image instide it. The .plist and Info.plist * files generally doesn't contain much extra information that's really * necessary, I hope. At least this is what the uuidmap example in the * lldb hints at (it links to the dwarf file, not the .dSYM dir). * * To avoid confusion with a .dSYM bundle, as well as collision with the * image file, we use .dwarf suffix for the file. * * For details on the uuid map see rtDbgSymCacheAddImageFile as well as * http://lldb.llvm.org/symbols.html . * * ASSUMES bundles contains Mach-O DWARF files. */ int rc = rtDbgSymCacheConstructBundlePath(pszPath, cchPath, cchName, "Contents/Resources/DWARF/", g_apszDSymBundleSuffixes); if (RT_SUCCESS(rc)) rc = rtDbgSymCacheAddImageFile(pszPath, RTDBG_CACHE_DSYM_FILE_SUFFIX, RTDBG_CACHE_UUID_MAP_DIR_DSYMS, pCfg); return rc; } /** * Figure the type of a file/dir based on path and FS object info. * * @returns The type. * @param pszPath The path to the file/dir. * @param pObjInfo The object information, symlinks followed. */ static RTDBGSYMCACHEFILETYPE rtDbgSymCacheFigureType2(const char *pszPath, PCRTFSOBJINFO pObjInfo) { const char *pszName = RTPathFilename(pszPath); const char *pszExt = RTPathSuffix(pszName); if (pszExt) pszExt++; else pszExt = ""; if ( RTFS_IS_DIRECTORY(pObjInfo->Attr.fMode) || (pObjInfo->Attr.fMode & RTFS_DOS_DIRECTORY)) /** @todo OS X samba reports reparse points in /Volumes/ that we cannot resolve. */ { /* Skip directories shouldn't bother with. */ if ( !RTStrICmp(pszName, ".Trashes") || !RTStrICmp(pszName, ".$RESCYCLE.BIN") || !RTStrICmp(pszName, "System.kext") /* Usually only plugins here, so skip it. */ ) return RTDBGSYMCACHEFILETYPE_IGNORE; /* Directories can also be bundles on the mac. */ if (!RTStrICmp(pszExt, "dSYM")) return RTDBGSYMCACHEFILETYPE_DEBUG_BUNDLE; for (unsigned i = 0; i < RT_ELEMENTS(g_apszBundleSuffixes) - 1; i++) if (!RTStrICmp(pszExt, &g_apszBundleSuffixes[i][1])) return RTDBGSYMCACHEFILETYPE_IMAGE_BUNDLE; return RTDBGSYMCACHEFILETYPE_DIR; } if (!RTFS_IS_FILE(pObjInfo->Attr.fMode)) return RTDBGSYMCACHEFILETYPE_INVALID; /* Select image vs debug info based on extension. */ if ( !RTStrICmp(pszExt, "pdb") || !RTStrICmp(pszExt, "dbg") || !RTStrICmp(pszExt, "sym") || !RTStrICmp(pszExt, "dwo") || !RTStrICmp(pszExt, "dwp") || !RTStrICmp(pszExt, "debug") || !RTStrICmp(pszExt, "dsym") || !RTStrICmp(pszExt, "dwarf") || !RTStrICmp(pszExt, "map") || !RTStrICmp(pszExt, "cv")) return RTDBGSYMCACHEFILETYPE_DEBUG_FILE; /* Filter out a bunch of files which obviously shouldn't be images. */ if ( !RTStrICmp(pszExt, "txt") || !RTStrICmp(pszExt, "html") || !RTStrICmp(pszExt, "htm") || !RTStrICmp(pszExt, "rtf") || !RTStrICmp(pszExt, "zip") || !RTStrICmp(pszExt, "doc") || !RTStrICmp(pszExt, "gz") || !RTStrICmp(pszExt, "bz2") || !RTStrICmp(pszExt, "xz") || !RTStrICmp(pszExt, "kmk") || !RTStrICmp(pszExt, "c") || !RTStrICmp(pszExt, "cpp") || !RTStrICmp(pszExt, "h") || !RTStrICmp(pszExt, "m") || !RTStrICmp(pszExt, "mm") || !RTStrICmp(pszExt, "asm") || !RTStrICmp(pszExt, "S") || !RTStrICmp(pszExt, "inc") || !RTStrICmp(pszExt, "sh") ) return RTDBGSYMCACHEFILETYPE_IGNORE; if ( !RTStrICmp(pszName, "Makefile") || !RTStrICmp(pszName, "GNUmakefile") || !RTStrICmp(pszName, "createsymbolfiles") || !RTStrICmp(pszName, "kgmacros") ) return RTDBGSYMCACHEFILETYPE_IGNORE; return RTDBGSYMCACHEFILETYPE_IMAGE_FILE; } /** * Figure file type based on name, will stat the file/dir. * * @returns File type. * @param pszPath The path to the file/dir to figure. */ static RTDBGSYMCACHEFILETYPE rtDbgSymCacheFigureType(const char *pszPath) { const char *pszName = RTPathFilename(pszPath); /* Trailing slash. */ if (!pszName) return RTDBGSYMCACHEFILETYPE_DIR; /* Wildcard means listing directory and filtering. */ if (strpbrk(pszName, "?*")) return RTDBGSYMCACHEFILETYPE_DIR_FILTER; /* Get object info, following links. */ RTFSOBJINFO ObjInfo; int rc = RTPathQueryInfoEx(pszPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK); if (RT_FAILURE(rc)) return RTDBGSYMCACHEFILETYPE_INVALID; return rtDbgSymCacheFigureType2(pszPath, &ObjInfo); } /** * Recursive worker for rtDbgSymCacheAddDir, for minimal stack wasting. * * @returns IPRT status code (fully bitched). * @param pszPath Pointer to a RTPATH_MAX size buffer containing * the path to the current directory ending with a * slash. * @param cchPath The size of the current directory path. * @param pDirEntry Pointer to the RTDIRENTRYEX structure to use. * @param pCfg The configuration. */ static int rtDbgSymCacheAddDirWorker(char *pszPath, size_t cchPath, PRTDIRENTRYEX pDirEntry, PCRTDBGSYMCACHEADDCFG pCfg) { /* * Open the directory. */ RTDIR hDir; int rc, rc2; if (pCfg->pszFilter) { rc = RTStrCopy(&pszPath[cchPath], RTPATH_MAX - cchPath, pCfg->pszFilter); if (RT_FAILURE(rc)) { pszPath[cchPath] = '\0'; return RTMsgErrorRc(rc, "Filename too long (%Rrc): '%s" RTPATH_SLASH_STR "%s'", rc, pszPath, pCfg->pszFilter); } rc = RTDirOpenFiltered(&hDir, pszPath, RTDIRFILTER_WINNT, 0 /*fFlags*/); } else rc = RTDirOpen(&hDir, pszPath); if (RT_FAILURE(rc)) return RTMsgErrorRc(rc, "RTDirOpen%s failed on '%s': %Rrc", pCfg->pszFilter ? "Filtered" : "", pszPath, rc); /* * Enumerate the files. */ for (;;) { rc2 = RTDirReadEx(hDir, pDirEntry, NULL, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK); if (RT_FAILURE(rc2)) { pszPath[cchPath] = '\0'; if (rc2 != VERR_NO_MORE_FILES) { RTMsgError("RTDirReadEx failed in '%s': %Rrc\n", pszPath, rc2); rc = rc2; } break; } /* Skip dot and dot-dot. */ if (RTDirEntryExIsStdDotLink(pDirEntry)) continue; /* Construct a full path. */ rc = RTStrCopy(&pszPath[cchPath], RTPATH_MAX, pDirEntry->szName); if (RT_FAILURE(rc)) { pszPath[cchPath] = '\0'; RTMsgError("File name too long in '%s': '%s' (%Rrc)", pszPath, pDirEntry->szName, rc); break; } switch (rtDbgSymCacheFigureType2(pszPath, &pDirEntry->Info)) { case RTDBGSYMCACHEFILETYPE_DIR: if (!pCfg->fRecursive) RTMsgInfo("Skipping directory '%s'...", pszPath); else { if (cchPath + pDirEntry->cbName + 3 <= RTPATH_MAX) { pszPath[cchPath + pDirEntry->cbName] = RTPATH_SLASH; pszPath[cchPath + pDirEntry->cbName + 1] = '\0'; rc2 = rtDbgSymCacheAddDirWorker(pszPath, cchPath + pDirEntry->cbName + 1, pDirEntry, pCfg); } else { RTMsgError("File name too long in '%s': '%s' (%Rrc)", pszPath, pDirEntry->szName, rc); rc2 = VERR_FILENAME_TOO_LONG; } } break; case RTDBGSYMCACHEFILETYPE_DEBUG_FILE: rc2 = rtDbgSymCacheAddDebugFile(pszPath, pCfg); break; case RTDBGSYMCACHEFILETYPE_IMAGE_FILE: rc2 = rtDbgSymCacheAddImageFile(pszPath, NULL /*pszExtraSuff*/, RTDBG_CACHE_UUID_MAP_DIR_IMAGES, pCfg); break; case RTDBGSYMCACHEFILETYPE_DEBUG_BUNDLE: rc2 = rtDbgSymCacheAddDebugBundle(pszPath, cchPath, pDirEntry->cbName, pCfg); break; case RTDBGSYMCACHEFILETYPE_IMAGE_BUNDLE: rc2 = rtDbgSymCacheAddImageBundle(pszPath, cchPath, pDirEntry->cbName, pDirEntry, pCfg); break; case RTDBGSYMCACHEFILETYPE_DIR_FILTER: case RTDBGSYMCACHEFILETYPE_INVALID: rc2 = RTMsgErrorRc(VERR_INTERNAL_ERROR_2, "Invalid: '%s'", pszPath); break; case RTDBGSYMCACHEFILETYPE_IGNORE: rc2 = VINF_SUCCESS; break; } if (RT_FAILURE(rc2) && RT_SUCCESS(rc)) rc = rc2; } /* * Clean up. */ rc2 = RTDirClose(hDir); if (RT_FAILURE(rc2)) { RTMsgError("RTDirClose failed in '%s': %Rrc", pszPath, rc); rc = rc2; } return rc; } /** * Adds a directory. * * @returns IPRT status code (fully bitched). * @param pszPath The directory path. * @param pCfg The configuration. */ static int rtDbgSymCacheAddDir(const char *pszPath, PCRTDBGSYMCACHEADDCFG pCfg) { /* * Set up the path buffer, stripping any filter. */ char szPath[RTPATH_MAX]; int rc = RTStrCopy(szPath, sizeof(szPath) - 2, pszPath); if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "Path too long: '%s'", pszPath); size_t cchPath = strlen(pszPath); if (!cchPath) return RTMsgErrorExit(RTEXITCODE_FAILURE, "Path empty: '%s'", pszPath); if (pCfg->pszFilter) szPath[cchPath - strlen(pCfg->pszFilter)] = '\0'; cchPath = RTPathStripTrailingSlash(szPath); if (!RTPATH_IS_SEP(pszPath[cchPath - 1])) { szPath[cchPath++] = RTPATH_SLASH; szPath[cchPath] = '\0'; } /* * Let the worker do the rest. */ RTDIRENTRYEX DirEntry; return rtDbgSymCacheAddDirWorker(szPath, cchPath, &DirEntry, pCfg); } /** * Adds a file or directory. * * @returns Program exit code. * @param pszPath The user supplied path to the file or directory. * @param pszCache The path to the cache. * @param fRecursive Whether to process directories recursively. * @param fOverwriteOnConflict Whether to overwrite existing cache entry on * conflict, or just leave it. */ static RTEXITCODE rtDbgSymCacheAddFileOrDir(const char *pszPath, const char *pszCache, bool fRecursive, bool fOverwriteOnConflict) { RT_NOREF1(fOverwriteOnConflict); RTDBGSYMCACHEADDCFG Cfg; Cfg.fRecursive = fRecursive; Cfg.pszCache = pszCache; Cfg.pszFilter = NULL; int rc; RTDBGSYMCACHEFILETYPE enmType = rtDbgSymCacheFigureType(pszPath); switch (enmType) { default: case RTDBGSYMCACHEFILETYPE_INVALID: return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid: '%s'", pszPath); case RTDBGSYMCACHEFILETYPE_DIR_FILTER: Cfg.pszFilter = RTPathFilename(pszPath); RT_FALL_THRU(); case RTDBGSYMCACHEFILETYPE_DIR: rc = rtDbgSymCacheAddDir(pszPath, &Cfg); break; case RTDBGSYMCACHEFILETYPE_DEBUG_FILE: rc = rtDbgSymCacheAddDebugFile(pszPath, &Cfg); break; case RTDBGSYMCACHEFILETYPE_IMAGE_FILE: rc = rtDbgSymCacheAddImageFile(pszPath, NULL /*pszExtraSuff*/, RTDBG_CACHE_UUID_MAP_DIR_IMAGES, &Cfg); break; case RTDBGSYMCACHEFILETYPE_DEBUG_BUNDLE: case RTDBGSYMCACHEFILETYPE_IMAGE_BUNDLE: { size_t cchPath = strlen(pszPath); size_t cchFilename = strlen(RTPathFilename(pszPath)); char szPathBuf[RTPATH_MAX]; if (cchPath < sizeof(szPathBuf)) { memcpy(szPathBuf, pszPath, cchPath + 1); if (enmType == RTDBGSYMCACHEFILETYPE_DEBUG_BUNDLE) rc = rtDbgSymCacheAddDebugBundle(szPathBuf, cchPath - cchFilename, cchFilename, &Cfg); else { RTDIRENTRYEX DirEntry; rc = rtDbgSymCacheAddImageBundle(szPathBuf, cchPath - cchFilename, cchFilename, &DirEntry, &Cfg); } } else rc = RTMsgErrorRc(VERR_FILENAME_TOO_LONG, "Filename too long: '%s'", pszPath); break; } case RTDBGSYMCACHEFILETYPE_IGNORE: rc = RTMsgErrorRc(VERR_INVALID_PARAMETER, "Invalid file: '%s'", pszPath); break; } return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; } /** * Handles the 'add' command. * * @returns Program exit code. * @param pszArg0 The program name. * @param cArgs The number of arguments to the 'add' command. * @param papszArgs The argument vector, starting after 'add'. */ static RTEXITCODE rtDbgSymCacheCmdAdd(const char *pszArg0, int cArgs, char **papszArgs) { /* * Parse the command line. */ static RTGETOPTDEF const s_aOptions[] = { { "--recursive", 'R', RTGETOPT_REQ_NOTHING }, { "--no-recursive", 'n', RTGETOPT_REQ_NOTHING }, { "--overwrite-on-conflict", 'o', RTGETOPT_REQ_NOTHING }, }; const char *pszCache = NULL; bool fRecursive = false; bool fOverwriteOnConflict = false; RTGETOPTSTATE State; int rc = RTGetOptInit(&State, cArgs, papszArgs, &s_aOptions[0], RT_ELEMENTS(s_aOptions), 0, RTGETOPTINIT_FLAGS_OPTS_FIRST); if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc", rc); //uint32_t cAdded = 0; RTGETOPTUNION ValueUnion; int chOpt; while ((chOpt = RTGetOpt(&State, &ValueUnion)) != 0) { switch (chOpt) { case 'R': fRecursive = true; break; case 'n': fRecursive = false; break; case 'o': fOverwriteOnConflict = true; break; case VINF_GETOPT_NOT_OPTION: /* The first non-option is a cache directory. */ if (!pszCache) { pszCache = ValueUnion.psz; if (!RTPathExists(pszCache)) { rc = RTDirCreate(pszCache, 0755, RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL); if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Error creating cache directory '%s': %Rrc", pszCache, rc); } else if (!RTDirExists(pszCache)) return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Specified cache directory is not a directory: '%s'", pszCache); } /* Subsequent non-options are files to be added to the cache. */ else { RTEXITCODE rcExit = rtDbgSymCacheAddFileOrDir(ValueUnion.psz, pszCache, fRecursive, fOverwriteOnConflict); if (rcExit != RTEXITCODE_FAILURE) return rcExit; } break; case 'h': return rtDbgSymCacheUsage(pszArg0, "add"); case 'V': return rtDbgSymCacheVersion(); default: return RTGetOptPrintError(chOpt, &ValueUnion); } } if (!pszCache) return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No cache directory or files to add were specified."); return RTEXITCODE_SUCCESS; } int main(int argc, char **argv) { int rc = RTR3InitExe(argc, &argv, 0); if (RT_FAILURE(rc)) return RTMsgInitFailure(rc); /* * Switch on the command. */ RTEXITCODE rcExit = RTEXITCODE_SYNTAX; if (argc < 2) rtDbgSymCacheUsage(argv[0], NULL); else if (!strcmp(argv[1], "add")) rcExit = rtDbgSymCacheCmdAdd(argv[0], argc - 2, argv + 2); else if ( !strcmp(argv[1], "-h") || !strcmp(argv[1], "-?") || !strcmp(argv[1], "--help")) rcExit = rtDbgSymCacheUsage(argv[0], NULL); else if ( !strcmp(argv[1], "-V") || !strcmp(argv[1], "--version")) rcExit = rtDbgSymCacheVersion(); else RTMsgError("Unknown command: '%s'", argv[1]); return rcExit; }