1 /******************************************************************************
2 *
3 * Copyright 2014 Google, Inc.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at:
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 ******************************************************************************/
18
19 #define LOG_TAG "bt_osi_allocation_tracker"
20
21 #include "osi/include/allocation_tracker.h"
22
23 #include <base/logging.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <mutex>
27 #include <unordered_map>
28
29 #include "osi/include/allocator.h"
30 #include "osi/include/log.h"
31 #include "osi/include/osi.h"
32
33 typedef struct {
34 uint8_t allocator_id;
35 void* ptr;
36 size_t size;
37 bool freed;
38 } allocation_t;
39
40 static const size_t canary_size = 8;
41 static char canary[canary_size];
42 static std::unordered_map<void*, allocation_t*> allocations;
43 static std::mutex tracker_lock;
44 static bool enabled = false;
45
46 // Memory allocation statistics
47 static size_t alloc_counter = 0;
48 static size_t free_counter = 0;
49 static size_t alloc_total_size = 0;
50 static size_t free_total_size = 0;
51
allocation_tracker_init(void)52 void allocation_tracker_init(void) {
53 std::unique_lock<std::mutex> lock(tracker_lock);
54 if (enabled) return;
55
56 // randomize the canary contents
57 for (size_t i = 0; i < canary_size; i++) canary[i] = (char)osi_rand();
58
59 LOG_DEBUG("canary initialized");
60
61 enabled = true;
62 }
63
64 // Test function only. Do not call in the normal course of operations.
allocation_tracker_uninit(void)65 void allocation_tracker_uninit(void) {
66 std::unique_lock<std::mutex> lock(tracker_lock);
67 if (!enabled) return;
68
69 allocations.clear();
70 enabled = false;
71 }
72
allocation_tracker_reset(void)73 void allocation_tracker_reset(void) {
74 std::unique_lock<std::mutex> lock(tracker_lock);
75 if (!enabled) return;
76
77 allocations.clear();
78 }
79
allocation_tracker_expect_no_allocations(void)80 size_t allocation_tracker_expect_no_allocations(void) {
81 std::unique_lock<std::mutex> lock(tracker_lock);
82 if (!enabled) return 0;
83
84 size_t unfreed_memory_size = 0;
85
86 for (const auto& entry : allocations) {
87 allocation_t* allocation = entry.second;
88 if (!allocation->freed) {
89 unfreed_memory_size +=
90 allocation->size; // Report back the unfreed byte count
91 LOG_ERROR("%s found unfreed allocation. address: 0x%zx size: %zd bytes",
92 __func__, (uintptr_t)allocation->ptr, allocation->size);
93 }
94 }
95
96 return unfreed_memory_size;
97 }
98
allocation_tracker_notify_alloc(uint8_t allocator_id,void * ptr,size_t requested_size)99 void* allocation_tracker_notify_alloc(uint8_t allocator_id, void* ptr,
100 size_t requested_size) {
101 char* return_ptr;
102 {
103 std::unique_lock<std::mutex> lock(tracker_lock);
104 if (!enabled || !ptr) return ptr;
105
106 // Keep statistics
107 alloc_counter++;
108 alloc_total_size += allocation_tracker_resize_for_canary(requested_size);
109
110 return_ptr = ((char*)ptr) + canary_size;
111
112 auto map_entry = allocations.find(return_ptr);
113 allocation_t* allocation;
114 if (map_entry != allocations.end()) {
115 allocation = map_entry->second;
116 CHECK(allocation->freed); // Must have been freed before
117 } else {
118 allocation = (allocation_t*)calloc(1, sizeof(allocation_t));
119 allocations[return_ptr] = allocation;
120 }
121
122 allocation->allocator_id = allocator_id;
123 allocation->freed = false;
124 allocation->size = requested_size;
125 allocation->ptr = return_ptr;
126 }
127
128 // Add the canary on both sides
129 memcpy(return_ptr - canary_size, canary, canary_size);
130 memcpy(return_ptr + requested_size, canary, canary_size);
131
132 return return_ptr;
133 }
134
allocation_tracker_notify_free(UNUSED_ATTR uint8_t allocator_id,void * ptr)135 void* allocation_tracker_notify_free(UNUSED_ATTR uint8_t allocator_id,
136 void* ptr) {
137 std::unique_lock<std::mutex> lock(tracker_lock);
138
139 if (!enabled || !ptr) return ptr;
140
141 auto map_entry = allocations.find(ptr);
142 CHECK(map_entry != allocations.end());
143 allocation_t* allocation = map_entry->second;
144 CHECK(allocation); // Must have been tracked before
145 CHECK(!allocation->freed); // Must not be a double free
146 CHECK(allocation->allocator_id ==
147 allocator_id); // Must be from the same allocator
148
149 // Keep statistics
150 free_counter++;
151 free_total_size += allocation_tracker_resize_for_canary(allocation->size);
152
153 allocation->freed = true;
154
155 UNUSED_ATTR const char* beginning_canary = ((char*)ptr) - canary_size;
156 UNUSED_ATTR const char* end_canary = ((char*)ptr) + allocation->size;
157
158 for (size_t i = 0; i < canary_size; i++) {
159 CHECK(beginning_canary[i] == canary[i]);
160 CHECK(end_canary[i] == canary[i]);
161 }
162
163 // Free the hash map entry to avoid unlimited memory usage growth.
164 // Double-free of memory is detected with "assert(allocation)" above
165 // as the allocation entry will not be present.
166 allocations.erase(ptr);
167 free(allocation);
168
169 return ((char*)ptr) - canary_size;
170 }
171
allocation_tracker_resize_for_canary(size_t size)172 size_t allocation_tracker_resize_for_canary(size_t size) {
173 return (!enabled) ? size : size + (2 * canary_size);
174 }
175
osi_allocator_debug_dump(int fd)176 void osi_allocator_debug_dump(int fd) {
177 dprintf(fd, "\nBluetooth Memory Allocation Statistics:\n");
178
179 std::unique_lock<std::mutex> lock(tracker_lock);
180
181 dprintf(fd, " Total allocated/free/used counts : %zu / %zu / %zu\n",
182 alloc_counter, free_counter, alloc_counter - free_counter);
183 dprintf(fd, " Total allocated/free/used octets : %zu / %zu / %zu\n",
184 alloc_total_size, free_total_size,
185 alloc_total_size - free_total_size);
186 }
187