Reapply "Add MappingTracker"

This reverts commit e5df54a90ec655ec4c47b7d26b0de7a54c98d75f.

The reason the tests were failing is that the new code is more strict in
determining whether a build_id is a hex module id. The old code just
uses a length of 33 to determine an id is a module_id. My new code was
also checking that the build_id was a hex string. Turns out some tests
just use dummy 33 length strings.

So I removed the extra checks (see diff patchset 1 vs 2)

Bug: b/283794416
Change-Id: I6ea5653534a258c7ca25b9bd96df56a107bcddd3
diff --git a/Android.bp b/Android.bp
index a991cc1..c3b04f9 100644
--- a/Android.bp
+++ b/Android.bp
@@ -2378,6 +2378,7 @@
         ":perfetto_src_trace_processor_storage_storage",
         ":perfetto_src_trace_processor_tables_tables",
         ":perfetto_src_trace_processor_types_types",
+        ":perfetto_src_trace_processor_util_build_id",
         ":perfetto_src_trace_processor_util_bump_allocator",
         ":perfetto_src_trace_processor_util_descriptors",
         ":perfetto_src_trace_processor_util_glob",
@@ -2391,7 +2392,6 @@
         ":perfetto_src_trace_processor_util_protozero_to_text",
         ":perfetto_src_trace_processor_util_regex",
         ":perfetto_src_trace_processor_util_sql_argument",
-        ":perfetto_src_trace_processor_util_stack_traces_util",
         ":perfetto_src_trace_processor_util_stdlib",
         ":perfetto_src_trace_processor_util_util",
         ":perfetto_src_trace_processor_util_zip_reader",
@@ -11110,6 +11110,7 @@
         "src/trace_processor/importers/common/event_tracker.cc",
         "src/trace_processor/importers/common/flow_tracker.cc",
         "src/trace_processor/importers/common/global_args_tracker.cc",
+        "src/trace_processor/importers/common/mapping_tracker.cc",
         "src/trace_processor/importers/common/metadata_tracker.cc",
         "src/trace_processor/importers/common/process_tracker.cc",
         "src/trace_processor/importers/common/sched_event_tracker.cc",
@@ -11120,6 +11121,7 @@
         "src/trace_processor/importers/common/thread_state_tracker.cc",
         "src/trace_processor/importers/common/trace_parser.cc",
         "src/trace_processor/importers/common/track_tracker.cc",
+        "src/trace_processor/importers/common/virtual_memory_mapping.cc",
     ],
 }
 
@@ -12324,6 +12326,14 @@
     name: "perfetto_src_trace_processor_unittests",
 }
 
+// GN: //src/trace_processor/util:build_id
+filegroup {
+    name: "perfetto_src_trace_processor_util_build_id",
+    srcs: [
+        "src/trace_processor/util/build_id.cc",
+    ],
+}
+
 // GN: //src/trace_processor/util:bump_allocator
 filegroup {
     name: "perfetto_src_trace_processor_util_bump_allocator",
@@ -12424,14 +12434,6 @@
     ],
 }
 
-// GN: //src/trace_processor/util:stack_traces_util
-filegroup {
-    name: "perfetto_src_trace_processor_util_stack_traces_util",
-    srcs: [
-        "src/trace_processor/util/stack_traces_util.cc",
-    ],
-}
-
 // GN: //src/trace_processor/util:stdlib
 filegroup {
     name: "perfetto_src_trace_processor_util_stdlib",
@@ -13959,6 +13961,7 @@
         ":perfetto_src_trace_processor_types_types",
         ":perfetto_src_trace_processor_types_unittests",
         ":perfetto_src_trace_processor_unittests",
+        ":perfetto_src_trace_processor_util_build_id",
         ":perfetto_src_trace_processor_util_bump_allocator",
         ":perfetto_src_trace_processor_util_descriptors",
         ":perfetto_src_trace_processor_util_glob",
@@ -13972,7 +13975,6 @@
         ":perfetto_src_trace_processor_util_protozero_to_text",
         ":perfetto_src_trace_processor_util_regex",
         ":perfetto_src_trace_processor_util_sql_argument",
-        ":perfetto_src_trace_processor_util_stack_traces_util",
         ":perfetto_src_trace_processor_util_stdlib",
         ":perfetto_src_trace_processor_util_unittests",
         ":perfetto_src_trace_processor_util_util",
@@ -14662,6 +14664,7 @@
         ":perfetto_src_trace_processor_storage_storage",
         ":perfetto_src_trace_processor_tables_tables",
         ":perfetto_src_trace_processor_types_types",
+        ":perfetto_src_trace_processor_util_build_id",
         ":perfetto_src_trace_processor_util_bump_allocator",
         ":perfetto_src_trace_processor_util_descriptors",
         ":perfetto_src_trace_processor_util_glob",
@@ -14675,7 +14678,6 @@
         ":perfetto_src_trace_processor_util_protozero_to_text",
         ":perfetto_src_trace_processor_util_regex",
         ":perfetto_src_trace_processor_util_sql_argument",
-        ":perfetto_src_trace_processor_util_stack_traces_util",
         ":perfetto_src_trace_processor_util_stdlib",
         ":perfetto_src_trace_processor_util_util",
         ":perfetto_src_trace_processor_util_zip_reader",
@@ -14896,6 +14898,7 @@
         ":perfetto_src_trace_processor_storage_storage",
         ":perfetto_src_trace_processor_tables_tables",
         ":perfetto_src_trace_processor_types_types",
+        ":perfetto_src_trace_processor_util_build_id",
         ":perfetto_src_trace_processor_util_bump_allocator",
         ":perfetto_src_trace_processor_util_descriptors",
         ":perfetto_src_trace_processor_util_glob",
@@ -14909,7 +14912,6 @@
         ":perfetto_src_trace_processor_util_protozero_to_text",
         ":perfetto_src_trace_processor_util_regex",
         ":perfetto_src_trace_processor_util_sql_argument",
-        ":perfetto_src_trace_processor_util_stack_traces_util",
         ":perfetto_src_trace_processor_util_stdlib",
         ":perfetto_src_trace_processor_util_util",
         ":perfetto_src_trace_processor_util_zip_reader",
diff --git a/BUILD b/BUILD
index ac30232..a2a4a15 100644
--- a/BUILD
+++ b/BUILD
@@ -266,6 +266,7 @@
         ":src_trace_processor_tables_tables",
         ":src_trace_processor_tables_tables_python",
         ":src_trace_processor_types_types",
+        ":src_trace_processor_util_build_id",
         ":src_trace_processor_util_bump_allocator",
         ":src_trace_processor_util_descriptors",
         ":src_trace_processor_util_glob",
@@ -279,7 +280,6 @@
         ":src_trace_processor_util_protozero_to_text",
         ":src_trace_processor_util_regex",
         ":src_trace_processor_util_sql_argument",
-        ":src_trace_processor_util_stack_traces_util",
         ":src_trace_processor_util_stdlib",
         ":src_trace_processor_util_util",
         ":src_trace_processor_util_zip_reader",
@@ -1467,6 +1467,7 @@
         "src/trace_processor/importers/common/clock_converter.h",
         "src/trace_processor/importers/common/clock_tracker.cc",
         "src/trace_processor/importers/common/clock_tracker.h",
+        "src/trace_processor/importers/common/create_mapping_params.h",
         "src/trace_processor/importers/common/deobfuscation_mapping_table.cc",
         "src/trace_processor/importers/common/deobfuscation_mapping_table.h",
         "src/trace_processor/importers/common/event_tracker.cc",
@@ -1475,6 +1476,8 @@
         "src/trace_processor/importers/common/flow_tracker.h",
         "src/trace_processor/importers/common/global_args_tracker.cc",
         "src/trace_processor/importers/common/global_args_tracker.h",
+        "src/trace_processor/importers/common/mapping_tracker.cc",
+        "src/trace_processor/importers/common/mapping_tracker.h",
         "src/trace_processor/importers/common/metadata_tracker.cc",
         "src/trace_processor/importers/common/metadata_tracker.h",
         "src/trace_processor/importers/common/process_tracker.cc",
@@ -1495,6 +1498,8 @@
         "src/trace_processor/importers/common/trace_parser.cc",
         "src/trace_processor/importers/common/track_tracker.cc",
         "src/trace_processor/importers/common/track_tracker.h",
+        "src/trace_processor/importers/common/virtual_memory_mapping.cc",
+        "src/trace_processor/importers/common/virtual_memory_mapping.h",
     ],
 )
 
@@ -2699,6 +2704,15 @@
     ],
 )
 
+# GN target: //src/trace_processor/util:build_id
+perfetto_filegroup(
+    name = "src_trace_processor_util_build_id",
+    srcs = [
+        "src/trace_processor/util/build_id.cc",
+        "src/trace_processor/util/build_id.h",
+    ],
+)
+
 # GN target: //src/trace_processor/util:bump_allocator
 perfetto_filegroup(
     name = "src_trace_processor_util_bump_allocator",
@@ -2818,15 +2832,6 @@
     ],
 )
 
-# GN target: //src/trace_processor/util:stack_traces_util
-perfetto_filegroup(
-    name = "src_trace_processor_util_stack_traces_util",
-    srcs = [
-        "src/trace_processor/util/stack_traces_util.cc",
-        "src/trace_processor/util/stack_traces_util.h",
-    ],
-)
-
 # GN target: //src/trace_processor/util:stdlib
 perfetto_filegroup(
     name = "src_trace_processor_util_stdlib",
@@ -5644,6 +5649,7 @@
         ":src_trace_processor_tables_tables",
         ":src_trace_processor_tables_tables_python",
         ":src_trace_processor_types_types",
+        ":src_trace_processor_util_build_id",
         ":src_trace_processor_util_bump_allocator",
         ":src_trace_processor_util_descriptors",
         ":src_trace_processor_util_glob",
@@ -5657,7 +5663,6 @@
         ":src_trace_processor_util_protozero_to_text",
         ":src_trace_processor_util_regex",
         ":src_trace_processor_util_sql_argument",
-        ":src_trace_processor_util_stack_traces_util",
         ":src_trace_processor_util_stdlib",
         ":src_trace_processor_util_util",
         ":src_trace_processor_util_zip_reader",
@@ -5815,6 +5820,7 @@
         ":src_trace_processor_tables_tables",
         ":src_trace_processor_tables_tables_python",
         ":src_trace_processor_types_types",
+        ":src_trace_processor_util_build_id",
         ":src_trace_processor_util_bump_allocator",
         ":src_trace_processor_util_descriptors",
         ":src_trace_processor_util_glob",
@@ -5828,7 +5834,6 @@
         ":src_trace_processor_util_protozero_to_text",
         ":src_trace_processor_util_regex",
         ":src_trace_processor_util_sql_argument",
-        ":src_trace_processor_util_stack_traces_util",
         ":src_trace_processor_util_stdlib",
         ":src_trace_processor_util_util",
         ":src_trace_processor_util_zip_reader",
@@ -5906,7 +5911,7 @@
         ":src_profiling_deobfuscator",
         ":src_profiling_symbolizer_symbolize_database",
         ":src_profiling_symbolizer_symbolizer",
-        ":src_trace_processor_util_stack_traces_util",
+        ":src_trace_processor_util_build_id",
         ":src_traceconv_pprofbuilder",
         ":src_traceconv_utils",
     ],
@@ -6038,6 +6043,7 @@
         ":src_trace_processor_tables_tables",
         ":src_trace_processor_tables_tables_python",
         ":src_trace_processor_types_types",
+        ":src_trace_processor_util_build_id",
         ":src_trace_processor_util_bump_allocator",
         ":src_trace_processor_util_descriptors",
         ":src_trace_processor_util_glob",
@@ -6051,7 +6057,6 @@
         ":src_trace_processor_util_protozero_to_text",
         ":src_trace_processor_util_regex",
         ":src_trace_processor_util_sql_argument",
-        ":src_trace_processor_util_stack_traces_util",
         ":src_trace_processor_util_stdlib",
         ":src_trace_processor_util_util",
         ":src_trace_processor_util_zip_reader",
diff --git a/src/profiling/symbolizer/BUILD.gn b/src/profiling/symbolizer/BUILD.gn
index 722d1cf..2eb2c93 100644
--- a/src/profiling/symbolizer/BUILD.gn
+++ b/src/profiling/symbolizer/BUILD.gn
@@ -49,7 +49,7 @@
       "../../../include/perfetto/trace_processor:trace_processor",
       "../../../protos/perfetto/trace:zero",
       "../../../protos/perfetto/trace/profiling:zero",
