227 lines
8.5 KiB
Python
227 lines
8.5 KiB
Python
|
#!/usr/bin/env python
|
||
|
# Copyright (c) 2011 The Chromium Authors. All rights reserved.
|
||
|
# Use of this source code is governed by a BSD-style license that can be
|
||
|
# found in the LICENSE file.
|
||
|
|
||
|
"""Usage: <win-path-to-pdb.pdb>
|
||
|
This tool will take a PDB on the command line, extract the source files that
|
||
|
were used in building the PDB, query SVN for which repository and revision
|
||
|
these files are at, and then finally write this information back into the PDB
|
||
|
in a format that the debugging tools understand. This allows for automatic
|
||
|
source debugging, as all of the information is contained in the PDB, and the
|
||
|
debugger can go out and fetch the source files via SVN.
|
||
|
|
||
|
You most likely want to run these immediately after a build, since the source
|
||
|
input files need to match the generated PDB, and we want the correct SVN
|
||
|
revision information for the exact files that were used for the build.
|
||
|
|
||
|
The following files from a windbg + source server installation are expected
|
||
|
to reside in the same directory as this python script:
|
||
|
dbghelp.dll
|
||
|
pdbstr.exe
|
||
|
srctool.exe
|
||
|
|
||
|
NOTE: Expected to run under a native win32 python, NOT cygwin. All paths are
|
||
|
dealt with as win32 paths, since we have to interact with the Microsoft tools.
|
||
|
"""
|
||
|
|
||
|
import sys
|
||
|
import os
|
||
|
import time
|
||
|
import subprocess
|
||
|
import tempfile
|
||
|
|
||
|
# This serves two purposes. First, it acts as a whitelist, and only files
|
||
|
# from repositories listed here will be source indexed. Second, it allows us
|
||
|
# to map from one SVN URL to another, so we can map to external SVN servers.
|
||
|
REPO_MAP = {
|
||
|
"svn://chrome-svn/blink": "http://src.chromium.org/blink",
|
||
|
"svn://chrome-svn/chrome": "http://src.chromium.org/chrome",
|
||
|
"svn://chrome-svn/multivm": "http://src.chromium.org/multivm",
|
||
|
"svn://chrome-svn/native_client": "http://src.chromium.org/native_client",
|
||
|
"svn://chrome-svn.corp.google.com/blink": "http://src.chromium.org/blink",
|
||
|
"svn://chrome-svn.corp.google.com/chrome": "http://src.chromium.org/chrome",
|
||
|
"svn://chrome-svn.corp.google.com/multivm": "http://src.chromium.org/multivm",
|
||
|
"svn://chrome-svn.corp.google.com/native_client":
|
||
|
"http://src.chromium.org/native_client",
|
||
|
"svn://svn-mirror.golo.chromium.org/blink": "http://src.chromium.org/blink",
|
||
|
"svn://svn-mirror.golo.chromium.org/chrome": "http://src.chromium.org/chrome",
|
||
|
"svn://svn-mirror.golo.chromium.org/multivm":
|
||
|
"http://src.chromium.org/multivm",
|
||
|
"svn://svn-mirror.golo.chromium.org/native_client":
|
||
|
"http://src.chromium.org/native_client",
|
||
|
"http://v8.googlecode.com/svn": None,
|
||
|
"http://google-breakpad.googlecode.com/svn": None,
|
||
|
"http://googletest.googlecode.com/svn": None,
|
||
|
"http://open-vcdiff.googlecode.com/svn": None,
|
||
|
"http://google-url.googlecode.com/svn": None,
|
||
|
}
|
||
|
|
||
|
|
||
|
def FindFile(filename):
|
||
|
"""Return the full windows path to a file in the same dir as this code."""
|
||
|
thisdir = os.path.dirname(os.path.join(os.path.curdir, __file__))
|
||
|
return os.path.abspath(os.path.join(thisdir, filename))
|
||
|
|
||
|
|
||
|
def ExtractSourceFiles(pdb_filename):
|
||
|
"""Extract a list of local paths of the source files from a PDB."""
|
||
|
srctool = subprocess.Popen([FindFile('srctool.exe'), '-r', pdb_filename],
|
||
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||
|
filelist = srctool.stdout.read()
|
||
|
res = srctool.wait()
|
||
|
if res != 0 or filelist.startswith("srctool: "):
|
||
|
raise "srctool failed: " + filelist
|
||
|
return [x for x in filelist.split('\r\n') if len(x) != 0]
|
||
|
|
||
|
|
||
|
def ReadSourceStream(pdb_filename):
|
||
|
"""Read the contents of the source information stream from a PDB."""
|
||
|
srctool = subprocess.Popen([FindFile('pdbstr.exe'),
|
||
|
'-r', '-s:srcsrv',
|
||
|
'-p:%s' % pdb_filename],
|
||
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||
|
data = srctool.stdout.read()
|
||
|
res = srctool.wait()
|
||
|
|
||
|
if (res != 0 and res != -1) or data.startswith("pdbstr: "):
|
||
|
raise "pdbstr failed: " + data
|
||
|
return data
|
||
|
|
||
|
|
||
|
def WriteSourceStream(pdb_filename, data):
|
||
|
"""Write the contents of the source information stream to a PDB."""
|
||
|
# Write out the data to a temporary filename that we can pass to pdbstr.
|
||
|
(f, fname) = tempfile.mkstemp()
|
||
|
f = os.fdopen(f, "wb")
|
||
|
f.write(data)
|
||
|
f.close()
|
||
|
|
||
|
srctool = subprocess.Popen([FindFile('pdbstr.exe'),
|
||
|
'-w', '-s:srcsrv',
|
||
|
'-i:%s' % fname,
|
||
|
'-p:%s' % pdb_filename],
|
||
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||
|
data = srctool.stdout.read()
|
||
|
res = srctool.wait()
|
||
|
|
||
|
if (res != 0 and res != -1) or data.startswith("pdbstr: "):
|
||
|
raise "pdbstr failed: " + data
|
||
|
|
||
|
os.unlink(fname)
|
||
|
|
||
|
|
||
|
# TODO for performance, we should probably work in directories instead of
|
||
|
# files. I'm scared of DEPS and generated files, so for now we query each
|
||
|
# individual file, and don't make assumptions that all files in the same
|
||
|
# directory are part of the same repository or at the same revision number.
|
||
|
def ExtractSvnInfo(local_filename):
|
||
|
"""Calls svn info to extract the repository, path, and revision."""
|
||
|
# We call svn.bat to make sure and get the depot tools SVN and not cygwin.
|
||
|
srctool = subprocess.Popen(['svn.bat', 'info', local_filename],
|
||
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||
|
info = srctool.stdout.read()
|
||
|
res = srctool.wait()
|
||
|
if res != 0:
|
||
|
return None
|
||
|
# Hack up into a dictionary of the fields printed by svn info.
|
||
|
vals = dict((y.split(': ', 2) for y in info.split('\r\n') if y))
|
||
|
|
||
|
root = vals['Repository Root']
|
||
|
if not vals['URL'].startswith(root):
|
||
|
raise "URL is not inside of the repository root?!?"
|
||
|
path = vals['URL'][len(root):]
|
||
|
rev = int(vals['Revision'])
|
||
|
|
||
|
return [root, path, rev]
|
||
|
|
||
|
|
||
|
def UpdatePDB(pdb_filename, verbose=False):
|
||
|
"""Update a pdb file with source information."""
|
||
|
dir_blacklist = { }
|
||
|
# TODO(deanm) look into "compressing" our output, by making use of vars
|
||
|
# and other things, so we don't need to duplicate the repo path and revs.
|
||
|
lines = [
|
||
|
'SRCSRV: ini ------------------------------------------------',
|
||
|
'VERSION=1',
|
||
|
'INDEXVERSION=2',
|
||
|
'VERCTRL=Subversion',
|
||
|
'DATETIME=%s' % time.asctime(),
|
||
|
'SRCSRV: variables ------------------------------------------',
|
||
|
'SVN_EXTRACT_TARGET_DIR=%targ%\%fnbksl%(%var3%)\%var4%',
|
||
|
'SVN_EXTRACT_TARGET=%svn_extract_target_dir%\%fnfile%(%var1%)',
|
||
|
'SVN_EXTRACT_CMD=cmd /c mkdir "%svn_extract_target_dir%" && cmd /c svn cat "%var2%%var3%@%var4%" --non-interactive > "%svn_extract_target%"',
|
||
|
'SRCSRVTRG=%SVN_extract_target%',
|
||
|
'SRCSRVCMD=%SVN_extract_cmd%',
|
||
|
'SRCSRV: source files ---------------------------------------',
|
||
|
]
|
||
|
|
||
|
if ReadSourceStream(pdb_filename):
|
||
|
raise "PDB already has source indexing information!"
|
||
|
|
||
|
filelist = ExtractSourceFiles(pdb_filename)
|
||
|
for filename in filelist:
|
||
|
filedir = os.path.dirname(filename)
|
||
|
|
||
|
if verbose:
|
||
|
print "Processing: %s" % filename
|
||
|
# This directory is blacklisted, either because it's not part of the SVN
|
||
|
# repository, or from one we're not interested in indexing.
|
||
|
if dir_blacklist.get(filedir, False):
|
||
|
if verbose:
|
||
|
print " skipping, directory is blacklisted."
|
||
|
continue
|
||
|
|
||
|
info = ExtractSvnInfo(filename)
|
||
|
|
||
|
# Skip the file if it's not under an svn repository. To avoid constantly
|
||
|
# querying SVN for files outside of SVN control (for example, the CRT
|
||
|
# sources), check if the directory is outside of SVN and blacklist it.
|
||
|
if not info:
|
||
|
if not ExtractSvnInfo(filedir):
|
||
|
dir_blacklist[filedir] = True
|
||
|
if verbose:
|
||
|
print " skipping, file is not in an SVN repository"
|
||
|
continue
|
||
|
|
||
|
root = info[0]
|
||
|
path = info[1]
|
||
|
rev = info[2]
|
||
|
|
||
|
# Check if file was from a svn repository we don't know about, or don't
|
||
|
# want to index. Blacklist the entire directory.
|
||
|
if not REPO_MAP.has_key(info[0]):
|
||
|
if verbose:
|
||
|
print " skipping, file is from an unknown SVN repository %s" % root
|
||
|
dir_blacklist[filedir] = True
|
||
|
continue
|
||
|
|
||
|
# We might want to map an internal repository URL to an external repository.
|
||
|
if REPO_MAP[root]:
|
||
|
root = REPO_MAP[root]
|
||
|
|
||
|
lines.append('%s*%s*%s*%s' % (filename, root, path, rev))
|
||
|
if verbose:
|
||
|
print " indexed file."
|
||
|
|
||
|
lines.append('SRCSRV: end ------------------------------------------------')
|
||
|
|
||
|
WriteSourceStream(pdb_filename, '\r\n'.join(lines))
|
||
|
|
||
|
|
||
|
def main():
|
||
|
if len(sys.argv) < 2 or len(sys.argv) > 3:
|
||
|
print "usage: file.pdb [-v]"
|
||
|
return 1
|
||
|
|
||
|
verbose = False
|
||
|
if len(sys.argv) == 3:
|
||
|
verbose = (sys.argv[2] == '-v')
|
||
|
|
||
|
UpdatePDB(sys.argv[1], verbose=verbose)
|
||
|
return 0
|
||
|
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
sys.exit(main())
|