Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 33e6b18d authored by Christopher Ferris's avatar Christopher Ferris Committed by Gerrit Code Review
Browse files

Merge "Implement maps parsing."

parents a74635d3 09385e7d
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -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",
+196 −0
Original line number Diff line number Diff line
/*
 * 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 <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>

#include <android-base/unique_fd.h>

#include <memory>
#include <string>
#include <vector>

#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/<pid>/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<FILE, decltype(&fclose)> 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:
  //   <uint64_t> StartOffset
  //   <uint64_t> EndOffset
  //   <uint64_t> offset
  //   <uint16_t> flags
  //   <uint16_t> MapNameLength
  //   <VariableLengthValue> MapName
  android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(file_.c_str(), O_RDONLY)));
  if (fd == -1) {
    return false;
  }

  std::vector<char> 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;
}

libunwindstack/Maps.h

0 → 100644
+107 −0
Original line number Diff line number Diff line
/*
 * 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 <sys/types.h>
#include <unistd.h>

#include <string>
#include <vector>

#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<MapInfo>::iterator iterator;
  iterator begin() { return maps_.begin(); }
  iterator end() { return maps_.end(); }

  typedef std::vector<MapInfo>::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<MapInfo> 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
+237 −0
Original line number Diff line number Diff line
/*
 * 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 <sys/mman.h>

#include <android-base/file.h>
#include <android-base/test_utils.h>
#include <gtest/gtest.h>

#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);
}