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