• 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 #ifdef UNSAFE_BUFFERS_BUILD
6 // TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
7 #pragma allow_unsafe_buffers
8 #endif
9 
10 #include "base/profiler/stack_sampling_profiler_test_util.h"
11 
12 #include <string_view>
13 #include <utility>
14 
15 #include "base/functional/bind.h"
16 #include "base/functional/callback.h"
17 #include "base/location.h"
18 #include "base/memory/raw_ptr.h"
19 #include "base/path_service.h"
20 #include "base/profiler/native_unwinder_android_map_delegate.h"
21 #include "base/profiler/native_unwinder_android_memory_regions_map.h"
22 #include "base/profiler/profiler_buildflags.h"
23 #include "base/profiler/stack_buffer.h"
24 #include "base/profiler/stack_sampling_profiler.h"
25 #include "base/profiler/unwinder.h"
26 #include "base/strings/stringprintf.h"
27 #include "base/test/bind.h"
28 #include "build/build_config.h"
29 #include "testing/gtest/include/gtest/gtest.h"
30 
31 #if BUILDFLAG(IS_ANDROID) && BUILDFLAG(ENABLE_ARM_CFI_TABLE)
32 #include "base/android/apk_assets.h"
33 #include "base/android/library_loader/anchor_functions.h"
34 #include "base/files/memory_mapped_file.h"
35 #include "base/no_destructor.h"
36 #include "base/profiler/chrome_unwinder_android_32.h"
37 #include "base/profiler/native_unwinder_android.h"
38 #endif
39 
40 #if BUILDFLAG(IS_WIN)
41 // Windows doesn't provide an alloca function like Linux does.
42 // Fortunately, it provides _alloca, which functions identically.
43 #include <malloc.h>
44 #define alloca _alloca
45 #else
46 #include <alloca.h>
47 #endif
48 
49 extern "C" {
50 // The address of |__executable_start| gives the start address of the
51 // executable or shared library. This value is used to find the offset address
52 // of the instruction in binary from PC.
53 extern char __executable_start;
54 }
55 
56 namespace base {
57 
58 namespace {
59 
60 // A profile builder for test use that expects to receive exactly one sample.
61 class TestProfileBuilder : public ProfileBuilder {
62  public:
63   // The callback is passed the last sample recorded.
64   using CompletedCallback = OnceCallback<void(std::vector<Frame>)>;
65 
TestProfileBuilder(ModuleCache * module_cache,CompletedCallback callback)66   TestProfileBuilder(ModuleCache* module_cache, CompletedCallback callback)
67       : module_cache_(module_cache), callback_(std::move(callback)) {}
68 
69   ~TestProfileBuilder() override = default;
70 
71   TestProfileBuilder(const TestProfileBuilder&) = delete;
72   TestProfileBuilder& operator=(const TestProfileBuilder&) = delete;
73 
74   // ProfileBuilder:
GetModuleCache()75   ModuleCache* GetModuleCache() override { return module_cache_; }
RecordMetadata(const MetadataRecorder::MetadataProvider & metadata_provider)76   void RecordMetadata(
77       const MetadataRecorder::MetadataProvider& metadata_provider) override {}
78 
OnSampleCompleted(std::vector<Frame> sample,TimeTicks sample_timestamp)79   void OnSampleCompleted(std::vector<Frame> sample,
80                          TimeTicks sample_timestamp) override {
81     EXPECT_TRUE(sample_.empty());
82     sample_ = std::move(sample);
83   }
84 
OnProfileCompleted(TimeDelta profile_duration,TimeDelta sampling_period)85   void OnProfileCompleted(TimeDelta profile_duration,
86                           TimeDelta sampling_period) override {
87     EXPECT_FALSE(sample_.empty());
88     std::move(callback_).Run(std::move(sample_));
89   }
90 
91  private:
92   const raw_ptr<ModuleCache> module_cache_;
93   CompletedCallback callback_;
94   std::vector<Frame> sample_;
95 };
96 
97 // The function to be executed by the code in the other library.
OtherLibraryCallback(void * arg)98 void OtherLibraryCallback(void* arg) {
99   OnceClosure* wait_for_sample = static_cast<OnceClosure*>(arg);
100 
101   std::move(*wait_for_sample).Run();
102 
103   // Prevent tail call.
104   [[maybe_unused]] volatile int i = 0;
105 }
106 
107 #if BUILDFLAG(IS_ANDROID) && BUILDFLAG(ENABLE_ARM_CFI_TABLE)
108 class NativeUnwinderAndroidMapDelegateForTesting
109     : public NativeUnwinderAndroidMapDelegate {
110  public:
NativeUnwinderAndroidMapDelegateForTesting(std::unique_ptr<NativeUnwinderAndroidMemoryRegionsMap> memory_regions_map)111   explicit NativeUnwinderAndroidMapDelegateForTesting(
112       std::unique_ptr<NativeUnwinderAndroidMemoryRegionsMap> memory_regions_map)
113       : memory_regions_map_(std::move(memory_regions_map)) {}
114 
GetMapReference()115   NativeUnwinderAndroidMemoryRegionsMap* GetMapReference() override {
116     return memory_regions_map_.get();
117   }
ReleaseMapReference()118   void ReleaseMapReference() override {}
119 
120  private:
121   const std::unique_ptr<NativeUnwinderAndroidMemoryRegionsMap>
122       memory_regions_map_;
123 };
124 
125 // `map_delegate` should outlive the unwinder instance, so we cannot make a
126 // derived `NativeUnwinderAndroidForTesting` to own the `map_delegate`, as
127 // the base class outlives the derived class.
GetMapDelegateForTesting()128 NativeUnwinderAndroidMapDelegateForTesting* GetMapDelegateForTesting() {
129   static base::NoDestructor<NativeUnwinderAndroidMapDelegateForTesting>
130       map_delegate(NativeUnwinderAndroid::CreateMemoryRegionsMap());
131   return map_delegate.get();
132 }
133 
CreateNativeUnwinderAndroidForTesting(uintptr_t exclude_module_with_base_address)134 std::unique_ptr<NativeUnwinderAndroid> CreateNativeUnwinderAndroidForTesting(
135     uintptr_t exclude_module_with_base_address) {
136   return std::make_unique<NativeUnwinderAndroid>(
137       exclude_module_with_base_address, GetMapDelegateForTesting());
138 }
139 
CreateChromeUnwinderAndroid32ForTesting(uintptr_t chrome_module_base_address)140 std::unique_ptr<Unwinder> CreateChromeUnwinderAndroid32ForTesting(
141     uintptr_t chrome_module_base_address) {
142   static constexpr char kCfiFileName[] = "assets/unwind_cfi_32_v2";
143 
144   // The wrapper class ensures that `MemoryMappedFile` has the same lifetime
145   // as the unwinder.
146   class ChromeUnwinderAndroid32ForTesting : public ChromeUnwinderAndroid32 {
147    public:
148     ChromeUnwinderAndroid32ForTesting(
149         std::unique_ptr<MemoryMappedFile> cfi_file,
150         const ChromeUnwindInfoAndroid32& unwind_info,
151         uintptr_t chrome_module_base_address,
152         uintptr_t text_section_start_address)
153         : ChromeUnwinderAndroid32(unwind_info,
154                                   chrome_module_base_address,
155                                   text_section_start_address),
156           cfi_file_(std::move(cfi_file)) {}
157     ~ChromeUnwinderAndroid32ForTesting() override = default;
158 
159    private:
160     std::unique_ptr<MemoryMappedFile> cfi_file_;
161   };
162 
163   MemoryMappedFile::Region cfi_region;
164   int fd = base::android::OpenApkAsset(kCfiFileName, &cfi_region);
165   DCHECK_GT(fd, 0);
166   auto cfi_file = std::make_unique<MemoryMappedFile>();
167   bool ok = cfi_file->Initialize(base::File(fd), cfi_region);
168   DCHECK(ok);
169   return std::make_unique<ChromeUnwinderAndroid32ForTesting>(
170       std::move(cfi_file),
171       base::CreateChromeUnwindInfoAndroid32(
172           {cfi_file->data(), cfi_file->length()}),
173       chrome_module_base_address,
174       /* text_section_start_address= */ base::android::kStartOfText);
175 }
176 #endif  // #if BUILDFLAG(IS_ANDROID) && BUILDFLAG(ENABLE_ARM_CFI_TABLE)
177 
178 }  // namespace
179 
TargetThread(OnceClosure to_run)180 TargetThread::TargetThread(OnceClosure to_run) : to_run_(std::move(to_run)) {}
181 
182 TargetThread::~TargetThread() = default;
183 
Start()184 void TargetThread::Start() {
185   EXPECT_TRUE(PlatformThread::Create(0, this, &target_thread_handle_));
186 }
187 
Join()188 void TargetThread::Join() {
189   PlatformThread::Join(target_thread_handle_);
190 }
191 
ThreadMain()192 void TargetThread::ThreadMain() {
193   thread_token_ = GetSamplingProfilerCurrentThreadToken();
194   std::move(to_run_).Run();
195 }
196 
UnwindScenario(const SetupFunction & setup_function)197 UnwindScenario::UnwindScenario(const SetupFunction& setup_function)
198     : setup_function_(setup_function) {}
199 
200 UnwindScenario::~UnwindScenario() = default;
201 
GetWaitForSampleAddressRange() const202 FunctionAddressRange UnwindScenario::GetWaitForSampleAddressRange() const {
203   return WaitForSample(nullptr);
204 }
205 
GetSetupFunctionAddressRange() const206 FunctionAddressRange UnwindScenario::GetSetupFunctionAddressRange() const {
207   return setup_function_.Run(OnceClosure());
208 }
209 
GetOuterFunctionAddressRange() const210 FunctionAddressRange UnwindScenario::GetOuterFunctionAddressRange() const {
211   return InvokeSetupFunction(SetupFunction(), nullptr);
212 }
213 
Execute(SampleEvents * events)214 void UnwindScenario::Execute(SampleEvents* events) {
215   InvokeSetupFunction(setup_function_, events);
216 }
217 
218 // static
219 // Disable inlining for this function so that it gets its own stack frame.
220 NOINLINE FunctionAddressRange
InvokeSetupFunction(const SetupFunction & setup_function,SampleEvents * events)221 UnwindScenario::InvokeSetupFunction(const SetupFunction& setup_function,
222                                     SampleEvents* events) {
223   const void* start_program_counter = GetProgramCounter();
224 
225   if (!setup_function.is_null()) {
226     const auto wait_for_sample_closure =
227         BindLambdaForTesting([&] { UnwindScenario::WaitForSample(events); });
228     setup_function.Run(wait_for_sample_closure);
229   }
230 
231   // Volatile to prevent a tail call to GetProgramCounter().
232   const void* volatile end_program_counter = GetProgramCounter();
233   return {start_program_counter, end_program_counter};
234 }
235 
236 // static
237 // Disable inlining for this function so that it gets its own stack frame.
238 NOINLINE FunctionAddressRange
WaitForSample(SampleEvents * events)239 UnwindScenario::WaitForSample(SampleEvents* events) {
240   const void* start_program_counter = GetProgramCounter();
241 
242   if (events) {
243     events->ready_for_sample.Signal();
244     events->sample_finished.Wait();
245   }
246 
247   // Volatile to prevent a tail call to GetProgramCounter().
248   const void* volatile end_program_counter = GetProgramCounter();
249   return {start_program_counter, end_program_counter};
250 }
251 
252 // Disable inlining for this function so that it gets its own stack frame.
253 NOINLINE FunctionAddressRange
CallWithPlainFunction(OnceClosure wait_for_sample)254 CallWithPlainFunction(OnceClosure wait_for_sample) {
255   const void* start_program_counter = GetProgramCounter();
256 
257   if (!wait_for_sample.is_null())
258     std::move(wait_for_sample).Run();
259 
260   // Volatile to prevent a tail call to GetProgramCounter().
261   const void* volatile end_program_counter = GetProgramCounter();
262   return {start_program_counter, end_program_counter};
263 }
264 
265 // Disable inlining for this function so that it gets its own stack frame.
CallWithAlloca(OnceClosure wait_for_sample)266 NOINLINE FunctionAddressRange CallWithAlloca(OnceClosure wait_for_sample) {
267   const void* start_program_counter = GetProgramCounter();
268 
269   // Volatile to force a dynamic stack allocation.
270   const volatile size_t alloca_size = 100;
271   // Use the memory via volatile writes to prevent the allocation from being
272   // optimized out.
273   volatile char* const allocation =
274       const_cast<volatile char*>(static_cast<char*>(alloca(alloca_size)));
275   for (volatile char* p = allocation; p < allocation + alloca_size; ++p)
276     *p = '\0';
277 
278   if (!wait_for_sample.is_null())
279     std::move(wait_for_sample).Run();
280 
281   // Volatile to prevent a tail call to GetProgramCounter().
282   const void* volatile end_program_counter = GetProgramCounter();
283   return {start_program_counter, end_program_counter};
284 }
285 
286 // Disable inlining for this function so that it gets its own stack frame.
287 NOINLINE FunctionAddressRange
CallThroughOtherLibrary(NativeLibrary library,OnceClosure wait_for_sample)288 CallThroughOtherLibrary(NativeLibrary library, OnceClosure wait_for_sample) {
289   const void* start_program_counter = GetProgramCounter();
290 
291   if (!wait_for_sample.is_null()) {
292     // A function whose arguments are a function accepting void*, and a void*.
293     using InvokeCallbackFunction = void (*)(void (*)(void*), void*);
294     EXPECT_TRUE(library);
295     InvokeCallbackFunction function = reinterpret_cast<InvokeCallbackFunction>(
296         GetFunctionPointerFromNativeLibrary(library, "InvokeCallbackFunction"));
297     EXPECT_TRUE(function);
298     (*function)(&OtherLibraryCallback, &wait_for_sample);
299   }
300 
301   // Volatile to prevent a tail call to GetProgramCounter().
302   const void* volatile end_program_counter = GetProgramCounter();
303   return {start_program_counter, end_program_counter};
304 }
305 
WithTargetThread(UnwindScenario * scenario,ProfileCallback profile_callback)306 void WithTargetThread(UnwindScenario* scenario,
307                       ProfileCallback profile_callback) {
308   UnwindScenario::SampleEvents events;
309   TargetThread target_thread(
310       BindLambdaForTesting([&] { scenario->Execute(&events); }));
311 
312   target_thread.Start();
313   events.ready_for_sample.Wait();
314 
315   std::move(profile_callback).Run(target_thread.thread_token());
316 
317   events.sample_finished.Signal();
318   target_thread.Join();
319 }
320 
SampleScenario(UnwindScenario * scenario,ModuleCache * module_cache,UnwinderFactory aux_unwinder_factory)321 std::vector<Frame> SampleScenario(UnwindScenario* scenario,
322                                   ModuleCache* module_cache,
323                                   UnwinderFactory aux_unwinder_factory) {
324   StackSamplingProfiler::SamplingParams params;
325   params.sampling_interval = Milliseconds(0);
326   params.samples_per_profile = 1;
327 
328   std::vector<Frame> sample;
329   WithTargetThread(
330       scenario,
331       BindLambdaForTesting(
332           [&](SamplingProfilerThreadToken target_thread_token) {
333             WaitableEvent sampling_thread_completed(
334                 WaitableEvent::ResetPolicy::MANUAL,
335                 WaitableEvent::InitialState::NOT_SIGNALED);
336             StackSamplingProfiler profiler(
337                 target_thread_token, params,
338                 std::make_unique<TestProfileBuilder>(
339                     module_cache,
340                     BindLambdaForTesting([&sample, &sampling_thread_completed](
341                                              std::vector<Frame> result_sample) {
342                       sample = std::move(result_sample);
343                       sampling_thread_completed.Signal();
344                     })),
345                 CreateCoreUnwindersFactoryForTesting(module_cache));
346             if (aux_unwinder_factory)
347               profiler.AddAuxUnwinder(std::move(aux_unwinder_factory).Run());
348             profiler.Start();
349             sampling_thread_completed.Wait();
350           }));
351 
352   return sample;
353 }
354 
FormatSampleForDiagnosticOutput(const std::vector<Frame> & sample)355 std::string FormatSampleForDiagnosticOutput(const std::vector<Frame>& sample) {
356   std::string output;
357   for (const auto& frame : sample) {
358     output += StringPrintf(
359         "0x%p %s\n", reinterpret_cast<const void*>(frame.instruction_pointer),
360         frame.module ? frame.module->GetDebugBasename().AsUTF8Unsafe().c_str()
361                      : "null module");
362   }
363   return output;
364 }
365 
ExpectStackContains(const std::vector<Frame> & stack,const std::vector<FunctionAddressRange> & functions)366 void ExpectStackContains(const std::vector<Frame>& stack,
367                          const std::vector<FunctionAddressRange>& functions) {
368   auto frame_it = stack.begin();
369   auto function_it = functions.begin();
370   for (; frame_it != stack.end() && function_it != functions.end();
371        ++frame_it) {
372     if (frame_it->instruction_pointer >=
373             reinterpret_cast<uintptr_t>(function_it->start.get()) &&
374         frame_it->instruction_pointer <=
375             reinterpret_cast<uintptr_t>(function_it->end.get())) {
376       ++function_it;
377     }
378   }
379 
380   EXPECT_EQ(function_it, functions.end())
381       << "Function in position " << function_it - functions.begin() << " at "
382       << function_it->start << " was not found in stack "
383       << "(or did not appear in the expected order):\n"
384       << FormatSampleForDiagnosticOutput(stack);
385 }
386 
ExpectStackDoesNotContain(const std::vector<Frame> & stack,const std::vector<FunctionAddressRange> & functions)387 void ExpectStackDoesNotContain(
388     const std::vector<Frame>& stack,
389     const std::vector<FunctionAddressRange>& functions) {
390   struct FunctionAddressRangeCompare {
391     bool operator()(const FunctionAddressRange& a,
392                     const FunctionAddressRange& b) const {
393       return std::make_pair(a.start, a.end) < std::make_pair(b.start, b.end);
394     }
395   };
396 
397   std::set<FunctionAddressRange, FunctionAddressRangeCompare> seen_functions;
398   for (const auto& frame : stack) {
399     for (const auto& function : functions) {
400       if (frame.instruction_pointer >=
401               reinterpret_cast<uintptr_t>(function.start.get()) &&
402           frame.instruction_pointer <=
403               reinterpret_cast<uintptr_t>(function.end.get())) {
404         seen_functions.insert(function);
405       }
406     }
407   }
408 
409   for (const auto& function : seen_functions) {
410     ADD_FAILURE() << "Function at " << function.start
411                   << " was unexpectedly found in stack:\n"
412                   << FormatSampleForDiagnosticOutput(stack);
413   }
414 }
415 
LoadTestLibrary(std::string_view library_name)416 NativeLibrary LoadTestLibrary(std::string_view library_name) {
417   // The lambda gymnastics works around the fact that we can't use ASSERT_*
418   // macros in a function returning non-null.
419   const auto load = [&](NativeLibrary* library) {
420     FilePath library_path;
421 #if BUILDFLAG(IS_FUCHSIA) || BUILDFLAG(IS_IOS)
422     // TODO(crbug.com/40799492): Find a solution that works across platforms.
423     ASSERT_TRUE(PathService::Get(DIR_ASSETS, &library_path));
424 #else
425     // The module is next to the test module rather than with test data.
426     ASSERT_TRUE(PathService::Get(DIR_MODULE, &library_path));
427 #endif  // BUILDFLAG(IS_FUCHSIA)
428     library_path =
429         library_path.AppendASCII(GetLoadableModuleName(library_name));
430     NativeLibraryLoadError load_error;
431     *library = LoadNativeLibrary(library_path, &load_error);
432     ASSERT_TRUE(*library) << "error loading " << library_path.value() << ": "
433                           << load_error.ToString();
434   };
435 
436   NativeLibrary library = nullptr;
437   load(&library);
438   return library;
439 }
440 
LoadOtherLibrary()441 NativeLibrary LoadOtherLibrary() {
442   return LoadTestLibrary("base_profiler_test_support_library");
443 }
444 
GetAddressInOtherLibrary(NativeLibrary library)445 uintptr_t GetAddressInOtherLibrary(NativeLibrary library) {
446   EXPECT_TRUE(library);
447   uintptr_t address = reinterpret_cast<uintptr_t>(
448       GetFunctionPointerFromNativeLibrary(library, "InvokeCallbackFunction"));
449   EXPECT_NE(address, 0u);
450   return address;
451 }
452 
CreateCoreUnwindersFactoryForTesting(ModuleCache * module_cache)453 StackSamplingProfiler::UnwindersFactory CreateCoreUnwindersFactoryForTesting(
454     ModuleCache* module_cache) {
455 #if BUILDFLAG(IS_ANDROID) && BUILDFLAG(ENABLE_ARM_CFI_TABLE)
456   std::vector<std::unique_ptr<Unwinder>> unwinders;
457   unwinders.push_back(CreateNativeUnwinderAndroidForTesting(
458       reinterpret_cast<uintptr_t>(&__executable_start)));
459   unwinders.push_back(CreateChromeUnwinderAndroid32ForTesting(
460       reinterpret_cast<uintptr_t>(&__executable_start)));
461   return BindOnce(
462       [](std::vector<std::unique_ptr<Unwinder>> unwinders) {
463         return unwinders;
464       },
465       std::move(unwinders));
466 #else
467   return StackSamplingProfiler::UnwindersFactory();
468 #endif
469 }
470 
GetBaseAddress() const471 uintptr_t TestModule::GetBaseAddress() const {
472   return base_address_;
473 }
GetId() const474 std::string TestModule::GetId() const {
475   return id_;
476 }
GetDebugBasename() const477 FilePath TestModule::GetDebugBasename() const {
478   return debug_basename_;
479 }
GetSize() const480 size_t TestModule::GetSize() const {
481   return size_;
482 }
IsNative() const483 bool TestModule::IsNative() const {
484   return is_native_;
485 }
486 
operator ==(const Frame & a,const Frame & b)487 bool operator==(const Frame& a, const Frame& b) {
488   return a.instruction_pointer == b.instruction_pointer && a.module == b.module;
489 }
490 
491 }  // namespace base
492