Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 22 additions & 7 deletions src/analysis/pe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,16 @@ fn detect_imports(obj: &mut ObjInfo, pe: &PeFile32, _data: &[u8]) -> Result<()>
Ok((_hint, name)) => {
let n =
std::str::from_utf8(name).unwrap_or("unknown").trim_end_matches('\0');
format!("__imp__{}", n)
// MSVC IAT name convention: '__imp_' + decorated name.
// Cdecl/stdcall C names already carry a leading '_' (so
// the result is '__imp__Foo'); C++ mangled ('?...') and
// fastcall ('@...') names do not, yielding '__imp_?foo'
// or '__imp_@foo@8' with a single underscore.
if n.starts_with('?') || n.starts_with('@') {
format!("__imp_{}", n)
} else {
format!("__imp__{}", n)
}
}
Err(_) => {
iat_off += 4;
Expand Down Expand Up @@ -177,12 +186,18 @@ fn detect_imports(obj: &mut ObjInfo, pe: &PeFile32, _data: &[u8]) -> Result<()>
i += 1;
continue;
}
// Derive thunk name: "__imp__SetWindowPos" → "_SetWindowPos"
// (strip double underscore, keep single)
let preferred = imp_name
.strip_prefix("__imp__")
.map(|n| format!("_{}", n))
.unwrap_or_else(|| imp_name.to_string());
// Derive thunk name from IAT symbol:
// "__imp__SetWindowPos" → "_SetWindowPos" (cdecl: drop one '_')
// "__imp_?foo@@YAXXZ" → "?foo@@YAXXZ" (MSVC C++: drop '__imp_')
// "__imp_@foo@8" → "@foo@8" (fastcall: drop '__imp_')
let preferred = if let Some(rest) = imp_name.strip_prefix("__imp_") {
// `rest` is the decorated thunk name as MSVC writes it:
// for cdecl it still starts with '_' (the original C prefix);
// for '?'/'@' it is the bare mangled name.
rest.to_string()
} else {
imp_name.to_string()
};
// If the preferred name is already taken (e.g. two thunks for the same
// import), fall back to a generated name so there's no duplicate symbol.
let thunk_name = if used_names.contains(&preferred) {
Expand Down
1 change: 1 addition & 0 deletions src/cmd/rel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,7 @@ fn merge(args: MergeArgs) -> Result<()> {
file_offset: mod_section.file_offset,
section_known: mod_section.section_known,
splits: mod_section.splits.clone(),
sub_regions: mod_section.sub_regions.clone(),
});
section_map.nested_insert(module.module_id, mod_section.elf_index, offset)?;
for (_, mod_symbol) in module.symbols.for_section(mod_section_index) {
Expand Down
2 changes: 1 addition & 1 deletion src/obj/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use anyhow::{Result, anyhow, bail, ensure};
use objdiff_core::obj::split_meta::SplitMeta;
pub use relocations::{ObjReloc, ObjRelocKind, ObjRelocations};
pub use sections::{
ObjSection, ObjSectionKind, ObjSections, SectionIndex, section_kind_for_section,
ObjSection, ObjSectionKind, ObjSections, ObjSubRegion, SectionIndex, section_kind_for_section,
};
pub use splits::{ObjSplit, ObjSplits};
pub use symbols::{
Expand Down
12 changes: 12 additions & 0 deletions src/obj/sections.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,17 @@ pub enum ObjSectionKind {
Bss,
}

/// A logical sub-section that exists within a physical section's VA range.
/// Used to model MSVC COFF grouped sub-sections like `.rdata$r` / `.xdata$x`
/// which the linker merges into a single PE section (`.rdata`) but which
/// .obj files still emit under their distinct names.
#[derive(Debug, Clone)]
pub struct ObjSubRegion {
pub start: u32,
pub end: u32,
pub name: String,
}

#[derive(Debug, Clone)]
pub struct ObjSection {
pub name: String,
Expand All @@ -35,6 +46,7 @@ pub struct ObjSection {
pub file_offset: u64,
pub section_known: bool,
pub splits: ObjSplits,
pub sub_regions: Vec<ObjSubRegion>,
}

#[derive(Debug, Clone)]
Expand Down
10 changes: 9 additions & 1 deletion src/util/coff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use object::{
RelocationEncoding, RelocationKind, RelocationTarget, SectionKind, SymbolFlags, SymbolKind,
SymbolScope,
write::{
Object as WriteObject, Relocation, RelocationFlags, SectionId, Symbol, SymbolId,
Mangling, Object as WriteObject, Relocation, RelocationFlags, SectionId, Symbol, SymbolId,
SymbolSection as WriteSymbolSection,
},
};
Expand Down Expand Up @@ -78,6 +78,7 @@ pub fn process_coff(data: &[u8], name: &str) -> Result<(ObjInfo, Option<u32>)> {
file_offset: section.file_range().map(|(v, _)| v).unwrap_or_default(),
section_known: true,
splits: Default::default(),
sub_regions: Vec::new(),
});
}

Expand Down Expand Up @@ -208,6 +209,13 @@ pub fn process_coff(data: &[u8], name: &str) -> Result<(ObjInfo, Option<u32>)> {

pub fn write_coff(obj: &ObjInfo, export_all: bool) -> Result<Vec<u8>> {
let mut out = WriteObject::new(BinaryFormat::Coff, Architecture::I386, Endianness::Little);
// Disable object crate's auto leading-underscore mangling: it blindly
// prepends '_' to every Text/Data symbol, which corrupts MSVC C++
// mangled names ('?...') and fastcall names ('@...'). dtk's stored
// symbol names already follow the MSVC convention literally (cdecl C
// names include their leading '_' in symbols.txt; mangled names do not),
// so we write them verbatim.
out.set_mangling(Mangling::None);

// Add sections and build section id map (indexed by ObjSectionIndex)
let mut section_ids: Vec<Option<SectionId>> = vec![None; obj.sections.len() as usize];
Expand Down
39 changes: 37 additions & 2 deletions src/util/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,18 @@ where
write!(w, " vaddr:{:#010X}", vaddr)?;
}
writeln!(w)?;
// Round-trip logical sub-section declarations (e.g. .rdata$r) so they
// survive `dtk coff split` regenerating splits.txt.
for region in &section.sub_regions {
writeln!(
w,
"\t{:<11} type:{} vaddr:{:#010X} end:{:#010X}",
region.name,
section_kind_to_str(section.kind),
region.start,
region.end,
)?;
}
}
for unit in obj.link_order.iter().filter(|unit| all || !unit.autogenerated) {
write!(w, "\n{}:", unit.name)?;
Expand Down Expand Up @@ -555,6 +567,10 @@ pub struct SectionDef {
pub kind: Option<ObjSectionKind>,
pub align: Option<u32>,
pub vaddr: Option<u32>,
/// Optional end VA. Combined with `vaddr`, declares this entry as a
/// logical sub-section nested inside an existing physical section (e.g.
/// MSVC COFF `.rdata$r` mapped onto a PE `.rdata`).
pub end: Option<u32>,
}

enum SplitLine {
Expand Down Expand Up @@ -610,7 +626,7 @@ fn parse_section_line(captures: Captures, state: &SplitState) -> Result<SplitLin
if matches!(state, SplitState::Sections(_)) {
let name = &captures["name"];
let mut section =
SectionDef { name: name.to_string(), kind: None, align: None, vaddr: None };
SectionDef { name: name.to_string(), kind: None, align: None, vaddr: None, end: None };

for attr in captures["attrs"].split(' ').filter(|&s| !s.is_empty()) {
if let Some((attr, value)) = attr.split_once(':') {
Expand All @@ -627,6 +643,9 @@ fn parse_section_line(captures: Captures, state: &SplitState) -> Result<SplitLin
"vaddr" => {
section.vaddr = Some(parse_u32(value)?);
}
"end" => {
section.end = Some(parse_u32(value)?);
}
_ => bail!("Unknown section attribute '{attr}'"),
}
} else {
Expand Down Expand Up @@ -726,8 +745,24 @@ where
}
(
SplitState::Sections(index),
SplitLine::Section(SectionDef { name, kind, align, vaddr }),
SplitLine::Section(SectionDef { name, kind, align, vaddr, end }),
) => {
// Logical sub-section: declared with `vaddr:X end:Y`. Mapped
// onto whichever physical section contains [X, Y) — no new
// physical section is consumed.
if obj.kind == ObjKind::Executable
&& let (Some(start), Some(stop)) = (vaddr, end)
{
ensure!(stop > start, "Section '{name}' end must exceed vaddr");
let (parent_idx, _) = obj.sections.with_range(start..stop)?;
let parent = obj.sections.get_mut(parent_idx).unwrap();
parent.sub_regions.push(crate::obj::ObjSubRegion {
start,
end: stop,
name: name.clone(),
});
continue;
}
let Some(obj_section) = obj.sections.get_mut(*index) else {
bail!(
"Section out of bounds: {} (index {}), object has {} sections",
Expand Down
6 changes: 6 additions & 0 deletions src/util/dol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,7 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result<ObjInfo> {
file_offset: file_offset as u64,
section_known: known,
splits: Default::default(),
sub_regions: Vec::new(),
});
}
} else {
Expand Down Expand Up @@ -436,6 +437,7 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result<ObjInfo> {
file_offset: dol_section.file_offset as u64,
section_known: known,
splits: Default::default(),
sub_regions: Vec::new(),
});
}
}
Expand Down Expand Up @@ -468,6 +470,7 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result<ObjInfo> {
file_offset: 0,
section_known: false,
splits: Default::default(),
sub_regions: Vec::new(),
});
}

