| /****************************************************************************** |
| * |
| * Copyright (C) 2014 Google, Inc. |
| * |
| * 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. |
| * |
| ******************************************************************************/ |
| |
| #define LOG_TAG "bt_osi_allocation_tracker" |
| |
| #include "osi/include/allocation_tracker.h" |
| |
| #include <assert.h> |
| #include <pthread.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include "osi/include/allocator.h" |
| #include "osi/include/hash_functions.h" |
| #include "osi/include/hash_map.h" |
| #include "osi/include/log.h" |
| #include "osi/include/osi.h" |
| |
| typedef struct { |
| uint8_t allocator_id; |
| void *ptr; |
| size_t size; |
| bool freed; |
| } allocation_t; |
| |
| // Hidden constructor for hash map for our use only. Everything else should use the |
| // normal interface. |
| hash_map_t *hash_map_new_internal( |
| size_t size, |
| hash_index_fn hash_fn, |
| key_free_fn key_fn, |
| data_free_fn, |
| key_equality_fn equality_fn, |
| const allocator_t *zeroed_allocator); |
| |
| static bool allocation_entry_freed_checker(hash_map_entry_t *entry, void *context); |
| static void *untracked_calloc(size_t size); |
| |
| static const size_t allocation_hash_map_size = 1024; |
| static const char *canary = "tinybird"; |
| static const allocator_t untracked_calloc_allocator = { |
| untracked_calloc, |
| free |
| }; |
| |
| static size_t canary_size; |
| static hash_map_t *allocations; |
| static pthread_mutex_t lock; |
| |
| void allocation_tracker_init(void) { |
| if (allocations) |
| return; |
| |
| canary_size = strlen(canary); |
| |
| pthread_mutex_init(&lock, NULL); |
| |
| pthread_mutex_lock(&lock); |
| allocations = hash_map_new_internal( |
| allocation_hash_map_size, |
| hash_function_pointer, |
| NULL, |
| free, |
| NULL, |
| &untracked_calloc_allocator); |
| pthread_mutex_unlock(&lock); |
| } |
| |
| // Test function only. Do not call in the normal course of operations. |
| void allocation_tracker_uninit(void) { |
| if (!allocations) |
| return; |
| |
| pthread_mutex_lock(&lock); |
| hash_map_free(allocations); |
| allocations = NULL; |
| pthread_mutex_unlock(&lock); |
| } |
| |
| void allocation_tracker_reset(void) { |
| if (!allocations) |
| return; |
| |
| pthread_mutex_lock(&lock); |
| hash_map_clear(allocations); |
| pthread_mutex_unlock(&lock); |
| } |
| |
| size_t allocation_tracker_expect_no_allocations(void) { |
| if (!allocations) |
| return 0; |
| |
| pthread_mutex_lock(&lock); |
| |
| size_t unfreed_memory_size = 0; |
| hash_map_foreach(allocations, allocation_entry_freed_checker, &unfreed_memory_size); |
| |
| pthread_mutex_unlock(&lock); |
| |
| return unfreed_memory_size; |
| } |
| |
| void *allocation_tracker_notify_alloc(uint8_t allocator_id, void *ptr, size_t requested_size) { |
| if (!allocations || !ptr) |
| return ptr; |
| |
| char *return_ptr = (char *)ptr; |
| |
| return_ptr += canary_size; |
| |
| pthread_mutex_lock(&lock); |
| |
| allocation_t *allocation = (allocation_t *)hash_map_get(allocations, return_ptr); |
| if (allocation) { |
| assert(allocation->freed); // Must have been freed before |
| } else { |
| allocation = (allocation_t *)calloc(1, sizeof(allocation_t)); |
| hash_map_set(allocations, return_ptr, allocation); |
| } |
| |
| allocation->allocator_id = allocator_id; |
| allocation->freed = false; |
| allocation->size = requested_size; |
| allocation->ptr = return_ptr; |
| |
| pthread_mutex_unlock(&lock); |
| |
| // Add the canary on both sides |
| memcpy(return_ptr - canary_size, canary, canary_size); |
| memcpy(return_ptr + requested_size, canary, canary_size); |
| |
| return return_ptr; |
| } |
| |
| void *allocation_tracker_notify_free(uint8_t allocator_id, void *ptr) { |
| if (!allocations || !ptr) |
| return ptr; |
| |
| pthread_mutex_lock(&lock); |
| |
| allocation_t *allocation = (allocation_t *)hash_map_get(allocations, ptr); |
| assert(allocation); // Must have been tracked before |
| assert(!allocation->freed); // Must not be a double free |
| assert(allocation->allocator_id == allocator_id); // Must be from the same allocator |
| allocation->freed = true; |
| |
| const char *beginning_canary = ((char *)ptr) - canary_size; |
| const char *end_canary = ((char *)ptr) + allocation->size; |
| |
| for (size_t i = 0; i < canary_size; i++) { |
| assert(beginning_canary[i] == canary[i]); |
| assert(end_canary[i] == canary[i]); |
| } |
| |
| pthread_mutex_unlock(&lock); |
| |
| return ((char *)ptr) - canary_size; |
| } |
| |
| size_t allocation_tracker_resize_for_canary(size_t size) { |
| return (!allocations) ? size : size + (2 * canary_size); |
| } |
| |
| static bool allocation_entry_freed_checker(hash_map_entry_t *entry, void *context) { |
| allocation_t *allocation = (allocation_t *)entry->data; |
| if (!allocation->freed) { |
| *((size_t *)context) += allocation->size; // Report back the unfreed byte count |
| LOG_ERROR(LOG_TAG, "%s found unfreed allocation. address: 0x%zx size: %zd bytes", __func__, (uintptr_t)allocation->ptr, allocation->size); |
| } |
| |
| return true; |
| } |
| |
| static void *untracked_calloc(size_t size) { |
| return calloc(size, 1); |
| } |