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