use std::fs;
use std::path::Path;
use crate::error::Error;
use crate::gobin::{Container, GoBinary};
use crate::pclntab::Function;
use crate::Result;
const ELF_MAGIC: [u8; 4] = [0x7f, b'E', b'L', b'F'];
const ELFCLASS64: u8 = 2;
const ELFDATA2LSB: u8 = 1;
const EHDR_E_SHOFF: usize = 40;
const EHDR_E_SHENTSIZE: usize = 58;
const EHDR_E_SHNUM: usize = 60;
const EHDR_E_SHSTRNDX: usize = 62;
const SHDR_SIZE: usize = 64;
const SHT_NULL: u32 = 0;
const SHT_STRTAB: u32 = 3;
const SHT_SYMTAB: u32 = 2;
const STB_GLOBAL: u8 = 1;
const STT_FUNC: u8 = 2;
const SYM_INFO_GLOBAL_FUNC: u8 = (STB_GLOBAL << 4) | STT_FUNC;
const SYM_SIZE: usize = 24;
pub fn write_symbols_as_elf(
bin: &GoBinary,
functions: &[Function],
out_path: &Path,
source_path: Option<&Path>,
) -> Result<usize> {
if bin.container != Container::Elf {
return Err(Error::Rewrite(format!(
"--symbols-as elf requires an ELF input, got {:?}",
bin.container
)));
}
if !bin.little_endian {
return Err(Error::Rewrite(
"--symbols-as elf only supports little-endian ELF64 today".into(),
));
}
if bin.bytes.len() < 64 {
return Err(Error::Rewrite("input is smaller than an ELF header".into()));
}
if bin.bytes[0..4] != ELF_MAGIC {
return Err(Error::Rewrite("input does not start with ELF magic".into()));
}
if bin.bytes[4] != ELFCLASS64 {
return Err(Error::Rewrite(
"--symbols-as elf only supports ELF64".into(),
));
}
if bin.bytes[5] != ELFDATA2LSB {
return Err(Error::Rewrite(
"--symbols-as elf only supports little-endian ELF".into(),
));
}
let original_shoff = u64::from_le_bytes(
bin.bytes[EHDR_E_SHOFF..EHDR_E_SHOFF + 8]
.try_into()
.unwrap(),
) as usize;
let original_shnum = u16::from_le_bytes(
bin.bytes[EHDR_E_SHNUM..EHDR_E_SHNUM + 2]
.try_into()
.unwrap(),
) as usize;
let original_shstrndx = u16::from_le_bytes(
bin.bytes[EHDR_E_SHSTRNDX..EHDR_E_SHSTRNDX + 2]
.try_into()
.unwrap(),
) as usize;
let shentsize = u16::from_le_bytes(
bin.bytes[EHDR_E_SHENTSIZE..EHDR_E_SHENTSIZE + 2]
.try_into()
.unwrap(),
) as usize;
if shentsize != SHDR_SIZE {
return Err(Error::Rewrite(format!(
"unexpected section header entry size {shentsize}, want {SHDR_SIZE}"
)));
}
let shdr_table_size = original_shnum * SHDR_SIZE;
if original_shoff + shdr_table_size > bin.bytes.len() {
return Err(Error::Rewrite("section header table overruns file".into()));
}
let original_shdrs = &bin.bytes[original_shoff..original_shoff + shdr_table_size];
let orig_shstrtab_hdr =
&original_shdrs[original_shstrndx * SHDR_SIZE..original_shstrndx * SHDR_SIZE + SHDR_SIZE];
let orig_shstrtab_off =
u64::from_le_bytes(orig_shstrtab_hdr[24..32].try_into().unwrap()) as usize;
let orig_shstrtab_size =
u64::from_le_bytes(orig_shstrtab_hdr[32..40].try_into().unwrap()) as usize;
if orig_shstrtab_off + orig_shstrtab_size > bin.bytes.len() {
return Err(Error::Rewrite("original shstrtab overruns file".into()));
}
let mut shstrtab = Vec::with_capacity(orig_shstrtab_size + 64);
shstrtab
.extend_from_slice(&bin.bytes[orig_shstrtab_off..orig_shstrtab_off + orig_shstrtab_size]);
let shstrtab_symtab_off = shstrtab.len() as u32;
shstrtab.extend_from_slice(b".symtab\0");
let shstrtab_strtab_off = shstrtab.len() as u32;
shstrtab.extend_from_slice(b".strtab\0");
let shstrtab_shstrtab_off = shstrtab.len() as u32;
shstrtab.extend_from_slice(b".shstrtab\0");
let mut strtab = Vec::with_capacity(functions.len() * 32);
strtab.push(0u8); let mut strtab_offsets: Vec<u32> = Vec::with_capacity(functions.len());
for f in functions {
strtab_offsets.push(strtab.len() as u32);
strtab.extend_from_slice(f.name.as_bytes());
strtab.push(0);
}
let mut symtab = Vec::with_capacity((functions.len() + 1) * SYM_SIZE);
symtab.extend_from_slice(&[0u8; SYM_SIZE]);
for (f, &name_off) in functions.iter().zip(strtab_offsets.iter()) {
symtab.extend_from_slice(&name_off.to_le_bytes()); symtab.push(SYM_INFO_GLOBAL_FUNC); symtab.push(0); symtab.extend_from_slice(&1u16.to_le_bytes()); symtab.extend_from_slice(&f.address.to_le_bytes()); symtab.extend_from_slice(&0u64.to_le_bytes());
}
let symbol_count = functions.len() + 1;
let mut out = Vec::with_capacity(bin.bytes.len() + symtab.len() + strtab.len() + 4096);
out.extend_from_slice(&bin.bytes);
while out.len() % 8 != 0 {
out.push(0);
}
let symtab_off = out.len() as u64;
let symtab_size = symtab.len() as u64;
out.extend_from_slice(&symtab);
let strtab_off = out.len() as u64;
let strtab_size = strtab.len() as u64;
out.extend_from_slice(&strtab);
let shstrtab_off = out.len() as u64;
let shstrtab_size = shstrtab.len() as u64;
out.extend_from_slice(&shstrtab);
while out.len() % 8 != 0 {
out.push(0);
}
let new_shoff = out.len() as u64;
out.extend_from_slice(original_shdrs);
let new_shnum_before_extras = original_shnum;
let mut text_shndx: u16 = 1;
for i in 0..original_shnum {
let off = i * SHDR_SIZE;
let sh_flags = u64::from_le_bytes(original_shdrs[off + 8..off + 16].try_into().unwrap());
if sh_flags & 0x4 != 0 {
text_shndx = i as u16;
break;
}
}
for sym_idx in 1..symbol_count {
let sym_start = symtab_off as usize + sym_idx * SYM_SIZE;
out[sym_start + 6..sym_start + 8].copy_from_slice(&text_shndx.to_le_bytes());
}
let new_symtab_shndx = new_shnum_before_extras as u16;
let new_strtab_shndx = new_shnum_before_extras as u16 + 1;
let new_shstrtab_shndx = new_shnum_before_extras as u16 + 2;
out.extend_from_slice(&make_shdr(
shstrtab_symtab_off, SHT_SYMTAB, 0, 0, symtab_off, symtab_size, new_strtab_shndx as u32, 1, 8, SYM_SIZE as u64, ));
out.extend_from_slice(&make_shdr(
shstrtab_strtab_off,
SHT_STRTAB,
0,
0,
strtab_off,
strtab_size,
0,
0,
1,
0,
));
out.extend_from_slice(&make_shdr(
shstrtab_shstrtab_off,
SHT_STRTAB,
0,
0,
shstrtab_off,
shstrtab_size,
0,
0,
1,
0,
));
let new_shnum = (new_shnum_before_extras + 3) as u16;
let _ = original_shstrndx;
let _ = new_symtab_shndx;
out[EHDR_E_SHOFF..EHDR_E_SHOFF + 8].copy_from_slice(&new_shoff.to_le_bytes());
out[EHDR_E_SHNUM..EHDR_E_SHNUM + 2].copy_from_slice(&new_shnum.to_le_bytes());
out[EHDR_E_SHSTRNDX..EHDR_E_SHSTRNDX + 2].copy_from_slice(&new_shstrtab_shndx.to_le_bytes());
fs::write(out_path, &out).map_err(Error::Io)?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mode = source_path
.and_then(|p| fs::metadata(p).ok())
.map(|m| m.permissions().mode())
.unwrap_or(0o755);
let _ = fs::set_permissions(out_path, fs::Permissions::from_mode(mode));
}
#[cfg(not(unix))]
{
let _ = source_path;
}
Ok(functions.len())
}
#[allow(clippy::too_many_arguments)]
fn make_shdr(
sh_name: u32,
sh_type: u32,
sh_flags: u64,
sh_addr: u64,
sh_offset: u64,
sh_size: u64,
sh_link: u32,
sh_info: u32,
sh_addralign: u64,
sh_entsize: u64,
) -> [u8; SHDR_SIZE] {
let mut h = [0u8; SHDR_SIZE];
h[0..4].copy_from_slice(&sh_name.to_le_bytes());
h[4..8].copy_from_slice(&sh_type.to_le_bytes());
h[8..16].copy_from_slice(&sh_flags.to_le_bytes());
h[16..24].copy_from_slice(&sh_addr.to_le_bytes());
h[24..32].copy_from_slice(&sh_offset.to_le_bytes());
h[32..40].copy_from_slice(&sh_size.to_le_bytes());
h[40..44].copy_from_slice(&sh_link.to_le_bytes());
h[44..48].copy_from_slice(&sh_info.to_le_bytes());
h[48..56].copy_from_slice(&sh_addralign.to_le_bytes());
h[56..64].copy_from_slice(&sh_entsize.to_le_bytes());
h
}
#[allow(dead_code)]
const _: () = {
let _ = SHT_NULL;
};
const PE_E_LFANEW_OFFSET: usize = 0x3c;
const PE_SIGNATURE: [u8; 4] = [b'P', b'E', 0, 0];
const COFF_PTR_TO_SYMTAB: usize = 8;
const COFF_NUM_SYMBOLS: usize = 12;
const COFF_SIZE_OF_OPT_HDR: usize = 16;
const COFF_HEADER_SIZE: usize = 20;
const COFF_SECTION_HEADER_SIZE: usize = 40;
const COFF_SYMBOL_SIZE: usize = 18;
const IMAGE_SCN_MEM_EXECUTE: u32 = 0x2000_0000;
const IMAGE_SYM_CLASS_EXTERNAL: u8 = 2;
const COFF_SYM_TYPE_FUNCTION: u16 = 0x20;
pub fn write_symbols_as_pe(
bin: &GoBinary,
functions: &[Function],
out_path: &Path,
source_path: Option<&Path>,
) -> Result<usize> {
if bin.container != Container::Pe {
return Err(Error::Rewrite(format!(
"--symbols-as pe requires a PE input, got {:?}",
bin.container
)));
}
if !bin.little_endian {
return Err(Error::Rewrite(
"--symbols-as pe only supports little-endian PE today".into(),
));
}
if bin.bytes.len() < PE_E_LFANEW_OFFSET + 4 {
return Err(Error::Rewrite("input is smaller than a DOS header".into()));
}
let e_lfanew = u32::from_le_bytes(
bin.bytes[PE_E_LFANEW_OFFSET..PE_E_LFANEW_OFFSET + 4]
.try_into()
.unwrap(),
) as usize;
if e_lfanew + 4 + COFF_HEADER_SIZE > bin.bytes.len() {
return Err(Error::Rewrite("PE header overruns file".into()));
}
if bin.bytes[e_lfanew..e_lfanew + 4] != PE_SIGNATURE {
return Err(Error::Rewrite("missing PE\\0\\0 signature".into()));
}
let coff_off = e_lfanew + 4;
let size_of_opt_hdr = u16::from_le_bytes(
bin.bytes[coff_off + COFF_SIZE_OF_OPT_HDR..coff_off + COFF_SIZE_OF_OPT_HDR + 2]
.try_into()
.unwrap(),
) as usize;
let section_table_off = coff_off + COFF_HEADER_SIZE + size_of_opt_hdr;
let opt_hdr_off = coff_off + COFF_HEADER_SIZE;
if opt_hdr_off + 2 > bin.bytes.len() {
return Err(Error::Rewrite("optional header truncated".into()));
}
let opt_magic = u16::from_le_bytes(bin.bytes[opt_hdr_off..opt_hdr_off + 2].try_into().unwrap());
let image_base: u64 = match opt_magic {
0x20b => {
if opt_hdr_off + 24 + 8 > bin.bytes.len() {
return Err(Error::Rewrite("PE32+ optional header truncated".into()));
}
u64::from_le_bytes(
bin.bytes[opt_hdr_off + 24..opt_hdr_off + 32]
.try_into()
.unwrap(),
)
}
0x10b => {
if opt_hdr_off + 28 + 4 > bin.bytes.len() {
return Err(Error::Rewrite("PE32 optional header truncated".into()));
}
u32::from_le_bytes(
bin.bytes[opt_hdr_off + 28..opt_hdr_off + 32]
.try_into()
.unwrap(),
) as u64
}
other => {
return Err(Error::Rewrite(format!(
"unrecognized optional header magic 0x{other:x}"
)))
}
};
let num_sections =
u16::from_le_bytes(bin.bytes[coff_off + 2..coff_off + 4].try_into().unwrap()) as usize;
if section_table_off + num_sections * COFF_SECTION_HEADER_SIZE > bin.bytes.len() {
return Err(Error::Rewrite("section table overruns file".into()));
}
let mut text_section_index: u16 = 1;
for i in 0..num_sections {
let off = section_table_off + i * COFF_SECTION_HEADER_SIZE;
let characteristics = u32::from_le_bytes(bin.bytes[off + 36..off + 40].try_into().unwrap());
if characteristics & IMAGE_SCN_MEM_EXECUTE != 0 {
text_section_index = (i + 1) as u16;
break;
}
}
let mut symbols: Vec<u8> = Vec::with_capacity(functions.len() * COFF_SYMBOL_SIZE);
let mut strtab: Vec<u8> = Vec::with_capacity(functions.len() * 24);
strtab.extend_from_slice(&[0u8; 4]);
for f in functions {
let mut rec = [0u8; COFF_SYMBOL_SIZE];
let name_bytes = f.name.as_bytes();
if name_bytes.len() <= 8 {
rec[..name_bytes.len()].copy_from_slice(name_bytes);
} else {
let off = strtab.len() as u32;
rec[0..4].copy_from_slice(&0u32.to_le_bytes());
rec[4..8].copy_from_slice(&off.to_le_bytes());
strtab.extend_from_slice(name_bytes);
strtab.push(0);
}
let rva = f.address.saturating_sub(image_base) as u32;
rec[8..12].copy_from_slice(&rva.to_le_bytes());
rec[12..14].copy_from_slice(&text_section_index.to_le_bytes());
rec[14..16].copy_from_slice(&COFF_SYM_TYPE_FUNCTION.to_le_bytes());
rec[16] = IMAGE_SYM_CLASS_EXTERNAL;
rec[17] = 0; symbols.extend_from_slice(&rec);
}
let strtab_size = strtab.len() as u32;
strtab[0..4].copy_from_slice(&strtab_size.to_le_bytes());
let mut out = Vec::with_capacity(bin.bytes.len() + symbols.len() + strtab.len() + 16);
out.extend_from_slice(&bin.bytes);
while out.len() % 4 != 0 {
out.push(0);
}
let symtab_file_offset = out.len() as u32;
out.extend_from_slice(&symbols);
out.extend_from_slice(&strtab);
out[coff_off + COFF_PTR_TO_SYMTAB..coff_off + COFF_PTR_TO_SYMTAB + 4]
.copy_from_slice(&symtab_file_offset.to_le_bytes());
let num_symbols = functions.len() as u32;
out[coff_off + COFF_NUM_SYMBOLS..coff_off + COFF_NUM_SYMBOLS + 4]
.copy_from_slice(&num_symbols.to_le_bytes());
fs::write(out_path, &out).map_err(Error::Io)?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mode = source_path
.and_then(|p| fs::metadata(p).ok())
.map(|m| m.permissions().mode())
.unwrap_or(0o755);
let _ = fs::set_permissions(out_path, fs::Permissions::from_mode(mode));
}
#[cfg(not(unix))]
{
let _ = source_path;
}
Ok(functions.len())
}