Skip to content

Commit

Permalink
Add simple write example
Browse files Browse the repository at this point in the history
  • Loading branch information
philipc committed Apr 9, 2024
1 parent e05a658 commit f1e3204
Show file tree
Hide file tree
Showing 2 changed files with 310 additions and 1 deletion.
2 changes: 1 addition & 1 deletion crates/examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ fallible-iterator = { version = "0.3.0", default-features = false, optional = tr
getopts = "0.2"
memmap2 = "0.7.1"
num_cpus = "1"
object = { version = "0.32.0", features = ["wasm"] }
object = { version = "0.34.0", features = ["wasm", "write"] }
rayon = "1.0"
regex = "1"
typed-arena = "2"
Expand Down
309 changes: 309 additions & 0 deletions crates/examples/src/bin/simple_write.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,309 @@
//! A small example for writing an object file containing DWARF sections.
//!
//! The resulting object file can be linked with a C runtime to create a complete executable:
//! ```sh
//! $ cargo run --bin simple_write
//! $ gcc -o hello hello.o
//! $ ./hello
//! Hello, world!
//! ```
use gimli::write::{
Address, AttributeValue, DwarfUnit, EndianVec, LineProgram, LineString, Range, RangeList,
RelocateWriter, Relocation, RelocationTarget, Sections, Writer,
};
use gimli::{Encoding, Format, LineEncoding, LittleEndian};

/// Record information needed to write a section.
#[derive(Clone)]
struct Section {
data: EndianVec<LittleEndian>,
relocations: Vec<Relocation>,
id: Option<object::write::SectionId>,
}

impl Section {
fn new() -> Self {
Self {
data: EndianVec::new(LittleEndian),
relocations: Vec::new(),
id: None,
}
}
}

impl RelocateWriter for Section {
type Writer = EndianVec<LittleEndian>;

fn writer(&self) -> &Self::Writer {
&self.data
}

fn writer_mut(&mut self) -> &mut Self::Writer {
&mut self.data
}

fn relocate(&mut self, relocation: Relocation) {
self.relocations.push(relocation);
}
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut obj = object::write::Object::new(
object::BinaryFormat::native_object(),
object::Architecture::X86_64,
object::Endianness::Little,
);

let comp_dir = b"/tmp";
let file_name = b"hello.c";

let (main_symbol, main_size) = define_main(&mut obj)?;
let main_address = Address::Symbol {
// This is a user defined identifier for the symbol.
// In this case, we will use 0 to mean the main function.
symbol: 0,
addend: 0,
};

// Choose the encoding parameters.
let encoding = Encoding {
format: Format::Dwarf32,
version: 5,
address_size: 8,
};

// Create a container for a single compilation unit.
let mut dwarf = DwarfUnit::new(encoding);

// Set attributes on the root DIE.
let range_list_id = dwarf.unit.ranges.add(RangeList(vec![Range::StartLength {
begin: main_address,
length: obj.symbol(main_symbol).size,
}]));
let root = dwarf.unit.root();
let entry = dwarf.unit.get_mut(root);
entry.set(
gimli::DW_AT_producer,
AttributeValue::String(b"gimli example".into()),
);
entry.set(
gimli::DW_AT_language,
AttributeValue::Language(gimli::DW_LANG_C11),
);
entry.set(gimli::DW_AT_name, AttributeValue::String(b"hello.c".into()));
entry.set(gimli::DW_AT_comp_dir, AttributeValue::String(b"/".into()));
entry.set(gimli::DW_AT_low_pc, AttributeValue::Address(main_address));
entry.set(
gimli::DW_AT_ranges,
AttributeValue::RangeListRef(range_list_id),
);
// DW_AT_stmt_list will be set automatically.

// Add a line program for the main function.
// For this example, we will only have one line in the line program.
let line_strings = &mut dwarf.line_strings;
let mut line_program = LineProgram::new(
encoding,
LineEncoding::default(),
LineString::new(comp_dir, encoding, line_strings),
LineString::new(file_name, encoding, line_strings),
None,
);
let dir_id = line_program.default_directory();
let file_string = LineString::new(file_name, encoding, line_strings);
let file_id = line_program.add_file(file_string, dir_id, None);
line_program.begin_sequence(Some(main_address));
line_program.row().file = file_id;
line_program.row().line = 2;
line_program.generate_row();
line_program.end_sequence(main_size);
dwarf.unit.line_program = line_program;

// Add a subprogram DIE for the main function.
// Note that this is an example and does not include all attributes.
let subprogram = dwarf.unit.add(root, gimli::DW_TAG_subprogram);
let entry = dwarf.unit.get_mut(subprogram);
entry.set(gimli::DW_AT_external, AttributeValue::Flag(true));
entry.set(gimli::DW_AT_name, AttributeValue::String(b"main".into()));
entry.set(
gimli::DW_AT_decl_file,
AttributeValue::FileIndex(Some(file_id)),
);
entry.set(gimli::DW_AT_decl_line, AttributeValue::Udata(2));
entry.set(gimli::DW_AT_decl_column, AttributeValue::Udata(5));
entry.set(gimli::DW_AT_low_pc, AttributeValue::Address(main_address));
entry.set(gimli::DW_AT_high_pc, AttributeValue::Udata(main_size));

// Build the DWARF sections.
// This will populate the sections with the DWARF data and relocations.
let mut sections = Sections::new(Section::new());
dwarf.write(&mut sections)?;

// Add the DWARF section data to the object file.
sections.for_each_mut(|id, section| -> object::write::Result<()> {
if section.data.len() == 0 {
return Ok(());
}
let section_id = obj.add_section(Vec::new(), id.name().into(), object::SectionKind::Debug);
obj.set_section_data(section_id, section.data.take(), 1);

// Record the section ID so that it can be used for relocations.
section.id = Some(section_id);
Ok(())
})?;

// Add the relocations to the object file.
sections.for_each(|_, section| -> object::write::Result<()> {
let Some(section_id) = section.id else {
debug_assert!(section.relocations.is_empty());
return Ok(());
};
for reloc in &section.relocations {
let symbol = match reloc.target {
RelocationTarget::Section(id) => {
obj.section_symbol(sections.get(id).unwrap().id.unwrap())
}
RelocationTarget::Symbol(id) => {
// The main function is the only symbol we have defined.
debug_assert_eq!(id, 0);
main_symbol
}
};
let kind = if reloc.relative {
object::RelocationKind::Relative
} else {
object::RelocationKind::Absolute
};
obj.add_relocation(
section_id,
object::write::Relocation {
offset: reloc.offset as u64,
symbol,
addend: reloc.addend,
flags: object::RelocationFlags::Generic {
kind,
encoding: object::RelocationEncoding::Generic,
size: reloc.size * 8,
},
},
)?;
}
Ok(())
})?;

// Finally, write the object file.
let file = std::fs::File::create("hello.o")?;
obj.write_stream(file)?;
Ok(())
}

/// Define the data and symbol for the main function.
///
/// This function is unrelated to gimli. It's a copy of the `simple_write` example
/// from the `object` crate.
fn define_main(
obj: &mut object::write::Object,
) -> Result<(object::write::SymbolId, u64), Box<dyn std::error::Error>> {
// Add a file symbol (STT_FILE or equivalent).
obj.add_file_symbol(b"hello.c".into());

// Generate code for the equivalent of this C function:
// int main() {
// puts("Hello, world!");
// return 0;
// }
let mut main_data = Vec::new();
// sub $0x28, %rsp
main_data.extend_from_slice(&[0x48, 0x83, 0xec, 0x28]);
// Handle different calling convention on Windows.
if cfg!(target_os = "windows") {
// lea 0x0(%rip), %rcx
main_data.extend_from_slice(&[0x48, 0x8d, 0x0d, 0x00, 0x00, 0x00, 0x00]);
} else {
// lea 0x0(%rip), %rdi
main_data.extend_from_slice(&[0x48, 0x8d, 0x3d, 0x00, 0x00, 0x00, 0x00]);
}
// R_X86_64_PC32 .rodata-0x4
let s_reloc_offset = main_data.len() - 4;
let s_reloc_addend = -4;
let s_reloc_flags = object::RelocationFlags::Generic {
kind: object::RelocationKind::Relative,
encoding: object::RelocationEncoding::Generic,
size: 32,
};
// call 14 <main+0x14>
main_data.extend_from_slice(&[0xe8, 0x00, 0x00, 0x00, 0x00]);
// R_X86_64_PLT32 puts-0x4
let puts_reloc_offset = main_data.len() - 4;
let puts_reloc_addend = -4;
let puts_reloc_flags = object::RelocationFlags::Generic {
kind: object::RelocationKind::PltRelative,
encoding: object::RelocationEncoding::X86Branch,
size: 32,
};
// xor %eax, %eax
main_data.extend_from_slice(&[0x31, 0xc0]);
// add $0x28, %rsp
main_data.extend_from_slice(&[0x48, 0x83, 0xc4, 0x28]);
// ret
main_data.extend_from_slice(&[0xc3]);

// Add the main function in its own subsection (equivalent to -ffunction-sections).
let (main_section, main_offset) =
obj.add_subsection(object::write::StandardSection::Text, b"main", &main_data, 1);
// Add a globally visible symbol for the main function.
let main_size = main_data.len() as u64;
let main_symbol = obj.add_symbol(object::write::Symbol {
name: b"main".into(),
value: main_offset,
size: main_size,
kind: object::SymbolKind::Text,
scope: object::SymbolScope::Linkage,
weak: false,
section: object::write::SymbolSection::Section(main_section),
flags: object::SymbolFlags::None,
});

// Add a read only string constant for the puts argument.
// We don't create a symbol for the constant, but instead refer to it by
// the section symbol and section offset.
let rodata_section = obj.section_id(object::write::StandardSection::ReadOnlyData);
let rodata_symbol = obj.section_symbol(rodata_section);
let s_offset = obj.append_section_data(rodata_section, b"Hello, world!\0", 1);

// Relocation for the string constant.
obj.add_relocation(
main_section,
object::write::Relocation {
offset: s_reloc_offset as u64,
symbol: rodata_symbol,
addend: s_offset as i64 + s_reloc_addend,
flags: s_reloc_flags,
},
)?;

// External symbol for puts.
let puts_symbol = obj.add_symbol(object::write::Symbol {
name: b"puts".into(),
value: 0,
size: 0,
kind: object::SymbolKind::Text,
scope: object::SymbolScope::Dynamic,
weak: false,
section: object::write::SymbolSection::Undefined,
flags: object::SymbolFlags::None,
});

// Relocation for the call to puts.
obj.add_relocation(
main_section,
object::write::Relocation {
offset: puts_reloc_offset as u64,
symbol: puts_symbol,
addend: puts_reloc_addend,
flags: puts_reloc_flags,
},
)?;

Ok((main_symbol, main_size))
}

0 comments on commit f1e3204

Please sign in to comment.