1 /******************************************************************************
2 *
3 * Copyright (C) 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(LOG_TAG, "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(LOG_TAG,
92 "%s found unfreed allocation. address: 0x%zx size: %zd bytes",
93 __func__, (uintptr_t)allocation->ptr, allocation->size);
94 }
95 }
96
97 return unfreed_memory_size;
98 }
99
allocation_tracker_notify_alloc(uint8_t allocator_id,void * ptr,size_t requested_size)100 void* allocation_tracker_notify_alloc(uint8_t allocator_id, void* ptr,
101 size_t requested_size) {
102 char* return_ptr;
103 {
104 std::unique_lock<std::mutex> lock(tracker_lock);
105 if (!enabled || !ptr) return ptr;
106
107 // Keep statistics
108 alloc_counter++;
109 alloc_total_size += allocation_tracker_resize_for_canary(requested_size);
110
111 return_ptr = ((char*)ptr) + canary_size;
112
113 auto map_entry = allocations.find(return_ptr);
114 allocation_t* allocation;
115 if (map_entry != allocations.end()) {
116 allocation = map_entry->second;
117 CHECK(allocation->freed); // Must have been freed before
118 } else {
119 allocation = (allocation_t*)calloc(1, sizeof(allocation_t));
120 allocations[return_ptr] = allocation;
121 }
122
123 allocation->allocator_id = allocator_id;
124 allocation->freed = false;
125 allocation->size = requested_size;
126 allocation->ptr = return_ptr;
127 }
128
129 // Add the canary on both sides
130 memcpy(return_ptr - canary_size, canary, canary_size);
131 memcpy(return_ptr + requested_size, canary, canary_size);
132
133 return return_ptr;
134 }
135
allocation_tracker_notify_free(UNUSED_ATTR uint8_t allocator_id,void * ptr)136 void* allocation_tracker_notify_free(UNUSED_ATTR uint8_t allocator_id,
137 void* ptr) {
138 std::unique_lock<std::mutex> lock(tracker_lock);
139
140 if (!enabled || !ptr) return ptr;
141
142 auto map_entry = allocations.find(ptr);
143 CHECK(map_entry != allocations.end());
144 allocation_t* allocation = map_entry->second;
145 CHECK(allocation); // Must have been tracked before
146 CHECK(!allocation->freed); // Must not be a double free
147 CHECK(allocation->allocator_id ==
148 allocator_id); // Must be from the same allocator
149
150 // Keep statistics
151 free_counter++;
152 free_total_size += allocation_tracker_resize_for_canary(allocation->size);
153
154 allocation->freed = true;
155
156 UNUSED_ATTR const char* beginning_canary = ((char*)ptr) - canary_size;
157 UNUSED_ATTR const char* end_canary = ((char*)ptr) + allocation->size;
158
159 for (size_t i = 0; i < canary_size; i++) {
160 CHECK(beginning_canary[i] == canary[i]);
161 CHECK(end_canary[i] == canary[i]);
162 }
163
164 // Free the hash map entry to avoid unlimited memory usage growth.
165 // Double-free of memory is detected with "assert(allocation)" above
166 // as the allocation entry will not be present.
167 allocations.erase(ptr);
168 free(allocation);
169
170 return ((char*)ptr) - canary_size;
171 }
172
allocation_tracker_resize_for_canary(size_t size)173 size_t allocation_tracker_resize_for_canary(size_t size) {
174 return (!enabled) ? size : size + (2 * canary_size);
175 }
176
osi_allocator_debug_dump(int fd)177 void osi_allocator_debug_dump(int fd) {
178 dprintf(fd, "\nBluetooth Memory Allocation Statistics:\n");
179
180 std::unique_lock<std::mutex> lock(tracker_lock);
181
182 dprintf(fd, " Total allocated/free/used counts : %zu / %zu / %zu\n",
183 alloc_counter, free_counter, alloc_counter - free_counter);
184 dprintf(fd, " Total allocated/free/used octets : %zu / %zu / %zu\n",
185 alloc_total_size, free_total_size,
186 alloc_total_size - free_total_size);
187 }
188