├── LICENSE ├── Readme.md ├── framework ├── CMakeLists.txt ├── README.md ├── external │ └── CMakeLists.txt ├── include │ ├── addr.hpp │ ├── framework.hpp │ ├── oracle.hpp │ └── utils.hpp └── src │ ├── addr.cpp │ ├── bitwise-framework.cpp │ ├── main.cpp │ ├── naive-framework.cpp │ ├── oracles │ ├── drama-oracle.cpp │ ├── oracle.cpp │ ├── slice-oracle.cpp │ ├── slice-timing-oracle.cpp │ └── utag-oracle.cpp │ ├── phys-addr.cpp │ └── virt-addr.cpp └── minimizer ├── Readme.md ├── limit-bits.py ├── minimize-groebner.sage ├── read-and-minimize.py ├── read-csv.py ├── setcover.py ├── test.py └── tree.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 CISPA 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Artifacts for Efficient and Generic Microarchitectural Hash-Function Recovery 2 | This repository contains the artifacts for the IEEE S&P 2024 paper "Efficient and Generic Microarchitectural Hash-Function Recovery". 3 | 4 | # Structure of the Artifacts 5 | The code is split across two folders both containing documentation on how to setup and run the code. 6 | - `./framework` contains all code that is necessary to measure microarchitectural hash functions reliably. If you want to measure a function on your computer you should head there to find out how to obtain the measurement data. 7 | - `./minimizer` contains all necessary code to minimize a measured function. After obtaining the measurements using the framework they can be minimized here. 8 | 9 | ## Citing Paper and Artifacts 10 | If you use our results in your research, please cite our paper as: 11 | ```bibtex 12 | @inproceedings{Gerlach2024Efficient, 13 | author={Gerlach, Lukas and Schwarz, Simon and Faro{\ss}, Nicolas and Schwarz, Michael}, 14 | booktitle = {S\&P}, 15 | title={Efficient and Generic Microarchitectural Hash-Function Recovery}, 16 | year = {2024} 17 | } 18 | 19 | ``` 20 | And our artifacts as: 21 | ```bibtex 22 | @misc{Gerlach2024EfficientArtifacts, 23 | author={Gerlach, Lukas and Schwarz, Simon and Faro{\ss}, Nicolas and Schwarz, Michael}, 24 | url = {https://github.com/cispa/Microarchitectural-Hash-Function-Recovery} 25 | title = {{Efficient and Generic Microarchitectural Hash-Function Recovery Artifact Repository}}, 26 | year = {2024} 27 | } 28 | ``` 29 | -------------------------------------------------------------------------------- /framework/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.11 FATAL_ERROR) 2 | project(unscatter) 3 | 4 | # === SANITIZERS ============================================================= 5 | 6 | #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread") 7 | #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined") 8 | #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=leak") 9 | #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address") 10 | #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread") 11 | 12 | # === DEPENDENCIES ============================================================= 13 | 14 | add_subdirectory(external) 15 | 16 | FetchContent_MakeAvailable(pcgrand) 17 | FetchContent_MakeAvailable(cli11) 18 | FetchContent_MakeAvailable(plog) 19 | FetchContent_MakeAvailable(pteditor) 20 | 21 | # === unscatter ============================================================= 22 | 23 | include_directories( 24 | include 25 | external 26 | ${cli11_SOURCE_DIR}/include 27 | ${plog_SOURCE_DIR}/include 28 | ${pcgrand_SOURCE_DIR}/include 29 | ${pteditor_SOURCE_DIR} 30 | ) 31 | 32 | 33 | add_executable( 34 | unscatter 35 | src/main.cpp 36 | src/addr.cpp 37 | src/bitwise-framework.cpp 38 | src/naive-framework.cpp 39 | src/phys-addr.cpp 40 | src/virt-addr.cpp 41 | src/oracles/oracle.cpp 42 | src/oracles/slice-oracle.cpp 43 | src/oracles/slice-timing-oracle.cpp 44 | src/oracles/utag-oracle.cpp 45 | src/oracles/drama-oracle.cpp 46 | ) 47 | 48 | target_link_libraries( 49 | unscatter 50 | CLI11::CLI11 51 | ) 52 | 53 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -Os -g -pg -DNO_LIVEPATCH") 54 | -------------------------------------------------------------------------------- /framework/README.md: -------------------------------------------------------------------------------- 1 | # Unscatter Measurement Framework 2 | Unscatter is the measurement framework for microarchitectural hash functions. 3 | It allows dumping measurement results that can later be minimized using a logic minimization algorithm. 4 | 5 | # Running Unscatter 6 | Currently, only tested on Ubuntu 22.04. 7 | ## Dependencies 8 | - Pteditor 9 | - Boost 10 | ## Building 11 | ```bash 12 | mkdir build 13 | cd build 14 | cmake .. 15 | make -j 16 | ``` 17 | ## Running 18 | For example to run the cache slice measurement on the isolated core 0 of an Intel Core processor run. 19 | ```bash 20 | sudo ./unscatter -c 0 -s 0 21 | ``` 22 | ## Options 23 | ``` 24 | Unscatter framework. 25 | Usage: ./unscatter [OPTIONS] 26 | 27 | Options: 28 | -h,--help Print this help message and exit 29 | -c,--core INT Core to pin framwork to 30 | -s,--set-option INT Set currently implemented frameworks 0=Slice Direct, 1=Slice Indirect, 2=Utag Indirect, 3=DRAM Indirect 31 | -o,--output-classes INT Number of output classes, (if not given then determined automatically) 32 | -t,--tresh-oracle INT Treshold of oracle in percent default 90% 33 | -u,--bit-limit-upper INT Bit limit for highest bit that gets dumped 34 | -l,--bit-limit-lower INT Bit limit for lowest bit that gets dumped 35 | -r,--relevant-input-bits TEXT File containing the relevant input bits 36 | --dry-run Perform a dry run without measurements. 37 | --xeon Enable if tested processor is an Intel Xeon chip 38 | ``` 39 | -------------------------------------------------------------------------------- /framework/external/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(FetchContent) 2 | 3 | function(message) 4 | if (NOT MESSAGE_QUIET) 5 | _message(${ARGN}) 6 | endif() 7 | endfunction() 8 | 9 | # === PCG Random ==================================================================== 10 | 11 | message(STATUS "Fetching external dependency PCG-Random") 12 | set(MESSAGE_QUIET OFF) 13 | 14 | FetchContent_Declare( 15 | pcgrand 16 | GIT_REPOSITORY https://github.com/imneme/pcg-cpp.git 17 | GIT_TAG v0.98.1 18 | ) 19 | 20 | FetchContent_MakeAvailable(pcgrand) 21 | 22 | set(MESSAGE_QUIET OFF) 23 | message(STATUS "Fetching external dependency PCG-Random -- done!") 24 | 25 | # === CLI11 ========================================================================= 26 | 27 | message(STATUS "Fetching external dependency CLI11") 28 | set(MESSAGE_QUIET OFF) 29 | 30 | FetchContent_Declare( 31 | cli11 32 | GIT_REPOSITORY https://github.com/CLIUtils/CLI11 33 | GIT_TAG v2.2.0 34 | ) 35 | 36 | FetchContent_MakeAvailable(cli11) 37 | 38 | set(MESSAGE_QUIET OFF) 39 | message(STATUS "Fetching external dependency CLI11 -- done!") 40 | 41 | # === Plog =========================================================================== 42 | 43 | message(STATUS "Fetching external dependency Plog") 44 | set(MESSAGE_QUIET OFF) 45 | 46 | FetchContent_Declare( 47 | plog 48 | GIT_REPOSITORY https://github.com/SergiusTheBest/plog.git 49 | GIT_TAG 1.1.9 50 | ) 51 | 52 | FetchContent_MakeAvailable(plog) 53 | 54 | set(MESSAGE_QUIET OFF) 55 | message(STATUS "Fetching external dependency Plog -- done!") 56 | 57 | # === Pteditor ======================================================================= 58 | 59 | message(STATUS "Fetching external dependency Pteditor") 60 | set(MESSAGE_QUIET OFF) 61 | 62 | FetchContent_Declare( 63 | pteditor 64 | GIT_REPOSITORY https://github.com/s8lvg/PTEditor.git 65 | ) 66 | 67 | FetchContent_MakeAvailable(pteditor) 68 | 69 | set(MESSAGE_QUIET OFF) 70 | message(STATUS "Fetching external dependency Pteditor -- done!") 71 | 72 | -------------------------------------------------------------------------------- /framework/include/addr.hpp: -------------------------------------------------------------------------------- 1 | #ifndef _ADDR_H_ 2 | #define _ADDR_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "pcg_random.hpp" 18 | 19 | typedef uint64_t pointer; 20 | 21 | #define ADDR_RETRIES_INC 100000 22 | #define ADDR_RETRIES_RAND_SELECT 1000 23 | 24 | /* 25 | * Contexts for the mappable dram ranges, it is used to work with physical 26 | * addresses 27 | */ 28 | typedef struct dram_ctx { 29 | boost::icl::interval_set ram_ranges; 30 | uint64_t ram_addresses; 31 | uint64_t max_address; 32 | } dram_ctx; 33 | 34 | 35 | /* 36 | * Interface to the get addresses 37 | */ 38 | class IAddr { 39 | private: 40 | std::random_device rd; // seed the random number generator 41 | 42 | protected: 43 | pcg64 rnd{rd()}; 44 | pointer bitmsask_iterator_position; // current position of the iterator 45 | uint64_t bitmask; 46 | std::vector idx_vec; 47 | std::vector idx_vec_invert; 48 | 49 | public: 50 | size_t maxbits; 51 | std::pair get_flip_pair(int idx); 52 | void init_bitmask_iterator(std::vector idx_vec); 53 | pointer get_alternative_addr(pointer addr); 54 | virtual std::pair advance_bitmask_iterator(size_t step) = 0; 55 | virtual bool valid_address(pointer address) = 0; 56 | virtual pointer get_random_addr() = 0; 57 | virtual pointer map_addr(pointer addr) = 0; 58 | virtual pointer flip_unused_bits(pointer addr) = 0; 59 | }; 60 | 61 | /* 62 | * Addr class for physical addresses 63 | */ 64 | class PhysAddr : public IAddr 65 | { 66 | private: 67 | dram_ctx dram; 68 | char* map_base; 69 | 70 | public: 71 | virtual std::pair advance_bitmask_iterator(size_t step); 72 | virtual bool valid_address(pointer addr); 73 | virtual pointer get_random_addr(); 74 | virtual pointer map_addr(pointer addr); 75 | virtual pointer flip_unused_bits(pointer addr); 76 | virtual void make_cachable(); 77 | virtual void make_uncachable(); 78 | PhysAddr(); 79 | ~PhysAddr(); 80 | }; 81 | 82 | /* 83 | * Addr class for virtual addresses 84 | */ 85 | class VirtAddr : public IAddr 86 | { 87 | private: 88 | char* map_base; 89 | 90 | public: 91 | virtual std::pair advance_bitmask_iterator(size_t step); 92 | virtual bool valid_address(pointer addr); 93 | virtual pointer get_random_addr(); 94 | virtual pointer map_addr(pointer addr); 95 | virtual pointer flip_unused_bits(pointer addr); 96 | VirtAddr(size_t maxbits); 97 | ~VirtAddr(); 98 | }; 99 | 100 | #endif 101 | 102 | -------------------------------------------------------------------------------- /framework/include/framework.hpp: -------------------------------------------------------------------------------- 1 | #ifndef _FRAMEWORK_H_ 2 | #define _FRAMEWORK_H_ 3 | 4 | #include "../include/addr.hpp" 5 | #include "../include/oracle.hpp" 6 | 7 | typedef uint64_t pointer; 8 | 9 | /* 10 | * Framwork class that can be generalized to infer arbitrary microarchitectual 11 | * hash functions 12 | */ 13 | class Framework { 14 | protected: 15 | Oracle* oracle; // Oracle used for measuremnts 16 | IAddr* addr; // Address pool to measure on 17 | 18 | public: 19 | uint64_t bit_limit_hi; // lowest considered bit 20 | uint64_t bit_limit_lo; // highest considered bit 21 | uint64_t output_classes; // number of output classes 22 | virtual void determine_output_classes() = 0; 23 | virtual void get_input_space_bits() = 0; 24 | virtual void dump_truth_table() = 0; 25 | virtual void dry_run() = 0; 26 | 27 | std::vector> input_space_bits; 28 | std::vector input_space_linear; 29 | 30 | }; 31 | 32 | /* 33 | * Decompose the function into bitwise parts, saving on the ammount of measuremnts that neeed to be performend 34 | * Requires that the used oracle is a direct one 35 | */ 36 | class BitwiseFramework : public Framework{ 37 | public: 38 | void determine_output_classes(); 39 | void get_input_space_bits(); 40 | void dump_truth_table(); 41 | void dry_run(); 42 | BitwiseFramework(int core, IAddr* addr, Oracle* oracle); 43 | ~BitwiseFramework(); 44 | }; 45 | 46 | /* 47 | * Naive implementation that measures all bits at once 48 | */ 49 | class NaiveFramework : public Framework{ 50 | public: 51 | void determine_output_classes(); 52 | void get_input_space_bits(); 53 | void dump_truth_table(); 54 | void dry_run(); 55 | NaiveFramework(int core, IAddr* addr, Oracle* oracle); 56 | ~NaiveFramework(); 57 | }; 58 | 59 | #endif -------------------------------------------------------------------------------- /framework/include/oracle.hpp: -------------------------------------------------------------------------------- 1 | #ifndef _ORACLE_H_ 2 | #define _ORACLE_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include "addr.hpp" 8 | 9 | #define MAX_OUTPUT_CLASS_ASSUMPTION 100 10 | 11 | typedef uint64_t pointer; 12 | 13 | /* 14 | * Generic oracle class that can be used to model arbitrary oracles 15 | */ 16 | 17 | class Oracle 18 | { 19 | private: 20 | public: 21 | int runs; 22 | int confidence; 23 | int precision; // precision parameter that can be used to tweak oracle 24 | uint64_t oracle_robust(pointer addr, pointer paddr, int retries); 25 | virtual uint64_t oracle(pointer addr) = 0; 26 | uint64_t output_classes; 27 | Oracle(int runs,int confidence); 28 | }; 29 | 30 | class SliceOracle : public Oracle 31 | { 32 | private: 33 | bool is_xeon; 34 | int cores; 35 | int cpu_architecture; 36 | size_t measure_slice(void* address); 37 | size_t measure_slice_core(void* address); 38 | size_t measure_slice_xeon(void* address); 39 | 40 | public: 41 | SliceOracle(int runs,int confidence,bool is_xeon); 42 | ~SliceOracle(); 43 | uint64_t oracle(pointer addr); 44 | }; 45 | 46 | class UtagOracle : public Oracle 47 | { 48 | private: 49 | std::vector> class_examples; 50 | int pc_l1d_read_miss; 51 | IAddr* addr; 52 | bool weak_utag_oracle(void* address1, void* address2, size_t threshold); 53 | void build_oracle(); 54 | 55 | public: 56 | UtagOracle(int runs,int confidence,IAddr* addr); 57 | ~UtagOracle(); 58 | uint64_t oracle(pointer addr); 59 | }; 60 | 61 | class DramaOracle : public Oracle 62 | { 63 | private: 64 | std::vector> class_examples; 65 | IAddr* addr; 66 | size_t threshold; 67 | size_t num_reads = 5000; 68 | bool weak_drama_oracle(void* address1, void* address2, size_t threshold); 69 | void build_oracle(); 70 | void determine_treshold(); 71 | uint64_t getTiming(pointer first, pointer second); 72 | 73 | public: 74 | DramaOracle(int runs,int confidence,IAddr* addr); 75 | ~DramaOracle(); 76 | uint64_t oracle(pointer addr); 77 | }; 78 | 79 | class SliceTimingOracle : public Oracle 80 | { 81 | private: 82 | IAddr* addr; 83 | size_t threshold; 84 | int cores; 85 | void determine_treshold(); 86 | 87 | public: 88 | SliceTimingOracle(int runs,int confidence,IAddr* addr); 89 | ~SliceTimingOracle(); 90 | uint64_t oracle(pointer addr); 91 | }; 92 | 93 | 94 | #endif 95 | -------------------------------------------------------------------------------- /framework/include/utils.hpp: -------------------------------------------------------------------------------- 1 | #ifndef _UTILS_H_ 2 | #define _UTILS_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include /* Definition of HW_* constants */ 14 | #include /* Definition of SYS_* constants */ 15 | #include 16 | 17 | /* 18 | * Function that prints an array 19 | */ 20 | template 21 | std::ostream& operator<<(std::ostream& out, const std::vector& v) { 22 | out << "{"; 23 | size_t last = v.size() - 1; 24 | for (size_t i = 0; i < v.size(); ++i) { 25 | out << v[i]; 26 | if (i != last) out << ", "; 27 | } 28 | out << "}"; 29 | return out; 30 | } 31 | 32 | /* 33 | * Function that prints a nested array 34 | */ 35 | template 36 | std::ostream& operator<<(std::ostream& out, const std::vector>& v) { 37 | out << "{"; 38 | size_t last = v.size() - 1; 39 | for (size_t i = 0; i < v.size(); ++i) { 40 | out << v[i]; 41 | if (i != last) out << "\n"; 42 | } 43 | out << "}"; 44 | return out; 45 | } 46 | 47 | /* 48 | * Function that prints a pair 49 | */ 50 | template 51 | std::ostream& operator<<(std::ostream& out, const std::pair& p) { 52 | out << "(" << p.first << "," << p.second << ")"; 53 | return out; 54 | } 55 | 56 | /* 57 | * Gets the index in the total mesurement series based on the bitmask vector and address 58 | */ 59 | static uint64_t idx_from_idx_vec_and_addr(std::vector idx_vec, pointer addr){ 60 | uint64_t idx = 0; 61 | for (size_t i = 0; i < idx_vec.size(); i++) 62 | { 63 | idx |= (((addr >> idx_vec[i]) & 0x1) << i); 64 | } 65 | return idx; 66 | } 67 | 68 | /* 69 | * Function that performs a memory access on the specified virtual address 70 | */ 71 | static void maccess(void *p) { asm volatile("movq (%0), %%rax\n" : : "c"(p) : "rax"); } 72 | 73 | // ---------------------------------------------- 74 | static void nospec() { asm volatile("lfence"); } 75 | 76 | // ---------------------------------------------- 77 | static void mfence() { asm volatile("mfence"); } 78 | 79 | // ---------------------------------------------- 80 | static void flush(void* p) { 81 | asm volatile ("clflush 0(%0)\n" 82 | : 83 | : "c" (p) 84 | : "rax"); 85 | } 86 | 87 | // ---------------------------------------------- 88 | static uint64_t rdtsc() { 89 | uint64_t a, d; 90 | asm volatile ("xor %%rax, %%rax\n" "cpuid"::: "rax", "rbx", "rcx", "rdx"); 91 | asm volatile ("rdtscp" : "=a" (a), "=d" (d) : : "rcx"); 92 | a = (d << 32) | a; 93 | return a; 94 | } 95 | 96 | // ---------------------------------------------- 97 | static uint64_t rdtsc2() { 98 | uint64_t a, d; 99 | asm volatile ("rdtscp" : "=a" (a), "=d" (d) : : "rcx"); 100 | asm volatile ("cpuid"::: "rax", "rbx", "rcx", "rdx"); 101 | a = (d << 32) | a; 102 | return a; 103 | } 104 | 105 | // ---------------------------------------------- 106 | static void pin_to_core(pid_t pid, int core) { 107 | cpu_set_t mask; 108 | mask.__bits[0] = 1 << core; 109 | sched_setaffinity(pid, sizeof(cpu_set_t), &mask); 110 | } 111 | 112 | // ---------------------------------------------- 113 | static int phys_cores() { 114 | FILE *f = fopen("/proc/cpuinfo", "r"); 115 | if (!f) { 116 | return 1; 117 | } 118 | char *line = NULL; 119 | 120 | int cores[256] = {0}; 121 | size_t len = 0; 122 | while (getline(&line, &len, f) != -1) { 123 | if(strncmp(line, "core id", 7) == 0) { 124 | int id = 0; 125 | sscanf(strrchr(line, ':') + 1, "%d", &id); 126 | if(id >= 0 && id < 256) { 127 | cores[id]++; 128 | } 129 | } 130 | } 131 | free(line); 132 | fclose(f); 133 | 134 | int phys_cores = 0; 135 | for(int i = 0; i < 256; i++) { 136 | if(cores[i]) { 137 | phys_cores++; 138 | } 139 | } 140 | return phys_cores; 141 | } 142 | 143 | // ---------------------------------------------- 144 | static uint64_t findMedianSorted(std::vector& v,size_t offset) { 145 | if ((v.size()-offset) % 2 == 0) { 146 | return (v[(v.size()+offset) / 2 - 1] + v[(v.size()+offset) / 2]) / 2; 147 | } else { 148 | return v[(v.size()+offset) / 2]; 149 | } 150 | } 151 | 152 | // ---------------------------------------------- 153 | static uint64_t ostsu_treshold(std::vector measures){ 154 | 155 | // Prefilter to remove outlier 156 | std::sort(measures.begin(), measures.end()); 157 | auto Q1 = findMedianSorted(measures,0); 158 | auto Q3 = findMedianSorted(measures,measures.size()/2); 159 | auto IQR = Q3 - Q1; 160 | 161 | std::vector::iterator it = measures.begin(); 162 | while (it != measures.end()) { 163 | if (*it < Q1 - 1.5 * IQR || *it > Q3 + 1.5 * IQR) { 164 | it = measures.erase(it); 165 | } else { 166 | ++it; 167 | } 168 | } 169 | 170 | // Normalize remaining vector entries 171 | auto min = *measures.begin(); 172 | auto max = *measures.end(); 173 | for (size_t i = 0; i < measures.size(); i++) 174 | { 175 | measures[i] = ((measures[i]-min)*255)/((float)(max-min)); 176 | } 177 | 178 | // Build a histogram for the vector 179 | std::vector hist(255,0); 180 | for (size_t i = 0; i < measures.size(); i++) 181 | { 182 | hist[measures[i]]++; 183 | } 184 | 185 | // Compute threshold 186 | // Init variables 187 | float sum = 0; 188 | float sumB = 0; 189 | int q1 = 0; 190 | int q2 = 0; 191 | float varMax = 0; 192 | int threshold = 0; 193 | 194 | // Auxiliary value for computing m2 195 | for (int i = 0; i <= 255; i++){ 196 | sum += i * ((int)hist[i]); 197 | } 198 | 199 | for (int i = 0 ; i <= 255 ; i++) { 200 | // Update q1 201 | q1 += hist[i]; 202 | if (q1 == 0) continue; 203 | 204 | // Update q2 205 | q2 = measures.size() - q1; 206 | if (q2 == 0) break; 207 | 208 | // Update m1 and m2 209 | sumB += (float) (i * ((int)hist[i])); 210 | float m1 = sumB / q1; 211 | float m2 = (sum - sumB) / q2; 212 | 213 | // Update the between class variance 214 | float varBetween = (float) q1 * (float) q2 * (m1 - m2) * (m1 - m2); 215 | 216 | // Update the threshold if necessary 217 | if (varBetween > varMax) { 218 | varMax = varBetween; 219 | threshold = i; 220 | } 221 | } 222 | 223 | // Denormalize treshold to get usable value 224 | uint64_t denormalized_threshold = ((threshold*(max-min))/255)+min; 225 | 226 | return denormalized_threshold; 227 | 228 | } 229 | 230 | static size_t rdmsr(int cpu, uint32_t reg) { 231 | char msr_file_name[64]; 232 | sprintf(msr_file_name, "/dev/cpu/%d/msr", cpu); 233 | 234 | int fd = open(msr_file_name, O_RDONLY); 235 | if(fd < 0) { 236 | return -1; 237 | } 238 | 239 | size_t data = 0; 240 | if(pread(fd, &data, sizeof(data), reg) != sizeof(data)) { 241 | close(fd); 242 | return -1; 243 | } 244 | 245 | close(fd); 246 | 247 | return data; 248 | } 249 | 250 | static int wrmsr(int cpu, uint32_t reg, uint64_t val) { 251 | char msr_file_name[64]; 252 | sprintf(msr_file_name, "/dev/cpu/%d/msr", cpu); 253 | 254 | int fd = open(msr_file_name, O_WRONLY); 255 | if(fd < 0) { 256 | return 1; 257 | } 258 | 259 | if(pwrite(fd, &val, sizeof(val), reg) != sizeof(val)) { 260 | close(fd); 261 | return 1; 262 | } 263 | 264 | close(fd); 265 | 266 | return 0; 267 | } 268 | 269 | static int find_index_of_nth_largest_size_t(size_t* list, size_t nmemb, size_t skip) { 270 | size_t sorted[nmemb]; 271 | size_t idx[nmemb]; 272 | size_t i, j; 273 | size_t tmp; 274 | memset(sorted, 0, sizeof(sorted)); 275 | for(i = 0; i < nmemb; i++) { 276 | sorted[i] = list[i]; 277 | idx[i] = i; 278 | } 279 | for(i = 0; i < nmemb; i++) { 280 | int swaps = 0; 281 | for(j = 0; j < nmemb - 1; j++) { 282 | if(sorted[j] < sorted[j + 1]) { 283 | tmp = sorted[j]; 284 | sorted[j] = sorted[j + 1]; 285 | sorted[j + 1] = tmp; 286 | tmp = idx[j]; 287 | idx[j] = idx[j + 1]; 288 | idx[j + 1] = tmp; 289 | swaps++; 290 | } 291 | } 292 | if(!swaps) break; 293 | } 294 | 295 | return idx[skip]; 296 | } 297 | 298 | 299 | 300 | /* 301 | * Performance countner stuff 302 | */ 303 | #define PERF_CACHE_TYPE(id, op_id, op_result_id) \ 304 | ((id) | ((op_id) << 8) | ((op_result_id) << 16)) 305 | 306 | static int event_open(enum perf_type_id type, __u64 config, __u64 exclude_kernel, __u64 exclude_hv, __u64 exclude_callchain_kernel, int cpu) { 307 | static struct perf_event_attr attr; 308 | memset(&attr, 0, sizeof(attr)); 309 | attr.type = type; 310 | attr.config = config; 311 | attr.size = sizeof(attr); 312 | attr.exclude_kernel = exclude_kernel; 313 | attr.exclude_hv = exclude_hv; 314 | attr.exclude_callchain_kernel = exclude_callchain_kernel; 315 | attr.sample_type = PERF_SAMPLE_IDENTIFIER; 316 | attr.inherit = 1; 317 | attr.disabled = 1; 318 | 319 | int fd = syscall(__NR_perf_event_open, &attr, -1, 0, -1, 0); 320 | assert(fd >= 0 && "perf_event_open failed: you forgot sudo or you have no perf event interface available for the userspace."); 321 | 322 | return fd; 323 | } 324 | 325 | static int performance_counter_open(size_t pid, size_t type, size_t config) { 326 | struct perf_event_attr pe_attr; 327 | memset(&pe_attr, 0, sizeof(struct perf_event_attr)); 328 | 329 | pe_attr.type = type; 330 | pe_attr.size = sizeof(pe_attr); 331 | pe_attr.config = config; 332 | pe_attr.exclude_kernel = 1; 333 | pe_attr.exclude_hv = 1; 334 | pe_attr.exclude_callchain_kernel = 1; 335 | 336 | int fd = syscall(__NR_perf_event_open, &pe_attr, pid, -1, -1, 0); 337 | if (fd == -1) { 338 | std::throw_with_nested(std::runtime_error("Performance counter monitor could not be opened")); 339 | } 340 | assert(fd >= 0); 341 | 342 | return fd; 343 | } 344 | 345 | static void performance_counter_reset(int fd) { 346 | int rc = ioctl(fd, PERF_EVENT_IOC_RESET, 0); 347 | assert(rc == 0); 348 | } 349 | 350 | static void performance_counter_enable(int fd) { 351 | int rc = ioctl(fd, PERF_EVENT_IOC_ENABLE, 0); 352 | assert(rc == 0); 353 | } 354 | 355 | static void performance_counter_disable(int fd) { 356 | int rc = ioctl(fd, PERF_EVENT_IOC_DISABLE, 0); 357 | assert(rc == 0); 358 | } 359 | 360 | static size_t performance_counter_read(int fd) { 361 | size_t count; 362 | int got = read(fd, &count, sizeof(count)); 363 | assert(got == sizeof(count)); 364 | 365 | return count; 366 | } 367 | #endif 368 | 369 | -------------------------------------------------------------------------------- /framework/src/addr.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../include/addr.hpp" 4 | 5 | 6 | /* 7 | * Returns a pair of two addresses both addresses are idential except for the 8 | * index idx which has been flipped. 9 | */ 10 | std::pair IAddr::get_flip_pair(int idx) { 11 | uint64_t addr, flip_addr; 12 | for (size_t i = 0; i < 100; i++) 13 | { 14 | addr = this->get_random_addr() & ~63; 15 | flip_addr = addr ^ (1ULL << idx); 16 | if (this->valid_address(flip_addr)) { 17 | return std::make_pair(addr, flip_addr); 18 | } 19 | } 20 | std::cout << idx << " " << " " < idx_vec) { 28 | // Init bitmask 29 | uint64_t bitmask = 0; 30 | for(uint64_t idx : idx_vec){ 31 | bitmask |= 1L << idx; 32 | } 33 | 34 | std::vector idx_vec_invert; 35 | for (size_t i = 0; i > this->maxbits; i--) 36 | { 37 | if(std::find(idx_vec.begin(), idx_vec.end(), i) == idx_vec.end()) { 38 | idx_vec_invert.push_back(i); 39 | } 40 | } 41 | 42 | this->idx_vec = idx_vec; 43 | this->idx_vec_invert = idx_vec_invert; 44 | this->bitmsask_iterator_position = 0; 45 | this->bitmask = bitmask; 46 | } 47 | 48 | pointer IAddr::get_alternative_addr(pointer addr){ 49 | do{ 50 | addr = flip_unused_bits(addr); 51 | }while (!(valid_address(addr))); 52 | return addr; 53 | } -------------------------------------------------------------------------------- /framework/src/bitwise-framework.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include "CLI/App.hpp" 24 | #include "CLI/Formatter.hpp" 25 | #include "CLI/Config.hpp" 26 | 27 | #include "../include/addr.hpp" 28 | #include "../include/utils.hpp" 29 | #include "../include/framework.hpp" 30 | #include "../include/oracle.hpp" 31 | 32 | #define THRESHOLD_FIXPOINT_ITERATION 1000 33 | #define ITERATIONS_INPUT_SPACE_MEASURE 100 34 | #define WEAK_ORACLE_FIXPOINT_ITERATON 100 35 | #define MAX_RETRIES_ORACLE 100 36 | #define MULTIMEASURE 37 | 38 | /* 39 | * Peroforms fixpoint iteration with threshold to find the number of output 40 | * classes. 41 | * If concrete output classes are known beforehand this part can be skipped. 42 | * To skip this step just overwrite the output classes of the framework direcly. 43 | */ 44 | void BitwiseFramework::determine_output_classes() { 45 | std::set oracle_outputs; 46 | uint64_t unchanged_iterations = 0; 47 | while (unchanged_iterations < THRESHOLD_FIXPOINT_ITERATION) { 48 | auto size_before = oracle_outputs.size(); 49 | auto random_addr = this->addr->get_random_addr(); 50 | auto mapped_addr = this->addr->map_addr(random_addr); 51 | oracle_outputs.insert(this->oracle->oracle(mapped_addr)); 52 | auto size_after = oracle_outputs.size(); 53 | if (size_before == size_after) { 54 | unchanged_iterations++; 55 | } else { 56 | unchanged_iterations = 0; 57 | } 58 | } 59 | this->output_classes = oracle_outputs.size(); 60 | this->oracle->output_classes = this->output_classes; 61 | } 62 | 63 | /* 64 | * Returns the bit indices relevant for each bit of the output. 65 | * The precision of this measurement can be increased by increasing the 66 | * threshold parameter 67 | */ 68 | void BitwiseFramework::get_input_space_bits() { 69 | // Iterate over all determined output classes and compute relevant input bits 70 | for (size_t out_class_idx = 0; out_class_idx < ceil(log2(this->output_classes)); 71 | out_class_idx++) { 72 | std::vector out_class_bits; 73 | PLOG_INFO << "Computing relevant input space for bit " << out_class_idx; 74 | PLOG_DEBUG << this->addr->maxbits; 75 | // Try each address bit that can be mapped 76 | bool linear = true; 77 | for (size_t addr_bit= 0; addr_bit < this->addr->maxbits; addr_bit++) { 78 | // True and false counts to determine linearity 79 | int true_cnt = 0; 80 | int false_cnt = 0; 81 | bool addr_bit_relevant = false; 82 | // Perform iterations to determine likelyhood that the input bit influences the output result 83 | for (size_t iteration = 0; iteration < ITERATIONS_INPUT_SPACE_MEASURE; 84 | iteration++) { 85 | // Get a pair of addresses where the bit index addr_bit is flipped between the two addresses 86 | auto pair = this->addr->get_flip_pair(addr_bit); 87 | auto addr_1 = pair.first; 88 | auto addr_2 = pair.second; 89 | auto addr_1_mapped = this->addr->map_addr(addr_1); 90 | auto addr_2_mapped = this->addr->map_addr(addr_2); 91 | 92 | auto first_measure = this->oracle->oracle_robust(addr_1_mapped, addr_1, MAX_RETRIES_ORACLE); 93 | auto second_measure = this->oracle->oracle_robust(addr_2_mapped, addr_2, MAX_RETRIES_ORACLE); 94 | addr_bit_relevant |= ((first_measure >> out_class_idx) & 0x1) != ((second_measure >> out_class_idx) & 0x1); 95 | if(((first_measure >> out_class_idx) & 0x1) != ((second_measure >> out_class_idx) & 0x1)){ 96 | true_cnt++; 97 | }else{ 98 | false_cnt++; 99 | } 100 | } 101 | PLOG_DEBUG <<"Bit:" << addr_bit << " rel/irel:" << true_cnt << "/" << false_cnt; 102 | if (addr_bit_relevant) { 103 | out_class_bits.push_back(addr_bit); 104 | linear &= (true_cnt == ITERATIONS_INPUT_SPACE_MEASURE); 105 | } 106 | } 107 | // If there are relevant bits record them 108 | if(out_class_bits.size() > 0){ 109 | this->input_space_bits.push_back(out_class_bits); 110 | this->input_space_linear.push_back(linear); 111 | } 112 | 113 | // Dump relevant input space bits 114 | std::ofstream bitfile; 115 | bitfile.open("measurements/bits.csv"); 116 | 117 | for(auto input_space : this->input_space_bits){ 118 | if(input_space_bits.size() != 0){ 119 | for (size_t i = 0; i < input_space.size(); i++) 120 | { 121 | if(input_space[i] >= this->bit_limit_hi || input_space[i] <= this->bit_limit_lo){continue;} 122 | if(i != input_space.size()-1){ 123 | bitfile << input_space[i] << ", "; 124 | }else{ 125 | bitfile << input_space[i]; 126 | } 127 | } 128 | bitfile << "\n"; 129 | } 130 | } 131 | } 132 | } 133 | 134 | /* 135 | * Dumps truth tables for all relevant bits 136 | */ 137 | void BitwiseFramework::dump_truth_table(){ 138 | for (size_t bit_idx = 0; bit_idx < this->input_space_bits.size(); bit_idx++) 139 | { 140 | if(this->input_space_linear[bit_idx]){ 141 | PLOG_INFO << "h[" << bit_idx << "] is linear, skipping dump"; 142 | continue; 143 | } 144 | 145 | // Open dumpfile 146 | std::ofstream dumpfile; 147 | char buff[100]; 148 | snprintf(buff, sizeof(buff), "measurements/bit_%ld.csv", bit_idx); 149 | dumpfile.open(buff); 150 | dumpfile << "addr,class\n"; 151 | 152 | // Init bitmask iterator 153 | this->addr->init_bitmask_iterator(this->input_space_bits[bit_idx]); 154 | 155 | // Reduce by applying lower and upper bit bounds 156 | auto reduce = 0; 157 | std::vector bit_idx_reduce; 158 | std::vector bit_idx_expand; 159 | for(auto b: this->input_space_bits[bit_idx]){ 160 | if(b >= this->bit_limit_hi || b <= this->bit_limit_lo){ 161 | reduce++; 162 | bit_idx_expand.push_back(b); 163 | }else{ 164 | bit_idx_reduce.push_back(b); 165 | } 166 | } 167 | size_t iterations = 1L << (this->input_space_bits[bit_idx].size()-reduce); 168 | 169 | 170 | PLOG_INFO << "Dumping h[" << bit_idx << "] iteration count " << iterations; 171 | std::vector seen_idx(iterations); 172 | // Iterate over addresses dumping truth table 173 | int perc = 0, last_perc = -1; 174 | for (size_t i = 0; i < iterations ; i++) 175 | { 176 | // Try to determine output class of an address 177 | auto addr_tuple = this->addr->advance_bitmask_iterator(1ULL << (bit_idx_reduce[0])); 178 | pointer select_addr = addr_tuple.first; 179 | bool can_map = addr_tuple.second; 180 | if(!can_map){ 181 | dumpfile << select_addr << ", "<< "-" << "\n"; 182 | }else{ 183 | int count = 0; 184 | // Try to map an address 185 | auto mapped_addr = this->addr->map_addr(select_addr); 186 | // Try to measure until succesfull 187 | bool oracle_success = false; 188 | uint64_t oracle_out = 0; 189 | while (!oracle_success) 190 | { 191 | try{ 192 | // This can error if the maximum retry is exceeded 193 | oracle_out = this->oracle->oracle_robust(mapped_addr,select_addr,10); 194 | //std::cout << ((oracle_out>>bit_idx) & 0x1) << "\n"; 195 | dumpfile << select_addr << ", "<< ((oracle_out>>bit_idx) & 0x1) << "\n"; 196 | count += ((oracle_out>>bit_idx) & 0x1); 197 | oracle_success = true; 198 | }catch (...){ 199 | PLOG_DEBUG << "REMEASURE triggerd while dumping"; 200 | } 201 | } 202 | } 203 | 204 | seen_idx[idx_from_idx_vec_and_addr(bit_idx_reduce,select_addr)] = true; 205 | perc = (int)(i * 100.0 / iterations); 206 | if(perc != last_perc) { 207 | PLOG_DEBUG << perc <<"% " << i << "/" << iterations; 208 | last_perc = perc; 209 | } 210 | } 211 | // If one index has not been mapped throw an error 212 | bool abort = false; 213 | for (size_t i = 0; i < iterations; i++) 214 | { 215 | if(!seen_idx[i]){ 216 | PLOG_ERROR << "Bit:" << bit_idx << " Unmapped Idx:" << i; 217 | abort = true; 218 | } 219 | } 220 | if(abort){ 221 | PLOG_ERROR << "Unexplored index detected, for bit " << bit_idx << ". This is most likely a bug!"; 222 | } 223 | } 224 | } 225 | 226 | /* 227 | * Performs a dry run without measurements, good for debugging 228 | */ 229 | void BitwiseFramework::dry_run(){ 230 | for (size_t bit_idx = 0; bit_idx < this->input_space_bits.size(); bit_idx++) 231 | { 232 | uint64_t unmappable = 0; 233 | // Init bitmask iterator 234 | this->addr->init_bitmask_iterator(this->input_space_bits[bit_idx]); 235 | 236 | // Reduce by applying lower and upper bit bounds 237 | auto reduce = 0; 238 | std::vector bit_idx_reduce; 239 | std::vector bit_idx_expand; 240 | for(auto b: this->input_space_bits[bit_idx]){ 241 | if(b >= this->bit_limit_hi || b <= this->bit_limit_lo){ 242 | reduce++; 243 | bit_idx_expand.push_back(b); 244 | }else{ 245 | bit_idx_reduce.push_back(b); 246 | } 247 | } 248 | size_t iterations = 1L << (this->input_space_bits[bit_idx].size()-reduce); 249 | 250 | 251 | PLOG_INFO << "Dry run on h[" << bit_idx << "] iteration count " << iterations; 252 | std::vector seen_idx(iterations); 253 | // Iterate over addresses dumping truth table 254 | int perc = 0, last_perc = -1; 255 | for (size_t i = 0; i < iterations ; i++) 256 | { 257 | // Try to determine output class of an address 258 | auto addr_tuple = this->addr->advance_bitmask_iterator(1ULL << (bit_idx_reduce[0])); 259 | pointer select_addr = addr_tuple.first; 260 | bool can_map = addr_tuple.second; 261 | if(!can_map){ 262 | unmappable++; 263 | } 264 | 265 | seen_idx[idx_from_idx_vec_and_addr(bit_idx_reduce,select_addr)] = true; 266 | perc = (int)(i * 100.0 / iterations); 267 | if(perc != last_perc) { 268 | PLOG_DEBUG << perc <<"% " << i << "/" << iterations; 269 | last_perc = perc; 270 | } 271 | } 272 | // If one index has not been mapped throw an error 273 | bool abort = false; 274 | for (size_t i = 0; i < iterations; i++) 275 | { 276 | if(!seen_idx[i]){ 277 | PLOG_ERROR << "Bit:" << bit_idx << " Unmapped Idx:" << i; 278 | abort = true; 279 | } 280 | } 281 | if(abort){ 282 | PLOG_ERROR << "Unexplored index detected, for bit " << bit_idx << ". This is most likely a bug!"; 283 | } 284 | PLOG_INFO << unmappable << "/" << ((float)unmappable/(float)iterations)*100 <<"%" << " Addresses not mappable for bit " << bit_idx; 285 | } 286 | 287 | } 288 | 289 | /* 290 | * Initializer for the framework that allows to pin the code to a core. 291 | */ 292 | BitwiseFramework::BitwiseFramework(int core, IAddr* addr, Oracle* oracle) { 293 | // Set selected oracle and address type 294 | this->addr = addr; 295 | this->oracle = oracle; 296 | // Pin to selected core 297 | cpu_set_t mask; 298 | mask.__bits[0] = 1 << core; 299 | sched_setaffinity(getpid(), sizeof(cpu_set_t), &mask); 300 | } 301 | 302 | BitwiseFramework::~BitwiseFramework() { 303 | delete this->addr; 304 | delete this->oracle; 305 | } 306 | 307 | -------------------------------------------------------------------------------- /framework/src/main.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "CLI/App.hpp" 28 | #include "CLI/Formatter.hpp" 29 | #include "CLI/Config.hpp" 30 | 31 | #include "../include/addr.hpp" 32 | #include "../include/utils.hpp" 33 | #include "../include/framework.hpp" 34 | #include "../include/oracle.hpp" 35 | 36 | 37 | int main(int argc, char** argv) { 38 | // Parse command line arguments 39 | CLI::App app{"Unscatter framework."}; 40 | 41 | int core = 0; 42 | app.add_option("-c,--core", core, "Core to pin framwork to"); 43 | 44 | int set = 0; 45 | app.add_option("-s,--set-option", set, "Set currently implemented frameworks 0=Slice Direct, 1=Slice Indirect, 2=Utag Indirect, 3=DRAM Indirect"); 46 | 47 | int output_classes = 0; 48 | app.add_option("-o,--output-classes", output_classes, "Number of output classes, (if not given then determined automatically)"); 49 | 50 | int tresh_oracle = 9; 51 | app.add_option("-t,--tresh-oracle", tresh_oracle, "Treshold of oracle in percent default 90%"); 52 | 53 | int bit_limit_hi = 64; 54 | app.add_option("-u,--bit-limit-upper", bit_limit_hi, "Bit limit for highest bit that gets dumped"); 55 | 56 | int bit_limit_lo = 0; 57 | app.add_option("-l,--bit-limit-lower", bit_limit_lo, "Bit limit for lowest bit that gets dumped"); 58 | 59 | std::string input_bits_file = ""; 60 | app.add_option("-r,--relevant-input-bits", input_bits_file, "File containing the relevant input bits"); 61 | 62 | bool dry_run = false; 63 | app.add_flag("--dry-run",dry_run,"Perform a dry run without measurements."); 64 | 65 | bool is_xeon = false; 66 | app.add_flag("--xeon",is_xeon,"Enable if tested processor is an Intel Xeon chip"); 67 | 68 | //std::string measurement_dir = ""; 69 | //app.add_option("-m,--measurement-result-dir", input_bits_file, "Directory for the measurement results"); 70 | 71 | // Parse command line ars 72 | CLI11_PARSE(app,argc,argv); 73 | 74 | // Init logging 75 | static plog::ColorConsoleAppender consoleAppender; 76 | plog::init(plog::debug, &consoleAppender); 77 | 78 | // Check if started as root 79 | if (geteuid()) { 80 | PLOG_FATAL << "Framework must be run as root"; 81 | exit(1); 82 | } 83 | 84 | // Check if output dir exists 85 | if(std::filesystem::is_directory("measurements")){ 86 | PLOG_INFO << "Delete ./measurements folde and rerun unscatter to discard previous results"; 87 | exit(1); 88 | }else{ 89 | PLOG_INFO << "Creating ./measurement folder"; 90 | std::filesystem::create_directory("measurements"); 91 | } 92 | 93 | // Time execution 94 | auto start = std::chrono::high_resolution_clock::now(); 95 | 96 | 97 | Oracle* oracle; 98 | IAddr* addr; 99 | Framework* framework; 100 | switch (set) 101 | { 102 | case 0: 103 | PLOG_INFO << "Measuring cache slices with performance counters"; 104 | oracle = new SliceOracle(10,tresh_oracle,is_xeon); 105 | addr = new PhysAddr(); 106 | framework = new BitwiseFramework(core,addr,oracle); 107 | break; 108 | case 1: 109 | PLOG_INFO << "Measuring cache slices with timing"; 110 | addr = new PhysAddr(); 111 | oracle = new SliceTimingOracle(10,tresh_oracle,addr); 112 | framework = new BitwiseFramework(core,addr,oracle); 113 | break; 114 | case 2: 115 | PLOG_INFO << "Measuring utag hash function"; 116 | addr = new VirtAddr(30); 117 | oracle = new UtagOracle(10,tresh_oracle,addr); 118 | framework = new NaiveFramework(core,addr,oracle); 119 | output_classes = oracle->output_classes; 120 | break; 121 | case 3: 122 | PLOG_INFO << "Measuring DRAM addressing function"; 123 | addr = new PhysAddr(); 124 | oracle = new DramaOracle(10,tresh_oracle,addr); 125 | framework = new NaiveFramework(core,addr,oracle); 126 | output_classes = oracle->output_classes; 127 | break; 128 | default: 129 | PLOG_ERROR << "Invalid oracle selected current choices 0-3"; 130 | exit(1); 131 | } 132 | 133 | framework->bit_limit_hi = bit_limit_hi; 134 | framework->bit_limit_lo = bit_limit_lo; 135 | 136 | // Set output classes if given else infer 137 | if(output_classes == 0){ 138 | PLOG_INFO << "Determening output classes"; 139 | framework->determine_output_classes(); 140 | }else{ 141 | oracle->output_classes = output_classes; 142 | framework->output_classes = output_classes; 143 | } 144 | PLOG_INFO << "Output class count: " << framework->output_classes; 145 | 146 | if(input_bits_file == ""){ 147 | PLOG_INFO << "Measuring relevant input bits"; 148 | framework->get_input_space_bits(); 149 | }else{ 150 | PLOG_INFO << "Reading relevant input bits from " << input_bits_file; 151 | std::ifstream file(input_bits_file.c_str()); 152 | typedef boost::tokenizer > Tokenizer; 153 | std::vector vec; 154 | std::string line; 155 | 156 | while (getline(file,line)) 157 | { 158 | Tokenizer tok(line); 159 | vec.assign(tok.begin(),tok.end()); 160 | std::vector bits; 161 | for(auto elem : vec){ 162 | bits.push_back(std::stoi(elem)); 163 | } 164 | framework->input_space_bits.push_back(bits); 165 | framework->input_space_linear.push_back(false); 166 | } 167 | } 168 | 169 | PLOG_INFO << "Relevant input bits for h[x]:\n" <input_space_bits; 170 | PLOG_INFO << "Linear bits of h:\n" << framework->input_space_linear; 171 | if(dry_run){ 172 | PLOG_INFO << "Performing dry run"; 173 | framework->dry_run(); 174 | }else{ 175 | PLOG_INFO << "Dumping truth table"; 176 | framework->dump_truth_table(); 177 | } 178 | 179 | // Log execution time 180 | auto stop = std::chrono::high_resolution_clock::now(); 181 | auto duration = std::chrono::duration_cast(stop - start); 182 | PLOG_INFO << "Overall runtime " << duration.count() << "s"; 183 | 184 | } 185 | -------------------------------------------------------------------------------- /framework/src/naive-framework.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include "CLI/App.hpp" 24 | #include "CLI/Formatter.hpp" 25 | #include "CLI/Config.hpp" 26 | 27 | #include "../include/addr.hpp" 28 | #include "../include/utils.hpp" 29 | #include "../include/framework.hpp" 30 | #include "../include/oracle.hpp" 31 | 32 | #define THRESHOLD_FIXPOINT_ITERATION 500 33 | #define ITERATIONS_INPUT_SPACE_MEASURE 10 34 | #define WEAK_ORACLE_FIXPOINT_ITERATON 100 35 | #define MAX_RETRIES_ORACLE 10000 36 | #define MULTIMEASURE 37 | 38 | /* 39 | * Peroforms fixpoint iteration with threshold to find the number of output 40 | * classes. 41 | * If concrete output classes are known beforehand this part can be skipped. 42 | * To skip this step just overwrite the output classes of the framework direcly. 43 | */ 44 | void NaiveFramework::determine_output_classes() { 45 | std::set oracle_outputs; 46 | uint64_t unchanged_iterations = 0; 47 | while (unchanged_iterations < THRESHOLD_FIXPOINT_ITERATION) { 48 | auto size_before = oracle_outputs.size(); 49 | auto random_addr = this->addr->get_random_addr(); 50 | auto mapped_addr = this->addr->map_addr(random_addr); 51 | oracle_outputs.insert(this->oracle->oracle(mapped_addr)); 52 | auto size_after = oracle_outputs.size(); 53 | if (size_before == size_after) { 54 | unchanged_iterations++; 55 | } else { 56 | unchanged_iterations = 0; 57 | } 58 | } 59 | this->output_classes = oracle_outputs.size(); 60 | this->oracle->output_classes = this->output_classes; 61 | } 62 | 63 | /* 64 | * Returns the bit indices relevant for the output. 65 | * The precision of this measurement can be increased by increasing the 66 | * threshold parameter 67 | */ 68 | void NaiveFramework::get_input_space_bits() { 69 | std::vector out_class_bits; 70 | PLOG_INFO << "Computing relevant input space"; 71 | PLOG_DEBUG << this->addr->maxbits; 72 | // Try each address bit that can be mapped 73 | bool linear = true; 74 | for (size_t addr_bit= 0; addr_bit < this->addr->maxbits; addr_bit++) { 75 | // True and false counts to determine linearity 76 | int true_cnt = 0; 77 | int false_cnt = 0; 78 | bool addr_bit_relevant = false; 79 | // Perform iterations to determine likelyhood that the input bit influences the output result 80 | for (size_t iteration = 0; iteration < ITERATIONS_INPUT_SPACE_MEASURE; 81 | iteration++) { 82 | // Get a pair of addresses where the bit index addr_bit is flipped between the two addresses 83 | auto pair = this->addr->get_flip_pair(addr_bit); 84 | auto addr_1 = pair.first; 85 | auto addr_2 = pair.second; 86 | auto addr_1_mapped = this->addr->map_addr(addr_1); 87 | auto addr_2_mapped = this->addr->map_addr(addr_2); 88 | 89 | auto first_measure = this->oracle->oracle_robust(addr_1_mapped, addr_1, MAX_RETRIES_ORACLE); 90 | auto second_measure = this->oracle->oracle_robust(addr_2_mapped, addr_2, MAX_RETRIES_ORACLE); 91 | 92 | if(first_measure != second_measure){ 93 | addr_bit_relevant = true; 94 | true_cnt++; 95 | }else{ 96 | false_cnt++; 97 | } 98 | } 99 | PLOG_DEBUG <<"Bit:" << addr_bit << " rel/irel:" << true_cnt << "/" << false_cnt; 100 | if (addr_bit_relevant) { 101 | out_class_bits.push_back(addr_bit); 102 | linear &= (true_cnt == ITERATIONS_INPUT_SPACE_MEASURE); 103 | } 104 | } 105 | // If there are relevant bits record them 106 | if(out_class_bits.size() > 0){ 107 | this->input_space_bits.push_back(out_class_bits); 108 | this->input_space_linear.push_back(linear); 109 | } 110 | 111 | // Dump relevant input space bits 112 | std::ofstream bitfile; 113 | bitfile.open("measurements/bits.csv"); 114 | 115 | for(auto input_space : this->input_space_bits){ 116 | if(input_space_bits.size() != 0){ 117 | for (size_t i = 0; i < input_space.size(); i++) 118 | { 119 | if(input_space[i] >= this->bit_limit_hi || input_space[i] <= this->bit_limit_lo){continue;} 120 | if(i != input_space.size()-1){ 121 | bitfile << input_space[i] << ", "; 122 | }else{ 123 | bitfile << input_space[i]; 124 | } 125 | } 126 | bitfile << "\n"; 127 | } 128 | } 129 | } 130 | 131 | /* 132 | * Dumps truth tables for all relevant bits 133 | */ 134 | void NaiveFramework::dump_truth_table(){ 135 | 136 | // Open dumpfile 137 | std::ofstream dumpfile; 138 | dumpfile.open("measurements/allbits.csv"); 139 | dumpfile << "addr,class\n"; 140 | 141 | // Init bitmask iterator 142 | this->addr->init_bitmask_iterator(this->input_space_bits[0]); 143 | 144 | // Reduce by applying lower and upper bit bounds 145 | auto reduce = 0; 146 | std::vector bit_idx_reduce; 147 | std::vector bit_idx_expand; 148 | for(auto b: this->input_space_bits[0]){ 149 | if(b >= this->bit_limit_hi || b <= this->bit_limit_lo){ 150 | reduce++; 151 | bit_idx_expand.push_back(b); 152 | }else{ 153 | bit_idx_reduce.push_back(b); 154 | } 155 | } 156 | size_t iterations = 1L << (this->input_space_bits[0].size()-reduce); 157 | 158 | 159 | PLOG_INFO << "Dumping h naively, iteration count " << iterations; 160 | 161 | // Iterate over addresses dumping truth table 162 | int perc = 0, last_perc = -1; 163 | for (size_t i = 0; i < iterations ; i++) 164 | { 165 | // Try to determine output class of an address 166 | auto addr_tuple = this->addr->advance_bitmask_iterator(1ULL << (bit_idx_reduce[0])); 167 | pointer select_addr = addr_tuple.first; 168 | bool can_map = addr_tuple.second; 169 | if(!can_map){ 170 | dumpfile << select_addr << ", "<< "-" << "\n"; 171 | }else{ 172 | int count = 0; 173 | // Try to map an address 174 | auto mapped_addr = this->addr->map_addr(select_addr); 175 | // Try to measure until succesfull 176 | bool oracle_success = false; 177 | uint64_t oracle_out = 0; 178 | while (!oracle_success) 179 | { 180 | try{ 181 | // This can error if the maximum retry is exceeded 182 | oracle_out = this->oracle->oracle_robust(mapped_addr,select_addr,10); 183 | //std::cout << ((oracle_out>>bit_idx) & 0x1) << "\n"; 184 | dumpfile << select_addr << ", "<< oracle_out << "\n"; 185 | oracle_success = true; 186 | }catch (...){ 187 | PLOG_DEBUG << "REMEASURE triggerd while dumping"; 188 | } 189 | } 190 | } 191 | 192 | perc = (int)(i * 100.0 / iterations); 193 | if(perc != last_perc) { 194 | PLOG_DEBUG << perc <<"% " << i << "/" << iterations; 195 | last_perc = perc; 196 | } 197 | } 198 | } 199 | 200 | /* 201 | * Performs a dry run without measurements, good for debugging 202 | */ 203 | void NaiveFramework::dry_run(){ 204 | uint64_t unmappable = 0; 205 | // Init bitmask iterator 206 | this->addr->init_bitmask_iterator(this->input_space_bits[0]); 207 | 208 | // Reduce by applying lower and upper bit bounds 209 | auto reduce = 0; 210 | std::vector bit_idx_reduce; 211 | std::vector bit_idx_expand; 212 | for(auto b: this->input_space_bits[0]){ 213 | if(b >= this->bit_limit_hi || b <= this->bit_limit_lo){ 214 | reduce++; 215 | bit_idx_expand.push_back(b); 216 | }else{ 217 | bit_idx_reduce.push_back(b); 218 | } 219 | } 220 | size_t iterations = 1L << (this->input_space_bits[0].size()-reduce); 221 | 222 | PLOG_INFO << "Dry run on h naive iteration count " << iterations; 223 | // Iterate over addresses dumping truth table 224 | int perc = 0, last_perc = -1; 225 | for (size_t i = 0; i < iterations ; i++) 226 | { 227 | // Try to determine output class of an address 228 | auto addr_tuple = this->addr->advance_bitmask_iterator(1ULL << (bit_idx_reduce[0])); 229 | pointer select_addr = addr_tuple.first; 230 | bool can_map = addr_tuple.second; 231 | if(!can_map){ 232 | unmappable++; 233 | } 234 | 235 | perc = (int)(i * 100.0 / iterations); 236 | if(perc != last_perc) { 237 | PLOG_DEBUG << perc <<"% " << i << "/" << iterations; 238 | last_perc = perc; 239 | } 240 | } 241 | PLOG_INFO << unmappable << " Addresses not mappable when dumping all function bits"; 242 | } 243 | 244 | /* 245 | * Initializer for the framework that allows to pin the code to a core. 246 | */ 247 | NaiveFramework::NaiveFramework(int core, IAddr* addr, Oracle* oracle) { 248 | // Set selected oracle and address type 249 | this->addr = addr; 250 | this->oracle = oracle; 251 | // Pin to selected core 252 | cpu_set_t mask; 253 | mask.__bits[0] = 1 << core; 254 | sched_setaffinity(getpid(), sizeof(cpu_set_t), &mask); 255 | } 256 | 257 | NaiveFramework::~NaiveFramework() { 258 | delete this->addr; 259 | delete this->oracle; 260 | } 261 | 262 | -------------------------------------------------------------------------------- /framework/src/oracles/drama-oracle.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "../../include/oracle.hpp" 11 | #include "../../include/utils.hpp" 12 | #include "ptedit_header.h" 13 | 14 | 15 | #define ALLIGN_PAGE(a) (a & ~(4096-1)) + 4096 16 | #define FIXPOINT_DRAM 100 17 | 18 | void DramaOracle::build_oracle(){ 19 | // Set up initial address 20 | std::vector start = {ALLIGN_PAGE(this->addr->get_random_addr())}; 21 | this->class_examples.push_back(start); 22 | 23 | // Iterate until fixpoint reached 24 | auto unchanged_iterations = 0; 25 | while(unchanged_iterations < FIXPOINT_DRAM){ 26 | auto rand_addr = ALLIGN_PAGE(this->addr->get_random_addr()); 27 | auto rand_addr_mapped = this->addr->map_addr(rand_addr); 28 | bool is_new_class = true; 29 | // Iterate over all previously seen classes 30 | for (size_t i = 0; i < this->class_examples.size(); i++) 31 | { 32 | auto class_mapped = this->addr->map_addr(this->class_examples[i][0]); 33 | if(weak_drama_oracle((void*) class_mapped, (void*) rand_addr_mapped ,this->threshold)){ 34 | // If example is already in classes stop 35 | is_new_class = false; 36 | this->class_examples[i].push_back(rand_addr); 37 | break; 38 | } 39 | } 40 | 41 | if(is_new_class){ 42 | std::vector new_class = {rand_addr}; 43 | this->class_examples.push_back(new_class); 44 | PLOG_INFO << "Class count:" << this->class_examples.size(); 45 | unchanged_iterations = 0; 46 | } 47 | unchanged_iterations++; 48 | } 49 | 50 | // Filter classes only containing one element 51 | this->class_examples.erase(std::remove_if(this->class_examples.begin(), this->class_examples.end(), 52 | [](std::vector v) { return v.size() <= 1; }), this->class_examples.end()); 53 | PLOG_INFO << "Class count filtered:" << this->class_examples.size(); 54 | 55 | // Print solution 56 | // for (std::vector p : this->class_examples) 57 | // { 58 | // std::cout << p.size() << " "; 59 | // } 60 | // std::cout << "\n"; 61 | this->output_classes = class_examples.size(); 62 | } 63 | 64 | 65 | DramaOracle::DramaOracle(int runs, int confidence, IAddr* addr) : Oracle(runs,confidence) 66 | { 67 | this->addr = addr; 68 | PLOG_INFO << "Determening Treshold"; 69 | determine_treshold(); 70 | PLOG_INFO << "Treshold set to " << this->threshold; 71 | build_oracle(); 72 | } 73 | 74 | DramaOracle::~DramaOracle() 75 | { 76 | 77 | } 78 | 79 | uint64_t DramaOracle::oracle(pointer addr){ 80 | addr = ALLIGN_PAGE(addr); 81 | int hist[this->class_examples.size()] = {0}; 82 | for (size_t i = 0; i < this->class_examples.size(); i++) 83 | { 84 | for(auto class_example : this->class_examples[i]){ 85 | auto class_map = this->addr->map_addr(class_example); 86 | if(this->weak_drama_oracle((void*) class_map, (void*) addr,20)){ 87 | hist[i]++; 88 | } 89 | } 90 | } 91 | // Normalize 92 | for (size_t i = 0; i < this->class_examples.size(); i++) 93 | { 94 | hist[i] /= this->class_examples[i].size(); 95 | } 96 | return std::distance(hist, std::max_element(hist,hist+this->class_examples.size())); 97 | } 98 | 99 | void DramaOracle::determine_treshold(){ 100 | std::vector measurements; 101 | for (size_t i = 0; i < 5000; i++) 102 | { 103 | auto rand_addr_1 = ALLIGN_PAGE(this->addr->get_random_addr()); 104 | auto rand_addr_mapped_1 = this->addr->map_addr(rand_addr_1); 105 | auto rand_addr_2 = ALLIGN_PAGE(this->addr->get_random_addr()); 106 | auto rand_addr_mapped_2 = this->addr->map_addr(rand_addr_2); 107 | uint64_t res = getTiming(rand_addr_mapped_1,rand_addr_mapped_2); 108 | measurements.push_back(res); 109 | } 110 | this->threshold = ostsu_treshold(measurements); 111 | } 112 | 113 | bool DramaOracle::weak_drama_oracle(void* address1, void* address2, size_t threshold) 114 | { 115 | auto timing = getTiming((pointer) address1, (pointer) address2); 116 | return timing > threshold; 117 | } 118 | 119 | uint64_t DramaOracle::getTiming(pointer first, pointer second) { 120 | size_t min_res = (-1ull); 121 | for (int i = 0; i < 4; i++) { 122 | size_t number_of_reads = num_reads; 123 | volatile size_t *f = (volatile size_t *) first; 124 | volatile size_t *s = (volatile size_t *) second; 125 | 126 | for (int j = 0; j < 10; j++) 127 | sched_yield(); 128 | size_t t0 = rdtsc(); 129 | 130 | while (number_of_reads-- > 0) { 131 | *f; 132 | *(f + number_of_reads); 133 | 134 | *s; 135 | *(s + number_of_reads); 136 | 137 | asm volatile("clflush (%0)" : : "r" (f) : "memory"); 138 | asm volatile("clflush (%0)" : : "r" (s) : "memory"); 139 | } 140 | 141 | uint64_t res = (rdtsc2() - t0) / (num_reads); 142 | for (int j = 0; j < 10; j++) 143 | sched_yield(); 144 | if (res < min_res) 145 | min_res = res; 146 | } 147 | return min_res; 148 | } 149 | 150 | -------------------------------------------------------------------------------- /framework/src/oracles/oracle.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | 6 | #include "../../include/oracle.hpp" 7 | 8 | Oracle::Oracle(int runs, int confidence){ 9 | this->runs = runs; 10 | this->confidence = confidence; 11 | } 12 | 13 | uint64_t Oracle::oracle_robust(pointer addr, pointer paddr, int retries){ 14 | if(this->output_classes == 0){ 15 | std::throw_with_nested(std::runtime_error("Set output classes before starting robust measurement, output classes can also be set highter than required\n")); 16 | } 17 | #ifdef DEBUG 18 | std::cout << "\r" << std::hex << addr << " [0x" << paddr << "] " << std::dec; 19 | #endif 20 | for (size_t t = 0; t < retries; t++) 21 | { 22 | std::vector hist(std::max(this->output_classes,(uint64_t)MAX_OUTPUT_CLASS_ASSUMPTION),0); 23 | for (size_t i = 0; i < this->runs; i++) 24 | { 25 | hist[this->oracle(addr)]++; 26 | } 27 | auto it = std::max_element(hist.begin(), hist.end()); 28 | if(*it > this->confidence){ 29 | return std::distance(hist.begin(), it); 30 | } 31 | 32 | PLOG_DEBUG << "Remeasure triggered for 0x" << std::hex << addr << " [0x" << paddr << "] " << std::dec << " max was " << *it << "/" << runs; 33 | } 34 | std::throw_with_nested(std::runtime_error("Maximum retries for robust oracle exceeded.\n")); 35 | return 0; // Unreachable 36 | } 37 | -------------------------------------------------------------------------------- /framework/src/oracles/slice-oracle.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "../../include/oracle.hpp" 7 | #include "../../include/utils.hpp" 8 | 9 | 10 | SliceOracle::SliceOracle(int runs, int confidence,bool is_xeon): Oracle(runs,confidence) 11 | { 12 | this->is_xeon = is_xeon; 13 | this->cores = phys_cores(); 14 | 15 | int name[4] = {0, 0, 0, 0}; 16 | __cpuid(0, this->cpu_architecture, name[0], name[2], name[1]); 17 | if(!strcmp((char *) name, "GenuineIntel") == 0) { 18 | int res[4]; 19 | __cpuid(1, res[0], res[1], res[2], res[3]); 20 | this->cpu_architecture = ((res[0] >> 8) & 7) + (res[0] >> 20) & 255; 21 | } 22 | } 23 | 24 | SliceOracle::~SliceOracle() 25 | { 26 | 27 | } 28 | 29 | uint64_t SliceOracle::oracle(pointer addr){ 30 | return measure_slice((void* )addr); 31 | } 32 | 33 | size_t SliceOracle::measure_slice(void* address) { 34 | if(this->is_xeon) { 35 | return measure_slice_xeon(address); 36 | } else { 37 | return measure_slice_core(address); 38 | } 39 | } 40 | 41 | size_t SliceOracle::measure_slice_core(void *address) { 42 | int msr_unc_perf_global_ctr; 43 | int val_enable_ctrs; 44 | int ctr_msr = 0x706, ctr_space = 0x10; 45 | int ctrl_msr = 0x700, ctrl_space = 0x10; 46 | int ctrl_config = 0x408f34; 47 | if(this->cpu_architecture >= 0x16) { 48 | // >= skylake 49 | msr_unc_perf_global_ctr = 0xe01; 50 | val_enable_ctrs = 0x20000000; 51 | if(this->cpu_architecture >= 0x1b) { 52 | // >= ice lake 53 | ctr_msr = 0x702; 54 | ctr_space = 0x8; 55 | ctrl_space = 0x8; 56 | ctrl_config = 0x408834; 57 | if(this->cpu_architecture >= 0x20) { 58 | // >= alder lake 59 | msr_unc_perf_global_ctr = 0x2ff0; 60 | ctr_msr = 0x2002; 61 | ctr_space = 0x8; 62 | ctrl_space = 0x8; 63 | ctrl_msr = 0x2000; 64 | } 65 | } 66 | } else { 67 | msr_unc_perf_global_ctr = 0x391; 68 | val_enable_ctrs = 0x2000000f; 69 | } 70 | 71 | // Disable counters 72 | if(wrmsr(0, msr_unc_perf_global_ctr, 0x0)) { 73 | return -1ull; 74 | } 75 | 76 | // Reset counters 77 | for (int i = 0; i < this->cores; i++) { 78 | wrmsr(0, ctr_msr + i * ctr_space, 0x0); 79 | } 80 | 81 | // Select event to monitor 82 | for (int i = 0; i < this->cores; i++) { 83 | wrmsr(0, ctrl_msr + i * ctrl_space, ctrl_config); 84 | } 85 | 86 | // Enable counting 87 | if(wrmsr(0, msr_unc_perf_global_ctr, val_enable_ctrs)) { 88 | return -1ull; 89 | } 90 | 91 | // Monitor 92 | int access = 10000; 93 | while (--access) { 94 | flush(address); 95 | } 96 | 97 | // Read counter 98 | size_t cboxes[this->cores]; 99 | for (int i = 0; i < this->cores; i++) { 100 | int cnt = rdmsr( 0, ctr_msr + i * ctr_space); 101 | if(cnt < 0) cnt = 0; 102 | cboxes[i] = cnt; 103 | } 104 | 105 | return find_index_of_nth_largest_size_t(cboxes, this->cores, 0); 106 | } 107 | 108 | 109 | size_t SliceOracle::measure_slice_xeon(void *address) { 110 | #define REP4(x) x x x x 111 | #define REP16(x) REP4(x) REP4(x) REP4(x) REP4(x) 112 | #define REP256(x) REP16(x) REP16(x) REP16(x) REP16(x) REP16(x) REP16(x) REP16(x) REP16(x) REP16(x) REP16(x) REP16(x) REP16(x) REP16(x) REP16(x) REP16(x) REP16(x) 113 | #define REP1K(x) REP256(x) REP256(x) REP256(x) REP256(x) 114 | #define REP4K(x) REP1K(x) REP1K(x) REP1K(x) REP1K(x) 115 | 116 | char buffer[16]; 117 | char path[1024]; 118 | int event[512]; 119 | memset(event, 0, sizeof(event)); 120 | volatile int dummy = 0; 121 | int slices = 0; 122 | 123 | DIR* dir = opendir("/sys/bus/event_source/devices/"); 124 | if(!dir) return -1; 125 | struct dirent* entry; 126 | while((entry = readdir(dir)) != NULL) { 127 | if(!strncmp(entry->d_name, "uncore_cha_", 11)) { 128 | snprintf(path, sizeof(path), "/sys/bus/event_source/devices/%s/type", entry->d_name); 129 | FILE* f = fopen(path, "r"); 130 | if(!f) return -1; 131 | dummy += fread(buffer, 16, 1, f); 132 | fclose(f); 133 | int slice = atoi(entry->d_name + 11); 134 | int event_id = atoi(buffer); 135 | event[slice] = event_id; 136 | if(slice > slices) slices = slice; 137 | } 138 | } 139 | closedir(dir); 140 | 141 | size_t hist[slices]; 142 | memset(hist, 0, sizeof(hist)); 143 | 144 | int fds[slices]; 145 | 146 | for(int i = 0; i < slices; i++) { 147 | fds[i] = event_open((enum perf_type_id)(event[i]), 0x1bc10000ff34, 0, 0, 0, i); 148 | ioctl(fds[i], PERF_EVENT_IOC_ENABLE, 0); 149 | ioctl(fds[i], PERF_EVENT_IOC_RESET, 0); 150 | } 151 | REP4K(asm volatile("mfence; clflush (%0); mfence; \n" : : "r" (address) : "memory"); *(volatile char*)address;) 152 | for(int i = 0; i < slices; i++) { 153 | ioctl(fds[i], PERF_EVENT_IOC_DISABLE, 0); 154 | 155 | long long result = 0; 156 | read(fds[i], &result, sizeof(result)); 157 | hist[i] = result; 158 | close(fds[i]); 159 | } 160 | 161 | return find_index_of_nth_largest_size_t(hist, slices, 0); 162 | } 163 | 164 | 165 | -------------------------------------------------------------------------------- /framework/src/oracles/slice-timing-oracle.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "../../include/oracle.hpp" 11 | #include "../../include/utils.hpp" 12 | 13 | #define ALLIGN_PAGE(a) (a & ~(4096-1)) + 4096 14 | 15 | 16 | SliceTimingOracle::SliceTimingOracle(int runs, int confidence, IAddr* addr) : Oracle(runs,confidence) 17 | { 18 | this->addr = addr; 19 | this->cores = phys_cores(); 20 | PLOG_INFO << "Determening Treshold"; 21 | determine_treshold(); 22 | PLOG_INFO << "Treshold set to " << this->threshold; 23 | } 24 | 25 | SliceTimingOracle::~SliceTimingOracle() 26 | { 27 | } 28 | 29 | uint64_t SliceTimingOracle::oracle(pointer addr){ 30 | uint64_t hist[this->cores] = {0}; 31 | for (size_t i = 0; i < 5000; i++) 32 | { 33 | for (size_t c = 0; c < this->cores; c++) 34 | { 35 | pin_to_core(getpid(), c); 36 | size_t start = rdtsc(); 37 | maccess((void*) addr); 38 | flush((void*) addr); 39 | size_t end = rdtsc(); 40 | if(end-start < this->threshold){ 41 | hist[c]++; 42 | } 43 | } 44 | } 45 | return std::distance(hist, std::max_element(hist,hist+this->cores)); 46 | } 47 | 48 | void SliceTimingOracle::determine_treshold(){ 49 | std::vector measurements; 50 | for (size_t i = 0; i < 100000; i++) 51 | { 52 | auto rand_addr = this->addr->get_random_addr(); 53 | auto rand_addr_mapped = (void*) this->addr->map_addr(rand_addr); 54 | for (size_t c = 0; c < this->cores; c++) 55 | { 56 | pin_to_core(getpid(), c); 57 | size_t start = rdtsc(); 58 | maccess((void*) addr); 59 | flush((void*) addr); 60 | size_t end = rdtsc(); 61 | measurements.push_back(end-start); 62 | } 63 | } 64 | this->threshold = ostsu_treshold(measurements); 65 | } 66 | 67 | -------------------------------------------------------------------------------- /framework/src/oracles/utag-oracle.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "../../include/oracle.hpp" 11 | #include "../../include/utils.hpp" 12 | 13 | #define ALLIGN_PAGE(a) (a & ~(4096-1)) + 4096 14 | #define FIXPOINT_UTAG 1000 15 | #define UTAG_REDUCE_MAX 20 16 | 17 | void UtagOracle::build_oracle(){ 18 | // Set up initial address 19 | std::vector start = {ALLIGN_PAGE(this->addr->get_random_addr())}; 20 | this->class_examples.push_back(start); 21 | 22 | // Iterate until fixpoint reached 23 | auto unchanged_iterations = 0; 24 | while(unchanged_iterations < FIXPOINT_UTAG){ 25 | auto rand_addr = ALLIGN_PAGE(this->addr->get_random_addr()); 26 | auto rand_addr_mapped = this->addr->map_addr(rand_addr); 27 | bool is_new_class = true; 28 | // Iterate over all previously seen classes 29 | for (size_t i = 0; i < this->class_examples.size(); i++) 30 | { 31 | auto class_mapped = this->addr->map_addr(this->class_examples[i][0]); 32 | if(weak_utag_oracle((void*) class_mapped, (void*) rand_addr_mapped ,20)){ 33 | // If example is already in classes stop 34 | is_new_class = false; 35 | this->class_examples[i].push_back(rand_addr); 36 | break; 37 | } 38 | } 39 | 40 | if(is_new_class){ 41 | std::vector new_class = {rand_addr}; 42 | this->class_examples.push_back(new_class); 43 | PLOG_INFO << "Class count:" << this->class_examples.size(); 44 | unchanged_iterations = 0; 45 | } 46 | unchanged_iterations++; 47 | } 48 | 49 | // Filter classes only containing one element 50 | this->class_examples.erase(std::remove_if(this->class_examples.begin(), this->class_examples.end(), 51 | [](std::vector v) { return v.size() <= 1; }), this->class_examples.end()); 52 | PLOG_INFO << "Class count filtered:" << this->class_examples.size(); 53 | 54 | // Print solution 55 | // for (std::vector p : this->class_examples) 56 | // { 57 | // std::cout << p.size() << " "; 58 | // } 59 | // std::cout << "\n"; 60 | this->output_classes = class_examples.size(); 61 | } 62 | 63 | 64 | UtagOracle::UtagOracle(int runs, int confidence, IAddr* addr) : Oracle(runs,confidence) 65 | { 66 | this->addr = addr; 67 | this->pc_l1d_read_miss = -1; 68 | build_oracle(); 69 | } 70 | 71 | UtagOracle::~UtagOracle() 72 | { 73 | 74 | } 75 | 76 | uint64_t UtagOracle::oracle(pointer addr){ 77 | addr = ALLIGN_PAGE(addr); 78 | int hist[this->class_examples.size()] = {0}; 79 | for (size_t i = 0; i < this->class_examples.size(); i++) 80 | { 81 | for(auto class_example : this->class_examples[i]){ 82 | auto class_map = this->addr->map_addr(class_example); 83 | if(this->weak_utag_oracle((void*) class_map, (void*) addr,20)){ 84 | hist[i]++; 85 | } 86 | } 87 | } 88 | // Normalize 89 | for (size_t i = 0; i < this->class_examples.size(); i++) 90 | { 91 | hist[i] /= this->class_examples[i].size(); 92 | } 93 | return std::distance(hist, std::max_element(hist,hist+this->class_examples.size())); 94 | } 95 | 96 | bool UtagOracle::weak_utag_oracle(void* address1, void* address2, size_t threshold) 97 | { 98 | /* Open performance counter */ 99 | if (pc_l1d_read_miss == -1) { 100 | size_t type = PERF_CACHE_TYPE(PERF_COUNT_HW_CACHE_L1D, PERF_COUNT_HW_CACHE_OP_READ, PERF_COUNT_HW_CACHE_RESULT_MISS); 101 | pc_l1d_read_miss = performance_counter_open(getpid(), PERF_TYPE_HW_CACHE, type); 102 | } 103 | 104 | /* Try to read */ 105 | #define TRIES (3) 106 | #define HAMMER_TIMES (20) 107 | 108 | std::vector measurements; 109 | 110 | performance_counter_disable(pc_l1d_read_miss); 111 | performance_counter_reset(pc_l1d_read_miss); 112 | performance_counter_enable(pc_l1d_read_miss); 113 | 114 | for (size_t t = 0; t < TRIES; t++) { 115 | size_t begin = performance_counter_read(pc_l1d_read_miss); 116 | mfence(); 117 | 118 | for (size_t i = 0; i < HAMMER_TIMES; i++) { 119 | maccess(address1); 120 | mfence(); 121 | maccess(address2); 122 | mfence(); 123 | } 124 | 125 | nospec(); 126 | 127 | size_t end = performance_counter_read(pc_l1d_read_miss); 128 | measurements.push_back(end - begin); 129 | } 130 | 131 | /* Return median */ 132 | std::nth_element(measurements.begin(), measurements.begin() + measurements.size()/2, measurements.end()); 133 | //if(measurements[measurements.size()/2] > threshold){ 134 | // PLOG_INFO << measurements << " " << measurements[measurements.size()/2]; 135 | //} 136 | return measurements[measurements.size()/2] > threshold; 137 | } -------------------------------------------------------------------------------- /framework/src/phys-addr.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "ptedit_header.h" 7 | 8 | #include "../include/addr.hpp" 9 | #include "../include/utils.hpp" 10 | 11 | #define FIRST_LEVEL_ENTRIES 256 // only 256, because upper half is kernel 12 | 13 | /* 14 | * True if present bit is set 15 | */ 16 | int is_present(size_t entry) { 17 | return entry & (1ull << PTEDIT_PAGE_BIT_PRESENT); 18 | } 19 | 20 | /* 21 | * True if page has 4K size 22 | */ 23 | int is_normal_page(size_t entry) { 24 | return !(entry & (1ull << PTEDIT_PAGE_BIT_PSE)); 25 | } 26 | 27 | /* 28 | * Filters page table entries to exclude the ones that are not mapped as writeback 29 | */ 30 | boost::icl::interval_set get_page_blacklist(){ 31 | boost::icl::interval_set blacklist; 32 | int uc_mt = ptedit_find_first_mt(PTEDIT_MT_WB); 33 | 34 | size_t root = ptedit_get_paging_root(0); 35 | size_t pagesize = ptedit_get_pagesize(); 36 | size_t pml4[pagesize / sizeof(size_t)], pdpt[pagesize / sizeof(size_t)], 37 | pd[pagesize / sizeof(size_t)], pt[pagesize / sizeof(size_t)]; 38 | 39 | ptedit_read_physical_page(root / pagesize, (char *)pml4); 40 | 41 | int pml4i, pdpti, pdi, pti; 42 | 43 | /* Iterate through PML4 entries */ 44 | for (pml4i = FIRST_LEVEL_ENTRIES; pml4i < 512; pml4i++) { 45 | size_t pml4_entry = pml4[pml4i]; 46 | if (!is_present(pml4_entry)) 47 | continue; 48 | 49 | /* Iterate through PDPT entries */ 50 | ptedit_read_physical_page(ptedit_get_pfn(pml4_entry), (char *)pdpt); 51 | for (pdpti = 0; pdpti < 512; pdpti++) { 52 | size_t pdpt_entry = pdpt[pdpti]; 53 | if (!is_present(pdpt_entry)) 54 | continue; 55 | 56 | /* Iterate through PD entries */ 57 | ptedit_read_physical_page(ptedit_get_pfn(pdpt_entry), (char *)pd); 58 | for (pdi = 0; pdi < 512; pdi++) { 59 | size_t pd_entry = pd[pdi]; 60 | if (!is_present(pd_entry)) 61 | continue; 62 | 63 | /* Normal 4kb page */ 64 | if (is_normal_page(pd_entry)) { 65 | /* Iterate through PT entries */ 66 | ptedit_read_physical_page(ptedit_get_pfn(pd_entry), (char *)pt); 67 | for (pti = 0; pti < 512; pti++) { 68 | size_t pt_entry = pt[pti]; 69 | if (!is_present(pt_entry)) 70 | continue; 71 | // Remove uncachable pages 72 | if(ptedit_extract_mt(pt_entry) != uc_mt){ 73 | size_t uc_addr = ptedit_get_pfn(pt_entry)*4096; 74 | blacklist += boost::icl::discrete_interval::closed(uc_addr, uc_addr+4096); 75 | } 76 | } 77 | } 78 | } 79 | } 80 | } 81 | return blacklist; 82 | } 83 | 84 | /* 85 | * Parses dram regions on the system and returns a dram context than can be used 86 | * to map physical adresses. 87 | */ 88 | dram_ctx get_dram_ctx() { 89 | std::ifstream indata; 90 | indata.open("/proc/iomem"); 91 | if (!indata) { 92 | std::throw_with_nested( 93 | std::runtime_error("/proc/iomem could not be opened")); 94 | } 95 | 96 | boost::icl::interval_set ram_ranges; 97 | uint64_t ram_addresses = 0; 98 | 99 | std::string line = ""; 100 | std::string start = ""; 101 | const char* cStart; 102 | std::string end = ""; 103 | const char* cEnd; 104 | std::string key = ""; 105 | char* endPointer; 106 | 107 | while (getline(indata, line)) { 108 | if (line.find("System RAM") == 0) { 109 | continue; 110 | } 111 | if (line.find("\n") != std::string::npos) { 112 | line = line.substr(0, line.find("\n")); 113 | } 114 | 115 | // parse range 116 | start = line.substr(0, line.find("-")); 117 | cStart = start.c_str(); 118 | line = line.substr(line.find("-") + 1); 119 | end = line.substr(0, line.find(" : ")); 120 | cEnd = end.c_str(); 121 | key = line.substr(line.find(" : ") + 3); 122 | 123 | if (key == "System RAM") { 124 | ram_ranges += boost::icl::discrete_interval::closed(strtoull(cStart, &endPointer, 16), strtoull(cEnd, &endPointer, 16)); 125 | } 126 | } 127 | 128 | // Generate blacklist of uncachable addresses and remove them 129 | auto blacklist = get_page_blacklist(); 130 | ram_ranges = ram_ranges - blacklist; 131 | 132 | for (auto pair : ram_ranges) { 133 | ram_addresses += pair.upper() - pair.lower(); 134 | } 135 | 136 | uint64_t max_addr = 0; 137 | for(auto range: ram_ranges){ 138 | max_addr = std::max(max_addr,range.upper()); 139 | } 140 | 141 | PLOG_DEBUG << "Unmappable DRAM addresses: " <dram = get_dram_ctx(); 155 | 156 | // Print accesible memory ranges 157 | PLOG_DEBUG << std::hex << this->dram.ram_ranges << std::dec; 158 | for(auto range : this->dram.ram_ranges){ 159 | PLOG_DEBUG<< std::hex << range << std::dec <<": " <<(range.upper() - range.lower()) << "b"; 160 | } 161 | 162 | this->maxbits = ceil(log2(this->dram.ram_addresses)); 163 | PLOG_INFO << this->maxbits << " bits of physical memory can be mapped "; 164 | 165 | // Map all availiable physical memory 166 | int fd = open("/dev/mem", O_RDONLY); 167 | if(fd < 3) { 168 | PLOG_FATAL << "Could not open /dev/mem! Did you load the kernel module and start as root?"; 169 | exit(1); 170 | } 171 | 172 | this->map_base = (char*) mmap(0, this->dram.ram_addresses*2, PROT_READ, MAP_SHARED, fd, 0); 173 | assert(this->map_base != MAP_FAILED); 174 | if(this->map_base == (char*)-1 || this->map_base == NULL) { 175 | PLOG_FATAL << "Could not map physical memory!"; 176 | exit(1); 177 | } 178 | PLOG_INFO << "Physical memory mapped at base address: 0x"<< std::hex << (uint64_t) this->map_base << std::dec; 179 | } 180 | 181 | /* 182 | * Remove ptedit 183 | */ 184 | PhysAddr::~PhysAddr() { 185 | munmap(this->map_base, this->dram.ram_addresses*2); 186 | ptedit_cleanup(); 187 | } 188 | 189 | /* 190 | * Checks if the address maps to a valid dram range 191 | */ 192 | bool PhysAddr::valid_address(pointer addr){ 193 | return this->dram.ram_ranges.find(addr) != this->dram.ram_ranges.end(); 194 | } 195 | 196 | 197 | /* 198 | * Maps an address and returns a pointer to the mapping 199 | */ 200 | pointer PhysAddr::map_addr(pointer addr){ 201 | return (pointer)(this->map_base + addr); 202 | } 203 | 204 | void PhysAddr::make_uncachable(){ 205 | PLOG_INFO << "Making memory uncachable"; 206 | int uc_mt = ptedit_find_first_mt(PTEDIT_MT_UC); 207 | for(auto range: dram.ram_ranges){ 208 | for(size_t offset = range.lower(); offset < range.upper(); offset+=4096){ 209 | pointer vaddr = map_addr(offset); 210 | ptedit_entry_t entry = ptedit_resolve((void*)vaddr, 0); 211 | entry.pte = ptedit_apply_mt(entry.pte, uc_mt); 212 | entry.valid = PTEDIT_VALID_MASK_PTE; 213 | ptedit_update((void*)vaddr, 0, &entry); 214 | } 215 | } 216 | } 217 | 218 | void PhysAddr::make_cachable(){ 219 | PLOG_INFO << "Making memory cachable"; 220 | int wb_mt = ptedit_find_first_mt(PTEDIT_MT_WB); 221 | for(auto range: dram.ram_ranges){ 222 | for(size_t offset = range.lower(); offset < range.upper(); offset+=4096){ 223 | pointer vaddr = map_addr(offset); 224 | ptedit_entry_t entry = ptedit_resolve((void*)vaddr, 0); 225 | entry.pte = ptedit_apply_mt(entry.pte, wb_mt); 226 | entry.valid = PTEDIT_VALID_MASK_PTE; 227 | ptedit_update((void*)vaddr, 0, &entry); 228 | } 229 | } 230 | } 231 | 232 | /* 233 | * Returns a random physical address 234 | * This can potentially be optimized by directky returning a valid address from one of the intervals 235 | */ 236 | pointer PhysAddr::get_random_addr(){ 237 | // Select address smaller than max dram range 238 | int range_select = this->rnd(this->dram.ram_ranges.iterative_size()); 239 | auto range_it = *std::next(this->dram.ram_ranges.begin(), range_select); 240 | auto range_delta = range_it.upper()-range_it.lower(); 241 | int low = contains(range_it, lower(range_it)) ? 0 : 1; 242 | int up = contains(range_it, upper(range_it)) ? 0 : 1; 243 | pointer addr = (range_it.lower() + low) + (this->rnd(range_delta-up)); 244 | 245 | if(!valid_address(addr)){ 246 | PLOG_FATAL << "Invalid physical address selected: " << range_select << " " << range_it << " " << addr << " " << (addr&0x11111) << " " << range_delta << "\n"; 247 | exit(1); 248 | } 249 | return addr; 250 | } 251 | 252 | 253 | /* 254 | * Advances the bitmask iterator returning an address 255 | */ 256 | std::pair PhysAddr::advance_bitmask_iterator(size_t step) { 257 | 258 | auto addr=this->bitmsask_iterator_position; 259 | 260 | auto addr_max_bm = this->bitmsask_iterator_position; 261 | for(auto idx : this->idx_vec_invert){ 262 | addr_max_bm |= (1L << idx); 263 | } 264 | auto bitmask_range = boost::icl::discrete_interval::closed(addr, addr_max_bm); 265 | auto intersect = bitmask_range & this->dram.ram_ranges; 266 | if(intersect.empty()){ 267 | this->bitmsask_iterator_position = ((this->bitmsask_iterator_position | ~this->bitmask) + step) & this->bitmask; 268 | //PLOG_ERROR << "Address [" << std::hex << this->bitmsask_iterator_position << std::dec <<"] cannot be mapped"; 269 | return std::make_pair(addr,false); 270 | } 271 | 272 | auto addr_new = addr; 273 | while(!(valid_address(addr_new))){ 274 | addr_new = flip_unused_bits(addr); 275 | PLOG_VERBOSE << "Flipping physical address bits from " << std::hex << addr << std::dec << " to " << std::hex << addr_new << std::dec; 276 | } 277 | addr=addr_new; 278 | 279 | // increment bitmask 280 | this->bitmsask_iterator_position = ((this->bitmsask_iterator_position | ~this->bitmask) + step) & this->bitmask; 281 | // Return found address 282 | return std::make_pair(addr,true); 283 | } 284 | 285 | /* 286 | * Flips bit no in the bitmask iterator to get a usable address 287 | */ 288 | pointer PhysAddr::flip_unused_bits(pointer addr){ 289 | auto rand_mask = (this->rnd() & ~this->bitmask) & (((uint64_t) 1 << this->maxbits) - 1); 290 | return rand_mask ^ addr; 291 | } 292 | -------------------------------------------------------------------------------- /framework/src/virt-addr.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "../include/addr.hpp" 5 | #include "../include/utils.hpp" 6 | 7 | 8 | 9 | bool VirtAddr::valid_address(pointer addr){ 10 | return (addr < (1ull << this->maxbits)); 11 | } 12 | 13 | pointer VirtAddr::get_random_addr(){ 14 | return this->rnd(1ull << this->maxbits); 15 | } 16 | 17 | pointer VirtAddr::map_addr(pointer addr) 18 | { 19 | return (pointer) this->map_base+addr; 20 | } 21 | 22 | std::pair VirtAddr::advance_bitmask_iterator(size_t step) 23 | { 24 | auto save = this->bitmsask_iterator_position; 25 | this->bitmsask_iterator_position = 26 | ((this->bitmsask_iterator_position | ~this->bitmask) + step) & this->bitmask; 27 | if(!valid_address(save)){ 28 | return std::make_pair(save,false); 29 | } 30 | return std::make_pair(save,true); 31 | } 32 | 33 | /* 34 | * Flips bit no in the bitmask iterator to get a usavle address 35 | */ 36 | pointer VirtAddr::flip_unused_bits(pointer addr){ 37 | auto rand_mask = (this->rnd() & ~this->bitmask) & (((uint64_t) 1 << this->maxbits) - 1); 38 | return rand_mask ^ addr; 39 | } 40 | 41 | VirtAddr::VirtAddr(size_t maxbits) 42 | { 43 | this->maxbits = maxbits; 44 | 45 | PLOG_INFO << "Mapping " << maxbits << " of virtual addresses"; 46 | PLOG_INFO << "Mapping " << this->maxbits << " of virtual addresses"; 47 | this->map_base = (char*) mmap((void*) (1ull<<(this->maxbits+1)), (1ull<maxbits), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE | MAP_FIXED, -1, 0); 48 | if(this->map_base == MAP_FAILED){ 49 | PLOG_FATAL << "Cannot map " << this->maxbits << " bits of virtual memory, aborting"; 50 | exit(1); 51 | } 52 | PLOG_INFO << "Mapped virtual memory"; 53 | 54 | } 55 | 56 | VirtAddr::~VirtAddr(){ 57 | free(this->map_base); 58 | } 59 | -------------------------------------------------------------------------------- /minimizer/Readme.md: -------------------------------------------------------------------------------- 1 | # Logic Minimizer for Microarchitectual Hash Functions 2 | These are the python/sage scripts that allow to minimize microarchitectural hash functions. 3 | In addition to these scripts, a running installation of the [espresso logic minimizer](https://github.com/classabbyamp/espresso-logic.git) is needed. 4 | 5 | # Dependencies 6 | - [Espresso Logic Minimizer](https://github.com/classabbyamp/espresso-logic.git) 7 | - [PuLP](https://pypi.org/project/PuLP/) 8 | - [SageMath](https://doc.sagemath.org/html/en/installation/index.html) 9 | 10 | # Running 11 | After obtaining the `./measurements` folder using the tools in `../framework` the solver can be run using. 12 | ``` 13 | export ESPRESSO_EXECUTABLE=/path/to/espresso 14 | python3 read-and-minimize.py /path/to/measurements 15 | ``` 16 | In case the solver does not terminate it is also possible to reduce the number of bits and solve for a smaller function with the `limit-bits.py` script. 17 | -------------------------------------------------------------------------------- /minimizer/limit-bits.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | path = sys.argv[1] 4 | bit_num = int(sys.argv[2]) 5 | 6 | with open(path) as f: 7 | data = f.readlines() 8 | 9 | with open(path, "w") as f: 10 | for line in data: 11 | if line.startswith(".i"): 12 | i = int(line.split(" ")[1]) - bit_num 13 | f.write(f".i {i}\n") 14 | elif line.startswith("."): 15 | f.write(line) 16 | elif "0" * bit_num + " 1" in line: 17 | f.write(line.replace("0" * bit_num + " 1", " 1")) -------------------------------------------------------------------------------- /minimizer/minimize-groebner.sage: -------------------------------------------------------------------------------- 1 | from sage.all import * 2 | import itertools as it 3 | import sys 4 | 5 | if len(sys.argv) < 2: 6 | print("sage minimize-groebner.sage ") 7 | exit(1) 8 | 9 | NEGATE = 0 10 | 11 | with open(sys.argv[1]) as f: 12 | lines = f.readlines() 13 | 14 | n = [int(line.split(" ")[1]) for line in lines if line[:2] == ".i"][0] 15 | 16 | 17 | R = PolynomialRing(GF(2), "x", n) 18 | x = R.gens() 19 | Q = R 20 | 21 | relations = [] 22 | 23 | lines_preproc = [] 24 | 25 | # Filter out all bits that are always constant 26 | bit_status = [0] * n 27 | for line in lines: 28 | if line[0] == ".": 29 | continue 30 | 31 | 32 | var, res = line.split(" ") 33 | assert len(var) == n 34 | 35 | for (i, val) in enumerate(var): 36 | bit_var = { 37 | "0": 0b001, 38 | "1": 0b010, 39 | "-": 0b100, 40 | }[var[i]] 41 | bit_status[i] |= bit_var 42 | 43 | for i in range(n): 44 | if not (bit_status[i] & (bit_status[i] - 1)): 45 | print(f"Bit {i} is irrelevant!") 46 | 47 | 48 | for line in lines: 49 | 50 | if line[0] == ".": 51 | continue 52 | 53 | var, res = line.split(" ") 54 | 55 | 56 | term = 1 57 | 58 | for i in range(len(var)): 59 | if not (bit_status[i] & (bit_status[i] - 1)): 60 | continue 61 | if var[i] == "-": 62 | continue 63 | elif var[i] == '1': 64 | term *= x[i] + NEGATE 65 | elif var[i] == '0': 66 | term *= x[i] + (1-NEGATE) 67 | 68 | 69 | relations.append(term) 70 | 71 | idempotency_relations = [x[i]**2 + x[i] for i in range(n)] 72 | 73 | for i in range(n): 74 | relations.append(idempotency_relations[i]) 75 | 76 | 77 | def groebner_core(relations): 78 | global n 79 | global Q 80 | 81 | if len(relations) == 0: 82 | print("This formula is always false.") 83 | exit(1) 84 | if len(relations) == 2**n: 85 | print("This formula is a tautology") 86 | 87 | print("Sampling completed.") 88 | 89 | I = Q.ideal(relations) 90 | print("Starting Groebner.") 91 | 92 | B = I.groebner_basis() 93 | print("Done Groebner.") 94 | return B 95 | 96 | B = groebner_core(relations) 97 | 98 | def is_trivial_elem(b): 99 | return b in idempotency_relations 100 | 101 | B = [b for b in B if not is_trivial_elem(b)] 102 | 103 | print("==========================================") 104 | 105 | for b in B: 106 | fB = factor(b) 107 | 108 | outfun = "" 109 | terms = [] 110 | for (term,_) in fB: 111 | monomials = [] 112 | for monom in term.monomials(): 113 | variables = [] 114 | if monom == 1: 115 | variables.append("1") 116 | for var in monom.variables(): 117 | variables.append(f"x[{x.index(var)}]") 118 | monomials.append("" + "&".join(variables) + "") 119 | terms.append("(" + " ^ ".join(monomials) + ")") 120 | outfun = " & ".join(terms) 121 | print(outfun) 122 | 123 | 124 | print(f"===== {len(B)} element in Groebner Basis =====") 125 | -------------------------------------------------------------------------------- /minimizer/read-and-minimize.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | 4 | path = sys.argv[1] 5 | 6 | with open(f"{path}/bits.csv") as f: 7 | dat = f.readlines() 8 | 9 | num_bits = len(dat) 10 | 11 | for bit in range(num_bits): 12 | print("=====================") 13 | print(f"Starting bit {bit}") 14 | print("=====================") 15 | 16 | os.system(f"/usr/bin/python3 read-csv.py {path} {bit}") 17 | os.system(f"/usr/bin/python3 test.py {path}/bit_{bit}.espresso.in") -------------------------------------------------------------------------------- /minimizer/read-csv.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | if len(sys.argv) < 3: 4 | print("./read-csv.py [negate]") 5 | exit(1) 6 | 7 | path = sys.argv[1] 8 | bit_num = int(sys.argv[2]) 9 | negate = 0 if len(sys.argv) == 4 and sys.argv[3] == "negate" else 1 10 | 11 | with open(f"{path}/bit_{bit_num}.csv") as f: 12 | data = f.readlines() 13 | 14 | with open(f"{path}/bits.csv") as f: 15 | bits_data = f.readlines() 16 | 17 | relevant_bits = [int(x.strip()) for x in bits_data[bit_num].split(",") if x.strip() != ''] 18 | irrelevant_bits = set(range(46)) - set(relevant_bits) 19 | 20 | n = len(relevant_bits) 21 | 22 | outfile = f"{path}/bit_{bit_num}.espresso.in" 23 | 24 | seen = set() 25 | 26 | with open(outfile, "w") as f: 27 | 28 | f.write(f".i {n}\n") 29 | f.write(f".o 1\n") 30 | 31 | for line in data[1:]: 32 | x,y = line.split(",") 33 | 34 | x = int(x) 35 | y = int(y) 36 | 37 | bin_repr = "" 38 | 39 | for bits in relevant_bits: 40 | bin_repr += str((x >> (bits)) & 1) 41 | 42 | 43 | bit_val = int(bin_repr, 2) 44 | 45 | seen.add(bit_val) 46 | 47 | if y == negate: 48 | f.write(f"{bin_repr} {1}\n") 49 | else: 50 | pass 51 | 52 | f.write(".e\n") 53 | 54 | 55 | print(f"Output written to {outfile}/") 56 | print(f"espresso {outfile} > {path}/bit_{bit_num}.espresso.sol") 57 | print(f"sage minimize-groebner.sage {path}/bit_{bit_num}.espresso.sol") 58 | 59 | assert len(seen) == 2**n, f"{len(seen)=} {n=}" 60 | -------------------------------------------------------------------------------- /minimizer/setcover.py: -------------------------------------------------------------------------------- 1 | from pulp import * 2 | 3 | def set_cover(subsets, weights=None): 4 | if weights is None: 5 | weights = [1] * len(subsets) 6 | 7 | label = dict() 8 | new_subsets = [] 9 | for subset in subsets: 10 | new_subset = set() 11 | for element in subset: 12 | if element not in label: 13 | label[element] = len(label) 14 | new_subset.add(label[element]) 15 | new_subsets.append(new_subset) 16 | subsets = new_subsets 17 | 18 | n = len(subsets) 19 | m = len(label) 20 | 21 | problem = LpProblem("set-cover", LpMinimize) 22 | 23 | x = [LpVariable(f"x{i}", 0, 1, LpInteger) for i in range(n)] 24 | 25 | problem += lpSum(weights[i] * x[i] for i in range(n)) 26 | 27 | for i in range(m): 28 | problem += lpSum(x[j] for j in range(n) if i in subsets[j]) >= 1 29 | 30 | solver = GLPK_CMD(msg=False) 31 | problem.solve(solver) 32 | 33 | solution = [x[i].value() > 0.5 for i in range(n)] 34 | 35 | return solution 36 | 37 | if __name__ == "__main__": 38 | subsets = [ 39 | [1, 2, 4, 8, 9], 40 | [1, 3, 4], 41 | [2, 4, 7, 8], 42 | [2, 7, 9] 43 | ] 44 | weights = [1, 1, 2, 1] 45 | print(set_cover(subsets, weights)) -------------------------------------------------------------------------------- /minimizer/test.py: -------------------------------------------------------------------------------- 1 | from sage.all import * 2 | 3 | import itertools as it 4 | import sys 5 | import subprocess 6 | import setcover 7 | import time 8 | import os 9 | 10 | 11 | 12 | ESPRESSO_EXECUTABLE = os.environ.get("ESPRESSO_EXECUTABLE") # Path to the espresso executable 13 | 14 | _TIME_TOTAL = -time.time() 15 | _TIME_IN_ESPRESSO = 0 16 | _TIME_IN_GROEBNER = 0 17 | _TIME_IN_SETCOVER = 0 18 | _TOTAL_GROEBNER_CALLS = 0 19 | 20 | def simplify_not_not(node): 21 | if not isinstance(node, UnaryNode): 22 | return node 23 | if not isinstance(node.child, UnaryNode): 24 | return node 25 | if node.symbol == "~" and node.child.symbol == "~": 26 | return node.child.child 27 | else: 28 | return node 29 | 30 | def simplify_binary_one_child(node): 31 | if not isinstance(node, BinaryNode): 32 | return node 33 | if len(node.children) == 1: 34 | return node.children[0] 35 | else: 36 | return node 37 | 38 | def simplify_binary_same_child(node): 39 | if not isinstance(node, BinaryNode): 40 | return node 41 | children = [] 42 | for child in node.children: 43 | if isinstance(child, BinaryNode) and child.symbol == node.symbol: 44 | children.extend(child.children) 45 | else: 46 | children.append(child) 47 | return BinaryNode(node.symbol, children) 48 | 49 | def simplify_all(node): 50 | node = simplify_not_not(node) 51 | node = simplify_binary_one_child(node) 52 | node = simplify_binary_same_child(node) 53 | return node 54 | 55 | def modify(node, func): 56 | if isinstance(node, BinaryNode): 57 | node.children = [modify(child, func) for child in node.children] 58 | if isinstance(node, UnaryNode): 59 | node.child = modify(node.child, func) 60 | return func(node) 61 | 62 | class BinaryNode: 63 | 64 | def __init__(self, symbol, children): 65 | self.symbol = symbol 66 | self.children = children 67 | def __repr__(self): 68 | return f"BinaryNode(\"{self.symbol}\", {repr(self.children)})" 69 | 70 | def __str__(self): 71 | str_children = [str(child) for child in self.children] 72 | if len(str_children) == 1: 73 | return str_children[0] 74 | else: 75 | return "(" + (" " + self.symbol + " ").join(str_children) + ")" 76 | 77 | def copy(self): 78 | return BinaryNode(self.symbol, [child.copy() for child in self.children]) 79 | 80 | def size(self): 81 | return 1 + sum(child.size() for child in self.children) 82 | 83 | def find(self, symbol): 84 | if self.symbol == symbol: 85 | yield self 86 | else: 87 | for child in self.children: 88 | yield from child.find(symbol) 89 | 90 | def leafs(self): 91 | for child in self.children: 92 | yield from child.leafs() 93 | 94 | def traverse(self, depth=0): 95 | global _TIME_IN_SETCOVER 96 | print("-" * depth, self.symbol) 97 | if self.symbol != "^": 98 | for child in self.children: 99 | child.traverse(depth + 1) 100 | if self.symbol in ["|", "&"]: 101 | subsets = [] 102 | weights = [] 103 | for child in self.children: 104 | weights.append(child.size()) 105 | formula = eval("lambda x: " + str(child) + " & 1") 106 | if self.symbol == "|": 107 | subset = [value for value in it.product([0, 1], repeat=n) if formula(value)] 108 | else: 109 | subset = [value for value in it.product([0, 1], repeat=n) if not formula(value)] 110 | subsets.append(subset) 111 | time_start = time.time() 112 | cover = setcover.set_cover(subsets, weights) 113 | _TIME_IN_SETCOVER += time.time() - time_start 114 | self.children = [child for i, child in enumerate(self.children) if cover[i]] 115 | 116 | 117 | class UnaryNode: 118 | 119 | def __init__(self, symbol, child): 120 | self.symbol = symbol 121 | self.child = child 122 | 123 | def __repr__(self): 124 | return f"UnaryNode(\"{self.symbol}\", {repr(self.child)})" 125 | 126 | def __str__(self): 127 | return self.symbol + str(self.child) 128 | 129 | def copy(self): 130 | return UnaryNode(self.symbol, self.child.copy()) 131 | 132 | def size(self): 133 | return 1 + self.child.size() 134 | 135 | def find(self, symbol): 136 | if self.symbol == symbol: 137 | yield self 138 | else: 139 | yield from self.child.find(symbol) 140 | 141 | def leafs(self): 142 | yield from self.child.leafs() 143 | 144 | def traverse(self, depth=0): 145 | print("-" * depth, self.symbol) 146 | self.child.traverse(depth + 1) 147 | 148 | class LeafNode: 149 | 150 | def __init__(self, value): 151 | self.value = value 152 | 153 | def __repr__(self): 154 | return f"LeafNode({repr(self.value)})" 155 | 156 | def __str__(self): 157 | return str(self.value) 158 | 159 | def copy(self): 160 | return LeafNode(self.value) 161 | 162 | def size(self): 163 | return 1 164 | 165 | def leafs(self): 166 | yield self 167 | 168 | def traverse(self, depth=0): 169 | print("-" * depth, self.value) 170 | 171 | def find(self, symbol): 172 | yield from [] 173 | 174 | 175 | def And(children): 176 | return BinaryNode("&", children) 177 | 178 | def Or(children): 179 | return BinaryNode("|", children) 180 | 181 | def Xor(children): 182 | return BinaryNode("^", children) 183 | 184 | def Not(child): 185 | return UnaryNode("~", child) 186 | 187 | def Value(value): 188 | return LeafNode(value) 189 | 190 | def parse_polynomial(polynomial): 191 | negate = False 192 | monomials = [] 193 | for monomial in polynomial.monomials(): 194 | if monomial == 1: 195 | negate = True 196 | continue 197 | variables = [Value(var) for var, _ in factor(monomial)] 198 | monomial = And(variables) 199 | monomials.append(monomial) 200 | polynomial = Xor(monomials) 201 | if negate: 202 | polynomial = Not(polynomial) 203 | return polynomial 204 | 205 | 206 | def sample_polynomial(p): 207 | var = p.variables() 208 | n = len(var) 209 | values = [] 210 | for i in range(1 << n): 211 | bitstring = [(i >> j) & 1 for j in range(n)] 212 | assignment = dict(zip(var, bitstring)) 213 | value = p.subs(assignment) 214 | if value == 1: 215 | values.append(bitstring) 216 | return var, values 217 | 218 | def read_bits_csv(path, bit_num): 219 | with open(f"{path}/bit_{bit_num}.csv") as f: 220 | data = f.readlines() 221 | 222 | with open(f"{path}/bits.csv") as f: 223 | bits_data = f.readlines() 224 | 225 | relevant_bits = [int(x.strip()) for x in bits_data[bit_num].split(",") if x.strip() != ''] 226 | n = len(relevant_bits) 227 | 228 | values = [] 229 | for line in data: 230 | x, y = line.split(",") 231 | x = int(x) 232 | y = int(y) 233 | 234 | bin_repr = [] 235 | for bits in relevant_bits: 236 | bin_repr.append((x >> bits) & 1) 237 | 238 | values.append((tuple(bin_repr), y)) 239 | 240 | return relevant_bits, values 241 | 242 | def read_espresso_in(path): 243 | with open(f"{path}") as f: 244 | data = f.readlines() 245 | 246 | values = [] 247 | seen_inputs = set() 248 | n = -1 249 | for line in data: 250 | line = line.strip() 251 | if line[0] == ".": 252 | continue 253 | 254 | x, y = line.split(" ") 255 | y = int(y) 256 | 257 | assert n == -1 or n == len(x) 258 | n = len(x) 259 | 260 | bin_repr = [] 261 | for bit in x: 262 | assert bit == "1" or bit == "0", "found wildcard, this does not work." 263 | bin_repr.append(int(bit)) 264 | 265 | values.append((tuple(bin_repr), y)) 266 | seen_inputs.add(tuple(bin_repr)) 267 | 268 | for bits in it.product([0, 1], repeat=n): 269 | if bits not in seen_inputs: 270 | values.append((bits, 0)) 271 | 272 | 273 | return n, values 274 | 275 | def read_blackbox(): 276 | n = 6 277 | blackbox = lambda x: x[1] & ((x[0] ^ x[5]) & (x[2]) | (x[3] & (x[4] ^ x[5]))) 278 | values = [] 279 | for bits in it.product([0, 1], repeat=n): 280 | values.append((bits, blackbox(bits))) 281 | return (n, values) 282 | 283 | 284 | def compute_groebner_basis(bits, values): 285 | global _TIME_IN_ESPRESSO, _TIME_IN_GROEBNER, _TOTAL_GROEBNER_CALLS 286 | 287 | _TOTAL_GROEBNER_CALLS += 1 288 | 289 | n = len(bits) 290 | print(f"Starting Espresso ({n} bits, {len(values)} truth-values).") 291 | espresso = subprocess.Popen(ESPRESSO_EXECUTABLE, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 292 | write = lambda line: espresso.stdin.write(line.encode()) 293 | write(f".i {n}\n") 294 | write(f".o 1\n") 295 | for bitstring in values: 296 | bin_repr = "".join(map(str, bitstring)) 297 | write(f"{bin_repr} {1}\n") 298 | write(".e\n") 299 | 300 | 301 | time_start = time.time() 302 | out, err = espresso.communicate() 303 | time_end = time.time() 304 | 305 | _TIME_IN_ESPRESSO += (time_end - time_start) 306 | 307 | if err: 308 | print("Error:", err.decode()) 309 | sys.exit(1) 310 | 311 | relations = [] 312 | 313 | for line in out.decode().split("\n"): 314 | if not line or line.startswith("."): 315 | continue 316 | var, res = line.split(" ") 317 | term = 1 318 | for i in range(len(var)): 319 | if var[i] == "-": 320 | continue 321 | elif var[i] == '1': 322 | term *= bits[i] 323 | elif var[i] == '0': 324 | term *= bits[i] + 1 325 | relations.append(term) 326 | 327 | idempotency_relations = [bits[i]**2 + bits[i] for i in range(n)] 328 | relations.extend(idempotency_relations) 329 | 330 | 331 | I = R.ideal(relations) 332 | 333 | print(f"Starting Groebner ({len(relations)} relations).") 334 | time_start = time.time() 335 | 336 | B = I.groebner_basis() 337 | B = [b for b in B if b not in idempotency_relations] 338 | time_end = time.time() 339 | _TIME_IN_GROEBNER += (time_end - time_start) 340 | 341 | return B 342 | 343 | seen = set() 344 | 345 | def minimize_rec(x, bitstrings, negate=False, depth=0, size=10**10): 346 | basis = compute_groebner_basis(x, bitstrings) 347 | newsize = sum( len(list(str(b))) for b in basis ) 348 | if newsize > size: 349 | return None 350 | result = [] 351 | for element in basis: 352 | factors = [] 353 | for fac, _ in factor(element): 354 | if fac.degree() <= 1 or len(list(fac)) <= 1 or fac in seen or depth > 5: 355 | 356 | if negate: 357 | factors.append(Not(parse_polynomial(fac))) 358 | else: 359 | factors.append(parse_polynomial(fac)) 360 | else: 361 | new_bits, new_values = sample_polynomial(fac + 1) 362 | min_fac = minimize_rec(new_bits, new_values, not negate, depth + 1, newsize) 363 | seen.add(fac) 364 | 365 | if min_fac is None: 366 | if negate: 367 | factors.append(Not(parse_polynomial(fac))) 368 | else: 369 | factors.append(parse_polynomial(fac)) 370 | else: 371 | factors.append(min_fac) 372 | if negate: 373 | result.append(Or(factors)) 374 | else: 375 | result.append(And(factors)) 376 | if negate: 377 | return And(result) 378 | else: 379 | return Or(result) 380 | 381 | import sys 382 | 383 | if len(sys.argv) < 2: 384 | print("python test.py [negate]") 385 | exit(1) 386 | 387 | path = sys.argv[1] 388 | negate = sys.argv[2] == "negate" if len(sys.argv) > 2 else False 389 | 390 | 391 | n, data = read_espresso_in(path) 392 | 393 | 394 | check_value = 1 if not negate else 0 395 | values = [ value for value, y in data if y == check_value ] 396 | 397 | R = PolynomialRing(GF(2), "x", n) 398 | x = R.gens() 399 | 400 | tree = minimize_rec(x, values, negate=negate) 401 | tree = modify(tree, simplify_all) 402 | tree = modify(tree, simplify_all) 403 | 404 | for leaf in tree.leafs(): 405 | leaf.value = x.index(leaf.value) 406 | 407 | for leaf in tree.leafs(): 408 | leaf.value = f"x[{leaf.value}]" 409 | 410 | _TIME_TREE_SIMPLIFICATION = -time.time() 411 | tree.traverse() 412 | _TIME_TREE_SIMPLIFICATION += time.time() 413 | 414 | 415 | extract_xors = False 416 | 417 | print("Generating Code.") 418 | if not extract_xors: 419 | code = "formula = lambda x: " + str(tree) + " & 1\n" 420 | tree_size = tree.size() 421 | else: 422 | tmp_tree = tree.copy() 423 | tree_size = 0 424 | code = "def formula(x):\n" 425 | remap = {} 426 | for node in tmp_tree.find("^"): 427 | str_node = str(node) 428 | if str_node in remap: 429 | idx = remap[str_node] 430 | else: 431 | idx = remap[str_node] = len(remap) 432 | code += f" y{idx} = " + str_node + "\n" 433 | tree_size += node.size() 434 | node.children = [LeafNode(f"y{idx}")] 435 | code += f" return " + str(tmp_tree) + " & 1\n" 436 | tmp_tree = modify(tmp_tree, simplify_all) 437 | tree_size += tmp_tree.size() 438 | 439 | print() 440 | print(code) 441 | print() 442 | 443 | exec(code) 444 | 445 | print("Testing.") 446 | for value, y in data: 447 | if formula(value) != y: 448 | print(f"Error: formula({value}) != {y}") 449 | sys.exit(1) 450 | 451 | import os 452 | input_path = sys.argv[1] 453 | dir = os.path.dirname(input_path) 454 | file = os.path.basename(input_path) 455 | file_ending = ".neg.sol" if negate else ".sol" 456 | file = file.split(".")[0] + file_ending 457 | 458 | _TIME_TOTAL += time.time() 459 | 460 | with open(f"{dir}/{file}", "w") as f: 461 | f.write(code) 462 | 463 | print("\nTiming: ") 464 | print(f"{_TIME_IN_ESPRESSO=:.3f}") 465 | print(f"{_TIME_IN_GROEBNER=:.3f}") 466 | print(f"{_TIME_IN_SETCOVER=:.3f}") 467 | print(f"{_TIME_TREE_SIMPLIFICATION=:.3f}") 468 | print(f"{_TIME_TOTAL=:.3f}") 469 | 470 | f.write("Timing: \n") 471 | f.write(f"{_TIME_IN_ESPRESSO=:.3f}\n") 472 | f.write(f"{_TIME_IN_GROEBNER=:.3f}\n") 473 | f.write(f"{_TIME_IN_SETCOVER=:.3f}\n") 474 | f.write(f"{_TIME_TREE_SIMPLIFICATION=:.3f}\n") 475 | f.write(f"{_TIME_TOTAL=:.3f}\n") 476 | 477 | f.write("\nStats: \n") 478 | f.write(f"{_TOTAL_GROEBNER_CALLS=}\n") 479 | f.write(f"{n=}\n") 480 | f.write(f"{tree_size=}\n") 481 | 482 | 483 | print("Done.") 484 | -------------------------------------------------------------------------------- /minimizer/tree.py: -------------------------------------------------------------------------------- 1 | class BinaryOperation: 2 | 3 | def __init__(self, symbol, func, children): 4 | self.symbol = symbol 5 | self.func = func 6 | self.children = children 7 | 8 | def __str__(self): 9 | str_children = [str(child) for child in self.children] 10 | if len(str_children) == 1: 11 | return str_children[0] 12 | else: 13 | return "(" + (" " + self.symbol + " ").join(str_children) + ")" 14 | 15 | def eval(self, assignment): 16 | return self.func(child.eval(assignment) for child in self.children) 17 | 18 | 19 | class And(BinaryOperation): 20 | 21 | def __init__(self, *children): 22 | BinaryOperation.__init__(self, "&", all, children) 23 | 24 | 25 | class Or(BinaryOperation): 26 | 27 | def __init__(self, *children): 28 | BinaryOperation.__init__(self, "|", any, children) 29 | 30 | 31 | class Xor(BinaryOperation): 32 | 33 | def __init__(self, *children): 34 | func = lambda x: sum(x) % 2 == 1 35 | BinaryOperation.__init__(self, "^", func, children) 36 | 37 | 38 | class Not: 39 | 40 | def __init__(self, child): 41 | self.child = child 42 | 43 | def __str__(self): 44 | return "~" + str(self.child) 45 | 46 | def eval(self, assignment): 47 | return not self.child.eval(assignment) 48 | 49 | 50 | class Var: 51 | 52 | def __init__(self, number): 53 | self.number = number 54 | 55 | def __str__(self): 56 | return f"x{self.number}" 57 | 58 | def eval(self, assignment): 59 | return assignment[self.number] 60 | 61 | 62 | f = Or(And(Not(Var(1)), Not(Var(0)), Var(3)), And(Not(Var(1)), Not(Var(0)), Var(2)), Not(Var(4))) 63 | 64 | print(f) 65 | print(f.eval([0, 1, 0, 1, 1])) 66 | --------------------------------------------------------------------------------