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

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

Merge "Speed up map creation."

parents 06e0c704 60521c7d
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -244,3 +244,17 @@ cc_test {
        },
    },
}

cc_benchmark {
    name: "backtrace_benchmarks",
    defaults: ["libbacktrace_common"],

    srcs: [
        "backtrace_benchmarks.cpp",
    ],

    shared_libs: [
        "libbacktrace",
        "libbase",
    ],
}
+162 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 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 <stdio.h>
#include <string.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include <string>

#include <android-base/file.h>

#include <benchmark/benchmark.h>

#include <backtrace/Backtrace.h>
#include <backtrace/BacktraceMap.h>

// Definitions of prctl arguments to set a vma name in Android kernels.
#define ANDROID_PR_SET_VMA 0x53564d41
#define ANDROID_PR_SET_VMA_ANON_NAME 0

constexpr size_t kNumMaps = 2000;
constexpr size_t kNumIterations = 1000;

static bool CountMaps(pid_t pid, size_t* num_maps) {
  // Minimize the calls that might allocate memory. If too much memory
  // gets allocated, then this routine will add extra maps and the next
  // call will fail to get the same number of maps as before.
  int fd =
      open((std::string("/proc/") + std::to_string(pid) + "/maps").c_str(), O_RDONLY | O_CLOEXEC);
  if (fd == -1) {
    fprintf(stderr, "Cannot open map file for pid %d: %s\n", pid, strerror(errno));
    return false;
  }
  *num_maps = 0;
  while (true) {
    char buffer[2048];
    ssize_t bytes = read(fd, buffer, sizeof(buffer));
    if (bytes <= 0) {
      break;
    }
    // Count the '\n'.
    for (size_t i = 0; i < static_cast<size_t>(bytes); i++) {
      if (buffer[i] == '\n') {
        ++*num_maps;
      }
    }
  }

  close(fd);
  return true;
}

