1 | # -*- coding: utf-8 -*-
|
---|
2 | # $Id: btresolver.py 106061 2024-09-16 14:03:52Z vboxsync $
|
---|
3 | # pylint: disable=too-many-lines
|
---|
4 |
|
---|
5 | """
|
---|
6 | Backtrace resolver using external debugging symbols and RTLdrFlt.
|
---|
7 | """
|
---|
8 |
|
---|
9 | __copyright__ = \
|
---|
10 | """
|
---|
11 | Copyright (C) 2016-2024 Oracle and/or its affiliates.
|
---|
12 |
|
---|
13 | This file is part of VirtualBox base platform packages, as
|
---|
14 | available from https://www.virtualbox.org.
|
---|
15 |
|
---|
16 | This program is free software; you can redistribute it and/or
|
---|
17 | modify it under the terms of the GNU General Public License
|
---|
18 | as published by the Free Software Foundation, in version 3 of the
|
---|
19 | License.
|
---|
20 |
|
---|
21 | This program is distributed in the hope that it will be useful, but
|
---|
22 | WITHOUT ANY WARRANTY; without even the implied warranty of
|
---|
23 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
---|
24 | General Public License for more details.
|
---|
25 |
|
---|
26 | You should have received a copy of the GNU General Public License
|
---|
27 | along with this program; if not, see <https://www.gnu.org/licenses>.
|
---|
28 |
|
---|
29 | The contents of this file may alternatively be used under the terms
|
---|
30 | of the Common Development and Distribution License Version 1.0
|
---|
31 | (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
|
---|
32 | in the VirtualBox distribution, in which case the provisions of the
|
---|
33 | CDDL are applicable instead of those of the GPL.
|
---|
34 |
|
---|
35 | You may elect to license modified versions of this file under the
|
---|
36 | terms and conditions of either the GPL or the CDDL or both.
|
---|
37 |
|
---|
38 | SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
|
---|
39 | """
|
---|
40 | __version__ = "$Revision: 106061 $"
|
---|
41 |
|
---|
42 |
|
---|
43 | # Standard Python imports.
|
---|
44 | import os;
|
---|
45 | import re;
|
---|
46 | import shutil;
|
---|
47 | import subprocess;
|
---|
48 |
|
---|
49 | # Validation Kit imports.
|
---|
50 | from common import utils;
|
---|
51 |
|
---|
52 | def getRTLdrFltPath(asPaths):
|
---|
53 | """
|
---|
54 | Returns the path to the RTLdrFlt tool looking in the provided paths
|
---|
55 | or None if not found.
|
---|
56 | """
|
---|
57 |
|
---|
58 | for sPath in asPaths:
|
---|
59 | for sDirPath, _, asFiles in os.walk(sPath):
|
---|
60 | if 'RTLdrFlt' in asFiles:
|
---|
61 | return os.path.join(sDirPath, 'RTLdrFlt');
|
---|
62 |
|
---|
63 | return None;
|
---|
64 |
|
---|
65 |
|
---|
66 |
|
---|
67 | class BacktraceResolverOs(object):
|
---|
68 | """
|
---|
69 | Base class for all OS specific resolvers.
|
---|
70 | """
|
---|
71 |
|
---|
72 | def __init__(self, sScratchPath, sBuildRoot, fnLog = None):
|
---|
73 | self.sScratchPath = sScratchPath;
|
---|
74 | self.sBuildRoot = sBuildRoot;
|
---|
75 | self.fnLog = fnLog;
|
---|
76 |
|
---|
77 | def log(self, sText):
|
---|
78 | """
|
---|
79 | Internal logger callback.
|
---|
80 | """
|
---|
81 | if self.fnLog is not None:
|
---|
82 | self.fnLog(sText);
|
---|
83 |
|
---|
84 |
|
---|
85 |
|
---|
86 | class BacktraceResolverOsLinux(BacktraceResolverOs):
|
---|
87 | """
|
---|
88 | Linux specific backtrace resolver.
|
---|
89 | """
|
---|
90 |
|
---|
91 | def __init__(self, sScratchPath, sBuildRoot, fnLog = None):
|
---|
92 | """
|
---|
93 | Constructs a Linux host specific backtrace resolver.
|
---|
94 | """
|
---|
95 | BacktraceResolverOs.__init__(self, sScratchPath, sBuildRoot, fnLog);
|
---|
96 |
|
---|
97 | self.asDbgFiles = {};
|
---|
98 |
|
---|
99 | def prepareEnv(self):
|
---|
100 | """
|
---|
101 | Prepares the environment for annotating Linux reports.
|
---|
102 | """
|
---|
103 | fRc = False;
|
---|
104 | try:
|
---|
105 | sDbgArchive = os.path.join(self.sBuildRoot, 'bin', 'VirtualBox-dbg.tar.bz2');
|
---|
106 |
|
---|
107 | # Extract debug symbol archive if it was found.
|
---|
108 | if os.path.exists(sDbgArchive):
|
---|
109 | asMembers = utils.unpackFile(sDbgArchive, self.sScratchPath, self.fnLog,
|
---|
110 | self.fnLog);
|
---|
111 | if asMembers:
|
---|
112 | # Populate the list of debug files.
|
---|
113 | for sMember in asMembers:
|
---|
114 | if os.path.isfile(sMember):
|
---|
115 | self.asDbgFiles[os.path.basename(sMember)] = sMember;
|
---|
116 | fRc = True;
|
---|
117 | except:
|
---|
118 | self.log('Failed to setup debug symbols');
|
---|
119 |
|
---|
120 | return fRc;
|
---|
121 |
|
---|
122 | def cleanupEnv(self):
|
---|
123 | """
|
---|
124 | Cleans up the environment.
|
---|
125 | """
|
---|
126 | fRc = False;
|
---|
127 | try:
|
---|
128 | shutil.rmtree(self.sScratchPath, True);
|
---|
129 | fRc = True;
|
---|
130 | except:
|
---|
131 | pass;
|
---|
132 |
|
---|
133 | return fRc;
|
---|
134 |
|
---|
135 | def getDbgSymPathFromBinary(self, sBinary, sArch):
|
---|
136 | """
|
---|
137 | Returns the path to file containing the debug symbols for the specified binary.
|
---|
138 | """
|
---|
139 | _ = sArch;
|
---|
140 | sDbgFilePath = None;
|
---|
141 | try:
|
---|
142 | sDbgFilePath = self.asDbgFiles[sBinary];
|
---|
143 | except:
|
---|
144 | pass;
|
---|
145 |
|
---|
146 | return sDbgFilePath;
|
---|
147 |
|
---|
148 | def getBinaryListWithLoadAddrFromReport(self, asReport):
|
---|
149 | """
|
---|
150 | Parses the given VM state report and returns a list of binaries and their
|
---|
151 | load address.
|
---|
152 |
|
---|
153 | Returns a list if tuples containing the binary and load addres or an empty
|
---|
154 | list on failure.
|
---|
155 | """
|
---|
156 | asListBinaries = [];
|
---|
157 |
|
---|
158 | # Look for the line "Mapped address spaces:"
|
---|
159 | iLine = 0;
|
---|
160 | while iLine < len(asReport):
|
---|
161 | if asReport[iLine].startswith('Mapped address spaces:'):
|
---|
162 | break;
|
---|
163 | iLine += 1;
|
---|
164 |
|
---|
165 | for sLine in asReport[iLine:]:
|
---|
166 | asCandidate = sLine.split();
|
---|
167 | if len(asCandidate) == 5 \
|
---|
168 | and asCandidate[0].startswith('0x') \
|
---|
169 | and asCandidate[1].startswith('0x') \
|
---|
170 | and asCandidate[2].startswith('0x') \
|
---|
171 | and (asCandidate[3] == '0x0' or asCandidate[3] == '0')\
|
---|
172 | and 'VirtualBox' in asCandidate[4]:
|
---|
173 | asListBinaries.append((asCandidate[0], os.path.basename(asCandidate[4])));
|
---|
174 |
|
---|
175 | return asListBinaries;
|
---|
176 |
|
---|
177 |
|
---|
178 |
|
---|
179 | class BacktraceResolverOsDarwin(BacktraceResolverOs):
|
---|
180 | """
|
---|
181 | Darwin specific backtrace resolver.
|
---|
182 | """
|
---|
183 |
|
---|
184 | def __init__(self, sScratchPath, sBuildRoot, fnLog = None):
|
---|
185 | """
|
---|
186 | Constructs a Linux host specific backtrace resolver.
|
---|
187 | """
|
---|
188 | BacktraceResolverOs.__init__(self, sScratchPath, sBuildRoot, fnLog);
|
---|
189 |
|
---|
190 | self.asDbgFiles = {};
|
---|
191 |
|
---|
192 | def prepareEnv(self):
|
---|
193 | """
|
---|
194 | Prepares the environment for annotating Darwin reports.
|
---|
195 | """
|
---|
196 | fRc = False;
|
---|
197 | try:
|
---|
198 | #
|
---|
199 | # Walk the scratch path directory and look for .dSYM directories, building a
|
---|
200 | # list of them.
|
---|
201 | #
|
---|
202 | asDSymPaths = [];
|
---|
203 |
|
---|
204 | for sDirPath, asDirs, _ in os.walk(self.sBuildRoot):
|
---|
205 | for sDir in asDirs:
|
---|
206 | if sDir.endswith('.dSYM'):
|
---|
207 | asDSymPaths.append(os.path.join(sDirPath, sDir));
|
---|
208 |
|
---|
209 | # Expand the dSYM paths to full DWARF debug files in the next step
|
---|
210 | # and add them to the debug files dictionary.
|
---|
211 | for sDSymPath in asDSymPaths:
|
---|
212 | sBinary = os.path.basename(sDSymPath).strip('.dSYM');
|
---|
213 | self.asDbgFiles[sBinary] = os.path.join(sDSymPath, 'Contents', 'Resources',
|
---|
214 | 'DWARF', sBinary);
|
---|
215 |
|
---|
216 | fRc = True;
|
---|
217 | except:
|
---|
218 | self.log('Failed to setup debug symbols');
|
---|
219 |
|
---|
220 | return fRc;
|
---|
221 |
|
---|
222 | def cleanupEnv(self):
|
---|
223 | """
|
---|
224 | Cleans up the environment.
|
---|
225 | """
|
---|
226 | fRc = False;
|
---|
227 | try:
|
---|
228 | shutil.rmtree(self.sScratchPath, True);
|
---|
229 | fRc = True;
|
---|
230 | except:
|
---|
231 | pass;
|
---|
232 |
|
---|
233 | return fRc;
|
---|
234 |
|
---|
235 | def getDbgSymPathFromBinary(self, sBinary, sArch):
|
---|
236 | """
|
---|
237 | Returns the path to file containing the debug symbols for the specified binary.
|
---|
238 | """
|
---|
239 | # Hack to exclude executables as RTLdrFlt has some problems with it currently.
|
---|
240 | _ = sArch;
|
---|
241 | sDbgSym = None;
|
---|
242 | try:
|
---|
243 | sDbgSym = self.asDbgFiles[sBinary];
|
---|
244 | except:
|
---|
245 | pass;
|
---|
246 |
|
---|
247 | if sDbgSym is not None and sDbgSym.endswith('.dylib'):
|
---|
248 | return sDbgSym;
|
---|
249 |
|
---|
250 | return None;
|
---|
251 |
|
---|
252 | def _getReportVersion(self, asReport):
|
---|
253 | """
|
---|
254 | Returns the version of the darwin report.
|
---|
255 | """
|
---|
256 | # Find the line starting with "Report Version:"
|
---|
257 | iLine = 0;
|
---|
258 | iVersion = 0;
|
---|
259 | while iLine < len(asReport):
|
---|
260 | if asReport[iLine].startswith('Report Version:'):
|
---|
261 | break;
|
---|
262 | iLine += 1;
|
---|
263 |
|
---|
264 | if iLine < len(asReport):
|
---|
265 | # Look for the start of the number
|
---|
266 | sVersion = asReport[iLine];
|
---|
267 | iStartVersion = len('Report Version:');
|
---|
268 | iEndVersion = len(sVersion);
|
---|
269 |
|
---|
270 | while iStartVersion < len(sVersion) \
|
---|
271 | and not sVersion[iStartVersion:iStartVersion+1].isdigit():
|
---|
272 | iStartVersion += 1;
|
---|
273 |
|
---|
274 | while iEndVersion > 0 \
|
---|
275 | and not sVersion[iEndVersion-1:iEndVersion].isdigit():
|
---|
276 | iEndVersion -= 1;
|
---|
277 |
|
---|
278 | iVersion = int(sVersion[iStartVersion:iEndVersion]);
|
---|
279 | else:
|
---|
280 | self.log('Couldn\'t find the report version');
|
---|
281 |
|
---|
282 | return iVersion;
|
---|
283 |
|
---|
284 | def _getListOfBinariesFromReportPreSierra(self, asReport):
|
---|
285 | """
|
---|
286 | Returns a list of loaded binaries with their load address obtained from
|
---|
287 | a pre Sierra report.
|
---|
288 | """
|
---|
289 | asListBinaries = [];
|
---|
290 |
|
---|
291 | # Find the line starting with "Binary Images:"
|
---|
292 | iLine = 0;
|
---|
293 | while iLine < len(asReport):
|
---|
294 | if asReport[iLine].startswith('Binary Images:'):
|
---|
295 | break;
|
---|
296 | iLine += 1;
|
---|
297 |
|
---|
298 | if iLine < len(asReport):
|
---|
299 | # List starts after that
|
---|
300 | iLine += 1;
|
---|
301 |
|
---|
302 | # A line for a loaded binary looks like the following:
|
---|
303 | # 0x100042000 - 0x100095fff +VBoxDDU.dylib (4.3.15) <EB19C44D-F882-0803-DBDD-9995723111B7> /Application...
|
---|
304 | # We need the start address and the library name.
|
---|
305 | # To distinguish between our own libraries and ones from Apple we check whether the path at the end starts with
|
---|
306 | # /Applications/VirtualBox.app/Contents/MacOS
|
---|
307 | oRegExpPath = re.compile(r'/VirtualBox.app/Contents/MacOS');
|
---|
308 | oRegExpAddr = re.compile(r'0x\w+');
|
---|
309 | oRegExpBinPath = re.compile(r'VirtualBox.app/Contents/MacOS/\S*');
|
---|
310 | while iLine < len(asReport):
|
---|
311 | asMatches = oRegExpPath.findall(asReport[iLine]);
|
---|
312 | if asMatches:
|
---|
313 | # Line contains the path, extract start address and path to binary
|
---|
314 | sAddr = oRegExpAddr.findall(asReport[iLine]);
|
---|
315 | sPath = oRegExpBinPath.findall(asReport[iLine]);
|
---|
316 |
|
---|
317 | if sAddr and sPath:
|
---|
318 | # Construct the path in into the build cache containing the debug symbols
|
---|
319 | oRegExp = re.compile(r'\w+\.{0,1}\w*$');
|
---|
320 | sFilename = oRegExp.findall(sPath[0]);
|
---|
321 |
|
---|
322 | asListBinaries.append((sAddr[0], sFilename[0]));
|
---|
323 | else:
|
---|
324 | break; # End of image list
|
---|
325 | iLine += 1;
|
---|
326 | else:
|
---|
327 | self.log('Couldn\'t find the list of loaded binaries in the given report');
|
---|
328 |
|
---|
329 | return asListBinaries;
|
---|
330 |
|
---|
331 | def _getListOfBinariesFromReportSierra(self, asReport):
|
---|
332 | """
|
---|
333 | Returns a list of loaded binaries with their load address obtained from
|
---|
334 | a Sierra+ report.
|
---|
335 | """
|
---|
336 | asListBinaries = [];
|
---|
337 |
|
---|
338 | # A line for a loaded binary looks like the following:
|
---|
339 | # 4 VBoxXPCOMIPCC.dylib 0x00000001139f17ea 0x1139e4000 + 55274
|
---|
340 | # We need the start address and the library name.
|
---|
341 | # To distinguish between our own libraries and ones from Apple we check whether the library
|
---|
342 | # name contains VBox or VirtualBox
|
---|
343 | iLine = 0;
|
---|
344 | while iLine < len(asReport):
|
---|
345 | asStackTrace = asReport[iLine].split();
|
---|
346 |
|
---|
347 | # Check whether the line is made up of 6 elements separated by whitespace
|
---|
348 | # and the first one is a number.
|
---|
349 | if len(asStackTrace) == 6 and asStackTrace[0].isdigit() \
|
---|
350 | and (asStackTrace[1].find('VBox') != -1 or asStackTrace[1].find('VirtualBox') != -1) \
|
---|
351 | and asStackTrace[3].startswith('0x'):
|
---|
352 |
|
---|
353 | # Check whether the library is already in our list an only add new ones
|
---|
354 | fFound = False;
|
---|
355 | for _, sLibrary in asListBinaries:
|
---|
356 | if asStackTrace[1] == sLibrary:
|
---|
357 | fFound = True;
|
---|
358 | break;
|
---|
359 |
|
---|
360 | if not fFound:
|
---|
361 | asListBinaries.append((asStackTrace[3], asStackTrace[1]));
|
---|
362 | iLine += 1;
|
---|
363 |
|
---|
364 | return asListBinaries;
|
---|
365 |
|
---|
366 | def getBinaryListWithLoadAddrFromReport(self, asReport):
|
---|
367 | """
|
---|
368 | Parses the given VM state report and returns a list of binaries and their
|
---|
369 | load address.
|
---|
370 |
|
---|
371 | Returns a list if tuples containing the binary and load addres or an empty
|
---|
372 | list on failure.
|
---|
373 | """
|
---|
374 | asListBinaries = [];
|
---|
375 |
|
---|
376 | iVersion = self._getReportVersion(asReport);
|
---|
377 | if iVersion > 0:
|
---|
378 | if iVersion <= 11:
|
---|
379 | self.log('Pre Sierra Report');
|
---|
380 | asListBinaries = self._getListOfBinariesFromReportPreSierra(asReport);
|
---|
381 | elif iVersion == 12:
|
---|
382 | self.log('Sierra report');
|
---|
383 | asListBinaries = self._getListOfBinariesFromReportSierra(asReport);
|
---|
384 | else:
|
---|
385 | self.log('Unsupported report version %s' % (iVersion, ));
|
---|
386 |
|
---|
387 | return asListBinaries;
|
---|
388 |
|
---|
389 |
|
---|
390 |
|
---|
391 | class BacktraceResolverOsSolaris(BacktraceResolverOs):
|
---|
392 | """
|
---|
393 | Solaris specific backtrace resolver.
|
---|
394 | """
|
---|
395 |
|
---|
396 | def __init__(self, sScratchPath, sBuildRoot, fnLog = None):
|
---|
397 | """
|
---|
398 | Constructs a Linux host specific backtrace resolver.
|
---|
399 | """
|
---|
400 | BacktraceResolverOs.__init__(self, sScratchPath, sBuildRoot, fnLog);
|
---|
401 |
|
---|
402 | self.asDbgFiles = {};
|
---|
403 |
|
---|
404 | def prepareEnv(self):
|
---|
405 | """
|
---|
406 | Prepares the environment for annotating Linux reports.
|
---|
407 | """
|
---|
408 | fRc = False;
|
---|
409 | try:
|
---|
410 | sDbgArchive = os.path.join(self.sBuildRoot, 'bin', 'VirtualBoxDebug.tar.bz2');
|
---|
411 |
|
---|
412 | # Extract debug symbol archive if it was found.
|
---|
413 | if os.path.exists(sDbgArchive):
|
---|
414 | asMembers = utils.unpackFile(sDbgArchive, self.sScratchPath, self.fnLog,
|
---|
415 | self.fnLog);
|
---|
416 | if asMembers:
|
---|
417 | # Populate the list of debug files.
|
---|
418 | for sMember in asMembers:
|
---|
419 | if os.path.isfile(sMember):
|
---|
420 | sArch = '';
|
---|
421 | if 'amd64' in sMember:
|
---|
422 | sArch = 'amd64';
|
---|
423 | else:
|
---|
424 | sArch = 'x86';
|
---|
425 | self.asDbgFiles[os.path.basename(sMember) + '/' + sArch] = sMember;
|
---|
426 | fRc = True;
|
---|
427 | else:
|
---|
428 | self.log('Unpacking the debug archive failed');
|
---|
429 | except:
|
---|
430 | self.log('Failed to setup debug symbols');
|
---|
431 |
|
---|
432 | return fRc;
|
---|
433 |
|
---|
434 | def cleanupEnv(self):
|
---|
435 | """
|
---|
436 | Cleans up the environment.
|
---|
437 | """
|
---|
438 | fRc = False;
|
---|
439 | try:
|
---|
440 | shutil.rmtree(self.sScratchPath, True);
|
---|
441 | fRc = True;
|
---|
442 | except:
|
---|
443 | pass;
|
---|
444 |
|
---|
445 | return fRc;
|
---|
446 |
|
---|
447 | def getDbgSymPathFromBinary(self, sBinary, sArch):
|
---|
448 | """
|
---|
449 | Returns the path to file containing the debug symbols for the specified binary.
|
---|
450 | """
|
---|
451 | sDbgFilePath = None;
|
---|
452 | try:
|
---|
453 | sDbgFilePath = self.asDbgFiles[sBinary + '/' + sArch];
|
---|
454 | except:
|
---|
455 | pass;
|
---|
456 |
|
---|
457 | return sDbgFilePath;
|
---|
458 |
|
---|
459 | def getBinaryListWithLoadAddrFromReport(self, asReport):
|
---|
460 | """
|
---|
461 | Parses the given VM state report and returns a list of binaries and their
|
---|
462 | load address.
|
---|
463 |
|
---|
464 | Returns a list if tuples containing the binary and load addres or an empty
|
---|
465 | list on failure.
|
---|
466 | """
|
---|
467 | asListBinaries = [];
|
---|
468 |
|
---|
469 | # Look for the beginning of the process address space mappings"
|
---|
470 | for sLine in asReport:
|
---|
471 | asItems = sLine.split();
|
---|
472 | if len(asItems) == 4 \
|
---|
473 | and asItems[3].startswith('/opt/VirtualBox') \
|
---|
474 | and ( asItems[2] == 'r-x--' \
|
---|
475 | or asItems[2] == 'r-x----'):
|
---|
476 | fFound = False;
|
---|
477 | sBinaryFile = os.path.basename(asItems[3]);
|
---|
478 | for _, sBinary in asListBinaries:
|
---|
479 | if sBinary == sBinaryFile:
|
---|
480 | fFound = True;
|
---|
481 | break;
|
---|
482 | if not fFound:
|
---|
483 | asListBinaries.append(('0x' + asItems[0], sBinaryFile));
|
---|
484 |
|
---|
485 | return asListBinaries;
|
---|
486 |
|
---|
487 |
|
---|
488 |
|
---|
489 | class BacktraceResolver(object):
|
---|
490 | """
|
---|
491 | A backtrace resolving class.
|
---|
492 | """
|
---|
493 |
|
---|
494 | def __init__(self, sScratchPath, sBuildRoot, sTargetOs, sArch, sRTLdrFltPath = None, fnLog = None):
|
---|
495 | """
|
---|
496 | Constructs a backtrace resolver object for the given target OS,
|
---|
497 | architecture and path to the directory containing the debug symbols and tools
|
---|
498 | we need.
|
---|
499 | """
|
---|
500 | # Initialize all members first.
|
---|
501 | self.sScratchPath = sScratchPath;
|
---|
502 | self.sBuildRoot = sBuildRoot;
|
---|
503 | self.sTargetOs = sTargetOs;
|
---|
504 | self.sArch = sArch;
|
---|
505 | self.sRTLdrFltPath = sRTLdrFltPath;
|
---|
506 | self.fnLog = fnLog;
|
---|
507 | self.sDbgSymPath = None;
|
---|
508 | self.oResolverOs = None;
|
---|
509 | self.sScratchDbgPath = os.path.join(self.sScratchPath, 'dbgsymbols');
|
---|
510 |
|
---|
511 | if self.fnLog is None:
|
---|
512 | self.fnLog = self.logStub;
|
---|
513 |
|
---|
514 | if self.sRTLdrFltPath is None:
|
---|
515 | self.sRTLdrFltPath = getRTLdrFltPath([self.sScratchPath, self.sBuildRoot]);
|
---|
516 | if self.sRTLdrFltPath is not None:
|
---|
517 | self.log('Found RTLdrFlt in %s' % (self.sRTLdrFltPath,));
|
---|
518 | else:
|
---|
519 | self.log('Couldn\'t find RTLdrFlt in either %s or %s' % (self.sScratchPath, self.sBuildRoot));
|
---|
520 |
|
---|
521 | def log(self, sText):
|
---|
522 | """
|
---|
523 | Internal logger callback.
|
---|
524 | """
|
---|
525 | if self.fnLog is not None:
|
---|
526 | self.fnLog(sText);
|
---|
527 |
|
---|
528 | def logStub(self, sText):
|
---|
529 | """
|
---|
530 | Logging stub doing nothing.
|
---|
531 | """
|
---|
532 | _ = sText;
|
---|
533 |
|
---|
534 | def prepareEnv(self):
|
---|
535 | """
|
---|
536 | Prepares the environment to annotate backtraces, finding the required tools
|
---|
537 | and retrieving the debug symbols depending on the host OS.
|
---|
538 |
|
---|
539 | Returns True on success and False on error or if not supported.
|
---|
540 | """
|
---|
541 |
|
---|
542 | # No access to the RTLdrFlt tool means no symbols so no point in trying
|
---|
543 | # to set something up.
|
---|
544 | if self.sRTLdrFltPath is None:
|
---|
545 | return False;
|
---|
546 |
|
---|
547 | # Create a directory containing the scratch space for the OS resolver backends.
|
---|
548 | fRc = True;
|
---|
549 | if not os.path.exists(self.sScratchDbgPath):
|
---|
550 | try:
|
---|
551 | os.makedirs(self.sScratchDbgPath, 0o750);
|
---|
552 | except:
|
---|
553 | fRc = False;
|
---|
554 | self.log('Failed to create scratch directory for debug symbols');
|
---|
555 |
|
---|
556 | if fRc:
|
---|
557 | if self.sTargetOs == 'linux':
|
---|
558 | self.oResolverOs = BacktraceResolverOsLinux(self.sScratchDbgPath, self.sScratchPath, self.fnLog);
|
---|
559 | elif self.sTargetOs == 'darwin':
|
---|
560 | self.oResolverOs = BacktraceResolverOsDarwin(self.sScratchDbgPath, self.sScratchPath, self.fnLog); # pylint: disable=redefined-variable-type
|
---|
561 | elif self.sTargetOs == 'solaris':
|
---|
562 | self.oResolverOs = BacktraceResolverOsSolaris(self.sScratchDbgPath, self.sScratchPath, self.fnLog); # pylint: disable=redefined-variable-type
|
---|
563 | else:
|
---|
564 | self.log('The backtrace resolver is not supported on %s' % (self.sTargetOs,));
|
---|
565 | fRc = False;
|
---|
566 |
|
---|
567 | if fRc:
|
---|
568 | fRc = self.oResolverOs.prepareEnv();
|
---|
569 | if not fRc:
|
---|
570 | self.oResolverOs = None;
|
---|
571 |
|
---|
572 | if not fRc:
|
---|
573 | shutil.rmtree(self.sScratchDbgPath, True)
|
---|
574 |
|
---|
575 | return fRc;
|
---|
576 |
|
---|
577 | def cleanupEnv(self):
|
---|
578 | """
|
---|
579 | Prepares the environment to annotate backtraces, finding the required tools
|
---|
580 | and retrieving the debug symbols depending on the host OS.
|
---|
581 |
|
---|
582 | Returns True on success and False on error or if not supported.
|
---|
583 | """
|
---|
584 | fRc = False;
|
---|
585 | if self.oResolverOs is not None:
|
---|
586 | fRc = self.oResolverOs.cleanupEnv();
|
---|
587 |
|
---|
588 | shutil.rmtree(self.sScratchDbgPath, True);
|
---|
589 | return fRc;
|
---|
590 |
|
---|
591 | def annotateReport(self, sReport):
|
---|
592 | """
|
---|
593 | Annotates the given report with the previously prepared environment.
|
---|
594 |
|
---|
595 | Returns the annotated report on success or None on failure.
|
---|
596 | """
|
---|
597 | sReportAn = None;
|
---|
598 |
|
---|
599 | if self.oResolverOs is not None:
|
---|
600 | asListBinaries = self.oResolverOs.getBinaryListWithLoadAddrFromReport(sReport.split('\n'));
|
---|
601 |
|
---|
602 | if asListBinaries:
|
---|
603 | asArgs = [self.sRTLdrFltPath, ];
|
---|
604 |
|
---|
605 | for sLoadAddr, sBinary in asListBinaries:
|
---|
606 | sDbgSymPath = self.oResolverOs.getDbgSymPathFromBinary(sBinary, self.sArch);
|
---|
607 | if sDbgSymPath is not None:
|
---|
608 | asArgs.append(sDbgSymPath);
|
---|
609 | asArgs.append(sLoadAddr);
|
---|
610 |
|
---|
611 | oRTLdrFltProc = subprocess.Popen(asArgs, stdin=subprocess.PIPE, # pylint: disable=consider-using-with
|
---|
612 | stdout=subprocess.PIPE, bufsize=0);
|
---|
613 | if oRTLdrFltProc is not None:
|
---|
614 | try:
|
---|
615 | sReportAn, _ = oRTLdrFltProc.communicate(sReport);
|
---|
616 | except:
|
---|
617 | self.log('Retrieving annotation report failed (broken pipe / no matching interpreter?)');
|
---|
618 | else:
|
---|
619 | self.log('Error spawning RTLdrFlt process');
|
---|
620 | else:
|
---|
621 | self.log('Getting list of loaded binaries failed');
|
---|
622 | else:
|
---|
623 | self.log('Backtrace resolver not fully initialized, not possible to annotate');
|
---|
624 |
|
---|
625 | return sReportAn;
|
---|
626 |
|
---|