/* * Copyright 2015 Rockchip Electronics Co. LTD * * 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 MODULE_TAG "mpp_mem" #include #include "mpp_env.h" #include "mpp_mem.h" #include "mpp_list.h" #include "mpp_debug.h" #include "mpp_common.h" #include "os_mem.h" // mpp_mem_debug bit mask #define MEM_DEBUG_EN (0x00000001) // NOTE: runtime log need debug enable #define MEM_RUNTIME_LOG (0x00000002) #define MEM_NODE_LOG (0x00000004) #define MEM_EXT_ROOM (0x00000010) #define MEM_POISON (0x00000020) // default memory align size is set to 32 #define MEM_MAX_INDEX (0x7fffffff) #define MEM_ALIGN 32 #define MEM_ALIGN_MASK (MEM_ALIGN - 1) #define MEM_ALIGNED(x) (((x) + MEM_ALIGN) & (~MEM_ALIGN_MASK)) #define MEM_HEAD_ROOM(debug) ((debug & MEM_EXT_ROOM) ? (MEM_ALIGN) : (0)) #define MEM_NODE_MAX (1024) #define MEM_FREE_MAX (512) #define MEM_LOG_MAX (1024) #define MEM_CHECK_MARK (0xdd) #define MEM_HEAD_MASK (0xab) #define MEM_TAIL_MASK (0xcd) #define MPP_MEM_ASSERT(cond) \ do { \ if (!(cond)) { \ mpp_err("found mpp_mem assert failed, start dumping:\n"); \ service.dump(__FUNCTION__); \ mpp_assert(cond); \ } \ } while (0) typedef enum MppMemOps_e { MEM_MALLOC, MEM_REALLOC, MEM_FREE, MEM_FREE_DELAY, MEM_OPS_BUTT, } MppMemOps; /* * Here we combined valid flag with index value to keep node structure small * If index >= 0 this node is valid otherwise it is invalid * When we need to invalid one index use ~ to revert all bit * Then max valid index is 0x7fffffff. When index goes beyond it and becomes * negative value index will be reset to zero. */ typedef struct MppMemNode_s { RK_S32 index; size_t size; void *ptr; const char *caller; } MppMemNode; typedef struct MppMemLog_s { RK_U32 index; MppMemOps ops; size_t size_0; // size at input size_t size_1; // size at output void *ptr; // ptr at input void *ret; // ptr at output MppMemNode *node; // node for operation const char *caller; } MppMemLog; class MppMemService { public: // avoid any unwanted function MppMemService(); ~MppMemService(); void add_node(const char *caller, void *ptr, size_t size); /* * try delete node will return index in nodes * return 1 for need os_free call to real free * return 0 for reserve memory for check */ RK_S32 find_node(const char *caller, void *ptr, size_t *size, RK_S32 *idx); /* */ void del_node(const char *caller, void *ptr, size_t *size); void* delay_del_node(const char *caller, void *ptr, size_t *size); void reset_node(const char *caller, void *ptr, void *ret, size_t size); void chk_node(const char *caller, MppMemNode *node); void chk_mem(const char *caller, void *ptr, size_t size); RK_S32 chk_poison(MppMemNode *node); void add_log(MppMemOps ops, const char *caller, void *ptr, void *ret, size_t size_0, size_t size_1); void dump(const char *caller); RK_U32 total_now(void) { return m_total_size; } RK_U32 total_max(void) { return m_total_max; } Mutex lock; RK_U32 debug; private: // data for node record and delay free check RK_S32 nodes_max; RK_S32 nodes_idx; RK_S32 nodes_cnt; RK_S32 frees_max; RK_S32 frees_idx; RK_S32 frees_cnt; MppMemNode *nodes; MppMemNode *frees; // data for log record RK_U32 log_index; RK_S32 log_max; RK_S32 log_idx; RK_S32 log_cnt; MppMemLog *logs; RK_U32 m_total_size; RK_U32 m_total_max; MppMemService(const MppMemService &); MppMemService &operator=(const MppMemService &); }; static MppMemService service; static const char *ops2str[MEM_OPS_BUTT] = { "malloc", "realloc", "free", "delayed", }; static void show_mem(RK_U32 *buf, RK_S32 size) { mpp_err("dumping buf %p size %d start\n", buf, size); while (size > 0) { if (size >= 16) { mpp_err("%08x %08x %08x %08x\n", buf[0], buf[1], buf[2], buf[3]); buf += 4; size -= 16; } else if (size >= 12) { mpp_err("%08x %08x %08x\n", buf[0], buf[1], buf[2]); buf += 3; size -= 12; } else if (size >= 8) { mpp_err("%08x %08x\n", buf[0], buf[1]); buf += 2; size -= 8; } else if (size >= 4) { mpp_err("%08x\n", buf[0]); buf += 1; size -= 4; } else { mpp_log("end with size %d\n", size); break; } } mpp_err("dumping buf %p size %d end\n", buf, size); } static void set_mem_ext_room(void *p, size_t size) { memset((RK_U8 *)p - MEM_ALIGN, MEM_HEAD_MASK, MEM_ALIGN); memset((RK_U8 *)p + size, MEM_TAIL_MASK, MEM_ALIGN); } MppMemService::MppMemService() : debug(0), nodes_max(MEM_NODE_MAX), nodes_idx(0), nodes_cnt(0), frees_max(MEM_FREE_MAX), frees_idx(0), frees_cnt(0), nodes(NULL), frees(NULL), log_index(0), log_max(MEM_LOG_MAX), log_idx(0), log_cnt(0), logs(NULL), m_total_size(0), m_total_max(0) { mpp_env_get_u32("mpp_mem_debug", &debug, 0); // add more flag if debug enabled if (debug) debug |= MEM_DEBUG_EN; if (debug & MEM_DEBUG_EN) { mpp_env_get_u32("mpp_mem_node_max", (RK_U32 *)&nodes_max, MEM_NODE_MAX); mpp_log_f("mpp_mem_debug enabled %x max node %d\n", debug, nodes_max); size_t size = nodes_max * sizeof(MppMemNode); os_malloc((void **)&nodes, MEM_ALIGN, size); mpp_assert(nodes); memset(nodes, 0xff, size); add_node(__FUNCTION__, nodes, size); size = frees_max * sizeof(MppMemNode); os_malloc((void **)&frees, MEM_ALIGN, size); mpp_assert(frees); memset(frees, 0xff, size); add_node(__FUNCTION__, frees, size); size = log_max * sizeof(MppMemLog); os_malloc((void **)&logs, MEM_ALIGN, size); mpp_assert(logs); add_node(__FUNCTION__, logs, size); add_node(__FUNCTION__, this, sizeof(MppMemService)); } } MppMemService::~MppMemService() { if (debug & MEM_DEBUG_EN) { AutoMutex auto_lock(&lock); RK_S32 i = 0; MppMemNode *node = nodes; // delete self node first size_t size = 0; del_node(__FUNCTION__, this, &size); del_node(__FUNCTION__, nodes, &size); del_node(__FUNCTION__, frees, &size); del_node(__FUNCTION__, logs, &size); // then check leak memory if (nodes_cnt) { for (i = 0; i < nodes_max; i++, node++) { if (node->index >= 0) { mpp_log("found idx %8d mem %10p size %d leaked\n", node->index, node->ptr, node->size); nodes_cnt--; add_log(MEM_FREE, __FUNCTION__, node->ptr, NULL, node->size, 0); } } mpp_assert(nodes_cnt == 0); } // finally release all delay free memory if (frees_cnt) { node = frees; for (i = 0; i < frees_max; i++, node++) { if (node->index >= 0) { os_free((RK_U8 *)node->ptr - MEM_HEAD_ROOM(debug)); node->index = ~node->index; frees_cnt--; add_log(MEM_FREE_DELAY, __FUNCTION__, node->ptr, NULL, node->size, 0); } } mpp_assert(frees_cnt == 0); } os_free(nodes); os_free(frees); os_free(logs); } } void MppMemService::add_node(const char *caller, void *ptr, size_t size) { RK_S32 i = 0; if (debug & MEM_NODE_LOG) mpp_log("mem cnt: %5d total %8d inc size %8d at %s\n", nodes_cnt, m_total_size, size, caller); if (nodes_cnt >= nodes_max) { mpp_err("******************************************************\n"); mpp_err("* Reach max limit of mpp_mem counter %5d *\n", nodes_max); mpp_err("* Increase limit by setup env mpp_mem_node_max or *\n"); mpp_err("* recompile mpp with larger macro MEM_NODE_MAX value *\n"); mpp_err("******************************************************\n"); mpp_abort(); } MppMemNode *node = nodes; for (i = 0; i < nodes_max; i++, node++) { if (node->index < 0) { node->index = nodes_idx++; node->size = size; node->ptr = ptr; node->caller = caller; // NOTE: reset node index on revert if (nodes_idx < 0) nodes_idx = 0; nodes_cnt++; m_total_size += size; if (m_total_size > m_total_max) m_total_max = m_total_size; break; } } } RK_S32 MppMemService::find_node(const char *caller, void *ptr, size_t *size, RK_S32 *idx) { RK_S32 i = 0; MppMemNode *node = nodes; MPP_MEM_ASSERT(nodes_cnt <= nodes_max); for (i = 0; i < nodes_max; i++, node++) { if (node->index >= 0 && node->ptr == ptr) { *size = node->size; *idx = i; return 1; } } mpp_err("%s can NOT found node with ptr %p\n", caller, ptr); mpp_abort(); return 0; } void MppMemService::del_node(const char *caller, void *ptr, size_t *size) { RK_S32 i = 0; MppMemNode *node = nodes; MPP_MEM_ASSERT(nodes_cnt <= nodes_max); for (i = 0; i < nodes_max; i++, node++) { if (node->index >= 0 && node->ptr == ptr) { *size = node->size; node->index = ~node->index; nodes_cnt--; m_total_size -= node->size; if (debug & MEM_NODE_LOG) mpp_log("mem cnt: %5d total %8d dec size %8d at %s\n", nodes_cnt, m_total_size, node->size, caller); return ; } } mpp_err("%s fail to find node with ptr %p\n", caller, ptr); mpp_abort(); return ; } void *MppMemService::delay_del_node(const char *caller, void *ptr, size_t *size) { RK_S32 i = 0; MppMemNode *node = nodes; // clear output first void *ret = NULL; *size = 0; // find the node to save MPP_MEM_ASSERT(nodes_cnt <= nodes_max); for (i = 0; i < nodes_max; i++, node++) { if (node->index >= 0 && node->ptr == ptr) { chk_node(caller, node); break; } } MPP_MEM_ASSERT(i < nodes_max); if (debug & MEM_NODE_LOG) mpp_log("mem cnt: %5d total %8d dec size %8d at %s\n", nodes_cnt, m_total_size, node->size, caller); MppMemNode *free_node = NULL; MPP_MEM_ASSERT(frees_cnt <= frees_max); if (frees_cnt) { MppMemNode *tmp = frees; // NODE: check all data here for (i = 0; i < frees_max; i++, tmp++) { if (tmp->index >= 0) { chk_node(caller, tmp); chk_poison(tmp); } } } if (frees_cnt >= frees_max) { // free list full start del RK_S32 frees_last = frees_idx - frees_cnt; if (frees_last < 0) frees_last += frees_max; free_node = &frees[frees_last]; if (free_node->index >= 0) { chk_node(caller, free_node); chk_poison(free_node); ret = free_node->ptr; *size = free_node->size; free_node->index = ~free_node->index; frees_cnt--; } } MPP_MEM_ASSERT(frees_cnt <= frees_max); // free list is NOT full just store free_node = &frees[frees_idx]; frees_idx++; if (frees_idx >= frees_max) frees_idx = 0; if (frees_cnt < frees_max) frees_cnt++; MPP_MEM_ASSERT(frees_cnt <= frees_max); memcpy(&frees[frees_idx], node, sizeof(*node)); if ((debug & MEM_POISON) && (node->size < 1024)) memset(node->ptr, MEM_CHECK_MARK, node->size); node->index = ~node->index; m_total_size -= node->size; nodes_cnt--; return ret; } void MppMemService::chk_node(const char *caller, MppMemNode *node) { if ((debug & MEM_EXT_ROOM) == 0) return ; chk_mem(caller, node->ptr, node->size); } void MppMemService::chk_mem(const char *caller, void *ptr, size_t size) { if ((debug & MEM_EXT_ROOM) == 0) return ; RK_S32 i = 0; RK_U8 *p = (RK_U8 *)ptr - MEM_ALIGN; for (i = 0; i < MEM_ALIGN; i++) { if (p[i] != MEM_HEAD_MASK) { mpp_err("%s checking ptr %p head room found error!\n", caller, ptr); dump(caller); show_mem((RK_U32 *)p, MEM_ALIGN); mpp_abort(); } } p = (RK_U8 *)ptr + size; for (i = 0; i < MEM_ALIGN; i++) { if (p[i] != MEM_TAIL_MASK) { mpp_err("%s checking ptr %p tail room found error!\n", caller, ptr); dump(caller); show_mem((RK_U32 *)p, MEM_ALIGN); mpp_abort(); } } } RK_S32 MppMemService::chk_poison(MppMemNode *node) { if ((debug & MEM_POISON) == 0) return 0; // check oldest memory and free it RK_U8 *node_ptr = (RK_U8 *)node->ptr; RK_S32 size = node->size; RK_S32 i = 0; RK_S32 start = -1; RK_S32 end = -1; if (size >= 1024) return 0; for (; i < size; i++) { if (node_ptr[i] != MEM_CHECK_MARK) { if (start < 0) { start = i; } end = i + 1; } } if (start >= 0 || end >= 0) { mpp_err_f("found memory %p size %d caller %s overwrite from %d to %d\n", node_ptr, size, node->caller, start, end); dump(node->caller); } return end - start; } void MppMemService::reset_node(const char *caller, void *ptr, void *ret, size_t size) { RK_S32 i = 0; MppMemNode *node = nodes; if (debug & MEM_NODE_LOG) mpp_log("mem cnt: %5d total %8d equ size %8d at %s\n", nodes_cnt, m_total_size, size, __FUNCTION__); MPP_MEM_ASSERT(nodes_cnt <= nodes_max); for (i = 0; i < nodes_max; i++, node++) { if (node->index >= 0 && node->ptr == ptr) { m_total_size += size; m_total_size -= node->size; node->ptr = ret; node->size = size; node->caller = caller; if (debug & MEM_EXT_ROOM) set_mem_ext_room(ret, size); break; } } } void MppMemService::add_log(MppMemOps ops, const char *caller, void *ptr, void *ret, size_t size_0, size_t size_1) { MppMemLog *log = &logs[log_idx]; if (service.debug & MEM_RUNTIME_LOG) mpp_log("%-7s ptr %010p %010p size %8u %8u at %s\n", ops2str[ops], ptr, ret, size_0, size_1, caller); log->index = log_index++; log->ops = ops; log->size_0 = size_0; log->size_1 = size_1; log->ptr = ptr; log->ret = ret; log->node = NULL; log->caller = caller; log_idx++; if (log_idx >= log_max) log_idx = 0; if (log_cnt < log_max) log_cnt++; } void MppMemService::dump(const char *caller) { RK_S32 i; MppMemNode *node = nodes; mpp_log("mpp_mem enter status dumping from %s:\n", caller); mpp_log("mpp_mem node count %d:\n", nodes_cnt); if (nodes_cnt) { for (i = 0; i < nodes_max; i++, node++) { if (node->index < 0) continue; mpp_log("mpp_memory index %d caller %-32s size %-8u ptr %p\n", node->index, node->caller, node->size, node->ptr); } } node = frees; mpp_log("mpp_mem free count %d:\n", frees_cnt); if (frees_cnt) { for (i = 0; i < frees_max; i++, node++) { if (node->index < 0) continue; mpp_log("mpp_freed index %d caller %-32s size %-8u ptr %p\n", node->index, node->caller, node->size, node->ptr); } } RK_S32 start = log_idx - log_cnt; RK_S32 tmp_cnt = log_cnt; if (start < 0) start += log_max; mpp_log("mpp_mem enter log dumping:\n"); while (tmp_cnt) { MppMemLog *log = &logs[start]; mpp_log("idx %-8d op: %-7s from %-32s ptr %10p %10p size %7d %7d\n", log->index, ops2str[log->ops], log->caller, log->ptr, log->ret, log->size_0, log->size_1); start++; if (start >= log_max) start = 0; tmp_cnt--; } } void *mpp_osal_malloc(const char *caller, size_t size) { RK_U32 debug = service.debug; size_t size_align = MEM_ALIGNED(size); size_t size_real = (debug & MEM_EXT_ROOM) ? (size_align + 2 * MEM_ALIGN) : (size_align); void *ptr; os_malloc(&ptr, MEM_ALIGN, size_real); if (debug) { AutoMutex auto_lock(&service.lock); service.add_log(MEM_MALLOC, caller, NULL, ptr, size, size_real); if (ptr) { if (debug & MEM_EXT_ROOM) { ptr = (RK_U8 *)ptr + MEM_ALIGN; set_mem_ext_room(ptr, size); } service.add_node(caller, ptr, size); } } return ptr; } void *mpp_osal_calloc(const char *caller, size_t size) { void *ptr = mpp_osal_malloc(caller, size); if (ptr) memset(ptr, 0, size); return ptr; } void *mpp_osal_realloc(const char *caller, void *ptr, size_t size) { RK_U32 debug = service.debug; void *ret; if (NULL == ptr) return mpp_osal_malloc(caller, size); if (0 == size) { mpp_err("warning: realloc %p to zero size\n", ptr); return NULL; } size_t size_align = MEM_ALIGNED(size); size_t size_real = (debug & MEM_EXT_ROOM) ? (size_align + 2 * MEM_ALIGN) : (size_align); void *ptr_real = (RK_U8 *)ptr - MEM_HEAD_ROOM(debug); os_realloc(ptr_real, &ret, MEM_ALIGN, size_align); if (NULL == ret) { // if realloc fail the original buffer will be kept the same. mpp_err("mpp_realloc ptr %p to size %d failed\n", ptr, size); } else { AutoMutex auto_lock(&service.lock); // if realloc success reset the node and record if (debug) { void *ret_ptr = (debug & MEM_EXT_ROOM) ? ((RK_U8 *)ret + MEM_ALIGN) : (ret); service.reset_node(caller, ptr, ret_ptr, size); service.add_log(MEM_REALLOC, caller, ptr, ret_ptr, size, size_real); ret = ret_ptr; } } return ret; } void mpp_osal_free(const char *caller, void *ptr) { RK_U32 debug = service.debug; if (NULL == ptr) return; if (!debug) { os_free(ptr); return ; } size_t size = 0; AutoMutex auto_lock(&service.lock); if (debug & MEM_POISON) { // NODE: keep this node and delete delay node void *ret = service.delay_del_node(caller, ptr, &size); if (ret) os_free((RK_U8 *)ret - MEM_ALIGN); service.add_log(MEM_FREE_DELAY, caller, ptr, ret, size, 0); } else { void *ptr_real = (RK_U8 *)ptr - MEM_HEAD_ROOM(debug); // NODE: delete node and return size here service.del_node(caller, ptr, &size); service.chk_mem(caller, ptr, size); os_free(ptr_real); service.add_log(MEM_FREE, caller, ptr, ptr_real, size, 0); } } /* dump memory status */ void mpp_show_mem_status() { AutoMutex auto_lock(&service.lock); if (service.debug & MEM_DEBUG_EN) service.dump(__FUNCTION__); } RK_U32 mpp_mem_total_now() { AutoMutex auto_lock(&service.lock); return service.total_now(); } RK_U32 mpp_mem_total_max() { AutoMutex auto_lock(&service.lock); return service.total_max(); }