#![allow(non_upper_case_globals, dead_code)]
use std::fmt;
#[derive(Debug, Clone)]
pub struct X86NopSled {
pub is_64bit: bool,
pub strategy: NopSledStrategy,
pub microarch: NopMicroArch,
pub current_offset: u64,
pub stats: NopSledStats,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NopSledStrategy {
SingleByte,
Optimal,
Intel,
Amd,
MinSize,
MinDecode,
Compat,
MaxPatchable,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum NopMicroArch {
Generic,
SandyBridge,
IvyBridge,
Haswell,
Broadwell,
Skylake,
KabyLake,
CoffeeLake,
CometLake,
CascadeLake,
IceLake,
TigerLake,
RocketLake,
AlderLakeP,
RaptorLakeP,
MeteorLakeP,
ArrowLakeP,
LunarLakeP,
GraniteRapids,
Goldmont,
GoldmontPlus,
Tremont,
Gracemont,
Crestmont,
Skymont,
Darkmont,
Bonnell,
Saltwell,
Silvermont,
Airmont,
Zen1,
Zen2,
Zen3,
Zen4,
Zen5,
Zen6,
Nano,
NanoX2,
NanoX4,
}
impl NopMicroArch {
pub fn prefers_long_nops(&self) -> bool {
match self {
NopMicroArch::Generic
| NopMicroArch::SandyBridge
| NopMicroArch::IvyBridge
| NopMicroArch::Haswell
| NopMicroArch::Broadwell
| NopMicroArch::Skylake
| NopMicroArch::KabyLake
| NopMicroArch::CoffeeLake
| NopMicroArch::CometLake
| NopMicroArch::CascadeLake
| NopMicroArch::IceLake
| NopMicroArch::TigerLake
| NopMicroArch::RocketLake
| NopMicroArch::AlderLakeP
| NopMicroArch::RaptorLakeP
| NopMicroArch::MeteorLakeP
| NopMicroArch::ArrowLakeP
| NopMicroArch::LunarLakeP
| NopMicroArch::GraniteRapids
| NopMicroArch::Zen1
| NopMicroArch::Zen2
| NopMicroArch::Zen3
| NopMicroArch::Zen4
| NopMicroArch::Zen5
| NopMicroArch::Zen6 => true,
NopMicroArch::Bonnell
| NopMicroArch::Saltwell
| NopMicroArch::Silvermont
| NopMicroArch::Airmont
| NopMicroArch::Goldmont
| NopMicroArch::GoldmontPlus => false,
NopMicroArch::Tremont
| NopMicroArch::Gracemont
| NopMicroArch::Crestmont
| NopMicroArch::Skymont
| NopMicroArch::Darkmont => true,
_ => false,
}
}
pub fn decode_width(&self) -> u8 {
match self {
NopMicroArch::Bonnell | NopMicroArch::Saltwell => 2,
NopMicroArch::Silvermont | NopMicroArch::Airmont => 2,
NopMicroArch::Goldmont | NopMicroArch::GoldmontPlus => 3,
NopMicroArch::Tremont | NopMicroArch::Gracemont => 6, NopMicroArch::Crestmont | NopMicroArch::Skymont => 6,
NopMicroArch::Darkmont => 6,
NopMicroArch::SandyBridge | NopMicroArch::IvyBridge => 4,
NopMicroArch::Haswell | NopMicroArch::Broadwell => 4,
NopMicroArch::Skylake
| NopMicroArch::KabyLake
| NopMicroArch::CoffeeLake
| NopMicroArch::CometLake
| NopMicroArch::CascadeLake => 5, NopMicroArch::IceLake | NopMicroArch::TigerLake | NopMicroArch::RocketLake => 5,
NopMicroArch::AlderLakeP | NopMicroArch::RaptorLakeP => 6,
NopMicroArch::MeteorLakeP | NopMicroArch::ArrowLakeP | NopMicroArch::LunarLakeP => 6,
NopMicroArch::GraniteRapids => 6,
NopMicroArch::Zen1 | NopMicroArch::Zen2 => 4,
NopMicroArch::Zen3 | NopMicroArch::Zen4 => 4,
NopMicroArch::Zen5 | NopMicroArch::Zen6 => 4, _ => 4,
}
}
pub fn cache_line_size(&self) -> u32 {
match self {
NopMicroArch::Bonnell | NopMicroArch::Saltwell => 64,
_ => 64, }
}
}
#[derive(Debug, Clone, Default)]
pub struct NopSledStats {
pub total_nop_bytes: u64,
pub single_byte_nops: u64,
pub multi_byte_nops: u64,
pub alignment_bytes: u64,
pub function_alignments: u64,
pub loop_alignments: u64,
pub branch_alignments: u64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NopSequence {
pub bytes: Vec<u8>,
pub len: usize,
pub variant: NopVariant,
pub uses_segment_override: bool,
pub prefix_count: u8,
pub decode_cost: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NopVariant {
XchgEaxEax,
OpSizeXchg,
TwoByte0F1F,
TwoByte0F1FSib,
TwoByte0F1FDisp32,
LongNop0F1F,
SegmentNop,
MultiPrefixNop,
}
impl NopVariant {
pub fn is_official_intel(&self) -> bool {
match self {
NopVariant::XchgEaxEax
| NopVariant::OpSizeXchg
| NopVariant::TwoByte0F1F
| NopVariant::TwoByte0F1FSib
| NopVariant::TwoByte0F1FDisp32
| NopVariant::LongNop0F1F => true,
NopVariant::SegmentNop | NopVariant::MultiPrefixNop => false,
}
}
pub fn is_amd_recommended(&self) -> bool {
match self {
NopVariant::XchgEaxEax
| NopVariant::OpSizeXchg
| NopVariant::TwoByte0F1F
| NopVariant::TwoByte0F1FSib
| NopVariant::TwoByte0F1FDisp32
| NopVariant::LongNop0F1F
| NopVariant::SegmentNop => true,
NopVariant::MultiPrefixNop => false,
}
}
}
pub fn intel_nop_sequence(len: usize) -> Option<NopSequence> {
match len {
1 => Some(NopSequence {
bytes: vec![0x90],
len: 1,
variant: NopVariant::XchgEaxEax,
uses_segment_override: false,
prefix_count: 0,
decode_cost: 1,
}),
2 => Some(NopSequence {
bytes: vec![0x66, 0x90],
len: 2,
variant: NopVariant::OpSizeXchg,
uses_segment_override: false,
prefix_count: 1,
decode_cost: 1,
}),
3 => Some(NopSequence {
bytes: vec![0x0F, 0x1F, 0x00],
len: 3,
variant: NopVariant::TwoByte0F1F,
uses_segment_override: false,
prefix_count: 0,
decode_cost: 1,
}),
4 => Some(NopSequence {
bytes: vec![0x0F, 0x1F, 0x40, 0x00],
len: 4,
variant: NopVariant::TwoByte0F1FSib,
uses_segment_override: false,
prefix_count: 0,
decode_cost: 1,
}),
5 => Some(NopSequence {
bytes: vec![0x0F, 0x1F, 0x44, 0x00, 0x00],
len: 5,
variant: NopVariant::LongNop0F1F,
uses_segment_override: false,
prefix_count: 0,
decode_cost: 1,
}),
6 => Some(NopSequence {
bytes: vec![0x66, 0x0F, 0x1F, 0x44, 0x00, 0x00],
len: 6,
variant: NopVariant::LongNop0F1F,
uses_segment_override: false,
prefix_count: 1,
decode_cost: 1,
}),
7 => Some(NopSequence {
bytes: vec![0x0F, 0x1F, 0x80, 0x00, 0x00, 0x00, 0x00],
len: 7,
variant: NopVariant::TwoByte0F1FDisp32,
uses_segment_override: false,
prefix_count: 0,
decode_cost: 1,
}),
8 => Some(NopSequence {
bytes: vec![0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00],
len: 8,
variant: NopVariant::LongNop0F1F,
uses_segment_override: false,
prefix_count: 0,
decode_cost: 1,
}),
9 => Some(NopSequence {
bytes: vec![0x66, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00],
len: 9,
variant: NopVariant::LongNop0F1F,
uses_segment_override: false,
prefix_count: 1,
decode_cost: 1,
}),
10 => Some(NopSequence {
bytes: vec![0x66, 0x66, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00],
len: 10,
variant: NopVariant::MultiPrefixNop,
uses_segment_override: false,
prefix_count: 2,
decode_cost: 1,
}),
11 => Some(NopSequence {
bytes: vec![
0x66, 0x66, 0x66, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00,
],
len: 11,
variant: NopVariant::MultiPrefixNop,
uses_segment_override: false,
prefix_count: 3,
decode_cost: 2,
}),
12 => Some(NopSequence {
bytes: vec![
0x66, 0x66, 0x66, 0x66, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00,
],
len: 12,
variant: NopVariant::MultiPrefixNop,
uses_segment_override: false,
prefix_count: 4,
decode_cost: 2,
}),
13 => Some(NopSequence {
bytes: vec![
0x66, 0x66, 0x66, 0x66, 0x66, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00,
],
len: 13,
variant: NopVariant::MultiPrefixNop,
uses_segment_override: false,
prefix_count: 5,
decode_cost: 2,
}),
14 => Some(NopSequence {
bytes: vec![
0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00,
],
len: 14,
variant: NopVariant::MultiPrefixNop,
uses_segment_override: false,
prefix_count: 6,
decode_cost: 2,
}),
15 => Some(NopSequence {
bytes: vec![
0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00,
0x00,
],
len: 15,
variant: NopVariant::MultiPrefixNop,
uses_segment_override: false,
prefix_count: 7,
decode_cost: 2,
}),
_ => None,
}
}
pub fn amd_nop_sequence(len: usize) -> Option<NopSequence> {
match len {
1 => Some(NopSequence {
bytes: vec![0x90],
len: 1,
variant: NopVariant::XchgEaxEax,
uses_segment_override: false,
prefix_count: 0,
decode_cost: 1,
}),
2 => Some(NopSequence {
bytes: vec![0x66, 0x90],
len: 2,
variant: NopVariant::OpSizeXchg,
uses_segment_override: false,
prefix_count: 1,
decode_cost: 1,
}),
3 => Some(NopSequence {
bytes: vec![0x0F, 0x1F, 0x00],
len: 3,
variant: NopVariant::TwoByte0F1F,
uses_segment_override: false,
prefix_count: 0,
decode_cost: 1,
}),
4 => Some(NopSequence {
bytes: vec![0x0F, 0x1F, 0x40, 0x00],
len: 4,
variant: NopVariant::TwoByte0F1FSib,
uses_segment_override: false,
prefix_count: 0,
decode_cost: 1,
}),
5 => Some(NopSequence {
bytes: vec![0x0F, 0x1F, 0x44, 0x00, 0x00],
len: 5,
variant: NopVariant::LongNop0F1F,
uses_segment_override: false,
prefix_count: 0,
decode_cost: 1,
}),
6 => Some(NopSequence {
bytes: vec![0x66, 0x0F, 0x1F, 0x44, 0x00, 0x00],
len: 6,
variant: NopVariant::LongNop0F1F,
uses_segment_override: false,
prefix_count: 1,
decode_cost: 1,
}),
7 => Some(NopSequence {
bytes: vec![0x0F, 0x1F, 0x80, 0x00, 0x00, 0x00, 0x00],
len: 7,
variant: NopVariant::TwoByte0F1FDisp32,
uses_segment_override: false,
prefix_count: 0,
decode_cost: 1,
}),
8 => Some(NopSequence {
bytes: vec![0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00],
len: 8,
variant: NopVariant::LongNop0F1F,
uses_segment_override: false,
prefix_count: 0,
decode_cost: 1,
}),
9 => Some(NopSequence {
bytes: vec![0x66, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00],
len: 9,
variant: NopVariant::LongNop0F1F,
uses_segment_override: false,
prefix_count: 1,
decode_cost: 1,
}),
10 => Some(NopSequence {
bytes: vec![0x66, 0x2E, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00],
len: 10,
variant: NopVariant::SegmentNop,
uses_segment_override: true,
prefix_count: 2,
decode_cost: 1,
}),
11 => Some(NopSequence {
bytes: vec![
0x66, 0x66, 0x2E, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00,
],
len: 11,
variant: NopVariant::SegmentNop,
uses_segment_override: true,
prefix_count: 3,
decode_cost: 2,
}),
12 => Some(NopSequence {
bytes: vec![
0x66, 0x66, 0x66, 0x2E, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00,
],
len: 12,
variant: NopVariant::SegmentNop,
uses_segment_override: true,
prefix_count: 4,
decode_cost: 2,
}),
13 => Some(NopSequence {
bytes: vec![
0x66, 0x66, 0x66, 0x66, 0x2E, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00,
],
len: 13,
variant: NopVariant::SegmentNop,
uses_segment_override: true,
prefix_count: 5,
decode_cost: 2,
}),
14 => Some(NopSequence {
bytes: vec![
0x66, 0x66, 0x66, 0x66, 0x66, 0x2E, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00,
],
len: 14,
variant: NopVariant::SegmentNop,
uses_segment_override: true,
prefix_count: 6,
decode_cost: 3,
}),
15 => Some(NopSequence {
bytes: vec![
0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x2E, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00,
0x00,
],
len: 15,
variant: NopVariant::SegmentNop,
uses_segment_override: true,
prefix_count: 7,
decode_cost: 3,
}),
_ => None,
}
}
pub fn optimal_nop_sequence(len: usize) -> Option<NopSequence> {
if len == 0 {
return None;
}
if len > 15 {
return None;
}
if len <= 9 {
intel_nop_sequence(len)
} else {
let intel = intel_nop_sequence(len);
let amd = amd_nop_sequence(len);
match (intel, amd) {
(Some(i), Some(a)) => {
if i.decode_cost <= a.decode_cost {
Some(i)
} else {
Some(a)
}
}
(Some(i), None) => Some(i),
(None, Some(a)) => Some(a),
(None, None) => None,
}
}
}
pub fn compat_nop_sequence(len: usize) -> Option<NopSequence> {
if len == 0 {
return None;
}
if len > 9 {
None } else {
intel_nop_sequence(len)
}
}
impl X86NopSled {
pub fn new(is_64bit: bool, strategy: NopSledStrategy, microarch: NopMicroArch) -> Self {
X86NopSled {
is_64bit,
strategy,
microarch,
current_offset: 0,
stats: NopSledStats::default(),
}
}
pub fn default_for(is_64bit: bool) -> Self {
Self::new(is_64bit, NopSledStrategy::Optimal, NopMicroArch::Generic)
}
pub fn generate_sled(&mut self, size: usize) -> Vec<u8> {
if size == 0 {
return vec![];
}
let bytes = match self.strategy {
NopSledStrategy::SingleByte => {
self.stats.single_byte_nops += size as u64;
self.stats.total_nop_bytes += size as u64;
vec![0x90; size]
}
NopSledStrategy::Optimal => {
self.generate_sled_with_strategy(size, optimal_nop_sequence)
}
NopSledStrategy::Intel => self.generate_sled_with_strategy(size, intel_nop_sequence),
NopSledStrategy::Amd => self.generate_sled_with_strategy(size, amd_nop_sequence),
NopSledStrategy::MinSize => self.generate_min_size_sled(size),
NopSledStrategy::MinDecode => self.generate_min_decode_sled(size),
NopSledStrategy::Compat => self.generate_compat_sled(size),
NopSledStrategy::MaxPatchable => self.generate_max_patchable_sled(size),
};
self.current_offset += bytes.len() as u64;
bytes
}
fn generate_sled_with_strategy<F>(&mut self, size: usize, get_nop: F) -> Vec<u8>
where
F: Fn(usize) -> Option<NopSequence>,
{
let mut bytes = Vec::with_capacity(size);
let mut remaining = size;
while remaining > 0 {
let nop_len = if remaining > 15 { 15 } else { remaining };
if let Some(nop) = get_nop(nop_len) {
bytes.extend_from_slice(&nop.bytes);
self.stats.multi_byte_nops += 1;
self.stats.total_nop_bytes += nop.len as u64;
remaining -= nop.len;
} else {
let count = remaining.min(15);
bytes.extend(std::iter::repeat(0x90).take(count));
self.stats.single_byte_nops += count as u64;
self.stats.total_nop_bytes += count as u64;
remaining -= count;
}
}
bytes
}
fn generate_min_size_sled(&mut self, size: usize) -> Vec<u8> {
let mut bytes = Vec::with_capacity(size);
let mut remaining = size;
let nop_lengths = [15, 9, 8, 7, 6, 5, 4, 3, 2, 1];
for &nop_len in &nop_lengths {
while remaining >= nop_len {
let nop_seq = match self.strategy {
NopSledStrategy::Amd => amd_nop_sequence(nop_len),
_ => intel_nop_sequence(nop_len),
};
if let Some(nop) = nop_seq {
bytes.extend_from_slice(&nop.bytes);
remaining -= nop_len;
self.stats.multi_byte_nops += 1;
self.stats.total_nop_bytes += nop_len as u64;
}
}
}
bytes
}
fn generate_min_decode_sled(&mut self, size: usize) -> Vec<u8> {
let mut bytes = Vec::with_capacity(size);
let mut remaining = size;
while remaining >= 4 {
if let Some(nop) = intel_nop_sequence(4) {
bytes.extend_from_slice(&nop.bytes);
remaining -= 4;
self.stats.multi_byte_nops += 1;
self.stats.total_nop_bytes += 4;
}
}
while remaining >= 2 {
if let Some(nop) = intel_nop_sequence(2) {
bytes.extend_from_slice(&nop.bytes);
remaining -= 2;
self.stats.multi_byte_nops += 1;
self.stats.total_nop_bytes += 2;
}
}
if remaining > 0 {
bytes.push(0x90);
self.stats.single_byte_nops += 1;
self.stats.total_nop_bytes += 1;
}
bytes
}
fn generate_compat_sled(&mut self, size: usize) -> Vec<u8> {
let mut bytes = Vec::with_capacity(size);
let mut remaining = size;
while remaining >= 9 {
if let Some(nop) = intel_nop_sequence(9) {
bytes.extend_from_slice(&nop.bytes);
remaining -= 9;
self.stats.multi_byte_nops += 1;
self.stats.total_nop_bytes += 9;
}
}
while remaining > 0 {
let nop_len = remaining.min(9);
if let Some(nop) = intel_nop_sequence(nop_len) {
bytes.extend_from_slice(&nop.bytes);
remaining -= nop_len;
self.stats.multi_byte_nops += 1;
self.stats.total_nop_bytes += nop_len as u64;
} else {
bytes.push(0x90);
remaining -= 1;
self.stats.single_byte_nops += 1;
self.stats.total_nop_bytes += 1;
}
}
bytes
}
fn generate_max_patchable_sled(&mut self, size: usize) -> Vec<u8> {
let mut bytes = Vec::with_capacity(size);
let mut remaining = size;
while remaining >= 15 {
if let Some(nop) = intel_nop_sequence(15) {
bytes.extend_from_slice(&nop.bytes);
remaining -= 15;
self.stats.multi_byte_nops += 1;
self.stats.total_nop_bytes += 15;
}
}
if remaining > 0 {
if let Some(nop) = intel_nop_sequence(remaining) {
bytes.extend_from_slice(&nop.bytes);
self.stats.multi_byte_nops += 1;
self.stats.total_nop_bytes += remaining as u64;
} else {
bytes.extend(std::iter::repeat(0x90).take(remaining));
self.stats.single_byte_nops += remaining as u64;
self.stats.total_nop_bytes += remaining as u64;
}
}
bytes
}
pub fn align_to(&mut self, boundary: u64) -> Vec<u8> {
let misalignment = self.current_offset % boundary;
if misalignment == 0 {
return vec![];
}
let pad_size = (boundary - misalignment) as usize;
self.stats.alignment_bytes += pad_size as u64;
self.generate_sled(pad_size)
}
pub fn align_to_16(&mut self) -> Vec<u8> {
self.align_to(16)
}
pub fn align_to_32(&mut self) -> Vec<u8> {
self.align_to(32)
}
pub fn align_to_64(&mut self) -> Vec<u8> {
self.align_to(64)
}
pub fn align_to_microarch(&mut self) -> Vec<u8> {
let boundary = self.microarch.cache_line_size() as u64;
self.align_to(boundary)
}
pub fn reset_offset(&mut self) {
self.current_offset = 0;
}
pub fn advance_offset(&mut self, bytes: u64) {
self.current_offset += bytes;
}
}
pub fn generate_function_entry_alignment(
current_offset: u64,
boundary: u32,
strategy: NopSledStrategy,
microarch: NopMicroArch,
is_64bit: bool,
) -> Vec<u8> {
let mut sled = X86NopSled::new(is_64bit, strategy, microarch);
sled.current_offset = current_offset;
sled.align_to(boundary as u64)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AlignmentTarget {
FunctionEntry16,
FunctionEntry32,
FunctionEntry64,
LoopHeader16,
LoopHeader32,
LoopHeader64,
BranchTarget16,
BranchTarget32,
BasicBlock8,
NoAlignment,
}
impl AlignmentTarget {
pub fn boundary(&self) -> u64 {
match self {
AlignmentTarget::FunctionEntry16 => 16,
AlignmentTarget::FunctionEntry32 => 32,
AlignmentTarget::FunctionEntry64 => 64,
AlignmentTarget::LoopHeader16 => 16,
AlignmentTarget::LoopHeader32 => 32,
AlignmentTarget::LoopHeader64 => 64,
AlignmentTarget::BranchTarget16 => 16,
AlignmentTarget::BranchTarget32 => 32,
AlignmentTarget::BasicBlock8 => 8,
AlignmentTarget::NoAlignment => 1,
}
}
pub fn max_nop_bytes(&self) -> usize {
match self {
AlignmentTarget::FunctionEntry16 => 15,
AlignmentTarget::FunctionEntry32 => 31,
AlignmentTarget::FunctionEntry64 => 48,
AlignmentTarget::LoopHeader16 => 15,
AlignmentTarget::LoopHeader32 => 24,
AlignmentTarget::LoopHeader64 => 48,
AlignmentTarget::BranchTarget16 => 15,
AlignmentTarget::BranchTarget32 => 24,
AlignmentTarget::BasicBlock8 => 7,
AlignmentTarget::NoAlignment => 0,
}
}
pub fn use_long_nops(&self) -> bool {
match self {
AlignmentTarget::NoAlignment | AlignmentTarget::BasicBlock8 => false,
_ => true,
}
}
pub fn recommended_strategy(&self) -> NopSledStrategy {
if self.use_long_nops() {
NopSledStrategy::Optimal
} else {
NopSledStrategy::MinDecode
}
}
}
pub fn generate_loop_header_alignment(
current_offset: u64,
microarch: NopMicroArch,
is_64bit: bool,
) -> (Vec<u8>, AlignmentTarget) {
let target = loop_alignment_target(microarch);
let max_nops = target.max_nop_bytes();
let misalignment = current_offset % target.boundary();
if misalignment == 0 {
return (vec![], target);
}
let pad_size = (target.boundary() - misalignment) as usize;
if pad_size > max_nops {
return (vec![], AlignmentTarget::NoAlignment);
}
let strategy = target.recommended_strategy();
let padding = generate_function_entry_alignment(
current_offset,
target.boundary() as u32,
strategy,
microarch,
is_64bit,
);
(padding, target)
}
pub fn loop_alignment_target(microarch: NopMicroArch) -> AlignmentTarget {
match microarch {
NopMicroArch::Bonnell | NopMicroArch::Saltwell => {
AlignmentTarget::BasicBlock8
}
NopMicroArch::Silvermont
| NopMicroArch::Airmont
| NopMicroArch::Goldmont
| NopMicroArch::GoldmontPlus => AlignmentTarget::LoopHeader16,
NopMicroArch::SandyBridge
| NopMicroArch::IvyBridge
| NopMicroArch::Haswell
| NopMicroArch::Broadwell => {
AlignmentTarget::LoopHeader16
}
NopMicroArch::Skylake
| NopMicroArch::KabyLake
| NopMicroArch::CoffeeLake
| NopMicroArch::CometLake
| NopMicroArch::CascadeLake => {
AlignmentTarget::LoopHeader16
}
NopMicroArch::IceLake
| NopMicroArch::TigerLake
| NopMicroArch::RocketLake
| NopMicroArch::AlderLakeP
| NopMicroArch::RaptorLakeP
| NopMicroArch::MeteorLakeP
| NopMicroArch::ArrowLakeP
| NopMicroArch::LunarLakeP
| NopMicroArch::GraniteRapids => AlignmentTarget::LoopHeader32,
NopMicroArch::Tremont
| NopMicroArch::Gracemont
| NopMicroArch::Crestmont
| NopMicroArch::Skymont
| NopMicroArch::Darkmont => AlignmentTarget::LoopHeader32,
NopMicroArch::Zen1 | NopMicroArch::Zen2 => {
AlignmentTarget::LoopHeader32
}
NopMicroArch::Zen3 | NopMicroArch::Zen4 => {
AlignmentTarget::LoopHeader32
}
NopMicroArch::Zen5 | NopMicroArch::Zen6 => {
AlignmentTarget::LoopHeader64
}
_ => AlignmentTarget::LoopHeader16,
}
}
pub fn is_loop_alignment_beneficial(loop_byte_size: usize, microarch: NopMicroArch) -> bool {
if loop_byte_size < 8 {
return false;
}
match microarch {
NopMicroArch::SandyBridge | NopMicroArch::IvyBridge => {
loop_byte_size <= 84
}
NopMicroArch::Haswell | NopMicroArch::Broadwell => {
loop_byte_size <= 60
}
NopMicroArch::Skylake
| NopMicroArch::KabyLake
| NopMicroArch::CoffeeLake
| NopMicroArch::CometLake
| NopMicroArch::CascadeLake => {
loop_byte_size <= 64
}
NopMicroArch::IceLake | NopMicroArch::TigerLake | NopMicroArch::RocketLake => {
loop_byte_size <= 128
}
NopMicroArch::Zen1
| NopMicroArch::Zen2
| NopMicroArch::Zen3
| NopMicroArch::Zen4
| NopMicroArch::Zen5
| NopMicroArch::Zen6 => {
loop_byte_size <= 512
}
_ => loop_byte_size <= 128,
}
}
pub fn generate_branch_target_alignment(
current_offset: u64,
_is_indirect: bool,
microarch: NopMicroArch,
is_64bit: bool,
) -> Vec<u8> {
let boundary = match microarch {
NopMicroArch::SandyBridge
| NopMicroArch::IvyBridge
| NopMicroArch::Haswell
| NopMicroArch::Broadwell => 16u64,
NopMicroArch::Skylake
| NopMicroArch::KabyLake
| NopMicroArch::CoffeeLake
| NopMicroArch::CometLake
| NopMicroArch::CascadeLake
| NopMicroArch::IceLake
| NopMicroArch::TigerLake
| NopMicroArch::RocketLake => 16,
NopMicroArch::AlderLakeP
| NopMicroArch::RaptorLakeP
| NopMicroArch::MeteorLakeP
| NopMicroArch::ArrowLakeP
| NopMicroArch::LunarLakeP
| NopMicroArch::GraniteRapids => 32,
NopMicroArch::Zen1
| NopMicroArch::Zen2
| NopMicroArch::Zen3
| NopMicroArch::Zen4
| NopMicroArch::Zen5
| NopMicroArch::Zen6 => 32,
_ => 16,
};
let misalignment = current_offset % boundary;
if misalignment == 0 {
return vec![];
}
let pad_size = (boundary - misalignment) as usize;
if pad_size > 15 {
return vec![];
}
let mut sled = X86NopSled::new(is_64bit, NopSledStrategy::Optimal, microarch);
sled.current_offset = current_offset;
sled.align_to(boundary)
}
pub fn generate_hotpatch_sled(
size: usize,
style: HotPatchStyle,
strategy: NopSledStrategy,
is_64bit: bool,
) -> Vec<u8> {
match style {
HotPatchStyle::None => vec![],
HotPatchStyle::Win64 => generate_win64_hotpatch_sled(size, strategy, is_64bit),
HotPatchStyle::LinuxFtrace => generate_ftrace_sled(size, strategy, is_64bit),
HotPatchStyle::LinuxLivepatch => generate_livepatch_sled(size, strategy, is_64bit),
HotPatchStyle::Generic => {
let mut sled = X86NopSled::new(is_64bit, strategy, NopMicroArch::Generic);
sled.generate_sled(size)
}
HotPatchStyle::JitCompiler => generate_jit_sled(size, strategy, is_64bit),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HotPatchStyle {
None,
Win64,
LinuxFtrace,
LinuxLivepatch,
Generic,
JitCompiler,
}
fn generate_win64_hotpatch_sled(size: usize, strategy: NopSledStrategy, is_64bit: bool) -> Vec<u8> {
let mut bytes = Vec::with_capacity(size);
let mut remaining = size;
if remaining >= 2 {
bytes.push(0x89);
bytes.push(0xFF);
remaining -= 2;
}
if remaining > 0 {
let mut sled = X86NopSled::new(is_64bit, strategy, NopMicroArch::Generic);
bytes.extend(sled.generate_sled(remaining));
}
bytes
}
fn generate_ftrace_sled(size: usize, strategy: NopSledStrategy, is_64bit: bool) -> Vec<u8> {
let mut bytes = Vec::with_capacity(size);
let mut remaining = size;
if remaining >= 5 {
bytes.extend_from_slice(&[0x0F, 0x1F, 0x44, 0x00, 0x00]);
remaining -= 5;
}
if remaining > 0 {
let mut sled = X86NopSled::new(is_64bit, strategy, NopMicroArch::Generic);
bytes.extend(sled.generate_sled(remaining));
}
bytes
}
fn generate_livepatch_sled(size: usize, strategy: NopSledStrategy, is_64bit: bool) -> Vec<u8> {
generate_ftrace_sled(size, strategy, is_64bit)
}
fn generate_jit_sled(size: usize, strategy: NopSledStrategy, is_64bit: bool) -> Vec<u8> {
let mut sled = X86NopSled::new(
is_64bit,
NopSledStrategy::MaxPatchable,
NopMicroArch::Generic,
);
sled.generate_sled(size)
}
pub fn select_optimal_nop_length(
remaining_padding: usize,
microarch: NopMicroArch,
is_loop: bool,
) -> usize {
if remaining_padding == 0 {
return 0;
}
let remaining = remaining_padding.min(15);
if remaining <= 2 {
return remaining;
}
match microarch {
NopMicroArch::Bonnell | NopMicroArch::Saltwell => {
if remaining >= 4 {
4
} else {
remaining
}
}
NopMicroArch::Silvermont | NopMicroArch::Airmont => {
if remaining >= 4 {
4
} else {
remaining
}
}
NopMicroArch::Goldmont | NopMicroArch::GoldmontPlus => {
if remaining >= 5 {
5
} else {
remaining
}
}
NopMicroArch::SandyBridge | NopMicroArch::IvyBridge => {
if is_loop {
remaining
} else if remaining >= 4 {
4
} else {
remaining
}
}
NopMicroArch::Haswell | NopMicroArch::Broadwell => {
if remaining >= 5 {
5
} else {
remaining
}
}
NopMicroArch::Skylake
| NopMicroArch::KabyLake
| NopMicroArch::CoffeeLake
| NopMicroArch::CometLake
| NopMicroArch::CascadeLake => {
remaining
}
NopMicroArch::IceLake | NopMicroArch::TigerLake | NopMicroArch::RocketLake => remaining,
_ => remaining,
}
}
pub fn nop_sled_decode_cost(nop_lengths: &[usize], decode_width: u8) -> u64 {
if nop_lengths.is_empty() {
return 0;
}
let mut cycles = 0u64;
let mut instrs_in_cycle = 0u8;
for &len in nop_lengths {
let complexity = if len <= 2 { 1 } else { 1 };
if instrs_in_cycle + complexity > decode_width {
cycles += 1;
instrs_in_cycle = complexity;
} else {
instrs_in_cycle += complexity;
}
}
if instrs_in_cycle > 0 {
cycles += 1;
}
cycles
}
pub fn nop_uop_count(nop_len: usize, microarch: NopMicroArch) -> u8 {
match microarch {
NopMicroArch::SandyBridge
| NopMicroArch::IvyBridge
| NopMicroArch::Haswell
| NopMicroArch::Broadwell
| NopMicroArch::Skylake
| NopMicroArch::KabyLake
| NopMicroArch::CoffeeLake
| NopMicroArch::CometLake
| NopMicroArch::CascadeLake
| NopMicroArch::IceLake
| NopMicroArch::TigerLake
| NopMicroArch::RocketLake
| NopMicroArch::AlderLakeP
| NopMicroArch::RaptorLakeP
| NopMicroArch::MeteorLakeP
| NopMicroArch::ArrowLakeP
| NopMicroArch::LunarLakeP
| NopMicroArch::GraniteRapids => {
1
}
NopMicroArch::Zen1
| NopMicroArch::Zen2
| NopMicroArch::Zen3
| NopMicroArch::Zen4
| NopMicroArch::Zen5
| NopMicroArch::Zen6 => {
1
}
NopMicroArch::Bonnell
| NopMicroArch::Saltwell
| NopMicroArch::Silvermont
| NopMicroArch::Airmont
| NopMicroArch::Goldmont
| NopMicroArch::GoldmontPlus
| NopMicroArch::Tremont
| NopMicroArch::Gracemont
| NopMicroArch::Crestmont
| NopMicroArch::Skymont
| NopMicroArch::Darkmont => {
1
}
_ => 1,
}
}
pub fn intel_alignment_nops(pad_size: usize) -> Vec<u8> {
match pad_size {
0 => vec![],
1 => vec![0x90],
2 => vec![0x66, 0x90],
3 => vec![0x0F, 0x1F, 0x00],
4 => vec![0x0F, 0x1F, 0x40, 0x00],
5 => vec![0x0F, 0x1F, 0x44, 0x00, 0x00],
6 => vec![0x66, 0x0F, 0x1F, 0x44, 0x00, 0x00],
7 => vec![0x0F, 0x1F, 0x80, 0x00, 0x00, 0x00, 0x00],
8 => vec![0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00],
9 => vec![0x66, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00],
10 => vec![0x66, 0x66, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00],
11 => vec![
0x66, 0x66, 0x66, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00,
],
12 => vec![
0x66, 0x66, 0x66, 0x66, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00,
],
13 => vec![
0x66, 0x66, 0x66, 0x66, 0x66, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00,
],
14 => vec![
0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00,
],
15 => vec![
0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00,
0x00,
],
_ => {
let mut result = Vec::with_capacity(pad_size);
let mut remaining = pad_size;
while remaining >= 15 {
result.extend_from_slice(&intel_alignment_nops(15));
remaining -= 15;
}
if remaining > 0 {
result.extend_from_slice(&intel_alignment_nops(remaining));
}
result
}
}
}
pub fn amd_alignment_nops(pad_size: usize) -> Vec<u8> {
match pad_size {
0 => vec![],
1 => vec![0x90],
2 => vec![0x66, 0x90],
3 => vec![0x0F, 0x1F, 0x00],
4 => vec![0x0F, 0x1F, 0x40, 0x00],
5 => vec![0x0F, 0x1F, 0x44, 0x00, 0x00],
6 => vec![0x66, 0x0F, 0x1F, 0x44, 0x00, 0x00],
7 => vec![0x0F, 0x1F, 0x80, 0x00, 0x00, 0x00, 0x00],
8 => vec![0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00],
9 => vec![0x66, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00],
10 => vec![0x66, 0x2E, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00],
11 => vec![
0x66, 0x66, 0x2E, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00,
],
12 => vec![
0x66, 0x66, 0x66, 0x2E, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00,
],
13 => vec![
0x66, 0x66, 0x66, 0x66, 0x2E, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00,
],
14 => vec![
0x66, 0x66, 0x66, 0x66, 0x66, 0x2E, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00,
],
15 => vec![
0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x2E, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00,
0x00,
],
_ => {
let mut result = Vec::with_capacity(pad_size);
let mut remaining = pad_size;
while remaining >= 15 {
result.extend_from_slice(&amd_alignment_nops(15));
remaining -= 15;
}
if remaining > 0 {
result.extend_from_slice(&amd_alignment_nops(remaining));
}
result
}
}
}
pub fn validate_nop_sled(sled: &[u8]) -> Result<(), String> {
if sled.is_empty() {
return Ok(());
}
let mut i = 0;
while i < sled.len() {
let remaining = sled.len() - i;
let (valid, consumed) = is_valid_nop(&sled[i..]);
if !valid {
return Err(format!(
"Invalid NOP sequence at byte offset {}: byte 0x{:02X} is not a valid NOP",
i, sled[i]
));
}
if consumed == 0 || consumed > remaining {
return Err(format!(
"NOP decoder error at offset {}: consumed={}, remaining={}",
i, consumed, remaining
));
}
i += consumed;
}
Ok(())
}
fn is_valid_nop(data: &[u8]) -> (bool, usize) {
if data.is_empty() {
return (false, 0);
}
if data[0] == 0x90 {
return (true, 1);
}
let mut pos = 0;
let mut has_66 = false;
let mut has_segment = false;
while pos < data.len() {
match data[pos] {
0x66 => {
has_66 = true;
pos += 1;
}
0x2E | 0x3E | 0x26 | 0x64 | 0x65 | 0x67 => {
has_segment = true;
pos += 1;
}
0xF0 | 0xF2 | 0xF3 => {
pos += 1;
}
_ => break,
}
}
if pos >= data.len() {
return (false, pos);
}
if pos + 1 < data.len() && data[pos] == 0x0F && data[pos + 1] == 0x1F {
pos += 2;
if pos >= data.len() {
return (false, pos);
}
let modrm = data[pos];
pos += 1;
let mod_bits = (modrm >> 6) & 0x03;
let rm = modrm & 0x07;
if rm == 0x04 && mod_bits != 0x03 {
if pos >= data.len() {
return (false, pos);
}
let sib = data[pos];
pos += 1;
let base = sib & 0x07;
if base == 0x05 && mod_bits == 0x00 {
if pos + 4 > data.len() {
return (false, pos);
}
pos += 4;
}
}
match mod_bits {
0x01 => {
if pos >= data.len() {
return (false, pos);
}
pos += 1;
}
0x02 => {
if pos + 4 > data.len() {
return (false, pos);
}
pos += 4;
}
_ => {}
}
return (true, pos);
}
(false, pos.max(1))
}
pub fn is_nop_prefix(byte: u8) -> bool {
matches!(
byte,
0x66 | 0x67 | 0x2E | 0x3E | 0x26 | 0x64 | 0x65 | 0xF0 | 0xF2 | 0xF3 )
}
pub fn optimize_nop_sled(sled: &[u8], microarch: NopMicroArch) -> Vec<u8> {
if sled.len() <= 3 {
return sled.to_vec();
}
let preferred_len = select_optimal_nop_length(sled.len(), microarch, false);
if preferred_len == 0 || preferred_len >= sled.len() {
return sled.to_vec();
}
let mut result = Vec::with_capacity(sled.len());
let mut remaining = sled.len();
while remaining >= preferred_len {
let nop = intel_alignment_nops(preferred_len);
result.extend_from_slice(&nop);
remaining -= preferred_len;
}
if remaining > 0 {
let nop = intel_alignment_nops(remaining);
result.extend_from_slice(&nop);
}
result
}
pub fn minimize_nop_count(sled: &[u8]) -> Vec<u8> {
if sled.len() <= 15 {
return intel_alignment_nops(sled.len());
}
let mut result = Vec::with_capacity(sled.len());
let mut remaining = sled.len();
while remaining >= 15 {
result.extend_from_slice(&intel_alignment_nops(15));
remaining -= 15;
}
if remaining > 0 {
result.extend_from_slice(&intel_alignment_nops(remaining));
}
result
}
impl fmt::Display for NopSequence {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "NOP({}B) [", self.len)?;
for (i, b) in self.bytes.iter().enumerate() {
if i > 0 {
write!(f, " ")?;
}
write!(f, "{:02X}", b)?;
}
write!(f, "] variant={:?} dc={}", self.variant, self.decode_cost)
}
}
impl X86NopSled {
pub fn stats_summary(&self) -> String {
format!(
"X86NopSled Stats:\n\
- Total NOP bytes: {}\n\
- Single-byte NOPs: {}\n\
- Multi-byte NOPs: {}\n\
- Alignment bytes: {}\n\
- Function alignments: {}\n\
- Loop alignments: {}\n\
- Branch alignments: {}\n\
- Current offset: 0x{:X}\n\
- Strategy: {:?}\n\
- Microarch: {:?}",
self.stats.total_nop_bytes,
self.stats.single_byte_nops,
self.stats.multi_byte_nops,
self.stats.alignment_bytes,
self.stats.function_alignments,
self.stats.loop_alignments,
self.stats.branch_alignments,
self.current_offset,
self.strategy,
self.microarch,
)
}
pub fn disassemble_sled(sled: &[u8]) -> String {
let mut result = String::new();
let mut offset = 0usize;
while offset < sled.len() {
let remaining = sled.len() - offset;
let (valid, consumed) = is_valid_nop(&sled[offset..]);
if valid {
result.push_str(&format!(
" {:04X}: NOP ({})\n",
offset,
hex_str(&sled[offset..offset + consumed])
));
offset += consumed;
} else {
result.push_str(&format!(" {:04X}: ??? {:02X}\n", offset, sled[offset]));
offset += 1;
}
}
result
}
}
fn hex_str(bytes: &[u8]) -> String {
bytes
.iter()
.map(|b| format!("{:02X}", b))
.collect::<Vec<_>>()
.join(" ")
}
pub mod constant_nops {
pub const NOP1: [u8; 1] = [0x90];
pub const NOP2: [u8; 2] = [0x66, 0x90];
pub const NOP3: [u8; 3] = [0x0F, 0x1F, 0x00];
pub const NOP4: [u8; 4] = [0x0F, 0x1F, 0x40, 0x00];
pub const NOP5: [u8; 5] = [0x0F, 0x1F, 0x44, 0x00, 0x00];
pub const NOP6: [u8; 6] = [0x66, 0x0F, 0x1F, 0x44, 0x00, 0x00];
pub const NOP7: [u8; 7] = [0x0F, 0x1F, 0x80, 0x00, 0x00, 0x00, 0x00];
pub const NOP8: [u8; 8] = [0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00];
pub const NOP9: [u8; 9] = [0x66, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00];
}
pub fn make_test_nop_sled() -> X86NopSled {
X86NopSled::default_for(true)
}
pub fn make_intel_nop_sled() -> X86NopSled {
X86NopSled::new(true, NopSledStrategy::Intel, NopMicroArch::Generic)
}
pub fn make_amd_nop_sled() -> X86NopSled {
X86NopSled::new(true, NopSledStrategy::Amd, NopMicroArch::Generic)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_nop_sled_single_byte() {
let mut sled = make_test_nop_sled();
sled.strategy = NopSledStrategy::SingleByte;
let bytes = sled.generate_sled(10);
assert_eq!(bytes.len(), 10);
assert!(bytes.iter().all(|&b| b == 0x90));
}
#[test]
fn test_nop_sled_optimal() {
let mut sled = make_test_nop_sled();
for size in [0, 1, 5, 9, 15, 16, 32, 64, 128] {
let bytes = sled.generate_sled(size);
assert_eq!(bytes.len(), size, "Size mismatch for {} bytes", size);
}
}
#[test]
fn test_intel_nop_sequences_all_sizes() {
for len in 1..=15 {
let seq = intel_nop_sequence(len);
assert!(seq.is_some(), "Missing Intel NOP for {} bytes", len);
let seq = seq.unwrap();
assert_eq!(seq.bytes.len(), len);
}
}
#[test]
fn test_amd_nop_sequences_all_sizes() {
for len in 1..=15 {
let seq = amd_nop_sequence(len);
assert!(seq.is_some(), "Missing AMD NOP for {} bytes", len);
let seq = seq.unwrap();
assert_eq!(seq.bytes.len(), len);
}
}
#[test]
fn test_optimal_nop_sequence() {
for len in 1..=15 {
let seq = optimal_nop_sequence(len);
assert!(seq.is_some(), "Missing optimal NOP for {} bytes", len);
}
}
#[test]
fn test_intel_alignment_nops() {
for size in 0..32 {
let nops = intel_alignment_nops(size);
assert_eq!(nops.len(), size);
}
}
#[test]
fn test_amd_alignment_nops() {
for size in 0..32 {
let nops = amd_alignment_nops(size);
assert_eq!(nops.len(), size);
}
}
#[test]
fn test_validate_nop_sled() {
assert!(validate_nop_sled(&[0x90]).is_ok());
let nop5 = intel_alignment_nops(5);
assert!(validate_nop_sled(&nop5).is_ok());
assert!(validate_nop_sled(&[]).is_ok());
assert!(validate_nop_sled(&[0xCC]).is_err()); let large = intel_alignment_nops(64);
assert!(validate_nop_sled(&large).is_ok());
}
#[test]
fn test_validate_nop_sled_sizes() {
for size in 1..64 {
let sled = intel_alignment_nops(size);
assert_eq!(sled.len(), size);
let result = validate_nop_sled(&sled);
assert!(
result.is_ok(),
"Validation failed for {} bytes: {:?}",
size,
result
);
}
}
#[test]
fn test_is_nop_prefix() {
assert!(is_nop_prefix(0x66));
assert!(is_nop_prefix(0x67));
assert!(is_nop_prefix(0x2E));
assert!(is_nop_prefix(0x3E));
assert!(is_nop_prefix(0x26));
assert!(is_nop_prefix(0x64));
assert!(is_nop_prefix(0x65));
assert!(is_nop_prefix(0xF0));
assert!(is_nop_prefix(0xF2));
assert!(is_nop_prefix(0xF3));
assert!(!is_nop_prefix(0x90));
assert!(!is_nop_prefix(0x00));
}
#[test]
fn test_alignment_to_16() {
let mut sled = make_test_nop_sled();
sled.current_offset = 3;
let pad = sled.align_to_16();
assert_eq!(pad.len(), 13); assert_eq!(sled.current_offset, 16);
}
#[test]
fn test_alignment_to_64() {
let mut sled = make_test_nop_sled();
sled.current_offset = 60;
let pad = sled.align_to_64();
assert_eq!(pad.len(), 4); }
#[test]
fn test_no_alignment_needed() {
let mut sled = make_test_nop_sled();
sled.current_offset = 16;
let pad = sled.align_to_16();
assert!(pad.is_empty());
}
#[test]
fn test_win64_hotpatch_sled() {
let sled = generate_hotpatch_sled(7, HotPatchStyle::Win64, NopSledStrategy::Optimal, true);
assert_eq!(sled.len(), 7);
assert_eq!(sled[0], 0x89); assert_eq!(sled[1], 0xFF); }
#[test]
fn test_ftrace_sled() {
let sled = generate_hotpatch_sled(
5,
HotPatchStyle::LinuxFtrace,
NopSledStrategy::Optimal,
true,
);
assert_eq!(sled.len(), 5);
assert_eq!(&sled[0..5], &[0x0F, 0x1F, 0x44, 0x00, 0x00]);
}
#[test]
fn test_livepatch_sled() {
let sled = generate_hotpatch_sled(
5,
HotPatchStyle::LinuxLivepatch,
NopSledStrategy::Optimal,
true,
);
assert_eq!(sled.len(), 5);
}
#[test]
fn test_jit_sled() {
let sled = generate_hotpatch_sled(
16,
HotPatchStyle::JitCompiler,
NopSledStrategy::Optimal,
true,
);
assert_eq!(sled.len(), 16);
}
#[test]
fn test_loop_alignment_target() {
let target = loop_alignment_target(NopMicroArch::Skylake);
assert_eq!(target.boundary(), 16);
let target_zen4 = loop_alignment_target(NopMicroArch::Zen4);
assert_eq!(target_zen4.boundary(), 32);
}
#[test]
fn test_is_loop_alignment_beneficial() {
assert!(!is_loop_alignment_beneficial(4, NopMicroArch::Skylake));
assert!(is_loop_alignment_beneficial(32, NopMicroArch::Skylake));
assert!(is_loop_alignment_beneficial(256, NopMicroArch::Zen4));
assert!(!is_loop_alignment_beneficial(1024, NopMicroArch::Zen4));
}
#[test]
fn test_nop_sled_decode_cost() {
let cost = nop_sled_decode_cost(&[1, 1, 1, 1], 4);
assert_eq!(cost, 1);
let cost2 = nop_sled_decode_cost(&[1, 1, 1, 1, 1], 4);
assert!(cost2 >= 2); }
#[test]
fn test_select_optimal_nop_length() {
assert_eq!(
select_optimal_nop_length(3, NopMicroArch::Skylake, false),
3
);
assert_eq!(
select_optimal_nop_length(8, NopMicroArch::Skylake, false),
8
);
assert_eq!(
select_optimal_nop_length(1, NopMicroArch::Skylake, false),
1
);
}
#[test]
fn test_nop_variant_official() {
assert!(NopVariant::XchgEaxEax.is_official_intel());
assert!(NopVariant::LongNop0F1F.is_official_intel());
assert!(!NopVariant::MultiPrefixNop.is_official_intel());
}
#[test]
fn test_nop_variant_amd() {
assert!(NopVariant::SegmentNop.is_amd_recommended());
assert!(!NopVariant::MultiPrefixNop.is_amd_recommended());
}
#[test]
fn test_optimize_nop_sled() {
let original = vec![0x90; 16]; let optimized = optimize_nop_sled(&original, NopMicroArch::Skylake);
assert_eq!(optimized.len(), 16);
assert!(optimized.contains(&0x0F));
}
#[test]
fn test_minimize_nop_count() {
let original = vec![0x90; 30];
let minimized = minimize_nop_count(&original);
assert_eq!(minimized.len(), 30);
}
#[test]
fn test_disassemble_sled() {
let sled = intel_alignment_nops(5);
let disasm = X86NopSled::disassemble_sled(&sled);
assert!(disasm.contains("NOP"));
assert!(disasm.contains("0000"));
}
#[test]
fn test_stats_summary() {
let mut sled = make_test_nop_sled();
sled.generate_sled(16);
let summary = sled.stats_summary();
assert!(summary.contains("Total NOP bytes: 16"));
assert!(summary.contains("Strategy: Optimal"));
}
#[test]
fn test_microarch_decode_width() {
assert_eq!(NopMicroArch::Skylake.decode_width(), 5);
assert_eq!(NopMicroArch::Zen4.decode_width(), 4);
assert_eq!(NopMicroArch::AlderLakeP.decode_width(), 6);
assert_eq!(NopMicroArch::Goldmont.decode_width(), 3);
assert_eq!(NopMicroArch::Bonnell.decode_width(), 2);
}
#[test]
fn test_microarch_cache_line() {
assert_eq!(NopMicroArch::Skylake.cache_line_size(), 64);
assert_eq!(NopMicroArch::Zen4.cache_line_size(), 64);
}
#[test]
fn test_nop_display() {
let seq = intel_nop_sequence(5).unwrap();
let display = format!("{}", seq);
assert!(display.contains("NOP(5B)"));
assert!(display.contains("0F 1F 44 00 00"));
}
#[test]
fn test_constant_nops() {
assert_eq!(constant_nops::NOP1.len(), 1);
assert_eq!(constant_nops::NOP1[0], 0x90);
assert_eq!(constant_nops::NOP5.len(), 5);
assert_eq!(&constant_nops::NOP5[..], &[0x0F, 0x1F, 0x44, 0x00, 0x00]);
}
#[test]
fn test_function_entry_alignment_64() {
let padding = generate_function_entry_alignment(
50,
64,
NopSledStrategy::Optimal,
NopMicroArch::Generic,
true,
);
assert_eq!(padding.len(), 14); }
#[test]
fn test_loop_header_alignment_generation() {
let (padding, target) = generate_loop_header_alignment(10, NopMicroArch::Zen4, true);
assert_eq!(target.boundary(), 32);
assert!(!padding.is_empty());
}
#[test]
fn test_branch_target_alignment() {
let padding = generate_branch_target_alignment(7, false, NopMicroArch::Skylake, true);
assert_eq!(padding.len(), 9); }
#[test]
fn test_compat_nop_sequence() {
for len in 1..=9 {
let seq = compat_nop_sequence(len);
assert!(seq.is_some());
}
assert!(compat_nop_sequence(10).is_none());
}
#[test]
fn test_nop_uop_count() {
assert_eq!(nop_uop_count(5, NopMicroArch::Skylake), 1);
assert_eq!(nop_uop_count(5, NopMicroArch::Zen4), 1);
}
#[test]
fn test_alignment_target_max_nops() {
assert_eq!(AlignmentTarget::FunctionEntry16.max_nop_bytes(), 15);
assert_eq!(AlignmentTarget::LoopHeader32.max_nop_bytes(), 24);
assert_eq!(AlignmentTarget::NoAlignment.max_nop_bytes(), 0);
}
#[test]
fn test_is_valid_nop_edge_cases() {
let (valid, _) = is_valid_nop(&[0x66]);
assert!(!valid);
let (valid, _) = is_valid_nop(&[0x0F]);
assert!(!valid);
let (valid, _) = is_valid_nop(&[0x0F, 0x1F]);
assert!(!valid);
}
#[test]
fn test_advance_offset() {
let mut sled = make_test_nop_sled();
sled.advance_offset(42);
assert_eq!(sled.current_offset, 42);
}
#[test]
fn test_reset_offset() {
let mut sled = make_test_nop_sled();
sled.advance_offset(100);
sled.reset_offset();
assert_eq!(sled.current_offset, 0);
}
#[test]
fn test_hotpatch_style_enum() {
let styles = [
HotPatchStyle::None,
HotPatchStyle::Win64,
HotPatchStyle::LinuxFtrace,
HotPatchStyle::LinuxLivepatch,
HotPatchStyle::Generic,
HotPatchStyle::JitCompiler,
];
for (i, a) in styles.iter().enumerate() {
for (j, b) in styles.iter().enumerate() {
if i == j {
assert_eq!(a, b);
} else {
assert_ne!(a, b);
}
}
}
}
#[test]
fn test_nop_sled_strategy_enum() {
let strategies = [
NopSledStrategy::SingleByte,
NopSledStrategy::Optimal,
NopSledStrategy::Intel,
NopSledStrategy::Amd,
NopSledStrategy::MinSize,
NopSledStrategy::MinDecode,
NopSledStrategy::Compat,
NopSledStrategy::MaxPatchable,
];
for (i, a) in strategies.iter().enumerate() {
for (j, b) in strategies.iter().enumerate() {
if i == j {
assert_eq!(a, b);
} else {
assert_ne!(a, b);
}
}
}
}
#[test]
fn test_microarch_prefers_long_nops() {
assert!(NopMicroArch::Skylake.prefers_long_nops());
assert!(NopMicroArch::Zen4.prefers_long_nops());
assert!(NopMicroArch::AlderLakeP.prefers_long_nops());
assert!(!NopMicroArch::Bonnell.prefers_long_nops());
}
#[test]
fn test_loop_alignment_target_all_microarchs() {
let microarchs = [
NopMicroArch::Bonnell,
NopMicroArch::Silvermont,
NopMicroArch::Goldmont,
NopMicroArch::Tremont,
NopMicroArch::Skylake,
NopMicroArch::IceLake,
NopMicroArch::Zen3,
NopMicroArch::Zen5,
];
for ma in µarchs {
let target = loop_alignment_target(*ma);
assert!(target.boundary() >= 8);
}
}
#[test]
fn test_hotpatch_sled_all_styles() {
for style in &[
HotPatchStyle::None,
HotPatchStyle::Win64,
HotPatchStyle::LinuxFtrace,
HotPatchStyle::LinuxLivepatch,
HotPatchStyle::Generic,
HotPatchStyle::JitCompiler,
] {
let sled = generate_hotpatch_sled(16, *style, NopSledStrategy::Optimal, true);
if *style == HotPatchStyle::None {
assert!(sled.is_empty());
} else {
assert_eq!(sled.len(), 16);
assert!(validate_nop_sled(&sled).is_ok());
}
}
}
#[test]
fn test_is_nop_prefix_all() {
let prefixes: &[u8] = &[0x66, 0x67, 0x2E, 0x3E, 0x26, 0x64, 0x65, 0xF0, 0xF2, 0xF3];
for &p in prefixes {
assert!(is_nop_prefix(p), "0x{:02X} should be a NOP prefix", p);
}
}
#[test]
fn test_validate_nop_sled_complex() {
let mut sled = make_test_nop_sled();
sled.strategy = NopSledStrategy::Optimal;
let bytes = sled.generate_sled(67);
assert_eq!(bytes.len(), 67);
assert!(validate_nop_sled(&bytes).is_ok());
sled.strategy = NopSledStrategy::Intel;
let bytes = sled.generate_sled(67);
assert_eq!(bytes.len(), 67);
assert!(validate_nop_sled(&bytes).is_ok());
sled.strategy = NopSledStrategy::Amd;
let bytes = sled.generate_sled(67);
assert_eq!(bytes.len(), 67);
assert!(validate_nop_sled(&bytes).is_ok());
}
#[test]
fn test_sled_across_strategies() {
let strategies = [
NopSledStrategy::SingleByte,
NopSledStrategy::Optimal,
NopSledStrategy::Intel,
NopSledStrategy::Amd,
NopSledStrategy::MinSize,
NopSledStrategy::MinDecode,
NopSledStrategy::Compat,
NopSledStrategy::MaxPatchable,
];
for &strat in &strategies {
let mut sled = X86NopSled::new(true, strat, NopMicroArch::Generic);
for size in [0, 1, 2, 3, 7, 11, 15, 16, 32, 64, 100, 127] {
let bytes = sled.generate_sled(size);
assert_eq!(
bytes.len(),
size,
"Strategy {:?} failed for size {}",
strat,
size
);
}
}
}
#[test]
fn test_branch_target_alignment_multiple() {
for offset in 0..20 {
let padding =
generate_branch_target_alignment(offset, false, NopMicroArch::Skylake, true);
let new_offset = offset + padding.len() as u64;
if padding.len() <= 15 {
assert_eq!(new_offset % 16, 0);
}
}
}
#[test]
fn test_no_loop_alignment_when_too_much_padding() {
let (padding, _) = generate_loop_header_alignment(1, NopMicroArch::Zen4, true);
assert!(padding.is_empty());
}
#[test]
fn test_nop_sequence_display_format() {
let seq = amd_nop_sequence(10).unwrap();
let s = format!("{}", seq);
assert!(s.contains("NOP(10B)"));
assert!(s.contains("SegmentNop"));
}
#[test]
fn test_hex_str_utility() {
assert_eq!(hex_str(&[0x90]), "90");
assert_eq!(hex_str(&[0x0F, 0x1F, 0x00]), "0F 1F 00");
assert_eq!(hex_str(&[]), "");
}
#[test]
fn test_alignment_targets_boundary() {
assert_eq!(AlignmentTarget::FunctionEntry16.boundary(), 16);
assert_eq!(AlignmentTarget::FunctionEntry32.boundary(), 32);
assert_eq!(AlignmentTarget::FunctionEntry64.boundary(), 64);
assert_eq!(AlignmentTarget::LoopHeader16.boundary(), 16);
assert_eq!(AlignmentTarget::LoopHeader32.boundary(), 32);
assert_eq!(AlignmentTarget::LoopHeader64.boundary(), 64);
assert_eq!(AlignmentTarget::BranchTarget16.boundary(), 16);
assert_eq!(AlignmentTarget::BranchTarget32.boundary(), 32);
assert_eq!(AlignmentTarget::BasicBlock8.boundary(), 8);
assert_eq!(AlignmentTarget::NoAlignment.boundary(), 1);
}
#[test]
fn test_alignment_target_use_long_nops() {
assert!(AlignmentTarget::FunctionEntry16.use_long_nops());
assert!(!AlignmentTarget::BasicBlock8.use_long_nops());
assert!(!AlignmentTarget::NoAlignment.use_long_nops());
}
#[test]
fn test_nop_sled_32bit_vs_64bit() {
let mut sled_64 = make_test_nop_sled();
let mut sled_32 = X86NopSled::default_for(false);
let b64 = sled_64.generate_sled(16);
let b32 = sled_32.generate_sled(16);
assert_eq!(b64.len(), 16);
assert_eq!(b32.len(), 16);
}
#[test]
fn test_optimal_nop_prefers_lower_decode_cost() {
let seq10 = optimal_nop_sequence(10).unwrap();
assert_eq!(seq10.decode_cost, 1);
}
#[test]
fn test_function_entry_alignment_skip_when_aligned() {
let pad = generate_function_entry_alignment(
32,
16,
NopSledStrategy::Optimal,
NopMicroArch::Generic,
true,
);
assert!(pad.is_empty());
}
#[test]
fn test_nop_sled_validation_rejects_invalid() {
assert!(validate_nop_sled(&[0xCC]).is_err());
assert!(validate_nop_sled(&[0xC3]).is_err());
assert!(validate_nop_sled(&[0x90, 0xCC]).is_err());
}
#[test]
fn test_minimize_nop_count_large_sled() {
let original = vec![0x90; 150];
let minimized = minimize_nop_count(&original);
assert_eq!(minimized.len(), 150);
}
#[test]
fn test_decode_cost_monotonicity() {
let c1 = nop_sled_decode_cost(&[5], 4);
let c2 = nop_sled_decode_cost(&[5, 5], 4);
assert!(c2 >= c1);
}
#[test]
fn test_nop_uop_count_all_microarchs() {
let microarchs = [
NopMicroArch::Skylake,
NopMicroArch::Zen4,
NopMicroArch::Bonnell,
NopMicroArch::Goldmont,
NopMicroArch::Tremont,
];
for ma in µarchs {
assert_eq!(nop_uop_count(5, *ma), 1);
}
}
#[test]
fn test_advance_offset_then_align() {
let mut sled = make_test_nop_sled();
sled.advance_offset(5);
assert_eq!(sled.current_offset, 5);
let pad = sled.align_to_16();
assert_eq!(pad.len(), 11); }
#[test]
fn test_constant_nops_verification() {
use constant_nops::*;
assert!(validate_nop_sled(&NOP1).is_ok());
assert!(validate_nop_sled(&NOP2).is_ok());
assert!(validate_nop_sled(&NOP3).is_ok());
assert!(validate_nop_sled(&NOP4).is_ok());
assert!(validate_nop_sled(&NOP5).is_ok());
assert!(validate_nop_sled(&NOP6).is_ok());
assert!(validate_nop_sled(&NOP7).is_ok());
assert!(validate_nop_sled(&NOP8).is_ok());
assert!(validate_nop_sled(&NOP9).is_ok());
}
#[test]
fn test_intel_vs_amd_nop_10_byte_differs() {
let intel = intel_nop_sequence(10).unwrap();
let amd = amd_nop_sequence(10).unwrap();
assert_ne!(intel.bytes, amd.bytes);
assert!(validate_nop_sled(&intel.bytes).is_ok());
assert!(validate_nop_sled(&amd.bytes).is_ok());
}
}