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