citrun

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

commit 683ec2c97c1c74fda6402ebc2aaa51367960caa1
parent 509b9b5121a02df42717f13ba77861c136fe8a25
Author: Kyle Milz <kyle@getaddrinfo.net>
Date:   Thu,  7 Apr 2016 22:54:12 -0600

src: fundamentally rework how instrumentation works

- the per translation unit book keeping struct is now named after the source
  file name, and not a monotonically increasing number
  - this has advantages, namely objects compiled in different directories can be
    combined without symbol name collisions
- don't rely on ELF common symbols for the whole scheme to work
  - these common symbols allow multiple definitions in different translation
    units to be linked together successfully
  - unfortunately this is a fortran-compatible feature and C++ _never_ emits
    common symbols
- instead use symbol chaining and abuse the --defsym linker arg
  - --defsym allows the entry point in the runtime library to be bound to the
    last link in the instrumented translation unit chain
- also rename stuff from _scv_* to _citrun_*

Diffstat:
MSCV/Project.pm | 6+++---
Mlib/runtime.c | 22+++++++++++++++-------
Mlib/runtime.h | 4++--
Msrc/instrument_action.cc | 68+++++++++++++++++++++++++++++++++++++++++---------------------------
Msrc/instrument_ast_visitor.cc | 2+-
Msrc/instrument_main.cc | 38++++++++++++++++----------------------
Msrc/runtime_h.h | 5+++--
Mt/fibonacci.t | 22+++++++++++-----------
Mt/for.t | 4++--
Mt/hello_world.t | 4++--
Mt/if.t | 16++++++++--------
Mt/inst_preamble.t | 15++++++++-------
Mt/return.t | 8++++----
Mt/runtime_sanity.t | 12++++++------
Mt/switch.t | 4++--
Mt/while.t | 4++--
16 files changed, 126 insertions(+), 108 deletions(-)

