• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2019 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 #include "base/android/reached_code_profiler.h"
6 
7 #include <signal.h>
8 #include <sys/time.h>
9 #include <ucontext.h>
10 #include <unistd.h>
11 
12 #include <atomic>
13 
14 #include "base/android/library_loader/anchor_functions.h"
15 #include "base/android/orderfile/orderfile_buildflags.h"
16 #include "base/android/reached_addresses_bitset.h"
17 #include "base/base_switches.h"
18 #include "base/command_line.h"
19 #include "base/feature_list.h"
20 #include "base/files/file_path.h"
21 #include "base/files/file_util.h"
22 #include "base/files/important_file_writer.h"
23 #include "base/functional/bind.h"
24 #include "base/linux_util.h"
25 #include "base/logging.h"
26 #include "base/no_destructor.h"
27 #include "base/path_service.h"
28 #include "base/scoped_generic.h"
29 #include "base/strings/string_number_conversions.h"
30 #include "base/strings/string_piece.h"
31 #include "base/strings/stringprintf.h"
32 #include "base/synchronization/lock.h"
33 #include "base/task/single_thread_task_runner.h"
34 #include "base/threading/thread.h"
35 #include "base/time/time.h"
36 #include "base/timer/timer.h"
37 #include "build/build_config.h"
38 #include "third_party/abseil-cpp/absl/types/optional.h"
39 
40 #if !BUILDFLAG(SUPPORTS_CODE_ORDERING)
41 #error Code ordering support is required for the reached code profiler.
42 #endif
43 
44 namespace base {
45 namespace android {
46 
47 namespace {
48 
49 #if !defined(NDEBUG) || defined(COMPONENT_BUILD) || defined(OFFICIAL_BUILD)
50 // Always disabled for debug builds to avoid hitting a limit of signal
51 // interrupts that can get delivered into a single HANDLE_EINTR. Also
52 // debugging experience would be bad if there are a lot of signals flying
53 // around.
54 // Always disabled for component builds because in this case the code is not
55 // organized in one contiguous region which is required for the reached code
56 // profiler.
57 // Disabled for official builds because `g_text_bitfield` isn't included in
58 // official builds.
59 constexpr const bool kConfigurationSupported = false;
60 #else
61 constexpr const bool kConfigurationSupported = true;
62 #endif
63 
64 constexpr const char kDumpToFileFlag[] = "reached-code-profiler-dump-to-file";
65 
66 constexpr uint64_t kIterationsBeforeSkipping = 50;
67 constexpr uint64_t kIterationsBetweenUpdates = 100;
68 constexpr int kProfilerSignal = SIGWINCH;
69 
70 constexpr base::TimeDelta kSamplingInterval = base::Milliseconds(10);
71 constexpr base::TimeDelta kDumpInterval = base::Seconds(30);
72 
HandleSignal(int signal,siginfo_t * info,void * context)73 void HandleSignal(int signal, siginfo_t* info, void* context) {
74   if (signal != kProfilerSignal)
75     return;
76 
77   ucontext_t* ucontext = reinterpret_cast<ucontext_t*>(context);
78 #if defined(ARCH_CPU_ARM64)
79   uintptr_t address = ucontext->uc_mcontext.pc;
80 #else
81   uintptr_t address = ucontext->uc_mcontext.arm_pc;
82 #endif
83   ReachedAddressesBitset::GetTextBitset()->RecordAddress(address);
84 }
85 
86 struct ScopedTimerCloseTraits {
InvalidValuebase::android::__anon862450060111::ScopedTimerCloseTraits87   static absl::optional<timer_t> InvalidValue() { return absl::nullopt; }
88 
Freebase::android::__anon862450060111::ScopedTimerCloseTraits89   static void Free(absl::optional<timer_t> x) { timer_delete(*x); }
90 };
91 
92 // RAII object holding an interval timer.
93 using ScopedTimer =
94     base::ScopedGeneric<absl::optional<timer_t>, ScopedTimerCloseTraits>;
95 
DumpToFile(const base::FilePath & path,scoped_refptr<base::SingleThreadTaskRunner> task_runner)96 void DumpToFile(const base::FilePath& path,
97                 scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
98   DCHECK(task_runner->BelongsToCurrentThread());
99 
100   auto dir_path = path.DirName();
101   if (!base::DirectoryExists(dir_path) && !base::CreateDirectory(dir_path)) {
102     PLOG(ERROR) << "Could not create " << dir_path;
103     return;
104   }
105 
106   std::vector<uint32_t> reached_offsets =
107       ReachedAddressesBitset::GetTextBitset()->GetReachedOffsets();
108   base::StringPiece contents(
109       reinterpret_cast<const char*>(reached_offsets.data()),
110       reached_offsets.size());
111   if (!base::ImportantFileWriter::WriteFileAtomically(path, contents,
112                                                       "ReachedDump")) {
113     LOG(ERROR) << "Could not write reached dump into " << path;
114   }
115 
116   task_runner->PostDelayedTask(
117       FROM_HERE, base::BindOnce(&DumpToFile, path, task_runner), kDumpInterval);
118 }
119 
120 class ReachedCodeProfiler {
121  public:
GetInstance()122   static ReachedCodeProfiler* GetInstance() {
123     static base::NoDestructor<ReachedCodeProfiler> instance;
124     return instance.get();
125   }
126 
127   ReachedCodeProfiler(const ReachedCodeProfiler&) = delete;
128   ReachedCodeProfiler& operator=(const ReachedCodeProfiler&) = delete;
129 
130   // Starts to periodically send |kProfilerSignal| to all threads.
Start(LibraryProcessType library_process_type,base::TimeDelta sampling_interval)131   void Start(LibraryProcessType library_process_type,
132              base::TimeDelta sampling_interval) {
133     if (is_enabled_)
134       return;
135 
136     // Set |kProfilerSignal| signal handler.
137     // TODO(crbug.com/916263): consider restoring |old_handler| after the
138     // profiler gets stopped.
139     struct sigaction old_handler;
140     struct sigaction sa;
141     sigemptyset(&sa.sa_mask);
142     sa.sa_sigaction = &HandleSignal;
143     sa.sa_flags = SA_RESTART | SA_SIGINFO;
144     int ret = sigaction(kProfilerSignal, &sa, &old_handler);
145     if (ret) {
146       PLOG(ERROR) << "Error setting signal handler. The reached code profiler "
147                      "is disabled";
148       return;
149     }
150 
151     // Create a new interval timer.
152     struct sigevent sevp;
153     memset(&sevp, 0, sizeof(sevp));
154     sevp.sigev_notify = SIGEV_THREAD;
155     sevp.sigev_notify_function = &OnTimerNotify;
156     timer_t timerid;
157     ret = timer_create(CLOCK_PROCESS_CPUTIME_ID, &sevp, &timerid);
158     if (ret) {
159       PLOG(ERROR)
160           << "timer_create() failed. The reached code profiler is disabled";
161       return;
162     }
163 
164     timer_.reset(timerid);
165 
166     // Start the interval timer.
167     struct itimerspec its;
168     memset(&its, 0, sizeof(its));
169     its.it_interval.tv_nsec =
170         checked_cast<long>(sampling_interval.InNanoseconds());
171     its.it_value = its.it_interval;
172     ret = timer_settime(timerid, 0, &its, nullptr);
173     if (ret) {
174       PLOG(ERROR)
175           << "timer_settime() failed. The reached code profiler is disabled";
176       return;
177     }
178 
179     if (library_process_type == PROCESS_BROWSER)
180       StartDumpingReachedCode();
181 
182     is_enabled_ = true;
183   }
184 
185   // Stops profiling.
Stop()186   void Stop() {
187     timer_.reset();
188     dumping_thread_.reset();
189     is_enabled_ = false;
190   }
191 
192   // Returns whether the profiler is currently enabled.
IsEnabled()193   bool IsEnabled() { return is_enabled_; }
194 
195  private:
ReachedCodeProfiler()196   ReachedCodeProfiler()
197       : current_pid_(getpid()), iteration_number_(0), is_enabled_(false) {}
198 
OnTimerNotify(sigval_t ignored)199   static void OnTimerNotify(sigval_t ignored) {
200     ReachedCodeProfiler::GetInstance()->SendSignalToAllThreads();
201   }
202 
SendSignalToAllThreads()203   void SendSignalToAllThreads() {
204     // This code should be thread-safe.
205     base::AutoLock scoped_lock(lock_);
206     ++iteration_number_;
207 
208     if (iteration_number_ <= kIterationsBeforeSkipping ||
209         iteration_number_ % kIterationsBetweenUpdates == 0) {
210       tids_.clear();
211       if (!base::GetThreadsForProcess(current_pid_, &tids_)) {
212         LOG(WARNING) << "Failed to get a list of threads for process "
213                      << current_pid_;
214         return;
215       }
216     }
217 
218     pid_t current_tid = gettid();
219     for (pid_t tid : tids_) {
220       if (tid != current_tid)
221         tgkill(current_pid_, tid, kProfilerSignal);
222     }
223   }
224 
StartDumpingReachedCode()225   void StartDumpingReachedCode() {
226     const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
227     if (!cmdline->HasSwitch(kDumpToFileFlag))
228       return;
229 
230     base::FilePath dir_path(cmdline->GetSwitchValueASCII(kDumpToFileFlag));
231     if (dir_path.empty()) {
232       if (!base::PathService::Get(base::DIR_CACHE, &dir_path)) {
233         LOG(WARNING) << "Failed to get cache dir path.";
234         return;
235       }
236     }
237 
238     auto file_path =
239         dir_path.Append(base::StringPrintf("reached-code-%d.txt", getpid()));
240 
241     dumping_thread_ =
242         std::make_unique<base::Thread>("ReachedCodeProfilerDumpingThread");
243     dumping_thread_->StartWithOptions(
244         base::Thread::Options(base::ThreadType::kBackground));
245     dumping_thread_->task_runner()->PostDelayedTask(
246         FROM_HERE,
247         base::BindOnce(&DumpToFile, file_path, dumping_thread_->task_runner()),
248         kDumpInterval);
249   }
250 
251   base::Lock lock_;
252   std::vector<pid_t> tids_;
253   const pid_t current_pid_;
254   uint64_t iteration_number_;
255   ScopedTimer timer_;
256   std::unique_ptr<base::Thread> dumping_thread_;
257 
258   bool is_enabled_;
259 
260   friend class NoDestructor<ReachedCodeProfiler>;
261 };
262 
ShouldEnableReachedCodeProfiler()263 bool ShouldEnableReachedCodeProfiler() {
264   if (!kConfigurationSupported)
265     return false;
266 
267   const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
268   return cmdline->HasSwitch(switches::kEnableReachedCodeProfiler);
269 }
270 
271 }  // namespace
272 
InitReachedCodeProfilerAtStartup(LibraryProcessType library_process_type)273 void InitReachedCodeProfilerAtStartup(LibraryProcessType library_process_type) {
274   // The profiler shouldn't be run as part of webview.
275   CHECK(library_process_type == PROCESS_BROWSER ||
276         library_process_type == PROCESS_CHILD);
277 
278   if (!ShouldEnableReachedCodeProfiler())
279     return;
280 
281   int interval_us = 0;
282   base::TimeDelta sampling_interval = kSamplingInterval;
283   if (base::StringToInt(
284           base::CommandLine::ForCurrentProcess()->GetSwitchValueNative(
285               switches::kReachedCodeSamplingIntervalUs),
286           &interval_us) &&
287       interval_us > 0) {
288     sampling_interval = base::Microseconds(interval_us);
289   }
290   ReachedCodeProfiler::GetInstance()->Start(library_process_type,
291                                             sampling_interval);
292 }
293 
IsReachedCodeProfilerEnabled()294 bool IsReachedCodeProfilerEnabled() {
295   return ReachedCodeProfiler::GetInstance()->IsEnabled();
296 }
297 
IsReachedCodeProfilerSupported()298 bool IsReachedCodeProfilerSupported() {
299   return kConfigurationSupported;
300 }
301 
302 }  // namespace android
303 }  // namespace base
304