• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2017 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifdef UNSAFE_BUFFERS_BUILD
6 // TODO(crbug.com/40284755): Remove this and use spans.
7 #pragma allow_unsafe_buffers
8 #endif
9 
10 #include "base/android/orderfile/orderfile_instrumentation.h"
11 
12 #include <time.h>
13 #include <unistd.h>
14 
15 #include <atomic>
16 #include <cstdio>
17 #include <cstring>
18 #include <sstream>
19 #include <string>
20 #include <thread>
21 #include <vector>
22 
23 #include "base/android/library_loader/anchor_functions.h"
24 #include "base/android/orderfile/orderfile_buildflags.h"
25 #include "base/command_line.h"
26 #include "base/containers/span.h"
27 #include "base/files/file.h"
28 #include "base/format_macros.h"
29 #include "base/logging.h"
30 #include "base/strings/stringprintf.h"
31 #include "build/build_config.h"
32 
33 #if BUILDFLAG(DEVTOOLS_INSTRUMENTATION_DUMPING)
34 #include <sstream>
35 
36 #include "base/time/time.h"
37 #include "base/trace_event/memory_dump_manager.h"   // no-presubmit-check
38 #include "base/trace_event/memory_dump_provider.h"  // no-presubmit-check
39 #endif  // BUILDFLAG(DEVTOOLS_INSTRUMENTATION_DUMPING)
40 
41 #if !BUILDFLAG(SUPPORTS_CODE_ORDERING)
42 #error Requires code ordering support (arm/arm64/x86/x86_64).
43 #endif  // !BUILDFLAG(SUPPORTS_CODE_ORDERING)
44 
45 // Must be applied to all functions within this file.
46 #define NO_INSTRUMENT_FUNCTION __attribute__((no_instrument_function))
47 
48 namespace base::android::orderfile {
49 
50 namespace {
51 // Constants used for StartDelayedDump().
52 constexpr int kDelayInSeconds = 30;
53 constexpr int kInitialDelayInSeconds = kPhases == 1 ? kDelayInSeconds : 5;
54 
55 // This is defined in content/public/common/content_switches.h, which is not
56 // accessible in ::base.
57 constexpr const char kProcessTypeSwitch[] = "type";
58 
59 // These are large overestimates, which is not an issue, as the data is
60 // allocated in .bss, and on linux doesn't take any actual memory when it's not
61 // touched.
62 constexpr size_t kBitfieldSize = 1 << 22;
63 constexpr size_t kMaxTextSizeInBytes = kBitfieldSize * (4 * 32);
64 constexpr size_t kMaxElements = 1 << 20;
65 
66 // Data required to log reached offsets.
67 struct LogData {
68   std::atomic<uint32_t> offsets[kBitfieldSize];
69   std::atomic<size_t> ordered_offsets[kMaxElements];
70   std::atomic<size_t> index;
71 };
72 
73 LogData g_data[kPhases];
74 std::atomic<int> g_data_index;
75 
76 // Number of unexpected addresses, that is addresses that are not within [start,
77 // end) bounds for the executable code.
78 //
79 // This should be exactly 0, since the start and end of .text should be known
80 // perfectly by the linker, but it does happen. See crbug.com/1186598.
81 std::atomic<int> g_unexpected_addresses;
82 
83 #if BUILDFLAG(DEVTOOLS_INSTRUMENTATION_DUMPING)
84 // Dump offsets when a memory dump is requested. Used only if
85 // switches::kDevtoolsInstrumentationDumping is set.
86 class OrderfileMemoryDumpHook : public base::trace_event::MemoryDumpProvider {
OnMemoryDump(const base::trace_event::MemoryDumpArgs & args,base::trace_event::ProcessMemoryDump * pmd)87   NO_INSTRUMENT_FUNCTION bool OnMemoryDump(
88       const base::trace_event::MemoryDumpArgs& args,
89       base::trace_event::ProcessMemoryDump* pmd) override {
90     // Disable instrumentation now to cut down on orderfile pollution.
91     if (!Disable()) {
92       return true;  // A dump has already been started.
93     }
94     std::stringstream process_type_str;
95     Dump(base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
96         kProcessTypeSwitch));
97     return true;  // If something goes awry, a fatal error will be created
98                   // internally.
99   }
100 };
101 #endif  // BUILDFLAG(DEVTOOLS_INSTRUMENTATION_DUMPING)
102 
103 // |RecordAddress()| adds an element to a concurrent bitset and to a concurrent
104 // append-only list of offsets.
105 //
106 // Ordering:
107 // Two consecutive calls to |RecordAddress()| from the same thread will be
108 // ordered in the same way in the result, as written by
109 // |StopAndDumpToFile()|. The result will contain exactly one instance of each
110 // unique offset relative to |kStartOfText| passed to |RecordAddress()|.
111 //
112 // Implementation:
113 // The "set" part is implemented with a bitfield, |g_offset|. The insertion
114 // order is recorded in |g_ordered_offsets|.
115 // This is not a class to make sure there isn't a static constructor, as it
116 // would cause issue with an instrumented static constructor calling this code.
117 //
118 // Limitations:
119 // - Only records offsets to addresses between |kStartOfText| and |kEndOfText|.
120 // - Capacity of the set is limited by |kMaxElements|.
121 // - Some insertions at the end of collection may be lost.
122 
123 // Records that |address| has been reached, if recording is enabled.
124 // To avoid infinite recursion, this *must* *never* call any instrumented
125 // function, unless |Disable()| is called first.
126 template <bool for_testing>
RecordAddress(size_t address)127 __attribute__((always_inline, no_instrument_function)) void RecordAddress(
128     size_t address) {
129   int index = g_data_index.load(std::memory_order_relaxed);
130   if (index >= kPhases)
131     return;
132 
133   const size_t start =
134       for_testing ? kStartOfTextForTesting : base::android::kStartOfText;
135   const size_t end =
136       for_testing ? kEndOfTextForTesting : base::android::kEndOfText;
137   if (address < start || address > end) [[unlikely]] {
138     if (!AreAnchorsSane()) {
139       // Something is really wrong with the anchors, and this is likely to be
140       // triggered from within a static constructor, where logging is likely to
141       // deadlock.  By crashing immediately we at least have a chance to get a
142       // stack trace from the system to give some clue about the nature of the
143       // problem.
144       ImmediateCrash();
145     }
146 
147     // Observing return addresses outside of the intended range indicates a
148     // potentially serious problem in the way the build is set up. However, a
149     // small number of unexpected addresses is tolerable for production builds.
150     // It seems useful to allow a limited number of out-of-range addresses to
151     // let the orderfile_generator guess the root causes. See
152     // crbug.com/330761384, crbug.com/352317042.
153     if (g_unexpected_addresses.fetch_add(1, std::memory_order_relaxed) < 10) {
154       return;
155     }
156 
157     Disable();
158     LOG(FATAL) << "Too many unexpected addresses! start = " << std::hex << start
159                << " end = " << end << " address = " << address;
160   }
161 
162   size_t offset = address - start;
163   static_assert(sizeof(int) == 4,
164                 "Collection and processing code assumes that sizeof(int) == 4");
165   size_t offset_index = offset / 4;
166 
167   auto* offsets = g_data[index].offsets;
168   // Atomically set the corresponding bit in the array.
169   std::atomic<uint32_t>* element = offsets + (offset_index / 32);
170   // First, a racy check. This saves a CAS if the bit is already set, and
171   // allows the cache line to remain shared acoss CPUs in this case.
172   uint32_t value = element->load(std::memory_order_relaxed);
173   uint32_t mask = 1 << (offset_index % 32);
174   if (value & mask)
175     return;
176 
177   auto before = element->fetch_or(mask, std::memory_order_relaxed);
178   if (before & mask)
179     return;
180 
181   // We were the first one to set the element, record it in the ordered
182   // elements list.
183   // Use relaxed ordering, as the value is not published, or used for
184   // synchronization.
185   auto* ordered_offsets = g_data[index].ordered_offsets;
186   auto& ordered_offsets_index = g_data[index].index;
187   size_t insertion_index =
188       ordered_offsets_index.fetch_add(1, std::memory_order_relaxed);
189   if (insertion_index >= kMaxElements) [[unlikely]] {
190     Disable();
191     LOG(FATAL) << "Too many reached offsets";
192   }
193   ordered_offsets[insertion_index].store(offset, std::memory_order_relaxed);
194 }
195 
DumpToFile(const base::FilePath & path,const LogData & data)196 NO_INSTRUMENT_FUNCTION bool DumpToFile(const base::FilePath& path,
197                                        const LogData& data) {
198   auto file =
199       base::File(path, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
200   if (!file.IsValid()) {
201     PLOG(ERROR) << "Could not open " << path;
202     return false;
203   }
204 
205   if (data.index == 0) {
206     LOG(ERROR) << "No entries to dump";
207     return false;
208   }
209 
210   size_t count = data.index - 1;
211   for (size_t i = 0; i < count; i++) {
212     // |g_ordered_offsets| is initialized to 0, so a 0 in the middle of it
213     // indicates a case where the index was incremented, but the write is not
214     // visible in this thread yet. Safe to skip, also because the function at
215     // the start of text is never called.
216     auto offset = data.ordered_offsets[i].load(std::memory_order_relaxed);
217     if (!offset)
218       continue;
219     auto offset_str = base::StringPrintf("%" PRIuS "\n", offset);
220     if (!file.WriteAtCurrentPosAndCheck(base::as_byte_span(offset_str))) {
221       // If the file could be opened, but writing has failed, it's likely that
222       // data was partially written. Producing incomplete profiling data would
223       // lead to a poorly performing orderfile, but might not be otherwised
224       // noticed. So we crash instead.
225       LOG(FATAL) << "Error writing profile data";
226     }
227   }
228   return true;
229 }
230 
231 // Stops recording, and outputs the data to |path|.
StopAndDumpToFile(int pid,uint64_t start_ns_since_epoch,const std::string & tag)232 NO_INSTRUMENT_FUNCTION void StopAndDumpToFile(int pid,
233                                               uint64_t start_ns_since_epoch,
234                                               const std::string& tag) {
235   Disable();
236 
237   for (int phase = 0; phase < kPhases; phase++) {
238     std::string tag_str;
239     if (!tag.empty())
240       tag_str = base::StringPrintf("%s-", tag.c_str());
241     auto path = base::StringPrintf(
242         "/data/local/tmp/chrome/orderfile/profile-hitmap-%s%d-%" PRIu64
243         ".txt_%d",
244         tag_str.c_str(), pid, start_ns_since_epoch, phase);
245     if (!DumpToFile(base::FilePath(path), g_data[phase])) {
246       LOG(ERROR) << "Problem with dump " << phase << " (" << tag << ")";
247     }
248   }
249 
250   int unexpected_addresses =
251       g_unexpected_addresses.load(std::memory_order_relaxed);
252   if (unexpected_addresses != 0) {
253     LOG(WARNING) << "Got " << unexpected_addresses << " unexpected addresses!";
254   }
255 }
256 
257 }  // namespace
258 
259 // After a call to Disable(), any function can be called, as reentrancy into the
260 // instrumentation function will be mitigated.
Disable()261 NO_INSTRUMENT_FUNCTION bool Disable() {
262   auto old_phase = g_data_index.exchange(kPhases, std::memory_order_relaxed);
263   std::atomic_thread_fence(std::memory_order_seq_cst);
264   return old_phase != kPhases;
265 }
266 
SanityChecks()267 NO_INSTRUMENT_FUNCTION void SanityChecks() {
268   CHECK_LT(base::android::kEndOfText - base::android::kStartOfText,
269            kMaxTextSizeInBytes);
270   CHECK(base::android::IsOrderingSane());
271 }
272 
SwitchToNextPhaseOrDump(int pid,uint64_t start_ns_since_epoch,const std::string & tag)273 NO_INSTRUMENT_FUNCTION bool SwitchToNextPhaseOrDump(
274     int pid,
275     uint64_t start_ns_since_epoch,
276     const std::string& tag) {
277   int before = g_data_index.fetch_add(1, std::memory_order_relaxed);
278   if (before + 1 == kPhases) {
279     StopAndDumpToFile(pid, start_ns_since_epoch, tag);
280     return true;
281   }
282   return false;
283 }
284 
StartDelayedDump()285 NO_INSTRUMENT_FUNCTION void StartDelayedDump() {
286   // Using std::thread and not using base::TimeTicks() in order to to not call
287   // too many base:: symbols that would pollute the reached symbol dumps.
288   struct timespec ts;
289   if (clock_gettime(CLOCK_MONOTONIC, &ts))
290     PLOG(FATAL) << "clock_gettime.";
291   uint64_t start_ns_since_epoch =
292       static_cast<uint64_t>(ts.tv_sec) * 1000 * 1000 * 1000 + ts.tv_nsec;
293   int pid = getpid();
294   std::string tag = base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
295       kProcessTypeSwitch);
296 
297 #if BUILDFLAG(DEVTOOLS_INSTRUMENTATION_DUMPING)
298   static auto* g_orderfile_memory_dump_hook = new OrderfileMemoryDumpHook();
299   base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
300       g_orderfile_memory_dump_hook, "Orderfile", nullptr);
301 #endif  // BUILDFLAG(DEVTOOLS_INSTRUMENTATION_DUMPING)
302 
303   std::thread([pid, start_ns_since_epoch, tag] {
304     sleep(kInitialDelayInSeconds);
305 #if BUILDFLAG(DEVTOOLS_INSTRUMENTATION_DUMPING)
306     SwitchToNextPhaseOrDump(pid, start_ns_since_epoch, tag);
307 // Return, letting devtools tracing handle any post-startup phases.
308 #else
309     while (!SwitchToNextPhaseOrDump(pid, start_ns_since_epoch, tag))
310       sleep(kDelayInSeconds);
311 #endif  // BUILDFLAG(DEVTOOLS_INSTRUMENTATION_DUMPING)
312   }).detach();
313 }
314 
Dump(const std::string & tag)315 NO_INSTRUMENT_FUNCTION void Dump(const std::string& tag) {
316   // As profiling has been disabled, none of the uses of ::base symbols below
317   // will enter the symbol dump.
318   StopAndDumpToFile(
319       getpid(), (base::Time::Now() - base::Time::UnixEpoch()).InNanoseconds(),
320       tag);
321 }
322 
ResetForTesting()323 NO_INSTRUMENT_FUNCTION void ResetForTesting() {
324   Disable();
325   g_data_index = 0;
326   for (int i = 0; i < kPhases; i++) {
327     memset(reinterpret_cast<uint32_t*>(g_data[i].offsets), 0,
328            sizeof(uint32_t) * kBitfieldSize);
329     memset(reinterpret_cast<uint32_t*>(g_data[i].ordered_offsets), 0,
330            sizeof(uint32_t) * kMaxElements);
331     g_data[i].index.store(0);
332   }
333 
334   g_unexpected_addresses.store(0, std::memory_order_relaxed);
335 }
336 
RecordAddressForTesting(size_t address)337 NO_INSTRUMENT_FUNCTION void RecordAddressForTesting(size_t address) {
338   return RecordAddress<true>(address);
339 }
340 
GetOrderedOffsetsForTesting()341 NO_INSTRUMENT_FUNCTION std::vector<size_t> GetOrderedOffsetsForTesting() {
342   std::vector<size_t> result;
343   size_t max_index = g_data[0].index.load(std::memory_order_relaxed);
344   for (size_t i = 0; i < max_index; ++i) {
345     auto value = g_data[0].ordered_offsets[i].load(std::memory_order_relaxed);
346     if (value)
347       result.push_back(value);
348   }
349   return result;
350 }
351 
352 }  // namespace base::android::orderfile
353 
354 extern "C" {
355 
__cyg_profile_func_enter_bare()356 NO_INSTRUMENT_FUNCTION void __cyg_profile_func_enter_bare() {
357   base::android::orderfile::RecordAddress<false>(
358       reinterpret_cast<size_t>(__builtin_return_address(0)));
359 }
360 
361 }  // extern "C"
362