CCTLib
Calling-context and data-object attribution library for Intel Pin
exception_integration_test.cpp
Go to the documentation of this file.
1 // Integration tests for cctlib's exception + signal / non-local-return
2 // handling. Each victim exercises a specific pattern (throw/catch, deep
3 // unwind, rethrow, catch-all, destructor cleanup during unwind, high-
4 // frequency loop, polymorphic catch, setjmp/longjmp, sigsegv-recovery).
5 // For every (client tool, victim) combination we assert:
6 // * pin exits with code 0 (no SIGSEGV in the tool),
7 // * a report file with the expected prefix is produced.
8 //
9 // This suite exists specifically to guard against regressions of the
10 // cctlib exception-unwind bug where a direct call to _Unwind_GetIP
11 // resolved to Pin's private libunwind and corrupted context reads.
12 // See RememberUnwindGetIPFromImage() in src/cctlib.cpp.
13 
14 #include <cerrno>
15 #include <cstdio>
16 #include <cstdlib>
17 #include <cstring>
18 #include <fcntl.h>
19 #include <fstream>
20 #include <sstream>
21 #include <string>
22 #include <sys/stat.h>
23 #include <sys/wait.h>
24 #include <unistd.h>
25 #include <vector>
26 
27 #include <gtest/gtest.h>
28 
29 namespace {
30 
31 std::string env(const char* n) { const char* v = getenv(n); return v ? v : ""; }
32 std::string cctlib_root() {
33  std::string r = env("CCTLIB_ROOT");
34  return r.empty() ? "../.." : r;
35 }
36 std::string pin_root() { return env("PIN_ROOT"); }
37 
38 // Run pin with the given tool against the victim; return pin's exit code.
39 int run_pin(const std::string& tool, const std::string& victim,
40  const std::vector<std::string>& extra_args = {}) {
41  std::string pin = pin_root() + "/pin";
42  std::vector<char*> argv;
43  argv.push_back(const_cast<char*>(pin.c_str()));
44  argv.push_back(const_cast<char*>("-t"));
45  argv.push_back(const_cast<char*>(tool.c_str()));
46  argv.push_back(const_cast<char*>("--"));
47  argv.push_back(const_cast<char*>(victim.c_str()));
48  for (auto& a : extra_args) argv.push_back(const_cast<char*>(a.c_str()));
49  argv.push_back(nullptr);
50 
51  pid_t pid = fork();
52  if (pid < 0) return -1;
53  if (pid == 0) {
54  if (env("CCTLIB_TEST_VERBOSE").empty()) {
55  int devnull = open("/dev/null", O_WRONLY);
56  if (devnull >= 0) { dup2(devnull, 1); dup2(devnull, 2); close(devnull); }
57  }
58  execv(pin.c_str(), argv.data());
59  _exit(127);
60  }
61  int status = 0;
62  if (waitpid(pid, &status, 0) < 0) return -1;
63  if (WIFEXITED(status)) return WEXITSTATUS(status);
64  return -128 - WTERMSIG(status);
65 }
66 
67 // True if any file matching dir/prefix* exists.
68 bool has_output_file(const std::string& dir, const std::string& prefix) {
69  std::string cmd = "ls " + dir + "/" + prefix + "* >/dev/null 2>&1";
70  return system(cmd.c_str()) == 0;
71 }
72 
73 // Remove stale output files that would otherwise trip the presence check.
74 void cleanup(const std::string& dir, const std::string& prefix) {
75  std::string cmd = "rm -f " + dir + "/" + prefix + "*";
76  (void)system(cmd.c_str());
77 }
78 
79 // One row in the (tool, output-prefix) product.
80 struct ToolSpec {
81  const char* name; // gtest label
82  const char* soPath; // relative to clients/obj-intel64/
83  const char* outPrefix; // report file prefix in CWD
84 };
85 
86 const ToolSpec kTools[] = {
87  { "deadspy", "deadspy_client.so", "deadspy.out." },
88  { "redspy", "redspy_client.so", "redspy.out." },
89  { "loadspy", "loadspy_client.so", "redLoad.out." },
90 };
91 
92 // Victim programs under tests/gtest/obj/apps/exceptions.
93 struct VictimSpec {
94  const char* name; // gtest label + binary basename
95 };
96 
97 const VictimSpec kVictims[] = {
98  { "exc_simple_throw" },
99  { "exc_deep_unwind" },
100  { "exc_rethrow" },
101  { "exc_catchall" },
102  { "exc_dtor_cleanup" },
103  { "exc_stress_loop" },
104  { "exc_polymorphic" },
105  { "exc_none_tn" },
106  { "exc_uncaught_tn" },
107  { "exc_ctor_throw" },
108  { "exc_catch_and_resume" },
109  { "sig_longjmp" },
110  { "sig_sigsegv_recover" },
111 };
112 
114  : public ::testing::TestWithParam<std::tuple<ToolSpec, VictimSpec>> {
115  protected:
116  std::string root_;
117  void SetUp() override {
118  root_ = cctlib_root();
119  ASSERT_FALSE(pin_root().empty()) << "PIN_ROOT required";
120  // Report files land in CWD; keep them next to the repo so parallel
121  // gtest invocations don't stomp on each other.
122  ASSERT_EQ(0, chdir(root_.c_str())) << "chdir(" << root_ << ") failed";
123  }
124 };
125 
126 TEST_P(ExceptionRun, PinExitsCleanlyAndReportIsProduced) {
127  auto [tool, victim] = GetParam();
128 
129  // TODO(loadspy-fault-safe): loadspy's analysis routine dereferences
130  // the load address to capture "value at read" (see
131  // RedSpyAnalysis::CheckNByteValueAfterRead in loadspy_client.cpp).
132  // For the sig_sigsegv_recover victim, the app deliberately reads
133  // from 0x1 to trigger SIGSEGV; loadspy's callback runs first and
134  // faults on the same address. Fix by routing the load through
135  // PIN_SafeCopy. Deadspy (shadow-only) and redspy (read-only address
136  // never written to) don't hit this. Tracked separately.
137  if (std::string(tool.name) == "loadspy" &&
138  std::string(victim.name) == "sig_sigsegv_recover") {
139  GTEST_SKIP() << "known loadspy fault-safety bug; independent of the"
140  " cctlib exception fix under test";
141  }
142 
143  std::string toolPath = root_ + "/clients/obj-intel64/" + tool.soPath;
144  std::string victimPath = root_ + "/tests/gtest/obj/apps/exceptions/" + victim.name;
145  ASSERT_EQ(0, access(toolPath.c_str(), F_OK)) << toolPath << " missing";
146  ASSERT_EQ(0, access(victimPath.c_str(), F_OK)) << victimPath << " missing";
147 
148  cleanup(root_, tool.outPrefix);
149  int rc = run_pin(toolPath, victimPath);
150  // Non-zero == SIGSEGV in the tool (rc < -128 encodes the terminating
151  // signal), a fatal PIN_ExitProcess(-1) from the cctlib unwind
152  // resolver, or the victim itself failing.
153  EXPECT_EQ(0, rc)
154  << "tool=" << tool.name << " victim=" << victim.name << " rc=" << rc
155  << " -- see repo root for output artifacts";
156  EXPECT_TRUE(has_output_file(root_, tool.outPrefix))
157  << "no " << tool.outPrefix << "* file produced by " << tool.name
158  << " for " << victim.name;
159 }
160 
162  ToolAndVictim, ExceptionRun,
163  ::testing::Combine(::testing::ValuesIn(kTools),
164  ::testing::ValuesIn(kVictims)),
165  [](const testing::TestParamInfo<ExceptionRun::ParamType>& info) {
166  std::string s;
167  s += std::get<0>(info.param).name;
168  s += "__";
169  s += std::get<1>(info.param).name;
170  return s;
171  });
172 
173 } // namespace
volatile uint8_t status
Definition: cctlib.cpp:230
bool has_output_file(const std::string &dir, const std::string &prefix)
TEST_P(ExceptionRun, PinExitsCleanlyAndReportIsProduced)
INSTANTIATE_TEST_SUITE_P(ToolAndVictim, ExceptionRun, ::testing::Combine(::testing::ValuesIn(kTools), ::testing::ValuesIn(kVictims)), [](const testing::TestParamInfo< ExceptionRun::ParamType > &info) { std::string s;s+=std::get< 0 >(info.param).name;s+="__";s+=std::get< 1 >(info.param).name;return s;})
int run_pin(const std::string &tool, const std::string &victim, const std::vector< std::string > &extra_args={})
void cleanup(const std::string &dir, const std::string &prefix)
int a[1000]
Definition: testArray.c:5