VirtualBox

source: vbox/trunk/src/VBox/Runtime/tools/RTDbgSymCache.cpp@ 100911

Last change on this file since 100911 was 100911, checked in by vboxsync, 16 months ago

IPRT/RTDbgSymCache: Added support for adding PDBs and DBGs to the symbol cache.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 76.1 KB
Line 
1/* $Id: RTDbgSymCache.cpp 100911 2023-08-19 02:59:14Z vboxsync $ */
2/** @file
3 * IPRT - Debug Symbol Cache Utility.
4 */
5
6/*
7 * Copyright (C) 2013-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * The contents of this file may alternatively be used under the terms
26 * of the Common Development and Distribution License Version 1.0
27 * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
28 * in the VirtualBox distribution, in which case the provisions of the
29 * CDDL are applicable instead of those of the GPL.
30 *
31 * You may elect to license modified versions of this file under the
32 * terms and conditions of either the GPL or the CDDL or both.
33 *
34 * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
35 */
36
37
38/*********************************************************************************************************************************
39* Header Files *
40*********************************************************************************************************************************/
41#include <iprt/zip.h>
42
43#include <iprt/buildconfig.h>
44#include <iprt/dbg.h>
45#include <iprt/err.h>
46#include <iprt/file.h>
47#include <iprt/fsvfs.h>
48#include <iprt/getopt.h>
49#include <iprt/initterm.h>
50#include <iprt/ldr.h>
51#include <iprt/message.h>
52#include <iprt/param.h>
53#include <iprt/path.h>
54#include <iprt/stream.h>
55#include <iprt/string.h>
56#include <iprt/uuid.h>
57#include <iprt/vfs.h>
58#include <iprt/zip.h>
59#include <iprt/formats/mach-o.h>
60#include <iprt/formats/pecoff.h>
61#include <iprt/formats/pdb.h>
62
63
64/*********************************************************************************************************************************
65* Structures and Typedefs *
66*********************************************************************************************************************************/
67/**
68 * Cache file type.
69 */
70typedef enum RTDBGSYMCACHEFILETYPE
71{
72 RTDBGSYMCACHEFILETYPE_INVALID,
73 RTDBGSYMCACHEFILETYPE_DIR,
74 RTDBGSYMCACHEFILETYPE_DIR_FILTER,
75 RTDBGSYMCACHEFILETYPE_DEBUG_FILE,
76 RTDBGSYMCACHEFILETYPE_IMAGE_FILE,
77 RTDBGSYMCACHEFILETYPE_DEBUG_BUNDLE,
78 RTDBGSYMCACHEFILETYPE_IMAGE_BUNDLE,
79 RTDBGSYMCACHEFILETYPE_IGNORE
80} RTDBGSYMCACHEFILETYPE;
81
82
83/**
84 * Configuration for the 'add' command.
85 */
86typedef struct RTDBGSYMCACHEADDCFG
87{
88 bool fRecursive;
89 bool fOverwriteOnConflict;
90 const char *pszFilter;
91 const char *pszCache;
92} RTDBGSYMCACHEADDCFG;
93/** Pointer to a read only 'add' config. */
94typedef RTDBGSYMCACHEADDCFG const *PCRTDBGSYMCACHEADDCFG;
95
96
97/*********************************************************************************************************************************
98* Global Variables *
99*********************************************************************************************************************************/
100/** Bundle suffixes. */
101static const char * const g_apszBundleSuffixes[] =
102{
103 ".kext",
104 ".app",
105 ".framework", /** @todo framework is different. */
106 ".component",
107 ".action",
108 ".caction",
109 ".bundle",
110 ".sourcebundle",
111 ".plugin",
112 ".ppp",
113 ".menu",
114 ".monitorpanel",
115 ".scripting",
116 ".prefPane",
117 ".qlgenerator",
118 ".brailledriver",
119 ".saver",
120 ".SpeechVoice",
121 ".SpeechRecognizer",
122 ".SpeechSynthesizer",
123 ".mdimporter",
124 ".spreporter",
125 ".xpc",
126 NULL
127};
128
129/** Debug bundle suffixes. (Same as above + .dSYM) */
130static const char * const g_apszDSymBundleSuffixes[] =
131{
132 ".kext.dSYM",
133 ".app.dSYM",
134 ".framework.dSYM",
135 ".component.dSYM",
136 ".action.dSYM",
137 ".caction.dSYM",
138 ".bundle.dSYM",
139 ".sourcebundle.dSYM",
140 ".menu.dSYM",
141 ".plugin.dSYM",
142 ".ppp.dSYM",
143 ".monitorpanel.dSYM",
144 ".scripting.dSYM",
145 ".prefPane.dSYM",
146 ".qlgenerator.dSYM",
147 ".brailledriver.dSYM",
148 ".saver.dSYM",
149 ".SpeechVoice.dSYM",
150 ".SpeechRecognizer.dSYM",
151 ".SpeechSynthesizer.dSYM",
152 ".mdimporter.dSYM",
153 ".spreporter.dSYM",
154 ".xpc.dSYM",
155 ".dSYM",
156 NULL
157};
158
159
160/*********************************************************************************************************************************
161* Internal Functions *
162*********************************************************************************************************************************/
163static int rtDbgSymCacheAddDirWorker(char *pszPath, size_t cchPath, PRTDIRENTRYEX pDirEntry, PCRTDBGSYMCACHEADDCFG pCfg);
164
165
166/*********************************************************************************************************************************
167* Global Variables *
168*********************************************************************************************************************************/
169/** Verbositity level. */
170static uint32_t g_iLogLevel = 99;
171
172
173/**
174 * Display the version of the cache program.
175 *
176 * @returns exit code.
177 */
178static RTEXITCODE rtDbgSymCacheVersion(void)
179{
180 RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision());
181 return RTEXITCODE_SUCCESS;
182}
183
184
185/**
186 * Shows the usage of the cache program.
187 *
188 * @returns Exit code.
189 * @param pszArg0 Program name.
190 * @param pszCommand Command selector, NULL if all.
191 */
192static RTEXITCODE rtDbgSymCacheUsage(const char *pszArg0, const char *pszCommand)
193{
194 if (!pszCommand || !strcmp(pszCommand, "add"))
195 RTPrintf("Usage: %s add [-Rno] <cache-root-dir> <file1[=cache-name]> [fileN..]\n"
196 "\n"
197 "Options:\n"
198 " -R, --recursive\n"
199 " Process directory arguments recursively.\n"
200 " -n, --no-recursive\n"
201 " No recursion. (default)\n"
202 " -o, --overwrite-on-conflict\n"
203 " Overwrite existing cache entry.\n"
204 , RTPathFilename(pszArg0));
205
206
207 if (!pszCommand || !strcmp(pszCommand, "get"))
208 RTPrintf("Usage: %s get <query-options> <cache-options> [--output|-o <path>]\n"
209 "\n"
210 "Query Options:\n"
211 " --for-exe[cutable] <path>\n"
212 " Get debug file for the given executable.\n"
213 " --dwo, --dwarf, --dwarf-external\n"
214 " Get external DWARF debug file. Needs --name and --dwo-crc32.\n"
215 " --dsym\n"
216 " Get DWARF debug file from .dSYM bundle. Needs --uuid or --name.\n"
217 " --dbg\n"
218 " Get NT DBG debug file. Needs --name, --timestamp and --size.\n"
219 " --pdb20\n"
220 " Get PDB 2.0 debug file. Needs --name, --timestamp, --size\n"
221 " and --pdb-age (if non-zero).\n"
222 " --pdb70\n"
223 " Get PDB 7.0 debug file. Needs --name, --uuid, and --pdb-age\n"
224 " (if non-zero).\n"
225 " --macho\n"
226 " Get Mach-O image file. Needs --uuid or --name.\n"
227 " --pe\n"
228 " Get PE image file. Needs --name, --timestamp and --size.\n"
229 " --timestamp, --ts, -t <timestamp>\n"
230 " The timestamp (32-bit) for the file to get. Used with --dbg, --pdb20\n"
231 " and --pe.\n"
232 " --uuid, -u, <uuid>\n"
233 " The UUID for the file to get. Used with --dsym, --pdb70 and --macho\n"
234 " --image-size, --size, -z <size>\n"
235 " The image size (32-bit) for the file to get. Used with --dbg,\n"
236 " --pdb20, --pdb70 and --pe.\n"
237 " --pdb-age, -a <age>\n"
238 " The PDB age (32-bit) for the file to get. Used with --pdb20 and --pdb70.\n"
239 " --dwo-crc32, -c <crc32>\n"
240 " The CRC32 for the file to get. Used with --dwo.\n"
241 " --name, -n <name>\n"
242 " The name (in the cache) of the file to get.\n"
243 "\n"
244 "Debug Cache Options:\n"
245 " --sym-path, -s <path>\n"
246 " Adds the path to the debug configuration, NT style with 'srv*' and\n"
247 " 'cache*' prefixes as well as our own 'rec*' and 'norec*' recursion\n"
248 " prefixes.\n"
249 " --env-prefix, -p <prefix>\n"
250 " The enviornment variable prefix, default is 'IPRT_' making the\n"
251 " symbol path variable 'IPRT_PATH'.\n"
252 " --use-native-paths (default), --no-native-paths\n"
253 " Pick up native symbol paths from the environment.\n"
254 "\n"
255 "Output Options:\n"
256 " --output, -o <path>\n"
257 " The output filename or directory. Directories must end with a\n"
258 " path separator. The default filename that in the cache.\n"
259 "\n"
260 "This is handy for triggering downloading of symbol files from a server. Say\n"
261 "you have the executable but want the corrsponding PDB or .dSYM file:\n"
262 " %s get --for-executable VBoxRT.dll\n"
263 " %s get --for-executable VBoxRT.dylib\n"
264 " "
265 , RTPathFilename(pszArg0), RTPathFilename(pszArg0), RTPathFilename(pszArg0));
266
267 return RTEXITCODE_SUCCESS;
268}
269
270
271/**
272 * @callback_method_impl{FNRTDBGCFGLOG}
273 */
274static DECLCALLBACK(void) rtDbgSymCacheLogCallback(RTDBGCFG hDbgCfg, uint32_t iLevel, const char *pszMsg, void *pvUser)
275{
276 RT_NOREF(hDbgCfg, pvUser);
277 if (iLevel <= g_iLogLevel)
278 {
279 size_t cchMsg = strlen(pszMsg);
280 if (cchMsg > 0 && pszMsg[cchMsg - 1] == '\n')
281 RTMsgInfo("[%u] %s", iLevel, pszMsg);
282 else if (cchMsg > 0)
283 RTMsgInfo("[%u] %s\n", iLevel, pszMsg);
284 }
285}
286
287
288/**
289 * Creates a UUID mapping for the file.
290 *
291 * @returns IPRT status code.
292 * @param pszCacheFile The path to the file in the cache.
293 * @param pFileUuid The UUID of the file.
294 * @param pszUuidMapDir The UUID map subdirectory in the cache, if this is
295 * wanted, otherwise NULL.
296 * @param pCfg The configuration.
297 */
298static int rtDbgSymCacheAddCreateUuidMapping(const char *pszCacheFile, PRTUUID pFileUuid,
299 const char *pszUuidMapDir, PCRTDBGSYMCACHEADDCFG pCfg)
300{
301 /*
302 * Create the UUID map entry first, deep.
303 */
304 char szMapPath[RTPATH_MAX];
305 int rc = RTPathJoin(szMapPath, sizeof(szMapPath) - sizeof("/xxxx/yyyy/xxxx/yyyy/xxxx/zzzzzzzzzzzz") + 1,
306 pCfg->pszCache, pszUuidMapDir);
307 if (RT_FAILURE(rc))
308 return RTMsgErrorRc(rc, "Error constructing UUID map path (RTPathJoin): %Rrc", rc);
309
310 size_t cch = strlen(szMapPath);
311 szMapPath[cch] = '-';
312
313 rc = RTUuidToStr(pFileUuid, &szMapPath[cch + 2], sizeof(szMapPath) - cch);
314 if (RT_FAILURE(rc))
315 return RTMsgErrorRc(rc, "Error constructing UUID map path (RTUuidToStr): %Rrc", rc);
316
317 /* Uppercase the whole lot. */
318 RTStrToUpper(&szMapPath[cch + 2]);
319
320 /* Split the first dword in two. */
321 szMapPath[cch + 1] = szMapPath[cch + 2];
322 szMapPath[cch + 2] = szMapPath[cch + 3];
323 szMapPath[cch + 3] = szMapPath[cch + 4];
324 szMapPath[cch + 4] = szMapPath[cch + 5];
325 szMapPath[cch + 5] = '-';
326
327 /*
328 * Create the directories in the path.
329 */
330 for (unsigned i = 0; i < 6; i++, cch += 5)
331 {
332 Assert(szMapPath[cch] == '-');
333 szMapPath[cch] = '\0';
334 if (!RTDirExists(szMapPath))
335 {
336 rc = RTDirCreate(szMapPath, 0755, RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL);
337 if (RT_FAILURE(rc))
338 return RTMsgErrorRc(rc, "RTDirCreate failed on '%s' (UUID map path): %Rrc", szMapPath, rc);
339 }
340 szMapPath[cch] = RTPATH_SLASH;
341 }
342 cch -= 5;
343
344 /*
345 * Calculate a relative path from there to the actual file.
346 */
347 char szLinkTarget[RTPATH_MAX];
348 szMapPath[cch] = '\0';
349 rc = RTPathCalcRelative(szLinkTarget, sizeof(szLinkTarget), szMapPath, false /*fFromFile*/, pszCacheFile);
350 szMapPath[cch] = RTPATH_SLASH;
351 if (RT_FAILURE(rc))
352 return RTMsgErrorRc(rc, "Failed to calculate relative path from '%s' to '%s': %Rrc", szMapPath, pszCacheFile, rc);
353
354 /*
355 * If there is already a link there, check if it matches or whether
356 * perhaps it's target doesn't exist.
357 */
358 RTFSOBJINFO ObjInfo;
359 rc = RTPathQueryInfoEx(szMapPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
360 if (RT_SUCCESS(rc))
361 {
362 if (RTFS_IS_SYMLINK(ObjInfo.Attr.fMode))
363 {
364 rc = RTPathQueryInfoEx(szMapPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK);
365 if (RT_SUCCESS(rc))
366 {
367 char *pszCurTarget = NULL;
368 rc = RTSymlinkReadA(szMapPath, &pszCurTarget);
369 if (RT_FAILURE(rc))
370 return RTMsgErrorRc(rc, "UUID map: failed to read existing symlink '%s': %Rrc", szMapPath, rc);
371 if (RTPathCompare(pszCurTarget, szLinkTarget) == 0)
372 RTMsgInfo("UUID map: existing link '%s' has the same target ('%s').", szMapPath, pszCurTarget);
373 else
374 {
375 RTMsgError("UUID map: Existing mapping '%s' pointing to '%s' insted of '%s'",
376 szMapPath, pszCurTarget, szLinkTarget);
377 rc = VERR_ALREADY_EXISTS;
378 }
379 RTStrFree(pszCurTarget);
380 return rc;
381 }
382 else
383 RTMsgInfo("UUID map: replacing dangling link '%s'", szMapPath);
384 RTSymlinkDelete(szMapPath, 0 /*fFlags*/);
385 }
386 else if (RTFS_IS_FILE(ObjInfo.Attr.fMode))
387 return RTMsgErrorRc(VERR_IS_A_FILE,
388 "UUID map: found file at '%s', expect symbolic link or nothing.", szMapPath);
389 else if (RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode))
390 return RTMsgErrorRc(VERR_IS_A_DIRECTORY,
391 "UUID map: found directory at '%s', expect symbolic link or nothing.", szMapPath);
392 else
393 return RTMsgErrorRc(VERR_NOT_SYMLINK,
394 "UUID map: Expected symbolic link or nothing at '%s', found: fMode=%#x",
395 szMapPath, ObjInfo.Attr.fMode);
396 }
397
398 /*
399 * Create the symbolic link.
400 */
401 rc = RTSymlinkCreate(szMapPath, szLinkTarget, RTSYMLINKTYPE_FILE, 0);
402 if (RT_FAILURE(rc))
403 return RTMsgErrorRc(rc, "Failed to create UUID map symlink '%s' to '%s': %Rrc", szMapPath, szLinkTarget, rc);
404 RTMsgInfo("UUID map: %s => %s", szMapPath, szLinkTarget);
405 return VINF_SUCCESS;
406}
407
408
409/**
410 * Adds a file to the cache.
411 *
412 * @returns IPRT status code.
413 * @param pszSrcPath Path to the source file.
414 * @param pszDstName The name of the destionation file (no path stuff).
415 * @param pszExtraSuff Optional extra suffix. Mach-O dSYM hack.
416 * @param pszDstSubDir The subdirectory to file it under. This is the
417 * stringification of a relatively unique identifier of
418 * the file in question.
419 * @param pAddToUuidMap Optional file UUID that is used to create a UUID map
420 * entry.
421 * @param pszUuidMapDir The UUID map subdirectory in the cache, if this is
422 * wanted, otherwise NULL.
423 * @param pCfg The configuration.
424 */
425static int rtDbgSymCacheAddOneFile(const char *pszSrcPath, const char *pszDstName, const char *pszExtraSuff,
426 const char *pszDstSubDir, PRTUUID pAddToUuidMap, const char *pszUuidMapDir,
427 PCRTDBGSYMCACHEADDCFG pCfg)
428{
429 /*
430 * Build and create the destination path, step by step.
431 */
432 char szDstPath[RTPATH_MAX];
433 int rc = RTPathJoin(szDstPath, sizeof(szDstPath), pCfg->pszCache, pszDstName);
434 if (RT_FAILURE(rc))
435 return RTMsgErrorRc(rc, "Error constructing cache path for '%s': %Rrc", pszSrcPath, rc);
436
437 if (!RTDirExists(szDstPath))
438 {
439 rc = RTDirCreate(szDstPath, 0755, RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL);
440 if (RT_FAILURE(rc))
441 return RTMsgErrorRc(rc, "Error creating '%s': %Rrc", szDstPath, rc);
442 }
443
444 rc = RTPathAppend(szDstPath, sizeof(szDstPath), pszDstSubDir);
445 if (RT_FAILURE(rc))
446 return RTMsgErrorRc(rc, "Error constructing cache path for '%s': %Rrc", pszSrcPath, rc);
447
448 if (!RTDirExists(szDstPath))
449 {
450 rc = RTDirCreate(szDstPath, 0755, RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL);
451 if (RT_FAILURE(rc))
452 return RTMsgErrorRc(rc, "Error creating '%s': %Rrc", szDstPath, rc);
453 }
454
455 rc = RTPathAppend(szDstPath, sizeof(szDstPath), pszDstName);
456 if (RT_FAILURE(rc))
457 return RTMsgErrorRc(rc, "Error constructing cache path for '%s': %Rrc", pszSrcPath, rc);
458 if (pszExtraSuff)
459 {
460 rc = RTStrCat(szDstPath, sizeof(szDstPath), pszExtraSuff);
461 if (RT_FAILURE(rc))
462 return RTMsgErrorRc(rc, "Error constructing cache path for '%s': %Rrc", pszSrcPath, rc);
463 }
464
465 /*
466 * If the file exists, we compare the two and throws an error if the doesn't match.
467 */
468 if (RTPathExists(szDstPath))
469 {
470 rc = RTFileCompare(pszSrcPath, szDstPath);
471 if (RT_SUCCESS(rc))
472 {
473 RTMsgInfo("%s is already in the cache.", pszSrcPath);
474 if (pAddToUuidMap && pszUuidMapDir)
475 return rtDbgSymCacheAddCreateUuidMapping(szDstPath, pAddToUuidMap, pszUuidMapDir, pCfg);
476 return VINF_SUCCESS;
477 }
478 if (rc == VERR_NOT_EQUAL)
479 RTMsgInfo("Cache conflict with existing entry '%s' when inserting '%s'.", szDstPath, pszSrcPath);
480 else
481 RTMsgInfo("Error comparing '%s' with '%s': %Rrc", pszSrcPath, szDstPath, rc);
482 if (!pCfg->fOverwriteOnConflict)
483 return rc;
484 }
485
486 /*
487 * The file doesn't exist or we should overwrite it,
488 */
489 RTMsgInfo("Copying '%s' to '%s'...", pszSrcPath, szDstPath);
490 rc = RTFileCopy(pszSrcPath, szDstPath);
491 if (RT_FAILURE(rc))
492 return RTMsgErrorRc(rc, "Error copying '%s' to '%s': %Rrc", pszSrcPath, szDstPath, rc);
493 if (pAddToUuidMap && pszUuidMapDir)
494 return rtDbgSymCacheAddCreateUuidMapping(szDstPath, pAddToUuidMap, pszUuidMapDir, pCfg);
495 return VINF_SUCCESS;
496}
497
498
499/**
500 * Worker that add the image file to the right place.
501 *
502 * @returns IPRT status code.
503 * @param pszPath Path to the image file.
504 * @param pszDstName Add to the cache under this name. Typically the
505 * filename part of @a pszPath.
506 * @param pCfg Configuration data.
507 * @param hLdrMod Image handle.
508 * @param pszExtraSuff Optional extra suffix. Mach-O dSYM hack.
509 * @param pszUuidMapDir Optional UUID map cache directory if the image
510 * should be mapped by UUID.
511 * The map is a Mac OS X debug feature supported by
512 * the two native debuggers gdb and lldb. Look for
513 * descriptions of DBGFileMappedPaths in the
514 * com.apple.DebugSymbols in the user defaults.
515 */
516static int rtDbgSymCacheAddImageFileWorker(const char *pszPath, const char *pszDstName, PCRTDBGSYMCACHEADDCFG pCfg,
517 RTLDRMOD hLdrMod, const char *pszExtraSuff, const char *pszUuidMapDir)
518{
519 /*
520 * Determine which subdirectory to put the files in.
521 */
522 RTUUID Uuid;
523 PRTUUID pUuid = NULL;
524 int rc;
525 char szSubDir[48];
526 RTLDRFMT enmFmt = RTLdrGetFormat(hLdrMod);
527 switch (enmFmt)
528 {
529 case RTLDRFMT_MACHO:
530 {
531 rc = RTLdrQueryProp(hLdrMod, RTLDRPROP_UUID, &Uuid, sizeof(Uuid));
532 if (RT_FAILURE(rc))
533 return RTMsgErrorRc(rc, "Error quering image UUID from image '%s': %Rrc", pszPath, rc);
534
535 rc = RTUuidToStr(&Uuid, szSubDir, sizeof(szSubDir));
536 if (RT_FAILURE(rc))
537 return RTMsgErrorRc(rc, "Error convering UUID for image '%s' to string: %Rrc", pszPath, rc);
538 pUuid = &Uuid;
539 break;
540 }
541
542 case RTLDRFMT_PE:
543 {
544 uint32_t uTimestamp;
545 rc = RTLdrQueryProp(hLdrMod, RTLDRPROP_TIMESTAMP_SECONDS, &uTimestamp, sizeof(uTimestamp));
546 if (RT_FAILURE(rc))
547 return RTMsgErrorRc(rc, "Error quering timestamp from image '%s': %Rrc", pszPath, rc);
548
549 size_t cbImage = RTLdrSize(hLdrMod);
550 if (cbImage == ~(size_t)0)
551 return RTMsgErrorRc(rc, "Error quering size of image '%s': %Rrc", pszPath, rc);
552
553 RTStrPrintf(szSubDir, sizeof(szSubDir), "%08X%x", uTimestamp, cbImage);
554 break;
555 }
556
557 case RTLDRFMT_AOUT:
558 return RTMsgErrorRc(VERR_NOT_SUPPORTED, "Caching of a.out image has not yet been implemented: %s", pszPath);
559 case RTLDRFMT_ELF:
560 return RTMsgErrorRc(VERR_NOT_SUPPORTED, "Caching of ELF image has not yet been implemented: %s", pszPath);
561 case RTLDRFMT_LX:
562 return RTMsgErrorRc(VERR_NOT_SUPPORTED, "Caching of LX image has not yet been implemented: %s", pszPath);
563 default:
564 return RTMsgErrorRc(VERR_NOT_SUPPORTED, "Unknown loader format for '%s': %d", pszPath, enmFmt);
565 }
566
567 /*
568 * Now add it.
569 */
570 return rtDbgSymCacheAddOneFile(pszPath, pszDstName, pszExtraSuff, szSubDir, pUuid, pszUuidMapDir, pCfg);
571}
572
573
574/**
575 * Adds what we think is an image file to the cache.
576 *
577 * @returns IPRT status code.
578 * @param pszPath Path to the image file.
579 * @param pszDstName Add to the cache under this name. Typically the
580 * filename part of @a pszPath.
581 * @param pszExtraSuff Optional extra suffix. Mach-O dSYM hack.
582 * @param pszUuidMapDir The UUID map subdirectory in the cache, if this is
583 * wanted, otherwise NULL.
584 * @param pCfg Configuration data.
585 */
586static int rtDbgSymCacheAddImageFile(const char *pszPath, const char *pszDstName, const char *pszExtraSuff,
587 const char *pszUuidMapDir, PCRTDBGSYMCACHEADDCFG pCfg)
588{
589 RTERRINFOSTATIC ErrInfo;
590
591 /*
592 * Use the loader to open the alleged image file. We need to open it with
593 * arch set to amd64 and x86_32 in order to handle FAT images from the mac
594 * guys (we should actually enumerate archs, but that's currently not
595 * implemented nor necessary for our current use).
596 */
597 /* Open it as AMD64. */
598 RTLDRMOD hLdrMod64;
599 int rc = RTLdrOpenEx(pszPath, RTLDR_O_FOR_DEBUG, RTLDRARCH_AMD64, &hLdrMod64, RTErrInfoInitStatic(&ErrInfo));
600 if (RT_FAILURE(rc))
601 {
602 if (rc != VERR_LDR_ARCH_MISMATCH)
603 {
604 if (rc != VERR_INVALID_EXE_SIGNATURE)
605 return RTMsgErrorRc(rc, "RTLdrOpen failed opening '%s' [arch=amd64]: %Rrc%s%s", pszPath, rc,
606 RTErrInfoIsSet(&ErrInfo.Core) ? " - " : "",
607 RTErrInfoIsSet(&ErrInfo.Core) ? ErrInfo.Core.pszMsg : "");
608
609 RTMsgInfo("Skipping '%s', no a recognizable image file...", pszPath);
610 return VINF_SUCCESS;
611 }
612 hLdrMod64 = NIL_RTLDRMOD;
613 }
614
615 /* Open it as X86. */
616 RTLDRMOD hLdrMod32;
617 rc = RTLdrOpenEx(pszPath, RTLDR_O_FOR_DEBUG, RTLDRARCH_X86_32, &hLdrMod32, RTErrInfoInitStatic(&ErrInfo));
618 if (RT_FAILURE(rc))
619 {
620 if (rc != VERR_LDR_ARCH_MISMATCH)
621 {
622 RTLdrClose(hLdrMod64);
623 return RTMsgErrorRc(rc, "RTLdrOpen failed opening '%s' [arch=x86]: %Rrc%s%s", pszPath, rc,
624 RTErrInfoIsSet(&ErrInfo.Core) ? " - " : "",
625 RTErrInfoIsSet(&ErrInfo.Core) ? ErrInfo.Core.pszMsg : "");
626 }
627 hLdrMod32 = NIL_RTLDRMOD;
628 }
629
630 /*
631 * Add the file.
632 */
633 if (hLdrMod32 == NIL_RTLDRMOD)
634 rc = rtDbgSymCacheAddImageFileWorker(pszPath, pszDstName, pCfg, hLdrMod64, pszExtraSuff, pszUuidMapDir);
635 else if (hLdrMod64 == NIL_RTLDRMOD)
636 rc = rtDbgSymCacheAddImageFileWorker(pszPath, pszDstName, pCfg, hLdrMod32, pszExtraSuff, pszUuidMapDir);
637 else
638 {
639 /*
640 * Do we need to add it once or twice?
641 */
642 RTLDRFMT enmFmt = RTLdrGetFormat(hLdrMod32);
643 bool fSame = enmFmt == RTLdrGetFormat(hLdrMod64);
644 if (fSame && enmFmt == RTLDRFMT_MACHO)
645 {
646 RTUUID Uuid32, Uuid64;
647 int rc32 = RTLdrQueryProp(hLdrMod32, RTLDRPROP_UUID, &Uuid32, sizeof(Uuid32));
648 int rc64 = RTLdrQueryProp(hLdrMod64, RTLDRPROP_UUID, &Uuid64, sizeof(Uuid64));
649 fSame = RT_SUCCESS(rc32) == RT_SUCCESS(rc64);
650 if (fSame && RT_SUCCESS(rc32))
651 fSame = RTUuidCompare(&Uuid32, &Uuid64) == 0;
652 }
653 else if (fSame && enmFmt == RTLDRFMT_PE)
654 {
655 fSame = RTLdrSize(hLdrMod32) == RTLdrSize(hLdrMod64);
656 if (fSame)
657 {
658 uint32_t uTimestamp32, uTimestamp64;
659 int rc32 = RTLdrQueryProp(hLdrMod32, RTLDRPROP_TIMESTAMP_SECONDS, &uTimestamp32, sizeof(uTimestamp32));
660 int rc64 = RTLdrQueryProp(hLdrMod64, RTLDRPROP_TIMESTAMP_SECONDS, &uTimestamp64, sizeof(uTimestamp64));
661 fSame = RT_SUCCESS(rc32) == RT_SUCCESS(rc64);
662 if (fSame && RT_SUCCESS(rc32))
663 fSame = uTimestamp32 == uTimestamp64;
664 }
665 }
666
667 rc = rtDbgSymCacheAddImageFileWorker(pszPath, pszDstName, pCfg, hLdrMod64, pszExtraSuff, pszUuidMapDir);
668 if (!fSame)
669 {
670 /** @todo should symlink or hardlink this second copy. */
671 int rc2 = rtDbgSymCacheAddImageFileWorker(pszPath, pszDstName, pCfg, hLdrMod32, pszExtraSuff, pszUuidMapDir);
672 if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
673 rc = rc2;
674 }
675 }
676
677 RTLdrClose(hLdrMod32);
678 RTLdrClose(hLdrMod64);
679 return VINF_SUCCESS;
680}
681
682
683/**
684 * Worker for rtDbgSymCacheAddDebugFile that adds a Mach-O debug file to the
685 * cache.
686 *
687 * @returns IPRT status code
688 * @param pszPath The path to the PDB file.
689 * @param pszDstName Add to the cache under this name. Typically the
690 * filename part of @a pszPath.
691 * @param pCfg The configuration.
692 * @param hFile Handle to the file.
693 */
694static int rtDbgSymCacheAddDebugMachO(const char *pszPath, const char *pszDstName, PCRTDBGSYMCACHEADDCFG pCfg)
695{
696 /* This shouldn't happen, figure out what to do if it does. */
697 RT_NOREF(pCfg, pszDstName);
698 return RTMsgErrorRc(VERR_NOT_IMPLEMENTED,
699 "'%s' is an OS X image file, did you point me to a file inside a .dSYM or .sym file?",
700 pszPath);
701}
702
703
704/**
705 * Worker for rtDbgSymCacheAddDebugFile that adds DBGs to the cache.
706 *
707 * @returns IPRT status code
708 * @param pszPath The path to the PDB file.
709 * @param pszDstName Add to the cache under this name. Typically the
710 * filename part of @a pszPath.
711 * @param pCfg The configuration.
712 * @param pHdr The DBG file header.
713 */
714static int rtDbgSymCacheAddDebugDbg(const char *pszPath, const char *pszDstName, PCRTDBGSYMCACHEADDCFG pCfg,
715 PCIMAGE_SEPARATE_DEBUG_HEADER pHdr)
716{
717 if ( pHdr->SizeOfImage == 0
718 || pHdr->SizeOfImage >= UINT32_MAX / 2
719 || pHdr->TimeDateStamp < 16
720 || pHdr->TimeDateStamp >= UINT32_MAX - 16
721 || pHdr->NumberOfSections >= UINT16_MAX / 2)
722 return RTMsgErrorRc(VERR_OUT_OF_RANGE,
723 "Bogus separate debug header in '%s': SizeOfImage=%#RX32 TimeDateStamp=%#RX32 NumberOfSections=%#RX16",
724 pszPath, pHdr->SizeOfImage, pHdr->TimeDateStamp, pHdr->NumberOfSections);
725 char szSubDir[32];
726 RTStrPrintf(szSubDir, sizeof(szSubDir), "%08X%x", pHdr->TimeDateStamp, pHdr->SizeOfImage);
727 return rtDbgSymCacheAddOneFile(pszPath, pszDstName, NULL, szSubDir, NULL, NULL, pCfg);
728}
729
730
731/**
732 * Worker for rtDbgSymCacheAddDebugFile that adds v7 PDBs to the cache.
733 *
734 * @returns IPRT status code
735 * @param pszPath The path to the PDB file.
736 * @param pszDstName Add to the cache under this name. Typically the
737 * filename part of @a pszPath.
738 * @param pCfg The configuration.
739 * @param hFile Handle to the file.
740 */
741static int rtDbgSymCacheAddDebugPdb(const char *pszPath, const char *pszDstName, PCRTDBGSYMCACHEADDCFG pCfg, RTFILE hFile)
742{
743 /*
744 * Open the PDB as a VFS.
745 */
746 RTVFSFILE hVfsFile = NIL_RTVFSFILE;
747 int rc = RTVfsFileFromRTFile(hFile, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE, true /*fLeaveOpen*/, &hVfsFile);
748 if (RT_SUCCESS(rc))
749 {
750 RTERRINFOSTATIC ErrInfo;
751 RTVFS hVfsPdb = NIL_RTVFS;
752 rc = RTFsPdbVolOpen(hVfsFile, 0, &hVfsPdb, RTErrInfoInitStatic(&ErrInfo));
753 RTVfsFileRelease(hVfsFile);
754 if (RT_SUCCESS(rc))
755 {
756 /*
757 * Get the version.
758 */
759 char szPdbVer[16];
760 rc = RTVfsQueryLabel(hVfsPdb, true /*fAlternative*/, szPdbVer, sizeof(szPdbVer), NULL);
761 if (RT_SUCCESS(rc))
762 {
763 /*
764 * Read the PDB metadata header.
765 */
766 RTVFSFILE hVfsFileHdr = NIL_RTVFSFILE;
767 rc = RTVfsFileOpen(hVfsPdb, "1", RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE, &hVfsFileHdr);
768 if (RT_SUCCESS(rc))
769 {
770 union
771 {
772 uint8_t abHdr[128];
773 RTPDB70NAMES Hdr70;
774 RTPDB20NAMES Hdr20;
775 } uBuf = {{0}};
776 size_t cbHdr = 0;
777 rc = RTVfsFileRead(hVfsFileHdr, &uBuf, sizeof(uBuf), &cbHdr);
778 RTVfsFileRelease(hVfsFileHdr);
779 if (RT_SUCCESS(rc))
780 {
781 /*
782 * Use the header to determine the subdirectory name.
783 */
784 char szSubDir[48];
785 if (strcmp(szPdbVer, "pdb-v7") == 0)
786 RTStrPrintf(szSubDir, sizeof(szSubDir), "%08X%04X%04X%02X%02X%02X%02X%02X%02X%02X%02X%x",
787 uBuf.Hdr70.Uuid.Gen.u32TimeLow,
788 uBuf.Hdr70.Uuid.Gen.u16TimeMid,
789 uBuf.Hdr70.Uuid.Gen.u16TimeHiAndVersion,
790 uBuf.Hdr70.Uuid.Gen.u8ClockSeqHiAndReserved,
791 uBuf.Hdr70.Uuid.Gen.u8ClockSeqLow,
792 uBuf.Hdr70.Uuid.Gen.au8Node[0],
793 uBuf.Hdr70.Uuid.Gen.au8Node[1],
794 uBuf.Hdr70.Uuid.Gen.au8Node[2],
795 uBuf.Hdr70.Uuid.Gen.au8Node[3],
796 uBuf.Hdr70.Uuid.Gen.au8Node[4],
797 uBuf.Hdr70.Uuid.Gen.au8Node[5],
798 uBuf.Hdr70.uAge);
799 else if (strcmp(szPdbVer, "pdb-v2") == 0)
800 RTStrPrintf(szSubDir, sizeof(szSubDir), "%08X%x", uBuf.Hdr20.uTimestamp, uBuf.Hdr20.uAge);
801 else
802 {
803 szSubDir[0] = '\0';
804 rc = RTMsgErrorRc(VERR_VERSION_MISMATCH, "Unsupported PDB version string: %s", szPdbVer);
805 }
806
807 /*
808 * Add it to the symbol cache if that went well.
809 */
810 if (RT_SUCCESS(rc))
811 rc = rtDbgSymCacheAddOneFile(pszPath, pszDstName, NULL, szSubDir, NULL, NULL, pCfg);
812 }
813 else
814 RTMsgErrorRc(rc, "RTVfsFileRead('1',) failed on '%s': %Rrc", pszPath, rc);
815 }
816 else
817 RTMsgErrorRc(rc, "RTVfsFileOpen('1',) failed on '%s': %Rrc", pszPath, rc);
818 }
819 else
820 RTMsgErrorRc(rc, "RTVfsQueryLabel failed on '%s': %Rrc", pszPath, rc);
821 RTVfsRelease(hVfsPdb);
822 }
823 else
824 RTMsgErrorRc(rc, "RTFsPdbVolOpen failed on '%s': %Rrc%#RTeim", pszPath, rc, &ErrInfo.Core);
825 }
826 else
827 RTMsgErrorRc(rc, "RTVfsFileFromRTFile failed on '%s': %Rrc", pszPath, rc);
828 return rc;
829
830}
831
832
833/**
834 * Adds a debug file to the cache.
835 *
836 * @returns IPRT status code
837 * @param pszPath The path to the debug file in question.
838 * @param pszDstName Add to the cache under this name. Typically the
839 * filename part of @a pszPath.
840 * @param pCfg The configuration.
841 */
842static int rtDbgSymCacheAddDebugFile(const char *pszPath, const char *pszDstName, PCRTDBGSYMCACHEADDCFG pCfg)
843{
844 /*
845 * Need to extract an identifier of sorts here in order to put them in
846 * the right place in the cache. Currently only implemnted for Mach-O
847 * files since these use executable containers.
848 *
849 * We take a look at the file header in hope to figure out what to do
850 * with the file.
851 */
852 RTFILE hFile;
853 int rc = RTFileOpen(&hFile, pszPath, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
854 if (RT_FAILURE(rc))
855 return RTMsgErrorRc(rc, "Error opening '%s': %Rrc", pszPath, rc);
856
857 uint64_t cbFile = 0;
858 rc = RTFileQuerySize(hFile, &cbFile);
859 if (RT_SUCCESS(rc))
860 {
861
862 union
863 {
864 uint64_t au64[16];
865 uint32_t au32[16];
866 uint16_t au16[32];
867 uint8_t ab[64];
868 RTPDB70HDR Pdb70Hdr;
869 RTPDB20HDR Pdb20Hdr;
870 IMAGE_SEPARATE_DEBUG_HEADER DbgHdr;
871 } uBuf;
872 rc = RTFileRead(hFile, &uBuf, sizeof(uBuf), NULL);
873 if (RT_SUCCESS(rc))
874 {
875 /*
876 * Look for magics and call workers.
877 */
878 if ( memcmp(uBuf.Pdb70Hdr.szSignature, RTPDB_SIGNATURE_700, sizeof(uBuf.Pdb70Hdr.szSignature)) == 0
879 || memcmp(uBuf.Pdb20Hdr.szSignature, RTPDB_SIGNATURE_200, sizeof(uBuf.Pdb20Hdr.szSignature)) == 0)
880 rc = rtDbgSymCacheAddDebugPdb(pszPath, pszDstName, pCfg, hFile);
881 else if (uBuf.au16[0] == IMAGE_SEPARATE_DEBUG_SIGNATURE)
882 rc = rtDbgSymCacheAddDebugDbg(pszPath, pszDstName, pCfg, &uBuf.DbgHdr);
883 else if ( uBuf.au32[0] == IMAGE_FAT_SIGNATURE
884 || uBuf.au32[0] == IMAGE_FAT_SIGNATURE_OE
885 || uBuf.au32[0] == IMAGE_MACHO32_SIGNATURE
886 || uBuf.au32[0] == IMAGE_MACHO64_SIGNATURE
887 || uBuf.au32[0] == IMAGE_MACHO32_SIGNATURE_OE
888 || uBuf.au32[0] == IMAGE_MACHO64_SIGNATURE_OE)
889 rc = rtDbgSymCacheAddDebugMachO(pszPath, pszDstName, pCfg);
890 else
891 rc = RTMsgErrorRc(VERR_INVALID_MAGIC, "Unsupported debug file '%s' magic: %#010x", pszPath, uBuf.au32[0]);
892 }
893 else
894 rc = RTMsgErrorRc(rc, "Error reading '%s': %Rrc", pszPath, rc);
895 }
896 else
897 rc = RTMsgErrorRc(rc, "Error query size of '%s': %Rrc", pszPath, rc);
898
899 /* close the file. */
900 int rc2 = RTFileClose(hFile);
901 if (RT_FAILURE(rc2))
902 {
903 RTMsgError("Error closing '%s': %Rrc", pszPath, rc2);
904 if (RT_SUCCESS(rc))
905 rc = rc2;
906 }
907 return rc;
908}
909
910
911/**
912 * Constructs the path to the file instide the bundle that we're keen on.
913 *
914 * @returns IPRT status code.
915 * @param pszPath Path to the bundle on input, on successful
916 * return it's the path to the desired file. This
917 * a RTPATH_MAX size buffer.
918 * @param cchPath The length of the path up to the bundle name.
919 * @param cchName The length of the bundle name.
920 * @param pszSubDir The bundle subdirectory the file lives in.
921 * @param papszSuffixes Pointer to an array of bundle suffixes.
922 */
923static int rtDbgSymCacheConstructBundlePath(char *pszPath, size_t cchPath, size_t cchName, const char *pszSubDir,
924 const char * const *papszSuffixes)
925{
926 /*
927 * Calc the name without the bundle extension.
928 */
929 size_t const cchOrgName = cchName;
930 const char *pszEnd = &pszPath[cchPath + cchName];
931 for (unsigned i = 0; papszSuffixes[i]; i++)
932 {
933 Assert(papszSuffixes[i][0] == '.');
934 size_t cchSuff = strlen(papszSuffixes[i]);
935 if ( cchSuff < cchName
936 && !memcmp(&pszEnd[-(ssize_t)cchSuff], papszSuffixes[i], cchSuff))
937 {
938 cchName -= cchSuff;
939 break;
940 }
941 }
942
943 /*
944 * Check the immediate directory first, in case it's layed out like
945 * IOPCIFamily.kext.
946 */
947 int rc = RTPathAppendEx(pszPath, RTPATH_MAX, &pszPath[cchPath], cchName, RTPATH_STR_F_STYLE_HOST);
948 if (RT_FAILURE(rc) || !RTFileExists(pszPath))
949 {
950 /*
951 * Not there, ok then try the given subdirectory + name.
952 */
953 pszPath[cchPath + cchOrgName] = '\0';
954 rc = RTPathAppend(pszPath, RTPATH_MAX, pszSubDir);
955 if (RT_SUCCESS(rc))
956 rc = RTPathAppendEx(pszPath, RTPATH_MAX, &pszPath[cchPath], cchName, RTPATH_STR_F_STYLE_HOST);
957 if (RT_FAILURE(rc))
958 {
959 pszPath[cchPath + cchOrgName] = '\0';
960 return RTMsgErrorRc(rc, "Error constructing image bundle path for '%s': %Rrc", pszPath, rc);
961 }
962 }
963
964 return VINF_SUCCESS;
965}
966
967
968/**
969 * Adds a image bundle of some sort.
970 *
971 * @returns IPRT status code.
972 * @param pszPath Path to the bundle. This a RTPATH_MAX size
973 * buffer that we can write to when creating the
974 * path to the file inside the bundle that we're
975 * interested in.
976 * @param cchPath The length of the path up to the bundle name.
977 * @param cchName The length of the bundle name.
978 * @param pszDstName Add to the cache under this name, NULL if not
979 * specified.
980 * @param pDirEntry The directory entry buffer, for handling bundle
981 * within bundle recursion.
982 * @param pCfg The configuration.
983 */
984static int rtDbgSymCacheAddImageBundle(char *pszPath, size_t cchPath, size_t cchName, const char *pszDstName,
985 PRTDIRENTRYEX pDirEntry, PCRTDBGSYMCACHEADDCFG pCfg)
986{
987 /*
988 * Assuming these are kexts or simple applications, we only add the image
989 * file itself to the cache. No Info.plist or other files.
990 */
991 /** @todo consider looking for Frameworks and handling framework bundles. */
992 int rc = rtDbgSymCacheConstructBundlePath(pszPath, cchPath, cchName, "Contents/MacOS/", g_apszBundleSuffixes);
993 if (RT_SUCCESS(rc))
994 {
995 if (!pszDstName)
996 pszDstName = RTPathFilename(pszPath);
997 rc = rtDbgSymCacheAddImageFile(pszPath, pszDstName, NULL, RTDBG_CACHE_UUID_MAP_DIR_IMAGES, pCfg);
998 }
999
1000 /*
1001 * Look for plugins and other sub-bundles.
1002 */
1003 if (pCfg->fRecursive)
1004 {
1005 static char const * const s_apszSubBundleDirs[] =
1006 {
1007 "Contents/Plugins/",
1008 /** @todo Frameworks ++ */
1009 };
1010 for (uint32_t i = 0; i < RT_ELEMENTS(s_apszSubBundleDirs); i++)
1011 {
1012 pszPath[cchPath + cchName] = '\0';
1013 int rc2 = RTPathAppend(pszPath, RTPATH_MAX - 1, s_apszSubBundleDirs[i]);
1014 if (RT_SUCCESS(rc2))
1015 {
1016 if (RTDirExists(pszPath))
1017 {
1018 size_t cchPath2 = strlen(pszPath);
1019 if (!RTPATH_IS_SLASH(pszPath[cchPath2 - 1]))
1020 {
1021 pszPath[cchPath2++] = RTPATH_SLASH;
1022 pszPath[cchPath2] = '\0';
1023 }
1024 rc2 = rtDbgSymCacheAddDirWorker(pszPath, cchPath2, pDirEntry, pCfg);
1025 }
1026 }
1027 else
1028 {
1029 pszPath[cchPath + cchName] = '\0';
1030 RTMsgError("Error constructing bundle subdir path for '%s' + '%s': %Rrc", pszPath, s_apszSubBundleDirs[i], rc);
1031 }
1032 if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
1033 rc = rc2;
1034 }
1035 }
1036
1037 return rc;
1038}
1039
1040
1041/**
1042 * Adds a debug bundle.
1043 *
1044 * @returns IPRT status code.
1045 * @param pszPath Path to the bundle. This a RTPATH_MAX size
1046 * buffer that we can write to when creating the
1047 * path to the file inside the bundle that we're
1048 * interested in.
1049 * @param cchPath The length of the path up to the bundle name.
1050 * @param cchName The length of the bundle name.
1051 * @param pszDstName Add to the cache under this name, NULL if not
1052 * specified.
1053 * @param pCfg The configuration.
1054 */
1055static int rtDbgSymCacheAddDebugBundle(char *pszPath, size_t cchPath, size_t cchName, const char *pszDstName,
1056 PCRTDBGSYMCACHEADDCFG pCfg)
1057{
1058 /*
1059 * The current policy is not to add the whole .dSYM (or .sym) bundle, but
1060 * rather just the dwarf image instide it. The <UUID>.plist and Info.plist
1061 * files generally doesn't contain much extra information that's really
1062 * necessary, I hope. At least this is what the uuidmap example in the
1063 * lldb hints at (it links to the dwarf file, not the .dSYM dir).
1064 *
1065 * To avoid confusion with a .dSYM bundle, as well as collision with the
1066 * image file, we use .dwarf suffix for the file.
1067 *
1068 * For details on the uuid map see rtDbgSymCacheAddImageFile as well as
1069 * http://lldb.llvm.org/symbols.html .
1070 *
1071 * ASSUMES bundles contains Mach-O DWARF files.
1072 */
1073 int rc = rtDbgSymCacheConstructBundlePath(pszPath, cchPath, cchName, "Contents/Resources/DWARF/", g_apszDSymBundleSuffixes);
1074 if (RT_SUCCESS(rc))
1075 {
1076 if (!pszDstName)
1077 pszDstName = RTPathFilename(pszPath);
1078 rc = rtDbgSymCacheAddImageFile(pszPath, pszDstName, RTDBG_CACHE_DSYM_FILE_SUFFIX, RTDBG_CACHE_UUID_MAP_DIR_DSYMS, pCfg);
1079 }
1080 return rc;
1081}
1082
1083
1084/**
1085 * Figure the type of a file/dir based on path and FS object info.
1086 *
1087 * @returns The type.
1088 * @param pszPath The path to the file/dir.
1089 * @param pObjInfo The object information, symlinks followed.
1090 */
1091static RTDBGSYMCACHEFILETYPE rtDbgSymCacheFigureType2(const char *pszPath, PCRTFSOBJINFO pObjInfo)
1092{
1093 const char *pszName = RTPathFilename(pszPath);
1094 const char *pszExt = RTPathSuffix(pszName);
1095 if (pszExt)
1096 pszExt++;
1097 else
1098 pszExt = "";
1099
1100 if ( RTFS_IS_DIRECTORY(pObjInfo->Attr.fMode)
1101 || (pObjInfo->Attr.fMode & RTFS_DOS_DIRECTORY)) /** @todo OS X samba reports reparse points in /Volumes/ that we cannot resolve. */
1102 {
1103 /* Skip directories shouldn't bother with. */
1104 if ( !RTStrICmp(pszName, ".Trashes")
1105 || !RTStrICmp(pszName, ".$RESCYCLE.BIN")
1106 || !RTStrICmp(pszName, "System.kext") /* Usually only plugins here, so skip it. */
1107 )
1108 return RTDBGSYMCACHEFILETYPE_IGNORE;
1109
1110 /* Directories can also be bundles on the mac. */
1111 if (!RTStrICmp(pszExt, "dSYM"))
1112 return RTDBGSYMCACHEFILETYPE_DEBUG_BUNDLE;
1113
1114 for (unsigned i = 0; i < RT_ELEMENTS(g_apszBundleSuffixes) - 1; i++)
1115 if (!RTStrICmp(pszExt, &g_apszBundleSuffixes[i][1]))
1116 return RTDBGSYMCACHEFILETYPE_IMAGE_BUNDLE;
1117
1118 return RTDBGSYMCACHEFILETYPE_DIR;
1119 }
1120
1121 if (!RTFS_IS_FILE(pObjInfo->Attr.fMode))
1122 return RTDBGSYMCACHEFILETYPE_INVALID;
1123
1124 /* Select image vs debug info based on extension. */
1125 if ( !RTStrICmp(pszExt, "pdb")
1126 || !RTStrICmp(pszExt, "dbg")
1127 || !RTStrICmp(pszExt, "sym")
1128 || !RTStrICmp(pszExt, "dwo")
1129 || !RTStrICmp(pszExt, "dwp")
1130 || !RTStrICmp(pszExt, "debug")
1131 || !RTStrICmp(pszExt, "dsym")
1132 || !RTStrICmp(pszExt, "dwarf")
1133 || !RTStrICmp(pszExt, "map")
1134 || !RTStrICmp(pszExt, "cv"))
1135 return RTDBGSYMCACHEFILETYPE_DEBUG_FILE;
1136
1137 /* Filter out a bunch of files which obviously shouldn't be images. */
1138 if ( !RTStrICmp(pszExt, "txt")
1139 || !RTStrICmp(pszExt, "html")
1140 || !RTStrICmp(pszExt, "htm")
1141 || !RTStrICmp(pszExt, "rtf")
1142 || !RTStrICmp(pszExt, "zip")
1143 || !RTStrICmp(pszExt, "doc")
1144 || !RTStrICmp(pszExt, "gz")
1145 || !RTStrICmp(pszExt, "bz2")
1146 || !RTStrICmp(pszExt, "xz")
1147 || !RTStrICmp(pszExt, "kmk")
1148 || !RTStrICmp(pszExt, "c")
1149 || !RTStrICmp(pszExt, "cpp")
1150 || !RTStrICmp(pszExt, "h")
1151 || !RTStrICmp(pszExt, "m")
1152 || !RTStrICmp(pszExt, "mm")
1153 || !RTStrICmp(pszExt, "asm")
1154 || !RTStrICmp(pszExt, "S")
1155 || !RTStrICmp(pszExt, "inc")
1156 || !RTStrICmp(pszExt, "sh")
1157 )
1158 return RTDBGSYMCACHEFILETYPE_IGNORE;
1159 if ( !RTStrICmp(pszName, "Makefile")
1160 || !RTStrICmp(pszName, "GNUmakefile")
1161 || !RTStrICmp(pszName, "createsymbolfiles")
1162 || !RTStrICmp(pszName, "kgmacros")
1163 )
1164 return RTDBGSYMCACHEFILETYPE_IGNORE;
1165
1166 return RTDBGSYMCACHEFILETYPE_IMAGE_FILE;
1167}
1168
1169
1170/**
1171 * Figure file type based on name, will stat the file/dir.
1172 *
1173 * @returns File type.
1174 * @param pszPath The path to the file/dir to figure.
1175 */
1176static RTDBGSYMCACHEFILETYPE rtDbgSymCacheFigureType(const char *pszPath)
1177{
1178 const char *pszName = RTPathFilename(pszPath);
1179
1180 /* Trailing slash. */
1181 if (!pszName)
1182 return RTDBGSYMCACHEFILETYPE_DIR;
1183
1184 /* Wildcard means listing directory and filtering. */
1185 if (strpbrk(pszName, "?*"))
1186 return RTDBGSYMCACHEFILETYPE_DIR_FILTER;
1187
1188 /* Get object info, following links. */
1189 RTFSOBJINFO ObjInfo;
1190 int rc = RTPathQueryInfoEx(pszPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK);
1191 if (RT_FAILURE(rc))
1192 return RTDBGSYMCACHEFILETYPE_INVALID;
1193 return rtDbgSymCacheFigureType2(pszPath, &ObjInfo);
1194}
1195
1196
1197/**
1198 * Recursive worker for rtDbgSymCacheAddDir, for minimal stack wasting.
1199 *
1200 * @returns IPRT status code (fully bitched).
1201 * @param pszPath Pointer to a RTPATH_MAX size buffer containing
1202 * the path to the current directory ending with a
1203 * slash.
1204 * @param cchPath The size of the current directory path.
1205 * @param pDirEntry Pointer to the RTDIRENTRYEX structure to use.
1206 * @param pCfg The configuration.
1207 */
1208static int rtDbgSymCacheAddDirWorker(char *pszPath, size_t cchPath, PRTDIRENTRYEX pDirEntry, PCRTDBGSYMCACHEADDCFG pCfg)
1209{
1210 /*
1211 * Open the directory.
1212 */
1213 RTDIR hDir;
1214 int rc, rc2;
1215 if (pCfg->pszFilter)
1216 {
1217 rc = RTStrCopy(&pszPath[cchPath], RTPATH_MAX - cchPath, pCfg->pszFilter);
1218 if (RT_FAILURE(rc))
1219 {
1220 pszPath[cchPath] = '\0';
1221 return RTMsgErrorRc(rc, "Filename too long (%Rrc): '%s" RTPATH_SLASH_STR "%s'", rc, pszPath, pCfg->pszFilter);
1222 }
1223 rc = RTDirOpenFiltered(&hDir, pszPath, RTDIRFILTER_WINNT, 0 /*fFlags*/);
1224 }
1225 else
1226 rc = RTDirOpen(&hDir, pszPath);
1227 if (RT_FAILURE(rc))
1228 return RTMsgErrorRc(rc, "RTDirOpen%s failed on '%s': %Rrc", pCfg->pszFilter ? "Filtered" : "", pszPath, rc);
1229
1230 /*
1231 * Enumerate the files.
1232 */
1233 for (;;)
1234 {
1235 rc2 = RTDirReadEx(hDir, pDirEntry, NULL, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK);
1236 if (RT_FAILURE(rc2))
1237 {
1238 pszPath[cchPath] = '\0';
1239 if (rc2 != VERR_NO_MORE_FILES)
1240 {
1241 RTMsgError("RTDirReadEx failed in '%s': %Rrc\n", pszPath, rc2);
1242 rc = rc2;
1243 }
1244 break;
1245 }
1246
1247 /* Skip dot and dot-dot. */
1248 if (RTDirEntryExIsStdDotLink(pDirEntry))
1249 continue;
1250
1251 /* Construct a full path. */
1252 rc = RTStrCopy(&pszPath[cchPath], RTPATH_MAX, pDirEntry->szName);
1253 if (RT_FAILURE(rc))
1254 {
1255 pszPath[cchPath] = '\0';
1256 RTMsgError("File name too long in '%s': '%s' (%Rrc)", pszPath, pDirEntry->szName, rc);
1257 break;
1258 }
1259
1260 switch (rtDbgSymCacheFigureType2(pszPath, &pDirEntry->Info))
1261 {
1262 case RTDBGSYMCACHEFILETYPE_DIR:
1263 if (!pCfg->fRecursive)
1264 RTMsgInfo("Skipping directory '%s'...", pszPath);
1265 else
1266 {
1267 if (cchPath + pDirEntry->cbName + 3 <= RTPATH_MAX)
1268 {
1269 pszPath[cchPath + pDirEntry->cbName] = RTPATH_SLASH;
1270 pszPath[cchPath + pDirEntry->cbName + 1] = '\0';
1271 rc2 = rtDbgSymCacheAddDirWorker(pszPath, cchPath + pDirEntry->cbName + 1, pDirEntry, pCfg);
1272 }
1273 else
1274 {
1275 RTMsgError("File name too long in '%s': '%s' (%Rrc)", pszPath, pDirEntry->szName, rc);
1276 rc2 = VERR_FILENAME_TOO_LONG;
1277 }
1278 }
1279 break;
1280
1281 case RTDBGSYMCACHEFILETYPE_DEBUG_FILE:
1282 rc2 = rtDbgSymCacheAddDebugFile(pszPath, pDirEntry->szName, pCfg);
1283 break;
1284
1285 case RTDBGSYMCACHEFILETYPE_IMAGE_FILE:
1286 rc2 = rtDbgSymCacheAddImageFile(pszPath, pDirEntry->szName, NULL /*pszExtraSuff*/, RTDBG_CACHE_UUID_MAP_DIR_IMAGES, pCfg);
1287 break;
1288
1289 case RTDBGSYMCACHEFILETYPE_DEBUG_BUNDLE:
1290 rc2 = rtDbgSymCacheAddDebugBundle(pszPath, cchPath, pDirEntry->cbName, NULL /*pszDstName*/, pCfg);
1291 break;
1292
1293 case RTDBGSYMCACHEFILETYPE_IMAGE_BUNDLE:
1294 rc2 = rtDbgSymCacheAddImageBundle(pszPath, cchPath, pDirEntry->cbName, NULL /*pszDstName*/, pDirEntry, pCfg);
1295 break;
1296
1297 case RTDBGSYMCACHEFILETYPE_DIR_FILTER:
1298 case RTDBGSYMCACHEFILETYPE_INVALID:
1299 rc2 = RTMsgErrorRc(VERR_INTERNAL_ERROR_2, "Invalid: '%s'", pszPath);
1300 break;
1301
1302 case RTDBGSYMCACHEFILETYPE_IGNORE:
1303 rc2 = VINF_SUCCESS;
1304 break;
1305 }
1306
1307 if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
1308 rc = rc2;
1309 }
1310
1311 /*
1312 * Clean up.
1313 */
1314 rc2 = RTDirClose(hDir);
1315 if (RT_FAILURE(rc2))
1316 {
1317 RTMsgError("RTDirClose failed in '%s': %Rrc", pszPath, rc);
1318 rc = rc2;
1319 }
1320 return rc;
1321}
1322
1323
1324/**
1325 * Adds a directory.
1326 *
1327 * @returns IPRT status code (fully bitched).
1328 * @param pszPath The directory path.
1329 * @param pCfg The configuration.
1330 */
1331static int rtDbgSymCacheAddDir(const char *pszPath, PCRTDBGSYMCACHEADDCFG pCfg)
1332{
1333 /*
1334 * Set up the path buffer, stripping any filter.
1335 */
1336 char szPath[RTPATH_MAX];
1337 int rc = RTStrCopy(szPath, sizeof(szPath) - 2, pszPath);
1338 if (RT_FAILURE(rc))
1339 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Path too long: '%s'", pszPath);
1340
1341 size_t cchPath = strlen(pszPath);
1342 if (!cchPath)
1343 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Path empty: '%s'", pszPath);
1344
1345 if (pCfg->pszFilter)
1346 szPath[cchPath - strlen(pCfg->pszFilter)] = '\0';
1347 cchPath = RTPathStripTrailingSlash(szPath);
1348 if (!RTPATH_IS_SEP(pszPath[cchPath - 1]))
1349 {
1350 szPath[cchPath++] = RTPATH_SLASH;
1351 szPath[cchPath] = '\0';
1352 }
1353
1354 /*
1355 * Let the worker do the rest.
1356 */
1357 RTDIRENTRYEX DirEntry;
1358 return rtDbgSymCacheAddDirWorker(szPath, cchPath, &DirEntry, pCfg);
1359}
1360
1361
1362/**
1363 * Adds a file or directory.
1364 *
1365 * @returns Program exit code.
1366 * @param pszPath The user supplied path to the file or directory.
1367 * @param pszCache The path to the cache.
1368 * @param fRecursive Whether to process directories recursively.
1369 * @param fOverwriteOnConflict Whether to overwrite existing cache entry on
1370 * conflict, or just leave it.
1371 */
1372static RTEXITCODE rtDbgSymCacheAddFileOrDir(const char *pszPath, const char *pszCache, bool fRecursive,
1373 bool fOverwriteOnConflict)
1374{
1375 RT_NOREF1(fOverwriteOnConflict);
1376 RTDBGSYMCACHEADDCFG Cfg;
1377 Cfg.fRecursive = fRecursive;
1378 Cfg.pszCache = pszCache;
1379 Cfg.pszFilter = NULL;
1380
1381 /* If the filename contains an equal ('=') char, treat the left as the file
1382 to add tne right part as the name to add it under (handy for kernels). */
1383 char *pszFree = NULL;
1384 const char *pszDstName = RTPathFilename(pszPath);
1385 const char *pszEqual = pszDstName ? strchr(pszDstName, '=') : NULL;
1386 if (pszEqual)
1387 {
1388 pszPath = pszFree = RTStrDupN(pszPath, pszEqual - pszPath);
1389 if (!pszFree)
1390 return RTMsgErrorExitFailure("out of memory!\n");
1391 pszDstName = pszEqual + 1;
1392 if (!*pszDstName)
1393 return RTMsgErrorExitFailure("add-as filename is empty!\n");
1394 }
1395
1396 int rc;
1397 RTDBGSYMCACHEFILETYPE enmType = rtDbgSymCacheFigureType(pszPath);
1398 switch (enmType)
1399 {
1400 default:
1401 case RTDBGSYMCACHEFILETYPE_INVALID:
1402 rc = RTMsgErrorRc(VERR_INVALID_PARAMETER, "Invalid: '%s'", pszPath);
1403 break;
1404
1405 case RTDBGSYMCACHEFILETYPE_DIR_FILTER:
1406 Cfg.pszFilter = RTPathFilename(pszPath);
1407 RT_FALL_THRU();
1408 case RTDBGSYMCACHEFILETYPE_DIR:
1409 if (!pszEqual)
1410 rc = rtDbgSymCacheAddDir(pszPath, &Cfg);
1411 else
1412 rc = RTMsgErrorRc(VERR_INVALID_PARAMETER, "Add-as filename is not applicable to directories!");
1413 break;
1414
1415 case RTDBGSYMCACHEFILETYPE_DEBUG_FILE:
1416 rc = rtDbgSymCacheAddDebugFile(pszPath, pszDstName, &Cfg);
1417 break;
1418
1419 case RTDBGSYMCACHEFILETYPE_IMAGE_FILE:
1420 rc = rtDbgSymCacheAddImageFile(pszPath, pszDstName, NULL /*pszExtraSuff*/, RTDBG_CACHE_UUID_MAP_DIR_IMAGES, &Cfg);
1421 break;
1422
1423 case RTDBGSYMCACHEFILETYPE_DEBUG_BUNDLE:
1424 case RTDBGSYMCACHEFILETYPE_IMAGE_BUNDLE:
1425 {
1426 size_t cchPath = strlen(pszPath);
1427 size_t cchFilename = strlen(RTPathFilename(pszPath));
1428 char szPathBuf[RTPATH_MAX];
1429 if (cchPath < sizeof(szPathBuf))
1430 {
1431 memcpy(szPathBuf, pszPath, cchPath + 1);
1432 if (enmType == RTDBGSYMCACHEFILETYPE_DEBUG_BUNDLE)
1433 rc = rtDbgSymCacheAddDebugBundle(szPathBuf, cchPath - cchFilename, cchFilename,
1434 pszEqual ? pszDstName : NULL, &Cfg);
1435 else
1436 {
1437 RTDIRENTRYEX DirEntry;
1438 rc = rtDbgSymCacheAddImageBundle(szPathBuf, cchPath - cchFilename, cchFilename,
1439 pszEqual ? pszDstName : NULL, &DirEntry, &Cfg);
1440 }
1441 }
1442 else
1443 rc = RTMsgErrorRc(VERR_FILENAME_TOO_LONG, "Filename too long: '%s'", pszPath);
1444 break;
1445 }
1446
1447 case RTDBGSYMCACHEFILETYPE_IGNORE:
1448 rc = RTMsgErrorRc(VERR_INVALID_PARAMETER, "Invalid file: '%s'", pszPath);
1449 break;
1450 }
1451
1452 RTStrFree(pszFree);
1453 return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1454}
1455
1456
1457/**
1458 * Handles the 'add' command.
1459 *
1460 * @returns Program exit code.
1461 * @param pszArg0 The program name.
1462 * @param cArgs The number of arguments to the 'add' command.
1463 * @param papszArgs The argument vector, starting after 'add'.
1464 */
1465static RTEXITCODE rtDbgSymCacheCmdAdd(const char *pszArg0, int cArgs, char **papszArgs)
1466{
1467 /*
1468 * Parse the command line.
1469 */
1470 static RTGETOPTDEF const s_aOptions[] =
1471 {
1472 { "--recursive", 'R', RTGETOPT_REQ_NOTHING },
1473 { "--no-recursive", 'n', RTGETOPT_REQ_NOTHING },
1474 { "--overwrite-on-conflict", 'o', RTGETOPT_REQ_NOTHING },
1475 };
1476
1477 const char *pszCache = NULL;
1478 bool fRecursive = false;
1479 bool fOverwriteOnConflict = false;
1480
1481 RTGETOPTSTATE State;
1482 int rc = RTGetOptInit(&State, cArgs, papszArgs, &s_aOptions[0], RT_ELEMENTS(s_aOptions), 0, RTGETOPTINIT_FLAGS_OPTS_FIRST);
1483 if (RT_FAILURE(rc))
1484 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc", rc);
1485
1486 //uint32_t cAdded = 0;
1487 RTGETOPTUNION ValueUnion;
1488 int chOpt;
1489 while ((chOpt = RTGetOpt(&State, &ValueUnion)) != 0)
1490 {
1491 switch (chOpt)
1492 {
1493 case 'R':
1494 fRecursive = true;
1495 break;
1496
1497 case 'n':
1498 fRecursive = false;
1499 break;
1500
1501 case 'o':
1502 fOverwriteOnConflict = true;
1503 break;
1504
1505 case VINF_GETOPT_NOT_OPTION:
1506 /* The first non-option is a cache directory. */
1507 if (!pszCache)
1508 {
1509 pszCache = ValueUnion.psz;
1510 if (!RTPathExists(pszCache))
1511 {
1512 rc = RTDirCreate(pszCache, 0755, RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL);
1513 if (RT_FAILURE(rc))
1514 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Error creating cache directory '%s': %Rrc", pszCache, rc);
1515 }
1516 else if (!RTDirExists(pszCache))
1517 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Specified cache directory is not a directory: '%s'", pszCache);
1518 }
1519 /* Subsequent non-options are files to be added to the cache. */
1520 else
1521 {
1522 RTEXITCODE rcExit = rtDbgSymCacheAddFileOrDir(ValueUnion.psz, pszCache, fRecursive, fOverwriteOnConflict);
1523 if (rcExit != RTEXITCODE_FAILURE)
1524 return rcExit;
1525 }
1526 break;
1527
1528 case 'h':
1529 return rtDbgSymCacheUsage(pszArg0, "add");
1530 case 'V':
1531 return rtDbgSymCacheVersion();
1532 default:
1533 return RTGetOptPrintError(chOpt, &ValueUnion);
1534 }
1535 }
1536
1537 if (!pszCache)
1538 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No cache directory or files to add were specified.");
1539 return RTEXITCODE_SUCCESS;
1540}
1541
1542
1543/**
1544 * Debug info + external path for the 'get' command.
1545 */
1546typedef struct MYDBGINFO
1547{
1548 /** The kind of debug info. */
1549 RTLDRDBGINFOTYPE enmType;
1550 /** The CRC32 of the external file (RTLDRDBGINFOTYPE_DWARF_DWO). */
1551 uint32_t uDwoCrc32;
1552 /** The PE image size (RTLDRDBGINFOTYPE_CODEVIEW_DBG,
1553 * RTLDRDBGINFOTYPE_CODEVIEW_PDB20, RTLDRDBGINFOTYPE_CODEVIEW_PDB70 (,
1554 * RTLDRDBGINFOTYPE_CODEVIEW, RTLDRDBGINFOTYPE_COFF)). */
1555 uint32_t cbImage;
1556 /** Timestamp in seconds since unix epoch (RTLDRDBGINFOTYPE_CODEVIEW_DBG,
1557 * RTLDRDBGINFOTYPE_CODEVIEW_PDB20 (, RTLDRDBGINFOTYPE_CODEVIEW,
1558 * RTLDRDBGINFOTYPE_COFF)). */
1559 uint32_t uTimestamp;
1560 /** The PDB age (RTLDRDBGINFOTYPE_CODEVIEW_PDB20, RTLDRDBGINFOTYPE_CODEVIEW_PDB70). */
1561 uint32_t uPdbAge;
1562 /** The UUID of the PDB or mach-o image (RTLDRDBGINFOTYPE_CODEVIEW_PDB70, +). */
1563 RTUUID Uuid;
1564 /** External path (can be empty). */
1565 char szExtFile[RTPATH_MAX];
1566} MYDBGINFO;
1567
1568/**
1569 * @callback_method_impl{FNRTLDRENUMDBG, For the 'get' command.}
1570 */
1571static DECLCALLBACK(int) rtDbgSymCacheCmdGetForExeDbgInfoCallback(RTLDRMOD hLdrMod, PCRTLDRDBGINFO pDbgInfo, void *pvUser)
1572{
1573 RT_NOREF(hLdrMod);
1574 if (!pDbgInfo->pszExtFile)
1575 switch (pDbgInfo->enmType)
1576 {
1577 case RTLDRDBGINFOTYPE_CODEVIEW_PDB20:
1578 case RTLDRDBGINFOTYPE_CODEVIEW_PDB70:
1579 case RTLDRDBGINFOTYPE_CODEVIEW_DBG:
1580 break;
1581 default:
1582 return VINF_SUCCESS;
1583 }
1584
1585 /* Copy the info: */
1586 MYDBGINFO *pMyInfo = (MYDBGINFO *)pvUser;
1587 RT_ZERO(*pMyInfo);
1588 pMyInfo->enmType = pDbgInfo->enmType;
1589 int rc = VINF_SUCCESS;
1590 if (pDbgInfo->pszExtFile)
1591 rc = RTStrCopy(pMyInfo->szExtFile, sizeof(pMyInfo->szExtFile), pDbgInfo->pszExtFile);
1592
1593 switch (pDbgInfo->enmType)
1594 {
1595 case RTLDRDBGINFOTYPE_DWARF_DWO:
1596 pMyInfo->uDwoCrc32 = pDbgInfo->u.Dwo.uCrc32;
1597 break;
1598
1599 case RTLDRDBGINFOTYPE_CODEVIEW:
1600 case RTLDRDBGINFOTYPE_COFF:
1601 pMyInfo->cbImage = pDbgInfo->u.Cv.cbImage;
1602 pMyInfo->uTimestamp = pDbgInfo->u.Cv.uTimestamp;
1603 break;
1604
1605 case RTLDRDBGINFOTYPE_CODEVIEW_DBG:
1606 pMyInfo->cbImage = pDbgInfo->u.Dbg.cbImage;
1607 pMyInfo->uTimestamp = pDbgInfo->u.Dbg.uTimestamp;
1608 break;
1609
1610 case RTLDRDBGINFOTYPE_CODEVIEW_PDB20:
1611 pMyInfo->cbImage = pDbgInfo->u.Pdb20.cbImage;
1612 pMyInfo->uTimestamp = pDbgInfo->u.Pdb20.uTimestamp;
1613 pMyInfo->uPdbAge = pDbgInfo->u.Pdb20.uAge;
1614 break;
1615
1616 case RTLDRDBGINFOTYPE_CODEVIEW_PDB70:
1617 pMyInfo->cbImage = pDbgInfo->u.Pdb70.cbImage;
1618 pMyInfo->Uuid = pDbgInfo->u.Pdb70.Uuid;
1619 pMyInfo->uPdbAge = pDbgInfo->u.Pdb70.uAge;
1620 break;
1621
1622 default:
1623 return VINF_SUCCESS;
1624 }
1625
1626 return rc;
1627}
1628
1629
1630/**
1631 * @callback_method_impl{FNRTDBGCFGOPEN}
1632 */
1633static DECLCALLBACK(int) rtDbgSymCacheCmdGetCallback(RTDBGCFG hDbgCfg, const char *pszFilename, void *pvUser1, void *pvUser2)
1634{
1635 RT_NOREF(hDbgCfg, pvUser2);
1636
1637 char *pszJoined = NULL;
1638 const char *pszOutput = (const char *)pvUser1;
1639 if (!pszOutput || *pszOutput == '\0')
1640 pszOutput = RTPathFilename(pszFilename);
1641 else if (RTPathFilename(pszOutput) == NULL)
1642 pszOutput = pszJoined = RTPathJoinA(pszOutput, RTPathFilename(pszFilename));
1643
1644 if (g_iLogLevel > 0) // --pe --name wintypes.dll --image-size 1388544 --timestamp 0x57F8D9F0
1645 RTMsgInfo("Copying '%s' to '%s...", pszFilename, pszOutput);
1646 int rc = RTFileCopy(pszFilename, pszOutput);
1647 if (RT_FAILURE(rc))
1648 {
1649 if (rc == VERR_ALREADY_EXISTS)
1650 {
1651 rc = RTFileCompare(pszFilename, pszOutput);
1652 if (RT_SUCCESS(rc))
1653 RTMsgInfo("Output '%s' exists and matches '%s'.", pszOutput, pszFilename);
1654 else
1655 RTMsgError("Output '%s' already exists (does not match '%s')", pszOutput, pszFilename);
1656 }
1657 else
1658 RTMsgError("Copying '%s' to '%s failed: %Rrc", pszFilename, pszOutput, rc);
1659 }
1660 RTStrFree(pszJoined);
1661 if (RT_SUCCESS(rc))
1662 return VINF_CALLBACK_RETURN;
1663 return rc;
1664}
1665
1666
1667/**
1668 * Handles the 'get' command.
1669 *
1670 * @returns Program exit code.
1671 * @param pszArg0 The program name.
1672 * @param cArgs The number of arguments to the 'add' command.
1673 * @param papszArgs The argument vector, starting after 'add'.
1674 */
1675static RTEXITCODE rtDbgSymCacheCmdGet(const char *pszArg0, int cArgs, char **papszArgs)
1676{
1677 RTERRINFOSTATIC ErrInfo;
1678
1679 /*
1680 * Parse the command line.
1681 */
1682 static RTGETOPTDEF const s_aOptions[] =
1683 {
1684 { "--output", 'o', RTGETOPT_REQ_STRING },
1685
1686 /* Query: */
1687 { "--for-exe", 'e', RTGETOPT_REQ_STRING },
1688 { "--for-executable", 'e', RTGETOPT_REQ_STRING },
1689 { "--uuid", 'u', RTGETOPT_REQ_UUID },
1690 { "--ts", 't', RTGETOPT_REQ_UINT32 },
1691 { "--timestamp", 't', RTGETOPT_REQ_UINT32 },
1692 { "--size", 'z', RTGETOPT_REQ_UINT32 },
1693 { "--image-size", 'z', RTGETOPT_REQ_UINT32 },
1694 { "--pdb-age", 'a', RTGETOPT_REQ_UINT32 },
1695 { "--dwo-crc32", 'c', RTGETOPT_REQ_UINT32 },
1696 { "--name", 'n', RTGETOPT_REQ_STRING },
1697
1698 { "--dwo", 'd', RTGETOPT_REQ_NOTHING },
1699 { "--dwarf", 'd', RTGETOPT_REQ_NOTHING },
1700 { "--dwarf-external", 'd', RTGETOPT_REQ_NOTHING },
1701 { "--dsym", 'D', RTGETOPT_REQ_NOTHING },
1702 { "--dbg", '0', RTGETOPT_REQ_NOTHING },
1703 { "--pdb20", '2', RTGETOPT_REQ_NOTHING },
1704 { "--pdb70", '7', RTGETOPT_REQ_NOTHING },
1705
1706 { "--pe", 'P', RTGETOPT_REQ_NOTHING },
1707 { "--macho", 'M', RTGETOPT_REQ_NOTHING },
1708 { "--elf", 'E', RTGETOPT_REQ_NOTHING },
1709
1710 /* RTDbgCfg: */
1711 { "--env-prefix", 'p', RTGETOPT_REQ_STRING },
1712 { "--sym-path", 's', RTGETOPT_REQ_STRING },
1713 { "--use-native-paths", 1000, RTGETOPT_REQ_NOTHING },
1714 { "--no-native-paths", 1001, RTGETOPT_REQ_NOTHING },
1715 };
1716
1717 RTGETOPTSTATE State;
1718 int rc = RTGetOptInit(&State, cArgs, papszArgs, &s_aOptions[0], RT_ELEMENTS(s_aOptions), 0, RTGETOPTINIT_FLAGS_OPTS_FIRST);
1719 if (RT_FAILURE(rc))
1720 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc", rc);
1721
1722 const char *pszOutput = NULL;
1723
1724 bool fGetExeImage = true;
1725 const char *pszForExec = NULL;
1726 const char *pszName = NULL;
1727 RTLDRARCH enmImageArch = RTLDRARCH_WHATEVER;
1728 RTLDRFMT enmImageFmt = RTLDRFMT_INVALID;
1729 MYDBGINFO DbgInfo;
1730 RT_ZERO(DbgInfo);
1731
1732 const char *pszEnvPrefix = "IPRT_";
1733 bool fNativePaths = true;
1734 const char *apszSymPaths[12] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
1735 unsigned cSymPaths = 0;
1736
1737 RTGETOPTUNION ValueUnion;
1738 int chOpt;
1739 while ((chOpt = RTGetOpt(&State, &ValueUnion)) != 0)
1740 {
1741 switch (chOpt)
1742 {
1743 case 'o':
1744 pszOutput = ValueUnion.psz;
1745 break;
1746
1747 /*
1748 * Query elements:
1749 */
1750 case 'z':
1751 DbgInfo.cbImage = ValueUnion.u32;
1752 break;
1753
1754 case 't':
1755 DbgInfo.uTimestamp = ValueUnion.u32;
1756 enmImageFmt = RTLDRFMT_PE;
1757 break;
1758
1759 case 'u':
1760 DbgInfo.Uuid = ValueUnion.Uuid;
1761 enmImageFmt = RTLDRFMT_MACHO;
1762 break;
1763
1764 case 'a':
1765 DbgInfo.uPdbAge = ValueUnion.u32;
1766 if (DbgInfo.enmType != RTLDRDBGINFOTYPE_CODEVIEW_PDB20)
1767 DbgInfo.enmType = RTLDRDBGINFOTYPE_CODEVIEW_PDB70;
1768 break;
1769
1770 case 'c':
1771 DbgInfo.uDwoCrc32 = ValueUnion.u32;
1772 DbgInfo.enmType = RTLDRDBGINFOTYPE_DWARF_DWO;
1773 break;
1774
1775 case 'n':
1776 pszName = ValueUnion.psz;
1777 DbgInfo.szExtFile[0] = '\0';
1778 break;
1779
1780 case 'd':
1781 fGetExeImage = false;
1782 DbgInfo.enmType = RTLDRDBGINFOTYPE_DWARF_DWO;
1783 break;
1784
1785 case 'D':
1786 fGetExeImage = false;
1787 DbgInfo.enmType = RTLDRDBGINFOTYPE_DWARF; /* == dSYM */
1788 break;
1789
1790 case '0':
1791 fGetExeImage = false;
1792 DbgInfo.enmType = RTLDRDBGINFOTYPE_CODEVIEW_DBG;
1793 break;
1794
1795 case '2':
1796 fGetExeImage = false;
1797 DbgInfo.enmType = RTLDRDBGINFOTYPE_CODEVIEW_PDB20;
1798 break;
1799
1800 case '7':
1801 fGetExeImage = false;
1802 DbgInfo.enmType = RTLDRDBGINFOTYPE_CODEVIEW_PDB70;
1803 break;
1804
1805 case 'E':
1806 fGetExeImage = true;
1807 enmImageFmt = RTLDRFMT_ELF;
1808 break;
1809
1810 case 'M':
1811 fGetExeImage = true;
1812 enmImageFmt = RTLDRFMT_MACHO;
1813 break;
1814
1815 case 'P':
1816 fGetExeImage = true;
1817 enmImageFmt = RTLDRFMT_PE;
1818 break;
1819
1820 case 'e':
1821 {
1822 /* Open the executable and retrieve the query parameters from it: */
1823 fGetExeImage = false;
1824 pszForExec = ValueUnion.psz;
1825 if (!pszName)
1826 pszName = RTPathFilename(pszForExec);
1827
1828 RTLDRMOD hLdrMod;
1829 rc = RTLdrOpenEx(pszForExec, RTLDR_O_FOR_DEBUG, enmImageArch, &hLdrMod, RTErrInfoInitStatic(&ErrInfo));
1830 if (RT_FAILURE(rc))
1831 return RTMsgErrorExitFailure("Failed to open image '%s': %Rrc%#RTeim", pszForExec, rc, &ErrInfo);
1832
1833 DbgInfo.cbImage = (uint32_t)RTLdrSize(hLdrMod);
1834 enmImageFmt = RTLdrGetFormat(hLdrMod);
1835 if (enmImageFmt == RTLDRFMT_MACHO)
1836 {
1837 DbgInfo.enmType = RTLDRDBGINFOTYPE_DWARF; /* .dSYM */
1838 rc = RTLdrQueryProp(hLdrMod, RTLDRPROP_UUID, &DbgInfo.Uuid, sizeof(DbgInfo.Uuid));
1839 if (RT_FAILURE(rc))
1840 RTMsgError("Failed to query image UUID from '%s': %Rrc", pszForExec, rc);
1841 }
1842 else
1843 {
1844 rc = RTLdrQueryProp(hLdrMod, RTLDRPROP_TIMESTAMP_SECONDS, &DbgInfo.uTimestamp, sizeof(DbgInfo.uTimestamp));
1845 if (RT_SUCCESS(rc) || (rc == VERR_NOT_FOUND && enmImageFmt != RTLDRFMT_PE))
1846 {
1847 RT_ZERO(DbgInfo);
1848 rc = RTLdrEnumDbgInfo(hLdrMod, NULL, rtDbgSymCacheCmdGetForExeDbgInfoCallback, &DbgInfo);
1849 if (RT_FAILURE(rc))
1850 RTMsgError("RTLdrEnumDbgInfo failed on '%s': %Rrc", pszForExec, rc);
1851 }
1852 else if (RT_FAILURE(rc))
1853 RTMsgError("Failed to query image timestamp from '%s': %Rrc", pszForExec, rc);
1854 }
1855
1856 RTLdrClose(hLdrMod);
1857 if (RT_FAILURE(rc))
1858 return RTEXITCODE_FAILURE;
1859 break;
1860 }
1861
1862 /*
1863 * RTDbgCfg setup:
1864 */
1865 case 'p':
1866 pszEnvPrefix = ValueUnion.psz;
1867 break;
1868
1869 case 's':
1870 if (cSymPaths < RT_ELEMENTS(apszSymPaths))
1871 apszSymPaths[cSymPaths++] = ValueUnion.psz;
1872 else
1873 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --sym-paths arguments: max %u", RT_ELEMENTS(apszSymPaths));
1874 break;
1875
1876 case 1000:
1877 fNativePaths = true;
1878 break;
1879
1880 case 1001:
1881 fNativePaths = false;
1882 break;
1883
1884 case 'h':
1885 return rtDbgSymCacheUsage(pszArg0, "get");
1886 case 'V':
1887 return rtDbgSymCacheVersion();
1888 default:
1889 return RTGetOptPrintError(chOpt, &ValueUnion);
1890 }
1891 }
1892
1893 /*
1894 * Instantiate the debug config we'll be querying.
1895 */
1896 RTDBGCFG hDbgCfg;
1897 rc = RTDbgCfgCreate(&hDbgCfg, pszEnvPrefix, fNativePaths);
1898 if (RT_FAILURE(rc))
1899 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTDbgCfgCreate failed: %Rrc", rc);
1900
1901 rc = RTDbgCfgSetLogCallback(hDbgCfg, rtDbgSymCacheLogCallback, NULL);
1902 AssertRCStmt(rc, RTMsgError("RTDbgCfgSetLogCallback failed: %Rrc", rc));
1903
1904 for (unsigned i = 0; i < cSymPaths && RT_SUCCESS(rc); i++)
1905 {
1906 rc = RTDbgCfgChangeString(hDbgCfg, RTDBGCFGPROP_PATH, RTDBGCFGOP_APPEND, apszSymPaths[i]);
1907 if (RT_FAILURE(rc))
1908 RTMsgError("Failed to append symbol path '%s': %Rrc", apszSymPaths[i], rc);
1909 }
1910 if (RT_SUCCESS(rc))
1911 {
1912 /*
1913 * Do the getting.
1914 */
1915 if (fGetExeImage)
1916 {
1917 if (enmImageFmt == RTLDRFMT_INVALID)
1918 {
1919 if (!RTUuidIsNull(&DbgInfo.Uuid))
1920 enmImageFmt = RTLDRFMT_MACHO;
1921 else if (DbgInfo.cbImage && DbgInfo.uTimestamp)
1922 enmImageFmt = RTLDRFMT_PE;
1923 else
1924 rc = RTMsgErrorRc(VERR_NOT_IMPLEMENTED, "Not enough to go on to find executable");
1925 }
1926 if (enmImageFmt == RTLDRFMT_PE)
1927 rc = RTDbgCfgOpenPeImage(hDbgCfg, pszName, DbgInfo.cbImage, DbgInfo.uTimestamp,
1928 rtDbgSymCacheCmdGetCallback, (void *)pszOutput, NULL);
1929 else if (enmImageFmt == RTLDRFMT_MACHO)
1930 rc = RTDbgCfgOpenMachOImage(hDbgCfg, pszName, &DbgInfo.Uuid,
1931 rtDbgSymCacheCmdGetCallback, (void *)pszOutput, NULL);
1932 else if (enmImageFmt != RTLDRFMT_INVALID)
1933 rc = RTMsgErrorRc(VERR_NOT_IMPLEMENTED, "Format not implemented: %s", RTLdrGetFormat);
1934 }
1935 else if (DbgInfo.enmType == RTLDRDBGINFOTYPE_CODEVIEW_PDB70)
1936 rc = RTDbgCfgOpenPdb70(hDbgCfg, DbgInfo.szExtFile[0] ? DbgInfo.szExtFile : pszName, &DbgInfo.Uuid, DbgInfo.uPdbAge,
1937 rtDbgSymCacheCmdGetCallback, (void *)pszOutput, NULL);
1938 else if (DbgInfo.enmType == RTLDRDBGINFOTYPE_CODEVIEW_PDB20)
1939 rc = RTDbgCfgOpenPdb20(hDbgCfg, DbgInfo.szExtFile[0] ? DbgInfo.szExtFile : pszName, DbgInfo.cbImage,
1940 DbgInfo.uTimestamp, DbgInfo.uPdbAge, rtDbgSymCacheCmdGetCallback, (void *)pszOutput, NULL);
1941 else if (DbgInfo.enmType == RTLDRDBGINFOTYPE_CODEVIEW_DBG)
1942 rc = RTDbgCfgOpenDbg(hDbgCfg, DbgInfo.szExtFile[0] ? DbgInfo.szExtFile : pszName, DbgInfo.cbImage,
1943 DbgInfo.uTimestamp, rtDbgSymCacheCmdGetCallback, (void *)pszOutput, NULL);
1944 else if (DbgInfo.enmType == RTLDRDBGINFOTYPE_DWARF_DWO)
1945 rc = RTDbgCfgOpenDwo(hDbgCfg, DbgInfo.szExtFile[0] ? DbgInfo.szExtFile : pszName, DbgInfo.uDwoCrc32,
1946 rtDbgSymCacheCmdGetCallback, (void *)pszOutput, NULL);
1947 else if (DbgInfo.enmType == RTLDRDBGINFOTYPE_DWARF)
1948 rc = RTDbgCfgOpenDsymBundle(hDbgCfg, DbgInfo.szExtFile[0] ? DbgInfo.szExtFile : pszName, &DbgInfo.Uuid,
1949 rtDbgSymCacheCmdGetCallback, (void *)pszOutput, NULL);
1950 else
1951 rc = RTMsgErrorRc(VERR_NOT_IMPLEMENTED, "Format not implemented");
1952 }
1953
1954 RTDbgCfgRelease(hDbgCfg);
1955 return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1956}
1957
1958
1959int main(int argc, char **argv)
1960{
1961 int rc = RTR3InitExe(argc, &argv, 0);
1962 if (RT_FAILURE(rc))
1963 return RTMsgInitFailure(rc);
1964
1965 /*
1966 * Switch on the command.
1967 */
1968 RTEXITCODE rcExit = RTEXITCODE_SYNTAX;
1969 if (argc < 2)
1970 rtDbgSymCacheUsage(argv[0], NULL);
1971 else if (!strcmp(argv[1], "add"))
1972 rcExit = rtDbgSymCacheCmdAdd(argv[0], argc - 2, argv + 2);
1973 else if (!strcmp(argv[1], "get"))
1974 rcExit = rtDbgSymCacheCmdGet(argv[0], argc - 2, argv + 2);
1975 else if ( !strcmp(argv[1], "-h")
1976 || !strcmp(argv[1], "-?")
1977 || !strcmp(argv[1], "--help"))
1978 rcExit = rtDbgSymCacheUsage(argv[0], NULL);
1979 else if ( !strcmp(argv[1], "-V")
1980 || !strcmp(argv[1], "--version"))
1981 rcExit = rtDbgSymCacheVersion();
1982 else
1983 RTMsgError("Unknown command: '%s'", argv[1]);
1984
1985 return rcExit;
1986}
1987
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette