From 89da3e8e9467205eaacfcdca1097508195043eab Mon Sep 17 00:00:00 2001 From: Peter Goodman Date: Sat, 28 Oct 2017 19:13:59 -0400 Subject: [PATCH] Made it so that tests can be run on their own, independent of a symbolic executor. This will open up libFuzzer support, and concrete execution of solved-for test case inputs. Removed all stuff related to sections. Made tests get registered via initializers. Working on exposing the API functions to be hooked by Manticore via a special system call with addres 0x41414141. Split the Angr version out into the mctest-angr binary, and going to try to make the mctest binary use Manticore. --- bin/mctest/__main__.py | 188 ++++--------------------- bin/mctest/angr.py | 221 ++++++++++++++++++++++++++++++ bin/setup.py.in | 5 +- examples/ArithmeticProperties.cpp | 7 +- examples/OutOfBoundsInt.c | 6 +- src/include/mctest/Compiler.h | 96 +++++++++++++ src/include/mctest/McTest.h | 94 +++++++------ src/include/mctest/McTest.hpp | 62 ++++----- src/lib/McTest.c | 104 +++++++++++--- 9 files changed, 525 insertions(+), 258 deletions(-) create mode 100644 bin/mctest/angr.py create mode 100644 src/include/mctest/Compiler.h diff --git a/bin/mctest/__main__.py b/bin/mctest/__main__.py index 60be841..3f65bb8 100644 --- a/bin/mctest/__main__.py +++ b/bin/mctest/__main__.py @@ -13,177 +13,47 @@ # See the License for the specific language governing permissions and # limitations under the License. -import angr + import logging +import manticore import sys + L = logging.getLogger("mctest") L.setLevel(logging.INFO) -def hook_function(project, name, cls): - """Hook the function `name` with the SimProcedure `cls`.""" - project.hook(project.kb.labels.lookup(name), - cls(project=project)) + +class EntryPointPlugin(manticore.core.plugin.Plugin): + """Interpose on system calls. When we come across McTest's special system + call that is invoked at the beginnning of McTest_Run, then we stop execution + there and take over.""" + def on_syscall_callback(self, state, index): + if 0x41414141 == index: + print 'here!!!!!' + state_id = self._executor._workspace.save_state(state) + self._executor.put(state_id) + raise manticore.TerminateState("Canceled", testcase=False) -def read_c_string(state, ea): - """Read a concrete NUL-terminated string from `ea`.""" - assert isinstance(ea, (int, long)) - chars = [] - i = 0 - while True: - char = state.mem[ea + i].char.resolved - char = state.solver.eval(char, cast_to=str) - if not ord(char[0]): - break - chars.append(char) - i += 1 - return "".join(chars) +class McTest(manticore.Manticore): + def __init__(self, argv): + assert isinstance(argv, (list, tuple)) + super(McTest, self).__init__(argv[0], argv=argv) + self._unregister_default_plugins() + self.register_plugin(EntryPointPlugin()) - -def read_uintptr_t(state, ea): - """Read a uint64_t value from memory.""" - return state.solver.eval(state.mem[ea].uintptr_t.resolved, cast_to=int) - - -def read_uint32_t(state, ea): - """Read a uint64_t value from memory.""" - return state.solver.eval(state.mem[ea].uint32_t.resolved, cast_to=int) - - -def find_test_cases(project, state): - """Find the test case descriptors.""" - obj = project.loader.main_object - tests = [] - addr_size_bytes = state.arch.bits // 8 - for sec in obj.sections: - if sec.name != ".mctest_funcs": - continue - - for ea in xrange(sec.vaddr, sec.vaddr + sec.memsize, 32): - test_func_ea = read_uintptr_t(state, ea + 0 * addr_size_bytes) - test_name_ea = read_uintptr_t(state, ea + 1 * addr_size_bytes) - file_name_ea = read_uintptr_t(state, ea + 2 * addr_size_bytes) - file_line_num = read_uint32_t(state, ea + 3 * addr_size_bytes) - - if not test_func_ea or \ - not test_name_ea or \ - not file_name_ea or \ - not file_line_num: # `__LINE__` in C always starts at `1` ;-) - continue - - test_name = read_c_string(state, test_name_ea) - file_name = read_c_string(state, file_name_ea) - L.info("Test case {} at {:x} is at {}:{}".format( - test_name, test_func_ea, file_name, file_line_num)) - tests.append((test_func_ea, test_name, file_name, file_line_num)) - - return tests - - -def make_symbolic_input(project, state): - """Fill in the input data array with symbolic data.""" - obj = project.loader.main_object - for sec in obj.sections: - if sec.name == ".mctest_data": - data = state.se.Unconstrained('MCTEST_INPUT', sec.memsize * 8) - state.memory.store(sec.vaddr, data) - return data - - -def hook_predicate_int_func(project, state, name, num_bits): - """Hook a McTest function that checks whether or not its integer argument - is symbolic.""" - class Hook(angr.SimProcedure): - def run(self, arg): - return int(self.state.se.symbolic(arg)) - hook_function(project, "McTest_IsSymbolic{}".format(name), Hook) - - -class Assume(angr.SimProcedure): - """Implements _McTest_CanAssume, which tries to inject a constraint.""" - def run(self, arg): - constraint = arg != 0 - self.state.solver.add(constraint) - if not self.state.solver.satisfiable(): - L.error("Failed to assert assumption {}".format(constraint)) - self.exit(2) - - -class Pass(angr.SimProcedure): - """Implements McTest_Pass, which notifies us of a passing test.""" - def run(self): - L.info("Passed test case") - self.exit(0) - - -class Fail(angr.SimProcedure): - """Implements McTest_Fail, which notifies us of a passing test.""" - def run(self): - L.error("Failed test case") - self.exit(1) + def _unregister_default_plugins(self): + """Unregister the default plugins.""" + for plugin in tuple(self.plugins): + self.unregister_plugin(plugin) def main(): - """Run McTest.""" - if 2 > len(sys.argv): - return 1 - - project = angr.Project( - sys.argv[1], - use_sim_procedures=True, - translation_cache=True, - support_selfmodifying_code=False, - auto_load_libs=False) - - entry_state = project.factory.entry_state() - addr_size_bits = entry_state.arch.bits - - # Find the test cases that we want to run. - tests = find_test_cases(project, entry_state) - - # Concretely execute up until `main`. - concrete_manager = angr.SimulationManager( - project=project, - active_states=[entry_state]) - ea_of_main = project.kb.labels.lookup('main') - concrete_manager.explore(find=ea_of_main) - main_state = concrete_manager.found[0] - - # Introduce symbolic input that the tested code will use. - symbolic_input = make_symbolic_input(project, main_state) - - # Hook predicate functions that should return 1 or 0 depending on whether - # or not their argument is symbolic. - hook_predicate_int_func(project, main_state, 'UInt', 32) - - hook_function(project, '_McTest_Assume', Assume) - hook_function(project, 'McTest_Pass', Pass) - hook_function(project, 'McTest_Fail', Fail) - - # For each test, create a simulation manager whose initial state calls into - # the test case function, and returns to `McTest_DoneTestCase`. - test_managers = [] - for entry_ea, test_name, file_name, line_num in tests: - test_state = project.factory.call_state( - entry_ea, - base_state=main_state) - - # NOTE(pag): Enabling Veritesting seems to miss some cases where the - # tests fail. - test_manager = angr.SimulationManager( - project=project, - active_states=[test_state]) - - L.info("Running test case {}".format(test_name)) - test_manager.run() - - for state in test_manager.deadended: - last_event = state.history.events[-1] - if 'terminate' == last_event.type: - code = last_event.objects['exit_code']._model_concrete.value - - return 0 + print 'here' + McTest.verbosity(1) + m = McTest(sys.argv[1:]) + print 'running...' + m.run() if "__main__" == __name__: exit(main()) diff --git a/bin/mctest/angr.py b/bin/mctest/angr.py new file mode 100644 index 0000000..510b1da --- /dev/null +++ b/bin/mctest/angr.py @@ -0,0 +1,221 @@ +#!/usr/bin/env python +# Copyright (c) 2017 Trail of Bits, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +import angr +import collections +import logging +import sys + +L = logging.getLogger("mctest") +L.setLevel(logging.INFO) + +def hook_function(project, ea, cls): + """Hook the function `name` with the SimProcedure `cls`.""" + project.hook(ea, cls(project=project)) + + +def read_c_string(state, ea): + """Read a concrete NUL-terminated string from `ea`.""" + assert isinstance(ea, (int, long)) + chars = [] + i = 0 + while True: + char = state.mem[ea + i].char.resolved + char = state.solver.eval(char, cast_to=str) + if not ord(char[0]): + break + chars.append(char) + i += 1 + return "".join(chars) + + +def read_uintptr_t(state, ea): + """Read a uint64_t value from memory.""" + next_ea = ea + (state.arch.bits // 8) + val = state.solver.eval(state.mem[ea].uintptr_t.resolved, cast_to=int) + return val, next_ea + + +def read_uint32_t(state, ea): + """Read a uint64_t value from memory.""" + next_ea = ea + (state.arch.bits // 8) + val = state.solver.eval(state.mem[ea].uint32_t.resolved, cast_to=int) + return val, next_ea + + +TestInfo = collections.namedtuple( + 'TestInfo', 'ea name file_name line_number') + + +def read_test_info(state, ea): + """Read in a `McTest_TestInfo` info structure from memory.""" + prev_test_ea, ea = read_uintptr_t(state, ea) + test_func_ea, ea = read_uintptr_t(state, ea) + test_name_ea, ea = read_uintptr_t(state, ea) + file_name_ea, ea = read_uintptr_t(state, ea) + file_line_num, _ = read_uint32_t(state, ea) + + if not test_func_ea or \ + not test_name_ea or \ + not file_name_ea or \ + not file_line_num: # `__LINE__` in C always starts at `1` ;-) + return None, prev_test_ea + + test_name = read_c_string(state, test_name_ea) + file_name = read_c_string(state, file_name_ea) + info = TestInfo(test_func_ea, test_name, file_name, file_line_num) + return info, prev_test_ea + + +def read_api_table(state, ea): + """Reads in the API table.""" + apis = {} + while True: + api_name_ea, ea = read_uintptr_t(state, ea) + api_ea, ea = read_uintptr_t(state, ea) + if not api_name_ea or not api_ea: + break + api_name = read_c_string(state, api_name_ea) + apis[api_name] = api_ea + return apis + + +def find_test_cases(state, info_ea): + """Find the test case descriptors.""" + tests = [] + while info_ea: + test, info_ea = read_test_info(state, info_ea) + if test: + tests.append(test) + tests.sort(key=lambda t: (t.file_name, t.line_number)) + return tests + + +def make_symbolic_input(state, input_begin_ea, input_end_ea): + """Fill in the input data array with symbolic data.""" + input_size = input_end_ea - input_begin_ea + data = state.se.Unconstrained('MCTEST_INPUT', input_size * 8) + state.memory.store(input_begin_ea, data) + return data + + +class IsSymbolicUInt(angr.SimProcedure): + """Implements McTest_IsSymblicUInt, which returns 1 if its input argument + has more then one solutions, and zero otherwise.""" + def run(self, arg): + solutions = self.state.solver.eval_upto(arg, 2) + if not solutions: + return 0 + elif 1 == len(solutions): + if self.state.se.symbolic(arg): + self.state.solver.add(arg == solutions[0]) + return 0 + else: + return 1 + + +class Assume(angr.SimProcedure): + """Implements _McTest_CanAssume, which tries to inject a constraint.""" + def run(self, arg): + constraint = arg != 0 + self.state.solver.add(constraint) + if not self.state.solver.satisfiable(): + L.error("Failed to assert assumption {}".format(constraint)) + self.exit(2) + + +class Pass(angr.SimProcedure): + """Implements McTest_Pass, which notifies us of a passing test.""" + def run(self): + L.info("Passed test case") + self.exit(0) + + +class Fail(angr.SimProcedure): + """Implements McTest_Fail, which notifies us of a passing test.""" + def run(self): + L.error("Failed test case") + self.exit(1) + + +def main(): + """Run McTest.""" + if 2 > len(sys.argv): + return 1 + + project = angr.Project( + sys.argv[1], + use_sim_procedures=True, + translation_cache=True, + support_selfmodifying_code=False, + auto_load_libs=False) + + entry_state = project.factory.entry_state() + addr_size_bits = entry_state.arch.bits + + # Concretely execute up until `McTest_InjectAngr`. + concrete_manager = angr.SimulationManager( + project=project, + active_states=[entry_state]) + run_ea = project.kb.labels.lookup('McTest_Run') + concrete_manager.explore(find=run_ea) + run_state = concrete_manager.found[0] + + # Read the API table, which will tell us about the location of various + # symbols. Technically we can look these up with the `labels.lookup` API, + # but we have the API table for Manticore-compatibility, so we may as well + # use it. + ea_of_api_table = project.kb.labels.lookup('McTest_API') + apis = read_api_table(run_state, ea_of_api_table) + + # Introduce symbolic input that the tested code will use. + symbolic_input = make_symbolic_input( + run_state, apis['InputBegin'], apis['InputEnd']) + + # Hook various functions. + hook_function(project, apis['IsSymbolicUInt'], IsSymbolicUInt) + hook_function(project, apis['Assume'], Assume) + hook_function(project, apis['Pass'], Pass) + hook_function(project, apis['Fail'], Fail) + + # Find the test cases that we want to run. + tests = find_test_cases(run_state, apis['LastTestInfo']) + + # For each test, create a simulation manager whose initial state calls into + # the test case function. + test_managers = [] + for test in tests: + test_state = project.factory.call_state( + test.ea, + base_state=run_state) + + test_manager = angr.SimulationManager( + project=project, + active_states=[test_state]) + + L.info("Running test case {} from {}:{}".format( + test.name, test.file_name, test.line_number)) + test_manager.run() + + for state in test_manager.deadended: + last_event = state.history.events[-1] + if 'terminate' == last_event.type: + code = last_event.objects['exit_code']._model_concrete.value + + return 0 + +if "__main__" == __name__: + exit(main()) diff --git a/bin/setup.py.in b/bin/setup.py.in index ef38d8b..f49d54b 100644 --- a/bin/setup.py.in +++ b/bin/setup.py.in @@ -30,9 +30,10 @@ setuptools.setup( author_email="peter@trailofbits.com", license="Apache-2.0", keywords="tdd testing symbolic execution", - install_requires=['angr'], + install_requires=['angr', 'manticore'], entry_points={ 'console_scripts': [ - 'mctest = mctest.__main__:main' + 'mctest = mctest.__main__:main', + 'mctest-angr = mctest.angr:main' ] }) diff --git a/examples/ArithmeticProperties.cpp b/examples/ArithmeticProperties.cpp index d7157e1..0b53e56 100644 --- a/examples/ArithmeticProperties.cpp +++ b/examples/ArithmeticProperties.cpp @@ -18,8 +18,7 @@ using namespace mctest; -__attribute__((noinline)) -int add(int x, int y) { +MCTEST_NOINLINE int add(int x, int y) { return x + y; } @@ -28,3 +27,7 @@ McTest_EntryPoint(AdditionIsCommutative) { McTest_Assert(add(x, y) == add(y, x)); }); } + +int main(int argc, char *argv[]) { + return McTest_Run(); +} diff --git a/examples/OutOfBoundsInt.c b/examples/OutOfBoundsInt.c index 99d603d..a34cafe 100644 --- a/examples/OutOfBoundsInt.c +++ b/examples/OutOfBoundsInt.c @@ -25,5 +25,9 @@ McTest_EntryPoint(YIsAlwaysPositive) { McTest_EntryPoint(YIsAlwaysPositive_CanFail) { int x = McTest_IntInRange(-10, 10); int y = x * x * x; - McTest_Assert(y >= 0); /* This will fail */ + McTest_Assert(y >= 0); /* This can fail */ +} + +int main(int argc, char *argv[]) { + return McTest_Run(); } diff --git a/src/include/mctest/Compiler.h b/src/include/mctest/Compiler.h new file mode 100644 index 0000000..5e420b1 --- /dev/null +++ b/src/include/mctest/Compiler.h @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2017 Trail of Bits, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef INCLUDE_MCTEST_COMPILER_H_ +#define INCLUDE_MCTEST_COMPILER_H_ + +#include +#include + +/* Stringify a macro parameter. */ +#define MCTEST_TO_STR(a) _MCTEST_TO_STR(a) +#define _MCTEST_TO_STR(a) __MCTEST_TO_STR(a) +#define __MCTEST_TO_STR(a) #a + +/* Mark a function as not returning. */ +#if defined(_MSC_VER) +# define MCTEST_NORETURN __declspec(noreturn) +#else +# define MCTEST_NORETURN __attribute__((noreturn)) +#endif + +/* Mark a function for inlining. */ +#if defined(_MSC_VER) +# define MCTEST_INLINE __forceinline +# define MCTEST_NOINLINE __declspec(noinline) +#else +# define MCTEST_INLINE inline __attribute__((always_inline)) +# define MCTEST_NOINLINE __attribute__((noinline)) +#endif + +/* Introduce a trap instruction to halt execution. */ +#if defined(_MSC_VER) +# include +# define McTest_Trap __debugbreak +#else +# define McTest_Trap __builtin_trap +#endif + +/* Wrap a block of code in `extern "C"` if we are compiling with a C++ + * compiler. */ +#ifdef __cplusplus +# define MCTEST_BEGIN_EXTERN_C extern "C" { +# define MCTEST_END_EXTERN_C } +#else +# define MCTEST_BEGIN_EXTERN_C +# define MCTEST_END_EXTERN_C +#endif + +/* Initializer/finalizer sample for MSVC and GCC/Clang. + * 2010-2016 Joe Lowe. Released into the public domain. + * + * See: https://stackoverflow.com/a/2390626/247591 */ +#ifdef __cplusplus +# define MCTEST_INITIALIZER(f) \ + static void f(void); \ + struct f ##_t_ { \ + f##_t_(void) { \ + f(); \ + } \ + }; \ + static f##_t_ f##_; \ + static void f(void) + +#elif defined(_MSC_VER) +# pragma section(".CRT$XCU",read) +# define MCTEST_INITIALIZER2_(f, p) \ + static void f(void); \ + __declspec(allocate(".CRT$XCU")) void (*f##_)(void) = f; \ + __pragma(comment(linker,"/include:" p #f "_")) \ + static void f(void) + +# ifdef _WIN64 +# define MCTEST_INITIALIZER(f) MCTEST_INITIALIZER2_(f,"") +# else +# define MCTEST_INITIALIZER(f) MCTEST_INITIALIZER2_(f,"_") +# endif +#else +# define MCTEST_INITIALIZER(f) \ + static void f(void) __attribute__((constructor)); \ + static void f(void) +#endif + +#endif /* INCLUDE_MCTEST_COMPILER_H_ */ diff --git a/src/include/mctest/McTest.h b/src/include/mctest/McTest.h index b96d1dc..f92f92f 100644 --- a/src/include/mctest/McTest.h +++ b/src/include/mctest/McTest.h @@ -22,14 +22,14 @@ #include #include +#include + #ifdef assert # undef assert #endif #define assert McTest_Assert -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ +MCTEST_BEGIN_EXTERN_C /* Return a symbolic value of a given type. */ extern int McTest_Bool(void); @@ -46,7 +46,7 @@ extern int8_t McTest_Char(void); /* Symbolize the data in the range `[begin, end)`. */ extern void McTest_SymbolizeData(void *begin, void *end); -inline static void *McTest_Malloc(size_t num_bytes) { +MCTEST_INLINE static void *McTest_Malloc(size_t num_bytes) { void *data = malloc(num_bytes); uintptr_t data_end = ((uintptr_t) data) + num_bytes; McTest_SymbolizeData(data, (void *) data_end); @@ -54,7 +54,8 @@ inline static void *McTest_Malloc(size_t num_bytes) { } #define MCTEST_MAKE_SYMBOLIC_ARRAY(Tname, tname) \ - inline static tname *McTest_Symbolic ## Tname ## Array(size_t num_elms) { \ + MCTEST_INLINE static \ + tname *McTest_Symbolic ## Tname ## Array(size_t num_elms) { \ tname *arr = (tname *) malloc(sizeof(tname) * num_elms); \ McTest_SymbolizeData(arr, &(arr[num_elms])); \ return arr; \ @@ -72,7 +73,7 @@ MCTEST_MAKE_SYMBOLIC_ARRAY(UChar, unsigned char) #undef MCTEST_MAKE_SYMBOLIC_ARRAY /* Return a symbolic C string. */ -inline static char *McTest_CStr(size_t len) { +MCTEST_INLINE static char *McTest_CStr(size_t len) { char *str = (char *) malloc(sizeof(char) * len); if (len) { McTest_SymbolizeData(str, &(str[len - 1])); @@ -87,14 +88,14 @@ extern void _McTest_Assume(int expr); #define McTest_Assume(x) _McTest_Assume(!!(x)) -__attribute__((noreturn)) +MCTEST_NORETURN extern void McTest_Fail(void); -__attribute__((noreturn)) +MCTEST_NORETURN extern void McTest_Pass(void); /* Asserts that `expr` must hold. */ -inline static void McTest_Assert(int expr) { +MCTEST_INLINE static void McTest_Assert(int expr) { if (!expr) { McTest_Fail(); } @@ -102,14 +103,13 @@ inline static void McTest_Assert(int expr) { /* Return a symbolic value in a the range `[low_inc, high_inc]`. */ #define MCTEST_MAKE_SYMBOLIC_RANGE(Tname, tname) \ - inline static tname McTest_ ## Tname ## InRange( \ + MCTEST_INLINE static tname McTest_ ## Tname ## InRange( \ tname low, tname high) { \ tname x = McTest_ ## Tname(); \ (void) McTest_Assume(low <= x && x <= high); \ return x; \ } - MCTEST_MAKE_SYMBOLIC_RANGE(Size, size_t) MCTEST_MAKE_SYMBOLIC_RANGE(Int64, int64_t) MCTEST_MAKE_SYMBOLIC_RANGE(UInt64, uint64_t) @@ -125,88 +125,96 @@ MCTEST_MAKE_SYMBOLIC_RANGE(UChar, unsigned char) /* Predicates to check whether or not a particular value is symbolic */ extern int McTest_IsSymbolicUInt(uint32_t x); -inline static int McTest_IsSymbolicInt(int x) { +/* The following predicates are implemented in terms of `McTest_IsSymbolicUInt`. + * This simplifies the portability of hooking this predicate interface across + * architectures, because basically all hooking mechanisms know how to get at + * the first integer argument. Passing in floating point values, or 64-bit + * integers on 32-bit architectures, can be more subtle. */ + +MCTEST_INLINE static int McTest_IsSymbolicInt(int x) { return McTest_IsSymbolicUInt((uint32_t) x); } -inline static int McTest_IsSymbolicUShort(uint16_t x) { +MCTEST_INLINE static int McTest_IsSymbolicUShort(uint16_t x) { return McTest_IsSymbolicUInt((uint32_t) x); } -inline static int McTest_IsSymbolicShort(int16_t x) { +MCTEST_INLINE static int McTest_IsSymbolicShort(int16_t x) { return McTest_IsSymbolicUInt((uint32_t) (uint16_t) x); } -inline static int McTest_IsSymbolicUChar(unsigned char x) { +MCTEST_INLINE static int McTest_IsSymbolicUChar(unsigned char x) { return McTest_IsSymbolicUInt((uint32_t) x); } -inline static int McTest_IsSymbolicChar(char x) { +MCTEST_INLINE static int McTest_IsSymbolicChar(char x) { return McTest_IsSymbolicUInt((uint32_t) (unsigned char) x); } -inline static int McTest_IsSymbolicUInt64(uint64_t x) { +MCTEST_INLINE static int McTest_IsSymbolicUInt64(uint64_t x) { return McTest_IsSymbolicUInt((uint32_t) x) || McTest_IsSymbolicUInt((uint32_t) (x >> 32U)); } -inline static int McTest_IsSymbolicInt64(int64_t x) { +MCTEST_INLINE static int McTest_IsSymbolicInt64(int64_t x) { return McTest_IsSymbolicUInt64((uint64_t) x); } -inline static int McTest_IsSymbolicBool(int x) { +MCTEST_INLINE static int McTest_IsSymbolicBool(int x) { return McTest_IsSymbolicInt(x); } -inline static int McTest_IsSymbolicFloat(float x) { +MCTEST_INLINE static int McTest_IsSymbolicFloat(float x) { return McTest_IsSymbolicUInt(*((uint32_t *) &x)); } -inline static int McTest_IsSymbolicDouble(double x) { +MCTEST_INLINE static int McTest_IsSymbolicDouble(double x) { return McTest_IsSymbolicUInt64(*((uint64_t *) &x)); } -#define _MCTEST_TO_STR(a) __MCTEST_TO_STR(a) -#define __MCTEST_TO_STR(a) #a - -#ifdef __cplusplus -# define MCTEST_BEGIN_EXTERN_C extern "C" { -# define MCTEST_END_EXTERN_C } -#else -# define MCTEST_BEGIN_EXTERN_C -# define MCTEST_END_EXTERN_C -#endif - +/* Used to define the entrypoint of a test case. */ #define McTest_EntryPoint(test_name) \ _McTest_EntryPoint(test_name, __FILE__, __LINE__) -struct __attribute__((packed)) McTest_TestInfo { +/* Contains information about a test case */ +struct McTest_TestInfo { + struct McTest_TestInfo *prev; void (*test_func)(void); const char *test_name; const char *file_name; unsigned line_number; - uint8_t padding[28 - (3 * sizeof(void *))]; }; +/* Pointer to the last registered `TestInfo` structure. */ +extern struct McTest_TestInfo *McTest_LastTestInfo; + +/* Defines the entrypoint of a test case. This creates a data structure that + * contains the information about the test, and then creates an initializer + * function that runs before `main` that registers the test entrypoint with + * McTest. */ #define _McTest_EntryPoint(test_name, file, line) \ static void McTest_Test_ ## test_name (void); \ static void McTest_Run_ ## test_name (void) { \ McTest_Test_ ## test_name(); \ McTest_Pass(); \ } \ - MCTEST_BEGIN_EXTERN_C \ - struct McTest_TestInfo McTest_Register_ ## test_name \ - __attribute__((section(".mctest_funcs"))) = { \ + static struct McTest_TestInfo McTest_Info_ ## test_name = { \ + NULL, \ McTest_Run_ ## test_name, \ - _MCTEST_TO_STR(test_name), \ + MCTEST_TO_STR(test_name), \ file, \ - line \ + line, \ }; \ - MCTEST_END_EXTERN_C \ + MCTEST_INITIALIZER(McTest_Register_ ## test_name) { \ + McTest_Info_ ## test_name.prev = McTest_LastTestInfo; \ + McTest_LastTestInfo = &(McTest_Info_ ## test_name); \ + } \ void McTest_Test_ ## test_name(void) -#ifdef __cplusplus -} /* extern C */ -#endif /* __cplusplus */ +/* Start McTest and run the tests. Returns the number of failed tests. */ +extern int McTest_Run(void); + +MCTEST_END_EXTERN_C + #endif /* INCLUDE_MCTEST_MCTEST_H_ */ diff --git a/src/include/mctest/McTest.hpp b/src/include/mctest/McTest.hpp index 37d7492..c566003 100644 --- a/src/include/mctest/McTest.hpp +++ b/src/include/mctest/McTest.hpp @@ -25,95 +25,95 @@ namespace mctest { -inline static void *Malloc(size_t num_bytes) { +MCTEST_INLINE static void *Malloc(size_t num_bytes) { return McTest_Malloc(num_bytes); } -inline static void SymbolizeData(void *begin, void *end) { +MCTEST_INLINE static void SymbolizeData(void *begin, void *end) { McTest_SymbolizeData(begin, end); } -inline static bool Bool(void) { +MCTEST_INLINE static bool Bool(void) { return static_cast(McTest_Bool()); } -inline static size_t Size(void) { +MCTEST_INLINE static size_t Size(void) { return McTest_Size(); } -inline static uint64_t UInt64(void) { +MCTEST_INLINE static uint64_t UInt64(void) { return McTest_UInt64(); } -inline static int64_t Int64(void) { +MCTEST_INLINE static int64_t Int64(void) { return McTest_Int64(); } -inline static uint32_t UInt(void) { +MCTEST_INLINE static uint32_t UInt(void) { return McTest_UInt(); } -inline static int32_t Int(void) { +MCTEST_INLINE static int32_t Int(void) { return McTest_Int(); } -inline static uint16_t UShort(void) { +MCTEST_INLINE static uint16_t UShort(void) { return McTest_UShort(); } -inline static int16_t Short(void) { +MCTEST_INLINE static int16_t Short(void) { return McTest_Short(); } -inline static unsigned char UChar(void) { +MCTEST_INLINE static unsigned char UChar(void) { return McTest_UChar(); } -inline static char Char(void) { +MCTEST_INLINE static char Char(void) { return McTest_Char(); } -inline static bool IsSymbolic(uint64_t x) { +MCTEST_INLINE static bool IsSymbolic(uint64_t x) { return McTest_IsSymbolicUInt64(x); } -inline static int IsSymbolic(int64_t x) { +MCTEST_INLINE static int IsSymbolic(int64_t x) { return McTest_IsSymbolicInt64(x); } -inline static bool IsSymbolic(uint32_t x) { +MCTEST_INLINE static bool IsSymbolic(uint32_t x) { return McTest_IsSymbolicUInt(x); } -inline static bool IsSymbolic(int32_t x) { +MCTEST_INLINE static bool IsSymbolic(int32_t x) { return McTest_IsSymbolicInt(x); } -inline static int IsSymbolic(uint16_t x) { +MCTEST_INLINE static int IsSymbolic(uint16_t x) { return McTest_IsSymbolicUShort(x); } -inline static bool IsSymbolic(int16_t x) { +MCTEST_INLINE static bool IsSymbolic(int16_t x) { return McTest_IsSymbolicShort(x); } -inline static bool IsSymbolic(unsigned char x) { +MCTEST_INLINE static bool IsSymbolic(unsigned char x) { return McTest_IsSymbolicUChar(x); } -inline static bool IsSymbolic(char x) { +MCTEST_INLINE static bool IsSymbolic(char x) { return McTest_IsSymbolicChar(x); } -inline static bool IsSymbolic(float x) { +MCTEST_INLINE static bool IsSymbolic(float x) { return McTest_IsSymbolicFloat(x); } -inline static bool IsSymbolic(double x) { +MCTEST_INLINE static bool IsSymbolic(double x) { return McTest_IsSymbolicDouble(x); } -inline static bool IsSymbolic(void *x) { +MCTEST_INLINE static bool IsSymbolic(void *x) { return IsSymbolic((uintptr_t) x); } @@ -121,15 +121,15 @@ template class Symbolic { public: template - inline Symbolic(Args&& ...args) + MCTEST_INLINE Symbolic(Args&& ...args) : value(std::forward(args)...) {} - inline Symbolic(void) { + MCTEST_INLINE Symbolic(void) { T *val_ptr = &value; McTest_SymbolizeData(val_ptr, &(val_ptr[1])); } - inline operator T (void) const { + MCTEST_INLINE operator T (void) const { return value; } @@ -139,17 +139,17 @@ class Symbolic { template class SymbolicLinearContainer { public: - inline explicit SymbolicLinearContainer(size_t len) + MCTEST_INLINE explicit SymbolicLinearContainer(size_t len) : value(len) { if (len) { McTest_SymbolizeData(&(value.begin()), &(value.end())); } } - inline SymbolicLinearContainer(void) + MCTEST_INLINE SymbolicLinearContainer(void) : SymbolicLinearContainer(McTest_SizeInRange(0, 32)) {} - inline operator T (void) const { + MCTEST_INLINE operator T (void) const { return value; } @@ -174,9 +174,9 @@ class Symbolic> : template <> \ class Symbolic { \ public: \ - inline Symbolic(void) \ + MCTEST_INLINE Symbolic(void) \ : value(McTest_ ## Tname()) {} \ - inline operator tname (void) const { \ + MCTEST_INLINE operator tname (void) const { \ return value; \ } \ tname value; \ diff --git a/src/lib/McTest.c b/src/lib/McTest.c index 3fe9024..e2ffc3e 100644 --- a/src/lib/McTest.c +++ b/src/lib/McTest.c @@ -17,24 +17,47 @@ #include #include +#include -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ +#if defined(unix) || defined(__unix) || defined(__unix__) +# define _GNU_SOURCE +# include /* For `syscall` */ +#endif -volatile uint8_t McTest_Input[8192] - __attribute__((section(".mctest_data"))); +MCTEST_BEGIN_EXTERN_C -uint32_t McTest_InputIndex = 0; +/* Pointer to the last registers McTest_TestInfo data structure */ +struct McTest_TestInfo *McTest_LastTestInfo = NULL; -__attribute__((noreturn)) +enum { + McTest_InputLength = 8192 +}; + +/* Byte buffer that will contain symbolic data that is used to supply requests + * for symbolic values (e.g. `int`s). */ +static volatile uint8_t McTest_Input[McTest_InputLength]; + +/* Index into the `McTest_Input` array that tracks how many input bytes have + * been consumed. */ +static uint32_t McTest_InputIndex = 0; + +/* Jump buffer for returning to `McTest_Main`. */ +static jmp_buf McTest_ReturnToMain; + +static int McTest_TestPassed = 0; + +/* Mark this test as failing. */ +MCTEST_NORETURN extern void McTest_Fail(void) { - exit(EXIT_FAILURE); + McTest_TestPassed = 0; + longjmp(McTest_ReturnToMain, 1); } -__attribute__((noreturn)) +/* Mark this test as passing. */ +MCTEST_NORETURN extern void McTest_Pass(void) { - exit(EXIT_SUCCESS); + McTest_TestPassed = 1; + longjmp(McTest_ReturnToMain, 0); } void McTest_SymbolizeData(void *begin, void *end) { @@ -99,15 +122,56 @@ int McTest_IsSymbolicUInt(uint32_t x) { return 0; } -void McTest_DoneTestCase(void) { - exit(EXIT_SUCCESS); +/* A McTest-specific symbol that is needed for hooking. */ +struct McTest_IndexEntry { + const char * const name; + void * const address; +}; + +/* An index of symbols that the symbolic executors will hook or + * need access to. */ +const struct McTest_IndexEntry McTest_API[] = { + {"Pass", (void *) McTest_Pass}, + {"Fail", (void *) McTest_Fail}, + {"Assume", (void *) _McTest_Assume}, + {"IsSymbolicUInt", (void *) McTest_IsSymbolicUInt}, + {"InputBegin", (void *) &(McTest_Input[0])}, + {"InputEnd", (void *) &(McTest_Input[McTest_InputLength])}, + {"InputIndex", (void *) &McTest_InputIndex}, + {"LastTestInfo", (void *) &McTest_LastTestInfo}, + {NULL, NULL}, +}; + +int McTest_Run(void) { + + /* Manticore entrypoint. Manticore doesn't (yet?) support symbol lookups, so + * we instead interpose on this fake system call, and discover the API table + * via the first argument to the system call. */ +#if defined(_MSC_VER) +# warning "TODO: Implement Windows interception support for Manticore." +#else + syscall(0x41414141, &McTest_API); +#endif + + int num_failed_tests = 0; + for (struct McTest_TestInfo *info = McTest_LastTestInfo; + info != NULL; + info = info->prev) { + + McTest_TestPassed = 0; + if (!setjmp(McTest_ReturnToMain)) { + printf("Running %s from %s:%u\n", info->test_name, info->file_name, + info->line_number); + info->test_func(); + + } else if (McTest_TestPassed) { + printf(" %s Passed\n", info->test_name); + } else { + printf(" %s Failed\n", info->test_name); + num_failed_tests += 1; + } + } + return num_failed_tests; } -/* McTest implements the `main` function so that test code can focus on tests */ -int main(void) { - return EXIT_SUCCESS; -} - -#ifdef __cplusplus -} /* extern C */ -#endif /* __cplusplus */ +MCTEST_END_EXTERN_C