oid* pvGlblAddr, void* pvGlblVal, unsigned int dwGlbSize,
74 ? ? ? ? ? ? ? ?const char* pFileName, INT32U dwCodeLine)
75 {
76 ? ? if((NULL == pvGlblAddr) || (NULL == pvGlblVal))
77 ? ? {
78 ? ? ? ? printf("[%s(%d)]Null Pointer: (%p), (%p)!\n", pFileName, dwCodeLine, pvGlblAddr, pvGlblVal);
79 ? ? ? ? return;
80 ? ? }
81?
82 ? ? memcpy(pvGlblVal, pvGlblAddr, dwGlbSize);
83 ? ? CHK_OVERRUN(pvGlblAddr, dwGlbSize, pFileName, dwCodeLine);
84 }
85?
86 int main(void)
87 {
88 ? ? INIT_GLOBAL(>ChkMem, 0, sizeof(T_CHK_MEM));
89 ? ? printf("[%d]ChkHead:0x%X,ChkTail:0x%X!\n", __LINE__, HEAD_VAL(>ChkMem), TAIL_VAL(>ChkMem, sizeof(T_CHK_MEM))); ? ?
90 ? ? T_CHK_MEM tChkMem;
91 ? ? GET_GLOBAL(>ChkMem, &tChkMem, sizeof(T_CHK_MEM));
92?
93 ? ? strcpy(tChkMem.szName, "Elizabeth");
94 ? ? SET_GLOBAL(>ChkMem, &tChkMem, sizeof(T_CHK_MEM));
95?
96 ? ? return 0;
97 }
复制代码
? ? ?其中,TAIL_VAL宏假定系统为1字节对齐(否则请置CODE_SIZE为4字节)。因0xCC默认为四字节(对应于0xFFFFFFCC),故需用(CODE_TYPE)0xCC做类型转换,否则CHK_OVERRUN宏内if判断恒为真。
?
? ? ?该检查机制的缺点是仅用于检测写越界,且拷贝和解引用次数增多,访问效率有所降低。读越界后果通常并不严重,除非试图读取不可访问的区域,否则难以也不必检测。
?
? ? ?数据区内存越界通常会导致相邻的全局变量被意外改写。因此若已确定被越界改写的全局变量,则可通过工具查看符号表,根据地址顺序找到前面(通常向高地址越界)相邻的全局数据,然后在代码中排查访问该数据的地方,看看有哪些位置可能存在越界操作。
?
? ? ?有时,全局数据被意外改写并非内存越界导致,而是某指针(通常为野指针)意外地指向该数据地址,导致其内容被改写。野指针导致的内存改写往往后果严重且难以定位。此时,可编码检测全局数据发生变化的时机。若能结合堆栈回溯(Call Backtrace),则通常能很快地定位问题所在。
?
? ? ?修改只读数据区内容会引发段错误(Segmentation Fault),但这种低级失误并不常见。一种比较隐秘的缺陷是函数内试图修改由指针参数传入的只读字符串,详见《关于Linux系统basename函数缺陷的思考》一文。
?
? ? ?因其作用域限制,静态局部变量的内存越界相比全局变量越界更易发现和排查。
?
? ? 【对策】某些工具可帮助检查内存越界的问题,但并非万能。内存越界通常依赖于测试环境和测试数据,甚至在极端情况下才会出现,除非精心设计测试数据,否则工具也无能为力。此外,工具本身也有限制,甚至在某些大型项目中,工具变得完全不可用。
?
? ? ?与使用工具类似的是自行添加越界检测代码,如本节上文所示。但为求安全性而封装检测机制的做法在某种意义上得不偿失,既不及Java等高级语言的优雅,又损失了
C语言的简洁和高效。因此,根本的解决之道还是在于设计和编码的审慎周密。相比事后检测,更应注重事前预防。
?
? ? ?
编程时应重点走查代码中所有操作全局数据的地方,杜绝可能导致越界的操作,尤其注意内存覆写和拷贝函数memset/memcpy/memmove和数组下标访问。
?
? ? ?在内存拷贝时,必须确保目的空间大于或等于源空间。也可封装库函数使之具备安全校验功能,如:
?
复制代码
?1 /******************************************************************************
?2 * 函数名称: ?StrCopy
?3 * 功能说明: ?带长度安全拷贝字符串
?4 * 输入参数: ?dwSrcLen : 目的字符串缓冲区长度
?5 ? ? ? ? ? ? pSrcStr ?: 源字符串
?6 ? ? ? ? ? ? dwSrcLen : 源字符串长度(含终止符'\0')
?7 * 输出参数: ?pDstStr ?: 目的字符串缓冲区
?8 * 返回值 ?: ?成功: ptDest; 失败: "Nil"
?9 * 用法示例: ?char *pSrcStr = "HelloWorld"; char szDstStr[20] = {0};
10 ? ? ? ? ? ? StrCopy(szDstStr, sizeof(szDstStr), pSrcStr, strlen(pSrcStr))+1);
11 * 注意事项: ?拷贝长度为min(dwDstLen, dwSrcLen) - 1{Terminator}
12 ******************************************************************************/
13 char *StrCopy(char *pDstStr, int dwDstLen, char *pSrcStr, int dwSrcLen)
14 {
15 ? ? if(((NULL == pDstStr) || (NULL == pSrcStr)) ||
16 ? ? ? ?((0 == dwDstLen) || (0 == dwSrcLen)))
17 ? ? ? ? return (char *)"Nil";
18 ? ??
19 ? ? int dwActLen = (dwDstLen <= dwSrcLen) ? dwDstLen : dwSrcLen;
20 ? ? pDstStr[dwActLen - 1] = '\0';
21 ? ??
22 ? ? return strncpy(pDstStr, pSrcStr, dwActLen - 1);
23 }
复制代码
? ? ?在使用memcpy和strcpy拷贝字符串时应注意是否包括结束符(memcpy不自动拷贝’\0’)。
?
? ? ?按照下标访问数组元素前,可进行下标合法性校验:
?
复制代码
1 /* 数组下标合法性校验宏 */
2 #define CHECK_ARRAY_INDEX(index, maxIndex) do{\
3 ? ? if(index > maxIndex) { \
4 ? ? ?printf("Too large "#index": %d(Max: %d)!!!\n\r", index, maxIndex); \