linux: Refactor reading start time from the stat file

Bug: crashpad:30
Change-Id: Ie8137db2a5b6f2d4947df108d1fb5bdd9f8ab391
Reviewed-on: https://chromium-review.googlesource.com/580448
Commit-Queue: Joshua Peraza <jperaza@chromium.org>
Reviewed-by: Mark Mentovai <mark@chromium.org>
This commit is contained in:
Joshua Peraza 2017-07-24 10:55:02 -07:00
parent 37f20f7b14
commit 9299d409ab
8 changed files with 413 additions and 133 deletions

View File

@ -0,0 +1,138 @@
// 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/linux/proc_stat_reader.h"
#include <stdio.h>
#include <time.h>
#include <unistd.h>
#include "base/files/file_path.h"
#include "base/logging.h"
#include "util/file/file_io.h"
#include "util/misc/lexing.h"
namespace crashpad {
namespace {
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;
}
} // namespace
ProcStatReader::ProcStatReader() : tid_(-1) {}
ProcStatReader::~ProcStatReader() {}
bool ProcStatReader::Initialize(pid_t tid) {
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
// This might do more in the future.
tid_ = tid;
INITIALIZATION_STATE_SET_VALID(initialized_);
return true;
}
bool ProcStatReader::StartTime(timeval* start_time) const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
std::string stat_contents;
if (!ReadFile(&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;
}
for (int index = 1; index < 21; ++index) {
stat_pos = stat_contents.find(' ', stat_pos);
if (stat_pos == std::string::npos) {
break;
}
++stat_pos;
}
if (stat_pos >= stat_contents.size()) {
LOG(ERROR) << "format error";
return false;
}
const char* ticks_ptr = &stat_contents[stat_pos];
// 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, &current_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);
return true;
}
bool ProcStatReader::ReadFile(std::string* contents) const {
char path[32];
snprintf(path, arraysize(path), "/proc/%d/stat", tid_);
if (!LoggingReadEntireFile(base::FilePath(path), contents)) {
return false;
}
return true;
}
} // namespace crashpad

View File

@ -0,0 +1,60 @@
// 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.
#ifndef CRASHPAD_UTIL_LINUX_PROC_STAT_READER_H_
#define CRASHPAD_UTIL_LINUX_PROC_STAT_READER_H_
#include <sys/time.h>
#include <sys/types.h>
#include <string>
#include "base/macros.h"
#include "util/misc/initialization_state_dcheck.h"
namespace crashpad {
//! \brief Reads the /proc/[pid]/stat file for a thread.
class ProcStatReader {
public:
ProcStatReader();
~ProcStatReader();
//! \brief Initializes the reader.
//!
//! This method must be successfully called before calling any other.
//!
//! \param[in] tid The thread ID to read the stat file for.
bool Initialize(pid_t tid);
//! \brief Determines the target threads start time.
//!
//! \param[out] start_time The time that the thread started.
//!
//! \return `true` on success, with \a start_time set. Otherwise, `false` with
//! a message logged.
bool StartTime(timeval* start_time) const;
private:
bool ReadFile(std::string* contents) const;
pid_t tid_;
InitializationStateDcheck initialized_;
DISALLOW_COPY_AND_ASSIGN(ProcStatReader);
};
} // namespace crashpad
#endif // CRASHPAD_UTIL_LINUX_PROC_STAT_READER_H_

View File

@ -0,0 +1,85 @@
// 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/linux/proc_stat_reader.h"
#include <sys/syscall.h>
#include <time.h>
#include <unistd.h>
#include "base/logging.h"
#include "gtest/gtest.h"
#include "util/thread/thread.h"
namespace crashpad {
namespace test {
namespace {
TEST(ProcStatReader, Basic) {
ProcStatReader stat;
ASSERT_TRUE(stat.Initialize(getpid()));
timeval start_time;
ASSERT_TRUE(stat.StartTime(&start_time));
time_t now;
time(&now);
EXPECT_LE(start_time.tv_sec, now);
}
pid_t gettid() {
return syscall(SYS_gettid);
}
void GetStartTime(timeval* start_time) {
ProcStatReader stat;
ASSERT_TRUE(stat.Initialize(gettid()));
ASSERT_TRUE(stat.StartTime(start_time));
}
class StatTimeThread : public Thread {
public:
StatTimeThread(timeval* start_time) : start_time_(start_time) {}
private:
void ThreadMain() override { GetStartTime(start_time_); }
timeval* start_time_;
};
TEST(ProcStatReader, Threads) {
timeval main_time;
ASSERT_NO_FATAL_FAILURE(GetStartTime(&main_time));
timeval thread_time;
StatTimeThread thread(&thread_time);
thread.Start();
ASSERT_NO_FATAL_FAILURE(thread.Join());
EXPECT_PRED4(
[](time_t main_sec,
suseconds_t main_usec,
time_t thread_sec,
suseconds_t thread_usec) {
return (thread_sec > main_sec) ||
(thread_sec == main_sec && thread_usec > main_usec);
},
main_time.tv_sec,
main_time.tv_usec,
thread_time.tv_sec,
thread_time.tv_usec);
}
} // namespace
} // namespace test
} // namespace crashpad

