From 3a35e48ea75ad93b79bb42f10ab1599628d0461b Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Fri, 14 Aug 2026 18:14:48 +0200 Subject: [PATCH 1/6] inspector: avoid calling into JS from V8 interrupts Our inspector implementation dispatches inspector messages from a V8 interrupt handler, so they could be handled during an arbitrary point of JS execution where re-calling into another irrelevant JS code is not safe. This patch tracks V8 interrupt state in this case and rewrite the async hook toggling as state reconciliation, so requests only record the desired state, which is applied once calling into JS is possible and safe, and the actual invocation is deferred to an immediate when inside an interrupt. This simplifies the previous mechanism and makes re-entracy and early termination safer. Drive-by: skip installing command line API extensions during teardown when calling into JS is no longer safe. Signed-off-by: Joyee Cheung PR-URL: https://github.com/nodejs/node/pull/65028 Refs: https://issues.chromium.org/u/1/issues/42212250 Refs: https://chromium-review.googlesource.com/c/v8/v8/+/8173727 Refs: https://github.com/nodejs/node/pull/26935 Reviewed-By: Chengzhong Wu --- src/env-inl.h | 4 + src/env.cc | 2 + src/env.h | 7 ++ src/inspector_agent.cc | 112 ++++++++++-------- src/inspector_agent.h | 13 +- .../test-inspector-async-hook-after-done.js | 4 +- 6 files changed, 83 insertions(+), 59 deletions(-) diff --git a/src/env-inl.h b/src/env-inl.h index e9f940c63e53..efabadbcc0bc 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -623,6 +623,10 @@ inline void Environment::set_can_call_into_js(bool can_call_into_js) { can_call_into_js_ = can_call_into_js; } +inline bool Environment::is_processing_v8_interrupt() const { + return is_processing_v8_interrupt_; +} + inline bool Environment::has_run_bootstrapping_code() const { return principal_realm_->has_run_bootstrapping_code(); } diff --git a/src/env.cc b/src/env.cc index 13344ad135e2..61921995cdd0 100644 --- a/src/env.cc +++ b/src/env.cc @@ -1549,7 +1549,9 @@ void Environment::RequestInterruptFromV8() { return; } env->interrupt_data_.store(nullptr); + env->is_processing_v8_interrupt_ = true; env->RunAndClearInterrupts(); + env->is_processing_v8_interrupt_ = false; }, interrupt_data); } diff --git a/src/env.h b/src/env.h index c2bf9fdd497a..288084e3a589 100644 --- a/src/env.h +++ b/src/env.h @@ -799,6 +799,12 @@ class Environment final : public MemoryRetainer { inline bool can_call_into_js() const; inline void set_can_call_into_js(bool can_call_into_js); + // True while RequestInterrupt() callbacks are being invoked from the + // v8::Isolate::RequestInterrupt() handler, i.e. potentially at an + // arbitrary point during JS execution. Calling into JS must be avoided + // in that case. + inline bool is_processing_v8_interrupt() const; + // Increase or decrease a counter that manages whether this Environment // keeps the event loop alive on its own or not. The counter starts out at 0, // meaning it does not, and any positive value will make it keep the event @@ -1252,6 +1258,7 @@ class Environment final : public MemoryRetainer { bool task_queues_async_initialized_ = false; std::atomic interrupt_data_ {nullptr}; + bool is_processing_v8_interrupt_ = false; void RequestInterruptFromV8(); static void CheckImmediate(uv_check_t* handle); diff --git a/src/inspector_agent.cc b/src/inspector_agent.cc index 00b4982d9b83..4bb34e56bcbf 100644 --- a/src/inspector_agent.cc +++ b/src/inspector_agent.cc @@ -555,11 +555,7 @@ class NodeInspectorClient : public V8InspectorClient { return; } if (auto agent = env_->inspector_agent()) { - if (depth == 0) { - agent->DisableAsyncHook(); - } else { - agent->EnableAsyncHook(); - } + agent->SetAsyncHookTrackingEnabled(depth != 0); } } @@ -655,6 +651,7 @@ class NodeInspectorClient : public V8InspectorClient { void installAdditionalCommandLineAPI(Local context, Local target) override { + if (!env_->can_call_into_js()) return; Local installer = env_->inspector_console_extension_installer(); if (!installer.IsEmpty()) { Local argv[] = {target}; @@ -1076,58 +1073,69 @@ void Agent::RegisterAsyncHook(Isolate* isolate, Local disable_function) { parent_env_->set_inspector_enable_async_hooks(enable_function); parent_env_->set_inspector_disable_async_hooks(disable_function); - if (pending_enable_async_hook_) { - CHECK(!pending_disable_async_hook_); - pending_enable_async_hook_ = false; - EnableAsyncHook(); - } else if (pending_disable_async_hook_) { - CHECK(!pending_enable_async_hook_); - pending_disable_async_hook_ = false; - DisableAsyncHook(); - } + SyncAsyncHookState(); } -void Agent::EnableAsyncHook() { - HandleScope scope(parent_env_->isolate()); - Local enable = parent_env_->inspector_enable_async_hooks(); - if (!enable.IsEmpty()) { - ToggleAsyncHook(parent_env_->isolate(), enable); - } else if (pending_disable_async_hook_) { - CHECK(!pending_enable_async_hook_); - pending_disable_async_hook_ = false; - } else { - pending_enable_async_hook_ = true; - } +void Agent::SetAsyncHookTrackingEnabled(bool enabled) { + async_hook_wanted_ = enabled; + SyncAsyncHookState(); } -void Agent::DisableAsyncHook() { - HandleScope scope(parent_env_->isolate()); - Local disable = parent_env_->inspector_disable_async_hooks(); - if (!disable.IsEmpty()) { - ToggleAsyncHook(parent_env_->isolate(), disable); - } else if (pending_enable_async_hook_) { - CHECK(!pending_disable_async_hook_); - pending_enable_async_hook_ = false; - } else { - pending_disable_async_hook_ = true; - } -} +// Reconcile the state of the async hook used for async stack traces with the +// state last requested by the protocol. The hook is set up in JS land, +// (see inspector_async_hooks.js), which isn't safe to do when: +// 1. We are in early bootstrap and the setup functions aren't registered in +// C++ yet. +// 2. We are in a V8 interrupt requested by inspector protocol message +// dispatch e.g. from maxAsyncCallStackDepthChanged() notifications. +// When it's not safe to call into JS, this is a no-op and we'll try again in +// RegisterAsyncHook() (for 1) or from a scheduled immediate (for 2). +void Agent::SyncAsyncHookState() { + // The debugger can request an interrupt within the toggle JS function itself, + // A nested call only records the new requested state, the outermost call sees + // it when re-checking the loop condition after each toggle. + if (syncing_async_hook_state_) return; + syncing_async_hook_state_ = true; + auto on_exit = OnScopeLeave([this]() { syncing_async_hook_state_ = false; }); + + Isolate* isolate = parent_env_->isolate(); + HandleScope scope(isolate); + while (async_hook_wanted_ != async_hook_enabled_) { + // Guard against running this during cleanup -- no async events will be + // emitted anyway at that point anymore, and calling into JS is not + // possible. This should probably not be something we're attempting in the + // first place, + // Refs: https://github.com/nodejs/node/pull/34362#discussion_r456006039 + if (!parent_env_->can_call_into_js()) return; + + bool enable = async_hook_wanted_; + Local fn = enable ? parent_env_->inspector_enable_async_hooks() + : parent_env_->inspector_disable_async_hooks(); + if (fn.IsEmpty()) return; + + if (parent_env_->is_processing_v8_interrupt()) { + parent_env_->SetImmediate( + [](Environment* env) { + Agent* agent = env->inspector_agent(); + if (agent != nullptr) agent->SyncAsyncHookState(); + }, + CallbackFlags::kUnrefed); + return; + } -void Agent::ToggleAsyncHook(Isolate* isolate, Local fn) { - // Guard against running this during cleanup -- no async events will be - // emitted anyway at that point anymore, and calling into JS is not possible. - // This should probably not be something we're attempting in the first place, - // Refs: https://github.com/nodejs/node/pull/34362#discussion_r456006039 - if (!parent_env_->can_call_into_js()) return; - CHECK(parent_env_->has_run_bootstrapping_code()); - HandleScope handle_scope(isolate); - CHECK(!fn.IsEmpty()); - auto context = parent_env_->context(); - v8::TryCatch try_catch(isolate); - USE(fn->Call(context, Undefined(isolate), 0, nullptr)); - if (try_catch.HasCaught() && !try_catch.HasTerminated()) { - PrintCaughtException(isolate, context, try_catch); - UNREACHABLE("Cannot toggle Inspector's AsyncHook, please report this."); + CHECK(parent_env_->has_run_bootstrapping_code()); + Local context = parent_env_->context(); + v8::TryCatch try_catch(isolate); + USE(fn->Call(context, Undefined(isolate), 0, nullptr)); + if (try_catch.HasCaught()) { + // Termination may abort the toggle invocation, retrying now would just + // be terminated again. Instead of recording the toggle that may not have + // taken effect, leave the states as-is so that a later sync retries. + if (try_catch.HasTerminated()) return; + PrintCaughtException(isolate, context, try_catch); + UNREACHABLE("Cannot toggle Inspector's AsyncHook, please report this."); + } + async_hook_enabled_ = enable; } } diff --git a/src/inspector_agent.h b/src/inspector_agent.h index 5ace72a64012..932e4e8dce89 100644 --- a/src/inspector_agent.h +++ b/src/inspector_agent.h @@ -90,8 +90,7 @@ class Agent { void RegisterAsyncHook(v8::Isolate* isolate, v8::Local enable_function, v8::Local disable_function); - void EnableAsyncHook(); - void DisableAsyncHook(); + void SetAsyncHookTrackingEnabled(bool enabled); void SetParentHandle(std::unique_ptr parent_handle); std::unique_ptr GetParentHandle(uint64_t thread_id, @@ -132,7 +131,7 @@ class Agent { std::shared_ptr GetNetworkResourceManager(); private: - void ToggleAsyncHook(v8::Isolate* isolate, v8::Local fn); + void SyncAsyncHookState(); void ToggleNetworkTracking(v8::Isolate* isolate, v8::Local fn); node::Environment* parent_env_; @@ -150,8 +149,12 @@ class Agent { DebugOptions debug_options_; std::shared_ptr> host_port_; - bool pending_enable_async_hook_ = false; - bool pending_disable_async_hook_ = false; + // The state of the async hook used for async stack traces that the protocol + // last requested, and the state JS currently has. SyncAsyncHookState() + // reconciles the two when it is possible and safe to call into JS. + bool async_hook_wanted_ = false; + bool async_hook_enabled_ = false; + bool syncing_async_hook_state_ = false; bool network_tracking_enabled_ = false; bool pending_enable_network_tracking = false; diff --git a/test/parallel/test-inspector-async-hook-after-done.js b/test/parallel/test-inspector-async-hook-after-done.js index f9cd7b491360..b4eff0467ecd 100644 --- a/test/parallel/test-inspector-async-hook-after-done.js +++ b/test/parallel/test-inspector-async-hook-after-done.js @@ -34,8 +34,8 @@ function onAttachToWorker({ params: { sessionId } }) { session.once('NodeWorker.receivedMessageFromWorker', onMessageReceived); return; } - // Force a call to node::inspector::Agent::ToggleAsyncHook by changing the - // async call stack depth + // Force a call to node::inspector::Agent::SyncAsyncHookState by changing + // the async call stack depth postToWorkerInspector('Debugger.setAsyncCallStackDepth', { maxDepth: 1 }); // This is were the original crash happened session.post('NodeWorker.detach', { sessionId }, () => { From f83e7df96177a619a1c1258490443692650134c2 Mon Sep 17 00:00:00 2001 From: Maya Lekova Date: Fri, 14 Aug 2026 19:14:58 +0300 Subject: [PATCH 2/6] test: add a simple test for `import defer` of a CJS module This tests imports a CommonJS modules with the `defer` modifier. It ensures that the imported module is not evaluated before accessing properties from its exports. Signed-off-by: Maya Lekova PR-URL: https://github.com/nodejs/node/pull/64694 Reviewed-By: Joyee Cheung --- .../test-cjs-defer-static-import-eval.mjs | 30 +++++++++++++++++++ .../es-modules/module-cjs-deferred-eval.js | 17 +++++++++++ 2 files changed, 47 insertions(+) create mode 100644 test/es-module/test-cjs-defer-static-import-eval.mjs create mode 100644 test/fixtures/es-modules/module-cjs-deferred-eval.js diff --git a/test/es-module/test-cjs-defer-static-import-eval.mjs b/test/es-module/test-cjs-defer-static-import-eval.mjs new file mode 100644 index 000000000000..0150f6f40b38 --- /dev/null +++ b/test/es-module/test-cjs-defer-static-import-eval.mjs @@ -0,0 +1,30 @@ +// Flags: --js-defer-import-eval + +// Test that uses import.defer for a CJS module. It ensures that: +// 1. the module is imported successfully; +// 2. it's evaluated synchronously, regardless of the `defer` modifier; +// 3. Evaluation of the imported module is deferred +// until first namespace access. + +import '../common/index.mjs'; +import * as assert from 'assert'; + +// Import the CJS module with the `defer` modifier. +import defer * as imported from '../fixtures/es-modules/module-cjs-deferred-eval.js'; + +// At this point, the deferred module should not yet be evaluated. Initialize +// the `eval_list`, which will be populated only when the module is evaluated +// for the first time, triggered by namespace access below. +globalThis.eval_list = []; + +// Additionally check that the exported properties `foo` and `identifier` +// are defined and have their values assigned at this point. +assert.strictEqual(imported.foo, 42); +assert.strictEqual(imported.identifier, 'package-type-commonjs'); + +// Check that the module has been evaluated at this point, +// also that it's not evaluated more than once. +assert.deepStrictEqual(['defer-1'], globalThis.eval_list); + +// Clean-up +delete globalThis.eval_list; diff --git a/test/fixtures/es-modules/module-cjs-deferred-eval.js b/test/fixtures/es-modules/module-cjs-deferred-eval.js new file mode 100644 index 000000000000..a7239e6ff454 --- /dev/null +++ b/test/fixtures/es-modules/module-cjs-deferred-eval.js @@ -0,0 +1,17 @@ +// This fixture is imported as a module +// in test/es-module/test-cjs-defer-static-import-eval.mjs +// to ensure a CommonJS module imported with `import defer` +// is only executed once. +const assert = require('assert'); + +const identifier = 'package-type-commonjs'; + +module.exports.foo = 42; +module.exports.identifier = identifier; + +// The `eval_list` is initialised by the importing module, +// so by the time the fixture is executed, `eval_list` should +// already be initialised. +assert.deepEqual(globalThis.eval_list, []); + +globalThis.eval_list.push('defer-1'); From 9cf2ce75f20a3a175d05335da3143404046bac2f Mon Sep 17 00:00:00 2001 From: "Node.js GitHub Bot" Date: Fri, 14 Aug 2026 14:33:27 -0400 Subject: [PATCH 3/6] deps: update perfetto to 57.2 PR-URL: https://github.com/nodejs/node/pull/65114 Reviewed-By: Chengzhong Wu --- deps/perfetto/LICENSE | 20 + deps/perfetto/VERSION | 2 +- deps/perfetto/sdk/perfetto.cc | 6726 +++++++--- deps/perfetto/sdk/perfetto.h | 23033 ++++++++++++++------------------ 4 files changed, 15265 insertions(+), 14516 deletions(-) diff --git a/deps/perfetto/LICENSE b/deps/perfetto/LICENSE index cbdc2881d57d..681d40008ee3 100644 --- a/deps/perfetto/LICENSE +++ b/deps/perfetto/LICENSE @@ -224,6 +224,26 @@ Files: src/trace_processor/perfetto_sql/stdlib/chromium/*, protos/third_party/ch OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +------------------ + +Files: src/trace_processor/perfetto_sql/syntaqlite/syntaqlite_perfetto.{c, h} + + Copyright 2025 The syntaqlite Authors. All rights reserved. + + Machine-generated amalgamation of syntaqlite runtime + Perfetto dialect + sources (https://github.com/LalitMaganti/syntaqlite). Portions derive + from SQLite's public-domain `parse.y` grammar. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + + 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. + + ------------------ Files: src/trace_processor/perfetto_sql/preprocessor/preprocessor_grammar.{c, h} diff --git a/deps/perfetto/VERSION b/deps/perfetto/VERSION index 517ac763657c..06b0aef2d7b5 100644 --- a/deps/perfetto/VERSION +++ b/deps/perfetto/VERSION @@ -1 +1 @@ -54.0 +57.2 diff --git a/deps/perfetto/sdk/perfetto.cc b/deps/perfetto/sdk/perfetto.cc index 2ccd11f7b831..4df59d2c46dd 100644 --- a/deps/perfetto/sdk/perfetto.cc +++ b/deps/perfetto/sdk/perfetto.cc @@ -560,6 +560,7 @@ struct std::hash<::perfetto::base::StringView> { #include #include #include +#include #include #include @@ -723,7 +724,9 @@ std::vector SplitString(const std::string& text, const std::string& delimiter); std::string StripPrefix(const std::string& str, const std::string& prefix); std::string StripSuffix(const std::string& str, const std::string& suffix); +std::string_view TrimWhitespace(std::string_view str); std::string TrimWhitespace(const std::string& str); +std::string_view TrimWhitespace(const char* str); std::string ToLower(const std::string& str); std::string ToUpper(const std::string& str); std::string StripChars(const std::string& str, @@ -1544,10 +1547,10 @@ std::optional Base64Decode(const char* src, size_t src_size) { } // namespace base } // namespace perfetto -// gen_amalgamated begin source: src/base/crash_keys.cc -// gen_amalgamated begin header: include/perfetto/ext/base/crash_keys.h +// gen_amalgamated begin source: src/base/cpu_info.cc +// gen_amalgamated begin header: include/perfetto/ext/base/cpu_info.h /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2026 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -1562,154 +1565,42 @@ std::optional Base64Decode(const char* src, size_t src_size) { * limitations under the License. */ -#ifndef INCLUDE_PERFETTO_EXT_BASE_CRASH_KEYS_H_ -#define INCLUDE_PERFETTO_EXT_BASE_CRASH_KEYS_H_ - -#include -#include +#ifndef INCLUDE_PERFETTO_EXT_BASE_CPU_INFO_H_ +#define INCLUDE_PERFETTO_EXT_BASE_CPU_INFO_H_ #include -#include - -// gen_amalgamated expanded: #include "perfetto/base/compiler.h" -// gen_amalgamated expanded: #include "perfetto/ext/base/string_view.h" - -// Crash keys are very simple global variables with static-storage that -// are reported on crash time for managed crashes (CHECK/FATAL/Watchdog). -// - Translation units can define a CrashKey and register it at some point -// during initialization. -// - CrashKey instances must be long-lived. They should really be just global -// static variable in the anonymous namespace. -// Example: -// subsystem_1.cc -// CrashKey g_client_id("ipc_client_id"); -// ... -// OnIpcReceived(client_id) { -// g_client_id.Set(client_id); -// ... // Process the IPC -// g_client_id.Clear(); -// } -// Or equivalently: -// OnIpcReceived(client_id) { -// auto scoped_key = g_client_id.SetScoped(client_id); -// ... // Process the IPC -// } -// -// If a crash happens while processing the IPC, the crash report will -// have a line "ipc_client_id: 42". -// -// Thread safety considerations: -// CrashKeys can be registered and set/cleared from any thread. -// There is no compelling use-case to have full acquire/release consistency when -// setting a key. This means that if a thread crashes immediately after a -// crash key has been set on another thread, the value printed on the crash -// report could be incomplete. The code guarantees defined behavior and does -// not rely on null-terminated string (in the worst case 32 bytes of random -// garbage will be printed out). - -// The tests live in logging_unittest.cc. +#include +#include +#include namespace perfetto { namespace base { -constexpr size_t kCrashKeyMaxStrSize = 32; - -// CrashKey instances must be long lived -class CrashKey { - public: - class ScopedClear { - public: - explicit ScopedClear(CrashKey* k) : key_(k) {} - ~ScopedClear() { - if (key_) - key_->Clear(); - } - ScopedClear(const ScopedClear&) = delete; - ScopedClear& operator=(const ScopedClear&) = delete; - ScopedClear& operator=(ScopedClear&&) = delete; - ScopedClear(ScopedClear&& other) noexcept : key_(other.key_) { - other.key_ = nullptr; - } - - private: - CrashKey* key_; - }; - - // constexpr so it can be used in the anon namespace without requiring a - // global constructor. - // |name| must be a long-lived string. - constexpr explicit CrashKey(const char* name) - : registered_{}, type_(Type::kUnset), name_(name), str_value_{} {} - CrashKey(const CrashKey&) = delete; - CrashKey& operator=(const CrashKey&) = delete; - CrashKey(CrashKey&&) = delete; - CrashKey& operator=(CrashKey&&) = delete; - - enum class Type : uint8_t { kUnset = 0, kInt, kStr }; - - void Clear() { - int_value_.store(0, std::memory_order_relaxed); - type_.store(Type::kUnset, std::memory_order_relaxed); - } - - void Set(int64_t value) { - int_value_.store(value, std::memory_order_relaxed); - type_.store(Type::kInt, std::memory_order_relaxed); - if (PERFETTO_UNLIKELY(!registered_.load(std::memory_order_relaxed))) - Register(); - } - - void Set(StringView sv) { - size_t len = std::min(sv.size(), sizeof(str_value_) - 1); - for (size_t i = 0; i < len; ++i) - str_value_[i].store(sv.data()[i], std::memory_order_relaxed); - str_value_[len].store('\0', std::memory_order_relaxed); - type_.store(Type::kStr, std::memory_order_relaxed); - if (PERFETTO_UNLIKELY(!registered_.load(std::memory_order_relaxed))) - Register(); - } - - ScopedClear SetScoped(int64_t value) PERFETTO_WARN_UNUSED_RESULT { - Set(value); - return ScopedClear(this); - } - - ScopedClear SetScoped(StringView sv) PERFETTO_WARN_UNUSED_RESULT { - Set(sv); - return ScopedClear(this); - } - - void Register(); - - int64_t int_value() const { - return int_value_.load(std::memory_order_relaxed); - } - size_t ToString(char* dst, size_t len); - - private: - std::atomic registered_; - std::atomic type_; - const char* const name_; - union { - std::atomic str_value_[kCrashKeyMaxStrSize]; - std::atomic int_value_; - }; +struct CpuInfo { + std::string processor; + uint32_t cpu_index = 0; + std::optional implementer; + std::optional architecture; + std::optional variant; + std::optional part; + std::optional revision; + uint64_t features = 0; + char arm_cpuid[32] = {}; }; -// Fills |dst| with a string containing one line for each crash key -// (excluding the unset ones). -// Returns number of chars written, without counting the NUL terminator. -// This is used in logging.cc when emitting the crash report abort message. -size_t SerializeCrashKeys(char* dst, size_t len); +// Parses the contents of the input string into per-CPU entries. +std::vector ParseCpuInfo(std::string proc_cpu_info); -void UnregisterAllCrashKeysForTesting(); +// Reads /proc/cpuinfo and parses it into per-CPU entries. +std::vector ReadCpuInfo(); } // namespace base } // namespace perfetto -#endif // INCLUDE_PERFETTO_EXT_BASE_CRASH_KEYS_H_ +#endif // INCLUDE_PERFETTO_EXT_BASE_CPU_INFO_H_ +// gen_amalgamated begin header: include/perfetto/ext/base/cpu_info_features_allowlist.h /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -1724,91 +1615,29 @@ void UnregisterAllCrashKeysForTesting(); * limitations under the License. */ -// gen_amalgamated expanded: #include "perfetto/ext/base/crash_keys.h" - -#include - -#include -#include - -// gen_amalgamated expanded: #include "perfetto/ext/base/string_utils.h" +#ifndef INCLUDE_PERFETTO_EXT_BASE_CPU_INFO_FEATURES_ALLOWLIST_H_ +#define INCLUDE_PERFETTO_EXT_BASE_CPU_INFO_FEATURES_ALLOWLIST_H_ namespace perfetto { namespace base { -namespace { - -constexpr size_t kMaxKeys = 32; - -std::atomic g_keys[kMaxKeys]{}; -std::atomic g_num_keys{}; -} // namespace - -void CrashKey::Register() { - // If doesn't matter if we fail below. If there are no slots left, don't - // keep trying re-registering on every Set(), the outcome won't change. - - // If two threads raced on the Register(), avoid registering the key twice. - if (registered_.exchange(true)) - return; - - uint32_t slot = g_num_keys.fetch_add(1); - if (slot >= kMaxKeys) { - PERFETTO_LOG("Too many crash keys registered"); - return; - } - g_keys[slot].store(this); -} - -// Returns the number of chars written, without counting the \0. -size_t CrashKey::ToString(char* dst, size_t len) { - if (len > 0) - *dst = '\0'; - switch (type_.load(std::memory_order_relaxed)) { - case Type::kUnset: - break; - case Type::kInt: - return SprintfTrunc(dst, len, "%s: %" PRId64 "\n", name_, - int_value_.load(std::memory_order_relaxed)); - case Type::kStr: - char buf[sizeof(str_value_)]; - for (size_t i = 0; i < sizeof(str_value_); i++) - buf[i] = str_value_[i].load(std::memory_order_relaxed); - - // Don't assume |str_value_| is properly null-terminated. - return SprintfTrunc(dst, len, "%s: %.*s\n", name_, int(sizeof(buf)), buf); - } - return 0; -} - -void UnregisterAllCrashKeysForTesting() { - g_num_keys.store(0); - for (auto& key : g_keys) - key.store(nullptr); -} - -size_t SerializeCrashKeys(char* dst, size_t len) { - size_t written = 0; - uint32_t num_keys = g_num_keys.load(); - if (len > 0) - *dst = '\0'; - for (uint32_t i = 0; i < num_keys && written < len; i++) { - CrashKey* key = g_keys[i].load(); - if (!key) - continue; // Can happen if we hit this between the add and the store. - written += key->ToString(dst + written, len - written); - } - PERFETTO_DCHECK(written <= len); - PERFETTO_DCHECK(len == 0 || dst[written] == '\0'); - return written; -} +// APPEND ONLY. DO NOT EVER REMOVE ENTRIES FROM THIS ARRAY OR REORDER. +// This array is used both by traced_probes and trace_processor to index the +// cpuinfo flags. Changing the order will break trace_processor compatibility +// with old traces. +constexpr const char* kCpuInfoFeatures[] = { + "mte", // DO NOT REMOVE/REODER. + "mte3", // DO NOT REMOVE/REODER. +}; } // namespace base } // namespace perfetto -// gen_amalgamated begin source: src/base/ctrl_c_handler.cc -// gen_amalgamated begin header: include/perfetto/ext/base/ctrl_c_handler.h + +#endif // INCLUDE_PERFETTO_EXT_BASE_CPU_INFO_FEATURES_ALLOWLIST_H_ +// gen_amalgamated begin header: include/perfetto/ext/base/file_utils.h +// gen_amalgamated begin header: include/perfetto/base/status.h /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -1823,106 +1652,107 @@ size_t SerializeCrashKeys(char* dst, size_t len) { * limitations under the License. */ -#ifndef INCLUDE_PERFETTO_EXT_BASE_CTRL_C_HANDLER_H_ -#define INCLUDE_PERFETTO_EXT_BASE_CTRL_C_HANDLER_H_ +#ifndef INCLUDE_PERFETTO_BASE_STATUS_H_ +#define INCLUDE_PERFETTO_BASE_STATUS_H_ + +#include +#include +#include +#include + +// gen_amalgamated expanded: #include "perfetto/base/compiler.h" +// gen_amalgamated expanded: #include "perfetto/base/export.h" +// gen_amalgamated expanded: #include "perfetto/base/logging.h" namespace perfetto { namespace base { -// On Linux/Android/Mac: installs SIGINT + SIGTERM signal handlers. -// On Windows: installs a SetConsoleCtrlHandler() handler. -// The passed handler must be async safe. -using CtrlCHandlerFunction = void (*)(); -void InstallCtrlCHandler(CtrlCHandlerFunction); +// Represents either the success or the failure message of a function. +// This can used as the return type of functions which would usually return an +// bool for success or int for errno but also wants to add some string context +// (ususally for logging). +// +// Similar to absl::Status, an optional "payload" can also be included with more +// context about the error. This allows passing additional metadata about the +// error (e.g. location of errors, potential mitigations etc). +class PERFETTO_EXPORT_COMPONENT Status { + public: + Status() : ok_(true) {} + explicit Status(std::string msg) : ok_(false), message_(std::move(msg)) { + PERFETTO_CHECK(!message_.empty()); + } -} // namespace base -} // namespace perfetto + // Copy operations. + Status(const Status&) = default; + Status& operator=(const Status&) = default; -#endif // INCLUDE_PERFETTO_EXT_BASE_CTRL_C_HANDLER_H_ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * 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. - */ + // Move operations. The moved-from state is valid but unspecified. + Status(Status&&) noexcept = default; + Status& operator=(Status&&) = default; -// gen_amalgamated expanded: #include "perfetto/ext/base/ctrl_c_handler.h" + bool ok() const { return ok_; } -// gen_amalgamated expanded: #include "perfetto/base/build_config.h" -// gen_amalgamated expanded: #include "perfetto/base/compiler.h" -// gen_amalgamated expanded: #include "perfetto/base/logging.h" + // When ok() is false this returns the error message. Returns the empty string + // otherwise. + const std::string& message() const { return message_; } + const char* c_message() const { return message_.c_str(); } -#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) -#include + ////////////////////////////////////////////////////////////////////////////// + // Payload Management APIs + ////////////////////////////////////////////////////////////////////////////// -#include -#else -#include -#include -#endif + // Payloads can be attached to error statuses to provide additional context. + // + // Payloads are (key, value) pairs, where the key is a string acting as a + // unique "type URL" and the value is an opaque string. The "type URL" should + // be unique, follow the format of a URL and, ideally, documentation on how to + // interpret its associated data should be available. + // + // To attach a payload to a status object, call `Status::SetPayload()`. + // Similarly, to extract the payload from a status, call + // `Status::GetPayload()`. + // + // Note: the payload APIs are only meaningful to call when the status is an + // error. Otherwise, all methods are noops. -namespace perfetto { -namespace base { + // Gets the payload for the given |type_url| if one exists. + // + // Will always return std::nullopt if |ok()|. + std::optional GetPayload(std::string_view type_url) const; -namespace { -CtrlCHandlerFunction g_handler = nullptr; + // Sets the payload for the given key. The key should + // + // Will always do nothing if |ok()|. + void SetPayload(std::string_view type_url, std::string value); -#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) -BOOL WINAPI Trampoline(DWORD type) { - if (type == CTRL_C_EVENT) { - g_handler(); - return TRUE; - } - return FALSE; -} -#endif -} // namespace + // Erases the payload for the given string and returns true if the payload + // existed and was erased. + // + // Will always do nothing if |ok()|. + bool ErasePayload(std::string_view type_url); -void InstallCtrlCHandler(CtrlCHandlerFunction handler) { - PERFETTO_CHECK(g_handler == nullptr); - g_handler = handler; + private: + struct Payload { + std::string type_url; + std::string payload; + }; -#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) - ::SetConsoleCtrlHandler(Trampoline, true); -#elif PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \ - PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \ - PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE) - // Setup signal handler. - struct sigaction sa{}; + bool ok_ = false; + std::string message_; + std::vector payloads_; +}; -// Glibc headers for sa_sigaction trigger this. -#pragma GCC diagnostic push -#if defined(__clang__) -#pragma GCC diagnostic ignored "-Wdisabled-macro-expansion" -#endif - sa.sa_handler = [](int) { g_handler(); }; -#if !PERFETTO_BUILDFLAG(PERFETTO_OS_QNX) - sa.sa_flags = static_cast(SA_RESETHAND | SA_RESTART); -#else // POSIX-compliant - sa.sa_flags = static_cast(SA_RESETHAND); -#endif -#pragma GCC diagnostic pop - sigaction(SIGINT, &sa, nullptr); - sigaction(SIGTERM, &sa, nullptr); -#else - // Do nothing on NaCL and Fuchsia. - ignore_result(handler); -#endif +// Returns a status object which represents the Ok status. +inline Status OkStatus() { + return Status(); } +Status ErrStatus(const char* format, ...) PERFETTO_PRINTF_FORMAT(1, 2); + } // namespace base } // namespace perfetto -// gen_amalgamated begin source: src/base/event_fd.cc -// gen_amalgamated begin header: include/perfetto/ext/base/event_fd.h + +#endif // INCLUDE_PERFETTO_BASE_STATUS_H_ // gen_amalgamated begin header: include/perfetto/ext/base/scoped_file.h /* * Copyright (C) 2017 The Android Open Source Project @@ -2061,58 +1891,198 @@ using ScopedDir = ScopedResource; * limitations under the License. */ -#ifndef INCLUDE_PERFETTO_EXT_BASE_EVENT_FD_H_ -#define INCLUDE_PERFETTO_EXT_BASE_EVENT_FD_H_ +#ifndef INCLUDE_PERFETTO_EXT_BASE_FILE_UTILS_H_ +#define INCLUDE_PERFETTO_EXT_BASE_FILE_UTILS_H_ + +#include // For mode_t & O_RDONLY/RDWR. Exists also on Windows. +#include + +#include +#include +#include +#include +#include // gen_amalgamated expanded: #include "perfetto/base/build_config.h" -// gen_amalgamated expanded: #include "perfetto/base/platform_handle.h" +// gen_amalgamated expanded: #include "perfetto/base/export.h" +// gen_amalgamated expanded: #include "perfetto/base/status.h" // gen_amalgamated expanded: #include "perfetto/ext/base/scoped_file.h" +// gen_amalgamated expanded: #include "perfetto/ext/base/sys_types.h" namespace perfetto { namespace base { -// A waitable event that can be used with poll/select. -// This is really a wrapper around eventfd_create with a pipe-based fallback -// for other platforms where eventfd is not supported. -class EventFd { - public: - EventFd(); - ~EventFd(); - EventFd(EventFd&&) noexcept = default; - EventFd& operator=(EventFd&&) = default; +class TaskRunner; - // The non-blocking file descriptor that can be polled to wait for the event. - PlatformHandle fd() const { return event_handle_.get(); } +#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) +using FileOpenMode = int; +inline constexpr char kDevNull[] = "NUL"; +inline constexpr char kFopenReadFlag[] = "r"; +#else +using FileOpenMode = mode_t; +inline constexpr char kDevNull[] = "/dev/null"; +inline constexpr char kFopenReadFlag[] = "re"; +#endif - // Can be called from any thread. - void Notify(); +constexpr FileOpenMode kFileModeInvalid = static_cast(-1); - // Can be called from any thread. If more Notify() are queued a Clear() call - // can clear all of them (up to 16 per call). - void Clear(); +// Cross-platform variant of ReadFileDescriptor() that takes a PlatformHandle. +// On Windows normalizes ERROR_BROKEN_PIPE to EOF so behavior matches POSIX. +bool ReadPlatformHandle(PlatformHandle, std::string* out); - private: - // The eventfd, when eventfd is supported, otherwise this is the read end of - // the pipe for fallback mode. - ScopedPlatformHandle event_handle_; +// Reads from |fd|, appending what is currently available into |*out|. +// Returns: +// True: EOF reached (all writers of |fd| have closed their end). +// False: read error. On a non-blocking |fd| this includes EAGAIN (no data +// currently available but writers are still alive); callers can check +// IsAgain(errno) and retry on the next readability notification. +bool ReadFileDescriptor(int fd, std::string* out); -// QNX is specified because it is a non-Linux UNIX platform but it -// still sets the PERFETTO_OS_LINUX flag to be as compatible as possible -// with the Linux build. -#if !PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX_BUT_NOT_QNX) && \ - !PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) && \ - !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) - // On Mac and other non-Linux UNIX platforms a pipe-based fallback is used. - // The write end of the wakeup pipe. - ScopedFile write_fd_; -#endif +// Convenience wrapper around ReadFileDescriptor() that takes a FILE*. +bool ReadFileStream(FILE* f, std::string* out); + +// Opens |path| read-only and reads its contents into |*out|. +// Returns false if the file cannot be opened. +bool ReadFile(const std::string& path, std::string* out); + +// A wrapper around read(2). It deals with Linux vs Windows includes. It also +// deals with handling EINTR. Has the same semantics of UNIX's read(2). +ssize_t Read(int fd, void* dst, size_t dst_size); + +// Call write until all data is written or an error is detected. +// +// man 2 write: +// If a write() is interrupted by a signal handler before any bytes are +// written, then the call fails with the error EINTR; if it is +// interrupted after at least one byte has been written, the call +// succeeds, and returns the number of bytes written. +ssize_t WriteAll(int fd, const void* buf, size_t count); + +// Copies all data from |fd_in| to |fd_out|. Saves the offset of |fd_in|, +// rewinds it to the beginning, copies the content, and restores the offset. +// |fd_in| can't be a pipe, socket of FIFO. +base::Status CopyFileContents(int fd_in, int fd_out); + +ssize_t WriteAllHandle(PlatformHandle, const void* buf, size_t count); + +ScopedFile OpenFile(const std::string& path, + int flags, + FileOpenMode = kFileModeInvalid); +ScopedFstream OpenFstream(const std::string& path, const std::string& mode); + +// This is an alias for close(). It's to avoid leaking windows.h in headers. +// Exported because ScopedFile is used in the /include/ext API by Chromium +// component builds. +int PERFETTO_EXPORT_COMPONENT CloseFile(int fd); + +bool FlushFile(int fd); + +// Returns true if mkdir succeeds, false if it fails (see errno in that case). +// `mode` is the permission bits for the new directory; it is ignored on +// Windows. +bool Mkdir(const std::string& path, uint32_t mode = 0755); + +// Calls rmdir() on UNIX, _rmdir() on Windows. +bool Rmdir(const std::string& path); + +// Removes a file: unlink() on UNIX, _unlink() on Windows. Takes a const char* +// and is async-signal-safe on POSIX, so it's callable from a signal handler. +bool Unlink(const char* path); + +// Wrapper around access(path, F_OK). +bool FileExists(const std::string& path); + +// Gets the extension for a filename. If the file has two extensions, returns +// only the last one (foo.pb.gz => .gz). Returns empty string if there is no +// extension. +std::string GetFileExtension(const std::string& filename); + +// Returns the basename component of a path (the final component after the last +// directory separator). Behaves like man 2 basename, but works with both '/' +// and '\' separators for cross-platform compatibility. +// Examples: +// Basename("/usr/bin/ls") => "ls" +// Basename("/usr/bin/") => "bin" +// Basename("/") => "/" +// Basename("foo") => "foo" +// Basename("") => "." +// Basename("C:\\Windows\\System32") => "System32" +std::string Basename(const std::string& path); + +// Returns the directory component of a path (everything up to but not +// including the final component). Behaves like man 2 dirname, but works with +// both '/' and '\' separators for cross-platform compatibility. +// Examples: +// Dirname("/usr/bin/ls") => "/usr/bin" +// Dirname("/usr/bin") => "/usr" +// Dirname("/") => "/" +// Dirname("foo") => "." +// Dirname("") => "." +// Dirname("C:\\Windows\\System32") => "C:\\Windows" +std::string Dirname(const std::string& path); + +// Puts the path to all files under |dir_path| in |output|, recursively walking +// subdirectories. File paths are relative to |dir_path|. Only files are +// included, not directories. Path separator is always '/', even on windows (not +// '\'). +base::Status ListFilesRecursive(const std::string& dir_path, + std::vector& output); + +// Lists immediate subdirectories in |dir_path| (non-recursive). Directory names +// are relative to |dir_path| and do not include the path separator. Returns +// only directories, not files. Works on both Unix and Windows. +base::Status ListDirectories(const std::string& dir_path, + std::vector& output); + +// Sets |path|'s owner group to |group_name| and permission mode bits to +// |mode_bits|. +base::Status SetFilePermissions(const std::string& path, + const std::string& group_name, + const std::string& mode_bits); + +// Returns the size of the file located at |path|, or nullopt in case of error. +std::optional GetFileSize(const std::string& path); + +// Returns the size of the open file |fd|, or nullopt in case of error. +std::optional GetFileSize(PlatformHandle fd); + +// This class uses inotify (on Linux/Android) to watch for the creation of +// files in the filesystem. When the specified file is created, it triggers a +// callback function. +// Destroying the returned unique_ptr will automatically unregister the watch. +// +// Note: This only works with filesystem paths (not abstract sockets or other +// special file types). +// It's only supported on Linux and Android, it's a no-op (returns nullptr) on +// other platforms. +// +// Usage: +// auto watch = LinuxFileWatch::WatchFileCreation( +// task_runner, "/tmp/my_file", []() { +// // Called when /tmp/my_file is created +// }); +class LinuxFileWatch { + public: + // Creates a watcher for file creation. Returns nullptr if the path is not a + // valid filesystem path or if the platform doesn't support inotify. The + // callback will be invoked on the provided TaskRunner when the file is + // created. + static std::unique_ptr WatchFileCreation( + TaskRunner*, + const char* path, + std::function callback); + + virtual ~LinuxFileWatch(); + + protected: + LinuxFileWatch() = default; }; } // namespace base } // namespace perfetto -#endif // INCLUDE_PERFETTO_EXT_BASE_EVENT_FD_H_ -// gen_amalgamated begin header: include/perfetto/ext/base/pipe.h +#endif // INCLUDE_PERFETTO_EXT_BASE_FILE_UTILS_H_ +// gen_amalgamated begin header: include/perfetto/ext/base/string_splitter.h /* * Copyright (C) 2018 The Android Open Source Project * @@ -2129,42 +2099,93 @@ class EventFd { * limitations under the License. */ -#ifndef INCLUDE_PERFETTO_EXT_BASE_PIPE_H_ -#define INCLUDE_PERFETTO_EXT_BASE_PIPE_H_ +#ifndef INCLUDE_PERFETTO_EXT_BASE_STRING_SPLITTER_H_ +#define INCLUDE_PERFETTO_EXT_BASE_STRING_SPLITTER_H_ -// gen_amalgamated expanded: #include "perfetto/base/platform_handle.h" -// gen_amalgamated expanded: #include "perfetto/ext/base/scoped_file.h" +#include namespace perfetto { namespace base { -class Pipe { +// C++ version of strtok(). Splits a string without making copies or any heap +// allocations. Destructs the original string passed in input. +// Supports the special case of using \0 as a delimiter. +// The token returned in output are valid as long as the input string is valid. +class StringSplitter { public: - enum Flags { - kBothBlock = 0, -#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) - kBothNonBlock, - kRdNonBlock, - kWrNonBlock, -#endif + // Whether an empty string (two delimiters side-to-side) is a valid token. + enum class EmptyTokenMode { + DISALLOW_EMPTY_TOKENS, + ALLOW_EMPTY_TOKENS, + + DEFAULT = DISALLOW_EMPTY_TOKENS, }; - static Pipe Create(Flags = kBothBlock); + // Can take ownership of the string if passed via std::move(), e.g.: + // StringSplitter(std::move(str), '\n'); + StringSplitter(std::string, + char delimiter, + EmptyTokenMode empty_token_mode = EmptyTokenMode::DEFAULT); - Pipe(); - Pipe(Pipe&&) noexcept; - Pipe& operator=(Pipe&&); + // Splits a C-string. The input string will be forcefully null-terminated (so + // str[size - 1] should be == '\0' or the last char will be truncated). + StringSplitter(char* str, + size_t size, + char delimiter, + EmptyTokenMode empty_token_mode = EmptyTokenMode::DEFAULT); - ScopedPlatformHandle rd; - ScopedPlatformHandle wr; + // Splits the current token from an outer StringSplitter instance. This is to + // chain splitters as follows: + // for (base::StringSplitter lines(x, '\n'); ss.Next();) + // for (base::StringSplitter words(&lines, ' '); words.Next();) + StringSplitter(StringSplitter*, + char delimiter, + EmptyTokenMode empty_token_mode = EmptyTokenMode::DEFAULT); + + // Returns true if a token is found (in which case it will be stored in + // cur_token()), false if no more tokens are found. + bool Next(); + + // Returns the next token if found (in which case it will be stored in + // cur_token()), nullptr if no more tokens are found. + char* NextToken() { return Next() ? cur_token() : nullptr; } + + // Returns the current token iff last call to Next() returned true. In this + // case it guarantees that the returned string is always null terminated. + // In all other cases (before the 1st call to Next() and after Next() returns + // false) returns nullptr. + char* cur_token() { return cur_; } + + // Returns the length of the current token (excluding the null terminator). + size_t cur_token_size() const { return cur_size_; } + + // Return the untokenized remainder of the input string that occurs after the + // current token. + char* remainder() { return next_; } + + // Returns the size of the untokenized input + size_t remainder_size() { return static_cast(end_ - next_); } + + private: + StringSplitter(const StringSplitter&) = delete; + StringSplitter& operator=(const StringSplitter&) = delete; + void Initialize(char* str, size_t size); + + std::string str_; + char* cur_; + size_t cur_size_; + char* next_; + char* end_; // STL-style, points one past the last char. + const char delimiter_; + const EmptyTokenMode empty_token_mode_; }; } // namespace base } // namespace perfetto -#endif // INCLUDE_PERFETTO_EXT_BASE_PIPE_H_ +#endif // INCLUDE_PERFETTO_EXT_BASE_STRING_SPLITTER_H_ /* - * Copyright (C) 2018 The Android Open Source Project + * Copyright (C) 2026 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -2179,110 +2200,166 @@ class Pipe { * limitations under the License. */ -// gen_amalgamated expanded: #include "perfetto/base/build_config.h" - -#include -#include - -#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) -#include - -#include -#elif PERFETTO_BUILDFLAG(PERFETTO_OS_QNX) -#include -#elif PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \ - PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) -#include -#include -#else // Mac, Fuchsia and other non-Linux UNIXes -#include -#endif +#include +#include +#include +#include -// gen_amalgamated expanded: #include "perfetto/base/logging.h" -// gen_amalgamated expanded: #include "perfetto/ext/base/event_fd.h" -// gen_amalgamated expanded: #include "perfetto/ext/base/pipe.h" +// gen_amalgamated expanded: #include "perfetto/ext/base/cpu_info.h" +// gen_amalgamated expanded: #include "perfetto/ext/base/cpu_info_features_allowlist.h" +// gen_amalgamated expanded: #include "perfetto/ext/base/file_utils.h" +// gen_amalgamated expanded: #include "perfetto/ext/base/string_splitter.h" +// gen_amalgamated expanded: #include "perfetto/ext/base/string_utils.h" // gen_amalgamated expanded: #include "perfetto/ext/base/utils.h" namespace perfetto { namespace base { +namespace { -EventFd::~EventFd() = default; +// Key for default processor string in /proc/cpuinfo as seen on arm. Note the +// uppercase P. +const char kDefaultProcessor[] = "Processor"; -#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) -EventFd::EventFd() { - event_handle_.reset( - CreateEventA(/*lpEventAttributes=*/nullptr, /*bManualReset=*/true, - /*bInitialState=*/false, /*bInitialState=*/nullptr)); -} +// Key for processor entry in /proc/cpuinfo. Used to determine whether a group +// of lines describes a CPU. +const char kProcessor[] = "processor"; -void EventFd::Notify() { - if (!SetEvent(event_handle_.get())) // 0: fail, !0: success, unlike UNIX. - PERFETTO_DFATAL("EventFd::Notify()"); -} +// Key for CPU implementer in /proc/cpuinfo. Arm only. +const char kImplementer[] = "CPU implementer"; -void EventFd::Clear() { - if (!ResetEvent(event_handle_.get())) // 0: fail, !0: success, unlike UNIX. - PERFETTO_DFATAL("EventFd::Clear()"); -} +// Key for CPU architecture in /proc/cpuinfo. Arm only. +const char kArchitecture[] = "CPU architecture"; -#elif PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX_BUT_NOT_QNX) || \ - PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) +// Key for CPU variant in /proc/cpuinfo. Arm only. +const char kVariant[] = "CPU variant"; -EventFd::EventFd() { - event_handle_.reset(eventfd(/*initval=*/0, EFD_CLOEXEC | EFD_NONBLOCK)); - PERFETTO_CHECK(event_handle_); -} +// Key for CPU part in /proc/cpuinfo. Arm only. +const char kPart[] = "CPU part"; -void EventFd::Notify() { - const uint64_t value = 1; - ssize_t ret = write(event_handle_.get(), &value, sizeof(value)); - if (ret <= 0 && errno != EAGAIN) - PERFETTO_DFATAL("EventFd::Notify()"); -} +// Key for CPU revision in /proc/cpuinfo. Arm only. +const char kRevision[] = "CPU revision"; -void EventFd::Clear() { - uint64_t value; - ssize_t ret = - PERFETTO_EINTR(read(event_handle_.get(), &value, sizeof(value))); - if (ret <= 0 && errno != EAGAIN) - PERFETTO_DFATAL("EventFd::Clear()"); +// Key for feature flags in /proc/cpuinfo. Arm calls them Features, +// Intel calls them Flags. +const char kFeatures[] = "Features"; +const char kFlags[] = "Flags"; + +std::string ReadFile(const std::string& path) { + std::string contents; + if (!base::ReadFile(path, &contents)) + return ""; + return contents; } -#else +} // namespace -EventFd::EventFd() { - // Make the pipe non-blocking so that we never block the waking thread (either - // the main thread or another one) when scheduling a wake-up. - Pipe pipe = Pipe::Create(Pipe::kBothNonBlock); - event_handle_ = ScopedPlatformHandle(std::move(pipe.rd).release()); - write_fd_ = std::move(pipe.wr); -} +std::vector ParseCpuInfo(std::string proc_cpu_info) { + std::vector cpus; + std::string processor = "unknown"; + + std::optional cpu_index; + std::optional implementer; + std::optional architecture; + std::optional variant; + std::optional part; + std::optional revision; + uint64_t features = 0; + uint32_t next_cpu_index = 0; + + auto flush_cpu = [&] { + if (cpu_index.has_value()) { + CpuInfo cpu{}; + cpu.processor = processor; + cpu.cpu_index = *cpu_index; + cpu.implementer = implementer; + cpu.architecture = architecture; + cpu.variant = variant; + cpu.part = part; + cpu.revision = revision; + cpu.features = features; +#if PERFETTO_BUILDFLAG(PERFETTO_ARCH_CPU_ARM64) + if (cpu.implementer && cpu.part) { + std::string cpuid = + base::Uint64ToHexStringNoPrefix(cpu.implementer.value()) + + base::Uint64ToHexStringNoPrefix(cpu.part.value()); + if (cpu.variant) { + cpuid += base::Uint64ToHexStringNoPrefix(cpu.variant.value()); + if (cpu.revision) { + cpuid += base::Uint64ToHexStringNoPrefix(cpu.revision.value()); + } + } + base::StringCopy(cpu.arm_cpuid, cpuid.c_str(), sizeof(cpu.arm_cpuid)); + } +#endif // PERFETTO_BUILDFLAG(PERFETTO_ARCH_CPU_ARM64) + cpus.emplace_back(std::move(cpu)); + next_cpu_index++; + } + cpu_index = std::nullopt; + implementer = std::nullopt; + architecture = std::nullopt; + variant = std::nullopt; + part = std::nullopt; + revision = std::nullopt; + features = 0; + }; -void EventFd::Notify() { - const uint64_t value = 1; - ssize_t ret = write(write_fd_.get(), &value, sizeof(uint8_t)); - if (ret <= 0 && errno != EAGAIN) - PERFETTO_DFATAL("EventFd::Notify()"); + for (base::StringSplitter lines( + std::move(proc_cpu_info), '\n', + base::StringSplitter::EmptyTokenMode::ALLOW_EMPTY_TOKENS); + lines.Next();) { + std::string line(lines.cur_token(), lines.cur_token_size()); + if (line.empty() && cpu_index.has_value()) { + flush_cpu(); + continue; + } + + auto splits = base::SplitString(line, ":"); + if (splits.size() != 2) + continue; + std::string key = + base::StripSuffix(base::StripChars(splits[0], "\t", ' '), " "); + std::string value = base::StripPrefix(splits[1], " "); + + if (key == kDefaultProcessor) { + processor = value; + } else if (key == kProcessor) { + cpu_index = base::StringToUInt32(value); + } else if (key == kImplementer) { + implementer = base::CStringToUInt32(value.data(), 16); + } else if (key == kArchitecture) { + architecture = base::CStringToUInt32(value.data(), 10); + } else if (key == kVariant) { + variant = base::CStringToUInt32(value.data(), 16); + } else if (key == kPart) { + part = base::CStringToUInt32(value.data(), 16); + } else if (key == kRevision) { + revision = base::CStringToUInt32(value.data(), 10); + } else if (key == kFeatures || key == kFlags) { + for (base::StringSplitter ss(value.data(), ' '); ss.Next();) { + for (size_t i = 0; i < base::ArraySize(kCpuInfoFeatures); ++i) { + if (strcmp(ss.cur_token(), kCpuInfoFeatures[i]) == 0) { + static_assert(base::ArraySize(kCpuInfoFeatures) < 64); + features |= 1ull << i; + } + } + } + } + } + + flush_cpu(); + return cpus; } -void EventFd::Clear() { - // Drain the byte(s) written to the wake-up pipe. We can potentially read - // more than one byte if several wake-ups have been scheduled. - char buffer[16]; - ssize_t ret = - PERFETTO_EINTR(read(event_handle_.get(), &buffer[0], sizeof(buffer))); - if (ret <= 0 && errno != EAGAIN) - PERFETTO_DFATAL("EventFd::Clear()"); +std::vector ReadCpuInfo() { + return ParseCpuInfo(ReadFile("/proc/cpuinfo")); } -#endif } // namespace base } // namespace perfetto -// gen_amalgamated begin source: src/base/file_utils.cc -// gen_amalgamated begin header: include/perfetto/ext/base/file_utils.h -// gen_amalgamated begin header: include/perfetto/base/status.h +// gen_amalgamated begin source: src/base/crash_keys.cc +// gen_amalgamated begin header: include/perfetto/ext/base/crash_keys.h /* - * Copyright (C) 2019 The Android Open Source Project + * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -2297,107 +2374,695 @@ void EventFd::Clear() { * limitations under the License. */ -#ifndef INCLUDE_PERFETTO_BASE_STATUS_H_ -#define INCLUDE_PERFETTO_BASE_STATUS_H_ +#ifndef INCLUDE_PERFETTO_EXT_BASE_CRASH_KEYS_H_ +#define INCLUDE_PERFETTO_EXT_BASE_CRASH_KEYS_H_ -#include -#include -#include -#include +#include +#include + +#include +#include // gen_amalgamated expanded: #include "perfetto/base/compiler.h" -// gen_amalgamated expanded: #include "perfetto/base/export.h" -// gen_amalgamated expanded: #include "perfetto/base/logging.h" +// gen_amalgamated expanded: #include "perfetto/ext/base/string_view.h" + +// Crash keys are very simple global variables with static-storage that +// are reported on crash time for managed crashes (CHECK/FATAL/Watchdog). +// - Translation units can define a CrashKey and register it at some point +// during initialization. +// - CrashKey instances must be long-lived. They should really be just global +// static variable in the anonymous namespace. +// Example: +// subsystem_1.cc +// CrashKey g_client_id("ipc_client_id"); +// ... +// OnIpcReceived(client_id) { +// g_client_id.Set(client_id); +// ... // Process the IPC +// g_client_id.Clear(); +// } +// Or equivalently: +// OnIpcReceived(client_id) { +// auto scoped_key = g_client_id.SetScoped(client_id); +// ... // Process the IPC +// } +// +// If a crash happens while processing the IPC, the crash report will +// have a line "ipc_client_id: 42". +// +// Thread safety considerations: +// CrashKeys can be registered and set/cleared from any thread. +// There is no compelling use-case to have full acquire/release consistency when +// setting a key. This means that if a thread crashes immediately after a +// crash key has been set on another thread, the value printed on the crash +// report could be incomplete. The code guarantees defined behavior and does +// not rely on null-terminated string (in the worst case 32 bytes of random +// garbage will be printed out). + +// The tests live in logging_unittest.cc. namespace perfetto { namespace base { -// Represents either the success or the failure message of a function. -// This can used as the return type of functions which would usually return an -// bool for success or int for errno but also wants to add some string context -// (ususally for logging). -// -// Similar to absl::Status, an optional "payload" can also be included with more -// context about the error. This allows passing additional metadata about the -// error (e.g. location of errors, potential mitigations etc). -class PERFETTO_EXPORT_COMPONENT Status { +constexpr size_t kCrashKeyMaxStrSize = 32; + +// CrashKey instances must be long lived +class CrashKey { public: - Status() : ok_(true) {} - explicit Status(std::string msg) : ok_(false), message_(std::move(msg)) { - PERFETTO_CHECK(!message_.empty()); - } + class ScopedClear { + public: + explicit ScopedClear(CrashKey* k) : key_(k) {} + ~ScopedClear() { + if (key_) + key_->Clear(); + } + ScopedClear(const ScopedClear&) = delete; + ScopedClear& operator=(const ScopedClear&) = delete; + ScopedClear& operator=(ScopedClear&&) = delete; + ScopedClear(ScopedClear&& other) noexcept : key_(other.key_) { + other.key_ = nullptr; + } - // Copy operations. - Status(const Status&) = default; - Status& operator=(const Status&) = default; + private: + CrashKey* key_; + }; - // Move operations. The moved-from state is valid but unspecified. - Status(Status&&) noexcept = default; - Status& operator=(Status&&) = default; + // constexpr so it can be used in the anon namespace without requiring a + // global constructor. + // |name| must be a long-lived string. + constexpr explicit CrashKey(const char* name) + : registered_{}, type_(Type::kUnset), name_(name), str_value_{} {} + CrashKey(const CrashKey&) = delete; + CrashKey& operator=(const CrashKey&) = delete; + CrashKey(CrashKey&&) = delete; + CrashKey& operator=(CrashKey&&) = delete; - bool ok() const { return ok_; } + enum class Type : uint8_t { kUnset = 0, kInt, kStr }; - // When ok() is false this returns the error message. Returns the empty string - // otherwise. - const std::string& message() const { return message_; } - const char* c_message() const { return message_.c_str(); } + void Clear() { + int_value_.store(0, std::memory_order_relaxed); + type_.store(Type::kUnset, std::memory_order_relaxed); + } - ////////////////////////////////////////////////////////////////////////////// - // Payload Management APIs - ////////////////////////////////////////////////////////////////////////////// + void Set(int64_t value) { + int_value_.store(value, std::memory_order_relaxed); + type_.store(Type::kInt, std::memory_order_relaxed); + if (PERFETTO_UNLIKELY(!registered_.load(std::memory_order_relaxed))) + Register(); + } - // Payloads can be attached to error statuses to provide additional context. - // - // Payloads are (key, value) pairs, where the key is a string acting as a - // unique "type URL" and the value is an opaque string. The "type URL" should - // be unique, follow the format of a URL and, ideally, documentation on how to - // interpret its associated data should be available. - // - // To attach a payload to a status object, call `Status::SetPayload()`. - // Similarly, to extract the payload from a status, call - // `Status::GetPayload()`. - // - // Note: the payload APIs are only meaningful to call when the status is an - // error. Otherwise, all methods are noops. + void Set(StringView sv) { + size_t len = std::min(sv.size(), sizeof(str_value_) - 1); + for (size_t i = 0; i < len; ++i) + str_value_[i].store(sv.data()[i], std::memory_order_relaxed); + str_value_[len].store('\0', std::memory_order_relaxed); + type_.store(Type::kStr, std::memory_order_relaxed); + if (PERFETTO_UNLIKELY(!registered_.load(std::memory_order_relaxed))) + Register(); + } - // Gets the payload for the given |type_url| if one exists. - // - // Will always return std::nullopt if |ok()|. - std::optional GetPayload(std::string_view type_url) const; + ScopedClear SetScoped(int64_t value) PERFETTO_WARN_UNUSED_RESULT { + Set(value); + return ScopedClear(this); + } - // Sets the payload for the given key. The key should - // - // Will always do nothing if |ok()|. - void SetPayload(std::string_view type_url, std::string value); + ScopedClear SetScoped(StringView sv) PERFETTO_WARN_UNUSED_RESULT { + Set(sv); + return ScopedClear(this); + } - // Erases the payload for the given string and returns true if the payload - // existed and was erased. - // - // Will always do nothing if |ok()|. - bool ErasePayload(std::string_view type_url); + void Register(); + + int64_t int_value() const { + return int_value_.load(std::memory_order_relaxed); + } + size_t ToString(char* dst, size_t len); private: - struct Payload { - std::string type_url; - std::string payload; + std::atomic registered_; + std::atomic type_; + const char* const name_; + union { + std::atomic str_value_[kCrashKeyMaxStrSize]; + std::atomic int_value_; }; +}; - bool ok_ = false; - std::string message_; - std::vector payloads_; +// Fills |dst| with a string containing one line for each crash key +// (excluding the unset ones). +// Returns number of chars written, without counting the NUL terminator. +// This is used in logging.cc when emitting the crash report abort message. +size_t SerializeCrashKeys(char* dst, size_t len); + +void UnregisterAllCrashKeysForTesting(); + +} // namespace base +} // namespace perfetto + +#endif // INCLUDE_PERFETTO_EXT_BASE_CRASH_KEYS_H_ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * 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. + */ + +// gen_amalgamated expanded: #include "perfetto/ext/base/crash_keys.h" + +#include + +#include +#include + +// gen_amalgamated expanded: #include "perfetto/ext/base/string_utils.h" + +namespace perfetto { +namespace base { + +namespace { + +constexpr size_t kMaxKeys = 32; + +std::atomic g_keys[kMaxKeys]{}; +std::atomic g_num_keys{}; +} // namespace + +void CrashKey::Register() { + // If doesn't matter if we fail below. If there are no slots left, don't + // keep trying re-registering on every Set(), the outcome won't change. + + // If two threads raced on the Register(), avoid registering the key twice. + if (registered_.exchange(true)) + return; + + uint32_t slot = g_num_keys.fetch_add(1); + if (slot >= kMaxKeys) { + PERFETTO_LOG("Too many crash keys registered"); + return; + } + g_keys[slot].store(this); +} + +// Returns the number of chars written, without counting the \0. +size_t CrashKey::ToString(char* dst, size_t len) { + if (len > 0) + *dst = '\0'; + switch (type_.load(std::memory_order_relaxed)) { + case Type::kUnset: + break; + case Type::kInt: + return SprintfTrunc(dst, len, "%s: %" PRId64 "\n", name_, + int_value_.load(std::memory_order_relaxed)); + case Type::kStr: + char buf[sizeof(str_value_)]; + for (size_t i = 0; i < sizeof(str_value_); i++) + buf[i] = str_value_[i].load(std::memory_order_relaxed); + + // Don't assume |str_value_| is properly null-terminated. + return SprintfTrunc(dst, len, "%s: %.*s\n", name_, int(sizeof(buf)), buf); + } + return 0; +} + +void UnregisterAllCrashKeysForTesting() { + g_num_keys.store(0); + for (auto& key : g_keys) + key.store(nullptr); +} + +size_t SerializeCrashKeys(char* dst, size_t len) { + size_t written = 0; + uint32_t num_keys = g_num_keys.load(); + if (len > 0) + *dst = '\0'; + for (uint32_t i = 0; i < num_keys && written < len; i++) { + CrashKey* key = g_keys[i].load(); + if (!key) + continue; // Can happen if we hit this between the add and the store. + written += key->ToString(dst + written, len - written); + } + PERFETTO_DCHECK(written <= len); + PERFETTO_DCHECK(len == 0 || dst[written] == '\0'); + return written; +} + +} // namespace base +} // namespace perfetto +// gen_amalgamated begin source: src/base/ctrl_c_handler.cc +// gen_amalgamated begin header: include/perfetto/ext/base/ctrl_c_handler.h +/* + * Copyright (C) 2021 The Android Open Source Project + * + * 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 INCLUDE_PERFETTO_EXT_BASE_CTRL_C_HANDLER_H_ +#define INCLUDE_PERFETTO_EXT_BASE_CTRL_C_HANDLER_H_ + +namespace perfetto { +namespace base { + +// On Linux/Android/Mac: installs SIGINT + SIGTERM signal handlers. +// On Windows: installs a SetConsoleCtrlHandler() handler. +// The passed handler must be async safe. +using CtrlCHandlerFunction = void (*)(); +void InstallCtrlCHandler(CtrlCHandlerFunction); + +} // namespace base +} // namespace perfetto + +#endif // INCLUDE_PERFETTO_EXT_BASE_CTRL_C_HANDLER_H_ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * 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. + */ + +// gen_amalgamated expanded: #include "perfetto/ext/base/ctrl_c_handler.h" + +// gen_amalgamated expanded: #include "perfetto/base/build_config.h" +// gen_amalgamated expanded: #include "perfetto/base/compiler.h" +// gen_amalgamated expanded: #include "perfetto/base/logging.h" + +#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) +#include + +#include +#else +#include +#include +#endif + +namespace perfetto { +namespace base { + +namespace { +CtrlCHandlerFunction g_handler = nullptr; + +#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) +BOOL WINAPI Trampoline(DWORD type) { + if (type == CTRL_C_EVENT) { + g_handler(); + return TRUE; + } + return FALSE; +} +#endif +} // namespace + +void InstallCtrlCHandler(CtrlCHandlerFunction handler) { + PERFETTO_CHECK(g_handler == nullptr); + g_handler = handler; + +#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) + ::SetConsoleCtrlHandler(Trampoline, true); +#elif PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \ + PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \ + PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE) + // Setup signal handler. + struct sigaction sa{}; + +// Glibc headers for sa_sigaction trigger this. +#pragma GCC diagnostic push +#if defined(__clang__) +#pragma GCC diagnostic ignored "-Wdisabled-macro-expansion" +#endif + sa.sa_handler = [](int) { g_handler(); }; +#if !PERFETTO_BUILDFLAG(PERFETTO_OS_QNX) + sa.sa_flags = static_cast(SA_RESETHAND | SA_RESTART); +#else // POSIX-compliant + sa.sa_flags = static_cast(SA_RESETHAND); +#endif +#pragma GCC diagnostic pop + sigaction(SIGINT, &sa, nullptr); + sigaction(SIGTERM, &sa, nullptr); +#else + // Do nothing on NaCL and Fuchsia. + ignore_result(handler); +#endif +} + +} // namespace base +} // namespace perfetto +// gen_amalgamated begin source: src/base/dynamic_string_writer.cc +// gen_amalgamated begin header: include/perfetto/ext/base/dynamic_string_writer.h +/* + * Copyright (C) 2019 The Android Open Source Project + * + * 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 INCLUDE_PERFETTO_EXT_BASE_DYNAMIC_STRING_WRITER_H_ +#define INCLUDE_PERFETTO_EXT_BASE_DYNAMIC_STRING_WRITER_H_ + +#include + +#include +#include +#include +#include +#include +#include +#include + +// gen_amalgamated expanded: #include "perfetto/base/logging.h" +// gen_amalgamated expanded: #include "perfetto/ext/base/string_utils.h" +// gen_amalgamated expanded: #include "perfetto/ext/base/string_view.h" + +namespace perfetto { +namespace base { + +// A helper class which writes formatted data to a string buffer. +// This is used in the trace processor where we write O(GBs) of strings and +// sprintf is too slow. +class DynamicStringWriter { + public: + using ScopedCString = std::unique_ptr; + + // Creates a string buffer from a char buffer and length. + DynamicStringWriter() {} + + // Appends n instances of a char to the buffer. + void AppendChar(char in, size_t n = 1) { buffer_.append(n, in); } + + // Appends a length delimited string to the buffer. + void AppendString(const char* in, size_t n) { buffer_.append(in, n); } + + void AppendStringView(StringView sv) { AppendString(sv.data(), sv.size()); } + + // Appends a null-terminated string literal to the buffer. + template + inline void AppendLiteral(const char (&in)[N]) { + AppendString(in, N - 1); + } + + // Appends a StringView to the buffer. + void AppendString(StringView data) { + buffer_.append(data.data(), data.size()); + } + + // Appends an integer to the buffer. + void AppendInt(int64_t value) { + constexpr size_t STACK_BUFFER_SIZE = 32; + StackString buf("%" PRId64, value); + AppendString(buf.string_view()); + } + + // Appends an integer to the buffer, padding with |padchar| if the number of + // digits of the integer is less than |padding|. + template + void AppendPaddedInt(int64_t sign_value) { + const bool negate = std::signbit(static_cast(sign_value)); + uint64_t absolute_value; + if (sign_value == std::numeric_limits::min()) { + absolute_value = + static_cast(std::numeric_limits::max()) + 1; + } else { + absolute_value = static_cast(std::abs(sign_value)); + } + AppendPaddedIntImpl(absolute_value, negate); + } + + void AppendUnsignedInt(uint64_t value) { + constexpr size_t STACK_BUFFER_SIZE = 32; + StackString buf("%" PRIu64, value); + AppendString(buf.string_view()); + } + + template + void AppendPaddedUnsignedInt(uint64_t value) { + AppendPaddedIntImpl(value, false); + } + + template + void AppendPaddedHexInt(IntType value, char padchar, uint64_t padding) { + using UnsignedType = std::make_unsigned_t; + constexpr size_t kMaxHexDigits = sizeof(IntType) * 2; + constexpr size_t kBufferSize = 32; + auto size_needed = + kMaxHexDigits > padding ? kMaxHexDigits : static_cast(padding); + PERFETTO_DCHECK(size_needed <= kBufferSize); + + std::array data; + constexpr char hex_asc[] = "0123456789abcdef"; + + size_t idx = size_needed - 1; + auto uvalue = static_cast(value); + do { + data[idx--] = hex_asc[uvalue & 0xF]; + uvalue >>= 4; + } while (uvalue != 0); + + if (padding > 0) { + const auto num_digits = static_cast(size_needed - 1 - idx); + // std::max() needed to work around GCC not being able to tell that + // padding > 0. + for (auto i = num_digits; i < std::max(uint64_t{1u}, padding); i++) { + data[idx--] = padchar; + } + } + AppendString(&data[idx + 1], size_needed - idx - 1); + } + + // Appends a hex integer to the buffer. + template + void AppendHexInt(IntType value) { + constexpr size_t STACK_BUFFER_SIZE = 64; + StackString buf("%" PRIx64, value); + AppendString(buf.string_view()); + } + + void AppendHexString(const uint8_t* data, size_t size, char separator); + + void AppendHexString(StringView data, char separator) { + AppendHexString(reinterpret_cast(data.data()), data.size(), + separator); + } + + // Appends a double to the buffer. + void AppendDouble(double value) { + constexpr size_t STACK_BUFFER_SIZE = 32; + StackString buf("%.16g", value); + AppendString(buf.string_view()); + } + + void AppendBool(bool value) { + if (value) { + AppendLiteral("true"); + return; + } + AppendLiteral("false"); + } + + StringView GetStringView() { + return StringView(buffer_.c_str(), buffer_.size()); + } + + ScopedCString CreateStringCopy() const { + size_t n = buffer_.size(); + char* dup = reinterpret_cast(malloc(n + 1)); + if (dup) { + memcpy(dup, buffer_.data(), n); + dup[n] = '\0'; + } + return {dup, free}; + } + + size_t pos() const { return buffer_.size(); } + + void Clear() { buffer_.clear(); } + + private: + template + void AppendPaddedIntImpl(uint64_t absolute_value, bool negate) { + // Need to add 2 to the number of digits to account for minus sign and + // rounding down of digits10. + constexpr auto kMaxDigits = std::numeric_limits::digits10 + 2; + constexpr auto kSizeNeeded = kMaxDigits > padding ? kMaxDigits : padding; + + char data[kSizeNeeded]; + + size_t idx; + for (idx = kSizeNeeded - 1; absolute_value >= 10;) { + char digit = absolute_value % 10; + absolute_value /= 10; + data[idx--] = digit + '0'; + } + data[idx--] = static_cast(absolute_value) + '0'; + + if (padding > 0) { + size_t num_digits = kSizeNeeded - 1 - idx; + // std::max() needed to work around GCC not being able to tell that + // padding > 0. + for (size_t i = num_digits; i < std::max(uint64_t{1u}, padding); i++) { + data[idx--] = padchar; + } + } + + if (negate) + AppendChar('-'); + AppendString(&data[idx + 1], kSizeNeeded - idx - 1); + } + + std::string buffer_; }; -// Returns a status object which represents the Ok status. -inline Status OkStatus() { - return Status(); -} +} // namespace base +} // namespace perfetto + +#endif // INCLUDE_PERFETTO_EXT_BASE_DYNAMIC_STRING_WRITER_H_ +/* + * Copyright (C) 2026 The Android Open Source Project + * + * 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. + */ + +// gen_amalgamated expanded: #include "perfetto/ext/base/dynamic_string_writer.h" + +#include +#include +#include + +namespace perfetto { +namespace base { + +void DynamicStringWriter::AppendHexString(const uint8_t* data, + size_t size, + char separator) { + // Truncate to 64 bytes, as this is the maximum supported by the Linux + // kernel's vsnprintf implementation. + size_t printed_size = std::min(size, size_t{64}); + + if (printed_size) { + AppendPaddedHexInt(data[0], '0', 2); + } + for (size_t pos = 1; pos < printed_size; pos++) { + AppendChar(separator); + AppendPaddedHexInt(data[pos], '0', 2); + } +} + +} // namespace base +} // namespace perfetto +// gen_amalgamated begin source: src/base/event_fd.cc +// gen_amalgamated begin header: include/perfetto/ext/base/event_fd.h +/* + * Copyright (C) 2018 The Android Open Source Project + * + * 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 INCLUDE_PERFETTO_EXT_BASE_EVENT_FD_H_ +#define INCLUDE_PERFETTO_EXT_BASE_EVENT_FD_H_ + +// gen_amalgamated expanded: #include "perfetto/base/build_config.h" +// gen_amalgamated expanded: #include "perfetto/base/platform_handle.h" +// gen_amalgamated expanded: #include "perfetto/ext/base/scoped_file.h" + +namespace perfetto { +namespace base { + +// A waitable event that can be used with poll/select. +// This is really a wrapper around eventfd_create with a pipe-based fallback +// for other platforms where eventfd is not supported. +class EventFd { + public: + EventFd(); + ~EventFd(); + EventFd(EventFd&&) noexcept = default; + EventFd& operator=(EventFd&&) = default; + + // The non-blocking file descriptor that can be polled to wait for the event. + PlatformHandle fd() const { return event_handle_.get(); } + + // Can be called from any thread. + void Notify(); + + // Can be called from any thread. If more Notify() are queued a Clear() call + // can clear all of them (up to 16 per call). + void Clear(); + + private: + // The eventfd, when eventfd is supported, otherwise this is the read end of + // the pipe for fallback mode. + ScopedPlatformHandle event_handle_; -Status ErrStatus(const char* format, ...) PERFETTO_PRINTF_FORMAT(1, 2); +// QNX is specified because it is a non-Linux UNIX platform but it +// still sets the PERFETTO_OS_LINUX flag to be as compatible as possible +// with the Linux build. +#if !PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX_BUT_NOT_QNX) && \ + !PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) && \ + !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) + // On Mac and other non-Linux UNIX platforms a pipe-based fallback is used. + // The write end of the wakeup pipe. + ScopedFile write_fd_; +#endif +}; } // namespace base } // namespace perfetto -#endif // INCLUDE_PERFETTO_BASE_STATUS_H_ +#endif // INCLUDE_PERFETTO_EXT_BASE_EVENT_FD_H_ +// gen_amalgamated begin header: include/perfetto/ext/base/pipe.h /* * Copyright (C) 2018 The Android Open Source Project * @@ -2414,177 +3079,156 @@ Status ErrStatus(const char* format, ...) PERFETTO_PRINTF_FORMAT(1, 2); * limitations under the License. */ -#ifndef INCLUDE_PERFETTO_EXT_BASE_FILE_UTILS_H_ -#define INCLUDE_PERFETTO_EXT_BASE_FILE_UTILS_H_ - -#include // For mode_t & O_RDONLY/RDWR. Exists also on Windows. -#include - -#include -#include -#include -#include -#include +#ifndef INCLUDE_PERFETTO_EXT_BASE_PIPE_H_ +#define INCLUDE_PERFETTO_EXT_BASE_PIPE_H_ -// gen_amalgamated expanded: #include "perfetto/base/build_config.h" -// gen_amalgamated expanded: #include "perfetto/base/export.h" -// gen_amalgamated expanded: #include "perfetto/base/status.h" +// gen_amalgamated expanded: #include "perfetto/base/platform_handle.h" // gen_amalgamated expanded: #include "perfetto/ext/base/scoped_file.h" -// gen_amalgamated expanded: #include "perfetto/ext/base/sys_types.h" namespace perfetto { namespace base { -class TaskRunner; - -#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) -using FileOpenMode = int; -inline constexpr char kDevNull[] = "NUL"; -inline constexpr char kFopenReadFlag[] = "r"; -#else -using FileOpenMode = mode_t; -inline constexpr char kDevNull[] = "/dev/null"; -inline constexpr char kFopenReadFlag[] = "re"; +class Pipe { + public: + enum Flags { + kBothBlock = 0, +#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) + kBothNonBlock, + kRdNonBlock, + kWrNonBlock, #endif + }; -constexpr FileOpenMode kFileModeInvalid = static_cast(-1); + static Pipe Create(Flags = kBothBlock); -bool ReadPlatformHandle(PlatformHandle, std::string* out); -bool ReadFileDescriptor(int fd, std::string* out); -bool ReadFileStream(FILE* f, std::string* out); -bool ReadFile(const std::string& path, std::string* out); + Pipe(); + Pipe(Pipe&&) noexcept; + Pipe& operator=(Pipe&&); -// A wrapper around read(2). It deals with Linux vs Windows includes. It also -// deals with handling EINTR. Has the same semantics of UNIX's read(2). -ssize_t Read(int fd, void* dst, size_t dst_size); + ScopedPlatformHandle rd; + ScopedPlatformHandle wr; +}; -// Call write until all data is written or an error is detected. -// -// man 2 write: -// If a write() is interrupted by a signal handler before any bytes are -// written, then the call fails with the error EINTR; if it is -// interrupted after at least one byte has been written, the call -// succeeds, and returns the number of bytes written. -ssize_t WriteAll(int fd, const void* buf, size_t count); +} // namespace base +} // namespace perfetto -// Copies all data from |fd_in| to |fd_out|. Saves the offset of |fd_in|, -// rewinds it to the beginning, copies the content, and restores the offset. -// |fd_in| can't be a pipe, socket of FIFO. -base::Status CopyFileContents(int fd_in, int fd_out); +#endif // INCLUDE_PERFETTO_EXT_BASE_PIPE_H_ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * 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. + */ -ssize_t WriteAllHandle(PlatformHandle, const void* buf, size_t count); +// gen_amalgamated expanded: #include "perfetto/base/build_config.h" -ScopedFile OpenFile(const std::string& path, - int flags, - FileOpenMode = kFileModeInvalid); -ScopedFstream OpenFstream(const std::string& path, const std::string& mode); +#include +#include -// This is an alias for close(). It's to avoid leaking windows.h in headers. -// Exported because ScopedFile is used in the /include/ext API by Chromium -// component builds. -int PERFETTO_EXPORT_COMPONENT CloseFile(int fd); +#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) +#include -bool FlushFile(int fd); +#include +#elif PERFETTO_BUILDFLAG(PERFETTO_OS_QNX) +#include +#elif PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \ + PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) +#include +#include +#else // Mac, Fuchsia and other non-Linux UNIXes +#include +#endif -// Returns true if mkdir succeeds, false if it fails (see errno in that case). -bool Mkdir(const std::string& path); +// gen_amalgamated expanded: #include "perfetto/base/logging.h" +// gen_amalgamated expanded: #include "perfetto/ext/base/event_fd.h" +// gen_amalgamated expanded: #include "perfetto/ext/base/pipe.h" +// gen_amalgamated expanded: #include "perfetto/ext/base/utils.h" -// Calls rmdir() on UNIX, _rmdir() on Windows. -bool Rmdir(const std::string& path); +namespace perfetto { +namespace base { -// Wrapper around access(path, F_OK). -bool FileExists(const std::string& path); +EventFd::~EventFd() = default; -// Gets the extension for a filename. If the file has two extensions, returns -// only the last one (foo.pb.gz => .gz). Returns empty string if there is no -// extension. -std::string GetFileExtension(const std::string& filename); +#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) +EventFd::EventFd() { + event_handle_.reset( + CreateEventA(/*lpEventAttributes=*/nullptr, /*bManualReset=*/true, + /*bInitialState=*/false, /*bInitialState=*/nullptr)); +} -// Returns the basename component of a path (the final component after the last -// directory separator). Behaves like man 2 basename, but works with both '/' -// and '\' separators for cross-platform compatibility. -// Examples: -// Basename("/usr/bin/ls") => "ls" -// Basename("/usr/bin/") => "bin" -// Basename("/") => "/" -// Basename("foo") => "foo" -// Basename("") => "." -// Basename("C:\\Windows\\System32") => "System32" -std::string Basename(const std::string& path); +void EventFd::Notify() { + if (!SetEvent(event_handle_.get())) // 0: fail, !0: success, unlike UNIX. + PERFETTO_DFATAL("EventFd::Notify()"); +} -// Returns the directory component of a path (everything up to but not -// including the final component). Behaves like man 2 dirname, but works with -// both '/' and '\' separators for cross-platform compatibility. -// Examples: -// Dirname("/usr/bin/ls") => "/usr/bin" -// Dirname("/usr/bin") => "/usr" -// Dirname("/") => "/" -// Dirname("foo") => "." -// Dirname("") => "." -// Dirname("C:\\Windows\\System32") => "C:\\Windows" -std::string Dirname(const std::string& path); +void EventFd::Clear() { + if (!ResetEvent(event_handle_.get())) // 0: fail, !0: success, unlike UNIX. + PERFETTO_DFATAL("EventFd::Clear()"); +} -// Puts the path to all files under |dir_path| in |output|, recursively walking -// subdirectories. File paths are relative to |dir_path|. Only files are -// included, not directories. Path separator is always '/', even on windows (not -// '\'). -base::Status ListFilesRecursive(const std::string& dir_path, - std::vector& output); +#elif PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX_BUT_NOT_QNX) || \ + PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) -// Lists immediate subdirectories in |dir_path| (non-recursive). Directory names -// are relative to |dir_path| and do not include the path separator. Returns -// only directories, not files. Works on both Unix and Windows. -base::Status ListDirectories(const std::string& dir_path, - std::vector& output); +EventFd::EventFd() { + event_handle_.reset(eventfd(/*initval=*/0, EFD_CLOEXEC | EFD_NONBLOCK)); + PERFETTO_CHECK(event_handle_); +} -// Sets |path|'s owner group to |group_name| and permission mode bits to -// |mode_bits|. -base::Status SetFilePermissions(const std::string& path, - const std::string& group_name, - const std::string& mode_bits); +void EventFd::Notify() { + const uint64_t value = 1; + ssize_t ret = write(event_handle_.get(), &value, sizeof(value)); + if (ret <= 0 && errno != EAGAIN) + PERFETTO_DFATAL("EventFd::Notify()"); +} -// Returns the size of the file located at |path|, or nullopt in case of error. -std::optional GetFileSize(const std::string& path); +void EventFd::Clear() { + uint64_t value; + ssize_t ret = + PERFETTO_EINTR(read(event_handle_.get(), &value, sizeof(value))); + if (ret <= 0 && errno != EAGAIN) + PERFETTO_DFATAL("EventFd::Clear()"); +} -// Returns the size of the open file |fd|, or nullopt in case of error. -std::optional GetFileSize(PlatformHandle fd); +#else -// This class uses inotify (on Linux/Android) to watch for the creation of -// files in the filesystem. When the specified file is created, it triggers a -// callback function. -// Destroying the returned unique_ptr will automatically unregister the watch. -// -// Note: This only works with filesystem paths (not abstract sockets or other -// special file types). -// It's only supported on Linux and Android, it's a no-op (returns nullptr) on -// other platforms. -// -// Usage: -// auto watch = LinuxFileWatch::WatchFileCreation( -// task_runner, "/tmp/my_file", []() { -// // Called when /tmp/my_file is created -// }); -class LinuxFileWatch { - public: - // Creates a watcher for file creation. Returns nullptr if the path is not a - // valid filesystem path or if the platform doesn't support inotify. The - // callback will be invoked on the provided TaskRunner when the file is - // created. - static std::unique_ptr WatchFileCreation( - TaskRunner*, - const char* path, - std::function callback); +EventFd::EventFd() { + // Make the pipe non-blocking so that we never block the waking thread (either + // the main thread or another one) when scheduling a wake-up. + Pipe pipe = Pipe::Create(Pipe::kBothNonBlock); + event_handle_ = ScopedPlatformHandle(std::move(pipe.rd).release()); + write_fd_ = std::move(pipe.wr); +} - virtual ~LinuxFileWatch(); +void EventFd::Notify() { + const uint64_t value = 1; + ssize_t ret = write(write_fd_.get(), &value, sizeof(uint8_t)); + if (ret <= 0 && errno != EAGAIN) + PERFETTO_DFATAL("EventFd::Notify()"); +} - protected: - LinuxFileWatch() = default; -}; +void EventFd::Clear() { + // Drain the byte(s) written to the wake-up pipe. We can potentially read + // more than one byte if several wake-ups have been scheduled. + char buffer[16]; + ssize_t ret = + PERFETTO_EINTR(read(event_handle_.get(), &buffer[0], sizeof(buffer))); + if (ret <= 0 && errno != EAGAIN) + PERFETTO_DFATAL("EventFd::Clear()"); +} +#endif } // namespace base } // namespace perfetto - -#endif // INCLUDE_PERFETTO_EXT_BASE_FILE_UTILS_H_ +// gen_amalgamated begin source: src/base/file_utils.cc // gen_amalgamated begin header: include/perfetto/base/task_runner.h /* * Copyright (C) 2017 The Android Open Source Project @@ -3121,11 +3765,12 @@ bool FlushFile(int fd) { #endif } -bool Mkdir(const std::string& path) { +bool Mkdir(const std::string& path, uint32_t mode) { #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) + base::ignore_result(mode); return _mkdir(path.c_str()) == 0; #else - return mkdir(path.c_str(), 0755) == 0; + return mkdir(path.c_str(), mode) == 0; #endif } @@ -3137,6 +3782,14 @@ bool Rmdir(const std::string& path) { #endif } +bool Unlink(const char* path) { +#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) + return _unlink(path) == 0; +#else + return unlink(path) == 0; +#endif +} + int CloseFile(int fd) { return close(fd); } @@ -3265,6 +3918,9 @@ base::Status ListFilesRecursive(const std::string& dir_path, struct stat dirstat; std::string full_path = cur_dir + dirent->d_name; PERFETTO_CHECK(stat(full_path.c_str(), &dirstat) == 0); + // MSan's stat() interceptor on glibc 2.35+ does not mark the output + // buffer as initialized (the syscall goes through statx). + PERFETTO_MSAN_UNPOISON(&dirstat, sizeof(dirstat)); if (S_ISDIR(dirstat.st_mode)) { dir_queue.push_back(full_path + '/'); } else if (S_ISREG(dirstat.st_mode)) { @@ -3596,275 +4252,6 @@ LinuxFileWatch::~LinuxFileWatch() = default; #endif // OS_LINUX || OS_ANDROID -} // namespace base -} // namespace perfetto -// gen_amalgamated begin source: src/base/fixed_string_writer.cc -// gen_amalgamated begin header: include/perfetto/ext/base/fixed_string_writer.h -/* - * Copyright (C) 2019 The Android Open Source Project - * - * 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 INCLUDE_PERFETTO_EXT_BASE_FIXED_STRING_WRITER_H_ -#define INCLUDE_PERFETTO_EXT_BASE_FIXED_STRING_WRITER_H_ - -#include - -#include -#include -#include -#include -#include -#include - -// gen_amalgamated expanded: #include "perfetto/base/logging.h" -// gen_amalgamated expanded: #include "perfetto/ext/base/string_utils.h" -// gen_amalgamated expanded: #include "perfetto/ext/base/string_view.h" - -namespace perfetto { -namespace base { - -// A helper class which writes formatted data to a string buffer. -// This is used in the trace processor where we write O(GBs) of strings and -// sprintf is too slow. -class FixedStringWriter { - public: - // Creates a string buffer from a char buffer and length. - FixedStringWriter(char* buffer, size_t size) : buffer_(buffer), size_(size) {} - - // Appends n instances of a char to the buffer. - void AppendChar(char in, size_t n = 1) { - PERFETTO_DCHECK(pos_ + n <= size_); - memset(&buffer_[pos_], in, n); - pos_ += n; - } - - // Appends a length delimited string to the buffer. - void AppendString(const char* in, size_t n) { - PERFETTO_DCHECK(pos_ + n <= size_); - memcpy(&buffer_[pos_], in, n); - pos_ += n; - } - - void AppendStringView(StringView sv) { AppendString(sv.data(), sv.size()); } - - // Appends a null-terminated string literal to the buffer. - template - inline void AppendLiteral(const char (&in)[N]) { - AppendString(in, N - 1); - } - - // Appends a StringView to the buffer. - void AppendString(StringView data) { AppendString(data.data(), data.size()); } - - // Appends an integer to the buffer. - void AppendInt(int64_t value) { AppendPaddedInt<'0', 0>(value); } - - // Appends an integer to the buffer, padding with |padchar| if the number of - // digits of the integer is less than |padding|. - template - void AppendPaddedInt(int64_t sign_value) { - const bool negate = std::signbit(static_cast(sign_value)); - uint64_t absolute_value; - if (sign_value == std::numeric_limits::min()) { - absolute_value = - static_cast(std::numeric_limits::max()) + 1; - } else { - absolute_value = static_cast(std::abs(sign_value)); - } - AppendPaddedInt(absolute_value, negate); - } - - void AppendUnsignedInt(uint64_t value) { - AppendPaddedUnsignedInt<'0', 0>(value); - } - - // Appends an unsigned integer to the buffer, padding with |padchar| if the - // number of digits of the integer is less than |padding|. - template - void AppendPaddedUnsignedInt(uint64_t value) { - AppendPaddedInt(value, false); - } - - template - void AppendPaddedHexInt(IntType value, char padchar, uint64_t padding) { - using UnsignedType = std::make_unsigned_t; - constexpr size_t kMaxHexDigits = sizeof(IntType) * 2; - // 32 bytes is more than enough for any integer type (max 16 hex digits for - // 64-bit) - constexpr size_t kBufferSize = 32; - auto size_needed = - kMaxHexDigits > padding ? kMaxHexDigits : static_cast(padding); - PERFETTO_DCHECK(size_needed <= kBufferSize); - PERFETTO_DCHECK(pos_ + size_needed <= size_); - - std::array data; - constexpr char hex_asc[] = "0123456789abcdef"; - - size_t idx = size_needed - 1; - auto uvalue = static_cast(value); - do { - data[idx--] = hex_asc[uvalue & 0xF]; - uvalue >>= 4; - } while (uvalue != 0); - - if (padding > 0) { - const auto num_digits = static_cast(size_needed - 1 - idx); - // std::max() needed to work around GCC not being able to tell that - // padding > 0. - for (auto i = num_digits; i < std::max(uint64_t{1u}, padding); i++) { - data[idx--] = padchar; - } - } - AppendString(&data[idx + 1], size_needed - idx - 1); - } - - // Appends a hex integer to the buffer. - template - void AppendHexInt(IntType value) { - AppendPaddedHexInt(value, '0', 0); - } - - // Appends a hex string to the buffer. - void AppendHexString(const uint8_t* data, size_t size, char separator); - - void AppendHexString(StringView data, char separator) { - AppendHexString(reinterpret_cast(data.data()), data.size(), - separator); - } - - // Appends a double to the buffer. - void AppendDouble(double value) { - // TODO(lalitm): trying to optimize this is premature given we almost never - // print doubles. Reevaluate this in the future if we do print them more. - size_t res = base::SprintfTrunc(buffer_ + pos_, size_ - pos_, "%lf", value); - PERFETTO_DCHECK(pos_ + res <= size_); - pos_ += res; - } - - void AppendBool(bool value) { - if (value) { - AppendLiteral("true"); - return; - } - AppendLiteral("false"); - } - - StringView GetStringView() { - PERFETTO_DCHECK(pos_ <= size_); - return StringView(buffer_, pos_); - } - - char* CreateStringCopy() { - char* dup = reinterpret_cast(malloc(pos_ + 1)); - if (dup) { - memcpy(dup, buffer_, pos_); - dup[pos_] = '\0'; - } - return dup; - } - - size_t pos() const { return pos_; } - size_t size() const { return size_; } - void reset() { pos_ = 0; } - - private: - template - void AppendPaddedInt(uint64_t absolute_value, bool negate) { - // Need to add 2 to the number of digits to account for minus sign and - // rounding down of digits10. - constexpr auto kMaxDigits = std::numeric_limits::digits10 + 2; - constexpr auto kSizeNeeded = kMaxDigits > padding ? kMaxDigits : padding; - PERFETTO_DCHECK(pos_ + kSizeNeeded <= size_); - - char data[kSizeNeeded]; - - size_t idx; - for (idx = kSizeNeeded - 1; absolute_value >= 10;) { - char digit = absolute_value % 10; - absolute_value /= 10; - data[idx--] = digit + '0'; - } - data[idx--] = static_cast(absolute_value) + '0'; - - if (padding > 0) { - size_t num_digits = kSizeNeeded - 1 - idx; - // std::max() needed to work around GCC not being able to tell that - // padding > 0. - for (size_t i = num_digits; i < std::max(uint64_t{1u}, padding); i++) { - data[idx--] = padchar; - } - } - - if (negate) - buffer_[pos_++] = '-'; - AppendString(&data[idx + 1], kSizeNeeded - idx - 1); - } - - char* buffer_ = nullptr; - size_t size_ = 0; - size_t pos_ = 0; -}; - -} // namespace base -} // namespace perfetto - -#endif // INCLUDE_PERFETTO_EXT_BASE_FIXED_STRING_WRITER_H_ -/* - * Copyright (C) 2026 The Android Open Source Project - * - * 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. - */ - -// gen_amalgamated expanded: #include "perfetto/ext/base/fixed_string_writer.h" - -#include -#include -#include - -namespace perfetto { -namespace base { - -void FixedStringWriter::AppendHexString(const uint8_t* data, - size_t size, - char separator) { - // Truncate to 64 bytes, as this is the maximum supported by the Linux - // kernel's vsnprintf implementation. - size_t printed_size = std::min(size, size_t{64}); - // Remove trailing separator from calculation if printed_size > 0. - size_t max_chars = printed_size * 3 - (printed_size > 0 ? 1 : 0); - PERFETTO_DCHECK(pos_ + max_chars <= size_); - - if (printed_size) { - AppendPaddedHexInt(data[0], '0', 2); - } - for (size_t pos = 1; pos < printed_size; pos++) { - AppendChar(separator); - AppendPaddedHexInt(data[pos], '0', 2); - } -} - } // namespace base } // namespace perfetto // gen_amalgamated begin source: src/base/getopt_compat.cc @@ -3997,6 +4384,36 @@ const option* LookupShortOpt(const std::vector