如何实现类静态链接器:将自定义.o文件程序追加至ELF32可执行文件?
Great question! Building a tool that appends code from a .o file to an existing ELF32 executable is a deep dive into ELF internals—let's break this down step by step, focusing on the key challenges you've highlighted: section appending, symbol table merging, and relocation handling.
Before doing any modifications, you need to fully parse both the target .elf executable (type ET_EXEC) and the input .o relocatable file (type ET_REL). You’ll need to work with core ELF32 structures:
Elf32_Ehdr: The main ELF file header (tracks section count, section table offset, etc.)Elf32_Shdr: Section headers (describes each section’s type, size, file offset, memory address, alignment, etc.)Elf32_Sym: Symbol table entries (tracks symbol names, types, values, and associated sections)Elf32_Rel: Relocation entries (marks which instructions need address fixes and how to apply them)
You can use the standard <linux/elf.h> header for these definitions, or reimplement them yourself if working cross-platform.
.text and .data Sections to the Target ELF - Prepare the target file:
- Find the end of the target
.elffile on disk. Calculate padding bytes to ensure the new sections meet theirsh_addralignrequirements (e.g.,.texttypically aligns to 16 bytes). - Write the
.textand.datacontent from the.ofile to the end of the target.elf, adding padding as needed.
- Find the end of the target
- Update section headers:
- Increment the target’s
e_shnum(section count) by 2 in theElf32_Ehdr. - Create new
Elf32_Shdrentries for the added sections:- For
.text: Setsh_type = SHT_PROGBITS,sh_flags = SHF_EXECINSTR | SHF_ALLOC,sh_offsetto the file offset where you wrote the.textcontent,sh_addrto the next available executable memory address (calculate as the last loaded section’ssh_addr + sh_size, aligned tosh_addralign). - For
.data: Setsh_type = SHT_PROGBITS,sh_flags = SHF_WRITE | SHF_ALLOC, with similar offset/address calculations (using writable memory addresses).
- For
- Adjust the target’s
e_shoff(section table offset) if the section table needs to be moved to accommodate the new headers.
- Increment the target’s
The .o file’s symbol table (usually .symtab) needs to be merged into the target .elf’s symbol table:
- Merge string tables:
- Append the
.o’s.strtab(symbol name strings) to the target’s.strtab. Update all.osymbol entries’st_namevalues to reflect their new offset in the merged string table (add the original size of the target’s.strtabto each.osymbol’sst_name).
- Append the
- Add symbols to the target’s symbol table:
- Iterate over each symbol in the
.o’s.symtab:- Local symbols (
STB_LOCAL): Add them directly, updatingst_shndxto match the new section indices of your appended.text/.datasections. - Global/weak symbols: Check if the target already has a symbol with the same name. Follow static linking rules:
- A strong global symbol in the target overrides a weak/global symbol in the
.o. - Duplicate strong global symbols are an error.
- If the symbol doesn’t exist in the target, add it, updating
st_valueto the final memory address (.osymbol’sst_value+ new section’ssh_addr).
- A strong global symbol in the target overrides a weak/global symbol in the
- Local symbols (
- Iterate over each symbol in the
- Update the target’s
.symtabsection header: Increasesh_sizebynumber_of_new_symbols * sizeof(Elf32_Sym).
This is the most critical part—you need to resolve the .o’s relocation entries to fix address references in the appended code:
- Process each relocation entry (
.rel.text,.rel.data):- For every
Elf32_Relentry:- Extract the symbol index (
ELF32_R_SYM(r_info)) and relocation type (ELF32_R_TYPE(r_info)). - Resolve the symbol’s final memory address:
- If the symbol exists in the target, use its
st_value. - If it’s a local symbol from the
.o, usenew_section_sh_addr + symbol_st_value.
- If the symbol exists in the target, use its
- Find the location in the target file to modify:
new_section_sh_offset + r_offset(ther_offsetfrom the.ois relative to its own.text/.datasection).
- Extract the symbol index (
- For every
- Apply the correct relocation based on type:
For x86 32-bit ELF, common types include:R_386_32: Direct 32-bit absolute address. Write the full symbol memory address into the 4-byte location.R_386_PC32: PC-relative offset. Calculate assymbol_address - (current_instruction_address + 4)(x86 uses the next instruction’s address as the PC base).current_instruction_addressisnew_text_sh_addr + r_offset. Write this signed 32-bit offset into the instruction.R_386_GOTPC/R_386_PLT: These are for dynamic linking—since you’re building a static-like tool, you can ignore these if your.odoesn’t use dynamic symbols.
Once relocations are applied, you don’t need to keep the .o’s relocation sections in the target .elf (executables don’t require runtime relocations).
- Update program headers: The target’s
Phdr(program header table) defines how sections are mapped into memory. You can either:- Add new
PT_LOADentries for your new.text/.datasections (matching permissions:PF_R | PF_Xfor.text,PF_R | PF_Wfor.data), or - Merge the new sections into existing
PT_LOADsegments if their permissions and alignment match (e.g., append your.textto the target’s existing.textsegment, then update the segment’sp_fileszandp_memsz).
- Add new
- Validate alignment: Double-check that all section offsets and memory addresses adhere to their
sh_addralignrequirements—misalignment will cause runtime crashes. - Update section links: Ensure fields like
sh_link(e.g., symbol table’s link to the string table) point to the correct section indices after merging.
You don’t need to manually inspect instructions to find which ones need fixes—that’s exactly what the .o’s relocation tables are for! The compiler generates these entries to mark every location where an address reference (like a function call or global variable access) needs adjustment. Your tool only needs to iterate over these entries and apply the relocation logic as defined by the ELF spec.
内容的提问来源于stack exchange,提问作者Scher Khan

