/****************************************************************************** * * 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 #include #include #include #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(UNUSED_ATTR 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; UNUSED_ATTR const char *beginning_canary = ((char *)ptr) - canary_size; UNUSED_ATTR 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]); } // Free the hash map entry to avoid unlimited memory usage growth. // Double-free of memory is detected with "assert(allocation)" above // as the allocation entry will not be present. hash_map_erase(allocations, ptr); 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); }