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