Small Rust scripts
at main 122 lines 3.4 kB view raw
1#!/usr/bin/env -S cargo +nightly -Zscript 2 3--- 4[package] 5edition = "2024" 6[dependencies] 7argh = "0.1" 8object = "0.38.0" 9--- 10 11use std::fs::OpenOptions; 12use std::io::{self, BufReader, Read, Write}; 13use std::path::PathBuf; 14 15use argh::FromArgs; 16use object::{File, Object, ObjectSection, SectionKind}; 17 18const MIN_STR_LEN: usize = 4; 19 20const ELF_SIG: [u8; 4] = [0x7F, 0x45, 0x4C, 0x46]; 21const MACH_O_32_SIG: [u8; 4] = [0xFE, 0xED, 0xFA, 0xCE]; 22const MACH_O_64_SIG: [u8; 4] = [0xFE, 0xED, 0xFA, 0xCF]; 23const MACH_O_FAT_SIG: [u8; 4] = [0xCA, 0xFE, 0xBA, 0xBE]; 24const MACH_O_32_REV_SIG: [u8; 4] = [0xCE, 0xFA, 0xED, 0xFE]; 25const MACH_O_64_REV_SIG: [u8; 4] = [0xCF, 0xFA, 0xED, 0xFE]; 26 27#[derive(FromArgs)] 28#[argh(description = "Rust clone of strings(1).")] 29struct App { 30 #[argh( 31 switch, 32 description = "ignore heuristics and read all sections of an object file." 33 )] 34 all: bool, 35 36 #[argh( 37 option, 38 description = "the minimum length a valid string needs to be in order to be printed.", 39 default = "MIN_STR_LEN" 40 )] 41 min_length: usize, 42 43 #[argh(positional, description = "input file.")] 44 file: PathBuf, 45} 46 47fn main() { 48 let app: App = argh::from_env(); 49 50 let file = OpenOptions::new() 51 .read(true) 52 .open(app.file) 53 .expect("could not open source"); 54 55 let mut reader = BufReader::new(file); 56 57 // Preallocate 4MB of space since we're usually dealing with files 58 let mut contents = Vec::with_capacity(1024 * 1024 * 4); 59 reader.read_to_end(&mut contents); 60 61 let stdout = io::stdout(); 62 let mut handle = stdout.lock(); 63 64 let mut magic = [0, 0, 0, 0]; 65 if contents.len() >= 4 { 66 magic.copy_from_slice(&contents[..4]); 67 } 68 69 if is_object_file(&magic) { 70 print_object_strings(&mut handle, &contents, app.all, app.min_length); 71 } else { 72 print_strings(&mut handle, &contents, app.min_length); 73 } 74} 75 76fn is_object_file(magic: &[u8; 4]) -> bool { 77 magic == &ELF_SIG 78 || magic == &MACH_O_FAT_SIG 79 || magic == &MACH_O_32_SIG 80 || magic == &MACH_O_64_SIG 81 || magic == &MACH_O_32_REV_SIG 82 || magic == &MACH_O_64_REV_SIG 83} 84 85fn print_object_strings(handle: &mut impl Write, contents: &[u8], all: bool, min_length: usize) { 86 // We could also read this in a lazy fashion but... I'm lazy at the moment ;) 87 let object = File::parse(contents).expect("Could not parse object file"); 88 object 89 .sections() 90 .filter(|section| { 91 all || matches!( 92 section.kind(), 93 SectionKind::Data | SectionKind::ReadOnlyString | SectionKind::ReadOnlyData 94 ) 95 }) 96 .for_each(|section| { 97 let contents = section.data().expect("section should have data"); 98 print_strings(handle, contents, min_length); 99 }); 100} 101 102fn print_strings(handle: &mut impl Write, contents: &[u8], min_length: usize) { 103 let mut start = 0; 104 let mut end = 0; 105 106 for c in contents.iter() { 107 let is_printable = c.is_ascii_graphic() || c.is_ascii_whitespace(); 108 let has_minimum_len = (end - start) >= min_length; 109 110 if !is_printable && has_minimum_len { 111 handle.write(&contents[start..end]); 112 handle.write(&[b'\n']); 113 start = end + 1; 114 } else if !is_printable { 115 start = end + 1; 116 } 117 118 end += 1; 119 } 120 121 handle.flush(); 122}