RTDbgSymCache.cpp revision 9ec22033341c104cf895501e1c2347b15a21ec1e
/* $Id$ */
/** @file
* IPRT - Debug Symbol Cache Utility.
*/
/*
* Copyright (C) 2013 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*
* The contents of this file may alternatively be used under the terms
* of the Common Development and Distribution License Version 1.0
* (CDDL) only, as it comes in the "COPYING.CDDL" file of the
* VirtualBox OSE distribution, in which case the provisions of the
* CDDL are applicable instead of those of the GPL.
*
* You may elect to license modified versions of this file under the
* terms and conditions of either the GPL or the CDDL or both.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#include <iprt/buildconfig.h>
#include <iprt/initterm.h>
/*******************************************************************************
* Structures and Typedefs *
*******************************************************************************/
/**
* Cache file type.
*/
typedef enum RTDBGSYMCACHEFILETYPE
{
/**
* Configuration for the 'add' command.
*/
typedef struct RTDBGSYMCACHEADDCFG
{
bool fRecursive;
const char *pszFilter;
const char *pszCache;
/** 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",
};
/** 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",
};
/**
* Display the version of the cache program.
*
* @returns exit code.
*/
static RTEXITCODE rtDbgSymCacheVersion(void)
{
return RTEXITCODE_SUCCESS;
}
/**
* Shows the usage of the cache program.
*
* @returns Exit code.
* @param pszArg0 Program name.
* @param pszCommand Command selector, NULL if all.
*/
{
return RTEXITCODE_SUCCESS;
}
/**
* Creates a UUID mapping for the file.
*
* @returns IPRT status code.
* @param pszCacheFile The path to the file in the cache.
* @param pFileUuid The UUID of the file.
* @param pszUuidMapDir The UUID map subdirectory in the cache, if this is
* wanted, otherwise NULL.
* @param pCfg The configuration.
*/
{
/*
* 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,
if (RT_FAILURE(rc))
if (RT_FAILURE(rc))
/* Uppercase the whole lot. */
/* Split the first dword in two. */
/*
* Create the directories in the path.
*/
char chSaved = RTPATH_SLASH;
{
if (!RTDirExists(szMapPath))
{
if (RT_FAILURE(rc))
}
}
cch -= 5;
/*
* Calculate a relative path from there to the actual file.
*/
char szLinkTarget[RTPATH_MAX];
//szMapPath[cch] = '\0';
//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.
*/
if (RT_SUCCESS(rc))
{
{
if (RT_SUCCESS(rc))
{
char *pszCurTarget = NULL;
if (RT_FAILURE(rc))
else
{
RTMsgError("UUID map: Existing mapping '%s' pointing to '%s' insted of '%s'",
}
return rc;
}
else
}
return RTMsgErrorRc(VERR_IS_A_FILE,
"UUID map: found file at '%s', expect symbolic link or nothing.", szMapPath);
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",
}
/*
* Create the symbolic link.
*/
if (RT_FAILURE(rc))
return RTMsgErrorRc(rc, "Failed to create UUID map symlink '%s' to '%s': %Rrc", szMapPath, szLinkTarget, rc);
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,
{
/*
* Build and create the destination path, step by step.
*/
char szDstPath[RTPATH_MAX];
if (RT_FAILURE(rc))
if (!RTDirExists(szDstPath))
{
if (RT_FAILURE(rc))
}
if (RT_FAILURE(rc))
if (!RTDirExists(szDstPath))
{
if (RT_FAILURE(rc))
}
if (RT_FAILURE(rc))
if (pszExtraStuff)
{
if (RT_FAILURE(rc))
}
/*
* If the file exists, we compare the two and throws an error if the doesn't match.
*/
if (RTPathExists(szDstPath))
{
if (RT_SUCCESS(rc))
{
if (pAddToUuidMap && pszUuidMapDir)
return VINF_SUCCESS;
}
if (rc == VERR_NOT_EQUAL)
else
return rc;
}
/*
* The file doesn't exist or we should overwrite it,
*/
if (RT_FAILURE(rc))
if (pAddToUuidMap && pszUuidMapDir)
return VINF_SUCCESS;
}
/**
* Worker that add the image file to the right place.
*
* @returns IPRT status code.
* @param pszPath Path to the image file.
* @param pCfg Configuration data.
* @param hLdrMod Image handle.
* @param pszExtraSuff Optional extra suffix. Mach-O dSYM hack.
* @param pszUuidMapDir Optional UUID map cache directory if the image
* should be mapped by UUID.
* The map is a Mac OS X debug feature supported by
* the two native debuggers gdb and lldb. Look for
* descriptions of DBGFileMappedPaths in the
* com.apple.DebugSymbols in the user defaults.
*/
static int rtDbgSymCacheAddImageFileWorker(const char *pszPath, PCRTDBGSYMCACHEADDCFG pCfg, RTLDRMOD hLdrMod,
const char *pszExtrSuff, const char *pszUuidMapDir)
{
/*
* Determine which subdirectory to put the files in.
*/
int rc;
char szSubDir[48];
switch (enmFmt)
{
case RTLDRFMT_MACHO:
{
if (RT_FAILURE(rc))
if (RT_FAILURE(rc))
break;
}
case RTLDRFMT_PE:
{
if (RT_FAILURE(rc))
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:
}
/*
* Now add it.
*/
}
/**
* Adds what we think is an image file to the cache.
*
* @returns IPRT status code.
* @param pszPath Path to the image file.
* @param pszExtraSuff Optional extra suffix. Mach-O dSYM hack.
* @param pszUuidMapDir The UUID map subdirectory in the cache, if this is
* wanted, otherwise NULL.
* @param pCfg Configuration data.
*/
static int rtDbgSymCacheAddImageFile(const char *pszPath, const char *pszExtraSuff, const char *pszUuidMapDir,
{
/*
* 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. */
if (RT_FAILURE(rc))
{
if (rc != VERR_LDR_ARCH_MISMATCH)
{
if (rc != VERR_INVALID_EXE_SIGNATURE)
return VINF_SUCCESS;
}
}
/* Open it as X86. */
if (RT_FAILURE(rc))
{
if (rc != VERR_LDR_ARCH_MISMATCH)
{
}
}
/*
* Add the file.
*/
if (hLdrMod32 == NIL_RTLDRMOD)
else if (hLdrMod64 == NIL_RTLDRMOD)
else
{
/*
* Do we need to add it once or twice?
*/
{
}
{
if (fSame)
{
int rc32 = RTLdrQueryProp(hLdrMod32, RTLDRPROP_TIMESTAMP_SECONDS, &uTimestamp32, sizeof(uTimestamp32));
int rc64 = RTLdrQueryProp(hLdrMod64, RTLDRPROP_TIMESTAMP_SECONDS, &uTimestamp64, sizeof(uTimestamp64));
}
}
if (!fSame)
{
/** @todo should symlink or hardlink this second copy. */
}
}
return VINF_SUCCESS;
}
/**
* Worker for rtDbgSymCacheAddDebugFile that adds a Mach-O debug file to the
* cache.
*
* @returns IPRT status code
* @param pszPath The path to the PDB file.
* @param pCfg The configuration.
* @param hFile Handle to the file.
*/
{
/* This shouldn't happen, figure out what to do if it does. */
return RTMsgErrorRc(VERR_NOT_IMPLEMENTED,
"'%s' is an OS X image file, did you point me to a file inside a .dSYM or .sym file?",
pszPath);
}
/**
* Worker for rtDbgSymCacheAddDebugFile that adds PDBs to the cace.
*
* @returns IPRT status code
* @param pszPath The path to the PDB file.
* @param pCfg The configuration.
* @param hFile Handle to the file.
*/
{
}
/**
* Adds a debug file to the cache.
*
* @returns IPRT status code
* @param pszPath The path to the debug file in question.
* @param pCfg The configuration.
*/
{
/*
* 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.
*/
if (RT_FAILURE(rc))
union
{
} uBuf;
if (RT_SUCCESS(rc))
{
/*
* Look for magics and call workers.
*/
else
rc = RTMsgErrorRc(VERR_INVALID_MAGIC, "Unsupported debug file '%s' magic: %#010x", pszPath, uBuf.au32[0]);
}
else
/* close the file. */
if (RT_FAILURE(rc2))
{
if (RT_SUCCESS(rc))
}
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)
{
if (RT_SUCCESS(rc))
{
/* Strip off the bundle suffix. */
for (unsigned i = 0; papszSuffixes[i]; i++)
{
{
break;
}
}
/* Add the name. */
}
if (RT_FAILURE(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 pCfg The configuration.
*/
static int rtDbgSymCacheAddImageBundle(char *pszPath, size_t cchPath, size_t cchName, 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))
return rc;
}
/**
* Adds a debug bundle.
*
* @returns IPRT status code.
* @param pszPath Path to the bundle. This a RTPATH_MAX size
* buffer that we can write to when creating the
* path to the file inside the bundle that we're
* interested in.
* @param cchPath The length of the path up to the bundle name.
* @param cchName The length of the bundle name.
* @param pCfg The configuration.
*/
static int rtDbgSymCacheAddDebugBundle(char *pszPath, size_t cchPath, size_t cchName, PCRTDBGSYMCACHEADDCFG pCfg)
{
/*
* The current policy is not to add the whole .dSYM (or .sym) bundle, but
* rather just the dwarf image instide it. The <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
*
* ASSUMES bundles contains Mach-O DWARF files.
*/
int rc = rtDbgSymCacheConstructBundlePath(pszPath, cchPath, cchName, "Contents/Resources/DWARF/", g_apszDSymBundleSuffixes);
if (RT_SUCCESS(rc))
rc = rtDbgSymCacheAddImageFile(pszPath, RTDBG_CACHE_DSYM_FILE_SUFFIX, RTDBG_CACHE_UUID_MAP_DIR_DSYMS, pCfg);
return rc;
}
/**
*
* @returns The type.
* @param pObjInfo The object information, symlinks followed.
*/
{
if (pszExt)
pszExt++;
else
pszExt = "";
|| (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. */
)
return RTDBGSYMCACHEFILETYPE_IGNORE;
/* Directories can also be bundles on the mac. */
return RTDBGSYMCACHEFILETYPE_DIR;
}
return RTDBGSYMCACHEFILETYPE_INVALID;
/* Select image vs debug info based on extension. */
return RTDBGSYMCACHEFILETYPE_DEBUG_FILE;
/* Filter out a bunch of files which obviously shouldn't be images. */
)
return RTDBGSYMCACHEFILETYPE_IGNORE;
)
return RTDBGSYMCACHEFILETYPE_IGNORE;
return RTDBGSYMCACHEFILETYPE_IMAGE_FILE;
}
/**
*
* @returns File type.
*/
{
/* Trailing slash. */
if (!pszName)
return RTDBGSYMCACHEFILETYPE_DIR;
/* Wildcard means listing directory and filtering. */
return RTDBGSYMCACHEFILETYPE_DIR_FILTER;
/* Get object info, following links. */
if (RT_FAILURE(rc))
return RTDBGSYMCACHEFILETYPE_INVALID;
}
/**
* 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.
*/
{
if (RT_FAILURE(rc))
{
return RTMsgErrorRc(rc, "Filename too long (%Rrc): '%s" RTPATH_SLASH_STR "%s'", rc, pszPath, pCfg->pszFilter);
}
}
else
if (RT_FAILURE(rc))
return RTMsgErrorRc(rc, "RTDirOpen%s failed on '%s': %Rrc", pCfg->pszFilter ? "Filtered" : "", pszPath, rc);
/*
* Enumerate the files.
*/
for (;;)
{
if (RT_FAILURE(rc2))
{
if (rc2 != VERR_NO_MORE_FILES)
{
}
break;
}
/* Skip dot and dot-dot. */
continue;
/* Construct a full path. */
if (RT_FAILURE(rc))
{
break;
}
{
if (!pCfg->fRecursive)
else
{
{
}
else
{
}
}
break;
break;
rc2 = rtDbgSymCacheAddImageFile(pszPath, NULL /*pszExtraSuff*/, RTDBG_CACHE_UUID_MAP_DIR_IMAGES, pCfg);
break;
break;
break;
break;
rc2 = VINF_SUCCESS;
break;
}
}
/*
* Clean up.
*/
if (RT_FAILURE(rc2))
{
}
return rc;
}
/**
* Adds a directory.
*
* @returns IPRT status code (fully bitched).
* @param pszPath The directory path.
* @param pCfg The configuration.
*/
{
/*
* Set up the path buffer, stripping any filter.
*/
char szPath[RTPATH_MAX];
if (RT_FAILURE(rc))
if (!cchPath)
{
}
/*
* Let the worker do the rest.
*/
}
/**
* 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.
*/
static RTEXITCODE rtDbgSymCacheAddFileOrDir(const char *pszPath, const char *pszCache, bool fRecursive)
{
int rc;
switch (enmType)
{
default:
/* fall thru */
break;
break;
rc = rtDbgSymCacheAddImageFile(pszPath, NULL /*pszExtraSuff*/, RTDBG_CACHE_UUID_MAP_DIR_IMAGES, &Cfg);
break;
{
char szPathBuf[RTPATH_MAX];
{
else
}
else
break;
}
break;
}
}
/**
* 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'.
*/
{
/*
* Parse the command line.
*/
static RTGETOPTDEF const s_aOptions[] =
{
};
bool fRecursive = false;
int rc = RTGetOptInit(&State, cArgs, papszArgs, &s_aOptions[0], RT_ELEMENTS(s_aOptions), 0, RTGETOPTINIT_FLAGS_OPTS_FIRST);
if (RT_FAILURE(rc))
int chOpt;
{
switch (chOpt)
{
case 'R':
fRecursive = true;
break;
case 'n':
fRecursive = false;
break;
case VINF_GETOPT_NOT_OPTION:
/* The first non-option is a cache directory. */
if (!pszCache)
{
if (!RTPathExists(pszCache))
{
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
{
if (rcExit != RTEXITCODE_FAILURE)
return rcExit;
}
break;
case 'h':
case 'V':
return rtDbgSymCacheVersion();
default:
}
}
if (!pszCache)
return RTEXITCODE_SUCCESS;
}
{
if (RT_FAILURE(rc))
return RTMsgInitFailure(rc);
/*
* Switch on the command.
*/
if (argc < 2)
else
return rcExit;
}