VirtualBox

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

Last change on this file was 106366, checked in by vboxsync, 8 weeks ago

Runtime/tools/RTDbgSymSrv: Fix building it and build by default, bugref:10393

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