diff --git a/INSTALL.md b/INSTALL.md index 26e650d9..f55c416e 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -8,6 +8,9 @@ and (optional) binutils and libiberty to build kcov. Note that elfutils is found in multiple variants, and at least in RH/Centos/Fedora you'll need elfutils-devel and *not* elfutils-libelf-devel. +On Linux, if [dyninst](http://www.dyninst.org) is present, kcov will also be +able to do full-system instrumentation. + Ubuntu ------ Install binutils-dev libcurl4-openssl-dev zlib1g-dev libdw-dev libiberty-dev @@ -15,7 +18,7 @@ Install binutils-dev libcurl4-openssl-dev zlib1g-dev libdw-dev libiberty-dev Fedora / Centos / RHEL ---------------------- -Install elfutils-libelf-devel libcurl-devel binutils-devel elfutils-devel +Install elfutils-libelf-devel libcurl-devel binutils-devel elfutils-devel dyninst-devel Mac OS X -------- diff --git a/README.md b/README.md index 9d26fff2..25523a87 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,11 @@ after_success: the -s /path/to/outdir part can be skipped if kcov produces output in the current directory. +Full-system instrumentation +--------------------------- +Using [dyninst](http://www.dyninst.org), Kcov can instrument all binaries with very low overhead for embedded systems. +Refer to the [full system instrumentation](doc/full-system-instrumentation.md) documentation for details. + More information ---------------- kcov is written by Simon Kagstrom and more diff --git a/cmake/FindDyninst.cmake b/cmake/FindDyninst.cmake new file mode 100644 index 00000000..9dbb9a74 --- /dev/null +++ b/cmake/FindDyninst.cmake @@ -0,0 +1,58 @@ +# - Try to find dyninst +# Once done this will define +# +# DYNINST_FOUND - system has dyninst +# DYNINST_INCLUDE_DIRS - the dyninst include directory +# DYNINST_LIBRARIES - Link these to use dyninst +# DYNINST_DEFINITIONS - Compiler switches required for using dyninst +# + +if (DYNINST_LIBRARIES AND DYNINST_INCLUDE_DIRS) + set (Dyninst_FIND_QUIETLY TRUE) +endif (DYNINST_LIBRARIES AND DYNINST_INCLUDE_DIRS) + +find_path (DYNINST_INCLUDE_DIR + NAMES + BPatch.h + PATHS + /usr/include/dyninst + /usr/local/include/dyninst + /opt/local/include/dyninst + /sw/include/dyninst + /sw/include/dyninst + /usr/local/include + ENV CPATH) # PATH and INCLUDE will also work +if (DYNINST_INCLUDE_DIR) + set (DYNINST_INCLUDE_DIRS ${DYNINST_INCLUDE_DIR}) +endif (DYNINST_INCLUDE_DIR) + +find_library (DYNINST_LIBRARIES + NAMES + dyninstAPI + PATHS + /usr/lib + /usr/lib64 + /usr/local/lib + /opt/local/lib + /usr/local/lib64 + /opt/local/lib64 + /sw/lib + /usr/lib/dyninst + /usr/lib64/dyninst + /usr/local/lib/dyninst + /opt/local/lib/dyninst + /usr/local/lib64/dyninst + /opt/local/lib64/dyninst + ENV LIBRARY_PATH # PATH and LIB will also work + ENV LD_LIBRARY_PATH) + +include (FindPackageHandleStandardArgs) + + +# handle the QUIETLY and REQUIRED arguments and set DYNINST_FOUND to TRUE +# if all listed variables are TRUE +FIND_PACKAGE_HANDLE_STANDARD_ARGS(DYNINST DEFAULT_MSG + DYNINST_LIBRARIES + DYNINST_INCLUDE_DIR) + +mark_as_advanced(DYNINST_INCLUDE_DIR DYNINST_LIBRARIES) diff --git a/doc/full-system-instrumentation.md b/doc/full-system-instrumentation.md new file mode 100644 index 00000000..2f6715ec --- /dev/null +++ b/doc/full-system-instrumentation.md @@ -0,0 +1,22 @@ +## *Full system instrumentation with kcov* +Using [dyninst](http://www.dyninst.org), Kcov can instrument all binaries with very low overhead for embedded systems. + +Instrumenting binaries for a target system +------------------------------------------ +If your binaries (with debug symbols) are in e.g., sysroot, you can instrument binaries using + +``` +kcov-system --system-record /tmp/out-sysroot sysroot +``` + +After this finishes, build your filesystem (squashfs etc) using the instrumented binaries +and install and run on your target. + + +Creating a report from the collected data +----------------------------------------- +Copy `/tmp/kcov-data` from your target system and run + +``` +kcov-system --system-report /tmp/kcov /tmp/kcov-data +``` diff --git a/doc/kcov.1 b/doc/kcov.1 index 181866c0..c71dadfd 100644 --- a/doc/kcov.1 +++ b/doc/kcov.1 @@ -103,6 +103,12 @@ executed as /bin/bash). Does not work well on some systems, so the default is no .TP \fB\-\-replace\-src\-path\fP=\fIP1\fP:\fIP2\fP Replace source file path P1 with P2, if found. +.TP +\fB\-\-system\-record +Perform full-system instrumentation on a sysroot, outputting patched binaries which collect coverage data +.TP +\fB\-\-system\-report +Produce coverage output for a full-system coverage run. .RE .SH EXAMPLES .PP diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f97ab480..7011ff3d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,6 +5,7 @@ set (CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/../../cmake) find_package (Threads) find_package (Bfd) +find_package (Dyninst) include(TargetArch) target_architecture(CMAKE_TARGET_ARCHITECTURES) @@ -51,6 +52,9 @@ set (DISASSEMBLER_SRCS parsers/dummy-disassembler.cc ) +set (DYNINST_SRCS +) + set (HAS_LIBBFD "0") if("${CMAKE_TARGET_ARCHITECTURES}" STREQUAL "i386" OR "${CMAKE_TARGET_ARCHITECTURES}" STREQUAL "x86_64") @@ -67,6 +71,13 @@ if("${CMAKE_TARGET_ARCHITECTURES}" STREQUAL "i386" OR "${CMAKE_TARGET_ARCHITECTU endif() endif() + +if(DYNINST_FOUND) + set (DYNINST_SRCS + engines/dyninst-engine.cc + ) +endif() + set (coveralls_SRCS writers/coveralls-writer.cc) if ("${KCOV_STATIC_BUILD}" STREQUAL "1") @@ -84,6 +95,7 @@ set (ELF_SRCS set (MACHO_SRCS ) set (SOLIB_generated ) +set (DYNINST_SOLIB_generated ) # Linux-specific sources if (${CMAKE_SYSTEM_NAME} MATCHES Linux) @@ -131,6 +143,7 @@ set (${KCOV}_SRCS configuration.cc engine-factory.cc engines/bash-engine.cc + engines/dyninst-file-format.cc engines/gcov-engine.cc engines/python-engine.cc filter.cc @@ -141,7 +154,7 @@ set (${KCOV}_SRCS ${DISASSEMBLER_SRCS} parser-manager.cc reporter.cc - source-file-cache.cc + source-file-cache.cc utils.cc writers/cobertura-writer.cc writers/json-writer.cc @@ -170,6 +183,51 @@ set (${KCOV}_SRCS include/phdr_data.h ) +# Should be some better way of doing this... +set (KCOV_DYNINST_SRCS + capabilities.cc + collector.cc + configuration.cc + dummy-solib-handler.cc + engine-factory.cc + engines/bash-engine.cc + engines/dyninst-engine.cc + engines/dyninst-file-format.cc + engines/gcov-engine.cc + engines/python-engine.cc + filter.cc + gcov.cc + main.cc + merge-file-parser.cc + output-handler.cc + parser-manager.cc + reporter.cc + source-file-cache.cc + utils.cc + writers/cobertura-writer.cc + writers/json-writer.cc + ${coveralls_SRCS} + writers/html-writer.cc + writers/sonarqube-xml-writer.cc + writers/writer-base.cc + include/capabilities.hh + include/gcov.hh + include/reporter.hh + include/collector.hh + include/generated-data-base.hh + include/solib-handler.hh + include/configuration.hh + include/lineid.hh + include/swap-endian.hh + include/engine.hh + include/manager.hh + include/utils.hh + include/file-parser.hh + include/output-handler.hh + include/writer.hh + include/filter.hh + include/phdr_data.h +) set (KCOV_LIBRARY_PREFIX "/tmp") @@ -182,6 +240,7 @@ include_directories( ${LIBDW_INCLUDE_DIRS} ${LIBZ_INCLUDE_DIRS} ${LIBCURL_INCLUDE_DIRS} + ${DYNINST_INCLUDE_DIRS} ) link_directories (/home/ska/local/lib) @@ -197,6 +256,12 @@ add_custom_command( DEPENDS ${SOLIB} ${CMAKE_CURRENT_SOURCE_DIR}/bin-to-c-source.py ) +add_custom_command( + OUTPUT dyninst-binary-library.cc + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/bin-to-c-source.py libkcov-binary-dyninst.so __dyninst_binary_library > dyninst-binary-library.cc + DEPENDS kcov-binary-dyninst ${CMAKE_CURRENT_SOURCE_DIR}/bin-to-c-source.py + ) + add_custom_command( OUTPUT bash-redirector-library.cc COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/bin-to-c-source.py $ bash_redirector_library > bash-redirector-library.cc @@ -278,5 +343,25 @@ target_link_libraries(${KCOV} ${LLDB_LIBRARY} ) +if(DYNINST_FOUND) + add_library (kcov-binary-dyninst SHARED engines/dyninst-binary-lib.cc engines/dyninst-file-format.cc utils.cc) + set_target_properties(kcov-binary-dyninst PROPERTIES SUFFIX ".so") + + add_executable (kcov-system ${KCOV_DYNINST_SRCS} ${SOLIB_generated} bash-redirector-library.cc dyninst-binary-library.cc python-helper.cc bash-helper.cc html-data-files.cc version.c) + + target_link_libraries(kcov-system + ${DYNINST_LIBRARIES} + ${LIBELF_LIBRARIES} + stdc++ + ${LIBCURL_LIBRARIES} + m + ${DISASSEMBLER_LIBRARIES} + ${LIBZ_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} + dl + ${LLDB_LIBRARY} +) +endif() + install (TARGETS ${PROJECT_NAME} ${INSTALL_TARGETS_PATH}) diff --git a/src/configuration.cc b/src/configuration.cc index 1ced2d0b..7a016652 100644 --- a/src/configuration.cc +++ b/src/configuration.cc @@ -94,7 +94,11 @@ class Configuration : public IConfiguration " # including /src/frodo\n" " kcov --collect-only /tmp/kcov ./frodo # Collect coverage, don't report\n" " kcov --report-only /tmp/kcov ./frodo # Report coverage collected above\n" - " kcov --merge /tmp/out /tmp/dir1 /tmp/dir2 # Merge the dir1/dir2 reports\n" + " kcov --merge /tmp/out /tmp/dir1 /tmp/dir2 # Merge the dir1/dir2 reports\n" + " kcov --system-record /tmp/out-dir sysroot # Perform full-system in-\n" + " strumentation for sysroot\n" + " kcov --system-report /tmp/data-dir # Report all data from a full-\n" + " system run.\n" "", keyAsInt("low-limit"), keyAsInt("high-limit"), uncommonOptions().c_str()); @@ -138,6 +142,8 @@ class Configuration : public IConfiguration {"python-parser", required_argument, 0, 'P'}, {"bash-parser", required_argument, 0, 'B'}, {"bash-method", required_argument, 0, '4'}, + {"system-record", no_argument, 0, '8'}, + {"system-report", no_argument, 0, '9'}, {"verify", no_argument, 0, 'V'}, {"version", no_argument, 0, 'v'}, {"uncommon-options", no_argument, 0, 'U'}, @@ -371,6 +377,12 @@ class Configuration : public IConfiguration case 'm': setKey("running-mode", IConfiguration::MODE_MERGE_ONLY); break; + case '8': // Full system record + setKey("running-mode", IConfiguration::MODE_SYSTEM_RECORD); + break; + case '9': // Full system report + setKey("running-mode", IConfiguration::MODE_SYSTEM_REPORT); + break; case 'l': { StrVecMap_t vec = getCommaSeparatedList(std::string(optarg)); @@ -537,6 +549,8 @@ class Configuration : public IConfiguration setKey("merged-name", "[merged]"); setKey("css-file", ""); setKey("lldb-use-raw-breakpoint-writes", 0); + setKey("system-mode-write-file", ""); + setKey("system-mode-read-results-file", 0); } @@ -585,6 +599,10 @@ class Configuration : public IConfiguration setKey(key, stoul(std::string(value))); else if (key == "high-limit") setKey(key, stoul(std::string(value))); + else if (key == "system-mode-write-file") + setKey(key, std::string(value)); + else if (key == "system-mode-read-results-file") + setKey(key, stoul(std::string(value))); else if (key == "bash-use-basic-parser") setKey(key, stoul(std::string(value))); else if (key == "lldb-use-raw-breakpoint-writes") @@ -622,6 +640,8 @@ class Configuration : public IConfiguration "\n" " --gcov use gcov parser instead of DWARF debugging info\n" " --clang use Clang Sanitizer-coverage parser\n" + " --system-record perform full-system instrumentation\n" + " --system-report report full-system coverage data\n" " --skip-solibs don't parse shared libraries (default: parse solibs)\n" " --exit-first-process exit when the first process exits, i.e., honor the\n" " behavior of daemons (default: wait until last)\n" diff --git a/src/engines/dyninst-binary-lib.cc b/src/engines/dyninst-binary-lib.cc new file mode 100644 index 00000000..7eaf0f73 --- /dev/null +++ b/src/engines/dyninst-binary-lib.cc @@ -0,0 +1,147 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "dyninst-file-format.hh" + +struct Instance +{ + uint32_t id; + time_t last_time; + + bool initialized; + kcov_dyninst::dyninst_memory *data; +}; + +static Instance g_instance; +static uint32_t early_hits[4096]; +static uint32_t early_hits_index; + + +static void write_report(unsigned int idx) +{ + size_t size; + struct kcov_dyninst::dyninst_file *dst = kcov_dyninst::memoryToFile(*g_instance.data, size); + + if (!dst) + { + fprintf(stderr, "kcov-binary-lib: Can't marshal data\n"); + return; + } + + (void)mkdir("/tmp/kcov-data", 0755); + + std::string out = fmt("/tmp/kcov-data/%08lx", (long)g_instance.id); + std::string tmp = fmt("%s.%u", out.c_str(), idx); + FILE *fp = fopen(tmp.c_str(), "w"); + + if (fp) + { + fwrite(dst, 1, size, fp); + fclose(fp); + rename(tmp.c_str(), out.c_str()); + } + else + { + fprintf(stderr, "kcov-binary-lib: Can't write outfile\n"); + } + + free(dst); +} + +static void write_at_exit(void) +{ + write_report(0); +} + +static kcov_dyninst::dyninst_memory *read_report(size_t expectedSize) +{ + std::string in = fmt("/tmp/kcov-data/%08lx", (long)g_instance.id); + + if (!file_exists(in)) + { + return NULL; + } + + size_t sz; + struct kcov_dyninst::dyninst_file *src = (struct kcov_dyninst::dyninst_file *)read_file(&sz, "%s", in.c_str()); + if (!src) + { + printf("Can't read\n"); + return NULL; + } + // Wrong size + if (sz != expectedSize) + { + printf("Wrong size??? %zu vs %zu\n", sz, expectedSize); + free(src); + return NULL; + } + + kcov_dyninst::dyninst_memory *out = kcov_dyninst::fileToMemory(*src); + free(src); + + return out; +} + +extern "C" void kcov_dyninst_binary_init(uint32_t id, size_t vectorSize, const char *filename, const char *kcovOptions) +{ + g_instance.id = id; + g_instance.last_time = time(NULL); + + size_t size = (vectorSize + 31) / 32; + + g_instance.data = read_report(strlen(filename) + strlen(kcovOptions) + 2 + size * sizeof(uint32_t) + + sizeof(struct kcov_dyninst::dyninst_file)); + if (!g_instance.data) + { + g_instance.data = new kcov_dyninst::dyninst_memory(filename, kcovOptions, size); + } + + atexit(write_at_exit); + g_instance.initialized = true; +} + +extern "C" void kcov_dyninst_binary_report_address(uint32_t bitIdx) +{ + // Handle hits which happen before we're initialized + if (!g_instance.initialized) + { + uint32_t dst = __sync_fetch_and_add(&early_hits_index, 1); + + if (dst >= sizeof(early_hits) / sizeof(early_hits[0])) + { + fprintf(stderr, "kcov: Library not initialized yet and hit table full, missing point %u\n", bitIdx); + return; + } + early_hits[dst] = bitIdx; + return; + } + + if (early_hits_index != 0) + { + uint32_t to_loop = early_hits_index; + + early_hits_index = 0; // Avoid inifite recursion + for (uint32_t i = 0; i < to_loop; i++) + { + kcov_dyninst_binary_report_address(early_hits[i]); + } + } + g_instance.data->reportIndex(bitIdx); + + // Write out the report + time_t now = time(NULL); + if (now - g_instance.last_time >= 2) + { + write_report(bitIdx); + g_instance.last_time = now; + } +} diff --git a/src/engines/dyninst-engine.cc b/src/engines/dyninst-engine.cc new file mode 100644 index 00000000..9e05bf70 --- /dev/null +++ b/src/engines/dyninst-engine.cc @@ -0,0 +1,503 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + + +#include +#include +#include +#include + +#include "dyninst-file-format.hh" + +using namespace kcov; + +extern GeneratedData __dyninst_binary_library_data; + +class DyninstEngine : public IEngine, public IFileParser +{ +public: + DyninstEngine() : + m_mode(mode_unset), + m_resultsData(NULL), + m_listener(NULL), + m_bpatch(NULL), + m_addressSpace(NULL), + m_binaryEdit(NULL), + m_image(NULL), + m_reporterInitFunction(NULL), + m_addressReporterFunction(NULL), + m_breakpointIdx(0), + m_checksum(0) + { + IParserManager::getInstance().registerParser(*this); + } + + virtual ~DyninstEngine() + { + } + + // From IEngine + virtual int registerBreakpoint(unsigned long addr) + { + if (m_mode == mode_write_file) + { + m_pendingAddresses.push_back(std::pair(addr, m_breakpointIdx)); + } + + // Only needed for read mode, but always set it + m_indexToAddress.push_back(addr); + m_breakpointIdx++; + + return m_breakpointIdx - 1; + } + + // From IFileParser + virtual bool addFile(const std::string &filename, struct phdr_data_entry *phdr_data) + { + IConfiguration &conf = IConfiguration::getInstance(); + std::string dst = conf.keyAsString("system-mode-write-file"); + + if (dst != "") + { + m_outFile = dst; + m_mode = mode_write_file; + m_filename = filename; + } + else + { + std::string src = conf.keyAsString("system-mode-read-results-file"); + + m_resultsData = kcov_dyninst::diskToMemory(src); + + if (!m_resultsData) + { + return false; + } + + m_filename = m_resultsData->filename; + m_mode = mode_read_file; + } + + for (FileListenerList_t::const_iterator it = m_fileListeners.begin(); + it != m_fileListeners.end(); + ++it) + (*it)->onFile(File(m_filename, IFileParser::FLG_NONE)); + + + // Actual parsing is done in start + return true; + } + + virtual bool setMainFileRelocation(unsigned long relocation) + { + return true; + } + + virtual void registerLineListener(ILineListener &listener) + { + m_lineListeners.push_back(&listener); + } + + virtual void registerFileListener(IFileListener &listener) + { + m_fileListeners.push_back(&listener); + } + + virtual bool parse() + { + BPatch_Vector *modules = m_image->getModules(); + + if (modules) + handleModules(*modules); + + // Handled when the program is launched + return true; + } + + virtual uint64_t getChecksum() + { + return m_checksum; + } + + virtual enum IFileParser::PossibleHits maxPossibleHits() + { + return IFileParser::HITS_LIMITED; + } + + virtual void setupParser(IFilter *filter) + { + m_bpatch = new BPatch(); + m_bpatch->setLivenessAnalysis(false); + m_bpatch->setDelayedParsing(true); + m_bpatch->setTypeChecking(false); + } + + std::string getParserType() + { + return "dyninst"; + } + + unsigned int matchParser(const std::string &filename, uint8_t *data, size_t dataSize) + { + IConfiguration &conf = IConfiguration::getInstance(); + + if (conf.keyAsString("system-mode-write-file") != "" || + conf.keyAsString("system-mode-read-results-file") != "") + return match_perfect; + + return match_none; + } + + bool start(IEventListener &listener, const std::string &executable) + { + m_listener = &listener; + + size_t sz; + uint8_t *p = (uint8_t *)read_file(&sz, "%s", m_filename.c_str()); + m_checksum = hash_block(p, sz); + free(p); + + m_binaryEdit = m_bpatch->openBinary(m_filename.c_str()); + + if (!m_binaryEdit) { + error("Can't open binary for rewriting\n"); + + return false; + } + + + m_addressSpace = m_binaryEdit; + m_image = m_addressSpace->getImage(); + + if (!m_image) { + // FIXME! + + return false; + } + + return true; + } + + + bool continueExecution() + { + // After parsing, write the binary + if (m_mode == mode_write_file) + { + handleFileWriter(); + + m_addressSpace->beginInsertionSet(); + for (unsigned i = 0; i < m_pendingAddresses.size(); i++) + { + std::pair cur = m_pendingAddresses[i]; + registerPendingBreakpoint(cur.first, cur.second); + } + m_addressSpace->finalizeInsertionSet(true); + + BPatch_function *main = lookupFunction("main"); + if (!main) + return false; + + + std::vector< BPatch_snippet * > args; + BPatch_snippet id = BPatch_constExpr(m_checksum); + BPatch_snippet size = BPatch_constExpr(m_breakpointIdx); + BPatch_snippet filename = BPatch_constExpr(m_filename.c_str()); + BPatch_snippet options = BPatch_constExpr(getKcovOptionsString().c_str()); + + args.push_back(&id); + args.push_back(&size); + args.push_back(&filename); + args.push_back(&options); + BPatch_funcCallExpr call(*m_reporterInitFunction, args); + + BPatch_Vector mainEntries; + main->getEntryPoints(mainEntries); + + BPatchSnippetHandle *handle = m_addressSpace->insertSnippet(call, mainEntries, + BPatch_firstSnippet); + if (!handle) { + error("Can't insert init func\n"); + return false; + } + + m_binaryEdit->writeFile(m_outFile.c_str()); + } + else + { + handleFileReader(); + } + + // Nothing to do after this + reportEvent(ev_exit, 0, 0); + + return false; + } + + + void kill(int signal) + { + } + +private: + std::string getKcovOptionsString() + { + IConfiguration &conf = IConfiguration::getInstance(); + + return fmt("%s %s %s %s %s %s ", + keyListAsString(conf.keyAsList("include-pattern")).c_str(), + keyListAsString(conf.keyAsList("exclude-pattern")).c_str(), + keyListAsString(conf.keyAsList("include-path")).c_str(), + keyListAsString(conf.keyAsList("exclude-path")).c_str(), + conf.keyAsString("exclude-line").c_str(), + conf.keyAsString("exclude-region").c_str()); + } + + std::string keyListAsString(const std::vector &list) + { + std::string out; + + if (list.empty()) + { + return ""; + } + + for (std::vector::const_iterator it = list.begin(); + it != list.end(); + ++it) + { + out = out + *it + ","; + } + out = out.substr(0, out.size() - 1); // Remove last , + + return out; + } + + void registerPendingBreakpoint(uint64_t addr, uint32_t idx) + { + std::vector pts; + + if (!m_image->findPoints(addr, pts)) + return; + + std::vector< BPatch_snippet * > args; + BPatch_snippet id = BPatch_constExpr(idx); + + args.push_back(&id); + + BPatch_funcCallExpr call(*m_addressReporterFunction, args); + addSnippet(call, pts); + } + + void addSnippet(BPatch_snippet &snippet, std::vector &where) + { + BPatchSnippetHandle *handle = m_addressSpace->insertSnippet(snippet, where, + BPatch_lastSnippet); + + if (!handle) + return; + + for (std::vector::iterator it = where.begin(); + it != where.end(); + ++it) + m_snippetsByPoint[*it] = handle; + } + + + bool handleFileReader() + { + if (m_resultsData->n_entries > m_indexToAddress.size()) + { + error("Too many entries in the file, %u vs %zu\n", + m_resultsData->n_entries, m_indexToAddress.size()); + + return false; + } + + for (unsigned i = 0; i < m_resultsData->n_entries * 32; i++) + { + if (m_resultsData->indexIsHit(i)) + { + reportEvent(ev_breakpoint, 0, m_indexToAddress[i]); + } + } + + return true; + } + + bool handleFileWriter() + { + std::string binaryLibraryPath = + IOutputHandler::getInstance().getBaseDirectory() + "libkcov-binary-dyninst.so"; + + write_file(__dyninst_binary_library_data.data(), __dyninst_binary_library_data.size(), + "%s", binaryLibraryPath.c_str()); + + BPatch_object *lib = m_binaryEdit->loadLibrary(binaryLibraryPath.c_str()); + if (!lib) { + kcov_debug(INFO_MSG, "Can't load kcov dyninst library\n"); + + return false; + } + + m_addressReporterFunction = lookupFunction("kcov_dyninst_binary_report_address"); + m_reporterInitFunction = lookupFunction("kcov_dyninst_binary_init"); + + if (!m_addressReporterFunction || !m_reporterInitFunction) + return false; + + return true; + } + + BPatch_function *lookupFunction(const char *name) + { + std::vector funcs; + + m_image->findFunction(name, funcs); + if (funcs.empty()) { + error("unable to find function %s\n", name); + + return NULL; + } + + return funcs[0]; + } + + uint64_t *readCoverageDatum(void *buf, size_t totalSize) + { + return (uint64_t *)buf; + } + + void handleModules(BPatch_Vector &modules) + { + for (BPatch_Vector::iterator it = modules.begin(); + it != modules.end(); + ++it) + { + BPatch_Vector stmts; + + bool res = (*it)->getStatements(stmts); + if (!res) + continue; + + handleStatements(stmts); + } + } + + void handleStatements(BPatch_Vector &stmts) + { + for (BPatch_Vector::iterator it = stmts.begin(); + it != stmts.end(); + ++it) { + handleOneStatement(*it); + } + } + + void handleOneStatement(BPatch_statement &stmt) + { + const std::string filename = stmt.fileName(); + int lineNo = stmt.lineNumber(); + uint64_t addr = (uint64_t)stmt.startAddr(); + + for (LineListenerList_t::iterator it = m_lineListeners.begin(); + it != m_lineListeners.end(); + ++it) + (*it)->onLine(filename, lineNo, addr); + } + + void reportEvent(enum event_type type, int data = -1, uint64_t address = 0) + { + if (!m_listener) + return; + + m_listener->onEvent(Event(type, data, address)); + } + + + typedef std::vector LineListenerList_t; + typedef std::vector FileListenerList_t; + + enum mode + { + mode_unset, + mode_write_file, + mode_read_file, + }; + + enum mode m_mode; + std::string m_filename; + std::string m_outFile; + kcov_dyninst::dyninst_memory *m_resultsData; + + LineListenerList_t m_lineListeners; + FileListenerList_t m_fileListeners; + + IEventListener *m_listener; + BPatch *m_bpatch; + BPatch_addressSpace *m_addressSpace; + BPatch_binaryEdit *m_binaryEdit; + BPatch_image *m_image; + + std::unordered_map m_snippetsByPoint; + BPatch_function *m_reporterInitFunction; + BPatch_function *m_addressReporterFunction; + + uint32_t m_breakpointIdx; + uint32_t m_checksum; + + std::vector m_indexToAddress; + std::vector> m_pendingAddresses; +}; + + + +// This ugly stuff was inherited from bashEngine +static DyninstEngine *g_dyninstEngine; +class DyninstCtor +{ +public: + DyninstCtor() + { + g_dyninstEngine = new DyninstEngine(); + } +}; +static DyninstCtor g_dyninstCtor; + + +class DyninstEngineCreator : public IEngineFactory::IEngineCreator +{ +public: + virtual ~DyninstEngineCreator() + { + } + + virtual IEngine *create(IFileParser &parser) + { + return g_dyninstEngine; + } + + unsigned int matchFile(const std::string &filename, uint8_t *data, size_t dataSize) + { + // Better than the ptrace engine + return 2; + } +}; + +static DyninstEngineCreator g_dyninstEngineCreator; diff --git a/src/engines/dyninst-file-format.cc b/src/engines/dyninst-file-format.cc new file mode 100644 index 00000000..61512e3f --- /dev/null +++ b/src/engines/dyninst-file-format.cc @@ -0,0 +1,113 @@ +#include "dyninst-file-format.hh" + +#include + +struct kcov_dyninst::dyninst_file *kcov_dyninst::memoryToFile(const class kcov_dyninst::dyninst_memory &mem, size_t &outSize) +{ + outSize = sizeof(struct dyninst_file) + mem.n_entries * sizeof(uint32_t) + + mem.filename.size() + mem.options.size() + 2; + struct dyninst_file *out = (struct dyninst_file *)xmalloc(outSize); + char *p = (char *)out; + + out->magic = DYNINST_MAGIC; + out->version = DYNINST_VERSION; + out->n_entries = mem.n_entries; + out->filename_offset = mem.n_entries * sizeof(uint32_t) + sizeof(*out); + out->kcov_options_offset = out->filename_offset + mem.filename.size() + 1; + + strcpy(&p[out->filename_offset], mem.filename.c_str()); + strcpy(&p[out->kcov_options_offset], mem.options.c_str()); + + memcpy(out->data, mem.data, out->n_entries * sizeof(uint32_t)); + + return out; +} + +class kcov_dyninst::dyninst_memory *kcov_dyninst::fileToMemory(const struct kcov_dyninst::dyninst_file &file) +{ + if (file.magic != DYNINST_MAGIC) + { + return NULL; + } + + if (file.version != DYNINST_VERSION) + { + return NULL; + } + + size_t sizeExceptStrings = sizeof(file) + file.n_entries * sizeof(uint32_t); + + if (file.filename_offset < sizeExceptStrings) + { + return NULL; + } + + if (file.kcov_options_offset < file.filename_offset + 1) + { + return NULL; + } + char *p = (char *)&file; + + kcov_dyninst::dyninst_memory *out = new kcov_dyninst::dyninst_memory(std::string(&p[file.filename_offset]), + std::string(&p[file.kcov_options_offset]), + file.n_entries); + + memcpy(out->data, file.data, out->n_entries * sizeof(uint32_t)); + + return out; +} + +class kcov_dyninst::dyninst_memory *kcov_dyninst::diskToMemory(const std::string &src) +{ + size_t sz; + kcov_dyninst::dyninst_file *p = (kcov_dyninst::dyninst_file *)read_file(&sz, "%s", src.c_str()); + + if (!p) + { + return NULL; + } + + kcov_dyninst::dyninst_memory *out = kcov_dyninst::fileToMemory(*p); + free(p); + + return out; +} + +bool kcov_dyninst::dyninst_memory::indexIsHit(uint32_t index) +{ + if (index / 32 >= n_entries) + { + return false; + } + + uint32_t cur = data[index / 32]; + uint32_t bit = index % 32; + + return !!(cur & (1 << bit)); +} + +void kcov_dyninst::dyninst_memory::reportIndex(uint32_t index) +{ + unsigned int wordIdx = index / 32; + unsigned int bit = index % 32; + + if (wordIdx >= n_entries) + { + return; + } + + // Update the bit atomically + uint32_t *p = &data[wordIdx]; + + // Already hit? + if (*p & (1 << bit)) + return; + + uint32_t val, newVal; + do + { + val = *p; + newVal = val | (1 << bit); + + } while (!__sync_bool_compare_and_swap(p, val, newVal)); +} diff --git a/src/engines/dyninst-file-format.hh b/src/engines/dyninst-file-format.hh new file mode 100644 index 00000000..d2735877 --- /dev/null +++ b/src/engines/dyninst-file-format.hh @@ -0,0 +1,73 @@ +#pragma once + +#include +#include +#include + +#include + +const uint32_t DYNINST_MAGIC = 0x4d455247; // "MERG" +const uint32_t DYNINST_VERSION = 1; + +namespace kcov_dyninst +{ + struct dyninst_file + { + uint32_t magic; + uint32_t version; + uint32_t n_entries; + uint32_t header_checksum; + uint32_t filename_offset; + uint32_t kcov_options_offset; // --include-pattern etc + uint32_t data[]; + }; + + class dyninst_memory + { + public: + dyninst_memory(const std::string &fn, const std::string &opts, uint32_t n) : + filename(fn), + options(opts), + n_entries(n) + { + mapped = true; + data = (uint32_t *)::mmap(NULL, n_entries * sizeof(uint32_t), PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANONYMOUS, -1, 0); + if (!data) + { + // Fallback to plain malloc - won't work with forks + data = (uint32_t *)xmalloc(n_entries * sizeof(uint32_t)); + } + } + + ~dyninst_memory() + { + if (mapped) + { + munmap(data, n_entries * sizeof(uint32_t)); + } + else + { + free(data); + } + } + + void reportIndex(uint32_t index); + + bool indexIsHit(uint32_t index); + + const std::string filename; + const std::string options; + const uint32_t n_entries; + uint32_t *data; + + private: + bool mapped; + }; + + struct dyninst_file *memoryToFile(const class dyninst_memory &mem, size_t &outSize); + + class dyninst_memory *fileToMemory(const struct dyninst_file &file); + + class dyninst_memory *diskToMemory(const std::string &src); +}; diff --git a/src/include/configuration.hh b/src/include/configuration.hh index ab5bcb0c..04c54c3c 100644 --- a/src/include/configuration.hh +++ b/src/include/configuration.hh @@ -18,6 +18,8 @@ namespace kcov MODE_REPORT_ONLY = 2, MODE_COLLECT_AND_REPORT = 3, MODE_MERGE_ONLY = 4, + MODE_SYSTEM_RECORD = 5, + MODE_SYSTEM_REPORT = 6, } RunMode_t; class IListener @@ -82,6 +84,14 @@ namespace kcov */ virtual const std::vector &keyAsList(const std::string &key) = 0; + /** + * Set a key. Use these with caution. + */ + virtual void setKey(const std::string &key, const std::string &val) = 0; + + virtual void setKey(const std::string &key, int val) = 0; + + virtual void setKey(const std::string &key, const std::vector &val) = 0; /** * Return the coveree argv (i.e., without kcov and kcov options) diff --git a/src/main.cc b/src/main.cc index 299a2216..2b0aba7c 100644 --- a/src/main.cc +++ b/src/main.cc @@ -13,10 +13,12 @@ #include #include #include +#include #include #include #include "merge-parser.hh" +#include "engines/dyninst-file-format.hh" #include "writers/html-writer.hh" #include "writers/json-writer.hh" #include "writers/coveralls-writer.hh" @@ -190,18 +192,10 @@ static int runMergeMode() return 0; } -int main(int argc, const char *argv[]) +static int runKcov(IConfiguration::RunMode_t runningMode) { IConfiguration &conf = IConfiguration::getInstance(); - if (!conf.parse(argc, argv)) - return 1; - - IConfiguration::RunMode_t runningMode = (IConfiguration::RunMode_t)conf.keyAsInt("running-mode"); - - if (runningMode == IConfiguration::MODE_MERGE_ONLY) - return runMergeMode(); - std::string file = conf.keyAsString("binary-path") + conf.keyAsString("binary-name"); IFileParser *parser = IParserManager::getInstance().matchParser(file); if (!parser) { @@ -304,3 +298,220 @@ int main(int argc, const char *argv[]) return ret; } + +static int runSystemModeRecordFile(const std::string &dir, const std::string &file) +{ + pid_t child = fork(); + if (child < 0) + { + panic("Fork failed?\n"); + } + else if (child == 0) + { + IConfiguration &conf = IConfiguration::getInstance(); + std::string rootDir = conf.keyAsString("binary-path") + conf.keyAsString("binary-name"); + + if (dir.size() < rootDir.size()) + { + error("kcov: Something strange with the directories: %s vs %s\n", + dir.c_str(), rootDir.c_str()); + return -1; + } + + // FIXME! There are stupid assumptions here... + std::string dstDir = conf.keyAsString("out-directory") + "/" + dir.substr(rootDir.size()); + std::string dstFile = conf.keyAsString("out-directory") + "/" + file.substr(rootDir.size()); + + conf.setKey("system-mode-write-file", dstFile); + conf.setKey("binary-name", file); + conf.setKey("binary-path", ""); // file uses an absolute path + + (void)mkdir(conf.keyAsString("out-directory").c_str(), 0755); + (void)mkdir(dstDir.c_str(), 0755); + + runKcov(IConfiguration::MODE_COLLECT_ONLY); + exit(0); + } + else + { + // Parent + int status; + + ::waitpid(child, &status, 0); + } + + return 0; +} + +static int runSystemModeRecordDirectory(const std::string &base) +{ + DIR *dir = ::opendir(base.c_str()); + if (!dir) + { + error("Can't open directory %s\n", base.c_str()); + return -1; + } + + // Loop through the directory structure + struct dirent *de; + for (de = ::readdir(dir); de; de = ::readdir(dir)) { + std::string cur = base + "/" + de->d_name; + + if (strcmp(de->d_name, ".") == 0) + continue; + + if (strcmp(de->d_name, "..") == 0) + continue; + + struct stat st; + + if (lstat(cur.c_str(), &st) < 0) + continue; + + // Executable file? + if (S_ISREG(st.st_mode) && + (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) + { + runSystemModeRecordFile(base, cur); + continue; + } + + if (S_ISDIR(st.st_mode)) + runSystemModeRecordDirectory(cur); + } + ::closedir(dir); + + return 0; +} + +static int runSystemModeRecord() +{ + IConfiguration &conf = IConfiguration::getInstance(); + + std::string base = conf.keyAsString("binary-path") + conf.keyAsString("binary-name"); + + return runSystemModeRecordDirectory(base); +} + +std::vector optionsStringToConfigurationVector(const std::string &in) +{ + std::vector parts = split_string(in, " "); + if (parts.size() != 6) + { + warning("Options vector is not correct: '%s' splits into %zu\n", + in.c_str(), parts.size()); + return std::vector(); + } + + return parts; +} + +static int runSystemModeReportFile(const std::string &file) +{ + kcov_dyninst::dyninst_memory *data = kcov_dyninst::diskToMemory(file); + + if (!data) + { + return -1; + } + + pid_t child = fork(); + if (child < 0) + { + panic("Fork failed?\n"); + } + else if (child == 0) + { + IConfiguration &conf = IConfiguration::getInstance(); + std::pair both = split_path(data->filename); // absolute path + + conf.setKey("system-mode-read-results-file", file); + conf.setKey("binary-name", both.second); + conf.setKey("binary-path", both.first); + conf.setKey("target-directory", conf.keyAsString("out-directory") + "/" + both.second); + + std::vector options = optionsStringToConfigurationVector(data->options); + if (options.size() == 6) + { + conf.setKey("include-pattern", options[0]); + conf.setKey("exclude-pattern", options[1]); + conf.setKey("include-path", options[2]); + conf.setKey("exclude-path", options[3]); + conf.setKey("exclude-line", options[4]); + conf.setKey("exclude-region", options[5]); + } + + runKcov(IConfiguration::MODE_COLLECT_AND_REPORT); + exit(0); + } + else + { + // Parent + int status; + + ::waitpid(child, &status, 0); + } + + delete data; + + return 0; +} + +static int runSystemModeReportDirectory(const std::string &base) +{ + DIR *dir = ::opendir(base.c_str()); + if (!dir) + { + error("Can't open directory %s\n", base.c_str()); + return -1; + } + + // Loop through the directory structure + struct dirent *de; + for (de = ::readdir(dir); de; de = ::readdir(dir)) { + std::string cur = base + "/" + de->d_name; + struct stat st; + + if (lstat(cur.c_str(), &st) < 0) + continue; + + // Executable file? + if (S_ISREG(st.st_mode)) + { + runSystemModeReportFile(cur); + } + } + ::closedir(dir); + + return 0; +} + +static int runSystemModeReport() +{ + IConfiguration &conf = IConfiguration::getInstance(); + + std::string base = conf.keyAsString("binary-path") + conf.keyAsString("binary-name"); + + return runSystemModeReportDirectory(base); +} + +int main(int argc, const char *argv[]) +{ + IConfiguration &conf = IConfiguration::getInstance(); + + if (!conf.parse(argc, argv)) + return 1; + + IConfiguration::RunMode_t runningMode = (IConfiguration::RunMode_t)conf.keyAsInt("running-mode"); + + if (runningMode == IConfiguration::MODE_MERGE_ONLY) + return runMergeMode(); + + if (runningMode == IConfiguration::MODE_SYSTEM_RECORD) + return runSystemModeRecord(); + + if (runningMode == IConfiguration::MODE_SYSTEM_REPORT) + return runSystemModeReport(); + + return runKcov(runningMode); +} diff --git a/tests/unit-tests/CMakeLists.txt b/tests/unit-tests/CMakeLists.txt index 5150ddfb..114a17dc 100644 --- a/tests/unit-tests/CMakeLists.txt +++ b/tests/unit-tests/CMakeLists.txt @@ -17,6 +17,7 @@ set (${TGT}_SRCS ../../src/capabilities.cc ../../src/collector.cc ../../src/engine-factory.cc + ../../src/engines/dyninst-file-format.cc ../../src/gcov.cc ../../src/output-handler.cc ../../src/parser-manager.cc @@ -31,6 +32,7 @@ set (${TGT}_SRCS tests-elf.cc tests-filter.cc tests-reporter.cc + tests-system-mode.cc tests-utils.cc tests-writer.cc ) diff --git a/tests/unit-tests/tests-system-mode.cc b/tests/unit-tests/tests-system-mode.cc new file mode 100644 index 00000000..9705caa7 --- /dev/null +++ b/tests/unit-tests/tests-system-mode.cc @@ -0,0 +1,235 @@ +#include "test.hh" + +#include +#include + +#include "../../src/engines/dyninst-file-format.hh" + +using namespace kcov_dyninst; + +TESTSUITE(system_mode_formats) +{ + TEST(can_marshal_empty_struct) + { + struct dyninst_memory mem("kalle-anka", "--include-pattern=anka --exclude-pattern=hejbo", 0); + + size_t size; + auto file = memoryToFile(mem, size); + char *p = (char *)file; + ASSERT_TRUE(file); + + ASSERT_TRUE(file->magic == DYNINST_MAGIC); + ASSERT_TRUE(file->version == DYNINST_VERSION); + ASSERT_TRUE(file->n_entries == 0); + ASSERT_TRUE(file->filename_offset == sizeof(*file)); + ASSERT_TRUE(file->kcov_options_offset == file->filename_offset + mem.filename.size() + 1); + + auto fn = std::string(&p[file->filename_offset]); + auto opts = std::string(&p[file->kcov_options_offset]); + + ASSERT_TRUE(fn == "kalle-anka"); + ASSERT_TRUE(opts == "--include-pattern=anka --exclude-pattern=hejbo"); + } + + TEST(can_marshal_single_entry_struct) + { + struct dyninst_memory mem("roy-gunnar-ramstedt", "moa", 1); + + mem.data[0] = 0x12345678; + + size_t size; + auto file = memoryToFile(mem, size); + char *p = (char *)file; + ASSERT_TRUE(file); + + ASSERT_TRUE(file->n_entries == 1); + ASSERT_TRUE(file->data[0] == 0x12345678); + + auto fn = std::string(&p[file->filename_offset]); + auto opts = std::string(&p[file->kcov_options_offset]); + + ASSERT_TRUE(fn == "roy-gunnar-ramstedt"); + ASSERT_TRUE(opts == "moa"); + } + + TEST(can_marshal_multi_entry_struct) + { + struct dyninst_memory mem("", "simon", 3); + + mem.data[0] = 0x12345678; + mem.data[1] = 0x56789abc; + mem.data[2] = 0xaabbccdd; + + size_t size; + auto file = memoryToFile(mem, size); + char *p = (char *)file; + ASSERT_TRUE(file); + + ASSERT_TRUE(file->n_entries == 3); + ASSERT_TRUE(file->data[0] == 0x12345678); + ASSERT_TRUE(file->data[1] == 0x56789abc); + ASSERT_TRUE(file->data[2] == 0xaabbccdd); + + auto fn = std::string(&p[file->filename_offset]); + auto opts = std::string(&p[file->kcov_options_offset]); + + ASSERT_TRUE(fn == ""); + ASSERT_TRUE(opts == "simon"); + } + + TEST(cannot_unmarshal_with_invalid_magic) + { + struct dyninst_file f{}; + + f.magic = DYNINST_MAGIC+1; + f.version = DYNINST_VERSION; + + auto mem = fileToMemory(f); + ASSERT_FALSE(mem); + } + + TEST(cannot_unmarshal_with_invalid_version) + { + struct dyninst_file f{}; + + f.magic = DYNINST_MAGIC; + f.version = DYNINST_VERSION + 1; + + auto mem = fileToMemory(f); + ASSERT_FALSE(mem); + } + + TEST(cannot_unmarshal_with_invalid_string_offsets) + { + struct dyninst_file f{}; + + f.magic = DYNINST_MAGIC; + f.version = DYNINST_VERSION; + + f.filename_offset = 0; // Should be atleast sizeof(f) + + auto mem = fileToMemory(f); + ASSERT_FALSE(mem); + + f.filename_offset = sizeof(f); + f.kcov_options_offset = sizeof(f); // Should be at least + 1 + mem = fileToMemory(f); + + ASSERT_FALSE(mem); + } + + TEST(can_unmarshal_empty_struct) + { + struct dyninst_file *f = (struct dyninst_file *)xmalloc(sizeof(struct dyninst_file) + 2); + + f->magic = DYNINST_MAGIC; + f->version = DYNINST_VERSION; + f->filename_offset = sizeof(*f); + f->kcov_options_offset = f->filename_offset + 1; + + auto mem = fileToMemory(*f); + ASSERT_TRUE(mem); + + ASSERT_TRUE(mem->n_entries == 0); + ASSERT_TRUE(mem->filename == ""); + ASSERT_TRUE(mem->options == ""); + } + + TEST(can_unmarshal_single_entry_struct) + { + struct dyninst_file *f = (struct dyninst_file *)xmalloc(sizeof(struct dyninst_file) + + sizeof(uint32_t) + + strlen("hej") + strlen("hopp") + 2); + char *p = (char *)f; + + f->magic = DYNINST_MAGIC; + f->version = DYNINST_VERSION; + f->n_entries = 1; + f->filename_offset = sizeof(*f) + sizeof(uint32_t); + f->kcov_options_offset = f->filename_offset + strlen("hej") + 1; + strcpy(&p[f->filename_offset], "hej"); + strcpy(&p[f->kcov_options_offset], "hopp"); + f->data[0] = 0x12345678; + + + auto mem = fileToMemory(*f); + ASSERT_TRUE(mem); + + ASSERT_TRUE(mem->filename == "hej"); + ASSERT_TRUE(mem->options == "hopp"); + ASSERT_TRUE(mem->n_entries == 1); + ASSERT_TRUE(mem->data[0] == 0x12345678); + } + + TEST(can_unmarshal_multi_entry_struct) + { + struct dyninst_file *f = (struct dyninst_file *)xmalloc(sizeof(struct dyninst_file) + + sizeof(uint32_t) * 3 + + strlen("a") + strlen("b") + 2); + char *p = (char *)f; + + f->magic = DYNINST_MAGIC; + f->version = DYNINST_VERSION; + f->n_entries = 3; + f->filename_offset = sizeof(*f) + sizeof(uint32_t) * 3; + f->kcov_options_offset = f->filename_offset + strlen("hej") + 1; + strcpy(&p[f->filename_offset], "a"); + strcpy(&p[f->kcov_options_offset], "b"); + f->data[0] = 0x12345678; + f->data[1] = 123; + f->data[2] = 4711; + + + auto mem = fileToMemory(*f); + ASSERT_TRUE(mem); + + ASSERT_TRUE(mem->filename == "a"); + ASSERT_TRUE(mem->options == "b"); + ASSERT_TRUE(mem->n_entries == 3); + ASSERT_TRUE(mem->data[0] == 0x12345678); + ASSERT_TRUE(mem->data[1] == 123); + ASSERT_TRUE(mem->data[2] == 4711); + } + + TEST(out_of_bounds_indexes_are_not_hit) + { + struct dyninst_memory mem("roy-gunnar-ramstedt", "moa", 1); + struct dyninst_memory memEmpty("roy-gunnar-ramstedt", "moa", 0); + + // 0..31 are possible + ASSERT_FALSE(mem.indexIsHit(32)); + ASSERT_FALSE(memEmpty.indexIsHit(0)); + } + + TEST(can_report_single_hit) + { + struct dyninst_memory mem("roy-gunnar-ramstedt", "moa", 1); + + mem.reportIndex(1); + + ASSERT_FALSE(mem.indexIsHit(0)); + ASSERT_TRUE(mem.indexIsHit(1)); + } + + TEST(can_report_multiple_hits) + { + struct dyninst_memory mem("roy-gunnar-ramstedt", "moa", 4); + + for (unsigned i = 0; i < 4 * 32; i += 2) + { + mem.reportIndex(i); + } + + for (unsigned i = 0; i < 4 * 32; i++) + { + if (i % 2 == 0) + { + ASSERT_TRUE(mem.indexIsHit(i)); + } + else + { + ASSERT_FALSE(mem.indexIsHit(i)); + } + } + } +} diff --git a/travis/Makefile b/travis/Makefile index d7c8f5d9..b10abeca 100644 --- a/travis/Makefile +++ b/travis/Makefile @@ -51,6 +51,24 @@ build_gcc: build run_unit_tests build_reference sudo i386 chroot ${chroot} sh -c "mkdir -p /tmp/kcov/build-static && cd /tmp/kcov/build-static && cmake -DKCOV_STATIC_BUILD=1 .." sudo i386 chroot ${chroot} sh -c "make -C /tmp/kcov/build-static" +build_dyninst: + cd /tmp && wget https://github.com/dyninst/dyninst/archive/v9.3.1.tar.gz + cd /tmp && tar -xf v9.3.1.tar.gz + mkdir -p /tmp/dyninst-9.3.1/bld + cd /tmp/dyninst-9.3.1/bld && CC=clang-3.9 CXX=clang++-3.9 cmake .. + make -C /tmp/dyninst-9.3.1/bld + sudo make -C /tmp/dyninst-9.3.1/bld install + +build_clang_linux: build_dyninst + mkdir -p build build-tests build-tools + cd build && CC=clang-3.9 CXX=clang++-3.9 CXXFLAGS=-Werror cmake .. + make -C build + cd build-tools && cmake ../tools + make -C build-tools + sudo make -C build install + cd build-tests && CC=clang-3.9 CXX=clang++-3.9 cmake ../tests + CC=clang-3.9 CXX=clang++-3.9 make -C build-tests + build_clang: mkdir -p build build-tests build-tools cd build && CC=clang-3.9 CXX=clang++-3.9 CXXFLAGS=-Werror cmake .. @@ -76,7 +94,7 @@ run-tests-linux-gcc: run-performance wget https://codecov.io/bash || true bash bash -s /tmp/kcov-kcov || true -run-tests-linux-clang: build_clang +run-tests-linux-clang: build_clang_linux tests/tools/run-tests build/src/kcov /tmp/ build-tests/ `pwd` -v trompeloeil: @@ -133,6 +151,6 @@ prepare_linux_gcc: prepare_linux_clang: sudo apt-add-repository -y 'deb http://llvm.org/apt/trusty llvm-toolchain-trusty-3.9 main' sudo apt-get update --force-yes -qq - sudo apt-get install -qq --force-yes -y clang-3.9 + sudo apt-get install -qq --force-yes -y clang-3.9 libboost-all-dev binutils-dev prepare_environment: prepare_${TRAVIS_OS_NAME} prepare_${TRAVIS_OS_NAME}_${CC}