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.
This commit is contained in:
@@ -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())
|
||||
|
||||
221
bin/mctest/angr.py
Normal file
221
bin/mctest/angr.py
Normal file
@@ -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())
|
||||
@@ -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'
|
||||
]
|
||||
})
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
96
src/include/mctest/Compiler.h
Normal file
96
src/include/mctest/Compiler.h
Normal file
@@ -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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* 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 <intrin.h>
|
||||
# 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_ */
|
||||
@@ -22,14 +22,14 @@
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <mctest/Compiler.h>
|
||||
|
||||
#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_ */
|
||||
|
||||
@@ -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<bool>(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 <typename T>
|
||||
class Symbolic {
|
||||
public:
|
||||
template <typename... Args>
|
||||
inline Symbolic(Args&& ...args)
|
||||
MCTEST_INLINE Symbolic(Args&& ...args)
|
||||
: value(std::forward<Args...>(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 <typename T>
|
||||
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<std::vector<T>> :
|
||||
template <> \
|
||||
class Symbolic<tname> { \
|
||||
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; \
|
||||
|
||||
104
src/lib/McTest.c
104
src/lib/McTest.c
@@ -17,24 +17,47 @@
|
||||
#include <mctest/McTest.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <setjmp.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
#if defined(unix) || defined(__unix) || defined(__unix__)
|
||||
# define _GNU_SOURCE
|
||||
# include <unistd.h> /* 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
|
||||
|
||||
Reference in New Issue
Block a user