Merge "Release canary to pick up a fix for b/227763167"
diff --git a/Android.bp b/Android.bp
index 5652bdb..b0d0881 100644
--- a/Android.bp
+++ b/Android.bp
@@ -23,7 +23,6 @@
         ":perfetto_include_perfetto_ext_ipc_ipc",
         ":perfetto_include_perfetto_ext_tracing_core_core",
         ":perfetto_include_perfetto_ext_tracing_ipc_ipc",
-        ":perfetto_include_perfetto_profiling_normalize",
         ":perfetto_include_perfetto_protozero_protozero",
         ":perfetto_include_perfetto_tracing_core_core",
         ":perfetto_include_perfetto_tracing_core_forward_decls",
@@ -79,6 +78,7 @@
         ":perfetto_src_profiling_common_callstack_trie",
         ":perfetto_src_profiling_common_interner",
         ":perfetto_src_profiling_common_interning_output",
+        ":perfetto_src_profiling_common_proc_cmdline",
         ":perfetto_src_profiling_common_proc_utils",
         ":perfetto_src_profiling_common_producer_support",
         ":perfetto_src_profiling_common_profiler_guardrails",
@@ -277,7 +277,6 @@
         ":perfetto_include_perfetto_ext_ipc_ipc",
         ":perfetto_include_perfetto_ext_tracing_core_core",
         ":perfetto_include_perfetto_ext_tracing_ipc_ipc",
-        ":perfetto_include_perfetto_profiling_normalize",
         ":perfetto_include_perfetto_protozero_protozero",
         ":perfetto_include_perfetto_tracing_core_core",
         ":perfetto_include_perfetto_tracing_core_forward_decls",
@@ -333,6 +332,7 @@
         ":perfetto_src_profiling_common_callstack_trie",
         ":perfetto_src_profiling_common_interner",
         ":perfetto_src_profiling_common_interning_output",
+        ":perfetto_src_profiling_common_proc_cmdline",
         ":perfetto_src_profiling_common_proc_utils",
         ":perfetto_src_profiling_common_producer_support",
         ":perfetto_src_profiling_common_profiler_guardrails",
@@ -1621,11 +1621,6 @@
     name: "perfetto_include_perfetto_ext_tracing_ipc_ipc",
 }
 
-// GN: //include/perfetto/profiling:normalize
-filegroup {
-    name: "perfetto_include_perfetto_profiling_normalize",
-}
-
 // GN: //include/perfetto/profiling:pprof_builder
 filegroup {
     name: "perfetto_include_perfetto_profiling_pprof_builder",
@@ -1689,7 +1684,6 @@
         ":perfetto_include_perfetto_ext_traced_traced",
         ":perfetto_include_perfetto_ext_tracing_core_core",
         ":perfetto_include_perfetto_ext_tracing_ipc_ipc",
-        ":perfetto_include_perfetto_profiling_normalize",
         ":perfetto_include_perfetto_protozero_protozero",
         ":perfetto_include_perfetto_trace_processor_basic_types",
         ":perfetto_include_perfetto_trace_processor_storage",
@@ -1773,6 +1767,7 @@
         ":perfetto_src_profiling_common_callstack_trie",
         ":perfetto_src_profiling_common_interner",
         ":perfetto_src_profiling_common_interning_output",
+        ":perfetto_src_profiling_common_proc_cmdline",
         ":perfetto_src_profiling_common_proc_utils",
         ":perfetto_src_profiling_common_producer_support",
         ":perfetto_src_profiling_common_profiler_guardrails",
@@ -7522,6 +7517,14 @@
     ],
 }
 
