VirtualBox

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

Last change on this file since 95830 was 93115, checked in by vboxsync, 3 years ago

scm --update-copyright-year

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