CCTLib
Calling-context and data-object attribution library for Intel Pin
exception_shape_test.cpp
Go to the documentation of this file.
1 // Shape-based integration tests for cctlib's exception + signal
2 // handling. Each victim exercises a specific pattern (throw/catch,
3 // deep unwind, rethrow, catch-all, destructor cleanup, high-frequency
4 // loop, polymorphic catch, setjmp/longjmp, sigsegv-recovery), and we
5 // launch it under clients/obj-intel64/cct_shape_check.so with
6 // -check <victim_name>. The check tool walks the CCT programmatically
7 // at ThreadFini and asserts per-victim structural invariants -- the
8 // generic check is "main appears in some chain AND no HARD sentinels"
9 // (proving cctlib's unwind path left the CCT coherent); per-victim
10 // overrides tighten this where an unwind-specific shape is expected.
11 //
12 // This suite complements the existing exception_integration_test.cpp
13 // which drives deadspy/redspy/loadspy and catches tool-side SIGSEGVs
14 // in those clients' analysis routines. The shape suite catches the
15 // dual failure mode: cctlib silently corrupting the CCT during unwind
16 // (would show as sentinel frames -- BAD IP / CRASHED / etc.) or
17 // mis-anchoring post-catch code under a throwing frame (caught by
18 // the exc_catch_and_resume-specific check_exc_catch_and_resume).
19 
20 #include <cerrno>
21 #include <cstdio>
22 #include <cstdlib>
23 #include <cstring>
24 #include <fcntl.h>
25 #include <fstream>
26 #include <sstream>
27 #include <string>
28 #include <sys/stat.h>
29 #include <sys/wait.h>
30 #include <unistd.h>
31 #include <vector>
32 
33 #include <gtest/gtest.h>
34 
35 namespace {
36 
37 std::string env(const char* n) { const char* v = getenv(n); return v ? v : ""; }
38 std::string cctlib_root() {
39  std::string r = env("CCTLIB_ROOT");
40  return r.empty() ? "../.." : r;
41 }
42 std::string pin_root() { return env("PIN_ROOT"); }
43 
44 int run_pin_capture(const std::string& tool,
45  const std::vector<std::string>& toolArgs,
46  const std::string& victim,
47  std::string* stderrOut) {
48  std::string pin = pin_root() + "/pin";
49  std::vector<char*> argv;
50  argv.push_back(const_cast<char*>(pin.c_str()));
51  argv.push_back(const_cast<char*>("-t"));
52  argv.push_back(const_cast<char*>(tool.c_str()));
53  for (auto& a : toolArgs) argv.push_back(const_cast<char*>(a.c_str()));
54  argv.push_back(const_cast<char*>("--"));
55  argv.push_back(const_cast<char*>(victim.c_str()));
56  argv.push_back(nullptr);
57 
58  char errPath[] = "/tmp/cct_shape_exc_stderr_XXXXXX";
59  int errFd = mkstemp(errPath);
60  if (errFd < 0) return -1;
61 
62  pid_t pid = fork();
63  if (pid < 0) { close(errFd); return -1; }
64  if (pid == 0) {
65  int devnull = open("/dev/null", O_WRONLY);
66  if (devnull >= 0) { dup2(devnull, 1); close(devnull); }
67  dup2(errFd, 2); close(errFd);
68  execv(pin.c_str(), argv.data());
69  _exit(127);
70  }
71  int status = 0;
72  if (waitpid(pid, &status, 0) < 0) { close(errFd); unlink(errPath); return -1; }
73 
74  if (stderrOut) {
75  lseek(errFd, 0, SEEK_SET);
76  stderrOut->clear();
77  char buf[4096];
78  ssize_t n;
79  while ((n = read(errFd, buf, sizeof(buf))) > 0) {
80  stderrOut->append(buf, n);
81  }
82  }
83  close(errFd);
84  unlink(errPath);
85 
86  if (WIFEXITED(status)) return WEXITSTATUS(status);
87  return -128 - WTERMSIG(status);
88 }
89 
90 // All 13 exception victims are wired for shape checks. Each victim's
91 // per-shape assertion lives in clients/cct_shape_check.cpp
92 // (check_exc_*). The stress victims -- exc_simple_throw (N=5000),
93 // exc_rethrow (ITERS=2000), exc_stress_loop (ITERS=5000),
94 // exc_none_tn (ITERS=20000) -- are slow under Pin+cctlib (many
95 // minutes each) and are gated behind CCTLIB_EXPENSIVE_SHAPE=1 so
96 // they don't dominate the default `make test` runtime. The full
97 // suite runs in CI or when the env var is set. The compact suite
98 // still covers every distinct CCT-structural pattern (single
99 // throw, nested rethrow, catch-all, multi-phase dtor cleanup,
100 // polymorphic dispatch, uncaught path, ctor throw, catch+resume,
101 // setjmp/longjmp, signal recovery).
102 struct VictimSpec {
103  const char* name;
104  bool expensive; // true = only run when CCTLIB_EXPENSIVE_SHAPE=1
105 };
106 
107 const VictimSpec kVictims[] = {
108  { "exc_simple_throw", true }, // N=5000 throws
109  { "exc_deep_unwind", false }, // ITERS=200
110  { "exc_rethrow", true }, // ITERS=2000, 2x _Unwind_SetIP per iter
111  { "exc_catchall", false }, // ITERS=400
112  { "exc_dtor_cleanup", false }, // ITERS=500
113  { "exc_stress_loop", true }, // ITERS=5000
114  { "exc_polymorphic", false }, // ITERS=1000
115  { "exc_recurse_trycatch", false }, // D=8, ITERS=100 (rec+rethrow chain)
116  { "exc_none_tn", true }, // ITERS=20000 (no throws)
117  { "exc_uncaught_tn", false }, // 1 uncaught throw + terminate
118  { "exc_ctor_throw", false }, // ITERS=200
119  { "exc_catch_and_resume", false }, // ITERS=2000
120  { "sig_longjmp", false }, // ITERS=500
121  { "sig_sigsegv_recover", false }, // ITERS=200
122 };
123 
124 class ExceptionShape : public ::testing::TestWithParam<VictimSpec> {
125  protected:
126  std::string root_;
127  void SetUp() override {
128  root_ = cctlib_root();
129  ASSERT_FALSE(pin_root().empty()) << "PIN_ROOT required";
130  ASSERT_EQ(0, chdir(root_.c_str())) << "chdir(" << root_ << ") failed";
131  }
132 };
133 
134 TEST_P(ExceptionShape, CctShapeSurvivesUnwind) {
135  const auto& v = GetParam();
136  if (v.expensive && env("CCTLIB_EXPENSIVE_SHAPE").empty()) {
137  GTEST_SKIP() << v.name << " gated behind CCTLIB_EXPENSIVE_SHAPE=1 "
138  << "(slow under Pin+cctlib; runs in nightly / on-demand)";
139  }
140  std::string tool = root_ + "/clients/obj-intel64/cct_shape_check.so";
141  std::string victim = root_ + "/tests/gtest/obj/apps/exceptions/" + v.name;
142  ASSERT_EQ(0, access(tool.c_str(), F_OK)) << tool << " missing";
143  ASSERT_EQ(0, access(victim.c_str(), F_OK)) << victim << " missing";
144 
145  std::string errText;
146  int rc = run_pin_capture(tool, {"-check", v.name}, victim, &errText);
147  EXPECT_EQ(0, rc)
148  << "victim=" << v.name << " rc=" << rc
149  << "\n---- cct_shape_check stderr ----\n" << errText
150  << "---- end stderr ----";
151 }
152 
154  Victims, ExceptionShape,
155  ::testing::ValuesIn(kVictims),
156  [](const testing::TestParamInfo<ExceptionShape::ParamType>& info) {
157  return std::string(info.param.name);
158  });
159 
160 } // namespace
static uint64_t buf[ITERS]
volatile uint8_t status
Definition: cctlib.cpp:230
TEST_P(ExceptionShape, CctShapeSurvivesUnwind)
INSTANTIATE_TEST_SUITE_P(Victims, ExceptionShape, ::testing::ValuesIn(kVictims), [](const testing::TestParamInfo< ExceptionShape::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