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