chromium/third_party/crashpad/crashpad/util/linux/ptracer.cc

// Copyright 2017 The Crashpad Authors
//
// 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 "util/linux/ptracer.h"

#include <errno.h>
#include <linux/elf.h>
#include <string.h>
#include <sys/ptrace.h>
#include <sys/uio.h>

#include "base/check_op.h"
#include "base/logging.h"
#include "build/build_config.h"
#include "util/misc/from_pointer_cast.h"

#if defined(ARCH_CPU_X86_FAMILY)
#include <asm/ldt.h>
#endif

namespace crashpad {

namespace {

#if defined(ARCH_CPU_X86_FAMILY)

template <typename Destination>
bool GetRegisterSet(pid_t tid, int set, Destination* dest, bool can_log) {}

bool GetFloatingPointRegisters32(pid_t tid,
                                 FloatContext* context,
                                 bool can_log) {}

bool GetFloatingPointRegisters64(pid_t tid,
                                 FloatContext* context,
                                 bool can_log) {}

bool GetThreadArea32(pid_t tid,
                     const ThreadContext& context,
                     LinuxVMAddress* address,
                     bool can_log) {}

bool GetThreadArea64(pid_t tid,
                     const ThreadContext& context,
                     LinuxVMAddress* address,
                     bool can_log) {}

#elif defined(ARCH_CPU_ARM_FAMILY)

#if defined(ARCH_CPU_ARMEL)
// PTRACE_GETREGSET, introduced in Linux 2.6.34 (2225a122ae26), requires kernel
// support enabled by HAVE_ARCH_TRACEHOOK. This has been set for x86 (including
// x86_64) since Linux 2.6.28 (99bbc4b1e677a), but for ARM only since
// Linux 3.5.0 (0693bf68148c4). Older Linux kernels support PTRACE_GETREGS,
// PTRACE_GETFPREGS, and PTRACE_GETVFPREGS instead, which don't allow checking
// the size of data copied.
//
// Fortunately, 64-bit ARM support only appeared in Linux 3.7.0, so if
// PTRACE_GETREGSET fails on ARM with EIO, indicating that the request is not
// supported, the kernel must be old enough that 64-bit ARM isn’t supported
// either.
//
// TODO(mark): Once helpers to interpret the kernel version are available, add
// a DCHECK to ensure that the kernel is older than 3.5.

bool GetGeneralPurposeRegistersLegacy(pid_t tid,
                                      ThreadContext* context,
                                      bool can_log) {
  if (ptrace(PTRACE_GETREGS, tid, nullptr, &context->t32) != 0) {
    PLOG_IF(ERROR, can_log) << "ptrace";
    return false;
  }
  return true;
}

bool GetFloatingPointRegistersLegacy(pid_t tid,
                                     FloatContext* context,
                                     bool can_log) {
  if (ptrace(PTRACE_GETFPREGS, tid, nullptr, &context->f32.fpregs) != 0) {
    PLOG_IF(ERROR, can_log) << "ptrace";
    return false;
  }
  context->f32.have_fpregs = true;

  if (ptrace(PTRACE_GETVFPREGS, tid, nullptr, &context->f32.vfp) != 0) {
    switch (errno) {
      case EINVAL:
        // These registers are optional on 32-bit ARM cpus
        break;
      default:
        PLOG_IF(ERROR, can_log) << "ptrace";
        return false;
    }
  } else {
    context->f32.have_vfp = true;
  }
  return true;
}
#endif  // ARCH_CPU_ARMEL

// Normally, the Linux kernel will copy out register sets according to the size
// of the struct that contains them. Tracing a 32-bit ARM process running in
// compatibility mode on a 64-bit ARM cpu will only copy data for the number of
// registers times the size of the register, which won't include any possible
// trailing padding in the struct. These are the sizes of the register data, not
// including any possible padding.
constexpr size_t kArmVfpSize = 32 * 8 + 4;

// Target is 32-bit
bool GetFloatingPointRegisters32(pid_t tid,
                                 FloatContext* context,
                                 bool can_log) {
  context->f32.have_fpregs = false;
  context->f32.have_vfp = false;

  iovec iov;
  iov.iov_base = &context->f32.fpregs;
  iov.iov_len = sizeof(context->f32.fpregs);
  if (ptrace(
          PTRACE_GETREGSET, tid, reinterpret_cast<void*>(NT_PRFPREG), &iov) !=
      0) {
    switch (errno) {
#if defined(ARCH_CPU_ARMEL)
      case EIO:
        return GetFloatingPointRegistersLegacy(tid, context, can_log);
#endif  // ARCH_CPU_ARMEL
      case EINVAL:
        // A 32-bit process running on a 64-bit CPU doesn't have this register
        // set. It should have a VFP register set instead.
        break;
      default:
        PLOG_IF(ERROR, can_log) << "ptrace";
        return false;
    }
  } else {
    if (iov.iov_len != sizeof(context->f32.fpregs)) {
      LOG_IF(ERROR, can_log) << "Unexpected registers size " << iov.iov_len
                             << " != " << sizeof(context->f32.fpregs);
      return false;
    }
    context->f32.have_fpregs = true;
  }

  iov.iov_base = &context->f32.vfp;
  iov.iov_len = sizeof(context->f32.vfp);
  if (ptrace(
          PTRACE_GETREGSET, tid, reinterpret_cast<void*>(NT_ARM_VFP), &iov) !=
      0) {
    switch (errno) {
      case EINVAL:
        // VFP may not be present on 32-bit ARM cpus.
        break;
      default:
        PLOG_IF(ERROR, can_log) << "ptrace";
        return false;
    }
  } else {
    if (iov.iov_len != kArmVfpSize && iov.iov_len != sizeof(context->f32.vfp)) {
      LOG_IF(ERROR, can_log) << "Unexpected registers size " << iov.iov_len
                             << " != " << sizeof(context->f32.vfp);
      return false;
    }
    context->f32.have_vfp = true;
  }

  if (!(context->f32.have_fpregs || context->f32.have_vfp)) {
    LOG_IF(ERROR, can_log) << "Unable to collect registers";
    return false;
  }
  return true;
}

bool GetFloatingPointRegisters64(pid_t tid,
                                 FloatContext* context,
                                 bool can_log) {
  iovec iov;
  iov.iov_base = context;
  iov.iov_len = sizeof(*context);
  if (ptrace(
          PTRACE_GETREGSET, tid, reinterpret_cast<void*>(NT_PRFPREG), &iov) !=
      0) {
    PLOG_IF(ERROR, can_log) << "ptrace";
    return false;
  }
  if (iov.iov_len != sizeof(context->f64)) {
    LOG_IF(ERROR, can_log) << "Unexpected registers size " << iov.iov_len
                           << " != " << sizeof(context->f64);
    return false;
  }
  return true;
}

bool GetThreadArea32(pid_t tid,
                     const ThreadContext& context,
                     LinuxVMAddress* address,
                     bool can_log) {
#if defined(ARCH_CPU_ARMEL)
  void* result;
  if (ptrace(PTRACE_GET_THREAD_AREA, tid, nullptr, &result) != 0) {
    PLOG_IF(ERROR, can_log) << "ptrace";
    return false;
  }
  *address = FromPointerCast<LinuxVMAddress>(result);
  return true;
#else
  // TODO(jperaza): it doesn't look like there is a way for a 64-bit ARM process
  // to get the thread area for a 32-bit ARM process with ptrace.
  LOG_IF(WARNING, can_log)
      << "64-bit ARM cannot trace TLS area for a 32-bit process";
  return false;
#endif  // ARCH_CPU_ARMEL
}

bool GetThreadArea64(pid_t tid,
                     const ThreadContext& context,
                     LinuxVMAddress* address,
                     bool can_log) {
  iovec iov;
  iov.iov_base = address;
  iov.iov_len = sizeof(*address);
  if (ptrace(
          PTRACE_GETREGSET, tid, reinterpret_cast<void*>(NT_ARM_TLS), &iov) !=
      0) {
    PLOG_IF(ERROR, can_log) << "ptrace";
    return false;
  }
  if (iov.iov_len != 8) {
    LOG_IF(ERROR, can_log) << "address size mismatch";
    return false;
  }
  return true;
}
#elif defined(ARCH_CPU_MIPS_FAMILY)
// PTRACE_GETREGSET, introduced in Linux 2.6.34 (2225a122ae26), requires kernel
// support enabled by HAVE_ARCH_TRACEHOOK. This has been set for x86 (including
// x86_64) since Linux 2.6.28 (99bbc4b1e677a), but for MIPS only since
// Linux 3.13 (c0ff3c53d4f99). Older Linux kernels support PTRACE_GETREGS,
// and PTRACE_GETFPREGS instead, which don't allow checking the size of data
// copied. Also, PTRACE_GETREGS assumes register size of 64 bits even for 32 bit
// MIPS CPU (contrary to PTRACE_GETREGSET behavior), so we need buffer
// structure here.

bool GetGeneralPurposeRegistersLegacy(pid_t tid,
                                      ThreadContext* context,
                                      bool can_log) {
  ThreadContext context_buffer;
  if (ptrace(PTRACE_GETREGS, tid, nullptr, &context_buffer.t64) != 0) {
    PLOG_IF(ERROR, can_log) << "ptrace";
    return false;
  }
// Bitness of target process can't be determined through ptrace here, so we
// assume target process has the same as current process, making cross-bit
// ptrace unsupported on MIPS for kernels older than 3.13
#if defined(ARCH_CPU_MIPSEL)
#define THREAD_CONTEXT_FIELD
#elif defined(ARCH_CPU_MIPS64EL)
#define THREAD_CONTEXT_FIELD
#endif
  for (size_t reg = 0; reg < 32; ++reg) {
    context->THREAD_CONTEXT_FIELD.regs[reg] = context_buffer.t64.regs[reg];
  }
  context->THREAD_CONTEXT_FIELD.lo = context_buffer.t64.lo;
  context->THREAD_CONTEXT_FIELD.hi = context_buffer.t64.hi;
  context->THREAD_CONTEXT_FIELD.cp0_epc = context_buffer.t64.cp0_epc;
  context->THREAD_CONTEXT_FIELD.cp0_badvaddr = context_buffer.t64.cp0_badvaddr;
  context->THREAD_CONTEXT_FIELD.cp0_status = context_buffer.t64.cp0_status;
  context->THREAD_CONTEXT_FIELD.cp0_cause = context_buffer.t64.cp0_cause;
#undef THREAD_CONTEXT_FIELD
  return true;
}

bool GetFloatingPointRegistersLegacy(pid_t tid,
                                     FloatContext* context,
                                     bool can_log) {
  if (ptrace(PTRACE_GETFPREGS, tid, nullptr, &context->f32.fpregs) != 0) {
    PLOG_IF(ERROR, can_log) << "ptrace";
    return false;
  }
  return true;
}

bool GetFloatingPointRegisters32(pid_t tid,
                                 FloatContext* context,
                                 bool can_log) {
  iovec iov;
  iov.iov_base = &context->f32.fpregs;
  iov.iov_len = sizeof(context->f32.fpregs);
  if (ptrace(PTRACE_GETFPREGS, tid, nullptr, &context->f32.fpregs) != 0) {
    switch (errno) {
      case EINVAL:
        // fp may not be present
        break;
      case EIO:
        return GetFloatingPointRegistersLegacy(tid, context, can_log);
      default:
        PLOG_IF(ERROR, can_log) << "ptrace";
        return false;
    }
  }
  return true;
}

bool GetFloatingPointRegisters64(pid_t tid,
                                 FloatContext* context,
                                 bool can_log) {
  iovec iov;
  iov.iov_base = &context->f64.fpregs;
  iov.iov_len = sizeof(context->f64.fpregs);
  if (ptrace(PTRACE_GETFPREGS, tid, nullptr, &context->f64.fpregs) != 0) {
    switch (errno) {
      case EINVAL:
        // fp may not be present
        break;
      case EIO:
        return GetFloatingPointRegistersLegacy(tid, context, can_log);
      default:
        PLOG_IF(ERROR, can_log) << "ptrace";
        return false;
    }
  }
  return true;
}

bool GetThreadArea32(pid_t tid,
                     const ThreadContext& context,
                     LinuxVMAddress* address,
                     bool can_log) {
#if defined(ARCH_CPU_MIPSEL)
  void* result;
  if (ptrace(PTRACE_GET_THREAD_AREA, tid, nullptr, &result) != 0) {
    PLOG_IF(ERROR, can_log) << "ptrace";
    return false;
  }
  *address = FromPointerCast<LinuxVMAddress>(result);
  return true;
#else
  return false;
#endif
}

bool GetThreadArea64(pid_t tid,
                     const ThreadContext& context,
                     LinuxVMAddress* address,
                     bool can_log) {
  void* result;
#if defined(ARCH_CPU_MIPSEL)
  if (ptrace(PTRACE_GET_THREAD_AREA_3264, tid, nullptr, &result) != 0) {
#else
  if (ptrace(PTRACE_GET_THREAD_AREA, tid, nullptr, &result) != 0) {
#endif
    PLOG_IF(ERROR, can_log) << "ptrace";
    return false;
  }
  *address = FromPointerCast<LinuxVMAddress>(result);
  return true;
}

#elif defined(ARCH_CPU_RISCV64)

bool GetFloatingPointRegisters64(pid_t tid,
                                 FloatContext* context,
                                 bool can_log) {
  iovec iov;
  iov.iov_base = context;
  iov.iov_len = sizeof(*context);
  if (ptrace(
          PTRACE_GETREGSET, tid, reinterpret_cast<void*>(NT_PRFPREG), &iov) !=
      0) {
    PLOG_IF(ERROR, can_log) << "ptrace";
    return false;
  }
  if (iov.iov_len != sizeof(context->f64)) {
    LOG_IF(ERROR, can_log) << "Unexpected registers size " << iov.iov_len
                           << " != " << sizeof(context->f64);
    return false;
  }
  return true;
}

bool GetThreadArea64(pid_t tid,
                     const ThreadContext& context,
                     LinuxVMAddress* address,
                     bool can_log) {
  // Thread pointer register
  *address = context.t64.regs[3];
  return true;
}

#else
#error Port.
#endif  // ARCH_CPU_X86_FAMILY

size_t GetGeneralPurposeRegistersAndLength(pid_t tid,
                                           ThreadContext* context,
                                           bool can_log) {}

#if !defined(ARCH_CPU_RISCV64)
bool GetGeneralPurposeRegisters32(pid_t tid,
                                  ThreadContext* context,
                                  bool can_log) {}
#endif

bool GetGeneralPurposeRegisters64(pid_t tid,
                                  ThreadContext* context,
                                  bool can_log) {}

}  // namespace

Ptracer::Ptracer(bool can_log)
    :{}

Ptracer::Ptracer(bool is_64_bit, bool can_log)
    :{}

Ptracer::~Ptracer() {}

bool Ptracer::Initialize(pid_t pid) {}

bool Ptracer::Is64Bit() {}

bool Ptracer::GetThreadInfo(pid_t tid, ThreadInfo* info) {}

ssize_t Ptracer::ReadUpTo(pid_t pid,
                          LinuxVMAddress address,
                          size_t size,
                          char* buffer) {}

// Handles an EIO by reading at most size bytes from address into buffer if
// address was within a word of a possible page boundary, by aligning to read
// the last word of the page and extracting the desired bytes.
ssize_t Ptracer::ReadLastBytes(pid_t pid,
                               LinuxVMAddress address,
                               size_t size,
                               char* buffer) {}

}  // namespace crashpad