use std::env;
use std::path::Path;
pub fn run_cli() {
let args: Vec<String> = env::args().collect();
let program = Path::new(&args[0])
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("llvm-native");
match program {
"llc" | "llvm-native-llc" => llc::run(&args),
"opt" | "llvm-native-opt" => opt::run(&args),
"llvm-as" | "llvm-native-as" => llvm_as::run(&args),
"llvm-dis" | "llvm-native-dis" => llvm_dis::run(&args),
"llvm-link" | "llvm-native-link" => llvm_link::run(&args),
"llvm-ar" | "llvm-native-ar" => llvm_ar::run(&args),
"llvm-nm" | "llvm-native-nm" => llvm_nm::run(&args),
"llvm-objdump" | "llvm-native-objdump" => llvm_objdump::run(&args),
"llvm-size" | "llvm-native-size" => llvm_size::run(&args),
"llvm-strings" | "llvm-native-strings" => llvm_strings::run(&args),
"llvm-dwarfdump" | "llvm-native-dwarfdump" => llvm_dwarfdump::run(&args),
"llvm-readobj" | "llvm-native-readobj" => llvm_readobj::run(&args),
"llvm-symbolizer" | "llvm-native-symbolizer" => llvm_symbolizer::run(&args),
"llvm-cov" | "llvm-native-cov" => llvm_cov::run(&args),
"llvm-config" | "llvm-native-config" => llvm_config::run(&args),
"llvm-mca" | "llvm-native-mca" => llvm_mca::run(&args),
"llvm-readelf" | "llvm-native-readelf" => llvm_readelf::run(&args),
"llvm-profdata" | "llvm-native-profdata" => llvm_profdata::run(&args),
"lld" | "ld.lld" | "llvm-native-lld" => lld::run(&args),
"clang" | "llvm-native-clang" => clang::run(&args),
_ => {
if args.len() > 1 {
match args[1].as_str() {
"llc" => llc::run(&args[1..]),
"opt" => opt::run(&args[1..]),
"llvm-as" => llvm_as::run(&args[1..]),
"llvm-dis" => llvm_dis::run(&args[1..]),
"llvm-link" => llvm_link::run(&args[1..]),
"llvm-objdump" => llvm_objdump::run(&args[1..]),
"llvm-ar" => llvm_ar::run(&args[1..]),
"llvm-nm" => llvm_nm::run(&args[1..]),
"llvm-size" => llvm_size::run(&args[1..]),
"llvm-strings" => llvm_strings::run(&args[1..]),
"llvm-dwarfdump" => llvm_dwarfdump::run(&args[1..]),
"llvm-readobj" => llvm_readobj::run(&args[1..]),
"llvm-symbolizer" => llvm_symbolizer::run(&args[1..]),
"llvm-cov" => llvm_cov::run(&args[1..]),
"llvm-config" => llvm_config::run(&args[1..]),
"llvm-mca" => llvm_mca::run(&args[1..]),
"llvm-readelf" => llvm_readelf::run(&args[1..]),
"llvm-profdata" => llvm_profdata::run(&args[1..]),
"lld" | "ld.lld" => lld::run(&args[1..]),
"clang" => clang::run(&args[1..]),
_ => print_usage(),
}
} else {
print_usage();
}
}
}
}
fn print_usage() {
println!("llvm-native v{}", env!("CARGO_PKG_VERSION"));
println!("Forensic-parity LLVM toolchain reimplementation");
println!();
println!("Available tools:");
println!(" llc — Compile LLVM IR to native assembly");
println!(" opt — Run optimization passes on LLVM IR");
println!(" llvm-as — Assemble .ll text to .bc bitcode");
println!(" llvm-dis — Disassemble .bc bitcode to .ll text");
println!(" llvm-link — Link multiple .bc files");
println!(" llvm-ar — Archive tool (create/extract/list)");
println!(" llvm-nm — Symbol table dump");
println!(" llvm-objdump — Disassemble object files");
println!(" llvm-size — Display section sizes");
println!(" llvm-strings — Extract printable strings");
println!(" llvm-dwarfdump — Dump DWARF debug information");
println!(" llvm-readobj — Display object file contents");
println!(" llvm-symbolizer — Symbolize addresses to source locations");
println!(" llvm-cov — Coverage report tool");
println!(" llvm-config — Print LLVM build configuration");
println!(" llvm-mca — Machine Code Analyzer");
println!(" llvm-readelf — Display ELF object information");
println!(" llvm-profdata — Profile data tool");
println!(" lld — Link object files (ELF)");
println!(" clang — Compile C source to native object code");
println!();
println!("Usage: llvm-native <tool> [options] [input]");
}
pub mod llc {
use llvm_native_core::asm_parser;
use llvm_native_core::bitcode_reader;
use llvm_native_core::codegen;
use llvm_native_core::module::Module;
use std::fs;
pub fn run(args: &[String]) {
let input = args
.iter()
.skip(1)
.find(|a| !a.starts_with('-'))
.cloned()
.unwrap_or_else(|| "input.bc".into());
let output = args
.iter()
.skip(1)
.position(|a| a == "-o")
.and_then(|i| args.get(i + 2))
.cloned()
.unwrap_or_else(|| "output.s".into());
eprintln!("llc: compiling {} -> {}", input, output);
let module = if let Ok(bytes) = fs::read(&input) {
if bytes.len() >= 4 && &bytes[0..4] == b"BSC\x00" {
bitcode_reader::read_bitcode(&bytes).unwrap_or_else(|| Module::new("input"))
} else if let Ok(text) = String::from_utf8(bytes) {
asm_parser::parse_assembly(&text).unwrap_or_else(|| Module::new("input"))
} else {
Module::new("input")
}
} else {
eprintln!("llc: could not read {}", input);
return;
};
let mut asm = String::new();
if let Some(ref _triple) = module.target_triple {
asm.push_str(&format!("\t.file\t\"{}\"\n", input));
}
asm.push_str("\t.text\n");
for func in &module.functions {
let f = func.borrow();
if f.is_function() && f.num_operands > 0 {
let compiled = codegen::compile_function(func);
asm.push_str(&compiled);
}
}
let _ = fs::write(&output, &asm);
eprintln!(" Wrote {} bytes to {}", asm.len(), output);
}
}
pub mod opt {
use llvm_native_core::bitcode_reader;
use llvm_native_core::module::Module;
use llvm_native_core::passes;
use std::fs;
pub fn run(args: &[String]) {
let input = args
.iter()
.skip(1)
.find(|a| !a.starts_with('-'))
.cloned()
.unwrap_or_else(|| "input.bc".into());
println!("opt: optimizing {}", input);
let mut module = if let Ok(bytes) = fs::read(&input) {
bitcode_reader::read_bitcode(&bytes).unwrap_or_else(|| Module::new("input"))
} else {
Module::new("input")
};
let mut pm = passes::create_standard_pipeline();
let changed = pm.run(&mut module);
println!(
" Pipeline: {}",
if changed { "modified" } else { "no changes" }
);
pm.print_stats();
}
}
pub mod llvm_as {
use std::fs;
pub fn run(args: &[String]) {
let input = args
.iter()
.skip(1)
.find(|a| !a.starts_with('-'))
.cloned()
.unwrap_or_else(|| "input.ll".into());
let output = args
.iter()
.skip(1)
.position(|a| a == "-o")
.and_then(|i| args.get(i + 2))
.cloned()
.unwrap_or_else(|| "output.bc".into());
println!("llvm-as: assembling {} -> {}", input, output);
if let Ok(text) = fs::read_to_string(&input) {
if let Some(module) = llvm_native_core::asm_parser::parse_assembly(&text) {
let bc = llvm_native_core::bitcode_writer::write_bitcode(&module);
let _ = fs::write(&output, &bc);
println!(" Wrote {} bytes to {}", bc.len(), output);
}
}
}
}
pub mod llvm_dis {
use std::fs;
pub fn run(args: &[String]) {
let input = args
.iter()
.skip(1)
.find(|a| !a.starts_with('-'))
.cloned()
.unwrap_or_else(|| "input.bc".into());
let output = args
.iter()
.skip(1)
.position(|a| a == "-o")
.and_then(|i| args.get(i + 2))
.cloned()
.unwrap_or_else(|| "output.ll".into());
println!("llvm-dis: disassembling {} -> {}", input, output);
if let Ok(bytes) = fs::read(&input) {
if let Some(module) = llvm_native_core::bitcode_reader::read_bitcode(&bytes) {
let text = llvm_native_core::asm_writer::write_assembly(&module);
let _ = fs::write(&output, &text);
println!(" Wrote {} bytes to {}", text.len(), output);
}
}
}
}
pub mod llvm_link {
use llvm_native_core::linker;
use llvm_native_core::module::Module;
use std::fs;
pub fn run(args: &[String]) {
let inputs: Vec<String> = args
.iter()
.skip(1)
.filter(|a| !a.starts_with('-') && *a != "-o")
.cloned()
.collect();
let output = args
.iter()
.skip(1)
.position(|a| a == "-o")
.and_then(|i| args.get(i + 2))
.cloned()
.unwrap_or_else(|| "linked.bc".into());
println!("llvm-link: linking {} files -> {}", inputs.len(), output);
let mut dest = Module::new("linked");
for input in &inputs {
if let Ok(bytes) = fs::read(input) {
if let Some(module) = llvm_native_core::bitcode_reader::read_bitcode(&bytes) {
let n = linker::link_modules(&mut dest, &module);
println!(" Linked {} entities from {}", n, input);
}
}
}
let bc = llvm_native_core::bitcode_writer::write_bitcode(&dest);
let _ = fs::write(&output, &bc);
println!(" Wrote {} bytes to {}", bc.len(), output);
}
}
pub mod llvm_nm {
use std::fs;
pub fn run(args: &[String]) {
let input = args
.iter()
.skip(1)
.find(|a| !a.starts_with('-'))
.cloned()
.unwrap_or_else(|| "input.bc".into());
println!("llvm-nm: symbols in {}", input);
if let Ok(bytes) = fs::read(&input) {
if let Some(module) = llvm_native_core::bitcode_reader::read_bitcode(&bytes) {
for func in &module.functions {
let f = func.borrow();
println!(" T {}", f.name);
}
} else if is_elf(&bytes) {
print_elf_symbols(&bytes);
} else {
println!(" (unknown format)");
}
}
}
fn is_elf(data: &[u8]) -> bool {
data.len() >= 4 && &data[0..4] == b"\x7fELF"
}
fn print_elf_symbols(data: &[u8]) {
if data.len() < 64 {
return;
}
let is_64 = data[4] == 2;
if is_64 {
let shoff = read_u64(data, 40);
let shentsize = read_u16(data, 58) as u64;
let shnum = read_u16(data, 60) as u64;
let shstrndx = read_u16(data, 62) as u64;
let shstr_offset = shoff + shstrndx * shentsize;
let _shstr_sh_offset = read_u64(data, (shstr_offset + 24) as usize);
let shstr_sh_size = read_u64(data, (shstr_offset + 32) as usize);
for i in 0..shnum {
let shdr = (shoff + i * shentsize) as usize;
let sh_type = read_u32(data, shdr + 4);
if sh_type == 2 || sh_type == 11 {
let offset = read_u64(data, shdr + 24) as usize;
let size = read_u64(data, shdr + 32) as usize;
let link = read_u32(data, shdr + 40) as u64;
let str_shdr = (shoff + link * shentsize) as usize;
let str_offset = read_u64(data, str_shdr + 24) as usize;
let entsize = read_u64(data, shdr + 56);
let count = if entsize > 0 {
size / entsize as usize
} else {
0
};
for j in 0..count {
let sym = offset + j * entsize as usize;
let st_name = read_u32(data, sym) as usize;
let st_info = data[sym + 4];
let st_type = st_info & 0xf;
if st_name > 0 && st_name < shstr_sh_size as usize && st_type != 0 {
let name = read_cstr(data, str_offset + st_name);
let bind = (st_info >> 4) & 0xf;
let tchar = match st_type {
1 => 'O', 2 => 'F', _ => '?',
};
let bchar = match bind {
1 => 'l', 2 => 'g', _ => '?',
};
println!(" {}{} {}", bchar, tchar, name);
}
}
}
}
}
}
fn read_u16(data: &[u8], offset: usize) -> u16 {
if offset + 2 > data.len() {
return 0;
}
u16::from_le_bytes([data[offset], data[offset + 1]])
}
fn read_u32(data: &[u8], offset: usize) -> u32 {
if offset + 4 > data.len() {
return 0;
}
u32::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
])
}
fn read_u64(data: &[u8], offset: usize) -> u64 {
if offset + 8 > data.len() {
return 0;
}
u64::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
data[offset + 4],
data[offset + 5],
data[offset + 6],
data[offset + 7],
])
}
fn read_cstr(data: &[u8], offset: usize) -> String {
let mut s = String::new();
let mut i = offset;
while i < data.len() {
if data[i] == 0 {
break;
}
s.push(data[i] as char);
i += 1;
}
s
}
}
pub mod llvm_objdump {
use llvm_native_core::objdump;
use std::fs;
pub fn run(args: &[String]) {
let input = args
.iter()
.skip(1)
.find(|a| !a.starts_with('-'))
.cloned()
.unwrap_or_else(|| "input.o".into());
match fs::read(&input) {
Ok(data) => {
if let Some(output) = objdump::disassemble_object(&data) {
println!("{}", output.text);
eprintln!(
" {} instructions in {} functions",
output.instructions, output.functions
);
} else {
eprintln!("llvm-objdump: not a valid ELF file");
}
}
Err(e) => eprintln!("llvm-objdump: {}: {}", input, e),
}
}
}
pub mod llvm_ar {
use std::collections::HashMap;
use std::fs;
use std::path::Path;
const AR_MAGIC: &[u8; 8] = b"!<arch>\n";
pub fn run(args: &[String]) {
if args.len() < 2 {
eprintln!("llvm-ar: missing operation");
eprintln!("Usage: llvm-ar [rcsxt] archive [files...]");
return;
}
let op = &args[1];
let archive = args.get(2).cloned().unwrap_or_else(|| "archive.a".into());
let files: Vec<&str> = args.iter().skip(3).map(|s| s.as_str()).collect();
match op.as_str() {
"r" | "rc" | "rcs" => create_archive(&archive, &files),
"x" => extract_archive(&archive, &files),
"t" => list_archive(&archive),
"d" => delete_from_archive(&archive, &files),
_ => eprintln!("llvm-ar: unknown operation '{}'", op),
}
}
fn create_archive(archive: &str, files: &[&str]) {
let mut out = Vec::new();
out.extend_from_slice(AR_MAGIC);
for file in files {
let path = Path::new(file);
let name = path.file_name().and_then(|n| n.to_str()).unwrap_or(file);
match fs::read(file) {
Ok(data) => {
let timestamp = 0u64;
let owner = 0u32;
let group = 0u32;
let mode = 0o100644u32;
let size = data.len() as u64;
let mut header = format!(
"{:<16}{:<12}{:<6}{:<6}{:<8}{:<10}`\n",
name, timestamp, owner, group, mode, size
);
while header.len() < 60 {
header.push(' ');
}
if header.len() > 60 {
header.truncate(60);
}
let len = header.len();
header.replace_range(len - 2.., "`\n");
out.extend_from_slice(header.as_bytes());
out.extend_from_slice(&data);
if data.len() % 2 != 0 {
out.push(b'\n');
}
}
Err(e) => eprintln!("llvm-ar: cannot read {}: {}", file, e),
}
}
if let Err(e) = fs::write(archive, &out) {
eprintln!("llvm-ar: cannot write {}: {}", archive, e);
} else {
eprintln!("llvm-ar: created {} with {} files", archive, files.len());
}
}
fn extract_archive(archive: &str, files: &[&str]) {
let data = match fs::read(archive) {
Ok(d) => d,
Err(e) => {
eprintln!("llvm-ar: cannot read {}: {}", archive, e);
return;
}
};
let filter: Option<Vec<&str>> = if files.is_empty() {
None
} else {
Some(files.to_vec())
};
let entries = parse_archive(&data);
for (name, content) in &entries {
let should_extract = filter
.as_ref()
.map(|f| f.contains(&name.as_str()))
.unwrap_or(true);
if should_extract {
if let Err(e) = fs::write(name, content) {
eprintln!("llvm-ar: cannot write {}: {}", name, e);
}
println!(" x - {}", name);
}
}
}
fn list_archive(archive: &str) {
let data = match fs::read(archive) {
Ok(d) => d,
Err(e) => {
eprintln!("llvm-ar: cannot read {}: {}", archive, e);
return;
}
};
let entries = parse_archive(&data);
for (name, content) in &entries {
println!("{:>8} {}", content.len(), name);
}
}
fn delete_from_archive(archive: &str, files: &[&str]) {
let data = match fs::read(archive) {
Ok(d) => d,
Err(e) => {
eprintln!("llvm-ar: cannot read {}: {}", archive, e);
return;
}
};
let entries = parse_archive(&data);
let mut out = Vec::new();
out.extend_from_slice(AR_MAGIC);
let mut removed = 0;
for (name, content) in &entries {
if files.contains(&name.as_str()) {
removed += 1;
continue;
}
let timestamp = 0u64;
let owner = 0u32;
let group = 0u32;
let mode = 0o100644u32;
let size = content.len() as u64;
let mut header = format!(
"{:<16}{:<12}{:<6}{:<6}{:<8}{:<10}`\n",
name, timestamp, owner, group, mode, size
);
while header.len() < 60 {
header.push(' ');
}
if header.len() > 60 {
header.truncate(60);
}
let len = header.len();
header.replace_range(len - 2.., "`\n");
out.extend_from_slice(header.as_bytes());
out.extend_from_slice(content);
if content.len() % 2 != 0 {
out.push(b'\n');
}
}
if let Err(e) = fs::write(archive, &out) {
eprintln!("llvm-ar: cannot write {}: {}", archive, e);
} else {
eprintln!("llvm-ar: removed {} file(s) from {}", removed, archive);
}
}
fn parse_archive(data: &[u8]) -> HashMap<String, Vec<u8>> {
let mut entries = HashMap::new();
if data.len() < 8 || &data[0..8] != AR_MAGIC {
return entries;
}
let mut offset = 8usize;
while offset + 60 <= data.len() {
let header = &data[offset..offset + 60];
let name = std::str::from_utf8(&header[0..16])
.unwrap_or("")
.trim_end_matches('/')
.trim()
.to_string();
let size_str = std::str::from_utf8(&header[48..58]).unwrap_or("0").trim();
let size: usize = size_str.parse().unwrap_or(0);
offset += 60;
if offset + size > data.len() {
break;
}
let content = data[offset..offset + size].to_vec();
if !name.is_empty() {
entries.insert(name, content);
}
offset += size;
if offset < data.len() && size % 2 != 0 {
offset += 1;
}
}
entries
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn archive_roundtrip() {
let tmp = std::env::temp_dir().join("test_archive.a");
let path = tmp.to_str().unwrap();
let f1 = std::env::temp_dir().join("_ar_test_a.txt");
let f2 = std::env::temp_dir().join("_ar_test_b.txt");
std::fs::write(&f1, b"hello").unwrap();
std::fs::write(&f2, b"world").unwrap();
create_archive(path, &[f1.to_str().unwrap(), f2.to_str().unwrap()]);
let data = std::fs::read(path).unwrap();
let entries = parse_archive(&data);
assert!(entries.len() >= 2);
let _ = std::fs::remove_file(path);
let _ = std::fs::remove_file(f1);
let _ = std::fs::remove_file(f2);
}
}
}
pub mod llvm_size {
use std::fs;
pub fn run(args: &[String]) {
let input = args
.iter()
.skip(1)
.find(|a| !a.starts_with('-'))
.cloned()
.unwrap_or_else(|| "input.o".into());
let radix = if args.iter().any(|a| a == "-x" || a == "--radix=16") {
16
} else {
10
};
match fs::read(&input) {
Ok(data) => print_sizes(&data, &input, radix),
Err(e) => eprintln!("llvm-size: {}: {}", input, e),
}
}
fn print_sizes(data: &[u8], filename: &str, radix: u32) {
if is_elf(data) {
print_elf_sizes(data, filename, radix);
} else if is_macho(data) {
print_macho_sizes(data, filename, radix);
} else {
print_fallback(data.len() as u64, filename, radix);
}
}
fn print_elf_sizes(data: &[u8], filename: &str, radix: u32) {
if data.len() < 64 {
return;
}
let is_64 = data[4] == 2;
let (shoff, shentsize, shnum) = if is_64 {
(
read_u64(data, 40),
read_u16(data, 58) as u64,
read_u16(data, 60) as u64,
)
} else {
(
read_u32(data, 32) as u64,
read_u16(data, 46) as u64,
read_u16(data, 48) as u64,
)
};
let mut text_size = 0u64;
let mut data_size = 0u64;
let mut bss_size = 0u64;
for i in 0..shnum {
let shdr = (shoff + i * shentsize) as usize;
let sh_flags = if is_64 {
read_u64(data, shdr + 8)
} else {
read_u32(data, shdr + 8) as u64
};
let sh_size = if is_64 {
read_u64(data, shdr + 32)
} else {
read_u32(data, shdr + 20) as u64
};
let sh_type = read_u32(data, shdr + 4);
if sh_flags & 0x2 != 0 {
if sh_type == 8 {
bss_size += sh_size;
} else if sh_flags & 0x4 != 0 {
text_size += sh_size;
} else {
data_size += sh_size;
}
}
}
let total = text_size + data_size + bss_size;
print_row(text_size, data_size, bss_size, total, filename, radix);
}
fn print_macho_sizes(data: &[u8], filename: &str, radix: u32) {
let mut text_size = 0u64;
let mut data_size = 0u64;
let mut bss_size = 0u64;
if data.len() < 28 {
return;
}
let ncmds = read_u32(data, 16);
let mut offset = if read_u32(data, 4) == 0x01000007 {
32usize
} else {
28usize
};
for _ in 0..ncmds {
if offset + 8 > data.len() {
break;
}
let cmd = read_u32(data, offset);
let cmdsize = read_u32(data, offset + 4) as usize;
if cmdsize == 0 {
break;
}
if cmd == 0x19 {
let nsects = read_u32(data, offset + 64);
let sec_start = offset + 72;
for i in 0..nsects as usize {
let sec = sec_start + i * 80;
if sec + 80 > data.len() {
break;
}
let sflags = read_u32(data, sec + 68);
let ssize = read_u64(data, sec + 48);
if sflags & 0x400 != 0 {
bss_size += ssize;
} else if sflags & 0x4 != 0 {
text_size += ssize;
} else {
data_size += ssize;
}
}
}
offset += cmdsize;
}
let total = text_size + data_size + bss_size;
if total > 0 {
print_row(text_size, data_size, bss_size, total, filename, radix);
} else {
print_fallback(data.len() as u64, filename, radix);
}
}
fn print_fallback(total: u64, filename: &str, radix: u32) {
print_row(total, 0, 0, total, filename, radix);
}
fn print_row(text: u64, data: u64, bss: u64, total: u64, filename: &str, radix: u32) {
if radix == 16 {
println!(" text\t data\t bss\t dec\t hex\tfilename");
println!(
" {:x}\t {:x}\t {:x}\t {:x}\t {:x}\t{}",
text, data, bss, total, total, filename
);
} else {
println!(" text\t data\t bss\t dec\t hex\tfilename");
println!(
" {}\t {}\t {}\t {}\t {:x}\t{}",
text, data, bss, total, total, filename
);
}
}
fn is_elf(data: &[u8]) -> bool {
data.len() >= 4 && &data[0..4] == b"\x7fELF"
}
fn is_macho(data: &[u8]) -> bool {
if data.len() < 4 {
return false;
}
let magic = read_u32(data, 0);
matches!(magic, 0xFEEDFACE | 0xFEEDFACF | 0xCEFAEDFE | 0xCFFAEDFE)
|| (data.len() >= 4 && magic == 0xBEBAFECA)
}
fn read_u16(data: &[u8], offset: usize) -> u16 {
if offset + 2 > data.len() {
return 0;
}
u16::from_le_bytes([data[offset], data[offset + 1]])
}
fn read_u32(data: &[u8], offset: usize) -> u32 {
if offset + 4 > data.len() {
return 0;
}
u32::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
])
}
fn read_u64(data: &[u8], offset: usize) -> u64 {
if offset + 8 > data.len() {
return 0;
}
u64::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
data[offset + 4],
data[offset + 5],
data[offset + 6],
data[offset + 7],
])
}
}
pub mod llvm_strings {
use std::fs;
pub fn run(args: &[String]) {
let input = args
.iter()
.skip(1)
.find(|a| !a.starts_with('-'))
.cloned()
.unwrap_or_else(|| "input".into());
let min_len: usize = args
.iter()
.skip(1)
.position(|a| a == "-n" || a == "--bytes")
.and_then(|i| args.get(i + 2))
.and_then(|s| s.parse().ok())
.unwrap_or(4);
let print_offset = args.iter().any(|a| a == "-o" || a == "--print-offset");
let radix = if args.iter().any(|a| a == "-x" || a == "--radix=x") {
'x'
} else if args.iter().any(|a| a == "-d" || a == "--radix=d") {
'd'
} else {
'o'
};
match fs::read(&input) {
Ok(data) => {
let mut current = Vec::new();
let mut start_offset = 0usize;
for (i, &byte) in data.iter().enumerate() {
if byte.is_ascii_graphic() || byte == b' ' {
if current.is_empty() {
start_offset = i;
}
current.push(byte);
} else {
if current.len() >= min_len {
let s = String::from_utf8_lossy(¤t);
if print_offset {
match radix {
'x' => println!("{:8x} {}", start_offset, s),
'd' => println!("{:8} {}", start_offset, s),
_ => println!("{:8o} {}", start_offset, s),
}
} else {
println!("{}", s);
}
}
current.clear();
}
}
if current.len() >= min_len {
let s = String::from_utf8_lossy(¤t);
if print_offset {
match radix {
'x' => println!("{:8x} {}", start_offset, s),
'd' => println!("{:8} {}", start_offset, s),
_ => println!("{:8o} {}", start_offset, s),
}
} else {
println!("{}", s);
}
}
}
Err(e) => eprintln!("llvm-strings: {}: {}", input, e),
}
}
}
pub mod llvm_dwarfdump {
use std::fs;
pub fn run(args: &[String]) {
let input = args
.iter()
.skip(1)
.find(|a| !a.starts_with('-'))
.cloned()
.unwrap_or_else(|| "input.o".into());
let sections = args
.iter()
.skip(1)
.filter(|a| a.starts_with("--debug-"))
.map(|a| a.trim_start_matches("--debug-").to_string())
.collect::<Vec<_>>();
let all = sections.is_empty() || sections.iter().any(|s| s == "all");
match fs::read(&input) {
Ok(data) => {
if !is_elf(&data) {
eprintln!("llvm-dwarfdump: not a supported format");
return;
}
let debug_sections = find_debug_sections(&data);
if all || sections.iter().any(|s| s == "info") {
if let Some(sec) = debug_sections.get("debug_info") {
println!("{}", dump_debug_info(sec));
}
}
if all || sections.iter().any(|s| s == "abbrev") {
if let Some(sec) = debug_sections.get("debug_abbrev") {
println!("{}", dump_debug_abbrev(sec));
}
}
if all || sections.iter().any(|s| s == "line") {
if let Some(sec) = debug_sections.get("debug_line") {
println!("{}", dump_debug_line(sec));
}
}
if all || sections.iter().any(|s| s == "str") {
if let Some(sec) = debug_sections.get("debug_str") {
println!("{}", dump_debug_str(sec));
}
}
if all || sections.iter().any(|s| s == "ranges") {
if let Some(sec) = debug_sections.get("debug_ranges") {
println!("{}", dump_debug_ranges(sec));
}
}
if all || sections.iter().any(|s| s == "frame") {
if let Some(sec) = debug_sections.get("debug_frame") {
println!("{}", dump_debug_frame(sec));
}
}
if all || sections.iter().any(|s| s == "eh_frame") {
if let Some(sec) = debug_sections.get("eh_frame") {
println!("{}", dump_eh_frame(sec));
}
}
}
Err(e) => eprintln!("llvm-dwarfdump: {}: {}", input, e),
}
}
fn is_elf(data: &[u8]) -> bool {
data.len() >= 4 && &data[0..4] == b"\x7fELF"
}
fn find_debug_sections(data: &[u8]) -> std::collections::HashMap<String, Vec<u8>> {
let mut sections = std::collections::HashMap::new();
if data.len() < 64 {
return sections;
}
let is_64 = data[4] == 2;
let (shoff, shentsize, shnum, shstrndx) = if is_64 {
(
read_u64(data, 40),
read_u16(data, 58) as u64,
read_u16(data, 60) as u64,
read_u16(data, 62) as u64,
)
} else {
(
read_u32(data, 32) as u64,
read_u16(data, 46) as u64,
read_u16(data, 48) as u64,
read_u16(data, 50) as u64,
)
};
let shstr_offset = (shoff + shstrndx * shentsize) as usize;
let shstr_data_offset = if is_64 {
read_u64(data, shstr_offset + 24) as usize
} else {
read_u32(data, shstr_offset + 16) as usize
};
for i in 0..shnum {
let shdr = (shoff + i * shentsize) as usize;
let sh_name = read_u32(data, shdr);
let sh_offset = if is_64 {
read_u64(data, shdr + 24) as usize
} else {
read_u32(data, shdr + 16) as usize
};
let sh_size = if is_64 {
read_u64(data, shdr + 32) as usize
} else {
read_u32(data, shdr + 20) as usize
};
let name = read_cstr(data, shstr_data_offset + sh_name as usize);
if name.starts_with(".debug_") || name == ".eh_frame" {
let section_name = name.trim_start_matches('.').to_string();
if sh_offset + sh_size <= data.len() {
sections.insert(section_name, data[sh_offset..sh_offset + sh_size].to_vec());
}
}
}
sections
}
pub fn dump_debug_info(data: &[u8]) -> String {
let mut out = String::new();
out.push_str(".debug_info contents:\n");
if data.is_empty() {
out.push_str(" <empty>\n");
return out;
}
let mut offset = 0usize;
while offset + 11 <= data.len() {
let length = read_u32(data, offset) as usize;
let version = read_u16(data, offset + 4);
let _abbrev_offset = if data.len() >= offset + 10 {
read_u32(data, offset + 6) as usize
} else {
0
};
let _addr_size = data[offset + 10];
out.push_str(&format!(" Compilation Unit @ offset 0x{:x}:\n", offset));
out.push_str(&format!(" Length: {}\n", length));
out.push_str(&format!(" Version: {}\n", version));
out.push_str(&format!(" Abbrev Offset: 0x{:x}\n", _abbrev_offset));
out.push_str(&format!(" Address Size: {}\n", _addr_size));
if length == 0 {
break;
}
let next = if length == 0xFFFFFFFF {
offset + 12 + read_u64(data, offset + 4) as usize
} else {
offset + 4 + length
};
offset = next;
}
out
}
pub fn dump_debug_abbrev(data: &[u8]) -> String {
let mut out = String::new();
out.push_str(".debug_abbrev contents:\n");
if data.is_empty() {
out.push_str(" <empty>\n");
return out;
}
let mut offset = 0usize;
while offset < data.len() {
let code = read_uleb128(data, &mut offset);
if code == 0 {
break;
}
let tag = read_uleb128(data, &mut offset);
let has_children = data[offset];
offset += 1;
out.push_str(&format!(
" Abbrev {}: [{}] children:{}\n",
code,
tag_name(tag),
has_children
));
loop {
let attr = read_uleb128(data, &mut offset);
let form = read_uleb128(data, &mut offset);
if attr == 0 && form == 0 {
break;
}
out.push_str(&format!(
" AT_{}({}) FORM_{}({})\n",
attr_name(attr),
attr,
form_name(form),
form
));
}
}
out
}
pub fn dump_debug_line(data: &[u8]) -> String {
let mut out = String::new();
out.push_str(".debug_line contents:\n");
if data.len() < 10 {
out.push_str(" <empty>\n");
return out;
}
let mut offset = 0usize;
while offset + 10 <= data.len() {
let length = read_u32(data, offset) as usize;
let version = read_u16(data, offset + 4);
out.push_str(&format!(
" Line table @ 0x{:x}: length={}, version={}\n",
offset, length, version
));
if let Some(desc) = read_line_program(data, offset + 6, length.saturating_sub(4)) {
out.push_str(&desc);
}
let adv = if length == 0 { 4 } else { 4 + length };
offset += adv;
if adv == 0 {
break;
}
}
out
}
fn read_line_program(_data: &[u8], _offset: usize, _length: usize) -> Option<String> {
Some(format!(" ({} bytes of line number program)\n", _length))
}
pub fn dump_debug_str(data: &[u8]) -> String {
let mut out = String::new();
out.push_str(".debug_str contents:\n");
let mut start = 0usize;
for (i, &byte) in data.iter().enumerate() {
if byte == 0 {
if i > start {
let s = String::from_utf8_lossy(&data[start..i]);
out.push_str(&format!(" 0x{:08x} \"{}\"\n", start, s));
}
start = i + 1;
}
}
out
}
pub fn dump_debug_ranges(data: &[u8]) -> String {
let mut out = String::new();
out.push_str(".debug_ranges contents:\n");
let mut offset = 0usize;
while offset + 16 <= data.len() {
let begin = read_u64(data, offset);
let end = read_u64(data, offset + 8);
if begin == 0 && end == 0 {
out.push_str(&format!(" 0x{:08x} <end of list>\n", offset));
offset += 16;
continue;
}
if begin == 0xFFFFFFFF_FFFFFFFF && end != 0xFFFFFFFF_FFFFFFFF {
out.push_str(&format!(" 0x{:08x} base address 0x{:x}\n", offset, end));
} else {
out.push_str(&format!(
" 0x{:08x} [0x{:016x}, 0x{:016x})\n",
offset, begin, end
));
}
offset += 16;
if begin == 0 && end == 0 {
}
}
out
}
pub fn dump_debug_frame(data: &[u8]) -> String {
let mut out = String::new();
out.push_str(".debug_frame contents:\n");
if data.is_empty() {
out.push_str(" <empty>\n");
return out;
}
let mut offset = 0usize;
while offset + 4 <= data.len() {
let length = read_u32(data, offset) as usize;
if length == 0 {
break;
}
let cie_id = read_u32(data, offset + 4);
if cie_id == 0xFFFFFFFF {
out.push_str(&format!(" CIE @ 0x{:x}: length={}\n", offset, length));
} else {
out.push_str(&format!(
" FDE @ 0x{:x}: length={}, CIE=0x{:x}\n",
offset, length, cie_id
));
}
offset += 4 + length;
}
out
}
pub fn dump_eh_frame(data: &[u8]) -> String {
let mut out = String::new();
out.push_str(".eh_frame contents:\n");
if data.is_empty() {
out.push_str(" <empty>\n");
return out;
}
let mut offset = 0usize;
let mut cie_count = 0u32;
let mut fde_count = 0u32;
while offset + 4 <= data.len() {
let length = read_u32(data, offset) as usize;
if length == 0 {
out.push_str(&format!(" Terminator @ 0x{:x}\n", offset));
break;
}
let cie_id = read_u32(data, offset + 4);
if cie_id == 0 {
cie_count += 1;
let version = if offset + 8 < data.len() {
data[offset + 8]
} else {
0
};
let aug_start = offset + 9;
let aug_str = read_cstr(data, aug_start);
out.push_str(&format!(
" CIE #{} @ 0x{:x}: length={}, version={}, augmentation={}\n",
cie_count, offset, length, version, aug_str
));
} else {
fde_count += 1;
let pc_begin = if offset + 8 < data.len() {
read_u32(data, offset + 8) as u64
} else {
0
};
let pc_range = if offset + 12 < data.len() {
read_u32(data, offset + 12) as u64
} else {
0
};
out.push_str(&format!(
" FDE #{} @ 0x{:x}: length={}, CIE_pointer=0x{:x}, pc=0x{:08x}..0x{:08x}\n",
fde_count,
offset,
length,
cie_id,
pc_begin,
pc_begin + pc_range
));
}
offset += 4 + length;
}
out.push_str(&format!(
" Total: {} CIEs, {} FDEs\n",
cie_count, fde_count
));
out
}
fn read_u16(data: &[u8], offset: usize) -> u16 {
if offset + 2 > data.len() {
return 0;
}
u16::from_le_bytes([data[offset], data[offset + 1]])
}
fn read_u32(data: &[u8], offset: usize) -> u32 {
if offset + 4 > data.len() {
return 0;
}
u32::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
])
}
fn read_u64(data: &[u8], offset: usize) -> u64 {
if offset + 8 > data.len() {
return 0;
}
u64::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
data[offset + 4],
data[offset + 5],
data[offset + 6],
data[offset + 7],
])
}
fn read_cstr(data: &[u8], offset: usize) -> String {
let mut s = String::new();
let mut i = offset;
while i < data.len() && data[i] != 0 {
s.push(data[i] as char);
i += 1;
}
s
}
fn read_uleb128(data: &[u8], offset: &mut usize) -> u64 {
let mut result = 0u64;
let mut shift = 0u32;
loop {
if *offset >= data.len() {
break;
}
let byte = data[*offset];
*offset += 1;
result |= ((byte & 0x7F) as u64) << shift;
if byte & 0x80 == 0 {
break;
}
shift += 7;
}
result
}
fn tag_name(tag: u64) -> &'static str {
match tag {
0x01 => "DW_TAG_array_type",
0x11 => "DW_TAG_compile_unit",
0x2E => "DW_TAG_subprogram",
0x2D => "DW_TAG_subroutine_type",
0x01_00 => "DW_TAG_variable",
_ => "DW_TAG_unknown",
}
}
fn attr_name(attr: u64) -> &'static str {
match attr {
0x03 => "DW_AT_name",
0x10 => "DW_AT_stmt_list",
0x1B => "DW_AT_comp_dir",
0x25 => "DW_AT_producer",
0x27 => "DW_AT_language",
0x49 => "DW_AT_low_pc",
0x4A => "DW_AT_high_pc",
_ => "DW_AT_unknown",
}
}
fn form_name(form: u64) -> &'static str {
match form {
0x01 => "DW_FORM_addr",
0x03 => "DW_FORM_block2",
0x08 => "DW_FORM_string",
0x0B => "DW_FORM_data1",
0x0C => "DW_FORM_flag",
0x0D => "DW_FORM_sdata",
0x0F => "DW_FORM_udata",
0x10 => "DW_FORM_ref_addr",
0x19 => "DW_FORM_exprloc",
_ => "DW_FORM_unknown",
}
}
}
pub mod llvm_readobj {
use std::fs;
pub fn run(args: &[String]) {
let input = args
.iter()
.skip(1)
.find(|a| !a.starts_with('-'))
.cloned()
.unwrap_or_else(|| "input.o".into());
let sections_only = args.iter().any(|a| a == "-s" || a == "--sections");
let symbols_only = args.iter().any(|a| a == "-t" || a == "--symbols");
let all = !sections_only && !symbols_only;
match fs::read(&input) {
Ok(data) => {
if is_elf(&data) {
if all || !sections_only || !symbols_only {
let hdr = print_elf_header(&data);
println!("{}", hdr);
}
if all || sections_only {
let secs = print_elf_sections(&data);
println!("{}", secs);
}
if all || symbols_only {
let syms = print_elf_symbols(&data);
println!("{}", syms);
}
} else if is_macho(&data) {
println!("{}", print_macho_header(&data));
} else if is_wasm(&data) {
println!("{}", print_wasm_header(&data));
} else {
println!("File: {}\n Format: unknown", input);
}
}
Err(e) => eprintln!("llvm-readobj: {}: {}", input, e),
}
}
pub fn print_elf_header(data: &[u8]) -> String {
if data.len() < 64 {
return "ELF: file too short".into();
}
let is_64 = data[4] == 2;
let is_le = data[5] == 1;
let mut out = String::new();
out.push_str("ELF Header:\n");
out.push_str(&format!(
" Magic: {:02x} {:02x} {:02x} {:02x}\n",
data[0], data[1], data[2], data[3]
));
out.push_str(&format!(
" Class: {}\n",
if is_64 { "ELF64" } else { "ELF32" }
));
out.push_str(&format!(
" Data: {}\n",
if is_le {
"2's complement, little endian"
} else {
"2's complement, big endian"
}
));
let e_machine = read_u16_any(data, 18, is_le);
out.push_str(&format!(" Machine: {}\n", machine_name(e_machine)));
let e_entry = if is_64 {
read_u64_any(data, 24, is_le)
} else {
read_u32_any(data, 24, is_le) as u64
};
out.push_str(&format!(" Entry: 0x{:x}\n", e_entry));
out
}
pub fn print_elf_sections(data: &[u8]) -> String {
let mut out = String::new();
out.push_str("Sections:\n");
if data.len() < 64 {
return out;
}
let is_64 = data[4] == 2;
let is_le = data[5] == 1;
let (shoff, shentsize, shnum, shstrndx) = if is_64 {
(
read_u64_any(data, 40, is_le),
read_u16_any(data, 58, is_le) as u64,
read_u16_any(data, 60, is_le) as u64,
read_u16_any(data, 62, is_le) as u64,
)
} else {
(
read_u32_any(data, 32, is_le) as u64,
read_u16_any(data, 46, is_le) as u64,
read_u16_any(data, 48, is_le) as u64,
read_u16_any(data, 50, is_le) as u64,
)
};
let shstr_offset = (shoff + shstrndx * shentsize) as usize;
let shstr_data = if is_64 {
read_u64_any(data, shstr_offset + 24, is_le) as usize
} else {
read_u32_any(data, shstr_offset + 16, is_le) as usize
};
out.push_str(&format!(
" [Nr] Name Type Address Size Flags\n",
));
for i in 0..shnum.min(64) {
let shdr = (shoff + i * shentsize) as usize;
let sh_name = read_u32_any(data, shdr, is_le);
let sh_type = read_u32_any(data, shdr + 4, is_le);
let sh_flags = if is_64 {
read_u64_any(data, shdr + 8, is_le)
} else {
read_u32_any(data, shdr + 8, is_le) as u64
};
let sh_addr = if is_64 {
read_u64_any(data, shdr + 16, is_le)
} else {
read_u32_any(data, shdr + 12, is_le) as u64
};
let sh_size = if is_64 {
read_u64_any(data, shdr + 32, is_le)
} else {
read_u32_any(data, shdr + 20, is_le) as u64
};
let name = read_cstr(data, shstr_data + sh_name as usize);
let sf = if sh_flags & 0x4 != 0 { "X" } else { " " };
let sw = if sh_flags & 0x1 != 0 { "W" } else { " " };
let sa = if sh_flags & 0x2 != 0 { "A" } else { " " };
out.push_str(&format!(
" [{:2}] {:<16} {:<15} {:08x} {:08x} {}{}{}\n",
i,
truncate_str(&name, 16),
section_type_name(sh_type),
sh_addr,
sh_size,
sa,
sw,
sf,
));
}
out
}
pub fn print_elf_symbols(data: &[u8]) -> String {
let mut out = String::new();
out.push_str("Symbols:\n");
if data.len() < 64 {
return out;
}
let is_64 = data[4] == 2;
let is_le = data[5] == 1;
let (shoff, shentsize, shnum, shstrndx) = if is_64 {
(
read_u64_any(data, 40, is_le),
read_u16_any(data, 58, is_le) as u64,
read_u16_any(data, 60, is_le) as u64,
read_u16_any(data, 62, is_le) as u64,
)
} else {
(
read_u32_any(data, 32, is_le) as u64,
read_u16_any(data, 46, is_le) as u64,
read_u16_any(data, 48, is_le) as u64,
read_u16_any(data, 50, is_le) as u64,
)
};
let shstr_offset = (shoff + shstrndx * shentsize) as usize;
let _shstr_data = if is_64 {
read_u64_any(data, shstr_offset + 24, is_le) as usize
} else {
read_u32_any(data, shstr_offset + 16, is_le) as usize
};
for i in 0..shnum {
let shdr = (shoff + i * shentsize) as usize;
let sh_type = read_u32_any(data, shdr + 4, is_le);
if sh_type == 2 || sh_type == 11 {
let sh_link = read_u32_any(data, shdr + 40, is_le) as u64;
let str_shdr = (shoff + sh_link * shentsize) as usize;
let str_data = if is_64 {
read_u64_any(data, str_shdr + 24, is_le) as usize
} else {
read_u32_any(data, str_shdr + 16, is_le) as usize
};
let sh_offset = if is_64 {
read_u64_any(data, shdr + 24, is_le)
} else {
read_u32_any(data, shdr + 16, is_le) as u64
};
let sh_entsize = if is_64 {
read_u64_any(data, shdr + 56, is_le)
} else {
read_u32_any(data, shdr + 36, is_le) as u64
};
let sh_size = if is_64 {
read_u64_any(data, shdr + 32, is_le)
} else {
read_u32_any(data, shdr + 20, is_le) as u64
};
let count = if sh_entsize > 0 {
sh_size / sh_entsize
} else {
0
};
for j in 0..count.min(100) {
let sym = (sh_offset as usize) + (j as usize) * (sh_entsize as usize);
if sym + 8 > data.len() {
break;
}
let st_name = read_u32_any(data, sym, is_le) as usize;
let st_value = if is_64 {
read_u64_any(data, sym + 8, is_le)
} else {
read_u32_any(data, sym + 4, is_le) as u64
};
if st_name > 0 {
let name = read_cstr(data, str_data + st_name);
out.push_str(&format!(" {:>4}: {:08x} {}\n", j, st_value, name));
}
}
}
}
out
}
pub fn print_elf_relocations(_data: &[u8]) -> String {
"Relocations: (see llvm-objdump -r)\n".into()
}
pub fn print_elf_program_headers(_data: &[u8]) -> String {
"Program Headers: (see llvm-readobj -l)\n".into()
}
pub fn print_elf_dynamic(_data: &[u8]) -> String {
"Dynamic Section: (see llvm-readobj -d)\n".into()
}
pub fn print_elf_notes(_data: &[u8]) -> String {
"Notes: (none)\n".into()
}
pub fn print_macho_header(data: &[u8]) -> String {
if data.len() < 28 {
return "Mach-O: file too short".into();
}
let mut out = String::new();
let magic = read_u32(data, 0);
let is_64 = magic == 0xFEEDFACF || magic == 0xCFFAEDFE;
out.push_str("Mach-O Header:\n");
out.push_str(&format!(" Magic: 0x{:08x}\n", magic));
out.push_str(&format!(
" CPU: {}\n",
macho_cpu_name(read_u32(data, 4))
));
out.push_str(&format!(
" File: {}\n",
macho_filetype_name(read_u32(data, if is_64 { 12 } else { 12 }))
));
let ncmds = read_u32(data, if is_64 { 20 } else { 16 });
out.push_str(&format!(" Load commands: {}\n", ncmds));
out
}
pub fn print_coff_header(data: &[u8]) -> String {
if data.len() < 20 {
return "COFF: file too short".into();
}
let mut out = String::new();
out.push_str("COFF Header:\n");
let machine = read_u16(data, 0);
let nsections = read_u16(data, 2);
out.push_str(&format!(" Machine: 0x{:04x}\n", machine));
out.push_str(&format!(" Sections: {}\n", nsections));
out
}
pub fn print_wasm_header(data: &[u8]) -> String {
let mut out = String::new();
out.push_str("WebAssembly Module:\n");
out.push_str(&format!(" Size: {} bytes\n", data.len()));
if data.len() >= 8 && &data[0..4] == b"\0asm" {
let version = read_u32(data, 4);
out.push_str(&format!(" Version: {}\n", version));
let mut offset = 8usize;
let mut section_count = 0;
while offset < data.len() {
let id = data[offset];
offset += 1;
let size = read_uleb128_simple(data, &mut offset);
out.push_str(&format!(
" Section {}: id={} size={}\n",
section_count, id, size
));
offset += size;
section_count += 1;
if section_count > 20 {
break;
}
}
}
out
}
fn is_elf(data: &[u8]) -> bool {
data.len() >= 4 && &data[0..4] == b"\x7fELF"
}
fn is_macho(data: &[u8]) -> bool {
if data.len() < 4 {
return false;
}
matches!(
read_u32(data, 0),
0xFEEDFACE | 0xFEEDFACF | 0xCEFAEDFE | 0xCFFAEDFE
)
}
fn is_wasm(data: &[u8]) -> bool {
data.len() >= 4 && &data[0..4] == b"\0asm"
}
fn read_u16(data: &[u8], offset: usize) -> u16 {
u16::from_le_bytes([data[offset], data[offset + 1]])
}
fn read_u16_any(data: &[u8], offset: usize, le: bool) -> u16 {
if le {
u16::from_le_bytes([data[offset], data[offset + 1]])
} else {
u16::from_be_bytes([data[offset], data[offset + 1]])
}
}
fn read_u32(data: &[u8], offset: usize) -> u32 {
u32::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
])
}
fn read_u32_any(data: &[u8], offset: usize, le: bool) -> u32 {
if le {
u32::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
])
} else {
u32::from_be_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
])
}
}
fn read_u64_any(data: &[u8], offset: usize, le: bool) -> u64 {
if le {
u64::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
data[offset + 4],
data[offset + 5],
data[offset + 6],
data[offset + 7],
])
} else {
u64::from_be_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
data[offset + 4],
data[offset + 5],
data[offset + 6],
data[offset + 7],
])
}
}
fn read_cstr(data: &[u8], offset: usize) -> String {
let mut s = String::new();
let mut i = offset;
while i < data.len() && data[i] != 0 {
s.push(data[i] as char);
i += 1;
}
s
}
fn read_uleb128_simple(data: &[u8], offset: &mut usize) -> usize {
let mut result = 0usize;
let mut shift = 0u32;
loop {
let byte = data[*offset];
*offset += 1;
result |= ((byte & 0x7F) as usize) << shift;
if byte & 0x80 == 0 {
break;
}
shift += 7;
}
result
}
fn machine_name(m: u16) -> &'static str {
match m {
0x03 => "i386",
0x3E => "x86_64",
0x28 => "ARM",
0xB7 => "AArch64",
0xF3 => "RISC-V",
0x08 => "MIPS",
_ => "unknown",
}
}
fn section_type_name(t: u32) -> &'static str {
match t {
0 => "NULL",
1 => "PROGBITS",
2 => "SYMTAB",
3 => "STRTAB",
4 => "RELA",
5 => "HASH",
6 => "DYNAMIC",
7 => "NOTE",
8 => "NOBITS",
9 => "REL",
11 => "DYNSYM",
_ => "UNKNOWN",
}
}
fn macho_cpu_name(cpu: u32) -> &'static str {
match cpu {
7 => "x86",
0x0100_0007 => "x86_64",
12 => "ARM",
0x0100_000C => "AArch64",
18 => "PowerPC",
_ => "unknown",
}
}
fn macho_filetype_name(ft: u32) -> &'static str {
match ft {
1 => "OBJECT",
2 => "EXECUTE",
6 => "DYLIB",
_ => "UNKNOWN",
}
}
fn truncate_str(s: &str, max: usize) -> &str {
if s.len() > max {
&s[..max]
} else {
s
}
}
}
pub mod llvm_readelf {
use std::fs;
pub fn run(args: &[String]) {
let input = args
.iter()
.skip(1)
.find(|a| !a.starts_with('-'))
.cloned()
.unwrap_or_else(|| "a.out".into());
let all = !args.iter().any(|a| {
a.starts_with("--file-header")
|| a.starts_with("--section-headers")
|| a.starts_with("--program-headers")
|| a.starts_with("--syms")
|| a.starts_with("--relocs")
|| a.starts_with("--dynamic")
|| a.starts_with("--notes")
});
match fs::read(&input) {
Ok(data) => {
if !is_elf(&data) {
eprintln!("llvm-readelf: not an ELF file");
return;
}
if all || args.iter().any(|a| a == "--file-header" || a == "-h") {
println!("{}", print_file_header(&data));
}
if all || args.iter().any(|a| a == "--section-headers" || a == "-S") {
println!("{}", print_section_headers(&data));
}
if args.iter().any(|a| a == "--program-headers" || a == "-l") {
println!("{}", print_program_headers(&data));
}
if all || args.iter().any(|a| a == "--syms" || a == "-s") {
println!("{}", print_symbol_tables(&data));
}
if args.iter().any(|a| a == "--relocs" || a == "-r") {
println!("{}", print_relocations(&data));
}
if args.iter().any(|a| a == "--dynamic" || a == "-d") {
println!("{}", print_dynamic(&data));
}
if args.iter().any(|a| a == "--notes" || a == "-n") {
println!("{}", print_notes(&data));
}
}
Err(e) => eprintln!("llvm-readelf: {}: {}", input, e),
}
}
pub fn print_file_header(data: &[u8]) -> String {
let mut out = String::new();
out.push_str("ELF Header:\n");
if data.len() < 64 {
out.push_str(" <truncated>\n");
return out;
}
let is_64 = data[4] == 2;
let is_le = data[5] == 1;
out.push_str(&format!(" Magic: {:02x?}\n", &data[0..16]));
out.push_str(&format!(
" Class: {}\n",
if is_64 { "ELF64" } else { "ELF32" }
));
out.push_str(&format!(
" Data: {}\n",
if is_le {
"2's complement, little endian"
} else {
"big endian"
}
));
out.push_str(&format!(" Version: {}\n", data[6]));
out.push_str(&format!(" OS/ABI: 0x{:02x}\n", data[7]));
out.push_str(&format!(" ABI Version: {}\n", data[8]));
let e_type = read_u16(data, 16);
let e_machine = read_u16(data, 18);
out.push_str(&format!(
" Type: {} (0x{:x})\n",
elftype_name(e_type),
e_type
));
out.push_str(&format!(
" Machine: {} (0x{:x})\n",
machine_name(e_machine),
e_machine
));
let e_entry = if is_64 {
read_u64(data, 24)
} else {
read_u32(data, 24) as u64
};
out.push_str(&format!(" Entry point address: 0x{:x}\n", e_entry));
let (phoff, shoff, phnum, shnum) = if is_64 {
(
read_u64(data, 32),
read_u64(data, 40),
read_u16(data, 56),
read_u16(data, 60),
)
} else {
(
read_u32(data, 28) as u64,
read_u32(data, 32) as u64,
read_u16(data, 44),
read_u16(data, 48),
)
};
out.push_str(&format!(
" Start of program headers: {} (bytes into file)\n",
phoff
));
out.push_str(&format!(
" Start of section headers: {} (bytes into file)\n",
shoff
));
out.push_str(&format!(" Number of program headers: {}\n", phnum));
out.push_str(&format!(" Number of section headers: {}\n", shnum));
out
}
pub fn print_section_headers(data: &[u8]) -> String {
let mut out = String::new();
out.push_str("Section Headers:\n");
if data.len() < 64 {
out.push_str(" <truncated>\n");
return out;
}
let is_64 = data[4] == 2;
let (shoff, shentsize, shnum, shstrndx) = if is_64 {
(
read_u64(data, 40),
read_u16(data, 58) as u64,
read_u16(data, 60) as u64,
read_u16(data, 62) as u64,
)
} else {
(
read_u32(data, 32) as u64,
read_u16(data, 46) as u64,
read_u16(data, 48) as u64,
read_u16(data, 50) as u64,
)
};
out.push_str(&format!(" [Nr] Name Type Address Offset Size EntSize Flags\n"));
let shstr_off = (shoff + shstrndx * shentsize) as usize;
let shstr_data = if is_64 {
read_u64(data, shstr_off + 24) as usize
} else {
read_u32(data, shstr_off + 16) as usize
};
for i in 0..shnum.min(50) {
let shdr = (shoff + i * shentsize) as usize;
let sh_name = read_u32(data, shdr) as usize;
let sh_type = read_u32(data, shdr + 4);
let sh_flags = if is_64 {
read_u64(data, shdr + 8)
} else {
read_u32(data, shdr + 8) as u64
};
let sh_addr = if is_64 {
read_u64(data, shdr + 16)
} else {
read_u32(data, shdr + 12) as u64
};
let sh_offset = if is_64 {
read_u64(data, shdr + 24)
} else {
read_u32(data, shdr + 16) as u64
};
let sh_size = if is_64 {
read_u64(data, shdr + 32)
} else {
read_u32(data, shdr + 20) as u64
};
let sh_entsize = if is_64 {
read_u64(data, shdr + 56)
} else {
read_u32(data, shdr + 36) as u64
};
let name = read_cstr(data, shstr_data + sh_name);
let flags = section_flags(sh_flags);
let type_name = section_type_name(sh_type);
out.push_str(&format!(
" [{:2}] {:16.16} {:16.16} {:016x} {:08x} {:08x} {:08x} {}\n",
i,
truncate_str(&name, 16),
truncate_str(type_name, 16),
sh_addr,
sh_offset,
sh_size,
sh_entsize,
flags
));
}
out
}
pub fn print_program_headers(data: &[u8]) -> String {
let mut out = String::new();
out.push_str("Program Headers:\n");
if data.len() < 64 {
out.push_str(" <truncated>\n");
return out;
}
let is_64 = data[4] == 2;
let (phoff, phentsize, phnum) = if is_64 {
(
read_u64(data, 32),
read_u16(data, 54) as u64,
read_u16(data, 56),
)
} else {
(
read_u32(data, 28) as u64,
read_u16(data, 42) as u64,
read_u16(data, 44),
)
};
out.push_str(" Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align\n");
for i in 0..phnum.min(20) {
let phdr = (phoff + i as u64 * phentsize) as usize;
let p_type = read_u32(data, phdr);
let (p_offset, p_vaddr, p_paddr, p_filesz, p_memsz, p_flags, p_align) = if is_64 {
(
read_u64(data, phdr + 8),
read_u64(data, phdr + 16),
read_u64(data, phdr + 24),
read_u64(data, phdr + 32),
read_u64(data, phdr + 40),
read_u32(data, phdr + 4),
read_u64(data, phdr + 48),
)
} else {
(
read_u32(data, phdr + 4) as u64,
read_u32(data, phdr + 8) as u64,
read_u32(data, phdr + 12) as u64,
read_u32(data, phdr + 16) as u64,
read_u32(data, phdr + 20) as u64,
read_u32(data, phdr + 24),
read_u32(data, phdr + 28) as u64,
)
};
out.push_str(&format!(
" {:14.14} 0x{:016x} 0x{:016x} 0x{:016x} 0x{:016x} 0x{:016x} {:3} 0x{:x}\n",
phdr_type_name(p_type),
p_offset,
p_vaddr,
p_paddr,
p_filesz,
p_memsz,
segment_flags(p_flags),
p_align
));
}
out
}
pub fn print_symbol_tables(data: &[u8]) -> String {
let mut out = String::new();
out.push_str("Symbol Tables:\n");
if data.len() < 64 {
out.push_str(" <truncated>\n");
return out;
}
let is_64 = data[4] == 2;
let (shoff, shentsize, shnum, shstrndx) = if is_64 {
(
read_u64(data, 40),
read_u16(data, 58) as u64,
read_u16(data, 60) as u64,
read_u16(data, 62) as u64,
)
} else {
(
read_u32(data, 32) as u64,
read_u16(data, 46) as u64,
read_u16(data, 48) as u64,
read_u16(data, 50) as u64,
)
};
let shstr_off = (shoff + shstrndx * shentsize) as usize;
let shstr_data = if is_64 {
read_u64(data, shstr_off + 24) as usize
} else {
read_u32(data, shstr_off + 16) as usize
};
for i in 0..shnum.min(50) {
let shdr = (shoff + i * shentsize) as usize;
let sh_type = read_u32(data, shdr + 4);
if sh_type == 2 || sh_type == 11 {
let sh_link = read_u32(data, shdr + 40) as u64;
let str_shdr = (shoff + sh_link * shentsize) as usize;
let str_data = if is_64 {
read_u64(data, str_shdr + 24) as usize
} else {
read_u32(data, str_shdr + 16) as usize
};
let sh_name = read_u32(data, shdr) as usize;
let sname = read_cstr(data, shstr_data + sh_name);
out.push_str(&format!("Symbol table '{}':\n", sname));
out.push_str(" Num: Value Size Type Bind Vis Ndx Name\n");
let sh_offset = if is_64 {
read_u64(data, shdr + 24)
} else {
read_u32(data, shdr + 16) as u64
};
let sh_entsize = if is_64 {
read_u64(data, shdr + 56)
} else {
read_u32(data, shdr + 36) as u64
};
let sh_size = if is_64 {
read_u64(data, shdr + 32)
} else {
read_u32(data, shdr + 20) as u64
};
let count = if sh_entsize > 0 {
sh_size / sh_entsize
} else {
0
};
for j in 0..count.min(100) {
let sym = (sh_offset as usize) + (j as usize) * (sh_entsize as usize);
if sym + 8 > data.len() {
break;
}
let st_name = read_u32(data, sym) as usize;
let st_value = if is_64 {
read_u64(data, sym + 8)
} else {
read_u32(data, sym + 4) as u64
};
let st_size = if is_64 {
read_u64(data, sym + 16)
} else {
read_u32(data, sym + 8) as u64
};
let st_info = if sym + 4 <= data.len() {
data[sym + 4]
} else {
0
};
let st_other = if sym + 5 <= data.len() {
data[sym + 5]
} else {
0
};
let st_shndx = read_u16(data, sym + 6);
let name = if st_name > 0 {
read_cstr(data, str_data + st_name)
} else {
"".to_string()
};
let bind = match st_info >> 4 {
0 => "LOCAL",
1 => "GLOBAL",
2 => "WEAK",
_ => "UNKNOWN",
};
let stype = match st_info & 0xF {
0 => "NOTYPE",
1 => "OBJECT",
2 => "FUNC",
3 => "SECTION",
4 => "FILE",
_ => "UNKNOWN",
};
let vis = match st_other & 0x3 {
0 => "DEFAULT",
1 => "INTERNAL",
2 => "HIDDEN",
_ => "PROTECTED",
};
out.push_str(&format!(
" {:3}: {:016x} {:8} {:6} {:6} {:8} {:4} {}\n",
j, st_value, st_size, stype, bind, vis, st_shndx, name
));
}
}
}
out
}
pub fn print_relocations(data: &[u8]) -> String {
let mut out = String::new();
out.push_str("Relocations:\n");
if data.len() < 64 {
out.push_str(" <truncated>\n");
return out;
}
let is_64 = data[4] == 2;
let (shoff, shentsize, shnum) = if is_64 {
(
read_u64(data, 40),
read_u16(data, 58) as u64,
read_u16(data, 60) as u64,
)
} else {
(
read_u32(data, 32) as u64,
read_u16(data, 46) as u64,
read_u16(data, 48) as u64,
)
};
let mut found = false;
for i in 0..shnum.min(50) {
let shdr = (shoff + i * shentsize) as usize;
let sh_type = read_u32(data, shdr + 4);
if sh_type == 4 || sh_type == 9 {
found = true;
out.push_str(&format!("Section {}:\n", i));
out.push_str(" Offset Info Type Sym. Value Sym. Name + Addend\n");
let sh_offset = if is_64 {
read_u64(data, shdr + 24)
} else {
read_u32(data, shdr + 16) as u64
};
let sh_entsize = if is_64 {
read_u64(data, shdr + 56)
} else {
read_u32(data, shdr + 36) as u64
};
let sh_size = if is_64 {
read_u64(data, shdr + 32)
} else {
read_u32(data, shdr + 20) as u64
};
let count = if sh_entsize > 0 {
sh_size / sh_entsize
} else {
0
};
for j in 0..count.min(50) {
let rel = (sh_offset as usize) + (j as usize) * (sh_entsize as usize);
if rel + 16 > data.len() {
break;
}
let r_offset = if is_64 {
read_u64(data, rel)
} else {
read_u32(data, rel) as u64
};
let r_info = if is_64 {
read_u64(data, rel + 8)
} else {
read_u32(data, rel + 4) as u64
};
let r_addend = if sh_type == 4 {
if is_64 {
read_u64(data, rel + 16)
} else {
read_u32(data, rel + 8) as u64
}
} else {
0
};
out.push_str(&format!(
" {:016x} {:016x} {:14} {:016x} <sym> + {:x}\n",
r_offset,
r_info,
reloc_type_name(r_info),
0u64,
r_addend
));
}
}
}
if !found {
out.push_str(" None found\n");
}
out
}
pub fn print_dynamic(data: &[u8]) -> String {
let mut out = String::new();
out.push_str("Dynamic section:\n");
if data.len() < 64 {
out.push_str(" <truncated>\n");
return out;
}
let is_64 = data[4] == 2;
let (shoff, shentsize, shnum) = if is_64 {
(
read_u64(data, 40),
read_u16(data, 58) as u64,
read_u16(data, 60) as u64,
)
} else {
(
read_u32(data, 32) as u64,
read_u16(data, 46) as u64,
read_u16(data, 48) as u64,
)
};
for i in 0..shnum.min(50) {
let shdr = (shoff + i * shentsize) as usize;
let sh_type = read_u32(data, shdr + 4);
if sh_type == 6 {
let sh_offset = if is_64 {
read_u64(data, shdr + 24) as usize
} else {
read_u32(data, shdr + 16) as usize
};
let sh_size = if is_64 {
read_u64(data, shdr + 32) as usize
} else {
read_u32(data, shdr + 20) as usize
};
let mut off = sh_offset;
while off + 16 <= sh_offset + sh_size && off + 16 <= data.len() {
let d_tag = if is_64 {
read_u64(data, off)
} else {
read_u32(data, off) as u64
};
let d_val = if is_64 {
read_u64(data, off + 8)
} else {
read_u32(data, off + 4) as u64
};
if d_tag == 0 {
break;
}
out.push_str(&format!(
" 0x{:016x} ({:30}) 0x{:x}\n",
d_tag,
dyn_tag_name(d_tag),
d_val
));
off += if is_64 { 16 } else { 8 };
}
break;
}
}
out
}
pub fn print_notes(data: &[u8]) -> String {
let mut out = String::new();
out.push_str("Notes:\n");
if data.len() < 64 {
out.push_str(" <truncated>\n");
return out;
}
let is_64 = data[4] == 2;
let (shoff, shentsize, shnum) = if is_64 {
(
read_u64(data, 40),
read_u16(data, 58) as u64,
read_u16(data, 60) as u64,
)
} else {
(
read_u32(data, 32) as u64,
read_u16(data, 46) as u64,
read_u16(data, 48) as u64,
)
};
for i in 0..shnum.min(50) {
let shdr = (shoff + i * shentsize) as usize;
let sh_type = read_u32(data, shdr + 4);
if sh_type == 7 {
let sh_offset = if is_64 {
read_u64(data, shdr + 24) as usize
} else {
read_u32(data, shdr + 16) as usize
};
let sh_size = if is_64 {
read_u64(data, shdr + 32) as usize
} else {
read_u32(data, shdr + 20) as usize
};
let mut off = sh_offset;
while off + 12 <= sh_offset + sh_size && off + 12 <= data.len() {
let n_namesz = read_u32(data, off) as usize;
let n_descsz = read_u32(data, off + 4) as usize;
let n_type = read_u32(data, off + 8);
let name_start = off + 12;
let name = read_cstr(data, name_start);
let desc_start = name_start + n_namesz;
let desc_end = desc_start + n_descsz;
let desc = if desc_end <= data.len() && desc_start < desc_end {
format!("{} bytes", n_descsz)
} else {
"<empty>".to_string()
};
out.push_str(&format!(
" Owner: {} Type: 0x{:x} Description: {}\n",
name, n_type, desc
));
let align = 4usize;
off = ((desc_end + align - 1) / align) * align;
}
break;
}
}
out
}
fn is_elf(data: &[u8]) -> bool {
data.len() >= 4 && &data[0..4] == b"\x7fELF"
}
fn read_u16(data: &[u8], offset: usize) -> u16 {
if offset + 2 > data.len() {
0
} else {
u16::from_le_bytes([data[offset], data[offset + 1]])
}
}
fn read_u32(data: &[u8], offset: usize) -> u32 {
if offset + 4 > data.len() {
0
} else {
u32::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
])
}
}
fn read_u64(data: &[u8], offset: usize) -> u64 {
if offset + 8 > data.len() {
0
} else {
u64::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
data[offset + 4],
data[offset + 5],
data[offset + 6],
data[offset + 7],
])
}
}
fn read_cstr(data: &[u8], offset: usize) -> String {
let mut s = String::new();
let mut i = offset;
while i < data.len() && data[i] != 0 {
s.push(data[i] as char);
i += 1;
}
s
}
fn elftype_name(t: u16) -> &'static str {
match t {
0 => "NONE",
1 => "REL",
2 => "EXEC",
3 => "DYN",
4 => "CORE",
_ => "UNKNOWN",
}
}
fn machine_name(m: u16) -> &'static str {
match m {
0x03 => "i386",
0x3E => "x86_64",
0x28 => "ARM",
0xB7 => "AArch64",
0xF3 => "RISC-V",
0x08 => "MIPS",
0x14 => "PowerPC",
_ => "unknown",
}
}
fn section_type_name(t: u32) -> &'static str {
match t {
0 => "NULL",
1 => "PROGBITS",
2 => "SYMTAB",
3 => "STRTAB",
4 => "RELA",
5 => "HASH",
6 => "DYNAMIC",
7 => "NOTE",
8 => "NOBITS",
9 => "REL",
10 => "SHLIB",
11 => "DYNSYM",
_ => "UNKNOWN",
}
}
fn section_flags(flags: u64) -> String {
let mut s = String::new();
if flags & 1 != 0 {
s.push('W');
} else {
s.push(' ');
}
if flags & 2 != 0 {
s.push('A');
} else {
s.push(' ');
}
if flags & 4 != 0 {
s.push('X');
} else {
s.push(' ');
}
s
}
fn phdr_type_name(t: u32) -> &'static str {
match t {
0 => "NULL",
1 => "LOAD",
2 => "DYNAMIC",
3 => "INTERP",
4 => "NOTE",
5 => "SHLIB",
6 => "PHDR",
7 => "TLS",
_ => "UNKNOWN",
}
}
fn segment_flags(f: u32) -> String {
let mut s = String::new();
if f & 4 != 0 {
s.push('R');
} else {
s.push(' ');
}
if f & 2 != 0 {
s.push('W');
} else {
s.push(' ');
}
if f & 1 != 0 {
s.push('E');
} else {
s.push(' ');
}
s
}
fn dyn_tag_name(tag: u64) -> &'static str {
match tag {
0 => "NULL",
1 => "NEEDED",
2 => "PLTRELSZ",
3 => "PLTGOT",
4 => "HASH",
5 => "STRTAB",
6 => "SYMTAB",
7 => "RELA",
8 => "RELASZ",
9 => "RELAENT",
10 => "STRSZ",
11 => "SYMENT",
12 => "INIT",
13 => "FINI",
14 => "SONAME",
15 => "RPATH",
_ => "UNKNOWN",
}
}
fn reloc_type_name(r_info: u64) -> String {
let t = if r_info > 0xFFFFFFFF {
(r_info & 0xFFFFFFFF) as u32
} else {
r_info as u32
};
match t {
1 => "R_386_32".to_string(),
2 => "R_386_PC32".to_string(),
7 => "R_X86_64_64".to_string(),
_ => format!("0x{:x}", t),
}
}
fn truncate_str(s: &str, max_len: usize) -> String {
if s.len() <= max_len {
s.to_string()
} else {
format!("{}..", &s[..max_len.saturating_sub(2)])
}
}
}
pub mod llvm_symbolizer {
use std::fs;
pub struct SymbolInfo {
pub function_name: String,
pub source_file: String,
pub source_line: u32,
pub start_address: u64,
}
#[derive(Clone)]
pub struct ElfSymbol {
pub name: String,
pub value: u64,
pub size: u64,
}
#[derive(Clone)]
pub struct LineInfo {
pub file_name: String,
pub line: u32,
pub column: u32,
pub address: u64,
}
pub fn run(args: &[String]) {
let addresses: Vec<u64> = args
.iter()
.skip(1)
.filter_map(|a| {
if a.starts_with("0x") {
u64::from_str_radix(&a[2..], 16).ok()
} else {
a.parse::<u64>().ok()
}
})
.collect();
let obj_file = args
.iter()
.skip(1)
.find(|a| !a.starts_with("0x") && !a.chars().all(|c| c.is_ascii_hexdigit()))
.cloned()
.unwrap_or_else(|| "a.out".into());
let data = match fs::read(&obj_file) {
Ok(d) => d,
Err(e) => {
eprintln!("llvm-symbolizer: cannot read {}: {}", obj_file, e);
return;
}
};
for &addr in &addresses {
match symbolize_address(&data, addr) {
Some(info) => {
println!(
"{}\n{}:{}\n",
info.function_name,
if info.source_file.is_empty() {
"??"
} else {
&info.source_file
},
info.source_line
);
}
None => {
println!("??\n??:0\n");
}
}
}
}
pub fn symbolize_address(object_file: &[u8], address: u64) -> Option<SymbolInfo> {
if !is_elf(object_file) {
return None;
}
let symbols = extract_elf_symbols(object_file);
let sym = find_symbol_at_address(&symbols, address)?;
let (func_name, _addr, _line) =
find_function_for_address(object_file, address).unwrap_or_default();
Some(SymbolInfo {
function_name: if func_name.is_empty() {
sym.name.clone()
} else {
func_name
},
source_file: String::new(),
source_line: _line,
start_address: sym.value,
})
}
pub fn find_symbol_at_address(symbols: &[ElfSymbol], addr: u64) -> Option<ElfSymbol> {
let mut best: Option<&ElfSymbol> = None;
for sym in symbols {
if sym.value <= addr && sym.value + sym.size > addr {
match best {
None => best = Some(sym),
Some(b) if sym.value > b.value => best = Some(sym),
_ => {}
}
}
}
best.cloned()
}
pub fn find_function_for_address(_data: &[u8], addr: u64) -> Option<(String, u64, u32)> {
let symbols = extract_elf_symbols(_data);
let sym = find_symbol_at_address(&symbols, addr)?;
Some((sym.name, sym.value, 0))
}
pub fn find_line_for_address(
_data: &[u8],
addr: u64,
_line_info: &[LineInfo],
) -> Option<(String, u32)> {
if _line_info.is_empty() {
let symbols = extract_elf_symbols(_data);
let sym = find_symbol_at_address(&symbols, addr)?;
return Some((sym.name, 0));
}
let mut best: Option<&LineInfo> = None;
for li in _line_info {
if li.address <= addr {
match best {
None => best = Some(li),
Some(b) if li.address > b.address => best = Some(li),
_ => {}
}
}
}
best.map(|li| (li.file_name.clone(), li.line))
}
fn extract_elf_symbols(data: &[u8]) -> Vec<ElfSymbol> {
let mut syms = Vec::new();
if !is_elf(data) || data.len() < 64 {
return syms;
}
let is_64 = data[4] == 2;
let (shoff, shentsize, shnum) = if is_64 {
(
read_u64(data, 40),
read_u16(data, 58) as u64,
read_u16(data, 60) as u64,
)
} else {
(
read_u32(data, 32) as u64,
read_u16(data, 46) as u64,
read_u16(data, 48) as u64,
)
};
let shstrndx = if is_64 {
read_u16(data, 62) as u64
} else {
read_u16(data, 50) as u64
};
let shstr_offset = (shoff + shstrndx * shentsize) as usize;
let _shstr_data = if is_64 {
read_u64(data, shstr_offset + 24) as usize
} else {
read_u32(data, shstr_offset + 16) as usize
};
for i in 0..shnum {
let shdr = (shoff + i * shentsize) as usize;
let sh_type = read_u32(data, shdr + 4);
if sh_type == 2 || sh_type == 11 {
let sh_link = read_u32(data, shdr + 40) as u64;
let str_shdr = (shoff + sh_link * shentsize) as usize;
let str_data = if is_64 {
read_u64(data, str_shdr + 24) as usize
} else {
read_u32(data, str_shdr + 16) as usize
};
let sh_offset = if is_64 {
read_u64(data, shdr + 24)
} else {
read_u32(data, shdr + 16) as u64
};
let sh_entsize = if is_64 {
read_u64(data, shdr + 56)
} else {
read_u32(data, shdr + 36) as u64
};
let sh_size = if is_64 {
read_u64(data, shdr + 32)
} else {
read_u32(data, shdr + 20) as u64
};
let count = if sh_entsize > 0 {
sh_size / sh_entsize
} else {
0
};
for j in 0..count.min(500) {
let sym = (sh_offset as usize) + (j as usize) * (sh_entsize as usize);
if sym + 8 > data.len() {
break;
}
let st_name = read_u32(data, sym) as usize;
let st_value = if is_64 {
read_u64(data, sym + 8)
} else {
read_u32(data, sym + 4) as u64
};
let st_size = if is_64 {
read_u64(data, sym + 16)
} else {
read_u32(data, sym + 8) as u64
};
if st_name > 0 {
let name = read_cstr(data, str_data + st_name);
syms.push(ElfSymbol {
name,
value: st_value,
size: st_size,
});
}
}
}
}
syms.sort_by_key(|s| s.value);
syms
}
fn is_elf(data: &[u8]) -> bool {
data.len() >= 4 && &data[0..4] == b"\x7fELF"
}
fn read_u16(data: &[u8], offset: usize) -> u16 {
u16::from_le_bytes([data[offset], data[offset + 1]])
}
fn read_u32(data: &[u8], offset: usize) -> u32 {
u32::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
])
}
fn read_u64(data: &[u8], offset: usize) -> u64 {
u64::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
data[offset + 4],
data[offset + 5],
data[offset + 6],
data[offset + 7],
])
}
fn read_cstr(data: &[u8], offset: usize) -> String {
let mut s = String::new();
let mut i = offset;
while i < data.len() && data[i] != 0 {
s.push(data[i] as char);
i += 1;
}
s
}
}
pub mod llvm_cov {
use std::fs;
pub struct CoverageData {
pub functions: Vec<FunctionCoverage>,
pub total_lines: u64,
pub covered_lines: u64,
}
pub struct FunctionCoverage {
pub name: String,
pub regions: Vec<CoverageRegion>,
pub total_lines: u64,
pub covered_lines: u64,
}
pub struct CoverageRegion {
pub start_line: u32,
pub end_line: u32,
pub execution_count: u64,
}
pub fn run(args: &[String]) {
let input = args
.iter()
.skip(1)
.find(|a| !a.starts_with('-'))
.cloned()
.unwrap_or_else(|| "default.profdata".into());
let html = args.iter().any(|a| a == "--html" || a == "-html");
let summary = args.iter().any(|a| a == "--summary" || a == "-summary");
let func_filter = args
.iter()
.skip(1)
.position(|a| a == "--function" || a == "-f")
.and_then(|i| args.get(i + 2).cloned());
match read_coverage_data(&input) {
Ok(data) => {
if let Some(fname) = func_filter {
print_function_coverage(&data, &fname);
} else if html {
println!("{}", generate_html_report(&data));
} else if summary {
print_summary(&data);
} else {
print_summary(&data);
println!();
for func in &data.functions {
print_function_coverage(&data, &func.name);
}
}
}
Err(e) => eprintln!("llvm-cov: {}", e),
}
}
pub fn read_coverage_data(path: &str) -> Result<CoverageData, String> {
let content = fs::read_to_string(path).map_err(|e| format!("{}: {}", path, e))?;
parse_coverage_text(&content)
}
fn parse_coverage_text(text: &str) -> Result<CoverageData, String> {
let mut functions = Vec::new();
let mut total_lines = 0u64;
let mut covered_lines = 0u64;
let mut current_func: Option<FunctionCoverage> = None;
for line in text.lines() {
let line = line.trim();
if line.is_empty() {
if let Some(func) = current_func.take() {
total_lines += func.total_lines;
covered_lines += func.covered_lines;
functions.push(func);
}
continue;
}
let parts: Vec<&str> = line.split(':').collect();
if parts.len() == 2 {
if let Some(func) = current_func.take() {
total_lines += func.total_lines;
covered_lines += func.covered_lines;
functions.push(func);
}
current_func = Some(FunctionCoverage {
name: parts[0].to_string(),
regions: Vec::new(),
total_lines: 0,
covered_lines: 0,
});
} else if parts.len() >= 4 {
if let Ok(start) = parts[0].parse::<u32>() {
let end = parts[1].parse::<u32>().unwrap_or(start);
let count = parts[2].parse::<u64>().unwrap_or(0);
let nlines = (end - start + 1) as u64;
if let Some(ref mut func) = current_func {
func.total_lines += nlines;
if count > 0 {
func.covered_lines += nlines;
}
func.regions.push(CoverageRegion {
start_line: start,
end_line: end,
execution_count: count,
});
}
} else {
if let Some(func) = current_func.take() {
total_lines += func.total_lines;
covered_lines += func.covered_lines;
functions.push(func);
}
current_func = Some(FunctionCoverage {
name: line.to_string(),
regions: Vec::new(),
total_lines: 0,
covered_lines: 0,
});
}
}
}
if let Some(func) = current_func {
total_lines += func.total_lines;
covered_lines += func.covered_lines;
functions.push(func);
}
Ok(CoverageData {
functions,
total_lines,
covered_lines,
})
}
pub fn generate_html_report(data: &CoverageData) -> String {
let pct = if data.total_lines > 0 {
(data.covered_lines as f64 / data.total_lines as f64) * 100.0
} else {
0.0
};
let mut html = String::new();
html.push_str("<!DOCTYPE html>\n<html>\n<head>\n");
html.push_str("<title>Coverage Report</title>\n");
html.push_str(
"<style>body{font-family:monospace} .cov{background:#cfc} .nocov{background:#fcc}",
);
html.push_str("</style>\n</head>\n<body>\n");
html.push_str(&format!("<h1>Coverage: {:.1}%</h1>\n", pct));
html.push_str(&format!(
"<p>{} / {} lines covered</p>\n",
data.covered_lines, data.total_lines
));
html.push_str("<table>\n");
for func in &data.functions {
let fpct = if func.total_lines > 0 {
(func.covered_lines as f64 / func.total_lines as f64) * 100.0
} else {
0.0
};
html.push_str(&format!(
"<tr><td>{}</td><td>{:.1}%</td></tr>\n",
func.name, fpct
));
}
html.push_str("</table>\n</body>\n</html>\n");
html
}
pub fn print_summary(data: &CoverageData) {
let pct = if data.total_lines > 0 {
(data.covered_lines as f64 / data.total_lines as f64) * 100.0
} else {
0.0
};
println!("Coverage Summary:");
println!(" Total lines: {}", data.total_lines);
println!(" Covered lines: {}", data.covered_lines);
println!(" Coverage: {:.1}%", pct);
println!(" Functions: {}", data.functions.len());
}
pub fn print_function_coverage(data: &CoverageData, func_name: &str) {
for func in &data.functions {
if func.name.contains(func_name) {
let pct = if func.total_lines > 0 {
(func.covered_lines as f64 / func.total_lines as f64) * 100.0
} else {
0.0
};
println!(
" {}: {}/{} ({:.1}%)",
func.name, func.covered_lines, func.total_lines, pct
);
for region in &func.regions {
println!(
" L{}-{}: {} executions",
region.start_line, region.end_line, region.execution_count
);
}
}
}
}
pub fn generate_lcov_report(data: &CoverageData) -> String {
let mut out = String::new();
out.push_str("TN:\n");
for func in &data.functions {
out.push_str(&format!("SF:{}\n", func.name));
out.push_str(&format!("FN:,{}\n", func.name));
out.push_str(&format!("FNDA:,{}\n", func.name));
for region in &func.regions {
let count = region.execution_count;
out.push_str(&format!("DA:{},{}\n", region.start_line, count));
}
let hits = func.covered_lines;
let total = func.total_lines;
out.push_str(&format!("LH:{}\n", hits));
out.push_str(&format!("LF:{}\n", total));
out.push_str("end_of_record\n");
}
out
}
}
pub mod llvm_config {
pub fn run(args: &[String]) {
if args.len() < 2 {
print_usage();
return;
}
for arg in &args[1..] {
match arg.as_str() {
"--cflags" | "--cxxflags" => print_cflags(),
"--ldflags" => print_ldflags(),
"--libs" => print_libs(),
"--components" => print_components(),
"--targets-built" => print_targets(),
"--version" => print_version(),
"--build-mode" => println!("{}", get_llvm_build_mode()),
"--help" => print_usage(),
_ => {
if !arg.starts_with('-') {
continue;
}
eprintln!("llvm-config: unknown option: {}", arg);
}
}
}
}
fn print_usage() {
println!("Usage: llvm-config <option>...");
println!("Options:");
println!(" --cflags C compiler flags");
println!(" --ldflags Linker flags");
println!(" --libs Libraries to link");
println!(" --components Available components");
println!(" --targets-built Built targets");
println!(" --version Version string");
}
pub fn print_cflags() {
println!(
"-I/usr/include -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS"
);
}
pub fn print_ldflags() {
println!("-L/usr/lib -Wl,-rpath,/usr/lib");
}
pub fn print_libs() {
let libs = [
"LLVMX86CodeGen",
"LLVMX86Desc",
"LLVMX86Info",
"LLVMARMCodeGen",
"LLVMARMDesc",
"LLVMARMInfo",
"LLVMAArch64CodeGen",
"LLVMAArch64Desc",
"LLVMAArch64Info",
"LLVMRISCVCodeGen",
"LLVMRISCVDesc",
"LLVMRISCVInfo",
"LLVMWebAssemblyCodeGen",
"LLVMWebAssemblyDesc",
"LLVMWebAssemblyInfo",
"LLVMBPFCodeGen",
"LLVMBPFDesc",
"LLVMBPFInfo",
"LLVMMipsCodeGen",
"LLVMMipsDesc",
"LLVMMipsInfo",
"LLVMPowerPCCodeGen",
"LLVMPowerPCDesc",
"LLVMPowerPCInfo",
"LLVMCore",
"LLVMSupport",
"LLVMTarget",
"LLVMMC",
"LLVMAnalysis",
"LLVMCodeGen",
"LLVMTransformUtils",
"LLVMScalarOpts",
"LLVMipo",
"LLVMObject",
"LLVMBitReader",
"LLVMBitWriter",
"LLVMAsmParser",
"LLVMIRReader",
"LLVMLinker",
"LLVMPasses",
"LLVMDebugInfoDWARF",
"LLVMMCDisassembler",
];
for lib in &libs {
print!("-l{} ", lib);
}
println!();
}
pub fn print_components() {
let comps = [
"x86",
"arm",
"aarch64",
"riscv",
"webassembly",
"bpf",
"mips",
"powerpc",
"core",
"support",
"target",
"mc",
"analysis",
"codegen",
"scalaropts",
"ipo",
"object",
"bitreader",
"bitwriter",
"asmparser",
"irreader",
"linker",
"passes",
"debuginfodwarf",
"mcdissassembler",
"all-targets",
"native",
"engine",
];
println!("{}", comps.join(" "));
}
pub fn print_targets() {
let targets = [
"X86",
"ARM",
"AArch64",
"RISCV",
"WebAssembly",
"BPF",
"Mips",
"PowerPC",
];
println!("{}", targets.join(" "));
}
pub fn print_version() {
println!(
"llvm-native {} (forensic-parity reimplementation)",
env!("CARGO_PKG_VERSION")
);
}
pub fn get_llvm_build_mode() -> String {
if cfg!(debug_assertions) {
"Debug".to_string()
} else {
"Release".to_string()
}
}
}
pub mod llvm_mca {
use std::collections::HashMap;
use std::fs;
pub struct MCAReport {
pub timeline: Vec<InstructionTiming>,
pub resource_pressure: HashMap<String, f64>,
pub total_cycles: u64,
pub ipc: f64,
}
pub struct InstructionTiming {
pub instruction: String,
pub dispatch_cycle: u64,
pub execute_cycle: u64,
pub retire_cycle: u64,
}
pub struct MCInst {
pub opcode: String,
pub operands: Vec<String>,
}
pub fn run(args: &[String]) {
let input = args
.iter()
.skip(1)
.find(|a| !a.starts_with('-'))
.cloned()
.unwrap_or_else(|| "input.s".into());
let target = args
.iter()
.skip(1)
.position(|a| a == "-mcpu" || a == "--mcpu")
.and_then(|i| args.get(i + 2).cloned())
.unwrap_or_else(|| "generic".into());
let iters: usize = args
.iter()
.skip(1)
.position(|a| a == "-iterations" || a == "--iters")
.and_then(|i| args.get(i + 2))
.and_then(|s| s.parse().ok())
.unwrap_or(100);
match fs::read_to_string(&input) {
Ok(asm) => {
println!("llvm-mca: analyzing '{}' for target '{}'", input, target);
let report = analyze_assembly(&asm, &target);
print_report(&report, iters);
}
Err(e) => eprintln!("llvm-mca: {}: {}", input, e),
}
}
pub fn analyze_assembly(asm: &str, target: &str) -> MCAReport {
let instructions = parse_instructions(asm);
simulate_pipeline(&instructions, target)
}
fn parse_instructions(asm: &str) -> Vec<MCInst> {
let mut insts = Vec::new();
for line in asm.lines() {
let line = line.trim();
if line.is_empty()
|| line.starts_with('#')
|| line.starts_with('.')
|| line.ends_with(':')
{
continue;
}
let line = if let Some(pos) = line.find('#') {
&line[..pos]
} else {
line
};
let line = line.trim();
if line.is_empty() {
continue;
}
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.is_empty() {
continue;
}
let opcode = parts[0].to_lowercase();
let operands: Vec<String> = parts[1..].iter().map(|s| s.to_string()).collect();
insts.push(MCInst { opcode, operands });
}
insts
}
pub fn simulate_pipeline(instructions: &[MCInst], _target: &str) -> MCAReport {
let latencies = build_latency_table();
let mut timeline = Vec::new();
let mut resource_pressure: HashMap<String, f64> = HashMap::new();
let mut current_cycle = 1u64;
for inst in instructions {
let latency = latencies.get(inst.opcode.as_str()).copied().unwrap_or(1);
let dispatch = current_cycle;
let execute = dispatch; let retire = execute + latency;
timeline.push(InstructionTiming {
instruction: format!("{} {}", inst.opcode, inst.operands.join(", ")),
dispatch_cycle: dispatch,
execute_cycle: execute,
retire_cycle: retire,
});
let res_key = match inst.opcode.as_str() {
"add" | "sub" | "and" | "or" | "xor" | "cmp" | "mov" | "lea" => "ALU",
"mul" | "imul" | "div" | "idiv" => "IMUL",
"fadd" | "fsub" | "fmul" | "fdiv" => "FPU",
"ldr" | "ldrb" | "ldrh" | "ldrsw" | "load" => "LoadUnit",
"str" | "strb" | "strh" | "store" => "StoreUnit",
"b" | "bl" | "br" | "call" | "ret" | "jmp" | "je" | "jne" => "BranchUnit",
_ => "Default",
};
*resource_pressure.entry(res_key.to_string()).or_insert(0.0) += latency as f64;
current_cycle += 1; }
let total_cycles = timeline.last().map(|t| t.retire_cycle).unwrap_or(0);
let ipc = if total_cycles > 0 {
instructions.len() as f64 / total_cycles as f64
} else {
0.0
};
for v in resource_pressure.values_mut() {
if total_cycles > 0 {
*v /= total_cycles as f64;
}
}
MCAReport {
timeline,
resource_pressure,
total_cycles,
ipc,
}
}
fn build_latency_table() -> HashMap<&'static str, u64> {
let mut m = HashMap::new();
m.insert("add", 1);
m.insert("sub", 1);
m.insert("and", 1);
m.insert("or", 1);
m.insert("xor", 1);
m.insert("shl", 1);
m.insert("shr", 1);
m.insert("cmp", 1);
m.insert("test", 1);
m.insert("mov", 1);
m.insert("lea", 1);
m.insert("nop", 0);
m.insert("mul", 3);
m.insert("imul", 3);
m.insert("div", 20);
m.insert("idiv", 20);
m.insert("fadd", 3);
m.insert("fsub", 3);
m.insert("fmul", 5);
m.insert("fdiv", 14);
m.insert("ldr", 4);
m.insert("ldrb", 4);
m.insert("ldrh", 4);
m.insert("str", 1);
m.insert("strb", 1);
m.insert("strh", 1);
m.insert("b", 1);
m.insert("bl", 1);
m.insert("br", 1);
m.insert("ret", 1);
m.insert("call", 1);
m.insert("jmp", 1);
m.insert("je", 1);
m.insert("jne", 1);
m.insert("push", 1);
m.insert("pop", 1);
m
}
fn print_report(report: &MCAReport, iters: usize) {
println!();
println!("Iterations: {}", iters);
println!("Total Cycles: {}", report.total_cycles);
println!("Instructions: {}", report.timeline.len());
println!("IPC: {:.3}", report.ipc);
println!();
println!("Resource Pressure (per cycle):");
let mut resources: Vec<_> = report.resource_pressure.iter().collect();
resources.sort_by_key(|(k, _)| *k);
for (res, pressure) in &resources {
let bar_len = (*pressure * 40.0).min(40.0) as usize;
let bar = "â–ˆ".repeat(bar_len) + &" ".repeat(40 - bar_len);
println!(" {:>12} |{}| {:.2}", res, bar, pressure);
}
println!();
println!("Timeline:");
println!(" {:>4} {:>8} {:>8} {:>8} Instruction", "D", "E", "R", "");
println!(
" {} {} {} {} {}",
"----", "------", "------", "------", "-----------"
);
for t in &report.timeline {
println!(
" {:>4} {:>6} {:>6} {:>6} {}",
t.dispatch_cycle, t.execute_cycle, t.retire_cycle, "", t.instruction
);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_simple_assembly() {
let asm = "add r0, r1, r2\nsub r3, r4, r5\n";
let insts = parse_instructions(asm);
assert_eq!(insts.len(), 2);
assert_eq!(insts[0].opcode, "add");
assert_eq!(insts[1].opcode, "sub");
}
#[test]
fn simulate_basic() {
let insts = vec![
MCInst {
opcode: "add".into(),
operands: vec!["r0".into(), "r1".into()],
},
MCInst {
opcode: "mul".into(),
operands: vec!["r2".into(), "r3".into()],
},
];
let report = simulate_pipeline(&insts, "generic");
assert_eq!(report.timeline.len(), 2);
assert!(report.total_cycles >= 2);
assert!(report.ipc > 0.0);
}
}
}
pub mod lld {
use llvm_native_core::lld::{LldDriver, LldOptions, OutputFormat};
pub fn run(args: &[String]) {
let consumers: &[&str] = &["-o", "-l", "-L", "-T", "--script", "-soname", "-z"];
let mut skip: std::collections::HashSet<usize> = std::collections::HashSet::new();
for (i, a) in args.iter().skip(1).enumerate() {
if consumers.iter().any(|c| a == *c) {
skip.insert(i + 1);
}
}
let input_files: Vec<String> = args
.iter()
.skip(1)
.enumerate()
.filter(|(i, a)| !a.starts_with('-') && !skip.contains(i))
.map(|(_, a)| a.clone())
.collect();
let output = args
.iter()
.skip(1)
.position(|a| a == "-o")
.and_then(|i| args.get(i + 2))
.cloned()
.unwrap_or_else(|| "a.out".into());
let shared = args.iter().skip(1).any(|a| a == "-shared");
let mut options = LldOptions::default();
if shared {
options.output_format = OutputFormat::Shared;
}
let mut driver = LldDriver::new(options);
driver.set_output(&output);
for path in &input_files {
if let Err(e) = driver.add_file(path) {
eprintln!("lld: warning: cannot open '{}': {}", path, e);
}
}
if driver.inputs.is_empty() {
eprintln!("lld: no input files");
std::process::exit(1);
}
match driver.run() {
Ok(()) => {}
Err(errors) => {
for e in &errors {
eprintln!("lld: error: {}", e);
}
let _ = std::fs::remove_file(&output);
std::process::exit(1);
}
}
}
}
pub mod clang {
use llvm_native_core::clang::clang_x86_e2e_pipeline_full::{X86E2EOptions, X86E2EPipeline};
use std::fs;
pub fn run(args: &[String]) {
let consumers: &[&str] = &["-o", "-target", "-I", "-D", "-U", "-include"];
let mut skip: std::collections::HashSet<usize> = std::collections::HashSet::new();
for (i, a) in args.iter().skip(1).enumerate() {
if consumers.iter().any(|c| a == *c) {
skip.insert(i + 1);
}
}
let input = args
.iter()
.skip(1)
.enumerate()
.find(|(i, a)| !a.starts_with('-') && !skip.contains(i))
.map(|(_, a)| a.clone())
.unwrap_or_else(|| "input.c".into());
let output = args
.iter()
.skip(1)
.position(|a| a == "-o")
.and_then(|i| args.get(i + 2))
.cloned()
.unwrap_or_else(|| {
let stem = std::path::Path::new(&input)
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("output");
format!("{}.o", stem)
});
eprintln!("clang: compiling {} -> {}", input, output);
let mut pipeline = X86E2EPipeline::new(X86E2EOptions::default());
let (obj_bytes, diagnostics) = match pipeline.compile_file(&input) {
Some(result) => result,
None => {
eprintln!("clang: compilation returned None");
return;
}
};
if !diagnostics.is_empty() {
for d in &diagnostics {
eprintln!("clang: {}", d);
}
}
if obj_bytes.is_empty() {
eprintln!("clang: no object bytes produced (compilation failed)");
return;
}
let is_object = output.ends_with(".o") || output.ends_with(".obj");
if is_object {
if let Err(e) = fs::write(&output, &obj_bytes) {
eprintln!("clang: cannot write {}: {}", output, e);
} else {
eprintln!(" Wrote {} bytes to {}", obj_bytes.len(), output);
}
} else {
let stem = std::path::Path::new(&output)
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("a");
let tmp_obj = format!("/tmp/{}.o", stem);
if let Err(e) = fs::write(&tmp_obj, &obj_bytes) {
eprintln!("clang: cannot write temp object: {}", e);
} else {
eprintln!(" Wrote object to {}", tmp_obj);
use llvm_native_core::lld::lld_driver::{LldDriver, LldOptions};
let opts = LldOptions {
output_format: llvm_native_core::lld::lld_driver::OutputFormat::ELF,
library_paths: vec![
"/usr/lib".to_string(),
"/lib/x86_64-linux-gnu".to_string(),
],
..Default::default()
};
let mut lld = LldDriver::new(opts);
lld.set_output(&output);
if let Err(e) = lld.add_file(&tmp_obj) {
eprintln!("lld: cannot add '{}': {:?}", tmp_obj, e);
let _ = fs::write(&output, &obj_bytes);
} else if let Err(e) = lld.run() {
eprintln!("lld: link failed: {:?}", e);
eprintln!(" Saving unlinked object: {}", output);
let _ = fs::write(&output, &obj_bytes);
} else {
eprintln!(" Linked executable: {}", output);
}
}
}
}
}
pub mod llvm_profdata {
use std::collections::HashMap;
use std::fs;
pub struct OverlapReport {
pub base_total: u64,
pub test_total: u64,
pub overlap: u64,
pub overlap_pct: f64,
}
pub fn run(args: &[String]) {
let mut input_files = Vec::new();
let mut output: Option<String> = None;
let mut show = false;
let mut merge = false;
let mut overlap_base: Option<String> = None;
let mut overlap_test: Option<String> = None;
let mut i = 1usize;
while i < args.len() {
match args[i].as_str() {
"merge" => merge = true,
"show" => show = true,
"overlap" => {
if i + 2 < args.len() {
overlap_base = Some(args[i + 1].clone());
overlap_test = Some(args[i + 2].clone());
i += 2;
}
}
"-o" | "--output" => {
if i + 1 < args.len() {
output = Some(args[i + 1].clone());
i += 1;
}
}
a if !a.starts_with('-') && !["merge", "show", "overlap"].contains(&a) => {
input_files.push(a.to_string());
}
_ => {}
}
i += 1;
}
if let (Some(base), Some(test)) = (overlap_base, overlap_test) {
match overlap_profiles(&base, &test) {
Ok(report) => {
println!("Profile overlap:");
println!(" Base total count: {}", report.base_total);
println!(" Test total count: {}", report.test_total);
println!(" Overlap: {}", report.overlap);
println!(" Overlap %: {:.1}%", report.overlap_pct);
}
Err(e) => eprintln!("llvm-profdata: {}", e),
}
return;
}
if merge && !input_files.is_empty() {
let output_path = output.unwrap_or_else(|| "merged.profdata".into());
match merge_profiles(&input_files, &output_path) {
Ok(()) => println!("Merged {} profiles into {}", input_files.len(), output_path),
Err(e) => eprintln!("llvm-profdata: {}", e),
}
return;
}
if show && !input_files.is_empty() {
match show_profile(&input_files[0]) {
Ok(s) => println!("{}", s),
Err(e) => eprintln!("llvm-profdata: {}", e),
}
return;
}
eprintln!("Usage: llvm-profdata <command> [args]");
eprintln!("Commands:");
eprintln!(" merge [files...] [-o output] Merge profile data");
eprintln!(" show <file> Show profile contents");
eprintln!(" overlap <base> <test> Compute profile overlap");
}
pub fn merge_profiles(inputs: &[String], output: &str) -> Result<(), String> {
let mut merged_counts: HashMap<String, u64> = HashMap::new();
let mut total_inputs = 0usize;
for input in inputs {
let content =
fs::read_to_string(input).map_err(|e| format!("cannot read {}: {}", input, e))?;
total_inputs += 1;
for line in content.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 2 {
let name = parts[0].to_string();
let count: u64 = parts[1].parse().unwrap_or(0);
*merged_counts.entry(name).or_insert(0) += count;
}
}
}
if total_inputs == 0 {
return Err("no input files".into());
}
let mut output_content = String::new();
output_content.push_str(&format!("# Merged from {} files\n", total_inputs));
for (name, count) in &merged_counts {
output_content.push_str(&format!("{} {}\n", name, count));
}
fs::write(output, output_content).map_err(|e| format!("cannot write {}: {}", output, e))?;
Ok(())
}
pub fn show_profile(path: &str) -> Result<String, String> {
let content =
fs::read_to_string(path).map_err(|e| format!("cannot read {}: {}", path, e))?;
let mut out = String::new();
out.push_str(&format!("Profile: {}\n", path));
out.push_str("Functions:\n");
let mut total_count = 0u64;
let mut func_count = 0u32;
for line in content.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 2 {
let count: u64 = parts[1].parse().unwrap_or(0);
out.push_str(&format!(" {}: {}\n", parts[0], count));
total_count += count;
func_count += 1;
}
}
out.push_str(&format!(
"Total: {} functions, {} executions\n",
func_count, total_count
));
Ok(out)
}
pub fn overlap_profiles(base: &str, test: &str) -> Result<OverlapReport, String> {
let base_content =
fs::read_to_string(base).map_err(|e| format!("cannot read {}: {}", base, e))?;
let test_content =
fs::read_to_string(test).map_err(|e| format!("cannot read {}: {}", test, e))?;
let mut base_counts: HashMap<String, u64> = HashMap::new();
for line in base_content.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 2 {
let count: u64 = parts[1].parse().unwrap_or(0);
*base_counts.entry(parts[0].to_string()).or_insert(0) += count;
}
}
let mut test_counts: HashMap<String, u64> = HashMap::new();
for line in test_content.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 2 {
let count: u64 = parts[1].parse().unwrap_or(0);
*test_counts.entry(parts[0].to_string()).or_insert(0) += count;
}
}
let base_total: u64 = base_counts.values().sum();
let test_total: u64 = test_counts.values().sum();
let overlap: u64 = base_counts
.iter()
.filter(|(k, _)| test_counts.contains_key(*k))
.map(|(_, v)| v)
.sum();
let overlap_pct = if base_total > 0 {
(overlap as f64 / base_total as f64) * 100.0
} else {
0.0
};
Ok(OverlapReport {
base_total,
test_total,
overlap,
overlap_pct,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn usage_prints_without_crashing() {
let args: Vec<String> = vec!["llvm-native".into()];
llvm_config::print_version();
llvm_config::print_cflags();
llvm_config::print_targets();
}
#[test]
fn llvm_ar_roundtrip() {
let data = b"!<arch>\nhello.txt/ 0 0 0 644 5 `\nhello\n";
let entries = llvm_ar::parse_archive(data);
assert!(entries.contains_key("hello.txt"));
assert_eq!(entries["hello.txt"], b"hello");
}
#[test]
fn llvm_cov_parsing() {
let text = "main\n1:5:100\n6:10:50\n";
let data = llvm_cov::parse_coverage_text(text).unwrap();
assert_eq!(data.functions.len(), 1);
assert_eq!(data.functions[0].name, "main");
}
#[test]
fn llvm_mca_parse() {
let asm = "add r0, r1\n mul r2, r3 # comment\nsub r4, r5\n";
let insts = llvm_mca::parse_instructions(asm);
assert_eq!(insts.len(), 3);
}
#[test]
fn llvm_readelf_file_header() {
let mut data = vec![0u8; 64];
data[0..4].copy_from_slice(b"\x7fELF");
data[4] = 2; data[5] = 1; data[6] = 1; let out = llvm_readelf::print_file_header(&data);
assert!(out.contains("ELF64"));
assert!(out.contains("ELF Header"));
}
#[test]
fn llvm_readelf_section_headers() {
let mut data = vec![0u8; 128];
data[0..4].copy_from_slice(b"\x7fELF");
data[4] = 2;
data[5] = 1;
let out = llvm_readelf::print_section_headers(&data);
assert!(out.contains("Section Headers"));
}
#[test]
fn llvm_dwarfdump_eh_frame() {
let mut data = vec![0u8; 64];
data[0..4].copy_from_slice(&16u32.to_le_bytes());
data[4..8].copy_from_slice(&0u32.to_le_bytes());
data[8] = 1; data[20..24].copy_from_slice(&20u32.to_le_bytes());
data[24..28].copy_from_slice(&16u32.to_le_bytes());
data[28..32].copy_from_slice(&0x1000u32.to_le_bytes());
data[32..36].copy_from_slice(&0x100u32.to_le_bytes());
data[40..44].copy_from_slice(&0u32.to_le_bytes());
let out = llvm_dwarfdump::dump_eh_frame(&data);
assert!(out.contains(".eh_frame"));
assert!(out.contains("Terminator"));
}
#[test]
fn llvm_symbolizer_line_info() {
let line_info = vec![
llvm_symbolizer::LineInfo {
file_name: "main.c".into(),
line: 10,
column: 5,
address: 0x1000,
},
llvm_symbolizer::LineInfo {
file_name: "main.c".into(),
line: 20,
column: 3,
address: 0x2000,
},
];
let data = vec![0u8; 64];
let result = llvm_symbolizer::find_line_for_address(&data, 0x1500, &line_info);
assert!(result.is_some());
assert_eq!(result.unwrap().0, "main.c");
assert_eq!(result.unwrap().1, 10);
}
#[test]
fn llvm_cov_lcov_report() {
let data = llvm_cov::CoverageData {
functions: vec![llvm_cov::FunctionCoverage {
name: "test_func".into(),
regions: vec![llvm_cov::CoverageRegion {
start_line: 1,
end_line: 5,
execution_count: 100,
}],
total_lines: 5,
covered_lines: 5,
}],
total_lines: 5,
covered_lines: 5,
};
let report = llvm_cov::generate_lcov_report(&data);
assert!(report.contains("SF:test_func"));
assert!(report.contains("DA:1,100"));
assert!(report.contains("LH:5"));
assert!(report.contains("LF:5"));
assert!(report.contains("end_of_record"));
}
#[test]
fn llvm_config_build_mode() {
let mode = llvm_config::get_llvm_build_mode();
assert!(mode == "Debug" || mode == "Release");
}
#[test]
fn llvm_profdata_overlap() {
use std::io::Write;
let dir = std::env::temp_dir();
let base_path = dir.join("test_base_prof.txt");
let test_path = dir.join("test_test_prof.txt");
let mut f = std::fs::File::create(&base_path).unwrap();
writeln!(f, "func_a 100").unwrap();
writeln!(f, "func_b 50").unwrap();
drop(f);
let mut f = std::fs::File::create(&test_path).unwrap();
writeln!(f, "func_a 75").unwrap();
writeln!(f, "func_c 25").unwrap();
drop(f);
let report = llvm_profdata::overlap_profiles(
base_path.to_str().unwrap(),
test_path.to_str().unwrap(),
)
.unwrap();
assert_eq!(report.base_total, 150);
assert_eq!(report.test_total, 100);
assert_eq!(report.overlap, 100);
let _ = std::fs::remove_file(&base_path);
let _ = std::fs::remove_file(&test_path);
}
}