From 2e046bdcd182dba703b58680275ba5c6b49423cd Mon Sep 17 00:00:00 2001 From: RadonCoding <86915746+RadonCoding@users.noreply.github.com> Date: Sun, 6 Sep 2026 16:57:52 +0300 Subject: [PATCH 01/33] chore: initial infrastructure --- api/generated/binsafe.hpp | 9 +- api/templates/binsafe.hpp | 9 +- example.ps1 | 5 +- examples/cpp/bench/main.cpp | 64 ++ markers/build.rs | 2 + .../virtualization/attestation/mod.rs | 8 +- .../src/protections/virtualization/mod.rs | 57 +- runtime/src/lib.rs | 1 + runtime/src/obfuscation/immediate.rs | 19 +- runtime/src/obfuscation/mod.rs | 39 -- runtime/src/register.rs | 40 ++ runtime/src/runtime.rs | 34 +- runtime/src/vm/bytecode.rs | 69 +- runtime/src/vm/functions/crypt.rs | 6 +- runtime/src/vm/handlers/add.rs | 93 ++- runtime/src/vm/handlers/and.rs | 71 +- runtime/src/vm/handlers/bit_scan_reverse.rs | 27 +- runtime/src/vm/handlers/bit_test.rs | 32 +- .../src/vm/handlers/bit_test_complement.rs | 38 +- runtime/src/vm/handlers/bit_test_reset.rs | 38 +- runtime/src/vm/handlers/bit_test_set.rs | 38 +- runtime/src/vm/handlers/byte_swap.rs | 130 ++-- runtime/src/vm/handlers/mod.rs | 176 +---- runtime/src/vm/handlers/or.rs | 71 +- runtime/src/vm/handlers/rol.rs | 48 +- runtime/src/vm/handlers/ror.rs | 48 +- runtime/src/vm/handlers/sar.rs | 74 ++- runtime/src/vm/handlers/semantic.rs | 624 ++++++++++++++++++ runtime/src/vm/handlers/shl.rs | 74 ++- runtime/src/vm/handlers/shr.rs | 74 ++- runtime/src/vm/handlers/sub.rs | 93 ++- runtime/src/vm/handlers/trailing_zeros.rs | 50 +- runtime/src/vm/handlers/xor.rs | 71 +- runtime/src/vm/transform/permute.rs | 80 ++- runtime/src/vm/transform/scramble.rs | 21 +- tests/Cargo.toml | 1 + tests/src/instructions.rs | 101 ++- tests/src/lib.rs | 6 +- 38 files changed, 1932 insertions(+), 509 deletions(-) create mode 100644 examples/cpp/bench/main.cpp create mode 100644 runtime/src/register.rs create mode 100644 runtime/src/vm/handlers/semantic.rs diff --git a/api/generated/binsafe.hpp b/api/generated/binsafe.hpp index 96b4fd6e..2ac2931b 100644 --- a/api/generated/binsafe.hpp +++ b/api/generated/binsafe.hpp @@ -1,11 +1,10 @@ #pragma once #define BINSAFE_BEGIN() \ - __label__ _l##__LINE__; \ - __asm__ volatile(".byte 0xEB, 0x08, 0x42, 0x49, 0x4E, 0x53, 0x41, 0x46, 0x45, 0x01" ::: "memory"); \ - __asm__ goto("" :::: _l##__LINE__) + { \ + __asm__ volatile(".byte 0xEB, 0x08, 0x42, 0x49, 0x4E, 0x53, 0x41, 0x46, 0x45, 0x01" ::: "memory"); #define BINSAFE_END() \ - _l##__LINE__: \ - __asm__ volatile(".byte 0xEB, 0x08, 0x42, 0x49, 0x4E, 0x53, 0x41, 0x46, 0x45, 0x02" ::: "memory") + __asm__ volatile(".byte 0xEB, 0x08, 0x42, 0x49, 0x4E, 0x53, 0x41, 0x46, 0x45, 0x02" ::: "memory"); \ + } \ No newline at end of file diff --git a/api/templates/binsafe.hpp b/api/templates/binsafe.hpp index 5617c925..27b96a02 100644 --- a/api/templates/binsafe.hpp +++ b/api/templates/binsafe.hpp @@ -1,11 +1,10 @@ #pragma once #define BINSAFE_BEGIN() \ - __label__ _l##__LINE__; \ - __asm__ volatile(".byte {begin}" ::: "memory"); \ - __asm__ goto("" :::: _l##__LINE__) + { \ + __asm__ volatile(".byte {begin}" ::: "memory"); #define BINSAFE_END() \ - _l##__LINE__: \ - __asm__ volatile(".byte {end}" ::: "memory") + __asm__ volatile(".byte {end}" ::: "memory"); \ + } \ No newline at end of file diff --git a/example.ps1 b/example.ps1 index a3397a24..17e72b66 100644 --- a/example.ps1 +++ b/example.ps1 @@ -19,14 +19,15 @@ cargo build --bin obfuscator if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } switch ($language) { - "rust" { +"rust" { cargo build --manifest-path "$Example/Cargo.toml" if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } $metadata = cargo metadata --manifest-path "$Example/Cargo.toml" --format-version 1 --no-deps | ConvertFrom-Json - $package = $metadata.packages[0] + $manifest = (Resolve-Path "$Example/Cargo.toml").Path + $package = $metadata.packages | Where-Object { (Resolve-Path $_.manifest_path).Path -eq $manifest } $target = $package.targets | Where-Object { $_.kind -contains "bin" } | Select-Object -First 1 $source = Join-Path $metadata.target_directory "debug\$($target.name).exe" diff --git a/examples/cpp/bench/main.cpp b/examples/cpp/bench/main.cpp new file mode 100644 index 00000000..725cc34c --- /dev/null +++ b/examples/cpp/bench/main.cpp @@ -0,0 +1,64 @@ +#include +#include +#include +#include "../../../api/generated/binsafe.hpp" + +int main() { + uint32_t key[4] = {0xA3B1C2D3, 0xE4F50617, 0x28394A5B, 0x6C7D8E9F}; + uint32_t const delta = 0x9E3779B9; + + uint32_t data_native[2] = {0x12345678, 0x9ABCDEF0}; + + auto start_native = std::chrono::high_resolution_clock::now(); + + uint32_t v0_nat = data_native[0]; + uint32_t v1_nat = data_native[1]; + + for (int i = 0; i < 200000; ++i) { + uint32_t sum = 0; + for (uint32_t r = 0; r < 32; r++) { + v0_nat += (((v1_nat << 4) ^ (v1_nat >> 5)) + v1_nat) ^ (sum + key[sum & 3]); + sum += delta; + v1_nat += (((v0_nat << 4) ^ (v0_nat >> 5)) + v0_nat) ^ (sum + key[(sum >> 11) & 3]); + } + } + + data_native[0] = v0_nat; + data_native[1] = v1_nat; + + auto end_native = std::chrono::high_resolution_clock::now(); + + std::chrono::duration elapsed_native = end_native - start_native; + + uint32_t data_virtualized[2] = {0x12345678, 0x9ABCDEF0}; + + auto start_virtualized = std::chrono::high_resolution_clock::now(); + + + uint32_t v0_virtualized = data_virtualized[0]; + uint32_t v1_virtualized = data_virtualized[1]; + + for (int i = 0; i < 200000; ++i) { + uint32_t sum = 0; + + for (uint32_t r = 0; r < 32; r++) { + v0_virtualized += (((v1_virtualized << 4) ^ (v1_virtualized >> 5)) + v1_virtualized) ^ (sum + key[sum & 3]); + sum += delta; + v1_virtualized += (((v0_virtualized << 4) ^ (v0_virtualized >> 5)) + v0_virtualized) ^ (sum + key[(sum >> 11) & 3]); + } + } + + data_virtualized[0] = v0_virtualized; + data_virtualized[1] = v1_virtualized; + + + auto end_virtualized = std::chrono::high_resolution_clock::now(); + + std::chrono::duration elapsed_virtualized = end_virtualized - start_virtualized; + + std::cout << "Native: " << elapsed_native.count() << " ms" << std::endl; + std::cout << "Virtualized: " << elapsed_virtualized.count() << " ms" << std::endl; + std::cout << "Overhead: " << (elapsed_virtualized.count() / elapsed_native.count()) << "x" << std::endl; + + return 0; +} \ No newline at end of file diff --git a/markers/build.rs b/markers/build.rs index 4d396b5b..ba7c1ed3 100644 --- a/markers/build.rs +++ b/markers/build.rs @@ -24,6 +24,8 @@ fn main() { let output = PathBuf::from(output); let templates = PathBuf::from(templates); + println!("cargo:rerun-if-changed={}", templates.display()); + let begin = bytes_to_csv(&MARKER_BEGIN); let end = bytes_to_csv(&MARKER_END); diff --git a/obfuscator/src/protections/virtualization/attestation/mod.rs b/obfuscator/src/protections/virtualization/attestation/mod.rs index 88d4397b..ac82c8af 100644 --- a/obfuscator/src/protections/virtualization/attestation/mod.rs +++ b/obfuscator/src/protections/virtualization/attestation/mod.rs @@ -53,10 +53,14 @@ pub fn generate(engine: &mut Engine, key: u64) -> Vec>> { block.extend(timestamp()); block.extend(mask(None, !((1u64 << WINDOW) - 1))); - block.extend(load_data(engine, DataDef::VmKeyMul, VMWidth::Lower64)); + block.extend(load_data( + engine, + DataDef::VmKeyMultiplier, + VMWidth::Lower64, + )); block.extend(mul(None, None)); - block.extend(load_data(engine, DataDef::VmKeyAdd, VMWidth::Lower64)); + block.extend(load_data(engine, DataDef::VmKeyAddend, VMWidth::Lower64)); block.extend(add(None, None)); block.extend(reload_register(VMReg::Vt0)); diff --git a/obfuscator/src/protections/virtualization/mod.rs b/obfuscator/src/protections/virtualization/mod.rs index 53ea95c7..d2e2b561 100644 --- a/obfuscator/src/protections/virtualization/mod.rs +++ b/obfuscator/src/protections/virtualization/mod.rs @@ -20,29 +20,8 @@ mod attestation; pub mod crypt; mod language; -struct Keys { - seed: u64, - mul: u64, - add: u64, - att: u64, -} - -impl Default for Keys { - fn default() -> Self { - let mut rng = rand::thread_rng(); - - Self { - seed: rng.gen::(), - mul: rng.gen::(), - add: rng.gen::(), - att: rng.gen::(), - } - } -} - #[derive(Default)] pub struct Virtualization { - keys: Keys, programs: Vec>>, groups: Vec>, virtualized: HashMap, @@ -59,7 +38,7 @@ impl Virtualization { #[cfg(debug_assertions)] let mut log = Vec::new(); - let blocks = attestation::generate(engine, self.keys.att); + let blocks = attestation::generate(engine, engine.rt.keys.secret); for (_index, operations) in blocks.into_iter().enumerate() { let mut rng = rand::thread_rng(); @@ -84,13 +63,25 @@ impl Virtualization { let mut bytes = bytecode::assemble(&mut engine.rt.mapper, &transformed); let key = if vcode.is_empty() { - self.keys.seed + engine.rt.keys.initializer } else { crypt::derive_key(&vcode) }; - crypt::encrypt_block(&mut bytes, key, self.keys.mul, self.keys.add, 0); - crypt::decrypt_payload(&mut bytes, key, self.keys.mul, self.keys.add, 0); + crypt::encrypt_block( + &mut bytes, + key, + engine.rt.keys.multiplier, + engine.rt.keys.addend, + 0, + ); + crypt::decrypt_payload( + &mut bytes, + key, + engine.rt.keys.multiplier, + engine.rt.keys.addend, + 0, + ); vcode.extend_from_slice(&bytes); } @@ -101,7 +92,7 @@ impl Virtualization { debug!( "ATTESTATION @ 0x{:016X}:\n{}", - self.keys.att, + engine.rt.keys.secret, log.join("\n") ); } @@ -229,13 +220,13 @@ impl Protection for Virtualization { engine .rt - .define_data_qword(DataDef::VmKeySeed, self.keys.seed); + .define_data_qword(DataDef::VmKeyInitializer, engine.rt.keys.initializer); engine .rt - .define_data_qword(DataDef::VmKeyMul, self.keys.mul); + .define_data_qword(DataDef::VmKeyMultiplier, engine.rt.keys.multiplier); engine .rt - .define_data_qword(DataDef::VmKeyAdd, self.keys.add); + .define_data_qword(DataDef::VmKeyAddend, engine.rt.keys.addend); } fn apply(&self, engine: &mut Engine) { @@ -257,7 +248,13 @@ impl Protection for Virtualization { crypt::derive_key(&vcode) }; - crypt::encrypt_block(&mut bytes, key, self.keys.mul, self.keys.add, self.keys.att); + crypt::encrypt_block( + &mut bytes, + key, + engine.rt.keys.multiplier, + engine.rt.keys.addend, + engine.rt.keys.secret, + ); let offset = TryInto::::try_into(vcode.len()).unwrap(); vcode.extend_from_slice(&bytes); diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 874892ab..e0d3858d 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -3,6 +3,7 @@ pub mod debug; pub mod functions; pub mod mapper; mod obfuscation; +pub mod register; pub mod runtime; pub mod vm; diff --git a/runtime/src/obfuscation/immediate.rs b/runtime/src/obfuscation/immediate.rs index 0fd9d977..b6f545bc 100644 --- a/runtime/src/obfuscation/immediate.rs +++ b/runtime/src/obfuscation/immediate.rs @@ -7,7 +7,10 @@ use iced_x86::{ }; use rand::{seq::SliceRandom, Rng}; -use crate::obfuscation::{flags, scratch, sized, Operation}; +use crate::{ + obfuscation::{flags, scratch, Operation}, + register, +}; #[derive(Clone, Copy)] enum Condition { @@ -226,7 +229,7 @@ fn rewrite_immediate(instruction: &Instruction, scratch: Register) -> Option Option Option { let mut rewritten = instruction.clone(); - rewritten.set_memory_index(sized(scratch, 8)?); + rewritten.set_memory_index(register::sized(scratch, 8)?); rewritten.set_memory_index_scale(1); rewritten.set_memory_displacement64(0); rewritten.set_memory_displ_size(0); @@ -275,12 +278,12 @@ fn reconstruct( assembler: &mut CodeAssembler, rng: &mut impl Rng, ) { - let r1_8 = get_gpr8(sized(register1, 1).unwrap()).unwrap(); - let r1_16 = get_gpr16(sized(register1, 2).unwrap()).unwrap(); - let r1_32 = get_gpr32(sized(register1, 4).unwrap()).unwrap(); - let r1_64 = get_gpr64(sized(register1, 8).unwrap()).unwrap(); + let r1_8 = get_gpr8(register::sized(register1, 1).unwrap()).unwrap(); + let r1_16 = get_gpr16(register::sized(register1, 2).unwrap()).unwrap(); + let r1_32 = get_gpr32(register::sized(register1, 4).unwrap()).unwrap(); + let r1_64 = get_gpr64(register::sized(register1, 8).unwrap()).unwrap(); - let r2_64 = get_gpr64(sized(register2, 8).unwrap()).unwrap(); + let r2_64 = get_gpr64(register::sized(register2, 8).unwrap()).unwrap(); let operation = Operation::random(rng); diff --git a/runtime/src/obfuscation/mod.rs b/runtime/src/obfuscation/mod.rs index 14ca5bb6..137538b0 100644 --- a/runtime/src/obfuscation/mod.rs +++ b/runtime/src/obfuscation/mod.rs @@ -136,45 +136,6 @@ pub fn flags(operation: Operation, left: u64, right: u64, size: usize) -> Flags } } -fn sized(register: Register, size: usize) -> Option { - let number = register.number() as usize; - - match size { - 1 => match number { - 0 => Some(Register::AL), - 1 => Some(Register::CL), - 2 => Some(Register::DL), - 3 => Some(Register::BL), - 4 => Some(Register::SPL), - 5 => Some(Register::BPL), - 6 => Some(Register::SIL), - 7 => Some(Register::DIL), - 8 => Some(Register::R8L), - 9 => Some(Register::R9L), - 10 => Some(Register::R10L), - 11 => Some(Register::R11L), - 12 => Some(Register::R12L), - 13 => Some(Register::R13L), - 14 => Some(Register::R14L), - 15 => Some(Register::R15L), - _ => None, - }, - 2 => { - let base = register.full_register().number() as u32; - Some(Register::AX + base) - } - 4 => { - let base = register.full_register().number() as u32; - Some(Register::EAX + base) - } - 8 => { - let base = register.full_register().number() as u32; - Some(Register::RAX + base) - } - _ => None, - } -} - pub fn scratch(excluded: &[Register], rng: &mut impl Rng) -> Register { const REGISTERS: [Register; 12] = [ Register::RAX, diff --git a/runtime/src/register.rs b/runtime/src/register.rs new file mode 100644 index 00000000..0b898080 --- /dev/null +++ b/runtime/src/register.rs @@ -0,0 +1,40 @@ +use iced_x86::Register; + +pub fn sized(register: Register, size: usize) -> Option { + let number = register.number() as usize; + + match size { + 1 => match number { + 0 => Some(Register::AL), + 1 => Some(Register::CL), + 2 => Some(Register::DL), + 3 => Some(Register::BL), + 4 => Some(Register::SPL), + 5 => Some(Register::BPL), + 6 => Some(Register::SIL), + 7 => Some(Register::DIL), + 8 => Some(Register::R8L), + 9 => Some(Register::R9L), + 10 => Some(Register::R10L), + 11 => Some(Register::R11L), + 12 => Some(Register::R12L), + 13 => Some(Register::R13L), + 14 => Some(Register::R14L), + 15 => Some(Register::R15L), + _ => None, + }, + 2 => { + let base = register.full_register().number() as u32; + Some(Register::AX + base) + } + 4 => { + let base = register.full_register().number() as u32; + Some(Register::EAX + base) + } + 8 => { + let base = register.full_register().number() as u32; + Some(Register::RAX + base) + } + _ => None, + } +} diff --git a/runtime/src/runtime.rs b/runtime/src/runtime.rs index ee8296b6..2cc2fa2a 100644 --- a/runtime/src/runtime.rs +++ b/runtime/src/runtime.rs @@ -114,9 +114,9 @@ mapped! { VmCode, VmAttestation, VmTrampolines, - VmKeySeed, - VmKeyMul, - VmKeyAdd, + VmKeyInitializer, + VmKeyMultiplier, + VmKeyAddend, VehEnd, ImportAddresses, ImportNames, @@ -253,11 +253,33 @@ impl Dispatch { } } +pub struct Keys { + pub initializer: u64, + pub multiplier: u64, + pub addend: u64, + pub secret: u64, +} + +impl Default for Keys { + fn default() -> Self { + let mut rng = rand::thread_rng(); + + Self { + initializer: rng.gen::(), + multiplier: rng.gen::(), + addend: rng.gen::(), + secret: rng.gen::(), + } + } +} + pub struct Runtime { pub asm: CodeAssembler, pub nonce: u64, + pub keys: Keys, + pub function_labels: HashMap, pub data_labels: HashMap, pub bool_labels: HashMap, @@ -328,6 +350,8 @@ impl Runtime { nonce, + keys: Keys::default(), + function_labels, data_labels, bool_labels, @@ -705,6 +729,10 @@ impl Runtime { self.define_data_bytes(DataDef::Functions, &vec![0u8; FnDef::COUNT * 8]); + self.define_data_qword(DataDef::VmKeyInitializer, self.keys.initializer); + self.define_data_qword(DataDef::VmKeyAddend, self.keys.addend); + self.define_data_qword(DataDef::VmKeyMultiplier, self.keys.multiplier); + self.define_bool(BoolDef::IsLocked, false); self.define_bool(BoolDef::HasAvx, false); self.define_bool(BoolDef::HasVeh, false); diff --git a/runtime/src/vm/bytecode.rs b/runtime/src/vm/bytecode.rs index 6685e3f7..f4719daa 100644 --- a/runtime/src/vm/bytecode.rs +++ b/runtime/src/vm/bytecode.rs @@ -1,5 +1,7 @@ use core::panic; use std::any::Any; +#[cfg(debug_assertions)] +use std::cell::RefCell; use iced_x86::{Instruction, Mnemonic, Register}; use strum_macros::EnumIter; @@ -10,12 +12,13 @@ use crate::vm::lifters::{ arithmetic, branch, bsr, bswap, bt, cmov, cmpxchg, div, extend, integer, lea, multiply, pcmpeqb, pmovskb, rdtsc, scalar, set, stack, transfer, tzcnt, xadd, xchg, }; -#[cfg(debug_assertions)] use crate::vm::snapshot::Snapshots; use crate::vm::transform::encrypt::Encrypt; use crate::vm::transform::mutation::Mutation; use crate::vm::transform::peephole::Peephole; -use crate::vm::transform::{permute, scramble, Transform}; +use crate::vm::transform::permute::Permute; +use crate::vm::transform::scramble::Scramble; +use crate::vm::transform::Transform; mapped! { VMOp { @@ -272,6 +275,16 @@ impl VMWidth { other => other, } } + + pub fn mask(self) -> u64 { + let bits = (self.size() * 8).min(64); + + if bits == 64 { + u64::MAX + } else { + (1u64 << bits) - 1 + } + } } impl From for VMWidth { @@ -633,13 +646,20 @@ where { let mut operations = operations; - operations = Peephole.run(mapper, operations); - operations = permute::permute(operations, &mut picker); - operations = scramble::scramble(operations); - operations = Mutation.run(mapper, operations); - operations = Encrypt.run(mapper, operations); - operations = permute::permute(operations, &mut picker); - operations = Peephole.run(mapper, operations); + let picker = RefCell::<&mut dyn FnMut(&[usize]) -> usize>::new(&mut picker); + + let mut transforms = Vec::>::new(); + transforms.push(Box::new(Peephole)); + transforms.push(Box::new(Permute { picker: &picker })); + transforms.push(Box::new(Scramble)); + transforms.push(Box::new(Mutation)); + transforms.push(Box::new(Encrypt)); + transforms.push(Box::new(Permute { picker: &picker })); + transforms.push(Box::new(Peephole)); + + for transform in transforms { + operations = transform.run(mapper, operations); + } operations } @@ -658,26 +678,21 @@ where let mut snapshots = Snapshots::new(); snapshots.record(Phase::Lift, &operations); - operations = Peephole.run(mapper, operations); - snapshots.record(Phase::Peephole, &operations); - - operations = permute::permute(operations, &mut picker); - snapshots.record(Phase::Permute, &operations); - - operations = scramble::scramble(operations); - snapshots.record(Phase::Scramble, &operations); + let picker = RefCell::<&mut dyn FnMut(&[usize]) -> usize>::new(&mut picker); - operations = Mutation.run(mapper, operations); - snapshots.record(Mutation.phase(), &operations); + let mut transforms = Vec::>::new(); + transforms.push(Box::new(Peephole)); + transforms.push(Box::new(Permute { picker: &picker })); + transforms.push(Box::new(Scramble)); + transforms.push(Box::new(Mutation)); + transforms.push(Box::new(Encrypt)); + transforms.push(Box::new(Permute { picker: &picker })); + transforms.push(Box::new(Peephole)); - operations = Encrypt.run(mapper, operations); - snapshots.record(Encrypt.phase(), &operations); - - operations = permute::permute(operations, &mut picker); - snapshots.record(Phase::Permute, &operations); - - operations = Peephole.run(mapper, operations); - snapshots.record(Phase::Peephole, &operations); + for transform in transforms { + operations = transform.run(mapper, operations); + snapshots.record(transform.phase(), &operations); + } (operations, snapshots) } diff --git a/runtime/src/vm/functions/crypt.rs b/runtime/src/vm/functions/crypt.rs index 85ad99c7..3cfc50a9 100644 --- a/runtime/src/vm/functions/crypt.rs +++ b/runtime/src/vm/functions/crypt.rs @@ -244,7 +244,7 @@ pub fn build(rt: &mut Runtime) { { // mov rcx, [...] rt.asm - .mov(rcx, ptr(rt.data_labels[&DataDef::VmKeySeed])) + .mov(rcx, ptr(rt.data_labels[&DataDef::VmKeyInitializer])) .unwrap(); } @@ -286,13 +286,13 @@ pub fn build(rt: &mut Runtime) { // mov rax, [...] rt.asm - .mov(rax, ptr(rt.data_labels[&DataDef::VmKeyMul])) + .mov(rax, ptr(rt.data_labels[&DataDef::VmKeyMultiplier])) .unwrap(); // imul rcx, rax rt.asm.imul_2(rcx, rax).unwrap(); // mov rax, [...] rt.asm - .mov(rax, ptr(rt.data_labels[&DataDef::VmKeyAdd])) + .mov(rax, ptr(rt.data_labels[&DataDef::VmKeyAddend])) .unwrap(); // add rcx, rax rt.asm.add(rcx, rax).unwrap(); diff --git a/runtime/src/vm/handlers/add.rs b/runtime/src/vm/handlers/add.rs index c087180e..096f198f 100644 --- a/runtime/src/vm/handlers/add.rs +++ b/runtime/src/vm/handlers/add.rs @@ -1,10 +1,83 @@ -crate::vm::handlers::arithmetic!( - add, - r8, - crate::vm::bytecode::Flag::Carry.bit64() - | crate::vm::bytecode::Flag::Overflow.bit64() - | crate::vm::bytecode::Flag::Sign.bit64() - | crate::vm::bytecode::Flag::Auxiliary.bit64() - | crate::vm::bytecode::Flag::Zero.bit64() - | crate::vm::bytecode::Flag::Parity.bit64() -); +use crate::{ + runtime::Runtime, + vm::{ + bytecode::{Flag, VMWidth}, + handlers::semantic::{self, Compare, Effect, Expression, FlagDef, Operand, Operation}, + }, +}; + +pub fn build(rt: &mut Runtime) { + semantic::build( + rt, + &Operation { + effects: vec![Effect::Add( + Expression::Operand(Operand::A), + Expression::Operand(Operand::B), + )], + flags: vec![ + ( + Flag::Zero, + FlagDef::Compare(Compare::Equal( + Expression::Operand(Operand::Result), + Expression::Constant(0), + )), + ), + ( + Flag::Sign, + FlagDef::Compare(Compare::BitSet( + Expression::Operand(Operand::Result), + Expression::SignBit, + )), + ), + ( + Flag::Carry, + FlagDef::Compare(Compare::LessThan( + Expression::Operand(Operand::Result), + Expression::Operand(Operand::A), + )), + ), + ( + Flag::Overflow, + FlagDef::Compare(Compare::BitSet( + Expression::BitAnd( + Box::new(Expression::BitXor( + Box::new(Expression::Operand(Operand::A)), + Box::new(Expression::Operand(Operand::Result)), + )), + Box::new(Expression::BitXor( + Box::new(Expression::Operand(Operand::B)), + Box::new(Expression::Operand(Operand::Result)), + )), + ), + Expression::SignBit, + )), + ), + ( + Flag::Parity, + FlagDef::Parity(Expression::Operand(Operand::Result)), + ), + ( + Flag::Auxiliary, + FlagDef::Compare(Compare::BitSet( + Expression::BitXor( + Box::new(Expression::BitXor( + Box::new(Expression::Operand(Operand::A)), + Box::new(Expression::Operand(Operand::B)), + )), + Box::new(Expression::Operand(Operand::Result)), + ), + Expression::Constant(4), + )), + ), + ], + store: None, + widths: &[ + VMWidth::Lower64, + VMWidth::Lower32, + VMWidth::Lower16, + VMWidth::Lower8, + ], + operands: 2, + }, + ); +} diff --git a/runtime/src/vm/handlers/and.rs b/runtime/src/vm/handlers/and.rs index abbb6964..11224047 100644 --- a/runtime/src/vm/handlers/and.rs +++ b/runtime/src/vm/handlers/and.rs @@ -1,10 +1,61 @@ -crate::vm::handlers::arithmetic!( - and, - r8, - crate::vm::bytecode::Flag::Carry.bit64() - | crate::vm::bytecode::Flag::Overflow.bit64() - | crate::vm::bytecode::Flag::Sign.bit64() - | crate::vm::bytecode::Flag::Auxiliary.bit64() - | crate::vm::bytecode::Flag::Zero.bit64() - | crate::vm::bytecode::Flag::Parity.bit64() -); +use crate::{ + runtime::Runtime, + vm::{ + bytecode::{Flag, VMWidth}, + handlers::semantic::{self, Compare, Effect, Expression, FlagDef, Operand, Operation}, + }, +}; + +pub fn build(rt: &mut Runtime) { + semantic::build( + rt, + &Operation { + effects: vec![Effect::And( + Expression::Operand(Operand::A), + Expression::Operand(Operand::B), + )], + flags: vec![ + ( + Flag::Carry, + FlagDef::Compare(Compare::Equal( + Expression::Constant(1), + Expression::Constant(0), + )), + ), + ( + Flag::Overflow, + FlagDef::Compare(Compare::Equal( + Expression::Constant(1), + Expression::Constant(0), + )), + ), + ( + Flag::Sign, + FlagDef::Compare(Compare::BitSet( + Expression::Operand(Operand::Result), + Expression::SignBit, + )), + ), + ( + Flag::Zero, + FlagDef::Compare(Compare::Equal( + Expression::Operand(Operand::Result), + Expression::Constant(0), + )), + ), + ( + Flag::Parity, + FlagDef::Parity(Expression::Operand(Operand::Result)), + ), + ], + store: None, + widths: &[ + VMWidth::Lower64, + VMWidth::Lower32, + VMWidth::Lower16, + VMWidth::Lower8, + ], + operands: 2, + }, + ); +} diff --git a/runtime/src/vm/handlers/bit_scan_reverse.rs b/runtime/src/vm/handlers/bit_scan_reverse.rs index c52fa5b2..42e8a173 100644 --- a/runtime/src/vm/handlers/bit_scan_reverse.rs +++ b/runtime/src/vm/handlers/bit_scan_reverse.rs @@ -1 +1,26 @@ -crate::vm::handlers::arithmetic!(bsr, bitscan, crate::vm::bytecode::Flag::Zero.bit64()); +use crate::{ + runtime::Runtime, + vm::{ + bytecode::{Flag, VMWidth}, + handlers::semantic::{self, Compare, Effect, Expression, FlagDef, Operand, Operation}, + }, +}; + +pub fn build(rt: &mut Runtime) { + semantic::build( + rt, + &Operation { + effects: vec![Effect::Bsr(Expression::Operand(Operand::A))], + flags: vec![( + Flag::Zero, + FlagDef::Compare(Compare::Equal( + Expression::Operand(Operand::A), + Expression::Constant(0), + )), + )], + store: None, + widths: &[VMWidth::Lower64, VMWidth::Lower32, VMWidth::Lower16], + operands: 1, + }, + ); +} diff --git a/runtime/src/vm/handlers/bit_test.rs b/runtime/src/vm/handlers/bit_test.rs index ad1cfba7..01c764b6 100644 --- a/runtime/src/vm/handlers/bit_test.rs +++ b/runtime/src/vm/handlers/bit_test.rs @@ -1 +1,31 @@ -crate::vm::handlers::arithmetic!(bt, bitscan, crate::vm::bytecode::Flag::Carry.bit64()); +use crate::{ + runtime::Runtime, + vm::{ + bytecode::{Flag, VMWidth}, + handlers::semantic::{self, Compare, Expression, FlagDef, Operand, Operation}, + }, +}; + +pub fn build(rt: &mut Runtime) { + semantic::build( + rt, + &Operation { + effects: vec![], + flags: vec![( + Flag::Carry, + FlagDef::Compare(Compare::BitSet( + Expression::Operand(Operand::A), + Expression::Operand(Operand::B), + )), + )], + store: Some(Expression::Operand(Operand::A)), + widths: &[ + VMWidth::Lower64, + VMWidth::Lower32, + VMWidth::Lower16, + VMWidth::Lower8, + ], + operands: 2, + }, + ); +} diff --git a/runtime/src/vm/handlers/bit_test_complement.rs b/runtime/src/vm/handlers/bit_test_complement.rs index 3871a04a..ee43ddc7 100644 --- a/runtime/src/vm/handlers/bit_test_complement.rs +++ b/runtime/src/vm/handlers/bit_test_complement.rs @@ -1 +1,37 @@ -crate::vm::handlers::arithmetic!(btc, bitscan, crate::vm::bytecode::Flag::Carry.bit64()); +use crate::{ + runtime::Runtime, + vm::{ + bytecode::{Flag, VMWidth}, + handlers::semantic::{self, Compare, Effect, Expression, FlagDef, Operand, Operation}, + }, +}; + +pub fn build(rt: &mut Runtime) { + semantic::build( + rt, + &Operation { + effects: vec![Effect::Xor( + Expression::Operand(Operand::A), + Expression::BitShl( + Box::new(Expression::Constant(1)), + Box::new(Expression::Operand(Operand::B)), + ), + )], + flags: vec![( + Flag::Carry, + FlagDef::Compare(Compare::BitSet( + Expression::Operand(Operand::A), + Expression::Operand(Operand::B), + )), + )], + store: None, + widths: &[ + VMWidth::Lower64, + VMWidth::Lower32, + VMWidth::Lower16, + VMWidth::Lower8, + ], + operands: 2, + }, + ); +} diff --git a/runtime/src/vm/handlers/bit_test_reset.rs b/runtime/src/vm/handlers/bit_test_reset.rs index a99fa749..d3af5c2d 100644 --- a/runtime/src/vm/handlers/bit_test_reset.rs +++ b/runtime/src/vm/handlers/bit_test_reset.rs @@ -1 +1,37 @@ -crate::vm::handlers::arithmetic!(btr, bitscan, crate::vm::bytecode::Flag::Carry.bit64()); +use crate::{ + runtime::Runtime, + vm::{ + bytecode::{Flag, VMWidth}, + handlers::semantic::{self, Compare, Effect, Expression, FlagDef, Operand, Operation}, + }, +}; + +pub fn build(rt: &mut Runtime) { + semantic::build( + rt, + &Operation { + effects: vec![Effect::And( + Expression::Operand(Operand::A), + Expression::BitNot(Box::new(Expression::BitShl( + Box::new(Expression::Constant(1)), + Box::new(Expression::Operand(Operand::B)), + ))), + )], + flags: vec![( + Flag::Carry, + FlagDef::Compare(Compare::BitSet( + Expression::Operand(Operand::A), + Expression::Operand(Operand::B), + )), + )], + store: None, + widths: &[ + VMWidth::Lower64, + VMWidth::Lower32, + VMWidth::Lower16, + VMWidth::Lower8, + ], + operands: 2, + }, + ); +} diff --git a/runtime/src/vm/handlers/bit_test_set.rs b/runtime/src/vm/handlers/bit_test_set.rs index 5700844f..92550bbb 100644 --- a/runtime/src/vm/handlers/bit_test_set.rs +++ b/runtime/src/vm/handlers/bit_test_set.rs @@ -1 +1,37 @@ -crate::vm::handlers::arithmetic!(bts, bitscan, crate::vm::bytecode::Flag::Carry.bit64()); +use crate::{ + runtime::Runtime, + vm::{ + bytecode::{Flag, VMWidth}, + handlers::semantic::{self, Compare, Effect, Expression, FlagDef, Operand, Operation}, + }, +}; + +pub fn build(rt: &mut Runtime) { + semantic::build( + rt, + &Operation { + effects: vec![Effect::Or( + Expression::Operand(Operand::A), + Expression::BitShl( + Box::new(Expression::Constant(1)), + Box::new(Expression::Operand(Operand::B)), + ), + )], + flags: vec![( + Flag::Carry, + FlagDef::Compare(Compare::BitSet( + Expression::Operand(Operand::A), + Expression::Operand(Operand::B), + )), + )], + store: None, + widths: &[ + VMWidth::Lower64, + VMWidth::Lower32, + VMWidth::Lower16, + VMWidth::Lower8, + ], + operands: 2, + }, + ); +} diff --git a/runtime/src/vm/handlers/byte_swap.rs b/runtime/src/vm/handlers/byte_swap.rs index 5144dfc9..e9e6f471 100644 --- a/runtime/src/vm/handlers/byte_swap.rs +++ b/runtime/src/vm/handlers/byte_swap.rs @@ -1,50 +1,98 @@ -use iced_x86::code_asm::{al, eax, r12, r8, r8d, rax, rcx}; - use crate::{ runtime::Runtime, vm::{ bytecode::VMWidth, - utils::{self, scratch}, + handlers::semantic::{self, Effect, Expression, Operand, Operation}, }, }; -// unsigned char* (unsigned char*) pub fn build(rt: &mut Runtime) { - let mut wide = rt.asm.create_label(); - let mut epilogue = rt.asm.create_label(); - - // eax -> width - utils::bytecode::read_byte_zx(rt, rcx, eax); - - // load r8 - scratch::load(rt, r12, r8); - - // cmp al, ... - rt.asm - .cmp(al, rt.mapper.index(VMWidth::Lower64) as i32) - .unwrap(); - // je ... - rt.asm.je(wide).unwrap(); - - // bswap r8d - rt.asm.bswap(r8d).unwrap(); - // jmp ... - rt.asm.jmp(epilogue).unwrap(); - - rt.asm.set_label(&mut wide).unwrap(); - { - // bswap r8 - rt.asm.bswap(r8).unwrap(); - } - - rt.asm.set_label(&mut epilogue).unwrap(); - { - // store r8 - scratch::store(rt, r12, r8); - - // mov rax, rcx - rt.asm.mov(rax, rcx).unwrap(); - // ret - rt.asm.ret().unwrap(); - } + semantic::build( + rt, + &Operation { + effects: vec![ + Effect::Assign(Expression::BitAnd( + Box::new(Expression::BitShr( + Box::new(Expression::Operand(Operand::A)), + Box::new(Expression::Constant(56)), + )), + Box::new(Expression::Constant(0xFF)), + )), + Effect::Or( + Expression::Operand(Operand::Result), + Expression::BitAnd( + Box::new(Expression::BitShr( + Box::new(Expression::Operand(Operand::A)), + Box::new(Expression::Constant(40)), + )), + Box::new(Expression::Constant(0xFF00)), + ), + ), + Effect::Or( + Expression::Operand(Operand::Result), + Expression::BitAnd( + Box::new(Expression::BitShr( + Box::new(Expression::Operand(Operand::A)), + Box::new(Expression::Constant(24)), + )), + Box::new(Expression::Constant(0xFF_0000)), + ), + ), + Effect::Or( + Expression::Operand(Operand::Result), + Expression::BitAnd( + Box::new(Expression::BitShr( + Box::new(Expression::Operand(Operand::A)), + Box::new(Expression::Constant(8)), + )), + Box::new(Expression::Constant(0xFF_000000)), + ), + ), + Effect::Or( + Expression::Operand(Operand::Result), + Expression::BitAnd( + Box::new(Expression::BitShl( + Box::new(Expression::Operand(Operand::A)), + Box::new(Expression::Constant(8)), + )), + Box::new(Expression::Constant(0xFF_00000000)), + ), + ), + Effect::Or( + Expression::Operand(Operand::Result), + Expression::BitAnd( + Box::new(Expression::BitShl( + Box::new(Expression::Operand(Operand::A)), + Box::new(Expression::Constant(24)), + )), + Box::new(Expression::Constant(0xFF_0000000000)), + ), + ), + Effect::Or( + Expression::Operand(Operand::Result), + Expression::BitAnd( + Box::new(Expression::BitShl( + Box::new(Expression::Operand(Operand::A)), + Box::new(Expression::Constant(40)), + )), + Box::new(Expression::Constant(0xFF_000000000000)), + ), + ), + Effect::Or( + Expression::Operand(Operand::Result), + Expression::BitAnd( + Box::new(Expression::BitShl( + Box::new(Expression::Operand(Operand::A)), + Box::new(Expression::Constant(56)), + )), + Box::new(Expression::Constant(0xFF_00000000000000)), + ), + ), + ], + flags: vec![], + store: None, + widths: &[VMWidth::Lower64, VMWidth::Lower32], + operands: 1, + }, + ); } diff --git a/runtime/src/vm/handlers/mod.rs b/runtime/src/vm/handlers/mod.rs index 7f365812..2141e2e0 100644 --- a/runtime/src/vm/handlers/mod.rs +++ b/runtime/src/vm/handlers/mod.rs @@ -1,178 +1,3 @@ -#[macro_export] -macro_rules! __arithmetic { - ($rt:expr, $operation:ident, r8, $epilogue:expr) => { - $crate::vm::utils::width::dispatch( - $rt, - rax, - $epilogue, - Some(Box::new(|rt| { - rt.asm.$operation(r14, r8).unwrap(); - })), - Some(Box::new(|rt| { - rt.asm.$operation(r14d, r8d).unwrap(); - })), - None, - Some(Box::new(|rt| { - rt.asm.$operation(r14w, r8w).unwrap(); - })), - None, - Some(Box::new(|rt| { - rt.asm.$operation(r14b, r8b).unwrap(); - })), - None, - None, - None, - None, - None, - None, - ); - }; - ($rt:expr, $operation:ident, shift, $epilogue:expr) => { - $rt.asm.mov(cl, r8b).unwrap(); - $crate::vm::utils::width::dispatch( - $rt, - rax, - $epilogue, - Some(Box::new(|rt| { - rt.asm.$operation(r14, cl).unwrap(); - })), - Some(Box::new(|rt| { - rt.asm.$operation(r14d, cl).unwrap(); - })), - None, - Some(Box::new(|rt| { - rt.asm.$operation(r14w, cl).unwrap(); - })), - None, - Some(Box::new(|rt| { - rt.asm.$operation(r14b, cl).unwrap(); - })), - None, - None, - None, - None, - None, - None, - ); - }; - ($rt:expr, $operation:ident, bitscan, $epilogue:expr) => { - $crate::vm::utils::width::dispatch( - $rt, - rax, - $epilogue, - Some(Box::new(|rt| { - rt.asm.$operation(r14, r8).unwrap(); - })), - Some(Box::new(|rt| { - rt.asm.$operation(r14d, r8d).unwrap(); - })), - None, - Some(Box::new(|rt| { - rt.asm.$operation(r14w, r8w).unwrap(); - })), - None, - None, - None, - None, - None, - None, - None, - None, - ); - }; - ($rt:expr, $operation:ident, carry, $epilogue:expr) => { - // r9d -> flags - $crate::vm::utils::vreg::load_reg32($rt, r12, $crate::vm::bytecode::VMReg::Flags, r9d); - $crate::vm::utils::width::dispatch( - $rt, - rax, - $epilogue, - Some(Box::new(|rt| { - rt.asm.bt(r9d, 0i32).unwrap(); - rt.asm.$operation(r14, r8).unwrap(); - })), - Some(Box::new(|rt| { - rt.asm.bt(r9d, 0i32).unwrap(); - rt.asm.$operation(r14d, r8d).unwrap(); - })), - None, - Some(Box::new(|rt| { - rt.asm.bt(r9d, 0i32).unwrap(); - rt.asm.$operation(r14w, r8w).unwrap(); - })), - None, - Some(Box::new(|rt| { - rt.asm.bt(r9d, 0i32).unwrap(); - rt.asm.$operation(r14b, r8b).unwrap(); - })), - None, - None, - None, - None, - None, - None, - ); - }; -} - -#[macro_export] -macro_rules! arithmetic { - ($operation:ident, $register:ident, $mask:expr) => { - use crate::{ - runtime::{FnDef, Runtime}, - vm::utils::{self, scratch}, - }; - use iced_x86::code_asm::*; - - // unsigned char* (unsigned char*) - pub fn build(rt: &mut Runtime) { - let mut epilogue = rt.asm.create_label(); - - // push r13 - rt.asm.push(r13).unwrap(); - // push r14 - rt.asm.push(r14).unwrap(); - - // mov r13, rcx - rt.asm.mov(r13, rcx).unwrap(); - - // eax -> width - utils::bytecode::read_byte_zx(rt, r13, eax); - - // load r8 - scratch::load(rt, r12, r8); - // load r14 - scratch::load(rt, r12, r14); - - $crate::__arithmetic!(rt, $operation, $register, &mut epilogue); - - rt.asm.set_label(&mut epilogue).unwrap(); - { - // mov rcx, ... - rt.asm.mov(rcx, $mask).unwrap(); - // pushfq - rt.asm.pushfq().unwrap(); - // call ... - rt.asm.call(rt.function_labels[&FnDef::VmFlags]).unwrap(); - - // store r14 - scratch::store(rt, r12, r14); - - // mov rax, r13 - rt.asm.mov(rax, r13).unwrap(); - // pop r14 - rt.asm.pop(r14).unwrap(); - // pop r13 - rt.asm.pop(r13).unwrap(); - // ret - rt.asm.ret().unwrap(); - } - } - }; -} - -pub(crate) use arithmetic; - pub mod add; pub mod and; pub mod bit_scan_reverse; @@ -203,6 +28,7 @@ pub mod ret; pub mod rol; pub mod ror; pub mod sar; +pub mod semantic; pub mod shl; pub mod shr; pub mod store_extend; diff --git a/runtime/src/vm/handlers/or.rs b/runtime/src/vm/handlers/or.rs index 14fcfd2d..eb584b91 100644 --- a/runtime/src/vm/handlers/or.rs +++ b/runtime/src/vm/handlers/or.rs @@ -1,10 +1,61 @@ -crate::vm::handlers::arithmetic!( - or, - r8, - crate::vm::bytecode::Flag::Carry.bit64() - | crate::vm::bytecode::Flag::Overflow.bit64() - | crate::vm::bytecode::Flag::Sign.bit64() - | crate::vm::bytecode::Flag::Auxiliary.bit64() - | crate::vm::bytecode::Flag::Zero.bit64() - | crate::vm::bytecode::Flag::Parity.bit64() -); +use crate::{ + runtime::Runtime, + vm::{ + bytecode::{Flag, VMWidth}, + handlers::semantic::{self, Compare, Effect, Expression, FlagDef, Operand, Operation}, + }, +}; + +pub fn build(rt: &mut Runtime) { + semantic::build( + rt, + &Operation { + effects: vec![Effect::Or( + Expression::Operand(Operand::A), + Expression::Operand(Operand::B), + )], + flags: vec![ + ( + Flag::Carry, + FlagDef::Compare(Compare::Equal( + Expression::Constant(1), + Expression::Constant(0), + )), + ), + ( + Flag::Overflow, + FlagDef::Compare(Compare::Equal( + Expression::Constant(1), + Expression::Constant(0), + )), + ), + ( + Flag::Sign, + FlagDef::Compare(Compare::BitSet( + Expression::Operand(Operand::Result), + Expression::SignBit, + )), + ), + ( + Flag::Zero, + FlagDef::Compare(Compare::Equal( + Expression::Operand(Operand::Result), + Expression::Constant(0), + )), + ), + ( + Flag::Parity, + FlagDef::Parity(Expression::Operand(Operand::Result)), + ), + ], + store: None, + widths: &[ + VMWidth::Lower64, + VMWidth::Lower32, + VMWidth::Lower16, + VMWidth::Lower8, + ], + operands: 2, + }, + ); +} diff --git a/runtime/src/vm/handlers/rol.rs b/runtime/src/vm/handlers/rol.rs index 3b940f27..f82527c3 100644 --- a/runtime/src/vm/handlers/rol.rs +++ b/runtime/src/vm/handlers/rol.rs @@ -1,5 +1,43 @@ -crate::vm::handlers::arithmetic!( - rol, - shift, - crate::vm::bytecode::Flag::Carry.bit64() | crate::vm::bytecode::Flag::Overflow.bit64() -); +use crate::{ + runtime::Runtime, + vm::{ + bytecode::{Flag, VMWidth}, + handlers::semantic::{self, Compare, Effect, Expression, FlagDef, Operand, Operation}, + }, +}; + +pub fn build(rt: &mut Runtime) { + semantic::build( + rt, + &Operation { + effects: vec![Effect::Rol( + Expression::Operand(Operand::A), + Expression::Operand(Operand::B), + )], + flags: vec![ + ( + Flag::Carry, + FlagDef::Compare(Compare::BitSet( + Expression::Operand(Operand::Result), + Expression::Constant(0), + )), + ), + ( + Flag::Overflow, + FlagDef::Compare(Compare::Equal( + Expression::Constant(1), + Expression::Constant(0), + )), + ), + ], + store: None, + widths: &[ + VMWidth::Lower64, + VMWidth::Lower32, + VMWidth::Lower16, + VMWidth::Lower8, + ], + operands: 2, + }, + ); +} diff --git a/runtime/src/vm/handlers/ror.rs b/runtime/src/vm/handlers/ror.rs index 4ae03b21..cec63355 100644 --- a/runtime/src/vm/handlers/ror.rs +++ b/runtime/src/vm/handlers/ror.rs @@ -1,5 +1,43 @@ -crate::vm::handlers::arithmetic!( - ror, - shift, - crate::vm::bytecode::Flag::Carry.bit64() | crate::vm::bytecode::Flag::Overflow.bit64() -); +use crate::{ + runtime::Runtime, + vm::{ + bytecode::{Flag, VMWidth}, + handlers::semantic::{self, Compare, Effect, Expression, FlagDef, Operand, Operation}, + }, +}; + +pub fn build(rt: &mut Runtime) { + semantic::build( + rt, + &Operation { + effects: vec![Effect::Ror( + Expression::Operand(Operand::A), + Expression::Operand(Operand::B), + )], + flags: vec![ + ( + Flag::Carry, + FlagDef::Compare(Compare::BitSet( + Expression::Operand(Operand::Result), + Expression::SignBit + )), + ), + ( + Flag::Overflow, + FlagDef::Compare(Compare::Equal( + Expression::Constant(1), + Expression::Constant(0), + )), + ), + ], + store: None, + widths: &[ + VMWidth::Lower64, + VMWidth::Lower32, + VMWidth::Lower16, + VMWidth::Lower8, + ], + operands: 2, + }, + ); +} diff --git a/runtime/src/vm/handlers/sar.rs b/runtime/src/vm/handlers/sar.rs index 5473be73..37a56a2c 100644 --- a/runtime/src/vm/handlers/sar.rs +++ b/runtime/src/vm/handlers/sar.rs @@ -1,10 +1,64 @@ -crate::vm::handlers::arithmetic!( - sar, - shift, - crate::vm::bytecode::Flag::Carry.bit64() - | crate::vm::bytecode::Flag::Overflow.bit64() - | crate::vm::bytecode::Flag::Sign.bit64() - | crate::vm::bytecode::Flag::Auxiliary.bit64() - | crate::vm::bytecode::Flag::Zero.bit64() - | crate::vm::bytecode::Flag::Parity.bit64() -); +use crate::{ + runtime::Runtime, + vm::{ + bytecode::{Flag, VMWidth}, + handlers::semantic::{self, Compare, Effect, Expression, FlagDef, Operand, Operation}, + }, +}; + +pub fn build(rt: &mut Runtime) { + semantic::build( + rt, + &Operation { + effects: vec![Effect::Sar( + Expression::Operand(Operand::A), + Expression::Operand(Operand::B), + )], + flags: vec![ + ( + Flag::Carry, + FlagDef::Compare(Compare::BitSet( + Expression::Operand(Operand::A), + Expression::Sub( + Box::new(Expression::Operand(Operand::B)), + Box::new(Expression::Constant(1)), + ), + )), + ), + ( + Flag::Overflow, + FlagDef::Compare(Compare::Equal( + Expression::Constant(1), + Expression::Constant(0), + )), + ), + ( + Flag::Sign, + FlagDef::Compare(Compare::BitSet( + Expression::Operand(Operand::Result), + Expression::SignBit + )), + ), + ( + Flag::Zero, + FlagDef::Compare(Compare::Equal( + Expression::Operand(Operand::Result), + Expression::Constant(0), + )), + ), + ( + Flag::Parity, + FlagDef::Parity(Expression::Operand(Operand::Result)), + ), + ], + store: None, + widths: &[ + VMWidth::Lower64, + VMWidth::Lower32, + VMWidth::Lower16, + VMWidth::Lower8, + ], + operands: 2, + }, + ); +} diff --git a/runtime/src/vm/handlers/semantic.rs b/runtime/src/vm/handlers/semantic.rs new file mode 100644 index 00000000..823a64ff --- /dev/null +++ b/runtime/src/vm/handlers/semantic.rs @@ -0,0 +1,624 @@ +use iced_x86::code_asm::{ + cl, get_gpr32, get_gpr8, qword_ptr, r12, r13, r14, r8, r9, rax, rcx, rdx, rsp, AsmRegister64, + CodeLabel, +}; +use rand::Rng; + +use crate::{ + register, + runtime::Runtime, + vm::{ + bytecode::{Flag, VMReg, VMWidth}, + utils::{bytecode, scratch, vreg}, + }, +}; + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum Operand { + A, + B, + Result, +} + +#[derive(Clone)] +pub enum Expression { + Operand(Operand), + Constant(u64), + BitAnd(Box, Box), + BitOr(Box, Box), + BitXor(Box, Box), + BitShr(Box, Box), + BitShl(Box, Box), + BitNot(Box), + Sub(Box, Box), + LowByte(Box), + SignBit, +} + +#[derive(Clone)] +pub enum Effect { + Add(Expression, Expression), + Sub(Expression, Expression), + And(Expression, Expression), + Or(Expression, Expression), + Xor(Expression, Expression), + Shr(Expression, Expression), + Shl(Expression, Expression), + Ror(Expression, Expression), + Rol(Expression, Expression), + Mul(Expression, Expression), + Assign(Expression), + Sar(Expression, Expression), + Bsr(Expression), + Tzcnt(Expression), +} + +#[derive(Clone)] +pub enum Compare { + Equal(Expression, Expression), + LessThan(Expression, Expression), + GreaterThan(Expression, Expression), + BitSet(Expression, Expression), +} + +#[derive(Clone)] +pub enum FlagDef { + Compare(Compare), + Parity(Expression), +} + +pub struct Operation { + pub effects: Vec, + pub flags: Vec<(Flag, FlagDef)>, + pub store: Option, + pub widths: &'static [VMWidth], + pub operands: u8, +} + +#[derive(Clone)] +struct Allocator { + available: Vec, + protected: Vec, +} + +impl Allocator { + fn new() -> Self { + Self { + available: vec![rax, rcx, rdx, r8, r9], + protected: vec![], + } + } + + fn alloc(&mut self) -> AsmRegister64 { + let mut rng = rand::thread_rng(); + let index = rng.gen_range(0..self.available.len()); + self.available.swap_remove(index) + } + + fn free(&mut self, register: AsmRegister64) { + if !self.protected.contains(®ister) && !self.available.contains(®ister) { + self.available.push(register); + } + } + + fn protect(&mut self, register: AsmRegister64) { + self.protected.push(register); + } + + fn protected(&self, register: AsmRegister64) -> bool { + self.protected.contains(®ister) + } +} + +#[derive(Clone)] +struct Context { + first: AsmRegister64, + second: AsmRegister64, + result: AsmRegister64, +} + +pub fn build(rt: &mut Runtime, operation: &Operation) { + let mut epilogue = rt.asm.create_label(); + + rt.asm.push(r13).unwrap(); + rt.asm.push(r14).unwrap(); + + rt.asm.mov(r13, rcx).unwrap(); + + let mut allocator = Allocator::new(); + let index = allocator.alloc(); + + bytecode::read_byte_zx( + rt, + r13, + get_gpr32(register::sized(index.into(), 4).unwrap()).unwrap(), + ); + + allocator.available.retain(|&r| r != rcx); + + let first = allocator.alloc(); + let second = allocator.alloc(); + let result = allocator.alloc(); + + allocator.available.push(rcx); + + allocator.protect(first); + allocator.protect(second); + allocator.protect(result); + + let context = Context { + first, + second, + result, + }; + + if operation.operands > 1 { + scratch::load(rt, r12, second); + } + scratch::load(rt, r12, first); + + let handlers = operation + .widths + .iter() + .map(|&width| { + let actions = operation.effects.clone(); + let rules = operation.flags.clone(); + let context = context.clone(); + ( + width, + Box::new(move |rt: &mut Runtime, allocator: &mut Allocator| { + effects(rt, allocator, &context, &actions, width); + let temporary = allocator.alloc(); + rt.asm.mov(temporary, width.mask() as i64).unwrap(); + rt.asm.and(context.result, temporary).unwrap(); + allocator.free(temporary); + flags(rt, allocator, &context, &rules, width); + }) as Box, + ) + }) + .collect::)>>(); + + let cases = handlers + .iter() + .map( + |(width, _function): &(VMWidth, Box)| { + (rt.mapper.index(*width) as u8, rt.asm.create_label()) + }, + ) + .collect::>(); + + rt.jumps( + index, + cases + .iter() + .map(|(key, label): &(u8, CodeLabel)| (*key, *label)) + .collect::>(), + ); + + allocator.free(index); + + for ((_key, mut label), (_width, handler)) in cases.into_iter().zip(handlers.into_iter()) { + rt.asm.set_label(&mut label).unwrap(); + let mut local = allocator.clone(); + handler(rt, &mut local); + rt.asm.jmp(epilogue).unwrap(); + } + + rt.asm.set_label(&mut epilogue).unwrap(); + + match &operation.store { + Some(target) => { + let register = expression(rt, &mut allocator, &context, target, VMWidth::Lower64); + scratch::store(rt, r12, register); + allocator.free(register); + } + None => { + scratch::store(rt, r12, context.result); + } + } + + rt.asm.mov(rax, r13).unwrap(); + + rt.asm.pop(r14).unwrap(); + rt.asm.pop(r13).unwrap(); + rt.asm.ret().unwrap(); +} + +fn effects( + rt: &mut Runtime, + allocator: &mut Allocator, + context: &Context, + list: &[Effect], + width: VMWidth, +) { + for item in list { + effect(rt, allocator, context, item, width); + } +} + +fn effect( + rt: &mut Runtime, + allocator: &mut Allocator, + context: &Context, + item: &Effect, + width: VMWidth, +) { + macro_rules! binary { + ($rt:expr, $allocator:expr, $context:expr, $first:expr, $second:expr, $operation:ident, $width:expr) => {{ + let first = expression($rt, $allocator, $context, $first, $width); + let second = expression($rt, $allocator, $context, $second, $width); + if first != $context.result { + $rt.asm.mov($context.result, first).unwrap(); + } + $rt.asm.$operation($context.result, second).unwrap(); + $allocator.free(first); + $allocator.free(second); + }}; + } + + macro_rules! shift { + ($rt:expr, $allocator:expr, $context:expr, $first:expr, $second:expr, $operation:ident, $width:expr) => {{ + let second = expression($rt, $allocator, $context, $second, $width); + let busy = + !$allocator.available.contains(&rcx) && second != rcx && !$allocator.protected(rcx); + if busy { + $rt.asm.push(rcx).unwrap(); + } + if second != rcx { + $rt.asm.mov(rcx, second).unwrap(); + $allocator.free(second); + } + let available = $allocator.available.contains(&rcx); + if available { + $allocator.available.retain(|®ister| register != rcx); + } + + let first = expression($rt, $allocator, $context, $first, $width); + if first != $context.result { + $rt.asm.mov($context.result, first).unwrap(); + } + $rt.asm.$operation($context.result, cl).unwrap(); + $allocator.free(first); + + if busy { + $rt.asm.pop(rcx).unwrap(); + } else { + $allocator.free(rcx); + } + }}; + } + + match item { + Effect::Add(first, second) => binary!(rt, allocator, context, first, second, add, width), + Effect::Sub(first, second) => binary!(rt, allocator, context, first, second, sub, width), + Effect::And(first, second) => binary!(rt, allocator, context, first, second, and, width), + Effect::Or(first, second) => binary!(rt, allocator, context, first, second, or, width), + Effect::Xor(first, second) => binary!(rt, allocator, context, first, second, xor, width), + Effect::Mul(first, second) => binary!(rt, allocator, context, first, second, imul_2, width), + Effect::Shr(first, second) => shift!(rt, allocator, context, first, second, shr, width), + Effect::Shl(first, second) => shift!(rt, allocator, context, first, second, shl, width), + Effect::Ror(first, second) => shift!(rt, allocator, context, first, second, ror, width), + Effect::Rol(first, second) => shift!(rt, allocator, context, first, second, rol, width), + Effect::Sar(first, second) => shift!(rt, allocator, context, first, second, sar, width), + Effect::Assign(expr) => { + let value = expression(rt, allocator, context, expr, width); + if value != context.result { + rt.asm.mov(context.result, value).unwrap(); + allocator.free(value); + } + } + Effect::Bsr(expr) => { + let mut src = expression(rt, allocator, context, expr, width); + if allocator.protected(src) { + let temporary = allocator.alloc(); + rt.asm.mov(temporary, src).unwrap(); + src = temporary; + } + let mask = allocator.alloc(); + rt.asm.mov(mask, width.mask() as i64).unwrap(); + rt.asm.and(src, mask).unwrap(); + allocator.free(mask); + rt.asm.bsr(context.result, src).unwrap(); + allocator.free(src); + } + Effect::Tzcnt(expr) => { + let mut src = expression(rt, allocator, context, expr, width); + if allocator.protected(src) { + let temporary = allocator.alloc(); + rt.asm.mov(temporary, src).unwrap(); + src = temporary; + } + let mask = allocator.alloc(); + rt.asm.mov(mask, width.mask() as i64).unwrap(); + rt.asm.and(src, mask).unwrap(); + allocator.free(mask); + rt.asm.tzcnt(context.result, src).unwrap(); + allocator.free(src); + } + } +} + +fn expression( + rt: &mut Runtime, + allocator: &mut Allocator, + context: &Context, + node: &Expression, + width: VMWidth, +) -> AsmRegister64 { + match node { + Expression::Operand(Operand::A) => context.first, + Expression::Operand(Operand::B) => context.second, + Expression::Operand(Operand::Result) => context.result, + Expression::Constant(value) => { + let temporary = allocator.alloc(); + rt.asm.mov(temporary, *value as i64).unwrap(); + temporary + } + Expression::SignBit => { + let temporary = allocator.alloc(); + rt.asm.mov(temporary, width.mask().ilog2() as i64).unwrap(); + temporary + } + Expression::Sub(first, second) => { + let mut register = expression(rt, allocator, context, first, width); + if allocator.protected(register) { + let temporary = allocator.alloc(); + rt.asm.mov(temporary, register).unwrap(); + register = temporary; + } + let other = expression(rt, allocator, context, second, width); + rt.asm.sub(register, other).unwrap(); + allocator.free(other); + register + } + Expression::LowByte(inner) => { + let mut register = expression(rt, allocator, context, inner, width); + if allocator.protected(register) { + let temporary = allocator.alloc(); + rt.asm.mov(temporary, register).unwrap(); + register = temporary; + } + let byte = get_gpr8(register::sized(register.into(), 1).unwrap()).unwrap(); + rt.asm.movzx(register, byte).unwrap(); + register + } + Expression::BitNot(inner) => { + let mut register = expression(rt, allocator, context, inner, width); + if allocator.protected(register) { + let temporary = allocator.alloc(); + rt.asm.mov(temporary, register).unwrap(); + register = temporary; + } + rt.asm.not(register).unwrap(); + register + } + Expression::BitAnd(first, second) => { + let mut register = expression(rt, allocator, context, first, width); + if allocator.protected(register) { + let temporary = allocator.alloc(); + rt.asm.mov(temporary, register).unwrap(); + register = temporary; + } + let other = expression(rt, allocator, context, second, width); + rt.asm.and(register, other).unwrap(); + allocator.free(other); + register + } + Expression::BitOr(first, second) => { + let mut register = expression(rt, allocator, context, first, width); + if allocator.protected(register) { + let temporary = allocator.alloc(); + rt.asm.mov(temporary, register).unwrap(); + register = temporary; + } + let other = expression(rt, allocator, context, second, width); + rt.asm.or(register, other).unwrap(); + allocator.free(other); + register + } + Expression::BitXor(first, second) => { + let mut register = expression(rt, allocator, context, first, width); + if allocator.protected(register) { + let temporary = allocator.alloc(); + rt.asm.mov(temporary, register).unwrap(); + register = temporary; + } + let other = expression(rt, allocator, context, second, width); + rt.asm.xor(register, other).unwrap(); + allocator.free(other); + register + } + Expression::BitShr(first, second) => { + let other = expression(rt, allocator, context, second, width); + let busy = + !allocator.available.contains(&rcx) && other != rcx && !allocator.protected(rcx); + + if busy { + rt.asm.push(rcx).unwrap(); + } + if other != rcx { + rt.asm.mov(rcx, other).unwrap(); + allocator.free(other); + } + + let available = allocator.available.contains(&rcx); + + if available { + allocator.available.retain(|®ister| register != rcx); + } + + let mut register = expression(rt, allocator, context, first, width); + if allocator.protected(register) { + let temporary = allocator.alloc(); + rt.asm.mov(temporary, register).unwrap(); + register = temporary; + } + + rt.asm.shr(register, cl).unwrap(); + + if busy { + rt.asm.pop(rcx).unwrap(); + } else { + allocator.free(rcx); + } + register + } + Expression::BitShl(first, second) => { + let other = expression(rt, allocator, context, second, width); + + let busy = + !allocator.available.contains(&rcx) && other != rcx && !allocator.protected(rcx); + + if busy { + rt.asm.push(rcx).unwrap(); + } + + if other != rcx { + rt.asm.mov(rcx, other).unwrap(); + allocator.free(other); + } + + let available = allocator.available.contains(&rcx); + + if available { + allocator.available.retain(|®ister| register != rcx); + } + + let mut register = expression(rt, allocator, context, first, width); + + if allocator.protected(register) { + let temporary = allocator.alloc(); + rt.asm.mov(temporary, register).unwrap(); + register = temporary; + } + + rt.asm.shl(register, cl).unwrap(); + + if busy { + rt.asm.pop(rcx).unwrap(); + } else { + allocator.free(rcx); + } + register + } + } +} + +fn flags( + rt: &mut Runtime, + allocator: &mut Allocator, + context: &Context, + list: &[(Flag, FlagDef)], + width: VMWidth, +) { + let temporary = allocator.alloc(); + rt.asm.xor(temporary, temporary).unwrap(); + rt.asm.push(temporary).unwrap(); + allocator.free(temporary); + + for (flag, definition) in list { + match definition { + FlagDef::Compare(item) => compare(rt, allocator, context, *flag, item, width), + FlagDef::Parity(node) => parity(rt, allocator, context, *flag, node, width), + } + } + + let register = allocator.alloc(); + rt.asm.pop(register).unwrap(); + vreg::store_reg(rt, r12, register, VMReg::Flags); + allocator.free(register); +} + +fn compare( + rt: &mut Runtime, + allocator: &mut Allocator, + context: &Context, + flag: Flag, + compare: &Compare, + width: VMWidth, +) { + let temporary = match compare { + Compare::Equal(first, second) => { + let lhs = expression(rt, allocator, context, first, width); + let rhs = expression(rt, allocator, context, second, width); + rt.asm.cmp(lhs, rhs).unwrap(); + allocator.free(lhs); + allocator.free(rhs); + let result = allocator.alloc(); + let byte = get_gpr8(register::sized(result.into(), 1).unwrap()).unwrap(); + rt.asm.sete(byte).unwrap(); + rt.asm.movzx(result, byte).unwrap(); + result + } + Compare::LessThan(first, second) => { + let lhs = expression(rt, allocator, context, first, width); + let rhs = expression(rt, allocator, context, second, width); + rt.asm.cmp(lhs, rhs).unwrap(); + allocator.free(lhs); + allocator.free(rhs); + let result = allocator.alloc(); + let byte = get_gpr8(register::sized(result.into(), 1).unwrap()).unwrap(); + rt.asm.setb(byte).unwrap(); + rt.asm.movzx(result, byte).unwrap(); + result + } + Compare::GreaterThan(first, second) => { + let lhs = expression(rt, allocator, context, first, width); + let rhs = expression(rt, allocator, context, second, width); + rt.asm.cmp(lhs, rhs).unwrap(); + allocator.free(lhs); + allocator.free(rhs); + let result = allocator.alloc(); + let byte = get_gpr8(register::sized(result.into(), 1).unwrap()).unwrap(); + rt.asm.seta(byte).unwrap(); + rt.asm.movzx(result, byte).unwrap(); + result + } + Compare::BitSet(first, second) => { + let lhs = expression(rt, allocator, context, first, width); + let rhs = expression(rt, allocator, context, second, width); + rt.asm.bt(lhs, rhs).unwrap(); + allocator.free(lhs); + allocator.free(rhs); + let result = allocator.alloc(); + let byte = get_gpr8(register::sized(result.into(), 1).unwrap()).unwrap(); + rt.asm.setc(byte).unwrap(); + rt.asm.movzx(result, byte).unwrap(); + result + } + }; + + rt.asm + .shl(temporary, flag.bit32().trailing_zeros() as i32) + .unwrap(); + rt.asm.or(qword_ptr(rsp), temporary).unwrap(); + allocator.free(temporary); +} + +fn parity( + rt: &mut Runtime, + allocator: &mut Allocator, + context: &Context, + flag: Flag, + node: &Expression, + width: VMWidth, +) { + let mut temporary = expression(rt, allocator, context, node, width); + + if allocator.protected(temporary) { + let copy = allocator.alloc(); + rt.asm.mov(copy, temporary).unwrap(); + temporary = copy; + } + + let byte = get_gpr8(register::sized(temporary.into(), 1).unwrap()).unwrap(); + rt.asm.movzx(temporary, byte).unwrap(); + rt.asm.popcnt(temporary, temporary).unwrap(); + rt.asm.not(temporary).unwrap(); + rt.asm.and(temporary, 1i32).unwrap(); + rt.asm + .shl(temporary, flag.bit32().trailing_zeros() as i32) + .unwrap(); + rt.asm.or(qword_ptr(rsp), temporary).unwrap(); + allocator.free(temporary); +} diff --git a/runtime/src/vm/handlers/shl.rs b/runtime/src/vm/handlers/shl.rs index 1669752e..fc54eace 100644 --- a/runtime/src/vm/handlers/shl.rs +++ b/runtime/src/vm/handlers/shl.rs @@ -1,10 +1,64 @@ -crate::vm::handlers::arithmetic!( - shl, - shift, - crate::vm::bytecode::Flag::Carry.bit64() - | crate::vm::bytecode::Flag::Overflow.bit64() - | crate::vm::bytecode::Flag::Sign.bit64() - | crate::vm::bytecode::Flag::Auxiliary.bit64() - | crate::vm::bytecode::Flag::Zero.bit64() - | crate::vm::bytecode::Flag::Parity.bit64() -); +use crate::{ + runtime::Runtime, + vm::{ + bytecode::{Flag, VMWidth}, + handlers::semantic::{self, Compare, Effect, Expression, FlagDef, Operand, Operation}, + }, +}; + +pub fn build(rt: &mut Runtime) { + semantic::build( + rt, + &Operation { + effects: vec![Effect::Shl( + Expression::Operand(Operand::A), + Expression::Operand(Operand::B), + )], + flags: vec![ + ( + Flag::Carry, + FlagDef::Compare(Compare::BitSet( + Expression::Operand(Operand::A), + Expression::Sub( + Box::new(Expression::Constant(64)), + Box::new(Expression::Operand(Operand::B)), + ), + )), + ), + ( + Flag::Overflow, + FlagDef::Compare(Compare::Equal( + Expression::Constant(1), + Expression::Constant(0), + )), + ), + ( + Flag::Sign, + FlagDef::Compare(Compare::BitSet( + Expression::Operand(Operand::Result), + Expression::SignBit + )), + ), + ( + Flag::Zero, + FlagDef::Compare(Compare::Equal( + Expression::Operand(Operand::Result), + Expression::Constant(0), + )), + ), + ( + Flag::Parity, + FlagDef::Parity(Expression::Operand(Operand::Result)), + ), + ], + store: None, + widths: &[ + VMWidth::Lower64, + VMWidth::Lower32, + VMWidth::Lower16, + VMWidth::Lower8, + ], + operands: 2, + }, + ); +} diff --git a/runtime/src/vm/handlers/shr.rs b/runtime/src/vm/handlers/shr.rs index 89573329..e781339f 100644 --- a/runtime/src/vm/handlers/shr.rs +++ b/runtime/src/vm/handlers/shr.rs @@ -1,10 +1,64 @@ -crate::vm::handlers::arithmetic!( - shr, - shift, - crate::vm::bytecode::Flag::Carry.bit64() - | crate::vm::bytecode::Flag::Overflow.bit64() - | crate::vm::bytecode::Flag::Sign.bit64() - | crate::vm::bytecode::Flag::Auxiliary.bit64() - | crate::vm::bytecode::Flag::Zero.bit64() - | crate::vm::bytecode::Flag::Parity.bit64() -); +use crate::{ + runtime::Runtime, + vm::{ + bytecode::{Flag, VMWidth}, + handlers::semantic::{self, Compare, Effect, Expression, FlagDef, Operand, Operation}, + }, +}; + +pub fn build(rt: &mut Runtime) { + semantic::build( + rt, + &Operation { + effects: vec![Effect::Shr( + Expression::Operand(Operand::A), + Expression::Operand(Operand::B), + )], + flags: vec![ + ( + Flag::Carry, + FlagDef::Compare(Compare::BitSet( + Expression::Operand(Operand::A), + Expression::Sub( + Box::new(Expression::Operand(Operand::B)), + Box::new(Expression::Constant(1)), + ), + )), + ), + ( + Flag::Overflow, + FlagDef::Compare(Compare::Equal( + Expression::Constant(1), + Expression::Constant(0), + )), + ), + ( + Flag::Sign, + FlagDef::Compare(Compare::BitSet( + Expression::Operand(Operand::Result), + Expression::SignBit + )), + ), + ( + Flag::Zero, + FlagDef::Compare(Compare::Equal( + Expression::Operand(Operand::Result), + Expression::Constant(0), + )), + ), + ( + Flag::Parity, + FlagDef::Parity(Expression::Operand(Operand::Result)), + ), + ], + store: None, + widths: &[ + VMWidth::Lower64, + VMWidth::Lower32, + VMWidth::Lower16, + VMWidth::Lower8, + ], + operands: 2, + }, + ); +} diff --git a/runtime/src/vm/handlers/sub.rs b/runtime/src/vm/handlers/sub.rs index 87e3ee54..7d2463c6 100644 --- a/runtime/src/vm/handlers/sub.rs +++ b/runtime/src/vm/handlers/sub.rs @@ -1,10 +1,83 @@ -crate::vm::handlers::arithmetic!( - sub, - r8, - crate::vm::bytecode::Flag::Carry.bit64() - | crate::vm::bytecode::Flag::Overflow.bit64() - | crate::vm::bytecode::Flag::Sign.bit64() - | crate::vm::bytecode::Flag::Auxiliary.bit64() - | crate::vm::bytecode::Flag::Zero.bit64() - | crate::vm::bytecode::Flag::Parity.bit64() -); +use crate::{ + runtime::Runtime, + vm::{ + bytecode::{Flag, VMWidth}, + handlers::semantic::{self, Compare, Effect, Expression, FlagDef, Operand, Operation}, + }, +}; + +pub fn build(rt: &mut Runtime) { + semantic::build( + rt, + &Operation { + effects: vec![Effect::Sub( + Expression::Operand(Operand::A), + Expression::Operand(Operand::B), + )], + flags: vec![ + ( + Flag::Zero, + FlagDef::Compare(Compare::Equal( + Expression::Operand(Operand::Result), + Expression::Constant(0), + )), + ), + ( + Flag::Sign, + FlagDef::Compare(Compare::BitSet( + Expression::Operand(Operand::Result), + Expression::SignBit, + )), + ), + ( + Flag::Carry, + FlagDef::Compare(Compare::LessThan( + Expression::Operand(Operand::A), + Expression::Operand(Operand::B), + )), + ), + ( + Flag::Overflow, + FlagDef::Compare(Compare::BitSet( + Expression::BitAnd( + Box::new(Expression::BitXor( + Box::new(Expression::Operand(Operand::A)), + Box::new(Expression::Operand(Operand::B)), + )), + Box::new(Expression::BitXor( + Box::new(Expression::Operand(Operand::A)), + Box::new(Expression::Operand(Operand::Result)), + )), + ), + Expression::SignBit, + )), + ), + ( + Flag::Parity, + FlagDef::Parity(Expression::Operand(Operand::Result)), + ), + ( + Flag::Auxiliary, + FlagDef::Compare(Compare::BitSet( + Expression::BitXor( + Box::new(Expression::BitXor( + Box::new(Expression::Operand(Operand::A)), + Box::new(Expression::Operand(Operand::B)), + )), + Box::new(Expression::Operand(Operand::Result)), + ), + Expression::Constant(4), + )), + ), + ], + store: None, + widths: &[ + VMWidth::Lower64, + VMWidth::Lower32, + VMWidth::Lower16, + VMWidth::Lower8, + ], + operands: 2, + }, + ); +} diff --git a/runtime/src/vm/handlers/trailing_zeros.rs b/runtime/src/vm/handlers/trailing_zeros.rs index f630c76a..a11502ac 100644 --- a/runtime/src/vm/handlers/trailing_zeros.rs +++ b/runtime/src/vm/handlers/trailing_zeros.rs @@ -1,10 +1,40 @@ -crate::vm::handlers::arithmetic!( - tzcnt, - bitscan, - crate::vm::bytecode::Flag::Carry.bit64() - | crate::vm::bytecode::Flag::Overflow.bit64() - | crate::vm::bytecode::Flag::Sign.bit64() - | crate::vm::bytecode::Flag::Auxiliary.bit64() - | crate::vm::bytecode::Flag::Zero.bit64() - | crate::vm::bytecode::Flag::Parity.bit64() -); +use crate::{ + runtime::Runtime, + vm::{ + bytecode::{Flag, VMWidth}, + handlers::semantic::{self, Compare, Effect, Expression, FlagDef, Operand, Operation}, + }, +}; + +pub fn build(rt: &mut Runtime) { + semantic::build( + rt, + &Operation { + effects: vec![Effect::Tzcnt(Expression::Operand(Operand::A))], + flags: vec![ + ( + Flag::Carry, + FlagDef::Compare(Compare::Equal( + Expression::Operand(Operand::A), + Expression::Constant(0), + )), + ), + ( + Flag::Zero, + FlagDef::Compare(Compare::Equal( + Expression::Operand(Operand::Result), + Expression::Constant(0), + )), + ), + ], + store: None, + widths: &[ + VMWidth::Lower64, + VMWidth::Lower32, + VMWidth::Lower16, + VMWidth::Lower8, + ], + operands: 1, + }, + ); +} diff --git a/runtime/src/vm/handlers/xor.rs b/runtime/src/vm/handlers/xor.rs index 27ab3eaa..c58e81f2 100644 --- a/runtime/src/vm/handlers/xor.rs +++ b/runtime/src/vm/handlers/xor.rs @@ -1,10 +1,61 @@ -crate::vm::handlers::arithmetic!( - xor, - r8, - crate::vm::bytecode::Flag::Carry.bit64() - | crate::vm::bytecode::Flag::Overflow.bit64() - | crate::vm::bytecode::Flag::Sign.bit64() - | crate::vm::bytecode::Flag::Auxiliary.bit64() - | crate::vm::bytecode::Flag::Zero.bit64() - | crate::vm::bytecode::Flag::Parity.bit64() -); +use crate::{ + runtime::Runtime, + vm::{ + bytecode::{Flag, VMWidth}, + handlers::semantic::{self, Compare, Effect, Expression, FlagDef, Operand, Operation}, + }, +}; + +pub fn build(rt: &mut Runtime) { + semantic::build( + rt, + &Operation { + effects: vec![Effect::Xor( + Expression::Operand(Operand::A), + Expression::Operand(Operand::B), + )], + flags: vec![ + ( + Flag::Carry, + FlagDef::Compare(Compare::Equal( + Expression::Constant(1), + Expression::Constant(0), + )), + ), + ( + Flag::Overflow, + FlagDef::Compare(Compare::Equal( + Expression::Constant(1), + Expression::Constant(0), + )), + ), + ( + Flag::Sign, + FlagDef::Compare(Compare::BitSet( + Expression::Operand(Operand::Result), + Expression::Constant(63), + )), + ), + ( + Flag::Zero, + FlagDef::Compare(Compare::Equal( + Expression::Operand(Operand::Result), + Expression::Constant(0), + )), + ), + ( + Flag::Parity, + FlagDef::Parity(Expression::Operand(Operand::Result)), + ), + ], + store: None, + widths: &[ + VMWidth::Lower64, + VMWidth::Lower32, + VMWidth::Lower16, + VMWidth::Lower8, + ], + operands: 2, + }, + ); +} diff --git a/runtime/src/vm/transform/permute.rs b/runtime/src/vm/transform/permute.rs index 763e3444..e6c4a2c8 100644 --- a/runtime/src/vm/transform/permute.rs +++ b/runtime/src/vm/transform/permute.rs @@ -1,8 +1,9 @@ use std::any::Any; +use std::cell::RefCell; use std::collections::HashSet; use std::mem; -use crate::mapper::Mappable; +use crate::mapper::{Mappable, Mapper}; use crate::vm::bytecode::{VMMem, VMReg, VMVec, VMWidth}; use crate::vm::encoders::load_address::LoadAddress; use crate::vm::encoders::load_memory::LoadMemory; @@ -10,7 +11,7 @@ use crate::vm::encoders::load_register::LoadRegister; use crate::vm::encoders::store_memory::StoreMemory; use crate::vm::encoders::store_register::StoreRegister; use crate::vm::encoders::{identity, Effect, Encode}; -use crate::vm::transform::{branches, collapse, descend, effects, vacant}; +use crate::vm::transform::{branches, collapse, descend, effects, vacant, Phase, Transform}; struct Access { memory: VMMem, @@ -29,41 +30,54 @@ struct Profile { } /// Shuffles `operations` into a semantically equivalent sequence by using `picker` to select among available atomic blocks. -pub fn permute(mut operations: Vec>, picker: &mut F) -> Vec> -where - F: FnMut(&[usize]) -> usize, -{ - descend(&mut operations, |operations| { - let atoms = collapse(mem::take(operations)); - - let live = atoms - .iter() - .flat_map(|atom| effects(atom).0) - .collect::>(); - let (atoms, parked) = decouple(atoms, &live); - let (successors, mut indegree) = dependencies(&atoms); - - let n = successors.len(); - let mut ready = (0..n).filter(|&i| indegree[i] == 0).collect::>(); - let mut order = Vec::with_capacity(n); - - while !ready.is_empty() { - let chosen = ready.swap_remove(picker(&ready)); - order.push(chosen); - for &next in &successors[chosen] { - indegree[next] -= 1; - if indegree[next] == 0 { - ready.push(next); +pub struct Permute<'a> { + pub picker: &'a RefCell<&'a mut dyn FnMut(&[usize]) -> usize>, +} + +impl<'a> Transform for Permute<'a> { + fn phase(&self) -> Phase { + Phase::Permute + } + + fn run( + &self, + _mapper: &mut Mapper, + mut operations: Vec>, + ) -> Vec> { + let mut picker = self.picker.borrow_mut(); + + descend(&mut operations, |operations| { + let atoms = collapse(mem::take(operations)); + + let live = atoms + .iter() + .flat_map(|atom| effects(atom).0) + .collect::>(); + let (atoms, parked) = decouple(atoms, &live); + let (successors, mut indegree) = dependencies(&atoms); + + let n = successors.len(); + let mut ready = (0..n).filter(|&i| indegree[i] == 0).collect::>(); + let mut order = Vec::with_capacity(n); + + while !ready.is_empty() { + let chosen = ready.swap_remove(picker(&ready)); + order.push(chosen); + for &next in &successors[chosen] { + indegree[next] -= 1; + if indegree[next] == 0 { + ready.push(next); + } } } - } - let mut permutated = schedule(atoms, &order); - cleanup(&mut permutated, &parked); - *operations = permutated; - }); + let mut permutated = schedule(atoms, &order); + cleanup(&mut permutated, &parked); + *operations = permutated; + }); - operations + operations + } } /// Removes adjacent [`StoreRegister`]/[`LoadRegister`] pairs created by incomplete scheduling. diff --git a/runtime/src/vm/transform/scramble.rs b/runtime/src/vm/transform/scramble.rs index 5e911006..ff4bb71f 100644 --- a/runtime/src/vm/transform/scramble.rs +++ b/runtime/src/vm/transform/scramble.rs @@ -3,19 +3,32 @@ use std::mem; use rand::seq::SliceRandom; use rand::Rng; +use crate::mapper::Mapper; use crate::vm::bytecode::VMWidth; use crate::vm::encoders::block::{Block, Jump, Target}; use crate::vm::encoders::jcc::Jcc; use crate::vm::encoders::label::Label; use crate::vm::encoders::load_immediate::LoadImmediate; use crate::vm::encoders::Encode; -use crate::vm::transform::atomize; +use crate::vm::transform::{atomize, Phase, Transform}; /// Shuffles the physical order of atoms by chaining them in execution order through signed-offset [`Jcc`]s. -pub fn scramble(mut operations: Vec>) -> Vec> { - walk(&mut operations, true); +pub struct Scramble; - operations +impl Transform for Scramble { + fn phase(&self) -> Phase { + Phase::Scramble + } + + fn run( + &self, + _mapper: &mut Mapper, + mut operations: Vec>, + ) -> Vec> { + walk(&mut operations, true); + + operations + } } /// Recurses into existing blocks and scrambles the current level. diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 774e2585..e62ae718 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -16,3 +16,4 @@ windows = { version = "0.62.2", features = [ "Win32_System_Memory", "Win32_System_Threading", ] } +paste = "1.0.15" diff --git a/tests/src/instructions.rs b/tests/src/instructions.rs index 41ba0acf..5e7f1567 100644 --- a/tests/src/instructions.rs +++ b/tests/src/instructions.rs @@ -1,7 +1,7 @@ use std::slice; use iced_x86::MemoryOperand; -use iced_x86::Register::{AL, CL, EAX, RAX, RBX, RCX, XMM0, XMM1, XMM2}; +use iced_x86::Register::{AL, AX, CL, CX, EAX, ECX, RAX, RBX, RCX, XMM0, XMM1, XMM2}; use iced_x86::{Instruction, RflagsBits}; use runtime::vm::bytecode::{self, Flag, VMReg}; @@ -107,6 +107,29 @@ macro_rules! testing { }; } +macro_rules! testing_widths { + ($name:ident, $ins64:ident, $ins32:ident, $ins16:ident, $ins8:ident) => { + paste::paste! { + #[test] + fn [<$name _64>]() { + check(gpr(), instruction!($ins64, RAX, RCX)); + } + #[test] + fn [<$name _32>]() { + check(gpr(), instruction!($ins32, EAX, ECX)); + } + #[test] + fn [<$name _16>]() { + check(gpr(), instruction!($ins16, AX, CX)); + } + #[test] + fn [<$name _8>]() { + check(gpr(), instruction!($ins8, AL, CL)); + } + } + }; +} + macro_rules! testing_memory { ($name:ident, mut $buf:ident = $init:expr, $state:expr, $instruction:expr) => { #[test] @@ -228,7 +251,13 @@ testing_memory!( instruction!(Movsxd_r64_rm32, RCX, MemoryOperand::with_base(RAX)) ); -testing!(test_add, gpr(), instruction!(Add_rm64_r64, RAX, RCX)); +testing_widths!( + test_add, + Add_rm64_r64, + Add_rm32_r32, + Add_rm16_r16, + Add_rm8_r8 +); testing_memory!( test_add_load, buf = [IMM64_B], @@ -256,7 +285,13 @@ testing!( instruction!(Add_rm64_imm8, RAX, SIMM8_A) ); -testing!(test_sub, gpr(), instruction!(Sub_rm64_r64, RAX, RCX)); +testing_widths!( + test_sub, + Sub_rm64_r64, + Sub_rm32_r32, + Sub_rm16_r16, + Sub_rm8_r8 +); testing_memory!( test_sub_load, buf = [IMM64_B], @@ -284,10 +319,12 @@ testing!( instruction!(Sub_rm64_imm8, RAX, SIMM8_A) ); -testing!( +testing_widths!( test_adc, - gpr().with(VMReg::Flags, 0b0000000000000001u64), - instruction!(Adc_rm64_r64, RAX, RCX) + Adc_rm64_r64, + Adc_rm32_r32, + Adc_rm16_r16, + Adc_rm8_r8 ); testing_memory!( test_adc_load, @@ -318,10 +355,12 @@ testing!( instruction!(Adc_rm64_imm8, RAX, SIMM8_A) ); -testing!( +testing_widths!( test_sbb, - gpr().with(VMReg::Flags, 0b0000000000000001u64), - instruction!(Sbb_rm64_r64, RAX, RCX) + Sbb_rm64_r64, + Sbb_rm32_r32, + Sbb_rm16_r16, + Sbb_rm8_r8 ); testing_memory!( test_sbb_load, @@ -352,7 +391,13 @@ testing!( instruction!(Sbb_rm64_imm8, RAX, SIMM8_A) ); -testing!(test_cmp, gpr(), instruction!(Cmp_rm64_r64, RAX, RCX)); +testing_widths!( + test_cmp, + Cmp_rm64_r64, + Cmp_rm32_r32, + Cmp_rm16_r16, + Cmp_rm8_r8 +); testing_memory!( test_cmp_load, buf = [IMM64_B], @@ -380,7 +425,13 @@ testing!( instruction!(Cmp_rm64_imm8, RAX, SIMM8_A) ); -testing!(test_test, gpr(), instruction!(Test_rm64_r64, RAX, RCX)); +testing_widths!( + test_test, + Test_rm64_r64, + Test_rm32_r32, + Test_rm16_r16, + Test_rm8_r8 +); testing_memory!( test_test_store, buf = [IMM64_A], @@ -395,7 +446,13 @@ testing!( instruction!(Test_rm64_imm32, RAX, SIMM32_A) ); -testing!(test_and, gpr(), instruction!(And_rm64_r64, RAX, RCX)); +testing_widths!( + test_and, + And_rm64_r64, + And_rm32_r32, + And_rm16_r16, + And_rm8_r8 +); testing_memory!( test_and_load, buf = [IMM64_B], @@ -423,7 +480,7 @@ testing!( instruction!(And_rm64_imm8, RAX, SIMM8_A) ); -testing!(test_or, gpr(), instruction!(Or_rm64_r64, RAX, RCX)); +testing_widths!(test_or, Or_rm64_r64, Or_rm32_r32, Or_rm16_r16, Or_rm8_r8); testing_memory!( test_or_load, buf = [IMM64_B], @@ -451,7 +508,13 @@ testing!( instruction!(Or_rm64_imm8, RAX, SIMM8_A) ); -testing!(test_xor, gpr(), instruction!(Xor_rm64_r64, RAX, RCX)); +testing_widths!( + test_xor, + Xor_rm64_r64, + Xor_rm32_r32, + Xor_rm16_r16, + Xor_rm8_r8 +); testing_memory!( test_xor_load, buf = [IMM64_B], @@ -1374,16 +1437,6 @@ testing_simd_load!( [IMM128_B, 0u128], instruction!(Pmullw_xmm_xmmm128, XMM0, MemoryOperand::with_base(RAX)) ); -// testing!( -// test_pmulhw, -// simd(), -// instruction!(Pmulhw_xmm_xmmm128, XMM0, XMM1) -// ); -// testing_simd_load!( -// test_pmulhw_load, -// [IMM128_B, 0u128], -// instruction!(Pmulhw_xmm_xmmm128, XMM0, MemoryOperand::with_base(RAX)) -// ); testing!( test_pmulld, simd(), diff --git a/tests/src/lib.rs b/tests/src/lib.rs index f2322735..e8636653 100644 --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -135,9 +135,9 @@ impl Executor { let mut rt = Runtime::new(64); - rt.define_data_qword(DataDef::VmKeySeed, Self::TEST_KEY_SEED); - rt.define_data_qword(DataDef::VmKeyMul, Self::TEST_KEY_MUL); - rt.define_data_qword(DataDef::VmKeyAdd, Self::TEST_KEY_ADD); + rt.define_data_qword(DataDef::VmKeyInitializer, Self::TEST_KEY_SEED); + rt.define_data_qword(DataDef::VmKeyMultiplier, Self::TEST_KEY_MUL); + rt.define_data_qword(DataDef::VmKeyAddend, Self::TEST_KEY_ADD); rt.define_bool(BoolDef::HasVeh, true); From f39c8a3a1e0510ec470449c68abbf233d993b29f Mon Sep 17 00:00:00 2001 From: unknown <86915746+RadonCoding@users.noreply.github.com> Date: Tue, 8 Sep 2026 00:42:37 +0300 Subject: [PATCH 02/33] fix: marker scanning --- example.ps1 | 4 +- examples/cpp/bench/main.cpp | 4 +- examples/rust/bench/Cargo.toml | 8 +++ examples/rust/bench/src/main.rs | 80 ++++++++++++++++++++++++ examples/rust/hello/Cargo.toml | 2 + obfuscator/src/engine.rs | 37 ++++++----- runtime/src/vm/bytecode.rs | 14 ++--- runtime/src/vm/transform/mutation/jcc.rs | 2 - 8 files changed, 118 insertions(+), 33 deletions(-) create mode 100644 examples/rust/bench/Cargo.toml create mode 100644 examples/rust/bench/src/main.rs diff --git a/example.ps1 b/example.ps1 index 17e72b66..09190c2c 100644 --- a/example.ps1 +++ b/example.ps1 @@ -33,11 +33,11 @@ switch ($language) { $source = Join-Path $metadata.target_directory "debug\$($target.name).exe" } "cpp" { - g++ "$Example/main.cpp" + g++ "$Example/main.cpp" -o "$Example.exe" if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - $source = Join-Path $PWD "a.exe" + $source = Join-Path $PWD "$Example.exe" } } diff --git a/examples/cpp/bench/main.cpp b/examples/cpp/bench/main.cpp index 725cc34c..9abc4383 100644 --- a/examples/cpp/bench/main.cpp +++ b/examples/cpp/bench/main.cpp @@ -57,8 +57,8 @@ int main() { std::chrono::duration elapsed_virtualized = end_virtualized - start_virtualized; std::cout << "Native: " << elapsed_native.count() << " ms" << std::endl; - std::cout << "Virtualized: " << elapsed_virtualized.count() << " ms" << std::endl; - std::cout << "Overhead: " << (elapsed_virtualized.count() / elapsed_native.count()) << "x" << std::endl; + std::cout << "Virtualized: " << elapsed_virtualized.count() << " ms" << std::endl; + std::cout << "Overhead: " << (elapsed_virtualized.count() / elapsed_native.count()) << "x" << std::endl; return 0; } \ No newline at end of file diff --git a/examples/rust/bench/Cargo.toml b/examples/rust/bench/Cargo.toml new file mode 100644 index 00000000..16ea2df0 --- /dev/null +++ b/examples/rust/bench/Cargo.toml @@ -0,0 +1,8 @@ +[workspace] + +[package] +name = "bench" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/examples/rust/bench/src/main.rs b/examples/rust/bench/src/main.rs new file mode 100644 index 00000000..e6854e13 --- /dev/null +++ b/examples/rust/bench/src/main.rs @@ -0,0 +1,80 @@ +include!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../../api/generated/binsafe.rs" +)); + +use std::time::Instant; + +fn main() { + let key: [u32; 4] = [0xA3B1C2D3, 0xE4F50617, 0x28394A5B, 0x6C7D8E9F]; + let delta: u32 = 0x9E3779B9; + + let mut data_native: [u32; 2] = [0x12345678, 0x9ABCDEF0]; + + let start_native = Instant::now(); + + let mut v0_native = data_native[0]; + let mut v1_native = data_native[1]; + + for _ in 0..200000 { + let mut sum: u32 = 0; + + for _ in 0..32 { + v0_native = v0_native.wrapping_add( + (((v1_native << 4) ^ (v1_native >> 5)).wrapping_add(v1_native)) + ^ sum.wrapping_add(key[(sum & 3) as usize]), + ); + sum = sum.wrapping_add(delta); + v1_native = v1_native.wrapping_add( + (((v0_native << 4) ^ (v0_native >> 5)).wrapping_add(v0_native)) + ^ sum.wrapping_add(key[((sum >> 11) & 3) as usize]), + ); + } + } + + data_native[0] = v0_native; + data_native[1] = v1_native; + + let end_native = Instant::now(); + let elapsed_native = end_native.duration_since(start_native); + + let mut data_virtualized: [u32; 2] = [0x12345678, 0x9ABCDEF0]; + + let start_virtualized = Instant::now(); + + binsafe_begin!(); + + let mut v0_virtualized = data_virtualized[0]; + let mut v1_virtualized = data_virtualized[1]; + + for _ in 0..200000 { + let mut sum: u32 = 0; + + for _ in 0..32 { + v0_virtualized = v0_virtualized.wrapping_add( + (((v1_virtualized << 4) ^ (v1_virtualized >> 5)).wrapping_add(v1_virtualized)) + ^ sum.wrapping_add(key[(sum & 3) as usize]), + ); + sum = sum.wrapping_add(delta); + v1_virtualized = v1_virtualized.wrapping_add( + (((v0_virtualized << 4) ^ (v0_virtualized >> 5)).wrapping_add(v0_virtualized)) + ^ sum.wrapping_add(key[((sum >> 11) & 3) as usize]), + ); + } + } + + data_virtualized[0] = v0_virtualized; + data_virtualized[1] = v1_virtualized; + + binsafe_end!(); + + let end_virtualized = Instant::now(); + let elapsed_virtualized = end_virtualized.duration_since(start_virtualized); + + let native_ms = elapsed_native.as_secs_f64() * 1000.0; + let virtualized_ms = elapsed_virtualized.as_secs_f64() * 1000.0; + + println!("Native: {} ms", native_ms); + println!("Virtualized: {} ms", virtualized_ms); + println!("Overhead: {}x", virtualized_ms / native_ms); +} diff --git a/examples/rust/hello/Cargo.toml b/examples/rust/hello/Cargo.toml index f6f3649e..fbba8f5c 100644 --- a/examples/rust/hello/Cargo.toml +++ b/examples/rust/hello/Cargo.toml @@ -1,3 +1,5 @@ +[workspace] + [package] name = "hello" version = "0.1.0" diff --git a/obfuscator/src/engine.rs b/obfuscator/src/engine.rs index 84c9bef0..9a9effb7 100644 --- a/obfuscator/src/engine.rs +++ b/obfuscator/src/engine.rs @@ -139,14 +139,14 @@ impl<'a> Engine<'a> { let ip = code_section.virtual_address.0 as u64; let code = code_section.read(&self.pe).unwrap().to_vec(); - if self.scan_markers(ip, &code) { + if self.scan_markers(ip, &code, &code_section) { return; } self.scan_blocks(ip, &code, &code_section); } - fn scan_markers(&mut self, ip: u64, code: &[u8]) -> bool { + fn scan_markers(&mut self, ip: u64, code: &[u8], code_section: &ImageSectionHeader) -> bool { let mut markers = Vec::new(); let mut begin = None; let mut cursor = 0; @@ -173,6 +173,13 @@ impl<'a> Engine<'a> { info!("Found {} marked regions", markers.len()); + let data_references = self.collect_data_references(code, ip); + let mut code_references = self + .collect_code_references(code, ip, code_section, &data_references) + .into_iter() + .collect::>(); + code_references.sort(); + let mut capture = |block: &mut Vec, end: u32| { if block.is_empty() { return; @@ -191,11 +198,12 @@ impl<'a> Engine<'a> { }; for &(start, end) in &markers { - let rva = ip as u32 + start as u32; + let ip = ip as u32 + start as u32; + let mut decoder = Decoder::with_ip( self.bitness, &code[start..end], - rva as u64, + ip as u64, DecoderOptions::NONE, ); @@ -203,6 +211,12 @@ impl<'a> Engine<'a> { let mut block = Vec::new(); while decoder.can_decode() { + let rva = decoder.ip() as u32; + + if !block.is_empty() && code_references.binary_search(&rva).is_ok() { + capture(&mut block, rva); + } + decoder.decode_out(&mut instruction); if instruction.is_invalid() { @@ -316,21 +330,6 @@ impl<'a> Engine<'a> { } info!("Found {} blocks", self.blocks.len()); - - // let start = 1230; - // let end = 1335; - // let middle = start + (end - start) / 2; - - // self.blocks.drain(end..); - // self.blocks.drain(..start); - - // info!("{} to {} (middle: {})", start, end, middle); - - // println!( - // "0x{:016X} {}", - // self.pe.get_image_base().unwrap() + self.blocks[0].rva as u64, - // self.blocks[0] - // ); } fn collect_data_references(&self, code: &[u8], ip: u64) -> HashSet { diff --git a/runtime/src/vm/bytecode.rs b/runtime/src/vm/bytecode.rs index f4719daa..71a34a30 100644 --- a/runtime/src/vm/bytecode.rs +++ b/runtime/src/vm/bytecode.rs @@ -1,17 +1,10 @@ -use core::panic; -use std::any::Any; -#[cfg(debug_assertions)] -use std::cell::RefCell; - -use iced_x86::{Instruction, Mnemonic, Register}; -use strum_macros::EnumIter; - use crate::mapper::{mapped, Mapper}; use crate::vm::encoders::Encode; use crate::vm::lifters::{ arithmetic, branch, bsr, bswap, bt, cmov, cmpxchg, div, extend, integer, lea, multiply, pcmpeqb, pmovskb, rdtsc, scalar, set, stack, transfer, tzcnt, xadd, xchg, }; +#[cfg(debug_assertions)] use crate::vm::snapshot::Snapshots; use crate::vm::transform::encrypt::Encrypt; use crate::vm::transform::mutation::Mutation; @@ -19,6 +12,11 @@ use crate::vm::transform::peephole::Peephole; use crate::vm::transform::permute::Permute; use crate::vm::transform::scramble::Scramble; use crate::vm::transform::Transform; +use core::panic; +use iced_x86::{Instruction, Mnemonic, Register}; +use std::any::Any; +use std::cell::RefCell; +use strum_macros::EnumIter; mapped! { VMOp { diff --git a/runtime/src/vm/transform/mutation/jcc.rs b/runtime/src/vm/transform/mutation/jcc.rs index fe58401b..c7b06d0c 100644 --- a/runtime/src/vm/transform/mutation/jcc.rs +++ b/runtime/src/vm/transform/mutation/jcc.rs @@ -58,7 +58,6 @@ pub fn mutate(operations: &mut Vec>, rng: &mut R) { jcc.conditions.push(y); } } - 1 => { // X OR !X == 1. jcc.logic = or; @@ -70,7 +69,6 @@ pub fn mutate(operations: &mut Vec>, rng: &mut R) { jcc.conditions.push(random(&flags, rng)); } } - _ => { // X AND 1 == X. jcc.logic = and; From 78feeea8ccf20cce2596bfd63435ff3cbeffe036 Mon Sep 17 00:00:00 2001 From: RadonCoding <86915746+RadonCoding@users.noreply.github.com> Date: Tue, 8 Sep 2026 06:43:57 +0300 Subject: [PATCH 03/33] Update engine.rs --- obfuscator/src/engine.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/obfuscator/src/engine.rs b/obfuscator/src/engine.rs index 9a9effb7..847b59c0 100644 --- a/obfuscator/src/engine.rs +++ b/obfuscator/src/engine.rs @@ -213,8 +213,10 @@ impl<'a> Engine<'a> { while decoder.can_decode() { let rva = decoder.ip() as u32; - if !block.is_empty() && code_references.binary_search(&rva).is_ok() { - capture(&mut block, rva); + if code_references.binary_search(&rva).is_ok() { + if !block.is_empty() { + capture(&mut block, rva); + } } decoder.decode_out(&mut instruction); From 35f03d35c36170a35511eafc19b4f70a42ffb5e2 Mon Sep 17 00:00:00 2001 From: RadonCoding <86915746+RadonCoding@users.noreply.github.com> Date: Fri, 11 Sep 2026 20:53:50 +0300 Subject: [PATCH 04/33] fix: fixed marker scanner --- examples/cpp/bench/main.cpp | 6 +- examples/rust/bench/Cargo.toml | 8 - examples/rust/bench/src/main.rs | 80 ------- obfuscator/src/engine.rs | 224 +++++++----------- .../virtualization/attestation/mod.rs | 2 + .../src/protections/virtualization/mod.rs | 44 ++-- runtime/src/vm/handlers/semantic.rs | 2 + 7 files changed, 114 insertions(+), 252 deletions(-) delete mode 100644 examples/rust/bench/Cargo.toml delete mode 100644 examples/rust/bench/src/main.rs diff --git a/examples/cpp/bench/main.cpp b/examples/cpp/bench/main.cpp index 9abc4383..10f4a507 100644 --- a/examples/cpp/bench/main.cpp +++ b/examples/cpp/bench/main.cpp @@ -14,7 +14,7 @@ int main() { uint32_t v0_nat = data_native[0]; uint32_t v1_nat = data_native[1]; - for (int i = 0; i < 200000; ++i) { + for (int i = 0; i < 20000; ++i) { uint32_t sum = 0; for (uint32_t r = 0; r < 32; r++) { v0_nat += (((v1_nat << 4) ^ (v1_nat >> 5)) + v1_nat) ^ (sum + key[sum & 3]); @@ -34,11 +34,12 @@ int main() { auto start_virtualized = std::chrono::high_resolution_clock::now(); + BINSAFE_BEGIN(); uint32_t v0_virtualized = data_virtualized[0]; uint32_t v1_virtualized = data_virtualized[1]; - for (int i = 0; i < 200000; ++i) { + for (int i = 0; i < 20000; ++i) { uint32_t sum = 0; for (uint32_t r = 0; r < 32; r++) { @@ -51,6 +52,7 @@ int main() { data_virtualized[0] = v0_virtualized; data_virtualized[1] = v1_virtualized; + BINSAFE_END(); auto end_virtualized = std::chrono::high_resolution_clock::now(); diff --git a/examples/rust/bench/Cargo.toml b/examples/rust/bench/Cargo.toml deleted file mode 100644 index 16ea2df0..00000000 --- a/examples/rust/bench/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[workspace] - -[package] -name = "bench" -version = "0.1.0" -edition = "2024" - -[dependencies] diff --git a/examples/rust/bench/src/main.rs b/examples/rust/bench/src/main.rs deleted file mode 100644 index e6854e13..00000000 --- a/examples/rust/bench/src/main.rs +++ /dev/null @@ -1,80 +0,0 @@ -include!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/../../../api/generated/binsafe.rs" -)); - -use std::time::Instant; - -fn main() { - let key: [u32; 4] = [0xA3B1C2D3, 0xE4F50617, 0x28394A5B, 0x6C7D8E9F]; - let delta: u32 = 0x9E3779B9; - - let mut data_native: [u32; 2] = [0x12345678, 0x9ABCDEF0]; - - let start_native = Instant::now(); - - let mut v0_native = data_native[0]; - let mut v1_native = data_native[1]; - - for _ in 0..200000 { - let mut sum: u32 = 0; - - for _ in 0..32 { - v0_native = v0_native.wrapping_add( - (((v1_native << 4) ^ (v1_native >> 5)).wrapping_add(v1_native)) - ^ sum.wrapping_add(key[(sum & 3) as usize]), - ); - sum = sum.wrapping_add(delta); - v1_native = v1_native.wrapping_add( - (((v0_native << 4) ^ (v0_native >> 5)).wrapping_add(v0_native)) - ^ sum.wrapping_add(key[((sum >> 11) & 3) as usize]), - ); - } - } - - data_native[0] = v0_native; - data_native[1] = v1_native; - - let end_native = Instant::now(); - let elapsed_native = end_native.duration_since(start_native); - - let mut data_virtualized: [u32; 2] = [0x12345678, 0x9ABCDEF0]; - - let start_virtualized = Instant::now(); - - binsafe_begin!(); - - let mut v0_virtualized = data_virtualized[0]; - let mut v1_virtualized = data_virtualized[1]; - - for _ in 0..200000 { - let mut sum: u32 = 0; - - for _ in 0..32 { - v0_virtualized = v0_virtualized.wrapping_add( - (((v1_virtualized << 4) ^ (v1_virtualized >> 5)).wrapping_add(v1_virtualized)) - ^ sum.wrapping_add(key[(sum & 3) as usize]), - ); - sum = sum.wrapping_add(delta); - v1_virtualized = v1_virtualized.wrapping_add( - (((v0_virtualized << 4) ^ (v0_virtualized >> 5)).wrapping_add(v0_virtualized)) - ^ sum.wrapping_add(key[((sum >> 11) & 3) as usize]), - ); - } - } - - data_virtualized[0] = v0_virtualized; - data_virtualized[1] = v1_virtualized; - - binsafe_end!(); - - let end_virtualized = Instant::now(); - let elapsed_virtualized = end_virtualized.duration_since(start_virtualized); - - let native_ms = elapsed_native.as_secs_f64() * 1000.0; - let virtualized_ms = elapsed_virtualized.as_secs_f64() * 1000.0; - - println!("Native: {} ms", native_ms); - println!("Virtualized: {} ms", virtualized_ms); - println!("Overhead: {}x", virtualized_ms / native_ms); -} diff --git a/obfuscator/src/engine.rs b/obfuscator/src/engine.rs index 847b59c0..0916ceb4 100644 --- a/obfuscator/src/engine.rs +++ b/obfuscator/src/engine.rs @@ -139,153 +139,37 @@ impl<'a> Engine<'a> { let ip = code_section.virtual_address.0 as u64; let code = code_section.read(&self.pe).unwrap().to_vec(); - if self.scan_markers(ip, &code, &code_section) { + let data_references = self.collect_data_references(&code, ip); + let code_references = + self.collect_code_references(&code, ip, &code_section, &data_references); + + if self.scan_markers(ip, &code, &code_references) { return; } - self.scan_blocks(ip, &code, &code_section); + self.scan_blocks(ip, &code, &code_references); } - fn scan_markers(&mut self, ip: u64, code: &[u8], code_section: &ImageSectionHeader) -> bool { - let mut markers = Vec::new(); - let mut begin = None; - let mut cursor = 0; - - while cursor + MARKER_SIZE <= code.len() { - if code[cursor..cursor + MARKER_SIZE] == MARKER_BEGIN { - begin = Some(cursor + MARKER_SIZE); - cursor += MARKER_SIZE; - continue; - } - if code[cursor..cursor + MARKER_SIZE] == MARKER_END { - if let Some(start) = begin.take() { - markers.push((start, cursor)); - } - cursor += MARKER_SIZE; - continue; - } - cursor += 1; - } - - if markers.is_empty() { - return false; - } - - info!("Found {} marked regions", markers.len()); - - let data_references = self.collect_data_references(code, ip); - let mut code_references = self - .collect_code_references(code, ip, code_section, &data_references) - .into_iter() - .collect::>(); - code_references.sort(); - - let mut capture = |block: &mut Vec, end: u32| { - if block.is_empty() { - return; - } - let rva = block[0].ip() as u32; - let offset = self.pe.translate(PETranslation::Memory(RVA(rva))).unwrap(); - let size = (end - rva) as usize; - let block = Block { - rva, - offset, - size, - instructions: mem::take(block), - }; - info!("{block}"); - self.blocks.push(block); - }; - - for &(start, end) in &markers { - let ip = ip as u32 + start as u32; - - let mut decoder = Decoder::with_ip( - self.bitness, - &code[start..end], - ip as u64, - DecoderOptions::NONE, - ); - - let mut instruction = Instruction::default(); - let mut block = Vec::new(); - - while decoder.can_decode() { - let rva = decoder.ip() as u32; - - if code_references.binary_search(&rva).is_ok() { - if !block.is_empty() { - capture(&mut block, rva); - } - } - - decoder.decode_out(&mut instruction); - - if instruction.is_invalid() { - if !block.is_empty() { - capture(&mut block, instruction.ip() as u32); - } - break; - } - - block.push(instruction); - - if !matches!(instruction.flow_control(), FlowControl::Next) { - capture(&mut block, instruction.next_ip() as u32); - } - } - - if !block.is_empty() { - capture(&mut block, ip as u32 + end as u32); - } - } - - for (start, end) in markers { - let start = self - .pe - .translate(PETranslation::Memory(RVA(ip as u32 + start as u32))) - .unwrap(); - let end = self - .pe - .translate(PETranslation::Memory(RVA(ip as u32 + end as u32))) - .unwrap(); - - self.nop(start - MARKER_SIZE, MARKER_SIZE); - self.nop(end, MARKER_SIZE); + fn capture_block(&mut self, block: &mut Vec, end: u32) { + if block.is_empty() { + return; } - - info!("Found {} blocks", self.blocks.len()); - - true + let rva = block[0].ip() as u32; + let offset = self.pe.translate(PETranslation::Memory(RVA(rva))).unwrap(); + let size = (end - rva) as usize; + self.blocks.push(Block { + rva, + offset, + size, + instructions: mem::take(block), + }); + info!("{}", self.blocks[self.blocks.len() - 1]) } - fn scan_blocks(&mut self, ip: u64, code: &[u8], code_section: &ImageSectionHeader) { - let data_references = self.collect_data_references(code, ip); - - let mut code_references = self - .collect_code_references(code, ip, code_section, &data_references) - .into_iter() - .collect::>(); - code_references.sort(); - - let mut capture = |block: &mut Vec, end: u32| { - if block.is_empty() { - return; - } - let rva = block[0].ip() as u32; - let offset = self.pe.translate(PETranslation::Memory(RVA(rva))).unwrap(); - let size = (end - rva) as usize; - self.blocks.push(Block { - rva, - offset, - size, - instructions: mem::take(block), - }); - }; - + fn collect_blocks(&mut self, code: &[u8], ip: u64, code_references: &[u32]) { let mut decoder = Decoder::with_ip(self.bitness, code, ip, DecoderOptions::NONE); let mut instruction = Instruction::default(); - let mut block = Vec::new(); + let mut block: Vec = Vec::new(); let mut inblock = false; while decoder.can_decode() { @@ -293,7 +177,7 @@ impl<'a> Engine<'a> { if code_references.binary_search(&rva).is_ok() { if inblock { - capture(&mut block, rva); + self.capture_block(&mut block, rva); } inblock = true; } @@ -318,7 +202,7 @@ impl<'a> Engine<'a> { && code_references[position - 1] < end; if overlaps { - capture(&mut block, start); + self.capture_block(&mut block, start); inblock = false; continue; } @@ -326,10 +210,70 @@ impl<'a> Engine<'a> { block.push(instruction); if !matches!(instruction.flow_control(), FlowControl::Next) { - capture(&mut block, instruction.next_ip() as u32); + self.capture_block(&mut block, instruction.next_ip() as u32); inblock = false; } } + } + + fn scan_markers(&mut self, ip: u64, code: &[u8], code_references: &HashSet) -> bool { + let mut markers = Vec::new(); + let mut begin = None; + let mut cursor = 0; + + while cursor + MARKER_SIZE <= code.len() { + if code[cursor..cursor + MARKER_SIZE] == MARKER_BEGIN { + begin = Some(cursor + MARKER_SIZE); + cursor += MARKER_SIZE; + continue; + } + if code[cursor..cursor + MARKER_SIZE] == MARKER_END { + if let Some(start) = begin.take() { + markers.push((start, cursor)); + } + cursor += MARKER_SIZE; + continue; + } + cursor += 1; + } + + if markers.is_empty() { + return false; + } + + info!("Found {} marked regions", markers.len()); + + let mut sorted_code_references = code_references.iter().copied().collect::>(); + sorted_code_references.sort(); + + for &(start, end) in &markers { + self.collect_blocks(&code[start..end], ip, &sorted_code_references); + } + + for (start, end) in markers { + let start_offset = self + .pe + .translate(PETranslation::Memory(RVA(ip as u32 + start as u32))) + .unwrap(); + let end_offset = self + .pe + .translate(PETranslation::Memory(RVA(ip as u32 + end as u32))) + .unwrap(); + + self.nop(start_offset - MARKER_SIZE, MARKER_SIZE); + self.nop(end_offset, MARKER_SIZE); + } + + info!("Found {} blocks", self.blocks.len()); + + true + } + + fn scan_blocks(&mut self, ip: u64, code: &[u8], code_references: &HashSet) { + let mut sorted_code_references = code_references.iter().copied().collect::>(); + sorted_code_references.sort(); + + self.collect_blocks(code, ip, &sorted_code_references); info!("Found {} blocks", self.blocks.len()); } diff --git a/obfuscator/src/protections/virtualization/attestation/mod.rs b/obfuscator/src/protections/virtualization/attestation/mod.rs index ac82c8af..deda3c28 100644 --- a/obfuscator/src/protections/virtualization/attestation/mod.rs +++ b/obfuscator/src/protections/virtualization/attestation/mod.rs @@ -43,6 +43,8 @@ impl Operation { } } +// TODO: Harden and centralize the LCG logic! + pub fn generate(engine: &mut Engine, key: u64) -> Vec>> { let mut rng = rand::thread_rng(); diff --git a/obfuscator/src/protections/virtualization/mod.rs b/obfuscator/src/protections/virtualization/mod.rs index d2e2b561..f860d4d9 100644 --- a/obfuscator/src/protections/virtualization/mod.rs +++ b/obfuscator/src/protections/virtualization/mod.rs @@ -35,30 +35,30 @@ impl Virtualization { fn attestation(&self, engine: &mut Engine) -> Vec { let mut vcode = Vec::new(); - #[cfg(debug_assertions)] - let mut log = Vec::new(); + // #[cfg(debug_assertions)] + // let mut log = Vec::new(); let blocks = attestation::generate(engine, engine.rt.keys.secret); for (_index, operations) in blocks.into_iter().enumerate() { let mut rng = rand::thread_rng(); - #[cfg(debug_assertions)] - let (transformed, snapshots) = - bytecode::transform_with_snapshots(&mut engine.rt.mapper, operations, |ready| { - rng.gen_range(0..ready.len()) - }); + // #[cfg(debug_assertions)] + // let (transformed, snapshots) = + // bytecode::transform_with_snapshots(&mut engine.rt.mapper, operations, |ready| { + // rng.gen_range(0..ready.len()) + // }); - #[cfg(not(debug_assertions))] + // #[cfg(not(debug_assertions))] let transformed = bytecode::transform(&mut engine.rt.mapper, operations, |ready| { rng.gen_range(0..ready.len()) }); - #[cfg(debug_assertions)] - { - let index = _index; - log.push(format!(" BLOCK {}:\n{}", index, snapshots)); - } + // #[cfg(debug_assertions)] + // { + // let index = _index; + // log.push(format!(" BLOCK {}:\n{}", index, snapshots)); + // } let mut bytes = bytecode::assemble(&mut engine.rt.mapper, &transformed); @@ -86,16 +86,16 @@ impl Virtualization { vcode.extend_from_slice(&bytes); } - #[cfg(debug_assertions)] - { - use logger::debug; + // #[cfg(debug_assertions)] + // { + // use logger::debug; - debug!( - "ATTESTATION @ 0x{:016X}:\n{}", - engine.rt.keys.secret, - log.join("\n") - ); - } + // debug!( + // "ATTESTATION @ 0x{:016X}:\n{}", + // engine.rt.keys.secret, + // log.join("\n") + // ); + // } vcode } diff --git a/runtime/src/vm/handlers/semantic.rs b/runtime/src/vm/handlers/semantic.rs index 823a64ff..912b4ced 100644 --- a/runtime/src/vm/handlers/semantic.rs +++ b/runtime/src/vm/handlers/semantic.rs @@ -117,6 +117,8 @@ struct Context { result: AsmRegister64, } +// TODO: Implement a mixed-boolean-arithmetic engine! + pub fn build(rt: &mut Runtime, operation: &Operation) { let mut epilogue = rt.asm.create_label(); From e59935b48efa6da8eecd25d2f44a69475e3444f9 Mon Sep 17 00:00:00 2001 From: RadonCoding <86915746+RadonCoding@users.noreply.github.com> Date: Sat, 12 Sep 2026 19:25:17 +0300 Subject: [PATCH 05/33] chore: large refactors, fixes and a new obfuscation pass --- obfuscator/src/engine.rs | 133 ++++++++--- .../virtualization/attestation/anti_debug.rs | 29 +-- .../virtualization/attestation/anti_tamper.rs | 21 +- .../virtualization/attestation/debug.rs | 12 +- .../virtualization/attestation/mod.rs | 194 +++++++++------- .../protections/virtualization/language.rs | 34 ++- runtime/src/functions/format.rs | 1 + runtime/src/runtime.rs | 2 + runtime/src/vm/bytecode.rs | 65 +++--- runtime/src/vm/encoders/add.rs | 14 +- runtime/src/vm/encoders/and.rs | 14 +- runtime/src/vm/encoders/bit_scan_reverse.rs | 14 +- runtime/src/vm/encoders/bit_test.rs | 14 +- .../src/vm/encoders/bit_test_complement.rs | 18 +- runtime/src/vm/encoders/bit_test_reset.rs | 14 +- runtime/src/vm/encoders/bit_test_set.rs | 15 +- runtime/src/vm/encoders/block.rs | 34 +-- runtime/src/vm/encoders/byte_swap.rs | 14 +- runtime/src/vm/encoders/compare_exchange.rs | 17 +- runtime/src/vm/encoders/discard.rs | 16 +- runtime/src/vm/encoders/dispatch.rs | 71 ++++++ runtime/src/vm/encoders/div.rs | 14 +- runtime/src/vm/encoders/exchange.rs | 14 +- runtime/src/vm/encoders/exchange_add.rs | 15 +- runtime/src/vm/encoders/jcc.rs | 18 +- runtime/src/vm/encoders/label.rs | 9 +- runtime/src/vm/encoders/load_address.rs | 14 +- runtime/src/vm/encoders/load_immediate.rs | 12 +- runtime/src/vm/encoders/load_memory.rs | 14 +- runtime/src/vm/encoders/load_register.rs | 16 +- runtime/src/vm/encoders/load_vector.rs | 18 +- runtime/src/vm/encoders/mod.rs | 23 +- runtime/src/vm/encoders/mul.rs | 14 +- runtime/src/vm/encoders/or.rs | 15 +- runtime/src/vm/encoders/packed_byte_equal.rs | 17 +- runtime/src/vm/encoders/packed_byte_mask.rs | 14 +- runtime/src/vm/encoders/pop.rs | 14 +- runtime/src/vm/encoders/push.rs | 16 +- runtime/src/vm/encoders/ret.rs | 16 +- runtime/src/vm/encoders/rol.rs | 15 +- runtime/src/vm/encoders/ror.rs | 15 +- runtime/src/vm/encoders/sar.rs | 15 +- runtime/src/vm/encoders/shl.rs | 15 +- runtime/src/vm/encoders/shr.rs | 14 +- runtime/src/vm/encoders/store_extend.rs | 18 +- runtime/src/vm/encoders/store_memory.rs | 14 +- runtime/src/vm/encoders/store_merge.rs | 18 +- runtime/src/vm/encoders/store_register.rs | 19 +- runtime/src/vm/encoders/sub.rs | 15 +- runtime/src/vm/encoders/timestamp.rs | 14 +- runtime/src/vm/encoders/trailing_zeros.rs | 15 +- runtime/src/vm/encoders/vector_add.rs | 14 +- runtime/src/vm/encoders/vector_and.rs | 15 +- runtime/src/vm/encoders/vector_and_not.rs | 15 +- runtime/src/vm/encoders/vector_div.rs | 19 +- runtime/src/vm/encoders/vector_mul.rs | 14 +- runtime/src/vm/encoders/vector_or.rs | 15 +- runtime/src/vm/encoders/vector_sub.rs | 14 +- runtime/src/vm/encoders/vector_xor.rs | 15 +- runtime/src/vm/encoders/xor.rs | 15 +- runtime/src/vm/functions/dispatch.rs | 3 +- runtime/src/vm/functions/invoke.rs | 30 ++- runtime/src/vm/handlers/add.rs | 42 ++-- runtime/src/vm/handlers/and.rs | 22 +- runtime/src/vm/handlers/bit_scan_reverse.rs | 10 +- runtime/src/vm/handlers/bit_test.rs | 10 +- .../src/vm/handlers/bit_test_complement.rs | 14 +- runtime/src/vm/handlers/bit_test_reset.rs | 14 +- runtime/src/vm/handlers/bit_test_set.rs | 14 +- runtime/src/vm/handlers/byte_swap.rs | 32 +-- runtime/src/vm/handlers/dispatch.rs | 21 ++ runtime/src/vm/handlers/mod.rs | 1 + runtime/src/vm/handlers/or.rs | 22 +- runtime/src/vm/handlers/rol.rs | 14 +- runtime/src/vm/handlers/ror.rs | 16 +- runtime/src/vm/handlers/sar.rs | 28 +-- runtime/src/vm/handlers/semantic.rs | 208 ++++++++++-------- runtime/src/vm/handlers/shl.rs | 26 +-- runtime/src/vm/handlers/shr.rs | 28 +-- runtime/src/vm/handlers/sub.rs | 42 ++-- runtime/src/vm/handlers/trailing_zeros.rs | 14 +- runtime/src/vm/handlers/xor.rs | 24 +- runtime/src/vm/transform/encrypt.rs | 2 +- runtime/src/vm/transform/indirect.rs | 57 +++++ runtime/src/vm/transform/mod.rs | 5 + runtime/src/vm/transform/permute.rs | 8 +- 86 files changed, 1380 insertions(+), 669 deletions(-) create mode 100644 runtime/src/vm/encoders/dispatch.rs create mode 100644 runtime/src/vm/handlers/dispatch.rs create mode 100644 runtime/src/vm/transform/indirect.rs diff --git a/obfuscator/src/engine.rs b/obfuscator/src/engine.rs index 0916ceb4..e63e7b73 100644 --- a/obfuscator/src/engine.rs +++ b/obfuscator/src/engine.rs @@ -139,17 +139,54 @@ impl<'a> Engine<'a> { let ip = code_section.virtual_address.0 as u64; let code = code_section.read(&self.pe).unwrap().to_vec(); - let data_references = self.collect_data_references(&code, ip); - let code_references = - self.collect_code_references(&code, ip, &code_section, &data_references); + let markers = self.find_markers(&code); - if self.scan_markers(ip, &code, &code_references) { + let data_references = self.collect_data_references(&code, ip, &markers); + let mut code_references = + self.collect_code_references(&code, ip, &code_section, &data_references, &markers); + + for &(start, _) in &markers { + code_references.insert(ip as u32 + start as u32); + } + + if self.scan_markers(ip, &code, &code_references, &markers) { return; } self.scan_blocks(ip, &code, &code_references); } + fn find_markers(&self, code: &[u8]) -> Vec<(usize, usize)> { + let mut markers = Vec::new(); + let mut begin = None; + let mut cursor = 0; + + while cursor + MARKER_SIZE <= code.len() { + if code[cursor..cursor + MARKER_SIZE] == MARKER_BEGIN { + begin = Some(cursor + MARKER_SIZE); + cursor += MARKER_SIZE; + continue; + } + if code[cursor..cursor + MARKER_SIZE] == MARKER_END { + if let Some(start) = begin.take() { + markers.push((start, cursor)); + } + cursor += MARKER_SIZE; + continue; + } + cursor += 1; + } + + markers + } + + fn is_marker(&self, offset: usize, markers: &[(usize, usize)]) -> bool { + markers.iter().any(|&(s, e)| { + (offset >= s.saturating_sub(MARKER_SIZE) && offset < s) + || (offset >= e && offset < e + MARKER_SIZE) + }) + } + fn capture_block(&mut self, block: &mut Vec, end: u32) { if block.is_empty() { return; @@ -216,27 +253,13 @@ impl<'a> Engine<'a> { } } - fn scan_markers(&mut self, ip: u64, code: &[u8], code_references: &HashSet) -> bool { - let mut markers = Vec::new(); - let mut begin = None; - let mut cursor = 0; - - while cursor + MARKER_SIZE <= code.len() { - if code[cursor..cursor + MARKER_SIZE] == MARKER_BEGIN { - begin = Some(cursor + MARKER_SIZE); - cursor += MARKER_SIZE; - continue; - } - if code[cursor..cursor + MARKER_SIZE] == MARKER_END { - if let Some(start) = begin.take() { - markers.push((start, cursor)); - } - cursor += MARKER_SIZE; - continue; - } - cursor += 1; - } - + fn scan_markers( + &mut self, + ip: u64, + code: &[u8], + code_references: &HashSet, + markers: &[(usize, usize)], + ) -> bool { if markers.is_empty() { return false; } @@ -246,11 +269,15 @@ impl<'a> Engine<'a> { let mut sorted_code_references = code_references.iter().copied().collect::>(); sorted_code_references.sort(); - for &(start, end) in &markers { - self.collect_blocks(&code[start..end], ip, &sorted_code_references); + for &(start, end) in markers { + self.collect_blocks( + &code[start..end], + ip + start as u64, + &sorted_code_references, + ); } - for (start, end) in markers { + for &(start, end) in markers { let start_offset = self .pe .translate(PETranslation::Memory(RVA(ip as u32 + start as u32))) @@ -278,21 +305,40 @@ impl<'a> Engine<'a> { info!("Found {} blocks", self.blocks.len()); } - fn collect_data_references(&self, code: &[u8], ip: u64) -> HashSet { + fn collect_data_references( + &self, + code: &[u8], + ip: u64, + markers: &[(usize, usize)], + ) -> HashSet { let mut data_references = HashSet::new(); - let mut decoder = Decoder::with_ip(self.bitness, code, ip, DecoderOptions::NONE); - let mut instruction = Instruction::default(); + let mut cursor = 0; - while decoder.can_decode() { + while cursor < code.len() { + if self.is_marker(cursor, markers) { + cursor += MARKER_SIZE; + continue; + } + + let mut decoder = Decoder::with_ip( + self.bitness, + &code[cursor..], + ip + cursor as u64, + DecoderOptions::NONE, + ); + let mut instruction = Instruction::default(); decoder.decode_out(&mut instruction); if instruction.is_invalid() { + cursor += 1; continue; } if instruction.is_ip_rel_memory_operand() { data_references.insert(instruction.ip_rel_memory_address() as u32); } + + cursor += instruction.len(); } data_references @@ -354,15 +400,30 @@ impl<'a> Engine<'a> { ip: u64, code_section: &ImageSectionHeader, data_references: &HashSet, + markers: &[(usize, usize)], ) -> HashSet { let mut references = self.collect_static_references(code_section); - let mut decoder = Decoder::with_ip(self.bitness, code, ip, DecoderOptions::NONE); - let mut instruction = Instruction::default(); + let mut cursor = 0; let mut previous = Vec::new(); - while decoder.can_decode() { + while cursor < code.len() { + if self.is_marker(cursor, markers) { + cursor += MARKER_SIZE; + previous.clear(); + continue; + } + + let mut decoder = Decoder::with_ip( + self.bitness, + &code[cursor..], + ip + cursor as u64, + DecoderOptions::NONE, + ); + let mut instruction = Instruction::default(); decoder.decode_out(&mut instruction); + if instruction.is_invalid() { + cursor += 1; previous.clear(); continue; } @@ -395,6 +456,7 @@ impl<'a> Engine<'a> { | FlowControl::UnconditionalBranch | FlowControl::Return ) { + cursor += instruction.len(); continue; } @@ -407,6 +469,7 @@ impl<'a> Engine<'a> { } previous.clear(); + cursor += instruction.len(); } references diff --git a/obfuscator/src/protections/virtualization/attestation/anti_debug.rs b/obfuscator/src/protections/virtualization/attestation/anti_debug.rs index 4b2b4bbf..cacb7d37 100644 --- a/obfuscator/src/protections/virtualization/attestation/anti_debug.rs +++ b/obfuscator/src/protections/virtualization/attestation/anti_debug.rs @@ -28,7 +28,6 @@ pub fn generate( engine: &mut Engine, rng: &mut impl Rng, expected: &mut u64, - mix: Operation, ) -> Vec> { let mut instructions = Vec::>::new(); @@ -41,10 +40,8 @@ pub fn generate( instructions.extend(set_hide_from_debugger(engine, rng, expected)); instructions.extend(query_hide_from_debbuger(engine, rng, expected)); - instructions.extend(spill_register(ACCUMULATOR)); - instructions.extend(spill_register(VMReg::Vt0)); - instructions.extend(register_operation(mix)); - instructions.extend(reload_register(VMReg::Vp0)); + instructions.extend(load_register(ACCUMULATOR)); + instructions.extend(store_register(VMReg::Vp0)); instructions.extend(release(0x30)); @@ -66,7 +63,13 @@ fn query_kd_debugger_enabled( VMSeg::None, VMWidth::Lower8, )); - b.extend(accumulate(rng, ACCUMULATOR, None, 0, &mut *expected)); + b.extend(accumulate_immediate( + rng, + ACCUMULATOR, + None, + 0, + &mut *expected, + )); b.extend(import(engine, ImportDef::NtQuerySystemInformation)); b.extend(accumulate_prologue( @@ -87,7 +90,7 @@ fn query_kd_debugger_enabled( VMSeg::None, )); // SystemInformation -> RDX - b.extend(reload_register(VMReg::Rdx)); + b.extend(store_register(VMReg::Rdx)); // SystemInformationLength -> R8 b.extend(set_register(VMReg::R8, 0x2)); // ReturnLength -> R9 @@ -95,7 +98,7 @@ fn query_kd_debugger_enabled( // NtQuerySystemInformation b.extend(invoke(VMReg::Rax)); - b.extend(accumulate( + b.extend(accumulate_immediate( rng, ACCUMULATOR, Some(VMReg::Rax), @@ -145,7 +148,7 @@ fn query_process_debug_object_handle( VMSeg::None, )); // ProcessInformation -> R8 - b.extend(reload_register(VMReg::R8)); + b.extend(store_register(VMReg::R8)); // ProcessInformationLength -> R9 b.extend(set_register(VMReg::R9, 8)); // ReturnLength -> [RSP + ...] @@ -153,7 +156,7 @@ fn query_process_debug_object_handle( // NtQueryInformationProcess b.extend(invoke(VMReg::Rax)); - b.extend(accumulate( + b.extend(accumulate_immediate( rng, ACCUMULATOR, Some(VMReg::Rax), @@ -201,7 +204,7 @@ fn set_hide_from_debugger( // NtSetInformationThread b.extend(invoke(VMReg::Rax)); - b.extend(accumulate( + b.extend(accumulate_immediate( rng, ACCUMULATOR, Some(VMReg::Rax), @@ -241,7 +244,7 @@ fn query_hide_from_debbuger( VMSeg::None, )); // ThreadInformation -> R8 - b.extend(reload_register(VMReg::R8)); + b.extend(store_register(VMReg::R8)); // ThreadInformationLength -> R9 b.extend(set_register(VMReg::R9, 1)); // ReturnLength -> [RSP + ...] @@ -249,7 +252,7 @@ fn query_hide_from_debbuger( // NtQueryInformationThread b.extend(invoke(VMReg::Rax)); - b.extend(accumulate( + b.extend(accumulate_immediate( rng, ACCUMULATOR, Some(VMReg::Rax), diff --git a/obfuscator/src/protections/virtualization/attestation/anti_tamper.rs b/obfuscator/src/protections/virtualization/attestation/anti_tamper.rs index 7b0fefd0..c081426a 100644 --- a/obfuscator/src/protections/virtualization/attestation/anti_tamper.rs +++ b/obfuscator/src/protections/virtualization/attestation/anti_tamper.rs @@ -15,7 +15,6 @@ pub fn generate( engine: &mut Engine, rng: &mut impl Rng, expected: &mut u64, - mix: Operation, ) -> Vec> { let operation = Operation::random(rng); @@ -64,22 +63,22 @@ pub fn generate( VMSeg::None, VMWidth::Lower64, )); - outer.extend(reload_register(VMReg::Rcx)); + outer.extend(store_register(VMReg::Rcx)); - outer.extend(spill_register(VMReg::Rcx)); + outer.extend(load_register(VMReg::Rcx)); outer.extend(immediate(0x20)); outer.extend(shr(None, None)); - outer.extend(reload_register(VMReg::Rdx)); + outer.extend(store_register(VMReg::Rdx)); outer.extend(mask(Some(VMReg::Rcx), 0xFFFF_FFFF)); - outer.extend(spill_register(VMReg::VImage)); + outer.extend(load_register(VMReg::VImage)); outer.extend(add(None, None)); - outer.extend(reload_register(VMReg::Rcx)); + outer.extend(store_register(VMReg::Rcx)); outer.extend(mask(Some(VMReg::Rdx), 15)); - outer.extend(reload_register(VMReg::R8)); + outer.extend(store_register(VMReg::R8)); outer.extend(sub(Some(VMReg::Rdx), Some(VMReg::R8))); - outer.extend(reload_register(VMReg::R9)); + outer.extend(store_register(VMReg::R9)); outer.extend(foreach(VMReg::R10, Bound::Register(VMReg::R9), 16, || { let mut inner = Vec::>::new(); @@ -100,7 +99,7 @@ pub fn generate( })); outer.extend(compute_memory(VMReg::Rcx, VMReg::R10, 1, 0, VMSeg::None)); - outer.extend(reload_register(VMReg::Rdx)); + outer.extend(store_register(VMReg::Rdx)); outer.extend(skip( engine, @@ -132,9 +131,7 @@ pub fn generate( instructions.extend(spill_vector(ACCUMULATOR, VMWidth::Lower128)); instructions.extend(xor(None, None)); - instructions.extend(spill_register(VMReg::Vt0)); - instructions.extend(register_operation(mix)); - instructions.extend(reload_register(VMReg::Vp1)); + instructions.extend(store_register(VMReg::Vp1)); instructions } diff --git a/obfuscator/src/protections/virtualization/attestation/debug.rs b/obfuscator/src/protections/virtualization/attestation/debug.rs index d9a5f2e7..bf41af85 100644 --- a/obfuscator/src/protections/virtualization/attestation/debug.rs +++ b/obfuscator/src/protections/virtualization/attestation/debug.rs @@ -29,11 +29,11 @@ pub fn print(engine: &mut Engine, message: &str, register: Option) -> Vec let mut instructions = Vec::>::new(); for ®ister in VOLATILE { - instructions.extend(spill_register(register)); + instructions.extend(load_register(register)); } if let Some(register) = register { - instructions.extend(spill_register(register)); + instructions.extend(load_register(register)); } let length = (message.len() + if register.is_some() { 19 } else { 2 } + 15) & !15; @@ -56,8 +56,8 @@ pub fn print(engine: &mut Engine, message: &str, register: Option) -> Vec offset as i32, VMSeg::None, )); - instructions.extend(reload_register(VMReg::Rcx)); - instructions.extend(reload_register(VMReg::Rdx)); + instructions.extend(store_register(VMReg::Rcx)); + instructions.extend(store_register(VMReg::Rdx)); instructions.extend(call(engine, FnDef::Format)); offset += 16; @@ -70,13 +70,13 @@ pub fn print(engine: &mut Engine, message: &str, register: Option) -> Vec } instructions.extend(compute_memory(VMReg::Rsp, VMReg::None, 1, 0, VMSeg::None)); - instructions.extend(reload_register(VMReg::Rcx)); + instructions.extend(store_register(VMReg::Rcx)); instructions.extend(call(engine, FnDef::Print)); instructions.extend(release(length as u64)); for ®ister in VOLATILE.iter().rev() { - instructions.extend(reload_register(register)); + instructions.extend(store_register(register)); } instructions diff --git a/obfuscator/src/protections/virtualization/attestation/mod.rs b/obfuscator/src/protections/virtualization/attestation/mod.rs index deda3c28..00efb335 100644 --- a/obfuscator/src/protections/virtualization/attestation/mod.rs +++ b/obfuscator/src/protections/virtualization/attestation/mod.rs @@ -18,6 +18,9 @@ mod debug; // Masks lower 34 bits of timestamp, creating a ~5s window on a 3.5 GHz CPU const WINDOW: u64 = 0x22; +const MINIMUM_CYCLES: u64 = 1_000_000; +const MAXIMUM_CYCLES: u64 = 100_000_000; + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Operation { Add, @@ -33,18 +36,8 @@ impl Operation { _ => Operation::Xor, } } - - pub fn invert(self) -> Self { - match self { - Operation::Add => Operation::Sub, - Operation::Sub => Operation::Add, - Operation::Xor => Operation::Xor, - } - } } -// TODO: Harden and centralize the LCG logic! - pub fn generate(engine: &mut Engine, key: u64) -> Vec>> { let mut rng = rand::thread_rng(); @@ -52,29 +45,17 @@ pub fn generate(engine: &mut Engine, key: u64) -> Vec>> { let mut block = Vec::>::new(); + let mut vp0 = 0; + let mut vp1 = 0; + block.extend(timestamp()); block.extend(mask(None, !((1u64 << WINDOW) - 1))); - - block.extend(load_data( - engine, - DataDef::VmKeyMultiplier, - VMWidth::Lower64, - )); - block.extend(mul(None, None)); - - block.extend(load_data(engine, DataDef::VmKeyAddend, VMWidth::Lower64)); - block.extend(add(None, None)); - - block.extend(reload_register(VMReg::Vt0)); + block.extend(lcg(engine, None)); + block.extend(store_register(VMReg::Vt0)); block.extend(sub(Some(VMReg::Vt0), Some(VMReg::Vt1))); - block.extend(reload_register(VMReg::Rax)); - let mut vp0 = 0; - let mut vp1 = 0; - - let mix0 = Operation::random(&mut rng); - let mix1 = Operation::random(&mut rng); + block.extend(store_register(VMReg::Rax)); block.extend(skip( engine, @@ -83,8 +64,71 @@ pub fn generate(engine: &mut Engine, key: u64) -> Vec>> { |engine| { let mut b = Vec::>::new(); - b.extend(anti_debug::generate(engine, &mut rng, &mut vp0, mix0)); - b.extend(anti_tamper::generate(engine, &mut rng, &mut vp1, mix1)); + b.extend(timestamp()); + + b.extend(anti_debug::generate(engine, &mut rng, &mut vp0)); + b.extend(anti_tamper::generate(engine, &mut rng, &mut vp1)); + + b.extend(store_register(VMReg::Rax)); + + b.extend(timestamp()); + + b.extend(sub(Some(VMReg::Rax), None)); + + b.extend(store_register(VMReg::Rax)); + + b.extend(load_register(VMReg::Rax)); + b.extend(immediate(MINIMUM_CYCLES)); + b.extend(sub(None, None)); + b.extend(discard()); + + b.extend(flag(Flag::Carry)); + + b.extend(store_register(VMReg::Rcx)); + + b.extend(accumulate_immediate( + &mut rng, + VMReg::Vp0, + Some(VMReg::Rcx), + 0, + &mut vp0, + )); + b.extend(accumulate_immediate( + &mut rng, + VMReg::Vp1, + Some(VMReg::Rcx), + 0, + &mut vp1, + )); + + b.extend(immediate(MAXIMUM_CYCLES)); + b.extend(load_register(VMReg::Rax)); + b.extend(sub(None, None)); + b.extend(discard()); + + b.extend(flag(Flag::Carry)); + + b.extend(store_register(VMReg::Rcx)); + + b.extend(accumulate_immediate( + &mut rng, + VMReg::Vp0, + Some(VMReg::Rcx), + 0, + &mut vp0, + )); + b.extend(accumulate_immediate( + &mut rng, + VMReg::Vp1, + Some(VMReg::Rcx), + 0, + &mut vp1, + )); + + b.extend(xor(Some(VMReg::Vp0), Some(VMReg::Vt0))); + b.extend(store_register(VMReg::Vp0)); + b.extend(xor(Some(VMReg::Vp1), Some(VMReg::Vt0))); + b.extend(store_register(VMReg::Vp1)); b.extend(copy(VMReg::Vt0, VMReg::Vt1)); @@ -92,7 +136,7 @@ pub fn generate(engine: &mut Engine, key: u64) -> Vec>> { }, )); - block.extend(correct(engine, &mut rng, key, vp0, mix0, vp1, mix1)); + block.extend(correct(engine, &mut rng, key, vp0, vp1)); blocks.push(block); @@ -104,9 +148,7 @@ fn correct( rng: &mut impl Rng, key: u64, vp0: u64, - mix0: Operation, vp1: u64, - mix1: Operation, ) -> Vec> { let mut instructions = Vec::>::new(); @@ -114,7 +156,7 @@ fn correct( instructions.extend(load_absolute(engine, DataDef::VmCode, VMReg::Rcx)); instructions.extend(sub(Some(VMReg::Vg0), Some(VMReg::Rax))); - instructions.extend(reload_register(VMReg::Rdx)); + instructions.extend(store_register(VMReg::Rdx)); instructions.extend(skip( engine, @@ -126,14 +168,14 @@ fn correct( b.extend(sub(Some(VMReg::Rax), Some(VMReg::Rcx))); b.extend(immediate((crypt::HEADER_SIZE + crypt::TRAILER_SIZE) as u64)); b.extend(sub(None, None)); - b.extend(reload_register(VMReg::R9)); + b.extend(store_register(VMReg::R9)); b.extend(set_register(VMReg::R10, 0)); b.extend(foreach(VMReg::R8, Bound::Register(VMReg::R9), 8, || { let mut outer = Vec::new(); - outer.extend(spill_register(VMReg::R10)); + outer.extend(load_register(VMReg::R10)); outer.extend(load_memory( VMReg::Rcx, VMReg::R8, @@ -143,12 +185,12 @@ fn correct( VMWidth::Lower64, )); outer.extend(xor(None, None)); - outer.extend(reload_register(VMReg::R10)); + outer.extend(store_register(VMReg::R10)); outer })); - b.extend(spill_register(VMReg::R10)); + b.extend(load_register(VMReg::R10)); b }, @@ -174,19 +216,31 @@ fn correct( let combined = combine_operation(operation, vp0, vp1); let correction = combined ^ key; - instructions.extend(spill_register(VMReg::Vp0)); - instructions.extend(spill_register(VMReg::Vt0)); - instructions.extend(register_operation(mix0.invert())); - instructions.extend(spill_register(VMReg::Vp1)); - instructions.extend(spill_register(VMReg::Vt0)); - instructions.extend(register_operation(mix1.invert())); + instructions.extend(xor(Some(VMReg::Vp0), Some(VMReg::Vt0))); + instructions.extend(xor(Some(VMReg::Vp1), Some(VMReg::Vt0))); instructions.extend(register_operation(operation)); instructions.extend(immediate(correction)); instructions.extend(xor(None, None)); instructions.extend(xor(None, None)); - instructions.extend(reload_register(VMReg::Vg0)); + instructions.extend(store_register(VMReg::Vg0)); + + instructions +} + +fn lcg(engine: &mut Engine, register: Option) -> Vec> { + let mut instructions = Vec::>::new(); + + instructions.extend(load_data( + engine, + DataDef::VmKeyMultiplier, + VMWidth::Lower64, + )); + instructions.extend(mul(register, None)); + + instructions.extend(load_data(engine, DataDef::VmKeyAddend, VMWidth::Lower64)); + instructions.extend(add(register, None)); instructions } @@ -238,7 +292,7 @@ fn vector_operation(operation: Operation) -> Box { fn accumulate( rng: &mut R, accumulator: VMReg, - source: Option, + source: Vec>, value: u64, expected: &mut u64, ) -> Vec> { @@ -248,24 +302,33 @@ fn accumulate( let mix = rng.gen::(); - instructions.extend(spill_register(accumulator)); - - if let Some(source) = source { - instructions.extend(spill_register(source)); - } + instructions.extend(load_register(accumulator)); + instructions.extend(source); instructions.extend(immediate(mix)); instructions.extend(xor(None, None)); - instructions.extend(register_operation(operation)); - - instructions.extend(reload_register(accumulator)); + instructions.extend(store_register(accumulator)); apply_operation(operation, value ^ mix, expected); instructions } -fn accumulate_memory( +pub fn accumulate_immediate( + rng: &mut R, + accumulator: VMReg, + source: Option, + value: u64, + expected: &mut u64, +) -> Vec> { + let source = match source { + Some(register) => load_register(register), + None => Vec::new(), + }; + accumulate(rng, accumulator, source, value, expected) +} + +pub fn accumulate_memory( rng: &mut R, accumulator: VMReg, base: VMReg, @@ -274,25 +337,8 @@ fn accumulate_memory( value: u64, expected: &mut u64, ) -> Vec> { - let mut instructions = Vec::>::new(); - - let operation = Operation::random(rng); - - instructions.extend(spill_register(accumulator)); - instructions.extend(load_memory( - base, - VMReg::None, - 1, - displacement, - VMSeg::None, - width, - )); - instructions.extend(register_operation(operation)); - instructions.extend(reload_register(accumulator)); - - apply_operation(operation, value, expected); - - instructions + let source = load_memory(base, VMReg::None, 1, displacement, VMSeg::None, width); + accumulate(rng, accumulator, source, value, expected) } fn accumulate_byte( diff --git a/obfuscator/src/protections/virtualization/language.rs b/obfuscator/src/protections/virtualization/language.rs index 3f874dcc..79001514 100644 --- a/obfuscator/src/protections/virtualization/language.rs +++ b/obfuscator/src/protections/virtualization/language.rs @@ -1,6 +1,6 @@ use crate::engine::Engine; use runtime::runtime::{DataDef, FnDef, ImportDef}; -use runtime::vm::bytecode::{VMCondition, Flag, VMLogic, VMMem, VMReg, VMSeg, VMVec, VMWidth}; +use runtime::vm::bytecode::{Flag, VMCondition, VMLogic, VMMem, VMReg, VMSeg, VMVec, VMWidth}; use runtime::vm::encoders::add::Add; use runtime::vm::encoders::and::And; use runtime::vm::encoders::block::{Block, Jump, Target}; @@ -78,7 +78,7 @@ pub fn foreach Vec>>( operations.extend(body()); operations.extend(increment(counter, step)); - operations.extend(spill_register(counter)); + operations.extend(load_register(counter)); match bound { Bound::Immediate(value) => { operations.push(Box::new(LoadImmediate { @@ -140,9 +140,9 @@ pub fn load_absolute(engine: &mut Engine, def: DataDef, register: VMReg) -> Vec< let mut operations = Vec::>::new(); operations.extend(compute_data(engine, def)); - operations.extend(reload_register(register)); + operations.extend(store_register(register)); - operations.extend(spill_register(register)); + operations.extend(load_register(register)); operations.extend(load_memory( register, VMReg::None, @@ -152,7 +152,7 @@ pub fn load_absolute(engine: &mut Engine, def: DataDef, register: VMReg) -> Vec< VMWidth::SLower32, )); operations.extend(add(None, None)); - operations.extend(reload_register(register)); + operations.extend(store_register(register)); operations } @@ -310,7 +310,7 @@ pub fn call(engine: &mut Engine, def: FnDef) -> Vec> { ] } -pub fn spill_register(source: VMReg) -> Vec> { +pub fn load_register(source: VMReg) -> Vec> { vec![Box::new(LoadRegister { width: VMWidth::Lower64, source, @@ -321,7 +321,7 @@ pub fn spill_vector(source: VMVec, width: VMWidth) -> Vec> { vec![Box::new(LoadVector { width, source })] } -pub fn reload_register(destination: VMReg) -> Vec> { +pub fn store_register(destination: VMReg) -> Vec> { vec![Box::new(StoreRegister { width: VMWidth::Lower64, destination, @@ -515,3 +515,23 @@ pub fn store_memory( }), ] } + +pub fn flag(flag: Flag) -> Vec> { + vec![ + Box::new(LoadRegister { + width: VMWidth::Lower64, + source: VMReg::Flags, + }), + Box::new(LoadImmediate { + width: VMWidth::Lower64, + source: flag.bit64().to_le_bytes().to_vec(), + }), + Box::new(And { + width: VMWidth::Lower64, + }), + ] +} + +pub fn discard() -> Vec> { + vec![Box::new(Discard::new())] +} diff --git a/runtime/src/functions/format.rs b/runtime/src/functions/format.rs index 0812d87c..89f1ae88 100644 --- a/runtime/src/functions/format.rs +++ b/runtime/src/functions/format.rs @@ -46,6 +46,7 @@ pub fn build(rt: &mut Runtime) { rt.asm.jge(convert_loop).unwrap(); } } + // mov [rax], 0x0 rt.asm.mov(byte_ptr(rax), 0x0).unwrap(); // ret diff --git a/runtime/src/runtime.rs b/runtime/src/runtime.rs index 2cc2fa2a..6d288b85 100644 --- a/runtime/src/runtime.rs +++ b/runtime/src/runtime.rs @@ -85,6 +85,7 @@ mapped! { VmHandlerVectorMul, VmHandlerVectorDiv, VmHandlerTimestamp, + VmHandlerDispatch, /* VM VEH */ VmVehInitialize, VmVehHandler, @@ -700,6 +701,7 @@ impl Runtime { (FnDef::VmHandlerVectorMul, vm::handlers::vector_mul::build), (FnDef::VmHandlerVectorDiv, vm::handlers::vector_div::build), (FnDef::VmHandlerTimestamp, vm::handlers::timestamp::build), + (FnDef::VmHandlerDispatch, vm::handlers::dispatch::build), (FnDef::VmFlags, vm::handlers::flags::build), (FnDef::VmVehInitialize, vm::functions::veh::initialize), (FnDef::Hash, functions::hash::build), diff --git a/runtime/src/vm/bytecode.rs b/runtime/src/vm/bytecode.rs index 71a34a30..5dde4451 100644 --- a/runtime/src/vm/bytecode.rs +++ b/runtime/src/vm/bytecode.rs @@ -7,6 +7,7 @@ use crate::vm::lifters::{ #[cfg(debug_assertions)] use crate::vm::snapshot::Snapshots; use crate::vm::transform::encrypt::Encrypt; +use crate::vm::transform::indirect::Indirect; use crate::vm::transform::mutation::Mutation; use crate::vm::transform::peephole::Peephole; use crate::vm::transform::permute::Permute; @@ -73,7 +74,8 @@ mapped! { VectorMul, VectorDiv, // Special - Timestamp + Timestamp, + Dispatch } } @@ -343,6 +345,10 @@ impl Encode for VMMem { self } + fn op(&self) -> Option { + None + } + fn encode(&self, mapper: &mut Mapper) -> Vec { let mut bytes = Vec::new(); bytes.push(mapper.index(self.base)); @@ -440,6 +446,10 @@ impl Encode for VMCondition { self } + fn op(&self) -> Option { + None + } + fn encode(&self, mapper: &mut Mapper) -> Vec { vec![mapper.index(self.test), self.lhs, self.rhs] } @@ -453,6 +463,7 @@ pub enum Phase { Scramble, Encrypt, Peephole, + Indirect, } impl Phase { @@ -464,6 +475,7 @@ impl Phase { Self::Scramble => "scramble", Self::Encrypt => "encrypt", Self::Peephole => "peephole", + Self::Indirect => "indirect", } } } @@ -629,33 +641,40 @@ pub fn assemble(mapper: &mut Mapper, operations: &[Box]) -> Vec let mut bytes = Vec::new(); for operation in operations { + if let Some(op) = operation.op() { + bytes.push(mapper.index(op)); + } bytes.extend(operation.encode(mapper)); } bytes } +fn transforms<'a>( + picker: &'a RefCell<&'a mut dyn FnMut(&[usize]) -> usize>, +) -> Vec> { + vec![ + Box::new(Peephole), + Box::new(Permute { picker }), + Box::new(Scramble), + Box::new(Mutation), + Box::new(Indirect), + Box::new(Encrypt), + Box::new(Permute { picker }), + Box::new(Peephole), + ] +} + pub fn transform( mapper: &mut Mapper, - operations: Vec>, + mut operations: Vec>, mut picker: F, ) -> Vec> where F: FnMut(&[usize]) -> usize, { - let mut operations = operations; - let picker = RefCell::<&mut dyn FnMut(&[usize]) -> usize>::new(&mut picker); - let mut transforms = Vec::>::new(); - transforms.push(Box::new(Peephole)); - transforms.push(Box::new(Permute { picker: &picker })); - transforms.push(Box::new(Scramble)); - transforms.push(Box::new(Mutation)); - transforms.push(Box::new(Encrypt)); - transforms.push(Box::new(Permute { picker: &picker })); - transforms.push(Box::new(Peephole)); - - for transform in transforms { + for transform in &transforms(&picker) { operations = transform.run(mapper, operations); } @@ -665,30 +684,20 @@ where #[cfg(debug_assertions)] pub fn transform_with_snapshots( mapper: &mut Mapper, - operations: Vec>, + mut operations: Vec>, mut picker: F, ) -> (Vec>, Snapshots) where F: FnMut(&[usize]) -> usize, { - let mut operations = operations; + let picker = RefCell::<&mut dyn FnMut(&[usize]) -> usize>::new(&mut picker); let mut snapshots = Snapshots::new(); snapshots.record(Phase::Lift, &operations); - let picker = RefCell::<&mut dyn FnMut(&[usize]) -> usize>::new(&mut picker); - - let mut transforms = Vec::>::new(); - transforms.push(Box::new(Peephole)); - transforms.push(Box::new(Permute { picker: &picker })); - transforms.push(Box::new(Scramble)); - transforms.push(Box::new(Mutation)); - transforms.push(Box::new(Encrypt)); - transforms.push(Box::new(Permute { picker: &picker })); - transforms.push(Box::new(Peephole)); - - for transform in transforms { + for transform in &transforms(&picker) { operations = transform.run(mapper, operations); + snapshots.record(transform.phase(), &operations); } diff --git a/runtime/src/vm/encoders/add.rs b/runtime/src/vm/encoders/add.rs index 6486f6ee..55c4fa5b 100644 --- a/runtime/src/vm/encoders/add.rs +++ b/runtime/src/vm/encoders/add.rs @@ -17,15 +17,23 @@ impl Encode for Add { self } + fn op(&self) -> Option { + Some(VMOp::Add) + } + fn encode(&self, mapper: &mut Mapper) -> Vec { - vec![mapper.index(VMOp::Add), mapper.index(self.width)] + vec![mapper.index(self.width)] } fn writes(&self) -> Vec { vec![Effect::Register(VMReg::Flags)] } - fn depth(&self) -> i32 { - -1 + fn consumes(&self) -> i32 { + 2 + } + + fn produces(&self) -> i32 { + 1 } } diff --git a/runtime/src/vm/encoders/and.rs b/runtime/src/vm/encoders/and.rs index 1aeea69e..22aeec32 100644 --- a/runtime/src/vm/encoders/and.rs +++ b/runtime/src/vm/encoders/and.rs @@ -17,15 +17,23 @@ impl Encode for And { self } + fn op(&self) -> Option { + Some(VMOp::And) + } + fn encode(&self, mapper: &mut Mapper) -> Vec { - vec![mapper.index(VMOp::And), mapper.index(self.width)] + vec![mapper.index(self.width)] } fn writes(&self) -> Vec { vec![Effect::Register(VMReg::Flags)] } - fn depth(&self) -> i32 { - -1 + fn consumes(&self) -> i32 { + 2 + } + + fn produces(&self) -> i32 { + 1 } } diff --git a/runtime/src/vm/encoders/bit_scan_reverse.rs b/runtime/src/vm/encoders/bit_scan_reverse.rs index 2841d4ec..665cb5a8 100644 --- a/runtime/src/vm/encoders/bit_scan_reverse.rs +++ b/runtime/src/vm/encoders/bit_scan_reverse.rs @@ -17,15 +17,23 @@ impl Encode for BitScanReverse { self } + fn op(&self) -> Option { + Some(VMOp::BitScanReverse) + } + fn encode(&self, mapper: &mut Mapper) -> Vec { - vec![mapper.index(VMOp::BitScanReverse), mapper.index(self.width)] + vec![mapper.index(self.width)] } fn writes(&self) -> Vec { vec![Effect::Register(VMReg::Flags)] } - fn depth(&self) -> i32 { - -1 + fn consumes(&self) -> i32 { + 1 + } + + fn produces(&self) -> i32 { + 1 } } diff --git a/runtime/src/vm/encoders/bit_test.rs b/runtime/src/vm/encoders/bit_test.rs index 3705cd79..e632f689 100644 --- a/runtime/src/vm/encoders/bit_test.rs +++ b/runtime/src/vm/encoders/bit_test.rs @@ -17,15 +17,23 @@ impl Encode for BitTest { self } + fn op(&self) -> Option { + Some(VMOp::BitTest) + } + fn encode(&self, mapper: &mut Mapper) -> Vec { - vec![mapper.index(VMOp::BitTest), mapper.index(self.width)] + vec![mapper.index(self.width)] } fn writes(&self) -> Vec { vec![Effect::Register(VMReg::Flags)] } - fn depth(&self) -> i32 { - -1 + fn consumes(&self) -> i32 { + 2 + } + + fn produces(&self) -> i32 { + 1 } } diff --git a/runtime/src/vm/encoders/bit_test_complement.rs b/runtime/src/vm/encoders/bit_test_complement.rs index 3ea991e1..f084a771 100644 --- a/runtime/src/vm/encoders/bit_test_complement.rs +++ b/runtime/src/vm/encoders/bit_test_complement.rs @@ -17,18 +17,24 @@ impl Encode for BitTestComplement { self } + fn op(&self) -> Option { + Some(VMOp::BitTestComplement) + } + fn encode(&self, mapper: &mut Mapper) -> Vec { - vec![ - mapper.index(VMOp::BitTestComplement), - mapper.index(self.width), - ] + vec![mapper.index(self.width)] } fn writes(&self) -> Vec { vec![Effect::Register(VMReg::Flags)] } - fn depth(&self) -> i32 { - -1 + fn consumes(&self) -> i32 { + 2 } + + fn produces(&self) -> i32 { + 1 + } + } diff --git a/runtime/src/vm/encoders/bit_test_reset.rs b/runtime/src/vm/encoders/bit_test_reset.rs index 7aacefa8..d7fbf278 100644 --- a/runtime/src/vm/encoders/bit_test_reset.rs +++ b/runtime/src/vm/encoders/bit_test_reset.rs @@ -17,15 +17,23 @@ impl Encode for BitTestReset { self } + fn op(&self) -> Option { + Some(VMOp::BitTestReset) + } + fn encode(&self, mapper: &mut Mapper) -> Vec { - vec![mapper.index(VMOp::BitTestReset), mapper.index(self.width)] + vec![mapper.index(self.width)] } fn writes(&self) -> Vec { vec![Effect::Register(VMReg::Flags)] } - fn depth(&self) -> i32 { - -1 + fn consumes(&self) -> i32 { + 2 + } + + fn produces(&self) -> i32 { + 1 } } diff --git a/runtime/src/vm/encoders/bit_test_set.rs b/runtime/src/vm/encoders/bit_test_set.rs index d569a7e0..0e064c6b 100644 --- a/runtime/src/vm/encoders/bit_test_set.rs +++ b/runtime/src/vm/encoders/bit_test_set.rs @@ -17,15 +17,24 @@ impl Encode for BitTestSet { self } + fn op(&self) -> Option { + Some(VMOp::BitTestSet) + } + fn encode(&self, mapper: &mut Mapper) -> Vec { - vec![mapper.index(VMOp::BitTestSet), mapper.index(self.width)] + vec![mapper.index(self.width)] } fn writes(&self) -> Vec { vec![Effect::Register(VMReg::Flags)] } - fn depth(&self) -> i32 { - -1 + fn consumes(&self) -> i32 { + 2 } + + fn produces(&self) -> i32 { + 1 + } + } diff --git a/runtime/src/vm/encoders/block.rs b/runtime/src/vm/encoders/block.rs index 0acff4dc..27e76c64 100644 --- a/runtime/src/vm/encoders/block.rs +++ b/runtime/src/vm/encoders/block.rs @@ -1,7 +1,7 @@ use std::any::Any; use crate::mapper::Mapper; -use crate::vm::bytecode::{self, VMCondition, VMLogic, VMWidth}; +use crate::vm::bytecode::{self, VMCondition, VMLogic, VMOp, VMWidth}; use crate::vm::encoders::jcc::Jcc; use crate::vm::encoders::label::Label; use crate::vm::encoders::load_immediate::LoadImmediate; @@ -89,22 +89,18 @@ impl Block { for i in index + 1..self.body.len() { let operation = &self.body[i]; + let consumes = operation.consumes() as usize; + if operation.as_any().downcast_ref::().is_some() { - if stack.len() == 1 { - return Some((stack[0], i)); - } + return Some((stack[stack.len() - consumes], i)); } - let delta = operation.depth(); + for _ in 0..consumes { + stack.pop(); + } - if delta > 0 { - for _ in 0..delta { - stack.push(i); - } - } else { - for _ in 0..(-delta) { - stack.pop(); - } + for _ in 0..operation.produces() { + stack.push(i); } } @@ -121,6 +117,10 @@ impl Encode for Block { self } + fn op(&self) -> Option { + None + } + fn encode(&self, mapper: &mut Mapper) -> Vec { bytecode::assemble(mapper, &self.body) } @@ -137,8 +137,12 @@ impl Encode for Block { self.body.iter().flat_map(|op| op.writes()).collect() } - fn depth(&self) -> i32 { - self.body.iter().map(|op| op.depth()).sum() + fn consumes(&self) -> i32 { + self.body.iter().map(|op| op.consumes()).sum() + } + + fn produces(&self) -> i32 { + self.body.iter().map(|op| op.produces()).sum() } fn children_ref(&self) -> Option<&[Box]> { diff --git a/runtime/src/vm/encoders/byte_swap.rs b/runtime/src/vm/encoders/byte_swap.rs index 55e42b64..e772c8dd 100644 --- a/runtime/src/vm/encoders/byte_swap.rs +++ b/runtime/src/vm/encoders/byte_swap.rs @@ -17,7 +17,19 @@ impl Encode for ByteSwap { self } + fn op(&self) -> Option { + Some(VMOp::ByteSwap) + } + fn encode(&self, mapper: &mut Mapper) -> Vec { - vec![mapper.index(VMOp::ByteSwap), mapper.index(self.width)] + vec![mapper.index(self.width)] + } + + fn consumes(&self) -> i32 { + 1 + } + + fn produces(&self) -> i32 { + 1 } } diff --git a/runtime/src/vm/encoders/compare_exchange.rs b/runtime/src/vm/encoders/compare_exchange.rs index f83f1b12..852cf8f8 100644 --- a/runtime/src/vm/encoders/compare_exchange.rs +++ b/runtime/src/vm/encoders/compare_exchange.rs @@ -17,18 +17,23 @@ impl Encode for CompareExchange { self } + fn op(&self) -> Option { + Some(VMOp::CompareExchange) + } + fn encode(&self, mapper: &mut Mapper) -> Vec { - vec![ - mapper.index(VMOp::CompareExchange), - mapper.index(self.width), - ] + vec![mapper.index(self.width)] } fn writes(&self) -> Vec { vec![Effect::Memory, Effect::Register(VMReg::Flags)] } - fn depth(&self) -> i32 { - -2 + fn consumes(&self) -> i32 { + 3 + } + + fn produces(&self) -> i32 { + 1 } } diff --git a/runtime/src/vm/encoders/discard.rs b/runtime/src/vm/encoders/discard.rs index e4263294..c1a01f47 100644 --- a/runtime/src/vm/encoders/discard.rs +++ b/runtime/src/vm/encoders/discard.rs @@ -30,11 +30,19 @@ impl Encode for Discard { self } - fn encode(&self, mapper: &mut Mapper) -> Vec { - vec![mapper.index(VMOp::Discard)] + fn op(&self) -> Option { + Some(VMOp::Discard) } - fn depth(&self) -> i32 { - -1 + fn encode(&self, _mapper: &mut Mapper) -> Vec { + vec![] + } + + fn consumes(&self) -> i32 { + 1 + } + + fn produces(&self) -> i32 { + 0 } } diff --git a/runtime/src/vm/encoders/dispatch.rs b/runtime/src/vm/encoders/dispatch.rs new file mode 100644 index 00000000..ab3ec6db --- /dev/null +++ b/runtime/src/vm/encoders/dispatch.rs @@ -0,0 +1,71 @@ +use crate::mapper::Mapper; +use crate::vm::bytecode::VMOp; +use crate::vm::encoders::{Effect, Encode}; +use std::any::Any; + +#[derive(Debug)] +pub struct Dispatch { + pub operation: Box, +} + +impl Encode for Dispatch { + fn as_any(&self) -> &dyn Any { + self.operation.as_any() + } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self.operation.as_any_mut() + } + + fn op(&self) -> Option { + Some(VMOp::Dispatch) + } + + fn encode(&self, mapper: &mut Mapper) -> Vec { + self.operation.encode(mapper) + } + + fn size(&self, mapper: &mut Mapper) -> usize { + self.operation.size(mapper) + } + + fn reads(&self) -> Vec { + self.operation.reads() + } + + fn writes(&self) -> Vec { + self.operation.writes() + } + + fn consumes(&self) -> i32 { + 1 + self.operation.consumes() + } + + fn produces(&self) -> i32 { + self.operation.produces() + } + + fn is_source(&self) -> bool { + self.operation.is_source() + } + + fn is_destination(&self) -> bool { + self.operation.is_destination() + } + + fn is_branch(&self) -> bool { + self.operation.is_branch() + } + + fn children_ref(&self) -> Option<&[Box]> { + self.operation.children_ref() + } + + fn children_mut(&mut self) -> Option<&mut Vec>> { + self.operation.children_mut() + } + + fn seal(&mut self, mapper: &mut Mapper, transform: &mut dyn FnMut(&mut [u8], usize)) { + self.operation.seal(mapper, transform); + } +} diff --git a/runtime/src/vm/encoders/div.rs b/runtime/src/vm/encoders/div.rs index 2c44804d..731625cf 100644 --- a/runtime/src/vm/encoders/div.rs +++ b/runtime/src/vm/encoders/div.rs @@ -17,11 +17,19 @@ impl Encode for Div { self } + fn op(&self) -> Option { + Some(VMOp::Div) + } + fn encode(&self, mapper: &mut Mapper) -> Vec { - vec![mapper.index(VMOp::Div), mapper.index(self.width)] + vec![mapper.index(self.width)] + } + + fn consumes(&self) -> i32 { + 3 } - fn depth(&self) -> i32 { - -1 + fn produces(&self) -> i32 { + 2 } } diff --git a/runtime/src/vm/encoders/exchange.rs b/runtime/src/vm/encoders/exchange.rs index cbde10e8..0755490a 100644 --- a/runtime/src/vm/encoders/exchange.rs +++ b/runtime/src/vm/encoders/exchange.rs @@ -17,15 +17,23 @@ impl Encode for Exchange { self } + fn op(&self) -> Option { + Some(VMOp::Exchange) + } + fn encode(&self, mapper: &mut Mapper) -> Vec { - vec![mapper.index(VMOp::Exchange), mapper.index(self.width)] + vec![mapper.index(self.width)] } fn writes(&self) -> Vec { vec![Effect::Memory] } - fn depth(&self) -> i32 { - -1 + fn consumes(&self) -> i32 { + 2 + } + + fn produces(&self) -> i32 { + 1 } } diff --git a/runtime/src/vm/encoders/exchange_add.rs b/runtime/src/vm/encoders/exchange_add.rs index 6f768dbc..eef9e2d7 100644 --- a/runtime/src/vm/encoders/exchange_add.rs +++ b/runtime/src/vm/encoders/exchange_add.rs @@ -17,15 +17,24 @@ impl Encode for ExchangeAdd { self } + fn op(&self) -> Option { + Some(VMOp::ExchangeAdd) + } + fn encode(&self, mapper: &mut Mapper) -> Vec { - vec![mapper.index(VMOp::ExchangeAdd), mapper.index(self.width)] + vec![mapper.index(self.width)] } fn writes(&self) -> Vec { vec![Effect::Memory, Effect::Register(VMReg::Flags)] } - fn depth(&self) -> i32 { - -1 + fn consumes(&self) -> i32 { + 2 } + + fn produces(&self) -> i32 { + 1 + } + } diff --git a/runtime/src/vm/encoders/jcc.rs b/runtime/src/vm/encoders/jcc.rs index 28b651c6..d0d31c46 100644 --- a/runtime/src/vm/encoders/jcc.rs +++ b/runtime/src/vm/encoders/jcc.rs @@ -52,12 +52,12 @@ impl Encode for Jcc { self } + fn op(&self) -> Option { + Some(VMOp::Jcc) + } + fn encode(&self, mapper: &mut Mapper) -> Vec { - let mut bytes = vec![ - mapper.index(VMOp::Jcc), - mapper.index(self.logic), - self.conditions.len() as u8, - ]; + let mut bytes = vec![mapper.index(self.logic), self.conditions.len() as u8]; for condition in &self.conditions { bytes.extend_from_slice(&condition.encode(mapper)); @@ -80,8 +80,12 @@ impl Encode for Jcc { } } - fn depth(&self) -> i32 { - -1 + fn consumes(&self) -> i32 { + 1 + } + + fn produces(&self) -> i32 { + 0 } fn is_branch(&self) -> bool { diff --git a/runtime/src/vm/encoders/label.rs b/runtime/src/vm/encoders/label.rs index 6747740b..a382d3c4 100644 --- a/runtime/src/vm/encoders/label.rs +++ b/runtime/src/vm/encoders/label.rs @@ -1,4 +1,7 @@ -use crate::{mapper::Mapper, vm::encoders::Encode}; +use crate::{ + mapper::Mapper, + vm::{bytecode::VMOp, encoders::Encode}, +}; use std::{ any::Any, sync::atomic::{AtomicUsize, Ordering}, @@ -47,6 +50,10 @@ impl Encode for Label { self } + fn op(&self) -> Option { + None + } + fn encode(&self, _mapper: &mut Mapper) -> Vec { vec![] } diff --git a/runtime/src/vm/encoders/load_address.rs b/runtime/src/vm/encoders/load_address.rs index edbd5e9a..39c9ea3c 100644 --- a/runtime/src/vm/encoders/load_address.rs +++ b/runtime/src/vm/encoders/load_address.rs @@ -17,10 +17,12 @@ impl Encode for LoadAddress { self } + fn op(&self) -> Option { + Some(VMOp::LoadAddress) + } + fn encode(&self, mapper: &mut Mapper) -> Vec { - let mut bytes = vec![mapper.index(VMOp::LoadAddress)]; - bytes.extend(self.source.encode(mapper)); - bytes + self.source.encode(mapper) } fn reads(&self) -> Vec { @@ -32,7 +34,11 @@ impl Encode for LoadAddress { ] } - fn depth(&self) -> i32 { + fn consumes(&self) -> i32 { + 0 + } + + fn produces(&self) -> i32 { 1 } } diff --git a/runtime/src/vm/encoders/load_immediate.rs b/runtime/src/vm/encoders/load_immediate.rs index 45bf4c18..a86ebde3 100644 --- a/runtime/src/vm/encoders/load_immediate.rs +++ b/runtime/src/vm/encoders/load_immediate.rs @@ -18,8 +18,12 @@ impl Encode for LoadImmediate { self } + fn op(&self) -> Option { + Some(VMOp::LoadImmediate) + } + fn encode(&self, mapper: &mut Mapper) -> Vec { - let mut bytes = vec![mapper.index(VMOp::LoadImmediate), mapper.index(self.width)]; + let mut bytes = vec![mapper.index(self.width)]; bytes.extend_from_slice(&self.source); bytes } @@ -31,7 +35,11 @@ impl Encode for LoadImmediate { ] } - fn depth(&self) -> i32 { + fn consumes(&self) -> i32 { + 0 + } + + fn produces(&self) -> i32 { 1 } } diff --git a/runtime/src/vm/encoders/load_memory.rs b/runtime/src/vm/encoders/load_memory.rs index f7c2d125..c27c9997 100644 --- a/runtime/src/vm/encoders/load_memory.rs +++ b/runtime/src/vm/encoders/load_memory.rs @@ -17,15 +17,23 @@ impl Encode for LoadMemory { self } + fn op(&self) -> Option { + Some(VMOp::LoadMemory) + } + fn encode(&self, mapper: &mut Mapper) -> Vec { - vec![mapper.index(VMOp::LoadMemory), mapper.index(self.width)] + vec![mapper.index(self.width)] } fn reads(&self) -> Vec { vec![Effect::Memory] } - fn depth(&self) -> i32 { - self.width.slots() - 1 + fn consumes(&self) -> i32 { + 1 + } + + fn produces(&self) -> i32 { + 1 } } diff --git a/runtime/src/vm/encoders/load_register.rs b/runtime/src/vm/encoders/load_register.rs index 4ea4edef..12b89b06 100644 --- a/runtime/src/vm/encoders/load_register.rs +++ b/runtime/src/vm/encoders/load_register.rs @@ -18,19 +18,23 @@ impl Encode for LoadRegister { self } + fn op(&self) -> Option { + Some(VMOp::LoadRegister) + } + fn encode(&self, mapper: &mut Mapper) -> Vec { - vec![ - mapper.index(VMOp::LoadRegister), - mapper.index(self.width), - mapper.index(self.source), - ] + vec![mapper.index(self.width), mapper.index(self.source)] } fn reads(&self) -> Vec { vec![Effect::Register(self.source)] } - fn depth(&self) -> i32 { + fn consumes(&self) -> i32 { + 0 + } + + fn produces(&self) -> i32 { 1 } } diff --git a/runtime/src/vm/encoders/load_vector.rs b/runtime/src/vm/encoders/load_vector.rs index 9eb1bbb9..42195f87 100644 --- a/runtime/src/vm/encoders/load_vector.rs +++ b/runtime/src/vm/encoders/load_vector.rs @@ -18,19 +18,23 @@ impl Encode for LoadVector { self } + fn op(&self) -> Option { + Some(VMOp::LoadVector) + } + fn encode(&self, mapper: &mut Mapper) -> Vec { - vec![ - mapper.index(VMOp::LoadVector), - mapper.index(self.width), - mapper.index(self.source), - ] + vec![mapper.index(self.width), mapper.index(self.source)] } fn reads(&self) -> Vec { vec![Effect::Vector(self.source)] } - fn depth(&self) -> i32 { - self.width.slots() + fn consumes(&self) -> i32 { + 1 + } + + fn produces(&self) -> i32 { + 1 } } diff --git a/runtime/src/vm/encoders/mod.rs b/runtime/src/vm/encoders/mod.rs index 43a72646..c25e3197 100644 --- a/runtime/src/vm/encoders/mod.rs +++ b/runtime/src/vm/encoders/mod.rs @@ -2,7 +2,7 @@ use std::any::{type_name, Any}; use std::fmt::Debug; use crate::mapper::Mapper; -use crate::vm::bytecode::{VMReg, VMVec}; +use crate::vm::bytecode::{VMOp, VMReg, VMVec}; #[cfg(debug_assertions)] mod debug; @@ -18,6 +18,16 @@ pub fn identity(operation: &Box) -> usize { operation.as_ref() as *const dyn Encode as *const () as usize } +pub trait Depth { + fn depth(&self) -> i32; +} + +impl Depth for T { + fn depth(&self) -> i32 { + self.produces() - self.consumes() + } +} + pub trait Encode: Debug + Any { fn as_any(&self) -> &dyn Any; @@ -27,10 +37,12 @@ pub trait Encode: Debug + Any { type_name::().rsplit("::").next().unwrap() } + fn op(&self) -> Option; + fn encode(&self, mapper: &mut Mapper) -> Vec; fn size(&self, mapper: &mut Mapper) -> usize { - self.encode(mapper).len() + self.op().map_or(0, |_| 1) + self.encode(mapper).len() } fn reads(&self) -> Vec { @@ -41,7 +53,11 @@ pub trait Encode: Debug + Any { vec![] } - fn depth(&self) -> i32 { + fn consumes(&self) -> i32 { + 0 + } + + fn produces(&self) -> i32 { 0 } @@ -79,6 +95,7 @@ pub mod block; pub mod byte_swap; pub mod compare_exchange; pub mod discard; +pub mod dispatch; pub mod div; pub mod exchange; pub mod exchange_add; diff --git a/runtime/src/vm/encoders/mul.rs b/runtime/src/vm/encoders/mul.rs index 5a87b5bf..dac49edf 100644 --- a/runtime/src/vm/encoders/mul.rs +++ b/runtime/src/vm/encoders/mul.rs @@ -17,15 +17,23 @@ impl Encode for Mul { self } + fn op(&self) -> Option { + Some(VMOp::Mul) + } + fn encode(&self, mapper: &mut Mapper) -> Vec { - vec![mapper.index(VMOp::Mul), mapper.index(self.width)] + vec![mapper.index(self.width)] } fn writes(&self) -> Vec { vec![Effect::Register(VMReg::Flags)] } - fn depth(&self) -> i32 { - 0 + fn consumes(&self) -> i32 { + 2 + } + + fn produces(&self) -> i32 { + 2 } } diff --git a/runtime/src/vm/encoders/or.rs b/runtime/src/vm/encoders/or.rs index 15e6c4fc..36fa39ff 100644 --- a/runtime/src/vm/encoders/or.rs +++ b/runtime/src/vm/encoders/or.rs @@ -17,15 +17,24 @@ impl Encode for Or { self } + fn op(&self) -> Option { + Some(VMOp::Or) + } + fn encode(&self, mapper: &mut Mapper) -> Vec { - vec![mapper.index(VMOp::Or), mapper.index(self.width)] + vec![mapper.index(self.width)] } fn writes(&self) -> Vec { vec![Effect::Register(VMReg::Flags)] } - fn depth(&self) -> i32 { - -1 + fn consumes(&self) -> i32 { + 2 } + + fn produces(&self) -> i32 { + 1 + } + } diff --git a/runtime/src/vm/encoders/packed_byte_equal.rs b/runtime/src/vm/encoders/packed_byte_equal.rs index 3fed8d28..e3e3baab 100644 --- a/runtime/src/vm/encoders/packed_byte_equal.rs +++ b/runtime/src/vm/encoders/packed_byte_equal.rs @@ -17,14 +17,19 @@ impl Encode for PackedByteEqual { self } + fn op(&self) -> Option { + Some(VMOp::PackedByteEqual) + } + fn encode(&self, mapper: &mut Mapper) -> Vec { - vec![ - mapper.index(VMOp::PackedByteEqual), - mapper.index(self.width), - ] + vec![mapper.index(self.width)] + } + + fn consumes(&self) -> i32 { + 2 } - fn depth(&self) -> i32 { - -self.width.slots() + fn produces(&self) -> i32 { + 1 } } diff --git a/runtime/src/vm/encoders/packed_byte_mask.rs b/runtime/src/vm/encoders/packed_byte_mask.rs index e7f73862..d338cb11 100644 --- a/runtime/src/vm/encoders/packed_byte_mask.rs +++ b/runtime/src/vm/encoders/packed_byte_mask.rs @@ -17,11 +17,19 @@ impl Encode for PackedByteMask { self } + fn op(&self) -> Option { + Some(VMOp::PackedByteMask) + } + fn encode(&self, mapper: &mut Mapper) -> Vec { - vec![mapper.index(VMOp::PackedByteMask), mapper.index(self.width)] + vec![mapper.index(self.width)] + } + + fn consumes(&self) -> i32 { + 1 } - fn depth(&self) -> i32 { - 1 - self.width.slots() + fn produces(&self) -> i32 { + 1 } } diff --git a/runtime/src/vm/encoders/pop.rs b/runtime/src/vm/encoders/pop.rs index 5f3bce9b..668db529 100644 --- a/runtime/src/vm/encoders/pop.rs +++ b/runtime/src/vm/encoders/pop.rs @@ -30,8 +30,12 @@ impl Encode for Pop { self } - fn encode(&self, mapper: &mut Mapper) -> Vec { - vec![mapper.index(VMOp::Pop)] + fn op(&self) -> Option { + Some(VMOp::Pop) + } + + fn encode(&self, _mapper: &mut Mapper) -> Vec { + vec![] } fn reads(&self) -> Vec { @@ -42,7 +46,11 @@ impl Encode for Pop { vec![Effect::Register(VMReg::Rsp)] } - fn depth(&self) -> i32 { + fn consumes(&self) -> i32 { + 0 + } + + fn produces(&self) -> i32 { 1 } } diff --git a/runtime/src/vm/encoders/push.rs b/runtime/src/vm/encoders/push.rs index 0daa9253..b1a7dfcf 100644 --- a/runtime/src/vm/encoders/push.rs +++ b/runtime/src/vm/encoders/push.rs @@ -30,8 +30,12 @@ impl Encode for Push { self } - fn encode(&self, mapper: &mut Mapper) -> Vec { - vec![mapper.index(VMOp::Push)] + fn op(&self) -> Option { + Some(VMOp::Push) + } + + fn encode(&self, _mapper: &mut Mapper) -> Vec { + vec![] } fn reads(&self) -> Vec { @@ -42,7 +46,11 @@ impl Encode for Push { vec![Effect::Memory, Effect::Register(VMReg::Rsp)] } - fn depth(&self) -> i32 { - -1 + fn consumes(&self) -> i32 { + 1 + } + + fn produces(&self) -> i32 { + 0 } } diff --git a/runtime/src/vm/encoders/ret.rs b/runtime/src/vm/encoders/ret.rs index b4776963..8d20f80b 100644 --- a/runtime/src/vm/encoders/ret.rs +++ b/runtime/src/vm/encoders/ret.rs @@ -15,8 +15,12 @@ impl Encode for Ret { self } - fn encode(&self, mapper: &mut Mapper) -> Vec { - vec![mapper.index(VMOp::Ret)] + fn op(&self) -> Option { + Some(VMOp::Ret) + } + + fn encode(&self, _mapper: &mut Mapper) -> Vec { + vec![] } fn reads(&self) -> Vec { @@ -30,8 +34,12 @@ impl Encode for Ret { ] } - fn depth(&self) -> i32 { - -1 + fn consumes(&self) -> i32 { + 1 + } + + fn produces(&self) -> i32 { + 0 } fn is_branch(&self) -> bool { diff --git a/runtime/src/vm/encoders/rol.rs b/runtime/src/vm/encoders/rol.rs index 08cac5d0..f863be45 100644 --- a/runtime/src/vm/encoders/rol.rs +++ b/runtime/src/vm/encoders/rol.rs @@ -17,15 +17,24 @@ impl Encode for Rol { self } + fn op(&self) -> Option { + Some(VMOp::Rol) + } + fn encode(&self, mapper: &mut Mapper) -> Vec { - vec![mapper.index(VMOp::Rol), mapper.index(self.width)] + vec![mapper.index(self.width)] } fn writes(&self) -> Vec { vec![Effect::Register(VMReg::Flags)] } - fn depth(&self) -> i32 { - -1 + fn consumes(&self) -> i32 { + 2 } + + fn produces(&self) -> i32 { + 1 + } + } diff --git a/runtime/src/vm/encoders/ror.rs b/runtime/src/vm/encoders/ror.rs index 9fb51e60..54bb1c7d 100644 --- a/runtime/src/vm/encoders/ror.rs +++ b/runtime/src/vm/encoders/ror.rs @@ -17,15 +17,24 @@ impl Encode for Ror { self } + fn op(&self) -> Option { + Some(VMOp::Ror) + } + fn encode(&self, mapper: &mut Mapper) -> Vec { - vec![mapper.index(VMOp::Ror), mapper.index(self.width)] + vec![mapper.index(self.width)] } fn writes(&self) -> Vec { vec![Effect::Register(VMReg::Flags)] } - fn depth(&self) -> i32 { - -1 + fn consumes(&self) -> i32 { + 2 } + + fn produces(&self) -> i32 { + 1 + } + } diff --git a/runtime/src/vm/encoders/sar.rs b/runtime/src/vm/encoders/sar.rs index d9c1ae4f..d046f32b 100644 --- a/runtime/src/vm/encoders/sar.rs +++ b/runtime/src/vm/encoders/sar.rs @@ -17,15 +17,24 @@ impl Encode for Sar { self } + fn op(&self) -> Option { + Some(VMOp::Sar) + } + fn encode(&self, mapper: &mut Mapper) -> Vec { - vec![mapper.index(VMOp::Sar), mapper.index(self.width)] + vec![mapper.index(self.width)] } fn writes(&self) -> Vec { vec![Effect::Register(VMReg::Flags)] } - fn depth(&self) -> i32 { - -1 + fn consumes(&self) -> i32 { + 2 } + + fn produces(&self) -> i32 { + 1 + } + } diff --git a/runtime/src/vm/encoders/shl.rs b/runtime/src/vm/encoders/shl.rs index 85e81856..e0d6a291 100644 --- a/runtime/src/vm/encoders/shl.rs +++ b/runtime/src/vm/encoders/shl.rs @@ -17,15 +17,24 @@ impl Encode for Shl { self } + fn op(&self) -> Option { + Some(VMOp::Shl) + } + fn encode(&self, mapper: &mut Mapper) -> Vec { - vec![mapper.index(VMOp::Shl), mapper.index(self.width)] + vec![mapper.index(self.width)] } fn writes(&self) -> Vec { vec![Effect::Register(VMReg::Flags)] } - fn depth(&self) -> i32 { - -1 + fn consumes(&self) -> i32 { + 2 } + + fn produces(&self) -> i32 { + 1 + } + } diff --git a/runtime/src/vm/encoders/shr.rs b/runtime/src/vm/encoders/shr.rs index d1394318..376899a6 100644 --- a/runtime/src/vm/encoders/shr.rs +++ b/runtime/src/vm/encoders/shr.rs @@ -17,15 +17,23 @@ impl Encode for Shr { self } + fn op(&self) -> Option { + Some(VMOp::Shr) + } + fn encode(&self, mapper: &mut Mapper) -> Vec { - vec![mapper.index(VMOp::Shr), mapper.index(self.width)] + vec![mapper.index(self.width)] } fn writes(&self) -> Vec { vec![Effect::Register(VMReg::Flags)] } - fn depth(&self) -> i32 { - -1 + fn consumes(&self) -> i32 { + 2 + } + + fn produces(&self) -> i32 { + 1 } } diff --git a/runtime/src/vm/encoders/store_extend.rs b/runtime/src/vm/encoders/store_extend.rs index ad69f00b..dccce633 100644 --- a/runtime/src/vm/encoders/store_extend.rs +++ b/runtime/src/vm/encoders/store_extend.rs @@ -18,19 +18,23 @@ impl Encode for StoreExtend { self } + fn op(&self) -> Option { + Some(VMOp::StoreExtend) + } + fn encode(&self, mapper: &mut Mapper) -> Vec { - vec![ - mapper.index(VMOp::StoreExtend), - mapper.index(self.width), - mapper.index(self.destination), - ] + vec![mapper.index(self.width), mapper.index(self.destination)] } fn writes(&self) -> Vec { vec![Effect::Vector(self.destination)] } - fn depth(&self) -> i32 { - -self.width.slots() + fn consumes(&self) -> i32 { + 2 + } + + fn produces(&self) -> i32 { + 0 } } diff --git a/runtime/src/vm/encoders/store_memory.rs b/runtime/src/vm/encoders/store_memory.rs index a220cbc7..84050f3f 100644 --- a/runtime/src/vm/encoders/store_memory.rs +++ b/runtime/src/vm/encoders/store_memory.rs @@ -17,15 +17,23 @@ impl Encode for StoreMemory { self } + fn op(&self) -> Option { + Some(VMOp::StoreMemory) + } + fn encode(&self, mapper: &mut Mapper) -> Vec { - vec![mapper.index(VMOp::StoreMemory), mapper.index(self.width)] + vec![mapper.index(self.width)] } fn writes(&self) -> Vec { vec![Effect::Memory] } - fn depth(&self) -> i32 { - -(self.width.slots() + 1) + fn consumes(&self) -> i32 { + 2 + } + + fn produces(&self) -> i32 { + 0 } } diff --git a/runtime/src/vm/encoders/store_merge.rs b/runtime/src/vm/encoders/store_merge.rs index 5da277c7..2ee89c61 100644 --- a/runtime/src/vm/encoders/store_merge.rs +++ b/runtime/src/vm/encoders/store_merge.rs @@ -18,19 +18,23 @@ impl Encode for StoreMerge { self } + fn op(&self) -> Option { + Some(VMOp::StoreMerge) + } + fn encode(&self, mapper: &mut Mapper) -> Vec { - vec![ - mapper.index(VMOp::StoreMerge), - mapper.index(self.width), - mapper.index(self.destination), - ] + vec![mapper.index(self.width), mapper.index(self.destination)] } fn writes(&self) -> Vec { vec![Effect::Vector(self.destination)] } - fn depth(&self) -> i32 { - -self.width.slots() + fn consumes(&self) -> i32 { + 2 + } + + fn produces(&self) -> i32 { + 0 } } diff --git a/runtime/src/vm/encoders/store_register.rs b/runtime/src/vm/encoders/store_register.rs index 82b5b587..5a28a011 100644 --- a/runtime/src/vm/encoders/store_register.rs +++ b/runtime/src/vm/encoders/store_register.rs @@ -18,19 +18,24 @@ impl Encode for StoreRegister { self } + fn op(&self) -> Option { + Some(VMOp::StoreRegister) + } + fn encode(&self, mapper: &mut Mapper) -> Vec { - vec![ - mapper.index(VMOp::StoreRegister), - mapper.index(self.width), - mapper.index(self.destination), - ] + vec![mapper.index(self.width), mapper.index(self.destination)] } fn writes(&self) -> Vec { vec![Effect::Register(self.destination)] } - fn depth(&self) -> i32 { - -1 + fn consumes(&self) -> i32 { + 1 } + + fn produces(&self) -> i32 { + 0 + } + } diff --git a/runtime/src/vm/encoders/sub.rs b/runtime/src/vm/encoders/sub.rs index 82e276a6..7b7904b5 100644 --- a/runtime/src/vm/encoders/sub.rs +++ b/runtime/src/vm/encoders/sub.rs @@ -17,15 +17,24 @@ impl Encode for Sub { self } + fn op(&self) -> Option { + Some(VMOp::Sub) + } + fn encode(&self, mapper: &mut Mapper) -> Vec { - vec![mapper.index(VMOp::Sub), mapper.index(self.width)] + vec![mapper.index(self.width)] } fn writes(&self) -> Vec { vec![Effect::Register(VMReg::Flags)] } - fn depth(&self) -> i32 { - -1 + fn consumes(&self) -> i32 { + 2 } + + fn produces(&self) -> i32 { + 1 + } + } diff --git a/runtime/src/vm/encoders/timestamp.rs b/runtime/src/vm/encoders/timestamp.rs index 36aff9c4..a256b96e 100644 --- a/runtime/src/vm/encoders/timestamp.rs +++ b/runtime/src/vm/encoders/timestamp.rs @@ -30,11 +30,19 @@ impl Encode for Timestamp { self } - fn encode(&self, mapper: &mut Mapper) -> Vec { - vec![mapper.index(VMOp::Timestamp)] + fn op(&self) -> Option { + Some(VMOp::Timestamp) } - fn depth(&self) -> i32 { + fn encode(&self, _mapper: &mut Mapper) -> Vec { + vec![] + } + + fn consumes(&self) -> i32 { + 0 + } + + fn produces(&self) -> i32 { 2 } } diff --git a/runtime/src/vm/encoders/trailing_zeros.rs b/runtime/src/vm/encoders/trailing_zeros.rs index 34895e54..afe30175 100644 --- a/runtime/src/vm/encoders/trailing_zeros.rs +++ b/runtime/src/vm/encoders/trailing_zeros.rs @@ -17,15 +17,24 @@ impl Encode for TrailingZeros { self } + fn op(&self) -> Option { + Some(VMOp::TrailingZeros) + } + fn encode(&self, mapper: &mut Mapper) -> Vec { - vec![mapper.index(VMOp::TrailingZeros), mapper.index(self.width)] + vec![mapper.index(self.width)] } fn writes(&self) -> Vec { vec![Effect::Register(VMReg::Flags)] } - fn depth(&self) -> i32 { - -1 + fn consumes(&self) -> i32 { + 1 } + + fn produces(&self) -> i32 { + 1 + } + } diff --git a/runtime/src/vm/encoders/vector_add.rs b/runtime/src/vm/encoders/vector_add.rs index 647493b0..1cdfbf3d 100644 --- a/runtime/src/vm/encoders/vector_add.rs +++ b/runtime/src/vm/encoders/vector_add.rs @@ -19,16 +19,24 @@ impl Encode for VectorAdd { self } + fn op(&self) -> Option { + Some(VMOp::VectorAdd) + } + fn encode(&self, mapper: &mut Mapper) -> Vec { vec![ - mapper.index(VMOp::VectorAdd), mapper.index(self.width), mapper.index(self.stride), mapper.index(self.precision), ] } - fn depth(&self) -> i32 { - -self.width.slots() + fn consumes(&self) -> i32 { + 2 } + + fn produces(&self) -> i32 { + 1 + } + } diff --git a/runtime/src/vm/encoders/vector_and.rs b/runtime/src/vm/encoders/vector_and.rs index f4061c25..552a238a 100644 --- a/runtime/src/vm/encoders/vector_and.rs +++ b/runtime/src/vm/encoders/vector_and.rs @@ -17,11 +17,20 @@ impl Encode for VectorAnd { self } + fn op(&self) -> Option { + Some(VMOp::VectorAnd) + } + fn encode(&self, mapper: &mut Mapper) -> Vec { - vec![mapper.index(VMOp::VectorAnd), mapper.index(self.width)] + vec![mapper.index(self.width)] } - fn depth(&self) -> i32 { - -self.width.slots() + fn consumes(&self) -> i32 { + 2 } + + fn produces(&self) -> i32 { + 1 + } + } diff --git a/runtime/src/vm/encoders/vector_and_not.rs b/runtime/src/vm/encoders/vector_and_not.rs index f1fac5ef..d87676c5 100644 --- a/runtime/src/vm/encoders/vector_and_not.rs +++ b/runtime/src/vm/encoders/vector_and_not.rs @@ -17,11 +17,20 @@ impl Encode for VectorAndNot { self } + fn op(&self) -> Option { + Some(VMOp::VectorAndNot) + } + fn encode(&self, mapper: &mut Mapper) -> Vec { - vec![mapper.index(VMOp::VectorAndNot), mapper.index(self.width)] + vec![mapper.index(self.width)] } - fn depth(&self) -> i32 { - -self.width.slots() + fn consumes(&self) -> i32 { + 2 } + + fn produces(&self) -> i32 { + 1 + } + } diff --git a/runtime/src/vm/encoders/vector_div.rs b/runtime/src/vm/encoders/vector_div.rs index aa6f82e4..75ec6a09 100644 --- a/runtime/src/vm/encoders/vector_div.rs +++ b/runtime/src/vm/encoders/vector_div.rs @@ -18,15 +18,20 @@ impl Encode for VectorDiv { self } + fn op(&self) -> Option { + Some(VMOp::VectorDiv) + } + fn encode(&self, mapper: &mut Mapper) -> Vec { - vec![ - mapper.index(VMOp::VectorDiv), - mapper.index(self.width), - mapper.index(self.stride), - ] + vec![mapper.index(self.width), mapper.index(self.stride)] } - fn depth(&self) -> i32 { - -self.width.slots() + fn consumes(&self) -> i32 { + 2 } + + fn produces(&self) -> i32 { + 1 + } + } diff --git a/runtime/src/vm/encoders/vector_mul.rs b/runtime/src/vm/encoders/vector_mul.rs index eec60369..fa9b563c 100644 --- a/runtime/src/vm/encoders/vector_mul.rs +++ b/runtime/src/vm/encoders/vector_mul.rs @@ -19,16 +19,24 @@ impl Encode for VectorMul { self } + fn op(&self) -> Option { + Some(VMOp::VectorMul) + } + fn encode(&self, mapper: &mut Mapper) -> Vec { vec![ - mapper.index(VMOp::VectorMul), mapper.index(self.width), mapper.index(self.stride), mapper.index(self.precision), ] } - fn depth(&self) -> i32 { - -self.width.slots() + fn consumes(&self) -> i32 { + 2 } + + fn produces(&self) -> i32 { + 1 + } + } diff --git a/runtime/src/vm/encoders/vector_or.rs b/runtime/src/vm/encoders/vector_or.rs index 855f01ff..d8a60241 100644 --- a/runtime/src/vm/encoders/vector_or.rs +++ b/runtime/src/vm/encoders/vector_or.rs @@ -17,11 +17,20 @@ impl Encode for VectorOr { self } + fn op(&self) -> Option { + Some(VMOp::VectorOr) + } + fn encode(&self, mapper: &mut Mapper) -> Vec { - vec![mapper.index(VMOp::VectorOr), mapper.index(self.width)] + vec![mapper.index(self.width)] } - fn depth(&self) -> i32 { - -self.width.slots() + fn consumes(&self) -> i32 { + 2 } + + fn produces(&self) -> i32 { + 1 + } + } diff --git a/runtime/src/vm/encoders/vector_sub.rs b/runtime/src/vm/encoders/vector_sub.rs index 5693dac1..62a00888 100644 --- a/runtime/src/vm/encoders/vector_sub.rs +++ b/runtime/src/vm/encoders/vector_sub.rs @@ -19,16 +19,24 @@ impl Encode for VectorSub { self } + fn op(&self) -> Option { + Some(VMOp::VectorSub) + } + fn encode(&self, mapper: &mut Mapper) -> Vec { vec![ - mapper.index(VMOp::VectorSub), mapper.index(self.width), mapper.index(self.stride), mapper.index(self.precision), ] } - fn depth(&self) -> i32 { - -self.width.slots() + fn consumes(&self) -> i32 { + 2 } + + fn produces(&self) -> i32 { + 1 + } + } diff --git a/runtime/src/vm/encoders/vector_xor.rs b/runtime/src/vm/encoders/vector_xor.rs index 63d02974..f6fb2d69 100644 --- a/runtime/src/vm/encoders/vector_xor.rs +++ b/runtime/src/vm/encoders/vector_xor.rs @@ -17,11 +17,20 @@ impl Encode for VectorXor { self } + fn op(&self) -> Option { + Some(VMOp::VectorXor) + } + fn encode(&self, mapper: &mut Mapper) -> Vec { - vec![mapper.index(VMOp::VectorXor), mapper.index(self.width)] + vec![mapper.index(self.width)] } - fn depth(&self) -> i32 { - -self.width.slots() + fn consumes(&self) -> i32 { + 2 } + + fn produces(&self) -> i32 { + 1 + } + } diff --git a/runtime/src/vm/encoders/xor.rs b/runtime/src/vm/encoders/xor.rs index 87f6a299..50657ca9 100644 --- a/runtime/src/vm/encoders/xor.rs +++ b/runtime/src/vm/encoders/xor.rs @@ -17,15 +17,24 @@ impl Encode for Xor { self } + fn op(&self) -> Option { + Some(VMOp::Xor) + } + fn encode(&self, mapper: &mut Mapper) -> Vec { - vec![mapper.index(VMOp::Xor), mapper.index(self.width)] + vec![mapper.index(self.width)] } fn writes(&self) -> Vec { vec![Effect::Register(VMReg::Flags)] } - fn depth(&self) -> i32 { - -1 + fn consumes(&self) -> i32 { + 2 } + + fn produces(&self) -> i32 { + 1 + } + } diff --git a/runtime/src/vm/functions/dispatch.rs b/runtime/src/vm/functions/dispatch.rs index 388f98c8..afe178ab 100644 --- a/runtime/src/vm/functions/dispatch.rs +++ b/runtime/src/vm/functions/dispatch.rs @@ -16,7 +16,7 @@ use crate::{ #[cfg(feature = "profile")] use crate::debug::{start_profiling, stop_profiling}; -const HANDLERS: [(VMOp, FnDef); VMOp::COUNT] = [ +pub const HANDLERS: [(VMOp, FnDef); VMOp::COUNT] = [ (VMOp::Jcc, FnDef::VmHandlerJcc), (VMOp::Ret, FnDef::VmHandlerRet), (VMOp::LoadImmediate, FnDef::VmHandlerLoadImmediate), @@ -64,6 +64,7 @@ const HANDLERS: [(VMOp, FnDef); VMOp::COUNT] = [ (VMOp::VectorMul, FnDef::VmHandlerVectorMul), (VMOp::VectorDiv, FnDef::VmHandlerVectorDiv), (VMOp::Timestamp, FnDef::VmHandlerTimestamp), + (VMOp::Dispatch, FnDef::VmHandlerDispatch), ]; pub fn build(rt: &mut Runtime) { diff --git a/runtime/src/vm/functions/invoke.rs b/runtime/src/vm/functions/invoke.rs index 027af008..091ca761 100644 --- a/runtime/src/vm/functions/invoke.rs +++ b/runtime/src/vm/functions/invoke.rs @@ -1,7 +1,7 @@ -use iced_x86::code_asm::{ptr, r12, r13, r14, r15, rax, rbp, rbx, rcx, rdi, rdx, rsi, rsp}; +use iced_x86::code_asm::{ptr, r12, r13, r13d, r14, r15, rax, rbp, rbx, rcx, rdi, rdx, rsi, rsp}; use crate::{ - runtime::{FnDef, Runtime}, + runtime::{DataDef, FnDef, Runtime}, vm::{ bytecode::VMReg, utils::{self, scratch}, @@ -80,8 +80,15 @@ pub fn build(rt: &mut Runtime) { rt.asm.set_label(&mut execute_loop).unwrap(); { + // mov r13d, [...] + rt.asm + .mov(r13d, ptr(rt.data_labels[&DataDef::VmRegistersTlsIndex])) + .unwrap(); + // mov r13, gs:[0x1480 + r13*8] + rt.asm.mov(r13, ptr(0x1480 + r13 * 8).gs()).unwrap(); + // store rsp - scratch::store(rt, r12, rsp); + scratch::store(rt, r13, rsp); // call ... rt.asm.call(rt.function_labels[&FnDef::VmDispatch]).unwrap(); @@ -91,16 +98,23 @@ pub fn build(rt: &mut Runtime) { rt.asm.set_label(&mut execute_continue).unwrap(); + // call ... + rt.asm + .call(rt.function_labels[&FnDef::VmRegistersCaptureVolatile]) + .unwrap(); + // mov [r12 + ...], rsp utils::vreg::store_reg(rt, r12, rsp, VMReg::Rsp); - // load rsp - scratch::load(rt, r12, rsp); - - // call ... + // mov r13d, [...] rt.asm - .call(rt.function_labels[&FnDef::VmRegistersCaptureVolatile]) + .mov(r13d, ptr(rt.data_labels[&DataDef::VmRegistersTlsIndex])) .unwrap(); + // mov r13, gs:[0x1480 + r13*8] + rt.asm.mov(r13, ptr(0x1480 + r13 * 8).gs()).unwrap(); + + // load rsp + scratch::load(rt, r13, rsp); // cmp [r12 + ...], 0x0 utils::vreg::cmp_imm(rt, r12, VMReg::BResume, 0x0); diff --git a/runtime/src/vm/handlers/add.rs b/runtime/src/vm/handlers/add.rs index 096f198f..a8c7842b 100644 --- a/runtime/src/vm/handlers/add.rs +++ b/runtime/src/vm/handlers/add.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Compare, Effect, Expression, FlagDef, Operand, Operation}, + handlers::semantic::{self, Compare, Condition, Effect, Expression, Operand, Operation}, }, }; @@ -11,42 +11,42 @@ pub fn build(rt: &mut Runtime) { rt, &Operation { effects: vec![Effect::Add( - Expression::Operand(Operand::A), - Expression::Operand(Operand::B), + Expression::Operand(Operand::InputA), + Expression::Operand(Operand::InputB), )], flags: vec![ ( Flag::Zero, - FlagDef::Compare(Compare::Equal( - Expression::Operand(Operand::Result), + Condition::Compare(Compare::Equal( + Expression::Operand(Operand::OutputA), Expression::Constant(0), )), ), ( Flag::Sign, - FlagDef::Compare(Compare::BitSet( - Expression::Operand(Operand::Result), + Condition::Compare(Compare::BitSet( + Expression::Operand(Operand::OutputA), Expression::SignBit, )), ), ( Flag::Carry, - FlagDef::Compare(Compare::LessThan( - Expression::Operand(Operand::Result), - Expression::Operand(Operand::A), + Condition::Compare(Compare::LessThan( + Expression::Operand(Operand::OutputA), + Expression::Operand(Operand::InputA), )), ), ( Flag::Overflow, - FlagDef::Compare(Compare::BitSet( + Condition::Compare(Compare::BitSet( Expression::BitAnd( Box::new(Expression::BitXor( - Box::new(Expression::Operand(Operand::A)), - Box::new(Expression::Operand(Operand::Result)), + Box::new(Expression::Operand(Operand::InputA)), + Box::new(Expression::Operand(Operand::OutputA)), )), Box::new(Expression::BitXor( - Box::new(Expression::Operand(Operand::B)), - Box::new(Expression::Operand(Operand::Result)), + Box::new(Expression::Operand(Operand::InputB)), + Box::new(Expression::Operand(Operand::OutputA)), )), ), Expression::SignBit, @@ -54,23 +54,23 @@ pub fn build(rt: &mut Runtime) { ), ( Flag::Parity, - FlagDef::Parity(Expression::Operand(Operand::Result)), + Condition::Parity(Expression::Operand(Operand::OutputA)), ), ( Flag::Auxiliary, - FlagDef::Compare(Compare::BitSet( + Condition::Compare(Compare::BitSet( Expression::BitXor( Box::new(Expression::BitXor( - Box::new(Expression::Operand(Operand::A)), - Box::new(Expression::Operand(Operand::B)), + Box::new(Expression::Operand(Operand::InputA)), + Box::new(Expression::Operand(Operand::InputB)), )), - Box::new(Expression::Operand(Operand::Result)), + Box::new(Expression::Operand(Operand::OutputA)), ), Expression::Constant(4), )), ), ], - store: None, + stores: None, widths: &[ VMWidth::Lower64, VMWidth::Lower32, diff --git a/runtime/src/vm/handlers/and.rs b/runtime/src/vm/handlers/and.rs index 11224047..e59c0d11 100644 --- a/runtime/src/vm/handlers/and.rs +++ b/runtime/src/vm/handlers/and.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Compare, Effect, Expression, FlagDef, Operand, Operation}, + handlers::semantic::{self, Compare, Effect, Expression, Condition, Operand, Operation}, }, }; @@ -11,44 +11,44 @@ pub fn build(rt: &mut Runtime) { rt, &Operation { effects: vec![Effect::And( - Expression::Operand(Operand::A), - Expression::Operand(Operand::B), + Expression::Operand(Operand::InputA), + Expression::Operand(Operand::InputB), )], flags: vec![ ( Flag::Carry, - FlagDef::Compare(Compare::Equal( + Condition::Compare(Compare::Equal( Expression::Constant(1), Expression::Constant(0), )), ), ( Flag::Overflow, - FlagDef::Compare(Compare::Equal( + Condition::Compare(Compare::Equal( Expression::Constant(1), Expression::Constant(0), )), ), ( Flag::Sign, - FlagDef::Compare(Compare::BitSet( - Expression::Operand(Operand::Result), + Condition::Compare(Compare::BitSet( + Expression::Operand(Operand::OutputA), Expression::SignBit, )), ), ( Flag::Zero, - FlagDef::Compare(Compare::Equal( - Expression::Operand(Operand::Result), + Condition::Compare(Compare::Equal( + Expression::Operand(Operand::OutputA), Expression::Constant(0), )), ), ( Flag::Parity, - FlagDef::Parity(Expression::Operand(Operand::Result)), + Condition::Parity(Expression::Operand(Operand::OutputA)), ), ], - store: None, + stores: None, widths: &[ VMWidth::Lower64, VMWidth::Lower32, diff --git a/runtime/src/vm/handlers/bit_scan_reverse.rs b/runtime/src/vm/handlers/bit_scan_reverse.rs index 42e8a173..c4a63324 100644 --- a/runtime/src/vm/handlers/bit_scan_reverse.rs +++ b/runtime/src/vm/handlers/bit_scan_reverse.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Compare, Effect, Expression, FlagDef, Operand, Operation}, + handlers::semantic::{self, Compare, Condition, Effect, Expression, Operand, Operation}, }, }; @@ -10,15 +10,15 @@ pub fn build(rt: &mut Runtime) { semantic::build( rt, &Operation { - effects: vec![Effect::Bsr(Expression::Operand(Operand::A))], + effects: vec![Effect::Bsr(Expression::Operand(Operand::InputA))], flags: vec![( Flag::Zero, - FlagDef::Compare(Compare::Equal( - Expression::Operand(Operand::A), + Condition::Compare(Compare::Equal( + Expression::Operand(Operand::InputA), Expression::Constant(0), )), )], - store: None, + stores: None, widths: &[VMWidth::Lower64, VMWidth::Lower32, VMWidth::Lower16], operands: 1, }, diff --git a/runtime/src/vm/handlers/bit_test.rs b/runtime/src/vm/handlers/bit_test.rs index 01c764b6..1e451cfe 100644 --- a/runtime/src/vm/handlers/bit_test.rs +++ b/runtime/src/vm/handlers/bit_test.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Compare, Expression, FlagDef, Operand, Operation}, + handlers::semantic::{self, Compare, Condition, Expression, Operand, Operation}, }, }; @@ -13,12 +13,12 @@ pub fn build(rt: &mut Runtime) { effects: vec![], flags: vec![( Flag::Carry, - FlagDef::Compare(Compare::BitSet( - Expression::Operand(Operand::A), - Expression::Operand(Operand::B), + Condition::Compare(Compare::BitSet( + Expression::Operand(Operand::InputA), + Expression::Operand(Operand::InputB), )), )], - store: Some(Expression::Operand(Operand::A)), + stores: Some(vec![Expression::Operand(Operand::InputA)]), widths: &[ VMWidth::Lower64, VMWidth::Lower32, diff --git a/runtime/src/vm/handlers/bit_test_complement.rs b/runtime/src/vm/handlers/bit_test_complement.rs index ee43ddc7..f42ca7fb 100644 --- a/runtime/src/vm/handlers/bit_test_complement.rs +++ b/runtime/src/vm/handlers/bit_test_complement.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Compare, Effect, Expression, FlagDef, Operand, Operation}, + handlers::semantic::{self, Compare, Condition, Effect, Expression, Operand, Operation}, }, }; @@ -11,20 +11,20 @@ pub fn build(rt: &mut Runtime) { rt, &Operation { effects: vec![Effect::Xor( - Expression::Operand(Operand::A), + Expression::Operand(Operand::InputA), Expression::BitShl( Box::new(Expression::Constant(1)), - Box::new(Expression::Operand(Operand::B)), + Box::new(Expression::Operand(Operand::InputB)), ), )], flags: vec![( Flag::Carry, - FlagDef::Compare(Compare::BitSet( - Expression::Operand(Operand::A), - Expression::Operand(Operand::B), + Condition::Compare(Compare::BitSet( + Expression::Operand(Operand::InputA), + Expression::Operand(Operand::InputB), )), )], - store: None, + stores: None, widths: &[ VMWidth::Lower64, VMWidth::Lower32, diff --git a/runtime/src/vm/handlers/bit_test_reset.rs b/runtime/src/vm/handlers/bit_test_reset.rs index d3af5c2d..7a7f4efb 100644 --- a/runtime/src/vm/handlers/bit_test_reset.rs +++ b/runtime/src/vm/handlers/bit_test_reset.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Compare, Effect, Expression, FlagDef, Operand, Operation}, + handlers::semantic::{self, Compare, Condition, Effect, Expression, Operand, Operation}, }, }; @@ -11,20 +11,20 @@ pub fn build(rt: &mut Runtime) { rt, &Operation { effects: vec![Effect::And( - Expression::Operand(Operand::A), + Expression::Operand(Operand::InputA), Expression::BitNot(Box::new(Expression::BitShl( Box::new(Expression::Constant(1)), - Box::new(Expression::Operand(Operand::B)), + Box::new(Expression::Operand(Operand::InputB)), ))), )], flags: vec![( Flag::Carry, - FlagDef::Compare(Compare::BitSet( - Expression::Operand(Operand::A), - Expression::Operand(Operand::B), + Condition::Compare(Compare::BitSet( + Expression::Operand(Operand::InputA), + Expression::Operand(Operand::InputB), )), )], - store: None, + stores: None, widths: &[ VMWidth::Lower64, VMWidth::Lower32, diff --git a/runtime/src/vm/handlers/bit_test_set.rs b/runtime/src/vm/handlers/bit_test_set.rs index 92550bbb..6aa74280 100644 --- a/runtime/src/vm/handlers/bit_test_set.rs +++ b/runtime/src/vm/handlers/bit_test_set.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Compare, Effect, Expression, FlagDef, Operand, Operation}, + handlers::semantic::{self, Compare, Condition, Effect, Expression, Operand, Operation}, }, }; @@ -11,20 +11,20 @@ pub fn build(rt: &mut Runtime) { rt, &Operation { effects: vec![Effect::Or( - Expression::Operand(Operand::A), + Expression::Operand(Operand::InputA), Expression::BitShl( Box::new(Expression::Constant(1)), - Box::new(Expression::Operand(Operand::B)), + Box::new(Expression::Operand(Operand::InputB)), ), )], flags: vec![( Flag::Carry, - FlagDef::Compare(Compare::BitSet( - Expression::Operand(Operand::A), - Expression::Operand(Operand::B), + Condition::Compare(Compare::BitSet( + Expression::Operand(Operand::InputA), + Expression::Operand(Operand::InputB), )), )], - store: None, + stores: None, widths: &[ VMWidth::Lower64, VMWidth::Lower32, diff --git a/runtime/src/vm/handlers/byte_swap.rs b/runtime/src/vm/handlers/byte_swap.rs index e9e6f471..fe1c4c45 100644 --- a/runtime/src/vm/handlers/byte_swap.rs +++ b/runtime/src/vm/handlers/byte_swap.rs @@ -13,76 +13,76 @@ pub fn build(rt: &mut Runtime) { effects: vec![ Effect::Assign(Expression::BitAnd( Box::new(Expression::BitShr( - Box::new(Expression::Operand(Operand::A)), + Box::new(Expression::Operand(Operand::InputA)), Box::new(Expression::Constant(56)), )), Box::new(Expression::Constant(0xFF)), )), Effect::Or( - Expression::Operand(Operand::Result), + Expression::Operand(Operand::OutputA), Expression::BitAnd( Box::new(Expression::BitShr( - Box::new(Expression::Operand(Operand::A)), + Box::new(Expression::Operand(Operand::InputA)), Box::new(Expression::Constant(40)), )), Box::new(Expression::Constant(0xFF00)), ), ), Effect::Or( - Expression::Operand(Operand::Result), + Expression::Operand(Operand::OutputA), Expression::BitAnd( Box::new(Expression::BitShr( - Box::new(Expression::Operand(Operand::A)), + Box::new(Expression::Operand(Operand::InputA)), Box::new(Expression::Constant(24)), )), Box::new(Expression::Constant(0xFF_0000)), ), ), Effect::Or( - Expression::Operand(Operand::Result), + Expression::Operand(Operand::OutputA), Expression::BitAnd( Box::new(Expression::BitShr( - Box::new(Expression::Operand(Operand::A)), + Box::new(Expression::Operand(Operand::InputA)), Box::new(Expression::Constant(8)), )), Box::new(Expression::Constant(0xFF_000000)), ), ), Effect::Or( - Expression::Operand(Operand::Result), + Expression::Operand(Operand::OutputA), Expression::BitAnd( Box::new(Expression::BitShl( - Box::new(Expression::Operand(Operand::A)), + Box::new(Expression::Operand(Operand::InputA)), Box::new(Expression::Constant(8)), )), Box::new(Expression::Constant(0xFF_00000000)), ), ), Effect::Or( - Expression::Operand(Operand::Result), + Expression::Operand(Operand::OutputA), Expression::BitAnd( Box::new(Expression::BitShl( - Box::new(Expression::Operand(Operand::A)), + Box::new(Expression::Operand(Operand::InputA)), Box::new(Expression::Constant(24)), )), Box::new(Expression::Constant(0xFF_0000000000)), ), ), Effect::Or( - Expression::Operand(Operand::Result), + Expression::Operand(Operand::OutputA), Expression::BitAnd( Box::new(Expression::BitShl( - Box::new(Expression::Operand(Operand::A)), + Box::new(Expression::Operand(Operand::InputA)), Box::new(Expression::Constant(40)), )), Box::new(Expression::Constant(0xFF_000000000000)), ), ), Effect::Or( - Expression::Operand(Operand::Result), + Expression::Operand(Operand::OutputA), Expression::BitAnd( Box::new(Expression::BitShl( - Box::new(Expression::Operand(Operand::A)), + Box::new(Expression::Operand(Operand::InputA)), Box::new(Expression::Constant(56)), )), Box::new(Expression::Constant(0xFF_00000000000000)), @@ -90,7 +90,7 @@ pub fn build(rt: &mut Runtime) { ), ], flags: vec![], - store: None, + stores: None, widths: &[VMWidth::Lower64, VMWidth::Lower32], operands: 1, }, diff --git a/runtime/src/vm/handlers/dispatch.rs b/runtime/src/vm/handlers/dispatch.rs new file mode 100644 index 00000000..2d8738ca --- /dev/null +++ b/runtime/src/vm/handlers/dispatch.rs @@ -0,0 +1,21 @@ +use iced_x86::code_asm::{r12, r8, CodeLabel}; + +use crate::{ + runtime::Runtime, + vm::{functions::dispatch::HANDLERS, utils::scratch}, +}; + +pub fn build(rt: &mut Runtime) { + // load r8 + scratch::load(rt, r12, r8); + + let cases = HANDLERS + .iter() + .map(|&(op, def)| (rt.mapper.index(op), rt.function_labels[&def])) + .collect::>(); + + rt.calls(r8, cases); + + // ret + rt.asm.ret().unwrap(); +} diff --git a/runtime/src/vm/handlers/mod.rs b/runtime/src/vm/handlers/mod.rs index 2141e2e0..b9328f60 100644 --- a/runtime/src/vm/handlers/mod.rs +++ b/runtime/src/vm/handlers/mod.rs @@ -8,6 +8,7 @@ pub mod bit_test_set; pub mod byte_swap; pub mod compare_exchange; pub mod discard; +pub mod dispatch; pub mod div; pub mod exchange; pub mod exchange_add; diff --git a/runtime/src/vm/handlers/or.rs b/runtime/src/vm/handlers/or.rs index eb584b91..d78ca5a7 100644 --- a/runtime/src/vm/handlers/or.rs +++ b/runtime/src/vm/handlers/or.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Compare, Effect, Expression, FlagDef, Operand, Operation}, + handlers::semantic::{self, Compare, Effect, Expression, Condition, Operand, Operation}, }, }; @@ -11,44 +11,44 @@ pub fn build(rt: &mut Runtime) { rt, &Operation { effects: vec![Effect::Or( - Expression::Operand(Operand::A), - Expression::Operand(Operand::B), + Expression::Operand(Operand::InputA), + Expression::Operand(Operand::InputB), )], flags: vec![ ( Flag::Carry, - FlagDef::Compare(Compare::Equal( + Condition::Compare(Compare::Equal( Expression::Constant(1), Expression::Constant(0), )), ), ( Flag::Overflow, - FlagDef::Compare(Compare::Equal( + Condition::Compare(Compare::Equal( Expression::Constant(1), Expression::Constant(0), )), ), ( Flag::Sign, - FlagDef::Compare(Compare::BitSet( - Expression::Operand(Operand::Result), + Condition::Compare(Compare::BitSet( + Expression::Operand(Operand::OutputA), Expression::SignBit, )), ), ( Flag::Zero, - FlagDef::Compare(Compare::Equal( - Expression::Operand(Operand::Result), + Condition::Compare(Compare::Equal( + Expression::Operand(Operand::OutputA), Expression::Constant(0), )), ), ( Flag::Parity, - FlagDef::Parity(Expression::Operand(Operand::Result)), + Condition::Parity(Expression::Operand(Operand::OutputA)), ), ], - store: None, + stores: None, widths: &[ VMWidth::Lower64, VMWidth::Lower32, diff --git a/runtime/src/vm/handlers/rol.rs b/runtime/src/vm/handlers/rol.rs index f82527c3..09a53d5a 100644 --- a/runtime/src/vm/handlers/rol.rs +++ b/runtime/src/vm/handlers/rol.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Compare, Effect, Expression, FlagDef, Operand, Operation}, + handlers::semantic::{self, Compare, Condition, Effect, Expression, Operand, Operation}, }, }; @@ -11,26 +11,26 @@ pub fn build(rt: &mut Runtime) { rt, &Operation { effects: vec![Effect::Rol( - Expression::Operand(Operand::A), - Expression::Operand(Operand::B), + Expression::Operand(Operand::InputA), + Expression::Operand(Operand::InputB), )], flags: vec![ ( Flag::Carry, - FlagDef::Compare(Compare::BitSet( - Expression::Operand(Operand::Result), + Condition::Compare(Compare::BitSet( + Expression::Operand(Operand::OutputA), Expression::Constant(0), )), ), ( Flag::Overflow, - FlagDef::Compare(Compare::Equal( + Condition::Compare(Compare::Equal( Expression::Constant(1), Expression::Constant(0), )), ), ], - store: None, + stores: None, widths: &[ VMWidth::Lower64, VMWidth::Lower32, diff --git a/runtime/src/vm/handlers/ror.rs b/runtime/src/vm/handlers/ror.rs index cec63355..febe5b93 100644 --- a/runtime/src/vm/handlers/ror.rs +++ b/runtime/src/vm/handlers/ror.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Compare, Effect, Expression, FlagDef, Operand, Operation}, + handlers::semantic::{self, Compare, Condition, Effect, Expression, Operand, Operation}, }, }; @@ -11,26 +11,26 @@ pub fn build(rt: &mut Runtime) { rt, &Operation { effects: vec![Effect::Ror( - Expression::Operand(Operand::A), - Expression::Operand(Operand::B), + Expression::Operand(Operand::InputA), + Expression::Operand(Operand::InputB), )], flags: vec![ ( Flag::Carry, - FlagDef::Compare(Compare::BitSet( - Expression::Operand(Operand::Result), - Expression::SignBit + Condition::Compare(Compare::BitSet( + Expression::Operand(Operand::OutputA), + Expression::SignBit, )), ), ( Flag::Overflow, - FlagDef::Compare(Compare::Equal( + Condition::Compare(Compare::Equal( Expression::Constant(1), Expression::Constant(0), )), ), ], - store: None, + stores: None, widths: &[ VMWidth::Lower64, VMWidth::Lower32, diff --git a/runtime/src/vm/handlers/sar.rs b/runtime/src/vm/handlers/sar.rs index 37a56a2c..af588e6d 100644 --- a/runtime/src/vm/handlers/sar.rs +++ b/runtime/src/vm/handlers/sar.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Compare, Effect, Expression, FlagDef, Operand, Operation}, + handlers::semantic::{self, Compare, Condition, Effect, Expression, Operand, Operation}, }, }; @@ -11,47 +11,47 @@ pub fn build(rt: &mut Runtime) { rt, &Operation { effects: vec![Effect::Sar( - Expression::Operand(Operand::A), - Expression::Operand(Operand::B), + Expression::Operand(Operand::InputA), + Expression::Operand(Operand::InputB), )], flags: vec![ ( Flag::Carry, - FlagDef::Compare(Compare::BitSet( - Expression::Operand(Operand::A), + Condition::Compare(Compare::BitSet( + Expression::Operand(Operand::InputA), Expression::Sub( - Box::new(Expression::Operand(Operand::B)), + Box::new(Expression::Operand(Operand::InputB)), Box::new(Expression::Constant(1)), ), )), ), ( Flag::Overflow, - FlagDef::Compare(Compare::Equal( + Condition::Compare(Compare::Equal( Expression::Constant(1), Expression::Constant(0), )), ), ( Flag::Sign, - FlagDef::Compare(Compare::BitSet( - Expression::Operand(Operand::Result), - Expression::SignBit + Condition::Compare(Compare::BitSet( + Expression::Operand(Operand::OutputA), + Expression::SignBit, )), ), ( Flag::Zero, - FlagDef::Compare(Compare::Equal( - Expression::Operand(Operand::Result), + Condition::Compare(Compare::Equal( + Expression::Operand(Operand::OutputA), Expression::Constant(0), )), ), ( Flag::Parity, - FlagDef::Parity(Expression::Operand(Operand::Result)), + Condition::Parity(Expression::Operand(Operand::OutputA)), ), ], - store: None, + stores: None, widths: &[ VMWidth::Lower64, VMWidth::Lower32, diff --git a/runtime/src/vm/handlers/semantic.rs b/runtime/src/vm/handlers/semantic.rs index 912b4ced..0d21f189 100644 --- a/runtime/src/vm/handlers/semantic.rs +++ b/runtime/src/vm/handlers/semantic.rs @@ -13,14 +13,14 @@ use crate::{ }, }; -#[derive(Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Operand { - A, - B, - Result, + InputA, + InputB, + OutputA, } -#[derive(Clone)] +#[derive(Debug, Clone)] pub enum Expression { Operand(Operand), Constant(u64), @@ -35,7 +35,7 @@ pub enum Expression { SignBit, } -#[derive(Clone)] +#[derive(Debug, Clone)] pub enum Effect { Add(Expression, Expression), Sub(Expression, Expression), @@ -53,7 +53,7 @@ pub enum Effect { Tzcnt(Expression), } -#[derive(Clone)] +#[derive(Debug, Clone)] pub enum Compare { Equal(Expression, Expression), LessThan(Expression, Expression), @@ -61,16 +61,17 @@ pub enum Compare { BitSet(Expression, Expression), } -#[derive(Clone)] -pub enum FlagDef { +#[derive(Debug, Clone)] +pub enum Condition { Compare(Compare), Parity(Expression), } +#[derive(Debug)] pub struct Operation { pub effects: Vec, - pub flags: Vec<(Flag, FlagDef)>, - pub store: Option, + pub flags: Vec<(Flag, Condition)>, + pub stores: Option>, pub widths: &'static [VMWidth], pub operands: u8, } @@ -112,9 +113,9 @@ impl Allocator { #[derive(Clone)] struct Context { - first: AsmRegister64, - second: AsmRegister64, - result: AsmRegister64, + input_a: AsmRegister64, + input_b: AsmRegister64, + output_a: AsmRegister64, } // TODO: Implement a mixed-boolean-arithmetic engine! @@ -128,36 +129,34 @@ pub fn build(rt: &mut Runtime, operation: &Operation) { rt.asm.mov(r13, rcx).unwrap(); let mut allocator = Allocator::new(); - let index = allocator.alloc(); - bytecode::read_byte_zx( - rt, - r13, - get_gpr32(register::sized(index.into(), 4).unwrap()).unwrap(), - ); + let index = allocator.alloc(); + let index32 = get_gpr32(register::sized(index.into(), 4).unwrap()).unwrap(); + bytecode::read_byte_zx(rt, r13, index32); allocator.available.retain(|&r| r != rcx); - let first = allocator.alloc(); - let second = allocator.alloc(); - let result = allocator.alloc(); + let input_a = allocator.alloc(); + let input_b = allocator.alloc(); + + let output_a = allocator.alloc(); allocator.available.push(rcx); - allocator.protect(first); - allocator.protect(second); - allocator.protect(result); + allocator.protect(input_a); + allocator.protect(input_b); + allocator.protect(output_a); let context = Context { - first, - second, - result, + input_a, + input_b, + output_a, }; if operation.operands > 1 { - scratch::load(rt, r12, second); + scratch::load(rt, r12, input_b); } - scratch::load(rt, r12, first); + scratch::load(rt, r12, input_a); let handlers = operation .widths @@ -172,9 +171,9 @@ pub fn build(rt: &mut Runtime, operation: &Operation) { effects(rt, allocator, &context, &actions, width); let temporary = allocator.alloc(); rt.asm.mov(temporary, width.mask() as i64).unwrap(); - rt.asm.and(context.result, temporary).unwrap(); + rt.asm.and(context.output_a, temporary).unwrap(); allocator.free(temporary); - flags(rt, allocator, &context, &rules, width); + compile_flags(rt, allocator, &context, &rules, width); }) as Box, ) }) @@ -208,14 +207,31 @@ pub fn build(rt: &mut Runtime, operation: &Operation) { rt.asm.set_label(&mut epilogue).unwrap(); - match &operation.store { - Some(target) => { - let register = expression(rt, &mut allocator, &context, target, VMWidth::Lower64); - scratch::store(rt, r12, register); - allocator.free(register); + match &operation.stores { + Some(stores) => { + let evaluated = stores + .iter() + .map(|expression| { + let source = compile_expression( + rt, + &mut allocator, + &context, + expression, + VMWidth::Lower64, + ); + let temporary = allocator.alloc(); + rt.asm.mov(temporary, source).unwrap(); + allocator.free(source); + temporary + }) + .collect::>(); + for temporary in evaluated { + scratch::store(rt, r12, temporary); + allocator.free(temporary); + } } None => { - scratch::store(rt, r12, context.result); + scratch::store(rt, r12, context.output_a); } } @@ -242,17 +258,17 @@ fn effect( rt: &mut Runtime, allocator: &mut Allocator, context: &Context, - item: &Effect, + expression: &Effect, width: VMWidth, ) { macro_rules! binary { ($rt:expr, $allocator:expr, $context:expr, $first:expr, $second:expr, $operation:ident, $width:expr) => {{ - let first = expression($rt, $allocator, $context, $first, $width); - let second = expression($rt, $allocator, $context, $second, $width); - if first != $context.result { - $rt.asm.mov($context.result, first).unwrap(); + let first = compile_expression($rt, $allocator, $context, $first, $width); + let second = compile_expression($rt, $allocator, $context, $second, $width); + if first != $context.output_a { + $rt.asm.mov($context.output_a, first).unwrap(); } - $rt.asm.$operation($context.result, second).unwrap(); + $rt.asm.$operation($context.output_a, second).unwrap(); $allocator.free(first); $allocator.free(second); }}; @@ -260,7 +276,7 @@ fn effect( macro_rules! shift { ($rt:expr, $allocator:expr, $context:expr, $first:expr, $second:expr, $operation:ident, $width:expr) => {{ - let second = expression($rt, $allocator, $context, $second, $width); + let second = compile_expression($rt, $allocator, $context, $second, $width); let busy = !$allocator.available.contains(&rcx) && second != rcx && !$allocator.protected(rcx); if busy { @@ -275,11 +291,11 @@ fn effect( $allocator.available.retain(|®ister| register != rcx); } - let first = expression($rt, $allocator, $context, $first, $width); - if first != $context.result { - $rt.asm.mov($context.result, first).unwrap(); + let first = compile_expression($rt, $allocator, $context, $first, $width); + if first != $context.output_a { + $rt.asm.mov($context.output_a, first).unwrap(); } - $rt.asm.$operation($context.result, cl).unwrap(); + $rt.asm.$operation($context.output_a, cl).unwrap(); $allocator.free(first); if busy { @@ -290,7 +306,7 @@ fn effect( }}; } - match item { + match expression { Effect::Add(first, second) => binary!(rt, allocator, context, first, second, add, width), Effect::Sub(first, second) => binary!(rt, allocator, context, first, second, sub, width), Effect::And(first, second) => binary!(rt, allocator, context, first, second, and, width), @@ -303,14 +319,14 @@ fn effect( Effect::Rol(first, second) => shift!(rt, allocator, context, first, second, rol, width), Effect::Sar(first, second) => shift!(rt, allocator, context, first, second, sar, width), Effect::Assign(expr) => { - let value = expression(rt, allocator, context, expr, width); - if value != context.result { - rt.asm.mov(context.result, value).unwrap(); + let value = compile_expression(rt, allocator, context, expr, width); + if value != context.output_a { + rt.asm.mov(context.output_a, value).unwrap(); allocator.free(value); } } Effect::Bsr(expr) => { - let mut src = expression(rt, allocator, context, expr, width); + let mut src = compile_expression(rt, allocator, context, expr, width); if allocator.protected(src) { let temporary = allocator.alloc(); rt.asm.mov(temporary, src).unwrap(); @@ -320,11 +336,11 @@ fn effect( rt.asm.mov(mask, width.mask() as i64).unwrap(); rt.asm.and(src, mask).unwrap(); allocator.free(mask); - rt.asm.bsr(context.result, src).unwrap(); + rt.asm.bsr(context.output_a, src).unwrap(); allocator.free(src); } Effect::Tzcnt(expr) => { - let mut src = expression(rt, allocator, context, expr, width); + let mut src = compile_expression(rt, allocator, context, expr, width); if allocator.protected(src) { let temporary = allocator.alloc(); rt.asm.mov(temporary, src).unwrap(); @@ -334,23 +350,23 @@ fn effect( rt.asm.mov(mask, width.mask() as i64).unwrap(); rt.asm.and(src, mask).unwrap(); allocator.free(mask); - rt.asm.tzcnt(context.result, src).unwrap(); + rt.asm.tzcnt(context.output_a, src).unwrap(); allocator.free(src); } } } -fn expression( +fn compile_expression( rt: &mut Runtime, allocator: &mut Allocator, context: &Context, - node: &Expression, + expression: &Expression, width: VMWidth, ) -> AsmRegister64 { - match node { - Expression::Operand(Operand::A) => context.first, - Expression::Operand(Operand::B) => context.second, - Expression::Operand(Operand::Result) => context.result, + match expression { + Expression::Operand(Operand::InputA) => context.input_a, + Expression::Operand(Operand::InputB) => context.input_b, + Expression::Operand(Operand::OutputA) => context.output_a, Expression::Constant(value) => { let temporary = allocator.alloc(); rt.asm.mov(temporary, *value as i64).unwrap(); @@ -362,19 +378,19 @@ fn expression( temporary } Expression::Sub(first, second) => { - let mut register = expression(rt, allocator, context, first, width); + let mut register = compile_expression(rt, allocator, context, first, width); if allocator.protected(register) { let temporary = allocator.alloc(); rt.asm.mov(temporary, register).unwrap(); register = temporary; } - let other = expression(rt, allocator, context, second, width); + let other = compile_expression(rt, allocator, context, second, width); rt.asm.sub(register, other).unwrap(); allocator.free(other); register } Expression::LowByte(inner) => { - let mut register = expression(rt, allocator, context, inner, width); + let mut register = compile_expression(rt, allocator, context, inner, width); if allocator.protected(register) { let temporary = allocator.alloc(); rt.asm.mov(temporary, register).unwrap(); @@ -385,7 +401,7 @@ fn expression( register } Expression::BitNot(inner) => { - let mut register = expression(rt, allocator, context, inner, width); + let mut register = compile_expression(rt, allocator, context, inner, width); if allocator.protected(register) { let temporary = allocator.alloc(); rt.asm.mov(temporary, register).unwrap(); @@ -395,43 +411,43 @@ fn expression( register } Expression::BitAnd(first, second) => { - let mut register = expression(rt, allocator, context, first, width); + let mut register = compile_expression(rt, allocator, context, first, width); if allocator.protected(register) { let temporary = allocator.alloc(); rt.asm.mov(temporary, register).unwrap(); register = temporary; } - let other = expression(rt, allocator, context, second, width); + let other = compile_expression(rt, allocator, context, second, width); rt.asm.and(register, other).unwrap(); allocator.free(other); register } Expression::BitOr(first, second) => { - let mut register = expression(rt, allocator, context, first, width); + let mut register = compile_expression(rt, allocator, context, first, width); if allocator.protected(register) { let temporary = allocator.alloc(); rt.asm.mov(temporary, register).unwrap(); register = temporary; } - let other = expression(rt, allocator, context, second, width); + let other = compile_expression(rt, allocator, context, second, width); rt.asm.or(register, other).unwrap(); allocator.free(other); register } Expression::BitXor(first, second) => { - let mut register = expression(rt, allocator, context, first, width); + let mut register = compile_expression(rt, allocator, context, first, width); if allocator.protected(register) { let temporary = allocator.alloc(); rt.asm.mov(temporary, register).unwrap(); register = temporary; } - let other = expression(rt, allocator, context, second, width); + let other = compile_expression(rt, allocator, context, second, width); rt.asm.xor(register, other).unwrap(); allocator.free(other); register } Expression::BitShr(first, second) => { - let other = expression(rt, allocator, context, second, width); + let other = compile_expression(rt, allocator, context, second, width); let busy = !allocator.available.contains(&rcx) && other != rcx && !allocator.protected(rcx); @@ -449,7 +465,7 @@ fn expression( allocator.available.retain(|®ister| register != rcx); } - let mut register = expression(rt, allocator, context, first, width); + let mut register = compile_expression(rt, allocator, context, first, width); if allocator.protected(register) { let temporary = allocator.alloc(); rt.asm.mov(temporary, register).unwrap(); @@ -466,7 +482,7 @@ fn expression( register } Expression::BitShl(first, second) => { - let other = expression(rt, allocator, context, second, width); + let other = compile_expression(rt, allocator, context, second, width); let busy = !allocator.available.contains(&rcx) && other != rcx && !allocator.protected(rcx); @@ -486,7 +502,7 @@ fn expression( allocator.available.retain(|®ister| register != rcx); } - let mut register = expression(rt, allocator, context, first, width); + let mut register = compile_expression(rt, allocator, context, first, width); if allocator.protected(register) { let temporary = allocator.alloc(); @@ -506,11 +522,11 @@ fn expression( } } -fn flags( +fn compile_flags( rt: &mut Runtime, allocator: &mut Allocator, context: &Context, - list: &[(Flag, FlagDef)], + rules: &[(Flag, Condition)], width: VMWidth, ) { let temporary = allocator.alloc(); @@ -518,10 +534,12 @@ fn flags( rt.asm.push(temporary).unwrap(); allocator.free(temporary); - for (flag, definition) in list { + for (flag, definition) in rules { match definition { - FlagDef::Compare(item) => compare(rt, allocator, context, *flag, item, width), - FlagDef::Parity(node) => parity(rt, allocator, context, *flag, node, width), + Condition::Compare(item) => { + compile_comparison(rt, allocator, context, *flag, item, width) + } + Condition::Parity(node) => compile_parity(rt, allocator, context, *flag, node, width), } } @@ -531,7 +549,7 @@ fn flags( allocator.free(register); } -fn compare( +fn compile_comparison( rt: &mut Runtime, allocator: &mut Allocator, context: &Context, @@ -541,8 +559,8 @@ fn compare( ) { let temporary = match compare { Compare::Equal(first, second) => { - let lhs = expression(rt, allocator, context, first, width); - let rhs = expression(rt, allocator, context, second, width); + let lhs = compile_expression(rt, allocator, context, first, width); + let rhs = compile_expression(rt, allocator, context, second, width); rt.asm.cmp(lhs, rhs).unwrap(); allocator.free(lhs); allocator.free(rhs); @@ -553,8 +571,8 @@ fn compare( result } Compare::LessThan(first, second) => { - let lhs = expression(rt, allocator, context, first, width); - let rhs = expression(rt, allocator, context, second, width); + let lhs = compile_expression(rt, allocator, context, first, width); + let rhs = compile_expression(rt, allocator, context, second, width); rt.asm.cmp(lhs, rhs).unwrap(); allocator.free(lhs); allocator.free(rhs); @@ -565,8 +583,8 @@ fn compare( result } Compare::GreaterThan(first, second) => { - let lhs = expression(rt, allocator, context, first, width); - let rhs = expression(rt, allocator, context, second, width); + let lhs = compile_expression(rt, allocator, context, first, width); + let rhs = compile_expression(rt, allocator, context, second, width); rt.asm.cmp(lhs, rhs).unwrap(); allocator.free(lhs); allocator.free(rhs); @@ -577,8 +595,8 @@ fn compare( result } Compare::BitSet(first, second) => { - let lhs = expression(rt, allocator, context, first, width); - let rhs = expression(rt, allocator, context, second, width); + let lhs = compile_expression(rt, allocator, context, first, width); + let rhs = compile_expression(rt, allocator, context, second, width); rt.asm.bt(lhs, rhs).unwrap(); allocator.free(lhs); allocator.free(rhs); @@ -597,7 +615,7 @@ fn compare( allocator.free(temporary); } -fn parity( +fn compile_parity( rt: &mut Runtime, allocator: &mut Allocator, context: &Context, @@ -605,7 +623,7 @@ fn parity( node: &Expression, width: VMWidth, ) { - let mut temporary = expression(rt, allocator, context, node, width); + let mut temporary = compile_expression(rt, allocator, context, node, width); if allocator.protected(temporary) { let copy = allocator.alloc(); @@ -617,7 +635,7 @@ fn parity( rt.asm.movzx(temporary, byte).unwrap(); rt.asm.popcnt(temporary, temporary).unwrap(); rt.asm.not(temporary).unwrap(); - rt.asm.and(temporary, 1i32).unwrap(); + rt.asm.and(temporary, 1).unwrap(); rt.asm .shl(temporary, flag.bit32().trailing_zeros() as i32) .unwrap(); diff --git a/runtime/src/vm/handlers/shl.rs b/runtime/src/vm/handlers/shl.rs index fc54eace..ded1eefc 100644 --- a/runtime/src/vm/handlers/shl.rs +++ b/runtime/src/vm/handlers/shl.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Compare, Effect, Expression, FlagDef, Operand, Operation}, + handlers::semantic::{self, Compare, Effect, Expression, Condition, Operand, Operation}, }, }; @@ -11,47 +11,47 @@ pub fn build(rt: &mut Runtime) { rt, &Operation { effects: vec![Effect::Shl( - Expression::Operand(Operand::A), - Expression::Operand(Operand::B), + Expression::Operand(Operand::InputA), + Expression::Operand(Operand::InputB), )], flags: vec![ ( Flag::Carry, - FlagDef::Compare(Compare::BitSet( - Expression::Operand(Operand::A), + Condition::Compare(Compare::BitSet( + Expression::Operand(Operand::InputA), Expression::Sub( Box::new(Expression::Constant(64)), - Box::new(Expression::Operand(Operand::B)), + Box::new(Expression::Operand(Operand::InputB)), ), )), ), ( Flag::Overflow, - FlagDef::Compare(Compare::Equal( + Condition::Compare(Compare::Equal( Expression::Constant(1), Expression::Constant(0), )), ), ( Flag::Sign, - FlagDef::Compare(Compare::BitSet( - Expression::Operand(Operand::Result), + Condition::Compare(Compare::BitSet( + Expression::Operand(Operand::OutputA), Expression::SignBit )), ), ( Flag::Zero, - FlagDef::Compare(Compare::Equal( - Expression::Operand(Operand::Result), + Condition::Compare(Compare::Equal( + Expression::Operand(Operand::OutputA), Expression::Constant(0), )), ), ( Flag::Parity, - FlagDef::Parity(Expression::Operand(Operand::Result)), + Condition::Parity(Expression::Operand(Operand::OutputA)), ), ], - store: None, + stores: None, widths: &[ VMWidth::Lower64, VMWidth::Lower32, diff --git a/runtime/src/vm/handlers/shr.rs b/runtime/src/vm/handlers/shr.rs index e781339f..c8341021 100644 --- a/runtime/src/vm/handlers/shr.rs +++ b/runtime/src/vm/handlers/shr.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Compare, Effect, Expression, FlagDef, Operand, Operation}, + handlers::semantic::{self, Compare, Condition, Effect, Expression, Operand, Operation}, }, }; @@ -11,47 +11,47 @@ pub fn build(rt: &mut Runtime) { rt, &Operation { effects: vec![Effect::Shr( - Expression::Operand(Operand::A), - Expression::Operand(Operand::B), + Expression::Operand(Operand::InputA), + Expression::Operand(Operand::InputB), )], flags: vec![ ( Flag::Carry, - FlagDef::Compare(Compare::BitSet( - Expression::Operand(Operand::A), + Condition::Compare(Compare::BitSet( + Expression::Operand(Operand::InputA), Expression::Sub( - Box::new(Expression::Operand(Operand::B)), + Box::new(Expression::Operand(Operand::InputB)), Box::new(Expression::Constant(1)), ), )), ), ( Flag::Overflow, - FlagDef::Compare(Compare::Equal( + Condition::Compare(Compare::Equal( Expression::Constant(1), Expression::Constant(0), )), ), ( Flag::Sign, - FlagDef::Compare(Compare::BitSet( - Expression::Operand(Operand::Result), - Expression::SignBit + Condition::Compare(Compare::BitSet( + Expression::Operand(Operand::OutputA), + Expression::SignBit, )), ), ( Flag::Zero, - FlagDef::Compare(Compare::Equal( - Expression::Operand(Operand::Result), + Condition::Compare(Compare::Equal( + Expression::Operand(Operand::OutputA), Expression::Constant(0), )), ), ( Flag::Parity, - FlagDef::Parity(Expression::Operand(Operand::Result)), + Condition::Parity(Expression::Operand(Operand::OutputA)), ), ], - store: None, + stores: None, widths: &[ VMWidth::Lower64, VMWidth::Lower32, diff --git a/runtime/src/vm/handlers/sub.rs b/runtime/src/vm/handlers/sub.rs index 7d2463c6..417f4dc3 100644 --- a/runtime/src/vm/handlers/sub.rs +++ b/runtime/src/vm/handlers/sub.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Compare, Effect, Expression, FlagDef, Operand, Operation}, + handlers::semantic::{self, Compare, Effect, Expression, Condition, Operand, Operation}, }, }; @@ -11,42 +11,42 @@ pub fn build(rt: &mut Runtime) { rt, &Operation { effects: vec![Effect::Sub( - Expression::Operand(Operand::A), - Expression::Operand(Operand::B), + Expression::Operand(Operand::InputA), + Expression::Operand(Operand::InputB), )], flags: vec![ ( Flag::Zero, - FlagDef::Compare(Compare::Equal( - Expression::Operand(Operand::Result), + Condition::Compare(Compare::Equal( + Expression::Operand(Operand::OutputA), Expression::Constant(0), )), ), ( Flag::Sign, - FlagDef::Compare(Compare::BitSet( - Expression::Operand(Operand::Result), + Condition::Compare(Compare::BitSet( + Expression::Operand(Operand::OutputA), Expression::SignBit, )), ), ( Flag::Carry, - FlagDef::Compare(Compare::LessThan( - Expression::Operand(Operand::A), - Expression::Operand(Operand::B), + Condition::Compare(Compare::LessThan( + Expression::Operand(Operand::InputA), + Expression::Operand(Operand::InputB), )), ), ( Flag::Overflow, - FlagDef::Compare(Compare::BitSet( + Condition::Compare(Compare::BitSet( Expression::BitAnd( Box::new(Expression::BitXor( - Box::new(Expression::Operand(Operand::A)), - Box::new(Expression::Operand(Operand::B)), + Box::new(Expression::Operand(Operand::InputA)), + Box::new(Expression::Operand(Operand::InputB)), )), Box::new(Expression::BitXor( - Box::new(Expression::Operand(Operand::A)), - Box::new(Expression::Operand(Operand::Result)), + Box::new(Expression::Operand(Operand::InputA)), + Box::new(Expression::Operand(Operand::OutputA)), )), ), Expression::SignBit, @@ -54,23 +54,23 @@ pub fn build(rt: &mut Runtime) { ), ( Flag::Parity, - FlagDef::Parity(Expression::Operand(Operand::Result)), + Condition::Parity(Expression::Operand(Operand::OutputA)), ), ( Flag::Auxiliary, - FlagDef::Compare(Compare::BitSet( + Condition::Compare(Compare::BitSet( Expression::BitXor( Box::new(Expression::BitXor( - Box::new(Expression::Operand(Operand::A)), - Box::new(Expression::Operand(Operand::B)), + Box::new(Expression::Operand(Operand::InputA)), + Box::new(Expression::Operand(Operand::InputB)), )), - Box::new(Expression::Operand(Operand::Result)), + Box::new(Expression::Operand(Operand::OutputA)), ), Expression::Constant(4), )), ), ], - store: None, + stores: None, widths: &[ VMWidth::Lower64, VMWidth::Lower32, diff --git a/runtime/src/vm/handlers/trailing_zeros.rs b/runtime/src/vm/handlers/trailing_zeros.rs index a11502ac..016fad43 100644 --- a/runtime/src/vm/handlers/trailing_zeros.rs +++ b/runtime/src/vm/handlers/trailing_zeros.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Compare, Effect, Expression, FlagDef, Operand, Operation}, + handlers::semantic::{self, Compare, Condition, Effect, Expression, Operand, Operation}, }, }; @@ -10,24 +10,24 @@ pub fn build(rt: &mut Runtime) { semantic::build( rt, &Operation { - effects: vec![Effect::Tzcnt(Expression::Operand(Operand::A))], + effects: vec![Effect::Tzcnt(Expression::Operand(Operand::InputA))], flags: vec![ ( Flag::Carry, - FlagDef::Compare(Compare::Equal( - Expression::Operand(Operand::A), + Condition::Compare(Compare::Equal( + Expression::Operand(Operand::InputA), Expression::Constant(0), )), ), ( Flag::Zero, - FlagDef::Compare(Compare::Equal( - Expression::Operand(Operand::Result), + Condition::Compare(Compare::Equal( + Expression::Operand(Operand::OutputA), Expression::Constant(0), )), ), ], - store: None, + stores: None, widths: &[ VMWidth::Lower64, VMWidth::Lower32, diff --git a/runtime/src/vm/handlers/xor.rs b/runtime/src/vm/handlers/xor.rs index c58e81f2..ddf4ee06 100644 --- a/runtime/src/vm/handlers/xor.rs +++ b/runtime/src/vm/handlers/xor.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Compare, Effect, Expression, FlagDef, Operand, Operation}, + handlers::semantic::{self, Compare, Condition, Effect, Expression, Operand, Operation}, }, }; @@ -11,44 +11,44 @@ pub fn build(rt: &mut Runtime) { rt, &Operation { effects: vec![Effect::Xor( - Expression::Operand(Operand::A), - Expression::Operand(Operand::B), + Expression::Operand(Operand::InputA), + Expression::Operand(Operand::InputB), )], flags: vec![ ( Flag::Carry, - FlagDef::Compare(Compare::Equal( + Condition::Compare(Compare::Equal( Expression::Constant(1), Expression::Constant(0), )), ), ( Flag::Overflow, - FlagDef::Compare(Compare::Equal( + Condition::Compare(Compare::Equal( Expression::Constant(1), Expression::Constant(0), )), ), ( Flag::Sign, - FlagDef::Compare(Compare::BitSet( - Expression::Operand(Operand::Result), - Expression::Constant(63), + Condition::Compare(Compare::BitSet( + Expression::Operand(Operand::OutputA), + Expression::SignBit, )), ), ( Flag::Zero, - FlagDef::Compare(Compare::Equal( - Expression::Operand(Operand::Result), + Condition::Compare(Compare::Equal( + Expression::Operand(Operand::OutputA), Expression::Constant(0), )), ), ( Flag::Parity, - FlagDef::Parity(Expression::Operand(Operand::Result)), + Condition::Parity(Expression::Operand(Operand::OutputA)), ), ], - store: None, + stores: None, widths: &[ VMWidth::Lower64, VMWidth::Lower32, diff --git a/runtime/src/vm/transform/encrypt.rs b/runtime/src/vm/transform/encrypt.rs index 498690a0..bb7a0372 100644 --- a/runtime/src/vm/transform/encrypt.rs +++ b/runtime/src/vm/transform/encrypt.rs @@ -278,7 +278,7 @@ fn invert(x: u64) -> u64 { } /// Encrypts `source` against `addend` and `multiplier`. -fn encrypt(source: &mut [u8], addend: u64, multiplier: u64) { +pub fn encrypt(source: &mut [u8], addend: u64, multiplier: u64) { let mut buffer = [0u8; 8]; buffer[..source.len()].copy_from_slice(source); diff --git a/runtime/src/vm/transform/indirect.rs b/runtime/src/vm/transform/indirect.rs new file mode 100644 index 00000000..85e16c9e --- /dev/null +++ b/runtime/src/vm/transform/indirect.rs @@ -0,0 +1,57 @@ +use std::mem; + +use crate::{ + mapper::Mapper, + vm::{ + bytecode::{Phase, VMWidth}, + encoders::{dispatch::Dispatch, load_immediate::LoadImmediate, Encode}, + transform::Transform, + }, +}; + +pub struct Indirect; + +impl Transform for Indirect { + fn phase(&self) -> Phase { + Phase::Indirect + } + + fn run( + &self, + mapper: &mut Mapper, + mut operations: Vec>, + ) -> Vec> { + indirect(mapper, &mut operations); + operations + } +} + +/// Redirects operations to use an indirect dispatch via [`Dispatch`]. +fn indirect(mapper: &mut Mapper, operations: &mut Vec>) { + let mut index = 0; + + while index < operations.len() { + if let Some(children) = operations[index].children_mut() { + indirect(mapper, children); + } + + let Some(op) = operations[index].op() else { + index += 1; + continue; + }; + + let operation = mem::replace( + &mut operations[index], + Box::new(LoadImmediate { + width: VMWidth::Lower8, + source: mapper.index(op).to_le_bytes().to_vec(), + }), + ); + + index += 1; + + operations.insert(index, Box::new(Dispatch { operation })); + + index += 1; + } +} diff --git a/runtime/src/vm/transform/mod.rs b/runtime/src/vm/transform/mod.rs index c1487f56..eaf58eb1 100644 --- a/runtime/src/vm/transform/mod.rs +++ b/runtime/src/vm/transform/mod.rs @@ -4,12 +4,14 @@ use std::mem; use crate::mapper::Mapper; use crate::vm::bytecode::{VMReg, VMWidth}; use crate::vm::encoders::store_register::StoreRegister; +use crate::vm::encoders::Depth; use crate::vm::{ bytecode::Phase, encoders::{Effect, Encode}, }; pub mod encrypt; +pub mod indirect; pub mod mutation; pub mod peephole; pub mod permute; @@ -49,10 +51,13 @@ pub fn atomize(operations: Vec>) -> Vec>> { /// Atomizes `operations` into depth-balanced atoms, collapsing into one if the sequence is unbalanced. pub fn collapse(operations: Vec>) -> Vec>> { let mut atoms = atomize(operations); + if atoms.len() <= 1 { return atoms; } + let single = atoms.drain(..).flatten().collect(); + vec![single] } diff --git a/runtime/src/vm/transform/permute.rs b/runtime/src/vm/transform/permute.rs index e6c4a2c8..d40e2986 100644 --- a/runtime/src/vm/transform/permute.rs +++ b/runtime/src/vm/transform/permute.rs @@ -10,7 +10,7 @@ use crate::vm::encoders::load_memory::LoadMemory; use crate::vm::encoders::load_register::LoadRegister; use crate::vm::encoders::store_memory::StoreMemory; use crate::vm::encoders::store_register::StoreRegister; -use crate::vm::encoders::{identity, Effect, Encode}; +use crate::vm::encoders::{identity, Depth, Effect, Encode}; use crate::vm::transform::{branches, collapse, descend, effects, vacant, Phase, Transform}; struct Access { @@ -39,11 +39,7 @@ impl<'a> Transform for Permute<'a> { Phase::Permute } - fn run( - &self, - _mapper: &mut Mapper, - mut operations: Vec>, - ) -> Vec> { + fn run(&self, _rt: &mut Mapper, mut operations: Vec>) -> Vec> { let mut picker = self.picker.borrow_mut(); descend(&mut operations, |operations| { From 97792d24dbb6dbcb3721285b7855af51ee52cddb Mon Sep 17 00:00:00 2001 From: unknown <86915746+RadonCoding@users.noreply.github.com> Date: Tue, 15 Sep 2026 20:40:23 +0300 Subject: [PATCH 06/33] chore: allocator rework for semantic builder --- examples/cpp/hello.exe | Bin 0 -> 132643 bytes examples/cpp/hello.protected.exe | Bin 0 -> 360995 bytes runtime/src/vm/handlers/add.rs | 29 +- runtime/src/vm/handlers/and.rs | 13 +- runtime/src/vm/handlers/bit_scan_reverse.rs | 5 +- runtime/src/vm/handlers/bit_test.rs | 7 +- .../src/vm/handlers/bit_test_complement.rs | 9 +- runtime/src/vm/handlers/bit_test_reset.rs | 9 +- runtime/src/vm/handlers/bit_test_set.rs | 9 +- runtime/src/vm/handlers/byte_swap.rs | 31 +- runtime/src/vm/handlers/or.rs | 13 +- runtime/src/vm/handlers/rol.rs | 7 +- runtime/src/vm/handlers/ror.rs | 7 +- runtime/src/vm/handlers/sar.rs | 15 +- runtime/src/vm/handlers/semantic.rs | 1223 +++++++++++------ runtime/src/vm/handlers/shl.rs | 19 +- runtime/src/vm/handlers/shr.rs | 15 +- runtime/src/vm/handlers/sub.rs | 31 +- runtime/src/vm/handlers/trailing_zeros.rs | 7 +- runtime/src/vm/handlers/xor.rs | 11 +- 20 files changed, 950 insertions(+), 510 deletions(-) create mode 100644 examples/cpp/hello.exe create mode 100644 examples/cpp/hello.protected.exe diff --git a/examples/cpp/hello.exe b/examples/cpp/hello.exe new file mode 100644 index 0000000000000000000000000000000000000000..55698f7e872a415e3819886375130ec09a592c28 GIT binary patch literal 132643 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 Y6V6bo!aLurCZESk literal 0 HcmV?d00001 diff --git a/examples/cpp/hello.protected.exe b/examples/cpp/hello.protected.exe new file mode 100644 index 0000000000000000000000000000000000000000..0acd44ccdf606b78a6665d773ef067ca22543235 GIT binary patch literal 360995 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

IlPmg@z_@v0&OwHP=tO(y8iZ%kz z-ijrRC`-xFiw!9dL)dl*P8CH*98=;<}o&Ad!$L8Sf^>lrubySu60*`@xx{3q@PE`BwxCssinnp z`W-xk2F-0Fb_vd>VjM!I&1@+KM)Zqk1(bmq{eA6&Lfz>bIJ3Nex*V2Yk6RB#ZVdt3 zS%h)zL6g?NKRai}y+mp4i87w`RYV5yLT?N|;cXl;;<1@tF)y^~Y>LYQ=lY2hm@5BR zQR(Yj_~DA-@YZJi)>3!*nmdN`RW1!V`3RC+~AHfj^o8HG6LCR@lGM1^{{lfGaqb7?YR5D|@_W z-gyM~^`L2T_%6eO4+EFf?>3Uzf$ee0Jz0WF{M@-jd3tv6VtXbSvHyAP*E3&vO;ADr$Wqscz!B`k(r~rW=s= z4@D)p;MJ?LP=x=8?UX^Pv=a`Wh6?MhsMcAEzmO{JNhfhq=<+f|FCR75j$=3a)H-AP z55{OgN%LWMa3Ge9)+F`7-4nPDtzLX<$byXKsZ>#Sv-fOQ)N3b zBVM4u(dA1o2#o{p_SE9w*uXO_bmT&T*h`9@s6oEQD+=H*BfEZ(p+%D)m1jIeQy&8!Cb@eg=v_tuq*xEsS$wk}< zg+pUaqUs+a67`20F)#3wOYOLZI~4diCiP?blw0`#@D)RWy+pUj+jlkqYJ;JD02;rx zC2JqTO*KrK z1doP*`J(a1xXCrYnoZNr0wB?Qdz#Cvp^9@s>tgOb65eH%E6Qnq5s6MXtzuK=JX}Wi zbXbh9K$N?bI4N3>#dDt84}8VagZrW4w|xa6pTt2xYl>Ye+G{rBD~fu*DJEZG#jz3U z+?hRriHdjop2mt_(xACkjnaO2xN6-DzOL6?Q@vVV_}k?BRa5d8FQtIxE|)QT^@rpF zz=w3>IHO;?*_W`l^_%?Er+(uRd>WHN5M?^#yH1{eqIQnU9AaN1d5j7^wJ2ZxgOV2tzSjq#;=3aG4dRc`Tw-6>om#jw$%+D_#=VJ3u>Ph3K-@CQ1&yEsMs!=sRNnqn zgz|cJ;lvoIC68ko79>5N#xxvNFoFu(RSqjpgvg*A;c`u@@2xazGV{yoi%7 zOQ&VLUf*B_S8aq{=aV1Tn-1GRfgc9~Z_imCv9a`__Y$1rM(7;qVxT@|Pcc{T7dK*ab$9c#U+2M5 z4`A90O=GX*DLexBWVk@8W{+-ZX?YzdzZzP!ERiOVB&CV!aF|CmtEvA2qhTcpaf2om z=ioSqBw01?@pW9HkLL9Iwqdok49(RTNU}y21bt|Ni;bIemG7! z%x2u1RkIy-PQti`laT7HB%!=$F7dwRa)qg#gi{ZGJ{?ELG5RKbkDj?KZ4(7X=Kbn4 zXZCe203t~o#FAt{iL@l)2xUxb6fKOH2C6mwcLw_MSQT(IAJMICD%! zRTrmFLX?E59x%;;aa!&@-3m)=FGz+q;dY{}Z&hhT3$Xee%|luEGHB}-eBF^A{_=|N zYWqrDx1xN+=nQ@n+T`r<4L9H)>cRy@cd#qaz*5k# zdEg^7CX?&1la*p5+O>28m10^MMrJSqEZ;7}+B(~p)*-yd5o(|BYJk`7Rf)VTt!EH@ zGRgL$WNmUbhB5fj*{cQ@%++4CZlO*1%)@>OOvAO|&14+A*!Rt(U4_sDkJz<5aT(Ka zy7`biRyP4vt!&t( zParlHZ;2|B0#< zI;WeUOxJfauCx}PEh%#7(ISN58vnEQIZ3`}iyUa_xiU%0A?)kv+`3D+46YxuA3Y4G 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⪻&jE>a`{db)xFZBC5~cQ-FyW){)FG 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

~@0vp=OMF)#B3lN#=TBvFRi!hIViLxwD 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

IlPmg@z_@v0&OwHP=tO(y8iZ%kz z-ijrRC`-xFiw!9dL)dl*P8CH*98=;<}o&Ad!$L8Sf^>lrubySu60*`@xx{3q@PE`BwxCssinnp z`W-xk2F-0Fb_vd>VjM!I&1@+KM)Zqk1(bmq{eA6&Lfz>bIJ3Nex*V2Yk6RB#ZVdt3 zS%h)zL6g?NKRai}y+mp4i87w`RYV5yLT?N|;cXl;;<1@tF)y^~Y>LYQ=lY2hm@5BR zQR(Yj_~DA-@YZJi)>3!*nmdN`RW1!V`3RC+~AHfj^o8HG6LCR@lGM1^{{lfGaqb7?YR5D|@_W z-gyM~^`L2T_%6eO4+EFf?>3Uzf$ee0Jz0WF{M@-jd3tv6VtXbSvHyAP*E3&vO;ADr$Wqscz!B`k(r~rW=s= z4@D)p;MJ?LP=x=8?UX^Pv=a`Wh6?MhsMcAEzmO{JNhfhq=<+f|FCR75j$=3a)H-AP z55{OgN%LWMa3Ge9)+F`7-4nPDtzLX<$byXKsZ>#Sv-fOQ)N3b zBVM4u(dA1o2#o{p_SE9w*uXO_bmT&T*h`9@s6oEQD+=H*BfEZ(p+%D)m1jIeQy&8!Cb@eg=v_tuq*xEsS$wk}< zg+pUaqUs+a67`20F)#3wOYOLZI~4diCiP?blw0`#@D)RWy+pUj+jlkqYJ;JD02;rx zC2JqTO*KrK z1doP*`J(a1xXCrYnoZNr0wB?Qdz#Cvp^9@s>tgOb65eH%E6Qnq5s6MXtzuK=JX}Wi zbXbh9K$N?bI4N3>#dDt84}8VagZrW4w|xa6pTt2xYl>Ye+G{rBD~fu*DJEZG#jz3U z+?hRriHdjop2mt_(xACkjnaO2xN6-DzOL6?Q@vVV_}k?BRa5d8FQtIxE|)QT^@rpF zz=w3>IHO;?*_W`l^_%?Er+(uRd>WHN5M?^#yH1{eqIQnU9AaN1d5j7^wJ2ZxgOV2tzSjq#;=3aG4dRc`Tw-6>om#jw$%+D_#=VJ3u>Ph3K-@CQ1&yEsMs!=sRNnqn zgz|cJ;lvoIC68ko79>5N#xxvNFoFu(RSqjpgvg*A;c`u@@2xazGV{yoi%7 zOQ&VLUf*B_S8aq{=aV1Tn-1GRfgc9~Z_imCv9a`__Y$1rM(7;qVxT@|Pcc{T7dK*ab$9c#U+2M5 z4`A90O=GX*DLexBWVk@8W{+-ZX?YzdzZzP!ERiOVB&CV!aF|CmtEvA2qhTcpaf2om z=ioSqBw01?@pW9HkLL9Iwqdok49(RTNU}y21bt|Ni;bIemG7! z%x2u1RkIy-PQti`laT7HB%!=$F7dwRa)qg#gi{ZGJ{?ELG5RKbkDj?KZ4(7X=Kbn4 zXZCe203t~o#FAt{iL@l)2xUxb6fKOH2C6mwcLw_MSQT(IAJMICD%! zRTrmFLX?E59x%;;aa!&@-3m)=FGz+q;dY{}Z&hhT3$Xee%|luEGHB}-eBF^A{_=|N zYWqrDx1xN+=nQ@n+T`r<4L9H)>cRy@cd#qaz*5k# zdEg^7CX?&1la*p5+O>28m10^MMrJSqEZ;7}+B(~p)*-yd5o(|BYJk`7Rf)VTt!EH@ zGRgL$WNmUbhB5fj*{cQ@%++4CZlO*1%)@>OOvAO|&14+A*!Rt(U4_sDkJz<5aT(Ka zy7`biRyP4vt!&t( zParlHZ;2|B0#< zI;WeUOxJfauCx}PEh%#7(ISN58vnEQIZ3`}iyUa_xiU%0A?)kv+`3D+46YxuA3Y4G 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⪻&jE>a`{db)xFZBC5~cQ-FyW){)FG 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

~@0vp=OMF)#B3lN#=TBvFRi!hIViLxwD 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

;Z6tMBLcAmey><1dZ{V1J zygLiTme~(S{FwRsNR~ez=jkBdq;rv5>0NFgjL5d=4AU@TkV$ata~WH3ilE1I>8J*}3bLD+{Lhn#PXxzu~L4|X1>rd}_8~g{4Zb0`-b~ep<)m@=p zK#ZbL%W-{aN!UzgrA}bqG@}#kq4B2`8faoxPTowDY4PIfUqFpXfq^~+oszi6|G!^34XZE z7SI0UK@7Fk7Ha)75x;s zwg&=b62C@_l<1BC@#mEgU>`H1Chq8<5E7*Z8pAsKm<`)_kU#D%mN=QArp<1n4MyKw zRmpzwGU!x5d;_>+ZkF7++U^gjBn+!c@AkiHLYUL!$NLd#!xl zGSDF#rQGCd(yomV9tdJxAn|=0XDt~C1OP}8D3dlTIN{QrJZ&PTTdjfb$MZmq^UjF} z?1NanJCC;v;9HmWIc6gaR*d2-C8UXM4os4HGXaBhS!p%ihNxvI%0~G3yYUYmN3UNs zV?O#hq8KQD{&^?|VobL;vQbjC9X2sN6hwYJRu1bwBJix~Mu$?;{|*s|gXeAPhYHkW z78{}yYmPtt0+u0I8K*sz8^ye-)cYmG@Q)ks9gtqTPNT((-`;xrQZQ7P+I2^`Uv7`& z;fEc0a@htWFAQbE%2jcyW?@5#n;#r+q?`AR_KgbL!2$!ql0{NsNKZvm^pE(u`jo7h zgU9TW6Cvriww<}Zu3+D13`A{D#KPs_s2nQc{c9U5vN=TA+92M8%}HQtOBc`RWduu* z^iwN3ms%^~CNTie0LaSwoMmfIWnR-}xr*-Re>C(Pynt>|+3wIJD44ULUk-jq&-Rv_ z1QN1Kq=oI$g*^G?w{xN_h&HqALe09Cc)S)#4^YC{wt=8;V75c7ivH=BpfObiPSbN1n}C zQ5m0M0}dv$*Mcfy&31HkXyH;SBuC34W_|gdiFrzo>2uQfbG#ns`jqq~fr-W$lF{Ck zAEX}uXaq<8oVW zkDa$5Lr@%8%*y(oZldSFFp;-&7}4JkdT_5nkC$NgWa6J$ z?}P+n@$u!6Sj+z7vfP1V%$;7K0YENBRl@`hsb>s&;eV!NmHdQV2&!mLCWc8!TQD0P zva~Es-ky%kzPGuPbV?{{P)d-C|6199dadgT0 ztg~Y(E{{f4TmmcInr2jkb%Q_eayGPCg(>u@pPQ^5=8ypZoq)aD7IWh@(~&`&O^PWk z8&I$NK_40Smpi32V{|J!Ru216;1}nLhv&4r# zFFJmY7iu+;bNLnpK5{JmVXgIeB(bqwHB1|zv8cVP5Xg(U@-~K%wfJXkA{DZdM3JsT}Sce8vUMX+JSkQiM-hi;4U zR_XVu1A!`xZU*?OoxYv}>iIr*i#F^%AAauKEUwqJh9o~pRwLY_Ck6n!(~|qkhjPB| zs3~-lkt(nqVEZnFNGKuBK;bccv9KM~-Yho0y+HX$L)$%8hr{XU9jk*O`}0VzCht;2rg<@56<$V=Eb#lpQ*bpRv+2o+c8M!jlY z1^(V-uEZ|KAt5Ys4>CsM3$~)HN?hQF-E5~m(YNdy>5J(Kz$jg_npzhO?^<9xE8m3D z96T@vpSv_JtRz_V_fgdQJu6o$Ocj?AsrhawV$YKDnE?L9WpniG&=~+=26(6E$m^9% z*N~<~ByBPjfkJ}kJ28#Z=dxs5&b53Lt_PxbAxd*6>f_F_qCO9tf1gq0IzbgvK*&m( z>-(a?YR093Qout*&l#LjzZv(ffrM3R@f7K3%}w=hXP)y?)pb$uX?Mx1X+3lL(Ak5; z>r=QNJv>kId|tKrGU(f#<@+AAi+D{6Idfwl%!s4I8<9><89tB<+5%8pK$CLVzGxX* zp4e!|D}O6q@q^9pfXRC7YP}B{FzGV{4G}i9zEEpja!B=c`%pCg#d}nBS|4Sp7Pb7~ zzSCW7mujlG|H#RdV{iNZ4zPQOx*v0%QhY)hMep^Hl@|RN#!$U_Gn0zS^;24`VvFjL zI{%+<);>R7+~Yd?+o!Ocn5RwoPh-aAkGnMXM<3^?r((5Wmi8}Gc!6bxQ6-; z5Hpu-cH1le<%4r=UB<1-?x!F_OztNdJa{jOOB{8t{W{c1`)4i_VQ5rTQ{_`dW;rtA>{R zy(hGnJ%P%iEYEr+A_1S(GEW*}7M4&f0`7@3sGVJsQiSJ+^V61_QciViN?jnMlld&d zkxX!3666&Bmxc_6u#qgv&7!A23{1{UrNCVk_)2oM)|P?*SC8(Txpw$~rU0N(;-77@8E)m+R81ObMWB+3eGj&B+(aQ-*S|&9ooe3}|A& z#=zkz==(ic>5lxxK`dLAr~Uo_+8A}K$i!QLGt}r|S1GfO;jj9Bh%7U}xK1VO+5@>f zpa#c;8SCFE`&|)AaAB3u5YsRlg?k+OXSHBqKOZ?4SJ? z2TRj50_Mf1&GEOSFwjB$^~f0Y*J+p9cPzO8fGYsuZ4vUjDl^2ynD@DlEQzfYxpQ7C zmwJcTUmzuytnO&w2c2x;bplqu{^%IIAO4s&hQ#cBR9bYhv?q8&s&_cw|2KK`@(ur}8V#hJqiQ z*9t_hNa&qQVI3A8V&s`$0)O~dC-wIU7GuM8INF)a3)GRSKq*!A#>O5yrm1{ORwWG! z=a5RGx$=%v8oT*xSllJGvV#``DA;G#5K8$J=eILlFR~@|uZOi`r}^dKXf0;}a3Zu? zKkk*(g%8$e`<`dH{fo_@2PeS|^2o3u-$~>P8)f14J3rXf%X{`g!%Qde&K-*h8@-%z z3^5DA`D~L}cM7$j%u(Ub>FCY8e8x`Cdk>WU{ntGbwx)GDzXW${HW=MlUMaPIClFfN zV-2MK%`N@;W4-W_&Ebm=auO5lRTX(*u@f%4Kk@Jz7Cc=mxr#Re+0=q8WaAbYr8|07 zq`--~)LX_;PC%%y(~n)9Yg_5hjw!a+uh63_e3i#I3L&uW-|0+WX<6r4Q(sp5W~E`M zN!O&6IQ5632|Y=L4KL{yV5a|`?C!OIiLvgDl>}m;xgil+E!RV&$NlTCBL3$2BBAVw z3D(9;jg5Glf4dWB3(0KTR-SPrV3R3XfMYFsfZIhULBl0W7lAsu@QIi;;%?|mH}eAh z`y&Pps#e~u#^nG18{pmq&?Q#tB(`h#CB zq{K$T! zTAoQ|UDAPH8~c}7KyV?Nrf@?kk>&Udv7?5=imYd^`S#7v#v|nr?m(rdWv-9yu^MXy zSfY|CH7xDn@O>CKJiS)`?9W3YU{f_@Bk#lILd0ciMv+}GjCI9q^=9OV_AiO52jmsV zRkkq^e(oh(uYkz}qzbE>;FofF4C|o_mPJR*OIt(Qs>(@Ms7O2e_L9u-51N|fwmu^Q z5L#e#!{@EKaivYOp?)H*aK6Z{LAkJmc*k?!5kcY_#B8fgj0K}h8sAzmQZ;*7d2o~m zC@&02&*g~W2er$UILBJljF96#g5(TV(D6)2i4pM0S+f{ElFnIwgKNBaFe6mIJ^TEp z{Ho#mR~s=>+UKp)U?SpVaw`?u&Jy3(Nj&4hiRD`xm8wc6rQyaFZJ3E;e0|qJ9Qnic zrPggzqOV?Ti`PiD!#&7=BCc9T$Q!S8N;%$>J$?JK_ekE_hK*M7i+!(z!0f;Cr2;$V zYZ)NFvx9Tp2YI=Bnt%7TJ&F9ZPC0NCRoO1Rg#ptkVp~oN5J@E%T~AI3Z(BS{CKLm0 z=7KcdI(~|r2iD|E)0JGvvYoWbL+Bp(CSk8wZkC^{ufn)ITtG6F-{PNcYiQx~62X?d ze|FJLmoFAI%#%;25C2$ijK;)Vb`D!h3{@68BZ=z4;0_%8Htf{L?LAoNJRqdc@P^_; z>ZOWEiwu3I?i`s4()RFm*p((=7f)5m9jV6-tNPTBB-9HrPDU#$LD6l44S`gNAM zhCEjbo-^HL1Mk!yq7n`}DSw968C7gvSEy%$f!G+*a~P&nWZ3nNi0_y#r2g@qh9=iI z653rP0?H}i8N%&PHn|48<@c1-MfjJ(c`p_DQIrmz*P~z4~ddr@H2 zYMqQ*DU=XA%0l(SUX=-Fsq@o@#3yq-KkuXOMw*m6P!e#bFu++9tRv9KW+tZx(on-Q2>#{)bYI< zpTIIZom-+f^`>ZfM*pE&7I9m5Pj94@9-NzRo;|FaE6NO|$(ZyMaPZQ)`H5W|swr&v zr?blM(*=AU5PfU0Smi#UY^$;R*_bnQt6iq-gv<{2LTg>yzS8~V_Rtt7WFSL}RjAF? z=5UXS;_Eqs%s*VCq&M;A*d2r#kC`e~iXf^kW|CaDfLt|`SU91N@Tu6SC)t4>7aw>) z&hl~WV~K)rW;x6c@ZKXvl-t20I#<+OdB<@2ep+|Nr5?pwxRCKcLG;pPT--Z%(bf-Z zp@6zd$|=RulnMagM?fIW?HV50#3a=tLihc|SN8g?#0NGM2M2#kP-8fe97EiL!I%xs zra};{E0NKGYgPWnL=~HhhI9XUU(V%aE_T%9)6T)4 zb2?a`pf#@i(Y}l)YX!`1(@J(urJoJ0gW~5Tco9gGer7*VTY_hzXziC5MuRMB#%?-m zvTHa>rpJ#xQ=j?7ouJt%A_5vV(io)ad^L*#?KV3Svi)E3>jj z!iqsk3@D=?5`CHB`@t}ML7dN!ylrvEWczBp!hScoDglM9ol#wkxMTxJqlW5&0ULSO ztpSx7o^XHrx=esFPg}{`jA`v_VdTrE@d8(H+ME9SNurS}sXtz9(bsGQ_pYY(dYl$4 zGjfXOKUDuVAaRI>q=?1T$UN)ch>ju-g2vA;BUR1L1v!Fn!%y%=Tx9IRP)v{;yWvNC(IG{nLGrE?PGQ))80^ zu2TCg{-zGF0hXF6AmBJczOXw)XjxIZ<#(K_BkV`5YkVEAbr9iG@(m-FzWF4>-`J@f zZ$HR>ejRtKFvX<~e{VRDEd7Ru@|*858gD&=y_5;7VV-a;g9#va!ak9Q1WfU3ui*R2 zhtd}xDTC5#^XV-eOcv$88x7NeL|RO;@VL^l$zR3(45ZC8jQh-g)rE5C9KrhTz(vs& zX`S*LiM|4MQ2s9AxWVxPyOu>=x$Wwm*y87W5qa|XqsxUNEsBWHwXa1?=j=G}ENTlt z$J>i$w>yRWOkhFLOL6F>26LOU^iR)Bl=q zpuTlY<~yEOoAq;zFN4fMk1GB3C)og*Q38?WGND6EgrXq*1pIBw_qXSTNKu75+p;2 z*<+S6Cr132+rK$4P3i=B0tKQ-Jg%{~A=z?zF`sLB#&mX;bP@jY(=M08`P8Trt|Oj6 z*4I4jE5U1U^O?%UYr0jKSW)JuEN_J=>|8%Tp*d0#D4)`CgjAqsVKYK-kq2Vqr8b4ecu!Wr9CpKcu;+bI*H@L9tv**S3OGrr$`5v1)3dD85E<-wk6hIqg z>!!a(jQtl)r-#m1n>^X~ZR5)KZJ&w`;tc!2?)5hu28G46M7C)>2UkPE_b{$Zl>uhj zaC$eT*nzY8p`jj%L$=X>6b-^V3f-%H4S2&x{YX|UoMhu zhtpzrjH#Bfww0^grg(9J90<`5v7IZ3vS!)nchi@u^wRxb-lVy~b0GK;&^MP=d~Wqk zJek54vH~zXrcifkEPOcuQI(;BnUcM&jWukIpy?xhR}QmM!Jo(L4iVR?%-()Ki&UKi1@o^m3)h4 z`%Yd2<#4`@DJGFw!~~9JEgqc;P?F1_Afb^dmukHm&4_%--$Fx`2?h=w<{^Nxhh@XB zCO;c>dBeF6<7iO`k6%ze;C8oWGBvefUtU+d?1fd@QAP2m#6ckPO65&$q^#C@`Fo#c zwo2`HK7L4)mJnTRxK&u*&rjklY}A|yPKSTnEX9odfY`>H1nDtq6WhrAN=}i#w{Wcn z{#6w*yiOYf5Rp^TyH~yB0ga-hR>A~TerXl2NY~_g57$NTJ(A{r1@UvO*%;lXloDt{ zPZ1xgba(NHEhV@VApK9eS7$ul^wkNMui2?A(!d1mhE?VSkyht+GpN&vj6WUk4eVn3 zVE9O?EZTc<#n6U_u}44(1`_AXWmmTwSh|SWOO%nHlpER~*FFXyG;1HaNQAL^_l{T( zY+2yiz0dQG*Z4XI_VeES7LwV(V56c$6=AI!?54o92llzbPoUa_amt`$Ob6- zaALPV>Lqy`J;9m>MvE*xprNNvLmzYBQ1XkvUbh?xZamV6a~^fz-Prg!CQg1X{J2e3 zB1{uRZk4XXTwxZla4j*fVuviF6*v3_jbk`f1q}N^#_Xddn?~ZT$Y=cO#MQ^L^f2%5 zuB?%9k!M0BdZ+U>Pe=D-aM`Q!eJziI@9;W>VClUUoL7a1Kq9~!b4!Z({ho!6R?ZAb z=lmYInk6?L&Z=B}cY;EIfcGVoRwkGT+58N# zq~1O@t)Ebq*N~8!TDTzglgs#U-#TW%^F^8FST-;xB*DoHSqgP3hz^rn!ylt9=eCg8 z_$QfPBv#DvEr6!C)V`qEdPS4Bzn5U+7+dUnZD7q)t56g`ux=$*WxBS?Lz~XFY>6oP z(^V_Ah2e8O2pwsC&G>TnCfQ(X z5JY^sm=q%E@$8+!*YJU2AH5MrrkYNU^(p854~sgL4kiySBcXSd5}~EJa6M9X&HaY? zyN$kswTtbm%rP_X-6jSen9sMI8uy|*NK=u}OQ^U%+mN0J=h8Kqvc`JVr1JwZe}#j2 z)>*0gQs3sc&G44@Bqa;j{MgxYC%z9VAFNq?+YBjlM}JP(gOk7QfiD!O2;rG)z7p0| zze>LZ$xauFKI_y6qdB*Qh zXrs@|FVM1IlDsW$Xz3}$yg^9{skV)eZ9a=Pn#30CTL$QXwKp<+E6Tipa)698sWN}r zG}K)7LGR9o#aGOfU4&y^z`yztQ@_5jkristgXa+z*8|N5?E`%W#pW*WkW%9ExB7<$ zY9!yT`d}B4P5yFX+{h=>Y<5!9i=5+cr(rRLVdxtsI&Jm*yB>kyp_j)h{Cds%tn!xB zimkY30tT80td*?D^r(svdxd=(SZjO!dV<^`&Y8V-ymY@uS2i4i2#pIMjr{JL?E!;u z0y~OAX`B;4Hf=G0nNzvG1WMK-o4r}ooEqgW8v<&Bx}#~Go8W6O5oP%s^8c}hnulvf zW=AO|cV*5qp367fy6|8+Wk$3xv3~yd`yfYat<1G>4XSpQ&DxCkY5u{|2K?|X%VdfzgKwk3Dz{K*4Fa9+sO}|& zLcm)~&DdiObRYYXHjhfScJ)38eFdO`*vLePUqpD7g1>C6L;eBW99h{cb!xF2wuc@cvKvCB$b=;lKLazGN|z+~(a}hLW6^ z=Xo-Z4^f?oh5=*uSfAbJ5NN?j>DH;JYBkYqBU4X*=L8t+sb+k%Y!vMFJSM$|$I%|9 zLOhz~l*J*ISeh$62a>kxF<8#daCy9psS#*k&DdGWao@w-^Ak3LLVQUoJ1I$lBr9CYnSXtzxnzw?RURhGe@bV3KmG$^SXipmlXO1&VaCwr5GLl_35%;V@QhN=zeWo~c| zOKvmX!DUeha}!2WL4l!E&)+kqd#S*vx3@yn`8#rUajacYDp@kP>fi-)w-ls$Rc0d_4NL~q zLf)@_jT8M1WZdf)&0gBB70Z4T?K_`@QZG6Bhf`)AuJ7mpWb73e%jv(dp!5zqRqHJc z`c@F*Kn}LO$36n{oh0HcgIXDj&CPO69`D>v?&-#ddl#kvPVA69>!c`8HOvYGSM@Gu zr{iVM|I}SL=OpQD#~FdyLBD^>knG?reNeB)33=P6?42$g4S8R&2m1x!cv^bb1G#gv zkiz9fgX`-%`@}8K%F;qS4{4CCZnR!J_+PB72y1W?r#$`qEKlooDW*ZP(a~Zj683Ye zrSWJ>VnsO3b`4o*`wz$2YC@ZC`H*HJ9Z&rr^%Nb2+KPc`Flwvad;oj z1zu3r%QB~8i+G_hu>L9*$yeV13K^(Ld}tBbb$B5{ZI*LC{JcMGzQ;5#hf2o0DtT?N z!%S%r1RB7=t9g#^#~$YV(pN00&`_f7xNFIq?8`iP8`UoCW;*-H@}ZH%N<|Eq8Q za)4v{{`2MW*%?{jnSK6N{z#4ChOT+N$d6?`XAVC{iJ26=Fn!Ny)H&OZ8j8 ztx|*KZFW3Es*LQ2apG$heEVN07+#)#F0KIeA+23#G%*rKvFJ3BDM5D>c z6CV79l{j@Nc&a+d?e+J2=uGaI))qY^%&95&1PKt-7c>1(<9wjMfu6Yo>?d}kses|5 zS1na(;{c(UA!zm$X7iTu-ZWa$eWf7a7aI~Bq`tdaz+JojPNJ%*r zvt&1v7d80*d}Er!RvON;-d~EZfG84hCjG<6<)XyG|I%go(y*~bzAYkrA4U(dzKHxP zEp{=-=XlG_oB8cbt3CFk_Ja;uxYJcAFk|i!02dt=FM-vtejLl6S`-+Vxj=mEQY`pP zsw20CfqD`9;pT9ETwBb$z(HrxMU zvdc!~>y*^RKXp6il%kaD&}f-+6!l$|1*gamlis!6KJJB4Y?e`IDAWd1f2zArSGzmW z4cq$M!_@L;bwf5VfrehO4V0Vvh-MO|{@>nWfI?0zdK%%08wXjd(@kpw<{*;sP(q1S z4dF+#f}v4pksFcm0_g9M$d_JYlh$FAiq@EYuoaiiX`8$k?E3{?<;|55(mSkQ_mSq1PGAb0Jjbn>Y3E_0?F>Gr9+#CQQ;54 zG!RzFwv^yi4$f!&rP@4N{y+U?d@UzWOlNXQ6XJ`=fKR`|osPVOhcH;bOs4*S8Zl|1 zEVmgSzr{1FuJAki8iZJg1y`b%5_lSGJBI^g8!Ek|^okfbYLa!P>(&1T&K)ivz4{5r z*zi#X=($od?mKaw^Gm41kjR`kCx;?|?XG~wPl@ow2cH+x79nK=G^69|!z2I>xpkbN za(|T#56bb+rR{mWA=Ndox!RmqgVS;jy1~>%L~GIdgxxu_;cEk694m@^v!K5+6P6Ba zcLpSwBK~P(crWGmLY)}w@`TZ=)?NFg?BVe!Y7z;GJ2^U{)RKhjtKP5zo;9ifc=m%1 z3wP-1o0whXDQ&2oqRJTe!*uogLkf9u8@ zg9HGlId*V#bAK%e?$er_VW;e8%Os<6ktS%AFA``de=Qf=-AM4d8KGwwbRGP~ecvUDRw^CjuHf=!tn7cTeFzN$B*)7a4ny zJNaiu*9@5B(3N@qEKK+j+jT7!9p2-DwKfBTJ-kVRN}{SxvApVr>*(f z2A0k-5jz|q{*Wimc(*9A3T%`cy9p6^`5`I&|@cPp($am@BD5~+wy@uq(uH{8# zPW#QVwlQW1Tsycgsh0@_FFu9(&=yGlKHo5#^ zrhJIgd^{$^-&wq{uETQjd7rst58N#drop3ZnUKtoZ9FWn7VsmBW87q!()D+U8(z&r z_hWT3>jcZ#4_YOm5q}6SPXWWnvJClNA&k;;V>R4@wSEmm#}U`vX9ECUBfyH`CapuL z;08H58vj1X@lbonCvJhC_zqLGhh5X4PoT}MqK;bP)ePW9>m!5`3*C@A%HB2+$Xd`9 zqAYOG^e8>X;`!5k(x9l@F5U=sP`-UF6B8U& zek=9u%RCu^=h3;f*f z;5);IG5g=z%IsS+UFP#0AMWpOy|(A}{`o9DZYttQ|0+e%>)ioo93$=%(Qc%`2w|L? ze^ihsmI{O3#HE<{GoOfinF_9HaE9XpO*T9Fv$&Y>2=nSk;ta(su7~=&x4sTh&ih#+ zRRPa^+%p8)yMO(wvUNj_h%F~fM8ms3e69S7(l(8{Zv8%S={7TnDt;Ke;X+X%5|lO_ z2eXcj46$+9mq)$mo59!7lj1cH+n%74TO&ibj+b=LTqk9wQPL|K&s;L< zk^6?YNSFES;s^X&;G>B`E6k@H5f|tz6t*@L@3t!5@aR{Lvy#OTz58akI~;-@pFoK8 z>Sk7IW9O=t8j~PAe|+uDXsAZyJ3E=7imh(fsw3p`_CdiIMS+FR!(g@yPjzqh{yCr#N-F zZ6YmxT3b&OonN(a$@9snPFNpvw?vdZxuDR}WeFwOXtGMi(SD5H}AhDeYPPD=2>xAfn zXUBioNYrhlDhiZ_8T1uqc)XUYF&|8HgSB7sQOyVyDaZsR%AMrRAMD?_S(&iJHH^d# zhYHAB(*}EosS%)2q7(`gGGZ`DmPJ)~U+E_HhZ@M+xN;k3B1{3jll+>IqI?>tY*ddP zBbWRenipp`P()YJxD8q>#MuivwM%Q%{5fmLW*(Vzek~uwrvSNYo5jf6Ll)c6O1cmO zyAxTF<5U_Qhit#P6X;cjG57F#c}B9z!OGab&M|EH}*`#y=W{4M{2W}eM2o-#F5~( za;<56+PGPBIT>P*t~y5A1UT4972ZZuZG-Ivu$u7`Sb_Zlx*p^6%d-33v_TF*#q@EX z+cK%;%|*@?eW?=tHGUn(ec*R`UD!t~WMoF?^l3tE^tNmbZT^`jfB8Uz&*r^~@)%C% zdrhAov7g6{CY{b{G^|~CVaTn^1Af47Iw=jY2M#GLanDxX)rGGbILC5MW@MMhME;s>#6C zg_)+UeBDWTa6zSHAN>`sEAM6m1J0YI=iIy1qChz3pBJ8c2UV?TOEm3@r_(6CtqWb1 z5OtcViXAFP)RZ)+zZL_Xo7pnIyTGkkpNGB4L36NWcd2-j0WpX1eDfzjfs#VJZ+^e3 zW~GK>WXG>9ERr6wgNdkB6 z&)%QctD5TcWbNm-@TsAY@3GV;d0CsYcRT1*Kj7Y{k2RaZKjO;iCG5nZ001RIASKYL zAG|SDrx1o5boE6gfLH%b0{_c7?7YtK0}I-C@PkpdKN4P?3UqoqiCL?n(YT5Z{E_pk z0#{>e`(6>F7gpMO6sn}Y$$I+C&36UF7Ns>W7NKq%udJKnQAPA>xCg4JJ`>7wruqzT zHQO8qu;2L;e$r2)nWm4?VsKV;n%Q>enyn%EW63bLF=w(L^CD5@gN7;Z18**VC~mL-BmZMSiD`;Anx3gvE@)?RVG@-{o@j-XwpL5oTffK&oEt z*T2ug)Y(V6Un5i!TdFtyqq@0GDzOtm0bdf#5 zOtz|ziv8*!3`{RiZq4)6QLhwMS!N}71oxc}`N9n;|%~@L}rw9M3@67GqT>#2M6xT(-)N?Mqqj-#trk(g;u*lK%odkGR^nGHynTZ zPnXfLDW)*WfV4`|=)2`7QGTfzkbdTFf$I)Dcs@2K2g6=%g25{UfvP$}@xHh+rM!HQ z(Ff$;L5WCksVW%rqBmQPgX411a$HZ`WyY6O{F}D8#{4S<>>^yhi5OgzOjYb#;Rm44 zMxopkIV5L2JlA5ABeLBo_9y=rqXVEPay5YXUyU4P5na4)!^_OU{|-`R=e3#s$1iu7 zhGOVJ*|lTlgOUF5%}5)~q(4jakm+lp&QAh%ATDoPop$xrGcXKclsY}>E*FB~^MU}@ z^Nz8Vm6P)+oO$Lr>UiNEy8Mh~-5O3jk~CcwZn(YYS8B7GarzUk_+Gk8v#m3VqPIwptRsCmbK<$_ErjKTg9sagUbNaGj^@%ly{C@dFWrSEG~Is&E@XU z{n>KdS7sH(Cxn7p#QpJGk^A%ET!a*W$>{M&g_Q#&S`mfDxKvAODM0b69~PKRj-2wi zwBg4Kdbq)Q^?9|Bq(-g0W7ip<8)-A$0<3vxG^1v__kz<^`D*!jLLi5B6}Zq zJHJi6hcJ?wfpo6A7+)4wNlI)^%>b;4ub5P{000=^wrrbDpT_cCQ=TPt3k+|oT}v-} zX*Wt0`>5oN7*CGKv)@$Cu#O-+)Qe0Y+xu>J6&HgJ*LnjeZ|Gt^8jxbNy`oAYs;7Wm zX42-E!DO)p&Xm(@N7|D_yf=&5;ig4=k^y8G9POtH#C-zo-X^?sAfHxr=yKz} zXLYzI6s=m|D9BP7Z+pG(wlo`U(~SkyT!dw$%N%1`JOc3j;Lm~kKDJr?A{Ww(OfeiU ze*k1SZV?LTuu$T3SY9WQ7jDUk$~iUYkaaClQG^|T^qB=sY5RvVZ!IPOO-(d=wJ{ zfP7i&h~1W)E4y6lB~&zG$quHOIqH~zHprOgjem^H7TF$-HjU`~^%&i7oM)%*i;gEb z#UI8PAbbs~1?W|>Xe}aH5W!|r4hYw&rc9aDVCELEiSm(lO?1y|7SIZUb;f7H-9^_A zKY{}ST>N8ns^!y>USgM7BK!)YVF7>RUtA+6WDp+GOXu1xbiez*_uS-WJ{+|Z39q*~ zwYGUwy-CiW^T^JigfGnOBK~xu2z(lcGJl}{@JamVjeH755Z4CjLFm%@Ta!? zZ-KIh$%Qcs>jS3LCt?H#XYD1-;YfJz@JsfJBA-qD#tY>H5k7+K_FQe|2;GGL;TiEV z;Fu*NBy>sik^Wb9O6SxPs1be(I=O58)K zaFr0;p0EyTwP+sej{lDMH|GPPxogZ_|Zbs^jgk*W`_7(I*-$r(bVVKx;sp; zsQcge$yBIgOHZ$kn3dJSvtVukHlAr7tNfw|nlIszvPamlzp~vLQp(@U4QDYCI}tKR zgV0*Jt4(6}vU3zg&8;iQ{`Vf*tH7p*uBh~zU963H$Jl*jSKyXV#2;R@vjS|V<~4sR zRM>0M{6L5j{YC)pL;Lt*S`yiF%&&4;AO?i)P#T0~2a;Bo?@oOT^}|_!nWEglC^#m> zlMimvZ(FBPyY}w9gs)9TwJ<0zySgA+b<`XNZrUPlSpx| zjO^YggmX=MX}%2Ip}@|J>@&VjNVdkeVilc+xjP#Z_dE3;h2ybA%xovyp>Q&;yQ?c$ zoB>~}*B;&P9}$YiZ~x@Y=A4<}2LL(%P}C%Tgl3df0+E8i#x6CFEHw+6uWfQl!zV=R zJ64}8K)gvJsM-R3?!$D~;$fB*waH)G_ISM!Zyh_4dk)`jlx;*`DmXMXTr zu8X1rCP!y}?pHT`39GJ-qVQxGg!|NrM}0qGkxc_p4E#EkNQc@%-pnNSF!FI8c0CT| z!RQLkLw|9Gn#|T_hM3>d}KYzZ>!)MbzI1k)i& zK`G=_-emM<$+BoLq*Ukk4U(Z!?oJ|9u@=67D>J`x%kJaSG#D0UkkFO*ww(38BOD8t z=IuCY6h7b}>5m5D5H2qLan*X$MV76QO~)oJt7sxzv&i!2X`@8|1L{vm8V$5Ozow?@ zKgUnzFfi+f#X6n$ul~3OGBEL-F9PMN+1N=N7((Ft?iaw9to8^~Pt1~yYm| zZ#TF>H>hm8k_xV(nk{h0As%BZCg@2GRIbS8P1d3G)&n;4eHlEWXFzZTRv6+$AGzQE zhPazAf28n?0vz1Ubq>hG=+1$|^0%otvl`T)Mvnt$=c!8RC`LK}z%pP|7sX-q@6HqU zS4;UW5zeUKeWyI5J$ELfA%4o)aRnOiwX`NnN}jpO-On`qgMj@cg}%)EEAAaQpBt~M zKOH@z{Gk$YCzFZQ^ZN|9^obH!AY#y6VS-QC@5jL9pGV)g|9HAD!#VQJ7fEEpmW{-h z8ed_JFz;5euP0&SU1%tj(ZeQ>H_ngMv&sB%6n`FnMv#M{Lz^bbL=H271M>!^Wa+VT z+X#5$`Joi&zpXcv<nJT z%`rEK_%`GWGZo%<{srfuXrb06@A9PoEch~S)oQVBSCc>ZD-~Huz4GhJ743ddr*K3p zaT1e_#IMJf62UxmJV5Cyw?p~e{(hO4fAm!Z$tukTx&y8MHy2Gr;kdKMH1xBCzp3~# z&T^^dGd}3T#;XIqz7YG!;vDs0K-8KW$ax_^HdJ%i#dbH$BuW}$fU-yOwgOin{X@9g zOEw$HTJNzH_HTa`)^w8|@d*{qE&vVZ6yr1VeXXebza@%C*a69~B ziqmyLQ3qqEYJwA#rBsO57$ga*gRV4!2TMdMU+XMNygksAt?shNL^xfn@}1bD$<4;x z%XbFEN!$n`1bx(YQ{0)Y(4J!PYD)CV1-aXV`}miISAo@W#~Q<>lp6rO#vkJ&3Tu1>|K zjy-O1c+pX%cS1}LDo*n7ENNM8&3xi~_7iEL@w3mmSfbA*)v1YDE?yqaB`=U8`qMJ} z&>`EO%hJ_8x#Dm%6vpLQSJV!1MjiT~vGF)Za<);t0zXT;$|o@<-)g$M!DEEUy4a^Z zGA>cWfxeoDN*en-uhY2=pKoMKkxm%W%b|>}P~IojTa_=iCZ(r^@m;Lco=pQv{7WQs z8h=#(2nFvdrtQ*FX0~tKGC9=E+9oXJRIQ_;!8F#LMtHSMe^OCM(nXlzaFSDqLdOFI zz1Z=gR!5@}8P5k61SejGm^gV(s>){Yn($TQ;9aF#=~Zqr*A0EOu2keTE2PA&XxBHb zC4^SHEs}2DxsUtnOQ{5{b;~2Py8>G>&rNBx{T!Z1xWHlkX-Su4oYywV<&WedrEk0~ zy2<9xu-&6cAgp0ga<9XU3ZL&4N)uO~ysc^GJNW!24#!UM-jllbbFp{A>9d_>B`Wo? z{k@S*7nQGB*MKNLy4qqZeAk28R}Jp=j`b(57t`nL*WM^Q7OtNMuYE}iJo`$#F3<S^IJ1R8sac8j8^L{;sLdC&7tM`D| zGG?(|K2gbk{C$#em*0-O$e@!;1??CAIf=SR8dSUCiIqze3e6ipe$b-ii7X zO=4sGd&4eM?dD?C(te?nUz5Tm+`+&@X*&`ux9rYI3zvFu@7g`^9H+vU+RL)(4sJ_P zB~Yl^+zqH#F{#p%(V0|OXnCnj7N18ck=*s{&R_}u0`)sJOj~h`##ujaDlfaRzVW?n;xpQioc}*XpPmKi81s4%d6(myh}WkuMeRP-?Q7Y0(yEuilnoslBRumnm(nYbn347YpVF zLcW)8c~#%GscH4vE$u7rs&3)rd~$ldL7&q2Xgj|#8&kQ+s_S5lt==ih=+UsOf%J!) zC6&vQ;>CTU(>p1i>n@1i+N9OkYP0UMn&2nnTP^#v4T8!+QvJava8->-Y4xZHxIeoI zJR@x1@{cPV*)7)UGYM<#W?m|}w(e_fY;U0*?b|r*<3f1Omlh9d&&mi`eei#<8@GQd zd%8#>fbGuBrUS0jXmZ{A`R|-vWHa-F2-}S{#JUCc9R| zap{YF+;BOJZrQ1dJhsPXtn0nSm|Egh={RiHd|r0K()(NS=sgMMO<+vP33)uC7DoC(kX%lp5&9Y)qa_zFKVd z^z)7*oQrh0&E?gTqG?~?nQQVWm(%lwMwW2wFAM8%>gjG;v1TWMIN!O(0iK8SkXj|a zCW^<)z0rQhbzrc7ysaKH_C-T!>qPTXEMT8k}s?)!MbrUb|9|&+Tv@4?X?%I z^8F5nZgI8rE;>0?Sn697xVS%K<=(-Y3cSv$lnRcCd};P?4GY~hZqaXz+3;TZg;<{1 z!HBEhlkL56%A+AG7v+XaCCXJFSIbTBxbke*rLe6{>mJ9aA7Xnidp5Us|y6C z-_zjtnmt-hY;C~T{1;1i=po~$5q>b17cZ_U^gTv1s=39j#MHODnZmKIxQ|;wu|nCc zs%0?X0K<~rN3ShT%GRHL82BkyQ%m4&70ZkBy*t$o8wHBywPZHY?48gMKK(7r?Vy|E zmkzcoJI<7d7d^Dbdye-kdHy|v;KJr386 z`@UhVZ~0~S5tYKu<0jNY;pa?kc!Q+cHMr9m6gk&xG7=`Y#uln%J-uJqf4rkpz#w|y zC@x>zvX08AYm)!lb{{RxW=G2r!u|~%uf-Rqulv!Rc6;h}0e5-QcP`hhazh$h1Z@Jg zt=b|ZAM)z1eN??7m2TUH1#6`WR|%QDGr8CKbV6^}hP^nIue#m2(f3AH8sZ~AyBm$P z_lIzd*)(c=%@rAl`kAR!{UXFC|B29!+K2*)EqkhrcpVQrwk*H6FnAT!JEd`*%G%`Z zO6i|BT6_~alBRe4V%4N!h>x+p$-WxiBNAJB-25AGWute6H@#c|-bp}5(QERR;z_%| zgKH=Cmm<+E=RXc7mFBg-yk*vPZc~L)g{xzAE<&(>m+%x^+5QpGJ#S z#YdMuO&eAgyUSOd# z_}2Y%a!qF9W>r(~_QYPOc)a73OSscS_%f={3mQRX9XEakf9DK6$LY8|yvTd)N`Qk}2iXP#+vaZ!)f6f2SLn-Y_hrX9 zN}WIDpwixT8onQlRjGNg@UzeL2`HT3L>0eszp&6Uc z>HyzimzTeJtyw#l$qQ5J*rmP_Cl}BUxTjGDQ)ZZi2SwdjAV!#c6`O>k693Tgrt#&e z&g5J!YPO4m0#o4m3w9TFW&I%DL&*^mvhWG}xKo*rult}E>eFf`Wy?Wd~X5=bwq<&!-Px$hTXG>S8im5zXg?Vo!y~C2@wUZO7 z_DYm)r#Ti@E#)p^&EQTtTkA?Mz?vf^Q*to1kZYVb$L}2V`!PADV`IzDRQLsTq%4^* z+D&(8QB|oJn17uieBTYm#D}BT2t2i*BP#E|bdsZu`Y1=X6cT`bSG5 zwQ{eBeso!MY_Y3AD|k<3Xza?kw6j_rTe25?YdS8Td}-9xBi3p0naIK#Dd9oE2NJ5K z8`&j8#;XFIO~QGEo%%(3?tcpq5jjiIXt&)(>e^cIskrD5I9w@?l`&=be8B0GTmpeQ zDTmd^_TmEYtrxm9Ycxdn6((}>6DHMS+nXe6Ou>o(mUWs7^rndD#9`aB@< zq?Tr$!KS#kMoN_r*ERMV<->E1_xPN55xxiRV;nmW+X~o`| z@AQu~-t#h~_))ymbLSz;7d%gIGzCrP+qd8Dq;V}~yHUE>N%*@${KXOcBl*e=V$2)M z8*Q1`XdgHa-F|soYQ=}8sb@#uaXah$G!fq9ll9V%LTxJbzW5>4mT(RnE*=-co|oM4 zuq>k8{gptuO8&E(RIbWtmX)zJ!u8z|H<@=3Cd*=LQtX%xR#+VUbYQ8T$C~R#(y}%7 zT2kWAxyr7b8S3DACR1u-NDx~-8g#*L1(l=v*H_Y_OuW7MS^OCk5}Aqn3vu}KrxsVp z`M*e_@ujM`e@NbPlGj<&w&0vhVx#O16)_{QX6tSQc;sj6rq{p^K36O~dR%4VTi>(# zRGQRwbo z-6h{wQ}yA<+pqX{&hfaoQu(lbJ+)Pb#g{zy?}$3IVGE()s;2D5md5VQnnoEC7xy^H z>vx=Zv37}%;-^xt(%W};t-aORY^|%Ap>?9VFihylOa8+Ac8)6y3w);6^jtb4PYAtj zMqm9TO|-+wI$|uW(cy@5P1K1uQ^FhFPMYq%w1Va#b*Q%Tl|ri@yFS0Zbo7``ui8MS zey$70!gEV`w{N~F$fXcswKVSafymJpP7y;>#@AnZ`kc$!#3$BVpoW9P?h=kr-AKBpP`AGQ6u$C%p6x7D-p z$)#)>#|?pnem~5VYEpxru9n=|{g|iNQ6Ts20`<)qy4M1q3)IIhHBCQ>j0uOI5cU?i zoe)%JjQOzcv)#p?_BB7hrsgfa-1ctyuKE&V-Fc#z)mIWeKO(+^nohy9d4+9HjL9xB_va5>pUE=5E{T3)J~re|S8BYAb>|JK ztGwZhxzajlwRh36Yq4v7zaM|mx<&reNvma}?6Jb{-#DrjwJuzE`}Tp??7M3wT$h0R z(+F+gT^`PNR8~h2#{|aUoS0s5P3sQdquj^p)-TGDjcr*n(4D#3?CJ39zVJG8#;(wX zK|HvY=rff=2?^9xg6+Q9l9LCP8LA&I~$_bM%C$+knL`Fgb20FpBdnhU%G2|aT`Sb@(y}%mU z+@+y=W0xGynX-Lc!!O%lr2iAFZBk6hiW90%EZ?GkS@#Lut;#dtm7FavLQd?y>ASN^uEeAg%Gor5$gqfaBWjT$W9 zxo29@T?5yq8gMEHKAIPGF|a+kWwY%@aoj~23uTMu0JU91`6B8=I3@yNl0DX=ZZrAx9r^4?Ia zKDxrY|3b%l>M-4qhZB#5w_opa@Dn!b*Dj{JSSmUi6k(#=;=jMvKr+Fr|M198t_0TP z6#BfEdulxSDz1oiJQ$)ZLz#ZLY53(Xg2T-+_>t|Rc>}#@C-ED1)Qg!m)v`0Z*x|a`di!4 z&NUPv(u_8@6ekOp7qZJ4yu_UxqbBfTT$M&+HY8i~1y<%hm6o*a_)lp0xc zKK8NI*XOZq10nm_Kgty@+sBYn6Lyw)nL%f}0m~-y?zjaRyoPrh7BkveT8OC7e4L(( zZ~bADrn;DcZuyzJ!Yks6*$3GPGKPWIKlc*KSCl>JzaAC|1^mZ?r5Ai& z-iyrhVtF)1c}$C&hS9)abB*+)y<3yhGPNd}v&*&~csVKNcrAOO#1s6!T$Q4b1^w(9 zUyT;<8*Gz5o}9>V;7ZH$tfmU(H=G;B^LKX$L^^MIHxXgFXS{l1k=Wfj{p&wF9pKtv zfiV}|j0t|Waa^F3qa2mR;}5~do}YSruSBH0%t6nrt3fR1ju@Y7n8h3O@3KCxxB0Uy zSKJzKeYMZxHC#1SucL4d_ms*S-X=$kxI7Ih4^iz<&HB{UvS*X(rK-bmVw;zl`X*%1 zBtCV%{(O;s+!pSY4tqb?5o1Lqh!uUsHT&SY?Uw@0dK|=hI&eNMo@D?xwprm$wqJ$N zWZfR7E#G_&+)D`5!Ie_eZ1C=U3C3?fIpCV zx!&g+2A_MItq%yAcOLr zP&>1{6rSrVYMK4kdkN;Xe|*8n_H-NTsT1Jd#KW24KRb&I6!NZ&h}lq2Of zlx%l^+Z@hoSX(#k66FodJ9;_gcos(-;Q-6p)i?VJ6wKNeT7N8i2cFHQA4~O0y@RLY zjdDbA2n=M&3-}=`-()-8(wHetRI)lrVXLgi(H3n_2h2-G?5=JdmFUV_vamf zK`o^P_HpNfjD({uoq1>PR@Br{U0%_1F;;H9b^bl;L9iC~&;r6mh0vNwuWP#=Mb&e2 zK9HAXq1b8`>@XM??>wp*`SIDsNfUvPwv6XWrr}Ymb;fP+T>)Z8j042dnwpt;v;x8| zdNJ%M_g~F=j(rWqh6iP|XUq96!DlzPSTu`xcDS+-D9`!0YOiY+?qk%sd)|ME{EOg( z)mIWCmGb$0Hz%HMa4bW{88rpwAGL)1)ec2=;1}J!V6D{tBe~x7Zf%$PRjOBVr+3Ay z9KR#GWf`F&8ULcPeqCrt$D7z6uN&5hdM1pNi@xjf{aAM~;BJsvh*|8r@Zr19#jaH@ z2&359oA2)O(~d?d-^N*`rT_5B_@P_bn|1qENuH7kOyGqei&5vtlH1?>e0t4Bv$kS` z`sQ<0k(N~D1R9mXcE;;L3m#sf$&Hn#?K*j{OV;&Ky%6OW zb6mUWt4)`r@S2^^?y|39S?}Lj6UeiZS^ZRXS2y*ddip(*Lwb3V?Bb92@@w_7uFW~) zeV`>h_x1ao%{C5{@}sBiE7u5{eyF7}6gVUj^E$c0F~M z6`7`YkKTAkuaIleAf(4}REh`=yjsaGk1u`9@|?+MS@J@>xQ*yfKIfd^pn}xN3lFQS zBN}~6URM@IoNOyR+Sa=iJVTWtYi*+Gq%5o0GG*#ldp7Y}Rc|(+Q#_#)B*)jbqCL+I$_ZBLApwyLDB<}^!ZW!OKmS!_SOLmjKT@u#{slARF^ zHY)Xn^d0)gc1u*T;eswfr7+psy;_o?XSXfABShcWp z!d#)uu!7d8cva+LbLC3yD?jB8{P_vuc4?+L!|KAe1)*EwiYK1AdOW=1Sw75YTTrpFnkIr;65USEA)aj*Cq!R})$+di0q zd)fMNi#1$ykEil>ZK}7CvUS-}cl2P7S++oJNsUClIoJBTYw@bGHx=#=o)?LHU*$H! zyyCgX$v6ecgQbXGb=A0U7P%dxR!L;c-N&YCIQjgWATpogbv`(%7oBlgmec3Cw0{U! zwB_erQO^SfTUMEt6u5tCQ4_pWpQI<$CoD9zU?O|LN8v*^zgx#%UNfF;e3*X4xm6*H z+yicC3>^~#FXC#w-Wmd*#kN_=*qK1jp>NqB6D(QzZPA$?FQ=}J7jM#xz4jI;=~AyV z(&zf*N}$roF1TP3 zL1G1_Qk{LO+q!$-MC=)T+`O+!iCffdc%37-KDR%%{k8c)(YSt{S1U}{@obIxt&U%Z~hFFa)T*xSV3<0oiLJ#W8PXQhqq&c5r$ziL>lf7ROdO?{7W z))yP?jwzn&ZW{LV(lsbM*Iun%?8kRz`H9Ezl`ap(zUOnU+JDQ0E5s{5yz~?X`s z;Q+%rzE**1#&q`7R@)bSpFF*_C*l~@imS@ZsL^wTNoOhuoGsu&n5Ez1Pme41NylEi z&0QWjJ|=^&i&6<5G&}lgU4h$Y6)&)^YV&digYIrgw(yOYS=w3TSG{WtQqMlif3?%+ zHppRe6z6$%P`>x~zC^iMz*^4WOk-zCYz0m$>haL9)JT2J16A-0+gNqE%^Uk#4(*Ss zU%jv?tl8Cam7%V*`PuRC34y1|L$BzxzEIN1K=q*upLz^<{r3z8 zCwAb@?k?Q?qA_~ojjz0hdLaqaxreR%r<5v>o=TeRQ32=IQoYq5HxPT8j1D(Se0h;^vNCaef7L2_--?v*COWzN@rtMHU60vW(>i<} zKl-wL=a&N)a+yS_TPZiJWF9-q^Kb)|-d=kJzQO=+vvdUZ1FLP{o1OP>&=Vk$@MiM)YFWDB5<*A-G;@-?l>vB;v3Hz zoicdGGo~-+-}OlOLYkvAb8&i-OtDDFy6AB*>7WF$2p6Lo-=ml zb_DG~kL&MjbHz$hINz`6^)q_c<3`{6uH@}t?zq?Svta$gUZUyhD@*+LGqi;^!)08dG9w#h5&l#VQ-uQ?&FIXk$QJDNtZO3{l zKW(i7h8@c+>~^ya@6W|6oHi5u_VLB4C&J=ucNwko{IF6#Ns}rg)iQLnJr!JhwdZqe zSlh&UiH&B@krt}_9NU192rjLR(%iR4b&>>CdEHyCExNFsV0Se&=~Uz4)w&Fcw3jt2 z7!Kw=mebJ{;&0ETJl42AI@2S({>Uv+F}5TH=l4#8GRlSTA{Eb69Z#RKH9x-ad}Q>t z6;+z1m3l$pLN^KG8d}R}_}{a)MTwkuu@tB{)teQemdf$wLyUBJ|02#j4&{bT>LXFS z`zX~zEUqu>syjy6+o|(;^bok8wHdckjX5HNlihWqSitFhCgtWAo^_7fJrG;scnP7rW|S7{iwi4nC>-DnWlimk z=Ub>dv@oDypSR+fWc-Jr6O)fLT&~M?j2+(X-<2D4pn8*D@%IZAv}QXvw$O;PSN=H7 z$j-nnziZhPi+tjz*HK$?OfgJ3L*Amfx8Fe zxJ10KPE0&3(5V`z@0U5`CARByjjw3Cf}wHSM_P}mu*z#HpY-%7Z)xA=Esb!k>jT#e z(MN z`fkA#1my-z)gse{Z`i?e1d4DD1E1+k#5H0+R|_m+`nvhT{hd$O@JpU^h?8F&x8&d& zb9nFkfr^j!kbCD}8Y_erGbcWJzo_Z5olUPi{YkKHW5l`UCPRT- zpDnyTme+n+!VSrgHqNYjIjXlhTy1t8kkB%_5yfz=e@tF#kbiqCyUQ`HkY`b1&kB|a zCvH$Nc~8Gi^myI&ipH0P`-9b{3I>kdnmX|$%G9*`G+p{J9U(I-mnMOHtGDhftn}sE zDSDEu-JDA-3jEtez&m5yD7HD=yb<8nms>fI^11-eeCmQdmpYxX+U+R?+HJNWvKR0h zmC7sX)V7rPrytgDebcO!G_v66iy>;U4XeMa9%B7LI`8Qkb*R50Y)920!D~Ny84@cv zDiaQ=(6z^~q#Q`L-ImlMeK<<>x{aO722S;#u0H)7Syo9$gb}NTk+@ohM3lEkI_??>6Uzcbm{>g*Cy)b5a+@SDE zFRguopOxlJ_`?nd`wgCvUKP?wFYMoWOG&iYELV(RzB9f>^2?R)U)j#}HGRuFP{znH zzICaw)cu&PIovU6CB^AY{b|o4sMA)1`}!u2dev!K1%UhY9-4=)U1(kP&U%~a;qNI~ zH1X6=-+vNy4~%#}64}>z$ zPsI4Y%hbQzdefNaP1ca4lIOzL%fNGPH^xfal+jIfm`L%Q-=(R*`HlANuvvJM_Z3Bo zj-mx3;LMfvwbo5Ew?d9D#dQZoH(jq3lO=#P=B%aTrc~OmE1%LznLHAr4z>}>)&Toi8olT}Hx?^_ z{S#xx+Iy+ms9q-8ZMe8eDBto?>~p5H!n@L86f(!CzD-1d6BYK^?V^&F=fc2wB1JHs zzcGH0KU;d`jnX3S6hbTW{kRWfGOx|e_FG*H+p$>>pZ#GZk2!c%S7gFnu*z3#R z(6Q{aeOTUnNya-DciHpnNB_EA;MqZnxcs-Yb%qw}+_k4P9wc7aUXx8`^S(#fpJ zJxn8jQx0Evd+OHI$30gXDjZGTCpElN|Hf`N7gqi=t`u)9E_luGE0mMl3=*e}+h!1rGQod`kq0}nDKmJ}rpy&wLN zNfBz6?PFxlqAZL*{bNTnc)#p37O@-8QtYT=3yizj^mz;`Z;H_e{g4d`jxVTg*gd%# zKG!v!A@?Ztu?X3`Jb}{HJ9=+1eo}LJ`2G=&TlAXP^H!E0FfMzXP6N-73FV2eRJfSd zc|Rm;`MGdmMNYXl`&8`2KHNxSSNh2(r^vpfZ`ZP2xmUJ@J_{2#5|=D_J|TBZm=J&X zX(VOfvO}%Q0s|f-?7-nrYQrz%8 zDj=il;Vs893@b7_ii}6^9KGfk?apU$^ZmvAJK)-~EO^!##i#6mBa7tUr<}jC`|IfK zP0m-tzGV)&Tq*SQTqAJ=ueNdjBH`q!qW0HDN1L=V>9n>>mIRLO;j?%sMQfz}8Hck2 zJd(3R+9$l*>^d!yBF~*G^`_1W_uU>kG&#I}_m5AMc$ciUUDs?fV~3s`+To2qh~#b_ zKSC+Im*YyWn5kLVHdWfjZIfb2a`rd*I30aCgxlC}wpQE~>Ua21yCmnZ`PG4PO*4c$A;wuSio{ULW73!Lp|5eumhO>p!o4D8;`GDt75H_hDxd z=)YK1_-O5o&_u5LWgCEOxK8O2A?W%0(yu-%*lDRhrE#`UEEst5%uTJ9A>(?~DIc9U zeh#OB@WFf1ir`sJ;MIB#;92tC;8{+2`(qthp5@m`!Ee%&|ZV{J1gDsVUlaIc7@ z>Fz9nsq`~4RU9U@ITKEg4dW#BHi@ZKuL`ei$j0w`_RJ$8<=a)YwjbuLTiJbXZpym5 zFLEPt-by70oVOZY=Q1^FB6B_aja25d6EB7*skI(ly>oi|#%G(l>kOQmEYhE*J;bcJd)Dyasl4X31}Sa!r}G-9$$;y zxo#b$ICL~4-E-CpvUn94vRM{lzXS5${ruU7h^WaHLePS);=E2$4xh0c=!IS&LdBni| zEV8)8tFshisdqoG>iHt0tWGOJYMQWSDGCqqc0nc4m!MxNAw0l2YhSAWGaa?Iisonjcr_sma3?%NVOzqz+Z*gWa z3BP{XICs*mZ6;%-Gzoi@ot-*qPN|JPZb90g$;<8jQn5Ph>aBuz8aDUa-zTo=eS6pJ zsfxaVE-`F3Aubb*=vsW^BvS3$iB8;b7&xag5P_U|9_=dRdFiZ%S-t=lI*7d=mtv$nREKlPF?C);}0gSs32 zl&>G|V+YU4bi|$BBr)ZDSF&-bseY13&Z>^bjbbTf_eD<@9&+rs;?kRl$LHAkgXb7T z$L&^oz`mzeq@wAy{hiRU)-Ru}OE;J?5hf|yT+M#6QA`ZIXfAkg|6{XR%&2I&u-OOo zvKyy@*f(_yA!^!*hS^_o!FO6 z`&H88bcPX|vP0E38N6xF>ep3e*EIZ-1+^u`aw^^m84$kE%;wH*3)Fx(#+i3JsKf18t^UGZaG9|tSg!-F}DRZm*U{t^> zZSmc@=H}`x4GH{t+i@k<-KY=Gvrw)aljS5cl61RU{~Dyplpi zzgQbo{Ge*5YS4N9@K&mhH${vPTgGbqqf28vXGFNl{3Do!4>u{EKG(cut98;Q;IGfu zr;YALd|uT-LiXqlAsdY?S(-PahA!`n(9o;pVW~kgHeRG@7;R&BYfd_SDlJ zRtwcUbMlAj_=8pOeM1hshlH~TjQ)oeUwQgw{?Oujv(9ZYczuCy;||$~lG;JM=83%? zz<=Z8B*Xk$#cwY*S!+r2AwnO#zb2fXf5ka&Ee*x2m}geRxr_qHHB|i%Vv_kzOpGRa zpS~vN-M}g{+!7tWQoY+X@$P~X9ji{1vfK7N8Zk*wtCs=&k;cWCB}AWfsLuX$M&t7S zw{C}7);YP{GTh*;({dHRaO1T+e7ZMRhtaVCJr?h{BVo*$3<29%*92!XuNT!yHMq9} z;D5d%YGdyRrV8NwY&hJ{DR_R?(%r+_+gj9;{3!UiA6O`GJUB`Ujzseg(g4wv;G2>H9y`;h1w^U^pi1`-G=hKz_5ijJ z^1|UHK}{|A`8&?m&R(vN4)zo|%EH~&64vm{t^x1s!r{Q8;8~pa0M4+bGqS8e4bvZ5 z7z${3{y=jN(1`zm<{hAkpQm52z2kres^fQkhctMg-l9Lyhyt4UKhS6Z8uLH2#~skL z{edP5(1iU#)=fa;_XnC{DC-Y2O@Joz4>X?vjq4w1=s=&$|3I@6&?o~M=uHsGGew&8 za~+WnmPU4(#savqy$5c(?^qfV8{8(qr*@w9hyp%<&2)iHp`-`^)T+c902mj>7 z2=ohubr-%!syfC*IPTzW=zcmeE3bm(LO_2%k2!w1&Dt;~7!1NSq4{jNX| zB5UrY=Oo)Km(CQi4(PlE##L~TLkV;o?Rm?4g`_aNcjO@B&(5;0-MAd zB4=6*XJrg$K@4Ya4CgUm5OU5;7|t6ooOv*uT``=y|AMm|hBF?+*$%_GVTLnt(h`9^sF^8lfGhMJY}|;RCUO;pUW^nVZW{ME+(~eh>zqejMTv1208Q9DUSMX1 z_}uWEd`N6?cK{z!B*CG+gR~j?&JZAlcA6Fyi_=1SPF`En)9gE0Fcc~At%g4E;%<~W$ykQ~=dzLU5G{oN0=8GM1h|31gT%!cH+ zAV3MaKdYeexenAJSar7WIN)L7?lCh@Lha534uy&#pCuv8P`fjM&1tMo&s>mw=qGA- zWl#ZIK}ms)b&wX?YeWpS7cU@P46DfswAUoqgV-yBY#+J-ZD9^J5L@toU$FJ$1==DE z=n%1mKG=zs1;-?CBm(<}0&Q^Uzzn8E1CD z8aRQi7lQd;=P8IdtX}}ZzZBFjctHK1Y+(*)5LTAG`c`X2sVJCrY$!uVj7WVX5Ln2 z767Y2Iny+_>6*hhTq>ysQjY>YMRUqRcww=T2zHR$>%U;b6&wx30gN#r0%m=p{%!^8 zpae<^mRWm|pXm4}6>Ng00~&ttNk0A&Ro$t_Z@5h@4DIPJXA~hPB8_U$d^E(l3=g13 z^sEVf{nGboS!zZktV-ba4W~cP1*J{#6SOg3m$NgXFISh0a(KC6U;TGbjsz!G6`RQ~_P*pMiBh3~g??2P1 zxXwcZ+XQ`CTLray0MHcleDVT)*^p#+C-R-dE$FAp;AkKYV2lTRf{kAawEJqn9xCMO>+6#Qm>4T`<<3JM-|8lZ+cc1Ys$OcUMJ?ua&V)tKj zE+Po)gX3Ta?BPuLFF9^rY!GvbA7G923u+T6Vpc)(pf{*P@*sZJUgRg52chPWJUBhm z$IwB$zu1J@od;}TVIK`0nv!yFuqVB_~O+<}CR zN8l(d4k0!eKsADm0>~OuAU2@d;MaWd8J@F3;v{Yy^qlz@dfxXJdbR~L2n4Cdj3Mvv+DtCPb-(T?$%_W zo&qYVoyIfIFLen zLBFtb8MqfIT95|(MEJnE-Jm8D^cMU9>7W_2(2M$TAfPFlhX#u%S8#*~WKSw+RR0@9 z#e<`!TR7Lug&5U~fNqdB82q7 zW5eMRU^Q8RQ9=MD1tdy5g3ZDd7}X7cAS9|wK#`aNqdFYr&KK3;l~74=ByuHmABG34 zfYBy~L>VXHL8R!l>hlj{)2Wm%bsf#eQ_B1(jWC~psMkGapqg-v4v+5+30 z|6=zda5xgR$lHQ)h4%Vkf!YgRv4#jBFVJ3Jm{5C(lJ6vLL0gEEY{3I+u?>f|U<7>r zG~N&a@I!3z`*YpH^VkB_^H!3c!P*5gI}2wkN9)u>dsPWJh^`8Ya*MY~Fzx z6e*E_CUZ_2WIvi^kX}HIC4ggBp@&a@{}+U}fg^xOz(;fX*x4T_!W%#(f^fmvyQhDn z2^T@IfaO;Yf{7q3nJVa3^5Rd85Ftso)X2}H?R;~EqjMWD^FA%B;NuvrG3`6{DcFlVojk;6}K)TU8DB18#! zfi^8%j@tD1nJANFFCM@ED-hb^G%(hmGSPcLfY{>qGhe@XY=P=Ig`{V2+Rx0!RYH75 z(O}cv8eDlr^!#fkIs=XbSVl;2bJIWycvc@-)j4A?(8*s4>dVTY9x|TOrocvY1NU@ zLc2dD*IO=Oaa9gnBb**rP{39BWX*vj({Yu+iuxebGqylmz!Z$cOdhZ?w-0iF z$-$p|kQ4MBu|*zM5U7Fiux%b&pn9Gl>Dd*yo8|0KH_i7U=qjS;Uw!ZqNm(^e9iTDT zoLfYqX#q64bJD;y=|~v`G{bY!l#pnczzEkBBqpq^QWA|IpoyQ8ri?_R0BD#1=COP# zNHkjkjq;o{RU{faKvO#>4bmqpR&#-6$mj6?CH}y;0o6Tcj6zqH0~#d$U~!0!CH9iy zk0am)9?twrftX)@1jW27K;+$DC=kqRkuV=Y(#dQ>IF@JxfJep>OR#|j5JSJ<0sns* zOQZu@plZEIKd?aUf5`X=lVS|_lYe}*&qHzBviwDaH z)Ij|x&!ZnS{xASKB+esezh|FGLiO|C+B`?jCFX8dpyc2A4(tTPe84#r^AK%VxWb(K z4xHpc#?QYWYr$105Pg&sJm3>rm%Ko%USa=poX-dG5c~l?ndA(JTTsugf1&3!fDX~K zC!F?sfw7alvxm*}fD7F~^!#g_M+9MQu@3AYcX<>)y2L$m#;Cur1v-ur2Wo>BnD5M6 z8KC*oI7&PWwW%=B0?e%B1=^Gj*aESs4AxkH9LBMB5EGC%WxCP^wE93>X=Nj8U zGHCagB)gvgHQ=P}F9pV*{Xh_6_o-j@BAd|e@CpL>0D1WgRado;D zU@{9Ev!MaU*bpZ;CNR##=1Qb3C^irp*d8^kEaD9i|5w=9@E6!<_zP?pMWEP_23nXK z8!7}88|J^oh5|4af(;EsDZhL|Y{Y~AKVicF$U??`AQ1fq8x9}|BG`a44QzqfsQn9U z6#WG@Yyq#CarkvG+959?pvHV2Y`D03*t<9%z>(}unvo$#LA1bGIJsi6kp$9^7jy!&i9H7Q1V|o^F4GgA`NHX{60wSUIFObklLISvO zFo!G{C2T-8N07inu0W!yAQB*m;MaUn0uQ=^L4z)2p`yH=r815L$P5F_P}H2#Ls`oGVod%l4X96 zjebD$r!4dAB8rVAfO>9hd?sPz0BAY3Kx{xke~J?7fDaNSOu+%zQ3}LHAg~33jZCZ{ zPy?|c{ukKL{0nR(175Q-h=tPuTQhi9E_yy^9Psd)2OFr}5gKSyQA825T8`S?71aHI z*&SWK3fc_*fGild$qO1c;A{)Q#vJj77&qYk`a!29+7gr7tbe*6I!NKIZKeq_MR4#CeP`A*^%#E(&yIXnuv`&Akmi>yWpUd1D;k$D7QMXVu8$N!Ubc=mvdvotM6 zx*0v=BjP(Y7W=J1cV-nkXA37-HPZ_l4?9oi{h(?_7O@vRXfNil7DlUc z(fq3Cxo8kQOU8o+O!s~IUIx&CS%qi|tmbE1huC*^(2L*o9nC5K!+uGbqxPC>JqZzG zOsM96Pb15MVuNDkEX^Q4G5z{am~Q})lpJ+sK>cJjQt;^~WHmD8GpHk?MG!^Hf0Eva zkq$?~WU{1qFw$YpB+?M2i~p1KW{mVkjC4Va^b;8Aa{nY9o{cB#sdO7gx;RF97)HAC zKS^)LNbkl-m%~U;!${ZsC+Xc7>2MZ6rVS}&jP$!R(uw{KpXu;Be@A2840_R6Hw)oJ z{OF>+m@7v8nnC8G`46$~3~<}&p3W2NX4;HqqFHSw_Wj~KeMe)$|FBYtB3cYlwEQRO zM={b3G15~p(t|M4&HqU{y#7wsFKKg(^h}KOD2#Nwf0FKxk#2{Po{NzlkCE>BPtxHE zN%FqCVx$*gq~~Fzd;gR4GZ^XK80qk=HCYQ~$}rOX{z-ZmM!FwHdM!qJEk=6KKS__o zNDsnDZ^TIdgpnThPtq@8q=#Xow_&7H&14y(zhC+%=`k4TmoU=1G18eZ(&PV0dOSvY zJVyF3M*0ej^wfWno`jK}ijfY_laOOx6eB(JpQNW^q-SEJ!-*rgbX|<}+<%gO6C*tr zBb^o_-Ec-aF~29zli$&?%}lmJ$2PMv8WFukXfJ@ie=Yyl^EhuHU|Os5jBRGxjE-$) zwVBxWym|VLjsgCM{gPrr?e+U{i*ylc<=@lD!qrfSet3U11?gNjsbBwTbOF~olHple z8Ep?~Y!im@IC!rQ@hGC^%jVHMin?@>qU*MCCY8k94NF==tMJ!p-TAKDAFMrJPt zb$#<_9!1>@9~5=7WD!x<_CG|O?q8s8rp+koX0@4!I(R|hcXL=2b^pVDNd=+y`aPng z$2#l{@Zm%#MvKcS8a<%}Xqng?wUS|jC(_5!Vuf$P7>V;#JE^LLs@Q8&W} zMcph}MAQxc4^g+_FHkqrW)yX^+Dt^9+&q0pt@A(Zmy|bZuiqm|S`fAJ?`dRdQEX7G z{B4FM_3J;Ot^t6DJj0HoqXHCTQkrNl&>9)S4C;tz8As9bpQJZpq~lO+pt7WNG17%G z(%}UqKn4!RjQ!@hGSZBZPK%Llh>7Q8%m2MARLer|&50{)hdNQbz6ddqhbOqgMVsjVyeoCE}F= zzxY2`vqbEBJ*n^5wNdb(RUlKrObsUfFdF}pl>v+CXb$-y1#@+Kj$S}YBj8(1*U^;* zHJ}EzgS^0%2J+nKN`o!rJBeFx6+svny(243ct9=a(k}(Bais?<7LZj0@?bAG;Fkhd z2n6HN6#_}W>_s-=3W1_|RtR8pNb>i1zyO#P>X{gO{bn>zeA0C!YE#sgr5jNspfys0 zD1OiynF-L9X(W&=D2V#$f07QL!$9Oiye?dek&z}yC-v60~#ivL*NL2W_q0@_(fj+UuoAHBG*;LAL$>gBIb({tKfoI zqv*0pnlzv6ZnBf^;O=I+Gn=MWdiTwnH=Eae^WOO0oBb1nZV{wbEh&CUMcWlYq#q=Z zq9TDtMXXRILOw(g2?eqCgM=C(AX0zlp8MzBH#?JRhSKc&y>rh!_x!u(-Zyh!0h(zP zBq$d^gNt4IqFUm{xLQJeD|i}ol8@}-88DmBoV8~jd=>YkKWpUhdw46vgU@PDFs+W- z=vFn!oc`XIH<{&GAZ6fRi{3>i6$j!hG^XUC$n?DLmn3B55-9*GmA}@JG&g}c<>jQkRaJBmL7x#9HwcOdj(3438L=;IWG%SO*Q z5@NkQ=z4PoA?llGb;Ys!JxCRo4d&U z*>+le0SG;Zwep+>qW9}q<^afql7*2?AbQ81kskrsYxDzR?;;S->HQ5LWK-^|+l>eL z8PA(`Z1Gpn=rs{@z6nI{Ffj6IEDgy&tbQH>vg06iq5-+pMhD1VC$kNJtUw;(y2ta| zX;$E`3ywVBAU^2G_H_}6+t;swkXD&S2)qSC&@TweF?g@~s=JNKj{!OD^z{G`{oP$2 z=_w$(US(tr$TT>!%+$xTK=cg4G%o|;UF!)+hqp8U*>-Gk2?*swJFYhf0Y9s;TR=8J zV3E7wgRURm4}`2^*V3($5<3u{2Kt(Q`NRDX10Z0+tZ-PDFF}rWq-<-v$i?D9L;th?_-! z41{uv)${KNalGy-kZFhJ9Uyvj!}5F(KXUpi`m!3^2V}zGvmeNuL-R0@6(e(AWJ%EL zd;6SLkAlY4>IxA3+=_dD9>^68!H(i5Kz7iV)#0CkOgN+X50Jjo>O=VD>yCqb637Ki z7M4#7hzC9v@qz3`D~lvRo^_CIY6W3!bbp6voRPi^M6Z{5T(1Gyc4+FX|p+qaytPXT!grm+$}3yoi`Q~Q~;>V z23CsC(`cplKzLj~0HWW{#>ksM8YMpPXTR#JH$b!FXyaWVmmLW|fM0RB-tsvhdM`y* zG68Y6UPp4yfM&{!i;&YmD0-~Dz5>L}KVJiaMv6vs=}bw;1H?wb3!r)3k?@B=t~jH3 zl{h<|`5U5fRN1g66!# z=P6uKCP5%5n{EKPX82I6=YX7ZB>XOr0(Y!7E&^#d$ZJ6KH?(;a{{S)p8n9A*-3x!8 za3by_K=hLu*i^k+X9X>6U$dY&2VQoavIs=~28#RoGLXxT=GTC{?u_CYAoQ`G@Zu2i z3J|i6mH8Jy-Y07bJ&Jz;IR}45(pKZzgZ=4gTv?g-0om`!JUsSKfyUaD*9nL(r`1EC zp%Ge{n?T-j#3K9*(^$c~|rKd{C9O^5ThL9^m$?0Z1Y7#ig8I85Lz2BAJKg65pl z>Q_Me4$U8kv!la*5@%@5YV1CoY;nd&NSq%4vWb6fTr2~z`XQPPAQMij7l8B~d3J!f z(S3pV7zw*sh}gzQ$}^!X_&R9xHw4(DZULbz$~L|4e(Yp*!uDWQ_?e%qhPl3sl5VR1 z_S@|&T-R4|l!W^4ZbrYaQyidL{U_0g@^u=-QC=ACS+dI4c>SV0^gT+}{W$70AVn`M z!fcL@W%clOR53{Skk$??9)$8d1%VwgPLKO5SM=Wca^v=FPA6p3Wm7bGX z*zFhDq!4(YU$jng$@>B;qP5!+fw&00Gkz|Dphz*mD$~wZB3G7l9Dtx&vF(Fx&rkf- zu;b(9u0t*Kh@&Dd@=lf(qMK#`)L3Qh5~82?LYEKHQFQelWIE>OBfEmQqS)#W-{+~L zDxfMvhXh}!FFx{^s4q5KGxPIewsvg!5wS4SnyW1>4bh6#IBm-c+mAz1_5e;&RfH@a zcSHRlT9s;t@J_1VAUJf3_mzg((>(lGlq|SjbsHyB=Qrs<7!yX7kf_9;i4YZB<)|wM zmI*Z0+1ayab=Mr5Lg0<7BEu`1a|DbigPpJfS5@#tvv@E_2gQ)a=mrX@xE?f%0|$aL zekO{{kBYn=)M`t>4z>L}3PcJ?L%&yViPn^;HIE2vKy^}Oa?wFN`Adyn?HG+$5UIJ#hCtw2Bbkw3($8S>i5`qd9jRR=oj!-n_5PiU>a-|l}@KOaI-eY zBi5&1>pM8jn1&|S!Y#JWWS?GtI#s?OV zMOM~KbraE=Z<=&keq+VwsaG7VAx_bZf$VJ4nm7?9okmTW%X2S}Z7tGdrFHX2SSY#^ zX*Sd8;Ivh8Mpm4?c|3)wE1YVZA-V@mqgLv>o-d|}_L_Qfe2`^fQb+>BH%taS5%r|c z#Zf!+vn|X@JxsfTrXxSbeRz;OvTPg$siS--&9FYXtx^5 znG^5$%suX@gn%;(rq8?a+@v9RR-~sZlWJmBZ}PU6@}LlVDWiPy$l=3>4l6lLJh7cQ ztRUH#$Uz_=3&nZ;y=9=Fh5U0^G`>iRB3{ zWX_%-*I_L}lVP$RWoZJ4Y78zZ5XlgEwv?>!E*rmNyYTyw=;dM~N(7xj2P3b;eMNjg z91v5OY?@)QtZSW`E_F_DrDLlYw<@sZ152lNt4gBs++6}t>aCI*WZ5EZCUek;Dv1jq zdI>D_B8xPEq`+00$sdS?c|R|Zdt@G_!R0bH=BygnS+n0|zUTc$nnwHpsz!K6K`ynK zBhQPpOH6__x$%V}%jQ5VXa{j9qMSA?F(Q|o&QH#eStPyxRGNu=(9X9o5B9`5-Y`U2 zUYd-If#)Jx6|Ab+Fh(JI9J4?Kotl)wE-+pjcCh7{>r{lQ)Q=w>gxMBkHLgVFSjQ8U zH0egGNT6^T8FA!q(oazRxS~1Ht4Vlc!*Gwka=RUnZ|joabl^NHbqUD zpgLda{4m-Fwb|L@`$`t0k2@(!VMnBgGO_&^;BU3o)TP$CVqLjUA39{Hw<`}ms zQtmdSDatcg(FnP7XGTh!>9q1uD$|9n4$58a>iCs5f!v%y9I28Q_0(!2El+7tt(K8l zX~}~*+0W&KXJ*Wb=TxUN?Y#8Z6;&ve>ukAB$!qE}E#p%Zw(5cB_?h2~Trc+2EUcZA z7(sLLIjIok9IW*0sMRF1UG*k&iy_fWDF3g~bXrt)c0jMBE~4iYLBD!ee2U|U36 zG$>ir)%RGR`rpKQ;^F#wlrmusQl0s zm3wV|=5bM9Xq?1XBWCMMHOd^18_9Q{+*BQhjN-1ebtx+IN4XN^z%C41q+e5&Qrabn zoLY-SF7r0oPj9~|9nt!)0#OVjPH_+=7>~-^yUqB( return None; } - let displacement = instruction.memory_displacement64(); + let displacement = match instruction.memory_base().size() { + 4 => instruction.memory_displacement32() as i32 as i64 as u64, + _ => instruction.memory_displacement64(), + }; let size = if displacement <= 0xFF { 1 @@ -206,6 +209,7 @@ fn dead_flags(instructions: &[Instruction]) -> bool { return true; } } + true } diff --git a/runtime/src/vm/bytecode.rs b/runtime/src/vm/bytecode.rs index 5dde4451..e9c02989 100644 --- a/runtime/src/vm/bytecode.rs +++ b/runtime/src/vm/bytecode.rs @@ -272,7 +272,7 @@ impl VMWidth { VMWidth::Lower16 | VMWidth::SLower16 => VMWidth::SLower16, VMWidth::Lower32 | VMWidth::SLower32 => VMWidth::SLower32, VMWidth::Lower64 | VMWidth::SLower64 => VMWidth::SLower64, - other => other, + _ => unreachable!(), } } diff --git a/runtime/src/vm/handlers/mul.rs b/runtime/src/vm/handlers/mul.rs index c015af9b..5603d911 100644 --- a/runtime/src/vm/handlers/mul.rs +++ b/runtime/src/vm/handlers/mul.rs @@ -1,145 +1,46 @@ -use iced_x86::code_asm::*; - use crate::{ - runtime::{FnDef, Runtime}, + runtime::Runtime, vm::{ - bytecode::Flag, - utils::{self, scratch}, + bytecode::{Flag, VMWidth}, + handlers::semantic::{self, Compare, Condition, Effect, Expression, Operand, Operation}, }, }; -// unsigned char* (unsigned char*) pub fn build(rt: &mut Runtime) { - let mut epilogue = rt.asm.create_label(); - - // push r13 - rt.asm.push(r13).unwrap(); - - // mov r13, rcx - rt.asm.mov(r13, rcx).unwrap(); - - // rax -> width - utils::bytecode::read_byte_zx(rt, r13, eax); - - // load r8 - scratch::load(rt, r12, r8); - // load r9 - scratch::load(rt, r12, r9); - - utils::width::dispatch( + semantic::build( rt, - rax, - &mut epilogue, - Some(Box::new(|rt| wide(rt, false))), - Some(Box::new(|rt| dword(rt, false))), - None, - Some(Box::new(|rt| word(rt, false))), - None, - Some(Box::new(|rt| byte(rt, false))), - Some(Box::new(|rt| wide(rt, true))), - Some(Box::new(|rt| dword(rt, true))), - Some(Box::new(|rt| word(rt, true))), - Some(Box::new(|rt| byte(rt, true))), - None, - None, + &Operation { + effects: vec![Effect::Mul( + Expression::Operand(Operand::Input(0)), + Expression::Operand(Operand::Input(1)), + )], + flags: vec![ + ( + Flag::Carry, + Condition::Compare(Compare::GreaterThan( + Expression::Operand(Operand::Output(1)), + Expression::Constant(0), + )), + ), + ( + Flag::Overflow, + Condition::Compare(Compare::GreaterThan( + Expression::Operand(Operand::Output(1)), + Expression::Constant(0), + )), + ), + ], + stores: None, + widths: &[ + VMWidth::Lower64, + VMWidth::Lower32, + VMWidth::Lower16, + VMWidth::Lower8, + VMWidth::SLower64, + VMWidth::SLower32, + VMWidth::SLower16, + VMWidth::SLower8, + ], + }, ); - - rt.asm.set_label(&mut epilogue).unwrap(); - { - // pushfq - rt.asm.pushfq().unwrap(); - - // store r8 - scratch::store(rt, r12, r8); - // store r9 - scratch::store(rt, r12, r9); - - // mov rcx, ... - rt.asm - .mov( - rcx, - Flag::Carry.bit64() - | Flag::Parity.bit64() - | Flag::Auxiliary.bit64() - | Flag::Zero.bit64() - | Flag::Sign.bit64() - | Flag::Overflow.bit64(), - ) - .unwrap(); - // call ... - rt.asm.call(rt.function_labels[&FnDef::VmFlags]).unwrap(); - - // mov rax, r13 - rt.asm.mov(rax, r13).unwrap(); - // pop r13 - rt.asm.pop(r13).unwrap(); - // ret - rt.asm.ret().unwrap(); - } -} - -fn wide(rt: &mut Runtime, signed: bool) { - // mov rax, r9 - rt.asm.mov(rax, r9).unwrap(); - if signed { - // rdx:rax = rax * r8 - rt.asm.imul(r8).unwrap(); - } else { - // rdx:rax = rax * r8 - rt.asm.mul(r8).unwrap(); - } - // mov r9, rax - rt.asm.mov(r9, rax).unwrap(); - // mov r8, rdx - rt.asm.mov(r8, rdx).unwrap(); -} - -fn dword(rt: &mut Runtime, signed: bool) { - // mov rax, r9 - rt.asm.mov(rax, r9).unwrap(); - if signed { - // edx:eax = eax * r8d - rt.asm.imul(r8d).unwrap(); - } else { - // edx:eax = eax * r8d - rt.asm.mul(r8d).unwrap(); - } - // mov r9, rax - rt.asm.mov(r9, rax).unwrap(); - // mov r8, rdx - rt.asm.mov(r8, rdx).unwrap(); -} - -fn word(rt: &mut Runtime, signed: bool) { - // mov rax, r9 - rt.asm.mov(rax, r9).unwrap(); - if signed { - // dx:ax = ax * r8w - rt.asm.imul(r8w).unwrap(); - } else { - // dx:ax = ax * r8w - rt.asm.mul(r8w).unwrap(); - } - // mov r9, rax - rt.asm.mov(r9, rax).unwrap(); - // mov r8, rdx - rt.asm.mov(r8, rdx).unwrap(); -} - -fn byte(rt: &mut Runtime, signed: bool) { - // mov rax, r9 - rt.asm.mov(rax, r9).unwrap(); - if signed { - // ax = al * r8b - rt.asm.imul(r8b).unwrap(); - } else { - // ax = al * r8b - rt.asm.mul(r8b).unwrap(); - } - // movzx ecx, ah - rt.asm.movzx(ecx, ah).unwrap(); - // movzx r9d, al - rt.asm.movzx(r9d, al).unwrap(); - // mov r8, rcx - rt.asm.mov(r8, rcx).unwrap(); } diff --git a/runtime/src/vm/handlers/semantic.rs b/runtime/src/vm/handlers/semantic.rs index f2bbc55b..2ce1dab5 100644 --- a/runtime/src/vm/handlers/semantic.rs +++ b/runtime/src/vm/handlers/semantic.rs @@ -1,6 +1,6 @@ use iced_x86::code_asm::{ - cl, get_gpr8, qword_ptr, r12, r13, r14, r14d, r8, r9, rax, rbp, rcx, rdx, rsp, AsmRegister64, - CodeLabel, + cl, get_gpr16, get_gpr32, get_gpr8, qword_ptr, r12, r13, r14, r14d, r8, r9, rax, rbp, rcx, rdx, + rsp, AsmRegister64, CodeLabel, }; use std::collections::HashSet; @@ -16,8 +16,8 @@ use crate::{ #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Operand { - Input(u8), - Output(u8), + Input(usize), + Output(usize), } #[derive(Debug, Clone)] @@ -77,11 +77,10 @@ pub struct Operation { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] enum Value { - InputA, - InputB, - OutputA, + Input(usize), + Output(usize), Flags, - Temporary(u32), + Temporary(usize), } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -94,19 +93,23 @@ enum ValueRef { struct Allocator { tracked: Vec<(AsmRegister64, Value)>, dirty: HashSet, - temporary: u32, - slots: u32, + temporary: usize, + inputs: usize, + outputs: usize, + flags: bool, } impl Allocator { const REGISTERS: [AsmRegister64; 5] = [rax, rcx, rdx, r8, r9]; - fn new(slots: u32) -> Self { + fn new(inputs: usize, outputs: usize, flags: bool) -> Self { Self { tracked: Vec::new(), dirty: HashSet::new(), temporary: 0, - slots, + inputs, + outputs, + flags, } } @@ -149,46 +152,41 @@ impl Allocator { .map(|(_, v)| *v) } - fn mark_dirty(&mut self, value: Value) { - self.dirty.insert(value); - } - - fn temp(&mut self) -> Value { + fn temporary(&mut self) -> Value { let value = Value::Temporary(self.temporary); self.temporary += 1; - assert!( - self.temporary <= self.slots, - "allocator ran out of temporary slots" - ); value } - fn offset(value: Value) -> i32 { + fn offset(inputs: usize, outputs: usize, flags: bool, value: Value) -> i32 { + let flags = flags as usize; let slot = match value { - Value::InputA => 0, - Value::InputB => 1, - Value::OutputA => 2, - Value::Flags => 3, - Value::Temporary(index) => 4 + index, + Value::Input(index) => index, + Value::Output(index) => inputs + index, + Value::Flags => inputs + outputs, + Value::Temporary(index) => inputs + outputs + flags + index, }; (slot as i32 + 1) * 8 } - fn spill_register(&mut self, rt: &mut Runtime, register: AsmRegister64) { + fn spill(&mut self, rt: &mut Runtime, register: AsmRegister64) { let Some(value) = self.tracked_value(register) else { return; }; if self.dirty.remove(&value) { rt.asm - .mov(qword_ptr(rbp - Self::offset(value)), register) + .mov( + qword_ptr(rbp - Self::offset(self.inputs, self.outputs, self.flags, value)), + register, + ) .unwrap(); } self.untrack(register); } - fn spill_registers(&mut self, rt: &mut Runtime) { + fn dump(&mut self, rt: &mut Runtime) { let registers = self .tracked .iter() @@ -196,7 +194,7 @@ impl Allocator { .collect::>(); for register in registers { - self.spill_register(rt, register); + self.spill(rt, register); } } @@ -224,7 +222,7 @@ impl Allocator { None => true, } }) { - self.spill_register(rt, register); + self.spill(rt, register); return register; } @@ -233,12 +231,12 @@ impl Allocator { .copied() .find(|register| !avoid.contains(register)) .expect("allocator has no usable register"); - self.spill_register(rt, register); + self.spill(rt, register); register } - fn copy_to_register(&mut self, rt: &mut Runtime, value: ValueRef, register: AsmRegister64) { - self.spill_register(rt, register); + fn copy(&mut self, rt: &mut Runtime, value: ValueRef, register: AsmRegister64) { + self.spill(rt, register); match value { ValueRef::Immediate(value) => { @@ -249,14 +247,19 @@ impl Allocator { rt.asm.mov(register, source).unwrap(); } else { rt.asm - .mov(register, qword_ptr(rbp - Self::offset(value))) + .mov( + register, + qword_ptr( + rbp - Self::offset(self.inputs, self.outputs, self.flags, value), + ), + ) .unwrap(); } } } } - fn load_mutable( + fn mutable( &mut self, rt: &mut Runtime, value: Value, @@ -270,7 +273,10 @@ impl Allocator { let register = self.acquire(rt, &[], avoid); rt.asm - .mov(register, qword_ptr(rbp - Self::offset(value))) + .mov( + register, + qword_ptr(rbp - Self::offset(self.inputs, self.outputs, self.flags, value)), + ) .unwrap(); self.track(register, value, false); register @@ -304,34 +310,76 @@ impl Allocator { } } -#[derive(Clone, Copy)] -struct Context { - input_a: Value, - input_b: Value, - output_a: Value, -} - // TODO: Implement a mixed-boolean-arithmetic engine! -fn operands(effects: &[Effect], flags: &[(Flag, Condition)]) -> u8 { - let mut value = 0; +impl Operation { + fn operands(&self) -> usize { + let mut value = 0; - for effect in effects { - effect.operands(&mut value); + for effect in &self.effects { + effect.operands(&mut value); + } + + for (_, condition) in &self.flags { + condition.operands(&mut value); + } + + if let Some(stores) = &self.stores { + for store in stores { + store.operands(&mut value); + } + } + + value } - for (_, condition) in flags { - condition.operands(&mut value); + fn outputs(&self) -> usize { + let mut value = 0; + + for effect in &self.effects { + effect.outputs(&mut value); + } + + for (_, condition) in &self.flags { + condition.outputs(&mut value); + } + + match &self.stores { + Some(stores) => { + value = value.max(stores.len()); + for store in stores { + store.outputs(&mut value); + } + } + None if !self.effects.is_empty() => value = value.max(1), + None => {} + } + + value } - value + fn temporaries(&self) -> usize { + let effects = self.effects.iter().map(Effect::temporary).sum::(); + let flags = self + .flags + .iter() + .map(|(_, condition)| condition.temporary()) + .sum::(); + let stores = self + .stores + .as_ref() + .map(|stores| stores.iter().map(Expression::temporary).sum::()) + .unwrap_or(0); + + effects + flags + stores + } } impl Expression { - fn operands(&self, value: &mut u8) { + fn operands(&self, value: &mut usize) { match self { Expression::Operand(Operand::Input(n)) => { - *value = (*value).max(n + 1); + *value = (*value).max(*n + 1); } Expression::Operand(Operand::Output(_)) | Expression::Constant(_) @@ -351,7 +399,30 @@ impl Expression { } } - fn temporary(&self) -> u32 { + fn outputs(&self, value: &mut usize) { + match self { + Expression::Operand(Operand::Output(index)) => { + *value = (*value).max(*index + 1); + } + Expression::Operand(Operand::Input(_)) + | Expression::Constant(_) + | Expression::SignBit => {} + Expression::BitAnd(first, second) + | Expression::BitOr(first, second) + | Expression::BitXor(first, second) + | Expression::BitShr(first, second) + | Expression::BitShl(first, second) + | Expression::Sub(first, second) => { + first.outputs(value); + second.outputs(value); + } + Expression::BitNot(inner) | Expression::LowByte(inner) => { + inner.outputs(value); + } + } + } + + fn temporary(&self) -> usize { match self { Expression::Operand(_) | Expression::Constant(_) | Expression::SignBit => 0, Expression::BitAnd(first, second) @@ -366,7 +437,7 @@ impl Expression { } impl Effect { - fn operands(&self, value: &mut u8) { + fn operands(&self, value: &mut usize) { match self { Effect::Add(first, second) | Effect::Sub(first, second) @@ -388,7 +459,29 @@ impl Effect { } } - fn temporary(&self) -> u32 { + fn outputs(&self, value: &mut usize) { + match self { + Effect::Add(first, second) + | Effect::Sub(first, second) + | Effect::And(first, second) + | Effect::Or(first, second) + | Effect::Xor(first, second) + | Effect::Shr(first, second) + | Effect::Shl(first, second) + | Effect::Ror(first, second) + | Effect::Rol(first, second) + | Effect::Mul(first, second) + | Effect::Sar(first, second) => { + first.outputs(value); + second.outputs(value); + } + Effect::Assign(expression) | Effect::Bsr(expression) | Effect::Tzcnt(expression) => { + expression.outputs(value); + } + } + } + + fn temporary(&self) -> usize { match self { Effect::Add(first, second) | Effect::Sub(first, second) @@ -409,14 +502,21 @@ impl Effect { } impl Condition { - fn operands(&self, value: &mut u8) { + fn operands(&self, value: &mut usize) { match self { Condition::Compare(compare) => compare.operands(value), Condition::Parity(expression) => expression.operands(value), } } - fn temporary(&self) -> u32 { + fn outputs(&self, value: &mut usize) { + match self { + Condition::Compare(compare) => compare.outputs(value), + Condition::Parity(expression) => expression.outputs(value), + } + } + + fn temporary(&self) -> usize { match self { Condition::Compare(compare) => compare.temporary(), Condition::Parity(expression) => expression.temporary(), @@ -425,7 +525,7 @@ impl Condition { } impl Compare { - fn operands(&self, value: &mut u8) { + fn operands(&self, value: &mut usize) { match self { Compare::Equal(first, second) | Compare::LessThan(first, second) @@ -437,37 +537,35 @@ impl Compare { } } - fn temporary(&self) -> u32 { + fn outputs(&self, value: &mut usize) { match self { Compare::Equal(first, second) | Compare::LessThan(first, second) | Compare::GreaterThan(first, second) - | Compare::BitSet(first, second) => first.temporary() + second.temporary(), + | Compare::BitSet(first, second) => { + first.outputs(value); + second.outputs(value); + } } } -} -fn temporaries(operation: &Operation) -> u32 { - let effects = operation.effects.iter().map(Effect::temporary).sum::(); - let flags = operation - .flags - .iter() - .map(|(_, condition)| condition.temporary()) - .sum::(); - let stores = operation - .stores - .as_ref() - .map(|stores| stores.iter().map(Expression::temporary).sum::()) - .unwrap_or(0); - - effects + flags + stores + fn temporary(&self) -> usize { + match self { + Compare::Equal(first, second) + | Compare::LessThan(first, second) + | Compare::GreaterThan(first, second) + | Compare::BitSet(first, second) => first.temporary() + second.temporary(), + } + } } pub fn build(rt: &mut Runtime, operation: &Operation) { let mut epilogue = rt.asm.create_label(); - let operand_count = operands(&operation.effects, &operation.flags); - let temporaries = temporaries(operation); - let slots = 4 + temporaries; + let operands = operation.operands(); + let outputs = operation.outputs(); + let flags = !operation.flags.is_empty(); + let temporaries = operation.temporaries(); + let slots = operands + outputs + flags as usize + temporaries; let size = ((slots as i32 * 8) + 15) & !15; rt.asm.push(r13).unwrap(); @@ -480,26 +578,17 @@ pub fn build(rt: &mut Runtime, operation: &Operation) { bytecode::read_byte_zx(rt, r13, r14d); - let context = Context { - input_a: Value::InputA, - input_b: Value::InputB, - output_a: Value::OutputA, - }; - - if operand_count > 1 { + for index in (0..operands).rev() { scratch::load(rt, r12, rax); rt.asm - .mov(qword_ptr(rbp - Allocator::offset(Value::InputB)), rax) + .mov( + qword_ptr(rbp - Allocator::offset(operands, outputs, flags, Value::Input(index))), + rax, + ) .unwrap(); } - scratch::load(rt, r12, rax); - - rt.asm - .mov(qword_ptr(rbp - Allocator::offset(Value::InputA)), rax) - .unwrap(); - let handlers = operation .widths .iter() @@ -509,18 +598,23 @@ pub fn build(rt: &mut Runtime, operation: &Operation) { ( width, Box::new(move |rt: &mut Runtime, allocator: &mut Allocator| { - compile_effects(rt, allocator, &context, &actions, width); + compile_effects(rt, allocator, &actions, width); - let output = allocator.load_mutable(rt, context.output_a, &[]); - let mask = allocator.acquire(rt, &[context.output_a], &[output]); + for index in 0..outputs { + let output = Value::Output(index); + let register = allocator.mutable(rt, output, &[]); + let mask = allocator.acquire(rt, &[output], &[register]); - rt.asm.mov(mask, width.mask() as i64).unwrap(); - rt.asm.and(output, mask).unwrap(); + rt.asm.mov(mask, width.mask() as i64).unwrap(); + rt.asm.and(register, mask).unwrap(); - allocator.spill_register(rt, mask); - allocator.mark_dirty(context.output_a); + allocator.spill(rt, mask); + allocator.dirty.insert(output); + } - compile_flags(rt, allocator, &context, &rules, width); + if !rules.is_empty() { + compile_flags(rt, allocator, &rules, width); + } }) as Box, ) }) @@ -546,11 +640,11 @@ pub fn build(rt: &mut Runtime, operation: &Operation) { for ((_key, mut label), (_width, handler)) in cases.into_iter().zip(handlers.into_iter()) { rt.asm.set_label(&mut label).unwrap(); - let mut local = Allocator::new(temporaries); + let mut local = Allocator::new(operands, outputs, flags); handler(rt, &mut local); - local.spill_registers(rt); + local.dump(rt); rt.asm.jmp(epilogue).unwrap(); } @@ -559,27 +653,27 @@ pub fn build(rt: &mut Runtime, operation: &Operation) { match &operation.stores { Some(stores) => { - let mut allocator = Allocator::new(temporaries); + let mut allocator = Allocator::new(operands, outputs, flags); for expression in stores { - let result = - compile_expression(rt, &mut allocator, &context, expression, VMWidth::Lower64); + let result = compile_expression(rt, &mut allocator, expression, VMWidth::Lower64); let register = allocator.acquire(rt, &[], &[]); - allocator.copy_to_register(rt, result, register); + allocator.copy(rt, result, register); scratch::store(rt, r12, register); - allocator.spill_register(rt, register); + allocator.spill(rt, register); allocator.consume(result); } } None => { - let mut allocator = Allocator::new(temporaries); - let register = allocator.acquire(rt, &[], &[]); - - allocator.copy_to_register(rt, ValueRef::Value(context.output_a), register); - scratch::store(rt, r12, register); + let mut allocator = Allocator::new(operands, outputs, flags); - allocator.spill_register(rt, register); + for index in (0..outputs).rev() { + let output = allocator.acquire(rt, &[], &[]); + allocator.copy(rt, ValueRef::Value(Value::Output(index)), output); + scratch::store(rt, r12, output); + allocator.spill(rt, output); + } } } @@ -595,19 +689,17 @@ pub fn build(rt: &mut Runtime, operation: &Operation) { fn compile_effects( rt: &mut Runtime, allocator: &mut Allocator, - context: &Context, effects: &[Effect], width: VMWidth, ) { for effect in effects { - compile_effect(rt, allocator, context, effect, width); + compile_effect(rt, allocator, effect, width); } } fn compile_binary( rt: &mut Runtime, allocator: &mut Allocator, - context: &Context, first: &Expression, second: &Expression, width: VMWidth, @@ -615,8 +707,8 @@ fn compile_binary( ) where F: FnOnce(&mut Runtime, AsmRegister64, AsmRegister64), { - let first = compile_expression(rt, allocator, context, first, width); - let second = compile_expression(rt, allocator, context, second, width); + let first = compile_expression(rt, allocator, first, width); + let second = compile_expression(rt, allocator, second, width); let pinned = [first, second] .into_iter() @@ -627,14 +719,83 @@ fn compile_binary( .collect::>(); let result = allocator.acquire(rt, &pinned, &[]); - allocator.copy_to_register(rt, first, result); + allocator.copy(rt, first, result); let other = allocator.acquire(rt, &pinned, &[result]); - allocator.copy_to_register(rt, second, other); + allocator.copy(rt, second, other); operation(rt, result, other); - allocator.replace(result, context.output_a, true); + allocator.replace(result, Value::Output(0), true); + allocator.consume(first); + allocator.consume(second); +} + +fn compile_mul( + rt: &mut Runtime, + allocator: &mut Allocator, + first: &Expression, + second: &Expression, + width: VMWidth, +) { + let first = compile_expression(rt, allocator, first, width); + let second = compile_expression(rt, allocator, second, width); + + let pinned = [first, second] + .into_iter() + .filter_map(|value| match value { + ValueRef::Value(value) => Some(value), + ValueRef::Immediate(_) => None, + }) + .collect::>(); + + let rhs = allocator.acquire(rt, &pinned, &[rax]); + allocator.copy(rt, second, rhs); + allocator.copy(rt, first, rax); + + let signed = width == width.signed(); + + match width.size() { + 8 => { + if signed { + rt.asm.imul(rhs).unwrap(); + } else { + rt.asm.mul(rhs).unwrap(); + } + } + 4 => { + let dword = get_gpr32(register::sized(rhs.into(), 4).unwrap()).unwrap(); + + if signed { + rt.asm.imul(dword).unwrap(); + } else { + rt.asm.mul(dword).unwrap(); + } + } + 2 => { + let word = get_gpr16(register::sized(rhs.into(), 2).unwrap()).unwrap(); + + if signed { + rt.asm.imul(word).unwrap(); + } else { + rt.asm.mul(word).unwrap(); + } + } + 1 => { + let byte = get_gpr8(register::sized(rhs.into(), 1).unwrap()).unwrap(); + + if signed { + rt.asm.imul(byte).unwrap(); + } else { + rt.asm.mul(byte).unwrap(); + } + } + _ => unreachable!(), + } + + allocator.replace(rax, Value::Output(0), true); + allocator.replace(rdx, Value::Output(1), true); + allocator.consume(first); allocator.consume(second); } @@ -642,7 +803,6 @@ fn compile_binary( fn compile_shift( rt: &mut Runtime, allocator: &mut Allocator, - context: &Context, first: &Expression, second: &Expression, width: VMWidth, @@ -650,8 +810,8 @@ fn compile_shift( ) where F: FnOnce(&mut Runtime, AsmRegister64), { - let first = compile_expression(rt, allocator, context, first, width); - let second = compile_expression(rt, allocator, context, second, width); + let first = compile_expression(rt, allocator, first, width); + let second = compile_expression(rt, allocator, second, width); let pinned = [first, second] .into_iter() @@ -661,228 +821,138 @@ fn compile_shift( }) .collect::>(); - allocator.copy_to_register(rt, second, rcx); + allocator.copy(rt, second, rcx); let result = allocator.acquire(rt, &pinned, &[rcx]); - allocator.copy_to_register(rt, first, result); + allocator.copy(rt, first, result); operation(rt, result); - allocator.replace(result, context.output_a, true); + allocator.replace(result, Value::Output(0), true); allocator.consume(first); allocator.consume(second); } -fn compile_effect( - rt: &mut Runtime, - allocator: &mut Allocator, - context: &Context, - effect: &Effect, - width: VMWidth, -) { +fn compile_effect(rt: &mut Runtime, allocator: &mut Allocator, effect: &Effect, width: VMWidth) { match effect { - Effect::Add(first, second) => compile_binary( - rt, - allocator, - context, - first, - second, - width, - |rt, left, right| { + Effect::Add(first, second) => { + compile_binary(rt, allocator, first, second, width, |rt, left, right| { rt.asm.add(left, right).unwrap(); - }, - ), - Effect::Sub(first, second) => compile_binary( - rt, - allocator, - context, - first, - second, - width, - |rt, left, right| { + }) + } + Effect::Sub(first, second) => { + compile_binary(rt, allocator, first, second, width, |rt, left, right| { rt.asm.sub(left, right).unwrap(); - }, - ), - Effect::And(first, second) => compile_binary( - rt, - allocator, - context, - first, - second, - width, - |rt, left, right| { + }) + } + Effect::And(first, second) => { + compile_binary(rt, allocator, first, second, width, |rt, left, right| { rt.asm.and(left, right).unwrap(); - }, - ), - Effect::Or(first, second) => compile_binary( - rt, - allocator, - context, - first, - second, - width, - |rt, left, right| { + }) + } + Effect::Or(first, second) => { + compile_binary(rt, allocator, first, second, width, |rt, left, right| { rt.asm.or(left, right).unwrap(); - }, - ), - Effect::Xor(first, second) => compile_binary( - rt, - allocator, - context, - first, - second, - width, - |rt, left, right| { + }) + } + Effect::Xor(first, second) => { + compile_binary(rt, allocator, first, second, width, |rt, left, right| { rt.asm.xor(left, right).unwrap(); - }, - ), - Effect::Mul(first, second) => compile_binary( - rt, - allocator, - context, - first, - second, - width, - |rt, left, right| { - rt.asm.imul_2(left, right).unwrap(); - }, - ), - Effect::Shr(first, second) => compile_shift( - rt, - allocator, - context, - first, - second, - width, - |rt, result| { + }) + } + Effect::Mul(first, second) => compile_mul(rt, allocator, first, second, width), + Effect::Shr(first, second) => { + compile_shift(rt, allocator, first, second, width, |rt, result| { rt.asm.shr(result, cl).unwrap(); - }, - ), - Effect::Shl(first, second) => compile_shift( - rt, - allocator, - context, - first, - second, - width, - |rt, result| { + }) + } + Effect::Shl(first, second) => { + compile_shift(rt, allocator, first, second, width, |rt, result| { rt.asm.shl(result, cl).unwrap(); - }, - ), - Effect::Ror(first, second) => compile_shift( - rt, - allocator, - context, - first, - second, - width, - |rt, result| { + }) + } + Effect::Ror(first, second) => { + compile_shift(rt, allocator, first, second, width, |rt, result| { rt.asm.ror(result, cl).unwrap(); - }, - ), - Effect::Rol(first, second) => compile_shift( - rt, - allocator, - context, - first, - second, - width, - |rt, result| { + }) + } + Effect::Rol(first, second) => { + compile_shift(rt, allocator, first, second, width, |rt, result| { rt.asm.rol(result, cl).unwrap(); - }, - ), - Effect::Sar(first, second) => compile_shift( - rt, - allocator, - context, - first, - second, - width, - |rt, result| { + }) + } + Effect::Sar(first, second) => { + compile_shift(rt, allocator, first, second, width, |rt, result| { rt.asm.sar(result, cl).unwrap(); - }, - ), + }) + } Effect::Assign(expression) => { - let value = compile_expression(rt, allocator, context, expression, width); + let value = compile_expression(rt, allocator, expression, width); let register = allocator.acquire(rt, &[], &[]); - allocator.copy_to_register(rt, value, register); + allocator.copy(rt, value, register); - allocator.replace(register, context.output_a, true); + allocator.replace(register, Value::Output(0), true); allocator.consume(value); } - Effect::Bsr(expression) => compile_unary( - rt, - allocator, - context, - expression, - width, - |rt, result, source| { + Effect::Bsr(expression) => { + compile_unary(rt, allocator, expression, width, |rt, result, source| { rt.asm.bsr(result, source).unwrap(); - }, - ), - Effect::Tzcnt(expression) => compile_unary( - rt, - allocator, - context, - expression, - width, - |rt, result, source| { + }) + } + Effect::Tzcnt(expression) => { + compile_unary(rt, allocator, expression, width, |rt, result, source| { rt.asm.tzcnt(result, source).unwrap(); - }, - ), + }) + } } } fn compile_unary( rt: &mut Runtime, allocator: &mut Allocator, - context: &Context, expression: &Expression, width: VMWidth, operation: F, ) where F: FnOnce(&mut Runtime, AsmRegister64, AsmRegister64), { - let value = compile_expression(rt, allocator, context, expression, width); + let value = compile_expression(rt, allocator, expression, width); let src = allocator.acquire(rt, &[], &[]); - allocator.copy_to_register(rt, value, src); + allocator.copy(rt, value, src); let mask = allocator.acquire(rt, &[], &[src]); rt.asm.mov(mask, width.mask() as i64).unwrap(); rt.asm.and(src, mask).unwrap(); - allocator.spill_register(rt, mask); + allocator.spill(rt, mask); - let dst = allocator.load_mutable(rt, context.output_a, &[src]); + let dst = allocator.mutable(rt, Value::Output(0), &[src]); operation(rt, dst, src); - allocator.mark_dirty(context.output_a); + allocator.dirty.insert(Value::Output(0)); allocator.consume(value); } fn compile_expression( rt: &mut Runtime, allocator: &mut Allocator, - context: &Context, expression: &Expression, width: VMWidth, ) -> ValueRef { match expression { - Expression::Operand(Operand::Input(0)) => ValueRef::Value(context.input_a), - Expression::Operand(Operand::Input(1)) => ValueRef::Value(context.input_b), - Expression::Operand(Operand::Output(0)) => ValueRef::Value(context.output_a), - Expression::Operand(_) => unreachable!(), + Expression::Operand(Operand::Input(index)) => ValueRef::Value(Value::Input(*index)), + Expression::Operand(Operand::Output(index)) => ValueRef::Value(Value::Output(*index)), Expression::Constant(value) => ValueRef::Immediate(*value as i64), Expression::SignBit => ValueRef::Immediate(width.mask().ilog2() as i64), Expression::Sub(first, second) | Expression::BitAnd(first, second) | Expression::BitOr(first, second) | Expression::BitXor(first, second) => { - let first = compile_expression(rt, allocator, context, first, width); - let second = compile_expression(rt, allocator, context, second, width); + let first = compile_expression(rt, allocator, first, width); + let second = compile_expression(rt, allocator, second, width); let pinned = [first, second] .into_iter() @@ -893,10 +963,10 @@ fn compile_expression( .collect::>(); let result = allocator.acquire(rt, &pinned, &[]); - allocator.copy_to_register(rt, first, result); + allocator.copy(rt, first, result); let other = allocator.acquire(rt, &pinned, &[result]); - allocator.copy_to_register(rt, second, other); + allocator.copy(rt, second, other); match expression { Expression::Sub(_, _) => rt.asm.sub(result, other).unwrap(), @@ -910,17 +980,17 @@ fn compile_expression( allocator.consume(second); if other != result { - allocator.spill_register(rt, other); + allocator.spill(rt, other); } - let value = allocator.temp(); + let value = allocator.temporary(); allocator.replace(result, value, true); ValueRef::Value(value) } Expression::BitShr(first, second) | Expression::BitShl(first, second) => { - let first = compile_expression(rt, allocator, context, first, width); - let second = compile_expression(rt, allocator, context, second, width); + let first = compile_expression(rt, allocator, first, width); + let second = compile_expression(rt, allocator, second, width); let pinned = [first, second] .into_iter() @@ -930,10 +1000,10 @@ fn compile_expression( }) .collect::>(); - allocator.copy_to_register(rt, second, rcx); + allocator.copy(rt, second, rcx); let result = allocator.acquire(rt, &pinned, &[rcx]); - allocator.copy_to_register(rt, first, result); + allocator.copy(rt, first, result); match expression { Expression::BitShr(_, _) => rt.asm.shr(result, cl).unwrap(), @@ -944,16 +1014,16 @@ fn compile_expression( allocator.consume(first); allocator.consume(second); - let value = allocator.temp(); + let value = allocator.temporary(); allocator.replace(result, value, true); ValueRef::Value(value) } Expression::BitNot(inner) | Expression::LowByte(inner) => { - let inner = compile_expression(rt, allocator, context, inner, width); + let inner = compile_expression(rt, allocator, inner, width); let result = allocator.acquire(rt, &[], &[]); - allocator.copy_to_register(rt, inner, result); + allocator.copy(rt, inner, result); match expression { Expression::BitNot(_) => rt.asm.not(result).unwrap(), @@ -966,7 +1036,7 @@ fn compile_expression( allocator.consume(inner); - let value = allocator.temp(); + let value = allocator.temporary(); allocator.replace(result, value, true); ValueRef::Value(value) @@ -977,27 +1047,44 @@ fn compile_expression( fn compile_flags( rt: &mut Runtime, allocator: &mut Allocator, - context: &Context, rules: &[(Flag, Condition)], width: VMWidth, ) { rt.asm - .mov(qword_ptr(rbp - Allocator::offset(Value::Flags)), 0i32) + .mov( + qword_ptr( + rbp - Allocator::offset( + allocator.inputs, + allocator.outputs, + allocator.flags, + Value::Flags, + ), + ), + 0x0, + ) .unwrap(); - for (flag, definition) in rules { - match definition { - Condition::Compare(item) => { - compile_comparison(rt, allocator, context, *flag, item, width) - } - Condition::Parity(node) => compile_parity(rt, allocator, context, *flag, node, width), + for (flag, condition) in rules { + match condition { + Condition::Compare(item) => compile_comparison(rt, allocator, *flag, item, width), + Condition::Parity(node) => compile_parity(rt, allocator, *flag, node, width), } } let register = allocator.acquire(rt, &[], &[]); rt.asm - .mov(register, qword_ptr(rbp - Allocator::offset(Value::Flags))) + .mov( + register, + qword_ptr( + rbp - Allocator::offset( + allocator.inputs, + allocator.outputs, + allocator.flags, + Value::Flags, + ), + ), + ) .unwrap(); vreg::store_reg(rt, r12, register, VMReg::Flags); @@ -1007,7 +1094,6 @@ fn compile_flags( fn compile_comparison( rt: &mut Runtime, allocator: &mut Allocator, - context: &Context, flag: Flag, compare: &Compare, width: VMWidth, @@ -1019,8 +1105,8 @@ fn compile_comparison( Compare::BitSet(first, second) => (first, second, 3u8), }; - let left = compile_expression(rt, allocator, context, first, width); - let right = compile_expression(rt, allocator, context, second, width); + let left = compile_expression(rt, allocator, first, width); + let right = compile_expression(rt, allocator, second, width); let pinned = [left, right] .into_iter() @@ -1030,18 +1116,18 @@ fn compile_comparison( }) .collect::>(); - let left_register = allocator.acquire(rt, &pinned, &[]); - allocator.copy_to_register(rt, left, left_register); + let lhs = allocator.acquire(rt, &pinned, &[]); + allocator.copy(rt, left, lhs); - let right_register = allocator.acquire(rt, &pinned, &[left_register]); - allocator.copy_to_register(rt, right, right_register); + let rhs = allocator.acquire(rt, &pinned, &[lhs]); + allocator.copy(rt, right, rhs); - let result = allocator.acquire(rt, &pinned, &[left_register, right_register]); + let result = allocator.acquire(rt, &pinned, &[lhs, rhs]); let byte = get_gpr8(register::sized(result.into(), 1).unwrap()).unwrap(); match condition { - 0 | 1 | 2 => rt.asm.cmp(left_register, right_register).unwrap(), - 3 => rt.asm.bt(left_register, right_register).unwrap(), + 0 | 1 | 2 => rt.asm.cmp(lhs, rhs).unwrap(), + 3 => rt.asm.bt(lhs, rhs).unwrap(), _ => unreachable!(), } @@ -1058,31 +1144,40 @@ fn compile_comparison( .shl(result, flag.bit32().trailing_zeros() as i32) .unwrap(); rt.asm - .or(qword_ptr(rbp - Allocator::offset(Value::Flags)), result) + .or( + qword_ptr( + rbp - Allocator::offset( + allocator.inputs, + allocator.outputs, + allocator.flags, + Value::Flags, + ), + ), + result, + ) .unwrap(); - allocator.spill_register(rt, result); + allocator.spill(rt, result); allocator.consume(left); allocator.consume(right); - allocator.spill_register(rt, left_register); + allocator.spill(rt, lhs); - if right_register != left_register { - allocator.spill_register(rt, right_register); + if rhs != lhs { + allocator.spill(rt, rhs); } } fn compile_parity( rt: &mut Runtime, allocator: &mut Allocator, - context: &Context, flag: Flag, node: &Expression, width: VMWidth, ) { - let value = compile_expression(rt, allocator, context, node, width); + let value = compile_expression(rt, allocator, node, width); let register = allocator.acquire(rt, &[], &[]); - allocator.copy_to_register(rt, value, register); + allocator.copy(rt, value, register); let byte = get_gpr8(register::sized(register.into(), 1).unwrap()).unwrap(); rt.asm.movzx(register, byte).unwrap(); @@ -1093,7 +1188,17 @@ fn compile_parity( .shl(register, flag.bit32().trailing_zeros() as i32) .unwrap(); rt.asm - .or(qword_ptr(rbp - Allocator::offset(Value::Flags)), register) + .or( + qword_ptr( + rbp - Allocator::offset( + allocator.inputs, + allocator.outputs, + allocator.flags, + Value::Flags, + ), + ), + register, + ) .unwrap(); allocator.untrack(register); From 43f165532c454612304930db424a497055038173 Mon Sep 17 00:00:00 2001 From: unknown <86915746+RadonCoding@users.noreply.github.com> Date: Sat, 19 Sep 2026 11:33:43 +0300 Subject: [PATCH 08/33] chore: division via semantics --- obfuscator/src/args.rs | 5 +- obfuscator/src/engine.rs | 7 +- .../src/protections/virtualization/mod.rs | 77 +- runtime/src/lib.rs | 1 - runtime/src/mapper.rs | 1 + runtime/src/obfuscation/immediate.rs | 369 ----- runtime/src/obfuscation/mod.rs | 167 --- runtime/src/runtime.rs | 15 +- runtime/src/vm/bytecode.rs | 2 - runtime/src/vm/handlers/add.rs | 2 +- runtime/src/vm/handlers/and.rs | 2 +- runtime/src/vm/handlers/bit_scan_reverse.rs | 2 +- runtime/src/vm/handlers/bit_test.rs | 2 +- .../src/vm/handlers/bit_test_complement.rs | 2 +- runtime/src/vm/handlers/bit_test_reset.rs | 2 +- runtime/src/vm/handlers/bit_test_set.rs | 2 +- runtime/src/vm/handlers/byte_swap.rs | 2 +- runtime/src/vm/handlers/div.rs | 158 +-- runtime/src/vm/handlers/mul.rs | 2 +- runtime/src/vm/handlers/or.rs | 2 +- runtime/src/vm/handlers/pop.rs | 4 +- runtime/src/vm/handlers/rol.rs | 2 +- runtime/src/vm/handlers/ror.rs | 2 +- runtime/src/vm/handlers/sar.rs | 2 +- runtime/src/vm/handlers/semantic.rs | 1206 ----------------- runtime/src/vm/handlers/semantic/allocator.rs | 223 +++ runtime/src/vm/handlers/semantic/compiler.rs | 730 ++++++++++ runtime/src/vm/handlers/semantic/mod.rs | 285 ++++ runtime/src/vm/handlers/shl.rs | 2 +- runtime/src/vm/handlers/shr.rs | 2 +- runtime/src/vm/handlers/sub.rs | 2 +- runtime/src/vm/handlers/trailing_zeros.rs | 2 +- runtime/src/vm/handlers/xor.rs | 2 +- runtime/src/vm/mod.rs | 1 - runtime/src/vm/utils/bytecode.rs | 14 +- tests/src/lib.rs | 12 +- 36 files changed, 1345 insertions(+), 1968 deletions(-) delete mode 100644 runtime/src/obfuscation/immediate.rs delete mode 100644 runtime/src/obfuscation/mod.rs delete mode 100644 runtime/src/vm/handlers/semantic.rs create mode 100644 runtime/src/vm/handlers/semantic/allocator.rs create mode 100644 runtime/src/vm/handlers/semantic/compiler.rs create mode 100644 runtime/src/vm/handlers/semantic/mod.rs diff --git a/obfuscator/src/args.rs b/obfuscator/src/args.rs index 02f5ea44..ce34a981 100644 --- a/obfuscator/src/args.rs +++ b/obfuscator/src/args.rs @@ -7,6 +7,9 @@ use clap::Parser; pub struct Args { pub input: PathBuf, - #[arg(short = 'v', long = "virtualization")] + #[arg(long = "virtualization")] pub virtualization: bool, + + #[arg(short = 'v', long = "verbose")] + pub verbose: bool, } diff --git a/obfuscator/src/engine.rs b/obfuscator/src/engine.rs index e63e7b73..35a06ba1 100644 --- a/obfuscator/src/engine.rs +++ b/obfuscator/src/engine.rs @@ -4,7 +4,7 @@ use exe::{ ThunkFunctions, VecPE, HDR32_MAGIC, HDR64_MAGIC, PE, RVA, }; use iced_x86::{Decoder, DecoderOptions, FlowControl, Formatter, Instruction, IntelFormatter}; -use logger::info; +use logger::{debug, info}; use markers::{MARKER_BEGIN, MARKER_END, MARKER_SIZE}; use runtime::runtime::Runtime; use std::{collections::HashSet, fmt, mem}; @@ -200,7 +200,10 @@ impl<'a> Engine<'a> { size, instructions: mem::take(block), }); - info!("{}", self.blocks[self.blocks.len() - 1]) + + if self.args.verbose { + debug!("{}", self.blocks[self.blocks.len() - 1]) + } } fn collect_blocks(&mut self, code: &[u8], ip: u64, code_references: &[u32]) { diff --git a/obfuscator/src/protections/virtualization/mod.rs b/obfuscator/src/protections/virtualization/mod.rs index f860d4d9..57bac154 100644 --- a/obfuscator/src/protections/virtualization/mod.rs +++ b/obfuscator/src/protections/virtualization/mod.rs @@ -9,7 +9,7 @@ use exe::{Buffer, SectionCharacteristics}; use exe::{PE, RVA}; use iced_x86::code_asm::CodeAssembler; use iced_x86::Mnemonic; -use logger::info; +use logger::{debug, info}; use rand::Rng; use runtime::runtime::{DataDef, FnDef}; use runtime::vm::bytecode::{self}; @@ -34,31 +34,28 @@ pub struct Virtualization { impl Virtualization { fn attestation(&self, engine: &mut Engine) -> Vec { let mut vcode = Vec::new(); - - // #[cfg(debug_assertions)] - // let mut log = Vec::new(); + let mut log = Vec::new(); let blocks = attestation::generate(engine, engine.rt.keys.secret); - for (_index, operations) in blocks.into_iter().enumerate() { + for (index, operations) in blocks.into_iter().enumerate() { let mut rng = rand::thread_rng(); - // #[cfg(debug_assertions)] - // let (transformed, snapshots) = - // bytecode::transform_with_snapshots(&mut engine.rt.mapper, operations, |ready| { - // rng.gen_range(0..ready.len()) - // }); + let transformed = if engine.args.verbose { + let (transformed, snapshots) = bytecode::transform_with_snapshots( + &mut engine.rt.mapper, + operations, + |ready| rng.gen_range(0..ready.len()), + ); - // #[cfg(not(debug_assertions))] - let transformed = bytecode::transform(&mut engine.rt.mapper, operations, |ready| { - rng.gen_range(0..ready.len()) - }); + log.push(format!("{}BLOCK {}:\n{}", " ".repeat(4), index, snapshots)); - // #[cfg(debug_assertions)] - // { - // let index = _index; - // log.push(format!(" BLOCK {}:\n{}", index, snapshots)); - // } + transformed + } else { + bytecode::transform(&mut engine.rt.mapper, operations, |ready| { + rng.gen_range(0..ready.len()) + }) + }; let mut bytes = bytecode::assemble(&mut engine.rt.mapper, &transformed); @@ -86,16 +83,13 @@ impl Virtualization { vcode.extend_from_slice(&bytes); } - // #[cfg(debug_assertions)] - // { - // use logger::debug; - - // debug!( - // "ATTESTATION @ 0x{:016X}:\n{}", - // engine.rt.keys.secret, - // log.join("\n") - // ); - // } + if engine.args.verbose { + debug!( + "ATTESTATION @ 0x{:016X}:\n{}", + engine.rt.keys.secret, + log.join("\n") + ); + } vcode } @@ -103,14 +97,10 @@ impl Virtualization { impl Protection for Virtualization { fn initialize(&mut self, engine: &mut Engine) { - #[cfg(debug_assertions)] - const MAX_LOGGING: usize = 16; + let mut log = Vec::new(); let mut vtable = Vec::new(); - #[cfg(debug_assertions)] - let mut logs = Vec::new(); - let mut lookup = HashMap::new(); 'outer: for block in &mut engine.blocks { @@ -147,14 +137,15 @@ impl Protection for Virtualization { if lookup.get(&hash).is_none() { let mut rng = rand::thread_rng(); - #[cfg(debug_assertions)] - let transformed = if logs.len() < MAX_LOGGING { + let transformed = if engine.args.verbose { let (transformed, snapshots) = bytecode::transform_with_snapshots( &mut engine.rt.mapper, lifted, |ready| rng.gen_range(0..ready.len()), ); - logs.push((block.rva, format!("{}", snapshots))); + + log.push((block.rva, format!("{}", snapshots))); + transformed } else { bytecode::transform(&mut engine.rt.mapper, lifted, |ready| { @@ -162,11 +153,6 @@ impl Protection for Virtualization { }) }; - #[cfg(not(debug_assertions))] - let transformed = bytecode::transform(&mut engine.rt.mapper, lifted, |ready| { - rng.gen_range(0..ready.len()) - }); - let index = self.programs.len(); self.programs.push(transformed); @@ -204,11 +190,8 @@ impl Protection for Virtualization { &vec![0u8; self.trampolines.len() * VM_DISPATCH_SIZE], ); - #[cfg(debug_assertions)] - { - use logger::debug; - - for (rva, log) in logs { + if engine.args.verbose { + for (rva, log) in log { debug!("VIRTUALIZED @ 0x{:08X}:\n{}", rva, log); } } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index e0d3858d..21aac2ed 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -2,7 +2,6 @@ pub mod debug; pub mod functions; pub mod mapper; -mod obfuscation; pub mod register; pub mod runtime; pub mod vm; diff --git a/runtime/src/mapper.rs b/runtime/src/mapper.rs index 2d1a476a..46e4b31d 100644 --- a/runtime/src/mapper.rs +++ b/runtime/src/mapper.rs @@ -138,4 +138,5 @@ macro_rules! mapped { } }; } + pub(crate) use mapped; diff --git a/runtime/src/obfuscation/immediate.rs b/runtime/src/obfuscation/immediate.rs deleted file mode 100644 index 9e8805a0..00000000 --- a/runtime/src/obfuscation/immediate.rs +++ /dev/null @@ -1,369 +0,0 @@ -use std::collections::HashSet; - -use iced_x86::{ - code_asm::{get_gpr16, get_gpr32, get_gpr64, get_gpr8, AsmRegister8, CodeAssembler}, - Code, FlowControl, Instruction, InstructionInfoFactory, OpAccess, OpCodeOperandKind, OpKind, - Register, RflagsBits, -}; -use rand::{seq::SliceRandom, Rng}; - -use crate::{ - obfuscation::{flags, scratch, Operation}, - register, -}; - -#[derive(Clone, Copy)] -enum Condition { - Carry, - Overflow, - Sign, - Zero, - Parity, -} - -pub fn obfuscate( - instructions: &[Instruction], - asm: &mut CodeAssembler, - rng: &mut R, -) -> Option { - let instruction = &instructions[0]; - - let mut factory = InstructionInfoFactory::new(); - let info = factory.info(instruction); - - let registers = info - .used_registers() - .iter() - .map(|r| r.register().full_register()) - .collect::>(); - - if registers.contains(&Register::RSP) { - return None; - } - - let count = instruction.op_count(); - - let immediate = (0..count).find(|&i| is_immediate(instruction.op_kind(i))); - let memory = (0..count).find(|&i| instruction.op_kind(i) == OpKind::Memory); - - let (value, size, memory) = if let Some(i) = immediate { - if !matches!(instruction.op0_kind(), OpKind::Register) { - return None; - } - - ( - instruction.immediate(i), - instruction.op_register(0).size(), - false, - ) - } else if memory.is_some() { - if instruction.memory_base() == Register::None || instruction.memory_base() == Register::RIP - { - return None; - } - - if instruction.memory_index() != Register::None { - return None; - } - - let displacement = match instruction.memory_base().size() { - 4 => instruction.memory_displacement32() as i32 as i64 as u64, - _ => instruction.memory_displacement64(), - }; - - let size = if displacement <= 0xFF { - 1 - } else if displacement <= 0xFFFF { - 2 - } else if displacement <= 0xFFFF_FFFF { - 4 - } else { - 8 - }; - - (displacement, size, true) - } else { - return None; - }; - - let mut excluded = registers.clone(); - excluded.push(Register::RSP); - - let (register1, preserve1) = match dead_register(instructions, &excluded) { - Some(register) => (register, false), - None => (scratch(&excluded, rng), true), - }; - - excluded.push(register1); - - let (register2, preserve2) = if size == 8 { - match dead_register(instructions, &excluded) { - Some(register) => (register, false), - None => (scratch(&excluded, rng), true), - } - } else { - (register1, false) - }; - - let rewritten = if memory { - rewrite_memory(instruction, register1)? - } else { - rewrite_immediate(instruction, register1)? - }; - - let reads = instruction.rflags_read() != RflagsBits::NONE; - let writes = instruction.rflags_written() != RflagsBits::NONE; - let dead = dead_flags(instructions); - - if preserve1 { - asm.push(get_gpr64(register1).unwrap()).unwrap(); - } - - if preserve2 { - asm.push(get_gpr64(register2).unwrap()).unwrap(); - } - - if !writes && !dead { - asm.pushfq().unwrap(); - } - - if reads { - asm.pushfq().unwrap(); - } - - reconstruct(value, size, register1, register2, asm, rng); - - if reads { - asm.popfq().unwrap(); - } - - asm.add_instruction(rewritten).unwrap(); - - if !writes && !dead { - asm.popfq().unwrap(); - } - - if preserve2 { - asm.pop(get_gpr64(register2).unwrap()).unwrap(); - } - - if preserve1 { - asm.pop(get_gpr64(register1).unwrap()).unwrap(); - } - - Some(1) -} - -fn dead_register(instructions: &[Instruction], excluded: &[Register]) -> Option { - let mut factory = InstructionInfoFactory::new(); - let mut reads = HashSet::new(); - - for instruction in instructions.iter().skip(1) { - if instruction.flow_control() != FlowControl::Next { - break; - } - - let info = factory.info(instruction); - - for register in info.used_registers() { - let access = register.access(); - - let register = register.register().full_register(); - - if !register.is_gpr() { - continue; - } - - if excluded.contains(®ister) { - continue; - } - - match access { - OpAccess::Read - | OpAccess::CondRead - | OpAccess::ReadWrite - | OpAccess::ReadCondWrite => { - reads.insert(register); - } - OpAccess::Write => { - if !reads.contains(®ister) { - return Some(register); - } - } - _ => {} - } - } - } - - None -} - -fn dead_flags(instructions: &[Instruction]) -> bool { - // This is a potentially unsafe implementation which assumes that after branches the flags are dead. - for instruction in instructions.iter().skip(1) { - if instruction.rflags_read() != RflagsBits::NONE { - return false; - } - - if instruction.rflags_modified() != RflagsBits::NONE { - return true; - } - } - - true -} - -fn rewrite_immediate(instruction: &Instruction, scratch: Register) -> Option { - let mnemonic = instruction.mnemonic(); - let count = instruction.op_count(); - let index = (0..count).find(|&i| is_immediate(instruction.op_kind(i)))?; - let destination = instruction.op_register(0); - - let kind = match destination.size() { - 1 => OpCodeOperandKind::r8_reg, - 2 => OpCodeOperandKind::r16_reg, - 4 => OpCodeOperandKind::r32_reg, - 8 => OpCodeOperandKind::r64_reg, - _ => return None, - }; - - let code = Code::values().find(|&c| { - let info = c.op_code(); - - info.mnemonic() == mnemonic && info.op_count() == count && info.op_kind(index) == kind - })?; - - let scratch = register::sized(scratch, destination.size())?; - - Instruction::with2(code, destination, scratch).ok() -} - -fn rewrite_memory(instruction: &Instruction, scratch: Register) -> Option { - let mut rewritten = instruction.clone(); - - rewritten.set_memory_index(register::sized(scratch, 8)?); - rewritten.set_memory_index_scale(1); - rewritten.set_memory_displacement64(0); - rewritten.set_memory_displ_size(0); - - Some(rewritten) -} - -fn is_immediate(kind: OpKind) -> bool { - matches!( - kind, - OpKind::Immediate8 - | OpKind::Immediate16 - | OpKind::Immediate32 - | OpKind::Immediate64 - | OpKind::Immediate8to16 - | OpKind::Immediate8to32 - | OpKind::Immediate8to64 - | OpKind::Immediate32to64 - ) -} - -fn setcc(assembler: &mut CodeAssembler, destination: AsmRegister8, condition: Condition) { - match condition { - Condition::Carry => assembler.setc(destination), - Condition::Overflow => assembler.seto(destination), - Condition::Sign => assembler.sets(destination), - Condition::Zero => assembler.setz(destination), - Condition::Parity => assembler.setp(destination), - } - .unwrap(); -} - -fn reconstruct( - immediate: u64, - size: usize, - register1: Register, - register2: Register, - assembler: &mut CodeAssembler, - rng: &mut impl Rng, -) { - let r1_8 = get_gpr8(register::sized(register1, 1).unwrap()).unwrap(); - let r1_16 = get_gpr16(register::sized(register1, 2).unwrap()).unwrap(); - let r1_32 = get_gpr32(register::sized(register1, 4).unwrap()).unwrap(); - let r1_64 = get_gpr64(register::sized(register1, 8).unwrap()).unwrap(); - - let r2_64 = get_gpr64(register::sized(register2, 8).unwrap()).unwrap(); - - let operation = Operation::random(rng); - - let limit = match size { - 1 => 0xFF, - 2 => 0xFFFF, - 4 => 0xFFFF_FFFF, - 8 => u64::MAX, - _ => unreachable!(), - }; - - let first = rng.gen::() as u64; - let second = rng.gen::() as u64; - - let flags = flags(operation, first, second, 4); - - let options = [ - (Condition::Carry, flags.carry), - (Condition::Overflow, flags.overflow), - (Condition::Sign, flags.sign), - (Condition::Zero, flags.zero), - (Condition::Parity, flags.parity), - ]; - - let &(condition, flag) = options.choose(rng).unwrap(); - - let arithmetic = match operation { - Operation::Add => first.wrapping_add(second) & 0xFFFF_FFFF, - Operation::Sub => first.wrapping_sub(second) & 0xFFFF_FFFF, - Operation::Xor => (first ^ second) & 0xFFFF_FFFF, - }; - - let shift = rng.gen_range(0..16u32); - - let state = ((arithmetic as u16 as u64) | ((flag as u64) << shift)) - .wrapping_mul((arithmetic as u16 as u64) | ((flag as u64) << shift)); - - assembler.mov(r1_32, first as i32).unwrap(); - - match operation { - Operation::Add => assembler.add(r1_32, second as i32), - Operation::Sub => assembler.sub(r1_32, second as i32), - Operation::Xor => assembler.xor(r1_32, second as i32), - } - .unwrap(); - - setcc(assembler, r1_8, condition); - assembler.movzx(r1_32, r1_8).unwrap(); - - if shift > 0 { - assembler.shl(r1_16, shift as i32).unwrap(); - } - - assembler.or(r1_16, arithmetic as u16 as i32).unwrap(); - - if size == 8 { - assembler.imul_2(r1_64, r1_64).unwrap(); - - let xor1 = rng.gen::(); - let noise = rng.gen::(); - let xor2 = immediate ^ state ^ xor1 ^ noise; - let xor3 = noise; - - assembler.mov(r2_64, xor1 as i64).unwrap(); - assembler.xor(r1_64, r2_64).unwrap(); - assembler.mov(r2_64, xor2 as i64).unwrap(); - assembler.xor(r1_64, r2_64).unwrap(); - assembler.mov(r2_64, xor3 as i64).unwrap(); - assembler.xor(r1_64, r2_64).unwrap(); - } else { - assembler.imul_2(r1_32, r1_32).unwrap(); - - let xor1 = rng.gen::(); - let xor2 = ((immediate & limit) as u32) ^ (state as u32) ^ xor1; - - assembler.xor(r1_32, xor1 as i32).unwrap(); - assembler.xor(r1_32, xor2 as i32).unwrap(); - } -} diff --git a/runtime/src/obfuscation/mod.rs b/runtime/src/obfuscation/mod.rs deleted file mode 100644 index 137538b0..00000000 --- a/runtime/src/obfuscation/mod.rs +++ /dev/null @@ -1,167 +0,0 @@ -use iced_x86::{ - code_asm::{CodeAssembler, CodeAssemblerResult}, - BlockEncoderOptions, Instruction, Register, -}; -use rand::{seq::SliceRandom, Rng}; - -mod immediate; - -#[derive(Debug, Clone, Copy)] -pub enum Operation { - Add, - Sub, - Xor, -} - -impl Operation { - pub fn random(rng: &mut impl Rng) -> Self { - match rng.gen_range(0..3) { - 0 => Operation::Add, - 1 => Operation::Sub, - _ => Operation::Xor, - } - } -} - -pub struct Flags { - pub carry: u64, - pub overflow: u64, - pub sign: u64, - pub zero: u64, - pub parity: u64, -} - -pub fn obfuscate(asm: &mut CodeAssembler, ip: u64) -> (Vec, CodeAssemblerResult) { - let mut rng = rand::thread_rng(); - - let mut instructions = asm.take_instructions(); - - let mut chunks = Vec::with_capacity(instructions.len()); - - let mut i = instructions.len(); - - while i > 0 { - i -= 1; - - let mut asm = CodeAssembler::new(64).unwrap(); - - let ip = instructions[i].ip(); - instructions[i].set_ip(0); - - let consumed = - None.or_else(|| immediate::obfuscate(&instructions[i..], &mut asm, &mut rng)); - - if let Some(consumed) = consumed { - let mut chunk = asm.take_instructions(); - - if let Some(instruction) = chunk.first_mut() { - instruction.set_ip(ip); - } - - chunks.push((i, chunk)); - - i = i.saturating_sub(consumed - 1); - } else { - let mut instruction = instructions[i].clone(); - instruction.set_ip(ip); - chunks.push((i, vec![instruction])); - } - } - - chunks.reverse(); - - let mut obfuscated = Vec::new(); - let mut map = vec![0; instructions.len()]; - - for (i, chunk) in chunks { - map[i] = obfuscated.len(); - obfuscated.extend(chunk); - } - - let mut asm = CodeAssembler::new(64).unwrap(); - - for &instruction in &obfuscated { - asm.add_instruction(instruction).unwrap(); - } - - let mut result = asm - .assemble_options(ip, BlockEncoderOptions::RETURN_NEW_INSTRUCTION_OFFSETS) - .unwrap(); - - let offsets = result.inner.new_instruction_offsets.clone(); - - result.inner.new_instruction_offsets = map.iter().map(|&slot| offsets[slot]).collect(); - - (obfuscated, result) -} - -pub fn flags(operation: Operation, left: u64, right: u64, size: usize) -> Flags { - let mask = match size { - 1 => 0xFF, - 2 => 0xFFFF, - 4 => 0xFFFF_FFFF, - 8 => u64::MAX, - _ => unreachable!(), - }; - - let left = left & mask; - let right = right & mask; - - let result = match operation { - Operation::Add => left.wrapping_add(right) & mask, - Operation::Sub => left.wrapping_sub(right) & mask, - Operation::Xor => left ^ right, - }; - - let bit = size * 8 - 1; - - let (carry, overflow) = match operation { - Operation::Xor => (0, 0), - Operation::Add => ( - ((left as u128 + right as u128) > mask as u128) as u64, - ((!(left ^ right) & (left ^ result)) >> bit) & 1, - ), - Operation::Sub => ( - (left < right) as u64, - (((left ^ right) & (left ^ result)) >> bit) & 1, - ), - }; - - Flags { - carry, - overflow, - sign: (result >> bit) & 1, - zero: (result == 0) as u64, - parity: ((result as u8).count_ones() % 2 == 0) as u64, - } -} - -pub fn scratch(excluded: &[Register], rng: &mut impl Rng) -> Register { - const REGISTERS: [Register; 12] = [ - Register::RAX, - Register::RCX, - Register::RDX, - Register::RSI, - Register::RDI, - Register::R8, - Register::R9, - Register::R11, - Register::R12, - Register::R13, - Register::R14, - Register::R15, - ]; - - let excluded = excluded - .iter() - .map(|r| r.full_register()) - .collect::>(); - - let registers = REGISTERS - .iter() - .cloned() - .filter(|r| !excluded.contains(r)) - .collect::>(); - - *registers.choose(rng).unwrap() -} diff --git a/runtime/src/runtime.rs b/runtime/src/runtime.rs index 6d288b85..f8735114 100644 --- a/runtime/src/runtime.rs +++ b/runtime/src/runtime.rs @@ -1,12 +1,14 @@ use std::collections::HashMap; -use iced_x86::code_asm::{ptr, r10, r11, rcx, AsmRegister64, CodeAssembler, CodeLabel}; +use iced_x86::{ + code_asm::{ptr, r10, r11, rcx, AsmRegister64, CodeAssembler, CodeLabel}, + BlockEncoderOptions, +}; use rand::{seq::SliceRandom, Rng}; use crate::{ functions, mapper::{mapped, Mappable, Mapper}, - obfuscation, vm::{ self, bytecode::{VMReg, VMVec}, @@ -851,7 +853,10 @@ impl Runtime { self.emit(&[EmissionTask::Data(DataDef::VehEnd)]); - let (instructions, result) = obfuscation::obfuscate(&mut self.asm, ip); + let result = self + .asm + .assemble_options(ip, BlockEncoderOptions::RETURN_NEW_INSTRUCTION_OFFSETS) + .unwrap(); let labels = self .function_labels @@ -868,11 +873,11 @@ impl Runtime { let offsets = &result.inner.new_instruction_offsets; - for (def, (_, end)) in &self.functions { + for (def, &(_, end)) in &self.functions { let label = self.function_labels[def]; let start = result.label_ip(&label).unwrap(); let offset = offsets[end - 1] as u64; - let size = instructions[end - 1].len() as u64; + let size = (offsets[end] - offsets[end - 1]) as u64; self.sizes.insert(label, ip + offset + size - start); } diff --git a/runtime/src/vm/bytecode.rs b/runtime/src/vm/bytecode.rs index e9c02989..d7f1c2db 100644 --- a/runtime/src/vm/bytecode.rs +++ b/runtime/src/vm/bytecode.rs @@ -4,7 +4,6 @@ use crate::vm::lifters::{ arithmetic, branch, bsr, bswap, bt, cmov, cmpxchg, div, extend, integer, lea, multiply, pcmpeqb, pmovskb, rdtsc, scalar, set, stack, transfer, tzcnt, xadd, xchg, }; -#[cfg(debug_assertions)] use crate::vm::snapshot::Snapshots; use crate::vm::transform::encrypt::Encrypt; use crate::vm::transform::indirect::Indirect; @@ -681,7 +680,6 @@ where operations } -#[cfg(debug_assertions)] pub fn transform_with_snapshots( mapper: &mut Mapper, mut operations: Vec>, diff --git a/runtime/src/vm/handlers/add.rs b/runtime/src/vm/handlers/add.rs index f4e2397f..ea099212 100644 --- a/runtime/src/vm/handlers/add.rs +++ b/runtime/src/vm/handlers/add.rs @@ -7,7 +7,7 @@ use crate::{ }; pub fn build(rt: &mut Runtime) { - semantic::build( + semantic::compiler::compile( rt, &Operation { effects: vec![Effect::Add( diff --git a/runtime/src/vm/handlers/and.rs b/runtime/src/vm/handlers/and.rs index 58807ce6..9d814ab2 100644 --- a/runtime/src/vm/handlers/and.rs +++ b/runtime/src/vm/handlers/and.rs @@ -7,7 +7,7 @@ use crate::{ }; pub fn build(rt: &mut Runtime) { - semantic::build( + semantic::compiler::compile( rt, &Operation { effects: vec![Effect::And( diff --git a/runtime/src/vm/handlers/bit_scan_reverse.rs b/runtime/src/vm/handlers/bit_scan_reverse.rs index 469843c9..bb636b91 100644 --- a/runtime/src/vm/handlers/bit_scan_reverse.rs +++ b/runtime/src/vm/handlers/bit_scan_reverse.rs @@ -7,7 +7,7 @@ use crate::{ }; pub fn build(rt: &mut Runtime) { - semantic::build( + semantic::compiler::compile( rt, &Operation { effects: vec![Effect::Bsr(Expression::Operand(Operand::Input(0)))], diff --git a/runtime/src/vm/handlers/bit_test.rs b/runtime/src/vm/handlers/bit_test.rs index 944ab959..6a4cb347 100644 --- a/runtime/src/vm/handlers/bit_test.rs +++ b/runtime/src/vm/handlers/bit_test.rs @@ -7,7 +7,7 @@ use crate::{ }; pub fn build(rt: &mut Runtime) { - semantic::build( + semantic::compiler::compile( rt, &Operation { effects: vec![], diff --git a/runtime/src/vm/handlers/bit_test_complement.rs b/runtime/src/vm/handlers/bit_test_complement.rs index 7740f600..e744446e 100644 --- a/runtime/src/vm/handlers/bit_test_complement.rs +++ b/runtime/src/vm/handlers/bit_test_complement.rs @@ -7,7 +7,7 @@ use crate::{ }; pub fn build(rt: &mut Runtime) { - semantic::build( + semantic::compiler::compile( rt, &Operation { effects: vec![Effect::Xor( diff --git a/runtime/src/vm/handlers/bit_test_reset.rs b/runtime/src/vm/handlers/bit_test_reset.rs index 5fe486d7..16fde7d6 100644 --- a/runtime/src/vm/handlers/bit_test_reset.rs +++ b/runtime/src/vm/handlers/bit_test_reset.rs @@ -7,7 +7,7 @@ use crate::{ }; pub fn build(rt: &mut Runtime) { - semantic::build( + semantic::compiler::compile( rt, &Operation { effects: vec![Effect::And( diff --git a/runtime/src/vm/handlers/bit_test_set.rs b/runtime/src/vm/handlers/bit_test_set.rs index 36189ea5..a43452ee 100644 --- a/runtime/src/vm/handlers/bit_test_set.rs +++ b/runtime/src/vm/handlers/bit_test_set.rs @@ -7,7 +7,7 @@ use crate::{ }; pub fn build(rt: &mut Runtime) { - semantic::build( + semantic::compiler::compile( rt, &Operation { effects: vec![Effect::Or( diff --git a/runtime/src/vm/handlers/byte_swap.rs b/runtime/src/vm/handlers/byte_swap.rs index 98d9907a..ef7446d1 100644 --- a/runtime/src/vm/handlers/byte_swap.rs +++ b/runtime/src/vm/handlers/byte_swap.rs @@ -7,7 +7,7 @@ use crate::{ }; pub fn build(rt: &mut Runtime) { - semantic::build( + semantic::compiler::compile( rt, &Operation { effects: vec![ diff --git a/runtime/src/vm/handlers/div.rs b/runtime/src/vm/handlers/div.rs index 8bdd2c77..d7be7a37 100644 --- a/runtime/src/vm/handlers/div.rs +++ b/runtime/src/vm/handlers/div.rs @@ -1,142 +1,32 @@ -use iced_x86::code_asm::*; - use crate::{ runtime::Runtime, - vm::utils::{self, scratch}, + vm::{ + bytecode::VMWidth, + handlers::semantic::{self, Effect, Expression, Operand, Operation}, + }, }; -// unsigned char* (unsigned char*) pub fn build(rt: &mut Runtime) { - let mut epilogue = rt.asm.create_label(); - - // push r13 - rt.asm.push(r13).unwrap(); - // push r14 - rt.asm.push(r14).unwrap(); - - // mov r13, rcx - rt.asm.mov(r13, rcx).unwrap(); - - // rax -> width - utils::bytecode::read_byte_zx(rt, r13, eax); - - // load r8 - scratch::load(rt, r12, r8); - // load r9 - scratch::load(rt, r12, r9); - // load r14 - scratch::load(rt, r12, r14); - - utils::width::dispatch( + semantic::compiler::compile( rt, - rax, - &mut epilogue, - Some(Box::new(|rt| wide(rt, false))), - Some(Box::new(|rt| dword(rt, false))), - None, - Some(Box::new(|rt| word(rt, false))), - None, - Some(Box::new(|rt| byte(rt, false))), - Some(Box::new(|rt| wide(rt, true))), - Some(Box::new(|rt| dword(rt, true))), - Some(Box::new(|rt| word(rt, true))), - Some(Box::new(|rt| byte(rt, true))), - None, - None, + &Operation { + effects: vec![Effect::Div( + Expression::Operand(Operand::Input(0)), + Expression::Operand(Operand::Input(1)), + Expression::Operand(Operand::Input(2)), + )], + flags: vec![], + stores: None, + widths: &[ + VMWidth::Lower64, + VMWidth::Lower32, + VMWidth::Lower16, + VMWidth::Lower8, + VMWidth::SLower64, + VMWidth::SLower32, + VMWidth::SLower16, + VMWidth::SLower8, + ], + }, ); - - rt.asm.set_label(&mut epilogue).unwrap(); - { - // store r8 - scratch::store(rt, r12, r8); - // store r9 - scratch::store(rt, r12, r9); - - // mov rax, r13 - rt.asm.mov(rax, r13).unwrap(); - // pop r14 - rt.asm.pop(r14).unwrap(); - // pop r13 - rt.asm.pop(r13).unwrap(); - // ret - rt.asm.ret().unwrap(); - } -} - -fn wide(rt: &mut Runtime, signed: bool) { - // mov rax, r9 - rt.asm.mov(rax, r9).unwrap(); - // mov rdx, r8 - rt.asm.mov(rdx, r8).unwrap(); - if signed { - // rax = rdx:rax / r14, rdx = rdx:rax % r14 - rt.asm.idiv(r14).unwrap(); - } else { - // rax = rdx:rax / r14, rdx = rdx:rax % r14 - rt.asm.div(r14).unwrap(); - } - // mov r9, rax - rt.asm.mov(r9, rax).unwrap(); - // mov r8, rdx - rt.asm.mov(r8, rdx).unwrap(); -} - -fn dword(rt: &mut Runtime, signed: bool) { - // mov rax, r9 - rt.asm.mov(rax, r9).unwrap(); - // mov rdx, r8 - rt.asm.mov(rdx, r8).unwrap(); - if signed { - // eax = edx:eax / r14d, edx = edx:eax % r14d - rt.asm.idiv(r14d).unwrap(); - } else { - // eax = edx:eax / r14d, edx = edx:eax % r14d - rt.asm.div(r14d).unwrap(); - } - // mov r9, rax - rt.asm.mov(r9, rax).unwrap(); - // mov r8, rdx - rt.asm.mov(r8, rdx).unwrap(); -} - -fn word(rt: &mut Runtime, signed: bool) { - // mov rax, r9 - rt.asm.mov(rax, r9).unwrap(); - // mov rdx, r8 - rt.asm.mov(rdx, r8).unwrap(); - if signed { - // ax = dx:ax / r14w, dx = dx:ax % r14w - rt.asm.idiv(r14w).unwrap(); - } else { - // ax = dx:ax / r14w, dx = dx:ax % r14w - rt.asm.div(r14w).unwrap(); - } - // mov r9, rax - rt.asm.mov(r9, rax).unwrap(); - // mov r8, rdx - rt.asm.mov(r8, rdx).unwrap(); -} - -fn byte(rt: &mut Runtime, signed: bool) { - // movzx ecx, r8b - rt.asm.movzx(ecx, r8b).unwrap(); - // shl ecx, 0x8 - rt.asm.shl(ecx, 0x8i32).unwrap(); - // movzx eax, r9b - rt.asm.movzx(eax, r9b).unwrap(); - // or eax, ecx - rt.asm.or(eax, ecx).unwrap(); - if signed { - // al = ax / r14b, ah = ax % r14b - rt.asm.idiv(r14b).unwrap(); - } else { - // al = ax / r14b, ah = ax % r14b - rt.asm.div(r14b).unwrap(); - } - // movzx ecx, ah - rt.asm.movzx(ecx, ah).unwrap(); - // movzx r9d, al - rt.asm.movzx(r9d, al).unwrap(); - // mov r8, rcx - rt.asm.mov(r8, rcx).unwrap(); } diff --git a/runtime/src/vm/handlers/mul.rs b/runtime/src/vm/handlers/mul.rs index 5603d911..3294ba40 100644 --- a/runtime/src/vm/handlers/mul.rs +++ b/runtime/src/vm/handlers/mul.rs @@ -7,7 +7,7 @@ use crate::{ }; pub fn build(rt: &mut Runtime) { - semantic::build( + semantic::compiler::compile( rt, &Operation { effects: vec![Effect::Mul( diff --git a/runtime/src/vm/handlers/or.rs b/runtime/src/vm/handlers/or.rs index ad48b1f0..8cbb48c7 100644 --- a/runtime/src/vm/handlers/or.rs +++ b/runtime/src/vm/handlers/or.rs @@ -7,7 +7,7 @@ use crate::{ }; pub fn build(rt: &mut Runtime) { - semantic::build( + semantic::compiler::compile( rt, &Operation { effects: vec![Effect::Or( diff --git a/runtime/src/vm/handlers/pop.rs b/runtime/src/vm/handlers/pop.rs index bcbc6a88..75fbeb3e 100644 --- a/runtime/src/vm/handlers/pop.rs +++ b/runtime/src/vm/handlers/pop.rs @@ -1,4 +1,4 @@ -use iced_x86::code_asm::{r8, r9, rax, r12, rcx}; +use iced_x86::code_asm::{r12, r8, r9, rax, rcx}; use crate::{ runtime::Runtime, @@ -7,7 +7,7 @@ use crate::{ }; // unsigned char* (unsigned char*) -pub fn build(rt: &mut Runtime) { +pub fn build(rt: &mut Runtime) { // mov r8, [r12 + ...]; mov r9, [r8] utils::vreg::load_mem(rt, r12, VMReg::Rsp, r8, r9); // add [r12 + ...], 0x8 diff --git a/runtime/src/vm/handlers/rol.rs b/runtime/src/vm/handlers/rol.rs index 8e5b1233..b2305353 100644 --- a/runtime/src/vm/handlers/rol.rs +++ b/runtime/src/vm/handlers/rol.rs @@ -7,7 +7,7 @@ use crate::{ }; pub fn build(rt: &mut Runtime) { - semantic::build( + semantic::compiler::compile( rt, &Operation { effects: vec![Effect::Rol( diff --git a/runtime/src/vm/handlers/ror.rs b/runtime/src/vm/handlers/ror.rs index b25f6615..29946359 100644 --- a/runtime/src/vm/handlers/ror.rs +++ b/runtime/src/vm/handlers/ror.rs @@ -7,7 +7,7 @@ use crate::{ }; pub fn build(rt: &mut Runtime) { - semantic::build( + semantic::compiler::compile( rt, &Operation { effects: vec![Effect::Ror( diff --git a/runtime/src/vm/handlers/sar.rs b/runtime/src/vm/handlers/sar.rs index 4cf5df72..0b8da886 100644 --- a/runtime/src/vm/handlers/sar.rs +++ b/runtime/src/vm/handlers/sar.rs @@ -7,7 +7,7 @@ use crate::{ }; pub fn build(rt: &mut Runtime) { - semantic::build( + semantic::compiler::compile( rt, &Operation { effects: vec![Effect::Sar( diff --git a/runtime/src/vm/handlers/semantic.rs b/runtime/src/vm/handlers/semantic.rs deleted file mode 100644 index 2ce1dab5..00000000 --- a/runtime/src/vm/handlers/semantic.rs +++ /dev/null @@ -1,1206 +0,0 @@ -use iced_x86::code_asm::{ - cl, get_gpr16, get_gpr32, get_gpr8, qword_ptr, r12, r13, r14, r14d, r8, r9, rax, rbp, rcx, rdx, - rsp, AsmRegister64, CodeLabel, -}; - -use std::collections::HashSet; - -use crate::{ - register, - runtime::Runtime, - vm::{ - bytecode::{Flag, VMReg, VMWidth}, - utils::{bytecode, scratch, vreg}, - }, -}; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Operand { - Input(usize), - Output(usize), -} - -#[derive(Debug, Clone)] -pub enum Expression { - Operand(Operand), - Constant(u64), - BitAnd(Box, Box), - BitOr(Box, Box), - BitXor(Box, Box), - BitShr(Box, Box), - BitShl(Box, Box), - BitNot(Box), - Sub(Box, Box), - LowByte(Box), - SignBit, -} - -#[derive(Debug, Clone)] -pub enum Effect { - Add(Expression, Expression), - Sub(Expression, Expression), - And(Expression, Expression), - Or(Expression, Expression), - Xor(Expression, Expression), - Shr(Expression, Expression), - Shl(Expression, Expression), - Ror(Expression, Expression), - Rol(Expression, Expression), - Mul(Expression, Expression), - Assign(Expression), - Sar(Expression, Expression), - Bsr(Expression), - Tzcnt(Expression), -} - -#[derive(Debug, Clone)] -pub enum Compare { - Equal(Expression, Expression), - LessThan(Expression, Expression), - GreaterThan(Expression, Expression), - BitSet(Expression, Expression), -} - -#[derive(Debug, Clone)] -pub enum Condition { - Compare(Compare), - Parity(Expression), -} - -#[derive(Debug)] -pub struct Operation { - pub effects: Vec, - pub flags: Vec<(Flag, Condition)>, - pub stores: Option>, - pub widths: &'static [VMWidth], -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -enum Value { - Input(usize), - Output(usize), - Flags, - Temporary(usize), -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum ValueRef { - Value(Value), - Immediate(i64), -} - -#[derive(Clone)] -struct Allocator { - tracked: Vec<(AsmRegister64, Value)>, - dirty: HashSet, - temporary: usize, - inputs: usize, - outputs: usize, - flags: bool, -} - -impl Allocator { - const REGISTERS: [AsmRegister64; 5] = [rax, rcx, rdx, r8, r9]; - - fn new(inputs: usize, outputs: usize, flags: bool) -> Self { - Self { - tracked: Vec::new(), - dirty: HashSet::new(), - temporary: 0, - inputs, - outputs, - flags, - } - } - - fn track(&mut self, register: AsmRegister64, value: Value, dirty: bool) { - assert!( - self.tracked.iter().all(|(r, _)| *r != register), - "register {:?} is already tracked", - register - ); - assert!( - self.tracked.iter().all(|(_, v)| *v != value), - "value {:?} is already tracked", - value - ); - self.tracked.push((register, value)); - - if dirty { - self.dirty.insert(value); - } else { - self.dirty.remove(&value); - } - } - - fn untrack(&mut self, register: AsmRegister64) -> Option { - let index = self.tracked.iter().position(|(r, _)| *r == register)?; - Some(self.tracked.swap_remove(index).1) - } - - fn tracked_register(&self, value: Value) -> Option { - self.tracked - .iter() - .find(|(_, v)| *v == value) - .map(|(r, _)| *r) - } - - fn tracked_value(&self, register: AsmRegister64) -> Option { - self.tracked - .iter() - .find(|(r, _)| *r == register) - .map(|(_, v)| *v) - } - - fn temporary(&mut self) -> Value { - let value = Value::Temporary(self.temporary); - self.temporary += 1; - value - } - - fn offset(inputs: usize, outputs: usize, flags: bool, value: Value) -> i32 { - let flags = flags as usize; - let slot = match value { - Value::Input(index) => index, - Value::Output(index) => inputs + index, - Value::Flags => inputs + outputs, - Value::Temporary(index) => inputs + outputs + flags + index, - }; - (slot as i32 + 1) * 8 - } - - fn spill(&mut self, rt: &mut Runtime, register: AsmRegister64) { - let Some(value) = self.tracked_value(register) else { - return; - }; - - if self.dirty.remove(&value) { - rt.asm - .mov( - qword_ptr(rbp - Self::offset(self.inputs, self.outputs, self.flags, value)), - register, - ) - .unwrap(); - } - - self.untrack(register); - } - - fn dump(&mut self, rt: &mut Runtime) { - let registers = self - .tracked - .iter() - .map(|(register, _)| *register) - .collect::>(); - - for register in registers { - self.spill(rt, register); - } - } - - fn acquire( - &mut self, - rt: &mut Runtime, - pinned: &[Value], - avoid: &[AsmRegister64], - ) -> AsmRegister64 { - if let Some(register) = Self::REGISTERS - .iter() - .copied() - .find(|register| !avoid.contains(register) && self.tracked_value(*register).is_none()) - { - return register; - } - - if let Some(register) = Self::REGISTERS.iter().copied().find(|register| { - if avoid.contains(register) { - return false; - } - - match self.tracked_value(*register) { - Some(value) => !pinned.contains(&value), - None => true, - } - }) { - self.spill(rt, register); - return register; - } - - let register = Self::REGISTERS - .iter() - .copied() - .find(|register| !avoid.contains(register)) - .expect("allocator has no usable register"); - self.spill(rt, register); - register - } - - fn copy(&mut self, rt: &mut Runtime, value: ValueRef, register: AsmRegister64) { - self.spill(rt, register); - - match value { - ValueRef::Immediate(value) => { - rt.asm.mov(register, value).unwrap(); - } - ValueRef::Value(value) => { - if let Some(source) = self.tracked_register(value) { - rt.asm.mov(register, source).unwrap(); - } else { - rt.asm - .mov( - register, - qword_ptr( - rbp - Self::offset(self.inputs, self.outputs, self.flags, value), - ), - ) - .unwrap(); - } - } - } - } - - fn mutable( - &mut self, - rt: &mut Runtime, - value: Value, - avoid: &[AsmRegister64], - ) -> AsmRegister64 { - if let Some(register) = self.tracked_register(value) { - if !avoid.contains(®ister) { - return register; - } - } - - let register = self.acquire(rt, &[], avoid); - rt.asm - .mov( - register, - qword_ptr(rbp - Self::offset(self.inputs, self.outputs, self.flags, value)), - ) - .unwrap(); - self.track(register, value, false); - register - } - - fn replace(&mut self, register: AsmRegister64, value: Value, dirty: bool) { - self.untrack(register); - - if let Some(old) = self - .tracked - .iter() - .find(|(_, v)| *v == value) - .map(|(r, _)| *r) - { - self.untrack(old); - } - - self.track(register, value, dirty); - } - - fn consume(&mut self, value: ValueRef) { - let ValueRef::Value(value) = value else { - return; - }; - if matches!(value, Value::Temporary(_)) { - if let Some(register) = self.tracked_register(value) { - self.untrack(register); - } - self.dirty.remove(&value); - } - } -} - -// TODO: Implement a mixed-boolean-arithmetic engine! - -impl Operation { - fn operands(&self) -> usize { - let mut value = 0; - - for effect in &self.effects { - effect.operands(&mut value); - } - - for (_, condition) in &self.flags { - condition.operands(&mut value); - } - - if let Some(stores) = &self.stores { - for store in stores { - store.operands(&mut value); - } - } - - value - } - - fn outputs(&self) -> usize { - let mut value = 0; - - for effect in &self.effects { - effect.outputs(&mut value); - } - - for (_, condition) in &self.flags { - condition.outputs(&mut value); - } - - match &self.stores { - Some(stores) => { - value = value.max(stores.len()); - for store in stores { - store.outputs(&mut value); - } - } - None if !self.effects.is_empty() => value = value.max(1), - None => {} - } - - value - } - - fn temporaries(&self) -> usize { - let effects = self.effects.iter().map(Effect::temporary).sum::(); - let flags = self - .flags - .iter() - .map(|(_, condition)| condition.temporary()) - .sum::(); - let stores = self - .stores - .as_ref() - .map(|stores| stores.iter().map(Expression::temporary).sum::()) - .unwrap_or(0); - - effects + flags + stores - } -} - -impl Expression { - fn operands(&self, value: &mut usize) { - match self { - Expression::Operand(Operand::Input(n)) => { - *value = (*value).max(*n + 1); - } - Expression::Operand(Operand::Output(_)) - | Expression::Constant(_) - | Expression::SignBit => {} - Expression::BitAnd(first, second) - | Expression::BitOr(first, second) - | Expression::BitXor(first, second) - | Expression::BitShr(first, second) - | Expression::BitShl(first, second) - | Expression::Sub(first, second) => { - first.operands(value); - second.operands(value); - } - Expression::BitNot(inner) | Expression::LowByte(inner) => { - inner.operands(value); - } - } - } - - fn outputs(&self, value: &mut usize) { - match self { - Expression::Operand(Operand::Output(index)) => { - *value = (*value).max(*index + 1); - } - Expression::Operand(Operand::Input(_)) - | Expression::Constant(_) - | Expression::SignBit => {} - Expression::BitAnd(first, second) - | Expression::BitOr(first, second) - | Expression::BitXor(first, second) - | Expression::BitShr(first, second) - | Expression::BitShl(first, second) - | Expression::Sub(first, second) => { - first.outputs(value); - second.outputs(value); - } - Expression::BitNot(inner) | Expression::LowByte(inner) => { - inner.outputs(value); - } - } - } - - fn temporary(&self) -> usize { - match self { - Expression::Operand(_) | Expression::Constant(_) | Expression::SignBit => 0, - Expression::BitAnd(first, second) - | Expression::BitOr(first, second) - | Expression::BitXor(first, second) - | Expression::BitShr(first, second) - | Expression::BitShl(first, second) - | Expression::Sub(first, second) => 1 + first.temporary() + second.temporary(), - Expression::BitNot(inner) | Expression::LowByte(inner) => 1 + inner.temporary(), - } - } -} - -impl Effect { - fn operands(&self, value: &mut usize) { - match self { - Effect::Add(first, second) - | Effect::Sub(first, second) - | Effect::And(first, second) - | Effect::Or(first, second) - | Effect::Xor(first, second) - | Effect::Shr(first, second) - | Effect::Shl(first, second) - | Effect::Ror(first, second) - | Effect::Rol(first, second) - | Effect::Mul(first, second) - | Effect::Sar(first, second) => { - first.operands(value); - second.operands(value); - } - Effect::Assign(expression) | Effect::Bsr(expression) | Effect::Tzcnt(expression) => { - expression.operands(value); - } - } - } - - fn outputs(&self, value: &mut usize) { - match self { - Effect::Add(first, second) - | Effect::Sub(first, second) - | Effect::And(first, second) - | Effect::Or(first, second) - | Effect::Xor(first, second) - | Effect::Shr(first, second) - | Effect::Shl(first, second) - | Effect::Ror(first, second) - | Effect::Rol(first, second) - | Effect::Mul(first, second) - | Effect::Sar(first, second) => { - first.outputs(value); - second.outputs(value); - } - Effect::Assign(expression) | Effect::Bsr(expression) | Effect::Tzcnt(expression) => { - expression.outputs(value); - } - } - } - - fn temporary(&self) -> usize { - match self { - Effect::Add(first, second) - | Effect::Sub(first, second) - | Effect::And(first, second) - | Effect::Or(first, second) - | Effect::Xor(first, second) - | Effect::Shr(first, second) - | Effect::Shl(first, second) - | Effect::Ror(first, second) - | Effect::Rol(first, second) - | Effect::Mul(first, second) - | Effect::Sar(first, second) => first.temporary() + second.temporary(), - Effect::Assign(expression) | Effect::Bsr(expression) | Effect::Tzcnt(expression) => { - expression.temporary() - } - } - } -} - -impl Condition { - fn operands(&self, value: &mut usize) { - match self { - Condition::Compare(compare) => compare.operands(value), - Condition::Parity(expression) => expression.operands(value), - } - } - - fn outputs(&self, value: &mut usize) { - match self { - Condition::Compare(compare) => compare.outputs(value), - Condition::Parity(expression) => expression.outputs(value), - } - } - - fn temporary(&self) -> usize { - match self { - Condition::Compare(compare) => compare.temporary(), - Condition::Parity(expression) => expression.temporary(), - } - } -} - -impl Compare { - fn operands(&self, value: &mut usize) { - match self { - Compare::Equal(first, second) - | Compare::LessThan(first, second) - | Compare::GreaterThan(first, second) - | Compare::BitSet(first, second) => { - first.operands(value); - second.operands(value); - } - } - } - - fn outputs(&self, value: &mut usize) { - match self { - Compare::Equal(first, second) - | Compare::LessThan(first, second) - | Compare::GreaterThan(first, second) - | Compare::BitSet(first, second) => { - first.outputs(value); - second.outputs(value); - } - } - } - - fn temporary(&self) -> usize { - match self { - Compare::Equal(first, second) - | Compare::LessThan(first, second) - | Compare::GreaterThan(first, second) - | Compare::BitSet(first, second) => first.temporary() + second.temporary(), - } - } -} - -pub fn build(rt: &mut Runtime, operation: &Operation) { - let mut epilogue = rt.asm.create_label(); - let operands = operation.operands(); - let outputs = operation.outputs(); - let flags = !operation.flags.is_empty(); - let temporaries = operation.temporaries(); - let slots = operands + outputs + flags as usize + temporaries; - let size = ((slots as i32 * 8) + 15) & !15; - - rt.asm.push(r13).unwrap(); - rt.asm.push(r14).unwrap(); - rt.asm.push(rbp).unwrap(); - rt.asm.mov(rbp, rsp).unwrap(); - rt.asm.sub(rsp, size).unwrap(); - - rt.asm.mov(r13, rcx).unwrap(); - - bytecode::read_byte_zx(rt, r13, r14d); - - for index in (0..operands).rev() { - scratch::load(rt, r12, rax); - - rt.asm - .mov( - qword_ptr(rbp - Allocator::offset(operands, outputs, flags, Value::Input(index))), - rax, - ) - .unwrap(); - } - - let handlers = operation - .widths - .iter() - .map(|&width| { - let actions = operation.effects.clone(); - let rules = operation.flags.clone(); - ( - width, - Box::new(move |rt: &mut Runtime, allocator: &mut Allocator| { - compile_effects(rt, allocator, &actions, width); - - for index in 0..outputs { - let output = Value::Output(index); - let register = allocator.mutable(rt, output, &[]); - let mask = allocator.acquire(rt, &[output], &[register]); - - rt.asm.mov(mask, width.mask() as i64).unwrap(); - rt.asm.and(register, mask).unwrap(); - - allocator.spill(rt, mask); - allocator.dirty.insert(output); - } - - if !rules.is_empty() { - compile_flags(rt, allocator, &rules, width); - } - }) as Box, - ) - }) - .collect::)>>(); - - let cases = handlers - .iter() - .map( - |(width, _function): &(VMWidth, Box)| { - (rt.mapper.index(*width) as u8, rt.asm.create_label()) - }, - ) - .collect::>(); - - rt.jumps( - r14, - cases - .iter() - .map(|(key, label): &(u8, CodeLabel)| (*key, *label)) - .collect::>(), - ); - - for ((_key, mut label), (_width, handler)) in cases.into_iter().zip(handlers.into_iter()) { - rt.asm.set_label(&mut label).unwrap(); - - let mut local = Allocator::new(operands, outputs, flags); - - handler(rt, &mut local); - - local.dump(rt); - - rt.asm.jmp(epilogue).unwrap(); - } - - rt.asm.set_label(&mut epilogue).unwrap(); - - match &operation.stores { - Some(stores) => { - let mut allocator = Allocator::new(operands, outputs, flags); - for expression in stores { - let result = compile_expression(rt, &mut allocator, expression, VMWidth::Lower64); - let register = allocator.acquire(rt, &[], &[]); - - allocator.copy(rt, result, register); - scratch::store(rt, r12, register); - - allocator.spill(rt, register); - allocator.consume(result); - } - } - None => { - let mut allocator = Allocator::new(operands, outputs, flags); - - for index in (0..outputs).rev() { - let output = allocator.acquire(rt, &[], &[]); - allocator.copy(rt, ValueRef::Value(Value::Output(index)), output); - scratch::store(rt, r12, output); - allocator.spill(rt, output); - } - } - } - - rt.asm.mov(rax, r13).unwrap(); - - rt.asm.add(rsp, size).unwrap(); - rt.asm.pop(rbp).unwrap(); - rt.asm.pop(r14).unwrap(); - rt.asm.pop(r13).unwrap(); - rt.asm.ret().unwrap(); -} - -fn compile_effects( - rt: &mut Runtime, - allocator: &mut Allocator, - effects: &[Effect], - width: VMWidth, -) { - for effect in effects { - compile_effect(rt, allocator, effect, width); - } -} - -fn compile_binary( - rt: &mut Runtime, - allocator: &mut Allocator, - first: &Expression, - second: &Expression, - width: VMWidth, - operation: F, -) where - F: FnOnce(&mut Runtime, AsmRegister64, AsmRegister64), -{ - let first = compile_expression(rt, allocator, first, width); - let second = compile_expression(rt, allocator, second, width); - - let pinned = [first, second] - .into_iter() - .filter_map(|value| match value { - ValueRef::Value(value) => Some(value), - ValueRef::Immediate(_) => None, - }) - .collect::>(); - - let result = allocator.acquire(rt, &pinned, &[]); - allocator.copy(rt, first, result); - - let other = allocator.acquire(rt, &pinned, &[result]); - allocator.copy(rt, second, other); - - operation(rt, result, other); - - allocator.replace(result, Value::Output(0), true); - allocator.consume(first); - allocator.consume(second); -} - -fn compile_mul( - rt: &mut Runtime, - allocator: &mut Allocator, - first: &Expression, - second: &Expression, - width: VMWidth, -) { - let first = compile_expression(rt, allocator, first, width); - let second = compile_expression(rt, allocator, second, width); - - let pinned = [first, second] - .into_iter() - .filter_map(|value| match value { - ValueRef::Value(value) => Some(value), - ValueRef::Immediate(_) => None, - }) - .collect::>(); - - let rhs = allocator.acquire(rt, &pinned, &[rax]); - allocator.copy(rt, second, rhs); - allocator.copy(rt, first, rax); - - let signed = width == width.signed(); - - match width.size() { - 8 => { - if signed { - rt.asm.imul(rhs).unwrap(); - } else { - rt.asm.mul(rhs).unwrap(); - } - } - 4 => { - let dword = get_gpr32(register::sized(rhs.into(), 4).unwrap()).unwrap(); - - if signed { - rt.asm.imul(dword).unwrap(); - } else { - rt.asm.mul(dword).unwrap(); - } - } - 2 => { - let word = get_gpr16(register::sized(rhs.into(), 2).unwrap()).unwrap(); - - if signed { - rt.asm.imul(word).unwrap(); - } else { - rt.asm.mul(word).unwrap(); - } - } - 1 => { - let byte = get_gpr8(register::sized(rhs.into(), 1).unwrap()).unwrap(); - - if signed { - rt.asm.imul(byte).unwrap(); - } else { - rt.asm.mul(byte).unwrap(); - } - } - _ => unreachable!(), - } - - allocator.replace(rax, Value::Output(0), true); - allocator.replace(rdx, Value::Output(1), true); - - allocator.consume(first); - allocator.consume(second); -} - -fn compile_shift( - rt: &mut Runtime, - allocator: &mut Allocator, - first: &Expression, - second: &Expression, - width: VMWidth, - operation: F, -) where - F: FnOnce(&mut Runtime, AsmRegister64), -{ - let first = compile_expression(rt, allocator, first, width); - let second = compile_expression(rt, allocator, second, width); - - let pinned = [first, second] - .into_iter() - .filter_map(|value| match value { - ValueRef::Value(value) => Some(value), - ValueRef::Immediate(_) => None, - }) - .collect::>(); - - allocator.copy(rt, second, rcx); - - let result = allocator.acquire(rt, &pinned, &[rcx]); - allocator.copy(rt, first, result); - - operation(rt, result); - - allocator.replace(result, Value::Output(0), true); - allocator.consume(first); - allocator.consume(second); -} - -fn compile_effect(rt: &mut Runtime, allocator: &mut Allocator, effect: &Effect, width: VMWidth) { - match effect { - Effect::Add(first, second) => { - compile_binary(rt, allocator, first, second, width, |rt, left, right| { - rt.asm.add(left, right).unwrap(); - }) - } - Effect::Sub(first, second) => { - compile_binary(rt, allocator, first, second, width, |rt, left, right| { - rt.asm.sub(left, right).unwrap(); - }) - } - Effect::And(first, second) => { - compile_binary(rt, allocator, first, second, width, |rt, left, right| { - rt.asm.and(left, right).unwrap(); - }) - } - Effect::Or(first, second) => { - compile_binary(rt, allocator, first, second, width, |rt, left, right| { - rt.asm.or(left, right).unwrap(); - }) - } - Effect::Xor(first, second) => { - compile_binary(rt, allocator, first, second, width, |rt, left, right| { - rt.asm.xor(left, right).unwrap(); - }) - } - Effect::Mul(first, second) => compile_mul(rt, allocator, first, second, width), - Effect::Shr(first, second) => { - compile_shift(rt, allocator, first, second, width, |rt, result| { - rt.asm.shr(result, cl).unwrap(); - }) - } - Effect::Shl(first, second) => { - compile_shift(rt, allocator, first, second, width, |rt, result| { - rt.asm.shl(result, cl).unwrap(); - }) - } - Effect::Ror(first, second) => { - compile_shift(rt, allocator, first, second, width, |rt, result| { - rt.asm.ror(result, cl).unwrap(); - }) - } - Effect::Rol(first, second) => { - compile_shift(rt, allocator, first, second, width, |rt, result| { - rt.asm.rol(result, cl).unwrap(); - }) - } - Effect::Sar(first, second) => { - compile_shift(rt, allocator, first, second, width, |rt, result| { - rt.asm.sar(result, cl).unwrap(); - }) - } - Effect::Assign(expression) => { - let value = compile_expression(rt, allocator, expression, width); - let register = allocator.acquire(rt, &[], &[]); - allocator.copy(rt, value, register); - - allocator.replace(register, Value::Output(0), true); - allocator.consume(value); - } - Effect::Bsr(expression) => { - compile_unary(rt, allocator, expression, width, |rt, result, source| { - rt.asm.bsr(result, source).unwrap(); - }) - } - Effect::Tzcnt(expression) => { - compile_unary(rt, allocator, expression, width, |rt, result, source| { - rt.asm.tzcnt(result, source).unwrap(); - }) - } - } -} - -fn compile_unary( - rt: &mut Runtime, - allocator: &mut Allocator, - expression: &Expression, - width: VMWidth, - operation: F, -) where - F: FnOnce(&mut Runtime, AsmRegister64, AsmRegister64), -{ - let value = compile_expression(rt, allocator, expression, width); - let src = allocator.acquire(rt, &[], &[]); - - allocator.copy(rt, value, src); - - let mask = allocator.acquire(rt, &[], &[src]); - - rt.asm.mov(mask, width.mask() as i64).unwrap(); - rt.asm.and(src, mask).unwrap(); - - allocator.spill(rt, mask); - - let dst = allocator.mutable(rt, Value::Output(0), &[src]); - - operation(rt, dst, src); - - allocator.dirty.insert(Value::Output(0)); - allocator.consume(value); -} - -fn compile_expression( - rt: &mut Runtime, - allocator: &mut Allocator, - expression: &Expression, - width: VMWidth, -) -> ValueRef { - match expression { - Expression::Operand(Operand::Input(index)) => ValueRef::Value(Value::Input(*index)), - Expression::Operand(Operand::Output(index)) => ValueRef::Value(Value::Output(*index)), - Expression::Constant(value) => ValueRef::Immediate(*value as i64), - Expression::SignBit => ValueRef::Immediate(width.mask().ilog2() as i64), - Expression::Sub(first, second) - | Expression::BitAnd(first, second) - | Expression::BitOr(first, second) - | Expression::BitXor(first, second) => { - let first = compile_expression(rt, allocator, first, width); - let second = compile_expression(rt, allocator, second, width); - - let pinned = [first, second] - .into_iter() - .filter_map(|value| match value { - ValueRef::Value(value) => Some(value), - ValueRef::Immediate(_) => None, - }) - .collect::>(); - - let result = allocator.acquire(rt, &pinned, &[]); - allocator.copy(rt, first, result); - - let other = allocator.acquire(rt, &pinned, &[result]); - allocator.copy(rt, second, other); - - match expression { - Expression::Sub(_, _) => rt.asm.sub(result, other).unwrap(), - Expression::BitAnd(_, _) => rt.asm.and(result, other).unwrap(), - Expression::BitOr(_, _) => rt.asm.or(result, other).unwrap(), - Expression::BitXor(_, _) => rt.asm.xor(result, other).unwrap(), - _ => unreachable!(), - } - - allocator.consume(first); - allocator.consume(second); - - if other != result { - allocator.spill(rt, other); - } - - let value = allocator.temporary(); - allocator.replace(result, value, true); - - ValueRef::Value(value) - } - Expression::BitShr(first, second) | Expression::BitShl(first, second) => { - let first = compile_expression(rt, allocator, first, width); - let second = compile_expression(rt, allocator, second, width); - - let pinned = [first, second] - .into_iter() - .filter_map(|value| match value { - ValueRef::Value(value) => Some(value), - ValueRef::Immediate(_) => None, - }) - .collect::>(); - - allocator.copy(rt, second, rcx); - - let result = allocator.acquire(rt, &pinned, &[rcx]); - allocator.copy(rt, first, result); - - match expression { - Expression::BitShr(_, _) => rt.asm.shr(result, cl).unwrap(), - Expression::BitShl(_, _) => rt.asm.shl(result, cl).unwrap(), - _ => unreachable!(), - } - - allocator.consume(first); - allocator.consume(second); - - let value = allocator.temporary(); - allocator.replace(result, value, true); - - ValueRef::Value(value) - } - Expression::BitNot(inner) | Expression::LowByte(inner) => { - let inner = compile_expression(rt, allocator, inner, width); - let result = allocator.acquire(rt, &[], &[]); - - allocator.copy(rt, inner, result); - - match expression { - Expression::BitNot(_) => rt.asm.not(result).unwrap(), - Expression::LowByte(_) => { - let byte = get_gpr8(register::sized(result.into(), 1).unwrap()).unwrap(); - rt.asm.movzx(result, byte).unwrap(); - } - _ => unreachable!(), - } - - allocator.consume(inner); - - let value = allocator.temporary(); - allocator.replace(result, value, true); - - ValueRef::Value(value) - } - } -} - -fn compile_flags( - rt: &mut Runtime, - allocator: &mut Allocator, - rules: &[(Flag, Condition)], - width: VMWidth, -) { - rt.asm - .mov( - qword_ptr( - rbp - Allocator::offset( - allocator.inputs, - allocator.outputs, - allocator.flags, - Value::Flags, - ), - ), - 0x0, - ) - .unwrap(); - - for (flag, condition) in rules { - match condition { - Condition::Compare(item) => compile_comparison(rt, allocator, *flag, item, width), - Condition::Parity(node) => compile_parity(rt, allocator, *flag, node, width), - } - } - - let register = allocator.acquire(rt, &[], &[]); - - rt.asm - .mov( - register, - qword_ptr( - rbp - Allocator::offset( - allocator.inputs, - allocator.outputs, - allocator.flags, - Value::Flags, - ), - ), - ) - .unwrap(); - vreg::store_reg(rt, r12, register, VMReg::Flags); - - allocator.untrack(register); -} - -fn compile_comparison( - rt: &mut Runtime, - allocator: &mut Allocator, - flag: Flag, - compare: &Compare, - width: VMWidth, -) { - let (first, second, condition) = match compare { - Compare::Equal(first, second) => (first, second, 0u8), - Compare::LessThan(first, second) => (first, second, 1u8), - Compare::GreaterThan(first, second) => (first, second, 2u8), - Compare::BitSet(first, second) => (first, second, 3u8), - }; - - let left = compile_expression(rt, allocator, first, width); - let right = compile_expression(rt, allocator, second, width); - - let pinned = [left, right] - .into_iter() - .filter_map(|value| match value { - ValueRef::Value(value) => Some(value), - ValueRef::Immediate(_) => None, - }) - .collect::>(); - - let lhs = allocator.acquire(rt, &pinned, &[]); - allocator.copy(rt, left, lhs); - - let rhs = allocator.acquire(rt, &pinned, &[lhs]); - allocator.copy(rt, right, rhs); - - let result = allocator.acquire(rt, &pinned, &[lhs, rhs]); - let byte = get_gpr8(register::sized(result.into(), 1).unwrap()).unwrap(); - - match condition { - 0 | 1 | 2 => rt.asm.cmp(lhs, rhs).unwrap(), - 3 => rt.asm.bt(lhs, rhs).unwrap(), - _ => unreachable!(), - } - - match condition { - 0 => rt.asm.sete(byte).unwrap(), - 1 => rt.asm.setb(byte).unwrap(), - 2 => rt.asm.seta(byte).unwrap(), - 3 => rt.asm.setc(byte).unwrap(), - _ => unreachable!(), - } - - rt.asm.movzx(result, byte).unwrap(); - rt.asm - .shl(result, flag.bit32().trailing_zeros() as i32) - .unwrap(); - rt.asm - .or( - qword_ptr( - rbp - Allocator::offset( - allocator.inputs, - allocator.outputs, - allocator.flags, - Value::Flags, - ), - ), - result, - ) - .unwrap(); - - allocator.spill(rt, result); - allocator.consume(left); - allocator.consume(right); - allocator.spill(rt, lhs); - - if rhs != lhs { - allocator.spill(rt, rhs); - } -} - -fn compile_parity( - rt: &mut Runtime, - allocator: &mut Allocator, - flag: Flag, - node: &Expression, - width: VMWidth, -) { - let value = compile_expression(rt, allocator, node, width); - let register = allocator.acquire(rt, &[], &[]); - - allocator.copy(rt, value, register); - - let byte = get_gpr8(register::sized(register.into(), 1).unwrap()).unwrap(); - rt.asm.movzx(register, byte).unwrap(); - rt.asm.popcnt(register, register).unwrap(); - rt.asm.not(register).unwrap(); - rt.asm.and(register, 0x1).unwrap(); - rt.asm - .shl(register, flag.bit32().trailing_zeros() as i32) - .unwrap(); - rt.asm - .or( - qword_ptr( - rbp - Allocator::offset( - allocator.inputs, - allocator.outputs, - allocator.flags, - Value::Flags, - ), - ), - register, - ) - .unwrap(); - - allocator.untrack(register); - allocator.consume(value); -} diff --git a/runtime/src/vm/handlers/semantic/allocator.rs b/runtime/src/vm/handlers/semantic/allocator.rs new file mode 100644 index 00000000..cedf8d56 --- /dev/null +++ b/runtime/src/vm/handlers/semantic/allocator.rs @@ -0,0 +1,223 @@ +use iced_x86::code_asm::{qword_ptr, r8, r9, rax, rbp, rcx, rdx, AsmRegister64}; + +use std::collections::HashSet; + +use crate::{ + runtime::Runtime, + vm::handlers::semantic::{Reference, Value}, +}; + +#[derive(Clone)] +pub struct Allocator { + tracked: Vec<(AsmRegister64, Value)>, + dirty: HashSet, + temporary: usize, + inputs: usize, + outputs: usize, + flags: bool, +} + +impl Allocator { + const REGISTERS: [AsmRegister64; 5] = [rax, rcx, rdx, r8, r9]; + + pub fn new(inputs: usize, outputs: usize, flags: bool) -> Self { + Self { + tracked: Vec::new(), + dirty: HashSet::new(), + temporary: 0, + inputs, + outputs, + flags, + } + } + + fn track(&mut self, register: AsmRegister64, value: Value, dirty: bool) { + assert!( + self.tracked.iter().all(|(r, _)| *r != register), + "register {:?} is already tracked", + register + ); + assert!( + self.tracked.iter().all(|(_, v)| *v != value), + "value {:?} is already tracked", + value + ); + self.tracked.push((register, value)); + + if dirty { + self.dirty.insert(value); + } else { + self.dirty.remove(&value); + } + } + + pub fn untrack(&mut self, register: AsmRegister64) -> Option { + let index = self.tracked.iter().position(|(r, _)| *r == register)?; + Some(self.tracked.swap_remove(index).1) + } + + fn tracked_register(&self, value: Value) -> Option { + self.tracked + .iter() + .find(|(_, v)| *v == value) + .map(|(r, _)| *r) + } + + fn tracked_value(&self, register: AsmRegister64) -> Option { + self.tracked + .iter() + .find(|(r, _)| *r == register) + .map(|(_, v)| *v) + } + + pub fn temporary(&mut self) -> Value { + let value = Value::Temporary(self.temporary); + self.temporary += 1; + value + } + + pub fn offset(&self, value: Value) -> i32 { + let flags = self.flags as usize; + let slot = match value { + Value::Input(index) => index, + Value::Output(index) => self.inputs + index, + Value::Flags => self.inputs + self.outputs, + Value::Temporary(index) => self.inputs + self.outputs + flags + index, + }; + (slot as i32 + 1) * 8 + } + + pub fn dirty(&mut self, value: Value) { + self.dirty.insert(value); + } + + pub fn spill(&mut self, rt: &mut Runtime, register: AsmRegister64) { + let Some(value) = self.tracked_value(register) else { + return; + }; + + if self.dirty.remove(&value) { + rt.asm + .mov(qword_ptr(rbp - self.offset(value)), register) + .unwrap(); + } + + self.untrack(register); + } + + pub fn dump(&mut self, rt: &mut Runtime) { + let registers = self + .tracked + .iter() + .map(|(register, _)| *register) + .collect::>(); + + for register in registers { + self.spill(rt, register); + } + } + + pub fn acquire( + &mut self, + rt: &mut Runtime, + pinned: &[Value], + avoid: &[AsmRegister64], + ) -> AsmRegister64 { + if let Some(register) = Self::REGISTERS + .iter() + .copied() + .find(|register| !avoid.contains(register) && self.tracked_value(*register).is_none()) + { + return register; + } + + if let Some(register) = Self::REGISTERS.iter().copied().find(|register| { + if avoid.contains(register) { + return false; + } + + match self.tracked_value(*register) { + Some(value) => !pinned.contains(&value), + None => true, + } + }) { + self.spill(rt, register); + return register; + } + + let register = Self::REGISTERS + .iter() + .copied() + .find(|register| !avoid.contains(register)) + .expect("allocator has no usable register"); + self.spill(rt, register); + register + } + + pub fn copy(&mut self, rt: &mut Runtime, src: Reference, dst: AsmRegister64) { + self.spill(rt, dst); + + match src { + Reference::Immediate(value) => { + rt.asm.mov(dst, value).unwrap(); + } + Reference::Value(value) => { + if let Some(src) = self.tracked_register(value) { + rt.asm.mov(dst, src).unwrap(); + } else { + rt.asm + .mov(dst, qword_ptr(rbp - self.offset(value))) + .unwrap(); + } + } + } + } + + pub fn mutable( + &mut self, + rt: &mut Runtime, + value: Value, + avoid: &[AsmRegister64], + ) -> AsmRegister64 { + if let Some(register) = self.tracked_register(value) { + if !avoid.contains(®ister) { + return register; + } + } + + let register = self.acquire(rt, &[], avoid); + rt.asm + .mov(register, qword_ptr(rbp - self.offset(value))) + .unwrap(); + self.track(register, value, false); + register + } + + pub fn replace(&mut self, register: AsmRegister64, value: Value, dirty: bool) { + self.untrack(register); + + if let Some(previous) = self + .tracked + .iter() + .find(|(_, v)| *v == value) + .map(|(r, _)| *r) + { + self.untrack(previous); + } + + self.track(register, value, dirty); + } + + pub fn consume(&mut self, value: Reference) { + let Reference::Value(value) = value else { + return; + }; + + if matches!(value, Value::Temporary(_)) { + if let Some(register) = self.tracked_register(value) { + self.untrack(register); + } + self.dirty.remove(&value); + } + } +} diff --git a/runtime/src/vm/handlers/semantic/compiler.rs b/runtime/src/vm/handlers/semantic/compiler.rs new file mode 100644 index 00000000..d9910e33 --- /dev/null +++ b/runtime/src/vm/handlers/semantic/compiler.rs @@ -0,0 +1,730 @@ +use iced_x86::code_asm::{ + cl, get_gpr16, get_gpr32, get_gpr8, qword_ptr, r12, r13, r14, r14d, rax, rbp, rcx, rdx, rsp, + AsmRegister16, AsmRegister32, AsmRegister64, AsmRegister8, CodeAssembler, CodeLabel, +}; + +use crate::{ + register, + runtime::Runtime, + vm::{ + bytecode::{Flag, VMReg, VMWidth}, + handlers::semantic::{ + allocator::Allocator, Compare, Condition, Effect, Expression, Operand, Operation, + Reference, Value, + }, + utils::{bytecode, scratch, vreg}, + }, +}; + +#[cfg(debug_assertions)] +#[track_caller] +pub fn compile(rt: &mut Runtime, operation: &Operation) { + #[cfg(debug_assertions)] + let index = rt.asm.instructions().len(); + + let mut epilogue = rt.asm.create_label(); + let operands = operation.operands(); + let outputs = operation.outputs(); + let flags = !operation.flags.is_empty(); + let temporaries = operation.temporaries(); + let slots = operands + outputs + flags as usize + temporaries; + let size = ((slots as i32 * 8) + 15) & !15; + let allocator = Allocator::new(operands, outputs, flags); + + rt.asm.push(r13).unwrap(); + rt.asm.push(r14).unwrap(); + rt.asm.push(rbp).unwrap(); + rt.asm.mov(rbp, rsp).unwrap(); + rt.asm.sub(rsp, size).unwrap(); + + rt.asm.mov(r13, rcx).unwrap(); + + bytecode::read_byte_zx(rt, r13, r14d); + + for index in (0..operands).rev() { + scratch::load(rt, r12, rax); + + rt.asm + .mov(qword_ptr(rbp - allocator.offset(Value::Input(index))), rax) + .unwrap(); + } + + let handlers = operation + .widths + .iter() + .map(|&width| { + let actions = operation.effects.clone(); + let rules = operation.flags.clone(); + + ( + width, + Box::new(move |rt: &mut Runtime, allocator: &mut Allocator| { + compile_effects(rt, allocator, &actions, width); + + for index in 0..outputs { + let output = Value::Output(index); + let register = allocator.mutable(rt, output, &[]); + let mask = allocator.acquire(rt, &[output], &[register]); + + rt.asm.mov(mask, width.mask() as i64).unwrap(); + rt.asm.and(register, mask).unwrap(); + + allocator.spill(rt, mask); + allocator.dirty(output); + } + + if !rules.is_empty() { + compile_flags(rt, allocator, &rules, width); + } + }) as Box, + ) + }) + .collect::)>>(); + + let cases = handlers + .iter() + .map( + |(width, _function): &(VMWidth, Box)| { + (rt.mapper.index(*width) as u8, rt.asm.create_label()) + }, + ) + .collect::>(); + + rt.jumps( + r14, + cases + .iter() + .map(|(key, label): &(u8, CodeLabel)| (*key, *label)) + .collect::>(), + ); + + for ((_key, mut label), (_width, handler)) in cases.into_iter().zip(handlers.into_iter()) { + rt.asm.set_label(&mut label).unwrap(); + + let mut local = Allocator::new(operands, outputs, flags); + + handler(rt, &mut local); + + local.dump(rt); + + rt.asm.jmp(epilogue).unwrap(); + } + + rt.asm.set_label(&mut epilogue).unwrap(); + + match &operation.stores { + Some(stores) => { + let mut allocator = Allocator::new(operands, outputs, flags); + + for expression in stores { + let src = compile_expression(rt, &mut allocator, expression, VMWidth::Lower64); + + let dst = allocator.acquire(rt, &[], &[]); + + allocator.copy(rt, src, dst); + + scratch::store(rt, r12, dst); + + allocator.spill(rt, dst); + allocator.consume(src); + } + } + None => { + let mut allocator = Allocator::new(operands, outputs, flags); + + for index in (0..outputs).rev() { + let dst = allocator.acquire(rt, &[], &[]); + allocator.copy(rt, Reference::Value(Value::Output(index)), dst); + scratch::store(rt, r12, dst); + allocator.spill(rt, dst); + } + } + } + + rt.asm.mov(rax, r13).unwrap(); + + rt.asm.add(rsp, size).unwrap(); + rt.asm.pop(rbp).unwrap(); + rt.asm.pop(r14).unwrap(); + rt.asm.pop(r13).unwrap(); + rt.asm.ret().unwrap(); + + #[cfg(debug_assertions)] + { + use std::panic::Location; + + use logger::debug; + + let location = Location::caller(); + + debug!("{}", location.file()); + + for instruction in &rt.asm.instructions()[index..] { + debug!("{}{instruction}", " ".repeat(4)); + } + } +} + +fn compile_effects( + rt: &mut Runtime, + allocator: &mut Allocator, + effects: &[Effect], + width: VMWidth, +) { + for effect in effects { + compile_effect(rt, allocator, effect, width); + } +} + +fn compile_binary( + rt: &mut Runtime, + allocator: &mut Allocator, + first: &Expression, + second: &Expression, + width: VMWidth, + operation: F, +) where + F: FnOnce(&mut Runtime, AsmRegister64, AsmRegister64), +{ + let first = compile_expression(rt, allocator, first, width); + let second = compile_expression(rt, allocator, second, width); + + let pinned = [first, second] + .into_iter() + .filter_map(|value| match value { + Reference::Value(value) => Some(value), + Reference::Immediate(_) => None, + }) + .collect::>(); + + let dst = allocator.acquire(rt, &pinned, &[]); + allocator.copy(rt, first, dst); + + let src = allocator.acquire(rt, &pinned, &[dst]); + allocator.copy(rt, second, src); + + operation(rt, dst, src); + + allocator.replace(dst, Value::Output(0), true); + allocator.consume(first); + allocator.consume(second); +} + +fn compile_mul( + rt: &mut Runtime, + allocator: &mut Allocator, + first: &Expression, + second: &Expression, + width: VMWidth, +) { + let first = compile_expression(rt, allocator, first, width); + let second = compile_expression(rt, allocator, second, width); + + let pinned = [first, second] + .into_iter() + .filter_map(|value| match value { + Reference::Value(value) => Some(value), + Reference::Immediate(_) => None, + }) + .collect::>(); + + let src = allocator.acquire(rt, &pinned, &[rax]); + allocator.copy(rt, second, src); + allocator.copy(rt, first, rax); + + compile_implicit( + rt, + width, + src, + |asm, byte| asm.mul(byte).unwrap(), + |asm, word| asm.mul(word).unwrap(), + |asm, dword| asm.mul(dword).unwrap(), + |asm, qword| asm.mul(qword).unwrap(), + |asm, byte| asm.imul(byte).unwrap(), + |asm, word| asm.imul(word).unwrap(), + |asm, dword| asm.imul(dword).unwrap(), + |asm, qword| asm.imul(qword).unwrap(), + ); + + allocator.replace(rax, Value::Output(0), true); + allocator.replace(rdx, Value::Output(1), true); + + allocator.consume(first); + allocator.consume(second); +} + +fn compile_div( + rt: &mut Runtime, + allocator: &mut Allocator, + first: &Expression, + second: &Expression, + third: &Expression, + width: VMWidth, +) { + let first = compile_expression(rt, allocator, first, width); + let second = compile_expression(rt, allocator, second, width); + let third = compile_expression(rt, allocator, third, width); + + let pinned = [first, second, third] + .into_iter() + .filter_map(|value| match value { + Reference::Value(value) => Some(value), + Reference::Immediate(_) => None, + }) + .collect::>(); + + let src = allocator.acquire(rt, &pinned, &[rax, rdx]); + + allocator.copy(rt, first, src); + allocator.copy(rt, second, rax); + allocator.copy(rt, third, rdx); + + compile_implicit( + rt, + width, + src, + |asm, byte| asm.div(byte).unwrap(), + |asm, word| asm.div(word).unwrap(), + |asm, dword| asm.div(dword).unwrap(), + |asm, qword| asm.div(qword).unwrap(), + |asm, byte| asm.idiv(byte).unwrap(), + |asm, word| asm.idiv(word).unwrap(), + |asm, dword| asm.idiv(dword).unwrap(), + |asm, qword| asm.idiv(qword).unwrap(), + ); + + allocator.replace(rax, Value::Output(0), true); + allocator.replace(rdx, Value::Output(1), true); + + allocator.consume(first); + allocator.consume(second); + allocator.consume(third); +} + +fn compile_implicit( + rt: &mut Runtime, + width: VMWidth, + src: AsmRegister64, + unsigned8: U8, + unsigned16: U16, + unsigned32: U32, + unsigned64: U64, + signed8: S8, + signed16: S16, + signed32: S32, + signed64: S64, +) where + U8: FnOnce(&mut CodeAssembler, AsmRegister8), + U16: FnOnce(&mut CodeAssembler, AsmRegister16), + U32: FnOnce(&mut CodeAssembler, AsmRegister32), + U64: FnOnce(&mut CodeAssembler, AsmRegister64), + S8: FnOnce(&mut CodeAssembler, AsmRegister8), + S16: FnOnce(&mut CodeAssembler, AsmRegister16), + S32: FnOnce(&mut CodeAssembler, AsmRegister32), + S64: FnOnce(&mut CodeAssembler, AsmRegister64), +{ + let signed = width == width.signed(); + + match (width.size(), signed) { + (1, false) => { + let byte = get_gpr8(register::sized(src.into(), 1).unwrap()).unwrap(); + unsigned8(&mut rt.asm, byte); + } + (2, false) => { + let word = get_gpr16(register::sized(src.into(), 2).unwrap()).unwrap(); + unsigned16(&mut rt.asm, word); + } + (4, false) => { + let dword = get_gpr32(register::sized(src.into(), 4).unwrap()).unwrap(); + unsigned32(&mut rt.asm, dword); + } + (8, false) => { + unsigned64(&mut rt.asm, src); + } + (1, true) => { + let byte = get_gpr8(register::sized(src.into(), 1).unwrap()).unwrap(); + signed8(&mut rt.asm, byte); + } + (2, true) => { + let word = get_gpr16(register::sized(src.into(), 2).unwrap()).unwrap(); + signed16(&mut rt.asm, word); + } + (4, true) => { + let dword = get_gpr32(register::sized(src.into(), 4).unwrap()).unwrap(); + signed32(&mut rt.asm, dword); + } + (8, true) => { + signed64(&mut rt.asm, src); + } + _ => unreachable!(), + } +} + +fn compile_shift( + rt: &mut Runtime, + allocator: &mut Allocator, + first: &Expression, + second: &Expression, + width: VMWidth, + operation: F, +) where + F: FnOnce(&mut Runtime, AsmRegister64), +{ + let first = compile_expression(rt, allocator, first, width); + let second = compile_expression(rt, allocator, second, width); + + let pinned = [first, second] + .into_iter() + .filter_map(|value| match value { + Reference::Value(value) => Some(value), + Reference::Immediate(_) => None, + }) + .collect::>(); + + allocator.copy(rt, second, rcx); + + let dst = allocator.acquire(rt, &pinned, &[rcx]); + allocator.copy(rt, first, dst); + + operation(rt, dst); + + allocator.replace(dst, Value::Output(0), true); + allocator.consume(first); + allocator.consume(second); +} + +fn compile_effect(rt: &mut Runtime, allocator: &mut Allocator, effect: &Effect, width: VMWidth) { + match effect { + Effect::Add(first, second) => { + compile_binary(rt, allocator, first, second, width, |rt, first, second| { + rt.asm.add(first, second).unwrap(); + }) + } + Effect::Sub(first, second) => { + compile_binary(rt, allocator, first, second, width, |rt, first, second| { + rt.asm.sub(first, second).unwrap(); + }) + } + Effect::And(first, second) => { + compile_binary(rt, allocator, first, second, width, |rt, first, second| { + rt.asm.and(first, second).unwrap(); + }) + } + Effect::Or(first, second) => { + compile_binary(rt, allocator, first, second, width, |rt, first, second| { + rt.asm.or(first, second).unwrap(); + }) + } + Effect::Xor(first, second) => { + compile_binary(rt, allocator, first, second, width, |rt, first, second| { + rt.asm.xor(first, second).unwrap(); + }) + } + Effect::Mul(first, second) => compile_mul(rt, allocator, first, second, width), + Effect::Div(first, second, third) => { + compile_div(rt, allocator, first, second, third, width) + } + Effect::Shr(first, second) => { + compile_shift(rt, allocator, first, second, width, |rt, dst| { + rt.asm.shr(dst, cl).unwrap(); + }) + } + Effect::Shl(first, second) => { + compile_shift(rt, allocator, first, second, width, |rt, dst| { + rt.asm.shl(dst, cl).unwrap(); + }) + } + Effect::Ror(first, second) => { + compile_shift(rt, allocator, first, second, width, |rt, dst| { + rt.asm.ror(dst, cl).unwrap(); + }) + } + Effect::Rol(first, second) => { + compile_shift(rt, allocator, first, second, width, |rt, dst| { + rt.asm.rol(dst, cl).unwrap(); + }) + } + Effect::Sar(first, second) => { + compile_shift(rt, allocator, first, second, width, |rt, dst| { + rt.asm.sar(dst, cl).unwrap(); + }) + } + Effect::Assign(expression) => { + let src = compile_expression(rt, allocator, expression, width); + let dst = allocator.acquire(rt, &[], &[]); + allocator.copy(rt, src, dst); + + allocator.replace(dst, Value::Output(0), true); + allocator.consume(src); + } + Effect::Bsr(expression) => { + compile_unary(rt, allocator, expression, width, |rt, dst, src| { + rt.asm.bsr(dst, src).unwrap(); + }) + } + Effect::Tzcnt(expression) => { + compile_unary(rt, allocator, expression, width, |rt, dst, src| { + rt.asm.tzcnt(dst, src).unwrap(); + }) + } + } +} + +fn compile_unary( + rt: &mut Runtime, + allocator: &mut Allocator, + expression: &Expression, + width: VMWidth, + operation: F, +) where + F: FnOnce(&mut Runtime, AsmRegister64, AsmRegister64), +{ + let value = compile_expression(rt, allocator, expression, width); + let src = allocator.acquire(rt, &[], &[]); + + allocator.copy(rt, value, src); + + let mask = allocator.acquire(rt, &[], &[src]); + + rt.asm.mov(mask, width.mask() as i64).unwrap(); + rt.asm.and(src, mask).unwrap(); + + allocator.spill(rt, mask); + + let dst = allocator.mutable(rt, Value::Output(0), &[src]); + + operation(rt, dst, src); + + allocator.dirty(Value::Output(0)); + allocator.consume(value); +} + +fn compile_expression( + rt: &mut Runtime, + allocator: &mut Allocator, + expression: &Expression, + width: VMWidth, +) -> Reference { + match expression { + Expression::Operand(Operand::Input(index)) => Reference::Value(Value::Input(*index)), + Expression::Operand(Operand::Output(index)) => Reference::Value(Value::Output(*index)), + Expression::Constant(value) => Reference::Immediate(*value as i64), + Expression::SignBit => Reference::Immediate(width.mask().ilog2() as i64), + Expression::Sub(first, second) + | Expression::BitAnd(first, second) + | Expression::BitOr(first, second) + | Expression::BitXor(first, second) => { + let first = compile_expression(rt, allocator, first, width); + let second = compile_expression(rt, allocator, second, width); + + let pinned = [first, second] + .into_iter() + .filter_map(|value| match value { + Reference::Value(value) => Some(value), + Reference::Immediate(_) => None, + }) + .collect::>(); + + let dst = allocator.acquire(rt, &pinned, &[]); + allocator.copy(rt, first, dst); + + let src = allocator.acquire(rt, &pinned, &[dst]); + allocator.copy(rt, second, src); + + match expression { + Expression::Sub(_, _) => rt.asm.sub(dst, src).unwrap(), + Expression::BitAnd(_, _) => rt.asm.and(dst, src).unwrap(), + Expression::BitOr(_, _) => rt.asm.or(dst, src).unwrap(), + Expression::BitXor(_, _) => rt.asm.xor(dst, src).unwrap(), + _ => unreachable!(), + } + + allocator.consume(first); + allocator.consume(second); + + if src != dst { + allocator.spill(rt, src); + } + + let tmp = allocator.temporary(); + allocator.replace(dst, tmp, true); + + Reference::Value(tmp) + } + Expression::BitShr(first, second) | Expression::BitShl(first, second) => { + let first = compile_expression(rt, allocator, first, width); + let second = compile_expression(rt, allocator, second, width); + + let pinned = [first, second] + .into_iter() + .filter_map(|value| match value { + Reference::Value(value) => Some(value), + Reference::Immediate(_) => None, + }) + .collect::>(); + + allocator.copy(rt, second, rcx); + + let dst = allocator.acquire(rt, &pinned, &[rcx]); + allocator.copy(rt, first, dst); + + match expression { + Expression::BitShr(_, _) => rt.asm.shr(dst, cl).unwrap(), + Expression::BitShl(_, _) => rt.asm.shl(dst, cl).unwrap(), + _ => unreachable!(), + } + + allocator.consume(first); + allocator.consume(second); + + let tmp = allocator.temporary(); + allocator.replace(dst, tmp, true); + + Reference::Value(tmp) + } + Expression::BitNot(inner) | Expression::LowByte(inner) => { + let src = compile_expression(rt, allocator, inner, width); + + let dst = allocator.acquire(rt, &[], &[]); + + allocator.copy(rt, src, dst); + + match expression { + Expression::BitNot(_) => rt.asm.not(dst).unwrap(), + Expression::LowByte(_) => { + let byte = get_gpr8(register::sized(dst.into(), 1).unwrap()).unwrap(); + rt.asm.movzx(dst, byte).unwrap(); + } + _ => unreachable!(), + } + + allocator.consume(src); + + let tmp = allocator.temporary(); + allocator.replace(dst, tmp, true); + + Reference::Value(tmp) + } + } +} + +fn compile_flags( + rt: &mut Runtime, + allocator: &mut Allocator, + flags: &[(Flag, Condition)], + width: VMWidth, +) { + rt.asm + .mov(qword_ptr(rbp - allocator.offset(Value::Flags)), 0x0) + .unwrap(); + + for (flag, condition) in flags { + match condition { + Condition::Compare(item) => compile_comparison(rt, allocator, *flag, item, width), + Condition::Parity(node) => compile_parity(rt, allocator, *flag, node, width), + } + } + + let register = allocator.acquire(rt, &[], &[]); + + rt.asm + .mov(register, qword_ptr(rbp - allocator.offset(Value::Flags))) + .unwrap(); + vreg::store_reg(rt, r12, register, VMReg::Flags); + + allocator.untrack(register); +} + +fn compile_comparison( + rt: &mut Runtime, + allocator: &mut Allocator, + flag: Flag, + compare: &Compare, + width: VMWidth, +) { + let (first, second, condition) = match compare { + Compare::Equal(first, second) => (first, second, 0u8), + Compare::LessThan(first, second) => (first, second, 1u8), + Compare::GreaterThan(first, second) => (first, second, 2u8), + Compare::BitSet(first, second) => (first, second, 3u8), + }; + + let first = compile_expression(rt, allocator, first, width); + let second = compile_expression(rt, allocator, second, width); + + let pinned = [first, second] + .into_iter() + .filter_map(|value| match value { + Reference::Value(value) => Some(value), + Reference::Immediate(_) => None, + }) + .collect::>(); + + let dst = allocator.acquire(rt, &pinned, &[]); + allocator.copy(rt, first, dst); + + let src = allocator.acquire(rt, &pinned, &[dst]); + allocator.copy(rt, second, src); + + let tmp = allocator.acquire(rt, &pinned, &[dst, src]); + let byte = get_gpr8(register::sized(tmp.into(), 1).unwrap()).unwrap(); + + match condition { + 0 | 1 | 2 => rt.asm.cmp(dst, src).unwrap(), + 3 => rt.asm.bt(dst, src).unwrap(), + _ => unreachable!(), + } + + match condition { + 0 => rt.asm.sete(byte).unwrap(), + 1 => rt.asm.setb(byte).unwrap(), + 2 => rt.asm.seta(byte).unwrap(), + 3 => rt.asm.setc(byte).unwrap(), + _ => unreachable!(), + } + + rt.asm.movzx(tmp, byte).unwrap(); + rt.asm + .shl(tmp, flag.bit32().trailing_zeros() as i32) + .unwrap(); + rt.asm + .or(qword_ptr(rbp - allocator.offset(Value::Flags)), tmp) + .unwrap(); + + allocator.spill(rt, tmp); + allocator.consume(first); + allocator.consume(second); + allocator.spill(rt, dst); + + if src != dst { + allocator.spill(rt, src); + } +} + +fn compile_parity( + rt: &mut Runtime, + allocator: &mut Allocator, + flag: Flag, + node: &Expression, + width: VMWidth, +) { + let src = compile_expression(rt, allocator, node, width); + let dst = allocator.acquire(rt, &[], &[]); + + allocator.copy(rt, src, dst); + + let byte = get_gpr8(register::sized(dst.into(), 1).unwrap()).unwrap(); + rt.asm.movzx(dst, byte).unwrap(); + rt.asm.popcnt(dst, dst).unwrap(); + rt.asm.not(dst).unwrap(); + rt.asm.and(dst, 0x1).unwrap(); + rt.asm + .shl(dst, flag.bit32().trailing_zeros() as i32) + .unwrap(); + rt.asm + .or(qword_ptr(rbp - allocator.offset(Value::Flags)), dst) + .unwrap(); + + allocator.untrack(dst); + allocator.consume(src); +} diff --git a/runtime/src/vm/handlers/semantic/mod.rs b/runtime/src/vm/handlers/semantic/mod.rs new file mode 100644 index 00000000..d82e4dfd --- /dev/null +++ b/runtime/src/vm/handlers/semantic/mod.rs @@ -0,0 +1,285 @@ +use crate::vm::bytecode::{Flag, VMWidth}; + +mod allocator; +pub mod compiler; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Operand { + Input(usize), + Output(usize), +} + +#[derive(Debug, Clone)] +pub enum Expression { + Operand(Operand), + Constant(u64), + BitAnd(Box, Box), + BitOr(Box, Box), + BitXor(Box, Box), + BitShr(Box, Box), + BitShl(Box, Box), + BitNot(Box), + Sub(Box, Box), + LowByte(Box), + SignBit, +} + +#[derive(Debug, Clone)] +pub enum Effect { + Add(Expression, Expression), + Sub(Expression, Expression), + And(Expression, Expression), + Or(Expression, Expression), + Xor(Expression, Expression), + Shr(Expression, Expression), + Shl(Expression, Expression), + Ror(Expression, Expression), + Rol(Expression, Expression), + Mul(Expression, Expression), + Div(Expression, Expression, Expression), + Assign(Expression), + Sar(Expression, Expression), + Bsr(Expression), + Tzcnt(Expression), +} + +#[derive(Debug, Clone)] +pub enum Compare { + Equal(Expression, Expression), + LessThan(Expression, Expression), + GreaterThan(Expression, Expression), + BitSet(Expression, Expression), +} + +#[derive(Debug, Clone)] +pub enum Condition { + Compare(Compare), + Parity(Expression), +} + +#[derive(Debug)] +pub struct Operation { + pub effects: Vec, + pub flags: Vec<(Flag, Condition)>, + pub stores: Option>, + pub widths: &'static [VMWidth], +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +enum Value { + Input(usize), + Output(usize), + Flags, + Temporary(usize), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Reference { + Value(Value), + Immediate(i64), +} + +// TODO: Implement a mixed-boolean-arithmetic engine! + +impl Operation { + fn operands(&self) -> usize { + let mut value = 0; + + for effect in &self.effects { + effect.operands(&mut value); + } + + for (_, condition) in &self.flags { + condition.operands(&mut value); + } + + if let Some(stores) = &self.stores { + for store in stores { + store.operands(&mut value); + } + } + + value + } + + fn outputs(&self) -> usize { + let mut value = 0; + + for effect in &self.effects { + effect.outputs(&mut value); + } + + if let Some(stores) = &self.stores { + value = value.max(stores.len()); + } + + value + } + + fn temporaries(&self) -> usize { + let effects = self.effects.iter().map(Effect::temporary).sum::(); + let flags = self + .flags + .iter() + .map(|(_, condition)| condition.temporary()) + .sum::(); + let stores = self + .stores + .as_ref() + .map(|stores| stores.iter().map(Expression::temporary).sum::()) + .unwrap_or(0); + + effects + flags + stores + } +} + +impl Expression { + fn operands(&self, value: &mut usize) { + match self { + Expression::Operand(Operand::Input(index)) => { + *value = (*value).max(*index + 1); + } + Expression::Operand(Operand::Output(_)) + | Expression::Constant(_) + | Expression::SignBit => {} + Expression::BitAnd(lhs, rhs) + | Expression::BitOr(lhs, rhs) + | Expression::BitXor(lhs, rhs) + | Expression::BitShr(lhs, rhs) + | Expression::BitShl(lhs, rhs) + | Expression::Sub(lhs, rhs) => { + lhs.operands(value); + rhs.operands(value); + } + Expression::BitNot(inner) | Expression::LowByte(inner) => { + inner.operands(value); + } + } + } + + fn temporary(&self) -> usize { + match self { + Expression::Operand(_) | Expression::Constant(_) | Expression::SignBit => 0, + Expression::BitAnd(lhs, rhs) + | Expression::BitOr(lhs, rhs) + | Expression::BitXor(lhs, rhs) + | Expression::BitShr(lhs, rhs) + | Expression::BitShl(lhs, rhs) + | Expression::Sub(lhs, rhs) => 1 + lhs.temporary() + rhs.temporary(), + Expression::BitNot(inner) | Expression::LowByte(inner) => 1 + inner.temporary(), + } + } +} + +impl Effect { + fn operands(&self, value: &mut usize) { + match self { + Effect::Add(lhs, rhs) + | Effect::Sub(lhs, rhs) + | Effect::And(lhs, rhs) + | Effect::Or(lhs, rhs) + | Effect::Xor(lhs, rhs) + | Effect::Shr(lhs, rhs) + | Effect::Shl(lhs, rhs) + | Effect::Ror(lhs, rhs) + | Effect::Rol(lhs, rhs) + | Effect::Mul(lhs, rhs) + | Effect::Sar(lhs, rhs) => { + lhs.operands(value); + rhs.operands(value); + } + Effect::Div(divisor, low, high) => { + divisor.operands(value); + low.operands(value); + high.operands(value); + } + Effect::Assign(expression) | Effect::Bsr(expression) | Effect::Tzcnt(expression) => { + expression.operands(value); + } + } + } + + fn outputs(&self, value: &mut usize) { + match self { + Effect::Add(..) + | Effect::Sub(..) + | Effect::And(..) + | Effect::Or(..) + | Effect::Xor(..) + | Effect::Shr(..) + | Effect::Shl(..) + | Effect::Ror(..) + | Effect::Rol(..) + | Effect::Sar(..) + | Effect::Assign(..) + | Effect::Bsr(..) + | Effect::Tzcnt(..) => { + *value = (*value).max(1); + } + Effect::Mul(..) | Effect::Div(..) => { + *value = (*value).max(2); + } + } + } + + fn temporary(&self) -> usize { + match self { + Effect::Add(lhs, rhs) + | Effect::Sub(lhs, rhs) + | Effect::And(lhs, rhs) + | Effect::Or(lhs, rhs) + | Effect::Xor(lhs, rhs) + | Effect::Shr(lhs, rhs) + | Effect::Shl(lhs, rhs) + | Effect::Ror(lhs, rhs) + | Effect::Rol(lhs, rhs) + | Effect::Mul(lhs, rhs) + | Effect::Sar(lhs, rhs) => lhs.temporary() + rhs.temporary(), + Effect::Div(divisor, low, high) => { + divisor.temporary() + low.temporary() + high.temporary() + } + Effect::Assign(expression) | Effect::Bsr(expression) | Effect::Tzcnt(expression) => { + expression.temporary() + } + } + } +} + +impl Condition { + fn operands(&self, value: &mut usize) { + match self { + Condition::Compare(compare) => compare.operands(value), + Condition::Parity(expression) => expression.operands(value), + } + } + + fn temporary(&self) -> usize { + match self { + Condition::Compare(compare) => compare.temporary(), + Condition::Parity(expression) => expression.temporary(), + } + } +} + +impl Compare { + fn operands(&self, value: &mut usize) { + match self { + Compare::Equal(lhs, rhs) + | Compare::LessThan(lhs, rhs) + | Compare::GreaterThan(lhs, rhs) + | Compare::BitSet(lhs, rhs) => { + lhs.operands(value); + rhs.operands(value); + } + } + } + + fn temporary(&self) -> usize { + match self { + Compare::Equal(lhs, rhs) + | Compare::LessThan(lhs, rhs) + | Compare::GreaterThan(lhs, rhs) + | Compare::BitSet(lhs, rhs) => lhs.temporary() + rhs.temporary(), + } + } +} diff --git a/runtime/src/vm/handlers/shl.rs b/runtime/src/vm/handlers/shl.rs index 5e878aa1..044fcadf 100644 --- a/runtime/src/vm/handlers/shl.rs +++ b/runtime/src/vm/handlers/shl.rs @@ -7,7 +7,7 @@ use crate::{ }; pub fn build(rt: &mut Runtime) { - semantic::build( + semantic::compiler::compile( rt, &Operation { effects: vec![Effect::Shl( diff --git a/runtime/src/vm/handlers/shr.rs b/runtime/src/vm/handlers/shr.rs index 7653079f..d1acb025 100644 --- a/runtime/src/vm/handlers/shr.rs +++ b/runtime/src/vm/handlers/shr.rs @@ -7,7 +7,7 @@ use crate::{ }; pub fn build(rt: &mut Runtime) { - semantic::build( + semantic::compiler::compile( rt, &Operation { effects: vec![Effect::Shr( diff --git a/runtime/src/vm/handlers/sub.rs b/runtime/src/vm/handlers/sub.rs index b63475f2..442f8df3 100644 --- a/runtime/src/vm/handlers/sub.rs +++ b/runtime/src/vm/handlers/sub.rs @@ -7,7 +7,7 @@ use crate::{ }; pub fn build(rt: &mut Runtime) { - semantic::build( + semantic::compiler::compile( rt, &Operation { effects: vec![Effect::Sub( diff --git a/runtime/src/vm/handlers/trailing_zeros.rs b/runtime/src/vm/handlers/trailing_zeros.rs index b02da61f..04d362ae 100644 --- a/runtime/src/vm/handlers/trailing_zeros.rs +++ b/runtime/src/vm/handlers/trailing_zeros.rs @@ -7,7 +7,7 @@ use crate::{ }; pub fn build(rt: &mut Runtime) { - semantic::build( + semantic::compiler::compile( rt, &Operation { effects: vec![Effect::Tzcnt(Expression::Operand(Operand::Input(0)))], diff --git a/runtime/src/vm/handlers/xor.rs b/runtime/src/vm/handlers/xor.rs index 5a9ed5df..69393005 100644 --- a/runtime/src/vm/handlers/xor.rs +++ b/runtime/src/vm/handlers/xor.rs @@ -7,7 +7,7 @@ use crate::{ }; pub fn build(rt: &mut Runtime) { - semantic::build( + semantic::compiler::compile( rt, &Operation { effects: vec![Effect::Xor( diff --git a/runtime/src/vm/mod.rs b/runtime/src/vm/mod.rs index e25789ae..9795ea28 100644 --- a/runtime/src/vm/mod.rs +++ b/runtime/src/vm/mod.rs @@ -3,7 +3,6 @@ pub mod encoders; pub mod functions; pub mod handlers; pub mod lifters; -#[cfg(debug_assertions)] mod snapshot; pub mod transform; pub mod utils; diff --git a/runtime/src/vm/utils/bytecode.rs b/runtime/src/vm/utils/bytecode.rs index 65a61dbd..fe7abd32 100644 --- a/runtime/src/vm/utils/bytecode.rs +++ b/runtime/src/vm/utils/bytecode.rs @@ -4,7 +4,7 @@ use iced_x86::code_asm::{ }; /// `mov {to}, [{base}]`; `inc {base}` -pub fn read_byte(rt: &mut Runtime, base: AsmRegister64, to: AsmRegister8) { +pub fn read_byte(rt: &mut Runtime, base: AsmRegister64, to: AsmRegister8) { // mov ..., [...] rt.asm.mov(to, ptr(base)).unwrap(); // inc ... @@ -12,7 +12,7 @@ pub fn read_byte(rt: &mut Runtime, base: AsmRegister64, to: AsmRegister8) { } /// `movzx {to}, [{base}]`; `inc {base}` -pub fn read_byte_zx(rt: &mut Runtime, base: AsmRegister64, to: AsmRegister32) { +pub fn read_byte_zx(rt: &mut Runtime, base: AsmRegister64, to: AsmRegister32) { // movzx ..., [...] rt.asm.movzx(to, byte_ptr(base)).unwrap(); // inc ... @@ -20,7 +20,7 @@ pub fn read_byte_zx(rt: &mut Runtime, base: AsmRegister64, to: AsmRegister32) { } /// `mov {to}, [{base}]`; `add {base}, 0x2` -pub fn read_word(rt: &mut Runtime, base: AsmRegister64, to: AsmRegister16) { +pub fn read_word(rt: &mut Runtime, base: AsmRegister64, to: AsmRegister16) { // mov ..., [...] rt.asm.mov(to, ptr(base)).unwrap(); // add ..., 0x2 @@ -28,7 +28,7 @@ pub fn read_word(rt: &mut Runtime, base: AsmRegister64, to: AsmRegister16) { } /// `movzx {to}, [{base}]`; `add {base}, 0x2` -pub fn read_word_zx(rt: &mut Runtime, base: AsmRegister64, to: AsmRegister32) { +pub fn read_word_zx(rt: &mut Runtime, base: AsmRegister64, to: AsmRegister32) { // movzx ..., [...] rt.asm.movzx(to, word_ptr(base)).unwrap(); // add ..., 0x2 @@ -36,7 +36,7 @@ pub fn read_word_zx(rt: &mut Runtime, base: AsmRegister64, to: AsmRegister32) { } /// `mov {to}, [{base}]`; `add {base}, 0x4` -pub fn read_dword(rt: &mut Runtime, base: AsmRegister64, to: AsmRegister32) { +pub fn read_dword(rt: &mut Runtime, base: AsmRegister64, to: AsmRegister32) { // mov ..., [...] rt.asm.mov(to, ptr(base)).unwrap(); // add ..., 0x4 @@ -44,7 +44,7 @@ pub fn read_dword(rt: &mut Runtime, base: AsmRegister64, to: AsmRegister32) { } /// `movsxd {to}, [{base}]`; `add {base}, 0x4` -pub fn read_dword_sx(rt: &mut Runtime, base: AsmRegister64, to: AsmRegister64) { +pub fn read_dword_sx(rt: &mut Runtime, base: AsmRegister64, to: AsmRegister64) { // movsxd ..., [...] rt.asm.movsxd(to, ptr(base)).unwrap(); // add ..., 0x4 @@ -52,7 +52,7 @@ pub fn read_dword_sx(rt: &mut Runtime, base: AsmRegister64, to: AsmRegister64) } /// `mov {to}, [{base}]`; `add {base}, 0x8` -pub fn read_qword(rt: &mut Runtime, base: AsmRegister64, to: AsmRegister64) { +pub fn read_qword(rt: &mut Runtime, base: AsmRegister64, to: AsmRegister64) { // mov ..., [...] rt.asm.mov(to, ptr(base)).unwrap(); // add ..., 0x8 diff --git a/tests/src/lib.rs b/tests/src/lib.rs index e8636653..7b299d64 100644 --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -71,12 +71,12 @@ const XSTATE_AVX: u32 = 2; const XSTATE_MASK_AVX: u64 = 4; #[derive(Clone, PartialEq, Eq, Default)] -pub(crate) struct State { +pub struct State { pub registers: HashMap, pub vectors: HashMap, } -pub(crate) enum Difference { +pub enum Difference { Register(VMReg, u64, u64), Vector(VMVec, [u128; 2], [u128; 2]), } @@ -111,7 +111,7 @@ impl State { } } -pub(crate) struct Executor { +pub struct Executor { pub rt: Runtime, pub mem: *mut c_void, } @@ -452,7 +452,7 @@ impl Drop for Executor { } } -pub(crate) fn encrypt_block(block: &mut Vec) { +pub fn encrypt_block(block: &mut Vec) { crypt::encrypt_block( block, Executor::TEST_KEY_SEED, @@ -462,7 +462,7 @@ pub(crate) fn encrypt_block(block: &mut Vec) { ); } -pub(crate) fn decrypt_payload(block: &mut Vec) { +pub fn decrypt_payload(block: &mut Vec) { crypt::decrypt_payload( block, Executor::TEST_KEY_SEED, @@ -471,7 +471,7 @@ pub(crate) fn decrypt_payload(block: &mut Vec) { 0, ) } -pub(crate) fn decrypt_block(block: &mut Vec) { +pub fn decrypt_block(block: &mut Vec) { crypt::decrypt_block( block, Executor::TEST_KEY_SEED, From 1deea2b0d20cbcc97342fc033180314f083d140f Mon Sep 17 00:00:00 2001 From: RadonCoding <86915746+RadonCoding@users.noreply.github.com> Date: Sun, 20 Sep 2026 18:04:20 +0300 Subject: [PATCH 09/33] chore: rewrote unit testing framework and tons of bug fixes --- runtime/src/vm/handlers/bit_test.rs | 12 +- .../src/vm/handlers/bit_test_complement.rs | 17 +- runtime/src/vm/handlers/bit_test_reset.rs | 17 +- runtime/src/vm/handlers/bit_test_set.rs | 17 +- runtime/src/vm/handlers/byte_swap.rs | 48 +- runtime/src/vm/handlers/discard.rs | 11 +- runtime/src/vm/handlers/sar.rs | 28 +- runtime/src/vm/handlers/semantic/compiler.rs | 351 ++- runtime/src/vm/handlers/semantic/mod.rs | 12 +- runtime/src/vm/handlers/shl.rs | 7 +- runtime/src/vm/handlers/shr.rs | 8 +- runtime/src/vm/handlers/store_register.rs | 4 +- runtime/src/vm/handlers/vector_add.rs | 9 +- runtime/src/vm/handlers/vector_sub.rs | 8 +- runtime/src/vm/lifters/cmov.rs | 2 +- runtime/src/vm/lifters/extend.rs | 11 +- tests/src/constants.rs | 4 +- tests/src/instructions.rs | 2388 ++++++----------- tests/src/instrumentation.rs | 36 +- tests/src/lib.rs | 51 +- 20 files changed, 1344 insertions(+), 1697 deletions(-) diff --git a/runtime/src/vm/handlers/bit_test.rs b/runtime/src/vm/handlers/bit_test.rs index 6a4cb347..9a811e11 100644 --- a/runtime/src/vm/handlers/bit_test.rs +++ b/runtime/src/vm/handlers/bit_test.rs @@ -15,16 +15,14 @@ pub fn build(rt: &mut Runtime) { Flag::Carry, Condition::Compare(Compare::BitSet( Expression::Operand(Operand::Input(0)), - Expression::Operand(Operand::Input(1)), + Expression::BitAnd( + Box::new(Expression::Operand(Operand::Input(1))), + Box::new(Expression::SignBit), + ), )), )], stores: Some(vec![Expression::Operand(Operand::Input(0))]), - widths: &[ - VMWidth::Lower64, - VMWidth::Lower32, - VMWidth::Lower16, - VMWidth::Lower8, - ], + widths: &[VMWidth::Lower64, VMWidth::Lower32, VMWidth::Lower16], }, ); } diff --git a/runtime/src/vm/handlers/bit_test_complement.rs b/runtime/src/vm/handlers/bit_test_complement.rs index e744446e..832d1c3a 100644 --- a/runtime/src/vm/handlers/bit_test_complement.rs +++ b/runtime/src/vm/handlers/bit_test_complement.rs @@ -14,23 +14,24 @@ pub fn build(rt: &mut Runtime) { Expression::Operand(Operand::Input(0)), Expression::BitShl( Box::new(Expression::Constant(1)), - Box::new(Expression::Operand(Operand::Input(1))), + Box::new(Expression::BitAnd( + Box::new(Expression::Operand(Operand::Input(1))), + Box::new(Expression::SignBit), + )), ), )], flags: vec![( Flag::Carry, Condition::Compare(Compare::BitSet( Expression::Operand(Operand::Input(0)), - Expression::Operand(Operand::Input(1)), + Expression::BitAnd( + Box::new(Expression::Operand(Operand::Input(1))), + Box::new(Expression::SignBit), + ), )), )], stores: None, - widths: &[ - VMWidth::Lower64, - VMWidth::Lower32, - VMWidth::Lower16, - VMWidth::Lower8, - ], + widths: &[VMWidth::Lower64, VMWidth::Lower32, VMWidth::Lower16], }, ); } diff --git a/runtime/src/vm/handlers/bit_test_reset.rs b/runtime/src/vm/handlers/bit_test_reset.rs index 16fde7d6..dadd99e0 100644 --- a/runtime/src/vm/handlers/bit_test_reset.rs +++ b/runtime/src/vm/handlers/bit_test_reset.rs @@ -14,23 +14,24 @@ pub fn build(rt: &mut Runtime) { Expression::Operand(Operand::Input(0)), Expression::BitNot(Box::new(Expression::BitShl( Box::new(Expression::Constant(1)), - Box::new(Expression::Operand(Operand::Input(1))), + Box::new(Expression::BitAnd( + Box::new(Expression::Operand(Operand::Input(1))), + Box::new(Expression::SignBit), + )), ))), )], flags: vec![( Flag::Carry, Condition::Compare(Compare::BitSet( Expression::Operand(Operand::Input(0)), - Expression::Operand(Operand::Input(1)), + Expression::BitAnd( + Box::new(Expression::Operand(Operand::Input(1))), + Box::new(Expression::SignBit), + ), )), )], stores: None, - widths: &[ - VMWidth::Lower64, - VMWidth::Lower32, - VMWidth::Lower16, - VMWidth::Lower8, - ], + widths: &[VMWidth::Lower64, VMWidth::Lower32, VMWidth::Lower16], }, ); } diff --git a/runtime/src/vm/handlers/bit_test_set.rs b/runtime/src/vm/handlers/bit_test_set.rs index a43452ee..6a946c3d 100644 --- a/runtime/src/vm/handlers/bit_test_set.rs +++ b/runtime/src/vm/handlers/bit_test_set.rs @@ -14,23 +14,24 @@ pub fn build(rt: &mut Runtime) { Expression::Operand(Operand::Input(0)), Expression::BitShl( Box::new(Expression::Constant(1)), - Box::new(Expression::Operand(Operand::Input(1))), + Box::new(Expression::BitAnd( + Box::new(Expression::Operand(Operand::Input(1))), + Box::new(Expression::SignBit), + )), ), )], flags: vec![( Flag::Carry, Condition::Compare(Compare::BitSet( Expression::Operand(Operand::Input(0)), - Expression::Operand(Operand::Input(1)), + Expression::BitAnd( + Box::new(Expression::Operand(Operand::Input(1))), + Box::new(Expression::SignBit), + ), )), )], stores: None, - widths: &[ - VMWidth::Lower64, - VMWidth::Lower32, - VMWidth::Lower16, - VMWidth::Lower8, - ], + widths: &[VMWidth::Lower64, VMWidth::Lower32, VMWidth::Lower16], }, ); } diff --git a/runtime/src/vm/handlers/byte_swap.rs b/runtime/src/vm/handlers/byte_swap.rs index ef7446d1..9a065ab9 100644 --- a/runtime/src/vm/handlers/byte_swap.rs +++ b/runtime/src/vm/handlers/byte_swap.rs @@ -14,7 +14,10 @@ pub fn build(rt: &mut Runtime) { Effect::Assign(Expression::BitAnd( Box::new(Expression::BitShr( Box::new(Expression::Operand(Operand::Input(0))), - Box::new(Expression::Constant(56)), + Box::new(Expression::Sub( + Box::new(Expression::BitSize), + Box::new(Expression::Constant(8)), + )), )), Box::new(Expression::Constant(0xFF)), )), @@ -23,7 +26,10 @@ pub fn build(rt: &mut Runtime) { Expression::BitAnd( Box::new(Expression::BitShr( Box::new(Expression::Operand(Operand::Input(0))), - Box::new(Expression::Constant(40)), + Box::new(Expression::Sub( + Box::new(Expression::BitSize), + Box::new(Expression::Constant(24)), + )), )), Box::new(Expression::Constant(0xFF00)), ), @@ -33,7 +39,10 @@ pub fn build(rt: &mut Runtime) { Expression::BitAnd( Box::new(Expression::BitShr( Box::new(Expression::Operand(Operand::Input(0))), - Box::new(Expression::Constant(24)), + Box::new(Expression::Sub( + Box::new(Expression::BitSize), + Box::new(Expression::Constant(40)), + )), )), Box::new(Expression::Constant(0xFF_0000)), ), @@ -43,7 +52,10 @@ pub fn build(rt: &mut Runtime) { Expression::BitAnd( Box::new(Expression::BitShr( Box::new(Expression::Operand(Operand::Input(0))), - Box::new(Expression::Constant(8)), + Box::new(Expression::Sub( + Box::new(Expression::BitSize), + Box::new(Expression::Constant(56)), + )), )), Box::new(Expression::Constant(0xFF_000000)), ), @@ -53,9 +65,12 @@ pub fn build(rt: &mut Runtime) { Expression::BitAnd( Box::new(Expression::BitShl( Box::new(Expression::Operand(Operand::Input(0))), - Box::new(Expression::Constant(8)), + Box::new(Expression::Sub( + Box::new(Expression::BitSize), + Box::new(Expression::Constant(56)), + )), )), - Box::new(Expression::Constant(0xFF_00000000)), + Box::new(Expression::ByteMask(3)), ), ), Effect::Or( @@ -63,9 +78,12 @@ pub fn build(rt: &mut Runtime) { Expression::BitAnd( Box::new(Expression::BitShl( Box::new(Expression::Operand(Operand::Input(0))), - Box::new(Expression::Constant(24)), + Box::new(Expression::Sub( + Box::new(Expression::BitSize), + Box::new(Expression::Constant(40)), + )), )), - Box::new(Expression::Constant(0xFF_0000000000)), + Box::new(Expression::ByteMask(2)), ), ), Effect::Or( @@ -73,9 +91,12 @@ pub fn build(rt: &mut Runtime) { Expression::BitAnd( Box::new(Expression::BitShl( Box::new(Expression::Operand(Operand::Input(0))), - Box::new(Expression::Constant(40)), + Box::new(Expression::Sub( + Box::new(Expression::BitSize), + Box::new(Expression::Constant(24)), + )), )), - Box::new(Expression::Constant(0xFF_000000000000)), + Box::new(Expression::ByteMask(1)), ), ), Effect::Or( @@ -83,9 +104,12 @@ pub fn build(rt: &mut Runtime) { Expression::BitAnd( Box::new(Expression::BitShl( Box::new(Expression::Operand(Operand::Input(0))), - Box::new(Expression::Constant(56)), + Box::new(Expression::Sub( + Box::new(Expression::BitSize), + Box::new(Expression::Constant(8)), + )), )), - Box::new(Expression::Constant(0xFF_00000000000000)), + Box::new(Expression::ByteMask(0)), ), ), ], diff --git a/runtime/src/vm/handlers/discard.rs b/runtime/src/vm/handlers/discard.rs index ba825590..883088da 100644 --- a/runtime/src/vm/handlers/discard.rs +++ b/runtime/src/vm/handlers/discard.rs @@ -1,11 +1,14 @@ -use iced_x86::code_asm::{r12, r9, rax, rcx}; +use iced_x86::code_asm::{r12, rax, rcx}; -use crate::{runtime::Runtime, vm::utils::scratch}; +use crate::{ + runtime::Runtime, + vm::{bytecode::VMReg, utils::vreg}, +}; // unsigned char* (unsigned char*) pub fn build(rt: &mut Runtime) { - // load r9 - scratch::load(rt, r12, r9); + // add [...], 0x8 + vreg::add_imm(rt, r12, 0x8, VMReg::VScratch); // mov rax, rcx rt.asm.mov(rax, rcx).unwrap(); diff --git a/runtime/src/vm/handlers/sar.rs b/runtime/src/vm/handlers/sar.rs index 0b8da886..5916e330 100644 --- a/runtime/src/vm/handlers/sar.rs +++ b/runtime/src/vm/handlers/sar.rs @@ -20,8 +20,32 @@ pub fn build(rt: &mut Runtime) { Condition::Compare(Compare::BitSet( Expression::Operand(Operand::Input(0)), Expression::Sub( - Box::new(Expression::Operand(Operand::Input(1))), - Box::new(Expression::Constant(1)), + Box::new(Expression::SignBit), + Box::new(Expression::BitAnd( + Box::new(Expression::Sub( + Box::new(Expression::BitSize), + Box::new(Expression::BitAnd( + Box::new(Expression::Operand(Operand::Input(1))), + Box::new(Expression::BitOr( + Box::new(Expression::SignBit), + Box::new(Expression::Constant(0x1f)), + )), + )), + )), + Box::new(Expression::BitShr( + Box::new(Expression::BitNot(Box::new(Expression::Constant(0)))), + Box::new(Expression::Sub( + Box::new(Expression::BitAnd( + Box::new(Expression::Operand(Operand::Input(1))), + Box::new(Expression::BitOr( + Box::new(Expression::SignBit), + Box::new(Expression::Constant(0x1f)), + )), + )), + Box::new(Expression::Constant(1)), + )), + )), + )), ), )), ), diff --git a/runtime/src/vm/handlers/semantic/compiler.rs b/runtime/src/vm/handlers/semantic/compiler.rs index d9910e33..dc707263 100644 --- a/runtime/src/vm/handlers/semantic/compiler.rs +++ b/runtime/src/vm/handlers/semantic/compiler.rs @@ -1,6 +1,7 @@ use iced_x86::code_asm::{ - cl, get_gpr16, get_gpr32, get_gpr8, qword_ptr, r12, r13, r14, r14d, rax, rbp, rcx, rdx, rsp, - AsmRegister16, AsmRegister32, AsmRegister64, AsmRegister8, CodeAssembler, CodeLabel, + ah, ax, cl, dl, dword_ptr, dx, get_gpr16, get_gpr32, get_gpr8, qword_ptr, r12, r13, r14, r14d, + rax, rbp, rcx, rdx, rsp, AsmRegister16, AsmRegister32, AsmRegister64, AsmRegister8, + CodeAssembler, CodeLabel, }; use crate::{ @@ -16,12 +17,7 @@ use crate::{ }, }; -#[cfg(debug_assertions)] -#[track_caller] pub fn compile(rt: &mut Runtime, operation: &Operation) { - #[cfg(debug_assertions)] - let index = rt.asm.instructions().len(); - let mut epilogue = rt.asm.create_label(); let operands = operation.operands(); let outputs = operation.outputs(); @@ -148,21 +144,6 @@ pub fn compile(rt: &mut Runtime, operation: &Operation) { rt.asm.pop(r14).unwrap(); rt.asm.pop(r13).unwrap(); rt.asm.ret().unwrap(); - - #[cfg(debug_assertions)] - { - use std::panic::Location; - - use logger::debug; - - let location = Location::caller(); - - debug!("{}", location.file()); - - for instruction in &rt.asm.instructions()[index..] { - debug!("{}{instruction}", " ".repeat(4)); - } - } } fn compile_effects( @@ -176,6 +157,25 @@ fn compile_effects( } } +fn mask(rt: &mut Runtime, register: AsmRegister64, width: VMWidth) { + match width.size() { + 1 => { + let byte = get_gpr8(register::sized(register.into(), 1).unwrap()).unwrap(); + rt.asm.movzx(register, byte).unwrap(); + } + 2 => { + let word = get_gpr16(register::sized(register.into(), 2).unwrap()).unwrap(); + rt.asm.movzx(register, word).unwrap(); + } + 4 => { + let dword = get_gpr32(register::sized(register.into(), 4).unwrap()).unwrap(); + rt.asm.mov(dword, dword).unwrap(); + } + 8 => {} + _ => unreachable!(), + } +} + fn compile_binary( rt: &mut Runtime, allocator: &mut Allocator, @@ -199,9 +199,11 @@ fn compile_binary( let dst = allocator.acquire(rt, &pinned, &[]); allocator.copy(rt, first, dst); + mask(rt, dst, width); let src = allocator.acquire(rt, &pinned, &[dst]); allocator.copy(rt, second, src); + mask(rt, src, width); operation(rt, dst, src); @@ -232,7 +234,7 @@ fn compile_mul( allocator.copy(rt, second, src); allocator.copy(rt, first, rax); - compile_implicit( + compile_operation( rt, width, src, @@ -246,6 +248,11 @@ fn compile_mul( |asm, qword| asm.imul(qword).unwrap(), ); + if width.size() == 1 { + rt.asm.movzx(rdx, ax).unwrap(); + rt.asm.shr(rdx, 0x8).unwrap(); + } + allocator.replace(rax, Value::Output(0), true); allocator.replace(rdx, Value::Output(1), true); @@ -279,7 +286,11 @@ fn compile_div( allocator.copy(rt, second, rax); allocator.copy(rt, third, rdx); - compile_implicit( + if width.size() == 1 { + rt.asm.mov(ah, dl).unwrap(); + } + + compile_operation( rt, width, src, @@ -293,6 +304,10 @@ fn compile_div( |asm, qword| asm.idiv(qword).unwrap(), ); + if width.size() == 1 { + rt.asm.movzx(dx, ah).unwrap(); + } + allocator.replace(rax, Value::Output(0), true); allocator.replace(rdx, Value::Output(1), true); @@ -301,7 +316,7 @@ fn compile_div( allocator.consume(third); } -fn compile_implicit( +fn compile_operation( rt: &mut Runtime, width: VMWidth, src: AsmRegister64, @@ -360,15 +375,29 @@ fn compile_implicit( } } -fn compile_shift( +fn compile_shift( rt: &mut Runtime, allocator: &mut Allocator, first: &Expression, second: &Expression, width: VMWidth, - operation: F, + unsigned8: U8, + unsigned16: U16, + unsigned32: U32, + unsigned64: U64, + signed8: S8, + signed16: S16, + signed32: S32, + signed64: S64, ) where - F: FnOnce(&mut Runtime, AsmRegister64), + U8: FnOnce(&mut CodeAssembler, AsmRegister8), + U16: FnOnce(&mut CodeAssembler, AsmRegister16), + U32: FnOnce(&mut CodeAssembler, AsmRegister32), + U64: FnOnce(&mut CodeAssembler, AsmRegister64), + S8: FnOnce(&mut CodeAssembler, AsmRegister8), + S16: FnOnce(&mut CodeAssembler, AsmRegister16), + S32: FnOnce(&mut CodeAssembler, AsmRegister32), + S64: FnOnce(&mut CodeAssembler, AsmRegister64), { let first = compile_expression(rt, allocator, first, width); let second = compile_expression(rt, allocator, second, width); @@ -386,7 +415,10 @@ fn compile_shift( let dst = allocator.acquire(rt, &pinned, &[rcx]); allocator.copy(rt, first, dst); - operation(rt, dst); + compile_operation( + rt, width, dst, unsigned8, unsigned16, unsigned32, unsigned64, signed8, signed16, signed32, + signed64, + ); allocator.replace(dst, Value::Output(0), true); allocator.consume(first); @@ -424,31 +456,161 @@ fn compile_effect(rt: &mut Runtime, allocator: &mut Allocator, effect: &Effect, Effect::Div(first, second, third) => { compile_div(rt, allocator, first, second, third, width) } - Effect::Shr(first, second) => { - compile_shift(rt, allocator, first, second, width, |rt, dst| { - rt.asm.shr(dst, cl).unwrap(); - }) - } - Effect::Shl(first, second) => { - compile_shift(rt, allocator, first, second, width, |rt, dst| { - rt.asm.shl(dst, cl).unwrap(); - }) - } - Effect::Ror(first, second) => { - compile_shift(rt, allocator, first, second, width, |rt, dst| { - rt.asm.ror(dst, cl).unwrap(); - }) - } - Effect::Rol(first, second) => { - compile_shift(rt, allocator, first, second, width, |rt, dst| { - rt.asm.rol(dst, cl).unwrap(); - }) - } - Effect::Sar(first, second) => { - compile_shift(rt, allocator, first, second, width, |rt, dst| { - rt.asm.sar(dst, cl).unwrap(); - }) - } + Effect::Shr(first, second) => compile_shift( + rt, + allocator, + first, + second, + width, + |asm, byte| { + asm.shr(byte, cl).unwrap(); + }, + |asm, word| { + asm.shr(word, cl).unwrap(); + }, + |asm, dword| { + asm.shr(dword, cl).unwrap(); + }, + |asm, qword| { + asm.shr(qword, cl).unwrap(); + }, + |asm, byte| { + asm.shr(byte, cl).unwrap(); + }, + |asm, word| { + asm.shr(word, cl).unwrap(); + }, + |asm, dword| { + asm.shr(dword, cl).unwrap(); + }, + |asm, qword| { + asm.shr(qword, cl).unwrap(); + }, + ), + Effect::Shl(first, second) => compile_shift( + rt, + allocator, + first, + second, + width, + |asm, byte| { + asm.shl(byte, cl).unwrap(); + }, + |asm, word| { + asm.shl(word, cl).unwrap(); + }, + |asm, dword| { + asm.shl(dword, cl).unwrap(); + }, + |asm, qword| { + asm.shl(qword, cl).unwrap(); + }, + |asm, byte| { + asm.shl(byte, cl).unwrap(); + }, + |asm, word| { + asm.shl(word, cl).unwrap(); + }, + |asm, dword| { + asm.shl(dword, cl).unwrap(); + }, + |asm, qword| { + asm.shl(qword, cl).unwrap(); + }, + ), + Effect::Ror(first, second) => compile_shift( + rt, + allocator, + first, + second, + width, + |asm, byte| { + asm.ror(byte, cl).unwrap(); + }, + |asm, word| { + asm.ror(word, cl).unwrap(); + }, + |asm, dword| { + asm.ror(dword, cl).unwrap(); + }, + |asm, qword| { + asm.ror(qword, cl).unwrap(); + }, + |asm, byte| { + asm.ror(byte, cl).unwrap(); + }, + |asm, word| { + asm.ror(word, cl).unwrap(); + }, + |asm, dword| { + asm.ror(dword, cl).unwrap(); + }, + |asm, qword| { + asm.ror(qword, cl).unwrap(); + }, + ), + Effect::Rol(first, second) => compile_shift( + rt, + allocator, + first, + second, + width, + |asm, byte| { + asm.rol(byte, cl).unwrap(); + }, + |asm, word| { + asm.rol(word, cl).unwrap(); + }, + |asm, dword| { + asm.rol(dword, cl).unwrap(); + }, + |asm, qword| { + asm.rol(qword, cl).unwrap(); + }, + |asm, byte| { + asm.rol(byte, cl).unwrap(); + }, + |asm, word| { + asm.rol(word, cl).unwrap(); + }, + |asm, dword| { + asm.rol(dword, cl).unwrap(); + }, + |asm, qword| { + asm.rol(qword, cl).unwrap(); + }, + ), + Effect::Sar(first, second) => compile_shift( + rt, + allocator, + first, + second, + width, + |asm, byte| { + asm.sar(byte, cl).unwrap(); + }, + |asm, word| { + asm.sar(word, cl).unwrap(); + }, + |asm, dword| { + asm.sar(dword, cl).unwrap(); + }, + |asm, qword| { + asm.sar(qword, cl).unwrap(); + }, + |asm, byte| { + asm.sar(byte, cl).unwrap(); + }, + |asm, word| { + asm.sar(word, cl).unwrap(); + }, + |asm, dword| { + asm.sar(dword, cl).unwrap(); + }, + |asm, qword| { + asm.sar(qword, cl).unwrap(); + }, + ), Effect::Assign(expression) => { let src = compile_expression(rt, allocator, expression, width); let dst = allocator.acquire(rt, &[], &[]); @@ -510,6 +672,10 @@ fn compile_expression( Expression::Operand(Operand::Output(index)) => Reference::Value(Value::Output(*index)), Expression::Constant(value) => Reference::Immediate(*value as i64), Expression::SignBit => Reference::Immediate(width.mask().ilog2() as i64), + Expression::BitSize => Reference::Immediate((width.size() * 8) as i64), + Expression::ByteMask(n) => { + Reference::Immediate(((0xFF as u64) << ((width.size() - 1 - n) * 8)) as i64) + } Expression::Sub(first, second) | Expression::BitAnd(first, second) | Expression::BitOr(first, second) @@ -527,9 +693,11 @@ fn compile_expression( let dst = allocator.acquire(rt, &pinned, &[]); allocator.copy(rt, first, dst); + mask(rt, dst, width); let src = allocator.acquire(rt, &pinned, &[dst]); allocator.copy(rt, second, src); + mask(rt, src, width); match expression { Expression::Sub(_, _) => rt.asm.sub(dst, src).unwrap(), @@ -539,6 +707,8 @@ fn compile_expression( _ => unreachable!(), } + mask(rt, dst, width); + allocator.consume(first); allocator.consume(second); @@ -567,6 +737,7 @@ fn compile_expression( let dst = allocator.acquire(rt, &pinned, &[rcx]); allocator.copy(rt, first, dst); + mask(rt, dst, width); match expression { Expression::BitShr(_, _) => rt.asm.shr(dst, cl).unwrap(), @@ -574,6 +745,8 @@ fn compile_expression( _ => unreachable!(), } + mask(rt, dst, width); + allocator.consume(first); allocator.consume(second); @@ -614,10 +787,16 @@ fn compile_flags( flags: &[(Flag, Condition)], width: VMWidth, ) { + let register = allocator.acquire(rt, &[], &[]); + + vreg::load_reg(rt, r12, VMReg::Flags, register); + rt.asm - .mov(qword_ptr(rbp - allocator.offset(Value::Flags)), 0x0) + .mov(qword_ptr(rbp - allocator.offset(Value::Flags)), register) .unwrap(); + allocator.untrack(register); + for (flag, condition) in flags { match condition { Condition::Compare(item) => compile_comparison(rt, allocator, *flag, item, width), @@ -630,6 +809,7 @@ fn compile_flags( rt.asm .mov(register, qword_ptr(rbp - allocator.offset(Value::Flags))) .unwrap(); + vreg::store_reg(rt, r12, register, VMReg::Flags); allocator.untrack(register); @@ -642,11 +822,11 @@ fn compile_comparison( compare: &Compare, width: VMWidth, ) { - let (first, second, condition) = match compare { - Compare::Equal(first, second) => (first, second, 0u8), - Compare::LessThan(first, second) => (first, second, 1u8), - Compare::GreaterThan(first, second) => (first, second, 2u8), - Compare::BitSet(first, second) => (first, second, 3u8), + let (first, second) = match compare { + Compare::Equal(first, second) + | Compare::LessThan(first, second) + | Compare::GreaterThan(first, second) + | Compare::BitSet(first, second) => (first, second), }; let first = compile_expression(rt, allocator, first, width); @@ -662,33 +842,47 @@ fn compile_comparison( let dst = allocator.acquire(rt, &pinned, &[]); allocator.copy(rt, first, dst); + mask(rt, dst, width); let src = allocator.acquire(rt, &pinned, &[dst]); allocator.copy(rt, second, src); + mask(rt, src, width); let tmp = allocator.acquire(rt, &pinned, &[dst, src]); - let byte = get_gpr8(register::sized(tmp.into(), 1).unwrap()).unwrap(); - match condition { - 0 | 1 | 2 => rt.asm.cmp(dst, src).unwrap(), - 3 => rt.asm.bt(dst, src).unwrap(), - _ => unreachable!(), - } + let byte = get_gpr8(register::sized(tmp.into(), 1).unwrap()).unwrap(); - match condition { - 0 => rt.asm.sete(byte).unwrap(), - 1 => rt.asm.setb(byte).unwrap(), - 2 => rt.asm.seta(byte).unwrap(), - 3 => rt.asm.setc(byte).unwrap(), - _ => unreachable!(), + match compare { + Compare::Equal(_, _) => { + rt.asm.cmp(dst, src).unwrap(); + rt.asm.sete(byte).unwrap(); + } + Compare::LessThan(_, _) => { + rt.asm.cmp(dst, src).unwrap(); + rt.asm.setb(byte).unwrap(); + } + Compare::GreaterThan(_, _) => { + rt.asm.cmp(dst, src).unwrap(); + rt.asm.seta(byte).unwrap(); + } + Compare::BitSet(_, _) => { + rt.asm.bt(dst, src).unwrap(); + rt.asm.setc(byte).unwrap(); + } } rt.asm.movzx(tmp, byte).unwrap(); + + let bit = flag.bit32().trailing_zeros() as i32; + + let mask = flag.bit32(); + rt.asm - .shl(tmp, flag.bit32().trailing_zeros() as i32) + .and(dword_ptr(rbp - allocator.offset(Value::Flags)), !mask) .unwrap(); + rt.asm.shl(tmp, bit).unwrap(); rt.asm - .or(qword_ptr(rbp - allocator.offset(Value::Flags)), tmp) + .or(dword_ptr(rbp - allocator.offset(Value::Flags)), tmp) .unwrap(); allocator.spill(rt, tmp); @@ -718,11 +912,16 @@ fn compile_parity( rt.asm.popcnt(dst, dst).unwrap(); rt.asm.not(dst).unwrap(); rt.asm.and(dst, 0x1).unwrap(); + + let shift = flag.bit32().trailing_zeros() as i32; + let mask = 1u32 << shift; + rt.asm - .shl(dst, flag.bit32().trailing_zeros() as i32) + .and(dword_ptr(rbp - allocator.offset(Value::Flags)), !mask) .unwrap(); + rt.asm.shl(dst, shift).unwrap(); rt.asm - .or(qword_ptr(rbp - allocator.offset(Value::Flags)), dst) + .or(dword_ptr(rbp - allocator.offset(Value::Flags)), dst) .unwrap(); allocator.untrack(dst); diff --git a/runtime/src/vm/handlers/semantic/mod.rs b/runtime/src/vm/handlers/semantic/mod.rs index d82e4dfd..a2e4eb46 100644 --- a/runtime/src/vm/handlers/semantic/mod.rs +++ b/runtime/src/vm/handlers/semantic/mod.rs @@ -22,6 +22,8 @@ pub enum Expression { Sub(Box, Box), LowByte(Box), SignBit, + BitSize, + ByteMask(usize), } #[derive(Debug, Clone)] @@ -141,7 +143,9 @@ impl Expression { } Expression::Operand(Operand::Output(_)) | Expression::Constant(_) - | Expression::SignBit => {} + | Expression::SignBit + | Expression::BitSize + | Expression::ByteMask(_) => {} Expression::BitAnd(lhs, rhs) | Expression::BitOr(lhs, rhs) | Expression::BitXor(lhs, rhs) @@ -159,7 +163,11 @@ impl Expression { fn temporary(&self) -> usize { match self { - Expression::Operand(_) | Expression::Constant(_) | Expression::SignBit => 0, + Expression::Operand(_) + | Expression::Constant(_) + | Expression::SignBit + | Expression::BitSize + | Expression::ByteMask(_) => 0, Expression::BitAnd(lhs, rhs) | Expression::BitOr(lhs, rhs) | Expression::BitXor(lhs, rhs) diff --git a/runtime/src/vm/handlers/shl.rs b/runtime/src/vm/handlers/shl.rs index 044fcadf..bb84a6f8 100644 --- a/runtime/src/vm/handlers/shl.rs +++ b/runtime/src/vm/handlers/shl.rs @@ -20,8 +20,11 @@ pub fn build(rt: &mut Runtime) { Condition::Compare(Compare::BitSet( Expression::Operand(Operand::Input(0)), Expression::Sub( - Box::new(Expression::Constant(64)), - Box::new(Expression::Operand(Operand::Input(1))), + Box::new(Expression::BitSize), + Box::new(Expression::BitAnd( + Box::new(Expression::Operand(Operand::Input(1))), + Box::new(Expression::Constant(0x1f)), + )), ), )), ), diff --git a/runtime/src/vm/handlers/shr.rs b/runtime/src/vm/handlers/shr.rs index d1acb025..baa3f1c7 100644 --- a/runtime/src/vm/handlers/shr.rs +++ b/runtime/src/vm/handlers/shr.rs @@ -20,7 +20,13 @@ pub fn build(rt: &mut Runtime) { Condition::Compare(Compare::BitSet( Expression::Operand(Operand::Input(0)), Expression::Sub( - Box::new(Expression::Operand(Operand::Input(1))), + Box::new(Expression::BitAnd( + Box::new(Expression::Operand(Operand::Input(1))), + Box::new(Expression::BitOr( + Box::new(Expression::SignBit), + Box::new(Expression::Constant(0x1f)), + )), + )), Box::new(Expression::Constant(1)), ), )), diff --git a/runtime/src/vm/handlers/store_register.rs b/runtime/src/vm/handlers/store_register.rs index 5d20bf1a..1bb161bd 100644 --- a/runtime/src/vm/handlers/store_register.rs +++ b/runtime/src/vm/handlers/store_register.rs @@ -1,4 +1,4 @@ -use iced_x86::code_asm::{eax, ptr, r12, r8, r8d, r9, r9b, r9w, rax, rcx}; +use iced_x86::code_asm::{eax, ptr, r12, r8, r8d, r9, r9b, r9d, r9w, rax, rcx}; use crate::{ runtime::Runtime, @@ -27,6 +27,8 @@ pub fn build(rt: &mut Runtime) { rt.asm.mov(ptr(r12 + r8 * 8), r9).unwrap(); })), Some(Box::new(|rt| { + // mov r9d, r9d + rt.asm.mov(r9d, r9d).unwrap(); // mov [r12 + r8*8], r9 rt.asm.mov(ptr(r12 + r8 * 8), r9).unwrap(); })), diff --git a/runtime/src/vm/handlers/vector_add.rs b/runtime/src/vm/handlers/vector_add.rs index 6e50d8f9..40e45184 100644 --- a/runtime/src/vm/handlers/vector_add.rs +++ b/runtime/src/vm/handlers/vector_add.rs @@ -34,9 +34,10 @@ pub fn build(rt: &mut Runtime) { rt.asm.addps(xmm0, xmm1).unwrap(); }, |rt| { - // addsd xmm0, xmm1 - rt.asm.addsd(xmm0, xmm1).unwrap(); + // addpd xmm0, xmm1 + rt.asm.addpd(xmm0, xmm1).unwrap(); }, + // avx_int |rt| { // vpaddb ymm0, ymm0, ymm1 rt.asm.vpaddb(ymm0, ymm0, ymm1).unwrap(); @@ -66,8 +67,8 @@ pub fn build(rt: &mut Runtime) { rt.asm.vaddps(ymm0, ymm0, ymm1).unwrap(); }, |rt| { - // vaddsd ymm0, ymm0, ymm1 - rt.asm.vaddsd(xmm0, xmm0, xmm1).unwrap(); + // vaddpd ymm0, ymm0, ymm1 + rt.asm.vaddpd(ymm0, ymm0, ymm1).unwrap(); }, ); } diff --git a/runtime/src/vm/handlers/vector_sub.rs b/runtime/src/vm/handlers/vector_sub.rs index 6799d522..a82af39c 100644 --- a/runtime/src/vm/handlers/vector_sub.rs +++ b/runtime/src/vm/handlers/vector_sub.rs @@ -33,8 +33,8 @@ pub fn build(rt: &mut Runtime) { rt.asm.subps(xmm0, xmm1).unwrap(); }, |rt| { - // subsd xmm0, xmm1 - rt.asm.subsd(xmm0, xmm1).unwrap(); + // subpd xmm0, xmm1 + rt.asm.subpd(xmm0, xmm1).unwrap(); }, // avx_int |rt| { @@ -66,8 +66,8 @@ pub fn build(rt: &mut Runtime) { rt.asm.vsubps(ymm0, ymm0, ymm1).unwrap(); }, |rt| { - // vsubsd ymm0, ymm0, ymm1 - rt.asm.vsubsd(xmm0, xmm0, xmm1).unwrap(); + // vsubpd ymm0, ymm0, ymm1 + rt.asm.vsubpd(ymm0, ymm0, ymm1).unwrap(); }, ); } diff --git a/runtime/src/vm/lifters/cmov.rs b/runtime/src/vm/lifters/cmov.rs index 9630ef91..738a7337 100644 --- a/runtime/src/vm/lifters/cmov.rs +++ b/runtime/src/vm/lifters/cmov.rs @@ -1,6 +1,6 @@ use iced_x86::{Code, Instruction}; -use crate::vm::bytecode::{VMCondition, Flag, VMLogic}; +use crate::vm::bytecode::{Flag, VMCondition, VMLogic}; use crate::vm::encoders::block::Block; use crate::vm::encoders::Encode; use crate::vm::lifters::transfer; diff --git a/runtime/src/vm/lifters/extend.rs b/runtime/src/vm/lifters/extend.rs index fb8f349c..e9d50064 100644 --- a/runtime/src/vm/lifters/extend.rs +++ b/runtime/src/vm/lifters/extend.rs @@ -1,7 +1,6 @@ use iced_x86::{Instruction, Mnemonic, OpKind}; - -use crate::vm::bytecode::{VMMem, VMReg, VMWidth}; +use crate::vm::bytecode::{VMMem, VMReg}; use crate::vm::encoders::{ load_address::LoadAddress, load_memory::LoadMemory, load_register::LoadRegister, store_register::StoreRegister, Encode, @@ -13,10 +12,7 @@ pub fn encode(instruction: &Instruction) -> Option>> { let signed = matches!(instruction.mnemonic(), Mnemonic::Movsx | Mnemonic::Movsxd); - let destination_width = match VMWidth::from(instruction.op0_register()) { - VMWidth::Lower16 => VMWidth::Lower16, - _ => VMWidth::Lower64, - }; + let destination_width = operation_width(instruction, 0); let destination_register = VMReg::from(instruction.op0_register()); let source_width = operation_width(instruction, 1); @@ -29,11 +25,13 @@ pub fn encode(instruction: &Instruction) -> Option>> { match instruction.op1_kind() { OpKind::Register => { let source_register = VMReg::from(instruction.op1_register()); + operations.push(Box::new(LoadRegister { width: source_width, source: source_register, })); } + OpKind::Memory => { operations.push(Box::new(LoadAddress { source: VMMem::from(instruction), @@ -42,6 +40,7 @@ pub fn encode(instruction: &Instruction) -> Option>> { width: source_width, })); } + _ => unreachable!(), } diff --git a/tests/src/constants.rs b/tests/src/constants.rs index 94b8bbd0..35f19d9d 100644 --- a/tests/src/constants.rs +++ b/tests/src/constants.rs @@ -106,9 +106,9 @@ pub const IMM64_A: u64 = 0x8A5C_E179_2D4B_7F67; pub const IMM64_B: u64 = 0x5BD4_F28A_3E5C_6E78; pub const IMM64_C: u64 = 0x4CE5_039B_4F6D_5D69; -pub const SIMM8_A: i32 = 0x75; +pub const _SIMM8_A: i32 = 0x75; pub const _SIMM16_A: i16 = 0x6A5C; -pub const SIMM32_A: i32 = 0x6A5C_E179; +pub const _SIMM32_A: i32 = 0x6A5C_E179; pub const _SIMM64_A: i64 = 0x6A5C_E179_2D4B_7F67; pub const IMM128_A: u128 = 0x6A5C_E179_2D4B_7F67_1032_5476_789A_BCDE; diff --git a/tests/src/instructions.rs b/tests/src/instructions.rs index 5e7f1567..3ea3316e 100644 --- a/tests/src/instructions.rs +++ b/tests/src/instructions.rs @@ -1,16 +1,13 @@ use std::slice; -use iced_x86::MemoryOperand; -use iced_x86::Register::{AL, AX, CL, CX, EAX, ECX, RAX, RBX, RCX, XMM0, XMM1, XMM2}; -use iced_x86::{Instruction, RflagsBits}; +use iced_x86::{ + Code, Instruction, InstructionInfoFactory, Mnemonic, OpAccess, OpCodeOperandKind, OpKind, + Register, RflagsBits, +}; use runtime::vm::bytecode::{self, Flag, VMReg}; -use crate::constants::{ - baseline, gpr, simd, IMM128_B, IMM128_C, IMM32_A, IMM64_A, IMM64_B, IMM8_A, SIMM32_A, SIMM8_A, -}; -use crate::{ - decrypt_block, decrypt_payload, encrypt_block, instruction, Difference, Executor, State, -}; +use crate::constants::{gpr, simd, _IMM16_A, IMM128_A, IMM32_A, IMM64_A, IMM8_A}; +use crate::{decrypt_block, decrypt_payload, encrypt_block, Difference, Executor, State}; #[test] fn test_crypt() { @@ -22,1574 +19,947 @@ fn test_crypt() { decrypt_block(&mut buffer); - let after = buffer.clone(); - - assert_eq!(before, after); + assert_eq!(before, buffer); } -fn check(state: State, instruction: Instruction) { - check_with_memory(state, instruction, &mut []); +macro_rules! define { + ($($code:ident)+ $(,)?) => { + $( + paste::paste! { + #[test] + fn []() { + test(Code::$code); + } + } + )+ + }; } -fn check_with_memory(state: State, instruction: Instruction, memory: &mut [u8]) { - let baseline = memory.to_vec(); - - let mut executor = Executor::new(); - let mut native = executor.run_native(state.clone(), &[instruction]); - - memory.copy_from_slice(&baseline); - - let mut executor = Executor::new(); - let lifted = bytecode::lift(&[instruction]) - .unwrap_or_else(|| panic!("{instruction} is not implemented")); - let transformed = bytecode::transform(&mut executor.rt.mapper, lifted, |_| 0); - - let mut bytes = bytecode::assemble(&mut executor.rt.mapper, &transformed); - encrypt_block(&mut bytes); - decrypt_payload(&mut bytes); - let mut emulated = executor.run_virtual(state.clone(), &bytes); - - normalize_and_compare(&mut native, &mut emulated, instruction); +define!( +Adc_r64_rm64 +Adc_rm16_r16 +Adc_rm32_r32 +Adc_rm64_imm32 +Adc_rm64_imm8 +Adc_rm64_r64 +Adc_rm8_r8 +Add_r64_rm64 +Add_rm16_r16 +Add_rm32_r32 +Add_rm64_imm32 +Add_rm64_imm8 +Add_rm64_r64 +Add_rm8_r8 +Addpd_xmm_xmmm128 +Addps_xmm_xmmm128 +And_r64_rm64 +And_rm16_r16 +And_rm32_r32 +And_rm64_imm32 +And_rm64_imm8 +And_rm64_r64 +And_rm8_r8 +Andnpd_xmm_xmmm128 +Andnps_xmm_xmmm128 +Andpd_xmm_xmmm128 +Andps_xmm_xmmm128 +Bsr_r64_rm64 +Bswap_r64 +Bt_rm64_imm8 +Bt_rm64_r64 +Btc_rm64_imm8 +Btc_rm64_r64 +Btr_rm64_imm8 +Btr_rm64_r64 +Bts_rm64_imm8 +Bts_rm64_r64 +Cmp_r64_rm64 +Cmp_rm16_r16 +Cmp_rm32_r32 +Cmp_rm64_imm32 +Cmp_rm64_imm8 +Cmp_rm64_r64 +Cmp_rm8_r8 +Cmpxchg_rm64_r64 +Dec_rm64 +Div_rm64 +Divpd_xmm_xmmm128 +Divps_xmm_xmmm128 +Idiv_rm64 +Imul_r64_rm64 +Imul_r64_rm64_imm32 +Imul_r64_rm64_imm8 +Imul_rm64 +Inc_rm64 +Mov_r64_imm64 +Mov_r64_rm64 +Mov_rm64_imm32 +Mov_rm64_r64 +Movapd_xmm_xmmm128 +Movapd_xmmm128_xmm +Movaps_xmm_xmmm128 +Movaps_xmmm128_xmm +Movd_rm32_xmm +Movd_xmm_rm32 +Movdqa_xmm_xmmm128 +Movdqa_xmmm128_xmm +Movdqu_xmm_xmmm128 +Movdqu_xmmm128_xmm +Movq_rm64_xmm +Movq_xmm_rm64 +Movsd_xmm_xmmm64 +Movsd_xmmm64_xmm +Movss_xmm_xmmm32 +Movss_xmmm32_xmm +Movsx_r64_rm8 +Movsxd_r64_rm32 +Movupd_xmm_xmmm128 +Movupd_xmmm128_xmm +Movups_xmm_xmmm128 +Movups_xmmm128_xmm +Movzx_r64_rm8 +Mul_rm64 +Mulpd_xmm_xmmm128 +Mulps_xmm_xmmm128 +Neg_rm64 +Not_rm64 +Or_r64_rm64 +Or_rm16_r16 +Or_rm32_r32 +Or_rm64_imm32 +Or_rm64_imm8 +Or_rm64_r64 +Or_rm8_r8 +Orpd_xmm_xmmm128 +Orps_xmm_xmmm128 +Paddb_xmm_xmmm128 +Paddd_xmm_xmmm128 +Paddq_xmm_xmmm128 +Paddw_xmm_xmmm128 +Pand_xmm_xmmm128 +Pandn_xmm_xmmm128 +Pcmpeqb_xmm_xmmm128 +Pmovmskb_r64_xmm +Pmulld_xmm_xmmm128 +Pmullw_xmm_xmmm128 +Por_xmm_xmmm128 +Psubb_xmm_xmmm128 +Psubd_xmm_xmmm128 +Psubq_xmm_xmmm128 +Psubw_xmm_xmmm128 +Pxor_xmm_xmmm128 +Rol_rm64_CL +Rol_rm64_imm8 +Ror_rm64_CL +Ror_rm64_imm8 +Sar_rm64_CL +Sar_rm64_imm8 +Sbb_r64_rm64 +Sbb_rm16_r16 +Sbb_rm32_r32 +Sbb_rm64_imm32 +Sbb_rm64_imm8 +Sbb_rm64_r64 +Sbb_rm8_r8 +Seta_rm8 +Setae_rm8 +Setb_rm8 +Setbe_rm8 +Sete_rm8 +Setg_rm8 +Setge_rm8 +Setl_rm8 +Setle_rm8 +Setne_rm8 +Setno_rm8 +Setnp_rm8 +Setns_rm8 +Seto_rm8 +Setp_rm8 +Sets_rm8 +Shl_rm64_CL +Shl_rm64_imm8 +Shr_rm64_CL +Shr_rm64_imm8 +Sub_r64_rm64 +Sub_rm16_r16 +Sub_rm32_r32 +Sub_rm64_imm32 +Sub_rm64_imm8 +Sub_rm64_r64 +Sub_rm8_r8 +Subpd_xmm_xmmm128 +Subps_xmm_xmmm128 +Test_rm16_r16 +Test_rm32_r32 +Test_rm64_imm32 +Test_rm64_r64 +Test_rm8_r8 +Tzcnt_r64_rm64 +VEX_Vaddpd_xmm_xmm_xmmm128 +VEX_Vaddps_xmm_xmm_xmmm128 +VEX_Vandps_xmm_xmm_xmmm128 +VEX_Vdivpd_xmm_xmm_xmmm128 +VEX_Vdivps_xmm_xmm_xmmm128 +VEX_Vmulpd_xmm_xmm_xmmm128 +VEX_Vmulps_xmm_xmm_xmmm128 +VEX_Vpmulhw_xmm_xmm_xmmm128 +VEX_Vpmulld_xmm_xmm_xmmm128 +VEX_Vpmullw_xmm_xmm_xmmm128 +VEX_Vpxor_xmm_xmm_xmmm128 +VEX_Vsubpd_xmm_xmm_xmmm128 +VEX_Vsubps_xmm_xmm_xmmm128 +VEX_Vxorps_xmm_xmm_xmmm128 +Xadd_rm64_r64 +Xchg_rm64_r64 +Xor_r64_rm64 +Xor_rm16_r16 +Xor_rm32_r32 +Xor_rm64_imm32 +Xor_rm64_imm8 +Xor_rm64_r64 +Xor_rm8_r8 +Xorpd_xmm_xmmm128 +Xorps_xmm_xmmm128 +Adc_r8_rm8 +Adc_r16_rm16 +Adc_r32_rm32 +Add_r8_rm8 +Add_r16_rm16 +Add_r32_rm32 +And_r8_rm8 +And_r16_rm16 +And_r32_rm32 +Cmp_r8_rm8 +Cmp_r16_rm16 +Cmp_r32_rm32 +Or_r8_rm8 +Or_r16_rm16 +Or_r32_rm32 +Sbb_r8_rm8 +Sbb_r16_rm16 +Sbb_r32_rm32 +Sub_r8_rm8 +Sub_r16_rm16 +Sub_r32_rm32 +Xor_r8_rm8 +Xor_r16_rm16 +Xor_r32_rm32 +Mov_rm8_r8 +Mov_rm16_r16 +Mov_rm32_r32 +Mov_r8_rm8 +Mov_r16_rm16 +Mov_r32_rm32 +Mov_r8_imm8 +Mov_r16_imm16 +Mov_r32_imm32 +Mov_rm8_imm8 +Mov_rm16_imm16 +Mov_rm32_imm32 +Inc_rm8 +Inc_rm16 +Inc_rm32 +Dec_rm8 +Dec_rm16 +Dec_rm32 +Neg_rm8 +Neg_rm16 +Neg_rm32 +Not_rm8 +Not_rm16 +Not_rm32 +Bswap_r32 +Rol_rm8_CL +Rol_rm16_CL +Rol_rm32_CL +Ror_rm8_CL +Ror_rm16_CL +Ror_rm32_CL +Shl_rm8_CL +Shl_rm16_CL +Shl_rm32_CL +Shr_rm8_CL +Shr_rm16_CL +Shr_rm32_CL +Sar_rm8_CL +Sar_rm16_CL +Sar_rm32_CL +Rol_rm8_imm8 +Rol_rm16_imm8 +Rol_rm32_imm8 +Ror_rm8_imm8 +Ror_rm16_imm8 +Ror_rm32_imm8 +Shl_rm8_imm8 +Shl_rm16_imm8 +Shl_rm32_imm8 +Shr_rm8_imm8 +Shr_rm16_imm8 +Shr_rm32_imm8 +Sar_rm8_imm8 +Sar_rm16_imm8 +Sar_rm32_imm8 +Mul_rm8 +Mul_rm16 +Mul_rm32 +Imul_rm8 +Imul_rm16 +Imul_rm32 +Imul_r16_rm16 +Imul_r32_rm32 +Imul_r16_rm16_imm16 +Imul_r32_rm32_imm32 +Imul_r16_rm16_imm8 +Imul_r32_rm32_imm8 +Div_rm8 +Div_rm16 +Div_rm32 +Idiv_rm8 +Idiv_rm16 +Idiv_rm32 +Bt_rm16_r16 +Bt_rm32_r32 +Btc_rm16_r16 +Btc_rm32_r32 +Btr_rm16_r16 +Btr_rm32_r32 +Bts_rm16_r16 +Bts_rm32_r32 +Bt_rm16_imm8 +Bt_rm32_imm8 +Btc_rm16_imm8 +Btc_rm32_imm8 +Btr_rm16_imm8 +Btr_rm32_imm8 +Bts_rm16_imm8 +Bts_rm32_imm8 +Bsr_r16_rm16 +Bsr_r32_rm32 +Tzcnt_r16_rm16 +Tzcnt_r32_rm32 +Xadd_rm8_r8 +Xadd_rm16_r16 +Xadd_rm32_r32 +Xchg_rm8_r8 +Xchg_rm16_r16 +Xchg_rm32_r32 +Cmpxchg_rm8_r8 +Cmpxchg_rm16_r16 +Cmpxchg_rm32_r32 +Movsx_r16_rm8 +Movsx_r32_rm8 +Movsx_r16_rm16 +Movsx_r32_rm16 +Movzx_r16_rm8 +Movzx_r32_rm8 +Movzx_r16_rm16 +Movzx_r32_rm16 +Test_rm8_imm8 +Test_rm16_imm16 +Test_rm32_imm32 +Add_rm8_imm8 +Add_rm16_imm16 +Add_rm32_imm32 +Add_rm16_imm8 +Add_rm32_imm8 +Or_rm8_imm8 +Or_rm16_imm16 +Or_rm32_imm32 +Or_rm16_imm8 +Or_rm32_imm8 +Adc_rm8_imm8 +Adc_rm16_imm16 +Adc_rm32_imm32 +Adc_rm16_imm8 +Adc_rm32_imm8 +Sbb_rm8_imm8 +Sbb_rm16_imm16 +Sbb_rm32_imm32 +Sbb_rm16_imm8 +Sbb_rm32_imm8 +And_rm8_imm8 +And_rm16_imm16 +And_rm32_imm32 +And_rm16_imm8 +And_rm32_imm8 +Sub_rm8_imm8 +Sub_rm16_imm16 +Sub_rm32_imm32 +Sub_rm16_imm8 +Sub_rm32_imm8 +Xor_rm8_imm8 +Xor_rm16_imm16 +Xor_rm32_imm32 +Xor_rm16_imm8 +Xor_rm32_imm8 +Cmp_rm8_imm8 +Cmp_rm16_imm16 +Cmp_rm32_imm32 +Cmp_rm16_imm8 +Cmp_rm32_imm8 +); + +fn test(code: Code) { + let instruction = build(code, Test::Registers, Register::None); + + case(instruction, None); + + let mut factory = InstructionInfoFactory::new(); + let info = factory.info(&instruction); + + if !info.used_memory().is_empty() { + let base = base(info.used_registers()); + let instruction = build(code, Test::Memory, base); + let size = instruction.memory_size().size() as usize; + + let words = size.div_ceil(16); + + let mut backing = vec![IMM128_A; words.max(1)]; + + let memory = unsafe { + slice::from_raw_parts_mut(backing.as_mut_ptr() as *mut u8, backing.len() * 16) + }; + + case(instruction, Some(&mut memory[..size])); + } } -fn normalize_and_compare(native: &mut State, emulated: &mut State, instruction: Instruction) { - let normalize = |state: &mut State| { - state.registers.remove(&VMReg::Rsp); +#[derive(Clone, Copy)] +enum Test { + Registers, + Memory, +} - if let Some(flags) = state.registers.get_mut(&VMReg::Flags) { - let mask = instruction.rflags_written() - | instruction.rflags_cleared() - | instruction.rflags_set(); - *flags &= ((mask & RflagsBits::CF != 0) as u64 * Flag::Carry.bit64()) - | ((mask & RflagsBits::PF != 0) as u64 * Flag::Parity.bit64()) - | ((mask & RflagsBits::AF != 0) as u64 * Flag::Auxiliary.bit64()) - | ((mask & RflagsBits::ZF != 0) as u64 * Flag::Zero.bit64()) - | ((mask & RflagsBits::SF != 0) as u64 * Flag::Sign.bit64()) - | ((mask & RflagsBits::OF != 0) as u64 * Flag::Overflow.bit64()); - } +fn build(code: Code, test: Test, memory_base: Register) -> Instruction { + let info = code.op_code(); + let kinds = [ + info.op0_kind(), + info.op1_kind(), + info.op2_kind(), + info.op3_kind(), + info.op4_kind(), + ]; + + let memory_operand = if matches!(test, Test::Memory) { + kinds.iter().position(|kind| memory(*kind)) + } else { + None }; - normalize(native); - normalize(emulated); + let mut instruction = Instruction::default(); + instruction.set_code(code); - let differences = native.compare(emulated); + for index in 0..5 { + let kind = kinds[index]; - assert!(differences.is_empty(), "{}", dump(&differences)); -} - -fn dump(differences: &[Difference]) -> String { - let mut lines = Vec::new(); + if kind == OpCodeOperandKind::None { + continue; + } - for difference in differences { - match difference { - Difference::Register(register, native, emulated) => { - lines.push(format!( - "{register:?}: native={native:016X} virtual={emulated:016X}" - )); - } - Difference::Vector(register, native, emulated) => { - lines.push(format!( - "{register:?}: native={native:02X?} virtual={emulated:02X?}" - )); - } + if Some(index) == memory_operand { + instruction.set_op_kind(index as u32, OpKind::Memory); + instruction.set_memory_base(memory_base); + instruction.set_memory_index(Register::None); + instruction.set_memory_index_scale(1); + instruction.set_memory_displacement64(0); + instruction.set_memory_displ_size(0); + continue; } + + operand(&mut instruction, index as u32, kind); } - lines.join("\n") + instruction } -macro_rules! testing { - ($name:ident, $state:expr, $instruction:expr) => { - #[test] - fn $name() { - check($state, $instruction); +fn operand(instruction: &mut Instruction, operand: u32, kind: OpCodeOperandKind) { + let op = match kind { + OpCodeOperandKind::r8_or_mem + | OpCodeOperandKind::r16_or_mem + | OpCodeOperandKind::r32_or_mem + | OpCodeOperandKind::r32_or_mem_mpx + | OpCodeOperandKind::r64_or_mem + | OpCodeOperandKind::r64_or_mem_mpx + | OpCodeOperandKind::mm_or_mem + | OpCodeOperandKind::xmm_or_mem + | OpCodeOperandKind::ymm_or_mem + | OpCodeOperandKind::zmm_or_mem + | OpCodeOperandKind::bnd_or_mem_mpx + | OpCodeOperandKind::k_or_mem + | OpCodeOperandKind::r8_reg + | OpCodeOperandKind::r8_opcode + | OpCodeOperandKind::r16_reg + | OpCodeOperandKind::r16_reg_mem + | OpCodeOperandKind::r16_rm + | OpCodeOperandKind::r16_opcode + | OpCodeOperandKind::r32_reg + | OpCodeOperandKind::r32_reg_mem + | OpCodeOperandKind::r32_rm + | OpCodeOperandKind::r32_opcode + | OpCodeOperandKind::r32_vvvv + | OpCodeOperandKind::r64_reg + | OpCodeOperandKind::r64_reg_mem + | OpCodeOperandKind::r64_rm + | OpCodeOperandKind::r64_opcode + | OpCodeOperandKind::r64_vvvv + | OpCodeOperandKind::seg_reg + | OpCodeOperandKind::k_reg + | OpCodeOperandKind::kp1_reg + | OpCodeOperandKind::k_rm + | OpCodeOperandKind::k_vvvv + | OpCodeOperandKind::mm_reg + | OpCodeOperandKind::mm_rm + | OpCodeOperandKind::xmm_reg + | OpCodeOperandKind::xmm_rm + | OpCodeOperandKind::xmm_vvvv + | OpCodeOperandKind::xmmp3_vvvv + | OpCodeOperandKind::xmm_is4 + | OpCodeOperandKind::xmm_is5 + | OpCodeOperandKind::ymm_reg + | OpCodeOperandKind::ymm_rm + | OpCodeOperandKind::ymm_vvvv + | OpCodeOperandKind::ymm_is4 + | OpCodeOperandKind::ymm_is5 + | OpCodeOperandKind::zmm_reg + | OpCodeOperandKind::zmm_rm + | OpCodeOperandKind::zmm_vvvv + | OpCodeOperandKind::zmmp3_vvvv + | OpCodeOperandKind::cr_reg + | OpCodeOperandKind::dr_reg + | OpCodeOperandKind::tr_reg + | OpCodeOperandKind::bnd_reg + | OpCodeOperandKind::es + | OpCodeOperandKind::cs + | OpCodeOperandKind::ss + | OpCodeOperandKind::ds + | OpCodeOperandKind::fs + | OpCodeOperandKind::gs + | OpCodeOperandKind::al + | OpCodeOperandKind::cl + | OpCodeOperandKind::ax + | OpCodeOperandKind::dx + | OpCodeOperandKind::eax + | OpCodeOperandKind::rax + | OpCodeOperandKind::st0 + | OpCodeOperandKind::sti_opcode + | OpCodeOperandKind::tmm_reg + | OpCodeOperandKind::tmm_rm + | OpCodeOperandKind::tmm_vvvv => OpKind::Register, + OpCodeOperandKind::imm4_m2z | OpCodeOperandKind::imm8 | OpCodeOperandKind::imm8_const_1 => { + OpKind::Immediate8 } + OpCodeOperandKind::imm8sex16 => OpKind::Immediate8to16, + OpCodeOperandKind::imm8sex32 => OpKind::Immediate8to32, + OpCodeOperandKind::imm8sex64 => OpKind::Immediate8to64, + OpCodeOperandKind::imm16 => OpKind::Immediate16, + OpCodeOperandKind::imm32 => OpKind::Immediate32, + OpCodeOperandKind::imm32sex64 => OpKind::Immediate32to64, + OpCodeOperandKind::imm64 => OpKind::Immediate64, + _ => panic!("unsupported operand kind: {kind:?}"), }; -} -macro_rules! testing_widths { - ($name:ident, $ins64:ident, $ins32:ident, $ins16:ident, $ins8:ident) => { - paste::paste! { - #[test] - fn [<$name _64>]() { - check(gpr(), instruction!($ins64, RAX, RCX)); - } - #[test] - fn [<$name _32>]() { - check(gpr(), instruction!($ins32, EAX, ECX)); - } - #[test] - fn [<$name _16>]() { - check(gpr(), instruction!($ins16, AX, CX)); - } - #[test] - fn [<$name _8>]() { - check(gpr(), instruction!($ins8, AL, CL)); - } - } - }; + instruction.set_op_kind(operand, op); + + if op == OpKind::Register { + instruction.set_op_register(operand, register(kind, operand as usize)); + } else { + let value = match kind { + OpCodeOperandKind::imm4_m2z => 0, + OpCodeOperandKind::imm8_const_1 => 1, + OpCodeOperandKind::imm8 + | OpCodeOperandKind::imm8sex16 + | OpCodeOperandKind::imm8sex32 + | OpCodeOperandKind::imm8sex64 => IMM8_A as u64, + OpCodeOperandKind::imm16 => _IMM16_A as u64, + OpCodeOperandKind::imm32 | OpCodeOperandKind::imm32sex64 => IMM32_A as u64, + OpCodeOperandKind::imm64 => IMM64_A, + _ => unreachable!(), + }; + + instruction.set_immediate_u64(operand, value); + } } -macro_rules! testing_memory { - ($name:ident, mut $buf:ident = $init:expr, $state:expr, $instruction:expr) => { - #[test] - fn $name() { - let mut $buf = $init; - check_with_memory($state, $instruction, unsafe { - slice::from_raw_parts_mut($buf.as_mut_ptr() as *mut u8, 8) - }); +fn register(kind: OpCodeOperandKind, operand: usize) -> Register { + match kind { + OpCodeOperandKind::al => Register::AL, + OpCodeOperandKind::cl => Register::CL, + OpCodeOperandKind::ax => Register::AX, + OpCodeOperandKind::dx => Register::DX, + OpCodeOperandKind::eax => Register::EAX, + OpCodeOperandKind::rax => Register::RAX, + OpCodeOperandKind::r8_reg | OpCodeOperandKind::r8_opcode | OpCodeOperandKind::r8_or_mem => { + [Register::AL, Register::CL, Register::DL, Register::BL][operand.min(3)] } - }; - ($name:ident, $buf:ident = $init:expr, $state:expr, $instruction:expr) => { - #[test] - fn $name() { - let $buf = $init; - check_with_memory($state, $instruction, unsafe { - slice::from_raw_parts_mut($buf.as_ptr() as *mut u8, 8) - }); + OpCodeOperandKind::r16_reg + | OpCodeOperandKind::r16_opcode + | OpCodeOperandKind::r16_reg_mem + | OpCodeOperandKind::r16_rm + | OpCodeOperandKind::r16_or_mem => { + [Register::AX, Register::CX, Register::DX, Register::BX][operand.min(3)] } - }; -} - -macro_rules! testing_set { - ($name:ident, $ins_variant:ident) => { - #[test] - fn $name() { - let mut buf = [0u8]; - check_with_memory( - baseline().with(VMReg::Rax, buf.as_mut_ptr() as u64), - instruction!($ins_variant, MemoryOperand::with_base(RAX)), - &mut buf, - ); + OpCodeOperandKind::r32_reg + | OpCodeOperandKind::r32_opcode + | OpCodeOperandKind::r32_vvvv + | OpCodeOperandKind::r32_reg_mem + | OpCodeOperandKind::r32_rm + | OpCodeOperandKind::r32_or_mem + | OpCodeOperandKind::r32_or_mem_mpx => { + [Register::EAX, Register::ECX, Register::EDX, Register::EBX][operand.min(3)] } - }; -} - -macro_rules! testing_simd_load { - ($name:ident, $init:expr, $instruction:expr) => { - #[test] - fn $name() { - let buf = $init; - check_with_memory( - simd().with(VMReg::Rax, buf.as_ptr() as u64), - $instruction, - unsafe { slice::from_raw_parts_mut(buf.as_ptr() as *mut u8, 32) }, - ); + OpCodeOperandKind::r64_reg + | OpCodeOperandKind::r64_opcode + | OpCodeOperandKind::r64_vvvv + | OpCodeOperandKind::r64_reg_mem + | OpCodeOperandKind::r64_rm + | OpCodeOperandKind::r64_or_mem + | OpCodeOperandKind::r64_or_mem_mpx => { + [Register::RAX, Register::RCX, Register::RDX, Register::RBX][operand.min(3)] } - }; + OpCodeOperandKind::xmm_reg + | OpCodeOperandKind::xmm_rm + | OpCodeOperandKind::xmm_vvvv + | OpCodeOperandKind::xmmp3_vvvv + | OpCodeOperandKind::xmm_is4 + | OpCodeOperandKind::xmm_is5 + | OpCodeOperandKind::xmm_or_mem => [ + Register::XMM0, + Register::XMM1, + Register::XMM2, + Register::XMM3, + ][operand.min(3)], + OpCodeOperandKind::ymm_reg + | OpCodeOperandKind::ymm_rm + | OpCodeOperandKind::ymm_vvvv + | OpCodeOperandKind::ymm_is4 + | OpCodeOperandKind::ymm_is5 + | OpCodeOperandKind::ymm_or_mem => [ + Register::YMM0, + Register::YMM1, + Register::YMM2, + Register::YMM3, + ][operand.min(3)], + OpCodeOperandKind::zmm_reg + | OpCodeOperandKind::zmm_rm + | OpCodeOperandKind::zmm_vvvv + | OpCodeOperandKind::zmmp3_vvvv + | OpCodeOperandKind::zmm_or_mem => [ + Register::ZMM0, + Register::ZMM1, + Register::ZMM2, + Register::ZMM3, + ][operand.min(3)], + _ => panic!("unsupported operand kind: {kind:?}"), + } } -macro_rules! testing_simd_store { - ($name:ident, $instruction:expr) => { - #[test] - fn $name() { - let mut buf = [0u128, 0u128]; - check_with_memory( - simd().with(VMReg::Rax, buf.as_mut_ptr() as u64), - $instruction, - unsafe { slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u8, 32) }, - ); - } - }; +fn memory(kind: OpCodeOperandKind) -> bool { + matches!( + kind, + OpCodeOperandKind::mem + | OpCodeOperandKind::mem_mpx + | OpCodeOperandKind::mem_mib + | OpCodeOperandKind::mem_vsib32x + | OpCodeOperandKind::mem_vsib64x + | OpCodeOperandKind::mem_vsib32y + | OpCodeOperandKind::mem_vsib64y + | OpCodeOperandKind::mem_vsib32z + | OpCodeOperandKind::mem_vsib64z + | OpCodeOperandKind::r8_or_mem + | OpCodeOperandKind::r16_or_mem + | OpCodeOperandKind::r32_or_mem + | OpCodeOperandKind::r32_or_mem_mpx + | OpCodeOperandKind::r64_or_mem + | OpCodeOperandKind::r64_or_mem_mpx + | OpCodeOperandKind::mm_or_mem + | OpCodeOperandKind::xmm_or_mem + | OpCodeOperandKind::ymm_or_mem + | OpCodeOperandKind::zmm_or_mem + | OpCodeOperandKind::bnd_or_mem_mpx + | OpCodeOperandKind::k_or_mem + | OpCodeOperandKind::r16_reg_mem + | OpCodeOperandKind::r32_reg_mem + | OpCodeOperandKind::r64_reg_mem + ) } -testing!(test_mov, gpr(), instruction!(Mov_r64_rm64, RAX, RCX)); -testing_memory!( - test_mov_load, - buf = [IMM64_B], - baseline() - .with(VMReg::Rax, buf.as_ptr() as u64) - .with(VMReg::Rcx, IMM64_A), - instruction!(Mov_r64_rm64, RCX, MemoryOperand::with_base(RAX)) -); -testing_memory!( - test_mov_store, - mut buf = [0u64], - baseline() - .with(VMReg::Rax, buf.as_mut_ptr() as u64) - .with(VMReg::Rcx, IMM64_A), - instruction!(Mov_rm64_r64, MemoryOperand::with_base(RAX), RCX) -); -testing!( - test_mov_imm64, - gpr(), - instruction!(Mov_r64_imm64, RAX, IMM64_A as i64) -); -testing!( - test_mov_imm32, - gpr(), - instruction!(Mov_rm64_imm32, RAX, SIMM32_A) -); - -testing!(test_movzx, gpr(), instruction!(Movzx_r64_rm8, RAX, CL)); -testing_memory!( - test_movzx_load, - buf = [IMM8_A], - baseline() - .with(VMReg::Rax, buf.as_ptr() as u64) - .with(VMReg::Rcx, IMM64_B), - instruction!(Movzx_r64_rm8, RCX, MemoryOperand::with_base(RAX)) -); - -testing!(test_movzsx, gpr(), instruction!(Movsx_r64_rm8, RAX, CL)); -testing_memory!( - test_movsx_load, - buf = [IMM8_A], - baseline() - .with(VMReg::Rax, buf.as_ptr() as u64) - .with(VMReg::Rcx, IMM64_B), - instruction!(Movsx_r64_rm8, RCX, MemoryOperand::with_base(RAX)) -); - -testing!(test_movzsxd, gpr(), instruction!(Movsxd_r64_rm32, RAX, EAX)); -testing_memory!( - test_movsxd_load, - buf = [IMM32_A], - baseline() - .with(VMReg::Rax, buf.as_ptr() as u64) - .with(VMReg::Rcx, IMM64_B), - instruction!(Movsxd_r64_rm32, RCX, MemoryOperand::with_base(RAX)) -); - -testing_widths!( - test_add, - Add_rm64_r64, - Add_rm32_r32, - Add_rm16_r16, - Add_rm8_r8 -); -testing_memory!( - test_add_load, - buf = [IMM64_B], - baseline() - .with(VMReg::Rax, buf.as_ptr() as u64) - .with(VMReg::Rcx, IMM64_A), - instruction!(Add_r64_rm64, RCX, MemoryOperand::with_base(RAX)) -); -testing_memory!( - test_add_store, - mut buf = [IMM64_A], - baseline() - .with(VMReg::Rax, buf.as_mut_ptr() as u64) - .with(VMReg::Rcx, IMM64_B), - instruction!(Add_rm64_r64, MemoryOperand::with_base(RAX), RCX) -); -testing!( - test_add_imm32, - gpr(), - instruction!(Add_rm64_imm32, RAX, SIMM32_A) -); -testing!( - test_add_imm8, - gpr(), - instruction!(Add_rm64_imm8, RAX, SIMM8_A) -); - -testing_widths!( - test_sub, - Sub_rm64_r64, - Sub_rm32_r32, - Sub_rm16_r16, - Sub_rm8_r8 -); -testing_memory!( - test_sub_load, - buf = [IMM64_B], - baseline() - .with(VMReg::Rax, buf.as_ptr() as u64) - .with(VMReg::Rcx, IMM64_A), - instruction!(Sub_r64_rm64, RCX, MemoryOperand::with_base(RAX)) -); -testing_memory!( - test_sub_store, - mut buf = [IMM64_A], - baseline() - .with(VMReg::Rax, buf.as_mut_ptr() as u64) - .with(VMReg::Rcx, IMM64_B), - instruction!(Sub_rm64_r64, MemoryOperand::with_base(RAX), RCX) -); -testing!( - test_sub_imm32, - gpr(), - instruction!(Sub_rm64_imm32, RAX, SIMM32_A) -); -testing!( - test_sub_imm8, - gpr(), - instruction!(Sub_rm64_imm8, RAX, SIMM8_A) -); - -testing_widths!( - test_adc, - Adc_rm64_r64, - Adc_rm32_r32, - Adc_rm16_r16, - Adc_rm8_r8 -); -testing_memory!( - test_adc_load, - buf = [IMM64_B], - baseline() - .with(VMReg::Flags, 0b0000000000000001u64) - .with(VMReg::Rax, buf.as_ptr() as u64) - .with(VMReg::Rcx, IMM64_A), - instruction!(Adc_r64_rm64, RCX, MemoryOperand::with_base(RAX)) -); -testing_memory!( - test_adc_store, - mut buf = [IMM64_A], - baseline() - .with(VMReg::Flags, 0b0000000000000001u64) - .with(VMReg::Rax, buf.as_mut_ptr() as u64) - .with(VMReg::Rcx, IMM64_B), - instruction!(Adc_rm64_r64, MemoryOperand::with_base(RAX), RCX) -); -testing!( - test_adc_imm32, - gpr().with(VMReg::Flags, 0b0000000000000001u64), - instruction!(Adc_rm64_imm32, RAX, SIMM32_A) -); -testing!( - test_adc_imm8, - gpr().with(VMReg::Flags, 0b0000000000000001u64), - instruction!(Adc_rm64_imm8, RAX, SIMM8_A) -); - -testing_widths!( - test_sbb, - Sbb_rm64_r64, - Sbb_rm32_r32, - Sbb_rm16_r16, - Sbb_rm8_r8 -); -testing_memory!( - test_sbb_load, - buf = [IMM64_B], - baseline() - .with(VMReg::Flags, 0b0000000000000001u64) - .with(VMReg::Rax, buf.as_ptr() as u64) - .with(VMReg::Rcx, IMM64_A), - instruction!(Sbb_r64_rm64, RCX, MemoryOperand::with_base(RAX)) -); -testing_memory!( - test_sbb_store, - mut buf = [IMM64_A], - baseline() - .with(VMReg::Flags, 0b0000000000000001u64) - .with(VMReg::Rax, buf.as_mut_ptr() as u64) - .with(VMReg::Rcx, IMM64_B), - instruction!(Sbb_rm64_r64, MemoryOperand::with_base(RAX), RCX) -); -testing!( - test_sbb_imm32, - gpr().with(VMReg::Flags, 0b0000000000000001u64), - instruction!(Sbb_rm64_imm32, RAX, SIMM32_A) -); -testing!( - test_sbb_imm8, - gpr().with(VMReg::Flags, 0b0000000000000001u64), - instruction!(Sbb_rm64_imm8, RAX, SIMM8_A) -); - -testing_widths!( - test_cmp, - Cmp_rm64_r64, - Cmp_rm32_r32, - Cmp_rm16_r16, - Cmp_rm8_r8 -); -testing_memory!( - test_cmp_load, - buf = [IMM64_B], - baseline() - .with(VMReg::Rax, buf.as_ptr() as u64) - .with(VMReg::Rcx, IMM64_A), - instruction!(Cmp_r64_rm64, RCX, MemoryOperand::with_base(RAX)) -); -testing_memory!( - test_cmp_store, - buf = [IMM64_A], - baseline() - .with(VMReg::Rax, buf.as_ptr() as u64) - .with(VMReg::Rcx, IMM64_B), - instruction!(Cmp_rm64_r64, MemoryOperand::with_base(RAX), RCX) -); -testing!( - test_cmp_imm32, - gpr(), - instruction!(Cmp_rm64_imm32, RAX, SIMM32_A) -); -testing!( - test_cmp_imm8, - gpr(), - instruction!(Cmp_rm64_imm8, RAX, SIMM8_A) -); - -testing_widths!( - test_test, - Test_rm64_r64, - Test_rm32_r32, - Test_rm16_r16, - Test_rm8_r8 -); -testing_memory!( - test_test_store, - buf = [IMM64_A], - baseline() - .with(VMReg::Rax, buf.as_ptr() as u64) - .with(VMReg::Rcx, IMM64_B), - instruction!(Test_rm64_r64, MemoryOperand::with_base(RAX), RCX) -); -testing!( - test_test_imm32, - gpr(), - instruction!(Test_rm64_imm32, RAX, SIMM32_A) -); - -testing_widths!( - test_and, - And_rm64_r64, - And_rm32_r32, - And_rm16_r16, - And_rm8_r8 -); -testing_memory!( - test_and_load, - buf = [IMM64_B], - baseline() - .with(VMReg::Rax, buf.as_ptr() as u64) - .with(VMReg::Rcx, IMM64_A), - instruction!(And_r64_rm64, RCX, MemoryOperand::with_base(RAX)) -); -testing_memory!( - test_and_store, - mut buf = [IMM64_A], - baseline() - .with(VMReg::Rax, buf.as_mut_ptr() as u64) - .with(VMReg::Rcx, IMM64_B), - instruction!(And_rm64_r64, MemoryOperand::with_base(RAX), RCX) -); -testing!( - test_and_imm32, - gpr(), - instruction!(And_rm64_imm32, RAX, SIMM32_A) -); -testing!( - test_and_imm8, - gpr(), - instruction!(And_rm64_imm8, RAX, SIMM8_A) -); - -testing_widths!(test_or, Or_rm64_r64, Or_rm32_r32, Or_rm16_r16, Or_rm8_r8); -testing_memory!( - test_or_load, - buf = [IMM64_B], - baseline() - .with(VMReg::Rax, buf.as_ptr() as u64) - .with(VMReg::Rcx, IMM64_A), - instruction!(Or_r64_rm64, RCX, MemoryOperand::with_base(RAX)) -); -testing_memory!( - test_or_store, - mut buf = [IMM64_A], - baseline() - .with(VMReg::Rax, buf.as_mut_ptr() as u64) - .with(VMReg::Rcx, IMM64_B), - instruction!(Or_rm64_r64, MemoryOperand::with_base(RAX), RCX) -); -testing!( - test_or_imm32, - gpr(), - instruction!(Or_rm64_imm32, RAX, SIMM32_A) -); -testing!( - test_or_imm8, - gpr(), - instruction!(Or_rm64_imm8, RAX, SIMM8_A) -); - -testing_widths!( - test_xor, - Xor_rm64_r64, - Xor_rm32_r32, - Xor_rm16_r16, - Xor_rm8_r8 -); -testing_memory!( - test_xor_load, - buf = [IMM64_B], - baseline() - .with(VMReg::Rax, buf.as_ptr() as u64) - .with(VMReg::Rcx, IMM64_A), - instruction!(Xor_r64_rm64, RCX, MemoryOperand::with_base(RAX)) -); -testing_memory!( - test_xor_store, - mut buf = [IMM64_A], - baseline() - .with(VMReg::Rax, buf.as_mut_ptr() as u64) - .with(VMReg::Rcx, IMM64_B), - instruction!(Xor_rm64_r64, MemoryOperand::with_base(RAX), RCX) -); -testing!( - test_xor_imm32, - gpr(), - instruction!(Xor_rm64_imm32, RAX, SIMM32_A) -); -testing!( - test_xor_imm8, - gpr(), - instruction!(Xor_rm64_imm8, RAX, SIMM8_A) -); - -testing!( - test_rol, - gpr().with(VMReg::Rcx, IMM8_A), - instruction!(Rol_rm64_CL, RAX, CL) -); -testing_memory!( - test_rol_store, - mut buf = [IMM64_A], - baseline() - .with(VMReg::Rax, buf.as_mut_ptr() as u64) - .with(VMReg::Rcx, IMM8_A), - instruction!(Rol_rm64_CL, MemoryOperand::with_base(RAX), CL) -); -testing!( - test_rol_imm8, - gpr(), - instruction!(Rol_rm64_imm8, RAX, SIMM8_A) -); - -testing!( - test_ror, - gpr().with(VMReg::Rcx, IMM8_A), - instruction!(Ror_rm64_CL, RAX, CL) -); -testing_memory!( - test_ror_store, - mut buf = [IMM64_A], - baseline() - .with(VMReg::Rax, buf.as_mut_ptr() as u64) - .with(VMReg::Rcx, IMM8_A), - instruction!(Ror_rm64_CL, MemoryOperand::with_base(RAX), CL) -); -testing!( - test_ror_imm8, - gpr(), - instruction!(Ror_rm64_imm8, RAX, SIMM8_A) -); - -testing!( - test_shl, - gpr().with(VMReg::Rcx, IMM8_A), - instruction!(Shl_rm64_CL, RAX, CL) -); -testing_memory!( - test_shl_store, - mut buf = [IMM64_A], - baseline() - .with(VMReg::Rax, buf.as_mut_ptr() as u64) - .with(VMReg::Rcx, IMM8_A), - instruction!(Shl_rm64_CL, MemoryOperand::with_base(RAX), CL) -); -testing!( - test_shl_imm8, - gpr(), - instruction!(Shl_rm64_imm8, RAX, SIMM8_A) -); - -testing!( - test_shr, - gpr().with(VMReg::Rcx, IMM8_A), - instruction!(Shr_rm64_CL, RAX, CL) -); -testing_memory!( - test_shr_store, - mut buf = [IMM64_A], - baseline() - .with(VMReg::Rax, buf.as_mut_ptr() as u64) - .with(VMReg::Rcx, IMM8_A), - instruction!(Shr_rm64_CL, MemoryOperand::with_base(RAX), CL) -); -testing!( - test_shr_imm8, - gpr(), - instruction!(Shr_rm64_imm8, RAX, SIMM8_A) -); - -testing!( - test_sar, - gpr().with(VMReg::Rcx, IMM8_A), - instruction!(Sar_rm64_CL, RAX, CL) -); -testing_memory!( - test_sar_store, - mut buf = [IMM64_A], - baseline() - .with(VMReg::Rax, buf.as_mut_ptr() as u64) - .with(VMReg::Rcx, IMM8_A), - instruction!(Sar_rm64_CL, MemoryOperand::with_base(RAX), CL) -); -testing!( - test_sar_imm8, - gpr(), - instruction!(Sar_rm64_imm8, RAX, SIMM8_A) -); - -testing!(test_inc, gpr(), instruction!(Inc_rm64, RAX)); -testing_memory!( - test_inc_store, - mut buf = [IMM64_A], - baseline().with(VMReg::Rax, buf.as_mut_ptr() as u64), - instruction!(Inc_rm64, MemoryOperand::with_base(RAX)) -); - -testing!(test_dec, gpr(), instruction!(Dec_rm64, RAX)); -testing_memory!( - test_dec_store, - mut buf = [IMM64_A], - baseline().with(VMReg::Rax, buf.as_mut_ptr() as u64), - instruction!(Dec_rm64, MemoryOperand::with_base(RAX)) -); - -testing!(test_neg, gpr(), instruction!(Neg_rm64, RAX)); -testing_memory!( - test_neg_store, - mut buf = [IMM64_A], - baseline().with(VMReg::Rax, buf.as_mut_ptr() as u64), - instruction!(Neg_rm64, MemoryOperand::with_base(RAX)) -); - -testing!(test_not, gpr(), instruction!(Not_rm64, RAX)); -testing_memory!( - test_not_store, - mut buf = [IMM64_A], - baseline().with(VMReg::Rax, buf.as_mut_ptr() as u64), - instruction!(Not_rm64, MemoryOperand::with_base(RAX)) -); - -testing!(test_mul, gpr(), instruction!(Mul_rm64, RCX)); -testing_memory!( - test_mul_load, - buf = [IMM64_B], - baseline() - .with(VMReg::Rax, IMM64_A) - .with(VMReg::Rcx, buf.as_ptr() as u64), - instruction!(Mul_rm64, MemoryOperand::with_base(RCX)) -); - -testing!(test_imul, gpr(), instruction!(Imul_rm64, RCX)); -testing_memory!( - test_imul_load, - buf = [IMM64_B], - baseline() - .with(VMReg::Rax, IMM64_A) - .with(VMReg::Rcx, buf.as_ptr() as u64), - instruction!(Imul_rm64, MemoryOperand::with_base(RCX)) -); - -testing!(test_imul2, gpr(), instruction!(Imul_r64_rm64, RAX, RCX)); -testing_memory!( - test_imul2_load, - buf = [IMM64_B], - baseline() - .with(VMReg::Rax, IMM64_A) - .with(VMReg::Rcx, buf.as_ptr() as u64), - instruction!(Imul_r64_rm64, RAX, MemoryOperand::with_base(RCX)) -); -testing!( - test_imul3, - gpr(), - instruction!(Imul_r64_rm64_imm32, RAX, RCX, SIMM32_A) -); -testing!( - test_imul3_imm8, - gpr(), - instruction!(Imul_r64_rm64_imm8, RAX, RCX, SIMM8_A) -); - -testing!( - test_div, - gpr().zeroed(VMReg::Rdx).with(VMReg::Rax, IMM64_A), - instruction!(Div_rm64, RCX) -); -testing_memory!( - test_div_load, - buf = [IMM64_B], - baseline() - .with(VMReg::Rax, IMM64_A) - .with(VMReg::Rdx, 0u64) - .with(VMReg::Rcx, buf.as_ptr() as u64), - instruction!(Div_rm64, MemoryOperand::with_base(RCX)) -); - -testing!( - test_idiv, - gpr().zeroed(VMReg::Rdx).with(VMReg::Rax, IMM64_A), - instruction!(Idiv_rm64, RCX) -); -testing_memory!( - test_idiv_load, - buf = [IMM64_B], - baseline() - .with(VMReg::Rax, IMM64_A) - .with(VMReg::Rdx, 0u64) - .with(VMReg::Rcx, buf.as_ptr() as u64), - instruction!(Idiv_rm64, MemoryOperand::with_base(RCX)) -); - -testing!(test_tzcnt, gpr(), instruction!(Tzcnt_r64_rm64, RAX, RCX)); -testing_memory!( - test_tzcnt_load, - buf = [IMM64_B], - baseline() - .with(VMReg::Rax, IMM64_A) - .with(VMReg::Rcx, buf.as_ptr() as u64), - instruction!(Tzcnt_r64_rm64, RAX, MemoryOperand::with_base(RCX)) -); - -testing!(test_bsr, gpr(), instruction!(Bsr_r64_rm64, RAX, RCX)); -testing_memory!( - test_bsr_load, - buf = [IMM64_B], - baseline() - .with(VMReg::Rax, IMM64_A) - .with(VMReg::Rcx, buf.as_ptr() as u64), - instruction!(Bsr_r64_rm64, RAX, MemoryOperand::with_base(RCX)) -); - -testing!(test_bswap, gpr(), instruction!(Bswap_r64, RAX)); - -testing!(test_bt, gpr(), instruction!(Bt_rm64_r64, RAX, RCX)); -testing_memory!( - test_bt_store, - buf = [IMM64_A], - baseline() - .with(VMReg::Rax, buf.as_ptr() as u64) - .with(VMReg::Rcx, IMM64_B), - instruction!(Bt_rm64_r64, MemoryOperand::with_base(RAX), RCX) -); -testing!( - test_bt_imm8, - gpr(), - instruction!(Bt_rm64_imm8, RAX, SIMM8_A) -); - -testing!(test_bts, gpr(), instruction!(Bts_rm64_r64, RAX, RCX)); -testing_memory!( - test_bts_store, - mut buf = [IMM64_A], - baseline() - .with(VMReg::Rax, buf.as_mut_ptr() as u64) - .with(VMReg::Rcx, IMM64_B), - instruction!(Bts_rm64_r64, MemoryOperand::with_base(RAX), RCX) -); -testing!( - test_bts_imm8, - gpr(), - instruction!(Bts_rm64_imm8, RAX, SIMM8_A) -); - -testing!(test_btr, gpr(), instruction!(Btr_rm64_r64, RAX, RCX)); -testing_memory!( - test_btr_store, - mut buf = [IMM64_A], - baseline() - .with(VMReg::Rax, buf.as_mut_ptr() as u64) - .with(VMReg::Rcx, IMM64_B), - instruction!(Btr_rm64_r64, MemoryOperand::with_base(RAX), RCX) -); -testing!( - test_btr_imm8, - gpr(), - instruction!(Btr_rm64_imm8, RAX, SIMM8_A) -); +fn base(used: &[iced_x86::UsedRegister]) -> Register { + const CANDIDATES: &[Register] = &[ + Register::R15, + Register::R14, + Register::R13, + Register::R12, + Register::R11, + Register::R10, + Register::R9, + Register::R8, + Register::RBX, + Register::RBP, + Register::RSI, + Register::RDI, + ]; + + CANDIDATES + .iter() + .copied() + .find(|candidate| !used.iter().any(|used| used.register() == *candidate)) + .unwrap() +} -testing!(test_btc, gpr(), instruction!(Btc_rm64_r64, RAX, RCX)); -testing_memory!( - test_btc_store, - mut buf = [IMM64_A], - baseline() - .with(VMReg::Rax, buf.as_mut_ptr() as u64) - .with(VMReg::Rcx, IMM64_B), - instruction!(Btc_rm64_r64, MemoryOperand::with_base(RAX), RCX) -); -testing!( - test_btc_imm8, - gpr(), - instruction!(Btc_rm64_imm8, RAX, SIMM8_A) -); +fn case(instruction: Instruction, mut memory: Option<&mut [u8]>) { + let mut info_factory = InstructionInfoFactory::new(); + let info = info_factory.info(&instruction); + + let vector = info + .used_registers() + .iter() + .any(|used| used.register().is_vector_register()); + + let mut state = if vector { simd() } else { gpr() }; + + let has_rax = info + .used_registers() + .iter() + .any(|used| used.register().full_register() == Register::RAX); + + let has_rdx = info + .used_registers() + .iter() + .any(|used| used.register().full_register() == Register::RDX); + + let reads_rdx = info.used_registers().iter().any(|used| { + used.register().full_register() == Register::RDX + && matches!( + used.access(), + OpAccess::Read | OpAccess::ReadWrite | OpAccess::ReadCondWrite | OpAccess::CondRead + ) + }); + + if has_rax && has_rdx && reads_rdx { + state = state.with(VMReg::from(Register::RDX), 0); + } -testing!(test_xchg, gpr(), instruction!(Xchg_rm64_r64, RAX, RCX)); -testing_memory!( - test_xchg_store, - mut buf = [IMM64_A], - baseline() - .with(VMReg::Rax, buf.as_mut_ptr() as u64) - .with(VMReg::Rcx, IMM64_B), - instruction!(Xchg_rm64_r64, MemoryOperand::with_base(RAX), RCX) -); + if matches!(instruction.mnemonic(), Mnemonic::Div | Mnemonic::Idiv) { + let size = match instruction.op_kind(0) { + OpKind::Register => instruction.op_register(0).info().size(), + OpKind::Memory => instruction.memory_size().size(), + _ => unreachable!(), + }; -testing!(test_xadd, gpr(), instruction!(Xadd_rm64_r64, RAX, RCX)); -testing_memory!( - test_xadd_store, - mut buf = [IMM64_A], - baseline() - .with(VMReg::Rax, buf.as_mut_ptr() as u64) - .with(VMReg::Rcx, IMM64_B), - instruction!(Xadd_rm64_r64, MemoryOperand::with_base(RAX), RCX) -); + let bits = size * 8; + let mask = if size == 8 { + u64::MAX + } else { + (1u64 << bits) - 1 + }; + let sign = 1u64 << (bits - 1); -testing!( - test_cmpxchg, - gpr(), - instruction!(Cmpxchg_rm64_r64, RBX, RCX) -); -testing_memory!( - test_cmpxchg_store, - mut buf = [IMM64_A], - baseline() - .with(VMReg::Rax, IMM64_A) - .with(VMReg::Rbx, buf.as_mut_ptr() as u64) - .with(VMReg::Rcx, IMM64_B), - instruction!(Cmpxchg_rm64_r64, MemoryOperand::with_base(RBX), RCX) -); + let rax = state.registers[&VMReg::Rax]; + let rdx = state.registers[&VMReg::Rdx]; -testing!(test_cmove, gpr(), instruction!(Cmove_r64_rm64, RAX, RCX)); -testing!(test_cmovne, gpr(), instruction!(Cmovne_r64_rm64, RAX, RCX)); -testing!(test_cmova, gpr(), instruction!(Cmova_r64_rm64, RAX, RCX)); -testing!(test_cmovae, gpr(), instruction!(Cmovae_r64_rm64, RAX, RCX)); -testing!(test_cmovb, gpr(), instruction!(Cmovb_r64_rm64, RAX, RCX)); -testing!(test_cmovbe, gpr(), instruction!(Cmovbe_r64_rm64, RAX, RCX)); -testing!(test_cmovg, gpr(), instruction!(Cmovg_r64_rm64, RAX, RCX)); -testing!(test_cmovge, gpr(), instruction!(Cmovge_r64_rm64, RAX, RCX)); -testing!(test_cmovl, gpr(), instruction!(Cmovl_r64_rm64, RAX, RCX)); -testing!(test_cmovle, gpr(), instruction!(Cmovle_r64_rm64, RAX, RCX)); -testing!(test_cmovo, gpr(), instruction!(Cmovo_r64_rm64, RAX, RCX)); -testing!(test_cmovno, gpr(), instruction!(Cmovno_r64_rm64, RAX, RCX)); -testing!(test_cmovp, gpr(), instruction!(Cmovp_r64_rm64, RAX, RCX)); -testing!(test_cmovnp, gpr(), instruction!(Cmovnp_r64_rm64, RAX, RCX)); -testing!(test_cmovs, gpr(), instruction!(Cmovs_r64_rm64, RAX, RCX)); -testing!(test_cmovns, gpr(), instruction!(Cmovns_r64_rm64, RAX, RCX)); + let mut low = rax & mask; + let mut high = if size == 1 { + (rax >> 8) & mask + } else { + rdx & mask + }; -testing!(test_seta, gpr(), instruction!(Seta_rm8, AL)); -testing_set!(test_seta_store, Seta_rm8); + let memory_operand = instruction.op_kind(0) == OpKind::Memory; -testing!(test_setae, gpr(), instruction!(Setae_rm8, AL)); -testing_set!(test_setae_store, Setae_rm8); + let mut divisor = if memory_operand { + let bytes = memory.as_deref().unwrap(); -testing!(test_setb, gpr(), instruction!(Setb_rm8, AL)); -testing_set!(test_setb_store, Setb_rm8); + let mut value = 0; -testing!(test_setbe, gpr(), instruction!(Setbe_rm8, AL)); -testing_set!(test_setbe_store, Setbe_rm8); + for index in 0..size { + value |= (bytes[index] as u64) << (index * 8); + } -testing!(test_sete, gpr(), instruction!(Sete_rm8, AL)); -testing_set!(test_sete_store, Sete_rm8); + value + } else { + let register = instruction.op_register(0).full_register(); -testing!(test_setg, gpr(), instruction!(Setg_rm8, AL)); -testing_set!(test_setg_store, Setg_rm8); + state.registers[&VMReg::from(register)] & mask + }; -testing!(test_setge, gpr(), instruction!(Setge_rm8, AL)); -testing_set!(test_setge_store, Setge_rm8); + if divisor == 0 { + divisor = divisor.wrapping_add(1); -testing!(test_setl, gpr(), instruction!(Setl_rm8, AL)); -testing_set!(test_setl_store, Setl_rm8); + if memory_operand { + let bytes = memory.as_deref_mut().unwrap(); -testing!(test_setle, gpr(), instruction!(Setle_rm8, AL)); -testing_set!(test_setle_store, Setle_rm8); + for index in 0..size { + bytes[index] = (divisor >> (index * 8)) as u8; + } + } else { + let register = instruction.op_register(0).full_register(); + let value = state.registers[&VMReg::from(register)]; -testing!(test_setne, gpr(), instruction!(Setne_rm8, AL)); -testing_set!(test_setne_store, Setne_rm8); + state = state.with(VMReg::from(register), (value & !mask) | divisor); + low = divisor; + } + } -testing!(test_seto, gpr(), instruction!(Seto_rm8, AL)); -testing_set!(test_seto_store, Seto_rm8); + match instruction.mnemonic() { + Mnemonic::Div => { + high %= divisor; + } + Mnemonic::Idiv => { + let signed_low = if low & sign != 0 { + low as i128 - (1i128 << bits) + } else { + low as i128 + }; + + let signed_divisor = if divisor & sign != 0 { + divisor as i128 - (1i128 << bits) + } else { + divisor as i128 + }; + + if signed_low == -(1i128 << (bits - 1)) && signed_divisor == -1 { + low = low.wrapping_add(1) & mask; + + if !memory_operand + && instruction.op_register(0).full_register() != Register::RAX + { + let register = instruction.op_register(0).full_register(); + let value = state.registers[&VMReg::from(register)]; + + state = state.with(VMReg::from(register), (value & !mask) | low); + } + + if memory_operand && instruction.op_register(0) != Register::None { + unreachable!(); + } + } + + high = if low & sign != 0 { mask } else { 0 }; + } + _ => unreachable!(), + } -testing!(test_setno, gpr(), instruction!(Setno_rm8, AL)); -testing_set!(test_setno_store, Setno_rm8); + if !memory_operand { + let register = instruction.op_register(0).full_register(); -testing!(test_setp, gpr(), instruction!(Setp_rm8, AL)); -testing_set!(test_setp_store, Setp_rm8); + if register == Register::RAX { + let value = state.registers[&VMReg::Rax]; -testing!(test_setnp, gpr(), instruction!(Setnp_rm8, AL)); -testing_set!(test_setnp_store, Setnp_rm8); + state = state.with(VMReg::Rax, (value & !mask) | low); + } + } -testing!(test_sets, gpr(), instruction!(Sets_rm8, AL)); -testing_set!(test_sets_store, Sets_rm8); + let rax = if size == 1 { + (rax & !(mask | (mask << 8))) | low | (high << 8) + } else { + (rax & !mask) | low + }; -testing!(test_setns, gpr(), instruction!(Setns_rm8, AL)); -testing_set!(test_setns_store, Setns_rm8); + let rdx = if size == 1 { rdx } else { (rdx & !mask) | high }; -testing!( - test_pcmpeqb, - simd(), - instruction!(Pcmpeqb_xmm_xmmm128, XMM0, XMM1) -); -testing_simd_load!( - test_pcmpeqb_load, - [IMM128_B, 0u128], - instruction!(Pcmpeqb_xmm_xmmm128, XMM0, MemoryOperand::with_base(RAX)) -); + state = state.with(VMReg::Rax, rax).with(VMReg::Rdx, rdx); + } -testing!( - test_pmovmskb, - simd(), - instruction!(Pmovmskb_r64_xmm, RAX, XMM1) -); + if instruction.rflags_read() != RflagsBits::NONE { + state = state.with( + VMReg::Flags, + Flag::Carry.bit64() + | Flag::Parity.bit64() + | Flag::Auxiliary.bit64() + | Flag::Zero.bit64() + | Flag::Sign.bit64() + | Flag::Overflow.bit64(), + ); + } -testing!(test_movd, simd(), instruction!(Movd_xmm_rm32, XMM0, EAX)); -testing_simd_load!( - test_movd_load, - [IMM128_B, 0u128], - instruction!(Movd_xmm_rm32, XMM0, MemoryOperand::with_base(RAX)) -); -testing_simd_store!( - test_movd_store, - instruction!(Movd_rm32_xmm, MemoryOperand::with_base(RAX), XMM1) -); + if let Some(memory) = memory { + state = state.with( + VMReg::from(instruction.memory_base()), + memory.as_mut_ptr() as u64, + ); + compare_memory(state, instruction, memory); + } else { + compare(state, instruction); + } +} -testing!(test_movq, simd(), instruction!(Movq_xmm_rm64, XMM0, RAX)); -testing_simd_load!( - test_movq_load, - [IMM128_B, 0u128], - instruction!(Movq_xmm_rm64, XMM0, MemoryOperand::with_base(RAX)) -); -testing_simd_store!( - test_movq_store, - instruction!(Movq_rm64_xmm, MemoryOperand::with_base(RAX), XMM1) -); +fn compare(state: State, instruction: Instruction) { + compare_memory(state, instruction, &mut []); +} -testing!( - test_movaps, - simd(), - instruction!(Movaps_xmm_xmmm128, XMM0, XMM1) -); -testing_simd_load!( - test_movaps_load, - [IMM128_B, 0u128], - instruction!(Movaps_xmm_xmmm128, XMM0, MemoryOperand::with_base(RAX)) -); -testing_simd_store!( - test_movaps_store, - instruction!(Movaps_xmmm128_xmm, MemoryOperand::with_base(RAX), XMM1) -); +fn compare_memory(state: State, instruction: Instruction, memory: &mut [u8]) { + let baseline = memory.to_vec(); -testing!( - test_movups, - simd(), - instruction!(Movups_xmm_xmmm128, XMM0, XMM1) -); -testing_simd_load!( - test_movups_load, - [IMM128_B, 0u128], - instruction!(Movups_xmm_xmmm128, XMM0, MemoryOperand::with_base(RAX)) -); -testing_simd_store!( - test_movups_store, - instruction!(Movups_xmmm128_xmm, MemoryOperand::with_base(RAX), XMM1) -); + let mut executor = Executor::new(); + let mut native = executor.run_native(state.clone(), &[instruction]); -testing!( - test_movapd, - simd(), - instruction!(Movapd_xmm_xmmm128, XMM0, XMM1) -); -testing_simd_load!( - test_movapd_load, - [IMM128_B, 0u128], - instruction!(Movapd_xmm_xmmm128, XMM0, MemoryOperand::with_base(RAX)) -); -testing_simd_store!( - test_movapd_store, - instruction!(Movapd_xmmm128_xmm, MemoryOperand::with_base(RAX), XMM1) -); + memory.copy_from_slice(&baseline); -testing!( - test_movupd, - simd(), - instruction!(Movupd_xmm_xmmm128, XMM0, XMM1) -); -testing_simd_load!( - test_movupd_load, - [IMM128_B, 0u128], - instruction!(Movupd_xmm_xmmm128, XMM0, MemoryOperand::with_base(RAX)) -); -testing_simd_store!( - test_movupd_store, - instruction!(Movupd_xmmm128_xmm, MemoryOperand::with_base(RAX), XMM1) -); + let mut executor = Executor::new(); + let lifted = bytecode::lift(&[instruction]) + .unwrap_or_else(|| panic!("{instruction} is not implemented")); -testing!( - test_movdqa, - simd(), - instruction!(Movdqa_xmm_xmmm128, XMM0, XMM1) -); -testing_simd_load!( - test_movdqa_load, - [IMM128_B, 0u128], - instruction!(Movdqa_xmm_xmmm128, XMM0, MemoryOperand::with_base(RAX)) -); -testing_simd_store!( - test_movdqa_store, - instruction!(Movdqa_xmmm128_xmm, MemoryOperand::with_base(RAX), XMM1) -); + println!( + "{}", + lifted + .iter() + .map(|op| format!("{op:?}")) + .collect::>() + .join("\n") + ); -testing!( - test_movdqu, - simd(), - instruction!(Movdqu_xmm_xmmm128, XMM0, XMM1) -); -testing_simd_load!( - test_movdqu_load, - [IMM128_B, 0u128], - instruction!(Movdqu_xmm_xmmm128, XMM0, MemoryOperand::with_base(RAX)) -); -testing_simd_store!( - test_movdqu_store, - instruction!(Movdqu_xmmm128_xmm, MemoryOperand::with_base(RAX), XMM1) -); + let transformed = bytecode::transform(&mut executor.rt.mapper, lifted, |_| 0); -testing!( - test_movss, - simd(), - instruction!(Movss_xmm_xmmm32, XMM0, XMM1) -); -testing_simd_load!( - test_movss_load, - [IMM128_B, 0u128], - instruction!(Movss_xmm_xmmm32, XMM0, MemoryOperand::with_base(RAX)) -); -testing_simd_store!( - test_movss_store, - instruction!(Movss_xmmm32_xmm, MemoryOperand::with_base(RAX), XMM1) -); + let mut bytes = bytecode::assemble(&mut executor.rt.mapper, &transformed); + encrypt_block(&mut bytes); + decrypt_payload(&mut bytes); + let mut emulated = executor.run_virtual(state.clone(), &bytes); -testing!( - test_movsd, - simd(), - instruction!(Movsd_xmm_xmmm64, XMM0, XMM1) -); -testing_simd_load!( - test_movsd_load, - [IMM128_B, 0u128], - instruction!(Movsd_xmm_xmmm64, XMM0, MemoryOperand::with_base(RAX)) -); -testing_simd_store!( - test_movsd_store, - instruction!(Movsd_xmmm64_xmm, MemoryOperand::with_base(RAX), XMM1) -); + normalize(&mut native, &mut emulated, instruction); +} -testing!( - test_pand, - simd(), - instruction!(Pand_xmm_xmmm128, XMM0, XMM1) -); -testing_simd_load!( - test_pand_load, - [IMM128_B, 0u128], - instruction!(Pand_xmm_xmmm128, XMM0, MemoryOperand::with_base(RAX)) -); -testing!( - test_andps, - simd(), - instruction!(Andps_xmm_xmmm128, XMM0, XMM1) -); -testing_simd_load!( - test_andps_load, - [IMM128_B, 0u128], - instruction!(Andps_xmm_xmmm128, XMM0, MemoryOperand::with_base(RAX)) -); -testing!( - test_andpd, - simd(), - instruction!(Andpd_xmm_xmmm128, XMM0, XMM1) -); -testing_simd_load!( - test_andpd_load, - [IMM128_B, 0u128], - instruction!(Andpd_xmm_xmmm128, XMM0, MemoryOperand::with_base(RAX)) -); -testing!( - test_vandps, - simd(), - instruction!(VEX_Vandps_xmm_xmm_xmmm128, XMM0, XMM1, XMM2) -); -testing_simd_load!( - test_vandps_load, - [IMM128_C, 0u128], - instruction!( - VEX_Vandps_xmm_xmm_xmmm128, - XMM0, - XMM1, - MemoryOperand::with_base(RAX) - ) -); +fn normalize(native: &mut State, emulated: &mut State, instruction: Instruction) { + native.registers.remove(&VMReg::Rsp); + emulated.registers.remove(&VMReg::Rsp); -testing!( - test_pandn, - simd(), - instruction!(Pandn_xmm_xmmm128, XMM0, XMM1) -); -testing_simd_load!( - test_pandn_load, - [IMM128_B, 0u128], - instruction!(Pandn_xmm_xmmm128, XMM0, MemoryOperand::with_base(RAX)) -); -testing!( - test_andnps, - simd(), - instruction!(Andnps_xmm_xmmm128, XMM0, XMM1) -); -testing_simd_load!( - test_andnps_load, - [IMM128_B, 0u128], - instruction!(Andnps_xmm_xmmm128, XMM0, MemoryOperand::with_base(RAX)) -); -testing!( - test_andnpd, - simd(), - instruction!(Andnpd_xmm_xmmm128, XMM0, XMM1) -); -testing_simd_load!( - test_andnpd_load, - [IMM128_B, 0u128], - instruction!(Andnpd_xmm_xmmm128, XMM0, MemoryOperand::with_base(RAX)) -); + for state in [&mut *native, &mut *emulated] { + if let Some(flags) = state.registers.get_mut(&VMReg::Flags) { + let mask = instruction.rflags_written() + | instruction.rflags_cleared() + | instruction.rflags_set(); -testing!(test_por, simd(), instruction!(Por_xmm_xmmm128, XMM0, XMM1)); -testing_simd_load!( - test_por_load, - [IMM128_B, 0u128], - instruction!(Por_xmm_xmmm128, XMM0, MemoryOperand::with_base(RAX)) -); -testing!( - test_orps, - simd(), - instruction!(Orps_xmm_xmmm128, XMM0, XMM1) -); -testing_simd_load!( - test_orps_load, - [IMM128_B, 0u128], - instruction!(Orps_xmm_xmmm128, XMM0, MemoryOperand::with_base(RAX)) -); -testing!( - test_orpd, - simd(), - instruction!(Orpd_xmm_xmmm128, XMM0, XMM1) -); -testing_simd_load!( - test_orpd_load, - [IMM128_B, 0u128], - instruction!(Orpd_xmm_xmmm128, XMM0, MemoryOperand::with_base(RAX)) -); + *flags &= ((mask & RflagsBits::CF != 0) as u64 * Flag::Carry.bit64()) + | ((mask & RflagsBits::PF != 0) as u64 * Flag::Parity.bit64()) + | ((mask & RflagsBits::AF != 0) as u64 * Flag::Auxiliary.bit64()) + | ((mask & RflagsBits::ZF != 0) as u64 * Flag::Zero.bit64()) + | ((mask & RflagsBits::SF != 0) as u64 * Flag::Sign.bit64()) + | ((mask & RflagsBits::OF != 0) as u64 * Flag::Overflow.bit64()); + } + } -testing!( - test_pxor, - simd(), - instruction!(Pxor_xmm_xmmm128, XMM0, XMM1) -); -testing_simd_load!( - test_pxor_load, - [IMM128_B, 0u128], - instruction!(Pxor_xmm_xmmm128, XMM0, MemoryOperand::with_base(RAX)) -); -testing!( - test_xorps, - simd(), - instruction!(Xorps_xmm_xmmm128, XMM0, XMM1) -); -testing_simd_load!( - test_xorps_load, - [IMM128_B, 0u128], - instruction!(Xorps_xmm_xmmm128, XMM0, MemoryOperand::with_base(RAX)) -); -testing!( - test_xorpd, - simd(), - instruction!(Xorpd_xmm_xmmm128, XMM0, XMM1) -); -testing_simd_load!( - test_xorpd_load, - [IMM128_B, 0u128], - instruction!(Xorpd_xmm_xmmm128, XMM0, MemoryOperand::with_base(RAX)) -); -testing!( - test_vpxor, - simd(), - instruction!(VEX_Vpxor_xmm_xmm_xmmm128, XMM0, XMM1, XMM2) -); -testing_simd_load!( - test_vpxor_load, - [IMM128_C, 0u128], - instruction!( - VEX_Vpxor_xmm_xmm_xmmm128, - XMM0, - XMM1, - MemoryOperand::with_base(RAX) - ) -); -testing!( - test_vxorps, - simd(), - instruction!(VEX_Vxorps_xmm_xmm_xmmm128, XMM0, XMM1, XMM2) -); -testing_simd_load!( - test_vxorps_load, - [IMM128_C, 0u128], - instruction!( - VEX_Vxorps_xmm_xmm_xmmm128, - XMM0, - XMM1, - MemoryOperand::with_base(RAX) - ) -); + let differences = native.compare(emulated); -testing!( - test_paddb, - simd(), - instruction!(Paddb_xmm_xmmm128, XMM0, XMM1) -); -testing_simd_load!( - test_paddb_load, - [IMM128_B, 0u128], - instruction!(Paddb_xmm_xmmm128, XMM0, MemoryOperand::with_base(RAX)) -); -testing!( - test_paddw, - simd(), - instruction!(Paddw_xmm_xmmm128, XMM0, XMM1) -); -testing_simd_load!( - test_paddw_load, - [IMM128_B, 0u128], - instruction!(Paddw_xmm_xmmm128, XMM0, MemoryOperand::with_base(RAX)) -); -testing!( - test_paddd, - simd(), - instruction!(Paddd_xmm_xmmm128, XMM0, XMM1) -); -testing_simd_load!( - test_paddd_load, - [IMM128_B, 0u128], - instruction!(Paddd_xmm_xmmm128, XMM0, MemoryOperand::with_base(RAX)) -); -testing!( - test_paddq, - simd(), - instruction!(Paddq_xmm_xmmm128, XMM0, XMM1) -); -testing_simd_load!( - test_paddq_load, - [IMM128_B, 0u128], - instruction!(Paddq_xmm_xmmm128, XMM0, MemoryOperand::with_base(RAX)) -); -testing!( - test_addps, - simd(), - instruction!(Addps_xmm_xmmm128, XMM0, XMM1) -); -testing_simd_load!( - test_addps_load, - [IMM128_B, 0u128], - instruction!(Addps_xmm_xmmm128, XMM0, MemoryOperand::with_base(RAX)) -); -testing!( - test_addpd, - simd(), - instruction!(Addpd_xmm_xmmm128, XMM0, XMM1) -); -testing_simd_load!( - test_addpd_load, - [IMM128_B, 0u128], - instruction!(Addpd_xmm_xmmm128, XMM0, MemoryOperand::with_base(RAX)) -); -testing!( - test_vaddps, - simd(), - instruction!(VEX_Vaddps_xmm_xmm_xmmm128, XMM0, XMM1, XMM2) -); -testing_simd_load!( - test_vaddps_load, - [IMM128_C, 0u128], - instruction!( - VEX_Vaddps_xmm_xmm_xmmm128, - XMM0, - XMM1, - MemoryOperand::with_base(RAX) - ) -); -testing!( - test_vaddpd, - simd(), - instruction!(VEX_Vaddpd_xmm_xmm_xmmm128, XMM0, XMM1, XMM2) -); -testing_simd_load!( - test_vaddpd_load, - [IMM128_C, 0u128], - instruction!( - VEX_Vaddpd_xmm_xmm_xmmm128, - XMM0, - XMM1, - MemoryOperand::with_base(RAX) - ) -); + assert!(differences.is_empty(), "{}", dump(&differences)); +} -testing!( - test_psubb, - simd(), - instruction!(Psubb_xmm_xmmm128, XMM0, XMM1) -); -testing_simd_load!( - test_psubb_load, - [IMM128_B, 0u128], - instruction!(Psubb_xmm_xmmm128, XMM0, MemoryOperand::with_base(RAX)) -); -testing!( - test_psubw, - simd(), - instruction!(Psubw_xmm_xmmm128, XMM0, XMM1) -); -testing_simd_load!( - test_psubw_load, - [IMM128_B, 0u128], - instruction!(Psubw_xmm_xmmm128, XMM0, MemoryOperand::with_base(RAX)) -); -testing!( - test_psubd, - simd(), - instruction!(Psubd_xmm_xmmm128, XMM0, XMM1) -); -testing_simd_load!( - test_psubd_load, - [IMM128_B, 0u128], - instruction!(Psubd_xmm_xmmm128, XMM0, MemoryOperand::with_base(RAX)) -); -testing!( - test_psubq, - simd(), - instruction!(Psubq_xmm_xmmm128, XMM0, XMM1) -); -testing_simd_load!( - test_psubq_load, - [IMM128_B, 0u128], - instruction!(Psubq_xmm_xmmm128, XMM0, MemoryOperand::with_base(RAX)) -); -testing!( - test_subps, - simd(), - instruction!(Subps_xmm_xmmm128, XMM0, XMM1) -); -testing_simd_load!( - test_subps_load, - [IMM128_B, 0u128], - instruction!(Subps_xmm_xmmm128, XMM0, MemoryOperand::with_base(RAX)) -); -testing!( - test_subpd, - simd(), - instruction!(Subpd_xmm_xmmm128, XMM0, XMM1) -); -testing_simd_load!( - test_subpd_load, - [IMM128_B, 0u128], - instruction!(Subpd_xmm_xmmm128, XMM0, MemoryOperand::with_base(RAX)) -); -testing!( - test_vsubps, - simd(), - instruction!(VEX_Vsubps_xmm_xmm_xmmm128, XMM0, XMM1, XMM2) -); -testing_simd_load!( - test_vsubps_load, - [IMM128_C, 0u128], - instruction!( - VEX_Vsubps_xmm_xmm_xmmm128, - XMM0, - XMM1, - MemoryOperand::with_base(RAX) - ) -); -testing!( - test_vsubpd, - simd(), - instruction!(VEX_Vsubpd_xmm_xmm_xmmm128, XMM0, XMM1, XMM2) -); -testing_simd_load!( - test_vsubpd_load, - [IMM128_C, 0u128], - instruction!( - VEX_Vsubpd_xmm_xmm_xmmm128, - XMM0, - XMM1, - MemoryOperand::with_base(RAX) - ) -); +fn dump(differences: &[Difference]) -> String { + let mut lines = Vec::new(); -testing!( - test_pmullw, - simd(), - instruction!(Pmullw_xmm_xmmm128, XMM0, XMM1) -); -testing_simd_load!( - test_pmullw_load, - [IMM128_B, 0u128], - instruction!(Pmullw_xmm_xmmm128, XMM0, MemoryOperand::with_base(RAX)) -); -testing!( - test_pmulld, - simd(), - instruction!(Pmulld_xmm_xmmm128, XMM0, XMM1) -); -testing_simd_load!( - test_pmulld_load, - [IMM128_B, 0u128], - instruction!(Pmulld_xmm_xmmm128, XMM0, MemoryOperand::with_base(RAX)) -); -testing!( - test_mulps, - simd(), - instruction!(Mulps_xmm_xmmm128, XMM0, XMM1) -); -testing_simd_load!( - test_mulps_load, - [IMM128_B, 0u128], - instruction!(Mulps_xmm_xmmm128, XMM0, MemoryOperand::with_base(RAX)) -); -testing!( - test_mulpd, - simd(), - instruction!(Mulpd_xmm_xmmm128, XMM0, XMM1) -); -testing_simd_load!( - test_mulpd_load, - [IMM128_B, 0u128], - instruction!(Mulpd_xmm_xmmm128, XMM0, MemoryOperand::with_base(RAX)) -); -testing!( - test_vpmullw, - simd(), - instruction!(VEX_Vpmullw_xmm_xmm_xmmm128, XMM0, XMM1, XMM2) -); -testing_simd_load!( - test_vpmullw_load, - [IMM128_C, 0u128], - instruction!( - VEX_Vpmullw_xmm_xmm_xmmm128, - XMM0, - XMM1, - MemoryOperand::with_base(RAX) - ) -); -testing!( - test_vpmulhw, - simd(), - instruction!(VEX_Vpmulhw_xmm_xmm_xmmm128, XMM0, XMM1, XMM2) -); -testing_simd_load!( - test_vpmulhw_load, - [IMM128_C, 0u128], - instruction!( - VEX_Vpmulhw_xmm_xmm_xmmm128, - XMM0, - XMM1, - MemoryOperand::with_base(RAX) - ) -); -testing!( - test_vpmulld, - simd(), - instruction!(VEX_Vpmulld_xmm_xmm_xmmm128, XMM0, XMM1, XMM2) -); -testing_simd_load!( - test_vpmulld_load, - [IMM128_C, 0u128], - instruction!( - VEX_Vpmulld_xmm_xmm_xmmm128, - XMM0, - XMM1, - MemoryOperand::with_base(RAX) - ) -); -testing!( - test_vmulps, - simd(), - instruction!(VEX_Vmulps_xmm_xmm_xmmm128, XMM0, XMM1, XMM2) -); -testing_simd_load!( - test_vmulps_load, - [IMM128_C, 0u128], - instruction!( - VEX_Vmulps_xmm_xmm_xmmm128, - XMM0, - XMM1, - MemoryOperand::with_base(RAX) - ) -); -testing!( - test_vmulpd, - simd(), - instruction!(VEX_Vmulpd_xmm_xmm_xmmm128, XMM0, XMM1, XMM2) -); -testing_simd_load!( - test_vmulpd_load, - [IMM128_C, 0u128], - instruction!( - VEX_Vmulpd_xmm_xmm_xmmm128, - XMM0, - XMM1, - MemoryOperand::with_base(RAX) - ) -); + for difference in differences { + match difference { + Difference::Register(register, native, emulated) => { + lines.push(format!( + "{register:?}: native={native:016X} virtual={emulated:016X}" + )); + } + Difference::Vector(register, native, emulated) => { + lines.push(format!( + "{register:?}: native={native:02X?} virtual={emulated:02X?}" + )); + } + } + } -testing!( - test_divps, - simd(), - instruction!(Divps_xmm_xmmm128, XMM0, XMM1) -); -testing_simd_load!( - test_divps_load, - [IMM128_B, 0u128], - instruction!(Divps_xmm_xmmm128, XMM0, MemoryOperand::with_base(RAX)) -); -testing!( - test_divpd, - simd(), - instruction!(Divpd_xmm_xmmm128, XMM0, XMM1) -); -testing_simd_load!( - test_divpd_load, - [IMM128_B, 0u128], - instruction!(Divpd_xmm_xmmm128, XMM0, MemoryOperand::with_base(RAX)) -); -testing!( - test_vdivps, - simd(), - instruction!(VEX_Vdivps_xmm_xmm_xmmm128, XMM0, XMM1, XMM2) -); -testing_simd_load!( - test_vdivps_load, - [IMM128_C, 0u128], - instruction!( - VEX_Vdivps_xmm_xmm_xmmm128, - XMM0, - XMM1, - MemoryOperand::with_base(RAX) - ) -); -testing!( - test_vdivpd, - simd(), - instruction!(VEX_Vdivpd_xmm_xmm_xmmm128, XMM0, XMM1, XMM2) -); -testing_simd_load!( - test_vdivpd_load, - [IMM128_C, 0u128], - instruction!( - VEX_Vdivpd_xmm_xmm_xmmm128, - XMM0, - XMM1, - MemoryOperand::with_base(RAX) - ) -); + lines.join("\n") +} diff --git a/tests/src/instrumentation.rs b/tests/src/instrumentation.rs index cc4a3b3f..16e55f22 100644 --- a/tests/src/instrumentation.rs +++ b/tests/src/instrumentation.rs @@ -20,34 +20,44 @@ pub unsafe extern "system" fn virtual_handler(_info: *mut EXCEPTION_POINTERS) -> .contains(&GetCurrentThreadId()) { TerminateThread(GetCurrentThread(), 0).unwrap(); + return EXCEPTION_CONTINUE_EXECUTION; } EXCEPTION_CONTINUE_SEARCH } pub unsafe extern "system" fn native_handler(info: *mut EXCEPTION_POINTERS) -> i32 { - let entry = NATIVE_REGISTRY - .lock() - .unwrap() - .get(&GetCurrentThreadId()) - .copied(); + let terminate = { + let mut registry = NATIVE_REGISTRY.lock().unwrap(); - if let Some((context, limit)) = entry { - if (*(*info).ExceptionRecord).ExceptionCode == EXCEPTION_SINGLE_STEP { - let rip = (*(*info).ContextRecord).Rip as usize; + if let Some((context, limit, exception)) = registry.get_mut(&GetCurrentThreadId()) { + if (*(*info).ExceptionRecord).ExceptionCode == EXCEPTION_SINGLE_STEP { + let rip = (*(*info).ContextRecord).Rip as usize; - if rip < limit { - (*(*info).ContextRecord).EFlags |= Flag::Trap.bit32(); - return EXCEPTION_CONTINUE_EXECUTION; - } + if rip < *limit { + (*(*info).ContextRecord).EFlags |= Flag::Trap.bit32(); + return EXCEPTION_CONTINUE_EXECUTION; + } + + ptr::copy_nonoverlapping((*info).ContextRecord, *context as *mut CONTEXT, 1); - ptr::copy_nonoverlapping((*info).ContextRecord, context as *mut CONTEXT, 1); + true + } else { + *exception = Some((*(*info).ExceptionRecord).ExceptionCode.0 as u32); + + true + } + } else { + false } + }; + if terminate { TerminateThread(GetCurrentThread(), 0).unwrap(); return EXCEPTION_CONTINUE_EXECUTION; } + EXCEPTION_CONTINUE_SEARCH } diff --git a/tests/src/lib.rs b/tests/src/lib.rs index 7b299d64..6299b296 100644 --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -34,8 +34,8 @@ use windows::Win32::{ Foundation::CloseHandle, System::{ Diagnostics::Debug::{ - AddVectoredExceptionHandler, GetThreadContext, SetThreadContext, SetXStateFeaturesMask, - CONTEXT, CONTEXT_ALL_AMD64, CONTEXT_XSTATE_AMD64, + AddVectoredExceptionHandler, GetThreadContext, RaiseException, SetThreadContext, + SetXStateFeaturesMask, CONTEXT, CONTEXT_ALL_AMD64, CONTEXT_XSTATE_AMD64, }, Memory::{ VirtualAlloc, VirtualFree, MEM_COMMIT, MEM_RELEASE, MEM_RESERVE, @@ -59,7 +59,7 @@ static TLS_KEY: OnceLock = OnceLock::new(); static TLS_DEBUG: OnceLock = OnceLock::new(); static FLS_CLEANUP: OnceLock = OnceLock::new(); -static NATIVE_REGISTRY: LazyLock>> = +static NATIVE_REGISTRY: LazyLock)>>> = LazyLock::new(|| Mutex::new(HashMap::new())); static NATIVE_HANDLER: OnceLock<()> = OnceLock::new(); @@ -70,7 +70,7 @@ static VIRTUAL_HANDLER: OnceLock<()> = OnceLock::new(); const XSTATE_AVX: u32 = 2; const XSTATE_MASK_AVX: u64 = 4; -#[derive(Clone, PartialEq, Eq, Default)] +#[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct State { pub registers: HashMap, pub vectors: HashMap, @@ -82,8 +82,11 @@ pub enum Difference { } impl State { - pub fn with>(mut self, register: VMReg, value: T) -> Self { - self.registers.insert(register, value.into()); + pub fn with>(mut self, register: VMReg, value: T) -> Self + where + T::Error: std::fmt::Debug, + { + self.registers.insert(register, value.try_into().unwrap()); self } @@ -421,7 +424,7 @@ impl Executor { NATIVE_REGISTRY .lock() .unwrap() - .insert(thread_id, (context as *mut CONTEXT as usize, limit)); + .insert(thread_id, (context as *mut CONTEXT as usize, limit, None)); unsafe { SetThreadContext(thread, context).unwrap(); @@ -430,7 +433,17 @@ impl Executor { CloseHandle(thread).unwrap(); } - NATIVE_REGISTRY.lock().unwrap().remove(&thread_id); + let exception = NATIVE_REGISTRY + .lock() + .unwrap() + .remove(&thread_id) + .and_then(|(_, _, exception)| exception); + + if let Some(exception) = exception { + unsafe { + RaiseException(exception, 0, None); + } + } context.EFlags &= !(Flag::Interrupt.bit32() | Flag::Reserved1.bit32()); @@ -469,8 +482,9 @@ pub fn decrypt_payload(block: &mut Vec) { Executor::TEST_KEY_MUL, Executor::TEST_KEY_ADD, 0, - ) + ); } + pub fn decrypt_block(block: &mut Vec) { crypt::decrypt_block( block, @@ -478,22 +492,5 @@ pub fn decrypt_block(block: &mut Vec) { Executor::TEST_KEY_MUL, Executor::TEST_KEY_ADD, 0, - ) -} - -macro_rules! instruction { - (branch $code:ident, $target:expr) => { - iced_x86::Instruction::with_branch(iced_x86::Code::$code, $target).unwrap() - }; - ($code:ident, $a:expr) => { - iced_x86::Instruction::with1(iced_x86::Code::$code, $a).unwrap() - }; - ($code:ident, $a:expr, $b:expr) => { - iced_x86::Instruction::with2(iced_x86::Code::$code, $a, $b).unwrap() - }; - ($code:ident, $a:expr, $b:expr, $c:expr) => { - iced_x86::Instruction::with3(iced_x86::Code::$code, $a, $b, $c).unwrap() - }; + ); } - -pub(crate) use instruction; From 7b707054975163365780be3cab868fa090839db4 Mon Sep 17 00:00:00 2001 From: RadonCoding <86915746+RadonCoding@users.noreply.github.com> Date: Sun, 20 Sep 2026 18:25:42 +0300 Subject: [PATCH 10/33] chore: rewrote unit testing framework and tons of bug fixes --- tests/src/constants.rs | 25 ++++----- tests/src/instructions.rs | 106 +++++++++++++++++++++++++++++++++++++- tests/src/lib.rs | 20 ++++--- 3 files changed, 128 insertions(+), 23 deletions(-) diff --git a/tests/src/constants.rs b/tests/src/constants.rs index 35f19d9d..15259d19 100644 --- a/tests/src/constants.rs +++ b/tests/src/constants.rs @@ -99,20 +99,15 @@ pub fn simd() -> State { vector(state, VMVec::Ymm2, bytes16(IMM128_C)) } -pub const IMM8_A: u8 = 0x85; -pub const _IMM16_A: u16 = 0x8A5C; -pub const IMM32_A: u32 = 0x8A5C_E179; -pub const IMM64_A: u64 = 0x8A5C_E179_2D4B_7F67; -pub const IMM64_B: u64 = 0x5BD4_F28A_3E5C_6E78; -pub const IMM64_C: u64 = 0x4CE5_039B_4F6D_5D69; +pub const IMM8_A: u8 = 0x01; +pub const IMM16_A: u16 = 0x0123; +pub const IMM32_A: u32 = 0x0123_4567; +pub const IMM64_A: u64 = 0x0123_4567_89AB_CDEF; +pub const IMM64_B: u64 = 0x1234_5678_9ABC_DEF0; +pub const IMM64_C: u64 = 0x2345_6789_ABCD_EF01; -pub const _SIMM8_A: i32 = 0x75; -pub const _SIMM16_A: i16 = 0x6A5C; -pub const _SIMM32_A: i32 = 0x6A5C_E179; -pub const _SIMM64_A: i64 = 0x6A5C_E179_2D4B_7F67; +pub const IMM128_A: u128 = 0x0123_4567_89AB_CDEF_0123_4567_89AB_CDEF; +pub const IMM128_B: u128 = 0x1234_5678_9ABC_DEF0_1234_5678_9ABC_DEF0; +pub const IMM128_C: u128 = 0x2345_6789_ABCD_EF01_2345_6789_ABCD_EF01; -pub const IMM128_A: u128 = 0x6A5C_E179_2D4B_7F67_1032_5476_789A_BCDE; -pub const IMM128_B: u128 = 0x5BD4_F28A_3E5C_6E78_2143_6587_89AB_CDEF; -pub const IMM128_C: u128 = 0x4CE5_039B_4F6D_5D69_3254_7698_9ABC_DEF0; - -pub const FAKE_BRANCH_ADDRESS: u64 = 0x1234_ABCD; +pub const IP: u64 = 0x0000_0000_3456_789A; diff --git a/tests/src/instructions.rs b/tests/src/instructions.rs index 3ea3316e..372b6e76 100644 --- a/tests/src/instructions.rs +++ b/tests/src/instructions.rs @@ -6,7 +6,7 @@ use iced_x86::{ }; use runtime::vm::bytecode::{self, Flag, VMReg}; -use crate::constants::{gpr, simd, _IMM16_A, IMM128_A, IMM32_A, IMM64_A, IMM8_A}; +use crate::constants::{gpr, simd, IMM128_A, IMM16_A, IMM32_A, IMM64_A, IMM8_A, IP}; use crate::{decrypt_block, decrypt_payload, encrypt_block, Difference, Executor, State}; #[test] @@ -398,6 +398,87 @@ Cmp_rm16_imm16 Cmp_rm32_imm32 Cmp_rm16_imm8 Cmp_rm32_imm8 +Cmova_r16_rm16 +Cmova_r32_rm32 +Cmova_r64_rm64 +Cmovae_r16_rm16 +Cmovae_r32_rm32 +Cmovae_r64_rm64 +Cmovb_r16_rm16 +Cmovb_r32_rm32 +Cmovb_r64_rm64 +Cmovbe_r16_rm16 +Cmovbe_r32_rm32 +Cmovbe_r64_rm64 +Cmove_r16_rm16 +Cmove_r32_rm32 +Cmove_r64_rm64 +Cmovg_r16_rm16 +Cmovg_r32_rm32 +Cmovg_r64_rm64 +Cmovge_r16_rm16 +Cmovge_r32_rm32 +Cmovge_r64_rm64 +Cmovl_r16_rm16 +Cmovl_r32_rm32 +Cmovl_r64_rm64 +Cmovle_r16_rm16 +Cmovle_r32_rm32 +Cmovle_r64_rm64 +Cmovne_r16_rm16 +Cmovne_r32_rm32 +Cmovne_r64_rm64 +Cmovno_r16_rm16 +Cmovno_r32_rm32 +Cmovno_r64_rm64 +Cmovnp_r16_rm16 +Cmovnp_r32_rm32 +Cmovnp_r64_rm64 +Cmovns_r16_rm16 +Cmovns_r32_rm32 +Cmovns_r64_rm64 +Cmovo_r16_rm16 +Cmovo_r32_rm32 +Cmovo_r64_rm64 +Cmovp_r16_rm16 +Cmovp_r32_rm32 +Cmovp_r64_rm64 +Cmovs_r16_rm16 +Cmovs_r32_rm32 +Cmovs_r64_rm64 +Jo_rel8_64 +Jo_rel32_64 +Jno_rel8_64 +Jno_rel32_64 +Jb_rel8_64 +Jb_rel32_64 +Jae_rel8_64 +Jae_rel32_64 +Je_rel8_64 +Je_rel32_64 +Jne_rel8_64 +Jne_rel32_64 +Jbe_rel8_64 +Jbe_rel32_64 +Ja_rel8_64 +Ja_rel32_64 +Js_rel8_64 +Js_rel32_64 +Jns_rel8_64 +Jns_rel32_64 +Jp_rel8_64 +Jp_rel32_64 +Jnp_rel8_64 +Jnp_rel32_64 +Jl_rel8_64 +Jl_rel32_64 +Jge_rel8_64 +Jge_rel32_64 +Jle_rel8_64 +Jle_rel32_64 +Jg_rel8_64 +Jg_rel32_64 +Call_rel32_64 ); fn test(code: Code) { @@ -474,6 +555,25 @@ fn build(code: Code, test: Test, memory_base: Register) -> Instruction { } fn operand(instruction: &mut Instruction, operand: u32, kind: OpCodeOperandKind) { + match kind { + OpCodeOperandKind::br16_1 | OpCodeOperandKind::br16_2 => { + instruction.set_op_kind(operand, OpKind::NearBranch16); + instruction.set_near_branch16(IP as u16); + return; + } + OpCodeOperandKind::br32_1 | OpCodeOperandKind::br32_4 => { + instruction.set_op_kind(operand, OpKind::NearBranch32); + instruction.set_near_branch32(IP as u32); + return; + } + OpCodeOperandKind::br64_1 | OpCodeOperandKind::br64_4 => { + instruction.set_op_kind(operand, OpKind::NearBranch64); + instruction.set_near_branch64(IP); + return; + } + _ => {} + } + let op = match kind { OpCodeOperandKind::r8_or_mem | OpCodeOperandKind::r16_or_mem @@ -571,7 +671,7 @@ fn operand(instruction: &mut Instruction, operand: u32, kind: OpCodeOperandKind) | OpCodeOperandKind::imm8sex16 | OpCodeOperandKind::imm8sex32 | OpCodeOperandKind::imm8sex64 => IMM8_A as u64, - OpCodeOperandKind::imm16 => _IMM16_A as u64, + OpCodeOperandKind::imm16 => IMM16_A as u64, OpCodeOperandKind::imm32 | OpCodeOperandKind::imm32sex64 => IMM32_A as u64, OpCodeOperandKind::imm64 => IMM64_A, _ => unreachable!(), @@ -889,6 +989,8 @@ fn compare(state: State, instruction: Instruction) { } fn compare_memory(state: State, instruction: Instruction, memory: &mut [u8]) { + println!("{instruction}"); + let baseline = memory.to_vec(); let mut executor = Executor::new(); diff --git a/tests/src/lib.rs b/tests/src/lib.rs index 6299b296..cbf9b769 100644 --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -11,7 +11,7 @@ use std::{ }; use crate::{ - constants::{FAKE_BRANCH_ADDRESS, REGISTERS, VECTORS}, + constants::{IP, REGISTERS, VECTORS}, instrumentation::{ initialize_context, native_handler, read_register, read_vectors, virtual_handler, write_register, write_vectors, @@ -52,7 +52,7 @@ mod constants; mod instructions; mod instrumentation; -static FAKE_BRANCH_MAPPED: OnceLock<()> = OnceLock::new(); +static IP_MAPPED: OnceLock<()> = OnceLock::new(); static TLS_REGISTERS: OnceLock = OnceLock::new(); static TLS_KEY: OnceLock = OnceLock::new(); @@ -127,12 +127,20 @@ impl Executor { pub const SIZE: usize = 0x100000; pub fn new() -> Self { - FAKE_BRANCH_MAPPED.get_or_init(|| unsafe { - let _ = VirtualAlloc( - Some(FAKE_BRANCH_ADDRESS as *const c_void), + IP_MAPPED.get_or_init(|| unsafe { + let memory = VirtualAlloc( + Some(IP_BASE as *const c_void), Self::SIZE, MEM_COMMIT | MEM_RESERVE, - PAGE_READWRITE, + PAGE_EXECUTE_READWRITE, + ); + + assert!(!memory.is_null()); + + let target = IP as *mut u8; + assert!( + target as usize >= memory as usize + && target as usize < memory as usize + Self::SIZE ); }); From 5ccd3235986d92fbde3636e761118f296a900d67 Mon Sep 17 00:00:00 2001 From: unknown <86915746+RadonCoding@users.noreply.github.com> Date: Sun, 20 Sep 2026 22:38:20 +0300 Subject: [PATCH 11/33] chore: division via semantics --- runtime/src/vm/encoders/store_register.rs | 1 - runtime/src/vm/handlers/mul.rs | 22 +++++- runtime/src/vm/handlers/rol.rs | 13 +++- runtime/src/vm/handlers/ror.rs | 19 ++++- runtime/src/vm/handlers/shl.rs | 18 ++++- runtime/src/vm/handlers/shr.rs | 6 +- runtime/src/vm/lifters/cmov.rs | 14 +++- runtime/src/vm/transform/peephole.rs | 10 ++- tests/src/constants.rs | 2 - tests/src/instructions.rs | 66 +--------------- tests/src/instrumentation.rs | 4 +- tests/src/lib.rs | 91 +++++++++-------------- 12 files changed, 126 insertions(+), 140 deletions(-) diff --git a/runtime/src/vm/encoders/store_register.rs b/runtime/src/vm/encoders/store_register.rs index 5a28a011..8e60b989 100644 --- a/runtime/src/vm/encoders/store_register.rs +++ b/runtime/src/vm/encoders/store_register.rs @@ -37,5 +37,4 @@ impl Encode for StoreRegister { fn produces(&self) -> i32 { 0 } - } diff --git a/runtime/src/vm/handlers/mul.rs b/runtime/src/vm/handlers/mul.rs index 3294ba40..2511f75c 100644 --- a/runtime/src/vm/handlers/mul.rs +++ b/runtime/src/vm/handlers/mul.rs @@ -18,14 +18,32 @@ pub fn build(rt: &mut Runtime) { ( Flag::Carry, Condition::Compare(Compare::GreaterThan( - Expression::Operand(Operand::Output(1)), + Expression::Sub( + Box::new(Expression::Operand(Operand::Output(1))), + Box::new(Expression::Sub( + Box::new(Expression::Constant(0)), + Box::new(Expression::BitShr( + Box::new(Expression::Operand(Operand::Output(0))), + Box::new(Expression::SignBit), + )), + )), + ), Expression::Constant(0), )), ), ( Flag::Overflow, Condition::Compare(Compare::GreaterThan( - Expression::Operand(Operand::Output(1)), + Expression::Sub( + Box::new(Expression::Operand(Operand::Output(1))), + Box::new(Expression::Sub( + Box::new(Expression::Constant(0)), + Box::new(Expression::BitShr( + Box::new(Expression::Operand(Operand::Output(0))), + Box::new(Expression::SignBit), + )), + )), + ), Expression::Constant(0), )), ), diff --git a/runtime/src/vm/handlers/rol.rs b/runtime/src/vm/handlers/rol.rs index b2305353..4c4f2dbd 100644 --- a/runtime/src/vm/handlers/rol.rs +++ b/runtime/src/vm/handlers/rol.rs @@ -24,8 +24,17 @@ pub fn build(rt: &mut Runtime) { ), ( Flag::Overflow, - Condition::Compare(Compare::Equal( - Expression::Constant(1), + Condition::Compare(Compare::GreaterThan( + Expression::BitXor( + Box::new(Expression::BitShr( + Box::new(Expression::Operand(Operand::Output(0))), + Box::new(Expression::SignBit), + )), + Box::new(Expression::BitAnd( + Box::new(Expression::Operand(Operand::Output(0))), + Box::new(Expression::Constant(1)), + )), + ), Expression::Constant(0), )), ), diff --git a/runtime/src/vm/handlers/ror.rs b/runtime/src/vm/handlers/ror.rs index 29946359..15d75281 100644 --- a/runtime/src/vm/handlers/ror.rs +++ b/runtime/src/vm/handlers/ror.rs @@ -24,8 +24,23 @@ pub fn build(rt: &mut Runtime) { ), ( Flag::Overflow, - Condition::Compare(Compare::Equal( - Expression::Constant(1), + Condition::Compare(Compare::GreaterThan( + Expression::BitXor( + Box::new(Expression::BitShr( + Box::new(Expression::Operand(Operand::Output(0))), + Box::new(Expression::SignBit), + )), + Box::new(Expression::BitAnd( + Box::new(Expression::BitShr( + Box::new(Expression::Operand(Operand::Output(0))), + Box::new(Expression::Sub( + Box::new(Expression::SignBit), + Box::new(Expression::Constant(1)), + )), + )), + Box::new(Expression::Constant(1)), + )), + ), Expression::Constant(0), )), ), diff --git a/runtime/src/vm/handlers/shl.rs b/runtime/src/vm/handlers/shl.rs index bb84a6f8..0ed47871 100644 --- a/runtime/src/vm/handlers/shl.rs +++ b/runtime/src/vm/handlers/shl.rs @@ -23,15 +23,27 @@ pub fn build(rt: &mut Runtime) { Box::new(Expression::BitSize), Box::new(Expression::BitAnd( Box::new(Expression::Operand(Operand::Input(1))), - Box::new(Expression::Constant(0x1f)), + Box::new(Expression::BitOr( + Box::new(Expression::SignBit), + Box::new(Expression::Constant(0x1f)), + )), )), ), )), ), ( Flag::Overflow, - Condition::Compare(Compare::Equal( - Expression::Constant(1), + Condition::Compare(Compare::GreaterThan( + Expression::BitXor( + Box::new(Expression::BitShr( + Box::new(Expression::Operand(Operand::Input(0))), + Box::new(Expression::SignBit), + )), + Box::new(Expression::BitShr( + Box::new(Expression::Operand(Operand::Output(0))), + Box::new(Expression::SignBit), + )), + ), Expression::Constant(0), )), ), diff --git a/runtime/src/vm/handlers/shr.rs b/runtime/src/vm/handlers/shr.rs index baa3f1c7..bc425ce4 100644 --- a/runtime/src/vm/handlers/shr.rs +++ b/runtime/src/vm/handlers/shr.rs @@ -33,9 +33,9 @@ pub fn build(rt: &mut Runtime) { ), ( Flag::Overflow, - Condition::Compare(Compare::Equal( - Expression::Constant(1), - Expression::Constant(0), + Condition::Compare(Compare::BitSet( + Expression::Operand(Operand::Input(0)), + Expression::SignBit, )), ), ( diff --git a/runtime/src/vm/lifters/cmov.rs b/runtime/src/vm/lifters/cmov.rs index 738a7337..167f00d3 100644 --- a/runtime/src/vm/lifters/cmov.rs +++ b/runtime/src/vm/lifters/cmov.rs @@ -80,5 +80,17 @@ pub fn encode(instruction: &Instruction) -> Option>> { let body = transfer::encode(instruction)?; - Some(vec![Box::new(Block::skip(logic, conditions, body))]) + let mut encoded = Vec::new(); + + if instruction.op0_register().is_gpr32() { + let mut zero_extend = Instruction::default(); + zero_extend.set_code(Code::Mov_rm32_r32); + zero_extend.set_op_register(0, instruction.op0_register()); + zero_extend.set_op_register(1, instruction.op0_register()); + encoded.extend(transfer::encode(&zero_extend)?); + } + + encoded.push(Box::new(Block::skip(logic, conditions, body))); + + Some(encoded) } diff --git a/runtime/src/vm/transform/peephole.rs b/runtime/src/vm/transform/peephole.rs index db08338d..56482595 100644 --- a/runtime/src/vm/transform/peephole.rs +++ b/runtime/src/vm/transform/peephole.rs @@ -36,7 +36,10 @@ fn optimize(operations: &mut Vec>) { .as_any() .downcast_ref::(), ) { - if load.source == store.destination && load.width == store.width { + if load.source == store.destination + && load.width == store.width + && store.width.size() != 4 + { operations.drain(index..index + 2); continue; } @@ -48,7 +51,10 @@ fn optimize(operations: &mut Vec>) { .as_any() .downcast_ref::(), ) { - if store.destination == load.source && store.width == load.width { + if store.destination == load.source + && store.width == load.width + && store.width.size() != 4 + { if overwritten(&operations[index + 2..], store.destination) { operations.drain(index..index + 2); continue; diff --git a/tests/src/constants.rs b/tests/src/constants.rs index 15259d19..0962a120 100644 --- a/tests/src/constants.rs +++ b/tests/src/constants.rs @@ -109,5 +109,3 @@ pub const IMM64_C: u64 = 0x2345_6789_ABCD_EF01; pub const IMM128_A: u128 = 0x0123_4567_89AB_CDEF_0123_4567_89AB_CDEF; pub const IMM128_B: u128 = 0x1234_5678_9ABC_DEF0_1234_5678_9ABC_DEF0; pub const IMM128_C: u128 = 0x2345_6789_ABCD_EF01_2345_6789_ABCD_EF01; - -pub const IP: u64 = 0x0000_0000_3456_789A; diff --git a/tests/src/instructions.rs b/tests/src/instructions.rs index 372b6e76..a635360d 100644 --- a/tests/src/instructions.rs +++ b/tests/src/instructions.rs @@ -6,7 +6,7 @@ use iced_x86::{ }; use runtime::vm::bytecode::{self, Flag, VMReg}; -use crate::constants::{gpr, simd, IMM128_A, IMM16_A, IMM32_A, IMM64_A, IMM8_A, IP}; +use crate::constants::{gpr, simd, IMM128_A, IMM16_A, IMM32_A, IMM64_A, IMM8_A}; use crate::{decrypt_block, decrypt_payload, encrypt_block, Difference, Executor, State}; #[test] @@ -446,39 +446,6 @@ Cmovp_r64_rm64 Cmovs_r16_rm16 Cmovs_r32_rm32 Cmovs_r64_rm64 -Jo_rel8_64 -Jo_rel32_64 -Jno_rel8_64 -Jno_rel32_64 -Jb_rel8_64 -Jb_rel32_64 -Jae_rel8_64 -Jae_rel32_64 -Je_rel8_64 -Je_rel32_64 -Jne_rel8_64 -Jne_rel32_64 -Jbe_rel8_64 -Jbe_rel32_64 -Ja_rel8_64 -Ja_rel32_64 -Js_rel8_64 -Js_rel32_64 -Jns_rel8_64 -Jns_rel32_64 -Jp_rel8_64 -Jp_rel32_64 -Jnp_rel8_64 -Jnp_rel32_64 -Jl_rel8_64 -Jl_rel32_64 -Jge_rel8_64 -Jge_rel32_64 -Jle_rel8_64 -Jle_rel32_64 -Jg_rel8_64 -Jg_rel32_64 -Call_rel32_64 ); fn test(code: Code) { @@ -555,25 +522,6 @@ fn build(code: Code, test: Test, memory_base: Register) -> Instruction { } fn operand(instruction: &mut Instruction, operand: u32, kind: OpCodeOperandKind) { - match kind { - OpCodeOperandKind::br16_1 | OpCodeOperandKind::br16_2 => { - instruction.set_op_kind(operand, OpKind::NearBranch16); - instruction.set_near_branch16(IP as u16); - return; - } - OpCodeOperandKind::br32_1 | OpCodeOperandKind::br32_4 => { - instruction.set_op_kind(operand, OpKind::NearBranch32); - instruction.set_near_branch32(IP as u32); - return; - } - OpCodeOperandKind::br64_1 | OpCodeOperandKind::br64_4 => { - instruction.set_op_kind(operand, OpKind::NearBranch64); - instruction.set_near_branch64(IP); - return; - } - _ => {} - } - let op = match kind { OpCodeOperandKind::r8_or_mem | OpCodeOperandKind::r16_or_mem @@ -989,8 +937,6 @@ fn compare(state: State, instruction: Instruction) { } fn compare_memory(state: State, instruction: Instruction, memory: &mut [u8]) { - println!("{instruction}"); - let baseline = memory.to_vec(); let mut executor = Executor::new(); @@ -1001,16 +947,6 @@ fn compare_memory(state: State, instruction: Instruction, memory: &mut [u8]) { let mut executor = Executor::new(); let lifted = bytecode::lift(&[instruction]) .unwrap_or_else(|| panic!("{instruction} is not implemented")); - - println!( - "{}", - lifted - .iter() - .map(|op| format!("{op:?}")) - .collect::>() - .join("\n") - ); - let transformed = bytecode::transform(&mut executor.rt.mapper, lifted, |_| 0); let mut bytes = bytecode::assemble(&mut executor.rt.mapper, &transformed); diff --git a/tests/src/instrumentation.rs b/tests/src/instrumentation.rs index 16e55f22..f90e12db 100644 --- a/tests/src/instrumentation.rs +++ b/tests/src/instrumentation.rs @@ -30,11 +30,11 @@ pub unsafe extern "system" fn native_handler(info: *mut EXCEPTION_POINTERS) -> i let terminate = { let mut registry = NATIVE_REGISTRY.lock().unwrap(); - if let Some((context, limit, exception)) = registry.get_mut(&GetCurrentThreadId()) { + if let Some((context, base, limit, exception)) = registry.get_mut(&GetCurrentThreadId()) { if (*(*info).ExceptionRecord).ExceptionCode == EXCEPTION_SINGLE_STEP { let rip = (*(*info).ContextRecord).Rip as usize; - if rip < *limit { + if rip >= *base && rip < *limit { (*(*info).ContextRecord).EFlags |= Flag::Trap.bit32(); return EXCEPTION_CONTINUE_EXECUTION; } diff --git a/tests/src/lib.rs b/tests/src/lib.rs index cbf9b769..d8a71cec 100644 --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -11,7 +11,7 @@ use std::{ }; use crate::{ - constants::{IP, REGISTERS, VECTORS}, + constants::{REGISTERS, VECTORS}, instrumentation::{ initialize_context, native_handler, read_register, read_vectors, virtual_handler, write_register, write_vectors, @@ -38,8 +38,7 @@ use windows::Win32::{ SetXStateFeaturesMask, CONTEXT, CONTEXT_ALL_AMD64, CONTEXT_XSTATE_AMD64, }, Memory::{ - VirtualAlloc, VirtualFree, MEM_COMMIT, MEM_RELEASE, MEM_RESERVE, - PAGE_EXECUTE_READWRITE, PAGE_READWRITE, + VirtualAlloc, VirtualFree, MEM_COMMIT, MEM_RELEASE, MEM_RESERVE, PAGE_EXECUTE_READWRITE, }, Threading::{ CreateThread, FlsAlloc, ResumeThread, SuspendThread, TlsAlloc, WaitForSingleObject, @@ -52,14 +51,12 @@ mod constants; mod instructions; mod instrumentation; -static IP_MAPPED: OnceLock<()> = OnceLock::new(); - static TLS_REGISTERS: OnceLock = OnceLock::new(); static TLS_KEY: OnceLock = OnceLock::new(); static TLS_DEBUG: OnceLock = OnceLock::new(); static FLS_CLEANUP: OnceLock = OnceLock::new(); -static NATIVE_REGISTRY: LazyLock)>>> = +static NATIVE_REGISTRY: LazyLock)>>> = LazyLock::new(|| Mutex::new(HashMap::new())); static NATIVE_HANDLER: OnceLock<()> = OnceLock::new(); @@ -116,39 +113,22 @@ impl State { pub struct Executor { pub rt: Runtime, - pub mem: *mut c_void, + pub memory: *mut c_void, } impl Executor { - pub const TEST_KEY_SEED: u64 = 0x1234567890ABCDEF; - pub const TEST_KEY_MUL: u64 = 0x1234567890ABCDEF; - pub const TEST_KEY_ADD: u64 = 0x1234567890ABCDEF; + pub const TEST_KEY_INITIALIZER: u64 = 0x1234567890ABCDEF; + pub const TEST_KEY_MULTIPLIER: u64 = 0x1234567890ABCDEF; + pub const TEST_KEY_ADDEND: u64 = 0x1234567890ABCDEF; pub const SIZE: usize = 0x100000; pub fn new() -> Self { - IP_MAPPED.get_or_init(|| unsafe { - let memory = VirtualAlloc( - Some(IP_BASE as *const c_void), - Self::SIZE, - MEM_COMMIT | MEM_RESERVE, - PAGE_EXECUTE_READWRITE, - ); - - assert!(!memory.is_null()); - - let target = IP as *mut u8; - assert!( - target as usize >= memory as usize - && target as usize < memory as usize + Self::SIZE - ); - }); - let mut rt = Runtime::new(64); - rt.define_data_qword(DataDef::VmKeyInitializer, Self::TEST_KEY_SEED); - rt.define_data_qword(DataDef::VmKeyMultiplier, Self::TEST_KEY_MUL); - rt.define_data_qword(DataDef::VmKeyAddend, Self::TEST_KEY_ADD); + rt.define_data_qword(DataDef::VmKeyInitializer, Self::TEST_KEY_INITIALIZER); + rt.define_data_qword(DataDef::VmKeyMultiplier, Self::TEST_KEY_MULTIPLIER); + rt.define_data_qword(DataDef::VmKeyAddend, Self::TEST_KEY_ADDEND); rt.define_bool(BoolDef::HasVeh, true); @@ -169,7 +149,7 @@ impl Executor { *FLS_CLEANUP.get_or_init(|| unsafe { FlsAlloc(None) }), ); - let mem = unsafe { + let memory = unsafe { VirtualAlloc( None, Self::SIZE, @@ -178,7 +158,7 @@ impl Executor { ) }; - Self { rt, mem } + Self { rt, memory } } pub fn run_virtual(&mut self, state: State, bytes: &[u8]) -> State { @@ -304,14 +284,14 @@ impl Executor { self.rt.define_data_bytes(DataDef::VmCode, bytes); - let ip = self.mem as u64; + let ip = self.memory as u64; let code = self.rt.assemble(ip); assert!(code.len() <= Self::SIZE); unsafe { - ptr::copy_nonoverlapping(code.as_ptr(), self.mem as *mut u8, code.len()); + ptr::copy_nonoverlapping(code.as_ptr(), self.memory as *mut u8, code.len()); } VIRTUAL_HANDLER.get_or_init(|| unsafe { @@ -327,8 +307,8 @@ impl Executor { Some(mem::transmute::< *const (), unsafe extern "system" fn(*mut c_void) -> u32, - >(self.mem as *const ())), - Some(self.mem as *mut c_void), + >(self.memory as *const ())), + Some(self.memory as *mut c_void), THREAD_CREATE_SUSPENDED, Some(&mut thread_id), ) @@ -358,7 +338,7 @@ impl Executor { } pub fn run_native(&mut self, state: State, instructions: &[Instruction]) -> State { - let ip = self.mem as u64; + let ip = self.memory as u64; let result = BlockEncoder::encode( 64, @@ -370,12 +350,13 @@ impl Executor { unsafe { ptr::copy_nonoverlapping( result.code_buffer.as_ptr(), - self.mem as *mut u8, + self.memory as *mut u8, result.code_buffer.len(), ); } - let limit = self.mem as usize + result.code_buffer.len(); + let base = self.memory as usize; + let limit = base + result.code_buffer.len(); let initialized = AtomicBool::new(false); @@ -416,7 +397,7 @@ impl Executor { &mut *context }; - context.Rip = self.mem as u64; + context.Rip = self.memory as u64; for (®ister, &value) in &state.registers { write_register(context, register, value); @@ -429,10 +410,10 @@ impl Executor { AddVectoredExceptionHandler(1, Some(native_handler)); }); - NATIVE_REGISTRY - .lock() - .unwrap() - .insert(thread_id, (context as *mut CONTEXT as usize, limit, None)); + NATIVE_REGISTRY.lock().unwrap().insert( + thread_id, + (context as *mut CONTEXT as usize, base, limit, None), + ); unsafe { SetThreadContext(thread, context).unwrap(); @@ -445,7 +426,7 @@ impl Executor { .lock() .unwrap() .remove(&thread_id) - .and_then(|(_, _, exception)| exception); + .and_then(|(_, _, _, exception)| exception); if let Some(exception) = exception { unsafe { @@ -468,7 +449,7 @@ impl Executor { impl Drop for Executor { fn drop(&mut self) { unsafe { - let _ = VirtualFree(self.mem, 0, MEM_RELEASE); + let _ = VirtualFree(self.memory, 0, MEM_RELEASE); } } } @@ -476,9 +457,9 @@ impl Drop for Executor { pub fn encrypt_block(block: &mut Vec) { crypt::encrypt_block( block, - Executor::TEST_KEY_SEED, - Executor::TEST_KEY_MUL, - Executor::TEST_KEY_ADD, + Executor::TEST_KEY_INITIALIZER, + Executor::TEST_KEY_MULTIPLIER, + Executor::TEST_KEY_ADDEND, 0, ); } @@ -486,9 +467,9 @@ pub fn encrypt_block(block: &mut Vec) { pub fn decrypt_payload(block: &mut Vec) { crypt::decrypt_payload( block, - Executor::TEST_KEY_SEED, - Executor::TEST_KEY_MUL, - Executor::TEST_KEY_ADD, + Executor::TEST_KEY_INITIALIZER, + Executor::TEST_KEY_MULTIPLIER, + Executor::TEST_KEY_ADDEND, 0, ); } @@ -496,9 +477,9 @@ pub fn decrypt_payload(block: &mut Vec) { pub fn decrypt_block(block: &mut Vec) { crypt::decrypt_block( block, - Executor::TEST_KEY_SEED, - Executor::TEST_KEY_MUL, - Executor::TEST_KEY_ADD, + Executor::TEST_KEY_INITIALIZER, + Executor::TEST_KEY_MULTIPLIER, + Executor::TEST_KEY_ADDEND, 0, ); } From 3a6050de36edf003a1f4744e190a162f111f81cc Mon Sep 17 00:00:00 2001 From: unknown <86915746+RadonCoding@users.noreply.github.com> Date: Wed, 23 Sep 2026 20:56:28 +0300 Subject: [PATCH 12/33] fix: fixes to unit tests & handlers --- runtime/src/vm/handlers/bit_scan_reverse.rs | 7 +- runtime/src/vm/handlers/mul.rs | 22 +- runtime/src/vm/handlers/semantic/compiler.rs | 55 +++- runtime/src/vm/handlers/store_extend.rs | 16 +- runtime/src/vm/handlers/trailing_zeros.rs | 7 +- runtime/src/vm/lifters/arithmetic.rs | 1 + tests/src/constants.rs | 62 ++-- tests/src/instructions.rs | 291 +++++-------------- tests/src/instrumentation.rs | 31 +- tests/src/lib.rs | 73 +++-- 10 files changed, 234 insertions(+), 331 deletions(-) diff --git a/runtime/src/vm/handlers/bit_scan_reverse.rs b/runtime/src/vm/handlers/bit_scan_reverse.rs index bb636b91..cf7b001a 100644 --- a/runtime/src/vm/handlers/bit_scan_reverse.rs +++ b/runtime/src/vm/handlers/bit_scan_reverse.rs @@ -10,11 +10,14 @@ pub fn build(rt: &mut Runtime) { semantic::compiler::compile( rt, &Operation { - effects: vec![Effect::Bsr(Expression::Operand(Operand::Input(0)))], + effects: vec![ + Effect::Assign(Expression::Operand(Operand::Input(0))), + Effect::Bsr(Expression::Operand(Operand::Input(1))), + ], flags: vec![( Flag::Zero, Condition::Compare(Compare::Equal( - Expression::Operand(Operand::Input(0)), + Expression::Operand(Operand::Input(1)), Expression::Constant(0), )), )], diff --git a/runtime/src/vm/handlers/mul.rs b/runtime/src/vm/handlers/mul.rs index 2511f75c..3294ba40 100644 --- a/runtime/src/vm/handlers/mul.rs +++ b/runtime/src/vm/handlers/mul.rs @@ -18,32 +18,14 @@ pub fn build(rt: &mut Runtime) { ( Flag::Carry, Condition::Compare(Compare::GreaterThan( - Expression::Sub( - Box::new(Expression::Operand(Operand::Output(1))), - Box::new(Expression::Sub( - Box::new(Expression::Constant(0)), - Box::new(Expression::BitShr( - Box::new(Expression::Operand(Operand::Output(0))), - Box::new(Expression::SignBit), - )), - )), - ), + Expression::Operand(Operand::Output(1)), Expression::Constant(0), )), ), ( Flag::Overflow, Condition::Compare(Compare::GreaterThan( - Expression::Sub( - Box::new(Expression::Operand(Operand::Output(1))), - Box::new(Expression::Sub( - Box::new(Expression::Constant(0)), - Box::new(Expression::BitShr( - Box::new(Expression::Operand(Operand::Output(0))), - Box::new(Expression::SignBit), - )), - )), - ), + Expression::Operand(Operand::Output(1)), Expression::Constant(0), )), ), diff --git a/runtime/src/vm/handlers/semantic/compiler.rs b/runtime/src/vm/handlers/semantic/compiler.rs index dc707263..f2f49dce 100644 --- a/runtime/src/vm/handlers/semantic/compiler.rs +++ b/runtime/src/vm/handlers/semantic/compiler.rs @@ -619,27 +619,39 @@ fn compile_effect(rt: &mut Runtime, allocator: &mut Allocator, effect: &Effect, allocator.replace(dst, Value::Output(0), true); allocator.consume(src); } - Effect::Bsr(expression) => { - compile_unary(rt, allocator, expression, width, |rt, dst, src| { - rt.asm.bsr(dst, src).unwrap(); - }) - } - Effect::Tzcnt(expression) => { - compile_unary(rt, allocator, expression, width, |rt, dst, src| { - rt.asm.tzcnt(dst, src).unwrap(); - }) - } + Effect::Bsr(expression) => compile_unary( + rt, + allocator, + expression, + width, + |rt, dst, src| rt.asm.bsr(dst, src).unwrap(), + |rt, dst, src| rt.asm.bsr(dst, src).unwrap(), + |rt, dst, src| rt.asm.bsr(dst, src).unwrap(), + ), + Effect::Tzcnt(expression) => compile_unary( + rt, + allocator, + expression, + width, + |rt, dst, src| rt.asm.tzcnt(dst, src).unwrap(), + |rt, dst, src| rt.asm.tzcnt(dst, src).unwrap(), + |rt, dst, src| rt.asm.tzcnt(dst, src).unwrap(), + ), } } -fn compile_unary( +fn compile_unary( rt: &mut Runtime, allocator: &mut Allocator, expression: &Expression, width: VMWidth, - operation: F, + unsigned16: U16, + unsigned32: U32, + unsigned64: U64, ) where - F: FnOnce(&mut Runtime, AsmRegister64, AsmRegister64), + U16: FnOnce(&mut Runtime, AsmRegister16, AsmRegister16), + U32: FnOnce(&mut Runtime, AsmRegister32, AsmRegister32), + U64: FnOnce(&mut Runtime, AsmRegister64, AsmRegister64), { let value = compile_expression(rt, allocator, expression, width); let src = allocator.acquire(rt, &[], &[]); @@ -655,7 +667,22 @@ fn compile_unary( let dst = allocator.mutable(rt, Value::Output(0), &[src]); - operation(rt, dst, src); + match width.size() { + 2 => { + let dst = get_gpr16(register::sized(dst.into(), 2).unwrap()).unwrap(); + let src = get_gpr16(register::sized(src.into(), 2).unwrap()).unwrap(); + unsigned16(rt, dst, src); + } + 4 => { + let dst = get_gpr32(register::sized(dst.into(), 4).unwrap()).unwrap(); + let src = get_gpr32(register::sized(src.into(), 4).unwrap()).unwrap(); + unsigned32(rt, dst, src); + } + 8 => { + unsigned64(rt, dst, src); + } + _ => unreachable!(), + } allocator.dirty(Value::Output(0)); allocator.consume(value); diff --git a/runtime/src/vm/handlers/store_extend.rs b/runtime/src/vm/handlers/store_extend.rs index 777ef638..24ad8865 100644 --- a/runtime/src/vm/handlers/store_extend.rs +++ b/runtime/src/vm/handlers/store_extend.rs @@ -28,22 +28,22 @@ pub fn build(rt: &mut Runtime) { r8, &mut epilogue, Some(Box::new(|rt| { + // load ymm0 + rt.asm.vmovups(ymm0, ptr(rax + r9)).unwrap(); // load r8 scratch::load(rt, r12, r8); - // vpxor ymm0, ymm0, ymm0 - rt.asm.vpxor(xmm0, xmm0, xmm0).unwrap(); - // vmovq xmm0, r8 - rt.asm.vmovq(xmm0, r8).unwrap(); + // movq xmm0, r8 + rt.asm.movq(xmm0, r8).unwrap(); // vmovups [rax + r9], ymm0 rt.asm.vmovups(ptr(rax + r9), ymm0).unwrap(); })), Some(Box::new(|rt| { + // load ymm0 + rt.asm.vmovups(ymm0, ptr(rax + r9)).unwrap(); // load r8 scratch::load(rt, r12, r8); - // vpxor ymm0, ymm0, ymm0 - rt.asm.vpxor(xmm0, xmm0, xmm0).unwrap(); - // vmovd xmm0, r8d - rt.asm.vmovd(xmm0, r8d).unwrap(); + // movd xmm0, r8d + rt.asm.movd(xmm0, r8d).unwrap(); // vmovups [rax + r9], ymm0 rt.asm.vmovups(ptr(rax + r9), ymm0).unwrap(); })), diff --git a/runtime/src/vm/handlers/trailing_zeros.rs b/runtime/src/vm/handlers/trailing_zeros.rs index 04d362ae..75045fab 100644 --- a/runtime/src/vm/handlers/trailing_zeros.rs +++ b/runtime/src/vm/handlers/trailing_zeros.rs @@ -28,12 +28,7 @@ pub fn build(rt: &mut Runtime) { ), ], stores: None, - widths: &[ - VMWidth::Lower64, - VMWidth::Lower32, - VMWidth::Lower16, - VMWidth::Lower8, - ], + widths: &[VMWidth::Lower64, VMWidth::Lower32, VMWidth::Lower16], }, ); } diff --git a/runtime/src/vm/lifters/arithmetic.rs b/runtime/src/vm/lifters/arithmetic.rs index f3156584..7d70ff8c 100644 --- a/runtime/src/vm/lifters/arithmetic.rs +++ b/runtime/src/vm/lifters/arithmetic.rs @@ -191,6 +191,7 @@ pub fn binary( Some(operations) } +// TODO: Create a handler for SBB & ADC pub fn carry( instruction: &Instruction, make: impl Fn(VMWidth) -> O, diff --git a/tests/src/constants.rs b/tests/src/constants.rs index 0962a120..e6900214 100644 --- a/tests/src/constants.rs +++ b/tests/src/constants.rs @@ -1,11 +1,13 @@ +use iced_x86::{Register, UsedRegister}; use runtime::{ mapper::Mappable, + register, vm::bytecode::{VMReg, VMVec}, }; use crate::State; -pub const REGISTERS: [VMReg; 17] = [ +pub const VIRTUAL_REGISTERS: [VMReg; 17] = [ VMReg::Rax, VMReg::Rcx, VMReg::Rdx, @@ -24,7 +26,8 @@ pub const REGISTERS: [VMReg; 17] = [ VMReg::R15, VMReg::Flags, ]; -pub const VECTORS: [VMVec; 16] = [ + +pub const VIRTUAL_VECTORS: [VMVec; 16] = [ VMVec::Ymm0, VMVec::Ymm1, VMVec::Ymm2, @@ -43,6 +46,26 @@ pub const VECTORS: [VMVec; 16] = [ VMVec::Ymm15, ]; +pub const NATIVE_REGISTERS: &[Register] = &[ + Register::RAX, + Register::RCX, + Register::RDX, + Register::RBX, + Register::RBP, + Register::RSI, + Register::RDI, + Register::R8, + Register::R9, + Register::R10, + Register::R11, + Register::R12, + Register::R13, + Register::R14, + Register::R15, +]; + +pub const IMMEDIATES: &[u64] = &[0, 0xFFFF_FFFF_FFFF_FFFF]; + pub fn baseline() -> State { let mut state = State::default(); @@ -79,33 +102,14 @@ pub fn vector(mut state: State, register: VMVec, bytes: [u128; 2]) -> State { state } -pub fn bytes16(value: u128) -> [u128; 2] { - let mut vector = [0u128; 2]; - vector[0] = value; - vector +pub fn register(operand: usize, size: usize) -> Register { + register::sized(NATIVE_REGISTERS[operand % NATIVE_REGISTERS.len()], size).unwrap() } -pub fn gpr() -> State { - baseline() - .with(VMReg::Rax, IMM64_A) - .with(VMReg::Rcx, IMM64_B) - .with(VMReg::Rdx, IMM64_C) +pub fn available(used: &[UsedRegister]) -> Register { + NATIVE_REGISTERS + .iter() + .copied() + .find(|candidate| !used.iter().any(|used| used.register() == *candidate)) + .unwrap() } - -pub fn simd() -> State { - let state = baseline(); - let state = vector(state, VMVec::Ymm0, bytes16(IMM128_A)); - let state = vector(state, VMVec::Ymm1, bytes16(IMM128_B)); - vector(state, VMVec::Ymm2, bytes16(IMM128_C)) -} - -pub const IMM8_A: u8 = 0x01; -pub const IMM16_A: u16 = 0x0123; -pub const IMM32_A: u32 = 0x0123_4567; -pub const IMM64_A: u64 = 0x0123_4567_89AB_CDEF; -pub const IMM64_B: u64 = 0x1234_5678_9ABC_DEF0; -pub const IMM64_C: u64 = 0x2345_6789_ABCD_EF01; - -pub const IMM128_A: u128 = 0x0123_4567_89AB_CDEF_0123_4567_89AB_CDEF; -pub const IMM128_B: u128 = 0x1234_5678_9ABC_DEF0_1234_5678_9ABC_DEF0; -pub const IMM128_C: u128 = 0x2345_6789_ABCD_EF01_2345_6789_ABCD_EF01; diff --git a/tests/src/instructions.rs b/tests/src/instructions.rs index a635360d..246337fc 100644 --- a/tests/src/instructions.rs +++ b/tests/src/instructions.rs @@ -1,12 +1,12 @@ use std::slice; use iced_x86::{ - Code, Instruction, InstructionInfoFactory, Mnemonic, OpAccess, OpCodeOperandKind, OpKind, - Register, RflagsBits, + Code, Instruction, InstructionInfoFactory, OpAccess, OpCodeOperandKind, OpKind, Register, + RflagsBits, }; -use runtime::vm::bytecode::{self, Flag, VMReg}; +use runtime::vm::bytecode::{self, Flag, VMReg, VMVec}; -use crate::constants::{gpr, simd, IMM128_A, IMM16_A, IMM32_A, IMM64_A, IMM8_A}; +use crate::constants::{available, baseline, register, vector, IMMEDIATES}; use crate::{decrypt_block, decrypt_payload, encrypt_block, Difference, Executor, State}; #[test] @@ -377,12 +377,10 @@ Sbb_rm8_imm8 Sbb_rm16_imm16 Sbb_rm32_imm32 Sbb_rm16_imm8 -Sbb_rm32_imm8 And_rm8_imm8 And_rm16_imm16 And_rm32_imm32 And_rm16_imm8 -And_rm32_imm8 Sub_rm8_imm8 Sub_rm16_imm16 Sub_rm32_imm32 @@ -449,27 +447,29 @@ Cmovs_r64_rm64 ); fn test(code: Code) { - let instruction = build(code, Test::Registers, Register::None); + for &immediate in IMMEDIATES { + let instruction = build(code, Test::Registers, Register::None, immediate); - case(instruction, None); + case(instruction, None, immediate); - let mut factory = InstructionInfoFactory::new(); - let info = factory.info(&instruction); + let mut factory = InstructionInfoFactory::new(); + let info = factory.info(&instruction); - if !info.used_memory().is_empty() { - let base = base(info.used_registers()); - let instruction = build(code, Test::Memory, base); - let size = instruction.memory_size().size() as usize; + if !info.used_memory().is_empty() { + let base = available(info.used_registers()); + let instruction = build(code, Test::Memory, base, immediate); + let size = instruction.memory_size().size() as usize; - let words = size.div_ceil(16); + let words = size.div_ceil(16); - let mut backing = vec![IMM128_A; words.max(1)]; + let mut backing = vec![(immediate as u128) | ((immediate as u128) << 64); words.max(1)]; - let memory = unsafe { - slice::from_raw_parts_mut(backing.as_mut_ptr() as *mut u8, backing.len() * 16) - }; + let memory = unsafe { + slice::from_raw_parts_mut(backing.as_mut_ptr() as *mut u8, backing.len() * 16) + }; - case(instruction, Some(&mut memory[..size])); + case(instruction, Some(&mut memory[..size]), immediate); + } } } @@ -479,7 +479,7 @@ enum Test { Memory, } -fn build(code: Code, test: Test, memory_base: Register) -> Instruction { +fn build(code: Code, test: Test, memory_base: Register, immediate: u64) -> Instruction { let info = code.op_code(); let kinds = [ info.op0_kind(), @@ -515,13 +515,13 @@ fn build(code: Code, test: Test, memory_base: Register) -> Instruction { continue; } - operand(&mut instruction, index as u32, kind); + operand(&mut instruction, index as u32, kind, immediate); } instruction } -fn operand(instruction: &mut Instruction, operand: u32, kind: OpCodeOperandKind) { +fn operand(instruction: &mut Instruction, operand: u32, kind: OpCodeOperandKind, immediate: u64) { let op = match kind { OpCodeOperandKind::r8_or_mem | OpCodeOperandKind::r16_or_mem @@ -610,7 +610,7 @@ fn operand(instruction: &mut Instruction, operand: u32, kind: OpCodeOperandKind) instruction.set_op_kind(operand, op); if op == OpKind::Register { - instruction.set_op_register(operand, register(kind, operand as usize)); + instruction.set_op_register(operand, register_for(kind, operand as usize)); } else { let value = match kind { OpCodeOperandKind::imm4_m2z => 0, @@ -618,10 +618,10 @@ fn operand(instruction: &mut Instruction, operand: u32, kind: OpCodeOperandKind) OpCodeOperandKind::imm8 | OpCodeOperandKind::imm8sex16 | OpCodeOperandKind::imm8sex32 - | OpCodeOperandKind::imm8sex64 => IMM8_A as u64, - OpCodeOperandKind::imm16 => IMM16_A as u64, - OpCodeOperandKind::imm32 | OpCodeOperandKind::imm32sex64 => IMM32_A as u64, - OpCodeOperandKind::imm64 => IMM64_A, + | OpCodeOperandKind::imm8sex64 => immediate as u8 as u64, + OpCodeOperandKind::imm16 => immediate as u16 as u64, + OpCodeOperandKind::imm32 | OpCodeOperandKind::imm32sex64 => immediate as u32 as u64, + OpCodeOperandKind::imm64 => immediate, _ => unreachable!(), }; @@ -629,7 +629,7 @@ fn operand(instruction: &mut Instruction, operand: u32, kind: OpCodeOperandKind) } } -fn register(kind: OpCodeOperandKind, operand: usize) -> Register { +fn register_for(kind: OpCodeOperandKind, operand: usize) -> Register { match kind { OpCodeOperandKind::al => Register::AL, OpCodeOperandKind::cl => Register::CL, @@ -638,66 +638,45 @@ fn register(kind: OpCodeOperandKind, operand: usize) -> Register { OpCodeOperandKind::eax => Register::EAX, OpCodeOperandKind::rax => Register::RAX, OpCodeOperandKind::r8_reg | OpCodeOperandKind::r8_opcode | OpCodeOperandKind::r8_or_mem => { - [Register::AL, Register::CL, Register::DL, Register::BL][operand.min(3)] + register(operand, 1) } OpCodeOperandKind::r16_reg | OpCodeOperandKind::r16_opcode | OpCodeOperandKind::r16_reg_mem | OpCodeOperandKind::r16_rm - | OpCodeOperandKind::r16_or_mem => { - [Register::AX, Register::CX, Register::DX, Register::BX][operand.min(3)] - } + | OpCodeOperandKind::r16_or_mem => register(operand, 2), OpCodeOperandKind::r32_reg | OpCodeOperandKind::r32_opcode | OpCodeOperandKind::r32_vvvv | OpCodeOperandKind::r32_reg_mem | OpCodeOperandKind::r32_rm | OpCodeOperandKind::r32_or_mem - | OpCodeOperandKind::r32_or_mem_mpx => { - [Register::EAX, Register::ECX, Register::EDX, Register::EBX][operand.min(3)] - } + | OpCodeOperandKind::r32_or_mem_mpx => register(operand, 4), OpCodeOperandKind::r64_reg | OpCodeOperandKind::r64_opcode | OpCodeOperandKind::r64_vvvv | OpCodeOperandKind::r64_reg_mem | OpCodeOperandKind::r64_rm | OpCodeOperandKind::r64_or_mem - | OpCodeOperandKind::r64_or_mem_mpx => { - [Register::RAX, Register::RCX, Register::RDX, Register::RBX][operand.min(3)] - } + | OpCodeOperandKind::r64_or_mem_mpx => register(operand, 8), OpCodeOperandKind::xmm_reg | OpCodeOperandKind::xmm_rm | OpCodeOperandKind::xmm_vvvv | OpCodeOperandKind::xmmp3_vvvv | OpCodeOperandKind::xmm_is4 | OpCodeOperandKind::xmm_is5 - | OpCodeOperandKind::xmm_or_mem => [ - Register::XMM0, - Register::XMM1, - Register::XMM2, - Register::XMM3, - ][operand.min(3)], + | OpCodeOperandKind::xmm_or_mem => Register::XMM0 + ((operand % 16) as u32), OpCodeOperandKind::ymm_reg | OpCodeOperandKind::ymm_rm | OpCodeOperandKind::ymm_vvvv | OpCodeOperandKind::ymm_is4 | OpCodeOperandKind::ymm_is5 - | OpCodeOperandKind::ymm_or_mem => [ - Register::YMM0, - Register::YMM1, - Register::YMM2, - Register::YMM3, - ][operand.min(3)], + | OpCodeOperandKind::ymm_or_mem => Register::YMM0 + ((operand % 16) as u32), OpCodeOperandKind::zmm_reg | OpCodeOperandKind::zmm_rm | OpCodeOperandKind::zmm_vvvv | OpCodeOperandKind::zmmp3_vvvv - | OpCodeOperandKind::zmm_or_mem => [ - Register::ZMM0, - Register::ZMM1, - Register::ZMM2, - Register::ZMM3, - ][operand.min(3)], + | OpCodeOperandKind::zmm_or_mem => Register::ZMM0 + ((operand % 16) as u32), _ => panic!("unsupported operand kind: {kind:?}"), } } @@ -732,39 +711,28 @@ fn memory(kind: OpCodeOperandKind) -> bool { ) } -fn base(used: &[iced_x86::UsedRegister]) -> Register { - const CANDIDATES: &[Register] = &[ - Register::R15, - Register::R14, - Register::R13, - Register::R12, - Register::R11, - Register::R10, - Register::R9, - Register::R8, - Register::RBX, - Register::RBP, - Register::RSI, - Register::RDI, - ]; +fn case(instruction: Instruction, memory: Option<&mut [u8]>, immediate: u64) { + let mut factory = InstructionInfoFactory::new(); + let info = factory.info(&instruction); - CANDIDATES - .iter() - .copied() - .find(|candidate| !used.iter().any(|used| used.register() == *candidate)) - .unwrap() -} + let mut state = baseline(); -fn case(instruction: Instruction, mut memory: Option<&mut [u8]>) { - let mut info_factory = InstructionInfoFactory::new(); - let info = info_factory.info(&instruction); + for used in info.used_registers() { + let register = used.register(); - let vector = info - .used_registers() - .iter() - .any(|used| used.register().is_vector_register()); + if register.is_gpr() { + state = state.with(VMReg::from(register.full_register()), immediate); + } + } - let mut state = if vector { simd() } else { gpr() }; + for index in 0..instruction.op_count() { + let register = instruction.op_register(index); + + if register.is_vector_register() { + let immediate = (immediate as u128) | ((immediate as u128) << 64); + state = vector(state, VMVec::from(register), [immediate, immediate]); + } + } let has_rax = info .used_registers() @@ -788,128 +756,9 @@ fn case(instruction: Instruction, mut memory: Option<&mut [u8]>) { state = state.with(VMReg::from(Register::RDX), 0); } - if matches!(instruction.mnemonic(), Mnemonic::Div | Mnemonic::Idiv) { - let size = match instruction.op_kind(0) { - OpKind::Register => instruction.op_register(0).info().size(), - OpKind::Memory => instruction.memory_size().size(), - _ => unreachable!(), - }; - - let bits = size * 8; - let mask = if size == 8 { - u64::MAX - } else { - (1u64 << bits) - 1 - }; - let sign = 1u64 << (bits - 1); - - let rax = state.registers[&VMReg::Rax]; - let rdx = state.registers[&VMReg::Rdx]; - - let mut low = rax & mask; - let mut high = if size == 1 { - (rax >> 8) & mask - } else { - rdx & mask - }; - - let memory_operand = instruction.op_kind(0) == OpKind::Memory; - - let mut divisor = if memory_operand { - let bytes = memory.as_deref().unwrap(); - - let mut value = 0; - - for index in 0..size { - value |= (bytes[index] as u64) << (index * 8); - } - - value - } else { - let register = instruction.op_register(0).full_register(); - - state.registers[&VMReg::from(register)] & mask - }; - - if divisor == 0 { - divisor = divisor.wrapping_add(1); - - if memory_operand { - let bytes = memory.as_deref_mut().unwrap(); - - for index in 0..size { - bytes[index] = (divisor >> (index * 8)) as u8; - } - } else { - let register = instruction.op_register(0).full_register(); - let value = state.registers[&VMReg::from(register)]; - - state = state.with(VMReg::from(register), (value & !mask) | divisor); - low = divisor; - } - } - - match instruction.mnemonic() { - Mnemonic::Div => { - high %= divisor; - } - Mnemonic::Idiv => { - let signed_low = if low & sign != 0 { - low as i128 - (1i128 << bits) - } else { - low as i128 - }; - - let signed_divisor = if divisor & sign != 0 { - divisor as i128 - (1i128 << bits) - } else { - divisor as i128 - }; - - if signed_low == -(1i128 << (bits - 1)) && signed_divisor == -1 { - low = low.wrapping_add(1) & mask; - - if !memory_operand - && instruction.op_register(0).full_register() != Register::RAX - { - let register = instruction.op_register(0).full_register(); - let value = state.registers[&VMReg::from(register)]; - - state = state.with(VMReg::from(register), (value & !mask) | low); - } - - if memory_operand && instruction.op_register(0) != Register::None { - unreachable!(); - } - } - - high = if low & sign != 0 { mask } else { 0 }; - } - _ => unreachable!(), - } - - if !memory_operand { - let register = instruction.op_register(0).full_register(); - - if register == Register::RAX { - let value = state.registers[&VMReg::Rax]; - - state = state.with(VMReg::Rax, (value & !mask) | low); - } - } - - let rax = if size == 1 { - (rax & !(mask | (mask << 8))) | low | (high << 8) - } else { - (rax & !mask) | low - }; - - let rdx = if size == 1 { rdx } else { (rdx & !mask) | high }; - - state = state.with(VMReg::Rax, rax).with(VMReg::Rdx, rdx); - } - - if instruction.rflags_read() != RflagsBits::NONE { + if instruction.rflags_read() != RflagsBits::NONE + || instruction.rflags_written() != RflagsBits::NONE + { state = state.with( VMReg::Flags, Flag::Carry.bit64() @@ -947,6 +796,7 @@ fn compare_memory(state: State, instruction: Instruction, memory: &mut [u8]) { let mut executor = Executor::new(); let lifted = bytecode::lift(&[instruction]) .unwrap_or_else(|| panic!("{instruction} is not implemented")); + let transformed = bytecode::transform(&mut executor.rt.mapper, lifted, |_| 0); let mut bytes = bytecode::assemble(&mut executor.rt.mapper, &transformed); @@ -962,18 +812,16 @@ fn normalize(native: &mut State, emulated: &mut State, instruction: Instruction) emulated.registers.remove(&VMReg::Rsp); for state in [&mut *native, &mut *emulated] { - if let Some(flags) = state.registers.get_mut(&VMReg::Flags) { - let mask = instruction.rflags_written() - | instruction.rflags_cleared() - | instruction.rflags_set(); - - *flags &= ((mask & RflagsBits::CF != 0) as u64 * Flag::Carry.bit64()) - | ((mask & RflagsBits::PF != 0) as u64 * Flag::Parity.bit64()) - | ((mask & RflagsBits::AF != 0) as u64 * Flag::Auxiliary.bit64()) - | ((mask & RflagsBits::ZF != 0) as u64 * Flag::Zero.bit64()) - | ((mask & RflagsBits::SF != 0) as u64 * Flag::Sign.bit64()) - | ((mask & RflagsBits::OF != 0) as u64 * Flag::Overflow.bit64()); - } + let flags = state.registers.get_mut(&VMReg::Flags).unwrap(); + let mask = + instruction.rflags_written() | instruction.rflags_cleared() | instruction.rflags_set(); + + *flags &= ((mask & RflagsBits::CF != 0) as u64 * Flag::Carry.bit64()) + | ((mask & RflagsBits::PF != 0) as u64 * Flag::Parity.bit64()) + | ((mask & RflagsBits::AF != 0) as u64 * Flag::Auxiliary.bit64()) + | ((mask & RflagsBits::ZF != 0) as u64 * Flag::Zero.bit64()) + | ((mask & RflagsBits::SF != 0) as u64 * Flag::Sign.bit64()) + | ((mask & RflagsBits::OF != 0) as u64 * Flag::Overflow.bit64()); } let differences = native.compare(emulated); @@ -996,6 +844,9 @@ fn dump(differences: &[Difference]) -> String { "{register:?}: native={native:02X?} virtual={emulated:02X?}" )); } + Difference::Exception(native, emulated) => { + lines.push(format!("Exception: native={native:?} virtual={emulated:?}")); + } } } diff --git a/tests/src/instrumentation.rs b/tests/src/instrumentation.rs index f90e12db..e346bc21 100644 --- a/tests/src/instrumentation.rs +++ b/tests/src/instrumentation.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, ffi::c_void, ptr}; -use crate::{constants::VECTORS, NATIVE_REGISTRY, VIRTUAL_REGISTRY, XSTATE_AVX}; +use crate::{constants::VIRTUAL_VECTORS, NATIVE_REGISTRY, VIRTUAL_REGISTRY, XSTATE_AVX}; use runtime::vm::bytecode::{Flag, VMReg, VMVec}; use windows::Win32::{ Foundation::EXCEPTION_SINGLE_STEP, @@ -13,16 +13,24 @@ use windows::Win32::{ }, }; -pub unsafe extern "system" fn virtual_handler(_info: *mut EXCEPTION_POINTERS) -> i32 { - if VIRTUAL_REGISTRY - .lock() - .unwrap() - .contains(&GetCurrentThreadId()) - { +pub unsafe extern "system" fn virtual_handler(info: *mut EXCEPTION_POINTERS) -> i32 { + let terminate = { + let mut registry = VIRTUAL_REGISTRY.lock().unwrap(); + + if let Some(exception) = registry.get_mut(&GetCurrentThreadId()) { + *exception = Some((*(*info).ExceptionRecord).ExceptionCode.0 as u32); + true + } else { + false + } + }; + + if terminate { TerminateThread(GetCurrentThread(), 0).unwrap(); return EXCEPTION_CONTINUE_EXECUTION; } + EXCEPTION_CONTINUE_SEARCH } @@ -111,11 +119,13 @@ pub unsafe fn write_vectors(context: &mut CONTEXT, vectors: &HashMap> 64) as i64, }; + *upper.add(index) = M128A { Low: value[1] as u64, High: (value[1] >> 64) as i64, @@ -126,20 +136,23 @@ pub unsafe fn write_vectors(context: &mut CONTEXT, vectors: &HashMap HashMap { let upper = LocateXStateFeature(context, XSTATE_AVX, None) as *const M128A; - VECTORS + VIRTUAL_VECTORS .iter() .enumerate() .map(|(index, &vector)| { let low = context.Anonymous.FltSave.XmmRegisters[index]; + let high = if upper.is_null() { M128A::default() } else { *upper.add(index) }; + let value = [ low.Low as u128 | ((low.High as u64 as u128) << 64), high.Low as u128 | ((high.High as u64 as u128) << 64), ]; + (vector, value) }) .collect() diff --git a/tests/src/lib.rs b/tests/src/lib.rs index d8a71cec..950d378b 100644 --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -1,7 +1,7 @@ #![cfg(test)] use std::{ - collections::{HashMap, HashSet}, + collections::HashMap, ffi::c_void, hint, mem, ptr, sync::{ @@ -11,7 +11,7 @@ use std::{ }; use crate::{ - constants::{REGISTERS, VECTORS}, + constants::{VIRTUAL_REGISTERS, VIRTUAL_VECTORS}, instrumentation::{ initialize_context, native_handler, read_register, read_vectors, virtual_handler, write_register, write_vectors, @@ -34,8 +34,8 @@ use windows::Win32::{ Foundation::CloseHandle, System::{ Diagnostics::Debug::{ - AddVectoredExceptionHandler, GetThreadContext, RaiseException, SetThreadContext, - SetXStateFeaturesMask, CONTEXT, CONTEXT_ALL_AMD64, CONTEXT_XSTATE_AMD64, + AddVectoredExceptionHandler, GetThreadContext, SetThreadContext, SetXStateFeaturesMask, + CONTEXT, CONTEXT_ALL_AMD64, CONTEXT_XSTATE_AMD64, }, Memory::{ VirtualAlloc, VirtualFree, MEM_COMMIT, MEM_RELEASE, MEM_RESERVE, PAGE_EXECUTE_READWRITE, @@ -60,8 +60,8 @@ static NATIVE_REGISTRY: LazyLock = OnceLock::new(); -static VIRTUAL_REGISTRY: LazyLock>> = - LazyLock::new(|| Mutex::new(HashSet::new())); +static VIRTUAL_REGISTRY: LazyLock>>> = + LazyLock::new(|| Mutex::new(HashMap::new())); static VIRTUAL_HANDLER: OnceLock<()> = OnceLock::new(); const XSTATE_AVX: u32 = 2; @@ -71,11 +71,13 @@ const XSTATE_MASK_AVX: u64 = 4; pub struct State { pub registers: HashMap, pub vectors: HashMap, + pub exception: Option, } pub enum Difference { Register(VMReg, u64, u64), Vector(VMVec, [u128; 2], [u128; 2]), + Exception(u32, u32), } impl State { @@ -93,6 +95,26 @@ impl State { pub fn compare(&self, other: &Self) -> Vec { let mut differences = Vec::new(); + + match (self.exception, other.exception) { + (Some(native), Some(virtual_)) => { + if native != virtual_ { + differences.push(Difference::Exception(native, virtual_)); + } + + return differences; + } + (Some(native), None) => { + differences.push(Difference::Exception(native, 0)); + return differences; + } + (None, Some(virtual_)) => { + differences.push(Difference::Exception(0, virtual_)); + return differences; + } + (None, None) => {} + } + for (®ister, &expected) in &self.registers { if let Some(&received) = other.registers.get(®ister) { if expected != received { @@ -100,6 +122,7 @@ impl State { } } } + for (&vector, &expected) in &self.vectors { if let Some(&received) = other.vectors.get(&vector) { if expected != received { @@ -107,6 +130,7 @@ impl State { } } } + differences } } @@ -198,9 +222,9 @@ impl Executor { ) .unwrap(); - let mut vectors = vec![[0u128; 2]; VECTORS.len()]; + let mut vectors = vec![[0u128; 2]; VIRTUAL_VECTORS.len()]; - for vector in VECTORS { + for vector in VIRTUAL_VECTORS { if let Some(v) = state.vectors.get(&vector) { vectors[self.rt.mapper.index(vector) as usize] = *v; } @@ -209,7 +233,7 @@ impl Executor { // mov r9, ... self.rt.asm.mov(r9, vectors.as_ptr() as u64).unwrap(); - for vector in VECTORS { + for vector in VIRTUAL_VECTORS { // vmovdqu ymm0, [r9 + ...] self.rt .asm @@ -242,7 +266,7 @@ impl Executor { // mov r8, ... self.rt.asm.mov(r8, registers.as_mut_ptr() as u64).unwrap(); - for register in REGISTERS { + for register in VIRTUAL_REGISTERS { // mov r9, [r12 + ...] self.rt .asm @@ -266,7 +290,7 @@ impl Executor { // mov r9, ... self.rt.asm.mov(r9, vectors.as_mut_ptr() as u64).unwrap(); - for vector in VECTORS { + for vector in VIRTUAL_VECTORS { // vmovdqu ymm0, [r8 + ...] self.rt .asm @@ -315,7 +339,7 @@ impl Executor { .unwrap() }; - VIRTUAL_REGISTRY.lock().unwrap().insert(thread_id); + VIRTUAL_REGISTRY.lock().unwrap().insert(thread_id, None); unsafe { ResumeThread(thread); @@ -323,17 +347,22 @@ impl Executor { CloseHandle(thread).unwrap(); } - VIRTUAL_REGISTRY.lock().unwrap().remove(&thread_id); + let exception = VIRTUAL_REGISTRY + .lock() + .unwrap() + .remove(&thread_id) + .flatten(); State { - registers: REGISTERS + registers: VIRTUAL_REGISTERS .iter() .map(|&r| (r, registers[self.rt.mapper.index(r) as usize])) .collect(), - vectors: VECTORS + vectors: VIRTUAL_VECTORS .iter() .map(|&v| (v, vectors[self.rt.mapper.index(v) as usize])) .collect(), + exception, } } @@ -428,21 +457,19 @@ impl Executor { .remove(&thread_id) .and_then(|(_, _, _, exception)| exception); - if let Some(exception) = exception { - unsafe { - RaiseException(exception, 0, None); - } - } - context.EFlags &= !(Flag::Interrupt.bit32() | Flag::Reserved1.bit32()); - let registers = REGISTERS + let registers = VIRTUAL_REGISTERS .iter() .map(|®ister| (register, read_register(context, register))) .collect(); let vectors = unsafe { read_vectors(context) }; - State { registers, vectors } + State { + registers, + vectors, + exception, + } } } From 731456eafb186d6bde79136b64ad045cc56fae72 Mon Sep 17 00:00:00 2001 From: unknown <86915746+RadonCoding@users.noreply.github.com> Date: Sat, 26 Sep 2026 18:18:05 +0300 Subject: [PATCH 13/33] fix: unit tests --- runtime/src/runtime.rs | 4 + runtime/src/vm/bytecode.rs | 2 + runtime/src/vm/encoders/adc.rs | 43 +++++ .../src/vm/encoders/bit_test_complement.rs | 1 - runtime/src/vm/encoders/bit_test_set.rs | 1 - runtime/src/vm/encoders/exchange_add.rs | 1 - runtime/src/vm/encoders/mod.rs | 2 + runtime/src/vm/encoders/or.rs | 1 - runtime/src/vm/encoders/rol.rs | 1 - runtime/src/vm/encoders/ror.rs | 1 - runtime/src/vm/encoders/sar.rs | 1 - runtime/src/vm/encoders/sbb.rs | 43 +++++ runtime/src/vm/encoders/shl.rs | 1 - runtime/src/vm/encoders/sub.rs | 1 - runtime/src/vm/encoders/trailing_zeros.rs | 1 - runtime/src/vm/encoders/vector_add.rs | 1 - runtime/src/vm/encoders/vector_and.rs | 1 - runtime/src/vm/encoders/vector_and_not.rs | 1 - runtime/src/vm/encoders/vector_div.rs | 1 - runtime/src/vm/encoders/vector_mul.rs | 1 - runtime/src/vm/encoders/vector_or.rs | 1 - runtime/src/vm/encoders/vector_sub.rs | 1 - runtime/src/vm/encoders/vector_xor.rs | 1 - runtime/src/vm/encoders/xor.rs | 1 - runtime/src/vm/functions/dispatch.rs | 2 + runtime/src/vm/handlers/adc.rs | 102 +++++++++++ runtime/src/vm/handlers/add.rs | 28 ++-- runtime/src/vm/handlers/and.rs | 24 +-- runtime/src/vm/handlers/bit_scan_reverse.rs | 10 +- runtime/src/vm/handlers/bit_test.rs | 10 +- .../src/vm/handlers/bit_test_complement.rs | 10 +- runtime/src/vm/handlers/bit_test_reset.rs | 10 +- runtime/src/vm/handlers/bit_test_set.rs | 10 +- runtime/src/vm/handlers/byte_swap.rs | 4 +- runtime/src/vm/handlers/div.rs | 4 +- runtime/src/vm/handlers/mod.rs | 2 + runtime/src/vm/handlers/mul.rs | 14 +- runtime/src/vm/handlers/or.rs | 24 +-- runtime/src/vm/handlers/rol.rs | 42 ++--- runtime/src/vm/handlers/ror.rs | 46 ++--- runtime/src/vm/handlers/sar.rs | 80 +++++---- runtime/src/vm/handlers/sbb.rs | 102 +++++++++++ runtime/src/vm/handlers/semantic/compiler.rs | 158 ++++++++++++------ runtime/src/vm/handlers/semantic/mod.rs | 87 ++++++---- runtime/src/vm/handlers/shl.rs | 78 +++++---- runtime/src/vm/handlers/shr.rs | 60 +++---- runtime/src/vm/handlers/sub.rs | 28 ++-- runtime/src/vm/handlers/trailing_zeros.rs | 14 +- runtime/src/vm/handlers/xor.rs | 24 +-- runtime/src/vm/lifters/arithmetic.rs | 65 +------ runtime/src/vm/lifters/branch.rs | 2 +- runtime/src/vm/lifters/bt.rs | 1 - runtime/src/vm/lifters/cmpxchg.rs | 2 +- runtime/src/vm/lifters/lea.rs | 1 - runtime/src/vm/lifters/pcmpeqb.rs | 1 - runtime/src/vm/lifters/pmovskb.rs | 1 - runtime/src/vm/lifters/transfer.rs | 1 - runtime/src/vm/lifters/tzcnt.rs | 1 - runtime/src/vm/lifters/xadd.rs | 1 - runtime/src/vm/lifters/xchg.rs | 1 - runtime/src/vm/utils/vvec.rs | 8 +- 61 files changed, 751 insertions(+), 420 deletions(-) create mode 100644 runtime/src/vm/encoders/adc.rs create mode 100644 runtime/src/vm/encoders/sbb.rs create mode 100644 runtime/src/vm/handlers/adc.rs create mode 100644 runtime/src/vm/handlers/sbb.rs diff --git a/runtime/src/runtime.rs b/runtime/src/runtime.rs index f8735114..8d207b93 100644 --- a/runtime/src/runtime.rs +++ b/runtime/src/runtime.rs @@ -53,6 +53,8 @@ mapped! { VmHandlerStoreExtend, VmHandlerAdd, VmHandlerSub, + VmHandlerAdc, + VmHandlerSbb, VmHandlerAnd, VmHandlerOr, VmHandlerXor, @@ -639,6 +641,8 @@ impl Runtime { ), (FnDef::VmHandlerAdd, vm::handlers::add::build), (FnDef::VmHandlerSub, vm::handlers::sub::build), + (FnDef::VmHandlerAdc, vm::handlers::adc::build), + (FnDef::VmHandlerSbb, vm::handlers::sbb::build), (FnDef::VmHandlerAnd, vm::handlers::and::build), (FnDef::VmHandlerOr, vm::handlers::or::build), (FnDef::VmHandlerXor, vm::handlers::xor::build), diff --git a/runtime/src/vm/bytecode.rs b/runtime/src/vm/bytecode.rs index d7f1c2db..e49aeb19 100644 --- a/runtime/src/vm/bytecode.rs +++ b/runtime/src/vm/bytecode.rs @@ -36,6 +36,8 @@ mapped! { // Arithmetic Add, Sub, + Adc, + Sbb, And, Or, Xor, diff --git a/runtime/src/vm/encoders/adc.rs b/runtime/src/vm/encoders/adc.rs new file mode 100644 index 00000000..f73bea43 --- /dev/null +++ b/runtime/src/vm/encoders/adc.rs @@ -0,0 +1,43 @@ +use crate::mapper::Mapper; +use crate::vm::bytecode::{VMOp, VMReg, VMWidth}; +use crate::vm::encoders::{Effect, Encode}; +use std::any::Any; + +#[derive(Debug)] +pub struct Adc { + pub width: VMWidth, +} + +impl Encode for Adc { + fn as_any(&self) -> &dyn Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } + + fn op(&self) -> Option { + Some(VMOp::Adc) + } + + fn encode(&self, mapper: &mut Mapper) -> Vec { + vec![mapper.index(self.width)] + } + + fn reads(&self) -> Vec { + vec![Effect::Register(VMReg::Flags)] + } + + fn writes(&self) -> Vec { + vec![Effect::Register(VMReg::Flags)] + } + + fn consumes(&self) -> i32 { + 2 + } + + fn produces(&self) -> i32 { + 1 + } +} diff --git a/runtime/src/vm/encoders/bit_test_complement.rs b/runtime/src/vm/encoders/bit_test_complement.rs index f084a771..ff7a3c3d 100644 --- a/runtime/src/vm/encoders/bit_test_complement.rs +++ b/runtime/src/vm/encoders/bit_test_complement.rs @@ -36,5 +36,4 @@ impl Encode for BitTestComplement { fn produces(&self) -> i32 { 1 } - } diff --git a/runtime/src/vm/encoders/bit_test_set.rs b/runtime/src/vm/encoders/bit_test_set.rs index 0e064c6b..f232b724 100644 --- a/runtime/src/vm/encoders/bit_test_set.rs +++ b/runtime/src/vm/encoders/bit_test_set.rs @@ -36,5 +36,4 @@ impl Encode for BitTestSet { fn produces(&self) -> i32 { 1 } - } diff --git a/runtime/src/vm/encoders/exchange_add.rs b/runtime/src/vm/encoders/exchange_add.rs index eef9e2d7..38cf9023 100644 --- a/runtime/src/vm/encoders/exchange_add.rs +++ b/runtime/src/vm/encoders/exchange_add.rs @@ -36,5 +36,4 @@ impl Encode for ExchangeAdd { fn produces(&self) -> i32 { 1 } - } diff --git a/runtime/src/vm/encoders/mod.rs b/runtime/src/vm/encoders/mod.rs index c25e3197..7fda0135 100644 --- a/runtime/src/vm/encoders/mod.rs +++ b/runtime/src/vm/encoders/mod.rs @@ -84,6 +84,7 @@ pub trait Encode: Debug + Any { fn seal(&mut self, _mapper: &mut Mapper, _transform: &mut dyn FnMut(&mut [u8], usize)) {} } +pub mod adc; pub mod add; pub mod and; pub mod bit_scan_reverse; @@ -116,6 +117,7 @@ pub mod ret; pub mod rol; pub mod ror; pub mod sar; +pub mod sbb; pub mod shl; pub mod shr; pub mod store_extend; diff --git a/runtime/src/vm/encoders/or.rs b/runtime/src/vm/encoders/or.rs index 36fa39ff..8d59154c 100644 --- a/runtime/src/vm/encoders/or.rs +++ b/runtime/src/vm/encoders/or.rs @@ -36,5 +36,4 @@ impl Encode for Or { fn produces(&self) -> i32 { 1 } - } diff --git a/runtime/src/vm/encoders/rol.rs b/runtime/src/vm/encoders/rol.rs index f863be45..bb9a57c3 100644 --- a/runtime/src/vm/encoders/rol.rs +++ b/runtime/src/vm/encoders/rol.rs @@ -36,5 +36,4 @@ impl Encode for Rol { fn produces(&self) -> i32 { 1 } - } diff --git a/runtime/src/vm/encoders/ror.rs b/runtime/src/vm/encoders/ror.rs index 54bb1c7d..aa942895 100644 --- a/runtime/src/vm/encoders/ror.rs +++ b/runtime/src/vm/encoders/ror.rs @@ -36,5 +36,4 @@ impl Encode for Ror { fn produces(&self) -> i32 { 1 } - } diff --git a/runtime/src/vm/encoders/sar.rs b/runtime/src/vm/encoders/sar.rs index d046f32b..1b781101 100644 --- a/runtime/src/vm/encoders/sar.rs +++ b/runtime/src/vm/encoders/sar.rs @@ -36,5 +36,4 @@ impl Encode for Sar { fn produces(&self) -> i32 { 1 } - } diff --git a/runtime/src/vm/encoders/sbb.rs b/runtime/src/vm/encoders/sbb.rs new file mode 100644 index 00000000..6490e974 --- /dev/null +++ b/runtime/src/vm/encoders/sbb.rs @@ -0,0 +1,43 @@ +use crate::mapper::Mapper; +use crate::vm::bytecode::{VMOp, VMReg, VMWidth}; +use crate::vm::encoders::{Effect, Encode}; +use std::any::Any; + +#[derive(Debug)] +pub struct Sbb { + pub width: VMWidth, +} + +impl Encode for Sbb { + fn as_any(&self) -> &dyn Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } + + fn op(&self) -> Option { + Some(VMOp::Sbb) + } + + fn encode(&self, mapper: &mut Mapper) -> Vec { + vec![mapper.index(self.width)] + } + + fn reads(&self) -> Vec { + vec![Effect::Register(VMReg::Flags)] + } + + fn writes(&self) -> Vec { + vec![Effect::Register(VMReg::Flags)] + } + + fn consumes(&self) -> i32 { + 2 + } + + fn produces(&self) -> i32 { + 1 + } +} diff --git a/runtime/src/vm/encoders/shl.rs b/runtime/src/vm/encoders/shl.rs index e0d6a291..7858b0b2 100644 --- a/runtime/src/vm/encoders/shl.rs +++ b/runtime/src/vm/encoders/shl.rs @@ -36,5 +36,4 @@ impl Encode for Shl { fn produces(&self) -> i32 { 1 } - } diff --git a/runtime/src/vm/encoders/sub.rs b/runtime/src/vm/encoders/sub.rs index 7b7904b5..4c289f66 100644 --- a/runtime/src/vm/encoders/sub.rs +++ b/runtime/src/vm/encoders/sub.rs @@ -36,5 +36,4 @@ impl Encode for Sub { fn produces(&self) -> i32 { 1 } - } diff --git a/runtime/src/vm/encoders/trailing_zeros.rs b/runtime/src/vm/encoders/trailing_zeros.rs index afe30175..9dc6a5f7 100644 --- a/runtime/src/vm/encoders/trailing_zeros.rs +++ b/runtime/src/vm/encoders/trailing_zeros.rs @@ -36,5 +36,4 @@ impl Encode for TrailingZeros { fn produces(&self) -> i32 { 1 } - } diff --git a/runtime/src/vm/encoders/vector_add.rs b/runtime/src/vm/encoders/vector_add.rs index 1cdfbf3d..d705ca74 100644 --- a/runtime/src/vm/encoders/vector_add.rs +++ b/runtime/src/vm/encoders/vector_add.rs @@ -38,5 +38,4 @@ impl Encode for VectorAdd { fn produces(&self) -> i32 { 1 } - } diff --git a/runtime/src/vm/encoders/vector_and.rs b/runtime/src/vm/encoders/vector_and.rs index 552a238a..67ff5ba2 100644 --- a/runtime/src/vm/encoders/vector_and.rs +++ b/runtime/src/vm/encoders/vector_and.rs @@ -32,5 +32,4 @@ impl Encode for VectorAnd { fn produces(&self) -> i32 { 1 } - } diff --git a/runtime/src/vm/encoders/vector_and_not.rs b/runtime/src/vm/encoders/vector_and_not.rs index d87676c5..4ea2a697 100644 --- a/runtime/src/vm/encoders/vector_and_not.rs +++ b/runtime/src/vm/encoders/vector_and_not.rs @@ -32,5 +32,4 @@ impl Encode for VectorAndNot { fn produces(&self) -> i32 { 1 } - } diff --git a/runtime/src/vm/encoders/vector_div.rs b/runtime/src/vm/encoders/vector_div.rs index 75ec6a09..20f5d530 100644 --- a/runtime/src/vm/encoders/vector_div.rs +++ b/runtime/src/vm/encoders/vector_div.rs @@ -33,5 +33,4 @@ impl Encode for VectorDiv { fn produces(&self) -> i32 { 1 } - } diff --git a/runtime/src/vm/encoders/vector_mul.rs b/runtime/src/vm/encoders/vector_mul.rs index fa9b563c..24880a80 100644 --- a/runtime/src/vm/encoders/vector_mul.rs +++ b/runtime/src/vm/encoders/vector_mul.rs @@ -38,5 +38,4 @@ impl Encode for VectorMul { fn produces(&self) -> i32 { 1 } - } diff --git a/runtime/src/vm/encoders/vector_or.rs b/runtime/src/vm/encoders/vector_or.rs index d8a60241..b8b16a2c 100644 --- a/runtime/src/vm/encoders/vector_or.rs +++ b/runtime/src/vm/encoders/vector_or.rs @@ -32,5 +32,4 @@ impl Encode for VectorOr { fn produces(&self) -> i32 { 1 } - } diff --git a/runtime/src/vm/encoders/vector_sub.rs b/runtime/src/vm/encoders/vector_sub.rs index 62a00888..c5172ebb 100644 --- a/runtime/src/vm/encoders/vector_sub.rs +++ b/runtime/src/vm/encoders/vector_sub.rs @@ -38,5 +38,4 @@ impl Encode for VectorSub { fn produces(&self) -> i32 { 1 } - } diff --git a/runtime/src/vm/encoders/vector_xor.rs b/runtime/src/vm/encoders/vector_xor.rs index f6fb2d69..bad28bfc 100644 --- a/runtime/src/vm/encoders/vector_xor.rs +++ b/runtime/src/vm/encoders/vector_xor.rs @@ -32,5 +32,4 @@ impl Encode for VectorXor { fn produces(&self) -> i32 { 1 } - } diff --git a/runtime/src/vm/encoders/xor.rs b/runtime/src/vm/encoders/xor.rs index 50657ca9..1a4434aa 100644 --- a/runtime/src/vm/encoders/xor.rs +++ b/runtime/src/vm/encoders/xor.rs @@ -36,5 +36,4 @@ impl Encode for Xor { fn produces(&self) -> i32 { 1 } - } diff --git a/runtime/src/vm/functions/dispatch.rs b/runtime/src/vm/functions/dispatch.rs index afe178ab..dd7b2d77 100644 --- a/runtime/src/vm/functions/dispatch.rs +++ b/runtime/src/vm/functions/dispatch.rs @@ -30,6 +30,8 @@ pub const HANDLERS: [(VMOp, FnDef); VMOp::COUNT] = [ (VMOp::StoreExtend, FnDef::VmHandlerStoreExtend), (VMOp::Add, FnDef::VmHandlerAdd), (VMOp::Sub, FnDef::VmHandlerSub), + (VMOp::Adc, FnDef::VmHandlerAdc), + (VMOp::Sbb, FnDef::VmHandlerSbb), (VMOp::Exchange, FnDef::VmHandlerExchange), (VMOp::ExchangeAdd, FnDef::VmHandlerExchangeAdd), (VMOp::CompareExchange, FnDef::VmHandlerCompareExchange), diff --git a/runtime/src/vm/handlers/adc.rs b/runtime/src/vm/handlers/adc.rs new file mode 100644 index 00000000..461f7979 --- /dev/null +++ b/runtime/src/vm/handlers/adc.rs @@ -0,0 +1,102 @@ +use crate::{ + runtime::Runtime, + vm::{ + bytecode::{Flag, VMWidth}, + handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, + }, +}; + +pub fn build(rt: &mut Runtime) { + semantic::compiler::compile( + rt, + &Operation { + effects: vec![Effect::Add( + Expression::Operand(Operand::Input(0)), + Expression::Sub( + Box::new(Expression::Sub( + Box::new(Expression::Operand(Operand::Input(1))), + Box::new(Expression::BitNot(Box::new(Expression::Flag(Flag::Carry)))), + )), + Box::new(Expression::Constant(1)), + ), + )], + flags: Flags::Always(vec![ + ( + Flag::Zero, + Expression::Compare(Box::new(Compare::Equal( + Expression::Operand(Operand::Output(0)), + Expression::Constant(0), + ))), + ), + ( + Flag::Sign, + Expression::Compare(Box::new(Compare::BitSet( + Expression::Operand(Operand::Output(0)), + Expression::SignBit, + ))), + ), + ( + Flag::Carry, + Expression::Compare(Box::new(Compare::BitSet( + Expression::BitOr( + Box::new(Expression::BitAnd( + Box::new(Expression::Operand(Operand::Input(0))), + Box::new(Expression::Operand(Operand::Input(1))), + )), + Box::new(Expression::BitAnd( + Box::new(Expression::BitXor( + Box::new(Expression::Operand(Operand::Input(0))), + Box::new(Expression::Operand(Operand::Input(1))), + )), + Box::new(Expression::BitNot(Box::new(Expression::Operand( + Operand::Output(0), + )))), + )), + ), + Expression::SignBit, + ))), + ), + ( + Flag::Overflow, + Expression::Compare(Box::new(Compare::BitSet( + Expression::BitAnd( + Box::new(Expression::BitXor( + Box::new(Expression::Operand(Operand::Input(0))), + Box::new(Expression::Operand(Operand::Output(0))), + )), + Box::new(Expression::BitXor( + Box::new(Expression::Operand(Operand::Input(1))), + Box::new(Expression::Operand(Operand::Output(0))), + )), + ), + Expression::SignBit, + ))), + ), + ( + Flag::Parity, + Expression::Parity(Box::new(Expression::Operand(Operand::Output(0)))), + ), + ( + Flag::Auxiliary, + Expression::Compare(Box::new(Compare::BitSet( + Expression::BitXor( + Box::new(Expression::BitXor( + Box::new(Expression::Operand(Operand::Input(0))), + Box::new(Expression::Operand(Operand::Input(1))), + )), + Box::new(Expression::Operand(Operand::Output(0))), + ), + Expression::Constant(4), + ))), + ), + ]), + stores: None, + widths: &[ + VMWidth::Lower64, + VMWidth::Lower32, + VMWidth::Lower16, + VMWidth::Lower8, + ], + }, + ); +} diff --git a/runtime/src/vm/handlers/add.rs b/runtime/src/vm/handlers/add.rs index ea099212..98436e93 100644 --- a/runtime/src/vm/handlers/add.rs +++ b/runtime/src/vm/handlers/add.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Compare, Condition, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, }, }; @@ -14,31 +14,31 @@ pub fn build(rt: &mut Runtime) { Expression::Operand(Operand::Input(0)), Expression::Operand(Operand::Input(1)), )], - flags: vec![ + flags: Flags::Always(vec![ ( Flag::Zero, - Condition::Compare(Compare::Equal( + Expression::Compare(Box::new(Compare::Equal( Expression::Operand(Operand::Output(0)), Expression::Constant(0), - )), + ))), ), ( Flag::Sign, - Condition::Compare(Compare::BitSet( + Expression::Compare(Box::new(Compare::BitSet( Expression::Operand(Operand::Output(0)), Expression::SignBit, - )), + ))), ), ( Flag::Carry, - Condition::Compare(Compare::LessThan( + Expression::Compare(Box::new(Compare::LessThan( Expression::Operand(Operand::Output(0)), Expression::Operand(Operand::Input(0)), - )), + ))), ), ( Flag::Overflow, - Condition::Compare(Compare::BitSet( + Expression::Compare(Box::new(Compare::BitSet( Expression::BitAnd( Box::new(Expression::BitXor( Box::new(Expression::Operand(Operand::Input(0))), @@ -50,15 +50,15 @@ pub fn build(rt: &mut Runtime) { )), ), Expression::SignBit, - )), + ))), ), ( Flag::Parity, - Condition::Parity(Expression::Operand(Operand::Output(0))), + Expression::Parity(Box::new(Expression::Operand(Operand::Output(0)))), ), ( Flag::Auxiliary, - Condition::Compare(Compare::BitSet( + Expression::Compare(Box::new(Compare::BitSet( Expression::BitXor( Box::new(Expression::BitXor( Box::new(Expression::Operand(Operand::Input(0))), @@ -67,9 +67,9 @@ pub fn build(rt: &mut Runtime) { Box::new(Expression::Operand(Operand::Output(0))), ), Expression::Constant(4), - )), + ))), ), - ], + ]), stores: None, widths: &[ VMWidth::Lower64, diff --git a/runtime/src/vm/handlers/and.rs b/runtime/src/vm/handlers/and.rs index 9d814ab2..fc70c714 100644 --- a/runtime/src/vm/handlers/and.rs +++ b/runtime/src/vm/handlers/and.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Compare, Condition, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, }, }; @@ -14,40 +14,40 @@ pub fn build(rt: &mut Runtime) { Expression::Operand(Operand::Input(0)), Expression::Operand(Operand::Input(1)), )], - flags: vec![ + flags: Flags::Always(vec![ ( Flag::Carry, - Condition::Compare(Compare::Equal( + Expression::Compare(Box::new(Compare::Equal( Expression::Constant(1), Expression::Constant(0), - )), + ))), ), ( Flag::Overflow, - Condition::Compare(Compare::Equal( + Expression::Compare(Box::new(Compare::Equal( Expression::Constant(1), Expression::Constant(0), - )), + ))), ), ( Flag::Sign, - Condition::Compare(Compare::BitSet( + Expression::Compare(Box::new(Compare::BitSet( Expression::Operand(Operand::Output(0)), Expression::SignBit, - )), + ))), ), ( Flag::Zero, - Condition::Compare(Compare::Equal( + Expression::Compare(Box::new(Compare::Equal( Expression::Operand(Operand::Output(0)), Expression::Constant(0), - )), + ))), ), ( Flag::Parity, - Condition::Parity(Expression::Operand(Operand::Output(0))), + Expression::Parity(Box::new(Expression::Operand(Operand::Output(0)))), ), - ], + ]), stores: None, widths: &[ VMWidth::Lower64, diff --git a/runtime/src/vm/handlers/bit_scan_reverse.rs b/runtime/src/vm/handlers/bit_scan_reverse.rs index cf7b001a..228c78e9 100644 --- a/runtime/src/vm/handlers/bit_scan_reverse.rs +++ b/runtime/src/vm/handlers/bit_scan_reverse.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Compare, Condition, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, }, }; @@ -14,13 +14,13 @@ pub fn build(rt: &mut Runtime) { Effect::Assign(Expression::Operand(Operand::Input(0))), Effect::Bsr(Expression::Operand(Operand::Input(1))), ], - flags: vec![( + flags: Flags::Always(vec![( Flag::Zero, - Condition::Compare(Compare::Equal( + Expression::Compare(Box::new(Compare::Equal( Expression::Operand(Operand::Input(1)), Expression::Constant(0), - )), - )], + ))), + )]), stores: None, widths: &[VMWidth::Lower64, VMWidth::Lower32, VMWidth::Lower16], }, diff --git a/runtime/src/vm/handlers/bit_test.rs b/runtime/src/vm/handlers/bit_test.rs index 9a811e11..86c03eda 100644 --- a/runtime/src/vm/handlers/bit_test.rs +++ b/runtime/src/vm/handlers/bit_test.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Compare, Condition, Expression, Operand, Operation}, + handlers::semantic::{self, Flags, Compare, Expression, Operand, Operation}, }, }; @@ -11,16 +11,16 @@ pub fn build(rt: &mut Runtime) { rt, &Operation { effects: vec![], - flags: vec![( + flags: Flags::Always(vec![( Flag::Carry, - Condition::Compare(Compare::BitSet( + Expression::Compare(Box::new(Compare::BitSet( Expression::Operand(Operand::Input(0)), Expression::BitAnd( Box::new(Expression::Operand(Operand::Input(1))), Box::new(Expression::SignBit), ), - )), - )], + ))), + )]), stores: Some(vec![Expression::Operand(Operand::Input(0))]), widths: &[VMWidth::Lower64, VMWidth::Lower32, VMWidth::Lower16], }, diff --git a/runtime/src/vm/handlers/bit_test_complement.rs b/runtime/src/vm/handlers/bit_test_complement.rs index 832d1c3a..b526dc01 100644 --- a/runtime/src/vm/handlers/bit_test_complement.rs +++ b/runtime/src/vm/handlers/bit_test_complement.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Compare, Condition, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, }, }; @@ -20,16 +20,16 @@ pub fn build(rt: &mut Runtime) { )), ), )], - flags: vec![( + flags: Flags::Always(vec![( Flag::Carry, - Condition::Compare(Compare::BitSet( + Expression::Compare(Box::new(Compare::BitSet( Expression::Operand(Operand::Input(0)), Expression::BitAnd( Box::new(Expression::Operand(Operand::Input(1))), Box::new(Expression::SignBit), ), - )), - )], + ))), + )]), stores: None, widths: &[VMWidth::Lower64, VMWidth::Lower32, VMWidth::Lower16], }, diff --git a/runtime/src/vm/handlers/bit_test_reset.rs b/runtime/src/vm/handlers/bit_test_reset.rs index dadd99e0..69ec3c0b 100644 --- a/runtime/src/vm/handlers/bit_test_reset.rs +++ b/runtime/src/vm/handlers/bit_test_reset.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Compare, Condition, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, }, }; @@ -20,16 +20,16 @@ pub fn build(rt: &mut Runtime) { )), ))), )], - flags: vec![( + flags: Flags::Always(vec![( Flag::Carry, - Condition::Compare(Compare::BitSet( + Expression::Compare(Box::new(Compare::BitSet( Expression::Operand(Operand::Input(0)), Expression::BitAnd( Box::new(Expression::Operand(Operand::Input(1))), Box::new(Expression::SignBit), ), - )), - )], + ))), + )]), stores: None, widths: &[VMWidth::Lower64, VMWidth::Lower32, VMWidth::Lower16], }, diff --git a/runtime/src/vm/handlers/bit_test_set.rs b/runtime/src/vm/handlers/bit_test_set.rs index 6a946c3d..fdcd696a 100644 --- a/runtime/src/vm/handlers/bit_test_set.rs +++ b/runtime/src/vm/handlers/bit_test_set.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Compare, Condition, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, }, }; @@ -20,16 +20,16 @@ pub fn build(rt: &mut Runtime) { )), ), )], - flags: vec![( + flags: Flags::Always(vec![( Flag::Carry, - Condition::Compare(Compare::BitSet( + Expression::Compare(Box::new(Compare::BitSet( Expression::Operand(Operand::Input(0)), Expression::BitAnd( Box::new(Expression::Operand(Operand::Input(1))), Box::new(Expression::SignBit), ), - )), - )], + ))), + )]), stores: None, widths: &[VMWidth::Lower64, VMWidth::Lower32, VMWidth::Lower16], }, diff --git a/runtime/src/vm/handlers/byte_swap.rs b/runtime/src/vm/handlers/byte_swap.rs index 9a065ab9..92c14f38 100644 --- a/runtime/src/vm/handlers/byte_swap.rs +++ b/runtime/src/vm/handlers/byte_swap.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::VMWidth, - handlers::semantic::{self, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Flags, Effect, Expression, Operand, Operation}, }, }; @@ -113,7 +113,7 @@ pub fn build(rt: &mut Runtime) { ), ), ], - flags: vec![], + flags: Flags::Always(vec![]), stores: None, widths: &[VMWidth::Lower64, VMWidth::Lower32], }, diff --git a/runtime/src/vm/handlers/div.rs b/runtime/src/vm/handlers/div.rs index d7be7a37..2959fb27 100644 --- a/runtime/src/vm/handlers/div.rs +++ b/runtime/src/vm/handlers/div.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::VMWidth, - handlers::semantic::{self, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Flags, Effect, Expression, Operand, Operation}, }, }; @@ -15,7 +15,7 @@ pub fn build(rt: &mut Runtime) { Expression::Operand(Operand::Input(1)), Expression::Operand(Operand::Input(2)), )], - flags: vec![], + flags: Flags::Always(vec![]), stores: None, widths: &[ VMWidth::Lower64, diff --git a/runtime/src/vm/handlers/mod.rs b/runtime/src/vm/handlers/mod.rs index b9328f60..ac15a2a6 100644 --- a/runtime/src/vm/handlers/mod.rs +++ b/runtime/src/vm/handlers/mod.rs @@ -1,3 +1,4 @@ +pub mod adc; pub mod add; pub mod and; pub mod bit_scan_reverse; @@ -29,6 +30,7 @@ pub mod ret; pub mod rol; pub mod ror; pub mod sar; +pub mod sbb; pub mod semantic; pub mod shl; pub mod shr; diff --git a/runtime/src/vm/handlers/mul.rs b/runtime/src/vm/handlers/mul.rs index 3294ba40..92876aba 100644 --- a/runtime/src/vm/handlers/mul.rs +++ b/runtime/src/vm/handlers/mul.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Compare, Condition, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, }, }; @@ -14,22 +14,22 @@ pub fn build(rt: &mut Runtime) { Expression::Operand(Operand::Input(0)), Expression::Operand(Operand::Input(1)), )], - flags: vec![ + flags: Flags::Always(vec![ ( Flag::Carry, - Condition::Compare(Compare::GreaterThan( + Expression::Compare(Box::new(Compare::GreaterThan( Expression::Operand(Operand::Output(1)), Expression::Constant(0), - )), + ))), ), ( Flag::Overflow, - Condition::Compare(Compare::GreaterThan( + Expression::Compare(Box::new(Compare::GreaterThan( Expression::Operand(Operand::Output(1)), Expression::Constant(0), - )), + ))), ), - ], + ]), stores: None, widths: &[ VMWidth::Lower64, diff --git a/runtime/src/vm/handlers/or.rs b/runtime/src/vm/handlers/or.rs index 8cbb48c7..6a5691e2 100644 --- a/runtime/src/vm/handlers/or.rs +++ b/runtime/src/vm/handlers/or.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Compare, Condition, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, }, }; @@ -14,40 +14,40 @@ pub fn build(rt: &mut Runtime) { Expression::Operand(Operand::Input(0)), Expression::Operand(Operand::Input(1)), )], - flags: vec![ + flags: Flags::Always(vec![ ( Flag::Carry, - Condition::Compare(Compare::Equal( + Expression::Compare(Box::new(Compare::Equal( Expression::Constant(1), Expression::Constant(0), - )), + ))), ), ( Flag::Overflow, - Condition::Compare(Compare::Equal( + Expression::Compare(Box::new(Compare::Equal( Expression::Constant(1), Expression::Constant(0), - )), + ))), ), ( Flag::Sign, - Condition::Compare(Compare::BitSet( + Expression::Compare(Box::new(Compare::BitSet( Expression::Operand(Operand::Output(0)), Expression::SignBit, - )), + ))), ), ( Flag::Zero, - Condition::Compare(Compare::Equal( + Expression::Compare(Box::new(Compare::Equal( Expression::Operand(Operand::Output(0)), Expression::Constant(0), - )), + ))), ), ( Flag::Parity, - Condition::Parity(Expression::Operand(Operand::Output(0))), + Expression::Parity(Box::new(Expression::Operand(Operand::Output(0)))), ), - ], + ]), stores: None, widths: &[ VMWidth::Lower64, diff --git a/runtime/src/vm/handlers/rol.rs b/runtime/src/vm/handlers/rol.rs index 4c4f2dbd..5f603c8b 100644 --- a/runtime/src/vm/handlers/rol.rs +++ b/runtime/src/vm/handlers/rol.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Compare, Condition, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, }, }; @@ -14,31 +14,33 @@ pub fn build(rt: &mut Runtime) { Expression::Operand(Operand::Input(0)), Expression::Operand(Operand::Input(1)), )], - flags: vec![ + flags: Flags::When( +Expression::BitAnd(Box::new(Expression::Operand(Operand::Input(1))), Box::new(Expression::BitOr(Box::new(Expression::SignBit), Box::new(Expression::Constant(0x1f))))), +vec![ ( Flag::Carry, - Condition::Compare(Compare::BitSet( - Expression::Operand(Operand::Output(0)), - Expression::Constant(0), - )), + Expression::Compare(Box::new(Compare::BitSet( + Expression::Operand(Operand::Output(0)), + Expression::Constant(0), + ))), ), ( Flag::Overflow, - Condition::Compare(Compare::GreaterThan( - Expression::BitXor( - Box::new(Expression::BitShr( - Box::new(Expression::Operand(Operand::Output(0))), - Box::new(Expression::SignBit), - )), - Box::new(Expression::BitAnd( - Box::new(Expression::Operand(Operand::Output(0))), - Box::new(Expression::Constant(1)), - )), - ), - Expression::Constant(0), - )), + Expression::Compare(Box::new(Compare::GreaterThan( + Expression::BitXor( + Box::new(Expression::BitShr( + Box::new(Expression::Operand(Operand::Output(0))), + Box::new(Expression::SignBit), + )), + Box::new(Expression::BitAnd( + Box::new(Expression::Operand(Operand::Output(0))), + Box::new(Expression::Constant(1)), + )), + ), + Expression::Constant(0), + ))), ), - ], + ]), stores: None, widths: &[ VMWidth::Lower64, diff --git a/runtime/src/vm/handlers/ror.rs b/runtime/src/vm/handlers/ror.rs index 15d75281..d4139052 100644 --- a/runtime/src/vm/handlers/ror.rs +++ b/runtime/src/vm/handlers/ror.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Compare, Condition, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, }, }; @@ -14,37 +14,39 @@ pub fn build(rt: &mut Runtime) { Expression::Operand(Operand::Input(0)), Expression::Operand(Operand::Input(1)), )], - flags: vec![ + flags: Flags::When( +Expression::BitAnd(Box::new(Expression::Operand(Operand::Input(1))), Box::new(Expression::BitOr(Box::new(Expression::SignBit), Box::new(Expression::Constant(0x1f))))), +vec![ ( Flag::Carry, - Condition::Compare(Compare::BitSet( - Expression::Operand(Operand::Output(0)), - Expression::SignBit, - )), + Expression::Compare(Box::new(Compare::BitSet( + Expression::Operand(Operand::Output(0)), + Expression::SignBit, + ))), ), ( Flag::Overflow, - Condition::Compare(Compare::GreaterThan( - Expression::BitXor( - Box::new(Expression::BitShr( - Box::new(Expression::Operand(Operand::Output(0))), - Box::new(Expression::SignBit), - )), - Box::new(Expression::BitAnd( + Expression::Compare(Box::new(Compare::GreaterThan( + Expression::BitXor( Box::new(Expression::BitShr( Box::new(Expression::Operand(Operand::Output(0))), - Box::new(Expression::Sub( - Box::new(Expression::SignBit), - Box::new(Expression::Constant(1)), + Box::new(Expression::SignBit), + )), + Box::new(Expression::BitAnd( + Box::new(Expression::BitShr( + Box::new(Expression::Operand(Operand::Output(0))), + Box::new(Expression::Sub( + Box::new(Expression::SignBit), + Box::new(Expression::Constant(1)), + )), )), + Box::new(Expression::Constant(1)), )), - Box::new(Expression::Constant(1)), - )), - ), - Expression::Constant(0), - )), + ), + Expression::Constant(0), + ))), ), - ], + ]), stores: None, widths: &[ VMWidth::Lower64, diff --git a/runtime/src/vm/handlers/sar.rs b/runtime/src/vm/handlers/sar.rs index 5916e330..8e33c30c 100644 --- a/runtime/src/vm/handlers/sar.rs +++ b/runtime/src/vm/handlers/sar.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Compare, Condition, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, }, }; @@ -14,27 +14,18 @@ pub fn build(rt: &mut Runtime) { Expression::Operand(Operand::Input(0)), Expression::Operand(Operand::Input(1)), )], - flags: vec![ + flags: Flags::When( +Expression::BitAnd(Box::new(Expression::Operand(Operand::Input(1))), Box::new(Expression::BitOr(Box::new(Expression::SignBit), Box::new(Expression::Constant(0x1f))))), +vec![ ( Flag::Carry, - Condition::Compare(Compare::BitSet( - Expression::Operand(Operand::Input(0)), - Expression::Sub( - Box::new(Expression::SignBit), - Box::new(Expression::BitAnd( - Box::new(Expression::Sub( - Box::new(Expression::BitSize), - Box::new(Expression::BitAnd( - Box::new(Expression::Operand(Operand::Input(1))), - Box::new(Expression::BitOr( - Box::new(Expression::SignBit), - Box::new(Expression::Constant(0x1f)), - )), - )), - )), - Box::new(Expression::BitShr( - Box::new(Expression::BitNot(Box::new(Expression::Constant(0)))), + Expression::Compare(Box::new(Compare::BitSet( + Expression::Operand(Operand::Input(0)), + Expression::Sub( + Box::new(Expression::SignBit), + Box::new(Expression::BitAnd( Box::new(Expression::Sub( + Box::new(Expression::BitSize), Box::new(Expression::BitAnd( Box::new(Expression::Operand(Operand::Input(1))), Box::new(Expression::BitOr( @@ -42,39 +33,54 @@ pub fn build(rt: &mut Runtime) { Box::new(Expression::Constant(0x1f)), )), )), - Box::new(Expression::Constant(1)), + )), + Box::new(Expression::BitShr( + Box::new(Expression::BitNot(Box::new( + Expression::Constant(0), + ))), + Box::new(Expression::Sub( + Box::new(Expression::BitAnd( + Box::new(Expression::Operand(Operand::Input(1))), + Box::new(Expression::BitOr( + Box::new(Expression::SignBit), + Box::new(Expression::Constant(0x1f)), + )), + )), + Box::new(Expression::Constant(1)), + )), )), )), - )), - ), - )), + ), + ))), ), ( Flag::Overflow, - Condition::Compare(Compare::Equal( - Expression::Constant(1), - Expression::Constant(0), - )), + Expression::Compare(Box::new(Compare::Equal( + Expression::Constant(1), + Expression::Constant(0), + ))), ), ( Flag::Sign, - Condition::Compare(Compare::BitSet( - Expression::Operand(Operand::Output(0)), - Expression::SignBit, - )), + Expression::Compare(Box::new(Compare::BitSet( + Expression::Operand(Operand::Output(0)), + Expression::SignBit, + ))), ), ( Flag::Zero, - Condition::Compare(Compare::Equal( - Expression::Operand(Operand::Output(0)), - Expression::Constant(0), - )), + Expression::Compare(Box::new(Compare::Equal( + Expression::Operand(Operand::Output(0)), + Expression::Constant(0), + ))), ), ( Flag::Parity, - Condition::Parity(Expression::Operand(Operand::Output(0))), + Expression::Parity(Box::new(Expression::Operand( + Operand::Output(0), + ))), ), - ], + ]), stores: None, widths: &[ VMWidth::Lower64, diff --git a/runtime/src/vm/handlers/sbb.rs b/runtime/src/vm/handlers/sbb.rs new file mode 100644 index 00000000..d6399207 --- /dev/null +++ b/runtime/src/vm/handlers/sbb.rs @@ -0,0 +1,102 @@ +use crate::{ + runtime::Runtime, + vm::{ + bytecode::{Flag, VMWidth}, + handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, + }, +}; + +pub fn build(rt: &mut Runtime) { + semantic::compiler::compile( + rt, + &Operation { + effects: vec![Effect::Sub( + Expression::Operand(Operand::Input(0)), + Expression::Sub( + Box::new(Expression::Sub( + Box::new(Expression::Operand(Operand::Input(1))), + Box::new(Expression::BitNot(Box::new(Expression::Flag(Flag::Carry)))), + )), + Box::new(Expression::Constant(1)), + ), + )], + flags: Flags::Always(vec![ + ( + Flag::Zero, + Expression::Compare(Box::new(Compare::Equal( + Expression::Operand(Operand::Output(0)), + Expression::Constant(0), + ))), + ), + ( + Flag::Sign, + Expression::Compare(Box::new(Compare::BitSet( + Expression::Operand(Operand::Output(0)), + Expression::SignBit, + ))), + ), + ( + Flag::Carry, + Expression::Compare(Box::new(Compare::BitSet( + Expression::BitOr( + Box::new(Expression::BitAnd( + Box::new(Expression::BitNot(Box::new(Expression::Operand( + Operand::Input(0), + )))), + Box::new(Expression::Operand(Operand::Input(1))), + )), + Box::new(Expression::BitAnd( + Box::new(Expression::BitNot(Box::new(Expression::BitXor( + Box::new(Expression::Operand(Operand::Input(0))), + Box::new(Expression::Operand(Operand::Input(1))), + )))), + Box::new(Expression::Operand(Operand::Output(0))), + )), + ), + Expression::SignBit, + ))), + ), + ( + Flag::Overflow, + Expression::Compare(Box::new(Compare::BitSet( + Expression::BitAnd( + Box::new(Expression::BitXor( + Box::new(Expression::Operand(Operand::Input(0))), + Box::new(Expression::Operand(Operand::Input(1))), + )), + Box::new(Expression::BitXor( + Box::new(Expression::Operand(Operand::Input(0))), + Box::new(Expression::Operand(Operand::Output(0))), + )), + ), + Expression::SignBit, + ))), + ), + ( + Flag::Parity, + Expression::Parity(Box::new(Expression::Operand(Operand::Output(0)))), + ), + ( + Flag::Auxiliary, + Expression::Compare(Box::new(Compare::BitSet( + Expression::BitXor( + Box::new(Expression::BitXor( + Box::new(Expression::Operand(Operand::Input(0))), + Box::new(Expression::Operand(Operand::Input(1))), + )), + Box::new(Expression::Operand(Operand::Output(0))), + ), + Expression::Constant(4), + ))), + ), + ]), + stores: None, + widths: &[ + VMWidth::Lower64, + VMWidth::Lower32, + VMWidth::Lower16, + VMWidth::Lower8, + ], + }, + ); +} diff --git a/runtime/src/vm/handlers/semantic/compiler.rs b/runtime/src/vm/handlers/semantic/compiler.rs index f2f49dce..543bab91 100644 --- a/runtime/src/vm/handlers/semantic/compiler.rs +++ b/runtime/src/vm/handlers/semantic/compiler.rs @@ -1,6 +1,6 @@ use iced_x86::code_asm::{ - ah, ax, cl, dl, dword_ptr, dx, get_gpr16, get_gpr32, get_gpr8, qword_ptr, r12, r13, r14, r14d, - rax, rbp, rcx, rdx, rsp, AsmRegister16, AsmRegister32, AsmRegister64, AsmRegister8, + ah, ax, cl, dl, dword_ptr, dx, get_gpr16, get_gpr32, get_gpr8, ptr, qword_ptr, r12, r13, r14, + r14d, rax, rbp, rcx, rdx, rsp, AsmRegister16, AsmRegister32, AsmRegister64, AsmRegister8, CodeAssembler, CodeLabel, }; @@ -10,8 +10,8 @@ use crate::{ vm::{ bytecode::{Flag, VMReg, VMWidth}, handlers::semantic::{ - allocator::Allocator, Compare, Condition, Effect, Expression, Operand, Operation, - Reference, Value, + allocator::Allocator, Compare, Effect, Expression, Flags, Operand, Operation, Reference, + Value, }, utils::{bytecode, scratch, vreg}, }, @@ -698,6 +698,27 @@ fn compile_expression( Expression::Operand(Operand::Input(index)) => Reference::Value(Value::Input(*index)), Expression::Operand(Operand::Output(index)) => Reference::Value(Value::Output(*index)), Expression::Constant(value) => Reference::Immediate(*value as i64), + Expression::Flag(flag) => { + let dst = allocator.acquire(rt, &[], &[]); + + vreg::load_reg(rt, r12, VMReg::Flags, dst); + + let bit = flag.bit32().trailing_zeros() as i32; + + if bit > 0 { + rt.asm.shr(dst, bit).unwrap(); + } + + rt.asm.and(dst, 0x1).unwrap(); + + let tmp = allocator.temporary(); + + allocator.replace(dst, tmp, true); + + Reference::Value(tmp) + } + Expression::Compare(compare) => compile_compare(rt, allocator, compare, width), + Expression::Parity(node) => compile_parity(rt, allocator, node, width), Expression::SignBit => Reference::Immediate(width.mask().ilog2() as i64), Expression::BitSize => Reference::Immediate((width.size() * 8) as i64), Expression::ByteMask(n) => { @@ -808,12 +829,7 @@ fn compile_expression( } } -fn compile_flags( - rt: &mut Runtime, - allocator: &mut Allocator, - flags: &[(Flag, Condition)], - width: VMWidth, -) { +fn compile_flags(rt: &mut Runtime, allocator: &mut Allocator, flags: &Flags, width: VMWidth) { let register = allocator.acquire(rt, &[], &[]); vreg::load_reg(rt, r12, VMReg::Flags, register); @@ -824,31 +840,89 @@ fn compile_flags( allocator.untrack(register); - for (flag, condition) in flags { - match condition { - Condition::Compare(item) => compile_comparison(rt, allocator, *flag, item, width), - Condition::Parity(node) => compile_parity(rt, allocator, *flag, node, width), - } + for (flag, expression) in flags.values() { + let value = compile_expression(rt, allocator, expression, width); + + let pinned = match value { + Reference::Value(value) => vec![value], + Reference::Immediate(_) => vec![], + }; + + let dst = allocator.acquire(rt, &pinned, &[]); + allocator.copy(rt, value, dst); + allocator.consume(value); + + let bit = flag.bit32().trailing_zeros() as i32; + let mask = flag.bit32(); + + rt.asm + .and(dword_ptr(rbp - allocator.offset(Value::Flags)), !mask) + .unwrap(); + rt.asm.shl(dst, bit).unwrap(); + rt.asm + .or(dword_ptr(rbp - allocator.offset(Value::Flags)), dst) + .unwrap(); + + allocator.spill(rt, dst); } - let register = allocator.acquire(rt, &[], &[]); + match flags.condition() { + None => { + let register = allocator.acquire(rt, &[], &[]); - rt.asm - .mov(register, qword_ptr(rbp - allocator.offset(Value::Flags))) - .unwrap(); + rt.asm + .mov(register, qword_ptr(rbp - allocator.offset(Value::Flags))) + .unwrap(); - vreg::store_reg(rt, r12, register, VMReg::Flags); + vreg::store_reg(rt, r12, register, VMReg::Flags); - allocator.untrack(register); + allocator.untrack(register); + } + // A conditional flag write stores back to the flag register only when + // the condition holds; otherwise the store is redirected at the local + // slot, so the pre-operation flags are left untouched (a shift or + // rotate with a zero count does not affect the flags). + Some(condition) => { + let condition = compile_expression(rt, allocator, condition, width); + + let pinned = match condition { + Reference::Value(value) => vec![value], + Reference::Immediate(_) => vec![], + }; + + let selector = allocator.acquire(rt, &pinned, &[]); + allocator.copy(rt, condition, selector); + allocator.consume(condition); + + let register = allocator.acquire(rt, &[], &[selector]); + + rt.asm + .mov(register, qword_ptr(rbp - allocator.offset(Value::Flags))) + .unwrap(); + + let offset = rt.mapper.index(VMReg::Flags) as i32 * 8; + + let target = allocator.acquire(rt, &[], &[selector, register]); + rt.asm.lea(target, ptr(r12 + offset)).unwrap(); + + let skip = allocator.acquire(rt, &[], &[selector, register, target]); + rt.asm + .lea(skip, ptr(rbp - allocator.offset(Value::Flags))) + .unwrap(); + + rt.asm.test(selector, selector).unwrap(); + rt.asm.cmovz(target, skip).unwrap(); + rt.asm.mov(ptr(target), register).unwrap(); + } + } } -fn compile_comparison( +fn compile_compare( rt: &mut Runtime, allocator: &mut Allocator, - flag: Flag, compare: &Compare, width: VMWidth, -) { +) -> Reference { let (first, second) = match compare { Compare::Equal(first, second) | Compare::LessThan(first, second) @@ -900,19 +974,6 @@ fn compile_comparison( rt.asm.movzx(tmp, byte).unwrap(); - let bit = flag.bit32().trailing_zeros() as i32; - - let mask = flag.bit32(); - - rt.asm - .and(dword_ptr(rbp - allocator.offset(Value::Flags)), !mask) - .unwrap(); - rt.asm.shl(tmp, bit).unwrap(); - rt.asm - .or(dword_ptr(rbp - allocator.offset(Value::Flags)), tmp) - .unwrap(); - - allocator.spill(rt, tmp); allocator.consume(first); allocator.consume(second); allocator.spill(rt, dst); @@ -920,15 +981,19 @@ fn compile_comparison( if src != dst { allocator.spill(rt, src); } + + let value = allocator.temporary(); + allocator.replace(tmp, value, true); + + Reference::Value(value) } fn compile_parity( rt: &mut Runtime, allocator: &mut Allocator, - flag: Flag, node: &Expression, width: VMWidth, -) { +) -> Reference { let src = compile_expression(rt, allocator, node, width); let dst = allocator.acquire(rt, &[], &[]); @@ -940,17 +1005,10 @@ fn compile_parity( rt.asm.not(dst).unwrap(); rt.asm.and(dst, 0x1).unwrap(); - let shift = flag.bit32().trailing_zeros() as i32; - let mask = 1u32 << shift; + allocator.consume(src); - rt.asm - .and(dword_ptr(rbp - allocator.offset(Value::Flags)), !mask) - .unwrap(); - rt.asm.shl(dst, shift).unwrap(); - rt.asm - .or(dword_ptr(rbp - allocator.offset(Value::Flags)), dst) - .unwrap(); + let value = allocator.temporary(); + allocator.replace(dst, value, true); - allocator.untrack(dst); - allocator.consume(src); + Reference::Value(value) } diff --git a/runtime/src/vm/handlers/semantic/mod.rs b/runtime/src/vm/handlers/semantic/mod.rs index a2e4eb46..690fb2b1 100644 --- a/runtime/src/vm/handlers/semantic/mod.rs +++ b/runtime/src/vm/handlers/semantic/mod.rs @@ -21,6 +21,9 @@ pub enum Expression { BitNot(Box), Sub(Box, Box), LowByte(Box), + Compare(Box), + Parity(Box), + Flag(Flag), SignBit, BitSize, ByteMask(usize), @@ -54,15 +57,15 @@ pub enum Compare { } #[derive(Debug, Clone)] -pub enum Condition { - Compare(Compare), - Parity(Expression), +pub enum Flags { + Always(Vec<(Flag, Expression)>), + When(Expression, Vec<(Flag, Expression)>), } #[derive(Debug)] pub struct Operation { pub effects: Vec, - pub flags: Vec<(Flag, Condition)>, + pub flags: Flags, pub stores: Option>, pub widths: &'static [VMWidth], } @@ -91,9 +94,7 @@ impl Operation { effect.operands(&mut value); } - for (_, condition) in &self.flags { - condition.operands(&mut value); - } + self.flags.operands(&mut value); if let Some(stores) = &self.stores { for store in stores { @@ -120,11 +121,7 @@ impl Operation { fn temporaries(&self) -> usize { let effects = self.effects.iter().map(Effect::temporary).sum::(); - let flags = self - .flags - .iter() - .map(|(_, condition)| condition.temporary()) - .sum::(); + let flags = self.flags.temporary(); let stores = self .stores .as_ref() @@ -135,6 +132,46 @@ impl Operation { } } +impl Flags { + fn values(&self) -> &[(Flag, Expression)] { + match self { + Flags::Always(values) | Flags::When(_, values) => values, + } + } + + fn condition(&self) -> Option<&Expression> { + match self { + Flags::Always(_) => None, + Flags::When(condition, _) => Some(condition), + } + } + + fn is_empty(&self) -> bool { + self.values().is_empty() + } + + fn operands(&self, value: &mut usize) { + if let Some(condition) = self.condition() { + condition.operands(value); + } + + for (_, expression) in self.values() { + expression.operands(value); + } + } + + fn temporary(&self) -> usize { + let condition = self.condition().map(Expression::temporary).unwrap_or(0); + let values = self + .values() + .iter() + .map(|(_, expression)| expression.temporary()) + .sum::(); + + condition + values + } +} + impl Expression { fn operands(&self, value: &mut usize) { match self { @@ -143,6 +180,7 @@ impl Expression { } Expression::Operand(Operand::Output(_)) | Expression::Constant(_) + | Expression::Flag(_) | Expression::SignBit | Expression::BitSize | Expression::ByteMask(_) => {} @@ -155,9 +193,10 @@ impl Expression { lhs.operands(value); rhs.operands(value); } - Expression::BitNot(inner) | Expression::LowByte(inner) => { + Expression::BitNot(inner) | Expression::LowByte(inner) | Expression::Parity(inner) => { inner.operands(value); } + Expression::Compare(compare) => compare.operands(value), } } @@ -168,13 +207,17 @@ impl Expression { | Expression::SignBit | Expression::BitSize | Expression::ByteMask(_) => 0, + Expression::Flag(_) => 1, Expression::BitAnd(lhs, rhs) | Expression::BitOr(lhs, rhs) | Expression::BitXor(lhs, rhs) | Expression::BitShr(lhs, rhs) | Expression::BitShl(lhs, rhs) | Expression::Sub(lhs, rhs) => 1 + lhs.temporary() + rhs.temporary(), - Expression::BitNot(inner) | Expression::LowByte(inner) => 1 + inner.temporary(), + Expression::BitNot(inner) | Expression::LowByte(inner) | Expression::Parity(inner) => { + 1 + inner.temporary() + } + Expression::Compare(compare) => 1 + compare.temporary(), } } } @@ -253,22 +296,6 @@ impl Effect { } } -impl Condition { - fn operands(&self, value: &mut usize) { - match self { - Condition::Compare(compare) => compare.operands(value), - Condition::Parity(expression) => expression.operands(value), - } - } - - fn temporary(&self) -> usize { - match self { - Condition::Compare(compare) => compare.temporary(), - Condition::Parity(expression) => expression.temporary(), - } - } -} - impl Compare { fn operands(&self, value: &mut usize) { match self { diff --git a/runtime/src/vm/handlers/shl.rs b/runtime/src/vm/handlers/shl.rs index 0ed47871..841de8bb 100644 --- a/runtime/src/vm/handlers/shl.rs +++ b/runtime/src/vm/handlers/shl.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Compare, Condition, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, }, }; @@ -14,58 +14,62 @@ pub fn build(rt: &mut Runtime) { Expression::Operand(Operand::Input(0)), Expression::Operand(Operand::Input(1)), )], - flags: vec![ + flags: Flags::When( +Expression::BitAnd(Box::new(Expression::Operand(Operand::Input(1))), Box::new(Expression::BitOr(Box::new(Expression::SignBit), Box::new(Expression::Constant(0x1f))))), +vec![ ( Flag::Carry, - Condition::Compare(Compare::BitSet( - Expression::Operand(Operand::Input(0)), - Expression::Sub( - Box::new(Expression::BitSize), - Box::new(Expression::BitAnd( - Box::new(Expression::Operand(Operand::Input(1))), - Box::new(Expression::BitOr( - Box::new(Expression::SignBit), - Box::new(Expression::Constant(0x1f)), + Expression::Compare(Box::new(Compare::BitSet( + Expression::Operand(Operand::Input(0)), + Expression::Sub( + Box::new(Expression::BitSize), + Box::new(Expression::BitAnd( + Box::new(Expression::Operand(Operand::Input(1))), + Box::new(Expression::BitOr( + Box::new(Expression::SignBit), + Box::new(Expression::Constant(0x1f)), + )), )), - )), - ), - )), + ), + ))), ), ( Flag::Overflow, - Condition::Compare(Compare::GreaterThan( - Expression::BitXor( - Box::new(Expression::BitShr( - Box::new(Expression::Operand(Operand::Input(0))), - Box::new(Expression::SignBit), - )), - Box::new(Expression::BitShr( - Box::new(Expression::Operand(Operand::Output(0))), - Box::new(Expression::SignBit), - )), - ), - Expression::Constant(0), - )), + Expression::Compare(Box::new(Compare::GreaterThan( + Expression::BitXor( + Box::new(Expression::BitShr( + Box::new(Expression::Operand(Operand::Input(0))), + Box::new(Expression::SignBit), + )), + Box::new(Expression::BitShr( + Box::new(Expression::Operand(Operand::Output(0))), + Box::new(Expression::SignBit), + )), + ), + Expression::Constant(0), + ))), ), ( Flag::Sign, - Condition::Compare(Compare::BitSet( - Expression::Operand(Operand::Output(0)), - Expression::SignBit, - )), + Expression::Compare(Box::new(Compare::BitSet( + Expression::Operand(Operand::Output(0)), + Expression::SignBit, + ))), ), ( Flag::Zero, - Condition::Compare(Compare::Equal( - Expression::Operand(Operand::Output(0)), - Expression::Constant(0), - )), + Expression::Compare(Box::new(Compare::Equal( + Expression::Operand(Operand::Output(0)), + Expression::Constant(0), + ))), ), ( Flag::Parity, - Condition::Parity(Expression::Operand(Operand::Output(0))), + Expression::Parity(Box::new(Expression::Operand( + Operand::Output(0), + ))), ), - ], + ]), stores: None, widths: &[ VMWidth::Lower64, diff --git a/runtime/src/vm/handlers/shr.rs b/runtime/src/vm/handlers/shr.rs index bc425ce4..c6b57ecf 100644 --- a/runtime/src/vm/handlers/shr.rs +++ b/runtime/src/vm/handlers/shr.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Compare, Condition, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, }, }; @@ -14,49 +14,53 @@ pub fn build(rt: &mut Runtime) { Expression::Operand(Operand::Input(0)), Expression::Operand(Operand::Input(1)), )], - flags: vec![ + flags: Flags::When( +Expression::BitAnd(Box::new(Expression::Operand(Operand::Input(1))), Box::new(Expression::BitOr(Box::new(Expression::SignBit), Box::new(Expression::Constant(0x1f))))), +vec![ ( Flag::Carry, - Condition::Compare(Compare::BitSet( - Expression::Operand(Operand::Input(0)), - Expression::Sub( - Box::new(Expression::BitAnd( - Box::new(Expression::Operand(Operand::Input(1))), - Box::new(Expression::BitOr( - Box::new(Expression::SignBit), - Box::new(Expression::Constant(0x1f)), + Expression::Compare(Box::new(Compare::BitSet( + Expression::Operand(Operand::Input(0)), + Expression::Sub( + Box::new(Expression::BitAnd( + Box::new(Expression::Operand(Operand::Input(1))), + Box::new(Expression::BitOr( + Box::new(Expression::SignBit), + Box::new(Expression::Constant(0x1f)), + )), )), - )), - Box::new(Expression::Constant(1)), - ), - )), + Box::new(Expression::Constant(1)), + ), + ))), ), ( Flag::Overflow, - Condition::Compare(Compare::BitSet( - Expression::Operand(Operand::Input(0)), - Expression::SignBit, - )), + Expression::Compare(Box::new(Compare::BitSet( + Expression::Operand(Operand::Input(0)), + Expression::SignBit, + ))), ), ( Flag::Sign, - Condition::Compare(Compare::BitSet( - Expression::Operand(Operand::Output(0)), - Expression::SignBit, - )), + Expression::Compare(Box::new(Compare::BitSet( + Expression::Operand(Operand::Output(0)), + Expression::SignBit, + ))), ), ( Flag::Zero, - Condition::Compare(Compare::Equal( - Expression::Operand(Operand::Output(0)), - Expression::Constant(0), - )), + Expression::Compare(Box::new(Compare::Equal( + Expression::Operand(Operand::Output(0)), + Expression::Constant(0), + ))), ), ( Flag::Parity, - Condition::Parity(Expression::Operand(Operand::Output(0))), + Expression::Parity(Box::new(Expression::Operand( + Operand::Output(0), + ))), ), - ], + ]), stores: None, widths: &[ VMWidth::Lower64, diff --git a/runtime/src/vm/handlers/sub.rs b/runtime/src/vm/handlers/sub.rs index 442f8df3..543bddec 100644 --- a/runtime/src/vm/handlers/sub.rs +++ b/runtime/src/vm/handlers/sub.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Compare, Condition, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, }, }; @@ -14,31 +14,31 @@ pub fn build(rt: &mut Runtime) { Expression::Operand(Operand::Input(0)), Expression::Operand(Operand::Input(1)), )], - flags: vec![ + flags: Flags::Always(vec![ ( Flag::Zero, - Condition::Compare(Compare::Equal( + Expression::Compare(Box::new(Compare::Equal( Expression::Operand(Operand::Output(0)), Expression::Constant(0), - )), + ))), ), ( Flag::Sign, - Condition::Compare(Compare::BitSet( + Expression::Compare(Box::new(Compare::BitSet( Expression::Operand(Operand::Output(0)), Expression::SignBit, - )), + ))), ), ( Flag::Carry, - Condition::Compare(Compare::LessThan( + Expression::Compare(Box::new(Compare::LessThan( Expression::Operand(Operand::Input(0)), Expression::Operand(Operand::Input(1)), - )), + ))), ), ( Flag::Overflow, - Condition::Compare(Compare::BitSet( + Expression::Compare(Box::new(Compare::BitSet( Expression::BitAnd( Box::new(Expression::BitXor( Box::new(Expression::Operand(Operand::Input(0))), @@ -50,15 +50,15 @@ pub fn build(rt: &mut Runtime) { )), ), Expression::SignBit, - )), + ))), ), ( Flag::Parity, - Condition::Parity(Expression::Operand(Operand::Output(0))), + Expression::Parity(Box::new(Expression::Operand(Operand::Output(0)))), ), ( Flag::Auxiliary, - Condition::Compare(Compare::BitSet( + Expression::Compare(Box::new(Compare::BitSet( Expression::BitXor( Box::new(Expression::BitXor( Box::new(Expression::Operand(Operand::Input(0))), @@ -67,9 +67,9 @@ pub fn build(rt: &mut Runtime) { Box::new(Expression::Operand(Operand::Output(0))), ), Expression::Constant(4), - )), + ))), ), - ], + ]), stores: None, widths: &[ VMWidth::Lower64, diff --git a/runtime/src/vm/handlers/trailing_zeros.rs b/runtime/src/vm/handlers/trailing_zeros.rs index 75045fab..d1ce4176 100644 --- a/runtime/src/vm/handlers/trailing_zeros.rs +++ b/runtime/src/vm/handlers/trailing_zeros.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Compare, Condition, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, }, }; @@ -11,22 +11,22 @@ pub fn build(rt: &mut Runtime) { rt, &Operation { effects: vec![Effect::Tzcnt(Expression::Operand(Operand::Input(0)))], - flags: vec![ + flags: Flags::Always(vec![ ( Flag::Carry, - Condition::Compare(Compare::Equal( + Expression::Compare(Box::new(Compare::Equal( Expression::Operand(Operand::Input(0)), Expression::Constant(0), - )), + ))), ), ( Flag::Zero, - Condition::Compare(Compare::Equal( + Expression::Compare(Box::new(Compare::Equal( Expression::Operand(Operand::Output(0)), Expression::Constant(0), - )), + ))), ), - ], + ]), stores: None, widths: &[VMWidth::Lower64, VMWidth::Lower32, VMWidth::Lower16], }, diff --git a/runtime/src/vm/handlers/xor.rs b/runtime/src/vm/handlers/xor.rs index 69393005..22ad4247 100644 --- a/runtime/src/vm/handlers/xor.rs +++ b/runtime/src/vm/handlers/xor.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Compare, Condition, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, }, }; @@ -14,40 +14,40 @@ pub fn build(rt: &mut Runtime) { Expression::Operand(Operand::Input(0)), Expression::Operand(Operand::Input(1)), )], - flags: vec![ + flags: Flags::Always(vec![ ( Flag::Carry, - Condition::Compare(Compare::Equal( + Expression::Compare(Box::new(Compare::Equal( Expression::Constant(1), Expression::Constant(0), - )), + ))), ), ( Flag::Overflow, - Condition::Compare(Compare::Equal( + Expression::Compare(Box::new(Compare::Equal( Expression::Constant(1), Expression::Constant(0), - )), + ))), ), ( Flag::Sign, - Condition::Compare(Compare::BitSet( + Expression::Compare(Box::new(Compare::BitSet( Expression::Operand(Operand::Output(0)), Expression::SignBit, - )), + ))), ), ( Flag::Zero, - Condition::Compare(Compare::Equal( + Expression::Compare(Box::new(Compare::Equal( Expression::Operand(Operand::Output(0)), Expression::Constant(0), - )), + ))), ), ( Flag::Parity, - Condition::Parity(Expression::Operand(Operand::Output(0))), + Expression::Parity(Box::new(Expression::Operand(Operand::Output(0)))), ), - ], + ]), stores: None, widths: &[ VMWidth::Lower64, diff --git a/runtime/src/vm/lifters/arithmetic.rs b/runtime/src/vm/lifters/arithmetic.rs index 7d70ff8c..6fe25eab 100644 --- a/runtime/src/vm/lifters/arithmetic.rs +++ b/runtime/src/vm/lifters/arithmetic.rs @@ -1,15 +1,15 @@ use iced_x86::{Instruction, Mnemonic, OpKind}; -use crate::vm::bytecode::{Flag, VMMem, VMPrecision, VMReg, VMVec, VMWidth}; +use crate::vm::bytecode::{VMMem, VMPrecision, VMReg, VMVec, VMWidth}; use crate::vm::encoders::vector_div::VectorDiv; use crate::vm::encoders::vector_mul::VectorMul; use crate::vm::encoders::{ - add::Add, and::And, discard::Discard, load_address::LoadAddress, load_immediate::LoadImmediate, - load_memory::LoadMemory, load_register::LoadRegister, load_vector::LoadVector, or::Or, - rol::Rol, ror::Ror, sar::Sar, shl::Shl, shr::Shr, store_memory::StoreMemory, - store_merge::StoreMerge, store_register::StoreRegister, sub::Sub, vector_add::VectorAdd, - vector_and::VectorAnd, vector_and_not::VectorAndNot, vector_or::VectorOr, - vector_sub::VectorSub, vector_xor::VectorXor, xor::Xor, Encode, + adc::Adc, add::Add, and::And, discard::Discard, load_address::LoadAddress, + load_immediate::LoadImmediate, load_memory::LoadMemory, load_register::LoadRegister, + load_vector::LoadVector, or::Or, rol::Rol, ror::Ror, sar::Sar, sbb::Sbb, shl::Shl, shr::Shr, + store_memory::StoreMemory, store_merge::StoreMerge, store_register::StoreRegister, sub::Sub, + vector_add::VectorAdd, vector_and::VectorAnd, vector_and_not::VectorAndNot, + vector_or::VectorOr, vector_sub::VectorSub, vector_xor::VectorXor, xor::Xor, Encode, }; use crate::vm::lifters::{operation_width, source}; @@ -22,8 +22,8 @@ pub fn encode(instruction: &Instruction) -> Option>> { match instruction.mnemonic() { Mnemonic::Add => binary(instruction, |width| Add { width }, Tail::Writeback), Mnemonic::Sub => binary(instruction, |width| Sub { width }, Tail::Writeback), - Mnemonic::Adc => carry(instruction, |width| Add { width }), - Mnemonic::Sbb => carry(instruction, |width| Sub { width }), + Mnemonic::Adc => binary(instruction, |width| Adc { width }, Tail::Writeback), + Mnemonic::Sbb => binary(instruction, |width| Sbb { width }, Tail::Writeback), Mnemonic::Shl => binary(instruction, |width| Shl { width }, Tail::Writeback), Mnemonic::Shr => binary(instruction, |width| Shr { width }, Tail::Writeback), Mnemonic::Sar => binary(instruction, |width| Sar { width }, Tail::Writeback), @@ -191,53 +191,6 @@ pub fn binary( Some(operations) } -// TODO: Create a handler for SBB & ADC -pub fn carry( - instruction: &Instruction, - make: impl Fn(VMWidth) -> O, -) -> Option>> { - let mut operations = Vec::>::new(); - - let width = operation_width(instruction, 0); - - source(&mut operations, instruction, 0, width); - source(&mut operations, instruction, 1, width); - - operations.push(Box::new(LoadRegister { - width: VMWidth::Lower64, - source: VMReg::Flags, - })); - operations.push(Box::new(LoadImmediate { - width: VMWidth::Lower64, - source: Flag::Carry.bit64().to_le_bytes().to_vec(), - })); - operations.push(Box::new(And { - width: VMWidth::Lower64, - })); - - operations.push(Box::new(Add { width })); - - operations.push(Box::new(make(width))); - - match instruction.op0_kind() { - OpKind::Register => { - operations.push(Box::new(StoreRegister { - width, - destination: VMReg::from(instruction.op0_register()), - })); - } - OpKind::Memory => { - operations.push(Box::new(LoadAddress { - source: VMMem::from(instruction), - })); - operations.push(Box::new(StoreMemory { width })); - } - _ => unreachable!(), - }; - - Some(operations) -} - fn unary( instruction: &Instruction, immediate: u64, diff --git a/runtime/src/vm/lifters/branch.rs b/runtime/src/vm/lifters/branch.rs index 8529f596..9efa7036 100644 --- a/runtime/src/vm/lifters/branch.rs +++ b/runtime/src/vm/lifters/branch.rs @@ -1,6 +1,6 @@ use iced_x86::{Code, Instruction, OpKind}; -use crate::vm::bytecode::{VMCondition, Flag, VMLogic, VMMem, VMReg, VMSeg, VMWidth}; +use crate::vm::bytecode::{Flag, VMCondition, VMLogic, VMMem, VMReg, VMSeg, VMWidth}; use crate::vm::encoders::load_address::LoadAddress; use crate::vm::encoders::load_immediate::LoadImmediate; use crate::vm::encoders::load_memory::LoadMemory; diff --git a/runtime/src/vm/lifters/bt.rs b/runtime/src/vm/lifters/bt.rs index 03163b6b..234a691f 100644 --- a/runtime/src/vm/lifters/bt.rs +++ b/runtime/src/vm/lifters/bt.rs @@ -1,6 +1,5 @@ use iced_x86::{Instruction, Mnemonic}; - use crate::vm::encoders::{ bit_test::BitTest, bit_test_complement::BitTestComplement, bit_test_reset::BitTestReset, bit_test_set::BitTestSet, Encode, diff --git a/runtime/src/vm/lifters/cmpxchg.rs b/runtime/src/vm/lifters/cmpxchg.rs index a07d0a6f..89be758f 100644 --- a/runtime/src/vm/lifters/cmpxchg.rs +++ b/runtime/src/vm/lifters/cmpxchg.rs @@ -1,6 +1,6 @@ use iced_x86::{Instruction, OpKind}; -use crate::vm::bytecode::{VMCondition, Flag, VMLogic, VMMem, VMReg}; +use crate::vm::bytecode::{Flag, VMCondition, VMLogic, VMMem, VMReg}; use crate::vm::encoders::block::Block; use crate::vm::encoders::{ compare_exchange::CompareExchange, discard::Discard, load_address::LoadAddress, diff --git a/runtime/src/vm/lifters/lea.rs b/runtime/src/vm/lifters/lea.rs index 92841346..33acb595 100644 --- a/runtime/src/vm/lifters/lea.rs +++ b/runtime/src/vm/lifters/lea.rs @@ -1,6 +1,5 @@ use iced_x86::{Code, Instruction}; - use crate::vm::bytecode::{VMMem, VMReg, VMWidth}; use crate::vm::encoders::{load_address::LoadAddress, store_register::StoreRegister, Encode}; diff --git a/runtime/src/vm/lifters/pcmpeqb.rs b/runtime/src/vm/lifters/pcmpeqb.rs index f3c5e617..98fd9204 100644 --- a/runtime/src/vm/lifters/pcmpeqb.rs +++ b/runtime/src/vm/lifters/pcmpeqb.rs @@ -1,6 +1,5 @@ use iced_x86::{Instruction, OpKind}; - use crate::vm::bytecode::{VMMem, VMVec, VMWidth}; use crate::vm::encoders::{ load_address::LoadAddress, load_memory::LoadMemory, load_vector::LoadVector, diff --git a/runtime/src/vm/lifters/pmovskb.rs b/runtime/src/vm/lifters/pmovskb.rs index 247d4320..51f05cf7 100644 --- a/runtime/src/vm/lifters/pmovskb.rs +++ b/runtime/src/vm/lifters/pmovskb.rs @@ -1,6 +1,5 @@ use iced_x86::Instruction; - use crate::vm::bytecode::{VMReg, VMVec, VMWidth}; use crate::vm::encoders::{ load_vector::LoadVector, packed_byte_mask::PackedByteMask, store_register::StoreRegister, diff --git a/runtime/src/vm/lifters/transfer.rs b/runtime/src/vm/lifters/transfer.rs index 03ceca86..35ba1010 100644 --- a/runtime/src/vm/lifters/transfer.rs +++ b/runtime/src/vm/lifters/transfer.rs @@ -1,6 +1,5 @@ use iced_x86::{Instruction, OpKind}; - use crate::vm::bytecode::{VMMem, VMReg, VMVec, VMWidth}; use crate::vm::encoders::{ load_address::LoadAddress, load_immediate::LoadImmediate, load_memory::LoadMemory, diff --git a/runtime/src/vm/lifters/tzcnt.rs b/runtime/src/vm/lifters/tzcnt.rs index 4b1f94e6..fbfec781 100644 --- a/runtime/src/vm/lifters/tzcnt.rs +++ b/runtime/src/vm/lifters/tzcnt.rs @@ -1,6 +1,5 @@ use iced_x86::Instruction; - use crate::vm::encoders::{trailing_zeros::TrailingZeros, Encode}; use crate::vm::lifters::arithmetic::{self, Tail}; diff --git a/runtime/src/vm/lifters/xadd.rs b/runtime/src/vm/lifters/xadd.rs index 9739ea6a..1c0b1097 100644 --- a/runtime/src/vm/lifters/xadd.rs +++ b/runtime/src/vm/lifters/xadd.rs @@ -1,6 +1,5 @@ use iced_x86::{Instruction, OpKind}; - use crate::vm::bytecode::{VMMem, VMReg}; use crate::vm::encoders::{ add::Add, exchange_add::ExchangeAdd, load_address::LoadAddress, load_register::LoadRegister, diff --git a/runtime/src/vm/lifters/xchg.rs b/runtime/src/vm/lifters/xchg.rs index 1341b47c..8589fdf1 100644 --- a/runtime/src/vm/lifters/xchg.rs +++ b/runtime/src/vm/lifters/xchg.rs @@ -1,6 +1,5 @@ use iced_x86::{Instruction, OpKind, Register}; - use crate::vm::bytecode::{VMMem, VMReg, VMWidth}; use crate::vm::encoders::{ exchange::Exchange, load_address::LoadAddress, load_register::LoadRegister, diff --git a/runtime/src/vm/utils/vvec.rs b/runtime/src/vm/utils/vvec.rs index 8671d615..1940a3be 100644 --- a/runtime/src/vm/utils/vvec.rs +++ b/runtime/src/vm/utils/vvec.rs @@ -1,25 +1,25 @@ use crate::{runtime::Runtime, vm::bytecode::VMVec}; use iced_x86::code_asm::{ptr, AsmRegister64, AsmRegisterXmm, AsmRegisterYmm}; -pub fn load_128(rt: &mut Runtime, base: AsmRegister64, src: VMVec, dst: AsmRegisterXmm) { +pub fn load_128(rt: &mut Runtime, base: AsmRegister64, src: VMVec, dst: AsmRegisterXmm) { rt.asm .movups(dst, ptr(base + rt.mapper.index(src) as i32 * 32)) .unwrap(); } -pub fn store_128(rt: &mut Runtime, base: AsmRegister64, src: AsmRegisterXmm, dst: VMVec) { +pub fn store_128(rt: &mut Runtime, base: AsmRegister64, src: AsmRegisterXmm, dst: VMVec) { rt.asm .movups(ptr(base + rt.mapper.index(dst) as i32 * 32), src) .unwrap(); } -pub fn load_256(rt: &mut Runtime, base: AsmRegister64, src: VMVec, dst: AsmRegisterYmm) { +pub fn load_256(rt: &mut Runtime, base: AsmRegister64, src: VMVec, dst: AsmRegisterYmm) { rt.asm .vmovups(dst, ptr(base + rt.mapper.index(src) as i32 * 32)) .unwrap(); } -pub fn store_256(rt: &mut Runtime, base: AsmRegister64, src: AsmRegisterYmm, dst: VMVec) { +pub fn store_256(rt: &mut Runtime, base: AsmRegister64, src: AsmRegisterYmm, dst: VMVec) { rt.asm .vmovups(ptr(base + rt.mapper.index(dst) as i32 * 32), src) .unwrap(); From 661166cf87bad0f530e44b105297f5584abeb877 Mon Sep 17 00:00:00 2001 From: RadonCoding <86915746+RadonCoding@users.noreply.github.com> Date: Sat, 26 Sep 2026 20:29:16 +0300 Subject: [PATCH 14/33] fix: correct discard handler --- example.ps1 | 72 ++++++++++++++----- obfuscator/src/engine.rs | 1 + runtime/src/debug.rs | 39 +--------- runtime/src/vm/bytecode.rs | 14 ++-- runtime/src/vm/functions/dispatch.rs | 40 +++++++---- runtime/src/vm/functions/entry.rs | 4 +- runtime/src/vm/handlers/adc.rs | 2 +- runtime/src/vm/handlers/add.rs | 2 +- runtime/src/vm/handlers/and.rs | 2 +- runtime/src/vm/handlers/bit_scan_reverse.rs | 2 +- runtime/src/vm/handlers/bit_test.rs | 2 +- .../src/vm/handlers/bit_test_complement.rs | 2 +- runtime/src/vm/handlers/bit_test_reset.rs | 2 +- runtime/src/vm/handlers/bit_test_set.rs | 2 +- runtime/src/vm/handlers/byte_swap.rs | 2 +- runtime/src/vm/handlers/dispatch.rs | 27 +++++++ runtime/src/vm/handlers/div.rs | 2 +- runtime/src/vm/handlers/mul.rs | 2 +- runtime/src/vm/handlers/or.rs | 2 +- runtime/src/vm/handlers/rol.rs | 31 ++++---- runtime/src/vm/handlers/ror.rs | 31 ++++---- runtime/src/vm/handlers/sar.rs | 57 ++++++++------- runtime/src/vm/handlers/sbb.rs | 2 +- runtime/src/vm/handlers/semantic/compiler.rs | 16 ++--- runtime/src/vm/handlers/shl.rs | 57 ++++++++------- runtime/src/vm/handlers/shr.rs | 57 ++++++++------- runtime/src/vm/handlers/sub.rs | 2 +- runtime/src/vm/handlers/trailing_zeros.rs | 2 +- runtime/src/vm/handlers/xor.rs | 2 +- runtime/src/vm/transform/encrypt.rs | 26 +++---- runtime/src/vm/transform/mod.rs | 4 +- tests/src/instructions.rs | 59 +++++++++++---- 32 files changed, 335 insertions(+), 232 deletions(-) diff --git a/example.ps1 b/example.ps1 index 09190c2c..117efc67 100644 --- a/example.ps1 +++ b/example.ps1 @@ -1,51 +1,87 @@ param( [Parameter(Mandatory)] - [string]$Example + [System.IO.DirectoryInfo]$Example, + [string[]]$Args ) $ErrorActionPreference = "Stop" -$language = $Example.Split("/")[1] +$language = [System.IO.Path]::GetFileName([System.IO.Path]::GetDirectoryName([System.IO.Path]::GetDirectoryName($Example))) -$env:OUTPUT_DIRECTORY = "$PWD/api/generated" -$env:TEMPLATES_DIRECTORY = "$PWD/api/templates" +$env:OUTPUT_DIRECTORY = Join-Path $PWD "api/generated" +$env:TEMPLATES_DIRECTORY = Join-Path $PWD "api/templates" cargo build --package markers if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } -cargo build --bin obfuscator +cargo build --bin obfuscator $Args if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } switch ($language) { -"rust" { - cargo build --manifest-path "$Example/Cargo.toml" + "rust" { + $manifest = Join-Path $Example.FullName "Cargo.toml" + + cargo build --manifest-path $manifest if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - $metadata = cargo metadata --manifest-path "$Example/Cargo.toml" --format-version 1 --no-deps | ConvertFrom-Json + $metadata = cargo metadata ` + --manifest-path $manifest ` + --format-version 1 ` + --no-deps | ConvertFrom-Json + + $manifest = [System.IO.Path]::GetFullPath($manifest) + + $package = $metadata.packages | + Where-Object { + [System.IO.Path]::GetFullPath($_.manifest_path) -eq $manifest + } | + Select-Object -First 1 - $manifest = (Resolve-Path "$Example/Cargo.toml").Path - $package = $metadata.packages | Where-Object { (Resolve-Path $_.manifest_path).Path -eq $manifest } - $target = $package.targets | Where-Object { $_.kind -contains "bin" } | Select-Object -First 1 + $target = $package.targets | + Where-Object { $_.kind -contains "bin" } | + Select-Object -First 1 - $source = Join-Path $metadata.target_directory "debug\$($target.name).exe" + $source = Join-Path ` + $metadata.target_directory ` + "debug\$($target.name).exe" } + "cpp" { - g++ "$Example/main.cpp" -o "$Example.exe" - + $main = Join-Path $Example.FullName "main.cpp" + + $source = Join-Path ` + $example.Parent.Parent.FullName ` + "$($example.Name).exe" + + g++ $main -o $source + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + } - $source = Join-Path $PWD "$Example.exe" + default { + throw "Unsupported language: $language" } } -& "target/debug/obfuscator.exe" --virtualization $source +$source = [System.IO.Path]::GetFullPath($source) + +if (-not (Test-Path -LiteralPath $source -PathType Leaf)) { + throw "Source executable not found: $source" +} + +& (Join-Path $PWD "target/debug/obfuscator.exe") ` + --virtualization -v` +$source if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } -$destination = [System.IO.Path]::ChangeExtension($source, ".protected.exe") +$destination = [System.IO.Path]::ChangeExtension( + $source, + ".protected.exe" +) & $destination @@ -54,4 +90,4 @@ $code = $LASTEXITCODE Remove-Item $source -Force -ErrorAction SilentlyContinue Remove-Item $destination -Force -ErrorAction SilentlyContinue -exit $code \ No newline at end of file +exit $code diff --git a/obfuscator/src/engine.rs b/obfuscator/src/engine.rs index 35a06ba1..f905452d 100644 --- a/obfuscator/src/engine.rs +++ b/obfuscator/src/engine.rs @@ -191,6 +191,7 @@ impl<'a> Engine<'a> { if block.is_empty() { return; } + let rva = block[0].ip() as u32; let offset = self.pe.translate(PETranslation::Memory(RVA(rva))).unwrap(); let size = (end - rva) as usize; diff --git a/runtime/src/debug.rs b/runtime/src/debug.rs index 21a78b57..5d06c397 100644 --- a/runtime/src/debug.rs +++ b/runtime/src/debug.rs @@ -328,8 +328,6 @@ pub fn print_thread_message( // pop rbp rt.asm.pop(rbp).unwrap(); - lock::release_debug(rt); - restore(rt); } @@ -363,77 +361,46 @@ fn load(rt: &mut Runtime, dst: AsmRegister64) { rt.asm.mov(ptr(0x1480 + r10 * 8).gs(), r11).unwrap(); } -pub fn start_profiling(rt: &mut Runtime, _message: &str) { - // pushfq +pub fn start_profiling(rt: &mut Runtime) { rt.asm.pushfq().unwrap(); - // push rdx rt.asm.push(rdx).unwrap(); - // push rax rt.asm.push(rax).unwrap(); - - // push r10 rt.asm.push(r10).unwrap(); - // push r11 rt.asm.push(r11).unwrap(); - // rdtsc rt.asm.rdtsc().unwrap(); - // shl rdx, 0x20 rt.asm.shl(rdx, 0x20).unwrap(); - // or rdx, rax rt.asm.or(rdx, rax).unwrap(); store(rt, rdx); - // pop r11 rt.asm.pop(r11).unwrap(); - // pop r10 rt.asm.pop(r10).unwrap(); - - // pop rax rt.asm.pop(rax).unwrap(); - // pop rdx rt.asm.pop(rdx).unwrap(); - // popfq rt.asm.popfq().unwrap(); } -pub fn stop_profiling(rt: &mut Runtime, message: &str) { - // pushfq +pub fn stop_profiling(rt: &mut Runtime, def: FnDef, message: &str) { rt.asm.pushfq().unwrap(); - // push rdx rt.asm.push(rdx).unwrap(); - // push rax rt.asm.push(rax).unwrap(); - - // push r10 rt.asm.push(r10).unwrap(); - // push r11 rt.asm.push(r11).unwrap(); - // rdtsc rt.asm.rdtsc().unwrap(); - // shl rdx, 0x20 rt.asm.shl(rdx, 0x20).unwrap(); - // or rdx, rax rt.asm.or(rdx, rax).unwrap(); load(rt, rax); - // sub rdx, rax rt.asm.sub(rdx, rax).unwrap(); - print_thread_message(rt, &format!("cycles for {}", message), Some(rdx), None); + print_thread_message(rt, &format!("{:?} - {}", def, message), Some(rdx), None); - // pop r11 rt.asm.pop(r11).unwrap(); - // pop r10 rt.asm.pop(r10).unwrap(); - - // pop rax rt.asm.pop(rax).unwrap(); - // pop rdx rt.asm.pop(rdx).unwrap(); - // popfq rt.asm.popfq().unwrap(); } diff --git a/runtime/src/vm/bytecode.rs b/runtime/src/vm/bytecode.rs index e49aeb19..a24540a7 100644 --- a/runtime/src/vm/bytecode.rs +++ b/runtime/src/vm/bytecode.rs @@ -654,14 +654,14 @@ fn transforms<'a>( picker: &'a RefCell<&'a mut dyn FnMut(&[usize]) -> usize>, ) -> Vec> { vec![ - Box::new(Peephole), - Box::new(Permute { picker }), - Box::new(Scramble), - Box::new(Mutation), - Box::new(Indirect), + // Box::new(Peephole), + // Box::new(Permute { picker }), + // Box::new(Scramble), + // Box::new(Mutation), + // Box::new(Indirect), Box::new(Encrypt), - Box::new(Permute { picker }), - Box::new(Peephole), + // Box::new(Permute { picker }), + // Box::new(Peephole), ] } diff --git a/runtime/src/vm/functions/dispatch.rs b/runtime/src/vm/functions/dispatch.rs index dd7b2d77..f5c2c992 100644 --- a/runtime/src/vm/functions/dispatch.rs +++ b/runtime/src/vm/functions/dispatch.rs @@ -129,7 +129,7 @@ pub fn build(rt: &mut Runtime) { rt.asm.set_label(&mut decrypt_block).unwrap(); { #[cfg(feature = "profile")] - start_profiling(rt, "vm_crypt_decrypt"); + start_profiling(rt); // Decrypt the block: // mov rcx, 0x1 @@ -138,7 +138,7 @@ pub fn build(rt: &mut Runtime) { rt.asm.call(rt.function_labels[&FnDef::VmCrypt]).unwrap(); #[cfg(feature = "profile")] - stop_profiling(rt, "vm_crypt_decrypt"); + stop_profiling(rt, FnDef::VmCrypt, "decrypt"); // mov rax, ... rt.asm.mov(rax, VM_INTEGRITY_QWORD).unwrap(); @@ -173,16 +173,30 @@ pub fn build(rt: &mut Runtime) { // r8d -> operation utils::bytecode::read_byte_zx(rt, r13, r8d); - // println!( - // "{}", - // VMOp::VARIANTS - // .iter() - // .map(|op| format!("{:?}={:016X}", op, rt.mapper.index(*op))) - // .collect::>() - // .join("\n") - // ); + #[cfg(feature = "profile")] + { + use crate::debug::print_thread_message; + + let mut epilogue = rt.asm.create_label(); + + let mut cases = Vec::new(); + + for op in VMOp::VARIANTS { + cases.push((rt.mapper.index(*op), rt.asm.create_label())); + } - // crate::debug::print_thread_message(rt, "Executing", Some(r8), None); + rt.jumps(r8, cases.clone()); + + for (op, (_, mut label)) in VMOp::VARIANTS.iter().zip(cases) { + rt.asm.set_label(&mut label).unwrap(); + + print_thread_message(rt, &format!("{:?}", op), None, None); + + rt.asm.jmp(epilogue).unwrap(); + } + + rt.asm.set_label(&mut epilogue).unwrap(); + } // mov rcx, r13 rt.asm.mov(rcx, r13).unwrap(); @@ -244,7 +258,7 @@ pub fn build(rt: &mut Runtime) { rt.asm.set_label(&mut check_exit).unwrap(); { #[cfg(feature = "profile")] - start_profiling(rt, "vm_crypt_encrypt"); + start_profiling(rt); // Re-encrypt the current block: // xor rcx, rcx @@ -253,7 +267,7 @@ pub fn build(rt: &mut Runtime) { rt.asm.call(rt.function_labels[&FnDef::VmCrypt]).unwrap(); #[cfg(feature = "profile")] - stop_profiling(rt, "vm_crypt_encrypt"); + stop_profiling(rt, FnDef::VmCrypt, "encrypt"); // Skip if there is no branch: // mov rax, [r12 + ...] diff --git a/runtime/src/vm/functions/entry.rs b/runtime/src/vm/functions/entry.rs index 457b99e3..6ab6857c 100644 --- a/runtime/src/vm/functions/entry.rs +++ b/runtime/src/vm/functions/entry.rs @@ -166,13 +166,13 @@ pub fn build(rt: &mut Runtime) { } #[cfg(feature = "profile")] - start_profiling(rt, "vm_dispatch"); + start_profiling(rt); // call ... rt.asm.call(rt.function_labels[&FnDef::VmDispatch]).unwrap(); #[cfg(feature = "profile")] - stop_profiling(rt, "vm_dispatch"); + stop_profiling(rt, FnDef::VmDispatch, "dispatch"); // jmp ... rt.asm.jmp(rt.function_labels[&FnDef::VmExit]).unwrap(); diff --git a/runtime/src/vm/handlers/adc.rs b/runtime/src/vm/handlers/adc.rs index 461f7979..11ede6ef 100644 --- a/runtime/src/vm/handlers/adc.rs +++ b/runtime/src/vm/handlers/adc.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Compare, Effect, Expression, Flags, Operand, Operation}, }, }; diff --git a/runtime/src/vm/handlers/add.rs b/runtime/src/vm/handlers/add.rs index 98436e93..9e762533 100644 --- a/runtime/src/vm/handlers/add.rs +++ b/runtime/src/vm/handlers/add.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Compare, Effect, Expression, Flags, Operand, Operation}, }, }; diff --git a/runtime/src/vm/handlers/and.rs b/runtime/src/vm/handlers/and.rs index fc70c714..d37ea58c 100644 --- a/runtime/src/vm/handlers/and.rs +++ b/runtime/src/vm/handlers/and.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Compare, Effect, Expression, Flags, Operand, Operation}, }, }; diff --git a/runtime/src/vm/handlers/bit_scan_reverse.rs b/runtime/src/vm/handlers/bit_scan_reverse.rs index 228c78e9..5b8ecd62 100644 --- a/runtime/src/vm/handlers/bit_scan_reverse.rs +++ b/runtime/src/vm/handlers/bit_scan_reverse.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Compare, Effect, Expression, Flags, Operand, Operation}, }, }; diff --git a/runtime/src/vm/handlers/bit_test.rs b/runtime/src/vm/handlers/bit_test.rs index 86c03eda..d220eed4 100644 --- a/runtime/src/vm/handlers/bit_test.rs +++ b/runtime/src/vm/handlers/bit_test.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Flags, Compare, Expression, Operand, Operation}, + handlers::semantic::{self, Compare, Expression, Flags, Operand, Operation}, }, }; diff --git a/runtime/src/vm/handlers/bit_test_complement.rs b/runtime/src/vm/handlers/bit_test_complement.rs index b526dc01..c1c88eef 100644 --- a/runtime/src/vm/handlers/bit_test_complement.rs +++ b/runtime/src/vm/handlers/bit_test_complement.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Compare, Effect, Expression, Flags, Operand, Operation}, }, }; diff --git a/runtime/src/vm/handlers/bit_test_reset.rs b/runtime/src/vm/handlers/bit_test_reset.rs index 69ec3c0b..56009a05 100644 --- a/runtime/src/vm/handlers/bit_test_reset.rs +++ b/runtime/src/vm/handlers/bit_test_reset.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Compare, Effect, Expression, Flags, Operand, Operation}, }, }; diff --git a/runtime/src/vm/handlers/bit_test_set.rs b/runtime/src/vm/handlers/bit_test_set.rs index fdcd696a..c8513b0e 100644 --- a/runtime/src/vm/handlers/bit_test_set.rs +++ b/runtime/src/vm/handlers/bit_test_set.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Compare, Effect, Expression, Flags, Operand, Operation}, }, }; diff --git a/runtime/src/vm/handlers/byte_swap.rs b/runtime/src/vm/handlers/byte_swap.rs index 92c14f38..a966e126 100644 --- a/runtime/src/vm/handlers/byte_swap.rs +++ b/runtime/src/vm/handlers/byte_swap.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::VMWidth, - handlers::semantic::{self, Flags, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Effect, Expression, Flags, Operand, Operation}, }, }; diff --git a/runtime/src/vm/handlers/dispatch.rs b/runtime/src/vm/handlers/dispatch.rs index 2d8738ca..ed83032b 100644 --- a/runtime/src/vm/handlers/dispatch.rs +++ b/runtime/src/vm/handlers/dispatch.rs @@ -9,6 +9,33 @@ pub fn build(rt: &mut Runtime) { // load r8 scratch::load(rt, r12, r8); + #[cfg(feature = "profile")] + { + use crate::debug::print_thread_message; + use crate::mapper::Mappable; + use crate::vm::bytecode::VMOp; + + let mut epilogue = rt.asm.create_label(); + + let mut cases = Vec::new(); + + for op in VMOp::VARIANTS { + cases.push((rt.mapper.index(*op), rt.asm.create_label())); + } + + rt.jumps(r8, cases.clone()); + + for (op, (_, mut label)) in VMOp::VARIANTS.iter().zip(cases) { + rt.asm.set_label(&mut label).unwrap(); + + print_thread_message(rt, &format!("{:?}", op), None, None); + + rt.asm.jmp(epilogue).unwrap(); + } + + rt.asm.set_label(&mut epilogue).unwrap(); + } + let cases = HANDLERS .iter() .map(|&(op, def)| (rt.mapper.index(op), rt.function_labels[&def])) diff --git a/runtime/src/vm/handlers/div.rs b/runtime/src/vm/handlers/div.rs index 2959fb27..63874f54 100644 --- a/runtime/src/vm/handlers/div.rs +++ b/runtime/src/vm/handlers/div.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::VMWidth, - handlers::semantic::{self, Flags, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Effect, Expression, Flags, Operand, Operation}, }, }; diff --git a/runtime/src/vm/handlers/mul.rs b/runtime/src/vm/handlers/mul.rs index 92876aba..4afcd7d0 100644 --- a/runtime/src/vm/handlers/mul.rs +++ b/runtime/src/vm/handlers/mul.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Compare, Effect, Expression, Flags, Operand, Operation}, }, }; diff --git a/runtime/src/vm/handlers/or.rs b/runtime/src/vm/handlers/or.rs index 6a5691e2..19d34320 100644 --- a/runtime/src/vm/handlers/or.rs +++ b/runtime/src/vm/handlers/or.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Compare, Effect, Expression, Flags, Operand, Operation}, }, }; diff --git a/runtime/src/vm/handlers/rol.rs b/runtime/src/vm/handlers/rol.rs index 5f603c8b..123bdad2 100644 --- a/runtime/src/vm/handlers/rol.rs +++ b/runtime/src/vm/handlers/rol.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Compare, Effect, Expression, Flags, Operand, Operation}, }, }; @@ -15,18 +15,24 @@ pub fn build(rt: &mut Runtime) { Expression::Operand(Operand::Input(1)), )], flags: Flags::When( -Expression::BitAnd(Box::new(Expression::Operand(Operand::Input(1))), Box::new(Expression::BitOr(Box::new(Expression::SignBit), Box::new(Expression::Constant(0x1f))))), -vec![ - ( - Flag::Carry, - Expression::Compare(Box::new(Compare::BitSet( + Expression::BitAnd( + Box::new(Expression::Operand(Operand::Input(1))), + Box::new(Expression::BitOr( + Box::new(Expression::SignBit), + Box::new(Expression::Constant(0x1f)), + )), + ), + vec![ + ( + Flag::Carry, + Expression::Compare(Box::new(Compare::BitSet( Expression::Operand(Operand::Output(0)), Expression::Constant(0), ))), - ), - ( - Flag::Overflow, - Expression::Compare(Box::new(Compare::GreaterThan( + ), + ( + Flag::Overflow, + Expression::Compare(Box::new(Compare::GreaterThan( Expression::BitXor( Box::new(Expression::BitShr( Box::new(Expression::Operand(Operand::Output(0))), @@ -39,8 +45,9 @@ vec![ ), Expression::Constant(0), ))), - ), - ]), + ), + ], + ), stores: None, widths: &[ VMWidth::Lower64, diff --git a/runtime/src/vm/handlers/ror.rs b/runtime/src/vm/handlers/ror.rs index d4139052..7e3ed88d 100644 --- a/runtime/src/vm/handlers/ror.rs +++ b/runtime/src/vm/handlers/ror.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Compare, Effect, Expression, Flags, Operand, Operation}, }, }; @@ -15,18 +15,24 @@ pub fn build(rt: &mut Runtime) { Expression::Operand(Operand::Input(1)), )], flags: Flags::When( -Expression::BitAnd(Box::new(Expression::Operand(Operand::Input(1))), Box::new(Expression::BitOr(Box::new(Expression::SignBit), Box::new(Expression::Constant(0x1f))))), -vec![ - ( - Flag::Carry, - Expression::Compare(Box::new(Compare::BitSet( + Expression::BitAnd( + Box::new(Expression::Operand(Operand::Input(1))), + Box::new(Expression::BitOr( + Box::new(Expression::SignBit), + Box::new(Expression::Constant(0x1f)), + )), + ), + vec![ + ( + Flag::Carry, + Expression::Compare(Box::new(Compare::BitSet( Expression::Operand(Operand::Output(0)), Expression::SignBit, ))), - ), - ( - Flag::Overflow, - Expression::Compare(Box::new(Compare::GreaterThan( + ), + ( + Flag::Overflow, + Expression::Compare(Box::new(Compare::GreaterThan( Expression::BitXor( Box::new(Expression::BitShr( Box::new(Expression::Operand(Operand::Output(0))), @@ -45,8 +51,9 @@ vec![ ), Expression::Constant(0), ))), - ), - ]), + ), + ], + ), stores: None, widths: &[ VMWidth::Lower64, diff --git a/runtime/src/vm/handlers/sar.rs b/runtime/src/vm/handlers/sar.rs index 8e33c30c..aba44ef4 100644 --- a/runtime/src/vm/handlers/sar.rs +++ b/runtime/src/vm/handlers/sar.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Compare, Effect, Expression, Flags, Operand, Operation}, }, }; @@ -15,11 +15,17 @@ pub fn build(rt: &mut Runtime) { Expression::Operand(Operand::Input(1)), )], flags: Flags::When( -Expression::BitAnd(Box::new(Expression::Operand(Operand::Input(1))), Box::new(Expression::BitOr(Box::new(Expression::SignBit), Box::new(Expression::Constant(0x1f))))), -vec![ - ( - Flag::Carry, - Expression::Compare(Box::new(Compare::BitSet( + Expression::BitAnd( + Box::new(Expression::Operand(Operand::Input(1))), + Box::new(Expression::BitOr( + Box::new(Expression::SignBit), + Box::new(Expression::Constant(0x1f)), + )), + ), + vec![ + ( + Flag::Carry, + Expression::Compare(Box::new(Compare::BitSet( Expression::Operand(Operand::Input(0)), Expression::Sub( Box::new(Expression::SignBit), @@ -52,35 +58,34 @@ vec![ )), ), ))), - ), - ( - Flag::Overflow, - Expression::Compare(Box::new(Compare::Equal( + ), + ( + Flag::Overflow, + Expression::Compare(Box::new(Compare::Equal( Expression::Constant(1), Expression::Constant(0), ))), - ), - ( - Flag::Sign, - Expression::Compare(Box::new(Compare::BitSet( + ), + ( + Flag::Sign, + Expression::Compare(Box::new(Compare::BitSet( Expression::Operand(Operand::Output(0)), Expression::SignBit, ))), - ), - ( - Flag::Zero, - Expression::Compare(Box::new(Compare::Equal( + ), + ( + Flag::Zero, + Expression::Compare(Box::new(Compare::Equal( Expression::Operand(Operand::Output(0)), Expression::Constant(0), ))), - ), - ( - Flag::Parity, - Expression::Parity(Box::new(Expression::Operand( - Operand::Output(0), - ))), - ), - ]), + ), + ( + Flag::Parity, + Expression::Parity(Box::new(Expression::Operand(Operand::Output(0)))), + ), + ], + ), stores: None, widths: &[ VMWidth::Lower64, diff --git a/runtime/src/vm/handlers/sbb.rs b/runtime/src/vm/handlers/sbb.rs index d6399207..21c61e27 100644 --- a/runtime/src/vm/handlers/sbb.rs +++ b/runtime/src/vm/handlers/sbb.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Compare, Effect, Expression, Flags, Operand, Operation}, }, }; diff --git a/runtime/src/vm/handlers/semantic/compiler.rs b/runtime/src/vm/handlers/semantic/compiler.rs index 543bab91..4e410993 100644 --- a/runtime/src/vm/handlers/semantic/compiler.rs +++ b/runtime/src/vm/handlers/semantic/compiler.rs @@ -8,10 +8,10 @@ use crate::{ register, runtime::Runtime, vm::{ - bytecode::{Flag, VMReg, VMWidth}, + bytecode::{VMReg, VMWidth}, handlers::semantic::{ - allocator::Allocator, Compare, Effect, Expression, Flags, Operand, Operation, Reference, - Value, + allocator::Allocator, Compare, Effect, Expression, Flags, Operand, Operation, + Reference, Value, }, utils::{bytecode, scratch, vreg}, }, @@ -878,10 +878,6 @@ fn compile_flags(rt: &mut Runtime, allocator: &mut Allocator, flags: &Flags, wid allocator.untrack(register); } - // A conditional flag write stores back to the flag register only when - // the condition holds; otherwise the store is redirected at the local - // slot, so the pre-operation flags are left untouched (a shift or - // rotate with a zero count does not affect the flags). Some(condition) => { let condition = compile_expression(rt, allocator, condition, width); @@ -900,10 +896,10 @@ fn compile_flags(rt: &mut Runtime, allocator: &mut Allocator, flags: &Flags, wid .mov(register, qword_ptr(rbp - allocator.offset(Value::Flags))) .unwrap(); - let offset = rt.mapper.index(VMReg::Flags) as i32 * 8; - let target = allocator.acquire(rt, &[], &[selector, register]); - rt.asm.lea(target, ptr(r12 + offset)).unwrap(); + rt.asm + .lea(target, ptr(r12 + rt.mapper.index(VMReg::Flags) as i32 * 8)) + .unwrap(); let skip = allocator.acquire(rt, &[], &[selector, register, target]); rt.asm diff --git a/runtime/src/vm/handlers/shl.rs b/runtime/src/vm/handlers/shl.rs index 841de8bb..c5589c01 100644 --- a/runtime/src/vm/handlers/shl.rs +++ b/runtime/src/vm/handlers/shl.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Compare, Effect, Expression, Flags, Operand, Operation}, }, }; @@ -15,11 +15,17 @@ pub fn build(rt: &mut Runtime) { Expression::Operand(Operand::Input(1)), )], flags: Flags::When( -Expression::BitAnd(Box::new(Expression::Operand(Operand::Input(1))), Box::new(Expression::BitOr(Box::new(Expression::SignBit), Box::new(Expression::Constant(0x1f))))), -vec![ - ( - Flag::Carry, - Expression::Compare(Box::new(Compare::BitSet( + Expression::BitAnd( + Box::new(Expression::Operand(Operand::Input(1))), + Box::new(Expression::BitOr( + Box::new(Expression::SignBit), + Box::new(Expression::Constant(0x1f)), + )), + ), + vec![ + ( + Flag::Carry, + Expression::Compare(Box::new(Compare::BitSet( Expression::Operand(Operand::Input(0)), Expression::Sub( Box::new(Expression::BitSize), @@ -32,10 +38,10 @@ vec![ )), ), ))), - ), - ( - Flag::Overflow, - Expression::Compare(Box::new(Compare::GreaterThan( + ), + ( + Flag::Overflow, + Expression::Compare(Box::new(Compare::GreaterThan( Expression::BitXor( Box::new(Expression::BitShr( Box::new(Expression::Operand(Operand::Input(0))), @@ -48,28 +54,27 @@ vec![ ), Expression::Constant(0), ))), - ), - ( - Flag::Sign, - Expression::Compare(Box::new(Compare::BitSet( + ), + ( + Flag::Sign, + Expression::Compare(Box::new(Compare::BitSet( Expression::Operand(Operand::Output(0)), Expression::SignBit, ))), - ), - ( - Flag::Zero, - Expression::Compare(Box::new(Compare::Equal( + ), + ( + Flag::Zero, + Expression::Compare(Box::new(Compare::Equal( Expression::Operand(Operand::Output(0)), Expression::Constant(0), ))), - ), - ( - Flag::Parity, - Expression::Parity(Box::new(Expression::Operand( - Operand::Output(0), - ))), - ), - ]), + ), + ( + Flag::Parity, + Expression::Parity(Box::new(Expression::Operand(Operand::Output(0)))), + ), + ], + ), stores: None, widths: &[ VMWidth::Lower64, diff --git a/runtime/src/vm/handlers/shr.rs b/runtime/src/vm/handlers/shr.rs index c6b57ecf..b4a8ebbc 100644 --- a/runtime/src/vm/handlers/shr.rs +++ b/runtime/src/vm/handlers/shr.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Compare, Effect, Expression, Flags, Operand, Operation}, }, }; @@ -15,11 +15,17 @@ pub fn build(rt: &mut Runtime) { Expression::Operand(Operand::Input(1)), )], flags: Flags::When( -Expression::BitAnd(Box::new(Expression::Operand(Operand::Input(1))), Box::new(Expression::BitOr(Box::new(Expression::SignBit), Box::new(Expression::Constant(0x1f))))), -vec![ - ( - Flag::Carry, - Expression::Compare(Box::new(Compare::BitSet( + Expression::BitAnd( + Box::new(Expression::Operand(Operand::Input(1))), + Box::new(Expression::BitOr( + Box::new(Expression::SignBit), + Box::new(Expression::Constant(0x1f)), + )), + ), + vec![ + ( + Flag::Carry, + Expression::Compare(Box::new(Compare::BitSet( Expression::Operand(Operand::Input(0)), Expression::Sub( Box::new(Expression::BitAnd( @@ -32,35 +38,34 @@ vec![ Box::new(Expression::Constant(1)), ), ))), - ), - ( - Flag::Overflow, - Expression::Compare(Box::new(Compare::BitSet( + ), + ( + Flag::Overflow, + Expression::Compare(Box::new(Compare::BitSet( Expression::Operand(Operand::Input(0)), Expression::SignBit, ))), - ), - ( - Flag::Sign, - Expression::Compare(Box::new(Compare::BitSet( + ), + ( + Flag::Sign, + Expression::Compare(Box::new(Compare::BitSet( Expression::Operand(Operand::Output(0)), Expression::SignBit, ))), - ), - ( - Flag::Zero, - Expression::Compare(Box::new(Compare::Equal( + ), + ( + Flag::Zero, + Expression::Compare(Box::new(Compare::Equal( Expression::Operand(Operand::Output(0)), Expression::Constant(0), ))), - ), - ( - Flag::Parity, - Expression::Parity(Box::new(Expression::Operand( - Operand::Output(0), - ))), - ), - ]), + ), + ( + Flag::Parity, + Expression::Parity(Box::new(Expression::Operand(Operand::Output(0)))), + ), + ], + ), stores: None, widths: &[ VMWidth::Lower64, diff --git a/runtime/src/vm/handlers/sub.rs b/runtime/src/vm/handlers/sub.rs index 543bddec..43d080ff 100644 --- a/runtime/src/vm/handlers/sub.rs +++ b/runtime/src/vm/handlers/sub.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Compare, Effect, Expression, Flags, Operand, Operation}, }, }; diff --git a/runtime/src/vm/handlers/trailing_zeros.rs b/runtime/src/vm/handlers/trailing_zeros.rs index d1ce4176..495a6b85 100644 --- a/runtime/src/vm/handlers/trailing_zeros.rs +++ b/runtime/src/vm/handlers/trailing_zeros.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Compare, Effect, Expression, Flags, Operand, Operation}, }, }; diff --git a/runtime/src/vm/handlers/xor.rs b/runtime/src/vm/handlers/xor.rs index 22ad4247..50ba21cc 100644 --- a/runtime/src/vm/handlers/xor.rs +++ b/runtime/src/vm/handlers/xor.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Compare, Effect, Expression, Flags, Operand, Operation}, }, }; diff --git a/runtime/src/vm/transform/encrypt.rs b/runtime/src/vm/transform/encrypt.rs index bb7a0372..3eee550e 100644 --- a/runtime/src/vm/transform/encrypt.rs +++ b/runtime/src/vm/transform/encrypt.rs @@ -215,23 +215,23 @@ fn walk( } if leaf(&mut operations[i], *addend, *multiplier) { - skip -= 1; + // skip -= 1; - if !operations[i].is_branch() { - if skip == 0 { - let sequence = transform(addend, multiplier, !flags[position]); - let length = sequence.len(); - operations.splice(i + 1..i + 1, sequence); + // if !operations[i].is_branch() { + // if skip == 0 { + // let sequence = transform(addend, multiplier, !flags[position]); + // let length = sequence.len(); + // operations.splice(i + 1..i + 1, sequence); - for _ in 0..length { - trace.push((*addend, *multiplier)); - } + // for _ in 0..length { + // trace.push((*addend, *multiplier)); + // } - i += length; + // i += length; - skip = rng.gen_range(0..=8); - } - } + // skip = rng.gen_range(0..=8); + // } + // } } i += 1; diff --git a/runtime/src/vm/transform/mod.rs b/runtime/src/vm/transform/mod.rs index eaf58eb1..4f28a5f1 100644 --- a/runtime/src/vm/transform/mod.rs +++ b/runtime/src/vm/transform/mod.rs @@ -87,13 +87,15 @@ pub fn deadzones( let mut events = Vec::new(); scan(operations, &mut events, &effect); + let first = events.iter().position(|(_, writes)| *writes); + let mut deadzones = vec![false; events.len()]; let mut live = true; for (i, (reads, writes)) in events.iter().enumerate().rev() { if *writes { - live = false; + live = Some(i) == first; } if !live { deadzones[i] = true; diff --git a/tests/src/instructions.rs b/tests/src/instructions.rs index 246337fc..74feb93c 100644 --- a/tests/src/instructions.rs +++ b/tests/src/instructions.rs @@ -1,5 +1,6 @@ use std::slice; +use iced_x86::code_asm::{eax, ecx, CodeAssembler}; use iced_x86::{ Code, Instruction, InstructionInfoFactory, OpAccess, OpCodeOperandKind, OpKind, Register, RflagsBits, @@ -35,6 +36,32 @@ macro_rules! define { }; } +#[test] +fn test_nested_loop() { + let mut asm = CodeAssembler::new(64).unwrap(); + + let mut outer = asm.create_label(); + let mut inner = asm.create_label(); + + asm.xor(eax, eax).unwrap(); + + asm.set_label(&mut outer).unwrap(); + asm.xor(ecx, ecx).unwrap(); + + asm.set_label(&mut inner).unwrap(); + asm.add(ecx, 1).unwrap(); + asm.cmp(ecx, 31).unwrap(); + asm.jbe(inner).unwrap(); + + asm.add(eax, 1).unwrap(); + asm.cmp(eax, 0x4E1F).unwrap(); + asm.jle(outer).unwrap(); + + let instructions = asm.take_instructions(); + + compare(baseline(), &instructions); +} + define!( Adc_r64_rm64 Adc_rm16_r16 @@ -775,46 +802,50 @@ fn case(instruction: Instruction, memory: Option<&mut [u8]>, immediate: u64) { VMReg::from(instruction.memory_base()), memory.as_mut_ptr() as u64, ); - compare_memory(state, instruction, memory); + compare_memory(state, &[instruction], memory); } else { - compare(state, instruction); + compare(state, &[instruction]); } } -fn compare(state: State, instruction: Instruction) { - compare_memory(state, instruction, &mut []); +fn compare(state: State, instructions: &[Instruction]) { + compare_memory(state, instructions, &mut []); } -fn compare_memory(state: State, instruction: Instruction, memory: &mut [u8]) { +fn compare_memory(state: State, instructions: &[Instruction], memory: &mut [u8]) { let baseline = memory.to_vec(); let mut executor = Executor::new(); - let mut native = executor.run_native(state.clone(), &[instruction]); + let mut native = executor.run_native(state.clone(), instructions); memory.copy_from_slice(&baseline); let mut executor = Executor::new(); - let lifted = bytecode::lift(&[instruction]) - .unwrap_or_else(|| panic!("{instruction} is not implemented")); - + let lifted = bytecode::lift(instructions).unwrap(); let transformed = bytecode::transform(&mut executor.rt.mapper, lifted, |_| 0); let mut bytes = bytecode::assemble(&mut executor.rt.mapper, &transformed); encrypt_block(&mut bytes); decrypt_payload(&mut bytes); - let mut emulated = executor.run_virtual(state.clone(), &bytes); + let mut emulated = executor.run_virtual(state, &bytes); - normalize(&mut native, &mut emulated, instruction); + normalize(&mut native, &mut emulated, instructions); } -fn normalize(native: &mut State, emulated: &mut State, instruction: Instruction) { +fn normalize(native: &mut State, emulated: &mut State, instructions: &[Instruction]) { native.registers.remove(&VMReg::Rsp); emulated.registers.remove(&VMReg::Rsp); + let mask = instructions + .iter() + .fold(RflagsBits::NONE, |mask, instruction| { + mask | instruction.rflags_written() + | instruction.rflags_cleared() + | instruction.rflags_set() + }); + for state in [&mut *native, &mut *emulated] { let flags = state.registers.get_mut(&VMReg::Flags).unwrap(); - let mask = - instruction.rflags_written() | instruction.rflags_cleared() | instruction.rflags_set(); *flags &= ((mask & RflagsBits::CF != 0) as u64 * Flag::Carry.bit64()) | ((mask & RflagsBits::PF != 0) as u64 * Flag::Parity.bit64()) From 48ea8ece42fde963ae5a88c1d1a6528287f2ddcc Mon Sep 17 00:00:00 2001 From: RadonCoding <86915746+RadonCoding@users.noreply.github.com> Date: Sat, 26 Sep 2026 20:29:16 +0300 Subject: [PATCH 15/33] fix: remove uncessary test --- example.ps1 | 72 ++++++++++++++----- obfuscator/src/engine.rs | 1 + runtime/src/debug.rs | 39 +--------- runtime/src/vm/bytecode.rs | 14 ++-- runtime/src/vm/functions/dispatch.rs | 40 +++++++---- runtime/src/vm/functions/entry.rs | 4 +- runtime/src/vm/handlers/adc.rs | 2 +- runtime/src/vm/handlers/add.rs | 2 +- runtime/src/vm/handlers/and.rs | 2 +- runtime/src/vm/handlers/bit_scan_reverse.rs | 2 +- runtime/src/vm/handlers/bit_test.rs | 2 +- .../src/vm/handlers/bit_test_complement.rs | 2 +- runtime/src/vm/handlers/bit_test_reset.rs | 2 +- runtime/src/vm/handlers/bit_test_set.rs | 2 +- runtime/src/vm/handlers/byte_swap.rs | 2 +- runtime/src/vm/handlers/dispatch.rs | 27 +++++++ runtime/src/vm/handlers/div.rs | 2 +- runtime/src/vm/handlers/mul.rs | 2 +- runtime/src/vm/handlers/or.rs | 2 +- runtime/src/vm/handlers/rol.rs | 31 ++++---- runtime/src/vm/handlers/ror.rs | 31 ++++---- runtime/src/vm/handlers/sar.rs | 57 ++++++++------- runtime/src/vm/handlers/sbb.rs | 2 +- runtime/src/vm/handlers/semantic/compiler.rs | 16 ++--- runtime/src/vm/handlers/shl.rs | 57 ++++++++------- runtime/src/vm/handlers/shr.rs | 57 ++++++++------- runtime/src/vm/handlers/sub.rs | 2 +- runtime/src/vm/handlers/trailing_zeros.rs | 2 +- runtime/src/vm/handlers/xor.rs | 2 +- runtime/src/vm/transform/encrypt.rs | 26 +++---- runtime/src/vm/transform/mod.rs | 4 +- tests/src/instructions.rs | 59 +++++++++++---- 32 files changed, 335 insertions(+), 232 deletions(-) diff --git a/example.ps1 b/example.ps1 index 09190c2c..117efc67 100644 --- a/example.ps1 +++ b/example.ps1 @@ -1,51 +1,87 @@ param( [Parameter(Mandatory)] - [string]$Example + [System.IO.DirectoryInfo]$Example, + [string[]]$Args ) $ErrorActionPreference = "Stop" -$language = $Example.Split("/")[1] +$language = [System.IO.Path]::GetFileName([System.IO.Path]::GetDirectoryName([System.IO.Path]::GetDirectoryName($Example))) -$env:OUTPUT_DIRECTORY = "$PWD/api/generated" -$env:TEMPLATES_DIRECTORY = "$PWD/api/templates" +$env:OUTPUT_DIRECTORY = Join-Path $PWD "api/generated" +$env:TEMPLATES_DIRECTORY = Join-Path $PWD "api/templates" cargo build --package markers if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } -cargo build --bin obfuscator +cargo build --bin obfuscator $Args if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } switch ($language) { -"rust" { - cargo build --manifest-path "$Example/Cargo.toml" + "rust" { + $manifest = Join-Path $Example.FullName "Cargo.toml" + + cargo build --manifest-path $manifest if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - $metadata = cargo metadata --manifest-path "$Example/Cargo.toml" --format-version 1 --no-deps | ConvertFrom-Json + $metadata = cargo metadata ` + --manifest-path $manifest ` + --format-version 1 ` + --no-deps | ConvertFrom-Json + + $manifest = [System.IO.Path]::GetFullPath($manifest) + + $package = $metadata.packages | + Where-Object { + [System.IO.Path]::GetFullPath($_.manifest_path) -eq $manifest + } | + Select-Object -First 1 - $manifest = (Resolve-Path "$Example/Cargo.toml").Path - $package = $metadata.packages | Where-Object { (Resolve-Path $_.manifest_path).Path -eq $manifest } - $target = $package.targets | Where-Object { $_.kind -contains "bin" } | Select-Object -First 1 + $target = $package.targets | + Where-Object { $_.kind -contains "bin" } | + Select-Object -First 1 - $source = Join-Path $metadata.target_directory "debug\$($target.name).exe" + $source = Join-Path ` + $metadata.target_directory ` + "debug\$($target.name).exe" } + "cpp" { - g++ "$Example/main.cpp" -o "$Example.exe" - + $main = Join-Path $Example.FullName "main.cpp" + + $source = Join-Path ` + $example.Parent.Parent.FullName ` + "$($example.Name).exe" + + g++ $main -o $source + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + } - $source = Join-Path $PWD "$Example.exe" + default { + throw "Unsupported language: $language" } } -& "target/debug/obfuscator.exe" --virtualization $source +$source = [System.IO.Path]::GetFullPath($source) + +if (-not (Test-Path -LiteralPath $source -PathType Leaf)) { + throw "Source executable not found: $source" +} + +& (Join-Path $PWD "target/debug/obfuscator.exe") ` + --virtualization -v` +$source if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } -$destination = [System.IO.Path]::ChangeExtension($source, ".protected.exe") +$destination = [System.IO.Path]::ChangeExtension( + $source, + ".protected.exe" +) & $destination @@ -54,4 +90,4 @@ $code = $LASTEXITCODE Remove-Item $source -Force -ErrorAction SilentlyContinue Remove-Item $destination -Force -ErrorAction SilentlyContinue -exit $code \ No newline at end of file +exit $code diff --git a/obfuscator/src/engine.rs b/obfuscator/src/engine.rs index 35a06ba1..f905452d 100644 --- a/obfuscator/src/engine.rs +++ b/obfuscator/src/engine.rs @@ -191,6 +191,7 @@ impl<'a> Engine<'a> { if block.is_empty() { return; } + let rva = block[0].ip() as u32; let offset = self.pe.translate(PETranslation::Memory(RVA(rva))).unwrap(); let size = (end - rva) as usize; diff --git a/runtime/src/debug.rs b/runtime/src/debug.rs index 21a78b57..5d06c397 100644 --- a/runtime/src/debug.rs +++ b/runtime/src/debug.rs @@ -328,8 +328,6 @@ pub fn print_thread_message( // pop rbp rt.asm.pop(rbp).unwrap(); - lock::release_debug(rt); - restore(rt); } @@ -363,77 +361,46 @@ fn load(rt: &mut Runtime, dst: AsmRegister64) { rt.asm.mov(ptr(0x1480 + r10 * 8).gs(), r11).unwrap(); } -pub fn start_profiling(rt: &mut Runtime, _message: &str) { - // pushfq +pub fn start_profiling(rt: &mut Runtime) { rt.asm.pushfq().unwrap(); - // push rdx rt.asm.push(rdx).unwrap(); - // push rax rt.asm.push(rax).unwrap(); - - // push r10 rt.asm.push(r10).unwrap(); - // push r11 rt.asm.push(r11).unwrap(); - // rdtsc rt.asm.rdtsc().unwrap(); - // shl rdx, 0x20 rt.asm.shl(rdx, 0x20).unwrap(); - // or rdx, rax rt.asm.or(rdx, rax).unwrap(); store(rt, rdx); - // pop r11 rt.asm.pop(r11).unwrap(); - // pop r10 rt.asm.pop(r10).unwrap(); - - // pop rax rt.asm.pop(rax).unwrap(); - // pop rdx rt.asm.pop(rdx).unwrap(); - // popfq rt.asm.popfq().unwrap(); } -pub fn stop_profiling(rt: &mut Runtime, message: &str) { - // pushfq +pub fn stop_profiling(rt: &mut Runtime, def: FnDef, message: &str) { rt.asm.pushfq().unwrap(); - // push rdx rt.asm.push(rdx).unwrap(); - // push rax rt.asm.push(rax).unwrap(); - - // push r10 rt.asm.push(r10).unwrap(); - // push r11 rt.asm.push(r11).unwrap(); - // rdtsc rt.asm.rdtsc().unwrap(); - // shl rdx, 0x20 rt.asm.shl(rdx, 0x20).unwrap(); - // or rdx, rax rt.asm.or(rdx, rax).unwrap(); load(rt, rax); - // sub rdx, rax rt.asm.sub(rdx, rax).unwrap(); - print_thread_message(rt, &format!("cycles for {}", message), Some(rdx), None); + print_thread_message(rt, &format!("{:?} - {}", def, message), Some(rdx), None); - // pop r11 rt.asm.pop(r11).unwrap(); - // pop r10 rt.asm.pop(r10).unwrap(); - - // pop rax rt.asm.pop(rax).unwrap(); - // pop rdx rt.asm.pop(rdx).unwrap(); - // popfq rt.asm.popfq().unwrap(); } diff --git a/runtime/src/vm/bytecode.rs b/runtime/src/vm/bytecode.rs index e49aeb19..a24540a7 100644 --- a/runtime/src/vm/bytecode.rs +++ b/runtime/src/vm/bytecode.rs @@ -654,14 +654,14 @@ fn transforms<'a>( picker: &'a RefCell<&'a mut dyn FnMut(&[usize]) -> usize>, ) -> Vec> { vec![ - Box::new(Peephole), - Box::new(Permute { picker }), - Box::new(Scramble), - Box::new(Mutation), - Box::new(Indirect), + // Box::new(Peephole), + // Box::new(Permute { picker }), + // Box::new(Scramble), + // Box::new(Mutation), + // Box::new(Indirect), Box::new(Encrypt), - Box::new(Permute { picker }), - Box::new(Peephole), + // Box::new(Permute { picker }), + // Box::new(Peephole), ] } diff --git a/runtime/src/vm/functions/dispatch.rs b/runtime/src/vm/functions/dispatch.rs index dd7b2d77..f5c2c992 100644 --- a/runtime/src/vm/functions/dispatch.rs +++ b/runtime/src/vm/functions/dispatch.rs @@ -129,7 +129,7 @@ pub fn build(rt: &mut Runtime) { rt.asm.set_label(&mut decrypt_block).unwrap(); { #[cfg(feature = "profile")] - start_profiling(rt, "vm_crypt_decrypt"); + start_profiling(rt); // Decrypt the block: // mov rcx, 0x1 @@ -138,7 +138,7 @@ pub fn build(rt: &mut Runtime) { rt.asm.call(rt.function_labels[&FnDef::VmCrypt]).unwrap(); #[cfg(feature = "profile")] - stop_profiling(rt, "vm_crypt_decrypt"); + stop_profiling(rt, FnDef::VmCrypt, "decrypt"); // mov rax, ... rt.asm.mov(rax, VM_INTEGRITY_QWORD).unwrap(); @@ -173,16 +173,30 @@ pub fn build(rt: &mut Runtime) { // r8d -> operation utils::bytecode::read_byte_zx(rt, r13, r8d); - // println!( - // "{}", - // VMOp::VARIANTS - // .iter() - // .map(|op| format!("{:?}={:016X}", op, rt.mapper.index(*op))) - // .collect::>() - // .join("\n") - // ); + #[cfg(feature = "profile")] + { + use crate::debug::print_thread_message; + + let mut epilogue = rt.asm.create_label(); + + let mut cases = Vec::new(); + + for op in VMOp::VARIANTS { + cases.push((rt.mapper.index(*op), rt.asm.create_label())); + } - // crate::debug::print_thread_message(rt, "Executing", Some(r8), None); + rt.jumps(r8, cases.clone()); + + for (op, (_, mut label)) in VMOp::VARIANTS.iter().zip(cases) { + rt.asm.set_label(&mut label).unwrap(); + + print_thread_message(rt, &format!("{:?}", op), None, None); + + rt.asm.jmp(epilogue).unwrap(); + } + + rt.asm.set_label(&mut epilogue).unwrap(); + } // mov rcx, r13 rt.asm.mov(rcx, r13).unwrap(); @@ -244,7 +258,7 @@ pub fn build(rt: &mut Runtime) { rt.asm.set_label(&mut check_exit).unwrap(); { #[cfg(feature = "profile")] - start_profiling(rt, "vm_crypt_encrypt"); + start_profiling(rt); // Re-encrypt the current block: // xor rcx, rcx @@ -253,7 +267,7 @@ pub fn build(rt: &mut Runtime) { rt.asm.call(rt.function_labels[&FnDef::VmCrypt]).unwrap(); #[cfg(feature = "profile")] - stop_profiling(rt, "vm_crypt_encrypt"); + stop_profiling(rt, FnDef::VmCrypt, "encrypt"); // Skip if there is no branch: // mov rax, [r12 + ...] diff --git a/runtime/src/vm/functions/entry.rs b/runtime/src/vm/functions/entry.rs index 457b99e3..6ab6857c 100644 --- a/runtime/src/vm/functions/entry.rs +++ b/runtime/src/vm/functions/entry.rs @@ -166,13 +166,13 @@ pub fn build(rt: &mut Runtime) { } #[cfg(feature = "profile")] - start_profiling(rt, "vm_dispatch"); + start_profiling(rt); // call ... rt.asm.call(rt.function_labels[&FnDef::VmDispatch]).unwrap(); #[cfg(feature = "profile")] - stop_profiling(rt, "vm_dispatch"); + stop_profiling(rt, FnDef::VmDispatch, "dispatch"); // jmp ... rt.asm.jmp(rt.function_labels[&FnDef::VmExit]).unwrap(); diff --git a/runtime/src/vm/handlers/adc.rs b/runtime/src/vm/handlers/adc.rs index 461f7979..11ede6ef 100644 --- a/runtime/src/vm/handlers/adc.rs +++ b/runtime/src/vm/handlers/adc.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Compare, Effect, Expression, Flags, Operand, Operation}, }, }; diff --git a/runtime/src/vm/handlers/add.rs b/runtime/src/vm/handlers/add.rs index 98436e93..9e762533 100644 --- a/runtime/src/vm/handlers/add.rs +++ b/runtime/src/vm/handlers/add.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Compare, Effect, Expression, Flags, Operand, Operation}, }, }; diff --git a/runtime/src/vm/handlers/and.rs b/runtime/src/vm/handlers/and.rs index fc70c714..d37ea58c 100644 --- a/runtime/src/vm/handlers/and.rs +++ b/runtime/src/vm/handlers/and.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Compare, Effect, Expression, Flags, Operand, Operation}, }, }; diff --git a/runtime/src/vm/handlers/bit_scan_reverse.rs b/runtime/src/vm/handlers/bit_scan_reverse.rs index 228c78e9..5b8ecd62 100644 --- a/runtime/src/vm/handlers/bit_scan_reverse.rs +++ b/runtime/src/vm/handlers/bit_scan_reverse.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Compare, Effect, Expression, Flags, Operand, Operation}, }, }; diff --git a/runtime/src/vm/handlers/bit_test.rs b/runtime/src/vm/handlers/bit_test.rs index 86c03eda..d220eed4 100644 --- a/runtime/src/vm/handlers/bit_test.rs +++ b/runtime/src/vm/handlers/bit_test.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Flags, Compare, Expression, Operand, Operation}, + handlers::semantic::{self, Compare, Expression, Flags, Operand, Operation}, }, }; diff --git a/runtime/src/vm/handlers/bit_test_complement.rs b/runtime/src/vm/handlers/bit_test_complement.rs index b526dc01..c1c88eef 100644 --- a/runtime/src/vm/handlers/bit_test_complement.rs +++ b/runtime/src/vm/handlers/bit_test_complement.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Compare, Effect, Expression, Flags, Operand, Operation}, }, }; diff --git a/runtime/src/vm/handlers/bit_test_reset.rs b/runtime/src/vm/handlers/bit_test_reset.rs index 69ec3c0b..56009a05 100644 --- a/runtime/src/vm/handlers/bit_test_reset.rs +++ b/runtime/src/vm/handlers/bit_test_reset.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Compare, Effect, Expression, Flags, Operand, Operation}, }, }; diff --git a/runtime/src/vm/handlers/bit_test_set.rs b/runtime/src/vm/handlers/bit_test_set.rs index fdcd696a..c8513b0e 100644 --- a/runtime/src/vm/handlers/bit_test_set.rs +++ b/runtime/src/vm/handlers/bit_test_set.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Compare, Effect, Expression, Flags, Operand, Operation}, }, }; diff --git a/runtime/src/vm/handlers/byte_swap.rs b/runtime/src/vm/handlers/byte_swap.rs index 92c14f38..a966e126 100644 --- a/runtime/src/vm/handlers/byte_swap.rs +++ b/runtime/src/vm/handlers/byte_swap.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::VMWidth, - handlers::semantic::{self, Flags, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Effect, Expression, Flags, Operand, Operation}, }, }; diff --git a/runtime/src/vm/handlers/dispatch.rs b/runtime/src/vm/handlers/dispatch.rs index 2d8738ca..ed83032b 100644 --- a/runtime/src/vm/handlers/dispatch.rs +++ b/runtime/src/vm/handlers/dispatch.rs @@ -9,6 +9,33 @@ pub fn build(rt: &mut Runtime) { // load r8 scratch::load(rt, r12, r8); + #[cfg(feature = "profile")] + { + use crate::debug::print_thread_message; + use crate::mapper::Mappable; + use crate::vm::bytecode::VMOp; + + let mut epilogue = rt.asm.create_label(); + + let mut cases = Vec::new(); + + for op in VMOp::VARIANTS { + cases.push((rt.mapper.index(*op), rt.asm.create_label())); + } + + rt.jumps(r8, cases.clone()); + + for (op, (_, mut label)) in VMOp::VARIANTS.iter().zip(cases) { + rt.asm.set_label(&mut label).unwrap(); + + print_thread_message(rt, &format!("{:?}", op), None, None); + + rt.asm.jmp(epilogue).unwrap(); + } + + rt.asm.set_label(&mut epilogue).unwrap(); + } + let cases = HANDLERS .iter() .map(|&(op, def)| (rt.mapper.index(op), rt.function_labels[&def])) diff --git a/runtime/src/vm/handlers/div.rs b/runtime/src/vm/handlers/div.rs index 2959fb27..63874f54 100644 --- a/runtime/src/vm/handlers/div.rs +++ b/runtime/src/vm/handlers/div.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::VMWidth, - handlers::semantic::{self, Flags, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Effect, Expression, Flags, Operand, Operation}, }, }; diff --git a/runtime/src/vm/handlers/mul.rs b/runtime/src/vm/handlers/mul.rs index 92876aba..4afcd7d0 100644 --- a/runtime/src/vm/handlers/mul.rs +++ b/runtime/src/vm/handlers/mul.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Compare, Effect, Expression, Flags, Operand, Operation}, }, }; diff --git a/runtime/src/vm/handlers/or.rs b/runtime/src/vm/handlers/or.rs index 6a5691e2..19d34320 100644 --- a/runtime/src/vm/handlers/or.rs +++ b/runtime/src/vm/handlers/or.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Compare, Effect, Expression, Flags, Operand, Operation}, }, }; diff --git a/runtime/src/vm/handlers/rol.rs b/runtime/src/vm/handlers/rol.rs index 5f603c8b..123bdad2 100644 --- a/runtime/src/vm/handlers/rol.rs +++ b/runtime/src/vm/handlers/rol.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Compare, Effect, Expression, Flags, Operand, Operation}, }, }; @@ -15,18 +15,24 @@ pub fn build(rt: &mut Runtime) { Expression::Operand(Operand::Input(1)), )], flags: Flags::When( -Expression::BitAnd(Box::new(Expression::Operand(Operand::Input(1))), Box::new(Expression::BitOr(Box::new(Expression::SignBit), Box::new(Expression::Constant(0x1f))))), -vec![ - ( - Flag::Carry, - Expression::Compare(Box::new(Compare::BitSet( + Expression::BitAnd( + Box::new(Expression::Operand(Operand::Input(1))), + Box::new(Expression::BitOr( + Box::new(Expression::SignBit), + Box::new(Expression::Constant(0x1f)), + )), + ), + vec![ + ( + Flag::Carry, + Expression::Compare(Box::new(Compare::BitSet( Expression::Operand(Operand::Output(0)), Expression::Constant(0), ))), - ), - ( - Flag::Overflow, - Expression::Compare(Box::new(Compare::GreaterThan( + ), + ( + Flag::Overflow, + Expression::Compare(Box::new(Compare::GreaterThan( Expression::BitXor( Box::new(Expression::BitShr( Box::new(Expression::Operand(Operand::Output(0))), @@ -39,8 +45,9 @@ vec![ ), Expression::Constant(0), ))), - ), - ]), + ), + ], + ), stores: None, widths: &[ VMWidth::Lower64, diff --git a/runtime/src/vm/handlers/ror.rs b/runtime/src/vm/handlers/ror.rs index d4139052..7e3ed88d 100644 --- a/runtime/src/vm/handlers/ror.rs +++ b/runtime/src/vm/handlers/ror.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Compare, Effect, Expression, Flags, Operand, Operation}, }, }; @@ -15,18 +15,24 @@ pub fn build(rt: &mut Runtime) { Expression::Operand(Operand::Input(1)), )], flags: Flags::When( -Expression::BitAnd(Box::new(Expression::Operand(Operand::Input(1))), Box::new(Expression::BitOr(Box::new(Expression::SignBit), Box::new(Expression::Constant(0x1f))))), -vec![ - ( - Flag::Carry, - Expression::Compare(Box::new(Compare::BitSet( + Expression::BitAnd( + Box::new(Expression::Operand(Operand::Input(1))), + Box::new(Expression::BitOr( + Box::new(Expression::SignBit), + Box::new(Expression::Constant(0x1f)), + )), + ), + vec![ + ( + Flag::Carry, + Expression::Compare(Box::new(Compare::BitSet( Expression::Operand(Operand::Output(0)), Expression::SignBit, ))), - ), - ( - Flag::Overflow, - Expression::Compare(Box::new(Compare::GreaterThan( + ), + ( + Flag::Overflow, + Expression::Compare(Box::new(Compare::GreaterThan( Expression::BitXor( Box::new(Expression::BitShr( Box::new(Expression::Operand(Operand::Output(0))), @@ -45,8 +51,9 @@ vec![ ), Expression::Constant(0), ))), - ), - ]), + ), + ], + ), stores: None, widths: &[ VMWidth::Lower64, diff --git a/runtime/src/vm/handlers/sar.rs b/runtime/src/vm/handlers/sar.rs index 8e33c30c..aba44ef4 100644 --- a/runtime/src/vm/handlers/sar.rs +++ b/runtime/src/vm/handlers/sar.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Compare, Effect, Expression, Flags, Operand, Operation}, }, }; @@ -15,11 +15,17 @@ pub fn build(rt: &mut Runtime) { Expression::Operand(Operand::Input(1)), )], flags: Flags::When( -Expression::BitAnd(Box::new(Expression::Operand(Operand::Input(1))), Box::new(Expression::BitOr(Box::new(Expression::SignBit), Box::new(Expression::Constant(0x1f))))), -vec![ - ( - Flag::Carry, - Expression::Compare(Box::new(Compare::BitSet( + Expression::BitAnd( + Box::new(Expression::Operand(Operand::Input(1))), + Box::new(Expression::BitOr( + Box::new(Expression::SignBit), + Box::new(Expression::Constant(0x1f)), + )), + ), + vec![ + ( + Flag::Carry, + Expression::Compare(Box::new(Compare::BitSet( Expression::Operand(Operand::Input(0)), Expression::Sub( Box::new(Expression::SignBit), @@ -52,35 +58,34 @@ vec![ )), ), ))), - ), - ( - Flag::Overflow, - Expression::Compare(Box::new(Compare::Equal( + ), + ( + Flag::Overflow, + Expression::Compare(Box::new(Compare::Equal( Expression::Constant(1), Expression::Constant(0), ))), - ), - ( - Flag::Sign, - Expression::Compare(Box::new(Compare::BitSet( + ), + ( + Flag::Sign, + Expression::Compare(Box::new(Compare::BitSet( Expression::Operand(Operand::Output(0)), Expression::SignBit, ))), - ), - ( - Flag::Zero, - Expression::Compare(Box::new(Compare::Equal( + ), + ( + Flag::Zero, + Expression::Compare(Box::new(Compare::Equal( Expression::Operand(Operand::Output(0)), Expression::Constant(0), ))), - ), - ( - Flag::Parity, - Expression::Parity(Box::new(Expression::Operand( - Operand::Output(0), - ))), - ), - ]), + ), + ( + Flag::Parity, + Expression::Parity(Box::new(Expression::Operand(Operand::Output(0)))), + ), + ], + ), stores: None, widths: &[ VMWidth::Lower64, diff --git a/runtime/src/vm/handlers/sbb.rs b/runtime/src/vm/handlers/sbb.rs index d6399207..21c61e27 100644 --- a/runtime/src/vm/handlers/sbb.rs +++ b/runtime/src/vm/handlers/sbb.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Compare, Effect, Expression, Flags, Operand, Operation}, }, }; diff --git a/runtime/src/vm/handlers/semantic/compiler.rs b/runtime/src/vm/handlers/semantic/compiler.rs index 543bab91..4e410993 100644 --- a/runtime/src/vm/handlers/semantic/compiler.rs +++ b/runtime/src/vm/handlers/semantic/compiler.rs @@ -8,10 +8,10 @@ use crate::{ register, runtime::Runtime, vm::{ - bytecode::{Flag, VMReg, VMWidth}, + bytecode::{VMReg, VMWidth}, handlers::semantic::{ - allocator::Allocator, Compare, Effect, Expression, Flags, Operand, Operation, Reference, - Value, + allocator::Allocator, Compare, Effect, Expression, Flags, Operand, Operation, + Reference, Value, }, utils::{bytecode, scratch, vreg}, }, @@ -878,10 +878,6 @@ fn compile_flags(rt: &mut Runtime, allocator: &mut Allocator, flags: &Flags, wid allocator.untrack(register); } - // A conditional flag write stores back to the flag register only when - // the condition holds; otherwise the store is redirected at the local - // slot, so the pre-operation flags are left untouched (a shift or - // rotate with a zero count does not affect the flags). Some(condition) => { let condition = compile_expression(rt, allocator, condition, width); @@ -900,10 +896,10 @@ fn compile_flags(rt: &mut Runtime, allocator: &mut Allocator, flags: &Flags, wid .mov(register, qword_ptr(rbp - allocator.offset(Value::Flags))) .unwrap(); - let offset = rt.mapper.index(VMReg::Flags) as i32 * 8; - let target = allocator.acquire(rt, &[], &[selector, register]); - rt.asm.lea(target, ptr(r12 + offset)).unwrap(); + rt.asm + .lea(target, ptr(r12 + rt.mapper.index(VMReg::Flags) as i32 * 8)) + .unwrap(); let skip = allocator.acquire(rt, &[], &[selector, register, target]); rt.asm diff --git a/runtime/src/vm/handlers/shl.rs b/runtime/src/vm/handlers/shl.rs index 841de8bb..c5589c01 100644 --- a/runtime/src/vm/handlers/shl.rs +++ b/runtime/src/vm/handlers/shl.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Compare, Effect, Expression, Flags, Operand, Operation}, }, }; @@ -15,11 +15,17 @@ pub fn build(rt: &mut Runtime) { Expression::Operand(Operand::Input(1)), )], flags: Flags::When( -Expression::BitAnd(Box::new(Expression::Operand(Operand::Input(1))), Box::new(Expression::BitOr(Box::new(Expression::SignBit), Box::new(Expression::Constant(0x1f))))), -vec![ - ( - Flag::Carry, - Expression::Compare(Box::new(Compare::BitSet( + Expression::BitAnd( + Box::new(Expression::Operand(Operand::Input(1))), + Box::new(Expression::BitOr( + Box::new(Expression::SignBit), + Box::new(Expression::Constant(0x1f)), + )), + ), + vec![ + ( + Flag::Carry, + Expression::Compare(Box::new(Compare::BitSet( Expression::Operand(Operand::Input(0)), Expression::Sub( Box::new(Expression::BitSize), @@ -32,10 +38,10 @@ vec![ )), ), ))), - ), - ( - Flag::Overflow, - Expression::Compare(Box::new(Compare::GreaterThan( + ), + ( + Flag::Overflow, + Expression::Compare(Box::new(Compare::GreaterThan( Expression::BitXor( Box::new(Expression::BitShr( Box::new(Expression::Operand(Operand::Input(0))), @@ -48,28 +54,27 @@ vec![ ), Expression::Constant(0), ))), - ), - ( - Flag::Sign, - Expression::Compare(Box::new(Compare::BitSet( + ), + ( + Flag::Sign, + Expression::Compare(Box::new(Compare::BitSet( Expression::Operand(Operand::Output(0)), Expression::SignBit, ))), - ), - ( - Flag::Zero, - Expression::Compare(Box::new(Compare::Equal( + ), + ( + Flag::Zero, + Expression::Compare(Box::new(Compare::Equal( Expression::Operand(Operand::Output(0)), Expression::Constant(0), ))), - ), - ( - Flag::Parity, - Expression::Parity(Box::new(Expression::Operand( - Operand::Output(0), - ))), - ), - ]), + ), + ( + Flag::Parity, + Expression::Parity(Box::new(Expression::Operand(Operand::Output(0)))), + ), + ], + ), stores: None, widths: &[ VMWidth::Lower64, diff --git a/runtime/src/vm/handlers/shr.rs b/runtime/src/vm/handlers/shr.rs index c6b57ecf..b4a8ebbc 100644 --- a/runtime/src/vm/handlers/shr.rs +++ b/runtime/src/vm/handlers/shr.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Compare, Effect, Expression, Flags, Operand, Operation}, }, }; @@ -15,11 +15,17 @@ pub fn build(rt: &mut Runtime) { Expression::Operand(Operand::Input(1)), )], flags: Flags::When( -Expression::BitAnd(Box::new(Expression::Operand(Operand::Input(1))), Box::new(Expression::BitOr(Box::new(Expression::SignBit), Box::new(Expression::Constant(0x1f))))), -vec![ - ( - Flag::Carry, - Expression::Compare(Box::new(Compare::BitSet( + Expression::BitAnd( + Box::new(Expression::Operand(Operand::Input(1))), + Box::new(Expression::BitOr( + Box::new(Expression::SignBit), + Box::new(Expression::Constant(0x1f)), + )), + ), + vec![ + ( + Flag::Carry, + Expression::Compare(Box::new(Compare::BitSet( Expression::Operand(Operand::Input(0)), Expression::Sub( Box::new(Expression::BitAnd( @@ -32,35 +38,34 @@ vec![ Box::new(Expression::Constant(1)), ), ))), - ), - ( - Flag::Overflow, - Expression::Compare(Box::new(Compare::BitSet( + ), + ( + Flag::Overflow, + Expression::Compare(Box::new(Compare::BitSet( Expression::Operand(Operand::Input(0)), Expression::SignBit, ))), - ), - ( - Flag::Sign, - Expression::Compare(Box::new(Compare::BitSet( + ), + ( + Flag::Sign, + Expression::Compare(Box::new(Compare::BitSet( Expression::Operand(Operand::Output(0)), Expression::SignBit, ))), - ), - ( - Flag::Zero, - Expression::Compare(Box::new(Compare::Equal( + ), + ( + Flag::Zero, + Expression::Compare(Box::new(Compare::Equal( Expression::Operand(Operand::Output(0)), Expression::Constant(0), ))), - ), - ( - Flag::Parity, - Expression::Parity(Box::new(Expression::Operand( - Operand::Output(0), - ))), - ), - ]), + ), + ( + Flag::Parity, + Expression::Parity(Box::new(Expression::Operand(Operand::Output(0)))), + ), + ], + ), stores: None, widths: &[ VMWidth::Lower64, diff --git a/runtime/src/vm/handlers/sub.rs b/runtime/src/vm/handlers/sub.rs index 543bddec..43d080ff 100644 --- a/runtime/src/vm/handlers/sub.rs +++ b/runtime/src/vm/handlers/sub.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Compare, Effect, Expression, Flags, Operand, Operation}, }, }; diff --git a/runtime/src/vm/handlers/trailing_zeros.rs b/runtime/src/vm/handlers/trailing_zeros.rs index d1ce4176..495a6b85 100644 --- a/runtime/src/vm/handlers/trailing_zeros.rs +++ b/runtime/src/vm/handlers/trailing_zeros.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Compare, Effect, Expression, Flags, Operand, Operation}, }, }; diff --git a/runtime/src/vm/handlers/xor.rs b/runtime/src/vm/handlers/xor.rs index 22ad4247..50ba21cc 100644 --- a/runtime/src/vm/handlers/xor.rs +++ b/runtime/src/vm/handlers/xor.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Flags, Compare, Effect, Expression, Operand, Operation}, + handlers::semantic::{self, Compare, Effect, Expression, Flags, Operand, Operation}, }, }; diff --git a/runtime/src/vm/transform/encrypt.rs b/runtime/src/vm/transform/encrypt.rs index bb7a0372..3eee550e 100644 --- a/runtime/src/vm/transform/encrypt.rs +++ b/runtime/src/vm/transform/encrypt.rs @@ -215,23 +215,23 @@ fn walk( } if leaf(&mut operations[i], *addend, *multiplier) { - skip -= 1; + // skip -= 1; - if !operations[i].is_branch() { - if skip == 0 { - let sequence = transform(addend, multiplier, !flags[position]); - let length = sequence.len(); - operations.splice(i + 1..i + 1, sequence); + // if !operations[i].is_branch() { + // if skip == 0 { + // let sequence = transform(addend, multiplier, !flags[position]); + // let length = sequence.len(); + // operations.splice(i + 1..i + 1, sequence); - for _ in 0..length { - trace.push((*addend, *multiplier)); - } + // for _ in 0..length { + // trace.push((*addend, *multiplier)); + // } - i += length; + // i += length; - skip = rng.gen_range(0..=8); - } - } + // skip = rng.gen_range(0..=8); + // } + // } } i += 1; diff --git a/runtime/src/vm/transform/mod.rs b/runtime/src/vm/transform/mod.rs index eaf58eb1..4f28a5f1 100644 --- a/runtime/src/vm/transform/mod.rs +++ b/runtime/src/vm/transform/mod.rs @@ -87,13 +87,15 @@ pub fn deadzones( let mut events = Vec::new(); scan(operations, &mut events, &effect); + let first = events.iter().position(|(_, writes)| *writes); + let mut deadzones = vec![false; events.len()]; let mut live = true; for (i, (reads, writes)) in events.iter().enumerate().rev() { if *writes { - live = false; + live = Some(i) == first; } if !live { deadzones[i] = true; diff --git a/tests/src/instructions.rs b/tests/src/instructions.rs index 246337fc..74feb93c 100644 --- a/tests/src/instructions.rs +++ b/tests/src/instructions.rs @@ -1,5 +1,6 @@ use std::slice; +use iced_x86::code_asm::{eax, ecx, CodeAssembler}; use iced_x86::{ Code, Instruction, InstructionInfoFactory, OpAccess, OpCodeOperandKind, OpKind, Register, RflagsBits, @@ -35,6 +36,32 @@ macro_rules! define { }; } +#[test] +fn test_nested_loop() { + let mut asm = CodeAssembler::new(64).unwrap(); + + let mut outer = asm.create_label(); + let mut inner = asm.create_label(); + + asm.xor(eax, eax).unwrap(); + + asm.set_label(&mut outer).unwrap(); + asm.xor(ecx, ecx).unwrap(); + + asm.set_label(&mut inner).unwrap(); + asm.add(ecx, 1).unwrap(); + asm.cmp(ecx, 31).unwrap(); + asm.jbe(inner).unwrap(); + + asm.add(eax, 1).unwrap(); + asm.cmp(eax, 0x4E1F).unwrap(); + asm.jle(outer).unwrap(); + + let instructions = asm.take_instructions(); + + compare(baseline(), &instructions); +} + define!( Adc_r64_rm64 Adc_rm16_r16 @@ -775,46 +802,50 @@ fn case(instruction: Instruction, memory: Option<&mut [u8]>, immediate: u64) { VMReg::from(instruction.memory_base()), memory.as_mut_ptr() as u64, ); - compare_memory(state, instruction, memory); + compare_memory(state, &[instruction], memory); } else { - compare(state, instruction); + compare(state, &[instruction]); } } -fn compare(state: State, instruction: Instruction) { - compare_memory(state, instruction, &mut []); +fn compare(state: State, instructions: &[Instruction]) { + compare_memory(state, instructions, &mut []); } -fn compare_memory(state: State, instruction: Instruction, memory: &mut [u8]) { +fn compare_memory(state: State, instructions: &[Instruction], memory: &mut [u8]) { let baseline = memory.to_vec(); let mut executor = Executor::new(); - let mut native = executor.run_native(state.clone(), &[instruction]); + let mut native = executor.run_native(state.clone(), instructions); memory.copy_from_slice(&baseline); let mut executor = Executor::new(); - let lifted = bytecode::lift(&[instruction]) - .unwrap_or_else(|| panic!("{instruction} is not implemented")); - + let lifted = bytecode::lift(instructions).unwrap(); let transformed = bytecode::transform(&mut executor.rt.mapper, lifted, |_| 0); let mut bytes = bytecode::assemble(&mut executor.rt.mapper, &transformed); encrypt_block(&mut bytes); decrypt_payload(&mut bytes); - let mut emulated = executor.run_virtual(state.clone(), &bytes); + let mut emulated = executor.run_virtual(state, &bytes); - normalize(&mut native, &mut emulated, instruction); + normalize(&mut native, &mut emulated, instructions); } -fn normalize(native: &mut State, emulated: &mut State, instruction: Instruction) { +fn normalize(native: &mut State, emulated: &mut State, instructions: &[Instruction]) { native.registers.remove(&VMReg::Rsp); emulated.registers.remove(&VMReg::Rsp); + let mask = instructions + .iter() + .fold(RflagsBits::NONE, |mask, instruction| { + mask | instruction.rflags_written() + | instruction.rflags_cleared() + | instruction.rflags_set() + }); + for state in [&mut *native, &mut *emulated] { let flags = state.registers.get_mut(&VMReg::Flags).unwrap(); - let mask = - instruction.rflags_written() | instruction.rflags_cleared() | instruction.rflags_set(); *flags &= ((mask & RflagsBits::CF != 0) as u64 * Flag::Carry.bit64()) | ((mask & RflagsBits::PF != 0) as u64 * Flag::Parity.bit64()) From 8df4ff0ad916c4e82e163e956acac9c984f6bc17 Mon Sep 17 00:00:00 2001 From: RadonCoding <86915746+RadonCoding@users.noreply.github.com> Date: Sat, 26 Sep 2026 23:24:44 +0300 Subject: [PATCH 16/33] chore: cleanup store_register.rs --- example.ps1 | 10 +++---- .../virtualization/attestation/mod.rs | 4 +-- runtime/src/vm/bytecode.rs | 14 +++++----- runtime/src/vm/handlers/discard.rs | 11 +++----- runtime/src/vm/handlers/store_register.rs | 15 +++++++---- runtime/src/vm/transform/encrypt.rs | 26 +++++++++--------- runtime/src/vm/utils/scratch.rs | 11 +++++++- tests/src/instructions.rs | 27 ------------------- 8 files changed, 50 insertions(+), 68 deletions(-) diff --git a/example.ps1 b/example.ps1 index 117efc67..21f0fac3 100644 --- a/example.ps1 +++ b/example.ps1 @@ -1,12 +1,12 @@ param( [Parameter(Mandatory)] [System.IO.DirectoryInfo]$Example, - [string[]]$Args + [string[]]$Arguments ) $ErrorActionPreference = "Stop" -$language = [System.IO.Path]::GetFileName([System.IO.Path]::GetDirectoryName([System.IO.Path]::GetDirectoryName($Example))) +$language = Split-Path (Split-Path $Example -Parent) -Leaf $env:OUTPUT_DIRECTORY = Join-Path $PWD "api/generated" $env:TEMPLATES_DIRECTORY = Join-Path $PWD "api/templates" @@ -15,7 +15,7 @@ cargo build --package markers if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } -cargo build --bin obfuscator $Args +cargo build --bin obfuscator $Arguments if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } @@ -72,9 +72,7 @@ if (-not (Test-Path -LiteralPath $source -PathType Leaf)) { throw "Source executable not found: $source" } -& (Join-Path $PWD "target/debug/obfuscator.exe") ` - --virtualization -v` -$source +& (Join-Path $PWD "target/debug/obfuscator.exe") --virtualization -v $source if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } diff --git a/obfuscator/src/protections/virtualization/attestation/mod.rs b/obfuscator/src/protections/virtualization/attestation/mod.rs index 00efb335..55a69db0 100644 --- a/obfuscator/src/protections/virtualization/attestation/mod.rs +++ b/obfuscator/src/protections/virtualization/attestation/mod.rs @@ -216,8 +216,8 @@ fn correct( let combined = combine_operation(operation, vp0, vp1); let correction = combined ^ key; - instructions.extend(xor(Some(VMReg::Vp0), Some(VMReg::Vt0))); - instructions.extend(xor(Some(VMReg::Vp1), Some(VMReg::Vt0))); + instructions.extend(xor(Some(VMReg::Vp0), Some(VMReg::Vt1))); + instructions.extend(xor(Some(VMReg::Vp1), Some(VMReg::Vt1))); instructions.extend(register_operation(operation)); instructions.extend(immediate(correction)); instructions.extend(xor(None, None)); diff --git a/runtime/src/vm/bytecode.rs b/runtime/src/vm/bytecode.rs index a24540a7..e49aeb19 100644 --- a/runtime/src/vm/bytecode.rs +++ b/runtime/src/vm/bytecode.rs @@ -654,14 +654,14 @@ fn transforms<'a>( picker: &'a RefCell<&'a mut dyn FnMut(&[usize]) -> usize>, ) -> Vec> { vec![ - // Box::new(Peephole), - // Box::new(Permute { picker }), - // Box::new(Scramble), - // Box::new(Mutation), - // Box::new(Indirect), + Box::new(Peephole), + Box::new(Permute { picker }), + Box::new(Scramble), + Box::new(Mutation), + Box::new(Indirect), Box::new(Encrypt), - // Box::new(Permute { picker }), - // Box::new(Peephole), + Box::new(Permute { picker }), + Box::new(Peephole), ] } diff --git a/runtime/src/vm/handlers/discard.rs b/runtime/src/vm/handlers/discard.rs index 883088da..ec2599a1 100644 --- a/runtime/src/vm/handlers/discard.rs +++ b/runtime/src/vm/handlers/discard.rs @@ -1,14 +1,11 @@ -use iced_x86::code_asm::{r12, rax, rcx}; +use iced_x86::code_asm::{r12, r8, rax, rcx}; -use crate::{ - runtime::Runtime, - vm::{bytecode::VMReg, utils::vreg}, -}; +use crate::{runtime::Runtime, vm::utils::scratch}; // unsigned char* (unsigned char*) pub fn build(rt: &mut Runtime) { - // add [...], 0x8 - vreg::add_imm(rt, r12, 0x8, VMReg::VScratch); + // load r8 + scratch::load(rt, r12, r8); // mov rax, rcx rt.asm.mov(rax, rcx).unwrap(); diff --git a/runtime/src/vm/handlers/store_register.rs b/runtime/src/vm/handlers/store_register.rs index 1bb161bd..57df52f6 100644 --- a/runtime/src/vm/handlers/store_register.rs +++ b/runtime/src/vm/handlers/store_register.rs @@ -15,33 +15,38 @@ pub fn build(rt: &mut Runtime) { // r8d -> destination utils::bytecode::read_byte_zx(rt, rcx, r8d); - // load r9 - scratch::load(rt, r12, r9); - utils::width::dispatch( rt, rax, &mut epilogue, Some(Box::new(|rt| { + // load r9 + scratch::load(rt, r12, r9); // mov [r12 + r8*8], r9 rt.asm.mov(ptr(r12 + r8 * 8), r9).unwrap(); })), Some(Box::new(|rt| { - // mov r9d, r9d - rt.asm.mov(r9d, r9d).unwrap(); + // load r9d + scratch::load_32(rt, r12, r9d); // mov [r12 + r8*8], r9 rt.asm.mov(ptr(r12 + r8 * 8), r9).unwrap(); })), None, Some(Box::new(|rt| { + // load r9 + scratch::load(rt, r12, r9); // mov [r12 + r8*8], r9w rt.asm.mov(ptr(r12 + r8 * 8), r9w).unwrap(); })), Some(Box::new(|rt| { + // load r9 + scratch::load(rt, r12, r9); // mov [r12 + r8*8 + 0x1], r9b rt.asm.mov(ptr(r12 + r8 * 8 + 0x1), r9b).unwrap(); })), Some(Box::new(|rt| { + // load r9 + scratch::load(rt, r12, r9); // mov [r12 + r8*8], r9b rt.asm.mov(ptr(r12 + r8 * 8), r9b).unwrap(); })), diff --git a/runtime/src/vm/transform/encrypt.rs b/runtime/src/vm/transform/encrypt.rs index 3eee550e..bb7a0372 100644 --- a/runtime/src/vm/transform/encrypt.rs +++ b/runtime/src/vm/transform/encrypt.rs @@ -215,23 +215,23 @@ fn walk( } if leaf(&mut operations[i], *addend, *multiplier) { - // skip -= 1; + skip -= 1; - // if !operations[i].is_branch() { - // if skip == 0 { - // let sequence = transform(addend, multiplier, !flags[position]); - // let length = sequence.len(); - // operations.splice(i + 1..i + 1, sequence); + if !operations[i].is_branch() { + if skip == 0 { + let sequence = transform(addend, multiplier, !flags[position]); + let length = sequence.len(); + operations.splice(i + 1..i + 1, sequence); - // for _ in 0..length { - // trace.push((*addend, *multiplier)); - // } + for _ in 0..length { + trace.push((*addend, *multiplier)); + } - // i += length; + i += length; - // skip = rng.gen_range(0..=8); - // } - // } + skip = rng.gen_range(0..=8); + } + } } i += 1; diff --git a/runtime/src/vm/utils/scratch.rs b/runtime/src/vm/utils/scratch.rs index c1ab3808..4a63d7c2 100644 --- a/runtime/src/vm/utils/scratch.rs +++ b/runtime/src/vm/utils/scratch.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{bytecode::VMReg, utils::vreg}, }; -use iced_x86::code_asm::{ptr, r10, AsmRegister64, AsmRegisterXmm, AsmRegisterYmm}; +use iced_x86::code_asm::{ptr, r10, AsmRegister32, AsmRegister64, AsmRegisterXmm, AsmRegisterYmm}; pub fn store(rt: &mut Runtime, base: AsmRegister64, src: AsmRegister64) { // sub [...], 0x8 @@ -22,6 +22,15 @@ pub fn load(rt: &mut Runtime, base: AsmRegister64, dst: AsmRegister64) { rt.asm.mov(dst, ptr(r10 - 0x8)).unwrap(); } +pub fn load_32(rt: &mut Runtime, base: AsmRegister64, dst: AsmRegister32) { + // add [...], 0x8 + vreg::add_imm(rt, base, 0x8, VMReg::VScratch); + // mov r10, [...] + vreg::load_reg(rt, base, VMReg::VScratch, r10); + // mov ..., [r10 - 0x8] + rt.asm.mov(dst, ptr(r10 - 0x8)).unwrap(); +} + pub fn store_128(rt: &mut Runtime, base: AsmRegister64, src: AsmRegisterXmm) { // sub [...], 0x10 vreg::sub_imm(rt, base, 0x10, VMReg::VScratch); diff --git a/tests/src/instructions.rs b/tests/src/instructions.rs index 74feb93c..26ce07da 100644 --- a/tests/src/instructions.rs +++ b/tests/src/instructions.rs @@ -1,6 +1,5 @@ use std::slice; -use iced_x86::code_asm::{eax, ecx, CodeAssembler}; use iced_x86::{ Code, Instruction, InstructionInfoFactory, OpAccess, OpCodeOperandKind, OpKind, Register, RflagsBits, @@ -36,32 +35,6 @@ macro_rules! define { }; } -#[test] -fn test_nested_loop() { - let mut asm = CodeAssembler::new(64).unwrap(); - - let mut outer = asm.create_label(); - let mut inner = asm.create_label(); - - asm.xor(eax, eax).unwrap(); - - asm.set_label(&mut outer).unwrap(); - asm.xor(ecx, ecx).unwrap(); - - asm.set_label(&mut inner).unwrap(); - asm.add(ecx, 1).unwrap(); - asm.cmp(ecx, 31).unwrap(); - asm.jbe(inner).unwrap(); - - asm.add(eax, 1).unwrap(); - asm.cmp(eax, 0x4E1F).unwrap(); - asm.jle(outer).unwrap(); - - let instructions = asm.take_instructions(); - - compare(baseline(), &instructions); -} - define!( Adc_r64_rm64 Adc_rm16_r16 From 36dee0fd642cb4980e3ddb326080c25df4a88722 Mon Sep 17 00:00:00 2001 From: RadonCoding <86915746+RadonCoding@users.noreply.github.com> Date: Sun, 27 Sep 2026 17:19:35 +0300 Subject: [PATCH 17/33] chore: exception handling tests --- examples/cpp/{seh => exception-cxx}/main.cpp | 1 - obfuscator/src/engine.rs | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) rename examples/cpp/{seh => exception-cxx}/main.cpp (99%) diff --git a/examples/cpp/seh/main.cpp b/examples/cpp/exception-cxx/main.cpp similarity index 99% rename from examples/cpp/seh/main.cpp rename to examples/cpp/exception-cxx/main.cpp index 7c916176..328003b1 100644 --- a/examples/cpp/seh/main.cpp +++ b/examples/cpp/exception-cxx/main.cpp @@ -31,6 +31,5 @@ int main() { std::cout << "OK" << std::endl; - return 0; } \ No newline at end of file diff --git a/obfuscator/src/engine.rs b/obfuscator/src/engine.rs index f905452d..173eb8d9 100644 --- a/obfuscator/src/engine.rs +++ b/obfuscator/src/engine.rs @@ -437,6 +437,7 @@ impl<'a> Engine<'a> { | FlowControl::UnconditionalBranch | FlowControl::Call => { let target = instruction.near_branch_target() as u32; + if code_section.has_rva(RVA(target)) { references.insert(target); } @@ -473,6 +474,7 @@ impl<'a> Engine<'a> { } previous.clear(); + cursor += instruction.len(); } From ad560522beef79d8b817dd4d3bcd8fe6b15c8d04 Mon Sep 17 00:00:00 2001 From: unknown <86915746+RadonCoding@users.noreply.github.com> Date: Sun, 27 Sep 2026 22:23:58 +0300 Subject: [PATCH 18/33] fix: fixed CXX exceptions --- examples/cpp/exception-cxx/main.cpp | 4 ---- obfuscator/src/protections/virtualization/mod.rs | 2 ++ runtime/src/lib.rs | 4 ++-- runtime/src/vm/functions/dispatch.rs | 8 ++++---- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/examples/cpp/exception-cxx/main.cpp b/examples/cpp/exception-cxx/main.cpp index 328003b1..f40975ca 100644 --- a/examples/cpp/exception-cxx/main.cpp +++ b/examples/cpp/exception-cxx/main.cpp @@ -20,8 +20,6 @@ int main() { throw 0x12345678; } catch (int value) { - std::cout << "caught" << std::endl; - b = static_cast(value); } @@ -29,7 +27,5 @@ int main() { assert(a == b); - std::cout << "OK" << std::endl; - return 0; } \ No newline at end of file diff --git a/obfuscator/src/protections/virtualization/mod.rs b/obfuscator/src/protections/virtualization/mod.rs index 57bac154..94532539 100644 --- a/obfuscator/src/protections/virtualization/mod.rs +++ b/obfuscator/src/protections/virtualization/mod.rs @@ -370,6 +370,8 @@ impl Protection for Virtualization { asm.reset(); + asm.nop().unwrap(); + asm.jmp(trampoline_rva as u64).unwrap(); let branch = asm.assemble(rva as u64).unwrap(); diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 21aac2ed..6f95af8e 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -23,7 +23,7 @@ pub const VM_DEBUG_SIZE: u64 = 0x100; // PUSH imm32 + CALL rel32 pub const VM_DISPATCH_SIZE: usize = 10; -// JMP rel32 -pub const VM_TRAMPOLINE_SIZE: usize = 5; +// NOP + JMP rel32 +pub const VM_TRAMPOLINE_SIZE: usize = 6; pub const VM_INTEGRITY_QWORD: u64 = 0xFA11ED175001FA11; diff --git a/runtime/src/vm/functions/dispatch.rs b/runtime/src/vm/functions/dispatch.rs index bfde4c8e..f6901274 100644 --- a/runtime/src/vm/functions/dispatch.rs +++ b/runtime/src/vm/functions/dispatch.rs @@ -284,12 +284,12 @@ pub fn build(rt: &mut Runtime) { rt.asm.je(epilogue).unwrap(); // Follow an indirect JMP rel32 entry into its trampoline: - // cmp [rax], 0xE9 - rt.asm.cmp(byte_ptr(rax), 0xE9).unwrap(); + // cmp [rax], 0x90 + rt.asm.cmp(byte_ptr(rax), 0x90).unwrap(); // jne ... rt.asm.jne(resolved).unwrap(); - // movsxd r9, [rax + 0x1] - rt.asm.movsxd(r9, dword_ptr(rax + 0x1)).unwrap(); + // movsxd r9, [rax + 0x2] + rt.asm.movsxd(r9, dword_ptr(rax + 0x2)).unwrap(); // lea rax, [rax + ...] rt.asm.lea(rax, ptr(rax + VM_TRAMPOLINE_SIZE)).unwrap(); // add rax, r9 From a3db664396c86620e64668ba7b79d6eb3b5a1281 Mon Sep 17 00:00:00 2001 From: RadonCoding <86915746+RadonCoding@users.noreply.github.com> Date: Mon, 28 Sep 2026 06:12:58 +0300 Subject: [PATCH 19/33] Update exceptions.rs --- obfuscator/src/exceptions.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/obfuscator/src/exceptions.rs b/obfuscator/src/exceptions.rs index b9cabb99..75bd54ab 100644 --- a/obfuscator/src/exceptions.rs +++ b/obfuscator/src/exceptions.rs @@ -29,7 +29,6 @@ pub fn get_exception_handlers(pe: &VecPE) -> HashSet { for rf in functions { handlers.insert(rf.begin_address); - handlers.insert(rf.end_address); if rf.unwind_info_address != 0 { unwinds.push(rf.unwind_info_address); From 1d7c36249fef0d1867a083e94fd861d230328ae9 Mon Sep 17 00:00:00 2001 From: RadonCoding <86915746+RadonCoding@users.noreply.github.com> Date: Mon, 28 Sep 2026 06:56:28 +0300 Subject: [PATCH 20/33] Update exceptions.rs --- obfuscator/src/exceptions.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/obfuscator/src/exceptions.rs b/obfuscator/src/exceptions.rs index 75bd54ab..ef0ceda9 100644 --- a/obfuscator/src/exceptions.rs +++ b/obfuscator/src/exceptions.rs @@ -64,19 +64,19 @@ fn get_exception_unwinders(pe: &VecPE, rva: u32, handlers: &mut HashSet) { return; } - let mut pos = 4 + codes * 2; + let mut position = 4 + codes * 2; - if pos % 4 != 0 { - pos += 4 - (pos % 4); + if position % 4 != 0 { + position += 4 - (position % 4); } if flags & 0x04 != 0 { - let bytes = match pe.get_slice_ref::(offset, pos + 12) { + let bytes = match pe.get_slice_ref::(offset, position + 12) { Ok(b) => b, Err(_) => return, }; - let chained = u32::from_le_bytes(bytes[pos + 8..pos + 12].try_into().unwrap()); + let chained = u32::from_le_bytes(bytes[pos + 8..position + 12].try_into().unwrap()); handlers.insert(chained); @@ -89,16 +89,16 @@ fn get_exception_unwinders(pe: &VecPE, rva: u32, handlers: &mut HashSet) { return; } - let bytes = match pe.get_slice_ref::(offset, pos + 4) { + let bytes = match pe.get_slice_ref::(offset, position + 4) { Ok(b) => b, Err(_) => return, }; - let handler = u32::from_le_bytes(bytes[pos..pos + 4].try_into().unwrap()); + let handler = u32::from_le_bytes(bytes[position..position + 4].try_into().unwrap()); handlers.insert(handler); - pos += 4; + position += 4; let section = match pe .get_section_table() @@ -114,7 +114,7 @@ fn get_exception_unwinders(pe: &VecPE, rva: u32, handlers: &mut HashSet) { .translate(PETranslation::Memory(section.virtual_address)) .unwrap(); let end = base + section.size_of_raw_data as usize; - let start = offset + pos; + let start = offset + position; if start >= end { return; From 1d458b42da40866f352b9c8cb0fe96e91486ea9f Mon Sep 17 00:00:00 2001 From: RadonCoding <86915746+RadonCoding@users.noreply.github.com> Date: Mon, 28 Sep 2026 07:03:13 +0300 Subject: [PATCH 21/33] Update exceptions.rs --- obfuscator/src/exceptions.rs | 34 +--------------------------------- 1 file changed, 1 insertion(+), 33 deletions(-) diff --git a/obfuscator/src/exceptions.rs b/obfuscator/src/exceptions.rs index ef0ceda9..05a8ec03 100644 --- a/obfuscator/src/exceptions.rs +++ b/obfuscator/src/exceptions.rs @@ -76,7 +76,7 @@ fn get_exception_unwinders(pe: &VecPE, rva: u32, handlers: &mut HashSet) { Err(_) => return, }; - let chained = u32::from_le_bytes(bytes[pos + 8..position + 12].try_into().unwrap()); + let chained = u32::from_le_bytes(bytes[position + 8..position + 12].try_into().unwrap()); handlers.insert(chained); @@ -97,36 +97,4 @@ fn get_exception_unwinders(pe: &VecPE, rva: u32, handlers: &mut HashSet) { let handler = u32::from_le_bytes(bytes[position..position + 4].try_into().unwrap()); handlers.insert(handler); - - position += 4; - - let section = match pe - .get_section_table() - .unwrap() - .iter() - .find(|s| rva >= s.virtual_address.0 && rva < s.virtual_address.0 + s.virtual_size) - { - Some(s) => s, - None => return, - }; - - let base = pe - .translate(PETranslation::Memory(section.virtual_address)) - .unwrap(); - let end = base + section.size_of_raw_data as usize; - let start = offset + position; - - if start >= end { - return; - } - - let bytes = match pe.get_slice_ref::(start, end - start) { - Ok(b) => b, - Err(_) => return, - }; - - for chunk in bytes.chunks_exact(4) { - let rva = u32::from_le_bytes(chunk.try_into().unwrap()); - handlers.insert(rva); - } } From 5a2245b7cedf1af92411ed7a9457001f438c25f7 Mon Sep 17 00:00:00 2001 From: unknown <86915746+RadonCoding@users.noreply.github.com> Date: Mon, 28 Sep 2026 18:48:45 +0300 Subject: [PATCH 22/33] chore: changed trampolines to calls --- .../src/protections/virtualization/mod.rs | 74 +++++++++++-------- runtime/src/lib.rs | 7 +- runtime/src/vm/functions/dispatch.rs | 35 +++++++-- runtime/src/vm/functions/lookup.rs | 39 +++++----- 4 files changed, 93 insertions(+), 62 deletions(-) diff --git a/obfuscator/src/protections/virtualization/mod.rs b/obfuscator/src/protections/virtualization/mod.rs index 94532539..7f16f7af 100644 --- a/obfuscator/src/protections/virtualization/mod.rs +++ b/obfuscator/src/protections/virtualization/mod.rs @@ -7,14 +7,14 @@ use crate::engine::Engine; use crate::protections::Protection; use exe::{Buffer, SectionCharacteristics}; use exe::{PE, RVA}; -use iced_x86::code_asm::CodeAssembler; +use iced_x86::code_asm::{dword_ptr, rsp, CodeAssembler}; use iced_x86::Mnemonic; use logger::{debug, info}; use rand::Rng; use runtime::runtime::{DataDef, FnDef}; use runtime::vm::bytecode::{self}; use runtime::vm::encoders::Encode; -use runtime::{VM_DISPATCH_SIZE, VM_TRAMPOLINE_SIZE}; +use runtime::{VM_DISPATCH_SIZE, VM_REDIRECT_SIZE, VM_TRAMPOLINE_SIZE}; mod attestation; pub mod crypt; @@ -25,7 +25,7 @@ pub struct Virtualization { programs: Vec>>, groups: Vec>, virtualized: HashMap, - trampolines: HashMap, + redirects: HashMap, duplicates: usize, blocked: usize, missing: HashMap, @@ -169,10 +169,11 @@ impl Protection for Virtualization { for (_, group) in self.programs.iter().zip(&self.groups) { for &rva in group { // Store the index of this VM-block's VM-table entry - let index = vtable.len() / 8; + let index = vtable.len() / 12; self.virtualized.insert(rva, index); - // Store placeholders for stub displacement and bytecode offset + // Store placeholders for exit displacement, entry displacement, and bytecode offset + vtable.extend_from_slice(&0u32.to_le_bytes()); vtable.extend_from_slice(&0u32.to_le_bytes()); vtable.extend_from_slice(&0u32.to_le_bytes()); } @@ -181,13 +182,13 @@ impl Protection for Virtualization { // Reserve a trampoline slot for each block too small for an inline stub for block in &engine.blocks { if self.virtualized.contains_key(&block.rva) && block.size < VM_DISPATCH_SIZE { - self.trampolines.insert(block.rva, self.trampolines.len()); + self.redirects.insert(block.rva, self.redirects.len()); } } engine.rt.define_data_bytes( DataDef::VmTrampolines, - &vec![0u8; self.trampolines.len() * VM_DISPATCH_SIZE], + &vec![0u8; self.redirects.len() * VM_REDIRECT_SIZE], ); if engine.args.verbose { @@ -246,7 +247,7 @@ impl Protection for Virtualization { let index = self.virtualized[&rva]; unsafe { vtable - .add(index * 8 + size_of::()) + .add(index * 12 + 2 * size_of::()) .copy_from(offset.to_le_bytes().as_ptr(), size_of::()); } } @@ -290,11 +291,11 @@ impl Protection for Virtualization { let ventry_rva = engine.rt.lookup(engine.rt.function_labels[&FnDef::VmEntry]); - let trampolines_rva = engine + let redirects_rva = engine .rt .lookup(engine.rt.data_labels[&DataDef::VmTrampolines]) as u32; - let trampolines_offset = engine.pe.translate(RVA(trampolines_rva).into()).unwrap(); + let trampolines_offset = engine.pe.translate(RVA(redirects_rva).into()).unwrap(); let trampolines = unsafe { engine.pe.as_ptr().add(trampolines_offset) as *mut u8 }; for i in 0..engine.blocks.len() { @@ -329,50 +330,59 @@ impl Protection for Virtualization { assert_eq!(dispatch1.len(), dispatch2.len()); - // Patch the stub displacement placeholder in the VM-table + // Patch the exit and entry displacement placeholders in the table unsafe { - let displacement = (size - dispatch2.len()) as u32; + let exit = (rva as i64 + size as i64 - return_address as i64) as i32; + let entry = (rva as i64 - return_address as i64) as i32; + vtable + .add(vtable_index * 12) + .copy_from((exit as u32).to_le_bytes().as_ptr(), size_of::()); vtable - .add(vtable_index * 8) - .copy_from(displacement.to_le_bytes().as_ptr(), size_of::()); + .add(vtable_index * 12 + size_of::()) + .copy_from((entry as u32).to_le_bytes().as_ptr(), size_of::()); } engine.replace(i, &dispatch2); } else { - let trampoline = self.trampolines[&rva]; - let trampoline_rva = trampolines_rva + (trampoline * VM_DISPATCH_SIZE) as u32; + let redirect = self.redirects[&rva]; + let redirect_rva = redirects_rva + (redirect * VM_REDIRECT_SIZE) as u32; let mut asm = CodeAssembler::new(engine.bitness).unwrap(); - let return_address = trampoline_rva + VM_DISPATCH_SIZE as u32; + let return_address = redirect_rva + VM_REDIRECT_SIZE as u32; - asm.push((vtable_index as i32 | 0x10000000) ^ return_address as i32) - .unwrap(); + asm.mov( + dword_ptr(rsp), + (vtable_index as i32 | 0x10000000) ^ return_address as i32, + ) + .unwrap(); asm.call(ventry_rva).unwrap(); - let dispatch = asm.assemble(trampoline_rva as u64).unwrap(); - assert_eq!(dispatch.len(), VM_DISPATCH_SIZE); + let dispatch = asm.assemble(redirect_rva as u64).unwrap(); + + assert_eq!(dispatch.len(), VM_REDIRECT_SIZE); unsafe { trampolines - .add(trampoline * VM_DISPATCH_SIZE) - .copy_from(dispatch.as_ptr(), VM_DISPATCH_SIZE); + .add(redirect * VM_REDIRECT_SIZE) + .copy_from(dispatch.as_ptr(), VM_REDIRECT_SIZE); } - // Patch the stub displacement placeholder in the VM-table to redirect to original block + // Patch the exit and entry displacement placeholders in the table unsafe { - let displacement = (rva as i64 + size as i64 - return_address as i64) as i32; - vtable.add(vtable_index * 8).copy_from( - (displacement as u32).to_le_bytes().as_ptr(), - size_of::(), - ); + let exit = (rva as i64 + size as i64 - return_address as i64) as i32; + let entry = (rva as i64 - return_address as i64) as i32; + vtable + .add(vtable_index * 12) + .copy_from((exit as u32).to_le_bytes().as_ptr(), size_of::()); + vtable + .add(vtable_index * 12 + size_of::()) + .copy_from((entry as u32).to_le_bytes().as_ptr(), size_of::()); } asm.reset(); - asm.nop().unwrap(); - - asm.jmp(trampoline_rva as u64).unwrap(); + asm.call(redirect_rva as u64).unwrap(); let branch = asm.assemble(rva as u64).unwrap(); diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 6f95af8e..1b5d7964 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -23,7 +23,10 @@ pub const VM_DEBUG_SIZE: u64 = 0x100; // PUSH imm32 + CALL rel32 pub const VM_DISPATCH_SIZE: usize = 10; -// NOP + JMP rel32 -pub const VM_TRAMPOLINE_SIZE: usize = 6; +// MOV dword [rsp], imm32 + CALL rel32 +pub const VM_REDIRECT_SIZE: usize = 12; + +// CALL rel32 +pub const VM_TRAMPOLINE_SIZE: usize = 5; pub const VM_INTEGRITY_QWORD: u64 = 0xFA11ED175001FA11; diff --git a/runtime/src/vm/functions/dispatch.rs b/runtime/src/vm/functions/dispatch.rs index f6901274..caa261da 100644 --- a/runtime/src/vm/functions/dispatch.rs +++ b/runtime/src/vm/functions/dispatch.rs @@ -10,7 +10,7 @@ use crate::{ bytecode::{VMOp, VMReg}, utils::{self, lock}, }, - VM_DISPATCH_SIZE, VM_INTEGRITY_QWORD, VM_TRAMPOLINE_SIZE, + VM_DISPATCH_SIZE, VM_INTEGRITY_QWORD, VM_REDIRECT_SIZE, VM_TRAMPOLINE_SIZE, }; #[cfg(feature = "profile")] @@ -79,6 +79,8 @@ pub fn build(rt: &mut Runtime) { let mut check_suspend = rt.asm.create_label(); let mut check_exit = rt.asm.create_label(); let mut resolved = rt.asm.create_label(); + let mut trampoline = rt.asm.create_label(); + let mut lookup = rt.asm.create_label(); let mut tamper = rt.asm.create_label(); let mut terminate = rt.asm.create_label(); let mut epilogue = rt.asm.create_label(); @@ -283,13 +285,13 @@ pub fn build(rt: &mut Runtime) { // je ... rt.asm.je(epilogue).unwrap(); - // Follow an indirect JMP rel32 entry into its trampoline: - // cmp [rax], 0x90 - rt.asm.cmp(byte_ptr(rax), 0x90).unwrap(); + // Follow an indirect CALL rel32 entry into its trampoline: + // cmp [rax], 0xE8 + rt.asm.cmp(byte_ptr(rax), 0xE8).unwrap(); // jne ... rt.asm.jne(resolved).unwrap(); - // movsxd r9, [rax + 0x2] - rt.asm.movsxd(r9, dword_ptr(rax + 0x2)).unwrap(); + // movsxd r9, [rax + 0x1] + rt.asm.movsxd(r9, dword_ptr(rax + 0x1)).unwrap(); // lea rax, [rax + ...] rt.asm.lea(rax, ptr(rax + VM_TRAMPOLINE_SIZE)).unwrap(); // add rax, r9 @@ -300,14 +302,31 @@ pub fn build(rt: &mut Runtime) { // cmp [rax], 0x68 rt.asm.cmp(byte_ptr(rax), 0x68).unwrap(); // jne ... - rt.asm.jne(epilogue).unwrap(); + rt.asm.jne(trampoline).unwrap(); // mov edx, [rax + 0x1] rt.asm.mov(edx, ptr(rax + 0x1)).unwrap(); - // add rax, ... rt.asm.add(rax, VM_DISPATCH_SIZE as i32).unwrap(); + // jmp ... + rt.asm.jmp(lookup).unwrap(); + } + + rt.asm.set_label(&mut trampoline).unwrap(); + { + // cmp [rax], 0xC7 + rt.asm.cmp(byte_ptr(rax), 0xC7).unwrap(); + // jne ... + rt.asm.jne(epilogue).unwrap(); + + // mov edx, [rax + 0x3] + rt.asm.mov(edx, ptr(rax + 0x3)).unwrap(); + // add rax, ... + rt.asm.add(rax, VM_REDIRECT_SIZE as i32).unwrap(); + } + rt.asm.set_label(&mut lookup).unwrap(); + { // mov rcx, rax rt.asm.mov(rcx, rax).unwrap(); // call ... diff --git a/runtime/src/vm/functions/lookup.rs b/runtime/src/vm/functions/lookup.rs index 2d5830e5..340987f1 100644 --- a/runtime/src/vm/functions/lookup.rs +++ b/runtime/src/vm/functions/lookup.rs @@ -6,7 +6,6 @@ use crate::{ bytecode::VMReg, utils::{self}, }, - VM_DISPATCH_SIZE, }; // unsigned char* (unsigned char*, unsigned int) @@ -27,30 +26,30 @@ pub fn build(rt: &mut Runtime) { rt.asm .lea(r8, ptr(rt.data_labels[&DataDef::VmTable])) .unwrap(); - // lea r8, [r8 + rdx*8] - rt.asm.lea(r8, ptr(r8 + rdx * 8)).unwrap(); + // lea rax, [rdx + rdx*2] + rt.asm.lea(rax, ptr(rdx + rdx * 2)).unwrap(); + // lea r8, [r8 + rax*4] + rt.asm.lea(r8, ptr(r8 + rax * 4)).unwrap(); - // Read the displacement and the offset into bytecode from the table: + // Apply the exit displacement of the caller stub to the return address: // movsxd rax, [r8] rt.asm.movsxd(rax, dword_ptr(r8)).unwrap(); - // mov edx, [r8 + 0x4] - rt.asm.mov(edx, ptr(r8 + 0x4)).unwrap(); + // add rax, rcx + rt.asm.add(rax, rcx).unwrap(); + // mov [r12 + ...], rax + utils::vreg::store_reg(rt, r12, rax, VMReg::NExit); - // Apply the displacement of the caller stub to the native exit point: - // mov r8, rcx - rt.asm.mov(r8, rcx).unwrap(); - // add r8, rax - rt.asm.add(r8, rax).unwrap(); - // mov [r12 + ...], r8 - utils::vreg::store_reg(rt, r12, r8, VMReg::NExit); + // Apply the entry displacement of the caller stub to the return address: + // movsxd rax, [r8 + 0x4] + rt.asm.movsxd(rax, dword_ptr(r8 + 0x4)).unwrap(); + // add rax, rcx + rt.asm.add(rax, rcx).unwrap(); + // mov [r12 + ...], rax + utils::vreg::store_reg(rt, r12, rax, VMReg::NEntry); - // Apply the displacement of the caller stub to the native entry point: - // mov r8, rcx - rt.asm.mov(r8, rcx).unwrap(); - // sub r8, ... - rt.asm.sub(r8, VM_DISPATCH_SIZE as i32).unwrap(); - // mov [r12 + ...], r8 - utils::vreg::store_reg(rt, r12, r8, VMReg::NEntry); + // Read the offset into bytecode from the table: + // mov edx, [r8 + 0x8] + rt.asm.mov(edx, ptr(r8 + 0x8)).unwrap(); // Compute the block pointer from the offset into bytecode: // lea rax, [...] From 1948a4acfceb7afbc55b102c86a609245c8bf257 Mon Sep 17 00:00:00 2001 From: unknown <86915746+RadonCoding@users.noreply.github.com> Date: Wed, 30 Sep 2026 06:53:41 +0300 Subject: [PATCH 23/33] chore: changed markers to use section, improved attestation, implemented xchg, xadd, cmpxchg as semantics --- .github/workflows/generate-templates.yml | 28 -- .github/workflows/validate-examples.yml | 59 +-- Cargo.toml | 2 +- README.md | 2 +- api/binsafe.hpp | 9 + api/binsafe.rs | 8 + api/generated/binsafe.hpp | 10 - api/generated/binsafe.rs | 17 - api/templates/binsafe.hpp | 10 - api/templates/binsafe.rs | 17 - example.ps1 | 7 - examples/cpp/bench/main.cpp | 71 ++- examples/cpp/exception-cxx/main.cpp | 31 +- examples/cpp/hello/main.cpp | 13 +- examples/cpp/multithread/main.cpp | 42 ++ examples/rust/hello/src/main.rs | 12 +- markers/Cargo.toml | 6 - markers/build.rs | 44 -- markers/src/lib.rs | 27 -- obfuscator/Cargo.toml | 1 - obfuscator/src/engine.rs | 117 +---- .../virtualization/attestation/anti_debug.rs | 16 +- .../virtualization/attestation/anti_tamper.rs | 5 +- .../virtualization/attestation/mod.rs | 64 ++- .../protections/virtualization/language.rs | 9 +- runtime/src/lib.rs | 2 +- runtime/src/runtime.rs | 9 +- runtime/src/{register.rs => utils.rs} | 14 +- runtime/src/vm/handlers/compare_exchange.rs | 170 +++---- runtime/src/vm/handlers/exchange.rs | 71 +-- runtime/src/vm/handlers/exchange_add.rs | 151 +++--- runtime/src/vm/handlers/flags.rs | 36 -- runtime/src/vm/handlers/mod.rs | 1 - runtime/src/vm/handlers/semantic/compiler.rs | 198 +++++++- runtime/src/vm/handlers/semantic/mod.rs | 129 ++--- runtime/src/vm/transform/encrypt.rs | 20 +- tests/src/constants.rs | 4 +- tests/src/instructions.rs | 450 +++++++++--------- 38 files changed, 908 insertions(+), 974 deletions(-) delete mode 100644 .github/workflows/generate-templates.yml create mode 100644 api/binsafe.hpp create mode 100644 api/binsafe.rs delete mode 100644 api/generated/binsafe.hpp delete mode 100644 api/generated/binsafe.rs delete mode 100644 api/templates/binsafe.hpp delete mode 100644 api/templates/binsafe.rs create mode 100644 examples/cpp/multithread/main.cpp delete mode 100644 markers/Cargo.toml delete mode 100644 markers/build.rs delete mode 100644 markers/src/lib.rs rename runtime/src/{register.rs => utils.rs} (75%) delete mode 100644 runtime/src/vm/handlers/flags.rs diff --git a/.github/workflows/generate-templates.yml b/.github/workflows/generate-templates.yml deleted file mode 100644 index 19e13a3e..00000000 --- a/.github/workflows/generate-templates.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Generate Templates - -on: - push: - branches: - - main -jobs: - generate-templates: - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - - name: Generate templates - env: - OUTPUT_DIRECTORY: ${{ github.workspace }}/api/generated - TEMPLATES_DIRECTORY: ${{ github.workspace }}/api/templates - run: cargo build --package markers - - - name: Commit and push - uses: EndBug/add-and-commit@v9 - with: - default_author: github_actions - add: "api/generated" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/validate-examples.yml b/.github/workflows/validate-examples.yml index d8101a72..fd7eb2b9 100644 --- a/.github/workflows/validate-examples.yml +++ b/.github/workflows/validate-examples.yml @@ -9,24 +9,8 @@ on: - main types: [opened, synchronize] jobs: - discover: - runs-on: ubuntu-latest - outputs: - examples: ${{ steps.discover.outputs.examples }} - steps: - - uses: actions/checkout@v4 - - - id: discover - run: | - examples=$(find examples -mindepth 2 -maxdepth 2 -type d | jq -R -s -c 'split("\n") | map(select(length > 0))') - echo "examples=$examples" >> $GITHUB_OUTPUT - validate: - needs: [discover] runs-on: ubuntu-latest - strategy: - matrix: - example: ${{ fromJson(needs.discover.outputs.examples) }} steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable @@ -44,45 +28,14 @@ jobs: sudo apt-get update sudo apt-get install -y mingw-w64 wine wine32 wine64 - - name: Generate templates - env: - OUTPUT_DIRECTORY: ${{ github.workspace }}/api/generated - TEMPLATES_DIRECTORY: ${{ github.workspace }}/api/templates - run: cargo build --package markers - - name: Build for Windows run: cargo build --bin obfuscator --release --target x86_64-pc-windows-gnu - - name: Build examples - run: | - language=$(echo "${{ matrix.example }}" | cut -d/ -f2) - case $language in - rust) cargo build --manifest-path ${{ matrix.example }}/Cargo.toml --release --target x86_64-pc-windows-gnu ;; - cpp) x86_64-w64-mingw32-g++ ${{ matrix.example }}/main.cpp -o ${{ matrix.example }}/example.exe -static-libgcc -static-libstdc++ ;; - esac + - name: Build example + run: x86_64-w64-mingw32-g++ examples/cpp/hello/main.cpp -o example.exe - - name: Obfuscate examples - run: | - language=$(echo "${{ matrix.example }}" | cut -d/ -f2) - case $language in - rust) - name=$(cargo metadata --manifest-path ${{ matrix.example }}/Cargo.toml --no-deps --format-version 1 | jq -r '.packages[0].name') - wine target/x86_64-pc-windows-gnu/release/obfuscator.exe ${{ matrix.example }}/target/x86_64-pc-windows-gnu/release/$name.exe --virtualization - ;; - cpp) - wine target/x86_64-pc-windows-gnu/release/obfuscator.exe ${{ matrix.example }}/example.exe --virtualization - ;; - esac + - name: Obfuscate example + run: wine target/x86_64-pc-windows-gnu/release/obfuscator.exe example.exe --virtualization - - name: Validate examples - run: | - language=$(echo "${{ matrix.example }}" | cut -d/ -f2) - case $language in - rust) - name=$(cargo metadata --manifest-path ${{ matrix.example }}/Cargo.toml --no-deps --format-version 1 | jq -r '.packages[0].name') - wine ${{ matrix.example }}/target/x86_64-pc-windows-gnu/release/$name.exe - ;; - cpp) - wine ${{ matrix.example }}/example.exe - ;; - esac + - name: Validate example + run: wine example.exe diff --git a/Cargo.toml b/Cargo.toml index 6f440681..605c06a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,4 @@ [workspace] -members = ["logger", "obfuscator", "runtime", "tests", "markers"] +members = ["logger", "obfuscator", "runtime", "tests"] resolver = "2" exclude = ["examples"] diff --git a/README.md b/README.md index 4d361276..80de41f8 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Obfuscator for compiled 64-bit portable executables. ### 1. Obfuscator #### Analysis -- **Disassembly**: the binary's code section is disassembled and partitioned into basic blocks by tracing all reachable control flow or by scanning for explicit inline markers as shown in the [examples](https://github.com/RadonCoding/binsafe/tree/main/examples). +- **Disassembly**: the binary's code section is disassembled and partitioned into basic blocks by tracing all reachable control flow or by scanning the `.binsafe` section that [marked functions](https://github.com/RadonCoding/binsafe/tree/main/examples) are placed in. #### Virtualization - **Lifting**: instructions are translated into bytecode that the runtime interprets. diff --git a/api/binsafe.hpp b/api/binsafe.hpp new file mode 100644 index 00000000..a8130835 --- /dev/null +++ b/api/binsafe.hpp @@ -0,0 +1,9 @@ +#pragma once + +#if defined(__GNUC__) || defined(__clang__) +#define BINSAFE __attribute__((section(".binsafe"), noinline)) +#elif defined(_MSC_VER) +#define BINSAFE __declspec(code_seg(".binsafe")) __declspec(noinline) +#else +#define BINSAFE +#endif diff --git a/api/binsafe.rs b/api/binsafe.rs new file mode 100644 index 00000000..49652dc5 --- /dev/null +++ b/api/binsafe.rs @@ -0,0 +1,8 @@ +#[macro_export] +macro_rules! binsafe { + ($item:item) => { + #[link_section = ".binsafe"] + #[inline(never)] + $item + }; +} diff --git a/api/generated/binsafe.hpp b/api/generated/binsafe.hpp deleted file mode 100644 index 2ac2931b..00000000 --- a/api/generated/binsafe.hpp +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#define BINSAFE_BEGIN() \ - { \ - __asm__ volatile(".byte 0xEB, 0x08, 0x42, 0x49, 0x4E, 0x53, 0x41, 0x46, 0x45, 0x01" ::: "memory"); - -#define BINSAFE_END() \ - __asm__ volatile(".byte 0xEB, 0x08, 0x42, 0x49, 0x4E, 0x53, 0x41, 0x46, 0x45, 0x02" ::: "memory"); \ - } - \ No newline at end of file diff --git a/api/generated/binsafe.rs b/api/generated/binsafe.rs deleted file mode 100644 index 6f091a4b..00000000 --- a/api/generated/binsafe.rs +++ /dev/null @@ -1,17 +0,0 @@ -#[macro_export] -macro_rules! binsafe_begin { - () => { - unsafe { - ::std::arch::asm!(".byte 0xEB, 0x08, 0x42, 0x49, 0x4E, 0x53, 0x41, 0x46, 0x45, 0x01", options(nostack, preserves_flags)); - } - }; -} - -#[macro_export] -macro_rules! binsafe_end { - () => { - unsafe { - ::std::arch::asm!(".byte 0xEB, 0x08, 0x42, 0x49, 0x4E, 0x53, 0x41, 0x46, 0x45, 0x02", options(nostack, preserves_flags)); - } - }; -} diff --git a/api/templates/binsafe.hpp b/api/templates/binsafe.hpp deleted file mode 100644 index 27b96a02..00000000 --- a/api/templates/binsafe.hpp +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#define BINSAFE_BEGIN() \ - { \ - __asm__ volatile(".byte {begin}" ::: "memory"); - -#define BINSAFE_END() \ - __asm__ volatile(".byte {end}" ::: "memory"); \ - } - \ No newline at end of file diff --git a/api/templates/binsafe.rs b/api/templates/binsafe.rs deleted file mode 100644 index d3b738bd..00000000 --- a/api/templates/binsafe.rs +++ /dev/null @@ -1,17 +0,0 @@ -#[macro_export] -macro_rules! binsafe_begin { - () => { - unsafe { - ::std::arch::asm!(".byte {begin}", options(nostack, preserves_flags)); - } - }; -} - -#[macro_export] -macro_rules! binsafe_end { - () => { - unsafe { - ::std::arch::asm!(".byte {end}", options(nostack, preserves_flags)); - } - }; -} diff --git a/example.ps1 b/example.ps1 index 21f0fac3..7956e7cc 100644 --- a/example.ps1 +++ b/example.ps1 @@ -8,13 +8,6 @@ $ErrorActionPreference = "Stop" $language = Split-Path (Split-Path $Example -Parent) -Leaf -$env:OUTPUT_DIRECTORY = Join-Path $PWD "api/generated" -$env:TEMPLATES_DIRECTORY = Join-Path $PWD "api/templates" - -cargo build --package markers - -if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - cargo build --bin obfuscator $Arguments if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } diff --git a/examples/cpp/bench/main.cpp b/examples/cpp/bench/main.cpp index 348c80c1..366c58d8 100644 --- a/examples/cpp/bench/main.cpp +++ b/examples/cpp/bench/main.cpp @@ -2,9 +2,37 @@ #include #include #include -#include "../../../api/generated/binsafe.hpp" - -int main() { +#include "../../../api/binsafe.hpp" + +#define ENCIPHER(data, key, delta) \ + do \ + { \ + uint32_t v0 = (data)[0]; \ + uint32_t v1 = (data)[1]; \ + \ + for (int i = 0; i < 20000; ++i) \ + { \ + uint32_t sum = 0; \ + \ + for (uint32_t r = 0; r < 32; r++) \ + { \ + v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + (key)[sum & 3]); \ + sum += (delta); \ + v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + (key)[(sum >> 11) & 3]); \ + } \ + } \ + \ + (data)[0] = v0; \ + (data)[1] = v1; \ + } while (0) + +BINSAFE void encipher(uint32_t data[2], uint32_t const key[4], uint32_t delta) +{ + ENCIPHER(data, key, delta); +} + +int main() +{ uint32_t key[4] = {0xA3B1C2D3, 0xE4F50617, 0x28394A5B, 0x6C7D8E9F}; uint32_t const delta = 0x9E3779B9; @@ -12,20 +40,7 @@ int main() { auto start_native = std::chrono::high_resolution_clock::now(); - uint32_t v0_nat = data_native[0]; - uint32_t v1_nat = data_native[1]; - - for (int i = 0; i < 20000; ++i) { - uint32_t sum = 0; - for (uint32_t r = 0; r < 32; r++) { - v0_nat += (((v1_nat << 4) ^ (v1_nat >> 5)) + v1_nat) ^ (sum + key[sum & 3]); - sum += delta; - v1_nat += (((v0_nat << 4) ^ (v0_nat >> 5)) + v0_nat) ^ (sum + key[(sum >> 11) & 3]); - } - } - - data_native[0] = v0_nat; - data_native[1] = v1_nat; + ENCIPHER(data_native, key, delta); auto end_native = std::chrono::high_resolution_clock::now(); @@ -35,25 +50,7 @@ int main() { auto start_virtualized = std::chrono::high_resolution_clock::now(); - BINSAFE_BEGIN(); - - uint32_t v0_virtualized = data_virtualized[0]; - uint32_t v1_virtualized = data_virtualized[1]; - - for (int i = 0; i < 20000; ++i) { - uint32_t sum = 0; - - for (uint32_t r = 0; r < 32; r++) { - v0_virtualized += (((v1_virtualized << 4) ^ (v1_virtualized >> 5)) + v1_virtualized) ^ (sum + key[sum & 3]); - sum += delta; - v1_virtualized += (((v0_virtualized << 4) ^ (v0_virtualized >> 5)) + v0_virtualized) ^ (sum + key[(sum >> 11) & 3]); - } - } - - data_virtualized[0] = v0_virtualized; - data_virtualized[1] = v1_virtualized; - - BINSAFE_END(); + encipher(data_virtualized, key, delta); auto end_virtualized = std::chrono::high_resolution_clock::now(); @@ -67,4 +64,4 @@ int main() { std::cout << "Overhead: " << (elapsed_virtualized.count() / elapsed_native.count()) << "x" << std::endl; return 0; -} \ No newline at end of file +} diff --git a/examples/cpp/exception-cxx/main.cpp b/examples/cpp/exception-cxx/main.cpp index f40975ca..afa869fc 100644 --- a/examples/cpp/exception-cxx/main.cpp +++ b/examples/cpp/exception-cxx/main.cpp @@ -1,31 +1,36 @@ #include #include #include -#include "../../../api/generated/binsafe.hpp" +#include "../../../api/binsafe.hpp" + +BINSAFE uint32_t guarded(int value) +{ + try { + throw value; + } + catch (int caught) { + return static_cast(caught); + } + + return 0; +} int main() { + constexpr int value = 0x12345678; + uint32_t a = 0; uint32_t b = 0; try { - throw 0x12345678; + throw value; } catch (int value) { a = static_cast(value); } - BINSAFE_BEGIN(); - - try { - throw 0x12345678; - } - catch (int value) { - b = static_cast(value); - } - - BINSAFE_END(); + b = guarded(value); assert(a == b); return 0; -} \ No newline at end of file +} diff --git a/examples/cpp/hello/main.cpp b/examples/cpp/hello/main.cpp index 2de2d73c..8eff4bc0 100644 --- a/examples/cpp/hello/main.cpp +++ b/examples/cpp/hello/main.cpp @@ -1,10 +1,13 @@ #include -#include "../../../api/generated/binsafe.hpp" +#include "../../../api/binsafe.hpp" -int main() +BINSAFE void hello() { - BINSAFE_BEGIN(); std::cout << "Hello, world!" << std::endl; +} + +int main() +{ + hello(); return 0; - BINSAFE_END(); -} \ No newline at end of file +} diff --git a/examples/cpp/multithread/main.cpp b/examples/cpp/multithread/main.cpp new file mode 100644 index 00000000..47a1f74c --- /dev/null +++ b/examples/cpp/multithread/main.cpp @@ -0,0 +1,42 @@ +#include +#include +#include +#include +#include +#include "../../../api/binsafe.hpp" + +BINSAFE uint32_t echo(uint32_t input) +{ + uint32_t output = input; + + return output; +} + +int main() +{ + constexpr int thread_count = 8; + + constexpr uint32_t value = 0x12345678; + + uint32_t results[thread_count] = {}; + + std::thread threads[thread_count]; + + for (int i = 0; i < thread_count; ++i) + { + threads[i] = std::thread([&, i]() + { results[i] = echo(value); }); + } + + for (int i = 0; i < thread_count; ++i) + { + threads[i].join(); + } + + for (int i = 0; i < thread_count; ++i) + { + assert(results[i] == value); + } + + return 0; +} diff --git a/examples/rust/hello/src/main.rs b/examples/rust/hello/src/main.rs index 055d0495..ee5a3574 100644 --- a/examples/rust/hello/src/main.rs +++ b/examples/rust/hello/src/main.rs @@ -1,10 +1,14 @@ include!(concat!( env!("CARGO_MANIFEST_DIR"), - "/../../../api/generated/binsafe.rs" + "/../../../api/binsafe.rs" )); +binsafe! { + fn hello() { + println!("Hello, world!"); + } +} + fn main() { - binsafe_begin!(); - println!("Hello, world!"); - binsafe_end!(); + hello(); } diff --git a/markers/Cargo.toml b/markers/Cargo.toml deleted file mode 100644 index 7b443ecc..00000000 --- a/markers/Cargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "markers" -version = "0.1.0" -edition = "2024" - -[dependencies] diff --git a/markers/build.rs b/markers/build.rs deleted file mode 100644 index ba7c1ed3..00000000 --- a/markers/build.rs +++ /dev/null @@ -1,44 +0,0 @@ -use std::{env, fs, path::PathBuf}; - -include!("src/lib.rs"); - -fn bytes_to_csv(bytes: &[u8]) -> String { - bytes - .iter() - .map(|b| format!("0x{b:02X}")) - .collect::>() - .join(", ") -} - -fn main() { - println!("cargo:rerun-if-env-changed=OUTPUT_DIRECTORY"); - println!("cargo:rerun-if-env-changed=TEMPLATES_DIRECTORY"); - - let (Ok(output), Ok(templates)) = ( - env::var("OUTPUT_DIRECTORY"), - env::var("TEMPLATES_DIRECTORY"), - ) else { - return; - }; - - let output = PathBuf::from(output); - let templates = PathBuf::from(templates); - - println!("cargo:rerun-if-changed={}", templates.display()); - - let begin = bytes_to_csv(&MARKER_BEGIN); - let end = bytes_to_csv(&MARKER_END); - - fs::create_dir_all(&output).unwrap(); - - for entry in fs::read_dir(&templates).unwrap() { - let entry = entry.unwrap(); - let path = entry.path(); - let content = fs::read_to_string(&path).unwrap(); - fs::write( - output.join(path.file_name().unwrap()), - content.replace("{begin}", &begin).replace("{end}", &end), - ) - .unwrap(); - } -} diff --git a/markers/src/lib.rs b/markers/src/lib.rs deleted file mode 100644 index ea9b3060..00000000 --- a/markers/src/lib.rs +++ /dev/null @@ -1,27 +0,0 @@ -pub const MARKER_SIZE: usize = 10; - -pub const MARKER_BEGIN: [u8; MARKER_SIZE] = [ - 0xEB, - (MARKER_SIZE - 2) as u8, - b'B', - b'I', - b'N', - b'S', - b'A', - b'F', - b'E', - 0x01, -]; - -pub const MARKER_END: [u8; MARKER_SIZE] = [ - 0xEB, - (MARKER_SIZE - 2) as u8, - b'B', - b'I', - b'N', - b'S', - b'A', - b'F', - b'E', - 0x02, -]; diff --git a/obfuscator/Cargo.toml b/obfuscator/Cargo.toml index 84f0dc30..2329f43b 100644 --- a/obfuscator/Cargo.toml +++ b/obfuscator/Cargo.toml @@ -6,7 +6,6 @@ edition = "2021" [dependencies] logger = { path = "../logger" } runtime = { path = "../runtime" } -markers = { path = "../markers" } iced-x86 = { version = "1.21.0", features = ["code_asm"] } rand = "0.8.5" clap = { version = "4.5.53", features = ["derive"] } diff --git a/obfuscator/src/engine.rs b/obfuscator/src/engine.rs index 173eb8d9..c318e0e7 100644 --- a/obfuscator/src/engine.rs +++ b/obfuscator/src/engine.rs @@ -5,12 +5,13 @@ use exe::{ }; use iced_x86::{Decoder, DecoderOptions, FlowControl, Formatter, Instruction, IntelFormatter}; use logger::{debug, info}; -use markers::{MARKER_BEGIN, MARKER_END, MARKER_SIZE}; use runtime::runtime::Runtime; use std::{collections::HashSet, fmt, mem}; use crate::{args::Args, exceptions, protections::Protection}; +const BINSAFE_SECTION: &str = ".binsafe"; + pub struct Block { pub rva: u32, pub offset: usize, @@ -124,7 +125,10 @@ impl<'a> Engine<'a> { pub fn scan(&mut self) { let entry_point = self.pe.get_entrypoint().unwrap(); - let code_section = self.pe.get_section_by_rva(entry_point).unwrap(); + let code_section = self + .pe + .get_section_by_name(BINSAFE_SECTION) + .unwrap_or(self.pe.get_section_by_rva(entry_point).unwrap()); info!( "Section VA: 0x{:X}, file offset: 0x{:X}", @@ -139,54 +143,13 @@ impl<'a> Engine<'a> { let ip = code_section.virtual_address.0 as u64; let code = code_section.read(&self.pe).unwrap().to_vec(); - let markers = self.find_markers(&code); - - let data_references = self.collect_data_references(&code, ip, &markers); - let mut code_references = - self.collect_code_references(&code, ip, &code_section, &data_references, &markers); - - for &(start, _) in &markers { - code_references.insert(ip as u32 + start as u32); - } - - if self.scan_markers(ip, &code, &code_references, &markers) { - return; - } + let data_references = self.collect_data_references(&code, ip); + let code_references = + self.collect_code_references(&code, ip, &code_section, &data_references); self.scan_blocks(ip, &code, &code_references); } - fn find_markers(&self, code: &[u8]) -> Vec<(usize, usize)> { - let mut markers = Vec::new(); - let mut begin = None; - let mut cursor = 0; - - while cursor + MARKER_SIZE <= code.len() { - if code[cursor..cursor + MARKER_SIZE] == MARKER_BEGIN { - begin = Some(cursor + MARKER_SIZE); - cursor += MARKER_SIZE; - continue; - } - if code[cursor..cursor + MARKER_SIZE] == MARKER_END { - if let Some(start) = begin.take() { - markers.push((start, cursor)); - } - cursor += MARKER_SIZE; - continue; - } - cursor += 1; - } - - markers - } - - fn is_marker(&self, offset: usize, markers: &[(usize, usize)]) -> bool { - markers.iter().any(|&(s, e)| { - (offset >= s.saturating_sub(MARKER_SIZE) && offset < s) - || (offset >= e && offset < e + MARKER_SIZE) - }) - } - fn capture_block(&mut self, block: &mut Vec, end: u32) { if block.is_empty() { return; @@ -257,49 +220,6 @@ impl<'a> Engine<'a> { } } - fn scan_markers( - &mut self, - ip: u64, - code: &[u8], - code_references: &HashSet, - markers: &[(usize, usize)], - ) -> bool { - if markers.is_empty() { - return false; - } - - info!("Found {} marked regions", markers.len()); - - let mut sorted_code_references = code_references.iter().copied().collect::>(); - sorted_code_references.sort(); - - for &(start, end) in markers { - self.collect_blocks( - &code[start..end], - ip + start as u64, - &sorted_code_references, - ); - } - - for &(start, end) in markers { - let start_offset = self - .pe - .translate(PETranslation::Memory(RVA(ip as u32 + start as u32))) - .unwrap(); - let end_offset = self - .pe - .translate(PETranslation::Memory(RVA(ip as u32 + end as u32))) - .unwrap(); - - self.nop(start_offset - MARKER_SIZE, MARKER_SIZE); - self.nop(end_offset, MARKER_SIZE); - } - - info!("Found {} blocks", self.blocks.len()); - - true - } - fn scan_blocks(&mut self, ip: u64, code: &[u8], code_references: &HashSet) { let mut sorted_code_references = code_references.iter().copied().collect::>(); sorted_code_references.sort(); @@ -309,21 +229,11 @@ impl<'a> Engine<'a> { info!("Found {} blocks", self.blocks.len()); } - fn collect_data_references( - &self, - code: &[u8], - ip: u64, - markers: &[(usize, usize)], - ) -> HashSet { + fn collect_data_references(&self, code: &[u8], ip: u64) -> HashSet { let mut data_references = HashSet::new(); let mut cursor = 0; while cursor < code.len() { - if self.is_marker(cursor, markers) { - cursor += MARKER_SIZE; - continue; - } - let mut decoder = Decoder::with_ip( self.bitness, &code[cursor..], @@ -404,19 +314,12 @@ impl<'a> Engine<'a> { ip: u64, code_section: &ImageSectionHeader, data_references: &HashSet, - markers: &[(usize, usize)], ) -> HashSet { let mut references = self.collect_static_references(code_section); let mut cursor = 0; let mut previous = Vec::new(); while cursor < code.len() { - if self.is_marker(cursor, markers) { - cursor += MARKER_SIZE; - previous.clear(); - continue; - } - let mut decoder = Decoder::with_ip( self.bitness, &code[cursor..], diff --git a/obfuscator/src/protections/virtualization/attestation/anti_debug.rs b/obfuscator/src/protections/virtualization/attestation/anti_debug.rs index cacb7d37..a6a5602c 100644 --- a/obfuscator/src/protections/virtualization/attestation/anti_debug.rs +++ b/obfuscator/src/protections/virtualization/attestation/anti_debug.rs @@ -64,6 +64,7 @@ fn query_kd_debugger_enabled( VMWidth::Lower8, )); b.extend(accumulate_immediate( + engine, rng, ACCUMULATOR, None, @@ -73,6 +74,7 @@ fn query_kd_debugger_enabled( b.extend(import(engine, ImportDef::NtQuerySystemInformation)); b.extend(accumulate_prologue( + engine, rng, ACCUMULATOR, VMReg::Rax, @@ -99,6 +101,7 @@ fn query_kd_debugger_enabled( b.extend(invoke(VMReg::Rax)); b.extend(accumulate_immediate( + engine, rng, ACCUMULATOR, Some(VMReg::Rax), @@ -108,6 +111,7 @@ fn query_kd_debugger_enabled( // READ KernelDebuggerEnabled + KernelDebuggerNotPresent b.extend(accumulate_memory( + engine, rng, ACCUMULATOR, VMReg::Rsp, @@ -129,6 +133,7 @@ fn query_process_debug_object_handle( b.extend(import(engine, ImportDef::NtQueryInformationProcess)); b.extend(accumulate_prologue( + engine, rng, ACCUMULATOR, VMReg::Rax, @@ -152,11 +157,12 @@ fn query_process_debug_object_handle( // ProcessInformationLength -> R9 b.extend(set_register(VMReg::R9, 8)); // ReturnLength -> [RSP + ...] - b.extend(store_memory(VMReg::Rsp, VMReg::None, 1, 0x20, 0)); + b.extend(store_immediate(VMReg::Rsp, VMReg::None, 1, 0x20, 0)); // NtQueryInformationProcess b.extend(invoke(VMReg::Rax)); b.extend(accumulate_immediate( + engine, rng, ACCUMULATOR, Some(VMReg::Rax), @@ -166,6 +172,7 @@ fn query_process_debug_object_handle( // READ ProcessDebugObjectHandle b.extend(accumulate_memory( + engine, rng, ACCUMULATOR, VMReg::Rsp, @@ -187,6 +194,7 @@ fn set_hide_from_debugger( b.extend(import(engine, ImportDef::NtSetInformationThread)); b.extend(accumulate_prologue( + engine, rng, ACCUMULATOR, VMReg::Rax, @@ -205,6 +213,7 @@ fn set_hide_from_debugger( b.extend(invoke(VMReg::Rax)); b.extend(accumulate_immediate( + engine, rng, ACCUMULATOR, Some(VMReg::Rax), @@ -224,6 +233,7 @@ fn query_hide_from_debbuger( b.extend(import(engine, ImportDef::NtQueryInformationThread)); b.extend(accumulate_prologue( + engine, rng, ACCUMULATOR, VMReg::Rax, @@ -248,11 +258,12 @@ fn query_hide_from_debbuger( // ThreadInformationLength -> R9 b.extend(set_register(VMReg::R9, 1)); // ReturnLength -> [RSP + ...] - b.extend(store_memory(VMReg::Rsp, VMReg::None, 1, 0x20, 0)); + b.extend(store_immediate(VMReg::Rsp, VMReg::None, 1, 0x20, 0)); // NtQueryInformationThread b.extend(invoke(VMReg::Rax)); b.extend(accumulate_immediate( + engine, rng, ACCUMULATOR, Some(VMReg::Rax), @@ -262,6 +273,7 @@ fn query_hide_from_debbuger( // READ ThreadHideFromDebugger b.extend(accumulate_byte( + engine, rng, ACCUMULATOR, VMReg::Rsp, diff --git a/obfuscator/src/protections/virtualization/attestation/anti_tamper.rs b/obfuscator/src/protections/virtualization/attestation/anti_tamper.rs index 207d3f97..acd8bad5 100644 --- a/obfuscator/src/protections/virtualization/attestation/anti_tamper.rs +++ b/obfuscator/src/protections/virtualization/attestation/anti_tamper.rs @@ -42,8 +42,6 @@ pub fn generate( } } - *expected = lane0 ^ lane1; - let count = FnDef::VARIANTS.len(); let functions = engine.rt.lookup(engine.rt.data_labels[&DataDef::Functions]) as i32; @@ -142,7 +140,10 @@ pub fn generate( instructions.extend(spill_vector(ACCUMULATOR, VMWidth::Lower128)); instructions.extend(xor(None, None)); + instructions.extend(register_lcg(engine, None)); instructions.extend(store_register(VMReg::Vp1)); + *expected = apply_lcg(engine, lane0 ^ lane1); + instructions } diff --git a/obfuscator/src/protections/virtualization/attestation/mod.rs b/obfuscator/src/protections/virtualization/attestation/mod.rs index ad0e015f..4e6fb85b 100644 --- a/obfuscator/src/protections/virtualization/attestation/mod.rs +++ b/obfuscator/src/protections/virtualization/attestation/mod.rs @@ -50,7 +50,7 @@ pub fn generate(engine: &mut Engine, key: u64) -> Vec>> { block.extend(timestamp()); block.extend(mask(None, !((1u64 << WINDOW) - 1))); - block.extend(lcg(engine, None)); + block.extend(register_lcg(engine, None)); block.extend(store_register(VMReg::Vt0)); block.extend(sub(Some(VMReg::Vt0), Some(VMReg::Vt1))); @@ -87,6 +87,7 @@ pub fn generate(engine: &mut Engine, key: u64) -> Vec>> { b.extend(store_register(VMReg::Rcx)); b.extend(accumulate_immediate( + engine, &mut rng, VMReg::Vp0, Some(VMReg::Rcx), @@ -94,6 +95,7 @@ pub fn generate(engine: &mut Engine, key: u64) -> Vec>> { &mut vp0, )); b.extend(accumulate_immediate( + engine, &mut rng, VMReg::Vp1, Some(VMReg::Rcx), @@ -111,6 +113,7 @@ pub fn generate(engine: &mut Engine, key: u64) -> Vec>> { b.extend(store_register(VMReg::Rcx)); b.extend(accumulate_immediate( + engine, &mut rng, VMReg::Vp0, Some(VMReg::Rcx), @@ -118,6 +121,7 @@ pub fn generate(engine: &mut Engine, key: u64) -> Vec>> { &mut vp0, )); b.extend(accumulate_immediate( + engine, &mut rng, VMReg::Vp1, Some(VMReg::Rcx), @@ -125,9 +129,10 @@ pub fn generate(engine: &mut Engine, key: u64) -> Vec>> { &mut vp1, )); - b.extend(xor(Some(VMReg::Vp0), Some(VMReg::Vt0))); + b.extend(register_lcg(engine, Some(VMReg::Vp0))); b.extend(store_register(VMReg::Vp0)); - b.extend(xor(Some(VMReg::Vp1), Some(VMReg::Vt0))); + + b.extend(register_lcg(engine, Some(VMReg::Vp1))); b.extend(store_register(VMReg::Vp1)); b.extend(copy(VMReg::Vt0, VMReg::Vt1)); @@ -219,12 +224,13 @@ fn correct( )); let operation = Operation::random(rng); - let combined = combine_operation(operation, vp0, vp1); + let combined = apply_lcg(engine, combine_operation(operation, vp0, vp1)); let correction = combined ^ key; - instructions.extend(xor(Some(VMReg::Vp0), Some(VMReg::Vt1))); - instructions.extend(xor(Some(VMReg::Vp1), Some(VMReg::Vt1))); + instructions.extend(register_lcg_inverse(engine, Some(VMReg::Vp0))); + instructions.extend(register_lcg_inverse(engine, Some(VMReg::Vp1))); instructions.extend(register_operation(operation)); + instructions.extend(register_lcg(engine, None)); instructions.extend(immediate(correction)); instructions.extend(xor(None, None)); @@ -235,7 +241,7 @@ fn correct( instructions } -fn lcg(engine: &mut Engine, register: Option) -> Vec> { +fn register_lcg(engine: &mut Engine, register: Option) -> Vec> { let mut instructions = Vec::>::new(); instructions.extend(load_data( @@ -251,6 +257,32 @@ fn lcg(engine: &mut Engine, register: Option) -> Vec> { instructions } +fn register_lcg_inverse(engine: &mut Engine, register: Option) -> Vec> { + let mut instructions = Vec::>::new(); + + if let Some(register) = register { + instructions.extend(load_register(register)); + } + + instructions.extend(load_data(engine, DataDef::VmKeyAddend, VMWidth::Lower64)); + instructions.extend(sub(None, None)); + + instructions.extend(load_data( + engine, + DataDef::VmKeyMultiplierInverse, + VMWidth::Lower64, + )); + instructions.extend(mul(None, None)); + + instructions +} + +fn apply_lcg(engine: &Engine, value: u64) -> u64 { + value + .wrapping_mul(engine.rt.keys.multiplier) + .wrapping_add(engine.rt.keys.addend) +} + fn combine_operation(operation: Operation, a: u64, b: u64) -> u64 { let mut result = a; apply_operation(operation, b, &mut result); @@ -296,6 +328,7 @@ fn vector_operation(operation: Operation) -> Box { } fn accumulate( + engine: &mut Engine, rng: &mut R, accumulator: VMReg, source: Vec>, @@ -306,21 +339,19 @@ fn accumulate( let operation = Operation::random(rng); - let mix = rng.gen::(); - instructions.extend(load_register(accumulator)); instructions.extend(source); - instructions.extend(immediate(mix)); - instructions.extend(xor(None, None)); + instructions.extend(register_lcg(engine, None)); instructions.extend(register_operation(operation)); instructions.extend(store_register(accumulator)); - apply_operation(operation, value ^ mix, expected); + apply_operation(operation, apply_lcg(engine, value), expected); instructions } pub fn accumulate_immediate( + engine: &mut Engine, rng: &mut R, accumulator: VMReg, source: Option, @@ -331,10 +362,11 @@ pub fn accumulate_immediate( Some(register) => load_register(register), None => Vec::new(), }; - accumulate(rng, accumulator, source, value, expected) + accumulate(engine, rng, accumulator, source, value, expected) } pub fn accumulate_memory( + engine: &mut Engine, rng: &mut R, accumulator: VMReg, base: VMReg, @@ -344,10 +376,11 @@ pub fn accumulate_memory( expected: &mut u64, ) -> Vec> { let source = load_memory(base, VMReg::None, 1, displacement, VMSeg::None, width); - accumulate(rng, accumulator, source, value, expected) + accumulate(engine, rng, accumulator, source, value, expected) } fn accumulate_byte( + engine: &mut Engine, rng: &mut R, accumulator: VMReg, base: VMReg, @@ -356,6 +389,7 @@ fn accumulate_byte( expected: &mut u64, ) -> Vec> { accumulate_memory( + engine, rng, accumulator, base, @@ -367,6 +401,7 @@ fn accumulate_byte( } fn accumulate_prologue( + engine: &mut Engine, rng: &mut R, accumulator: VMReg, base: VMReg, @@ -377,6 +412,7 @@ fn accumulate_prologue( for (offset, byte) in prologue.iter().enumerate() { instructions.extend(accumulate_byte( + engine, rng, accumulator, base, diff --git a/obfuscator/src/protections/virtualization/language.rs b/obfuscator/src/protections/virtualization/language.rs index 2b058231..9a1b86fd 100644 --- a/obfuscator/src/protections/virtualization/language.rs +++ b/obfuscator/src/protections/virtualization/language.rs @@ -14,6 +14,8 @@ use runtime::vm::encoders::load_register::LoadRegister; use runtime::vm::encoders::load_vector::LoadVector; use runtime::vm::encoders::mul::Mul; use runtime::vm::encoders::or::Or; +use runtime::vm::encoders::pop::Pop; +use runtime::vm::encoders::push::Push; use runtime::vm::encoders::shl::Shl; use runtime::vm::encoders::shr::Shr; use runtime::vm::encoders::store_memory::StoreMemory; @@ -420,9 +422,12 @@ pub fn mul(a: Option, b: Option) -> Vec> { })); } instructions.push(Box::new(Mul { - width: VMWidth::Lower64, + width: VMWidth::SLower64, })); + instructions.push(Box::new(Push::new())); instructions.push(Box::new(Discard::new())); + instructions.push(Box::new(Pop::new())); + instructions } @@ -500,7 +505,7 @@ pub fn load_memory( instructions } -pub fn store_memory( +pub fn store_immediate( base: VMReg, index: VMReg, scale: u8, diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 1b5d7964..b135791f 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -2,8 +2,8 @@ pub mod debug; pub mod functions; pub mod mapper; -pub mod register; pub mod runtime; +pub mod utils; pub mod vm; macro_rules! stack { diff --git a/runtime/src/runtime.rs b/runtime/src/runtime.rs index 8d207b93..ce1ffaeb 100644 --- a/runtime/src/runtime.rs +++ b/runtime/src/runtime.rs @@ -38,7 +38,6 @@ mapped! { VmVectorsCapture, VmVectorsRestore, VmVectorsCopy, - VmFlags, /* VM HANDLERS */ VmHandlerJcc, VmHandlerRet, @@ -121,6 +120,7 @@ mapped! { VmTrampolines, VmKeyInitializer, VmKeyMultiplier, + VmKeyMultiplierInverse, VmKeyAddend, VehEnd, ImportAddresses, @@ -271,7 +271,7 @@ impl Default for Keys { Self { initializer: rng.gen::(), - multiplier: rng.gen::(), + multiplier: rng.gen::() | 1, addend: rng.gen::(), secret: rng.gen::(), } @@ -708,7 +708,6 @@ impl Runtime { (FnDef::VmHandlerVectorDiv, vm::handlers::vector_div::build), (FnDef::VmHandlerTimestamp, vm::handlers::timestamp::build), (FnDef::VmHandlerDispatch, vm::handlers::dispatch::build), - (FnDef::VmFlags, vm::handlers::flags::build), (FnDef::VmVehInitialize, vm::functions::veh::initialize), (FnDef::Hash, functions::hash::build), (FnDef::Resolve, functions::resolve::build), @@ -740,6 +739,10 @@ impl Runtime { self.define_data_qword(DataDef::VmKeyInitializer, self.keys.initializer); self.define_data_qword(DataDef::VmKeyAddend, self.keys.addend); self.define_data_qword(DataDef::VmKeyMultiplier, self.keys.multiplier); + self.define_data_qword( + DataDef::VmKeyMultiplierInverse, + crate::utils::invert_multiplier(self.keys.multiplier), + ); self.define_bool(BoolDef::IsLocked, false); self.define_bool(BoolDef::HasAvx, false); diff --git a/runtime/src/register.rs b/runtime/src/utils.rs similarity index 75% rename from runtime/src/register.rs rename to runtime/src/utils.rs index 0b898080..2fd24cb0 100644 --- a/runtime/src/register.rs +++ b/runtime/src/utils.rs @@ -1,6 +1,18 @@ use iced_x86::Register; -pub fn sized(register: Register, size: usize) -> Option { +/// Computes the multiplicative inverse of odd `x` modulo 2^64. +pub fn invert_multiplier(x: u64) -> u64 { + let mut inverse = x; + + for _ in 0..5 { + inverse = inverse.wrapping_mul(2u64.wrapping_sub(x.wrapping_mul(inverse))); + } + + inverse +} + +// Converts `register` to the same register of `size`. +pub fn register_of_size(register: Register, size: usize) -> Option { let number = register.number() as usize; match size { diff --git a/runtime/src/vm/handlers/compare_exchange.rs b/runtime/src/vm/handlers/compare_exchange.rs index 750e4fea..76e4b467 100644 --- a/runtime/src/vm/handlers/compare_exchange.rs +++ b/runtime/src/vm/handlers/compare_exchange.rs @@ -1,90 +1,98 @@ -use iced_x86::code_asm::{edx, ptr, r12, r13, r8, r9, r9b, r9d, r9w, rax, rcx, rdx}; - use crate::{ - runtime::{FnDef, Runtime}, + runtime::Runtime, vm::{ - bytecode::Flag, - utils::{self, scratch}, + bytecode::{Flag, VMWidth}, + handlers::semantic::{self, Compare, Effect, Expression, Flags, Operand, Operation}, }, }; -// unsigned char* (unsigned char*) pub fn build(rt: &mut Runtime) { - let mut epilogue = rt.asm.create_label(); - - // push r13 - rt.asm.push(r13).unwrap(); - // mov r13, rcx - rt.asm.mov(r13, rcx).unwrap(); - - // edx -> width - utils::bytecode::read_byte_zx(rt, r13, edx); - - // load r8 - scratch::load(rt, r12, r8); - // load r9 - scratch::load(rt, r12, r9); - // load rax - scratch::load(rt, r12, rax); - - utils::width::dispatch( + semantic::compiler::compile( rt, - rdx, - &mut epilogue, - Some(Box::new(|rt| { - // lock cmpxchg [r8], r9 - rt.asm.lock().cmpxchg(ptr(r8), r9).unwrap(); - })), - Some(Box::new(|rt| { - // lock cmpxchg [r8], r9d - rt.asm.lock().cmpxchg(ptr(r8), r9d).unwrap(); - })), - None, - Some(Box::new(|rt| { - // lock cmpxchg [r8], r9w - rt.asm.lock().cmpxchg(ptr(r8), r9w).unwrap(); - })), - None, - Some(Box::new(|rt| { - // lock cmpxchg [r8], r9b - rt.asm.lock().cmpxchg(ptr(r8), r9b).unwrap(); - })), - None, - None, - None, - None, - None, - None, + &Operation { + effects: vec![Effect::CompareExchange( + Expression::Operand(Operand::Input(0)), + Expression::Operand(Operand::Input(1)), + Expression::Operand(Operand::Input(2)), + )], + flags: Flags::Always(vec![ + ( + Flag::Zero, + Expression::Compare(Box::new(Compare::Equal( + Expression::Sub( + Box::new(Expression::Operand(Operand::Input(0))), + Box::new(Expression::Operand(Operand::Output(0))), + ), + Expression::Constant(0), + ))), + ), + ( + Flag::Sign, + Expression::Compare(Box::new(Compare::BitSet( + Expression::Sub( + Box::new(Expression::Operand(Operand::Input(0))), + Box::new(Expression::Operand(Operand::Output(0))), + ), + Expression::SignBit, + ))), + ), + ( + Flag::Carry, + Expression::Compare(Box::new(Compare::LessThan( + Expression::Operand(Operand::Input(0)), + Expression::Operand(Operand::Output(0)), + ))), + ), + ( + Flag::Overflow, + Expression::Compare(Box::new(Compare::BitSet( + Expression::BitAnd( + Box::new(Expression::BitXor( + Box::new(Expression::Operand(Operand::Input(0))), + Box::new(Expression::Operand(Operand::Output(0))), + )), + Box::new(Expression::BitXor( + Box::new(Expression::Operand(Operand::Input(0))), + Box::new(Expression::Sub( + Box::new(Expression::Operand(Operand::Input(0))), + Box::new(Expression::Operand(Operand::Output(0))), + )), + )), + ), + Expression::SignBit, + ))), + ), + ( + Flag::Parity, + Expression::Parity(Box::new(Expression::Sub( + Box::new(Expression::Operand(Operand::Input(0))), + Box::new(Expression::Operand(Operand::Output(0))), + ))), + ), + ( + Flag::Auxiliary, + Expression::Compare(Box::new(Compare::BitSet( + Expression::BitXor( + Box::new(Expression::BitXor( + Box::new(Expression::Operand(Operand::Input(0))), + Box::new(Expression::Operand(Operand::Output(0))), + )), + Box::new(Expression::Sub( + Box::new(Expression::Operand(Operand::Input(0))), + Box::new(Expression::Operand(Operand::Output(0))), + )), + ), + Expression::Constant(4), + ))), + ), + ]), + stores: None, + widths: &[ + VMWidth::Lower64, + VMWidth::Lower32, + VMWidth::Lower16, + VMWidth::Lower8, + ], + }, ); - - rt.asm.set_label(&mut epilogue).unwrap(); - { - // pushfq - rt.asm.pushfq().unwrap(); - - // store rax - scratch::store(rt, r12, rax); - - // mov rcx, ... - rt.asm - .mov( - rcx, - Flag::Carry.bit64() - | Flag::Parity.bit64() - | Flag::Auxiliary.bit64() - | Flag::Zero.bit64() - | Flag::Sign.bit64() - | Flag::Overflow.bit64(), - ) - .unwrap(); - // call ... - rt.asm.call(rt.function_labels[&FnDef::VmFlags]).unwrap(); - - // mov rax, r13 - rt.asm.mov(rax, r13).unwrap(); - // pop r13 - rt.asm.pop(r13).unwrap(); - // ret - rt.asm.ret().unwrap(); - } } diff --git a/runtime/src/vm/handlers/exchange.rs b/runtime/src/vm/handlers/exchange.rs index a2e6c8bd..94ebe025 100644 --- a/runtime/src/vm/handlers/exchange.rs +++ b/runtime/src/vm/handlers/exchange.rs @@ -1,60 +1,27 @@ -use iced_x86::code_asm::{eax, ptr, r12, r8, r9, r9b, r9d, r9w, rax, rcx}; - use crate::{ runtime::Runtime, - vm::utils::{self, scratch}, + vm::{ + bytecode::VMWidth, + handlers::semantic::{self, Effect, Expression, Flags, Operand, Operation}, + }, }; -// unsigned char* (unsigned char*) pub fn build(rt: &mut Runtime) { - let mut epilogue = rt.asm.create_label(); - - // eax -> width - utils::bytecode::read_byte_zx(rt, rcx, eax); - - // load r8 - scratch::load(rt, r12, r8); - // load r9 - scratch::load(rt, r12, r9); - - utils::width::dispatch( + semantic::compiler::compile( rt, - rax, - &mut epilogue, - Some(Box::new(|rt| { - // xchg [r8], r9 - rt.asm.xchg(ptr(r8), r9).unwrap(); - })), - Some(Box::new(|rt| { - // xchg [r8], r9d - rt.asm.xchg(ptr(r8), r9d).unwrap(); - })), - None, - Some(Box::new(|rt| { - // xchg [r8], r9w - rt.asm.xchg(ptr(r8), r9w).unwrap(); - })), - None, - Some(Box::new(|rt| { - // xchg [r8], r9b - rt.asm.xchg(ptr(r8), r9b).unwrap(); - })), - None, - None, - None, - None, - None, - None, + &Operation { + effects: vec![Effect::Exchange( + Expression::Operand(Operand::Input(0)), + Expression::Operand(Operand::Input(1)), + )], + flags: Flags::Always(vec![]), + stores: None, + widths: &[ + VMWidth::Lower64, + VMWidth::Lower32, + VMWidth::Lower16, + VMWidth::Lower8, + ], + }, ); - - rt.asm.set_label(&mut epilogue).unwrap(); - { - // store r9 - scratch::store(rt, r12, r9); - - // mov rax, rcx - rt.asm.mov(rax, rcx).unwrap(); - // ret - rt.asm.ret().unwrap(); - } } diff --git a/runtime/src/vm/handlers/exchange_add.rs b/runtime/src/vm/handlers/exchange_add.rs index d6112058..fb5761cf 100644 --- a/runtime/src/vm/handlers/exchange_add.rs +++ b/runtime/src/vm/handlers/exchange_add.rs @@ -1,87 +1,82 @@ -use iced_x86::code_asm::{eax, ptr, r12, r13, r8, r9, r9b, r9d, r9w, rax, rcx}; - use crate::{ - runtime::{FnDef, Runtime}, + runtime::Runtime, vm::{ - bytecode::Flag, - utils::{self, scratch}, + bytecode::{Flag, VMWidth}, + handlers::semantic::{self, Compare, Effect, Expression, Flags, Operand, Operation}, }, }; -// unsigned char* (unsigned char*) pub fn build(rt: &mut Runtime) { - let mut epilogue = rt.asm.create_label(); - - // push r13 - rt.asm.push(r13).unwrap(); - // mov r13, rcx - rt.asm.mov(r13, rcx).unwrap(); - - // eax -> width - utils::bytecode::read_byte_zx(rt, r13, eax); - - // load r8 - scratch::load(rt, r12, r8); - // load r9 - scratch::load(rt, r12, r9); - - utils::width::dispatch( + semantic::compiler::compile( rt, - rax, - &mut epilogue, - Some(Box::new(|rt| { - // lock xadd [r8], r9 - rt.asm.lock().xadd(ptr(r8), r9).unwrap(); - })), - Some(Box::new(|rt| { - // lock xadd [r8], r9d - rt.asm.lock().xadd(ptr(r8), r9d).unwrap(); - })), - None, - Some(Box::new(|rt| { - // lock xadd [r8], r9w - rt.asm.lock().xadd(ptr(r8), r9w).unwrap(); - })), - None, - Some(Box::new(|rt| { - // lock xadd [r8], r9b - rt.asm.lock().xadd(ptr(r8), r9b).unwrap(); - })), - None, - None, - None, - None, - None, - None, + &Operation { + effects: vec![Effect::ExchangeAdd( + Expression::Operand(Operand::Input(0)), + Expression::Operand(Operand::Input(1)), + )], + flags: Flags::Always(vec![ + ( + Flag::Zero, + Expression::Compare(Box::new(Compare::Equal( + Expression::Operand(Operand::Output(0)), + Expression::Constant(0), + ))), + ), + ( + Flag::Sign, + Expression::Compare(Box::new(Compare::BitSet( + Expression::Operand(Operand::Output(0)), + Expression::SignBit, + ))), + ), + ( + Flag::Carry, + Expression::Compare(Box::new(Compare::LessThan( + Expression::Operand(Operand::Output(0)), + Expression::Operand(Operand::Input(0)), + ))), + ), + ( + Flag::Overflow, + Expression::Compare(Box::new(Compare::BitSet( + Expression::BitAnd( + Box::new(Expression::BitXor( + Box::new(Expression::Operand(Operand::Input(0))), + Box::new(Expression::Operand(Operand::Output(0))), + )), + Box::new(Expression::BitXor( + Box::new(Expression::Operand(Operand::Output(1))), + Box::new(Expression::Operand(Operand::Output(0))), + )), + ), + Expression::SignBit, + ))), + ), + ( + Flag::Parity, + Expression::Parity(Box::new(Expression::Operand(Operand::Output(0)))), + ), + ( + Flag::Auxiliary, + Expression::Compare(Box::new(Compare::BitSet( + Expression::BitXor( + Box::new(Expression::BitXor( + Box::new(Expression::Operand(Operand::Input(0))), + Box::new(Expression::Operand(Operand::Output(1))), + )), + Box::new(Expression::Operand(Operand::Output(0))), + ), + Expression::Constant(4), + ))), + ), + ]), + stores: Some(vec![Expression::Operand(Operand::Output(1))]), + widths: &[ + VMWidth::Lower64, + VMWidth::Lower32, + VMWidth::Lower16, + VMWidth::Lower8, + ], + }, ); - - rt.asm.set_label(&mut epilogue).unwrap(); - { - // mov rcx, ... - rt.asm - .mov( - rcx, - Flag::Carry.bit64() - | Flag::Parity.bit64() - | Flag::Auxiliary.bit64() - | Flag::Zero.bit64() - | Flag::Sign.bit64() - | Flag::Overflow.bit64(), - ) - .unwrap(); - // pushfq - rt.asm.pushfq().unwrap(); - // call ... - rt.asm.call(rt.function_labels[&FnDef::VmFlags]).unwrap(); - - // store r9 - scratch::store(rt, r12, r9); - - // mov rax, r13 - rt.asm.mov(rax, r13).unwrap(); - // pop r13 - rt.asm.pop(r13).unwrap(); - // ret - rt.asm.ret().unwrap(); - } } diff --git a/runtime/src/vm/handlers/flags.rs b/runtime/src/vm/handlers/flags.rs deleted file mode 100644 index a71eca0d..00000000 --- a/runtime/src/vm/handlers/flags.rs +++ /dev/null @@ -1,36 +0,0 @@ -use iced_x86::code_asm::{eax, r12, r8, rax, rcx, rdx}; - -use crate::{ - runtime::Runtime, - vm::{bytecode::VMReg, utils}, -}; - -pub fn build(rt: &mut Runtime) { - // pop rax - rt.asm.pop(rax).unwrap(); - // pop rdx - rt.asm.pop(rdx).unwrap(); - // push rax - rt.asm.push(rax).unwrap(); - - // mov eax, [r12 + ...] - utils::vreg::load_reg32(rt, r12, VMReg::Flags, eax); - - // mov r8, rcx - rt.asm.mov(r8, rcx).unwrap(); - // not r8 - rt.asm.not(r8).unwrap(); - // and rax, r8 - rt.asm.and(rax, r8).unwrap(); - - // and rdx, rcx - rt.asm.and(rdx, rcx).unwrap(); - // or rax, rdx - rt.asm.or(rax, rdx).unwrap(); - - // mov [r12 + ...], eax - utils::vreg::store_reg32(rt, r12, eax, VMReg::Flags); - - // ret - rt.asm.ret().unwrap(); -} diff --git a/runtime/src/vm/handlers/mod.rs b/runtime/src/vm/handlers/mod.rs index ac15a2a6..77b3c5a1 100644 --- a/runtime/src/vm/handlers/mod.rs +++ b/runtime/src/vm/handlers/mod.rs @@ -13,7 +13,6 @@ pub mod dispatch; pub mod div; pub mod exchange; pub mod exchange_add; -pub mod flags; pub mod jcc; pub mod load_address; pub mod load_immediate; diff --git a/runtime/src/vm/handlers/semantic/compiler.rs b/runtime/src/vm/handlers/semantic/compiler.rs index 4e410993..808bf4f2 100644 --- a/runtime/src/vm/handlers/semantic/compiler.rs +++ b/runtime/src/vm/handlers/semantic/compiler.rs @@ -5,8 +5,8 @@ use iced_x86::code_asm::{ }; use crate::{ - register, runtime::Runtime, + utils::register_of_size, vm::{ bytecode::{VMReg, VMWidth}, handlers::semantic::{ @@ -160,15 +160,15 @@ fn compile_effects( fn mask(rt: &mut Runtime, register: AsmRegister64, width: VMWidth) { match width.size() { 1 => { - let byte = get_gpr8(register::sized(register.into(), 1).unwrap()).unwrap(); + let byte = get_gpr8(register_of_size(register.into(), 1).unwrap()).unwrap(); rt.asm.movzx(register, byte).unwrap(); } 2 => { - let word = get_gpr16(register::sized(register.into(), 2).unwrap()).unwrap(); + let word = get_gpr16(register_of_size(register.into(), 2).unwrap()).unwrap(); rt.asm.movzx(register, word).unwrap(); } 4 => { - let dword = get_gpr32(register::sized(register.into(), 4).unwrap()).unwrap(); + let dword = get_gpr32(register_of_size(register.into(), 4).unwrap()).unwrap(); rt.asm.mov(dword, dword).unwrap(); } 8 => {} @@ -316,6 +316,163 @@ fn compile_div( allocator.consume(third); } +fn compile_exchange( + rt: &mut Runtime, + allocator: &mut Allocator, + first: &Expression, + second: &Expression, + width: VMWidth, +) { + let first = compile_expression(rt, allocator, first, width); + let second = compile_expression(rt, allocator, second, VMWidth::Lower64); + + let pinned = [first, second] + .into_iter() + .filter_map(|value| match value { + Reference::Value(value) => Some(value), + Reference::Immediate(_) => None, + }) + .collect::>(); + + let dst = allocator.acquire(rt, &pinned, &[]); + allocator.copy(rt, first, dst); + + let src = allocator.acquire(rt, &pinned, &[dst]); + allocator.copy(rt, second, src); + + match width.size() { + 1 => { + let byte = get_gpr8(register_of_size(dst.into(), 1).unwrap()).unwrap(); + rt.asm.xchg(ptr(src), byte).unwrap(); + } + 2 => { + let word = get_gpr16(register_of_size(dst.into(), 2).unwrap()).unwrap(); + rt.asm.xchg(ptr(src), word).unwrap(); + } + 4 => { + let dword = get_gpr32(register_of_size(dst.into(), 4).unwrap()).unwrap(); + rt.asm.xchg(ptr(src), dword).unwrap(); + } + 8 => { + rt.asm.xchg(ptr(src), dst).unwrap(); + } + _ => unreachable!(), + } + + allocator.replace(dst, Value::Output(0), true); + + allocator.consume(first); + allocator.consume(second); +} + +fn compile_exchange_add( + rt: &mut Runtime, + allocator: &mut Allocator, + first: &Expression, + second: &Expression, + width: VMWidth, +) { + let first = compile_expression(rt, allocator, first, width); + let second = compile_expression(rt, allocator, second, VMWidth::Lower64); + + let pinned = [first, second] + .into_iter() + .filter_map(|value| match value { + Reference::Value(value) => Some(value), + Reference::Immediate(_) => None, + }) + .collect::>(); + + let dst = allocator.acquire(rt, &pinned, &[]); + allocator.copy(rt, first, dst); + + let src = allocator.acquire(rt, &pinned, &[dst]); + allocator.copy(rt, second, src); + + match width.size() { + 1 => { + let byte = get_gpr8(register_of_size(dst.into(), 1).unwrap()).unwrap(); + rt.asm.lock().xadd(ptr(src), byte).unwrap(); + } + 2 => { + let word = get_gpr16(register_of_size(dst.into(), 2).unwrap()).unwrap(); + rt.asm.lock().xadd(ptr(src), word).unwrap(); + } + 4 => { + let dword = get_gpr32(register_of_size(dst.into(), 4).unwrap()).unwrap(); + rt.asm.lock().xadd(ptr(src), dword).unwrap(); + } + 8 => { + rt.asm.lock().xadd(ptr(src), dst).unwrap(); + } + _ => unreachable!(), + } + + allocator.copy(rt, first, src); + + rt.asm.add(src, dst).unwrap(); + + allocator.replace(dst, Value::Output(1), true); + allocator.replace(src, Value::Output(0), true); + + allocator.consume(first); + allocator.consume(second); +} + +fn compile_compare_exchange( + rt: &mut Runtime, + allocator: &mut Allocator, + first: &Expression, + second: &Expression, + third: &Expression, + width: VMWidth, +) { + let first = compile_expression(rt, allocator, first, width); + let second = compile_expression(rt, allocator, second, width); + let third = compile_expression(rt, allocator, third, VMWidth::Lower64); + + let pinned = [first, second, third] + .into_iter() + .filter_map(|value| match value { + Reference::Value(value) => Some(value), + Reference::Immediate(_) => None, + }) + .collect::>(); + + let src = allocator.acquire(rt, &pinned, &[rax]); + allocator.copy(rt, second, src); + + let dst = allocator.acquire(rt, &pinned, &[rax, src]); + allocator.copy(rt, third, dst); + + allocator.copy(rt, first, rax); + + match width.size() { + 1 => { + let byte = get_gpr8(register_of_size(src.into(), 1).unwrap()).unwrap(); + rt.asm.lock().cmpxchg(ptr(dst), byte).unwrap(); + } + 2 => { + let word = get_gpr16(register_of_size(src.into(), 2).unwrap()).unwrap(); + rt.asm.lock().cmpxchg(ptr(dst), word).unwrap(); + } + 4 => { + let dword = get_gpr32(register_of_size(src.into(), 4).unwrap()).unwrap(); + rt.asm.lock().cmpxchg(ptr(dst), dword).unwrap(); + } + 8 => { + rt.asm.lock().cmpxchg(ptr(dst), src).unwrap(); + } + _ => unreachable!(), + } + + allocator.replace(rax, Value::Output(0), true); + + allocator.consume(first); + allocator.consume(second); + allocator.consume(third); +} + fn compile_operation( rt: &mut Runtime, width: VMWidth, @@ -342,30 +499,30 @@ fn compile_operation( match (width.size(), signed) { (1, false) => { - let byte = get_gpr8(register::sized(src.into(), 1).unwrap()).unwrap(); + let byte = get_gpr8(register_of_size(src.into(), 1).unwrap()).unwrap(); unsigned8(&mut rt.asm, byte); } (2, false) => { - let word = get_gpr16(register::sized(src.into(), 2).unwrap()).unwrap(); + let word = get_gpr16(register_of_size(src.into(), 2).unwrap()).unwrap(); unsigned16(&mut rt.asm, word); } (4, false) => { - let dword = get_gpr32(register::sized(src.into(), 4).unwrap()).unwrap(); + let dword = get_gpr32(register_of_size(src.into(), 4).unwrap()).unwrap(); unsigned32(&mut rt.asm, dword); } (8, false) => { unsigned64(&mut rt.asm, src); } (1, true) => { - let byte = get_gpr8(register::sized(src.into(), 1).unwrap()).unwrap(); + let byte = get_gpr8(register_of_size(src.into(), 1).unwrap()).unwrap(); signed8(&mut rt.asm, byte); } (2, true) => { - let word = get_gpr16(register::sized(src.into(), 2).unwrap()).unwrap(); + let word = get_gpr16(register_of_size(src.into(), 2).unwrap()).unwrap(); signed16(&mut rt.asm, word); } (4, true) => { - let dword = get_gpr32(register::sized(src.into(), 4).unwrap()).unwrap(); + let dword = get_gpr32(register_of_size(src.into(), 4).unwrap()).unwrap(); signed32(&mut rt.asm, dword); } (8, true) => { @@ -637,6 +794,13 @@ fn compile_effect(rt: &mut Runtime, allocator: &mut Allocator, effect: &Effect, |rt, dst, src| rt.asm.tzcnt(dst, src).unwrap(), |rt, dst, src| rt.asm.tzcnt(dst, src).unwrap(), ), + Effect::Exchange(first, second) => compile_exchange(rt, allocator, first, second, width), + Effect::ExchangeAdd(first, second) => { + compile_exchange_add(rt, allocator, first, second, width) + } + Effect::CompareExchange(first, second, third) => { + compile_compare_exchange(rt, allocator, first, second, third, width) + } } } @@ -669,13 +833,13 @@ fn compile_unary( match width.size() { 2 => { - let dst = get_gpr16(register::sized(dst.into(), 2).unwrap()).unwrap(); - let src = get_gpr16(register::sized(src.into(), 2).unwrap()).unwrap(); + let dst = get_gpr16(register_of_size(dst.into(), 2).unwrap()).unwrap(); + let src = get_gpr16(register_of_size(src.into(), 2).unwrap()).unwrap(); unsigned16(rt, dst, src); } 4 => { - let dst = get_gpr32(register::sized(dst.into(), 4).unwrap()).unwrap(); - let src = get_gpr32(register::sized(src.into(), 4).unwrap()).unwrap(); + let dst = get_gpr32(register_of_size(dst.into(), 4).unwrap()).unwrap(); + let src = get_gpr32(register_of_size(src.into(), 4).unwrap()).unwrap(); unsigned32(rt, dst, src); } 8 => { @@ -813,7 +977,7 @@ fn compile_expression( match expression { Expression::BitNot(_) => rt.asm.not(dst).unwrap(), Expression::LowByte(_) => { - let byte = get_gpr8(register::sized(dst.into(), 1).unwrap()).unwrap(); + let byte = get_gpr8(register_of_size(dst.into(), 1).unwrap()).unwrap(); rt.asm.movzx(dst, byte).unwrap(); } _ => unreachable!(), @@ -947,7 +1111,7 @@ fn compile_compare( let tmp = allocator.acquire(rt, &pinned, &[dst, src]); - let byte = get_gpr8(register::sized(tmp.into(), 1).unwrap()).unwrap(); + let byte = get_gpr8(register_of_size(tmp.into(), 1).unwrap()).unwrap(); match compare { Compare::Equal(_, _) => { @@ -995,7 +1159,7 @@ fn compile_parity( allocator.copy(rt, src, dst); - let byte = get_gpr8(register::sized(dst.into(), 1).unwrap()).unwrap(); + let byte = get_gpr8(register_of_size(dst.into(), 1).unwrap()).unwrap(); rt.asm.movzx(dst, byte).unwrap(); rt.asm.popcnt(dst, dst).unwrap(); rt.asm.not(dst).unwrap(); diff --git a/runtime/src/vm/handlers/semantic/mod.rs b/runtime/src/vm/handlers/semantic/mod.rs index 690fb2b1..4bcb1b99 100644 --- a/runtime/src/vm/handlers/semantic/mod.rs +++ b/runtime/src/vm/handlers/semantic/mod.rs @@ -46,6 +46,9 @@ pub enum Effect { Sar(Expression, Expression), Bsr(Expression), Tzcnt(Expression), + Exchange(Expression, Expression), + ExchangeAdd(Expression, Expression), + CompareExchange(Expression, Expression, Expression), } #[derive(Debug, Clone)] @@ -184,14 +187,14 @@ impl Expression { | Expression::SignBit | Expression::BitSize | Expression::ByteMask(_) => {} - Expression::BitAnd(lhs, rhs) - | Expression::BitOr(lhs, rhs) - | Expression::BitXor(lhs, rhs) - | Expression::BitShr(lhs, rhs) - | Expression::BitShl(lhs, rhs) - | Expression::Sub(lhs, rhs) => { - lhs.operands(value); - rhs.operands(value); + Expression::BitAnd(first, second) + | Expression::BitOr(first, second) + | Expression::BitXor(first, second) + | Expression::BitShr(first, second) + | Expression::BitShl(first, second) + | Expression::Sub(first, second) => { + first.operands(value); + second.operands(value); } Expression::BitNot(inner) | Expression::LowByte(inner) | Expression::Parity(inner) => { inner.operands(value); @@ -208,12 +211,12 @@ impl Expression { | Expression::BitSize | Expression::ByteMask(_) => 0, Expression::Flag(_) => 1, - Expression::BitAnd(lhs, rhs) - | Expression::BitOr(lhs, rhs) - | Expression::BitXor(lhs, rhs) - | Expression::BitShr(lhs, rhs) - | Expression::BitShl(lhs, rhs) - | Expression::Sub(lhs, rhs) => 1 + lhs.temporary() + rhs.temporary(), + Expression::BitAnd(first, second) + | Expression::BitOr(first, second) + | Expression::BitXor(first, second) + | Expression::BitShr(first, second) + | Expression::BitShl(first, second) + | Expression::Sub(first, second) => 1 + first.temporary() + second.temporary(), Expression::BitNot(inner) | Expression::LowByte(inner) | Expression::Parity(inner) => { 1 + inner.temporary() } @@ -225,24 +228,31 @@ impl Expression { impl Effect { fn operands(&self, value: &mut usize) { match self { - Effect::Add(lhs, rhs) - | Effect::Sub(lhs, rhs) - | Effect::And(lhs, rhs) - | Effect::Or(lhs, rhs) - | Effect::Xor(lhs, rhs) - | Effect::Shr(lhs, rhs) - | Effect::Shl(lhs, rhs) - | Effect::Ror(lhs, rhs) - | Effect::Rol(lhs, rhs) - | Effect::Mul(lhs, rhs) - | Effect::Sar(lhs, rhs) => { - lhs.operands(value); - rhs.operands(value); + Effect::Add(first, second) + | Effect::Sub(first, second) + | Effect::And(first, second) + | Effect::Or(first, second) + | Effect::Xor(first, second) + | Effect::Shr(first, second) + | Effect::Shl(first, second) + | Effect::Ror(first, second) + | Effect::Rol(first, second) + | Effect::Mul(first, second) + | Effect::Sar(first, second) + | Effect::Exchange(first, second) + | Effect::ExchangeAdd(first, second) => { + first.operands(value); + second.operands(value); } - Effect::Div(divisor, low, high) => { - divisor.operands(value); - low.operands(value); - high.operands(value); + Effect::Div(first, second, third) => { + first.operands(value); + second.operands(value); + third.operands(value); + } + Effect::CompareExchange(first, second, third) => { + first.operands(value); + second.operands(value); + third.operands(value); } Effect::Assign(expression) | Effect::Bsr(expression) | Effect::Tzcnt(expression) => { expression.operands(value); @@ -264,10 +274,12 @@ impl Effect { | Effect::Sar(..) | Effect::Assign(..) | Effect::Bsr(..) - | Effect::Tzcnt(..) => { + | Effect::Tzcnt(..) + | Effect::Exchange(..) + | Effect::CompareExchange(..) => { *value = (*value).max(1); } - Effect::Mul(..) | Effect::Div(..) => { + Effect::Mul(..) | Effect::Div(..) | Effect::ExchangeAdd(..) => { *value = (*value).max(2); } } @@ -275,19 +287,24 @@ impl Effect { fn temporary(&self) -> usize { match self { - Effect::Add(lhs, rhs) - | Effect::Sub(lhs, rhs) - | Effect::And(lhs, rhs) - | Effect::Or(lhs, rhs) - | Effect::Xor(lhs, rhs) - | Effect::Shr(lhs, rhs) - | Effect::Shl(lhs, rhs) - | Effect::Ror(lhs, rhs) - | Effect::Rol(lhs, rhs) - | Effect::Mul(lhs, rhs) - | Effect::Sar(lhs, rhs) => lhs.temporary() + rhs.temporary(), - Effect::Div(divisor, low, high) => { - divisor.temporary() + low.temporary() + high.temporary() + Effect::Add(first, second) + | Effect::Sub(first, second) + | Effect::And(first, second) + | Effect::Or(first, second) + | Effect::Xor(first, second) + | Effect::Shr(first, second) + | Effect::Shl(first, second) + | Effect::Ror(first, second) + | Effect::Rol(first, second) + | Effect::Mul(first, second) + | Effect::Sar(first, second) + | Effect::Exchange(first, second) + | Effect::ExchangeAdd(first, second) => first.temporary() + second.temporary(), + Effect::Div(first, second, third) => { + first.temporary() + second.temporary() + third.temporary() + } + Effect::CompareExchange(first, second, third) => { + first.temporary() + second.temporary() + third.temporary() } Effect::Assign(expression) | Effect::Bsr(expression) | Effect::Tzcnt(expression) => { expression.temporary() @@ -299,22 +316,22 @@ impl Effect { impl Compare { fn operands(&self, value: &mut usize) { match self { - Compare::Equal(lhs, rhs) - | Compare::LessThan(lhs, rhs) - | Compare::GreaterThan(lhs, rhs) - | Compare::BitSet(lhs, rhs) => { - lhs.operands(value); - rhs.operands(value); + Compare::Equal(first, second) + | Compare::LessThan(first, second) + | Compare::GreaterThan(first, second) + | Compare::BitSet(first, second) => { + first.operands(value); + second.operands(value); } } } fn temporary(&self) -> usize { match self { - Compare::Equal(lhs, rhs) - | Compare::LessThan(lhs, rhs) - | Compare::GreaterThan(lhs, rhs) - | Compare::BitSet(lhs, rhs) => lhs.temporary() + rhs.temporary(), + Compare::Equal(first, second) + | Compare::LessThan(first, second) + | Compare::GreaterThan(first, second) + | Compare::BitSet(first, second) => first.temporary() + second.temporary(), } } } diff --git a/runtime/src/vm/transform/encrypt.rs b/runtime/src/vm/transform/encrypt.rs index bb7a0372..d867981f 100644 --- a/runtime/src/vm/transform/encrypt.rs +++ b/runtime/src/vm/transform/encrypt.rs @@ -3,6 +3,7 @@ use std::{matches, slice}; use rand::Rng; use crate::mapper::Mapper; +use crate::utils::invert_multiplier; use crate::vm::bytecode::{VMReg, VMWidth}; use crate::vm::encoders::discard::Discard; use crate::vm::encoders::jcc::Jcc; @@ -86,7 +87,7 @@ impl<'a> Encryptor<'a> { 1, Box::new(LoadImmediate { width: VMWidth::Lower64, - source: invert(multiplier).to_le_bytes().to_vec(), + source: invert_multiplier(multiplier).to_le_bytes().to_vec(), }), ); self.operations.insert( @@ -122,7 +123,9 @@ fn restore( source, })); - let mut source = invert(destination_multiplier).to_le_bytes().to_vec(); + let mut source = invert_multiplier(destination_multiplier) + .to_le_bytes() + .to_vec(); encrypt(&mut source, source_addend, source_multiplier); sequence.push(Box::new(LoadImmediate { width: VMWidth::Lower64, @@ -266,17 +269,6 @@ fn leaf(operation: &mut Box, addend: u64, multiplier: u64) -> bool { false } -/// Computes the multiplicative inverse of odd `x` modulo 2^64. -fn invert(x: u64) -> u64 { - let mut inverse = x; - - for _ in 0..5 { - inverse = inverse.wrapping_mul(2u64.wrapping_sub(x.wrapping_mul(inverse))); - } - - inverse -} - /// Encrypts `source` against `addend` and `multiplier`. pub fn encrypt(source: &mut [u8], addend: u64, multiplier: u64) { let mut buffer = [0u8; 8]; @@ -389,7 +381,7 @@ fn transform(addend: &mut u64, multiplier: &mut u64, preserve: bool) -> Vec State { } pub fn register(operand: usize, size: usize) -> Register { - register::sized(NATIVE_REGISTERS[operand % NATIVE_REGISTERS.len()], size).unwrap() + register_of_size(NATIVE_REGISTERS[operand % NATIVE_REGISTERS.len()], size).unwrap() } pub fn available(used: &[UsedRegister]) -> Register { diff --git a/tests/src/instructions.rs b/tests/src/instructions.rs index 26ce07da..58ad9bdb 100644 --- a/tests/src/instructions.rs +++ b/tests/src/instructions.rs @@ -36,65 +36,200 @@ macro_rules! define { } define!( +Adc_r16_rm16 +Adc_r32_rm32 Adc_r64_rm64 +Adc_rm16_imm16 +Adc_rm16_imm8 Adc_rm16_r16 +Adc_rm32_imm32 +Adc_rm32_imm8 Adc_rm32_r32 Adc_rm64_imm32 Adc_rm64_imm8 Adc_rm64_r64 +Adc_rm8_imm8 Adc_rm8_r8 +Adc_r8_rm8 +Add_r16_rm16 +Add_r32_rm32 Add_r64_rm64 +Add_rm16_imm16 +Add_rm16_imm8 Add_rm16_r16 +Add_rm32_imm32 +Add_rm32_imm8 Add_rm32_r32 Add_rm64_imm32 Add_rm64_imm8 Add_rm64_r64 +Add_rm8_imm8 Add_rm8_r8 +Add_r8_rm8 Addpd_xmm_xmmm128 Addps_xmm_xmmm128 +And_r16_rm16 +And_r32_rm32 And_r64_rm64 +And_rm16_imm16 +And_rm16_imm8 And_rm16_r16 +And_rm32_imm32 +And_rm32_imm8 And_rm32_r32 And_rm64_imm32 And_rm64_imm8 And_rm64_r64 +And_rm8_imm8 And_rm8_r8 +And_r8_rm8 Andnpd_xmm_xmmm128 Andnps_xmm_xmmm128 Andpd_xmm_xmmm128 Andps_xmm_xmmm128 +Bsr_r16_rm16 +Bsr_r32_rm32 Bsr_r64_rm64 +Bswap_r32 Bswap_r64 +Bt_rm16_imm8 +Bt_rm16_r16 +Bt_rm32_imm8 +Bt_rm32_r32 Bt_rm64_imm8 Bt_rm64_r64 +Btc_rm16_imm8 +Btc_rm16_r16 +Btc_rm32_imm8 +Btc_rm32_r32 Btc_rm64_imm8 Btc_rm64_r64 +Btr_rm16_imm8 +Btr_rm16_r16 +Btr_rm32_imm8 +Btr_rm32_r32 Btr_rm64_imm8 Btr_rm64_r64 +Bts_rm16_imm8 +Bts_rm16_r16 +Bts_rm32_imm8 +Bts_rm32_r32 Bts_rm64_imm8 Bts_rm64_r64 +Cmp_r16_rm16 +Cmp_r32_rm32 Cmp_r64_rm64 +Cmp_rm16_imm16 +Cmp_rm16_imm8 Cmp_rm16_r16 +Cmp_rm32_imm32 +Cmp_rm32_imm8 Cmp_rm32_r32 Cmp_rm64_imm32 Cmp_rm64_imm8 Cmp_rm64_r64 +Cmp_rm8_imm8 Cmp_rm8_r8 +Cmp_r8_rm8 +Cmpxchg_rm16_r16 +Cmpxchg_rm32_r32 Cmpxchg_rm64_r64 +Cmpxchg_rm8_r8 +Cmova_r16_rm16 +Cmova_r32_rm32 +Cmova_r64_rm64 +Cmovae_r16_rm16 +Cmovae_r32_rm32 +Cmovae_r64_rm64 +Cmovb_r16_rm16 +Cmovb_r32_rm32 +Cmovb_r64_rm64 +Cmovbe_r16_rm16 +Cmovbe_r32_rm32 +Cmovbe_r64_rm64 +Cmove_r16_rm16 +Cmove_r32_rm32 +Cmove_r64_rm64 +Cmovg_r16_rm16 +Cmovg_r32_rm32 +Cmovg_r64_rm64 +Cmovge_r16_rm16 +Cmovge_r32_rm32 +Cmovge_r64_rm64 +Cmovl_r16_rm16 +Cmovl_r32_rm32 +Cmovl_r64_rm64 +Cmovle_r16_rm16 +Cmovle_r32_rm32 +Cmovle_r64_rm64 +Cmovne_r16_rm16 +Cmovne_r32_rm32 +Cmovne_r64_rm64 +Cmovno_r16_rm16 +Cmovno_r32_rm32 +Cmovno_r64_rm64 +Cmovnp_r16_rm16 +Cmovnp_r32_rm32 +Cmovnp_r64_rm64 +Cmovns_r16_rm16 +Cmovns_r32_rm32 +Cmovns_r64_rm64 +Cmovo_r16_rm16 +Cmovo_r32_rm32 +Cmovo_r64_rm64 +Cmovp_r16_rm16 +Cmovp_r32_rm32 +Cmovp_r64_rm64 +Cmovs_r16_rm16 +Cmovs_r32_rm32 +Cmovs_r64_rm64 +Dec_rm16 +Dec_rm32 Dec_rm64 +Dec_rm8 +Div_rm16 +Div_rm32 Div_rm64 +Div_rm8 Divpd_xmm_xmmm128 Divps_xmm_xmmm128 +Idiv_rm16 +Idiv_rm32 Idiv_rm64 +Idiv_rm8 +Imul_r16_rm16 +Imul_r16_rm16_imm16 +Imul_r16_rm16_imm8 +Imul_r32_rm32 +Imul_r32_rm32_imm32 +Imul_r32_rm32_imm8 Imul_r64_rm64 Imul_r64_rm64_imm32 Imul_r64_rm64_imm8 +Imul_rm16 +Imul_rm32 Imul_rm64 +Imul_rm8 +Inc_rm16 +Inc_rm32 Inc_rm64 +Inc_rm8 +Mov_r16_imm16 +Mov_r16_rm16 +Mov_r32_imm32 +Mov_r32_rm32 Mov_r64_imm64 Mov_r64_rm64 +Mov_r8_imm8 +Mov_r8_rm8 +Mov_rm16_imm16 +Mov_rm16_r16 +Mov_rm32_imm32 +Mov_rm32_r32 Mov_rm64_imm32 Mov_rm64_r64 +Mov_rm8_imm8 +Mov_rm8_r8 Movapd_xmm_xmmm128 Movapd_xmmm128_xmm Movaps_xmm_xmmm128 @@ -111,25 +246,50 @@ Movsd_xmm_xmmm64 Movsd_xmmm64_xmm Movss_xmm_xmmm32 Movss_xmmm32_xmm +Movsx_r16_rm16 +Movsx_r16_rm8 +Movsx_r32_rm16 +Movsx_r32_rm8 Movsx_r64_rm8 Movsxd_r64_rm32 Movupd_xmm_xmmm128 Movupd_xmmm128_xmm Movups_xmm_xmmm128 Movups_xmmm128_xmm +Movzx_r16_rm16 +Movzx_r16_rm8 +Movzx_r32_rm16 +Movzx_r32_rm8 Movzx_r64_rm8 -Mul_rm64 Mulpd_xmm_xmmm128 Mulps_xmm_xmmm128 +Mul_rm16 +Mul_rm32 +Mul_rm64 +Mul_rm8 +Neg_rm16 +Neg_rm32 Neg_rm64 +Neg_rm8 +Not_rm16 +Not_rm32 Not_rm64 +Not_rm8 +Or_r16_rm16 +Or_r32_rm32 Or_r64_rm64 +Or_rm16_imm16 +Or_rm16_imm8 Or_rm16_r16 +Or_rm32_imm32 +Or_rm32_imm8 Or_rm32_r32 Or_rm64_imm32 Or_rm64_imm8 Or_rm64_r64 +Or_rm8_imm8 Or_rm8_r8 +Or_r8_rm8 Orpd_xmm_xmmm128 Orps_xmm_xmmm128 Paddb_xmm_xmmm128 @@ -148,19 +308,45 @@ Psubd_xmm_xmmm128 Psubq_xmm_xmmm128 Psubw_xmm_xmmm128 Pxor_xmm_xmmm128 +Rol_rm16_CL +Rol_rm16_imm8 +Rol_rm32_CL +Rol_rm32_imm8 Rol_rm64_CL Rol_rm64_imm8 +Rol_rm8_CL +Rol_rm8_imm8 +Ror_rm16_CL +Ror_rm16_imm8 +Ror_rm32_CL +Ror_rm32_imm8 Ror_rm64_CL Ror_rm64_imm8 +Ror_rm8_CL +Ror_rm8_imm8 +Sar_rm16_CL +Sar_rm16_imm8 +Sar_rm32_CL +Sar_rm32_imm8 Sar_rm64_CL Sar_rm64_imm8 +Sar_rm8_CL +Sar_rm8_imm8 +Sbb_r16_rm16 +Sbb_r32_rm32 Sbb_r64_rm64 +Sbb_rm16_imm16 +Sbb_rm16_imm8 Sbb_rm16_r16 +Sbb_rm32_imm32 +Sbb_rm32_imm8 Sbb_rm32_r32 Sbb_rm64_imm32 Sbb_rm64_imm8 Sbb_rm64_r64 +Sbb_rm8_imm8 Sbb_rm8_r8 +Sbb_r8_rm8 Seta_rm8 Setae_rm8 Setb_rm8 @@ -177,24 +363,49 @@ Setns_rm8 Seto_rm8 Setp_rm8 Sets_rm8 +Shl_rm16_CL +Shl_rm16_imm8 +Shl_rm32_CL +Shl_rm32_imm8 Shl_rm64_CL Shl_rm64_imm8 +Shl_rm8_CL +Shl_rm8_imm8 +Shr_rm16_CL +Shr_rm16_imm8 +Shr_rm32_CL +Shr_rm32_imm8 Shr_rm64_CL Shr_rm64_imm8 +Shr_rm8_CL +Shr_rm8_imm8 +Sub_r16_rm16 +Sub_r32_rm32 Sub_r64_rm64 +Sub_rm16_imm16 +Sub_rm16_imm8 Sub_rm16_r16 +Sub_rm32_imm32 +Sub_rm32_imm8 Sub_rm32_r32 Sub_rm64_imm32 Sub_rm64_imm8 Sub_rm64_r64 +Sub_rm8_imm8 Sub_rm8_r8 +Sub_r8_rm8 Subpd_xmm_xmmm128 Subps_xmm_xmmm128 +Test_rm16_imm16 Test_rm16_r16 +Test_rm32_imm32 Test_rm32_r32 Test_rm64_imm32 Test_rm64_r64 +Test_rm8_imm8 Test_rm8_r8 +Tzcnt_r16_rm16 +Tzcnt_r32_rm32 Tzcnt_r64_rm64 VEX_Vaddpd_xmm_xmm_xmmm128 VEX_Vaddps_xmm_xmm_xmmm128 @@ -210,240 +421,31 @@ VEX_Vpxor_xmm_xmm_xmmm128 VEX_Vsubpd_xmm_xmm_xmmm128 VEX_Vsubps_xmm_xmm_xmmm128 VEX_Vxorps_xmm_xmm_xmmm128 +Xadd_rm16_r16 +Xadd_rm32_r32 Xadd_rm64_r64 +Xadd_rm8_r8 +Xchg_rm16_r16 +Xchg_rm32_r32 Xchg_rm64_r64 +Xchg_rm8_r8 +Xor_r16_rm16 +Xor_r32_rm32 Xor_r64_rm64 +Xor_rm16_imm16 +Xor_rm16_imm8 Xor_rm16_r16 +Xor_rm32_imm32 +Xor_rm32_imm8 Xor_rm32_r32 Xor_rm64_imm32 Xor_rm64_imm8 Xor_rm64_r64 +Xor_rm8_imm8 Xor_rm8_r8 +Xor_r8_rm8 Xorpd_xmm_xmmm128 Xorps_xmm_xmmm128 -Adc_r8_rm8 -Adc_r16_rm16 -Adc_r32_rm32 -Add_r8_rm8 -Add_r16_rm16 -Add_r32_rm32 -And_r8_rm8 -And_r16_rm16 -And_r32_rm32 -Cmp_r8_rm8 -Cmp_r16_rm16 -Cmp_r32_rm32 -Or_r8_rm8 -Or_r16_rm16 -Or_r32_rm32 -Sbb_r8_rm8 -Sbb_r16_rm16 -Sbb_r32_rm32 -Sub_r8_rm8 -Sub_r16_rm16 -Sub_r32_rm32 -Xor_r8_rm8 -Xor_r16_rm16 -Xor_r32_rm32 -Mov_rm8_r8 -Mov_rm16_r16 -Mov_rm32_r32 -Mov_r8_rm8 -Mov_r16_rm16 -Mov_r32_rm32 -Mov_r8_imm8 -Mov_r16_imm16 -Mov_r32_imm32 -Mov_rm8_imm8 -Mov_rm16_imm16 -Mov_rm32_imm32 -Inc_rm8 -Inc_rm16 -Inc_rm32 -Dec_rm8 -Dec_rm16 -Dec_rm32 -Neg_rm8 -Neg_rm16 -Neg_rm32 -Not_rm8 -Not_rm16 -Not_rm32 -Bswap_r32 -Rol_rm8_CL -Rol_rm16_CL -Rol_rm32_CL -Ror_rm8_CL -Ror_rm16_CL -Ror_rm32_CL -Shl_rm8_CL -Shl_rm16_CL -Shl_rm32_CL -Shr_rm8_CL -Shr_rm16_CL -Shr_rm32_CL -Sar_rm8_CL -Sar_rm16_CL -Sar_rm32_CL -Rol_rm8_imm8 -Rol_rm16_imm8 -Rol_rm32_imm8 -Ror_rm8_imm8 -Ror_rm16_imm8 -Ror_rm32_imm8 -Shl_rm8_imm8 -Shl_rm16_imm8 -Shl_rm32_imm8 -Shr_rm8_imm8 -Shr_rm16_imm8 -Shr_rm32_imm8 -Sar_rm8_imm8 -Sar_rm16_imm8 -Sar_rm32_imm8 -Mul_rm8 -Mul_rm16 -Mul_rm32 -Imul_rm8 -Imul_rm16 -Imul_rm32 -Imul_r16_rm16 -Imul_r32_rm32 -Imul_r16_rm16_imm16 -Imul_r32_rm32_imm32 -Imul_r16_rm16_imm8 -Imul_r32_rm32_imm8 -Div_rm8 -Div_rm16 -Div_rm32 -Idiv_rm8 -Idiv_rm16 -Idiv_rm32 -Bt_rm16_r16 -Bt_rm32_r32 -Btc_rm16_r16 -Btc_rm32_r32 -Btr_rm16_r16 -Btr_rm32_r32 -Bts_rm16_r16 -Bts_rm32_r32 -Bt_rm16_imm8 -Bt_rm32_imm8 -Btc_rm16_imm8 -Btc_rm32_imm8 -Btr_rm16_imm8 -Btr_rm32_imm8 -Bts_rm16_imm8 -Bts_rm32_imm8 -Bsr_r16_rm16 -Bsr_r32_rm32 -Tzcnt_r16_rm16 -Tzcnt_r32_rm32 -Xadd_rm8_r8 -Xadd_rm16_r16 -Xadd_rm32_r32 -Xchg_rm8_r8 -Xchg_rm16_r16 -Xchg_rm32_r32 -Cmpxchg_rm8_r8 -Cmpxchg_rm16_r16 -Cmpxchg_rm32_r32 -Movsx_r16_rm8 -Movsx_r32_rm8 -Movsx_r16_rm16 -Movsx_r32_rm16 -Movzx_r16_rm8 -Movzx_r32_rm8 -Movzx_r16_rm16 -Movzx_r32_rm16 -Test_rm8_imm8 -Test_rm16_imm16 -Test_rm32_imm32 -Add_rm8_imm8 -Add_rm16_imm16 -Add_rm32_imm32 -Add_rm16_imm8 -Add_rm32_imm8 -Or_rm8_imm8 -Or_rm16_imm16 -Or_rm32_imm32 -Or_rm16_imm8 -Or_rm32_imm8 -Adc_rm8_imm8 -Adc_rm16_imm16 -Adc_rm32_imm32 -Adc_rm16_imm8 -Adc_rm32_imm8 -Sbb_rm8_imm8 -Sbb_rm16_imm16 -Sbb_rm32_imm32 -Sbb_rm16_imm8 -And_rm8_imm8 -And_rm16_imm16 -And_rm32_imm32 -And_rm16_imm8 -Sub_rm8_imm8 -Sub_rm16_imm16 -Sub_rm32_imm32 -Sub_rm16_imm8 -Sub_rm32_imm8 -Xor_rm8_imm8 -Xor_rm16_imm16 -Xor_rm32_imm32 -Xor_rm16_imm8 -Xor_rm32_imm8 -Cmp_rm8_imm8 -Cmp_rm16_imm16 -Cmp_rm32_imm32 -Cmp_rm16_imm8 -Cmp_rm32_imm8 -Cmova_r16_rm16 -Cmova_r32_rm32 -Cmova_r64_rm64 -Cmovae_r16_rm16 -Cmovae_r32_rm32 -Cmovae_r64_rm64 -Cmovb_r16_rm16 -Cmovb_r32_rm32 -Cmovb_r64_rm64 -Cmovbe_r16_rm16 -Cmovbe_r32_rm32 -Cmovbe_r64_rm64 -Cmove_r16_rm16 -Cmove_r32_rm32 -Cmove_r64_rm64 -Cmovg_r16_rm16 -Cmovg_r32_rm32 -Cmovg_r64_rm64 -Cmovge_r16_rm16 -Cmovge_r32_rm32 -Cmovge_r64_rm64 -Cmovl_r16_rm16 -Cmovl_r32_rm32 -Cmovl_r64_rm64 -Cmovle_r16_rm16 -Cmovle_r32_rm32 -Cmovle_r64_rm64 -Cmovne_r16_rm16 -Cmovne_r32_rm32 -Cmovne_r64_rm64 -Cmovno_r16_rm16 -Cmovno_r32_rm32 -Cmovno_r64_rm64 -Cmovnp_r16_rm16 -Cmovnp_r32_rm32 -Cmovnp_r64_rm64 -Cmovns_r16_rm16 -Cmovns_r32_rm32 -Cmovns_r64_rm64 -Cmovo_r16_rm16 -Cmovo_r32_rm32 -Cmovo_r64_rm64 -Cmovp_r16_rm16 -Cmovp_r32_rm32 -Cmovp_r64_rm64 -Cmovs_r16_rm16 -Cmovs_r32_rm32 -Cmovs_r64_rm64 ); fn test(code: Code) { From c910365f86bf8812d3067b613a67c50a48d98b03 Mon Sep 17 00:00:00 2001 From: RadonCoding <86915746+RadonCoding@users.noreply.github.com> Date: Wed, 30 Sep 2026 07:37:24 +0300 Subject: [PATCH 24/33] Update anti_tamper.rs --- .../protections/virtualization/attestation/anti_tamper.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/obfuscator/src/protections/virtualization/attestation/anti_tamper.rs b/obfuscator/src/protections/virtualization/attestation/anti_tamper.rs index acd8bad5..94a60d01 100644 --- a/obfuscator/src/protections/virtualization/attestation/anti_tamper.rs +++ b/obfuscator/src/protections/virtualization/attestation/anti_tamper.rs @@ -41,6 +41,8 @@ pub fn generate( apply_operation(operation, byte as u64, &mut lane0); } } + + *expected = lane0 ^ lane1; let count = FnDef::VARIANTS.len(); @@ -140,10 +142,7 @@ pub fn generate( instructions.extend(spill_vector(ACCUMULATOR, VMWidth::Lower128)); instructions.extend(xor(None, None)); - instructions.extend(register_lcg(engine, None)); instructions.extend(store_register(VMReg::Vp1)); - *expected = apply_lcg(engine, lane0 ^ lane1); - instructions } From b977107b9a13c86e4e6921afbd953b4f9d95def1 Mon Sep 17 00:00:00 2001 From: unknown <86915746+RadonCoding@users.noreply.github.com> Date: Sat, 3 Oct 2026 07:40:32 +0300 Subject: [PATCH 25/33] fix: fixed attestation and hardened immediate encryption --- .../virtualization/attestation/mod.rs | 14 +- .../src/protections/virtualization/crypt.rs | 109 +++--- .../src/protections/virtualization/mod.rs | 311 +++++++++--------- runtime/src/runtime.rs | 3 +- runtime/src/vm/bytecode.rs | 9 +- runtime/src/vm/functions/crypt.rs | 4 +- runtime/src/vm/functions/dispatch.rs | 27 +- runtime/src/vm/functions/lookup.rs | 2 +- runtime/src/vm/transform/encrypt.rs | 47 +-- tests/src/constants.rs | 2 +- tests/src/instructions.rs | 2 +- tests/src/lib.rs | 36 +- 12 files changed, 287 insertions(+), 279 deletions(-) diff --git a/obfuscator/src/protections/virtualization/attestation/mod.rs b/obfuscator/src/protections/virtualization/attestation/mod.rs index 4e6fb85b..a0cf3832 100644 --- a/obfuscator/src/protections/virtualization/attestation/mod.rs +++ b/obfuscator/src/protections/virtualization/attestation/mod.rs @@ -157,10 +157,10 @@ fn correct( ) -> Vec> { let mut instructions = Vec::>::new(); - instructions.extend(load_absolute(engine, DataDef::VmAttestation, VMReg::Rax)); - instructions.extend(load_absolute(engine, DataDef::VmCode, VMReg::Rcx)); + instructions.extend(load_absolute(engine, DataDef::VmCodeEnd, VMReg::Rax)); + instructions.extend(load_absolute(engine, DataDef::VmAttestation, VMReg::Rcx)); - instructions.extend(sub(Some(VMReg::Vg0), Some(VMReg::Rax))); + instructions.extend(sub(Some(VMReg::Vg0), Some(VMReg::Rcx))); instructions.extend(store_register(VMReg::Rdx)); instructions.extend(skip( @@ -244,15 +244,19 @@ fn correct( fn register_lcg(engine: &mut Engine, register: Option) -> Vec> { let mut instructions = Vec::>::new(); + if let Some(register) = register { + instructions.extend(load_register(register)); + } + instructions.extend(load_data( engine, DataDef::VmKeyMultiplier, VMWidth::Lower64, )); - instructions.extend(mul(register, None)); + instructions.extend(mul(None, None)); instructions.extend(load_data(engine, DataDef::VmKeyAddend, VMWidth::Lower64)); - instructions.extend(add(register, None)); + instructions.extend(add(None, None)); instructions } diff --git a/obfuscator/src/protections/virtualization/crypt.rs b/obfuscator/src/protections/virtualization/crypt.rs index bfef86da..3fa9754c 100644 --- a/obfuscator/src/protections/virtualization/crypt.rs +++ b/obfuscator/src/protections/virtualization/crypt.rs @@ -7,6 +7,65 @@ pub const TRAILER_SIZE: usize = size_of::() + size_of::(); const ENCRYPTED: u8 = 0; const DECRYPTED: u8 = 1; +pub struct Cipher { + multiplier: u64, + addend: u64, +} + +impl Cipher { + pub fn new(multiplier: u64, addend: u64) -> Self { + Self { multiplier, addend } + } + + pub fn encrypt_block(&self, block: &mut Vec, key: u64, secret: u64) { + let length = prepare_block(block); + self.encrypt_payload(block, key, secret); + finalize_encrypt(block, length); + } + + pub fn decrypt_block(&self, block: &mut Vec, key: u64, secret: u64) { + let length = u16::from_le_bytes(block[..HEADER_SIZE].try_into().unwrap()) as usize; + self.decrypt_payload(block, key, secret); + unprepare_block(block, length); + } + + fn encrypt_payload(&self, block: &mut [u8], key: u64, secret: u64) { + let mut key = key ^ secret; + + for chunk in block.chunks_exact_mut(8) { + let mut qword = u64::from_le_bytes(chunk.try_into().unwrap()); + qword ^= key; + chunk.copy_from_slice(&qword.to_le_bytes()); + key ^= qword; + key = key.wrapping_mul(self.multiplier).wrapping_add(self.addend); + } + } + + pub fn decrypt_payload(&self, block: &mut [u8], mut key: u64, secret: u64) { + let length = u16::from_le_bytes(block[..HEADER_SIZE].try_into().unwrap()) as usize; + let payload = &mut block[HEADER_SIZE..HEADER_SIZE + ((length + 8 + 7) & !7)]; + + for chunk in payload.chunks_exact_mut(8) { + let qword = u64::from_le_bytes(chunk.try_into().unwrap()); + let original = qword ^ key; + chunk.copy_from_slice(&original.to_le_bytes()); + key ^= qword ^ secret; + key = key.wrapping_mul(self.multiplier).wrapping_add(self.addend); + } + + assert_eq!( + u64::from_le_bytes( + payload[length..length + size_of::()] + .try_into() + .unwrap() + ), + VM_INTEGRITY_QWORD + ); + + finalize_decrypt(block); + } +} + pub fn derive_hash(bytes: &[u8]) -> u64 { let start = HEADER_SIZE; let end = bytes.len() - TRAILER_SIZE; @@ -22,18 +81,6 @@ pub fn derive_key(bytes: &[u8]) -> u64 { u64::from_le_bytes(bytes[start..end].try_into().unwrap()) } -pub fn encrypt_block(block: &mut Vec, key: u64, mul: u64, add: u64, att: u64) { - let length = prepare_block(block); - encrypt_payload(block, key, mul, add, att); - finalize_encrypt(block, length); -} - -pub fn decrypt_block(block: &mut Vec, key: u64, mul: u64, add: u64, att: u64) { - let length = u16::from_le_bytes(block[..HEADER_SIZE].try_into().unwrap()) as usize; - decrypt_payload(block, key, mul, add, att); - unprepare_block(block, length); -} - fn align_payload(block: &mut Vec) { let mut rng = rand::thread_rng(); @@ -44,43 +91,7 @@ fn align_payload(block: &mut Vec) { } } -fn encrypt_payload(block: &mut [u8], key: u64, mul: u64, add: u64, att: u64) { - let mut key = key ^ att; - - for chunk in block.chunks_exact_mut(8) { - let mut qword = u64::from_le_bytes(chunk.try_into().unwrap()); - qword ^= key; - chunk.copy_from_slice(&qword.to_le_bytes()); - key ^= qword; - key = key.wrapping_mul(mul).wrapping_add(add); - } -} - -pub fn decrypt_payload(block: &mut [u8], mut key: u64, mul: u64, add: u64, att: u64) { - let length = u16::from_le_bytes(block[..HEADER_SIZE].try_into().unwrap()) as usize; - let payload = &mut block[HEADER_SIZE..HEADER_SIZE + ((length + 8 + 7) & !7)]; - - for chunk in payload.chunks_exact_mut(8) { - let qword = u64::from_le_bytes(chunk.try_into().unwrap()); - let original = qword ^ key; - chunk.copy_from_slice(&original.to_le_bytes()); - key ^= qword ^ att; - key = key.wrapping_mul(mul).wrapping_add(add); - } - - assert_eq!( - u64::from_le_bytes( - payload[length..length + size_of::()] - .try_into() - .unwrap() - ), - VM_INTEGRITY_QWORD - ); - - finalize_decrypt(block); -} - -pub fn finalize_encrypt(block: &mut Vec, length: u16) { +fn finalize_encrypt(block: &mut Vec, length: u16) { // word - length block.splice(0..0, length.to_le_bytes()); // byte - state diff --git a/obfuscator/src/protections/virtualization/mod.rs b/obfuscator/src/protections/virtualization/mod.rs index 7f16f7af..f3b2351f 100644 --- a/obfuscator/src/protections/virtualization/mod.rs +++ b/obfuscator/src/protections/virtualization/mod.rs @@ -13,16 +13,71 @@ use logger::{debug, info}; use rand::Rng; use runtime::runtime::{DataDef, FnDef}; use runtime::vm::bytecode::{self}; -use runtime::vm::encoders::Encode; use runtime::{VM_DISPATCH_SIZE, VM_REDIRECT_SIZE, VM_TRAMPOLINE_SIZE}; mod attestation; pub mod crypt; mod language; +#[derive(Clone, Copy, Default)] +struct TableEntry { + exit: u32, + entry: u32, + offset: u32, +} + +impl TableEntry { + fn new(rva: u32, size: usize, return_address: i32, offset: u32) -> Self { + Self { + exit: (rva as i64 + size as i64 - return_address as i64) as u32, + entry: (rva as i64 - return_address as i64) as u32, + offset, + } + } +} + +impl From for [u8; size_of::()] { + fn from(entry: TableEntry) -> Self { + let mut bytes = [0u8; size_of::()]; + bytes[0..4].copy_from_slice(&entry.exit.to_le_bytes()); + bytes[4..8].copy_from_slice(&entry.entry.to_le_bytes()); + bytes[8..12].copy_from_slice(&entry.offset.to_le_bytes()); + bytes + } +} + +fn resolve(engine: &Engine, label: DataDef) -> (u32, usize) { + let rva = engine.rt.lookup(engine.rt.data_labels[&label]) as u32; + let offset = engine.pe.translate(RVA(rva).into()).unwrap(); + (rva, offset) +} + +fn write_displacement(engine: &mut Engine, label: DataDef, target: u32) { + let (rva, offset) = resolve(engine, label); + let displacement = (target as i64 - rva as i64) as i32; + engine.pe.write(offset, &displacement.to_le_bytes()).unwrap(); +} + +fn write_entry(engine: &mut Engine, index: usize, entry: TableEntry) { + let (_, offset) = resolve(engine, DataDef::VmTable); + let bytes = <[u8; size_of::()]>::from(entry); + engine + .pe + .write(offset + index * size_of::(), &bytes) + .unwrap(); +} + +fn write_trampoline(engine: &mut Engine, redirect: usize, bytes: &[u8]) { + let (_, offset) = resolve(engine, DataDef::VmTrampolines); + engine + .pe + .write(offset + redirect * VM_REDIRECT_SIZE, bytes) + .unwrap(); +} + #[derive(Default)] pub struct Virtualization { - programs: Vec>>, + programs: Vec>, groups: Vec>, virtualized: HashMap, redirects: HashMap, @@ -32,19 +87,25 @@ pub struct Virtualization { } impl Virtualization { - fn attestation(&self, engine: &mut Engine) -> Vec { - let mut vcode = Vec::new(); + fn attestation(&self, engine: &mut Engine, base: u64) -> Vec { + let mut code = Vec::new(); + let mut log = Vec::new(); let blocks = attestation::generate(engine, engine.rt.keys.secret); + let cipher = crypt::Cipher::new(engine.rt.keys.multiplier, engine.rt.keys.addend); + for (index, operations) in blocks.into_iter().enumerate() { let mut rng = rand::thread_rng(); + let offset = base + code.len() as u64; + let transformed = if engine.args.verbose { let (transformed, snapshots) = bytecode::transform_with_snapshots( &mut engine.rt.mapper, operations, + offset, |ready| rng.gen_range(0..ready.len()), ); @@ -52,35 +113,23 @@ impl Virtualization { transformed } else { - bytecode::transform(&mut engine.rt.mapper, operations, |ready| { + bytecode::transform(&mut engine.rt.mapper, operations, offset, |ready| { rng.gen_range(0..ready.len()) }) }; let mut bytes = bytecode::assemble(&mut engine.rt.mapper, &transformed); - let key = if vcode.is_empty() { + let key = if code.is_empty() { engine.rt.keys.initializer } else { - crypt::derive_key(&vcode) + crypt::derive_key(&code) }; - crypt::encrypt_block( - &mut bytes, - key, - engine.rt.keys.multiplier, - engine.rt.keys.addend, - 0, - ); - crypt::decrypt_payload( - &mut bytes, - key, - engine.rt.keys.multiplier, - engine.rt.keys.addend, - 0, - ); + cipher.encrypt_block(&mut bytes, key, 0); + cipher.decrypt_payload(&mut bytes, key, 0); - vcode.extend_from_slice(&bytes); + code.extend_from_slice(&bytes); } if engine.args.verbose { @@ -91,7 +140,7 @@ impl Virtualization { ); } - vcode + code } } @@ -99,10 +148,14 @@ impl Protection for Virtualization { fn initialize(&mut self, engine: &mut Engine) { let mut log = Vec::new(); - let mut vtable = Vec::new(); + let mut table = Vec::new(); let mut lookup = HashMap::new(); + let mut code = Vec::new(); + + let cipher = crypt::Cipher::new(engine.rt.keys.multiplier, engine.rt.keys.addend); + 'outer: for block in &mut engine.blocks { if block.size < VM_TRAMPOLINE_SIZE { continue; @@ -137,10 +190,13 @@ impl Protection for Virtualization { if lookup.get(&hash).is_none() { let mut rng = rand::thread_rng(); + let offset = code.len() as u64; + let transformed = if engine.args.verbose { let (transformed, snapshots) = bytecode::transform_with_snapshots( &mut engine.rt.mapper, lifted, + offset, |ready| rng.gen_range(0..ready.len()), ); @@ -148,13 +204,31 @@ impl Protection for Virtualization { transformed } else { - bytecode::transform(&mut engine.rt.mapper, lifted, |ready| { + bytecode::transform(&mut engine.rt.mapper, lifted, offset, |ready| { rng.gen_range(0..ready.len()) }) }; + let mut bytes = bytecode::assemble(&mut engine.rt.mapper, &transformed); + + let key = if code.is_empty() { + engine.rt.keys.initializer + } else { + crypt::derive_key(&code) + }; + + let secret = if code.is_empty() { + 0 + } else { + engine.rt.keys.secret + }; + + cipher.encrypt_block(&mut bytes, key, secret); + + code.extend_from_slice(&bytes); + let index = self.programs.len(); - self.programs.push(transformed); + self.programs.push(bytes); self.groups.push(vec![block.rva]); @@ -168,14 +242,12 @@ impl Protection for Virtualization { for (_, group) in self.programs.iter().zip(&self.groups) { for &rva in group { - // Store the index of this VM-block's VM-table entry - let index = vtable.len() / 12; + let index = table.len() / size_of::(); self.virtualized.insert(rva, index); - // Store placeholders for exit displacement, entry displacement, and bytecode offset - vtable.extend_from_slice(&0u32.to_le_bytes()); - vtable.extend_from_slice(&0u32.to_le_bytes()); - vtable.extend_from_slice(&0u32.to_le_bytes()); + table.extend_from_slice(&<[u8; size_of::()]>::from( + TableEntry::default(), + )); } } @@ -197,9 +269,10 @@ impl Protection for Virtualization { } } - engine.rt.define_data_bytes(DataDef::VmTable, &vtable); + engine.rt.define_data_bytes(DataDef::VmTable, &table); - engine.rt.define_data_dword(DataDef::VmCode, 0); + engine.rt.define_data_dword(DataDef::VmCodeStart, 0); + engine.rt.define_data_dword(DataDef::VmCodeEnd, 0); engine.rt.define_data_dword(DataDef::VmAttestation, 0); engine @@ -214,89 +287,44 @@ impl Protection for Virtualization { } fn apply(&self, engine: &mut Engine) { - let attestation = self.attestation(engine); + let mut code = Vec::new(); + let mut offsets = vec![0u32; self.virtualized.len()]; - let mut vcode = Vec::new(); - vcode.extend_from_slice(&attestation); - - let vtable_rva = engine.rt.lookup(engine.rt.data_labels[&DataDef::VmTable]) as u32; - let vtable_offset = engine.pe.translate(RVA(vtable_rva).into()).unwrap(); - let vtable = unsafe { engine.pe.as_ptr().add(vtable_offset) as *mut u8 }; - - for (program, group) in self.programs.iter().zip(&self.groups) { - let mut bytes = bytecode::assemble(&mut engine.rt.mapper, program); - - let key = if vcode.len() == attestation.len() { - crypt::derive_hash(&vcode) - } else { - crypt::derive_key(&vcode) - }; - - crypt::encrypt_block( - &mut bytes, - key, - engine.rt.keys.multiplier, - engine.rt.keys.addend, - engine.rt.keys.secret, - ); - - let offset = TryInto::::try_into(vcode.len()).unwrap(); - vcode.extend_from_slice(&bytes); + for (bytes, group) in self.programs.iter().zip(&self.groups) { + let offset = TryInto::::try_into(code.len()).unwrap(); + code.extend_from_slice(bytes); for &rva in group { - let index = self.virtualized[&rva]; - unsafe { - vtable - .add(index * 12 + 2 * size_of::()) - .copy_from(offset.to_le_bytes().as_ptr(), size_of::()); - } + offsets[self.virtualized[&rva]] = offset; } } + let attestation = self.attestation(engine, code.len() as u64); + code.extend_from_slice(&attestation); + let section = engine.create_section( Some("🏴‍☠️"), - &vcode, + &code, SectionCharacteristics::CNT_INITIALIZED_DATA | SectionCharacteristics::MEM_READ | SectionCharacteristics::MEM_WRITE, ); + let base = section.virtual_address.0; + + write_displacement(engine, DataDef::VmCodeStart, base); + write_displacement(engine, DataDef::VmCodeEnd, base + code.len() as u32); + write_displacement( + engine, + DataDef::VmAttestation, + base + (code.len() - attestation.len()) as u32, + ); - let vtable_rva = engine.rt.lookup(engine.rt.data_labels[&DataDef::VmTable]) as u32; - let vtable_offset = engine.pe.translate(RVA(vtable_rva).into()).unwrap(); - let vtable = unsafe { engine.pe.as_ptr().add(vtable_offset) as *mut u8 }; - - let vcode_rva = engine.rt.lookup(engine.rt.data_labels[&DataDef::VmCode]) as u32; - let vcode_offset = engine.pe.translate(RVA(vcode_rva).into()).unwrap(); - let vcode_displacement = (section.virtual_address.0 as i64 - vcode_rva as i64) as i32; - engine - .pe - .write(vcode_offset, &vcode_displacement.to_le_bytes()) - .unwrap(); - - let vattestation_rva = engine - .rt - .lookup(engine.rt.data_labels[&DataDef::VmAttestation]) - as u32; - let vattestation_offset = engine.pe.translate(RVA(vattestation_rva).into()).unwrap(); - let vattestation_displacement = ((section.virtual_address.0 as i64 - + attestation.len() as i64) - - vattestation_rva as i64) as i32; - engine - .pe - .write( - vattestation_offset, - &vattestation_displacement.to_le_bytes(), - ) - .unwrap(); - - let ventry_rva = engine.rt.lookup(engine.rt.function_labels[&FnDef::VmEntry]); + let entry_rva = engine.rt.lookup(engine.rt.function_labels[&FnDef::VmEntry]); let redirects_rva = engine .rt .lookup(engine.rt.data_labels[&DataDef::VmTrampolines]) as u32; - let trampolines_offset = engine.pe.translate(RVA(redirects_rva).into()).unwrap(); - let trampolines = unsafe { engine.pe.as_ptr().add(trampolines_offset) as *mut u8 }; for i in 0..engine.blocks.len() { let rva = engine.blocks[i].rva; @@ -306,84 +334,59 @@ impl Protection for Virtualization { continue; } - let vtable_index = self.virtualized[&rva]; + let index = self.virtualized[&rva]; - if size >= VM_DISPATCH_SIZE { - let mut asm = CodeAssembler::new(engine.bitness).unwrap(); + let mut asm = CodeAssembler::new(engine.bitness).unwrap(); - asm.push(vtable_index as i32 | 0x10000000).unwrap(); - asm.call(ventry_rva).unwrap(); - let dispatch1 = asm.assemble(rva as u64).unwrap(); + if size >= VM_DISPATCH_SIZE { + asm.push(index as i32 | 0x10000000).unwrap(); + asm.call(entry_rva).unwrap(); + let first = asm.assemble(rva as u64).unwrap(); - assert!(dispatch1.len() <= VM_DISPATCH_SIZE); + assert!(first.len() <= VM_DISPATCH_SIZE); asm.reset(); // Stub has to be assembled twice so that the runtime return address can be calculated - let return_address = rva as i32 + dispatch1.len() as i32; + let return_address = rva as i32 + first.len() as i32; - asm.push((vtable_index as i32 | 0x10000000) ^ return_address) + asm.push((index as i32 | 0x10000000) ^ return_address) .unwrap(); - asm.call(ventry_rva).unwrap(); - - let dispatch2 = asm.assemble(rva as u64).unwrap(); - - assert_eq!(dispatch1.len(), dispatch2.len()); - - // Patch the exit and entry displacement placeholders in the table - unsafe { - let exit = (rva as i64 + size as i64 - return_address as i64) as i32; - let entry = (rva as i64 - return_address as i64) as i32; - vtable - .add(vtable_index * 12) - .copy_from((exit as u32).to_le_bytes().as_ptr(), size_of::()); - vtable - .add(vtable_index * 12 + size_of::()) - .copy_from((entry as u32).to_le_bytes().as_ptr(), size_of::()); - } + asm.call(entry_rva).unwrap(); + let second = asm.assemble(rva as u64).unwrap(); + + assert_eq!(first.len(), second.len()); + + write_entry( + engine, + index, + TableEntry::new(rva, size, return_address, offsets[index]), + ); - engine.replace(i, &dispatch2); + engine.replace(i, &second); } else { let redirect = self.redirects[&rva]; let redirect_rva = redirects_rva + (redirect * VM_REDIRECT_SIZE) as u32; - let mut asm = CodeAssembler::new(engine.bitness).unwrap(); - - let return_address = redirect_rva + VM_REDIRECT_SIZE as u32; - - asm.mov( - dword_ptr(rsp), - (vtable_index as i32 | 0x10000000) ^ return_address as i32, - ) - .unwrap(); - asm.call(ventry_rva).unwrap(); + let return_address = (redirect_rva + VM_REDIRECT_SIZE as u32) as i32; + asm.mov(dword_ptr(rsp), (index as i32 | 0x10000000) ^ return_address) + .unwrap(); + asm.call(entry_rva).unwrap(); let dispatch = asm.assemble(redirect_rva as u64).unwrap(); assert_eq!(dispatch.len(), VM_REDIRECT_SIZE); - unsafe { - trampolines - .add(redirect * VM_REDIRECT_SIZE) - .copy_from(dispatch.as_ptr(), VM_REDIRECT_SIZE); - } - - // Patch the exit and entry displacement placeholders in the table - unsafe { - let exit = (rva as i64 + size as i64 - return_address as i64) as i32; - let entry = (rva as i64 - return_address as i64) as i32; - vtable - .add(vtable_index * 12) - .copy_from((exit as u32).to_le_bytes().as_ptr(), size_of::()); - vtable - .add(vtable_index * 12 + size_of::()) - .copy_from((entry as u32).to_le_bytes().as_ptr(), size_of::()); - } + write_trampoline(engine, redirect, &dispatch); + write_entry( + engine, + index, + TableEntry::new(rva, size, return_address, offsets[index]), + ); asm.reset(); asm.call(redirect_rva as u64).unwrap(); - let branch = asm.assemble(rva as u64).unwrap(); assert!(branch.len() <= size); diff --git a/runtime/src/runtime.rs b/runtime/src/runtime.rs index ce1ffaeb..5d393bb4 100644 --- a/runtime/src/runtime.rs +++ b/runtime/src/runtime.rs @@ -115,7 +115,8 @@ mapped! { VmDebugTlsIndex, VmCleanupFlsIndex, VmTable, - VmCode, + VmCodeStart, + VmCodeEnd, VmAttestation, VmTrampolines, VmKeyInitializer, diff --git a/runtime/src/vm/bytecode.rs b/runtime/src/vm/bytecode.rs index e49aeb19..56fde05d 100644 --- a/runtime/src/vm/bytecode.rs +++ b/runtime/src/vm/bytecode.rs @@ -652,6 +652,7 @@ pub fn assemble(mapper: &mut Mapper, operations: &[Box]) -> Vec fn transforms<'a>( picker: &'a RefCell<&'a mut dyn FnMut(&[usize]) -> usize>, + offset: u64, ) -> Vec> { vec![ Box::new(Peephole), @@ -659,7 +660,7 @@ fn transforms<'a>( Box::new(Scramble), Box::new(Mutation), Box::new(Indirect), - Box::new(Encrypt), + Box::new(Encrypt { offset }), Box::new(Permute { picker }), Box::new(Peephole), ] @@ -668,6 +669,7 @@ fn transforms<'a>( pub fn transform( mapper: &mut Mapper, mut operations: Vec>, + offset: u64, mut picker: F, ) -> Vec> where @@ -675,7 +677,7 @@ where { let picker = RefCell::<&mut dyn FnMut(&[usize]) -> usize>::new(&mut picker); - for transform in &transforms(&picker) { + for transform in &transforms(&picker, offset) { operations = transform.run(mapper, operations); } @@ -685,6 +687,7 @@ where pub fn transform_with_snapshots( mapper: &mut Mapper, mut operations: Vec>, + offset: u64, mut picker: F, ) -> (Vec>, Snapshots) where @@ -695,7 +698,7 @@ where let mut snapshots = Snapshots::new(); snapshots.record(Phase::Lift, &operations); - for transform in &transforms(&picker) { + for transform in &transforms(&picker, offset) { operations = transform.run(mapper, operations); snapshots.record(transform.phase(), &operations); diff --git a/runtime/src/vm/functions/crypt.rs b/runtime/src/vm/functions/crypt.rs index 3cfc50a9..8e862b98 100644 --- a/runtime/src/vm/functions/crypt.rs +++ b/runtime/src/vm/functions/crypt.rs @@ -169,7 +169,7 @@ pub fn build(rt: &mut Runtime) { { // lea rax, [...] rt.asm - .lea(rax, ptr(rt.data_labels[&DataDef::VmCode])) + .lea(rax, ptr(rt.data_labels[&DataDef::VmCodeStart])) .unwrap(); // movsxd rcx, [rax] rt.asm.movsxd(rcx, ptr(rax)).unwrap(); @@ -213,7 +213,7 @@ pub fn build(rt: &mut Runtime) { { // lea rcx, [...] rt.asm - .lea(rcx, ptr(rt.data_labels[&DataDef::VmCode])) + .lea(rcx, ptr(rt.data_labels[&DataDef::VmAttestation])) .unwrap(); // movsxd rax, [rcx] rt.asm.movsxd(rax, ptr(rcx)).unwrap(); diff --git a/runtime/src/vm/functions/dispatch.rs b/runtime/src/vm/functions/dispatch.rs index caa261da..e532d6e9 100644 --- a/runtime/src/vm/functions/dispatch.rs +++ b/runtime/src/vm/functions/dispatch.rs @@ -5,7 +5,7 @@ use iced_x86::code_asm::{ use crate::{ mapper::Mappable, - runtime::{FnDef, ImportDef, Runtime, StringDef}, + runtime::{DataDef, FnDef, ImportDef, Runtime, StringDef}, vm::{ bytecode::{VMOp, VMReg}, utils::{self, lock}, @@ -154,10 +154,27 @@ pub fn build(rt: &mut Runtime) { { // mov [r12 + ...], 0x0 utils::vreg::store_imm(rt, r12, 0x0, VMReg::NBranch); - // mov [r12 + ...], 0x0 - utils::vreg::store_imm(rt, r12, 0x0, VMReg::VImmAdd); - // mov [r12 + ...], 0x1 - utils::vreg::store_imm(rt, r12, 0x1, VMReg::VImmMul); + + // lea rax, [...] + rt.asm + .lea(rax, ptr(rt.data_labels[&DataDef::VmCodeStart])) + .unwrap(); + // movsxd rcx, [rax] + rt.asm.movsxd(rcx, ptr(rax)).unwrap(); + // add rax, rcx + rt.asm.add(rax, rcx).unwrap(); + // mov rcx, r13 + rt.asm.mov(rcx, r13).unwrap(); + // sub rcx, 0x2 + rt.asm.sub(rcx, 0x2).unwrap(); + // sub rcx, rax + rt.asm.sub(rcx, rax).unwrap(); + // mov [r12 + ...], rcx + utils::vreg::store_reg(rt, r12, rcx, VMReg::VImmAdd); + // or rcx, 0x1 + rt.asm.or(rcx, 0x1).unwrap(); + // mov [r12 + ...], rcx + utils::vreg::store_reg(rt, r12, rcx, VMReg::VImmMul); } rt.asm.set_label(&mut execute_loop).unwrap(); diff --git a/runtime/src/vm/functions/lookup.rs b/runtime/src/vm/functions/lookup.rs index 340987f1..ddbc2d10 100644 --- a/runtime/src/vm/functions/lookup.rs +++ b/runtime/src/vm/functions/lookup.rs @@ -54,7 +54,7 @@ pub fn build(rt: &mut Runtime) { // Compute the block pointer from the offset into bytecode: // lea rax, [...] rt.asm - .lea(rax, ptr(rt.data_labels[&DataDef::VmCode])) + .lea(rax, ptr(rt.data_labels[&DataDef::VmCodeStart])) .unwrap(); // movsxd rcx, [rax] rt.asm.movsxd(rcx, ptr(rax)).unwrap(); diff --git a/runtime/src/vm/transform/encrypt.rs b/runtime/src/vm/transform/encrypt.rs index d867981f..db8d80b5 100644 --- a/runtime/src/vm/transform/encrypt.rs +++ b/runtime/src/vm/transform/encrypt.rs @@ -26,7 +26,9 @@ struct Encryptor<'a> { } /// Encrypts every immediate against rolling immediates held in [`VMReg::VImmAdd`] and [`VMReg::VImmMul`]. -pub struct Encrypt; +pub struct Encrypt { + pub offset: u64, +} impl Transform for Encrypt { fn phase(&self) -> Phase { @@ -42,7 +44,16 @@ impl Transform for Encrypt { let mut encryptor = Encryptor::new(mapper, &mut operations, addend, multiplier); encryptor.process(); - encryptor.prologue(addend, multiplier); + + operations.splice( + 0..0, + restore( + addend, + multiplier, + self.offset, + invert_multiplier(self.offset | 1), + ), + ); operations } @@ -73,38 +84,6 @@ impl<'a> Encryptor<'a> { &mut self.multiplier, ); } - - /// Emits the seed sequence for [`VMReg::VImmAdd`] and [`VMReg::VImmMul`]. - fn prologue(&mut self, addend: u64, multiplier: u64) { - self.operations.insert( - 0, - Box::new(LoadImmediate { - width: VMWidth::Lower64, - source: addend.to_le_bytes().to_vec(), - }), - ); - self.operations.insert( - 1, - Box::new(LoadImmediate { - width: VMWidth::Lower64, - source: invert_multiplier(multiplier).to_le_bytes().to_vec(), - }), - ); - self.operations.insert( - 2, - Box::new(StoreRegister { - width: VMWidth::Lower64, - destination: VMReg::VImmMul, - }), - ); - self.operations.insert( - 3, - Box::new(StoreRegister { - width: VMWidth::Lower64, - destination: VMReg::VImmAdd, - }), - ); - } } /// Emits a sequence to restore [`VMReg::VImmAdd`]/[`VMReg::VImmMul`] using the current values. diff --git a/tests/src/constants.rs b/tests/src/constants.rs index 97783ae9..836b01fd 100644 --- a/tests/src/constants.rs +++ b/tests/src/constants.rs @@ -103,7 +103,7 @@ pub fn vector(mut state: State, register: VMVec, bytes: [u128; 2]) -> State { } pub fn register(operand: usize, size: usize) -> Register { - register_of_size(NATIVE_REGISTERS[operand % NATIVE_REGISTERS.len()], size).unwrap() + utils::register_of_size(NATIVE_REGISTERS[operand % NATIVE_REGISTERS.len()], size).unwrap() } pub fn available(used: &[UsedRegister]) -> Register { diff --git a/tests/src/instructions.rs b/tests/src/instructions.rs index 58ad9bdb..df62e23f 100644 --- a/tests/src/instructions.rs +++ b/tests/src/instructions.rs @@ -797,7 +797,7 @@ fn compare_memory(state: State, instructions: &[Instruction], memory: &mut [u8]) let mut executor = Executor::new(); let lifted = bytecode::lift(instructions).unwrap(); - let transformed = bytecode::transform(&mut executor.rt.mapper, lifted, |_| 0); + let transformed = bytecode::transform(&mut executor.rt.mapper, lifted, 0, |_| 0); let mut bytes = bytecode::assemble(&mut executor.rt.mapper, &transformed); encrypt_block(&mut bytes); diff --git a/tests/src/lib.rs b/tests/src/lib.rs index 950d378b..ded9a6c8 100644 --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -249,8 +249,10 @@ impl Executor { // lea rax, [...] self.rt .asm - .lea(rax, ptr(self.rt.data_labels[&DataDef::VmCode])) + .lea(rax, ptr(self.rt.data_labels[&DataDef::VmCodeStart])) .unwrap(); + // add rax, size_of::() + self.rt.asm.add(rax, size_of::() as i32).unwrap(); // mov [r12 + ...], rax utils::vreg::store_reg(&mut self.rt, r12, rax, VMReg::BPointer); @@ -306,7 +308,9 @@ impl Executor { // ret self.rt.asm.ret().unwrap(); - self.rt.define_data_bytes(DataDef::VmCode, bytes); + let mut data = (size_of::() as i32).to_le_bytes().to_vec(); + data.extend_from_slice(bytes); + self.rt.define_data_bytes(DataDef::VmCodeStart, &data); let ip = self.memory as u64; @@ -481,32 +485,18 @@ impl Drop for Executor { } } +fn cipher() -> crypt::Cipher { + crypt::Cipher::new(Executor::TEST_KEY_MULTIPLIER, Executor::TEST_KEY_ADDEND) +} + pub fn encrypt_block(block: &mut Vec) { - crypt::encrypt_block( - block, - Executor::TEST_KEY_INITIALIZER, - Executor::TEST_KEY_MULTIPLIER, - Executor::TEST_KEY_ADDEND, - 0, - ); + cipher().encrypt_block(block, Executor::TEST_KEY_INITIALIZER, 0); } pub fn decrypt_payload(block: &mut Vec) { - crypt::decrypt_payload( - block, - Executor::TEST_KEY_INITIALIZER, - Executor::TEST_KEY_MULTIPLIER, - Executor::TEST_KEY_ADDEND, - 0, - ); + cipher().decrypt_payload(block, Executor::TEST_KEY_INITIALIZER, 0); } pub fn decrypt_block(block: &mut Vec) { - crypt::decrypt_block( - block, - Executor::TEST_KEY_INITIALIZER, - Executor::TEST_KEY_MULTIPLIER, - Executor::TEST_KEY_ADDEND, - 0, - ); + cipher().decrypt_block(block, Executor::TEST_KEY_INITIALIZER, 0); } From 99d0a769915d82177966fb257089bffd390bcae6 Mon Sep 17 00:00:00 2001 From: unknown <86915746+RadonCoding@users.noreply.github.com> Date: Sat, 3 Oct 2026 16:33:53 +0300 Subject: [PATCH 26/33] chore: further hardening of attestation --- .../virtualization/attestation/anti_debug.rs | 55 +++-------- .../virtualization/attestation/anti_tamper.rs | 57 ++++++------ .../virtualization/attestation/mod.rs | 93 ++++++++----------- .../protections/virtualization/language.rs | 30 +----- 4 files changed, 82 insertions(+), 153 deletions(-) diff --git a/obfuscator/src/protections/virtualization/attestation/anti_debug.rs b/obfuscator/src/protections/virtualization/attestation/anti_debug.rs index a6a5602c..fecf1f74 100644 --- a/obfuscator/src/protections/virtualization/attestation/anti_debug.rs +++ b/obfuscator/src/protections/virtualization/attestation/anti_debug.rs @@ -5,8 +5,6 @@ use runtime::runtime::ImportDef; use runtime::vm::bytecode::{VMReg, VMSeg, VMWidth}; use runtime::vm::encoders::Encode; -const ACCUMULATOR: VMReg = VMReg::Rbx; - const NT_QUERY_INFORMATION_PROCESS_PROLOGUE: [u8; 3] = [0x4C, 0x8B, 0xD1]; const NT_SET_INFORMATION_THREAD_PROLOGUE: [u8; 3] = [0x4C, 0x8B, 0xD1]; const NT_QUERY_INFORMATION_THREAD_PROLOGUE: [u8; 3] = [0x4C, 0x8B, 0xD1]; @@ -33,16 +31,13 @@ pub fn generate( instructions.extend(reserve(0x30)); - instructions.extend(set_register(ACCUMULATOR, 0)); + instructions.extend(set_register(VMReg::Vp0, 0)); instructions.extend(query_kd_debugger_enabled(engine, rng, expected)); instructions.extend(query_process_debug_object_handle(engine, rng, expected)); instructions.extend(set_hide_from_debugger(engine, rng, expected)); instructions.extend(query_hide_from_debbuger(engine, rng, expected)); - instructions.extend(load_register(ACCUMULATOR)); - instructions.extend(store_register(VMReg::Vp0)); - instructions.extend(release(0x30)); instructions @@ -64,20 +59,16 @@ fn query_kd_debugger_enabled( VMWidth::Lower8, )); b.extend(accumulate_immediate( - engine, rng, - ACCUMULATOR, - None, + VMReg::Vp0, None, 0, &mut *expected, )); b.extend(import(engine, ImportDef::NtQuerySystemInformation)); b.extend(accumulate_prologue( - engine, rng, - ACCUMULATOR, - VMReg::Rax, + VMReg::Vp0, VMReg::Rax, &NT_QUERY_INFORMATION_PROCESS_PROLOGUE, expected, )); @@ -101,20 +92,16 @@ fn query_kd_debugger_enabled( b.extend(invoke(VMReg::Rax)); b.extend(accumulate_immediate( - engine, rng, - ACCUMULATOR, - Some(VMReg::Rax), + VMReg::Vp0, Some(VMReg::Rax), STATUS_SUCCESS, expected, )); // READ KernelDebuggerEnabled + KernelDebuggerNotPresent b.extend(accumulate_memory( - engine, rng, - ACCUMULATOR, - VMReg::Rsp, + VMReg::Vp0, VMReg::Rsp, 0x20, VMWidth::Lower16, 0x0100, @@ -133,10 +120,8 @@ fn query_process_debug_object_handle( b.extend(import(engine, ImportDef::NtQueryInformationProcess)); b.extend(accumulate_prologue( - engine, rng, - ACCUMULATOR, - VMReg::Rax, + VMReg::Vp0, VMReg::Rax, &NT_QUERY_INFORMATION_PROCESS_PROLOGUE, expected, )); @@ -162,20 +147,16 @@ fn query_process_debug_object_handle( b.extend(invoke(VMReg::Rax)); b.extend(accumulate_immediate( - engine, rng, - ACCUMULATOR, - Some(VMReg::Rax), + VMReg::Vp0, Some(VMReg::Rax), STATUS_PORT_NOT_SET, expected, )); // READ ProcessDebugObjectHandle b.extend(accumulate_memory( - engine, rng, - ACCUMULATOR, - VMReg::Rsp, + VMReg::Vp0, VMReg::Rsp, 0x28, VMWidth::Lower64, 0, @@ -194,10 +175,8 @@ fn set_hide_from_debugger( b.extend(import(engine, ImportDef::NtSetInformationThread)); b.extend(accumulate_prologue( - engine, rng, - ACCUMULATOR, - VMReg::Rax, + VMReg::Vp0, VMReg::Rax, &NT_SET_INFORMATION_THREAD_PROLOGUE, expected, )); @@ -213,10 +192,8 @@ fn set_hide_from_debugger( b.extend(invoke(VMReg::Rax)); b.extend(accumulate_immediate( - engine, rng, - ACCUMULATOR, - Some(VMReg::Rax), + VMReg::Vp0, Some(VMReg::Rax), STATUS_SUCCESS, expected, )); @@ -233,10 +210,8 @@ fn query_hide_from_debbuger( b.extend(import(engine, ImportDef::NtQueryInformationThread)); b.extend(accumulate_prologue( - engine, rng, - ACCUMULATOR, - VMReg::Rax, + VMReg::Vp0, VMReg::Rax, &NT_QUERY_INFORMATION_THREAD_PROLOGUE, expected, )); @@ -263,20 +238,16 @@ fn query_hide_from_debbuger( b.extend(invoke(VMReg::Rax)); b.extend(accumulate_immediate( - engine, rng, - ACCUMULATOR, - Some(VMReg::Rax), + VMReg::Vp0, Some(VMReg::Rax), STATUS_SUCCESS, expected, )); // READ ThreadHideFromDebugger b.extend(accumulate_byte( - engine, rng, - ACCUMULATOR, - VMReg::Rsp, + VMReg::Vp0, VMReg::Rsp, 0x28, 1, expected, diff --git a/obfuscator/src/protections/virtualization/attestation/anti_tamper.rs b/obfuscator/src/protections/virtualization/attestation/anti_tamper.rs index 94a60d01..8cc15a54 100644 --- a/obfuscator/src/protections/virtualization/attestation/anti_tamper.rs +++ b/obfuscator/src/protections/virtualization/attestation/anti_tamper.rs @@ -1,4 +1,4 @@ -use std::{convert::TryInto, i32}; +use std::convert::TryInto; use crate::engine::Engine; use crate::protections::virtualization::attestation::*; @@ -6,11 +6,9 @@ use exe::{Buffer, PE, RVA}; use rand::Rng; use runtime::mapper::Mappable; use runtime::runtime::{DataDef, FnDef}; -use runtime::vm::bytecode::{VMReg, VMSeg, VMVec, VMWidth}; +use runtime::vm::bytecode::{VMReg, VMSeg, VMWidth}; use runtime::vm::encoders::Encode; -const ACCUMULATOR: VMVec = VMVec::Ymm6; - pub fn generate( engine: &mut Engine, rng: &mut impl Rng, @@ -18,8 +16,9 @@ pub fn generate( ) -> Vec> { let operation = Operation::random(rng); - let mut lane0 = 0; - let mut lane1 = 0; + let seed = engine.rt.keys.initializer; + + let mut hash = seed; for &function in FnDef::VARIANTS.iter() { let rva = engine.rt.lookup(engine.rt.function_labels[&function]) as u32; @@ -28,21 +27,21 @@ pub fn generate( let offset = engine.pe.translate(RVA(rva).into()).unwrap(); let bytes = engine.pe.read(offset, size).unwrap(); - let mut chunks = bytes.chunks_exact(size_of::()); + let mut chunks = bytes.chunks_exact(size_of::()); for chunk in &mut chunks { - let lo = u64::from_le_bytes(chunk[0..8].try_into().unwrap()); - let hi = u64::from_le_bytes(chunk[8..16].try_into().unwrap()); - apply_operation(operation, lo, &mut lane0); - apply_operation(operation, hi, &mut lane1); + let word = u64::from_le_bytes(chunk.try_into().unwrap()); + apply_operation(operation, word, &mut hash); + hash = apply_lcg(engine, hash); } for &byte in chunks.remainder() { - apply_operation(operation, byte as u64, &mut lane0); + apply_operation(operation, byte as u64, &mut hash); + hash = apply_lcg(engine, hash); } } - - *expected = lane0 ^ lane1; + + *expected = hash; let count = FnDef::VARIANTS.len(); @@ -50,7 +49,7 @@ pub fn generate( let mut instructions = Vec::>::new(); - instructions.extend(set_vector(ACCUMULATOR, 0, 0)); + instructions.extend(set_register(VMReg::Vp1, seed)); instructions.extend(foreach( engine, @@ -80,7 +79,7 @@ pub fn generate( outer.extend(add(None, None)); outer.extend(store_register(VMReg::Rcx)); - outer.extend(mask(Some(VMReg::Rdx), 15)); + outer.extend(mask(Some(VMReg::Rdx), 7)); outer.extend(store_register(VMReg::R8)); outer.extend(sub(Some(VMReg::Rdx), Some(VMReg::R8))); outer.extend(store_register(VMReg::R9)); @@ -89,21 +88,22 @@ pub fn generate( engine, VMReg::R10, Bound::Register(VMReg::R9), - 16, - |_| { + 8, + |engine| { let mut inner = Vec::>::new(); - inner.extend(spill_vector(ACCUMULATOR, VMWidth::Lower128)); + inner.extend(load_register(VMReg::Vp1)); inner.extend(load_memory( VMReg::Rcx, VMReg::R10, 1, 0, VMSeg::None, - VMWidth::Lower128, + VMWidth::Lower64, )); - inner.push(vector_operation(operation)); - inner.extend(reload_vector(ACCUMULATOR, VMWidth::Lower128)); + inner.extend(register_operation(operation, None, None)); + inner.extend(register_lcg(engine, None)); + inner.extend(store_register(VMReg::Vp1)); inner }, @@ -117,10 +117,10 @@ pub fn generate( VMReg::R9, Bound::Register(VMReg::R8), 1, - |_| { + |engine| { let mut inner = Vec::>::new(); - inner.extend(spill_vector(ACCUMULATOR, VMWidth::Lower64)); + inner.extend(load_register(VMReg::Vp1)); inner.extend(load_memory( VMReg::Rdx, VMReg::R9, @@ -129,8 +129,9 @@ pub fn generate( VMSeg::None, VMWidth::Lower8, )); - inner.extend(register_operation(operation)); - inner.extend(reload_vector(ACCUMULATOR, VMWidth::Lower64)); + inner.extend(register_operation(operation, None, None)); + inner.extend(register_lcg(engine, None)); + inner.extend(store_register(VMReg::Vp1)); inner }, @@ -140,9 +141,5 @@ pub fn generate( }, )); - instructions.extend(spill_vector(ACCUMULATOR, VMWidth::Lower128)); - instructions.extend(xor(None, None)); - instructions.extend(store_register(VMReg::Vp1)); - instructions } diff --git a/obfuscator/src/protections/virtualization/attestation/mod.rs b/obfuscator/src/protections/virtualization/attestation/mod.rs index a0cf3832..caf36f86 100644 --- a/obfuscator/src/protections/virtualization/attestation/mod.rs +++ b/obfuscator/src/protections/virtualization/attestation/mod.rs @@ -4,10 +4,7 @@ use crate::engine::Engine; use crate::protections::virtualization::{crypt, language::*}; use rand::Rng; use runtime::runtime::DataDef; -use runtime::vm::bytecode::{Flag, VMCondition, VMPrecision, VMReg, VMSeg, VMWidth}; -use runtime::vm::encoders::vector_add::VectorAdd; -use runtime::vm::encoders::vector_sub::VectorSub; -use runtime::vm::encoders::vector_xor::VectorXor; +use runtime::vm::bytecode::{Flag, VMCondition, VMReg, VMSeg, VMWidth}; use runtime::vm::encoders::Encode; mod anti_debug; @@ -36,6 +33,14 @@ impl Operation { _ => Operation::Xor, } } + + fn inverse(self) -> Self { + match self { + Operation::Add => Operation::Sub, + Operation::Sub => Operation::Add, + Operation::Xor => Operation::Xor, + } + } } pub fn generate(engine: &mut Engine, key: u64) -> Vec>> { @@ -48,6 +53,9 @@ pub fn generate(engine: &mut Engine, key: u64) -> Vec>> { let mut vp0 = 0; let mut vp1 = 0; + let mix0 = Operation::random(&mut rng); + let mix1 = Operation::random(&mut rng); + block.extend(timestamp()); block.extend(mask(None, !((1u64 << WINDOW) - 1))); block.extend(register_lcg(engine, None)); @@ -87,7 +95,6 @@ pub fn generate(engine: &mut Engine, key: u64) -> Vec>> { b.extend(store_register(VMReg::Rcx)); b.extend(accumulate_immediate( - engine, &mut rng, VMReg::Vp0, Some(VMReg::Rcx), @@ -95,7 +102,6 @@ pub fn generate(engine: &mut Engine, key: u64) -> Vec>> { &mut vp0, )); b.extend(accumulate_immediate( - engine, &mut rng, VMReg::Vp1, Some(VMReg::Rcx), @@ -113,7 +119,6 @@ pub fn generate(engine: &mut Engine, key: u64) -> Vec>> { b.extend(store_register(VMReg::Rcx)); b.extend(accumulate_immediate( - engine, &mut rng, VMReg::Vp0, Some(VMReg::Rcx), @@ -121,7 +126,6 @@ pub fn generate(engine: &mut Engine, key: u64) -> Vec>> { &mut vp0, )); b.extend(accumulate_immediate( - engine, &mut rng, VMReg::Vp1, Some(VMReg::Rcx), @@ -129,10 +133,12 @@ pub fn generate(engine: &mut Engine, key: u64) -> Vec>> { &mut vp1, )); - b.extend(register_lcg(engine, Some(VMReg::Vp0))); + b.extend(register_operation(mix0, Some(VMReg::Vp0), Some(VMReg::Vt0))); + b.extend(register_lcg(engine, None)); b.extend(store_register(VMReg::Vp0)); - b.extend(register_lcg(engine, Some(VMReg::Vp1))); + b.extend(register_operation(mix1, Some(VMReg::Vp1), Some(VMReg::Vt0))); + b.extend(register_lcg(engine, None)); b.extend(store_register(VMReg::Vp1)); b.extend(copy(VMReg::Vt0, VMReg::Vt1)); @@ -141,7 +147,7 @@ pub fn generate(engine: &mut Engine, key: u64) -> Vec>> { }, )); - block.extend(correct(engine, &mut rng, key, vp0, vp1)); + block.extend(correct(engine, &mut rng, key, vp0, vp1, mix0, mix1)); blocks.push(block); @@ -154,6 +160,8 @@ fn correct( key: u64, vp0: u64, vp1: u64, + mix0: Operation, + mix1: Operation, ) -> Vec> { let mut instructions = Vec::>::new(); @@ -224,13 +232,16 @@ fn correct( )); let operation = Operation::random(rng); - let combined = apply_lcg(engine, combine_operation(operation, vp0, vp1)); + let combined = combine_operation(operation, vp0, vp1); let correction = combined ^ key; instructions.extend(register_lcg_inverse(engine, Some(VMReg::Vp0))); + instructions.extend(register_operation(mix0.inverse(), None, Some(VMReg::Vt1))); + instructions.extend(register_lcg_inverse(engine, Some(VMReg::Vp1))); - instructions.extend(register_operation(operation)); - instructions.extend(register_lcg(engine, None)); + instructions.extend(register_operation(mix1.inverse(), None, Some(VMReg::Vt1))); + + instructions.extend(register_operation(operation, None, None)); instructions.extend(immediate(correction)); instructions.extend(xor(None, None)); @@ -301,38 +312,19 @@ fn apply_operation(operation: Operation, value: u64, expected: &mut u64) { } } -fn register_operation(operation: Operation) -> Vec> { - let mut instructions = Vec::>::new(); - - match operation { - Operation::Add => instructions.extend(add(None, None)), - Operation::Sub => instructions.extend(sub(None, None)), - Operation::Xor => instructions.extend(xor(None, None)), - } - - instructions -} - -fn vector_operation(operation: Operation) -> Box { +fn register_operation( + operation: Operation, + a: Option, + b: Option, +) -> Vec> { match operation { - Operation::Add => Box::new(VectorAdd { - width: VMWidth::Lower128, - stride: VMWidth::Lower64, - precision: VMPrecision::Integer, - }), - Operation::Sub => Box::new(VectorSub { - width: VMWidth::Lower128, - stride: VMWidth::Lower64, - precision: VMPrecision::Integer, - }), - Operation::Xor => Box::new(VectorXor { - width: VMWidth::Lower128, - }), + Operation::Add => add(a, b), + Operation::Sub => sub(a, b), + Operation::Xor => xor(a, b), } } fn accumulate( - engine: &mut Engine, rng: &mut R, accumulator: VMReg, source: Vec>, @@ -343,19 +335,21 @@ fn accumulate( let operation = Operation::random(rng); + let mix = rng.gen::(); + instructions.extend(load_register(accumulator)); instructions.extend(source); - instructions.extend(register_lcg(engine, None)); - instructions.extend(register_operation(operation)); + instructions.extend(immediate(mix)); + instructions.extend(xor(None, None)); + instructions.extend(register_operation(operation, None, None)); instructions.extend(store_register(accumulator)); - apply_operation(operation, apply_lcg(engine, value), expected); + apply_operation(operation, value ^ mix, expected); instructions } pub fn accumulate_immediate( - engine: &mut Engine, rng: &mut R, accumulator: VMReg, source: Option, @@ -366,11 +360,10 @@ pub fn accumulate_immediate( Some(register) => load_register(register), None => Vec::new(), }; - accumulate(engine, rng, accumulator, source, value, expected) + accumulate(rng, accumulator, source, value, expected) } pub fn accumulate_memory( - engine: &mut Engine, rng: &mut R, accumulator: VMReg, base: VMReg, @@ -380,11 +373,10 @@ pub fn accumulate_memory( expected: &mut u64, ) -> Vec> { let source = load_memory(base, VMReg::None, 1, displacement, VMSeg::None, width); - accumulate(engine, rng, accumulator, source, value, expected) + accumulate(rng, accumulator, source, value, expected) } fn accumulate_byte( - engine: &mut Engine, rng: &mut R, accumulator: VMReg, base: VMReg, @@ -393,7 +385,6 @@ fn accumulate_byte( expected: &mut u64, ) -> Vec> { accumulate_memory( - engine, rng, accumulator, base, @@ -405,7 +396,6 @@ fn accumulate_byte( } fn accumulate_prologue( - engine: &mut Engine, rng: &mut R, accumulator: VMReg, base: VMReg, @@ -416,7 +406,6 @@ fn accumulate_prologue( for (offset, byte) in prologue.iter().enumerate() { instructions.extend(accumulate_byte( - engine, rng, accumulator, base, diff --git a/obfuscator/src/protections/virtualization/language.rs b/obfuscator/src/protections/virtualization/language.rs index 9a1b86fd..dbcc0acf 100644 --- a/obfuscator/src/protections/virtualization/language.rs +++ b/obfuscator/src/protections/virtualization/language.rs @@ -1,6 +1,6 @@ use crate::engine::Engine; use runtime::runtime::{DataDef, FnDef, ImportDef}; -use runtime::vm::bytecode::{Flag, VMCondition, VMLogic, VMMem, VMReg, VMSeg, VMVec, VMWidth}; +use runtime::vm::bytecode::{Flag, VMCondition, VMLogic, VMMem, VMReg, VMSeg, VMWidth}; use runtime::vm::encoders::add::Add; use runtime::vm::encoders::and::And; use runtime::vm::encoders::block::{Block, Jump, Target}; @@ -11,7 +11,6 @@ use runtime::vm::encoders::load_address::LoadAddress; use runtime::vm::encoders::load_immediate::LoadImmediate; use runtime::vm::encoders::load_memory::LoadMemory; use runtime::vm::encoders::load_register::LoadRegister; -use runtime::vm::encoders::load_vector::LoadVector; use runtime::vm::encoders::mul::Mul; use runtime::vm::encoders::or::Or; use runtime::vm::encoders::pop::Pop; @@ -19,7 +18,6 @@ use runtime::vm::encoders::push::Push; use runtime::vm::encoders::shl::Shl; use runtime::vm::encoders::shr::Shr; use runtime::vm::encoders::store_memory::StoreMemory; -use runtime::vm::encoders::store_merge::StoreMerge; use runtime::vm::encoders::store_register::StoreRegister; use runtime::vm::encoders::sub::Sub; use runtime::vm::encoders::timestamp::Timestamp; @@ -199,24 +197,6 @@ pub fn set_register(register: VMReg, value: u64) -> Vec> { ] } -pub fn set_vector(destination: VMVec, lo: u64, hi: u64) -> Vec> { - let mut instructions: Vec> = Vec::>::new(); - - instructions.push(Box::new(LoadImmediate { - width: VMWidth::Lower64, - source: lo.to_le_bytes().to_vec(), - })); - instructions.push(Box::new(LoadImmediate { - width: VMWidth::Lower64, - source: hi.to_le_bytes().to_vec(), - })); - instructions.push(Box::new(StoreMerge { - width: VMWidth::Lower128, - destination, - })); - instructions -} - pub fn copy(source: VMReg, destination: VMReg) -> Vec> { vec![ Box::new(LoadRegister { @@ -330,10 +310,6 @@ pub fn load_register(source: VMReg) -> Vec> { })] } -pub fn spill_vector(source: VMVec, width: VMWidth) -> Vec> { - vec![Box::new(LoadVector { width, source })] -} - pub fn store_register(destination: VMReg) -> Vec> { vec![Box::new(StoreRegister { width: VMWidth::Lower64, @@ -341,10 +317,6 @@ pub fn store_register(destination: VMReg) -> Vec> { })] } -pub fn reload_vector(destination: VMVec, width: VMWidth) -> Vec> { - vec![Box::new(StoreMerge { width, destination })] -} - pub fn mask(source: Option, mask: u64) -> Vec> { let mut instructions = Vec::>::new(); From 9a6465c25ce38f8f9a11a001f7c1b94e1c1fdf4d Mon Sep 17 00:00:00 2001 From: unknown <86915746+RadonCoding@users.noreply.github.com> Date: Sun, 4 Oct 2026 15:30:56 +0300 Subject: [PATCH 27/33] chore: improved case shuffle of dispatches --- example.ps1 | 49 ++++-- examples/cpp/bench/main.cpp | 12 +- examples/cpp/crack-me/main.cpp | 36 ++++ examples/cpp/exception-cxx/main.cpp | 23 ++- examples/cpp/multithread/main.cpp | 1 - runtime/src/runtime.rs | 78 ++++++++- runtime/src/vm/encoders/debug.rs | 148 ----------------- runtime/src/vm/encoders/mod.rs | 249 ++++++++++++++++++++++------ runtime/src/vm/utils/precision.rs | 23 +-- runtime/src/vm/utils/width.rs | 34 ++-- 10 files changed, 374 insertions(+), 279 deletions(-) create mode 100644 examples/cpp/crack-me/main.cpp delete mode 100644 runtime/src/vm/encoders/debug.rs diff --git a/example.ps1 b/example.ps1 index 7956e7cc..4d4003e1 100644 --- a/example.ps1 +++ b/example.ps1 @@ -1,6 +1,12 @@ +using namespace System.IO + param( [Parameter(Mandatory)] - [System.IO.DirectoryInfo]$Example, + [DirectoryInfo]$Example, + + [ValidateSet("debug", "release")] + [string]$Configuration = "debug", + [string[]]$Arguments ) @@ -8,7 +14,7 @@ $ErrorActionPreference = "Stop" $language = Split-Path (Split-Path $Example -Parent) -Leaf -cargo build --bin obfuscator $Arguments +cargo build --bin obfuscator --$Configuration $Arguments if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } @@ -16,7 +22,9 @@ switch ($language) { "rust" { $manifest = Join-Path $Example.FullName "Cargo.toml" - cargo build --manifest-path $manifest + cargo build ` + --manifest-path $manifest ` + --$Configuration if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } @@ -25,31 +33,36 @@ switch ($language) { --format-version 1 ` --no-deps | ConvertFrom-Json - $manifest = [System.IO.Path]::GetFullPath($manifest) + $manifest = [Path]::GetFullPath($manifest) $package = $metadata.packages | - Where-Object { - [System.IO.Path]::GetFullPath($_.manifest_path) -eq $manifest - } | - Select-Object -First 1 + Where-Object { + [Path]::GetFullPath($_.manifest_path) -eq $manifest + } | + Select-Object -First 1 $target = $package.targets | - Where-Object { $_.kind -contains "bin" } | - Select-Object -First 1 + Where-Object { $_.kind -contains "bin" } | + Select-Object -First 1 $source = Join-Path ` $metadata.target_directory ` - "debug\$($target.name).exe" + "$Configuration\$($target.name).exe" } "cpp" { $main = Join-Path $Example.FullName "main.cpp" $source = Join-Path ` - $example.Parent.Parent.FullName ` - "$($example.Name).exe" + $Example.Parent.Parent.FullName ` + "$($Example.Name).exe" - g++ $main -o $source + if ($Configuration -eq "release") { + g++ $main -o $source -O2 + } + else { + g++ $main -o $source -O0 -g + } if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } } @@ -59,17 +72,19 @@ switch ($language) { } } -$source = [System.IO.Path]::GetFullPath($source) +$source = [Path]::GetFullPath($source) if (-not (Test-Path -LiteralPath $source -PathType Leaf)) { throw "Source executable not found: $source" } -& (Join-Path $PWD "target/debug/obfuscator.exe") --virtualization -v $source +$obfuscator = Join-Path $PWD "target\$Configuration\obfuscator.exe" + +& $obfuscator --virtualization -v $source if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } -$destination = [System.IO.Path]::ChangeExtension( +$destination = [Path]::ChangeExtension( $source, ".protected.exe" ) diff --git a/examples/cpp/bench/main.cpp b/examples/cpp/bench/main.cpp index 366c58d8..6b5d9f8a 100644 --- a/examples/cpp/bench/main.cpp +++ b/examples/cpp/bench/main.cpp @@ -33,26 +33,26 @@ BINSAFE void encipher(uint32_t data[2], uint32_t const key[4], uint32_t delta) int main() { - uint32_t key[4] = {0xA3B1C2D3, 0xE4F50617, 0x28394A5B, 0x6C7D8E9F}; - uint32_t const delta = 0x9E3779B9; + constexpr uint32_t key[4] = {0xA3B1C2D3, 0xE4F50617, 0x28394A5B, 0x6C7D8E9F}; + constexpr uint32_t delta = 0x9E3779B9; uint32_t data_native[2] = {0x12345678, 0x9ABCDEF0}; - auto start_native = std::chrono::high_resolution_clock::now(); + std::chrono::high_resolution_clock::time_point start_native = std::chrono::high_resolution_clock::now(); ENCIPHER(data_native, key, delta); - auto end_native = std::chrono::high_resolution_clock::now(); + std::chrono::high_resolution_clock::time_point end_native = std::chrono::high_resolution_clock::now(); std::chrono::duration elapsed_native = end_native - start_native; uint32_t data_virtualized[2] = {0x12345678, 0x9ABCDEF0}; - auto start_virtualized = std::chrono::high_resolution_clock::now(); + std::chrono::high_resolution_clock::time_point start_virtualized = std::chrono::high_resolution_clock::now(); encipher(data_virtualized, key, delta); - auto end_virtualized = std::chrono::high_resolution_clock::now(); + std::chrono::high_resolution_clock::time_point end_virtualized = std::chrono::high_resolution_clock::now(); std::chrono::duration elapsed_virtualized = end_virtualized - start_virtualized; diff --git a/examples/cpp/crack-me/main.cpp b/examples/cpp/crack-me/main.cpp new file mode 100644 index 00000000..1c2c7fdc --- /dev/null +++ b/examples/cpp/crack-me/main.cpp @@ -0,0 +1,36 @@ +#include +#include +#include "../../../api/binsafe.hpp" + +BINSAFE bool validate(char const *serial) +{ + constexpr uint32_t initializer = 0x1505; + constexpr uint32_t expected = 0x323BFC7A; + + uint32_t hash = initializer; + + for (char const *p = serial; *p; ++p) + { + hash = ((hash << 5) + hash) ^ static_cast(*p); + } + + return hash == expected; +} + +BINSAFE int main(int argc, char *argv[]) +{ + if (argc < 2) + { + std::cout << "Usage: crack-me " << std::endl; + return 1; + } + + if (validate(argv[1])) + { + std::cout << "Access granted." << std::endl; + return 0; + } + + std::cout << "Access denied." << std::endl; + return 1; +} diff --git a/examples/cpp/exception-cxx/main.cpp b/examples/cpp/exception-cxx/main.cpp index afa869fc..f6b0d7e0 100644 --- a/examples/cpp/exception-cxx/main.cpp +++ b/examples/cpp/exception-cxx/main.cpp @@ -3,29 +3,34 @@ #include #include "../../../api/binsafe.hpp" -BINSAFE uint32_t guarded(int value) +BINSAFE uint32_t guarded(uint32_t value) { - try { + try + { throw value; } - catch (int caught) { - return static_cast(caught); + catch (uint32_t caught) + { + return caught; } return 0; } -int main() { - constexpr int value = 0x12345678; +int main() +{ + constexpr uint32_t value = 0x12345678; uint32_t a = 0; uint32_t b = 0; - try { + try + { throw value; } - catch (int value) { - a = static_cast(value); + catch (uint32_t caught) + { + a = caught; } b = guarded(value); diff --git a/examples/cpp/multithread/main.cpp b/examples/cpp/multithread/main.cpp index 47a1f74c..a213b627 100644 --- a/examples/cpp/multithread/main.cpp +++ b/examples/cpp/multithread/main.cpp @@ -2,7 +2,6 @@ #include #include #include -#include #include "../../../api/binsafe.hpp" BINSAFE uint32_t echo(uint32_t input) diff --git a/runtime/src/runtime.rs b/runtime/src/runtime.rs index 5d393bb4..57d383a6 100644 --- a/runtime/src/runtime.rs +++ b/runtime/src/runtime.rs @@ -243,6 +243,14 @@ enum EmissionTask { DispatchStub(usize, usize), } +pub type Handler = Box; + +struct DispatchCase { + label: CodeLabel, + epilogue: CodeLabel, + handler: Handler, +} + struct Dispatch { table: CodeLabel, stubs: Vec<(u8, CodeLabel, CodeLabel)>, @@ -301,6 +309,8 @@ pub struct Runtime { dispatches: Vec, + cases: Vec, + functions: HashMap, addresses: HashMap, @@ -373,6 +383,8 @@ impl Runtime { dispatches: Vec::new(), + cases: Vec::new(), + functions: HashMap::new(), addresses: HashMap::new(), @@ -480,6 +492,31 @@ impl Runtime { .unwrap(); } + pub fn switch( + &mut self, + key: AsmRegister64, + epilogue: CodeLabel, + handlers: Vec<(Vec, Handler)>, + ) { + let mut cases = Vec::new(); + + for (indices, handler) in handlers { + let label = self.asm.create_label(); + + for index in indices { + cases.push((index, label)); + } + + self.cases.push(DispatchCase { + label, + epilogue, + handler, + }); + } + + self.dispatch(key, cases); + } + pub fn jumps(&mut self, key: AsmRegister64, cases: Vec<(u8, CodeLabel)>) { self.dispatch(key, cases); } @@ -846,18 +883,43 @@ impl Runtime { let mut phase_two = Vec::new(); - for i in 0..self.dispatches.len() { - phase_two.push(EmissionTask::DispatchTable(i)); + phase_two.extend(data_phase_two.iter().cloned()); + + // A case handler may register further dispatches (nested width/precision + // dispatch), so drain in shuffled waves until no new work remains. + let mut dispatched = 0; - for j in 0..self.dispatches[i].stubs.len() { - phase_two.push(EmissionTask::DispatchStub(i, j)); + loop { + for i in dispatched..self.dispatches.len() { + phase_two.push(EmissionTask::DispatchTable(i)); + + for j in 0..self.dispatches[i].stubs.len() { + phase_two.push(EmissionTask::DispatchStub(i, j)); + } } - } + dispatched = self.dispatches.len(); - phase_two.extend(data_phase_two.iter().cloned()); - phase_two.shuffle(&mut rng); + let mut cases = std::mem::take(&mut self.cases); - self.emit(&phase_two); + if phase_two.is_empty() && cases.is_empty() { + break; + } + + phase_two.shuffle(&mut rng); + self.emit(&phase_two); + phase_two = Vec::new(); + + cases.shuffle(&mut rng); + + for case in cases { + let mut label = case.label; + + self.asm.set_label(&mut label).unwrap(); + (case.handler)(self); + // jmp ... + self.asm.jmp(case.epilogue).unwrap(); + } + } self.emit(&[EmissionTask::Data(DataDef::VehEnd)]); diff --git a/runtime/src/vm/encoders/debug.rs b/runtime/src/vm/encoders/debug.rs deleted file mode 100644 index f25feb71..00000000 --- a/runtime/src/vm/encoders/debug.rs +++ /dev/null @@ -1,148 +0,0 @@ -#![allow(unused)] - -use crate::vm::encoders::Encode; -use std::fmt; - -impl fmt::Display for dyn Encode { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let s = strip_fields(&format!("{self:?}")) - .replace(" { ", "(") - .replace(" }", ")"); - let s = hex_bytes(&s); - let s = hex_decimals(&s); - write!(f, "{s}") - } -} - -fn strip_fields(input: &str) -> String { - let mut output = String::with_capacity(input.len()); - - let mut characters = input.chars().peekable(); - - while let Some(c) = characters.next() { - if c == ':' && characters.peek() == Some(&' ') { - characters.next(); - - while output.ends_with(|c: char| c.is_alphanumeric() || c == '_') { - output.pop(); - } - } else { - output.push(c); - } - } - output -} - -fn hex_bytes(input: &str) -> String { - let mut output = String::with_capacity(input.len()); - - let mut characters = input.char_indices().peekable(); - - while let Some((i, c)) = characters.next() { - if c == '[' { - if let Some(end) = input[i + 1..].find(']') { - let bytes = input[i + 1..i + 1 + end] - .split(',') - .map(|b| b.trim().parse::().ok()) - .collect::>>(); - if let Some(bytes) = bytes { - let hex = match bytes.len() { - 1 => Some(format!( - "0x{:02X}", - u8::from_le_bytes(bytes.try_into().unwrap()) - )), - 2 => Some(format!( - "0x{:04X}", - u16::from_le_bytes(bytes.try_into().unwrap()) - )), - 4 => Some(format!( - "0x{:08X}", - u32::from_le_bytes(bytes.try_into().unwrap()) - )), - 8 => Some(format!( - "0x{:016X}", - u64::from_le_bytes(bytes.try_into().unwrap()) - )), - _ => None, - }; - if let Some(hex) = hex { - output.push_str(&hex); - characters.nth(end); - continue; - } - } - } - } - output.push(c); - } - output -} - -fn hex_decimals(input: &str) -> String { - let mut output = String::with_capacity(input.len()); - - let characters = input.chars().collect::>(); - - let mut i = 0; - - while i < characters.len() { - let c = characters[i]; - - if c == '0' && matches!(characters.get(i + 1), Some('x') | Some('X')) { - output.extend(characters[i..i + 2].iter()); - i += 2; - while matches!(characters.get(i), Some(c) if c.is_ascii_hexdigit()) { - output.push(characters[i]); - i += 1; - } - continue; - } - - let previous = - i > 0 && (characters[i - 1].is_ascii_alphanumeric() || characters[i - 1] == '_'); - - let (start, negative) = match c { - '-' if !previous && matches!(characters.get(i + 1), Some(c) if c.is_ascii_digit()) => { - (i + 1, true) - } - c if c.is_ascii_digit() && !previous => (i, false), - _ => { - output.push(c); - i += 1; - continue; - } - }; - - let mut end = start; - - while matches!(characters.get(end), Some(c) if c.is_ascii_digit()) { - end += 1; - } - - let digits = characters[start..end].iter().collect::(); - - match digits.parse::() { - Ok(value) => { - let value = if negative { -value } else { value }; - - if value >= i8::MIN as i64 && value <= i8::MAX as i64 { - output.push_str(&format!("0x{:02X}", value as u8)); - } else if value >= i16::MIN as i64 && value <= i16::MAX as i64 { - output.push_str(&format!("0x{:04X}", value as u16)); - } else if value >= i32::MIN as i64 && value <= i32::MAX as i64 { - output.push_str(&format!("0x{:08X}", value as u32)); - } else { - output.push_str(&format!("0x{:016X}", value as u64)); - } - } - Err(_) => { - if negative { - output.push('-'); - } - output.push_str(&digits); - } - } - i = end; - } - output -} diff --git a/runtime/src/vm/encoders/mod.rs b/runtime/src/vm/encoders/mod.rs index 7fda0135..1acb6ab4 100644 --- a/runtime/src/vm/encoders/mod.rs +++ b/runtime/src/vm/encoders/mod.rs @@ -1,11 +1,60 @@ use std::any::{type_name, Any}; -use std::fmt::Debug; +use std::fmt::{self, Debug}; use crate::mapper::Mapper; use crate::vm::bytecode::{VMOp, VMReg, VMVec}; -#[cfg(debug_assertions)] -mod debug; +pub mod adc; +pub mod add; +pub mod and; +pub mod bit_scan_reverse; +pub mod bit_test; +pub mod bit_test_complement; +pub mod bit_test_reset; +pub mod bit_test_set; +pub mod block; +pub mod byte_swap; +pub mod compare_exchange; +pub mod discard; +pub mod dispatch; +pub mod div; +pub mod exchange; +pub mod exchange_add; +pub mod jcc; +pub mod label; +pub mod load_address; +pub mod load_immediate; +pub mod load_memory; +pub mod load_register; +pub mod load_vector; +pub mod mul; +pub mod or; +pub mod packed_byte_equal; +pub mod packed_byte_mask; +pub mod pop; +pub mod push; +pub mod ret; +pub mod rol; +pub mod ror; +pub mod sar; +pub mod sbb; +pub mod shl; +pub mod shr; +pub mod store_extend; +pub mod store_memory; +pub mod store_merge; +pub mod store_register; +pub mod sub; +pub mod timestamp; +pub mod trailing_zeros; +pub mod vector_add; +pub mod vector_and; +pub mod vector_and_not; +pub mod vector_div; +pub mod vector_mul; +pub mod vector_or; +pub mod vector_sub; +pub mod vector_xor; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Effect { @@ -84,55 +133,147 @@ pub trait Encode: Debug + Any { fn seal(&mut self, _mapper: &mut Mapper, _transform: &mut dyn FnMut(&mut [u8], usize)) {} } -pub mod adc; -pub mod add; -pub mod and; -pub mod bit_scan_reverse; -pub mod bit_test; -pub mod bit_test_complement; -pub mod bit_test_reset; -pub mod bit_test_set; -pub mod block; -pub mod byte_swap; -pub mod compare_exchange; -pub mod discard; -pub mod dispatch; -pub mod div; -pub mod exchange; -pub mod exchange_add; -pub mod jcc; -pub mod label; -pub mod load_address; -pub mod load_immediate; -pub mod load_memory; -pub mod load_register; -pub mod load_vector; -pub mod mul; -pub mod or; -pub mod packed_byte_equal; -pub mod packed_byte_mask; -pub mod pop; -pub mod push; -pub mod ret; -pub mod rol; -pub mod ror; -pub mod sar; -pub mod sbb; -pub mod shl; -pub mod shr; -pub mod store_extend; -pub mod store_memory; -pub mod store_merge; -pub mod store_register; -pub mod sub; -pub mod timestamp; -pub mod trailing_zeros; -pub mod vector_add; -pub mod vector_and; -pub mod vector_and_not; -pub mod vector_div; -pub mod vector_mul; -pub mod vector_or; -pub mod vector_sub; -pub mod vector_xor; +impl fmt::Display for dyn Encode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let s = strip_fields(&format!("{self:?}")) + .replace(" { ", "(") + .replace(" }", ")"); + let s = hex_bytes(&s); + let s = hex_decimals(&s); + write!(f, "{s}") + } +} + +fn strip_fields(input: &str) -> String { + let mut output = String::with_capacity(input.len()); + + let mut characters = input.chars().peekable(); + + while let Some(c) = characters.next() { + if c == ':' && characters.peek() == Some(&' ') { + characters.next(); + + while output.ends_with(|c: char| c.is_alphanumeric() || c == '_') { + output.pop(); + } + } else { + output.push(c); + } + } + output +} + +fn hex_bytes(input: &str) -> String { + let mut output = String::with_capacity(input.len()); + + let mut characters = input.char_indices().peekable(); + + while let Some((i, c)) = characters.next() { + if c == '[' { + if let Some(end) = input[i + 1..].find(']') { + let bytes = input[i + 1..i + 1 + end] + .split(',') + .map(|b| b.trim().parse::().ok()) + .collect::>>(); + if let Some(bytes) = bytes { + let hex = match bytes.len() { + 1 => Some(format!( + "0x{:02X}", + u8::from_le_bytes(bytes.try_into().unwrap()) + )), + 2 => Some(format!( + "0x{:04X}", + u16::from_le_bytes(bytes.try_into().unwrap()) + )), + 4 => Some(format!( + "0x{:08X}", + u32::from_le_bytes(bytes.try_into().unwrap()) + )), + 8 => Some(format!( + "0x{:016X}", + u64::from_le_bytes(bytes.try_into().unwrap()) + )), + _ => None, + }; + if let Some(hex) = hex { + output.push_str(&hex); + characters.nth(end); + continue; + } + } + } + } + output.push(c); + } + output +} + +fn hex_decimals(input: &str) -> String { + let mut output = String::with_capacity(input.len()); + + let characters = input.chars().collect::>(); + + let mut i = 0; + + while i < characters.len() { + let c = characters[i]; + + if c == '0' && matches!(characters.get(i + 1), Some('x') | Some('X')) { + output.extend(characters[i..i + 2].iter()); + i += 2; + while matches!(characters.get(i), Some(c) if c.is_ascii_hexdigit()) { + output.push(characters[i]); + i += 1; + } + continue; + } + + let previous = + i > 0 && (characters[i - 1].is_ascii_alphanumeric() || characters[i - 1] == '_'); + + let (start, negative) = match c { + '-' if !previous && matches!(characters.get(i + 1), Some(c) if c.is_ascii_digit()) => { + (i + 1, true) + } + c if c.is_ascii_digit() && !previous => (i, false), + _ => { + output.push(c); + i += 1; + continue; + } + }; + + let mut end = start; + + while matches!(characters.get(end), Some(c) if c.is_ascii_digit()) { + end += 1; + } + + let digits = characters[start..end].iter().collect::(); + + match digits.parse::() { + Ok(value) => { + let value = if negative { -value } else { value }; + + if value >= i8::MIN as i64 && value <= i8::MAX as i64 { + output.push_str(&format!("0x{:02X}", value as u8)); + } else if value >= i16::MIN as i64 && value <= i16::MAX as i64 { + output.push_str(&format!("0x{:04X}", value as u16)); + } else if value >= i32::MIN as i64 && value <= i32::MAX as i64 { + output.push_str(&format!("0x{:08X}", value as u32)); + } else { + output.push_str(&format!("0x{:016X}", value as u64)); + } + } + Err(_) => { + if negative { + output.push('-'); + } + output.push_str(&digits); + } + } + i = end; + } + output +} pub mod xor; diff --git a/runtime/src/vm/utils/precision.rs b/runtime/src/vm/utils/precision.rs index c1f103c7..d411b08a 100644 --- a/runtime/src/vm/utils/precision.rs +++ b/runtime/src/vm/utils/precision.rs @@ -1,8 +1,9 @@ use iced_x86::code_asm::{AsmRegister64, CodeLabel}; -use crate::{runtime::Runtime, vm::bytecode::VMPrecision}; - -type Handler = Box; +use crate::{ + runtime::{Handler, Runtime}, + vm::bytecode::VMPrecision, +}; pub fn dispatch( rt: &mut Runtime, @@ -11,15 +12,12 @@ pub fn dispatch( mut int: Option, mut float: Option, ) { - let mut cases = Vec::new(); - let mut emits = Vec::new(); + let mut handlers = Vec::new(); macro_rules! case { ($opt:expr, $tag:expr) => { if let Some(f) = $opt.take() { - let label = rt.asm.create_label(); - cases.push((rt.mapper.index($tag) as u8, label)); - emits.push((label, f)); + handlers.push((vec![rt.mapper.index($tag) as u8], f)); } }; } @@ -27,12 +25,5 @@ pub fn dispatch( case!(int, VMPrecision::Integer); case!(float, VMPrecision::Float); - rt.jumps(precision, cases); - - for (mut label, f) in emits { - rt.asm.set_label(&mut label).unwrap(); - f(rt); - // jmp ... - rt.asm.jmp(*epilogue).unwrap(); - } + rt.switch(precision, *epilogue, handlers); } diff --git a/runtime/src/vm/utils/width.rs b/runtime/src/vm/utils/width.rs index 4116ca03..887b9a59 100644 --- a/runtime/src/vm/utils/width.rs +++ b/runtime/src/vm/utils/width.rs @@ -1,8 +1,9 @@ use iced_x86::code_asm::{AsmRegister64, CodeLabel}; -use crate::{runtime::Runtime, vm::bytecode::VMWidth}; - -type Handler = Box; +use crate::{ + runtime::{Handler, Runtime}, + vm::bytecode::VMWidth, +}; pub fn dispatch( rt: &mut Runtime, @@ -24,15 +25,12 @@ pub fn dispatch( let both8 = lower8.is_some() && higher8.is_some(); let merged8 = lower8.is_some() != higher8.is_some(); - let mut cases = Vec::new(); - let mut emits = Vec::new(); + let mut handlers = Vec::new(); macro_rules! case { ($opt:expr, $tag:expr) => { if let Some(f) = $opt.take() { - let label = rt.asm.create_label(); - cases.push((rt.mapper.index($tag) as u8, label)); - emits.push((label, f)); + handlers.push((vec![rt.mapper.index($tag) as u8], f)); } }; } @@ -41,10 +39,13 @@ pub fn dispatch( case!(lower8, VMWidth::Lower8); case!(higher8, VMWidth::Higher8); } else if merged8 { - let label = rt.asm.create_label(); - cases.push((rt.mapper.index(VMWidth::Lower8) as u8, label)); - cases.push((rt.mapper.index(VMWidth::Higher8) as u8, label)); - emits.push((label, lower8.take().or_else(|| higher8.take()).unwrap())); + handlers.push(( + vec![ + rt.mapper.index(VMWidth::Lower8) as u8, + rt.mapper.index(VMWidth::Higher8) as u8, + ], + lower8.take().or_else(|| higher8.take()).unwrap(), + )); } case!(lower16, VMWidth::Lower16); @@ -58,12 +59,5 @@ pub fn dispatch( case!(lower128, VMWidth::Lower128); case!(lower256, VMWidth::Lower256); - rt.jumps(width, cases); - - for (mut label, f) in emits { - rt.asm.set_label(&mut label).unwrap(); - f(rt); - // jmp ... - rt.asm.jmp(*epilogue).unwrap(); - } + rt.switch(width, *epilogue, handlers); } From dd67eb1c69c17362c3d72718e02237ea5a4b7ff2 Mon Sep 17 00:00:00 2001 From: unknown <86915746+RadonCoding@users.noreply.github.com> Date: Sun, 4 Oct 2026 18:17:29 +0300 Subject: [PATCH 28/33] chore: MBA --- example.ps1 | 20 ++- examples/cpp/bench/main.cpp | 2 +- .../virtualization/attestation/mod.rs | 2 + .../src/protections/virtualization/crypt.rs | 10 +- .../src/protections/virtualization/mod.rs | 13 +- runtime/src/vm/functions/crypt.rs | 13 ++ runtime/src/vm/functions/lookup.rs | 12 ++ runtime/src/vm/handlers/semantic/compiler.rs | 19 +++ runtime/src/vm/handlers/semantic/mod.rs | 136 +++++++++++++++++- tests/src/instructions.rs | 15 +- tests/src/lib.rs | 4 +- 11 files changed, 218 insertions(+), 28 deletions(-) diff --git a/example.ps1 b/example.ps1 index 4d4003e1..f0b6972b 100644 --- a/example.ps1 +++ b/example.ps1 @@ -1,12 +1,14 @@ using namespace System.IO param( - [Parameter(Mandatory)] + [Parameter(Mandatory, Position = 0)] [DirectoryInfo]$Example, + [Parameter(Position = 1)] [ValidateSet("debug", "release")] [string]$Configuration = "debug", + [Parameter(Position = 2)] [string[]]$Arguments ) @@ -14,7 +16,12 @@ $ErrorActionPreference = "Stop" $language = Split-Path (Split-Path $Example -Parent) -Leaf -cargo build --bin obfuscator --$Configuration $Arguments +if ($Configuration -eq "release") { + cargo build --bin obfuscator --release $Arguments +} +else { + cargo build --bin obfuscator $Arguments +} if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } @@ -22,9 +29,12 @@ switch ($language) { "rust" { $manifest = Join-Path $Example.FullName "Cargo.toml" - cargo build ` - --manifest-path $manifest ` - --$Configuration + if ($Configuration -eq "release") { + cargo build --manifest-path $manifest --release + } + else { + cargo build --manifest-path $manifest + } if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } diff --git a/examples/cpp/bench/main.cpp b/examples/cpp/bench/main.cpp index 6b5d9f8a..cb387215 100644 --- a/examples/cpp/bench/main.cpp +++ b/examples/cpp/bench/main.cpp @@ -10,7 +10,7 @@ uint32_t v0 = (data)[0]; \ uint32_t v1 = (data)[1]; \ \ - for (int i = 0; i < 20000; ++i) \ + for (int i = 0; i < 10000; ++i) \ { \ uint32_t sum = 0; \ \ diff --git a/obfuscator/src/protections/virtualization/attestation/mod.rs b/obfuscator/src/protections/virtualization/attestation/mod.rs index caf36f86..edafcfdc 100644 --- a/obfuscator/src/protections/virtualization/attestation/mod.rs +++ b/obfuscator/src/protections/virtualization/attestation/mod.rs @@ -247,6 +247,8 @@ fn correct( instructions.extend(xor(None, None)); + instructions.extend(register_lcg(engine, None)); + instructions.extend(store_register(VMReg::Vg0)); instructions diff --git a/obfuscator/src/protections/virtualization/crypt.rs b/obfuscator/src/protections/virtualization/crypt.rs index 3fa9754c..d395dcd4 100644 --- a/obfuscator/src/protections/virtualization/crypt.rs +++ b/obfuscator/src/protections/virtualization/crypt.rs @@ -30,7 +30,9 @@ impl Cipher { } fn encrypt_payload(&self, block: &mut [u8], key: u64, secret: u64) { - let mut key = key ^ secret; + let mut key = (key ^ secret) + .wrapping_mul(self.multiplier) + .wrapping_add(self.addend); for chunk in block.chunks_exact_mut(8) { let mut qword = u64::from_le_bytes(chunk.try_into().unwrap()); @@ -41,10 +43,14 @@ impl Cipher { } } - pub fn decrypt_payload(&self, block: &mut [u8], mut key: u64, secret: u64) { + pub fn decrypt_payload(&self, block: &mut [u8], key: u64, secret: u64) { let length = u16::from_le_bytes(block[..HEADER_SIZE].try_into().unwrap()) as usize; let payload = &mut block[HEADER_SIZE..HEADER_SIZE + ((length + 8 + 7) & !7)]; + let mut key = (key ^ secret) + .wrapping_mul(self.multiplier) + .wrapping_add(self.addend); + for chunk in payload.chunks_exact_mut(8) { let qword = u64::from_le_bytes(chunk.try_into().unwrap()); let original = qword ^ key; diff --git a/obfuscator/src/protections/virtualization/mod.rs b/obfuscator/src/protections/virtualization/mod.rs index f3b2351f..b43cdd6f 100644 --- a/obfuscator/src/protections/virtualization/mod.rs +++ b/obfuscator/src/protections/virtualization/mod.rs @@ -326,6 +326,9 @@ impl Protection for Virtualization { .lookup(engine.rt.data_labels[&DataDef::VmTrampolines]) as u32; + let multiplier = engine.rt.keys.multiplier; + let addend = engine.rt.keys.addend; + for i in 0..engine.blocks.len() { let rva = engine.blocks[i].rva; let size = engine.blocks[i].size; @@ -336,10 +339,14 @@ impl Protection for Virtualization { let index = self.virtualized[&rva]; + let token = ((index as u64).wrapping_mul(multiplier).wrapping_add(addend) & 0x0FFFFFFF) + as i32 + | 0x10000000; + let mut asm = CodeAssembler::new(engine.bitness).unwrap(); if size >= VM_DISPATCH_SIZE { - asm.push(index as i32 | 0x10000000).unwrap(); + asm.push(token).unwrap(); asm.call(entry_rva).unwrap(); let first = asm.assemble(rva as u64).unwrap(); @@ -350,7 +357,7 @@ impl Protection for Virtualization { // Stub has to be assembled twice so that the runtime return address can be calculated let return_address = rva as i32 + first.len() as i32; - asm.push((index as i32 | 0x10000000) ^ return_address) + asm.push(token ^ return_address) .unwrap(); asm.call(entry_rva).unwrap(); let second = asm.assemble(rva as u64).unwrap(); @@ -370,7 +377,7 @@ impl Protection for Virtualization { let return_address = (redirect_rva + VM_REDIRECT_SIZE as u32) as i32; - asm.mov(dword_ptr(rsp), (index as i32 | 0x10000000) ^ return_address) + asm.mov(dword_ptr(rsp), token ^ return_address) .unwrap(); asm.call(entry_rva).unwrap(); let dispatch = asm.assemble(redirect_rva as u64).unwrap(); diff --git a/runtime/src/vm/functions/crypt.rs b/runtime/src/vm/functions/crypt.rs index 8e862b98..b1716462 100644 --- a/runtime/src/vm/functions/crypt.rs +++ b/runtime/src/vm/functions/crypt.rs @@ -246,6 +246,19 @@ pub fn build(rt: &mut Runtime) { rt.asm .mov(rcx, ptr(rt.data_labels[&DataDef::VmKeyInitializer])) .unwrap(); + + // mov rax, [...] + rt.asm + .mov(rax, ptr(rt.data_labels[&DataDef::VmKeyMultiplier])) + .unwrap(); + // imul rcx, rax + rt.asm.imul_2(rcx, rax).unwrap(); + // mov rax, [...] + rt.asm + .mov(rax, ptr(rt.data_labels[&DataDef::VmKeyAddend])) + .unwrap(); + // add rcx, rax + rt.asm.add(rcx, rax).unwrap(); } rt.asm.set_label(&mut save_key).unwrap(); diff --git a/runtime/src/vm/functions/lookup.rs b/runtime/src/vm/functions/lookup.rs index ddbc2d10..b570c810 100644 --- a/runtime/src/vm/functions/lookup.rs +++ b/runtime/src/vm/functions/lookup.rs @@ -21,6 +21,18 @@ pub fn build(rt: &mut Runtime) { rt.asm.xor(edx, r8d).unwrap(); // and edx, 0x0FFFFFFF rt.asm.and(edx, 0x0FFFFFFF).unwrap(); + // sub edx, ... + rt.asm.sub(edx, rt.keys.addend as u32 as i32).unwrap(); + // imul edx, edx, ... + rt.asm + .imul_3( + edx, + edx, + crate::utils::invert_multiplier(rt.keys.multiplier) as u32 as i32, + ) + .unwrap(); + // and edx, 0x0FFFFFFF + rt.asm.and(edx, 0x0FFFFFFF).unwrap(); // lea r8, [...] rt.asm diff --git a/runtime/src/vm/handlers/semantic/compiler.rs b/runtime/src/vm/handlers/semantic/compiler.rs index 808bf4f2..b5266802 100644 --- a/runtime/src/vm/handlers/semantic/compiler.rs +++ b/runtime/src/vm/handlers/semantic/compiler.rs @@ -17,7 +17,26 @@ use crate::{ }, }; +const BUDGET: u32 = 2; + pub fn compile(rt: &mut Runtime, operation: &Operation) { + let mut rng = rand::thread_rng(); + + let mut budget = BUDGET; + + let operation = Operation { + effects: operation + .effects + .iter() + .cloned() + .map(|effect| effect.obfuscate(&mut rng, &mut budget)) + .collect(), + flags: operation.flags.clone(), + stores: operation.stores.clone(), + widths: operation.widths, + }; + let operation = &operation; + let mut epilogue = rt.asm.create_label(); let operands = operation.operands(); let outputs = operation.outputs(); diff --git a/runtime/src/vm/handlers/semantic/mod.rs b/runtime/src/vm/handlers/semantic/mod.rs index 4bcb1b99..efdc5f26 100644 --- a/runtime/src/vm/handlers/semantic/mod.rs +++ b/runtime/src/vm/handlers/semantic/mod.rs @@ -1,3 +1,5 @@ +use rand::Rng; + use crate::vm::bytecode::{Flag, VMWidth}; mod allocator; @@ -87,7 +89,139 @@ enum Reference { Immediate(i64), } -// TODO: Implement a mixed-boolean-arithmetic engine! +impl Expression { + fn obfuscate(self, rng: &mut impl Rng, budget: &mut u32) -> Expression { + let expression = match self { + Expression::BitAnd(a, b) => Expression::BitAnd( + Box::new(a.obfuscate(rng, budget)), + Box::new(b.obfuscate(rng, budget)), + ), + Expression::BitOr(a, b) => Expression::BitOr( + Box::new(a.obfuscate(rng, budget)), + Box::new(b.obfuscate(rng, budget)), + ), + Expression::BitXor(a, b) => Expression::BitXor( + Box::new(a.obfuscate(rng, budget)), + Box::new(b.obfuscate(rng, budget)), + ), + Expression::BitShr(a, b) => Expression::BitShr( + Box::new(a.obfuscate(rng, budget)), + Box::new(b.obfuscate(rng, budget)), + ), + Expression::BitShl(a, b) => Expression::BitShl( + Box::new(a.obfuscate(rng, budget)), + Box::new(b.obfuscate(rng, budget)), + ), + Expression::Sub(a, b) => Expression::Sub( + Box::new(a.obfuscate(rng, budget)), + Box::new(b.obfuscate(rng, budget)), + ), + Expression::BitNot(a) => Expression::BitNot(Box::new(a.obfuscate(rng, budget))), + Expression::LowByte(a) => Expression::LowByte(Box::new(a.obfuscate(rng, budget))), + Expression::Parity(a) => Expression::Parity(Box::new(a.obfuscate(rng, budget))), + Expression::Compare(compare) => { + Expression::Compare(Box::new(compare.obfuscate(rng, budget))) + } + leaf => leaf, + }; + + if *budget == 0 || !rng.gen_bool(0.5) { + return expression; + } + + match expression { + // a ^ b = (a | b) - (a & b) + Expression::BitXor(a, b) => { + *budget -= 1; + Expression::Sub( + Box::new(Expression::BitOr(a.clone(), b.clone())), + Box::new(Expression::BitAnd(a, b)), + ) + } + // a | b = ~(~a & ~b) + Expression::BitOr(a, b) => { + *budget -= 1; + Expression::BitNot(Box::new(Expression::BitAnd( + Box::new(Expression::BitNot(a)), + Box::new(Expression::BitNot(b)), + ))) + } + // a & b = ~(~a | ~b) + Expression::BitAnd(a, b) => { + *budget -= 1; + Expression::BitNot(Box::new(Expression::BitOr( + Box::new(Expression::BitNot(a)), + Box::new(Expression::BitNot(b)), + ))) + } + // ~a = -a - 1 + Expression::BitNot(a) => { + *budget -= 1; + Expression::Sub( + Box::new(Expression::Sub(Box::new(Expression::Constant(0)), a)), + Box::new(Expression::Constant(1)), + ) + } + other => other, + } + } +} + +impl Compare { + fn obfuscate(self, rng: &mut impl Rng, budget: &mut u32) -> Compare { + match self { + Compare::Equal(a, b) => { + Compare::Equal(a.obfuscate(rng, budget), b.obfuscate(rng, budget)) + } + Compare::LessThan(a, b) => { + Compare::LessThan(a.obfuscate(rng, budget), b.obfuscate(rng, budget)) + } + Compare::GreaterThan(a, b) => { + Compare::GreaterThan(a.obfuscate(rng, budget), b.obfuscate(rng, budget)) + } + Compare::BitSet(a, b) => { + Compare::BitSet(a.obfuscate(rng, budget), b.obfuscate(rng, budget)) + } + } + } +} + +impl Effect { + fn obfuscate(self, rng: &mut impl Rng, budget: &mut u32) -> Effect { + match self { + Effect::Add(a, b) => Effect::Add(a.obfuscate(rng, budget), b.obfuscate(rng, budget)), + Effect::Sub(a, b) => Effect::Sub(a.obfuscate(rng, budget), b.obfuscate(rng, budget)), + Effect::And(a, b) => Effect::And(a.obfuscate(rng, budget), b.obfuscate(rng, budget)), + Effect::Or(a, b) => Effect::Or(a.obfuscate(rng, budget), b.obfuscate(rng, budget)), + Effect::Xor(a, b) => Effect::Xor(a.obfuscate(rng, budget), b.obfuscate(rng, budget)), + Effect::Shr(a, b) => Effect::Shr(a.obfuscate(rng, budget), b.obfuscate(rng, budget)), + Effect::Shl(a, b) => Effect::Shl(a.obfuscate(rng, budget), b.obfuscate(rng, budget)), + Effect::Ror(a, b) => Effect::Ror(a.obfuscate(rng, budget), b.obfuscate(rng, budget)), + Effect::Rol(a, b) => Effect::Rol(a.obfuscate(rng, budget), b.obfuscate(rng, budget)), + Effect::Mul(a, b) => Effect::Mul(a.obfuscate(rng, budget), b.obfuscate(rng, budget)), + Effect::Div(a, b, c) => Effect::Div( + a.obfuscate(rng, budget), + b.obfuscate(rng, budget), + c.obfuscate(rng, budget), + ), + Effect::Assign(a) => Effect::Assign(a.obfuscate(rng, budget)), + Effect::Sar(a, b) => Effect::Sar(a.obfuscate(rng, budget), b.obfuscate(rng, budget)), + Effect::Bsr(a) => Effect::Bsr(a.obfuscate(rng, budget)), + Effect::Tzcnt(a) => Effect::Tzcnt(a.obfuscate(rng, budget)), + Effect::Exchange(a, b) => { + Effect::Exchange(a.obfuscate(rng, budget), b.obfuscate(rng, budget)) + } + Effect::ExchangeAdd(a, b) => { + Effect::ExchangeAdd(a.obfuscate(rng, budget), b.obfuscate(rng, budget)) + } + Effect::CompareExchange(a, b, c) => Effect::CompareExchange( + a.obfuscate(rng, budget), + b.obfuscate(rng, budget), + c.obfuscate(rng, budget), + ), + } + } +} impl Operation { fn operands(&self) -> usize { diff --git a/tests/src/instructions.rs b/tests/src/instructions.rs index df62e23f..66b5f400 100644 --- a/tests/src/instructions.rs +++ b/tests/src/instructions.rs @@ -7,20 +7,7 @@ use iced_x86::{ use runtime::vm::bytecode::{self, Flag, VMReg, VMVec}; use crate::constants::{available, baseline, register, vector, IMMEDIATES}; -use crate::{decrypt_block, decrypt_payload, encrypt_block, Difference, Executor, State}; - -#[test] -fn test_crypt() { - let mut buffer = vec![0xDE, 0xAD, 0xC0, 0xDE]; - - let before = buffer.clone(); - - encrypt_block(&mut buffer); - - decrypt_block(&mut buffer); - - assert_eq!(before, buffer); -} +use crate::{decrypt_payload, encrypt_block, Difference, Executor, State}; macro_rules! define { ($($code:ident)+ $(,)?) => { diff --git a/tests/src/lib.rs b/tests/src/lib.rs index ded9a6c8..7aee8242 100644 --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -21,7 +21,7 @@ use iced_x86::{ code_asm::{ptr, r12, r12d, r8, r9, rax, ymm0}, BlockEncoder, BlockEncoderOptions, Instruction, InstructionBlock, }; -use obfuscator::protections::virtualization::crypt; +use obfuscator::protections::virtualization::crypt::{self, Cipher}; use runtime::{ mapper::Mappable, runtime::{BoolDef, DataDef, FnDef, Runtime}, @@ -486,7 +486,7 @@ impl Drop for Executor { } fn cipher() -> crypt::Cipher { - crypt::Cipher::new(Executor::TEST_KEY_MULTIPLIER, Executor::TEST_KEY_ADDEND) + Cipher::new(Executor::TEST_KEY_MULTIPLIER, Executor::TEST_KEY_ADDEND) } pub fn encrypt_block(block: &mut Vec) { From 7a978090dde8db427b68ee00b5cce48b4901e079 Mon Sep 17 00:00:00 2001 From: unknown <86915746+RadonCoding@users.noreply.github.com> Date: Sun, 4 Oct 2026 18:53:30 +0300 Subject: [PATCH 29/33] chore: MBA cleanup --- runtime/src/vm/handlers/semantic/allocator.rs | 3 +- runtime/src/vm/handlers/semantic/compiler.rs | 6 ++- runtime/src/vm/handlers/semantic/mod.rs | 37 ++++++++++++++++++- 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/runtime/src/vm/handlers/semantic/allocator.rs b/runtime/src/vm/handlers/semantic/allocator.rs index cedf8d56..de308768 100644 --- a/runtime/src/vm/handlers/semantic/allocator.rs +++ b/runtime/src/vm/handlers/semantic/allocator.rs @@ -149,7 +149,8 @@ impl Allocator { .iter() .copied() .find(|register| !avoid.contains(register)) - .expect("allocator has no usable register"); + .unwrap(); + self.spill(rt, register); register } diff --git a/runtime/src/vm/handlers/semantic/compiler.rs b/runtime/src/vm/handlers/semantic/compiler.rs index b5266802..d5664df0 100644 --- a/runtime/src/vm/handlers/semantic/compiler.rs +++ b/runtime/src/vm/handlers/semantic/compiler.rs @@ -907,7 +907,9 @@ fn compile_expression( Expression::ByteMask(n) => { Reference::Immediate(((0xFF as u64) << ((width.size() - 1 - n) * 8)) as i64) } - Expression::Sub(first, second) + Expression::Add(first, second) + | Expression::Sub(first, second) + | Expression::Mul(first, second) | Expression::BitAnd(first, second) | Expression::BitOr(first, second) | Expression::BitXor(first, second) => { @@ -931,7 +933,9 @@ fn compile_expression( mask(rt, src, width); match expression { + Expression::Add(_, _) => rt.asm.add(dst, src).unwrap(), Expression::Sub(_, _) => rt.asm.sub(dst, src).unwrap(), + Expression::Mul(_, _) => rt.asm.imul_2(dst, src).unwrap(), Expression::BitAnd(_, _) => rt.asm.and(dst, src).unwrap(), Expression::BitOr(_, _) => rt.asm.or(dst, src).unwrap(), Expression::BitXor(_, _) => rt.asm.xor(dst, src).unwrap(), diff --git a/runtime/src/vm/handlers/semantic/mod.rs b/runtime/src/vm/handlers/semantic/mod.rs index efdc5f26..8857ee0f 100644 --- a/runtime/src/vm/handlers/semantic/mod.rs +++ b/runtime/src/vm/handlers/semantic/mod.rs @@ -21,7 +21,9 @@ pub enum Expression { BitShr(Box, Box), BitShl(Box, Box), BitNot(Box), + Add(Box, Box), Sub(Box, Box), + Mul(Box, Box), LowByte(Box), Compare(Box), Parity(Box), @@ -112,10 +114,18 @@ impl Expression { Box::new(a.obfuscate(rng, budget)), Box::new(b.obfuscate(rng, budget)), ), + Expression::Add(a, b) => Expression::Add( + Box::new(a.obfuscate(rng, budget)), + Box::new(b.obfuscate(rng, budget)), + ), Expression::Sub(a, b) => Expression::Sub( Box::new(a.obfuscate(rng, budget)), Box::new(b.obfuscate(rng, budget)), ), + Expression::Mul(a, b) => Expression::Mul( + Box::new(a.obfuscate(rng, budget)), + Box::new(b.obfuscate(rng, budget)), + ), Expression::BitNot(a) => Expression::BitNot(Box::new(a.obfuscate(rng, budget))), Expression::LowByte(a) => Expression::LowByte(Box::new(a.obfuscate(rng, budget))), Expression::Parity(a) => Expression::Parity(Box::new(a.obfuscate(rng, budget))), @@ -162,6 +172,25 @@ impl Expression { Box::new(Expression::Constant(1)), ) } + // a + b = (a ^ b) + ((a & b) << 1) + Expression::Add(a, b) => { + *budget -= 1; + Expression::Add( + Box::new(Expression::BitXor(a.clone(), b.clone())), + Box::new(Expression::BitShl( + Box::new(Expression::BitAnd(a, b)), + Box::new(Expression::Constant(1)), + )), + ) + } + // a - b = a + ~b + 1 + Expression::Sub(a, b) => { + *budget -= 1; + Expression::Add( + Box::new(Expression::Add(a, Box::new(Expression::BitNot(b)))), + Box::new(Expression::Constant(1)), + ) + } other => other, } } @@ -326,7 +355,9 @@ impl Expression { | Expression::BitXor(first, second) | Expression::BitShr(first, second) | Expression::BitShl(first, second) - | Expression::Sub(first, second) => { + | Expression::Add(first, second) + | Expression::Sub(first, second) + | Expression::Mul(first, second) => { first.operands(value); second.operands(value); } @@ -350,7 +381,9 @@ impl Expression { | Expression::BitXor(first, second) | Expression::BitShr(first, second) | Expression::BitShl(first, second) - | Expression::Sub(first, second) => 1 + first.temporary() + second.temporary(), + | Expression::Add(first, second) + | Expression::Sub(first, second) + | Expression::Mul(first, second) => 1 + first.temporary() + second.temporary(), Expression::BitNot(inner) | Expression::LowByte(inner) | Expression::Parity(inner) => { 1 + inner.temporary() } From 1b0380c04328adacda74a5a8e5d4725eb1ef357e Mon Sep 17 00:00:00 2001 From: unknown <86915746+RadonCoding@users.noreply.github.com> Date: Sun, 4 Oct 2026 19:48:59 +0300 Subject: [PATCH 30/33] chore: hardened cipher --- .../src/protections/virtualization/crypt.rs | 40 ++++--- runtime/src/lib.rs | 2 + runtime/src/vm/functions/crypt.rs | 110 ++++++++++++------ runtime/src/vm/handlers/semantic/mod.rs | 2 +- 4 files changed, 102 insertions(+), 52 deletions(-) diff --git a/obfuscator/src/protections/virtualization/crypt.rs b/obfuscator/src/protections/virtualization/crypt.rs index d395dcd4..5abd4634 100644 --- a/obfuscator/src/protections/virtualization/crypt.rs +++ b/obfuscator/src/protections/virtualization/crypt.rs @@ -1,5 +1,5 @@ use rand::Rng; -use runtime::VM_INTEGRITY_QWORD; +use runtime::{VM_CIPHER_ROUNDS, VM_INTEGRITY_QWORD}; pub const HEADER_SIZE: usize = size_of::(); pub const TRAILER_SIZE: usize = size_of::() + size_of::(); @@ -7,6 +7,22 @@ pub const TRAILER_SIZE: usize = size_of::() + size_of::(); const ENCRYPTED: u8 = 0; const DECRYPTED: u8 = 1; +fn keystream(key: u64, counter: u64) -> u64 { + let mut a = key as u32; + let mut b = (key >> 32) as u32; + let mut x = (counter >> 32) as u32; + let mut y = counter as u32; + + for round in 0..VM_CIPHER_ROUNDS { + x = x.rotate_right(8).wrapping_add(y) ^ a; + y = y.rotate_left(3) ^ x; + b = a.wrapping_add(b.rotate_right(8)) ^ round; + a = a.rotate_left(3) ^ b; + } + + ((x as u64) << 32) | (y as u64) +} + pub struct Cipher { multiplier: u64, addend: u64, @@ -30,16 +46,14 @@ impl Cipher { } fn encrypt_payload(&self, block: &mut [u8], key: u64, secret: u64) { - let mut key = (key ^ secret) + let key = (key ^ secret) .wrapping_mul(self.multiplier) .wrapping_add(self.addend); - for chunk in block.chunks_exact_mut(8) { - let mut qword = u64::from_le_bytes(chunk.try_into().unwrap()); - qword ^= key; - chunk.copy_from_slice(&qword.to_le_bytes()); - key ^= qword; - key = key.wrapping_mul(self.multiplier).wrapping_add(self.addend); + for (counter, chunk) in block.chunks_exact_mut(8).enumerate() { + let qword = u64::from_le_bytes(chunk.try_into().unwrap()); + let cipher = qword ^ keystream(key, counter as u64); + chunk.copy_from_slice(&cipher.to_le_bytes()); } } @@ -47,16 +61,14 @@ impl Cipher { let length = u16::from_le_bytes(block[..HEADER_SIZE].try_into().unwrap()) as usize; let payload = &mut block[HEADER_SIZE..HEADER_SIZE + ((length + 8 + 7) & !7)]; - let mut key = (key ^ secret) + let key = (key ^ secret) .wrapping_mul(self.multiplier) .wrapping_add(self.addend); - for chunk in payload.chunks_exact_mut(8) { - let qword = u64::from_le_bytes(chunk.try_into().unwrap()); - let original = qword ^ key; + for (counter, chunk) in payload.chunks_exact_mut(8).enumerate() { + let cipher = u64::from_le_bytes(chunk.try_into().unwrap()); + let original = cipher ^ keystream(key, counter as u64); chunk.copy_from_slice(&original.to_le_bytes()); - key ^= qword ^ secret; - key = key.wrapping_mul(self.multiplier).wrapping_add(self.addend); } assert_eq!( diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index b135791f..cdda557d 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -30,3 +30,5 @@ pub const VM_REDIRECT_SIZE: usize = 12; pub const VM_TRAMPOLINE_SIZE: usize = 5; pub const VM_INTEGRITY_QWORD: u64 = 0xFA11ED175001FA11; + +pub const VM_CIPHER_ROUNDS: u32 = 27; diff --git a/runtime/src/vm/functions/crypt.rs b/runtime/src/vm/functions/crypt.rs index b1716462..6bebc541 100644 --- a/runtime/src/vm/functions/crypt.rs +++ b/runtime/src/vm/functions/crypt.rs @@ -1,5 +1,6 @@ use iced_x86::code_asm::{ - al, byte_ptr, cl, eax, ecx, ptr, qword_ptr, r12, r13, r14, r15, rax, rbx, rcx, rdx, word_ptr, + al, byte_ptr, cl, eax, ecx, edx, ptr, qword_ptr, r10, r10d, r11, r11d, r12, r13, r14, r15, r8, + r8d, r9d, rax, rbx, rcx, rdx, word_ptr, }; use crate::{ @@ -8,6 +9,7 @@ use crate::{ bytecode::VMReg, utils::{self}, }, + VM_CIPHER_ROUNDS, }; // void (bool) @@ -26,8 +28,9 @@ pub fn build(rt: &mut Runtime) { let mut derive_key = rt.asm.create_label(); let mut start_key = rt.asm.create_label(); let mut save_key = rt.asm.create_label(); + let mut stream = rt.asm.create_label(); let mut crypt_loop = rt.asm.create_label(); - let mut continue_loop = rt.asm.create_label(); + let mut round_loop = rt.asm.create_label(); let mut unlock = rt.asm.create_label(); let mut finish_encrypt = rt.asm.create_label(); let mut finish_decrypt = rt.asm.create_label(); @@ -119,7 +122,7 @@ pub fn build(rt: &mut Runtime) { rt.asm.mov(rcx, ptr(0x1480 + rcx * 8).gs()).unwrap(); // jmp ... - rt.asm.jmp(crypt_loop).unwrap(); + rt.asm.jmp(stream).unwrap(); } // DECRYPTION @@ -272,6 +275,12 @@ pub fn build(rt: &mut Runtime) { } } + rt.asm.set_label(&mut stream).unwrap(); + { + // xor r8, r8 + rt.asm.xor(r8, r8).unwrap(); + } + rt.asm.set_label(&mut crypt_loop).unwrap(); { // cmp rbx, r15 @@ -279,42 +288,69 @@ pub fn build(rt: &mut Runtime) { // je ... rt.asm.je(unlock).unwrap(); - // mov rax, [rbx] - rt.asm.mov(rax, ptr(rbx)).unwrap(); - // xor [rbx], rcx - rt.asm.xor(ptr(rbx), rcx).unwrap(); - - // test r13, r13 - rt.asm.test(r13, r13).unwrap(); - // jnz ... - rt.asm.jnz(continue_loop).unwrap(); - - // mov rax, [rbx] - rt.asm.mov(rax, ptr(rbx)).unwrap(); - - rt.asm.set_label(&mut continue_loop).unwrap(); + // SPECK-64/128 keystream of the counter, keyed by the block key: + // mov r9d, ecx + rt.asm.mov(r9d, ecx).unwrap(); + // mov r10, rcx + rt.asm.mov(r10, rcx).unwrap(); + // shr r10, 0x20 + rt.asm.shr(r10, 0x20).unwrap(); + // mov r11, r8 + rt.asm.mov(r11, r8).unwrap(); + // shr r11, 0x20 + rt.asm.shr(r11, 0x20).unwrap(); + // mov edx, r8d + rt.asm.mov(edx, r8d).unwrap(); + // xor eax, eax + rt.asm.xor(eax, eax).unwrap(); + + rt.asm.set_label(&mut round_loop).unwrap(); { - // xor rcx, rax - rt.asm.xor(rcx, rax).unwrap(); - - // mov rax, [...] - rt.asm - .mov(rax, ptr(rt.data_labels[&DataDef::VmKeyMultiplier])) - .unwrap(); - // imul rcx, rax - rt.asm.imul_2(rcx, rax).unwrap(); - // mov rax, [...] - rt.asm - .mov(rax, ptr(rt.data_labels[&DataDef::VmKeyAddend])) - .unwrap(); - // add rcx, rax - rt.asm.add(rcx, rax).unwrap(); - - // add rbx, 0x8 - rt.asm.add(rbx, 0x8).unwrap(); - // jmp ... - rt.asm.jmp(crypt_loop).unwrap(); + // ror r11d, 0x8 + rt.asm.ror(r11d, 8u32).unwrap(); + // add r11d, edx + rt.asm.add(r11d, edx).unwrap(); + // xor r11d, r9d + rt.asm.xor(r11d, r9d).unwrap(); + // rol edx, 0x3 + rt.asm.rol(edx, 3u32).unwrap(); + // xor edx, r11d + rt.asm.xor(edx, r11d).unwrap(); + + // ror r10d, 0x8 + rt.asm.ror(r10d, 8u32).unwrap(); + // add r10d, r9d + rt.asm.add(r10d, r9d).unwrap(); + // xor r10d, eax + rt.asm.xor(r10d, eax).unwrap(); + // rol r9d, 0x3 + rt.asm.rol(r9d, 3u32).unwrap(); + // xor r9d, r10d + rt.asm.xor(r9d, r10d).unwrap(); + + // inc eax + rt.asm.inc(eax).unwrap(); + // cmp eax, ... + rt.asm.cmp(eax, VM_CIPHER_ROUNDS as i32).unwrap(); + // jb ... + rt.asm.jb(round_loop).unwrap(); } + + // shl r11, 0x20 + rt.asm.shl(r11, 0x20).unwrap(); + // mov eax, edx + rt.asm.mov(eax, edx).unwrap(); + // or r11, rax + rt.asm.or(r11, rax).unwrap(); + // xor [rbx], r11 + rt.asm.xor(ptr(rbx), r11).unwrap(); + + // add rbx, 0x8 + rt.asm.add(rbx, 0x8).unwrap(); + // inc r8 + rt.asm.inc(r8).unwrap(); + // jmp ... + rt.asm.jmp(crypt_loop).unwrap(); } rt.asm.set_label(&mut unlock).unwrap(); diff --git a/runtime/src/vm/handlers/semantic/mod.rs b/runtime/src/vm/handlers/semantic/mod.rs index 8857ee0f..78a2ac87 100644 --- a/runtime/src/vm/handlers/semantic/mod.rs +++ b/runtime/src/vm/handlers/semantic/mod.rs @@ -135,7 +135,7 @@ impl Expression { leaf => leaf, }; - if *budget == 0 || !rng.gen_bool(0.5) { + if *budget == 0 || rng.gen() { return expression; } From 7f9b94aa2270157a5fc2206ebebde349b6790f80 Mon Sep 17 00:00:00 2001 From: unknown <86915746+RadonCoding@users.noreply.github.com> Date: Mon, 5 Oct 2026 11:40:06 +0300 Subject: [PATCH 31/33] chore: hardened cipher --- .../virtualization/attestation/mod.rs | 6 +- .../src/protections/virtualization/crypt.rs | 20 +++++-- .../src/protections/virtualization/mod.rs | 56 ++++++++++++------- runtime/src/vm/functions/crypt.rs | 24 ++++---- 4 files changed, 64 insertions(+), 42 deletions(-) diff --git a/obfuscator/src/protections/virtualization/attestation/mod.rs b/obfuscator/src/protections/virtualization/attestation/mod.rs index edafcfdc..8349291d 100644 --- a/obfuscator/src/protections/virtualization/attestation/mod.rs +++ b/obfuscator/src/protections/virtualization/attestation/mod.rs @@ -167,8 +167,9 @@ fn correct( instructions.extend(load_absolute(engine, DataDef::VmCodeEnd, VMReg::Rax)); instructions.extend(load_absolute(engine, DataDef::VmAttestation, VMReg::Rcx)); + instructions.extend(load_absolute(engine, DataDef::VmCodeStart, VMReg::R8)); - instructions.extend(sub(Some(VMReg::Vg0), Some(VMReg::Rcx))); + instructions.extend(sub(Some(VMReg::Vg0), Some(VMReg::R8))); instructions.extend(store_register(VMReg::Rdx)); instructions.extend(skip( @@ -190,7 +191,7 @@ fn correct( VMReg::R8, Bound::Register(VMReg::R9), 8, - |_| { + |engine| { let mut outer = Vec::new(); outer.extend(load_register(VMReg::R10)); @@ -203,6 +204,7 @@ fn correct( VMWidth::Lower64, )); outer.extend(xor(None, None)); + outer.extend(register_lcg(engine, None)); outer.extend(store_register(VMReg::R10)); outer diff --git a/obfuscator/src/protections/virtualization/crypt.rs b/obfuscator/src/protections/virtualization/crypt.rs index 5abd4634..a5afda06 100644 --- a/obfuscator/src/protections/virtualization/crypt.rs +++ b/obfuscator/src/protections/virtualization/crypt.rs @@ -35,8 +35,8 @@ impl Cipher { pub fn encrypt_block(&self, block: &mut Vec, key: u64, secret: u64) { let length = prepare_block(block); - self.encrypt_payload(block, key, secret); finalize_encrypt(block, length); + self.encrypt_payload(block, key, secret); } pub fn decrypt_block(&self, block: &mut Vec, key: u64, secret: u64) { @@ -45,16 +45,22 @@ impl Cipher { unprepare_block(block, length); } - fn encrypt_payload(&self, block: &mut [u8], key: u64, secret: u64) { + pub fn encrypt_payload(&self, block: &mut [u8], key: u64, secret: u64) { + let length = u16::from_le_bytes(block[..HEADER_SIZE].try_into().unwrap()) as usize; + let payload = &mut block[HEADER_SIZE..HEADER_SIZE + ((length + 8 + 7) & !7)]; + let key = (key ^ secret) .wrapping_mul(self.multiplier) .wrapping_add(self.addend); - for (counter, chunk) in block.chunks_exact_mut(8).enumerate() { + for (counter, chunk) in payload.chunks_exact_mut(8).enumerate() { let qword = u64::from_le_bytes(chunk.try_into().unwrap()); let cipher = qword ^ keystream(key, counter as u64); chunk.copy_from_slice(&cipher.to_le_bytes()); } + + // byte - state + block[block.len() - TRAILER_SIZE] = ENCRYPTED; } pub fn decrypt_payload(&self, block: &mut [u8], key: u64, secret: u64) { @@ -84,12 +90,14 @@ impl Cipher { } } -pub fn derive_hash(bytes: &[u8]) -> u64 { +pub fn derive_hash(bytes: &[u8], multiplier: u64, addend: u64) -> u64 { let start = HEADER_SIZE; let end = bytes.len() - TRAILER_SIZE; let payload = &bytes[start..end]; - payload.chunks_exact(8).fold(0, |key, chunk| { - key ^ u64::from_le_bytes(chunk.try_into().unwrap()) + payload.chunks_exact(8).fold(0, |hash, chunk| { + (hash ^ u64::from_le_bytes(chunk.try_into().unwrap())) + .wrapping_mul(multiplier) + .wrapping_add(addend) }) } diff --git a/obfuscator/src/protections/virtualization/mod.rs b/obfuscator/src/protections/virtualization/mod.rs index b43cdd6f..d8b72b06 100644 --- a/obfuscator/src/protections/virtualization/mod.rs +++ b/obfuscator/src/protections/virtualization/mod.rs @@ -4,6 +4,7 @@ use std::hash::{DefaultHasher, Hash, Hasher}; use std::{i32, slice}; use crate::engine::Engine; +use crate::protections::virtualization::crypt::Cipher; use crate::protections::Protection; use exe::{Buffer, SectionCharacteristics}; use exe::{PE, RVA}; @@ -55,7 +56,10 @@ fn resolve(engine: &Engine, label: DataDef) -> (u32, usize) { fn write_displacement(engine: &mut Engine, label: DataDef, target: u32) { let (rva, offset) = resolve(engine, label); let displacement = (target as i64 - rva as i64) as i32; - engine.pe.write(offset, &displacement.to_le_bytes()).unwrap(); + engine + .pe + .write(offset, &displacement.to_le_bytes()) + .unwrap(); } fn write_entry(engine: &mut Engine, index: usize, entry: TableEntry) { @@ -94,7 +98,7 @@ impl Virtualization { let blocks = attestation::generate(engine, engine.rt.keys.secret); - let cipher = crypt::Cipher::new(engine.rt.keys.multiplier, engine.rt.keys.addend); + let cipher = Cipher::new(engine.rt.keys.multiplier, engine.rt.keys.addend); for (index, operations) in blocks.into_iter().enumerate() { let mut rng = rand::thread_rng(); @@ -154,7 +158,7 @@ impl Protection for Virtualization { let mut code = Vec::new(); - let cipher = crypt::Cipher::new(engine.rt.keys.multiplier, engine.rt.keys.addend); + let cipher = Cipher::new(engine.rt.keys.multiplier, engine.rt.keys.addend); 'outer: for block in &mut engine.blocks { if block.size < VM_TRAMPOLINE_SIZE { @@ -211,19 +215,8 @@ impl Protection for Virtualization { let mut bytes = bytecode::assemble(&mut engine.rt.mapper, &transformed); - let key = if code.is_empty() { - engine.rt.keys.initializer - } else { - crypt::derive_key(&code) - }; - - let secret = if code.is_empty() { - 0 - } else { - engine.rt.keys.secret - }; - - cipher.encrypt_block(&mut bytes, key, secret); + cipher.encrypt_block(&mut bytes, 0, 0); + cipher.decrypt_payload(&mut bytes, 0, 0); code.extend_from_slice(&bytes); @@ -300,10 +293,33 @@ impl Protection for Virtualization { } let attestation = self.attestation(engine, code.len() as u64); + + let cipher = Cipher::new(engine.rt.keys.multiplier, engine.rt.keys.addend); + + let secret = engine.rt.keys.secret; + + let mut key = crypt::derive_hash( + &attestation, + engine.rt.keys.multiplier, + engine.rt.keys.addend, + ); + + let mut position = 0; + + for bytes in &self.programs { + let block = &mut code[position..position + bytes.len()]; + + cipher.encrypt_payload(block, key, secret); + + key = crypt::derive_key(block); + + position += bytes.len(); + } + code.extend_from_slice(&attestation); let section = engine.create_section( - Some("🏴‍☠️"), + Some("☠️"), &code, SectionCharacteristics::CNT_INITIALIZED_DATA | SectionCharacteristics::MEM_READ @@ -357,8 +373,7 @@ impl Protection for Virtualization { // Stub has to be assembled twice so that the runtime return address can be calculated let return_address = rva as i32 + first.len() as i32; - asm.push(token ^ return_address) - .unwrap(); + asm.push(token ^ return_address).unwrap(); asm.call(entry_rva).unwrap(); let second = asm.assemble(rva as u64).unwrap(); @@ -377,8 +392,7 @@ impl Protection for Virtualization { let return_address = (redirect_rva + VM_REDIRECT_SIZE as u32) as i32; - asm.mov(dword_ptr(rsp), token ^ return_address) - .unwrap(); + asm.mov(dword_ptr(rsp), token ^ return_address).unwrap(); asm.call(entry_rva).unwrap(); let dispatch = asm.assemble(redirect_rva as u64).unwrap(); diff --git a/runtime/src/vm/functions/crypt.rs b/runtime/src/vm/functions/crypt.rs index 6bebc541..4898cfae 100644 --- a/runtime/src/vm/functions/crypt.rs +++ b/runtime/src/vm/functions/crypt.rs @@ -245,23 +245,21 @@ pub fn build(rt: &mut Runtime) { rt.asm.set_label(&mut start_key).unwrap(); { - // mov rcx, [...] - rt.asm - .mov(rcx, ptr(rt.data_labels[&DataDef::VmKeyInitializer])) - .unwrap(); - - // mov rax, [...] - rt.asm - .mov(rax, ptr(rt.data_labels[&DataDef::VmKeyMultiplier])) - .unwrap(); - // imul rcx, rax - rt.asm.imul_2(rcx, rax).unwrap(); - // mov rax, [...] + // lea rcx, [...] rt.asm - .mov(rax, ptr(rt.data_labels[&DataDef::VmKeyAddend])) + .lea(rcx, ptr(rt.data_labels[&DataDef::VmAttestation])) .unwrap(); + // movsxd rax, [rcx] + rt.asm.movsxd(rax, ptr(rcx)).unwrap(); // add rcx, rax rt.asm.add(rcx, rax).unwrap(); + // mov rdx, r14 + rt.asm.mov(rdx, r14).unwrap(); + // call ... + rt.asm.call(rt.function_labels[&FnDef::VmInvoke]).unwrap(); + + // mov rcx, rax + rt.asm.mov(rcx, rax).unwrap(); } rt.asm.set_label(&mut save_key).unwrap(); From fe6563d9733c46dc57c00c5806e3a68602125ce4 Mon Sep 17 00:00:00 2001 From: unknown <86915746+RadonCoding@users.noreply.github.com> Date: Mon, 5 Oct 2026 18:08:20 +0300 Subject: [PATCH 32/33] chore: shortened attestation window --- obfuscator/src/protections/virtualization/attestation/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/obfuscator/src/protections/virtualization/attestation/mod.rs b/obfuscator/src/protections/virtualization/attestation/mod.rs index 8349291d..54f36334 100644 --- a/obfuscator/src/protections/virtualization/attestation/mod.rs +++ b/obfuscator/src/protections/virtualization/attestation/mod.rs @@ -12,8 +12,8 @@ mod anti_tamper; #[cfg(debug_assertions)] mod debug; -// Masks lower 34 bits of timestamp, creating a ~5s window on a 3.5 GHz CPU -const WINDOW: u64 = 0x22; +// Masks lower 32 bits of timestamp, creating a ~1s window on a 3.5 GHz CPU +const WINDOW: u64 = 0x20; const MINIMUM_CYCLES: u64 = 1_000_000; const MAXIMUM_CYCLES: u64 = 100_000_000; From 989551fd2335fb84448cb47b7666c29f14886180 Mon Sep 17 00:00:00 2001 From: unknown <86915746+RadonCoding@users.noreply.github.com> Date: Mon, 5 Oct 2026 18:45:59 +0300 Subject: [PATCH 33/33] cleanup: refactored some examples and immediates --- examples/cpp/crack-me/main.cpp | 112 ++++++++++++++++-- .../virtualization/attestation/anti_debug.rs | 36 ++++-- runtime/src/lib.rs | 2 +- 3 files changed, 125 insertions(+), 25 deletions(-) diff --git a/examples/cpp/crack-me/main.cpp b/examples/cpp/crack-me/main.cpp index 1c2c7fdc..cda659ca 100644 --- a/examples/cpp/crack-me/main.cpp +++ b/examples/cpp/crack-me/main.cpp @@ -1,36 +1,124 @@ -#include +#include #include +#include #include "../../../api/binsafe.hpp" +enum class Operation : uint8_t +{ + Add, + Sub, + Xor +}; + +struct Constraint +{ + uint8_t lhs; + uint8_t rhs; + Operation operation; + uint8_t value; +}; + +template +constexpr auto create(const char (&key)[N]) +{ + constexpr size_t length = N - 1; + + std::array result{}; + + size_t out = 0; + + for (size_t i = 0; i < length - 1; ++i) + { + const uint8_t a = static_cast(key[i]); + const uint8_t b = static_cast(key[i + 1]); + + result[out++] = { + static_cast(i), + static_cast(i + 1), + Operation::Add, + static_cast(a + b)}; + + result[out++] = { + static_cast(i), + static_cast(i + 1), + Operation::Xor, + static_cast(a ^ b)}; + } + + return result; +} + +template BINSAFE bool validate(char const *serial) { - constexpr uint32_t initializer = 0x1505; - constexpr uint32_t expected = 0x323BFC7A; + if constexpr (i == constraints.size()) + { + return true; + } + else + { + const Constraint &constraint = constraints[i]; + + const uint8_t lhs = + static_cast(serial[constraint.lhs]); + + const uint8_t rhs = + static_cast(serial[constraint.rhs]); - uint32_t hash = initializer; + bool valid = false; - for (char const *p = serial; *p; ++p) + switch (constraint.operation) + { + case Operation::Add: + valid = static_cast(lhs + rhs) == constraint.value; + break; + + case Operation::Sub: + valid = static_cast(lhs - rhs) == constraint.value; + break; + + case Operation::Xor: + valid = static_cast(lhs ^ rhs) == constraint.value; + break; + } + + return valid && validate(serial); + } +} + +constexpr auto constraints = + create("0xP4553D17501P455"); + +BINSAFE bool validate(char const *serial) +{ + constexpr size_t length = (constraints.size() / 2) + 1; + + for (size_t i = 0; i < length; ++i) { - hash = ((hash << 5) + hash) ^ static_cast(*p); + if (serial[i] == '\0') + return false; } - return hash == expected; + if (serial[length] != '\0') + return false; + + return validate<0, constraints>(serial); } BINSAFE int main(int argc, char *argv[]) { if (argc < 2) { - std::cout << "Usage: crack-me " << std::endl; + std::cout << "Usage: crack-me \n"; return 1; } if (validate(argv[1])) { - std::cout << "Access granted." << std::endl; + std::cout << "Access granted.\n"; return 0; } - std::cout << "Access denied." << std::endl; - return 1; -} + std::cout << "Access denied.\n"; + return 0; +} \ No newline at end of file diff --git a/obfuscator/src/protections/virtualization/attestation/anti_debug.rs b/obfuscator/src/protections/virtualization/attestation/anti_debug.rs index fecf1f74..059aa8a9 100644 --- a/obfuscator/src/protections/virtualization/attestation/anti_debug.rs +++ b/obfuscator/src/protections/virtualization/attestation/anti_debug.rs @@ -60,7 +60,8 @@ fn query_kd_debugger_enabled( )); b.extend(accumulate_immediate( rng, - VMReg::Vp0, None, + VMReg::Vp0, + None, 0, &mut *expected, )); @@ -68,7 +69,8 @@ fn query_kd_debugger_enabled( b.extend(import(engine, ImportDef::NtQuerySystemInformation)); b.extend(accumulate_prologue( rng, - VMReg::Vp0, VMReg::Rax, + VMReg::Vp0, + VMReg::Rax, &NT_QUERY_INFORMATION_PROCESS_PROLOGUE, expected, )); @@ -93,7 +95,8 @@ fn query_kd_debugger_enabled( b.extend(accumulate_immediate( rng, - VMReg::Vp0, Some(VMReg::Rax), + VMReg::Vp0, + Some(VMReg::Rax), STATUS_SUCCESS, expected, )); @@ -101,7 +104,8 @@ fn query_kd_debugger_enabled( // READ KernelDebuggerEnabled + KernelDebuggerNotPresent b.extend(accumulate_memory( rng, - VMReg::Vp0, VMReg::Rsp, + VMReg::Vp0, + VMReg::Rsp, 0x20, VMWidth::Lower16, 0x0100, @@ -121,7 +125,8 @@ fn query_process_debug_object_handle( b.extend(import(engine, ImportDef::NtQueryInformationProcess)); b.extend(accumulate_prologue( rng, - VMReg::Vp0, VMReg::Rax, + VMReg::Vp0, + VMReg::Rax, &NT_QUERY_INFORMATION_PROCESS_PROLOGUE, expected, )); @@ -148,7 +153,8 @@ fn query_process_debug_object_handle( b.extend(accumulate_immediate( rng, - VMReg::Vp0, Some(VMReg::Rax), + VMReg::Vp0, + Some(VMReg::Rax), STATUS_PORT_NOT_SET, expected, )); @@ -156,7 +162,8 @@ fn query_process_debug_object_handle( // READ ProcessDebugObjectHandle b.extend(accumulate_memory( rng, - VMReg::Vp0, VMReg::Rsp, + VMReg::Vp0, + VMReg::Rsp, 0x28, VMWidth::Lower64, 0, @@ -176,7 +183,8 @@ fn set_hide_from_debugger( b.extend(import(engine, ImportDef::NtSetInformationThread)); b.extend(accumulate_prologue( rng, - VMReg::Vp0, VMReg::Rax, + VMReg::Vp0, + VMReg::Rax, &NT_SET_INFORMATION_THREAD_PROLOGUE, expected, )); @@ -193,7 +201,8 @@ fn set_hide_from_debugger( b.extend(accumulate_immediate( rng, - VMReg::Vp0, Some(VMReg::Rax), + VMReg::Vp0, + Some(VMReg::Rax), STATUS_SUCCESS, expected, )); @@ -211,7 +220,8 @@ fn query_hide_from_debbuger( b.extend(import(engine, ImportDef::NtQueryInformationThread)); b.extend(accumulate_prologue( rng, - VMReg::Vp0, VMReg::Rax, + VMReg::Vp0, + VMReg::Rax, &NT_QUERY_INFORMATION_THREAD_PROLOGUE, expected, )); @@ -239,7 +249,8 @@ fn query_hide_from_debbuger( b.extend(accumulate_immediate( rng, - VMReg::Vp0, Some(VMReg::Rax), + VMReg::Vp0, + Some(VMReg::Rax), STATUS_SUCCESS, expected, )); @@ -247,7 +258,8 @@ fn query_hide_from_debbuger( // READ ThreadHideFromDebugger b.extend(accumulate_byte( rng, - VMReg::Vp0, VMReg::Rsp, + VMReg::Vp0, + VMReg::Rsp, 0x28, 1, expected, diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index cdda557d..1f066667 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -29,6 +29,6 @@ pub const VM_REDIRECT_SIZE: usize = 12; // CALL rel32 pub const VM_TRAMPOLINE_SIZE: usize = 5; -pub const VM_INTEGRITY_QWORD: u64 = 0xFA11ED175001FA11; +pub const VM_INTEGRITY_QWORD: u64 = 0xF4113D17501F411; pub const VM_CIPHER_ROUNDS: u32 = 27;

;Z6tMBLcAmey><1dZ{V1J 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, pub stores: Option>, pub widths: &'static [VMWidth], - pub operands: u8, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +enum Value { + InputA, + InputB, + OutputA, + Flags, + Temporary(u32), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum ValueRef { + Value(Value), + Immediate(i64), } #[derive(Clone)] struct Allocator { - available: Vec, - protected: Vec, + tracked: Vec<(AsmRegister64, Value)>, + dirty: HashSet, + temporary: u32, + slots: u32, } impl Allocator { - fn new() -> Self { + const REGISTERS: [AsmRegister64; 5] = [rax, rcx, rdx, r8, r9]; + + fn new(slots: u32) -> Self { Self { - available: vec![rax, rcx, rdx, r8, r9], - protected: vec![], + tracked: Vec::new(), + dirty: HashSet::new(), + temporary: 0, + slots, + } + } + + fn track(&mut self, register: AsmRegister64, value: Value, dirty: bool) { + assert!( + self.tracked.iter().all(|(r, _)| *r != register), + "register {:?} is already tracked", + register + ); + assert!( + self.tracked.iter().all(|(_, v)| *v != value), + "value {:?} is already tracked", + value + ); + self.tracked.push((register, value)); + + if dirty { + self.dirty.insert(value); + } else { + self.dirty.remove(&value); } } - fn alloc(&mut self) -> AsmRegister64 { - let mut rng = rand::thread_rng(); - let index = rng.gen_range(0..self.available.len()); - self.available.swap_remove(index) + fn untrack(&mut self, register: AsmRegister64) -> Option { + let index = self.tracked.iter().position(|(r, _)| *r == register)?; + Some(self.tracked.swap_remove(index).1) } - fn free(&mut self, register: AsmRegister64) { - if !self.protected.contains(®ister) && !self.available.contains(®ister) { - self.available.push(register); + fn tracked_register(&self, value: Value) -> Option { + self.tracked + .iter() + .find(|(_, v)| *v == value) + .map(|(r, _)| *r) + } + + fn tracked_value(&self, register: AsmRegister64) -> Option { + self.tracked + .iter() + .find(|(r, _)| *r == register) + .map(|(_, v)| *v) + } + + fn mark_dirty(&mut self, value: Value) { + self.dirty.insert(value); + } + + fn temp(&mut self) -> Value { + let value = Value::Temporary(self.temporary); + self.temporary += 1; + assert!( + self.temporary <= self.slots, + "allocator ran out of temporary slots" + ); + value + } + + fn offset(value: Value) -> i32 { + let slot = match value { + Value::InputA => 0, + Value::InputB => 1, + Value::OutputA => 2, + Value::Flags => 3, + Value::Temporary(index) => 4 + index, + }; + (slot as i32 + 1) * 8 + } + + fn spill_register(&mut self, rt: &mut Runtime, register: AsmRegister64) { + let Some(value) = self.tracked_value(register) else { + return; + }; + + if self.dirty.remove(&value) { + rt.asm + .mov(qword_ptr(rbp - Self::offset(value)), register) + .unwrap(); } + + self.untrack(register); } - fn protect(&mut self, register: AsmRegister64) { - self.protected.push(register); + fn spill_registers(&mut self, rt: &mut Runtime) { + let registers = self + .tracked + .iter() + .map(|(register, _)| *register) + .collect::>(); + + for register in registers { + self.spill_register(rt, register); + } + } + + fn acquire( + &mut self, + rt: &mut Runtime, + pinned: &[Value], + avoid: &[AsmRegister64], + ) -> AsmRegister64 { + if let Some(register) = Self::REGISTERS + .iter() + .copied() + .find(|register| !avoid.contains(register) && self.tracked_value(*register).is_none()) + { + return register; + } + + if let Some(register) = Self::REGISTERS.iter().copied().find(|register| { + if avoid.contains(register) { + return false; + } + + match self.tracked_value(*register) { + Some(value) => !pinned.contains(&value), + None => true, + } + }) { + self.spill_register(rt, register); + return register; + } + + let register = Self::REGISTERS + .iter() + .copied() + .find(|register| !avoid.contains(register)) + .expect("allocator has no usable register"); + self.spill_register(rt, register); + register + } + + fn copy_to_register(&mut self, rt: &mut Runtime, value: ValueRef, register: AsmRegister64) { + self.spill_register(rt, register); + + match value { + ValueRef::Immediate(value) => { + rt.asm.mov(register, value).unwrap(); + } + ValueRef::Value(value) => { + if let Some(source) = self.tracked_register(value) { + rt.asm.mov(register, source).unwrap(); + } else { + rt.asm + .mov(register, qword_ptr(rbp - Self::offset(value))) + .unwrap(); + } + } + } + } + + fn load_mutable( + &mut self, + rt: &mut Runtime, + value: Value, + avoid: &[AsmRegister64], + ) -> AsmRegister64 { + if let Some(register) = self.tracked_register(value) { + if !avoid.contains(®ister) { + return register; + } + } + + let register = self.acquire(rt, &[], avoid); + rt.asm + .mov(register, qword_ptr(rbp - Self::offset(value))) + .unwrap(); + self.track(register, value, false); + register + } + + fn replace(&mut self, register: AsmRegister64, value: Value, dirty: bool) { + self.untrack(register); + + if let Some(old) = self + .tracked + .iter() + .find(|(_, v)| *v == value) + .map(|(r, _)| *r) + { + self.untrack(old); + } + + self.track(register, value, dirty); } - fn protected(&self, register: AsmRegister64) -> bool { - self.protected.contains(®ister) + fn consume(&mut self, value: ValueRef) { + let ValueRef::Value(value) = value else { + return; + }; + if matches!(value, Value::Temporary(_)) { + if let Some(register) = self.tracked_register(value) { + self.untrack(register); + } + self.dirty.remove(&value); + } } } -#[derive(Clone)] +#[derive(Clone, Copy)] struct Context { - input_a: AsmRegister64, - input_b: AsmRegister64, - output_a: AsmRegister64, + input_a: Value, + input_b: Value, + output_a: Value, } // TODO: Implement a mixed-boolean-arithmetic engine! -pub fn build(rt: &mut Runtime, operation: &Operation) { - let mut epilogue = rt.asm.create_label(); +fn operands(effects: &[Effect], flags: &[(Flag, Condition)]) -> u8 { + let mut value = 0; - rt.asm.push(r13).unwrap(); - rt.asm.push(r14).unwrap(); + for effect in effects { + effect.operands(&mut value); + } - rt.asm.mov(r13, rcx).unwrap(); + for (_, condition) in flags { + condition.operands(&mut value); + } + + value +} + +impl Expression { + fn operands(&self, value: &mut u8) { + match self { + Expression::Operand(Operand::Input(n)) => { + *value = (*value).max(n + 1); + } + Expression::Operand(Operand::Output(_)) + | Expression::Constant(_) + | Expression::SignBit => {} + Expression::BitAnd(first, second) + | Expression::BitOr(first, second) + | Expression::BitXor(first, second) + | Expression::BitShr(first, second) + | Expression::BitShl(first, second) + | Expression::Sub(first, second) => { + first.operands(value); + second.operands(value); + } + Expression::BitNot(inner) | Expression::LowByte(inner) => { + inner.operands(value); + } + } + } + + fn temporary(&self) -> u32 { + match self { + Expression::Operand(_) | Expression::Constant(_) | Expression::SignBit => 0, + Expression::BitAnd(first, second) + | Expression::BitOr(first, second) + | Expression::BitXor(first, second) + | Expression::BitShr(first, second) + | Expression::BitShl(first, second) + | Expression::Sub(first, second) => 1 + first.temporary() + second.temporary(), + Expression::BitNot(inner) | Expression::LowByte(inner) => 1 + inner.temporary(), + } + } +} + +impl Effect { + fn operands(&self, value: &mut u8) { + match self { + Effect::Add(first, second) + | Effect::Sub(first, second) + | Effect::And(first, second) + | Effect::Or(first, second) + | Effect::Xor(first, second) + | Effect::Shr(first, second) + | Effect::Shl(first, second) + | Effect::Ror(first, second) + | Effect::Rol(first, second) + | Effect::Mul(first, second) + | Effect::Sar(first, second) => { + first.operands(value); + second.operands(value); + } + Effect::Assign(expression) | Effect::Bsr(expression) | Effect::Tzcnt(expression) => { + expression.operands(value); + } + } + } + + fn temporary(&self) -> u32 { + match self { + Effect::Add(first, second) + | Effect::Sub(first, second) + | Effect::And(first, second) + | Effect::Or(first, second) + | Effect::Xor(first, second) + | Effect::Shr(first, second) + | Effect::Shl(first, second) + | Effect::Ror(first, second) + | Effect::Rol(first, second) + | Effect::Mul(first, second) + | Effect::Sar(first, second) => first.temporary() + second.temporary(), + Effect::Assign(expression) | Effect::Bsr(expression) | Effect::Tzcnt(expression) => { + expression.temporary() + } + } + } +} + +impl Condition { + fn operands(&self, value: &mut u8) { + match self { + Condition::Compare(compare) => compare.operands(value), + Condition::Parity(expression) => expression.operands(value), + } + } - let mut allocator = Allocator::new(); + fn temporary(&self) -> u32 { + match self { + Condition::Compare(compare) => compare.temporary(), + Condition::Parity(expression) => expression.temporary(), + } + } +} + +impl Compare { + fn operands(&self, value: &mut u8) { + match self { + Compare::Equal(first, second) + | Compare::LessThan(first, second) + | Compare::GreaterThan(first, second) + | Compare::BitSet(first, second) => { + first.operands(value); + second.operands(value); + } + } + } - let index = allocator.alloc(); - let index32 = get_gpr32(register::sized(index.into(), 4).unwrap()).unwrap(); - bytecode::read_byte_zx(rt, r13, index32); + fn temporary(&self) -> u32 { + match self { + Compare::Equal(first, second) + | Compare::LessThan(first, second) + | Compare::GreaterThan(first, second) + | Compare::BitSet(first, second) => first.temporary() + second.temporary(), + } + } +} - allocator.available.retain(|&r| r != rcx); +fn temporaries(operation: &Operation) -> u32 { + let effects = operation.effects.iter().map(Effect::temporary).sum::(); + let flags = operation + .flags + .iter() + .map(|(_, condition)| condition.temporary()) + .sum::(); + let stores = operation + .stores + .as_ref() + .map(|stores| stores.iter().map(Expression::temporary).sum::()) + .unwrap_or(0); + + effects + flags + stores +} - let input_a = allocator.alloc(); - let input_b = allocator.alloc(); +pub fn build(rt: &mut Runtime, operation: &Operation) { + let mut epilogue = rt.asm.create_label(); + let operand_count = operands(&operation.effects, &operation.flags); + let temporaries = temporaries(operation); + let slots = 4 + temporaries; + let size = ((slots as i32 * 8) + 15) & !15; - let output_a = allocator.alloc(); + rt.asm.push(r13).unwrap(); + rt.asm.push(r14).unwrap(); + rt.asm.push(rbp).unwrap(); + rt.asm.mov(rbp, rsp).unwrap(); + rt.asm.sub(rsp, size).unwrap(); - allocator.available.push(rcx); + rt.asm.mov(r13, rcx).unwrap(); - allocator.protect(input_a); - allocator.protect(input_b); - allocator.protect(output_a); + bytecode::read_byte_zx(rt, r13, r14d); let context = Context { - input_a, - input_b, - output_a, + input_a: Value::InputA, + input_b: Value::InputB, + output_a: Value::OutputA, }; - if operation.operands > 1 { - scratch::load(rt, r12, input_b); + if operand_count > 1 { + scratch::load(rt, r12, rax); + + rt.asm + .mov(qword_ptr(rbp - Allocator::offset(Value::InputB)), rax) + .unwrap(); } - scratch::load(rt, r12, input_a); + + scratch::load(rt, r12, rax); + + rt.asm + .mov(qword_ptr(rbp - Allocator::offset(Value::InputA)), rax) + .unwrap(); let handlers = operation .widths @@ -164,15 +506,20 @@ pub fn build(rt: &mut Runtime, operation: &Operation) { .map(|&width| { let actions = operation.effects.clone(); let rules = operation.flags.clone(); - let context = context.clone(); ( width, Box::new(move |rt: &mut Runtime, allocator: &mut Allocator| { - effects(rt, allocator, &context, &actions, width); - let temporary = allocator.alloc(); - rt.asm.mov(temporary, width.mask() as i64).unwrap(); - rt.asm.and(context.output_a, temporary).unwrap(); - allocator.free(temporary); + compile_effects(rt, allocator, &context, &actions, width); + + let output = allocator.load_mutable(rt, context.output_a, &[]); + let mask = allocator.acquire(rt, &[context.output_a], &[output]); + + rt.asm.mov(mask, width.mask() as i64).unwrap(); + rt.asm.and(output, mask).unwrap(); + + allocator.spill_register(rt, mask); + allocator.mark_dirty(context.output_a); + compile_flags(rt, allocator, &context, &rules, width); }) as Box, ) @@ -189,19 +536,22 @@ pub fn build(rt: &mut Runtime, operation: &Operation) { .collect::>(); rt.jumps( - index, + r14, cases .iter() .map(|(key, label): &(u8, CodeLabel)| (*key, *label)) .collect::>(), ); - allocator.free(index); - for ((_key, mut label), (_width, handler)) in cases.into_iter().zip(handlers.into_iter()) { rt.asm.set_label(&mut label).unwrap(); - let mut local = allocator.clone(); + + let mut local = Allocator::new(temporaries); + handler(rt, &mut local); + + local.spill_registers(rt); + rt.asm.jmp(epilogue).unwrap(); } @@ -209,315 +559,417 @@ pub fn build(rt: &mut Runtime, operation: &Operation) { match &operation.stores { Some(stores) => { - let evaluated = stores - .iter() - .map(|expression| { - let source = compile_expression( - rt, - &mut allocator, - &context, - expression, - VMWidth::Lower64, - ); - let temporary = allocator.alloc(); - rt.asm.mov(temporary, source).unwrap(); - allocator.free(source); - temporary - }) - .collect::>(); - for temporary in evaluated { - scratch::store(rt, r12, temporary); - allocator.free(temporary); + let mut allocator = Allocator::new(temporaries); + for expression in stores { + let result = + compile_expression(rt, &mut allocator, &context, expression, VMWidth::Lower64); + let register = allocator.acquire(rt, &[], &[]); + + allocator.copy_to_register(rt, result, register); + scratch::store(rt, r12, register); + + allocator.spill_register(rt, register); + allocator.consume(result); } } None => { - scratch::store(rt, r12, context.output_a); + let mut allocator = Allocator::new(temporaries); + let register = allocator.acquire(rt, &[], &[]); + + allocator.copy_to_register(rt, ValueRef::Value(context.output_a), register); + scratch::store(rt, r12, register); + + allocator.spill_register(rt, register); } } rt.asm.mov(rax, r13).unwrap(); + rt.asm.add(rsp, size).unwrap(); + rt.asm.pop(rbp).unwrap(); rt.asm.pop(r14).unwrap(); rt.asm.pop(r13).unwrap(); rt.asm.ret().unwrap(); } -fn effects( +fn compile_effects( rt: &mut Runtime, allocator: &mut Allocator, context: &Context, - list: &[Effect], + effects: &[Effect], width: VMWidth, ) { - for item in list { - effect(rt, allocator, context, item, width); + for effect in effects { + compile_effect(rt, allocator, context, effect, width); } } -fn effect( +fn compile_binary( rt: &mut Runtime, allocator: &mut Allocator, context: &Context, - expression: &Effect, + first: &Expression, + second: &Expression, width: VMWidth, -) { - macro_rules! binary { - ($rt:expr, $allocator:expr, $context:expr, $first:expr, $second:expr, $operation:ident, $width:expr) => {{ - let first = compile_expression($rt, $allocator, $context, $first, $width); - let second = compile_expression($rt, $allocator, $context, $second, $width); - if first != $context.output_a { - $rt.asm.mov($context.output_a, first).unwrap(); - } - $rt.asm.$operation($context.output_a, second).unwrap(); - $allocator.free(first); - $allocator.free(second); - }}; - } - - macro_rules! shift { - ($rt:expr, $allocator:expr, $context:expr, $first:expr, $second:expr, $operation:ident, $width:expr) => {{ - let second = compile_expression($rt, $allocator, $context, $second, $width); - let busy = - !$allocator.available.contains(&rcx) && second != rcx && !$allocator.protected(rcx); - if busy { - $rt.asm.push(rcx).unwrap(); - } - if second != rcx { - $rt.asm.mov(rcx, second).unwrap(); - $allocator.free(second); - } - let available = $allocator.available.contains(&rcx); - if available { - $allocator.available.retain(|®ister| register != rcx); - } + operation: F, +) where + F: FnOnce(&mut Runtime, AsmRegister64, AsmRegister64), +{ + let first = compile_expression(rt, allocator, context, first, width); + let second = compile_expression(rt, allocator, context, second, width); + + let pinned = [first, second] + .into_iter() + .filter_map(|value| match value { + ValueRef::Value(value) => Some(value), + ValueRef::Immediate(_) => None, + }) + .collect::>(); - let first = compile_expression($rt, $allocator, $context, $first, $width); - if first != $context.output_a { - $rt.asm.mov($context.output_a, first).unwrap(); - } - $rt.asm.$operation($context.output_a, cl).unwrap(); - $allocator.free(first); + let result = allocator.acquire(rt, &pinned, &[]); + allocator.copy_to_register(rt, first, result); - if busy { - $rt.asm.pop(rcx).unwrap(); - } else { - $allocator.free(rcx); - } - }}; - } + let other = allocator.acquire(rt, &pinned, &[result]); + allocator.copy_to_register(rt, second, other); - match expression { - Effect::Add(first, second) => binary!(rt, allocator, context, first, second, add, width), - Effect::Sub(first, second) => binary!(rt, allocator, context, first, second, sub, width), - Effect::And(first, second) => binary!(rt, allocator, context, first, second, and, width), - Effect::Or(first, second) => binary!(rt, allocator, context, first, second, or, width), - Effect::Xor(first, second) => binary!(rt, allocator, context, first, second, xor, width), - Effect::Mul(first, second) => binary!(rt, allocator, context, first, second, imul_2, width), - Effect::Shr(first, second) => shift!(rt, allocator, context, first, second, shr, width), - Effect::Shl(first, second) => shift!(rt, allocator, context, first, second, shl, width), - Effect::Ror(first, second) => shift!(rt, allocator, context, first, second, ror, width), - Effect::Rol(first, second) => shift!(rt, allocator, context, first, second, rol, width), - Effect::Sar(first, second) => shift!(rt, allocator, context, first, second, sar, width), - Effect::Assign(expr) => { - let value = compile_expression(rt, allocator, context, expr, width); - if value != context.output_a { - rt.asm.mov(context.output_a, value).unwrap(); - allocator.free(value); - } - } - Effect::Bsr(expr) => { - let mut src = compile_expression(rt, allocator, context, expr, width); - if allocator.protected(src) { - let temporary = allocator.alloc(); - rt.asm.mov(temporary, src).unwrap(); - src = temporary; - } - let mask = allocator.alloc(); - rt.asm.mov(mask, width.mask() as i64).unwrap(); - rt.asm.and(src, mask).unwrap(); - allocator.free(mask); - rt.asm.bsr(context.output_a, src).unwrap(); - allocator.free(src); - } - Effect::Tzcnt(expr) => { - let mut src = compile_expression(rt, allocator, context, expr, width); - if allocator.protected(src) { - let temporary = allocator.alloc(); - rt.asm.mov(temporary, src).unwrap(); - src = temporary; - } - let mask = allocator.alloc(); - rt.asm.mov(mask, width.mask() as i64).unwrap(); - rt.asm.and(src, mask).unwrap(); - allocator.free(mask); - rt.asm.tzcnt(context.output_a, src).unwrap(); - allocator.free(src); + operation(rt, result, other); + + allocator.replace(result, context.output_a, true); + allocator.consume(first); + allocator.consume(second); +} + +fn compile_shift( + rt: &mut Runtime, + allocator: &mut Allocator, + context: &Context, + first: &Expression, + second: &Expression, + width: VMWidth, + operation: F, +) where + F: FnOnce(&mut Runtime, AsmRegister64), +{ + let first = compile_expression(rt, allocator, context, first, width); + let second = compile_expression(rt, allocator, context, second, width); + + let pinned = [first, second] + .into_iter() + .filter_map(|value| match value { + ValueRef::Value(value) => Some(value), + ValueRef::Immediate(_) => None, + }) + .collect::>(); + + allocator.copy_to_register(rt, second, rcx); + + let result = allocator.acquire(rt, &pinned, &[rcx]); + allocator.copy_to_register(rt, first, result); + + operation(rt, result); + + allocator.replace(result, context.output_a, true); + allocator.consume(first); + allocator.consume(second); +} + +fn compile_effect( + rt: &mut Runtime, + allocator: &mut Allocator, + context: &Context, + effect: &Effect, + width: VMWidth, +) { + match effect { + Effect::Add(first, second) => compile_binary( + rt, + allocator, + context, + first, + second, + width, + |rt, left, right| { + rt.asm.add(left, right).unwrap(); + }, + ), + Effect::Sub(first, second) => compile_binary( + rt, + allocator, + context, + first, + second, + width, + |rt, left, right| { + rt.asm.sub(left, right).unwrap(); + }, + ), + Effect::And(first, second) => compile_binary( + rt, + allocator, + context, + first, + second, + width, + |rt, left, right| { + rt.asm.and(left, right).unwrap(); + }, + ), + Effect::Or(first, second) => compile_binary( + rt, + allocator, + context, + first, + second, + width, + |rt, left, right| { + rt.asm.or(left, right).unwrap(); + }, + ), + Effect::Xor(first, second) => compile_binary( + rt, + allocator, + context, + first, + second, + width, + |rt, left, right| { + rt.asm.xor(left, right).unwrap(); + }, + ), + Effect::Mul(first, second) => compile_binary( + rt, + allocator, + context, + first, + second, + width, + |rt, left, right| { + rt.asm.imul_2(left, right).unwrap(); + }, + ), + Effect::Shr(first, second) => compile_shift( + rt, + allocator, + context, + first, + second, + width, + |rt, result| { + rt.asm.shr(result, cl).unwrap(); + }, + ), + Effect::Shl(first, second) => compile_shift( + rt, + allocator, + context, + first, + second, + width, + |rt, result| { + rt.asm.shl(result, cl).unwrap(); + }, + ), + Effect::Ror(first, second) => compile_shift( + rt, + allocator, + context, + first, + second, + width, + |rt, result| { + rt.asm.ror(result, cl).unwrap(); + }, + ), + Effect::Rol(first, second) => compile_shift( + rt, + allocator, + context, + first, + second, + width, + |rt, result| { + rt.asm.rol(result, cl).unwrap(); + }, + ), + Effect::Sar(first, second) => compile_shift( + rt, + allocator, + context, + first, + second, + width, + |rt, result| { + rt.asm.sar(result, cl).unwrap(); + }, + ), + Effect::Assign(expression) => { + let value = compile_expression(rt, allocator, context, expression, width); + let register = allocator.acquire(rt, &[], &[]); + allocator.copy_to_register(rt, value, register); + + allocator.replace(register, context.output_a, true); + allocator.consume(value); } + Effect::Bsr(expression) => compile_unary( + rt, + allocator, + context, + expression, + width, + |rt, result, source| { + rt.asm.bsr(result, source).unwrap(); + }, + ), + Effect::Tzcnt(expression) => compile_unary( + rt, + allocator, + context, + expression, + width, + |rt, result, source| { + rt.asm.tzcnt(result, source).unwrap(); + }, + ), } } +fn compile_unary( + rt: &mut Runtime, + allocator: &mut Allocator, + context: &Context, + expression: &Expression, + width: VMWidth, + operation: F, +) where + F: FnOnce(&mut Runtime, AsmRegister64, AsmRegister64), +{ + let value = compile_expression(rt, allocator, context, expression, width); + let src = allocator.acquire(rt, &[], &[]); + + allocator.copy_to_register(rt, value, src); + + let mask = allocator.acquire(rt, &[], &[src]); + + rt.asm.mov(mask, width.mask() as i64).unwrap(); + rt.asm.and(src, mask).unwrap(); + + allocator.spill_register(rt, mask); + + let dst = allocator.load_mutable(rt, context.output_a, &[src]); + + operation(rt, dst, src); + + allocator.mark_dirty(context.output_a); + allocator.consume(value); +} + fn compile_expression( rt: &mut Runtime, allocator: &mut Allocator, context: &Context, expression: &Expression, width: VMWidth, -) -> AsmRegister64 { +) -> ValueRef { match expression { - Expression::Operand(Operand::InputA) => context.input_a, - Expression::Operand(Operand::InputB) => context.input_b, - Expression::Operand(Operand::OutputA) => context.output_a, - Expression::Constant(value) => { - let temporary = allocator.alloc(); - rt.asm.mov(temporary, *value as i64).unwrap(); - temporary - } - Expression::SignBit => { - let temporary = allocator.alloc(); - rt.asm.mov(temporary, width.mask().ilog2() as i64).unwrap(); - temporary - } - Expression::Sub(first, second) => { - let mut register = compile_expression(rt, allocator, context, first, width); - if allocator.protected(register) { - let temporary = allocator.alloc(); - rt.asm.mov(temporary, register).unwrap(); - register = temporary; - } - let other = compile_expression(rt, allocator, context, second, width); - rt.asm.sub(register, other).unwrap(); - allocator.free(other); - register - } - Expression::LowByte(inner) => { - let mut register = compile_expression(rt, allocator, context, inner, width); - if allocator.protected(register) { - let temporary = allocator.alloc(); - rt.asm.mov(temporary, register).unwrap(); - register = temporary; - } - let byte = get_gpr8(register::sized(register.into(), 1).unwrap()).unwrap(); - rt.asm.movzx(register, byte).unwrap(); - register - } - Expression::BitNot(inner) => { - let mut register = compile_expression(rt, allocator, context, inner, width); - if allocator.protected(register) { - let temporary = allocator.alloc(); - rt.asm.mov(temporary, register).unwrap(); - register = temporary; - } - rt.asm.not(register).unwrap(); - register - } - Expression::BitAnd(first, second) => { - let mut register = compile_expression(rt, allocator, context, first, width); - if allocator.protected(register) { - let temporary = allocator.alloc(); - rt.asm.mov(temporary, register).unwrap(); - register = temporary; - } - let other = compile_expression(rt, allocator, context, second, width); - rt.asm.and(register, other).unwrap(); - allocator.free(other); - register - } - Expression::BitOr(first, second) => { - let mut register = compile_expression(rt, allocator, context, first, width); - if allocator.protected(register) { - let temporary = allocator.alloc(); - rt.asm.mov(temporary, register).unwrap(); - register = temporary; - } - let other = compile_expression(rt, allocator, context, second, width); - rt.asm.or(register, other).unwrap(); - allocator.free(other); - register - } - Expression::BitXor(first, second) => { - let mut register = compile_expression(rt, allocator, context, first, width); - if allocator.protected(register) { - let temporary = allocator.alloc(); - rt.asm.mov(temporary, register).unwrap(); - register = temporary; - } - let other = compile_expression(rt, allocator, context, second, width); - rt.asm.xor(register, other).unwrap(); - allocator.free(other); - register - } - Expression::BitShr(first, second) => { - let other = compile_expression(rt, allocator, context, second, width); - let busy = - !allocator.available.contains(&rcx) && other != rcx && !allocator.protected(rcx); + Expression::Operand(Operand::Input(0)) => ValueRef::Value(context.input_a), + Expression::Operand(Operand::Input(1)) => ValueRef::Value(context.input_b), + Expression::Operand(Operand::Output(0)) => ValueRef::Value(context.output_a), + Expression::Operand(_) => unreachable!(), + Expression::Constant(value) => ValueRef::Immediate(*value as i64), + Expression::SignBit => ValueRef::Immediate(width.mask().ilog2() as i64), + Expression::Sub(first, second) + | Expression::BitAnd(first, second) + | Expression::BitOr(first, second) + | Expression::BitXor(first, second) => { + let first = compile_expression(rt, allocator, context, first, width); + let second = compile_expression(rt, allocator, context, second, width); + + let pinned = [first, second] + .into_iter() + .filter_map(|value| match value { + ValueRef::Value(value) => Some(value), + ValueRef::Immediate(_) => None, + }) + .collect::>(); - if busy { - rt.asm.push(rcx).unwrap(); - } - if other != rcx { - rt.asm.mov(rcx, other).unwrap(); - allocator.free(other); - } + let result = allocator.acquire(rt, &pinned, &[]); + allocator.copy_to_register(rt, first, result); - let available = allocator.available.contains(&rcx); + let other = allocator.acquire(rt, &pinned, &[result]); + allocator.copy_to_register(rt, second, other); - if available { - allocator.available.retain(|®ister| register != rcx); + match expression { + Expression::Sub(_, _) => rt.asm.sub(result, other).unwrap(), + Expression::BitAnd(_, _) => rt.asm.and(result, other).unwrap(), + Expression::BitOr(_, _) => rt.asm.or(result, other).unwrap(), + Expression::BitXor(_, _) => rt.asm.xor(result, other).unwrap(), + _ => unreachable!(), } - let mut register = compile_expression(rt, allocator, context, first, width); - if allocator.protected(register) { - let temporary = allocator.alloc(); - rt.asm.mov(temporary, register).unwrap(); - register = temporary; + allocator.consume(first); + allocator.consume(second); + + if other != result { + allocator.spill_register(rt, other); } - rt.asm.shr(register, cl).unwrap(); + let value = allocator.temp(); + allocator.replace(result, value, true); - if busy { - rt.asm.pop(rcx).unwrap(); - } else { - allocator.free(rcx); - } - register + ValueRef::Value(value) } - Expression::BitShl(first, second) => { - let other = compile_expression(rt, allocator, context, second, width); + Expression::BitShr(first, second) | Expression::BitShl(first, second) => { + let first = compile_expression(rt, allocator, context, first, width); + let second = compile_expression(rt, allocator, context, second, width); + + let pinned = [first, second] + .into_iter() + .filter_map(|value| match value { + ValueRef::Value(value) => Some(value), + ValueRef::Immediate(_) => None, + }) + .collect::>(); - let busy = - !allocator.available.contains(&rcx) && other != rcx && !allocator.protected(rcx); + allocator.copy_to_register(rt, second, rcx); - if busy { - rt.asm.push(rcx).unwrap(); - } + let result = allocator.acquire(rt, &pinned, &[rcx]); + allocator.copy_to_register(rt, first, result); - if other != rcx { - rt.asm.mov(rcx, other).unwrap(); - allocator.free(other); + match expression { + Expression::BitShr(_, _) => rt.asm.shr(result, cl).unwrap(), + Expression::BitShl(_, _) => rt.asm.shl(result, cl).unwrap(), + _ => unreachable!(), } - let available = allocator.available.contains(&rcx); - - if available { - allocator.available.retain(|®ister| register != rcx); - } + allocator.consume(first); + allocator.consume(second); - let mut register = compile_expression(rt, allocator, context, first, width); + let value = allocator.temp(); + allocator.replace(result, value, true); - if allocator.protected(register) { - let temporary = allocator.alloc(); - rt.asm.mov(temporary, register).unwrap(); - register = temporary; + ValueRef::Value(value) + } + Expression::BitNot(inner) | Expression::LowByte(inner) => { + let inner = compile_expression(rt, allocator, context, inner, width); + let result = allocator.acquire(rt, &[], &[]); + + allocator.copy_to_register(rt, inner, result); + + match expression { + Expression::BitNot(_) => rt.asm.not(result).unwrap(), + Expression::LowByte(_) => { + let byte = get_gpr8(register::sized(result.into(), 1).unwrap()).unwrap(); + rt.asm.movzx(result, byte).unwrap(); + } + _ => unreachable!(), } - rt.asm.shl(register, cl).unwrap(); + allocator.consume(inner); - if busy { - rt.asm.pop(rcx).unwrap(); - } else { - allocator.free(rcx); - } - register + let value = allocator.temp(); + allocator.replace(result, value, true); + + ValueRef::Value(value) } } } @@ -529,10 +981,9 @@ fn compile_flags( rules: &[(Flag, Condition)], width: VMWidth, ) { - let temporary = allocator.alloc(); - rt.asm.xor(temporary, temporary).unwrap(); - rt.asm.push(temporary).unwrap(); - allocator.free(temporary); + rt.asm + .mov(qword_ptr(rbp - Allocator::offset(Value::Flags)), 0i32) + .unwrap(); for (flag, definition) in rules { match definition { @@ -543,10 +994,14 @@ fn compile_flags( } } - let register = allocator.alloc(); - rt.asm.pop(register).unwrap(); + let register = allocator.acquire(rt, &[], &[]); + + rt.asm + .mov(register, qword_ptr(rbp - Allocator::offset(Value::Flags))) + .unwrap(); vreg::store_reg(rt, r12, register, VMReg::Flags); - allocator.free(register); + + allocator.untrack(register); } fn compile_comparison( @@ -557,62 +1012,63 @@ fn compile_comparison( compare: &Compare, width: VMWidth, ) { - let temporary = match compare { - Compare::Equal(first, second) => { - let lhs = compile_expression(rt, allocator, context, first, width); - let rhs = compile_expression(rt, allocator, context, second, width); - rt.asm.cmp(lhs, rhs).unwrap(); - allocator.free(lhs); - allocator.free(rhs); - let result = allocator.alloc(); - let byte = get_gpr8(register::sized(result.into(), 1).unwrap()).unwrap(); - rt.asm.sete(byte).unwrap(); - rt.asm.movzx(result, byte).unwrap(); - result - } - Compare::LessThan(first, second) => { - let lhs = compile_expression(rt, allocator, context, first, width); - let rhs = compile_expression(rt, allocator, context, second, width); - rt.asm.cmp(lhs, rhs).unwrap(); - allocator.free(lhs); - allocator.free(rhs); - let result = allocator.alloc(); - let byte = get_gpr8(register::sized(result.into(), 1).unwrap()).unwrap(); - rt.asm.setb(byte).unwrap(); - rt.asm.movzx(result, byte).unwrap(); - result - } - Compare::GreaterThan(first, second) => { - let lhs = compile_expression(rt, allocator, context, first, width); - let rhs = compile_expression(rt, allocator, context, second, width); - rt.asm.cmp(lhs, rhs).unwrap(); - allocator.free(lhs); - allocator.free(rhs); - let result = allocator.alloc(); - let byte = get_gpr8(register::sized(result.into(), 1).unwrap()).unwrap(); - rt.asm.seta(byte).unwrap(); - rt.asm.movzx(result, byte).unwrap(); - result - } - Compare::BitSet(first, second) => { - let lhs = compile_expression(rt, allocator, context, first, width); - let rhs = compile_expression(rt, allocator, context, second, width); - rt.asm.bt(lhs, rhs).unwrap(); - allocator.free(lhs); - allocator.free(rhs); - let result = allocator.alloc(); - let byte = get_gpr8(register::sized(result.into(), 1).unwrap()).unwrap(); - rt.asm.setc(byte).unwrap(); - rt.asm.movzx(result, byte).unwrap(); - result - } + let (first, second, condition) = match compare { + Compare::Equal(first, second) => (first, second, 0u8), + Compare::LessThan(first, second) => (first, second, 1u8), + Compare::GreaterThan(first, second) => (first, second, 2u8), + Compare::BitSet(first, second) => (first, second, 3u8), }; + let left = compile_expression(rt, allocator, context, first, width); + let right = compile_expression(rt, allocator, context, second, width); + + let pinned = [left, right] + .into_iter() + .filter_map(|value| match value { + ValueRef::Value(value) => Some(value), + ValueRef::Immediate(_) => None, + }) + .collect::>(); + + let left_register = allocator.acquire(rt, &pinned, &[]); + allocator.copy_to_register(rt, left, left_register); + + let right_register = allocator.acquire(rt, &pinned, &[left_register]); + allocator.copy_to_register(rt, right, right_register); + + let result = allocator.acquire(rt, &pinned, &[left_register, right_register]); + let byte = get_gpr8(register::sized(result.into(), 1).unwrap()).unwrap(); + + match condition { + 0 | 1 | 2 => rt.asm.cmp(left_register, right_register).unwrap(), + 3 => rt.asm.bt(left_register, right_register).unwrap(), + _ => unreachable!(), + } + + match condition { + 0 => rt.asm.sete(byte).unwrap(), + 1 => rt.asm.setb(byte).unwrap(), + 2 => rt.asm.seta(byte).unwrap(), + 3 => rt.asm.setc(byte).unwrap(), + _ => unreachable!(), + } + + rt.asm.movzx(result, byte).unwrap(); + rt.asm + .shl(result, flag.bit32().trailing_zeros() as i32) + .unwrap(); rt.asm - .shl(temporary, flag.bit32().trailing_zeros() as i32) + .or(qword_ptr(rbp - Allocator::offset(Value::Flags)), result) .unwrap(); - rt.asm.or(qword_ptr(rsp), temporary).unwrap(); - allocator.free(temporary); + + allocator.spill_register(rt, result); + allocator.consume(left); + allocator.consume(right); + allocator.spill_register(rt, left_register); + + if right_register != left_register { + allocator.spill_register(rt, right_register); + } } fn compile_parity( @@ -623,22 +1079,23 @@ fn compile_parity( node: &Expression, width: VMWidth, ) { - let mut temporary = compile_expression(rt, allocator, context, node, width); + let value = compile_expression(rt, allocator, context, node, width); + let register = allocator.acquire(rt, &[], &[]); - if allocator.protected(temporary) { - let copy = allocator.alloc(); - rt.asm.mov(copy, temporary).unwrap(); - temporary = copy; - } + allocator.copy_to_register(rt, value, register); - let byte = get_gpr8(register::sized(temporary.into(), 1).unwrap()).unwrap(); - rt.asm.movzx(temporary, byte).unwrap(); - rt.asm.popcnt(temporary, temporary).unwrap(); - rt.asm.not(temporary).unwrap(); - rt.asm.and(temporary, 1).unwrap(); + let byte = get_gpr8(register::sized(register.into(), 1).unwrap()).unwrap(); + rt.asm.movzx(register, byte).unwrap(); + rt.asm.popcnt(register, register).unwrap(); + rt.asm.not(register).unwrap(); + rt.asm.and(register, 0x1).unwrap(); + rt.asm + .shl(register, flag.bit32().trailing_zeros() as i32) + .unwrap(); rt.asm - .shl(temporary, flag.bit32().trailing_zeros() as i32) + .or(qword_ptr(rbp - Allocator::offset(Value::Flags)), register) .unwrap(); - rt.asm.or(qword_ptr(rsp), temporary).unwrap(); - allocator.free(temporary); + + allocator.untrack(register); + allocator.consume(value); } diff --git a/runtime/src/vm/handlers/shl.rs b/runtime/src/vm/handlers/shl.rs index ded1eefc..5e878aa1 100644 --- a/runtime/src/vm/handlers/shl.rs +++ b/runtime/src/vm/handlers/shl.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Compare, Effect, Expression, Condition, Operand, Operation}, + handlers::semantic::{self, Compare, Condition, Effect, Expression, Operand, Operation}, }, }; @@ -11,17 +11,17 @@ pub fn build(rt: &mut Runtime) { rt, &Operation { effects: vec![Effect::Shl( - Expression::Operand(Operand::InputA), - Expression::Operand(Operand::InputB), + Expression::Operand(Operand::Input(0)), + Expression::Operand(Operand::Input(1)), )], flags: vec![ ( Flag::Carry, Condition::Compare(Compare::BitSet( - Expression::Operand(Operand::InputA), + Expression::Operand(Operand::Input(0)), Expression::Sub( Box::new(Expression::Constant(64)), - Box::new(Expression::Operand(Operand::InputB)), + Box::new(Expression::Operand(Operand::Input(1))), ), )), ), @@ -35,20 +35,20 @@ pub fn build(rt: &mut Runtime) { ( Flag::Sign, Condition::Compare(Compare::BitSet( - Expression::Operand(Operand::OutputA), - Expression::SignBit + Expression::Operand(Operand::Output(0)), + Expression::SignBit, )), ), ( Flag::Zero, Condition::Compare(Compare::Equal( - Expression::Operand(Operand::OutputA), + Expression::Operand(Operand::Output(0)), Expression::Constant(0), )), ), ( Flag::Parity, - Condition::Parity(Expression::Operand(Operand::OutputA)), + Condition::Parity(Expression::Operand(Operand::Output(0))), ), ], stores: None, @@ -58,7 +58,6 @@ pub fn build(rt: &mut Runtime) { VMWidth::Lower16, VMWidth::Lower8, ], - operands: 2, }, ); } diff --git a/runtime/src/vm/handlers/shr.rs b/runtime/src/vm/handlers/shr.rs index c8341021..7653079f 100644 --- a/runtime/src/vm/handlers/shr.rs +++ b/runtime/src/vm/handlers/shr.rs @@ -11,16 +11,16 @@ pub fn build(rt: &mut Runtime) { rt, &Operation { effects: vec![Effect::Shr( - Expression::Operand(Operand::InputA), - Expression::Operand(Operand::InputB), + Expression::Operand(Operand::Input(0)), + Expression::Operand(Operand::Input(1)), )], flags: vec![ ( Flag::Carry, Condition::Compare(Compare::BitSet( - Expression::Operand(Operand::InputA), + Expression::Operand(Operand::Input(0)), Expression::Sub( - Box::new(Expression::Operand(Operand::InputB)), + Box::new(Expression::Operand(Operand::Input(1))), Box::new(Expression::Constant(1)), ), )), @@ -35,20 +35,20 @@ pub fn build(rt: &mut Runtime) { ( Flag::Sign, Condition::Compare(Compare::BitSet( - Expression::Operand(Operand::OutputA), + Expression::Operand(Operand::Output(0)), Expression::SignBit, )), ), ( Flag::Zero, Condition::Compare(Compare::Equal( - Expression::Operand(Operand::OutputA), + Expression::Operand(Operand::Output(0)), Expression::Constant(0), )), ), ( Flag::Parity, - Condition::Parity(Expression::Operand(Operand::OutputA)), + Condition::Parity(Expression::Operand(Operand::Output(0))), ), ], stores: None, @@ -58,7 +58,6 @@ pub fn build(rt: &mut Runtime) { VMWidth::Lower16, VMWidth::Lower8, ], - operands: 2, }, ); } diff --git a/runtime/src/vm/handlers/sub.rs b/runtime/src/vm/handlers/sub.rs index 417f4dc3..b63475f2 100644 --- a/runtime/src/vm/handlers/sub.rs +++ b/runtime/src/vm/handlers/sub.rs @@ -2,7 +2,7 @@ use crate::{ runtime::Runtime, vm::{ bytecode::{Flag, VMWidth}, - handlers::semantic::{self, Compare, Effect, Expression, Condition, Operand, Operation}, + handlers::semantic::{self, Compare, Condition, Effect, Expression, Operand, Operation}, }, }; @@ -11,29 +11,29 @@ pub fn build(rt: &mut Runtime) { rt, &Operation { effects: vec![Effect::Sub( - Expression::Operand(Operand::InputA), - Expression::Operand(Operand::InputB), + Expression::Operand(Operand::Input(0)), + Expression::Operand(Operand::Input(1)), )], flags: vec![ ( Flag::Zero, Condition::Compare(Compare::Equal( - Expression::Operand(Operand::OutputA), + Expression::Operand(Operand::Output(0)), Expression::Constant(0), )), ), ( Flag::Sign, Condition::Compare(Compare::BitSet( - Expression::Operand(Operand::OutputA), + Expression::Operand(Operand::Output(0)), Expression::SignBit, )), ), ( Flag::Carry, Condition::Compare(Compare::LessThan( - Expression::Operand(Operand::InputA), - Expression::Operand(Operand::InputB), + Expression::Operand(Operand::Input(0)), + Expression::Operand(Operand::Input(1)), )), ), ( @@ -41,12 +41,12 @@ pub fn build(rt: &mut Runtime) { Condition::Compare(Compare::BitSet( Expression::BitAnd( Box::new(Expression::BitXor( - Box::new(Expression::Operand(Operand::InputA)), - Box::new(Expression::Operand(Operand::InputB)), + Box::new(Expression::Operand(Operand::Input(0))), + Box::new(Expression::Operand(Operand::Input(1))), )), Box::new(Expression::BitXor( - Box::new(Expression::Operand(Operand::InputA)), - Box::new(Expression::Operand(Operand::OutputA)), + Box::new(Expression::Operand(Operand::Input(0))), + Box::new(Expression::Operand(Operand::Output(0))), )), ), Expression::SignBit, @@ -54,17 +54,17 @@ pub fn build(rt: &mut Runtime) { ), ( Flag::Parity, - Condition::Parity(Expression::Operand(Operand::OutputA)), + Condition::Parity(Expression::Operand(Operand::Output(0))), ), ( Flag::Auxiliary, Condition::Compare(Compare::BitSet( Expression::BitXor( Box::new(Expression::BitXor( - Box::new(Expression::Operand(Operand::InputA)), - Box::new(Expression::Operand(Operand::InputB)), + Box::new(Expression::Operand(Operand::Input(0))), + Box::new(Expression::Operand(Operand::Input(1))), )), - Box::new(Expression::Operand(Operand::OutputA)), + Box::new(Expression::Operand(Operand::Output(0))), ), Expression::Constant(4), )), @@ -77,7 +77,6 @@ pub fn build(rt: &mut Runtime) { VMWidth::Lower16, VMWidth::Lower8, ], - operands: 2, }, ); } diff --git a/runtime/src/vm/handlers/trailing_zeros.rs b/runtime/src/vm/handlers/trailing_zeros.rs index 016fad43..b02da61f 100644 --- a/runtime/src/vm/handlers/trailing_zeros.rs +++ b/runtime/src/vm/handlers/trailing_zeros.rs @@ -10,19 +10,19 @@ pub fn build(rt: &mut Runtime) { semantic::build( rt, &Operation { - effects: vec![Effect::Tzcnt(Expression::Operand(Operand::InputA))], + effects: vec![Effect::Tzcnt(Expression::Operand(Operand::Input(0)))], flags: vec![ ( Flag::Carry, Condition::Compare(Compare::Equal( - Expression::Operand(Operand::InputA), + Expression::Operand(Operand::Input(0)), Expression::Constant(0), )), ), ( Flag::Zero, Condition::Compare(Compare::Equal( - Expression::Operand(Operand::OutputA), + Expression::Operand(Operand::Output(0)), Expression::Constant(0), )), ), @@ -34,7 +34,6 @@ pub fn build(rt: &mut Runtime) { VMWidth::Lower16, VMWidth::Lower8, ], - operands: 1, }, ); } diff --git a/runtime/src/vm/handlers/xor.rs b/runtime/src/vm/handlers/xor.rs index ddf4ee06..5a9ed5df 100644 --- a/runtime/src/vm/handlers/xor.rs +++ b/runtime/src/vm/handlers/xor.rs @@ -11,8 +11,8 @@ pub fn build(rt: &mut Runtime) { rt, &Operation { effects: vec![Effect::Xor( - Expression::Operand(Operand::InputA), - Expression::Operand(Operand::InputB), + Expression::Operand(Operand::Input(0)), + Expression::Operand(Operand::Input(1)), )], flags: vec![ ( @@ -32,20 +32,20 @@ pub fn build(rt: &mut Runtime) { ( Flag::Sign, Condition::Compare(Compare::BitSet( - Expression::Operand(Operand::OutputA), + Expression::Operand(Operand::Output(0)), Expression::SignBit, )), ), ( Flag::Zero, Condition::Compare(Compare::Equal( - Expression::Operand(Operand::OutputA), + Expression::Operand(Operand::Output(0)), Expression::Constant(0), )), ), ( Flag::Parity, - Condition::Parity(Expression::Operand(Operand::OutputA)), + Condition::Parity(Expression::Operand(Operand::Output(0))), ), ], stores: None, @@ -55,7 +55,6 @@ pub fn build(rt: &mut Runtime) { VMWidth::Lower16, VMWidth::Lower8, ], - operands: 2, }, ); } From c34ae675315ca9486777cbfc2c1621cd59bfade8 Mon Sep 17 00:00:00 2001 From: unknown <86915746+RadonCoding@users.noreply.github.com> Date: Wed, 16 Sep 2026 20:06:09 +0300 Subject: [PATCH 07/33] chore: multiplication via semantics --- examples/cpp/bench/main.cpp | 4 + examples/cpp/hello.exe | Bin 132643 -> 0 bytes examples/cpp/hello.protected.exe | Bin 360995 -> 0 bytes runtime/src/obfuscation/immediate.rs | 6 +- runtime/src/vm/bytecode.rs | 2 +- runtime/src/vm/handlers/mul.rs | 173 ++----- runtime/src/vm/handlers/semantic.rs | 747 +++++++++++++++------------ 7 files changed, 473 insertions(+), 459 deletions(-) delete mode 100644 examples/cpp/hello.exe delete mode 100644 examples/cpp/hello.protected.exe diff --git a/examples/cpp/bench/main.cpp b/examples/cpp/bench/main.cpp index 10f4a507..348c80c1 100644 --- a/examples/cpp/bench/main.cpp +++ b/examples/cpp/bench/main.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "../../../api/generated/binsafe.hpp" int main() { @@ -58,6 +59,9 @@ int main() { std::chrono::duration elapsed_virtualized = end_virtualized - start_virtualized; + assert(data_native[0] == data_virtualized[0]); + assert(data_native[1] == data_virtualized[1]); + std::cout << "Native: " << elapsed_native.count() << " ms" << std::endl; std::cout << "Virtualized: " << elapsed_virtualized.count() << " ms" << std::endl; std::cout << "Overhead: " << (elapsed_virtualized.count() / elapsed_native.count()) << "x" << std::endl; diff --git a/examples/cpp/hello.exe b/examples/cpp/hello.exe deleted file mode 100644 index 55698f7e872a415e3819886375130ec09a592c28..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 132643 zcmeFa33!{uwLd(scGARN#9>Wv z!o?K@{d7TF zPjArf4-9q(y#4kL@4&!d$lmU=2Zjdhy#w~TrdE6ZV5e_IN=j0pWZmp!tn>N|rn%%x zmomS?UN$iH23yRMHJTMH2Smj%8pd{H;%nocY83jA9e9$PetH7vSBfuqe8;Roymo-p z&e75@?1G8_u4m)5dTnaztS3~^TgF!;=Av!@$Z$X^Z7Rg`gf zWC1>uICgyh!cmdG8XoQ{4sXiCXB>2tx0#ff(O(VGty;qoHWNM*aOdea1KpZZPIsK* zc?|9ppLH|PRSEe@=ED6r+~?t2GXq_<2-lJhcS<~}mlw`JS5{U*6g}i*2OWh!FrAKQ zKK%WkOMxTS1vRXzYlU^eJ}yMrGp?{XtQ1X6P1)Di)^4&7nZp<8MqX4#UR6eq9T_Wo z@`O2?u`ZR~vDLA~vDwkKdFz%|*T`$ze6Fyrh>Dym>^Hf>dA~s^aD~@z4ls5y!@7XE zsvo!RyASRUO={8O{Gx2)4VU$+6+^E$%ieK^iyi^FV`N>SV%>Km+@0avLg)TpTlakv zKAzAwc^IQ2FQ#5PdQafVuWhge=aoiHE72l|4CiZ1Pr=Om|qn41U5XtMKn0 zg(O*vV4J)jyu&FbSoc3^-8Y}0tamN}^h{{6Yvg;|K5~iYV#h^}iyfCZJV+t55039p zz$dmNT{t{iSGaC+K^8p1a`LH2dX!DL{zH4B6J$Wa-$Nvi;|X0Md7r!&GC9khbUaA4 z5J^4y=;RTgTnADP{e5a`@_E=tAKyP=-M<^&CK>8BoVJtG2XLCSS}8I0+t5xv!4m?& zM`D3XVu43dqr3K2HC#A!o~xsIol7~kw~D&kp|hGExOu+Q0&EJ@}7QO`5-y* zL*#Fy{L~+)d!4zSpr9!4`#+ePa*vh_K#r*_(;#r+tcQ?ZNRQj`nS2`jCW}#6sEAQf zJ8>L79zyY(npBASx4|5IUZ(OihH=e_PXjSB97?rbUj|!Ucx)2BD65~LtUi$oJ9K>cnlEU866fov}|=AX`qFHqQTOP? zc_Zr{V?%G8yffZCtY3Q}mH&`Iv};f37Xzjue!>gpV`UTZDL(SPHk6EJ(Rf6(XD6;i z_QbRYYJBwC5~{VDkl_j5)sMWd31y7DuY?+fya;U%0zoq6m0^VAID$ym*8U&j7z)Fxh!66NLc3|rGeg*Dc+s|uUztZ8+HJ4UJv*8g}K z!H21&+_MxRcj&G|i#T;WD%aQ&#@?#4+8p+`thU`b_`BTZ3SVyGP59)WQ@Ja+mDZ2` z5?t1T%UcviasB9UXq#t?rq!a4bgXt>)6 zzCH>s0em7T%RlHx3GRr(7ZBV^CE-Bc9c14`87F@{{#CBgr>Mo-#zl9d+1ueBbr<%y zM%xOvQ7c$HS%~N$5|6+?)fPRZc7;QQ#Uio>avl9u{pcIyco*gllOsTlb&ZK`zB#f*!p){|i>Y2Y2GDxlN0Aqfkw+B} z7dxuo9Qvhe^ts6$W@7mS^544e4^&pddOd}5AoqUPqc3S)3aEzdqHUaPMf()qYH>Ws z2O9@0$SVCWwi0z)q1`p=&dHg|dbbxaal4wbzOJzDK=TJ9Fa9-TI?y~Z^5S=g(ns{4 zgBM8gy=HXzR8>b#X#TJXE^Xcrg)n-)sebf1*J!J4D(glB zfvSH0R8|WGb|B@YVzii#O@?^&tU=3qqJXjr^$uRcThSAx+yD}HxGpDWvJq5BXW-g^ zn>-ibSbTlmEQVJi*=t+}`U-2pt6tfHK;H>vxJF-t*kYh^v09%Wha=VJ$y-oIT(`Ln z==Q*T-wKp#)hmB@ zhs`7J+pYV$5x({M%OTas`!&}6bKt)|Y#@eQbc7D}LV(F+9s-h54dJ5*fueOgZ-2u2 zOEyypu--ICc^O`RWjDIhUq||LWV{0eD7#{?6iPu&Kv{K}8b)83`UM6K2(tL>RXHHK z3IoFU46VV~B^jfzp_DqLHI23QZGr3v-a0Y`!QA0hzdj2vB$w?+_(~mGP&Azmt?r!j z4p`P85hpicOd!UbNF_o}exC{fB5~}***tHM3-=rLAroFhM3D(pWTTlda%zwD#=FR6 zJ#qk9aF8d5$bwhVr#l{mRB8hCg7=AGS^gbi1Lf_0IM+XnKjJ*)HXjq*`9Ou|bM?_{ z&qHQoZhX}SHFL)}XBsNY%(W8dd~lAoFELVzqH^G8NEbfRYe4IT`dEaj@Q3%+@wlL; z=9UJ#h!KzFi7ZGJKHr4d!2WXbj}mkulPm{P0$|{J!g-3QCqDy5z%3@Gx4=F|a~|Ze zuN6e-x0ayS*jKm*q+Qm`pQ6pRyAIGF8s1`ST3cZ$dWpKinyF*(_`PjMy2f0iDNEq@ zM4ed-gTT*K7p~XUkN&_h@`5I0b?||8-RMzxaH0PI&(g4&TDj6X*q2l5UgZY==E(<9 zO6$;gEvF&H)B7jfXv3OA-*JSKCJ(@g_Yl#P6aA^2w-qB&Z?@1NbEFe73FmzefNTG+ zL#3{8$v8)?zZ{>jaPrj8T%(Uq{lbL?C%%DvKuHtX?*jLPJ_Qg}M_wm+M5$kUUpw^2u>S34idg9PlRDI*lfUg63unDZsAq+7r!)MWB;xMlRb z19dFNeZYORI9fjH!Zj8*2H4e)1x#8O2B<#>B-0?qU43-uB?J?xPoqDcq_snIo2Ff< zNYaXhG||RvJd|1XYZpe4vVzEQwX35Ca(sX9)r?Qkf77-1YDhWsY}d%u1{QkS`hYXD z>>byEi}X41;{yj(dX(_@X#lke0V3Bv4{C$#S*jlo{O06MQTZXsmkOjLluWcP@Yvq9 zJejnRziaOm6zF4=j9=+ALZfEy2)wN zhxcSItMjoVNYCy6nEwA&dS;T4_0^A3`J(jP!|)5&6ux`x(a8n85XZDPkmpHE)E%Qo z#nKO=;6jqAHNnJdLLDV2)q2a>1h0M!C_T2AAE zi=LGEeSxK6^e67>X9DIrZN15j3VZ_c zetB{zIeealnyL_LHcHpE&xczq$k9k8zMvVS02@j)y3L%xT-V+c92zk*R|&Uq*Ilom zP`ZHJwmsrgv=3sSTuKw5(Vx3VpP0N7(Zj5SPi21P!b;HSRhXr%M+1o(SW2V*xP~P% z(7vkBuQI7C^wacdn7md^E}fAfA2*GBuO^Y9>wx|QMlF#*kUzVzq>r@(fucoI`{+|* z+I2HnJNV>se;!rpM<@G{Ob^kRYw{`lK~lASsT+NoNAQRX4b1~^8~K~UyUmg3rY4Ec zWB`9y1~X1x4hyYaj2b5MDcWS!QZw!knV;eYn~KGv$p z|NmFtD|mS7cy6lb!UTA20<4kpo$mY0M@arCzprt4s_1t4y;Xj%liz^+w##pe{BDrn za`|1zzbEFu9@nKI`Tvjpww)ip%Y*X9f2vRzCQ$K<{4iCD`w?+0qA(VK-Cq>Xe@_B@ zI05!w9#5aZ?nCh3oB}RiU*F(z`>w%2UnlzIX}_B6e~=$z!rdglSNR2eLqfTl##ka8 zf$pJx-$2OTIoR*T83A7)Fc`2G*EKab>Km7^h;y)apnIs#8|V$~wimb7Z-N{DHSP2T zy7~rp*#q8zZlCJCX^XREqq}MA64vYugnGSw_P)Vj&_38@5B7Es^mg^)Y(j_6Ufk?% zYHj75+6F_XrD}^%G1k_D;|#vwP+!Q7qYk0LL3^;@+t&vhKj`4|!x7Q(b=r$JH`b|A zY#z95U~rd=9hckF?+x`Jvc*f*+KYm8I-)38T-3eXUevwBZZ8h_LOZ>E>j9wi$ocuy z)DM3)HPs388q5bU7T|t^Zv)b0SIMr`m2}7=)Z6c~cX@mJh628|@Q9zIvl9{p?L~fj zk2h!^7_|5Hd%JxlL0?CxcW{8&?OS>Sp&@VI1w+2TZtfYfcOegoI_>SdL%yKh8?t*N zTsTwwMf^1f21DSb`rOqU>alm=kcYi=xM-N@)VTYDzM;-RBp&wPy%aVb6bTJ>4EEWP zhe1LVb*>On&+f0iH-vn=!WT4-SPW_jw^%ZV!0_-M$b~4o=JMyL)|o zodh65>`3(?F!rm0sHrN-Wl^UppPCNiv|k?cD=m$$FEt6UeP6u0DJveXnqF^e&}m10 ziSG6UdV5VgKYK#>V~Maoo_;t14kf_;1h^&vb|t_&66EVifc**Za00w1fnRe1Jdpq& zOK7ilB)D&?tzBy`Zt?A0wcNg8s2A0^eYd@#b$e?$6~{|al9$-aR<9^qQ7V%BL;4TX z=fm>wVSV|qefYn#Jybhh>VwgDGk+~(HL&wZ68`*W#rK~oUiJ6Se${cdJ)!^lXZ`=n zbXaRqVAu{EJH>Y;j1KlveAmF3V6VY<6%O`T$c}FdOfl?qCN}`H9QHl6`##J%*zH-2wZphz_v8B_Of&3n;A_oh>>}80_-4TDAbNaP!F0lo zv|`_a>4AL$-y<*~*wOd1qjMM=COf_>u!ArLdnvxRVw`pmb{enMU~%Ox*y(!>%mnOv z@Vyb{DC}eSz6SF{*y;RbH_WrJ`|&j_VC?s>+wlE7%xkb8#`ktRj&s63f$tHR4`4q= zUl`_vDM0*Sbg=W8yx|Ppn(4 zon&f}GLbzX?JA94!}R4!=}i1U@;~l(d{0!Q+x`Q2jr6P98^zrN+$376N7UB zmjm4Cel^l8Y7^gi5?(65mGZkTfo@|0 zd{F}IPk^tG@D1|&3HcqD--qS*sQez2-?!vfcfF9$CcpXeyFz}O<#(I>s5&`rP$wx`6W{0e@lKXRqA|wzL2jLE3;Tt z?Q50miY-KqfPD8VCeXB3D zd7#HT(AnqfbPjj;{Is695vw`A06VO2?eqEk>{)$VUl4(9p%p!*GstyLxn7BpoQmNq zpOoqzkKg0*4eaakObXTXa@+eJP=lMk0b$m7MuF%T|d9{#Ohp591;svcyVXq_FSdWu(%r;Dc+ zJo#9)@PowbXKSN&j}I54cp7;9eott(-^b1qaPI(?NqajzelPC%A@c(skr&9%t<2j# z7znX#Ovq2E>LodQ2irYeLjxV?b$Pry2K#xkGL3+{1f&HN40ZMnB0)hg;Mv*b5A+U% zy4XP+JMJ3__F#O?kg7Y|hq`dtDi{j%`H)+8lWm}*-;aF!w$XE8W9wjF(Am7PrFF2? zNldPm^p%yAm6S^2V$jB#1b;m~uRqSiy6L<{367&eUp$?T%5@x7 z=X4t6-%ga$I5IX2x_`wa#6>R8$Me1syk{~Aet9vA^*IiIWTw%f0CvX`GRD3Ex>(cK z+YWvm%a)a_R2bp(mux^sv7 zBO80`7h*qUq1`utjX_kU^$Rz*Z7iu;Xb*u>Md zZHNDX;QEC_fq}KbjvimXH(1i&+YuNH4t9k~&^fI22K!g+EL&*r_hLua7YuC?yNDvN zbCT@#b)mpeFjPO#HTX|5t5`_h;1Ro{L$uW_Kp+CX%ZCtzud^AAS#O`O+ZR;bVqKlX z@JM43w{NEpo6hvVexWy5Kd^J~GGAcfO4ZZ4m8a#nZe?uf>sCf0L4MaE;(qL&w)ghY zPWV4eqd0O(Eg`@<^yqyUu92t_kqSymXm`854m;Vjqux8vNhGub9Hq7ghx~qY@O+DS zD@X=+1+j&UoV1seL|928Ung$-*LSX8c%`qrw5+Uh)tZvBRh6qtDl0lxm3UWm`ASMl zt2%u?Uqxp{SLxL;$@8zvvSwB}Dpr+MRJB)oOICGOLyoTY&XOu$d1r~Yyt1;iynIzx zb%poeBS+P&a#VWDyUMG46(y@G+N(<{D=W)N+P&2sC7r9QJ1f1F-qpT}s(+IlE6Qe- zq^xW8n$;a^R+m(j`pQZw%PXo&s#mqQmsC}BmUgY~Eba8J;gZ;S%|HqK*R!UqbXG~K ze5GA$yk+Gj?Op8^$Qxf(336&xNo7@6=c-a~*(z@}&l@4hx|K1dl2?tDafKaC$x5{? zIVW+~UTvj5{L&i^Sdz!UXUDjIe9!pUIP5i8BgV%*UNi0*Zyw(^-h9W7JN$R-xnt~( z@jDLRF>%MSJC5J+#vSaC?U4OY>7klK&4+dz@*moBXzbAVp~HtJ4jnsm{7}u^H{3mT zH`sjm`yZSF%ORVF%V!c&+Qfd={+PZzR(wJ4$C)WXkA0jP1q6SJC5o}}P{pTJ0hg5HjKtq2c$Vw~PR zGdUCCf8bT=6UhX>TiR83?4@yj7bOzLh5yHXl^)9)oc?xED5v=n;s1R&LZ3h(!v7f7 zS44a!?1JCy<)!Khl|F$T6a2ESRNb+s$K`wQGz^7&`)^k1XXaNrrn+O_j`OQXB$TiS z|HA)N>9M!R>FZ{tzxfj?J@)@N{mi8adxzkoVNZNgrN9LQ<=~v83 z@BExfkG)1t&lgf=@FUXC_C=L`29gg^|Hc&PLrbp5jv28$cT?kLdu?^KJOF1e>Bq6s z5=^}M*Ly=D*g`{?sdf7Xe1YB$atL(vtRKJ$&H)VmyX_@S<@ORRf~@Zz7y^@$u7Sal zb~<9$JHRYCs?0O=FDAu6D{!o6ABfC!veOe{`bEpf0h&xk%n!S9eiwg319W^B=TosD z2p4_Sg%IW;{QE;#$z%HSxo7h(v9ibX?Ky9OSgrpYn_BD1&c2II(2zm@+Q@=*?I2F%4h;Dsv%;AiTYZ#_Oo@7H zs%molMJ2I%Ax8=9#54igeqBr(=yMyd)f!ApcfZaRE1+U{{iW12q$L!tYlnn-) zMo=1u`rCa0oWvH3#X)vTFvyAep-DO|l4Cx~-b+q<0Rwh4w5Af0Dxd!JU%}rBPi<3U zn{!(mJEl9wSoCQKr!<$j2K#;NTw_W(0s~ym;Ti#(l;#Q!I|OV~nkzZHLBKgm^C}M4 z3fQhRujX)_fQuE%L_EzVV(v61Q;L)-4a2p;0NW_QN~P7;T^nSZj7i7fT18-8khui7 zmOy8a)eCSPfg6MDJOQ3V;HDruUw}0Pwg#D7fEyI22)IFnRHxJhN?D_j%>|&0H3^^@ zKsjp`z%~FC>;eJo0I-_12%rbR8rCWRztZ9zW^Do(R$4lS*=7OkQCd2O*%krZptQ6P zv#kOcQ(A(4woL%H!Nt$E3t${Bz3f5(+yw@`>>~m=th7|Ii-i3_rKOr(EbJ3XOIazq zL;yztlrfJ0jsYlVI|T4c02RzDfa3rvS-Sux0jy#j0(e7d>GiWt0i1H&h$3WTO6nGD zNDT&BeBD^1!0yo1#^mqPY*0H#I8gQm2K(4G!r!=VPMxoPs9Qx}E6{i8=Fpzi2Am)7 z=xH0Ifc6NN!?7+K1B3nKwpX}4DBL2IN_f0ZI8G!us(wd=^HCkDlqpiTdHblR_A&n+ z6fyILrY1Ktj#y2odgcw=+nmf8o=f$?yp<}n@y2=7-kIwJbRcg&RZ*P|HzmAi9m$to zi|Ug99=vQo`zza39*jw&7Y!tDR5!bu8aH{G+gg~ZY{htl>FP>mdW^%y<}FS2bxbo) zNu!EtBzmT)QZlUwg+l^k)fCaSNh4<{P&SR=5N)(tnl`u9H#(W-Rn3ymLBPd729rl% z(NQC#qxM917ee7SW^A6p=j$5#b0E|2!(XkZp{%^hfs<{TGscbH_rP6X!M`PU+Kg6(1Ie zOqd#-XCviv*=qE4I*+Hl3$_)WZ4C{cR>u~n$I;?+RFq@bx5Svf$&6Hxg?;N_pc4`1 zZ5UHpLtgA$a@;b3( zR3OF`wlXnw*BbScaN0Po*fc%H?5n6Xa$##2O;E4VL?E-decj%U-Rx4MDH2>MyUb{! zf;Ucqm$5zp9VV!p^$Tc%pb9o1pkoA8vOxhICukM(3+RnhK&#p10zzvDa19Fx(8j?k z78IbJgVih~z)}vPeI62UjeyJ8P60OyxSZ`0@D2f2uweoF1zgE?3wV!!SFtMuJf?^m zMeQ`NVG2XDVbl1Gnbbg+ZR`uRV)LJM8L5l{M2Y5zZjNwyfVBx0KUP`HMT?-05H$OY zsnk;B*io0?R4G!KF{a57qaV%t$wnF&#kD5V>4W42i<42;h(YYYskx~h1Cf?iW=peV z_Je|th0-l^lVKM%3WJ6gUkBBx42z9KG{uG;VW7#h7?)8H^mo>&Gu&VY z{cXMNELC80ib-g@;t1e+SHK(Cy%{Y|^B}q=)T9hSQ;b1VUl+?vpF={FGCj#}l(K2E z(v2K$RuS3hS?_~D{~Z3x&uiM?Y1(j}v$oCC=xp;KF`ayn+0x|pY;-#|wepd@W_@P* zPw+I6b~bWnqho{HiIMzlL=BE@o(+!1x~=tfZ7vM$XQQoat!?(SIW{rftYljp?)o|@ ztPusIzQM7{>A_>m9+%UBD~POSeqQEcEAqjITZi4f9V|D`LIYJobhP*RS)PD31p8UO zfOG@}`a0MG0UNlhH_+1=V0M8r0oC6-AP&J42wW23yu$*wP~a@U1%qsnfKmzavqAyc z0Cfb1`dN{H(*a`$wOBxz+?Aarplm>WU0xEZSYUF18QeKA$d(9r9$?&eWJ~ib6yscS z_VxF6vSq@(P$DqhV9Nz+vG7vPO7bkL;inid0>QZ-wj$4R0Sf*)mP5%!j=ZJP3V5Rm zo0)N1nrl|(8L7k=n`d&GdnNqp$z9E^jrDG)nqQe8&C6`CA$uCU9X-7RJ{A^|GC7lK z>XN6df;h6ya}r%Mu70Aj`cSWM2IfE>pSkH2SyS2uTX*-j5B9ZrX;gBPa5Y8QamOIL zSvXjfR?NYhx|;a0DsP|cx5h+RHl-Gmb#DhMAS!upN08kj9MaWfh>eTp);!}LL{^Pw zoukcxi8ETOwx*Wt%=AQF=DieEc{)p-J02C1py_}l>`56NHGl*rnjhpDd%#2uqlr(? z9d0!}kN+qyb2Oc&CzbwVGBq_rr~*I9v(QjRqj?sKK)L2sxV$J>S86ig z@{$0mG!D4@UI6Pf=$_ci0;qvxxSt>x)M-8gvR4GKQF9nBlY-2pX#{XW0B+3<0A3Y9 zv*rkZlLBbd{08$o`m@OIfpn)%+>X z{w)yde|Y*l$Mob4B|q~rY;BpB65T-WD|n!|J5AOMl;MgfRi zHwi%Gx>*33njMHxQa+zs#Ih-m0k;7I2h#a;FK;&?m07MR_ z3qa&>hF~pnI8y*3hqDAAayVN6B8TS)1|o-Z1R!#Fu84xj;dugR<~clH0NZ#D=L%p4 z&*415x`*d*z5x0(HAs*J`8 zR+@||nl<^E=~>9{R;&?X?h^2^Dj^3`a5U5>Te0f=xzx7ml!Vr8ZT;@E4> z&Y!;$%=KRfjVfo9f@#jnFQC9xr#5%1r`F+iZ*bI}&ooyS%)1dAj>a}-Az*Xk`Hf9m8(9hgbw!pwPhirUdBZq8Aj#i`#n@a#KhRh=i zJw5DbX>n|4^9igK_FS@WZgsZQIX7Z0qmJbf!re^q%ooM^;Nx^OunofA)Pj*8tBpE1>*U~;)u|Lwl#@`zN~PII zh}w;86aTtfv8v-DD<0Em#Tqw!%HZm=$R_|H$&{7gExb#|q>KQO61e3@gxg!38yTx1 zJCf1avW3dk9s=v#Zs#Us+SF7G)Zl169~6RX+xBKBApo)pIz)_9zoi!U*|^4M@nEr^ zSsIe5XanlN9)P9M%vWG69-QCBbfbwo+AN+ftXwloGk47ahz5aKF5qA;ykbN{oGmRJ zV~6*lE^I_FODjhdT0EhCf7bvOu$iTe^DJg&$`oeVGM6euk_RWDdiy#%yqNG=ys0Eb z5*;4r$C@qe9C4n`vNQPx51Sl85)Ql@b2W82#m9JsD+-G6X2ap!`#HhGNS(s{Fe}uBo7XThkAEn7%sx3ZwN8l zzwA#lI^#6(Wz2H@+|OcYVD>a`*|>$Jl;!kenF3HfLFIF3B}`v7k0FWO?-oOjzv6!!csraVLQ{`ND1OW z8{ueS)+w!xRzwkSs)c=MoxvCLsBRqXS5Mk2Yq==%kscjv9k-jccKMhEVRy1~ z(w;&{W^Ind>W=}`e;p#t$I);4h>58MclnX4FI_~)Q2fz&_gHVl^(@FWx%XuQe127t%#J`EDHQj*j3h1 zUNNh@ePJt3r^V`Y-161k0gLYM)D7PP3TBm83epy*6TFN_sH8F+H08HFbWe(vZYQ~L zFLrq|A-!0`SL8k9LVrw84OKo_m&zL*8MSt-?qJzNm#)}vAVQK5g5mWeX1P3#pf9Ot z-6V|!=#LeM+ai>{$X#aX&iyW^^yIb_X+yr1^(hN^`NGt!pQWtIrc2Da2FT9;n6-75 zYcuAI$|K-44|kk(WMNj12an_sW0bZavs^icL~A7XzffXP@Zyi~I&xroYTT?58E}In zWi`)%o#ZX@$MjUgkWcO^etesP%Rk=S8Y5u7O!~bGp_*cSl-vxBHh1VoYA5V6X#F3U_aVo`AAVq7U`+N?e5F&g=>#8N3 za{*?5Lf$41F05c#JjAk=DEU@SIm>$gffYftdt>CCEXYg0ueYLVwMX7;z-k8F#h?{( zr7opeT&~eAQi|;eg5QBj-y;g$OT?p-GUcn3Jv>uHflj=ElkS^CB~ot(8TBpf0*|*p z*exy)SuV}VCkLMR3&mBc>}%(h6OOO?p8- z*N}}ZLNCLM{u+hX6Xh0AW{6usmnidj4PDhaB#tKgJbpi_BAP{PmMXlJd7sF=xI%QE zQbh7)cz2S$g7BH%ooEi+N-{NynZ20yDF&j+M7?Mf*p^ICv?)nERhXU#SK(xN$7LvD zyk(>;Ws4Qw;0zYPuOF*ua^9d6D_J|xvt;_4ohoXHl2wVSlId@36DZ71m8D89O=F`- z%ubYLO74xAIk43p3?xvcYFpY`Mc>N~l9V+zaV+L-d8|k|II@_rR_b^?{IZ#HZ!%B9 zWnHu^iv_LwI7~dTjLDV_@IwRaaMJzA{3>8dVdnGxosTHKnodlvzQ(&^ zKWMhVtSKzU?H(zDR<&S(o&WAz;91C6+h1d7^ocZ@2Z|SD&sl=y5O{eOrs^yJjBUOd zzV=AYifSPj`YB(}QSJoq>IqO~{|tYV!2S*<`xOARftU=#w$uP>P7_I_s20rP_)kev zQhrBVE(KNg75FQFrv>D34Hd=xsz=q5D*UmZ8kQ}BkLy6Q9Mnr;3U86bKS7saP+H8| zQYk!RYo|c?Rh1c~`md~hFsRBaU~Fy~{%A>sUKX8O2Y|HMG!w8LKrbxx>O5OZMV@7d zsIWP(|IM_v5K=q>{4P)gVY0sp;2Iq&7j$3r@G3LgE9y7NaXvaSXC_fI*BG}pO$DO&tZwC zj{h|^IGY;bEPrHQe#6B@*usX@fsGMo-ADgP-4 zzYUJ-@Z1bXQ|rvm#{H9q_{Q` zs7dGG{TuFq26`lGDcbK24A6(vtH;rqj=WCGx@h@EO>m?M|0xGQ3U=Eg(;`nN?W!qV zs|bA#YUo-Rrl)Ph^=juThYXQJQ|hiAb{vIJl!)|G4(6l9%kvG>sDoZnWUb|NW%{Pnl&qk11Jq5hTQ%_rQ)KT%P*w{_%_Nn`G+5)4IdM)ioL7CBpu@<@> zAS*a5E@lI);XRw4W|wj7YU!?10LSMrJ&)yTm0YPu%BVxfhaoDDr`Bp*%UL^zYq=K& zOapPxAYMKk-@&0dtMOtU;35wOSKiL<;N^Jz6!;dcGLCp_SE1+1kjD)ONoQv=snb*` z*hSQ>;Kd_eI`r^EKjk3rNtzLw8|ECmhQ!@vPolj;X#o{pb7yqk@;mUBdTe)q~7Z4H6jK?T7;us}9{M-wjAkrip zQ3O2wFsCN*tdmAPtOQ|W4<~724<~6N2W>P72W@Ca#S-NarHeTnqlp}jG0e6LD|Uh3 zq9|h*xEpDz%to&60?$g6unRmZZFCoSRM6RV?~V{|gpiB9!YUSf$*wN;l3iWiEg~Sg*h^4!v6mpR*h@Bb zu~*pTVsF$Ty0TlRQbbWsLd7li5`q?c`ByCVl6CrGFD(JbF7}EgVcNe%S4V)l*vkp! zVlUZa7JCVdF80Pik;PuZ#V+>hsQ2P4yh^gI3)MN5ilP$+dxIVi1@Cd=_>0HGENg)U zt}X_bLO4@oWz^Tn$}P{MOk+Ur7wxi5PDTxwjFQVCcu1pwSh~8NvRc7&bi5IdcI7_> zSq5IH<7fh~28y*`xt!>sCA1*3`=!4C-07zrT#tr7X#mY=kS)|8l&eq^2VjzV*&>4P zft}7PWj-RkeiXd3Xa=G-;Rzvi@{lrB*9{^dgmfiB!n$5|CRs4vEfumdJtZK^2a-CV zzY*1$?0^uLr(wnr=)ch6{CB#S8M>2%KF9DfVs17W-pA+P+|~Tkos4m|UPE*;B&C8* z=6s~vtd0Huj7}ygv6CVBH6KqMA@OJFWQ=MjL-bm7GAVj^K>h!)llicd;oaSomAZq- zjv@hPq?1V|U4lj(l|$WLTPqdAX&Y?7M7j~a!4``^zoKCl;-VYTQ!zFW6CAppaf5CD zGS7^ze1GlwxgU@i5wF{c$-CEzsuN!HvO`&nJR$MC59uR)i!l!kMdqCPxrO~m-*~+2_mCvknWh*FN zI^h>rh{&(bg#*$!TJf+1qc?_dVTdO~w5Y}xX44r`7|L=^ARN;YpfvK-2FYwU&IQ`6Rh1kGH@?Zn z7SFBXZfs0Uw}czs7{uXq;i9TY(%?)xf+#;TS8sy7*F$-7q%z%a1O;$-G=R)q5l&S`r zM9Sz1k$WViUpD)5Rh}pY(UUe=Hrkq7fTmb~A1tEWcf!WzMbm*M)l9iwQCo022thyP z;EnK@RD=4HL%yGYqWHUUK^0L$kv#9gPypZO6u>i>*CYAp>{!Gg3C z=jhByM4(l*J_+;{EJ>hMMnWreC_+1KI%+7?&9`j9b`fu#G{C5_>;_@iEAQeA0GDLSPS+V9I zoikAQxlGMx%)cSI6@D&L^Vu|VGAY=tD`T3^S@yxnMi3VJ$2pXv$a9&R&vV$$bTnr) zl0SobMps*4K?|br6htr5#Vqs7Y&O>pu`o-oiQOT!(FwiKKF_9B}!jg!m&ajBx5bd)t?Lx%FGq@)GC%JfI3C;W~IJZOMEw-TZ zKZDJD7Fw|LVG2(kV}A=!RihO zZt4uy&@6n6rTrd6Np}*ZTjFlvI774a`N?!6Dq#|xoB#>mIn;eBjZ5lot`+dXIm?J< z75*~FwgOoYyF6ozU7isUtoPEQ-t|L0jP@NJFEkSP=^e?J3nY*Yg< zMrOmt_!i%&#w>;+W@*P5S=w1R+q70}O*kp4m<8XC2I_yo)PJHI@E4yAv&~maYq^*G+$uof1SMGp&P8c=N*~{5HXF1>O{cSM>+{E<1&? z-=@V8oe(q_3=Y(Hvg6s7uK}5q6fKsmf>v+Nn~hubj5)y7;8r&tg| zy&g`mhG2d@oFE-R@_IPI2JRZU9!@9|P-s%csd-!vCtMQYVy=f1&H|jc9!^jyLHv3+ zK{i0@^>BjI0gLP51Z8qpx*kqYHlXPBaKhvOqh1ducphMRJ)B~kOU~-`aB?q{2zfo6 zP>Y2Zem$Jle~SS}uZQnN&aPu~h>#q48fyhys={VwopuKtx5FtQSvcjuey5e6>V30=Qx829A!GHlU`bry9!=PLq@qAlK8z`I3qrRQGZk{RRTQ!7l6 z-6vdgqONY7FdN{ne+AiL;hL)z&w*k2D8}no^LY;1BaX2?za|_PY7>`zzAji6qArLO zPDqk(@g=|2#lVHiGd;~Vh9&|BBo58TA2%y3>X;e`P|A}l#Hiz z2%@=_d9k^i%Bfz=t;}a}N1I;Et<1&TH3uNgt;|a}m$PtGlUIJ)%>VLPNA!iJXmfILlM6 z5=O7|Rm`QPbe?J5L5j4uvy&E8mGx$7ADNzR@ZfBwat?=~NKU;tBUFK-xkm%#!KJBw z&(1QYoU09i%RI}bw~1MBcRwBhOW;&5IO*xiDDDKF$3fGqjB)MbeC?y0aYHL61$zF@ z3W95tyg3Z2CdpfBJjZi|vvS`FFBfR%fn)g=^fblFmuxQ*`@uda-w}$}nR2%bgKoTf zkUVq_m?`&|X+&evW2q4%5f6>ZnFp-cZqBNbtUxZ#LYD%ieCHLEidOi`-Q`OuH z)>aW*vol(BlrN@I?9$W)VK_@sHx35WUQiJo=ksdtEKOb#v=pb)U|WOxBBnl)rj&v^ z28mduxQLa4i&%vb4MlG$6!|hDUrm2C^fy7|yuN0TOVYOhW}FRMp}G)pl6&hU;)u5* zk~x0y!R(tu46Y|S>WgS4Sxi`?v8o*$qRqZLc`mG)_gGBl%2U~HQ zX!R~!1QpB6FDcm~Gsk6SuIWeOp=iCV7|DT-5_iDllE1upf}7?XuzFL`D!`>*N`J%j zw-0|Qx8Uz;c<6`8yB&b`mItp*`5G+8fVm$gEz&tb;=`hK!<_e*fV7{)QqjX0%NGY*mat-|FyFmVc z6kSm*IPTAXKT&SAVW4{nXq;eI3!}RX0G;t~AYc~&A1oa(3u0O65?OI3evl22KVotf zEW-p4lQCFsf-xVFvP=MZIRb~&Bxv%p5>JIx;55nwnyX;nhNU6o0fla#FJhrYWw6!4v}(2_O}_&!*^0%MQ$Zy^6VMd5xQhULE7-6bHua@|kgr+NM)vqH@zbT1YfuLVyI&$$XIQ$l@e*v@h0{}s|61*d6yM`@f$$yQHVXR{`e<~4}ENW|IfmBqBTGW1|{0;nhf)^m<1tbb3 zcnK^^2%rRaz;Z4OFKQ|WooZ28aVBn%(T#l0WE(7WyPh-YgXK~f^F}EP<=SsV+TErhE|&KZaCK!Q>qVKm|7WT1k2?ufE$+adEcy zdD#F{i_R9~2yyI)g_SGS@NE;D%jL>k>WOSL@+nu&;&$rp0aOrRp>E&Pi7#XI5~-oeI6#WojsV#7m{u{d~HDwdx(#mXhUv3OhGSumTc+sq%xy$>2C-5>-(zh_RC;yZ!8p% zKYElZerY)=elsy?5JgwRH?!nsqK0oKem0F9Od7tID7O*`C}(mfQ6qN}<2DkLyXcY6 z2B?l!8k7ZX7t38vUT;G&{TPh(MoBjTq!Cx3`8_|I z$`?vSF)LHmziJMd)C@i+!d8oPW0nGn+C5!;NL=7F;LwtlHvKfW)THm0ff=6wY8Ks`_-Hu8?LKR{lnRN#D)@bVb1DHewHT5KhRUWHO)dYd~zhs1P8Rfj~>Wtt9&sM9nZ5{VZyV_ry( z6;PX_J&qik@%m=c!w@<(CT8-c8x%`)pwdL&1y=P+j9{)7gBD5-pjU$MI!izNK(E9% z!E|Q562Ao5%z7n=!Rhr%+=q=*JDX!lx)dQ;ujL{~s6JU%s2?ArZNe~#LO@hD}Z4w05;9+E`O@hE! zZ4wkxM4JT37SSd_Kt!7a0TFEy1Vpq+5D?KOL4aS?CP6?%n*;%SRBaLjM6^i|Fs5pg zARwYmf`D;Vn*;$7Z4v|=mf9r9u4DnZ2f(z5?r)iTQf8#oRqBaRa-=)*fpiP2Y4#&Ev z8X=%fg4`YyZjqKDqD_JvClVZ0ztAQ@&PR1<(-N)?7}fo8D)>fqfBXT&#u(WjA4d(0 zo+pamzmB_!EwnnQ``0%{nBerV{xN=L+Ys5nrWV0K8`yjk8)_lG0CKSb&N1?k_uM$$ z4QnumS9LeM3xT7$8|eCoiP<8$8_b|gCi2ZNCN7GAX{Cn4R+iQaPeUr~i9N0rKhB9p zKC;P9*Tz--3ehX%)|vGyWTR6~)UQBQktOI?AY7b&1rjhpzXGAA=~p0+)ATD)<(@&m z0?{VuS0GfPeg*P9UB3cRX2$DRAXJQg1@f1uUx9GBy6O5AXg$rYgEoRV{R)(0<9hWS z3a(#)G7Y8BHiLeJ2jDbsTkV*REa=d*fzfsLg8ify6RZv~td!(>ogw&qYeUI4J3 zeAM*@q2L+Mk_Bk!M|vQ(krzMQ`e5vMcpZxzOu5kXEacn+)HN^#$K>FiV%dlx^g_(Q zZ`r{X;U%CN2*esGR?H#?*S-u?)iXG_{&otEOv5B=NSRF^QNo#C9>T+_76PdfEH8SXt1PaW?fwKvXtoB+{9;u4e&sT-Lk>!O#S z=GPS5LUHsS@J%h& zANxzmvw2YOP0dP_aBpf>+UUKhS;^w>O<6rHZEjqUfjU1t-0e|MlJM6~S!qjAW`+() z%+F}G7FpKu1h2*2T&zLTrKL99NAiiAO>M$iqp8K(CLQUKTaIjVQPEey#H7K-X9qTE z-FP%9$Tg*GDN26}9BmpWw2|!=yLelRQtE-TYj`_=S>WJ+aM&gs3X%P^LA_mo#RzT& zX}E@~3x!0>HLdiX5--Oe5vUa?Su@3(755iqlCT;+fF<>OY@lcn?a?s}$t^?~EV9r- zj7bAcH$wq>eM;c{0&meY?ZRut-EBSe&=R}6DD}%cmc*dd*YEX152KF-1Ud)O;bBC+ zgBdJJ3sc;giBjFzJd=pxAzF4((V|~bqEaz4&P2yTMaI)}fomScQ7r38$N3hxtX=cr2u(x8}>|D~(lG09gm4!d%_ z(G#KFFI_mTpV?nE&^urZC$^j5DM=@o}JS1N>OcE*B zBG0Ur2e$&arG!V5#?f^gy%DH$U{b3kV=8tXU>P4b!(QpkGvp=-P#KP=n;r{nGDV}X zl%n9z7p3A8JOUJj3y~!}3QLP93YS5i4j419^h?XA(v#w^8(@-XbBSWLJd1pN8K^@r zsb7*zsZuO}1H@9waGykRvDBYPH;URh!Y!r@rMxYjXNZl7+NUC3RwPk(MCdtGs*WIR zIw7_AegLJCCRfrti?HTA0EeeR@g$6)Qc}=V#!xMRg#gZSNdR)weho{_k_Zy|&(nl1 z?v~Co2+eMVe_Cw*E_MsgESsk<2>3lJcBcdO3WB?VdiHdhh{rtD2@*r zzW}++T8Hd%wQ{a8t4|Lz312nR7wbb{R4mRz(KK z@LYN}Q)r{boarfYiJLR&)ypYnam7YEmUbn4B-6+m8g2L;8oVVzoirjGdvQh|dvV4b zH;tks5W$Fv)pyULN0a$tqdKvQUuYC*rmAbz$Ipi%BI+ZHfBKmD5ZQ%tRT~LigIRhS z@iXG$;Isq((82dH)Gz3k<3~YB1s@+f0ioWpAULXmkDyo;d;}+`;3HI`3O>Rms^BAB zR0SVFQ5Ady#j4;VII4n=pr{Hyg5p*15hhjzAHgvy_$bD4D)`7fK?NV7VpZ@Rf}eO5 zd_M-oI%Xq6a^z`@SDXp{h64HFz%_4B%GBWxjx_M-*DvsN!gphTefhHz;KzH!YX3 zsf+I$LN^WA)^40%&j}0*m4hhoqaj_;j1!a$(Tu zMvgYAx{n74#cqVlRH7$}q2BE`K;>X(jW5JG`f$XlA5SAQ=z1e!J6ARqMMLWwp$m~7 zNPs4KgcuvT-9pi+ni9|kN2ydSQsOj3&=N;PO9dw`lIkBi<5i~X0>>FsKv{~ECun5QHw|7CaT4wCT}LScvPEnA~Hsm-eGSKTJcK3JNcU+ zQ6mv0U{H!jK^N%sxaGDFwmH*pxsfDu;%~We;na3aU5Ja`a@&J3zM|f8D@A*TwnpA^ zYeE<%-p1i-8?jLvRrLZJuC@^x8}~C@Z6lY=7?+s)X}H=(uGrvL{l#8T!__u&&5ah0 z7_Ye6Mn%^iag6net8L`Ckhku*&PHqsBT1PMcU))tEaIL_DY;OmKU9$YGk}YzW6lwoO@D~cmCDLNoH#9fl8K636E7rB3Ik1Ig-ceA8IalEw z;Kq1Qc2@zHM5o}I#4czw!-Y1x6BE zEiTgu?kyr-N(+v4!x*my@wewv zX_iDmG;4uS%N7TF#7tRsHmKIXWYl6prb=U`$A%{U0<>-i&C@QUzpLr*2KcMs_AA_fHj zegex=1S|(&M*1i)+BENmz~$KuPsSSYCsnq{2FD>NgHdX_d`D~g3$B$iLRXI1sB4YVI^Wn zQ*fUFiR5Oq&g*0UP|VEdfg0(B1qXQ>yhJbP%H!};F;u1cZ}D{nEhQrD_%W0K;vx8n z7&;moGq}STT851!_(eQ*;U!|QI-WXcP-6KfR2jBo(h}n^X8rR(V-HpMgc1Y|q)%ixzwYm=F{uhCl58&&KN zmWT!)1NATCCp7pCY&?k{Celrc!Z*>A?}60>oFwhVNz?Bs_}zdL)`Rs~I5GjxkKs|Q zDdFHYrcqgx=pYVm(Re zRGh%ZA^2hXMUOF*IvWVVf`rWB@n~So!0#yLd_1_z0rF(*pMan6*mc-ApF8x}yRh+5 z{4ieO$McOZ!QKb*H1rP;vX9~SaeN9cR`|1?rgIO3Dz@sf&P`8;g(zw%NF2!T*IWTD zUxeQ-RgoM=Jz@DF_&T{?eKac1tB>>zC*sL+C+pQm@6>H4!8=ayQY3M~8y6bIxAz`4 zXPf|6kJVun_YUE+vpk9-wYMc>=r$YI$u^Iehx6wD7@uZPZPX#vpbXR%2|UUzXp~!z zndRR^gJRA3adR4WjbcspD+qVc!^@*&Yg}J`!W{nsV6khl!k0V62bSY@b%~8wE>AXe z;I@Vhczg#zquz`FQIh_S30&@l*#V*HAd_1){m9T{tfKzs&3hvHZH zn)1Rn;7-7oCy4bSII1*OGd%?qeDZr zdsr!fIGYzABZg{s=A_psN%qy-z1971mh<^9m#3K^`Ct<9`Bgr?9_3%}Ap2ka z4l;fdDfkYucjtpisP#p@_P-ZzR*07u6-aJsA~AlApUqIf7b^mSluggh#O9qrgE4CwIwGDo~$x%eIhrqt&PFkU8*I7L>~4odtU)G-ezq%A-~yIA~f zP<|S>(s9JWLl}4)`59*?PI>Qp6SL;l%&MtP9J6umY^<7lHlEm=$zUtf+nG9XeX0{* zac|*)?v@QFc6PONbv7rCS)VxO%wrSBU^;aoUhWGb$Fy~J9i!goYm7P$FDvN6Z-){h z3Eo_ez(}V@oc$hbic{U)on4q2Iv00|+?|$8e7#coiRP8Q_w0EQ*YAju?aWsN;w} zh3_Z|J<1pu62rFxjrdu>l0r{A$k*voRUEip{^uCDoV%gzG1;RRJAu*b5KMFnjb0`C z4VuK~=(QcO^RLnCU!xbkXDh#M`)+;iuiSPwl|BjY!*dav@!p`6;chNwK0rpnybBj! zd+`WLTbJSXIdsx-JoYzi*!RQ#ZLuX*gyU6{SUZehhct1{N={|gr!xF-A95OQXP-3Y zD4-Qv{c3b{`09dEzLSKXE#u3JQbzosN)l`UqAjG6Qyom5+Pm?nU(rib$W+aLm{X0S z!7NaVQS`|w3R#M?aGh=K>wEELB#6-6wOM3gqj>71?|{b_$%6?R`!C&YXosRxPXw|*!w9V zi@MHj?`S;*HyId<+4;W!_(%LI#l>*(;E_2dNnP`OCeNsmkNueN6SZ?acKAB6iFo2+ zlMzo$tF1ZAG~A!7!+gu0`%AUM=ZW1vs2ytDwWrGwz7{*UmW>~W^5dt1;Q<`VKY>&I z*Wx$&;K0OLo&X5Wo9}`+D6KA*BATDXu9=~T+S$B0c1a1Z$`xA!dd|$I!!Nj8Z^e(V zj-BAVw{&U6e9Y%#fRu$G)(qerghRGtEUf_ZF-B>1oLlZve0Sh7bfahj$&^Yeu3#V0z)sa%t$Alc83MZo2J$cdsUPf4Ae!$#PVdCg0A&ojpVMZ@8cnlNSket_; z)#dZyMPq8rlP764NG$*)!M1djv#Ocm!7RCXrQ&p>R@Wyw#q?gkek9IOhU- zb~Y5&{?bCxt#h&up#i&#^{k4sCvvlqhT_H=m34w4v9#@Q*S6VIfwz%-drF zBl$RAD_V&a(xT&~FIR>Pi~i9GC+LxBqBq*8BUNGZG3F;_KFyX*@Kx^7&(@33y~;9 zxUQ)42OTml^1YpdP(J5bM34VPvf>KFt+sTr^rDimGnab$ z45g+~=$|p#JEj6mrmZp$teOO^IVNNl*Y)W?WW$+*51)eMpRpD^3=qltm#5+!xVfpBsTRn>8vO*R|s;`Wl z%e#9MUK@`l?p+Iss9fsM+(X!5zM18WZ<C#gV>O>c~5aIo1-h&7)-y9wW1E4Xf9gC_)2;Yi%e6gEG}KD-h=b za4Z02!CLel|mT zTy}`&O5b|bm2&tj(y5?8y3E#mX*dmx@@JV@=`uT6LR?*F${E*~KtbnMh#@r0_(S;= zb`jZf?{lSAny@dG7A&_z2la^wI|@ZJnHKfbl~XX87d6B`k0Q*2kxr)-zwYsqWAMBH zPn?8L+KiTYwM-{1p5c*6Ym5M8Ppivi_EaZ=lQ*3_a94Lx?ony>Vh6v2W zd|;^_$4BWjUc{U)Bpbi}AXF)->^MLlYgbe_o}#{yl7JVlq`2W6+EgS})gk_Sbg zQ-sc^5#&3B5LG59q-ULftG%I?kj7Gd2Ix=^jv?5C;xwGD>U5Hy#!>QczZkz1Os5A2 ziX&(f45a*{&hzYTQ65!VEZkGy%MH`lezmL1_P|V1%p(1fMHS)NKSh?5X0!rk6Gb8t zq2EqrKyO0Gvk-n(K?)4&Po7xIc~aSLqXMAR%aoq8(?6pp??&qsNf~5{b;enuuRKf=qfT;&EWD=5087a6!#}fE9WKHS^>@{&LROKi zuMj!Z&NL=vI>_Nd1){Feweoa7@d$tCNzWM~zbQ0Sj=>mVa32{2;8e3vGRx6@Kat`2 z4$32nY-{5<;UulR*V0|XSqo{@>fq=Q*C+MxfFutSy?UI~L5ZAq{ zwtkAGet5n|oRtqL_Mb#+^hG(CDa&wofwKRP=`%BUCwQ#iECb;&R`R8SzU8EE68-LD82Gv1YjNIc2fp;CB-*Cv$N zCx*eH>0DJX8>*xqa2H+S1*a(b3`z9*t@gKw=~(jM)QpO`F-+WwSM~*6p&kDko+`Nf zQ)8iyjf$FymjEKCzRO^Xz>IuME)$f8FDS_xG?4nn`LoN4sI`2nC;|OIsSIftW}4s+ zN)xq@<_@Al0mKl&1q44{<(W|AhX0RCvkjr}L>Qm;GfnevsC@7TWhU>h!^Tu+3AUT? zyBa^G?n2Eo~mibq%EFi~Nzgm1>SL{%(j^>KbbHwVnTE^9RTy5a$Vyx`< zbL&G~-OANnTs^?m!(8zLps{DT`aV}b<;pRQ;uv0KN|J{4W5aY!ccNAD2iJ5g*BWCw zCGG@g!jaCr1&*6>-rMV}#7%-`$}#KRGaRb|ZxWm7SYz?WI`~J9gNBnT?3iqB{%Q3BwKePoMA8<|EbV}LWdg)Traopt<6%|g*eZNy;^<}Ve9$+T^-s()YF3F&2 zk@d4tK%9hjliAL-?(Ts^HvrEXy}(HTJD!1rJDkzjEOM={Ju$ zZX8|XIA2A|CY?s7bd%g{1@1{ssla_qwfTwoYkulS&S>kSTQ2_tDZgwiLo3TY(p9O_k0;5YcjQ_-Li~sdsETrb#3^&l`2A4gZClWQYgHe#MCmf`;jg72@K^ zzl+@pz55?RKQVW$XaHfI!+`D3gf|QM)Fx#Ror>46OU`)g$NQY|@l&z;K1C#crJ{1W z0E}|2@8J8SF?8BF0r+#;oGHtw!xI(S+6t!(g2X`DT?)D44H!YugWoDJj|oey5SAC# zx*K@Qol3GN>gCv7Lk%@LwrkzI#k6`}1<3k49t;JiMyQB3=yk2nKtHba6NBJhM22U|ieLG)eel2Y+HwboHgxkCuZHZB?FT#jl+#9 zgf3`K(}h(SKk?Z)!Pa-ocnx?tZk50tHMS912zZ8;Q0#q9{B#u% z7el-Y-1xDMwO;t)Si0nz@yZb|5Jrlb!aB>iZsaN>yzw9!wuVba0aaP~u zHBPN)^CRad@z<351HiG$<89O#-ORiPlv~nR{gy017_R}o>AZ(7=N>6hmO+3E8=Yd;%3O|33kkj0%CsrDhO}IjQYn1{+_$M572y6kywzI#A={* z;CC~Af`t758}j91=HrAt$(1nGF5Syg*~|Ev@z&JXdpI=37eL3J3$UiW%$%PJl4wtZ+)!$e#TA3=Q6+0pn()vdyoc>!0$xBL>&zU#Go zlZ}w2408yc%v{i!S7ZFz$G_qCN&E!gvahh=vt0d*uvfYI9an$l%EmM&#*cu+CUJEr zSA5lVY%W))an;C`)Uo>Jru96_Ec)$g zDclA$a{)xTLPgCjjFikWXFSvQa#d}t7k_67QJnZmP7SwOok{$&%!zN3Tj!v~S}``b zJb>Kn-X?aBS|(eZ=p1#f-ePgt*h^=E1lGm(S73Oqa1J}!IdqLvxf-xvir7zZgjkNv zvCh;J#U_;^#vT+2E(Ra85@l72JmU$}k{5Ceu*PPrzWCklAHV|1MSq*!x(j-;;xorr z0tcz34Vo-x#bkBIYX(J@U~PaRt7aLhznzz1V4q?=g6eF{iO!U}oKfV$l+#;@mS01y zhVo2QpO(A6qC)03P@B-E^l6Q`TWsM|LAH4}SJbvudAVrhik;E{QQKJV3PB$dPH-#b z=EVrKv5>)a<1?ItDKWHBjK6XBCR8Tl(A5bQPPuEfo2V!!v{6SVA=|A)`Gg|xQTUKG z`ca7bpb!-`gw+R`C%e|f1y=hq{6RT74(zSZn<%y@pY7WOlSs_{H$x;xQ1@;#_z z%a%Fi)@FFdJn*O|Q|ftOPB3*4xu702bxv66U}bH02H4Jl^%iK%{b8+ zeWWu3vmfiOEz6cUrPeQyUThSorYQNW@t&61nVpTWw6zl`MPuEa)YVKg%aVvaYoce# zR$6k3vLtj|>MCQ>mL;ejP3ucxokfj#KXm6>s|OULla$Ml5e7HqGF@a;zEinO8HkhF zhot9_S@}-z+A$ojF9=>2AmK>!XSpDH{FoabaIKA!F&so;VA#gAEZ7KBr!7#IRf4&R zYdvQSAh<{OVEYQ#WmzMxwVUSW)Toz#;CuiHm#=I3EfX*2)mD5I()R?ereP(MvHonV zHCpE4cS zh_r^nW<-TqFGRYLVo}uC7b)dex!R^Wx;*b-h3gu|O#IHlPqZ$&7+dBRu5KjkR<7>i z>OQU>)e0?RlUzeV^75lfTUGTXW8A?yQOUCY(YTz#CY?OZ*`)#F_4 z;!23EzkX%Cf^f2~`Ig9`B34l&7GYpI0@6;Xfg#s0mAkdffsDW9+yHn3$JAvqyy1Qz zVwJMY>qXB_U=Da_i#+oIeN09Rvqk8GL&Tw0Hz$dW&Y>VF6+w+zIBUU7I2Lu~pi_P^ z04E~}n8uqI>LYPH|( zE^{;P23f&c?j~m(a}zTkPh*Tajk8s2OgvE`4_H0&5R=zQqDG!J%PBbx>JZ9+F3uCD z5=z|_khn@tK#`z&?iOP_AjfEP3lIQe|HWMZMSjEDsm^F2NR_*uC)R_*?2#T_(`U_bA%=4k$6GbLcXL-#9lkL z%pR_WpQJqvwEyF^bn(c9IhHHor$g#}XgmdEv0^?JGvAR+(z-z6YW$=;50K#3xmu_S z*ZjHQ5Io^E7rzVelewS|TjmG3x{k10xEkc@vs`_FD_NClVaKTX4Jt}KhuQ=SZbl#2 zkyD_0|K!r;&Zu(7oC~+UOd#Lyn3e8@KtUR0NqUyXKFKMoa1O^F>WAucov9pS-8Y0k zm1LahYaAO@fqS+HqB5M(DRGmIIn$jkNL?XLeZeU&LG+&Dh`N%o*Uk!fDU~aPWt6#@ z3}WdR`doPpPxx4iFV8DI4#WQtW;_*yM&l`DHD;t^Atdt$&SQ9mCVXv-y99k0>p07u z@&(8xOF_9zW(pwyhDp>YA!$Ikp!RUC60+iSiEBN)1<|X8`KEyxtZCINazZkpK4cyX zXTz$kWec3C%LX9iv-!1%1an+t4LmPC8K((oP}BP4UW@ocig+AF+#VDW(-)i;g3it5 zF6}H#E_r-tuX+3@dB9}ka&3^y8|0G1cYZ2Z$B5%m5`)AKvt=uEWU#tG4CtAESL?EVh}hPT|C$2$u+wKLbafAMT2#ePn+kp&Ap z!_1&E{5k6L;WV?I@>>T9YdvvUo8N=an6MU4zp*MpxA6CLVE#2=W(DtkKf<%6Rq7UT+}pYa5W#RSUp$E zxmv}QOuf_8$vkz^g@xl-=g^zv1Q!Eqd`)8_A!NUPSP32l+3(}`HLScJ3G;IO{w+>R)alGP$gfr8Yx3zk_9Kw^9m$B1djj4X zWlrZx^eU};#Y;1oX}Y2&!rAu|g)aLQ_57ph)gZMEzo+pN9Ex5c@jr59Ox+ax`5dOjmWF2Ks#z^#k9+RT*@Ylqf|8sx7KVp)&5YeX1PKi2&)IR`y) zi4Lbjd4z#JGXiCvIFS{$c2sd$ML-{;lVlRgYv-z7=C|VyA>wZ-rm$lrE|QzoT%qAV z=K_~E-o}E0uP-sGC_bpVLbzDewN~9j4KG)4JSHy|4X;&joc0wq@J>EOCXss4N}( zeo0vZMyrC+%Hf6aMKm1!o9*=(+d<{uXumawe_uuazLNfZMSM|NzqxpnTX#mull4#4 ze`DdZ3%|4QyY=71;~b}!T3VE}>tt9gp{`?}6i# zjac&0qyF%UlgMYz7nL=vEh&pb^K!}8ei07Wp}jr_vR>f}y+E&5QLk6p^<^`za6mJ* zx@-n?n}BYspxbgpIE2PRIY=k_Q<#XNvmnRv(%41#Q(vm_vcLL?xNHEzqS9E0_@nW% zzvl{iX^HnC{8?6-uU~}Y4y?#G6AR%l0sgLp^sl4YDJ;Y%+^Cjt`XZM__z~Wq;I|2= z@W-w6$1J_2N}_t>WgL(G9!2M3MMv{ZXMM*&!v__de$7S0^~WV@CD?=O?`nK73%*Nz zWa5zkJXf5G_{T-Z?6)>Y;;&&tIQm9FcL;QK#Ynw!iO@xOgrZuaiT>r1m-?u};HE1< z>7`On`S6zjU)OlU#SP=Q5BRz;V=2M~a$gaHGn0QVKJ4EPw9}On&vA0e7jG-0ej^+F zYyH1U1ODgNyicgD(rpgg)H z)$DzsO^M;Xq)L9|FTg>6|IKfzc;@(=U^1EN#CP}bk!barFaFJ>4B0mNslZ%c2ozF# z`qCfJ^SI*``8`IVkNoi^^!|4N^`|6*-;rC~*OC(NxL=uS>B0-Ka(oC+^i3#L3m_nQ zJ{cJNf?f0renQv~t;k0W^XaX1^U73n4_;0d_yBqbzN4+aZr%ez;?-TJbm4>SeHqoC z?Ea+{KVxtC!=b0BBKl9v~3ugj8b0a=i zx%n7;aNXZJ25*{&OPYVd z0&QATZP;Fi1exh+<;HYa@@!oz0m`(y1Om!6ns=5A+EHL0P9H zK=46|4fvC3f!_xZHsbsC-IoA>4}E~V*uV!j@HgGv)spHVUGeU?6gJvAx|%6v&n2B8 zBP1E2G1s@W@Wq9~n!|dK0bv3Z$E|a%WP=HAiDqZ`-J?h5hgoP zyd*cII`EnB9wEsA8HcmkWbcnq+H_Bo(^k*A(q z1)Fv+S%k@gDo3p+vsR!|Q|04+dSx$WRICaQbYzA3g!fccIzq4L%k*Wu!S8^$Z-7H! zb3YZ05jX}r z!m}B#|7!9hzZ2J!Co=cbEg<$r&i+Dk%zc^r_=Nne5k?edQMj960Ie`7>aIg{oQ@(`>-QbppS*Vgm_?bpkO|%I}9V2(9$qC{_HEuH-^=Z z2-(ml5D$zhL?~zT$==_OP!|0Vs`5VFwSUem1TK(iBfKI2U3TR@&j{a;CxV5Oh(I2h zv7|o)zMgUb4OXE4BPcTT3CCO#=Ykbhm_LvO&P#e)#Hq1Rjuao=*}wFtLPLEDeW~_FbeSv8Q9!O?Pgw zpN_p{lipkQ)ehi)c0T0d&mY~ZiwON}(GSJnH;XDUirq>9a}tedS8OW)#1^0-Ipx7NKjn7>(c3uF-b zvEKy9N>^(PxVS`=l(9%yYys^Bh4M{BC#e zbBn!}_I)5^CV_Knk(o4LO+0MHMc}b3_qjuOQ^1yO_K^FG3l%(r24Y_{SuK#k>mwkG zZU}N0NR}7KV+Md|dA9vyw8S6L?rd?)#!IqR+Lbfyd(4$e9sR{u76W!=@m0kVdKNIV zJxmg|*R2P~^=Y7|H)HEA^OGk2eA3*3_h8sN%rA5A%jVblRt$E^M>WP`KS{xS385+Eg2cxNfRv$jdRvoxOK zpy@R#yherBsPVuT7BcLHAWwkohU?7OvHn4yfb4k7W#%gTvTN|>m%eTI&N95I;oK5y zaJlxLbFVSYRpJdzL8X9^(TL78Zd0AxX1+yT!biM+_Q`jlsZe%_wYyFe{IdD1Sz--& z`_Gy$V!yUtAAiyO4ff;S{%=6J#A?&~#xZu#KK6(0TM;9IF?FOD`=bsXcwb}R#oj~lH{74tZtjV3%FLC8}S>8WAnG!4-~&%Y{e7X>^~Q8 zEg{CIOSZ!-0DKAh0)|J)ehxJ3lDFU9W;@xi~||{u~D}n!~;YASoNSVzY&8 z3J57Q5SYJ;UtO&G_N(~sDsA^Q)?P%D4Rzu{~MNbxCxDGCM(_>6+kC{Ja#Q)g5!ICNO2J3bU z@Y}5~D8Lu2uUpU(78+D6_LJrhn2mp6J`}?c@RisN7XGja{(QmunuTLuW3#hiV=ngk ztzsbEYCd8zn8AaCC=R?B`(w;(lt@Me7*|{fW@!L|xy!Cwjzh~=_z4|Njp=|*pC9Ws z?D;48M{Hw0QoVq9ss3QI#Xk6?qI>L5Ra_~aO=t7@W-HJL98ciZES{9f#?Z3To_YjU zN1SY~wNE}jb`#-p;nB0~u~WetW5Cp-v2*nKVn<^L0*w*{>3{0U7;;V~N~0cxnA_rn ztP$#{I}eHR42%lF-h0$E-yPO7w9zxxYsyBiS$`woZ`MB);2+j?HUQVzuh^iyavT2s zhJfGL|D^!`WnYOe7Gw2EM_^<#5&QNI>w6Zu^*zZ6y%e0z(wqSJhqcuP##YG*0Kw@j zaJrg=0l3<^(*eevl2c%C%w{RMHEM29nBdl^xd9;h)u{UgfZ*1s`vrjLS0nl**aINg zH){4u@rb#P*`MLi@ELmt74skVV|MJO4YJn))_?;6*Z}L_l_+UHVbKSekL@qVzRP0J z-W~fjjv>2pIEU8^#dZ@{fE(;z$NFiUTdhyvk3bMeX*`y>(%z~o3_P5;#@=pjjrksC z69uX@xAD(5aZK0d_I>6qhTtyqzfH((|F`);l+0v)&;>GD4SU{vAk04tfsc20hCB)d z=dB!LZ*?CjLi8!Icep<$@W*a{QCWJyEjGTL8Ba`4b6mW<=;3S_d0k)h#Uhg>9k##Z zUXN{n9xf^mwLWY>CJy@!Ik4a3?f@H=P3(KX)atI(?T%OuB?0fEinv|le>*W4-*o;=uQuGEvTZy6zZW+7jfUj zgR55ejBWN`ASHTRgzuZXFS;`8Q8Z|5kNshW;D?<78G`nJ^JxM;?fhQFBz){;=hv9? z$OczAH!_k2oICJ`4mjXE=vWt5ZNrq!z60BU;(bxJBYiHNl)9nDykglkX0ffM=zGb4N6S+K!b{pL7~Ifr@Sft^EH z7z%Rp&dN6ZS?5d%q4yb#xkK5Ck#x=ngyrGW^Uh(9&&e!E8=(dQQ>p5p&=Kw(wB(@e zSDNt|SWVe(Y%^HOghy93U93eNRC}D?bLvja8fRgiJc|dsv5N;Vc{~<|JeCJAsw~Vw z_HZcZ@OqG~Ulb;LI2`Qog&@0-2LxGr!6Nh%kIIIGvyD-pfln5!63>=p%Uy+oRTn^t z3zA_5RbWtcJ{X+O0|B|KAa~Vy5a~P~fZTGi@wnj{yXr!mU$~G5L^Yz0{p-*F8V&II zBJE@Le^@(gan8yR=cnv}qC1Q1dvIpTuy^1rk~no_a9-=R__X~++y0Kd$F+AA?J41$ z@s|!Jn7@i+YWh?$W|e~gKIVSawZDmTNMc53*uN{fDlX<^hJ7Pu-(s?bJn#P5#q{Ng zA_H#(@HzCO^t1ahU>aWzf{P~DiK7hrHuGN7egt`t$^I)bk;4u90e^F6><2OA`x`Nd z9K?Vjh6^D0o4YXHh|$Kdf9`J%VPM3txEs@}@A{j2>>C|178~|I{LMR@dmN15FFDlO z)vkRViXMAAN*=?$$=ihN|09>0HawLA$#_oXA8#ARdJ`8u@GHkJiX%!hq8LN?)#4Y$(RHr;276sjoZ|qM%S-rE2i7bvER$`0p=5E(hx84j zVHlhlz+n$4p*%x#EFgSoZa&KRgnb&wqNlPI?oVzZB!Qm?*+dB56cC`i29znO&#M)X z@?j*r5jcrqa2^6q?Jzhm0cWs4zbNlpz@a+w^_@6XQ18$%IDDP+;4nCR-E-40@^k}d z_b@oO0cT(sUiSm1e;Ayvlh-gfKLyT?VQ}6API?%eQuL{57@TV0)B%T)LQnRWIo{@R zicS<~p1{E)RekLlL-ak05Ig3hi2^3;G1$cTlaSmss0K1D&~pPE)Y3aB7ZK~|Pf zIO}?P6pD-jbDG}u{?_#Qg!q88WzNPrqyVEP?c+cY;IJUp?=--sTGpapT==xRWv1U_ z7@rsW!Bv~W!P^Fb^8{H&mW;yT+axS=9lA%Z;pIw&!zX+#lR1uB2roA)9FB07d2INg z+Pg>LlnI;^7#$qJ<2$~&Fxy&q6^Yo%T0N?8#tNL*!U|Nl-vZ7aa3HE>9+gEw?*35G z!Q112!*^6rGJnw{$VC9D5#*Bfy!VF>iLFbm7Y`KS*ejqj0A5i))Yq8|<1h)qO#30dPx_%hLJ0({v>(#@AU5@bH7SM{>Nx=-RAnwb zYaQN-<5Gw6L9DbGX(TN|xV58zet5hL$J1aWdF37%j{r`80UWJo$%`^C1LbhP-Z4FQ z`>`x#4*M7Tq;0{s1xQ=y_gCwrVIu#B2^N;qxa zBAjXA=^_s@rxuJtklFo+#SmofZ0stCqRex1Vajz8$}=LA`y!P81cQVr~~k|U%XD?bQ3F}H>n zj+GoC!b$3c#M%m&Dr;6?24!ZD5`n;hfb z2p?2?jQ4)Xj6bB46Nh6QM>aXeT>~6+Kd|Wh{5`;=uLhp8RCwC%Hju_4%x$a+kJf!ltxE z0$Y?mywDZ{(4n-&IXD>Q#hQeb2>T|-!7vT((O%yHj0{GGvY#Cm(EgArpwur_cThJC2czc|T9p)u;AZwv5?nfGswy4QEDtBp% zeryhBi`P9{91rZMOw{ww@RNGhr~D)jy{4Xbk3i2RtR(g9KOd`UB=PV&pU#y&pFK~J zSW#Pman_>aA_4etwrB#5v;{V@7M%~90H+14Tsc$C9qz<&YEH&iR3_T=-#wdt0$L!~ z@IssZ)UzpV5nX80L4c)Anf2Ji@WS{?fV>}Hls>%Brk6rsX;VrcU1(F<0)No2{JCxr zy^*;tDvyq@O3&_rCyy0NZBxn&H1LYbvTrtx`Z6ig7H7=Y@pU^OQT@;s<1vg57hk*3 zdbs%7*etdd`k7err!I-vJJJMQfy~ z9?pxvX@HKTZ$MGPa*dD*74p77&%zx_`p%Qsq3HZ*oEl8nTB<^{;9!JPtzJ#EE>=~%{rq< zN($$cYYO40xl981QqN1UmDP8_YtdRytGQ|biBs>{A}`L0wReQ%0lUDWWUlWogafym zws!4WdIW#+Y-)fAHfw;RA^C84oN@Y^7pLDxW<&zbS{SDbK}5#s8=ij#(O5%145ySe zBr{PIjd^ts8lh{Dxfbi(c@^j3RbD+2M94L~ux^=$rt~hHO9>xTd(6ELdGY?coH)$A zY4E}y&^an64(m+59aHAsJAgya_7|NKT7V~HSv2MtP*YpeuV z*!1DL;dAeiC<%wly$J{`?Uev!N*`W04*nKiBget~x%VCjJY4P_1WrC%XgzQ7^c;8z zf)eSS*7E@9NxyjC{9IAIHkU6Se&J6`-aDBO44Ifl{j6uc) z{^X27ZTD>uLB^MQl|+D=eu7cPSN51I5u?8G77nn7{<1e?TtVMZV@f|r%YIRtP()zS z<6s|PavZD*9F?1T9Hi#tI4EW-5j@)c2Me{`6WEL9Oxpb$p52|OK|z%{{8{{D4!<6d z@Hj}u^o=I8mcEfchriGBjVI75>WB0VzA#q$##cx>vd}lEHvTD;pLxzo=I2IWhQ}1` zKM#8TUvN+Ut*twY}~EB&uiH>swIraQT_{?MYiC zK!?(Y7slxflupCt=Wlzq$Ujf1Eno|+=cheA%ZG{mH>3D-{9Pl^v-Yf%sF*#J-;;qJ?|KSp4W{)&+CCBeg1*4q0}CIzG(z{{d6y9Xh=THp+#U(vY? zkIH2kaj+@r5Oy{oJJ4JF2X}}O@LnCmTY)oF04Ex`X|yY_ zGD|!`u_6nz`ng~vv-&Kuj4aIRta4>0(V7aQ@k;2i0Ld$%>%s@s9<$AzUKBh96%aeT zuzEiS1}+EKGJ%|9;pk9_(Mm?jiin=sgpo2(AX13K>U{{j>(99dn2 z-=m{3A4E^;pya5B|IL%_8Q|@R+6svUP+nSSb+l&Ik4=F_j=rO%{|pM z`XSJUSm4E4BD`qN{BMAzXR;;dJ#(C=CAQZKJ@Xm_ zzVytD28aIAGdZLe@=SVN6L`rn?(=8>x5!%PQOzJCJ?dLoN98U(YG?!=#jGVgssRnd z0i`|aD9@t;FFaCQwcQ7VZ_pM|arVaULuf9uR#+BImG=7YW^FHCv8D%v7uxI3<=S2~ z;e%?Awy5=NkpMI*IBhWobcU-pj)MD1TjW32y{CXJw4RrHdgkjt{MSM04*U*Xm3l5b zFYs+~KAM7PVA&g6>_R(fivR(&sYi!ru`gqgqh5H?F*qKol`+UTg;KK?+VrM%+NKx4 zvw2%`c%e%*N=sEon?_G0k5FafnkyEmZqa51>lv->@0N$l{#%%21upuy)4 z2_IB@jMHyQ-WX2!RIVvZPxCaIblyO=IoN)KG|6~A`nI9+2)JwO51miR& zoH<$}o#hdnK^)ji*j+T9i_P8G@*#_`nO1bd_lB+Z=rjZ8jZmu$Am!*nbll+xDwkV; zvm+;tJg&2i>;*OUNwhs&Hy3~R#>01`1z3X4645re(jNXifYQTf1P_aC?coW}!)pPj zmxmX6`0qAq4=;y1Ee<1Kj~<>vi6K3Ft*4V9B0YR7$_zQmtjvi+5AVm3LLScg=N1r_ z^-na~$dlRcH(nHM14a5@c%g?s4uNI%J3N{~fld!^7=ee^c^=Mc5)A^2_V8;Uh3Dbw z&1f1;`^FGtrXV95iK3&7p83}4D9G6>SvmZpPuui1aHN-n7uxizmD;BHN1`2`y%Hc0 zWk_3G4`U5C61@xrX^Z?b-~Ix&(0cx~r)Qk@({F!MUBfQlw4afB&K`+wKuh?tL@{#X zNJ3G2p_5sQj^#SQNjRrX)eO3jy>u+I8kDh|!|v+JYy;HKqlK=_-g1ey`@O(q1_>{; z`)@tF9}_;P_86zTJi9Y*2-SsfY4>k|r!FVXvmVX{;OxqYBl{Hf)kk2N@Em?`@(=R{)txg(>AT8-BlAzx z4|OeZzL$SGKy8W?Hn678SK>o?(&y9g$S($t%1!2|bp`aJ^G_M@WS-A{m1f}V_g=}IW6V`!cN$907dz|(eg3KsYoDjLMctJ?{}N8} z$oe^dt;M@g=zW%%z?#+#FZ9)qPZ~bYKZbmW|DdxYOhD~X&*>59`DoxtJ&Tv!ZSvoA zX&8D(`bKu1myEQp`u=kq4unHq`%i;9k~2q*z!tiWs)gEUfkJ2Ab^>R(I;wU++w@3i zfg@{pp-oF+3u)82QDXs}dF)l>1ewRq4HHm%%wuWN!hWvWhG?|=4?VkI4hT-#W-ZJ= z7lDzq``cMZg^n_g_@PyYrPr!wP9K4t%+AuOoPe_Cns8mHypcC;=p)A3# zeab9R3(VYE;wli4o{;^fT*>U)pn)$!!;-m25MuoVID0@x$`iIbx#~0IOSo&= zH=1ySZF2h0UW_ulRwhT8{J!xU;0!m)Jbk#^YV7HISr@C|Kui9?&# zpk3G=+U{u_*)O~6`&S_|{(~3uc6iZwgR?E^8#(fi${T#1d^q3u!5!K+x{-649l{GM zkPQdvys-fkqYEpL4|<~v93aO+|Kam`!}-rWp8s44D#Q*i^q;wy*-8KTd-$N*qyIFG zz<>CqU+F*d(K2^#`2lG8qs-WdvMoS%jKF^+4sFVu2~`Ic?LYm1?U(?nHx}b^o*Yw{L8E8M=^MKa*1qw+yuNX#=NpWW=tAFU8i8*d0Y8+v<#iCD z<-!YR(BDJ9*lWv-9w#>a=Qwml`p-EzWv2fOjKF_(c>dD_%31!SX0_$;RWiX-tE$c% zP`H3(j|JYduA@*RfjC>qr(fsLSRu%4Ud0PR<~{+pQV_+xYM;3C>_Ld~oOpzLV}$!o zpS!BwUxprK?6fssltOm`%ZAS8jRg6E|`-}+p%Ol+9?-O^PjSuy7&h7~J+6ec72=}^u;{Ixc`x_DN^CR3p z8{xiWpSZsf;m%n=NE@^3BHX{|b64@scRJ*Ycb)5e@71|3;Ni-D8gwsm<*4itBp1$J z#sy+qq9e;%9jmbP+!oO!>|zF{V0f(xu8 z{o1Q+)2AtW<9maaM?+>0!nz?poO>w>eg#q9(x|;q0?t>BTdm`KqiE!V(Pu%Z~ zaNi!`&hsQ;pRb8<-?2~Jw@0||h;Zk`G0eRo!u^qb;(mXG`y&zV@d)=upSv30KUH9S z*R_p5TIt#*Frq2%9n!skzO$EidLGvY212U^Y8zi>UE2g?R(*f8K;Lx@@GkaicDc4! z{<>w(khXGuoO!$(O6up(Y*TpWy1jnwRdvC89Yg*+r%ub`)iwi>h~u+9s+H3G&H|d( zUgy)%UKik{yl&UK=yk0l@H$^+?R5c}Ro`DK(0A>1?_$4Z-=Xc5-=pR%(N@lnGjFx_ z4Ku&jdHvceuRB^();>0;R?DLyv-@=~G-U4i5wH7o0nKZ#^XX`>3-D52xBFf6x`q*W zoiDTYx`52e>v%yS-yBwZ-MiSY+1F@$<@cyL^R<=pic;G`mW>sUF_HF4ccD$V|h-MwsL-)d9P~UC@{)+{n{&^->xa^_?q)ZB;M!i zUTB=TB@wS{FQ9on%J_7&*9CYfuS>j(UWeO=hV?pMX6+MkN_lZ021Pf7~GZ^8%C&E1);oh`Q+%pmG zcSN|q5#in#;l5#?xThoB2O`{eN4VGd+|?+v+Z$!_-|gem(bYwOml}7{x)<=Z>_ykK zgE#;oahF#%21dQ?yqXI~dhfApE$m*PrroV^w9n58$gKL#H&f)R=k-|dF7|77t&Ya9!Yh!>i1X8W_2NKn#gUhchT;&_YzMY z`VS13qfR&nIGh*oXudadrhrop9U>5bBkq&LR(Qz(rw#=P$_3zHCkHRq68LzV!Z|8T z0DCp+uHZW02|5DfgCLgBl(lCb`$ZfR{;VN$M(|aL+1F@K@LJ9Hqg&M^OZ0tTkf*1+ zMM()DWWu|PTauSTV@e)IreWL_kWfM%rvji-aUZ_G`4b>J&?(9+argkL_BshUh>#FY z4IsM0mpJtlMA49t20$7@@+1Li1&+__LxAX!TDJNuAiN8XdX|u90pYIS>L-A_5Q6*) zkcJTCZ)}By9hP)TVBv-k<@W$0uW2TL108no{m=a&nU?@Zk7$z4*??%fOGrB)yTKj* zNvB1AbE-+4+aYBxTY=LAe}4OIEzjM6@J3l5@-QJ-SbfV@-w}{R0Oyy0Fedy~7UT`f zTnUKIkg8u$yMDK`q&y2a`Z_Fd@Vb^HG6uEfYwR4-@%n{BMh75zjwb0`0!U*>^Vb0) z&r$2X-wlX<+p27}gAmZ;o`if2kZ>=)2gsff_x}WhiX^wr`E9lnHo$5O2t8Kung0MYR- zA*VA5Veu_^9$SIC@4X)dWIkkq0IKhgvsK6wo(E(%5d2nu0AvtMe29YqVMnN6lK|-t zwK@q9UWfJNX#_-{uah#j0FqF$NJt+b`i#AVd>W8)Pd`959t6Y)aeo#N+EnstYwPIk z*Y>XKEog(M__75|M2xQ50JqSWGQig?rWnJkn&Jw>jk6<@{nrG zXkORVox#?QkUZCt4jB2o9t0%J>q$VURf&^K;wunIK0y$SL08@C?nmVmKpI25rURny z-IYB(2M}GaO2|ck)Pb^;nLMrmM2{d6XB!~$taU=*;VTV*^oMNmI3Uc2e!re21oV83 zy#+`g5PXOY9}N59WI$*gU!G$D(bo`TMvICac^+HK(*m42wDP%c1SG80EyM}M#r1@Q zS`j4+wLM-}hGl z2}k7~K=ip&$;-wg3o}5+Z&eFOe~9uTKGuN6$GH#?J$6ev8MeY$ASJ}O)6TKG&+94M z_X7t41Z2JgkZ=~=4hVCLujgk733=V~fYgO>{s4%c-AH*x;~}T_gO{(dDnJq;Ix_%S z62dtdkS0&&o=kUV%f_@(6>4=BaKc(`0z_Z8lH5N8$S!oj@5KXv41$-h!xsTbgnIFJ zK+>UBWANnbUU%99Z@@IZgeL=nP6Xsx4ak5LQg{sNEJ7fN z->MssCXbh>cHnh)cos~u08p6?q!gVkXr<4A$bNkS5dC#F33&yOh5#M-b91(@?g7qV zNE>ehG8B@q6i;!5z2#^?^tlvK$+VEN?{$>39yqmLzX)jrgwf;M>taB{`R58i&`9AV z+n6;V4dI5MQBI^MJ!1`ZBKqAn!< z8-ddl(%7AVZ18Z9!#h%)@D>k39uER%Ak^yXfTTk>-zDXc4*y8X(3-EYV!YX6gC`*= zPY0wAfBm>P8xUVV#Mul;BGl?8K+++31_232_YTtWBy8(WA-1syc@oNkJAtF`5Re}A z79h-`(x%0eG4pRtt?ON%Z0>IET%YREyX~EAU3$BD-Ma47CcW3u-kH+dZQXpnPEmkr z^(JwW!Tqk5j`p66N4>jqz1+v=7x5L@&B^x8P0bzctqqW3V=9yCUeesz+L7uuTGHuc zZ>M}oYbz|?k`nJzNaBnbu#BGOwp3D9;mKr%Uvk8>W)(hHmg!BaQySB1x>IfGOm|H( ziSPNfZ=zh{coH+Bi-tu4awcVLXzoe2v}C%_L2;fWK(zIePBF7=?`+Q`Su3t<2HB0x zoz3f0t1up5Cr}cuO}zriINtd09c+ zWag4|^tc>4vISIyXhiVSm!5IjxyhwxtXf^aVnuS%;)Q3QmOQpQyE zi3+>9BSmHR!b!3fA&SRb(B=rPO0^MqYnT3lVC0ZIuM{QEaroT!&eOwQHH?#OY{nb! zyS+dt5Jp)ckrjVlgs9*OMqMzlyg-vWyY$jabqvbbOeXQg)nqq5(cFW8Nd|$fsVs1| z3f8X5%x>xG%|tZD11O|AmbR?Q%$n7*p}9Mm>27Y%^ek;zym%#G$E|DbX>Un(LDE$7 z#-*#1t80^sSItf01XOF6GI_EU@zfK7Fv4<}|CH5du1v02x@t8lH-=d|QpKgIRyI|^ zM^b@ZB1ME&Cp?fRI?!*`qC1twoH+@LfxyP*?u+=bf=qi?XL56Qd!~6^M+(rsRT2ha z)2a63=Fd%b_jYF5H>RLhB#~w^-;4}Fw)jwOwi=Tw&pu=I($g0w8&)kod(oN6m5W!L zd5Y}M;xiVZPkYgT8kA`P`tC{T@7NeUnX`LR-KWF(7Q^{84o(&Y-cIj^pIEGwt+CO;?M+{_VKdmKCZSQPtSgg!tgm)KSas~%0t(((Q z8HIOhS9g7DE8ezBXOznI51!HmQ)f|H+?N7-;4~}_@Lt+;Mpviynx&nm^mccrIx_;n zqnqmN-I#3OD15G?eO-5R_azvWHe%RKayV-4z~I!{-j$Ruol5GL2Z>jf6~dr8l4)3i zE|}`XSe>0h!8C9<>Aj%@?bfHn$cb-!_85{=00PQVFpqRNX(Z*S$g3+gYG74g^7ePd zi$eTe5#=>==gc{7j*`=hCuwIHR*>Tp4uQ zz7Ecd^+l=f&Q!;7$0nQ6Q|Tx|XVfS%=f)&mM`{t8gpS+0yE*~l)3UOpKqN!tMS*0M z+>mN+^;tBh+mjo6lAGH*lYI3&jJyf%o9vjCoRzG_V6!TfIa}8{i*>1Us;qSUDkfYN z_~nC?PMxg^h$_eK005=lda6N`Ey89Z2j!`fWC6sFz(OygNJ}Cq$STdtABcq&%{>|9 z9+8LHxgL=ld!%Y$XH7oGcrVX4au{iDfvOSS?JYe*n?1<$$*wjMX}L(8_(GB8=Ri!V zdOK3d_8y+FM32Jge1CE`GK;`(eqUF2vZr@l&m|ZKHzqfwx_g-Ag~@0PTo=)*kg8gg z>OdiS3Pyo;=+sLo(gg}@!|!ZxW&^*89BEXf@!}-wrYWt zbCv#aO*~uFeGbwBu`fqW-`tQU9dr)ab(u*;joO*iuXt375BaO7vp|ig>_hd*Y6EE+ z&b?U&{zjyB&?+2@Vorwq8m({xc_%9=&?sI;@G?;#<9O04=3dR7C5=B1P>$DtRDn9i zH=fLUS^QOEWB)ucJV(R~Oq@Q`j+xG~_KH;VrfA5lJ!4f@N6+GhQ&+C)T9sU^OprZZ z>HLu22esLy$ETGno<8=cD1{x79=du$d4U6_jLp0$9Q7@xub!fmy%u*Oi$yX=p;?h~ zw}_^gXE38liZgd!PkAz(GascgUD)cN%xTwqTHh%w&$Os!%gC&p z@?cDE?hyl?H)3XaPWEu7otKwgQH8QxmzHY{c+JxK)t*l=Y}EzN!Xv-eb3NHpqp)^P z5`yN$b<$)qn1hv`Lu&PsSx&u)+~SSUI&B9Er0jU|>j(py3MNRckR;QjmQ`taQ%g$+ zh8kbXQdKS4P9DxUevFG~(zA3OO1aK=$R6bOP+AHFbgB=gYUZHW-nA~-*4xQqA&Zu( z3Y0}#0u~MHyP!|XVa|&l-=n>G9rLe$Ql+q^vSKknEu5tT=TVfRm^suuSF0{i*XIp> zJqgs_;n`RCpFGbf)vYeSEAMG+NoQ15m*k18R5BEJp~ED#)Ek5%Ii>LURG^_KKVe#@LNm1oRS5)qcSJbabEY&>YyIf>^fzzdTWk6G+~fy!@RC`>odhyc4Czg)Xr10q3WZfs~IQgaB{nOy_^fR_0|SMZ+}7}*|OoH zp3Fs_B0xjV6r9h&g3>s7=TlUIx-~{v5s1<55s8%t9MnJQtg|m}^lR2L zqTcS5uDwMu=BZWQQXs-f`M7teP^ndLUMDAE*(?i>JK4hkv@?7SE4rU!%I9@Ko|M)l Y6V6bo!aLurCZESk diff --git a/examples/cpp/hello.protected.exe b/examples/cpp/hello.protected.exe deleted file mode 100644 index 0acd44ccdf606b78a6665d773ef067ca22543235..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 360995 zcmeEv2Y6J~y7t<8W->F$B$*UK0!bJkflvZzKtM`D5=bPCBvi{VNhaBmlwl?b#TFGf zB9>sqBWe^B6$|#RC`U!H;1PQRDW-*2tGXC_0;J@-6^=fC&=o#$Dz*86?m z>T9oF$`*Gs9b-%nb?g{pk3gqi9{=zEuM6ymA&*6{2h6VweMBmKWoV7k=Pl+S=A`t99Dk9j&&eR$F03wXLPC-Z?HjJTyghy0VC|`n3a?eERKYutR$Gc?e^N z*$5UU%i~x)gwmj7#+JlFw{lB)5@}=xox)8&0|EN&Ffax;o-)gb*Aj@@I9as|vv5Sy zh^bP9To+>t%`o;bc7PnOWGqp28i=v&2n#p#{g;;~f*j24J|9+jz=Jsgk85|XXos^7V4ojfOU-CcTmq+f^dOSqxA~_eCj{kL0_?m=!Jgq@sp-@>+ z0lz$Quf3VOXC-bfIG&vZtp{?Hx3QF%{$C!+O_;<9Rui-x;PdqBLvB(Am)l42+yg$vXKEjE zxgvb&3E=Moe-QM%KIA3~e=X7AQ{vHgxwsFx%*5xN9QogRyxid%FYf{eWeEbL zMnJiG%Pm2O4&Qj$1D|wV@(KT>mmm`JA#=5kTh;#ZL{4e&PyJs=ft`^<@>oN|xX2-^ zxwJCK%B%JTf9|EvCVJBfb*#wI@Wr}4JG;H90P}un@X42u{9wrMmX*71!I(y@j54{{Ygn7R$(6L852HfF248ujs zyx&1P?X7){MVbFpHjrDdy(F_=Tx)lwMm=?*(nFLdLCG%5Rh}TQ9l*N_mR>7dR;BDD z()A$C+j#NyJCyecUJ6(;)O$B%lrM;IHV9*Xe|q5O$!S0Ta(Kw|A6;@(U+}0B76qOI zytgXeRTP;}HINccrV^MsKXA-E@|`DgNFmCxji}k6F6G#}2+ogrcIm_3#V}mwPmc6ZJvGYv2(;VZe`dBa zEgJ0EU^D%ym_@6f&p69pzCL6aH?O%>H`+m0w)>tk({q`>s7*=^^_2peGM2`*F)F%Dsel z1CM$D=~b(DE&ViZ%ko_5 z!aH9bbNYsn!(Mpc$mvf_dL{Rxa~`aySeZYuyE6RbWe0EBUzWLM$BH&(Obo0$V7>IN zyMO%Klq;m|(N6CN`8xK^_ph$``kYltED7X6;OnOGuSE{&q7Otd5pfKNJ>3hdOO4(K zVWIef=Ov|)7|~!T3YknWZ5pfC$$az$Pz%X)1SU}~9wYcEp!aOJ;444gl|afCVm=RM z?x%;W&&faJ&x%=?zyF`-|D!-zORQ~Rg~t3f>>!1lE${ z(}{Nv!R3HI?C?$^!V^Sj06{5>h3Qn79=Z4Tt>=$zEV|*wbZ-*L)DveWxe*Vz?@i@w zB4mQl9avwW0l@bU>e>gYp4`@S^kRJEf&8Jw|Mhv9wg2PQOAEfbJ-j#H`&^!mb^CwH z+a$jm@|##z9;6%z@#Q zS+|k!IS}3#J0`L8qE~-YZYN4JC?5!iy%q5PyzXxAT<9&(tD(1ccl+wMa|Xb|NwDBK z_m=bjUa zE`Hhg#MhtnJoj?-b;-qRUTn81uMlewSj`i4#y5|cK6~r&h+{F{H)rTr+R@W4RK6wt zcJMzqY3KOIbk`NYX=MWuuLJS=sLHZe^$PmZ)&uK-fBLoQjC*fBcuSD~TZz9EeAN6@ zKh?wmp8~8a{Pd%@yEAw6PI0dTzOVUvyK$$e&%CDq2j=e>vOgI1yFBqD*@C~E23 zIo{d$_mN#`YTr*1$shci%%<--&(XZ+TjPjc>FG^}^aEdJAKSxC&XLc)=p6^r z-ka6>bPw6z2Kx{2@CS!ixtI9s!LNM4z4_K>3jdaKSOorQ^VzbV>&|lnX-H!_@&@4D=a(xuQz<;9s7WR~oG!+FNN`(ImpRl(kb{mOWf3xV9EDJ!zlk1Tt0>Wo{a z-GBc@q21mN_>-I1TB7;e2K-@8fmcf(FInBaQ)V@5sQfD*5q=za*-DOIBfJeb@o$|pCTMu`YR$N#9U+Q4 zpqvvASvzE(IcM*(SCnRgm4FW)T>jK~%IO620WWf%b*=aHY4}bXx|MvQjsbP@*;mf^ zw*02cb}hT%*RQ&iaYT;+{hssI)-Jz&-rusuPuq9jCEm@jcgcBeiiP;!Ju5Tk6A$kE z>jx*ld4=)|k@kV~_KZ&N8pt)TSgs5v;%*R^exjt1`DvJqce}T}Y}s^j*V9d74-9|i zulttQ){h^zODQCQ8z8Wz_H1isnY?0@Bk{8<2YoaD^+8SH6Q7Hm#|C>R!r{RU%IC^5 z5?%)3_n!2Q0r8y|26@w=rylWUK+D@-Mc>mE36Potsc{*1es|qxg|h~CZ;Fl^aD$<6 zQr<+v-nXsOpD1C;xu;q*rcSv!ml7?~hY;?6C3%*$#*8%XYl5jPlFt z1^xCrl%vG>{u!B#eBzEPpHZ&8=fMZgO}h5fGdHeLWMaJw)|c;p^5k`>d3t?csl+6@wt zzhcmaHDB!e;*o1!&$;y63S}!<$$}N-HZrA=>2)$C!*rSQ92sL^Jam=vJeiF!Z+4~T z{q(l?+momo>_P~b_iLD6T7P%3_Zt|(y-%H?SV{Wb9cWjsZB+CGUItis!~GdA9e#9t zY{{L!I$bZOHeaKRA>MZI2JKZ&C-WAVmz|@uk$EG`yOtQnG`NClv}|I> zy;+OuTX(F#>duYIUx+yo%%c|$%E`ARM0)Qd)?l#yQGC_2%6EjqflNQ?jt)JX zd8hI`QGR_I8NZ`(|5-=Ozbda2#S2Qq4W}w2348#sKe?mzcg0Nb6~NY)KmMARpP#4b zSbit^r^LA*oWXY-^?n25vh1cal~qK&9@J@T{u+MvJI=o-O+;A_O3I!uYx1g|e0|dP zGatF{#Zk&^Vz|LL^#W#D`|P^!y!XKs@5A_`a(_!A*wy6Hlgh1Ro(=N{1C=Ytd=ktt z7rpzeaz3GSpvO(hXJj4<^Qh~TgJd26^I5U+Grlmb@See)Mg6sxIKMt6Gl%jL84ttw z^4G7vc&+kQvn$Dd}NQ5skUF=PS&w4Q*QUQ?=L4o z@)StsDSszpC5#t%-+?7J#&%)${>HSA83eG?Ahng?M`y&kNyuafAmgV;* zD1RaOP{@Bh=9M>cha8&U6M3`~o~h%@t;9a~1WL*(^hj?z>U|j&{h?Bdh`$^BXJ>Bd zOnh+3qd$KB{nZf>Q|gP{=BEU&eT1)?%B~Bc08#} zCXtIE(v-aav}2F4_g?zqROgC!?g)|7zsZXJFzL`=9`iQCt$EXxPliC$4$%Prj}dJS zXm{*d@wB%Lg128t-^Y7I&j$U?&nN#JdDF$p9Yh!g!gW>N42bQIx&|FG%KE_i+dBlT zDf1)HCG{r5_?P`wuMK+1x;(3QC=5zHx%1xRGCP0$R&N3*51s@6xM}Fa#e=C52<&go zA@OYxe_(gs@=@rSO;ctNX(LF>ZoQ~fNh5R-kP=0vE||!@HzvU^MwC`iysrWt{1D~u zunnYBi1Zvt-bbQmD*K643{vH{-OAepP6Fu3yXp7OCuJzl5n(h4%41}*!L;VmZC5|p zRrc!5_Y|M^L(CVo8L5Yxzj$KB81GqdQvL4EB>&GR8{a&}2gjm3cv~nS7x>G^bweSt z8zSS)OT3H0)Ot33>+gRW)L+enmFe#*pOZ7I;Y?A-#@PAjC2IYvW5ier#!tvC)K3-) z(kZ|bVvV(%Ym_9ybAWHIdtkpi&XD)}#}(2Gd2(sn-hJi|FHy3HKL-43e)7IHQOCmS zF7&<*ee}xLCc+zKF7d;_Pf1PxF{jS^GHBwuJ@8NO2G~1k`uW}~ppQ98*-Y#Qz~0jRde5+^y|I@+bno_?Pn)g0ON{Hl zm_KjuS=GOMy=F)IP0DT}t_88{BX`xHgO>)B?{A5<6s(%%pMIz$4+lC8=$!TqlP*X2 zz3K4p<&)l1rVyt9oMlQ58FOKb*mvyJ!=@(+_P_tjt-BsydF8rYvND&LgTX{i@aqH} zTXe$$?{Cm6TVJ^;bH~BEgUfF{$sc?~W}iFX*sNSb=tH1AX|FjvTcyX61fN>IS2Eu%}d*r zp8DB$DW|kvHu0PBM_QfUDzW%h-zasbB7@>%aaweFL1RzKeG{{C?ZTit6vRko4bZZJocdm~}@aAB)A3i{n& zoqCH%PhQ1%ji9}-Zm<-bzTcAl^|1fHl1j!|FkU?Voy%Wax#q$z6BDGkTYg&hts)U?3|M2+yw|{u zo0q>k&$|(ZCI3jBjR5wj-}_}CQN6PPdfD}_d#`}L@`;asR;G{xUp<5m$h!_S@1^*A zW{LK7D<2ViH`p64Eqd`0O==ntVT;t_0j41tStT!1xzxL4ey&O>Hra*KKM4ud( zadvyheS2S9_3@VH-uzqP)=fzdTGPI(U9od^gJLGBR7ka6``y-Gnm43RdHc`=@7vir zHY8$Ppo895K(iF3YBVK>Y#w}2W;-}Zd5-Y=z|pUNdV1F4WMwxIUIL*wXVwLazrE;j z|dPb*!-xEzf87bSkWd87AqcnCSSM~E%-L2}is$Qk)GgUoH)rWE2YlQn+VSrBE zh5wtsg>!nZ@*GotT9|$Z*36`1PPo^P!~+Q_fdFP#YH#{`}aKFGO0;eR*< zik;2PZDVcA+uY6dL;G+$FYdp{k8Z&?srp$ifiDaySA96mN%`)^juvNYyRE*h#eqEn zPPeYTQ;%F>GJYVJ}^Tl*hM)%c_s zt5L9>!RhH}Znt5pLwj4B&C}v&Zib0(ba1-BM0A|>wzPTWg<2@)wJvRKTdu~Ahuhxb zXjc%~v{6%RsUF%Lk?KiHZ5(S$Z5(B@rMaE$osQ;d0GO?bf9u$>m)|^gtRCtR)KMr4 zXdgf?L%J+aUp_I1Hd(YcwK#1Jj;7`gw{r?D2s4&nUyl%ZY^g4r;_%p7+iXoOjz(v? z$643j)Yi&uw)su&_6|q$+zzLECAW;QHy{sE>ut3w+npYpquu84xxyXApU+;UyR98= zX*QQPwJWwdZ1S*WtVmr!a$4M79%n~=8xjxe?+)^tHj1>j)wMO-kcS>3q}GoUq3+*a zZBsk)ag{Sro`h5Iq1!3Fj-h9)5-Z=O8?)t| zxc#yd)@mN)UDh;T1u+E6BadAiN_8V|k!gdMyd!&n_sK zVoR%Xc1{>;o7vHX&$xD_t*m-cbru!JGfJs<}T=KpfX_YhQ0|Z2j(8=H$mmX zydC;4P*Y*H4PdMmsu*S$^iQBFVLl8!GLErRVYWh#fm%ZH&?i9E!;G|Ik3%Ui?|{A& zsvTyu{cP7j##WFSI+mnaH_RE(FGoLZ6U@|K%S*yGO_)hP2Wkh*UC=Lv+68kr^g~cD z!%X`x8=>~X?1COLgt5JZG^q2Fx7c21agK;H><6y`moLoo-`P{a>P4>Rds zK$&1Z4BasdeSDZ*(65I|gt-U0WjNvuvlV(CR0_fwmiXP} zc7o|~W)^p02(zv*urNi>LOxOLkX)^5HtL!2a$SbJL~=LRce^X>{iX1j6r6G zEGeTe_aO2=?zgYYAEb?cL0%*Ma?kVA?gZ@sXvG1v&p`VLG*F z0IfTKRs&idXgvY6?VweH77sVI-yzU;fmRtni(LmU+F&JsRt#D^Xn*v}Q+W$j-KFYX zs=i6pA5itbtNMGYen8cKQFYCq&^4M#=z~-|TGew@eQJ>0tRQ@95OxLORVuzv)vs3d z9#wx#)px1-9#uc0>iV@Jd{$LYR`qeJUa9H}Rb7kK5*52u{d`sLR`okn{Q*_i>^-I8 zT|xA{Dn6j<->Z5K%2MF(&-@ap@xLX%MoWdxW@o#z05h|gRc)?T=M}4@BFuuigURd? zdA75?)Zu9_qKRI{uG5vY!lt7c^LfF-KkG`Jj?UxAtjAF8Y@gSvI9ltQo%Ka4>YOf` zPn?C>9H*OYH&iz}oi4W5P}A&zXY*-BkLg3yc}{h{54y)$Mkd-R^AdY;t2_ zlnrEdkF(w0>RfK8Lu)vYh3N6KLkX*OR+cb)Pf}^n=I@g@mkmvzuE4@ z!6=>v4wuW`zS8AnCkx!vifPiOdb`VkbAHHtw_W4~@^b-m)V8_X*+M45PpRsla5lBo z+8a7r>(J`*c-OVH@ML9JIj3}Q5J(5)X|Hc;LxMt})!y0QayPZMH?U3E2GiW(QP96; zNY&2Tjs_gI^0d2~oye_iWNNKzaUma{FxnTFSGP5LiYjMSRku~!i!e@!voEf0&#A*i zF!Knr6UC>buDU%lvrcii?d@(yQ@f|6uBfOAwCq}kr>V}~23MVqmXaEKO@_UwdZL}m z$pLP^*;Deb*{!RA%?b9(Iml)-kpG&RQ&d%6RGOVNuD-dMti{5f!_}1D;z?iL)S8YI zO%KS`^ybX;%=8RZI1RFaD#)JVbhvt1n0mb1q6GJnLR);i9F^-{QuW8nApbg1N_&a1 z6_ERPR8U;h>G|GnUktbVQbBfkF$=WW1AC;7lS2V)3>0LHJpj2t)!bAI_v*%sNuQ`? z8KnOF{h0#)n^HjH$Br>w($zcE5+5sN^g$=NVyJ~Zj9sQDBz{w8tZ4Bq>`fbn^^{>Y zXDb#4@hMFkHm_z@dhRfrryWhWqq(isIc?ZVr)SuV>7i2{9*?u7wt1xu_FFyEhIP1G zr+DfVXN$v=-qKX(Zu7J?w5OwSnBwrXjO)xCW@~X^Mc3(RpD$Jsg=f=4ZMLcH?ha3T zNozyfzvx)@FtP@VSRL)4rDg#^a66ZEzzb)6B`UL~W@n?*qwxZ{MJr&D`XZ&yPA3+f z>3`ZVho_{qvu&x9NOlMX`W@gTWN$HsrawewdWY1Ud8P3e~oUH80>60ha)~4rX*Jm_Ltk0--OyVK2@h<}<@ZZmx%#40RlIzT9nB>UJ zO0R9G%|_lhbJLMi6Vh{X8|o)yI5Hoh%)~$9 z3;*u&@v$bv{nG_Iq+;P-d2avieeUC9U5UH5xnK7l&iffTJD37xx{QrH0Pd-RO_OIz%k zS{Y7%tG_Je;0opt+ag?`%^I?2#&$e zA%eLB|E_k-ZrQwC%uQrjbM7arZ;d$^}wFW^1#Q@o+uGsujSBl>Ry!#fndG-t&Ig)4 zSD5T)6|^;D-ql$@uN9Mk^$aH!^u}JnGxr_3ka%4Nf@U~^zo>v1R23CeR28y~--X3) zLWL{&JT0IZ@9)FHKcr^EPoCB6X!Nij1YIv*0_Xl2HuP&~Cclun<(T~#7CtisyAh=F zj+R=d8#}SZWU+_+BpitM+o3@^O_F0g%6<-u+K&!9Dq2%Ig({i;40qvgoV}o;yryVj z4cnuiZcM!nfs-mTi`!b9Y=$vB3!VXIahxZxNvh1|IA36^RGGu^Oo8L2$_X472yBxo zCvsdUaGFG!h^yH|%!`a+lp+~Y*@^;>o6Qn1N2+!<7I@ffW9S}ma|srDSh0Xp2o`x* ziGWiH&hoI61)NTBwuj9TFpprhhm{IAQz{Z3mkFN=r9yWGD;Hra2FPR;0#pKIu}T3J z0%Wtf0xSWT$f^WT04A|&0bEj*V+E@bV1-mww}Q~V;5InW#7+@lyHu6SP8H@yq^im6G-2K$Rb^(d(*@WCkjd-< z>;cGPO9XfaAe%V^*awiqY6Um|FoD$xa9FBpa@stc)non2x4k>FA(fqdVEqQ-?z={M+4Q5$#wWnqI}Mr zh_+5|x7RknG|s-Ttju1WKflPHUsaT!orP}SC}Z?&Gg3h<>7_+C`Dc`RpGFUyVF^p$cAsOqloXv6sDNe1bCkT7- z0@}+Qo~3M}pe0&)a%okBhfOku4MNh}tP5LO{M1}y7?p;efm)<<*kofU6^I^*jSo)U zDMrHqux9m0R#`E|okg`#F_wl=1vME>1evwe+32WS$<8pEeBNcSrA8AKydLsAlQj#p zolq8Q5oiaYY}P8!9zr>+O`v^*CNP&kha-R{vSk9HHUyl++yYuT%w-+{Z5&Q!?E+?S zi2Auh;5>mdS*O620%x)10xuCbo2?MoC2$T~DR7s-6WA(&yCv~OQ9aGSFo~hsu*yzj zELG5%vzpthvG~s#j8sMeQKC7akrRsTpjw3!uW3#sphnPo2=Z!U1l1JrHhjzf&?F+5 zF`&v2y&w6PFe7!0detT>>uA`J5n=e&$brC)Q)NX7IwDoo%o=5hZGi;u3q@NJ!eAC( z6gmx6&N}+0Vk}k)qAAdCp96BN#W;q%pudqnMbU+=gUabL*q@^^J|}=9Wu5F70eS_Z zs%Umebxl=1?~JNlIqQ#6v1BXlQv9*BukdeT26A-d(Wqz(oGa$rCfw||s6?t*2Wd<9 zzYEMpMn+F1xmiuZNXJX;TZfE`$r3RjTf&tzL;%x}~P6mPH6^d>RGX zCgr`2Ta-pQ=0D7DWj&7WP(d-n3Q*yz{8(QD{Wyr?{X zW@!<6^8E>wFD}Z*5k!_ZI4O2SB=W(DQ-_UBbu2N-LLF5i)YUe-SdzdpVHZmlNKeS!T*rn8 z9KyK{w^HwBHbF9h)Y8-{Ho*)Pv{0fsRtVZKL9>A7@vz|nMG$ha6oITjb)JqEmMU;G zFuG791d8QcHd3HCpymb#1u9KY;z4QaY;9ws1Rey8^NwtEl7(WNNNi_IQ#~6a_$ev@ z!woi8kVXhASu8!tG7)ytfZ+-D{jhOKmbobSQ&~JE7cqHFr4x9ThRw`)T$;<{lZ;ei zjFo+{63&EO1M#)&npIL-q~%xaMM<$`R%B0^qfTjRb+Yv$q)Z)2RWzu(tUTDV&T|q? zGmd`ZV|C)Y!XB7QlK99?FR5QjO$d@M>yAW9|o`v}}`4~8(wyLS9 zTEt8*B*orMQB`+mX=BG-A|$9f5EAyH8Xe7pAV=hvl8g!*(fp|3!}I)7Ej{*-BUjg`+yu1Vg?+TD8N1$nXPXL>|6z;q)K#E)e@PPumyb|DR z0Zx_kAofjCB4y1I7Ei_CS{V5!7U}zNf-(~wSNYo{+v5;4y!^*`j^W8+DLHm2mbUN} zQLf9$qR!*Fu1gNcb-e&0*A2(aju2xPx0B_H za4-p5L!D|ZHaR(g>Q=oJRJ%<{9zvxh-WQ5q&dVny4;~N44fjJv3#VU#$tNcdCC@ch zO=-2gAiuP9W`4mOCZ9QU&@wPnE69aT{AH}ipBT+jPfCnt4c#D)pa$ng;;y5vlew~{ zqS9VkG{2}cVA>89EIOae95s;KF_-o$6rD=W8ED#IY`dee06o`|nwk04MZ9MddeyKv z^lSW7d)d6wnv$Ag3=#{AXq6#rJ_d~C^UAn*m?VeXiMd?ed~9kgtE^chjOFu6OVRNG zu>{{I*K?ST^r{^6UP#Zyj05z7MJ$ALRN)FDymp0 z7opjXFft)5CHdtw%tByZ`JD2K1?4Q9Kw(hA5avWO&#Nw~ zDlD3Xxr{=VM1;~xif6JgE+F#|GOOM86f$E-!QD`v3!ZALJ9h3dcQWi!<8>kLz0G4typMkO1 zv40oCjS9}RTI>y&xn`D1&W#5|g}^LxIZT9A^k~RsOBJWsV7;RO3lYpx%?T+MdwYwk zp%oL@%u>VMOk-xs6lR&9Kp#Y?9Xp|#n(ONv81Pvf5fqA0+C0v;HCt*q;bgs~GwebN zx{tA(yN{0;yPk1XNr$V6x~7&Dh8Bp;`4SN`>S@l$a-Q{5P;_>CN2`B>cRi>0F!t8r zE3^mf0Iz4bP>x)&Twu5!_S4CcP&+n;w>Nd78!r4Ly&W-IGv?bq+1L%d6nBOu+<>lu z*D)9AG84 zS2WojOv;Ri1wp`7Ov>Ugp5j~V6Rab_?1*WqV4WR;IU1~A*=lcZ_VB#1(@BC_41k%G zJFFSCGu*{Esu5fJwVn3T6dshpNRK);m7C2vn|hfAQ9If6sNL|%tczEvhCP6W`w^tU z*c$F)GgQ7+vxSY4NTz!D!SYoXO&SlE!`SQiMBGf3rB_*@_Og=l*$YrRRo9?JLq$7t z-fVkWeoZlIXeQ0!;-j<(P+4ql_tfKR7dw`8>YQw-L^*Jr0Fp!ShxN(#bhN4b;hbNl zd4OE(XvPrHW2c?*k|Kpr88GXZ8r;ahQxV|>1IY7lv8t@6yke2+=?iOGG)-1VoO?V86P)TLjXv$A}=wB2w-9^OVR&46Ygy=Mx&&b<}Lx0Rb z6;(1BN2@0~VhU`S-NCenK3cL}NP-kX1Pr$yG0UF;&>tHrPK!|bB6pdk zG4W|g8HhI;X+yT9Y2m|o`NGhwg@sRuqeIO4GK8J~vB>Ivj?I{3a(2S4K{(^ACj*PL z+i^(_IgHZgVU{xoQqanY|2-uZc`yEmt|taFP~{fs3j&C2fw@=T5Y%CmS%EL1!^&hFmHPuM~%C^uwhz8@%9W zV4}N3q5G8FsHaT1m$HXviYU;*Cvc*j1F1wBY!IWih0V1)T0D*70FmX4_+(=6#2+S( zQpKG+D2r%()(_=E)K%3*#ddxzlJE47R5XOvYg$N_uh-BwCe~}n#D=4l;YELv#J>}1 z10-X_si4!P!TbxIP~Rc8COhpe7d}N)i&!j`crEh_i92wF=wvCC!WZM{By%>=V;!BS z4oamksuW|JFzk~;NG2BFMY*6>#X_P+3gxN73?w)KJIm{qqKNUDk+PJHka&gDHWYSU zm_<{^4N{skUng7iB#cg_(NZD}WBo*oPNXqX;>8#_u!(kb zB=AWURMk|AwwG<9Q07_1wwNE(ZAH?i3q~+jO%1P|A2yTj4&zBUrh%qqF`;!2$K)oa zG1-__zG;AM4}A!kp9@L`)L`De^B%>$(e!ERHr#pc7&NNvKHe0&ATu9oQc4<5_ozY8 zX$DNN^WQx~?86wV`87bs5G*4ro`&ASY?x>T1X%|IObN%zM~ zx4`YmJ0KPJCjJh9`VmyzmjJYY7zV}W=K)nli6oLV14eQDCxuGkACW6(Kq_t({)WQR z5VAOlisBm0qGpH?_ShRCV}`@V`H&e4>CsRr8&u)f(PV^3Rc2j=8hFO09D^X9?0ywg1Dp=qiBeUfG?#__Q>9b0dZTdF-;G1E z_p?yUqBYC!~WNP?vbFL`CRxQF| zJ5}~;VCzx8sG$tsrPBXKDgUtw&P0$$L5K>2`4|g3tkRzaeY&vB!V_Xes@;ungbh~# zg|$}Ll%}xo(JHweuBYH08D7jt1};k#jGYuzAznrmBJP&qa;Qo9PulbZT%3yQW;mH@ zXLb_KpOp2kO#i|w)8p*^M0Rm)jo+v}ruPr0KZ-hFhSH z9)&dn^>-aQ=pEY8WB*7;Jx5qTH~sA8GM_jv{gH-IEp|aja0{zQR5{!S5U8mZc${_S+r+Bs!F1UmG7vkiQ|;H zNqpElPoRZK&9t^KKaRQp(xN1nwyw<XwbqrTCz47ZIJ@2%8hs-?cEp@H^RE*yRjZzldD53FvFSLV54%(s!eEeokIe4#= ztZi08U;{TN$$^`bWZy;`IcTE|t*BW1ZusQ_Hpj@m%`qYUt-^{`;3E`etO7S8O{MDLg@RUr`<3;t0{1J{dlfjm_e`%*PvO^H6>%d4zn{t85N)!T?Na^>?IRT_HrGQy`?N}{y_-tFKiNyjKiNx2O!ksVo9q>4b+Xsb@XzcP zY7%}@5s`XL_7Z_6d$}$qd&zkGWG_tt2Tt~iDPdZ_MN>yWo9yL+>SQmO115V3`X_q> zkZ-b=Xn~WxdTPD+46hVsZNTRoK}E3$3TuOQJ9%#}#r7Awomr-U3R+e9A-#*U|Ax+~1VH0kIg|P{+vxFb9gc zUv)asPE%+eW^<|b+~Cts+B6Lne`qVJQ4bp?!O6IHZr`qI~GaJfp0~TzzJz&!iJ!z z8ue5T_2*fusTdx&zy^x18Q}|TfdsTGGP95?`U?zHj6=u~Y`X4qfo;tgd!JmszP2{u zC%W&FAxAZaQRfS87@Z{%(_Z7 zNIEg9V6+#B+OGEyGWi6CY!wDc+n|CE8`D-|NgX^%e>ix;)sMVI@{NF`K8H$@h!ixx zU|&NcDX1;O^mOlIA(A#!LeK(-N>boZN%9SWq~1d%ziq!nz#vKT4U%-eKg)0K*IK)P$AXj^1|Wl{LbM^^gGaTG7Tu!|!^WLIB| z4bs?J@t6wzCx&ofh$n-;sP-<*rf*<9_Fpd21ODqSnHV`6&?!5FO5WU`1+7!ouTap) zzh7DZ$iH8)-Xnj#y5YFj4m4~)3r9_Bp*WbLVTv1xKf_H(Ekhaok%=F`pNUNo9(JDK#fOM}$cvrsHtgVQX6FwLr_kC$FuT`l2l54ik;3$1w{>s~Ru45& zg!|&MYlaS|xLHi7@O5HyOGswC7yg(=fh4QDHYm(CV*o8>f0BxO_kJMpb3`r?-03D zm2|1qeuCyEih+Np%>XMc%?&|SY`6yoQSR$uVuSqYK!a)~t(CMIoHjzxPug@bEQaRc z`-vypFTk)9Ds&S5MpL1D7lt>9G6#Prk-G01}=5q)pGmw?wfpzBHC{5tGMB{$NsKVe5?~#iC^k^MxwR7n(3%C?Z|lnX=GH za3#{^VddculPAgKMV{*MnZjR0lOKJQ5Rjh{a^nHWi~<-6Wjq2f7lslj>rnu!8NLr8 zcU-DFl4i)(9GoNVgb;VN1NN4YL*(jO7*-P?S1*U*Qm80$^%fXzB0#P`48#3UECLt) z`xDXE(HM=5>nF00`Zxl#8R~0maGyZF237=CofkZn1vj#H6U2njj`AIMh-{FsH%aF5RK-!6YbmmYJ z&}pwe3GylyD#+U7PlkQ4GQXE)+FKVkn_FJuuVq%y9y^O5`(BnxM0uf1cn+CxB4@Zp zAn_xh_NXQECyFS3XMh&4GNERV`BJd{8!ImVoe8jBdE}pv>fJXA@vm00G*gD5Sp?U|X0z z3T7iw(Tw)Kx>)BNNc5#7{4K1q2;b&38Jp9{wcx@aZ5o8)80iv=zf1~CCj1f9J5W}h z0aCb?l8&K*{vO1rboI`r68Q5rln!gpSkaiTl4(zejCuSZ_X&b?2w}4t7JOVIvVf^uG4n8V%PW zFz9Gxs4-Co`b4E5{&a$a*odIr097l37`zA9WVnV0uRExk{}TIlD0m|*X#7uLH9r6~ z*c_p>IQ(a_+~;J6QDiJM-(-g|XkG*O_#tY#Ddr56 z*$^>K)yF5z12P8BfQ0f$Y2#G=x#3x4hhK}KU!QO;0e%q%ZdXVoAV#qION49M2sWfr z*yxV>3__u|kYuS!+rVidm8#8aqK)_nLuuy(MEJ^~{@N%W($dNTfj12tLoyTa7el6T z$b!J>8Drq|jEEo~5*sqdQ6_{`mn<%_*D%8sBdcIL(l z@r_!{0we;ac8tEMonieAYsJz;5k(cF;G0oF{V!OWh_ja!mEnLX&RSQO6kxG+Rz(%I ztkX?R8Slr{qo&o-aRKeP=?8J)9wai;<gJ zD2+w_VFbR2Gg2-@R`w$?bSt75N@4@496tdR&p&Mz*XQ|gEBd&)faBp53nJ91$HNKB zg!%DsLV7~#@o>T+oa;LtP9zgZs8YqQc^nTXS}4&1j)xP?0-87;PAGy9KORoV3Zxwm zCmandj)xP9|Kh@t|nO!wC-pR*#2Mj1!5i9Sdr z)BJB5u>W}YdC1wRY#<2|lc%vx;0z6$S>$nNz;Qa9@?^X|S-`zT>P*kwf)g9SacUoohutH%@qTV8 zc9^yD+rK<)yWl43#C2epJ_@jUZ!*t8n~xc2^FF~GrVF0-xnDS$f^R{ja8QywkeqZ6 zk|dN8I3swml4>;LRS}0E(BER^Fm17tL?N%ACfz@qje8GLPag5mwc)m3cI$*kE0Zt;}OMA;sVyTbajlH`7=M zA6A*iC(svxIk&2L<#a^VURhlX_}epM`oZCJlLBlP3IU7$!P~?#8cp??omP6acHW=-kHgy8M=13 zGRQLf2Xf4_vIUob1-VorTr$v+QJe`pnL|^*4&&Iz9NjbA;j(HB3Jm<76?j*!y3H}9 zDpa@C7te8BVZW|-!pdCTAh?({A1zIqbi4Hva=xt@kMD?w*O|0U^@C2l+L1i8512`J znyE))GGM9^JrO(g@R@X1D0M}w2D~sJ&VDlKZu476$9Mw{0OA(PdlIM(v9WCOPTH>X zz_(bui;lHFT=a2YvEzOX^?PDo(`w}A?YO0qR*he74)|nf8iVO zcQ!1vKqcJ_pxf}sx#9Q0um_Zfpu%4OI33gEr$U9l1>l0Afxtch+M;(gRBi<3<;&sG zi#U)K9tm&&)O{qD0x$^GMKn~{SBWA}qcC2VX)E2XnQAC8V{#7cCl^EfVKwNIX25oT z{(D`z+=`Cw>5wUca|KZPr2w?Yzl^|g04EG}P(uQp(g!=m9dSXdmF$rtXTh+706EeP z!=+H>ooZNifVd2vBh(bou)Qjs3aOyU(p>p0m=|Jd2zfxh+Xjo+>FPjvN3zV0g7Xym z>)`aYg!^ zphv(@EzlEHdS8L2BWjbsMxH*X3RQzZzno~~;t?=Dgwtg$ z2eZ%{&x6ik!&um_y<-^2^yg0wJX4F>6tzG~nn5dSuS&nceV*V$;qwp*3MF_t45J88 zg6G391Bw?l%>}(?(469qltPS7_nXw$cbda|ckqclrmjFfN8W^sE;t|nY&}#YgKM|okF=wlH(^K{Exv*%~ZB(lWEUN{u;ig%) zL}RhU)d;{WHS^_laO|JBz!}9ed@C5QAym7elC}d-fekxX6`jF9-$nRvvA6eQwF0IZ zosGa2;=mpY%aSI;wpA=HXGsav5?QI|lO>JhW@_#MvI$tI*|#_0=C2$nJd_CN_V80i zInqE4qZ_1lmL{k|omqCe&26HTNPbz|Y?3r|I+<|So(K0BTdAEni7H!58&jka1u%`I zyDM;gDQl7V{1;Ks1HhpuF}cN_l?HGh(KeyiLDzM&oq~i~MJP7<=H0dD;PlfYFu@+v_PufI< zV-ye3Lqn)@au6B&F;r|VdnA!hByHG!?ofq2%6m?r<2l)eG3!$Yrw7+J>Sl^TjN6F;V7=i8n+M!zA;yM0F{VKo(P15@mHIvDZRkSOZ=1S%#-2 zaYZnb%mdfK5&fn3v(OS7-vvs4Vm&Lw(;g@u+76Ccw_WYCVhs-$C1CT6?+I3UtMy}u z7|2m9;HiD+V4+)0U264@8jJs=O_NX-bPX(VELq)%VtOT1x6^|Oz-P}QMNF2}9-jMKfj(bDG&pPf6iQog80XHNE z22dBT+lL(M^Y~`yV+eFaK+M#WZg^Ot4j+yGUSREAi3>1R3m}U}4)Cr7(e;)V*ulFJ z55m#D-<5a=Vtv0WK@R-!yAt(>|i9Kr5k!?S$Pc8G5Qn`(p*TEOOu*mxG=R)~uQa86M- zc_;LGyI~T>@Y>rAKO(^Xw;SmAh>2N!Z#S4B8AjsspiDd{0@F+l$B`_m36?rEY!BS7 z)w`eLkGyY@osNxb?<@F^kVp3YzCs)t<>2=f=u>1t?<)|k*ZT?-z@YaPh;-ch3S{xP z_Z8^l?&Ez0k_~!afk?scE0FEu-&Y{X*xv6e5Gmk&1+o|Xz5>w_^~b-jK=WxfJzgW| z^}Yh-SdT%whl0PaK$(V8Xzk;Dg@?f!)Fa+kn1ioSuU^Z9_Z0{-z5jg$pLhIy1u7gp zof0K`V#+eFcIx z4*B~E1T#29jfM9W2|s>6$p0;%->faj71^-6;RE^ zOTEMfGx5FxRaw#g_Z6s&0{Y)qAVT6e{8@z)v_pzJk-$QH?rmi2o1F?{-xWH?Mvf<)&OmZ-3v1u>D*#*)$P($~q zoqLL9Il9opFaqDOgbl|{KzRrdE2mg7i`uz%#^6&uft~9o!m(u&dflgU?cSiJU-)$ul{;R6r9mJqoIk-;p5fT_zid6fm98yRq{{BS0dcvGX4V9Q0rB z7c=U=N08{;)L^VHh4p7aJ2%y@P|&%ler5gVrur4@eQqk!UR6_y12TBd4;OdawVfpV zwo{f-l^PqPM-uZrTGgqRsXW08a5finkaTFN2IrBS;$%~eV9Rm=<~C_dkGkZ@=B1|I z3r9>c7C!5+NLz|alRW&Tl=-RAN8qAWF2ZYME5$0_g4FO5&}=fV2QUh3YZZ)zf{}vk zrv>Uo0;a*cK0?D^xLPbiG*+&r`;>S&K1Gnmp=9+HZ8$r)TCOzEH_Eh0pLWHKJ!@=G!XU740mm{?1@~r) zMb^OsK7~cI?tnTAg5%$OO_!Wl>RgHHQA!%}G@OaK34e*bx#(@#OuvI31yMHCOvQyo z@q#iXy#yYG`6PM1G>1d z%UmEOE&*{qIvED;WwP(eQzkJgg%D~ZD;G! zi9j2GWT=o#)h4A}_-QJj1!aN@av$WP|_sOnNIb1fRMMYFL!mn2W<$O?JaP^w6m}9>VFX~h#v364@NzcRe zI39T?FS#jkSa0fZvX%%+Gjvw%ST~xO6W|1vk8qp0RwbxcP7BtIAKOEts9DoqZ zPoqp3gyf5Z3Z)0O$TBP7-i-roG|^F|adIIi&jM*WRK#S}VJdcYaI$yYLe5j!JVP#3 z0hQt2a>vI4i%k9~jHW2Kl0~Vwnn!@5uozjwqcA#^qOcU}}kA8F}ef0G3*M(4_ zw75jE8ase&-2u`TsEFHDN9m&&0tSL5W#T-ERLmk?S9uh*>4FzfhQfbP*?q*uMDD*ZAn_tpNRBE&Lz$4tDi{VZvRDNO zH?7yOhyf~r0{z`_flg~w*?k0>T@L#++59wC3s2+>_|I5N+7GoJ-<#|^g=;Zf3jc8< zRffoBZ3?_c4b|5tI8Ti#s*gj~y72z`L^3^6+r;auHwP|N2VJlCy#hT$66^oM*xug>qxo;4$ zx1jc@8K6BXKac$X!$UX*HB!kpP+a|^sMVAu!3T5A*#L9l||^n7V*EEqKrjhbfs|K2%e3!r|V$^GB^+zZc{ zo%+r@@4Rj1l<^^Wdb~JT=7E2l!S^!c3unu*M?vWbKIS_iLwU!7()J_xDCKwrAEjN6 z;G-K?tcT=A)UR9fz- zdsI;Ise9Cew{Z=6z)5?QsHBY65osK>cO8RL#^Z8E9uC^Oiy;~F00oEZQHR)rriX%Y zxE{k=f$7o^XY0|MD&Ew(7Hm8tze(+;>{(qncP$>4+#@V76R?n1vj_2gC z3OK=yDtK~c=^^Q!DQvmT-g434L^mq*mXGdZ9pYd&%JbvwPZZ;Lw{MUYgga|&BhEY) zk2sCPr;#-{>y0Xhu*1ge**Nu$vco_R5TGZ0gcvt;M@a{rDwM!!a73kg5+y3Agj3?E zQUeJRLrH0mGvlbJk<{L)2q(xai$y<;~pf^NmC@NtgPVRJCZ3yMU__}WEq6X;~}*)jjPv5L!FTFkHr&lES?`$ zDHx~2v3P{8>#=yG^2$3FkEA(B#nFD~9q#SnRJ>+@?Xd$@ltI`lXmBVVbsZ{*c*|`r zVu5AfawALvo!@d}$SHbE*@&~htta|0Fdc_i+X#%JRFxOl z@M;@np10 znSDo3!v64vP%2S1wlyxkCq4rd)vXWqwQzD^Z;jvj&?v0A!qx+thFP*rp$rp&!)p># zaH1LZR7T@lgqnAxeAqC$m+`KY@`T1yJKG}Vc`H0$wzu~}^@O&^=Ud?8sBXk2Z7#2@XpkdmSwpF5r5pOp17r9O4aPDNBx$d%0*oIt)PFN z+c?nJ{bfXV`$u4Y?xW-$u3!m1zEVFzv#07vtb zX3ah~nzHt&)f$&?S1iaBY~1K$Lj}JeTNp;`v@sMsN5Mh_2C8~qMw}ahswU1}kEeR{ zBvv9Wp6aI~G8Pxyw$e%%K$tXSPKiWO5&nvef;+1F`6JkZ`kM$qC=zE8IZla2NVtJM z94@{!5`z&LhzmlPWXnpw8 zwZ1lycO0N4<6;zln^D|9ee!U*&IYf2sN+sl-N>lA995U%V&qdg&{J>kv;c5BSzO51 zLm^*lR4qfkHGsGR7bD;Mh`fgjgeUTimRs_XX!)K*GJ#0ONVNwQ^Qa|}su+=jlps?5 zgvfPVL@MmFI=;d58V%fAhTDv);E!A7hh;k>!yAkYY}%DtLI9eqbgOWD)s^vumN&lp zz~devM(hy=I)&iEpJ&ojspCh+ca!~|Zdu7AG*gLr*VdGHW-O~l$lV8WrsN(IG*hd- zi%bguMB!riK5G}0=}b@(Q8EA*o4DuL#3ds+oU)2AmovP70gO3F+HkRfDm@xB7=W$f zfo_#C<2=e6=u&`t9v2(vIz)1C;ZIc$uCG;%I*-yc8D?Wvmqz;`7VvCnsrI2g!-n=A z0KSWhA+0nNe?-f16(c=ELHZ8rmEpcTfZKtKA$<&yB3$?@2Wf{l+!AS?ZiY(pG?6tR z%=7Vxr72C5SZ5aK^9=xfii-_pkSbgdV-hPSLka|4F+e0uLF)>6>@T@9?o(7PGYTFI z@&J9YQ4lLv!X?X4kUV~#K36cpov9tW427U#bzDq_8X;1T5+p;>h*)ry;nc{f%viM} z8pO+J**8>4Y)xrWjasDj2LVPOT#ODK>{Hs2O(|`e(b?^cTRSms%|>z_Wf8Y_X56|N z$+vMas-?(%YV(2HQ+epGJgQYD#l29Wni2`3@!+ov1@{1Y9xg_~!-yQjg@yDm8Nv~a zcdtHUV5%vfDU2|ZDu9-{ytVwI4JS5T6fmSPUfd-E*%|5fxERMijmTh15XY`Y{W!3+_q9su!Ao?kNuR33xtjO>xLAUfgiGxRH#|LG&o3O;?LJaEF^ zEEioqee`!dcH;QN2|kL%Zg}HHBRhL<7gw)6SY6u%fk7?8v9nYPCw0<@OpVfJ+|yE< zT&^}f{I8|A8RRx{k=(!t=rRbr%FSSS^Lse&BWU2J^t_h~LsH|WK-~v%2RwW{N@|V! z%lVw&m&ns*Bf!a>?0{v15b7>Kmc5hW$KtVu(Rh6dPf7{3JSY=eKn9V$`Y$vM{uZrp zJ1!%0WaugeG4=(6l z8s5(fq~p;zqn@PwSrkvj71T;*ND`XhSUK?sp=k*KWM0-)0o;G^&RYunh3&Y8$!L*l zL4TJaG>X0WCrksc=rGPB_<8h)GY(_}i+f2-Czr4CwBA+1K^j+dxm6;2Fc+KN65a0n z!*rXEThjTs(qy{9on(LA(64f{6(hl>2cFTO&q&Z&)tsC6Td2itreG@ta}kJ?hp!k) zb)D*a`+kQqH-4B*8*V$2IN-^tlBW*=a76^Srs)$4#6zR;bb!B25MbZ|aMMbI%R0zHo^975o)(HX(o2FlfuT{O@$ zuK})~K++`#QjKf41QG?DSmA((v5u=I=2itsj!6V{iKD$WOK`a@lNEL`;L!7BBKy9w z+j=>hupZ2^@iBpru98$H779!++(d1+VB=WK3_jP5n=076pPU3(_?<-e$U#7d0?;8V4JY#A z(;fB-oI{0jinVw~5q-H;<2C4|Tw7Ge#)-@ZD^^EjV{(3?$mvx^2b1XhIM8Fi3+Vo^ zw;iO<=@O~PaGn33VYtLKpti}?deIXY@_1ljpN>-B%u3+LHN zr`!Hl9rx!g=q7s{1oz>+h$;APP_jlh#f|gub`w%(CD&6V$w=f_6H)u z7Vy8oVl1G1d}mPKH8*~iY#l}Vbb$wblZ1{f zqmLIQYYbKW31B=b3Je=*se`3cYC2x^(|=oqI+fQyq*FEewJupBH2OD#2uTm@!i`T! z9W@DGMgj`y6Q(dNtZ}Omd=hq?gm6EKWFckIaFj->=32WA*7N^mDCl}LK z_H&)L4Ddxk=*@YM_|TigO2MU>N!U0^=b;!K{X9JUlvi@Yq&ylS@1ZJ{IIS zDx4~^War=xbQ{6KJ;%-TfHuhWNN~74p!jR4YvAr z2qrMzWh(=gk1xj?JUrx~QN2o`7*A6a;V~^o^!;Qc8BM^7Z2aghK-7f$Y6Evg!Uqd< z>VuF8;y5cCMQ}@dwaQM_aHoxd4&@{QXhFKFL_<7h&QNeHsNR}Uy9R-!0Tad{>VScp zeZZkjG_G{O&b3+>#I6fc6M$N3rVJp&gR;Yb8*if!Hn7opM@+LSQ1A z0I7DE2!%kQ9F?_FvQe4Q#)qqD)+=Ngr(PIGXqYbeFzH~{9 zgO9hU)%pcGRBA8J37h;-kqGYyyaUO=Y%Z9@prbPuj@3HR6w1{)`*5`lz8-vyDmAEz zH~6&+gckESQN>o6WB)&A(D1QwhfzO^&1b3}{IS)*OTtjAo?%<0u znHCv5US^jj&ki&iKnMaDfcl8Rq!>GSct9)IGh;EgMkz_%$2rPj=U@_(9?tx`fRp*U zr^<3{+KA=iowHa~ylAfiU{aw1=uHoa^pQJcOiCKBuR3FFVm%VY0m$v=nv2=q8Oes# z-?a!0Xt)T&eftGcG=V0HB8jrraTJMxzR*O3{UB%Tt-c}XcyJRyQ6?Re$0?dwYgf3E zw#M`d&YIUjYnLw%db@~*`Y57ouxDbXXa?MzxD}!x;WM2Hf~4LhMg|O7&LI`9?C2?R zmKQ_Cg$s;|A~lgbdaDyA3#6pTd?Z^`NiAj5snCYV>+2vleh$LnuNbXIkWU)9;ApDE zmYVLq?LtM$BUD|qka-E`#Vi6#z#yTpjf6r)!N7B%!)fx**V*h$E!FO*{6~E zuIi*;$j5N5Et&r6cAYXEIMi@eV6r89Vy@7VnXN{8g(Qb(Y{3)-Be}xE$nGnQ1qPi`<== z*+ZqLlWOwB_%vQ>nJi$E+A^xyaudhdG&xO}(~xgy@_Jr?=0KL7EIV`;gpKR*+W)SQjYb38K-ALz>mT zjEZ+|l2}Sc35J7F6D%e-@=;0eG$WNH-^^Ewr)Us-|#$ovvYJ%suZ^#c`N z4N_JT@ckw45q&5&v@-L27|c*NYF5Qn`I7KgRRvFS4rRxo8XGF`sIHb1%aHph4hZ!m za4sT?lF+%@0rYflh}uSliD%cGP?TA!X89(pmVKI~6X z6lL%XLL}F@F1c3qoO;E=L75#!C>e_ecCJ(;uOaDJo~9HTnfS-!k^D61>&vlr?wnNPi<3Lc^^T`BM3tOh*ot4tSl>8dP&uHtA) zD=w3RLudC8ovLBQ@1##LFHBqh`?^vg8k=7tnxnhzRH$rB2xW88Oj3)=(v_BAl3rAD z?E5HedSK}kwPN>s?8?z`Uw}4FoHuQ}B)w`>losvqNJ`7ZvRs$d-jeo|H-e;PwU46S zD%po)Usltm33I_1va$sG+1V-7OLfcq%gS{?d7>I9iFS~~A~jeVfV%o5UMGcGX6ad_ zy!B{Rbv~SNM}@Nog8+hiPL@A{3#$ovZ=X1tIN1*tH5&pd0$ z{Z=r!gQtjgFOvK#NDfgajFd7lNNq*}=GQ2`avi(k zVf;>;p7of1ldHjVbd(Vi?gow-*lK1(CU^8d-^fsT2ksG}EGUyBIxA_JdwFGO*tL)* zHPq2M7?e#tydX)%R6VqkL<4te-BI2a8IfD^e7&RzP>eaFB#m6r&5SloHM0(6QW1%L zq&ZX6F>7h(AYW(cmy}-_rEFIU_O*~SO)k#me@r!nJ~&>^l|F6~eQ3Xj?aDh5_J0V~ zh!-VeCeIA55n#27_J4l==p;q zxU^gittqe+G46K?cvRMs|H{+%y0kSZXhZ?M&!O!?K{N$}C>Tw_L5IK#T7N0UDkQ)9A4X5LDfxP=I9VhZS-o%OSx&Setf!Ml( zsLK@FP7-_LAwe!#Cw5v?* zbo`nOVDvod@m?Lo5Y#qg0>f>h7ovI{e^gT>`XDOmazq3BeMtt#Sa)N$2t5B9p%^53 z>bQh0Kvb*aR{=>69q-18!Tgj2%5j^`y3kV71f3y55JVGyQ9}d~`6e7S2KA_v|7ixn zn3uR`ASibNMCAQICV#OiP~>&RVil*~W4>&>m>OO;MT|t5S2Us>MIEa)&sPkfNHm6E zsOt+-5#Gy1qmDCd0TExFi3Zed2KpSu$S>&jiQ2V*!HGT$U6j1XlD0Ih+(b_C5~ly>iCm5pHz!Mi|tWA zB1NncO*HH%R~zUn8i9xwpmkQ@Wr&9b#RzW5*;K}6DwfSWo2@md7bE&p_f&N@l8Hp2 z1X0lOFU{omjEl(P@8iW#^ppS+(HuSA+4ljVn|n%r)2mhc#7^RT=WTjrd;$35#yFqu!{2DqTmAx zc2jVO0;UW?{AFd}2gZry0E(T3Y=NfVoC{XaJxaC1q`&w;@L1;Kn z3fSymS5Z>cM$8VWUtcYr+_fgFs&+>J^C9%=~#JFJ; zq*7i61v4qgreGNb5-Tal&c`c&q`rcr$>M{SB9TGZ!+0d%7e=UyQhC!%sq!djJrkNi zxe1V0=twjg%^>KZdq|gYyca2DKI?Nt(@@Uip)Q%_CMkMCZWnf<4{4N!L_fZUwmz9ALkG~9=#`+;*7w8ceIa*xemGH+_q^o`iSrS)lXQ#K z81J!EjoWjRlsqPvkCq_%(pe*166yLN#tot1(JgnP+9|c=9clzErQr&|$)pR$S4c5B z?c-ToZ{lM88uwAd6BK++c^4`8iGtrL5U|YA(jg$)ULG?vfY z_@H5&0%W-}0ze0}|x zp@u=^bi<{nS#-aMSuyxA>kqc?nJBi-6g@hKp^WdBNY&seyozdE2cR-XmYSDIQZl1O zKT_W@vbQmc{f0ACL_;UB3B^W=!SoX?8YWAz{%A3f*#;dIKsW1NWyyxoQj3ma!vRW+ zoXdns#7w||pSZ;r=Gj-Q)mf~WDEjwB-W4YMA!H$95cLsjbYxK?3bQ?M5_B*=XvN&B zWa`rp0$*|{*#O=rlg~1&(cMB9P%g^Z?EGrbi8nO$^G6-1mK0FZn>;2y-Oyw$(-Pzx zz{zS74ezgL1`K-@J{R7ZsiRnBwdhH`kjk0lkCuh-)!?2n?~~V!^7WPU8=wWG$n@k* zYgi0N1qs|53W(ag|7_Ms#}Xb(nQz1EVi|m37@+f)q7xx#KEOezGt?8S5yqf~8-5MC zW$;WYVWHO8X#$1jILfs(W`-2533)3^~~sK(S!d1>P3Kq$}yLZ z_fx4%gDFbDO!GykqW94i5v`hXE;9+);{(;197!`-MV?8}*h42zCe5Wgyho1T=}fb- zleq=7)A4iBU|$6ye-2Fx~=WsZ%obeair@$Oig==jr`r4Vkf48-SPUZN9lubt!G zm91XIhI1sP6y@!VpNxSUEP9_mXsR3@IY!Y>*c7!m8Kf1q)a! z22+c8UqlnhjR1SIa&Q_}Oahv49d%Rj!vk4bdqOubwFbxLu^h8WKxqk~MW|{&A}se) zkRuN?rb1(d`x=_2xccK_t@RTT<7QH@gz{dYU^NAsDcDW{vqZ{STl|-@C4!&N-01G2 zuCvIE;HxvKN?$6*VMLw@O%lYo1pH!fIhYC$EJ>DPa4WHBAbU!$O_u8g^Kh^<)o?}O zVkmkfA;zUrkU@FRQIJi+OBAf6U=sz~DcDQF844KM%IR191&9;B@EFsfOjh9|>S37b z0^5Kln8-v@<+^6114;g3D1z+KWK*Lh@do<=2=75|-Xx5yJ?VfQGo_sm(4#V1a3dH! z$VAlS(`k{IAl3v(i4a6F9=jH#34P#Kt`)r}BBLYpkIq}RBh<&0D<;TeEF?kdT zS2`&yQxmAcOn^hz-WM6T7}#?*6?|nZTlv&Ax@cXdZnWgV^18`lCDKi#`Dh!%vpcO; zd6OZ;S1RB$q#{yYotTdt7B0GX2RRrqK#M_aE5Sn-3yg!L1t=6$#u{ehfky*Fri6&7 zzd``u>*?k)YS7Ax=YxPaSe@sG7g5($NFhao5Pr&9Z>=tY_8a(WG{PZjNeashf%OJ> z58ZOI_?%dW4J1?a8V%FYflU>e5U)f`AuBVA9&cea7o3hZ?}^n}+Z?i4dR+iU5GWy| z<72^-30ugC59V}5kLhAIzz51A=_6%K_EHh!GAMZTBJC)ueXq9E;gt!_hXTf@1@iaM zXbXrZkGVNkzK^p=n*b0Ca7p-VC4dJgh?E`IigiJCyy4Xf*AQHiF33WRvr({!@?NH3 zEd}pT@F4|~SE?XulGiuzD3vwTWX#|cjDZHy6{NRsF3lD_y+y7S*m?%5yhY^vbwg1F zYLHygUP|pwqS05ZjTHQc&;eo%vaz}n#!v2py&pz-3F_3?N4bf13*38G7 z_rEllcwYCCpV)zxc3h(FH+7CAv`;JDNDd1)gUf_x_#OQ7M^R=I;m=oPvz0$1#Xf?K z9A=BQ-^eS%u<#p)nqMnjIgThaS*!uKSP#FNjI68%uugDL17P5nIW`b%p8GTNP21Xf z-aSFKqs(ydSuj0%i5nYG4#~y)j6)2tAk6^Y1R6v>rvCM)gctg7N%}ViF>VkAGh|=v zsR|vPy{PsnuKT!HYh4g-YI5}`XoEm&rXYrbJ`_l*H%#8llQ&(+ar6;uE|V6xFsz2e zgpd%1`{GApup3}M#`On*Cwry~!1uVg+7z@zfUhGU=uSaT3K$07@{7B?f&obkrh&f$ z>V<-OZbVd4xdwQ@u=@!iS}jc3AQ_#Cz3}7c;|E-LFG)g&uZtKLL%{^f6FXsDu6%!s zmL>9brUBpw%HW$+L~rfq0Pz!nVU{}pUyb7GP{2l&ri_Yi&0wXeV?Ggf-#ao|&XS>5 zY)uycR1U7AxL6PR3k3W&1p;1}(0Wo3OhHWw8dAWzR=$|R8zpO+$fnezDM-jDsWXJ) zlsS&OAmcE!q7e!U05b>IQCt@YK8@7f3->&^PzoXtXgg96O+gBC@Y@F&w%0$LX*v-KW>t9p%kFu|I9zIyygM^;P7RIc4KL(irptA88C(B19VNJujM#&F1UO z#y0cxW@DGR2D8yJ$1TdZmeU?_H(JaGja|%#jBU(^jm^wQjP)W<7(*gY8iOKF8NDM< z8x3?kAN~|Py7)=D3i^%xDfz=H$e$(W!*f;Zmq!F zl*`lP+fwOrx!LGFS8Fx~&EcbrU9?@?jX@ZYHwI*|FvJNzf_qbL=SbJ`@SyrMSLzKG zxgW}}sQwwkTi!T_E@FSN2q|B=o$;)k?}YC#Cv>Mcq`G@V*+)W6segi}-iDCjRfHGa z4H%7exw4`Et6bl)|D{~~YwoWb-q301yMOiN7xoS#Jy?I`jW-N%3J9*eiA{=&{v6@P zBTfBNy=FlYzdQ}x=@0eh(R|J2&Bl;9LXQ73zC@287&pZel(zwnh5xH4U_X9MJ(|t& zJU2SGK|X!2`~%|XU_JYZXHrvkI-7i$n*H<#sf?YgSI$|0QmG|*xOzaFjE4G=D(R3v zhaQyk-|S80KFc{yFexc{JkH(2A<^<@zUVhIStGSAdsJX~Cj>IAReC80=&8Nq6ZsjM zhaK`qAECFO3#c3=>3EJ@x2zG#>>KyJlSfRz2eHaI2#<}85miBey1UTHo0f)VJ*fv%&6rw<>2Bi&OoGE+xnXwvNLBp(?kPn0HA0cHR2B>T47 z|0-er4+b06Zt#G?k%KeRM+~OVjE-#FuWRqVc%M0{UDE+gBby?>={Ou$Ii)cUT(`#> zqd^~%l5wQF=b-PC!~E|)Tw$xW`>1e*5RLK=SK;a!m1AVJe(8` zJmt{l38^s!+F%4e%lPEfaDe0xeN3htId&|4NAjOzN_z+P*b|bDKJ7`?=gCRu0LdZx zgy?0C&B&OP_TVuH~ywJBPa}#U~tX)_CX-%*K~>4#*yR zOl8NMm!#3jV{uG)2E*hZvBR$2xTYTy+q8`2Nh2pTPN&0;|2bqI!VWG7xFjcI36Vms z(c`P0@*FmWy(A}0a;VDj^(3w3;Hk;(;}de_B&?{&EBvP=%ef!tlbUV(?BF~(5z7NvUfz>pj(w~Ve*?M?5e#ewNL{Fr#}x~858w#~ zIW9Fr-q%U`TZ{|AqivBy}Mrps|I z7)-mn0S{X`6qSx+cry3YVfgYnO8*t3w|B(`Z%_UwV}GQjav0;oPD1==bRg^ek?imY zycm@pQOCaea^a0f(uawx(-DaO3@Ml>yY}OnegaWu<6xvxkM4RhW@ZRKMAMe(t4xOC zc9p-+2;Pu3f*F%AL4Lf+lK8>l^pt;a!X7gI$8a)~4af2@E-&xl1hX45hkZ$vir5-+ z(vj@YohMU|vS`Rhq5KWpgu_*&qe}tHG5#OiU-|FeWGJ3pqIaSk|AZksIIgd-Ub|h3 zpMPmz;_>s2VTXb1tC1g%(g;JI=d{gogy*@nT5c$-OUdn8)EcUhAA)iKYH#;DYxg_8 z?ZCx5v@4)wf%d4DU$$zda8&!NK=6JQ7Kq5dE54w^&u!fTJ>|Zm|BU_K(Z6A!^m@Z) z1BwYnByldE5TD2!5VJp-DU6#%W17WPJT$6I)d(W$F;K`ks{Kq$P!XNSzr?%q$|H^h z9?wYK;NKF^`5pbsz=6?6xPvS{EhvX&QiK{W7Nkt7!U?lp+5HG+=4gq%W(CzLoe;&=NnaT|ZVP;3HYR1^=ePdak!j zj#+MV-HaIZOg9JUt3Mj!HxIv?++K=HBG+^Cq_q-@y>Jae0)=;||bQ zF=$Zssu~}p9f1lOKQ!_4cR&O=y`-F8GHS?4ka7~F9700~8G)7~7ICbpu!vhhov+|l zN*NNSa=l)1yRAJv%-B3{hW5cfFGqj zBc_^Q5#bZzCW+%4;u0P7(rVo``Z=M$q(|m&`fP(SLwH&6q(`VK&(PG&5wZ=-kT=~a zZ<*nB1Mqp>u)*LtN#js?gW(-2AVuR`u)_fKh6;P!PrIX^)9ybY1$=}bpusRR;8tGJ ziNZqhpbi8-sJo1}7&CK(%YpCXNG(yH9{gATh2!E}owiGka0YdG14RtayJ_3x z2wUAQyYYsQ9O16pe0Qp`!F>~^1sOX*FVs*W38w)=aDVV1NB9(tc>^N5&|1evmm~ZR z0IXP}#Tyg!FSYQ{%#vHUAwn(0E-_&>4M!AgwArBjQQ*kC1(it&eG2$wM7plY_^T?2`Z@GcoK znhxP*WJ$PDOxu#-CT9W*B@;D&G%Rpa#`dG3)F6$mlyceFs9YP)+RIhh*veD|RHZ7Q z9&$UcLOtYm+)aXdoN^gdDwna6t1_r%ssgG~6;C^AguAkdT%bRwhq-qA6Nc>})wYNG zf+YS6?s;BPdC&8&@yLIT|4`2OkUzkKO8BD*Wq-m!?i13+pKv?0Fai6t&-3`9CiwY~ zFXT~HNX`0Tj_FSrHJ=&M`CKkX!i+x1h@#*d?QJcWAXU=%qGR8o=qwExj=D?e5`&_c zSi3?;Q%xP@)oG(m*9dJo*-HdX8>o7gqpJF9+z6psC;fWiZQr@<-E?Z+h8u}$l<7yA z^IiHRoCKJb-a?JK2n} z878MQRY_2c5a~7pq4bQuK2*aKF{@dy8xobeaG zNI=FS;er6zy>sw;oieTq_vDOw!d#qKjNna?)o|^ErG;($SsueWEA@m?vYvV=Jt5;S ze!hSj^QE4U!FuY2o)!>bWGoO@iKwwk>dC=4x^^k`mZ0!% zwCi}{0n*1pp7sLGnH+S_zrQ05o9S$bnyhnEGMooNWElSX!Nwtkmu z9`&_Y|B;>}mk#lry2XeiYnR^JsdcTTlHw3vmkj%Q-8OV1YZGBTI_1-Ya#*)64UtP7 zm&v8JoUj{sx=oY~GS!^zkc9fjA60tDm}Zw56P}r7h_QCYbibFiFp`ziH8pE748PIU zaTuB-iE7DJYl2)<_YPiM<2H&2``CB&|oNfnqm>7fA$P6qiaOC@d8> zP{szaRF+Bb*m?09);v;!dEye1q^08P_#p;dDsC6~i9tD7vI(yvj$C{%ipEVT<3t&$ zi4snG#qVkPbY4795Z$QBUeS(`@R{%}iSjw&8vR@oeimHVKq3v5Q>Uy(*}0+qg){v7 zBvtPVd+|f=!>{~u^7+W^VR2)cjGIia*M!|tIZ9s>6r`X4Zjdp#$^VKU8u3lQVQVPB zEaFd4F6(dyzmF;w@I`XP0u-`}tem=CtKCmhxnEnLm2_f(_H)YkT)RxtnldEqJ}rh{ zpe@1=by=YOL8}nC2yvOn0>xcPd|mq@^$*$>X|J<|qT2l|?nMim4y4=(nHvK#&( z_bQAi(dZ1t`b#uA!(HHvlVuuBmgesQmG`lilj>o4WZ8xNq!4IbC={U{KN(y>e}cw{ zUX6_;CC|J9ryN-}^{iHaM3~@)Qf=1W7=ZS{4VlCW!Hq~(CBV(vqoDmPhn11gx)#GRn<~;(_GF_4?&epnj8(eGS1djUM@C+AJnXcedI(twrUK=IysyQRNF-b zSUfhzL^h`aSe3|fz}5zVYMTMp9OVqQHW;jSB)~>e0Rt;^h{8DWs;m=og2odtaAZLr z_HLOg-a#k~8UieaBsq5wgbsoRql3Xz;DC1!@D3UTlm<}&@Rt4ryl$8%1P#Uh!cZz; z-iTcGe?R|gC_wKQDLf{8z;6@So|T5}PYFx)tMtNp>`ZBdZP-O(TSppN*QzZx2pBJ-hW`W-OKoK&_?`dx2)LMb8%)zP2O+8Ys|K* z%}V>uGc7GN?JQ0yJkO`tii_)+-Y>S5_OBX!!B+a>#E6S=i>G^OcJDs(w83^OA%D#& z)6xT`ogb$ZUhqt@9lJ2z^!~@TTi0*(*kijj+RL)oyx21!S6u)8R}sI@f2V=z^#aq* zf|SB@h7_*{wN39AM7X_Ix-%juxc7GJVn1DWQDI3{3?;wuCd&=8?Wd{MMs8-)iX#}o zU5wze5hFPD(NK)wuGx0?wc0xd;m2d~zO1OWH_by$^YZXY?5g-)z0L9FUgqBBKK(4V z>jjvz!+-h+VF+TwLJ;;1>w<7_*f4}|h0T)6w}h=lq@N}Gl4Z`tn;>PpB|A2(d9r1l z1^M)6$ri#ai~YhZOJc(eC9|H!C*PvNf_4Hn8V1dIR;0+M)k{1YgQkx6{g1mQL z`0MvD{49PZgpXQSVjePIr2WUa1(QCM#<(Mn(@-q9P+imAz<~uzl#BeS+b+T zI#kF2Gl%_63}D6l)g@M_mx7=^u|k7%1P#X_Xfzx_W8#}8#5YZ;v>8uUu$CDV&?TBUCgJrzuf!d5^dYSY5yhjw9ZKWqZ z8@mB4QwWwR2Fsjx2g?+H2@ZOnN^f|7|2%%Sv})@YwXn#XIA1(D*O6sr$0#h5CC93j zjF)4rOGe7EsFHzltX)YwVn@XJfFiTZvlM+ImN5@lW!`Vi@$YB09ero#oKMZBrK%Ft zk`lb#55plPIH}v~tMT&-*VPuYSm?Ccob+GBJ!Y6-Ov00^k0;@taA5FnaL*FlOhOBl zdp@46a8F-jxlJraj zu|_LOwx(norgqws+UZ)-7vF9|URzQ-?Vxtvm?bmLfc@i3gJNw*2S?lq=>j3`LY|84 z^szdSPTReDZMW{U-HY4WV|$l{zb4HWN1z#aWxr&cg1RfbVw7XmN(4F9s^s^lq|&Y> zH|1E{lFNu45vx+?60i7B^oe+-*Q&hMeDC$PLcimeo|WAuTXD=O<}zh9d3Ct0bpDlh zj#?HA?yD33R_3VKFkQyIH4ysARm=`5IZ8?eNN?)YZQGVE`b~m0}T3uXPR4Yn%fDp?dFj` z>nwp~DuAjiNHL#12h~#a(|o9w0u+Aa*V_|6=X_#gpjtHM1WWw`e;5D2v+WxeQK53# zFyOzAe@I`*o-Ja{C>c)bqd8L_5j2M#OZ+8-T9Eo^3H32>ItZzFE$?^GSHZHS^{VyV zV0Tonh3rmo)>=@Lyq5iU%!l#wSKO+1Wv?X=Fzj~cM5Mwc2jp0_l3Y2~tYoVk>sa!Z z9BWas0_44VUOe~aTei~ZZ$EwAnk|^}veK?Ev=y#-Z`*UGmu1LWKn|$RD*Ic1 zU1BSpI&9(#4D#}+J?_~GGp>!iq(a6@uvkoHm0Kw_R;i8u+4$1`TjkB&(cytZ^S(X|Xsp zl0oLG4ys`*opt=HmsQAmRJMJ=DrfZydSELZzJFpggWP|*-%#7t_(}!8VVz=UeBCpw z@-wG~SZ$?~w+~HbkdMER`H`(~U)xVMsC1?QE-Ye|$9Uyjx0PP=xjd0UzWe z+lN8^KJxe~TVbBIN3n{A2P(f#8p>9>r}M|Z+e%wZYFok}AE*-F!&X@MqApWK0}I~b zIq$N{GiM)o-d1{~W#_*b=i4&K52k;RYrFcvtHo2*0g_1dxXdcw{&MGL zTj|eN=BF^o=i4;t2fp$?@`75Kg!Lt9`{%nJn_ZM4k|m-1a3OSDt~eK?PnkvV?Mgi08i}n>Pp+y)$hJ|MdeW9 z{ZFMi(MOEDXDhuIeP|$qeD3(LB-_>5wmyoyGAxEhGr&W`AEeq!YsMr`XMkt-bc?nX zX13SHsDmB>#fLA~Y-%gKIQN_5D&c5+uLiTqe#iIivXy=`y2TDN41^`Txa6~Q8V&6_n)3&G zU%8F7($a?QV(n|CvzKY9bnIdrmP*$B!#u%ba>94UqIBcDR z<&wdi_hHzOSa=@u{nK`vK{92(gmzhr8<|M1;c~={{A~e#?!?qs&~ve$XYS~Q-Z_o$ zMb_R2$4}Y`6;r$>cC*0iv+lX{R1NR=@B6im4gb=*XXlKNX&>L6W@3rxZvqxP_xYKl zNW?An)JiDijxyP5MI81m9;?gqK+|w@p&9}|A zgSkJgI0--LAh7)!h#tSy3B>%`ntRhfu;cijy(8no4h!xE| zgLXX9T7ntK? zP3w!m00nWNm9_AkCe}>|OQa3rb6nze@SOk3z%T3@#I{q%Q$TlGnitewITk-}n7{27 zlkM5kJoM@>xRr^Px%=k*nQXR|c(xdD2`yzjZABmSCKRptR(?^p0nhdSB`*A?HNW@I zKR?*&yZ9NFn0K;i(r@FxU4sNrG(Q^0d5y*Pz%lmQRVko|yoVskMca58&k+qKuLqv# z^ZV_V@r(tYz0J9`_GZR|oQ?L)^R}ZYlhed8a_&ytYIrJeWB2;j@H3X(wXa-w_f|+! z5=%Utn|tHZf}qJr0M~&aCq%YadDJu~4A?(cJMeeg(d<3`vSX1Kbbq)*o3w?N6rcIUP_&bUA`e!8PH<3E;UEE+#HBb`lh z=Lq3nO>SgPV2PNeD~EQm>8m}~jAMIp%B<-JA#UrwZMF{bs)`%!=M{hcrYvron+?EC zjnW&t4;6r%p60}BaL)^^bH+kqa|;_~1&A9vW?I0WBzsoY=o5^zO|e?*)AVNfZW{bB}mmwP8l8-v0)haLeUp(#rB3 zUp688$7iqV+-3LDyVmeuE&IRx@TVWR3m0ay#G9i{HS;$vS&T$ileeHvN{KRkQ6SNd zL@r`ykMm&FzDJ`!v$J#IWfO2ZHGHDX&zpWY4K;JuT>H1zfD_~nH>dV~Ij(Iww*7L? ze2kst73$s~jpoF#eP~I0gHa2zp-z8^8j(}bGh3)>9(+}RBrt-6m~)5u^m@;1EBJ0k zz$FrR$o93Z>w+LT6VfLWLBOKxK2P5af|$}{lO3DUeWar0BPAB?dZo|s zef1e5TJmSSxo@fIhTkm4qHlclQvJC0*Plk>kZn3hGW76s3PpPFo>F)HH?~{fRoXPo z^s;;hjP6Qoj$n5sjOUtTzISDNMT4#U1`+|cg|BD`+Pw1hunvv{qz(2{wSSZe81md_ zlTc~ur?=LF49#H$$0PB(bIYQaePL9d%pxS2RTtp|}3clP$o zO5Pc2wq2`s>8TGeXxW&Z{Q+Y>K4KPT^=oV1_dECNHS0R9j|)F;&GWrK_5Q$d)pacK z-133l-fr>zKqO)o`&Bk4N(Rm2Cr3eq*0g4ywSMxOxqoi=*f*LsPF-Gml&TUuQoO#) zV>UbA<4&F?WXY((v1Bi*q}=R0sL_a4EHUP0)KhbZ{_zSD&_U***3e>0ctLz@b^(|n z-kiT{MW6ulFtYQ8_Z-=Q4CbjmgD{G_*4*hWe(t+(+91!k@FUjTr`s(#+Ws5&9V~Iz z|L*qr8+zp-5u06VPJlhCUt}?@KLEM6K)YFBEiJH?=DxX>x|hEe=hUkFsjT-3&q1Xf zk7oGqefzb6{pS`JM8t-lv+f=E+vLNyyEk}?CG^Yh%%AeYkIRrC{0E!kAy6-^ds+`} zu{`aOVCNQF(bv2rOJB!u8GT<}Qj>{K%i62oXj*^v9hTU+eff{i?l*mb1Sl{R8(M7( z;WYu(VoTw94~v^;SpU4YK3o5st+eB;xMfam^BetuYq2H&)vWhdOsaKcizU1izQmgD zvo%vQ>#;<8-^s$HfgjGLM0U|ot7&~9AjrV=1l-&&_q(*}?h$9}b^8t^l6)`s6faW( zqr<*EyR-hBvULBg*zgkTKGVL`?4_ITj%A5{0|t$`(#E<538KRgYujQFQl_zvXdIgj z%I_?=Z87B*Sniz}&};votF`@XKmTxYoK4-Kfvw&kI$QU< z{W|osxZP_n#fJZ8-JjQJbDh^VtUtpN4fo~^_*YdP-0~afY)-O<7D_x_XvSUlyqtC2 zE5k;=Ib+|qU}@DxlZhz4UlS}+Y}xG=8gMM_P_5e5@UxcP3!@i~%pacdEK6i}>;KNF zx8sK*0gB=Vu%$kxaQ!h?jvkOZaF1?<_}&MZdsKywwJ*T510>cp$?T<(K!cb`x-h-rPX zD_i%X~M%e-kUVJsbeadf`z1`?LgBJ&`HshKaod`n3V6k&^mX(eE4^l(i1LSi)P)P9Rt;0>U{}-hT+QAoqh9(Feo6BsXRyp$syZ*^d7S9>Bk_lI z--ipkRbPJc=eu#?*RA`eug|*n;lXxZEYZJOQmqi^pcSa;{&_9KuN<)z zR*m_!r@8>Ay{Gu;jJWxkiAVap@X_rbV#7aVi zX4wia&$rE&H!j$Y$ed-25_yvv94>9w@M8bi@M3FTu?n_BX z5GBCV4mJj=+S654oBQ`R=hyyoi68i{o?Du+X zMK>?Vj#bR1W^e*-UW=-F61rM2CtNMoIJCfajNn5O!2CjL3Fm{UvIg` zk`kA`#zO09pFjLstbe-kewLV@G}>~wX;LU9u%1?Zk3w@F_)qI@t=nO?-F~}tp%<`{ z{inho$Z0$-kSYJ#k{5hx!2Mn`=e4nhAGhQ!YT_Ndv-$;CZzN{_+T-Gt;iG;=A~yTW zL`ZY_dOALL!mzMZaofsz-Bl*xQ@mZvKdv>^H z?mGRj!X*8B=Px^XImUKmtfuxI7*vIyVY5!%KS}&-*}Z3()w*2w^==Ez$$hu&-#31F zO%s;r`BAqP`W9NN_(Mf+E ztVIzEa8Q0*5*=vh#6&wA z<8iX>Pc`Drwi{c8noGej>f>aj1W$^Q;AMK8Z16TuaMhhlVeW5%mm8UfT*LB>t-O~X zpOq-aeSIKDWny@s-{a;*LB!p@_pe0cyxC?kTYY~jykin~oH_cZIt{R8*`HCxW#c_f z5e1EX_Qg4_zLnj)+S`(`Y&y_C^iU=cR2Sj28F%biTE);ZDCbP+EBKjTxU!SWGH7?r zL2G8)e*DC5Yqq+W!-n*+o=>qA$eocbzO~)jTW|U)wgSlu`|kTxY%>h+yubq%%h}KA zb{_ImY~2%GRQa3eQft*XGKQBHuh{;VeBu`X zjW?{hfg#91!7A{bw%5i8XghIl3WP%W*LjmtABHJ!9^lcW~QFQMRkz#{%AwO%&tg?UxyI<=@=CD|5re;_J*P)$p#n z{?EV--B_aSbJy4P1Rqm&t&6}X}A!CHz@BELdeq{#}6(OX?e<&D)|@UoQ- zslHCHsx!Lv6JC|kd;j|(hw>s{JbBoH<;ecRyZdWhUA^N)mM{hmf8*UsS<#dTEm9Xz ziP$~JOW0|yg;mP%jxG~Uk0-LVAIYUOxxZW?lGFz92e*8e|-a6?gmiW&9 ze(dF^8~>s%(&&`+wmCXeRivcDh+H>~-qTWp&8eEoL6#f9Io z=BF>b)1brTekCk%YRA;A+ma^uAR+H^I+;v}0+!PQ@+xiEy3tm+mTR?0og2FE_~AyP zfp!1yNj(>3A7}+15R1sKue?9IU32eeSmL6oZSwY;b@nNXNcQ~t-cPbEVeqrRJ#exm z){d&5P11Y97z3Q)XXge^i&<3nkK4?(?sl(eZN|nQPqIYYPq*LyZI(|b2S3}9yCgH; zKd+e~qX}@{v1PwHZ?y0|vyb7NZ~D&j&$Ca>?8KDTr`I}M`{MUgZ?nXqKc8Qz+xx*v zRfCz<7r2N;@7zxQ$!1Ysl)GM?L-=alzNI;|>|S*9ug_=hj6ZIn&HBB+Zb_IB84<)1 z?{}ya?)^*hCzQZuz10!%^27o>+{c{v)xqzu?R>Q$%z`~I#atAgrzgCNgL6PH*q{me z@RESHmJDZRXJ78Ib{fUj9Lh z6#F}k+1UDzk+1$g_WnDr=J)>t$4`?eg{*{9MwCRyj2uFTN`;0NAu}t=2t8#bTUKWF zRz}Wgolbj~Es~Pe-X-&Uzpit7cD&x7-`~GKzTfxleS2T;ho0BD*5i8Y$K&$1BsOR* zLnrgAMP~ge;K`5a;J)*gI{|{kFfXJL^8)0vpKp~0DQz0`$$*t4AsDQeC%?y!{d%jT z_TzUp##AKRvS5zFiJmv0%RoL;J2ax7Ug8&A9Et4)Hk8>s(dJX7yO19d6RqR2!KQs|6I`{#;qCbn#XS zk;I4+W6yJp+EeRO$JDKe^fqF71>C+F71Qx!&-fzUgFmk640eNx^@A_k?QN%YvttP; zIDpapFFXm-i^~B(c!h_ASF{pcdMz9T1G?&LR@(3*SdJy(EdUh7h z2Rg>P*@K54OsO47fz85Cewy>*I=7N@$|+iZT4HpYBdEOfBcD5e)s%AfPN;jS9Y~@F z7nA>Un)ap1?}kfAnW`XhfBbQqZ9YuSmCSehM5ZSn;Zx zQgp?l5z?Uu%AJ&&_r`sl;NE@gR3nNNK1T$XJ4}DJmIBTmMkU(YPQrlu%~BWUJy;`Q zZ&THWU+i0CUO%RJtnRQ9DABEcG3eyEPft?6*s`^GYZ0c(`!f{ocKF5mBT=?hRuX7w zUFRF6RCoQrE*#F;IYa2e_w!$zjZ9{k(mD+R6;ANKg*eZinr znZgBt)EL#2!iTIhX=*R{?AtUz!UHpZJj4}%)OeMDH%CKl_+2U}T05w#$a#oUDUfw& zfUvWDE&QS-kdmIzC&XD{IpbA+n>BHb8qadCk!8!|ySL?s7vPUpw`89Y|hY4z@x zRwl*#7!Qrz#g)OSODS+OLF8WYi3bi*Os;?{p#avKs{)kT+sAvdND6soZWdjB?4DiE z208PR9?=sWU{R*<`svsA6gV^2c+bJZddtZ1LQ-^t-TA-P3`gOSQOB(qab)4rFqSP4 z)64X3WTKX$+9xG%yH%xUf4KtK@oZk) z&|x!v!Nl->zgHaSDQjY&t{9J&qUs;Qsm9^9R)l ze|(v7TdA*K++Y^(!Bu;@lMl}w|199XAH60NereVtD|9}&=W+ViGS)?Q@av8*aM1P| zppp;Cg4GR)hbvGi@4;Ql}L7_M7d0hnkrrC z*%Q+sH`W|>i+)F3WH|Te`mnu|5Od+`(Frc=5Z25>#IVd+#E1WmA9r)#fydP+HbhyQ z)bryi-Z)ss&aj4j1E8Sy0QtLmsgVF?k-!UgiE5@;E~}aeyFU%=iohe~a(7u%+1l3> zbECY^)GnV=eQ_L>J$Glu7#!}lq&EdNtMzMo{v#CzA|i<5YQQUF`TQGiPE?!k*Jnc95IO`>+7}#|K_BWm{yw@pV!VZ&lh1SH@ zYxuUpaQ%Z{aHjz%Uw=ivDe&Mk05BtybTcwxl>}&|Lo9~e;ChVTaFxYPpxlP-l-5M| ze|~jXva}Uco=PaW{U~sQIEeyE9>2C~Tu`|$6|SVS<2ed;B}6XfF5cR> z4x1>k<$_feiRf}ghwf=lZPD=tErvT~(t_TTcg%7aNrBwV*M3`8k3ZPzPHEjgN5P=A zLo=21u5Ljbtn>##t7k}D5~3J6!I}Yqc?0Ngc;q%+NDI&AQDByHY*ppRK8sp^kj}7( z>h>-N`Y6N7VPy^XBOAUXE+JPiQ+*P-N|vJIlYH7QyfI-ZRp;Z^e14`JR^EjIkHU{+ zgsQySOm+KwqT5SO+CdOF_uY3R-8|Da`-7U7n&&RM^f=A8X^{tvD&YYImu~73{xPdx zuM-s5W}@7gH=?0kE0>ne6p9>Au;Q$R9q8iW$+tW)RTe90?8eVRij^^n3CT&d^9DLt z6R&Fo_qO1q$*~Uk_?kik zd@>Rdn4K^(d!wEluUx^@C8-zttDlnEO(0lj_(<#~g8g019@fVAjJGmL;0u(mJ5Cz< z%D*=S^u|xU_jbAZ2LOaVIUy{H+;<1TVYPeKTot_@#w@x*xi~$HAc1Av(Q)Y!8^T-AusobqA3_Iz1W6gA3~8G>WH zgYY%DZ+bqOl`ew|p+IT#TuRg6(h@E#|AZK1rO8*;N_UM(Uur~l%rYt|Kgf0T1#cBf z!Kd2R&)(T<`C^x!`OxrPlNtq-A`o0^NF~-|WG1c2`;{GYPJ{63bYY z1GPNk<2pLen35kSa=H&2Kbmrx`==T;Pal?t^A0R0?Pz7`p}4Uwtt>}+r1nDLr{(>b z(tX<5*6ciq;m8<{da{w>i^C!sZ=XCz_b$563|E)WPJJlwfaAILo=t9mWFzOX)8siI zf~R7~=w>(}GcI=x#j&zt9AP*)19fpznk?hgRE}QsnCaAm5BFKz%Qam`I&!ZCQNaI( z>B;un{+tH@G0t7GtKc_tg=H%~g)rhXN4sxgFAA~R@A^Y#%a3)qnd7W~xVFs7q?8|< zq&n!-pnx#wOaKByKAS0M*uDioZnhgcU3-V96W9BjFm;Q?6MM0nN_570FS4nYadTW9 zoZnUo!amQ_jLwoTBsGO6arPFcM#MD6y2a};n~@>8Jo@(=^SGN^^; z<{_myj>7nJeTFlo;d}JfvQ{Z#;n5(>2Ue;4L)>qN*N5Dt;zg?6ssU>PE2dLGxnFEn zQS(M@vLv*jbt{WNBJFr_z9aAcz|mJ9EjS8=i9LE+k>IpMf)_q_;qN_b8T`uHB!@4w z9csJPY)}Ud1?pmZ9=mv$A%7tfyfb_DvjwLe?>{qhDZAMm+N+mLLoqhP?1_C1ZpUU*$8 zD<@P)<&KY`2W=X5HF;1`qtBohGmIzhR;GZX>&S|C(QBM#<&Glw^o)YXHZUo5Ym>*w z`1BxZ=JflG)x)Rv#(3V1g1Ke5rSR$5CR=@JdA(>J1^Ab39}U=Xa18b7)xxJoDY>J) zDB;Z!RRrF6@7OOB$_lj4{ji+i6v9ugXLvjbSbf>j z@^AtKwaf!b(V$spFv-}u%B8(v538P^4r5!0ltnVx#Wo=2#PzjKuy-e#uFlD4q2|cC zzV0>*4>oc+I^-EE`oY0r?}^jpX1|ZV#0|T7ekWCP>YK*(R(ezvGfQgWQoD}amw4{m zc#$7>=&Wc2Vz#5<fc|-wzs-xp+e_$tE5LMI6XMj&~suaBH&Ui1%@uqZv`3J?4dx+#jTe# zD~Cn^;HhhjJ;(|2+XT+{edS0&jKj$c4eaY5rgEo~Ww4K(TUZKy0Mp~irho29!PHhW)Ze01u|;-OR2JCi`r(%o!4 ztgWC}@t2>H@>6+ZmTEu+_(~n6gPa2<^Mq z^xMIdAPB6j9zV+c-U4$vLJgNa^MA`be9@W$zo#m_UwiTzWWN*=2CF)PL>tmW(s77; zeRI)nCM9@kQlZSrgNWyNRs&s_rmtgG`7lj0dqv*CbWGg|-Cy3TgCA4vlg~v)uQc48 z$!#_#_mdyHVy1Yzp3XXbO6D*7tB1>SN*{ zoC7gnj29{RqItkGUW3JVR+J|u4Di}S!K{mC-_0%b<;|t(B$Xy%8n|mmxw^_gRrW$s z2lRGD{Ma-6HLu%D12j+jdl*bnkF=0YClM$gyPt&FY#n2C@_%L%m?@pjl#1O0F&od) zStOlAktg&Spa?V(oB1fVU|1FlY5ME^Q#e^D2HDjUR8vC7-=~Iu#Sa_#s7aXyjju82u%w3_ndBAXnDLZ0OEx9 zkVM8$JtIq`5E000DpHTkvQUpq;|HacFU7xj@tTYHBvLo8+?jm9LNfvJS!e=%&zBTnFVM-_(l*kjV{l~c7svJp|5Em=) zqF#IVIt5$alhFy^u?+T_5vrS^GfH~RZuHB9_EmQybly6oENNQPM2i*AWYI|)bRl>h z5QFsbZ=ZL*iNWu&$~ujHulp&zbs-zJkHf9os1%${Z=D0=!G=k9PQ#*`A$}PQjAvym z8nifMJ0Yo-LXW-s&YwJ@@s4kjV=27gc|a7?IIw^M1=pVyzc`cr9RM!g2*`uUE!FW! z;!UgKlfpn{+Lz|H68rW%f#INguuXpRpIBimJ4u>a75+^+N!MU&BTRCA-H1PQk|fTH zqkh5?tnsGS`SIw;UP}_TlXFUd`wvE{5JJ25tUKK6?w=}#0>&Ne>mTcl zZ4ZDLAQ9*#nPdw)Nz>LzY7o8_s^8}|q=(z;o6Kdmgw{-%YKmzR=|QlpYAqTSpUH$H zLV6%7{n=^rQ<=JDA1v-k-CIvTLQ`oZ;mFVUg3FUvkO0|1u?uwi8)d3KZ3Lt33yh|M-Icz!)S9zWDh!@;Hj#_?$NRns*S*TPy%%Pps0g=M zf8`77nd+UrL+xdP+fg4N_k0;0LA@Un%`}f6xO3&yA#jVKUu1mgBKU^wqYi|=szuJ_ z28TxW7{H@5?!oMjW3a=!jZ9rP(Vk%z>Q59qlcX7>msW3$L-I<~Q#+-NZL*8_3)N;O z8j_pF7loKUQ!TVGhLsojrN`d=?9j3$=OhK>!uQJ!GB4(n{8G7Ek_T!ZwI_0l5IcIH zzJ=9{G5w5Hxs zo9S)v#pYjo0*`o2A4((dy_*}$no6;+)}cPMf=WUC%ekw1W47&w60 zgY#myije5sW35|dus-(%KANPuu(u7C%=2*M*@{jKGzwu->ksHmWd%d=SMb2xphr|V z6wJ0fI@RZSjlY#iDqmpj;V?*adMtu908E!JDax9W4qH>IrCbZo$%9zoF3z`?gB9+W zZO9Ku>JO83C=3)64R5KiiiwSA8>aop^#5o(j0ZjR&rYZ(`1V#E*mn-$MDX|{~()@%Iu zdrs?*Q^5Cf-PUo}1#}d7DMjg=e^wW@6zs>ERXA=)&Qn6n)q7v1Y23hYhxrAy@DY<mj3Qsh&N})Mom00H41Pl(iFH8?jD_5$_UPpJvU3#6mq3*Am~K^8WCesqK7pqB+HI z>xJ>9=fLsoqqKv}w=fB{_5~4AGR9=6;Szy_X*FazGIbB{RF+9e7zs=q6XT22)x?o8 z@s`*f_8^F?khx+bWy7M{PP!Qem1Z$iAl_krEs*>FBN19`3UrfR!9^UJ&hb zZKs(5kfzS{i=B&~WaWOc;pr8fu{NgT~cQJEby! zPLL??=zFGe-&p)}L-?ahvin8D(^tDm`!*K(3GvaQ_N&fJA40CJwR&7U>s?-^lYfv~ zn=xWDYpR0T0ds4#blWC&ir#;FkK# z)F9qvqVjQ|cL?r8-byxdZ1OZ?Wmah3b7_Zhr>yRAk139vNG^v3WD-GPV)zIMW#*Ba zVTo%KF9`)oE8al14}TqZY=9~nMTK)*3ti*{D+wOUajXw0=0z2a9-o4s=Bm1RY?>vg zP{yc%6xR&j4&TGn4Zgd32Y;cWhOJ;y;}}1qX)3cL*_mM}Kg!8cCVyW0m+RTBaUd^P zljDSC@a5tKI+I@=GkR+Nqb;BW()TQR^1Wd7j9hNsC;yAGzbf1%F;)J27>tuhtC$sGJk4ux7~>iBr@es zKW9WDH2thHZkn-BbvTTxa%q-wd5Par4xo(Eu6XsGVIa_sqJ|a~d8T>h>4X{?S_Ar! zE1FNy-!H#^In&(K{cbKQVwic5@qd_GMnb0DxN(RZ4lo6x3@t=Um|4y`fpq+{Ng7)* zzTkC33nV-ZMa;kfP6_5V(=>YC7;D@!S(8{N6qA8r2DF?1@cb(1Y}D2`+|9HAA8D>Pn@UF0988#d`R7YRnAG)Gq0I=g}iP9>P#W4#YI$m|R;J0cV$^FHN=%^lDVU5WS5 zC6rznzv=wZWPc6q_B4cf_p-upn0f0IfWXV6(xFc_3EJFSZ?>d^GiCXZAN5;}` z5A!49cDi0)+YyGQTf@E}IJnL1MQKtjsaHT~(nCESKC0Pl%oUQphNwViAC-AW6)4kF zk$aDo-hpBnC8(MOl(F_Moz2vJ+WFJ~^EB0%XCHMuYyuLuciY@{WKoPHRdj`A5Z@Q5 zt(n8raeYmL@jcEi(IKw+eC!*hiu26EPbO=Pn30>a=$#pNeCz?HX@$I=0WPSiJGr2j z{`QiG9^U(1&sdx`dqJtmdXGLP{3wG4VCSIK{sU!En987Eozaz` z@V$YM1QO|}K24o(T#2FqBd3r`w=o$RPoOw$^KuH>F3 zIt9E(v*gLfH|R>PDKtjK2iZ)j?hx&rGOUV-#f8HtJ^zq2w2Eo&Z7uqOjWHZU&%nCK z4K-^kKb+bC9kulgBthG&rb{4*oyBRQ4(9~W zBj{#d_ZzBtvso-gBG;4`mG`bUq8aPBGgrf#&}M`q)F_XPJcn>P4+1Njup?Wug-wvJOouPpGel+FwY14%Ym=XR zQFO3f)oF)c!znOYv5s(@LY+Kfet8xA&My;_`v&&f;?2Qevr-{Kn1s|9*r&%a?Da7IF>3 zyp}Ucup87&Nn2lq#j^HV%DfEt*d)Td(zO=op;@yoXf!p2I+3f`*Z$>gpqZQQ4tU*K zS*9etZi^2(5-bzP9_FHX!`UN8oNgU>&ZQwl!xd&kEV_q#+@=3DV0IYdZbQ+G-JSNpwznm9*fSg|i=sq+&6rBO$K%U--#;j-!;Aj2 zR;L!{-C$Tls5yJMG79YRQNTe}AA%J2yyyo@i*Q}a&uuXXlMi2LH12ge%-bZ751F+7p_iXc@QhM5VrF z!TmjalX}ZIJN~ji;R+LxrU#(sm(UI_I;&;_pr%wUM8cBNNjtX6;)(=+hFp~Wx8&bI zQLoY*7QL)@{rhL2==9S2?4LbX%Kka&<5r;PpyXc^M}yd}^cmT&l7CYi^`;09{6v-kRzCM~MKsb70($DId>`0>)rJL zwegm#09eVDc~CLkkry+j&T|k1m`V)`60^t(>IA{FUVg-ne&UNB?|iWO^I2b*;D7N& z2BRN|&fQz|ivkyXfAriqqRU+bY;jC9b(EHEd zzbPC}VyoZFW6xrU&EJa7euSm6EqI@#K&5RbJ_mw$XZ*Afs8I7j`_*?sN8wP?>ZxXq z`@h$0@+8@dOwKa%=sUt|EQQtePeqjxN7bnhsj<#-sq(b%vnZgfWFDf~{KvVmKzr5>G9>LpW;M@U7BC)~#^E6|8x?WBGc@EQD8w^726IZ--R^wc1?j zL)Zt^YQ9|%Q*xvWSL5yqv(FrqX?!t?cf%8WLQ@nc0Tv(P`ZZ)cJ+fB!nOL82t`-T zJQ9V6vc`m?m}vIS?X01IL=|k7lqdeA{__D-o2Rp;wTtmsZISb7gLDOUTu(g6Lh-Q1_cI=GQlc~S09XISA{JLI%K zhHJ@QYeGaa+AE+Jxz^#25=NmZ`jrVm7ZZ&BZRar^GDOAP{IRl9Hzhrt714``9$O#f znW5315N}3AkLYb>1?!peB=cuQxICofDJciQWJcY>=a5GdI)yRyYph@9SqE!t$Ib~z zSmR9#b)&OwT7am^vdTw5R6r*iO7_ln~mjG;;+S5yI(}Jbnil?nZag;ytsa6 zwLqU!3a*WUeAdb`#9s)x$oV-Ly@)mB1ZSoT8b8nM8M81oo~YY;o*_8Ua<3r)ATq3i zOUBCG-UYq>;;-2^D#xSId>B1$7MUR~f!~&O2XG0xY#)aVur~TiFG0``^dn59@Ofb; zH0qsd5!_>j46sDdHBy3?AN17-B>(h{dk8Tba6s{FNK%Xx%u}4_q04750;6|5HQf zW6A7RktOFIaguJ;+)j1D_-Kw=su>wgbs{lj%D*n}!_>v!H+#*FMu=>}MmpNhrB`pE zKuPb}N7>O}%khj_jSa5Y>o`WnpU7mr^~6BLjPvBXpAQ-zXV^X6I}Q8Ji@h2{2GMyZPbT614Kt8zr!jSe+|LoF3c59`9JBHpJ=9=!QiJ*qo_;Rt`y z4w*X{&G&C}dz0a`JMo-t1&?CJ_fujHrR`!#D5Ao-R)mChO82_BGOlG|W4^4j-)vP&q~xVu*zh z(^+SgUT3hKp4Qh<<)*a+!P8IZn&o35-SQm+&cH;}aCJD;lMsQS{c0%?t?M^*9 zll!f@vg;c(1t))*C&@uOQATtquI0UFp-XV%LRTKs#YJHiS8`D@P2sNM(>KQ_=tPq0x5~)3S@5?88|mA3!Mo;O1V%6!qRO1Yw=w&Fu7%9;9)I1 z0eGV6 zK3H~vsaq}o_f`@QSFQUzJK@W*`r8?C=E=B5)_Y9Y@x`ZmLWdR6cVb}Jv5hKa$OAsM@W?>x~i{hXyxrNy1MADD!D-7lhq ze?r^Sl6CM=LvZ<1(>Q>sd+WM8f^IA5vO^qaTWSfDvZaIa2MAKFDl3GYoGp7PFHS4! zy#)c+9rH($K}<)v6GTx7?S=bQzN)*u3wD!56e>`h36yTg_dSPHu_0j_2hU;=g%?|S zC}%2E+PGEAkmA?oVxot5`wzO3L%h1pw>KJXf$kTwmuACAZXBE(?`#^m^2PcZ;6+$JDjfA%NyF%$kV_m)d5rRgo+@0)w7)upn*$Ep8oMRaAypaiD1(bVZ zet0cHgWd+6p4ZkewL5d~uO&l~%K*HZlwH#Sc(vo;);jDXYyP0tyaX2vMer&=XFHKP zV|i^!n84wAU~2E{I7%Np6K}Hw8m!=tIhmUF)A@_J8}lwK$2j`R;D#Mm zUk_pu9$s?%$znQje#CaBr=xgr<21O{lqN&Crl(^ktb2d7Gb6sd%IGR9Ac3=|9|yaI zI;1n%HifBm-Y>|*0EV5M;e&DCia~%lH6Lkp!gHH7g--Pv2;RZ7EgO{49%7Iaz}9}rVu=cGtMXKXc)xL zb@k3<*Zy_JeZGsXMEopy6%U5d^yBo0dt}$R91@Z@cbyOw??H4Y z!VW!gK(b3!1;n7vX5A^8Ha9-NG;61+en<3vtH2^ICNwEMq6K4-E0KM80=W_^^~ZN2 zV-XhW5U1O_(-)?xXwBbRR&W=8Ayyu-5VM^O5b-9;cBmA3@jwPYzhG5uzb&A|i-kqG>L z3>k%uM{6exnT~q|LT@K@T(;X6w&>C|`B<0ac^k+`=_lZi!67#WD7-~b(ukKh9eR=M zgzXOX>54eL&LgIXs@Xx&9nqo7T*eIb|5 zsGYXI?US8BM#B^PY`ggZgiKja0qU+T@FzAY87SNfx+ewaQX4kPz_%tX`Sk5zQ5xotmZJggw~ zPd7io8MO(Igdt_RZN_9^ZoAy=txvSiiyCTSTYAQ8jhb-KEDHR1WS+w3s!n3fZIX%| zEB|qPA*I$%u+8b&Nu?Y67qr&7R2%pi24;j(=}pSaI#qjQ1C5?)7rM{5(V+^4cEnY^ z=m@63OW`T%KGD)A`G{PTUmD@HuIc)|;oU=SnO1X95PyZ1Qk=%2CO z^dz+al1#O2G(+XRX<>kN+wO{=ttk`{0C_JiuuaitS5U|1DR{WFemaL1d~+hwtESs-gc5Hf+;dvg_ASf_mRoR4n z!8WKI_r{8pk|e35s7*<>fXKSCFV8@;0h5~=)d#kOst@|N{x>hc>p1p+Fu8YJyZvZB zJbgF;dqCcwDMaKb*#ifGUE}_H+|Mqzs-$~BqTn9gqgxQyfBcugkTNAVT4iF(+-Zu; zo!})DNSk!JrrWfGDDO~1w!od;0+8f6Cz3_5=DRwbJqvVa;S3a!6L!`?f^`(FQ57`wL;Hp@hrDd z0Jc>;q1@*CFgv{KmbQA`Yee!PD*P*~#gd~p^=&Za-mL0qCDoD1vd^88oV=*saYnP{ z1teeNu>Eh@ROrS=pjaS);GmLUuNsF1{O`#-3gwp=>m$smS*$D-IGOZ{C}4Vefw>wum@K&Lm`djB`|N~ zz=j&nJz%k0nOmyct{>imQDh4n@#4m(%~%HniS4y|GVqc0+iW8d1q3%XHdw7hW7i`_ z3v|bn7w0aX{t+EMzDokiwWZw>l{Lrzv$U7d$@T^4H6c*c8Py_2w7x%s?fw7$^Z()< zfW!`Ss%2n>4nHW1_%Pr0m{G`7tygdr%1BuxA=isU>%xbbxJ6;`$e2fesoU~H-@dBW zV+`df5W6*Yz_y*IeiI)isuZ}ne+!qFZ)kkp!cIS2D666pP|6|NoToB^hRuG%OSMKuk(XakWpx9^2}D? z`*>cZk(^U%6noW(Y0HgVHH)NMm$=^bj{@*$jG~h*N;FNhNonTheqWu>G7`vmwULyZ zM8Dg3_tmz+YZPgvgfZ(5hTCteK{auZe)*kyKN&g%hzVd2&vS9&{e2}FpJ;vIi5Uyw zuN{2hx(I?3tMw5&n)`+rU5VCb^!jffG*iVd$HSk2=QVh9?C?wgHyBK);qjhbk+}3y zLmQKo@BrKL$x=9L;Ip2qLz{K@CRvuk$uU<8w=@oorhv-Rt5KS(3$RC7)&@n+P^4OC zY9qL4+armE=l|3MfJL+ zI0PXyPgbPUxzXHUIs`^*Vj8YErq@^u1O|a^+pU zrUwC1okjjvnf@=$Gv`HR4cT0cwQcoz*i&``NRUWI1c~S*8=$8-{v?{oryTdPZg3hU zbJI-L*((V)vlUeDSLv#xG+^&}_WHoA-Vj0Vn)AL&BX03qcLy4!LP*NP}|hlfDpsJ!M%x^Fb_3Y;uc4qG+>&({O$>d z8*AkwT6iF-q}Ie2{tg;C*!M=QoE53R(s${iS;b>}9-x3$@1}!GS9SLRfN8+K)knkR49P0*v*fdqgf#!HfBfOw-XC=lxLHxtkXuW?z z6{u5pR(t7v*`D= zvmkKhc0s-xG8=Bco3W5-PE8rO1&D&A4Op-uJSTL71&en27@b^R@jBk%`BNm=CU|HQdyVpCnpz3P&joRCnvr)#8Vq$*KNr1>L^Q`^lkPH3{R)Mr!`1eCXQA zCqgrk8_`-^M*Nu?iX1fwTjfWaZn(^St?P5d+T=YyT6Z8*rT*9E8U=#Bh~B;_oG=;y zslVoe-25LukoX*gO#NcK38;1EH>@}{H=jnJhDw`mKy9oKXW-XSGw|{Aj+I`QTrbmi z09UVHt~f9HOezI_y5IU`)+-gEtKShIMjR}N0_q`La1=(6$>tl{$y$%7mhAkUQbu9= z@L5(bnwHzsC)b{ouD;^_c0w)%?mJ}l+-K-`4uCa^$0Dtq_xB|g1ITpun7?!+x$YWU zh`r}uRi>k{WV%|%FP+x2j6!Jw)Q=w0?S|dg<2QCufVb~{eA4?atEJPmL-EN&-+QA# z<__~^NClHx(};0QEtMjsNN-clW?{;-A5=HEx$Kf%9+|Vs8WB2jTHG>Ze+NunYd0onJlc50iOKKP?D zKFay<=_GW$VT&!p=1T=QUaXUmqc^C<;_FkD+h!vTr;L3e{ef|RzaXWc@$yPz?-{{J zZi=mb;{y*~Q0!k_0_y9a`<=`PL=Fl1OGlW!a{e%; zX|l_@r`ACzc|;st^#KZoK=BUSx4L8dS*k|1Kdt327DJ?8?U^H8B7EpZ5q(%4@_<~bBMnXZ7^qCf%Iqx9Y2WBf~NApzo*O$G9&N7Vomc@ zGG}1Avx{E-lqHGOgP>9>UDGt9L*%3CS@+J*3NplQ*hh6Vbos>8xzuhnWYb~Enrz?G zHEkMn4ld2GzB%63L8ibyh%$FpT!VO3y0@E;7B-I+{Rs}G>pNf|BKqqZ-?)G#LTgUo ztROSwNV7q*{6{X(^uDDl+l0vOJvFNZ5O)^YdsVyNFoM_UcV6KLNk~ypIo0P9SN{60 zfvjs1$@A48K}KQWEQX2HxbnY#RVr)`v_$vL^sC1+15T|M|em7I)@KG;QMn(p|%ZZ#u zaWlbW-AJbC+L#|YR>26d6g*1W{FA%u+>sgVY@r93IAgL%_TE&@ed4*i257Iy58SC= z1RB&{Fwx@P4kOJuzB^cd3D!3R9-MgYr$Ilia*K)|Sy;;vHC7N17CRA zoZRA#5OG)?j^KYr#xX4B8FZJ?hxHgVvpcSP@gH{rPZ&AE(H|%2kQY7*Kfvw=`GNHL zL^CHRxp(<0cQhAsE^nbisbFD=E0m)6$9o!{$x3eI{L^}_psXdjLeMfvZQnAm7MKU7 z9gV6j2DxCDub)KN-;cYmWZ$rlXKHi4C5_VPPC`AgJrfVTU zivEtr_YtH3my4d>K{OOX)9)FJ|0xMe&NI_uF6M$_;77WdcubFJ=}PN=#}w~ZsHQw)5gjjVD93Bk%BgJ&!3G-&5BMyxo-1_h^OWS%Y_ROS(^F3?e2 zYIB!oWg_MadcSCXdi&5TJhB8J_m-E6*~FbFt}4d)@n8#1pbv1&5xzJfznl@94|wj! zT76+uO79v&e==sh`|FMghu)jg^8KYbOG-*6E=3(|6!ve}bjDLR;xPi_CAIu5$0k6C zmVG`IuL;)bPwA}Wb;MvYYx+_4Qgf78a|A03bB=#LpnQftXYljTcq^@T$}sHWp@Xpj zL!AEZrb@b^S z<9|JLHwD}mI|ux(3cp4G-8A;SLCLxxh7n!4Z<5w#rs`Zp38@0q>aiwPK!#L?hpv3a zJ7Ko-TTUERkC$%jlDFex{6Gpc{Sc0Nw>chfYvtg*JZQ@QE=G}fgQydE_`{GC7)Z7b z{s~2~%~?h<`r$s@1mZ+KRrGlin$D-=M}W7ZOhaj1J5o7}Vyd0cDLLdgQ(9SXeu>=# zA}+3YO3_NFeKz-CkN8oj6^^RVcXrL@b~rR}5(SzR4XgXi(tz41EfFojR7auuS*M4L zxbROyKlT@iykAMmL*p%n%7rB(cbK>Y(&rmg7gac2o;FmU0(-ybjMDX6b6xtD!~cP& zbA_h``pHaf&$4jSf23Q+o=!J*MC^&@S?dn#QN`9;%cwl%hrv7wESP-fc!#}0R5Fwn z>iy@X<~B@iy%_MhDdMrp|6r;;Ph^=m2R7K|{c^GKpgydLk|?9ONeKi~Eu&VLtod?m zGyf}})HpR&{XN|NYK$EPY|mVrEdJ1lhiqR1Q|GlJYU`*#R7L&&AgaCa`BT?hOzFnq zN4~LFpX{hh7~Rn*RaZm3@-53PsWy<`(BX^R&m1EP9GH0gVp+fyTA^AuZC>jOLoA~5 z=Gx(nkE=(zK+bEZ+w}-o#L9WZJeTc6tf!ThLdzk;ynXI{Lt(mXOW{K6aO2X+=hG>$ zy4S^tp%uPv0N@dEMg`=5ZXQKe>a|N;GJ|;SCRZ8vmX%hhAj)h4vJyd57-o9|&8bU* zBM^Xz*KR6(WR@c9TtUoEy{|@YJD0&=)mA6pgS=*fM~M0+?|V7LJq;8P?+$%mPe@mZ~mT30pm3bcMtLn-UomXRpJ_K;E+p~cEYqfuWCw_5m(GvdK04IBmcMV4HF?(d_T}kja``oJ6j(Pq zz_#Y_X1q94i}W-|j$|e>Z_)i<$clHD`b@dhw&4zJNzZ7f;*;2q0eVRQLqB`L98}(wS|4cEUqGzh~@s7<9O;b)hSR!70Hq_HEpOV&-pOJ`DK zg{QrNvQ$e^_1H62@q^Xicfl0rw7Be#pSk)H1zz;rUc_1GIa6A!nZ;Fsk>+O`Zg-WZ z4lFi-MUU@8u}O5vtv$2%q_~kj;}E$$d*nD(25$Nn~)tld`8-;VCM@N~-3MRIuI7Xq41^Wrc;%P6>m0sK6D z%QElWXq=_w6-WuDw*EMMf$INa|Nmm@YzP3+R}GccK{(Ulw!nejmY_;4TFDD=tjM8$ zaeUNETldEZLO~97UFGNIj&&<^DIi#Tt%J**LOB3dLHV$!xJWRybtY2&51tm8&c0VQ zTVQSP(p%}t8Pm2`QIUM4SS49?H`5U_1-2cY(}jMt_R!1Su7XTMton<}z{h^Z3aEqo zhKMhCOIz|_6^p0H;WSfx@`p)TtWt7JMny#sSxy*v)eAYC33m(AS_Uc<(zH$MLoTmV zavV{d89AI8=gj(!E)JgOV z@svgIOiis${{&NjsXLpQB^R_JiTi$GUnF=)u@XMM0$P_T^mhF!?xktFgv9S4P#XQ{ zs*-I}pLwin70PqEfmr{Jkmn4*HSkJR;E>pZ(iI9E zQl)J1J9$nCZ0}x(MVXI(pEW>%2k_midqX9fPf@~J#$0wkPXd*nHD z(T1Ou=Y*4z@*GQiAGcMWa|_=kbyeW>13XGWfzu81sWMjJ zEJoF$%a7zaNKV5{d5$vLZGIR8 zzw3NPfinl~y$cGQ7|e6yDg{mhD*g3XBF}k?enwN}IkN!Iyd}?x!2CjwD{$h_diyAF zuAzN>LY{L7KjpSVfzubi^QKUqa{#R-)=`B0Fn^@L$wj*u`?d$|R=zyP3*XyzSKxTy zyFLC2oZV;(AIo#rkpANpIP1{X`N(qu(I$u$IF~U_crOKHuw$Qb*g-+R5J6HSKy?fhPWVV+GCwtS1%wq=7$o2~gn7z&u)x z$#X`al_t&#XRO&df#{g;M;rvDkn%K1Aa)rc%tR0M);t@7CQL0VgXp0Sf=?4i`Eu>I zj(WgiJz@+ar;wX&8TUJS(~PWfYo^f$Mnsv<++Tkvf!2lB_6ldj{P?7`5-*3=SVIAI zm#EZGsJ~TCmgY@u#}*Kmhx@g;p2+jKnQXHR(~`QMC8;Zqi!d$X@o4#Av8jPOM1AHh;-PC`n*d;sABwnkCC;I-B{}xkt5P>j>80r9p?Qs_e3;? z7REVbf{RG{0SVVC^~~1*mt0>NkqU;*vQlEi$vH=AHxR+E(x5XO|4oaGm(k3ol_V(H zR?z##7&jxmt(^|DGzi9#??VU;x)rwJ9Cvxmlu$Cy^#~-j6J8xwHJT|6a9U@=3LZp5 z9`K6p#n1WvnM|!`fE&%u@s|d`r{8@JfwN|@S$&=C%BUsm&JpR#_HXPNijQ1<2BnZh z0)(w%WjOPL#}2)Mo2yG;{z!CYb9;zsFzaG>bS8&>;$q-vG``h4xTLey0OTu?Jq;P~ zdjmKjJ|CmPR6EiZ|Qyy1=Pnyn#Eu`}tGP13p`-{@oM!R`>$E0Gg+To*QODxYH z$l<0BUUZJ+Sh!+K2ySWR33dQ&CX6_OOs6*7qS-iuK4KW6BPL8}#L?A{d_O^v46~>; z4alYI!n4qI+P@l@iv|9pl+T8uKs}^xpBBQ>-P|x?%T&^LB?-bO1HT>+ya7o4{ztY8 z`m)}8vN@TLbfg3Oo_dVV^}D--=9A9r$-uUP?_JNzDP{G1*p_kzX(Y*ztkLbnu1aq9 ze1|f!Mt`zK)W3!w*XYvZ3e4h0;rb@*CaaYZpK0zcf9X_LkD2ikADz&Dmx`Eqnl3;P zbfj&P0&s7fZu1=UWf7D{f2piRUxJ`{CDz~3glSw&M_(&~pQLYb)Vvy)*Y$(uBdVg! zyiSy-93=~B-?Wq8GtoD~yeuDm=g_`DpPsj!X99h$7H@@dw5@~!88<6-Kl&2%sm%1l z(KJ}>9efadU0bo|)PqoRBfI?l<<@2c6BkjJ=q-}YPOxYDz7wTreyTrFLtn{-gi$to z>~l9z$4$-4R?BJyG&c7UiHj#B)bylsJ2Z=gMPcZ>QVoIo#Gre3Wg_;f^k8n>ur}%| zE+(g%-tU~WZ@c_3U-X^!G^acIuBUym-T>Qvr?HgdQ5C56(^{jH$?8U`9gwBo?M_Pl zHN03ic|N-Nw=Pv&cWpAveYMe{NZV*5KJ<#_jgl_aV9f4YSn4vnabXCirZ?0fF0D)W z7bb=0<}zDG2!rOFqp}8v$}}~1&Cz0-lNw24ObA-)fVJJ5I57ZwdhcA<7znQ>NE3h>_T73^#ln?T=4-o zS<{e*ka#dQQs&u+S?7grVIx+U62rC)6^ViIyLwVTMM;lj_EaT2^3-h*>7D@P*fx3OgqKS&!s1kAL) z$5t@D#BaA7P&cS;rUYFx-(li(rZ#Ja1!AWC3|qR-3^F8TOyR-m(QFPMRr2RGHs^3I ztYw2>PWG=Q=t(?SasfZn(DdT8`#v+s6do))p)H<=U`iL&iBLjDYP}`p04t0sBk5x> zw-i-|Zrm;A!&`w;VLksJ_P+bC#`llEs1z-m>=7xU2-%0|9F+!54J$G;vR5~vWR#f^ z-u5OL=bYwg?_@MYQb}p2A*Ao~y3XnD^nQOH-^ce4_yX;}& zIaYV3N`-D9dD87?Q=EC45Zu5t3KqtHGxHgZ&<(z6zixCLmU~?37%--dIZ|_{cDUr5 zCgxp)g!rMYowmW;X9B*-N+_~7Nn)agy_x);4JviU<`wDC&HMJK65a2%kLed^<}j*!bd%<8YE}{%?Pm&Ew=8=#g3YYow0&NNm-nk_0VSO|(s!pMF5TkU) zO&w|Q^1+Aa@{=#0VsM*JaKk zBVSEBXuxJ(MIPN;7x)hU>e+)rYF?kAFnUh^c(0{J=7-K#yF2vv2h2fqc+;8erP{Nr5ZSlBD#@m(_ z3x3f2a~ljc%V#}jzZ!{?(a5EVG}t7H%ry8prf(V#8)R3qC=xwmz2G z_w`Y^22a*&E@4j+UWG^*a`%@H2^|f-m?U&WLkZly5Nov)q*OWQGBrmMvmUUK2$EJ{ zf>sKJ@O^I2W@=79bj4^tiutsHP)C#gIKBEe=404+UHL?Q zZ0}IA;ciO{;RkQF-u(`KusC33o%XiTbHP$t%4x*^We0Szj5! zmqd+1+)1S3m5#$vLR3>bVBu|qyU92qthTc1F9)l=m6?m`v({QJaY+)duVk_J#@IZ= zVwdb3^%Ug@l$QM>1QVI>ZKVJq(7c~1&|L1}s6k-G{^?fLM>M-crJ2U8ohd%nX8 z)eUEpfIPcG>dpIBBUz!z5ECV=aM#t(73Sg<;@h@dT;Pp6?V1@E={so#X1`Xc{kZEN zs3SFZ^v>xZ`c1;HlF2$gt49w*>@e=ei+8x2jUAFbO^POJAc-A5^XmEqm#LH9?ZjlA zMeQc*d|!M150n*AP{D)$$f*aUmXpYf@bh1qaCvga{}+we34Y8L;V#ko-X?1y8QISz z+&giJ194t*`Ne^EOcKhi19%9R@waUDH(jjjUqXTP&&PaQH)HBa07w#MC+qAvPuN&# za`&O|>$UA6Z37f^P_l>T>zbf()Z{HPmL=vR|5~Rl1-jd|-NN)-iO&QqP1|4WC+fJ# zzH7jhnX=S1beA(zSl&1AF9lv%Co1^vaFPK)tVgi{%g;|@xCS-JpIeJhMENj9yP|1x*c|LE@we7lwbjXq*@S&h-%|PJukt*%%PlEc% z6{KYY(FN>^w%x=Fq4rALy^x7e)zusTZ|amc{%tyjzC$!(RuTT5tYZ<8nb~H@)q41`s2>=+cD&) zP+_(g_+e*g#}*vHr{Fs1xHVn;0{TmH5vm_&4kJj*EeCSLwSHp}1e0!U$p3r2P z6H9?-e^(N-_A|rGFw7t<2DZ%i_NlMuQh|x*^#pc$LS8sjC;+R`;d$)ND&#sJCXaA2=--#~o!Vn}k%m;nSW%8!xHrbg8d2 zE2Ie#<0jx$@JBsq(CD*ejk+?AxS*_hE{dwt7+^wy4v&<_?l@V?2LNX?hAt-2LNR3i z*4vvYGBs~wcV)A_4-(5BaT%M3?B4^wqlZ=19Tq42I$W_#GrD_}ghF{?d&WI{_P|m3 z8UYZGQPw;7MOJzDveDZN~L)w7)z5pqNP_*2VIPqXo^K~@c(*qrC( zbvN-hf@S~`d;gR1m(%$y`*?(1!6I6p--$dQu`{VZ$&f#xz>cT)8Z>}t77wu?GW&Jj zM7hskGQve4NZ?gsri(}D$r*$7XYK5QN1X*n%<5BR$gVX`fS?`;ap`4XFu7~N=7RFT7PlQ8b^)K?VH)7b5|SsZ?mLmCv85|6y*Ep4a9By8M64h$vB3R|?w*Kyy{9HV8fuV)-Kei=Z4 zHE%v^uhIEy0s! zbzr(`9yea$&r}&yAz`{|os#FI*}9e@3T!QWbSAu) ziZ2OAphBHJmx(<2R%~SwYPQqw4l{mIydglJ2{I!w@w+{hw=B*4GMxfy^K&(uik6^f zVhIoxF?>lx3%F&5@U-4*q~Y@oM*B7EUQ9pM79)ffbjgvRDlhb=cR{ntvN#|%GVXg1 za~gH2gYymwY}gQ4e{M;D004<*JKDAX7IDbcO4#SmPZOs0#Ebf)5^b2~wVvZqWms{v zC-PcN({E2frDqR^(uJ@S#Mp^=v+%?H&x}a>aY{lvWGeEe$Rz0aNjP-1>M^Kf2r-`= zH^FgF-z6Am3}AWRut`08mlqHKtk0gsPY(ZCj;#!KGWpq$&TTs>$|nX$!cKACT3GER z(a|rAOytePv7KU(3L`Ea9nKK6-cS$_Ju$Bjs6Ul^nOs7DdgoI^OHPl5v|KdKEMX~>o2 zBDBX)Y&?E$hHkZBszXE=)7#_7vTsb)6KnHAshnFx4$@uqumGg61lMVu_`Blrdpeo7 zB6nXW-ZkT66!89=;rS{i7t@5ZkfQD2@-N;)IVrm0lDh35W@`LMv|O|h+&q4BEmIR| zH9dm;T%vZZ_)I^SALe7=GiCXRM_i)Eue488j(>Bb0|k;!b~kPg@_GUQg20X^%I_d% z)e;_R#aSqfe?4+C^Zookd6!t15a?w70GDf2j%En$=><*CVgohj5frz}QU3d3=Pq<4NXLgC| zUst!p7Zh0CVawir2V^lFy_JiKg+2;nR+uh2gg1s%h+ufPIRJ^`p75E>#0F)mxhr`O z%$d`h*1`Rc`Ono&iy2`X79?wfmsLh~5vrH9 zMbWnn@6JtnI^UE6sRQ-GmSiWOfZ>1RE3M|#mS1SfFrNsYXwcjXHx`@cNw5>;1qCWy+jRHd5tzrkp`EV)}{bb0{sCA_!dSPP$N99xI( zqW8M@W^>5Vf+nd$FeTQo7n#+gzr^RCGu8Xf3tRI#WyOkR8$M9L?eml`@viGJd+s|j zZD3$|dN`|$!8HH-3=2c5tJQtuw}XS1D$=}H(dubW z3LKYTrGQ(blWbH~hw~%}c1RS*j+6~7b_OVXsXGqc!9}4U~tm@_N_i;T@MW7!q=GdtnxGOf$(9%0PS=$qy?XEL23SgbF z2t-^msqd8V>t8+Wb-QfcjkkObx#yT<5gi z0)djJe;{=5v`5t$0@RSy7;3!1%{k(aqPc> zk2FU#MTvrRcuhu4FU+1fg=th?8@*nNZix-*Le3lGric4=HS$l{JdRG7e)4$BvcL^# zPsQV=qtX{E-v~vfQ5ED2W=%sEh~%J6IDgNmDe!Ss&%C^wgcez?oG4cbb6$v9lFhMU z^Ovk1R7$zf^3+PX9`-A4QXu@q#Was`yUhqok`+0FO@}taj|mIoO@{h8C~Xg4{z0W! zTcnz}Thd1K0}c^4h8bTOtf--si90!8?PU)=47(yYgx1-_b&29U)5Af=aN3-I5LXVy z#zt0%iz&HK539@N3C5jR;JUQq|%@I0w;ON-qGKtNHcfx#R->O$xiaZ3LoCiIpdl&lBzZ< zJKtP_vgsI-v-pp9NgtS{uVk;)~D@8 zv2nbH^l*X+>^!^Qe(w|dd#F9E`1Z&V*PmT}QQ+w>|E7vY5!#76$Q14VLlX+D7F4Q;^M6G4lFd zr2?sNNu)bnrp~9YBjiqjF}Qf8;>)MG@Zyc&%EJH1KL(S3l->YE*>G&adxiO*Vk!lX zv)!b6Fp%DoPO3Pc241*Y#mz+9l+Q(`dH@F37CsZEOoclZz&9(O-4!xL$S7?f{WP4N zORkLtnbM+Py$aG>zoIO70V(QdStxP(q|P(MmZ9i3dEwnv@`6S<=TP+P+PF|Z>eD(@ zqX4+HR9oq_&(Te^=(hyy86)5nI@tGMs=D@0v~E}i)B z0T@^JOUFh5hgil%?o9rZfjfyC_{h*~1zhe}eZ4P^x|kW(dn^}wBGtSyaAty;MZ2yu zrDffp-(qzq;)`1J8kP+b3$zQ-T<>Pk+RunrBjJtLLMEF3YZ5`qS0o6s-ZPX^mjy^( zap(?Vw1{aK9j?1VVj8t-$be`I4-isGEGI!|60bSwE#r&J{?$A3NO>06DU^Bte#Upf z^gILil}KyNbbGt*lWgzPqQl3ry-oI~f=vv`_Nsa5;?4ZMUiQlJk^D{Q#?TfEm_CEh z@{_@}eGM{&Z+lT%hEC$3KI#`~?xoz>cO62e_mde6g)h)$wBZ{uyExV)bAf>1Nb zdcf4>aPzY}(b3R;+4|#B^&_NLU#~`r^${1${Z6g=R3kJc6PIIroty8E{SAL8fRLaU zOlocCECR^Zb6n_T!;N}fc0gk=id0-6tuc_bq&_FXBN7XLw%#!w7pt`=M&ocv<1i