• 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 }
133 
CreateChromeUnwinderAndroidForTesting(uintptr_t chrome_module_base_address)134 std::unique_ptr<Unwinder> CreateChromeUnwinderAndroidForTesting(
135     uintptr_t chrome_module_base_address) {
136   static constexpr char kCfiFileName[] = "assets/unwind_cfi_32_v2";
137 
138   // The wrapper class ensures that `MemoryMappedFile` has the same lifetime
139   // as the unwinder.
140   class ChromeUnwinderAndroidForTesting : public ChromeUnwinderAndroid {
141    public:
142     ChromeUnwinderAndroidForTesting(std::unique_ptr<MemoryMappedFile> cfi_file,
143                                     const ChromeUnwindInfoAndroid& unwind_info,
144                                     uintptr_t chrome_module_base_address,
145                                     uintptr_t text_section_start_address)
146         : ChromeUnwinderAndroid(unwind_info,
147                                 chrome_module_base_address,
148                                 text_section_start_address),
149           cfi_file_(std::move(cfi_file)) {}
150     ~ChromeUnwinderAndroidForTesting() override = default;
151 
152    private:
153     std::unique_ptr<MemoryMappedFile> cfi_file_;
154   };
155 
156   MemoryMappedFile::Region cfi_region;
157   int fd = base::android::OpenApkAsset(kCfiFileName, &cfi_region);
158   DCHECK_GT(fd, 0);
159   auto cfi_file = std::make_unique<MemoryMappedFile>();
160   bool ok = cfi_file->Initialize(base::File(fd), cfi_region);
161   DCHECK(ok);
162   return std::make_unique<ChromeUnwinderAndroidForTesting>(
163       std::move(cfi_file),
164       base::CreateChromeUnwindInfoAndroid(
165           {cfi_file->data(), cfi_file->length()}),
166       chrome_module_base_address,
167       /* text_section_start_address= */ base::android::kStartOfText);
168 }
169 #endif  // #if BUILDFLAG(IS_ANDROID) && BUILDFLAG(ENABLE_ARM_CFI_TABLE)
170 
171 }  // namespace
172 
TargetThread(OnceClosure to_run)173 TargetThread::TargetThread(OnceClosure to_run) : to_run_(std::move(to_run)) {}
174 
175 TargetThread::~TargetThread() = default;
176 
Start()177 void TargetThread::Start() {
178   EXPECT_TRUE(PlatformThread::Create(0, this, &target_thread_handle_));
179 }
180 
Join()181 void TargetThread::Join() {
182   PlatformThread::Join(target_thread_handle_);
183 }
184 
ThreadMain()185 void TargetThread::ThreadMain() {
186   thread_token_ = GetSamplingProfilerCurrentThreadToken();
187   std::move(to_run_).Run();
188 }
189 
UnwindScenario(const SetupFunction & setup_function)190 UnwindScenario::UnwindScenario(const SetupFunction& setup_function)
191     : setup_function_(setup_function) {}
192 
193 UnwindScenario::~UnwindScenario() = default;
194 
GetWaitForSampleAddressRange() const195 FunctionAddressRange UnwindScenario::GetWaitForSampleAddressRange() const {
196   return WaitForSample(nullptr);
197 }
198 
GetSetupFunctionAddressRange() const199 FunctionAddressRange UnwindScenario::GetSetupFunctionAddressRange() const {
200   return setup_function_.Run(OnceClosure());
201 }
202 
GetOuterFunctionAddressRange() const203 FunctionAddressRange UnwindScenario::GetOuterFunctionAddressRange() const {
204   return InvokeSetupFunction(SetupFunction(), nullptr);
205 }
206 
Execute(SampleEvents * events)207 void UnwindScenario::Execute(SampleEvents* events) {
208   InvokeSetupFunction(setup_function_, events);
209 }
210 
211 // static
212 // Disable inlining for this function so that it gets its own stack frame.
213 NOINLINE FunctionAddressRange
InvokeSetupFunction(const SetupFunction & setup_function,SampleEvents * events)214 UnwindScenario::InvokeSetupFunction(const SetupFunction& setup_function,
215                                     SampleEvents* events) {
216   const void* start_program_counter = GetProgramCounter();
217 
218   if (!setup_function.is_null()) {
219     const auto wait_for_sample_closure =
220         BindLambdaForTesting([&]() { UnwindScenario::WaitForSample(events); });
221     setup_function.Run(wait_for_sample_closure);
222   }
223 
224   // Volatile to prevent a tail call to GetProgramCounter().
225   const void* volatile end_program_counter = GetProgramCounter();
226   return {start_program_counter, end_program_counter};
227 }
228 
229 // static
230 // Disable inlining for this function so that it gets its own stack frame.
231 NOINLINE FunctionAddressRange
WaitForSample(SampleEvents * events)232 UnwindScenario::WaitForSample(SampleEvents* events) {
233   const void* start_program_counter = GetProgramCounter();
234 
235   if (events) {
236     events->ready_for_sample.Signal();
237     events->sample_finished.Wait();
238   }
239 
240   // Volatile to prevent a tail call to GetProgramCounter().
241   const void* volatile end_program_counter = GetProgramCounter();
242   return {start_program_counter, end_program_counter};
243 }
244 
245 // Disable inlining for this function so that it gets its own stack frame.
246 NOINLINE FunctionAddressRange
CallWithPlainFunction(OnceClosure wait_for_sample)247 CallWithPlainFunction(OnceClosure wait_for_sample) {
248   const void* start_program_counter = GetProgramCounter();
249 
250   if (!wait_for_sample.is_null())
251     std::move(wait_for_sample).Run();
252 
253   // Volatile to prevent a tail call to GetProgramCounter().
254   const void* volatile end_program_counter = GetProgramCounter();
255   return {start_program_counter, end_program_counter};
256 }
257 
258 // Disable inlining for this function so that it gets its own stack frame.
CallWithAlloca(OnceClosure wait_for_sample)259 NOINLINE FunctionAddressRange CallWithAlloca(OnceClosure wait_for_sample) {
260   const void* start_program_counter = GetProgramCounter();
261 
262   // Volatile to force a dynamic stack allocation.
263   const volatile size_t alloca_size = 100;
264   // Use the memory via volatile writes to prevent the allocation from being
265   // optimized out.
266   volatile char* const allocation =
267       const_cast<volatile char*>(static_cast<char*>(alloca(alloca_size)));
268   for (volatile char* p = allocation; p < allocation + alloca_size; ++p)
269     *p = '\0';
270 
271   if (!wait_for_sample.is_null())
272     std::move(wait_for_sample).Run();
273 
274   // Volatile to prevent a tail call to GetProgramCounter().
275   const void* volatile end_program_counter = GetProgramCounter();
276   return {start_program_counter, end_program_counter};
277 }
278 
279 // Disable inlining for this function so that it gets its own stack frame.
280 NOINLINE FunctionAddressRange
CallThroughOtherLibrary(NativeLibrary library,OnceClosure wait_for_sample)281 CallThroughOtherLibrary(NativeLibrary library, OnceClosure wait_for_sample) {
282   const void* start_program_counter = GetProgramCounter();
283 
284   if (!wait_for_sample.is_null()) {
285     // A function whose arguments are a function accepting void*, and a void*.
286     using InvokeCallbackFunction = void (*)(void (*)(void*), void*);
287     EXPECT_TRUE(library);
288     InvokeCallbackFunction function = reinterpret_cast<InvokeCallbackFunction>(
289         GetFunctionPointerFromNativeLibrary(library, "InvokeCallbackFunction"));
290     EXPECT_TRUE(function);
291     (*function)(&OtherLibraryCallback, &wait_for_sample);
292   }
293 
294   // Volatile to prevent a tail call to GetProgramCounter().
295   const void* volatile end_program_counter = GetProgramCounter();
296   return {start_program_counter, end_program_counter};
297 }
298 
WithTargetThread(UnwindScenario * scenario,ProfileCallback profile_callback)299 void WithTargetThread(UnwindScenario* scenario,
300                       ProfileCallback profile_callback) {
301   UnwindScenario::SampleEvents events;
302   TargetThread target_thread(
303       BindLambdaForTesting([&]() { scenario->Execute(&events); }));
304 
305   target_thread.Start();
306   events.ready_for_sample.Wait();
307 
308   std::move(profile_callback).Run(target_thread.thread_token());
309 
310   events.sample_finished.Signal();
311   target_thread.Join();
312 }
313 
SampleScenario(UnwindScenario * scenario,ModuleCache * module_cache,UnwinderFactory aux_unwinder_factory)314 std::vector<Frame> SampleScenario(UnwindScenario* scenario,
315                                   ModuleCache* module_cache,
316                                   UnwinderFactory aux_unwinder_factory) {
317   StackSamplingProfiler::SamplingParams params;
318   params.sampling_interval = Milliseconds(0);
319   params.samples_per_profile = 1;
320 
321   std::vector<Frame> sample;
322   WithTargetThread(
323       scenario,
324       BindLambdaForTesting(
325           [&](SamplingProfilerThreadToken target_thread_token) {
326             WaitableEvent sampling_thread_completed(
327                 WaitableEvent::ResetPolicy::MANUAL,
328                 WaitableEvent::InitialState::NOT_SIGNALED);
329             StackSamplingProfiler profiler(
330                 target_thread_token, params,
331                 std::make_unique<TestProfileBuilder>(
332                     module_cache,
333                     BindLambdaForTesting([&sample, &sampling_thread_completed](
334                                              std::vector<Frame> result_sample) {
335                       sample = std::move(result_sample);
336                       sampling_thread_completed.Signal();
337                     })),
338                 CreateCoreUnwindersFactoryForTesting(module_cache));
339             if (aux_unwinder_factory)
340               profiler.AddAuxUnwinder(std::move(aux_unwinder_factory).Run());
341             profiler.Start();
342             sampling_thread_completed.Wait();
343           }));
344 
345   return sample;
346 }
347 
FormatSampleForDiagnosticOutput(const std::vector<Frame> & sample)348 std::string FormatSampleForDiagnosticOutput(const std::vector<Frame>& sample) {
349   std::string output;
350   for (const auto& frame : sample) {
351     output += StringPrintf(
352         "0x%p %s\n", reinterpret_cast<const void*>(frame.instruction_pointer),
353         frame.module ? frame.module->GetDebugBasename().AsUTF8Unsafe().c_str()
354                      : "null module");
355   }
356   return output;
357 }
358 
ExpectStackContains(const std::vector<Frame> & stack,const std::vector<FunctionAddressRange> & functions)359 void ExpectStackContains(const std::vector<Frame>& stack,
360                          const std::vector<FunctionAddressRange>& functions) {
361   auto frame_it = stack.begin();
362   auto function_it = functions.begin();
363   for (; frame_it != stack.end() && function_it != functions.end();
364        ++frame_it) {
365     if (frame_it->instruction_pointer >=
366             reinterpret_cast<uintptr_t>(function_it->start) &&
367         frame_it->instruction_pointer <=
368             reinterpret_cast<uintptr_t>(function_it->end.get())) {
369       ++function_it;
370     }
371   }
372 
373   EXPECT_EQ(function_it, functions.end())
374       << "Function in position " << function_it - functions.begin() << " at "
375       << function_it->start << " was not found in stack "
376       << "(or did not appear in the expected order):\n"
377       << FormatSampleForDiagnosticOutput(stack);
378 }
379 
ExpectStackContainsNames(const std::vector<Frame> & stack,const std::vector<std::string> & function_names)380 void ExpectStackContainsNames(const std::vector<Frame>& stack,
381                               const std::vector<std::string>& function_names) {
382   auto frame_it = stack.begin();
383   auto names_it = function_names.begin();
384   for (; frame_it != stack.end() && names_it != function_names.end();
385        ++frame_it) {
386     if (frame_it->function_name == *names_it) {
387       ++names_it;
388     }
389   }
390 
391   EXPECT_EQ(names_it, function_names.end())
392       << "Function name in position " << names_it - function_names.begin()
393       << " - {" << *names_it << "} was not found in stack "
394       << "(or did not appear in the expected order):\n"
395       << FormatSampleForDiagnosticOutput(stack);
396 }
397 
ExpectStackDoesNotContain(const std::vector<Frame> & stack,const std::vector<FunctionAddressRange> & functions)398 void ExpectStackDoesNotContain(
399     const std::vector<Frame>& stack,
400     const std::vector<FunctionAddressRange>& functions) {
401   struct FunctionAddressRangeCompare {
402     bool operator()(const FunctionAddressRange& a,
403                     const FunctionAddressRange& b) const {
404       return std::make_pair(a.start, a.end) < std::make_pair(b.start, b.end);
405     }
406   };
407 
408   std::set<FunctionAddressRange, FunctionAddressRangeCompare> seen_functions;
409   for (const auto& frame : stack) {
410     for (const auto& function : functions) {
411       if (frame.instruction_pointer >=
412               reinterpret_cast<uintptr_t>(function.start) &&
413           frame.instruction_pointer <=
414               reinterpret_cast<uintptr_t>(function.end.get())) {
415         seen_functions.insert(function);
416       }
417     }
418   }
419 
420   for (const auto& function : seen_functions) {
421     ADD_FAILURE() << "Function at " << function.start
422                   << " was unexpectedly found in stack:\n"
423                   << FormatSampleForDiagnosticOutput(stack);
424   }
425 }
426 
LoadTestLibrary(StringPiece library_name)427 NativeLibrary LoadTestLibrary(StringPiece library_name) {
428   // The lambda gymnastics works around the fact that we can't use ASSERT_*
429   // macros in a function returning non-null.
430   const auto load = [&](NativeLibrary* library) {
431     FilePath library_path;
432 #if BUILDFLAG(IS_FUCHSIA)
433     // TODO(crbug.com/1262430): Find a solution that works across platforms.
434     ASSERT_TRUE(PathService::Get(DIR_ASSETS, &library_path));
435 #else
436     // The module is next to the test module rather than with test data.
437     ASSERT_TRUE(PathService::Get(DIR_MODULE, &library_path));
438 #endif  // BUILDFLAG(IS_FUCHSIA)
439     library_path =
440         library_path.AppendASCII(GetLoadableModuleName(library_name));
441     NativeLibraryLoadError load_error;
442     *library = LoadNativeLibrary(library_path, &load_error);
443     ASSERT_TRUE(*library) << "error loading " << library_path.value() << ": "
444                           << load_error.ToString();
445   };
446 
447   NativeLibrary library = nullptr;
448   load(&library);
449   return library;
450 }
451 
LoadOtherLibrary()452 NativeLibrary LoadOtherLibrary() {
453   return LoadTestLibrary("base_profiler_test_support_library");
454 }
455 
GetAddressInOtherLibrary(NativeLibrary library)456 uintptr_t GetAddressInOtherLibrary(NativeLibrary library) {
457   EXPECT_TRUE(library);
458   uintptr_t address = reinterpret_cast<uintptr_t>(
459       GetFunctionPointerFromNativeLibrary(library, "InvokeCallbackFunction"));
460   EXPECT_NE(address, 0u);
461   return address;
462 }
463 
CreateCoreUnwindersFactoryForTesting(ModuleCache * module_cache)464 StackSamplingProfiler::UnwindersFactory CreateCoreUnwindersFactoryForTesting(
465     ModuleCache* module_cache) {
466 #if BUILDFLAG(IS_ANDROID) && BUILDFLAG(ENABLE_ARM_CFI_TABLE)
467   std::vector<std::unique_ptr<Unwinder>> unwinders;
468   unwinders.push_back(CreateNativeUnwinderAndroidForTesting(
469       reinterpret_cast<uintptr_t>(&__executable_start)));
470   unwinders.push_back(CreateChromeUnwinderAndroidForTesting(
471       reinterpret_cast<uintptr_t>(&__executable_start)));
472   return BindOnce(
473       [](std::vector<std::unique_ptr<Unwinder>> unwinders) {
474         return unwinders;
475       },
476       std::move(unwinders));
477 #else
478   return StackSamplingProfiler::UnwindersFactory();
479 #endif
480 }
481 
GetBaseAddress() const482 uintptr_t TestModule::GetBaseAddress() const {
483   return base_address_;
484 }
GetId() const485 std::string TestModule::GetId() const {
486   return id_;
487 }
GetDebugBasename() const488 FilePath TestModule::GetDebugBasename() const {
489   return debug_basename_;
490 }
GetSize() const491 size_t TestModule::GetSize() const {
492   return size_;
493 }
IsNative() const494 bool TestModule::IsNative() const {
495   return is_native_;
496 }
497 
operator ==(const Frame & a,const Frame & b)498 bool operator==(const Frame& a, const Frame& b) {
499   return a.instruction_pointer == b.instruction_pointer && a.module == b.module;
500 }
501 
502 }  // namespace base
503