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