/* * Copyright (c) 2024, Sönke Holz * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include "PEDefinitions.h" // This tool converts the Prekernel ELF to a PE32+ EFI image. // It does so by translating the ELF sections into PE sections while keeping the virtual memory layout of the ELF file. // It also converts ELF R_*_RELATIVE relocations into PE base relocations and adds them as an additional .reloc section. static constexpr size_t PE_SECTION_ALIGNMENT = 4 * KiB; static_assert(is_power_of_two(PE_SECTION_ALIGNMENT)); // This is both the minimum and default value for OptionalHeader::WindowsSpecificFields::file_alignment. static constexpr size_t PE_FILE_ALIGNMENT = 512; static_assert(is_power_of_two(PE_FILE_ALIGNMENT)); ErrorOr translate_relocations(ELF::Image const& elf_image, Span sorted_elf_sections, Bytes raw_elf) { unsigned none_relocation_type; unsigned relative_relocation_type; switch (elf_image.machine()) { case EM_AARCH64: none_relocation_type = R_AARCH64_NONE; relative_relocation_type = R_AARCH64_RELATIVE; break; case EM_RISCV: none_relocation_type = R_RISCV_NONE; relative_relocation_type = R_RISCV_RELATIVE; break; case EM_X86_64: none_relocation_type = R_X86_64_NONE; relative_relocation_type = R_X86_64_RELATIVE; break; default: return Error::from_string_literal("Unsupported e_machine"); } HashMap> base_relocation_blocks; auto add_elf_relocation_to_pe_relocation_blocks = [&](ELF::Image::Relocation const& elf_relocation) { if (elf_relocation.type() == none_relocation_type) return; if (elf_relocation.type() != relative_relocation_type) { warnln("Unsupported relocation type: {}", elf_relocation.type()); VERIFY_NOT_REACHED(); } // Elf_Rela::r_offset is a virtual address for executables, so we need to translate it to an offset in the ELF file. FlatPtr* patch_ptr = nullptr; for (auto const* elf_section : sorted_elf_sections) { if (elf_relocation.offset() >= elf_section->address() && elf_relocation.offset() < elf_section->address() + elf_section->size()) { auto relocation_target_offset_in_section = elf_relocation.offset() - elf_section->address(); patch_ptr = reinterpret_cast(raw_elf.offset(elf_section->offset() + relocation_target_offset_in_section)); } } // PE doesn't use addends, so apply the ELF addends now. if (elf_relocation.addend_used()) *patch_ptr = elf_relocation.addend(); // The PE base relocation table is split into blocks each representing a 4K page. u32 page_rva = elf_relocation.offset() & ~0xfff; base_relocation_blocks.ensure(page_rva).append(BaseRelocationBlockEntry { .offset = static_cast(elf_relocation.offset() & 0xfff), .type = BaseRelocationBlockEntry::Type::DIR64, }); }; elf_image.for_each_section_of_type(SHT_REL, [&](ELF::Image::Section const& section) { auto relocation_section = ELF::Image::RelocationSection(section); relocation_section.for_each_relocation([&](ELF::Image::Relocation const& relocation) { add_elf_relocation_to_pe_relocation_blocks(relocation); }); }); elf_image.for_each_section_of_type(SHT_RELA, [&](ELF::Image::Section const& section) { auto relocation_section = ELF::Image::RelocationSection(section); relocation_section.for_each_relocation([&](ELF::Image::Relocation const& relocation) { add_elf_relocation_to_pe_relocation_blocks(relocation); }); }); AllocatingMemoryStream base_relocation_section_stream; for (auto& [page_rva, relocations] : base_relocation_blocks) { // Base Relocation Blocks have to be 32-bit aligned, so pad with a (16-bit) ABSOLUTE relocation if needed. // PE base relocations of type ABSOLUTE are ignored. if (relocations.size() % 2 != 0) { auto padding = BaseRelocationBlockEntry { .offset = 0, .type = BaseRelocationBlockEntry::Type::ABSOLUTE, }; relocations.append(padding); } auto header = BaseRelocationBlockHeader { .page_rva = page_rva, .block_size = static_cast(sizeof(BaseRelocationBlockHeader) + relocations.size() * sizeof(BaseRelocationBlockEntry)), }; TRY(base_relocation_section_stream.write_value(header)); for (auto const& relocation : relocations) TRY(base_relocation_section_stream.write_value(relocation)); } return base_relocation_section_stream.read_until_eof(); } ErrorOr generate_coff_header(ELF::Image const& elf_image, Span sorted_elf_sections) { COFFHeader::Machine coff_machine; switch (elf_image.machine()) { case EM_AARCH64: coff_machine = COFFHeader::Machine::ARM64; break; case EM_RISCV: coff_machine = COFFHeader::Machine::RISCV64; break; case EM_X86_64: coff_machine = COFFHeader::Machine::AMD64; break; default: return Error::from_string_literal("Unsupported e_machine"); } // +1 for the .reloc section u16 pe_section_count = sorted_elf_sections.size() + 1; // COFF File Header COFFHeader coff_header = { .machine = coff_machine, .number_of_sections = static_cast(pe_section_count), .time_date_stamp = static_cast(time(nullptr)), .pointer_to_symbol_table = 0, .number_of_symbols = 0, .size_of_optional_header = sizeof(OptionalHeader), .characteristics = COFFHeader::Characteristics::EXECUTABLE_IMAGE | COFFHeader::Characteristics::LINE_NUMS_STRIPPED | COFFHeader::Characteristics::DEBUG_STRIPPED, }; return coff_header; } ErrorOr generate_optional_header(ELF::Image const& elf_image, Span sorted_elf_sections, ReadonlyBytes base_relocation_section_data, u32 size_of_headers) { Optional base_of_code; u32 size_of_code = 0; u32 size_of_initialized_data = 0; u32 size_of_uninitialized_data = 0; // Place the .reloc section after the last ELF section. OptionalHeader::DataDirectory reloc_section_data_directory { .virtual_address = static_cast(round_up_to_power_of_two(sorted_elf_sections.last()->address() + sorted_elf_sections.last()->size(), PE_SECTION_ALIGNMENT)), .size = static_cast(base_relocation_section_data.size()), }; for (auto const* elf_section : sorted_elf_sections) { if (elf_section->name() == ".reloc"sv) return Error::from_string_literal("The Prekernel ELF shouldn't have a .reloc section, as this program will generate it"); if (elf_section->name().starts_with(".text"sv)) { if (!base_of_code.has_value()) base_of_code = elf_section->address(); size_of_code += round_up_to_power_of_two(elf_section->size(), PE_FILE_ALIGNMENT); } else if (elf_section->name().starts_with(".rdata"sv) || elf_section->name().starts_with(".data"sv)) { size_of_initialized_data += round_up_to_power_of_two(elf_section->size(), PE_FILE_ALIGNMENT); } else if (elf_section->name().starts_with(".bss"sv)) { size_of_uninitialized_data += round_up_to_power_of_two(elf_section->size(), PE_FILE_ALIGNMENT); } } // ImageBase has to be a multiple of 64K. Optional image_base; elf_image.for_each_symbol([&image_base](ELF::Image::Symbol const& symbol) { if (symbol.name() == "pe_image_base"sv) { image_base = symbol.value(); return IterationDecision::Break; } return IterationDecision::Continue; }); // We require the PE image base to be zero, so we don't have to subtract it from every address to get the relative virtual addresses. VERIFY(image_base.value() == 0); // Optional Header OptionalHeader optional_header = { .standard_fields = { .magic = OptionalHeader::StandardFields::Magic::PE32Plus, .major_linker_version = 0, .minor_linker_version = 0, .size_of_code = size_of_code, .size_of_initialized_data = size_of_initialized_data, .size_of_uninitialized_data = size_of_uninitialized_data, .address_of_entry_point = static_cast(elf_image.entry().get()), .base_of_code = base_of_code.release_value(), }, .windows_specific_fields = { .image_base = image_base.value(), .section_alignment = PE_SECTION_ALIGNMENT, .file_alignment = PE_FILE_ALIGNMENT, .major_operating_system_version = 0, .minor_operating_system_version = 0, .major_image_version = 0, .minor_image_version = 0, .major_subsystem_version = 0, .minor_subsystem_version = 0, .win32_version_value = 0, .size_of_image = reloc_section_data_directory.virtual_address + reloc_section_data_directory.size, // The .reloc section is the last section of the PE image. .size_of_headers = size_of_headers, .checksum = 0, // The checksum algorithm is not publicly defined. We probably don't need to set it as edk2 PEs built with gcc don't have the checksum set either. .subsystem = OptionalHeader::WindowsSpecificFields::Subsystem::EFI_APPLICATION, .dll_characteristics = 0, .size_of_stack_reserve = 0, // edk2 PEs built with gcc don't have these sizes set, so we probably don't need to set them either. .size_of_stack_commit = 0, .size_of_heap_reserve = 0, .size_of_heap_commit = 0, .loader_flags = 0, .number_of_rva_and_size = sizeof(OptionalHeader::DataDirectories) / sizeof(u64), }, .data_directories = { .export_table = {}, .import_table = {}, .resource_table = {}, .exception_table = {}, .certificate_table = {}, .base_relocation_table = reloc_section_data_directory, .debug = {}, .architecture = {}, .global_ptr = {}, .tls_table = {}, .load_config_table = {}, .bound_import = {}, .iat = {}, .delay_import_descriptor = {}, .clr_runtime_header = {}, .reserved = {}, }, }; return optional_header; } ErrorOr write_pe_headers(Core::OutputBufferedFile& stream, COFFHeader const& coff_header, OptionalHeader const& optional_header) { // MS-DOS Stub TRY(stream.write_until_depleted(DOS_MAGIC)); // Offset to PE signature TRY(stream.seek(PE_MAGIC_OFFSET_OFFSET, SeekMode::SetPosition)); TRY(stream.write_value(LittleEndian { PE_MAGIC_OFFSET_OFFSET + sizeof(u32) })); // Signature TRY(stream.write_until_depleted(PE_MAGIC)); TRY(stream.write_value(coff_header)); TRY(stream.write_value(optional_header)); return {}; } ErrorOr write_pe_sections(Core::OutputBufferedFile& stream, Span sorted_elf_sections, ReadonlyBytes base_relocation_section_data, COFFHeader const& coff_header, OptionalHeader optional_header) { u32 offset_for_first_raw_data = round_up_to_power_of_two(TRY(stream.tell()) + sizeof(SectionHeader) * coff_header.number_of_sections, PE_FILE_ALIGNMENT); Vector raw_data_offsets; raw_data_offsets.ensure_capacity(sorted_elf_sections.size()); u32 current_offset_to_raw_data = offset_for_first_raw_data; for (auto const* elf_section : sorted_elf_sections) { u32 file_size = elf_section->type() == SHT_NOBITS ? 0 : round_up_to_power_of_two(elf_section->size(), PE_FILE_ALIGNMENT); auto characteristics = SectionHeader::Characteristics::NONE; for (auto const& [special_section_name, special_characteristics] : SPECIAL_PE_SECTIONS) { if (special_section_name == elf_section->name()) { characteristics = special_characteristics; break; } } if (characteristics == SectionHeader::Characteristics::NONE) { // Fallback for non-special PE sections characteristics |= SectionHeader::Characteristics::MEM_READ; if (elf_section->is_writable()) characteristics |= SectionHeader::Characteristics::MEM_WRITE; if (elf_section->is_executable()) characteristics |= SectionHeader::Characteristics::MEM_EXECUTE | SectionHeader::Characteristics::CNT_CODE; characteristics |= file_size == 0 ? SectionHeader::Characteristics::CNT_UNINITIALIZED_DATA : SectionHeader::Characteristics::CNT_INITIALIZED_DATA; } SectionHeader section_header = { .name = {}, .virtual_size = static_cast(elf_section->size()), .virtual_address = static_cast(elf_section->address()), .size_of_raw_data = file_size, .pointer_to_raw_data = file_size == 0 ? 0 : current_offset_to_raw_data, .pointer_to_relocations = 0, .pointer_to_line_numbers = 0, .number_of_relocations = 0, .number_of_line_numbers = 0, .characteristics = characteristics, }; memset(§ion_header.name, 0, sizeof(section_header.name)); memcpy(§ion_header.name, elf_section->name().bytes().data(), min(elf_section->name().bytes().size(), 8)); TRY(stream.write_value(section_header)); raw_data_offsets.append(current_offset_to_raw_data); current_offset_to_raw_data = round_up_to_power_of_two(current_offset_to_raw_data + file_size, PE_FILE_ALIGNMENT); } // Also add the section header for the .reloc section. SectionHeader reloc_section_header = { .name = { ".reloc" }, .virtual_size = static_cast(base_relocation_section_data.size()), .virtual_address = optional_header.data_directories.base_relocation_table.virtual_address, .size_of_raw_data = static_cast(base_relocation_section_data.size()), .pointer_to_raw_data = current_offset_to_raw_data, .pointer_to_relocations = 0, .pointer_to_line_numbers = 0, .number_of_relocations = 0, .number_of_line_numbers = 0, .characteristics = SectionHeader::Characteristics::CNT_INITIALIZED_DATA | SectionHeader::Characteristics::MEM_READ | SectionHeader::Characteristics::MEM_DISCARDABLE, }; TRY(stream.write_value(reloc_section_header)); for (size_t section_index = 0; section_index < sorted_elf_sections.size(); section_index++) { u32 offset_to_raw_data = raw_data_offsets[section_index]; auto const* elf_section = sorted_elf_sections[section_index]; TRY(stream.seek(offset_to_raw_data, SeekMode::SetPosition)); TRY(stream.write_until_depleted(elf_section->bytes())); } TRY(stream.seek(reloc_section_header.pointer_to_raw_data, SeekMode::SetPosition)); TRY(stream.write_until_depleted(base_relocation_section_data)); return {}; } ErrorOr serenity_main(Main::Arguments arguments) { Core::ArgsParser argument_parser; StringView elf_file_name; StringView pe_file_name; argument_parser.add_positional_argument(elf_file_name, "Prekernel ELF file", "elf-file", Core::ArgsParser::Required::Yes); argument_parser.add_positional_argument(pe_file_name, "Target PE32+ image file", "pe-file", Core::ArgsParser::Required::Yes); argument_parser.parse(arguments); auto elf_file = TRY(Core::File::open(elf_file_name, Core::File::OpenMode::Read)); auto elf_data = TRY(elf_file->read_until_eof()); auto const elf_image = ELF::Image(elf_data.bytes()); if (!elf_image.is_valid()) { return Error::from_string_literal("Invalid ELF passed"); return -1; } VERIFY(elf_image.is_executable() || elf_image.is_dynamic()); if (elf_image.elf_class() != ELFCLASS64) return Error::from_string_literal("Unsupported EI_CLASS"); if (elf_image.byte_order() != ELFDATA2LSB) return Error::from_string_literal("Unsupported EI_DATA"); Vector elf_sections; elf_sections.ensure_capacity(elf_image.section_count()); elf_image.for_each_section([&elf_sections, &elf_image](ELF::Image::Section const& elf_section) { // We don't support converting RELR relocations. VERIFY(elf_section.type() != SHT_RELR); // We don't have a runtime to support .{preinit,init,fini}_array sections. VERIFY(elf_section.type() != SHT_INIT_ARRAY); VERIFY(elf_section.type() != SHT_PREINIT_ARRAY); VERIFY(elf_section.type() != SHT_FINI_ARRAY); // Don't include some unnecessary sections in the PE image. static constexpr Array section_types_to_discard = to_array({ SHT_SYMTAB, SHT_STRTAB, SHT_RELA, SHT_HASH, SHT_DYNAMIC, SHT_NOTE, SHT_REL, SHT_DYNSYM, SHT_GNU_HASH, }); if (elf_image.machine() == EM_RISCV && elf_section.type() == SHT_RISCV_ATTRIBUTES) return; if (section_types_to_discard.contains_slow(elf_section.type())) return; // Don't add sections with address 0 or without the ALLOC flag, as they won't appear in memory. if (elf_section.address() == 0 || (elf_section.flags() & SHF_ALLOC) == 0) return; // We keep the memory layout of the ELF sections when translating them to PE sections. // The ELF sections therefore have to be properly aligned, as PE sections have to be aligned by the specified amount in WindowsSpecificFields::section_alignment. if (elf_section.address() % PE_SECTION_ALIGNMENT != 0) { warnln("Prekernel ELF section \"{}\" is not aligned on a {}-byte boundary!", elf_section.name(), PE_SECTION_ALIGNMENT); warnln("Either add it to the Prekernel linker script or discard it in the PrekernelPEImageGenerator."); VERIFY_NOT_REACHED(); } elf_sections.append(elf_section); }); // PE sections have to be sorted by their virtual_address. Vector sorted_elf_sections; sorted_elf_sections.ensure_capacity(elf_sections.size()); for (auto const& section : elf_sections) sorted_elf_sections.append(§ion); quick_sort(sorted_elf_sections, [](auto const* a, auto const* b) { return a->address() < b->address(); }); auto output_file = TRY(Core::File::open(pe_file_name, Core::File::OpenMode::Write)); auto output = TRY(Core::OutputBufferedFile::create(move(output_file))); auto base_relocation_section_data = TRY(translate_relocations(elf_image, sorted_elf_sections, elf_data.bytes())); auto coff_header = TRY(generate_coff_header(elf_image, sorted_elf_sections)); u32 size_of_headers = round_up_to_power_of_two(PE_MAGIC_OFFSET_OFFSET + sizeof(u32) + sizeof(PE_MAGIC) + sizeof(COFFHeader) + sizeof(OptionalHeader) + sizeof(SectionHeader) * coff_header.number_of_sections, PE_FILE_ALIGNMENT); auto optional_header = TRY(generate_optional_header(elf_image, sorted_elf_sections, base_relocation_section_data, size_of_headers)); TRY(write_pe_headers(*output, coff_header, optional_header)); TRY(write_pe_sections(*output, sorted_elf_sections, base_relocation_section_data, coff_header, optional_header)); return 0; }