CCTLib
Calling-context and data-object attribution library for Intel Pin
recursion_integration_test.cpp
Go to the documentation of this file.
1 // Integration tests for cctlib's direct-self-recursion collapse.
2 //
3 // Each test launches Pin with clients/obj-intel64/cct_shape_check.so
4 // against one recursion victim, passing -check <victim_name>. The
5 // check tool walks the CCT programmatically at ThreadFini time (while
6 // images are still loaded so IPs resolve), builds an in-memory
7 // inventory of every reached call-chain, and applies per-victim
8 // assertions coded IN the tool. Exit code 0 iff every assertion
9 // passed; on failure the tool writes a diagnostic to stderr with the
10 // assertion name, expected vs observed values, and the inventory
11 // summary. We surface that stderr in the gtest failure message.
12 //
13 // Correctness signal: assertions target chainCountForFn("<fn>") --
14 // the number of distinct root-to-leaf function-name chains ending in
15 // the recursive routine. After collapse this is O(1); without
16 // collapse it grows with recursion depth. Sensitivity was verified
17 // by temporarily disabling the collapse gate in cctlib.cpp and
18 // rerunning -- 5 of 8 assertions flipped from pass to fail with
19 // chain counts jumping from 1 to 15-20.
20 
21 #include <cerrno>
22 #include <cstdio>
23 #include <cstdlib>
24 #include <cstring>
25 #include <fcntl.h>
26 #include <fstream>
27 #include <sstream>
28 #include <string>
29 #include <sys/stat.h>
30 #include <sys/wait.h>
31 #include <unistd.h>
32 #include <vector>
33 
34 #include <gtest/gtest.h>
35 
36 namespace {
37 
38 std::string env(const char* n) { const char* v = getenv(n); return v ? v : ""; }
39 std::string cctlib_root() {
40  std::string r = env("CCTLIB_ROOT");
41  return r.empty() ? "../.." : r;
42 }
43 std::string pin_root() { return env("PIN_ROOT"); }
44 
45 // Run pin with the given tool + args against the victim; capture the
46 // tool's stderr into a temp file so we can attach it to a gtest
47 // failure message. Returns pin's exit code; if capture failed, *stderrOut
48 // is left empty.
49 int run_pin_capture(const std::string& tool,
50  const std::vector<std::string>& toolArgs,
51  const std::string& victim,
52  std::string* stderrOut) {
53  std::string pin = pin_root() + "/pin";
54  std::vector<char*> argv;
55  argv.push_back(const_cast<char*>(pin.c_str()));
56  argv.push_back(const_cast<char*>("-t"));
57  argv.push_back(const_cast<char*>(tool.c_str()));
58  for (auto& a : toolArgs) argv.push_back(const_cast<char*>(a.c_str()));
59  argv.push_back(const_cast<char*>("--"));
60  argv.push_back(const_cast<char*>(victim.c_str()));
61  argv.push_back(nullptr);
62 
63  char errPath[] = "/tmp/cct_shape_stderr_XXXXXX";
64  int errFd = mkstemp(errPath);
65  if (errFd < 0) return -1;
66 
67  pid_t pid = fork();
68  if (pid < 0) { close(errFd); return -1; }
69  if (pid == 0) {
70  int devnull = open("/dev/null", O_WRONLY);
71  if (devnull >= 0) { dup2(devnull, 1); close(devnull); }
72  dup2(errFd, 2); close(errFd);
73  execv(pin.c_str(), argv.data());
74  _exit(127);
75  }
76  int status = 0;
77  if (waitpid(pid, &status, 0) < 0) { close(errFd); unlink(errPath); return -1; }
78 
79  if (stderrOut) {
80  lseek(errFd, 0, SEEK_SET);
81  stderrOut->clear();
82  char buf[4096];
83  ssize_t n;
84  while ((n = read(errFd, buf, sizeof(buf))) > 0) {
85  stderrOut->append(buf, n);
86  }
87  }
88  close(errFd);
89  unlink(errPath);
90 
91  if (WIFEXITED(status)) return WEXITSTATUS(status);
92  return -128 - WTERMSIG(status);
93 }
94 
95 struct VictimSpec {
96  const char* name; // gtest label + binary basename + -check arg
97 };
98 
99 const VictimSpec kVictims[] = {
100  { "rec_fib_deep" },
101  { "rec_ackermann" },
102  { "rec_multi_direct" },
103  { "rec_indirect_only" },
104  { "rec_mixed_direct_indirect" },
105  { "rec_stripped" },
106  { "rec_exception" },
107  { "rec_baseline_nonrec" },
108 };
109 
110 class RecursionShape : public ::testing::TestWithParam<VictimSpec> {
111  protected:
112  std::string root_;
113  void SetUp() override {
114  root_ = cctlib_root();
115  ASSERT_FALSE(pin_root().empty()) << "PIN_ROOT required";
116  ASSERT_EQ(0, chdir(root_.c_str())) << "chdir(" << root_ << ") failed";
117  }
118 };
119 
120 TEST_P(RecursionShape, CctShapeAssertionsPass) {
121  const auto& v = GetParam();
122  std::string tool = root_ + "/clients/obj-intel64/cct_shape_check.so";
123  std::string victim = root_ + "/tests/gtest/obj/apps/recursion/" + v.name;
124  ASSERT_EQ(0, access(tool.c_str(), F_OK)) << tool << " missing";
125  ASSERT_EQ(0, access(victim.c_str(), F_OK)) << victim << " missing";
126 
127  std::string errText;
128  int rc = run_pin_capture(tool, {"-check", v.name}, victim, &errText);
129  EXPECT_EQ(0, rc)
130  << "victim=" << v.name << " rc=" << rc
131  << "\n---- cct_shape_check stderr ----\n" << errText
132  << "---- end stderr ----";
133 }
134 
136  Victims, RecursionShape,
137  ::testing::ValuesIn(kVictims),
138  [](const testing::TestParamInfo<RecursionShape::ParamType>& info) {
139  return std::string(info.param.name);
140  });
141 
142 } // namespace
static uint64_t buf[ITERS]
volatile uint8_t status
Definition: cctlib.cpp:230
INSTANTIATE_TEST_SUITE_P(Victims, RecursionShape, ::testing::ValuesIn(kVictims), [](const testing::TestParamInfo< RecursionShape::ParamType > &info) { return std::string(info.param.name);})
int run_pin_capture(const std::string &tool, const std::vector< std::string > &toolArgs, const std::string &victim, std::string *stderrOut)
int a[1000]
Definition: testArray.c:5