Expand All @@ -488,6 +491,7 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result<ObjInfo> {
file_offset: 0,
section_known: false,
splits: Default::default(),
sub_regions: Vec::new(),
});
let mut obj = ObjInfo::new(
ObjKind::Executable,
Expand Down Expand Up @@ -515,6 +519,7 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result<ObjInfo> {
file_offset: 0,
section_known: false,
splits: Default::default(),
sub_regions: Vec::new(),
});
sections.push(ObjSection {
name: ".sbss".to_string(),
Expand All @@ -529,6 +534,7 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result<ObjInfo> {
file_offset: 0,
section_known: false,
splits: Default::default(),
sub_regions: Vec::new(),
});
}
n => bail!("Invalid number of BSS sections: {}", n),
Expand Down
1 change: 1 addition & 0 deletions src/util/elf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ pub fn process_elf(path: &Utf8NativePath) -> Result<ObjInfo> {
file_offset: section.file_range().map(|(v, _)| v).unwrap_or_default(),
section_known: true,
splits: Default::default(),
sub_regions: Vec::new(),
});
}

Expand Down
1 change: 1 addition & 0 deletions src/util/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1158,6 +1158,7 @@ pub fn create_obj(result: &MapInfo) -> Result<ObjInfo> {
file_offset,
section_known: true,
splits: Default::default(),
sub_regions: Vec::new(),
}
})
.collect();
Expand Down
1 change: 1 addition & 0 deletions src/util/rel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,7 @@ where
file_offset: offset as u64,
section_known,
splits: Default::default(),
sub_regions: Vec::new(),
});
}
ensure!(
Expand Down
1 change: 1 addition & 0 deletions src/util/rso.rs
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,7 @@ where
file_offset: offset as u64,
section_known: false,
splits: Default::default(),
sub_regions: Vec::new(),
});
if offset == 0 {
total_bss_size += size;
Expand Down
7 changes: 5 additions & 2 deletions src/util/rsp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,15 +166,18 @@ pub fn generate_args_rsp(

lines.push(format!("/BASE:{:#x}", pe.image_base));

// Resolve entry symbol name from the entry VA
// Resolve entry symbol name from the entry VA.
// lld-link's /ENTRY auto-prepends '_' for i386 PE, so strip the leading
// underscore from cdecl C names; mangled names ('?', '@') are passed as-is.
if let Some(entry_sym) = obj.entry.and_then(|e| {
let (sec_idx, _) = obj.sections.at_address(e as u32).ok()?;
obj.symbols
.at_section_address(sec_idx, e as u32)
.find(|(_, s)| s.kind == crate::obj::ObjSymbolKind::Function)
.map(|(_, s)| s.name.clone())
}) {
lines.push(format!("/ENTRY:{entry_sym}"));
let entry_arg = entry_sym.strip_prefix('_').unwrap_or(&entry_sym);
lines.push(format!("/ENTRY:{entry_arg}"));
}

lines.push(format!("/SUBSYSTEM:{},{}", pe.subsystem_name(), pe.major_subsystem_version,));
Expand Down
35 changes: 31 additions & 4 deletions src/util/split.rs
Original file line number Diff line number Diff line change
Expand Up @@ -716,11 +716,28 @@ fn create_gap_splits(obj: &mut ObjInfo) -> Result<()> {
current_address,
split_align
);
// Find any duplicate symbols in this range
// Break auto-fill at sub-region boundaries so the .rdata$r
// and .xdata$x slices each get their own auto unit (and the
// emitted COFF section name matches the region they fall in).
let mut new_split_end = split_start;
for r in &section.sub_regions {
if current_address.address < r.start && new_split_end.address > r.start {
new_split_end.address = r.start;
}
if current_address.address >= r.start
&& current_address.address < r.end
&& new_split_end.address > r.end
{
new_split_end.address = r.end;
}
}
// Find any duplicate symbols in this range
let symbols = obj
.symbols
.for_section_range(section_index, current_address.address..split_start.address)
.for_section_range(
section_index,
current_address.address..new_split_end.address,
)
.filter(|&(_, s)| !s.flags.is_stripped())
.collect_vec();
let mut existing_symbols = HashSet::new();
Expand Down Expand Up @@ -772,7 +789,16 @@ fn create_gap_splits(obj: &mut ObjInfo) -> Result<()> {
current_address,
new_split_end
);
let effective_section = prev_rename.as_deref().unwrap_or(&section.name);
// Prefer an explicit sub-region declaration (e.g. `.rdata$r`
// declared in splits.txt Sections:) over the carry-forward
// rename from the last consumed split.
let region_rename = section.sub_regions.iter().find_map(|r| {
(current_address.address >= r.start && current_address.address < r.end)
.then(|| r.name.clone())
});
let effective_rename = region_rename.or_else(|| prev_rename.clone());
let effective_section =
effective_rename.as_deref().unwrap_or(&section.name);
let unit = format!(
"auto_{:02}_{:08X}_{}",
current_address.section,
Expand All @@ -788,7 +814,7 @@ fn create_gap_splits(obj: &mut ObjInfo) -> Result<()> {
common: false,
autogenerated: true,
skip: false,
rename: prev_rename.clone(),
rename: effective_rename,
},
);
current_address = new_split_end;
Expand Down Expand Up @@ -1612,6 +1638,7 @@ pub fn split_obj(
+ (current_address.address as u64 - section.address),
section_known: true,
splits: Default::default(),
sub_regions: Vec::new(),
});
}

Expand Down
Loading