// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "crazy_linker_proc_maps.h"

#include <inttypes.h>
#include <limits.h>

#include "elf_traits.h"
#include "crazy_linker_debug.h"
#include "crazy_linker_line_reader.h"
#include "crazy_linker_util.h"
#include "crazy_linker_system.h"

namespace crazy {

namespace {

// Decompose the components of a /proc/$PID/maps file into multiple
// components. |line| should be the address of a zero-terminated line
// of input. On success, returns true and sets |*entry|, false otherwise.
//
// IMPORTANT: On success, |entry->path| will point into the input line,
// the caller will have to copy the string into a different location if
// it needs to persist it.
bool ParseProcMapsLine(const char* line,
                       const char* line_end,
                       ProcMaps::Entry* entry) {
  // Example input lines on a 64-bit system, one cannot assume that
  // everything is properly sized.
  //
  // 00400000-0040b000 r-xp 00000000 08:01 6570708
  // /bin/cat
  // 0060a000-0060b000 r--p 0000a000 08:01 6570708
  // /bin/cat
  // 0060b000-0060c000 rw-p 0000b000 08:01 6570708
  // /bin/cat
  // 01dd0000-01df1000 rw-p 00000000 00:00 0
  // [heap]
  // 7f4b8d4d7000-7f4b8e22a000 r--p 00000000 08:01 38666648
  // /usr/lib/locale/locale-archive
  // 7f4b8e22a000-7f4b8e3df000 r-xp 00000000 08:01 28836281
  // /lib/x86_64-linux-gnu/libc-2.15.so
  // 7f4b8e3df000-7f4b8e5de000 ---p 001b5000 08:01 28836281
  // /lib/x86_64-linux-gnu/libc-2.15.so
  // 7f4b8e5de000-7f4b8e5e2000 r--p 001b4000 08:01 28836281
  // /lib/x86_64-linux-gnu/libc-2.15.so
  // 7f4b8e5e2000-7f4b8e5e4000 rw-p 001b8000 08:01 28836281
  // /lib/x86_64-linux-gnu/libc-2.15.so
  const char* p = line;
  for (int token = 0; token < 7; ++token) {
    char separator = (token == 0) ? '-' : ' ';
    // skip leading token separators first.
    while (p < line_end && *p == separator)
      p++;

    // find start and end of current token, and compute start of
    // next search.
    const char* tok_start = p;
    const char* tok_end =
        static_cast<const char*>(memchr(p, separator, line_end - p));
    if (!tok_end) {
      tok_end = line_end;
      p = line_end;
    } else {
      p = tok_end + 1;
    }

    if (tok_end == tok_start) {
      if (token == 6) {
        // empty token can happen for index 6, when there is no path
        // element on the line. This corresponds to anonymous memory
        // mapped segments.
        entry->path = NULL;
        entry->path_len = 0;
        break;
      }
      return false;
    }

    switch (token) {
      case 0:  // vma_start
        entry->vma_start = static_cast<size_t>(strtoumax(tok_start, NULL, 16));
        break;

      case 1:  // vma_end
        entry->vma_end = static_cast<size_t>(strtoumax(tok_start, NULL, 16));
        break;

      case 2:  // protection bits
      {
        int flags = 0;
        for (const char* t = tok_start; t < tok_end; ++t) {
          if (*t == 'r')
            flags |= PROT_READ;
          if (*t == 'w')
            flags |= PROT_WRITE;
          if (*t == 'x')
            flags |= PROT_EXEC;
        }
        entry->prot_flags = flags;
      } break;

      case 3:  // page offset
        entry->load_offset =
            static_cast<size_t>(strtoumax(tok_start, NULL, 16)) * PAGE_SIZE;
        break;

      case 6:  // path
        // Get rid of trailing newlines, if any.
        while (tok_end > tok_start && tok_end[-1] == '\n')
          tok_end--;
        entry->path = tok_start;
        entry->path_len = tok_end - tok_start;
        break;

      default:  // ignore all other tokens.
        ;
    }
  }
  return true;
}

}  // namespace

// Internal implementation of ProcMaps class.
class ProcMapsInternal {
 public:
  ProcMapsInternal() : index_(0), entries_() {}

  ~ProcMapsInternal() { Reset(); }

