use llvm_native_core::lld::lld_elf::OutputSection;
use llvm_native_core::object_file::ObjectSymbol;
use std::collections::HashMap;
use std::fs;
use std::io::{BufRead, BufReader, Write};
use std::path::Path;
#[derive(Debug, Clone)]
pub struct LinkerMapFile {
pub sections: Vec<MapSection>,
pub symbols: Vec<MapSymbol>,
pub totals: MapTotals,
}
#[derive(Debug, Clone)]
pub struct MapSection {
pub name: String,
pub address: u64,
pub size: u64,
pub symbols: Vec<MapSymbol>,
}
#[derive(Debug, Clone)]
pub struct MapSymbol {
pub name: String,
pub address: u64,
pub size: u64,
pub section: String,
}
#[derive(Debug, Clone, Default)]
pub struct MapTotals {
pub text: u64,
pub data: u64,
pub bss: u64,
pub total: u64,
}
pub struct LinkerMap;
impl LinkerMap {
pub fn generate(sections: &[OutputSection], symbols: &[ObjectSymbol]) -> LinkerMapFile {
let mut map_sections: Vec<MapSection> = Vec::new();
let mut all_symbols: Vec<MapSymbol> = Vec::new();
let mut totals = MapTotals::default();
let symbol_by_addr: HashMap<u64, &ObjectSymbol> =
symbols.iter().map(|s| (s.value, s)).collect();
for section in sections {
let is_code = section.name.starts_with(".text")
|| section.name.starts_with(".init")
|| section.name.starts_with(".fini");
let is_data = section.name.starts_with(".data")
|| section.name.starts_with(".rodata")
|| section.name.starts_with(".got");
let is_bss = section.name.starts_with(".bss") || section.name.starts_with(".tbss");
if is_code {
totals.text += section.data.len() as u64;
} else if is_data {
totals.data += section.data.len() as u64;
} else if is_bss {
totals.bss += section.data.len() as u64;
}
totals.total += section.data.len() as u64;
let mut section_symbols: Vec<MapSymbol> = Vec::new();
let section_start = section.vaddr;
let section_end = section.vaddr + section.data.len() as u64;
for sym in symbols {
if sym.value >= section_start && sym.value < section_end {
section_symbols.push(MapSymbol {
name: sym.name.clone(),
address: sym.value,
size: sym.size,
section: section.name.clone(),
});
}
}
section_symbols.sort_by_key(|s| s.address);
all_symbols.extend(section_symbols.clone());
map_sections.push(MapSection {
name: section.name.clone(),
address: section.vaddr,
size: section.data.len() as u64,
symbols: section_symbols,
});
}
map_sections.sort_by_key(|s| s.address);
LinkerMapFile {
sections: map_sections,
symbols: all_symbols,
totals,
}
}
pub fn write_to_file(map: &LinkerMapFile, path: &str) -> Result<(), String> {
let mut output = String::new();
output.push_str("Linker script and memory map\n\n");
for section in &map.sections {
output.push_str(&format!(
"\n{:<16} 0x{:016x} 0x{:08x}\n",
section.name, section.address, section.size,
));
if !section.symbols.is_empty() {
output.push_str(&format!(" {:<15} {:<18} {:<10}\n", " ", "Address", "Size"));
for sym in §ion.symbols {
output.push_str(&format!(
" {:<16} 0x{:016x} 0x{:08x} {}\n",
" ", sym.address, sym.size, sym.name,
));
}
}
}
output.push_str("\nTotals\n");
output.push_str(&format!(" text 0x{:08x}\n", map.totals.text));
output.push_str(&format!(" data 0x{:08x}\n", map.totals.data));
output.push_str(&format!(" bss 0x{:08x}\n", map.totals.bss));
output.push_str(&format!(" total 0x{:08x}\n", map.totals.total));
fs::write(path, output.as_bytes())
.map_err(|e| format!("failed to write map file '{}': {}", path, e))
}
pub fn parse_map_file(path: &str) -> Result<LinkerMapFile, String> {
let file = fs::File::open(path).map_err(|e| format!("cannot open '{}': {}", path, e))?;
let reader = BufReader::new(file);
let mut sections: Vec<MapSection> = Vec::new();
let mut symbols: Vec<MapSymbol> = Vec::new();
let mut totals = MapTotals::default();
let mut current_section: Option<MapSection> = None;
let mut in_totals = false;
for line_result in reader.lines() {
let line = line_result.map_err(|e| format!("read error: {}", e))?;
let trimmed = line.trim();
if trimmed.is_empty() {
continue;
}
if trimmed == "Totals" {
in_totals = true;
if let Some(s) = current_section.take() {
sections.push(s);
}
continue;
}
if in_totals {
Self::parse_totals_line(trimmed, &mut totals);
continue;
}
if let Some(section) = Self::try_parse_section_header(trimmed) {
if let Some(s) = current_section.take() {
sections.push(s);
}
current_section = Some(section);
continue;
}
if let Some(sym) = Self::try_parse_symbol_line(trimmed, current_section.as_ref()) {
if let Some(ref mut sec) = current_section {
symbols.push(sym.clone());
sec.symbols.push(sym);
}
}
}
if let Some(s) = current_section {
sections.push(s);
}
Ok(LinkerMapFile {
sections,
symbols,
totals,
})
}
fn try_parse_section_header(line: &str) -> Option<MapSection> {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() < 3 {
return None;
}
let name = parts[0];
if !name.starts_with('.') {
return None;
}
let address = u64::from_str_radix(parts[1].trim_start_matches("0x"), 16).ok()?;
let size = u64::from_str_radix(parts[2].trim_start_matches("0x"), 16).ok()?;
Some(MapSection {
name: name.to_string(),
address,
size,
symbols: Vec::new(),
})
}
fn try_parse_symbol_line(line: &str, section: Option<&MapSection>) -> Option<MapSymbol> {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() < 3 {
return None;
}
let address = u64::from_str_radix(parts.get(1)?.trim_start_matches("0x"), 16).ok()?;
let size = u64::from_str_radix(parts.get(2)?.trim_start_matches("0x"), 16).ok()?;
let name = parts.get(3).map(|s| s.to_string()).unwrap_or_default();
Some(MapSymbol {
name,
address,
size,
section: section.map(|s| s.name.clone()).unwrap_or_default(),
})
}
fn parse_totals_line(line: &str, totals: &mut MapTotals) {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() < 2 {
return;
}
let value = u64::from_str_radix(parts[1].trim_start_matches("0x"), 16).unwrap_or(0);
match parts[0] {
"text" => totals.text = value,
"data" => totals.data = value,
"bss" => totals.bss = value,
"total" => totals.total = value,
_ => {}
}
}
}
#[derive(Debug, Clone)]
pub struct ArchiveMember {
pub archive_name: String,
pub member_name: String,
pub symbols_provided: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct CommonSymbol {
pub name: String,
pub size: u64,
pub alignment: u64,
}
#[derive(Debug, Clone)]
pub struct MemoryRegion {
pub name: String,
pub origin: u64,
pub length: u64,
pub attributes: Vec<String>,
}
impl LinkerMap {
pub fn generate_full(
sections: &[OutputSection],
symbols: &[ObjectSymbol],
archives: &[ArchiveMember],
common_syms: &[CommonSymbol],
mem_regions: &[MemoryRegion],
) -> LinkerMapFile {
let mut map = Self::generate(sections, symbols);
let mut output = String::new();
if !archives.is_empty() {
output.push_str("\nArchive member(s) included to satisfy reference:\n");
for archive in archives {
output.push_str(&format!(
" {}:{} provides: {}\n",
archive.archive_name,
archive.member_name,
archive.symbols_provided.join(", ")
));
}
}
if !mem_regions.is_empty() {
output.push_str("\nMemory Configuration\n");
output.push_str(&format!(
" {:<20} {:<16} {:<16} {}\n",
"Name", "Origin", "Length", "Attributes"
));
for region in mem_regions {
output.push_str(&format!(
" {:<20} 0x{:016x} 0x{:016x} {}\n",
region.name,
region.origin,
region.length,
region.attributes.join(", ")
));
}
}
if !common_syms.is_empty() {
output.push_str("\nCommon symbols:\n");
output.push_str(&format!(
" {:<30} {:<10} {:<10}\n",
"Name", "Size", "Alignment"
));
for cs in common_syms {
output.push_str(&format!(
" {:<30} 0x{:08x} 0x{:08x}\n",
cs.name, cs.size, cs.alignment
));
}
}
map
}
pub fn generate_cross_reference(
symbols: &[ObjectSymbol],
archives: &[ArchiveMember],
) -> HashMap<String, String> {
let mut xref = HashMap::new();
for sym in symbols {
for archive in archives {
if archive.symbols_provided.contains(&sym.name) {
xref.insert(
sym.name.clone(),
format!("{}:{}", archive.archive_name, archive.member_name),
);
}
}
}
xref
}
pub fn compute_totals_by_region(
sections: &[OutputSection],
regions: &[MemoryRegion],
) -> HashMap<String, u64> {
let mut totals: HashMap<String, u64> = HashMap::new();
for section in sections {
let section_end = section.vaddr + section.data.len() as u64;
for region in regions {
let region_end = region.origin + region.length;
if section.vaddr >= region.origin && section_end <= region_end {
*totals.entry(region.name.clone()).or_default() += section.data.len() as u64;
break;
}
}
}
totals
}
pub fn generate_summary(map: &LinkerMapFile) -> String {
let mut summary = String::new();
summary.push_str(&format!("Total sections: {}\n", map.sections.len()));
summary.push_str(&format!("Total symbols: {}\n", map.symbols.len()));
summary.push_str(&format!(
"Total size: 0x{:08x} ({} bytes)\n",
map.totals.total, map.totals.total
));
summary.push_str(&format!(" .text: 0x{:08x}\n", map.totals.text));
summary.push_str(&format!(" .data: 0x{:08x}\n", map.totals.data));
summary.push_str(&format!(" .bss : 0x{:08x}\n", map.totals.bss));
summary
}
pub fn find_section_by_address<'a>(
map: &'a LinkerMapFile,
address: u64,
) -> Option<&'a MapSection> {
map.sections
.iter()
.find(|s| address >= s.address && address < s.address + s.size)
}
pub fn find_symbols_in_range(map: &LinkerMapFile, start: u64, end: u64) -> Vec<&MapSymbol> {
map.symbols
.iter()
.filter(|s| s.address >= start && s.address < end)
.collect()
}
pub fn section_size_breakdown(sections: &[MapSection]) -> HashMap<String, u64> {
let mut breakdown = HashMap::new();
for section in sections {
let category = if section.name.starts_with(".text")
|| section.name.starts_with(".init")
|| section.name.starts_with(".fini")
{
"code"
} else if section.name.starts_with(".rodata") || section.name.starts_with(".rdata") {
"rodata"
} else if section.name.starts_with(".data")
|| section.name.starts_with(".got")
|| section.name.starts_with(".tdata")
{
"data"
} else if section.name.starts_with(".bss") || section.name.starts_with(".tbss") {
"bss"
} else {
"other"
};
*breakdown.entry(category.to_string()).or_default() += section.size;
}
breakdown
}
pub fn merge_maps(a: &LinkerMapFile, b: &LinkerMapFile) -> LinkerMapFile {
let mut sections = a.sections.clone();
sections.extend(b.sections.clone());
sections.sort_by_key(|s| s.address);
let mut symbols = a.symbols.clone();
symbols.extend(b.symbols.clone());
symbols.sort_by_key(|s| s.address);
let totals = MapTotals {
text: a.totals.text + b.totals.text,
data: a.totals.data + b.totals.data,
bss: a.totals.bss + b.totals.bss,
total: a.totals.total + b.totals.total,
};
LinkerMapFile {
sections,
symbols,
totals,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_section(name: &str, vaddr: u64, size: u64) -> OutputSection {
OutputSection {
name: name.to_string(),
data: vec![0u8; size as usize],
sh_type: 1, sh_flags: 6, sh_addralign: 16,
segment_index: None,
vaddr,
file_offset: vaddr,
}
}
fn make_symbol(name: &str, value: u64, size: u64) -> ObjectSymbol {
ObjectSymbol {
name: name.to_string(),
value,
size,
is_global: true,
is_function: true,
section_index: 1,
}
}
#[test]
fn test_generate_empty() {
let map = LinkerMap::generate(&[], &[]);
assert!(map.sections.is_empty());
assert!(map.symbols.is_empty());
assert_eq!(map.totals.total, 0);
}
#[test]
fn test_generate_with_sections() {
let sections = vec![
make_section(".text", 0x1000, 64),
make_section(".data", 0x2000, 32),
];
let symbols = vec![
make_symbol("main", 0x1000, 24),
make_symbol("helper", 0x1018, 16),
];
let map = LinkerMap::generate(§ions, &symbols);
assert_eq!(map.sections.len(), 2);
assert_eq!(map.sections[0].name, ".text");
assert_eq!(map.sections[0].symbols.len(), 2);
assert_eq!(map.totals.text, 64);
assert_eq!(map.totals.data, 32);
assert_eq!(map.totals.total, 96);
}
#[test]
fn test_write_then_parse_roundtrip() {
let sections = vec![make_section(".text", 0x1000, 48)];
let symbols = vec![make_symbol("_start", 0x1000, 16)];
let map = LinkerMap::generate(§ions, &symbols);
let tmp_path = "/tmp/test_linker_map.txt";
LinkerMap::write_to_file(&map, tmp_path).unwrap();
let parsed = LinkerMap::parse_map_file(tmp_path).unwrap();
assert!(!parsed.sections.is_empty());
assert!(parsed.totals.text > 0);
let _ = std::fs::remove_file(tmp_path);
}
#[test]
fn test_parse_totals_line() {
let mut totals = MapTotals::default();
LinkerMap::parse_totals_line("text 0x00001000", &mut totals);
assert_eq!(totals.text, 0x1000);
LinkerMap::parse_totals_line("data 0x00000200", &mut totals);
assert_eq!(totals.data, 0x200);
LinkerMap::parse_totals_line("bss 0x00000080", &mut totals);
assert_eq!(totals.bss, 0x80);
LinkerMap::parse_totals_line("total 0x00001280", &mut totals);
assert_eq!(totals.total, 0x1280);
}
#[test]
fn test_parse_map_file_not_found() {
let result = LinkerMap::parse_map_file("/nonexistent/path/map.txt");
assert!(result.is_err());
}
#[test]
fn test_totals_default() {
let totals = MapTotals::default();
assert_eq!(totals.text, 0);
assert_eq!(totals.data, 0);
assert_eq!(totals.bss, 0);
assert_eq!(totals.total, 0);
}
}