citrun

watch C/C++ source code execute
Log | Files | Refs | LICENSE

commit bb5e8b942452ec5cd91e6780950fadd718396c2b
parent ca94c0dcbc1f6f8042a8dd540a287ffe0fe006d2
Author: Kyle Milz <kyle@getaddrinfo.net>
Date:   Tue,  7 Jun 2016 22:57:44 -0600

src: rename source files instrument_* to inst_*

Diffstat:
Msrc/Jamfile | 6+++---
Asrc/inst_action.cc | 143+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/inst_action.h | 42++++++++++++++++++++++++++++++++++++++++++
Asrc/inst_ast_visitor.cc | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rsrc/instrument_ast_visitor.h -> src/inst_ast_visitor.h | 0
Asrc/inst_main.cc | 245+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/instrument_action.cc | 143-------------------------------------------------------------------------------
Dsrc/instrument_action.h | 42------------------------------------------
Dsrc/instrument_ast_visitor.cc | 94-------------------------------------------------------------------------------
Dsrc/instrument_main.cc | 245-------------------------------------------------------------------------------
10 files changed, 527 insertions(+), 527 deletions(-)

diff --git a/src/Jamfile b/src/Jamfile @@ -1,9 +1,9 @@ SubDir TOP src ; INSTRUMENT_SRCS = - instrument_main.cc - instrument_action.cc - instrument_ast_visitor.cc ; + inst_main.cc + inst_action.cc + inst_ast_visitor.cc ; CITRUN_SRCS = gl_main.cc diff --git a/src/inst_action.cc b/src/inst_action.cc @@ -0,0 +1,143 @@ +#include <err.h> +#include <fcntl.h> // open +#include <limits.h> +#include <sys/stat.h> // mode flags +#include <unistd.h> // getcwd, access + +#include <fstream> +#include <iostream> +#include <sstream> +#include <string> + +#include <clang/Frontend/CompilerInstance.h> + +#include "inst_action.h" +#include "runtime_h.h" + + +#if LLVM_VER > 35 +std::unique_ptr<clang::ASTConsumer> +#else +clang::ASTConsumer * +#endif +InstrumentAction::CreateASTConsumer(clang::CompilerInstance &CI, clang::StringRef file) +{ + // llvm::errs() << "** Creating AST consumer for: " << file << "\n"; + clang::SourceManager &sm = CI.getSourceManager(); + TheRewriter.setSourceMgr(sm, CI.getLangOpts()); + + // Hang onto a reference to this so we can read from it later + InstrumentASTConsumer = new RewriteASTConsumer(TheRewriter); +#if LLVM_VER > 35 + return std::unique_ptr<clang::ASTConsumer>(InstrumentASTConsumer); +#else + return InstrumentASTConsumer; +#endif +} + +std::string +get_current_node(std::string file_path) +{ + size_t last_slash = file_path.find_last_of('/'); + std::string fn(file_path.substr(last_slash + 1)); + + std::replace(fn.begin(), fn.end(), '.', '_'); + std::replace(fn.begin(), fn.end(), '-', '_'); + + return fn; +} + +std::string +swap_last_node(std::string curr_node) +{ + char *cwd = getcwd(NULL, PATH_MAX); + if (cwd == NULL) + errx(1, "getcwd"); + + std::string src_number_filename(cwd); + src_number_filename.append("/LAST_NODE"); + + std::string last_node("NULL"); + + if (access(src_number_filename.c_str(), F_OK) == 0) { + // LAST_NODE exists, read last_node from file + std::ifstream src_number_file; + src_number_file.open(src_number_filename, std::fstream::in); + src_number_file >> last_node; + src_number_file.close(); + } + + // Always write curr_node to file + std::ofstream src_number_file; + src_number_file.open(src_number_filename, std::fstream::out); + src_number_file << curr_node; + src_number_file.close(); + + return last_node; +} + +void +InstrumentAction::EndSourceFileAction() +{ + clang::SourceManager &sm = TheRewriter.getSourceMgr(); + const clang::FileID main_fid = sm.getMainFileID(); + // llvm::errs() << "** EndSourceFileAction for: " + // << sm.getFileEntryForID(main_fid)->getName() + // << "\n"; + + clang::SourceLocation start = sm.getLocForStartOfFile(main_fid); + clang::SourceLocation end = sm.getLocForEndOfFile(main_fid); + unsigned int num_lines = sm.getPresumedLineNumber(end); + + std::string file_name = getCurrentFile(); + std::string curr_node = get_current_node(file_name); + std::string last_node = swap_last_node(curr_node); + + //std::cerr << "LAST NODE = " << last_node << std::endl; + + std::stringstream ss; + // Add preprocessor stuff so that the C runtime library links against + // C++ object code. + ss << "#ifdef __cplusplus" << std::endl; + ss << "extern \"C\" {" << std::endl; + ss << "#endif" << std::endl; + + // Embed the header directly in the primary source file. + ss << runtime_h << std::endl; + + // Define storage for coverage data + ss << "static uint64_t _citrun_lines[" << num_lines << "];" << std::endl; + + // Get visitor instance to check how many times it rewrote something + RewriteASTVisitor visitor = InstrumentASTConsumer->get_visitor(); + + // Let the struct know this definition will be elsewhere + ss << "extern struct _citrun_node _citrun_node_" << last_node << ";" << std::endl; + + // Define this translation units main book keeping data structure + ss << "struct _citrun_node _citrun_node_" << curr_node << " = {" << std::endl + << " .lines_ptr = _citrun_lines," << std::endl + << " .size = " << num_lines << "," << std::endl + << " .inst_sites = " << visitor.GetRewriteCount() << "," << std::endl + << " .file_name = \"" << file_name << "\"," << std::endl; + if (last_node.compare("NULL") == 0) + ss << " .next = NULL," << std::endl; + else + ss << " .next = &_citrun_node_" << last_node << "," << std::endl; + ss << "};" << std::endl; + + // Close extern "C" { + ss << "#ifdef __cplusplus" << std::endl; + ss << "}" << std::endl; + ss << "#endif" << std::endl; + + TheRewriter.InsertTextAfter(start, ss.str()); + + int fd = open(file_name.c_str(), O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR); + if (fd < 0) + err(1, "open"); + llvm::raw_fd_ostream output(fd, /* close */ 1); + + // Write the instrumented source file + TheRewriter.getEditBuffer(main_fid).write(output); +} diff --git a/src/inst_action.h b/src/inst_action.h @@ -0,0 +1,42 @@ +#include <clang/AST/ASTConsumer.h> +#include <clang/Frontend/FrontendActions.h> +#include <clang/Rewrite/Core/Rewriter.h> + +#include "inst_ast_visitor.h" + +class RewriteASTConsumer : public clang::ASTConsumer { +public: + RewriteASTConsumer(clang::Rewriter &R) : Visitor(R) {} + + // Override the method that gets called for each parsed top-level + // declaration. + bool HandleTopLevelDecl(clang::DeclGroupRef DR) override { + for (auto &b : DR) { + // Traverse the declaration using our AST visitor. + Visitor.TraverseDecl(b); + // b->dump(); + } + return true; + } + + RewriteASTVisitor& get_visitor() { return Visitor; }; +private: + RewriteASTVisitor Visitor; +}; + +// For each source file provided to the tool, a new FrontendAction is created. +class InstrumentAction : public clang::ASTFrontendAction { +public: + InstrumentAction() {}; + + void EndSourceFileAction() override; +#if LLVM_VER > 35 + std::unique_ptr<clang::ASTConsumer> CreateASTConsumer(clang::CompilerInstance &, clang::StringRef) override; +#else + clang::ASTConsumer *CreateASTConsumer(clang::CompilerInstance &, clang::StringRef) override; +#endif + +private: + clang::Rewriter TheRewriter; + RewriteASTConsumer *InstrumentASTConsumer; +}; diff --git a/src/inst_ast_visitor.cc b/src/inst_ast_visitor.cc @@ -0,0 +1,94 @@ +#include <sstream> +#include <string> + +#include <clang/AST/AST.h> +#include <clang/Lex/Lexer.h> + +#include "inst_ast_visitor.h" + +bool +RewriteASTVisitor::VisitVarDecl(clang::VarDecl *d) +{ + return true; +} + +bool +RewriteASTVisitor::VisitStmt(clang::Stmt *s) +{ + std::stringstream ss; + unsigned line = SM.getPresumedLineNumber(s->getLocStart()); + clang::Stmt *stmt_to_inst = NULL; + + if (clang::isa<clang::IfStmt>(s)) { + stmt_to_inst = clang::cast<clang::IfStmt>(s)->getCond(); + } + else if (clang::isa<clang::ForStmt>(s)) { + stmt_to_inst = clang::cast<clang::ForStmt>(s)->getCond(); + } + else if (clang::isa<clang::WhileStmt>(s)) { + stmt_to_inst = clang::cast<clang::WhileStmt>(s)->getCond(); + } + else if (clang::isa<clang::SwitchStmt>(s)) { + stmt_to_inst = clang::cast<clang::SwitchStmt>(s)->getCond(); + } + else if (clang::isa<clang::ReturnStmt>(s)) { + stmt_to_inst = clang::cast<clang::ReturnStmt>(s)->getRetValue(); + } + /* + else if (isa<BreakStmt>(s) || isa<ContinueStmt>(s) || + || isa<SwitchCase>(s)) { + } + */ + else if (clang::isa<clang::DeclStmt>(s)) { + } + else if (clang::isa<clang::CallExpr>(s)) { + stmt_to_inst = s; + } + + if (stmt_to_inst == NULL) + return true; + + ss << "(++_citrun_lines[" << line << "], "; + if (TheRewriter.InsertTextBefore(stmt_to_inst->getLocStart(), ss.str())) + // writing failed, don't attempt to add ")" + return true; + + TheRewriter.InsertTextAfter(real_loc_end(stmt_to_inst), ")"); + ++rewrite_count; + + return true; +} + +bool +RewriteASTVisitor::VisitFunctionDecl(clang::FunctionDecl *f) +{ + // Only function definitions (with bodies), not declarations. + if (f->hasBody() == 0) + return true; + + clang::Stmt *FuncBody = f->getBody(); + + clang::DeclarationName DeclName = f->getNameInfo().getName(); + std::string FuncName = DeclName.getAsString(); + + if (FuncName.compare("main") != 0) + // Function is not main + return true; + + std::stringstream ss; + // On some platforms we need to depend directly on a symbol provided by + // the runtime. Normally this isn't needed because the runtime only + // depends on symbols in the isntrumented application. + ss << "libscv_init();"; + clang::SourceLocation curly_brace(FuncBody->getLocStart().getLocWithOffset(1)); + TheRewriter.InsertTextBefore(curly_brace, ss.str()); + + return true; +} + +clang::SourceLocation +RewriteASTVisitor::real_loc_end(clang::Stmt *d) +{ + clang::SourceLocation _e(d->getLocEnd()); + return clang::SourceLocation(clang::Lexer::getLocForEndOfToken(_e, 0, SM, lopt)); +} diff --git a/src/instrument_ast_visitor.h b/src/inst_ast_visitor.h diff --git a/src/inst_main.cc b/src/inst_main.cc @@ -0,0 +1,245 @@ +#include <err.h> +#include <fcntl.h> // open +#include <libgen.h> +#include <string.h> +#include <stdlib.h> // mkstemp, getenv +#include <stdio.h> // tmpnam +#include <unistd.h> // fork +#ifdef __gnu_linux__ + #include <bsd/stdlib.h> // setprogname +#endif +#include <sys/wait.h> // waitpid + +#include <fstream> +#include <iostream> +#include <sstream> +#include <string> + +#include <clang/Tooling/CommonOptionsParser.h> +#include <clang/Tooling/Tooling.h> + +#include "inst_action.h" + +#define STR_EXPAND(tok) #tok +#define STR(tok) STR_EXPAND(tok) + +static llvm::cl::OptionCategory ToolingCategory("instrument options"); + +void +clean_path() +{ + char *scv_path = getenv("CITRUN_PATH"); + char *path = getenv("PATH"); + if (scv_path == NULL) + errx(1, "CITRUN_PATH not found in environment, not running " + "native compiler"); + else if (path == NULL) + errx(1, "PATH not set, your build system needs to use " + "the PATH for this tool to be useful."); + + // Filter CITRUN_PATH out of PATH + std::stringstream path_ss(path); + std::ostringstream new_path; + std::string component; + bool first_component = 1; + + while (std::getline(path_ss, component, ':')) { + if (component.compare(scv_path) == 0) + continue; + + if (first_component == 0) + new_path << ":"; + + // It wasn't $CITRUN_PATH, keep it + new_path << component; + first_component = 0; + } + + // Set new $PATH + setenv("PATH", new_path.str().c_str(), 1); +} + +int +instrument(int argc, char *argv[], std::vector<std::string> const &source_files) +{ + if (source_files.size() == 0) + return 0; + + std::vector<const char *> clang_argv; + clang_argv.push_back(argv[0]); + for (auto s : source_files) + clang_argv.push_back(s.c_str()); + + clang_argv.push_back("--"); + + // Append original command line verbatim + clang_argv.insert(clang_argv.end(), argv, argv + argc); + + // When instrumenting certain code clang sometimes needs its own include + // files. These are defined globally per platform. + clang_argv.push_back(STR(CLANG_INCL)); + + // give clang it's <source files> -- <native command line> arg style + int clang_argc = clang_argv.size(); + clang::tooling::CommonOptionsParser op(clang_argc, &clang_argv[0], ToolingCategory); + clang::tooling::ClangTool Tool(op.getCompilations(), op.getSourcePathList()); + + // ClangTool::run accepts a FrontendActionFactory, which is then used to + // create new objects implementing the FrontendAction interface. Here we + // use the helper newFrontendActionFactory to create a default factory + // that will return a new MyFrontendAction object every time. To + // further customize this, we could create our own factory class. + // int ret = Tool.run(new MFAF(inst_files)); +#if LLVM_VER > 34 + int ret = Tool.run(&(*clang::tooling::newFrontendActionFactory<InstrumentAction>())); +#else + int ret = Tool.run(clang::tooling::newFrontendActionFactory<InstrumentAction>()); +#endif + return ret; +} + +bool +ends_with(std::string const &value, std::string const &suffix) +{ + if (suffix.length() > value.length()) + return false; + + return std::equal(suffix.rbegin(), suffix.rend(), value.rbegin()); +} + +void +copy_file(std::string dst_fn, std::string src_fn) +{ + std::ifstream src(src_fn, std::ios::binary); + std::ofstream dst(dst_fn, std::ios::binary); + + dst << src.rdbuf(); +} + +int +main(int argc, char *argv[]) +{ + // Set a better name than the symlink that was used to find this program + setprogname("citrun-inst"); + clean_path(); + + std::vector<char *> args(argv, argv + argc); + std::vector<std::string> source_files; + std::map<std::string, std::string> temp_file_map; + // Keep track of some "well known" compiler flags for later. + bool object_arg = false; + bool compile_arg = false; + + // Necessary because we're going to pass argv to execvp + argv[argc] = NULL; + + for (auto &arg : args) { + if (strcmp(arg, "-E") == 0) { + // Preprocessing argument found, exec native command + if (execvp(argv[0], argv)) + err(1, "execvp"); + } + else if (strcmp(arg, "-o") == 0) + object_arg = true; + else if (strcmp(arg, "-c") == 0) + compile_arg = true; + + // Find source files + if (ends_with(arg, ".c") || ends_with(arg, ".cc") || + ends_with(arg, ".cpp") || ends_with(arg, ".cxx")) { + + // Keep track of original source file names + source_files.push_back(arg); + + if (getenv("CITRUN_LEAVE_MODIFIED_SRC")) + // Don't copy and restore original source files + continue; + + char *dst_fn; + if ((dst_fn = tmpnam(NULL)) == NULL) + err(1, "tmpnam"); + + copy_file(dst_fn, arg); + temp_file_map[arg] = dst_fn; + } + } + + if (instrument(argc, argv, source_files)) { + // If instrumentation failed, then modified source files were + // not written. So no need to replace them. + warnx("Instrumentation failed, running unmodified command."); + if (execvp(argv[0], argv)) + err(1, "execvp"); + } + + bool linking = false; + if (!object_arg && !compile_arg && source_files.size() > 0) + // Assume single line a.out compilation + // $ gcc main.c + linking = true; + else if (object_arg && !compile_arg) + // gcc -o main main.o fib.o while.o + // gcc -o main main.c fib.c + linking = true; + + std::string last_node_path("LAST_NODE"); + if (linking) { + if (access(last_node_path.c_str(), F_OK)) { + // Couldn't access the LAST_NODE file, we cannot link + // to the runtime library without it. + warnx("LAST_NODE file not found."); + if (execvp(argv[0], argv)) + err(1, "execvp"); + } + + std::ifstream last_node_ifstream; + std::string last_node; + + last_node_ifstream.open(last_node_path, std::fstream::in); + last_node_ifstream >> last_node; + last_node_ifstream.close(); + + // We need to link the entry point in the runtime to the + // instrumented application. OS independent. + std::stringstream defsym_arg; +#ifdef __APPLE__ + defsym_arg << "-Wl,-alias,__citrun_node_"; + defsym_arg << last_node; + defsym_arg << ",__citrun_tu_head"; +#else + defsym_arg << "-Wl,--defsym=_citrun_tu_head=_citrun_node_"; + defsym_arg << last_node; +#endif + + // Add the runtime library and the symbol define hack + // automatically to the command line + args.push_back(strdup(defsym_arg.str().c_str())); + args.push_back(const_cast<char *>(STR(LIBCITRUN_PATH))); + } + + // Instrumentation succeeded. Run the native compiler with a modified + // command line. + args.push_back(NULL); + + pid_t child_pid; + if ((child_pid = fork()) < 0) + err(1, "fork"); + + if (child_pid == 0) { + // In child + if (execvp(args[0], &args[0])) + err(1, "execvp"); + } + + int status; + if (waitpid(child_pid, &status, 0) < 0) + err(1, "waitpid"); + + for (auto &tmp_file : temp_file_map) { + copy_file(tmp_file.first, tmp_file.second); + unlink(tmp_file.second.c_str()); + } + + if (linking) + unlink(last_node_path.c_str()); +} diff --git a/src/instrument_action.cc b/src/instrument_action.cc @@ -1,143 +0,0 @@ -#include <err.h> -#include <fcntl.h> // open -#include <limits.h> -#include <sys/stat.h> // mode flags -#include <unistd.h> // getcwd, access - -#include <fstream> -#include <iostream> -#include <sstream> -#include <string> - -#include <clang/Frontend/CompilerInstance.h> - -#include "instrument_action.h" -#include "runtime_h.h" - - -#if LLVM_VER > 35 -std::unique_ptr<clang::ASTConsumer> -#else -clang::ASTConsumer * -#endif -InstrumentAction::CreateASTConsumer(clang::CompilerInstance &CI, clang::StringRef file) -{ - // llvm::errs() << "** Creating AST consumer for: " << file << "\n"; - clang::SourceManager &sm = CI.getSourceManager(); - TheRewriter.setSourceMgr(sm, CI.getLangOpts()); - - // Hang onto a reference to this so we can read from it later - InstrumentASTConsumer = new RewriteASTConsumer(TheRewriter); -#if LLVM_VER > 35 - return std::unique_ptr<clang::ASTConsumer>(InstrumentASTConsumer); -#else - return InstrumentASTConsumer; -#endif -} - -std::string -get_current_node(std::string file_path) -{ - size_t last_slash = file_path.find_last_of('/'); - std::string fn(file_path.substr(last_slash + 1)); - - std::replace(fn.begin(), fn.end(), '.', '_'); - std::replace(fn.begin(), fn.end(), '-', '_'); - - return fn; -} - -std::string -swap_last_node(std::string curr_node) -{ - char *cwd = getcwd(NULL, PATH_MAX); - if (cwd == NULL) - errx(1, "getcwd"); - - std::string src_number_filename(cwd); - src_number_filename.append("/LAST_NODE"); - - std::string last_node("NULL"); - - if (access(src_number_filename.c_str(), F_OK) == 0) { - // LAST_NODE exists, read last_node from file - std::ifstream src_number_file; - src_number_file.open(src_number_filename, std::fstream::in); - src_number_file >> last_node; - src_number_file.close(); - } - - // Always write curr_node to file - std::ofstream src_number_file; - src_number_file.open(src_number_filename, std::fstream::out); - src_number_file << curr_node; - src_number_file.close(); - - return last_node; -} - -void -InstrumentAction::EndSourceFileAction() -{ - clang::SourceManager &sm = TheRewriter.getSourceMgr(); - const clang::FileID main_fid = sm.getMainFileID(); - // llvm::errs() << "** EndSourceFileAction for: " - // << sm.getFileEntryForID(main_fid)->getName() - // << "\n"; - - clang::SourceLocation start = sm.getLocForStartOfFile(main_fid); - clang::SourceLocation end = sm.getLocForEndOfFile(main_fid); - unsigned int num_lines = sm.getPresumedLineNumber(end); - - std::string file_name = getCurrentFile(); - std::string curr_node = get_current_node(file_name); - std::string last_node = swap_last_node(curr_node); - - //std::cerr << "LAST NODE = " << last_node << std::endl; - - std::stringstream ss; - // Add preprocessor stuff so that the C runtime library links against - // C++ object code. - ss << "#ifdef __cplusplus" << std::endl; - ss << "extern \"C\" {" << std::endl; - ss << "#endif" << std::endl; - - // Embed the header directly in the primary source file. - ss << runtime_h << std::endl; - - // Define storage for coverage data - ss << "static uint64_t _citrun_lines[" << num_lines << "];" << std::endl; - - // Get visitor instance to check how many times it rewrote something - RewriteASTVisitor visitor = InstrumentASTConsumer->get_visitor(); - - // Let the struct know this definition will be elsewhere - ss << "extern struct _citrun_node _citrun_node_" << last_node << ";" << std::endl; - - // Define this translation units main book keeping data structure - ss << "struct _citrun_node _citrun_node_" << curr_node << " = {" << std::endl - << " .lines_ptr = _citrun_lines," << std::endl - << " .size = " << num_lines << "," << std::endl - << " .inst_sites = " << visitor.GetRewriteCount() << "," << std::endl - << " .file_name = \"" << file_name << "\"," << std::endl; - if (last_node.compare("NULL") == 0) - ss << " .next = NULL," << std::endl; - else - ss << " .next = &_citrun_node_" << last_node << "," << std::endl; - ss << "};" << std::endl; - - // Close extern "C" { - ss << "#ifdef __cplusplus" << std::endl; - ss << "}" << std::endl; - ss << "#endif" << std::endl; - - TheRewriter.InsertTextAfter(start, ss.str()); - - int fd = open(file_name.c_str(), O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR); - if (fd < 0) - err(1, "open"); - llvm::raw_fd_ostream output(fd, /* close */ 1); - - // Write the instrumented source file - TheRewriter.getEditBuffer(main_fid).write(output); -} diff --git a/src/instrument_action.h b/src/instrument_action.h @@ -1,42 +0,0 @@ -#include <clang/AST/ASTConsumer.h> -#include <clang/Frontend/FrontendActions.h> -#include <clang/Rewrite/Core/Rewriter.h> - -#include "instrument_ast_visitor.h" - -class RewriteASTConsumer : public clang::ASTConsumer { -public: - RewriteASTConsumer(clang::Rewriter &R) : Visitor(R) {} - - // Override the method that gets called for each parsed top-level - // declaration. - bool HandleTopLevelDecl(clang::DeclGroupRef DR) override { - for (auto &b : DR) { - // Traverse the declaration using our AST visitor. - Visitor.TraverseDecl(b); - // b->dump(); - } - return true; - } - - RewriteASTVisitor& get_visitor() { return Visitor; }; -private: - RewriteASTVisitor Visitor; -}; - -// For each source file provided to the tool, a new FrontendAction is created. -class InstrumentAction : public clang::ASTFrontendAction { -public: - InstrumentAction() {}; - - void EndSourceFileAction() override; -#if LLVM_VER > 35 - std::unique_ptr<clang::ASTConsumer> CreateASTConsumer(clang::CompilerInstance &, clang::StringRef) override; -#else - clang::ASTConsumer *CreateASTConsumer(clang::CompilerInstance &, clang::StringRef) override; -#endif - -private: - clang::Rewriter TheRewriter; - RewriteASTConsumer *InstrumentASTConsumer; -}; diff --git a/src/instrument_ast_visitor.cc b/src/instrument_ast_visitor.cc @@ -1,94 +0,0 @@ -#include <sstream> -#include <string> - -#include <clang/AST/AST.h> -#include <clang/Lex/Lexer.h> - -#include "instrument_ast_visitor.h" - -bool -RewriteASTVisitor::VisitVarDecl(clang::VarDecl *d) -{ - return true; -} - -bool -RewriteASTVisitor::VisitStmt(clang::Stmt *s) -{ - std::stringstream ss; - unsigned line = SM.getPresumedLineNumber(s->getLocStart()); - clang::Stmt *stmt_to_inst = NULL; - - if (clang::isa<clang::IfStmt>(s)) { - stmt_to_inst = clang::cast<clang::IfStmt>(s)->getCond(); - } - else if (clang::isa<clang::ForStmt>(s)) { - stmt_to_inst = clang::cast<clang::ForStmt>(s)->getCond(); - } - else if (clang::isa<clang::WhileStmt>(s)) { - stmt_to_inst = clang::cast<clang::WhileStmt>(s)->getCond(); - } - else if (clang::isa<clang::SwitchStmt>(s)) { - stmt_to_inst = clang::cast<clang::SwitchStmt>(s)->getCond(); - } - else if (clang::isa<clang::ReturnStmt>(s)) { - stmt_to_inst = clang::cast<clang::ReturnStmt>(s)->getRetValue(); - } - /* - else if (isa<BreakStmt>(s) || isa<ContinueStmt>(s) || - || isa<SwitchCase>(s)) { - } - */ - else if (clang::isa<clang::DeclStmt>(s)) { - } - else if (clang::isa<clang::CallExpr>(s)) { - stmt_to_inst = s; - } - - if (stmt_to_inst == NULL) - return true; - - ss << "(++_citrun_lines[" << line << "], "; - if (TheRewriter.InsertTextBefore(stmt_to_inst->getLocStart(), ss.str())) - // writing failed, don't attempt to add ")" - return true; - - TheRewriter.InsertTextAfter(real_loc_end(stmt_to_inst), ")"); - ++rewrite_count; - - return true; -} - -bool -RewriteASTVisitor::VisitFunctionDecl(clang::FunctionDecl *f) -{ - // Only function definitions (with bodies), not declarations. - if (f->hasBody() == 0) - return true; - - clang::Stmt *FuncBody = f->getBody(); - - clang::DeclarationName DeclName = f->getNameInfo().getName(); - std::string FuncName = DeclName.getAsString(); - - if (FuncName.compare("main") != 0) - // Function is not main - return true; - - std::stringstream ss; - // On some platforms we need to depend directly on a symbol provided by - // the runtime. Normally this isn't needed because the runtime only - // depends on symbols in the isntrumented application. - ss << "libscv_init();"; - clang::SourceLocation curly_brace(FuncBody->getLocStart().getLocWithOffset(1)); - TheRewriter.InsertTextBefore(curly_brace, ss.str()); - - return true; -} - -clang::SourceLocation -RewriteASTVisitor::real_loc_end(clang::Stmt *d) -{ - clang::SourceLocation _e(d->getLocEnd()); - return clang::SourceLocation(clang::Lexer::getLocForEndOfToken(_e, 0, SM, lopt)); -} diff --git a/src/instrument_main.cc b/src/instrument_main.cc @@ -1,245 +0,0 @@ -#include <err.h> -#include <fcntl.h> // open -#include <libgen.h> -#include <string.h> -#include <stdlib.h> // mkstemp, getenv -#include <stdio.h> // tmpnam -#include <unistd.h> // fork -#ifdef __gnu_linux__ - #include <bsd/stdlib.h> // setprogname -#endif -#include <sys/wait.h> // waitpid - -#include <fstream> -#include <iostream> -#include <sstream> -#include <string> - -#include <clang/Tooling/CommonOptionsParser.h> -#include <clang/Tooling/Tooling.h> - -#include "instrument_action.h" - -#define STR_EXPAND(tok) #tok -#define STR(tok) STR_EXPAND(tok) - -static llvm::cl::OptionCategory ToolingCategory("instrument options"); - -void -clean_path() -{ - char *scv_path = getenv("CITRUN_PATH"); - char *path = getenv("PATH"); - if (scv_path == NULL) - errx(1, "CITRUN_PATH not found in environment, not running " - "native compiler"); - else if (path == NULL) - errx(1, "PATH not set, your build system needs to use " - "the PATH for this tool to be useful."); - - // Filter CITRUN_PATH out of PATH - std::stringstream path_ss(path); - std::ostringstream new_path; - std::string component; - bool first_component = 1; - - while (std::getline(path_ss, component, ':')) { - if (component.compare(scv_path) == 0) - continue; - - if (first_component == 0) - new_path << ":"; - - // It wasn't $CITRUN_PATH, keep it - new_path << component; - first_component = 0; - } - - // Set new $PATH - setenv("PATH", new_path.str().c_str(), 1); -} - -int -instrument(int argc, char *argv[], std::vector<std::string> const &source_files) -{ - if (source_files.size() == 0) - return 0; - - std::vector<const char *> clang_argv; - clang_argv.push_back(argv[0]); - for (auto s : source_files) - clang_argv.push_back(s.c_str()); - - clang_argv.push_back("--"); - - // Append original command line verbatim - clang_argv.insert(clang_argv.end(), argv, argv + argc); - - // When instrumenting certain code clang sometimes needs its own include - // files. These are defined globally per platform. - clang_argv.push_back(STR(CLANG_INCL)); - - // give clang it's <source files> -- <native command line> arg style - int clang_argc = clang_argv.size(); - clang::tooling::CommonOptionsParser op(clang_argc, &clang_argv[0], ToolingCategory); - clang::tooling::ClangTool Tool(op.getCompilations(), op.getSourcePathList()); - - // ClangTool::run accepts a FrontendActionFactory, which is then used to - // create new objects implementing the FrontendAction interface. Here we - // use the helper newFrontendActionFactory to create a default factory - // that will return a new MyFrontendAction object every time. To - // further customize this, we could create our own factory class. - // int ret = Tool.run(new MFAF(inst_files)); -#if LLVM_VER > 34 - int ret = Tool.run(&(*clang::tooling::newFrontendActionFactory<InstrumentAction>())); -#else - int ret = Tool.run(clang::tooling::newFrontendActionFactory<InstrumentAction>()); -#endif - return ret; -} - -bool -ends_with(std::string const &value, std::string const &suffix) -{ - if (suffix.length() > value.length()) - return false; - - return std::equal(suffix.rbegin(), suffix.rend(), value.rbegin()); -} - -void -copy_file(std::string dst_fn, std::string src_fn) -{ - std::ifstream src(src_fn, std::ios::binary); - std::ofstream dst(dst_fn, std::ios::binary); - - dst << src.rdbuf(); -} - -int -main(int argc, char *argv[]) -{ - // Set a better name than the symlink that was used to find this program - setprogname("citrun-inst"); - clean_path(); - - std::vector<char *> args(argv, argv + argc); - std::vector<std::string> source_files; - std::map<std::string, std::string> temp_file_map; - // Keep track of some "well known" compiler flags for later. - bool object_arg = false; - bool compile_arg = false; - - // Necessary because we're going to pass argv to execvp - argv[argc] = NULL; - - for (auto &arg : args) { - if (strcmp(arg, "-E") == 0) { - // Preprocessing argument found, exec native command - if (execvp(argv[0], argv)) - err(1, "execvp"); - } - else if (strcmp(arg, "-o") == 0) - object_arg = true; - else if (strcmp(arg, "-c") == 0) - compile_arg = true; - - // Find source files - if (ends_with(arg, ".c") || ends_with(arg, ".cc") || - ends_with(arg, ".cpp") || ends_with(arg, ".cxx")) { - - // Keep track of original source file names - source_files.push_back(arg); - - if (getenv("CITRUN_LEAVE_MODIFIED_SRC")) - // Don't copy and restore original source files - continue; - - char *dst_fn; - if ((dst_fn = tmpnam(NULL)) == NULL) - err(1, "tmpnam"); - - copy_file(dst_fn, arg); - temp_file_map[arg] = dst_fn; - } - } - - if (instrument(argc, argv, source_files)) { - // If instrumentation failed, then modified source files were - // not written. So no need to replace them. - warnx("Instrumentation failed, running unmodified command."); - if (execvp(argv[0], argv)) - err(1, "execvp"); - } - - bool linking = false; - if (!object_arg && !compile_arg && source_files.size() > 0) - // Assume single line a.out compilation - // $ gcc main.c - linking = true; - else if (object_arg && !compile_arg) - // gcc -o main main.o fib.o while.o - // gcc -o main main.c fib.c - linking = true; - - std::string last_node_path("LAST_NODE"); - if (linking) { - if (access(last_node_path.c_str(), F_OK)) { - // Couldn't access the LAST_NODE file, we cannot link - // to the runtime library without it. - warnx("LAST_NODE file not found."); - if (execvp(argv[0], argv)) - err(1, "execvp"); - } - - std::ifstream last_node_ifstream; - std::string last_node; - - last_node_ifstream.open(last_node_path, std::fstream::in); - last_node_ifstream >> last_node; - last_node_ifstream.close(); - - // We need to link the entry point in the runtime to the - // instrumented application. OS independent. - std::stringstream defsym_arg; -#ifdef __APPLE__ - defsym_arg << "-Wl,-alias,__citrun_node_"; - defsym_arg << last_node; - defsym_arg << ",__citrun_tu_head"; -#else - defsym_arg << "-Wl,--defsym=_citrun_tu_head=_citrun_node_"; - defsym_arg << last_node; -#endif - - // Add the runtime library and the symbol define hack - // automatically to the command line - args.push_back(strdup(defsym_arg.str().c_str())); - args.push_back(const_cast<char *>(STR(LIBCITRUN_PATH))); - } - - // Instrumentation succeeded. Run the native compiler with a modified - // command line. - args.push_back(NULL); - - pid_t child_pid; - if ((child_pid = fork()) < 0) - err(1, "fork"); - - if (child_pid == 0) { - // In child - if (execvp(args[0], &args[0])) - err(1, "execvp"); - } - - int status; - if (waitpid(child_pid, &status, 0) < 0) - err(1, "waitpid"); - - for (auto &tmp_file : temp_file_map) { - copy_file(tmp_file.first, tmp_file.second); - unlink(tmp_file.second.c_str()); - } - - if (linking) - unlink(last_node_path.c_str()); -}