/* $Id: RTDbgSymCache.cpp $ */ /** @file * IPRT - Debug Symbol Cache Utility. */ /* * Copyright (C) 2013-2023 Oracle and/or its affiliates. * * This file is part of VirtualBox base platform packages, as * available from https://www.virtualbox.org. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation, in version 3 of the * License. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * * The contents of this file may alternatively be used under the terms * of the Common Development and Distribution License Version 1.0 * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included * in the VirtualBox 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. * * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 */ /********************************************************************************************************************************* * 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); /********************************************************************************************************************************* * Global Variables * *********************************************************************************************************************************/ /** Verbositity level. */ static uint32_t g_iLogLevel = 99; /** * 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" "\n" "Options:\n" " -R, --recursive\n" " Process directory arguments recursively.\n" " -n, --no-recursive\n" " No recursion. (default)\n" " -o, --overwrite-on-conflict\n" " Overwrite existing cache entry.\n" , RTPathFilename(pszArg0)); if (!pszCommand || !strcmp(pszCommand, "get")) RTPrintf("Usage: %s get [--output|-o ]\n" "\n" "Query Options:\n" " --for-exe[cutable] \n" " Get debug file for the given executable.\n" " --dwo, --dwarf, --dwarf-external\n" " Get external DWARF debug file. Needs --name and --dwo-crc32.\n" " --dsym\n" " Get DWARF debug file from .dSYM bundle. Needs --uuid or --name.\n" " --dbg\n" " Get NT DBG debug file. Needs --name, --timestamp and --size.\n" " --pdb20\n" " Get PDB 2.0 debug file. Needs --name, --timestamp, --size\n" " and --pdb-age (if non-zero).\n" " --pdb70\n" " Get PDB 7.0 debug file. Needs --name, --uuid, and --pdb-age\n" " (if non-zero).\n" " --macho\n" " Get Mach-O image file. Needs --uuid or --name.\n" " --pe\n" " Get PE image file. Needs --name, --timestamp and --size.\n" " --timestamp, --ts, -t \n" " The timestamp (32-bit) for the file to get. Used with --dbg, --pdb20\n" " and --pe.\n" " --uuid, -u, \n" " The UUID for the file to get. Used with --dsym, --pdb70 and --macho\n" " --image-size, --size, -z \n" " The image size (32-bit) for the file to get. Used with --dbg,\n" " --pdb20, --pdb70 and --pe.\n" " --pdb-age, -a \n" " The PDB age (32-bit) for the file to get. Used with --pdb20 and --pdb70.\n" " --dwo-crc32, -c \n" " The CRC32 for the file to get. Used with --dwo.\n" " --name, -n \n" " The name (in the cache) of the file to get.\n" "\n" "Debug Cache Options:\n" " --sym-path, -s \n" " Adds the path to the debug configuration, NT style with 'srv*' and\n" " 'cache*' prefixes as well as our own 'rec*' and 'norec*' recursion\n" " prefixes.\n" " --env-prefix, -p \n" " The enviornment variable prefix, default is 'IPRT_' making the\n" " symbol path variable 'IPRT_PATH'.\n" " --use-native-paths (default), --no-native-paths\n" " Pick up native symbol paths from the environment.\n" "\n" "Output Options:\n" " --output, -o \n" " The output filename or directory. Directories must end with a\n" " path separator. The default filename that in the cache.\n" "\n" "This is handy for triggering downloading of symbol files from a server. Say\n" "you have the executable but want the corrsponding PDB or .dSYM file:\n" " %s get --for-executable VBoxRT.dll\n" " %s get --for-executable VBoxRT.dylib\n" " " , RTPathFilename(pszArg0), RTPathFilename(pszArg0), RTPathFilename(pszArg0)); return RTEXITCODE_SUCCESS; } /** * @callback_method_impl{FNRTDBGCFGLOG} */ static DECLCALLBACK(void) rtDbgSymCacheLogCallback(RTDBGCFG hDbgCfg, uint32_t iLevel, const char *pszMsg, void *pvUser) { RT_NOREF(hDbgCfg, pvUser); if (iLevel <= g_iLogLevel) { size_t cchMsg = strlen(pszMsg); if (cchMsg > 0 && pszMsg[cchMsg - 1] == '\n') RTMsgInfo("[%u] %s", iLevel, pszMsg); else if (cchMsg > 0) RTMsgInfo("[%u] %s\n", iLevel, pszMsg); } } /** * 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 pszDstName Add to the cache under this name. Typically the * filename part of @a pszPath. * @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, const char *pszDstName, 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, pszDstName, 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 pszDstName Add to the cache under this name. Typically the * filename part of @a pszPath. * @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 *pszDstName, const char *pszExtraSuff, const char *pszUuidMapDir, PCRTDBGSYMCACHEADDCFG pCfg) { RTERRINFOSTATIC ErrInfo; /* * 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 = RTLdrOpenEx(pszPath, RTLDR_O_FOR_DEBUG, RTLDRARCH_AMD64, &hLdrMod64, RTErrInfoInitStatic(&ErrInfo)); 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%s%s", pszPath, rc, RTErrInfoIsSet(&ErrInfo.Core) ? " - " : "", RTErrInfoIsSet(&ErrInfo.Core) ? ErrInfo.Core.pszMsg : ""); RTMsgInfo("Skipping '%s', no a recognizable image file...", pszPath); return VINF_SUCCESS; } hLdrMod64 = NIL_RTLDRMOD; } /* Open it as X86. */ RTLDRMOD hLdrMod32; rc = RTLdrOpenEx(pszPath, RTLDR_O_FOR_DEBUG, RTLDRARCH_X86_32, &hLdrMod32, RTErrInfoInitStatic(&ErrInfo)); if (RT_FAILURE(rc)) { if (rc != VERR_LDR_ARCH_MISMATCH) { RTLdrClose(hLdrMod64); return RTMsgErrorRc(rc, "RTLdrOpen failed opening '%s' [arch=x86]: %Rrc%s%s", pszPath, rc, RTErrInfoIsSet(&ErrInfo.Core) ? " - " : "", RTErrInfoIsSet(&ErrInfo.Core) ? ErrInfo.Core.pszMsg : ""); } hLdrMod32 = NIL_RTLDRMOD; } /* * Add the file. */ if (hLdrMod32 == NIL_RTLDRMOD) rc = rtDbgSymCacheAddImageFileWorker(pszPath, pszDstName, pCfg, hLdrMod64, pszExtraSuff, pszUuidMapDir); else if (hLdrMod64 == NIL_RTLDRMOD) rc = rtDbgSymCacheAddImageFileWorker(pszPath, pszDstName, 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, pszDstName, pCfg, hLdrMod64, pszExtraSuff, pszUuidMapDir); if (!fSame) { /** @todo should symlink or hardlink this second copy. */ int rc2 = rtDbgSymCacheAddImageFileWorker(pszPath, pszDstName, 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 pszDstName Add to the cache under this name. Typically the * filename part of @a pszPath. * @param pCfg The configuration. * @param hFile Handle to the file. */ static int rtDbgSymCacheAddDebugMachO(const char *pszPath, const char *pszDstName, PCRTDBGSYMCACHEADDCFG pCfg) { /* This shouldn't happen, figure out what to do if it does. */ RT_NOREF(pCfg, pszDstName); 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 pszDstName Add to the cache under this name. Typically the * filename part of @a pszPath. * @param pCfg The configuration. * @param hFile Handle to the file. */ static int rtDbgSymCacheAddDebugPdb(const char *pszPath, const char *pszDstName, PCRTDBGSYMCACHEADDCFG pCfg, RTFILE hFile) { RT_NOREF(pCfg, hFile, pszDstName); 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 pszDstName Add to the cache under this name. Typically the * filename part of @a pszPath. * @param pCfg The configuration. */ static int rtDbgSymCacheAddDebugFile(const char *pszPath, const char *pszDstName, 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, pszDstName, 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, pszDstName, 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, RTPATH_STR_F_STYLE_HOST); 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, RTPATH_STR_F_STYLE_HOST); 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 pszDstName Add to the cache under this name, NULL if not * specified. * @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, const char *pszDstName, 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)) { if (!pszDstName) pszDstName = RTPathFilename(pszPath); rc = rtDbgSymCacheAddImageFile(pszPath, pszDstName, 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 pszDstName Add to the cache under this name, NULL if not * specified. * @param pCfg The configuration. */ static int rtDbgSymCacheAddDebugBundle(char *pszPath, size_t cchPath, size_t cchName, const char *pszDstName, 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)) { if (!pszDstName) pszDstName = RTPathFilename(pszPath); rc = rtDbgSymCacheAddImageFile(pszPath, pszDstName, 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, pDirEntry->szName, pCfg); break; case RTDBGSYMCACHEFILETYPE_IMAGE_FILE: rc2 = rtDbgSymCacheAddImageFile(pszPath, pDirEntry->szName, NULL /*pszExtraSuff*/, RTDBG_CACHE_UUID_MAP_DIR_IMAGES, pCfg); break; case RTDBGSYMCACHEFILETYPE_DEBUG_BUNDLE: rc2 = rtDbgSymCacheAddDebugBundle(pszPath, cchPath, pDirEntry->cbName, NULL /*pszDstName*/, pCfg); break; case RTDBGSYMCACHEFILETYPE_IMAGE_BUNDLE: rc2 = rtDbgSymCacheAddImageBundle(pszPath, cchPath, pDirEntry->cbName, NULL /*pszDstName*/, 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; /* If the filename contains an equal ('=') char, treat the left as the file to add tne right part as the name to add it under (handy for kernels). */ char *pszFree = NULL; const char *pszDstName = RTPathFilename(pszPath); const char *pszEqual = pszDstName ? strchr(pszDstName, '=') : NULL; if (pszEqual) { pszPath = pszFree = RTStrDupN(pszPath, pszEqual - pszPath); if (!pszFree) return RTMsgErrorExitFailure("out of memory!\n"); pszDstName = pszEqual + 1; if (!*pszDstName) return RTMsgErrorExitFailure("add-as filename is empty!\n"); } int rc; RTDBGSYMCACHEFILETYPE enmType = rtDbgSymCacheFigureType(pszPath); switch (enmType) { default: case RTDBGSYMCACHEFILETYPE_INVALID: rc = RTMsgErrorRc(VERR_INVALID_PARAMETER, "Invalid: '%s'", pszPath); break; case RTDBGSYMCACHEFILETYPE_DIR_FILTER: Cfg.pszFilter = RTPathFilename(pszPath); RT_FALL_THRU(); case RTDBGSYMCACHEFILETYPE_DIR: if (!pszEqual) rc = rtDbgSymCacheAddDir(pszPath, &Cfg); else rc = RTMsgErrorRc(VERR_INVALID_PARAMETER, "Add-as filename is not applicable to directories!"); break; case RTDBGSYMCACHEFILETYPE_DEBUG_FILE: rc = rtDbgSymCacheAddDebugFile(pszPath, pszDstName, &Cfg); break; case RTDBGSYMCACHEFILETYPE_IMAGE_FILE: rc = rtDbgSymCacheAddImageFile(pszPath, pszDstName, 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, pszEqual ? pszDstName : NULL, &Cfg); else { RTDIRENTRYEX DirEntry; rc = rtDbgSymCacheAddImageBundle(szPathBuf, cchPath - cchFilename, cchFilename, pszEqual ? pszDstName : NULL, &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; } RTStrFree(pszFree); 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; } /** * Debug info + external path for the 'get' command. */ typedef struct MYDBGINFO { /** The kind of debug info. */ RTLDRDBGINFOTYPE enmType; /** The CRC32 of the external file (RTLDRDBGINFOTYPE_DWARF_DWO). */ uint32_t uDwoCrc32; /** The PE image size (RTLDRDBGINFOTYPE_CODEVIEW_DBG, * RTLDRDBGINFOTYPE_CODEVIEW_PDB20, RTLDRDBGINFOTYPE_CODEVIEW_PDB70 (, * RTLDRDBGINFOTYPE_CODEVIEW, RTLDRDBGINFOTYPE_COFF)). */ uint32_t cbImage; /** Timestamp in seconds since unix epoch (RTLDRDBGINFOTYPE_CODEVIEW_DBG, * RTLDRDBGINFOTYPE_CODEVIEW_PDB20 (, RTLDRDBGINFOTYPE_CODEVIEW, * RTLDRDBGINFOTYPE_COFF)). */ uint32_t uTimestamp; /** The PDB age (RTLDRDBGINFOTYPE_CODEVIEW_PDB20, RTLDRDBGINFOTYPE_CODEVIEW_PDB70). */ uint32_t uPdbAge; /** The UUID of the PDB or mach-o image (RTLDRDBGINFOTYPE_CODEVIEW_PDB70, +). */ RTUUID Uuid; /** External path (can be empty). */ char szExtFile[RTPATH_MAX]; } MYDBGINFO; /** * @callback_method_impl{FNRTLDRENUMDBG, For the 'get' command.} */ static DECLCALLBACK(int) rtDbgSymCacheCmdGetForExeDbgInfoCallback(RTLDRMOD hLdrMod, PCRTLDRDBGINFO pDbgInfo, void *pvUser) { RT_NOREF(hLdrMod); if (!pDbgInfo->pszExtFile) switch (pDbgInfo->enmType) { case RTLDRDBGINFOTYPE_CODEVIEW_PDB20: case RTLDRDBGINFOTYPE_CODEVIEW_PDB70: case RTLDRDBGINFOTYPE_CODEVIEW_DBG: break; default: return VINF_SUCCESS; } /* Copy the info: */ MYDBGINFO *pMyInfo = (MYDBGINFO *)pvUser; RT_ZERO(*pMyInfo); pMyInfo->enmType = pDbgInfo->enmType; int rc = VINF_SUCCESS; if (pDbgInfo->pszExtFile) rc = RTStrCopy(pMyInfo->szExtFile, sizeof(pMyInfo->szExtFile), pDbgInfo->pszExtFile); switch (pDbgInfo->enmType) { case RTLDRDBGINFOTYPE_DWARF_DWO: pMyInfo->uDwoCrc32 = pDbgInfo->u.Dwo.uCrc32; break; case RTLDRDBGINFOTYPE_CODEVIEW: case RTLDRDBGINFOTYPE_COFF: pMyInfo->cbImage = pDbgInfo->u.Cv.cbImage; pMyInfo->uTimestamp = pDbgInfo->u.Cv.uTimestamp; break; case RTLDRDBGINFOTYPE_CODEVIEW_DBG: pMyInfo->cbImage = pDbgInfo->u.Dbg.cbImage; pMyInfo->uTimestamp = pDbgInfo->u.Dbg.uTimestamp; break; case RTLDRDBGINFOTYPE_CODEVIEW_PDB20: pMyInfo->cbImage = pDbgInfo->u.Pdb20.cbImage; pMyInfo->uTimestamp = pDbgInfo->u.Pdb20.uTimestamp; pMyInfo->uPdbAge = pDbgInfo->u.Pdb20.uAge; break; case RTLDRDBGINFOTYPE_CODEVIEW_PDB70: pMyInfo->cbImage = pDbgInfo->u.Pdb70.cbImage; pMyInfo->Uuid = pDbgInfo->u.Pdb70.Uuid; pMyInfo->uPdbAge = pDbgInfo->u.Pdb70.uAge; break; default: return VINF_SUCCESS; } return rc; } /** * @callback_method_impl{FNRTDBGCFGOPEN} */ static DECLCALLBACK(int) rtDbgSymCacheCmdGetCallback(RTDBGCFG hDbgCfg, const char *pszFilename, void *pvUser1, void *pvUser2) { RT_NOREF(hDbgCfg, pvUser2); char *pszJoined = NULL; const char *pszOutput = (const char *)pvUser1; if (!pszOutput || *pszOutput == '\0') pszOutput = RTPathFilename(pszFilename); else if (RTPathFilename(pszOutput) == NULL) pszOutput = pszJoined = RTPathJoinA(pszOutput, RTPathFilename(pszFilename)); if (g_iLogLevel > 0) // --pe --name wintypes.dll --image-size 1388544 --timestamp 0x57F8D9F0 RTMsgInfo("Copying '%s' to '%s...", pszFilename, pszOutput); int rc = RTFileCopy(pszFilename, pszOutput); if (RT_FAILURE(rc)) { if (rc == VERR_ALREADY_EXISTS) { rc = RTFileCompare(pszFilename, pszOutput); if (RT_SUCCESS(rc)) RTMsgInfo("Output '%s' exists and matches '%s'.", pszOutput, pszFilename); else RTMsgError("Output '%s' already exists (does not match '%s')", pszOutput, pszFilename); } else RTMsgError("Copying '%s' to '%s failed: %Rrc", pszFilename, pszOutput, rc); } RTStrFree(pszJoined); if (RT_SUCCESS(rc)) return VINF_CALLBACK_RETURN; return rc; } /** * Handles the 'get' 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 rtDbgSymCacheCmdGet(const char *pszArg0, int cArgs, char **papszArgs) { RTERRINFOSTATIC ErrInfo; /* * Parse the command line. */ static RTGETOPTDEF const s_aOptions[] = { { "--output", 'o', RTGETOPT_REQ_STRING }, /* Query: */ { "--for-exe", 'e', RTGETOPT_REQ_STRING }, { "--for-executable", 'e', RTGETOPT_REQ_STRING }, { "--uuid", 'u', RTGETOPT_REQ_UUID }, { "--ts", 't', RTGETOPT_REQ_UINT32 }, { "--timestamp", 't', RTGETOPT_REQ_UINT32 }, { "--size", 'z', RTGETOPT_REQ_UINT32 }, { "--image-size", 'z', RTGETOPT_REQ_UINT32 }, { "--pdb-age", 'a', RTGETOPT_REQ_UINT32 }, { "--dwo-crc32", 'c', RTGETOPT_REQ_UINT32 }, { "--name", 'n', RTGETOPT_REQ_STRING }, { "--dwo", 'd', RTGETOPT_REQ_NOTHING }, { "--dwarf", 'd', RTGETOPT_REQ_NOTHING }, { "--dwarf-external", 'd', RTGETOPT_REQ_NOTHING }, { "--dsym", 'D', RTGETOPT_REQ_NOTHING }, { "--dbg", '0', RTGETOPT_REQ_NOTHING }, { "--pdb20", '2', RTGETOPT_REQ_NOTHING }, { "--pdb70", '7', RTGETOPT_REQ_NOTHING }, { "--pe", 'P', RTGETOPT_REQ_NOTHING }, { "--macho", 'M', RTGETOPT_REQ_NOTHING }, { "--elf", 'E', RTGETOPT_REQ_NOTHING }, /* RTDbgCfg: */ { "--env-prefix", 'p', RTGETOPT_REQ_STRING }, { "--sym-path", 's', RTGETOPT_REQ_STRING }, { "--use-native-paths", 1000, RTGETOPT_REQ_NOTHING }, { "--no-native-paths", 1001, RTGETOPT_REQ_NOTHING }, }; 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); const char *pszOutput = NULL; bool fGetExeImage = true; const char *pszForExec = NULL; const char *pszName = NULL; RTLDRARCH enmImageArch = RTLDRARCH_WHATEVER; RTLDRFMT enmImageFmt = RTLDRFMT_INVALID; MYDBGINFO DbgInfo; RT_ZERO(DbgInfo); const char *pszEnvPrefix = "IPRT_"; bool fNativePaths = true; const char *apszSymPaths[12] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; unsigned cSymPaths = 0; RTGETOPTUNION ValueUnion; int chOpt; while ((chOpt = RTGetOpt(&State, &ValueUnion)) != 0) { switch (chOpt) { case 'o': pszOutput = ValueUnion.psz; break; /* * Query elements: */ case 'z': DbgInfo.cbImage = ValueUnion.u32; break; case 't': DbgInfo.uTimestamp = ValueUnion.u32; enmImageFmt = RTLDRFMT_PE; break; case 'u': DbgInfo.Uuid = ValueUnion.Uuid; enmImageFmt = RTLDRFMT_MACHO; break; case 'a': DbgInfo.uPdbAge = ValueUnion.u32; if (DbgInfo.enmType != RTLDRDBGINFOTYPE_CODEVIEW_PDB20) DbgInfo.enmType = RTLDRDBGINFOTYPE_CODEVIEW_PDB70; break; case 'c': DbgInfo.uDwoCrc32 = ValueUnion.u32; DbgInfo.enmType = RTLDRDBGINFOTYPE_DWARF_DWO; break; case 'n': pszName = ValueUnion.psz; DbgInfo.szExtFile[0] = '\0'; break; case 'd': fGetExeImage = false; DbgInfo.enmType = RTLDRDBGINFOTYPE_DWARF_DWO; break; case 'D': fGetExeImage = false; DbgInfo.enmType = RTLDRDBGINFOTYPE_DWARF; /* == dSYM */ break; case '0': fGetExeImage = false; DbgInfo.enmType = RTLDRDBGINFOTYPE_CODEVIEW_DBG; break; case '2': fGetExeImage = false; DbgInfo.enmType = RTLDRDBGINFOTYPE_CODEVIEW_PDB20; break; case '7': fGetExeImage = false; DbgInfo.enmType = RTLDRDBGINFOTYPE_CODEVIEW_PDB70; break; case 'E': fGetExeImage = true; enmImageFmt = RTLDRFMT_ELF; break; case 'M': fGetExeImage = true; enmImageFmt = RTLDRFMT_MACHO; break; case 'P': fGetExeImage = true; enmImageFmt = RTLDRFMT_PE; break; case 'e': { /* Open the executable and retrieve the query parameters from it: */ fGetExeImage = false; pszForExec = ValueUnion.psz; if (!pszName) pszName = RTPathFilename(pszForExec); RTLDRMOD hLdrMod; rc = RTLdrOpenEx(pszForExec, RTLDR_O_FOR_DEBUG, enmImageArch, &hLdrMod, RTErrInfoInitStatic(&ErrInfo)); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("Failed to open image '%s': %Rrc%#RTeim", pszForExec, rc, &ErrInfo); DbgInfo.cbImage = (uint32_t)RTLdrSize(hLdrMod); enmImageFmt = RTLdrGetFormat(hLdrMod); if (enmImageFmt == RTLDRFMT_MACHO) { DbgInfo.enmType = RTLDRDBGINFOTYPE_DWARF; /* .dSYM */ rc = RTLdrQueryProp(hLdrMod, RTLDRPROP_UUID, &DbgInfo.Uuid, sizeof(DbgInfo.Uuid)); if (RT_FAILURE(rc)) RTMsgError("Failed to query image UUID from '%s': %Rrc", pszForExec, rc); } else { rc = RTLdrQueryProp(hLdrMod, RTLDRPROP_TIMESTAMP_SECONDS, &DbgInfo.uTimestamp, sizeof(DbgInfo.uTimestamp)); if (RT_SUCCESS(rc) || (rc == VERR_NOT_FOUND && enmImageFmt != RTLDRFMT_PE)) { RT_ZERO(DbgInfo); rc = RTLdrEnumDbgInfo(hLdrMod, NULL, rtDbgSymCacheCmdGetForExeDbgInfoCallback, &DbgInfo); if (RT_FAILURE(rc)) RTMsgError("RTLdrEnumDbgInfo failed on '%s': %Rrc", pszForExec, rc); } else if (RT_FAILURE(rc)) RTMsgError("Failed to query image timestamp from '%s': %Rrc", pszForExec, rc); } RTLdrClose(hLdrMod); if (RT_FAILURE(rc)) return RTEXITCODE_FAILURE; break; } /* * RTDbgCfg setup: */ case 'p': pszEnvPrefix = ValueUnion.psz; break; case 's': if (cSymPaths < RT_ELEMENTS(apszSymPaths)) apszSymPaths[cSymPaths++] = ValueUnion.psz; else return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --sym-paths arguments: max %u", RT_ELEMENTS(apszSymPaths)); break; case 1000: fNativePaths = true; break; case 1001: fNativePaths = false; break; case 'h': return rtDbgSymCacheUsage(pszArg0, "get"); case 'V': return rtDbgSymCacheVersion(); default: return RTGetOptPrintError(chOpt, &ValueUnion); } } /* * Instantiate the debug config we'll be querying. */ RTDBGCFG hDbgCfg; rc = RTDbgCfgCreate(&hDbgCfg, pszEnvPrefix, fNativePaths); if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTDbgCfgCreate failed: %Rrc", rc); rc = RTDbgCfgSetLogCallback(hDbgCfg, rtDbgSymCacheLogCallback, NULL); AssertRCStmt(rc, RTMsgError("RTDbgCfgSetLogCallback failed: %Rrc", rc)); for (unsigned i = 0; i < cSymPaths && RT_SUCCESS(rc); i++) { rc = RTDbgCfgChangeString(hDbgCfg, RTDBGCFGPROP_PATH, RTDBGCFGOP_APPEND, apszSymPaths[i]); if (RT_FAILURE(rc)) RTMsgError("Failed to append symbol path '%s': %Rrc", apszSymPaths[i], rc); } if (RT_SUCCESS(rc)) { /* * Do the getting. */ if (fGetExeImage) { if (enmImageFmt == RTLDRFMT_INVALID) { if (!RTUuidIsNull(&DbgInfo.Uuid)) enmImageFmt = RTLDRFMT_MACHO; else if (DbgInfo.cbImage && DbgInfo.uTimestamp) enmImageFmt = RTLDRFMT_PE; else rc = RTMsgErrorRc(VERR_NOT_IMPLEMENTED, "Not enough to go on to find executable"); } if (enmImageFmt == RTLDRFMT_PE) rc = RTDbgCfgOpenPeImage(hDbgCfg, pszName, DbgInfo.cbImage, DbgInfo.uTimestamp, rtDbgSymCacheCmdGetCallback, (void *)pszOutput, NULL); else if (enmImageFmt == RTLDRFMT_MACHO) rc = RTDbgCfgOpenMachOImage(hDbgCfg, pszName, &DbgInfo.Uuid, rtDbgSymCacheCmdGetCallback, (void *)pszOutput, NULL); else if (enmImageFmt != RTLDRFMT_INVALID) rc = RTMsgErrorRc(VERR_NOT_IMPLEMENTED, "Format not implemented: %s", RTLdrGetFormat); } else if (DbgInfo.enmType == RTLDRDBGINFOTYPE_CODEVIEW_PDB70) rc = RTDbgCfgOpenPdb70(hDbgCfg, DbgInfo.szExtFile[0] ? DbgInfo.szExtFile : pszName, &DbgInfo.Uuid, DbgInfo.uPdbAge, rtDbgSymCacheCmdGetCallback, (void *)pszOutput, NULL); else if (DbgInfo.enmType == RTLDRDBGINFOTYPE_CODEVIEW_PDB20) rc = RTDbgCfgOpenPdb20(hDbgCfg, DbgInfo.szExtFile[0] ? DbgInfo.szExtFile : pszName, DbgInfo.cbImage, DbgInfo.uTimestamp, DbgInfo.uPdbAge, rtDbgSymCacheCmdGetCallback, (void *)pszOutput, NULL); else if (DbgInfo.enmType == RTLDRDBGINFOTYPE_CODEVIEW_DBG) rc = RTDbgCfgOpenDbg(hDbgCfg, DbgInfo.szExtFile[0] ? DbgInfo.szExtFile : pszName, DbgInfo.cbImage, DbgInfo.uTimestamp, rtDbgSymCacheCmdGetCallback, (void *)pszOutput, NULL); else if (DbgInfo.enmType == RTLDRDBGINFOTYPE_DWARF_DWO) rc = RTDbgCfgOpenDwo(hDbgCfg, DbgInfo.szExtFile[0] ? DbgInfo.szExtFile : pszName, DbgInfo.uDwoCrc32, rtDbgSymCacheCmdGetCallback, (void *)pszOutput, NULL); else if (DbgInfo.enmType == RTLDRDBGINFOTYPE_DWARF) rc = RTDbgCfgOpenDsymBundle(hDbgCfg, DbgInfo.szExtFile[0] ? DbgInfo.szExtFile : pszName, &DbgInfo.Uuid, rtDbgSymCacheCmdGetCallback, (void *)pszOutput, NULL); else rc = RTMsgErrorRc(VERR_NOT_IMPLEMENTED, "Format not implemented"); } RTDbgCfgRelease(hDbgCfg); return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; } 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], "get")) rcExit = rtDbgSymCacheCmdGet(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; }