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