VirtualBox

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

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

Runtime/tools/RTDbgSymSrv: Experiment of a caching symbol server which can convert fetched PDB files to XML by using an external tool to get symbols on non Windows hosts with the VM debugger, source code backup not built yet.

  • 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 89898 2021-06-24 18:29:50Z vboxsync $ */
2/** @file
3 * IPRT - Debug Symbol Server.
4 */
5
6/*
7 * Copyright (C) 2021 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 && !RTPathExists(pszPathAbsXml))
401 {
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