mirror of
https://github.com/SerenityOS/serenity.git
synced 2026-11-07 12:38:38 -10:00
155 lines
6.6 KiB
C++
155 lines
6.6 KiB
C++
/*
|
|
* Copyright (c) 2024, Sönke Holz <[email protected]>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <Kernel/EFIPrekernel/Panic.h>
|
|
#include <Kernel/EFIPrekernel/Relocation.h>
|
|
|
|
#include <LibELF/Arch/GenericDynamicRelocationType.h>
|
|
#include <LibELF/ELFABI.h>
|
|
#include <LibELF/Image.h>
|
|
|
|
namespace Kernel {
|
|
|
|
// This function only works on ELF addresses that are not in a zero-padded area of a program header.
|
|
static ErrorOr<Bytes> get_data_at_kernel_elf_virtual_address(ELF::Image const& kernel_elf_image, Bytes kernel_elf_image_data, FlatPtr start_vaddr, size_t size)
|
|
{
|
|
ErrorOr<Bytes> result = EINVAL;
|
|
static Optional<ELF::Image::ProgramHeader> cached_program_header;
|
|
|
|
static auto is_range_in_program_header = [](FlatPtr start_vaddr, size_t size, ELF::Image::ProgramHeader const& program_header) {
|
|
return start_vaddr >= program_header.vaddr().get() && start_vaddr + size <= program_header.vaddr().get() + program_header.size_in_memory();
|
|
};
|
|
|
|
if (cached_program_header.has_value() && is_range_in_program_header(start_vaddr, size, cached_program_header.value()))
|
|
return kernel_elf_image_data.slice(cached_program_header->offset() + start_vaddr - cached_program_header->vaddr().get(), size);
|
|
|
|
kernel_elf_image.for_each_program_header([&result, kernel_elf_image_data, start_vaddr, size](ELF::Image::ProgramHeader const& program_header) {
|
|
if (program_header.type() == PT_LOAD && is_range_in_program_header(start_vaddr, size, program_header)) {
|
|
result = kernel_elf_image_data.slice(program_header.offset() + start_vaddr - program_header.vaddr().get(), size);
|
|
cached_program_header = program_header;
|
|
return IterationDecision::Break;
|
|
}
|
|
return IterationDecision::Continue;
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
void perform_kernel_relocations(ELF::Image const& kernel_elf_image, Bytes kernel_elf_image_data, FlatPtr base_address)
|
|
{
|
|
size_t dynamic_section_offset { 0 };
|
|
kernel_elf_image.for_each_program_header([&dynamic_section_offset](ELF::Image::ProgramHeader const& program_header) {
|
|
if (program_header.type() == PT_DYNAMIC) {
|
|
dynamic_section_offset = program_header.offset();
|
|
return IterationDecision::Break;
|
|
}
|
|
return IterationDecision::Continue;
|
|
});
|
|
|
|
if (dynamic_section_offset == 0)
|
|
PANIC("Kernel image does not have a PT_DYNAMIC program header; can't perform relocations");
|
|
|
|
FlatPtr relocation_table_vaddr = 0;
|
|
size_t relocation_table_size = 0;
|
|
size_t relocation_entry_size = 0;
|
|
size_t number_of_relative_relocs = 0;
|
|
|
|
FlatPtr relr_relocation_table_vaddr = 0;
|
|
size_t relr_relocation_table_size = 0;
|
|
|
|
auto const* dynamic_section_entries = bit_cast<Elf_Dyn const*>(bit_cast<FlatPtr>(kernel_elf_image_data.data()) + dynamic_section_offset);
|
|
for (size_t i = 0;; i++) {
|
|
auto const& entry = dynamic_section_entries[i];
|
|
|
|
if (entry.d_tag == DT_NULL)
|
|
break;
|
|
|
|
switch (entry.d_tag) {
|
|
case DT_REL:
|
|
case DT_RELSZ:
|
|
case DT_RELENT:
|
|
case DT_RELCOUNT:
|
|
PANIC("DT_REL relocation tables are not supported");
|
|
|
|
case DT_RELA:
|
|
relocation_table_vaddr = entry.d_un.d_ptr;
|
|
break;
|
|
case DT_RELASZ:
|
|
relocation_table_size = entry.d_un.d_val;
|
|
break;
|
|
case DT_RELAENT:
|
|
relocation_entry_size = entry.d_un.d_val;
|
|
break;
|
|
case DT_RELACOUNT:
|
|
number_of_relative_relocs = entry.d_un.d_val;
|
|
break;
|
|
|
|
case DT_RELR:
|
|
relr_relocation_table_vaddr = entry.d_un.d_ptr;
|
|
break;
|
|
case DT_RELRSZ:
|
|
relr_relocation_table_size = entry.d_un.d_val;
|
|
break;
|
|
case DT_RELRENT:
|
|
VERIFY(entry.d_un.d_val == sizeof(FlatPtr));
|
|
break;
|
|
}
|
|
}
|
|
|
|
VERIFY((relocation_table_vaddr != 0 && relocation_table_size != 0 && relocation_entry_size != 0 && number_of_relative_relocs != 0)
|
|
|| (relr_relocation_table_vaddr != 0 && relr_relocation_table_size != 0));
|
|
|
|
if (relocation_table_vaddr != 0) {
|
|
// We are still identity mapped, so translate the address of the relocation table.
|
|
auto relocation_table_data = MUST(get_data_at_kernel_elf_virtual_address(kernel_elf_image, kernel_elf_image_data, relocation_table_vaddr, relocation_table_size));
|
|
|
|
for (size_t i = 0; i < number_of_relative_relocs; i++) {
|
|
auto const& raw_relocation = *bit_cast<Elf_Rela const*>(bit_cast<FlatPtr>(relocation_table_data.data()) + (i * relocation_entry_size));
|
|
ELF::Image::Relocation relocation(kernel_elf_image, raw_relocation, true);
|
|
|
|
VERIFY(relocation.type() == to_underlying(ELF::GenericDynamicRelocationType::RELATIVE));
|
|
|
|
// Elf_Rela::r_offset is a virtual address for executables, so we need to translate it as well.
|
|
auto* patch_ptr = bit_cast<FlatPtr*>(MUST(get_data_at_kernel_elf_virtual_address(kernel_elf_image, kernel_elf_image_data, relocation.offset(), sizeof(FlatPtr))).data());
|
|
|
|
FlatPtr relocated_address = base_address + relocation.addend();
|
|
__builtin_memcpy(patch_ptr, &relocated_address, sizeof(FlatPtr));
|
|
}
|
|
}
|
|
|
|
if (relr_relocation_table_vaddr != 0) {
|
|
auto patch_relr = [&kernel_elf_image, kernel_elf_image_data, base_address](FlatPtr patch_vaddr) {
|
|
auto* patch_ptr = bit_cast<FlatPtr*>(MUST(get_data_at_kernel_elf_virtual_address(kernel_elf_image, kernel_elf_image_data, patch_vaddr, sizeof(FlatPtr))).data());
|
|
|
|
FlatPtr relocated_address;
|
|
__builtin_memcpy(&relocated_address, patch_ptr, sizeof(FlatPtr));
|
|
relocated_address += base_address;
|
|
__builtin_memcpy(patch_ptr, &relocated_address, sizeof(FlatPtr));
|
|
};
|
|
|
|
auto relr_relocation_table_data = MUST(get_data_at_kernel_elf_virtual_address(kernel_elf_image, kernel_elf_image_data, relr_relocation_table_vaddr, relr_relocation_table_size));
|
|
auto* entries = bit_cast<Elf_Relr*>(relr_relocation_table_data.data());
|
|
FlatPtr patch_vaddr = 0;
|
|
|
|
for (size_t i = 0; i < relr_relocation_table_size / sizeof(FlatPtr); i++) {
|
|
if ((entries[i] & 1) == 0) {
|
|
patch_vaddr = entries[i];
|
|
patch_relr(patch_vaddr);
|
|
patch_vaddr += sizeof(FlatPtr);
|
|
} else {
|
|
auto bitmap = entries[i];
|
|
for (size_t j = 0; (bitmap >>= 1) != 0; j++)
|
|
if ((bitmap & 1) != 0)
|
|
patch_relr(patch_vaddr + (j * sizeof(FlatPtr)));
|
|
|
|
patch_vaddr += (8 * sizeof(FlatPtr) - 1) * sizeof(FlatPtr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|