citrun

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

commit 4044fe3f56d911a9d476cce474264c057cc17bef
parent 384359e73cc57b35bca5f6eebf66904304f3dec9
Author: Kyle Milz <kyle@0x30.net>
Date:   Sat, 30 Jul 2016 22:49:52 -0600

src: add CitrunInst class

Diffstat:
Msrc/inst_main.cc | 304++++++++++++++++++++++++++++++++++++++++++-------------------------------------
1 file changed, 163 insertions(+), 141 deletions(-)

diff --git a/src/inst_main.cc b/src/inst_main.cc @@ -36,57 +36,115 @@ static llvm::cl::OptionCategory ToolingCategory("instrument options"); -void -clean_path() +class CitrunInst { +public: + CitrunInst(int, char *argv[]); + int instrument(); + void patch_link_command(); + int fork_compiler(); + void restore_original_src(); + +private: + std::vector<char *> m_args; + std::vector<std::string> m_source_files; + std::map<std::string, std::string> m_temp_file_map; + bool m_object_arg; + bool m_compile_arg; +}; + +// Returns true if value ends with suffix, false otherwise. +static bool +ends_with(std::string const &value, std::string const &suffix) { - char *path; + if (suffix.length() > value.length()) + return false; - if ((path = std::getenv("PATH")) == NULL) - errx(1, "PATH must be set"); + return std::equal(suffix.rbegin(), suffix.rend(), value.rbegin()); +} - // Filter CITRUN_PATH out of PATH - std::stringstream path_ss(path); - std::ostringstream new_path; - std::string component; - bool first_component = 1; - bool found_citrun_path = 0; +// Copies one file to another preserving timestamps. +static void +copy_file(std::string const &dst_fn, std::string const &src_fn) +{ + struct stat sb; + struct timeval st_tim[2]; - while (std::getline(path_ss, component, ':')) { - if (component.compare(STR(CITRUN_PATH)) == 0) { - found_citrun_path = 1; - continue; + // Save original access and modification times + stat(src_fn.c_str(), &sb); +#ifdef __APPLE__ + TIMESPEC_TO_TIMEVAL(&st_tim[0], &sb.st_atimespec); + TIMESPEC_TO_TIMEVAL(&st_tim[1], &sb.st_mtimespec); +#else + TIMESPEC_TO_TIMEVAL(&st_tim[0], &sb.st_atim); + TIMESPEC_TO_TIMEVAL(&st_tim[1], &sb.st_mtim); +#endif + + std::ifstream src(src_fn, std::ios::binary); + std::ofstream dst(dst_fn, std::ios::binary); + + dst << src.rdbuf(); + + src.close(); + dst.close(); + + // Restore the original access and modification time + utimes(dst_fn.c_str(), st_tim); +} + +CitrunInst::CitrunInst(int argc, char *argv[]) : + m_args(argv, argv + argc), + m_object_arg(false), + m_compile_arg(false) +{ + for (auto &arg : m_args) { + if (std::strcmp(arg, "-E") == 0) { + // Preprocessing argument found, exec native command + m_args.push_back(NULL); + if (execvp(m_args[0], &m_args[0])) + err(1, "execvp"); } + else if (std::strcmp(arg, "-o") == 0) + m_object_arg = true; + else if (std::strcmp(arg, "-c") == 0) + m_compile_arg = true; - if (first_component == 0) - new_path << ":"; + // Find source files + if (ends_with(arg, ".c") || ends_with(arg, ".cc") || + ends_with(arg, ".cpp") || ends_with(arg, ".cxx")) { - // It wasn't CITRUN_PATH, keep it - new_path << component; - first_component = 0; - } + // Keep track of original source file names + m_source_files.push_back(arg); - if (!found_citrun_path) - errx(1, "'%s' not in PATH", STR(CITRUN_PATH)); + if (std::getenv("CITRUN_TESTING")) + // Don't copy and restore original source files + continue; - // Set new $PATH - setenv("PATH", new_path.str().c_str(), 1); + char *dst_fn; + if ((dst_fn = std::tmpnam(NULL)) == NULL) + err(1, "tmpnam"); + + copy_file(dst_fn, arg); + m_temp_file_map[arg] = dst_fn; + } + } } int -instrument(int argc, char *argv[], std::vector<std::string> const &source_files) +CitrunInst::instrument() { - if (source_files.size() == 0) + std::vector<const char *> clang_argv; + + if (m_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(m_args[0]); + for (auto s : m_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); + clang_argv.insert(clang_argv.end(), m_args.begin(), m_args.end()); // We should be able to get this programmatically, but I don't know how. #if defined(__OpenBSD__) @@ -107,71 +165,64 @@ instrument(int argc, char *argv[], std::vector<std::string> const &source_files) // further customize this, we could create our own factory class. // int ret = Tool.run(new MFAF(inst_files)); int ret = Tool.run(&(*clang::tooling::newFrontendActionFactory<InstrumentAction>())); - 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 const &dst_fn, std::string const &src_fn) -{ - struct stat sb; - struct timeval st_tim[2]; - - // Save original access and modification times - stat(src_fn.c_str(), &sb); -#ifdef __APPLE__ - TIMESPEC_TO_TIMEVAL(&st_tim[0], &sb.st_atimespec); - TIMESPEC_TO_TIMEVAL(&st_tim[1], &sb.st_mtimespec); -#else - TIMESPEC_TO_TIMEVAL(&st_tim[0], &sb.st_atim); - TIMESPEC_TO_TIMEVAL(&st_tim[1], &sb.st_mtim); -#endif - std::ifstream src(src_fn, std::ios::binary); - std::ofstream dst(dst_fn, std::ios::binary); + if (ret == 0) + return 0; - dst << src.rdbuf(); + // Instrumentation failed, try compiling with native compiler. + restore_original_src(); + ret = fork_compiler(); - src.close(); - dst.close(); + // Native compiler succeeded. This is bad. + if (ret == 0) { + warnx("citrun instrumentation failed, but the native " + "compile succeeded!"); + return 0; + } - // Restore the original access and modification time - utimes(dst_fn.c_str(), st_tim); + // Native compiler failed too. That's okay. + return ret; } void -restore_original_src(std::map<std::string, std::string> const &temp_file_map) +CitrunInst::restore_original_src() { - for (auto &tmp_file : temp_file_map) { + for (auto &tmp_file : m_temp_file_map) { copy_file(tmp_file.first, tmp_file.second); unlink(tmp_file.second.c_str()); } } void -patch_link_command(std::vector<char *> &args) +CitrunInst::patch_link_command() { + bool linking = false; + + if (!m_object_arg && !m_compile_arg && m_source_files.size() > 0) + // Assume single line a.out compilation + // $ gcc main.c + linking = true; + else if (m_object_arg && !m_compile_arg) + // gcc -o main main.o fib.o while.o + // gcc -o main main.c fib.c + linking = true; + + if (!linking) + return; + // libcitrun.a uses pthread so we must link it here. #ifndef __APPLE__ // Except Mac OS, who always links this. - args.push_back(const_cast<char *>("-pthread")); + m_args.push_back(const_cast<char *>("-pthread")); #endif - args.push_back(const_cast<char *>(STR(CITRUN_LIB))); + m_args.push_back(const_cast<char *>(STR(CITRUN_LIB))); } int -fork_compiler(std::vector<char *> &args) +CitrunInst::fork_compiler() { - // args must be NULL terminated for exec*() functions. - args.push_back(NULL); + // m_args must be NULL terminated for exec*() functions. + m_args.push_back(NULL); pid_t child_pid; if ((child_pid = fork()) < 0) @@ -179,7 +230,7 @@ fork_compiler(std::vector<char *> &args) if (child_pid == 0) { // In child - if (execvp(args[0], &args[0])) + if (execvp(m_args[0], &m_args[0])) err(1, "execvp"); } @@ -194,87 +245,58 @@ fork_compiler(std::vector<char *> &args) return -1; } -int -main(int argc, char *argv[]) +void +clean_path() { - // We probably didn't call citrun-inst directly. - setprogname("citrun-inst"); - clean_path(); + char *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; + if ((path = std::getenv("PATH")) == NULL) + errx(1, "PATH must be set"); - // Necessary because we're going to pass argv to execvp - argv[argc] = NULL; + // Filter CITRUN_PATH out of PATH + std::stringstream path_ss(path); + std::ostringstream new_path; + std::string component; + bool first_component = 1; + bool found_citrun_path = 0; - for (auto &arg : args) { - if (std::strcmp(arg, "-E") == 0) { - // Preprocessing argument found, exec native command - if (execvp(argv[0], argv)) - err(1, "execvp"); + while (std::getline(path_ss, component, ':')) { + if (component.compare(STR(CITRUN_PATH)) == 0) { + found_citrun_path = 1; + continue; } - else if (std::strcmp(arg, "-o") == 0) - object_arg = true; - else if (std::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 (std::getenv("CITRUN_TESTING")) - // Don't copy and restore original source files - continue; - char *dst_fn; - if ((dst_fn = std::tmpnam(NULL)) == NULL) - err(1, "tmpnam"); + if (first_component == 0) + new_path << ":"; - copy_file(dst_fn, arg); - temp_file_map[arg] = dst_fn; - } + // It wasn't CITRUN_PATH, keep it + new_path << component; + first_component = 0; } - if (instrument(argc, argv, source_files)) { - // Instrumentation failed. - restore_original_src(temp_file_map); - - // Try compile with native compiler. - int ret = fork_compiler(args); + if (!found_citrun_path) + errx(1, "'%s' not in PATH", STR(CITRUN_PATH)); - // This is bad. - if (ret == 0) { - warnx("citrun instrumentation failed, but the native" - "compile succeeded!"); - return 0; - } + // Set new $PATH + if (setenv("PATH", new_path.str().c_str(), 1)) + err(1, "setenv"); +} - // Native compiler failed too. That's okay. - return ret; - } +int +main(int argc, char *argv[]) +{ + // We probably didn't call citrun-inst directly. + setprogname("citrun-inst"); + clean_path(); - 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; + CitrunInst main(argc, argv); - if (linking) - patch_link_command(args); + if (main.instrument()) + return 1; - int ret = fork_compiler(args); - restore_original_src(temp_file_map); + main.patch_link_command(); + int ret = main.fork_compiler(); + main.restore_original_src(); return ret; }