mirror of
https://github.com/systemd/systemd.git
synced 2025-03-10 16:58:28 +03:00
Merge pull request #29363 from medhefgo/elf2efi
elf2efi: Rework to allow using any linker
This commit is contained in:
commit
69f99d1e4e
23
.github/workflows/build_test.sh
vendored
23
.github/workflows/build_test.sh
vendored
@ -45,7 +45,7 @@ PACKAGES=(
|
||||
libxkbcommon-dev
|
||||
libxtables-dev
|
||||
libzstd-dev
|
||||
mold
|
||||
# mold
|
||||
mount
|
||||
net-tools
|
||||
python3-evdev
|
||||
@ -68,6 +68,14 @@ LINKER="${LINKER:?}"
|
||||
CRYPTOLIB="${CRYPTOLIB:?}"
|
||||
RELEASE="$(lsb_release -cs)"
|
||||
|
||||
# mold-2.2.0+ fixes some bugs breaking bootloader builds.
|
||||
# TODO: Switch to distro mold with ubuntu-24.04
|
||||
if [[ "$LINKER" == mold ]]; then
|
||||
wget https://github.com/rui314/mold/releases/download/v2.2.0/mold-2.2.0-x86_64-linux.tar.gz
|
||||
echo "d66e0230c562c2ba0e0b789cc5034e0fa2369cc843d0154920de4269cd94afeb mold-2.2.0-x86_64-linux.tar.gz" | sha256sum -c
|
||||
sudo tar -xz -C /usr --strip-components=1 -f mold-2.2.0-x86_64-linux.tar.gz
|
||||
fi
|
||||
|
||||
# Note: As we use postfixed clang/gcc binaries, we need to override $AR
|
||||
# as well, otherwise meson falls back to ar from binutils which
|
||||
# doesn't work with LTO
|
||||
@ -123,20 +131,11 @@ ninja --version
|
||||
for args in "${ARGS[@]}"; do
|
||||
SECONDS=0
|
||||
|
||||
# mold < 1.1 does not support LTO.
|
||||
if dpkg --compare-versions "$(dpkg-query --showformat='${Version}' --show mold)" ge 1.1; then
|
||||
fatal "Newer mold version detected, please remove this workaround."
|
||||
elif [[ "$args" == *"-Db_lto=true"* ]]; then
|
||||
LD="gold"
|
||||
else
|
||||
LD="$LINKER"
|
||||
fi
|
||||
|
||||
info "Checking build with $args"
|
||||
# shellcheck disable=SC2086
|
||||
if ! AR="$AR" \
|
||||
CC="$CC" CC_LD="$LD" CFLAGS="-Werror" \
|
||||
CXX="$CXX" CXX_LD="$LD" CXXFLAGS="-Werror" \
|
||||
CC="$CC" CC_LD="$LINKER" CFLAGS="-Werror" \
|
||||
CXX="$CXX" CXX_LD="$LINKER" CXXFLAGS="-Werror" \
|
||||
meson setup \
|
||||
-Dtests=unsafe -Dslow-tests=true -Dfuzz-tests=true --werror \
|
||||
-Dnobody-group=nogroup -Dcryptolib="${CRYPTOLIB:?}" -Ddebug=false \
|
||||
|
8
.github/workflows/build_test.yml
vendored
8
.github/workflows/build_test.yml
vendored
@ -26,10 +26,10 @@ jobs:
|
||||
matrix:
|
||||
env:
|
||||
- { COMPILER: "gcc", COMPILER_VERSION: "11", LINKER: "bfd", CRYPTOLIB: "gcrypt" }
|
||||
- { COMPILER: "gcc", COMPILER_VERSION: "13", LINKER: "gold", CRYPTOLIB: "openssl" }
|
||||
- { COMPILER: "clang", COMPILER_VERSION: "14", LINKER: "mold", CRYPTOLIB: "gcrypt" }
|
||||
- { COMPILER: "clang", COMPILER_VERSION: "15", LINKER: "lld", CRYPTOLIB: "openssl" }
|
||||
- { COMPILER: "clang", COMPILER_VERSION: "16", LINKER: "bfd", CRYPTOLIB: "auto" }
|
||||
- { COMPILER: "gcc", COMPILER_VERSION: "13", LINKER: "mold", CRYPTOLIB: "openssl" }
|
||||
- { COMPILER: "clang", COMPILER_VERSION: "14", LINKER: "bfd", CRYPTOLIB: "gcrypt" }
|
||||
- { COMPILER: "clang", COMPILER_VERSION: "15", LINKER: "mold", CRYPTOLIB: "openssl" }
|
||||
- { COMPILER: "clang", COMPILER_VERSION: "17", LINKER: "lld", CRYPTOLIB: "auto" }
|
||||
env: ${{ matrix.env }}
|
||||
steps:
|
||||
- name: Repository checkout
|
||||
|
@ -1788,7 +1788,6 @@ conf.set10('ENABLE_UKIFY', want_ukify)
|
||||
|
||||
############################################################
|
||||
|
||||
elf2efi_lds = project_source_root / 'tools/elf2efi.lds'
|
||||
elf2efi_py = find_program('tools/elf2efi.py')
|
||||
export_dbus_interfaces_py = find_program('tools/dbus_exporter.py')
|
||||
generate_gperfs = find_program('tools/generate-gperfs.py')
|
||||
|
@ -4,12 +4,11 @@
|
||||
#include "version.h"
|
||||
|
||||
/* Magic string for recognizing our own binaries */
|
||||
_used_ _section_(".sdmagic") static const char magic[] =
|
||||
"#### LoaderInfo: systemd-addon " GIT_VERSION " ####";
|
||||
DECLARE_NOALLOC_SECTION(".sdmagic", "#### LoaderInfo: systemd-addon " GIT_VERSION " ####");
|
||||
|
||||
/* This is intended to carry data, not to be executed */
|
||||
|
||||
EFIAPI EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *system_table);
|
||||
EFIAPI EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *system_table) {
|
||||
return EFI_UNSUPPORTED;
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
|
@ -26,14 +26,15 @@
|
||||
#include "vmm.h"
|
||||
|
||||
/* Magic string for recognizing our own binaries */
|
||||
_used_ _section_(".sdmagic") static const char magic[] =
|
||||
"#### LoaderInfo: systemd-boot " GIT_VERSION " ####";
|
||||
#define SD_MAGIC "#### LoaderInfo: systemd-boot " GIT_VERSION " ####"
|
||||
DECLARE_NOALLOC_SECTION(".sdmagic", SD_MAGIC);
|
||||
|
||||
/* Makes systemd-boot available from \EFI\Linux\ for testing purposes. */
|
||||
_used_ _section_(".osrel") static const char osrel[] =
|
||||
"ID=systemd-boot\n"
|
||||
"VERSION=\"" GIT_VERSION "\"\n"
|
||||
"NAME=\"systemd-boot " GIT_VERSION "\"\n";
|
||||
DECLARE_NOALLOC_SECTION(
|
||||
".osrel",
|
||||
"ID=systemd-boot\n"
|
||||
"VERSION=\"" GIT_VERSION "\"\n"
|
||||
"NAME=\"systemd-boot " GIT_VERSION "\"\n");
|
||||
|
||||
DECLARE_SBAT(SBAT_BOOT_SECTION_TEXT);
|
||||
|
||||
@ -1890,14 +1891,14 @@ static bool is_sd_boot(EFI_FILE *root_dir, const char16_t *loader_path) {
|
||||
assert(loader_path);
|
||||
|
||||
err = pe_file_locate_sections(root_dir, loader_path, sections, &offset, &size);
|
||||
if (err != EFI_SUCCESS || size != sizeof(magic))
|
||||
if (err != EFI_SUCCESS || size != sizeof(SD_MAGIC))
|
||||
return false;
|
||||
|
||||
err = file_read(root_dir, loader_path, offset, size, &content, &read);
|
||||
if (err != EFI_SUCCESS || size != read)
|
||||
return false;
|
||||
|
||||
return memcmp(content, magic, sizeof(magic)) == 0;
|
||||
return memcmp(content, SD_MAGIC, sizeof(SD_MAGIC)) == 0;
|
||||
}
|
||||
|
||||
static ConfigEntry *config_entry_add_loader_auto(
|
||||
|
@ -81,7 +81,7 @@ void __stack_chk_guard_init(void) {
|
||||
(void) rng->GetRNG(rng, NULL, sizeof(__stack_chk_guard), (void *) &__stack_chk_guard);
|
||||
else
|
||||
/* Better than no extra entropy. */
|
||||
__stack_chk_guard ^= (intptr_t) &__ImageBase;
|
||||
__stack_chk_guard ^= (intptr_t) __executable_start;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -147,10 +147,6 @@ if get_option('mode') == 'developer' and get_option('debug')
|
||||
endif
|
||||
|
||||
efi_c_ld_args = [
|
||||
# We only support bfd. gold is going away, lld has issues with LTO on x86
|
||||
# and mold does not support linker scripts.
|
||||
'-fuse-ld=bfd',
|
||||
|
||||
'-lgcc',
|
||||
'-nostdlib',
|
||||
'-static-pie',
|
||||
@ -166,13 +162,14 @@ efi_c_ld_args = [
|
||||
'-z', 'max-page-size=4096',
|
||||
|
||||
'-z', 'noexecstack',
|
||||
'-z', 'norelro',
|
||||
'-T' + elf2efi_lds,
|
||||
'-z', 'relro',
|
||||
'-z', 'separate-code',
|
||||
]
|
||||
|
||||
# On CentOS 8 the nopack-relative-relocs linker flag is not supported, and we get:
|
||||
# /usr/bin/ld.bfd: warning: -z nopack-relative-relocs ignored
|
||||
efi_c_ld_args += cc.get_supported_link_arguments('-Wl,-z,nopack-relative-relocs')
|
||||
efi_c_ld_args += cc.get_supported_link_arguments(
|
||||
# binutils >= 2.38
|
||||
'-Wl,-z,nopack-relative-relocs',
|
||||
)
|
||||
|
||||
# efi_c_args is explicitly passed to targets so that they can override distro-provided flags
|
||||
# that should not be used for EFI binaries.
|
||||
@ -213,11 +210,35 @@ efi_arch_c_args = {
|
||||
efi_arch_c_ld_args = {
|
||||
# libgcc is not compiled with -fshort-wchar, but it does not use it anyways,
|
||||
# so it's fine to link against it.
|
||||
'arm' : ['-Wl,--no-wchar-size-warning'],
|
||||
'arm' : cc.get_supported_link_arguments('-Wl,--no-wchar-size-warning'),
|
||||
'x86_64' : ['-m64'],
|
||||
'x86' : ['-m32'],
|
||||
}
|
||||
|
||||
linker_sanity_code = 'void a(void) {}; void _start(void) { a(); }'
|
||||
linker_sanity_args = ['-nostdlib', '-Wl,--fatal-warnings']
|
||||
if not cc.links(linker_sanity_code,
|
||||
name : 'linker supports -static-pie',
|
||||
args : [linker_sanity_args, '-static-pie'])
|
||||
error('Linker does not support -static-pie.')
|
||||
endif
|
||||
|
||||
# https://github.com/llvm/llvm-project/issues/67152
|
||||
if not cc.links(linker_sanity_code,
|
||||
name : 'linker supports LTO with -nostdlib',
|
||||
args : [linker_sanity_args, '-flto'])
|
||||
efi_c_args += '-fno-lto'
|
||||
efi_c_ld_args += '-fno-lto'
|
||||
endif
|
||||
|
||||
# https://github.com/llvm/llvm-project/issues/61101
|
||||
if efi_cpu_family_alt == 'x86' and not cc.links(linker_sanity_code,
|
||||
name : 'linker supports LTO with -nostdlib (x86)',
|
||||
args : [linker_sanity_args, '-flto', '-m32'])
|
||||
efi_arch_c_args += { 'x86' : efi_arch_c_args['x86'] + '-fno-lto' }
|
||||
efi_arch_c_ld_args += { 'x86' : efi_arch_c_ld_args['x86'] + '-fno-lto' }
|
||||
endif
|
||||
|
||||
############################################################
|
||||
|
||||
libefi_sources = files(
|
||||
@ -315,8 +336,6 @@ foreach archspec : efi_archspecs
|
||||
'include_directories' : efi_includes,
|
||||
'c_args' : archspec['c_args'],
|
||||
'link_args' : archspec['link_args'],
|
||||
'link_with' : libefi,
|
||||
'link_depends' : elf2efi_lds,
|
||||
'gnu_symbol_visibility' : 'hidden',
|
||||
'override_options' : efi_override_options,
|
||||
'pie' : true,
|
||||
@ -325,12 +344,14 @@ foreach archspec : efi_archspecs
|
||||
efi_elf_binaries += executable(
|
||||
'systemd-boot' + archspec['arch'],
|
||||
sources : [systemd_boot_sources, version_h],
|
||||
link_with : libefi,
|
||||
name_suffix : 'elf',
|
||||
kwargs : kwargs)
|
||||
|
||||
efi_elf_binaries += executable(
|
||||
'linux' + archspec['arch'],
|
||||
sources : [stub_sources, version_h],
|
||||
link_with : libefi,
|
||||
name_suffix : 'elf.stub',
|
||||
kwargs : kwargs)
|
||||
|
||||
@ -362,6 +383,7 @@ foreach efi_elf_binary : efi_elf_binaries
|
||||
'--efi-minor=1',
|
||||
'--subsystem=10',
|
||||
'--minimum-sections=' + minimum_sections,
|
||||
'--copy-sections=.sbat,.sdmagic,.osrel',
|
||||
'@INPUT@',
|
||||
'@OUTPUT@',
|
||||
])
|
||||
|
@ -21,7 +21,7 @@
|
||||
#include "vmm.h"
|
||||
|
||||
/* magic string to find in the binary image */
|
||||
_used_ _section_(".sdmagic") static const char magic[] = "#### LoaderInfo: systemd-stub " GIT_VERSION " ####";
|
||||
DECLARE_NOALLOC_SECTION(".sdmagic", "#### LoaderInfo: systemd-stub " GIT_VERSION " ####");
|
||||
|
||||
DECLARE_SBAT(SBAT_STUB_SECTION_TEXT);
|
||||
|
||||
|
@ -555,7 +555,7 @@ uint64_t get_os_indications_supported(void) {
|
||||
|
||||
__attribute__((noinline)) void notify_debugger(const char *identity, volatile bool wait) {
|
||||
#ifdef EFI_DEBUG
|
||||
printf("%s@%p %s\n", identity, &__ImageBase, GIT_VERSION);
|
||||
printf("%s@%p %s\n", identity, __executable_start, GIT_VERSION);
|
||||
if (wait)
|
||||
printf("Waiting for debugger to attach...\n");
|
||||
|
||||
|
@ -6,8 +6,8 @@
|
||||
#include "proto/file-io.h"
|
||||
#include "string-util-fundamental.h"
|
||||
|
||||
/* This is provided by linker script. */
|
||||
extern uint8_t __ImageBase;
|
||||
/* This is provided by the linker. */
|
||||
extern uint8_t __executable_start[];
|
||||
|
||||
static inline void free(void *p) {
|
||||
if (!p)
|
||||
|
@ -396,9 +396,15 @@ static inline size_t ALIGN_TO(size_t l, size_t ali) {
|
||||
type name[]; \
|
||||
}
|
||||
|
||||
/* Declares an ELF read-only string section that does not occupy memory at runtime. */
|
||||
#define DECLARE_NOALLOC_SECTION(name, text) \
|
||||
asm(".pushsection " name ",\"S\"\n\t" \
|
||||
".ascii " STRINGIFY(text) "\n\t" \
|
||||
".zero 1\n\t" \
|
||||
".popsection\n")
|
||||
|
||||
#ifdef SBAT_DISTRO
|
||||
#define DECLARE_SBAT(text) \
|
||||
static const char sbat[] _used_ _section_(".sbat") = (text)
|
||||
#define DECLARE_SBAT(text) DECLARE_NOALLOC_SECTION(".sbat", text)
|
||||
#else
|
||||
#define DECLARE_SBAT(text)
|
||||
#endif
|
||||
|
@ -1,58 +0,0 @@
|
||||
SECTIONS {
|
||||
__ImageBase = .;
|
||||
|
||||
/* We skip the first page because the space will be occupied by the PE headers after conversion. */
|
||||
. = CONSTANT(MAXPAGESIZE);
|
||||
.text ALIGN(CONSTANT(MAXPAGESIZE)) : {
|
||||
*(.text .text.*)
|
||||
}
|
||||
|
||||
/* When linking a minimal addon stub, the linker can merge .text and .dynsym, creating a RWE
|
||||
* segment, and then rejects it. Ensure there's a gap so that we end up with two separate segments.
|
||||
* The alignments for the next sections are only applied if the section exists, so they are not
|
||||
* enough, and we need to have this unconditional one. */
|
||||
. = ALIGN(CONSTANT(MAXPAGESIZE));
|
||||
|
||||
.rodata ALIGN(CONSTANT(MAXPAGESIZE)) : {
|
||||
*(.rodata .rodata.*)
|
||||
*(.srodata .srodata.*)
|
||||
}
|
||||
.data ALIGN(CONSTANT(MAXPAGESIZE)) : {
|
||||
*(.data .data.*)
|
||||
*(.sdata .sdata.*)
|
||||
*(.got .got.*)
|
||||
*(.got.plt .got.plt.*)
|
||||
|
||||
/* EDK2 says some firmware cannot handle BSS sections properly. */
|
||||
*(.bss .bss.*)
|
||||
*(.sbss .sbss.*)
|
||||
*(COMMON)
|
||||
}
|
||||
|
||||
.sdmagic ALIGN(CONSTANT(MAXPAGESIZE)) : { *(.sdmagic) }
|
||||
.osrel ALIGN(CONSTANT(MAXPAGESIZE)) : { *(.osrel) }
|
||||
.sbat ALIGN(CONSTANT(MAXPAGESIZE)) : { *(.sbat) }
|
||||
|
||||
/* These are used for PE conversion and then discarded. */
|
||||
.dynsym : { *(.dynsym) }
|
||||
.dynstr : { *(.dynstr) }
|
||||
.dynamic : { *(.dynamic) }
|
||||
.rel.dyn : { *(.rel.dyn) }
|
||||
.rela.dyn : { *(.rela.dyn) }
|
||||
|
||||
/* These aren't needed and could be discarded. Just in case that they're useful to the debugger
|
||||
* we keep them, but move them out of the way to keep the PE binary more compact. */
|
||||
.ARM.exidx : { *(.ARM.exidx) }
|
||||
.eh_frame : { *(.eh_frame) }
|
||||
.eh_frame_hdr : { *(.eh_frame_hdr) }
|
||||
.gnu.hash : { *(.gnu.hash) }
|
||||
.hash : { *(.hash) }
|
||||
.note.gnu.build-id : { *(.note.gnu.build-id ) }
|
||||
|
||||
/DISCARD/ : {
|
||||
*(.ARM.attributes)
|
||||
*(.comment)
|
||||
*(.note.*)
|
||||
*(.riscv.attributes)
|
||||
}
|
||||
}
|
274
tools/elf2efi.py
274
tools/elf2efi.py
@ -39,7 +39,7 @@ from ctypes import (
|
||||
)
|
||||
|
||||
from elftools.elf.constants import SH_FLAGS
|
||||
from elftools.elf.elffile import ELFFile, Section as ELFSection
|
||||
from elftools.elf.elffile import ELFFile
|
||||
from elftools.elf.enums import (
|
||||
ENUM_DT_FLAGS_1,
|
||||
ENUM_RELOC_TYPE_AARCH64,
|
||||
@ -204,6 +204,30 @@ assert sizeof(PeCoffHeader) == 20
|
||||
assert sizeof(PeOptionalHeader32) == 224
|
||||
assert sizeof(PeOptionalHeader32Plus) == 240
|
||||
|
||||
PE_CHARACTERISTICS_RX = 0x60000020 # CNT_CODE|MEM_READ|MEM_EXECUTE
|
||||
PE_CHARACTERISTICS_RW = 0xC0000040 # CNT_INITIALIZED_DATA|MEM_READ|MEM_WRITE
|
||||
PE_CHARACTERISTICS_R = 0x40000040 # CNT_INITIALIZED_DATA|MEM_READ
|
||||
|
||||
IGNORE_SECTIONS = [
|
||||
".eh_frame",
|
||||
".eh_frame_hdr",
|
||||
".ARM.exidx",
|
||||
]
|
||||
|
||||
IGNORE_SECTION_TYPES = [
|
||||
"SHT_DYNAMIC",
|
||||
"SHT_DYNSYM",
|
||||
"SHT_GNU_ATTRIBUTES",
|
||||
"SHT_GNU_HASH",
|
||||
"SHT_HASH",
|
||||
"SHT_NOTE",
|
||||
"SHT_REL",
|
||||
"SHT_RELA",
|
||||
"SHT_RELR",
|
||||
"SHT_STRTAB",
|
||||
"SHT_SYMTAB",
|
||||
]
|
||||
|
||||
# EFI mandates 4KiB memory pages.
|
||||
SECTION_ALIGNMENT = 4096
|
||||
FILE_ALIGNMENT = 512
|
||||
@ -217,79 +241,95 @@ def align_to(x: int, align: int) -> int:
|
||||
return (x + align - 1) & ~(align - 1)
|
||||
|
||||
|
||||
def use_section(elf_s: ELFSection) -> bool:
|
||||
# These sections are either needed during conversion to PE or are otherwise not needed
|
||||
# in the final PE image.
|
||||
IGNORE_SECTIONS = [
|
||||
".ARM.exidx",
|
||||
".dynamic",
|
||||
".dynstr",
|
||||
".dynsym",
|
||||
".eh_frame_hdr",
|
||||
".eh_frame",
|
||||
".gnu.hash",
|
||||
".hash",
|
||||
".note.gnu.build-id",
|
||||
".rel.dyn",
|
||||
".rela.dyn",
|
||||
]
|
||||
|
||||
# Known sections we care about and want to be in the final PE.
|
||||
COPY_SECTIONS = [
|
||||
".data",
|
||||
".osrel",
|
||||
".rodata",
|
||||
".sbat",
|
||||
".sdmagic",
|
||||
".text",
|
||||
]
|
||||
|
||||
# By only dealing with allocating sections we effectively filter out debug sections.
|
||||
if not elf_s["sh_flags"] & SH_FLAGS.SHF_ALLOC:
|
||||
return False
|
||||
|
||||
if elf_s.name in IGNORE_SECTIONS:
|
||||
return False
|
||||
|
||||
# For paranoia we only handle sections we know of. Any new sections that come up should
|
||||
# be added to IGNORE_SECTIONS/COPY_SECTIONS and/or the linker script.
|
||||
if elf_s.name not in COPY_SECTIONS:
|
||||
raise RuntimeError(f"Unknown section {elf_s.name}, refusing.")
|
||||
|
||||
if elf_s["sh_addr"] % SECTION_ALIGNMENT != 0:
|
||||
raise RuntimeError(f"Section {elf_s.name} is not aligned.")
|
||||
if len(elf_s.name) > 8:
|
||||
raise RuntimeError(f"ELF section name {elf_s.name} too long.")
|
||||
|
||||
return True
|
||||
def align_down(x: int, align: int) -> int:
|
||||
return x & ~(align - 1)
|
||||
|
||||
|
||||
def convert_elf_section(elf_s: ELFSection) -> PeSection:
|
||||
pe_s = PeSection()
|
||||
pe_s.Name = elf_s.name.encode()
|
||||
pe_s.VirtualSize = elf_s.data_size
|
||||
pe_s.VirtualAddress = elf_s["sh_addr"]
|
||||
pe_s.SizeOfRawData = align_to(elf_s.data_size, FILE_ALIGNMENT)
|
||||
pe_s.data = bytearray(elf_s.data())
|
||||
|
||||
if elf_s["sh_flags"] & SH_FLAGS.SHF_EXECINSTR:
|
||||
pe_s.Characteristics = 0x60000020 # CNT_CODE|MEM_READ|MEM_EXECUTE
|
||||
elif elf_s["sh_flags"] & SH_FLAGS.SHF_WRITE:
|
||||
pe_s.Characteristics = 0xC0000040 # CNT_INITIALIZED_DATA|MEM_READ|MEM_WRITE
|
||||
else:
|
||||
pe_s.Characteristics = 0x40000040 # CNT_INITIALIZED_DATA|MEM_READ
|
||||
|
||||
return pe_s
|
||||
def next_section_address(sections: typing.List[PeSection]) -> int:
|
||||
return align_to(
|
||||
sections[-1].VirtualAddress + sections[-1].VirtualSize, SECTION_ALIGNMENT
|
||||
)
|
||||
|
||||
|
||||
def copy_sections(elf: ELFFile, opt: PeOptionalHeader) -> typing.List[PeSection]:
|
||||
sections = []
|
||||
def iter_copy_sections(elf: ELFFile) -> typing.Iterator[PeSection]:
|
||||
pe_s = None
|
||||
|
||||
# This is essentially the same as copying by ELF load segments, except that we assemble them
|
||||
# manually, so that we can easily strip unwanted sections. We try to only discard things we know
|
||||
# about so that there are no surprises.
|
||||
|
||||
relro = None
|
||||
for elf_seg in elf.iter_segments():
|
||||
if elf_seg["p_type"] == "PT_LOAD" and elf_seg["p_align"] != SECTION_ALIGNMENT:
|
||||
raise RuntimeError("ELF segments are not properly aligned.")
|
||||
elif elf_seg["p_type"] == "PT_GNU_RELRO":
|
||||
relro = elf_seg
|
||||
|
||||
for elf_s in elf.iter_sections():
|
||||
if not use_section(elf_s):
|
||||
if (
|
||||
elf_s["sh_flags"] & SH_FLAGS.SHF_ALLOC == 0
|
||||
or elf_s["sh_type"] in IGNORE_SECTION_TYPES
|
||||
or elf_s.name in IGNORE_SECTIONS
|
||||
):
|
||||
continue
|
||||
if elf_s["sh_type"] not in ["SHT_PROGBITS", "SHT_NOBITS"]:
|
||||
raise RuntimeError(f"Unknown section {elf_s.name}.")
|
||||
|
||||
if elf_s["sh_flags"] & SH_FLAGS.SHF_EXECINSTR:
|
||||
rwx = PE_CHARACTERISTICS_RX
|
||||
elif elf_s["sh_flags"] & SH_FLAGS.SHF_WRITE:
|
||||
rwx = PE_CHARACTERISTICS_RW
|
||||
else:
|
||||
rwx = PE_CHARACTERISTICS_R
|
||||
|
||||
# PE images are always relro.
|
||||
if relro and relro.section_in_segment(elf_s):
|
||||
rwx = PE_CHARACTERISTICS_R
|
||||
|
||||
if pe_s and pe_s.Characteristics != rwx:
|
||||
yield pe_s
|
||||
pe_s = None
|
||||
|
||||
if pe_s:
|
||||
# Insert padding to properly align the section.
|
||||
pad_len = elf_s["sh_addr"] - pe_s.VirtualAddress - len(pe_s.data)
|
||||
pe_s.data += bytearray(pad_len) + elf_s.data()
|
||||
else:
|
||||
pe_s = PeSection()
|
||||
pe_s.VirtualAddress = elf_s["sh_addr"]
|
||||
pe_s.Characteristics = rwx
|
||||
pe_s.data = elf_s.data()
|
||||
|
||||
if pe_s:
|
||||
yield pe_s
|
||||
|
||||
|
||||
def convert_sections(elf: ELFFile, opt: PeOptionalHeader) -> typing.List[PeSection]:
|
||||
last_vma = 0
|
||||
sections = []
|
||||
|
||||
for pe_s in iter_copy_sections(elf):
|
||||
# Truncate the VMA to the nearest page and insert appropriate padding. This should not
|
||||
# cause any overlap as this is pretty much how ELF *segments* are loaded/mmapped anyways.
|
||||
# The ELF sections inside should also be properly aligned as we reuse the ELF VMA layout
|
||||
# for the PE image.
|
||||
vma = pe_s.VirtualAddress
|
||||
pe_s.VirtualAddress = align_down(vma, SECTION_ALIGNMENT)
|
||||
pe_s.data = bytearray(vma - pe_s.VirtualAddress) + pe_s.data
|
||||
|
||||
pe_s.VirtualSize = len(pe_s.data)
|
||||
pe_s.SizeOfRawData = align_to(len(pe_s.data), FILE_ALIGNMENT)
|
||||
pe_s.Name = {
|
||||
PE_CHARACTERISTICS_RX: b".text",
|
||||
PE_CHARACTERISTICS_RW: b".data",
|
||||
PE_CHARACTERISTICS_R: b".rodata",
|
||||
}[pe_s.Characteristics]
|
||||
|
||||
# This can happen if not building with `-z separate-code`.
|
||||
if pe_s.VirtualAddress < last_vma:
|
||||
raise RuntimeError("Overlapping PE sections.")
|
||||
last_vma = pe_s.VirtualAddress + pe_s.VirtualSize
|
||||
|
||||
pe_s = convert_elf_section(elf_s)
|
||||
if pe_s.Name == b".text":
|
||||
opt.BaseOfCode = pe_s.VirtualAddress
|
||||
opt.SizeOfCode += pe_s.VirtualSize
|
||||
@ -304,6 +344,32 @@ def copy_sections(elf: ELFFile, opt: PeOptionalHeader) -> typing.List[PeSection]
|
||||
return sections
|
||||
|
||||
|
||||
def copy_sections(
|
||||
elf: ELFFile,
|
||||
opt: PeOptionalHeader,
|
||||
input_names: str,
|
||||
sections: typing.List[PeSection],
|
||||
):
|
||||
for name in input_names.split(","):
|
||||
elf_s = elf.get_section_by_name(name)
|
||||
if not elf_s:
|
||||
continue
|
||||
if elf_s.data_alignment > 1 and SECTION_ALIGNMENT % elf_s.data_alignment != 0:
|
||||
raise RuntimeError(f"ELF section {name} is not aligned.")
|
||||
if elf_s["sh_flags"] & (SH_FLAGS.SHF_EXECINSTR | SH_FLAGS.SHF_WRITE) != 0:
|
||||
raise RuntimeError(f"ELF section {name} is not read-only data.")
|
||||
|
||||
pe_s = PeSection()
|
||||
pe_s.Name = name.encode()
|
||||
pe_s.data = elf_s.data()
|
||||
pe_s.VirtualAddress = next_section_address(sections)
|
||||
pe_s.VirtualSize = len(elf_s.data())
|
||||
pe_s.SizeOfRawData = align_to(len(elf_s.data()), FILE_ALIGNMENT)
|
||||
pe_s.Characteristics = PE_CHARACTERISTICS_R
|
||||
opt.SizeOfInitializedData += pe_s.VirtualSize
|
||||
sections.append(pe_s)
|
||||
|
||||
|
||||
def apply_elf_relative_relocation(
|
||||
reloc: ElfRelocation,
|
||||
image_base: int,
|
||||
@ -325,7 +391,6 @@ def apply_elf_relative_relocation(
|
||||
addend = target.data[addend_offset : addend_offset + addend_size]
|
||||
addend = int.from_bytes(addend, byteorder="little")
|
||||
|
||||
# This currently assumes that the ELF file has an image base of 0.
|
||||
value = (image_base + addend).to_bytes(addend_size, byteorder="little")
|
||||
target.data[addend_offset : addend_offset + addend_size] = value
|
||||
|
||||
@ -333,7 +398,7 @@ def apply_elf_relative_relocation(
|
||||
def convert_elf_reloc_table(
|
||||
elf: ELFFile,
|
||||
elf_reloc_table: ElfRelocationTable,
|
||||
image_base: int,
|
||||
elf_image_base: int,
|
||||
sections: typing.List[PeSection],
|
||||
pe_reloc_blocks: typing.Dict[int, PeRelocationBlock],
|
||||
):
|
||||
@ -361,7 +426,7 @@ def convert_elf_reloc_table(
|
||||
|
||||
if reloc["r_info_type"] == RELATIVE_RELOC:
|
||||
apply_elf_relative_relocation(
|
||||
reloc, image_base, sections, elf.elfclass // 8
|
||||
reloc, elf_image_base, sections, elf.elfclass // 8
|
||||
)
|
||||
|
||||
# Now that the ELF relocation has been applied, we can create a PE relocation.
|
||||
@ -381,7 +446,10 @@ def convert_elf_reloc_table(
|
||||
|
||||
|
||||
def convert_elf_relocations(
|
||||
elf: ELFFile, opt: PeOptionalHeader, sections: typing.List[PeSection]
|
||||
elf: ELFFile,
|
||||
opt: PeOptionalHeader,
|
||||
sections: typing.List[PeSection],
|
||||
minimum_sections: int,
|
||||
) -> typing.Optional[PeSection]:
|
||||
dynamic = elf.get_section_by_name(".dynamic")
|
||||
if dynamic is None:
|
||||
@ -391,14 +459,49 @@ def convert_elf_relocations(
|
||||
if not flags_tag["d_val"] & ENUM_DT_FLAGS_1["DF_1_PIE"]:
|
||||
raise RuntimeError("ELF file is not a PIE.")
|
||||
|
||||
# This checks that the ELF image base is 0.
|
||||
symtab = elf.get_section_by_name(".symtab")
|
||||
if symtab:
|
||||
exe_start = symtab.get_symbol_by_name("__executable_start")
|
||||
if exe_start and exe_start[0]["st_value"] != 0:
|
||||
raise RuntimeError("Unexpected ELF image base.")
|
||||
|
||||
opt.SizeOfHeaders = align_to(
|
||||
PE_OFFSET
|
||||
+ len(PE_MAGIC)
|
||||
+ sizeof(PeCoffHeader)
|
||||
+ sizeof(opt)
|
||||
+ sizeof(PeSection) * max(len(sections) + 1, minimum_sections),
|
||||
FILE_ALIGNMENT,
|
||||
)
|
||||
|
||||
# We use the basic VMA layout from the ELF image in the PE image. This could cause the first
|
||||
# section to overlap the PE image headers during runtime at VMA 0. We can simply apply a fixed
|
||||
# offset relative to the PE image base when applying/converting ELF relocations. Afterwards we
|
||||
# just have to apply the offset to the PE addresses so that the PE relocations work correctly on
|
||||
# the ELF portions of the image.
|
||||
segment_offset = 0
|
||||
if sections[0].VirtualAddress < opt.SizeOfHeaders:
|
||||
segment_offset = align_to(
|
||||
opt.SizeOfHeaders - sections[0].VirtualAddress, SECTION_ALIGNMENT
|
||||
)
|
||||
|
||||
opt.AddressOfEntryPoint = elf["e_entry"] + segment_offset
|
||||
opt.BaseOfCode += segment_offset
|
||||
if isinstance(opt, PeOptionalHeader32):
|
||||
opt.BaseOfData += segment_offset
|
||||
|
||||
pe_reloc_blocks: typing.Dict[int, PeRelocationBlock] = {}
|
||||
for reloc_type, reloc_table in dynamic.get_relocation_tables().items():
|
||||
if reloc_type not in ["REL", "RELA"]:
|
||||
raise RuntimeError("Unsupported relocation type {elf_reloc_type}.")
|
||||
convert_elf_reloc_table(
|
||||
elf, reloc_table, opt.ImageBase, sections, pe_reloc_blocks
|
||||
elf, reloc_table, opt.ImageBase + segment_offset, sections, pe_reloc_blocks
|
||||
)
|
||||
|
||||
for pe_s in sections:
|
||||
pe_s.VirtualAddress += segment_offset
|
||||
|
||||
if len(pe_reloc_blocks) == 0:
|
||||
return None
|
||||
|
||||
@ -413,6 +516,7 @@ def convert_elf_relocations(
|
||||
n_relocs += 1
|
||||
block.entries.append(PeRelocationEntry())
|
||||
|
||||
block.PageRVA += segment_offset
|
||||
block.BlockSize = (
|
||||
sizeof(PeRelocationBlock) + sizeof(PeRelocationEntry) * n_relocs
|
||||
)
|
||||
@ -423,11 +527,9 @@ def convert_elf_relocations(
|
||||
pe_reloc_s = PeSection()
|
||||
pe_reloc_s.Name = b".reloc"
|
||||
pe_reloc_s.data = data
|
||||
pe_reloc_s.VirtualAddress = next_section_address(sections)
|
||||
pe_reloc_s.VirtualSize = len(data)
|
||||
pe_reloc_s.SizeOfRawData = align_to(len(data), FILE_ALIGNMENT)
|
||||
pe_reloc_s.VirtualAddress = align_to(
|
||||
sections[-1].VirtualAddress + sections[-1].VirtualSize, SECTION_ALIGNMENT
|
||||
)
|
||||
# CNT_INITIALIZED_DATA|MEM_READ|MEM_DISCARDABLE
|
||||
pe_reloc_s.Characteristics = 0x42000040
|
||||
|
||||
@ -495,8 +597,9 @@ def elf2efi(args: argparse.Namespace):
|
||||
else:
|
||||
opt.ImageBase = (0x100000000 + opt.ImageBase) & 0x1FFFF0000
|
||||
|
||||
sections = copy_sections(elf, opt)
|
||||
pe_reloc_s = convert_elf_relocations(elf, opt, sections)
|
||||
sections = convert_sections(elf, opt)
|
||||
copy_sections(elf, opt, args.copy_sections, sections)
|
||||
pe_reloc_s = convert_elf_relocations(elf, opt, sections, args.minimum_sections)
|
||||
|
||||
coff.Machine = pe_arch
|
||||
coff.NumberOfSections = len(sections)
|
||||
@ -506,7 +609,6 @@ def elf2efi(args: argparse.Namespace):
|
||||
# and (32BIT_MACHINE or LARGE_ADDRESS_AWARE)
|
||||
coff.Characteristics = 0x30E if elf.elfclass == 32 else 0x22E
|
||||
|
||||
opt.AddressOfEntryPoint = elf["e_entry"]
|
||||
opt.SectionAlignment = SECTION_ALIGNMENT
|
||||
opt.FileAlignment = FILE_ALIGNMENT
|
||||
opt.MajorImageVersion = args.version_major
|
||||
@ -515,18 +617,8 @@ def elf2efi(args: argparse.Namespace):
|
||||
opt.MinorSubsystemVersion = args.efi_minor
|
||||
opt.Subsystem = args.subsystem
|
||||
opt.Magic = 0x10B if elf.elfclass == 32 else 0x20B
|
||||
opt.SizeOfImage = align_to(
|
||||
sections[-1].VirtualAddress + sections[-1].VirtualSize, SECTION_ALIGNMENT
|
||||
)
|
||||
opt.SizeOfImage = next_section_address(sections)
|
||||
|
||||
opt.SizeOfHeaders = align_to(
|
||||
PE_OFFSET
|
||||
+ len(PE_MAGIC)
|
||||
+ sizeof(PeCoffHeader)
|
||||
+ coff.SizeOfOptionalHeader
|
||||
+ sizeof(PeSection) * max(coff.NumberOfSections, args.minimum_sections),
|
||||
FILE_ALIGNMENT,
|
||||
)
|
||||
# DYNAMIC_BASE|NX_COMPAT|HIGH_ENTROPY_VA or DYNAMIC_BASE|NX_COMPAT
|
||||
opt.DllCharacteristics = 0x160 if elf.elfclass == 64 else 0x140
|
||||
|
||||
@ -593,6 +685,12 @@ def main():
|
||||
default=0,
|
||||
help="Minimum number of sections to leave space for",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--copy-sections",
|
||||
type=str,
|
||||
default="",
|
||||
help="Copy these sections if found",
|
||||
)
|
||||
|
||||
elf2efi(parser.parse_args())
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user