2017-03-14 13:05:40 -07:00
|
|
|
|
// Copyright 2017 The Crashpad Authors. All rights reserved.
|
|
|
|
|
//
|
|
|
|
|
// 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/posix/process_info.h"
|
|
|
|
|
|
|
|
|
|
#include <ctype.h>
|
|
|
|
|
#include <elf.h>
|
2017-03-15 13:12:35 -04:00
|
|
|
|
#include <errno.h>
|
2017-03-14 13:05:40 -07:00
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <sys/ptrace.h>
|
|
|
|
|
#include <sys/uio.h>
|
2017-03-14 18:01:55 -04:00
|
|
|
|
#include <sys/user.h>
|
2017-03-14 13:05:40 -07:00
|
|
|
|
#include <sys/wait.h>
|
|
|
|
|
#include <time.h>
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
2017-03-16 10:52:27 -04:00
|
|
|
|
#include "base/files/file_path.h"
|
2017-03-14 13:05:40 -07:00
|
|
|
|
#include "base/files/scoped_file.h"
|
|
|
|
|
#include "base/logging.h"
|
|
|
|
|
#include "base/posix/eintr_wrapper.h"
|
|
|
|
|
#include "base/strings/string_number_conversions.h"
|
|
|
|
|
#include "base/strings/string_piece.h"
|
2017-03-16 10:52:27 -04:00
|
|
|
|
#include "util/file/delimited_file_reader.h"
|
2017-03-14 13:05:40 -07:00
|
|
|
|
#include "util/file/file_reader.h"
|
|
|
|
|
|
|
|
|
|
namespace crashpad {
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
// If the string |pattern| is matched exactly at the start of |input|, advance
|
|
|
|
|
// |input| past |pattern| and return true.
|
|
|
|
|
bool AdvancePastPrefix(const char** input, const char* pattern) {
|
|
|
|
|
size_t length = strlen(pattern);
|
|
|
|
|
if (strncmp(*input, pattern, length) == 0) {
|
|
|
|
|
*input += length;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#define MAKE_ADAPTER(type, function) \
|
|
|
|
|
bool ConvertStringToNumber(const base::StringPiece& input, type* value) { \
|
|
|
|
|
return function(input, value); \
|
|
|
|
|
}
|
|
|
|
|
MAKE_ADAPTER(int, base::StringToInt)
|
|
|
|
|
MAKE_ADAPTER(unsigned int, base::StringToUint)
|
|
|
|
|
MAKE_ADAPTER(uint64_t, base::StringToUint64)
|
|
|
|
|
#undef MAKE_ADAPTER
|
|
|
|
|
|
|
|
|
|
// Attempt to convert a prefix of |input| to numeric type T. On success, set
|
|
|
|
|
// |value| to the number, advance |input| past the number, and return true.
|
|
|
|
|
template <typename T>
|
|
|
|
|
bool AdvancePastNumber(const char** input, T* value) {
|
|
|
|
|
size_t length = 0;
|
|
|
|
|
if (std::numeric_limits<T>::is_signed && **input == '-') {
|
|
|
|
|
++length;
|
|
|
|
|
}
|
|
|
|
|
while (isdigit((*input)[length])) {
|
|
|
|
|
++length;
|
|
|
|
|
}
|
|
|
|
|
bool success = ConvertStringToNumber(base::StringPiece(*input, length),
|
|
|
|
|
value);
|
|
|
|
|
if (success) {
|
|
|
|
|
*input += length;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ReadEntireFile(const char* path, std::string* contents) {
|
|
|
|
|
FileReader file;
|
|
|
|
|
if (!file.Open(base::FilePath(path))) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
char buffer[4096];
|
|
|
|
|
FileOperationResult length;
|
|
|
|
|
while ((length = file.Read(buffer, sizeof(buffer))) > 0) {
|
|
|
|
|
contents->append(buffer, length);
|
|
|
|
|
}
|
|
|
|
|
return length >= 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SubtractTimespec(const timespec& t1, const timespec& t2,
|
|
|
|
|
timespec* result) {
|
|
|
|
|
result->tv_sec = t1.tv_sec - t2.tv_sec;
|
|
|
|
|
result->tv_nsec = t1.tv_nsec - t2.tv_nsec;
|
|
|
|
|
if (result->tv_nsec < 0) {
|
|
|
|
|
result->tv_sec -= 1;
|
|
|
|
|
result->tv_nsec += static_cast<long>(1E9);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TimespecToTimeval(const timespec& ts, timeval* tv) {
|
|
|
|
|
tv->tv_sec = ts.tv_sec;
|
|
|
|
|
tv->tv_usec = ts.tv_nsec / 1000;
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-15 00:09:28 -04:00
|
|
|
|
class ScopedPtraceDetach {
|
|
|
|
|
public:
|
|
|
|
|
explicit ScopedPtraceDetach(pid_t pid) : pid_(pid) {}
|
|
|
|
|
~ScopedPtraceDetach() {
|
|
|
|
|
if (ptrace(PTRACE_DETACH, pid_, nullptr, nullptr) != 0) {
|
|
|
|
|
PLOG(ERROR) << "ptrace";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
pid_t pid_;
|
|
|
|
|
|
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(ScopedPtraceDetach);
|
|
|
|
|
};
|
|
|
|
|
|
2017-03-14 13:05:40 -07:00
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
ProcessInfo::ProcessInfo()
|
|
|
|
|
: supplementary_groups_(),
|
|
|
|
|
start_time_(),
|
|
|
|
|
pid_(-1),
|
|
|
|
|
ppid_(-1),
|
|
|
|
|
uid_(-1),
|
|
|
|
|
euid_(-1),
|
|
|
|
|
suid_(-1),
|
|
|
|
|
gid_(-1),
|
|
|
|
|
egid_(-1),
|
|
|
|
|
sgid_(-1),
|
2017-03-15 00:09:28 -04:00
|
|
|
|
start_time_initialized_(),
|
|
|
|
|
is_64_bit_initialized_(),
|
2017-03-14 13:05:40 -07:00
|
|
|
|
is_64_bit_(false),
|
|
|
|
|
initialized_() {}
|
|
|
|
|
|
|
|
|
|
ProcessInfo::~ProcessInfo() {}
|
|
|
|
|
|
|
|
|
|
bool ProcessInfo::Initialize(pid_t pid) {
|
|
|
|
|
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
|
|
|
|
|
|
|
|
|
|
pid_ = pid;
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
char path[32];
|
|
|
|
|
snprintf(path, sizeof(path), "/proc/%d/status", pid_);
|
2017-03-16 10:52:27 -04:00
|
|
|
|
FileReader status_file;
|
|
|
|
|
if (!status_file.Open(base::FilePath(path))) {
|
2017-03-14 13:05:40 -07:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-16 10:52:27 -04:00
|
|
|
|
DelimitedFileReader status_file_line_reader(&status_file);
|
2017-03-14 13:05:40 -07:00
|
|
|
|
|
|
|
|
|
bool have_ppid = false;
|
|
|
|
|
bool have_uids = false;
|
|
|
|
|
bool have_gids = false;
|
|
|
|
|
bool have_groups = false;
|
2017-03-16 10:52:27 -04:00
|
|
|
|
std::string line;
|
|
|
|
|
DelimitedFileReader::Result result;
|
|
|
|
|
while ((result = status_file_line_reader.GetLine(&line)) ==
|
|
|
|
|
DelimitedFileReader::Result::kSuccess) {
|
|
|
|
|
if (line.back() != '\n') {
|
|
|
|
|
LOG(ERROR) << "format error: unterminated line at EOF";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2017-03-14 13:05:40 -07:00
|
|
|
|
|
2017-03-16 10:52:27 -04:00
|
|
|
|
bool understood_line = false;
|
|
|
|
|
const char* line_c = line.c_str();
|
|
|
|
|
if (AdvancePastPrefix(&line_c, "PPid:\t")) {
|
2017-03-14 13:05:40 -07:00
|
|
|
|
if (have_ppid) {
|
|
|
|
|
LOG(ERROR) << "format error: multiple PPid lines";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2017-03-16 10:52:27 -04:00
|
|
|
|
have_ppid = AdvancePastNumber(&line_c, &ppid_);
|
2017-03-14 13:05:40 -07:00
|
|
|
|
if (!have_ppid) {
|
|
|
|
|
LOG(ERROR) << "format error: unrecognized PPid format";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2017-03-16 10:52:27 -04:00
|
|
|
|
understood_line = true;
|
|
|
|
|
} else if (AdvancePastPrefix(&line_c, "Uid:\t")) {
|
2017-03-14 13:05:40 -07:00
|
|
|
|
if (have_uids) {
|
|
|
|
|
LOG(ERROR) << "format error: multiple Uid lines";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2017-03-16 10:52:27 -04:00
|
|
|
|
uid_t fsuid;
|
|
|
|
|
have_uids = AdvancePastNumber(&line_c, &uid_) &&
|
|
|
|
|
AdvancePastPrefix(&line_c, "\t") &&
|
|
|
|
|
AdvancePastNumber(&line_c, &euid_) &&
|
|
|
|
|
AdvancePastPrefix(&line_c, "\t") &&
|
|
|
|
|
AdvancePastNumber(&line_c, &suid_) &&
|
|
|
|
|
AdvancePastPrefix(&line_c, "\t") &&
|
|
|
|
|
AdvancePastNumber(&line_c, &fsuid);
|
2017-03-14 13:05:40 -07:00
|
|
|
|
if (!have_uids) {
|
|
|
|
|
LOG(ERROR) << "format error: unrecognized Uid format";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2017-03-16 10:52:27 -04:00
|
|
|
|
understood_line = true;
|
|
|
|
|
} else if (AdvancePastPrefix(&line_c, "Gid:\t")) {
|
2017-03-14 13:05:40 -07:00
|
|
|
|
if (have_gids) {
|
|
|
|
|
LOG(ERROR) << "format error: multiple Gid lines";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2017-03-16 10:52:27 -04:00
|
|
|
|
gid_t fsgid;
|
|
|
|
|
have_gids = AdvancePastNumber(&line_c, &gid_) &&
|
|
|
|
|
AdvancePastPrefix(&line_c, "\t") &&
|
|
|
|
|
AdvancePastNumber(&line_c, &egid_) &&
|
|
|
|
|
AdvancePastPrefix(&line_c, "\t") &&
|
|
|
|
|
AdvancePastNumber(&line_c, &sgid_) &&
|
|
|
|
|
AdvancePastPrefix(&line_c, "\t") &&
|
|
|
|
|
AdvancePastNumber(&line_c, &fsgid);
|
2017-03-14 13:05:40 -07:00
|
|
|
|
if (!have_gids) {
|
|
|
|
|
LOG(ERROR) << "format error: unrecognized Gid format";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2017-03-16 10:52:27 -04:00
|
|
|
|
understood_line = true;
|
|
|
|
|
} else if (AdvancePastPrefix(&line_c, "Groups:\t")) {
|
2017-03-14 13:05:40 -07:00
|
|
|
|
if (have_groups) {
|
|
|
|
|
LOG(ERROR) << "format error: multiple Groups lines";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
gid_t group;
|
2017-03-16 10:52:27 -04:00
|
|
|
|
while (AdvancePastNumber(&line_c, &group)) {
|
2017-03-14 13:05:40 -07:00
|
|
|
|
supplementary_groups_.insert(group);
|
2017-03-16 10:52:27 -04:00
|
|
|
|
if (!AdvancePastPrefix(&line_c, " ")) {
|
|
|
|
|
LOG(ERROR) << "format error: unrecognized Groups format";
|
2017-03-14 13:05:40 -07:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
have_groups = true;
|
2017-03-16 10:52:27 -04:00
|
|
|
|
understood_line = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (understood_line && line_c != &line.back()) {
|
|
|
|
|
LOG(ERROR) << "format error: unconsumed trailing data";
|
|
|
|
|
return false;
|
2017-03-14 13:05:40 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
2017-03-16 10:52:27 -04:00
|
|
|
|
if (result != DelimitedFileReader::Result::kEndOfFile) {
|
2017-03-14 13:05:40 -07:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (!have_ppid || !have_uids || !have_gids || !have_groups) {
|
|
|
|
|
LOG(ERROR) << "format error: missing fields";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-15 00:09:28 -04:00
|
|
|
|
INITIALIZATION_STATE_SET_VALID(initialized_);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pid_t ProcessInfo::ProcessID() const {
|
|
|
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
|
|
|
return pid_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pid_t ProcessInfo::ParentProcessID() const {
|
|
|
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
|
|
|
return ppid_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uid_t ProcessInfo::RealUserID() const {
|
|
|
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
|
|
|
return uid_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uid_t ProcessInfo::EffectiveUserID() const {
|
|
|
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
|
|
|
return euid_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uid_t ProcessInfo::SavedUserID() const {
|
|
|
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
|
|
|
return suid_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gid_t ProcessInfo::RealGroupID() const {
|
|
|
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
|
|
|
return gid_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gid_t ProcessInfo::EffectiveGroupID() const {
|
|
|
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
|
|
|
return egid_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gid_t ProcessInfo::SavedGroupID() const {
|
|
|
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
|
|
|
return sgid_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::set<gid_t> ProcessInfo::SupplementaryGroups() const {
|
|
|
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
|
|
|
return supplementary_groups_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::set<gid_t> ProcessInfo::AllGroups() const {
|
|
|
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
|
|
|
|
|
|
|
|
std::set<gid_t> all_groups = SupplementaryGroups();
|
|
|
|
|
all_groups.insert(RealGroupID());
|
|
|
|
|
all_groups.insert(EffectiveGroupID());
|
|
|
|
|
all_groups.insert(SavedGroupID());
|
|
|
|
|
return all_groups;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ProcessInfo::DidChangePrivileges() const {
|
|
|
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
|
|
|
// TODO(jperaza): Is this possible to determine?
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ProcessInfo::Is64Bit(bool* is_64_bit) const {
|
|
|
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
|
|
|
|
|
|
|
|
if (is_64_bit_initialized_.is_uninitialized()) {
|
|
|
|
|
is_64_bit_initialized_.set_invalid();
|
|
|
|
|
|
|
|
|
|
#if defined(ARCH_CPU_64_BITS)
|
|
|
|
|
const bool am_64_bit = true;
|
|
|
|
|
#else
|
|
|
|
|
const bool am_64_bit = false;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
if (pid_ == getpid()) {
|
|
|
|
|
is_64_bit_ = am_64_bit;
|
|
|
|
|
} else {
|
|
|
|
|
if (ptrace(PTRACE_ATTACH, pid_, nullptr, nullptr) != 0) {
|
|
|
|
|
PLOG(ERROR) << "ptrace";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ScopedPtraceDetach ptrace_detach(pid_);
|
|
|
|
|
|
|
|
|
|
if (HANDLE_EINTR(waitpid(pid_, nullptr, __WALL)) < 0) {
|
|
|
|
|
PLOG(ERROR) << "waitpid";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Allocate more buffer space than is required to hold registers for this
|
|
|
|
|
// process. If the kernel fills the extra space, the target process uses
|
|
|
|
|
// more/larger registers than this process. If the kernel fills less space
|
|
|
|
|
// than sizeof(regs) then the target process uses smaller/fewer registers.
|
|
|
|
|
struct {
|
linux: Use user_regs instead of pt_regs for 32-bit ARM in ProcessInfo
Not all libc implementations reliably expose pt_regs from
<sys/ptrace.h>. glibc-2.25/sysdeps/generic/sys/ptrace.h, for example,
does not #include <asm/ptrace.h> (which defines the structure) or
anything else that would #include that file such as <linux/ptrace.h>. On
the other hand, Android 7.1.1 bionic/libc/include/sys/ptrace.h does
#include <linux/ptrace.h>.
It is not viable to #include <asm/ptrace.h> or <linux/ptrace.h>
directly: it would be natural to #include them, sorted, before
<sys/ptrace.h> but this causes problems for glibc’s <sys/ptrace.h>.
Constants like PTRACE_GETREGS and PTRACE_TRACEME are simple macros in
<asm/ptrace.h> and <linux/ptrace.h>, respectively, but are defined in
enums in glibc’s <sys/ptrace.h>, and this doesn’t mix well. It is
possible to #include <asm/ptrace.h> (but not <linux/ptrace.h>) after
<sys/ptrace.h>, but because this involves same-value macro redefinitions
and because it reaches into internal headers, it’s not preferred.
The alternative approach taken here is to use the user_regs structure
from <sys/user.h>, which is reliably defined by both Bionic and glibc,
and has the same layout as the kernel’s pt_regs structure. (All that
matters in this code is the size of the structure.) See Android 7.1.1
bionic/libc/include/sys/user.h,
glibc-2.25/sysdeps/unix/sysv/linux/arm/sys/user.h, and
linux-4.9.15/arch/arm/include/asm/ptrace.h for the various equivalent
definitions.
Take the same approach for 64-bit ARM: use user_regs_struct from
<sys/user.h> in preference to hoping for a C library’s <sys/ptrace.h> to
somehow provide the kernel’s user_pt_regs.
This mirrors the approach already being used for x86 and x86_64, which
use the C library’s <sys/user.h> user_regs_struct.
Bug: crashpad:30
Test: crashpad_util_test ProcessInfo.*
Change-Id: I3067e32c7fa4d6c8f4f2d5b63df141a0f490cd13
Reviewed-on: https://chromium-review.googlesource.com/455558
Reviewed-by: Joshua Peraza <jperaza@chromium.org>
2017-03-15 12:05:02 -04:00
|
|
|
|
#if defined(ARCH_CPU_X86_FAMILY) || defined(ARCH_CPU_ARM64)
|
2017-03-15 00:09:28 -04:00
|
|
|
|
using PrStatusType = user_regs_struct;
|
|
|
|
|
#elif defined(ARCH_CPU_ARMEL)
|
linux: Use user_regs instead of pt_regs for 32-bit ARM in ProcessInfo
Not all libc implementations reliably expose pt_regs from
<sys/ptrace.h>. glibc-2.25/sysdeps/generic/sys/ptrace.h, for example,
does not #include <asm/ptrace.h> (which defines the structure) or
anything else that would #include that file such as <linux/ptrace.h>. On
the other hand, Android 7.1.1 bionic/libc/include/sys/ptrace.h does
#include <linux/ptrace.h>.
It is not viable to #include <asm/ptrace.h> or <linux/ptrace.h>
directly: it would be natural to #include them, sorted, before
<sys/ptrace.h> but this causes problems for glibc’s <sys/ptrace.h>.
Constants like PTRACE_GETREGS and PTRACE_TRACEME are simple macros in
<asm/ptrace.h> and <linux/ptrace.h>, respectively, but are defined in
enums in glibc’s <sys/ptrace.h>, and this doesn’t mix well. It is
possible to #include <asm/ptrace.h> (but not <linux/ptrace.h>) after
<sys/ptrace.h>, but because this involves same-value macro redefinitions
and because it reaches into internal headers, it’s not preferred.
The alternative approach taken here is to use the user_regs structure
from <sys/user.h>, which is reliably defined by both Bionic and glibc,
and has the same layout as the kernel’s pt_regs structure. (All that
matters in this code is the size of the structure.) See Android 7.1.1
bionic/libc/include/sys/user.h,
glibc-2.25/sysdeps/unix/sysv/linux/arm/sys/user.h, and
linux-4.9.15/arch/arm/include/asm/ptrace.h for the various equivalent
definitions.
Take the same approach for 64-bit ARM: use user_regs_struct from
<sys/user.h> in preference to hoping for a C library’s <sys/ptrace.h> to
somehow provide the kernel’s user_pt_regs.
This mirrors the approach already being used for x86 and x86_64, which
use the C library’s <sys/user.h> user_regs_struct.
Bug: crashpad:30
Test: crashpad_util_test ProcessInfo.*
Change-Id: I3067e32c7fa4d6c8f4f2d5b63df141a0f490cd13
Reviewed-on: https://chromium-review.googlesource.com/455558
Reviewed-by: Joshua Peraza <jperaza@chromium.org>
2017-03-15 12:05:02 -04:00
|
|
|
|
using PrStatusType = user_regs;
|
2017-03-15 00:09:28 -04:00
|
|
|
|
#endif
|
|
|
|
|
PrStatusType regs;
|
|
|
|
|
char extra;
|
|
|
|
|
} regbuf;
|
|
|
|
|
|
|
|
|
|
iovec iov;
|
|
|
|
|
iov.iov_base = ®buf;
|
|
|
|
|
iov.iov_len = sizeof(regbuf);
|
|
|
|
|
if (ptrace(PTRACE_GETREGSET,
|
|
|
|
|
pid_,
|
|
|
|
|
reinterpret_cast<void*>(NT_PRSTATUS),
|
|
|
|
|
&iov) != 0) {
|
2017-03-15 13:12:35 -04:00
|
|
|
|
switch (errno) {
|
|
|
|
|
#if defined(ARCH_CPU_ARMEL)
|
|
|
|
|
case EIO:
|
|
|
|
|
// 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). 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.
|
|
|
|
|
is_64_bit_ = false;
|
|
|
|
|
break;
|
|
|
|
|
#endif
|
|
|
|
|
default:
|
|
|
|
|
PLOG(ERROR) << "ptrace";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
is_64_bit_ = am_64_bit == (iov.iov_len == sizeof(regbuf.regs));
|
2017-03-15 00:09:28 -04:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
is_64_bit_initialized_.set_valid();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!is_64_bit_initialized_.is_valid()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*is_64_bit = is_64_bit_;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ProcessInfo::StartTime(timeval* start_time) const {
|
|
|
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
|
|
|
|
|
|
|
|
if (start_time_initialized_.is_uninitialized()) {
|
|
|
|
|
start_time_initialized_.set_invalid();
|
|
|
|
|
|
2017-03-14 13:05:40 -07:00
|
|
|
|
char path[32];
|
|
|
|
|
snprintf(path, sizeof(path), "/proc/%d/stat", pid_);
|
|
|
|
|
std::string stat_contents;
|
|
|
|
|
if (!ReadEntireFile(path, &stat_contents)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The process start time is the 22nd column.
|
|
|
|
|
// The second column is the executable name in parentheses.
|
|
|
|
|
// The executable name may have parentheses itself, so find the end of the
|
|
|
|
|
// second column by working backwards to find the last closing parens and
|
|
|
|
|
// then count forward to the 22nd column.
|
|
|
|
|
size_t stat_pos = stat_contents.rfind(')');
|
|
|
|
|
if (stat_pos == std::string::npos) {
|
|
|
|
|
LOG(ERROR) << "format error";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-14 21:25:50 -04:00
|
|
|
|
for (int index = 1; index < 21; ++index) {
|
|
|
|
|
stat_pos = stat_contents.find(' ', stat_pos);
|
|
|
|
|
if (stat_pos == std::string::npos) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
2017-03-14 13:05:40 -07:00
|
|
|
|
++stat_pos;
|
|
|
|
|
}
|
2017-03-14 21:25:50 -04:00
|
|
|
|
if (stat_pos >= stat_contents.size()) {
|
|
|
|
|
LOG(ERROR) << "format error";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2017-03-14 13:05:40 -07:00
|
|
|
|
|
2017-03-14 21:25:50 -04:00
|
|
|
|
const char* ticks_ptr = &stat_contents[stat_pos];
|
2017-03-14 13:05:40 -07:00
|
|
|
|
|
|
|
|
|
// start time is in jiffies instead of clock ticks pre 2.6.
|
|
|
|
|
uint64_t ticks_after_boot;
|
|
|
|
|
if (!AdvancePastNumber<uint64_t>(&ticks_ptr, &ticks_after_boot)) {
|
|
|
|
|
LOG(ERROR) << "format error";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
long clock_ticks_per_s = sysconf(_SC_CLK_TCK);
|
|
|
|
|
if (clock_ticks_per_s <= 0) {
|
|
|
|
|
PLOG(ERROR) << "sysconf";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
timeval time_after_boot;
|
|
|
|
|
time_after_boot.tv_sec = ticks_after_boot / clock_ticks_per_s;
|
|
|
|
|
time_after_boot.tv_usec =
|
|
|
|
|
(ticks_after_boot % clock_ticks_per_s) *
|
|
|
|
|
(static_cast<long>(1E6) / clock_ticks_per_s);
|
|
|
|
|
|
|
|
|
|
timespec uptime;
|
|
|
|
|
if (clock_gettime(CLOCK_BOOTTIME, &uptime) != 0) {
|
|
|
|
|
PLOG(ERROR) << "clock_gettime";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
timespec current_time;
|
|
|
|
|
if (clock_gettime(CLOCK_REALTIME, ¤t_time) != 0) {
|
|
|
|
|
PLOG(ERROR) << "clock_gettime";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
timespec boot_time_ts;
|
|
|
|
|
SubtractTimespec(current_time, uptime, &boot_time_ts);
|
|
|
|
|
timeval boot_time_tv;
|
|
|
|
|
TimespecToTimeval(boot_time_ts, &boot_time_tv);
|
|
|
|
|
timeradd(&boot_time_tv, &time_after_boot, &start_time_);
|
|
|
|
|
|
2017-03-15 00:09:28 -04:00
|
|
|
|
start_time_initialized_.set_valid();
|
2017-03-14 13:05:40 -07:00
|
|
|
|
}
|
|
|
|
|
|
2017-03-15 00:09:28 -04:00
|
|
|
|
if (!start_time_initialized_.is_valid()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2017-03-14 13:05:40 -07:00
|
|
|
|
|
|
|
|
|
*start_time = start_time_;
|
2017-03-15 00:09:28 -04:00
|
|
|
|
return true;
|
2017-03-14 13:05:40 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ProcessInfo::Arguments(std::vector<std::string>* argv) const {
|
|
|
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
|
|
|
|
|
|
|
|
char path[32];
|
|
|
|
|
snprintf(path, sizeof(path), "/proc/%d/cmdline", pid_);
|
2017-03-16 10:52:27 -04:00
|
|
|
|
FileReader cmdline_file;
|
|
|
|
|
if (!cmdline_file.Open(base::FilePath(path))) {
|
2017-03-14 13:05:40 -07:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-16 10:52:27 -04:00
|
|
|
|
DelimitedFileReader cmdline_file_field_reader(&cmdline_file);
|
|
|
|
|
|
|
|
|
|
std::vector<std::string> local_argv;
|
|
|
|
|
std::string argument;
|
|
|
|
|
DelimitedFileReader::Result result;
|
|
|
|
|
while ((result = cmdline_file_field_reader.GetDelim('\0', &argument)) ==
|
|
|
|
|
DelimitedFileReader::Result::kSuccess) {
|
|
|
|
|
if (argument.back() != '\0') {
|
|
|
|
|
LOG(ERROR) << "format error";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
argument.pop_back();
|
|
|
|
|
local_argv.push_back(argument);
|
|
|
|
|
}
|
|
|
|
|
if (result != DelimitedFileReader::Result::kEndOfFile) {
|
2017-03-14 13:05:40 -07:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
argv->swap(local_argv);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace crashpad
|