Skip to content

Commit

Permalink
LibELF: Add support for loading objects with multiple data and text s…
Browse files Browse the repository at this point in the history
…egments

This enables loading executables with multiple data and text segments. Also
it fixes loading executables where the text segment has a non-zero offset.

Example:

  $ echo "main () {}" > test.c
  $ gcc -Wl,-z,separate-code -o test test.c
  $ objdump -p test
  test:     file format elf32-i386

  Program Header:
      PHDR off    0x00000034 vaddr 0x00000034 paddr 0x00000034 align 2**2
           filesz 0x000000e0 memsz 0x000000e0 flags r--
    INTERP off    0x00000114 vaddr 0x00000114 paddr 0x00000114 align 2**0
           filesz 0x00000013 memsz 0x00000013 flags r--
      LOAD off    0x00000000 vaddr 0x00000000 paddr 0x00000000 align 2**12
           filesz 0x000003c4 memsz 0x000003c4 flags r--
      LOAD off    0x00001000 vaddr 0x00001000 paddr 0x00001000 align 2**12
           filesz 0x00000279 memsz 0x00000279 flags r-x
      LOAD off    0x00002000 vaddr 0x00002000 paddr 0x00002000 align 2**12
           filesz 0x00000004 memsz 0x00000004 flags r--
      LOAD off    0x00002004 vaddr 0x00003004 paddr 0x00003004 align 2**12
           filesz 0x00000100 memsz 0x00000124 flags rw-
   DYNAMIC off    0x00002014 vaddr 0x00003014 paddr 0x00003014 align 2**2
           filesz 0x000000c8 memsz 0x000000c8 flags rw-
  • Loading branch information
gunnarbeutner authored and awesomekling committed Apr 14, 2021
1 parent c3ee705 commit cd7512a
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 79 deletions.
2 changes: 1 addition & 1 deletion Userland/Libraries/LibELF/DynamicLinker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ void ELF::DynamicLinker::linker_main(String&& main_program_name, int main_progra
auto main_executable_loader = load_main_executable(main_program_name);
auto entry_point = main_executable_loader->image().entry();
if (main_executable_loader->is_dynamic())
entry_point = entry_point.offset(main_executable_loader->text_segment_load_address().get());
entry_point = entry_point.offset(main_executable_loader->base_address().get());
return (EntryPointFunction)(entry_point.as_ptr());
}();

Expand Down
173 changes: 100 additions & 73 deletions Userland/Libraries/LibELF/DynamicLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

#include <AK/Debug.h>
#include <AK/Optional.h>
#include <AK/QuickSort.h>
#include <AK/StringBuilder.h>
#include <LibELF/DynamicLinker.h>
#include <LibELF/DynamicLoader.h>
Expand Down Expand Up @@ -167,7 +168,9 @@ RefPtr<DynamicObject> DynamicLoader::map()

load_program_headers();

m_dynamic_object = DynamicObject::create(m_text_segment_load_address, m_dynamic_section_address);
VERIFY(!m_base_address.is_null());

m_dynamic_object = DynamicObject::create(m_base_address, m_dynamic_section_address);
m_dynamic_object->set_tls_offset(m_tls_offset);
m_dynamic_object->set_tls_size(m_tls_size);

Expand All @@ -184,19 +187,21 @@ bool DynamicLoader::load_stage_2(unsigned flags, size_t total_tls_size)
VERIFY(flags & RTLD_GLOBAL);

