1- #![ allow( clippy:: missing_safety_doc) ]
2- #![ allow( clippy:: too_many_arguments) ]
3-
41use std:: collections:: { HashMap , HashSet } ;
52use std:: ffi:: { CStr , CString } ;
63use std:: fs:: { self , OpenOptions } ;
@@ -40,6 +37,7 @@ const SHT_PROGBITS: u32 = 1;
4037const SHT_DYNAMIC : u32 = 6 ;
4138const SHT_DYNSYM : u32 = 11 ;
4239const STT_TLS : u8 = 6 ;
40+ const STB_GLOBAL : u8 = 1 ;
4341const PT_LOAD : u32 = 1 ;
4442const PT_DYNAMIC : u32 = 2 ;
4543const PT_INTERP : u32 = 3 ;
@@ -223,6 +221,7 @@ struct Aarch64TlsSyntheticSlot {
223221 canonical_object_index : u32 ,
224222 canonical_symbol_index : u32 ,
225223 slot_index : u32 ,
224+ reloc_addend : u64 ,
226225}
227226
228227#[ derive( Clone , Default ) ]
@@ -1174,6 +1173,7 @@ fn aarch64_tls_synth_reserve_slot(
11741173 canonical_object_index,
11751174 canonical_symbol_index,
11761175 slot_index,
1176+ reloc_addend : 0 ,
11771177 } ) ;
11781178 Ok ( slot_index)
11791179}
@@ -1186,6 +1186,23 @@ fn aarch64_tls_synth_slot_address_inner(state: &mut LinkerState, slot_index: u32
11861186 Ok ( aarch64_tls_synth_slot_address ( base, slot_index) )
11871187}
11881188
1189+ fn aarch64_tls_synth_record_addend ( state : & mut LinkerState , slot_index : u32 , addend : u64 ) -> Result < ( ) , u32 > {
1190+ let slot = state
1191+ . aarch64_tls_synth_slots
1192+ . get_mut ( slot_index as usize )
1193+ . ok_or ( ERR_INVALID_RELOCATION ) ?;
1194+ if slot. reloc_addend == 0 {
1195+ slot. reloc_addend = addend;
1196+ return Ok ( ( ) ) ;
1197+ }
1198+ if slot. reloc_addend != addend {
1199+ // Current staged synthetic slot model coalesces by symbol/model and does not allocate
1200+ // distinct slots for per-site addend variants yet.
1201+ return Err ( ERR_NOT_IMPLEMENTED_YET ) ;
1202+ }
1203+ Ok ( ( ) )
1204+ }
1205+
11891206fn aarch64_page_delta_bytes_host ( target_addr : u64 , place_addr : u64 ) -> u64 {
11901207 let page_mask = !0xfffu64 ;
11911208 let target_page = target_addr & page_mask;
@@ -1214,6 +1231,7 @@ fn aarch64_tls_synth_reloc_value(
12141231 place_addr : u64 ,
12151232) -> Result < u64 , u32 > {
12161233 let slot_index = aarch64_tls_synth_reserve_slot ( state, object_index, symbol_index, reloc_type) ?;
1234+ aarch64_tls_synth_record_addend ( state, slot_index, addend) ?;
12171235 let slot_addr = aarch64_tls_synth_slot_address_inner ( state, slot_index) ?;
12181236 let target = slot_addr. wrapping_add ( addend) ;
12191237 match reloc_type {
@@ -1458,27 +1476,103 @@ fn elf64_r_info(symbol_index: u32, reloc_type: u32) -> u64 {
14581476 ( ( symbol_index as u64 ) << 32 ) | ( reloc_type as u64 )
14591477}
14601478
1461- fn build_aarch64_tls_synth_rela_dyn ( state : & LinkerState ) -> Vec < Elf64RelaRecord > {
1479+ fn append_elf64_sym (
1480+ dynsym : & mut Vec < u8 > ,
1481+ st_name : u32 ,
1482+ st_info : u8 ,
1483+ st_other : u8 ,
1484+ st_shndx : u16 ,
1485+ st_value : u64 ,
1486+ st_size : u64 ,
1487+ ) {
1488+ let base = dynsym. len ( ) ;
1489+ dynsym. resize ( base. saturating_add ( 24 ) , 0 ) ;
1490+ write_u32_le ( dynsym, base, st_name) ;
1491+ if base + 4 < dynsym. len ( ) {
1492+ dynsym[ base + 4 ] = st_info;
1493+ }
1494+ if base + 5 < dynsym. len ( ) {
1495+ dynsym[ base + 5 ] = st_other;
1496+ }
1497+ write_u16_le ( dynsym, base + 6 , st_shndx) ;
1498+ write_u64_le ( dynsym, base + 8 , st_value) ;
1499+ write_u64_le ( dynsym, base + 16 , st_size) ;
1500+ }
1501+
1502+ fn aarch64_tls_synth_symbol_name ( slot : & Aarch64TlsSyntheticSlot ) -> String {
1503+ let model_tag = match slot. model {
1504+ AARCH64_TLS_SYNTH_MODEL_TLSGD => "gd" ,
1505+ AARCH64_TLS_SYNTH_MODEL_TLSLD => "ld" ,
1506+ AARCH64_TLS_SYNTH_MODEL_TLSDESC => "desc" ,
1507+ _ => "tls" ,
1508+ } ;
1509+ format ! (
1510+ "__dust_tls_{}_h{:016x}_o{}_s{}" ,
1511+ model_tag,
1512+ slot. canonical_name_hash,
1513+ slot. canonical_object_index,
1514+ slot. canonical_symbol_index
1515+ )
1516+ }
1517+
1518+ fn build_aarch64_tls_synth_dynsym_and_rela_dyn (
1519+ state : & LinkerState ,
1520+ dynstr : & mut Vec < u8 > ,
1521+ ) -> ( Vec < u8 > , Vec < Elf64RelaRecord > ) {
14621522 let base = match aarch64_tls_synth_region_base_from_state ( state) {
14631523 Some ( v) => v,
1464- None => return Vec :: new ( ) ,
1524+ None => return ( Vec :: new ( ) , Vec :: new ( ) ) ,
14651525 } ;
1526+ let mut dynsym = vec ! [ 0u8 ; 24 ] ; // null dynsym entry
14661527 let mut out = Vec :: new ( ) ;
1528+ let mut sym_index_by_key: HashMap < ( u64 , u32 , u32 ) , u32 > = HashMap :: new ( ) ;
1529+ let mut slot_sym_index: HashMap < u32 , u32 > = HashMap :: new ( ) ;
1530+
1531+ for slot in & state. aarch64_tls_synth_slots {
1532+ if slot. model == AARCH64_TLS_SYNTH_MODEL_TLSLD {
1533+ continue ;
1534+ }
1535+ let key = (
1536+ slot. canonical_name_hash ,
1537+ slot. canonical_object_index ,
1538+ slot. canonical_symbol_index ,
1539+ ) ;
1540+ if let Some ( existing) = sym_index_by_key. get ( & key) {
1541+ slot_sym_index. insert ( slot. slot_index , * existing) ;
1542+ continue ;
1543+ }
1544+ let name = aarch64_tls_synth_symbol_name ( slot) ;
1545+ let st_name = dynstr. len ( ) as u32 ;
1546+ dynstr. extend_from_slice ( name. as_bytes ( ) ) ;
1547+ dynstr. push ( 0 ) ;
1548+ let sym_index = ( dynsym. len ( ) / 24 ) as u32 ;
1549+ append_elf64_sym (
1550+ & mut dynsym,
1551+ st_name,
1552+ ( ( STB_GLOBAL & 0x0f ) << 4 ) | ( STT_TLS & 0x0f ) ,
1553+ 0 ,
1554+ SHN_UNDEF ,
1555+ 0 ,
1556+ 0 ,
1557+ ) ;
1558+ sym_index_by_key. insert ( key, sym_index) ;
1559+ slot_sym_index. insert ( slot. slot_index , sym_index) ;
1560+ }
1561+
14671562 for slot in & state. aarch64_tls_synth_slots {
14681563 let slot_addr = aarch64_tls_synth_slot_address ( base, slot. slot_index ) ;
14691564 match slot. model {
14701565 AARCH64_TLS_SYNTH_MODEL_TLSGD => {
1471- // Provisional TLSGD GOT pair: module id + dtprel entry. Symbol index remains 0
1472- // until dynsym symbol emission is implemented for input .o TLS symbols.
1566+ let sym_index = * slot_sym_index. get ( & slot. slot_index ) . unwrap_or ( & 0 ) ;
14731567 out. push ( Elf64RelaRecord {
14741568 offset : slot_addr,
1475- info : elf64_r_info ( 0 , R_AARCH64_TLS_DTPMOD ) ,
1569+ info : elf64_r_info ( sym_index , R_AARCH64_TLS_DTPMOD ) ,
14761570 addend : 0 ,
14771571 } ) ;
14781572 out. push ( Elf64RelaRecord {
14791573 offset : slot_addr. saturating_add ( 8 ) ,
1480- info : elf64_r_info ( 0 , R_AARCH64_TLS_DTPREL ) ,
1481- addend : 0 ,
1574+ info : elf64_r_info ( sym_index , R_AARCH64_TLS_DTPREL ) ,
1575+ addend : slot . reloc_addend ,
14821576 } ) ;
14831577 }
14841578 AARCH64_TLS_SYNTH_MODEL_TLSLD => {
@@ -1490,16 +1584,17 @@ fn build_aarch64_tls_synth_rela_dyn(state: &LinkerState) -> Vec<Elf64RelaRecord>
14901584 } ) ;
14911585 }
14921586 AARCH64_TLS_SYNTH_MODEL_TLSDESC => {
1587+ let sym_index = * slot_sym_index. get ( & slot. slot_index ) . unwrap_or ( & 0 ) ;
14931588 out. push ( Elf64RelaRecord {
14941589 offset : slot_addr,
1495- info : elf64_r_info ( 0 , R_AARCH64_TLSDESC ) ,
1496- addend : 0 ,
1590+ info : elf64_r_info ( sym_index , R_AARCH64_TLSDESC ) ,
1591+ addend : slot . reloc_addend ,
14971592 } ) ;
14981593 }
14991594 _ => { }
15001595 }
15011596 }
1502- out
1597+ ( dynsym , out)
15031598}
15041599
15051600#[ derive( Clone ) ]
@@ -1658,16 +1753,13 @@ fn write_minimal_elf_exec(
16581753 || runpath_offset. is_some ( )
16591754 || z_now;
16601755
1661- let dynsym = if dynamic_enabled {
1662- // Minimal dynsym (null symbol). This keeps DT_SYMTAB/DT_SYMENT structurally valid while
1663- // synthetic TLS descriptor/GOT relocs are materialized. Full TLS dynsym binding/name
1664- // parity is tracked separately.
1665- vec ! [ 0u8 ; 24 ]
1666- } else {
1667- Vec :: new ( )
1668- } ;
1756+ let mut dynsym = if dynamic_enabled { vec ! [ 0u8 ; 24 ] } else { Vec :: new ( ) } ;
16691757 let tls_synth_rela_records = if dynamic_enabled && machine == EM_AARCH64 {
1670- build_aarch64_tls_synth_rela_dyn ( state)
1758+ let ( dynsym_tls, rela_tls) = build_aarch64_tls_synth_dynsym_and_rela_dyn ( state, & mut dynstr) ;
1759+ if !dynsym_tls. is_empty ( ) {
1760+ dynsym = dynsym_tls;
1761+ }
1762+ rela_tls
16711763 } else {
16721764 Vec :: new ( )
16731765 } ;
@@ -7677,3 +7769,156 @@ pub extern "C" fn host_linker_strip_debug() -> u32 {
76777769 state. strip_debug = true ;
76787770 set_last_error ( & mut state, ERR_OK )
76797771}
7772+
7773+ #[ cfg( test) ]
7774+ mod tests {
7775+ use super :: * ;
7776+
7777+ fn dynamic_tag_map ( raw : & [ u8 ] ) -> HashMap < u64 , u64 > {
7778+ let phoff = read_u64_le_at ( raw, 32 ) . unwrap_or ( 0 ) as usize ;
7779+ let phentsize = read_u16_le_at ( raw, 54 ) . unwrap_or ( 0 ) as usize ;
7780+ let phnum = read_u16_le_at ( raw, 56 ) . unwrap_or ( 0 ) as usize ;
7781+ let mut dynamic_off = 0usize ;
7782+ let mut dynamic_size = 0usize ;
7783+ let mut load_off = 0usize ;
7784+ let mut load_vaddr = 0u64 ;
7785+ for i in 0 ..phnum {
7786+ let off = phoff + i. saturating_mul ( phentsize) ;
7787+ let p_type = read_u32_le_at ( raw, off) . unwrap_or ( 0 ) ;
7788+ if p_type == PT_DYNAMIC {
7789+ dynamic_off = read_u64_le_at ( raw, off + 8 ) . unwrap_or ( 0 ) as usize ;
7790+ dynamic_size = read_u64_le_at ( raw, off + 32 ) . unwrap_or ( 0 ) as usize ;
7791+ }
7792+ if p_type == PT_LOAD {
7793+ load_off = read_u64_le_at ( raw, off + 8 ) . unwrap_or ( 0 ) as usize ;
7794+ load_vaddr = read_u64_le_at ( raw, off + 16 ) . unwrap_or ( 0 ) ;
7795+ }
7796+ }
7797+ let mut tags = HashMap :: new ( ) ;
7798+ let mut at = dynamic_off;
7799+ let end = dynamic_off. saturating_add ( dynamic_size) ;
7800+ while at + 16 <= end && at + 16 <= raw. len ( ) {
7801+ let tag = read_u64_le_at ( raw, at) . unwrap_or ( DT_NULL ) ;
7802+ let value = read_u64_le_at ( raw, at + 8 ) . unwrap_or ( 0 ) ;
7803+ tags. insert ( tag, value) ;
7804+ at += 16 ;
7805+ if tag == DT_NULL {
7806+ break ;
7807+ }
7808+ }
7809+ // Stash PT_LOAD mapping in high sentinel tags for helper-free callers.
7810+ tags. insert ( 0xffff_ffff_ffff_ff01 , load_off as u64 ) ;
7811+ tags. insert ( 0xffff_ffff_ffff_ff02 , load_vaddr) ;
7812+ tags
7813+ }
7814+
7815+ fn vaddr_to_file_off ( tags : & HashMap < u64 , u64 > , vaddr : u64 ) -> usize {
7816+ let load_off = * tags. get ( & 0xffff_ffff_ffff_ff01 ) . unwrap_or ( & 0 ) ;
7817+ let load_vaddr = * tags. get ( & 0xffff_ffff_ffff_ff02 ) . unwrap_or ( & 0 ) ;
7818+ if vaddr < load_vaddr {
7819+ return 0 ;
7820+ }
7821+ load_off. saturating_add ( vaddr - load_vaddr) as usize
7822+ }
7823+
7824+ fn cstr_from ( raw : & [ u8 ] , off : usize ) -> String {
7825+ if off >= raw. len ( ) {
7826+ return String :: new ( ) ;
7827+ }
7828+ let rest = & raw [ off..] ;
7829+ let end = rest. iter ( ) . position ( |b| * b == 0 ) . unwrap_or ( rest. len ( ) ) ;
7830+ String :: from_utf8_lossy ( & rest[ ..end] ) . to_string ( )
7831+ }
7832+
7833+ #[ test]
7834+ fn aarch64_tls_synth_elf_emits_symbol_aware_dynsym_and_rela ( ) {
7835+ let mut state = LinkerState :: default ( ) ;
7836+ reset_linker_state_defaults ( & mut state) ;
7837+ state. target = TARGET_AARCH64_LINUX ;
7838+ state. output_format = FORMAT_ELF64 ;
7839+ state. image_base = 0x0040_0000 ;
7840+ state. entry = 0x0040_1000 ;
7841+ state. aarch64_tls_synth_base = align_up (
7842+ state. image_base + AARCH64_TLS_SYNTH_REGION_OFFSET ,
7843+ AARCH64_TLS_SYNTH_SLOT_SIZE ,
7844+ ) ;
7845+ state. aarch64_tls_synth_slots . push ( Aarch64TlsSyntheticSlot {
7846+ model : AARCH64_TLS_SYNTH_MODEL_TLSDESC ,
7847+ canonical_name_hash : 0x1111_2222_3333_4444 ,
7848+ canonical_object_index : 1 ,
7849+ canonical_symbol_index : 7 ,
7850+ slot_index : 0 ,
7851+ reloc_addend : 0x20 ,
7852+ } ) ;
7853+ state. aarch64_tls_synth_slots . push ( Aarch64TlsSyntheticSlot {
7854+ model : AARCH64_TLS_SYNTH_MODEL_TLSGD ,
7855+ canonical_name_hash : 0x5555_6666_7777_8888 ,
7856+ canonical_object_index : 2 ,
7857+ canonical_symbol_index : 9 ,
7858+ slot_index : 1 ,
7859+ reloc_addend : 0x30 ,
7860+ } ) ;
7861+
7862+ let temp = std:: env:: temp_dir ( ) . join ( "dustlink_tls_synth_fixture_aarch64.elf" ) ;
7863+ let _ = fs:: remove_file ( & temp) ;
7864+ let rc = write_elf_output_for_state ( & temp, & state, state. entry , state. image_base ) ;
7865+ assert_eq ! ( rc, ERR_OK ) ;
7866+ let raw = fs:: read ( & temp) . expect ( "read synthetic elf fixture" ) ;
7867+ let _ = fs:: remove_file ( & temp) ;
7868+
7869+ let tags = dynamic_tag_map ( & raw ) ;
7870+ assert ! ( tags. contains_key( & DT_SYMTAB ) ) ;
7871+ assert_eq ! ( tags. get( & DT_SYMENT ) . copied( ) . unwrap_or( 0 ) , 24 ) ;
7872+ assert ! ( tags. contains_key( & DT_RELA ) ) ;
7873+ assert_eq ! ( tags. get( & DT_RELAENT ) . copied( ) . unwrap_or( 0 ) , 24 ) ;
7874+ assert ! ( tags. contains_key( & DT_RELASZ ) ) ;
7875+ assert_eq ! (
7876+ tags. get( & DT_PLTGOT ) . copied( ) . unwrap_or( 0 ) ,
7877+ state. aarch64_tls_synth_base
7878+ ) ;
7879+
7880+ let dynstr_vaddr = tags. get ( & DT_STRTAB ) . copied ( ) . unwrap_or ( 0 ) ;
7881+ let dynsym_vaddr = tags. get ( & DT_SYMTAB ) . copied ( ) . unwrap_or ( 0 ) ;
7882+ let rela_vaddr = tags. get ( & DT_RELA ) . copied ( ) . unwrap_or ( 0 ) ;
7883+ let rela_size = tags. get ( & DT_RELASZ ) . copied ( ) . unwrap_or ( 0 ) as usize ;
7884+ let dynstr_off = vaddr_to_file_off ( & tags, dynstr_vaddr) ;
7885+ let dynsym_off = vaddr_to_file_off ( & tags, dynsym_vaddr) ;
7886+ let rela_off = vaddr_to_file_off ( & tags, rela_vaddr) ;
7887+ assert ! ( rela_off > 0 && rela_size >= 24 ) ;
7888+ assert ! ( rela_off + rela_size <= raw. len( ) ) ;
7889+
7890+ let mut saw_tlsdesc = false ;
7891+ let mut saw_tlsgd_dtprel = false ;
7892+ let mut saw_named_symbol = false ;
7893+ let mut off = rela_off;
7894+ let end = rela_off + rela_size;
7895+ while off + 24 <= end {
7896+ let r_info = read_u64_le_at ( & raw , off + 8 ) . unwrap_or ( 0 ) ;
7897+ let r_addend = read_u64_le_at ( & raw , off + 16 ) . unwrap_or ( 0 ) ;
7898+ let sym_index = ( r_info >> 32 ) as u32 ;
7899+ let r_type = ( r_info & 0xffff_ffff ) as u32 ;
7900+ if r_type == R_AARCH64_TLSDESC {
7901+ saw_tlsdesc = true ;
7902+ assert_ne ! ( sym_index, 0 ) ;
7903+ assert_eq ! ( r_addend, 0x20 ) ;
7904+ }
7905+ if r_type == R_AARCH64_TLS_DTPREL {
7906+ saw_tlsgd_dtprel = true ;
7907+ assert_ne ! ( sym_index, 0 ) ;
7908+ assert_eq ! ( r_addend, 0x30 ) ;
7909+ }
7910+ if sym_index != 0 {
7911+ let sym_off = dynsym_off + ( sym_index as usize ) . saturating_mul ( 24 ) ;
7912+ let st_name = read_u32_le_at ( & raw , sym_off) . unwrap_or ( 0 ) as usize ;
7913+ let name = cstr_from ( & raw , dynstr_off + st_name) ;
7914+ if name. starts_with ( "__dust_tls_" ) {
7915+ saw_named_symbol = true ;
7916+ }
7917+ }
7918+ off += 24 ;
7919+ }
7920+ assert ! ( saw_tlsdesc) ;
7921+ assert ! ( saw_tlsgd_dtprel) ;
7922+ assert ! ( saw_named_symbol) ;
7923+ }
7924+ }
0 commit comments