1 : // Copyright 2012 Google Inc. All Rights Reserved.
2 : //
3 : // Licensed under the Apache License, Version 2.0 (the "License");
4 : // you may not use this file except in compliance with the License.
5 : // You may obtain a copy of the License at
6 : //
7 : // http://www.apache.org/licenses/LICENSE-2.0
8 : //
9 : // Unless required by applicable law or agreed to in writing, software
10 : // distributed under the License is distributed on an "AS IS" BASIS,
11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : // See the License for the specific language governing permissions and
13 : // limitations under the License.
14 :
15 : #include "syzygy/grinder/line_info.h"
16 :
17 : #include <dia2.h>
18 : #include <algorithm>
19 : #include <limits>
20 :
21 : #include "base/utf_string_conversions.h"
22 : #include "base/win/scoped_bstr.h"
23 : #include "base/win/scoped_comptr.h"
24 : #include "sawbuck/common/com_utils.h"
25 : #include "syzygy/core/address_space.h"
26 :
27 : namespace grinder {
28 :
29 : namespace {
30 :
31 : using base::win::ScopedBstr;
32 : using base::win::ScopedComPtr;
33 :
34 : typedef core::AddressRange<core::RelativeAddress, size_t> RelativeAddressRange;
35 : typedef std::map<DWORD, const std::string*> SourceFileMap;
36 :
37 : bool GetDiaSessionForPdb(const base::FilePath& pdb_path,
38 : IDiaDataSource* source,
39 E : IDiaSession** session) {
40 E : DCHECK(source != NULL);
41 E : DCHECK(session != NULL);
42 :
43 E : HRESULT hr = source->loadDataFromPdb(pdb_path.value().c_str());
44 E : if (FAILED(hr)) {
45 i : LOG(ERROR) << "Failure in loadDataFromPdb: " << com::LogHr(hr) << ".";
46 i : return false;
47 : }
48 :
49 E : hr = source->openSession(session);
50 E : if (FAILED(hr)) {
51 i : LOG(ERROR) << "Failure in openSession: " << com::LogHr(hr) << ".";
52 i : return false;
53 : }
54 :
55 E : return true;
56 E : }
57 :
58 :
59 E : bool DisableOmapTranslation(IDiaSession* session) {
60 E : DCHECK(session != NULL);
61 :
62 E : ScopedComPtr<IDiaAddressMap> addr_map;
63 : HRESULT hr = session->QueryInterface(IID_IDiaAddressMap,
64 E : addr_map.ReceiveVoid());
65 E : if (FAILED(hr)) {
66 i : LOG(ERROR) << "Failure in QueryInterface: " << com::LogHr(hr) << ".";
67 i : return false;
68 : }
69 E : hr = addr_map->put_addressMapEnabled(FALSE);
70 E : if (FAILED(hr)) {
71 i : LOG(ERROR) << "Failure in put_addressMapEnabled: " << com::LogHr(hr) << ".";
72 i : return false;
73 : }
74 :
75 E : return true;
76 E : }
77 :
78 : const std::string* GetSourceFileName(DWORD source_file_id,
79 : IDiaLineNumber* line_number,
80 : LineInfo::SourceFileSet* source_files,
81 E : SourceFileMap* source_file_map) {
82 E : DCHECK(line_number != NULL);
83 E : DCHECK(source_files != NULL);
84 E : DCHECK(source_file_map != NULL);
85 :
86 E : SourceFileMap::const_iterator map_it = source_file_map->find(source_file_id);
87 :
88 E : if (map_it != source_file_map->end())
89 E : return map_it->second;
90 :
91 E : ScopedComPtr<IDiaSourceFile> source_file;
92 E : HRESULT hr = line_number->get_sourceFile(source_file.Receive());
93 E : if (FAILED(hr)) {
94 i : LOG(ERROR) << "Failure in get_sourceFile: " << com::LogHr(hr) << ".";
95 i : return NULL;
96 : }
97 :
98 E : ScopedBstr source_file_path_bstr;
99 E : hr = source_file->get_fileName(source_file_path_bstr.Receive());
100 E : if (FAILED(hr)) {
101 i : LOG(ERROR) << "Failure in get_fileName: " << com::LogHr(hr) << ".";
102 i : return NULL;
103 : }
104 :
105 E : std::string source_file_path;
106 : if (!WideToUTF8(com::ToString(source_file_path_bstr),
107 : source_file_path_bstr.Length(),
108 E : &source_file_path)) {
109 i : LOG(ERROR) << "WideToUTF8 failed for path \""
110 : << com::ToString(source_file_path_bstr) << "\".";
111 i : return NULL;
112 : }
113 :
114 : LineInfo::SourceFileSet::const_iterator source_file_it =
115 E : source_files->insert(source_file_path).first;
116 E : const std::string* source_file_name = &(*source_file_it);
117 : source_file_map->insert(std::make_pair(source_file_id,
118 E : source_file_name));
119 :
120 E : return source_file_name;
121 E : }
122 :
123 : // Used for comparing the ranges covered by two source lines.
124 : struct SourceLineAddressComparator {
125 : bool operator()(const LineInfo::SourceLine& sl1,
126 E : const LineInfo::SourceLine& sl2) const {
127 E : return sl1.address + sl1.size <= sl2.address;
128 E : }
129 : };
130 :
131 : } // namespace
132 :
133 E : bool LineInfo::Init(const base::FilePath& pdb_path) {
134 E : ScopedComPtr<IDiaDataSource> source;
135 E : HRESULT hr = source.CreateInstance(CLSID_DiaSource);
136 E : if (FAILED(hr)) {
137 i : LOG(ERROR) << "Failed to create DiaSource: " << com::LogHr(hr) << ".";
138 i : return false;
139 : }
140 :
141 E : ScopedComPtr<IDiaSession> session;
142 E : if (!GetDiaSessionForPdb(pdb_path, source.get(), session.Receive()))
143 i : return false;
144 :
145 : // We want original module addresses so we disable OMAP translation.
146 E : if (!DisableOmapTranslation(session.get()))
147 i : return false;
148 :
149 : // Get the line number enumeration.
150 E : ScopedComPtr<IDiaEnumLineNumbers> line_number_enum;
151 E : hr = session->findLinesByRVA(0, 0xFFFFFF, line_number_enum.Receive());
152 E : if (FAILED(hr)) {
153 i : LOG(ERROR) << "Failure in findLinesByRVA: " << com::LogHr(hr) << ".";
154 i : return false;
155 : }
156 :
157 : // A map of source file IDs we've already seen, mapping back to the source
158 : // file path. We use this as a cache so we're not constantly doing source-file
159 : // lookups while iterating.
160 E : SourceFileMap source_file_map;
161 :
162 : // Get the line info count and reserve space.
163 E : LONG line_number_count = 0;
164 E : hr = line_number_enum->get_Count(&line_number_count);
165 E : if (FAILED(hr)) {
166 i : LOG(ERROR) << "Failure in get_Count: " << com::LogHr(hr) << ".";
167 i : return false;
168 : }
169 E : source_lines_.reserve(line_number_count);
170 :
171 : // Iterate over the source line information.
172 E : DWORD old_source_file_id = -1;
173 E : DWORD old_rva = 0;
174 E : const std::string* source_file_name = NULL;
175 E : while (true) {
176 E : ScopedComPtr<IDiaLineNumber> line_number;
177 E : ULONG fetched = 0;
178 E : hr = line_number_enum->Next(1, line_number.Receive(), &fetched);
179 E : if (hr != S_OK || fetched != 1)
180 E : break;
181 :
182 E : DWORD source_file_id = 0;
183 E : hr = line_number->get_sourceFileId(&source_file_id);
184 E : if (FAILED(hr)) {
185 i : LOG(ERROR) << "Failure in get_sourceFileId: " << com::LogHr(hr) << ".";
186 i : return false;
187 : }
188 :
189 : // Look for the source file by ID. Since we most often see successive
190 : // lines from the same file we have a shortcut to avoid extra processing in
191 : // this case.
192 E : if (source_file_id != old_source_file_id) {
193 : source_file_name = GetSourceFileName(source_file_id,
194 : line_number.get(),
195 : &source_files_,
196 E : &source_file_map);
197 : }
198 E : old_source_file_id = source_file_id;
199 E : DCHECK(source_file_name != NULL);
200 :
201 E : DWORD line = 0;
202 E : DWORD rva = 0;
203 E : DWORD length = 0;
204 : if (FAILED(line_number->get_lineNumber(&line)) ||
205 : FAILED(line_number->get_relativeVirtualAddress(&rva)) ||
206 E : FAILED(line_number->get_length(&length))) {
207 i : LOG(ERROR) << "Failed to get line number properties.";
208 i : return false;
209 : }
210 :
211 : // We rely on the enumeration returning us lines in order of increasing
212 : // address, as they are stored originally in the PDB. This is required for
213 : // the following zero-length fixing mechanism to work as intended.
214 E : DCHECK_LE(old_rva, rva);
215 E : old_rva = rva;
216 :
217 : // Is this a non-zero length? Back up and make any zero-length ranges
218 : // with the same start address the same length as us. This makes them
219 : // simply look like repeated entries in the array and makes searching for
220 : // them with lower_bound/upper_bound work as expected.
221 E : if (length != 0) {
222 E : SourceLines::reverse_iterator it = source_lines_.rbegin();
223 E : for (; it != source_lines_.rend(); ++it) {
224 E : if (it->size != 0)
225 E : break;
226 E : if (it->address.value() != rva) {
227 i : LOG(ERROR) << "Encountered zero-length line number with "
228 : << "inconsistent address.";
229 i : return false;
230 : }
231 E : it->size = length;
232 E : }
233 : }
234 :
235 : source_lines_.push_back(SourceLine(source_file_name,
236 : line,
237 : core::RelativeAddress(rva),
238 E : length));
239 E : }
240 :
241 E : return true;
242 E : }
243 :
244 : bool LineInfo::Visit(
245 E : core::RelativeAddress address, size_t size, size_t count) {
246 : // Visiting a range of size zero is a nop.
247 E : if (size == 0)
248 i : return true;
249 :
250 : // Create a dummy 'source line' for the search.
251 E : SourceLine visit_source_line(NULL, 0, address, size);
252 :
253 : SourceLines::iterator begin_it =
254 : std::lower_bound(source_lines_.begin(),
255 : source_lines_.end(),
256 : visit_source_line,
257 E : SourceLineAddressComparator());
258 :
259 : SourceLines::iterator end_it =
260 : std::upper_bound(source_lines_.begin(),
261 : source_lines_.end(),
262 : visit_source_line,
263 E : SourceLineAddressComparator());
264 :
265 E : SourceLines::iterator it = begin_it;
266 E : RelativeAddressRange visit(address, size);
267 E : for (; it != end_it; ++it) {
268 E : RelativeAddressRange range(it->address, it->size);
269 E : if (visit.Intersects(range)) {
270 : // We use saturation arithmetic here as overflow is a real possibility in
271 : // long trace files.
272 : it->visit_count =
273 : std::min(it->visit_count,
274 E : std::numeric_limits<uint32>::max() - count) + count;
275 : }
276 E : }
277 :
278 E : return true;
279 E : }
280 :
281 : } // namespace grinder
|