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/strings/utf_string_conversions.h"
22 : #include "base/win/scoped_bstr.h"
23 : #include "base/win/scoped_comptr.h"
24 : #include "syzygy/common/com_utils.h"
25 : #include "syzygy/core/address_range.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: " << common::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: " << common::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: " << common::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: " << common::LogHr(hr)
72 : << ".";
73 i : return false;
74 : }
75 :
76 E : return true;
77 E : }
78 :
79 : const std::string* GetSourceFileName(DWORD source_file_id,
80 : IDiaLineNumber* line_number,
81 : LineInfo::SourceFileSet* source_files,
82 E : SourceFileMap* source_file_map) {
83 E : DCHECK(line_number != NULL);
84 E : DCHECK(source_files != NULL);
85 E : DCHECK(source_file_map != NULL);
86 :
87 E : SourceFileMap::const_iterator map_it = source_file_map->find(source_file_id);
88 :
89 E : if (map_it != source_file_map->end())
90 E : return map_it->second;
91 :
92 E : ScopedComPtr<IDiaSourceFile> source_file;
93 E : HRESULT hr = line_number->get_sourceFile(source_file.Receive());
94 E : if (FAILED(hr)) {
95 i : LOG(ERROR) << "Failure in get_sourceFile: " << common::LogHr(hr) << ".";
96 i : return NULL;
97 : }
98 :
99 E : ScopedBstr source_file_path_bstr;
100 E : hr = source_file->get_fileName(source_file_path_bstr.Receive());
101 E : if (FAILED(hr)) {
102 i : LOG(ERROR) << "Failure in get_fileName: " << common::LogHr(hr) << ".";
103 i : return NULL;
104 : }
105 :
106 E : std::string source_file_path;
107 : if (!base::WideToUTF8(common::ToString(source_file_path_bstr),
108 : source_file_path_bstr.Length(),
109 E : &source_file_path)) {
110 i : LOG(ERROR) << "base::WideToUTF8 failed for path \""
111 : << common::ToString(source_file_path_bstr) << "\".";
112 i : return NULL;
113 : }
114 :
115 : LineInfo::SourceFileSet::const_iterator source_file_it =
116 E : source_files->insert(source_file_path).first;
117 E : const std::string* source_file_name = &(*source_file_it);
118 : source_file_map->insert(std::make_pair(source_file_id,
119 E : source_file_name));
120 :
121 E : return source_file_name;
122 E : }
123 :
124 : // Used for comparing the ranges covered by two source lines.
125 : struct SourceLineAddressComparator {
126 : bool operator()(const LineInfo::SourceLine& sl1,
127 E : const LineInfo::SourceLine& sl2) const {
128 E : return sl1.address + sl1.size <= sl2.address;
129 E : }
130 : };
131 :
132 : } // namespace
133 :
134 E : bool LineInfo::Init(const base::FilePath& pdb_path) {
135 E : ScopedComPtr<IDiaDataSource> source;
136 E : HRESULT hr = source.CreateInstance(CLSID_DiaSource);
137 E : if (FAILED(hr)) {
138 i : LOG(ERROR) << "Failed to create DiaSource: " << common::LogHr(hr) << ".";
139 i : return false;
140 : }
141 :
142 E : ScopedComPtr<IDiaSession> session;
143 E : if (!GetDiaSessionForPdb(pdb_path, source.get(), session.Receive()))
144 i : return false;
145 :
146 : // We want original module addresses so we disable OMAP translation.
147 E : if (!DisableOmapTranslation(session.get()))
148 i : return false;
149 :
150 : // Get the line number enumeration.
151 E : ScopedComPtr<IDiaEnumLineNumbers> line_number_enum;
152 E : hr = session->findLinesByRVA(0, 0xFFFFFF, line_number_enum.Receive());
153 E : if (FAILED(hr)) {
154 i : LOG(ERROR) << "Failure in findLinesByRVA: " << common::LogHr(hr) << ".";
155 i : return false;
156 : }
157 :
158 : // A map of source file IDs we've already seen, mapping back to the source
159 : // file path. We use this as a cache so we're not constantly doing source-file
160 : // lookups while iterating.
161 E : SourceFileMap source_file_map;
162 :
163 : // Get the line info count and reserve space.
164 E : LONG line_number_count = 0;
165 E : hr = line_number_enum->get_Count(&line_number_count);
166 E : if (FAILED(hr)) {
167 i : LOG(ERROR) << "Failure in get_Count: " << common::LogHr(hr) << ".";
168 i : return false;
169 : }
170 E : source_lines_.reserve(line_number_count);
171 :
172 : // Iterate over the source line information.
173 E : DWORD old_source_file_id = SIZE_MAX;
174 E : DWORD old_rva = 0;
175 E : const std::string* source_file_name = NULL;
176 E : while (true) {
177 E : ScopedComPtr<IDiaLineNumber> line_number;
178 E : ULONG fetched = 0;
179 E : hr = line_number_enum->Next(1, line_number.Receive(), &fetched);
180 E : if (hr != S_OK || fetched != 1)
181 E : break;
182 :
183 E : DWORD source_file_id = 0;
184 E : hr = line_number->get_sourceFileId(&source_file_id);
185 E : if (FAILED(hr)) {
186 i : LOG(ERROR) << "Failure in get_sourceFileId: " << common::LogHr(hr) << ".";
187 i : return false;
188 : }
189 :
190 : // Look for the source file by ID. Since we most often see successive
191 : // lines from the same file we have a shortcut to avoid extra processing in
192 : // this case.
193 E : if (source_file_id != old_source_file_id) {
194 : source_file_name = GetSourceFileName(source_file_id,
195 : line_number.get(),
196 : &source_files_,
197 E : &source_file_map);
198 : }
199 E : old_source_file_id = source_file_id;
200 E : DCHECK(source_file_name != NULL);
201 :
202 E : DWORD line = 0;
203 E : DWORD rva = 0;
204 E : DWORD length = 0;
205 : if (FAILED(line_number->get_lineNumber(&line)) ||
206 : FAILED(line_number->get_relativeVirtualAddress(&rva)) ||
207 E : FAILED(line_number->get_length(&length))) {
208 i : LOG(ERROR) << "Failed to get line number properties.";
209 i : return false;
210 : }
211 :
212 : // We rely on the enumeration returning us lines in order of increasing
213 : // address, as they are stored originally in the PDB. This is required for
214 : // the following zero-length fixing mechanism to work as intended.
215 E : DCHECK_LE(old_rva, rva);
216 E : old_rva = rva;
217 :
218 : // Is this a non-zero length? Back up and make any zero-length ranges
219 : // with the same start address the same length as us. This makes them
220 : // simply look like repeated entries in the array and makes searching for
221 : // them with lower_bound/upper_bound work as expected.
222 E : if (length != 0) {
223 E : SourceLines::reverse_iterator it = source_lines_.rbegin();
224 E : for (; it != source_lines_.rend(); ++it) {
225 E : if (it->size != 0)
226 E : break;
227 E : if (it->address.value() != rva) {
228 i : LOG(ERROR) << "Encountered zero-length line number with "
229 : << "inconsistent address.";
230 i : return false;
231 : }
232 E : it->size = length;
233 E : }
234 : }
235 :
236 : source_lines_.push_back(SourceLine(source_file_name,
237 : line,
238 : core::RelativeAddress(rva),
239 E : length));
240 E : }
241 :
242 E : return true;
243 E : }
244 :
245 : bool LineInfo::Visit(
246 E : core::RelativeAddress address, size_t size, size_t count) {
247 : // Visiting a range of size zero is a nop.
248 E : if (size == 0)
249 i : return true;
250 :
251 : // Create a dummy 'source line' for the search.
252 E : SourceLine visit_source_line(NULL, 0, address, size);
253 :
254 : SourceLines::iterator begin_it =
255 : std::lower_bound(source_lines_.begin(),
256 : source_lines_.end(),
257 : visit_source_line,
258 E : SourceLineAddressComparator());
259 :
260 : SourceLines::iterator end_it =
261 : std::upper_bound(source_lines_.begin(),
262 : source_lines_.end(),
263 : visit_source_line,
264 E : SourceLineAddressComparator());
265 :
266 E : SourceLines::iterator it = begin_it;
267 E : RelativeAddressRange visit(address, size);
268 E : for (; it != end_it; ++it) {
269 E : RelativeAddressRange range(it->address, it->size);
270 E : if (visit.Intersects(range)) {
271 : // We use saturation arithmetic here as overflow is a real possibility in
272 : // long trace files.
273 : it->visit_count =
274 : std::min(it->visit_count,
275 E : std::numeric_limits<uint32>::max() - count) + count;
276 : }
277 E : }
278 :
279 E : return true;
280 E : }
281 :
282 : } // namespace grinder
|