use crate::error::{Error, Result};
const ELFMAG: &[u8] = b"\x7fELF";
const ELFCLASS64: u8 = 2;
const ELFDATA2LSB: u8 = 1;
const EV_CURRENT: u8 = 1;
const ELFOSABI_NONE: u8 = 0;
const ET_CORE: u16 = 4;
const EM_X86_64: u16 = 62;
const PT_NOTE: u32 = 4;
const PT_LOAD: u32 = 1;
const ELF64_EHDR_SIZE: u16 = 64;
const ELF64_PHDR_SIZE: u16 = 56;
const NT_PRSTATUS: u32 = 1;
const NT_AUXV: u32 = 6;
const NT_FILE: u32 = 0x46494c45;
pub const PF_R: u32 = 0x4;
pub const PF_W: u32 = 0x2;
pub const PF_X: u32 = 0x1;
pub struct CoreDumpInfo {
pub registers: Vec<u64>,
pub signal: u32,
pub pid: u32,
pub mappings: Vec<CoreMapping>,
pub auxv: Vec<u8>,
}
pub struct CoreMapping {
pub start: u64,
pub end: u64,
pub flags: u32,
pub data: Vec<u8>,
pub pathname: String,
pub file_offset: u64,
}
pub fn serialize_elf_header(phnum: u16, phoff: u64) -> Vec<u8> {
let mut hdr = vec![0u8; ELF64_EHDR_SIZE as usize];
hdr[0..4].copy_from_slice(ELFMAG);
hdr[4] = ELFCLASS64;
hdr[5] = ELFDATA2LSB;
hdr[6] = EV_CURRENT;
hdr[7] = ELFOSABI_NONE;
hdr[16..18].copy_from_slice(&ET_CORE.to_le_bytes());
hdr[18..20].copy_from_slice(&EM_X86_64.to_le_bytes());
hdr[20..24].copy_from_slice(&1u32.to_le_bytes());
hdr[32..40].copy_from_slice(&phoff.to_le_bytes());
hdr[52..54].copy_from_slice(&ELF64_EHDR_SIZE.to_le_bytes());
hdr[54..56].copy_from_slice(&ELF64_PHDR_SIZE.to_le_bytes());
hdr[56..58].copy_from_slice(&phnum.to_le_bytes());
hdr[58..60].copy_from_slice(&ELF64_PHDR_SIZE.to_le_bytes());
hdr
}
pub fn serialize_phdr(
p_type: u32,
p_flags: u32,
p_offset: u64,
p_vaddr: u64,
p_filesz: u64,
p_memsz: u64,
) -> Vec<u8> {
let mut phdr = vec![0u8; ELF64_PHDR_SIZE as usize];
phdr[0..4].copy_from_slice(&p_type.to_le_bytes());
phdr[4..8].copy_from_slice(&p_flags.to_le_bytes());
phdr[8..16].copy_from_slice(&p_offset.to_le_bytes());
phdr[16..24].copy_from_slice(&p_vaddr.to_le_bytes());
phdr[24..32].copy_from_slice(&p_vaddr.to_le_bytes());
phdr[32..40].copy_from_slice(&p_filesz.to_le_bytes());
phdr[40..48].copy_from_slice(&p_memsz.to_le_bytes());
phdr
}
pub fn serialize_note(name: &[u8], note_type: u32, desc: &[u8]) -> Vec<u8> {
let namesz = name.len() as u32;
let descsz = desc.len() as u32;
let name_aligned = align4(name.len());
let desc_aligned = align4(desc.len());
let total = 12 + name_aligned + desc_aligned;
let mut note = vec![0u8; total];
note[0..4].copy_from_slice(&namesz.to_le_bytes());
note[4..8].copy_from_slice(&descsz.to_le_bytes());
note[8..12].copy_from_slice(¬e_type.to_le_bytes());
note[12..12 + name.len()].copy_from_slice(name);
let desc_off = 12 + name_aligned;
note[desc_off..desc_off + desc.len()].copy_from_slice(desc);
note
}
pub fn build_prstatus(signal: u32, pid: u32, registers: &[u64]) -> Vec<u8> {
let mut desc = vec![0u8; 332];
desc[0..4].copy_from_slice(&signal.to_le_bytes());
desc[12..14].copy_from_slice(&(signal as u16).to_le_bytes());
desc[32..36].copy_from_slice(&pid.to_le_bytes());
let reg_offset = 112;
for (i, &val) in registers.iter().enumerate().take(27) {
let off = reg_offset + i * 8;
desc[off..off + 8].copy_from_slice(&val.to_le_bytes());
}
desc
}
pub fn build_file_note(mappings: &[(u64, u64, u64, &str)]) -> Vec<u8> {
let file_mappings: Vec<_> = mappings.iter().filter(|(_, _, _, p)| !p.is_empty()).collect();
let count = file_mappings.len() as u64;
let page_size: u64 = 4096;
let mut desc = Vec::new();
desc.extend_from_slice(&count.to_le_bytes());
desc.extend_from_slice(&page_size.to_le_bytes());
for (start, end, offset, _) in &file_mappings {
desc.extend_from_slice(&start.to_le_bytes());
desc.extend_from_slice(&end.to_le_bytes());
desc.extend_from_slice(&(offset / page_size).to_le_bytes());
}
for (_, _, _, pathname) in &file_mappings {
desc.extend_from_slice(pathname.as_bytes());
desc.push(0);
}
desc
}
pub fn generate(info: &CoreDumpInfo) -> Result<Vec<u8>> {
let num_load = info.mappings.len();
let phnum = 1 + num_load;
if phnum > u16::MAX as usize {
return Err(Error::Other("too many segments for core dump".into()));
}
let prstatus_desc = build_prstatus(info.signal, info.pid, &info.registers);
let prstatus_note = serialize_note(b"CORE\0", NT_PRSTATUS, &prstatus_desc);
let file_mappings: Vec<(u64, u64, u64, &str)> = info
.mappings
.iter()
.map(|m| (m.start, m.end, m.file_offset, m.pathname.as_str()))
.collect();
let file_note = serialize_note(b"CORE\0", NT_FILE, &build_file_note(&file_mappings));
let auxv_note = if !info.auxv.is_empty() {
serialize_note(b"CORE\0", NT_AUXV, &info.auxv)
} else {
Vec::new()
};
let notes_data: Vec<u8> = [&prstatus_note[..], &file_note[..], &auxv_note[..]].concat();
let ehdr_size = ELF64_EHDR_SIZE as u64;
let phdr_total = phnum as u64 * ELF64_PHDR_SIZE as u64;
let notes_offset = ehdr_size + phdr_total;
let notes_size = notes_data.len() as u64;
let mut data_offset = align_page(notes_offset + notes_size);
let mut phdrs = Vec::new();
phdrs.push(serialize_phdr(
PT_NOTE,
0,
notes_offset,
0,
notes_size,
0,
));
let mut load_offsets = Vec::new();
for mapping in &info.mappings {
let size = mapping.data.len() as u64;
let memsz = mapping.end - mapping.start;
phdrs.push(serialize_phdr(
PT_LOAD,
mapping.flags,
data_offset,
mapping.start,
size,
memsz,
));
load_offsets.push(data_offset);
data_offset = align_page(data_offset + size);
}
let mut core = Vec::new();
core.extend_from_slice(&serialize_elf_header(phnum as u16, ehdr_size));
for phdr in &phdrs {
core.extend_from_slice(phdr);
}
while core.len() < notes_offset as usize {
core.push(0);
}
core.extend_from_slice(¬es_data);
for (i, mapping) in info.mappings.iter().enumerate() {
let target_offset = load_offsets[i] as usize;
while core.len() < target_offset {
core.push(0);
}
core.extend_from_slice(&mapping.data);
}
Ok(core)
}
fn align4(n: usize) -> usize {
(n + 3) & !3
}
fn align_page(n: u64) -> u64 {
(n + 4095) & !4095
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn elf_header_format() {
let hdr = serialize_elf_header(3, 64);
assert_eq!(&hdr[0..4], ELFMAG);
assert_eq!(hdr[4], ELFCLASS64);
assert_eq!(hdr[5], ELFDATA2LSB);
assert_eq!(u16::from_le_bytes([hdr[16], hdr[17]]), ET_CORE);
assert_eq!(u16::from_le_bytes([hdr[18], hdr[19]]), EM_X86_64);
assert_eq!(u16::from_le_bytes([hdr[56], hdr[57]]), 3); }
#[test]
fn phdr_format() {
let phdr = serialize_phdr(PT_LOAD, PF_R | PF_W, 0x1000, 0x400000, 0x2000, 0x3000);
assert_eq!(
u32::from_le_bytes(phdr[0..4].try_into().unwrap()),
PT_LOAD
);
assert_eq!(
u32::from_le_bytes(phdr[4..8].try_into().unwrap()),
PF_R | PF_W
);
assert_eq!(
u64::from_le_bytes(phdr[8..16].try_into().unwrap()),
0x1000
);
assert_eq!(
u64::from_le_bytes(phdr[16..24].try_into().unwrap()),
0x400000
);
}
#[test]
fn note_serialization() {
let note = serialize_note(b"CORE\0", NT_PRSTATUS, &[1, 2, 3, 4]);
let namesz = u32::from_le_bytes(note[0..4].try_into().unwrap());
let descsz = u32::from_le_bytes(note[4..8].try_into().unwrap());
let ntype = u32::from_le_bytes(note[8..12].try_into().unwrap());
assert_eq!(namesz, 5); assert_eq!(descsz, 4);
assert_eq!(ntype, NT_PRSTATUS);
}
#[test]
fn prstatus_has_registers() {
let regs = vec![0x1234u64; 27];
let desc = build_prstatus(11, 1000, ®s);
assert_eq!(u32::from_le_bytes(desc[0..4].try_into().unwrap()), 11);
assert_eq!(u32::from_le_bytes(desc[32..36].try_into().unwrap()), 1000);
assert_eq!(
u64::from_le_bytes(desc[112..120].try_into().unwrap()),
0x1234
);
}
#[test]
fn file_note_empty() {
let note = build_file_note(&[]);
let count = u64::from_le_bytes(note[0..8].try_into().unwrap());
assert_eq!(count, 0);
}
#[test]
fn align4_works() {
assert_eq!(align4(0), 0);
assert_eq!(align4(1), 4);
assert_eq!(align4(4), 4);
assert_eq!(align4(5), 8);
}
#[test]
fn generate_minimal_core() {
let info = CoreDumpInfo {
registers: vec![0; 27],
signal: 11,
pid: 1234,
mappings: vec![CoreMapping {
start: 0x400000,
end: 0x401000,
flags: PF_R | PF_X,
data: vec![0x90; 64], pathname: String::new(),
file_offset: 0,
}],
auxv: Vec::new(),
};
let core = generate(&info).unwrap();
assert_eq!(&core[0..4], ELFMAG);
assert_eq!(u16::from_le_bytes([core[16], core[17]]), ET_CORE);
}
}