diff options
Diffstat (limited to 'src/VBox/Runtime/tools/RTDbgSymCache.cpp')
-rw-r--r-- | src/VBox/Runtime/tools/RTDbgSymCache.cpp | 1858 |
1 files changed, 1858 insertions, 0 deletions
diff --git a/src/VBox/Runtime/tools/RTDbgSymCache.cpp b/src/VBox/Runtime/tools/RTDbgSymCache.cpp new file mode 100644 index 00000000..45097943 --- /dev/null +++ b/src/VBox/Runtime/tools/RTDbgSymCache.cpp @@ -0,0 +1,1858 @@ +/* $Id: RTDbgSymCache.cpp $ */ +/** @file + * IPRT - Debug Symbol Cache Utility. + */ + +/* + * Copyright (C) 2013-2022 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 <https://www.gnu.org/licenses>. + * + * 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 <iprt/zip.h> + +#include <iprt/buildconfig.h> +#include <iprt/dbg.h> +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/formats/mach-o.h> +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/ldr.h> +#include <iprt/message.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/uuid.h> +#include <iprt/vfs.h> +#include <iprt/zip.h> + + +/********************************************************************************************************************************* +* 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] <cache-root-dir> <file1[=cache-name]> [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 <query-options> <cache-options> [--output|-o <path>]\n" + "\n" + "Query Options:\n" + " --for-exe[cutable] <path>\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 <timestamp>\n" + " The timestamp (32-bit) for the file to get. Used with --dbg, --pdb20\n" + " and --pe.\n" + " --uuid, -u, <uuid>\n" + " The UUID for the file to get. Used with --dsym, --pdb70 and --macho\n" + " --image-size, --size, -z <size>\n" + " The image size (32-bit) for the file to get. Used with --dbg,\n" + " --pdb20, --pdb70 and --pe.\n" + " --pdb-age, -a <age>\n" + " The PDB age (32-bit) for the file to get. Used with --pdb20 and --pdb70.\n" + " --dwo-crc32, -c <crc32>\n" + " The CRC32 for the file to get. Used with --dwo.\n" + " --name, -n <name>\n" + " The name (in the cache) of the file to get.\n" + "\n" + "Debug Cache Options:\n" + " --sym-path, -s <path>\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 <prefix>\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 <path>\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 <UUID>.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; +} + |