VirtualBox

source: vbox/trunk/src/VBox/Runtime/tools/RTDbgSymSrv.cpp

Last change on this file was 106061, checked in by vboxsync, 2 months ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 19.4 KB
Line 
1/* $Id: RTDbgSymSrv.cpp 106061 2024-09-16 14:03:52Z vboxsync $ */
2/** @file
3 * IPRT - Debug Symbol Server.
4 */
5
6/*
7 * Copyright (C) 2021-2024 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
42#include <iprt/assert.h>
43#include <iprt/buildconfig.h>
44#include <iprt/err.h>
45#include <iprt/dir.h>
46#include <iprt/env.h>
47#include <iprt/file.h>
48#include <iprt/log.h>
49#include <iprt/getopt.h>
50#include <iprt/http.h>
51#include <iprt/http-server.h>
52#include <iprt/initterm.h>
53#include <iprt/string.h>
54#include <iprt/stream.h>
55#include <iprt/message.h>
56#include <iprt/path.h>
57#include <iprt/process.h>
58#include <iprt/thread.h>
59#include <iprt/pipe.h>
60
61
62/*********************************************************************************************************************************
63* Structures and Typedefs *
64*********************************************************************************************************************************/
65
66
67/*********************************************************************************************************************************
68* Internal Functions *
69*********************************************************************************************************************************/
70static DECLCALLBACK(int) dbgSymSrvOpen(PRTHTTPCALLBACKDATA pData, PRTHTTPSERVERREQ pReq, void **ppvHandle);
71static DECLCALLBACK(int) dbgSymSrvRead(PRTHTTPCALLBACKDATA pData, void *pvHandle, void *pvBuf, size_t cbBuf, size_t *pcbRead);
72static DECLCALLBACK(int) dbgSymSrvClose(PRTHTTPCALLBACKDATA pData, void *pvHandle);
73static DECLCALLBACK(int) dbgSymSrvQueryInfo(PRTHTTPCALLBACKDATA pData, PRTHTTPSERVERREQ pReq, PRTFSOBJINFO pObjInfo, char **ppszMIMEHint);
74static DECLCALLBACK(int) dbgSymSrvDestroy(PRTHTTPCALLBACKDATA pData);
75
76
77/*********************************************************************************************************************************
78* Global Variables *
79*********************************************************************************************************************************/
80/** Flag whether the server was interrupted. */
81static bool g_fCanceled = false;
82/** The symbol cache absolute root. */
83static const char *g_pszSymCacheRoot = NULL;
84/** The path to the pdb.exe. */
85static const char *g_pszPdbExe = NULL;
86/** Symbol server to forward requests to if not found locally. */
87static const char *g_pszSymSrvFwd = NULL;
88#ifndef RT_OS_WINDOWS
89/** The WINEPREFIX to use. */
90static const char *g_pszWinePrefix = NULL;
91/** The path to the wine binary to use for pdb.exe. */
92static const char *g_pszWinePath = NULL;
93#endif
94/** Verbositity level. */
95//static uint32_t g_iLogLevel = 99;
96/** Server callbacks. */
97static RTHTTPSERVERCALLBACKS g_SrvCallbacks =
98{
99 dbgSymSrvOpen,
100 dbgSymSrvRead,
101 dbgSymSrvClose,
102 dbgSymSrvQueryInfo,
103 NULL,
104 NULL,
105 dbgSymSrvDestroy
106};
107
108
109/**
110 * Resolves (and validates) a given URL to an absolute (local) path.
111 *
112 * @returns VBox status code.
113 * @param pszUrl URL to resolve.
114 * @param ppszPathAbs Where to store the resolved absolute path on success.
115 * Needs to be free'd with RTStrFree().
116 * @param ppszPathAbsXml Where to store the resolved absolute path for the converted XML
117 * file. Needs to be free'd with RTStrFree().
118 */
119static int rtDbgSymSrvPathResolve(const char *pszUrl, char **ppszPathAbs, char **ppszPathAbsXml)
120{
121 /* The URL needs to start with /download/symbols/. */
122 if (strncmp(pszUrl, "/download/symbols/", sizeof("/download/symbols/") - 1))
123 return VERR_NOT_FOUND;
124
125 pszUrl += sizeof("/download/symbols/") - 1;
126 /* Construct absolute path. */
127 char *pszPathAbs = NULL;
128 if (RTStrAPrintf(&pszPathAbs, "%s/%s", g_pszSymCacheRoot, pszUrl) <= 0)
129 return VERR_NO_MEMORY;
130
131 if (ppszPathAbsXml)
132 {
133 char *pszPathAbsXml = NULL;
134 if (RTStrAPrintf(&pszPathAbsXml, "%s/%s.xml", g_pszSymCacheRoot, pszUrl) <= 0)
135 return VERR_NO_MEMORY;
136
137 *ppszPathAbsXml = pszPathAbsXml;
138 }
139
140 *ppszPathAbs = pszPathAbs;
141
142 return VINF_SUCCESS;
143}
144
145
146static int rtDbgSymSrvFwdDownload(const char *pszUrl, char *pszPathAbs)
147{
148 RTPrintf("'%s' not in local cache, fetching from '%s'\n", pszPathAbs, g_pszSymSrvFwd);
149
150 char *pszFilename = RTPathFilename(pszPathAbs);
151 char chStart = *pszFilename;
152 *pszFilename = '\0';
153 int rc = RTDirCreateFullPath(pszPathAbs, 0766);
154 if (!RTDirExists(pszPathAbs))
155 {
156 Log(("Error creating cache dir '%s': %Rrc\n", pszPathAbs, rc));
157 return rc;
158 }
159 *pszFilename = chStart;
160
161 char szUrl[_2K];
162 RTHTTP hHttp;
163 rc = RTHttpCreate(&hHttp);
164 if (RT_SUCCESS(rc))
165 {
166 RTHttpUseSystemProxySettings(hHttp);
167 RTHttpSetFollowRedirects(hHttp, 8);
168
169 static const char * const s_apszHeaders[] =
170 {
171 "User-Agent: Microsoft-Symbol-Server/6.6.0999.9",
172 "Pragma: no-cache",
173 };
174
175 rc = RTHttpSetHeaders(hHttp, RT_ELEMENTS(s_apszHeaders), s_apszHeaders);
176 if (RT_SUCCESS(rc))
177 {
178 RTStrPrintf(szUrl, sizeof(szUrl), "%s/%s", g_pszSymSrvFwd, pszUrl + sizeof("/download/symbols/") - 1);
179
180 /** @todo Use some temporary file name and rename it after the operation
181 * since not all systems support read-deny file sharing
182 * settings. */
183 RTPrintf("Downloading '%s' to '%s'...\n", szUrl, pszPathAbs);
184 rc = RTHttpGetFile(hHttp, szUrl, pszPathAbs);
185 if (RT_FAILURE(rc))
186 {
187 RTFileDelete(pszPathAbs);
188 RTPrintf("%Rrc on URL '%s'\n", rc, szUrl);
189 }
190 if (rc == VERR_HTTP_NOT_FOUND)
191 {
192 /* Try the compressed version of the file. */
193 pszPathAbs[strlen(pszPathAbs) - 1] = '_';
194 szUrl[strlen(szUrl) - 1] = '_';
195 RTPrintf("Downloading '%s' to '%s'...\n", szUrl, pszPathAbs);
196 rc = RTHttpGetFile(hHttp, szUrl, pszPathAbs);
197#if 0 /** @todo */
198 if (RT_SUCCESS(rc))
199 rc = rtDbgCfgUnpackMsCacheFile(pThis, pszPathAbs, pszFilename);
200 else
201#endif
202 {
203 RTPrintf("%Rrc on URL '%s'\n", rc, pszPathAbs);
204 RTFileDelete(pszPathAbs);
205 }
206 }
207 }
208
209 RTHttpDestroy(hHttp);
210 }
211
212 return rc;
213}
214
215
216static int rtDbgSymSrvConvertToGhidraXml(char *pszPath, const char *pszFilename)
217{
218 RTPrintf("Converting '%s' to ghidra XML into '%s'\n", pszPath, pszFilename);
219
220 /*
221 * Figuring out the argument list for the platform specific way to call pdb.exe.
222 */
223#ifdef RT_OS_WINDOWS
224 RTENV hEnv = RTENV_DEFAULT;
225 RTPathChangeToDosSlashes(pszPath, false /*fForce*/);
226 const char *papszArgs[] =
227 {
228 g_pszPdbExe,
229 pszPath,
230 NULL
231 };
232
233#else
234 const char *papszArgs[] =
235 {
236 g_pszWinePath,
237 g_pszPdbExe,
238 pszPath,
239 NULL
240 };
241
242 RTENV hEnv;
243 {
244 int rc = RTEnvCreate(&hEnv);
245 if (RT_SUCCESS(rc))
246 {
247 rc = RTEnvSetEx(hEnv, "WINEPREFIX", g_pszWinePrefix);
248 if (RT_SUCCESS(rc))
249 rc = RTEnvSetEx(hEnv, "WINEDEBUG", "-all");
250 if (RT_FAILURE(rc))
251 {
252 RTEnvDestroy(hEnv);
253 return rc;
254 }
255 }
256 }
257#endif
258
259 RTPIPE hPipeR, hPipeW;
260 int rc = RTPipeCreate(&hPipeR, &hPipeW, RTPIPE_C_INHERIT_WRITE);
261 if (RT_SUCCESS(rc))
262 {
263 RTHANDLE Handle;
264 Handle.enmType = RTHANDLETYPE_PIPE;
265 Handle.u.hPipe = hPipeW;
266
267 /*
268 * Do the conversion.
269 */
270 RTPROCESS hChild;
271 RTFILE hFile;
272
273 rc = RTFileOpen(&hFile, pszFilename, RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE); AssertRC(rc);
274
275 rc = RTProcCreateEx(papszArgs[0], papszArgs, hEnv,
276#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
277 RTPROC_FLAGS_NO_WINDOW | RTPROC_FLAGS_HIDDEN | RTPROC_FLAGS_SEARCH_PATH,
278#else
279 RTPROC_FLAGS_SEARCH_PATH,
280#endif
281 NULL /*phStdIn*/, &Handle, NULL /*phStdErr*/,
282 NULL /*pszAsUser*/, NULL /*pszPassword*/, NULL /*pvExtraData*/,
283 &hChild);
284 if (RT_SUCCESS(rc))
285 {
286 rc = RTPipeClose(hPipeW); AssertRC(rc);
287
288 for (;;)
289 {
290 char szOutput[_4K];
291 size_t cbRead;
292 rc = RTPipeReadBlocking(hPipeR, &szOutput[0], sizeof(szOutput), &cbRead);
293 if (RT_FAILURE(rc))
294 {
295 Assert(rc == VERR_BROKEN_PIPE);
296 break;
297 }
298
299 rc = RTFileWrite(hFile, &szOutput[0], cbRead, NULL /*pcbWritten*/); AssertRC(rc);
300 }
301 rc = RTPipeClose(hPipeR); AssertRC(rc);
302
303 RTPROCSTATUS ProcStatus;
304 rc = RTProcWait(hChild, RTPROCWAIT_FLAGS_BLOCK, &ProcStatus);
305 if (RT_SUCCESS(rc))
306 {
307 if ( ProcStatus.enmReason == RTPROCEXITREASON_NORMAL
308 && ProcStatus.iStatus == 0)
309 {
310 if (RTPathExists(pszPath))
311 {
312 RTPrintf("Successfully unpacked '%s' to '%s'.\n", pszPath, pszFilename);
313 rc = VINF_SUCCESS;
314 }
315 else
316 {
317 RTPrintf("Successfully ran unpacker on '%s', but '%s' is missing!\n", pszPath, pszFilename);
318 rc = VERR_FILE_NOT_FOUND;
319 }
320 }
321 else
322 {
323 RTPrintf("Unpacking '%s' failed: iStatus=%d enmReason=%d\n",
324 pszPath, ProcStatus.iStatus, ProcStatus.enmReason);
325 rc = VERR_ZIP_CORRUPTED;
326 }
327 }
328 else
329 RTPrintf("Error waiting for process: %Rrc\n", rc);
330
331 RTFileClose(hFile);
332
333 }
334 else
335 RTPrintf("Error starting unpack process '%s': %Rrc\n", papszArgs[0], rc);
336 }
337
338#ifndef RT_OS_WINDOWS
339 RTEnvDestroy(hEnv);
340#endif
341 return rc;
342}
343
344
345static DECLCALLBACK(int) dbgSymSrvOpen(PRTHTTPCALLBACKDATA pData, PRTHTTPSERVERREQ pReq, void **ppvHandle)
346{
347 RT_NOREF(pData);
348
349 char *pszPathAbs = NULL;
350 char *pszPathAbsXml = NULL;
351 int rc = rtDbgSymSrvPathResolve(pReq->pszUrl, &pszPathAbs, &pszPathAbsXml);
352 if (RT_SUCCESS(rc))
353 {
354 RTFILE hFile;
355 if ( g_pszPdbExe
356 && RTPathExists(pszPathAbsXml))
357 {
358 RTPrintf("Opening '%s'\n", pszPathAbsXml);
359 rc = RTFileOpen(&hFile, pszPathAbsXml, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
360 }
361 else
362 {
363 RTPrintf("Opening '%s'\n", pszPathAbs);
364 rc = RTFileOpen(&hFile, pszPathAbs, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
365 }
366 if (RT_SUCCESS(rc))
367 *ppvHandle = hFile;
368
369 RTStrFree(pszPathAbs);
370 RTStrFree(pszPathAbsXml);
371 }
372
373 LogFlowFuncLeaveRC(rc);
374 return rc;
375}
376
377
378static DECLCALLBACK(int) dbgSymSrvRead(PRTHTTPCALLBACKDATA pData, void *pvHandle, void *pvBuf, size_t cbBuf, size_t *pcbRead)
379{
380 RT_NOREF(pData);
381 return RTFileRead((RTFILE)pvHandle, pvBuf, cbBuf, pcbRead);
382}
383
384
385static DECLCALLBACK(int) dbgSymSrvClose(PRTHTTPCALLBACKDATA pData, void *pvHandle)
386{
387 RT_NOREF(pData);
388 return RTFileClose((RTFILE)pvHandle);
389}
390
391
392static DECLCALLBACK(int) dbgSymSrvQueryInfo(PRTHTTPCALLBACKDATA pData, PRTHTTPSERVERREQ pReq, PRTFSOBJINFO pObjInfo, char **ppszMIMEHint)
393{
394 RT_NOREF(pData, ppszMIMEHint);
395 char *pszPathAbs = NULL;
396 char *pszPathAbsXml = NULL;
397 int rc = rtDbgSymSrvPathResolve(pReq->pszUrl, &pszPathAbs, &pszPathAbsXml);
398 if (RT_SUCCESS(rc))
399 {
400 if ( !RTPathExists(pszPathAbs)
401 && g_pszSymSrvFwd)
402 rc = rtDbgSymSrvFwdDownload(pReq->pszUrl, pszPathAbs);
403
404 if ( RT_SUCCESS(rc)
405 && RTPathExists(pszPathAbs))
406 {
407 const char *pszFile = pszPathAbs;
408
409 if (g_pszPdbExe)
410 {
411 if (!RTPathExists(pszPathAbsXml))
412 rc = rtDbgSymSrvConvertToGhidraXml(pszPathAbs, pszPathAbsXml);
413 if (RT_SUCCESS(rc))
414 pszFile = pszPathAbsXml;
415 }
416
417 if ( RT_SUCCESS(rc)
418 && RTPathExists(pszFile))
419 {
420 rc = RTPathQueryInfo(pszFile, pObjInfo, RTFSOBJATTRADD_NOTHING);
421 if (RT_SUCCESS(rc))
422 {
423 if (!RTFS_IS_FILE(pObjInfo->Attr.fMode))
424 rc = VERR_NOT_SUPPORTED;
425 }
426 }
427 else
428 rc = VERR_FILE_NOT_FOUND;
429 }
430 else
431 rc = VERR_FILE_NOT_FOUND;
432
433 RTStrFree(pszPathAbs);
434 RTStrFree(pszPathAbsXml);
435 }
436
437 LogFlowFuncLeaveRC(rc);
438 return rc;
439}
440
441
442static DECLCALLBACK(int) dbgSymSrvDestroy(PRTHTTPCALLBACKDATA pData)
443{
444 RTPrintf("%s\n", __FUNCTION__);
445 RT_NOREF(pData);
446 return VINF_SUCCESS;
447}
448
449
450/**
451 * Display the version of the server program.
452 *
453 * @returns exit code.
454 */
455static RTEXITCODE rtDbgSymSrvVersion(void)
456{
457 RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision());
458 return RTEXITCODE_SUCCESS;
459}
460
461
462/**
463 * Shows the usage of the cache program.
464 *
465 * @returns Exit code.
466 * @param pszArg0 Program name.
467 */
468static RTEXITCODE rtDbgSymSrvUsage(const char *pszArg0)
469{
470 RTPrintf("Usage: %s --address <interface> --port <port> --sym-cache <symbol cache root> --pdb-exe <ghidra pdb.exe path>\n"
471 "\n"
472 "Options:\n"
473 " -a, --address\n"
474 " The interface to listen on, default is localhost.\n"
475 " -p, --port\n"
476 " The port to listen on, default is 80.\n"
477 " -c, --sym-cache\n"
478 " The absolute path of the symbol cache.\n"
479 " -x, --pdb-exe\n"
480 " The path of Ghidra's pdb.exe to convert PDB files to XML on the fly.\n"
481 " -f, --sym-srv-forward\n"
482 " The symbol server to forward requests to if a file is not in the local cache\n"
483#ifndef RT_OS_WINDOWS
484 " -w, --wine-prefix\n"
485 " The prefix of the wine environment to use which has msdia140.dll set up for pdb.exe.\n"
486 " -b, --wine-bin\n"
487 " The wine binary path to run pdb.exe with.\n"
488#endif
489 , RTPathFilename(pszArg0));
490
491 return RTEXITCODE_SUCCESS;
492}
493
494
495int main(int argc, char **argv)
496{
497 int rc = RTR3InitExe(argc, &argv, 0);
498 if (RT_FAILURE(rc))
499 return RTMsgInitFailure(rc);
500
501 /*
502 * Parse the command line.
503 */
504 static RTGETOPTDEF const s_aOptions[] =
505 {
506 { "--address", 'a', RTGETOPT_REQ_STRING },
507 { "--port", 'p', RTGETOPT_REQ_UINT16 },
508 { "--sym-cache", 'c', RTGETOPT_REQ_STRING },
509 { "--pdb-exe", 'x', RTGETOPT_REQ_STRING },
510 { "--sym-srv-forward", 'f', RTGETOPT_REQ_STRING },
511#ifndef RT_OS_WINDOWS
512 { "--wine-prefix", 'w', RTGETOPT_REQ_STRING },
513 { "--wine-bin", 'b', RTGETOPT_REQ_STRING },
514#endif
515 { "--help", 'h', RTGETOPT_REQ_NOTHING },
516 { "--version", 'V', RTGETOPT_REQ_NOTHING },
517 };
518
519 RTGETOPTSTATE State;
520 rc = RTGetOptInit(&State, argc, argv, &s_aOptions[0], RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
521 if (RT_FAILURE(rc))
522 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc", rc);
523
524 const char *pszAddress = "localhost";
525 uint16_t uPort = 80;
526
527 RTGETOPTUNION ValueUnion;
528 int chOpt;
529 while ((chOpt = RTGetOpt(&State, &ValueUnion)) != 0)
530 {
531 switch (chOpt)
532 {
533 case 'a':
534 pszAddress = ValueUnion.psz;
535 break;
536 case 'p':
537 uPort = ValueUnion.u16;
538 break;
539 case 'c':
540 g_pszSymCacheRoot = ValueUnion.psz;
541 break;
542 case 'x':
543 g_pszPdbExe = ValueUnion.psz;
544 break;
545 case 'f':
546 g_pszSymSrvFwd = ValueUnion.psz;
547 break;
548#ifndef RT_OS_WINDOWS
549 case 'w':
550 g_pszWinePrefix = ValueUnion.psz;
551 break;
552 case 'b':
553 g_pszWinePath = ValueUnion.psz;
554 break;
555#endif
556
557 case 'h':
558 return rtDbgSymSrvUsage(argv[0]);
559 case 'V':
560 return rtDbgSymSrvVersion();
561 default:
562 return RTGetOptPrintError(chOpt, &ValueUnion);
563 }
564 }
565
566 if (!g_pszSymCacheRoot)
567 return RTMsgErrorExit(RTEXITCODE_FAILURE, "The symbol cache root needs to be set");
568
569 RTHTTPSERVER hHttpSrv;
570 rc = RTHttpServerCreate(&hHttpSrv, pszAddress, uPort, &g_SrvCallbacks,
571 NULL, 0);
572 if (RT_SUCCESS(rc))
573 {
574 RTPrintf("Starting HTTP server at %s:%RU16 ...\n", pszAddress, uPort);
575 RTPrintf("Root directory is '%s'\n", g_pszSymCacheRoot);
576
577 RTPrintf("Running HTTP server ...\n");
578
579 for (;;)
580 {
581 RTThreadSleep(1000);
582
583 if (g_fCanceled)
584 break;
585 }
586
587 RTPrintf("Stopping HTTP server ...\n");
588
589 int rc2 = RTHttpServerDestroy(hHttpSrv);
590 if (RT_SUCCESS(rc))
591 rc = rc2;
592
593 RTPrintf("Stopped HTTP server\n");
594 }
595 else
596 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTHttpServerCreate failed: %Rrc", rc);
597
598 return RTEXITCODE_SUCCESS;
599}
600
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