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