executor/x86: [feat] add a state machine to monitor measurement status

This commit is contained in:
Oleksii Oleksenko 2024-07-24 10:18:10 +01:00
Родитель 1539a2b963
Коммит 126cdeb80c
Не найден ключ, соответствующий данной подписи
9 изменённых файлов: 232 добавлений и 90 удалений

78
docs/registers.md Normal file
Просмотреть файл

@ -0,0 +1,78 @@
# Register Allocation
The test cases are executed in a sandboxed environment, some of the registers are reserved for internal use, and some are available for use in the test cases.
Below is a list of registers and their purpose.
## `R15`
Contains the base address of the UTILITY area in the [sandbox](./docs/sandbox.md).
If the test case does not enter a VM, the register value remains constant during the execution of the test cases.
Otherwise, the register value is updated to point to the UTILITY area of the currently active VM when the `switch_h2g` macro is called, and it is restored to the original value when the `switch_g2h` macro is called.
The register is used by internal functions, such as the implementation of Prime+Probe.
## `R14`
Contains the base address of the current actor's [sandbox](./docs/sandbox.md) (namely, it points to the base of the actor's MAIN area).
At the beginning of the test case execution, the register is set to the base address of the MAIN area of the first actor (actor `main`). The register value is updated to point to the MAIN area of the currently active actor when a macro from the `landing_*` group of macros is called. It is also updated by the `fault_handler` macro.
The register is used in test cases as a part of the sandboxing mechanism.
For example, all generated memory accesses are relative to the value stored in `R14`, and have the form of `[R14 + offset]`.
## `R13` (`HTRACE_REGISTER` constant in the kernel module)
Contains either intermediate or final result of the hardware trace measurements.
Before entering the test case, the register is set to 0.
When a `measurement_start` macro is executed, the register is (optionally) set to the starting value,
such a initial reading of time stamp counter when the `TSC` mode is used.
When a `measurement_end` macro is executed, the register is updated with the final value of the measurement and contains the resulting hardware trace.
## `R12` (`STATUS_REGISTER` constant in the kernel module)
Contains a compressed status of the test case execution:
Bits[0:7] contain a measurement status.
At the beginning of the test case execution, the bits are set to 0.
When `measurement_start` macro is executed, the bits are set to 1.
When `measurement_end` macro is executed, the bits are set to 2.
If the measurement status is not 2 at the end of the test case execution, the kernel module will report an error.
Bits[8:31] are unused.
Bits[32:63] contain a counter of SMI (System Management Interrupt) events.
The counter is set automatically before entering the test case (`READ_SMI_START`), and updated when the test case finishes (`READ_SMI_END`).
If the difference between the readings is not 0, the kernel module will report an error.
## `R11`
The register is used as a temporary buffer by some of the macros.
Before entering the test case, the register is set to 0.
When certain macros are executed (e.g., `set_k2u_target`), the register will contain temporary values.
The register should not be used in the test case, as the temporary value may be consumed by latter macros.
## `R10, R9, R8`
Stores the values of performance counters.
`R10` stores the value of performance counter #1, `R9` stores the value of performance counter #2, and `R8` stores the value of performance counter #3.
Before entering the test case, the registers are set to 0.
When a `measurement_start` macro is executed, the registers are (optionally) set to the starting values.
When a `measurement_end` macro is executed, the registers are updated with the final values of the measurements.
## Other General Purpose Registers
The remaining registers (`rax`, `rcx`, `rdx`, `rsi`, `rdi`, `rflags`) are available for use in the test cases and can be modified freely.
A special case are `rsp` and `rbp`, which can be used in the test cases, but their values must always remain within the sandbox (see [Sandbox](./docs/sandbox.md)).
## Vector Registers
Vector registers (`xmm0`-`xmm15`) are also available for use in the test cases.
However, only `xmm0-xmm7` are initialized with input-based values, and the remaining registers are always zero-initialized.
Large-size vector registers (`ymm` and `zmm`) are not supported.

1
docs/sandbox.md Normal file
Просмотреть файл

@ -0,0 +1 @@
UNDER CONSTRUCTION

Просмотреть файл

