• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2# Copyright (c) 2011 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Usage: <win-path-to-pdb.pdb>
7This tool will take a PDB on the command line, extract the source files that
8were used in building the PDB, query SVN for which repository and revision
9these files are at, and then finally write this information back into the PDB
10in a format that the debugging tools understand.  This allows for automatic
11source debugging, as all of the information is contained in the PDB, and the
12debugger can go out and fetch the source files via SVN.
13
14You most likely want to run these immediately after a build, since the source
15input files need to match the generated PDB, and we want the correct SVN
16revision information for the exact files that were used for the build.
17
18The following files from a windbg + source server installation are expected
19to reside in the same directory as this python script:
20  dbghelp.dll
21  pdbstr.exe
22  srctool.exe
23
24NOTE: Expected to run under a native win32 python, NOT cygwin.  All paths are
25dealt with as win32 paths, since we have to interact with the Microsoft tools.
26"""
27
28import sys
29import os
30import time
31import subprocess
32import tempfile
33
34# This serves two purposes.  First, it acts as a whitelist, and only files
35# from repositories listed here will be source indexed.  Second, it allows us
36# to map from one SVN URL to another, so we can map to external SVN servers.
37REPO_MAP = {
38  "svn://chrome-svn/blink": "http://src.chromium.org/blink",
39  "svn://chrome-svn/chrome": "http://src.chromium.org/chrome",
40  "svn://chrome-svn/multivm": "http://src.chromium.org/multivm",
41  "svn://chrome-svn/native_client": "http://src.chromium.org/native_client",
42  "svn://chrome-svn.corp.google.com/blink": "http://src.chromium.org/blink",
43  "svn://chrome-svn.corp.google.com/chrome": "http://src.chromium.org/chrome",
44  "svn://chrome-svn.corp.google.com/multivm": "http://src.chromium.org/multivm",
45  "svn://chrome-svn.corp.google.com/native_client":
46      "http://src.chromium.org/native_client",
47  "svn://svn-mirror.golo.chromium.org/blink": "http://src.chromium.org/blink",
48  "svn://svn-mirror.golo.chromium.org/chrome": "http://src.chromium.org/chrome",
49  "svn://svn-mirror.golo.chromium.org/multivm":
50      "http://src.chromium.org/multivm",
51  "svn://svn-mirror.golo.chromium.org/native_client":
52      "http://src.chromium.org/native_client",
53  "http://v8.googlecode.com/svn": None,
54  "http://google-breakpad.googlecode.com/svn": None,
55  "http://googletest.googlecode.com/svn": None,
56  "http://open-vcdiff.googlecode.com/svn": None,
57  "http://google-url.googlecode.com/svn": None,
58}
59
60
61def FindFile(filename):
62  """Return the full windows path to a file in the same dir as this code."""
63  thisdir = os.path.dirname(os.path.join(os.path.curdir, __file__))
64  return os.path.abspath(os.path.join(thisdir, filename))
65
66
67def ExtractSourceFiles(pdb_filename):
68  """Extract a list of local paths of the source files from a PDB."""
69  srctool = subprocess.Popen([FindFile('srctool.exe'), '-r', pdb_filename],
70                             stdout=subprocess.PIPE, stderr=subprocess.PIPE)
71  filelist = srctool.stdout.read()
72  res = srctool.wait()
73  if res != 0 or filelist.startswith("srctool: "):
74    raise "srctool failed: " + filelist
75  return [x for x in filelist.split('\r\n') if len(x) != 0]
76
77
78def ReadSourceStream(pdb_filename):
79  """Read the contents of the source information stream from a PDB."""
80  srctool = subprocess.Popen([FindFile('pdbstr.exe'),
81                              '-r', '-s:srcsrv',
82                              '-p:%s' % pdb_filename],
83                             stdout=subprocess.PIPE, stderr=subprocess.PIPE)
84  data = srctool.stdout.read()
85  res = srctool.wait()
86
87  if (res != 0 and res != -1) or data.startswith("pdbstr: "):
88    raise "pdbstr failed: " + data
89  return data
90
91
92def WriteSourceStream(pdb_filename, data):
93  """Write the contents of the source information stream to a PDB."""
94  # Write out the data to a temporary filename that we can pass to pdbstr.
95  (f, fname) = tempfile.mkstemp()
96  f = os.fdopen(f, "wb")
97  f.write(data)
98  f.close()
99
100  srctool = subprocess.Popen([FindFile('pdbstr.exe'),
101                              '-w', '-s:srcsrv',
102                              '-i:%s' % fname,
103                              '-p:%s' % pdb_filename],
104                             stdout=subprocess.PIPE, stderr=subprocess.PIPE)
105  data = srctool.stdout.read()
106  res = srctool.wait()
107
108  if (res != 0 and res != -1) or data.startswith("pdbstr: "):
109    raise "pdbstr failed: " + data
110
111  os.unlink(fname)
112
113
114# TODO for performance, we should probably work in directories instead of
115# files.  I'm scared of DEPS and generated files, so for now we query each
116# individual file, and don't make assumptions that all files in the same
117# directory are part of the same repository or at the same revision number.
118def ExtractSvnInfo(local_filename):
119  """Calls svn info to extract the repository, path, and revision."""
120  # We call svn.bat to make sure and get the depot tools SVN and not cygwin.
121  srctool = subprocess.Popen(['svn.bat', 'info', local_filename],
122                             stdout=subprocess.PIPE, stderr=subprocess.PIPE)
123  info = srctool.stdout.read()
124  res = srctool.wait()
125  if res != 0:
126    return None
127  # Hack up into a dictionary of the fields printed by svn info.
128  vals = dict((y.split(': ', 2) for y in info.split('\r\n') if y))
129
130  root = vals['Repository Root']
131  if not vals['URL'].startswith(root):
132    raise "URL is not inside of the repository root?!?"
133  path = vals['URL'][len(root):]
134  rev  = int(vals['Revision'])
135
136  return [root, path, rev]
137
138
139def UpdatePDB(pdb_filename, verbose=False):
140  """Update a pdb file with source information."""
141  dir_blacklist = { }
142  # TODO(deanm) look into "compressing" our output, by making use of vars
143  # and other things, so we don't need to duplicate the repo path and revs.
144  lines = [
145    'SRCSRV: ini ------------------------------------------------',
146    'VERSION=1',
147    'INDEXVERSION=2',
148    'VERCTRL=Subversion',
149    'DATETIME=%s' % time.asctime(),
150    'SRCSRV: variables ------------------------------------------',
151    'SVN_EXTRACT_TARGET_DIR=%targ%\%fnbksl%(%var3%)\%var4%',
152    'SVN_EXTRACT_TARGET=%svn_extract_target_dir%\%fnfile%(%var1%)',
153    'SVN_EXTRACT_CMD=cmd /c mkdir "%svn_extract_target_dir%" && cmd /c svn cat "%var2%%var3%@%var4%" --non-interactive > "%svn_extract_target%"',
154    'SRCSRVTRG=%SVN_extract_target%',
155    'SRCSRVCMD=%SVN_extract_cmd%',
156    'SRCSRV: source files ---------------------------------------',
157  ]
158
159  if ReadSourceStream(pdb_filename):
160    raise "PDB already has source indexing information!"
161
162  filelist = ExtractSourceFiles(pdb_filename)
163  for filename in filelist:
164    filedir = os.path.dirname(filename)
165
166    if verbose:
167      print "Processing: %s" % filename
168    # This directory is blacklisted, either because it's not part of the SVN
169    # repository, or from one we're not interested in indexing.
170    if dir_blacklist.get(filedir, False):
171      if verbose:
172        print "  skipping, directory is blacklisted."
173      continue
174
175    info = ExtractSvnInfo(filename)
176
177    # Skip the file if it's not under an svn repository.  To avoid constantly
178    # querying SVN for files outside of SVN control (for example, the CRT
179    # sources), check if the directory is outside of SVN and blacklist it.
180    if not info:
181      if not ExtractSvnInfo(filedir):
182        dir_blacklist[filedir] = True
183      if verbose:
184        print "  skipping, file is not in an SVN repository"
185      continue
186
187    root = info[0]
188    path = info[1]
189    rev  = info[2]
190
191    # Check if file was from a svn repository we don't know about, or don't
192    # want to index.  Blacklist the entire directory.
193    if not REPO_MAP.has_key(info[0]):
194      if verbose:
195        print "  skipping, file is from an unknown SVN repository %s" % root
196      dir_blacklist[filedir] = True
197      continue
198
199    # We might want to map an internal repository URL to an external repository.
200    if REPO_MAP[root]:
201      root = REPO_MAP[root]
202
203    lines.append('%s*%s*%s*%s' % (filename, root, path, rev))
204    if verbose:
205      print "  indexed file."
206
207  lines.append('SRCSRV: end ------------------------------------------------')
208
209  WriteSourceStream(pdb_filename, '\r\n'.join(lines))
210
211
212def main():
213  if len(sys.argv) < 2 or len(sys.argv) > 3:
214    print "usage: file.pdb [-v]"
215    return 1
216
217  verbose = False
218  if len(sys.argv) == 3:
219    verbose = (sys.argv[2] == '-v')
220
221  UpdatePDB(sys.argv[1], verbose=verbose)
222  return 0
223
224
225if __name__ == '__main__':
226  sys.exit(main())
227