+// GN: //src/profiling/common:proc_cmdline
+filegroup {
+    name: "perfetto_src_profiling_common_proc_cmdline",
+    srcs: [
+        "src/profiling/common/proc_cmdline.cc",
+    ],
+}
+
 // GN: //src/profiling/common:proc_utils
 filegroup {
     name: "perfetto_src_profiling_common_proc_utils",
@@ -7551,6 +7554,7 @@
     name: "perfetto_src_profiling_common_unittests",
     srcs: [
         "src/profiling/common/interner_unittest.cc",
+        "src/profiling/common/proc_cmdline_unittest.cc",
         "src/profiling/common/proc_utils_unittest.cc",
         "src/profiling/common/producer_support_unittest.cc",
         "src/profiling/common/profiler_guardrails_unittest.cc",
@@ -9727,7 +9731,6 @@
         ":perfetto_include_perfetto_ext_traced_traced",
         ":perfetto_include_perfetto_ext_tracing_core_core",
         ":perfetto_include_perfetto_ext_tracing_ipc_ipc",
-        ":perfetto_include_perfetto_profiling_normalize",
         ":perfetto_include_perfetto_protozero_protozero",
         ":perfetto_include_perfetto_test_test_support",
         ":perfetto_include_perfetto_trace_processor_basic_types",
@@ -9850,6 +9853,7 @@
         ":perfetto_src_profiling_common_callstack_trie",
         ":perfetto_src_profiling_common_interner",
         ":perfetto_src_profiling_common_interning_output",
+        ":perfetto_src_profiling_common_proc_cmdline",
         ":perfetto_src_profiling_common_proc_utils",
         ":perfetto_src_profiling_common_producer_support",
         ":perfetto_src_profiling_common_profiler_guardrails",
@@ -10466,7 +10470,6 @@
         ":perfetto_include_perfetto_ext_ipc_ipc",
         ":perfetto_include_perfetto_ext_tracing_core_core",
         ":perfetto_include_perfetto_ext_tracing_ipc_ipc",
-        ":perfetto_include_perfetto_profiling_normalize",
         ":perfetto_include_perfetto_protozero_protozero",
         ":perfetto_include_perfetto_tracing_core_core",
         ":perfetto_include_perfetto_tracing_core_forward_decls",
@@ -10527,6 +10530,7 @@
         ":perfetto_src_profiling_common_callstack_trie",
         ":perfetto_src_profiling_common_interner",
         ":perfetto_src_profiling_common_interning_output",
+        ":perfetto_src_profiling_common_proc_cmdline",
         ":perfetto_src_profiling_common_proc_utils",
         ":perfetto_src_profiling_common_producer_support",
         ":perfetto_src_profiling_common_profiler_guardrails",
diff --git a/include/perfetto/profiling/BUILD.gn b/include/perfetto/profiling/BUILD.gn
index e7f9ddf..1fd2092 100644
--- a/include/perfetto/profiling/BUILD.gn
+++ b/include/perfetto/profiling/BUILD.gn
@@ -15,7 +15,3 @@
 source_set("pprof_builder") {
   sources = [ "pprof_builder.h" ]
 }
-
-source_set("normalize") {
-  sources = [ "normalize.h" ]
-}
diff --git a/include/perfetto/profiling/normalize.h b/include/perfetto/profiling/normalize.h
deleted file mode 100644
index 7b7da1d..0000000
--- a/include/perfetto/profiling/normalize.h
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * 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_PROFILING_NORMALIZE_H_
-#define INCLUDE_PERFETTO_PROFILING_NORMALIZE_H_
-
-// Header only code that gets used in other projects.
-// This is currently used in
-// * ART
-// * Bionic
-// * Heapprofd
-//
-// DO NOT USE THE STL HERE. This gets used in parts of Bionic that do not
-// use the STL.
-
-#include <string.h>
-
-namespace perfetto {
-namespace profiling {
-
-// Normalize cmdline in place. Stores new beginning of string in *cmdline_ptr.
-// Returns new size of string (from new beginning).
-// Modifies string in *cmdline_ptr.
-static ssize_t NormalizeCmdLine(char** cmdline_ptr, size_t size) {
-  char* cmdline = *cmdline_ptr;
-  char* first_arg = static_cast<char*>(memchr(cmdline, '\0', size));
-  if (first_arg == nullptr) {
-    errno = EOVERFLOW;
-    return -1;
-  }
-  // For consistency with what we do with Java app cmdlines, trim everything
-  // after the @ sign of the first arg.
-  char* first_at = static_cast<char*>(memchr(cmdline, '@', size));
-  if (first_at != nullptr && first_at < first_arg) {
-    *first_at = '\0';
-    first_arg = first_at;
-  }
-  char* start = static_cast<char*>(
-      memrchr(cmdline, '/', static_cast<size_t>(first_arg - cmdline)));
-  if (start == nullptr) {
-    start = cmdline;
-  } else {
-    // Skip the /.
-    start++;
-  }
-  *cmdline_ptr = start;
-  return first_arg - start;
-}
-
-}  // namespace profiling
-}  // namespace perfetto
-
-#endif  // INCLUDE_PERFETTO_PROFILING_NORMALIZE_H_
diff --git a/protos/perfetto/config/perfetto_config.proto b/protos/perfetto/config/perfetto_config.proto
index 0e0a58d..b3fb91f 100644
--- a/protos/perfetto/config/perfetto_config.proto
+++ b/protos/perfetto/config/perfetto_config.proto
@@ -858,7 +858,7 @@
 
 // Begin of protos/perfetto/config/profiling/java_hprof_config.proto
 
-// Configuration for go/heapprofd.
+// Configuration for managed app heap graph snapshots.
 message JavaHprofConfig {
   // If dump_interval_ms != 0, the following configuration is used.
   message ContinuousDumpConfig {
@@ -876,12 +876,27 @@
     optional bool scan_pids_only_on_start = 3;
   }
 
-  // This input is normalized in the following way: if it contains slashes,
-  // everything up to the last slash is discarded. If it contains "@",
-  // everything after the first @ is discared.
-  // E.g. /system/bin/surfaceflinger@1.0 normalizes to surfaceflinger.
-  // This transformation is also applied to the processes' command lines when
-  // matching.
+  // Command line allowlist, matched against the /proc/<pid>/cmdline (not the
+  // comm string). The semantics of this field were changed since its original
+  // introduction.
+  //
+  // On Android T+ (13+), this field can specify a single wildcard (*), and
+  // the profiler will attempt to match it in two possible ways:
+  // * if the pattern starts with a '/', then it is matched against the first
+  //   segment of the cmdline (i.e. argv0). For example "/bin/e*" would match
+  //   "/bin/echo".
+  // * otherwise the pattern is matched against the part of argv0
+  //   corresponding to the binary name (this is unrelated to /proc/pid/exe).
+  //   For example "echo" would match "/bin/echo".
+  //
+  // On Android S (12) and below, both this pattern and /proc/pid/cmdline get
+  // normalized prior to an exact string comparison. Normalization is as
+  // follows: (1) trim everything beyond the first null or "@" byte; (2) if
+  // the string contains forward slashes, trim everything up to and including
+  // the last one.
+  //
+  // Implementation note: in either case, at most 511 characters of cmdline
+  // are considered.
   repeated string process_cmdline = 1;
 
   // For watermark based triggering or local debugging.
@@ -1184,18 +1199,34 @@
     // process.
     repeated int32 target_pid = 1;
 
-    // Command line allowlist, matched against the
-    // /proc/<pid>/cmdline (not the comm string), with both sides being
-    // "normalized". Normalization is as follows: (1) trim everything beyond the
-    // first null or "@" byte; (2) if the string contains forward slashes, trim
-    // everything up to and including the last one.
+    // Command line allowlist, matched against the /proc/<pid>/cmdline (not the
+    // comm string). The semantics of this field were changed since its original
+    // introduction.
+    //
+    // On Android T+ (13+), this field can specify a single wildcard (*), and
+    // the profiler will attempt to match it in two possible ways:
+    // * if the pattern starts with a '/', then it is matched against the first
+    //   segment of the cmdline (i.e. argv0). For example "/bin/e*" would match
+    //   "/bin/echo".
+    // * otherwise the pattern is matched against the part of argv0
+    //   corresponding to the binary name (this is unrelated to /proc/pid/exe).
+    //   For example "echo" would match "/bin/echo".
+    //
+    // On Android S (12) and below, both this pattern and /proc/pid/cmdline get
+    // normalized prior to an exact string comparison. Normalization is as
+    // follows: (1) trim everything beyond the first null or "@" byte; (2) if
+    // the string contains forward slashes, trim everything up to and including
+    // the last one.
+    //
+    // Implementation note: in either case, at most 511 characters of cmdline
+    // are considered.
     repeated string target_cmdline = 2;
 
     // List of excluded pids.
     repeated int32 exclude_pid = 3;
 
-    // List of excluded cmdlines. Normalized in the same way as
-    // |target_cmdline|.
+    // List of excluded cmdlines. See description of |target_cmdline| for how
+    // this is handled.
     repeated string exclude_cmdline = 4;
 
     // Number of additional command lines to sample. Only those which are
diff --git a/protos/perfetto/config/profiling/java_hprof_config.proto b/protos/perfetto/config/profiling/java_hprof_config.proto
index 9245ee6..c161396 100644
--- a/protos/perfetto/config/profiling/java_hprof_config.proto
+++ b/protos/perfetto/config/profiling/java_hprof_config.proto
@@ -18,7 +18,7 @@
 
 package perfetto.protos;
 
-// Configuration for go/heapprofd.
+// Configuration for managed app heap graph snapshots.
 message JavaHprofConfig {
   // If dump_interval_ms != 0, the following configuration is used.
   message ContinuousDumpConfig {
@@ -36,12 +36,27 @@
     optional bool scan_pids_only_on_start = 3;
   }
 
-  // This input is normalized in the following way: if it contains slashes,
-  // everything up to the last slash is discarded. If it contains "@",
-  // everything after the first @ is discared.
-  // E.g. /system/bin/surfaceflinger@1.0 normalizes to surfaceflinger.
-  // This transformation is also applied to the processes' command lines when
-  // matching.
+  // Command line allowlist, matched against the /proc/<pid>/cmdline (not the
+  // comm string). The semantics of this field were changed since its original
+  // introduction.
+  //
+  // On Android T+ (13+), this field can specify a single wildcard (*), and
+  // the profiler will attempt to match it in two possible ways:
+  // * if the pattern starts with a '/', then it is matched against the first
+  //   segment of the cmdline (i.e. argv0). For example "/bin/e*" would match
+  //   "/bin/echo".
+  // * otherwise the pattern is matched against the part of argv0
+  //   corresponding to the binary name (this is unrelated to /proc/pid/exe).
+  //   For example "echo" would match "/bin/echo".
+  //
+  // On Android S (12) and below, both this pattern and /proc/pid/cmdline get
+  // normalized prior to an exact string comparison. Normalization is as
+  // follows: (1) trim everything beyond the first null or "@" byte; (2) if
+  // the string contains forward slashes, trim everything up to and including
+  // the last one.
+  //
+  // Implementation note: in either case, at most 511 characters of cmdline
+  // are considered.
   repeated string process_cmdline = 1;
 
   // For watermark based triggering or local debugging.
diff --git a/protos/perfetto/config/profiling/perf_event_config.proto b/protos/perfetto/config/profiling/perf_event_config.proto
index a936b5b..a440464 100644
--- a/protos/perfetto/config/profiling/perf_event_config.proto
+++ b/protos/perfetto/config/profiling/perf_event_config.proto
@@ -148,18 +148,34 @@
     // process.
     repeated int32 target_pid = 1;
 
-    // Command line allowlist, matched against the
-    // /proc/<pid>/cmdline (not the comm string), with both sides being
-    // "normalized". Normalization is as follows: (1) trim everything beyond the
-    // first null or "@" byte; (2) if the string contains forward slashes, trim
-    // everything up to and including the last one.
+    // Command line allowlist, matched against the /proc/<pid>/cmdline (not the
+    // comm string). The semantics of this field were changed since its original
+    // introduction.
+    //
+    // On Android T+ (13+), this field can specify a single wildcard (*), and
+    // the profiler will attempt to match it in two possible ways:
+    // * if the pattern starts with a '/', then it is matched against the first
+    //   segment of the cmdline (i.e. argv0). For example "/bin/e*" would match
+    //   "/bin/echo".
+    // * otherwise the pattern is matched against the part of argv0
+    //   corresponding to the binary name (this is unrelated to /proc/pid/exe).
+    //   For example "echo" would match "/bin/echo".
+    //
+    // On Android S (12) and below, both this pattern and /proc/pid/cmdline get
+    // normalized prior to an exact string comparison. Normalization is as
+    // follows: (1) trim everything beyond the first null or "@" byte; (2) if
+    // the string contains forward slashes, trim everything up to and including
+    // the last one.
+    //
+    // Implementation note: in either case, at most 511 characters of cmdline
+    // are considered.
     repeated string target_cmdline = 2;
 
     // List of excluded pids.
     repeated int32 exclude_pid = 3;
 
-    // List of excluded cmdlines. Normalized in the same way as
-    // |target_cmdline|.
+    // List of excluded cmdlines. See description of |target_cmdline| for how
+    // this is handled.
     repeated string exclude_cmdline = 4;
 
     // Number of additional command lines to sample. Only those which are
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index 8dafad0..d683c97 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -858,7 +858,7 @@
 
 // Begin of protos/perfetto/config/profiling/java_hprof_config.proto
 
-// Configuration for go/heapprofd.
+// Configuration for managed app heap graph snapshots.
 message JavaHprofConfig {
   // If dump_interval_ms != 0, the following configuration is used.
   message ContinuousDumpConfig {
@@ -876,12 +876,27 @@
     optional bool scan_pids_only_on_start = 3;
   }
 
-  // This input is normalized in the following way: if it contains slashes,
-  // everything up to the last slash is discarded. If it contains "@",
-  // everything after the first @ is discared.
-  // E.g. /system/bin/surfaceflinger@1.0 normalizes to surfaceflinger.
-  // This transformation is also applied to the processes' command lines when
-  // matching.
+  // Command line allowlist, matched against the /proc/<pid>/cmdline (not the
+  // comm string). The semantics of this field were changed since its original
+  // introduction.
+  //
+  // On Android T+ (13+), this field can specify a single wildcard (*), and
+  // the profiler will attempt to match it in two possible ways:
+  // * if the pattern starts with a '/', then it is matched against the first
+  //   segment of the cmdline (i.e. argv0). For example "/bin/e*" would match
+  //   "/bin/echo".
+  // * otherwise the pattern is matched against the part of argv0
+  //   corresponding to the binary name (this is unrelated to /proc/pid/exe).
+  //   For example "echo" would match "/bin/echo".
+  //
+  // On Android S (12) and below, both this pattern and /proc/pid/cmdline get
+  // normalized prior to an exact string comparison. Normalization is as
+  // follows: (1) trim everything beyond the first null or "@" byte; (2) if
+  // the string contains forward slashes, trim everything up to and including
+  // the last one.
+  //
+  // Implementation note: in either case, at most 511 characters of cmdline
+  // are considered.
   repeated string process_cmdline = 1;
 
   // For watermark based triggering or local debugging.
@@ -1184,18 +1199,34 @@
     // process.
     repeated int32 target_pid = 1;
 
-    // Command line allowlist, matched against the
-    // /proc/<pid>/cmdline (not the comm string), with both sides being
-    // "normalized". Normalization is as follows: (1) trim everything beyond the
-    // first null or "@" byte; (2) if the string contains forward slashes, trim
-    // everything up to and including the last one.
+    // Command line allowlist, matched against the /proc/<pid>/cmdline (not the
+    // comm string). The semantics of this field were changed since its original
+    // introduction.
+    //
+    // On Android T+ (13+), this field can specify a single wildcard (*), and
+    // the profiler will attempt to match it in two possible ways:
+    // * if the pattern starts with a '/', then it is matched against the first
+    //   segment of the cmdline (i.e. argv0). For example "/bin/e*" would match
+    //   "/bin/echo".
+    // * otherwise the pattern is matched against the part of argv0
+    //   corresponding to the binary name (this is unrelated to /proc/pid/exe).
+    //   For example "echo" would match "/bin/echo".
+    //
+    // On Android S (12) and below, both this pattern and /proc/pid/cmdline get
+    // normalized prior to an exact string comparison. Normalization is as
+    // follows: (1) trim everything beyond the first null or "@" byte; (2) if
+    // the string contains forward slashes, trim everything up to and including
+    // the last one.
+    //
+    // Implementation note: in either case, at most 511 characters of cmdline
+    // are considered.
     repeated string target_cmdline = 2;
 
     // List of excluded pids.
     repeated int32 exclude_pid = 3;
 
-    // List of excluded cmdlines. Normalized in the same way as
-    // |target_cmdline|.
+    // List of excluded cmdlines. See description of |target_cmdline| for how
+    // this is handled.
     repeated string exclude_cmdline = 4;
 
     // Number of additional command lines to sample. Only those which are
diff --git a/python/perfetto/experimental/slice_breakdown/breakdown.py b/python/perfetto/experimental/slice_breakdown/breakdown.py
index 85b21d2..9eb99de 100644
--- a/python/perfetto/experimental/slice_breakdown/breakdown.py
+++ b/python/perfetto/experimental/slice_breakdown/breakdown.py
@@ -13,8 +13,14 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from perfetto.trace_processor.api import TraceProcessor
+from perfetto.trace_processor.api import TraceProcessorException
 
-def compute_breakdown(tp, start_ts=None, end_ts=None, process_name=None):
+
+def compute_breakdown(tp: TraceProcessor,
+                      start_ts=None,
+                      end_ts=None,
+                      process_name=None):
   """For each userspace slice in the trace processor instance |tp|, computes
   the self-time of that slice grouping by process name, thread name
   and thread state.
@@ -149,7 +155,9 @@
   return breakdown
 
 
-def compute_breakdown_for_startup(tp, package_name=None, process_name=None):
+def compute_breakdown_for_startup(tp: TraceProcessor,
+                                  package_name=None,
+                                  process_name=None):
   """Computes the slice breakdown (like |compute_breakdown|) but only
   considering slices which happened during an app startup
 
@@ -176,9 +184,9 @@
     {}
   '''.format(filter)).as_pandas_dataframe()
   if len(launches) == 0:
-    raise Exception("Didn't find startup in trace")
+    raise TraceProcessorException("Didn't find startup in trace")
   if len(launches) > 1:
-    raise Exception("Found multiple startups in trace")
+    raise TraceProcessorException("Found multiple startups in trace")
 
   start = launches['ts'][0]
   end = launches['ts_end'][0]
diff --git a/src/profiling/common/BUILD.gn b/src/profiling/common/BUILD.gn
index 583edc6..1fcc6fb 100644
--- a/src/profiling/common/BUILD.gn
+++ b/src/profiling/common/BUILD.gn
@@ -66,8 +66,8 @@
 
 source_set("proc_utils") {
   deps = [
+    ":proc_cmdline",
     "../../../gn:default_deps",
-    "../../../include/perfetto/profiling:normalize",
     "../../base",
   ]
   sources = [
@@ -76,6 +76,17 @@
   ]
 }
 
+source_set("proc_cmdline") {
+  deps = [
+    "../../../gn:default_deps",
+    "../../base",
+  ]
+  sources = [
+    "proc_cmdline.cc",
+    "proc_cmdline.h",
+  ]
+}
+
 source_set("producer_support") {
   deps = [
     "../../../gn:default_deps",
@@ -106,18 +117,19 @@
   testonly = true
   deps = [
     ":interner",
+    ":proc_cmdline",
     ":proc_utils",
     ":producer_support",
     ":profiler_guardrails",
     "../../../gn:default_deps",
     "../../../gn:gtest_and_gmock",
-    "../../../include/perfetto/profiling:normalize",
     "../../base",
     "../../base:test_support",
     "../../tracing/core",
   ]
   sources = [
     "interner_unittest.cc",
+    "proc_cmdline_unittest.cc",
     "proc_utils_unittest.cc",
     "producer_support_unittest.cc",
     "profiler_guardrails_unittest.cc",
diff --git a/src/profiling/common/proc_cmdline.cc b/src/profiling/common/proc_cmdline.cc
new file mode 100644
index 0000000..a6f7d93
--- /dev/null
+++ b/src/profiling/common/proc_cmdline.cc
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include "src/profiling/common/proc_cmdline.h"
+
+#include <fcntl.h>
+#include <fnmatch.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "perfetto/ext/base/file_utils.h"
+
+namespace perfetto {
+namespace profiling {
+namespace glob_aware {
+
+// Edge cases: the raw cmdline as read out of the kernel can have several
+// shapes, the process can rewrite the contents to be arbitrary, and overly long
+// cmdlines can be truncated as we use a 511 byte limit. Some examples to
+// consider for the implementation:
+// * "echo\0hello\0"
+// * "/bin/top\0\0\0\0\0\0\0"
+// * "arbitrary string as rewritten by the process\0"
+// * "some_bugged_kernels_forget_final_nul_terminator"
+//
+// The approach when performing the read->derive->match is to minimize early
+// return codepaths for the caller. So even if we read a non-conforming cmdline
+// (e.g. just a single nul byte), it can still be fed through FindBinaryName and
+// MatchGlobPattern. It'll just make the intermediate strings be empty (so
+// starting with a nul byte, but never nullptr).
+//
+// NB: bionic/libc/bionic/malloc_heapprofd will require a parallel
+// implementation of these functions (to avoid a bionic->perfetto dependency).
+// Keep them as STL-free as possible to allow for both implementations to be
+// close to verbatim copies.
+
+// TODO(rsavitski): consider changing to Optional<> return type.
+bool ReadProcCmdlineForPID(pid_t pid, std::string* cmdline_out) {
+  std::string filename = "/proc/" + std::to_string(pid) + "/cmdline";
+  base::ScopedFile fd(base::OpenFile(filename, O_RDONLY));
+  if (!fd) {
+    PERFETTO_DPLOG("Failed to open %s", filename.c_str());
+    return false;
+  }
+
+  // buf is 511 bytes to match an implementation that adds a null terminator to
+  // the back of a 512 byte buffer.
+  char buf[511];
+  ssize_t rd = PERFETTO_EINTR(read(*fd, buf, sizeof(buf)));
+  if (rd < 0) {
+    PERFETTO_DPLOG("Failed to read %s", filename.c_str());
+    return false;
+  }
+
+  cmdline_out->assign(buf, static_cast<size_t>(rd));
+  return true;
+}
+
+// Returns a pointer into |cmdline| corresponding to the argv0 without any
+// leading directories if the binary path is absolute. |cmdline_len| corresponds
+// to the length of the cmdline string as read out of procfs as a C string -
+// length doesn't include the final nul terminator, but it must be present at
+// cmdline[cmdline_len]. Note that normally the string itself will contain nul
+// bytes, as that's what the kernel uses to separate arguments.
+//
+// Function output examples:
+// * /system/bin/adb\0--flag -> adb
+// * adb -> adb
+// * com.example.app -> com.example.app
+const char* FindBinaryName(const char* cmdline, size_t cmdline_len) {
+  // Find the first nul byte that signifies the end of argv0. We might not find
+  // one if the process rewrote its cmdline without nul separators, and/or the
+  // cmdline didn't fully fit into our read buffer. In such cases, proceed with
+  // the full string to do best-effort matching.
+  const char* argv0_end =
+      static_cast<const char*>(memchr(cmdline, '\0', cmdline_len));
+  if (argv0_end == nullptr) {
+    argv0_end = cmdline + cmdline_len;  // set to final nul terminator
+  }
+  // Find the last path separator of argv0, if it exists.
+  const char* name_start = static_cast<const char*>(
+      memrchr(cmdline, '/', static_cast<size_t>(argv0_end - cmdline)));
+  if (name_start == nullptr) {
+    name_start = cmdline;
+  } else {
+    name_start++;  // skip the separator
+  }
+  return name_start;
+}
+
+// All inputs must be non-nullptr, but can start with a nul byte.
+bool MatchGlobPattern(const char* pattern,
+                      const char* cmdline,
+                      const char* binname) {
+  if (pattern[0] == '/') {
+    return fnmatch(pattern, cmdline, FNM_NOESCAPE) == 0;
+  }
+  return fnmatch(pattern, binname, FNM_NOESCAPE) == 0;
+}
+
+}  // namespace glob_aware
+}  // namespace profiling
+}  // namespace perfetto
diff --git a/src/profiling/common/proc_cmdline.h b/src/profiling/common/proc_cmdline.h
new file mode 100644
index 0000000..1d01f93
--- /dev/null
+++ b/src/profiling/common/proc_cmdline.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 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 SRC_PROFILING_COMMON_PROC_CMDLINE_H_
+#define SRC_PROFILING_COMMON_PROC_CMDLINE_H_
+
+#include <sys/types.h>
+
+#include <string>
+
+namespace perfetto {
+namespace profiling {
+
+// TODO(rsavitski): these functions are a reimplementation of those found in
+// proc_utils, but with a change in semantics that we intend for all profilers.
+// Eventually this should become the single canonical file dealing with proc
+// cmdlines. The transition will start with traced_perf and perfetto_hprof, and
+// heapprofd will follow later.
+namespace glob_aware {
+
+// These functions let the profilers read a /proc/pid/cmdline, find the
+// substrings corresponding to the argv0 as well as the binary name (e.g.
+// "/bin/echo" and "echo" respectively), and then match it against a set of glob
+// patterns.
+//
+// Example usage:
+//   std::string cmdline;
+//   bool success = ReadProcCmdlineForPID(42, &cmdline);
+//   if (!success) return false;
+//   const char* binname = FindBinaryName(cmdline.c_str(), cmdline.size());
+//   return MatchGlobPattern("test*", cmdline.c_str(), binname);
+
+bool ReadProcCmdlineForPID(pid_t pid, std::string* cmdline_out);
+const char* FindBinaryName(const char* cmdline, size_t cmdline_len);
+bool MatchGlobPattern(const char* pattern,
+                      const char* cmdline,
+                      const char* binname);
+
+}  // namespace glob_aware
+}  // namespace profiling
+}  // namespace perfetto
+
+#endif  // SRC_PROFILING_COMMON_PROC_CMDLINE_H_
diff --git a/src/profiling/common/proc_cmdline_unittest.cc b/src/profiling/common/proc_cmdline_unittest.cc
new file mode 100644
index 0000000..dc23594
--- /dev/null
+++ b/src/profiling/common/proc_cmdline_unittest.cc
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include "src/profiling/common/proc_cmdline.h"
+
+#include <string>
+
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace profiling {
+namespace glob_aware {
+namespace {
+
+TEST(ProcCmdlineTest, FindBinaryNameBinNameOnly) {
+  char cmdline[] = "surfaceflinger";
+  EXPECT_EQ(cmdline, FindBinaryName(cmdline, sizeof(cmdline) - 1));
+}
+
+TEST(ProcCmdlineTest, FindBinaryNameWithArg) {
+  char cmdline[] = "surfaceflinger\0--flag";
+  EXPECT_EQ(cmdline, FindBinaryName(cmdline, sizeof(cmdline) - 1));
+}
+
+TEST(ProcCmdlineTest, FindBinaryNameFullPathAndArgs) {
+  char cmdline[] = "/system/bin/surfaceflinger\0--flag\0--flag2";
+  EXPECT_STREQ("surfaceflinger", FindBinaryName(cmdline, sizeof(cmdline) - 1));
+}
+
+TEST(ProcCmdlineTest, FindBinaryNameSpecialCharsInName) {
+  {
+    char cmdline[] = "android.hardware.graphics.composer@2.2-service";
+    EXPECT_EQ(cmdline, FindBinaryName(cmdline, sizeof(cmdline) - 1));
+  }
+  {
+    char cmdline[] = "com.google.android.googlequicksearchbox:search";
+    EXPECT_EQ(cmdline, FindBinaryName(cmdline, sizeof(cmdline) - 1));
+  }
+  {
+    // chrome rewrites cmdline with spaces instead of nul bytes, parsing will
+    // therefore treat everything as argv0.
+    char cmdline[] =
+        "/opt/google/chrome/chrome --type=renderer --enable-crashpad";
+    EXPECT_STREQ("chrome --type=renderer --enable-crashpad",
+                 FindBinaryName(cmdline, sizeof(cmdline) - 1));
+  }
+}
+
+TEST(ProcCmdlineTest, FindBinaryNameEdgeCases) {
+  {
+    char cmdline[] = "";
+    EXPECT_STREQ("", FindBinaryName(cmdline, sizeof(cmdline) - 1));
+  }
+  {
+    char cmdline[] = "\0foo";
+    EXPECT_STREQ("", FindBinaryName(cmdline, sizeof(cmdline) - 1));
+  }
+  {
+    char cmdline[] = "/foo/";
+    EXPECT_STREQ("", FindBinaryName(cmdline, sizeof(cmdline) - 1));
+  }
+  {
+    char cmdline[] = "/";
+    EXPECT_STREQ("", FindBinaryName(cmdline, sizeof(cmdline) - 1));
+  }
+  {
+    char cmdline[] = "foo/\0";
+    EXPECT_STREQ("", FindBinaryName(cmdline, sizeof(cmdline) - 1));
+  }
+}
+
+TEST(ProcCmdlineTest, FindAndMatchAbsolutePath) {
+  char cmdline[] = "/system/bin/surfaceflinger\0--flag\0--flag2";
+  const char* binname = FindBinaryName(cmdline, sizeof(cmdline) - 1);
+  ASSERT_TRUE(binname != nullptr);
+
+  EXPECT_TRUE(MatchGlobPattern("/system/bin/surfaceflinger", cmdline, binname));
+  EXPECT_TRUE(MatchGlobPattern("/*/surfaceflinger", cmdline, binname));
+  EXPECT_TRUE(MatchGlobPattern("surfaceflinger", cmdline, binname));
+  EXPECT_TRUE(MatchGlobPattern("???faceflinger", cmdline, binname));
+  EXPECT_TRUE(MatchGlobPattern("*", cmdline, binname));
+
+  EXPECT_FALSE(MatchGlobPattern("/system", cmdline, binname));
+  EXPECT_FALSE(MatchGlobPattern("bin/surfaceflinger", cmdline, binname));
+  EXPECT_FALSE(
+      MatchGlobPattern("?system/bin/surfaceflinger", cmdline, binname));
+  EXPECT_FALSE(MatchGlobPattern("*/surfaceflinger", cmdline, binname));
+}
+
+TEST(ProcCmdlineTest, FindAndMatchRelativePath) {
+  char cmdline[] = "./top";
+  const char* binname = FindBinaryName(cmdline, sizeof(cmdline) - 1);
+  ASSERT_TRUE(binname != nullptr);
+
+  EXPECT_TRUE(MatchGlobPattern("top", cmdline, binname));
+  EXPECT_TRUE(MatchGlobPattern("*", cmdline, binname));
+
+  EXPECT_FALSE(MatchGlobPattern("./top", cmdline, binname));
+}
+
+}  // namespace
+}  // namespace glob_aware
+}  // namespace profiling
+}  // namespace perfetto
diff --git a/src/profiling/common/proc_utils.cc b/src/profiling/common/proc_utils.cc
index 3ec1e55..4188b2a 100644
--- a/src/profiling/common/proc_utils.cc
+++ b/src/profiling/common/proc_utils.cc
@@ -24,7 +24,7 @@
 #include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/base/optional.h"
 #include "perfetto/ext/base/string_utils.h"
-#include "perfetto/profiling/normalize.h"
+#include "src/profiling/common/proc_cmdline.h"
 
 namespace perfetto {
 namespace profiling {
@@ -47,6 +47,105 @@
 }
 }  // namespace
 
+base::Optional<std::string> ReadStatus(pid_t pid) {
+  std::string path = "/proc/" + std::to_string(pid) + "/status";
+  std::string status;
+  bool read_proc = base::ReadFile(path, &status);
+  if (!read_proc) {
+    PERFETTO_ELOG("Failed to read %s", path.c_str());
+    return base::nullopt;
+  }
+  return base::Optional<std::string>(status);
+}
+
+base::Optional<uint32_t> GetRssAnonAndSwap(const std::string& status) {
+  auto anon_rss = ParseProcStatusSize(status, "RssAnon:");
+  auto swap = ParseProcStatusSize(status, "VmSwap:");
+  if (anon_rss.has_value() && swap.has_value()) {
+    return *anon_rss + *swap;
+  }
+  return base::nullopt;
+}
+
+void RemoveUnderAnonThreshold(uint32_t min_size_kb, std::set<pid_t>* pids) {
+  for (auto it = pids->begin(); it != pids->end();) {
+    const pid_t pid = *it;
+
+    base::Optional<std::string> status = ReadStatus(pid);
+    base::Optional<uint32_t> rss_and_swap;
+    if (status)
+      rss_and_swap = GetRssAnonAndSwap(*status);
+
+    if (rss_and_swap && rss_and_swap < min_size_kb) {
+      PERFETTO_LOG("Removing pid %d from profiled set (anon: %d kB < %" PRIu32
+                   ")",
+                   pid, *rss_and_swap, min_size_kb);
+      it = pids->erase(it);
+    } else {
+      ++it;
+    }
+  }
+}
+
+base::Optional<Uids> GetUids(const std::string& status) {
+  auto entry_idx = status.find("Uid:");
+  if (entry_idx == std::string::npos)
+    return base::nullopt;
+
+  Uids uids;
+  const char* str = &status[entry_idx + 4];
+  char* endptr;
+
+  uids.real = strtoull(str, &endptr, 10);
+  if (*endptr != ' ' && *endptr != '\t')
+    return base::nullopt;
+
+  str = endptr;
+  uids.effective = strtoull(str, &endptr, 10);
+  if (*endptr != ' ' && *endptr != '\t')
+    return base::nullopt;
+
+  str = endptr;
+  uids.saved_set = strtoull(str, &endptr, 10);
+  if (*endptr != ' ' && *endptr != '\t')
+    return base::nullopt;
+
+  str = endptr;
+  uids.filesystem = strtoull(str, &endptr, 10);
+  if (*endptr != '\n' && *endptr != '\0')
+    return base::nullopt;
+  return uids;
+}
+
+// Normalize cmdline in place. Stores new beginning of string in *cmdline_ptr.
+// Returns new size of string (from new beginning).
+// Modifies string in *cmdline_ptr.
+ssize_t NormalizeCmdLine(char** cmdline_ptr, size_t size) {
+  char* cmdline = *cmdline_ptr;
+  char* first_arg = static_cast<char*>(memchr(cmdline, '\0', size));
+  if (first_arg == nullptr) {
+    errno = EOVERFLOW;
+    return -1;
+  }
+  // For consistency with what we do with Java app cmdlines, trim everything
+  // after the @ sign of the first arg.
+  char* first_at = static_cast<char*>(memchr(cmdline, '@', size));
+  if (first_at != nullptr && first_at < first_arg) {
+    *first_at = '\0';
+    first_arg = first_at;
+  }
+  char* start = static_cast<char*>(
+      memrchr(cmdline, '/', static_cast<size_t>(first_arg - cmdline)));
+  if (start == nullptr) {
+    start = cmdline;
+  } else {
+    // Skip the /.
+    start++;
+  }
+  *cmdline_ptr = start;
+  return first_arg - start;
+}
+
 base::Optional<std::vector<std::string>> NormalizeCmdlines(
     const std::vector<std::string>& cmdlines) {
   std::vector<std::string> normalized_cmdlines;
@@ -142,75 +241,27 @@
   });
 }
 
-base::Optional<std::string> ReadStatus(pid_t pid) {
-  std::string path = "/proc/" + std::to_string(pid) + "/status";
-  std::string status;
-  bool read_proc = base::ReadFile(path, &status);
-  if (!read_proc) {
-    PERFETTO_ELOG("Failed to read %s", path.c_str());
-    return base::nullopt;
-  }
-  return base::Optional<std::string>(status);
-}
+namespace glob_aware {
+void FindPidsForCmdlinePatterns(const std::vector<std::string>& patterns,
+                                std::set<pid_t>* pids) {
+  ForEachPid([&patterns, pids](pid_t pid) {
+    if (pid == getpid())
+      return;
+    std::string cmdline;
+    if (!glob_aware::ReadProcCmdlineForPID(pid, &cmdline))
+      return;
+    const char* binname =
+        glob_aware::FindBinaryName(cmdline.c_str(), cmdline.size());
 
-base::Optional<uint32_t> GetRssAnonAndSwap(const std::string& status) {
-  auto anon_rss = ParseProcStatusSize(status, "RssAnon:");
-  auto swap = ParseProcStatusSize(status, "VmSwap:");
-  if (anon_rss.has_value() && swap.has_value()) {
-    return *anon_rss + *swap;
-  }
-  return base::nullopt;
-}
-
-void RemoveUnderAnonThreshold(uint32_t min_size_kb, std::set<pid_t>* pids) {
-  for (auto it = pids->begin(); it != pids->end();) {
-    const pid_t pid = *it;
-
-    base::Optional<std::string> status = ReadStatus(pid);
-    base::Optional<uint32_t> rss_and_swap;
-    if (status)
-      rss_and_swap = GetRssAnonAndSwap(*status);
-
-    if (rss_and_swap && rss_and_swap < min_size_kb) {
-      PERFETTO_LOG("Removing pid %d from profiled set (anon: %d kB < %" PRIu32
-                   ")",
-                   pid, *rss_and_swap, min_size_kb);
-      it = pids->erase(it);
-    } else {
-      ++it;
+    for (const std::string& pattern : patterns) {
+      if (glob_aware::MatchGlobPattern(pattern.c_str(), cmdline.c_str(),
+                                       binname)) {
+        pids->insert(pid);
+      }
     }
-  }
+  });
 }
-
-base::Optional<Uids> GetUids(const std::string& status) {
-  auto entry_idx = status.find("Uid:");
-  if (entry_idx == std::string::npos)
-    return base::nullopt;
-
-  Uids uids;
-  const char* str = &status[entry_idx + 4];
-  char* endptr;
-
-  uids.real = strtoull(str, &endptr, 10);
-  if (*endptr != ' ' && *endptr != '\t')
-    return base::nullopt;
-
-  str = endptr;
-  uids.effective = strtoull(str, &endptr, 10);
-  if (*endptr != ' ' && *endptr != '\t')
-    return base::nullopt;
-
-  str = endptr;
-  uids.saved_set = strtoull(str, &endptr, 10);
-  if (*endptr != ' ' && *endptr != '\t')
-    return base::nullopt;
-
-  str = endptr;
-  uids.filesystem = strtoull(str, &endptr, 10);
-  if (*endptr != '\n' && *endptr != '\0')
-    return base::nullopt;
-  return uids;
-}
+}  // namespace glob_aware
 
 }  // namespace profiling
 }  // namespace perfetto
diff --git a/src/profiling/common/proc_utils.h b/src/profiling/common/proc_utils.h
index 6376478..ccbf633 100644
--- a/src/profiling/common/proc_utils.h
+++ b/src/profiling/common/proc_utils.h
@@ -53,14 +53,6 @@
   }
 }
 
-base::Optional<std::vector<std::string>> NormalizeCmdlines(
-    const std::vector<std::string>& cmdlines);
-
-void FindAllProfilablePids(std::set<pid_t>* pids);
-void FindPidsForCmdlines(const std::vector<std::string>& cmdlines,
-                         std::set<pid_t>* pids);
-bool GetCmdlineForPID(pid_t pid, std::string* name);
-
 base::Optional<std::string> ReadStatus(pid_t pid);
 base::Optional<uint32_t> GetRssAnonAndSwap(const std::string&);
 // Filters the list of pids (in-place), keeping only the
@@ -69,6 +61,24 @@
 
 base::Optional<Uids> GetUids(const std::string&);
 
+void FindAllProfilablePids(std::set<pid_t>* pids);
+
+// TODO(rsavitski): we're changing how the profilers treat proc cmdlines, the
+// newer semantics are implemented in proc_cmdline.h. Wrappers around those
+// implementations are placed in the "glob_aware" namespace here, until we
+// migrate to one implementation for all profilers.
+ssize_t NormalizeCmdLine(char** cmdline_ptr, size_t size);
+base::Optional<std::vector<std::string>> NormalizeCmdlines(
+    const std::vector<std::string>& cmdlines);
+void FindPidsForCmdlines(const std::vector<std::string>& cmdlines,
+                         std::set<pid_t>* pids);
+bool GetCmdlineForPID(pid_t pid, std::string* name);
+
+namespace glob_aware {
+void FindPidsForCmdlinePatterns(const std::vector<std::string>& cmdlines,
+                                std::set<pid_t>* pids);
+}  // namespace glob_aware
+
 }  // namespace profiling
 }  // namespace perfetto
 
diff --git a/src/profiling/common/proc_utils_unittest.cc b/src/profiling/common/proc_utils_unittest.cc
index cec73d8..36a7720 100644
--- a/src/profiling/common/proc_utils_unittest.cc
+++ b/src/profiling/common/proc_utils_unittest.cc
@@ -16,7 +16,6 @@
 
 #include "src/profiling/common/proc_utils.h"
 #include "perfetto/ext/base/optional.h"
-#include "perfetto/profiling/normalize.h"
 
 #include "perfetto/ext/base/utils.h"
 #include "test/gtest_and_gmock.h"
diff --git a/src/profiling/memory/BUILD.gn b/src/profiling/memory/BUILD.gn
index 34c6263..a4c7777 100644
--- a/src/profiling/memory/BUILD.gn
+++ b/src/profiling/memory/BUILD.gn
@@ -256,6 +256,7 @@
     "../common:callstack_trie",
     "../common:interner",
     "../common:interning_output",
+    "../common:proc_cmdline",
     "../common:proc_utils",
     "../common:producer_support",
     "../common:profiler_guardrails",
diff --git a/src/profiling/memory/java_hprof_producer.cc b/src/profiling/memory/java_hprof_producer.cc
index 6c68995..81a9c27 100644
--- a/src/profiling/memory/java_hprof_producer.cc
+++ b/src/profiling/memory/java_hprof_producer.cc
@@ -21,6 +21,7 @@
 
 #include "perfetto/ext/base/optional.h"
 #include "perfetto/ext/tracing/core/trace_writer.h"
+#include "src/profiling/common/proc_cmdline.h"
 #include "src/profiling/common/proc_utils.h"
 #include "src/profiling/common/producer_support.h"
 
@@ -58,10 +59,10 @@
 JavaHprofProducer::DataSource::DataSource(
     DataSourceConfig ds_config,
     JavaHprofConfig config,
-    std::vector<std::string> normalized_cmdlines)
-    : ds_config_(ds_config),
-      config_(config),
-      normalized_cmdlines_(normalized_cmdlines) {}
+    std::vector<std::string> target_cmdlines)
+    : ds_config_(std::move(ds_config)),
+      config_(std::move(config)),
+      target_cmdlines_(std::move(target_cmdlines)) {}
 
 void JavaHprofProducer::DataSource::SendSignal() const {
   for (pid_t pid : pids_) {
@@ -97,9 +98,9 @@
 void JavaHprofProducer::DataSource::CollectPids() {
   pids_.clear();
   for (uint64_t pid : config_.pid()) {
-    pids_.emplace(static_cast<pid_t>(pid));
+    pids_.insert(static_cast<pid_t>(pid));
   }
-  FindPidsForCmdlines(normalized_cmdlines_, &pids_);
+  glob_aware::FindPidsForCmdlinePatterns(target_cmdlines_, &pids_);
   if (config_.min_anonymous_memory_kb() > 0)
     RemoveUnderAnonThreshold(config_.min_anonymous_memory_kb(), &pids_);
 }
@@ -122,13 +123,8 @@
   }
   JavaHprofConfig config;
   config.ParseFromString(ds_config.java_hprof_config_raw());
-  base::Optional<std::vector<std::string>> normalized_cmdlines =
-      NormalizeCmdlines(config.process_cmdline());
-  if (!normalized_cmdlines.has_value()) {
-    PERFETTO_ELOG("Rejecting data source due to invalid cmdline in config.");
-    return;
-  }
-  DataSource ds(ds_config, std::move(config), std::move(*normalized_cmdlines));
+  std::vector<std::string> cmdline_patterns = config.process_cmdline();
+  DataSource ds(ds_config, std::move(config), std::move(cmdline_patterns));
   ds.CollectPids();
   data_sources_.emplace(id, ds);
 }
diff --git a/src/profiling/memory/java_hprof_producer.h b/src/profiling/memory/java_hprof_producer.h
index 056c60a..4355d96 100644
--- a/src/profiling/memory/java_hprof_producer.h
+++ b/src/profiling/memory/java_hprof_producer.h
@@ -73,7 +73,7 @@
    public:
     DataSource(DataSourceConfig ds_config,
                JavaHprofConfig config,
-               std::vector<std::string> normalized_cmdlines);
+               std::vector<std::string> target_cmdlines);
     void CollectPids();
     void SendSignal() const;
 
@@ -83,7 +83,7 @@
    private:
     DataSourceConfig ds_config_;
     JavaHprofConfig config_;
-    std::vector<std::string> normalized_cmdlines_;
+    std::vector<std::string> target_cmdlines_;
 
     std::set<pid_t> pids_;
   };
diff --git a/src/profiling/perf/BUILD.gn b/src/profiling/perf/BUILD.gn
index 899e09c..341bd3a 100644
--- a/src/profiling/perf/BUILD.gn
+++ b/src/profiling/perf/BUILD.gn
@@ -51,7 +51,6 @@
   deps = [
     ":proc_descriptors",
     "../../../gn:default_deps",
-    "../../../include/perfetto/profiling:normalize",
     "../../../protos/perfetto/common:cpp",
     "../../../protos/perfetto/common:zero",
     "../../../protos/perfetto/config:cpp",
@@ -65,7 +64,7 @@
     "../common:callstack_trie",
     "../common:interner",
     "../common:interning_output",
-    "../common:proc_utils",
+    "../common:proc_cmdline",
     "../common:producer_support",
     "../common:profiler_guardrails",
   ]
diff --git a/src/profiling/perf/event_config.cc b/src/profiling/perf/event_config.cc
index 3759533..8506a1c 100644
--- a/src/profiling/perf/event_config.cc
+++ b/src/profiling/perf/event_config.cc
@@ -25,7 +25,6 @@
 #include "perfetto/base/flat_set.h"
 #include "perfetto/ext/base/optional.h"
 #include "perfetto/ext/base/utils.h"
-#include "perfetto/profiling/normalize.h"
 #include "src/profiling/perf/regs_parsing.h"
 
 #include "protos/perfetto/common/perf_events.gen.h"
@@ -40,23 +39,6 @@
 constexpr uint32_t kDefaultReadTickPeriodMs = 100;
 constexpr uint32_t kDefaultRemoteDescriptorTimeoutMs = 100;
 
-base::Optional<std::string> Normalize(const std::string& src) {
-  // Construct a null-terminated string that will be mutated by the normalizer.
-  std::vector<char> base(src.size() + 1);
-  memcpy(base.data(), src.data(), src.size());
-  base[src.size()] = '\0';
-
-  char* new_start = base.data();
-  ssize_t new_sz = NormalizeCmdLine(&new_start, base.size());
-  if (new_sz < 0) {
-    PERFETTO_ELOG("Failed to normalize config cmdline [%s], aborting",
-                  base.data());
-    return base::nullopt;
-  }
-  return base::make_optional<std::string>(new_start,
-                                          static_cast<size_t>(new_sz));
-}
-
 // Acceptable forms: "sched/sched_switch" or "sched:sched_switch".
 std::pair<std::string, std::string> SplitTracepointString(
     const std::string& input) {
@@ -100,40 +82,29 @@
   return base::make_optional(tracepoint_id);
 }
 
-// Returns |base::nullopt| if any of the input cmdlines couldn't be normalized.
 // |T| is either gen::PerfEventConfig or gen::PerfEventConfig::Scope.
+// Note: the semantics of target_cmdline and exclude_cmdline were changed since
+// their original introduction. They used to be put through a canonicalization
+// function that simplified them to the binary name alone. We no longer do this,
+// regardless of whether we're parsing an old-style config. The overall outcome
+// shouldn't change for almost all existing uses.
 template <typename T>
-base::Optional<TargetFilter> ParseTargetFilter(const T& cfg) {
+TargetFilter ParseTargetFilter(const T& cfg) {
   TargetFilter filter;
   for (const auto& str : cfg.target_cmdline()) {
-    base::Optional<std::string> opt = Normalize(str);
-    if (!opt.has_value()) {
-      PERFETTO_ELOG("Failure normalizing cmdline: [%s]", str.c_str());
-      return base::nullopt;
-    }
-    filter.cmdlines.insert(std::move(opt.value()));
+    filter.cmdlines.push_back(str);
   }
-
   for (const auto& str : cfg.exclude_cmdline()) {
-    base::Optional<std::string> opt = Normalize(str);
-    if (!opt.has_value()) {
-      PERFETTO_ELOG("Failure normalizing cmdline: [%s]", str.c_str());
-      return base::nullopt;
-    }
-    filter.exclude_cmdlines.insert(std::move(opt.value()));
+    filter.exclude_cmdlines.push_back(str);
   }
-
   for (const int32_t pid : cfg.target_pid()) {
     filter.pids.insert(pid);
   }
-
   for (const int32_t pid : cfg.exclude_pid()) {
     filter.exclude_pids.insert(pid);
   }
-
   filter.additional_cmdline_count = cfg.additional_cmdline_count();
-
-  return base::make_optional(std::move(filter));
+  return filter;
 }
 
 constexpr bool IsPowerOfTwo(size_t v) {
@@ -389,14 +360,10 @@
     sample_callstacks = true;
 
     // Process scoping.
-    auto maybe_filter =
+    target_filter =
         pb_config.callstack_sampling().has_scope()
             ? ParseTargetFilter(pb_config.callstack_sampling().scope())
             : ParseTargetFilter(pb_config);  // backwards compatibility
-    if (!maybe_filter.has_value())
-      return base::nullopt;
-
-    target_filter = std::move(maybe_filter.value());
 
     // Inclusion of kernel callchains.
     kernel_frames = pb_config.callstack_sampling().kernel_frames() ||
diff --git a/src/profiling/perf/event_config.h b/src/profiling/perf/event_config.h
index 782b05d..cb84168 100644
--- a/src/profiling/perf/event_config.h
+++ b/src/profiling/perf/event_config.h
@@ -44,8 +44,8 @@
 // Parsed allow/deny-list for filtering samples.
 // An empty filter set means that all targets are allowed.
 struct TargetFilter {
-  base::FlatSet<std::string> cmdlines;
-  base::FlatSet<std::string> exclude_cmdlines;
+  std::vector<std::string> cmdlines;
+  std::vector<std::string> exclude_cmdlines;
   base::FlatSet<pid_t> pids;
   base::FlatSet<pid_t> exclude_pids;
   uint32_t additional_cmdline_count = 0;
diff --git a/src/profiling/perf/perf_producer.cc b/src/profiling/perf/perf_producer.cc
index d49c558..22c4a6d 100644
--- a/src/profiling/perf/perf_producer.cc
+++ b/src/profiling/perf/perf_producer.cc
@@ -36,7 +36,7 @@
 #include "perfetto/tracing/core/data_source_config.h"
 #include "perfetto/tracing/core/data_source_descriptor.h"
 #include "src/profiling/common/callstack_trie.h"
-#include "src/profiling/common/proc_utils.h"
+#include "src/profiling/common/proc_cmdline.h"
 #include "src/profiling/common/producer_support.h"
 #include "src/profiling/common/profiler_guardrails.h"
 #include "src/profiling/common/unwind_support.h"
@@ -180,14 +180,27 @@
                              base::FlatSet<std::string>* additional_cmdlines,
                              const TargetFilter& filter) {
   PERFETTO_CHECK(additional_cmdlines);
+
   std::string cmdline;
-  bool have_cmdline = GetCmdlineForPID(pid, &cmdline);  // normalized form
-  if (!have_cmdline) {
-    PERFETTO_DLOG("Failed to look up cmdline for pid [%d]",
-                  static_cast<int>(pid));
+  bool have_cmdline = glob_aware::ReadProcCmdlineForPID(pid, &cmdline);
+
+  const char* binname = "";
+  if (have_cmdline) {
+    binname = glob_aware::FindBinaryName(cmdline.c_str(), cmdline.size());
   }
 
-  if (have_cmdline && filter.exclude_cmdlines.count(cmdline)) {
+  auto has_matching_pattern = [](const std::vector<std::string>& patterns,
+                                 const char* cmd, const char* name) {
+    for (const std::string& pattern : patterns) {
+      if (glob_aware::MatchGlobPattern(pattern.c_str(), cmd, name)) {
+        return true;
+      }
+    }
+    return false;
+  };
+
+  if (have_cmdline &&
+      has_matching_pattern(filter.exclude_cmdlines, cmdline.c_str(), binname)) {
     PERFETTO_DLOG("Explicitly rejecting samples for pid [%d] due to cmdline",
                   static_cast<int>(pid));
     return true;
@@ -198,20 +211,21 @@
     return true;
   }
 
-  if (have_cmdline && filter.cmdlines.count(cmdline)) {
+  if (have_cmdline &&
+      has_matching_pattern(filter.cmdlines, cmdline.c_str(), binname)) {
     return false;
   }
   if (filter.pids.count(pid)) {
     return false;
   }
+
+  // Empty allow filter means keep everything that isn't explicitly excluded.
   if (filter.cmdlines.empty() && filter.pids.empty() &&
       !filter.additional_cmdline_count) {
-    // If no filters are set allow everything.
     return false;
   }
 
-  // If we didn't read the command line that's a good prediction we will not be
-  // able to profile either.
+  // Config option that allows to profile just the N first seen cmdlines.
   if (have_cmdline) {
     if (additional_cmdlines->count(cmdline)) {
       return false;
diff --git a/src/traced/probes/ftrace/cpu_reader.cc b/src/traced/probes/ftrace/cpu_reader.cc
index 16e8e3d..4f03344 100644
--- a/src/traced/probes/ftrace/cpu_reader.cc
+++ b/src/traced/probes/ftrace/cpu_reader.cc
@@ -282,22 +282,28 @@
     return pages_read;
 
   for (FtraceDataSource* data_source : started_data_sources) {
-    bool pages_parsed_ok = ProcessPagesForDataSource(
+    size_t pages_parsed_ok = ProcessPagesForDataSource(
         data_source->trace_writer(), data_source->mutable_metadata(), cpu_,
         data_source->parsing_config(), parsing_buf, pages_read, table_,
         symbolizer_, ftrace_clock_snapshot_, ftrace_clock_);
-    // If this CHECK fires, it means that we did not know how to parse the
-    // kernel binary format. This is a bug in either perfetto or the kernel, and
-    // must be investigated. Hence we CHECK instead of recording a bit
-    // in the ftrace stats proto, which is easier to overlook.
-    PERFETTO_CHECK(pages_parsed_ok);
+    // If this happens, it means that we did not know how to parse the kernel
+    // binary format. This is a bug in either perfetto or the kernel, and must
+    // be investigated. Hence we abort instead of recording a bit in the ftrace
+    // stats proto, which is easier to overlook.
+    if (pages_parsed_ok != pages_read) {
+      const size_t first_bad_page_idx = pages_parsed_ok;
+      const uint8_t* curr_page =
+          parsing_buf + (first_bad_page_idx * base::kPageSize);
+      LogInvalidPage(curr_page, base::kPageSize);
+      PERFETTO_FATAL("Failed to parse ftrace page");
+    }
   }
 
   return pages_read;
 }
 
 // static
-bool CpuReader::ProcessPagesForDataSource(
+size_t CpuReader::ProcessPagesForDataSource(
     TraceWriter* trace_writer,
     FtraceMetadata* metadata,
     size_t cpu,
@@ -405,7 +411,7 @@
       bundle->set_lost_events(true);
   };
 
-  bool pages_parsed_ok = true;
+  size_t correctly_parsed_pages = pages_read;
   start_new_packet(/*lost_events=*/false);
   for (size_t i = 0; i < pages_read; i++) {
     const uint8_t* curr_page = parsing_buf + (i * base::kPageSize);
@@ -417,9 +423,7 @@
     if (!page_header.has_value() || page_header->size == 0 ||
         parse_pos >= curr_page_end ||
         parse_pos + page_header->size > curr_page_end) {
-      LogInvalidPage(curr_page, base::kPageSize);
-      PERFETTO_DFATAL("invalid page header");
-      return false;
+      return i;
     }
 
     // Start a new bundle if either:
@@ -442,14 +446,16 @@
                          &compact_sched, bundle, metadata);
 
     if (evt_size != page_header->size) {
-      pages_parsed_ok = false;
-      LogInvalidPage(curr_page, base::kPageSize);
-      PERFETTO_DFATAL("could not parse ftrace page");
+      // TODO(ddiproietto,rsavitski): Consider returning early here instead, to
+      // improve readability.
+      if (correctly_parsed_pages == pages_read) {
+        correctly_parsed_pages = i;
+      }
     }
   }
   finalize_cur_packet();
 
-  return pages_parsed_ok;
+  return correctly_parsed_pages;
 }
 
 // A page header consists of:
diff --git a/src/traced/probes/ftrace/cpu_reader.h b/src/traced/probes/ftrace/cpu_reader.h
index 3f1eae7..414da31 100644
--- a/src/traced/probes/ftrace/cpu_reader.h
+++ b/src/traced/probes/ftrace/cpu_reader.h
@@ -234,17 +234,22 @@
   // Parses & encodes the given range of contiguous tracing pages. Called by
   // |ReadAndProcessBatch| for each active data source.
   //
+  // Returns the number of correctly processed pages. If the return value is
+  // equal to |pages_read|, there was no error. Otherwise, the return value
+  // points to the first page that contains an error.
+  //
   // public and static for testing
-  static bool ProcessPagesForDataSource(TraceWriter* trace_writer,
-                                        FtraceMetadata* metadata,
-                                        size_t cpu,
-                                        const FtraceDataSourceConfig* ds_config,
-                                        const uint8_t* parsing_buf,
-                                        const size_t pages_read,
-                                        const ProtoTranslationTable* table,
-                                        LazyKernelSymbolizer* symbolizer,
-                                        const FtraceClockSnapshot*,
-                                        protos::pbzero::FtraceClock);
+  static size_t ProcessPagesForDataSource(
+      TraceWriter* trace_writer,
+      FtraceMetadata* metadata,
+      size_t cpu,
+      const FtraceDataSourceConfig* ds_config,
+      const uint8_t* parsing_buf,
+      const size_t pages_read,
+      const ProtoTranslationTable* table,
+      LazyKernelSymbolizer* symbolizer,
+      const FtraceClockSnapshot*,
+      protos::pbzero::FtraceClock);
 
   void set_ftrace_clock(protos::pbzero::FtraceClock clock) {
     ftrace_clock_ = clock;
diff --git a/src/traced/probes/ftrace/cpu_reader_unittest.cc b/src/traced/probes/ftrace/cpu_reader_unittest.cc
index c918af8..44062e7 100644
--- a/src/traced/probes/ftrace/cpu_reader_unittest.cc
+++ b/src/traced/probes/ftrace/cpu_reader_unittest.cc
@@ -641,7 +641,6 @@
   EXPECT_EQ(event.print().buf(), "");
 }
 
-
 TEST(CpuReaderTest, FilterByEvent) {
   const ExamplePage* test_case = &g_single_print;
 
@@ -1244,6 +1243,17 @@
     00000050: 0000 0000 950e 0000 7800 0000 0000 0000  ................
     )";
 
+// Page with invalid data.
+static char g_invalid_page[] =
+    R"(
+    00000000: 2b16 c3be 90b6 0300 4b00 0000 0000 0000  ................
+    00000010: 1e00 0000 0000 0000 1000 0000 2f00 0103  ................
+    00000020: 0300 0000 6b73 6f66 7469 7271 642f 3000  ................
+    00000030: 0000 0000 0300 0000 7800 0000 0100 0000  ................
+    00000040: 0000 0000 736c 6565 7000 722f 3000 0000  ................
+    00000050: 0000 0000 950e 0000 7800 0000 0000 0000  ................
+    )";
+
 TEST(CpuReaderTest, NewPacketOnLostEvents) {
   auto page_ok = PageFromXxd(g_switch_page);
   auto page_loss = PageFromXxd(g_switch_page_lost_events);
@@ -1269,11 +1279,13 @@
       table->EventToFtraceId(GroupAndName("sched", "sched_switch")));
 
   TraceWriterForTesting trace_writer;
-  CpuReader::ProcessPagesForDataSource(
+  size_t processed_pages = CpuReader::ProcessPagesForDataSource(
       &trace_writer, &metadata, /*cpu=*/1, &ds_config, buf.get(), kTestPages,
       table, /*symbolizer=*/nullptr, /*ftrace_clock_snapshot=*/nullptr,
       protos::pbzero::FTRACE_CLOCK_UNSPECIFIED);
 
+  ASSERT_EQ(processed_pages, kTestPages);
+
   // Each packet should contain the parsed contents of a contiguous run of pages
   // without data loss.
   // So we should get three packets (each page has 1 event):
@@ -1291,6 +1303,39 @@
   EXPECT_EQ(4u, packets[2].ftrace_events().event().size());
 }
 
+TEST(CpuReaderTest, ProcessPagesForDataSourceError) {
+  auto page_ok = PageFromXxd(g_switch_page);
+  auto page_err = PageFromXxd(g_invalid_page);
+
+  std::vector<const void*> test_page_order = {
+      page_ok.get(), page_ok.get(), page_ok.get(),  page_err.get(),
+      page_ok.get(), page_ok.get(), page_err.get(), page_ok.get()};
+
+  // Prepare a buffer with 8 contiguous pages, with the above contents.
+  static constexpr size_t kTestPages = 8;
+
+  std::unique_ptr<uint8_t[]> buf(new uint8_t[base::kPageSize * kTestPages]());
+  for (size_t i = 0; i < kTestPages; i++) {
+    void* dest = buf.get() + (i * base::kPageSize);
+    memcpy(dest, static_cast<const void*>(test_page_order[i]), base::kPageSize);
+  }
+
+  BundleProvider bundle_provider(base::kPageSize);
+  ProtoTranslationTable* table = GetTable("synthetic");
+  FtraceMetadata metadata{};
+  FtraceDataSourceConfig ds_config = EmptyConfig();
+  ds_config.event_filter.AddEnabledEvent(
+      table->EventToFtraceId(GroupAndName("sched", "sched_switch")));
+
+  TraceWriterForTesting trace_writer;
+  size_t processed_pages = CpuReader::ProcessPagesForDataSource(
+      &trace_writer, &metadata, /*cpu=*/1, &ds_config, buf.get(), kTestPages,
+      table, /*symbolizer=*/nullptr, /*ftrace_clock_snapshot=*/nullptr,
+      protos::pbzero::FTRACE_CLOCK_UNSPECIFIED);
+
+  EXPECT_EQ(processed_pages, 3u);
+}
+
 // Page containing an absolute timestamp (RINGBUF_TYPE_TIME_STAMP).
 static char g_abs_timestamp[] =
     R"(
diff --git a/tools/cpu_profile b/tools/cpu_profile
index 2bce7b0..9dbeb44 100755
--- a/tools/cpu_profile
+++ b/tools/cpu_profile
@@ -439,7 +439,7 @@
 
 
 # BEGIN_SECTION_GENERATED_BY(roll-prebuilts)
-# Revision: v24.1
+# Revision: v25.0
 PERFETTO_PREBUILT_MANIFEST = [{
     'tool':
         'trace_to_text',
@@ -448,11 +448,11 @@
     'file_name':
         'trace_to_text',
     'file_size':
-        6972736,
+        6525752,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v24.1/mac-amd64/trace_to_text',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v25.0/mac-amd64/trace_to_text',
     'sha256':
-        'cd963e3f63b23977302fcbcb19a9fe64e98aeb2f419ce4212cfafa4e52b8faec',
+        '64ccf6bac87825145691c6533412e514891f82300d68ff7ce69e8d2ca69aaf62',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -464,11 +464,11 @@
     'file_name':
         'trace_to_text.exe',
     'file_size':
-        6685696,
+        5925888,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v24.1/windows-amd64/trace_to_text.exe',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v25.0/windows-amd64/trace_to_text.exe',
     'sha256':
-        'bc951b12148338ff151f33af87e79926d18b7ea3180419a7e6c6c150b604fe25',
+        '29e50ec4d8e28c7c322ba13273afcce80c63fe7d9f182b83af0e2077b4d2b952',
     'platform':
         'win32',
     'machine': ['amd64']
@@ -480,11 +480,11 @@
     'file_name':
         'trace_to_text',
     'file_size':
-        7539312,
+        6939560,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v24.1/linux-amd64/trace_to_text',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v25.0/linux-amd64/trace_to_text',
     'sha256':
-        'e40fbf44fca2673be1e6aedbc3159ce3cd4f6f33911f9dd1298471bd5b4a05ab',
+        '109f4ff3bbd47633b0c08a338f1230e69d529ddf1584656ed45d8a59acaaabeb',
     'platform':
         'linux',
     'machine': ['x86_64']
diff --git a/tools/heap_profile b/tools/heap_profile
index 4539dc1..65a3213 100755
--- a/tools/heap_profile
+++ b/tools/heap_profile
@@ -576,7 +576,7 @@
 
 
 # BEGIN_SECTION_GENERATED_BY(roll-prebuilts)
-# Revision: v24.1
+# Revision: v25.0
 PERFETTO_PREBUILT_MANIFEST = [{
     'tool':
         'trace_to_text',
@@ -585,11 +585,11 @@
     'file_name':
         'trace_to_text',
     'file_size':
-        6972736,
+        6525752,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v24.1/mac-amd64/trace_to_text',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v25.0/mac-amd64/trace_to_text',
     'sha256':
-        'cd963e3f63b23977302fcbcb19a9fe64e98aeb2f419ce4212cfafa4e52b8faec',
+        '64ccf6bac87825145691c6533412e514891f82300d68ff7ce69e8d2ca69aaf62',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -597,15 +597,31 @@
     'tool':
         'trace_to_text',
     'arch':
+        'mac-arm64',
+    'file_name':
+        'trace_to_text',
+    'file_size':
+        5661424,
+    'url':
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v25.0/mac-arm64/trace_to_text',
+    'sha256':
+        'fbb6d256c96cdc296f2faec2965d16a1527c4900e66a33aca979ff1c336a9f2f',
+    'platform':
+        'darwin',
+    'machine': ['arm64']
+}, {
+    'tool':
+        'trace_to_text',
+    'arch':
         'windows-amd64',
     'file_name':
         'trace_to_text.exe',
     'file_size':
-        6685696,
+        5925888,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v24.1/windows-amd64/trace_to_text.exe',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v25.0/windows-amd64/trace_to_text.exe',
     'sha256':
-        'bc951b12148338ff151f33af87e79926d18b7ea3180419a7e6c6c150b604fe25',
+        '29e50ec4d8e28c7c322ba13273afcce80c63fe7d9f182b83af0e2077b4d2b952',
     'platform':
         'win32',
     'machine': ['amd64']
@@ -617,11 +633,11 @@
     'file_name':
         'trace_to_text',
     'file_size':
-        7539312,
+        6939560,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v24.1/linux-amd64/trace_to_text',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v25.0/linux-amd64/trace_to_text',
     'sha256':
-        'e40fbf44fca2673be1e6aedbc3159ce3cd4f6f33911f9dd1298471bd5b4a05ab',
+        '109f4ff3bbd47633b0c08a338f1230e69d529ddf1584656ed45d8a59acaaabeb',
     'platform':
         'linux',
     'machine': ['x86_64']
diff --git a/tools/record_android_trace b/tools/record_android_trace
index 1134a06..f2931ed 100755
--- a/tools/record_android_trace
+++ b/tools/record_android_trace
@@ -404,7 +404,7 @@
 
 
 # BEGIN_SECTION_GENERATED_BY(roll-prebuilts)
-# Revision: v24.1
+# Revision: v25.0
 PERFETTO_PREBUILT_MANIFEST = [{
     'tool':
         'tracebox',
@@ -413,11 +413,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        1046604,
+        1067004,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v24.1/android-arm/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v25.0/android-arm/tracebox',
     'sha256':
-        'a6ab413ef1aecbe93a73d9e3c9d6f00eb06b4c9886c0627a979a9acbf071a4a3'
+        '6fff40fc02d154b187187fe70069cc93bde00d3be5b3582ba6120b0cfa83d379'
 }, {
     'tool':
         'tracebox',
@@ -426,11 +426,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        1588032,
+        1620704,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v24.1/android-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v25.0/android-arm64/tracebox',
     'sha256':
-        'a0da06d2df7106712f90a5475ab8ef994ae844f4fee7bf4f38aaacc8260062cd'
+        'd482c86eccd7dc0ff4f57bd5d841d90e97ca5e31b881fcea442d4ad03ecfd4f4'
 }, {
     'tool':
         'tracebox',
@@ -439,11 +439,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        1615908,
+        1644500,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v24.1/android-x86/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v25.0/android-x86/tracebox',
     'sha256':
-        'a56591c10f29c00b425ade21d1c61086584166fb00236b9744e3cb06b777fbc8'
+        '48de1d10959b6130d2d6d68ae3ba0d8bb3f3aa6fe526d77ea9abad058a631d8f'
 }, {
     'tool':
         'tracebox',
@@ -452,11 +452,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        1841984,
+        1870560,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v24.1/android-x64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v25.0/android-x64/tracebox',
     'sha256':
-        '21db734c29f96436685c2a65086e231e03425e35bfdb6fd25a4af729c55c6619'
+        '333c17912a143300ca6afa3b27d93497821615028e612e5d6132889427c10599'
 }]
 
 
diff --git a/tools/trace_processor b/tools/trace_processor
index 8bbab28..10c7836 100755
--- a/tools/trace_processor
+++ b/tools/trace_processor
@@ -27,7 +27,7 @@
 TOOL_NAME = 'trace_processor_shell'
 
 # BEGIN_SECTION_GENERATED_BY(roll-prebuilts)
-# Revision: v24.1
+# Revision: v25.0
 PERFETTO_PREBUILT_MANIFEST = [{
     'tool':
         'trace_processor_shell',
@@ -36,11 +36,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        6940768,
+        7302832,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v24.1/mac-amd64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v25.0/mac-amd64/trace_processor_shell',
     'sha256':
-        '2d553933fdaba7080ff207094c0ed514a2f5573e525a653417b656b9c9c90e16',
+        'edc841a7f6769959f3b03f1e93dcd263d30864dc3e1fc4e3fcdbe4e44e19b02a',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -48,15 +48,31 @@
     'tool':
         'trace_processor_shell',
     'arch':
+        'mac-arm64',
+    'file_name':
+        'trace_processor_shell',
+    'file_size':
+        6393768,
+    'url':
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v25.0/mac-arm64/trace_processor_shell',
+    'sha256':
+        '9f768e35d85501d18f09b0313c037a34f3306663d504f31e4874b1b3b20a00d6',
+    'platform':
+        'darwin',
+    'machine': ['arm64']
+}, {
+    'tool':
+        'trace_processor_shell',
+    'arch':
         'windows-amd64',
     'file_name':
         'trace_processor_shell.exe',
     'file_size':
-        6681600,
+        6942720,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v24.1/windows-amd64/trace_processor_shell.exe',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v25.0/windows-amd64/trace_processor_shell.exe',
     'sha256':
-        '1f4cb1a8a1726ebbada1f928c7d856a04fd57994077cead0b65c7b32225ba48b',
+        'c7118d820f720ca99acc9ef736f553cb0ba01d034e8f61b83f44d857f76058ce',
     'platform':
         'win32',
     'machine': ['amd64']
@@ -68,11 +84,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        7520208,
+        7824920,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v24.1/linux-amd64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v25.0/linux-amd64/trace_processor_shell',
     'sha256':
-        '232acef777f0b0206d023bf705d1abfd64503a7762e50e813c693dd8bafd91bd',
+        '77ec0f9dbec36079d83f2843372446c354d04e7971c1f159c465d4e8e78a3d4d',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -84,11 +100,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        4887472,
+        5076040,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v24.1/linux-arm/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v25.0/linux-arm/trace_processor_shell',
     'sha256':
-        'b03ae322ed90ee7746f5cb33c9a92a82cec34540f3fb1997b343d8cad18db158',
+        '7290ef3bda83736d0076e49bab5524294906bc8d1785ccef876ba8bd490e62b9',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -100,11 +116,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        6765656,
+        7055416,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v24.1/linux-arm64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v25.0/linux-arm64/trace_processor_shell',
     'sha256':
-        '222240d00ca2ae3eac6908df275ca79e2b72ff6a0c934594d2744a2399a58dcc',
+        'b3d91b69a4152419bc3de2df3aeaef400d829e4a87e5c5cb985af0b56f319279',
     'platform':
         'linux',
     'machine': ['aarch64']
diff --git a/tools/tracebox b/tools/tracebox
index e7b1253..8dc234e 100755
--- a/tools/tracebox
+++ b/tools/tracebox
@@ -27,7 +27,7 @@
 TOOL_NAME = 'tracebox'
 
 # BEGIN_SECTION_GENERATED_BY(roll-prebuilts)
-# Revision: v24.1
+# Revision: v25.0
 PERFETTO_PREBUILT_MANIFEST = [{
     'tool':
         'tracebox',
@@ -36,11 +36,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        1332696,
+        1382584,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v24.1/mac-amd64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v25.0/mac-amd64/tracebox',
     'sha256':
-        'be14a9040e8d0143d5b0838560cf20afb0be2d1971f6df4c79d7b4c46aa5852e',
+        '2b5eecebd92810d6f88598904fc52db4930f5f234bcb749c839eb25ebaf42a8c',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -48,15 +48,31 @@
     'tool':
         'tracebox',
     'arch':
+        'mac-arm64',
+    'file_name':
+        'tracebox',
+    'file_size':
+        1275896,
+    'url':
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v25.0/mac-arm64/tracebox',
+    'sha256':
+        '51049ce25a123b8990658bddbb52f5984e80c8c4e2eb06780f22534218cdbd1d',
+    'platform':
+        'darwin',
+    'machine': ['arm64']
+}, {
+    'tool':
+        'tracebox',
+    'arch':
         'linux-amd64',
     'file_name':
         'tracebox',
     'file_size':
-        1756264,
+        1786784,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v24.1/linux-amd64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v25.0/linux-amd64/tracebox',
     'sha256':
-        '2fde750c982826f6a7c3e49b2d6ee7ec72c956170b4bba07e2989f2b81f26269',
+        '0df104d808715733cf717d5f2808e48e57bf20a851b49e1e579c516a34f4f9f9',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -68,11 +84,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        1756264,
+        1786784,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v24.1/linux-amd64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v25.0/linux-amd64/tracebox',
     'sha256':
-        '2fde750c982826f6a7c3e49b2d6ee7ec72c956170b4bba07e2989f2b81f26269',
+        '0df104d808715733cf717d5f2808e48e57bf20a851b49e1e579c516a34f4f9f9',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -84,11 +100,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        1012940,
+        1032796,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v24.1/linux-arm/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v25.0/linux-arm/tracebox',
     'sha256':
-        'a6f964b1b155c309fda0fb0b765f9fbf3d030d8c4bd6f9f03dad173ee689668e',
+        '4db159c84073f95e5f704137bff4c97aa32a714a9c93ba77f99eeedf9f3fc714',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -100,11 +116,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        1635160,
+        1663696,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v24.1/linux-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v25.0/linux-arm64/tracebox',
     'sha256':
-        'ed998b18606a6cffb490336990220e6290faa4544e52a5c77273f69507f80e89',
+        '75b7d877808a927962d15f8d5659ccce082e07797305cc739809aef98853bfce',
     'platform':
         'linux',
     'machine': ['aarch64']
diff --git a/tools/traceconv b/tools/traceconv
index 663ad5a..ea13c77 100755
--- a/tools/traceconv
+++ b/tools/traceconv
@@ -27,7 +27,7 @@
 TOOL_NAME = 'trace_to_text'
 
 # BEGIN_SECTION_GENERATED_BY(roll-prebuilts)
-# Revision: v24.1
+# Revision: v25.0
 PERFETTO_PREBUILT_MANIFEST = [{
     'tool':
         'trace_to_text',
@@ -36,11 +36,11 @@
     'file_name':
         'trace_to_text',
     'file_size':
-        6972736,
+        6525752,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v24.1/mac-amd64/trace_to_text',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v25.0/mac-amd64/trace_to_text',
     'sha256':
-        'cd963e3f63b23977302fcbcb19a9fe64e98aeb2f419ce4212cfafa4e52b8faec',
+        '64ccf6bac87825145691c6533412e514891f82300d68ff7ce69e8d2ca69aaf62',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -48,15 +48,31 @@
     'tool':
         'trace_to_text',
     'arch':
+        'mac-arm64',
+    'file_name':
+        'trace_to_text',
+    'file_size':
+        5661424,
+    'url':
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v25.0/mac-arm64/trace_to_text',
+    'sha256':
+        'fbb6d256c96cdc296f2faec2965d16a1527c4900e66a33aca979ff1c336a9f2f',
+    'platform':
+        'darwin',
+    'machine': ['arm64']
+}, {
+    'tool':
+        'trace_to_text',
+    'arch':
         'linux-amd64',
     'file_name':
         'trace_to_text',
     'file_size':
-        7539312,
+        6939560,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v24.1/linux-amd64/trace_to_text',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v25.0/linux-amd64/trace_to_text',
     'sha256':
-        'e40fbf44fca2673be1e6aedbc3159ce3cd4f6f33911f9dd1298471bd5b4a05ab',
+        '109f4ff3bbd47633b0c08a338f1230e69d529ddf1584656ed45d8a59acaaabeb',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -68,11 +84,11 @@
     'file_name':
         'trace_to_text.exe',
     'file_size':
-        6685696,
+        5925888,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v24.1/windows-amd64/trace_to_text.exe',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v25.0/windows-amd64/trace_to_text.exe',
     'sha256':
-        'bc951b12148338ff151f33af87e79926d18b7ea3180419a7e6c6c150b604fe25',
+        '29e50ec4d8e28c7c322ba13273afcce80c63fe7d9f182b83af0e2077b4d2b952',
     'platform':
         'win32',
     'machine': ['amd64']