内存size检查
使用场景
memset和memcpy操作动态内存,发生越界踩内存问题。
功能说明
对于memset和memcpy操作,当入参为动态内存节点时,增加对内存节点实际大小与入参指定大小的检查,若指定大小大于节点实际大小时,输出error信息,并且取消该次memset或memcpy操作,所以能够防止操作越界。动态内存越界场景下,可开启该功能定位问题。
接口名 |
描述 |
---|---|
LOS_MemCheckLevelSet |
设置内存检查级别 |
LOS_MemCheckLevelGet |
获取内存检查级别 |
LOS_MemNodeSizeCheck |
获取指定内存块的总大小和可用大小 |
错误码
序号 |
定义 |
实际数值 |
描述 |
参考解决方案 |
---|---|---|---|---|
1 |
LOS_ERRNO_MEMCHECK_PARA_NULL |
0x02000101 |
LOS_MemNodeSizeCheck的入参中存在空指针 |
传入有效指针 |
2 |
LOS_ERRNO_MEMCHECK_OUTSIDE |
0x02000102 |
内存地址不在合法范围内 |
输入内存地址本身不在内存管理范围之内,不做处理 |
3 |
LOS_ERRNO_MEMCHECK_NO_HEAD |
0x02000103 |
内存地址已经被释放或者是野指针 |
检查传入的内存地址,保证该地址是有效地址 |
4 |
LOS_ERRNO_MEMCHECK_WRONG_LEVEL |
0x02000104 |
内存检测等级不合法 |
通过LOS_MemCheckLevelGet检查等级,并通过LOS_MemCheckLevelSet来配置合法的等级 |
5 |
LOS_ERRNO_MEMCHECK_DISABLED |
0x02000105 |
内存检测被关闭 |
通过LOS_MemCheckLevelSet使能内存检测 |
错误码定义见错误码简介。8~15位的所属模块为动态内存模块,值为0x01。
使用方法
通过make menuconfig打开内存size检查:
- 目前只有bestfit内存管理算法支持该功能,所以需要使能LOSCFG_KERNEL_MEM_BESTFIT。
Kernel ---> Memory Management ---> Dynamic Memory Management Algorithm ---> bestfit
- 同时该功能依赖于LOSCFG_BASE_MEM_NODE_SIZE_CHECK,该宏开关可以通过在菜单项中开启“Enable size check or not”使能。
Debug ---> Enable a Debug Version ---> Enable MEM Debug ---> Enable size check or not
注意事项
开启该功能后,memset和memcpy性能下降,建议仅在需要定位越界问题时开启,默认关闭。
内存越界问题定位实例
通过构造超出内存长度的memset和memcpy操作,构造越界踩内存问题,构造代码如下:
VOID test(VOID) { UINT32 size = 0x100; VOID *poolAddr1 = LOS_MemAlloc((VOID *)OS_SYS_MEM_ADDR, size); if (poolAddr1 == NULL) { PRINTK("malloc poolAddr1 failed\n"); return; } else { PRINTK("malloc poolAddr1 %p successed\n", poolAddr1); } VOID *poolAddr2 = LOS_MemAlloc((VOID *)OS_SYS_MEM_ADDR, size); if (poolAddr2 == NULL) { PRINTK("malloc poolAddr2 failed\n"); return; } else { PRINTK("malloc poolAddr2 %p successed\n", poolAddr2); } LOS_MemCheckLevelSet(LOS_MEM_CHECK_LEVEL_LOW); // 开启对memset和memcpy的长度检测 PRINTK("memset poolAddr1 overflow\n"); memset(poolAddr1, 0x10, size * 2); // 超出长度的memset PRINTK("memset poolAddr1\n"); memset(poolAddr1, 0x10, size); // 合理长度的memset PRINTK("memcpy poolAddr2 overflow\n"); memcpy(poolAddr2, poolAddr1, size * 2); // 超出长度的memcpy PRINTK("memcpy poolAddr2\n"); memcpy(poolAddr2, poolAddr1, size); // 合理长度的memcpy LOS_MemCheckLevelSet(LOS_MEM_CHECK_LEVEL_DISABLE); // 关闭对memset和memcpy的长度检测 LOS_MemFree((VOID *)OS_SYS_MEM_ADDR, (VOID *)poolAddr1); LOS_MemFree((VOID *)OS_SYS_MEM_ADDR, (VOID *)poolAddr2); return 0; }
log:
malloc poolAddr1 0x80349514 successed malloc poolAddr2 0x80349624 successed LOS_MemCheckLevelSet: LOS_MEM_CHECK_LEVEL_LOW memset poolAddr1 overflow [ERR] --------------------------------------------- memset: dst inode availSize is not enough availSize = 0x100, memcpy length = 0x200 runTask->taskName = osMain runTask->taskId = 64 *******backtrace begin******* traceback 0 -- lr = 0x80209798 fp = 0x802c6930 traceback 1 -- lr = 0x80210fc4 fp = 0x802c6954 traceback 2 -- lr = 0x8020194c fp = 0x802c6994 traceback 3 -- lr = 0x80201448 fp = 0x802c699c traceback 4 -- lr = 0x802012fc fp = 0x0 [ERR] --------------------------------------------- memset poolAddr1 memcpy poolAddr2 overflow [ERR] --------------------------------------------- memcpy: dst inode availSize is not enough availSize = 0x100, memcpy length = 0x200 runTask->taskName = osMain runTask->taskId = 64 *******backtrace begin******* traceback 0 -- lr = 0x80209798 fp = 0x802c6930 traceback 1 -- lr = 0x8020dbc4 fp = 0x802c6954 traceback 2 -- lr = 0x8020194c fp = 0x802c6994 traceback 3 -- lr = 0x80201448 fp = 0x802c699c traceback 4 -- lr = 0x802012fc fp = 0x0 [ERR] --------------------------------------------- memcpy poolAddr2
由于开启size检测,非法的memset和memcpy操作被取消,输出error信息。“runTask->taskName = osMain”显示了该非法操作发生在 osMain函数中,并打印寄存器lr和fp的值。此时可以打开编译后生成的 .asm反汇编文件,默认生成在Huawei_LiteOS/out/<platform>目录下,其中的platform为具体的平台名,通过对比“寄存器lr”的值,查看函数的嵌套调用。