Coverage Report

Created: 2024-01-21 06:52

/src/bloaty/src/pe.cc
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