/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "src/profiling/memory/unwinding.h"
#include "perfetto/base/scoped_file.h"
#include "src/profiling/memory/client.h"
#include "src/profiling/memory/wire_protocol.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include <cxxabi.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unwindstack/RegsGetLocal.h>
namespace perfetto {
namespace profiling {
namespace {
TEST(UnwindingTest, StackOverlayMemoryOverlay) {
base::ScopedFile proc_mem(base::OpenFile("/proc/self/mem", O_RDONLY));
ASSERT_TRUE(proc_mem);
uint8_t fake_stack[1] = {120};
std::shared_ptr<FDMemory> mem(
std::make_shared<FDMemory>(std::move(proc_mem)));
StackOverlayMemory memory(mem, 0u, fake_stack, 1);
uint8_t buf[1] = {};
ASSERT_EQ(memory.Read(0u, buf, 1), 1);
ASSERT_EQ(buf[0], 120);
}
TEST(UnwindingTest, StackOverlayMemoryNonOverlay) {
uint8_t value = 52;
base::ScopedFile proc_mem(base::OpenFile("/proc/self/mem", O_RDONLY));
ASSERT_TRUE(proc_mem);
uint8_t fake_stack[1] = {120};
std::shared_ptr<FDMemory> mem(
std::make_shared<FDMemory>(std::move(proc_mem)));
StackOverlayMemory memory(mem, 0u, fake_stack, 1);
uint8_t buf[1] = {1};
ASSERT_EQ(memory.Read(reinterpret_cast<uint64_t>(&value), buf, 1), 1);
ASSERT_EQ(buf[0], value);
}
TEST(UnwindingTest, FileDescriptorMapsParse) {
base::ScopedFile proc_maps(base::OpenFile("/proc/self/maps", O_RDONLY));
ASSERT_TRUE(proc_maps);
FileDescriptorMaps maps(std::move(proc_maps));
ASSERT_TRUE(maps.Parse());
unwindstack::MapInfo* map_info =
maps.Find(reinterpret_cast<uint64_t>(&proc_maps));
ASSERT_NE(map_info, nullptr);
ASSERT_EQ(map_info->name, "[stack]");
}
// This is needed because ASAN thinks copying the whole stack is a buffer
// underrun.
void __attribute__((noinline))
UnsafeMemcpy(void* dst, const void* src, size_t n)
__attribute__((no_sanitize("address", "hwaddress"))) {
const uint8_t* from = reinterpret_cast<const uint8_t*>(src);
uint8_t* to = reinterpret_cast<uint8_t*>(dst);
for (size_t i = 0; i < n; ++i)
to[i] = from[i];
}
struct RecordMemory {
std::unique_ptr<uint8_t[]> payload;
std::unique_ptr<AllocMetadata> metadata;
};
RecordMemory __attribute__((noinline)) GetRecord(WireMessage* msg) {
std::unique_ptr<AllocMetadata> metadata(new AllocMetadata);
*metadata = {};
const char* stackbase = GetThreadStackBase();
const char* stacktop = reinterpret_cast<char*>(__builtin_frame_address(0));
unwindstack::AsmGetRegs(metadata->register_data);
if (stackbase < stacktop) {
PERFETTO_DFATAL("Stacktop >= stackbase.");
return {nullptr, nullptr};
}
uint64_t stack_size = static_cast<uint64_t>(stackbase - stacktop);
metadata->alloc_size = 10;
metadata->alloc_address = 0x10;
metadata->stack_pointer = reinterpret_cast<uint64_t>(stacktop);
metadata->stack_pointer_offset = sizeof(AllocMetadata);
metadata->arch = unwindstack::Regs::CurrentArch();
metadata->sequence_number = 1;
std::unique_ptr<uint8_t[]> payload(new uint8_t[stack_size]);
UnsafeMemcpy(&payload[0], stacktop, stack_size);
*msg = {};
msg->alloc_header = metadata.get();
msg->payload = reinterpret_cast<char*>(payload.get());
msg->payload_size = static_cast<size_t>(stack_size);
return {std::move(payload), std::move(metadata)};
}
// TODO(rsavitski): Investigate TSAN unwinding.
#if defined(THREAD_SANITIZER)
#define MAYBE_DoUnwind DISABLED_DoUnwind
#else
#define MAYBE_DoUnwind DoUnwind
#endif
TEST(UnwindingTest, MAYBE_DoUnwind) {
base::ScopedFile proc_maps(base::OpenFile("/proc/self/maps", O_RDONLY));
base::ScopedFile proc_mem(base::OpenFile("/proc/self/mem", O_RDONLY));
GlobalCallstackTrie callsites;
UnwindingMetadata metadata(getpid(), std::move(proc_maps),
std::move(proc_mem));
WireMessage msg;
auto record = GetRecord(&msg);
AllocRecord out;
ASSERT_TRUE(DoUnwind(&msg, &metadata, &out));
int st;
std::unique_ptr<char, base::FreeDeleter> demangled(abi::__cxa_demangle(
out.frames[0].frame.function_name.c_str(), nullptr, nullptr, &st));
ASSERT_EQ(st, 0);
ASSERT_STREQ(demangled.get(),
"perfetto::profiling::(anonymous "
"namespace)::GetRecord(perfetto::profiling::WireMessage*)");
}
} // namespace
} // namespace profiling
} // namespace perfetto