use std::collections::HashMap;
use serde::Serialize;
use crate::error::Error;
use crate::gobin::GoBinary;
use crate::moduledata::ModuleData;
use crate::types::{
parse_type, read_name_public, KindData, KindName, Type, TFLAG_UNCOMMON, TYPE_HEADER_SIZE_64,
};
use crate::Result;
const METHOD_SIZE: usize = 16;
const UNCOMMON_TYPE_SIZE: usize = 16;
#[derive(Debug, Clone, Serialize)]
pub struct RecoveredMethod {
pub receiver: String,
pub name: String,
pub mtyp_addr: u64,
pub ifn_pc: u64,
pub tfn_pc: u64,
}
pub fn recover_methods_from_types(
bin: &GoBinary,
md: &ModuleData,
types: &[Type],
) -> Vec<RecoveredMethod> {
let mut out = Vec::new();
for t in types {
if t.tflag & TFLAG_UNCOMMON == 0 {
continue;
}
match methods_for_type(bin, md, t) {
Ok(methods) => out.extend(methods),
Err(_) => {
}
}
}
out
}
pub fn recover_methods_from_itabs(
types: &[Type],
itabs: &[crate::itabs::Itab],
) -> Vec<RecoveredMethod> {
let mut iface_by_name: HashMap<String, &Type> = HashMap::new();
for t in types
.iter()
.filter(|t| matches!(t.kind, KindName::Interface))
{
iface_by_name.insert(t.name.clone(), t);
iface_by_name.insert(format!("*{}", t.name), t);
}
let mut out = Vec::new();
for itab in itabs {
let iface_t = iface_by_name
.get(&itab.interface_name)
.copied()
.or_else(|| {
itab.interface_name
.strip_prefix('*')
.and_then(|bare| iface_by_name.get(bare).copied())
});
let Some(iface_t) = iface_t else {
continue;
};
let iface_methods = match &iface_t.kind_data {
KindData::Interface { methods } => methods,
_ => continue,
};
for (slot_idx, slot) in itab.methods.iter().enumerate() {
if slot.concrete_fn == 0 {
continue;
}
let Some(iface_method) = iface_methods.get(slot_idx) else {
continue;
};
out.push(RecoveredMethod {
receiver: itab.concrete_name.clone(),
name: slot.interface_method.clone(),
mtyp_addr: iface_method.typ,
ifn_pc: 0,
tfn_pc: slot.concrete_fn,
});
}
}
out
}
pub fn merge_methods(a: Vec<RecoveredMethod>, b: Vec<RecoveredMethod>) -> Vec<RecoveredMethod> {
use std::collections::HashSet;
let mut seen: HashSet<(String, String, u64)> = HashSet::new();
let mut out = Vec::with_capacity(a.len() + b.len());
for m in a.into_iter().chain(b) {
if m.tfn_pc == 0 {
out.push(m);
continue;
}
let key = (m.receiver.clone(), m.name.clone(), m.tfn_pc);
if seen.insert(key) {
out.push(m);
}
}
out
}
pub fn methods_for_type(bin: &GoBinary, md: &ModuleData, t: &Type) -> Result<Vec<RecoveredMethod>> {
if t.tflag & TFLAG_UNCOMMON == 0 {
return Ok(Vec::new());
}
let extra_size = kind_extension_size(t.kind, t.size).ok_or_else(|| {
Error::TypeRecovery(format!(
"kind {:?} has no known extension size; cannot locate uncommon block",
t.kind
))
})?;
let uncommon_addr = t.addr + TYPE_HEADER_SIZE_64 as u64 + extra_size as u64;
let header = bin
.read_at_addr(uncommon_addr, UNCOMMON_TYPE_SIZE)
.ok_or_else(|| {
Error::TypeRecovery(format!(
"uncommon header at 0x{uncommon_addr:x} unmapped (type {})",
t.name
))
})?;
let _pkg_path_off = i32::from_le_bytes(header[0..4].try_into().unwrap());
let mcount = u16::from_le_bytes(header[4..6].try_into().unwrap());
let _xcount = u16::from_le_bytes(header[6..8].try_into().unwrap());
let moff = u32::from_le_bytes(header[8..12].try_into().unwrap());
if mcount == 0 {
return Ok(Vec::new());
}
const MAX_METHODS: u16 = 4096;
if mcount > MAX_METHODS {
return Err(Error::TypeRecovery(format!(
"method count {mcount} unreasonably large for type {}",
t.name
)));
}
let methods_array_addr = uncommon_addr + moff as u64;
let total = mcount as usize * METHOD_SIZE;
let buf = bin.read_at_addr(methods_array_addr, total).ok_or_else(|| {
Error::TypeRecovery(format!(
"methods array at 0x{methods_array_addr:x} ({total} bytes) unmapped (type {})",
t.name
))
})?;
let mut out = Vec::with_capacity(mcount as usize);
for i in 0..mcount as usize {
let off = i * METHOD_SIZE;
let name_off = i32::from_le_bytes(buf[off..off + 4].try_into().unwrap());
let mtyp_off = i32::from_le_bytes(buf[off + 4..off + 8].try_into().unwrap());
let ifn_off = i32::from_le_bytes(buf[off + 8..off + 12].try_into().unwrap());
let tfn_off = i32::from_le_bytes(buf[off + 12..off + 16].try_into().unwrap());
let name = if name_off >= 0 {
let name_addr = md.types.wrapping_add(name_off as i64 as u64);
read_name_public(bin, md, name_addr).unwrap_or_default()
} else {
String::new()
};
let mtyp_addr = if mtyp_off >= 0 {
md.types.wrapping_add(mtyp_off as i64 as u64)
} else {
0
};
let ifn_pc = if ifn_off >= 0 {
md.text.wrapping_add(ifn_off as i64 as u64)
} else {
0
};
let tfn_pc = if tfn_off >= 0 {
md.text.wrapping_add(tfn_off as i64 as u64)
} else {
0
};
out.push(RecoveredMethod {
receiver: t.name.clone(),
name,
mtyp_addr,
ifn_pc,
tfn_pc,
});
}
Ok(out)
}
fn kind_extension_size(kind: KindName, _size: u64) -> Option<usize> {
match kind {
KindName::Bool
| KindName::Int
| KindName::Int8
| KindName::Int16
| KindName::Int32
| KindName::Int64
| KindName::Uint
| KindName::Uint8
| KindName::Uint16
| KindName::Uint32
| KindName::Uint64
| KindName::Uintptr
| KindName::Float32
| KindName::Float64
| KindName::Complex64
| KindName::Complex128
| KindName::String
| KindName::UnsafePointer => Some(0),
KindName::Pointer | KindName::Slice => Some(8),
KindName::Array => Some(24),
KindName::Chan => Some(16),
KindName::Func => Some(4),
KindName::Struct => Some(32),
KindName::Interface => Some(32),
KindName::Map => None,
KindName::Invalid | KindName::Unknown(_) => None,
}
}
pub fn signatures_by_pc(
methods: &[RecoveredMethod],
cache: &mut TypeCache<'_>,
) -> HashMap<u64, String> {
let mut out = HashMap::with_capacity(methods.len());
for m in methods {
if m.tfn_pc == 0 {
continue;
}
if let Some(sig) = render_method_signature(m, cache) {
out.entry(m.tfn_pc).or_insert(sig);
}
}
out
}
pub struct TypeCache<'a> {
bin: &'a GoBinary,
md: &'a ModuleData,
cache: HashMap<u64, Option<Type>>,
}
impl<'a> TypeCache<'a> {
pub fn new(bin: &'a GoBinary, md: &'a ModuleData) -> Self {
Self {
bin,
md,
cache: HashMap::new(),
}
}
pub fn seed_from(&mut self, types: &[Type]) {
for t in types {
self.cache.entry(t.addr).or_insert_with(|| Some(t.clone()));
}
}
pub fn get(&mut self, addr: u64) -> Option<&Type> {
if addr == 0 {
return None;
}
if !self.cache.contains_key(&addr) {
let parsed = parse_type(self.bin, self.md, addr).ok();
self.cache.insert(addr, parsed);
}
self.cache.get(&addr).and_then(|v| v.as_ref())
}
}
pub fn render_method_signature(
method: &RecoveredMethod,
cache: &mut TypeCache<'_>,
) -> Option<String> {
if method.mtyp_addr == 0 {
return None;
}
let mtyp = cache.get(method.mtyp_addr)?.clone();
let (in_types, out_types, variadic) = match &mtyp.kind_data {
KindData::Func {
in_types,
out_types,
variadic,
..
} => (in_types.clone(), out_types.clone(), *variadic),
_ => return None,
};
let mut params = String::from("(");
for (i, &addr) in in_types.iter().enumerate() {
if i > 0 {
params.push_str(", ");
}
let name = render_type_name(addr, cache);
let is_last = i + 1 == in_types.len();
if variadic && is_last {
let elem = name.strip_prefix("[]").unwrap_or(&name).to_string();
params.push_str(&format!("_{i} ...{elem}"));
} else {
params.push_str(&format!("_{i} {name}"));
}
}
params.push(')');
let returns = match out_types.len() {
0 => String::new(),
1 => format!(" {}", render_type_name(out_types[0], cache)),
_ => {
let mut s = String::from(" (");
for (i, &addr) in out_types.iter().enumerate() {
if i > 0 {
s.push_str(", ");
}
s.push_str(&render_type_name(addr, cache));
}
s.push(')');
s
}
};
Some(format!("{params}{returns}"))
}
fn render_type_name(addr: u64, cache: &mut TypeCache<'_>) -> String {
if addr == 0 {
return "<nil>".to_string();
}
match cache.get(addr) {
Some(t) if !t.name.is_empty() => t.name.clone(),
Some(_) => format!("type@0x{addr:x}"),
None => format!("type@0x{addr:x}"),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn kind_extension_sizes_match_go_layout() {
assert_eq!(kind_extension_size(KindName::Pointer, 0), Some(8));
assert_eq!(kind_extension_size(KindName::Slice, 0), Some(8));
assert_eq!(kind_extension_size(KindName::Array, 0), Some(24));
assert_eq!(kind_extension_size(KindName::Chan, 0), Some(16));
assert_eq!(kind_extension_size(KindName::Func, 0), Some(4));
assert_eq!(kind_extension_size(KindName::Struct, 0), Some(32));
assert_eq!(kind_extension_size(KindName::Interface, 0), Some(32));
assert_eq!(kind_extension_size(KindName::Map, 0), None);
assert_eq!(kind_extension_size(KindName::Bool, 0), Some(0));
assert_eq!(kind_extension_size(KindName::String, 0), Some(0));
}
}