interfaces: pack both asm and bin into a single TestCase object
This commit is contained in:
Родитель
0ef77dcc6a
Коммит
c7d338c1a0
|
@ -1,6 +1,5 @@
|
|||
cmake-build-*/
|
||||
build/
|
||||
.idea/
|
||||
.vscode/
|
||||
.lsync*
|
||||
venv/
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
# Architecture
|
||||
|
||||
![architecture](Arch.png)
|
||||
|
||||
**Under construction**
|
||||
|
||||
[comment]: <> (## Instruction Set Spec)
|
||||
|
||||
[comment]: <> (This XML file: https://www.uops.info/xml.html originating from Intel XED (https://intelxed.github.io/))
|
||||
|
||||
[comment]: <> (Received from: `--instruction-set` (or `-s`) CLI argument.)
|
||||
|
||||
[comment]: <> (Passed down to: `Generator.__init__`)
|
||||
|
||||
|
||||
[comment]: <> (## Generator Initializer)
|
||||
|
||||
[comment]: <> (None so far.)
|
||||
|
||||
[comment]: <> (In future, may include test case templates, grammar, etc.)
|
||||
|
||||
[comment]: <> (## Test Case)
|
||||
|
||||
[comment]: <> (An assembly file. Currently, in Intel syntax.)
|
||||
|
||||
[comment]: <> (Received from: `self.generator.create_test_case()` + `self.generator.materialize(filename)`)
|
||||
|
||||
[comment]: <> (Passed down to: `model.load_test_case` and `executor.load_test_case`)
|
||||
|
||||
|
||||
[comment]: <> (## Inputs)
|
||||
|
||||
[comment]: <> (Currently, each input is a single 32-bit integer, used later as a PRNG seed inside the test case to initialize memory and registers.)
|
||||
|
||||
[comment]: <> (Inputs are generated in batches; that is, Input Generator returns `List[int]`.)
|
||||
|
||||
[comment]: <> (Received from: `input_gen.generate(...)`)
|
||||
|
||||
[comment]: <> (Passed down to: `model.trace_test_case(inputs)` and `executor.trace_test_case(inputs)`.)
|
|
@ -210,7 +210,7 @@ class PatternCoverage(Coverage):
|
|||
self.current_patterns.append(PatternInstance(pair, pair_ids, type_))
|
||||
|
||||
def load_test_case(self, test_case: TestCase):
|
||||
obj_file = test_case.to_binary()
|
||||
obj_file = test_case.bin_path
|
||||
output = run(f'objdump -D {obj_file} -b binary --no-show-raw-insn -m i386:x86-64',
|
||||
shell=True,
|
||||
check=False,
|
||||
|
|
|
@ -33,7 +33,7 @@ class X86Intel(Executor):
|
|||
write_to_pseudo_file(CONF.attack_variant, "/sys/x86-executor/measurement_mode")
|
||||
|
||||
def load_test_case(self, test_case: TestCase):
|
||||
write_to_pseudo_file(test_case.to_binary(), "/sys/x86-executor/code")
|
||||
write_to_pseudo_file(test_case.bin_path, "/sys/x86-executor/code")
|
||||
|
||||
def trace_test_case(self, inputs: List[Input], num_measurements: int = 0) \
|
||||
-> List[CombinedHTrace]:
|
||||
|
|
|
@ -8,12 +8,13 @@ from __future__ import annotations
|
|||
|
||||
import random
|
||||
import abc
|
||||
import iced_x86
|
||||
from typing import List, Dict, Set
|
||||
|
||||
from instruction_set import OperandSpec, InstructionSpec, InstructionSet
|
||||
from interfaces import Generator, TestCase, Operand, RegisterOperand, FlagsOperand, MemoryOperand, \
|
||||
ImmediateOperand, AgenOperand, LabelOperand, OT, Instruction, BasicBlock, Function
|
||||
from helpers import NotSupportedException
|
||||
from helpers import NotSupportedException, run
|
||||
from config import CONF
|
||||
|
||||
|
||||
|
@ -58,12 +59,8 @@ class ConfigurableGenerator(Generator, abc.ABC):
|
|||
pass
|
||||
|
||||
def create_test_case(self, asm_file: str) -> TestCase:
|
||||
"""
|
||||
Create a simple test case with a single BB
|
||||
Run instrumentation passes and print the result into a file
|
||||
"""
|
||||
self.reset_generator()
|
||||
self.test_case = TestCase(asm_file)
|
||||
self.test_case = TestCase()
|
||||
|
||||
# create the main function
|
||||
func = self.generate_function("test_case_main")
|
||||
|
@ -81,9 +78,27 @@ class ConfigurableGenerator(Generator, abc.ABC):
|
|||
p.run_on_test_case(self.test_case)
|
||||
|
||||
self.printer.print(self.test_case, asm_file)
|
||||
self.test_case.asm_path = asm_file
|
||||
|
||||
bin_file = asm_file[:-4] + ".o"
|
||||
self.assemble(asm_file, bin_file)
|
||||
self.test_case.bin_path = bin_file
|
||||
|
||||
self.map_addresses(self.test_case, bin_file)
|
||||
|
||||
return self.test_case
|
||||
|
||||
@staticmethod
|
||||
def assemble(asm_file: str, bin_file: str) -> None:
|
||||
"""Assemble the test case into a stripped binary"""
|
||||
run(f"as {asm_file} -o {bin_file}", shell=True, check=True)
|
||||
run(f"strip --remove-section=.note.gnu.property {bin_file}", shell=True, check=True)
|
||||
run(f"objcopy {bin_file} -O binary {bin_file}", shell=True, check=True)
|
||||
|
||||
@abc.abstractmethod
|
||||
def map_addresses(self, test_case: TestCase, bin_file: str) -> None:
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def generate_function(self, name: str) -> Function:
|
||||
pass
|
||||
|
@ -414,6 +429,28 @@ class X86Generator(ConfigurableGenerator, abc.ABC):
|
|||
self.printer = X86Printer()
|
||||
self.register_set = X86Registers()
|
||||
|
||||
def map_addresses(self, test_case: TestCase, bin_file: str) -> None:
|
||||
with open(bin_file, "rb") as f:
|
||||
bin_file_contents = f.read()
|
||||
|
||||
# get a list of relative instruction addresses
|
||||
decoder = iced_x86.Decoder(64, bin_file_contents)
|
||||
address_list: List[int] = []
|
||||
for instruction in decoder:
|
||||
address_list.append(instruction.ip)
|
||||
|
||||
# connect them with instructions in the test case
|
||||
address_map: Dict[int, Instruction] = {}
|
||||
counter = self.printer.prologue_size
|
||||
for func in test_case.functions:
|
||||
for bb in func:
|
||||
for inst in bb:
|
||||
address = address_list[counter]
|
||||
address_map[address] = inst
|
||||
counter += 1
|
||||
|
||||
test_case.address_map = address_map
|
||||
|
||||
def get_return_instruction(self) -> Instruction:
|
||||
return Instruction("RET")
|
||||
|
||||
|
|
|
@ -13,7 +13,6 @@ import numpy as np
|
|||
from enum import Enum
|
||||
|
||||
from config import CONF
|
||||
from helpers import run
|
||||
|
||||
|
||||
# ==================================================================================================
|
||||
|
@ -325,22 +324,17 @@ class Function:
|
|||
|
||||
class TestCase:
|
||||
asm_path: str = ''
|
||||
bin_path: str = ''
|
||||
main: Function
|
||||
functions: List[Function]
|
||||
address_map: Dict[int, Instruction]
|
||||
|
||||
def __init__(self, path: str):
|
||||
def __init__(self):
|
||||
self.functions = []
|
||||
self.asm_path = path
|
||||
|
||||
def to_binary(self) -> str:
|
||||
"""
|
||||
Assemble the test case into a stripped binary
|
||||
"""
|
||||
outfile = self.asm_path[:-4] + ".o"
|
||||
run(f"as {self.asm_path} -o {outfile}", shell=True, check=True)
|
||||
run(f"strip --remove-section=.note.gnu.property {outfile}", shell=True, check=True)
|
||||
run(f"objcopy {outfile} -O binary {outfile}", shell=True, check=True)
|
||||
return outfile
|
||||
def __iter__(self):
|
||||
for func in self.functions:
|
||||
yield func
|
||||
|
||||
|
||||
# ==================================================================================================
|
||||
|
@ -479,6 +473,18 @@ class Generator(ABC):
|
|||
|
||||
@abstractmethod
|
||||
def create_test_case(self, path: str) -> TestCase:
|
||||
"""
|
||||
Create a simple test case with a single BB
|
||||
Run instrumentation passes and print the result into a file
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def parse_existing_test_case(self, asm_file: str) -> TestCase:
|
||||
"""
|
||||
Read a test case from a file and create a complete TestCase object based on it.
|
||||
Used instead of create_test_case when Revizor works with a user-provided test case.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
|
|
|
@ -196,7 +196,7 @@ class X86UnicornModel(Model):
|
|||
|
||||
def load_test_case(self, test_case: TestCase) -> None:
|
||||
# create and read a binary
|
||||
with open(test_case.to_binary(), 'rb') as f:
|
||||
with open(test_case.bin_path, 'rb') as f:
|
||||
self.code = f.read()
|
||||
|
||||
# initialize emulator in x86-64 mode
|
||||
|
|
Загрузка…
Ссылка в новой задаче