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