-      "../../trace_processor/util:stack_traces_util",
+      "../../trace_processor/util:build_id",
     ]
     sources = [
       "symbolize_database.cc",
diff --git a/src/profiling/symbolizer/symbolize_database.cc b/src/profiling/symbolizer/symbolize_database.cc
index 224874b..a008e1a 100644
--- a/src/profiling/symbolizer/symbolize_database.cc
+++ b/src/profiling/symbolizer/symbolize_database.cc
@@ -28,8 +28,7 @@
 #include "protos/perfetto/trace/profiling/profile_common.pbzero.h"
 #include "protos/perfetto/trace/trace.pbzero.h"
 #include "protos/perfetto/trace/trace_packet.pbzero.h"
-
-#include "src/trace_processor/util/stack_traces_util.h"
+#include "src/trace_processor/util/build_id.h"
 
 namespace perfetto {
 namespace profiling {
@@ -56,32 +55,6 @@
   }
 };
 
-std::string FromHex(const char* str, size_t size) {
-  if (size % 2) {
-    PERFETTO_DFATAL_OR_ELOG("Failed to parse hex %s", str);
-    return "";
-  }
-  std::string result(size / 2, '\0');
-  for (size_t i = 0; i < size; i += 2) {
-    char hex_byte[3];
-    hex_byte[0] = str[i];
-    hex_byte[1] = str[i + 1];
-    hex_byte[2] = '\0';
-    char* end;
-    long int byte = strtol(hex_byte, &end, 16);
-    if (*end != '\0') {
-      PERFETTO_DFATAL_OR_ELOG("Failed to parse hex %s", str);
-      return "";
-    }
-    result[i / 2] = static_cast<char>(byte);
-  }
-  return result;
-}
-
-std::string FromHex(const std::string& str) {
-  return FromHex(str.c_str(), str.size());
-}
-
 std::map<UnsymbolizedMapping, std::vector<uint64_t>> GetUnsymbolizedFrames(
     trace_processor::TraceProcessor* tp) {
   std::map<UnsymbolizedMapping, std::vector<uint64_t>> res;
@@ -89,17 +62,10 @@
   while (it.Next()) {
     int64_t load_bias = it.Get(3).AsLong();
     PERFETTO_CHECK(load_bias >= 0);
-    std::string build_id;
-    // TODO(b/148109467): Remove workaround once all active Chrome versions
-    // write raw bytes instead of a string as build_id.
-    std::string raw_build_id = it.Get(1).AsString();
-    if (!trace_processor::util::IsHexModuleId(base::StringView(raw_build_id))) {
-      build_id = FromHex(raw_build_id);
-    } else {
-      build_id = raw_build_id;
-    }
-    UnsymbolizedMapping unsymbolized_mapping{it.Get(0).AsString(), build_id,
-                                             static_cast<uint64_t>(load_bias)};
+    trace_processor::BuildId build_id =
+        trace_processor::BuildId::FromHex(it.Get(1).AsString());
+    UnsymbolizedMapping unsymbolized_mapping{
+        it.Get(0).AsString(), build_id.raw(), static_cast<uint64_t>(load_bias)};
     int64_t rel_pc = it.Get(2).AsLong();
     res[unsymbolized_mapping].emplace_back(rel_pc);
   }
diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn
index e81b184..e37c9c6 100644
--- a/src/trace_processor/BUILD.gn
+++ b/src/trace_processor/BUILD.gn
@@ -132,7 +132,6 @@
     "util:descriptors",
     "util:gzip",
     "util:proto_to_args_parser",
-    "util:stack_traces_util",
   ]
   public_deps = [ "../../include/perfetto/trace_processor:storage" ]
 }
diff --git a/src/trace_processor/importers/common/BUILD.gn b/src/trace_processor/importers/common/BUILD.gn
index 466876a..5108dca 100644
--- a/src/trace_processor/importers/common/BUILD.gn
+++ b/src/trace_processor/importers/common/BUILD.gn
@@ -28,6 +28,7 @@
     "clock_converter.h",
     "clock_tracker.cc",
     "clock_tracker.h",
+    "create_mapping_params.h",
     "deobfuscation_mapping_table.cc",
     "deobfuscation_mapping_table.h",
     "event_tracker.cc",
@@ -36,6 +37,8 @@
     "flow_tracker.h",
     "global_args_tracker.cc",
     "global_args_tracker.h",
+    "mapping_tracker.cc",
+    "mapping_tracker.h",
     "metadata_tracker.cc",
     "metadata_tracker.h",
     "process_tracker.cc",
@@ -56,6 +59,8 @@
     "trace_parser.cc",
     "track_tracker.cc",
     "track_tracker.h",
+    "virtual_memory_mapping.cc",
+    "virtual_memory_mapping.h",
   ]
   public_deps = [
     ":trace_parser_hdr",
@@ -75,8 +80,8 @@
     "../../storage",
     "../../tables:tables",
     "../../types",
+    "../../util:build_id",
     "../../util:profiler_util",
-    "../../util:stack_traces_util",
     "../fuchsia:fuchsia_record",
     "../systrace:systrace_line",
   ]
