blob: dcd9754f635114877c30ad11895a776cb2f2d9a0 [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/tracing/common/active_processes_win.h"
#include <algorithm>
#include "base/base_paths.h"
#include "base/command_line.h"
#include "base/path_service.h"
#include "base/version.h"
namespace tracing {
namespace {
// Returns the directory immediately above the current executable's directory if
// the directory of the current executable is of the form "W.X.Y.Z"; otherwise,
// returns the current executable's directory.
base::FilePath DetermineApplicationDirectory() {
base::FilePath dir_path = base::PathService::CheckedGet(base::DIR_EXE);
if (base::Version(dir_path.BaseName().MaybeAsASCII()).IsValid()) {
// This is likely a production install, where the elevated tracing service
// is installed in the version directory.
return dir_path.DirName();
}
// Otherwise, this is likely a developer, where the elevated tracing service
// is in the build output directory next to the browser.
return dir_path;
}
// Returns true if `one` and `two` are equal or if `one` is a parent of `two`.
bool IsSameOrParent(const base::FilePath& one, const base::FilePath& two) {
// Use a simple string comparison here, as the expectation is that the paths
// will be formatted the same if they truly are the same since they share a
// common origin in that case. There are cases where this will return a false
// negative, but for now this is acceptable since it will lead to under-
// rather than over-reporting.
return one == two || one.IsParent(two);
}
} // namespace
ActiveProcesses::Process::Process(uint32_t pid,
uint32_t parent_pid,
uint32_t session_id,
std::optional<base::win::Sid> sid,
std::string image_file_name,
std::wstring command_line)
: pid(pid),
parent_pid(parent_pid),
session_id(session_id),
sid(std::move(sid)),
image_file_name(std::move(image_file_name)),
command_line(std::move(command_line)),
category(Category::kOther) {}
ActiveProcesses::Process::~Process() = default;
ActiveProcesses::ActiveProcesses(base::ProcessId client_pid)
: client_pid_(client_pid),
application_dir_(DetermineApplicationDirectory()) {
DETACH_FROM_SEQUENCE(sequence_checker_);
}
ActiveProcesses::~ActiveProcesses() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void ActiveProcesses::AddProcess(uint32_t pid,
uint32_t parent_pid,
uint32_t session_id,
std::optional<base::win::Sid> sid,
std::string image_file_name,
std::wstring command_line) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
enum {
kNonClientAdded,
kClientAdded,
kDuplicateClientAdded,
} addition = kNonClientAdded;
if (pid != client_pid_) {
addition = kNonClientAdded;
} else if (!client_process_) {
addition = kClientAdded;
} else {
// A second process with the same pid as the client is not possible since
// the tracing service self-terminates when the client process terminates.
// Conservatively consider this to be an "other" process and forget about
// the client.
addition = kDuplicateClientAdded;
OnClientRemoved(client_process_.get());
}
auto [iter, inserted] = processes_.try_emplace(
pid, pid, parent_pid, session_id, std::move(sid),
std::move(image_file_name), std::move(command_line));
auto& process = iter->second;
if (!inserted) {
// Duplicate pid. The event for the removal of this pid must have been lost.
// Forget about the old process's threads before replacing it.
std::ranges::for_each(process.threads,
[this](uint32_t tid) { threads_.erase(tid); });
process.threads.clear();
// Move the new process's properties into the instance.
process.parent_pid = parent_pid;
process.session_id = session_id;
process.sid = std::move(sid);
process.image_file_name = std::move(image_file_name);
process.command_line = std::move(command_line);
}
// Finally, set the process's category.
switch (addition) {
case kNonClientAdded:
process.category = DetermineCategory(process);
break;
case kClientAdded:
process.category = Category::kClient; // This is the client's process.
OnClientAdded(&process);
break;
case kDuplicateClientAdded:
process.category = Category::kOther;
break;
}
}
void ActiveProcesses::RemoveProcess(uint32_t pid) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (pid == client_pid_ && client_process_) {
OnClientRemoved(client_process_.get());
}
if (auto iter = processes_.find(pid); iter != processes_.end()) {
// Forget about this process's threads if they haven't already been removed.
std::ranges::for_each(iter->second.threads,
[this](uint32_t tid) { threads_.erase(tid); });
processes_.erase(iter);
} // else the event for the addition of this process must have been lost.
}
void ActiveProcesses::AddThread(uint32_t pid,
uint32_t tid,
std::wstring thread_name) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto process_iter = processes_.find(pid);
if (process_iter == processes_.end()) {
// The event for the addition of this thread's process must have been lost.
// The reason for tracking threads is to map a tid to its process. Since it
// will not be possible to do so for this tid, skip adding it to `threads_`.
return;
}
auto [iter, inserted] =
threads_.try_emplace(tid, std::move(thread_name), &process_iter->second);
auto& [name, process_ptr] = iter->second;
if (!inserted) {
// The event for the removal of this tid must have been lost.
// Remove the thread from the previous process's collection.
process_ptr->threads.erase(tid);
// Associate this thread with its new name and process.
name = std::move(thread_name);
process_ptr = &process_iter->second;
}
// Add this thread to its process's collection.
process_ptr->threads.insert(tid);
}
void ActiveProcesses::SetThreadName(uint32_t pid,
uint32_t tid,
std::wstring thread_name) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto process_iter = processes_.find(pid);
if (process_iter == processes_.end()) {
// The event for the addition of this thread's process must have been lost.
return;
}
auto thread_iter = threads_.find(tid);
if (thread_iter == threads_.end()) {
// The event for the addition of this thread must have been lost.
return;
}
if (thread_iter->second.second.get() != &process_iter->second) {
// The pid and tid are both known, but don't relate. Aggressive id reuse
// and lost events make this possible.
return;
}
thread_iter->second.first = std::move(thread_name);
}
void ActiveProcesses::RemoveThread(uint32_t pid, uint32_t tid) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto iter = threads_.find(tid);
if (iter == threads_.end()) {
// The event for the addition of this thread must have been lost.
return;
}
auto& process_ptr = iter->second.second;
if (process_ptr->pid != pid) {
// Events for the removal of this tid and its addition to a different pid
// must have been lost. Ignore this removal to avoid corrupting tracking for
// the other process
return;
}
process_ptr->threads.erase(tid);
threads_.erase(iter);
}
ActiveProcesses::Category ActiveProcesses::GetThreadCategory(
uint32_t tid) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (auto iter = threads_.find(tid); iter != threads_.end()) {
return iter->second.second->category;
}
return Category::kOther;
}
std::wstring_view ActiveProcesses::GetThreadName(uint32_t tid) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (auto iter = threads_.find(tid); iter != threads_.end()) {
return iter->second.first;
}
return {};
}
std::string_view ActiveProcesses::GetProcessImageFileName(uint32_t pid) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (auto iter = processes_.find(pid); iter != processes_.end()) {
return iter->second.image_file_name;
}
return {};
}
void ActiveProcesses::OnClientAdded(Process* client) {
client_process_ = client;
client_in_application_ =
IsSameOrParent(application_dir_, GetProgram(*client).DirName());
// Re-categorize all existing "other" processes to detect client processes.
std::ranges::for_each(
processes_,
[this](auto& process) {
if (process.category == Category::kOther) {
process.category = DetermineCategory(process);
}
},
&PidProcessMap::value_type::second);
}
void ActiveProcesses::OnClientRemoved(Process* client) {
client_process_ = nullptr;
client_in_application_ = false;
// All previously-discovered client processes are now "other" processes.
std::ranges::for_each(
processes_,
[](auto& process) {
if (process.category == Category::kClient) {
process.category = Category::kOther;
}
},
&PidProcessMap::value_type::second);
}
ActiveProcesses::Category ActiveProcesses::DetermineCategory(
const Process& process) {
// The session id for Idle, System, Secure System, Registry, smss.exe, and
// MemCompression.
static constexpr uint32_t kSystemSession = 0xFFFFFFFF;
if (process.session_id == kSystemSession) {
return Category::kSystem; // Windows kernel processes.
}
if (!client_process_) {
return Category::kOther; // Not yet possible to associate with the client.
}
const Process& client = *client_process_;
if (process.session_id != client.session_id) {
return Category::kOther; // Not running in the same session as the client.
}
if (!process.sid.has_value() || process.sid != client.sid) {
return Category::kOther; // Not the same user.
}
if (client_in_application_ &&
IsSameOrParent(application_dir_, GetProgram(process).DirName())) {
return Category::kClient; // A program belonging to the client.
}
return Category::kOther;
}
// static
base::FilePath ActiveProcesses::GetProgram(const Process& process) {
return base::CommandLine::FromString(process.command_line).GetProgram();
}
} // namespace tracing