75
util/misc/lexing.cc Normal file
View File

@ -0,0 +1,75 @@
// 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/misc/lexing.h"
#include <ctype.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <limits>
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
namespace crashpad {
namespace {
#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(int64_t, base::StringToInt64);
MAKE_ADAPTER(uint64_t, base::StringToUint64);
#undef MAKE_ADAPTER
} // namespace
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;
}
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;
}
template bool AdvancePastNumber(const char** input, int* value);
template bool AdvancePastNumber(const char** input, unsigned int* value);
template bool AdvancePastNumber(const char** input, int64_t* value);
template bool AdvancePastNumber(const char** input, uint64_t* value);
} // namespace crashpad

44
util/misc/lexing.h Normal file
View File

@ -0,0 +1,44 @@
// 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.
#ifndef CRASHPAD_UTIL_MISC_LEXING_H_
#define CRASHPAD_UTIL_MISC_LEXING_H_
namespace crashpad {
//! \brief Match a pattern at the start of a char string.
//!
//! \param[in,out] input A pointer to the char string to match against. \a input
//! is advanced past the matched pattern if it is found.
//! \param[in] pattern The pattern to match at the start of \input.
//! \return `true` if the pattern is matched exactly and \input is advanced,
//! otherwise `false`.
bool AdvancePastPrefix(const char** input, const char* pattern);
//! \brief Convert a prefix of a char string to a numeric value.
//!
//! Valid values are positive or negative decimal numbers, matching the regular
//! expression "-?\d+", and within the limits of T.
//!
//! \param[in,out] input A pointer to the char string to match against. \a input
//! is advanced past the number if one is found.
//! \param[out] value The converted number, if one is found.
//! \return `true` if a number is found at the start of \a input and \a input is
//! advanced, otherwise `false`.
template <typename T>
bool AdvancePastNumber(const char** input, T* value);
} // namespace crashpad
#endif // CRASHPAD_UTIL_MISC_LEXING_H

View File

@ -14,84 +14,18 @@
#include "util/posix/process_info.h"
#include <ctype.h>
#include <errno.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <stdio.h>
#include "base/files/file_path.h"
#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"
#include "util/file/delimited_file_reader.h"
#include "util/file/file_io.h"
#include "util/file/file_reader.h"
#include "util/linux/proc_stat_reader.h"
#include "util/linux/thread_info.h"
#include "util/misc/lexing.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;
}
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;
}
} // namespace
ProcessInfo::ProcessInfo()
: supplementary_groups_(),
start_time_(),
@ -324,74 +258,13 @@ bool ProcessInfo::StartTime(timeval* start_time) const {
if (start_time_initialized_.is_uninitialized()) {
start_time_initialized_.set_invalid();
char path[32];
snprintf(path, sizeof(path), "/proc/%d/stat", pid_);
std::string stat_contents;
if (!LoggingReadEntireFile(base::FilePath(path), &stat_contents)) {
ProcStatReader reader;
if (!reader.Initialize(pid_)) {
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";
if (!reader.StartTime(&start_time_)) {
return false;
}
for (int index = 1; index < 21; ++index) {
stat_pos = stat_contents.find(' ', stat_pos);
if (stat_pos == std::string::npos) {
break;
}
++stat_pos;
}
if (stat_pos >= stat_contents.size()) {
LOG(ERROR) << "format error";
return false;
}
const char* ticks_ptr = &stat_contents[stat_pos];
// 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, &current_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_);
start_time_initialized_.set_valid();
}

View File

@ -58,6 +58,8 @@
'linux/thread_info.h',
'linux/scoped_ptrace_attach.cc',
'linux/scoped_ptrace_attach.h',
'linux/proc_stat_reader.cc',
'linux/proc_stat_reader.h',
'mac/checked_mach_address_range.h',
'mac/launchd.h',
'mac/launchd.mm',
@ -112,6 +114,8 @@
'misc/initialization_state.h',
'misc/initialization_state_dcheck.cc',
'misc/initialization_state_dcheck.h',
'misc/lexing.cc',
'misc/lexing.h',
'misc/metrics.cc',
'misc/metrics.h',
'misc/paths.h',

View File

@ -43,6 +43,7 @@
'linux/memory_map_test.cc',
'linux/process_memory_range_test.cc',
'linux/process_memory_test.cc',
'linux/proc_stat_reader_test.cc',
'linux/thread_info_test.cc',
'linux/scoped_ptrace_attach_test.cc',
'mac/launchd_test.mm',