use serde::Serialize;
use crate::error::Error;
use crate::gobin::GoBinary;
use crate::Result;
pub const MAGIC_1_20: u32 = 0xfffffff1;
pub const MAGIC_1_18: u32 = 0xfffffff0;
pub const MAGIC_1_16: u32 = 0xfffffffa;
pub const MAGIC_1_2: u32 = 0xfffffffb;
pub fn unsupported_pre118_magic(magic: u32) -> Option<&'static str> {
match magic {
MAGIC_1_2 => Some("Go 1.2 to 1.15"),
_ => None,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Layout {
Pre118,
Modern,
}
#[derive(Debug, Clone, Serialize)]
pub struct Function {
pub address: u64,
pub name: String,
pub file: Option<String>,
pub start_line: Option<u32>,
}
#[derive(Clone)]
pub struct Pclntab<'a> {
data: &'a [u8],
little_endian: bool,
magic: u32,
layout: Layout,
quantum: u8,
ptrsize: u8,
nfunc: u64,
text_start: u64,
funcname_off: usize,
cu_off: usize,
filetab_off: usize,
pctab_off: usize,
funcdata_off: usize,
gofunc: Option<u64>,
}
#[derive(Debug, Clone)]
pub struct FuncEntry {
pub address: u64,
pub name: String,
pub func_off: usize,
pub size: u64,
}
#[derive(Debug, Clone)]
pub struct InlineFrame {
pub name: String,
pub file: Option<String>,
pub line: Option<u32>,
pub inlined: bool,
}
impl<'a> Pclntab<'a> {
pub fn parse(bin: &'a GoBinary) -> Result<Self> {
let data = bin.pclntab_slice();
let little_endian = bin.little_endian;
if data.len() < 8 {
return Err(Error::BadPclntab {
offset: bin.pclntab_offset,
reason: format!("header truncated: only {} bytes", data.len()),
});
}
let magic = read_u32(data, 0, little_endian)?;
if magic == MAGIC_1_2 {
return Err(Error::UnsupportedPclntabVersion {
magic,
version: "Go 1.2 to 1.15",
});
}
let layout = if magic == MAGIC_1_16 {
Layout::Pre118
} else {
Layout::Modern
};
if data[4] != 0 || data[5] != 0 {
return Err(Error::BadPclntab {
offset: bin.pclntab_offset,
reason: format!(
"expected zero pad at [4..6], got {:02x} {:02x}",
data[4], data[5]
),
});
}
let quantum = data[6];
let ptrsize = data[7];
if !matches!(quantum, 1 | 2 | 4) {
return Err(Error::BadPclntab {
offset: bin.pclntab_offset,
reason: format!("invalid quantum {quantum}, expected 1, 2, or 4"),
});
}
if !matches!(ptrsize, 4 | 8) {
return Err(Error::BadPclntab {
offset: bin.pclntab_offset,
reason: format!("invalid ptrsize {ptrsize}, expected 4 or 8"),
});
}
let ps = ptrsize as usize;
let read_uptr = |off: usize| read_uintptr(data, off, ps, little_endian);
let nfunc = read_uptr(8)?;
let (text_start, funcname_off, cu_off, filetab_off, pctab_off, funcdata_off) = match layout
{
Layout::Modern => (
read_uptr(8 + 2 * ps)?,
read_uptr(8 + 3 * ps)? as usize,
read_uptr(8 + 4 * ps)? as usize,
read_uptr(8 + 5 * ps)? as usize,
read_uptr(8 + 6 * ps)? as usize,
read_uptr(8 + 7 * ps)? as usize,
),
Layout::Pre118 => (
0,
read_uptr(8 + 2 * ps)? as usize,
read_uptr(8 + 3 * ps)? as usize,
read_uptr(8 + 4 * ps)? as usize,
read_uptr(8 + 5 * ps)? as usize,
read_uptr(8 + 6 * ps)? as usize,
),
};
for (name, off) in [
("funcnameOffset", funcname_off),
("cuOffset", cu_off),
("filetabOffset", filetab_off),
("pctabOffset", pctab_off),
("funcdataOffset", funcdata_off),
] {
if off >= data.len() {
return Err(Error::BadPclntab {
offset: bin.pclntab_offset,
reason: format!(
"{name}=0x{off:x} is past end of pclntab (0x{:x})",
data.len()
),
});
}
}
let text_start = if layout == Layout::Modern && text_start == 0 {
bin.text_addr
} else {
text_start
};
Ok(Pclntab {
data,
little_endian,
magic,
layout,
quantum,
ptrsize,
nfunc,
text_start,
funcname_off,
cu_off,
filetab_off,
pctab_off,
funcdata_off,
gofunc: None,
})
}
pub fn with_gofunc(mut self, gofunc: u64) -> Self {
self.gofunc = Some(gofunc);
self
}
pub fn magic(&self) -> u32 {
self.magic
}
pub fn magic_is_official(&self) -> bool {
self.magic == MAGIC_1_20 || self.magic == MAGIC_1_18 || self.magic == MAGIC_1_16
}
pub fn nfunc(&self) -> u64 {
self.nfunc
}
fn functab_stride(&self) -> usize {
match self.layout {
Layout::Modern => 8,
Layout::Pre118 => 2 * self.ptrsize as usize,
}
}
fn functab_addr(&self, i: usize) -> Option<u64> {
let off = self.funcdata_off.checked_add(i.checked_mul(self.functab_stride())?)?;
match self.layout {
Layout::Modern => self
.text_start
.checked_add(read_u32(self.data, off, self.little_endian).ok()? as u64),
Layout::Pre118 => {
read_uintptr(self.data, off, self.ptrsize as usize, self.little_endian).ok()
}
}
}
fn functab_entry(&self, i: usize) -> Option<(u64, usize)> {
let ps = self.ptrsize as usize;
let off = self.funcdata_off.checked_add(i.checked_mul(self.functab_stride())?)?;
let func_off = match self.layout {
Layout::Modern => {
read_u32(self.data, off.checked_add(4)?, self.little_endian).ok()? as usize
}
Layout::Pre118 => {
read_uintptr(self.data, off.checked_add(ps)?, ps, self.little_endian).ok()? as usize
}
};
Some((self.functab_addr(i)?, self.funcdata_off.checked_add(func_off)?))
}
fn fields_base(&self) -> usize {
match self.layout {
Layout::Modern => 4,
Layout::Pre118 => self.ptrsize as usize,
}
}
pub fn data(&self) -> &[u8] {
self.data
}
pub fn little_endian(&self) -> bool {
self.little_endian
}
pub fn funcdata_off(&self) -> usize {
self.funcdata_off
}
pub fn funcname_off(&self) -> usize {
self.funcname_off
}
pub fn pctab_off(&self) -> usize {
self.pctab_off
}
pub fn gofunc(&self) -> Option<u64> {
self.gofunc
}
pub fn read_name_at(&self, name_off: usize) -> Result<String> {
self.read_name(name_off)
}
pub fn lookup(&self, pc: u64) -> Option<Function> {
let n = self.nfunc as usize;
let mut lo = 0usize;
let mut hi = n;
while lo < hi {
let mid = (lo + hi) / 2;
let mid_addr = self.functab_addr(mid)?;
if mid_addr <= pc {
lo = mid + 1;
} else {
hi = mid;
}
}
if lo == 0 {
return None;
}
let idx = lo - 1;
let next_addr = self.functab_addr(idx + 1).unwrap_or(u64::MAX);
if pc >= next_addr {
return None;
}
let (address, func_start) = self.functab_entry(idx)?;
self.read_function(address, func_start).ok()
}
pub fn lookup_inline(&self, bin: &crate::gobin::GoBinary, pc: u64) -> Vec<InlineFrame> {
let mut frames = Vec::new();
let Some(leaf) = self.lookup(pc) else {
return frames;
};
let physical_frame = InlineFrame {
name: leaf.name.clone(),
file: leaf.file.clone(),
line: leaf.start_line,
inlined: false,
};
if self.layout != Layout::Modern {
frames.push(physical_frame);
return frames;
}
let Some(gofunc) = self.gofunc else {
frames.push(physical_frame);
return frames;
};
let n = self.nfunc as usize;
let pc_off = match pc.checked_sub(self.text_start) {
Some(v) => v as u32,
None => {
frames.push(physical_frame);
return frames;
}
};
let read_text_off =
|i: usize| read_u32(self.data, self.funcdata_off + i * 8, self.little_endian).ok();
let mut lo = 0;
let mut hi = n;
while lo < hi {
let mid = (lo + hi) / 2;
match read_text_off(mid) {
Some(o) if o <= pc_off => lo = mid + 1,
_ => hi = mid,
}
}
if lo == 0 {
frames.push(physical_frame);
return frames;
}
let idx = lo - 1;
let Some(func_off) = read_u32(
self.data,
self.funcdata_off + idx * 8 + 4,
self.little_endian,
)
.ok() else {
frames.push(physical_frame);
return frames;
};
let func_start = self.funcdata_off + func_off as usize;
if func_start + 44 > self.data.len() {
frames.push(physical_frame);
return frames;
}
let npcdata = match read_u32(self.data, func_start + 28, self.little_endian) {
Ok(v) => v as usize,
Err(_) => {
frames.push(physical_frame);
return frames;
}
};
let nfuncdata = self.data[func_start + 43] as usize;
let pcdata_off = func_start + 44;
let funcdata_arr_off = pcdata_off + npcdata * 4;
const PCDATA_INLTREE_INDEX: usize = 2;
const FUNCDATA_INLTREE: usize = 3;
if nfuncdata <= FUNCDATA_INLTREE || npcdata <= PCDATA_INLTREE_INDEX {
frames.push(physical_frame);
return frames;
}
let inltree_funcdata = match read_u32(
self.data,
funcdata_arr_off + FUNCDATA_INLTREE * 4,
self.little_endian,
) {
Ok(v) => v,
Err(_) => {
frames.push(physical_frame);
return frames;
}
};
if inltree_funcdata == u32::MAX {
frames.push(physical_frame);
return frames;
}
let inltree_addr = gofunc.wrapping_add(inltree_funcdata as u64);
let pcdata_inltree_off = match read_u32(
self.data,
pcdata_off + PCDATA_INLTREE_INDEX * 4,
self.little_endian,
) {
Ok(v) => v as usize,
Err(_) => {
frames.push(physical_frame);
return frames;
}
};
let func_entry_pc = self.text_start + read_text_off(idx).unwrap_or(0) as u64;
let target_off = (pc - func_entry_pc) as u32;
let mut current_idx =
pcvalue_at(&self.data[self.pctab_off..], pcdata_inltree_off, target_off);
const MAX_INLINE_DEPTH: usize = 32;
const MAX_INLTREE_INDEX: i64 = 65_536;
let mut current_pc_off = target_off;
let mut iter_guard = 0;
while current_idx >= 0 && iter_guard < MAX_INLINE_DEPTH {
if current_idx > MAX_INLTREE_INDEX {
break;
}
iter_guard += 1;
let entry_addr = inltree_addr + (current_idx as u64) * 16;
let Some(entry) = bin.read_at_addr(entry_addr, 16) else {
break;
};
let name_off = i32::from_le_bytes(entry[4..8].try_into().unwrap()) as usize;
let parent_pc = i32::from_le_bytes(entry[8..12].try_into().unwrap());
let start_line = i32::from_le_bytes(entry[12..16].try_into().unwrap());
let name = self
.read_name(name_off)
.unwrap_or_else(|_| format!("inlined@{current_idx}"));
let file_and_line = self.file_and_line_for_pc(func_start, current_pc_off);
frames.push(InlineFrame {
name,
file: file_and_line.0,
line: file_and_line.1.or(if start_line > 0 {
Some(start_line as u32)
} else {
None
}),
inlined: true,
});
current_pc_off = parent_pc as u32;
current_idx = pcvalue_at(
&self.data[self.pctab_off..],
pcdata_inltree_off,
current_pc_off,
);
}
frames.push(physical_frame);
frames
}
fn file_and_line_for_pc(
&self,
func_start: usize,
target_off: u32,
) -> (Option<String>, Option<u32>) {
let Ok(pcfile_off) = read_u32(self.data, func_start + 20, self.little_endian) else {
return (None, None);
};
let Ok(pcln_off) = read_u32(self.data, func_start + 24, self.little_endian) else {
return (None, None);
};
let Ok(cu_offset) = read_u32(self.data, func_start + 32, self.little_endian) else {
return (None, None);
};
let file_idx = pcvalue_at(
&self.data[self.pctab_off..],
pcfile_off as usize,
target_off,
);
let line = pcvalue_at(&self.data[self.pctab_off..], pcln_off as usize, target_off);
let file = if file_idx >= 0 {
self.resolve_file(cu_offset as usize, file_idx as usize)
} else {
None
};
let line = if line > 0 { Some(line as u32) } else { None };
(file, line)
}
fn resolve_file(&self, cu_offset: usize, file_idx: usize) -> Option<String> {
let cutab_entry = cu_offset
.checked_add(file_idx)?
.checked_mul(4)?
.checked_add(self.cu_off)?;
if cutab_entry.checked_add(4)? > self.data.len() {
return None;
}
let raw = read_u32(self.data, cutab_entry, self.little_endian).ok()?;
if raw == u32::MAX {
return None;
}
let abs = self.filetab_off.checked_add(raw as usize)?;
read_cstring(self.data, abs).ok()
}
pub fn text_start(&self) -> u64 {
self.text_start
}
pub fn ptrsize(&self) -> u8 {
self.ptrsize
}
pub fn quantum(&self) -> u8 {
self.quantum
}
pub fn functions(&self) -> Result<Vec<Function>> {
let stride = self.functab_stride();
const MAX_FUNCS: u64 = 5_000_000;
let available_bytes = self.data.len().saturating_sub(self.funcdata_off);
let max_possible = (available_bytes / stride).saturating_sub(1) as u64;
let n = self.nfunc.min(MAX_FUNCS).min(max_possible) as usize;
if (self.nfunc > MAX_FUNCS || self.nfunc > max_possible) && self.nfunc != 0 {
return Err(Error::BadPclntab {
offset: 0,
reason: format!(
"nfunc {} exceeds available functab capacity (max {} from byte range, cap {})",
self.nfunc, max_possible, MAX_FUNCS,
),
});
}
let table_bytes = n.saturating_add(1).saturating_mul(stride);
if self.funcdata_off + table_bytes > self.data.len() {
return Err(Error::BadPclntab {
offset: 0,
reason: format!("functab ({n} entries x {stride}) extends past pclntab end"),
});
}
let mut out = Vec::with_capacity(n);
for i in 0..n {
let Some((address, func_start)) = self.functab_entry(i) else {
continue;
};
match self.read_function(address, func_start) {
Ok(f) => out.push(f),
Err(_) => continue,
}
}
Ok(out)
}
pub fn functions_with_offsets(&self) -> Result<Vec<FuncEntry>> {
let stride = self.functab_stride();
const MAX_FUNCS: u64 = 5_000_000;
let available_bytes = self.data.len().saturating_sub(self.funcdata_off);
let max_possible = (available_bytes / stride).saturating_sub(1) as u64;
let n = self.nfunc.min(MAX_FUNCS).min(max_possible) as usize;
if (self.nfunc > MAX_FUNCS || self.nfunc > max_possible) && self.nfunc != 0 {
return Err(Error::BadPclntab {
offset: 0,
reason: format!(
"nfunc {} exceeds functab capacity (max {}, cap {})",
self.nfunc, max_possible, MAX_FUNCS,
),
});
}
let table_bytes = n.saturating_add(1).saturating_mul(stride);
if self.funcdata_off + table_bytes > self.data.len() {
return Err(Error::BadPclntab {
offset: 0,
reason: format!("functab ({n} entries) extends past pclntab end"),
});
}
let fb = self.fields_base();
let mut out = Vec::with_capacity(n);
for i in 0..n {
let Some((address, func_start)) = self.functab_entry(i) else {
continue;
};
let next_addr = self.functab_addr(i + 1).unwrap_or(u64::MAX);
let size = next_addr.saturating_sub(address);
if func_start + fb + 4 > self.data.len() {
continue;
}
let name_off = match read_u32(self.data, func_start + fb, self.little_endian) {
Ok(v) => v as usize,
Err(_) => continue,
};
let name = self.read_name(name_off).unwrap_or_default();
out.push(FuncEntry {
address,
name,
func_off: func_start - self.funcdata_off,
size,
});
}
Ok(out)
}
fn read_function(&self, address: u64, func_start: usize) -> Result<Function> {
let fb = self.fields_base();
let end = func_start.checked_add(fb).and_then(|x| x.checked_add(4));
if end.is_none_or(|e| e > self.data.len()) {
return Err(Error::ShortRead {
wanted: fb + 4,
offset: func_start,
available: self.data.len().saturating_sub(func_start),
});
}
let name_off = read_u32(self.data, func_start + fb, self.little_endian)? as usize;
let name = self.read_name(name_off)?;
let (file, start_line) = self.read_file_and_line(func_start).unwrap_or((None, None));
Ok(Function {
address,
name,
file,
start_line,
})
}
fn read_name(&self, off: usize) -> Result<String> {
let abs = self.funcname_off + off;
read_cstring(self.data, abs)
}
fn read_file_and_line(&self, func_start: usize) -> Option<(Option<String>, Option<u32>)> {
let fb = self.fields_base();
let end = func_start.checked_add(fb).and_then(|x| x.checked_add(32));
if end.is_none_or(|e| e > self.data.len()) {
return None;
}
let pcfile_off = read_u32(self.data, func_start + fb + 16, self.little_endian).ok()? as usize;
let pcln_off = read_u32(self.data, func_start + fb + 20, self.little_endian).ok()? as usize;
let cu_offset = read_u32(self.data, func_start + fb + 28, self.little_endian).ok()? as usize;
let file_idx = first_pcvalue(&self.data[self.pctab_off..], pcfile_off)?;
let line = first_pcvalue(&self.data[self.pctab_off..], pcln_off)?;
let file = self.resolve_file(cu_offset, file_idx as usize);
Some((file, if line > 0 { Some(line as u32) } else { None }))
}
}
fn pcvalue_at(table: &[u8], start: usize, target_off: u32) -> i64 {
if start >= table.len() {
return -1;
}
let mut value: i64 = -1;
let mut pc: u32 = 0;
let mut pos = start;
let quantum: u32 = 1;
let mut iter_guard = 0;
while pos < table.len() && iter_guard < 1_000_000 {
iter_guard += 1;
let (uval, n) = match read_varint(&table[pos..]) {
Some(v) => v,
None => return -1,
};
if uval == 0 && pc != 0 {
return -1;
}
pos += n;
let delta_val = zig_zag(uval);
value = value.wrapping_add(delta_val);
let (pc_delta, n2) = match read_varint(&table[pos..]) {
Some(v) => v,
None => return -1,
};
pos += n2;
pc = pc.wrapping_add((pc_delta as u32) * quantum);
if target_off < pc {
return value;
}
}
-1
}
fn first_pcvalue(table: &[u8], start: usize) -> Option<i64> {
if start >= table.len() {
return None;
}
let (uval, n) = read_varint(&table[start..])?;
if uval == 0 {
return None;
}
let delta = zig_zag(uval);
let mut value: i64 = -1;
value = value.wrapping_add(delta);
let _ = n;
Some(value)
}
fn zig_zag(u: u64) -> i64 {
((u >> 1) as i64) ^ -((u & 1) as i64)
}
fn read_varint(buf: &[u8]) -> Option<(u64, usize)> {
const MAX_BYTES: usize = 10;
let mut result: u64 = 0;
let mut shift = 0;
for (i, &b) in buf.iter().take(MAX_BYTES).enumerate() {
result |= ((b & 0x7f) as u64) << shift;
if b & 0x80 == 0 {
return Some((result, i + 1));
}
shift += 7;
if shift >= 64 {
return None;
}
}
None
}
fn read_u32(buf: &[u8], off: usize, little_endian: bool) -> Result<u32> {
if off + 4 > buf.len() {
return Err(Error::ShortRead {
wanted: 4,
offset: off,
available: buf.len().saturating_sub(off),
});
}
let slice = &buf[off..off + 4];
Ok(if little_endian {
u32::from_le_bytes(slice.try_into().unwrap())
} else {
u32::from_be_bytes(slice.try_into().unwrap())
})
}
fn read_u64(buf: &[u8], off: usize, little_endian: bool) -> Result<u64> {
if off + 8 > buf.len() {
return Err(Error::ShortRead {
wanted: 8,
offset: off,
available: buf.len().saturating_sub(off),
});
}
let slice = &buf[off..off + 8];
Ok(if little_endian {
u64::from_le_bytes(slice.try_into().unwrap())
} else {
u64::from_be_bytes(slice.try_into().unwrap())
})
}
fn read_uintptr(buf: &[u8], off: usize, ptrsize: usize, little_endian: bool) -> Result<u64> {
match ptrsize {
4 => read_u32(buf, off, little_endian).map(|v| v as u64),
8 => read_u64(buf, off, little_endian),
_ => Err(Error::BadPclntab {
offset: off,
reason: format!("ptrsize {ptrsize} is neither 4 nor 8"),
}),
}
}
fn read_cstring(buf: &[u8], off: usize) -> Result<String> {
if off >= buf.len() {
return Err(Error::ShortRead {
wanted: 1,
offset: off,
available: 0,
});
}
let end = buf[off..]
.iter()
.position(|&b| b == 0)
.map(|p| off + p)
.unwrap_or(buf.len());
std::str::from_utf8(&buf[off..end])
.map(|s| s.to_string())
.map_err(|_| Error::BadString { offset: off })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn only_go_1_2_to_1_15_is_an_unsupported_layout() {
assert_eq!(unsupported_pre118_magic(MAGIC_1_2), Some("Go 1.2 to 1.15"));
assert_eq!(unsupported_pre118_magic(MAGIC_1_16), None);
assert_eq!(unsupported_pre118_magic(MAGIC_1_18), None);
assert_eq!(unsupported_pre118_magic(MAGIC_1_20), None);
assert_eq!(unsupported_pre118_magic(0x1234_5678), None);
}
#[test]
fn varint_decodes_single_byte() {
assert_eq!(read_varint(&[0x05]), Some((5, 1)));
assert_eq!(read_varint(&[0x7f]), Some((127, 1)));
}
#[test]
fn varint_decodes_multi_byte() {
assert_eq!(read_varint(&[0xac, 0x02]), Some((300, 2)));
}
#[test]
fn varint_unterminated_returns_none() {
assert!(read_varint(&[0x80, 0x80, 0x80]).is_none());
}
#[test]
fn zig_zag_round_trips() {
assert_eq!(zig_zag(0), 0);
assert_eq!(zig_zag(1), -1);
assert_eq!(zig_zag(2), 1);
assert_eq!(zig_zag(3), -2);
}
#[test]
fn cstring_stops_at_nul() {
let buf = b"hello\0world";
assert_eq!(read_cstring(buf, 0).unwrap(), "hello");
assert_eq!(read_cstring(buf, 6).unwrap(), "world");
}
#[test]
fn u32_respects_endianness() {
let buf = [0x01, 0x02, 0x03, 0x04];
assert_eq!(read_u32(&buf, 0, true).unwrap(), 0x04030201);
assert_eq!(read_u32(&buf, 0, false).unwrap(), 0x01020304);
}
#[test]
fn resolve_file_rejects_overflowing_indices() {
let path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("testdata")
.join("hello.linux-amd64.stripped");
if !path.exists() {
return;
}
let bin = GoBinary::open(&path).expect("open fixture");
let pcln = Pclntab::parse(&bin).expect("parse pclntab");
for (cu, idx) in [(usize::MAX, usize::MAX), (usize::MAX, 0), (0, usize::MAX)] {
assert!(
pcln.resolve_file(cu, idx).is_none(),
"overflowing ({cu}, {idx}) must resolve to no file"
);
}
}
#[test]
fn parses_go_1_17_functab() {
let path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("testdata")
.join("hello.go117.stripped");
if !path.exists() {
return;
}
let bin = GoBinary::open(&path).expect("open go1.17 fixture");
let pcln = Pclntab::parse(&bin).expect("parse go1.17 pclntab");
assert_eq!(pcln.magic(), MAGIC_1_16);
let funcs = pcln.functions().expect("walk go1.17 functab");
let names: Vec<&str> = funcs.iter().map(|f| f.name.as_str()).collect();
for required in ["main.main", "main.greet", "main.parseFlags"] {
assert!(
names.contains(&required),
"expected {required} among {} recovered functions",
names.len()
);
}
let main = funcs.iter().find(|f| f.name == "main.main").unwrap();
assert!(
main.address > 0x400000,
"main.main entry {:#x} is not a plausible absolute PC",
main.address
);
}
#[test]
fn parse_chain_never_panics_on_hostile_pclntab() {
use crate::gobin::{Arch, Container, Section, SectionKind};
fn make_bin(bytes: Vec<u8>) -> GoBinary {
let n = bytes.len();
GoBinary {
bytes,
container: Container::Elf,
arch: Arch::X86_64,
little_endian: true,
ptr_size: 8,
sections: vec![Section {
name: ".text".to_string(),
kind: SectionKind::Text,
file_offset: 0,
file_size: n,
addr: 0x1000,
vmsize: 0x100000,
}],
pclntab_offset: 0,
pclntab_size: n,
pclntab_addr: 0x1000,
text_addr: 0x1000,
}
}
let mut base = vec![0u8; 4096];
base[6] = 1; base[7] = 8;
let mut state: u64 = 0x9e37_79b9_7f4a_7c15;
let mut rng = || {
state = state.wrapping_add(0x9e37_79b9_7f4a_7c15);
let mut z = state;
z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
z ^ (z >> 31)
};
let magics: [[u8; 4]; 4] = [
[0xfa, 0xff, 0xff, 0xff], [0xfb, 0xff, 0xff, 0xff], [0xf0, 0xff, 0xff, 0xff], [0xf1, 0xff, 0xff, 0xff], ];
for magic in magics {
for _ in 0..2000 {
let mut b = base.clone();
b[0..4].copy_from_slice(&magic);
let muts = (rng() % 80) as usize;
for _ in 0..muts {
let idx = (rng() as usize) % b.len();
b[idx] = (rng() & 0xff) as u8;
}
if rng() % 4 == 0 {
let cut = (rng() as usize) % b.len();
b.truncate(cut);
}
let bin = make_bin(b);
let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
if let Ok(p) = Pclntab::parse(&bin) {
let _ = p.functions();
let _ = p.functions_with_offsets();
let _ = p.lookup(0x1234);
let _ = p.lookup(0xffff_ffff_ffff_ffff);
}
}));
assert!(res.is_ok(), "parse chain panicked on a hostile pclntab");
}
}
}
}