1/** 2 * Copyright (c) 2021-2022 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16#include <cstring> 17#include <algorithm> 18#include <iterator> 19#include <sstream> 20#include <fstream> 21#include <functional> 22#include <unordered_map> 23#include "utils/logger.h" 24#include "os/stacktrace.h" 25#include "mem/alloc_tracker.h" 26 27namespace panda { 28 29static constexpr size_t NUM_SKIP_FRAMES = 1; 30static constexpr size_t ARENA_SIZE = 4096; 31static constexpr size_t ENTRY_HDR_SIZE = sizeof(int32_t); 32 33static const char *GetDumpFilePath() 34{ 35#if defined(PANDA_TARGET_MOBILE) 36 return "/data/local/tmp/memdump.bin"; 37#else 38 return "memdump.bin"; 39#endif 40} 41 42static void Write(uint32_t val, std::ostream &out) 43{ 44 out.write(reinterpret_cast<char *>(&val), sizeof(val)); 45} 46 47static void Write(const std::string &str, std::ostream &out) 48{ 49 Write(static_cast<uint32_t>(str.size()), out); 50 out.write(str.c_str(), str.length()); 51} 52 53static size_t CalcHash(const std::vector<uintptr_t> &st) 54{ 55 size_t hash = 0; 56 std::hash<uintptr_t> addr_hash; 57 for (uintptr_t addr : st) { 58 hash |= addr_hash(addr); 59 } 60 return hash; 61} 62 63// On a phone getting a stacktrace is expensive operation. 64// An application doesn't launch in timeout and gets killed. 65// This function is aimed to skip getting stacktraces for some allocations. 66#if defined(PANDA_TARGET_MOBILE) 67static bool SkipStacktrace(size_t num) 68{ 69 static constexpr size_t FREQUENCY = 5; 70 return num % FREQUENCY != 0; 71} 72#else 73static bool SkipStacktrace([[maybe_unused]] size_t num) 74{ 75 return false; 76} 77#endif 78 79void DetailAllocTracker::TrackAlloc(void *addr, size_t size, SpaceType space) 80{ 81 if (addr == nullptr) { 82 return; 83 } 84 Stacktrace stacktrace = SkipStacktrace(++alloc_counter_) ? Stacktrace() : GetStacktrace(); 85 os::memory::LockHolder lock(mutex_); 86 87 uint32_t stacktrace_id = stacktraces_.size(); 88 if (stacktrace.size() > NUM_SKIP_FRAMES) { 89 stacktraces_.emplace_back(stacktrace.begin() + NUM_SKIP_FRAMES, stacktrace.end()); 90 } else { 91 stacktraces_.emplace_back(stacktrace); 92 } 93 if (cur_arena_.size() < sizeof(AllocInfo)) { 94 AllocArena(); 95 } 96 auto info = new (cur_arena_.data()) AllocInfo(cur_id_++, size, static_cast<uint32_t>(space), stacktrace_id); 97 cur_arena_ = cur_arena_.SubSpan(sizeof(AllocInfo)); 98 cur_allocs_.insert({addr, info}); 99} 100 101void DetailAllocTracker::TrackFree(void *addr) 102{ 103 if (addr == nullptr) { 104 return; 105 } 106 os::memory::LockHolder lock(mutex_); 107 auto it = cur_allocs_.find(addr); 108 ASSERT(it != cur_allocs_.end()); 109 AllocInfo *alloc = it->second; 110 cur_allocs_.erase(it); 111 if (cur_arena_.size() < sizeof(FreeInfo)) { 112 AllocArena(); 113 } 114 new (cur_arena_.data()) FreeInfo(alloc->GetId()); 115 cur_arena_ = cur_arena_.SubSpan(sizeof(FreeInfo)); 116} 117 118void DetailAllocTracker::AllocArena() 119{ 120 if (cur_arena_.size() >= ENTRY_HDR_SIZE) { 121 *reinterpret_cast<uint32_t *>(cur_arena_.data()) = 0; 122 } 123 arenas_.emplace_back(new uint8_t[ARENA_SIZE]); 124 cur_arena_ = Span<uint8_t>(arenas_.back().get(), arenas_.back().get() + ARENA_SIZE); 125} 126 127void DetailAllocTracker::Dump() 128{ 129 LOG(ERROR, RUNTIME) << "DetailAllocTracker::Dump to " << GetDumpFilePath(); 130 std::ofstream out(GetDumpFilePath(), std::ios::out | std::ios::binary | std::ios::trunc); 131 if (!out) { 132 LOG(ERROR, RUNTIME) << "DetailAllocTracker: Cannot open " << GetDumpFilePath() 133 << " for writing: " << strerror(errno) << "." 134 << "\nCheck the directory has write permissions or" 135 << " selinux is disabled."; 136 } 137 Dump(out); 138 LOG(ERROR, RUNTIME) << "DetailAllocTracker: dump file has been written"; 139} 140 141void DetailAllocTracker::Dump(std::ostream &out) 142{ 143 os::memory::LockHolder lock(mutex_); 144 145 Write(0, out); // number of items, will be updated later 146 Write(0, out); // number of stacktraces, will be updated later 147 148 std::map<uint32_t, uint32_t> id_map; 149 uint32_t num_stacks = WriteStacks(out, &id_map); 150 151 // Write end marker to the current arena 152 if (cur_arena_.size() >= ENTRY_HDR_SIZE) { 153 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) 154 *reinterpret_cast<uint32_t *>(cur_arena_.data()) = 0; 155 } 156 uint32_t num_items = 0; 157 for (auto &arena : arenas_) { 158 uint8_t *ptr = arena.get(); 159 size_t pos = 0; 160 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) 161 while (pos < ARENA_SIZE && *reinterpret_cast<uint32_t *>(ptr + pos) != 0) { 162 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) 163 uint32_t tag = *reinterpret_cast<uint32_t *>(ptr + pos); 164 if (tag == ALLOC_TAG) { 165 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) 166 auto alloc = reinterpret_cast<AllocInfo *>(ptr + pos); 167 Write(alloc->GetTag(), out); 168 Write(alloc->GetId(), out); 169 Write(alloc->GetSize(), out); 170 Write(alloc->GetSpace(), out); 171 Write(id_map[alloc->GetStacktraceId()], out); 172 pos += sizeof(AllocInfo); 173 } else if (tag == FREE_TAG) { 174 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) 175 auto info = reinterpret_cast<FreeInfo *>(ptr + pos); 176 Write(info->GetTag(), out); 177 Write(info->GetAllocId(), out); 178 pos += sizeof(FreeInfo); 179 } else { 180 UNREACHABLE(); 181 } 182 ++num_items; 183 } 184 } 185 186 out.seekp(0); 187 Write(num_items, out); 188 Write(num_stacks, out); 189} 190 191void DetailAllocTracker::DumpMemLeaks(std::ostream &out) 192{ 193 static constexpr size_t MAX_ENTRIES_TO_REPORT = 10; 194 195 os::memory::LockHolder lock(mutex_); 196 size_t num = 0; 197 out << "found " << cur_allocs_.size() << " leaks\n"; 198 for (auto &entry : cur_allocs_) { 199 out << "Allocation of " << entry.second->GetSize() << " is allocated at\n"; 200 uint32_t stacktrace_id = entry.second->GetStacktraceId(); 201 auto it = stacktraces_.begin(); 202 std::advance(it, stacktrace_id); 203 PrintStack(*it, out); 204 if (++num > MAX_ENTRIES_TO_REPORT) { 205 break; 206 } 207 } 208} 209 210uint32_t DetailAllocTracker::WriteStacks(std::ostream &out, std::map<uint32_t, uint32_t> *id_map) 211{ 212 class Key { 213 public: 214 explicit Key(const Stacktrace *stacktrace) : stacktrace_(stacktrace), hash_(CalcHash(*stacktrace)) {} 215 216 DEFAULT_COPY_SEMANTIC(Key); 217 DEFAULT_NOEXCEPT_MOVE_SEMANTIC(Key); 218 219 ~Key() = default; 220 221 bool operator==(const Key &k) const 222 { 223 return *stacktrace_ == *k.stacktrace_; 224 } 225 226 size_t GetHash() const 227 { 228 return hash_; 229 } 230 231 private: 232 const Stacktrace *stacktrace_ = nullptr; 233 size_t hash_ = 0U; 234 }; 235 236 struct KeyHash { 237 size_t operator()(const Key &k) const 238 { 239 return k.GetHash(); 240 } 241 }; 242 243 std::unordered_map<Key, uint32_t, KeyHash> alloc_stacks; 244 uint32_t stacktrace_id = 0; 245 uint32_t deduplicated_id = 0; 246 for (Stacktrace &stacktrace : stacktraces_) { 247 Key akey(&stacktrace); 248 auto res = alloc_stacks.insert({akey, deduplicated_id}); 249 if (res.second) { 250 std::stringstream str; 251 PrintStack(stacktrace, str); 252 Write(str.str(), out); 253 id_map->insert({stacktrace_id, deduplicated_id}); 254 ++deduplicated_id; 255 } else { 256 uint32_t id = res.first->second; 257 id_map->insert({stacktrace_id, id}); 258 } 259 ++stacktrace_id; 260 } 261 return deduplicated_id; 262} 263 264} // namespace panda 265