use std::{
collections::HashSet,
io::{self, Write},
sync::LazyLock,
};
use crate::{
assembly::{INSTRUCTIONS, INSTRUCTIONS_FE, INSTRUCTIONS_FE_MAX, INSTRUCTIONS_MAX},
file::pe::SectionTable,
metadata::{
method::Method,
signatures::{parse_type_spec_signature, SignatureParameter, TypeSignature},
tables::TypeSpecRaw,
token::Token,
typesystem::{CilPrimitive, CilPrimitiveData, CilType, CilTypeReference},
},
CilObject,
};
static ILASM_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
let mut set = HashSet::with_capacity(512);
for instr in &INSTRUCTIONS[..INSTRUCTIONS_MAX as usize] {
if !instr.instr.is_empty() {
set.insert(instr.instr);
}
}
for instr in &INSTRUCTIONS_FE[..INSTRUCTIONS_FE_MAX as usize] {
if !instr.instr.is_empty() {
set.insert(instr.instr);
}
}
for &kw in ILASM_KEYWORDS {
set.insert(kw);
}
set
});
const ILASM_KEYWORDS: &[&str] = &[
"void",
"bool",
"char",
"wchar",
"int",
"int8",
"int16",
"int32",
"int64",
"uint",
"uint8",
"uint16",
"uint32",
"uint64",
"float",
"float32",
"float64",
"refany",
"typedref",
"object",
"string",
"native",
"unsigned",
"value",
"valuetype",
"class",
"byreflike",
"vararg",
"default",
"stdcall",
"thiscall",
"fastcall",
"unmanaged",
"cdecl",
"static",
"public",
"private",
"family",
"final",
"sealed",
"abstract",
"auto",
"sequential",
"explicit",
"extended",
"ansi",
"unicode",
"autochar",
"import",
"enum",
"virtual",
"strict",
"forwarder",
"synchronized",
"interface",
"instance",
"specialname",
"rtspecialname",
"hidebysig",
"newslot",
"aggressiveinlining",
"pinvokeimpl",
"unmanagedexp",
"reqsecobj",
"noinlining",
"nooptimization",
"aggressiveoptimization",
"async",
"nested",
"assembly",
"famandassem",
"famorassem",
"privatescope",
"cil",
"il",
"optil",
"managed",
"preservesig",
"runtime",
"internalcall",
"beforefieldinit",
"forwardref",
"extends",
"implements",
"handler",
"finally",
"fault",
"catch",
"filter",
"request",
"demand",
"assert",
"deny",
"permitonly",
"linkcheck",
"inheritcheck",
"reqmin",
"reqopt",
"reqrefuse",
"prejitgrant",
"prejitdeny",
"noncasdemand",
"noncaslinkdemand",
"noncasinheritance",
"nomangle",
"lasterr",
"winapi",
"as",
"bestfit",
"charmaperror",
"on",
"off",
"marshal",
"custom",
"sysstring",
"fixed",
"variant",
"currency",
"syschar",
"decimal",
"date",
"bstr",
"tbstr",
"lpstr",
"lpwstr",
"lptstr",
"objectref",
"iunknown",
"idispatch",
"iidparam",
"struct",
"safearray",
"byvalstr",
"lpvoid",
"any",
"array",
"lpstruct",
"fromunmanaged",
"retainappdomain",
"callmostderived",
"in",
"out",
"opt",
"null",
"error",
"hresult",
"carray",
"userdefined",
"record",
"filetime",
"blob",
"stream",
"storage",
"streamed_object",
"stored_object",
"blob_object",
"cf",
"clsid",
"vector",
"nometadata",
"retargetable",
"windowsruntime",
"noplatform",
"legacy",
"library",
"x86",
"amd64",
"arm",
"arm64",
"extern",
"algorithm",
"tls",
"true",
"false",
"nullref",
"method",
"field",
"property",
"bytearray",
"to",
"at",
"pinned",
"modreq",
"modopt",
"serializable",
"type",
"initonly",
"literal",
"notserialized",
"flags",
"callconv",
"mdtoken",
"constraint",
"with",
"wrapper",
"init",
"alignment",
];
pub(super) fn write_indent(w: &mut dyn Write, depth: usize) -> io::Result<()> {
for _ in 0..depth {
write!(w, " ")?;
}
Ok(())
}
pub(super) fn quote_identifier(name: &str) -> String {
if name.contains('/') {
return name
.split('/')
.map(quote_single)
.collect::<Vec<_>>()
.join("/");
}
quote_single(name)
}
fn quote_single(name: &str) -> String {
let needs_quoting = name.contains('<')
|| name.contains('>')
|| name.contains('-')
|| name.contains('=')
|| name.contains('`')
|| name.starts_with(|c: char| c.is_ascii_digit())
|| ILASM_RESERVED.contains(name);
if needs_quoting {
format!("'{name}'")
} else {
name.to_string()
}
}
pub(super) fn hex_bytes(bytes: &[u8]) -> String {
bytes
.iter()
.map(|b| format!("{b:02X}"))
.collect::<Vec<_>>()
.join(" ")
}
pub(super) fn format_method_ref(method: &Method, asm: &CilObject) -> String {
let declaring_type = method
.declaring_type_fullname()
.map(|n| quote_identifier(&n));
format_method_call_sig(
method.signature.has_this && !method.is_static(),
&method.signature.return_type,
declaring_type.as_deref(),
&method.name,
&method.signature.params,
asm,
)
}
pub(super) fn assembly_scoped_name(cil_type: &CilType, asm: &CilObject) -> String {
if cil_type.token.table() == 0x1B && cil_type.enclosing_type().is_none() {
let base_name = strip_arity(&cil_type.name);
for entry in asm.types().iter() {
let td = entry.value();
if td.token.table() == 0x02
&& strip_arity(&td.name) == base_name
&& td.namespace == cil_type.namespace
&& td.enclosing_type().is_some()
{
return format_assembly_scoped(td);
}
}
}
format_assembly_scoped(cil_type)
}
fn strip_arity(name: &str) -> &str {
if let Some(pos) = name.rfind('`') {
if name[pos + 1..].chars().all(|c| c.is_ascii_digit()) {
return &name[..pos];
}
}
name
}
fn format_assembly_scoped(cil_type: &CilType) -> String {
let fullname = quote_identifier(&cil_type.fullname());
if let Some(asm_name) = find_assembly_ref(cil_type) {
return format!("[{asm_name}]{fullname}");
}
fullname
}
fn find_assembly_ref(cil_type: &CilType) -> Option<String> {
match cil_type.external() {
Some(CilTypeReference::AssemblyRef(aref)) => return Some(aref.name.clone()),
Some(CilTypeReference::TypeRef(parent_ref) | CilTypeReference::TypeDef(parent_ref)) => {
if let Some(parent) = parent_ref.upgrade() {
if let Some(name) = find_assembly_ref(&parent) {
return Some(name);
}
}
}
_ => {}
}
if let Some(enclosing) = cil_type.enclosing_type() {
if let Some(name) = find_assembly_ref(&enclosing) {
return Some(name);
}
}
None
}
pub(super) fn format_type_sig(sig: &TypeSignature, asm: &CilObject) -> String {
match sig {
TypeSignature::Class(token) => {
let name = asm
.types()
.get(token)
.map(|t| assembly_scoped_name(&t, asm))
.unwrap_or_else(|| format!("[{:08X}]", token.value()));
format!("class {name}")
}
TypeSignature::ValueType(token) => {
let name = asm
.types()
.get(token)
.map(|t| assembly_scoped_name(&t, asm))
.unwrap_or_else(|| format!("[{:08X}]", token.value()));
format!("valuetype {name}")
}
TypeSignature::SzArray(inner) => {
format!("{}[]", format_type_sig(&inner.base, asm))
}
TypeSignature::Array(arr) => {
format!("{}[{}]", format_type_sig(&arr.base, asm), arr.rank)
}
TypeSignature::Ptr(ptr) => {
format!("{}*", format_type_sig(&ptr.base, asm))
}
TypeSignature::ByRef(inner) => {
format!("{}&", format_type_sig(inner, asm))
}
TypeSignature::GenericInst(base, args) => {
let mut result = format_type_sig(base, asm);
result.push('<');
for (i, arg) in args.iter().enumerate() {
if i > 0 {
result.push_str(", ");
}
result.push_str(&format_type_sig(arg, asm));
}
result.push('>');
result
}
TypeSignature::Pinned(inner) => {
format!("pinned {}", format_type_sig(inner, asm))
}
TypeSignature::ModifiedRequired(modifiers) => {
let mut parts = Vec::new();
for m in modifiers {
let mod_name = asm
.types()
.get(&m.modifier_type)
.map(|t| assembly_scoped_name(&t, asm))
.unwrap_or_else(|| format!("{:08X}", m.modifier_type.value()));
parts.push(format!("modreq({mod_name})"));
}
parts.join(" ")
}
TypeSignature::ModifiedOptional(modifiers) => {
let mut parts = Vec::new();
for m in modifiers {
let mod_name = asm
.types()
.get(&m.modifier_type)
.map(|t| assembly_scoped_name(&t, asm))
.unwrap_or_else(|| format!("{:08X}", m.modifier_type.value()));
parts.push(format!("modopt({mod_name})"));
}
parts.join(" ")
}
TypeSignature::Void => "void".to_string(),
TypeSignature::Boolean => "bool".to_string(),
TypeSignature::Char => "char".to_string(),
TypeSignature::I1 => "int8".to_string(),
TypeSignature::U1 => "uint8".to_string(),
TypeSignature::I2 => "int16".to_string(),
TypeSignature::U2 => "uint16".to_string(),
TypeSignature::I4 => "int32".to_string(),
TypeSignature::U4 => "uint32".to_string(),
TypeSignature::I8 => "int64".to_string(),
TypeSignature::U8 => "uint64".to_string(),
TypeSignature::R4 => "float32".to_string(),
TypeSignature::R8 => "float64".to_string(),
TypeSignature::I => "native int".to_string(),
TypeSignature::U => "native uint".to_string(),
TypeSignature::String => "string".to_string(),
TypeSignature::Object => "object".to_string(),
TypeSignature::TypedByRef => "typedref".to_string(),
_ => sig.to_string(),
}
}
pub(super) fn format_sig_param(param: &SignatureParameter, asm: &CilObject) -> String {
let base = format_type_sig(¶m.base, asm);
if param.by_ref {
format!("{base}&")
} else {
base
}
}
pub(super) fn format_method_call_sig(
has_this: bool,
return_type: &SignatureParameter,
declaring_type: Option<&str>,
method_name: &str,
params: &[SignatureParameter],
asm: &CilObject,
) -> String {
let mut result = String::new();
if has_this {
result.push_str("instance ");
}
result.push_str(&format_sig_param(return_type, asm));
result.push(' ');
if let Some(decl) = declaring_type {
result.push_str(decl);
result.push_str("::");
}
result.push_str("e_identifier(method_name));
result.push('(');
for (i, param) in params.iter().enumerate() {
if i > 0 {
result.push_str(", ");
}
result.push_str(&format_sig_param(param, asm));
}
result.push(')');
result
}
pub(super) fn format_typespec_from_blob(asm: &CilObject, token: &Token) -> Option<String> {
let tables = asm.tables()?;
let table = tables.table::<TypeSpecRaw>()?;
let row = table.get(token.row())?;
let blob = asm.blob()?;
let sig_data = blob.get(row.signature as usize).ok()?;
let parsed = parse_type_spec_signature(sig_data).ok()?;
Some(format_type_sig(&parsed.base, asm))
}
pub(super) fn format_constant(value: &CilPrimitive) -> String {
match &value.data {
CilPrimitiveData::None => "nullref".to_string(),
CilPrimitiveData::Boolean(v) => format!("bool({v})"),
CilPrimitiveData::Char(v) => format!("char(0x{v:04X})"),
CilPrimitiveData::I1(v) => format!("int8(0x{:02X})", *v as u8),
CilPrimitiveData::U1(v) => format!("uint8(0x{v:02X})"),
CilPrimitiveData::I2(v) => format!("int16(0x{:04X})", *v as u16),
CilPrimitiveData::U2(v) => format!("uint16(0x{v:04X})"),
CilPrimitiveData::I4(v) => format!("int32(0x{:08X})", *v as u32),
CilPrimitiveData::U4(v) => format!("uint32(0x{v:08X})"),
CilPrimitiveData::I8(v) => format!("int64(0x{:X})", *v as u64),
CilPrimitiveData::U8(v) => format!("uint64(0x{v:X})"),
CilPrimitiveData::R4(v) => {
let formatted = format!("{v:.8}");
let round_tripped: f32 = formatted.parse().unwrap_or(f32::NAN);
if round_tripped.to_bits() == v.to_bits() {
format!("float32({formatted})")
} else {
format!("float32(0x{:08X})", v.to_bits())
}
}
CilPrimitiveData::R8(v) => {
let formatted = format!("{v:.17}");
let round_tripped: f64 = formatted.parse().unwrap_or(f64::NAN);
if round_tripped.to_bits() == v.to_bits() {
format!("float64({formatted})")
} else {
format!("float64(0x{:016X})", v.to_bits())
}
}
CilPrimitiveData::I(v) => format!("int32(0x{:08X})", *v as u32),
CilPrimitiveData::U(v) => format!("uint32(0x{:08X})", *v as u32),
CilPrimitiveData::String(v) => format!("\"{v}\""),
CilPrimitiveData::Bytes(v) => {
let mut result = String::from("bytearray (");
for (i, byte) in v.iter().enumerate() {
if i > 0 {
result.push(' ');
}
result.push_str(&format!("{byte:02X}"));
}
result.push(')');
result
}
}
}
pub(super) fn find_section_for_rva(sections: &[SectionTable], rva: u32) -> Option<&SectionTable> {
sections.iter().find(|s| {
let end = s.virtual_address.saturating_add(s.virtual_size);
rva >= s.virtual_address && rva < end
})
}
pub(super) fn write_blob_hex(w: &mut dyn Write, indent: &str, data: &[u8]) -> io::Result<()> {
write!(w, "{indent} ")?;
for (i, byte) in data.iter().enumerate() {
if i > 0 && i % 16 == 0 {
writeln!(w)?;
write!(w, "{indent} ")?;
}
write!(w, "{byte:02X} ")?;
}
Ok(())
}
pub(super) fn data_label_for_rva(
sections: &[SectionTable],
rva: u32,
) -> (&'static str, &'static str) {
match find_section_for_rva(sections, rva) {
Some(s) if s.name.starts_with(".tls") => ("T_", " tls"),
Some(s) if s.name.starts_with(".text") => ("I_", " cil"),
_ => ("D_", ""),
}
}