@ -9,15 +9,7 @@
// -----------------------------------------------------------------------------------------------
// Note on registers.
// Some of the registers are reserved for a specific purpose and should never be overwritten.
// These include:
// R8 - performance counter #3
// R9 - performance counter #2
// R10 - performance counter #1
// R11 - temporary data for macros
// R12 - SMI counter
// R13 - hardware trace
// R14 - base address of the current actor's main data area
// R15 - base address of the utility area
// See ./docs/registers.md for more information.
#include "code_loader.h"
#include "asm_snippets.h"
@ -298,30 +290,33 @@ static inline void prologue(void)
"mov r12, 0\n"
"mov r13, 0\n"
// set HTRACE_REGISTER to -1 to be able to detect missing measurement_start
"mov "HTRACE_REGISTER", -1\n"
// initialize special registers
"mov "HTRACE_REGISTER", 0\n"
"mov "STATUS_REGISTER", 0\n"
"mov rbp, rsp\n"
"sub rsp, 0x1000\n"
// start monitoring interrupts
READ_SMI_START("r12")
READ_SMI_START()
);
}
static inline void epilogue(void)
{
asm_volatile_intel(
READ_SMI_END("r12")
// rbx <- SMI counter
READ_SMI_END()
"mov rbx, "STATUS_REGISTER"\n"
"shr rbx, 32\n"
// rax <- &latest_measurement
"lea rax, [r15 + "xstr(MEASUREMENT_OFFSET)"]\n"
// if we see no interrupts, store the hardware trace (r13)
// otherwise, store zero
"cmp r12, 0; jne 1f \n"
// same goes for uninitialized hardware trace
"cmp "HTRACE_REGISTER", -1; je 1f \n"
// check for errors (detected SMI or incomplete measurement);
// if there are any, we clear the measurement; otherwise, we store the measurements
"cmp rbx, 0; jne 1f \n"
"cmp "STATUS_REGISTER_8", "xstr(SR_ENDED)"; jne 1f \n"
" mov qword ptr [rax + 0x00], r13 \n"
" mov qword ptr [rax + 0x08], r10 \n"
" mov qword ptr [rax + 0x10], r9 \n"

Просмотреть файл

@ -8,11 +8,26 @@
// clang-format off
#include "hardware_desc.h"
#include <asm/msr-index.h>
#ifndef VENDOR_ID
#error "VENDOR_ID is not defined! Make sure to include this header late enough."
#endif
/// Reserved registers
#define STATUS_REGISTER "r12"
#define STATUS_REGISTER_32 "r12d"
#define STATUS_REGISTER_8 "r12b"
#define HTRACE_REGISTER "r13"
/// State machine of the tracing process
#define SR_UNINITIALIZED 0
#define SR_STARTED 1
#define SR_ENDED 2
#define SET_SR_STARTED() "mov "STATUS_REGISTER_8", "xstr(SR_STARTED)" \n"
#define SET_SR_ENDED() "mov "STATUS_REGISTER_8", "xstr(SR_ENDED)" \n"
/// Accessors to MSRs
///
// clobber: rax, rcx, rdx
@ -59,17 +74,64 @@
/// Detection of System Management Interrupts (SMIs)
///
#if VENDOR_ID == 1 // Intel
#define READ_SMI_START(DEST) READ_MSR_START("0x00000034", DEST)
#define READ_SMI_END(DEST) READ_MSR_END("0x00000034", DEST)
#elif VENDOR_ID == 2 // AMD
#define READ_SMI_START(DEST) \
READ_PFC_ONE("5") \
"sub "DEST", rdx \n"
#define READ_SMI_END(DEST) \
READ_PFC_ONE("5") \
"add "DEST", rdx \n"
/// @brief Clear the upper 32 bits of the STATUS_REGISTER
#define CLEAR_SMI_STATUS() \
"mov "STATUS_REGISTER_32", "STATUS_REGISTER_32" \n"
#if VENDOR_ID == VENDOR_INTEL_
/// @brief Start monitoring SMIs by reading the current value of the SMI counter (MSR 0x34)
/// and storing it in the STATUS_REGISTER[63:32]
/// clobber: rax, rcx, rdx
#define READ_SMI_START() \
"mov rcx, "xstr(MSR_SMI_COUNT)"\n" \
"lfence; rdmsr; lfence \n" \
"mov rcx, 0 \n" \
"sub ecx, eax \n" \
"shl rcx, 32 \n" \
CLEAR_SMI_STATUS() \
"or "STATUS_REGISTER", rcx \n"
/// @brief End monitoring SMIs by reading the current value of the SMI counter (MSR 0x34)
/// and storing the difference between the current and the previous value
/// in the STATUS_REGISTER[31:0]
/// clobber: rax, rcx, rdx
#define READ_SMI_END() \
"mov rcx, "xstr(MSR_SMI_COUNT)"\n" \
"lfence; rdmsr; lfence \n" \
"mov rcx, "STATUS_REGISTER" \n" \
"shr rcx, 32 \n" \
"add ecx, eax \n" \
"shl rcx, 32 \n" \
CLEAR_SMI_STATUS() \
"or "STATUS_REGISTER", rcx \n"
#elif VENDOR_ID == VENDOR_AMD_
/// @brief Start monitoring SMIs by reading the current value of the SMI counter (PMU ID 5)
/// and storing it in the STATUS_REGISTER[63:32]
/// clobber: rax, rcx, rdx
#define READ_SMI_START() \
"mov rcx, 5 \n" \
"lfence; rdpmc; lfence \n" \
"mov rcx, 0 \n" \
"sub ecx, eax \n" \
"shl rcx, 32 \n" \
CLEAR_SMI_STATUS() \
"or "STATUS_REGISTER", rcx \n"
/// @brief End monitoring SMIs by reading the current value of the SMI counter (PMU ID 5)
/// and storing the difference between the current and the previous value
/// in the STATUS_REGISTER[31:0]
/// clobber: rax, rcx, rdx
#define READ_SMI_END() \
"mov rcx, 5 \n" \
"lfence; rdpmc; lfence \n" \
"mov rcx, "STATUS_REGISTER" \n" \
"shr rcx, 32 \n" \
"add ecx, eax \n" \
"shl rcx, 32 \n" \
CLEAR_SMI_STATUS() \
"or "STATUS_REGISTER", rcx \n"
#endif

Просмотреть файл

@ -12,15 +12,21 @@
#endif
#define VENDOR_INTEL_ 1
#define VENDOR_AMD_ 2
#define VENDOR_AMD_ 2
#undef VENDOR_INTEL
#undef VENDOR_AMD
// Cache configuration
#ifndef L1D_ASSOCIATIVITY
#error "Undefined L1D_ASSOCIATIVITY"
#define L1D_ASSOCIATIVITY 0
#elif L1D_ASSOCIATIVITY != 12 && L1D_ASSOCIATIVITY != 8 && L1D_ASSOCIATIVITY != 4 && L1D_ASSOCIATIVITY != 2
#elif L1D_ASSOCIATIVITY != 12 && L1D_ASSOCIATIVITY != 8 && L1D_ASSOCIATIVITY != 4 && \
L1D_ASSOCIATIVITY != 2
#warning "Unsupported/corrupted L1D associativity. Falling back to 8-way"
#define L1D_ASSOCIATIVITY 8
#endif
// Definitions of MSRs missing in the kernel
#define MSR_SYSCFG 0xc0010010
#endif // _HARDWARE_DESC_H_

Просмотреть файл

@ -7,6 +7,7 @@
#define _MACRO_H_
#include "test_case_parser.h"
#include "asm_snippets.h"
#include <linux/types.h>
typedef enum {
@ -31,7 +32,6 @@ typedef enum {
MACRO_SET_DATA_PERMISSIONS = 18,
} macro_name_e;
#define HTRACE_REGISTER "r13"
#define MACRO_PLACEHOLDER_SIZE 8
int expand_macro(tc_symbol_entry_t *macro, uint8_t *dest, uint8_t *macro_dest, size_t *macro_size);

Просмотреть файл

@ -9,9 +9,6 @@
#include <linux/types.h>
#include "hardware_desc.h"
// A few MSR definitions missing in the kernel
#define MSR_SYSCFG 0xc0010010
typedef struct {
uint64_t cr0;
uint64_t cr4;

Просмотреть файл

@ -249,8 +249,8 @@ void __attribute__((noipa)) body_macro_prime(void)
MACRO_PROLOGUE() //
"lea rax, [r15 + " xstr(L1D_PRIMING_OFFSET) "]\n" //
PRIME("rax", "rbx", "rcx", "rdx", "32") //
"xor " HTRACE_REGISTER ", " HTRACE_REGISTER "\n" //
READ_PFC_START() //
SET_SR_STARTED() //
MACRO_EPILOGUE() //
"lfence\n" //
);
@ -264,8 +264,8 @@ void __attribute__((noipa)) body_macro_fast_prime(void)
MACRO_PROLOGUE() //
"lea rax, [r15 + " xstr(L1D_PRIMING_OFFSET) "]\n" //
PRIME("rax", "rbx", "rcx", "rdx", "1") //
"xor " HTRACE_REGISTER ", " HTRACE_REGISTER "\n" //
READ_PFC_START() //
SET_SR_STARTED() //
MACRO_EPILOGUE() //
"lfence\n" //
);
@ -279,8 +279,8 @@ void __attribute__((noipa)) body_macro_partial_prime(void)
MACRO_PROLOGUE() //
"lea rax, [r15 + " xstr(L1D_PRIMING_OFFSET) "]\n" //
PRIME_PARTIAL("rax", "rbx", "rcx", "rdx", "32") //
"xor " HTRACE_REGISTER ", " HTRACE_REGISTER "\n" //
READ_PFC_START() //
SET_SR_STARTED() //
MACRO_EPILOGUE() //
"lfence\n" //
);
@ -294,8 +294,8 @@ void __attribute__((noipa)) body_macro_fast_partial_prime(void)
MACRO_PROLOGUE() //
"lea rax, [r15 + " xstr(L1D_PRIMING_OFFSET) "]\n" //
PRIME_PARTIAL("rax", "rbx", "rcx", "rdx", "1") //
"xor " HTRACE_REGISTER ", " HTRACE_REGISTER "\n" //
READ_PFC_START() //
SET_SR_STARTED() //
MACRO_EPILOGUE() //
"lfence\n" //
);
@ -305,22 +305,23 @@ void __attribute__((noipa)) body_macro_fast_partial_prime(void)
void __attribute__((noipa)) body_macro_probe(void)
{
asm volatile(".quad " xstr(MACRO_START));
asm_volatile_intel("" //
"cmp " HTRACE_REGISTER ", -1\n" // skip if uninitialized
"je 99f\n" //
"cmp " HTRACE_REGISTER ", 0\n" // skip if already called
"jnz 99f\n" //
MACRO_PROLOGUE() //
"push r15\n" //
"lfence\n" //
READ_PFC_END() //
"lea r15, [r15 + " xstr(L1D_PRIMING_OFFSET) "]\n" //
PROBE("r15", "rbx", "r11", HTRACE_REGISTER) //
"pop r15\n" //
"mov qword ptr [rsp - 8], 0 \n" //
MACRO_EPILOGUE() //
"99:\n" //
// clang-format off
asm_volatile_intel(""
"cmp " STATUS_REGISTER_8 ", "xstr(SR_STARTED)"\n"
"jne 99f\n"
MACRO_PROLOGUE()
"push r15\n"
"lfence\n"
READ_PFC_END()
"lea r15, [r15 + " xstr(L1D_PRIMING_OFFSET) "]\n"
PROBE("r15", "rbx", "r11", HTRACE_REGISTER)
"pop r15\n"
"mov qword ptr [rsp - 8], 0 \n"
SET_SR_ENDED()
MACRO_EPILOGUE()
"99:\n"
);
// clang-format on
asm volatile(".quad " xstr(MACRO_END));
}
@ -328,14 +329,14 @@ void __attribute__((noipa)) body_macro_probe(void)
void __attribute__((noipa)) body_macro_flush(void)
{
asm volatile(".quad " xstr(MACRO_START));
asm_volatile_intel("" //
MACRO_PROLOGUE() //
"lea rbx, [r14]\n" //
FLUSH("rbx", "rax") //
"xor " HTRACE_REGISTER ", " HTRACE_REGISTER "\n" //
READ_PFC_START() //
MACRO_EPILOGUE() //
"lfence\n" //
asm_volatile_intel("" //
MACRO_PROLOGUE() //
"lea rbx, [r14]\n" //
FLUSH("rbx", "rax") //
READ_PFC_START() //
SET_SR_STARTED() //
MACRO_EPILOGUE() //
"lfence\n" //
);
asm volatile(".quad " xstr(MACRO_END));
}
@ -343,21 +344,22 @@ void __attribute__((noipa)) body_macro_flush(void)
void __attribute__((noipa)) body_macro_reload(void)
{
asm volatile(".quad " xstr(MACRO_START));
asm_volatile_intel("" //
"cmp " HTRACE_REGISTER ", -1\n" // skip if uninitialized
"je 98f\n" //
"cmp " HTRACE_REGISTER ", 0\n" // skip if already called
"jnz 98f\n" //
MACRO_PROLOGUE() //
"lfence\n" //
READ_PFC_END() //
RELOAD("r14", "rbx", "r11", HTRACE_REGISTER) //
"mov rax, 1\n" //
"shl rax, 63\n" //
"or " HTRACE_REGISTER ", rax\n" //
MACRO_EPILOGUE() //
"98:\n" //
// clang-format off
asm_volatile_intel(""
"cmp " STATUS_REGISTER_8 ", "xstr(SR_STARTED)"\n"
"jne 98f\n"
MACRO_PROLOGUE()
"lfence\n"
READ_PFC_END()
RELOAD("r14", "rbx", "r11", HTRACE_REGISTER)
"mov rax, 1\n"
"shl rax, 63\n"
"or " HTRACE_REGISTER ", rax\n"
SET_SR_ENDED()
MACRO_EPILOGUE()
"98:\n"
);
// clang-format on
asm volatile(".quad " xstr(MACRO_END));
}
@ -374,6 +376,7 @@ void __attribute__((noipa)) body_macro_tsc_start(void)
"sub " HTRACE_REGISTER ", rdx\n" //
"lfence\n" //
READ_PFC_START() //
SET_SR_STARTED() //
MACRO_EPILOGUE() //
"lfence\n" //
);
@ -383,20 +386,21 @@ void __attribute__((noipa)) body_macro_tsc_start(void)
void __attribute__((noipa)) body_macro_tsc_end(void)
{
asm volatile(".quad " xstr(MACRO_START));
asm_volatile_intel("" //
"cmp " HTRACE_REGISTER ", -1\n" // skip if uninitialized
"je 97f\n" //
"cmp " HTRACE_REGISTER ", 0\n" // skip if already called
"jg 97f\n" //
MACRO_PROLOGUE() //
READ_PFC_END() //
"lfence; rdtsc; lfence\n" //
"shl rdx, 32\n" //
"or rdx, rax\n" //
"add " HTRACE_REGISTER ", rdx\n" //
MACRO_EPILOGUE() //
"97:\n" //
// clang-format off
asm_volatile_intel(""
"cmp " STATUS_REGISTER_8 ", "xstr(SR_STARTED)"\n"
"jne 97f\n"
MACRO_PROLOGUE()
READ_PFC_END()
"lfence; rdtsc; lfence\n"
"shl rdx, 32\n"
"or rdx, rax\n"
"add " HTRACE_REGISTER ", rdx\n"
SET_SR_ENDED()
MACRO_EPILOGUE()
"97:\n"
);
// clang-format on
asm volatile(".quad " xstr(MACRO_END));
}

Просмотреть файл

@ -23,8 +23,8 @@
#include "memory_guest.h"
#include "perf_counters.h"
#include "special_registers.h"
#include "vmx.h"
#include "svm.h"
#include "vmx.h"
measurement_t *measurements = NULL; // global
@ -153,7 +153,6 @@ int run_experiment(void)
goto cleanup;
// store the measurement
// printk(KERN_ERR "x86_executor: measurement %llu\n", result.htrace[0]);
measurement_t result = sandbox->util->latest_measurement;
measurements[i_].htrace[0] = result.htrace[0];
memcpy(measurements[i_].pfc_reading, result.pfc_reading, sizeof(uint64_t) * NUM_PFC);