CppInterOp
C++ Language Interoperability Layer
Loading...
Searching...
No Matches
Tracing.cpp
Go to the documentation of this file.
1//===--- Tracing.cpp - Tracing implementation -------------------*- C++ -*-===//
2//
3// Part of the compiler-research project, under the Apache License v2.0 with
4// LLVM Exceptions.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "Tracing.h"
10
12
13#include "llvm/ADT/StringMap.h"
14#include "llvm/Support/FileSystem.h"
15#include "llvm/Support/ManagedStatic.h"
16#include "llvm/Support/Path.h"
17#include "llvm/Support/Process.h"
18#include "llvm/Support/raw_ostream.h"
19
20#include <cassert>
21#include <cstdint>
22#include <system_error>
23
24#if defined(__linux__) || defined(__APPLE__)
25#include <dlfcn.h>
26#endif
27
28// libclangCppInterOp's own trace-hook slot storage; Dispatch.h
29// consumers carry their per-DSO copies, populated by LoadDispatchAPI.
30namespace CppInternal {
31namespace DispatchRaw {
33 void*, void**,
34 std::size_t,
35 void*) = nullptr;
37 const Cpp::JitCall*, void*, unsigned long, int) = nullptr;
39 const Cpp::JitCall*, void*) = nullptr;
40} // namespace DispatchRaw
41} // namespace CppInternal
42
43namespace CppInterOp {
44namespace Tracing {
45
47
49 assert(!TheTraceInfo);
50 static llvm::ManagedStatic<TraceInfo> TI;
51 TheTraceInfo = &*TI;
52 // Wire libclangCppInterOp's own slots; Dispatch.h consumers do
53 // the equivalent via the X-macro in LoadDispatchAPI.
60}
61
62std::optional<uint64_t> TraceRegion::lookupOutMask(llvm::StringRef Name) {
63 // Built once from CppInterOpAPI.inc via the OUT-only X-macro;
64 // CPPINTEROP_API_FUNC stays a no-op.
65 static const llvm::StringMap<uint64_t> Map = {
66#define CPPINTEROP_API_FUNC(DN, CN, Ret, DA, CA, RT)
67#define CPPINTEROP_API_OUT(CN, OutMask) {#CN, OutMask},
68#include "CppInterOp/CppInterOpAPI.inc"
69 };
70 auto It = Map.find(Name);
71 if (It == Map.end())
72 return std::nullopt;
73 return It->second;
74}
75
76/// RAII: hold m_Dumping while the reproducer is being emitted.
77namespace {
78class DumpScope {
79 TraceInfo& TI;
80
81public:
82 explicit DumpScope(TraceInfo& T) : TI(T) { TI.setDumping(true); }
83 ~DumpScope() { TI.setDumping(false); }
84};
85} // namespace
86
87/// Helper: emit version info as comment lines.
88static void WriteVersionComment(llvm::raw_ostream& OS,
89 const std::string& Version) {
90 llvm::StringRef Ver(Version);
91 while (!Ver.empty()) {
92 auto [Line, Rest] = Ver.split('\n');
93 if (!Line.empty())
94 OS << "// " << Line << "\n";
95 Ver = Rest;
96 }
97}
98
99/// dladdr-based path to libclangCppInterOp, used as the dispatch-mode
100/// fallback. Empty for statically-linked images (where dladdr returns
101/// the executable, not a real shared object) so the reproducer falls
102/// through to CPPINTEROP_LIBRARY_PATH.
103static std::string GetCppInterOpLibPath() {
104#if defined(__linux__) || defined(__APPLE__)
105 Dl_info info;
106 if (dladdr(reinterpret_cast<const void*>(&Cpp::GetVersion), &info) &&
107 info.dli_fname) {
108 llvm::StringRef Name(info.dli_fname);
109 if (Name.contains(".so") || Name.ends_with(".dylib") ||
110 Name.ends_with(".dll"))
111 return info.dli_fname;
112 }
113#endif
114 return {};
115}
116
117/// Stream Cpp::GetBuildInfo() into the prologue as `//` comments.
118static void WriteBuildContext(llvm::raw_ostream& OS) {
119 OS << "// Build context (CppInterOp configure-time snapshot):\n";
120 // Bind to a local: StringRef of the GetBuildInfo() rvalue would
121 // dangle past the next `;`.
122 std::string Snapshot = Cpp::GetBuildInfo();
123 llvm::StringRef Info(Snapshot);
124 while (!Info.empty()) {
125 auto [Line, Rest] = Info.split('\n');
126 if (!Line.empty())
127 OS << "//" << Line << "\n";
128 Info = Rest;
129 }
130}
131
132/// Emit a prologue compilable in two modes selected by -D:
133/// default <CppInterOp/CppInterOp.h>, static-link
134/// -DCPPINTEROP_USE_DISPATCH <CppInterOp/Dispatch.h>, dlopen-based
135/// The dispatch path reads CPPINTEROP_LIBRARY_PATH and falls back to
136/// the dladdr-captured libclangCppInterOp path (when available).
137static void WriteReproducerPrologue(llvm::raw_ostream& OS,
138 llvm::StringRef Title,
139 const std::string& Version,
140 llvm::StringRef Footnote) {
141 OS << "// CppInterOp " << Title << "\n";
142 WriteVersionComment(OS, Version);
143 if (!Footnote.empty())
144 OS << "// " << Footnote << "\n";
145 OS << "//\n";
146 OS << "// Replay scope: re-runs recorded CppInterOp API calls. JIT\n";
147 OS << "// wrappers (Cpp::JitCall::Invoke) are not replayed -- their\n";
148 OS << "// pointer args reference the original process's memory. The\n";
149 OS << "// trailing `// JitCall::Invoke ...` comment, if present,\n";
150 OS << "// names the call active at the crash site.\n";
151 OS << "//\n";
153 OS << "//\n";
154 OS << "// Build (default, static link):\n";
155 OS << "// c++ -std=c++17 -I<CppInterOp-include-dir>"
156 " reproducer.cpp -lclangCppInterOp -o reproducer\n";
157 OS << "// Build (dlopen via Dispatch.h):\n";
158 OS << "// c++ -std=c++17 -DCPPINTEROP_USE_DISPATCH"
159 " -I<CppInterOp-include-dir>"
160 " reproducer.cpp -ldl -o reproducer\n";
161 std::string LibPath = GetCppInterOpLibPath();
162 OS << "// Run: ./reproducer";
163 if (LibPath.empty())
164 OS << " (dispatch build: set "
165 "CPPINTEROP_LIBRARY_PATH=<libclangCppInterOp.so>)";
166 else
167 OS << " (dispatch build: CPPINTEROP_LIBRARY_PATH overrides the captured "
168 "path)";
169 OS << "\n\n";
170
171 OS << "#ifdef CPPINTEROP_USE_DISPATCH\n";
172 OS << "#include <CppInterOp/Dispatch.h>\n\n";
173 // X-macro defines one DispatchRaw slot per public API entry.
174 OS << "using namespace Cpp;\n";
175 OS << "#define CPPINTEROP_API_FUNC(DN, CN, Ret, DeclArgs, CallArgs, "
176 "RawTypes) \\\n"
177 " Ret(*CppInternal::DispatchRaw::DN) RawTypes = nullptr;\n"
178 "#include \"CppInterOp/CppInterOpAPI.inc\"\n\n";
179 // Lazy: lets a cling-driven harness call reproducer() before main().
180 OS << "static void initCppInterOpReproducer() {\n";
181 OS << " static bool inited = false;\n";
182 OS << " if (inited) return;\n";
183 OS << " const char* path = std::getenv(\"CPPINTEROP_LIBRARY_PATH\");\n";
184 if (!LibPath.empty())
185 OS << " if (!path) path = R\"PATH(" << LibPath << ")PATH\";\n";
186 OS << " Cpp::LoadDispatchAPI(path);\n";
187 OS << " inited = true;\n";
188 OS << "}\n";
189 OS << "#else\n";
190 OS << "#include <CppInterOp/CppInterOp.h>\n";
191 // Static-link path: no-op so reproducer()'s init call is harmless.
192 OS << "static void initCppInterOpReproducer() {}\n";
193 OS << "#endif\n\n";
194}
195
196std::string TraceInfo::writeToFile(const std::string& Version) {
197 llvm::SmallString<128> TmpDir;
198 llvm::sys::path::system_temp_directory(/*ErasedOnReboot=*/true, TmpDir);
199 llvm::SmallString<128> Path;
200 llvm::sys::path::append(Path, TmpDir, "cppinterop-reproducer-%%%%%%.cpp");
201
202 int FD;
203 std::error_code EC = llvm::sys::fs::createUniqueFile(Path, FD, Path);
204 if (EC)
205 return "";
206
207 llvm::raw_fd_ostream OS(FD, /*shouldClose=*/true);
208 DumpScope Guard(*this);
209
210 std::string Ver = Version.empty() ? Cpp::GetVersion() : Version;
211 WriteReproducerPrologue(OS, "crash reproducer", Ver,
212 "Generated automatically — re-run to reproduce.");
213 OS << "void reproducer() {\n";
214 OS << " initCppInterOpReproducer();\n";
215 for (const auto& Line : m_Log)
216 OS << Line << "\n";
217 OS << "}\n\n";
218 OS << "int main() { reproducer(); return 0; }\n";
219 OS.flush();
220 return std::string(Path);
221}
222
223std::string TraceInfo::StartRegion(bool WriteOnStdErr) {
224 m_RegionStart = m_Log.size();
225 m_InRegion = true;
226 m_WriteOnStdErr = WriteOnStdErr;
227
228 // When streaming to stderr, skip creating a file.
229 if (WriteOnStdErr) {
230 m_RegionPath.clear();
231 return "";
232 }
233
234 llvm::SmallString<128> TmpDir;
235 llvm::sys::path::system_temp_directory(/*ErasedOnReboot=*/true, TmpDir);
236 llvm::SmallString<128> Path;
237 llvm::sys::path::append(Path, TmpDir, "cppinterop-reproducer-%%%%%%.cpp");
238 int FD;
239 std::error_code EC = llvm::sys::fs::createUniqueFile(Path, FD, Path);
240 if (EC)
241 return "";
242 llvm::sys::Process::SafelyCloseFileDescriptor(FD);
243 m_RegionPath = std::string(Path);
244 return m_RegionPath;
245}
246
247void TraceInfo::StopRegion(const std::string& Version) {
248 if (!m_InRegion)
249 return;
250 m_InRegion = false;
251
252 // When streaming to stderr, there is no file to write.
253 if (m_WriteOnStdErr)
254 return;
255
256 std::error_code EC;
257 llvm::raw_fd_ostream OS(m_RegionPath, EC);
258 if (EC)
259 return;
260
261 DumpScope Guard(*this);
262 std::string Ver = Version.empty() ? Cpp::GetVersion() : Version;
263 WriteReproducerPrologue(OS, "trace region", Ver, "Generated automatically.");
264 OS << "void reproducer() {\n";
265 OS << " initCppInterOpReproducer();\n";
266 for (size_t i = m_RegionStart; i < m_Log.size(); ++i)
267 OS << m_Log[i] << "\n";
268 OS << "}\n\n";
269 OS << "int main() { reproducer(); return 0; }\n";
270 OS.flush();
271}
272
273} // namespace Tracing
274} // namespace CppInterOp
#define CPPINTEROP_API
#define CPPINTEROP_TRACE_API
Definition Tracing.h:23
std::string writeToFile(const std::string &Version="")
Write the accumulated reproducer log to a file.
Definition Tracing.cpp:196
void StopRegion(const std::string &Version="")
End the traced region and write only the region's entries to the file.
Definition Tracing.cpp:247
std::string StartRegion(bool WriteOnStdErr=true)
Begin a traced region.
Definition Tracing.cpp:223
static std::optional< uint64_t > lookupOutMask(llvm::StringRef Name)
.td-declared OUT-arg bitmask for Name (matches CppName / func), or std::nullopt for non-public-API tr...
Definition Tracing.cpp:62
static std::string GetCppInterOpLibPath()
dladdr-based path to libclangCppInterOp, used as the dispatch-mode fallback.
Definition Tracing.cpp:103
static void WriteReproducerPrologue(llvm::raw_ostream &OS, llvm::StringRef Title, const std::string &Version, llvm::StringRef Footnote)
Emit a prologue compilable in two modes selected by -D: default <CppInterOp/CppInterOp....
Definition Tracing.cpp:137
void InitTracing()
Activate tracing.
Definition Tracing.cpp:48
static void WriteBuildContext(llvm::raw_ostream &OS)
Stream Cpp::GetBuildInfo() into the prologue as // comments.
Definition Tracing.cpp:118
static void WriteVersionComment(llvm::raw_ostream &OS, const std::string &Version)
Helper: emit version info as comment lines.
Definition Tracing.cpp:88
TraceInfo * TheTraceInfo
Process-global tracer pointer.
Definition Tracing.cpp:46
void(* CppInterOpTraceJitCallInvokeImpl)(const Cpp::JitCall *, void *, void **, std::size_t, void *)
Definition Tracing.cpp:32
void(* CppInterOpTraceJitCallInvokeReturnImpl)(const Cpp::JitCall *, void *)
Definition Tracing.cpp:38
void(* CppInterOpTraceJitCallInvokeDestructorImpl)(const Cpp::JitCall *, void *, unsigned long, int)
Definition Tracing.cpp:36
void CppInterOpTraceJitCallInvokeImpl(const JitCall *JC, void *result, void **args, std::size_t nargs, void *self)
void CppInterOpTraceJitCallInvokeReturnImpl(const JitCall *JC, void *result)
std::string GetBuildInfo()
void CppInterOpTraceJitCallInvokeDestructorImpl(const JitCall *JC, void *object, unsigned long nary, int withFree)
std::string GetVersion()