diff --git a/libwild/src/elf_writer.rs b/libwild/src/elf_writer.rs index d4b3af7d9..f628acc07 100644 --- a/libwild/src/elf_writer.rs +++ b/libwild/src/elf_writer.rs @@ -480,7 +480,7 @@ fn write_file<'data, A: Arch>( write_object::(s, buffers, table_writer, layout, trace, sym_index_map)?; } FileLayout::Prelude(s) => write_prelude::(s, buffers, table_writer, layout)?, - FileLayout::Epilogue(s) => write_epilogue::(s, buffers, table_writer, layout)?, + FileLayout::Epilogue(s) => write_epilogue::(s, buffers, table_writer, layout, trace)?, FileLayout::SyntheticSymbols(s) => write_synthetic_symbols::(s, table_writer, layout)?, FileLayout::LinkerScript(s) => write_linker_script_state::(s, table_writer, layout)?, FileLayout::NotLoaded => {} @@ -1462,11 +1462,30 @@ fn write_object<'data, A: Arch>( let _span = debug_span!("write_file", filename = %object.input).entered(); let _file_span = layout.args().common().trace_span_for_file(object.file_id); + + let Some(FileLayout::Epilogue(epilogue)) = + layout.group_layouts.last().and_then(|g| g.files.last()) + else { + unreachable!("Epilogue is broken and must be the last file in the final layout group"); + }; + + let mut is_harvested = vec![false; object.sections.len()]; + for h in &epilogue.script_sorted_sections { + if h.file_id == object.file_id { + is_harvested[h.section_index.0] = true; + } + } + for (i, sec) in object.sections.iter().enumerate() { let section_index = object::SectionIndex(i); match sec { SectionSlot::Loaded(sec) => { + // Skip if handled by Harvester + if is_harvested[i] { + continue; + } + write_object_section::( object, layout, @@ -3927,6 +3946,7 @@ fn write_epilogue>( buffers: &mut OutputSectionPartMap<&mut [u8]>, table_writer: &mut TableWriter, layout: &ElfLayout, + trace: &TraceOutput, ) -> Result { verbose_timing_phase!("Write epilogue"); @@ -3971,8 +3991,26 @@ fn write_epilogue>( let build_id_buffer = buffers.get_mut(part_id::NOTE_GNU_BUILD_ID); build_id_buffer.fill(0); - write_compressed_debug_sections(layout, buffers); + for harvested in &epilogue.script_sorted_sections { + let crate::layout::FileLayout::Object(object) = layout.file_layout(harvested.file_id) + else { + continue; + }; + + if let SectionSlot::Loaded(sec) = &object.sections[harvested.section_index.0] { + write_object_section::( + object, + layout, + sec, + harvested.section_index, + buffers, + table_writer, + trace, + )?; + } + } + write_compressed_debug_sections(layout, buffers); Ok(()) } diff --git a/libwild/src/layout.rs b/libwild/src/layout.rs index 2089e1223..05c29c2fd 100644 --- a/libwild/src/layout.rs +++ b/libwild/src/layout.rs @@ -49,6 +49,7 @@ use crate::program_segments::ProgramSegments; use crate::resolution; use crate::resolution::NotLoaded; use crate::resolution::ResolvedGroup; +use crate::resolution::ScriptSortedSectionDetail; use crate::resolution::SectionSlot; use crate::resolution::UnloadedSection; use crate::sharding::ShardKey; @@ -155,12 +156,19 @@ pub fn compute<'data, P: Platform, A: Arch>( let mut dynamic_symbol_definitions = merge_dynamic_symbol_definitions(&group_states, &symbol_db)?; + let script_sorted_sections = harvest_and_sort_script_sections( + &mut group_states, + &output_sections, + &symbol_db.section_part_ids, + ); + let num_sorted_sections = script_sorted_sections.len(); group_states.push(GroupState { files: vec![FileLayoutState::Epilogue(EpilogueLayoutState::new( symbol_db.args, symbol_db.output_kind, &mut dynamic_symbol_definitions, + script_sorted_sections, ))], queue: LocalWorkQueue::new(epilogue_file_id.group()), common: CommonGroupState::new(&output_sections), @@ -298,6 +306,17 @@ pub fn compute<'data, P: Platform, A: Arch>( &merged_strings, ); + // At this stage, sections marked for sorting have been harvested but lack concrete memory + // addresses. We perform a two-step finalization: + // Sort the harvested sections according to the requested criteria. + // Linearize them in memory, starting from the base offset of their respective output section + // part, and advancing by the size of each section. + let harvested_sections_registry = assign_addresses_to_sorted_sections( + &mut group_states, + &starting_mem_offsets_by_group, + num_sorted_sections, + ); + let mut symbol_resolutions = SymbolResolutions { resolutions: Vec::with_capacity(symbol_db.num_symbols()), }; @@ -316,6 +335,7 @@ pub fn compute<'data, P: Platform, A: Arch>( let resources = FinaliseLayoutResources { symbol_db: &symbol_db, output_sections: &output_sections, + harvested_sections_registry: &harvested_sections_registry, output_order: &output_order, section_layouts: §ion_layouts, merged_string_start_addresses: &merged_string_start_addresses, @@ -756,6 +776,7 @@ pub(crate) struct SyntheticSymbolsLayoutState<'data, P: Platform> { pub(crate) struct EpilogueLayoutState { format_specific: P::EpilogueLayoutExt, + pub(crate) script_sorted_sections: Vec, } #[derive(Debug)] @@ -775,6 +796,7 @@ pub(crate) struct SyntheticSymbolsLayout<'data, P: Platform> { pub(crate) struct EpilogueLayout { pub(crate) format_specific: P::EpilogueLayoutExt, pub(crate) dynsym_start_index: u32, + pub(crate) script_sorted_sections: Vec, } #[derive(Debug)] @@ -1192,6 +1214,8 @@ pub(crate) struct ObjectLayoutState<'data, P: Platform> { /// and later transferred to `ObjectLayout`. section_relax_deltas: RelaxDeltaMap, + pub(crate) script_sorted_sections: Vec, + /// Which ThunkBlock handles primary-part thunks for this object. pub(crate) thunk_block_id: ThunkBlockId, @@ -1319,6 +1343,7 @@ pub(crate) struct FinaliseLayoutResources<'scope, 'data, P: Platform> { output_sections: &'scope OutputSections<'data, P>, output_order: &'scope OutputOrder, pub(crate) section_layouts: &'scope OutputSectionMap, + pub(crate) harvested_sections_registry: &'scope [HarvestedSortedSection], merged_string_start_addresses: &'scope MergedStringStartAddresses, merged_strings: &'scope OutputSectionMap>, dynamic_symbol_definitions: &'scope Vec>, @@ -3579,9 +3604,11 @@ impl<'data, P: Platform> EpilogueLayoutState

{ args: &P::Args, output_kind: OutputKind, dynamic_symbol_definitions: &mut [DynamicSymbolDefinition

], + script_sorted_sections: Vec, ) -> Self { EpilogueLayoutState { format_specific: P::new_epilogue_layout(args, output_kind, dynamic_symbol_definitions), + script_sorted_sections, } } @@ -3592,6 +3619,9 @@ impl<'data, P: Platform> EpilogueLayoutState

{ resources: &FinaliseSizesResources<'data, '_, P>, ) -> Result { let mut extra_sizes = OutputSectionPartMap::with_size(common.mem_sizes.num_parts()); + for sec in &self.script_sorted_sections { + extra_sizes.increment(sec.part_id, sec.size); + } P::apply_late_size_adjustments_epilogue( &mut self.format_specific, total_sizes, @@ -3645,10 +3675,16 @@ impl<'data, P: Platform> EpilogueLayoutState

{ dynsym_start_index, resources.dynamic_symbol_definitions, )?; - + for sec in &mut self.script_sorted_sections { + let offset = memory_offsets.get_mut(sec.part_id); + *offset = sec.alignment.align_up(*offset); + sec.mem_offset = *offset; + *offset += sec.size; + } Ok(EpilogueLayout { format_specific: self.format_specific, dynsym_start_index, + script_sorted_sections: self.script_sorted_sections, }) } } @@ -3678,6 +3714,7 @@ fn new_object_layout_state( relocations: input_state.relocations, format_specific: Default::default(), section_relax_deltas: RelaxDeltaMap::new(), + script_sorted_sections: input_state.script_sorted_sections, thunk_block_id: ThunkBlockId::default(), owns_thunk_block: false, post_gc_primary_bytes: 0, @@ -3977,22 +4014,36 @@ impl<'data, P: Platform> ObjectLayoutState<'data, P> { let section_id_range = self.section_id_range; let object_part_ids = &resources.symbol_db.section_part_ids[section_id_range.as_usize()]; - for (slot, &part_id) in self.sections.iter_mut().zip(object_part_ids) { + for (sec_idx, (slot, &part_id)) in self.sections.iter_mut().zip(object_part_ids).enumerate() + { let resolution = match slot { SectionSlot::Loaded(sec) => { - let address = *memory_offsets.get(part_id); + let mut address = *memory_offsets.get(part_id); + // TODO: We probably need to be able to handle sections that are ifuncs and // sections that need a TLS GOT struct. *memory_offsets.get_mut(part_id) += sec.capacity(part_id, resources.output_sections); + // Collect SFrame section ranges while we're already iterating if part_id.output_section_id() == output_section_id::SFRAME { let offset = (address - sframe_start_address) as usize; let len = sec.size as usize; sframe_ranges.push(offset..offset + len); } + + if let Ok(idx) = resources + .harvested_sections_registry + .binary_search_by_key(&(self.file_id, sec_idx), |s| { + (s.file_id, s.section_index.0) + }) + { + address = resources.harvested_sections_registry[idx].mem_offset; + } + SectionResolution { address } } + &mut SectionSlot::LoadedDebugInfo(sec) => { let address = *memory_offsets.get(part_id); *memory_offsets.get_mut(part_id) += @@ -5408,3 +5459,91 @@ impl OutputRecordLayout { impl<'data, P: Platform> Drop for Layout<'data, P> { fn drop(&mut self) {} } + +#[derive(Clone, Debug)] +pub(crate) struct HarvestedSortedSection { + pub(crate) file_id: FileId, + pub(crate) section_index: object::SectionIndex, + pub(crate) part_id: PartId, + pub(crate) size: u64, + pub(crate) alignment: Alignment, + pub(crate) mem_offset: u64, +} + +fn harvest_and_sort_script_sections<'data, P: Platform>( + group_states: &mut [GroupState<'data, P>], + output_sections: &OutputSections

, + section_part_ids: &[PartId], +) -> Vec { + timing_phase!("Harvest and sort script sections"); + + let has_any_sorting = group_states.iter().any(|g| { + g.files.iter().any(|f| { + if let FileLayoutState::Object(obj) = f { + !obj.script_sorted_sections.is_empty() + } else { + false + } + }) + }); + + if !has_any_sorting { + return Vec::new(); + } + + let mut temp = Vec::new(); + for group in group_states.iter_mut() { + for file in &mut group.files { + if let FileLayoutState::Object(obj) = file { + for section_req in &obj.script_sorted_sections { + if let SectionSlot::Loaded(sec) = &obj.sections[section_req.index.0] { + let part_id = obj.section_part_id(section_req.index, section_part_ids); + let capacity = sec.capacity(part_id, output_sections); + temp.push(( + obj.object + .section_name(section_req.index) + .unwrap_or_default(), + HarvestedSortedSection { + file_id: obj.file_id, + section_index: section_req.index, + part_id, + size: capacity, + alignment: part_id.alignment(output_sections), + mem_offset: 0, + }, + )); + } + } + } + } + } + + temp.sort_by_key(|a| a.0); + temp.into_iter().map(|(_, harvested)| harvested).collect() +} +// Assigning memory addresses to script sorted sections and returning the finalized registry. +fn assign_addresses_to_sorted_sections( + group_states: &mut [GroupState

], + starting_mem_offsets_by_group: &[OutputSectionPartMap], + num_sorted_sections: usize, +) -> Vec { + let mut harvested_sections_registry = Vec::with_capacity(num_sorted_sections); + let mut epilogue_offsets = starting_mem_offsets_by_group.last().unwrap().clone(); + + if let FileLayoutState::Epilogue(epilogue_state) = + &mut group_states.last_mut().unwrap().files[0] + { + for sec in &mut epilogue_state.script_sorted_sections { + let offset = epilogue_offsets.get_mut(sec.part_id); + *offset = sec.alignment.align_up(*offset); + sec.mem_offset = *offset; + *offset += sec.size; + + harvested_sections_registry.push(sec.clone()); + } + } + + harvested_sections_registry.sort_unstable_by_key(|s| (s.file_id, s.section_index.0)); + + harvested_sections_registry +} diff --git a/libwild/src/layout_rules.rs b/libwild/src/layout_rules.rs index 4e3ae6a92..d6402f4a3 100644 --- a/libwild/src/layout_rules.rs +++ b/libwild/src/layout_rules.rs @@ -106,6 +106,7 @@ pub(crate) enum SectionRuleOutcome { Debug, RiscVAttribute, SortedSection(SectionOutputInfo), + ScriptSortedSection(SectionOutputInfo), } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -201,7 +202,7 @@ impl<'data> LayoutRulesBuilder<'data> { ContentsCommand::Matcher(matcher) => { for pattern in &matcher.input_section_name_patterns { self.add_section_rule(SectionRule::new( - pattern, + pattern.name, matcher.input_file_pattern, crate::layout_rules::SectionRuleOutcome::Discard, )?); @@ -216,6 +217,7 @@ impl<'data> LayoutRulesBuilder<'data> { .unwrap_or(alignment::MIN) .max(replace(&mut extra_min_alignment, alignment::MIN)); + // Choose starting location for this output section. let section_location = match &sec.start_address_expression { Some(Expression::Number(address)) => { location.take(); @@ -252,15 +254,25 @@ impl<'data> LayoutRulesBuilder<'data> { }; for pattern in &matcher.input_section_name_patterns { + let output_info = SectionOutputInfo { + section_id, + must_keep: matcher.must_keep, + }; + + // If the script requested sorting, tag it for the + // global Harvester instead of standard placement. + let outcome = if pattern.sorted { + SectionRuleOutcome::ScriptSortedSection(output_info) + } else { + crate::layout_rules::SectionRuleOutcome::Section( + output_info, + ) + }; + self.add_section_rule(SectionRule::new( - pattern, + pattern.name, matcher.input_file_pattern, - crate::layout_rules::SectionRuleOutcome::Section( - SectionOutputInfo { - section_id, - must_keep: matcher.must_keep, - }, - ), + outcome, )?); } @@ -460,10 +472,11 @@ impl<'data> SectionRule<'data> { name: &'data [u8], section_id: OutputSectionId, ) -> SectionRule<'data> { - Self::prefix( - name, - SectionRuleOutcome::SortedSection(SectionOutputInfo::keep(section_id)), - ) + SectionRule { + name_matcher: SectionNameMatcher::Prefix(name), + input_file_pattern: None, + outcome: SectionRuleOutcome::SortedSection(SectionOutputInfo::keep(section_id)), + } } pub(crate) const fn exact( diff --git a/libwild/src/linker_script.rs b/libwild/src/linker_script.rs index de846615f..c91e0fdea 100644 --- a/libwild/src/linker_script.rs +++ b/libwild/src/linker_script.rs @@ -214,15 +214,19 @@ pub(crate) enum Expression<'a> { Negate(Box>), } +#[derive(Debug, PartialEq, Eq, Clone)] +pub(crate) struct SectionPattern<'a> { + pub(crate) name: &'a [u8], + pub(crate) sorted: bool, +} + #[derive(Debug, PartialEq, Eq)] pub(crate) struct Matcher<'a> { pub(crate) must_keep: bool, - /// Optional glob pattern for matching input filenames. `None` means match all files (i.e. the /// `*` wildcard was used, or no filename was specified). pub(crate) input_file_pattern: Option<&'a [u8]>, - - pub(crate) input_section_name_patterns: Vec<&'a [u8]>, + pub(crate) input_section_name_patterns: Vec>, } impl<'a> Expression<'a> { @@ -1111,10 +1115,32 @@ fn parse_matcher_pattern<'input>(input: &mut &'input BStr) -> winnow::Result(input: &mut &'input BStr) -> winnow::Result<&'input [u8]> { - let pattern = take_while(1.., |b| !b" \n\t)".contains(&b)).parse_next(input)?; - skip_comments_and_whitespace(input)?; - Ok(pattern) +fn parse_pattern<'input>(input: &mut &'input BStr) -> winnow::Result> { + // Handling SORT(...) wrapper: SORT is an alias for SORT_BY_NAME in GNU ld. + use winnow::combinator::opt; + use winnow::token::literal; + if opt(literal(b"SORT")).parse_next(input)?.is_some() { + skip_comments_and_whitespace(input)?; + + '('.parse_next(input)?; + skip_comments_and_whitespace(input)?; + + // Inside SORT(...), accept a single wildcard pattern up to ')' + let name = take_while(1.., |b| !b" \n\t)".contains(&b)).parse_next(input)?; + skip_comments_and_whitespace(input)?; + // Closing ')' + ')'.parse_next(input)?; + skip_comments_and_whitespace(input)?; + + Ok(SectionPattern { name, sorted: true }) + } else { + let name = take_while(1.., |b| !b" \n\t)".contains(&b)).parse_next(input)?; + skip_comments_and_whitespace(input)?; + Ok(SectionPattern { + name, + sorted: false, + }) + } } /// Call `cb` for each input file requested by `commands`. @@ -1320,12 +1346,24 @@ mod tests { ContentsCommand::Matcher(Matcher { must_keep: false, input_file_pattern: None, - input_section_name_patterns: vec![b".text", b".text2"], + input_section_name_patterns: vec![ + SectionPattern { + name: b".text", + sorted: false, + }, + SectionPattern { + name: b".text2", + sorted: false, + }, + ], }), ContentsCommand::Matcher(Matcher { must_keep: false, input_file_pattern: None, - input_section_name_patterns: vec![b".text3"], + input_section_name_patterns: vec![SectionPattern { + name: b".text3", + sorted: false, + }], }), ], alignment: None, @@ -1343,7 +1381,10 @@ mod tests { commands: vec![ContentsCommand::Matcher(Matcher { must_keep: false, input_file_pattern: None, - input_section_name_patterns: vec![b"___ksymtab+"], + input_section_name_patterns: vec![SectionPattern { + name: b"___ksymtab+", + sorted: false, + }], })], alignment: Some(Alignment::new(8).unwrap()), start_address_expression: Some(Expression::Number(0)), @@ -1390,7 +1431,10 @@ mod tests { ContentsCommand::Matcher(Matcher { must_keep: true, input_file_pattern: None, - input_section_name_patterns: vec![b".rodata.foo"], + input_section_name_patterns: vec![SectionPattern { + name: b".rodata.foo", + sorted: false, + }], }), ContentsCommand::Align(Alignment::new(32).unwrap()), ContentsCommand::SymbolAssignment(SymbolAssignment { @@ -1526,12 +1570,24 @@ mod tests { ContentsCommand::Matcher(Matcher { must_keep: false, input_file_pattern: Some(b"foo.o"), - input_section_name_patterns: vec![b".text", b".text2"], + input_section_name_patterns: vec![ + SectionPattern { + name: b".text", + sorted: false, + }, + SectionPattern { + name: b".text2", + sorted: false, + }, + ], }), ContentsCommand::Matcher(Matcher { must_keep: false, input_file_pattern: None, - input_section_name_patterns: vec![b".text3"], + input_section_name_patterns: vec![SectionPattern { + name: b".text3", + sorted: false, + }], }), ], alignment: None, @@ -1549,7 +1605,10 @@ mod tests { commands: vec![ContentsCommand::Matcher(Matcher { must_keep: false, input_file_pattern: Some(b"*crtbegin*.o"), - input_section_name_patterns: vec![b".ctors"], + input_section_name_patterns: vec![SectionPattern { + name: b".ctors", + sorted: false, + }], })], alignment: None, start_address_expression: None, @@ -1566,7 +1625,10 @@ mod tests { commands: vec![ContentsCommand::Matcher(Matcher { must_keep: true, input_file_pattern: Some(b"crti.o"), - input_section_name_patterns: vec![b".init"], + input_section_name_patterns: vec![SectionPattern { + name: b".init", + sorted: false, + }], })], alignment: None, start_address_expression: None, @@ -1591,7 +1653,10 @@ mod tests { commands: vec![ContentsCommand::Matcher(Matcher { must_keep: false, input_file_pattern: None, - input_section_name_patterns: vec![b".text"], + input_section_name_patterns: vec![SectionPattern { + name: b".text", + sorted: false, + }], })], alignment: None, start_address_expression: None, @@ -1627,7 +1692,10 @@ mod tests { commands: vec![ContentsCommand::Matcher(Matcher { must_keep: false, input_file_pattern: None, - input_section_name_patterns: vec![b".text"], + input_section_name_patterns: vec![SectionPattern { + name: b".text", + sorted: false, + }], })], alignment: None, start_address_expression: None, diff --git a/libwild/src/resolution.rs b/libwild/src/resolution.rs index 55eae95d0..6ea7a7cb1 100644 --- a/libwild/src/resolution.rs +++ b/libwild/src/resolution.rs @@ -749,6 +749,10 @@ pub(crate) struct ResolvedCommon<'data, P: Platform> { pub(crate) file_id: FileId, pub(crate) symbol_id_range: SymbolIdRange, } +#[derive(Debug, Clone)] +pub(crate) struct ScriptSortedSectionDetail { + pub(crate) index: object::SectionIndex, +} #[derive(Debug)] pub(crate) struct ResolvedObject<'data, P: Platform> { @@ -764,6 +768,8 @@ pub(crate) struct ResolvedObject<'data, P: Platform> { custom_sections: Vec>, init_fini_sections: Vec, + //// same as layout file + pub(crate) script_sorted_sections: Vec, /// Total size in bytes of all executable input sections in this object. Used to determine /// early-on if we can be sure that thunks won't be needed. @@ -1217,6 +1223,7 @@ impl<'data, P: Platform> ResolvedObject<'data, P> { string_merge_extras: Default::default(), custom_sections: Default::default(), init_fini_sections: Default::default(), + script_sorted_sections: Default::default(), executable_bytes: 0, } } @@ -1349,6 +1356,22 @@ fn resolve_section<'data, P: Platform>( unloaded_section = UnloadedSection::new(); } + SectionRuleOutcome::ScriptSortedSection(output_info) => { + part_id = if output_info.section_id.is_regular() { + output_info.section_id.part_id_with_alignment(alignment) + } else { + output_info.section_id.base_part_id() + }; + + obj.script_sorted_sections.push(ScriptSortedSectionDetail { + index: input_section_index, + }); + + must_load |= output_info.must_keep; + + unloaded_section = UnloadedSection::new(); + } + SectionRuleOutcome::Discard => return Ok((SectionSlot::Discard, part_id::UNMAPPED)), SectionRuleOutcome::NoteGnuStack => { P::validate_stack_section(input_section, obj, args)?; diff --git a/wild/tests/sources/elf/script-sort/script-sort-2.c b/wild/tests/sources/elf/script-sort/script-sort-2.c new file mode 100644 index 000000000..528e84389 --- /dev/null +++ b/wild/tests/sources/elf/script-sort/script-sort-2.c @@ -0,0 +1,4 @@ +__attribute__((used, section(".text.sort.b"))) int func_b() { return 2; } + +__attribute__((section(".text.kept.func"))) int func_kept() { return 4; } +__attribute__((section(".text.drop.func"))) int func_drop() { return 5; } diff --git a/wild/tests/sources/elf/script-sort/script-sort-3.c b/wild/tests/sources/elf/script-sort/script-sort-3.c new file mode 100644 index 000000000..7f48e2414 --- /dev/null +++ b/wild/tests/sources/elf/script-sort/script-sort-3.c @@ -0,0 +1,2 @@ +__attribute__((used, section(".text.sort.a"))) int func_a() { return 1; } +__attribute__((used, section(".text.sort.c"))) int func_c() { return 3; } diff --git a/wild/tests/sources/elf/script-sort/script-sort.c b/wild/tests/sources/elf/script-sort/script-sort.c new file mode 100644 index 000000000..fb5eb9d0c --- /dev/null +++ b/wild/tests/sources/elf/script-sort/script-sort.c @@ -0,0 +1,31 @@ +//#LinkArgs: -T tests/sources/elf/script-sort/script-sort.ld +//#Object:runtime.c +//#Object:script-sort-2.c +//#Object:script-sort-3.c + +#include "../common/runtime.h" + +extern int func_a(); +extern int func_b(); +extern int func_c(); + +void _start(void) { + runtime_init(); + + unsigned long a_addr = (unsigned long)&func_a; + unsigned long b_addr = (unsigned long)&func_b; + unsigned long c_addr = (unsigned long)&func_c; + + if (a_addr >= b_addr) { + exit_syscall(101); + } + if (b_addr >= c_addr) { + exit_syscall(102); + } + + if (func_a() != 1 || func_b() != 2 || func_c() != 3) { + exit_syscall(103); + } + + exit_syscall(42); +} diff --git a/wild/tests/sources/elf/script-sort/script-sort.ld b/wild/tests/sources/elf/script-sort/script-sort.ld new file mode 100644 index 000000000..55f6fc02f --- /dev/null +++ b/wild/tests/sources/elf/script-sort/script-sort.ld @@ -0,0 +1,21 @@ +ENTRY(_start) + +SECTIONS +{ + . = 0x401000; + + .text : { + *(.text) + + /* Test 1: Global sorting across files using standard ELF naming */ + *(SORT(.text.sort.*)) + + /* Test 2: SORT with KEEP */ + KEEP(*(SORT(.text.kept.*))) + + /* Test 3: SORT without KEEP */ + *(SORT(.text.drop.*)) + } + + PROVIDE(__global_pointer$ = .); +}