diff --git a/Cargo.lock b/Cargo.lock index 243dcce..65683e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -295,7 +295,7 @@ dependencies = [ [[package]] name = "decomp-toolkit" -version = "0.6.3" +version = "0.6.4" dependencies = [ "anyhow", "ar", diff --git a/Cargo.toml b/Cargo.toml index 7c69e64..f28b767 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "decomp-toolkit" description = "Yet another GameCube/Wii decompilation toolkit." authors = ["Luke Street "] license = "MIT OR Apache-2.0" -version = "0.6.3" +version = "0.6.4" edition = "2021" publish = false repository = "https://github.com/encounter/decomp-toolkit" diff --git a/src/cmd/dol.rs b/src/cmd/dol.rs index f0d82d9..06b9d2a 100644 --- a/src/cmd/dol.rs +++ b/src/cmd/dol.rs @@ -739,7 +739,7 @@ fn load_analyze_dol(config: &ProjectConfig) -> Result { } if let Some(map_path) = &config.base.map { - apply_map_file(map_path, &mut obj)?; + apply_map_file(map_path, &mut obj, config.common_start, config.mw_comment_version)?; dep.push(map_path.clone()); } @@ -963,7 +963,7 @@ fn load_analyze_rel(config: &ProjectConfig, module_config: &ModuleConfig) -> Res let mut dep = vec![module_config.object.clone()]; if let Some(map_path) = &module_config.map { - apply_map_file(map_path, &mut module_obj)?; + apply_map_file(map_path, &mut module_obj, None, None)?; dep.push(map_path.clone()); } @@ -1451,11 +1451,10 @@ fn diff(args: DiffArgs) -> Result<()> { log::info!("Loading {}", args.elf_file.display()); let linked_obj = process_elf(&args.elf_file)?; - for orig_sym in obj - .symbols - .iter() - .filter(|s| !matches!(s.kind, ObjSymbolKind::Unknown | ObjSymbolKind::Section)) - { + let common_bss = obj.sections.common_bss_start(); + for orig_sym in obj.symbols.iter().filter(|s| { + !matches!(s.kind, ObjSymbolKind::Unknown | ObjSymbolKind::Section) && !s.flags.is_stripped() + }) { let Some(orig_section_index) = orig_sym.section else { continue }; let orig_section = &obj.sections[orig_section_index]; let (linked_section_index, linked_section) = @@ -1474,7 +1473,12 @@ fn diff(args: DiffArgs) -> Result<()> { let mut found = false; if let Some((_, linked_sym)) = linked_sym { if linked_sym.name.starts_with(&orig_sym.name) { - if linked_sym.size != orig_sym.size { + if linked_sym.size != orig_sym.size && + // TODO validate common symbol sizes + // (need to account for inflation bug) + matches!(common_bss, Some((idx, addr)) if + orig_section_index == idx && orig_sym.address as u32 >= addr) + { log::error!( "Expected {} (type {:?}) to have size {:#X}, but found {:#X}", orig_sym.name, diff --git a/src/cmd/map.rs b/src/cmd/map.rs index 57fe327..ebb5636 100644 --- a/src/cmd/map.rs +++ b/src/cmd/map.rs @@ -57,7 +57,7 @@ pub fn run(args: Args) -> Result<()> { fn entries(args: EntriesArgs) -> Result<()> { let file = map_file(&args.map_file)?; - let entries = process_map(&mut file.as_reader())?; + let entries = process_map(&mut file.as_reader(), None, None)?; match entries.unit_entries.get_vec(&args.unit) { Some(vec) => { println!("Entries for {}:", args.unit); @@ -89,7 +89,7 @@ fn entries(args: EntriesArgs) -> Result<()> { fn symbol(args: SymbolArgs) -> Result<()> { let file = map_file(&args.map_file)?; log::info!("Processing map..."); - let entries = process_map(&mut file.as_reader())?; + let entries = process_map(&mut file.as_reader(), None, None)?; log::info!("Done!"); let mut opt_ref: Option<(String, SymbolEntry)> = None; diff --git a/src/obj/sections.rs b/src/obj/sections.rs index 076b9bf..59a90e0 100644 --- a/src/obj/sections.rs +++ b/src/obj/sections.rs @@ -125,6 +125,13 @@ impl ObjSections { self.iter() .flat_map(|(idx, s)| s.splits.iter().map(move |(addr, split)| (idx, s, addr, split))) } + + pub fn common_bss_start(&self) -> Option<(usize, u32)> { + let Ok(Some((section_index, section))) = self.by_name(".bss") else { + return None; + }; + section.splits.iter().find(|(_, split)| split.common).map(|(addr, _)| (section_index, addr)) + } } impl Index for ObjSections { diff --git a/src/obj/symbols.rs b/src/obj/symbols.rs index 74220be..3f68d39 100644 --- a/src/obj/symbols.rs +++ b/src/obj/symbols.rs @@ -26,9 +26,9 @@ pub enum ObjSymbolScope { } flags! { - #[repr(u8)] + #[repr(u32)] #[derive(Deserialize_repr, Serialize_repr)] - pub enum ObjSymbolFlags: u8 { + pub enum ObjSymbolFlags: u32 { Global, Local, Weak, @@ -39,6 +39,9 @@ flags! { RelocationIgnore, /// Symbol won't be written to symbols file NoWrite, + /// Symbol was stripped from the original object, + /// but is still useful for common BSS matching. + Stripped, } } @@ -83,6 +86,9 @@ impl ObjSymbolFlagSet { #[inline] pub fn is_no_write(&self) -> bool { self.0.contains(ObjSymbolFlags::NoWrite) } + #[inline] + pub fn is_stripped(&self) -> bool { self.0.contains(ObjSymbolFlags::Stripped) } + #[inline] pub fn set_scope(&mut self, scope: ObjSymbolScope) { match scope { @@ -119,7 +125,8 @@ impl ObjSymbolFlagSet { self.0 & (ObjSymbolFlags::ForceActive | ObjSymbolFlags::NoWrite - | ObjSymbolFlags::RelocationIgnore) + | ObjSymbolFlags::RelocationIgnore + | ObjSymbolFlags::Stripped) } } @@ -212,7 +219,10 @@ impl ObjSymbols { } pub fn add(&mut self, in_symbol: ObjSymbol, replace: bool) -> Result { - let opt = if let Some(section_index) = in_symbol.section { + let opt = if in_symbol.flags.is_stripped() { + // Stripped symbols don't overwrite existing symbols + None + } else if let Some(section_index) = in_symbol.section { self.at_section_address(section_index, in_symbol.address as u32).find(|(_, symbol)| { symbol.kind == in_symbol.kind || // Replace auto symbols with real symbols @@ -228,7 +238,8 @@ impl ObjSymbols { let replace = replace || (is_auto_symbol(existing) && !is_auto_symbol(&in_symbol)); let size = if existing.size_known && in_symbol.size_known && existing.size != in_symbol.size { - log::warn!( + // TODO fix this and restore to warning + log::debug!( "Conflicting size for {}: was {:#X}, now {:#X}", existing.name, existing.size, @@ -336,6 +347,8 @@ impl ObjSymbols { .into_iter() .flatten() .map(move |&idx| (idx, &self.symbols[idx])) + // "Stripped" symbols don't actually exist at the address + .filter(|(_, sym)| !sym.flags.is_stripped()) } pub fn kind_at_section_address( @@ -513,7 +526,7 @@ impl Index for ObjSymbols { impl ObjSymbol { /// Whether this symbol can be referenced by the given relocation kind. pub fn referenced_by(&self, reloc_kind: ObjRelocKind) -> bool { - if self.flags.is_relocation_ignore() { + if self.flags.is_relocation_ignore() || self.flags.is_stripped() { return false; } diff --git a/src/util/comment.rs b/src/util/comment.rs index adab1c2..d2d6b68 100644 --- a/src/util/comment.rs +++ b/src/util/comment.rs @@ -281,9 +281,10 @@ impl CommentSym { vis_flags |= 0xD; } let mut active_flags = 0; - if symbol.flags.is_force_active() - || (force_active - && matches!(symbol.kind, ObjSymbolKind::Function | ObjSymbolKind::Object)) + if !symbol.flags.is_stripped() + && (symbol.flags.is_force_active() + || (force_active + && matches!(symbol.kind, ObjSymbolKind::Function | ObjSymbolKind::Object))) { active_flags |= 0x8; } diff --git a/src/util/config.rs b/src/util/config.rs index 1255184..30d8031 100644 --- a/src/util/config.rs +++ b/src/util/config.rs @@ -130,6 +130,9 @@ pub fn parse_symbol_line(line: &str, obj: &mut ObjInfo) -> Result { symbol.flags.0 |= ObjSymbolFlags::ForceActive; } + "stripped" => { + symbol.flags.0 |= ObjSymbolFlags::Stripped; + } "noreloc" => { ensure!( symbol.size != 0, @@ -270,6 +273,9 @@ where W: Write + ?Sized { // if symbol.flags.is_force_active() { // write!(w, " force_active")?; // } + if symbol.flags.is_stripped() { + write!(w, " stripped")?; + } if let Some(section) = symbol.section { if obj.blocked_ranges.contains_key(&SectionAddress::new(section, symbol.address as u32)) { write!(w, " noreloc")?; diff --git a/src/util/map.rs b/src/util/map.rs index 92ab679..d8a9712 100644 --- a/src/util/map.rs +++ b/src/util/map.rs @@ -1,22 +1,26 @@ #![allow(dead_code)] #![allow(unused_mut)] use std::{ - collections::{BTreeMap, HashMap}, + collections::{BTreeMap, HashMap, HashSet}, hash::Hash, io::BufRead, - mem::replace, + mem::{replace, take}, path::Path, }; use anyhow::{anyhow, bail, Error, Result}; use cwdemangle::{demangle, DemangleOptions}; use flagset::FlagSet; +use itertools::Itertools; use multimap::MultiMap; use once_cell::sync::Lazy; use regex::{Captures, Regex}; use crate::{ - obj::{ObjInfo, ObjKind, ObjSplit, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind}, + obj::{ + ObjInfo, ObjKind, ObjSplit, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind, + ObjUnit, + }, util::{file::map_file, nested::NestedVec}, }; @@ -46,6 +50,7 @@ pub struct SymbolEntry { pub address: u32, pub size: u32, pub align: Option, + pub unused: bool, } #[derive(Debug, Clone, Hash, Eq, PartialEq)] @@ -124,6 +129,9 @@ pub struct MapInfo { pub link_map_symbols: HashMap, pub section_symbols: HashMap>>, pub section_units: HashMap>, + // For common BSS inflation correction + pub common_bss_start: Option, + pub mw_comment_version: Option, } impl MapInfo { @@ -146,10 +154,10 @@ struct LinkMapState { #[derive(Default)] struct SectionLayoutState { current_section: String, - current_unit: Option, units: Vec<(u32, String)>, symbols: BTreeMap>, has_link_map: bool, + last_address: u32, } enum ProcessMapState { @@ -379,6 +387,7 @@ impl StateMachine { address: 0, size: 0, align: None, + unused: false, }); if !is_duplicate { state.last_symbol = Some(symbol_ref.clone()); @@ -405,59 +414,137 @@ impl StateMachine { address: 0, size: 0, align: None, + unused: false, }); Ok(()) } fn end_section_layout(mut state: SectionLayoutState, entries: &mut MapInfo) -> Result<()> { - // Resolve duplicate TUs - // let mut existing = HashSet::new(); - // for idx in 0..state.units.len() { - // let (addr, unit) = &state.units[idx]; - // // FIXME - // if - // /*state.current_section == ".bss" ||*/ - // existing.contains(unit) { - // if - // /*state.current_section == ".bss" ||*/ - // &state.units[idx - 1].1 != unit { - // let new_name = format!("{unit}_{}_{:010X}", state.current_section, addr); - // log::info!("Renaming {unit} to {new_name}"); - // for idx2 in 0..idx { - // let (addr, n_unit) = &state.units[idx2]; - // if unit == n_unit { - // let new_name = - // format!("{n_unit}_{}_{:010X}", state.current_section, addr); - // log::info!("Renaming 2 {n_unit} to {new_name}"); - // state.units[idx2].1 = new_name; - // break; - // } - // } - // state.units[idx].1 = new_name; - // } - // } else { - // existing.insert(unit.clone()); - // } - // } + // Check for duplicate TUs and common BSS + let mut existing = HashSet::new(); + for (addr, unit) in state.units.iter().dedup_by(|(_, a), (_, b)| a == b) { + if existing.contains(unit) { + if state.current_section == ".bss" { + if entries.common_bss_start.is_none() { + log::warn!("Assuming common BSS start @ {:#010X} ({})", addr, unit); + log::warn!("Please verify and set common_start in config.yml"); + entries.common_bss_start = Some(*addr); + } + } else { + log::error!( + "Duplicate TU in {}: {} @ {:#010X}", + state.current_section, + unit, + addr + ); + log::error!("Please rename the TUs manually to avoid conflicts"); + } + } else { + existing.insert(unit.clone()); + } + } + + // Perform common BSS inflation correction + // https://github.com/encounter/dtk-template/blob/main/docs/common_bss.md#inflation-bug + let check_common_bss_inflation = state.current_section == ".bss" + && entries.common_bss_start.is_some() + && matches!(entries.mw_comment_version, Some(n) if n < 11); + if check_common_bss_inflation { + log::info!("Checking for common BSS inflation..."); + let common_bss_start = entries.common_bss_start.unwrap(); + + // Correct address for unused common BSS symbols that are first in a TU + let mut symbols_iter = state.symbols.iter_mut().peekable(); + let mut last_unit = None; + let mut add_to_next = vec![]; + while let Some((_, symbols)) = symbols_iter.next() { + let next_addr = if let Some((&next_addr, _)) = symbols_iter.peek() { + next_addr + } else { + u32::MAX + }; + let mut to_add = take(&mut add_to_next); + symbols.retain(|e| { + if e.address >= common_bss_start && e.unused && e.unit != last_unit { + log::debug!( + "Updating address for {} @ {:#010X} to {:#010X}", + e.name, + e.address, + next_addr + ); + let mut e = e.clone(); + e.address = next_addr; + add_to_next.push(e); + return false; + } + if !e.unused { + last_unit = e.unit.clone(); + } + true + }); + to_add.extend(take(symbols)); + *symbols = to_add; + } + + // Correct size for common BSS symbols that are first in a TU (inflated) + let mut unit_iter = state + .units + .iter() + .skip_while(|&&(addr, _)| addr < common_bss_start) + .dedup_by(|&(_, a), &(_, b)| a == b) + .peekable(); + while let Some((start_addr, unit)) = unit_iter.next() { + let unit_symbols = if let Some(&&(end_addr, _)) = unit_iter.peek() { + state.symbols.range(*start_addr..end_addr).collect_vec() + } else { + state.symbols.range(*start_addr..).collect_vec() + }; + let mut symbol_iter = unit_symbols.iter().flat_map(|(_, v)| *v); + let Some(first_symbol) = symbol_iter.next() else { continue }; + let first_addr = first_symbol.address; + let mut remaining_size = symbol_iter.map(|e| e.size).sum::(); + if remaining_size == 0 { + continue; + } + if first_symbol.size > remaining_size { + let new_size = first_symbol.size - remaining_size; + log::info!( + "Correcting size for {} ({}) @ {:#010X} ({:#X} -> {:#X})", + first_symbol.name, + unit, + first_addr, + first_symbol.size, + new_size + ); + state.symbols.get_mut(&first_addr).unwrap().iter_mut().next().unwrap().size = + new_size; + } else { + log::warn!( + "Inflated size not detected for {} ({}) @ {:#010X} ({} <= {})", + first_symbol.name, + unit, + first_addr, + first_symbol.size, + remaining_size + ); + } + } + } + if !state.symbols.is_empty() { + // Remove "unused" symbols + for symbols in state.symbols.values_mut() { + symbols.retain(|e| { + !e.unused || + // Except for unused common BSS symbols needed to match the inflated size + (check_common_bss_inflation && e.address >= entries.common_bss_start.unwrap()) + }); + } entries.section_symbols.insert(state.current_section.clone(), state.symbols); } if !state.units.is_empty() { entries.section_units.insert(state.current_section.clone(), state.units); } - // Set last section size - // if let Some(last_unit) = state.section_units.last() { - // let last_unit = state.unit_override.as_ref().unwrap_or(last_unit); - // nested_try_insert( - // &mut entries.unit_section_ranges, - // last_unit.clone(), - // state.current_section.clone(), - // state.last_unit_start..state.last_section_end, - // ) - // .with_context(|| { - // format!("TU '{}' already exists in section '{}'", last_unit, state.current_section) - // })?; - // } Ok(()) } @@ -466,10 +553,6 @@ impl StateMachine { state: &mut SectionLayoutState, result: &MapInfo, ) -> Result<()> { - if captures["rom_addr"].trim() == "UNUSED" { - return Ok(()); - } - let sym_name = captures["sym"].trim(); if sym_name == "*fill*" { return Ok(()); @@ -479,14 +562,27 @@ impl StateMachine { if tu == "*fill*" || tu == "Linker Generated Symbol File" { return Ok(()); } + let is_new_tu = match state.units.last() { + None => true, + Some((_, name)) => name != &tu, + }; - let address = u32::from_str_radix(captures["addr"].trim(), 16)?; + let (address, unused) = if captures["rom_addr"].trim() == "UNUSED" { + // Addresses for unused symbols that _start_ a TU + // are corrected in end_section_layout + (state.last_address, true) + } else { + let address = u32::from_str_radix(captures["addr"].trim(), 16)?; + state.last_address = address; + (address, false) + }; let size = u32::from_str_radix(captures["size"].trim(), 16)?; let align = captures.name("align").and_then(|m| m.as_str().trim().parse::().ok()); - if state.current_unit.as_ref() != Some(&tu) || sym_name == state.current_section { - state.current_unit = Some(tu.clone()); - state.units.push((address, tu.clone())); + if is_new_tu || sym_name == state.current_section { + if !unused { + state.units.push((address, tu.clone())); + } if sym_name == state.current_section { return Ok(()); } @@ -503,9 +599,10 @@ impl StateMachine { address, size, align, + unused, } } else { - let mut visibility = if state.has_link_map { + let mut visibility = if state.has_link_map && !unused { log::warn!( "Symbol not in link map: {} ({}). Type and visibility unknown.", sym_name, @@ -535,6 +632,7 @@ impl StateMachine { address, size, align, + unused, } }; state.symbols.nested_push(address, entry); @@ -581,18 +679,24 @@ impl StateMachine { address, size: 0, align: None, + unused: false, }); }; - // log::info!("Linker generated symbol: {} @ {:#010X}", name, address); Ok(()) } } -pub fn process_map(reader: &mut R) -> Result -where R: BufRead + ?Sized { +pub fn process_map( + reader: &mut R, + common_bss_start: Option, + mw_comment_version: Option, +) -> Result +where + R: BufRead + ?Sized, +{ let mut sm = StateMachine { state: ProcessMapState::None, - result: Default::default(), + result: MapInfo { common_bss_start, mw_comment_version, ..Default::default() }, has_link_map: false, }; for result in reader.lines() { @@ -607,10 +711,17 @@ where R: BufRead + ?Sized { Ok(sm.result) } -pub fn apply_map_file

(path: P, obj: &mut ObjInfo) -> Result<()> -where P: AsRef { +pub fn apply_map_file

( + path: P, + obj: &mut ObjInfo, + common_bss_start: Option, + mw_comment_version: Option, +) -> Result<()> +where + P: AsRef, +{ let file = map_file(&path)?; - let info = process_map(&mut file.as_reader())?; + let info = process_map(&mut file.as_reader(), common_bss_start, mw_comment_version)?; apply_map(&info, obj) } @@ -644,6 +755,7 @@ pub fn apply_map(result: &MapInfo, obj: &mut ObjInfo) -> Result<()> { log::warn!("Section {} @ {:#010X} not found in map", section.name, section.address); } } + // Add section symbols for (section_name, symbol_map) in &result.section_symbols { let (section_index, _) = obj @@ -654,11 +766,13 @@ pub fn apply_map(result: &MapInfo, obj: &mut ObjInfo) -> Result<()> { add_symbol(obj, symbol_entry, Some(section_index))?; } } + // Add absolute symbols // TODO // for symbol_entry in result.link_map_symbols.values().filter(|s| s.unit.is_none()) { // add_symbol(obj, symbol_entry, None)?; // } + // Add splits for (section_name, unit_order) in &result.section_units { let (_, section) = obj @@ -672,11 +786,24 @@ pub fn apply_map(result: &MapInfo, obj: &mut ObjInfo) -> Result<()> { .peek() .map(|(addr, _)| *addr) .unwrap_or_else(|| (section.address + section.size) as u32); + let common = section_name == ".bss" + && matches!(result.common_bss_start, Some(start) if *addr >= start); + let unit = unit.replace(' ', "/"); + + // Disable mw_comment_version for assembly units + if unit.ends_with(".s") && !obj.link_order.iter().any(|u| u.name == unit) { + obj.link_order.push(ObjUnit { + name: unit.clone(), + autogenerated: false, + comment_version: Some(0), + }); + } + section.splits.push(*addr, ObjSplit { - unit: unit.replace(' ', "/"), + unit, end: next, align: None, - common: false, + common, autogenerated: false, skip: false, rename: None, @@ -698,6 +825,9 @@ fn add_symbol(obj: &mut ObjInfo, symbol_entry: &SymbolEntry, section: Option Result<()> { /// Ensures that all .bss splits following a common split are also marked as common. fn update_common_splits(obj: &mut ObjInfo, common_start: Option) -> Result<()> { - let Some((bss_section_index, bss_section)) = obj.sections.by_name(".bss")? else { - return Ok(()); - }; - let Some(common_bss_start) = common_start.or_else(|| { - bss_section.splits.iter().find(|(_, split)| split.common).map(|(addr, _)| addr) + let Some((bss_section_index, common_bss_start)) = (match common_start { + Some(addr) => Some(( + obj.sections.by_name(".bss")?.ok_or_else(|| anyhow!("Failed to find .bss section"))?.0, + addr, + )), + None => obj.sections.common_bss_start(), }) else { return Ok(()); }; @@ -484,7 +485,7 @@ fn validate_splits(obj: &ObjInfo) -> Result<()> { if let Some((_, symbol)) = obj .symbols .for_section_range(section_index, ..addr) - .filter(|&(_, s)| s.size_known && s.size > 0) + .filter(|&(_, s)| s.size_known && s.size > 0 && !s.flags.is_stripped()) .next_back() { ensure!( @@ -503,7 +504,7 @@ fn validate_splits(obj: &ObjInfo) -> Result<()> { if let Some((_, symbol)) = obj .symbols .for_section_range(section_index, ..split.end) - .filter(|&(_, s)| s.size_known && s.size > 0) + .filter(|&(_, s)| s.size_known && s.size > 0 && !s.flags.is_stripped()) .next_back() { ensure!( @@ -573,6 +574,7 @@ fn add_padding_symbols(obj: &mut ObjInfo) -> Result<()> { } // Add padding symbols for gaps between symbols + let common_bss = obj.sections.common_bss_start(); for (section_index, section) in obj.sections.iter() { if section.name == ".ctors" || section.name == ".dtors" { continue; @@ -585,6 +587,12 @@ fn add_padding_symbols(obj: &mut ObjInfo) -> Result<()> { .filter(|(_, s)| s.size_known && s.size > 0) .peekable(); while let (Some((_, symbol)), Some(&(_, next_symbol))) = (iter.next(), iter.peek()) { + // Common BSS is allowed to have gaps and overlaps to accurately match the common BSS inflation bug + if matches!(common_bss, Some((idx, addr)) if + section_index == idx && symbol.address as u32 >= addr) + { + continue; + } let aligned_end = align_up((symbol.address + symbol.size) as u32, next_symbol.align.unwrap_or(1)); match aligned_end.cmp(&(next_symbol.address as u32)) { @@ -655,7 +663,7 @@ fn trim_split_alignment(obj: &mut ObjInfo) -> Result<()> { if let Some((_, symbol)) = obj .symbols .for_section_range(section_index, addr..split.end) - .filter(|&(_, s)| s.size_known && s.size > 0) + .filter(|&(_, s)| s.size_known && s.size > 0 && !s.flags.is_stripped()) .next_back() { split_end = symbol.address as u32 + symbol.size as u32; @@ -1038,7 +1046,7 @@ pub fn split_obj(obj: &ObjInfo) -> Result> { size: symbol.size, size_known: symbol.size_known, flags: if split.common { - ObjSymbolFlagSet(ObjSymbolFlags::Common.into()) + ObjSymbolFlagSet(symbol.flags.keep_flags() | ObjSymbolFlags::Common) } else { symbol.flags }, @@ -1303,7 +1311,12 @@ pub fn end_for_section(obj: &ObjInfo, section_index: usize) -> Result 0) + .filter(|(_, s)| { + s.kind == ObjSymbolKind::Object + && s.size_known + && s.size > 0 + && !s.flags.is_stripped() + }) .next_back(); match last_symbol { Some((_, symbol)) if is_linker_generated_object(&symbol.name) => {