if (m_dynamic_object->has_text_relocations()) {
VERIFY(m_text_segment_load_address.get() != 0);
for (auto& text_segment : m_text_segments) {
VERIFY(text_segment.address().get() != 0);

#ifndef AK_OS_MACOS
// Remap this text region as private.
if (mremap(m_text_segment_load_address.as_ptr(), m_text_segment_size, m_text_segment_size, MAP_PRIVATE) == MAP_FAILED) {
perror("mremap .text: MAP_PRIVATE");
return false;
}
// Remap this text region as private.
if (mremap(text_segment.address().as_ptr(), text_segment.size(), text_segment.size(), MAP_PRIVATE) == MAP_FAILED) {
perror("mremap .text: MAP_PRIVATE");
return false;
}
#endif

if (0 > mprotect(m_text_segment_load_address.as_ptr(), m_text_segment_size, PROT_READ | PROT_WRITE)) {
perror("mprotect .text: PROT_READ | PROT_WRITE"); // FIXME: dlerror?
return false;
if (0 > mprotect(text_segment.address().as_ptr(), text_segment.size(), PROT_READ | PROT_WRITE)) {
perror("mprotect .text: PROT_READ | PROT_WRITE"); // FIXME: dlerror?
return false;
}
}
}
do_main_relocations(total_tls_size);
Expand Down Expand Up @@ -230,9 +235,11 @@ RefPtr<DynamicObject> DynamicLoader::load_stage_3(unsigned flags, size_t total_t
setup_plt_trampoline();
}

if (mprotect(m_text_segment_load_address.as_ptr(), m_text_segment_size, PROT_READ | PROT_EXEC) < 0) {
perror("mprotect .text: PROT_READ | PROT_EXEC"); // FIXME: dlerror?
return nullptr;
for (auto& text_segment : m_text_segments) {
if (mprotect(text_segment.address().as_ptr(), text_segment.size(), PROT_READ | PROT_EXEC) < 0) {
perror("mprotect .text: PROT_READ | PROT_EXEC"); // FIXME: dlerror?
return nullptr;
}
}

if (m_relro_segment_size) {
Expand Down Expand Up @@ -269,8 +276,9 @@ void DynamicLoader::do_lazy_relocations(size_t total_tls_size)

void DynamicLoader::load_program_headers()
{
Optional<ProgramHeaderRegion> text_region;
Optional<ProgramHeaderRegion> data_region;
Vector<ProgramHeaderRegion> load_regions;
Vector<ProgramHeaderRegion> text_regions;
Vector<ProgramHeaderRegion> data_regions;
Optional<ProgramHeaderRegion> tls_region;
Optional<ProgramHeaderRegion> relro_region;

Expand All @@ -283,12 +291,11 @@ void DynamicLoader::load_program_headers()
VERIFY(!tls_region.has_value());
tls_region = region;
} else if (region.is_load()) {
load_regions.append(region);
if (region.is_executable()) {
VERIFY(!text_region.has_value());
text_region = region;
text_regions.append(region);
} else {
VERIFY(!data_region.has_value());
data_region = region;
data_regions.append(region);
}
} else if (region.is_dynamic()) {
dynamic_region_desired_vaddr = region.desired_load_address();
Expand All @@ -299,101 +306,121 @@ void DynamicLoader::load_program_headers()
return IterationDecision::Continue;
});

VERIFY(text_region.has_value());
VERIFY(data_region.has_value());
VERIFY(!text_regions.is_empty());
VERIFY(!data_regions.is_empty());

auto compare_load_address = [](ProgramHeaderRegion& a, ProgramHeaderRegion& b) {
return a.desired_load_address().as_ptr() < b.desired_load_address().as_ptr();
};

quick_sort(load_regions, compare_load_address);
quick_sort(text_regions, compare_load_address);
quick_sort(data_regions, compare_load_address);

// Process regions in order: .text, .data, .tls
void* requested_load_address = m_elf_image.is_dynamic() ? nullptr : text_region.value().desired_load_address().as_ptr();
void* requested_load_address = m_elf_image.is_dynamic() ? nullptr : load_regions.first().desired_load_address().as_ptr();

int reservation_mmap_flags = MAP_ANON | MAP_PRIVATE | MAP_NORESERVE;
if (m_elf_image.is_dynamic())
reservation_mmap_flags |= MAP_RANDOMIZED;
else
reservation_mmap_flags |= MAP_FIXED;

VERIFY(!text_region.value().is_writable());
for (auto& text_region : text_regions)
VERIFY(!text_region.is_writable());

// First, we make a dummy reservation mapping, in order to allocate enough VM
// to hold both text+data contiguously in the address space.

FlatPtr ph_text_base = text_region.value().desired_load_address().page_base().get();
FlatPtr ph_text_end = round_up_to_power_of_two(text_region.value().desired_load_address().offset(text_region.value().size_in_memory()).get(), PAGE_SIZE);
FlatPtr ph_data_base = data_region.value().desired_load_address().page_base().get();
FlatPtr ph_data_end = round_up_to_power_of_two(data_region.value().desired_load_address().offset(data_region.value().size_in_memory()).get(), PAGE_SIZE);
// to hold all regions contiguously in the address space.

size_t total_mapping_size = ph_data_end - ph_text_base;
FlatPtr ph_load_base = load_regions.first().desired_load_address().page_base().get();
FlatPtr ph_load_end = round_up_to_power_of_two(load_regions.last().desired_load_address().offset(load_regions.last().size_in_memory()).get(), PAGE_SIZE);

size_t text_segment_size = ph_text_end - ph_text_base;
size_t data_segment_size = ph_data_end - ph_data_base;
size_t total_mapping_size = ph_load_end - ph_load_base;

auto* reservation = mmap(requested_load_address, total_mapping_size, PROT_NONE, reservation_mmap_flags, 0, 0);
if (reservation == MAP_FAILED) {
perror("mmap reservation");
VERIFY_NOT_REACHED();
}

m_base_address = VirtualAddress { reservation };

// Then we unmap the reservation.
if (munmap(reservation, total_mapping_size) < 0) {
perror("munmap reservation");
VERIFY_NOT_REACHED();
}

// Now we can map the text segment at the reserved address.
auto* text_segment_begin = (u8*)mmap_with_name(
reservation,
text_segment_size,
PROT_READ,
MAP_FILE | MAP_SHARED | MAP_FIXED,
m_image_fd,
text_region.value().offset(),
String::formatted("{}: .text", m_filename).characters());

if (text_segment_begin == MAP_FAILED) {
perror("mmap text");
VERIFY_NOT_REACHED();
for (auto& text_region : text_regions) {
FlatPtr ph_text_base = text_region.desired_load_address().page_base().get();
FlatPtr ph_text_end = round_up_to_power_of_two(text_region.desired_load_address().offset(text_region.size_in_memory()).get(), PAGE_SIZE);
size_t text_segment_size = ph_text_end - ph_text_base;

auto text_segment_offset = ph_text_base - ph_load_base;
auto* text_segment_address = (u8*)reservation + text_segment_offset;

// Now we can map the text segment at the reserved address.
auto* text_segment_begin = (u8*)mmap_with_name(
text_segment_address,
text_segment_size,
PROT_READ,
MAP_FILE | MAP_SHARED | MAP_FIXED,
m_image_fd,
text_region.offset(),
String::formatted("{}: .text", m_filename).characters());

if (text_segment_begin == MAP_FAILED) {
perror("mmap text");
VERIFY_NOT_REACHED();
}

m_text_segments.append({ VirtualAddress { (FlatPtr)text_segment_begin }, text_segment_size });
}

VERIFY(requested_load_address == nullptr || requested_load_address == text_segment_begin);
m_text_segment_size = text_segment_size;
m_text_segment_load_address = VirtualAddress { (FlatPtr)text_segment_begin };
VERIFY(requested_load_address == nullptr || requested_load_address == reservation);

if (relro_region.has_value()) {
m_relro_segment_size = relro_region->size_in_memory();
m_relro_segment_address = m_text_segment_load_address.offset(relro_region->desired_load_address().get());
m_relro_segment_address = VirtualAddress { (u8*)reservation + relro_region->desired_load_address().get() };
}

if (m_elf_image.is_dynamic())
m_dynamic_section_address = dynamic_region_desired_vaddr.offset(m_text_segment_load_address.get());
m_dynamic_section_address = VirtualAddress { (u8*)reservation + dynamic_region_desired_vaddr.get() };
else
m_dynamic_section_address = dynamic_region_desired_vaddr;

FlatPtr data_segment_offset_from_text = ph_data_base - ph_text_base;

// Finally, we make an anonymous mapping for the data segment. Contents are then copied from the file.
auto* data_segment_address = (u8*)text_segment_begin + data_segment_offset_from_text;
for (auto& data_region : data_regions) {
FlatPtr ph_data_base = data_region.desired_load_address().page_base().get();
FlatPtr ph_data_end = round_up_to_power_of_two(data_region.desired_load_address().offset(data_region.size_in_memory()).get(), PAGE_SIZE);
size_t data_segment_size = ph_data_end - ph_data_base;

auto data_segment_offset = ph_data_base - ph_load_base;
auto* data_segment_address = (u8*)reservation + data_segment_offset;

// Finally, we make an anonymous mapping for the data segment. Contents are then copied from the file.
auto* data_segment = (u8*)mmap_with_name(
data_segment_address,
data_segment_size,
PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED,
0,
0,
String::formatted("{}: .data", m_filename).characters());

if (MAP_FAILED == data_segment) {
perror("mmap data");
VERIFY_NOT_REACHED();
}

auto* data_segment = (u8*)mmap_with_name(
data_segment_address,
data_segment_size,
PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED,
0,
0,
String::formatted("{}: .data", m_filename).characters());
VirtualAddress data_segment_start;
if (m_elf_image.is_dynamic())
data_segment_start = VirtualAddress { (u8*)reservation + data_region.desired_load_address().get() };
else
data_segment_start = data_region.desired_load_address();

if (MAP_FAILED == data_segment) {
perror("mmap data");
VERIFY_NOT_REACHED();
memcpy(data_segment_start.as_ptr(), (u8*)m_file_data + data_region.offset(), data_region.size_in_image());
}

VirtualAddress data_segment_start;
if (m_elf_image.is_dynamic())
data_segment_start = data_region.value().desired_load_address().offset((FlatPtr)text_segment_begin);
else
data_segment_start = data_region.value().desired_load_address();

memcpy(data_segment_start.as_ptr(), (u8*)m_file_data + data_region.value().offset(), data_region.value().size_in_image());

// FIXME: Initialize the values in the TLS section. Currently, it is zeroed.
}

Expand Down
23 changes: 20 additions & 3 deletions Userland/Libraries/LibELF/DynamicLoader.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,22 @@

namespace ELF {

class LoadedSegment {
public:
LoadedSegment(VirtualAddress address, size_t size)
: m_address(address)
, m_size(size)
{
}

VirtualAddress address() const { return m_address; }
size_t size() const { return m_size; }

private:
VirtualAddress m_address;
size_t m_size;
};

class DynamicLoader : public RefCounted<DynamicLoader> {
public:
static RefPtr<DynamicLoader> try_create(int fd, String filename);
Expand Down Expand Up @@ -74,7 +90,8 @@ class DynamicLoader : public RefCounted<DynamicLoader> {
template<typename F>
void for_each_needed_library(F) const;

VirtualAddress text_segment_load_address() const { return m_text_segment_load_address; }
VirtualAddress base_address() const { return m_base_address; }
const Vector<LoadedSegment> text_segments() const { return m_text_segments; }
bool is_dynamic() const { return m_elf_image.is_dynamic(); }

static Optional<DynamicObject::SymbolLookupResult> lookup_symbol(const ELF::DynamicObject::Symbol&);
Expand Down Expand Up @@ -141,8 +158,8 @@ class DynamicLoader : public RefCounted<DynamicLoader> {

RefPtr<DynamicObject> m_dynamic_object;

VirtualAddress m_text_segment_load_address;
size_t m_text_segment_size { 0 };
VirtualAddress m_base_address;
Vector<LoadedSegment> m_text_segments;

VirtualAddress m_relro_segment_address;
size_t m_relro_segment_size { 0 };
Expand Down
4 changes: 2 additions & 2 deletions Userland/Libraries/LibELF/DynamicObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ namespace ELF {

static const char* name_for_dtag(Elf32_Sword d_tag);

DynamicObject::DynamicObject(VirtualAddress base_address, VirtualAddress dynamic_section_addresss)
DynamicObject::DynamicObject(VirtualAddress base_address, VirtualAddress dynamic_section_address)
: m_base_address(base_address)
, m_dynamic_address(dynamic_section_addresss)
, m_dynamic_address(dynamic_section_address)
{
auto* header = (Elf32_Ehdr*)base_address.as_ptr();
auto* pheader = (Elf32_Phdr*)(base_address.as_ptr() + header->e_phoff);
Expand Down

0 comments on commit cd7512a

Please sign in to comment.