aboutsummaryrefslogtreecommitdiff
path: root/src/range_map.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/range_map.h')
-rw-r--r--src/range_map.h391
1 files changed, 391 insertions, 0 deletions
diff --git a/src/range_map.h b/src/range_map.h
new file mode 100644
index 0000000..89c58da
--- /dev/null
+++ b/src/range_map.h
@@ -0,0 +1,391 @@
+// Copyright 2016 Google Inc. All Rights Reserved.
+//
+// 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.
+
+// RagneMap maps
+//
+// [uint64_t, uint64_t) -> std::string, [optional other range base]
+//
+// where ranges must be non-overlapping.
+//
+// This is used to map the address space (either pointer offsets or file
+// offsets).
+//
+// The other range base allows us to use this RangeMap to translate addresses
+// from this domain to another one (like vm_addr -> file_addr or vice versa).
+//
+// This type is only exposed in the .h file for unit testing purposes.
+
+#ifndef BLOATY_RANGE_MAP_H_
+#define BLOATY_RANGE_MAP_H_
+
+#include <assert.h>
+#include <stdint.h>
+
+#include <exception>
+#include <map>
+#include <vector>
+
+#include "absl/strings/str_cat.h"
+
+namespace bloaty {
+
+class RangeMapTest;
+
+class RangeMap {
+ public:
+ RangeMap() = default;
+ RangeMap(RangeMap&& other) = default;
+ RangeMap& operator=(RangeMap&& other) = default;
+ RangeMap(RangeMap& other) = delete;
+ RangeMap& operator=(RangeMap& other) = delete;
+
+ // Adds a range to this map.
+ void AddRange(uint64_t addr, uint64_t size, const std::string& val);
+
+ // Adds a range to this map (in domain D1) that also corresponds to a
+ // different range in a different map (in domain D2). The correspondance will
+ // be noted to allow us to translate into the other domain later.
+ void AddDualRange(uint64_t addr, uint64_t size, uint64_t otheraddr,
+ const std::string& val);
+
+ // Adds a range to this map (in domain D1), and also adds corresponding ranges
+ // to |other| (in domain D2), using |translator| (in domain D1) to translate
+ // D1->D2. The translation is performed using information from previous
+ // AddDualRange() calls on |translator|.
+ //
+ // Returns true if the entire range [addr, size] was present in the
+ // |translator| map. (This does not necessarily mean that every part of the
+ // range was actually translated). If the return value is false, then the
+ // contents of |this| and |other| are undefined (Bloaty will bail in this
+ // case).
+ bool AddRangeWithTranslation(uint64_t addr, uint64_t size,
+ const std::string& val,
+ const RangeMap& translator, bool verbose,
+ RangeMap* other);
+
+ // Collapses adjacent ranges with the same label. This reduces memory usage
+ // and removes redundant noise from the output when dumping a full memory map
+ // (in normal Bloaty output it makes no difference, because all labels with
+ // the same name are added together).
+ //
+ // TODO(haberman): see if we can do this at insertion time instead, so it
+ // doesn't require a second pass.
+ void Compress();
+
+ // Returns whether this RangeMap fully covers the given range.
+ bool CoversRange(uint64_t addr, uint64_t size) const;
+
+ // Returns the maximum address contained in this map.
+ uint64_t GetMaxAddress() const;
+
+ // Translates |addr| into the other domain, returning |true| if this was
+ // successful.
+ bool Translate(uint64_t addr, uint64_t *translated) const;
+
+ // Looks for a range within this map that contains |addr|. If found, returns
+ // true and sets |label| to the corresponding label, and |offset| to the
+ // offset from the beginning of this range.
+ bool TryGetLabel(uint64_t addr, std::string* label) const;
+ bool TryGetLabelForRange(uint64_t addr, uint64_t size,
+ std::string* label) const;
+
+ // Looks for a range that starts exactly on |addr|. If it exists, returns
+ // true and sets |size| to its size.
+ bool TryGetSize(uint64_t addr, uint64_t* size) const;
+
+ std::string DebugString() const;
+
+ static std::string EntryDebugString(uint64_t addr, uint64_t size,
+ uint64_t other_start,
+ const std::string& label) {
+ std::string end =
+ size == kUnknownSize ? "?" : absl::StrCat(absl::Hex(addr + size));
+ std::string ret = absl::StrCat("[", absl::Hex(addr), ", ", end,
+ "] (size=", absl::Hex(size), "): ", label);
+ if (other_start != UINT64_MAX) {
+ absl::StrAppend(&ret, ", other_start=", absl::Hex(other_start));
+ }
+ return ret;
+ }
+
+ template <class T>
+ std::string EntryDebugString(T it) const {
+ if (it == mappings_.end()) {
+ return "[end]";
+ } else {
+ return EntryDebugString(it->first, it->second.size,
+ it->second.other_start, it->second.label);
+ }
+ }
+
+ template <class Func>
+ static void ComputeRollup(const std::vector<const RangeMap*>& range_maps,
+ Func func);
+
+ template <class Func>
+ void ForEachRange(Func func) const {
+ for (auto iter = mappings_.begin(); iter != mappings_.end(); ++iter) {
+ func(iter->first, RangeEnd(iter) - iter->first);
+ }
+ }
+
+ template <class Func>
+ void ForEachRangeWithStart(uint64_t start, Func func) const {
+ for (auto iter = FindContaining(start); iter != mappings_.end(); ++iter) {
+ if (!func(iter->second.label, iter->first,
+ RangeEnd(iter) - iter->first)) {
+ return;
+ }
+ }
+ }
+
+ static constexpr uint64_t kUnknownSize = UINT64_MAX;
+
+ private:
+ friend class RangeMapTest;
+ static const uint64_t kNoTranslation = UINT64_MAX;
+
+ struct Entry {
+ Entry(const std::string& label_, uint64_t size_, uint64_t other_)
+ : label(label_), size(size_), other_start(other_) {}
+ std::string label;
+ uint64_t size;
+ uint64_t other_start; // kNoTranslation if there is no mapping.
+
+ bool HasTranslation() const { return other_start != kNoTranslation; }
+ bool HasFallbackLabel() const { return !label.empty() && label[0] == '['; }
+
+ // We assume that short regions that were unattributed (have fallback
+ // labels) are actually padding. We could probably make this heuristic
+ // a bit more robust.
+ bool IsShortFallback() const { return size <= 16 && HasFallbackLabel(); }
+ };
+
+ typedef std::map<uint64_t, Entry> Map;
+ Map mappings_;
+
+ template <class T>
+ void CheckConsistency(T iter) const {
+ assert(iter->first + iter->second.size > iter->first);
+ assert(iter == mappings_.begin() ||
+ RangeEnd(std::prev(iter)) <= iter->first);
+ assert(std::next(iter) == mappings_.end() ||
+ RangeEnd(iter) <= std::next(iter)->first);
+ }
+
+ template <class T>
+ bool EntryContains(T iter, uint64_t addr) const {
+ return addr >= iter->first && addr < RangeEnd(iter);
+ }
+
+ template <class T>
+ bool EntryContainsStrict(T iter, uint64_t addr) const {
+ if (iter->second.size == kUnknownSize) {
+ return iter->first == addr;
+ } else {
+ return addr >= iter->first && addr < RangeEnd(iter);
+ }
+ }
+
+ template <class T>
+ void MaybeSetLabel(T iter, const std::string& label, uint64_t addr,
+ uint64_t end);
+
+ // When the size is unknown return |unknown| for the end.
+ uint64_t RangeEndUnknownLimit(Map::const_iterator iter,
+ uint64_t unknown) const {
+ if (iter->second.size == kUnknownSize) {
+ Map::const_iterator next = std::next(iter);
+ if (IterIsEnd(next) || next->first > unknown) {
+ return unknown;
+ } else {
+ return next->first;
+ }
+ } else {
+ uint64_t ret = iter->first + iter->second.size;
+ assert(ret > iter->first);
+ return ret;
+ }
+ }
+
+ uint64_t RangeEnd(Map::const_iterator iter) const {
+ return RangeEndUnknownLimit(iter, UINT64_MAX);
+ }
+
+ bool IterIsEnd(Map::const_iterator iter) const {
+ return iter == mappings_.end();
+ }
+
+ template <class T>
+ uint64_t TranslateWithEntry(T iter, uint64_t addr) const;
+
+ template <class T>
+ bool TranslateAndTrimRangeWithEntry(T iter, uint64_t addr, uint64_t size,
+ uint64_t* trimmed_addr,
+ uint64_t* translated_addr,
+ uint64_t* trimmed_size) const;
+
+ // Finds the entry that contains |addr|. If no such mapping exists, returns
+ // mappings_.end().
+ Map::const_iterator FindContaining(uint64_t addr) const;
+
+ // Finds the entry that contains |addr|, or the very next entry (which may be
+ // mappings_.end()).
+ Map::iterator FindContainingOrAfter(uint64_t addr);
+ Map::const_iterator FindContainingOrAfter(uint64_t addr) const;
+};
+
+template <class Func>
+void RangeMap::ComputeRollup(const std::vector<const RangeMap*>& range_maps,
+ Func func) {
+ assert(range_maps.size() > 0);
+ std::vector<Map::const_iterator> iters;
+
+ if (range_maps[0]->mappings_.empty()) {
+ for (int i = 0; i < range_maps.size(); i++) {
+ const RangeMap* range_map = range_maps[i];
+ if (!range_map->mappings_.empty()) {
+ printf(
+ "Error, range (%s) exists at index %d, but base map is empty\n",
+ range_map->EntryDebugString(range_map->mappings_.begin()).c_str(),
+ i);
+ assert(false);
+ throw std::runtime_error("Range extends beyond base map.");
+ }
+ }
+ return;
+ }
+
+ for (auto range_map : range_maps) {
+ iters.push_back(range_map->mappings_.begin());
+ }
+
+ // Iterate over all ranges in parallel to perform this transformation:
+ //
+ // ----- ----- ----- ---------------
+ // | | 1 A,X,1
+ // | X ----- ---------------
+ // | | | A,X,2
+ // A ----- | ---------------
+ // | | | |
+ // | | 2 -----> |
+ // | Y | A,Y,2
+ // | | | |
+ // ----- | | ---------------
+ // B | | B,Y,2
+ // ----- ----- ----- ---------------
+ //
+ //
+ // ----- ----- ----- ---------------
+ // C Z 3 C,Z,3
+ // ----- ----- ----- ---------------
+ //
+ // All input maps must cover exactly the same domain.
+
+ // Outer loop: once per continuous (gapless) region.
+ while (true) {
+ std::vector<std::string> keys;
+ uint64_t current = 0;
+
+ if (range_maps[0]->IterIsEnd(iters[0])) {
+ // Termination condition: all iterators must be at end.
+ for (int i = 0; i < range_maps.size(); i++) {
+ if (!range_maps[i]->IterIsEnd(iters[i])) {
+ printf(
+ "Error, range (%s) extends beyond final base map range "
+ "(%s)\n",
+ range_maps[i]->EntryDebugString(iters[i]).c_str(),
+ range_maps[0]->EntryDebugString(std::prev(iters[0])).c_str());
+ assert(false);
+ throw std::runtime_error("Range extends beyond base map.");
+ }
+ }
+ return;
+ } else {
+ // Starting a new continuous range: all iterators must start at the same
+ // place.
+ current = iters[0]->first;
+ for (int i = 0; i < range_maps.size(); i++) {
+ if (range_maps[i]->IterIsEnd(iters[i])) {
+ printf(
+ "Error, no more ranges for index %d but we need one "
+ "to match (%s)\n",
+ i, range_maps[0]->EntryDebugString(iters[0]).c_str());
+ assert(false);
+ throw std::runtime_error("No more ranges.");
+ } else if (iters[i]->first != current) {
+ printf(
+ "Error, range (%s) doesn't match the beginning of base range "
+ "(%s)\n",
+ range_maps[i]->EntryDebugString(iters[i]).c_str(),
+ range_maps[0]->EntryDebugString(iters[0]).c_str());
+ assert(false);
+ throw std::runtime_error("No more ranges.");
+ }
+ keys.push_back(iters[i]->second.label);
+ }
+ }
+
+ bool continuous = true;
+
+ // Inner loop: once per range within the continuous region.
+ while (continuous) {
+ uint64_t next_break = UINT64_MAX;
+
+ for (int i = 0; i < iters.size(); i++) {
+ next_break = std::min(next_break, range_maps[i]->RangeEnd(iters[i]));
+ }
+
+ func(keys, current, next_break);
+
+ // Advance all iterators with ranges ending at next_break.
+ for (int i = 0; i < iters.size(); i++) {
+ const RangeMap& map = *range_maps[i];
+ Map::const_iterator& iter = iters[i];
+ uint64_t end = continuous ? map.RangeEnd(iter)
+ : map.RangeEndUnknownLimit(iter, next_break);
+
+ if (end != next_break) {
+ continue;
+ }
+ ++iter;
+
+ // Test for discontinuity.
+ if (map.IterIsEnd(iter) || iter->first != next_break) {
+ if (i > 0 && continuous) {
+ printf(
+ "Error, gap between ranges (%s) and (%s) fails to cover base "
+ "range (%s)\n",
+ map.EntryDebugString(std::prev(iter)).c_str(),
+ map.EntryDebugString(iter).c_str(),
+ range_maps[0]->EntryDebugString(iters[0]).c_str());
+ assert(false);
+ throw std::runtime_error("Entry range extends beyond base range");
+ }
+ assert(i == 0 || !continuous);
+ continuous = false;
+ } else {
+ assert(continuous);
+ keys[i] = iter->second.label;
+ }
+ }
+ current = next_break;
+ }
+ }
+}
+
+
+} // namespace bloaty
+
+#endif // BLOATY_RANGE_MAP_H_