1 | //===-- xray_interface.cpp --------------------------------------*- C++ -*-===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | // |
9 | // This file is a part of XRay, a dynamic runtime instrumentation system. |
10 | // |
11 | // Implementation of the API functions. |
12 | // |
13 | //===----------------------------------------------------------------------===// |
14 | |
15 | #include "xray_interface_internal.h" |
16 | |
17 | #include <cinttypes> |
18 | #include <cstdio> |
19 | #include <errno.h> |
20 | #include <limits> |
21 | #include <string.h> |
22 | #include <sys/mman.h> |
23 | |
24 | #if SANITIZER_FUCHSIA |
25 | #include <zircon/process.h> |
26 | #include <zircon/sanitizer.h> |
27 | #include <zircon/status.h> |
28 | #include <zircon/syscalls.h> |
29 | #endif |
30 | |
31 | #include "sanitizer_common/sanitizer_addrhashmap.h" |
32 | #include "sanitizer_common/sanitizer_common.h" |
33 | |
34 | #include "xray_defs.h" |
35 | #include "xray_flags.h" |
36 | |
37 | extern __sanitizer::SpinMutex XRayInstrMapMutex; |
38 | extern __sanitizer::atomic_uint8_t XRayInitialized; |
39 | extern __xray::XRaySledMap XRayInstrMap; |
40 | |
41 | namespace __xray { |
42 | |
43 | #if defined(__x86_64__) |
44 | static const int16_t cSledLength = 12; |
45 | #elif defined(__aarch64__) |
46 | static const int16_t cSledLength = 32; |
47 | #elif defined(__arm__) |
48 | static const int16_t cSledLength = 28; |
49 | #elif SANITIZER_LOONGARCH64 |
50 | static const int16_t cSledLength = 48; |
51 | #elif SANITIZER_MIPS32 |
52 | static const int16_t cSledLength = 48; |
53 | #elif SANITIZER_MIPS64 |
54 | static const int16_t cSledLength = 64; |
55 | #elif defined(__powerpc64__) |
56 | static const int16_t cSledLength = 8; |
57 | #elif defined(__hexagon__) |
58 | static const int16_t cSledLength = 20; |
59 | #else |
60 | #error "Unsupported CPU Architecture" |
61 | #endif /* CPU architecture */ |
62 | |
63 | // This is the function to call when we encounter the entry or exit sleds. |
64 | atomic_uintptr_t XRayPatchedFunction{.val_dont_use: 0}; |
65 | |
66 | // This is the function to call from the arg1-enabled sleds/trampolines. |
67 | atomic_uintptr_t XRayArgLogger{.val_dont_use: 0}; |
68 | |
69 | // This is the function to call when we encounter a custom event log call. |
70 | atomic_uintptr_t XRayPatchedCustomEvent{.val_dont_use: 0}; |
71 | |
72 | // This is the function to call when we encounter a typed event log call. |
73 | atomic_uintptr_t XRayPatchedTypedEvent{.val_dont_use: 0}; |
74 | |
75 | // This is the global status to determine whether we are currently |
76 | // patching/unpatching. |
77 | atomic_uint8_t XRayPatching{.val_dont_use: 0}; |
78 | |
79 | struct TypeDescription { |
80 | uint32_t type_id; |
81 | std::size_t description_string_length; |
82 | }; |
83 | |
84 | using TypeDescriptorMapType = AddrHashMap<TypeDescription, 11>; |
85 | // An address map from immutable descriptors to type ids. |
86 | TypeDescriptorMapType TypeDescriptorAddressMap{}; |
87 | |
88 | atomic_uint32_t TypeEventDescriptorCounter{.val_dont_use: 0}; |
89 | |
90 | // MProtectHelper is an RAII wrapper for calls to mprotect(...) that will |
91 | // undo any successful mprotect(...) changes. This is used to make a page |
92 | // writeable and executable, and upon destruction if it was successful in |
93 | // doing so returns the page into a read-only and executable page. |
94 | // |
95 | // This is only used specifically for runtime-patching of the XRay |
96 | // instrumentation points. This assumes that the executable pages are |
97 | // originally read-and-execute only. |
98 | class MProtectHelper { |
99 | void *PageAlignedAddr; |
100 | std::size_t MProtectLen; |
101 | bool MustCleanup; |
102 | |
103 | public: |
104 | explicit MProtectHelper(void *PageAlignedAddr, |
105 | std::size_t MProtectLen, |
106 | std::size_t PageSize) XRAY_NEVER_INSTRUMENT |
107 | : PageAlignedAddr(PageAlignedAddr), |
108 | MProtectLen(MProtectLen), |
109 | MustCleanup(false) { |
110 | #if SANITIZER_FUCHSIA |
111 | MProtectLen = RoundUpTo(MProtectLen, PageSize); |
112 | #endif |
113 | } |
114 | |
115 | int MakeWriteable() XRAY_NEVER_INSTRUMENT { |
116 | #if SANITIZER_FUCHSIA |
117 | auto R = __sanitizer_change_code_protection( |
118 | reinterpret_cast<uintptr_t>(PageAlignedAddr), MProtectLen, true); |
119 | if (R != ZX_OK) { |
120 | Report("XRay: cannot change code protection: %s\n" , |
121 | _zx_status_get_string(R)); |
122 | return -1; |
123 | } |
124 | MustCleanup = true; |
125 | return 0; |
126 | #else |
127 | auto R = mprotect(addr: PageAlignedAddr, len: MProtectLen, |
128 | PROT_READ | PROT_WRITE | PROT_EXEC); |
129 | if (R != -1) |
130 | MustCleanup = true; |
131 | return R; |
132 | #endif |
133 | } |
134 | |
135 | ~MProtectHelper() XRAY_NEVER_INSTRUMENT { |
136 | if (MustCleanup) { |
137 | #if SANITIZER_FUCHSIA |
138 | auto R = __sanitizer_change_code_protection( |
139 | reinterpret_cast<uintptr_t>(PageAlignedAddr), MProtectLen, false); |
140 | if (R != ZX_OK) { |
141 | Report("XRay: cannot change code protection: %s\n" , |
142 | _zx_status_get_string(R)); |
143 | } |
144 | #else |
145 | mprotect(addr: PageAlignedAddr, len: MProtectLen, PROT_READ | PROT_EXEC); |
146 | #endif |
147 | } |
148 | } |
149 | }; |
150 | |
151 | namespace { |
152 | |
153 | bool patchSled(const XRaySledEntry &Sled, bool Enable, |
154 | int32_t FuncId) XRAY_NEVER_INSTRUMENT { |
155 | bool Success = false; |
156 | switch (Sled.Kind) { |
157 | case XRayEntryType::ENTRY: |
158 | Success = patchFunctionEntry(Enable, FuncId, Sled, Trampoline: __xray_FunctionEntry); |
159 | break; |
160 | case XRayEntryType::EXIT: |
161 | Success = patchFunctionExit(Enable, FuncId, Sled); |
162 | break; |
163 | case XRayEntryType::TAIL: |
164 | Success = patchFunctionTailExit(Enable, FuncId, Sled); |
165 | break; |
166 | case XRayEntryType::LOG_ARGS_ENTRY: |
167 | Success = patchFunctionEntry(Enable, FuncId, Sled, Trampoline: __xray_ArgLoggerEntry); |
168 | break; |
169 | case XRayEntryType::CUSTOM_EVENT: |
170 | Success = patchCustomEvent(Enable, FuncId, Sled); |
171 | break; |
172 | case XRayEntryType::TYPED_EVENT: |
173 | Success = patchTypedEvent(Enable, FuncId, Sled); |
174 | break; |
175 | default: |
176 | Report(format: "Unsupported sled kind '%" PRIu64 "' @%04x\n" , Sled.Address, |
177 | int(Sled.Kind)); |
178 | return false; |
179 | } |
180 | return Success; |
181 | } |
182 | |
183 | const XRayFunctionSledIndex |
184 | findFunctionSleds(int32_t FuncId, |
185 | const XRaySledMap &InstrMap) XRAY_NEVER_INSTRUMENT { |
186 | int32_t CurFn = 0; |
187 | uint64_t LastFnAddr = 0; |
188 | XRayFunctionSledIndex Index = {.Begin: nullptr, .Size: 0}; |
189 | |
190 | for (std::size_t I = 0; I < InstrMap.Entries && CurFn <= FuncId; I++) { |
191 | const auto &Sled = InstrMap.Sleds[I]; |
192 | const auto Function = Sled.function(); |
193 | if (Function != LastFnAddr) { |
194 | CurFn++; |
195 | LastFnAddr = Function; |
196 | } |
197 | |
198 | if (CurFn == FuncId) { |
199 | if (Index.Begin == nullptr) |
200 | Index.Begin = &Sled; |
201 | Index.Size = &Sled - Index.Begin + 1; |
202 | } |
203 | } |
204 | |
205 | return Index; |
206 | } |
207 | |
208 | XRayPatchingStatus patchFunction(int32_t FuncId, |
209 | bool Enable) XRAY_NEVER_INSTRUMENT { |
210 | if (!atomic_load(a: &XRayInitialized, |
211 | mo: memory_order_acquire)) |
212 | return XRayPatchingStatus::NOT_INITIALIZED; // Not initialized. |
213 | |
214 | uint8_t NotPatching = false; |
215 | if (!atomic_compare_exchange_strong( |
216 | a: &XRayPatching, cmp: &NotPatching, xchg: true, mo: memory_order_acq_rel)) |
217 | return XRayPatchingStatus::ONGOING; // Already patching. |
218 | |
219 | // Next, we look for the function index. |
220 | XRaySledMap InstrMap; |
221 | { |
222 | SpinMutexLock Guard(&XRayInstrMapMutex); |
223 | InstrMap = XRayInstrMap; |
224 | } |
225 | |
226 | // If we don't have an index, we can't patch individual functions. |
227 | if (InstrMap.Functions == 0) |
228 | return XRayPatchingStatus::NOT_INITIALIZED; |
229 | |
230 | // FuncId must be a positive number, less than the number of functions |
231 | // instrumented. |
232 | if (FuncId <= 0 || static_cast<size_t>(FuncId) > InstrMap.Functions) { |
233 | Report(format: "Invalid function id provided: %d\n" , FuncId); |
234 | return XRayPatchingStatus::FAILED; |
235 | } |
236 | |
237 | // Now we patch ths sleds for this specific function. |
238 | XRayFunctionSledIndex SledRange; |
239 | if (InstrMap.SledsIndex) { |
240 | SledRange = {.Begin: InstrMap.SledsIndex[FuncId - 1].fromPCRelative(), |
241 | .Size: InstrMap.SledsIndex[FuncId - 1].Size}; |
242 | } else { |
243 | SledRange = findFunctionSleds(FuncId, InstrMap); |
244 | } |
245 | auto *f = SledRange.Begin; |
246 | bool SucceedOnce = false; |
247 | for (size_t i = 0; i != SledRange.Size; ++i) |
248 | SucceedOnce |= patchSled(Sled: f[i], Enable, FuncId); |
249 | |
250 | atomic_store(a: &XRayPatching, v: false, |
251 | mo: memory_order_release); |
252 | |
253 | if (!SucceedOnce) { |
254 | Report(format: "Failed patching any sled for function '%d'." , FuncId); |
255 | return XRayPatchingStatus::FAILED; |
256 | } |
257 | |
258 | return XRayPatchingStatus::SUCCESS; |
259 | } |
260 | |
261 | // controlPatching implements the common internals of the patching/unpatching |
262 | // implementation. |Enable| defines whether we're enabling or disabling the |
263 | // runtime XRay instrumentation. |
264 | XRayPatchingStatus controlPatching(bool Enable) XRAY_NEVER_INSTRUMENT { |
265 | if (!atomic_load(a: &XRayInitialized, |
266 | mo: memory_order_acquire)) |
267 | return XRayPatchingStatus::NOT_INITIALIZED; // Not initialized. |
268 | |
269 | uint8_t NotPatching = false; |
270 | if (!atomic_compare_exchange_strong( |
271 | a: &XRayPatching, cmp: &NotPatching, xchg: true, mo: memory_order_acq_rel)) |
272 | return XRayPatchingStatus::ONGOING; // Already patching. |
273 | |
274 | uint8_t PatchingSuccess = false; |
275 | auto XRayPatchingStatusResetter = |
276 | at_scope_exit(fn: [&PatchingSuccess] { |
277 | if (!PatchingSuccess) |
278 | atomic_store(a: &XRayPatching, v: false, |
279 | mo: memory_order_release); |
280 | }); |
281 | |
282 | XRaySledMap InstrMap; |
283 | { |
284 | SpinMutexLock Guard(&XRayInstrMapMutex); |
285 | InstrMap = XRayInstrMap; |
286 | } |
287 | if (InstrMap.Entries == 0) |
288 | return XRayPatchingStatus::NOT_INITIALIZED; |
289 | |
290 | uint32_t FuncId = 1; |
291 | uint64_t CurFun = 0; |
292 | |
293 | // First we want to find the bounds for which we have instrumentation points, |
294 | // and try to get as few calls to mprotect(...) as possible. We're assuming |
295 | // that all the sleds for the instrumentation map are contiguous as a single |
296 | // set of pages. When we do support dynamic shared object instrumentation, |
297 | // we'll need to do this for each set of page load offsets per DSO loaded. For |
298 | // now we're assuming we can mprotect the whole section of text between the |
299 | // minimum sled address and the maximum sled address (+ the largest sled |
300 | // size). |
301 | auto *MinSled = &InstrMap.Sleds[0]; |
302 | auto *MaxSled = &InstrMap.Sleds[InstrMap.Entries - 1]; |
303 | for (std::size_t I = 0; I < InstrMap.Entries; I++) { |
304 | const auto &Sled = InstrMap.Sleds[I]; |
305 | if (Sled.address() < MinSled->address()) |
306 | MinSled = &Sled; |
307 | if (Sled.address() > MaxSled->address()) |
308 | MaxSled = &Sled; |
309 | } |
310 | |
311 | const size_t PageSize = flags()->xray_page_size_override > 0 |
312 | ? flags()->xray_page_size_override |
313 | : GetPageSizeCached(); |
314 | if ((PageSize == 0) || ((PageSize & (PageSize - 1)) != 0)) { |
315 | Report(format: "System page size is not a power of two: %zu\n" , PageSize); |
316 | return XRayPatchingStatus::FAILED; |
317 | } |
318 | |
319 | void *PageAlignedAddr = |
320 | reinterpret_cast<void *>(MinSled->address() & ~(PageSize - 1)); |
321 | size_t MProtectLen = |
322 | (MaxSled->address() - reinterpret_cast<uptr>(PageAlignedAddr)) + |
323 | cSledLength; |
324 | MProtectHelper Protector(PageAlignedAddr, MProtectLen, PageSize); |
325 | if (Protector.MakeWriteable() == -1) { |
326 | Report(format: "Failed mprotect: %d\n" , errno); |
327 | return XRayPatchingStatus::FAILED; |
328 | } |
329 | |
330 | for (std::size_t I = 0; I < InstrMap.Entries; ++I) { |
331 | auto &Sled = InstrMap.Sleds[I]; |
332 | auto F = Sled.function(); |
333 | if (CurFun == 0) |
334 | CurFun = F; |
335 | if (F != CurFun) { |
336 | ++FuncId; |
337 | CurFun = F; |
338 | } |
339 | patchSled(Sled, Enable, FuncId); |
340 | } |
341 | atomic_store(a: &XRayPatching, v: false, |
342 | mo: memory_order_release); |
343 | PatchingSuccess = true; |
344 | return XRayPatchingStatus::SUCCESS; |
345 | } |
346 | |
347 | XRayPatchingStatus mprotectAndPatchFunction(int32_t FuncId, |
348 | bool Enable) XRAY_NEVER_INSTRUMENT { |
349 | XRaySledMap InstrMap; |
350 | { |
351 | SpinMutexLock Guard(&XRayInstrMapMutex); |
352 | InstrMap = XRayInstrMap; |
353 | } |
354 | |
355 | // FuncId must be a positive number, less than the number of functions |
356 | // instrumented. |
357 | if (FuncId <= 0 || static_cast<size_t>(FuncId) > InstrMap.Functions) { |
358 | Report(format: "Invalid function id provided: %d\n" , FuncId); |
359 | return XRayPatchingStatus::FAILED; |
360 | } |
361 | |
362 | const size_t PageSize = flags()->xray_page_size_override > 0 |
363 | ? flags()->xray_page_size_override |
364 | : GetPageSizeCached(); |
365 | if ((PageSize == 0) || ((PageSize & (PageSize - 1)) != 0)) { |
366 | Report(format: "Provided page size is not a power of two: %zu\n" , PageSize); |
367 | return XRayPatchingStatus::FAILED; |
368 | } |
369 | |
370 | // Here we compute the minimum sled and maximum sled associated with a |
371 | // particular function ID. |
372 | XRayFunctionSledIndex SledRange; |
373 | if (InstrMap.SledsIndex) { |
374 | SledRange = {.Begin: InstrMap.SledsIndex[FuncId - 1].fromPCRelative(), |
375 | .Size: InstrMap.SledsIndex[FuncId - 1].Size}; |
376 | } else { |
377 | SledRange = findFunctionSleds(FuncId, InstrMap); |
378 | } |
379 | auto *f = SledRange.Begin; |
380 | auto *e = SledRange.Begin + SledRange.Size; |
381 | auto *MinSled = f; |
382 | auto *MaxSled = e - 1; |
383 | while (f != e) { |
384 | if (f->address() < MinSled->address()) |
385 | MinSled = f; |
386 | if (f->address() > MaxSled->address()) |
387 | MaxSled = f; |
388 | ++f; |
389 | } |
390 | |
391 | void *PageAlignedAddr = |
392 | reinterpret_cast<void *>(MinSled->address() & ~(PageSize - 1)); |
393 | size_t MProtectLen = |
394 | (MaxSled->address() - reinterpret_cast<uptr>(PageAlignedAddr)) + |
395 | cSledLength; |
396 | MProtectHelper Protector(PageAlignedAddr, MProtectLen, PageSize); |
397 | if (Protector.MakeWriteable() == -1) { |
398 | Report(format: "Failed mprotect: %d\n" , errno); |
399 | return XRayPatchingStatus::FAILED; |
400 | } |
401 | return patchFunction(FuncId, Enable); |
402 | } |
403 | |
404 | } // namespace |
405 | |
406 | } // namespace __xray |
407 | |
408 | using namespace __xray; |
409 | |
410 | // The following functions are declared `extern "C" {...}` in the header, hence |
411 | // they're defined in the global namespace. |
412 | |
413 | int __xray_set_handler(void (*entry)(int32_t, |
414 | XRayEntryType)) XRAY_NEVER_INSTRUMENT { |
415 | if (atomic_load(a: &XRayInitialized, |
416 | mo: memory_order_acquire)) { |
417 | |
418 | atomic_store(a: &__xray::XRayPatchedFunction, |
419 | v: reinterpret_cast<uintptr_t>(entry), |
420 | mo: memory_order_release); |
421 | return 1; |
422 | } |
423 | return 0; |
424 | } |
425 | |
426 | int __xray_set_customevent_handler(void (*entry)(void *, size_t)) |
427 | XRAY_NEVER_INSTRUMENT { |
428 | if (atomic_load(a: &XRayInitialized, |
429 | mo: memory_order_acquire)) { |
430 | atomic_store(a: &__xray::XRayPatchedCustomEvent, |
431 | v: reinterpret_cast<uintptr_t>(entry), |
432 | mo: memory_order_release); |
433 | return 1; |
434 | } |
435 | return 0; |
436 | } |
437 | |
438 | int __xray_set_typedevent_handler(void (*entry)(size_t, const void *, |
439 | size_t)) XRAY_NEVER_INSTRUMENT { |
440 | if (atomic_load(a: &XRayInitialized, |
441 | mo: memory_order_acquire)) { |
442 | atomic_store(a: &__xray::XRayPatchedTypedEvent, |
443 | v: reinterpret_cast<uintptr_t>(entry), |
444 | mo: memory_order_release); |
445 | return 1; |
446 | } |
447 | return 0; |
448 | } |
449 | |
450 | int __xray_remove_handler() XRAY_NEVER_INSTRUMENT { |
451 | return __xray_set_handler(entry: nullptr); |
452 | } |
453 | |
454 | int __xray_remove_customevent_handler() XRAY_NEVER_INSTRUMENT { |
455 | return __xray_set_customevent_handler(entry: nullptr); |
456 | } |
457 | |
458 | int __xray_remove_typedevent_handler() XRAY_NEVER_INSTRUMENT { |
459 | return __xray_set_typedevent_handler(entry: nullptr); |
460 | } |
461 | |
462 | uint16_t __xray_register_event_type( |
463 | const char *const event_type) XRAY_NEVER_INSTRUMENT { |
464 | TypeDescriptorMapType::Handle h(&TypeDescriptorAddressMap, (uptr)event_type); |
465 | if (h.created()) { |
466 | h->type_id = atomic_fetch_add( |
467 | a: &TypeEventDescriptorCounter, v: 1, mo: memory_order_acq_rel); |
468 | h->description_string_length = strnlen(string: event_type, maxlen: 1024); |
469 | } |
470 | return h->type_id; |
471 | } |
472 | |
473 | XRayPatchingStatus __xray_patch() XRAY_NEVER_INSTRUMENT { |
474 | return controlPatching(Enable: true); |
475 | } |
476 | |
477 | XRayPatchingStatus __xray_unpatch() XRAY_NEVER_INSTRUMENT { |
478 | return controlPatching(Enable: false); |
479 | } |
480 | |
481 | XRayPatchingStatus __xray_patch_function(int32_t FuncId) XRAY_NEVER_INSTRUMENT { |
482 | return mprotectAndPatchFunction(FuncId, Enable: true); |
483 | } |
484 | |
485 | XRayPatchingStatus |
486 | __xray_unpatch_function(int32_t FuncId) XRAY_NEVER_INSTRUMENT { |
487 | return mprotectAndPatchFunction(FuncId, Enable: false); |
488 | } |
489 | |
490 | int __xray_set_handler_arg1(void (*entry)(int32_t, XRayEntryType, uint64_t)) { |
491 | if (!atomic_load(a: &XRayInitialized, |
492 | mo: memory_order_acquire)) |
493 | return 0; |
494 | |
495 | // A relaxed write might not be visible even if the current thread gets |
496 | // scheduled on a different CPU/NUMA node. We need to wait for everyone to |
497 | // have this handler installed for consistency of collected data across CPUs. |
498 | atomic_store(a: &XRayArgLogger, v: reinterpret_cast<uint64_t>(entry), |
499 | mo: memory_order_release); |
500 | return 1; |
501 | } |
502 | |
503 | int __xray_remove_handler_arg1() { return __xray_set_handler_arg1(entry: nullptr); } |
504 | |
505 | uintptr_t __xray_function_address(int32_t FuncId) XRAY_NEVER_INSTRUMENT { |
506 | XRaySledMap InstrMap; |
507 | { |
508 | SpinMutexLock Guard(&XRayInstrMapMutex); |
509 | InstrMap = XRayInstrMap; |
510 | } |
511 | |
512 | if (FuncId <= 0 || static_cast<size_t>(FuncId) > InstrMap.Functions) |
513 | return 0; |
514 | const XRaySledEntry *Sled = |
515 | InstrMap.SledsIndex ? InstrMap.SledsIndex[FuncId - 1].fromPCRelative() |
516 | : findFunctionSleds(FuncId, InstrMap).Begin; |
517 | return Sled->function() |
518 | // On PPC, function entries are always aligned to 16 bytes. The beginning of a |
519 | // sled might be a local entry, which is always +8 based on the global entry. |
520 | // Always return the global entry. |
521 | #ifdef __PPC__ |
522 | & ~0xf |
523 | #endif |
524 | ; |
525 | } |
526 | |
527 | size_t __xray_max_function_id() XRAY_NEVER_INSTRUMENT { |
528 | SpinMutexLock Guard(&XRayInstrMapMutex); |
529 | return XRayInstrMap.Functions; |
530 | } |
531 | |