pelite\pe64/
scanner.rs

1/*!
2Pattern Scanner.
3
4See the [`pattern`](../../pattern/index.html) module for more information about patterns.
5
6# Examples
7
8```
9# #![allow(unused_variables)]
10use pelite::pe64::{Pe, PeFile};
11use pelite::pattern as pat;
12
13# #[allow(dead_code)]
14fn example(file: PeFile<'_>, pat: &[pat::Atom]) {
15	// Gets the pattern scanner interface
16	let scanner = file.scanner();
17
18	// Capture references in the pattern in a save array
19	let mut save = [0; 8];
20
21	// Finds a singular code match
22	if scanner.finds_code(pat, &mut save) {
23		println!("{:x?}", save);
24	}
25
26	// Finds all the code matches for the pattern
27	let mut matches = scanner.matches_code(pat);
28	while matches.next(&mut save) {
29		println!("{:x?}", save);
30	}
31}
32```
33*/
34
35use std::{cmp, mem, ptr};
36use std::ops::Range;
37
38use crate::{Pod, pattern as pat};
39use crate::util::AlignTo;
40
41use super::{Align, Rva, Pe, image::*};
42
43/// Size of the prefix buffer for search optimization.
44const QS_BUF_LEN: usize = 16;
45
46//----------------------------------------------------------------
47
48/// Pattern scanner.
49///
50/// For more information see the [module-level documentation](index.html).
51#[derive(Copy, Clone)]
52pub struct Scanner<P> {
53	pe: P,
54}
55impl<'a, P: Pe<'a>> Scanner<P> {
56	pub(crate) fn new(pe: P) -> Scanner<P> {
57		Scanner { pe }
58	}
59	/// Finds the unique match for the pattern in the given range.
60	///
61	/// The pattern may contain instructions to capture interesting addresses, these are stored in the save array.
62	/// Out of bounds stores are simply ignored, ensure the save array is large enough for the given pattern.
63	///
64	/// In case of mismatch, ie. returns false, the save array is still overwritten with temporary data and should be considered trashed.
65	/// Keep a copy, invoke with a fresh save array or reexecute the pattern at the saved cursor to get around this.
66	///
67	/// Returns `false` if no match is found or multiple matches are found to prevent subtle bugs where a pattern goes stale by not being unique any more.
68	///
69	/// Use `matches(pat, range).next(save)` if just the first match is desired.
70	pub fn finds(&self, pat: &[pat::Atom], range: Range<Rva>, save: &mut [Rva]) -> bool {
71		let mut matches = self.matches(pat, range);
72		if !matches.next(save) {
73			return false;
74		}
75		// Disallow more than one match as it indicates the signature isn't unique enough
76		// HOTFIX: It is important to not disturb the caller's save array for this check
77		// It is hard to recover the actual cursor used to match the pattern:
78		// * Store the actual cursor used in the matches object
79		// * Assume the first element in the save array is the cursor
80		// * Pass empty save array as a dummy
81		!matches.next(&mut save[..0])
82	}
83	/// Finds the unique code match for the pattern.
84	///
85	/// Restricts the range to the code section. See [`finds`](#finds) for more information.
86	pub fn finds_code(&self, pat: &[pat::Atom], save: &mut [Rva]) -> bool {
87		self.finds(pat, self.pe.headers().code_range(), save)
88	}
89	/// Returns an iterator over the matches of a pattern within the given range.
90	pub fn matches<'pat>(&self, pat: &'pat [pat::Atom], range: Range<Rva>) -> Matches<'pat, P> {
91		Matches { scanner: *self, pat, range, hits: 0 }
92	}
93	/// Returns an iterator over the code matches of a pattern.
94	///
95	/// Restricts the range to the code section. See [`matches`](#matches) for more information.
96	pub fn matches_code<'pat>(&self, pat: &'pat [pat::Atom]) -> Matches<'pat, P> {
97		self.matches(pat, self.pe.headers().code_range())
98	}
99	/// Pattern interpreter, returns if the pattern matches the binary image at the given rva.
100	///
101	/// The pattern may contain instructions to capture interesting addresses, these are stored in the save array.
102	/// Out of bounds stores are simply ignored, ensure the save array is large enough for the given pattern.
103	///
104	/// In case of mismatch, ie. returns false, the save array is still overwritten with temporary data and should be considered trashed.
105	/// Keep a copy, invoke with a fresh save array or reexecute the pattern at the saved cursor to get around this.
106	pub fn exec(&self, cursor: Rva, pat: &[pat::Atom], save: &mut [Rva]) -> bool {
107		Exec { pe: self.pe, pat, cursor, pc: 0 }.exec(save)
108	}
109}
110
111//----------------------------------------------------------------
112
113trait Scan<'a>: Copy {
114	fn read<T: Copy + Pod>(self, rva: Rva) -> Option<T>;
115	fn pointer(self, va: Va) -> Option<Rva>;
116	fn slice(self, rva: Rva) -> Option<&'a [u8]>;
117}
118
119impl<'a, P: Pe<'a>> Scan<'a> for P {
120	fn read<T: Copy + Pod>(self, rva: Rva) -> Option<T> {
121		self.derva_copy(rva).ok()
122	}
123	fn pointer(self, va: Va) -> Option<Rva> {
124		self.va_to_rva(va).ok()
125	}
126	fn slice(self, rva: Rva) -> Option<&'a [u8]> {
127		self.slice_bytes(rva).ok()
128	}
129}
130
131impl<'a> Scan<'a> for &'a [u8] {
132	fn read<T: Copy + Pod>(self, rva: Rva) -> Option<T> {
133		let bytes = self.get(rva as usize..(rva as usize + mem::size_of::<T>()))?;
134		let ptr = bytes.as_ptr() as *const T;
135		Some(unsafe { ptr::read_unaligned(ptr) })
136	}
137	fn pointer(self, va: Va) -> Option<Rva> {
138		Some(va as Rva)
139	}
140	fn slice(self, rva: Rva) -> Option<&'a [u8]> {
141		self.get(rva as usize..)
142	}
143}
144
145#[derive(Clone)]
146struct Exec<'pat, P> {
147	pe: P,
148	pat: &'pat [pat::Atom],
149	cursor: Rva,
150	pc: usize,
151}
152impl<'a, 'pat, P: Scan<'a>> Exec<'pat, P> {
153	fn exec(&mut self, save: &mut [Rva]) -> bool {
154		const SKIP_VA: u32 = mem::size_of::<Va>() as u32;
155		let mut mask = 0xff;
156		let mut ext_range = 0u32;
157		while let Some(atom) = self.pat.get(self.pc).cloned() {
158			self.pc += 1;
159			match atom {
160				pat::Atom::Byte(pat_byte) => {
161					match self.pe.read::<u8>(self.cursor) {
162						Some(byte) if byte & mask == pat_byte & mask => (),
163						_ => return false,
164					}
165					mask = 0xff;
166					self.cursor += 1;
167				},
168				pat::Atom::Save(slot) => {
169					if let Some(slot) = save.get_mut(slot as usize) {
170						*slot = self.cursor;
171					}
172				},
173				pat::Atom::Push(skip) => {
174					let skip = ext_range + skip as u32;
175					let skip = if skip == 0 { SKIP_VA } else { skip };
176					let cursor = self.cursor.wrapping_add(skip);
177					if !self.exec(save) {
178						return false;
179					}
180					mask = 0xff;
181					ext_range = 0;
182					self.cursor = cursor;
183				},
184				pat::Atom::Pop => {
185					return true;
186				},
187				pat::Atom::Fuzzy(pat_mask) => {
188					mask = pat_mask;
189				},
190				pat::Atom::Skip(skip) => {
191					let skip = ext_range + skip as u32;
192					let skip = if skip == 0 { SKIP_VA } else { skip };
193					let cursor = self.cursor.wrapping_add(skip);
194					ext_range = 0;
195					self.cursor = cursor;
196				},
197				pat::Atom::Back(back) => {
198					let rewind = ext_range + back as u32;
199					let rewind = if rewind == 0 { SKIP_VA } else { rewind };
200					let cursor = self.cursor.wrapping_sub(rewind);
201					ext_range = 0;
202					self.cursor = cursor;
203				},
204				pat::Atom::Rangext(ext) => {
205					ext_range = ext as u32 * 256;
206				},
207				pat::Atom::Many(limit) => {
208					let limit = ext_range + limit as u32;
209					return self.exec_many(save, limit);
210				},
211				pat::Atom::Jump1 => {
212					if let Some(sbyte) = self.pe.read::<i8>(self.cursor) {
213						self.cursor = self.cursor.wrapping_add(sbyte as Rva).wrapping_add(1);
214					}
215					else {
216						return false;
217					}
218				},
219				pat::Atom::Jump4 => {
220					if let Some(sdword) = self.pe.read::<i32>(self.cursor) {
221						self.cursor = self.cursor.wrapping_add(sdword as Rva).wrapping_add(4);
222					}
223					else {
224						return false;
225					}
226				},
227				pat::Atom::Ptr => {
228					if let Some(rva) = self.pe.read::<Va>(self.cursor).and_then(|va| self.pe.pointer(va)) {
229						self.cursor = rva;
230					}
231					else {
232						return false;
233					}
234				},
235				pat::Atom::Pir(slot) => {
236					if let Some(sdword) = self.pe.read::<i32>(self.cursor) {
237						let base = save.get(slot as usize).cloned().unwrap_or(self.cursor);
238						self.cursor = base.wrapping_add(sdword as Rva);
239					}
240					else {
241						return false;
242					}
243				},
244				pat::Atom::VTypeName => {
245					branch! {
246						pe32 {
247							fn get<'a, S: Scan<'a>>(scan: S, cursor: u32) -> Option<u32> {
248								if (cursor & 3) != 0 { return None; }
249								let col_ptr = scan.read::<u32>(cursor.wrapping_sub(4))?;
250								let col_rva = scan.pointer(col_ptr)?;
251								let type_ptr = scan.read::<u32>(col_rva.wrapping_add(12))?;
252								let type_rva = scan.pointer(type_ptr)?;
253								Some(type_rva.wrapping_add(8))
254							}
255						}
256						pe64 {
257							fn get<'a, S: Scan<'a>>(scan: S, cursor: u32) -> Option<u32> {
258								if (cursor & 7) != 0 { return None; }
259								let col_ptr = scan.read::<u64>(cursor.wrapping_sub(8))?;
260								let col_rva = scan.pointer(col_ptr)?;
261								let type_rva = scan.read::<u32>(col_rva.wrapping_add(12))?;
262								Some(type_rva.wrapping_add(16))
263							}
264						}
265					}
266					if let Some(cursor) = get(self.pe, self.cursor) {
267						self.cursor = cursor;
268					}
269					else {
270						return false;
271					}
272				},
273				pat::Atom::Check(slot) => {
274					if let Some(&rva) = save.get(slot as usize) {
275						if rva != self.cursor {
276							return false;
277						}
278					}
279				},
280				pat::Atom::Aligned(align) => {
281					if !self.cursor.aligned_to(1 << align as u32) {
282						return false;
283					}
284				},
285				pat::Atom::ReadU8(slot) => {
286					if let Some(byte) = self.pe.read::<u8>(self.cursor) {
287						if let Some(slot) = save.get_mut(slot as usize) {
288							*slot = byte as Rva;
289						}
290						self.cursor = self.cursor.wrapping_add(1);
291					}
292					else {
293						return false;
294					}
295				},
296				pat::Atom::ReadI8(slot) => {
297					if let Some(sbyte) = self.pe.read::<i8>(self.cursor) {
298						if let Some(slot) = save.get_mut(slot as usize) {
299							*slot = sbyte as Rva;
300						}
301						self.cursor = self.cursor.wrapping_add(1);
302					}
303					else {
304						return false;
305					}
306				},
307				pat::Atom::ReadU16(slot) => {
308					if let Some(word) = self.pe.read::<u16>(self.cursor) {
309						if let Some(slot) = save.get_mut(slot as usize) {
310							*slot = word as Rva;
311						}
312						self.cursor = self.cursor.wrapping_add(2);
313					}
314					else {
315						return false;
316					}
317				},
318				pat::Atom::ReadI16(slot) => {
319					if let Some(sword) = self.pe.read::<i16>(self.cursor) {
320						if let Some(slot) = save.get_mut(slot as usize) {
321							*slot = sword as Rva;
322						}
323						self.cursor = self.cursor.wrapping_add(2);
324					}
325					else {
326						return false;
327					}
328				},
329				pat::Atom::ReadU32(slot) | pat::Atom::ReadI32(slot) => {
330					if let Some(dword) = self.pe.read::<Rva>(self.cursor) {
331						if let Some(slot) = save.get_mut(slot as usize) {
332							*slot = dword;
333						}
334						self.cursor = self.cursor.wrapping_add(4);
335					}
336					else {
337						return false;
338					}
339				},
340				pat::Atom::Zero(slot) => {
341					if let Some(slot) = save.get_mut(slot as usize) {
342						*slot = 0;
343					}
344				},
345				pat::Atom::Case(next) => {
346					let pc = self.pc;
347					let cursor = self.cursor;
348					if !self.exec(save) {
349						self.pc = pc + next as usize;
350						self.cursor = cursor;
351					}
352				},
353				pat::Atom::Break(next) => {
354					self.pc = self.pc + next as usize;
355					return true;
356				},
357				pat::Atom::Nop => {
358				},
359			}
360		}
361		return true;
362	}
363	fn exec_many(&mut self, save: &mut [Rva], limit: u32) -> bool {
364		// Capture the current cursor and PC to restore while trying
365		let cursor = self.cursor;
366		let pc = self.pc;
367		// Slice a section of bytes to limit the scan to
368		let bytes = match self.pe.slice(cursor) {
369			Some(bytes) if limit == 0 => bytes,
370			Some(bytes) => &bytes[..cmp::min(limit as usize, bytes.len())],
371			None => return false,
372		};
373		// Peek at a byte to match on
374		let mut peek = None;
375		for &atom in &self.pat[pc..] {
376			match atom {
377				pat::Atom::Byte(byte) => {
378					peek = Some(byte);
379					break;
380				},
381				pat::Atom::Save(_) => (),
382				_ => break,
383			}
384		}
385		// Optimize the next scan with memchr, happy path
386		if let Some(byte) = peek {
387			for i in 0..bytes.len() as u32 {
388				if bytes[i as usize] == byte {
389					self.cursor = cursor.wrapping_add(i);
390					self.pc = pc;
391					if self.exec(save) {
392						return true;
393					}
394				}
395			}
396		}
397		// Not optimizable, perf cliff!
398		else {
399			for i in 0..bytes.len() as u32 {
400				self.cursor = cursor.wrapping_add(i);
401				self.pc = pc;
402				if self.exec(save) {
403					return true;
404				}
405			}
406		}
407		// No match found, exec fails
408		return false;
409	}
410}
411
412//----------------------------------------------------------------
413
414/// An iterator over the matches of a pattern.
415///
416/// Created with the method [`matches`](struct.Scanner.html#method.matches).
417#[derive(Clone)]
418pub struct Matches<'pat, P> {
419	scanner: Scanner<P>,
420	pat: &'pat [pat::Atom],
421	range: Range<Rva>,
422	hits: u32,
423}
424
425impl<'a, 'pat, P: Pe<'a>> Matches<'pat, P> {
426	/// Gets the scanner instance.
427	pub fn scanner(&self) -> Scanner<P> {
428		self.scanner
429	}
430	/// Gets the pattern.
431	pub fn pattern(&self) -> &'pat [pat::Atom] {
432		self.pat
433	}
434	/// Gets the remaining range to scan.
435	pub fn range(&self) -> Range<Rva> {
436		self.range.clone()
437	}
438	/// Performance counter.
439	///
440	/// Number of times the slow [`exec`](struct.Scanner.html#method.exec) was invoked.
441	pub fn hits(&self) -> u32 {
442		self.hits
443	}
444	// Extract the prefix of bytes for optimizing the search
445	fn setup<'b>(&self, qsbuf: &'b mut [u8; QS_BUF_LEN]) -> &'b [u8] {
446		let mut qslen = 0usize;
447		for &atom in self.pat {
448			match atom {
449				pat::Atom::Byte(byte) => {
450					if qslen >= QS_BUF_LEN {
451						break;
452					}
453					qsbuf[qslen] = byte;
454					qslen += 1;
455				},
456				// These atoms do not interfere with optimizing search
457				pat::Atom::Save(_) => {},
458				pat::Atom::Aligned(_) => {},
459				pat::Atom::Nop => {},
460				// All other atoms interfere with optimizing search
461				_ => break,
462			}
463		}
464		&qsbuf[..qslen]
465	}
466	// Select the search strategy and execute the query.
467	fn strategy(&mut self, qsbuf: &[u8], slice: &'a [u8], save: &mut [Rva]) -> bool {
468		// FIXME! Profile the performance!
469		if qsbuf.len() == 0 {
470			self.strategy0(qsbuf, slice, save)
471		}
472		else if qsbuf.len() < 4 {
473			self.strategy1(qsbuf, slice, save)
474		}
475		else {
476			self.strategy2(qsbuf, slice, save)
477		}
478	}
479	// Strategy:
480	//  Cannot optimize the search, just (slowly) brute-force it.
481	fn strategy0(&mut self, _qsbuf: &[u8], slice: &'a [u8], save: &mut [Rva]) -> bool {
482		let end = self.range.start + slice.len() as u32;
483		while self.range.start < end {
484			let cursor = self.range.start;
485			self.hits += 1;
486			self.range.start += 1;
487			if self.scanner.exec(cursor, self.pat, save) {
488				return true;
489			}
490		}
491		return false;
492	}
493	// Strategy:
494	//  Prefix is too small for full blown quicksearch.
495	//  Memchr for the first byte and only eval pattern on potential matches.
496	fn strategy1(&mut self, qsbuf: &[u8], slice: &'a [u8], save: &mut [Rva]) -> bool {
497		let byte = qsbuf[0];
498		// Find all places with matching byte
499		// TODO! Replace with actual memchr
500		for i in slice.iter().enumerate().filter_map(|(i, &a)| if a == byte { Some(i as u32) } else { None }) {
501			self.hits += 1;
502			let cursor = self.range.start + i;
503			if self.scanner.exec(cursor, self.pat, save) {
504				self.range.start = cursor + 1;
505				return true;
506			}
507		}
508		self.range.start += slice.len() as u32;
509		return false;
510	}
511	// Strategy:
512	//  Full blown quicksearch for the prefix.
513	//  Most likely completely unnecessary but oh well... it was fun to write!
514	fn strategy2(&mut self, qsbuf: &[u8], slice: &'a [u8], save: &mut [Rva]) -> bool {
515		// Initialize jump table for quicksearch
516		let qslen = qsbuf.len();
517		let mut jumps = [qslen as u8; 256];
518		for i in 0..qslen - 1 {
519			jumps[qsbuf[i] as usize] = qslen as u8 - i as u8 - 1;
520		}
521		// Quicksearch baby!
522		let mut i = 0;
523		while i + qslen <= slice.len() {
524			let tbuf = &slice[i..i + qslen];
525			let last = tbuf[qslen - 1];
526			let jump = jumps[last as usize] as u32;
527			if qsbuf[qslen - 1] == last && tbuf == qsbuf {
528				self.hits += 1;
529				let cursor = self.range.start + i as u32;
530				if self.scanner.exec(cursor, self.pat, save) {
531					self.range.start = cursor + jump;
532					return true;
533				}
534			}
535			i += jump as usize;
536		}
537		// FIXME! Quicksearch stops too soon!
538		// It assumes there can't be another match in the last `qsbuf.len()` bytes
539		// Even though there clearly can since the scan range can be artificially limited
540		// For now let's ignore this edge case...
541		self.range.start += slice.len() as u32;
542		return false;
543	}
544	/// Finds the next match with the given save array.
545	pub fn next(&mut self, save: &mut [Rva]) -> bool {
546		// Build the quicksearch buffer
547		let mut qsbuf = [0u8; QS_BUF_LEN];
548		let qsbuf = self.setup(&mut qsbuf);
549
550		let image = self.scanner.pe.image();
551		match self.scanner.pe.align() {
552			Align::File => {
553				for section in self.scanner.pe.section_headers() {
554					// If section overlaps with the scanning range
555					if section.VirtualAddress < self.range.end && u32::wrapping_add(section.VirtualAddress, section.VirtualSize) > self.range.start {
556						// Get the image slice for this section for further processing, skipping corrupt section headers
557						if let Some(slice) = image.get(section.PointerToRawData as usize..u32::wrapping_add(section.PointerToRawData, section.SizeOfRawData) as usize) {
558							if self.next_section(qsbuf, section.VirtualAddress, slice, save) {
559								return true;
560							}
561						}
562					}
563				}
564				false
565			},
566			Align::Section => {
567				self.next_section(qsbuf, 0, image, save)
568			},
569		}
570	}
571	fn next_section(&mut self, qsbuf: &[u8], base: Rva, slice: &'a [u8], save: &mut [Rva]) -> bool {
572		// Let's talk about this code for a sec, for it has a problem.
573		// This method gets called for every section and is supposed to find matches in that section.
574		// A match is found, true is returned, all is well?
575		// Unfortunately, no, the caller is going to want to find the next match and calls us again.
576		// We have to continue from where we left off but that is currently not really possible.
577		// It is implicitly assumed that all sections are sorted by their virtual addresses.
578		// If this is not the case the code below will incorrectly clamp self.range.start and the section with lower virtual address will be skipped.
579		// Plz fix.
580
581		// Clamp the slice to the expected input scan range
582		self.range.start = cmp::max(base, self.range.start);
583		let start = self.range.start - base;
584		let end = cmp::min(base + slice.len() as u32, self.range.end) - base;
585
586		self.strategy(qsbuf, &slice[start as usize..end as usize], save)
587	}
588}
589
590//----------------------------------------------------------------
591
592#[cfg(test)]
593pub(crate) fn test<'a, P: Pe<'a>>(pe: P) -> crate::Result<()> {
594	use crate::pattern::Atom::*;
595	let scanner = pe.scanner();
596	let mut save = [0; 4];
597
598	let mut matches = scanner.matches_code(&[Save(0), Byte(0xE8), Push(4), Jump4, Save(1), Pop, Save(2)]);
599	while matches.next(&mut save) {
600		assert_eq!(save[0] + 5, save[2]);
601	}
602
603	let mut matches = scanner.matches_code(&[Jump1, Save(1), Byte(0x0F), Byte(0x0D)]);
604	while matches.next(&mut save) {}
605
606	scanner.finds_code(&[Byte(0x8B), Byte(0x01), Byte(0x8B), Byte(0x10), Byte(0xFF), Byte(0xD2)], &mut save);
607
608	Ok(())
609}
610
611// Test the core scanner engine
612#[test]
613fn exec_tests_parse_docs() {
614	use crate::pattern::{Atom, parse};
615
616	fn exec(bytes: &[u8], pat: &[Atom], save: &mut [Rva]) -> bool {
617		Exec { pe: bytes, pat, cursor: 0, pc: 0 }.exec(save)
618	}
619
620	{
621		let bytes = [0x55, 0x89, 0xe5, 0x83, 0xff, 0xec];
622		let pat = parse("55 89 e5 83 ? ec").unwrap();
623		assert!(exec(&bytes, &pat, &mut []));
624	}{
625		let bytes = [0xb9, 0x37, 0x13, 0x00, 0x00];
626		let pat = parse("b9 '37 13 00 00").unwrap();
627		let mut save = [0; 2];
628		assert!(exec(&bytes, &pat, &mut save));
629		assert_eq!(save[1], 1);
630	}{
631		let mut bytes = [0; 64];
632		bytes[0] = 0xb8;
633		bytes[17] = 0x50;
634		bytes[41] = 0xff;
635		let pat = parse("b8 [16] 50 [13-42] 'ff").unwrap();
636		let mut save = [0; 2];
637		assert!(exec(&bytes, &pat, &mut save));
638		assert_eq!(save[1], 41);
639	}{
640		let bytes = [0x31, 0xc0, 0x74, (-3i8) as u8];
641		let pat = parse("31 c0 74 % 'c0").unwrap();
642		let mut save = [0; 2];
643		assert!(exec(&bytes, &pat, &mut save));
644		assert_eq!(save[1], 1);
645	}{
646		let bytes = [0xe8, 10, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0x31, 0xc0, 0xc3];
647		let pat = parse("e8 $ '31 c0 c3").unwrap();
648		let mut save = [0; 2];
649		assert!(exec(&bytes, &pat, &mut save));
650		assert_eq!(save[1], 15);
651	}{
652		let bytes = [0x68, 10, 0, 0, 0, 1, 2, 3, 4, 5, 0x31, 0xc0, 0xc3];
653		let pat = parse("68 * '31 c0 c3").unwrap();
654		let mut save = [0; 2];
655		assert!(exec(&bytes, &pat, &mut save));
656		assert_eq!(save[1], 10);
657	}{
658		let bytes = b"\xb8\x0a\x00\x00\x00\x01\x02\x03\x04\x05STRING\x00";
659		let pat = parse(r#"b8 * "STRING" 00"#).unwrap();
660		assert!(exec(bytes, &pat, &mut []));
661	}{
662		let bytes = [0xe8, 10, 0, 0, 0, 0x83, 0xf0, 0x5c, 0xc3, 5, 6, 7, 8, 9, 10];
663		let pat = parse("e8 $ { ' } 83 f0 5c c3").unwrap();
664		let mut save = [0; 2];
665		assert!(exec(&bytes, &pat, &mut save));
666		assert_eq!(save[1], 15);
667	}{
668		let bytes = [0xe8, 0xff, 0xa0, 0x78, 0x56, 0x34, 0x12];
669		let pat = parse("e8 i1 a0 u4").unwrap();
670		let mut save = [0; 3];
671		assert!(exec(&bytes, &pat, &mut save));
672		assert_eq!(save[1], (-1i8) as u32);
673		assert_eq!(save[2], 0x12345678);
674	}{
675		let bytes1 = [0x83, 0xc0, 0x2a, 0x6a, 0x00, 0xe8];
676		let bytes2 = [0x83, 0xc0, 0x2a, 0x68, 0x00, 0x00, 0x00, 0x10, 0xe8];
677		let pat = parse("83 c0 2a ( 6a ? | 68 ? ? ? ? ) e8").unwrap();
678		assert!(exec(&bytes1, &pat, &mut []));
679		assert!(exec(&bytes2, &pat, &mut []));
680	}
681}