1 | /// This code was based on the code by Fedor Korotkiy (prime@yandex-team.ru) for YT product in Yandex. |
2 | |
3 | #if defined(__has_feature) |
4 | #if __has_feature(address_sanitizer) |
5 | #define ADDRESS_SANITIZER 1 |
6 | #endif |
7 | #if __has_feature(thread_sanitizer) |
8 | #define THREAD_SANITIZER 1 |
9 | #endif |
10 | #else |
11 | #if defined(__SANITIZE_ADDRESS__) |
12 | #define ADDRESS_SANITIZER 1 |
13 | #endif |
14 | #if defined(__SANITIZE_THREAD__) |
15 | #define THREAD_SANITIZER 1 |
16 | #endif |
17 | #endif |
18 | |
19 | #if defined(__linux__) && !defined(THREAD_SANITIZER) |
20 | #define USE_PHDR_CACHE 1 |
21 | #endif |
22 | |
23 | #define __msan_unpoison(X, Y) |
24 | #if defined(__has_feature) |
25 | # if __has_feature(memory_sanitizer) |
26 | # undef __msan_unpoison |
27 | # include <sanitizer/msan_interface.h> |
28 | # endif |
29 | #endif |
30 | |
31 | /// Thread Sanitizer uses dl_iterate_phdr function on initialization and fails if we provide our own. |
32 | #ifdef USE_PHDR_CACHE |
33 | |
34 | #include <link.h> |
35 | #include <dlfcn.h> |
36 | #include <vector> |
37 | #include <atomic> |
38 | #include <cstddef> |
39 | #include <stdexcept> |
40 | |
41 | |
42 | namespace |
43 | { |
44 | |
45 | // This is adapted from |
46 | // https://github.com/scylladb/seastar/blob/master/core/exception_hacks.hh |
47 | // https://github.com/scylladb/seastar/blob/master/core/exception_hacks.cc |
48 | |
49 | using DLIterateFunction = int (*) (int (*callback) (dl_phdr_info * info, size_t size, void * data), void * data); |
50 | |
51 | DLIterateFunction getOriginalDLIteratePHDR() |
52 | { |
53 | void * func = dlsym(RTLD_NEXT, "dl_iterate_phdr" ); |
54 | if (!func) |
55 | throw std::runtime_error("Cannot find dl_iterate_phdr function with dlsym" ); |
56 | return reinterpret_cast<DLIterateFunction>(func); |
57 | } |
58 | |
59 | |
60 | using PHDRCache = std::vector<dl_phdr_info>; |
61 | std::atomic<PHDRCache *> phdr_cache {}; |
62 | |
63 | } |
64 | |
65 | |
66 | extern "C" |
67 | #ifndef __clang__ |
68 | [[gnu::visibility("default" )]] |
69 | [[gnu::externally_visible]] |
70 | #endif |
71 | int dl_iterate_phdr(int (*callback) (dl_phdr_info * info, size_t size, void * data), void * data) |
72 | { |
73 | auto current_phdr_cache = phdr_cache.load(); |
74 | if (!current_phdr_cache) |
75 | { |
76 | // Cache is not yet populated, pass through to the original function. |
77 | return getOriginalDLIteratePHDR()(callback, data); |
78 | } |
79 | |
80 | int result = 0; |
81 | for (auto & entry : *current_phdr_cache) |
82 | { |
83 | result = callback(&entry, offsetof(dl_phdr_info, dlpi_adds), data); |
84 | if (result != 0) |
85 | break; |
86 | } |
87 | return result; |
88 | } |
89 | |
90 | |
91 | extern "C" |
92 | { |
93 | #ifdef ADDRESS_SANITIZER |
94 | void __lsan_ignore_object(const void *); |
95 | #else |
96 | void __lsan_ignore_object(const void *) {} |
97 | #endif |
98 | } |
99 | |
100 | |
101 | void updatePHDRCache() |
102 | { |
103 | // Fill out ELF header cache for access without locking. |
104 | // This assumes no dynamic object loading/unloading after this point |
105 | |
106 | PHDRCache * new_phdr_cache = new PHDRCache; |
107 | getOriginalDLIteratePHDR()([] (dl_phdr_info * info, size_t /*size*/, void * data) |
108 | { |
109 | // `info` is created by dl_iterate_phdr, which is a non-instrumented |
110 | // libc function, so we have to unpoison it manually. |
111 | __msan_unpoison(info, sizeof(*info)); |
112 | |
113 | reinterpret_cast<PHDRCache *>(data)->push_back(*info); |
114 | return 0; |
115 | }, new_phdr_cache); |
116 | phdr_cache.store(new_phdr_cache); |
117 | |
118 | /// Memory is intentionally leaked. |
119 | __lsan_ignore_object(new_phdr_cache); |
120 | } |
121 | |
122 | |
123 | bool hasPHDRCache() |
124 | { |
125 | return phdr_cache.load() != nullptr; |
126 | } |
127 | |
128 | #else |
129 | |
130 | void updatePHDRCache() {} |
131 | bool hasPHDRCache() { return false; } |
132 | |
133 | #endif |
134 | |