Skip to content

Commit

Permalink
LibELF: Only collect region sizes before reserving memory
Browse files Browse the repository at this point in the history
This keeps us from needlessly allocating storage via `malloc` as part
of the `Vector`s that early, which we might conflict on while reserving
memory for the main executable.
  • Loading branch information
timschumi authored and linusg committed Jun 21, 2022
1 parent c0b3179 commit d2b8741
Showing 1 changed file with 62 additions and 40 deletions.
102 changes: 62 additions & 40 deletions Userland/Libraries/LibELF/DynamicLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -263,49 +263,26 @@ void DynamicLoader::do_lazy_relocations()

void DynamicLoader::load_program_headers()
{
Vector<ProgramHeaderRegion> load_regions;
Vector<ProgramHeaderRegion> map_regions;
Vector<ProgramHeaderRegion> copy_regions;
Optional<ProgramHeaderRegion> tls_region;
Optional<ProgramHeaderRegion> relro_region;

VirtualAddress dynamic_region_desired_vaddr;
FlatPtr ph_load_start = SIZE_MAX;
FlatPtr ph_load_end = 0;

// We walk the program header list once to find the requested address ranges of the program.
// We don't fill in the list of regions yet to keep malloc memory blocks from interfering with our reservation.
image().for_each_program_header([&](Image::ProgramHeader const& program_header) {
ProgramHeaderRegion region {};
region.set_program_header(program_header.raw_header());
if (region.is_tls_template()) {
VERIFY(!tls_region.has_value());
tls_region = region;
} else if (region.is_load()) {
if (region.size_in_memory() == 0)
return;
load_regions.append(region);
if (region.is_writable()) {
copy_regions.append(region);
} else {
map_regions.append(region);
}
} else if (region.is_dynamic()) {
dynamic_region_desired_vaddr = region.desired_load_address();
} else if (region.is_relro()) {
VERIFY(!relro_region.has_value());
relro_region = region;
}
});
if (program_header.type() != PT_LOAD)
return;

VERIFY(!map_regions.is_empty() || !copy_regions.is_empty());
FlatPtr section_start = program_header.vaddr().get();
FlatPtr section_end = section_start + program_header.size_in_memory();

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

quick_sort(load_regions, compare_load_address);
quick_sort(map_regions, compare_load_address);
quick_sort(copy_regions, compare_load_address);
if (ph_load_end < section_end)
ph_load_end = section_end;
});

// Process regions in order: .text, .data, .tls
void* requested_load_address = image().is_dynamic() ? nullptr : load_regions.first().desired_load_address().as_ptr();
void* requested_load_address = image().is_dynamic() ? nullptr : reinterpret_cast<void*>(ph_load_start);

int reservation_mmap_flags = MAP_ANON | MAP_PRIVATE | MAP_NORESERVE;
if (image().is_dynamic())
Expand All @@ -318,14 +295,13 @@ void DynamicLoader::load_program_headers()
// First, we make a dummy reservation mapping, in order to allocate enough VM
// to hold all regions contiguously in the address space.

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);
FlatPtr ph_load_base = ph_load_start & ~(FlatPtr)0xfffu;
ph_load_end = round_up_to_power_of_two(ph_load_end, PAGE_SIZE);

size_t total_mapping_size = ph_load_end - ph_load_base;

// Before we make our reservation, unmap our existing mapped ELF image that we used for reading header information.
// This leaves our pointers dangling momentarily, but it reduces the chance that we will conflict with ourselves.
// The regions that we collected above are still safe to use because they are independent copies.
if (munmap(m_file_data, m_file_size) < 0) {
perror("munmap old mapping");
VERIFY_NOT_REACHED();
Expand Down Expand Up @@ -360,6 +336,52 @@ void DynamicLoader::load_program_headers()
VERIFY_NOT_REACHED();
}

// Most binaries have four loadable regions, three of which are mapped
// (symbol tables/relocation information, executable instructions, read-only data)
// and one of which is copied (modifiable data).
// These are allocated in-line to cut down on the malloc calls.
Vector<ProgramHeaderRegion, 4> load_regions;
Vector<ProgramHeaderRegion, 3> map_regions;
Vector<ProgramHeaderRegion, 1> copy_regions;
Optional<ProgramHeaderRegion> tls_region;
Optional<ProgramHeaderRegion> relro_region;

VirtualAddress dynamic_region_desired_vaddr;

image().for_each_program_header([&](Image::ProgramHeader const& program_header) {
ProgramHeaderRegion region {};
region.set_program_header(program_header.raw_header());
if (region.is_tls_template()) {
VERIFY(!tls_region.has_value());
tls_region = region;
} else if (region.is_load()) {
if (region.size_in_memory() == 0)
return;
load_regions.append(region);
if (region.is_writable()) {
copy_regions.append(region);
} else {
map_regions.append(region);
}
} else if (region.is_dynamic()) {
dynamic_region_desired_vaddr = region.desired_load_address();
} else if (region.is_relro()) {
VERIFY(!relro_region.has_value());
relro_region = region;
}
});

VERIFY(!map_regions.is_empty() || !copy_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(map_regions, compare_load_address);
quick_sort(copy_regions, compare_load_address);

// Process regions in order: .text, .data, .tls
for (auto& region : map_regions) {
FlatPtr ph_desired_base = region.desired_load_address().get();
FlatPtr ph_base = region.desired_load_address().page_base().get();
Expand Down

0 comments on commit d2b8741

Please sign in to comment.