use serde::Serialize;
use crate::error::Error;
use crate::gobin::{Arch, GoBinary, SectionKind};
use crate::pclntab::Pclntab;
use crate::Result;
#[derive(Debug, Clone, Serialize)]
pub struct DataXref {
pub instruction_pc: u64,
pub function_name: Option<String>,
pub function_addr: Option<u64>,
pub target_addr: u64,
pub kind: Kind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum Kind {
Lea,
MovLoad,
MovStore,
Cmp,
CallIndirect,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Direction {
Readers,
Writers,
Both,
}
pub fn find_refs(
bin: &GoBinary,
pcln: &Pclntab<'_>,
target: u64,
direction: Direction,
) -> Result<Vec<DataXref>> {
if !bin.little_endian {
return Err(Error::Xrefs(
"only little-endian binaries supported today".into(),
));
}
if !matches!(bin.arch, Arch::X86_64) {
return Err(Error::Xrefs(format!(
"data xref scan is amd64 only today; got {:?}",
bin.arch
)));
}
let text = bin
.sections
.iter()
.find(|s| s.kind == SectionKind::Text && s.name.ends_with(".text"))
.or_else(|| {
bin.sections
.iter()
.filter(|s| s.kind == SectionKind::Text)
.max_by_key(|s| s.file_size)
})
.ok_or_else(|| Error::Xrefs("no text section".into()))?;
let text_start = text.addr;
let text_bytes = &bin.bytes[text.file_offset..text.file_offset + text.file_size];
let mut hits = Vec::new();
scan_amd64(text_bytes, text_start, target, direction, pcln, &mut hits);
Ok(hits)
}
fn scan_amd64(
text_bytes: &[u8],
text_start: u64,
target: u64,
direction: Direction,
pcln: &Pclntab<'_>,
out: &mut Vec<DataXref>,
) {
let mut i = 0usize;
while i + 7 <= text_bytes.len() {
let b = text_bytes[i];
if b == 0xff && i + 6 <= text_bytes.len() {
let modrm = text_bytes[i + 1];
if modrm & 0xC7 == 0x05 {
let reg = (modrm >> 3) & 0x7;
if reg == 2 || reg == 4 {
let disp = read_i32(&text_bytes[i + 2..i + 6]);
let next_pc = text_start + (i as u64) + 6;
if next_pc.wrapping_add(disp as i64 as u64) == target
&& wants(direction, Kind::CallIndirect)
{
out.push(make_hit(
text_start + i as u64,
target,
Kind::CallIndirect,
pcln,
));
}
}
i += 6;
continue;
}
}
if b == 0x48 && i + 7 <= text_bytes.len() {
let op = text_bytes[i + 1];
let modrm = text_bytes[i + 2];
if modrm & 0xC7 == 0x05 {
let kind = match op {
0x8D => Some(Kind::Lea),
0x8B => Some(Kind::MovLoad),
0x89 => Some(Kind::MovStore),
0x39 | 0x3B => Some(Kind::Cmp),
_ => None,
};
if let Some(kind) = kind {
let disp = read_i32(&text_bytes[i + 3..i + 7]);
let next_pc = text_start + (i as u64) + 7;
let referenced = next_pc.wrapping_add(disp as i64 as u64);
if referenced == target && wants(direction, kind) {
out.push(make_hit(text_start + i as u64, target, kind, pcln));
}
i += 7;
continue;
}
}
}
i += 1;
}
}
fn wants(d: Direction, k: Kind) -> bool {
let is_write = matches!(k, Kind::MovStore);
match d {
Direction::Readers => !is_write,
Direction::Writers => is_write,
Direction::Both => true,
}
}
fn make_hit(pc: u64, target: u64, kind: Kind, pcln: &Pclntab<'_>) -> DataXref {
let containing = pcln.lookup(pc);
DataXref {
instruction_pc: pc,
function_name: containing.as_ref().map(|f| f.name.clone()),
function_addr: containing.as_ref().map(|f| f.address),
target_addr: target,
kind,
}
}
fn read_i32(b: &[u8]) -> i32 {
i32::from_le_bytes(b[..4].try_into().unwrap())
}
pub fn referenced_addresses(bin: &GoBinary) -> Result<std::collections::HashSet<u64>> {
let mut out = std::collections::HashSet::new();
if !bin.little_endian || !matches!(bin.arch, Arch::X86_64) {
return Ok(out);
}
let text = bin
.sections
.iter()
.find(|s| s.kind == SectionKind::Text && s.name.ends_with(".text"))
.or_else(|| {
bin.sections
.iter()
.filter(|s| s.kind == SectionKind::Text)
.max_by_key(|s| s.file_size)
});
if let Some(text) = text {
scan_text_amd64(
&bin.bytes[text.file_offset..text.file_offset + text.file_size],
text.addr,
&mut out,
);
}
let mut bounds: Vec<(u64, u64)> = bin
.sections
.iter()
.filter(|s| !s.name.is_empty() && s.file_size > 0)
.map(|s| {
(
s.addr,
s.addr.saturating_add(s.vmsize.max(s.file_size as u64)),
)
})
.collect();
bounds.sort();
for s in &bin.sections {
if !matches!(
s.kind,
SectionKind::Data | SectionKind::NoPtrData | SectionKind::ReadOnlyData
) {
continue;
}
let end = s
.file_offset
.saturating_add(s.file_size)
.min(bin.bytes.len());
let slice = &bin.bytes[s.file_offset..end];
let mut i = 0;
while i + 8 <= slice.len() {
let v = u64::from_le_bytes(slice[i..i + 8].try_into().unwrap());
if looks_like_address(v, &bounds) {
out.insert(v);
}
i += 8;
}
}
Ok(out)
}
fn scan_text_amd64(text_bytes: &[u8], text_start: u64, out: &mut std::collections::HashSet<u64>) {
let mut i = 0usize;
while i + 7 <= text_bytes.len() {
let b = text_bytes[i];
if b == 0xff && i + 6 <= text_bytes.len() {
let modrm = text_bytes[i + 1];
if modrm & 0xC7 == 0x05 {
let reg = (modrm >> 3) & 0x7;
if reg == 2 || reg == 4 {
let disp = read_i32(&text_bytes[i + 2..i + 6]);
let next_pc = text_start + (i as u64) + 6;
out.insert(next_pc.wrapping_add(disp as i64 as u64));
}
i += 6;
continue;
}
}
if b == 0x48 && i + 7 <= text_bytes.len() {
let op = text_bytes[i + 1];
let modrm = text_bytes[i + 2];
if modrm & 0xC7 == 0x05 && matches!(op, 0x8D | 0x8B | 0x89 | 0x39 | 0x3B) {
let disp = read_i32(&text_bytes[i + 3..i + 7]);
let next_pc = text_start + (i as u64) + 7;
out.insert(next_pc.wrapping_add(disp as i64 as u64));
i += 7;
continue;
}
}
i += 1;
}
}
fn looks_like_address(v: u64, bounds: &[(u64, u64)]) -> bool {
if v < 0x1000 {
return false;
}
bounds.iter().any(|&(lo, hi)| v >= lo && v < hi)
}