Complete reference for every public item in
object-lang0.2, with examples. Status: pre-1.0. The surface is designed across the 0.x series and frozen at1.0, after a real consumer has driven it end to end. See../dev/ROADMAP.md.
Copyright © 2026 James Gober.
- Overview
- Installation
- Quick start
- Concepts
- The
elfmodule ObjectArchitectureFileKindSectionId,SymbolIdSectionSectionKindSectionFlagsSymbolSymbolKindBindingVisibilitySymbolSectionRelocationRelocationKindX86_64RelocAarch64RelocRelocationTargetLimitsModelErrorSectionProblemSymbolProblemRelocationProblemWriteErrorReadError- Feature flags
- Not supported yet
object-lang turns a format-neutral description of an object file into the bytes of a
real file format, and back. Version 0.2 writes and reads ELF64, little-endian, for
x86-64 and AArch64: relocatable objects (ET_REL) for a linker, and static
executables (ET_EXEC) for the Linux kernel.
| Item | Kind | Purpose |
|---|---|---|
elf |
module | ELF writer, reader, and the one-call executable builder. |
Object |
struct | One object file or executable. |
Architecture |
enum | x86-64 or AArch64. |
FileKind |
enum | Relocatable object or executable. |
SectionId, SymbolId |
structs | Dense ids: positions in the object's lists. |
Section |
struct | Bytes (or a BSS size), kind, flags, alignment, address, relocations. |
SectionKind |
enum | What a section holds. |
SectionFlags |
struct | How a section is used (a bit set). |
Symbol |
struct | A named address. |
SymbolKind, Binding, Visibility, SymbolSection |
enums | A symbol's properties. |
Relocation |
struct | A place to patch. |
RelocationKind, X86_64Reloc, Aarch64Reloc |
enums | How to patch it. |
RelocationTarget |
enum | Whose address to patch in. |
Limits |
struct | Budgets for reading untrusted files. |
ModelError, SectionProblem, SymbolProblem, RelocationProblem |
enums | Rules an object breaks. |
WriteError |
enum | Why a file could not be written. |
ReadError |
enum | Why bytes could not be read. |
[dependencies]
object-lang = "0.2"For no_std targets (the crate needs only alloc):
[dependencies]
object-lang = { version = "0.2", default-features = false }use object_lang::{
Architecture, Object, Relocation, RelocationTarget, Section, SectionKind, Symbol,
X86_64Reloc,
};
// The one-call path: machine code in, a runnable static executable out.
let exit42 = [0xbf, 0x2a, 0, 0, 0, 0xb8, 0x3c, 0, 0, 0, 0x0f, 0x05];
let program = object_lang::elf::executable(Architecture::X86_64, &exit42)?;
assert_eq!(object_lang::elf::read(&program)?.entry(), 0x40_1000);
// The general path: describe an object, write it, read it back.
let mut obj = Object::relocatable(Architecture::X86_64);
let text = obj.add_section(Section::new(".text", SectionKind::Text).with_data(vec![0xe8, 0, 0, 0, 0, 0xc3]));
obj.add_symbol(Symbol::function("main", text, 0, 6));
let exit = obj.add_symbol(Symbol::undefined("exit"));
obj.add_relocation(text, Relocation::new(1, X86_64Reloc::Plt32, RelocationTarget::Symbol(exit), -4))?;
let bytes = object_lang::elf::write(&obj)?;
assert_eq!(object_lang::elf::read(&bytes)?, obj);
# Ok::<(), Box<dyn std::error::Error>>(())A relocatable object is what a compiler produces for a linker: sections at no fixed address (normally zero), symbol values that are offsets within their section, and relocations still to be applied. An executable is fully linked: every loadable section has its final address, symbol values are addresses, nothing is left to relocate, and execution starts at the entry point, which must lie inside an executable section.
| Relocatable | Executable | |
|---|---|---|
| Section address | as given (normally 0) | final load address |
| Symbol value | offset in its section | address |
| Relocations | allowed | refused |
| Undefined symbols | allowed | weak only |
| Common symbols | allowed | refused |
| Entry point | as given (normally 0) | must be inside an executable section |
| Loadable sections | may overlap (they are not placed yet) | must not overlap |
A section's SectionKind says what it holds; its SectionFlags say
how it is used. Section::new gives each kind its required flags; extra flags
(TLS on data, MERGE/STRINGS on constants, ALLOC on a note) are added with
with_flags. For the four kinds stored as plain bytes, the flags alone determine the
kind — with ALLOC, EXEC means code, else WRITE means data, else read-only data,
and without ALLOC the section is Other — so a file read back gives the exact kind
written.
use object_lang::{Section, SectionFlags, SectionKind};
let strings = Section::new(".rodata.str1.1", SectionKind::ReadOnlyData)
.with_flags(SectionFlags::ALLOC | SectionFlags::MERGE | SectionFlags::STRINGS)
.with_entry_size(1);
assert!(SectionKind::ReadOnlyData.allows(strings.flags()));
// Writable flags on read-only data would read back as data, so they are refused.
assert!(!SectionKind::ReadOnlyData.allows(SectionFlags::ALLOC | SectionFlags::WRITE));A symbol is defined in a section, or is undefined (another object
defines it), absolute (a constant), or common (a
tentative definition the linker allocates). Its value is an offset (relocatable), an
address (executable), or an alignment (common). A defined symbol must lie inside its
section: value + size may reach the section's end but not pass it.
Writers may reorder symbols: ELF puts every local symbol before every other. A file read back lists them in the format's order, so ids issued while building may differ from ids after reading; names and properties are preserved.
use object_lang::{Architecture, Binding, Object, Section, SectionKind, Symbol};
let mut obj = Object::relocatable(Architecture::X86_64);
let text = obj.add_section(Section::new(".text", SectionKind::Text).with_data(vec![0xc3; 2]));
obj.add_symbol(Symbol::function("api", text, 0, 1));
obj.add_symbol(Symbol::function("helper", text, 1, 1).with_binding(Binding::Local));
let back = object_lang::elf::read(&object_lang::elf::write(&obj)?)?;
let names: Vec<&str> = back.symbols().iter().map(|s| s.name.as_str()).collect();
assert_eq!(names, ["helper", "api"]); // locals first
# Ok::<(), Box<dyn std::error::Error>>(())A relocation lives in the section it patches. It names an offset, a kind (which says how many bytes are patched and with what formula), a target (a symbol, or the start of a section), and an explicit addend. Relocation kinds belong to one architecture; an object may only carry its own. Instruction relocations on AArch64 must be 4-byte aligned.
use object_lang::{Aarch64Reloc, Architecture, Object, Relocation, RelocationTarget, Section, SectionKind, Symbol};
let mut obj = Object::relocatable(Architecture::Aarch64);
let text = obj.add_section(Section::new(".text", SectionKind::Text).with_data(vec![0; 8]));
let data = obj.add_section(Section::new(".data", SectionKind::Data).with_data(vec![0; 16]));
// adrp x0, data+8 ; add x0, x0, :lo12:data+8
let at = RelocationTarget::Section(data);
obj.add_relocation(text, Relocation::new(0, Aarch64Reloc::AdrPrelPgHi21, at, 8))?;
obj.add_relocation(text, Relocation::new(4, Aarch64Reloc::AddAbsLo12Nc, at, 8))?;
assert!(obj.validate().is_ok());
# Ok::<(), object_lang::ModelError>(())Building never fails (except for the few Section builders that compute
offsets); Object::validate checks every rule at once and returns the
first violation as a ModelError. Writers validate before writing, and
readers validate what they decode, so an invalid object is never written and an invalid
file is never accepted.
ELF64, little-endian, for x86-64 (EM_X86_64) and AArch64 (EM_AARCH64).
pub fn write(object: &Object) -> Result<Vec<u8>, WriteError>Writes an object as an ELF file. The object is validated first; the output is deterministic (the same object always gives the same bytes).
Errors: WriteError::Invalid for a model rule; for relocatable
objects, ReservedSectionName if a section is named .note.GNU-stack; for
executables, SegmentsSharePage, NoRoomForHeaders, and
Unsupported for thread-local sections; TooLarge if a table
outgrows ELF's 32-bit fields.
use object_lang::{Architecture, Object, Section, SectionKind, Symbol};
let mut obj = Object::relocatable(Architecture::Aarch64);
let text = obj.add_section(Section::new(".text", SectionKind::Text).with_data(vec![0xc0, 0x03, 0x5f, 0xd6]));
obj.add_symbol(Symbol::function("ret", text, 0, 4));
let bytes = object_lang::elf::write(&obj)?;
assert_eq!(&bytes[..4], b"\x7fELF");
assert_eq!(object_lang::elf::write(&obj)?, bytes); // deterministic
# Ok::<(), object_lang::WriteError>(())pub fn write_into(object: &Object, out: &mut Vec<u8>) -> Result<(), WriteError>As write, appending to out so a buffer can be reused. On error out is
left exactly as it was.
use object_lang::{Architecture, Object};
let mut buffer = Vec::with_capacity(4096);
object_lang::elf::write_into(&Object::relocatable(Architecture::X86_64), &mut buffer)?;
let first = buffer.len();
buffer.clear();
object_lang::elf::write_into(&Object::relocatable(Architecture::X86_64), &mut buffer)?;
assert_eq!(buffer.len(), first);
# Ok::<(), object_lang::WriteError>(())pub fn read(bytes: &[u8]) -> Result<Object, ReadError>Reads an ELF file with the default Limits. Never panics, whatever the bytes.
Errors: a ReadError naming the first problem.
use object_lang::{Architecture, FileKind, ReadError};
let exe = object_lang::elf::executable(Architecture::Aarch64, &[0x00, 0x00, 0x00, 0x14])?;
let obj = object_lang::elf::read(&exe)?;
assert_eq!((obj.kind(), obj.architecture()), (FileKind::Executable, Architecture::Aarch64));
assert_eq!(object_lang::elf::read(b"\x7fELF"), Err(ReadError::Truncated { what: "file header" }));
# Ok::<(), Box<dyn std::error::Error>>(())pub fn read_with_limits(bytes: &[u8], limits: &Limits) -> Result<Object, ReadError>As read, with the caller's budgets.
use object_lang::{Architecture, Limits, Object, ReadError, Symbol};
let mut obj = Object::relocatable(Architecture::X86_64);
for i in 0..10 {
obj.add_symbol(Symbol::undefined(format!("s{i}")));
}
let bytes = object_lang::elf::write(&obj)?;
let mut limits = Limits::default();
limits.max_symbols = 5;
assert_eq!(
object_lang::elf::read_with_limits(&bytes, &limits),
Err(ReadError::LimitExceeded { limit: "max_symbols" }),
);
# Ok::<(), object_lang::WriteError>(())pub fn executable(arch: Architecture, code: &[u8]) -> Result<Vec<u8>, WriteError>The one-call path: a static executable whose .text (16-byte aligned) holds code at
code_address(arch), with execution starting at its first byte,
marked by a global _start. The code must be position-dependent machine code that
needs no relocation.
Errors: WriteError::Invalid wrapping
EntryNotExecutable if code is empty.
use object_lang::Architecture;
// exit(42) on Linux AArch64: mov x0, #42; mov x8, #93; svc #0
let code = [0x40, 0x05, 0x80, 0xd2, 0xa8, 0x0b, 0x80, 0xd2, 0x01, 0x00, 0x00, 0xd4];
let program = object_lang::elf::executable(Architecture::Aarch64, &code)?;
let obj = object_lang::elf::read(&program)?;
assert_eq!(obj.symbols()[0].name, "_start");
assert!(object_lang::elf::executable(Architecture::Aarch64, &[]).is_err());
# Ok::<(), Box<dyn std::error::Error>>(())pub const fn code_address(arch: Architecture) -> u64Where executable loads its code: 0x40_0000 + arch.page_size(), so
the file headers are mapped on the page(s) at 0x40_0000 just below.
use object_lang::{Architecture, elf};
assert_eq!(elf::code_address(Architecture::X86_64), 0x40_1000);
assert_eq!(elf::code_address(Architecture::Aarch64), 0x41_0000);Relocatable objects. Section header i + 1 is the model's section i. After the
content sections come one .rela<name> (SHT_RELA, SHF_INFO_LINK, entry size 24) per
section with relocations, an empty .note.GNU-stack (SHF_EXECINSTR when
executable_stack is set), .symtab, .symtab_shndx when a symbol's section
index exceeds 16 bits, .strtab, and .shstrtab. File offsets of contents are aligned to
the section's alignment, capped at 4 KiB.
The symbol table holds the null symbol, then one local STT_SECTION symbol for each
section a relocation targets directly (in section order), then the object's local
symbols, then the rest, each group in model order; sh_info is the first non-local
index. Names share bytes when one is a suffix of another. More than 65,279 sections use
ELF's extended numbering: e_shnum = 0 and e_shstrndx = SHN_XINDEX, with the real values
in section header 0, and SHN_XINDEX symbols resolved through .symtab_shndx.
Executables. The loadable sections, sorted by address, are grouped into PT_LOAD
segments: a new segment starts where permissions change (R, RW, RX, RWX), where
initialized data follows BSS, or where a gap of a page or more opens. A new segment must
start on a page the previous one does not touch, or the write fails with
SegmentsSharePage. Segments are aligned to the
page size with file offsets congruent to their addresses. The ELF header
and program headers are mapped read-only on the page(s) directly below the lowest
section (so AT_PHDR is valid). A PT_GNU_STACK header (RW, or RWX with
executable_stack) follows. There is no PT_INTERP and no PT_DYNAMIC. Section
headers and the symbol table are written as for relocatable objects, so tools can name
what they show.
ELF64, little-endian, EV_CURRENT, OS ABI NONE or GNU, ABI version 0, e_flags
0, type ET_REL or ET_EXEC, machine EM_X86_64 or EM_AARCH64, with a section
header table. Section types PROGBITS, NOBITS, NOTE, INIT_ARRAY, FINI_ARRAY,
PREINIT_ARRAY, SYMTAB (one), STRTAB (each referenced), RELA (relocatable only),
SYMTAB_SHNDX, and on x86-64 X86_64_UNWIND (read as read-only data). Section flags
WRITE, ALLOC, EXECINSTR, MERGE, STRINGS, TLS. Symbol types NOTYPE,
OBJECT, FUNC, SECTION, FILE, TLS; bindings LOCAL, GLOBAL, WEAK;
visibilities DEFAULT, HIDDEN, PROTECTED. Relocation types: exactly the
x86-64 and AArch64 kinds. Program header types
PT_NULL, PT_LOAD, PT_GNU_STACK, and PT_PHDR (ignored).
Dropped by rule, because the model derives them: STT_SECTION symbols (relocations
against them become RelocationTarget::Section),
.note.GNU-stack in relocatable objects (it becomes executable_stack; when absent,
executable_stack is true, as GNU ld assumes), PT_GNU_STACK (likewise for
executables), PT_PHDR, and LLVM's .llvm_addrsig hint.
Checked for executables: every loadable section lies inside one PT_LOAD whose
permissions cover its flags, at the file offset the segment implies; segments are sorted,
do not overlap, and have offsets congruent to addresses; the entry point is in an
executable segment.
use object_lang::{Architecture, Object, ReadError};
let mut bytes = object_lang::elf::write(&Object::relocatable(Architecture::X86_64))?;
bytes[0x12] = 40; // e_machine = EM_ARM
assert_eq!(
object_lang::elf::read(&bytes),
Err(ReadError::Unsupported { what: "machine", value: 40 }),
);
# Ok::<(), object_lang::WriteError>(())#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Object { /* private */ }One object file or executable: a FileKind, an
Architecture, Sections, Symbols, an entry
point, and whether the program asks for an executable stack.
| Method | Description |
|---|---|
new(kind, arch) |
An empty object; entry 0, stack not executable. |
relocatable(arch), executable(arch) |
Shorthands for new. |
kind(), architecture() |
What it is and what it targets. |
entry(), set_entry(address) |
The entry point. |
executable_stack(), set_executable_stack(bool) |
Whether the program asks for an executable stack (default false). |
add_section(section) -> SectionId |
Appends a section. |
sections() -> &[Section] |
All sections; a SectionId indexes this slice. |
section(id), section_mut(id) |
One section, or None. |
section_by_name(name) |
The first section with this name (linear search). |
add_symbol(symbol) -> SymbolId |
Appends a symbol. |
symbols() -> &[Symbol] |
All symbols; a SymbolId indexes this slice. |
symbol(id), symbol_mut(id) |
One symbol, or None. |
symbol_by_name(name) |
The first symbol with this name (linear search). |
add_relocation(section, relocation) |
Adds a relocation to a section; Err(ModelError::UnknownSection) if there is no such section. |
validate() |
Checks every rule; see below. |
use object_lang::{Architecture, Binding, Object, Section, SectionKind, Symbol};
let mut obj = Object::relocatable(Architecture::X86_64);
let data = obj.add_section(Section::new(".data", SectionKind::Data).with_data(vec![0; 8]));
let counter = obj.add_symbol(Symbol::data("counter", data, 0, 8));
if let Some(sym) = obj.symbol_mut(counter) {
sym.binding = Binding::Weak;
}
assert_eq!(obj.symbol_by_name("counter"), Some(counter));
assert_eq!(obj.section(data).map(|s| s.size()), Some(8));
assert!(!obj.executable_stack());pub fn validate(&self) -> Result<(), ModelError>Checks every rule of the model and returns the first violation. The rules are the
problems listed under SectionProblem,
SymbolProblem, and RelocationProblem, plus
two for executables (loadable sections may not overlap; the entry point must be inside an
executable section) and a size limit (at most 2³¹ − 1 sections and symbols). Cost: one
pass, plus a sort of the loadable sections for executables.
use object_lang::{Architecture, ModelError, Object, Section, SectionKind};
let mut exe = Object::executable(Architecture::X86_64);
exe.add_section(Section::new(".text", SectionKind::Text).with_address(0x40_1000).with_data(vec![0xc3]));
exe.set_entry(0x40_2000);
assert_eq!(exe.validate(), Err(ModelError::EntryNotExecutable { entry: 0x40_2000 }));
exe.set_entry(0x40_1000);
assert!(exe.validate().is_ok());#[non_exhaustive]
pub enum Architecture { X86_64, Aarch64 }The instruction set. page_size() is the page size executables are laid out with:
4 KiB on x86-64, 64 KiB on AArch64 (correct for 4, 16, and 64 KiB kernels, and what GNU
ld and LLVM lld default to). A linker placing sections for an executable must start each
change of permissions on a fresh page of this size.
use object_lang::Architecture;
assert_eq!(Architecture::X86_64.page_size(), 4096);
assert_eq!(Architecture::Aarch64.page_size(), 65536);#[non_exhaustive]
pub enum FileKind { Relocatable, Executable }What a file is for. See Relocatable objects and executables.
use object_lang::{Architecture, FileKind, Object};
assert_eq!(Object::executable(Architecture::X86_64).kind(), FileKind::Executable);#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
pub struct SectionId(/* private */);
pub struct SymbolId(/* private */);Dense ids: a section's or symbol's position in Object::sections or
Object::symbols. index() gives the position; new(index) makes an id from
one. An id that names nothing in the object it is used with is caught by
validate (or refused by section/symbol), never trusted.
use object_lang::{Architecture, Object, Section, SectionId, SectionKind, Symbol, SymbolId};
let mut obj = Object::relocatable(Architecture::X86_64);
let text = obj.add_section(Section::new(".text", SectionKind::Text));
let f = obj.add_symbol(Symbol::undefined("f"));
assert_eq!((text, f), (SectionId::new(0), SymbolId::new(0)));
assert_eq!(text.index(), 0);#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Section { /* private */ }| Method | Description |
|---|---|
new(name, kind) |
An empty section with the kind's default flags, alignment 1, address 0. |
with_flags(flags) |
Replaces the flags. |
with_align(align) |
Sets the alignment (a power of two, checked by validate). |
with_entry_size(size) |
Size of fixed-size entries; required (non-zero) with MERGE. |
with_address(address) |
Sets the address (an executable's load address; must be a multiple of the alignment). |
with_data(bytes) |
Replaces the contents (not for BSS). |
with_bss_size(size) |
Sets a BSS section's size (only for BSS). |
append(bytes, align) -> Result<u64, ModelError> |
Pads to align, appends, returns the offset; raises the section's alignment. Errors: InvalidAlignment, AppendToBss, SizeOverflow. |
reserve(size, align) -> Result<u64, ModelError> |
Reserves size zero bytes at align (grows a BSS size, or appends zeros); returns the offset. Errors: InvalidAlignment, SizeOverflow. Nothing changes on error. |
add_relocation(relocation) |
Adds a relocation that patches this section. |
name(), kind(), flags(), align(), entry_size(), address() |
Accessors. |
data() -> &[u8] |
The bytes (empty for BSS). |
size() -> u64 |
The size in memory: the data length, or the BSS size. |
relocations() -> &[Relocation] |
In the order added. |
use object_lang::{ModelError, Section, SectionKind};
let mut text = Section::new(".text", SectionKind::Text);
assert_eq!(text.append(&[0x90], 1)?, 0);
assert_eq!(text.append(&[0xc3], 16)?, 16); // padded to the requested alignment
assert_eq!((text.size(), text.align()), (17, 16));
let mut bss = Section::new(".bss", SectionKind::Bss);
assert_eq!(bss.reserve(100, 8)?, 0);
assert_eq!(bss.reserve(8, 64)?, 128);
assert_eq!(bss.size(), 136);
assert_eq!(bss.append(&[1], 1), Err(ModelError::AppendToBss));
# Ok::<(), ModelError>(())#[non_exhaustive]
pub enum SectionKind {
Text, Data, ReadOnlyData, Bss, Note, InitArray, FiniArray, PreinitArray, Other,
}| Kind | Contents | ELF type | Required flags | Forbidden flags |
|---|---|---|---|---|
Text |
machine code | PROGBITS |
ALLOC, EXEC |
TLS |
Data |
writable data (TLS: .tdata) |
PROGBITS |
ALLOC, WRITE |
EXEC |
ReadOnlyData |
constants | PROGBITS |
ALLOC |
WRITE, EXEC, TLS |
Bss |
zero-filled, no file bytes (TLS: .tbss) |
NOBITS |
ALLOC |
EXEC, MERGE, STRINGS |
Note |
notes | NOTE |
WRITE, EXEC, TLS, MERGE, STRINGS |
|
InitArray, FiniArray, PreinitArray |
constructor/destructor pointers | INIT_ARRAY, FINI_ARRAY, PREINIT_ARRAY |
ALLOC, WRITE |
EXEC, TLS, MERGE, STRINGS |
Other |
not loaded (comments, metadata, debug info) | PROGBITS |
ALLOC, WRITE, EXEC, TLS |
default_flags() returns the required flags; allows(flags) applies the table;
is_uninitialized() is true for Bss.
use object_lang::{SectionFlags, SectionKind};
assert_eq!(SectionKind::Bss.default_flags(), SectionFlags::ALLOC | SectionFlags::WRITE);
assert!(SectionKind::Bss.allows(SectionFlags::ALLOC | SectionFlags::WRITE | SectionFlags::TLS));
assert!(!SectionKind::Text.allows(SectionFlags::ALLOC));#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct SectionFlags(/* private */);A bit set: ALLOC, WRITE, EXEC, TLS, MERGE, STRINGS. Combine with | (and
|=); query with contains, is_empty; build with empty, union, difference,
from_bits (None for unknown bits); bits gives the raw value. Debug prints the
flag names.
use object_lang::SectionFlags;
let mut flags = SectionFlags::ALLOC;
flags |= SectionFlags::WRITE;
assert!(flags.contains(SectionFlags::ALLOC | SectionFlags::WRITE));
assert_eq!(flags.difference(SectionFlags::WRITE), SectionFlags::ALLOC);
assert_eq!(format!("{flags:?}"), "SectionFlags(ALLOC | WRITE)");
assert_eq!(SectionFlags::from_bits(0xff), None);#[derive(Clone, PartialEq, Eq, Hash, Debug)]
#[non_exhaustive]
pub struct Symbol {
pub name: String,
pub kind: SymbolKind,
pub binding: Binding,
pub visibility: Visibility,
pub section: SymbolSection,
pub value: u64,
pub size: u64,
}The fields are public; build with a constructor, then adjust fields or chain with_*.
| Constructor | Kind | Binding | Section | Value |
|---|---|---|---|---|
new(name, kind, binding, section, value, size) |
as given | as given | as given | as given |
function(name, section, value, size) |
Function |
Global |
the section | offset/address |
data(name, section, value, size) |
Data |
Global |
the section | offset/address |
undefined(name) |
NoType |
Global |
Undefined |
0 |
absolute(name, value) |
NoType |
Global |
Absolute |
the value |
common(name, size, align) |
Data |
Global |
Common |
the alignment |
file(name) |
File |
Local |
Absolute |
0 |
with_binding, with_visibility, and with_kind return a changed copy. Visibility is
Default unless set.
use object_lang::{Binding, Symbol, SymbolKind, SymbolSection, Visibility};
let s = Symbol::undefined("hook").with_binding(Binding::Weak).with_kind(SymbolKind::Function);
assert_eq!((s.binding, s.kind, s.section), (Binding::Weak, SymbolKind::Function, SymbolSection::Undefined));
let hidden = Symbol::absolute("PAGE", 4096).with_visibility(Visibility::Hidden);
assert_eq!(hidden.value, 4096);#[non_exhaustive]
pub enum SymbolKind { NoType, Function, Data, Tls, File }NoType (a label, or an unknown reference), Function, Data, Tls (a thread-local
variable; if defined, in a TLS section), File (the source file name; local, absolute,
value and size zero).
use object_lang::{Symbol, SymbolKind};
assert_eq!(Symbol::file("lib.rs").kind, SymbolKind::File);#[non_exhaustive]
pub enum Binding { Local, Global, Weak }Local symbols are visible only inside the object and must be defined. Global symbols
are visible to the whole link. Weak symbols yield to a global definition, and an
undefined weak reference resolves to zero.
use object_lang::{Binding, Symbol};
assert_eq!(Symbol::undefined("x").with_binding(Binding::Weak).binding, Binding::Weak);#[non_exhaustive]
pub enum Visibility { Default, Hidden, Protected }How far a non-local symbol is exported beyond the module it is linked into: Default
follows the binding, Hidden is not exported, Protected is exported but always binds
locally.
use object_lang::{Symbol, Visibility};
assert_eq!(Symbol::undefined("x").visibility, Visibility::Default);#[non_exhaustive]
pub enum SymbolSection { Undefined, Absolute, Common, Section(SectionId) }Where a symbol is defined: nowhere (another object defines it), as a constant, as a common block (relocatable only; the value is the alignment, a power of two; the binding must be global), or in a section of this object.
use object_lang::{Symbol, SymbolSection};
assert_eq!(Symbol::common("buf", 64, 8).section, SymbolSection::Common);#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
#[non_exhaustive]
pub struct Relocation {
pub offset: u64,
pub kind: RelocationKind,
pub target: RelocationTarget,
pub addend: i64,
}Relocation::new(offset, kind, target, addend) takes the kind as anything convertible to
RelocationKind (an X86_64Reloc or
Aarch64Reloc). The patched bytes, offset .. offset + kind.size(),
must lie inside the section.
use object_lang::{Relocation, RelocationTarget, SymbolId, X86_64Reloc};
let r = Relocation::new(16, X86_64Reloc::Abs64, RelocationTarget::Symbol(SymbolId::new(0)), 8);
assert_eq!((r.offset, r.kind.size(), r.addend), (16, 8, 8));#[non_exhaustive]
pub enum RelocationKind { X86_64(X86_64Reloc), Aarch64(Aarch64Reloc) }architecture() names the architecture the kind belongs to; size() the number of bytes
patched (8 for the 64-bit data kinds, otherwise 4); is_instruction() whether it patches
an instruction field (AArch64 instruction relocations; their offset must be a multiple of
4). From<X86_64Reloc> and From<Aarch64Reloc> are implemented.
use object_lang::{Aarch64Reloc, Architecture, RelocationKind};
let k = RelocationKind::from(Aarch64Reloc::Prel64);
assert_eq!((k.architecture(), k.size(), k.is_instruction()), (Architecture::Aarch64, 8, false));#[non_exhaustive]
pub enum X86_64Reloc { Abs64, Abs32, Abs32Signed, Pc32, Pc64, Plt32, GotPcRel, GotPcRelX, RexGotPcRelX }S is the target address, A the addend, P the patched address, L the PLT entry (the
target itself in a static link), G the GOT entry.
| Variant | ELF | Value | Bytes |
|---|---|---|---|
Abs64 |
R_X86_64_64 (1) |
S + A |
8 |
Pc32 |
R_X86_64_PC32 (2) |
S + A - P |
4 |
Plt32 |
R_X86_64_PLT32 (4) |
L + A - P |
4 |
GotPcRel |
R_X86_64_GOTPCREL (9) |
G + A - P |
4 |
Abs32 |
R_X86_64_32 (10) |
S + A, zero-extended |
4 |
Abs32Signed |
R_X86_64_32S (11) |
S + A, sign-extended |
4 |
Pc64 |
R_X86_64_PC64 (24) |
S + A - P |
8 |
GotPcRelX |
R_X86_64_GOTPCRELX (41) |
G + A - P, relaxable |
4 |
RexGotPcRelX |
R_X86_64_REX_GOTPCRELX (42) |
G + A - P, relaxable, REX prefix |
4 |
use object_lang::{RelocationKind, X86_64Reloc};
assert_eq!(RelocationKind::from(X86_64Reloc::Pc64).size(), 8);#[non_exhaustive]
pub enum Aarch64Reloc {
Abs64, Abs32, Prel64, Prel32, Call26, Jump26, CondBr19, TstBr14, AdrPrelLo21,
AdrPrelPgHi21, AddAbsLo12Nc, Ldst8AbsLo12Nc, Ldst16AbsLo12Nc, Ldst32AbsLo12Nc,
Ldst64AbsLo12Nc, Ldst128AbsLo12Nc, AdrGotPage, Ld64GotLo12Nc,
}Page(x) is x with its low 12 bits cleared.
| Variant | ELF | Value | Patches |
|---|---|---|---|
Abs64 |
R_AARCH64_ABS64 (257) |
S + A |
8 data bytes |
Abs32 |
R_AARCH64_ABS32 (258) |
S + A |
4 data bytes |
Prel64 |
R_AARCH64_PREL64 (260) |
S + A - P |
8 data bytes |
Prel32 |
R_AARCH64_PREL32 (261) |
S + A - P |
4 data bytes |
AdrPrelLo21 |
R_AARCH64_ADR_PREL_LO21 (274) |
S + A - P |
ADR |
AdrPrelPgHi21 |
R_AARCH64_ADR_PREL_PG_HI21 (275) |
Page(S + A) - Page(P) |
ADRP |
AddAbsLo12Nc |
R_AARCH64_ADD_ABS_LO12_NC (277) |
S + A, bits 11:0 |
ADD |
Ldst8AbsLo12Nc |
R_AARCH64_LDST8_ABS_LO12_NC (278) |
S + A, bits 11:0 |
LDRB/STRB |
TstBr14 |
R_AARCH64_TSTBR14 (279) |
S + A - P |
TBZ/TBNZ |
CondBr19 |
R_AARCH64_CONDBR19 (280) |
S + A - P |
B.cond/CBZ/CBNZ |
Jump26 |
R_AARCH64_JUMP26 (282) |
S + A - P |
B |
Call26 |
R_AARCH64_CALL26 (283) |
S + A - P |
BL |
Ldst16AbsLo12Nc |
R_AARCH64_LDST16_ABS_LO12_NC (284) |
S + A, bits 11:1 |
16-bit LDR/STR |
Ldst32AbsLo12Nc |
R_AARCH64_LDST32_ABS_LO12_NC (285) |
S + A, bits 11:2 |
32-bit LDR/STR |
Ldst64AbsLo12Nc |
R_AARCH64_LDST64_ABS_LO12_NC (286) |
S + A, bits 11:3 |
64-bit LDR/STR |
Ldst128AbsLo12Nc |
R_AARCH64_LDST128_ABS_LO12_NC (299) |
S + A, bits 11:4 |
128-bit LDR/STR |
AdrGotPage |
R_AARCH64_ADR_GOT_PAGE (311) |
Page(G) - Page(P) |
ADRP |
Ld64GotLo12Nc |
R_AARCH64_LD64_GOT_LO12_NC (312) |
G, bits 11:3 |
LDR |
use object_lang::{Aarch64Reloc, RelocationKind};
assert!(RelocationKind::from(Aarch64Reloc::Ldst64AbsLo12Nc).is_instruction());#[non_exhaustive]
pub enum RelocationTarget { Symbol(SymbolId), Section(SectionId) }Whose address a relocation uses: a symbol's, or a section's start (the addend picks the
place inside it, which is how compilers refer to unnamed data such as string literals).
ELF writes a section target as a relocation against a local STT_SECTION symbol, which
the reader turns back into a section target.
use object_lang::{RelocationTarget, SectionId};
let start_of_rodata = RelocationTarget::Section(SectionId::new(2));
assert!(matches!(start_of_rodata, RelocationTarget::Section(_)));#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
#[non_exhaustive]
pub struct Limits {
pub max_sections: u32,
pub max_symbols: u32,
pub max_relocations: u64,
pub max_name_len: u32,
pub max_name_bytes: u64,
}Budgets for reading untrusted files; exceeding one is
ReadError::LimitExceeded naming the field. Start from
Limits::default() (or Limits::DEFAULT) and change fields.
| Field | Default | Bounds |
|---|---|---|
max_sections |
1,048,576 | section headers |
max_symbols |
16,777,216 | symbol table entries |
max_relocations |
67,108,864 | relocation records, all sections |
max_name_len |
65,536 | bytes in one name |
max_name_bytes |
268,435,456 (256 MiB) | name bytes copied out, all names |
Independently of these, the reader never copies more section data than the input holds,
however the section headers overlap (LimitExceeded { limit: "section data exceeds the input size" }), and checks every count against the input length before allocating.
use object_lang::Limits;
let mut strict = Limits::default();
strict.max_name_len = 256;
assert_ne!(strict, Limits::DEFAULT);#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
#[non_exhaustive]
pub enum ModelError { /* variants below */ }
impl Display for ModelError {}
impl core::error::Error for ModelError {}A rule an object breaks. Match with a wildcard arm.
| Variant | Meaning | Message |
|---|---|---|
Section { section, problem } |
A section rule. | section #2: alignment is not a power of two |
Symbol { symbol, problem } |
A symbol rule. | symbol #0: a local symbol must be defined |
Relocation { section, index, problem } |
A relocation rule. | relocation #1 of section #0: patches bytes outside its section |
SectionsOverlap { first, second } |
Two loadable sections of an executable overlap. | sections #0 and #1 overlap in memory |
EntryNotExecutable { entry } |
An executable's entry is not in an executable section. | entry point 0x401000 is not inside an executable section |
UnknownSection { section } |
Object::add_relocation with a section the object lacks. |
the object has no section #3 |
InvalidAlignment { align } |
Section::append/reserve with a bad alignment. |
alignment 3 is not a power of two |
AppendToBss |
Section::append on BSS. |
cannot append bytes to a BSS section; reserve space instead |
SizeOverflow |
A section would outgrow the size range or memory. | section size overflows |
TooManyItems |
More than 2³¹ − 1 sections or symbols. | too many sections or symbols to number |
use object_lang::{Architecture, ModelError, Object, Section, SectionKind, SectionProblem};
let mut obj = Object::relocatable(Architecture::X86_64);
let s = obj.add_section(Section::new("bad\0name", SectionKind::Data));
let err = obj.validate().unwrap_err();
assert_eq!(err, ModelError::Section { section: s, problem: SectionProblem::NameContainsNul });
assert_eq!(err.to_string(), "section #0: name contains a NUL byte");#[non_exhaustive]. Each variant is a rule every section must keep.
| Variant | Rule |
|---|---|
NameContainsNul |
The name has no NUL byte. |
BadAlignment |
The alignment is a non-zero power of two. |
FlagsDoNotMatchKind |
The flags are allowed for the kind. |
MergeWithoutEntrySize |
MERGE needs a non-zero entry size. |
ContentsDoNotMatchKind |
BSS has no bytes; other kinds have no BSS size. |
MisalignedAddress |
The address is a multiple of the alignment. |
AddressOverflow |
Address plus size fits in 64 bits. |
use object_lang::{Architecture, ModelError, Object, Section, SectionKind, SectionProblem};
let mut obj = Object::relocatable(Architecture::X86_64);
obj.add_section(Section::new(".bss", SectionKind::Bss).with_data(vec![1]));
assert!(matches!(
obj.validate(),
Err(ModelError::Section { problem: SectionProblem::ContentsDoNotMatchKind, .. })
));#[non_exhaustive]. Each variant is a rule every symbol must keep.
| Variant | Rule |
|---|---|
NameContainsNul |
The name has no NUL byte. |
UnknownSection |
A section-defined symbol's section exists. |
OutOfSection |
value .. value + size lies within the section (offsets in a relocatable object, addresses in an executable). |
LocalUndefined |
Local symbols are defined (not undefined, not common). |
BadFileSymbol |
File symbols are local and absolute, with value and size zero. |
BadCommonSymbol |
Common symbols are global with a power-of-two alignment. |
CommonInExecutable |
Executables have no common symbols. |
UndefinedInExecutable |
Executables have no non-weak undefined symbols. |
TlsOutsideTlsSection |
A defined Tls symbol is in a TLS section. |
use object_lang::{Architecture, ModelError, Object, Symbol, SymbolProblem};
let mut exe = Object::executable(Architecture::X86_64);
exe.add_symbol(Symbol::undefined("printf"));
assert!(matches!(
exe.validate(),
Err(ModelError::Symbol { problem: SymbolProblem::UndefinedInExecutable, .. })
));#[non_exhaustive]. Each variant is a rule every relocation must keep.
| Variant | Rule |
|---|---|
WrongArchitecture |
The kind belongs to the object's architecture. |
OutOfSection |
The patched bytes lie inside the section. |
Misaligned |
Instruction relocations are 4-byte aligned. |
UnknownTarget |
The target symbol or section exists. |
InUninitializedSection |
BSS sections have no relocations. |
InExecutable |
Executables have no relocations. |
use object_lang::{
Architecture, ModelError, Object, Relocation, RelocationProblem, RelocationTarget, Section,
SectionKind, SymbolId, X86_64Reloc,
};
let mut obj = Object::relocatable(Architecture::X86_64);
let text = obj.add_section(Section::new(".text", SectionKind::Text).with_data(vec![0; 4]));
obj.add_relocation(text, Relocation::new(0, X86_64Reloc::Pc32, RelocationTarget::Symbol(SymbolId::new(9)), 0))?;
assert!(matches!(
obj.validate(),
Err(ModelError::Relocation { problem: RelocationProblem::UnknownTarget, .. })
));
# Ok::<(), ModelError>(())#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
#[non_exhaustive]
pub enum WriteError { /* variants below */ }
impl Display for WriteError {}
impl core::error::Error for WriteError {} // source() is the ModelError for Invalid
impl From<ModelError> for WriteError {}| Variant | Meaning |
|---|---|
Invalid(ModelError) |
The object breaks a model rule. |
ReservedSectionName { section } |
A relocatable object has a section named .note.GNU-stack, which the writer emits itself. |
SegmentsSharePage { first, second } |
Two loadable sections of an executable need different segments (different permissions, or data after BSS) but share a page. |
NoRoomForHeaders |
The lowest section of an executable is too close to address zero to map the headers below it. |
Unsupported { what } |
Something this writer does not support yet (thread-local sections in executables; more than 65,534 program headers). |
TooLarge |
The file would exceed ELF's 32-bit table fields or the address space. |
use object_lang::{Architecture, Object, Section, SectionId, SectionKind, WriteError};
let mut exe = Object::executable(Architecture::X86_64);
exe.add_section(Section::new(".text", SectionKind::Text).with_address(0x40_1000).with_data(vec![0xc3]));
exe.add_section(Section::new(".data", SectionKind::Data).with_address(0x40_1100).with_data(vec![0]));
exe.set_entry(0x40_1000);
assert_eq!(
object_lang::elf::write(&exe),
Err(WriteError::SegmentsSharePage { first: SectionId::new(0), second: SectionId::new(1) }),
);#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
#[non_exhaustive]
pub enum ReadError { /* variants below */ }
impl Display for ReadError {}
impl core::error::Error for ReadError {} // source() is the ModelError for Invalid
impl From<ModelError> for ReadError {}| Variant | Meaning |
|---|---|
BadMagic |
Not ELF. |
Truncated { what } |
A structure runs past the end of the input. |
Malformed { what } |
A field breaks the format, or structures contradict each other. |
Unsupported { what, value } |
Valid ELF the model cannot represent; value is the raw field (type, flags, class). |
LimitExceeded { limit } |
A Limits budget (named by its field), or section data larger than the input. |
Invalid(ModelError) |
The decoded object breaks a model rule. |
use object_lang::{Architecture, Object, ReadError};
let bytes = object_lang::elf::write(&Object::relocatable(Architecture::X86_64))?;
let mut big_endian = bytes.clone();
big_endian[5] = 2;
assert!(matches!(object_lang::elf::read(&big_endian), Err(ReadError::Unsupported { value: 2, .. })));
assert_eq!(object_lang::elf::read(&bytes[..100]), Err(ReadError::Truncated { what: "section header table" }));
# Ok::<(), object_lang::WriteError>(())| Feature | Default | Effect |
|---|---|---|
std |
yes | Nothing beyond alloc is used. Without it the crate is no_std; the API is identical. |
Each of these is scheduled in ../dev/ROADMAP.md; until then the
writer or reader refuses it with an error naming it.
- Thread-local storage relocations (
R_X86_64_TPOFF32,R_AARCH64_TLSLE_*, …) andPT_TLSin executables. TLS sections and symbols in relocatable objects are supported. - COMDAT groups (
SHT_GROUP),SHF_LINK_ORDER,SHF_GNU_RETAIN, compressed sections, and IFUNC /STB_GNU_UNIQUEsymbols. - Dynamic linking: shared objects,
PT_INTERP,PT_DYNAMIC, PIE. - Other formats: COFF/PE and Mach-O (v0.5), with import/export tables.
- Debug information and unwind tables (DWARF,
.eh_framegeneration), indebuginfo-tier work; existing.eh_frameand.debug_*sections are carried as plain data.