use std::collections::HashMap;
use std::sync::LazyLock;
use crate::ir::InferredType;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SigType {
Void,
Int,
UInt,
Long,
ULong,
SizeT,
CharPtr,
ConstCharPtr,
VoidPtr,
ConstVoidPtr,
FilePtr,
Bool,
Fd,
SockFd,
WCharPtr,
ConstWCharPtr,
Handle,
DWord,
LpVoid,
HKey, HModule, Hwnd, HIcon, HBrush, HDc, HBitmap, HFont, HMenu, HInstance, LResult, WParam, LParam, Atom, LpCStr, LpCWStr, LpStr, LpWStr, LpByte, LpDWord, PhKey, RegSam, LStatus, ScHandle, Ntstatus, HResult, Word, Byte, PyObjectPtr, ConstPyObjectPtr, PyObjectPtrPtr, PyTypeObjectPtr, PyFrameObjectPtr, PySsizeT, PyHashT, PyCFunction, PyRichCmpOp, }
impl SigType {
pub fn c_str(&self) -> &'static str {
match self {
SigType::Void => "void",
SigType::Int => "int",
SigType::UInt => "unsigned int",
SigType::Long => "long",
SigType::ULong => "unsigned long",
SigType::SizeT => "size_t",
SigType::CharPtr | SigType::LpStr => "char *",
SigType::ConstCharPtr | SigType::LpCStr => "const char *",
SigType::VoidPtr | SigType::LpVoid => "void *",
SigType::ConstVoidPtr => "const void *",
SigType::FilePtr => "FILE *",
SigType::Bool => "bool",
SigType::Fd => "int",
SigType::SockFd => "int",
SigType::WCharPtr | SigType::LpWStr => "wchar_t *",
SigType::ConstWCharPtr | SigType::LpCWStr => "const wchar_t *",
SigType::LpByte => "LPBYTE",
SigType::LpDWord => "LPDWORD",
SigType::Handle => "HANDLE",
SigType::DWord => "DWORD",
SigType::Word => "WORD",
SigType::Byte => "BYTE",
SigType::HKey => "HKEY",
SigType::HModule => "HMODULE",
SigType::Hwnd => "HWND",
SigType::HIcon => "HICON",
SigType::HBrush => "HBRUSH",
SigType::HDc => "HDC",
SigType::HBitmap => "HBITMAP",
SigType::HFont => "HFONT",
SigType::HMenu => "HMENU",
SigType::HInstance => "HINSTANCE",
SigType::LResult => "LRESULT",
SigType::WParam => "WPARAM",
SigType::LParam => "LPARAM",
SigType::Atom => "ATOM",
SigType::PhKey => "PHKEY",
SigType::RegSam => "REGSAM",
SigType::LStatus => "LSTATUS",
SigType::ScHandle => "SC_HANDLE",
SigType::Ntstatus => "NTSTATUS",
SigType::HResult => "HRESULT",
SigType::PyObjectPtr => "PyObject *",
SigType::ConstPyObjectPtr => "const PyObject *",
SigType::PyObjectPtrPtr => "PyObject **",
SigType::PyTypeObjectPtr => "PyTypeObject *",
SigType::PyFrameObjectPtr => "PyFrameObject *",
SigType::PySsizeT => "Py_ssize_t",
SigType::PyHashT => "Py_hash_t",
SigType::PyCFunction => "PyCFunction",
SigType::PyRichCmpOp => "int",
}
}
pub fn to_inferred(&self) -> InferredType {
match self {
SigType::Void => InferredType::Unknown,
SigType::Bool => InferredType::Bool,
SigType::Int
| SigType::Long
| SigType::Fd
| SigType::SockFd
| SigType::LStatus
| SigType::Ntstatus
| SigType::HResult
| SigType::LResult
| SigType::LParam => InferredType::Signed,
SigType::UInt
| SigType::ULong
| SigType::SizeT
| SigType::DWord
| SigType::Word
| SigType::Byte
| SigType::RegSam
| SigType::Atom
| SigType::WParam => InferredType::Unsigned,
SigType::Handle
| SigType::HKey
| SigType::HModule
| SigType::Hwnd
| SigType::HIcon
| SigType::HBrush
| SigType::HDc
| SigType::HBitmap
| SigType::HFont
| SigType::HMenu
| SigType::HInstance
| SigType::ScHandle => InferredType::Pointer,
SigType::PySsizeT => InferredType::Signed,
SigType::PyHashT => InferredType::Signed,
SigType::PyRichCmpOp => InferredType::Signed,
SigType::PyObjectPtr
| SigType::ConstPyObjectPtr
| SigType::PyObjectPtrPtr
| SigType::PyTypeObjectPtr
| SigType::PyFrameObjectPtr
| SigType::PyCFunction => InferredType::Pointer,
SigType::CharPtr
| SigType::ConstCharPtr
| SigType::VoidPtr
| SigType::ConstVoidPtr
| SigType::FilePtr
| SigType::WCharPtr
| SigType::ConstWCharPtr
| SigType::LpVoid
| SigType::LpStr
| SigType::LpCStr
| SigType::LpWStr
| SigType::LpCWStr
| SigType::LpByte
| SigType::LpDWord
| SigType::PhKey => InferredType::Pointer,
}
}
}
#[derive(Debug, Clone)]
pub struct SigParam {
pub name: &'static str,
pub ty: SigType,
}
#[derive(Debug, Clone)]
pub struct FuncSig {
pub name: &'static str,
pub ret: SigType,
pub params: &'static [SigParam],
pub variadic: bool,
}
#[macro_export]
macro_rules! define_signatures {
( $( fn $name:ident ( $($params:tt)* ) $(-> $ret:ident)? ; )* ) => {
&[
$(
$crate::signatures::FuncSig {
name: stringify!($name),
ret: $crate::define_signatures!(@ret $($ret)?),
params: $crate::define_signatures!(@params $($params)*),
variadic: $crate::define_signatures!(@variadic $($params)*),
}
),*
]
};
(@ret $ret:ident) => { $crate::signatures::SigType::$ret };
(@ret) => { $crate::signatures::SigType::Void };
(@variadic) => { false };
(@variadic ...) => { true };
(@variadic $($name:ident : $ty:ident),+ , ...) => { true };
(@variadic $($name:ident : $ty:ident),+) => { false };
(@params) => { &[] };
(@params ...) => { &[] };
(@params $($name:ident : $ty:ident),+ , ...) => {
&[
$( $crate::signatures::SigParam { name: stringify!($name), ty: $crate::signatures::SigType::$ty } ),+
]
};
(@params $($name:ident : $ty:ident),+) => {
&[
$( $crate::signatures::SigParam { name: stringify!($name), ty: $crate::signatures::SigType::$ty } ),+
]
};
}
static SIGNATURE_MAP: LazyLock<HashMap<&'static str, &'static FuncSig>> = LazyLock::new(|| {
let mut map = HashMap::new();
for sig in crate::signatures_libc::LIBC_SIGNATURES {
map.insert(sig.name, sig);
}
for sig in crate::signatures_win32::WIN32_SIGNATURES {
map.insert(sig.name, sig);
}
for sig in crate::signatures_python::PYTHON_SIGNATURES {
map.insert(sig.name, sig);
}
for sig in crate::signatures_cxxabi::CXXABI_SIGNATURES {
map.insert(sig.name, sig);
}
for sig in crate::signatures_msvcrt::MSVCRT_SIGNATURES {
map.insert(sig.name, sig);
}
for sig in crate::signatures_crypto::CRYPTO_SIGNATURES {
map.insert(sig.name, sig);
}
load_embedded_tsv(&mut map);
load_embedded_qt_tsv(&mut map);
load_embedded_json(&mut map);
map
});
pub fn lookup(name: &str) -> Option<&'static FuncSig> {
if let Some(rt) = RUNTIME_SIGS.get() {
if let Some(sig) = rt.get(name) {
return Some(sig);
}
}
SIGNATURE_MAP.get(name).copied()
}
fn load_embedded_tsv(map: &mut HashMap<&'static str, &'static FuncSig>) {
use flate2::read::GzDecoder;
use std::io::Read;
let compressed = include_bytes!("../data/signatures.tsv.gz");
let mut decoder = GzDecoder::new(&compressed[..]);
let mut text = String::new();
if decoder.read_to_string(&mut text).is_err() {
return;
}
for line in text.lines() {
let parts: Vec<&str> = line.split('\t').collect();
if parts.len() < 4 {
continue;
}
let name = parts[0];
if map.contains_key(name) {
continue;
}
let ret = tsv_type_code(parts[1]);
let params: Vec<SigParam> = if parts[2].is_empty() {
Vec::new()
} else {
parts[2]
.split(',')
.map(|p| {
let mut it = p.splitn(2, ':');
let pname = it.next().unwrap_or("arg");
let ptype = tsv_type_code(it.next().unwrap_or("i"));
SigParam {
name: leak_str(pname),
ty: ptype,
}
})
.collect()
};
let variadic = parts[3] == "1";
let sig = FuncSig {
name: leak_str(name),
ret,
params: Box::leak(params.into_boxed_slice()),
variadic,
};
let sig_ref: &'static FuncSig = Box::leak(Box::new(sig));
map.insert(sig_ref.name, sig_ref);
}
}
fn load_embedded_qt_tsv(map: &mut HashMap<&'static str, &'static FuncSig>) {
use flate2::read::GzDecoder;
use std::io::Read;
let compressed = include_bytes!("../data/qt_signatures.tsv.gz");
let mut decoder = GzDecoder::new(&compressed[..]);
let mut text = String::new();
if decoder.read_to_string(&mut text).is_err() {
return;
}
for line in text.lines() {
let parts: Vec<&str> = line.split('\t').collect();
if parts.len() < 4 {
continue;
}
let name = parts[0];
if map.contains_key(name) {
continue;
}
let ret = tsv_type_code(parts[1]);
let params: Vec<SigParam> = if parts[2].is_empty() {
Vec::new()
} else {
parts[2]
.split(',')
.map(|p| {
let mut it = p.splitn(2, ':');
let pname = it.next().unwrap_or("arg");
let ptype = tsv_type_code(it.next().unwrap_or("i"));
SigParam {
name: leak_str(pname),
ty: ptype,
}
})
.collect()
};
let variadic = parts[3] == "1";
let sig = FuncSig {
name: leak_str(name),
ret,
params: Box::leak(params.into_boxed_slice()),
variadic,
};
let sig_ref: &'static FuncSig = Box::leak(Box::new(sig));
map.insert(sig_ref.name, sig_ref);
}
}
fn load_embedded_json(map: &mut HashMap<&'static str, &'static FuncSig>) {
let json_str = include_str!("../data/signatures.json");
let Ok(entries) = serde_json::from_str::<Vec<JsonSigEntry>>(json_str) else {
return;
};
for entry in &entries {
if map.contains_key(entry.name.as_str()) {
continue;
}
let params: Vec<SigParam> = entry
.params
.iter()
.map(|p| SigParam {
name: leak_str(&clean_param_name(&p.name)),
ty: ghidra_type_to_sigtype(&p.ty),
})
.collect();
let sig = FuncSig {
name: leak_str(&entry.name),
ret: ghidra_type_to_sigtype(&entry.ret),
params: Box::leak(params.into_boxed_slice()),
variadic: entry.variadic,
};
let sig_ref: &'static FuncSig = Box::leak(Box::new(sig));
map.insert(sig_ref.name, sig_ref);
}
}
fn tsv_type_code(code: &str) -> SigType {
match code {
"v" => SigType::Void,
"i" => SigType::Int,
"u" => SigType::UInt,
"l" => SigType::Long,
"U" => SigType::ULong,
"z" => SigType::SizeT,
"b" => SigType::Bool,
"s" => SigType::CharPtr,
"W" => SigType::WCharPtr,
"p" => SigType::VoidPtr,
"F" => SigType::FilePtr,
_ => SigType::Int,
}
}
struct RuntimeSigStore {
map: HashMap<String, &'static FuncSig>,
_storage: Vec<Box<dyn std::any::Any + Send + Sync>>,
}
impl RuntimeSigStore {
fn get(&self, name: &str) -> Option<&'static FuncSig> {
self.map.get(name).copied()
}
}
static RUNTIME_SIGS: std::sync::OnceLock<RuntimeSigStore> = std::sync::OnceLock::new();
static LEARNED_SIGS: std::sync::OnceLock<std::sync::Mutex<HashMap<u64, &'static FuncSig>>> =
std::sync::OnceLock::new();
pub fn register_learned_types(types: &[crate::LearnedFuncType]) {
let store = LEARNED_SIGS.get_or_init(|| std::sync::Mutex::new(HashMap::new()));
let mut map = store.lock().unwrap();
for lt in types {
if lt.param_types.iter().all(|t| t.is_none()) && lt.return_type.is_none() {
continue;
}
let params: Vec<SigParam> = lt
.param_types
.iter()
.enumerate()
.map(|(i, dt)| SigParam {
name: leak_str(&format!("param_{}", i)),
ty: match dt {
Some(s) => c_str_to_sigtype(s),
None => SigType::Int,
},
})
.collect();
let ret = match lt.return_type {
Some(s) => c_str_to_sigtype(s),
None => SigType::Void,
};
let sig = FuncSig {
name: leak_str(&format!("func_{:x}", lt.addr)),
ret,
params: Box::leak(params.into_boxed_slice()),
variadic: false,
};
map.insert(lt.addr, Box::leak(Box::new(sig)));
}
}
pub fn lookup_addr(addr: u64) -> Option<&'static FuncSig> {
let store = LEARNED_SIGS.get()?;
let map = store.lock().ok()?;
map.get(&addr).copied()
}
fn c_str_to_sigtype(s: &str) -> SigType {
match s {
"void" => SigType::Void,
"int" => SigType::Int,
"unsigned int" => SigType::UInt,
"long" => SigType::Long,
"unsigned long" => SigType::ULong,
"size_t" => SigType::SizeT,
"char *" => SigType::CharPtr,
"const char *" => SigType::ConstCharPtr,
"void *" => SigType::VoidPtr,
"const void *" => SigType::ConstVoidPtr,
"FILE *" => SigType::FilePtr,
"bool" => SigType::Bool,
"wchar_t *" => SigType::WCharPtr,
"const wchar_t *" => SigType::ConstWCharPtr,
"HANDLE" => SigType::Handle,
"DWORD" => SigType::DWord,
"WORD" => SigType::Word,
"BYTE" => SigType::Byte,
"LPVOID" => SigType::LpVoid,
"LPBYTE" => SigType::LpByte,
"LPDWORD" => SigType::LpDWord,
"HKEY" => SigType::HKey,
"HMODULE" => SigType::HModule,
"HWND" => SigType::Hwnd,
"HICON" => SigType::HIcon,
"HDC" => SigType::HDc,
"HMENU" => SigType::HMenu,
"HINSTANCE" => SigType::HInstance,
"LRESULT" => SigType::LResult,
"WPARAM" => SigType::WParam,
"LPARAM" => SigType::LParam,
"PHKEY" => SigType::PhKey,
"REGSAM" => SigType::RegSam,
"LSTATUS" => SigType::LStatus,
"SC_HANDLE" => SigType::ScHandle,
"NTSTATUS" => SigType::Ntstatus,
"HRESULT" => SigType::HResult,
_ => SigType::Int,
}
}
static LEARNED_STRUCTS: std::sync::OnceLock<std::sync::Mutex<HashMap<(u64, u32), String>>> =
std::sync::OnceLock::new();
pub fn register_learned_structs(structs: &[crate::LearnedStructParam]) {
let store = LEARNED_STRUCTS.get_or_init(|| std::sync::Mutex::new(HashMap::new()));
let mut map = store.lock().unwrap();
for sp in structs {
map.insert((sp.func_addr, sp.param_index), sp.struct_name.clone());
}
}
pub fn lookup_struct_param(func_addr: u64, param_index: u32) -> Option<String> {
let store = LEARNED_STRUCTS.get()?;
let map = store.lock().ok()?;
map.get(&(func_addr, param_index)).cloned()
}
pub fn lookup_all_struct_params(func_addr: u64) -> Vec<(u32, String)> {
let store = match LEARNED_STRUCTS.get() {
Some(s) => s,
None => return Vec::new(),
};
let map = match store.lock() {
Ok(m) => m,
Err(_) => return Vec::new(),
};
map.iter()
.filter(|((addr, _), _)| *addr == func_addr)
.map(|((_, idx), name)| (*idx, name.clone()))
.collect()
}
pub fn load_json(json_str: &str) -> Result<usize, String> {
let entries: Vec<JsonSigEntry> = serde_json::from_str(json_str)
.map_err(|e| format!("failed to parse signature JSON: {}", e))?;
let mut map = HashMap::new();
let mut storage: Vec<Box<dyn std::any::Any + Send + Sync>> = Vec::new();
for entry in &entries {
let params: Vec<SigParam> = entry
.params
.iter()
.map(|p| SigParam {
name: leak_str(&clean_param_name(&p.name)),
ty: ghidra_type_to_sigtype(&p.ty),
})
.collect();
let params_ref: &'static [SigParam] = Box::leak(params.into_boxed_slice());
let sig = FuncSig {
name: leak_str(&entry.name),
ret: ghidra_type_to_sigtype(&entry.ret),
params: params_ref,
variadic: entry.variadic,
};
let sig_ref: &'static FuncSig = Box::leak(Box::new(sig));
map.insert(entry.name.clone(), sig_ref);
}
let count = map.len();
storage.push(Box::new(()));
let _ = RUNTIME_SIGS.set(RuntimeSigStore {
map,
_storage: storage,
});
Ok(count)
}
pub fn load_json_file(path: &std::path::Path) -> Result<usize, String> {
let data = std::fs::read_to_string(path)
.map_err(|e| format!("cannot read {}: {}", path.display(), e))?;
load_json(&data)
}
fn leak_str(s: &str) -> &'static str {
Box::leak(s.to_string().into_boxed_str())
}
fn clean_param_name(name: &str) -> String {
let n = name.trim_start_matches('_');
if n.is_empty() {
return "arg".to_string();
}
match n {
"type" | "fn" | "mod" | "self" | "super" | "use" | "in" | "ref" | "mut" | "loop"
| "match" | "if" | "else" | "return" | "struct" | "enum" | "trait" | "impl" | "as"
| "where" | "break" | "continue" | "for" | "while" | "pub" | "const" | "static" | "let"
| "move" | "unsafe" => {
format!("{}_", n)
}
_ => n.to_string(),
}
}
fn ghidra_type_to_sigtype(ty: &str) -> SigType {
let t = ty.trim();
match t {
"void" => SigType::Void,
"int" => SigType::Int,
"uint" | "unsigned int" => SigType::UInt,
"long" => SigType::Long,
"ulong" | "unsigned long" => SigType::ULong,
"longlong" => SigType::Long,
"ulonglong" => SigType::ULong,
"short" | "ushort" | "char" | "uchar" | "unsigned char" | "byte" => SigType::Int,
"size_t" => SigType::SizeT,
"ssize_t" => SigType::Long,
"bool" | "BOOL" => SigType::Bool,
"HANDLE" => SigType::Handle,
"DWORD" => SigType::DWord,
"LPVOID" => SigType::LpVoid,
"FILE *" => SigType::FilePtr,
_ if t.ends_with(" *") || t.ends_with(" * *") => {
if t.contains("char") && !t.contains("* *") {
if t.contains("wchar") {
SigType::WCharPtr
} else {
SigType::CharPtr
}
} else {
SigType::VoidPtr
}
}
_ => SigType::Int,
}
}
#[derive(serde::Deserialize)]
struct JsonSigEntry {
name: String,
ret: String,
params: Vec<JsonParam>,
variadic: bool,
}
#[derive(serde::Deserialize)]
struct JsonParam {
name: String,
#[serde(rename = "type")]
ty: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lookup_printf() {
let sig = lookup("printf").expect("printf should exist");
assert_eq!(sig.name, "printf");
assert_eq!(sig.ret, SigType::Int);
assert!(sig.variadic);
assert_eq!(sig.params[0].name, "format");
assert_eq!(sig.params[0].ty, SigType::ConstCharPtr);
}
#[test]
fn lookup_malloc() {
let sig = lookup("malloc").expect("malloc should exist");
assert_eq!(sig.ret, SigType::VoidPtr);
assert_eq!(sig.params.len(), 1);
assert_eq!(sig.params[0].name, "size");
assert_eq!(sig.params[0].ty, SigType::SizeT);
}
#[test]
fn lookup_unknown() {
assert!(lookup("this_function_does_not_exist_xyz").is_none());
}
#[test]
fn lookup_win32() {
let sig = lookup("VirtualAlloc").expect("VirtualAlloc should exist");
assert_eq!(sig.name, "VirtualAlloc");
}
#[test]
fn lookup_python_richcompare() {
let sig = lookup("PyObject_RichCompare").expect("PyObject_RichCompare should exist");
assert_eq!(sig.ret, SigType::PyObjectPtr);
assert_eq!(sig.params.len(), 3);
assert_eq!(sig.params[0].ty, SigType::PyObjectPtr);
assert_eq!(sig.params[1].ty, SigType::PyObjectPtr);
assert_eq!(sig.params[2].ty, SigType::PyRichCmpOp);
}
#[test]
fn python_coverage() {
for name in [
"PyObject_GetAttr",
"PyObject_RichCompare",
"PyObject_Call",
"PyNumber_Add",
"PyNumber_Remainder",
"PyLong_FromLong",
"PyLong_AsLongLong",
"PyFloat_FromDouble",
"PyBytes_FromStringAndSize",
"PyBytes_AsString",
"PyUnicode_FromString",
"PyUnicode_AsUTF8",
"PyUnicode_Join",
"PyDict_New",
"PyDict_GetItem",
"PyDict_SetItemString",
"PyList_New",
"PyList_Append",
"PyTuple_New",
"PyTuple_Pack",
"PySet_New",
"PySet_Add",
"PySlice_New",
"PySequence_Contains",
"PyMapping_HasKey",
"PyErr_SetString",
"PyErr_Occurred",
"PyImport_ImportModule",
"PyModule_Create2",
"PyType_Ready",
"PyType_GenericNew",
"PyArg_ParseTuple",
"Py_BuildValue",
"PyEval_GetBuiltins",
] {
assert!(lookup(name).is_some(), "missing python sig: {}", name);
}
}
#[test]
fn libc_coverage() {
for name in [
"printf",
"fprintf",
"sprintf",
"snprintf",
"puts",
"fputs",
"fgets",
"fread",
"fwrite",
"fopen",
"fclose",
"fseek",
"ftell",
"feof",
"ferror",
"fflush",
"fputc",
"fgetc",
"putchar",
"getchar",
"malloc",
"calloc",
"realloc",
"free",
"atoi",
"atol",
"strtol",
"strtoul",
"exit",
"abort",
"abs",
"qsort",
"strlen",
"strcpy",
"strncpy",
"strcmp",
"strncmp",
"strcat",
"strncat",
"strchr",
"strrchr",
"strstr",
"memcpy",
"memset",
"memmove",
"memcmp",
"strerror",
"read",
"write",
"open",
"close",
"fork",
"execve",
"getpid",
"sleep",
"dup2",
"pipe",
"socket",
"bind",
"listen",
"accept",
"connect",
"send",
"recv",
"sendto",
"recvfrom",
"setsockopt",
"getsockopt",
"shutdown",
] {
assert!(lookup(name).is_some(), "missing libc sig: {}", name);
}
}
#[test]
fn win32_coverage() {
for name in [
"VirtualAlloc",
"VirtualFree",
"VirtualProtect",
"CreateFileA",
"CreateFileW",
"ReadFile",
"WriteFile",
"CloseHandle",
"GetProcAddress",
"LoadLibraryA",
"LoadLibraryW",
"GetModuleHandleA",
"GetModuleHandleW",
"CreateProcessA",
"CreateProcessW",
"CreateRemoteThread",
"WriteProcessMemory",
"GetLastError",
"SetLastError",
"HeapAlloc",
"HeapFree",
"RegOpenKeyExA",
"RegSetValueExA",
] {
assert!(lookup(name).is_some(), "missing win32 sig: {}", name);
}
}
#[test]
fn cxxabi_coverage() {
for name in [
"__cxa_throw",
"__cxa_allocate_exception",
"__cxa_begin_catch",
"__cxa_end_catch",
"__cxa_pure_virtual",
"__cxa_atexit",
"__cxa_finalize",
"__cxa_demangle",
"__cxa_guard_acquire",
"__cxa_guard_release",
"__dynamic_cast",
"_Unwind_RaiseException",
"_Unwind_Resume",
"_Unwind_GetIP",
"_Znwm",
"_Znam",
"_ZdlPv",
"_ZdaPv",
] {
assert!(lookup(name).is_some(), "missing C++ ABI sig: {}", name);
}
}
#[test]
fn msvcrt_coverage() {
for name in [
"__security_check_cookie",
"__report_gsfailure",
"_invalid_parameter",
"_CrtDbgReport",
"_CrtSetDbgFlag",
"__stdio_common_vfprintf",
"__stdio_common_vsprintf",
"_wfopen",
"fopen_s",
"strcpy_s",
"memcpy_s",
"_aligned_malloc",
"_aligned_free",
"_msize",
"_initterm",
"__p__commode",
] {
assert!(lookup(name).is_some(), "missing MSVC CRT sig: {}", name);
}
}
#[test]
fn crypto_coverage() {
for name in [
"EVP_MD_CTX_new",
"EVP_DigestInit_ex",
"EVP_DigestUpdate",
"EVP_sha256",
"EVP_aes_256_gcm",
"MD5",
"SHA1",
"SHA256",
"HMAC",
"RAND_bytes",
"BN_new",
"EVP_PKEY_new",
"RSA_new",
"RSA_public_encrypt",
"SSL_CTX_new",
"SSL_read",
"SSL_write",
"TLS_client_method",
"ERR_get_error",
"getrandom",
] {
assert!(lookup(name).is_some(), "missing crypto sig: {}", name);
}
}
#[test]
fn param_names_are_meaningful() {
let sig = lookup("memcpy").unwrap();
assert_eq!(sig.params[0].name, "dest");
assert_eq!(sig.params[1].name, "src");
assert_eq!(sig.params[2].name, "n");
}
#[test]
fn embedded_json_loads() {
for name in [
"mmap",
"pthread_create",
"dlopen",
"epoll_create",
"CreateToolhelp32Snapshot",
"Process32First",
"WSAStartup",
"InternetOpenA",
"IsDebuggerPresent",
"NtQueryInformationProcess",
"TlsAlloc",
] {
assert!(
lookup(name).is_some(),
"missing from embedded JSON: {}",
name
);
}
}
#[test]
fn total_signature_count() {
let count = SIGNATURE_MAP.len();
assert!(count >= 30000, "expected 30000+ signatures, got {}", count);
}
}