LibELF: Add builders to help with creating ELF images

Introduces new builders, mainly `SectionTable` and `StringTable`, and a
final `build_elf_image` to merge everything into a single memory image.

Each of the builders are fully detached from one another, although
StringTable provides an extra API to remove steps when using it with a
SectionTable.

This automates the part of figuring out and properly writing offsets to
headers, and making sure all required data is properly copied and
referenced in the final image.
This commit is contained in:
Jesús (gsus) Lapastora 2023-11-13 19:57:25 +01:00 committed by Andrew Kaster
parent bc70144df1
commit 48a9d0ede8
Notes: sideshowbarker 2024-07-17 05:18:58 +09:00
2 changed files with 314 additions and 0 deletions

View file

@ -0,0 +1,136 @@
/*
* Copyright (c) 2023, Jesús Lapastora <cyber.gsuscode@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibELF/ELFBuild.h>
namespace ELF {
SectionTable::Index StringTable::emit_into_builder(u32 name_index, SectionTable& builder) const noexcept
{
return builder.append(emit_section(name_index));
}
Section StringTable::emit_section(u32 name_index) const noexcept
{
Elf64_Shdr header {};
header.sh_name = name_index;
header.sh_type = SHT_STRTAB;
header.sh_flags = 0;
header.sh_addr = 0;
header.sh_link = 0;
header.sh_info = 0;
header.sh_entsize = 0;
header.sh_addralign = 0;
return Section(m_data.span(), header);
}
u32 StringTable::insert(StringView str) noexcept
{
// The offsets for sh_name and st_name are 32-bit unsigned integers, so it
// won't make sense to address a string table bigger than what u32 can
// provide.
VERIFY(m_data.size() < NumericLimits<u32>::max());
auto const offset = static_cast<u32>(m_data.size());
auto const final_size = m_data.size() + str.length() + 1;
VERIFY(final_size < NumericLimits<u32>::max());
m_data.ensure_capacity(m_data.size() + str.length() + 1);
for (auto ch : str) {
VERIFY(ch != 0);
m_data.unchecked_append(ch);
}
m_data.append(0);
return offset;
}
FixedArray<u8> build_elf_image(u64 shstrndx, Elf64_Quarter image_type, ReadonlySpan<Section> sections)
{
Checked<u64> final_image_size = sizeof(Elf64_Ehdr);
Vector<u64> section_offsets;
section_offsets.ensure_capacity(sections.size());
auto const sections_begin = final_image_size.value_unchecked();
final_image_size += sizeof(Elf64_Shdr) * sections.size();
for (auto const& section : sections) {
auto const offset = final_image_size.value();
section_offsets.unchecked_append(offset);
if (section.data.has_value()) {
final_image_size += section.data.value().size();
}
}
auto image = MUST(FixedArray<u8>::create(final_image_size.value()));
{
auto section_headers = Span<Elf64_Shdr> {
reinterpret_cast<Elf64_Shdr*>(image.span().offset_pointer(sections_begin)),
sections.size(),
};
for (size_t i = 0; i < sections.size(); ++i) {
section_headers[i] = sections[i].header;
section_headers[i].sh_offset = section_offsets[i];
if (sections[i].data.has_value()) {
auto const data = sections[i].data.value();
auto const data_in_elf = image.span().slice(section_offsets[i], data.size());
data.copy_to(data_in_elf);
section_headers[i].sh_size = data.size();
}
}
}
{
auto* const final_elf_hdr = reinterpret_cast<Elf64_Ehdr*>(image.data());
final_elf_hdr->e_ident[EI_MAG0] = 0x7f;
final_elf_hdr->e_ident[EI_MAG1] = 'E';
final_elf_hdr->e_ident[EI_MAG2] = 'L';
final_elf_hdr->e_ident[EI_MAG3] = 'F';
final_elf_hdr->e_ident[EI_CLASS] = ELFCLASS64;
// FIXME: This is platform-dependent. Any big-endian host will write the
// data in MSB format, so the EI_DATA field should be set to
// ELFDATA2MSB.
final_elf_hdr->e_ident[EI_DATA] = ELFDATA2LSB;
final_elf_hdr->e_ident[EI_VERSION] = EV_CURRENT;
// FIXME: This is platform-dependent. The host must set the OSABI to the
// one of the image target.
final_elf_hdr->e_ident[EI_OSABI] = ELFOSABI_SYSV;
final_elf_hdr->e_ident[EI_ABIVERSION] = 0;
auto padding = Bytes {
&final_elf_hdr->e_ident[EI_PAD],
EI_NIDENT - EI_PAD,
};
padding.fill(0);
final_elf_hdr->e_type = image_type;
// FIXME: This is platform-dependent. This must be set to the host
// architecture.
final_elf_hdr->e_machine = EM_AMD64;
final_elf_hdr->e_version = EV_CURRENT;
// Currently segments aren't supported, hence no program headers.
// FIXME: Update program header info on ELF header when adding segment
// information.
final_elf_hdr->e_phoff = 0;
final_elf_hdr->e_phnum = 0;
final_elf_hdr->e_phentsize = 0;
final_elf_hdr->e_shoff = sections_begin;
final_elf_hdr->e_shnum = sections.size();
final_elf_hdr->e_shentsize = sizeof(Section::header);
// FIXME: This is platform-dependent. The flags field should be in sync
// with the architecture flags assumed in the code sections, otherwise
// instructions may be misinterpreted.
final_elf_hdr->e_flags = 0;
final_elf_hdr->e_ehsize = sizeof(*final_elf_hdr);
final_elf_hdr->e_shstrndx = shstrndx;
}
return image;
}
};

View file

@ -0,0 +1,178 @@
/*
* Copyright (c) 2023, Jesús Lapastora <cyber.gsuscode@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
// Collection of utilities to produce an in-memory ELF file in the same format
// as the host.
#include <AK/FixedArray.h>
#include <AK/Span.h>
#include <AK/Vector.h>
#include <LibELF/ELFABI.h>
namespace ELF {
// Represents an ELF Section that is optionally bound to some data.
struct Section {
Elf64_Shdr header;
Optional<ReadonlyBytes> data {};
explicit Section(Elf64_Shdr header)
: header(header)
{
}
Section(ReadonlyBytes data, Elf64_Shdr header)
: header(header)
, data(data)
{
}
};
// Receives a list of sections, and writes the following layout:
// <elf layout> <section headers> <section data>
//
// Both the section headers & the data for those sections will be written in the
// exact order as they appear in the list.
// If a `Section` contains data, then its `sh_offset` is set to the offset in
// the final image, and `sh_size` to the size of the specified data. `Section`s
// that do not contain data will have their `sh_offset` set to the end offset of
// the section that comes right before them.
//
// Notes on the ELF Header:
// The elf header is mostly filled by this function. It needs help in a couple
// of fields: `e_shstrndx` and `e_type`.
//
// - `shstrndx` is the index of the `Section` that contains the section name
// string table.
// - `image_type` is the image file type: ET_CORE, ET_REL, ET_EXEC, etc.
FixedArray<u8> build_elf_image(u64 shstrndx, Elf64_Quarter image_type, ReadonlySpan<Section> sections);
// Takes care of tracking section header indices and their order
struct SectionTable {
struct Index {
u64 index;
constexpr explicit Index(u64 index)
: index(index)
{
}
constexpr u64 raw_index() const noexcept { return index; }
};
ReadonlySpan<Section> span() const noexcept { return m_sections.span(); }
// Appends a default-intialized header with no data. The client is
// responsible for initializing the header before producing the final image.
Index reserve() noexcept
{
return append(Section(Elf64_Shdr()));
}
// Appends a Section and returns the index to refer to it.
Index append(Section section) noexcept
{
auto const index = m_sections.size();
m_sections.append(move(section));
return Index(index);
}
template<typename... Args>
Index empend(Args&&... args) noexcept
{
auto const index = m_sections.size();
m_sections.empend(forward<Args>(args)...);
return Index(index);
}
// Calls `header_builder` with a reference to the Section header, so that
// the builder can initialize it.
// Returns the index for the section.
template<typename Builder>
Index build_nobits(Builder header_builder)
{
auto index = reserve();
build_nobits_at(index, move(header_builder));
return index;
}
// Creates a null section header. Useful for avoiding index 0 for the text
// section, since if we use 0 for its index then symbols that relate to
// .text will be misinterpreted as related to an 'undefined' section.
Index build_null()
{
Elf64_Shdr header {};
header.sh_type = SHT_NULL;
header.sh_name = 0;
return empend(header);
}
// Same as `build_nobits`, but writes an already reserved header instead of
// creating a new one.
template<typename Builder>
void build_nobits_at(Index at, Builder header_builder)
{
Elf64_Shdr header {};
header.sh_type = SHT_NOBITS;
header_builder(header);
new (&m_sections[at.raw_index()]) Section(header);
}
// Reinterprets `typed_data` as a byte slice, and calls `header_builder`
// with a reference to the Section header to be initialized.
// Sets the header's `sh_entsize` to `sizeof(T)` before calling the builder,
// so it can be overridden if required.
// Returns the index for the section.
template<typename T, typename Builder>
Index build(ReadonlySpan<T> typed_data, Builder header_builder)
{
auto index = reserve();
build_at(index, move(typed_data), move(header_builder));
return index;
}
// Same as `build`, but writes an already reserved header instead of
// creating a new one.
template<typename T, typename Builder>
void build_at(Index at, ReadonlySpan<T> typed_data, Builder header_builder)
{
Elf64_Shdr header {};
header.sh_entsize = sizeof(T);
header_builder(static_cast<Elf64_Shdr&>(header));
ReadonlyBytes data = ReadonlyBytes {
reinterpret_cast<u8 const*>(typed_data.offset(0)),
typed_data.size() * sizeof(T),
};
new (&m_sections[at.raw_index()]) Section(data, header);
}
// Makes header editing available after construction. The reference is valid
// until another header is added.
Elf64_Shdr& header_at(Index index) noexcept { return m_sections[index.raw_index()].header; }
private:
Vector<Section> m_sections;
};
struct StringTable {
// Inserts the given string into the table, giving back the offset it begins
// at. The string must not contain any zeroes.
u32 insert(StringView str) noexcept;
// Emits the section information for the current state, so that it can be
// merged into an ELF image.
Section emit_section(u32 name_index) const noexcept;
// Like `emit_section`, but writes the section directly into the builder.
// Returns the index for the section.
SectionTable::Index emit_into_builder(u32 name_index, SectionTable& builder) const noexcept;
private:
Vector<u8> m_data;
};
};