Skip to main content

ab_contracts_tooling/
recover.rs

1//! Recover RISC-V ELF `cdylib` from Abundance contract file format.
2//!
3//! Conversion of ELF into the contract file is lossy, so the recovered ELF file only contains what
4//! is necessary for it to be a valid and functional shared library that converts back into exactly
5//! the same contract file: read-only data with contract metadata, code, exported methods, host call
6//! function and its import with corresponding PLT and GOT entries.
7//!
8//! The layout resembles what `lld` produces for contracts, except that read-only data starts at a
9//! page boundary since its original alignment is not stored in the contract file.
10
11use ab_contract_file::ContractFile;
12use ab_contract_file::instruction::{ContractInstruction, ContractRegister};
13use ab_contracts_common::{HOST_CALL_FN, HOST_CALL_FN_IMPORT};
14use ab_riscv_primitives::prelude::*;
15use anyhow::Context;
16use object::elf::{
17    DF_1_NOW, DF_BIND_NOW, DF_SYMBOLIC, DT_FLAGS, DT_FLAGS_1, DT_HASH, DT_JMPREL, DT_NULL,
18    DT_PLTGOT, DT_PLTREL, DT_PLTRELSZ, DT_RELA, DT_STRSZ, DT_STRTAB, DT_SYMENT, DT_SYMTAB, Dyn64,
19    EF_RISCV_RVC, ELFOSABI_GNU, EM_RISCV, ET_DYN, PF_R, PF_W, PF_X, PT_DYNAMIC, PT_GNU_STACK,
20    PT_LOAD, PT_PHDR, PT_RISCV_ATTRIBUTES, ProgramFlags, ProgramHeader64, ProgramType,
21    R_RISCV_JUMP_SLOT, Rela64, SHF_ALLOC, SHF_EXECINSTR, SHF_INFO_LINK, SHF_WRITE, SHN_UNDEF,
22    SHT_PROGBITS, SHT_RELA, SHT_RISCV_ATTRIBUTES, STB_GLOBAL, STT_FUNC, STT_NOTYPE, STV_DEFAULT,
23    Sym64, SymbolInfo, Tag_File,
24};
25use object::pod::bytes_of_slice;
26use object::write::StringId;
27use object::write::elf::{
28    AttributesWriter, FileHeader, ProgramHeader, Rel, SectionHeader, SectionIndex, Sym,
29    SymbolIndex, Writer,
30};
31use object::{Endianness, LittleEndian, U64};
32use std::collections::HashSet;
33
34/// Page size used for segment alignment
35const PAGE_SIZE: u64 = 0x1000;
36/// Alignment of 64-bit words, ELF structures and tables containing them
37const WORD_ALIGNMENT: u64 = size_of::<u64>() as u64;
38/// Alignment of instructions with compressed instructions support
39const INSTRUCTION_ALIGNMENT: u64 = size_of::<u16>() as u64;
40const PROGRAM_HEADER_SIZE: u64 = size_of::<ProgramHeader64<LittleEndian>>() as u64;
41const SYMBOL_SIZE: u64 = size_of::<Sym64<LittleEndian>>() as u64;
42const RELA_SIZE: u64 = size_of::<Rela64<LittleEndian>>() as u64;
43const DYNAMIC_ENTRY_SIZE: u64 = size_of::<Dyn64<LittleEndian>>() as u64;
44/// `PT_PHDR`, three `PT_LOAD`, `PT_DYNAMIC`, `PT_GNU_STACK` and `PT_RISCV_ATTRIBUTES`
45const NUM_PROGRAM_HEADERS: usize = 7;
46/// `DT_FLAGS`, `DT_FLAGS_1`, `DT_SYMTAB`, `DT_SYMENT`, `DT_STRTAB`, `DT_STRSZ`, `DT_HASH` and
47/// `DT_NULL`
48const NUM_DYNAMIC_ENTRIES: usize = 8;
49/// `DT_JMPREL`, `DT_PLTRELSZ`, `DT_PLTGOT` and `DT_PLTREL`
50const NUM_PLT_DYNAMIC_ENTRIES: usize = 4;
51/// Size of the PLT header, which is followed by the only PLT entry for the host call function
52/// import
53const PLT_HEADER_SIZE: u64 = 32;
54const PLT_ENTRY_SIZE: u64 = 16;
55const PLT_SIZE: u64 = PLT_HEADER_SIZE + PLT_ENTRY_SIZE;
56/// Two reserved entries for the dynamic linker followed by a single entry for the host call
57/// function import
58const GOT_PLT_ENTRIES: usize = 3;
59const GOT_PLT_SIZE: u64 = (GOT_PLT_ENTRIES * size_of::<u64>()) as u64;
60/// Offset of the host call function import entry relative to the beginning of `.got.plt`
61const GOT_PLT_IMPORT_ENTRY_OFFSET: u64 = 2 * WORD_ALIGNMENT;
62/// `Tag_RISCV_stack_align` attribute tag
63const TAG_RISCV_STACK_ALIGN: u64 = 4;
64/// `Tag_RISCV_arch` attribute tag
65const TAG_RISCV_ARCH: u64 = 5;
66/// `Tag_RISCV_stack_align` as required by the psABI
67const RISCV_STACK_ALIGN: u64 = 16;
68
69#[derive(Debug, Copy, Clone)]
70struct ParsedHostCallFn {
71    /// Address relative to the beginning of contract memory
72    address: u64,
73    size: u64,
74    /// Address of the PLT entry that the host call function jumps to, relative to the beginning of
75    /// contract memory
76    plt_entry_address: u64,
77}
78
79#[derive(Debug)]
80struct ParsedContract<'a> {
81    metadata: &'a [u8],
82    rodata: &'a [u8],
83    /// Whether the metadata is located before `.rodata` in memory
84    metadata_first: bool,
85    /// Size of the read-only memory region, which includes zero padding before code
86    read_only_memory_size: u64,
87    code: &'a [u8],
88    /// Dynamic symbols except the null symbol: the host call function import (if any), methods and
89    /// the host call function (if any)
90    symbols: Vec<DynamicSymbol<'a>>,
91    host_call_fn: Option<ParsedHostCallFn>,
92}
93
94#[derive(Debug, Copy, Clone)]
95struct DynamicSymbol<'a> {
96    name: &'a str,
97    /// Address relative to the beginning of contract memory and size of a function, `None` for an
98    /// import
99    function: Option<(u64, u64)>,
100}
101
102fn parse_host_call_fn(
103    contract_file: &ContractFile<'_>,
104    code_offset: usize,
105    read_only_memory_size: u64,
106) -> anyhow::Result<Option<ParsedHostCallFn>> {
107    let host_call_fn_offset = contract_file.header().host_call_fn_offset;
108    if host_call_fn_offset == 0 {
109        return Ok(None);
110    }
111
112    let offset_in_code = usize::try_from(host_call_fn_offset)?
113        .checked_sub(code_offset)
114        .context("Host call function is before code section")?;
115    let instruction_bytes = contract_file
116        .get_code()
117        .get(offset_in_code..)
118        .context("Host call function is out of range of code section")?;
119
120    // Compressed instruction might be the last one in the file, in which case the remaining bytes
121    // are zero-padded for decoding
122    let mut instruction_word = [0; size_of::<u32>()];
123    instruction_word
124        .iter_mut()
125        .zip(instruction_bytes)
126        .for_each(|(target, source)| *target = *source);
127    let instruction =
128        <ContractInstruction as Instruction>::try_decode(u32::from_le_bytes(instruction_word))
129            .context("Failed to decode host call function instruction")?;
130
131    #[expect(
132        clippy::rest_pattern_accessible_field,
133        reason = "Do not need other fields"
134    )]
135    let jump_offset = match instruction {
136        ContractInstruction::Jal {
137            rd: ContractRegister::Zero,
138            imm,
139            ..
140        } => i64::from(imm),
141        ContractInstruction::CJ { imm, .. } => i64::from(imm),
142        _ => {
143            return Err(anyhow::anyhow!(
144                "Unexpected host call function instruction {instruction}"
145            ));
146        }
147    };
148
149    let address = read_only_memory_size + u64::try_from(offset_in_code)?;
150    let plt_entry_address = address
151        .checked_add_signed(jump_offset)
152        .context("Host call function jumps outside of contract memory")?;
153
154    Ok(Some(ParsedHostCallFn {
155        address,
156        size: u64::from(instruction.size()),
157        plt_entry_address,
158    }))
159}
160
161fn parse_contract(input_file: &[u8]) -> anyhow::Result<ParsedContract<'_>> {
162    let mut contract_file_methods = Vec::new();
163    let contract_file = ContractFile::parse(input_file, |contract_file_method| {
164        contract_file_methods.push(contract_file_method);
165        Ok(())
166    })
167    .context("Failed to parse contract file")?;
168    let header = contract_file.header();
169
170    let code_offset = input_file.len() - contract_file.get_code().len();
171    let code = input_file
172        .get(code_offset..)
173        .context("Code section is out of range of the contract file")?;
174    let read_only_size = usize::try_from(header.read_only_section_file_size)?;
175    let read_only_offset = code_offset
176        .checked_sub(read_only_size)
177        .context("Read-only section is out of range of the contract file")?;
178    let read_only = input_file
179        .get(read_only_offset..code_offset)
180        .context("Read-only section is out of range of the contract file")?;
181    let read_only_memory_size = u64::from(header.read_only_section_memory_size);
182
183    if header.metadata_size == 0 {
184        return Err(anyhow::anyhow!("Metadata not found"));
185    }
186    let metadata_offset = usize::try_from(header.metadata_offset)?;
187    let metadata = input_file
188        .get(metadata_offset..)
189        .and_then(|metadata| metadata.get(..usize::from(header.metadata_size)))
190        .context("Metadata is out of range of the contract file")?;
191
192    // Metadata and `.rodata` are stored next to each other, and which one goes first can only be
193    // derived from the metadata offset
194    let (metadata_first, rodata) = if metadata_offset == read_only_offset {
195        let rodata = read_only
196            .get(metadata.len()..)
197            .context("Metadata is larger than read-only section")?;
198        (true, rodata)
199    } else if metadata_offset + metadata.len() == code_offset {
200        let rodata = read_only
201            .len()
202            .checked_sub(metadata.len())
203            .and_then(|rodata_size| read_only.get(..rodata_size))
204            .context("Metadata is larger than read-only section")?;
205        (false, rodata)
206    } else {
207        return Err(anyhow::anyhow!(
208            "Metadata must be either at the beginning or at the end of read-only section: \
209            metadata_offset={metadata_offset}, metadata_size={}, \
210            read_only_offset={read_only_offset}, code_offset={code_offset}",
211            metadata.len()
212        ));
213    };
214
215    let host_call_fn = parse_host_call_fn(&contract_file, code_offset, read_only_memory_size)?;
216
217    // Names of exported methods must not clash with each other or with host call function and its
218    // import, or else the recovered file will not convert back
219    let mut symbol_names = HashSet::from([HOST_CALL_FN, HOST_CALL_FN_IMPORT]);
220    let methods = contract_file_methods
221        .into_iter()
222        .map(|contract_file_method| {
223            let method_name = contract_file_method.method_metadata_item.method_name;
224            let name = str::from_utf8(method_name)
225                .with_context(|| format!("Non-UTF-8 method name: {method_name:?}"))?;
226            if name.contains('\0') {
227                return Err(anyhow::anyhow!("Method name {name:?} contains zero byte"));
228            }
229            if !symbol_names.insert(name) {
230                return Err(anyhow::anyhow!("Duplicate or reserved method name {name}"));
231            }
232
233            Ok(DynamicSymbol {
234                name,
235                function: Some((
236                    u64::from(contract_file_method.address),
237                    u64::from(contract_file_method.size),
238                )),
239            })
240        })
241        .collect::<anyhow::Result<Vec<_>>>()?;
242
243    let symbols = host_call_fn
244        .map(|_host_call_fn| DynamicSymbol {
245            name: HOST_CALL_FN_IMPORT,
246            function: None,
247        })
248        .into_iter()
249        .chain(methods)
250        .chain(host_call_fn.map(|host_call_fn| DynamicSymbol {
251            name: HOST_CALL_FN,
252            function: Some((host_call_fn.address, host_call_fn.size)),
253        }))
254        .collect();
255
256    Ok(ParsedContract {
257        metadata,
258        rodata,
259        metadata_first,
260        read_only_memory_size,
261        code,
262        symbols,
263        host_call_fn,
264    })
265}
266
267/// Location of a section or segment in the file and in memory
268#[derive(Debug, Default, Copy, Clone)]
269struct Placement {
270    offset: u64,
271    address: u64,
272    size: u64,
273}
274
275impl Placement {
276    /// Placement in the first segment, where file offsets are equal to addresses
277    fn identity(offset: u64, size: u64) -> Self {
278        Self {
279            offset,
280            address: offset,
281            size,
282        }
283    }
284
285    fn end_offset(&self) -> u64 {
286        self.offset + self.size
287    }
288
289    fn end_address(&self) -> u64 {
290        self.address + self.size
291    }
292
293    /// Placement of the next section in the same segment at a file offset reserved after this one
294    fn next_at(&self, offset: u64, size: u64) -> Self {
295        Self {
296            offset,
297            address: self.end_address() + (offset - self.end_offset()),
298            size,
299        }
300    }
301
302    /// Reserve the file range at the exact offset
303    fn reserve(&self, writer: &mut Writer<'_>) {
304        writer.reserve_until(self.offset);
305        writer.reserve(self.size, 1);
306    }
307
308    fn section_header(&self) -> SectionHeader {
309        SectionHeader {
310            sh_addr: self.address,
311            sh_offset: self.offset,
312            sh_size: self.size,
313            ..SectionHeader::default()
314        }
315    }
316
317    fn program_header(
318        &self,
319        p_type: ProgramType,
320        p_flags: ProgramFlags,
321        p_align: u64,
322    ) -> ProgramHeader {
323        ProgramHeader {
324            p_type,
325            p_flags,
326            p_offset: self.offset,
327            p_vaddr: self.address,
328            p_paddr: self.address,
329            p_filesz: self.size,
330            p_memsz: self.size,
331            p_align,
332        }
333    }
334}
335
336/// Section other than those whose headers [`Writer`] writes on its own
337#[derive(Debug, Copy, Clone)]
338struct Section {
339    name: StringId,
340    index: SectionIndex,
341}
342
343impl Section {
344    fn reserve(writer: &mut Writer<'_>, name: &'static [u8]) -> Self {
345        Self {
346            name: writer.add_section_name(name),
347            index: writer.reserve_section_index(),
348        }
349    }
350
351    fn write_header(self, writer: &mut Writer<'_>, header: SectionHeader) {
352        writer.write_section_header(&SectionHeader {
353            sh_name: writer.section_name_offset(Some(self.name)),
354            ..header
355        });
356    }
357}
358
359/// Parts of the ELF file that only exist when there is a host call function
360#[derive(Debug, Copy, Clone)]
361struct HostCallParts {
362    import_symbol_index: SymbolIndex,
363    rela_plt_section: Section,
364    rela_plt: Placement,
365    plt_section: Section,
366    plt: Placement,
367    got_plt_section: Section,
368    got_plt: Placement,
369}
370
371/// Split PC-relative offset into `hi20` and `lo12` parts of `auipc` and I-type instruction pair,
372/// already shifted into their positions in the instruction encoding
373fn pc_relative_hi_lo(offset: u64) -> (u32, u32) {
374    let hi = u32::try_from((offset.wrapping_add(0x800) >> 12) & 0xf_ffff)
375        .expect("Masked to 20 bits; qed");
376    let lo = u32::try_from(offset & 0xfff).expect("Masked to 12 bits; qed");
377    (hi << 12, lo << 20)
378}
379
380/// PLT header followed by a single entry, the same way as `lld` generates them
381fn plt_code(plt_address: u64, got_plt_address: u64) -> [u8; PLT_SIZE as usize] {
382    let (header_hi, header_lo) = pc_relative_hi_lo(got_plt_address - plt_address);
383    let (entry_hi, entry_lo) = pc_relative_hi_lo(
384        (got_plt_address + GOT_PLT_IMPORT_ENTRY_OFFSET) - (plt_address + PLT_HEADER_SIZE),
385    );
386
387    let instructions: [u32; PLT_SIZE as usize / size_of::<u32>()] = [
388        // auipc t2, %pcrel_hi(.got.plt)
389        0x0000_0397 | header_hi,
390        // sub t1, t1, t3
391        0x41c3_0333,
392        // ld t3, %pcrel_lo(.got.plt)(t2)
393        0x0003_be03 | header_lo,
394        // addi t1, t1, -(PLT header size + 12)
395        0xfd43_0313,
396        // addi t0, t2, %pcrel_lo(.got.plt)
397        0x0003_8293 | header_lo,
398        // srli t1, t1, 1
399        0x0013_5313,
400        // ld t0, 8(t0)
401        0x0082_b283,
402        // jr t3
403        0x000e_0067,
404        // auipc t3, %pcrel_hi(import entry in .got.plt)
405        0x0000_0e17 | entry_hi,
406        // ld t3, %pcrel_lo(import entry in .got.plt)(t3)
407        0x000e_3e03 | entry_lo,
408        // jalr t1, t3
409        0x000e_0367,
410        // nop
411        0x0000_0013,
412    ];
413
414    let mut plt = [0; _];
415    plt.as_chunks_mut::<{ size_of::<u32>() }>()
416        .0
417        .iter_mut()
418        .zip(instructions)
419        .for_each(|(bytes, instruction)| *bytes = instruction.to_le_bytes());
420    plt
421}
422
423fn riscv_attributes() -> Vec<u8> {
424    let mut attributes = AttributesWriter::new(Endianness::Little);
425    attributes.start_subsection(b"riscv");
426    attributes.start_subsubsection(Tag_File);
427    attributes.write_attribute_tag(TAG_RISCV_STACK_ALIGN);
428    attributes.write_attribute_integer(RISCV_STACK_ALIGN);
429    attributes.write_attribute_tag(TAG_RISCV_ARCH);
430    // ISA string of contract instructions allows disassemblers and debuggers to decode all
431    // instructions correctly
432    attributes.write_attribute_string(<ContractInstruction as Instruction>::ISA_STRING.as_bytes());
433    attributes.end_subsubsection();
434    attributes.end_subsection();
435    attributes.data()
436}
437
438/// Recover RISC-V ELF `cdylib` from Abundance contract file format.
439///
440/// The output converts back into exactly the same contract file with [`convert()`].
441///
442/// [`convert()`]: crate::convert::convert
443pub fn recover(input_file: &[u8]) -> anyhow::Result<Vec<u8>> {
444    let ParsedContract {
445        metadata,
446        rodata,
447        metadata_first,
448        read_only_memory_size,
449        code,
450        symbols,
451        host_call_fn,
452    } = parse_contract(input_file)?;
453
454    let riscv_attributes = riscv_attributes();
455    // Metadata and `.rodata` in memory order, `.rodata` section is omitted when empty
456    let read_only_parts: [(&'static [u8], &[u8]); 2] = if metadata_first {
457        [(b"ab-contract-metadata", metadata), (b".rodata", rodata)]
458    } else {
459        [(b".rodata", rodata), (b"ab-contract-metadata", metadata)]
460    };
461
462    let mut output_file = Vec::new();
463    let mut writer = Writer::new(Endianness::Little, true, &mut output_file);
464
465    // Section indices are reserved in the same order as section headers are written
466    writer.reserve_dynsym_section_index();
467    writer.reserve_hash_section_index();
468    writer.reserve_dynstr_section_index();
469    let rela_plt_section = host_call_fn.map(|_| Section::reserve(&mut writer, b".rela.plt"));
470    let read_only_sections = read_only_parts
471        .into_iter()
472        .filter(|(_name, data)| !data.is_empty())
473        .map(|(name, data)| (Section::reserve(&mut writer, name), data))
474        .collect::<Vec<_>>();
475    let text_section = Section::reserve(&mut writer, b".text");
476    let plt_section = host_call_fn.map(|_| Section::reserve(&mut writer, b".plt"));
477    writer.reserve_dynamic_section_index();
478    let got_plt_section = host_call_fn.map(|_| Section::reserve(&mut writer, b".got.plt"));
479    let riscv_attributes_section = Section::reserve(&mut writer, b".riscv.attributes");
480    writer.reserve_shstrtab_section_index();
481
482    writer.reserve_null_dynamic_symbol_index();
483    let symbol_ids = symbols
484        .iter()
485        .map(|symbol| {
486            (
487                writer.reserve_dynamic_symbol_index(),
488                writer.add_dynamic_string(symbol.name.as_bytes()),
489            )
490        })
491        .collect::<Vec<_>>();
492    let import_symbol_index = symbols
493        .iter()
494        .zip(&symbol_ids)
495        .find_map(|(symbol, &(index, _name))| symbol.function.is_none().then_some(index));
496    let num_symbols = writer.dynamic_symbol_count();
497
498    // The first `PT_LOAD` segment contains headers, dynamic linking information and read-only data,
499    // with file offsets equal to addresses
500    writer.reserve_file_header();
501    writer.reserve_program_headers(u32::try_from(NUM_PROGRAM_HEADERS)?);
502    let dynsym_address = writer.reserve_dynsym();
503    let hash_address = writer.reserve_hash(num_symbols, num_symbols);
504    let dynstr = Placement::identity(writer.reserve_dynstr()?, u64::from(writer.dynstr_len()));
505    let rela_plt =
506        host_call_fn.map(|_| Placement::identity(writer.reserve_relocations(1, true), RELA_SIZE));
507    // Read-only data starts at a page boundary
508    let read_only_start = writer.reserve(0, PAGE_SIZE);
509    let read_only_sections = read_only_sections
510        .into_iter()
511        .map(|(section, data)| {
512            let size = u64::try_from(data.len())?;
513            let placement = Placement::identity(writer.reserve(size, 1), size);
514            anyhow::Ok((section, placement, data))
515        })
516        .collect::<anyhow::Result<Vec<_>>>()?;
517    let read_only = Placement::identity(read_only_start, writer.reserved_len() - read_only_start);
518    let read_only_padding = read_only_memory_size
519        .checked_sub(read_only.size)
520        .context("Read-only section file size is larger than memory size")?;
521
522    // The second `PT_LOAD` segment contains code and PLT. Code address is fixed relative to
523    // read-only data, while file offset only needs to be congruent with it modulo page size.
524    let text = Placement {
525        offset: read_only.end_offset() + read_only_padding % PAGE_SIZE,
526        address: read_only.address + read_only_memory_size,
527        size: u64::try_from(code.len())?,
528    };
529    if !text.address.is_multiple_of(INSTRUCTION_ALIGNMENT) {
530        return Err(anyhow::anyhow!(
531            "Code section is not aligned to instruction boundary: read-only memory size \
532            {read_only_memory_size}"
533        ));
534    }
535    text.reserve(&mut writer);
536    // PLT location is defined by the host call function that jumps to its only entry
537    let plt = host_call_fn
538        .map(|host_call_fn| {
539            let address = (read_only.address + host_call_fn.plt_entry_address)
540                .checked_sub(PLT_HEADER_SIZE)
541                .filter(|&address| address >= text.end_address())
542                .with_context(|| {
543                    format!(
544                        "Host call function must jump to PLT entry after code section, but jumps \
545                        to {} relative to the beginning of contract memory",
546                        host_call_fn.plt_entry_address
547                    )
548                })?;
549
550            let plt = Placement {
551                offset: text.offset + (address - text.address),
552                address,
553                size: PLT_SIZE,
554            };
555            plt.reserve(&mut writer);
556            anyhow::Ok(plt)
557        })
558        .transpose()?;
559    let executable_end = plt.unwrap_or(text);
560
561    // The third `PT_LOAD` segment contains dynamic section and GOT, it starts at a new page in
562    // memory
563    let num_dynamic_entries = if host_call_fn.is_some() {
564        NUM_DYNAMIC_ENTRIES + NUM_PLT_DYNAMIC_ENTRIES
565    } else {
566        NUM_DYNAMIC_ENTRIES
567    };
568    let dynamic = {
569        let offset = writer.reserve_dynamic(num_dynamic_entries);
570        Placement {
571            offset,
572            address: executable_end.end_address().next_multiple_of(PAGE_SIZE) + offset % PAGE_SIZE,
573            size: u64::try_from(num_dynamic_entries)? * DYNAMIC_ENTRY_SIZE,
574        }
575    };
576    let got_plt = host_call_fn
577        .map(|_| dynamic.next_at(writer.reserve(GOT_PLT_SIZE, WORD_ALIGNMENT), GOT_PLT_SIZE));
578    let writable_end = got_plt.unwrap_or(dynamic);
579
580    // Non-allocated sections
581    let riscv_attributes_placement = {
582        let size = u64::try_from(riscv_attributes.len())?;
583        Placement {
584            offset: writer.reserve(size, 1),
585            address: 0,
586            size,
587        }
588    };
589    writer.reserve_shstrtab()?;
590    writer.reserve_section_headers();
591
592    let host_call = try {
593        HostCallParts {
594            import_symbol_index: import_symbol_index?,
595            rela_plt_section: rela_plt_section?,
596            rela_plt: rela_plt?,
597            plt_section: plt_section?,
598            plt: plt?,
599            got_plt_section: got_plt_section?,
600            got_plt: got_plt?,
601        }
602    };
603
604    let mut dynamic_entries = vec![
605        (DT_FLAGS, (DF_SYMBOLIC | DF_BIND_NOW).0),
606        (DT_FLAGS_1, DF_1_NOW.0),
607    ];
608    if let Some(host_call) = host_call {
609        dynamic_entries.extend([
610            (DT_JMPREL, host_call.rela_plt.address),
611            (DT_PLTRELSZ, host_call.rela_plt.size),
612            (DT_PLTGOT, host_call.got_plt.address),
613            (DT_PLTREL, u64::try_from(DT_RELA.0)?),
614        ]);
615    }
616    dynamic_entries.extend([
617        (DT_SYMTAB, dynsym_address),
618        (DT_SYMENT, SYMBOL_SIZE),
619        (DT_STRTAB, dynstr.address),
620        (DT_STRSZ, dynstr.size),
621        (DT_HASH, hash_address),
622        (DT_NULL, 0),
623    ]);
624    if dynamic_entries.len() != num_dynamic_entries {
625        return Err(anyhow::anyhow!(
626            "Number of dynamic entries mismatch: {} != {num_dynamic_entries}",
627            dynamic_entries.len()
628        ));
629    }
630
631    writer.write_file_header(&FileHeader {
632        os_abi: ELFOSABI_GNU,
633        abi_version: 0,
634        e_type: ET_DYN,
635        e_machine: EM_RISCV,
636        e_entry: 0,
637        e_flags: EF_RISCV_RVC,
638    })?;
639
640    let program_headers = Placement::identity(
641        writer.write_align_program_headers(),
642        u64::try_from(NUM_PROGRAM_HEADERS)? * PROGRAM_HEADER_SIZE,
643    );
644    let program_headers: [_; NUM_PROGRAM_HEADERS] = [
645        program_headers.program_header(PT_PHDR, PF_R, WORD_ALIGNMENT),
646        Placement::identity(0, read_only.end_offset()).program_header(PT_LOAD, PF_R, PAGE_SIZE),
647        Placement {
648            size: executable_end.end_address() - text.address,
649            ..text
650        }
651        .program_header(PT_LOAD, PF_R | PF_X, PAGE_SIZE),
652        Placement {
653            size: writable_end.end_address() - dynamic.address,
654            ..dynamic
655        }
656        .program_header(PT_LOAD, PF_R | PF_W, PAGE_SIZE),
657        dynamic.program_header(PT_DYNAMIC, PF_R | PF_W, WORD_ALIGNMENT),
658        Placement::default().program_header(PT_GNU_STACK, PF_R | PF_W, 0),
659        riscv_attributes_placement.program_header(PT_RISCV_ATTRIBUTES, PF_R, 1),
660    ];
661    for program_header in &program_headers {
662        writer.write_program_header(program_header);
663    }
664
665    writer.write_null_dynamic_symbol();
666    for (symbol, (_index, name)) in symbols.iter().zip(symbol_ids) {
667        let (section, st_type, st_value, st_size) = match symbol.function {
668            Some((address, size)) => (
669                Some(text_section.index.0),
670                STT_FUNC,
671                read_only.address + address,
672                size,
673            ),
674            None => (None, STT_NOTYPE, 0, 0),
675        };
676        writer.write_dynamic_symbol(&Sym {
677            section,
678            st_name: writer.dynamic_string_offset(Some(name)),
679            st_info: SymbolInfo::new(STB_GLOBAL, st_type),
680            st_other: STV_DEFAULT.into(),
681            // Only used for symbols without a section
682            st_shndx: SHN_UNDEF,
683            st_value,
684            st_size,
685        });
686    }
687    // SysV hash table with as many buckets as there are symbols, the same way as `lld` builds it
688    writer.write_hash(num_symbols, num_symbols, |symbol_index| {
689        let symbol = symbols.get(usize::try_from(symbol_index.checked_sub(1)?).ok()?)?;
690        Some(object::elf::hash(symbol.name.as_bytes()))
691    });
692    writer.write_dynstr();
693
694    if let Some(host_call) = host_call {
695        writer.write_align_relocation();
696        writer.write_relocation(
697            true,
698            &Rel {
699                r_offset: host_call.got_plt.address + GOT_PLT_IMPORT_ENTRY_OFFSET,
700                r_sym: host_call.import_symbol_index.0,
701                r_type: R_RISCV_JUMP_SLOT,
702                r_addend: 0,
703            },
704        );
705    }
706    for (_section, placement, data) in &read_only_sections {
707        writer.pad_until(placement.offset);
708        writer.write(data);
709    }
710    writer.pad_until(text.offset);
711    writer.write(code);
712    if let Some(host_call) = host_call {
713        writer.pad_until(host_call.plt.offset);
714        writer.write(&plt_code(host_call.plt.address, host_call.got_plt.address));
715    }
716
717    writer.write_align_dynamic();
718    for (d_tag, d_val) in dynamic_entries {
719        writer.write_dynamic(d_tag, d_val)?;
720    }
721    if let Some(host_call) = host_call {
722        // Reserved entries are filled by the dynamic linker, while the import entry initially
723        // points to the PLT header for lazy binding
724        let got_plt_entries: [_; GOT_PLT_ENTRIES] =
725            [0, 0, host_call.plt.address].map(|entry| U64::new(LittleEndian, entry));
726        writer.pad_until(host_call.got_plt.offset);
727        writer.write(bytes_of_slice(&got_plt_entries));
728    }
729    writer.pad_until(riscv_attributes_placement.offset);
730    writer.write(&riscv_attributes);
731    writer.write_shstrtab();
732
733    writer.write_null_section_header();
734    // Only the null symbol is local
735    writer.write_dynsym_section_header(dynsym_address, 1);
736    writer.write_hash_section_header(hash_address);
737    writer.write_dynstr_section_header(dynstr.address);
738    if let Some(host_call) = host_call {
739        let dynsym_index = writer.dynsym_index();
740        host_call.rela_plt_section.write_header(
741            &mut writer,
742            SectionHeader {
743                sh_type: SHT_RELA,
744                sh_flags: SHF_ALLOC | SHF_INFO_LINK,
745                sh_link: dynsym_index.0,
746                sh_info: host_call.got_plt_section.index.0,
747                sh_addralign: WORD_ALIGNMENT,
748                sh_entsize: RELA_SIZE,
749                ..host_call.rela_plt.section_header()
750            },
751        );
752    }
753    for (section, placement, _data) in &read_only_sections {
754        section.write_header(
755            &mut writer,
756            SectionHeader {
757                sh_type: SHT_PROGBITS,
758                sh_flags: SHF_ALLOC,
759                sh_addralign: 1,
760                ..placement.section_header()
761            },
762        );
763    }
764    text_section.write_header(
765        &mut writer,
766        SectionHeader {
767            sh_type: SHT_PROGBITS,
768            sh_flags: SHF_ALLOC | SHF_EXECINSTR,
769            sh_addralign: INSTRUCTION_ALIGNMENT,
770            ..text.section_header()
771        },
772    );
773    if let Some(host_call) = host_call {
774        host_call.plt_section.write_header(
775            &mut writer,
776            SectionHeader {
777                sh_type: SHT_PROGBITS,
778                sh_flags: SHF_ALLOC | SHF_EXECINSTR,
779                sh_addralign: INSTRUCTION_ALIGNMENT,
780                ..host_call.plt.section_header()
781            },
782        );
783    }
784    writer.write_dynamic_section_header(dynamic.address);
785    if let Some(host_call) = host_call {
786        host_call.got_plt_section.write_header(
787            &mut writer,
788            SectionHeader {
789                sh_type: SHT_PROGBITS,
790                sh_flags: SHF_ALLOC | SHF_WRITE,
791                sh_addralign: WORD_ALIGNMENT,
792                ..host_call.got_plt.section_header()
793            },
794        );
795    }
796    riscv_attributes_section.write_header(
797        &mut writer,
798        SectionHeader {
799            sh_type: SHT_RISCV_ATTRIBUTES,
800            sh_addralign: 1,
801            ..riscv_attributes_placement.section_header()
802        },
803    );
804    writer.write_shstrtab_section_header();
805
806    Ok(output_file)
807}