Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
stenzek
GitHub Repository: stenzek/duckstation
Path: blob/master/src/util-tests/elf_parser_tests.cpp
4802 views
1
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <[email protected]>
2
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
3
4
#include "util/elf_file.h"
5
6
#include "common/error.h"
7
8
#include <gtest/gtest.h>
9
10
namespace {
11
12
// Helper to create minimal valid ELF file data
13
ELFFile::DataArray CreateValidELFData()
14
{
15
// Create a minimal valid ELF file (32-bit MIPS)
16
constexpr size_t elf_size = 768;
17
ELFFile::DataArray data(elf_size);
18
std::memset(data.data(), 0, data.size());
19
20
// ELF Header
21
auto* ehdr = reinterpret_cast<ELFFile::Elf32_Ehdr*>(data.data());
22
ehdr->e_ident[0] = 0x7F; // Magic bytes
23
ehdr->e_ident[1] = 'E';
24
ehdr->e_ident[2] = 'L';
25
ehdr->e_ident[3] = 'F';
26
ehdr->e_type = ELFFile::ET_EXEC;
27
ehdr->e_machine = ELFFile::EM_MIPS;
28
ehdr->e_version = 1;
29
ehdr->e_entry = 0x80010000; // Entry point
30
ehdr->e_phoff = sizeof(ELFFile::Elf32_Ehdr); // Program header table right after ELF header
31
ehdr->e_shoff =
32
sizeof(ELFFile::Elf32_Ehdr) + 2 * sizeof(ELFFile::Elf32_Phdr); // Section header table after program headers
33
ehdr->e_flags = 0;
34
ehdr->e_ehsize = sizeof(ELFFile::Elf32_Ehdr);
35
ehdr->e_phentsize = sizeof(ELFFile::Elf32_Phdr);
36
ehdr->e_phnum = 2; // Two program headers
37
ehdr->e_shentsize = sizeof(ELFFile::Elf32_Shdr);
38
ehdr->e_shnum = 3; // Three section headers (null + .text + .shstrtab)
39
ehdr->e_shstrndx = 2; // String table is section 2
40
41
// Program Headers
42
auto* phdr = reinterpret_cast<ELFFile::Elf32_Phdr*>(data.data() + ehdr->e_phoff);
43
44
// First program header - loadable segment with code
45
phdr[0].p_type = ELFFile::PT_LOAD;
46
phdr[0].p_offset = 0x100; // Start of section data
47
phdr[0].p_vaddr = 0x80010000; // Virtual address matching entry point
48
phdr[0].p_filesz = 0x100; // Size in file
49
phdr[0].p_memsz = 0x100; // Size in memory
50
phdr[0].p_flags = 5; // Read + execute
51
phdr[0].p_align = 0x1000; // Page alignment
52
53
// Second program header - non-loadable segment
54
phdr[1].p_type = ELFFile::PT_NOTE;
55
56
// Section Headers
57
auto* shdr = reinterpret_cast<ELFFile::Elf32_Shdr*>(data.data() + ehdr->e_shoff);
58
59
// Section 0 - NULL section
60
shdr[0].sh_name = 0;
61
shdr[0].sh_type = ELFFile::SHT_NULL;
62
63
// Section 1 - .text
64
shdr[1].sh_name = 1; // Offset in string table
65
shdr[1].sh_type = ELFFile::SHT_PROGBITS;
66
shdr[1].sh_flags = 6; // Executable, allocated
67
shdr[1].sh_addr = 0x80010000;
68
shdr[1].sh_offset = 0x100; // Start of section data
69
shdr[1].sh_size = 0x100; // Size
70
shdr[1].sh_link = 0;
71
shdr[1].sh_info = 0;
72
shdr[1].sh_addralign = 4;
73
shdr[1].sh_entsize = 0;
74
75
// Section 2 - .shstrtab (string table)
76
shdr[2].sh_name = 7; // Offset in string table
77
shdr[2].sh_type = ELFFile::SHT_STRTAB;
78
shdr[2].sh_flags = 0;
79
shdr[2].sh_addr = 0;
80
shdr[2].sh_offset = 0x200; // String table location
81
shdr[2].sh_size = 0x20; // String table size
82
shdr[2].sh_link = 0;
83
shdr[2].sh_info = 0;
84
shdr[2].sh_addralign = 1;
85
shdr[2].sh_entsize = 0;
86
87
// String table section
88
char* strtab = reinterpret_cast<char*>(data.data() + shdr[2].sh_offset);
89
strtab[0] = '\0'; // Empty string at index 0
90
strcpy(strtab + 1, ".text"); // Section name at index 1
91
strcpy(strtab + 7, ".shstrtab"); // Section name at index 7
92
93
// Add some fake code to the .text section
94
u8* text = data.data() + shdr[1].sh_offset;
95
memset(text, 0xAA, shdr[1].sh_size);
96
97
return data;
98
}
99
100
// Helper to create invalid ELF data (wrong magic)
101
ELFFile::DataArray CreateInvalidELFData()
102
{
103
auto data = CreateValidELFData();
104
data[1] = 'X'; // Corrupt magic number
105
return data;
106
}
107
108
// Helper to create ELF with wrong machine type
109
ELFFile::DataArray CreateWrongMachineELFData()
110
{
111
auto data = CreateValidELFData();
112
auto* ehdr = reinterpret_cast<ELFFile::Elf32_Ehdr*>(data.data());
113
ehdr->e_machine = 0x42; // Not MIPS
114
return data;
115
}
116
117
// Helper to create ELF with missing entry point
118
ELFFile::DataArray CreateMissingEntryELFData()
119
{
120
auto data = CreateValidELFData();
121
auto* ehdr = reinterpret_cast<ELFFile::Elf32_Ehdr*>(data.data());
122
// auto* phdr = reinterpret_cast<ELFFile::Elf32_Phdr*>(data.data() + ehdr->e_phoff);
123
124
// Set entry point outside of loadable segment
125
ehdr->e_entry = 0x90000000;
126
127
return data;
128
}
129
130
// Helper to create ELF with out-of-range program header
131
ELFFile::DataArray CreateOutOfRangeProgramHeaderELFData()
132
{
133
auto data = CreateValidELFData();
134
auto* ehdr = reinterpret_cast<ELFFile::Elf32_Ehdr*>(data.data());
135
auto* phdr = reinterpret_cast<ELFFile::Elf32_Phdr*>(data.data() + ehdr->e_phoff);
136
137
// Set offset and size to be out of file range
138
phdr[0].p_offset = static_cast<u32>(data.size()) - 10;
139
phdr[0].p_filesz = 100;
140
141
return data;
142
}
143
144
class ELFParserTest : public ::testing::Test
145
{
146
protected:
147
void SetUp() override
148
{
149
valid_elf_data = CreateValidELFData();
150
invalid_elf_data = CreateInvalidELFData();
151
wrong_machine_elf_data = CreateWrongMachineELFData();
152
missing_entry_elf_data = CreateMissingEntryELFData();
153
out_of_range_program_header_elf_data = CreateOutOfRangeProgramHeaderELFData();
154
}
155
156
ELFFile::DataArray valid_elf_data;
157
ELFFile::DataArray invalid_elf_data;
158
ELFFile::DataArray wrong_machine_elf_data;
159
ELFFile::DataArray missing_entry_elf_data;
160
ELFFile::DataArray out_of_range_program_header_elf_data;
161
};
162
163
} // namespace
164
165
TEST_F(ELFParserTest, ValidELFHeader)
166
{
167
Error error;
168
EXPECT_TRUE(ELFFile::IsValidElfHeader(valid_elf_data.cspan(), &error));
169
EXPECT_FALSE(error.IsValid());
170
171
// Test the static header checking method
172
const auto& header = *reinterpret_cast<const ELFFile::Elf32_Ehdr*>(valid_elf_data.data());
173
EXPECT_TRUE(ELFFile::IsValidElfHeader(header, &error));
174
}
175
176
TEST_F(ELFParserTest, InvalidELFHeader)
177
{
178
Error error;
179
EXPECT_FALSE(ELFFile::IsValidElfHeader(invalid_elf_data.cspan(), &error));
180
EXPECT_TRUE(error.IsValid());
181
}
182
183
TEST_F(ELFParserTest, WrongMachineType)
184
{
185
Error error;
186
EXPECT_FALSE(ELFFile::IsValidElfHeader(wrong_machine_elf_data.cspan(), &error));
187
EXPECT_TRUE(error.IsValid());
188
}
189
190
TEST_F(ELFParserTest, TooSmallBuffer)
191
{
192
Error error;
193
std::span<const u8> small_span = valid_elf_data.cspan(10); // Too small for header
194
EXPECT_FALSE(ELFFile::IsValidElfHeader(small_span, &error));
195
EXPECT_TRUE(error.IsValid());
196
}
197
198
TEST_F(ELFParserTest, OpenValidELF)
199
{
200
ELFFile elf;
201
Error error;
202
EXPECT_TRUE(elf.Open(std::move(valid_elf_data), &error));
203
EXPECT_FALSE(error.IsValid());
204
}
205
206
TEST_F(ELFParserTest, OpenInvalidELF)
207
{
208
ELFFile elf;
209
Error error;
210
EXPECT_FALSE(elf.Open(invalid_elf_data, &error));
211
EXPECT_TRUE(error.IsValid());
212
}
213
214
TEST_F(ELFParserTest, OpenWrongMachineELF)
215
{
216
ELFFile elf;
217
Error error;
218
EXPECT_FALSE(elf.Open(wrong_machine_elf_data, &error));
219
EXPECT_TRUE(error.IsValid());
220
}
221
222
TEST_F(ELFParserTest, GetELFHeader)
223
{
224
ELFFile elf;
225
Error error;
226
ASSERT_TRUE(elf.Open(std::move(valid_elf_data), &error));
227
228
const auto& header = elf.GetELFHeader();
229
EXPECT_EQ(header.e_type, ELFFile::ET_EXEC);
230
EXPECT_EQ(header.e_machine, ELFFile::EM_MIPS);
231
EXPECT_EQ(header.e_entry, 0x80010000);
232
}
233
234
TEST_F(ELFParserTest, GetEntryPoint)
235
{
236
ELFFile elf;
237
Error error;
238
ASSERT_TRUE(elf.Open(std::move(valid_elf_data), &error));
239
240
EXPECT_EQ(elf.GetEntryPoint(), 0x80010000);
241
}
242
243
TEST_F(ELFParserTest, GetSectionCount)
244
{
245
ELFFile elf;
246
Error error;
247
ASSERT_TRUE(elf.Open(std::move(valid_elf_data), &error));
248
249
EXPECT_EQ(elf.GetSectionCount(), 3u);
250
}
251
252
TEST_F(ELFParserTest, GetValidSectionHeader)
253
{
254
ELFFile elf;
255
Error error;
256
ASSERT_TRUE(elf.Open(std::move(valid_elf_data), &error));
257
258
// Get .text section
259
const auto* section = elf.GetSectionHeader(1);
260
ASSERT_NE(section, nullptr);
261
EXPECT_EQ(section->sh_type, ELFFile::SHT_PROGBITS);
262
EXPECT_EQ(section->sh_addr, 0x80010000);
263
}
264
265
TEST_F(ELFParserTest, GetInvalidSectionHeader)
266
{
267
ELFFile elf;
268
Error error;
269
ASSERT_TRUE(elf.Open(std::move(valid_elf_data), &error));
270
271
// Try to get a section with an out-of-bounds index
272
EXPECT_EQ(elf.GetSectionHeader(99), nullptr);
273
274
// Try the special "undefined" section
275
EXPECT_EQ(elf.GetSectionHeader(ELFFile::SHN_UNDEF), nullptr);
276
}
277
278
TEST_F(ELFParserTest, GetSectionName)
279
{
280
ELFFile elf;
281
Error error;
282
ASSERT_TRUE(elf.Open(std::move(valid_elf_data), &error));
283
284
// Get .text section name
285
const auto* section = elf.GetSectionHeader(1);
286
ASSERT_NE(section, nullptr);
287
EXPECT_EQ(elf.GetSectionName(*section), ".text");
288
289
// Get .shstrtab section name
290
const auto* strtab_section = elf.GetSectionHeader(2);
291
ASSERT_NE(strtab_section, nullptr);
292
EXPECT_EQ(elf.GetSectionName(*strtab_section), ".shstrtab");
293
}
294
295
TEST_F(ELFParserTest, GetProgramHeaderCount)
296
{
297
ELFFile elf;
298
Error error;
299
ASSERT_TRUE(elf.Open(std::move(valid_elf_data), &error));
300
301
EXPECT_EQ(elf.GetProgramHeaderCount(), 2u);
302
}
303
304
TEST_F(ELFParserTest, GetValidProgramHeader)
305
{
306
ELFFile elf;
307
Error error;
308
ASSERT_TRUE(elf.Open(std::move(valid_elf_data), &error));
309
310
// Get loadable segment
311
const auto* phdr = elf.GetProgramHeader(0);
312
ASSERT_NE(phdr, nullptr);
313
EXPECT_EQ(phdr->p_type, ELFFile::PT_LOAD);
314
EXPECT_EQ(phdr->p_vaddr, 0x80010000u);
315
316
// Get note segment
317
const auto* phdr2 = elf.GetProgramHeader(1);
318
ASSERT_NE(phdr2, nullptr);
319
EXPECT_EQ(phdr2->p_type, ELFFile::PT_NOTE);
320
}
321
322
TEST_F(ELFParserTest, GetInvalidProgramHeader)
323
{
324
ELFFile elf;
325
Error error;
326
ASSERT_TRUE(elf.Open(std::move(valid_elf_data), &error));
327
328
// Try to get a program header with an out-of-bounds index
329
EXPECT_EQ(elf.GetProgramHeader(99), nullptr);
330
}
331
332
TEST_F(ELFParserTest, LoadExecutableSections)
333
{
334
ELFFile elf;
335
Error error;
336
ASSERT_TRUE(elf.Open(std::move(valid_elf_data), &error));
337
338
// Track loaded sections
339
struct LoadedSection
340
{
341
std::vector<u8> data;
342
u32 vaddr;
343
u32 size;
344
};
345
std::vector<LoadedSection> loaded_sections;
346
347
bool result = elf.LoadExecutableSections(
348
[&loaded_sections](std::span<const u8> data, u32 dest_vaddr, u32 dest_size, Error* error) {
349
LoadedSection section;
350
section.data.assign(data.begin(), data.end());
351
section.vaddr = dest_vaddr;
352
section.size = dest_size;
353
loaded_sections.push_back(std::move(section));
354
return true;
355
},
356
&error);
357
358
EXPECT_TRUE(result);
359
EXPECT_FALSE(error.IsValid());
360
361
// We should have loaded one section (the loadable segment)
362
ASSERT_EQ(loaded_sections.size(), 1u);
363
EXPECT_EQ(loaded_sections[0].vaddr, 0x80010000u);
364
EXPECT_EQ(loaded_sections[0].size, 0x100u);
365
EXPECT_EQ(loaded_sections[0].data[0], 0xAAu); // Check first byte of our fake code
366
}
367
368
TEST_F(ELFParserTest, MissingEntryPoint)
369
{
370
ELFFile elf;
371
Error error;
372
ASSERT_TRUE(elf.Open(std::move(missing_entry_elf_data), &error));
373
374
bool result = elf.LoadExecutableSections(
375
[](std::span<const u8> data, u32 dest_vaddr, u32 dest_size, Error* error) { return true; }, &error);
376
377
EXPECT_FALSE(result);
378
EXPECT_TRUE(error.IsValid());
379
}
380
381
TEST_F(ELFParserTest, OutOfRangeProgramHeader)
382
{
383
ELFFile elf;
384
Error error;
385
ASSERT_TRUE(elf.Open(std::move(out_of_range_program_header_elf_data), &error));
386
387
bool result = elf.LoadExecutableSections(
388
[](std::span<const u8> data, u32 dest_vaddr, u32 dest_size, Error* error) { return true; }, &error);
389
390
EXPECT_FALSE(result);
391
EXPECT_TRUE(error.IsValid());
392
}
393
394