用戶:Leonzhu211
外觀
一次malloc,兩次free
[編輯]原始碼 && 分析
[編輯]#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef struct _page{
char *url;
}page;
#define EIS_ASSERT(c) assert(!(c))
page *page_new()
{
page *me = (page*)calloc(1, sizeof(page));
if(!me){
EIS_ASSERT(1);
return NULL;
}
return me;
}
void page_print(page *me)
{
printf("%s\n", me->url);
}
void page_set_url(page *me, const char *url)
{
if(me){
me->url = strdup(url);
}
else{
EIS_ASSERT(1);
}
}
int page_delete(page *me)
{
if(!me){
EIS_ASSERT(1);
return -1;
}
if(me->url){
free(me->url);
}
free(me);
return 0;
}
int page_clean(page *me)
{
if(!me){
EIS_ASSERT(1);
return -1;
}
if(me->url){
free(me->url);
}
/*
这里缺少 me->url = NULL; 导致 me->url 会在 page_delete 中被再 free 一次。
在 page_clean 与 page_delete 之间,me->url 可能正好被分配另一个模块。
这样 page_delete 中第二次 free 相当于释放了另一个模块使用的指针。
进一步导致另一个模块继续使用这个指针时,会出现死机。
*/
}
int main()
{
page *t1 = page_new();
page *t2 = page_new();
page_set_url(t1, "http://www.eis/");
/* 释放 t1->url,但 t1->url 未被置 NULL */
page_clean(t1);
/* t2 请求内存空间存放 url,假设这里请求到的空间正好是刚刚释放的 t1->url */
page_set_url(t2, "http://www.eis/");
/* 由于 page_clean 中没有置 t1->url 为 NULL,delete 中 free(me->url) 实际是释放了 t2->url */
page_delete(t1);
/* 打印 t2->url,但不幸已经被 free,如果内存管理在 free 时填充 0xFE,打印的内存会比较奇怪 */
page_print(t2);
page_delete(t2);
}
編譯運行
[編輯]gcc 一次編譯後輸出為
$ gcc test1.c $ ./a.exe ?�a?�aww.eis/
擴展
[編輯]類似的情況也出現在其他成對函數,例如 http_open 與 http_close。如果在 http_close 後沒有將記錄的 fd 清零,可能引起第二次 http_close。如果兩次 http_close 中間,對應的 http fd 已經分配給其他模塊使用,將會導致其他模塊的 http 被莫名其妙關閉。
mantis bug
[編輯]參見:10535-33712: 播ocstream的過程中死機
局部變量未初始化
[編輯]原始碼 && 分析
[編輯]#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
int test_stack(){
/* 这个函数会导致 stack 中函数局部变量值为 -1 */
int a = -1;
return a;
}
int test1(int x)
{
/* flag 定义,但未初始化,平台上一般是直接使用 stack 中残留内容,即 test_stack 导致的 -1 */
int flag;
/* 下面这一段 if,并不是所有分支都会对 flag 进行赋值 */
if(x > 0){
flag = 1;
}
/* 这里缺少 else 语句对 flag 进行赋值 */
/* flag 未赋值,使用 stack 中残留内容,由于之前调用了 test_stack,导致值恰好为 -1,而不是想当然的 0 */
if(flag == 0){
return 0;
}
return 1;
}
int test2(int x)
{
/* 与 test1 对比,这里初始化 */
int flag = 0;
/* 下面这一段 if,并不是所有分支都会对 flag 进行赋值 */
if(x > 0){
flag = 1;
}
/* 这里缺少 else 语句对 flag 进行赋值,但 flag 已初始化,不会导致异常 */
/* flag 未赋值,使用函数初始化值 0 */
if(flag == 0){
return 0;
}
return 1;
}
int main()
{
/* 调用这个函数,改变 stack 中函数局部变量的初始值 */
test_stack();
printf("test1: %d\n", test1(0));
test_stack();
printf("test2: %d\n", test2(0));
}
編譯運行
[編輯]gcc 編譯後輸出為:
$ gcc test2.c $ ./a.exe test1: 1 test2: 0
mantis bug
[編輯]參見:926-30638: JVM 應用有時啟動不了;開關打印表現有差異
lint(靜態代碼檢查)
[編輯]對上述代碼執行 lint 檢查,會有下面的警告。與局部變量未初始化的警告信息見紅色粗體文字。
$ lint test2.c
Splint 3.1.1 --- 02 May 2003
test2.c: (in function test1)
test2.c:23:8: Variable flag used before definition
An rvalue is used that may not be initialized to a value on some execution
path. (Use -usedef to inhibit warning)
test2.c: (in function main)
test2.c:52:5: Return value (type int) ignored: test_stack()
Result returned by function call is not used. If this is intended, can cast
result to (void) to eliminate message. (Use -retvalint to inhibit warning)
test2.c:55:5: Return value (type int) ignored: test_stack()
test2.c:57:2: Path with no return in function declared to return int
There is a path through a function declared to return a value on which there
is no return statement. This means the execution may fall through without
returning a meaningful result to the caller. (Use -noret to inhibit warning)
test2.c:5:5: Function exported but not used outside test2: test_stack
A declaration is exported, but not used outside this module. Declaration can
use static qualifier. (Use -exportlocal to inhibit warning)
test2.c:9:1: Definition of test_stack
test2.c:11:5: Function exported but not used outside test2: test1
test2.c:28:1: Definition of test1
test2.c:30:5: Function exported but not used outside test2: test2
test2.c:47:1: Definition of test2
Finished checking --- 7 code warnings
越界寫
[編輯]示例一(strcat)
[編輯]原始碼 && 分析
[編輯]#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef struct _t{
char url[16];
struct _t *next;
}t;
int test_url(char *url)
{
/* 这里 strcat 导致 "hello" 超出 url 分配的内存,实际改写了 struct_t 中 next 成员 */
strcat(url, "hello");
return 0;
}
int test_t(t *t1)
{
printf("start while\n");
/* t1 的 next 被改写。非零,导致 while 条件满足 */
while(t1){
printf("t1: 0x%x\n", t1);
/* 第二次进入循环, t1 不是一个合法的指针,导致 t1->next 时,死机。 */
t1 = t1->next;
}
printf("end while\n");
}
int main()
{
t *t1 = (t *)calloc(1, sizeof(t));
strcpy(t1->url, "http://www.eis/");
test_url(t1->url);
printf("t1: 0x%x, t1->next: 0x%x\n", t1, t1->next);
test_t(t1);
free(t1);
}
編譯運行
[編輯]gcc 上一次編譯運行結果:
$ ./a.exe t1: 0x4b0008, t1->next: 0x6f6c6c65 start while t1: 0x4b0008 t1: 0x6f6c6c65 Segmentation fault (core dumped)
mantis bug
[編輯]參見:775-32898: EPG應用切歌死機。 stop()
示例二(全局結構)
[編輯]原始碼 && 分析
[編輯]#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
/* 定义一个结构类型 */
typedef struct _s{
int i;
char name[256];
}s;
/* 定义两个不同类型指针,s1 中越界,可能会导致 sp1、ip1 被改写 */
s *sp1;
int *ip1;
/* 定义一个全局变量,是结构,而不是结构的指针 */
s s1;
/* 再定义两个不同类型指针,s1 中越界,也可能会导致 sp1、ip1 被改写 */
s *sp2;
int *ip2;
int main()
{
s ss1, ss2;
int ii1, ii2;
char *name;
sp1 = &ss1;
sp2 = &ss2;
ip1 = &ii1;
ip2 = &ii2;
printf("&s1: 0x%x\n", &s1);
printf("&sp1: 0x%x\n", &sp1);
printf("&sp2: 0x%x\n", &sp2);
printf("&ip1: 0x%x\n", &ip1);
printf("&ip2: 0x%x\n", &ip2);
printf("\n");
printf("sp1: 0x%x\n", sp1);
printf("sp2: 0x%x\n", sp2);
printf("ip1: 0x%x\n", ip1);
printf("ip2: 0x%x\n", ip2);
/* 下面一句,类型出错,导致 memset 实际越界 */
memset(s1.name, 0, 256*sizeof(int));
/* 越界破坏了哪些指针不确定,应该跟编译器及系统有关 */
/* 从 linux gcc 编译后程序运行的结果看,上面的 memset 是改写了 sp1 和 sp2 中的内容 */
printf("\n");
printf("sp1: 0x%x\n", sp1);
printf("sp2: 0x%x\n", sp2);
printf("ip1: 0x%x\n", ip1);
printf("ip2: 0x%x\n", ip2);
/* 由于 sp1 中内容被改写,下面取 ->name 时,实际取到的是 name 成员在结构中的偏移,这里虽然出了问题,但不会死机 */
name = sp1->name;
printf("name: %x\n", name);
/* 如果要写点什么,就死定了 */
strcpy(sp1->name, "hello");
/* 下面这句打印应该是没有机会看到了 */
printf("after strcpy\n");
}
編譯運行
[編輯]在 linux 上用 gcc 編譯後運行:
root@linux ~/f/c> $ gcc -g test5.c root@linux ~/f/c> $ ./a.out &s1: 0x8049800 &sp1: 0x8049904 &sp2: 0x8049908 &ip1: 0x80497e4 &ip2: 0x80497e0 sp1: 0xbfffe410 sp2: 0xbfffe300 ip1: 0xbfffe2fc ip2: 0xbfffe2f8 sp1: 0x0 sp2: 0x0 ip1: 0xbfffe2fc ip2: 0xbfffe2f8 name: 4 Segmentation fault (core dumped)
從程序輸出看,gcc 對全局變量,會按類型安排內存區域,與變量在原始碼中出現的順序無關。 這樣,導致比較難找出越界寫的指針。
mantis bug
[編輯]參見:10534-33441: 開機後進入UI界面,按遙控器,ipanel_proc出現段錯誤死機
建議
[編輯]不直接使用結構作全局變量,而改用指針作全局變量。這樣,內存受 sdk xmem 統一管理,才有可能通過加打印確認是哪裏越界。 直接使用結構,越界時破壞的目標內存,與編譯器、平台關係太密切。在死機的模塊,即被越界寫壞內存的模塊,很難反向猜測是哪個模塊發生了越界寫。
xmem查越界
[編輯]- 定義 DEBUG_MALLOC_FILE_LINE。
- 定義 HAVE_RO_FILENAME。
- 在 sdk 模塊 XMalloc_malloc、XMalloc_malloc2、XMalloc_realloc 中返回有效 ptr 前,打印 filename、line、ptr。
- 在 sdk 模塊 XMalloc_free 打印 ptr。
- 由調試工具(gdb or VC) 確定由 sdk xmem 分配的、被改寫的內存。
- 由瀏覽器初始化時的 log 確定被改寫內存所在的 segment 及 blocksize。
- 在 log 中找符合以下特點的內存:
- 第一個
- 位於被改寫內存之前
- 與被改寫內存的偏移為 blocksize 整數倍
- 已經被分配,但未被釋放
- 根據該內存在分配置打印的 filename、line,檢查相關代碼,主要是被分配出的內存的使用情況,重點檢查 memset, strcpy 等會改寫內容的標準 C 函數。