]> git.ipfire.org Git - thirdparty/systemd.git/blame - tools/elf2efi.py
tools/elf2efi: align columns in tables, unify formatting
[thirdparty/systemd.git] / tools / elf2efi.py
CommitLineData
2afeaf16
JJ
1#!/usr/bin/env python3
2# SPDX-License-Identifier: LGPL-2.1-or-later
3
4# Convert ELF static PIE to PE/EFI image.
5
6# To do so we simply copy desired ELF sections while preserving their memory layout to ensure that
7# code still runs as expected. We then translate ELF relocations to PE relocations so that the EFI
8# loader/firmware can properly load the binary to any address at runtime.
9#
10# To make this as painless as possible we only operate on static PIEs as they should only contain
11# base relocations that are easy to handle as they have a one-to-one mapping to PE relocations.
12#
13# EDK2 does a similar process using their GenFw tool. The main difference is that they use the
14# --emit-relocs linker flag, which emits a lot of different (static) ELF relocation types that have
15# to be handled differently for each architecture and is overall more work than its worth.
16#
17# Note that on arches where binutils has PE support (x86/x86_64 mostly, aarch64 only recently)
18# objcopy can be used to convert ELF to PE. But this will still not convert ELF relocations, making
19# the resulting binary useless. gnu-efi relies on this method and contains a stub that performs the
20# ELF dynamic relocations at runtime.
21
79f902eb 22# pylint: disable=attribute-defined-outside-init
2afeaf16
JJ
23
24import argparse
25import hashlib
26import io
27import os
28import pathlib
29import time
ab7d54b9 30import typing
2afeaf16
JJ
31from ctypes import (
32 c_char,
33 c_uint8,
34 c_uint16,
35 c_uint32,
36 c_uint64,
37 LittleEndianStructure,
38 sizeof,
39)
40
41from elftools.elf.constants import SH_FLAGS
142f0c61 42from elftools.elf.elffile import ELFFile
2afeaf16
JJ
43from elftools.elf.enums import (
44 ENUM_DT_FLAGS_1,
45 ENUM_RELOC_TYPE_AARCH64,
46 ENUM_RELOC_TYPE_ARM,
47 ENUM_RELOC_TYPE_i386,
48 ENUM_RELOC_TYPE_x64,
49)
50from elftools.elf.relocation import (
51 Relocation as ElfRelocation,
52 RelocationTable as ElfRelocationTable,
53)
54
55
56class PeCoffHeader(LittleEndianStructure):
57 _fields_ = (
a0797b4a
ZJS
58 ("Machine", c_uint16),
59 ("NumberOfSections", c_uint16),
60 ("TimeDateStamp", c_uint32),
2afeaf16 61 ("PointerToSymbolTable", c_uint32),
a0797b4a 62 ("NumberOfSymbols", c_uint32),
2afeaf16 63 ("SizeOfOptionalHeader", c_uint16),
a0797b4a 64 ("Characteristics", c_uint16),
2afeaf16
JJ
65 )
66
67
68class PeDataDirectory(LittleEndianStructure):
69 _fields_ = (
70 ("VirtualAddress", c_uint32),
a0797b4a 71 ("Size", c_uint32),
2afeaf16
JJ
72 )
73
74
75class PeRelocationBlock(LittleEndianStructure):
76 _fields_ = (
a0797b4a 77 ("PageRVA", c_uint32),
2afeaf16
JJ
78 ("BlockSize", c_uint32),
79 )
80
81 def __init__(self, PageRVA: int):
82 super().__init__(PageRVA)
09444a2e 83 self.entries: typing.List[PeRelocationEntry] = []
2afeaf16
JJ
84
85
86class PeRelocationEntry(LittleEndianStructure):
87 _fields_ = (
88 ("Offset", c_uint16, 12),
a0797b4a 89 ("Type", c_uint16, 4),
2afeaf16
JJ
90 )
91
92
93class PeOptionalHeaderStart(LittleEndianStructure):
94 _fields_ = (
a0797b4a
ZJS
95 ("Magic", c_uint16),
96 ("MajorLinkerVersion", c_uint8),
97 ("MinorLinkerVersion", c_uint8),
98 ("SizeOfCode", c_uint32),
99 ("SizeOfInitializedData", c_uint32),
2afeaf16 100 ("SizeOfUninitializedData", c_uint32),
a0797b4a
ZJS
101 ("AddressOfEntryPoint", c_uint32),
102 ("BaseOfCode", c_uint32),
2afeaf16
JJ
103 )
104
105
106class PeOptionalHeaderMiddle(LittleEndianStructure):
107 _fields_ = (
a0797b4a
ZJS
108 ("SectionAlignment", c_uint32),
109 ("FileAlignment", c_uint32),
2afeaf16
JJ
110 ("MajorOperatingSystemVersion", c_uint16),
111 ("MinorOperatingSystemVersion", c_uint16),
a0797b4a
ZJS
112 ("MajorImageVersion", c_uint16),
113 ("MinorImageVersion", c_uint16),
114 ("MajorSubsystemVersion", c_uint16),
115 ("MinorSubsystemVersion", c_uint16),
116 ("Win32VersionValue", c_uint32),
117 ("SizeOfImage", c_uint32),
118 ("SizeOfHeaders", c_uint32),
119 ("CheckSum", c_uint32),
120 ("Subsystem", c_uint16),
121 ("DllCharacteristics", c_uint16),
2afeaf16
JJ
122 )
123
124
125class PeOptionalHeaderEnd(LittleEndianStructure):
126 _fields_ = (
a0797b4a
ZJS
127 ("LoaderFlags", c_uint32),
128 ("NumberOfRvaAndSizes", c_uint32),
129 ("ExportTable", PeDataDirectory),
130 ("ImportTable", PeDataDirectory),
131 ("ResourceTable", PeDataDirectory),
132 ("ExceptionTable", PeDataDirectory),
133 ("CertificateTable", PeDataDirectory),
134 ("BaseRelocationTable", PeDataDirectory),
135 ("Debug", PeDataDirectory),
136 ("Architecture", PeDataDirectory),
137 ("GlobalPtr", PeDataDirectory),
138 ("TLSTable", PeDataDirectory),
139 ("LoadConfigTable", PeDataDirectory),
140 ("BoundImport", PeDataDirectory),
141 ("IAT", PeDataDirectory),
2afeaf16 142 ("DelayImportDescriptor", PeDataDirectory),
a0797b4a
ZJS
143 ("CLRRuntimeHeader", PeDataDirectory),
144 ("Reserved", PeDataDirectory),
2afeaf16
JJ
145 )
146
147
148class PeOptionalHeader(LittleEndianStructure):
149 pass
150
151
152class PeOptionalHeader32(PeOptionalHeader):
153 _anonymous_ = ("Start", "Middle", "End")
154 _fields_ = (
a0797b4a
ZJS
155 ("Start", PeOptionalHeaderStart),
156 ("BaseOfData", c_uint32),
157 ("ImageBase", c_uint32),
158 ("Middle", PeOptionalHeaderMiddle),
2afeaf16 159 ("SizeOfStackReserve", c_uint32),
a0797b4a
ZJS
160 ("SizeOfStackCommit", c_uint32),
161 ("SizeOfHeapReserve", c_uint32),
162 ("SizeOfHeapCommit", c_uint32),
163 ("End", PeOptionalHeaderEnd),
2afeaf16
JJ
164 )
165
166
167class PeOptionalHeader32Plus(PeOptionalHeader):
168 _anonymous_ = ("Start", "Middle", "End")
169 _fields_ = (
a0797b4a
ZJS
170 ("Start", PeOptionalHeaderStart),
171 ("ImageBase", c_uint64),
172 ("Middle", PeOptionalHeaderMiddle),
2afeaf16 173 ("SizeOfStackReserve", c_uint64),
a0797b4a
ZJS
174 ("SizeOfStackCommit", c_uint64),
175 ("SizeOfHeapReserve", c_uint64),
176 ("SizeOfHeapCommit", c_uint64),
177 ("End", PeOptionalHeaderEnd),
2afeaf16
JJ
178 )
179
180
181class PeSection(LittleEndianStructure):
182 _fields_ = (
a0797b4a
ZJS
183 ("Name", c_char * 8),
184 ("VirtualSize", c_uint32),
185 ("VirtualAddress", c_uint32),
186 ("SizeOfRawData", c_uint32),
187 ("PointerToRawData", c_uint32),
2afeaf16
JJ
188 ("PointerToRelocations", c_uint32),
189 ("PointerToLinenumbers", c_uint32),
a0797b4a
ZJS
190 ("NumberOfRelocations", c_uint16),
191 ("NumberOfLinenumbers", c_uint16),
192 ("Characteristics", c_uint32),
2afeaf16
JJ
193 )
194
195 def __init__(self):
196 super().__init__()
197 self.data = bytearray()
198
199
200N_DATA_DIRECTORY_ENTRIES = 16
201
202assert sizeof(PeSection) == 40
203assert sizeof(PeCoffHeader) == 20
204assert sizeof(PeOptionalHeader32) == 224
205assert sizeof(PeOptionalHeader32Plus) == 240
206
142f0c61
JJ
207PE_CHARACTERISTICS_RX = 0x60000020 # CNT_CODE|MEM_READ|MEM_EXECUTE
208PE_CHARACTERISTICS_RW = 0xC0000040 # CNT_INITIALIZED_DATA|MEM_READ|MEM_WRITE
a0797b4a 209PE_CHARACTERISTICS_R = 0x40000040 # CNT_INITIALIZED_DATA|MEM_READ
142f0c61
JJ
210
211IGNORE_SECTIONS = [
212 ".eh_frame",
213 ".eh_frame_hdr",
214 ".ARM.exidx",
215]
216
217IGNORE_SECTION_TYPES = [
218 "SHT_DYNAMIC",
219 "SHT_DYNSYM",
220 "SHT_GNU_ATTRIBUTES",
221 "SHT_GNU_HASH",
222 "SHT_HASH",
223 "SHT_NOTE",
224 "SHT_REL",
225 "SHT_RELA",
226 "SHT_RELR",
227 "SHT_STRTAB",
228 "SHT_SYMTAB",
229]
230
2afeaf16
JJ
231# EFI mandates 4KiB memory pages.
232SECTION_ALIGNMENT = 4096
233FILE_ALIGNMENT = 512
234
235# Nobody cares about DOS headers, so put the PE header right after.
236PE_OFFSET = 64
ee91e06a 237PE_MAGIC = b"PE\0\0"
2afeaf16
JJ
238
239
240def align_to(x: int, align: int) -> int:
241 return (x + align - 1) & ~(align - 1)
242
243
142f0c61
JJ
244def align_down(x: int, align: int) -> int:
245 return x & ~(align - 1)
2afeaf16 246
2afeaf16 247
7d6fd7f0 248def next_section_address(sections: typing.List[PeSection]) -> int:
a0797b4a
ZJS
249 return align_to(sections[-1].VirtualAddress + sections[-1].VirtualSize,
250 SECTION_ALIGNMENT)
7d6fd7f0
JJ
251
252
142f0c61
JJ
253def iter_copy_sections(elf: ELFFile) -> typing.Iterator[PeSection]:
254 pe_s = None
2afeaf16 255
142f0c61
JJ
256 # This is essentially the same as copying by ELF load segments, except that we assemble them
257 # manually, so that we can easily strip unwanted sections. We try to only discard things we know
258 # about so that there are no surprises.
2afeaf16 259
823bf39a
JJ
260 relro = None
261 for elf_seg in elf.iter_segments():
262 if elf_seg["p_type"] == "PT_LOAD" and elf_seg["p_align"] != SECTION_ALIGNMENT:
263 raise RuntimeError("ELF segments are not properly aligned.")
264 elif elf_seg["p_type"] == "PT_GNU_RELRO":
265 relro = elf_seg
266
142f0c61
JJ
267 for elf_s in elf.iter_sections():
268 if (
269 elf_s["sh_flags"] & SH_FLAGS.SHF_ALLOC == 0
270 or elf_s["sh_type"] in IGNORE_SECTION_TYPES
271 or elf_s.name in IGNORE_SECTIONS
272 ):
273 continue
274 if elf_s["sh_type"] not in ["SHT_PROGBITS", "SHT_NOBITS"]:
275 raise RuntimeError(f"Unknown section {elf_s.name}.")
2afeaf16 276
142f0c61
JJ
277 if elf_s["sh_flags"] & SH_FLAGS.SHF_EXECINSTR:
278 rwx = PE_CHARACTERISTICS_RX
279 elif elf_s["sh_flags"] & SH_FLAGS.SHF_WRITE:
280 rwx = PE_CHARACTERISTICS_RW
281 else:
282 rwx = PE_CHARACTERISTICS_R
2afeaf16 283
823bf39a
JJ
284 # PE images are always relro.
285 if relro and relro.section_in_segment(elf_s):
286 rwx = PE_CHARACTERISTICS_R
287
142f0c61
JJ
288 if pe_s and pe_s.Characteristics != rwx:
289 yield pe_s
290 pe_s = None
2afeaf16 291
142f0c61
JJ
292 if pe_s:
293 # Insert padding to properly align the section.
294 pad_len = elf_s["sh_addr"] - pe_s.VirtualAddress - len(pe_s.data)
295 pe_s.data += bytearray(pad_len) + elf_s.data()
296 else:
297 pe_s = PeSection()
298 pe_s.VirtualAddress = elf_s["sh_addr"]
299 pe_s.Characteristics = rwx
300 pe_s.data = elf_s.data()
2afeaf16 301
142f0c61
JJ
302 if pe_s:
303 yield pe_s
2afeaf16
JJ
304
305
142f0c61
JJ
306def convert_sections(elf: ELFFile, opt: PeOptionalHeader) -> typing.List[PeSection]:
307 last_vma = 0
2afeaf16
JJ
308 sections = []
309
142f0c61
JJ
310 for pe_s in iter_copy_sections(elf):
311 # Truncate the VMA to the nearest page and insert appropriate padding. This should not
312 # cause any overlap as this is pretty much how ELF *segments* are loaded/mmapped anyways.
313 # The ELF sections inside should also be properly aligned as we reuse the ELF VMA layout
314 # for the PE image.
315 vma = pe_s.VirtualAddress
316 pe_s.VirtualAddress = align_down(vma, SECTION_ALIGNMENT)
317 pe_s.data = bytearray(vma - pe_s.VirtualAddress) + pe_s.data
318
319 pe_s.VirtualSize = len(pe_s.data)
320 pe_s.SizeOfRawData = align_to(len(pe_s.data), FILE_ALIGNMENT)
321 pe_s.Name = {
322 PE_CHARACTERISTICS_RX: b".text",
323 PE_CHARACTERISTICS_RW: b".data",
324 PE_CHARACTERISTICS_R: b".rodata",
325 }[pe_s.Characteristics]
326
327 # This can happen if not building with `-z separate-code`.
328 if pe_s.VirtualAddress < last_vma:
329 raise RuntimeError("Overlapping PE sections.")
330 last_vma = pe_s.VirtualAddress + pe_s.VirtualSize
2afeaf16 331
2afeaf16
JJ
332 if pe_s.Name == b".text":
333 opt.BaseOfCode = pe_s.VirtualAddress
334 opt.SizeOfCode += pe_s.VirtualSize
335 else:
336 opt.SizeOfInitializedData += pe_s.VirtualSize
337
338 if pe_s.Name == b".data" and isinstance(opt, PeOptionalHeader32):
339 opt.BaseOfData = pe_s.VirtualAddress
340
341 sections.append(pe_s)
342
343 return sections
344
345
898e9edc
JJ
346def copy_sections(
347 elf: ELFFile,
348 opt: PeOptionalHeader,
349 input_names: str,
350 sections: typing.List[PeSection],
351):
352 for name in input_names.split(","):
353 elf_s = elf.get_section_by_name(name)
354 if not elf_s:
355 continue
356 if elf_s.data_alignment > 1 and SECTION_ALIGNMENT % elf_s.data_alignment != 0:
357 raise RuntimeError(f"ELF section {name} is not aligned.")
358 if elf_s["sh_flags"] & (SH_FLAGS.SHF_EXECINSTR | SH_FLAGS.SHF_WRITE) != 0:
359 raise RuntimeError(f"ELF section {name} is not read-only data.")
360
361 pe_s = PeSection()
362 pe_s.Name = name.encode()
363 pe_s.data = elf_s.data()
364 pe_s.VirtualAddress = next_section_address(sections)
365 pe_s.VirtualSize = len(elf_s.data())
366 pe_s.SizeOfRawData = align_to(len(elf_s.data()), FILE_ALIGNMENT)
367 pe_s.Characteristics = PE_CHARACTERISTICS_R
368 opt.SizeOfInitializedData += pe_s.VirtualSize
369 sections.append(pe_s)
370
371
2afeaf16 372def apply_elf_relative_relocation(
ee91e06a
JJ
373 reloc: ElfRelocation,
374 image_base: int,
375 sections: typing.List[PeSection],
376 addend_size: int,
2afeaf16 377):
a0797b4a
ZJS
378 [target] = [pe_s for pe_s in sections
379 if pe_s.VirtualAddress <= reloc["r_offset"] < pe_s.VirtualAddress + len(pe_s.data)]
2afeaf16
JJ
380
381 addend_offset = reloc["r_offset"] - target.VirtualAddress
382
383 if reloc.is_RELA():
384 addend = reloc["r_addend"]
385 else:
386 addend = target.data[addend_offset : addend_offset + addend_size]
387 addend = int.from_bytes(addend, byteorder="little")
388
2afeaf16
JJ
389 value = (image_base + addend).to_bytes(addend_size, byteorder="little")
390 target.data[addend_offset : addend_offset + addend_size] = value
391
392
393def convert_elf_reloc_table(
394 elf: ELFFile,
395 elf_reloc_table: ElfRelocationTable,
142f0c61 396 elf_image_base: int,
09444a2e
DDM
397 sections: typing.List[PeSection],
398 pe_reloc_blocks: typing.Dict[int, PeRelocationBlock],
2afeaf16
JJ
399):
400 NONE_RELOC = {
401 "EM_386": ENUM_RELOC_TYPE_i386["R_386_NONE"],
402 "EM_AARCH64": ENUM_RELOC_TYPE_AARCH64["R_AARCH64_NONE"],
403 "EM_ARM": ENUM_RELOC_TYPE_ARM["R_ARM_NONE"],
31ffb6b1 404 "EM_LOONGARCH": 0,
2afeaf16
JJ
405 "EM_RISCV": 0,
406 "EM_X86_64": ENUM_RELOC_TYPE_x64["R_X86_64_NONE"],
407 }[elf["e_machine"]]
408
409 RELATIVE_RELOC = {
410 "EM_386": ENUM_RELOC_TYPE_i386["R_386_RELATIVE"],
411 "EM_AARCH64": ENUM_RELOC_TYPE_AARCH64["R_AARCH64_RELATIVE"],
412 "EM_ARM": ENUM_RELOC_TYPE_ARM["R_ARM_RELATIVE"],
31ffb6b1 413 "EM_LOONGARCH": 3,
2afeaf16
JJ
414 "EM_RISCV": 3,
415 "EM_X86_64": ENUM_RELOC_TYPE_x64["R_X86_64_RELATIVE"],
416 }[elf["e_machine"]]
417
418 for reloc in elf_reloc_table.iter_relocations():
419 if reloc["r_info_type"] == NONE_RELOC:
420 continue
421
422 if reloc["r_info_type"] == RELATIVE_RELOC:
a0797b4a
ZJS
423 apply_elf_relative_relocation(reloc,
424 elf_image_base,
425 sections,
426 elf.elfclass // 8)
2afeaf16
JJ
427
428 # Now that the ELF relocation has been applied, we can create a PE relocation.
429 block_rva = reloc["r_offset"] & ~0xFFF
430 if block_rva not in pe_reloc_blocks:
431 pe_reloc_blocks[block_rva] = PeRelocationBlock(block_rva)
432
433 entry = PeRelocationEntry()
434 entry.Offset = reloc["r_offset"] & 0xFFF
435 # REL_BASED_HIGHLOW or REL_BASED_DIR64
436 entry.Type = 3 if elf.elfclass == 32 else 10
437 pe_reloc_blocks[block_rva].entries.append(entry)
438
439 continue
440
441 raise RuntimeError(f"Unsupported relocation {reloc}")
442
443
444def convert_elf_relocations(
142f0c61
JJ
445 elf: ELFFile,
446 opt: PeOptionalHeader,
447 sections: typing.List[PeSection],
448 minimum_sections: int,
ab7d54b9 449) -> typing.Optional[PeSection]:
2afeaf16
JJ
450 dynamic = elf.get_section_by_name(".dynamic")
451 if dynamic is None:
452 raise RuntimeError("ELF .dynamic section is missing.")
453
454 [flags_tag] = dynamic.iter_tags("DT_FLAGS_1")
455 if not flags_tag["d_val"] & ENUM_DT_FLAGS_1["DF_1_PIE"]:
456 raise RuntimeError("ELF file is not a PIE.")
457
5713c50d
JJ
458 # This checks that the ELF image base is 0.
459 symtab = elf.get_section_by_name(".symtab")
460 if symtab:
461 exe_start = symtab.get_symbol_by_name("__executable_start")
462 if exe_start and exe_start[0]["st_value"] != 0:
463 raise RuntimeError("Unexpected ELF image base.")
464
a0797b4a
ZJS
465 opt.SizeOfHeaders = align_to(PE_OFFSET
466 + len(PE_MAGIC)
467 + sizeof(PeCoffHeader)
468 + sizeof(opt)
469 + sizeof(PeSection) * max(len(sections) + 1, minimum_sections),
470 FILE_ALIGNMENT)
142f0c61
JJ
471
472 # We use the basic VMA layout from the ELF image in the PE image. This could cause the first
473 # section to overlap the PE image headers during runtime at VMA 0. We can simply apply a fixed
474 # offset relative to the PE image base when applying/converting ELF relocations. Afterwards we
475 # just have to apply the offset to the PE addresses so that the PE relocations work correctly on
476 # the ELF portions of the image.
477 segment_offset = 0
478 if sections[0].VirtualAddress < opt.SizeOfHeaders:
a0797b4a
ZJS
479 segment_offset = align_to(opt.SizeOfHeaders - sections[0].VirtualAddress,
480 SECTION_ALIGNMENT)
142f0c61
JJ
481
482 opt.AddressOfEntryPoint = elf["e_entry"] + segment_offset
483 opt.BaseOfCode += segment_offset
484 if isinstance(opt, PeOptionalHeader32):
485 opt.BaseOfData += segment_offset
486
09444a2e 487 pe_reloc_blocks: typing.Dict[int, PeRelocationBlock] = {}
2afeaf16
JJ
488 for reloc_type, reloc_table in dynamic.get_relocation_tables().items():
489 if reloc_type not in ["REL", "RELA"]:
490 raise RuntimeError("Unsupported relocation type {elf_reloc_type}.")
a0797b4a
ZJS
491 convert_elf_reloc_table(elf,
492 reloc_table,
493 opt.ImageBase + segment_offset,
494 sections,
495 pe_reloc_blocks)
2afeaf16 496
142f0c61
JJ
497 for pe_s in sections:
498 pe_s.VirtualAddress += segment_offset
499
ab7d54b9
JJ
500 if len(pe_reloc_blocks) == 0:
501 return None
502
2afeaf16
JJ
503 data = bytearray()
504 for rva in sorted(pe_reloc_blocks):
505 block = pe_reloc_blocks[rva]
506 n_relocs = len(block.entries)
507
508 # Each block must start on a 32-bit boundary. Because each entry is 16 bits
509 # the len has to be even. We pad by adding a none relocation.
510 if n_relocs % 2 != 0:
511 n_relocs += 1
512 block.entries.append(PeRelocationEntry())
513
142f0c61 514 block.PageRVA += segment_offset
a0797b4a 515 block.BlockSize = sizeof(PeRelocationBlock) + sizeof(PeRelocationEntry) * n_relocs
2afeaf16
JJ
516 data += block
517 for entry in sorted(block.entries, key=lambda e: e.Offset):
518 data += entry
519
520 pe_reloc_s = PeSection()
521 pe_reloc_s.Name = b".reloc"
522 pe_reloc_s.data = data
7d6fd7f0 523 pe_reloc_s.VirtualAddress = next_section_address(sections)
2afeaf16
JJ
524 pe_reloc_s.VirtualSize = len(data)
525 pe_reloc_s.SizeOfRawData = align_to(len(data), FILE_ALIGNMENT)
2afeaf16
JJ
526 # CNT_INITIALIZED_DATA|MEM_READ|MEM_DISCARDABLE
527 pe_reloc_s.Characteristics = 0x42000040
528
529 sections.append(pe_reloc_s)
530 opt.SizeOfInitializedData += pe_reloc_s.VirtualSize
531 return pe_reloc_s
532
533
534def write_pe(
a0797b4a
ZJS
535 file,
536 coff: PeCoffHeader,
537 opt: PeOptionalHeader,
538 sections: typing.List[PeSection],
2afeaf16
JJ
539):
540 file.write(b"MZ")
541 file.seek(0x3C, io.SEEK_SET)
542 file.write(PE_OFFSET.to_bytes(2, byteorder="little"))
543 file.seek(PE_OFFSET, io.SEEK_SET)
ee91e06a 544 file.write(PE_MAGIC)
2afeaf16
JJ
545 file.write(coff)
546 file.write(opt)
547
548 offset = opt.SizeOfHeaders
549 for pe_s in sorted(sections, key=lambda s: s.VirtualAddress):
550 if pe_s.VirtualAddress < opt.SizeOfHeaders:
2afeaf16
JJ
551 raise RuntimeError(f"Section {pe_s.Name} overlapping PE headers.")
552
553 pe_s.PointerToRawData = offset
554 file.write(pe_s)
555 offset = align_to(offset + len(pe_s.data), FILE_ALIGNMENT)
556
ee91e06a
JJ
557 assert file.tell() <= opt.SizeOfHeaders
558
2afeaf16
JJ
559 for pe_s in sections:
560 file.seek(pe_s.PointerToRawData, io.SEEK_SET)
561 file.write(pe_s.data)
562
563 file.truncate(offset)
564
565
566def elf2efi(args: argparse.Namespace):
567 elf = ELFFile(args.ELF)
568 if not elf.little_endian:
569 raise RuntimeError("ELF file is not little-endian.")
570 if elf["e_type"] not in ["ET_DYN", "ET_EXEC"]:
571 raise RuntimeError("Unsupported ELF type.")
572
573 pe_arch = {
574 "EM_386": 0x014C,
575 "EM_AARCH64": 0xAA64,
576 "EM_ARM": 0x01C2,
31ffb6b1
JJ
577 "EM_LOONGARCH": 0x6232 if elf.elfclass == 32 else 0x6264,
578 "EM_RISCV": 0x5032 if elf.elfclass == 32 else 0x5064,
2afeaf16
JJ
579 "EM_X86_64": 0x8664,
580 }.get(elf["e_machine"])
581 if pe_arch is None:
1365355d 582 raise RuntimeError(f"Unsupported ELF arch {elf['e_machine']}")
2afeaf16
JJ
583
584 coff = PeCoffHeader()
585 opt = PeOptionalHeader32() if elf.elfclass == 32 else PeOptionalHeader32Plus()
586
587 # We relocate to a unique image base to reduce the chances for runtime relocation to occur.
588 base_name = pathlib.Path(args.PE.name).name.encode()
589 opt.ImageBase = int(hashlib.sha1(base_name).hexdigest()[0:8], 16)
590 if elf.elfclass == 32:
591 opt.ImageBase = (0x400000 + opt.ImageBase) & 0xFFFF0000
592 else:
593 opt.ImageBase = (0x100000000 + opt.ImageBase) & 0x1FFFF0000
594
142f0c61 595 sections = convert_sections(elf, opt)
898e9edc 596 copy_sections(elf, opt, args.copy_sections, sections)
142f0c61 597 pe_reloc_s = convert_elf_relocations(elf, opt, sections, args.minimum_sections)
2afeaf16
JJ
598
599 coff.Machine = pe_arch
600 coff.NumberOfSections = len(sections)
601 coff.TimeDateStamp = int(os.environ.get("SOURCE_DATE_EPOCH", time.time()))
602 coff.SizeOfOptionalHeader = sizeof(opt)
603 # EXECUTABLE_IMAGE|LINE_NUMS_STRIPPED|LOCAL_SYMS_STRIPPED|DEBUG_STRIPPED
604 # and (32BIT_MACHINE or LARGE_ADDRESS_AWARE)
605 coff.Characteristics = 0x30E if elf.elfclass == 32 else 0x22E
606
2afeaf16
JJ
607 opt.SectionAlignment = SECTION_ALIGNMENT
608 opt.FileAlignment = FILE_ALIGNMENT
609 opt.MajorImageVersion = args.version_major
610 opt.MinorImageVersion = args.version_minor
611 opt.MajorSubsystemVersion = args.efi_major
612 opt.MinorSubsystemVersion = args.efi_minor
613 opt.Subsystem = args.subsystem
614 opt.Magic = 0x10B if elf.elfclass == 32 else 0x20B
7d6fd7f0
JJ
615 opt.SizeOfImage = next_section_address(sections)
616
2afeaf16
JJ
617 # DYNAMIC_BASE|NX_COMPAT|HIGH_ENTROPY_VA or DYNAMIC_BASE|NX_COMPAT
618 opt.DllCharacteristics = 0x160 if elf.elfclass == 64 else 0x140
619
620 # These values are taken from a natively built PE binary (although, unused by EDK2/EFI).
621 opt.SizeOfStackReserve = 0x100000
622 opt.SizeOfStackCommit = 0x001000
623 opt.SizeOfHeapReserve = 0x100000
624 opt.SizeOfHeapCommit = 0x001000
625
626 opt.NumberOfRvaAndSizes = N_DATA_DIRECTORY_ENTRIES
ab7d54b9
JJ
627 if pe_reloc_s:
628 opt.BaseRelocationTable = PeDataDirectory(
629 pe_reloc_s.VirtualAddress, pe_reloc_s.VirtualSize
630 )
2afeaf16
JJ
631
632 write_pe(args.PE, coff, opt, sections)
633
634
635def main():
636 parser = argparse.ArgumentParser(description="Convert ELF binaries to PE/EFI")
637 parser.add_argument(
638 "--version-major",
639 type=int,
640 default=0,
641 help="Major image version of EFI image",
642 )
643 parser.add_argument(
644 "--version-minor",
645 type=int,
646 default=0,
647 help="Minor image version of EFI image",
648 )
649 parser.add_argument(
650 "--efi-major",
651 type=int,
652 default=0,
653 help="Minimum major EFI subsystem version",
654 )
655 parser.add_argument(
656 "--efi-minor",
657 type=int,
658 default=0,
659 help="Minimum minor EFI subsystem version",
660 )
661 parser.add_argument(
662 "--subsystem",
663 type=int,
664 default=10,
665 help="PE subsystem",
666 )
667 parser.add_argument(
668 "ELF",
669 type=argparse.FileType("rb"),
670 help="Input ELF file",
671 )
672 parser.add_argument(
673 "PE",
674 type=argparse.FileType("wb"),
675 help="Output PE/EFI file",
676 )
e18e3c43
LB
677 parser.add_argument(
678 "--minimum-sections",
679 type=int,
680 default=0,
681 help="Minimum number of sections to leave space for",
682 )
898e9edc
JJ
683 parser.add_argument(
684 "--copy-sections",
685 type=str,
686 default="",
687 help="Copy these sections if found",
688 )
2afeaf16
JJ
689
690 elf2efi(parser.parse_args())
691
692
693if __name__ == "__main__":
694 main()