diff --git a/src/trace_processor/importers/common/create_mapping_params.h b/src/trace_processor/importers/common/create_mapping_params.h
new file mode 100644
index 0000000..7aba456
--- /dev/null
+++ b/src/trace_processor/importers/common/create_mapping_params.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2024 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_TRACE_PROCESSOR_IMPORTERS_COMMON_CREATE_MAPPING_PARAMS_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_CREATE_MAPPING_PARAMS_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <optional>
+#include <string>
+#include <tuple>
+
+#include "perfetto/ext/base/hash.h"
+#include "src/trace_processor/importers/common/address_range.h"
+#include "src/trace_processor/util/build_id.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+struct CreateMappingParams {
+  AddressRange memory_range;
+  // This is the offset into the file that has been mapped at
+  // memory_range.start()
+  uint64_t exact_offset = 0;
+  // This is the offset into the file where the ELF header starts. We assume
+  // all file mappings are ELF files an thus this offset is 0.
+  uint64_t start_offset = 0;
+  // This can only be read out of the actual ELF file.
+  uint64_t load_bias = 0;
+  std::string name;
+  std::optional<BuildId> build_id;
+
+  auto ToTuple() const {
+    return std::tie(memory_range, exact_offset, start_offset, load_bias, name,
+                    build_id);
+  }
+
+  bool operator==(const CreateMappingParams& o) const {
+    return ToTuple() == o.ToTuple();
+  }
+
+  struct Hasher {
+    size_t operator()(const CreateMappingParams& p) const {
+      base::Hasher h;
+      h.UpdateAll(p.memory_range.start(), p.memory_range.end(), p.exact_offset,
+                  p.start_offset, p.load_bias, p.name);
+      if (p.build_id) {
+        h.Update(*p.build_id);
+      }
+      return static_cast<size_t>(h.digest());
+    }
+  };
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_CREATE_MAPPING_PARAMS_H_
diff --git a/src/trace_processor/importers/common/mapping_tracker.cc b/src/trace_processor/importers/common/mapping_tracker.cc
new file mode 100644
index 0000000..13b8274
--- /dev/null
+++ b/src/trace_processor/importers/common/mapping_tracker.cc
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2024 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/trace_processor/importers/common/mapping_tracker.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <utility>
+
+#include "perfetto/ext/base/string_view.h"
+#include "src/trace_processor/importers/common/address_range.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/util/build_id.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace {
+
+bool IsKernelModule(base::StringView name) {
+  return !name.StartsWith("[kernel.kallsyms]");
+}
+
+}  // namespace
+
+JitDelegate::~JitDelegate() = default;
+
+template <typename MappingImpl>
+MappingImpl& MappingTracker::AddMapping(std::unique_ptr<MappingImpl> mapping) {
+  auto ptr = mapping.get();
+  PERFETTO_CHECK(
+      mappings_by_id_.Insert(ptr->mapping_id(), std::move(mapping)).second);
+
+  mappings_by_name_and_build_id_[NameAndBuildId{base::StringView(ptr->name()),
+                                                ptr->build_id()}]
+      .push_back(ptr);
+
+  return *ptr;
+}
+
+KernelMemoryMapping& MappingTracker::CreateKernelMemoryMapping(
+    CreateMappingParams params) {
+  // TODO(carlscab): Guess build_id if not provided. Some tools like simpleperf
+  // add a mapping file_name ->build_id that we could use here
+
+  const bool is_module = IsKernelModule(base::StringView(params.name));
+
+  if (!is_module && kernel_ != nullptr) {
+    PERFETTO_CHECK(params.memory_range == kernel_->memory_range());
+    return *kernel_;
+  }
+
+  std::unique_ptr<KernelMemoryMapping> mapping(
+      new KernelMemoryMapping(context_, std::move(params)));
+
+  if (is_module) {
+    // TODO(carlscab): Overlaps not supported (for now?). Should be fine for
+    // kernel.
+    PERFETTO_CHECK(
+        kernel_modules_.Emplace(mapping->memory_range(), mapping.get()));
+  } else {
+    kernel_ = mapping.get();
+  }
+
+  return AddMapping(std::move(mapping));
+}
+
+UserMemoryMapping& MappingTracker::CreateUserMemoryMapping(
+    UniquePid upid,
+    CreateMappingParams params) {
+  // TODO(carlscab): Guess build_id if not provided. Some tools like simpleperf
+  // add a mapping file_name ->build_id that we could use here
+
+  const AddressRange mapping_range = params.memory_range;
+  std::unique_ptr<UserMemoryMapping> mapping(
+      new UserMemoryMapping(context_, upid, std::move(params)));
+  // TODO(carlscab): Overlaps not supported (for now?).
+  PERFETTO_CHECK(user_memory_[upid].Emplace(mapping_range, mapping.get()));
+
+  jit_delegates_[upid].ForOverlaps(
+      mapping_range, [&](std::pair<const AddressRange, JitDelegate*>& entry) {
+        const auto& jit_range = entry.first;
+        JitDelegate* jit_delegate = entry.second;
+        PERFETTO_CHECK(jit_range.Contains(mapping_range));
+        mapping->SetJitDelegate(jit_delegate);
+      });
+
+  return AddMapping(std::move(mapping));
+}
+
+KernelMemoryMapping* MappingTracker::FindKernelMappingForAddress(
+    uint64_t address) const {
+  if (auto it = kernel_modules_.Find(address); it != kernel_modules_.end()) {
+    return it->second;
+  }
+  if (kernel_ && kernel_->memory_range().Contains(address)) {
+    return kernel_;
+  }
+  return nullptr;
+}
+
+UserMemoryMapping* MappingTracker::FindUserMappingForAddress(
+    UniquePid upid,
+    uint64_t address) const {
+  if (auto* vm = user_memory_.Find(upid); vm) {
+    if (auto it = vm->Find(address); it != vm->end()) {
+      return it->second;
+    }
+  }
+
+  if (auto* delegates = jit_delegates_.Find(upid); delegates) {
+    if (auto it = delegates->Find(address); it != delegates->end()) {
+      return it->second->CreateMapping();
+    }
+  }
+
+  return nullptr;
+}
+
+std::vector<VirtualMemoryMapping*> MappingTracker::FindMappings(
+    base::StringView name,
+    const BuildId& build_id) const {
+  if (auto res = mappings_by_name_and_build_id_.Find({name, build_id});
+      res != nullptr) {
+    return *res;
+  }
+  return {};
+}
+
+VirtualMemoryMapping& MappingTracker::InternMemoryMapping(
+    CreateMappingParams params) {
+  if (auto* mapping = interned_mappings_.Find(params); mapping) {
+    return **mapping;
+  }
+
+  std::unique_ptr<VirtualMemoryMapping> mapping(
+      new VirtualMemoryMapping(context_, params));
+  interned_mappings_.Insert(std::move(params), mapping.get());
+  return AddMapping(std::move(mapping));
+}
+
+void MappingTracker::AddJitRange(UniquePid upid,
+                                 AddressRange jit_range,
+                                 JitDelegate* delegate) {
+  // TODO(carlscab): Deal with overlaps
+  jit_delegates_[upid].DeleteOverlapsAndEmplace(jit_range, delegate);
+  user_memory_[upid].ForOverlaps(
+      jit_range, [&](std::pair<const AddressRange, UserMemoryMapping*>& entry) {
+        PERFETTO_CHECK(jit_range.Contains(entry.first));
+        entry.second->SetJitDelegate(delegate);
+      });
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/common/mapping_tracker.h b/src/trace_processor/importers/common/mapping_tracker.h
new file mode 100644
index 0000000..95dc355
--- /dev/null
+++ b/src/trace_processor/importers/common/mapping_tracker.h
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2024 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_TRACE_PROCESSOR_IMPORTERS_COMMON_MAPPING_TRACKER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_MAPPING_TRACKER_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <optional>
+#include <vector>
+
+#include "perfetto/ext/base/flat_hash_map.h"
+#include "perfetto/ext/base/hash.h"
+#include "perfetto/ext/base/string_view.h"
+#include "src/trace_processor/importers/common/address_range.h"
+#include "src/trace_processor/importers/common/virtual_memory_mapping.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/util/build_id.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+// Api used to forward frame interning requests for frames that fall in a
+// jitted memory region.
+// MappingTracker allows other trackers to register ranges of memory for
+// which they need to control when a new frame is created. Jitted code can
+// move in memory over time, so the same program counter might refer to
+// different functions at different point in time. MappingTracker does
+// not keep track of such moves but instead delegates the creation of jitted
+// frames to a delegate.
+class JitDelegate {
+ public:
+  virtual ~JitDelegate();
+  // Forward frame interning request.
+  // Implementations are free to intern the frame as needed.
+  // Returns frame_id, and whether a new row as created or not.
+  virtual std::pair<FrameId, bool> InternFrame(
+      VirtualMemoryMapping* mapping,
+      uint64_t rel_pc,
+      base::StringView function_name) = 0;
+
+  // Simpleperf does not emit mmap events for jitted ranges (actually for non
+  // file backed executable mappings). So have a way to generate a mapping on
+  // the fly for FindMapping requests in a jitted region with no associated
+  // mapping.
+  virtual UserMemoryMapping* CreateMapping() = 0;
+};
+
+// Keeps track of all aspects relative to memory mappings.
+// This class keeps track of 3 types of mappings: UserMemoryMapping,
+// KernelMemoryMapping and others. The others are used to represent mapping
+// where we do not have enough information to determine what type of
+// mapping (user, kernel) we are dealing with. This is usually the case with
+// data sources that do not provide enough information about the mappings.
+//
+// TODO(carlscab): Hopefully we can slowly get rid of cases where these other
+// mappings are needed. The biggest blocker right now is determining the upid.
+// we could infer this from the actual samples that use said mapping (those
+// usually have a pid attached). So we would need to have a "fake" mapping that
+// actually materializes when we see a sample with a pid.
+//
+// ATTENTION: No overlaps allowed (for now). Eventually the order in which
+// mappings are create will matter as newer mappings will delete old ones.
+// This is how tools like linux perf behave, mmap event have a timestamp
+// associated and there are no "delete events" just new mmap events that
+// overlap (to be deleted) mappings.
+class MappingTracker {
+ public:
+  explicit MappingTracker(TraceProcessorContext* context) : context_(context) {}
+
+  // Create a new kernel space mapping. Returned reference will be valid for the
+  // duration of this instance.
+  KernelMemoryMapping& CreateKernelMemoryMapping(CreateMappingParams params);
+
+  // Create a new user space mapping. Returned reference will be valid for the
+  // duration of this instance.
+  UserMemoryMapping& CreateUserMemoryMapping(UniquePid upid,
+                                             CreateMappingParams params);
+
+  // Create an "other" mapping. Returned reference will be valid for the
+  // duration of this instance.
+  VirtualMemoryMapping& InternMemoryMapping(CreateMappingParams params);
+
+  // Given an absolute address find the kernel mapping where this address
+  // belongs to. Returns `nullptr` if none is found.
+  KernelMemoryMapping* FindKernelMappingForAddress(uint64_t address) const;
+
+  // Given an absolute address find the user mapping where this address
+  // belongs to. Returns `nullptr` if none is found.
+  UserMemoryMapping* FindUserMappingForAddress(UniquePid upid,
+                                               uint64_t address) const;
+
+  std::vector<VirtualMemoryMapping*> FindMappings(
+      base::StringView name,
+      const BuildId& build_id) const;
+
+  // Marks a range of memory as containing jitted code.
+  // If the added region overlaps with other existing ranges the latter are all
+  // deleted.
+  // Jitted ranges will only be applied to UserMemoryMappings
+  void AddJitRange(UniquePid upid, AddressRange range, JitDelegate* delegate);
+
+ private:
+  template <typename MappingImpl>
+  MappingImpl& AddMapping(std::unique_ptr<MappingImpl> mapping);
+
+  TraceProcessorContext* const context_;
+  base::FlatHashMap<MappingId, std::unique_ptr<VirtualMemoryMapping>>
+      mappings_by_id_;
+
+  base::FlatHashMap<CreateMappingParams,
+                    VirtualMemoryMapping*,
+                    CreateMappingParams::Hasher>
+      interned_mappings_;
+
+  struct NameAndBuildId {
+    base::StringView name;
+    std::optional<BuildId> build_id;
+
+    bool operator==(const NameAndBuildId& o) const {
+      return name == o.name && build_id == o.build_id;
+    }
+
+    bool operator!=(const NameAndBuildId& o) const { return !(*this == o); }
+
+    struct Hasher {
+      size_t operator()(const NameAndBuildId& o) const {
+        base::Hasher hasher;
+        hasher.Update(o.name);
+        if (o.build_id) {
+          hasher.Update(*o.build_id);
+        }
+        return static_cast<size_t>(hasher.digest());
+      }
+    };
+  };
+  base::FlatHashMap<NameAndBuildId,
+                    std::vector<VirtualMemoryMapping*>,
+                    NameAndBuildId::Hasher>
+      mappings_by_name_and_build_id_;
+
+  base::FlatHashMap<UniquePid, AddressRangeMap<UserMemoryMapping*>>
+      user_memory_;
+  AddressRangeMap<KernelMemoryMapping*> kernel_modules_;
+  KernelMemoryMapping* kernel_ = nullptr;
+
+  base::FlatHashMap<UniquePid, AddressRangeMap<JitDelegate*>> jit_delegates_;
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_MAPPING_TRACKER_H_
diff --git a/src/trace_processor/importers/common/stack_profile_tracker.cc b/src/trace_processor/importers/common/stack_profile_tracker.cc
index ad57523..799dc29 100644
--- a/src/trace_processor/importers/common/stack_profile_tracker.cc
+++ b/src/trace_processor/importers/common/stack_profile_tracker.cc
@@ -16,35 +16,18 @@
 
 #include "src/trace_processor/importers/common/stack_profile_tracker.h"
 
-#include "perfetto/ext/base/string_utils.h"
+#include <cstddef>
+#include <cstdint>
+
 #include "perfetto/ext/base/string_view.h"
 #include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/tables/profiler_tables_py.h"
 #include "src/trace_processor/types/trace_processor_context.h"
 #include "src/trace_processor/util/profiler_util.h"
-#include "src/trace_processor/util/stack_traces_util.h"
 
 namespace perfetto {
 namespace trace_processor {
 
-namespace {
-std::string CleanBuildId(base::StringView build_id) {
-  if (build_id.empty()) {
-    return build_id.ToStdString();
-  }
-  // If the build_id is 33 characters long, we assume it's a Breakpad debug
-  // identifier which is already in Hex and doesn't need conversion.
-  // TODO(b/148109467): Remove workaround once all active Chrome versions
-  // write raw bytes instead of a string as build_id.
-  if (util::IsHexModuleId(build_id)) {
-    return build_id.ToStdString();
-  }
-
-  return base::ToHex(build_id.data(), build_id.size());
-}
-
-}  // namespace
-
 std::vector<FrameId> StackProfileTracker::JavaFramesForName(
     NameInPackage name) const {
   if (const auto* frames = java_frames_for_name_.Find(name); frames) {
@@ -53,50 +36,6 @@
   return {};
 }
 
-std::vector<MappingId> StackProfileTracker::FindMappingRow(
-    StringId name,
-    StringId build_id) const {
-  if (const auto* mappings =
-          mappings_by_name_and_build_id_.Find(std::make_pair(name, build_id));
-      mappings) {
-    return *mappings;
-  }
-  return {};
-}
-
-std::vector<FrameId> StackProfileTracker::FindFrameIds(MappingId mapping_id,
-                                                       uint64_t rel_pc) const {
-  if (const auto* frames =
-          frame_by_mapping_and_rel_pc_.Find(std::make_pair(mapping_id, rel_pc));
-      frames) {
-    return *frames;
-  }
-  return {};
-}
-
-MappingId StackProfileTracker::InternMapping(
-    const CreateMappingParams& params) {
-  tables::StackProfileMappingTable::Row row;
-  row.build_id = InternBuildId(params.build_id);
-  row.exact_offset = static_cast<int64_t>(params.exact_offset);
-  row.start_offset = static_cast<int64_t>(params.start_offset);
-  row.start = static_cast<int64_t>(params.start);
-  row.end = static_cast<int64_t>(params.end);
-  row.load_bias = static_cast<int64_t>(params.load_bias);
-  row.name = context_->storage->InternString(params.name);
-
-  if (MappingId* id = mapping_unique_row_index_.Find(row); id) {
-    return *id;
-  }
-
-  MappingId mapping_id =
-      context_->storage->mutable_stack_profile_mapping_table()->Insert(row).id;
-  mapping_unique_row_index_.Insert(row, mapping_id);
-  mappings_by_name_and_build_id_[{row.name, row.build_id}].push_back(
-      mapping_id);
-  return mapping_id;
-}
-
 CallsiteId StackProfileTracker::InternCallsite(
     std::optional<CallsiteId> parent_callsite_id,
     FrameId frame_id,
@@ -113,22 +52,12 @@
   return callsite_id;
 }
 
-FrameId StackProfileTracker::InternFrame(MappingId mapping_id,
-                                         uint64_t rel_pc,
-                                         base::StringView function_name) {
-  tables::StackProfileFrameTable::Row row;
-  row.mapping = mapping_id;
-  row.rel_pc = static_cast<int64_t>(rel_pc);
-  row.name = context_->storage->InternString(function_name);
-
-  if (FrameId* id = frame_unique_row_index_.Find(row); id) {
-    return *id;
-  }
-
-  FrameId frame_id =
-      context_->storage->mutable_stack_profile_frame_table()->Insert(row).id;
-  frame_unique_row_index_.Insert(row, frame_id);
-  frame_by_mapping_and_rel_pc_[{mapping_id, rel_pc}].push_back(frame_id);
+void StackProfileTracker::OnFrameCreated(FrameId frame_id) {
+  auto frame =
+      *context_->storage->stack_profile_frame_table().FindById(frame_id);
+  const MappingId mapping_id = frame.mapping();
+  const StringId name_id = frame.name();
+  const auto function_name = context_->storage->GetString(name_id);
 
   if (function_name.find('.') != base::StringView::npos) {
     // Java frames always contain a '.'
@@ -139,21 +68,14 @@
     std::optional<std::string> package =
         PackageFromLocation(context_->storage.get(), mapping_name);
     if (package) {
-      NameInPackage nip{row.name, context_->storage->InternString(
-                                      base::StringView(*package))};
+      NameInPackage nip{
+          name_id, context_->storage->InternString(base::StringView(*package))};
       java_frames_for_name_[nip].push_back(frame_id);
     } else if (mapping_name.find("/memfd:") == 0) {
-      NameInPackage nip{row.name, context_->storage->InternString("memfd")};
+      NameInPackage nip{name_id, context_->storage->InternString("memfd")};
       java_frames_for_name_[nip].push_back(frame_id);
     }
   }
-
-  return frame_id;
-}
-
-StringId StackProfileTracker::InternBuildId(base::StringView build_id) {
-  return context_->storage->InternString(
-      base::StringView(CleanBuildId(build_id)));
 }
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/importers/common/stack_profile_tracker.h b/src/trace_processor/importers/common/stack_profile_tracker.h
index a1067b8..b018f74 100644
--- a/src/trace_processor/importers/common/stack_profile_tracker.h
+++ b/src/trace_processor/importers/common/stack_profile_tracker.h
@@ -20,12 +20,11 @@
 #include <cstdint>
 #include <optional>
 #include <tuple>
-#include <utility>
 #include <vector>
 
 #include "perfetto/ext/base/flat_hash_map.h"
 #include "perfetto/ext/base/hash.h"
-#include "perfetto/ext/base/string_view.h"
+
 #include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/tables/profiler_tables_py.h"
 
@@ -52,64 +51,39 @@
 
 class StackProfileTracker {
  public:
-  struct CreateMappingParams {
-    base::StringView build_id;
-    uint64_t exact_offset;
-    uint64_t start_offset;
-    uint64_t start;
-    uint64_t end;
-    uint64_t load_bias;
-    base::StringView name;
-  };
-
   explicit StackProfileTracker(TraceProcessorContext* context)
       : context_(context) {}
 
   std::vector<FrameId> JavaFramesForName(NameInPackage name) const;
-  std::vector<MappingId> FindMappingRow(StringId name, StringId build_id) const;
-  std::vector<FrameId> FindFrameIds(MappingId mapping_id,
-                                    uint64_t rel_pc) const;
 
-  MappingId InternMapping(const CreateMappingParams& params);
   CallsiteId InternCallsite(std::optional<CallsiteId> parent_callsite_id,
                             FrameId frame_id,
                             uint32_t depth);
-  FrameId InternFrame(MappingId mapping_id,
-                      uint64_t rel_pc,
-                      base::StringView function_name);
+
+  void OnFrameCreated(FrameId frame_id);
 
  private:
-  StringId InternBuildId(base::StringView build_id);
-
   TraceProcessorContext* const context_;
-  base::FlatHashMap<tables::StackProfileMappingTable::Row, MappingId>
-      mapping_unique_row_index_;
   base::FlatHashMap<tables::StackProfileCallsiteTable::Row, CallsiteId>
       callsite_unique_row_index_;
-  base::FlatHashMap<tables::StackProfileFrameTable::Row, FrameId>
-      frame_unique_row_index_;
 
-  struct MappingHasher {
-    size_t operator()(const std::pair<StringId, StringId>& o) const {
-      return static_cast<size_t>(
-          base::Hasher::Combine(o.first.raw_id(), o.second.raw_id()));
-    }
-  };
-  base::FlatHashMap<std::pair<StringId, StringId>,
-                    std::vector<MappingId>,
-                    MappingHasher>
-      mappings_by_name_and_build_id_;
+  struct FrameKey {
+    MappingId mapping_id;
+    uint64_t rel_pc;
 
-  struct FrameHasher {
-    size_t operator()(const std::pair<MappingId, uint64_t>& o) const {
-      return static_cast<size_t>(
-          base::Hasher::Combine(o.first.value, o.second));
+    bool operator==(const FrameKey& o) const {
+      return mapping_id == o.mapping_id && rel_pc == o.rel_pc;
     }
+
+    bool operator!=(const FrameKey& o) const { return !(*this == o); }
+
+    struct Hasher {
+      size_t operator()(const FrameKey& o) const {
+        return static_cast<size_t>(
+            base::Hasher::Combine(o.mapping_id.value, o.rel_pc));
+      }
+    };
   };
-  base::FlatHashMap<std::pair<MappingId, uint64_t>,
-                    std::vector<FrameId>,
-                    FrameHasher>
-      frame_by_mapping_and_rel_pc_;
 
   base::FlatHashMap<NameInPackage, std::vector<FrameId>, NameInPackage::Hasher>
       java_frames_for_name_;
diff --git a/src/trace_processor/importers/common/virtual_memory_mapping.cc b/src/trace_processor/importers/common/virtual_memory_mapping.cc
new file mode 100644
index 0000000..60166f59
--- /dev/null
+++ b/src/trace_processor/importers/common/virtual_memory_mapping.cc
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2024 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/trace_processor/importers/common/virtual_memory_mapping.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <optional>
+#include <string>
+#include <utility>
+
+#include "perfetto/ext/base/string_view.h"
+#include "src/trace_processor/importers/common/address_range.h"
+#include "src/trace_processor/importers/common/mapping_tracker.h"
+#include "src/trace_processor/importers/common/stack_profile_tracker.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/tables/profiler_tables_py.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/util/build_id.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace {
+
+MappingId CreateMapping(TraceProcessorContext* context,
+                        const CreateMappingParams& params) {
+  StringId build_id = context->storage->InternString(base::StringView(
+      params.build_id ? params.build_id->ToHex() : std::string()));
+  MappingId mapping_id =
+      context->storage->mutable_stack_profile_mapping_table()
+          ->Insert(
+              {build_id, static_cast<int64_t>(params.exact_offset),
+               static_cast<int64_t>(params.start_offset),
+               static_cast<int64_t>(params.memory_range.start()),
+               static_cast<int64_t>(params.memory_range.end()),
+               static_cast<int64_t>(params.load_bias),
+               context->storage->InternString(base::StringView(params.name))})
+          .id;
+
+  return mapping_id;
+}
+
+}  // namespace
+
+VirtualMemoryMapping::VirtualMemoryMapping(TraceProcessorContext* context,
+                                           CreateMappingParams params)
+    : context_(context),
+      mapping_id_(CreateMapping(context, params)),
+      memory_range_(params.memory_range),
+      offset_(params.exact_offset),
+      load_bias_(params.load_bias),
+      name_(std::move(params.name)),
+      build_id_(std::move(params.build_id)) {}
+
+VirtualMemoryMapping::~VirtualMemoryMapping() = default;
+
+KernelMemoryMapping::KernelMemoryMapping(TraceProcessorContext* context,
+                                         CreateMappingParams params)
+    : VirtualMemoryMapping(context, std::move(params)) {}
+
+KernelMemoryMapping::~KernelMemoryMapping() = default;
+
+UserMemoryMapping::UserMemoryMapping(TraceProcessorContext* context,
+                                     UniquePid upid,
+                                     CreateMappingParams params)
+    : VirtualMemoryMapping(context, std::move(params)), upid_(upid) {}
+
+UserMemoryMapping::~UserMemoryMapping() = default;
+
+FrameId VirtualMemoryMapping::InternFrame(uint64_t rel_pc,
+                                          base::StringView function_name) {
+  auto [frame_id, was_inserted] =
+      jit_delegate_ ? jit_delegate_->InternFrame(this, rel_pc, function_name)
+                    : InternFrameImpl(rel_pc, function_name);
+  if (was_inserted) {
+    frames_by_rel_pc_[rel_pc].push_back(frame_id);
+    context_->stack_profile_tracker->OnFrameCreated(frame_id);
+  }
+  return frame_id;
+}
+
+std::vector<FrameId> VirtualMemoryMapping::FindFrameIds(uint64_t rel_pc) const {
+  if (auto* res = frames_by_rel_pc_.Find(rel_pc); res != nullptr) {
+    return *res;
+  }
+  return {};
+}
+
+std::pair<FrameId, bool> VirtualMemoryMapping::InternFrameImpl(
+    uint64_t rel_pc,
+    base::StringView function_name) {
+  const FrameKey frame_key{rel_pc,
+                           context_->storage->InternString(function_name)};
+  if (FrameId* id = interned_frames_.Find(frame_key); id) {
+    return {*id, false};
+  }
+
+  const FrameId frame_id =
+      context_->storage->mutable_stack_profile_frame_table()
+          ->Insert(
+              {frame_key.name_id, mapping_id_, static_cast<int64_t>(rel_pc)})
+          .id;
+  interned_frames_.Insert(frame_key, frame_id);
+
+  return {frame_id, true};
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/common/virtual_memory_mapping.h b/src/trace_processor/importers/common/virtual_memory_mapping.h
new file mode 100644
index 0000000..7b8ef58
--- /dev/null
+++ b/src/trace_processor/importers/common/virtual_memory_mapping.h
@@ -0,0 +1,152 @@
+
+/*
+ * Copyright (C) 2024 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_TRACE_PROCESSOR_IMPORTERS_COMMON_VIRTUAL_MEMORY_MAPPING_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_VIRTUAL_MEMORY_MAPPING_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <optional>
+#include <string>
+#include <vector>
+
+#include "perfetto/ext/base/flat_hash_map.h"
+#include "perfetto/ext/base/hash.h"
+#include "perfetto/ext/base/string_view.h"
+#include "src/trace_processor/importers/common/address_range.h"
+#include "src/trace_processor/importers/common/create_mapping_params.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/util/build_id.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+// TODO(carlscab): Reconsider whether jit is the best abstraction here. All we
+// really care is about mapping a `rel_pc` to a symbol (aka symbolization) and
+// whether is this is constant.
+class JitDelegate;
+
+// Represents a mapping in virtual memory.
+class VirtualMemoryMapping {
+ public:
+  virtual ~VirtualMemoryMapping();
+  // Range of virtual memory this mapping covers.
+  AddressRange memory_range() const { return memory_range_; }
+  MappingId mapping_id() const { return mapping_id_; }
+  // This name could be the path of the underlying file mapped into memory.
+  const std::string& name() const { return name_; }
+  // For file mappings, this is the offset into the file for the first byte in
+  // the mapping
+  uint64_t offset() const { return offset_; }
+  // If the mapped file is an executable or shared library this will return the
+  // load bias, if known. Returns 0 otherwise.
+  uint64_t load_bias() const { return load_bias_; }
+  // If the mapped file is an executable or shared library this will return its
+  // build id, if known.
+  const std::optional<BuildId>& build_id() const { return build_id_; }
+
+  // Whether this maps to a region that holds jitted code.
+  bool is_jitted() const { return jit_delegate_ != nullptr; }
+
+  // Converts an absolute address into a relative one.
+  uint64_t ToRelativePc(uint64_t address) const {
+    return address - memory_range_.start() + offset_ + load_bias_;
+  }
+
+  // Creates a frame for the given `rel_pc`. Note that if the mapping
+  // `is_jitted()` same `rel_pc` values can return different mappings (as jitted
+  // functions can be created and deleted over time.) So for such mappings the
+  // returned `FrameId` should not be cached.
+  FrameId InternFrame(uint64_t rel_pc, base::StringView function_name);
+
+  // Returns all frames ever created in this mapping for the given `rel_pc`.
+  std::vector<FrameId> FindFrameIds(uint64_t rel_pc) const;
+
+ protected:
+  VirtualMemoryMapping(TraceProcessorContext* context,
+                       CreateMappingParams params);
+
+ private:
+  friend class MappingTracker;
+
+  std::pair<FrameId, bool> InternFrameImpl(uint64_t rel_pc,
+                                           base::StringView function_name);
+
+  void SetJitDelegate(JitDelegate* jit_delegate) {
+    jit_delegate_ = jit_delegate;
+  }
+
+  TraceProcessorContext* const context_;
+  const MappingId mapping_id_;
+  const AddressRange memory_range_;
+  const uint64_t offset_;
+  const uint64_t load_bias_;
+  const std::string name_;
+  std::optional<BuildId> const build_id_;
+  JitDelegate* jit_delegate_ = nullptr;
+
+  struct FrameKey {
+    uint64_t rel_pc;
+    // It doesn't seem to make too much sense to key on name, as for the same
+    // mapping and same rel_pc the name should always be the same. But who knows
+    // how producers behave.
+    StringId name_id;
+
+    bool operator==(const FrameKey& o) const {
+      return rel_pc == o.rel_pc && name_id == o.name_id;
+    }
+
+    struct Hasher {
+      size_t operator()(const FrameKey& k) const {
+        return static_cast<size_t>(
+            base::Hasher::Combine(k.rel_pc, k.name_id.raw_id()));
+      }
+    };
+  };
+  base::FlatHashMap<FrameKey, FrameId, FrameKey::Hasher> interned_frames_;
+  base::FlatHashMap<uint64_t, std::vector<FrameId>> frames_by_rel_pc_;
+};
+
+class KernelMemoryMapping : public VirtualMemoryMapping {
+ public:
+  ~KernelMemoryMapping() override;
+
+ private:
+  friend class MappingTracker;
+  KernelMemoryMapping(TraceProcessorContext* context,
+                      CreateMappingParams params);
+};
+
+class UserMemoryMapping : public VirtualMemoryMapping {
+ public:
+  ~UserMemoryMapping() override;
+  UniquePid upid() const { return upid_; }
+
+ private:
+  friend class MappingTracker;
+  UserMemoryMapping(TraceProcessorContext* context,
+                    UniquePid upid,
+                    CreateMappingParams params);
+
+  const UniquePid upid_;
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_VIRTUAL_MEMORY_MAPPING_H_
diff --git a/src/trace_processor/importers/perf/BUILD.gn b/src/trace_processor/importers/perf/BUILD.gn
index 387468f..960cd27 100644
--- a/src/trace_processor/importers/perf/BUILD.gn
+++ b/src/trace_processor/importers/perf/BUILD.gn
@@ -28,6 +28,7 @@
   ]
   deps = [
     "../../../../gn:default_deps",
+    "../../../../protos/perfetto/trace/profiling:zero",
     "../../importers/common",
     "../../importers/common:parser_types",
     "../../sorter",
@@ -47,6 +48,8 @@
     ":perf",
     "../../../../gn:default_deps",
     "../../../../gn:gtest_and_gmock",
+    "../../../../protos/perfetto/trace/profiling:zero",
     "../../../base",
+    "../../importers/common",
   ]
 }
diff --git a/src/trace_processor/importers/perf/perf_data_parser.cc b/src/trace_processor/importers/perf/perf_data_parser.cc
index 11a5a13..bb79209 100644
--- a/src/trace_processor/importers/perf/perf_data_parser.cc
+++ b/src/trace_processor/importers/perf/perf_data_parser.cc
@@ -22,6 +22,7 @@
 #include "perfetto/base/logging.h"
 #include "perfetto/ext/base/string_utils.h"
 #include "perfetto/trace_processor/trace_blob_view.h"
+#include "src/trace_processor/importers/common/mapping_tracker.h"
 #include "src/trace_processor/importers/common/process_tracker.h"
 #include "src/trace_processor/importers/perf/perf_data_reader.h"
 #include "src/trace_processor/importers/perf/perf_data_tracker.h"
@@ -58,7 +59,8 @@
 
   // First instruction pointer in the callchain should be from kernel space, so
   // it shouldn't be available in mappings.
-  if (tracker_->FindMapping(*sample.pid, sample.callchain.front()).ok()) {
+  if (context_->mapping_tracker->FindUserMappingForAddress(
+          *sample.pid, sample.callchain.front())) {
     context_->storage->IncrementStats(stats::perf_samples_skipped);
     return;
   }
@@ -70,19 +72,22 @@
 
   std::vector<FramesTable::Row> frame_rows;
   for (uint32_t i = 1; i < sample.callchain.size(); i++) {
-    auto mapping = tracker_->FindMapping(*sample.pid, sample.callchain[i]);
-    if (!mapping.ok()) {
+    UserMemoryMapping* mapping =
+        context_->mapping_tracker->FindUserMappingForAddress(
+            *sample.pid, sample.callchain[i]);
+    if (!mapping) {
       context_->storage->IncrementStats(stats::perf_samples_skipped);
       return;
     }
     FramesTable::Row new_row;
     std::string mock_name =
-        base::StackString<1024>("%" PRIu64,
-                                sample.callchain[i] - mapping->start)
+        base::StackString<1024>(
+            "%" PRIu64, sample.callchain[i] - mapping->memory_range().start())
             .ToStdString();
     new_row.name = context_->storage->InternString(mock_name.c_str());
-    new_row.mapping = mapping->id;
-    new_row.rel_pc = static_cast<int64_t>(sample.callchain[i] - mapping->start);
+    new_row.mapping = mapping->mapping_id();
+    new_row.rel_pc =
+        static_cast<int64_t>(mapping->ToRelativePc(sample.callchain[i]));
     frame_rows.push_back(new_row);
   }
 
diff --git a/src/trace_processor/importers/perf/perf_data_tokenizer.cc b/src/trace_processor/importers/perf/perf_data_tokenizer.cc
index be1ec02..334148a 100644
--- a/src/trace_processor/importers/perf/perf_data_tokenizer.cc
+++ b/src/trace_processor/importers/perf/perf_data_tokenizer.cc
@@ -15,6 +15,7 @@
  */
 
 #include "src/trace_processor/importers/perf/perf_data_tokenizer.h"
+
 #include <cstdint>
 #include <cstring>
 #include <vector>
@@ -31,9 +32,29 @@
 #include "src/trace_processor/storage/stats.h"
 #include "src/trace_processor/util/status_macros.h"
 
+#include "protos/perfetto/trace/profiling/profile_packet.pbzero.h"
+
 namespace perfetto {
 namespace trace_processor {
 namespace perf_importer {
+namespace {
+protos::pbzero::Profiling::CpuMode GetCpuMode(const perf_event_header& header) {
+  switch (header.misc & kPerfRecordMiscCpumodeMask) {
+    case PERF_RECORD_MISC_KERNEL:
+      return protos::pbzero::Profiling::MODE_KERNEL;
+    case PERF_RECORD_MISC_USER:
+      return protos::pbzero::Profiling::MODE_USER;
+    case PERF_RECORD_MISC_HYPERVISOR:
+      return protos::pbzero::Profiling::MODE_HYPERVISOR;
+    case PERF_RECORD_MISC_GUEST_KERNEL:
+      return protos::pbzero::Profiling::MODE_GUEST_KERNEL;
+    case PERF_RECORD_MISC_GUEST_USER:
+      return protos::pbzero::Profiling::MODE_GUEST_USER;
+    default:
+      return protos::pbzero::Profiling::MODE_UNKNOWN;
+  }
+}
+}  // namespace
 
 PerfDataTokenizer::PerfDataTokenizer(TraceProcessorContext* ctx)
     : context_(ctx),
@@ -124,6 +145,7 @@
                        sizeof(PerfDataTracker::Mmap2Record::Numeric));
         auto record = ParseMmap2Record(record_size);
         RETURN_IF_ERROR(record.status());
+        record->cpu_mode = GetCpuMode(ev_header);
         tracker_->PushMmap2Record(*record);
         break;
       }
diff --git a/src/trace_processor/importers/perf/perf_data_tracker.cc b/src/trace_processor/importers/perf/perf_data_tracker.cc
index 0c9b209..c670258 100644
--- a/src/trace_processor/importers/perf/perf_data_tracker.cc
+++ b/src/trace_processor/importers/perf/perf_data_tracker.cc
@@ -15,12 +15,53 @@
  */
 
 #include "src/trace_processor/importers/perf/perf_data_tracker.h"
+
+#include <optional>
+
 #include "perfetto/base/status.h"
+#include "src/trace_processor/importers/common/address_range.h"
+#include "src/trace_processor/importers/common/mapping_tracker.h"
+#include "src/trace_processor/importers/common/process_tracker.h"
 #include "src/trace_processor/storage/stats.h"
+#include "src/trace_processor/storage/trace_storage.h"
+
+#include "protos/perfetto/trace/profiling/profile_packet.pbzero.h"
 
 namespace perfetto {
 namespace trace_processor {
 namespace perf_importer {
+namespace {
+
+bool IsInKernel(protos::pbzero::Profiling::CpuMode cpu_mode) {
+  switch (cpu_mode) {
+    case protos::pbzero::Profiling::MODE_UNKNOWN:
+      PERFETTO_CHECK(false);
+    case protos::pbzero::Profiling::MODE_GUEST_KERNEL:
+    case protos::pbzero::Profiling::MODE_KERNEL:
+      return true;
+    case protos::pbzero::Profiling::MODE_USER:
+    case protos::pbzero::Profiling::MODE_HYPERVISOR:
+    case protos::pbzero::Profiling::MODE_GUEST_USER:
+      return false;
+  }
+  PERFETTO_CHECK(false);
+}
+
+CreateMappingParams BuildCreateMappingParams(
+    PerfDataTracker::Mmap2Record record) {
+  return {AddressRange::FromStartAndSize(record.num.addr, record.num.len),
+          record.num.pgoff,
+          // start_offset: This is the offset into the file where the ELF header
+          // starts. We assume all file mappings are ELF files an thus this
+          // offset is 0.
+          0,
+          // load_bias: This can only be read out of the actual ELF file, which
+          // we do not have here, so we set it to 0. When symbolizing we will
+          // hopefully have the real load bias and we can compensate there for a
+          // possible mismatch.
+          0, record.filename, std::nullopt};
+}
+}  // namespace
 
 PerfDataTracker::~PerfDataTracker() = default;
 
@@ -48,31 +89,15 @@
 }
 
 void PerfDataTracker::PushMmap2Record(Mmap2Record record) {
-  const auto mappings =
-      context_->storage->mutable_stack_profile_mapping_table();
-  MappingTable::Row row;
-  row.start = static_cast<int64_t>(record.num.addr);
-  row.end = static_cast<int64_t>(record.num.addr + record.num.len);
-  row.name = context_->storage->InternString(record.filename.c_str());
-  MappingTable::Id id = mappings->Insert(row).id;
-  MmapRange mmap2_range{record.num.addr, record.num.addr + record.num.len, id};
-  mmap2_ranges_[record.num.pid].push_back(mmap2_range);
-}
-
-base::StatusOr<PerfDataTracker::MmapRange> PerfDataTracker::FindMapping(
-    uint32_t pid,
-    uint64_t ips) {
-  auto vec = mmap2_ranges_.Find(pid);
-  if (!vec) {
-    return base::ErrStatus("Sample pid not found in mappings.");
+  if (IsInKernel(record.cpu_mode)) {
+    context_->mapping_tracker->CreateKernelMemoryMapping(
+        BuildCreateMappingParams(std::move(record)));
+  } else {
+    UniquePid upid =
+        context_->process_tracker->GetOrCreateProcess(record.num.pid);
+    context_->mapping_tracker->CreateUserMemoryMapping(
+        upid, BuildCreateMappingParams(std::move(record)));
   }
-
-  for (const auto& range : *vec) {
-    if (ips >= range.start && ips < range.end) {
-      return range;
-    }
-  }
-  return base::ErrStatus("No mapping for callstack frame instruction pointer");
 }
 
 base::StatusOr<PerfDataTracker::PerfSample> PerfDataTracker::ParseSample(
diff --git a/src/trace_processor/importers/perf/perf_data_tracker.h b/src/trace_processor/importers/perf/perf_data_tracker.h
index 0ab99aa..11258ed 100644
--- a/src/trace_processor/importers/perf/perf_data_tracker.h
+++ b/src/trace_processor/importers/perf/perf_data_tracker.h
@@ -25,6 +25,7 @@
 #include "perfetto/ext/base/flat_hash_map.h"
 #include "perfetto/ext/base/status_or.h"
 #include "perfetto/ext/base/string_utils.h"
+#include "protos/perfetto/trace/profiling/profile_packet.pbzero.h"
 #include "src/trace_processor/importers/perf/perf_data_reader.h"
 #include "src/trace_processor/importers/perf/perf_event.h"
 #include "src/trace_processor/storage/trace_storage.h"
@@ -76,14 +77,10 @@
       uint32_t prot;
       uint32_t flags;
     };
+    protos::pbzero::Profiling::CpuMode cpu_mode;
     Numeric num;
     std::string filename;
   };
-  struct MmapRange {
-    uint64_t start;
-    uint64_t end;
-    MappingTable::Id id;
-  };
 
   PerfDataTracker(const PerfDataTracker&) = delete;
   PerfDataTracker& operator=(const PerfDataTracker&) = delete;
@@ -103,14 +100,11 @@
   base::StatusOr<PerfSample> ParseSample(
       perfetto::trace_processor::perf_importer::Reader&);
 
-  base::StatusOr<MmapRange> FindMapping(uint32_t pid, uint64_t ips);
-
  private:
   const perf_event_attr* FindAttrWithId(uint64_t id) const;
   TraceProcessorContext* context_;
   std::vector<AttrAndIds> attrs_;
 
-  base::FlatHashMap</*pid=*/uint32_t, std::vector<MmapRange>> mmap2_ranges_;
   uint64_t common_sample_type_;
 };
 }  // namespace perf_importer
diff --git a/src/trace_processor/importers/perf/perf_data_tracker_unittest.cc b/src/trace_processor/importers/perf/perf_data_tracker_unittest.cc
index 6c59be6..3cbdc80 100644
--- a/src/trace_processor/importers/perf/perf_data_tracker_unittest.cc
+++ b/src/trace_processor/importers/perf/perf_data_tracker_unittest.cc
@@ -22,16 +22,35 @@
 #include <vector>
 
 #include "perfetto/base/build_config.h"
+#include "protos/perfetto/trace/profiling/profile_packet.pbzero.h"
+#include "src/trace_processor/importers/common/address_range.h"
+#include "src/trace_processor/importers/common/mapping_tracker.h"
+#include "src/trace_processor/importers/common/process_tracker.h"
+#include "src/trace_processor/importers/common/stack_profile_tracker.h"
 #include "src/trace_processor/importers/perf/perf_event.h"
 #include "test/gtest_and_gmock.h"
 
 namespace perfetto {
 namespace trace_processor {
 namespace perf_importer {
+namespace {
 
-TEST(PerfDataTrackerUnittest, ComputeCommonSampleType) {
-  TraceProcessorContext context;
-  PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context);
+class PerfDataTrackerUnittest : public testing::Test {
+ public:
+  PerfDataTrackerUnittest() {
+    context_.storage = std::make_unique<TraceStorage>();
+    context_.process_tracker = std::make_unique<ProcessTracker>(&context_);
+    context_.stack_profile_tracker =
+        std::make_unique<StackProfileTracker>(&context_);
+    context_.mapping_tracker = std::make_unique<MappingTracker>(&context_);
+  }
+
+ protected:
+  TraceProcessorContext context_;
+};
+
+TEST_F(PerfDataTrackerUnittest, ComputeCommonSampleType) {
+  PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context_);
 
   PerfDataTracker::AttrAndIds attr_and_ids;
   attr_and_ids.attr.sample_type =
@@ -46,16 +65,15 @@
   EXPECT_FALSE(tracker->common_sample_type() & PERF_SAMPLE_CALLCHAIN);
 }
 
-TEST(PerfDataTrackerUnittest, FindMapping) {
-  TraceProcessorContext context;
-  context.storage = std::make_unique<TraceStorage>();
-  PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context);
+TEST_F(PerfDataTrackerUnittest, FindMapping) {
+  PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context_);
 
   PerfDataTracker::Mmap2Record rec;
   rec.filename = "file1";
   rec.num.addr = 1000;
   rec.num.len = 100;
   rec.num.pid = 1;
+  rec.cpu_mode = protos::pbzero::Profiling::MODE_USER;
   tracker->PushMmap2Record(rec);
 
   rec.num.addr = 2000;
@@ -64,32 +82,33 @@
   rec.num.addr = 3000;
   tracker->PushMmap2Record(rec);
 
-  auto res_status = tracker->FindMapping(1, 2050);
-  EXPECT_TRUE(res_status.ok());
-  EXPECT_EQ(res_status->start, 2000u);
-  EXPECT_EQ(res_status->end, 2100u);
+  UserMemoryMapping* mapping =
+      context_.mapping_tracker->FindUserMappingForAddress(
+          context_.process_tracker->GetOrCreateProcess(1), 2050);
+  ASSERT_NE(mapping, nullptr);
+  EXPECT_EQ(mapping->memory_range().start(), 2000u);
+  EXPECT_EQ(mapping->memory_range().end(), 2100u);
 }
 
-TEST(PerfDataTrackerUnittest, FindMappingFalse) {
-  TraceProcessorContext context;
-  context.storage = std::make_unique<TraceStorage>();
-  PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context);
+TEST_F(PerfDataTrackerUnittest, FindMappingFalse) {
+  PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context_);
 
   PerfDataTracker::Mmap2Record rec;
   rec.filename = "file1";
   rec.num.addr = 1000;
   rec.num.len = 100;
   rec.num.pid = 1;
+  rec.cpu_mode = protos::pbzero::Profiling::MODE_USER;
   tracker->PushMmap2Record(rec);
 
-  auto res_status = tracker->FindMapping(2, 2050);
-  EXPECT_FALSE(res_status.ok());
+  UserMemoryMapping* mapping =
+      context_.mapping_tracker->FindUserMappingForAddress(
+          context_.process_tracker->GetOrCreateProcess(2), 2050);
+  EXPECT_EQ(mapping, nullptr);
 }
 
-TEST(PerfDataTrackerUnittest, ParseSampleTrivial) {
-  TraceProcessorContext context;
-  context.storage = std::make_unique<TraceStorage>();
-  PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context);
+TEST_F(PerfDataTrackerUnittest, ParseSampleTrivial) {
+  PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context_);
 
   PerfDataTracker::AttrAndIds attr_and_ids;
   attr_and_ids.attr.sample_type = PERF_SAMPLE_TIME;
@@ -107,10 +126,8 @@
   EXPECT_EQ(parsed_sample->ts, 100u);
 }
 
-TEST(PerfDataTrackerUnittest, ParseSampleCallchain) {
-  TraceProcessorContext context;
-  context.storage = std::make_unique<TraceStorage>();
-  PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context);
+TEST_F(PerfDataTrackerUnittest, ParseSampleCallchain) {
+  PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context_);
 
   PerfDataTracker::AttrAndIds attr_and_ids;
   attr_and_ids.attr.sample_type = PERF_SAMPLE_CALLCHAIN;
@@ -137,10 +154,8 @@
   EXPECT_EQ(parsed_sample->callchain.size(), 3u);
 }
 
-TEST(PerfDataTrackerUnittest, ParseSampleWithoutId) {
-  TraceProcessorContext context;
-  context.storage = std::make_unique<TraceStorage>();
-  PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context);
+TEST_F(PerfDataTrackerUnittest, ParseSampleWithoutId) {
+  PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context_);
 
   PerfDataTracker::AttrAndIds attr_and_ids;
   attr_and_ids.attr.sample_type = PERF_SAMPLE_TID | PERF_SAMPLE_TIME |
@@ -177,10 +192,8 @@
   EXPECT_EQ(sample.ts, parsed_sample->ts);
 }
 
-TEST(PerfDataTrackerUnittest, ParseSampleWithId) {
-  TraceProcessorContext context;
-  context.storage = std::make_unique<TraceStorage>();
-  PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context);
+TEST_F(PerfDataTrackerUnittest, ParseSampleWithId) {
+  PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context_);
 
   PerfDataTracker::AttrAndIds attr_and_ids;
   attr_and_ids.attr.sample_type = PERF_SAMPLE_CPU | PERF_SAMPLE_TID |
@@ -222,6 +235,7 @@
   EXPECT_EQ(100u, parsed_sample->ts);
 }
 
+}  // namespace
 }  // namespace perf_importer
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/importers/perf/perf_event.h b/src/trace_processor/importers/perf/perf_event.h
index 53f5f05..c60709f 100644
--- a/src/trace_processor/importers/perf/perf_event.h
+++ b/src/trace_processor/importers/perf/perf_event.h
@@ -223,4 +223,15 @@
   PERF_SAMPLE_MAX = 1U << 25, /* non-ABI */
 };
 
+constexpr auto kPerfRecordMiscCpumodeMask = 0x7;
+
+enum perf_record_misc {
+  PERF_RECORD_MISC_CPUMODE_UNKNOWN = 0,
+  PERF_RECORD_MISC_KERNEL = 1,
+  PERF_RECORD_MISC_USER = 2,
+  PERF_RECORD_MISC_HYPERVISOR = 3,
+  PERF_RECORD_MISC_GUEST_KERNEL = 4,
+  PERF_RECORD_MISC_GUEST_USER = 5,
+};
+
 #endif  // SRC_TRACE_PROCESSOR_IMPORTERS_PERF_PERF_EVENT_H_
diff --git a/src/trace_processor/importers/proto/BUILD.gn b/src/trace_processor/importers/proto/BUILD.gn
index 2eaa5fe..2e542d0 100644
--- a/src/trace_processor/importers/proto/BUILD.gn
+++ b/src/trace_processor/importers/proto/BUILD.gn
@@ -91,9 +91,9 @@
     "../../storage",
     "../../tables",
     "../../types",
+    "../../util:build_id",
     "../../util:gzip",
     "../../util:profiler_util",
-    "../../util:stack_traces_util",
     "../common",
     "../common:parser_types",
     "../ftrace:minimal",
@@ -181,7 +181,6 @@
     "../../util:profiler_util",
     "../../util:proto_profiler",
     "../../util:proto_to_args_parser",
-    "../../util:stack_traces_util",
     "../common",
     "../common:parser_types",
     "../etw:full",
@@ -276,7 +275,6 @@
     "../../types",
     "../../util:descriptors",
     "../../util:profiler_util",
-    "../../util:stack_traces_util",
     "../common",
     "../ftrace:full",
   ]
diff --git a/src/trace_processor/importers/proto/profile_module.cc b/src/trace_processor/importers/proto/profile_module.cc
index 61596d3..b80cae7 100644
--- a/src/trace_processor/importers/proto/profile_module.cc
+++ b/src/trace_processor/importers/proto/profile_module.cc
@@ -24,6 +24,7 @@
 #include "src/trace_processor/importers/common/clock_tracker.h"
 #include "src/trace_processor/importers/common/deobfuscation_mapping_table.h"
 #include "src/trace_processor/importers/common/event_tracker.h"
+#include "src/trace_processor/importers/common/mapping_tracker.h"
 #include "src/trace_processor/importers/common/process_tracker.h"
 #include "src/trace_processor/importers/common/stack_profile_tracker.h"
 #include "src/trace_processor/importers/proto/packet_sequence_state.h"
@@ -36,8 +37,8 @@
 #include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/tables/profiler_tables_py.h"
 #include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/util/build_id.h"
 #include "src/trace_processor/util/profiler_util.h"
-#include "src/trace_processor/util/stack_traces_util.h"
 
 #include "protos/perfetto/common/builtin_clock.pbzero.h"
 #include "protos/perfetto/common/perf_events.pbzero.h"
@@ -428,19 +429,11 @@
 
 void ProfileModule::ParseModuleSymbols(ConstBytes blob) {
   protos::pbzero::ModuleSymbols::Decoder module_symbols(blob.data, blob.size);
-  StringId build_id;
-  // TODO(b/148109467): Remove workaround once all active Chrome versions
-  // write raw bytes instead of a string as build_id.
-  if (util::IsHexModuleId(module_symbols.build_id())) {
-    build_id = context_->storage->InternString(module_symbols.build_id());
-  } else {
-    build_id = context_->storage->InternString(base::StringView(base::ToHex(
-        module_symbols.build_id().data, module_symbols.build_id().size)));
-  }
+  BuildId build_id = BuildId::FromRaw(module_symbols.build_id());
 
-  auto mapping_ids = context_->stack_profile_tracker->FindMappingRow(
-      context_->storage->InternString(module_symbols.path()), build_id);
-  if (mapping_ids.empty()) {
+  auto mappings =
+      context_->mapping_tracker->FindMappings(module_symbols.path(), build_id);
+  if (mappings.empty()) {
     context_->storage->IncrementStats(stats::stackprofile_invalid_mapping_id);
     return;
   }
@@ -467,12 +460,11 @@
       continue;
     }
     bool frame_found = false;
-    for (MappingId mapping_id : mapping_ids) {
+    for (VirtualMemoryMapping* mapping : mappings) {
       context_->args_translation_table->AddNativeSymbolTranslationRule(
-          mapping_id, address_symbols.address(), last_location);
+          mapping->mapping_id(), address_symbols.address(), last_location);
       std::vector<FrameId> frame_ids =
-          context_->stack_profile_tracker->FindFrameIds(
-              mapping_id, address_symbols.address());
+          mapping->FindFrameIds(address_symbols.address());
 
       for (const FrameId frame_id : frame_ids) {
         auto* frames = context_->storage->mutable_stack_profile_frame_table();
diff --git a/src/trace_processor/importers/proto/profile_packet_sequence_state.cc b/src/trace_processor/importers/proto/profile_packet_sequence_state.cc
index 9d889ef..31841fc 100644
--- a/src/trace_processor/importers/proto/profile_packet_sequence_state.cc
+++ b/src/trace_processor/importers/proto/profile_packet_sequence_state.cc
@@ -19,6 +19,8 @@
 #include "perfetto/base/flat_set.h"
 #include "perfetto/base/logging.h"
 #include "perfetto/ext/base/string_view.h"
+#include "src/trace_processor/importers/common/address_range.h"
+#include "src/trace_processor/importers/common/mapping_tracker.h"
 #include "src/trace_processor/importers/common/process_tracker.h"
 #include "src/trace_processor/importers/common/stack_profile_tracker.h"
 #include "src/trace_processor/importers/proto/packet_sequence_state.h"
@@ -28,6 +30,7 @@
 #include "src/trace_processor/storage/stats.h"
 #include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/util/build_id.h"
 
 namespace perfetto {
 namespace trace_processor {
@@ -68,17 +71,16 @@
 
 void ProfilePacketSequenceState::AddMapping(SourceMappingId id,
                                             const SourceMapping& mapping) {
-  StackProfileTracker::CreateMappingParams params;
+  CreateMappingParams params;
   if (std::string* str = strings_.Find(mapping.build_id); str) {
-    params.build_id = base::StringView(*str);
+    params.build_id = BuildId::FromRaw(*str);
   } else {
     context_->storage->IncrementStats(stats::stackprofile_invalid_string_id);
     return;
   }
   params.exact_offset = mapping.exact_offset;
   params.start_offset = mapping.start_offset;
-  params.start = mapping.start;
-  params.end = mapping.end;
+  params.memory_range = AddressRange(mapping.start, mapping.end);
   params.load_bias = mapping.load_bias;
 
   std::vector<base::StringView> path_components;
@@ -93,16 +95,16 @@
       break;
     }
   }
-  std::string path = ProfilePacketUtils::MakeMappingName(path_components);
-  params.name = base::StringView(path);
-  MappingId mapping_id = context_->stack_profile_tracker->InternMapping(params);
-  mappings_.Insert(id, mapping_id);
+
+  params.name = ProfilePacketUtils::MakeMappingName(path_components);
+  mappings_.Insert(
+      id, &context_->mapping_tracker->InternMemoryMapping(std::move(params)));
 }
 
 void ProfilePacketSequenceState::AddFrame(SourceFrameId id,
                                           const SourceFrame& frame) {
-  MappingId* mapping_id = mappings_.Find(frame.mapping_id);
-  if (!mapping_id) {
+  VirtualMemoryMapping** mapping = mappings_.Find(frame.mapping_id);
+  if (!mapping) {
     context_->storage->IncrementStats(stats::stackprofile_invalid_mapping_id);
     return;
   }
@@ -113,9 +115,8 @@
     return;
   }
 
-  FrameId frame_id = context_->stack_profile_tracker->InternFrame(
-      *mapping_id, frame.rel_pc, base::StringView(*function_name));
-
+  FrameId frame_id =
+      (*mapping)->InternFrame(frame.rel_pc, base::StringView(*function_name));
   frames_.Insert(id, frame_id);
 }
 
diff --git a/src/trace_processor/importers/proto/profile_packet_sequence_state.h b/src/trace_processor/importers/proto/profile_packet_sequence_state.h
index 678aab2..99661da 100644
--- a/src/trace_processor/importers/proto/profile_packet_sequence_state.h
+++ b/src/trace_processor/importers/proto/profile_packet_sequence_state.h
@@ -23,7 +23,6 @@
 
 #include "perfetto/ext/base/hash.h"
 #include "perfetto/ext/base/string_view.h"
-#include "protos/perfetto/trace/profiling/profile_common.pbzero.h"
 #include "src/trace_processor/importers/proto/packet_sequence_state_generation.h"
 #include "src/trace_processor/importers/proto/stack_profile_sequence_state.h"
 #include "src/trace_processor/storage/trace_storage.h"
@@ -31,6 +30,8 @@
 namespace perfetto {
 namespace trace_processor {
 
+class VirtualMemoryMapping;
+
 // Keeps sequence specific state for profile packets.
 class ProfilePacketSequenceState final
     : public PacketSequenceStateGeneration::InternedDataTracker {
@@ -126,7 +127,7 @@
   TraceProcessorContext* const context_;
 
   base::FlatHashMap<SourceStringId, std::string> strings_;
-  base::FlatHashMap<SourceMappingId, MappingId> mappings_;
+  base::FlatHashMap<SourceMappingId, VirtualMemoryMapping*> mappings_;
   base::FlatHashMap<SourceFrameId, FrameId> frames_;
   base::FlatHashMap<SourceCallstackId, CallsiteId> callstacks_;
 
diff --git a/src/trace_processor/importers/proto/profile_packet_sequence_state_unittest.cc b/src/trace_processor/importers/proto/profile_packet_sequence_state_unittest.cc
index ab947fa..c9cd6e3 100644
--- a/src/trace_processor/importers/proto/profile_packet_sequence_state_unittest.cc
+++ b/src/trace_processor/importers/proto/profile_packet_sequence_state_unittest.cc
@@ -18,8 +18,9 @@
 
 #include <memory>
 
-#include "src/trace_processor/importers/proto/packet_sequence_state.h"
+#include "src/trace_processor/importers/common/mapping_tracker.h"
 #include "src/trace_processor/importers/common/stack_profile_tracker.h"
+#include "src/trace_processor/importers/proto/packet_sequence_state.h"
 #include "src/trace_processor/types/trace_processor_context.h"
 #include "test/gtest_and_gmock.h"
 
@@ -58,6 +59,7 @@
  public:
   HeapProfileTrackerDupTest() {
     context.storage.reset(new TraceStorage());
+    context.mapping_tracker.reset(new MappingTracker(&context));
     context.stack_profile_tracker.reset(new StackProfileTracker(&context));
     packet_sequence_state.reset(new PacketSequenceState(&context));
 
@@ -196,6 +198,7 @@
 TEST(HeapProfileTrackerTest, SourceMappingPath) {
   TraceProcessorContext context;
   context.storage.reset(new TraceStorage());
+  context.mapping_tracker.reset(new MappingTracker(&context));
   context.stack_profile_tracker.reset(new StackProfileTracker(&context));
   PacketSequenceState pss(&context);
   ProfilePacketSequenceState& ppss =
@@ -229,6 +232,7 @@
 TEST(HeapProfileTrackerTest, Functional) {
   TraceProcessorContext context;
   context.storage.reset(new TraceStorage());
+  context.mapping_tracker.reset(new MappingTracker(&context));
   context.stack_profile_tracker.reset(new StackProfileTracker(&context));
 
   PacketSequenceState pss(&context);
diff --git a/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc b/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
index 11a45f5..e612709 100644
--- a/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
+++ b/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
@@ -25,6 +25,7 @@
 #include "src/trace_processor/importers/common/clock_tracker.h"
 #include "src/trace_processor/importers/common/event_tracker.h"
 #include "src/trace_processor/importers/common/flow_tracker.h"
+#include "src/trace_processor/importers/common/mapping_tracker.h"
 #include "src/trace_processor/importers/common/metadata_tracker.h"
 #include "src/trace_processor/importers/common/process_tracker.h"
 #include "src/trace_processor/importers/common/slice_tracker.h"
@@ -252,6 +253,7 @@
     context_.track_tracker.reset(new TrackTracker(&context_));
     context_.global_args_tracker.reset(
         new GlobalArgsTracker(context_.storage.get()));
+    context_.mapping_tracker.reset(new MappingTracker(&context_));
     context_.stack_profile_tracker.reset(new StackProfileTracker(&context_));
     context_.args_tracker.reset(new ArgsTracker(&context_));
     context_.args_translation_table.reset(new ArgsTranslationTable(storage_));
diff --git a/src/trace_processor/importers/proto/stack_profile_sequence_state.cc b/src/trace_processor/importers/proto/stack_profile_sequence_state.cc
index e503a09..a469f92 100644
--- a/src/trace_processor/importers/proto/stack_profile_sequence_state.cc
+++ b/src/trace_processor/importers/proto/stack_profile_sequence_state.cc
@@ -23,6 +23,8 @@
 #include "perfetto/ext/base/string_view.h"
 #include "protos/perfetto/trace/interned_data/interned_data.pbzero.h"
 #include "protos/perfetto/trace/profiling/profile_common.pbzero.h"
+#include "src/trace_processor/importers/common/address_range.h"
+#include "src/trace_processor/importers/common/mapping_tracker.h"
 #include "src/trace_processor/importers/common/stack_profile_tracker.h"
 #include "src/trace_processor/importers/proto/packet_sequence_state.h"
 #include "src/trace_processor/importers/proto/packet_sequence_state_generation.h"
@@ -30,6 +32,7 @@
 #include "src/trace_processor/storage/stats.h"
 #include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/util/build_id.h"
 
 namespace perfetto {
 namespace trace_processor {
@@ -48,21 +51,23 @@
 
 std::optional<MappingId> StackProfileSequenceState::FindOrInsertMapping(
     uint64_t iid) {
-  if (MappingId* id = cached_mappings_.Find(iid); id) {
-    return *id;
+  if (VirtualMemoryMapping* mapping = FindOrInsertMappingImpl(iid); mapping) {
+    return mapping->mapping_id();
+  }
+  return std::nullopt;
+}
+
+VirtualMemoryMapping* StackProfileSequenceState::FindOrInsertMappingImpl(
+    uint64_t iid) {
+  if (auto ptr = cached_mappings_.Find(iid); ptr) {
+    return *ptr;
   }
   auto* decoder =
       LookupInternedMessage<protos::pbzero::InternedData::kMappingsFieldNumber,
                             protos::pbzero::Mapping>(iid);
   if (!decoder) {
     context_->storage->IncrementStats(stats::stackprofile_invalid_mapping_id);
-    return std::nullopt;
-  }
-
-  std::optional<base::StringView> build_id =
-      LookupInternedBuildId(decoder->build_id());
-  if (!build_id) {
-    return std::nullopt;
+    return nullptr;
   }
 
   std::vector<base::StringView> path_components;
@@ -75,19 +80,26 @@
     }
     path_components.push_back(*str);
   }
-  std::string path = ProfilePacketUtils::MakeMappingName(path_components);
 
-  StackProfileTracker::CreateMappingParams params;
-  params.build_id = *build_id;
+  CreateMappingParams params;
+  std::optional<base::StringView> build_id =
+      LookupInternedBuildId(decoder->build_id());
+  if (!build_id) {
+    return nullptr;
+  }
+  params.build_id = BuildId::FromRaw(*build_id);
+
+  params.memory_range = AddressRange(decoder->start(), decoder->end());
   params.exact_offset = decoder->exact_offset();
   params.start_offset = decoder->start_offset();
-  params.start = decoder->start();
-  params.end = decoder->end();
   params.load_bias = decoder->load_bias();
-  params.name = base::StringView(path);
-  MappingId mapping_id = context_->stack_profile_tracker->InternMapping(params);
-  cached_mappings_.Insert(iid, mapping_id);
-  return mapping_id;
+  params.name = ProfilePacketUtils::MakeMappingName(path_components);
+
+  VirtualMemoryMapping& mapping =
+      context_->mapping_tracker->InternMemoryMapping(std::move(params));
+
+  cached_mappings_.Insert(iid, &mapping);
+  return &mapping;
 }
 
 std::optional<base::StringView>
@@ -110,11 +122,6 @@
 
 std::optional<base::StringView>
 StackProfileSequenceState::LookupInternedMappingPath(uint64_t iid) {
-  // This should really be an error (value not set) or at the very least return
-  // a null string, but for backward compatibility use an empty string instead.
-  if (iid == 0) {
-    return "";
-  }
   auto* decoder = LookupInternedMessage<
       protos::pbzero::InternedData::kMappingPathsFieldNumber,
       protos::pbzero::InternedString>(iid);
@@ -158,7 +165,7 @@
 
   cached_callstacks_.Insert(iid, *parent_callsite_id);
 
-  return *parent_callsite_id;
+  return parent_callsite_id;
 }
 
 std::optional<FrameId> StackProfileSequenceState::FindOrInsertFrame(
@@ -174,9 +181,9 @@
     return std::nullopt;
   }
 
-  std::optional<MappingId> mapping_id =
-      FindOrInsertMapping(decoder->mapping_id());
-  if (!mapping_id) {
+  VirtualMemoryMapping* mapping =
+      FindOrInsertMappingImpl(decoder->mapping_id());
+  if (!mapping) {
     return std::nullopt;
   }
 
@@ -190,9 +197,7 @@
     function_name = *func;
   }
 
-  FrameId frame_id = context_->stack_profile_tracker->InternFrame(
-      *mapping_id, decoder->rel_pc(), function_name);
-
+  FrameId frame_id = mapping->InternFrame(decoder->rel_pc(), function_name);
   cached_frames_.Insert(iid, frame_id);
 
   return frame_id;
diff --git a/src/trace_processor/importers/proto/stack_profile_sequence_state.h b/src/trace_processor/importers/proto/stack_profile_sequence_state.h
index 7a7879a..82de785 100644
--- a/src/trace_processor/importers/proto/stack_profile_sequence_state.h
+++ b/src/trace_processor/importers/proto/stack_profile_sequence_state.h
@@ -30,6 +30,7 @@
 namespace trace_processor {
 
 class TraceProcessorContext;
+class VirtualMemoryMapping;
 
 class StackProfileSequenceState final
     : public PacketSequenceStateGeneration::InternedDataTracker {
@@ -44,13 +45,15 @@
   std::optional<CallsiteId> FindOrInsertCallstack(uint64_t iid);
 
  private:
+  // Returns `nullptr`if non could be found.
+  VirtualMemoryMapping* FindOrInsertMappingImpl(uint64_t iid);
   std::optional<base::StringView> LookupInternedBuildId(uint64_t iid);
   std::optional<base::StringView> LookupInternedMappingPath(uint64_t iid);
   std::optional<base::StringView> LookupInternedFunctionName(uint64_t iid);
   std::optional<FrameId> FindOrInsertFrame(uint64_t iid);
 
   TraceProcessorContext* const context_;
-  base::FlatHashMap<uint64_t, MappingId> cached_mappings_;
+  base::FlatHashMap<uint64_t, VirtualMemoryMapping*> cached_mappings_;
   base::FlatHashMap<uint64_t, CallsiteId> cached_callstacks_;
   base::FlatHashMap<uint64_t, FrameId> cached_frames_;
 };
diff --git a/src/trace_processor/trace_processor_context.cc b/src/trace_processor/trace_processor_context.cc
index 908fdea..2ee7b3d 100644
--- a/src/trace_processor/trace_processor_context.cc
+++ b/src/trace_processor/trace_processor_context.cc
@@ -27,6 +27,7 @@
 #include "src/trace_processor/importers/common/event_tracker.h"
 #include "src/trace_processor/importers/common/flow_tracker.h"
 #include "src/trace_processor/importers/common/global_args_tracker.h"
+#include "src/trace_processor/importers/common/mapping_tracker.h"
 #include "src/trace_processor/importers/common/metadata_tracker.h"
 #include "src/trace_processor/importers/common/process_tracker.h"
 #include "src/trace_processor/importers/common/sched_event_tracker.h"
diff --git a/src/trace_processor/trace_processor_storage_impl.cc b/src/trace_processor/trace_processor_storage_impl.cc
index 1f06a0f..b0c7a15 100644
--- a/src/trace_processor/trace_processor_storage_impl.cc
+++ b/src/trace_processor/trace_processor_storage_impl.cc
@@ -26,6 +26,7 @@
 #include "src/trace_processor/importers/common/clock_tracker.h"
 #include "src/trace_processor/importers/common/event_tracker.h"
 #include "src/trace_processor/importers/common/flow_tracker.h"
+#include "src/trace_processor/importers/common/mapping_tracker.h"
 #include "src/trace_processor/importers/common/metadata_tracker.h"
 #include "src/trace_processor/importers/common/process_tracker.h"
 #include "src/trace_processor/importers/common/sched_event_tracker.h"
@@ -64,6 +65,7 @@
   context_.process_tracker.reset(new ProcessTracker(&context_));
   context_.clock_tracker.reset(new ClockTracker(&context_));
   context_.clock_converter.reset(new ClockConverter(&context_));
+  context_.mapping_tracker.reset(new MappingTracker(&context_));
   context_.perf_sample_tracker.reset(new PerfSampleTracker(&context_));
   context_.stack_profile_tracker.reset(new StackProfileTracker(&context_));
   context_.metadata_tracker.reset(new MetadataTracker(context_.storage.get()));
diff --git a/src/trace_processor/types/trace_processor_context.h b/src/trace_processor/types/trace_processor_context.h
index 3b3336c..26d1ff5 100644
--- a/src/trace_processor/types/trace_processor_context.h
+++ b/src/trace_processor/types/trace_processor_context.h
@@ -55,6 +55,7 @@
 class StackProfileTracker;
 class HeapGraphTracker;
 class PerfSampleTracker;
+class MappingTracker;
 class MetadataTracker;
 class PacketAnalyzer;
 class ProtoImporterModule;
@@ -102,6 +103,7 @@
   std::unique_ptr<SchedEventTracker> sched_event_tracker;
   std::unique_ptr<ClockTracker> clock_tracker;
   std::unique_ptr<ClockConverter> clock_converter;
+  std::unique_ptr<MappingTracker> mapping_tracker;
   std::unique_ptr<PerfSampleTracker> perf_sample_tracker;
   std::unique_ptr<StackProfileTracker> stack_profile_tracker;
   std::unique_ptr<MetadataTracker> metadata_tracker;
diff --git a/src/trace_processor/util/BUILD.gn b/src/trace_processor/util/BUILD.gn
index 8420a06..13aefe5 100644
--- a/src/trace_processor/util/BUILD.gn
+++ b/src/trace_processor/util/BUILD.gn
@@ -61,6 +61,17 @@
   }
 }
 
+source_set("build_id") {
+  sources = [
+    "build_id.cc",
+    "build_id.h",
+  ]
+  deps = [
+    "../../../gn:default_deps",
+    "../../../include/perfetto/ext/base:base",
+  ]
+}
+
 source_set("profiler_util") {
   sources = [
     "profiler_util.cc",
@@ -74,18 +85,6 @@
   ]
 }
 
-source_set("stack_traces_util") {
-  sources = [
-    "stack_traces_util.cc",
-    "stack_traces_util.h",
-  ]
-  deps = [
-    "../../../gn:default_deps",
-    "../../../include/perfetto/ext/base:base",
-    "../../../protos/perfetto/trace/profiling:zero",
-  ]
-}
-
 source_set("protozero_to_text") {
   sources = [
     "protozero_to_text.cc",
diff --git a/src/trace_processor/util/build_id.cc b/src/trace_processor/util/build_id.cc
new file mode 100644
index 0000000..20d76dd
--- /dev/null
+++ b/src/trace_processor/util/build_id.cc
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2023 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/trace_processor/util/build_id.h"
+
+#include <cctype>
+#include <cstddef>
+#include <string>
+
+#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/string_utils.h"
+#include "perfetto/ext/base/string_view.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace {
+uint8_t HexToBinary(char c) {
+  switch (c) {
+    case '0':
+      return 0;
+    case '1':
+      return 1;
+    case '2':
+      return 2;
+    case '3':
+      return 3;
+    case '4':
+      return 4;
+    case '5':
+      return 5;
+    case '6':
+      return 6;
+    case '7':
+      return 7;
+    case '8':
+      return 8;
+    case '9':
+      return 9;
+    case 'a':
+    case 'A':
+      return 10;
+    case 'b':
+    case 'B':
+      return 11;
+    case 'c':
+    case 'C':
+      return 12;
+    case 'd':
+    case 'D':
+      return 13;
+    case 'e':
+    case 'E':
+      return 14;
+    case 'f':
+    case 'F':
+      return 15;
+    default:
+      PERFETTO_CHECK(false);
+  }
+}
+
+std::string HexToBinary(base::StringView hex) {
+  std::string res;
+  res.reserve((hex.size() + 1) / 2);
+  auto it = hex.begin();
+
+  if (hex.size() % 2 != 0) {
+    res.push_back(static_cast<char>(HexToBinary(*it)));
+    ++it;
+  }
+
+  while (it != hex.end()) {
+    int v = (HexToBinary(*it++) << 4);
+    v += HexToBinary(*it++);
+    res.push_back(static_cast<char>(v));
+  }
+  return res;
+}
+
+// Returns whether this string is of a hex chrome module or not to decide
+// whether to convert the module to/from hex.
+// TODO(b/148109467): Remove workaround once all active Chrome versions
+// write raw bytes instead of a string as build_id.
+bool IsHexModuleId(base::StringView module) {
+  return module.size() == 33;
+}
+
+}  // namespace
+
+// static
+BuildId BuildId::FromHex(base::StringView data) {
+  if (IsHexModuleId(data)) {
+    return BuildId(data.ToStdString());
+  }
+  return BuildId(HexToBinary(data));
+}
+
+std::string BuildId::ToHex() const {
+  if (IsHexModuleId(base::StringView(raw_))) {
+    return raw_;
+  }
+  return base::ToHex(raw_.data(), raw_.size());
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/util/build_id.h b/src/trace_processor/util/build_id.h
new file mode 100644
index 0000000..64ec9ac
--- /dev/null
+++ b/src/trace_processor/util/build_id.h
@@ -0,0 +1,84 @@
+/*
+ * 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 SRC_TRACE_PROCESSOR_UTIL_BUILD_ID_H_
+#define SRC_TRACE_PROCESSOR_UTIL_BUILD_ID_H_
+
+#include <string>
+#include <utility>
+
+#include "perfetto/ext/base/hash.h"
+#include "perfetto/ext/base/string_view.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+// Represents the unique identifier of an executable, shared library, or module.
+// For example for ELF files this is the id stored in the .note.gnu.build-id
+// section. Sometimes a breakpad module id is used.
+// This class abstracts away the details of where this id comes from and how it
+// is converted to a StringId which is the representation used by tables in
+// trace_processor.
+class BuildId {
+ public:
+  // Allow hashing with base::Hash.
+  static constexpr bool kHashable = true;
+  size_t size() const { return raw_.size(); }
+  const char* data() const { return raw_.data(); }
+
+  static BuildId FromHex(base::StringView data);
+
+  static BuildId FromRaw(base::StringView data) {
+    return BuildId(data.ToStdString());
+  }
+  static BuildId FromRaw(std::string data) { return BuildId(std::move(data)); }
+  static BuildId FromRaw(const uint8_t* data, size_t size) {
+    return BuildId(std::string(reinterpret_cast<const char*>(data), size));
+  }
+
+  BuildId(const BuildId&) = default;
+  BuildId(BuildId&&) = default;
+
+  BuildId& operator=(const BuildId&) = default;
+  BuildId& operator=(BuildId&&) = default;
+
+  bool operator==(const BuildId& o) const { return raw_ == o.raw_; }
+
+  bool operator!=(const BuildId& o) const { return !(*this == o); }
+
+  bool operator<(const BuildId& o) const { return raw_ < o.raw_; }
+
+  std::string ToHex() const;
+
+  const std::string& raw() const { return raw_; }
+
+ private:
+  explicit BuildId(std::string data) : raw_(std::move(data)) {}
+  std::string raw_;
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+template <>
+struct std::hash<perfetto::trace_processor::BuildId> {
+  std::size_t operator()(
+      const perfetto::trace_processor::BuildId& o) const noexcept {
+    return perfetto::base::Hasher::Combine(o);
+  }
+};
+
+#endif  // SRC_TRACE_PROCESSOR_UTIL_BUILD_ID_H_
diff --git a/src/trace_processor/util/stack_traces_util.cc b/src/trace_processor/util/stack_traces_util.cc
deleted file mode 100644
index a255560..0000000
--- a/src/trace_processor/util/stack_traces_util.cc
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * 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/trace_processor/util/stack_traces_util.h"
-#include "perfetto/ext/base/string_view.h"
-
-namespace perfetto {
-namespace trace_processor {
-namespace util {
-
-bool IsHexModuleId(base::StringView module) {
-  return module.size() == 33;
-}
-
-}  // namespace util
-}  // namespace trace_processor
-}  // namespace perfetto
diff --git a/src/trace_processor/util/stack_traces_util.h b/src/trace_processor/util/stack_traces_util.h
deleted file mode 100644
index 1171786..0000000
--- a/src/trace_processor/util/stack_traces_util.h
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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_TRACE_PROCESSOR_UTIL_STACK_TRACES_UTIL_H_
-#define SRC_TRACE_PROCESSOR_UTIL_STACK_TRACES_UTIL_H_
-
-#include "perfetto/ext/base/string_view.h"
-
-namespace perfetto {
-namespace trace_processor {
-namespace util {
-
-// Returns whether this string is of a hex chrome module or not to decide
-// whether to convert the module to/from hex.
-// TODO(b/148109467): Remove workaround once all active Chrome versions
-// write raw bytes instead of a string as build_id.
-bool IsHexModuleId(base::StringView module);
-
-}  // namespace util
-}  // namespace trace_processor
-}  // namespace perfetto
-
-#endif  // SRC_TRACE_PROCESSOR_UTIL_STACK_TRACES_UTIL_H_