use std::collections::HashMap;
#[derive(Debug, Default, Clone)]
pub struct GoSymbols {
pub func_names: HashMap<u64, String>,
pub version: Option<GoVersion>,
pub pclntab_offset: Option<usize>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GoVersion {
V12, V116, V118, V120, }
const MAGIC_V12: u32 = 0xFFFF_FFFB;
const MAGIC_V116: u32 = 0xFFFF_FFFA;
const MAGIC_V118: u32 = 0xFFFF_FFF1;
const MAGIC_V120: u32 = 0xFFFF_FFF0;
#[must_use]
pub fn find_pclntab(raw: &[u8]) -> Option<(usize, GoVersion)> {
if raw.len() < 8 {
return None;
}
let mut i = 0usize;
while i + 8 <= raw.len() {
let m = u32::from_le_bytes(raw[i..i + 4].try_into().ok()?);
let version = match m {
MAGIC_V12 => GoVersion::V12,
MAGIC_V116 => GoVersion::V116,
MAGIC_V118 => GoVersion::V118,
MAGIC_V120 => GoVersion::V120,
_ => {
i += 1;
continue;
}
};
let z1 = raw[i + 4];
let z2 = raw[i + 5];
let quantum = raw[i + 6];
let ptrsize = raw[i + 7];
if z1 == 0 && z2 == 0 && matches!(quantum, 1 | 2 | 4) && matches!(ptrsize, 4 | 8) {
return Some((i, version));
}
i += 1;
}
None
}
#[must_use]
pub fn parse(raw: &[u8], text_section_va: u64) -> GoSymbols {
let Some((off, version)) = find_pclntab(raw) else {
return GoSymbols::default();
};
let mut out = GoSymbols {
func_names: HashMap::new(),
version: Some(version),
pclntab_offset: Some(off),
};
let tab = &raw[off..];
let _ = match version {
GoVersion::V12 => parse_v12(tab, off, raw, &mut out.func_names),
GoVersion::V116 => parse_v116(tab, off, raw, &mut out.func_names),
GoVersion::V118 | GoVersion::V120 => {
parse_v118(tab, off, raw, text_section_va, &mut out.func_names)
}
};
out
}
fn read_uintptr(buf: &[u8], offset: usize, ptrsize: u8) -> Option<u64> {
match ptrsize {
4 => buf
.get(offset..offset + 4)?
.try_into()
.ok()
.map(|b: [u8; 4]| u32::from_le_bytes(b) as u64),
8 => buf
.get(offset..offset + 8)?
.try_into()
.ok()
.map(u64::from_le_bytes),
_ => None,
}
}
fn read_u32(buf: &[u8], offset: usize) -> Option<u32> {
buf.get(offset..offset + 4)?
.try_into()
.ok()
.map(u32::from_le_bytes)
}
fn read_cstr(raw: &[u8], name_file_offset: usize) -> Option<String> {
let bytes = raw.get(name_file_offset..)?;
let end = bytes.iter().position(|b| *b == 0).unwrap_or(bytes.len());
if end == 0 || end > 512 {
return None;
}
std::str::from_utf8(&bytes[..end]).ok().map(str::to_owned)
}
fn parse_v12(
tab: &[u8],
tab_file_offset: usize,
raw: &[u8],
out: &mut HashMap<u64, String>,
) -> Option<()> {
let ptrsize = *tab.get(7)?;
let ps = ptrsize as usize;
let nfunc = read_uintptr(tab, 8, ptrsize)?;
if nfunc == 0 || nfunc > 1_000_000 {
return None;
}
let entry_size = ps.checked_mul(2)?;
let functab_off = 8usize.checked_add(ps)?;
for i in 0..nfunc as usize {
let Some(row) = i
.checked_mul(entry_size)
.and_then(|x| x.checked_add(functab_off))
else {
return Some(());
};
let Some(entry_va) = read_uintptr(tab, row, ptrsize) else {
continue;
};
let Some(funcoff_pos) = row.checked_add(ps) else {
continue;
};
let funcoff = match read_uintptr(tab, funcoff_pos, ptrsize) {
Some(f) => f as usize,
None => continue,
};
let Some(nameoff_pos) = funcoff.checked_add(ps) else {
continue;
};
let nameoff = match read_u32(tab, nameoff_pos) {
Some(n) => n as usize,
None => continue,
};
let Some(name_file_off) = tab_file_offset.checked_add(nameoff) else {
continue;
};
if let Some(name) = read_cstr(raw, name_file_off)
&& !name.is_empty()
{
out.insert(entry_va, name);
}
}
Some(())
}
fn parse_v116(
tab: &[u8],
tab_file_offset: usize,
raw: &[u8],
out: &mut HashMap<u64, String>,
) -> Option<()> {
let ptrsize = *tab.get(7)?;
let ps = ptrsize as usize;
let nfunc = read_uintptr(tab, 8, ptrsize)?;
if nfunc == 0 || nfunc > 1_000_000 {
return None;
}
let funcname_offset =
read_uintptr(tab, 8usize.checked_add(ps.checked_mul(2)?)?, ptrsize)? as usize;
let pcln_offset = read_uintptr(tab, 8usize.checked_add(ps.checked_mul(6)?)?, ptrsize)? as usize;
let entry_size = ps.checked_mul(2)?;
let functab_off = pcln_offset;
for i in 0..nfunc as usize {
let Some(row) = i
.checked_mul(entry_size)
.and_then(|x| x.checked_add(functab_off))
else {
return Some(());
};
let Some(entry_va) = read_uintptr(tab, row, ptrsize) else {
continue;
};
let Some(funcoff_pos) = row.checked_add(ps) else {
continue;
};
let funcoff = match read_uintptr(tab, funcoff_pos, ptrsize) {
Some(f) => f as usize,
None => continue,
};
let Some(nameoff_pos) = funcoff.checked_add(ps) else {
continue;
};
let nameoff = match read_u32(tab, nameoff_pos) {
Some(n) => n as usize,
None => continue,
};
let Some(name_file_off) = tab_file_offset
.checked_add(funcname_offset)
.and_then(|x| x.checked_add(nameoff))
else {
continue;
};
if let Some(name) = read_cstr(raw, name_file_off)
&& !name.is_empty()
{
out.insert(entry_va, name);
}
}
Some(())
}
fn parse_v118(
tab: &[u8],
tab_file_offset: usize,
raw: &[u8],
text_section_va_fallback: u64,
out: &mut HashMap<u64, String>,
) -> Option<()> {
let ptrsize = *tab.get(7)?;
let ps = ptrsize as usize;
let nfunc = read_uintptr(tab, 8, ptrsize)?;
if nfunc == 0 || nfunc > 1_000_000 {
return None;
}
let mut text_start = read_uintptr(tab, 8usize.checked_add(ps.checked_mul(2)?)?, ptrsize)?;
if text_start == 0 {
text_start = text_section_va_fallback;
}
let funcname_offset =
read_uintptr(tab, 8usize.checked_add(ps.checked_mul(3)?)?, ptrsize)? as usize;
let pcln_offset = read_uintptr(tab, 8usize.checked_add(ps.checked_mul(7)?)?, ptrsize)? as usize;
let entry_size = 8usize;
for i in 0..nfunc as usize {
let Some(row) = i
.checked_mul(entry_size)
.and_then(|x| x.checked_add(pcln_offset))
else {
return Some(());
};
let Some(entry_off) = read_u32(tab, row).map(|v| v as u64) else {
continue;
};
let Some(funcoff_pos) = row.checked_add(4) else {
continue;
};
let funcoff = match read_u32(tab, funcoff_pos) {
Some(f) => f as usize,
None => continue,
};
let Some(nameoff_pos) = pcln_offset
.checked_add(funcoff)
.and_then(|x| x.checked_add(4))
else {
continue;
};
let nameoff = match read_u32(tab, nameoff_pos) {
Some(n) => n as usize,
None => continue,
};
let Some(entry_va) = text_start.checked_add(entry_off) else {
continue;
};
let Some(name_file_off) = tab_file_offset
.checked_add(funcname_offset)
.and_then(|x| x.checked_add(nameoff))
else {
continue;
};
if let Some(name) = read_cstr(raw, name_file_off)
&& !name.is_empty()
{
out.insert(entry_va, name);
}
}
Some(())
}