blob: cb5977bf179d397cf33863c6d2cd2579bcd7db11 [file]
// Copyright 2026 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/etw_stack_sampling_win.h"
#include <windows.h>
#include <dbghelp.h>
#include <string>
#include <utility>
#include "absl/container/flat_hash_map.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/no_destructor.h"
#include "base/path_service.h"
#include "base/profiler/module_cache.h"
#include "base/strings/utf_string_conversions.h"
#include "base/win/pe_image.h"
#include "third_party/perfetto/include/perfetto/tracing/core/data_source_config.h"
#include "third_party/perfetto/include/perfetto/tracing/core/trace_config.h"
#include "third_party/perfetto/protos/perfetto/config/etw/etw_config.gen.h"
namespace tracing {
namespace {
// Images whose functions should be symbolized in ETW call stacks, and the
// directory where each is expected to be.
constexpr std::pair<const wchar_t*, int> kImagesOfInterest[] = {
{L"chrome.exe", base::DIR_EXE}, {L"chrome.dll", base::DIR_MODULE},
{L"dxcompiler.dll", base::DIR_MODULE}, {L"dxil.dll", base::DIR_MODULE},
{L"libEGL.dll", base::DIR_MODULE}, {L"libGLESv2.dll", base::DIR_MODULE},
{L"vulkan-1.dll", base::DIR_MODULE}, {L"advapi32.dll", base::DIR_SYSTEM},
{L"apphelp.dll", base::DIR_SYSTEM}, {L"dbghelp.dll", base::DIR_SYSTEM},
{L"kernel32.dll", base::DIR_SYSTEM}, {L"ntdll.dll", base::DIR_SYSTEM},
{L"ntoskrnl.exe", base::DIR_SYSTEM}, {L"user32.dll", base::DIR_SYSTEM}};
// Retrieves the full path and debug ID for each image in `kImagesOfInterest`,
// from memory or from disk if needed, and returns as a [path, debug ID] map.
absl::flat_hash_map<std::string, std::string> GetDebugIdsOfInterest() {
absl::flat_hash_map<std::string, std::string> images;
for (const auto& [name, dir] : kImagesOfInterest) {
std::string path;
HMODULE module = ::GetModuleHandle(name);
if (module) {
// The image is loaded, so get its details from memory.
std::wstring buffer(MAX_PATH, L'\0');
const DWORD length = ::GetModuleFileName(module, &buffer[0], MAX_PATH);
if (length && length < MAX_PATH) {
buffer.resize(length);
path = base::WideToUTF8(buffer);
GUID guid;
DWORD age;
if (base::win::PEImage(module).GetDebugId(&guid, &age, nullptr,
nullptr)) {
images[path] = base::AsBuildId(guid, age);
continue;
}
}
}
if (path.empty()) {
// The image is not loaded, so try using its expected location on disk.
base::FilePath dir_path;
if (!base::PathService::Get(dir, &dir_path)) {
continue;
}
path = base::WideToUTF8(dir_path.Append(name).value());
}
SYMSRV_INDEX_INFO info = {};
info.sizeofstruct = sizeof(info);
if (::SymSrvGetFileIndexInfo(path.c_str(), &info, 0)) {
images[path] = base::AsBuildId(info.guid, info.age);
}
}
return images;
}
} // namespace
void AddEtwStackSamplingDebugIds(perfetto::DataSourceConfig* config) {
// Do nothing if this is a child process; only the browser should query
// information about local files.
if (!base::CommandLine::ForCurrentProcess()
->GetSwitchValueASCII("type")
.empty()) {
return;
}
perfetto::protos::gen::EtwConfig etw_config;
if (!config->etw_config_raw().empty()) {
etw_config.ParseFromString(config->etw_config_raw());
}
// Do nothing if stack sampling isn't enabled; the debug IDs won't be used.
if (etw_config.stack_sampling_events().empty()) {
return;
}
// Don't overwrite debug IDs if they're already set (e.g., in a config
// file).
if (!etw_config.stack_sampling_debug_ids().empty()) {
return;
}
// Retrieve the debug IDs once and cache them for subsequent calls (i.e.,
// subsequent tracing sessions) to reduce disk I/O.
static const base::NoDestructor<absl::flat_hash_map<std::string, std::string>>
kDebugIdsOfInterest(GetDebugIdsOfInterest());
for (const auto& [path, debug_id] : *kDebugIdsOfInterest) {
auto* entry = etw_config.add_stack_sampling_debug_ids();
entry->set_path(path);
entry->set_debug_id(debug_id);
}
config->set_etw_config_raw(etw_config.SerializeAsString());
}
void AddEtwStackSamplingDebugIds(perfetto::TraceConfig& perfetto_config) {
for (auto& data_source_config : *perfetto_config.mutable_data_sources()) {
if (data_source_config.config().name() == "org.chromium.etw_system") {
AddEtwStackSamplingDebugIds(data_source_config.mutable_config());
}
}
}
} // namespace tracing