forked from flipperzero-rs/flipperzero
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
tools: Add
fap-lld
wrapping linker that generates .fast.rel
sections
Closes flipperzero-rs#89.
- Loading branch information
Showing
7 changed files
with
283 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(()) | ||
} |