diff --git a/Userland/Libraries/LibELF/ELFBuild.cpp b/Userland/Libraries/LibELF/ELFBuild.cpp new file mode 100644 index 00000000000..04724e8f52a --- /dev/null +++ b/Userland/Libraries/LibELF/ELFBuild.cpp @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2023, Jesús Lapastora + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +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::max()); + auto const offset = static_cast(m_data.size()); + + auto const final_size = m_data.size() + str.length() + 1; + VERIFY(final_size < NumericLimits::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 build_elf_image(u64 shstrndx, Elf64_Quarter image_type, ReadonlySpan
sections) +{ + Checked final_image_size = sizeof(Elf64_Ehdr); + Vector 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::create(final_image_size.value())); + + { + auto section_headers = Span { + reinterpret_cast(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(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; +} +}; diff --git a/Userland/Libraries/LibELF/ELFBuild.h b/Userland/Libraries/LibELF/ELFBuild.h new file mode 100644 index 00000000000..b5f16c6b0ac --- /dev/null +++ b/Userland/Libraries/LibELF/ELFBuild.h @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2023, Jesús Lapastora + * + * 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 +#include +#include +#include + +namespace ELF { + +// Represents an ELF Section that is optionally bound to some data. +struct Section { + Elf64_Shdr header; + Optional 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: +//
+// +// 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 build_elf_image(u64 shstrndx, Elf64_Quarter image_type, ReadonlySpan
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
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 + Index empend(Args&&... args) noexcept + { + auto const index = m_sections.size(); + m_sections.empend(forward(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 + 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 + 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 + Index build(ReadonlySpan 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 + void build_at(Index at, ReadonlySpan typed_data, Builder header_builder) + { + Elf64_Shdr header {}; + header.sh_entsize = sizeof(T); + header_builder(static_cast(header)); + ReadonlyBytes data = ReadonlyBytes { + reinterpret_cast(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
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 m_data; +}; + +};