use crate::mc_disassembler::X86Disassembler;
use crate::mc_streamer::x86_mnemonic;
use crate::object_file::ObjectFile;
pub struct ObjdumpOutput {
pub text: String,
pub instructions: usize,
pub functions: usize,
}
pub fn disassemble_object(data: &[u8]) -> Option<ObjdumpOutput> {
let obj = ObjectFile::parse(data)?;
if !obj.is_valid() {
return None;
}
let mut output = String::new();
let mut total_instructions = 0usize;
let mut total_functions = 0usize;
output.push_str("\ninput.o:\tfile format elf64-x86-64\n\n");
let (text_offset, text_size) = find_text_section(data);
if text_size == 0 {
output.push_str("(no .text section)\n");
return Some(ObjdumpOutput {
text: output,
instructions: 0,
functions: 0,
});
}
let symbols = find_symbols(data);
output.push_str("Disassembly of section .text:\n\n");
let disassembler = X86Disassembler::new();
let text_bytes = &data[text_offset..text_offset + text_size];
let decoded = disassembler.decode_all(text_bytes);
let mut func_starts: Vec<(u64, String)> = symbols
.iter()
.filter(|s| s.sym_type == 2) .map(|s| (s.value, s.name.clone()))
.collect();
func_starts.sort_by_key(|(v, _)| *v);
let mut current_func: Option<&str> = None;
let mut func_idx = 0usize;
for inst in &decoded {
while func_idx < func_starts.len() && func_starts[func_idx].0 <= inst.address {
let (addr, ref name) = func_starts[func_idx];
if let Some(prev) = current_func {
if prev != name.as_str() {
output.push('\n');
}
}
output.push_str(&format!("{:016x} <{}>:\n", addr, name));
current_func = Some(name);
total_functions += 1;
func_idx += 1;
}
let mnemonic = x86_mnemonic(inst.inst.opcode);
let mut operand_str = String::new();
for (i, op) in inst.inst.operands.iter().enumerate() {
if i > 0 {
operand_str.push_str(", ");
}
match op {
crate::mc_inst::MCOperand::Reg(r) => {
operand_str.push_str(&format!("%{}", crate::mc_streamer::x86_reg_name(*r)));
}
crate::mc_inst::MCOperand::Imm(v) => {
operand_str.push_str(&format!("${:#x}", v));
}
crate::mc_inst::MCOperand::Expr(e) => {
operand_str.push_str(&format!("{}", e));
}
_ => {}
}
}
let byte_str: String = (0..inst.size)
.map(|i| {
if (inst.address as usize) + i < text_offset + text_size {
format!("{:02x}", data[text_offset + (inst.address as usize) + i])
} else {
"??".into()
}
})
.collect::<Vec<_>>()
.join(" ");
output.push_str(&format!(
" {:4x}: {:20} {}\t{}\n",
inst.address, byte_str, mnemonic, operand_str
));
total_instructions += 1;
}
if !symbols.is_empty() {
output.push_str("\nSYMBOL TABLE:\n");
for sym in &symbols {
let bind = match sym.binding {
0 => "LOCAL",
1 => "GLOBAL",
2 => "WEAK",
_ => "UNKNOWN",
};
let stype = match sym.sym_type {
0 => "NOTYPE",
1 => "OBJECT",
2 => "FUNC",
3 => "SECTION",
4 => "FILE",
_ => "UNKNOWN",
};
output.push_str(&format!(
"{:016x} {} {} {}\n",
sym.value, bind, stype, sym.name
));
}
}
Some(ObjdumpOutput {
text: output,
instructions: total_instructions,
functions: total_functions,
})
}
struct RawSymbol {
name: String,
value: u64,
sym_type: u8,
binding: u8,
}
fn find_text_section(data: &[u8]) -> (usize, usize) {
if data.len() < 64 || &data[0..4] != b"\x7fELF" {
return (0, 0);
}
let shoff = u64::from_le_bytes(data[40..48].try_into().unwrap_or([0; 8])) as usize;
let shentsize = u16::from_le_bytes([data[58], data[59]]) as usize;
let shnum = u16::from_le_bytes([data[60], data[61]]) as usize;
let shstrndx = u16::from_le_bytes([data[62], data[63]]) as usize;
if shoff == 0 || shentsize == 0 || shnum == 0 {
return (0, 0);
}
let strtab_off = shoff + shstrndx * shentsize;
if strtab_off + 0x28 > data.len() {
return (0, 0);
}
let strtab_offset = u64::from_le_bytes(
data[strtab_off + 0x18..strtab_off + 0x20]
.try_into()
.unwrap_or([0; 8]),
) as usize;
let _strtab_size = u64::from_le_bytes(
data[strtab_off + 0x20..strtab_off + 0x28]
.try_into()
.unwrap_or([0; 8]),
) as usize;
for i in 0..shnum {
let secoff = shoff + i * shentsize;
if secoff + 0x28 > data.len() {
break;
}
let sh_name =
u32::from_le_bytes(data[secoff..secoff + 4].try_into().unwrap_or([0; 4])) as usize;
let sh_offset = u64::from_le_bytes(
data[secoff + 0x18..secoff + 0x20]
.try_into()
.unwrap_or([0; 8]),
) as usize;
let sh_size = u64::from_le_bytes(
data[secoff + 0x20..secoff + 0x28]
.try_into()
.unwrap_or([0; 8]),
) as usize;
if strtab_offset + sh_name < data.len() {
let name_end = data[strtab_offset + sh_name..]
.iter()
.position(|&b| b == 0)
.unwrap_or(0);
let name = std::str::from_utf8(
&data[strtab_offset + sh_name..strtab_offset + sh_name + name_end],
)
.unwrap_or("");
if name == ".text" {
return (sh_offset, sh_size);
}
}
}
(0, 0)
}
fn find_symbols(data: &[u8]) -> Vec<RawSymbol> {
let mut symbols = Vec::new();
if data.len() < 64 || &data[0..4] != b"\x7fELF" {
return symbols;
}
let shoff = u64::from_le_bytes(data[40..48].try_into().unwrap_or([0; 8])) as usize;
let shentsize = u16::from_le_bytes([data[58], data[59]]) as usize;
let shnum = u16::from_le_bytes([data[60], data[61]]) as usize;
let shstrndx = u16::from_le_bytes([data[62], data[63]]) as usize;
if shoff == 0 || shentsize == 0 {
return symbols;
}
let mut strtab_off = 0usize;
let mut _strtab_size = 0usize;
let mut symtab_off = 0usize;
let mut symtab_size = 0usize;
let mut symtab_entsize = 0usize;
let mut symtab_link = 0usize;
let strtab_hdr = shoff + shstrndx * shentsize;
if strtab_hdr + 0x28 <= data.len() {
strtab_off = u64::from_le_bytes(
data[strtab_hdr + 0x18..strtab_hdr + 0x20]
.try_into()
.unwrap_or([0; 8]),
) as usize;
_strtab_size = u64::from_le_bytes(
data[strtab_hdr + 0x20..strtab_hdr + 0x28]
.try_into()
.unwrap_or([0; 8]),
) as usize;
}
for i in 0..shnum {
let secoff = shoff + i * shentsize;
if secoff + 0x28 > data.len() {
break;
}
let sh_type = u32::from_le_bytes(data[secoff + 4..secoff + 8].try_into().unwrap_or([0; 4]));
if sh_type == 2 {
symtab_off = u64::from_le_bytes(
data[secoff + 0x18..secoff + 0x20]
.try_into()
.unwrap_or([0; 8]),
) as usize;
symtab_size = u64::from_le_bytes(
data[secoff + 0x20..secoff + 0x28]
.try_into()
.unwrap_or([0; 8]),
) as usize;
symtab_entsize = u64::from_le_bytes(
data[secoff + 0x38..secoff + 0x40]
.try_into()
.unwrap_or([0; 8]),
) as usize;
symtab_link = u32::from_le_bytes(
data[secoff + 0x18..secoff + 0x1c]
.try_into()
.unwrap_or([0; 4]),
) as usize;
}
}
if symtab_off == 0 || symtab_entsize == 0 {
return symbols;
}
let linked_strtab_off = if symtab_link < shnum {
let lsecoff = shoff + symtab_link * shentsize;
if lsecoff + 0x28 <= data.len() {
u64::from_le_bytes(
data[lsecoff + 0x18..lsecoff + 0x20]
.try_into()
.unwrap_or([0; 8]),
) as usize
} else {
strtab_off
}
} else {
strtab_off
};
let count = symtab_size / symtab_entsize;
for i in 0..count.min(1000) {
let symoff = symtab_off + i * symtab_entsize;
if symoff + 24 > data.len() {
break;
}
let st_name =
u32::from_le_bytes(data[symoff..symoff + 4].try_into().unwrap_or([0; 4])) as usize;
let st_info = data[symoff + 4];
let _st_other = data[symoff + 5];
let _st_shndx = u16::from_le_bytes([data[symoff + 6], data[symoff + 7]]);
let st_value =
u64::from_le_bytes(data[symoff + 8..symoff + 16].try_into().unwrap_or([0; 8]));
let _st_size =
u64::from_le_bytes(data[symoff + 16..symoff + 24].try_into().unwrap_or([0; 8]));
let binding = st_info >> 4;
let sym_type = st_info & 0x0f;
let name = if linked_strtab_off + st_name < data.len() {
let end = data[linked_strtab_off + st_name..]
.iter()
.position(|&b| b == 0)
.unwrap_or(0);
std::str::from_utf8(
&data[linked_strtab_off + st_name..linked_strtab_off + st_name + end],
)
.unwrap_or("")
.to_string()
} else {
String::new()
};
if !name.is_empty() {
symbols.push(RawSymbol {
name,
value: st_value,
sym_type,
binding,
});
}
}
symbols
}
#[derive(Debug, Clone)]
pub struct DisassemblyOutput {
pub text: String,
pub instructions: usize,
pub functions: usize,
pub sections: Vec<SectionDisassembly>,
}
impl DisassemblyOutput {
pub fn new() -> Self {
Self {
text: String::new(),
instructions: 0,
functions: 0,
sections: Vec::new(),
}
}
pub fn add_section(&mut self, section: SectionDisassembly) {
self.instructions += section.instructions.len();
self.sections.push(section);
}
pub fn total_bytes(&self) -> u64 {
self.sections.iter().map(|s| s.size).sum()
}
pub fn is_empty(&self) -> bool {
self.sections.is_empty() || self.instructions == 0
}
pub fn to_objdump_output(&self) -> ObjdumpOutput {
ObjdumpOutput {
text: self.text.clone(),
instructions: self.instructions,
functions: self.functions,
}
}
}
impl Default for DisassemblyOutput {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct SectionDisassembly {
pub name: String,
pub size: u64,
pub vma: u64,
pub instructions: Vec<DisassembledLine>,
}
impl SectionDisassembly {
pub fn new(name: &str, size: u64, vma: u64) -> Self {
Self {
name: name.to_string(),
size,
vma,
instructions: Vec::new(),
}
}
pub fn add_line(&mut self, line: DisassembledLine) {
self.instructions.push(line);
}
pub fn instruction_count(&self) -> usize {
self.instructions.len()
}
}
#[derive(Debug, Clone)]
pub struct DisassembledLine {
pub address: u64,
pub bytes: Vec<u8>,
pub mnemonic: String,
pub operands: String,
pub comment: Option<String>,
pub is_label: bool,
pub label: Option<String>,
}
impl DisassembledLine {
pub fn new(address: u64, bytes: Vec<u8>, mnemonic: &str, operands: &str) -> Self {
Self {
address,
bytes,
mnemonic: mnemonic.to_string(),
operands: operands.to_string(),
comment: None,
is_label: false,
label: None,
}
}
pub fn with_comment(mut self, comment: &str) -> Self {
self.comment = Some(comment.to_string());
self
}
pub fn with_label(mut self, label: &str) -> Self {
self.is_label = true;
self.label = Some(label.to_string());
self
}
pub fn format_objdump(&self) -> String {
let byte_str: String = self
.bytes
.iter()
.map(|b| format!("{:02x}", b))
.collect::<Vec<_>>()
.join(" ");
let label_str = if let Some(ref lbl) = self.label {
format!("<{}>:\n", lbl)
} else {
String::new()
};
let comment_str = if let Some(ref cmt) = self.comment {
format!("\t# {}", cmt)
} else {
String::new()
};
format!(
"{}{:4x}: {:20} {}\t{}{}",
label_str, self.address, byte_str, self.mnemonic, self.operands, comment_str
)
}
pub fn size(&self) -> usize {
self.bytes.len()
}
}
pub struct UniversalDisassembler;
impl UniversalDisassembler {
pub fn new() -> Self {
Self
}
pub fn disassemble(&self, data: &[u8], arch: &str, base_addr: u64) -> DisassemblyOutput {
let mut output = DisassemblyOutput::new();
let section = match arch {
"x86_64" | "x86-64" | "amd64" => self.disassemble_x86_64(data, base_addr),
"x86" | "i386" | "i686" => self.disassemble_x86_32(data, base_addr),
_ => {
let mut lines = Vec::new();
for chunk in data.chunks(8) {
let addr = base_addr + (lines.len() * 8) as u64;
let bytes = chunk.to_vec();
lines.push(DisassembledLine {
address: addr,
bytes,
mnemonic: ".byte".to_string(),
operands: chunk
.iter()
.map(|b| format!("{:#04x}", b))
.collect::<Vec<_>>()
.join(", "),
comment: Some(format!("unknown arch: {}", arch)),
is_label: false,
label: None,
});
}
SectionDisassembly {
name: ".text".to_string(),
size: data.len() as u64,
vma: base_addr,
instructions: lines,
}
}
};
output.add_section(section);
output.text = format_disassembly(&output);
output
}
fn disassemble_x86_64(&self, data: &[u8], base_addr: u64) -> SectionDisassembly {
let mut section = SectionDisassembly::new(".text", data.len() as u64, base_addr);
let disassembler = X86Disassembler::new();
let decoded = disassembler.decode_all(data);
for inst in &decoded {
let mnemonic = x86_mnemonic(inst.inst.opcode);
let mut operand_str = String::new();
for (i, op) in inst.inst.operands.iter().enumerate() {
if i > 0 {
operand_str.push_str(", ");
}
match op {
crate::mc_inst::MCOperand::Reg(r) => {
operand_str.push_str(&format!("%{}", crate::mc_streamer::x86_reg_name(*r)));
}
crate::mc_inst::MCOperand::Imm(v) => {
operand_str.push_str(&format!("${:#x}", v));
}
crate::mc_inst::MCOperand::Expr(e) => {
operand_str.push_str(&format!("{}", e));
}
crate::mc_inst::MCOperand::FpImm(v) => {
operand_str.push_str(&format!("{}", v));
}
_ => {}
}
}
let byte_slice = if (inst.address as usize + inst.size) <= data.len() {
data[inst.address as usize..inst.address as usize + inst.size].to_vec()
} else {
vec![]
};
section.add_line(DisassembledLine::new(
inst.address + base_addr,
byte_slice,
&mnemonic,
&operand_str,
));
}
section
}
fn disassemble_x86_32(&self, data: &[u8], base_addr: u64) -> SectionDisassembly {
let mut section = SectionDisassembly::new(".text", data.len() as u64, base_addr);
let disassembler = X86Disassembler::new();
let decoded = disassembler.decode_all(data);
for inst in &decoded {
let mnemonic = x86_mnemonic(inst.inst.opcode);
let mut operand_str = String::new();
for (i, op) in inst.inst.operands.iter().enumerate() {
if i > 0 {
operand_str.push_str(", ");
}
match op {
crate::mc_inst::MCOperand::Reg(r) => {
operand_str.push_str(&format!("%{}", crate::mc_streamer::x86_reg_name(*r)));
}
crate::mc_inst::MCOperand::Imm(v) => {
operand_str.push_str(&format!("${:#x}", v));
}
crate::mc_inst::MCOperand::Expr(e) => {
operand_str.push_str(&format!("{}", e));
}
_ => {}
}
}
let byte_slice = if (inst.address as usize + inst.size) <= data.len() {
data[inst.address as usize..inst.address as usize + inst.size].to_vec()
} else {
vec![]
};
section.add_line(DisassembledLine::new(
inst.address + base_addr,
byte_slice,
&mnemonic,
&operand_str,
));
}
section
}
pub fn disassemble_elf(&self, elf_data: &[u8]) -> Option<DisassemblyOutput> {
let obj = ObjectFile::parse(elf_data)?;
if !obj.is_valid() {
return None;
}
let mut output = DisassemblyOutput::new();
output.text.push_str(&format!(
"\n{}:\tfile format elf64-x86-64\n\n",
obj.machine_name()
));
output.functions = obj.symbols.len();
let (text_offset, text_size) = find_text_section(elf_data);
if text_size > 0 {
let section =
self.disassemble_x86_64(&elf_data[text_offset..text_offset + text_size], 0);
output.add_section(section);
}
for name in &[".init", ".plt", ".fini"] {
let (off, size) = find_section_by_name(elf_data, name);
if size > 0 {
let section = self.disassemble_x86_64(&elf_data[off..off + size], off as u64);
output.add_section(section);
}
}
output.text = format_disassembly(&output);
Some(output)
}
pub fn disassemble_object(&self, data: &[u8]) -> Option<DisassemblyOutput> {
if data.len() >= 4 && &data[0..4] == b"\x7fELF" {
return self.disassemble_elf(data);
}
let output = self.disassemble(data, "x86_64", 0);
if !output.is_empty() {
return Some(output);
}
None
}
pub fn print_objdump_style(output: &DisassemblyOutput) -> String {
format_disassembly(output)
}
pub fn print_section_headers(data: &[u8]) -> Option<String> {
if data.len() < 64 || &data[0..4] != b"\x7fELF" {
return None;
}
let shoff = u64::from_le_bytes(data[40..48].try_into().unwrap_or([0; 8])) as usize;
let shentsize = u16::from_le_bytes([data[58], data[59]]) as usize;
let shnum = u16::from_le_bytes([data[60], data[61]]) as usize;
let shstrndx = u16::from_le_bytes([data[62], data[63]]) as usize;
if shoff == 0 || shentsize == 0 || shnum == 0 {
return None;
}
let strtab_hdr = shoff + shstrndx * shentsize;
if strtab_hdr + 0x28 > data.len() {
return None;
}
let strtab_off = u64::from_le_bytes(
data[strtab_hdr + 0x18..strtab_hdr + 0x20]
.try_into()
.unwrap_or([0; 8]),
) as usize;
let mut out = String::new();
out.push_str("Sections:\n");
out.push_str(&format!(
"Idx {:20} {:>16} {:>16} {:>8} {}\n",
"Name", "Size", "VMA", "Type", "Flags"
));
for i in 0..shnum {
let secoff = shoff + i * shentsize;
if secoff + 0x28 > data.len() {
break;
}
let sh_name =
u32::from_le_bytes(data[secoff..secoff + 4].try_into().unwrap_or([0; 4])) as usize;
let sh_type =
u32::from_le_bytes(data[secoff + 4..secoff + 8].try_into().unwrap_or([0; 4]));
let sh_flags =
u64::from_le_bytes(data[secoff + 8..secoff + 16].try_into().unwrap_or([0; 8]));
let sh_addr = u64::from_le_bytes(
data[secoff + 0x10..secoff + 0x18]
.try_into()
.unwrap_or([0; 8]),
);
let sh_size = u64::from_le_bytes(
data[secoff + 0x20..secoff + 0x28]
.try_into()
.unwrap_or([0; 8]),
);
let name = read_strtab(data, strtab_off, sh_name).unwrap_or("???");
let type_str = match sh_type {
0 => "NULL",
1 => "PROGBITS",
2 => "SYMTAB",
3 => "STRTAB",
4 => "RELA",
5 => "HASH",
6 => "DYNAMIC",
7 => "NOTE",
8 => "NOBITS",
9 => "REL",
11 => "DYNSYM",
_ => "UNKNOWN",
};
let flags_str = format_flags(sh_flags);
out.push_str(&format!(
" {:2} {:20} {:016x} {:016x} {:>8} {}\n",
i, name, sh_size, sh_addr, type_str, flags_str
));
}
Some(out)
}
pub fn print_symbol_table(data: &[u8]) -> Option<String> {
let symbols = find_symbols(data);
if symbols.is_empty() {
return None;
}
let mut out = String::new();
out.push_str("SYMBOL TABLE:\n");
for sym in &symbols {
let bind = match sym.binding {
0 => "LOCAL",
1 => "GLOBAL",
2 => "WEAK",
_ => "UNKNOWN",
};
let stype = match sym.sym_type {
0 => "NOTYPE",
1 => "OBJECT",
2 => "FUNC",
3 => "SECTION",
4 => "FILE",
_ => "UNKNOWN",
};
out.push_str(&format!(
"{:016x} l {} {} {}\n",
sym.value, bind, stype, sym.name
));
}
Some(out)
}
pub fn print_relocations(data: &[u8]) -> Option<String> {
if data.len() < 64 || &data[0..4] != b"\x7fELF" {
return None;
}
let shoff = u64::from_le_bytes(data[40..48].try_into().unwrap_or([0; 8])) as usize;
let shentsize = u16::from_le_bytes([data[58], data[59]]) as usize;
let shnum = u16::from_le_bytes([data[60], data[61]]) as usize;
let shstrndx = u16::from_le_bytes([data[62], data[63]]) as usize;
if shoff == 0 || shentsize == 0 {
return None;
}
let strtab_hdr = shoff + shstrndx * shentsize;
if strtab_hdr + 0x28 > data.len() {
return None;
}
let strtab_off = u64::from_le_bytes(
data[strtab_hdr + 0x18..strtab_hdr + 0x20]
.try_into()
.unwrap_or([0; 8]),
) as usize;
let symtab_off = find_symtab_strtab(data, shoff, shentsize, shnum, strtab_off);
let mut out = String::new();
let mut found_any = false;
for i in 0..shnum {
let secoff = shoff + i * shentsize;
if secoff + 0x28 > data.len() {
break;
}
let sh_type =
u32::from_le_bytes(data[secoff + 4..secoff + 8].try_into().unwrap_or([0; 4]));
if sh_type == 4 {
let sh_name =
u32::from_le_bytes(data[secoff..secoff + 4].try_into().unwrap_or([0; 4]))
as usize;
let sh_offset = u64::from_le_bytes(
data[secoff + 0x18..secoff + 0x20]
.try_into()
.unwrap_or([0; 8]),
) as usize;
let sh_size = u64::from_le_bytes(
data[secoff + 0x20..secoff + 0x28]
.try_into()
.unwrap_or([0; 8]),
) as usize;
let sh_entsize = u64::from_le_bytes(
data[secoff + 0x38..secoff + 0x40]
.try_into()
.unwrap_or([0; 8]),
) as usize;
let name = read_strtab(data, strtab_off, sh_name).unwrap_or("???");
let count = if sh_entsize > 0 {
sh_size / sh_entsize
} else {
0
};
if count > 0 {
if !found_any {
out.push_str("RELOCATION RECORDS:\n");
found_any = true;
}
out.push_str(&format!("\n{}:\n", name));
out.push_str("OFFSET TYPE VALUE\n");
for j in 0..count.min(500) {
let reloff = sh_offset + j * sh_entsize;
if reloff + 24 > data.len() {
break;
}
let r_offset = u64::from_le_bytes(
data[reloff..reloff + 8].try_into().unwrap_or([0; 8]),
);
let r_info = u64::from_le_bytes(
data[reloff + 8..reloff + 16].try_into().unwrap_or([0; 8]),
);
let _r_addend = i64::from_le_bytes(
data[reloff + 16..reloff + 24].try_into().unwrap_or([0; 8]),
);
let rel_type = (r_info & 0xffffffff) as u32;
let sym_idx = (r_info >> 32) as u32;
let type_str = match rel_type {
0 => "R_X86_64_NONE",
1 => "R_X86_64_64",
2 => "R_X86_64_PC32",
3 => "R_X86_64_GOT32",
4 => "R_X86_64_PLT32",
5 => "R_X86_64_COPY",
6 => "R_X86_64_GLOB_DAT",
7 => "R_X86_64_JUMP_SLOT",
8 => "R_X86_64_RELATIVE",
10 => "R_X86_64_32",
11 => "R_X86_64_32S",
_ => "UNKNOWN",
};
let sym_name = read_sym_name(data, symtab_off, sym_idx as usize);
out.push_str(&format!(
"{:016x} {:<16} {}\n",
r_offset, type_str, sym_name
));
}
}
}
}
if found_any {
Some(out)
} else {
None
}
}
pub fn print_dynamic_symbols(data: &[u8]) -> Option<String> {
if data.len() < 64 || &data[0..4] != b"\x7fELF" {
return None;
}
let shoff = u64::from_le_bytes(data[40..48].try_into().unwrap_or([0; 8])) as usize;
let shentsize = u16::from_le_bytes([data[58], data[59]]) as usize;
let shnum = u16::from_le_bytes([data[60], data[61]]) as usize;
if shoff == 0 || shentsize == 0 {
return None;
}
for i in 0..shnum {
let secoff = shoff + i * shentsize;
if secoff + 0x40 > data.len() {
break;
}
let sh_type =
u32::from_le_bytes(data[secoff + 4..secoff + 8].try_into().unwrap_or([0; 4]));
if sh_type == 11 {
let sh_offset = u64::from_le_bytes(
data[secoff + 0x18..secoff + 0x20]
.try_into()
.unwrap_or([0; 8]),
) as usize;
let sh_size = u64::from_le_bytes(
data[secoff + 0x20..secoff + 0x28]
.try_into()
.unwrap_or([0; 8]),
) as usize;
let sh_entsize = u64::from_le_bytes(
data[secoff + 0x38..secoff + 0x40]
.try_into()
.unwrap_or([0; 8]),
) as usize;
let sh_link = u32::from_le_bytes(
data[secoff + 0x18..secoff + 0x1c]
.try_into()
.unwrap_or([0; 4]),
) as usize;
let strtab_off = if sh_link < shnum {
let lsecoff = shoff + sh_link * shentsize;
if lsecoff + 0x28 <= data.len() {
u64::from_le_bytes(
data[lsecoff + 0x18..lsecoff + 0x20]
.try_into()
.unwrap_or([0; 8]),
) as usize
} else {
0
}
} else {
0
};
let count = if sh_entsize > 0 {
sh_size / sh_entsize
} else {
0
};
let mut out = String::new();
out.push_str("DYNAMIC SYMBOL TABLE:\n");
for j in 0..count.min(500) {
let symoff = sh_offset + j * sh_entsize;
if symoff + 24 > data.len() {
break;
}
let st_name =
u32::from_le_bytes(data[symoff..symoff + 4].try_into().unwrap_or([0; 4]))
as usize;
let st_info = data[symoff + 4];
let st_value = u64::from_le_bytes(
data[symoff + 8..symoff + 16].try_into().unwrap_or([0; 8]),
);
let binding = st_info >> 4;
let sym_type = st_info & 0x0f;
let bind_str = match binding {
0 => "LOCAL",
1 => "GLOBAL",
2 => "WEAK",
_ => "UNKNOWN",
};
let type_str = match sym_type {
0 => "NOTYPE",
1 => "OBJECT",
2 => "FUNC",
_ => "UNKNOWN",
};
let name = read_strtab(data, strtab_off, st_name).unwrap_or("");
if !name.is_empty() {
out.push_str(&format!(
"{:016x} {} {} {}\n",
st_value, bind_str, type_str, name
));
}
}
return Some(out);
}
}
None
}
pub fn print_file_header(data: &[u8]) -> Option<String> {
if data.len() < 64 || &data[0..4] != b"\x7fELF" {
return None;
}
let ei_class = data[4];
let ei_data = data[5];
let e_type = u16::from_le_bytes([data[16], data[17]]);
let e_machine = u16::from_le_bytes([data[18], data[19]]);
let e_entry = u64::from_le_bytes(data[24..32].try_into().unwrap_or([0; 8]));
let class_str = match ei_class {
1 => "ELF32",
2 => "ELF64",
_ => "UNKNOWN",
};
let data_str = match ei_data {
1 => "2's complement, little endian",
2 => "2's complement, big endian",
_ => "UNKNOWN",
};
let type_str = match e_type {
0 => "NONE",
1 => "REL (Relocatable file)",
2 => "EXEC (Executable file)",
3 => "DYN (Shared object file)",
4 => "CORE (Core file)",
_ => "UNKNOWN",
};
let machine_str = match e_machine {
0x03 => "Intel 80386",
0x3E => "AMD x86-64",
0x28 => "ARM",
0xF3 => "RISC-V",
_ => "UNKNOWN",
};
let mut out = String::new();
out.push_str("ELF Header:\n");
out.push_str(&format!(
" Class: {}\n",
class_str
));
out.push_str(&format!(
" Data: {}\n",
data_str
));
out.push_str(&format!(
" Type: {}\n",
type_str
));
out.push_str(&format!(
" Machine: {}\n",
machine_str
));
out.push_str(&format!(
" Entry point address: {:#x}\n",
e_entry
));
Some(out)
}
}
impl Default for UniversalDisassembler {
fn default() -> Self {
Self::new()
}
}
fn read_strtab(data: &[u8], strtab_off: usize, offset: usize) -> Option<&str> {
if strtab_off + offset >= data.len() {
return None;
}
let end = data[strtab_off + offset..]
.iter()
.position(|&b| b == 0)
.unwrap_or(0);
std::str::from_utf8(&data[strtab_off + offset..strtab_off + offset + end]).ok()
}
fn find_symtab_strtab(
data: &[u8],
shoff: usize,
shentsize: usize,
shnum: usize,
default_strtab: usize,
) -> usize {
for i in 0..shnum {
let secoff = shoff + i * shentsize;
if secoff + 0x28 > data.len() {
break;
}
let sh_type = u32::from_le_bytes(data[secoff + 4..secoff + 8].try_into().unwrap_or([0; 4]));
if sh_type == 2 {
let sh_link = u32::from_le_bytes(
data[secoff + 0x18..secoff + 0x1c]
.try_into()
.unwrap_or([0; 4]),
) as usize;
if sh_link < shnum {
let lsecoff = shoff + sh_link * shentsize;
if lsecoff + 0x28 <= data.len() {
return u64::from_le_bytes(
data[lsecoff + 0x18..lsecoff + 0x20]
.try_into()
.unwrap_or([0; 8]),
) as usize;
}
}
}
}
default_strtab
}
fn read_sym_name(_data: &[u8], _symtab_strtab: usize, sym_idx: usize) -> String {
let _symoff = sym_idx * 24;
if sym_idx == 0 {
"*UND*".to_string()
} else {
format!("sym.{}", sym_idx)
}
}
fn format_flags(flags: u64) -> String {
let mut parts = Vec::new();
if flags & 0x1 != 0 {
parts.push("W");
}
if flags & 0x2 != 0 {
parts.push("A");
}
if flags & 0x4 != 0 {
parts.push("X");
}
if flags & 0x10 != 0 {
parts.push("M");
}
if flags & 0x20 != 0 {
parts.push("S");
}
if flags & 0x40 != 0 {
parts.push("I");
}
if flags & 0x100 != 0 {
parts.push("L");
}
if parts.is_empty() {
" ".to_string()
} else {
parts.join("")
}
}
fn find_section_by_name(data: &[u8], name: &str) -> (usize, usize) {
if data.len() < 64 || &data[0..4] != b"\x7fELF" {
return (0, 0);
}
let shoff = u64::from_le_bytes(data[40..48].try_into().unwrap_or([0; 8])) as usize;
let shentsize = u16::from_le_bytes([data[58], data[59]]) as usize;
let shnum = u16::from_le_bytes([data[60], data[61]]) as usize;
let shstrndx = u16::from_le_bytes([data[62], data[63]]) as usize;
if shoff == 0 || shentsize == 0 || shnum == 0 {
return (0, 0);
}
let strtab_hdr = shoff + shstrndx * shentsize;
if strtab_hdr + 0x28 > data.len() {
return (0, 0);
}
let strtab_off = u64::from_le_bytes(
data[strtab_hdr + 0x18..strtab_hdr + 0x20]
.try_into()
.unwrap_or([0; 8]),
) as usize;
for i in 0..shnum {
let secoff = shoff + i * shentsize;
if secoff + 0x28 > data.len() {
break;
}
let sh_name =
u32::from_le_bytes(data[secoff..secoff + 4].try_into().unwrap_or([0; 4])) as usize;
let sh_offset = u64::from_le_bytes(
data[secoff + 0x18..secoff + 0x20]
.try_into()
.unwrap_or([0; 8]),
) as usize;
let sh_size = u64::from_le_bytes(
data[secoff + 0x20..secoff + 0x28]
.try_into()
.unwrap_or([0; 8]),
) as usize;
if let Some(sec_name) = read_strtab(data, strtab_off, sh_name) {
if sec_name == name && sh_size > 0 {
return (sh_offset, sh_size);
}
}
}
(0, 0)
}
pub fn format_disassembly(output: &DisassemblyOutput) -> String {
let mut out = String::new();
for section in &output.sections {
out.push_str(&format!("\nDisassembly of section {}:\n\n", section.name));
for line in §ion.instructions {
if line.is_label {
if let Some(ref lbl) = line.label {
out.push_str(&format!("{:016x} <{}>:\n", line.address, lbl));
}
}
let byte_str: String = line
.bytes
.iter()
.map(|b| format!("{:02x}", b))
.collect::<Vec<_>>()
.join(" ");
let comment_str = if let Some(ref cmt) = line.comment {
format!("\t# {}", cmt)
} else {
String::new()
};
out.push_str(&format!(
" {:4x}: {:20} {}\t{}{}\n",
line.address, byte_str, line.mnemonic, line.operands, comment_str
));
}
}
out
}
pub fn dump_section(data: &[u8], offset: usize) -> String {
let mut out = String::new();
const BYTES_PER_LINE: usize = 16;
for (line_idx, chunk) in data.chunks(BYTES_PER_LINE).enumerate() {
let addr = offset + line_idx * BYTES_PER_LINE;
out.push_str(&format!("{:08x} ", addr));
let hex_str: Vec<String> = chunk.iter().map(|b| format!("{:02x}", b)).collect();
let hex_padded = if chunk.len() < BYTES_PER_LINE {
let mut h = hex_str.join(" ");
h.push_str(&" ".repeat(BYTES_PER_LINE - chunk.len()));
h
} else {
hex_str.join(" ")
};
out.push_str(&format!("{:48} ", hex_padded));
out.push_str("|");
for &b in chunk {
if b.is_ascii_graphic() || b == b' ' {
out.push(b as char);
} else {
out.push('.');
}
}
out.push_str("|\n");
}
out
}
pub fn print_symbol_table_detailed(data: &[u8]) -> Option<String> {
let symbols = find_symbols(data);
if symbols.is_empty() {
return None;
}
let mut out = String::new();
out.push_str("SYMBOL TABLE:\n");
out.push_str(&format!(
"{:>16} {:>8} {:>8} {:>8} {:>8} {:>16} {}\n",
"Value", "Size", "Type", "Bind", "Vis", "Section", "Name"
));
for sym in &symbols {
let bind = match sym.binding {
0 => "LOCAL",
1 => "GLOBAL",
2 => "WEAK",
_ => "UNKNOWN",
};
let stype = match sym.sym_type {
0 => "NOTYPE",
1 => "OBJECT",
2 => "FUNC",
3 => "SECTION",
4 => "FILE",
_ => "UNKNOWN",
};
let vis = "DEFAULT"; let section = "*ABS*";
out.push_str(&format!(
"{:016x} {:>8} {:>8} {:>8} {:>8} {:>16} {}\n",
sym.value, 0u64, stype, bind, vis, section, sym.name
));
}
Some(out)
}
pub fn print_relocations_detailed(data: &[u8]) -> Option<String> {
if data.len() < 64 || &data[0..4] != b"\x7fELF" {
return None;
}
let shoff = u64::from_le_bytes(data[40..48].try_into().unwrap_or([0; 8])) as usize;
let shentsize = u16::from_le_bytes([data[58], data[59]]) as usize;
let shnum = u16::from_le_bytes([data[60], data[61]]) as usize;
let shstrndx = u16::from_le_bytes([data[62], data[63]]) as usize;
if shoff == 0 || shentsize == 0 {
return None;
}
let strtab_hdr = shoff + shstrndx * shentsize;
if strtab_hdr + 0x28 > data.len() {
return None;
}
let strtab_off = u64::from_le_bytes(
data[strtab_hdr + 0x18..strtab_hdr + 0x20]
.try_into()
.unwrap_or([0; 8]),
) as usize;
let symtab_off = find_symtab_strtab(data, shoff, shentsize, shnum, strtab_off);
let mut out = String::new();
let mut found_any = false;
for i in 0..shnum {
let secoff = shoff + i * shentsize;
if secoff + 0x40 > data.len() {
break;
}
let sh_type = u32::from_le_bytes(data[secoff + 4..secoff + 8].try_into().unwrap_or([0; 4]));
if sh_type == 4 || sh_type == 9 {
let sh_name =
u32::from_le_bytes(data[secoff..secoff + 4].try_into().unwrap_or([0; 4])) as usize;
let sh_offset = u64::from_le_bytes(
data[secoff + 0x18..secoff + 0x20]
.try_into()
.unwrap_or([0; 8]),
) as usize;
let sh_size = u64::from_le_bytes(
data[secoff + 0x20..secoff + 0x28]
.try_into()
.unwrap_or([0; 8]),
) as usize;
let sh_entsize = u64::from_le_bytes(
data[secoff + 0x38..secoff + 0x40]
.try_into()
.unwrap_or([0; 8]),
) as usize;
let section_name = read_strtab(data, strtab_off, sh_name).unwrap_or("???");
let count = if sh_entsize > 0 {
sh_size / sh_entsize
} else {
0
};
if count > 0 {
if !found_any {
out.push_str("RELOCATION RECORDS FOR [");
found_any = true;
}
out.push_str(&format!("\n{}:\n", section_name));
out.push_str(&format!(
"{:>16} {:>16} {:>24} {:>16} {}\n",
"Offset", "Info", "Type", "Addend", "Symbol"
));
let is_rela = sh_type == 4;
for j in 0..count.min(500) {
let reloff = sh_offset + j * sh_entsize;
if reloff + 24 > data.len() {
break;
}
let r_offset =
u64::from_le_bytes(data[reloff..reloff + 8].try_into().unwrap_or([0; 8]));
let r_info = u64::from_le_bytes(
data[reloff + 8..reloff + 16].try_into().unwrap_or([0; 8]),
);
let r_addend = if is_rela {
i64::from_le_bytes(
data[reloff + 16..reloff + 24].try_into().unwrap_or([0; 8]),
)
} else {
0
};
let rel_type = (r_info & 0xffffffff) as u32;
let sym_idx = (r_info >> 32) as u32;
let type_str = rel_type_name(rel_type);
let sym_name = read_sym_name(data, symtab_off, sym_idx as usize);
out.push_str(&format!(
"{:016x} {:016x} {:>24} {:>16} {}\n",
r_offset,
r_info,
type_str,
if is_rela { r_addend } else { 0 },
sym_name
));
}
}
}
}
if found_any {
Some(out)
} else {
None
}
}
fn rel_type_name(rel_type: u32) -> &'static str {
match rel_type {
0 => "R_X86_64_NONE",
1 => "R_X86_64_64",
2 => "R_X86_64_PC32",
3 => "R_X86_64_GOT32",
4 => "R_X86_64_PLT32",
5 => "R_X86_64_COPY",
6 => "R_X86_64_GLOB_DAT",
7 => "R_X86_64_JUMP_SLOT",
8 => "R_X86_64_RELATIVE",
9 => "R_X86_64_GOTPCREL",
10 => "R_X86_64_32",
11 => "R_X86_64_32S",
12 => "R_X86_64_16",
13 => "R_X86_64_PC16",
14 => "R_X86_64_8",
15 => "R_X86_64_PC8",
16 => "R_X86_64_DTPMOD64",
17 => "R_X86_64_DTPOFF64",
18 => "R_X86_64_TPOFF64",
19 => "R_X86_64_TLSGD",
20 => "R_X86_64_TLSLD",
21 => "R_X86_64_DTPOFF32",
22 => "R_X86_64_GOTTPOFF",
23 => "R_X86_64_TPOFF32",
24 => "R_X86_64_PC64",
25 => "R_X86_64_GOTOFF64",
26 => "R_X86_64_GOTPC32",
27 => "R_X86_64_GOT64",
28 => "R_X86_64_GOTPCREL64",
29 => "R_X86_64_GOTPC64",
30 => "R_X86_64_GOTPLT64",
31 => "R_X86_64_PLTOFF64",
32 => "R_X86_64_SIZE32",
33 => "R_X86_64_SIZE64",
34 => "R_X86_64_GOTPC32_TLSDESC",
35 => "R_X86_64_TLSDESC_CALL",
36 => "R_X86_64_TLSDESC",
37 => "R_X86_64_IRELATIVE",
38 => "R_X86_64_RELATIVE64",
41 => "R_X86_64_GOTPCRELX",
42 => "R_X86_64_REX_GOTPCRELX",
_ => "UNKNOWN",
}
}
pub fn print_dynamic(data: &[u8]) -> Option<String> {
if data.len() < 64 || &data[0..4] != b"\x7fELF" {
return None;
}
let shoff = u64::from_le_bytes(data[40..48].try_into().unwrap_or([0; 8])) as usize;
let shentsize = u16::from_le_bytes([data[58], data[59]]) as usize;
let shnum = u16::from_le_bytes([data[60], data[61]]) as usize;
if shoff == 0 || shentsize == 0 {
return None;
}
for i in 0..shnum {
let secoff = shoff + i * shentsize;
if secoff + 0x40 > data.len() {
break;
}
let sh_type = u32::from_le_bytes(data[secoff + 4..secoff + 8].try_into().unwrap_or([0; 4]));
if sh_type == 6 {
let sh_offset = u64::from_le_bytes(
data[secoff + 0x18..secoff + 0x20]
.try_into()
.unwrap_or([0; 8]),
) as usize;
let sh_size = u64::from_le_bytes(
data[secoff + 0x20..secoff + 0x28]
.try_into()
.unwrap_or([0; 8]),
) as usize;
let mut out = String::new();
out.push_str("\nDynamic Section:\n");
let mut pos = sh_offset;
while pos + 16 <= sh_offset + sh_size && pos + 16 <= data.len() {
let d_tag = i64::from_le_bytes(data[pos..pos + 8].try_into().unwrap_or([0; 8]));
let d_val =
u64::from_le_bytes(data[pos + 8..pos + 16].try_into().unwrap_or([0; 8]));
pos += 16;
if d_tag == 0 {
break;
}
let tag_name = dynamic_tag_name(d_tag);
out.push_str(&format!(" {:<24} 0x{:016x}\n", tag_name, d_val));
}
return Some(out);
}
}
None
}
fn dynamic_tag_name(tag: i64) -> String {
match tag {
0 => "DT_NULL".to_string(),
1 => "DT_NEEDED".to_string(),
2 => "DT_PLTRELSZ".to_string(),
3 => "DT_PLTGOT".to_string(),
4 => "DT_HASH".to_string(),
5 => "DT_STRTAB".to_string(),
6 => "DT_SYMTAB".to_string(),
7 => "DT_RELA".to_string(),
8 => "DT_RELASZ".to_string(),
9 => "DT_RELAENT".to_string(),
10 => "DT_STRSZ".to_string(),
11 => "DT_SYMENT".to_string(),
12 => "DT_INIT".to_string(),
13 => "DT_FINI".to_string(),
14 => "DT_SONAME".to_string(),
15 => "DT_RPATH".to_string(),
16 => "DT_SYMBOLIC".to_string(),
17 => "DT_REL".to_string(),
18 => "DT_RELSZ".to_string(),
19 => "DT_RELENT".to_string(),
20 => "DT_PLTREL".to_string(),
21 => "DT_DEBUG".to_string(),
22 => "DT_TEXTREL".to_string(),
23 => "DT_JMPREL".to_string(),
24 => "DT_BIND_NOW".to_string(),
25 => "DT_INIT_ARRAY".to_string(),
26 => "DT_FINI_ARRAY".to_string(),
27 => "DT_INIT_ARRAYSZ".to_string(),
28 => "DT_FINI_ARRAYSZ".to_string(),
29 => "DT_RUNPATH".to_string(),
30 => "DT_FLAGS".to_string(),
32 => "DT_PREINIT_ARRAY".to_string(),
33 => "DT_PREINIT_ARRAYSZ".to_string(),
0x6ffffef5 => "DT_GNU_HASH".to_string(),
0x6ffffff0 => "DT_VERSYM".to_string(),
0x6ffffffc => "DT_VERNEED".to_string(),
0x6ffffffe => "DT_VERNEEDNUM".to_string(),
_ => format!("0x{:x}", tag),
}
}
pub fn print_program_headers(data: &[u8]) -> Option<String> {
if data.len() < 64 || &data[0..4] != b"\x7fELF" {
return None;
}
let is_64bit = data[4] == 2;
let phoff = if is_64bit {
u64::from_le_bytes(data[32..40].try_into().unwrap_or([0; 8])) as usize
} else {
u32::from_le_bytes(data[28..32].try_into().unwrap_or([0; 4])) as usize
};
let phentsize = u16::from_le_bytes(if is_64bit {
[data[54], data[55]]
} else {
[data[42], data[43]]
}) as usize;
let phnum = u16::from_le_bytes(if is_64bit {
[data[56], data[57]]
} else {
[data[44], data[45]]
}) as usize;
if phoff == 0 || phentsize == 0 || phnum == 0 {
return None;
}
let mut out = String::new();
out.push_str("\nProgram Headers:\n");
out.push_str(&format!(
" {:16} {:>12} {:>16} {:>16} {:>12} {:>12} {:>6} {:>8}\n",
"Type", "Offset", "VirtAddr", "PhysAddr", "FileSiz", "MemSiz", "Flg", "Align"
));
for i in 0..phnum {
let phoff_i = phoff + i * phentsize;
if phoff_i + phentsize > data.len() {
break;
}
let (p_type, p_offset, p_vaddr, p_paddr, p_filesz, p_memsz, p_flags, p_align) = if is_64bit
{
(
u32::from_le_bytes(data[phoff_i..phoff_i + 4].try_into().unwrap_or([0; 4])),
u64::from_le_bytes(data[phoff_i + 8..phoff_i + 16].try_into().unwrap_or([0; 8])),
u64::from_le_bytes(
data[phoff_i + 16..phoff_i + 24]
.try_into()
.unwrap_or([0; 8]),
),
u64::from_le_bytes(
data[phoff_i + 24..phoff_i + 32]
.try_into()
.unwrap_or([0; 8]),
),
u64::from_le_bytes(
data[phoff_i + 32..phoff_i + 40]
.try_into()
.unwrap_or([0; 8]),
),
u64::from_le_bytes(
data[phoff_i + 40..phoff_i + 48]
.try_into()
.unwrap_or([0; 8]),
),
u32::from_le_bytes(data[phoff_i + 4..phoff_i + 8].try_into().unwrap_or([0; 4])),
u64::from_le_bytes(
data[phoff_i + 48..phoff_i + 56]
.try_into()
.unwrap_or([0; 8]),
),
)
} else {
(
u32::from_le_bytes(data[phoff_i..phoff_i + 4].try_into().unwrap_or([0; 4])),
u32::from_le_bytes(data[phoff_i + 4..phoff_i + 8].try_into().unwrap_or([0; 4]))
as u64,
u32::from_le_bytes(data[phoff_i + 8..phoff_i + 12].try_into().unwrap_or([0; 4]))
as u64,
u32::from_le_bytes(
data[phoff_i + 12..phoff_i + 16]
.try_into()
.unwrap_or([0; 4]),
) as u64,
u32::from_le_bytes(
data[phoff_i + 16..phoff_i + 20]
.try_into()
.unwrap_or([0; 4]),
) as u64,
u32::from_le_bytes(
data[phoff_i + 20..phoff_i + 24]
.try_into()
.unwrap_or([0; 4]),
) as u64,
u32::from_le_bytes(
data[phoff_i + 24..phoff_i + 28]
.try_into()
.unwrap_or([0; 4]),
),
u32::from_le_bytes(
data[phoff_i + 28..phoff_i + 32]
.try_into()
.unwrap_or([0; 4]),
) as u64,
)
};
let type_str = phdr_type_name(p_type);
let flags_str = phdr_flags_str(p_flags);
out.push_str(&format!(
" {:16} 0x{:010x} 0x{:014x} 0x{:014x} 0x{:010x} 0x{:010x} {:>6} 0x{:08x}\n",
type_str, p_offset, p_vaddr, p_paddr, p_filesz, p_memsz, flags_str, p_align
));
}
Some(out)
}
fn phdr_type_name(p_type: u32) -> &'static str {
match p_type {
0 => "PHDR",
1 => "LOAD",
2 => "DYNAMIC",
3 => "INTERP",
4 => "NOTE",
5 => "SHLIB",
6 => "PHDR",
0x6474e550 => "GNU_EH_FRAME",
0x6474e551 => "GNU_STACK",
0x6474e552 => "GNU_RELRO",
0x6474e553 => "GNU_PROPERTY",
_ => "UNKNOWN",
}
}
fn phdr_flags_str(flags: u32) -> String {
let mut s = String::new();
if flags & 4 != 0 {
s.push('R');
}
if flags & 2 != 0 {
s.push('W');
}
if flags & 1 != 0 {
s.push('E');
}
if s.is_empty() {
s.push_str("---");
}
s
}
pub fn print_section_headers_detailed(data: &[u8]) -> Option<String> {
if data.len() < 64 || &data[0..4] != b"\x7fELF" {
return None;
}
let shoff = u64::from_le_bytes(data[40..48].try_into().unwrap_or([0; 8])) as usize;
let shentsize = u16::from_le_bytes([data[58], data[59]]) as usize;
let shnum = u16::from_le_bytes([data[60], data[61]]) as usize;
let shstrndx = u16::from_le_bytes([data[62], data[63]]) as usize;
if shoff == 0 || shentsize == 0 || shnum == 0 {
return None;
}
let strtab_hdr = shoff + shstrndx * shentsize;
if strtab_hdr + 0x40 > data.len() {
return None;
}
let strtab_off = u64::from_le_bytes(
data[strtab_hdr + 0x18..strtab_hdr + 0x20]
.try_into()
.unwrap_or([0; 8]),
) as usize;
let mut out = String::new();
out.push_str("Section Headers:\n");
out.push_str(&format!(
" [Nr] {:20} {:>12} {:>16} {:>16} {:>8} {:>8} {:>4} {:>4} {:>4} {:>8}\n",
"Name", "Type", "Address", "Offset", "Size", "EntSize", "Flg", "Lk", "Inf", "Al"
));
for i in 0..shnum {
let secoff = shoff + i * shentsize;
if secoff + 0x40 > data.len() {
break;
}
let sh_name =
u32::from_le_bytes(data[secoff..secoff + 4].try_into().unwrap_or([0; 4])) as usize;
let sh_type = u32::from_le_bytes(data[secoff + 4..secoff + 8].try_into().unwrap_or([0; 4]));
let sh_flags =
u64::from_le_bytes(data[secoff + 8..secoff + 16].try_into().unwrap_or([0; 8]));
let sh_addr = u64::from_le_bytes(
data[secoff + 0x10..secoff + 0x18]
.try_into()
.unwrap_or([0; 8]),
);
let sh_offset = u64::from_le_bytes(
data[secoff + 0x18..secoff + 0x20]
.try_into()
.unwrap_or([0; 8]),
);
let sh_size = u64::from_le_bytes(
data[secoff + 0x20..secoff + 0x28]
.try_into()
.unwrap_or([0; 8]),
);
let sh_link = u32::from_le_bytes(
data[secoff + 0x28..secoff + 0x2c]
.try_into()
.unwrap_or([0; 4]),
);
let sh_info = u32::from_le_bytes(
data[secoff + 0x2c..secoff + 0x30]
.try_into()
.unwrap_or([0; 4]),
);
let sh_addralign = u64::from_le_bytes(
data[secoff + 0x30..secoff + 0x38]
.try_into()
.unwrap_or([0; 8]),
);
let sh_entsize = u64::from_le_bytes(
data[secoff + 0x38..secoff + 0x40]
.try_into()
.unwrap_or([0; 8]),
);
let name = read_strtab(data, strtab_off, sh_name).unwrap_or("???");
let type_str = sh_type_name(sh_type);
let flags_str = format_flags(sh_flags);
out.push_str(&format!(
" [{:2}] {:20} {:>12} {:016x} {:016x} {:08x} {:08x} {:>4} {:>4} {:>4} {:08x}\n",
i,
name,
type_str,
sh_addr,
sh_offset,
sh_size,
sh_entsize,
flags_str,
sh_link,
sh_info,
sh_addralign
));
}
out.push_str(&format!(
"Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings), I (info),\n\
L (link order), O (extra OS processing required), G (group), T (TLS),\n\
C (compressed), x (unknown), o (OS specific), E (exclude),\n\
D (mbind), l (large), p (processor specific)\n"
));
Some(out)
}
fn sh_type_name(sh_type: u32) -> &'static str {
match sh_type {
0 => "NULL",
1 => "PROGBITS",
2 => "SYMTAB",
3 => "STRTAB",
4 => "RELA",
5 => "HASH",
6 => "DYNAMIC",
7 => "NOTE",
8 => "NOBITS",
9 => "REL",
10 => "SHLIB",
11 => "DYNSYM",
14 => "INIT_ARRAY",
15 => "FINI_ARRAY",
16 => "PREINIT_ARRAY",
17 => "GROUP",
18 => "SYMTAB_SHNDX",
0x6ffffff6 => "GNU_HASH",
0x6ffffffd => "GNU_verdef",
0x6ffffffe => "GNU_verneed",
0x6fffffff => "GNU_versym",
_ => "UNKNOWN",
}
}
pub fn print_notes(data: &[u8]) -> Option<String> {
if data.len() < 64 || &data[0..4] != b"\x7fELF" {
return None;
}
let shoff = u64::from_le_bytes(data[40..48].try_into().unwrap_or([0; 8])) as usize;
let shentsize = u16::from_le_bytes([data[58], data[59]]) as usize;
let shnum = u16::from_le_bytes([data[60], data[61]]) as usize;
if shoff == 0 || shentsize == 0 {
return None;
}
let mut out = String::new();
let mut found_any = false;
for i in 0..shnum {
let secoff = shoff + i * shentsize;
if secoff + 0x28 > data.len() {
break;
}
let sh_type = u32::from_le_bytes(data[secoff + 4..secoff + 8].try_into().unwrap_or([0; 4]));
if sh_type == 7 {
let sh_offset = u64::from_le_bytes(
data[secoff + 0x18..secoff + 0x20]
.try_into()
.unwrap_or([0; 8]),
) as usize;
let sh_size = u64::from_le_bytes(
data[secoff + 0x20..secoff + 0x28]
.try_into()
.unwrap_or([0; 8]),
) as usize;
if !found_any {
out.push_str("\nNotes:\n");
found_any = true;
}
let mut pos = sh_offset;
while pos + 12 <= sh_offset + sh_size && pos + 12 <= data.len() {
let namesz =
u32::from_le_bytes(data[pos..pos + 4].try_into().unwrap_or([0; 4])) as usize;
let descsz = u32::from_le_bytes(data[pos + 4..pos + 8].try_into().unwrap_or([0; 4]))
as usize;
let note_type =
u32::from_le_bytes(data[pos + 8..pos + 12].try_into().unwrap_or([0; 4]));
pos += 12;
let name_end = pos + namesz;
let name_bytes = if name_end <= data.len() {
&data[pos..name_end]
} else {
break;
};
let name = String::from_utf8_lossy(
&name_bytes[..name_bytes
.iter()
.position(|&b| b == 0)
.unwrap_or(name_bytes.len())],
);
pos = (name_end + 3) & !3;
let desc_end = pos + descsz;
let desc = if desc_end <= data.len() {
&data[pos..desc_end]
} else {
break;
};
pos = (desc_end + 3) & !3;
let type_str = if name == "GNU" {
match note_type {
1 => "NT_GNU_ABI_TAG",
2 => "NT_GNU_HWCAP",
3 => "NT_GNU_BUILD_ID",
4 => "NT_GNU_GOLD_VERSION",
5 => "NT_GNU_PROPERTY_TYPE_0",
_ => "NT_GNU_UNKNOWN",
}
} else {
"NT_UNKNOWN"
};
out.push_str(&format!(
" Owner: {} Data size: 0x{:x} Type: {} ({})\n",
name, descsz, type_str, note_type
));
if !desc.is_empty() && desc.len() <= 64 {
let hex_desc: Vec<String> = desc.iter().map(|b| format!("{:02x}", b)).collect();
out.push_str(&format!(" description data: {}\n", hex_desc.join(" ")));
}
}
}
}
if found_any {
Some(out)
} else {
None
}
}
pub fn print_archive(_data: &[u8]) -> Option<String> {
Some("Archive display: not yet implemented\n".to_string())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ObjectFormat {
ELF32,
ELF64,
MachO32,
MachO64,
COFF,
Wasm,
Archive,
Raw,
Unknown,
}
pub fn detect_object_format(data: &[u8]) -> ObjectFormat {
if data.len() < 4 {
return ObjectFormat::Unknown;
}
if &data[0..4] == b"\x7fELF" {
if data.len() >= 5 {
match data[4] {
2 => return ObjectFormat::ELF64,
1 => return ObjectFormat::ELF32,
_ => {}
}
}
return ObjectFormat::ELF64;
}
if data.len() >= 4 {
let magic = u32::from_le_bytes(data[0..4].try_into().unwrap_or([0; 4]));
match magic {
0xFEEDFACE => return ObjectFormat::MachO32,
0xFEEDFACF => return ObjectFormat::MachO64,
0xCEFAEDFE | 0xCFFAEDFE => return ObjectFormat::MachO32,
_ => {}
}
}
if data.len() >= 2 {
let pe_magic = u16::from_le_bytes(data[0..2].try_into().unwrap_or([0; 2]));
if pe_magic == 0x8664 || pe_magic == 0x014C {
return ObjectFormat::COFF;
}
}
if &data[0..4] == b"\x00asm" {
return ObjectFormat::Wasm;
}
if data.len() >= 8 && &data[0..8] == b"!<arch>\n" {
return ObjectFormat::Archive;
}
ObjectFormat::Raw
}
pub fn object_format_name(fmt: ObjectFormat) -> &'static str {
match fmt {
ObjectFormat::ELF32 => "elf32",
ObjectFormat::ELF64 => "elf64",
ObjectFormat::MachO32 => "mach-o-32",
ObjectFormat::MachO64 => "mach-o-64",
ObjectFormat::COFF => "coff",
ObjectFormat::Wasm => "wasm",
ObjectFormat::Archive => "archive",
ObjectFormat::Raw => "raw",
ObjectFormat::Unknown => "unknown",
}
}
pub fn detect_elf_machine(data: &[u8]) -> Option<&'static str> {
if data.len() < 20 || &data[0..4] != b"\x7fELF" {
return None;
}
let e_machine = u16::from_le_bytes([data[18], data[19]]);
Some(match e_machine {
0x03 => "i386",
0x3E => "x86-64",
0x28 => "ARM",
0xB7 => "AArch64",
0xF3 => "RISC-V",
0x14 => "PowerPC",
0x15 => "PowerPC64",
0x08 => "MIPS",
0x2A => "SuperH",
0x16 => "S390",
0xEB => "ARC",
0x5E => "Xtensa",
0xF7 => "BPF",
0x12 => "SPARC",
_ => "unknown",
})
}
pub fn format_att_line(
address: u64,
bytes: &[u8],
mnemonic: &str,
operands: &str,
comment: Option<&str>,
) -> String {
let byte_str: String = bytes
.iter()
.map(|b| format!("{:02x}", b))
.collect::<Vec<_>>()
.join(" ");
let comment_str = if let Some(c) = comment {
format!(" # {}", c)
} else {
String::new()
};
format!(
" {:4x}: {:20} {}\t{}{}\n",
address, byte_str, mnemonic, operands, comment_str
)
}
pub fn format_intel_line(
address: u64,
bytes: &[u8],
mnemonic: &str,
operands: &str,
comment: Option<&str>,
) -> String {
let byte_str: String = bytes
.iter()
.map(|b| format!("{:02x}", b))
.collect::<Vec<_>>()
.join(" ");
let comment_str = if let Some(c) = comment {
format!(" # {}", c)
} else {
String::new()
};
format!(
" {:4x}: {:20} {:<8}{}{}\n",
address, byte_str, mnemonic, operands, comment_str
)
}
pub fn att_to_intel_operand(operand: &str) -> String {
let mut result = operand.to_string();
if result.starts_with('%') {
result = result[1..].to_string();
}
if result.starts_with('$') {
result = result[1..].to_string();
}
result = result.replace('(', "[").replace(')', "]");
result
}
pub fn disassembly_summary(output: &DisassemblyOutput) -> String {
let mut out = String::new();
out.push_str(&format!("Sections: {}\n", output.sections.len()));
let total_insts: usize = output.sections.iter().map(|s| s.instruction_count()).sum();
let total_bytes: u64 = output.total_bytes();
out.push_str(&format!(
"Total instructions: {}, Total bytes: {}\n",
total_insts, total_bytes
));
out
}
pub fn filter_by_address_range(
output: &DisassemblyOutput,
start: u64,
end: u64,
) -> DisassemblyOutput {
let mut filtered = DisassemblyOutput::new();
filtered.text = output.text.clone();
filtered.functions = output.functions;
for section in &output.sections {
let mut filtered_section =
SectionDisassembly::new(§ion.name, section.size, section.vma);
for line in §ion.instructions {
if line.address >= start && line.address < end {
filtered_section.add_line(line.clone());
}
}
filtered.add_section(filtered_section);
}
filtered
}
#[derive(Debug, Clone)]
pub struct ObjdumpConfig {
pub disassemble_all: bool,
pub disassemble: bool,
pub full_contents: bool,
pub symtab: bool,
pub reloc: bool,
pub section_headers: bool,
pub all_headers: bool,
pub private_headers: bool,
pub dynamic_syms: bool,
pub dynamic_reloc: bool,
pub notes: bool,
pub file_headers: bool,
pub archive_headers: bool,
pub start_address: Option<u64>,
pub stop_address: Option<u64>,
pub adjust_vma: i64,
pub show_raw_insn: bool,
pub print_imm_hex: bool,
pub intel_syntax: bool,
pub demangle: bool,
pub zero_as_base: bool,
pub prefix: String,
pub addr_width: usize,
}
impl Default for ObjdumpConfig {
fn default() -> Self {
Self {
disassemble_all: false,
disassemble: true,
full_contents: false,
symtab: false,
reloc: false,
section_headers: false,
all_headers: false,
private_headers: false,
dynamic_syms: false,
dynamic_reloc: false,
notes: false,
file_headers: false,
archive_headers: false,
start_address: None,
stop_address: None,
adjust_vma: 0,
show_raw_insn: true,
print_imm_hex: true,
intel_syntax: false,
demangle: true,
zero_as_base: false,
prefix: String::new(),
addr_width: 16,
}
}
}
impl ObjdumpConfig {
pub fn from_args(args: &[String]) -> Self {
let mut config = ObjdumpConfig::default();
let mut i = 0;
while i < args.len() {
match args[i].as_str() {
"-d" | "--disassemble" => {
config.disassemble = true;
}
"-D" | "--disassemble-all" => {
config.disassemble_all = true;
config.disassemble = false;
}
"-s" | "--full-contents" => {
config.full_contents = true;
}
"-t" | "--syms" => {
config.symtab = true;
}
"-r" | "--reloc" => {
config.reloc = true;
}
"-h" | "--section-headers" | "--headers" => {
config.section_headers = true;
}
"-x" | "--all-headers" => {
config.all_headers = true;
}
"-p" | "--private-headers" => {
config.private_headers = true;
}
"-R" | "--dynamic-reloc" => {
config.dynamic_reloc = true;
}
"-T" | "--dynamic-syms" => {
config.dynamic_syms = true;
}
"-f" | "--file-headers" => {
config.file_headers = true;
}
"-a" | "--archive-headers" => {
config.archive_headers = true;
}
"-n" | "--notes" => {
config.notes = true;
}
"-M" | "--intel-syntax" => {
config.intel_syntax = true;
}
"-C" | "--demangle" => {
config.demangle = true;
}
"--no-show-raw-insn" => {
config.show_raw_insn = false;
}
"-z" | "--zero-as-base" => {
config.zero_as_base = true;
}
s if s.starts_with("--start-address=") => {
if let Ok(addr) = u64::from_str_radix(&s[16..], 16) {
config.start_address = Some(addr);
}
}
s if s.starts_with("--stop-address=") => {
if let Ok(addr) = u64::from_str_radix(&s[15..], 16) {
config.stop_address = Some(addr);
}
}
s if s.starts_with("--adjust-vma=") => {
if let Ok(offset) = i64::from_str_radix(&s[13..], 10) {
config.adjust_vma = offset;
}
}
s if s.starts_with("--prefix=") => {
config.prefix = s[9..].to_string();
}
s if s.starts_with("--prefix-strip=") => {
let _prefix = &s[15..];
}
_ => {}
}
i += 1;
}
config
}
pub fn process(&self, data: &[u8]) -> String {
let mut out = String::new();
if self.file_headers || self.all_headers {
if let Some(hdr) = UniversalDisassembler::print_file_header(data) {
out.push_str(&hdr);
}
}
if self.section_headers || self.all_headers {
if let Some(secs) = print_section_headers_detailed(data) {
out.push_str(&secs);
}
}
if self.private_headers || self.all_headers {
if let Some(phdrs) = print_program_headers(data) {
out.push_str(&phdrs);
}
}
if self.symtab || self.all_headers {
if let Some(syms) = print_symbol_table_detailed(data) {
out.push_str(&format!("\n{}", syms));
}
}
if self.dynamic_syms {
if let Some(dynsyms) = UniversalDisassembler::print_dynamic_symbols(data) {
out.push_str(&format!("\n{}", dynsyms));
}
}
if self.reloc || self.all_headers {
if let Some(relocs) = print_relocations_detailed(data) {
out.push_str(&format!("\n{}", relocs));
}
}
if self.dynamic_reloc {
if let Some(dynamic) = print_dynamic(data) {
out.push_str(&dynamic);
}
}
if self.notes {
if let Some(notes) = print_notes(data) {
out.push_str(¬es);
}
}
if self.disassemble || self.disassemble_all {
let dis = UniversalDisassembler::new();
if let Some(obj) = dis.disassemble_elf(data) {
out.push_str(&format_disassembly(&obj));
}
}
if self.full_contents {
if let (text_offset, text_size) = find_text_section(data) {
if text_size > 0 {
out.push_str(&format!(
"\nContents of section .text:\n{}",
dump_section(&data[text_offset..text_offset + text_size], text_offset)
));
}
}
}
out
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::context::LLVMContext;
use crate::function;
use crate::instruction;
use crate::object_emitter;
fn build_and_compile() -> Vec<u8> {
let mut ctx = LLVMContext::new();
let func = function::new_function("test", ctx.void_ty(), &[]);
let entry = crate::basic_block::new_basic_block("entry");
entry.borrow_mut().push_operand(instruction::ret_void());
func.borrow_mut().push_operand(entry);
object_emitter::compile_to_object(&[&func], "x86_64-unknown-linux-gnu")
}
#[test]
fn test_objdump_empty_object() {
let obj = build_and_compile();
let result = disassemble_object(&obj);
assert!(result.is_some());
let output = result.unwrap();
assert!(output.text.contains(".text") || output.instructions > 0);
}
#[test]
fn test_objdump_has_instructions() {
let obj = build_and_compile();
let result = disassemble_object(&obj).unwrap();
assert!(
result.instructions > 0 || result.functions > 0,
"Should have at least one instruction or function"
);
}
#[test]
fn test_objdump_finds_text_section() {
let obj = build_and_compile();
let result = disassemble_object(&obj).unwrap();
assert!(
result.text.contains(".text"),
"Should reference .text section"
);
}
#[test]
fn test_objdump_invalid_elf() {
let data = vec![0u8; 100];
assert!(disassemble_object(&data).is_none());
}
#[test]
fn test_disassembly_output_new() {
let output = DisassemblyOutput::new();
assert!(output.is_empty());
assert_eq!(output.instructions, 0);
assert_eq!(output.functions, 0);
assert_eq!(output.total_bytes(), 0);
}
#[test]
fn test_disassembly_output_add_section() {
let mut output = DisassemblyOutput::new();
let section = SectionDisassembly::new(".text", 100, 0x400000);
output.add_section(section);
assert!(!output.is_empty());
assert_eq!(output.total_bytes(), 100);
}
#[test]
fn test_disassembly_output_to_objdump() {
let mut output = DisassemblyOutput::new();
output.text = "test output".to_string();
output.instructions = 5;
output.functions = 1;
let old = output.to_objdump_output();
assert_eq!(old.text, "test output");
assert_eq!(old.instructions, 5);
assert_eq!(old.functions, 1);
}
#[test]
fn test_section_disassembly_new() {
let section = SectionDisassembly::new(".text", 256, 0x1000);
assert_eq!(section.name, ".text");
assert_eq!(section.size, 256);
assert_eq!(section.vma, 0x1000);
assert_eq!(section.instruction_count(), 0);
}
#[test]
fn test_section_disassembly_add_line() {
let mut section = SectionDisassembly::new(".text", 10, 0);
let line = DisassembledLine::new(0, vec![0x90], "nop", "");
section.add_line(line);
assert_eq!(section.instruction_count(), 1);
}
#[test]
fn test_disassembled_line_new() {
let line = DisassembledLine::new(0x1000, vec![0x90], "nop", "");
assert_eq!(line.address, 0x1000);
assert_eq!(line.bytes, vec![0x90]);
assert_eq!(line.mnemonic, "nop");
assert_eq!(line.size(), 1);
assert!(!line.is_label);
}
#[test]
fn test_disassembled_line_with_comment() {
let line = DisassembledLine::new(0, vec![0x90, 0x90], "nop", "").with_comment("2-byte NOP");
assert_eq!(line.comment, Some("2-byte NOP".to_string()));
}
#[test]
fn test_disassembled_line_with_label() {
let line = DisassembledLine::new(0x4000, vec![0x55], "push", "%rbp").with_label("main");
assert!(line.is_label);
assert_eq!(line.label, Some("main".to_string()));
}
#[test]
fn test_disassembled_line_format() {
let line = DisassembledLine::new(0x10, vec![0x90], "nop", "");
let formatted = line.format_objdump();
assert!(formatted.contains("10:"));
assert!(formatted.contains("90"));
assert!(formatted.contains("nop"));
}
#[test]
fn test_universal_disassembler_create() {
let dis = UniversalDisassembler::new();
let _ = dis; }
#[test]
fn test_universal_disassembler_unknown_arch() {
let dis = UniversalDisassembler::new();
let output = dis.disassemble(&[0x90, 0x90, 0x90], "mips", 0x1000);
assert_eq!(output.sections.len(), 1);
assert!(output.sections[0].instructions[0]
.mnemonic
.contains(".byte"));
}
#[test]
fn test_universal_disassembler_x86_64() {
let dis = UniversalDisassembler::new();
let data = vec![0x90, 0xC3]; let output = dis.disassemble(&data, "x86_64", 0x400000);
assert_eq!(output.sections.len(), 1);
assert!(output.instructions > 0);
}
#[test]
fn test_universal_disassembler_disassemble_elf() {
let obj = build_and_compile();
let dis = UniversalDisassembler::new();
let result = dis.disassemble_elf(&obj);
assert!(result.is_some());
let output = result.unwrap();
assert!(output.functions > 0 || output.instructions > 0);
}
#[test]
fn test_universal_disassembler_disassemble_object() {
let obj = build_and_compile();
let dis = UniversalDisassembler::new();
let result = dis.disassemble_object(&obj);
assert!(result.is_some());
assert!(!result.unwrap().is_empty());
}
#[test]
fn test_print_objdump_style() {
let mut output = DisassemblyOutput::new();
let mut section = SectionDisassembly::new(".text", 10, 0);
section.add_line(DisassembledLine::new(0, vec![0x90], "nop", ""));
output.add_section(section);
let formatted = UniversalDisassembler::print_objdump_style(&output);
assert!(formatted.contains(".text"));
assert!(formatted.contains("nop"));
}
#[test]
fn test_print_section_headers() {
let obj = build_and_compile();
let result = UniversalDisassembler::print_section_headers(&obj);
assert!(result.is_some());
let headers = result.unwrap();
assert!(headers.contains("Sections"));
assert!(headers.contains(".text"));
}
#[test]
fn test_print_symbol_table() {
let obj = build_and_compile();
let result = UniversalDisassembler::print_symbol_table(&obj);
if let Some(symtab) = result {
assert!(symtab.contains("SYMBOL TABLE"));
}
}
#[test]
fn test_print_relocations() {
let obj = build_and_compile();
let result = UniversalDisassembler::print_relocations(&obj);
if let Some(relocs) = result {
assert!(relocs.contains("RELOCATION") || relocs.contains("OFFSET"));
}
}
#[test]
fn test_print_file_header() {
let obj = build_and_compile();
let result = UniversalDisassembler::print_file_header(&obj);
assert!(result.is_some());
let header = result.unwrap();
assert!(header.contains("ELF Header"));
assert!(header.contains("AMD x86-64"));
}
#[test]
fn test_format_disassembly() {
let mut output = DisassemblyOutput::new();
let mut section = SectionDisassembly::new(".init", 2, 0x1000);
section.add_line(DisassembledLine::new(0x1000, vec![0x90], "nop", ""));
section
.add_line(DisassembledLine::new(0x1001, vec![0xC3], "ret", "").with_comment("return"));
output.add_section(section);
let formatted = format_disassembly(&output);
assert!(formatted.contains(".init"));
assert!(formatted.contains("nop"));
assert!(formatted.contains("ret"));
assert!(formatted.contains("return"));
}
#[test]
fn test_format_disassembly_with_label() {
let mut output = DisassemblyOutput::new();
let mut section = SectionDisassembly::new(".text", 1, 0);
section.add_line(DisassembledLine::new(0, vec![0x55], "push", "%rbp").with_label("main"));
output.add_section(section);
let formatted = format_disassembly(&output);
assert!(formatted.contains("<main>"));
}
#[test]
fn test_disassembled_line_no_bytes() {
let line = DisassembledLine::new(0x100, vec![], ".byte", "");
assert_eq!(line.size(), 0);
let formatted = line.format_objdump();
assert!(formatted.contains("100:"));
}
}