static void CreateMap(benchmark::State& state, BacktraceMap* (*map_func)(pid_t, bool)) {
  state.PauseTiming();
  // Create a remote process so that the map data is exactly the same.
  // Also, so that we can create a set number of maps.
  pid_t pid;
  if ((pid = fork()) == 0) {
    size_t num_maps;
    if (!CountMaps(getpid(), &num_maps)) {
      exit(1);
    }
    // Create uniquely named maps.
    std::vector<void*> maps;
    for (size_t i = num_maps; i < kNumMaps; i++) {
      int flags = PROT_READ | PROT_WRITE;
      // Alternate page type to make sure a map entry is added for each call.
      if ((i % 2) == 0) {
        flags |= PROT_EXEC;
      }
      void* memory = mmap(nullptr, PAGE_SIZE, flags, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
      if (memory == MAP_FAILED) {
        fprintf(stderr, "Failed to create map: %s\n", strerror(errno));
        exit(1);
      }
      memset(memory, 0x1, PAGE_SIZE);
      if (prctl(ANDROID_PR_SET_VMA, ANDROID_PR_SET_VMA_ANON_NAME, memory, PAGE_SIZE, "test_map") ==
          -1) {
        fprintf(stderr, "Failed: %s\n", strerror(errno));
      }
      maps.push_back(memory);
    }

    if (!CountMaps(getpid(), &num_maps)) {
      exit(1);
    }

    if (num_maps != kNumMaps) {
      fprintf(stderr, "Maps set incorrectly: %zu found, %zu expected.\n", num_maps, kNumMaps);
      std::string str;
      android::base::ReadFileToString("/proc/self/maps", &str);
      fprintf(stderr, "%s\n", str.c_str());
      exit(1);
    }

    // Wait for an hour at most.
    sleep(3600);
    exit(1);
  } else if (pid < 0) {
    fprintf(stderr, "Fork failed: %s\n", strerror(errno));
    return;
  }

  size_t num_maps = 0;
  for (size_t i = 0; i < 2000; i++) {
    if (CountMaps(pid, &num_maps) && num_maps == kNumMaps) {
      break;
    }
    usleep(1000);
  }
  if (num_maps != kNumMaps) {
    fprintf(stderr, "Timed out waiting for the number of maps available: %zu\n", num_maps);
    return;
  }

  state.ResumeTiming();
  while (state.KeepRunning()) {
    for (size_t i = 0; i < static_cast<size_t>(state.range(0)); i++) {
      BacktraceMap* map = map_func(pid, false);
      if (map == nullptr) {
        fprintf(stderr, "Failed to create map\n");
        return;
      }
      delete map;
    }
  }
  state.PauseTiming();

  kill(pid, SIGKILL);
  waitpid(pid, nullptr, 0);
}

static void BM_create_map(benchmark::State& state) {
  CreateMap(state, BacktraceMap::Create);
}
BENCHMARK(BM_create_map)->Arg(kNumIterations);

static void BM_create_map_new(benchmark::State& state) {
  CreateMap(state, BacktraceMap::CreateNew);
}
BENCHMARK(BM_create_map_new)->Arg(kNumIterations);

BENCHMARK_MAIN();
+124 −35
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@

#include <android-base/unique_fd.h>

#include <cctype>
#include <memory>
#include <string>
#include <vector>
@@ -55,63 +56,151 @@ MapInfo* Maps::Find(uint64_t pc) {
  return nullptr;
}

bool Maps::ParseLine(const char* line, MapInfo* map_info) {
  char permissions[5];
  int name_pos;
  // Linux /proc/<pid>/maps lines:
// Assumes that line does not end in '\n'.
static bool InternalParseLine(const char* line, MapInfo* map_info) {
  // Do not use a sscanf implementation since it is not performant.

  // Example 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) {
  char* str;
  const char* old_str = line;
  map_info->start = strtoul(old_str, &str, 16);
  if (old_str == str || *str++ != '-') {
    return false;
  }

  old_str = str;
  map_info->end = strtoul(old_str, &str, 16);
  if (old_str == str || !std::isspace(*str++)) {
    return false;
  }
  map_info->flags = PROT_NONE;
  if (permissions[0] == 'r') {

  while (std::isspace(*str)) {
    str++;
  }

  // Parse permissions data.
  if (*str == '\0') {
    return false;
  }
  map_info->flags = 0;
  if (*str == 'r') {
    map_info->flags |= PROT_READ;
  } else if (*str != '-') {
    return false;
  }
  if (permissions[1] == 'w') {
  str++;
  if (*str == 'w') {
    map_info->flags |= PROT_WRITE;
  } else if (*str != '-') {
    return false;
  }
  if (permissions[2] == 'x') {
  str++;
  if (*str == 'x') {
    map_info->flags |= PROT_EXEC;
  } else if (*str != '-') {
    return false;
  }
  str++;
  if (*str != 'p' && *str != 's') {
    return false;
  }
  str++;

  if (!std::isspace(*str++)) {
    return false;
  }

  old_str = str;
  map_info->offset = strtoul(old_str, &str, 16);
  if (old_str == str || !std::isspace(*str)) {
    return false;
  }

  // Ignore the 00:00 values.
  old_str = str;
  (void)strtoul(old_str, &str, 16);
  if (old_str == str || *str++ != ':') {
    return false;
  }
  if (std::isspace(*str)) {
    return false;
  }

  // Skip the inode.
  old_str = str;
  (void)strtoul(str, &str, 16);
  if (old_str == str || !std::isspace(*str++)) {
    return false;
  }

  if (line[name_pos] != '\0') {
    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);
  // Skip decimal digit.
  old_str = str;
  (void)strtoul(old_str, &str, 10);
  if (old_str == str || (!std::isspace(*str) && *str != '\0')) {
    return false;
  }

  while (std::isspace(*str)) {
    str++;
  }
  if (*str == '\0') {
    map_info->name = str;
    return true;
  }

  // Save the name data.
  map_info->name = str;

  // Mark a device map in /dev/ and not in /dev/ashmem/ specially.
  if (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) {
  int fd = open(GetMapsFile().c_str(), O_RDONLY | O_CLOEXEC);
  if (fd == -1) {
    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;
  bool return_value = true;
  char buffer[2048];
  size_t leftover = 0;
  while (true) {
    ssize_t bytes = read(fd, &buffer[leftover], 2048 - leftover);
    if (bytes == -1) {
      return_value = false;
      break;
    }
    if (bytes == 0) {
      break;
    }
    bytes += leftover;
    char* line = buffer;
    while (bytes > 0) {
      char* newline = static_cast<char*>(memchr(line, '\n', bytes));
      if (newline == nullptr) {
        memmove(buffer, line, bytes);
        break;
      }
      *newline = '\0';

    maps_.push_back(map_info);
      MapInfo map_info;
      if (!InternalParseLine(line, &map_info)) {
        return_value = false;
        break;
      }
  free(line);
      maps_.push_back(map_info);

  return valid;
      bytes -= newline - line + 1;
      line = newline + 1;
    }
    leftover = bytes;
  }
  close(fd);
  return return_value;
}

Maps::~Maps() {
@@ -129,12 +218,12 @@ bool BufferMaps::Parse() {
    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);
      end_of_line++;
    }

    MapInfo map_info;
    if (!ParseLine(line.c_str(), &map_info)) {
    if (!InternalParseLine(line.c_str(), &map_info)) {
      return false;
    }
    maps_.push_back(map_info);
+5 −2
Original line number Diff line number Diff line
@@ -38,8 +38,6 @@ class Maps {

  MapInfo* Find(uint64_t pc);

  bool ParseLine(const char* line, MapInfo* map_info);

  virtual bool Parse();

  virtual const std::string GetMapsFile() const { return ""; }
@@ -54,6 +52,11 @@ class Maps {

  size_t Total() { return maps_.size(); }

  MapInfo* Get(size_t index) {
    if (index >= maps_.size()) return nullptr;
    return &maps_[index];
  }

 protected:
  std::vector<MapInfo> maps_;
};
+285 −23
Original line number Diff line number Diff line
@@ -14,9 +14,11 @@
 * limitations under the License.
 */

#include <inttypes.h>
#include <sys/mman.h>

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

@@ -24,13 +26,116 @@

namespace unwindstack {

static void VerifyLine(std::string line, MapInfo* info) {
  BufferMaps maps(line.c_str());

  if (info == nullptr) {
    ASSERT_FALSE(maps.Parse()) << "Failed on: " + line;
  } else {
    ASSERT_TRUE(maps.Parse()) << "Failed on: " + line;
    MapInfo* element = maps.Get(0);
    ASSERT_TRUE(element != nullptr) << "Failed on: " + line;
    *info = *element;
  }
}

TEST(MapsTest, verify_parse_line) {
  MapInfo info;

  VerifyLine("01-02 rwxp 03 04:05 06\n", &info);
  EXPECT_EQ(1U, info.start);
  EXPECT_EQ(2U, info.end);
  EXPECT_EQ(PROT_READ | PROT_WRITE | PROT_EXEC, info.flags);
  EXPECT_EQ(3U, info.offset);
  EXPECT_EQ("", info.name);

  VerifyLine("0a-0b ---s 0c 0d:0e 06 /fake/name\n", &info);
  EXPECT_EQ(0xaU, info.start);
  EXPECT_EQ(0xbU, info.end);
  EXPECT_EQ(0U, info.flags);
  EXPECT_EQ(0xcU, info.offset);
  EXPECT_EQ("/fake/name", info.name);

  VerifyLine("01-02   rwxp   03    04:05    06    /fake/name/again\n", &info);
  EXPECT_EQ(1U, info.start);
  EXPECT_EQ(2U, info.end);
  EXPECT_EQ(PROT_READ | PROT_WRITE | PROT_EXEC, info.flags);
  EXPECT_EQ(3U, info.offset);
  EXPECT_EQ("/fake/name/again", info.name);

  VerifyLine("-00 rwxp 00 00:00 0\n", nullptr);
  VerifyLine("00- rwxp 00 00:00 0\n", nullptr);
  VerifyLine("00-00 rwxp 00 :00 0\n", nullptr);
  VerifyLine("00-00 rwxp 00 00:00 \n", nullptr);
  VerifyLine("x-00 rwxp 00 00:00 0\n", nullptr);
  VerifyLine("00 -00 rwxp 00 00:00 0\n", nullptr);
  VerifyLine("00-x rwxp 00 00:00 0\n", nullptr);
  VerifyLine("00-x rwxp 00 00:00 0\n", nullptr);
  VerifyLine("00-00x rwxp 00 00:00 0\n", nullptr);
  VerifyLine("00-00 rwxp0 00 00:00 0\n", nullptr);
  VerifyLine("00-00 rwxp0 00 00:00 0\n", nullptr);
  VerifyLine("00-00 rwp 00 00:00 0\n", nullptr);
  VerifyLine("00-00 rwxp 0000:00 0\n", nullptr);
  VerifyLine("00-00 rwxp 00 00 :00 0\n", nullptr);
  VerifyLine("00-00 rwxp 00 00: 00 0\n", nullptr);
  VerifyLine("00-00 rwxp 00 00:000\n", nullptr);
  VerifyLine("00-00 rwxp 00 00:00 0/fake\n", nullptr);
  VerifyLine("00-00 xxxx 00 00:00 0 /fake\n", nullptr);
  VerifyLine("00-00 ywxp 00 00:00 0 /fake\n", nullptr);
  VerifyLine("00-00 ryxp 00 00:00 0 /fake\n", nullptr);
  VerifyLine("00-00 rwyp 00 00:00 0 /fake\n", nullptr);
  VerifyLine("00-00 rwx- 00 00:00 0 /fake\n", nullptr);
  VerifyLine("0\n", nullptr);
  VerifyLine("00\n", nullptr);
  VerifyLine("00-\n", nullptr);
  VerifyLine("00-0\n", nullptr);
  VerifyLine("00-00\n", nullptr);
  VerifyLine("00-00 \n", nullptr);
  VerifyLine("00-00 -\n", nullptr);
  VerifyLine("00-00 r\n", nullptr);
  VerifyLine("00-00 --\n", nullptr);
  VerifyLine("00-00 rw\n", nullptr);
  VerifyLine("00-00 ---\n", nullptr);
  VerifyLine("00-00 rwx\n", nullptr);
  VerifyLine("00-00 ---s\n", nullptr);
  VerifyLine("00-00 ---p\n", nullptr);
  VerifyLine("00-00 ---s 0\n", nullptr);
  VerifyLine("00-00 ---p 0 \n", nullptr);
  VerifyLine("00-00 ---p 0 0\n", nullptr);
  VerifyLine("00-00 ---p 0 0:\n", nullptr);
  VerifyLine("00-00 ---p 0 0:0\n", nullptr);
  VerifyLine("00-00 ---p 0 0:0 \n", nullptr);

  // Line to verify that the parser will detect a completely malformed line
  // properly.
  VerifyLine("7ffff7dda000-7ffff7dfd7ffff7ff3000-7ffff7ff4000 ---p 0000f000 fc:02 44171565\n",
             nullptr);
}

TEST(MapsTest, verify_large_values) {
  MapInfo info;
#if defined(__LP64__)
  VerifyLine("fabcdef012345678-f12345678abcdef8 rwxp f0b0d0f010305070 00:00 0\n", &info);
  EXPECT_EQ(0xfabcdef012345678UL, info.start);
  EXPECT_EQ(0xf12345678abcdef8UL, info.end);
  EXPECT_EQ(PROT_READ | PROT_WRITE | PROT_EXEC, info.flags);
  EXPECT_EQ(0xf0b0d0f010305070UL, info.offset);
#else
  VerifyLine("f2345678-fabcdef8 rwxp f0305070 00:00 0\n", &info);
  EXPECT_EQ(0xf2345678UL, info.start);
  EXPECT_EQ(0xfabcdef8UL, info.end);
  EXPECT_EQ(PROT_READ | PROT_WRITE | PROT_EXEC, info.flags);
  EXPECT_EQ(0xf0305070UL, info.offset);
#endif
}

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");
      "1000-2000 ---s 00000000 00:00 0\n"
      "2000-3000 r--s 00000000 00:00 0\n"
      "3000-4000 -w-s 00000000 00:00 0\n"
      "4000-5000 --xp 00000000 00:00 0\n"
      "5000-6000 rwxp 00000000 00:00 0\n");

  ASSERT_TRUE(maps.Parse());
  ASSERT_EQ(5U, maps.Total());
@@ -70,28 +175,28 @@ TEST(MapsTest, parse_permissions) {

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");
      "7b29b000-7b29e000 rw-p 00000000 00:00 0\n"
      "7b29e000-7b29f000 rw-p 00000000 00:00 0 /system/lib/fake.so\n"
      "7b29f000-7b2a0000 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(0x7b29b000U, it->start);
  ASSERT_EQ(0x7b29e000U, 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(0x7b29e000U, it->start);
  ASSERT_EQ(0x7b29f000U, 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(0x7b29f000U, it->start);
  ASSERT_EQ(0x7b2a0000U, it->end);
  ASSERT_EQ(0U, it->offset);
  ASSERT_EQ(PROT_READ | PROT_WRITE, it->flags);
  ++it;
@@ -149,9 +254,9 @@ TEST(MapsTest, file_smoke) {
  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",
      android::base::WriteStringToFile("7b29b000-7b29e000 r-xp a0000000 00:00 0   /fake.so\n"
                                       "7b2b0000-7b2e0000 r-xp b0000000 00:00 0   /fake2.so\n"
                                       "7b2e0000-7b2f0000 r-xp c0000000 00:00 0   /fake3.so\n",
                                       tf.path, 0660, getuid(), getgid()));

  FileMaps maps(tf.path);
@@ -159,20 +264,20 @@ TEST(MapsTest, file_smoke) {
  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(0x7b29b000U, it->start);
  ASSERT_EQ(0x7b29e000U, 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(0x7b2b0000U, it->start);
  ASSERT_EQ(0x7b2e0000U, 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(0x7b2e0000U, it->start);
  ASSERT_EQ(0x7b2f0000U, it->end);
  ASSERT_EQ(0xc0000000U, it->offset);
  ASSERT_EQ(PROT_READ | PROT_EXEC, it->flags);
  ASSERT_EQ("/fake3.so", it->name);
@@ -180,6 +285,163 @@ TEST(MapsTest, file_smoke) {
  ASSERT_EQ(it, maps.end());
}

TEST(MapsTest, file_no_map_name) {
  TemporaryFile tf;
  ASSERT_TRUE(tf.fd != -1);

  ASSERT_TRUE(
      android::base::WriteStringToFile("7b29b000-7b29e000 r-xp a0000000 00:00 0\n"
                                       "7b2b0000-7b2e0000 r-xp b0000000 00:00 0   /fake2.so\n"
                                       "7b2e0000-7b2f0000 r-xp c0000000 00:00 0 \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(0x7b29b000U, it->start);
  ASSERT_EQ(0x7b29e000U, it->end);
  ASSERT_EQ(0xa0000000U, it->offset);
  ASSERT_EQ(PROT_READ | PROT_EXEC, it->flags);
  ASSERT_EQ("", it->name);
  ++it;
  ASSERT_EQ(0x7b2b0000U, it->start);
  ASSERT_EQ(0x7b2e0000U, it->end);
  ASSERT_EQ(0xb0000000U, it->offset);
  ASSERT_EQ(PROT_READ | PROT_EXEC, it->flags);
  ASSERT_EQ("/fake2.so", it->name);
  ++it;
  ASSERT_EQ(0x7b2e0000U, it->start);
  ASSERT_EQ(0x7b2f0000U, it->end);
  ASSERT_EQ(0xc0000000U, it->offset);
  ASSERT_EQ(PROT_READ | PROT_EXEC, it->flags);
  ASSERT_EQ("", it->name);
  ++it;
  ASSERT_EQ(it, maps.end());
}

// Verify that a file that crosses a buffer is parsed correctly.
static std::string CreateEntry(size_t index) {
  return android::base::StringPrintf("%08zx-%08zx rwxp 0000 00:00 0\n", index * 4096,
                                     (index + 1) * 4096);
}

TEST(MapsTest, file_buffer_cross) {
  constexpr size_t kBufferSize = 2048;
  TemporaryFile tf;
  ASSERT_TRUE(tf.fd != -1);

  // Compute how many to add in the first buffer.
  size_t entry_len = CreateEntry(0).size();
  size_t index;
  std::string file_data;
  for (index = 0; index < kBufferSize / entry_len; index++) {
    file_data += CreateEntry(index);
  }
  // Add a long name to make sure that the first buffer does not contain a
  // complete line.
  // Remove the last newline.
  size_t extra = 0;
  size_t leftover = kBufferSize % entry_len;
  size_t overlap1_index = 0;
  std::string overlap1_name;
  if (leftover == 0) {
    // Exact match, add a long name to cross over the value.
    overlap1_name = "/fake/name/is/long/on/purpose";
    file_data.erase(file_data.size() - 1);
    file_data += ' ' + overlap1_name + '\n';
    extra = entry_len + overlap1_name.size() + 1;
    overlap1_index = index;
  }

  // Compute how many need to go in to hit the buffer boundary exactly.
  size_t bytes_left_in_buffer = kBufferSize - extra;
  size_t entries_to_add = bytes_left_in_buffer / entry_len + index;
  for (; index < entries_to_add; index++) {
    file_data += CreateEntry(index);
  }

  // Now figure out how many bytes to add to get exactly to the buffer boundary.
  leftover = bytes_left_in_buffer % entry_len;
  std::string overlap2_name;
  size_t overlap2_index = 0;
  if (leftover != 0) {
    file_data.erase(file_data.size() - 1);
    file_data += ' ';
    overlap2_name = std::string(leftover - 1, 'x');
    file_data += overlap2_name + '\n';
    overlap2_index = index - 1;
  }

  // Now add a few entries on the next page.
  for (size_t start = index; index < start + 10; index++) {
    file_data += CreateEntry(index);
  }

  ASSERT_TRUE(android::base::WriteStringToFile(file_data, tf.path, 0660, getuid(), getgid()));

  FileMaps maps(tf.path);
  ASSERT_TRUE(maps.Parse());
  EXPECT_EQ(index, maps.Total());
  // Verify all of the maps.
  for (size_t i = 0; i < index; i++) {
    MapInfo* info = maps.Get(i);
    ASSERT_TRUE(info != nullptr) << "Failed verifying index " + std::to_string(i);
    EXPECT_EQ(i * 4096, info->start) << "Failed verifying index " + std::to_string(i);
    EXPECT_EQ((i + 1) * 4096, info->end) << "Failed verifying index " + std::to_string(i);
    EXPECT_EQ(0U, info->offset) << "Failed verifying index " + std::to_string(i);
    if (overlap1_index != 0 && i == overlap1_index) {
      EXPECT_EQ(overlap1_name, info->name) << "Failed verifying overlap1 name " + std::to_string(i);
    } else if (overlap2_index != 0 && i == overlap2_index) {
      EXPECT_EQ(overlap2_name, info->name) << "Failed verifying overlap2 name " + std::to_string(i);
    } else {
      EXPECT_EQ("", info->name) << "Failed verifying index " + std::to_string(i);
    }
  }
}

TEST(MapsTest, file_should_fail) {
  TemporaryFile tf;
  ASSERT_TRUE(tf.fd != -1);

  ASSERT_TRUE(android::base::WriteStringToFile(
      "7ffff7dda000-7ffff7dfd7ffff7ff3000-7ffff7ff4000 ---p 0000f000 fc:02 44171565\n", tf.path,
      0660, getuid(), getgid()));

  FileMaps maps(tf.path);

  ASSERT_FALSE(maps.Parse());
}

// Create a maps file that is extremely large.
TEST(MapsTest, large_file) {
  TemporaryFile tf;
  ASSERT_TRUE(tf.fd != -1);

  std::string file_data;
  uint64_t start = 0x700000;
  for (size_t i = 0; i < 5000; i++) {
    file_data +=
        android::base::StringPrintf("%" PRIx64 "-%" PRIx64 " r-xp 1000 00:0 0 /fake%zu.so\n",
                                    start + i * 4096, start + (i + 1) * 4096, i);
  }

  ASSERT_TRUE(android::base::WriteStringToFile(file_data, tf.path, 0660, getuid(), getgid()));

  FileMaps maps(tf.path);

  ASSERT_TRUE(maps.Parse());
  ASSERT_EQ(5000U, maps.Total());
  for (size_t i = 0; i < 5000; i++) {
    MapInfo* info = maps.Get(i);
    ASSERT_EQ(start + i * 4096, info->start) << "Failed at map " + std::to_string(i);
    ASSERT_EQ(start + (i + 1) * 4096, info->end) << "Failed at map " + std::to_string(i);
    std::string name = "/fake" + std::to_string(i) + ".so";
    ASSERT_EQ(name, info->name) << "Failed at map " + std::to_string(i);
  }
}

TEST(MapsTest, find) {
  BufferMaps maps(
      "1000-2000 r--p 00000010 00:00 0 /system/lib/fake1.so\n"