Skip to content

Commit

Permalink
tools: Add fap-lld wrapping linker that generates .fast.rel sections
Browse files Browse the repository at this point in the history
  • Loading branch information
str4d committed Aug 26, 2023
1 parent 7858322 commit 6e2f492
Show file tree
Hide file tree
Showing 7 changed files with 283 additions and 0 deletions.
1 change: 1 addition & 0 deletions .github/workflows/_build_and_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ jobs:
working-directory: ${{ inputs.target }}
steps:
- uses: actions/[email protected]
- run: rustup component add llvm-tools
- run: sudo apt install libudev-dev
- name: Build
run: cargo build --release --verbose
Expand Down
1 change: 1 addition & 0 deletions crates/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
[target.thumbv7em-none-eabihf]
linker = "./fap-lld.py"
runner = "python3 ../cargo-runner.py"
rustflags = [
# CPU is Cortex-M4 (STM32WB55)
Expand Down
33 changes: 33 additions & 0 deletions crates/fap-lld.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env python3
# Helper script for linking and post-processing FAP binaries.

import os
import sys
from subprocess import run

TOOLS_PATH = '../tools'


def main():
args = sys.argv[1:]
print(args)

# Run the linker with the given arguments.
result = run(
[
'cargo',
'run',
'--quiet',
'--release',
'--bin',
'fap-lld',
'--',
] + args,
cwd=os.path.join(os.path.dirname(__file__), TOOLS_PATH),
)
if result.returncode:
sys.exit(result.returncode)


if __name__ == '__main__':
main()
41 changes: 41 additions & 0 deletions tools/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions tools/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@ clap = { version = "4.0.32", features = ["cargo", "derive"] }
crossterm = "0.26.1"
csv = "1.1.6"
doxygen-rs = "0.3.1"
elf = "0.7"
once_cell = "1.17.1"
rand = "0.8"
regex = "1.7.1"
serde = { version = "1.0.152", features = ["derive"] }
serde_json = "1.0.91"
serialport = "4.2.0"
shlex = "1.1.0"
tempfile = "3"
which = "4"
148 changes: 148 additions & 0 deletions tools/src/bin/fap-lld/fastrel.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
use std::{
collections::HashMap,
fs,
io::{self, Write},
path::Path,
process::Command,
};

use elf::{
abi::SHT_REL, endian::LittleEndian, relocation::RelIterator, string_table::StringTable,
symbol::SymbolTable, ElfBytes,
};
use tempfile::NamedTempFile;

use super::Error;

pub(crate) fn postprocess_fap(output_fap: &Path, objcopy: &Path) -> Result<(), Error> {
// Parse the FAP as an ELF binary.
let fap_data = fs::read(output_fap)?;
let fap = ElfBytes::<LittleEndian>::minimal_parse(&fap_data)?;

// Get the section header table alongside its string table.
let (shdrs_opt, strtab_opt) = fap.section_headers_with_strtab()?;
let (shdrs, strtab) = shdrs_opt.zip(strtab_opt).ok_or(Error::NoSectionHeaders)?;

// Collect the sections with relocations.
let rel_sections = shdrs
.iter()
.filter(|shdr| shdr.sh_type == SHT_REL)
.map(|shdr| -> Result<_, Error> {
let name = strtab.get(shdr.sh_name as usize)?;
let section = fap.section_data_as_rels(&shdr)?;
Ok((name, section))
})
.collect::<Result<Vec<_>, _>>()?;

// Convert the relocations into `.fast.rel` sections.
let (symtab, strtab) = fap.symbol_table()?.ok_or(Error::NoSymbolTable)?;
let fastrel_sections = rel_sections
.into_iter()
.map(|(section_name, section)| FastRelSection::new(section_name, section, &symtab, &strtab))
.collect::<Result<Vec<_>, _>>()?;

// Write the `.fast.rel` sections into the binary.
for section in fastrel_sections {
let mut data = NamedTempFile::new()?;
section.write(&mut data)?;
data.flush()?;

let res = Command::new(objcopy)
.arg("--add-section")
.arg(format!("{}={}", section.name, data.path().display()))
.arg(output_fap)
.status()?;
if !res.success() {
return Err(Error::ObjcopyFailed);
}

data.close()?;
}

Ok(())
}

#[derive(Debug, PartialEq, Eq, Hash)]
struct FastRel<'data> {
section_index: u16,
section_value: u64,
r_type: u32,
name: &'data str,
}