  bool Open(const char* path) {
    Reset();
    LineReader reader(path);
    index_ = 0;
    while (reader.GetNextLine()) {
      ProcMaps::Entry entry = {0, };
      if (!ParseProcMapsLine(
               reader.line(), reader.line() + reader.length(), &entry)) {
        // Ignore broken lines.
        continue;
      }

      // Reallocate path.
      const char* old_path = entry.path;
      if (old_path) {
        char* new_path = static_cast<char*>(::malloc(entry.path_len + 1));
        ::memcpy(new_path, old_path, entry.path_len);
        new_path[entry.path_len] = '\0';
        entry.path = const_cast<const char*>(new_path);
      }

      entries_.PushBack(entry);
    }
    return true;
  }

  void Rewind() { index_ = 0; }

  bool GetNextEntry(ProcMaps::Entry* entry) {
    if (index_ >= entries_.GetCount())
      return false;

    *entry = entries_[index_++];
    return true;
  }

 private:
  void Reset() {
    for (size_t n = 0; n < entries_.GetCount(); ++n) {
      ProcMaps::Entry& entry = entries_[n];
      ::free(const_cast<char*>(entry.path));
    }
    entries_.Resize(0);
  }

  size_t index_;
  Vector<ProcMaps::Entry> entries_;
};

ProcMaps::ProcMaps() {
  internal_ = new ProcMapsInternal();
  (void)internal_->Open("/proc/self/maps");
}

ProcMaps::ProcMaps(pid_t pid) {
  internal_ = new ProcMapsInternal();
  char maps_file[32];
  snprintf(maps_file, sizeof maps_file, "/proc/%u/maps", pid);
  (void)internal_->Open(maps_file);
}

ProcMaps::~ProcMaps() { delete internal_; }

void ProcMaps::Rewind() { internal_->Rewind(); }

bool ProcMaps::GetNextEntry(Entry* entry) {
  return internal_->GetNextEntry(entry);
}

int ProcMaps::GetProtectionFlagsForAddress(void* address) {
  size_t vma_addr = reinterpret_cast<size_t>(address);
  internal_->Rewind();
  ProcMaps::Entry entry;
  while (internal_->GetNextEntry(&entry)) {
    if (entry.vma_start <= vma_addr && vma_addr < entry.vma_end)
      return entry.prot_flags;
  }
  return 0;
}

bool FindElfBinaryForAddress(void* address,
                             uintptr_t* load_address,
                             char* path_buffer,
                             size_t path_buffer_len) {
  ProcMaps self_maps;
  ProcMaps::Entry entry;

  uintptr_t addr = reinterpret_cast<uintptr_t>(address);

  while (self_maps.GetNextEntry(&entry)) {
    if (entry.vma_start <= addr && addr < entry.vma_end) {
      *load_address = entry.vma_start;
      if (!entry.path) {
        LOG("Could not find ELF binary path!?\n");
        return false;
      }
      if (entry.path_len >= path_buffer_len) {
        LOG("ELF binary path too long: '%s'\n", entry.path);
        return false;
      }
      memcpy(path_buffer, entry.path, entry.path_len);
      path_buffer[entry.path_len] = '\0';
      return true;
    }
  }
  return false;
}

// Returns the current protection bit flags for the page holding a given
// address. Returns true on success, or false if the address is not mapped.
bool FindProtectionFlagsForAddress(void* address, int* prot_flags) {
  ProcMaps self_maps;
  ProcMaps::Entry entry;

  uintptr_t addr = reinterpret_cast<uintptr_t>(address);

  while (self_maps.GetNextEntry(&entry)) {
    if (entry.vma_start <= addr && addr < entry.vma_end) {
      *prot_flags = entry.prot_flags;
      return true;
    }
  }
  return false;
}

bool FindLoadAddressForFile(const char* file_name,
                            uintptr_t* load_address,
                            uintptr_t* load_offset) {
  size_t file_name_len = strlen(file_name);
  bool is_base_name = (strchr(file_name, '/') == NULL);
  ProcMaps self_maps;
  ProcMaps::Entry entry;

  while (self_maps.GetNextEntry(&entry)) {
    // Skip vDSO et al.
    if (entry.path_len == 0 || entry.path[0] == '[')
      continue;

    const char* entry_name = entry.path;
    size_t entry_len = entry.path_len;

    if (is_base_name) {
      const char* p = reinterpret_cast<const char*>(
          ::memrchr(entry.path, '/', entry.path_len));
      if (p) {
        entry_name = p + 1;
        entry_len = entry.path_len - (p - entry.path) - 1;
      }
    }

    if (file_name_len == entry_len &&
        !memcmp(file_name, entry_name, entry_len)) {
      *load_address = entry.vma_start;
      *load_offset = entry.load_offset;
      return true;
    }
  }

  return false;
}

}  // namespace crazy