diff --git a/SCV/Project.pm b/SCV/Project.pm @@ -46,8 +46,8 @@ EOF syswrite( $jamfile_fh, $jamfile ); close( $jamfile_fh ); + # Use the tools in this source tree my $cwd = getcwd; - $ENV{CITRUN_PATH} = "$cwd/share"; $ENV{PATH} = "$ENV{CITRUN_PATH}:$ENV{PATH}"; @@ -61,7 +61,7 @@ sub instrumented_src { open( my $inst_fh, "<", "$self->{tmp_dir}/inst/source_0.c" ); # Knock off the instrumentation preamble - my $line = <$inst_fh> for (1..25); + my $line = <$inst_fh> for (1..26); my $inst_src; while (my $line = <$inst_fh>) { @@ -77,7 +77,7 @@ sub inst_src_preamble { open( my $inst_fh, "<", "$self->{tmp_dir}/inst/source_0.c" ); my $preamble; - for (1..25) { + for (1..26) { my $line = <$inst_fh>; $preamble .= $line; } diff --git a/lib/runtime.c b/lib/runtime.c @@ -15,7 +15,7 @@ #include "runtime.h" /* Entry point into instrumented application */ -extern struct _scv_node _scv_node0; +extern struct _citrun_node _citrun_tu_head; void send_metadata(int); void send_execution_data(int); @@ -78,14 +78,17 @@ control_thread(void *arg) void send_metadata(int fd) { - struct _scv_node walk = _scv_node0; + struct _citrun_node walk = _citrun_tu_head; pid_t process_id, parent_process_id, process_group; uint64_t num_tus = 0; size_t file_name_sz; /* Send the total number of translation unit records we'll send later */ - while (walk.size != 0) { + while (1) { ++num_tus; + + if (walk.next == NULL) + break; walk = *walk.next; } xwrite(fd, &num_tus, sizeof(num_tus)); @@ -100,9 +103,9 @@ send_metadata(int fd) xwrite(fd, &parent_process_id, sizeof(pid_t)); xwrite(fd, &process_group, sizeof(pid_t)); - walk = _scv_node0; + walk = _citrun_tu_head; /* Send translation unit records */ - while (walk.size != 0) { + while (1) { /* Send file name size and then the file name itself. */ file_name_sz = strnlen(walk.file_name, PATH_MAX); xwrite(fd, &file_name_sz, sizeof(file_name_sz)); @@ -114,6 +117,8 @@ send_metadata(int fd) /* Send the total number of instrumentation sites */ xwrite(fd, &walk.inst_sites, sizeof(walk.size)); + if (walk.next == NULL) + break; walk = *walk.next; } } @@ -125,11 +130,14 @@ send_metadata(int fd) void send_execution_data(int fd) { - struct _scv_node walk = _scv_node0; + struct _citrun_node walk = _citrun_tu_head; - while (walk.size != 0) { + while (1) { /* Write execution buffer, one 8 byte counter per source line */ xwrite(fd, walk.lines_ptr, walk.size * sizeof(uint64_t)); + + if (walk.next == NULL) + break; walk = *walk.next; } } diff --git a/lib/runtime.h b/lib/runtime.h @@ -1,9 +1,9 @@ #include <stdint.h> -struct _scv_node { +struct _citrun_node { uint64_t *lines_ptr; uint32_t size; uint32_t inst_sites; const char *file_name; - struct _scv_node *next; + struct _citrun_node *next; }; void libscv_init(); diff --git a/src/instrument_action.cc b/src/instrument_action.cc @@ -35,46 +35,55 @@ InstrumentAction::CreateASTConsumer(clang::CompilerInstance &CI, clang::StringRe #endif } -unsigned int -read_src_number() +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(), '.', '_'); + + return fn; +} + +std::string +get_last_node() { char *cwd = getcwd(NULL, PATH_MAX); if (cwd == NULL) errx(1, "getcwd"); std::string src_number_filename(cwd); - src_number_filename.append("/SRC_NUMBER"); + src_number_filename.append("/LAST_NODE"); - if (access(src_number_filename.c_str(), F_OK) == -1) { - // SRC_NUMBER does not exist, source number is 0 - return 0; - } + if (access(src_number_filename.c_str(), F_OK) == -1) + // No LAST_NODE, the .next pointer will be NULL + return std::string("NULL"); - // SRC_NUMBER exists, read its content + // LAST_NODE exists, read its content std::ifstream src_number_file; - unsigned int src_num = 0; + std::string last_node; src_number_file.open(src_number_filename, std::fstream::in); - src_number_file >> src_num; + src_number_file >> last_node; src_number_file.close(); - // Pre-increment. The current source number is the last one plus one - return ++src_num; + return last_node; } void -write_src_number(int src_num) +set_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("/SRC_NUMBER"); + src_number_filename.append("/LAST_NODE"); std::ofstream src_number_file; src_number_file.open(src_number_filename, std::fstream::out); - src_number_file << src_num; + src_number_file << curr_node; src_number_file.close(); } @@ -92,7 +101,10 @@ InstrumentAction::EndSourceFileAction() unsigned int num_lines = sm.getPresumedLineNumber(end); std::string file_name = getCurrentFile(); - unsigned int tu_number = read_src_number(); + std::string last_node = get_last_node(); + std::string curr_node = get_current_node(file_name); + + //std::cerr << "LAST NODE = " << last_node << std::endl; std::stringstream ss; // Add preprocessor stuff so that the C runtime library links against @@ -105,23 +117,25 @@ InstrumentAction::EndSourceFileAction() ss << runtime_h << std::endl; // Define storage for coverage data - ss << "static uint64_t _scv_lines[" << num_lines << "];" << std::endl; - - // Always declare this. The next TU will overwrite this or there won't - // be a next TU. - ss << "struct _scv_node _scv_node" << tu_number + 1 << ";" << std::endl; + 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 _scv_node _scv_node" << tu_number << " = {" << std::endl - << " .lines_ptr = _scv_lines," << std::endl + 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 - << " .next = &_scv_node" << tu_number + 1 << "," << std::endl - << "};" << 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; @@ -152,5 +166,5 @@ InstrumentAction::EndSourceFileAction() // Write the instrumented source file TheRewriter.getEditBuffer(main_fid).write(output); - write_src_number(tu_number); + set_last_node(curr_node); } diff --git a/src/instrument_ast_visitor.cc b/src/instrument_ast_visitor.cc @@ -48,7 +48,7 @@ RewriteASTVisitor::VisitStmt(clang::Stmt *s) if (stmt_to_inst == NULL) return true; - ss << "(++_scv_lines[" << line << "], "; + ss << "(++_citrun_lines[" << line << "], "; if (TheRewriter.InsertTextBefore(stmt_to_inst->getLocStart(), ss.str())) // writing failed, don't attempt to add ")" return true; diff --git a/src/instrument_main.cc b/src/instrument_main.cc @@ -167,7 +167,7 @@ main(int argc, char *argv[]) // Instrument source files found on the command line if (instrument(argc, argv, source_files)) { - // Instrumentation failed, exec native command + warnx("instrumentation failed, running unmodified command"); if (execvp(argv[0], argv)) err(1, "execvp"); } @@ -185,36 +185,30 @@ main(int argc, char *argv[]) linking = true; if (linking) { - char *cwd = getcwd(NULL, PATH_MAX); - if (cwd == NULL) - errx(1, "getcwd"); - - std::string src_number_filename(cwd); - src_number_filename.append("/SRC_NUMBER"); - - if (access(src_number_filename.c_str(), F_OK)) { - // Couldn't access the SRC_NUMBER file, we cannot link + std::string last_node_path("LAST_NODE"); + 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("SRC_NUMBER file not found."); + warnx("LAST_NODE file not found."); if (execvp(argv[0], argv)) err(1, "execvp"); } - std::ifstream src_number_file; - std::string src_number_buffer; - src_number_file.open(src_number_filename, std::fstream::in); - src_number_file >> src_number_buffer; - src_number_file.close(); + 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(); - std::stringstream src_number; - src_number << "-Wl,--defsym=\"_scv_node0="; - src_number << src_number_buffer; - src_number << "\""; + std::stringstream defsym_arg; + defsym_arg << "-Wl,--defsym=_citrun_tu_head=_citrun_node_"; + defsym_arg << last_node; // Add the runtime library and the symbol define hack // automatically to the command line - modified_args.push_back("/home/kyle/citrun/lib/libcitrun.so.0.0"); - //modified_args.push_back(const_cast<char *>(src_number.str().c_str())); + modified_args.push_back(strdup(defsym_arg.str().c_str())); + modified_args.push_back(const_cast<char *>("/home/kyle/citrun/lib/libcitrun.so.0.0")); } // Instrumentation succeeded. Run the native compiler with a modified diff --git a/src/runtime_h.h b/src/runtime_h.h @@ -1,11 +1,12 @@ static const char runtime_h[] = "#include <stdint.h>\n" -"struct _scv_node {\n" +"#include <stddef.h>\n" +"struct _citrun_node {\n" " uint64_t *lines_ptr;\n" " uint32_t size;\n" " uint32_t inst_sites;\n" " const char *file_name;\n" -" struct _scv_node *next;\n" +" struct _citrun_node *next;\n" "};\n" "void libscv_init();\n" ; diff --git a/t/fibonacci.t b/t/fibonacci.t @@ -50,12 +50,12 @@ my $inst_src_good = <<EOF; long long fibonacci(long long n) { - if ((++_scv_lines[7], n == 0)) - return (++_scv_lines[8], 0); - else if ((++_scv_lines[9], n == 1)) - return (++_scv_lines[10], 1); + if ((++_citrun_lines[7], n == 0)) + return (++_citrun_lines[8], 0); + else if ((++_citrun_lines[9], n == 1)) + return (++_citrun_lines[10], 1); - return (++_scv_lines[12], (++_scv_lines[12], fibonacci(n - 1)) + (++_scv_lines[12], fibonacci(n - 2))); + return (++_citrun_lines[12], (++_citrun_lines[12], fibonacci(n - 1)) + (++_citrun_lines[12], fibonacci(n - 2))); } int @@ -63,16 +63,16 @@ main(int argc, char *argv[]) {libscv_init(); long long n; - if ((++_scv_lines[20], argc != 2)) { - (++_scv_lines[21], printf("usage: %s <N>", argv[0])); - return (++_scv_lines[22], 1); + if ((++_citrun_lines[20], argc != 2)) { + (++_citrun_lines[21], printf("usage: %s <N>", argv[0])); + return (++_citrun_lines[22], 1); } - n = (++_scv_lines[25], atoi(argv[1])); + n = (++_citrun_lines[25], atoi(argv[1])); - (++_scv_lines[27], printf("result: %lli", (++_scv_lines[27], fibonacci(n)))); + (++_citrun_lines[27], printf("result: %lli", (++_citrun_lines[27], fibonacci(n)))); - return (++_scv_lines[29], 0); + return (++_citrun_lines[29], 0); } EOF diff --git a/t/for.t b/t/for.t @@ -30,11 +30,11 @@ main(void) {libscv_init(); int i; - for (i = 0; (++_scv_lines[6], i < 19); i++) { + for (i = 0; (++_citrun_lines[6], i < 19); i++) { i++; } - return (++_scv_lines[10], i); + return (++_citrun_lines[10], i); } EOF diff --git a/t/hello_world.t b/t/hello_world.t @@ -27,8 +27,8 @@ my $inst_src_good = <<EOF; int main(void) {libscv_init(); - (++_scv_lines[6], printf("hello, world!")); - return (++_scv_lines[7], 0); + (++_citrun_lines[6], printf("hello, world!")); + return (++_citrun_lines[7], 0); } EOF diff --git a/t/if.t b/t/if.t @@ -37,19 +37,19 @@ my $inst_src_good = <<EOF; int main(int argc, char *argv[]) {libscv_init(); - if ((++_scv_lines[6], argc == 1)) - return (++_scv_lines[7], 1); + if ((++_citrun_lines[6], argc == 1)) + return (++_citrun_lines[7], 1); else - (++_scv_lines[9], exit(14)); + (++_citrun_lines[9], exit(14)); - if ((++_scv_lines[11], argc == 2)) { - return (++_scv_lines[12], 5); + if ((++_citrun_lines[11], argc == 2)) { + return (++_citrun_lines[12], 5); } - else if ((++_scv_lines[14], argc == 3)) { - return (++_scv_lines[15], 0); + else if ((++_citrun_lines[14], argc == 3)) { + return (++_citrun_lines[15], 0); } else { - (++_scv_lines[18], exit(0)); + (++_citrun_lines[18], exit(0)); } } EOF diff --git a/t/inst_preamble.t b/t/inst_preamble.t @@ -26,23 +26,24 @@ my $preamble_good = <<EOF; extern "C" { #endif #include <stdint.h> -struct _scv_node { +#include <stddef.h> +struct _citrun_node { uint64_t *lines_ptr; uint32_t size; uint32_t inst_sites; const char *file_name; - struct _scv_node *next; + struct _citrun_node *next; }; void libscv_init(); -static uint64_t _scv_lines[6]; -struct _scv_node _scv_node1; -struct _scv_node _scv_node0 = { - .lines_ptr = _scv_lines, +static uint64_t _citrun_lines[6]; +extern struct _citrun_node _citrun_node_NULL; +struct _citrun_node _citrun_node_source_0_c = { + .lines_ptr = _citrun_lines, .size = 6, .inst_sites = 1, .file_name = "$tmp_dir/source_0.c", - .next = &_scv_node1, + .next = NULL, }; #ifdef __cplusplus } diff --git a/t/return.t b/t/return.t @@ -26,15 +26,15 @@ $project->compile(); my $inst_src_good = <<EOF; int foo() { - return (++_scv_lines[2], 0); + return (++_citrun_lines[2], 0); } int main(void) {libscv_init(); - return (++_scv_lines[6], 10); + return (++_citrun_lines[6], 10); - return (++_scv_lines[8], 10 + 10); + return (++_citrun_lines[8], 10 + 10); - return (++_scv_lines[10], (++_scv_lines[10], foo())); + return (++_citrun_lines[10], (++_citrun_lines[10], foo())); } EOF diff --git a/t/runtime_sanity.t b/t/runtime_sanity.t @@ -65,22 +65,22 @@ my $runtime_metadata = $viewer->get_metadata(); my $tus = $runtime_metadata->{tus}; my ($source_0, $source_1, $source_2) = @$tus; -like( $source_0->{filename}, qr/.*source_0.c/, "runtime filename check 0" ); -is( $source_0->{lines}, 20, "runtime line count check 0" ); +like( $source_0->{filename}, qr/.*source_2.c/, "runtime filename check 0" ); +is( $source_0->{lines}, 9, "runtime line count check 0" ); #is( $source_0->{inst_sites}, 7, "instrumented site count 0" ); like( $source_1->{filename}, qr/.*source_1.c/, "runtime filename check 1" ); is( $source_1->{lines}, 11, "runtime line count check 1" ); #is( $source_1->{inst_sites}, 7, "instrumented site count 1" ); -like( $source_2->{filename}, qr/.*source_2.c/, "runtime filename check 2" ); -is( $source_2->{lines}, 9, "runtime line count check 2" ); +like( $source_2->{filename}, qr/.*source_0.c/, "runtime filename check 2" ); +is( $source_2->{lines}, 20, "runtime line count check 2" ); #is( $source_2->{inst_sites}, 6, "instrumented site count 2" ); # Request and check execution data my $data = $viewer->get_execution_data($tus); -my @lines = @{ $data->[0] }; +my @lines = @{ $data->[2] }; is ( $lines[$_], 0, "src 0 line $_ check" ) for (1..11); is ( $lines[12], 1, "src 0 line 14 check" ); is ( $lines[$_], 0, "src 0 line $_ check" ) for (13..14); @@ -94,7 +94,7 @@ is ( $lines[$_], 0, "src 1 line $_ check" ) for (0..3); cmp_ok ( $lines[$_], ">", 10, "src 1 line $_ check" ) for (4..7); is ( $lines[8], 0, "src 1 line 8 check" ); -my @lines = @{ $data->[2] }; +my @lines = @{ $data->[0] }; is ( $lines[$_], 0, "src 2 line $_ check" ) for (0..8); $project->kill(); diff --git a/t/switch.t b/t/switch.t @@ -33,14 +33,14 @@ main(void) {libscv_init(); int i; - switch ((++_scv_lines[6], i)) { + switch ((++_citrun_lines[6], i)) { case 0: break; case 1: break; } - return (++_scv_lines[13], 0); + return (++_citrun_lines[13], 0); } EOF diff --git a/t/while.t b/t/while.t @@ -32,11 +32,11 @@ main(void) int i; i = 0; - while ((++_scv_lines[7], i < 17)) { + while ((++_citrun_lines[7], i < 17)) { i++; } - return (++_scv_lines[11], i); + return (++_citrun_lines[11], i); } EOF