VirtualBox

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

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

IPRT/RTDbgSymCache: Corrected exit check for 'add'.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 73.3 KB
Line 
1/* $Id: RTDbgSymCache.cpp 100949 2023-08-22 22:57:01Z 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 * The cache subdirectory name can be retrieved via RTVFSQIEX_VOL_LABEL,
758 * the primary volume label. Then we just add it to the cache.
759 */
760 char szSubDir[64];
761 rc = RTVfsQueryLabel(hVfsPdb, false /*fAlternative*/, szSubDir, sizeof(szSubDir), NULL);
762 if (RT_SUCCESS(rc))
763 rc = rtDbgSymCacheAddOneFile(pszPath, pszDstName, NULL, szSubDir, NULL, NULL, pCfg);
764 else
765 RTMsgErrorRc(rc, "RTVfsQueryLabel failed on '%s': %Rrc", pszPath, rc);
766 RTVfsRelease(hVfsPdb);
767 }
768 else
769 RTMsgErrorRc(rc, "RTFsPdbVolOpen failed on '%s': %Rrc%#RTeim", pszPath, rc, &ErrInfo.Core);
770 }
771 else
772 RTMsgErrorRc(rc, "RTVfsFileFromRTFile failed on '%s': %Rrc", pszPath, rc);
773 return rc;
774
775}
776
777
778/**
779 * Adds a debug file to the cache.
780 *
781 * @returns IPRT status code
782 * @param pszPath The path to the debug file in question.
783 * @param pszDstName Add to the cache under this name. Typically the
784 * filename part of @a pszPath.
785 * @param pCfg The configuration.
786 */
787static int rtDbgSymCacheAddDebugFile(const char *pszPath, const char *pszDstName, PCRTDBGSYMCACHEADDCFG pCfg)
788{
789 /*
790 * Need to extract an identifier of sorts here in order to put them in
791 * the right place in the cache. Currently only implemnted for Mach-O
792 * files since these use executable containers.
793 *
794 * We take a look at the file header in hope to figure out what to do
795 * with the file.
796 */
797 RTFILE hFile;
798 int rc = RTFileOpen(&hFile, pszPath, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
799 if (RT_FAILURE(rc))
800 return RTMsgErrorRc(rc, "Error opening '%s': %Rrc", pszPath, rc);
801
802 uint64_t cbFile = 0;
803 rc = RTFileQuerySize(hFile, &cbFile);
804 if (RT_SUCCESS(rc))
805 {
806
807 union
808 {
809 uint64_t au64[16];
810 uint32_t au32[16];
811 uint16_t au16[32];
812 uint8_t ab[64];
813 RTPDB70HDR Pdb70Hdr;
814 RTPDB20HDR Pdb20Hdr;
815 IMAGE_SEPARATE_DEBUG_HEADER DbgHdr;
816 } uBuf;
817 rc = RTFileRead(hFile, &uBuf, sizeof(uBuf), NULL);
818 if (RT_SUCCESS(rc))
819 {
820 /*
821 * Look for magics and call workers.
822 */
823 if ( memcmp(uBuf.Pdb70Hdr.szSignature, RTPDB_SIGNATURE_700, sizeof(uBuf.Pdb70Hdr.szSignature)) == 0
824 || memcmp(uBuf.Pdb20Hdr.szSignature, RTPDB_SIGNATURE_200, sizeof(uBuf.Pdb20Hdr.szSignature)) == 0)
825 rc = rtDbgSymCacheAddDebugPdb(pszPath, pszDstName, pCfg, hFile);
826 else if (uBuf.au16[0] == IMAGE_SEPARATE_DEBUG_SIGNATURE)
827 rc = rtDbgSymCacheAddDebugDbg(pszPath, pszDstName, pCfg, &uBuf.DbgHdr);
828 else if ( uBuf.au32[0] == IMAGE_FAT_SIGNATURE
829 || uBuf.au32[0] == IMAGE_FAT_SIGNATURE_OE
830 || uBuf.au32[0] == IMAGE_MACHO32_SIGNATURE
831 || uBuf.au32[0] == IMAGE_MACHO64_SIGNATURE
832 || uBuf.au32[0] == IMAGE_MACHO32_SIGNATURE_OE
833 || uBuf.au32[0] == IMAGE_MACHO64_SIGNATURE_OE)
834 rc = rtDbgSymCacheAddDebugMachO(pszPath, pszDstName, pCfg);
835 else
836 rc = RTMsgErrorRc(VERR_INVALID_MAGIC, "Unsupported debug file '%s' magic: %#010x", pszPath, uBuf.au32[0]);
837 }
838 else
839 rc = RTMsgErrorRc(rc, "Error reading '%s': %Rrc", pszPath, rc);
840 }
841 else
842 rc = RTMsgErrorRc(rc, "Error query size of '%s': %Rrc", pszPath, rc);
843
844 /* close the file. */
845 int rc2 = RTFileClose(hFile);
846 if (RT_FAILURE(rc2))
847 {
848 RTMsgError("Error closing '%s': %Rrc", pszPath, rc2);
849 if (RT_SUCCESS(rc))
850 rc = rc2;
851 }
852 return rc;
853}
854
855
856/**
857 * Constructs the path to the file instide the bundle that we're keen on.
858 *
859 * @returns IPRT status code.
860 * @param pszPath Path to the bundle on input, on successful
861 * return it's the path to the desired file. This
862 * a RTPATH_MAX size buffer.
863 * @param cchPath The length of the path up to the bundle name.
864 * @param cchName The length of the bundle name.
865 * @param pszSubDir The bundle subdirectory the file lives in.
866 * @param papszSuffixes Pointer to an array of bundle suffixes.
867 */
868static int rtDbgSymCacheConstructBundlePath(char *pszPath, size_t cchPath, size_t cchName, const char *pszSubDir,
869 const char * const *papszSuffixes)
870{
871 /*
872 * Calc the name without the bundle extension.
873 */
874 size_t const cchOrgName = cchName;
875 const char *pszEnd = &pszPath[cchPath + cchName];
876 for (unsigned i = 0; papszSuffixes[i]; i++)
877 {
878 Assert(papszSuffixes[i][0] == '.');
879 size_t cchSuff = strlen(papszSuffixes[i]);
880 if ( cchSuff < cchName
881 && !memcmp(&pszEnd[-(ssize_t)cchSuff], papszSuffixes[i], cchSuff))
882 {
883 cchName -= cchSuff;
884 break;
885 }
886 }
887
888 /*
889 * Check the immediate directory first, in case it's layed out like
890 * IOPCIFamily.kext.
891 */
892 int rc = RTPathAppendEx(pszPath, RTPATH_MAX, &pszPath[cchPath], cchName, RTPATH_STR_F_STYLE_HOST);
893 if (RT_FAILURE(rc) || !RTFileExists(pszPath))
894 {
895 /*
896 * Not there, ok then try the given subdirectory + name.
897 */
898 pszPath[cchPath + cchOrgName] = '\0';
899 rc = RTPathAppend(pszPath, RTPATH_MAX, pszSubDir);
900 if (RT_SUCCESS(rc))
901 rc = RTPathAppendEx(pszPath, RTPATH_MAX, &pszPath[cchPath], cchName, RTPATH_STR_F_STYLE_HOST);
902 if (RT_FAILURE(rc))
903 {
904 pszPath[cchPath + cchOrgName] = '\0';
905 return RTMsgErrorRc(rc, "Error constructing image bundle path for '%s': %Rrc", pszPath, rc);
906 }
907 }
908
909 return VINF_SUCCESS;
910}
911
912
913/**
914 * Adds a image bundle of some sort.
915 *
916 * @returns IPRT status code.
917 * @param pszPath Path to the bundle. This a RTPATH_MAX size
918 * buffer that we can write to when creating the
919 * path to the file inside the bundle that we're
920 * interested in.
921 * @param cchPath The length of the path up to the bundle name.
922 * @param cchName The length of the bundle name.
923 * @param pszDstName Add to the cache under this name, NULL if not
924 * specified.
925 * @param pDirEntry The directory entry buffer, for handling bundle
926 * within bundle recursion.
927 * @param pCfg The configuration.
928 */
929static int rtDbgSymCacheAddImageBundle(char *pszPath, size_t cchPath, size_t cchName, const char *pszDstName,
930 PRTDIRENTRYEX pDirEntry, PCRTDBGSYMCACHEADDCFG pCfg)
931{
932 /*
933 * Assuming these are kexts or simple applications, we only add the image
934 * file itself to the cache. No Info.plist or other files.
935 */
936 /** @todo consider looking for Frameworks and handling framework bundles. */
937 int rc = rtDbgSymCacheConstructBundlePath(pszPath, cchPath, cchName, "Contents/MacOS/", g_apszBundleSuffixes);
938 if (RT_SUCCESS(rc))
939 {
940 if (!pszDstName)
941 pszDstName = RTPathFilename(pszPath);
942 rc = rtDbgSymCacheAddImageFile(pszPath, pszDstName, NULL, RTDBG_CACHE_UUID_MAP_DIR_IMAGES, pCfg);
943 }
944
945 /*
946 * Look for plugins and other sub-bundles.
947 */
948 if (pCfg->fRecursive)
949 {
950 static char const * const s_apszSubBundleDirs[] =
951 {
952 "Contents/Plugins/",
953 /** @todo Frameworks ++ */
954 };
955 for (uint32_t i = 0; i < RT_ELEMENTS(s_apszSubBundleDirs); i++)
956 {
957 pszPath[cchPath + cchName] = '\0';
958 int rc2 = RTPathAppend(pszPath, RTPATH_MAX - 1, s_apszSubBundleDirs[i]);
959 if (RT_SUCCESS(rc2))
960 {
961 if (RTDirExists(pszPath))
962 {
963 size_t cchPath2 = strlen(pszPath);
964 if (!RTPATH_IS_SLASH(pszPath[cchPath2 - 1]))
965 {
966 pszPath[cchPath2++] = RTPATH_SLASH;
967 pszPath[cchPath2] = '\0';
968 }
969 rc2 = rtDbgSymCacheAddDirWorker(pszPath, cchPath2, pDirEntry, pCfg);
970 }
971 }
972 else
973 {
974 pszPath[cchPath + cchName] = '\0';
975 RTMsgError("Error constructing bundle subdir path for '%s' + '%s': %Rrc", pszPath, s_apszSubBundleDirs[i], rc);
976 }
977 if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
978 rc = rc2;
979 }
980 }
981
982 return rc;
983}
984
985
986/**
987 * Adds a debug bundle.
988 *
989 * @returns IPRT status code.
990 * @param pszPath Path to the bundle. This a RTPATH_MAX size
991 * buffer that we can write to when creating the
992 * path to the file inside the bundle that we're
993 * interested in.
994 * @param cchPath The length of the path up to the bundle name.
995 * @param cchName The length of the bundle name.
996 * @param pszDstName Add to the cache under this name, NULL if not
997 * specified.
998 * @param pCfg The configuration.
999 */
1000static int rtDbgSymCacheAddDebugBundle(char *pszPath, size_t cchPath, size_t cchName, const char *pszDstName,
1001 PCRTDBGSYMCACHEADDCFG pCfg)
1002{
1003 /*
1004 * The current policy is not to add the whole .dSYM (or .sym) bundle, but
1005 * rather just the dwarf image instide it. The <UUID>.plist and Info.plist
1006 * files generally doesn't contain much extra information that's really
1007 * necessary, I hope. At least this is what the uuidmap example in the
1008 * lldb hints at (it links to the dwarf file, not the .dSYM dir).
1009 *
1010 * To avoid confusion with a .dSYM bundle, as well as collision with the
1011 * image file, we use .dwarf suffix for the file.
1012 *
1013 * For details on the uuid map see rtDbgSymCacheAddImageFile as well as
1014 * http://lldb.llvm.org/symbols.html .
1015 *
1016 * ASSUMES bundles contains Mach-O DWARF files.
1017 */
1018 int rc = rtDbgSymCacheConstructBundlePath(pszPath, cchPath, cchName, "Contents/Resources/DWARF/", g_apszDSymBundleSuffixes);
1019 if (RT_SUCCESS(rc))
1020 {
1021 if (!pszDstName)
1022 pszDstName = RTPathFilename(pszPath);
1023 rc = rtDbgSymCacheAddImageFile(pszPath, pszDstName, RTDBG_CACHE_DSYM_FILE_SUFFIX, RTDBG_CACHE_UUID_MAP_DIR_DSYMS, pCfg);
1024 }
1025 return rc;
1026}
1027
1028
1029/**
1030 * Figure the type of a file/dir based on path and FS object info.
1031 *
1032 * @returns The type.
1033 * @param pszPath The path to the file/dir.
1034 * @param pObjInfo The object information, symlinks followed.
1035 */
1036static RTDBGSYMCACHEFILETYPE rtDbgSymCacheFigureType2(const char *pszPath, PCRTFSOBJINFO pObjInfo)
1037{
1038 const char *pszName = RTPathFilename(pszPath);
1039 const char *pszExt = RTPathSuffix(pszName);
1040 if (pszExt)
1041 pszExt++;
1042 else
1043 pszExt = "";
1044
1045 if ( RTFS_IS_DIRECTORY(pObjInfo->Attr.fMode)
1046 || (pObjInfo->Attr.fMode & RTFS_DOS_DIRECTORY)) /** @todo OS X samba reports reparse points in /Volumes/ that we cannot resolve. */
1047 {
1048 /* Skip directories shouldn't bother with. */
1049 if ( !RTStrICmp(pszName, ".Trashes")
1050 || !RTStrICmp(pszName, ".$RESCYCLE.BIN")
1051 || !RTStrICmp(pszName, "System.kext") /* Usually only plugins here, so skip it. */
1052 )
1053 return RTDBGSYMCACHEFILETYPE_IGNORE;
1054
1055 /* Directories can also be bundles on the mac. */
1056 if (!RTStrICmp(pszExt, "dSYM"))
1057 return RTDBGSYMCACHEFILETYPE_DEBUG_BUNDLE;
1058
1059 for (unsigned i = 0; i < RT_ELEMENTS(g_apszBundleSuffixes) - 1; i++)
1060 if (!RTStrICmp(pszExt, &g_apszBundleSuffixes[i][1]))
1061 return RTDBGSYMCACHEFILETYPE_IMAGE_BUNDLE;
1062
1063 return RTDBGSYMCACHEFILETYPE_DIR;
1064 }
1065
1066 if (!RTFS_IS_FILE(pObjInfo->Attr.fMode))
1067 return RTDBGSYMCACHEFILETYPE_INVALID;
1068
1069 /* Select image vs debug info based on extension. */
1070 if ( !RTStrICmp(pszExt, "pdb")
1071 || !RTStrICmp(pszExt, "dbg")
1072 || !RTStrICmp(pszExt, "sym")
1073 || !RTStrICmp(pszExt, "dwo")
1074 || !RTStrICmp(pszExt, "dwp")
1075 || !RTStrICmp(pszExt, "debug")
1076 || !RTStrICmp(pszExt, "dsym")
1077 || !RTStrICmp(pszExt, "dwarf")
1078 || !RTStrICmp(pszExt, "map")
1079 || !RTStrICmp(pszExt, "cv"))
1080 return RTDBGSYMCACHEFILETYPE_DEBUG_FILE;
1081
1082 /* Filter out a bunch of files which obviously shouldn't be images. */
1083 if ( !RTStrICmp(pszExt, "txt")
1084 || !RTStrICmp(pszExt, "html")
1085 || !RTStrICmp(pszExt, "htm")
1086 || !RTStrICmp(pszExt, "rtf")
1087 || !RTStrICmp(pszExt, "zip")
1088 || !RTStrICmp(pszExt, "doc")
1089 || !RTStrICmp(pszExt, "gz")
1090 || !RTStrICmp(pszExt, "bz2")
1091 || !RTStrICmp(pszExt, "xz")
1092 || !RTStrICmp(pszExt, "kmk")
1093 || !RTStrICmp(pszExt, "c")
1094 || !RTStrICmp(pszExt, "cpp")
1095 || !RTStrICmp(pszExt, "h")
1096 || !RTStrICmp(pszExt, "m")
1097 || !RTStrICmp(pszExt, "mm")
1098 || !RTStrICmp(pszExt, "asm")
1099 || !RTStrICmp(pszExt, "S")
1100 || !RTStrICmp(pszExt, "inc")
1101 || !RTStrICmp(pszExt, "sh")
1102 )
1103 return RTDBGSYMCACHEFILETYPE_IGNORE;
1104 if ( !RTStrICmp(pszName, "Makefile")
1105 || !RTStrICmp(pszName, "GNUmakefile")
1106 || !RTStrICmp(pszName, "createsymbolfiles")
1107 || !RTStrICmp(pszName, "kgmacros")
1108 )
1109 return RTDBGSYMCACHEFILETYPE_IGNORE;
1110
1111 return RTDBGSYMCACHEFILETYPE_IMAGE_FILE;
1112}
1113
1114
1115/**
1116 * Figure file type based on name, will stat the file/dir.
1117 *
1118 * @returns File type.
1119 * @param pszPath The path to the file/dir to figure.
1120 */
1121static RTDBGSYMCACHEFILETYPE rtDbgSymCacheFigureType(const char *pszPath)
1122{
1123 const char *pszName = RTPathFilename(pszPath);
1124
1125 /* Trailing slash. */
1126 if (!pszName)
1127 return RTDBGSYMCACHEFILETYPE_DIR;
1128
1129 /* Wildcard means listing directory and filtering. */
1130 if (strpbrk(pszName, "?*"))
1131 return RTDBGSYMCACHEFILETYPE_DIR_FILTER;
1132
1133 /* Get object info, following links. */
1134 RTFSOBJINFO ObjInfo;
1135 int rc = RTPathQueryInfoEx(pszPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK);
1136 if (RT_FAILURE(rc))
1137 return RTDBGSYMCACHEFILETYPE_INVALID;
1138 return rtDbgSymCacheFigureType2(pszPath, &ObjInfo);
1139}
1140
1141
1142/**
1143 * Recursive worker for rtDbgSymCacheAddDir, for minimal stack wasting.
1144 *
1145 * @returns IPRT status code (fully bitched).
1146 * @param pszPath Pointer to a RTPATH_MAX size buffer containing
1147 * the path to the current directory ending with a
1148 * slash.
1149 * @param cchPath The size of the current directory path.
1150 * @param pDirEntry Pointer to the RTDIRENTRYEX structure to use.
1151 * @param pCfg The configuration.
1152 */
1153static int rtDbgSymCacheAddDirWorker(char *pszPath, size_t cchPath, PRTDIRENTRYEX pDirEntry, PCRTDBGSYMCACHEADDCFG pCfg)
1154{
1155 /*
1156 * Open the directory.
1157 */
1158 RTDIR hDir;
1159 int rc, rc2;
1160 if (pCfg->pszFilter)
1161 {
1162 rc = RTStrCopy(&pszPath[cchPath], RTPATH_MAX - cchPath, pCfg->pszFilter);
1163 if (RT_FAILURE(rc))
1164 {
1165 pszPath[cchPath] = '\0';
1166 return RTMsgErrorRc(rc, "Filename too long (%Rrc): '%s" RTPATH_SLASH_STR "%s'", rc, pszPath, pCfg->pszFilter);
1167 }
1168 rc = RTDirOpenFiltered(&hDir, pszPath, RTDIRFILTER_WINNT, 0 /*fFlags*/);
1169 }
1170 else
1171 rc = RTDirOpen(&hDir, pszPath);
1172 if (RT_FAILURE(rc))
1173 return RTMsgErrorRc(rc, "RTDirOpen%s failed on '%s': %Rrc", pCfg->pszFilter ? "Filtered" : "", pszPath, rc);
1174
1175 /*
1176 * Enumerate the files.
1177 */
1178 for (;;)
1179 {
1180 rc2 = RTDirReadEx(hDir, pDirEntry, NULL, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK);
1181 if (RT_FAILURE(rc2))
1182 {
1183 pszPath[cchPath] = '\0';
1184 if (rc2 != VERR_NO_MORE_FILES)
1185 {
1186 RTMsgError("RTDirReadEx failed in '%s': %Rrc\n", pszPath, rc2);
1187 rc = rc2;
1188 }
1189 break;
1190 }
1191
1192 /* Skip dot and dot-dot. */
1193 if (RTDirEntryExIsStdDotLink(pDirEntry))
1194 continue;
1195
1196 /* Construct a full path. */
1197 rc = RTStrCopy(&pszPath[cchPath], RTPATH_MAX, pDirEntry->szName);
1198 if (RT_FAILURE(rc))
1199 {
1200 pszPath[cchPath] = '\0';
1201 RTMsgError("File name too long in '%s': '%s' (%Rrc)", pszPath, pDirEntry->szName, rc);
1202 break;
1203 }
1204
1205 switch (rtDbgSymCacheFigureType2(pszPath, &pDirEntry->Info))
1206 {
1207 case RTDBGSYMCACHEFILETYPE_DIR:
1208 if (!pCfg->fRecursive)
1209 RTMsgInfo("Skipping directory '%s'...", pszPath);
1210 else
1211 {
1212 if (cchPath + pDirEntry->cbName + 3 <= RTPATH_MAX)
1213 {
1214 pszPath[cchPath + pDirEntry->cbName] = RTPATH_SLASH;
1215 pszPath[cchPath + pDirEntry->cbName + 1] = '\0';
1216 rc2 = rtDbgSymCacheAddDirWorker(pszPath, cchPath + pDirEntry->cbName + 1, pDirEntry, pCfg);
1217 }
1218 else
1219 {
1220 RTMsgError("File name too long in '%s': '%s' (%Rrc)", pszPath, pDirEntry->szName, rc);
1221 rc2 = VERR_FILENAME_TOO_LONG;
1222 }
1223 }
1224 break;
1225
1226 case RTDBGSYMCACHEFILETYPE_DEBUG_FILE:
1227 rc2 = rtDbgSymCacheAddDebugFile(pszPath, pDirEntry->szName, pCfg);
1228 break;
1229
1230 case RTDBGSYMCACHEFILETYPE_IMAGE_FILE:
1231 rc2 = rtDbgSymCacheAddImageFile(pszPath, pDirEntry->szName, NULL /*pszExtraSuff*/, RTDBG_CACHE_UUID_MAP_DIR_IMAGES, pCfg);
1232 break;
1233
1234 case RTDBGSYMCACHEFILETYPE_DEBUG_BUNDLE:
1235 rc2 = rtDbgSymCacheAddDebugBundle(pszPath, cchPath, pDirEntry->cbName, NULL /*pszDstName*/, pCfg);
1236 break;
1237
1238 case RTDBGSYMCACHEFILETYPE_IMAGE_BUNDLE:
1239 rc2 = rtDbgSymCacheAddImageBundle(pszPath, cchPath, pDirEntry->cbName, NULL /*pszDstName*/, pDirEntry, pCfg);
1240 break;
1241
1242 case RTDBGSYMCACHEFILETYPE_DIR_FILTER:
1243 case RTDBGSYMCACHEFILETYPE_INVALID:
1244 rc2 = RTMsgErrorRc(VERR_INTERNAL_ERROR_2, "Invalid: '%s'", pszPath);
1245 break;
1246
1247 case RTDBGSYMCACHEFILETYPE_IGNORE:
1248 rc2 = VINF_SUCCESS;
1249 break;
1250 }
1251
1252 if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
1253 rc = rc2;
1254 }
1255
1256 /*
1257 * Clean up.
1258 */
1259 rc2 = RTDirClose(hDir);
1260 if (RT_FAILURE(rc2))
1261 {
1262 RTMsgError("RTDirClose failed in '%s': %Rrc", pszPath, rc);
1263 rc = rc2;
1264 }
1265 return rc;
1266}
1267
1268
1269/**
1270 * Adds a directory.
1271 *
1272 * @returns IPRT status code (fully bitched).
1273 * @param pszPath The directory path.
1274 * @param pCfg The configuration.
1275 */
1276static int rtDbgSymCacheAddDir(const char *pszPath, PCRTDBGSYMCACHEADDCFG pCfg)
1277{
1278 /*
1279 * Set up the path buffer, stripping any filter.
1280 */
1281 char szPath[RTPATH_MAX];
1282 int rc = RTStrCopy(szPath, sizeof(szPath) - 2, pszPath);
1283 if (RT_FAILURE(rc))
1284 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Path too long: '%s'", pszPath);
1285
1286 size_t cchPath = strlen(pszPath);
1287 if (!cchPath)
1288 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Path empty: '%s'", pszPath);
1289
1290 if (pCfg->pszFilter)
1291 szPath[cchPath - strlen(pCfg->pszFilter)] = '\0';
1292 cchPath = RTPathStripTrailingSlash(szPath);
1293 if (!RTPATH_IS_SEP(pszPath[cchPath - 1]))
1294 {
1295 szPath[cchPath++] = RTPATH_SLASH;
1296 szPath[cchPath] = '\0';
1297 }
1298
1299 /*
1300 * Let the worker do the rest.
1301 */
1302 RTDIRENTRYEX DirEntry;
1303 return rtDbgSymCacheAddDirWorker(szPath, cchPath, &DirEntry, pCfg);
1304}
1305
1306
1307/**
1308 * Adds a file or directory.
1309 *
1310 * @returns Program exit code.
1311 * @param pszPath The user supplied path to the file or directory.
1312 * @param pszCache The path to the cache.
1313 * @param fRecursive Whether to process directories recursively.
1314 * @param fOverwriteOnConflict Whether to overwrite existing cache entry on
1315 * conflict, or just leave it.
1316 */
1317static RTEXITCODE rtDbgSymCacheAddFileOrDir(const char *pszPath, const char *pszCache, bool fRecursive,
1318 bool fOverwriteOnConflict)
1319{
1320 RT_NOREF1(fOverwriteOnConflict);
1321 RTDBGSYMCACHEADDCFG Cfg;
1322 Cfg.fRecursive = fRecursive;
1323 Cfg.pszCache = pszCache;
1324 Cfg.pszFilter = NULL;
1325
1326 /* If the filename contains an equal ('=') char, treat the left as the file
1327 to add tne right part as the name to add it under (handy for kernels). */
1328 char *pszFree = NULL;
1329 const char *pszDstName = RTPathFilename(pszPath);
1330 const char *pszEqual = pszDstName ? strchr(pszDstName, '=') : NULL;
1331 if (pszEqual)
1332 {
1333 pszPath = pszFree = RTStrDupN(pszPath, pszEqual - pszPath);
1334 if (!pszFree)
1335 return RTMsgErrorExitFailure("out of memory!\n");
1336 pszDstName = pszEqual + 1;
1337 if (!*pszDstName)
1338 return RTMsgErrorExitFailure("add-as filename is empty!\n");
1339 }
1340
1341 int rc;
1342 RTDBGSYMCACHEFILETYPE enmType = rtDbgSymCacheFigureType(pszPath);
1343 switch (enmType)
1344 {
1345 default:
1346 case RTDBGSYMCACHEFILETYPE_INVALID:
1347 rc = RTMsgErrorRc(VERR_INVALID_PARAMETER, "Invalid: '%s'", pszPath);
1348 break;
1349
1350 case RTDBGSYMCACHEFILETYPE_DIR_FILTER:
1351 Cfg.pszFilter = RTPathFilename(pszPath);
1352 RT_FALL_THRU();
1353 case RTDBGSYMCACHEFILETYPE_DIR:
1354 if (!pszEqual)
1355 rc = rtDbgSymCacheAddDir(pszPath, &Cfg);
1356 else
1357 rc = RTMsgErrorRc(VERR_INVALID_PARAMETER, "Add-as filename is not applicable to directories!");
1358 break;
1359
1360 case RTDBGSYMCACHEFILETYPE_DEBUG_FILE:
1361 rc = rtDbgSymCacheAddDebugFile(pszPath, pszDstName, &Cfg);
1362 break;
1363
1364 case RTDBGSYMCACHEFILETYPE_IMAGE_FILE:
1365 rc = rtDbgSymCacheAddImageFile(pszPath, pszDstName, NULL /*pszExtraSuff*/, RTDBG_CACHE_UUID_MAP_DIR_IMAGES, &Cfg);
1366 break;
1367
1368 case RTDBGSYMCACHEFILETYPE_DEBUG_BUNDLE:
1369 case RTDBGSYMCACHEFILETYPE_IMAGE_BUNDLE:
1370 {
1371 size_t cchPath = strlen(pszPath);
1372 size_t cchFilename = strlen(RTPathFilename(pszPath));
1373 char szPathBuf[RTPATH_MAX];
1374 if (cchPath < sizeof(szPathBuf))
1375 {
1376 memcpy(szPathBuf, pszPath, cchPath + 1);
1377 if (enmType == RTDBGSYMCACHEFILETYPE_DEBUG_BUNDLE)
1378 rc = rtDbgSymCacheAddDebugBundle(szPathBuf, cchPath - cchFilename, cchFilename,
1379 pszEqual ? pszDstName : NULL, &Cfg);
1380 else
1381 {
1382 RTDIRENTRYEX DirEntry;
1383 rc = rtDbgSymCacheAddImageBundle(szPathBuf, cchPath - cchFilename, cchFilename,
1384 pszEqual ? pszDstName : NULL, &DirEntry, &Cfg);
1385 }
1386 }
1387 else
1388 rc = RTMsgErrorRc(VERR_FILENAME_TOO_LONG, "Filename too long: '%s'", pszPath);
1389 break;
1390 }
1391
1392 case RTDBGSYMCACHEFILETYPE_IGNORE:
1393 rc = RTMsgErrorRc(VERR_INVALID_PARAMETER, "Invalid file: '%s'", pszPath);
1394 break;
1395 }
1396
1397 RTStrFree(pszFree);
1398 return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1399}
1400
1401
1402/**
1403 * Handles the 'add' command.
1404 *
1405 * @returns Program exit code.
1406 * @param pszArg0 The program name.
1407 * @param cArgs The number of arguments to the 'add' command.
1408 * @param papszArgs The argument vector, starting after 'add'.
1409 */
1410static RTEXITCODE rtDbgSymCacheCmdAdd(const char *pszArg0, int cArgs, char **papszArgs)
1411{
1412 /*
1413 * Parse the command line.
1414 */
1415 static RTGETOPTDEF const s_aOptions[] =
1416 {
1417 { "--recursive", 'R', RTGETOPT_REQ_NOTHING },
1418 { "--no-recursive", 'n', RTGETOPT_REQ_NOTHING },
1419 { "--overwrite-on-conflict", 'o', RTGETOPT_REQ_NOTHING },
1420 };
1421
1422 const char *pszCache = NULL;
1423 bool fRecursive = false;
1424 bool fOverwriteOnConflict = false;
1425
1426 RTGETOPTSTATE State;
1427 int rc = RTGetOptInit(&State, cArgs, papszArgs, &s_aOptions[0], RT_ELEMENTS(s_aOptions), 0, RTGETOPTINIT_FLAGS_OPTS_FIRST);
1428 if (RT_FAILURE(rc))
1429 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc", rc);
1430
1431 //uint32_t cAdded = 0;
1432 RTGETOPTUNION ValueUnion;
1433 int chOpt;
1434 while ((chOpt = RTGetOpt(&State, &ValueUnion)) != 0)
1435 {
1436 switch (chOpt)
1437 {
1438 case 'R':
1439 fRecursive = true;
1440 break;
1441
1442 case 'n':
1443 fRecursive = false;
1444 break;
1445
1446 case 'o':
1447 fOverwriteOnConflict = true;
1448 break;
1449
1450 case VINF_GETOPT_NOT_OPTION:
1451 /* The first non-option is a cache directory. */
1452 if (!pszCache)
1453 {
1454 pszCache = ValueUnion.psz;
1455 if (!RTPathExists(pszCache))
1456 {
1457 rc = RTDirCreate(pszCache, 0755, RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL);
1458 if (RT_FAILURE(rc))
1459 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Error creating cache directory '%s': %Rrc", pszCache, rc);
1460 }
1461 else if (!RTDirExists(pszCache))
1462 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Specified cache directory is not a directory: '%s'", pszCache);
1463 }
1464 /* Subsequent non-options are files to be added to the cache. */
1465 else
1466 {
1467 RTEXITCODE rcExit = rtDbgSymCacheAddFileOrDir(ValueUnion.psz, pszCache, fRecursive, fOverwriteOnConflict);
1468 if (rcExit != RTEXITCODE_SUCCESS)
1469 return rcExit;
1470 }
1471 break;
1472
1473 case 'h':
1474 return rtDbgSymCacheUsage(pszArg0, "add");
1475 case 'V':
1476 return rtDbgSymCacheVersion();
1477 default:
1478 return RTGetOptPrintError(chOpt, &ValueUnion);
1479 }
1480 }
1481
1482 if (!pszCache)
1483 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No cache directory or files to add were specified.");
1484 return RTEXITCODE_SUCCESS;
1485}
1486
1487
1488/**
1489 * Debug info + external path for the 'get' command.
1490 */
1491typedef struct MYDBGINFO
1492{
1493 /** The kind of debug info. */
1494 RTLDRDBGINFOTYPE enmType;
1495 /** The CRC32 of the external file (RTLDRDBGINFOTYPE_DWARF_DWO). */
1496 uint32_t uDwoCrc32;
1497 /** The PE image size (RTLDRDBGINFOTYPE_CODEVIEW_DBG,
1498 * RTLDRDBGINFOTYPE_CODEVIEW_PDB20, RTLDRDBGINFOTYPE_CODEVIEW_PDB70 (,
1499 * RTLDRDBGINFOTYPE_CODEVIEW, RTLDRDBGINFOTYPE_COFF)). */
1500 uint32_t cbImage;
1501 /** Timestamp in seconds since unix epoch (RTLDRDBGINFOTYPE_CODEVIEW_DBG,
1502 * RTLDRDBGINFOTYPE_CODEVIEW_PDB20 (, RTLDRDBGINFOTYPE_CODEVIEW,
1503 * RTLDRDBGINFOTYPE_COFF)). */
1504 uint32_t uTimestamp;
1505 /** The PDB age (RTLDRDBGINFOTYPE_CODEVIEW_PDB20, RTLDRDBGINFOTYPE_CODEVIEW_PDB70). */
1506 uint32_t uPdbAge;
1507 /** The UUID of the PDB or mach-o image (RTLDRDBGINFOTYPE_CODEVIEW_PDB70, +). */
1508 RTUUID Uuid;
1509 /** External path (can be empty). */
1510 char szExtFile[RTPATH_MAX];
1511} MYDBGINFO;
1512
1513/**
1514 * @callback_method_impl{FNRTLDRENUMDBG, For the 'get' command.}
1515 */
1516static DECLCALLBACK(int) rtDbgSymCacheCmdGetForExeDbgInfoCallback(RTLDRMOD hLdrMod, PCRTLDRDBGINFO pDbgInfo, void *pvUser)
1517{
1518 RT_NOREF(hLdrMod);
1519 if (!pDbgInfo->pszExtFile)
1520 switch (pDbgInfo->enmType)
1521 {
1522 case RTLDRDBGINFOTYPE_CODEVIEW_PDB20:
1523 case RTLDRDBGINFOTYPE_CODEVIEW_PDB70:
1524 case RTLDRDBGINFOTYPE_CODEVIEW_DBG:
1525 break;
1526 default:
1527 return VINF_SUCCESS;
1528 }
1529
1530 /* Copy the info: */
1531 MYDBGINFO *pMyInfo = (MYDBGINFO *)pvUser;
1532 RT_ZERO(*pMyInfo);
1533 pMyInfo->enmType = pDbgInfo->enmType;
1534 int rc = VINF_SUCCESS;
1535 if (pDbgInfo->pszExtFile)
1536 rc = RTStrCopy(pMyInfo->szExtFile, sizeof(pMyInfo->szExtFile), pDbgInfo->pszExtFile);
1537
1538 switch (pDbgInfo->enmType)
1539 {
1540 case RTLDRDBGINFOTYPE_DWARF_DWO:
1541 pMyInfo->uDwoCrc32 = pDbgInfo->u.Dwo.uCrc32;
1542 break;
1543
1544 case RTLDRDBGINFOTYPE_CODEVIEW:
1545 case RTLDRDBGINFOTYPE_COFF:
1546 pMyInfo->cbImage = pDbgInfo->u.Cv.cbImage;
1547 pMyInfo->uTimestamp = pDbgInfo->u.Cv.uTimestamp;
1548 break;
1549
1550 case RTLDRDBGINFOTYPE_CODEVIEW_DBG:
1551 pMyInfo->cbImage = pDbgInfo->u.Dbg.cbImage;
1552 pMyInfo->uTimestamp = pDbgInfo->u.Dbg.uTimestamp;
1553 break;
1554
1555 case RTLDRDBGINFOTYPE_CODEVIEW_PDB20:
1556 pMyInfo->cbImage = pDbgInfo->u.Pdb20.cbImage;
1557 pMyInfo->uTimestamp = pDbgInfo->u.Pdb20.uTimestamp;
1558 pMyInfo->uPdbAge = pDbgInfo->u.Pdb20.uAge;
1559 break;
1560
1561 case RTLDRDBGINFOTYPE_CODEVIEW_PDB70:
1562 pMyInfo->cbImage = pDbgInfo->u.Pdb70.cbImage;
1563 pMyInfo->Uuid = pDbgInfo->u.Pdb70.Uuid;
1564 pMyInfo->uPdbAge = pDbgInfo->u.Pdb70.uAge;
1565 break;
1566
1567 default:
1568 return VINF_SUCCESS;
1569 }
1570
1571 return rc;
1572}
1573
1574
1575/**
1576 * @callback_method_impl{FNRTDBGCFGOPEN}
1577 */
1578static DECLCALLBACK(int) rtDbgSymCacheCmdGetCallback(RTDBGCFG hDbgCfg, const char *pszFilename, void *pvUser1, void *pvUser2)
1579{
1580 RT_NOREF(hDbgCfg, pvUser2);
1581
1582 char *pszJoined = NULL;
1583 const char *pszOutput = (const char *)pvUser1;
1584 if (!pszOutput || *pszOutput == '\0')
1585 pszOutput = RTPathFilename(pszFilename);
1586 else if (RTPathFilename(pszOutput) == NULL)
1587 pszOutput = pszJoined = RTPathJoinA(pszOutput, RTPathFilename(pszFilename));
1588
1589 if (g_iLogLevel > 0) // --pe --name wintypes.dll --image-size 1388544 --timestamp 0x57F8D9F0
1590 RTMsgInfo("Copying '%s' to '%s...", pszFilename, pszOutput);
1591 int rc = RTFileCopy(pszFilename, pszOutput);
1592 if (RT_FAILURE(rc))
1593 {
1594 if (rc == VERR_ALREADY_EXISTS)
1595 {
1596 rc = RTFileCompare(pszFilename, pszOutput);
1597 if (RT_SUCCESS(rc))
1598 RTMsgInfo("Output '%s' exists and matches '%s'.", pszOutput, pszFilename);
1599 else
1600 RTMsgError("Output '%s' already exists (does not match '%s')", pszOutput, pszFilename);
1601 }
1602 else
1603 RTMsgError("Copying '%s' to '%s failed: %Rrc", pszFilename, pszOutput, rc);
1604 }
1605 RTStrFree(pszJoined);
1606 if (RT_SUCCESS(rc))
1607 return VINF_CALLBACK_RETURN;
1608 return rc;
1609}
1610
1611
1612/**
1613 * Handles the 'get' command.
1614 *
1615 * @returns Program exit code.
1616 * @param pszArg0 The program name.
1617 * @param cArgs The number of arguments to the 'add' command.
1618 * @param papszArgs The argument vector, starting after 'add'.
1619 */
1620static RTEXITCODE rtDbgSymCacheCmdGet(const char *pszArg0, int cArgs, char **papszArgs)
1621{
1622 RTERRINFOSTATIC ErrInfo;
1623
1624 /*
1625 * Parse the command line.
1626 */
1627 static RTGETOPTDEF const s_aOptions[] =
1628 {
1629 { "--output", 'o', RTGETOPT_REQ_STRING },
1630
1631 /* Query: */
1632 { "--for-exe", 'e', RTGETOPT_REQ_STRING },
1633 { "--for-executable", 'e', RTGETOPT_REQ_STRING },
1634 { "--uuid", 'u', RTGETOPT_REQ_UUID },
1635 { "--ts", 't', RTGETOPT_REQ_UINT32 },
1636 { "--timestamp", 't', RTGETOPT_REQ_UINT32 },
1637 { "--size", 'z', RTGETOPT_REQ_UINT32 },
1638 { "--image-size", 'z', RTGETOPT_REQ_UINT32 },
1639 { "--pdb-age", 'a', RTGETOPT_REQ_UINT32 },
1640 { "--dwo-crc32", 'c', RTGETOPT_REQ_UINT32 },
1641 { "--name", 'n', RTGETOPT_REQ_STRING },
1642
1643 { "--dwo", 'd', RTGETOPT_REQ_NOTHING },
1644 { "--dwarf", 'd', RTGETOPT_REQ_NOTHING },
1645 { "--dwarf-external", 'd', RTGETOPT_REQ_NOTHING },
1646 { "--dsym", 'D', RTGETOPT_REQ_NOTHING },
1647 { "--dbg", '0', RTGETOPT_REQ_NOTHING },
1648 { "--pdb20", '2', RTGETOPT_REQ_NOTHING },
1649 { "--pdb70", '7', RTGETOPT_REQ_NOTHING },
1650
1651 { "--pe", 'P', RTGETOPT_REQ_NOTHING },
1652 { "--macho", 'M', RTGETOPT_REQ_NOTHING },
1653 { "--elf", 'E', RTGETOPT_REQ_NOTHING },
1654
1655 /* RTDbgCfg: */
1656 { "--env-prefix", 'p', RTGETOPT_REQ_STRING },
1657 { "--sym-path", 's', RTGETOPT_REQ_STRING },
1658 { "--use-native-paths", 1000, RTGETOPT_REQ_NOTHING },
1659 { "--no-native-paths", 1001, RTGETOPT_REQ_NOTHING },
1660 };
1661
1662 RTGETOPTSTATE State;
1663 int rc = RTGetOptInit(&State, cArgs, papszArgs, &s_aOptions[0], RT_ELEMENTS(s_aOptions), 0, RTGETOPTINIT_FLAGS_OPTS_FIRST);
1664 if (RT_FAILURE(rc))
1665 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc", rc);
1666
1667 const char *pszOutput = NULL;
1668
1669 bool fGetExeImage = true;
1670 const char *pszForExec = NULL;
1671 const char *pszName = NULL;
1672 RTLDRARCH enmImageArch = RTLDRARCH_WHATEVER;
1673 RTLDRFMT enmImageFmt = RTLDRFMT_INVALID;
1674 MYDBGINFO DbgInfo;
1675 RT_ZERO(DbgInfo);
1676
1677 const char *pszEnvPrefix = "IPRT_";
1678 bool fNativePaths = true;
1679 const char *apszSymPaths[12] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
1680 unsigned cSymPaths = 0;
1681
1682 RTGETOPTUNION ValueUnion;
1683 int chOpt;
1684 while ((chOpt = RTGetOpt(&State, &ValueUnion)) != 0)
1685 {
1686 switch (chOpt)
1687 {
1688 case 'o':
1689 pszOutput = ValueUnion.psz;
1690 break;
1691
1692 /*
1693 * Query elements:
1694 */
1695 case 'z':
1696 DbgInfo.cbImage = ValueUnion.u32;
1697 break;
1698
1699 case 't':
1700 DbgInfo.uTimestamp = ValueUnion.u32;
1701 enmImageFmt = RTLDRFMT_PE;
1702 break;
1703
1704 case 'u':
1705 DbgInfo.Uuid = ValueUnion.Uuid;
1706 enmImageFmt = RTLDRFMT_MACHO;
1707 break;
1708
1709 case 'a':
1710 DbgInfo.uPdbAge = ValueUnion.u32;
1711 if (DbgInfo.enmType != RTLDRDBGINFOTYPE_CODEVIEW_PDB20)
1712 DbgInfo.enmType = RTLDRDBGINFOTYPE_CODEVIEW_PDB70;
1713 break;
1714
1715 case 'c':
1716 DbgInfo.uDwoCrc32 = ValueUnion.u32;
1717 DbgInfo.enmType = RTLDRDBGINFOTYPE_DWARF_DWO;
1718 break;
1719
1720 case 'n':
1721 pszName = ValueUnion.psz;
1722 DbgInfo.szExtFile[0] = '\0';
1723 break;
1724
1725 case 'd':
1726 fGetExeImage = false;
1727 DbgInfo.enmType = RTLDRDBGINFOTYPE_DWARF_DWO;
1728 break;
1729
1730 case 'D':
1731 fGetExeImage = false;
1732 DbgInfo.enmType = RTLDRDBGINFOTYPE_DWARF; /* == dSYM */
1733 break;
1734
1735 case '0':
1736 fGetExeImage = false;
1737 DbgInfo.enmType = RTLDRDBGINFOTYPE_CODEVIEW_DBG;
1738 break;
1739
1740 case '2':
1741 fGetExeImage = false;
1742 DbgInfo.enmType = RTLDRDBGINFOTYPE_CODEVIEW_PDB20;
1743 break;
1744
1745 case '7':
1746 fGetExeImage = false;
1747 DbgInfo.enmType = RTLDRDBGINFOTYPE_CODEVIEW_PDB70;
1748 break;
1749
1750 case 'E':
1751 fGetExeImage = true;
1752 enmImageFmt = RTLDRFMT_ELF;
1753 break;
1754
1755 case 'M':
1756 fGetExeImage = true;
1757 enmImageFmt = RTLDRFMT_MACHO;
1758 break;
1759
1760 case 'P':
1761 fGetExeImage = true;
1762 enmImageFmt = RTLDRFMT_PE;
1763 break;
1764
1765 case 'e':
1766 {
1767 /* Open the executable and retrieve the query parameters from it: */
1768 fGetExeImage = false;
1769 pszForExec = ValueUnion.psz;
1770 if (!pszName)
1771 pszName = RTPathFilename(pszForExec);
1772
1773 RTLDRMOD hLdrMod;
1774 rc = RTLdrOpenEx(pszForExec, RTLDR_O_FOR_DEBUG, enmImageArch, &hLdrMod, RTErrInfoInitStatic(&ErrInfo));
1775 if (RT_FAILURE(rc))
1776 return RTMsgErrorExitFailure("Failed to open image '%s': %Rrc%#RTeim", pszForExec, rc, &ErrInfo);
1777
1778 DbgInfo.cbImage = (uint32_t)RTLdrSize(hLdrMod);
1779 enmImageFmt = RTLdrGetFormat(hLdrMod);
1780 if (enmImageFmt == RTLDRFMT_MACHO)
1781 {
1782 DbgInfo.enmType = RTLDRDBGINFOTYPE_DWARF; /* .dSYM */
1783 rc = RTLdrQueryProp(hLdrMod, RTLDRPROP_UUID, &DbgInfo.Uuid, sizeof(DbgInfo.Uuid));
1784 if (RT_FAILURE(rc))
1785 RTMsgError("Failed to query image UUID from '%s': %Rrc", pszForExec, rc);
1786 }
1787 else
1788 {
1789 rc = RTLdrQueryProp(hLdrMod, RTLDRPROP_TIMESTAMP_SECONDS, &DbgInfo.uTimestamp, sizeof(DbgInfo.uTimestamp));
1790 if (RT_SUCCESS(rc) || (rc == VERR_NOT_FOUND && enmImageFmt != RTLDRFMT_PE))
1791 {
1792 RT_ZERO(DbgInfo);
1793 rc = RTLdrEnumDbgInfo(hLdrMod, NULL, rtDbgSymCacheCmdGetForExeDbgInfoCallback, &DbgInfo);
1794 if (RT_FAILURE(rc))
1795 RTMsgError("RTLdrEnumDbgInfo failed on '%s': %Rrc", pszForExec, rc);
1796 }
1797 else if (RT_FAILURE(rc))
1798 RTMsgError("Failed to query image timestamp from '%s': %Rrc", pszForExec, rc);
1799 }
1800
1801 RTLdrClose(hLdrMod);
1802 if (RT_FAILURE(rc))
1803 return RTEXITCODE_FAILURE;
1804 break;
1805 }
1806
1807 /*
1808 * RTDbgCfg setup:
1809 */
1810 case 'p':
1811 pszEnvPrefix = ValueUnion.psz;
1812 break;
1813
1814 case 's':
1815 if (cSymPaths < RT_ELEMENTS(apszSymPaths))
1816 apszSymPaths[cSymPaths++] = ValueUnion.psz;
1817 else
1818 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --sym-paths arguments: max %u", RT_ELEMENTS(apszSymPaths));
1819 break;
1820
1821 case 1000:
1822 fNativePaths = true;
1823 break;
1824
1825 case 1001:
1826 fNativePaths = false;
1827 break;
1828
1829 case 'h':
1830 return rtDbgSymCacheUsage(pszArg0, "get");
1831 case 'V':
1832 return rtDbgSymCacheVersion();
1833 default:
1834 return RTGetOptPrintError(chOpt, &ValueUnion);
1835 }
1836 }
1837
1838 /*
1839 * Instantiate the debug config we'll be querying.
1840 */
1841 RTDBGCFG hDbgCfg;
1842 rc = RTDbgCfgCreate(&hDbgCfg, pszEnvPrefix, fNativePaths);
1843 if (RT_FAILURE(rc))
1844 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTDbgCfgCreate failed: %Rrc", rc);
1845
1846 rc = RTDbgCfgSetLogCallback(hDbgCfg, rtDbgSymCacheLogCallback, NULL);
1847 AssertRCStmt(rc, RTMsgError("RTDbgCfgSetLogCallback failed: %Rrc", rc));
1848
1849 for (unsigned i = 0; i < cSymPaths && RT_SUCCESS(rc); i++)
1850 {
1851 rc = RTDbgCfgChangeString(hDbgCfg, RTDBGCFGPROP_PATH, RTDBGCFGOP_APPEND, apszSymPaths[i]);
1852 if (RT_FAILURE(rc))
1853 RTMsgError("Failed to append symbol path '%s': %Rrc", apszSymPaths[i], rc);
1854 }
1855 if (RT_SUCCESS(rc))
1856 {
1857 /*
1858 * Do the getting.
1859 */
1860 if (fGetExeImage)
1861 {
1862 if (enmImageFmt == RTLDRFMT_INVALID)
1863 {
1864 if (!RTUuidIsNull(&DbgInfo.Uuid))
1865 enmImageFmt = RTLDRFMT_MACHO;
1866 else if (DbgInfo.cbImage && DbgInfo.uTimestamp)
1867 enmImageFmt = RTLDRFMT_PE;
1868 else
1869 rc = RTMsgErrorRc(VERR_NOT_IMPLEMENTED, "Not enough to go on to find executable");
1870 }
1871 if (enmImageFmt == RTLDRFMT_PE)
1872 rc = RTDbgCfgOpenPeImage(hDbgCfg, pszName, DbgInfo.cbImage, DbgInfo.uTimestamp,
1873 rtDbgSymCacheCmdGetCallback, (void *)pszOutput, NULL);
1874 else if (enmImageFmt == RTLDRFMT_MACHO)
1875 rc = RTDbgCfgOpenMachOImage(hDbgCfg, pszName, &DbgInfo.Uuid,
1876 rtDbgSymCacheCmdGetCallback, (void *)pszOutput, NULL);
1877 else if (enmImageFmt != RTLDRFMT_INVALID)
1878 rc = RTMsgErrorRc(VERR_NOT_IMPLEMENTED, "Format not implemented: %s", RTLdrGetFormat);
1879 }
1880 else if (DbgInfo.enmType == RTLDRDBGINFOTYPE_CODEVIEW_PDB70)
1881 rc = RTDbgCfgOpenPdb70(hDbgCfg, DbgInfo.szExtFile[0] ? DbgInfo.szExtFile : pszName, &DbgInfo.Uuid, DbgInfo.uPdbAge,
1882 rtDbgSymCacheCmdGetCallback, (void *)pszOutput, NULL);
1883 else if (DbgInfo.enmType == RTLDRDBGINFOTYPE_CODEVIEW_PDB20)
1884 rc = RTDbgCfgOpenPdb20(hDbgCfg, DbgInfo.szExtFile[0] ? DbgInfo.szExtFile : pszName, DbgInfo.cbImage,
1885 DbgInfo.uTimestamp, DbgInfo.uPdbAge, rtDbgSymCacheCmdGetCallback, (void *)pszOutput, NULL);
1886 else if (DbgInfo.enmType == RTLDRDBGINFOTYPE_CODEVIEW_DBG)
1887 rc = RTDbgCfgOpenDbg(hDbgCfg, DbgInfo.szExtFile[0] ? DbgInfo.szExtFile : pszName, DbgInfo.cbImage,
1888 DbgInfo.uTimestamp, rtDbgSymCacheCmdGetCallback, (void *)pszOutput, NULL);
1889 else if (DbgInfo.enmType == RTLDRDBGINFOTYPE_DWARF_DWO)
1890 rc = RTDbgCfgOpenDwo(hDbgCfg, DbgInfo.szExtFile[0] ? DbgInfo.szExtFile : pszName, DbgInfo.uDwoCrc32,
1891 rtDbgSymCacheCmdGetCallback, (void *)pszOutput, NULL);
1892 else if (DbgInfo.enmType == RTLDRDBGINFOTYPE_DWARF)
1893 rc = RTDbgCfgOpenDsymBundle(hDbgCfg, DbgInfo.szExtFile[0] ? DbgInfo.szExtFile : pszName, &DbgInfo.Uuid,
1894 rtDbgSymCacheCmdGetCallback, (void *)pszOutput, NULL);
1895 else
1896 rc = RTMsgErrorRc(VERR_NOT_IMPLEMENTED, "Format not implemented");
1897 }
1898
1899 RTDbgCfgRelease(hDbgCfg);
1900 return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1901}
1902
1903
1904int main(int argc, char **argv)
1905{
1906 int rc = RTR3InitExe(argc, &argv, 0);
1907 if (RT_FAILURE(rc))
1908 return RTMsgInitFailure(rc);
1909
1910 /*
1911 * Switch on the command.
1912 */
1913 RTEXITCODE rcExit = RTEXITCODE_SYNTAX;
1914 if (argc < 2)
1915 rtDbgSymCacheUsage(argv[0], NULL);
1916 else if (!strcmp(argv[1], "add"))
1917 rcExit = rtDbgSymCacheCmdAdd(argv[0], argc - 2, argv + 2);
1918 else if (!strcmp(argv[1], "get"))
1919 rcExit = rtDbgSymCacheCmdGet(argv[0], argc - 2, argv + 2);
1920 else if ( !strcmp(argv[1], "-h")
1921 || !strcmp(argv[1], "-?")
1922 || !strcmp(argv[1], "--help"))
1923 rcExit = rtDbgSymCacheUsage(argv[0], NULL);
1924 else if ( !strcmp(argv[1], "-V")
1925 || !strcmp(argv[1], "--version"))
1926 rcExit = rtDbgSymCacheVersion();
1927 else
1928 RTMsgError("Unknown command: '%s'", argv[1]);
1929
1930 return rcExit;
1931}
1932
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