Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright 2021 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 "absl/strings/substitute.h" |
16 | | #include "bloaty.h" |
17 | | #include "util.h" |
18 | | |
19 | | using absl::string_view; |
20 | | using std::string; |
21 | | |
22 | | namespace bloaty { |
23 | | namespace pe { |
24 | | constexpr uint16_t dos_magic = 0x5A4D; // MZ |
25 | | |
26 | | // From LIEF/include/LIEF/PE/Structures.hpp.in |
27 | | //! Sizes in bytes of various things in the COFF format. |
28 | | constexpr size_t kHeader16Size = 20; |
29 | | constexpr size_t kHeader32Size = 56; |
30 | | constexpr size_t kNameSize = 8; |
31 | | constexpr size_t kSymbol16Size = 18; |
32 | | constexpr size_t kSymbol32Size = 20; |
33 | | constexpr size_t kSectionSize = 40; |
34 | | constexpr size_t kRelocationSize = 10; |
35 | | constexpr size_t kBaseRelocationBlockSize = 8; |
36 | | constexpr size_t kImportDirectoryTableEntrySize = 20; |
37 | | constexpr size_t kResourceDirectoryTableSize = 16; |
38 | | constexpr size_t kResourceDirectoryEntriesSize = 8; |
39 | | constexpr size_t kResourceDataEntrySize = 16; |
40 | | |
41 | | #include "third_party/lief_pe/pe_enums.h" |
42 | | #include "third_party/lief_pe/pe_structures.h" |
43 | | |
44 | | static_assert(kSectionSize == sizeof(pe_section), |
45 | | "Compiler options broke LIEF struct layout"); |
46 | | static_assert(kRelocationSize == sizeof(pe_relocation), |
47 | | "Compiler options broke LIEF struct layout"); |
48 | | static_assert(kBaseRelocationBlockSize == sizeof(pe_base_relocation_block), |
49 | | "Compiler options broke LIEF struct layout"); |
50 | | static_assert(kImportDirectoryTableEntrySize == sizeof(pe_import), |
51 | | "Compiler options broke LIEF struct layout"); |
52 | | static_assert(kResourceDirectoryTableSize == |
53 | | sizeof(pe_resource_directory_table), |
54 | | "Compiler options broke LIEF struct layout"); |
55 | | static_assert(kResourceDirectoryEntriesSize == |
56 | | sizeof(pe_resource_directory_entries), |
57 | | "Compiler options broke LIEF struct layout"); |
58 | | static_assert(kResourceDataEntrySize == sizeof(pe_resource_data_entry), |
59 | | "Compiler options broke LIEF struct layout"); |
60 | | static_assert(kSymbol16Size == sizeof(pe_symbol), |
61 | | "Compiler options broke LIEF struct layout"); |
62 | | |
63 | | static_assert(sizeof(PE_TYPE) == sizeof(uint16_t), |
64 | | "Compiler options broke PE_TYPE size"); |
65 | | |
66 | | class PeFile { |
67 | | public: |
68 | 1.09k | PeFile(string_view data) : data_(data) { ok_ = Initialize(); } |
69 | | |
70 | 1.81k | bool IsOpen() const { return ok_; } |
71 | | |
72 | 4.32k | string_view entire_file() const { return data_; } |
73 | 792 | string_view pe_headers() const { return pe_headers_; } |
74 | 792 | string_view section_headers() const { return section_headers_; } |
75 | | |
76 | 4.59k | uint32_t section_count() const { return section_count_; } |
77 | 4.32k | string_view section_header(size_t n) const { |
78 | 4.32k | return StrictSubstr(section_headers_, n * sizeof(pe_section), |
79 | 4.32k | sizeof(pe_section)); |
80 | 4.32k | } |
81 | | |
82 | | private: |
83 | | bool Initialize(); |
84 | | |
85 | 1.56k | string_view GetRegion(uint64_t start, uint64_t n) const { |
86 | 1.56k | return StrictSubstr(data_, start, n); |
87 | 1.56k | } |
88 | | |
89 | | bool ok_; |
90 | | bool is_64bit_; |
91 | | const string_view data_; |
92 | | |
93 | | pe_dos_header dos_header_; |
94 | | pe_header pe_header_; |
95 | | |
96 | | string_view pe_headers_; |
97 | | string_view section_headers_; |
98 | | uint32_t section_count_; |
99 | | }; |
100 | | |
101 | 1.09k | bool PeFile::Initialize() { |
102 | 1.09k | if (data_.size() < sizeof(dos_header_)) { |
103 | 0 | return false; |
104 | 0 | } |
105 | | |
106 | 1.09k | memcpy(&dos_header_, data_.data(), sizeof(dos_header_)); |
107 | | |
108 | 1.09k | if (dos_header_.Magic != dos_magic) { |
109 | | // Not a PE file. |
110 | 0 | return false; |
111 | 0 | } |
112 | | |
113 | 1.09k | PE_TYPE Magic; |
114 | 1.09k | auto pe_end = |
115 | 1.09k | CheckedAdd(dos_header_.AddressOfNewExeHeader, sizeof(pe_header_)); |
116 | 1.09k | if (CheckedAdd(pe_end, sizeof(Magic)) > data_.size()) { |
117 | | // Cannot fit the headers / magic from optional header |
118 | 84 | return false; |
119 | 84 | } |
120 | | |
121 | 1.01k | memcpy(&pe_header_, data_.data() + dos_header_.AddressOfNewExeHeader, |
122 | 1.01k | sizeof(pe_header_)); |
123 | | |
124 | 1.01k | if (!std::equal(pe_header_.signature, pe_header_.signature + sizeof(PE_Magic), |
125 | 1.01k | std::begin(PE_Magic))) { |
126 | | // Not a PE file. |
127 | 84 | return false; |
128 | 84 | } |
129 | | |
130 | 930 | memcpy(&Magic, data_.data() + pe_end, sizeof(Magic)); |
131 | | |
132 | 930 | if (Magic != PE_TYPE::PE32 && Magic != PE_TYPE::PE32_PLUS) { |
133 | | // Unknown PE magic |
134 | 84 | return false; |
135 | 84 | } |
136 | | |
137 | 846 | is_64bit_ = Magic == PE_TYPE::PE32_PLUS; |
138 | | |
139 | 846 | section_count_ = pe_header_.NumberOfSections; |
140 | | |
141 | | // TODO(mj): Figure out if we should trust SizeOfOptionalHeader here |
142 | 846 | const uint32_t sections_offset = dos_header_.AddressOfNewExeHeader + |
143 | 846 | sizeof(pe_header_) + |
144 | 846 | pe_header_.SizeOfOptionalHeader; |
145 | | |
146 | 846 | auto sections_size = CheckedMul(section_count_, sizeof(pe_section)); |
147 | 846 | if ((sections_offset + sections_size) > data_.size()) { |
148 | | // Cannot fit the headers |
149 | 66 | return false; |
150 | 66 | } |
151 | | |
152 | 780 | pe_headers_ = GetRegion(0, sections_offset); |
153 | 780 | section_headers_ = GetRegion(sections_offset, sections_size); |
154 | | |
155 | 780 | return true; |
156 | 846 | } |
157 | | |
158 | | class Section { |
159 | | public: |
160 | | string name; |
161 | | |
162 | 4.32k | uint32_t raw_offset() const { return header_.PointerToRawData; } |
163 | 4.32k | uint32_t raw_size() const { return header_.SizeOfRawData; } |
164 | | |
165 | 4.32k | uint32_t virtual_addr() const { return header_.VirtualAddress; } |
166 | 4.32k | uint32_t virtual_size() const { return header_.VirtualSize; } |
167 | | |
168 | 4.32k | Section(string_view header_data) { |
169 | 4.32k | assert(header_data.size() == sizeof(header_)); |
170 | 0 | memcpy(&header_, header_data.data(), sizeof(header_)); |
171 | | |
172 | | // TODO(mj): Handle long section names: |
173 | | // For longer names, this member contains a forward slash (/) followed by an |
174 | | // ASCII representation of a decimal number that is an offset into the |
175 | | // string table. |
176 | 4.32k | name = string(header_.Name, strnlen(header_.Name, kNameSize)); |
177 | 4.32k | } |
178 | | |
179 | | private: |
180 | | pe_section header_; |
181 | | }; |
182 | | |
183 | | template <class Func> |
184 | 456 | void ForEachSection(const PeFile& pe, Func&& section_func) { |
185 | 4.78k | for (auto n = 0; n < pe.section_count(); ++n) { |
186 | 4.32k | Section section(pe.section_header(n)); |
187 | 4.32k | section_func(section); |
188 | 4.32k | } |
189 | 456 | } |
190 | | |
191 | 456 | void ParseSections(const PeFile& pe, RangeSink* sink) { |
192 | 456 | assert(pe.IsOpen()); |
193 | 4.32k | ForEachSection(pe, [sink, &pe](const Section& section) { |
194 | 4.32k | uint64_t vmaddr = section.virtual_addr(); |
195 | 4.32k | uint64_t vmsize = section.virtual_size(); |
196 | 4.32k | absl::string_view section_data = StrictSubstr( |
197 | 4.32k | pe.entire_file(), section.raw_offset(), section.raw_size()); |
198 | | |
199 | 4.32k | sink->AddRange("pe_sections", section.name, vmaddr, vmsize, section_data); |
200 | 4.32k | }); |
201 | 456 | } |
202 | | |
203 | 264 | void AddCatchAll(const PeFile& pe, RangeSink* sink) { |
204 | 264 | assert(pe.IsOpen()); |
205 | | |
206 | 0 | auto begin = pe.pe_headers().data() - sink->input_file().data().data(); |
207 | 264 | sink->AddRange("pe_catchall", "[PE Headers]", begin, pe.pe_headers().size(), |
208 | 264 | pe.pe_headers()); |
209 | | |
210 | 264 | begin = pe.section_headers().data() - sink->input_file().data().data(); |
211 | 264 | sink->AddRange("pe_catchall", "[PE Section Headers]", begin, |
212 | 264 | pe.section_headers().size(), pe.section_headers()); |
213 | | |
214 | | // The last-line fallback to make sure we cover the entire file. |
215 | 264 | sink->AddFileRange("pe_catchall", "[Unmapped]", sink->input_file().data()); |
216 | 264 | } |
217 | | |
218 | | class PEObjectFile : public ObjectFile { |
219 | | public: |
220 | | PEObjectFile(std::unique_ptr<InputFile> file_data, |
221 | | std::unique_ptr<pe::PeFile> pe) |
222 | 780 | : ObjectFile(std::move(file_data)), pe_file(std::move(pe)) {} |
223 | | |
224 | 780 | std::string GetBuildId() const override { |
225 | | // TODO(mj): Read from pe_pdb_?? |
226 | 780 | return std::string(); |
227 | 780 | } |
228 | | |
229 | 390 | void ProcessFile(const std::vector<RangeSink*>& sinks) const override { |
230 | 588 | for (auto sink : sinks) { |
231 | 588 | switch (sink->data_source()) { |
232 | 423 | case DataSource::kSegments: |
233 | | // TODO(mj): sections: list out imports and other stuff! |
234 | 456 | case DataSource::kSections: |
235 | 456 | ParseSections(*pe_file, sink); |
236 | 456 | break; |
237 | 0 | case DataSource::kSymbols: |
238 | 0 | case DataSource::kRawSymbols: |
239 | 33 | case DataSource::kShortSymbols: |
240 | 33 | case DataSource::kFullSymbols: |
241 | | // TODO(mj): Generate symbols from debug info, exports, imports, tls |
242 | | // data, relocations, resources ... |
243 | 66 | case DataSource::kArchiveMembers: |
244 | 99 | case DataSource::kCompileUnits: |
245 | 132 | case DataSource::kInlines: |
246 | 132 | default: |
247 | 132 | THROW("PE doesn't support this data source"); |
248 | 588 | } |
249 | 264 | AddCatchAll(*pe_file, sink); |
250 | 264 | } |
251 | 390 | } |
252 | | |
253 | | bool GetDisassemblyInfo(string_view /*symbol*/, DataSource /*symbol_source*/, |
254 | 0 | DisassemblyInfo* /*info*/) const override { |
255 | 0 | WARN("PE files do not support disassembly yet"); |
256 | 0 | return false; |
257 | 0 | } |
258 | | |
259 | | protected: |
260 | | std::unique_ptr<pe::PeFile> pe_file; |
261 | | }; |
262 | | |
263 | 15.6M | bool ReadMagic(const string_view& data) { |
264 | | // If the size is smaller than a dos header, it cannot be a PE file, right? |
265 | 15.6M | if (data.size() < sizeof(pe_dos_header)) { |
266 | 15.6M | return false; |
267 | 15.6M | } |
268 | | |
269 | 22.2k | uint16_t magic; |
270 | 22.2k | memcpy(&magic, data.data(), sizeof(magic)); |
271 | | |
272 | 22.2k | return magic == dos_magic; |
273 | 15.6M | } |
274 | | } // namespace pe |
275 | | |
276 | 15.6M | std::unique_ptr<ObjectFile> TryOpenPEFile(std::unique_ptr<InputFile>& file) { |
277 | | // Do not bother creating an object if the first magic is not even there |
278 | 15.6M | if (pe::ReadMagic(file->data())) { |
279 | 1.09k | std::unique_ptr<pe::PeFile> pe(new pe::PeFile(file->data())); |
280 | | |
281 | 1.09k | if (pe->IsOpen()) { |
282 | 780 | return std::unique_ptr<ObjectFile>( |
283 | 780 | new pe::PEObjectFile(std::move(file), std::move(pe))); |
284 | 780 | } |
285 | 1.09k | } |
286 | | |
287 | 15.6M | return nullptr; |
288 | 15.6M | } |
289 | | |
290 | | } // namespace bloaty |