codeCoverage.c /* * A simple code coverage tool using DyninstAPI * * This tool uses DyninstAPI to instrument the functions and basic blocks in * an executable and its shared libraries in order to record code coverage * data when the executable is run. This code coverage data is output when the * rewritten executable finishes running. * * The intent of this tool is to demonstrate some capabilities of DyninstAPI; * it should serve as a good stepping stone to building a more feature-rich * code coverage tool on top of Dyninst. */ #include <cstdlib> #include <iostream> #include <vector> #include <string> using namespace std; // Command line parsing #include <getopt.h> // DyninstAPI includes #include "BPatch.h" #include "BPatch_binaryEdit.h" #include "BPatch_flowGraph.h" #include "BPatch_function.h" #include "BPatch_point.h" using namespace Dyninst; static const char *USAGE = " [-bpsa] <binary> <output binary>\n -b: Basic block level code coverage\n -p: Print all functions (including functions that are never executed)\n -s: Instrument shared libraries also\n -a: Sort results alphabetically by function name\n"; static const char *OPT_STR = "bpsa"; // configuration options char *inBinary = NULL; char *outBinary = NULL; bool includeSharedLib = false; int printAll = 0; bool bbCoverage = false; int alphabetical = 0; set < string > skipLibraries; void initSkipLibraries () { /* List of shared libraries to skip instrumenting */ /* Do not instrument the instrumentation library */ skipLibraries.insert ("libInst.so"); skipLibraries.insert ("libc.so.6"); skipLibraries.insert ("libc.so.7"); skipLibraries.insert ("ld-2.5.so"); skipLibraries.insert ("ld-linux.so.2"); skipLibraries.insert ("ld-lsb.so.3"); skipLibraries.insert ("ld-linux-x86-64.so.2"); skipLibraries.insert ("ld-lsb-x86-64.so"); skipLibraries.insert ("ld-elf.so.1"); skipLibraries.insert ("ld-elf32.so.1"); skipLibraries.insert ("libstdc++.so.6"); return; } bool parseArgs (int argc, char *argv[]) { int c; while ((c = getopt (argc, argv, OPT_STR)) != -1) { switch ((char) c) { case ‘b‘: bbCoverage = true; break; case ‘p‘: printAll = 1; break; case ‘s‘: /* if includeSharedLib is set, * all libraries linked to the binary will also be instrumented */ includeSharedLib = true; break; case ‘a‘: alphabetical = 1; break; default: cerr << "Usage: " << argv[0] << USAGE; return false; } } int endArgs = optind; if (endArgs >= argc) { cerr << "Input binary not specified." << endl << "Usage: " << argv[0] << USAGE; return false; } /* Input Binary */ inBinary = argv[endArgs]; endArgs++; if (endArgs >= argc) { cerr << "Output binary not specified." << endl << "Usage: " << argv[0] << USAGE; return false; } /* Rewritten Binary */ outBinary = argv[endArgs]; return true; } BPatch_function *findFuncByName (BPatch_image * appImage, char *funcName) { /* fundFunctions returns a list of all functions with the name ‘funcName‘ in the binary */ BPatch_Vector < BPatch_function * >funcs; if (NULL == appImage->findFunction (funcName, funcs) || !funcs.size () || NULL == funcs[0]) { cerr << "Failed to find " << funcName << " function in the instrumentation library" << endl; return NULL; } return funcs[0]; } bool insertFuncEntry (BPatch_binaryEdit * appBin, BPatch_function * curFunc, char *funcName, BPatch_function * instIncFunc, int funcId) { /* Find the instrumentation points */ vector < BPatch_point * >*funcEntry = curFunc->findPoint (BPatch_entry); if (NULL == funcEntry) { cerr << "Failed to find entry for function " << funcName << endl; return false; } cout << "Inserting instrumention at function entry of " << funcName << endl; /* Create a vector of arguments to the function * incCoverage function takes the function name as argument */ BPatch_Vector < BPatch_snippet * >instArgs; BPatch_constExpr id (funcId); instArgs.push_back (&id); BPatch_funcCallExpr instIncExpr (*instIncFunc, instArgs); /* Insert the snippet at function entry */ BPatchSnippetHandle *handle = appBin->insertSnippet (instIncExpr, *funcEntry, BPatch_callBefore, BPatch_lastSnippet); if (!handle) { cerr << "Failed to insert instrumention at function entry of " << funcName << endl; return false; } return true; } bool insertBBEntry (BPatch_binaryEdit * appBin, BPatch_function * curFunc, char *funcName, const char *moduleName, BPatch_function * instBBIncFunc, BPatch_function * registerBB, int *bbIndex, BPatch_Vector < BPatch_snippet * >*registerCalls) { BPatch_flowGraph *appCFG = curFunc->getCFG (); BPatch_Set < BPatch_basicBlock * >allBlocks; BPatch_Set < BPatch_basicBlock * >::iterator iter; if (!appCFG) { cerr << "Failed to find CFG for function " << funcName << endl; return EXIT_FAILURE; } if (!appCFG->getAllBasicBlocks (allBlocks)) { cerr << "Failed to find basic blocks for function " << funcName << endl; return EXIT_FAILURE; } else if (allBlocks.size () == 0) { cerr << "No basic blocks for function " << funcName << endl; return EXIT_FAILURE; } /* Instrument the entry of every basic block */ for (iter = allBlocks.begin (); iter != allBlocks.end (); iter++) { unsigned long address = (*iter)->getStartAddress (); cout << "Instrumenting Basic Block 0x" << hex << address << " of " << funcName << endl; BPatch_Vector < BPatch_snippet * >instArgs; BPatch_constExpr bbId (*bbIndex); instArgs.push_back (&bbId); BPatch_point *bbEntry = (*iter)->findEntryPoint (); if (NULL == bbEntry) { cerr << "Failed to find entry for basic block at 0x" << hex << address << endl; return false; } BPatch_funcCallExpr instIncExpr (*instBBIncFunc, instArgs); BPatchSnippetHandle *handle = appBin->insertSnippet (instIncExpr, *bbEntry, BPatch_callBefore, BPatch_lastSnippet); if (!handle) { cerr << "Failed to insert instrumention in basic block at 0x" << hex << address << endl; return false; } /* Create a call to the registration function for this basic block */ BPatch_Vector < BPatch_snippet * >regArgs; BPatch_constExpr bbIdReg (*bbIndex); regArgs.push_back (&bbIdReg); BPatch_constExpr coverageFunc (funcName); regArgs.push_back (&coverageFunc); BPatch_constExpr coverageModule (moduleName); regArgs.push_back (&coverageModule); BPatch_constExpr addrArg (address); regArgs.push_back (&addrArg); BPatch_funcCallExpr *regCall = new BPatch_funcCallExpr (*registerBB, regArgs); registerCalls->push_back (regCall); (*bbIndex)++; } return true; } int main (int argc, char *argv[]) { if (!parseArgs (argc, argv)) return EXIT_FAILURE; /* Initialize list of libraries that should not be instrumented - relevant only if includeSharedLib is true */ initSkipLibraries (); /* Every Dyninst mutator needs to declare one instance of BPatch */ BPatch bpatch; /* Open the specified binary for binary rewriting. * When the second parameter is set to true, all the library dependencies * as well as the binary are opened */ BPatch_binaryEdit *appBin = bpatch.openBinary (inBinary, true); if (appBin == NULL) { cerr << "Failed to open binary" << endl; return EXIT_FAILURE; } /* Open the instrumentation library. * loadLibrary loads the instrumentation library into the binary image and * adds it as a new dynamic dependency in the rewritten library */ const char *instLibrary = "./libInst.so"; if (!appBin->loadLibrary (instLibrary)) { cerr << "Failed to open instrumentation library" << endl; return EXIT_FAILURE; } BPatch_image *appImage = appBin->getImage (); /* Find code coverage functions in the instrumentation library */ BPatch_function *instInitFunc = findFuncByName (appImage, (char *) "initCoverage"); BPatch_function *registerFunc = findFuncByName (appImage, (char *) "registerFunc"); BPatch_function *instIncFunc = findFuncByName (appImage, (char *) "incFuncCoverage"); BPatch_function *registerBB = findFuncByName (appImage, (char *) "registerBB"); BPatch_function *instBBIncFunc = findFuncByName (appImage, (char *) "incBBCoverage"); BPatch_function *instExitFunc = findFuncByName (appImage, (char *) "exitCoverage"); if (!instInitFunc || !instIncFunc || !instExitFunc || !instBBIncFunc || !registerFunc || !registerBB) { return EXIT_FAILURE; } /* To instrument every function in the binary * --> iterate over all the modules in the binary * --> iterate over all functions in each modules */ vector < BPatch_module * >*modules = appImage->getModules (); vector < BPatch_module * >::iterator moduleIter; BPatch_module *defaultModule; BPatch_Vector < BPatch_snippet * >registerCalls; int bbIndex = 0; int funcIndex = 0; for (moduleIter = modules->begin (); moduleIter != modules->end (); ++moduleIter) { char moduleName[1024]; (*moduleIter)->getName (moduleName, 1024); /* if includeSharedLib is not set, skip instrumenting dependent libraries */ if ((*moduleIter)->isSharedLib ()) { if (!includeSharedLib || skipLibraries.find (moduleName) != skipLibraries.end ()) { cout << "Skipping library: " << moduleName << endl; continue; } } /* Every binary has one default module. * code coverage initialize and finalize functions should be called only once. * Hence call them from the default module */ if (string (moduleName).find ("DEFAULT_MODULE") != string::npos) { defaultModule = (*moduleIter); } cout << "Instrumenting module: " << moduleName << endl; vector < BPatch_function * >*allFunctions = (*moduleIter)->getProcedures (); vector < BPatch_function * >::iterator funcIter; /* Insert snippets at the entry of every function */ for (funcIter = allFunctions->begin (); funcIter != allFunctions->end (); ++funcIter) { BPatch_function *curFunc = *funcIter; char funcName[1024]; curFunc->getName (funcName, 1024); /* * Replace DEFAULT_MODULE with the name of the input binary in the output */ string passedModName (moduleName); if (passedModName.find ("DEFAULT_MODULE") != string::npos) { // Strip the directory passedModName = inBinary; passedModName = passedModName.substr (passedModName.find_last_of ("\\/") + 1); } insertFuncEntry (appBin, curFunc, funcName, instIncFunc, funcIndex); /* Create a call to the registration function */ BPatch_Vector < BPatch_snippet * >regArgs; BPatch_constExpr funcIdReg (funcIndex); regArgs.push_back (&funcIdReg); BPatch_constExpr coverageFunc (funcName); regArgs.push_back (&coverageFunc); BPatch_constExpr coverageModule (passedModName.c_str ()); regArgs.push_back (&coverageModule); BPatch_funcCallExpr *regCall = new BPatch_funcCallExpr (*registerFunc, regArgs); registerCalls.push_back (regCall); funcIndex++; if (bbCoverage) { insertBBEntry (appBin, curFunc, funcName, passedModName.c_str (), instBBIncFunc, registerBB, &bbIndex, ®isterCalls); } } } /* Create argument list for initCoverage function * with the number of functions the number of basic blocks */ BPatch_Vector < BPatch_snippet * >instInitArgs; BPatch_constExpr numFuncs (funcIndex); instInitArgs.push_back (&numFuncs); BPatch_constExpr numBBs (bbIndex); instInitArgs.push_back (&numBBs); BPatch_funcCallExpr instInitExpr (*instInitFunc, instInitArgs); // Execute the function registration and basic block registration calls after // the call to the initialization function, i.e., // initCoverage() // registerFunc() // ... // registerBB() // ... BPatch_sequence registerSequence (registerCalls); BPatch_Vector < BPatch_snippet * >initSequenceVec; initSequenceVec.push_back (&instInitExpr); initSequenceVec.push_back (®isterSequence); BPatch_sequence initSequence (initSequenceVec); /* Locate _init */ BPatch_Vector<BPatch_function*> funcs; appImage->findFunction("_init", funcs); if (funcs.size()) { BPatch_Vector<BPatch_function*>::iterator fIter; for (fIter = funcs.begin(); fIter != funcs.end(); ++fIter) { BPatch_module * mod = (*fIter)->getModule(); char modName[1024]; mod->getName(modName, 1024); if (!mod->isSharedLib ()) { mod->insertInitCallback(initSequence); cerr << "insertInitCallback on " << modName << endl; } } } /* Insert initCoverage function in the init section of the default module * to be executed exactly once when the module is loaded */ // if (!defaultModule->insertInitCallback (initSequence)) { // cerr << "Failed to insert init function in the module" << endl; // return EXIT_FAILURE; // } /* Insert exitCoverage function in the fini section of the default module * to be executed exactly once when the module is unloaded */ BPatch_Vector < BPatch_snippet * >instExitArgs; BPatch_constExpr varPrint (printAll); instExitArgs.push_back (&varPrint); BPatch_constExpr varBBPrint (bbCoverage ? 1 : 0); instExitArgs.push_back (&varBBPrint); BPatch_constExpr varAlpha (alphabetical); instExitArgs.push_back (&varAlpha); BPatch_funcCallExpr instExitExpr (*instExitFunc, instExitArgs); /* Locate _fini */ funcs.clear(); appImage->findFunction("_fini", funcs); if (funcs.size()) { BPatch_Vector<BPatch_function*>::iterator fIter; for (fIter = funcs.begin(); fIter != funcs.end(); ++fIter) { BPatch_module * mod = (*fIter)->getModule(); char modName[1024]; mod->getName(modName, 1024); if (!mod->isSharedLib ()) { mod->insertFiniCallback(instExitExpr); cerr << "insertFiniCallback on " << modName << endl; } } } // if (!defaultModule->insertFiniCallback (instExitExpr)) { // cerr << "Failed to insert exit function in the module" << endl; // return EXIT_FAILURE; // } // Output the instrumented binary if (!appBin->writeFile (outBinary)) { cerr << "Failed to write output file: " << outBinary << endl; return EXIT_FAILURE; } return EXIT_SUCCESS; }
libInst.C
/* * The instrumentation library for the codeCoverage tool. Provides * functions for initialization, registering functions and basic * blocks for coverage tracking, and outputting the results. */ #include<cstdlib> #include<cstdio> #include<iostream> #include<cstring> #include<vector> #include<algorithm> using namespace std; class bbRecord { public: string funcName; string modName; unsigned long address; unsigned long count; bbRecord() : funcName(""), modName(""), address(0), count(0) {} }; class funcRecord { public: string funcName; string modName; unsigned long count; funcRecord() : funcName(""), modName(""), count(0) {} }; // Used to records via qsort static int compareFuncRecordByName(const void *left, const void *right) { funcRecord *leftRecord = (funcRecord *)left; funcRecord *rightRecord = (funcRecord *)right; return leftRecord->funcName.compare(rightRecord->funcName); } static int compareFuncRecordByCount(const void *left, const void *right) { funcRecord *leftRecord = (funcRecord *)left; funcRecord *rightRecord = (funcRecord *)right; if( leftRecord->count < rightRecord->count ) return 1; if( leftRecord->count > rightRecord->count ) return -1; return 0; } static int compareBBRecordByName(const void *left, const void *right) { bbRecord *leftRecord = (bbRecord *)left; bbRecord *rightRecord = (bbRecord *)right; return leftRecord->funcName.compare(rightRecord->funcName); } static int compareBBRecordByCount(const void *left, const void *right) { bbRecord *leftRecord = (bbRecord *)left; bbRecord *rightRecord = (bbRecord *)right; if( leftRecord->count < rightRecord->count ) return 1; if( leftRecord->count > rightRecord->count ) return -1; return 0; } // For efficency in instrumentation, indexed by id static bbRecord *bbs; static funcRecord *funcs; int numFuncs = 0; int numBBs = 0; int enabled = 0; // Allocates space for all tracked functions and basic blocks void initCoverage(int totalFuncs, int totalBBs) { numFuncs = totalFuncs; numBBs = totalBBs; funcs = new funcRecord[numFuncs]; bbs = new bbRecord[numBBs]; enabled = 1; } // Populates a record for a function void registerFunc(int id, char *name, char *modName) { if( !enabled ) return; funcs[id].funcName = name; funcs[id].modName = modName; funcs[id].count = 0; } // Populates a record for a basic block void registerBB(int id, char *name, char *modName, unsigned long addr) { if( !enabled ) return; bbs[id].funcName = name; bbs[id].modName = modName; bbs[id].address = addr; bbs[id].count = 0; } // Should be called on function entry void incFuncCoverage(int id) { if( !enabled ) return; funcs[id].count++; } // Should be called on basic block entry void incBBCoverage(int id) { if( !enabled ) return; bbs[id].count++; } // Prints the code coverage stats. to standard out, also disables any more tracking void exitCoverage(int printAll, int printBasicBlocks, int sortAlphabetical) { if( !enabled ) return; printf("\n\n ************************** Code Coverage ************************* \n\n"); int count = 0; if( sortAlphabetical ) qsort(funcs, numFuncs, sizeof(funcRecord), &compareFuncRecordByName); else qsort(funcs, numFuncs, sizeof(funcRecord), &compareFuncRecordByCount); for(int i = 0; i < numFuncs; ++i) { if( funcs[i].count > 0 ) count++; if( printAll || (funcs[i].count > 0) ) printf(" %4lu : %s, %s\n", funcs[i].count, funcs[i].funcName.c_str(), funcs[i].modName.c_str()); } printf("\n ************** Code Coverage %d out of %d functions ************** \n\n", count, numFuncs); if (printBasicBlocks) { int bbCount = 0; printf("\n\n ************************** Basic Block Coverage ************************* \n\n"); if( sortAlphabetical ) qsort(bbs, numBBs, sizeof(bbRecord), &compareBBRecordByName); else qsort(bbs, numBBs, sizeof(bbRecord), &compareBBRecordByCount); string curFunc; string curMod; for(int i = 0; i < numBBs; ++i) { if( bbs[i].count > 0 ) bbCount++; else if( !printAll ) continue; if( curFunc != bbs[i].funcName || curMod != bbs[i].modName ) { curFunc = bbs[i].funcName; curMod = bbs[i].modName; printf(" (%s, %s)\n", bbs[i].funcName.c_str(), bbs[i].modName.c_str()); printf(" \t %4lu : 0x%-8lx\n", bbs[i].count, bbs[i].address); }else{ printf(" \t %4lu : 0x%-8lx\n", bbs[i].count, bbs[i].address); } } printf("\n ************** Basic Block Coverage %d out of %d blocks ************** \n\n", bbCount, numBBs); } enabled = 0; }
libtestcc.c
/* * A toy library to demonstrate the codeCoverage tool */ #include "libtestcc.h" static int otherFunctionCalled = 0; static void otherFunction() { otherFunctionCalled = 1; } void libFooFunction(int callOtherFunction) { if( callOtherFunction ) otherFunction(); }
libtestcc.h
/* * Header for libtestcc */ #ifndef __LIB_TESTCC_H__ #define __LIB_TESTCC_H__ extern void libFooFunction(int callOtherFunction); #endif
Makefile
README
This directory contains a simple code coverage tool built with DyninstAPI. The tool uses Dyninst to instrument every function in a program binary as well as optionally instrumenting every basic block to record code coverage data. The goal of this tool is to demonstrate some of the capabilities of DyninstAPI. It does very little processing of the raw code coverage data, just some basic sorting when outputting the data. This tool should serve as the basis for creating a more feature-rich code coverage tool using Dyninst. The presence of debugging information in the program binary is recommended to acquire the most useful information about code coverage, but it is not required. Without debugging information, source file information will not be available in the code coverage output. This tool makes use of an instrumentation library, libInst, to collect the code coverage data. The tool adds this library as a dependency to the input executable and also inserts calls into this library at function entries and basic block entries. The provided Makefile can be used to build the program. The DYNINST_ROOT environment variable should be set to the directory where Dyninst was built/installed. This directory should contain an include directory with the Dyninst headers and a lib directory with the Dyninst libraries. Also, make sure to set the LD_LIBRARY_PATH environment variable to include the library directory and set the DYNINSTAPI_RT_LIB environment variable to ${DYNINST_LIBRARY_DIRECTORY}/libdyninstAPI_RT.so.1 Also included in this directory is a toy program that demonstrates a use of the codeCoverage tool: testcc and libtestcc. They can be built with the provided Makefile. An example usage of the codeCoverage tool with this program follows. It is assumed that the following binaries have been built: codeCoverage, libtestcc, testcc. Also, this example assumes you are using csh/tcsh, but should easily transfer to another shell. Before starting, the testcc program will not work unless the libtestcc.so library can be found by the dynamic linker. Among other solutions, this command should make testcc execute successfully. % setenv LD_LIBRARY_PATH ${LD_LIBRARY_PATH}:. First, execute codeCoverage without any arguments to see the usage. % ./codeCoverage Input binary not specified. Usage: ./codeCoverage [-bpsa] <binary> <output binary> -b: Basic block level code coverage -p: Print all functions (including functions that are never executed) -s: Instrument shared libraries also -a: Sort results alphabetically by function name Now, pass the testcc executable as input, instrumenting basic blocks as well as a shared library used by testcc, libtestcc.so. % ./codeCoverage -sb ./testcc testcc.inst [ Lots of output stating what the tool is doing ] You may notice that the tool skips some shared libraries. The default behavior of the tool is to not instrument standard libraries such as libc. The last command will output a rewritten version of testcc, testcc.inst. It will also overwrite the existing libtestcc.so file with a rewritten version. The default Dyninst behavior when rewritting the shared libraries of an executable is to output the shared libraries in the same directory as the rewritten executable. Now, run the testcc.inst program to generate code coverage data. An abridged version of the output follows. % ./testcc.inst ************************** Code Coverage ************************* 1 : _init, testcc 1 : __do_global_dtors_aux, testcc 1 : frame_dummy, testcc 1 : __do_global_ctors_aux, testcc 1 : _fini, testcc 1 : two, testcc.c 1 : one, testcc.c 1 : main, testcc.c ************** Code Coverage 8 out of 17 functions ************** ************************** Basic Block Coverage ************************* [...snip...] (two, testcc.c) 1 : 0x4006ab (one, testcc.c) 1 : 0x4006c0 1 : 0x4006d6 (__do_global_dtors_aux, testcc) 1 : 0x40063c (one, testcc.c) 1 : 0x4006e4 (main, testcc.c) 1 : 0x4006f0 (__do_global_dtors_aux, testcc) 1 : 0x400643 (main, testcc.c) 1 : 0x40071a [...snip...] ************** Basic Block Coverage 23 out of 69 blocks ************** END OUTPUT The first set of data contains counts for the number of times each function was called. The second set of data contains counts for the number of times each basic block was entered. If you run the program again with a few command line arguments, you will see that the ‘three‘ function in testcc.c was now called as well as functions in libtestcc.so. % ./testcc.inst 1 2 ************************** Code Coverage ************************* 1 : _init, testcc 1 : three, testcc.c 1 : two, testcc.c 1 : one, testcc.c 1 : main, testcc.c 1 : otherFunction, libtestcc.so 1 : libFooFunction, libtestcc.so 1 : __do_global_dtors_aux, testcc 1 : frame_dummy, testcc 1 : __do_global_ctors_aux, testcc 1 : _fini, testcc ************** Code Coverage 11 out of 17 functions ************** [...snip...] basic block output not included END OUTPUT This output shows that more functions have been called with this new input set.
时间: 2024-11-05 11:37:09