#![deny(clippy::arithmetic_side_effects)]
use std::collections::BTreeMap;
use crate::error::{Error, Result};
use super::arm64::Thunks;
use super::inputs::CoffInput;
use super::machine::Machine;
use super::options::PeOptions;
use super::read::consts::{
IMAGE_SCN_CNT_CODE, IMAGE_SCN_CNT_INITIALIZED_DATA, IMAGE_SCN_CNT_UNINITIALIZED_DATA,
IMAGE_SCN_MEM_DISCARDABLE, IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ, IMAGE_SCN_MEM_WRITE,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Sort {
None,
ByName,
ByFile,
}
#[derive(Clone, Copy, Debug)]
enum Match {
Exact(&'static [u8]),
Prefix(&'static [u8]),
}
impl Match {
fn matches(self, name: &[u8]) -> bool {
match self {
Self::Exact(text) => name == text,
Self::Prefix(text) => name.starts_with(text),
}
}
}
#[derive(Clone, Copy, Debug)]
struct Rule {
output: &'static [u8],
pattern: Match,
sort: Sort,
}
const fn rule(output: &'static [u8], pattern: Match, sort: Sort) -> Rule {
Rule {
output,
pattern,
sort,
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Marker {
Etext,
DataStart,
DataEnd,
PseudoStart,
PseudoEnd,
CtorHead,
CtorTail,
DtorHead,
DtorTail,
CrtXcStart,
CrtXcEnd,
CrtXiStart,
CrtXiEnd,
CrtXlStart,
CrtXpStart,
CrtXpEnd,
CrtXtStart,
CrtXtEnd,
CrtXdStart,
CrtXdEnd,
IdataNull,
IatStart,
IatEnd,
TlsStart,
TlsEnd,
SafeSehTable,
}
#[derive(Clone, Copy, Debug)]
enum Step {
Place(Rule),
Insert(&'static [u8], Marker),
Align(&'static [u8], u32),
AlignWide(&'static [u8], u32),
}
const RECIPE: &[Step] = &[
Step::Place(rule(b".text", Match::Exact(b".init"), Sort::None)),
Step::Place(rule(b".text", Match::Exact(b".text"), Sort::None)),
Step::Place(rule(b".text", Match::Prefix(b".text$"), Sort::ByName)),
Step::Place(rule(b".text", Match::Prefix(b".text."), Sort::None)),
Step::Place(rule(
b".text",
Match::Prefix(b".gnu.linkonce.t."),
Sort::None,
)),
Step::Place(rule(b".text", Match::Exact(b".glue_7t"), Sort::None)),
Step::Place(rule(b".text", Match::Exact(b".glue_7"), Sort::None)),
Step::AlignWide(b".text", 8),
Step::Place(rule(b".text", Match::Exact(b".fini"), Sort::None)),
Step::Place(rule(b".text", Match::Exact(b".gcc_exc"), Sort::None)),
Step::Place(rule(
b".text",
Match::Exact(b".gcc_except_table"),
Sort::None,
)),
Step::Insert(b".text", Marker::Etext),
Step::Insert(b".data", Marker::DataStart),
Step::Place(rule(b".data", Match::Exact(b".data"), Sort::None)),
Step::Place(rule(b".data", Match::Exact(b".data2"), Sort::None)),
Step::Place(rule(b".data", Match::Prefix(b".data$"), Sort::ByName)),
Step::Place(rule(b".data", Match::Prefix(b".data."), Sort::None)),
Step::Place(rule(
b".data",
Match::Prefix(b".gnu.linkonce.d."),
Sort::None,
)),
Step::Place(rule(b".data", Match::Exact(b".jcr"), Sort::None)),
Step::Insert(b".data", Marker::DataEnd),
Step::Place(rule(
b".data",
Match::Exact(b".data_cygwin_nocopy"),
Sort::None,
)),
Step::Place(rule(b".rdata", Match::Exact(b".rdata"), Sort::None)),
Step::Place(rule(b".rdata", Match::Prefix(b".rdata$"), Sort::ByName)),
Step::Place(rule(b".rdata", Match::Prefix(b".rdata."), Sort::None)),
Step::Place(rule(b".rdata", Match::Exact(b".rodata"), Sort::None)),
Step::Place(rule(b".rdata", Match::Prefix(b".rodata."), Sort::None)),
Step::Place(rule(
b".rdata",
Match::Prefix(b".gnu.linkonce.r."),
Sort::None,
)),
Step::Align(b".rdata", 4),
Step::Insert(b".rdata", Marker::PseudoStart),
Step::Place(rule(
b".rdata",
Match::Exact(b".rdata_runtime_pseudo_reloc"),
Sort::None,
)),
Step::Insert(b".rdata", Marker::PseudoEnd),
Step::AlignWide(b".rdata", 8),
Step::Insert(b".rdata", Marker::CtorHead),
Step::Place(rule(b".rdata", Match::Exact(b".ctors"), Sort::None)),
Step::Place(rule(b".rdata", Match::Exact(b".ctor"), Sort::None)),
Step::Place(rule(b".rdata", Match::Prefix(b".ctors."), Sort::ByName)),
Step::Insert(b".rdata", Marker::CtorTail),
Step::Insert(b".rdata", Marker::DtorHead),
Step::Place(rule(b".rdata", Match::Exact(b".dtors"), Sort::None)),
Step::Place(rule(b".rdata", Match::Exact(b".dtor"), Sort::None)),
Step::Place(rule(b".rdata", Match::Prefix(b".dtors."), Sort::ByName)),
Step::Insert(b".rdata", Marker::DtorTail),
Step::Insert(b".rdata", Marker::CrtXcStart),
Step::Place(rule(b".rdata", Match::Prefix(b".CRT$XC"), Sort::ByName)),
Step::Insert(b".rdata", Marker::CrtXcEnd),
Step::Insert(b".rdata", Marker::CrtXiStart),
Step::Place(rule(b".rdata", Match::Prefix(b".CRT$XI"), Sort::ByName)),
Step::Insert(b".rdata", Marker::CrtXiEnd),
Step::Insert(b".rdata", Marker::CrtXlStart),
Step::Place(rule(b".rdata", Match::Prefix(b".CRT$XL"), Sort::ByName)),
Step::Insert(b".rdata", Marker::CrtXpStart),
Step::Place(rule(b".rdata", Match::Prefix(b".CRT$XP"), Sort::ByName)),
Step::Insert(b".rdata", Marker::CrtXpEnd),
Step::Insert(b".rdata", Marker::CrtXtStart),
Step::Place(rule(b".rdata", Match::Prefix(b".CRT$XT"), Sort::ByName)),
Step::Insert(b".rdata", Marker::CrtXtEnd),
Step::Insert(b".rdata", Marker::CrtXdStart),
Step::Place(rule(b".rdata", Match::Prefix(b".CRT$XD"), Sort::ByName)),
Step::Insert(b".rdata", Marker::CrtXdEnd),
Step::Place(rule(b".rdata", Match::Prefix(b".CRT$"), Sort::ByName)),
Step::Align(b".rdata", 4),
Step::Insert(b".rdata", Marker::SafeSehTable),
Step::Place(rule(b".eh_frame", Match::Prefix(b".eh_frame"), Sort::None)),
Step::Place(rule(b".pdata", Match::Prefix(b".pdata"), Sort::None)),
Step::Place(rule(b".xdata", Match::Prefix(b".xdata"), Sort::None)),
Step::Place(rule(b".bss", Match::Exact(b".bss"), Sort::None)),
Step::Place(rule(b".bss", Match::Prefix(b".bss$"), Sort::ByName)),
Step::Place(rule(b".bss", Match::Prefix(b".bss."), Sort::None)),
Step::Place(rule(
b".bss",
Match::Prefix(b".gnu.linkonce.b."),
Sort::None,
)),
Step::Place(rule(b".edata", Match::Exact(b".edata"), Sort::None)),
Step::Place(rule(b".idata", Match::Exact(b".idata$2"), Sort::ByFile)),
Step::Place(rule(b".idata", Match::Exact(b".idata$3"), Sort::ByFile)),
Step::Insert(b".idata", Marker::IdataNull),
Step::AlignWide(b".idata", 8),
Step::Place(rule(b".idata", Match::Exact(b".idata$4"), Sort::ByFile)),
Step::Insert(b".idata", Marker::IatStart),
Step::Place(rule(b".idata", Match::Exact(b".idata$5"), Sort::ByFile)),
Step::Insert(b".idata", Marker::IatEnd),
Step::Place(rule(b".idata", Match::Exact(b".idata$6"), Sort::ByFile)),
Step::Place(rule(b".idata", Match::Exact(b".idata$7"), Sort::ByFile)),
Step::Insert(b".tls", Marker::TlsStart),
Step::Place(rule(b".tls", Match::Exact(b".tls$AAA"), Sort::None)),
Step::Place(rule(b".tls", Match::Exact(b".tls"), Sort::None)),
Step::Place(rule(b".tls", Match::Exact(b".tls$"), Sort::None)),
Step::Place(rule(b".tls", Match::Prefix(b".tls$"), Sort::ByName)),
Step::Insert(b".tls", Marker::TlsEnd),
];
const TRAILING: &[&[u8]] = &[b".rsrc", b".reloc"];
#[derive(Clone, Debug)]
pub enum Piece {
Input {
file: u32,
section: u32,
},
Fill(Vec<u8>),
Zero,
Thunks {
file: u32,
section: u32,
},
}
#[derive(Clone, Debug)]
pub struct Chunk {
pub offset: u32,
pub size: u32,
pub piece: Piece,
}
#[derive(Clone, Debug, Default)]
pub struct OutSection {
pub name: Vec<u8>,
pub characteristics: u32,
pub rva: u32,
pub virtual_size: u32,
pub raw_size: u32,
pub file_offset: u32,
pub chunks: Vec<Chunk>,
pub align: u32,
}
impl OutSection {
#[must_use]
pub fn is_bss(&self) -> bool {
self.characteristics & IMAGE_SCN_CNT_UNINITIALIZED_DATA != 0
&& self.characteristics & IMAGE_SCN_CNT_INITIALIZED_DATA == 0
}
}
#[must_use]
pub fn characteristics_for(name: &[u8]) -> u32 {
const R: u32 = IMAGE_SCN_MEM_READ;
const W: u32 = IMAGE_SCN_MEM_WRITE;
const X: u32 = IMAGE_SCN_MEM_EXECUTE;
const DATA: u32 = IMAGE_SCN_CNT_INITIALIZED_DATA;
const BSS: u32 = IMAGE_SCN_CNT_UNINITIALIZED_DATA;
const CODE: u32 = IMAGE_SCN_CNT_CODE;
match name {
b".text" => CODE | X | R,
b".data" | b".tls" => DATA | R | W,
b".bss" => BSS | R | W,
b".reloc" => DATA | IMAGE_SCN_MEM_DISCARDABLE | R,
_ if name.starts_with(b".debug") || name == b".stab" || name == b".stabstr" => {
DATA | IMAGE_SCN_MEM_DISCARDABLE | R
}
_ => DATA | R,
}
}
#[derive(Clone, Copy, Debug)]
struct Placed {
step: u32,
sort: Sort,
file: u32,
section: u32,
}
#[derive(Debug, Default)]
pub struct Layout {
pub sections: Vec<OutSection>,
pub rvas: Vec<Vec<u32>>,
pub outputs: Vec<Vec<u32>>,
pub size_of_headers: u32,
pub size_of_image: u32,
pub file_size: u64,
pub markers: Vec<(Marker, u32, u32)>,
pub thunks: Thunks,
pub thunk_rvas: BTreeMap<(u32, u32), u32>,
pub machine: Machine,
}
impl Layout {
#[must_use]
pub fn by_name(&self, name: &[u8]) -> Option<&OutSection> {
self.sections.iter().find(|section| section.name == name)
}
#[must_use]
pub fn index_of(&self, name: &[u8]) -> Option<u32> {
self.sections
.iter()
.position(|section| section.name == name)
.and_then(|index| u32::try_from(index).ok())
}
#[must_use]
pub fn rva_of(&self, file: usize, section: u32) -> Option<u32> {
let index = usize::try_from(section.checked_sub(1)?).ok()?;
self.rvas.get(file)?.get(index).copied()
}
#[must_use]
pub fn marker(&self, marker: Marker) -> Option<u32> {
self.markers
.iter()
.find(|&&(kind, _, _)| kind == marker)
.and_then(|&(_, section, offset)| {
Some(
self.sections
.get(section as usize)?
.rva
.wrapping_add(offset),
)
})
}
#[must_use]
pub fn end_of(&self, name: &[u8], alignment: u32) -> Option<u32> {
let section = self.by_name(name)?;
Some(align_up32(
section.rva.wrapping_add(section.virtual_size),
alignment,
))
}
}
#[must_use]
pub fn align_up32(value: u32, align: u32) -> u32 {
if align <= 1 {
return value;
}
let mask = align.wrapping_sub(1);
value.checked_add(mask).map_or(value, |sum| sum & !mask)
}
#[must_use]
pub fn align_up64(value: u64, align: u64) -> u64 {
if align <= 1 {
return value;
}
let mask = align.wrapping_sub(1);
value.checked_add(mask).map_or(value, |sum| sum & !mask)
}
#[derive(Clone, Copy, Debug)]
pub struct CommonSymbol {
pub symbol: crate::ids::SymbolId,
pub size: u32,
pub align: u32,
}
#[derive(Debug)]
pub struct LayoutInput<'i, 'a> {
pub files: &'i [CoffInput<'a>],
pub options: &'i PeOptions,
pub commons: &'i [CommonSymbol],
pub synthetic: &'i [(Vec<u8>, u32, u32)],
pub pseudo_reloc_size: u32,
pub thunks: &'i Thunks,
pub safe_seh_size: u32,
}
pub fn layout(input: &LayoutInput<'_, '_>) -> Result<Layout> {
let options = input.options;
let mut build = Builder::default();
let mut step_output: Vec<usize> = Vec::with_capacity(RECIPE.len());
for (index, step) in RECIPE.iter().enumerate() {
let name = match step {
Step::Place(rule) => rule.output,
Step::Insert(name, _) | Step::Align(name, _) | Step::AlignWide(name, _) => name,
};
let rank = u32::try_from(index).unwrap_or(0);
step_output.push(build.section(name, rank));
}
for (offset, name) in TRAILING.iter().enumerate() {
let rank = RANK_TRAILING.saturating_add(u32::try_from(offset).unwrap_or(0));
build.section(name, rank);
}
let mut placements: Vec<Vec<u32>> = Vec::with_capacity(input.files.len());
for (file_index, file) in input.files.iter().enumerate() {
let file_index = u32::try_from(file_index).unwrap_or(u32::MAX);
let Some(parsed) = file.object() else {
placements.push(Vec::new());
continue;
};
let mut per_section = vec![u32::MAX; parsed.sections.len()];
for section in &parsed.sections {
if !section.is_live() {
continue;
}
let name: &[u8] = §ion.name;
let step = RECIPE.iter().position(|step| match step {
Step::Place(rule) => rule.pattern.matches(name),
Step::Insert(..) | Step::Align(..) | Step::AlignWide(..) => false,
});
let (at, sort, step_index) = match step {
Some(step) => {
let Some(Step::Place(rule)) = RECIPE.get(step) else {
continue;
};
(
step_output.get(step).copied().unwrap_or(0),
rule.sort,
u32::try_from(step).unwrap_or(u32::MAX),
)
}
None => {
let base = orphan_name(name);
let at = build.section(base, orphan_rank(base));
let sort = if base.len() == name.len() {
Sort::None
} else {
Sort::ByName
};
(at, sort, u32::MAX)
}
};
if let Some(slot) = per_section.get_mut(section.number.wrapping_sub(1) as usize) {
*slot = u32::try_from(at).unwrap_or(u32::MAX);
}
if let Some(list) = build.order.get_mut(at) {
list.push(Placed {
step: step_index,
sort,
file: file_index,
section: section.number,
});
}
if let Some(out) = build.sections.get_mut(at) {
out.align = out.align.max(section.align);
if section.header.is_code() {
out.characteristics |= IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE;
}
if out.characteristics & IMAGE_SCN_CNT_CODE != 0
&& section.header.is_initialized_data()
{
out.characteristics |= IMAGE_SCN_CNT_INITIALIZED_DATA;
}
}
}
placements.push(per_section);
}
for (name, size, align) in input.synthetic {
let rank = if TRAILING.contains(&name.as_slice()) {
RANK_TRAILING
} else {
RANK_ORPHAN
};
let at = build.section(name, rank);
if let Some(out) = build.sections.get_mut(at) {
out.align = out.align.max(*align);
build.pending[at].push(*size);
}
}
let pointer = options.target().pointer_size();
let wide = !options.target().is_pe32();
let mut markers = Vec::new();
for at in 0..build.sections.len() {
let mut list = std::mem::take(&mut build.order[at]);
sort_contributions(&mut list, input.files);
let pending = std::mem::take(&mut build.pending[at]);
let mut chunks: Vec<Chunk> = Vec::with_capacity(list.len());
let name = build.sections[at].name.clone();
let align = build.sections[at].align;
let mut offset = 0u32;
let mut cursor = 0usize;
for (step_index, step) in RECIPE.iter().enumerate() {
let step_index = u32::try_from(step_index).unwrap_or(u32::MAX);
match step {
Step::Align(step_name, step_align) if *step_name == name.as_slice() => {
offset = align_up32(offset, *step_align);
}
Step::AlignWide(step_name, step_align) if wide && *step_name == name.as_slice() => {
offset = align_up32(offset, *step_align);
}
Step::Insert(step_name, marker) if *step_name == name.as_slice() => {
let bytes = marker_bytes(*marker, pointer);
markers.push((*marker, u32::try_from(at).unwrap_or(u32::MAX), offset));
if !bytes.is_empty() {
let size = u32::try_from(bytes.len()).unwrap_or(0);
chunks.push(Chunk {
offset,
size,
piece: Piece::Fill(bytes),
});
offset = offset.saturating_add(size);
}
let reserved = match marker {
Marker::PseudoStart => input.pseudo_reloc_size,
Marker::SafeSehTable => input.safe_seh_size,
_ => 0,
};
if reserved > 0 {
chunks.push(Chunk {
offset,
size: reserved,
piece: Piece::Zero,
});
offset = offset.saturating_add(reserved);
}
}
Step::Place(_) => {
while let Some(placed) = list.get(cursor).copied() {
if placed.step != step_index {
break;
}
cursor = cursor.saturating_add(1);
offset = push_input(&mut chunks, input, placed, offset);
}
}
Step::Align(..) | Step::AlignWide(..) | Step::Insert(..) => {}
}
}
while let Some(placed) = list.get(cursor).copied() {
cursor = cursor.saturating_add(1);
offset = push_input(&mut chunks, input, placed, offset);
}
if name == b".bss" {
for common in input.commons {
let common_align = common.align.max(1);
offset = align_up32(offset, common_align);
build.sections[at].align = build.sections[at].align.max(common_align);
chunks.push(Chunk {
offset,
size: common.size,
piece: Piece::Zero,
});
offset = offset.saturating_add(common.size);
}
}
for size in pending {
offset = align_up32(offset, align.max(1));
chunks.push(Chunk {
offset,
size,
piece: Piece::Zero,
});
offset = offset.saturating_add(size);
}
build.sections[at].virtual_size = offset;
build.sections[at].chunks = chunks;
}
let mut keep: Vec<usize> = (0..build.sections.len())
.filter(|&at| build.sections[at].virtual_size > 0)
.collect();
keep.sort_by_key(|&at| (build.ranks[at], at));
let mut remap = vec![u32::MAX; build.sections.len()];
for (new, &old) in keep.iter().enumerate() {
remap[old] = u32::try_from(new).unwrap_or(u32::MAX);
}
let mut sections: Vec<OutSection> = Vec::with_capacity(keep.len());
for &old in &keep {
sections.push(std::mem::take(&mut build.sections[old]));
}
for per_section in &mut placements {
for slot in per_section.iter_mut() {
*slot = remap.get(*slot as usize).copied().unwrap_or(u32::MAX);
}
}
let markers: Vec<(Marker, u32, u32)> = markers
.into_iter()
.filter_map(|(marker, at, offset)| {
let at = remap.get(at as usize).copied()?;
(at != u32::MAX).then_some((marker, at, offset))
})
.collect();
let header_size = u64::try_from(super::write::header_size(sections.len(), options.target()))
.map_err(|_| Error::Limit("too many output sections".into()))?;
let file_alignment = u64::from(options.file_alignment);
let section_alignment = options.section_alignment;
let size_of_headers = u32::try_from(align_up64(header_size, file_alignment))
.map_err(|_| Error::Limit("PE headers too large".into()))?;
let mut rva = align_up32(size_of_headers, section_alignment);
let mut file_offset = u64::from(size_of_headers);
for section in &mut sections {
section.rva = rva;
if section.is_bss() {
section.raw_size = 0;
section.file_offset = 0;
} else {
section.raw_size =
u32::try_from(align_up64(u64::from(section.virtual_size), file_alignment))
.map_err(|_| Error::Limit("output section too large".into()))?;
section.file_offset = u32::try_from(file_offset)
.map_err(|_| Error::Limit("output file too large".into()))?;
file_offset = file_offset.saturating_add(u64::from(section.raw_size));
}
rva = rva
.checked_add(section.virtual_size)
.map(|end| align_up32(end, section_alignment))
.ok_or_else(|| Error::Limit("image larger than 4 GiB".into()))?;
}
let mut thunk_rvas = BTreeMap::new();
for section in §ions {
for chunk in §ion.chunks {
if let Piece::Thunks {
file,
section: number,
} = chunk.piece
{
thunk_rvas.insert((file, number), section.rva.wrapping_add(chunk.offset));
}
}
}
let mut rvas: Vec<Vec<u32>> = Vec::with_capacity(input.files.len());
for (file_index, per_section) in placements.iter().enumerate() {
let mut file_rvas = vec![0u32; per_section.len()];
for (index, &at) in per_section.iter().enumerate() {
let Some(section) = sections.get(at as usize) else {
continue;
};
let number = u32::try_from(index.saturating_add(1)).unwrap_or(u32::MAX);
let offset = section
.chunks
.iter()
.find(|chunk| {
matches!(chunk.piece, Piece::Input { file, section }
if file as usize == file_index && section == number)
})
.map_or(0, |chunk| chunk.offset);
if let Some(slot) = file_rvas.get_mut(index) {
*slot = section.rva.wrapping_add(offset);
}
}
rvas.push(file_rvas);
}
Ok(Layout {
size_of_image: rva,
size_of_headers,
file_size: file_offset,
sections,
rvas,
outputs: placements,
markers,
thunks: input.thunks.clone(),
thunk_rvas,
machine: options.target(),
})
}
const RANK_ORPHAN: u32 = 1000;
const RANK_TRAILING: u32 = 2000;
const RANK_DEBUG: u32 = 3000;
const DEBUG_ORDER: &[&[u8]] = &[
b".stab",
b".stabstr",
b".debug_aranges",
b".debug_pubnames",
b".debug_info",
b".debug_abbrev",
b".debug_line",
b".debug_frame",
b".debug_str",
b".debug_loc",
b".debug_macinfo",
b".debug_weaknames",
b".debug_funcnames",
b".debug_typenames",
b".debug_varnames",
b".debug_pubtypes",
b".debug_ranges",
b".debug_types",
b".debug_addr",
b".debug_line_str",
b".debug_loclists",
b".debug_macro",
b".debug_names",
b".debug_rnglists",
b".debug_str_offsets",
b".debug_sup",
b".debug_gdb_scripts",
];
#[must_use]
pub fn is_debug_section(name: &[u8]) -> bool {
name.starts_with(b".debug") || name.starts_with(b".zdebug") || name.starts_with(b".stab")
}
fn orphan_rank(name: &[u8]) -> u32 {
let plain = match name.strip_prefix(b".z") {
Some(rest) if rest.starts_with(b"debug") => [b".".as_slice(), rest].concat(),
_ => name.to_vec(),
};
if let Some(index) = DEBUG_ORDER
.iter()
.position(|known| *known == plain.as_slice())
{
return RANK_DEBUG.saturating_add(u32::try_from(index).unwrap_or(0));
}
if plain.starts_with(b".debug") {
return RANK_DEBUG.saturating_add(u32::try_from(DEBUG_ORDER.len()).unwrap_or(0));
}
RANK_ORPHAN
}
#[derive(Default)]
struct Builder {
sections: Vec<OutSection>,
order: Vec<Vec<Placed>>,
pending: Vec<Vec<u32>>,
ranks: Vec<u32>,
}
impl Builder {
fn section(&mut self, name: &[u8], rank: u32) -> usize {
if let Some(at) = self.sections.iter().position(|out| out.name == name) {
self.ranks[at] = self.ranks[at].min(rank);
return at;
}
self.sections.push(OutSection {
name: name.to_vec(),
characteristics: characteristics_for(name),
rva: 0,
virtual_size: 0,
raw_size: 0,
file_offset: 0,
chunks: Vec::new(),
align: 1,
});
self.order.push(Vec::new());
self.pending.push(Vec::new());
self.ranks.push(rank);
self.sections.len().saturating_sub(1)
}
}
fn push_input(
chunks: &mut Vec<Chunk>,
input: &LayoutInput<'_, '_>,
placed: Placed,
offset: u32,
) -> u32 {
let Some(section) = input
.files
.get(placed.file as usize)
.and_then(CoffInput::object)
.and_then(|parsed| parsed.section(placed.section))
else {
return offset;
};
let offset = align_up32(offset, section.align.max(1));
chunks.push(Chunk {
offset,
size: section.size,
piece: Piece::Input {
file: placed.file,
section: placed.section,
},
});
let end = offset.saturating_add(section.size);
let thunks = input.thunks.block_size(placed.file, placed.section);
if thunks == 0 {
return end;
}
let at = align_up32(end, 4);
chunks.push(Chunk {
offset: at,
size: thunks,
piece: Piece::Thunks {
file: placed.file,
section: placed.section,
},
});
at.saturating_add(thunks)
}
fn sort_contributions(list: &mut [Placed], files: &[CoffInput<'_>]) {
let name_of = |placed: &Placed| -> Vec<u8> {
files
.get(placed.file as usize)
.and_then(CoffInput::object)
.and_then(|parsed| parsed.section(placed.section))
.map_or_else(Vec::new, |section| section.name.to_vec())
};
let file_of = |placed: &Placed| -> Vec<u8> {
files
.get(placed.file as usize)
.map_or_else(Vec::new, |file| file.sort_name().to_vec())
};
list.sort_by_cached_key(|placed| {
let key = match placed.sort {
Sort::None => Vec::new(),
Sort::ByName => name_of(placed),
Sort::ByFile => file_of(placed),
};
(placed.step, key, placed.file, placed.section)
});
}
fn orphan_name(name: &[u8]) -> &[u8] {
match name.iter().position(|&byte| byte == b'$') {
Some(at) => name.get(..at).unwrap_or(name),
None => name,
}
}
fn marker_bytes(marker: Marker, pointer: u32) -> Vec<u8> {
match marker {
Marker::CtorHead | Marker::DtorHead => vec![0xffu8; pointer as usize],
Marker::CtorTail | Marker::DtorTail => vec![0u8; pointer as usize],
Marker::IdataNull => vec![0u8; 20],
_ => Vec::new(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn alignment_helpers() {
assert_eq!(align_up32(0, 16), 0);
assert_eq!(align_up32(1, 16), 16);
assert_eq!(align_up32(16, 16), 16);
assert_eq!(align_up32(u32::MAX, 16), u32::MAX);
assert_eq!(align_up64(513, 512), 1024);
}
#[test]
fn debugging_sections_follow_reloc_in_gnu_order() {
assert!(orphan_rank(b".debug_aranges") < orphan_rank(b".debug_info"));
assert!(orphan_rank(b".debug_line_str") < orphan_rank(b".debug_rnglists"));
assert_eq!(orphan_rank(b".zdebug_info"), orphan_rank(b".debug_info"));
assert!(orphan_rank(b".debug_info") > RANK_TRAILING);
assert!(orphan_rank(b".debug_unknown") > orphan_rank(b".debug_gdb_scripts"));
assert_eq!(orphan_rank(b".mysection"), RANK_ORPHAN);
}
#[test]
fn orphan_names_drop_the_group_suffix() {
assert_eq!(orphan_name(b".CRT$XCA"), b".CRT");
assert_eq!(orphan_name(b".mysection"), b".mysection");
}
#[test]
fn known_characteristics_match_gnu_ld() {
assert_eq!(characteristics_for(b".text"), 0x6000_0020);
assert_eq!(characteristics_for(b".data"), 0xC000_0040);
assert_eq!(characteristics_for(b".rdata"), 0x4000_0040);
assert_eq!(characteristics_for(b".bss"), 0xC000_0080);
assert_eq!(characteristics_for(b".reloc"), 0x4200_0040);
assert_eq!(characteristics_for(b".tls"), 0xC000_0040);
}
}