diff --git a/libunwindstack/Android.bp b/libunwindstack/Android.bp index ee646de41..e35593f5c 100644 --- a/libunwindstack/Android.bp +++ b/libunwindstack/Android.bp @@ -54,6 +54,7 @@ cc_defaults { "ElfInterfaceArm.cpp", "Log.cpp", "Regs.cpp", + "Maps.cpp", "Memory.cpp", "Symbols.cpp", ], @@ -97,6 +98,7 @@ cc_defaults { "tests/ElfInterfaceTest.cpp", "tests/ElfTest.cpp", "tests/LogFake.cpp", + "tests/MapsTest.cpp", "tests/MemoryFake.cpp", "tests/MemoryFileTest.cpp", "tests/MemoryLocalTest.cpp", diff --git a/libunwindstack/Maps.cpp b/libunwindstack/Maps.cpp new file mode 100644 index 000000000..b369c435e --- /dev/null +++ b/libunwindstack/Maps.cpp @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2016 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 +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "Maps.h" + +MapInfo* Maps::Find(uint64_t pc) { + if (maps_.empty()) { + return nullptr; + } + size_t first = 0; + size_t last = maps_.size(); + while (first < last) { + size_t index = (first + last) / 2; + MapInfo* cur = &maps_[index]; + if (pc >= cur->start && pc < cur->end) { + return cur; + } else if (pc < cur->start) { + last = index; + } else { + first = index + 1; + } + } + return nullptr; +} + +bool Maps::ParseLine(const char* line, MapInfo* map_info) { + char permissions[5]; + int name_pos; + // Linux /proc//maps lines: + // 6f000000-6f01e000 rwxp 00000000 00:0c 16389419 /system/lib/libcomposer.so + if (sscanf(line, "%" SCNx64 "-%" SCNx64 " %4s %" SCNx64 " %*x:%*x %*d %n", &map_info->start, + &map_info->end, permissions, &map_info->offset, &name_pos) != 4) { + return false; + } + map_info->flags = PROT_NONE; + if (permissions[0] == 'r') { + map_info->flags |= PROT_READ; + } + if (permissions[1] == 'w') { + map_info->flags |= PROT_WRITE; + } + if (permissions[2] == 'x') { + map_info->flags |= PROT_EXEC; + } + + map_info->name = &line[name_pos]; + size_t length = map_info->name.length() - 1; + if (map_info->name[length] == '\n') { + map_info->name.erase(length); + } + // Mark a device map in /dev/and not in /dev/ashmem/ specially. + if (!map_info->name.empty() && map_info->name.substr(0, 5) == "/dev/" && + map_info->name.substr(5, 7) != "ashmem/") { + map_info->flags |= MAPS_FLAGS_DEVICE_MAP; + } + + return true; +} + +bool Maps::Parse() { + std::unique_ptr fp(fopen(GetMapsFile().c_str(), "re"), fclose); + if (!fp) { + return false; + } + + bool valid = true; + char* line = nullptr; + size_t line_len; + while (getline(&line, &line_len, fp.get()) > 0) { + MapInfo map_info; + if (!ParseLine(line, &map_info)) { + valid = false; + break; + } + + maps_.push_back(map_info); + } + free(line); + + return valid; +} + +Maps::~Maps() { + for (auto& map : maps_) { + delete map.elf; + map.elf = nullptr; + } +} + +bool BufferMaps::Parse() { + const char* start_of_line = buffer_; + do { + std::string line; + const char* end_of_line = strchr(start_of_line, '\n'); + if (end_of_line == nullptr) { + line = start_of_line; + } else { + end_of_line++; + line = std::string(start_of_line, end_of_line - start_of_line); + } + + MapInfo map_info; + if (!ParseLine(line.c_str(), &map_info)) { + return false; + } + maps_.push_back(map_info); + + start_of_line = end_of_line; + } while (start_of_line != nullptr && *start_of_line != '\0'); + return true; +} + +const std::string RemoteMaps::GetMapsFile() const { + return "/proc/" + std::to_string(pid_) + "/maps"; +} + +bool OfflineMaps::Parse() { + // Format of maps information: + // StartOffset + // EndOffset + // offset + // flags + // MapNameLength + // MapName + android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(file_.c_str(), O_RDONLY))); + if (fd == -1) { + return false; + } + + std::vector name; + while (true) { + MapInfo map_info; + ssize_t bytes = TEMP_FAILURE_RETRY(read(fd, &map_info.start, sizeof(map_info.start))); + if (bytes == 0) { + break; + } + if (bytes == -1 || bytes != sizeof(map_info.start)) { + return false; + } + bytes = TEMP_FAILURE_RETRY(read(fd, &map_info.end, sizeof(map_info.end))); + if (bytes == -1 || bytes != sizeof(map_info.end)) { + return false; + } + bytes = TEMP_FAILURE_RETRY(read(fd, &map_info.offset, sizeof(map_info.offset))); + if (bytes == -1 || bytes != sizeof(map_info.offset)) { + return false; + } + bytes = TEMP_FAILURE_RETRY(read(fd, &map_info.flags, sizeof(map_info.flags))); + if (bytes == -1 || bytes != sizeof(map_info.flags)) { + return false; + } + uint16_t len; + bytes = TEMP_FAILURE_RETRY(read(fd, &len, sizeof(len))); + if (bytes == -1 || bytes != sizeof(len)) { + return false; + } + if (len > 0) { + name.resize(len); + bytes = TEMP_FAILURE_RETRY(read(fd, name.data(), len)); + if (bytes == -1 || bytes != len) { + return false; + } + map_info.name = std::string(name.data(), len); + } + maps_.push_back(map_info); + } + return true; +} diff --git a/libunwindstack/Maps.h b/libunwindstack/Maps.h new file mode 100644 index 000000000..239b64a23 --- /dev/null +++ b/libunwindstack/Maps.h @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2016 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. + */ + +#ifndef _LIBUNWINDSTACK_MAPS_H +#define _LIBUNWINDSTACK_MAPS_H + +#include +#include + +#include +#include + +#include "Elf.h" +#include "MapInfo.h" + +// Special flag to indicate a map is in /dev/. However, a map in +// /dev/ashmem/... does not set this flag. +static constexpr int MAPS_FLAGS_DEVICE_MAP = 0x8000; + +class Maps { + public: + Maps() = default; + virtual ~Maps(); + + MapInfo* Find(uint64_t pc); + + bool ParseLine(const char* line, MapInfo* map_info); + + virtual bool Parse(); + + virtual const std::string GetMapsFile() const { return ""; } + + typedef std::vector::iterator iterator; + iterator begin() { return maps_.begin(); } + iterator end() { return maps_.end(); } + + typedef std::vector::const_iterator const_iterator; + const_iterator begin() const { return maps_.begin(); } + const_iterator end() const { return maps_.end(); } + + size_t Total() { return maps_.size(); } + + protected: + std::vector maps_; +}; + +class RemoteMaps : public Maps { + public: + RemoteMaps(pid_t pid) : pid_(pid) {} + virtual ~RemoteMaps() = default; + + virtual const std::string GetMapsFile() const override; + + private: + pid_t pid_; +}; + +class LocalMaps : public RemoteMaps { + public: + LocalMaps() : RemoteMaps(getpid()) {} + virtual ~LocalMaps() = default; +}; + +class BufferMaps : public Maps { + public: + BufferMaps(const char* buffer) : buffer_(buffer) {} + virtual ~BufferMaps() = default; + + bool Parse() override; + + private: + const char* buffer_; +}; + +class FileMaps : public Maps { + public: + FileMaps(const std::string& file) : file_(file) {} + virtual ~FileMaps() = default; + + const std::string GetMapsFile() const override { return file_; } + + protected: + const std::string file_; +}; + +class OfflineMaps : public FileMaps { + public: + OfflineMaps(const std::string& file) : FileMaps(file) {} + virtual ~OfflineMaps() = default; + + bool Parse() override; +}; + +#endif // _LIBUNWINDSTACK_MAPS_H diff --git a/libunwindstack/tests/MapsTest.cpp b/libunwindstack/tests/MapsTest.cpp new file mode 100644 index 000000000..7eb9bae7d --- /dev/null +++ b/libunwindstack/tests/MapsTest.cpp @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2016 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 + +#include +#include +#include + +#include "Maps.h" + +TEST(MapsTest, parse_permissions) { + BufferMaps maps( + "1000-2000 ---- 00000000 00:00 0\n" + "2000-3000 r--- 00000000 00:00 0\n" + "3000-4000 -w-- 00000000 00:00 0\n" + "4000-5000 --x- 00000000 00:00 0\n" + "5000-6000 rwx- 00000000 00:00 0\n"); + + ASSERT_TRUE(maps.Parse()); + ASSERT_EQ(5U, maps.Total()); + auto it = maps.begin(); + ASSERT_EQ(PROT_NONE, it->flags); + ASSERT_EQ(0x1000U, it->start); + ASSERT_EQ(0x2000U, it->end); + ASSERT_EQ(0U, it->offset); + ASSERT_EQ("", it->name); + ++it; + ASSERT_EQ(PROT_READ, it->flags); + ASSERT_EQ(0x2000U, it->start); + ASSERT_EQ(0x3000U, it->end); + ASSERT_EQ(0U, it->offset); + ASSERT_EQ("", it->name); + ++it; + ASSERT_EQ(PROT_WRITE, it->flags); + ASSERT_EQ(0x3000U, it->start); + ASSERT_EQ(0x4000U, it->end); + ASSERT_EQ(0U, it->offset); + ASSERT_EQ("", it->name); + ++it; + ASSERT_EQ(PROT_EXEC, it->flags); + ASSERT_EQ(0x4000U, it->start); + ASSERT_EQ(0x5000U, it->end); + ASSERT_EQ(0U, it->offset); + ASSERT_EQ("", it->name); + ++it; + ASSERT_EQ(PROT_READ | PROT_WRITE | PROT_EXEC, it->flags); + ASSERT_EQ(0x5000U, it->start); + ASSERT_EQ(0x6000U, it->end); + ASSERT_EQ(0U, it->offset); + ASSERT_EQ("", it->name); + ++it; + ASSERT_EQ(it, maps.end()); +} + +TEST(MapsTest, parse_name) { + BufferMaps maps( + "720b29b000-720b29e000 rw-p 00000000 00:00 0\n" + "720b29e000-720b29f000 rw-p 00000000 00:00 0 /system/lib/fake.so\n" + "720b29f000-720b2a0000 rw-p 00000000 00:00 0"); + + ASSERT_TRUE(maps.Parse()); + ASSERT_EQ(3U, maps.Total()); + auto it = maps.begin(); + ASSERT_EQ("", it->name); + ASSERT_EQ(0x720b29b000U, it->start); + ASSERT_EQ(0x720b29e000U, it->end); + ASSERT_EQ(0U, it->offset); + ASSERT_EQ(PROT_READ | PROT_WRITE, it->flags); + ++it; + ASSERT_EQ("/system/lib/fake.so", it->name); + ASSERT_EQ(0x720b29e000U, it->start); + ASSERT_EQ(0x720b29f000U, it->end); + ASSERT_EQ(0U, it->offset); + ASSERT_EQ(PROT_READ | PROT_WRITE, it->flags); + ++it; + ASSERT_EQ("", it->name); + ASSERT_EQ(0x720b29f000U, it->start); + ASSERT_EQ(0x720b2a0000U, it->end); + ASSERT_EQ(0U, it->offset); + ASSERT_EQ(PROT_READ | PROT_WRITE, it->flags); + ++it; + ASSERT_EQ(it, maps.end()); +} + +TEST(MapsTest, parse_offset) { + BufferMaps maps( + "a000-e000 rw-p 00000000 00:00 0 /system/lib/fake.so\n" + "e000-f000 rw-p 00a12345 00:00 0 /system/lib/fake.so\n"); + + ASSERT_TRUE(maps.Parse()); + ASSERT_EQ(2U, maps.Total()); + auto it = maps.begin(); + ASSERT_EQ(0U, it->offset); + ASSERT_EQ(0xa000U, it->start); + ASSERT_EQ(0xe000U, it->end); + ASSERT_EQ(PROT_READ | PROT_WRITE, it->flags); + ASSERT_EQ("/system/lib/fake.so", it->name); + ++it; + ASSERT_EQ(0xa12345U, it->offset); + ASSERT_EQ(0xe000U, it->start); + ASSERT_EQ(0xf000U, it->end); + ASSERT_EQ(PROT_READ | PROT_WRITE, it->flags); + ASSERT_EQ("/system/lib/fake.so", it->name); + ++it; + ASSERT_EQ(maps.end(), it); +} + +TEST(MapsTest, device) { + BufferMaps maps( + "a000-e000 rw-p 00000000 00:00 0 /dev/\n" + "f000-f100 rw-p 00000000 00:00 0 /dev/does_not_exist\n" + "f100-f200 rw-p 00000000 00:00 0 /dev/ashmem/does_not_exist\n" + "f200-f300 rw-p 00000000 00:00 0 /devsomething/does_not_exist\n"); + + ASSERT_TRUE(maps.Parse()); + ASSERT_EQ(4U, maps.Total()); + auto it = maps.begin(); + ASSERT_TRUE(it->flags & 0x8000); + ASSERT_EQ("/dev/", it->name); + ++it; + ASSERT_TRUE(it->flags & 0x8000); + ASSERT_EQ("/dev/does_not_exist", it->name); + ++it; + ASSERT_FALSE(it->flags & 0x8000); + ASSERT_EQ("/dev/ashmem/does_not_exist", it->name); + ++it; + ASSERT_FALSE(it->flags & 0x8000); + ASSERT_EQ("/devsomething/does_not_exist", it->name); +} + +TEST(MapsTest, file_smoke) { + TemporaryFile tf; + ASSERT_TRUE(tf.fd != -1); + + ASSERT_TRUE( + android::base::WriteStringToFile("720b29b000-720b29e000 r-xp a0000000 00:00 0 /fake.so\n" + "720b2b0000-720b2e0000 r-xp b0000000 00:00 0 /fake2.so\n" + "720b2e0000-720b2f0000 r-xp c0000000 00:00 0 /fake3.so\n", + tf.path, 0660, getuid(), getgid())); + + FileMaps maps(tf.path); + + ASSERT_TRUE(maps.Parse()); + ASSERT_EQ(3U, maps.Total()); + auto it = maps.begin(); + ASSERT_EQ(0x720b29b000U, it->start); + ASSERT_EQ(0x720b29e000U, it->end); + ASSERT_EQ(0xa0000000U, it->offset); + ASSERT_EQ(PROT_READ | PROT_EXEC, it->flags); + ASSERT_EQ("/fake.so", it->name); + ++it; + ASSERT_EQ(0x720b2b0000U, it->start); + ASSERT_EQ(0x720b2e0000U, it->end); + ASSERT_EQ(0xb0000000U, it->offset); + ASSERT_EQ(PROT_READ | PROT_EXEC, it->flags); + ASSERT_EQ("/fake2.so", it->name); + ++it; + ASSERT_EQ(0x720b2e0000U, it->start); + ASSERT_EQ(0x720b2f0000U, it->end); + ASSERT_EQ(0xc0000000U, it->offset); + ASSERT_EQ(PROT_READ | PROT_EXEC, it->flags); + ASSERT_EQ("/fake3.so", it->name); + ++it; + ASSERT_EQ(it, maps.end()); +} + +TEST(MapsTest, find) { + BufferMaps maps( + "1000-2000 r--p 00000010 00:00 0 /system/lib/fake1.so\n" + "3000-4000 -w-p 00000020 00:00 0 /system/lib/fake2.so\n" + "6000-8000 --xp 00000030 00:00 0 /system/lib/fake3.so\n" + "a000-b000 rw-p 00000040 00:00 0 /system/lib/fake4.so\n" + "e000-f000 rwxp 00000050 00:00 0 /system/lib/fake5.so\n"); + ASSERT_TRUE(maps.Parse()); + ASSERT_EQ(5U, maps.Total()); + + ASSERT_TRUE(maps.Find(0x500) == nullptr); + ASSERT_TRUE(maps.Find(0x2000) == nullptr); + ASSERT_TRUE(maps.Find(0x5010) == nullptr); + ASSERT_TRUE(maps.Find(0x9a00) == nullptr); + ASSERT_TRUE(maps.Find(0xf000) == nullptr); + ASSERT_TRUE(maps.Find(0xf010) == nullptr); + + MapInfo* info = maps.Find(0x1000); + ASSERT_TRUE(info != nullptr); + ASSERT_EQ(0x1000U, info->start); + ASSERT_EQ(0x2000U, info->end); + ASSERT_EQ(0x10U, info->offset); + ASSERT_EQ(PROT_READ, info->flags); + ASSERT_EQ("/system/lib/fake1.so", info->name); + + info = maps.Find(0x3020); + ASSERT_TRUE(info != nullptr); + ASSERT_EQ(0x3000U, info->start); + ASSERT_EQ(0x4000U, info->end); + ASSERT_EQ(0x20U, info->offset); + ASSERT_EQ(PROT_WRITE, info->flags); + ASSERT_EQ("/system/lib/fake2.so", info->name); + + info = maps.Find(0x6020); + ASSERT_TRUE(info != nullptr); + ASSERT_EQ(0x6000U, info->start); + ASSERT_EQ(0x8000U, info->end); + ASSERT_EQ(0x30U, info->offset); + ASSERT_EQ(PROT_EXEC, info->flags); + ASSERT_EQ("/system/lib/fake3.so", info->name); + + info = maps.Find(0xafff); + ASSERT_TRUE(info != nullptr); + ASSERT_EQ(0xa000U, info->start); + ASSERT_EQ(0xb000U, info->end); + ASSERT_EQ(0x40U, info->offset); + ASSERT_EQ(PROT_READ | PROT_WRITE, info->flags); + ASSERT_EQ("/system/lib/fake4.so", info->name); + + info = maps.Find(0xe500); + ASSERT_TRUE(info != nullptr); + ASSERT_EQ(0xe000U, info->start); + ASSERT_EQ(0xf000U, info->end); + ASSERT_EQ(0x50U, info->offset); + ASSERT_EQ(PROT_READ | PROT_WRITE | PROT_EXEC, info->flags); + ASSERT_EQ("/system/lib/fake5.so", info->name); +}