impl<'data> FastRel<'data> {
fn gnu_sym_hash(&self) -> u32 {
let mut h = 0x1505;
for c in self.name.as_bytes() {
h = (h << 5) + h + u32::from(*c);
}
h
}
}

/// A `.fast.rel` section.
#[derive(Debug)]
struct FastRelSection<'data> {
name: String,
fastrel_offsets: HashMap<FastRel<'data>, Vec<u64>>,
}

impl<'data> FastRelSection<'data> {
fn new(
section_name: &str,
section: RelIterator<'_, LittleEndian>,
symtab: &SymbolTable<'_, LittleEndian>,
strtab: &StringTable<'data>,
) -> Result<Self, Error> {
assert!(section_name.starts_with(".rel"));

let mut fastrel_offsets = HashMap::<_, Vec<u64>>::new();
for rel in section {
let symbol = symtab.get(rel.r_sym as usize)?;
let name = if symbol.st_name == 0 {
""
} else {
strtab.get(symbol.st_name as usize)?
};

fastrel_offsets
.entry(FastRel {
section_index: symbol.st_shndx,
section_value: symbol.st_value,
r_type: rel.r_type,
name,
})
.or_default()
.push(rel.r_offset);
}

Ok(FastRelSection {
name: format!(".fast{}", section_name),
fastrel_offsets,
})
}

fn write(&self, mut w: impl Write) -> io::Result<()> {
const VERSION: u8 = 1;

w.write_all(&[VERSION])?;
w.write_all(&(self.fastrel_offsets.len() as u32).to_le_bytes())?;
for (unique, offsets) in &self.fastrel_offsets {
if unique.section_index > 0 {
w.write_all(&[(1 << 7) | (unique.r_type & 0x7F) as u8])?;
w.write_all(&u32::from(unique.section_index).to_le_bytes())?;
w.write_all(&u32::try_from(unique.section_value).unwrap().to_le_bytes())?;
} else {
w.write_all(&[(unique.r_type & 0x7F) as u8])?;
w.write_all(&unique.gnu_sym_hash().to_le_bytes())?;
}

w.write_all(&(offsets.len() as u32).to_le_bytes())?;
for offset in offsets {
w.write_all(&offset.to_le_bytes()[..3])?;
}
}

Ok(())
}
}
56 changes: 56 additions & 0 deletions tools/src/bin/fap-lld/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use std::{
env, io,
path::PathBuf,
process::{self, Command},
};

use elf::ParseError;
use which::which;

mod fastrel;

#[derive(Debug)]
enum Error {
Io(io::Error),
Parse(ParseError),
NoSymbolTable,
NoSectionHeaders,
ObjcopyFailed,
}

impl From<io::Error> for Error {
fn from(e: io::Error) -> Self {
Error::Io(e)
}
}

impl From<ParseError> for Error {
fn from(e: ParseError) -> Self {
Error::Parse(e)
}
}

fn main() -> Result<(), Error> {
// Run the real linker with the given arguments.
let res = Command::new("rust-lld")
.args(env::args_os().skip(1))
.status()?;
if !res.success() {
process::exit(res.code().unwrap_or(-1));
}

// If we don't have objcopy available, skip post-linking optimizations.
if let Ok(objcopy) = which("llvm-objcopy") {
// Parse the arguments to find the path to the linked binary.
let output_fap = PathBuf::from(env::args_os().skip_while(|a| a != "-o").nth(1).unwrap());

// Add `.fast.rel` sections.
fastrel::postprocess_fap(&output_fap, &objcopy)?;
} else {
println!("Cannot find llvm-objcopy, skipping post-linker optimizations.");
println!("Please install the llvm-tools for your Rust compiler. For example:");
println!(" rustup component add llvm-tools");
}

Ok(())
}

0 comments on commit 6e2f492

Please sign in to comment.