Skip to content

Commit

Permalink
Reduce binary size while adding segments or relocating PHDR
Browse files Browse the repository at this point in the history
Commit based on the work of @Miniman2718

Resolve: #1085
  • Loading branch information
romainthomas committed Dec 9, 2024
1 parent 426b240 commit 68caa32
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 10 deletions.
14 changes: 12 additions & 2 deletions src/ELF/Binary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2648,6 +2648,16 @@ uint64_t Binary::relocate_phdr_table_v3() {
// Reserve space for 10 user's segments
static constexpr size_t USER_SEGMENTS = 10;

uint64_t last_off = 0;
for (const std::unique_ptr<Segment>& segment : segments_) {
if (segment != nullptr && segment->is_load()) {
last_off = std::max(last_off, segment->file_offset());
// Possible because segment are not overlapping
last_off += segment->physical_size();
}
}
last_off = align(last_off, static_cast<uint64_t>(get_pagesize(*this)));

if (phdr_reloc_info_.new_offset > 0) {
return phdr_reloc_info_.new_offset;
}
Expand All @@ -2660,7 +2670,7 @@ uint64_t Binary::relocate_phdr_table_v3() {
type() == Header::CLASS::ELF32 ? sizeof(details::ELF32::Elf_Phdr) :
sizeof(details::ELF64::Elf_Phdr);

const uint64_t last_offset = virtual_size();
const uint64_t last_offset = last_off;

LIEF_DEBUG("Moving segment table at the end of the binary (0x{:010x})",
last_offset);
Expand Down Expand Up @@ -2688,7 +2698,7 @@ uint64_t Binary::relocate_phdr_table_v3() {
phdr_load_segment->file_offset(phdr_reloc_info_.new_offset);
phdr_load_segment->physical_size(new_segtbl_sz);
phdr_load_segment->virtual_size(new_segtbl_sz);
phdr_load_segment->virtual_address(imagebase() + phdr_reloc_info_.new_offset);
phdr_load_segment->virtual_address(imagebase() + virtual_size());
phdr_load_segment->physical_address(phdr_load_segment->virtual_address());
phdr_load_segment->alignment(0x1000);
phdr_load_segment->add(Segment::FLAGS::R);
Expand Down
7 changes: 2 additions & 5 deletions src/ELF/Binary.tcc
Original file line number Diff line number Diff line change
Expand Up @@ -364,12 +364,8 @@ Segment* Binary::add_segment<Header::FILE_TYPE::EXEC>(const Segment& segment, ui
return nullptr;
}

//const uint64_t phdr_size = type() == ELF_CLASS::ELFCLASS32 ?
// sizeof(details::ELF32::Elf_Phdr) : sizeof(details::ELF64::Elf_Phdr);

// Add the segment itself
// ====================================================================
//datahandler_->make_hole(new_phdr_offset + phdr_size * header.numberof_segments(), phdr_size);
header.numberof_segments(header.numberof_segments() + 1);
span<const uint8_t> content_ref = segment.content();
std::vector<uint8_t> content{content_ref.data(), std::end(content_ref)};
Expand All @@ -378,6 +374,7 @@ Segment* Binary::add_segment<Header::FILE_TYPE::EXEC>(const Segment& segment, ui
uint64_t last_offset_sections = last_offset_section();
uint64_t last_offset_segments = last_offset_segment();


uint64_t last_offset = std::max<uint64_t>(last_offset_sections, last_offset_segments);

const auto psize = static_cast<uint64_t>(get_pagesize(*this));
Expand Down Expand Up @@ -413,7 +410,7 @@ Segment* Binary::add_segment<Header::FILE_TYPE::EXEC>(const Segment& segment, ui
new_segment->content(content);

if (header.section_headers_offset() <= new_segment->file_offset() + new_segment->physical_size()) {
header.section_headers_offset(header.section_headers_offset() + new_segment->file_offset() + new_segment->physical_size());
header.section_headers_offset(new_segment->file_offset() + new_segment->physical_size());
}

const auto it_new_segment_place = std::find_if(segments_.rbegin(), segments_.rend(),
Expand Down
46 changes: 43 additions & 3 deletions tests/elf/test_i872.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
#!/usr/bin/env python

import subprocess
from subprocess import Popen
import pytest
import stat

import lief
import pathlib

from utils import get_sample, is_linux, is_x86_64
from subprocess import Popen
from pathlib import Path

from utils import get_sample, is_linux, is_x86_64, glibc_version

def test_issue_872(tmp_path):
tmp = pathlib.Path(tmp_path)
Expand Down Expand Up @@ -190,3 +191,42 @@ def test_docker_init(tmp_path, mode):
with Popen([outpath.as_posix(), "--version"], **popen_args) as proc: # type: ignore
stdout = proc.stdout.read()
assert "tini version 0.19.0" in stdout, f"Error: {stdout}"

@pytest.mark.skipif(not (is_linux() and is_x86_64()), reason="incompatible env")
@pytest.mark.parametrize("mode", [
lief.ELF.Binary.PHDR_RELOC.SEGMENT_GAP,
lief.ELF.Binary.PHDR_RELOC.BSS_END
])
def test_python312d(tmp_path: Path, mode: lief.ELF.Binary.PHDR_RELOC):
sample = Path(get_sample("ELF/python3.12d"))

elf: lief.ELF.Binary = lief.ELF.parse(sample)
elf.relocate_phdr_table(mode)

segment = lief.ELF.Segment()
segment.type = lief.ELF.Segment.TYPE.LOAD
segment.content = [1] * 12

elf.add(segment)
outpath = tmp_path / "modified.elf"
elf.write(outpath.as_posix())

delta_size = outpath.stat().st_size - sample.stat().st_size

if mode == lief.ELF.Binary.PHDR_RELOC.SEGMENT_GAP:
assert delta_size < 0x2000

if mode == lief.ELF.Binary.PHDR_RELOC.BSS_END:
assert delta_size < 0x6000

if glibc_version() >= (2, 38):
outpath.chmod(outpath.stat().st_mode | stat.S_IEXEC)
popen_args = {
"stdout": subprocess.PIPE,
"stderr": subprocess.STDOUT,
"universal_newlines": True
}

with Popen([outpath.as_posix(), "--version"], **popen_args) as proc: # type: ignore
stdout = proc.stdout.read()
assert "Python 3.12.7" in stdout, f"Error: {stdout}"

0 comments on commit 68caa32

Please sign in to comment.