diff options
-rw-r--r-- | src/elf.rs | 1 | ||||
-rw-r--r-- | src/linker.rs | 73 | ||||
-rw-r--r-- | src/main.rs | 1 | ||||
-rw-r--r-- | test.c | 8 | ||||
-rw-r--r-- | tests/tests.rs | 42 |
5 files changed, 68 insertions, 57 deletions
@@ -22,7 +22,6 @@ pub const PF_X: u32 = 1 << 0; pub const PF_W: u32 = 1 << 1; pub const PF_R: u32 = 1 << 2; -pub const DT_NULL: u32 = 0; pub const DT_NEEDED: u32 = 1; pub const DT_HASH: u32 = 4; pub const DT_STRTAB: u32 = 5; diff --git a/src/linker.rs b/src/linker.rs index f4b4237..26e306b 100644 --- a/src/linker.rs +++ b/src/linker.rs @@ -571,7 +571,8 @@ impl OffsetMap { /// info about the final executable file. struct LinkerOutput { - interp: Vec<u8>, + /// ELF interpreter + interp: String, /// virtual address of big ol' section containing data + elf header + etc. load_addr: u64, /// .bss section address and size if there is one. @@ -598,7 +599,7 @@ impl LinkerOutput { bss: None, load_addr, data: vec![], - interp: vec![], + interp: String::new(), relocations: vec![], lib_strtab_offsets: vec![], dynsyms: HashMap::new(), @@ -613,8 +614,7 @@ impl LinkerOutput { /// set the ELF interpreter (typically `/lib/ld-linux.so.2`) pub fn set_interp(&mut self, interp: &str) { - self.interp = interp.as_bytes().into(); - self.interp.push(b'\0'); + self.interp = interp.into(); } /// returns offset into strtab @@ -657,7 +657,8 @@ impl LinkerOutput { /// offset of program headers fn ph_offset(&self) -> u64 { - elf::Ehdr32::size_of().into() + // - 4 because we overwrite shnum, shstrndx + u64::from(elf::Ehdr32::size_of()) - 4 } /// size of program headers @@ -728,11 +729,25 @@ impl LinkerOutput { if !self.interp.is_empty() { // now interp interp_offset = stream_position32(&mut out)?; - out.write_all(&self.interp)?; - interp_size = usize_to_u32(self.interp.len())?; + // NOTE: we don't need a null terminator, since + // this section is immediately followed by strtab + out.write_all(self.interp.as_bytes())?; + interp_size = usize_to_u32(self.interp.len() + 1)?; // now strtab let strtab_offset = stream_position32(&mut out)?; out.write_all(&self.strtab)?; + // now hash + let hashtab_offset = stream_position32(&mut out)?; + // put everything in a single bucket + let nsymbols = usize_to_u32(self.dynsyms.len())?; + out.write_all(&u32::to_le_bytes(1))?; // nbucket + out.write_all(&u32::to_le_bytes(nsymbols + 1))?; // nchain + out.write_all(&u32::to_le_bytes(0))?; // bucket begins at 0 + // chain 1 -> 2 -> 3 -> ... -> n -> 0 + for i in 1..nsymbols { + out.write_all(&u32::to_le_bytes(i))?; + } + // (note : we need two more 0 entries, and those are provided just below by null_symbol) // now symtab let symtab_offset = stream_position32(&mut out)?; let null_symbol = [0; mem::size_of::<elf::Sym32>()]; @@ -761,28 +776,11 @@ impl LinkerOutput { out.write_all(&rel.to_bytes())?; } let reltab_size = stream_position32(&mut out)? - reltab_offset; - // now hash - let hashtab_offset = stream_position32(&mut out)?; - // put everything in a single bucket - let nsymbols = u64_to_u32(symbols.len() as u64)?; - out.write_all(&u32::to_le_bytes(1))?; // nbucket - out.write_all(&u32::to_le_bytes(nsymbols + 1))?; // nchain - out.write_all(&u32::to_le_bytes(0))?; // bucket begins at 0 - // chain 1 -> 2 -> 3 -> ... -> n -> 0 - for i in 1..nsymbols { - out.write_all(&u32::to_le_bytes(i))?; - } - out.write_all(&u32::to_le_bytes(0))?; - // i don't know why this needs to be here. - out.write_all(&u32::to_le_bytes(0))?; - // now dyntab dyntab_offset = stream_position32(&mut out)?; let mut dyn_data = vec![ elf::DT_RELSZ, reltab_size, - elf::DT_RELENT, - 8, elf::DT_REL, load_addr + reltab_offset, elf::DT_STRSZ, @@ -799,13 +797,18 @@ impl LinkerOutput { for lib in &self.lib_strtab_offsets { dyn_data.extend([elf::DT_NEEDED, u64_to_u32(*lib)?]); } - dyn_data.extend([elf::DT_NULL, 0]); + dyn_data.push(elf::DT_RELENT); let mut dyn_bytes = Vec::with_capacity(dyn_data.len() * 4); for x in dyn_data { dyn_bytes.extend(u32::to_le_bytes(x)); } dyntab_size = usize_to_u32(dyn_bytes.len())?; out.write_all(&dyn_bytes)?; + + // dyn_data should have been extended with [8 (value of DT_RELENT), 0, 0 (terminal record)] + // however, we don't need to include all those zero bytes, because we're at the end of the file. + // we just need one 8-byte: + out.write_all(&[8])?; } let file_size: u32 = stream_position32(&mut out)?; @@ -814,14 +817,17 @@ impl LinkerOutput { let ehdr = elf::Ehdr32 { phnum: self.segment_count(), - phoff: elf::Ehdr32::size_of().into(), - // apparently you're supposed to set this to zero if there are no sections. - // at least, that's what readelf seems to think. + phoff: u64_to_u32(self.ph_offset())?, + // by setting shentsize to 0, we ensure that + // linux will ignore the sections, + // even if shnum != 0 shentsize: 0, entry: u64_to_u32(entry_point)?, ..Default::default() }; out.write_all(&ehdr.to_bytes())?; + // go back to overwrite shnum, shstrndx + out.seek(io::SeekFrom::Current(-4))?; let phdr_data = elf::Phdr32 { flags: elf::PF_R | elf::PF_W | elf::PF_X, // read, write, execute @@ -879,8 +885,15 @@ impl LinkerOutput { } impl<'a> Linker<'a> { - pub const DEFAULT_CFLAGS: [&str; 5] = ["-Wall", "-Os", "-m32", "-fno-pic", "-c"]; - pub const DEFAULT_CXXFLAGS: [&str; 5] = Self::DEFAULT_CFLAGS; + pub const DEFAULT_CFLAGS: [&str; 6] = [ + "-Wall", + "-Os", + "-fomit-frame-pointer", + "-m32", + "-fno-pic", + "-c", + ]; + pub const DEFAULT_CXXFLAGS: [&str; 6] = Self::DEFAULT_CFLAGS; fn default_warning_handler(warning: LinkWarning) { eprintln!("warning: {warning}"); diff --git a/src/main.rs b/src/main.rs index 201b649..f0a3725 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,5 @@ /* @TODO: -- make executables more tiny (overlap sections, etc.) - generate a warning/error on position-independent object files - static libraries */ @@ -1,13 +1,7 @@ // temporary test file -#include<stdio.h> #include <stdlib.h> -int f() { - return 7; -} - void entry(void) { - printf("%d\n", f()); - exit(0); + exit(42); } diff --git a/tests/tests.rs b/tests/tests.rs index 9e23641..14f90bc 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1,9 +1,9 @@ #[cfg(test)] mod tests { - use std::process::{Command, Output}; use std::fs::File; - use tinyld::linker::{Linker, LinkWarning}; - + use std::process::{Command, Output}; + use tinyld::linker::{LinkWarning, Linker}; + fn panic_warning_handler(warning: LinkWarning) { eprintln!("warning: {warning}"); panic!("this should not generate a warning"); @@ -14,7 +14,7 @@ mod tests { linker.set_warning_handler(panic_warning_handler); linker } - + fn file(s: &str) -> String { format!("./tests/{s}") } @@ -24,30 +24,30 @@ mod tests { let s = if is_local { &f } else { src }; linker.add_input(s).expect(&format!("failed to add {s}")); } - + fn link(linker: &Linker, out: &str, entry: &str) { - linker.link_to_file(&file(out), entry).expect("failed to link"); + linker + .link_to_file(&file(out), entry) + .expect("failed to link"); } - + fn run_with_stdin(name: &str, stdin: Option<&str>) -> Output { let mut command = Command::new(&file(name)); if let Some(s) = stdin { let file = File::open(&file(s)).expect("stdin file does not exist"); command.stdin(file); } - - let output = command - .output() - .expect("failed to run output executable"); + + let output = command.output().expect("failed to run output executable"); assert!(output.status.success()); assert!(output.stderr.is_empty()); output } - + fn run(name: &str) -> std::process::Output { run_with_stdin(name, None) } - + #[test] fn tiny_c() { let mut linker = test_linker(); @@ -66,16 +66,22 @@ mod tests { let output = run("basic.out"); assert_eq!(output.stdout, b"137\n"); } - + #[test] fn dylib_c() { let status = Command::new("gcc") - .args(&["-m32", "-fPIC", "-shared", &file("dylib.c"), "-o", &file("dylib.so")]) + .args(&[ + "-m32", + "-fPIC", + "-shared", + &file("dylib.c"), + "-o", + &file("dylib.so"), + ]) .status() .expect("failed to create dylib.so"); assert!(status.success()); - - + let mut linker = test_linker(); add(&mut linker, "dylib-test.c", true); add(&mut linker, "dylib.so", true); @@ -84,7 +90,7 @@ mod tests { let output = run("dylib-test.out"); assert_eq!(output.stdout, b"7\n8\n"); } - + #[test] fn cpp() { let mut linker = test_linker(); |