1. 内存检查
实际开发中经常出现的问题是内存泄漏,用开源的代码加入检测一下是个很实用的方法
比如用memwatch,它简单适用,只需要memwatch.h和memwatch.c两个文件加入源代码编译;另外记得需要检测的*.c加入memwatch.h这个头文件;最后编译选项加入MEMWATCH。重新编译代码,在memwatch.c目录下产生memwatch.log文件:
============= MEMWATCH 2.71 Copyright (C) 1992-1999 Johan Lindh =============
Started at Wed Jan 28 21:50:56 2008
Modes: __STDC__ 32-bit mwDWORD==(unsigned long)
mwROUNDALLOC==4 sizeof(mwData)==32 mwDataSize==32
Stopped at Wed Jan 28 21:51:59 2008
unfreed: <1> main.c(371), 8192 bytes at 0x805f7fc {00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................}
Memory usage statistics (global):
N)umber of allocations made: 1
L)argest memory usage : 8192
T)otal of all alloc() calls: 8192
U)nfreed bytes totals : 8192
上面log文件指出,在main.c被执行到第371行时所分配的内存仍未被释放,该段内存的大小为8192byte,查看第371行代码
char *commandBuf=(char *)malloc(MAX_INPUT_LEN);
这里没有释放,需要添加代码free。
2. Crash
另外一个问题就是系统crash问题,这个可以利用gdb连接到调试目标板上,当crash的时候用bt看栈信息;但是推荐在代码中直接利用加sigsegv.c和sigsegv.h,另外在main函数中加调用setup_sigsegv();即可,下面是写的一个测试crash函数,因为die函数中NULL指针赋值了,运行main的时候会直接挂掉的
......
/*
* Function: die
* Purpose: test for backtrace
* Arguments:
* Returns:
*/
int die()
{
char *err = NULL;
strcpy(err, "die");
return 0;
}
/*
* Function: main
* Purpose:
* Arguments:
* Returns:
*/
int main(int argc, char* argv[])
{
int rc;
pthread_t pid;
pthread_t pid_server;
setup_sigsegv();
rc = pthread_create(&pid, NULL, (void *) &dbg_entry, NULL);
if (rc < 0)
printf("error:%s/n", strerror(rc));
rc = pthread_create(&pid_server, NULL, (void *) &dbg_server1,
(void *) "/var/dbg.ipc");
if (rc < 0)
printf("error:%s/n", strerror(rc));
die();
while (1);
return 0;
}
这里是挂掉后打印出来的信息,在地址0x8049f49的地方挂掉的,此函数运行结束后PC的地址是0x8049fd9,
### BEGIN LOG - DATE: 081025, TIME: 145642 ###
./debug
Segmentation Fault!
info.si_signo = 11
info.si_errno = 0
info.si_code = 1 (SEGV_MAPERR)
info.si_addr = (nil)
reg[00] = 0x00000033
reg[01] = 0x00000000
reg[02] = 0x0000007b
reg[03] = 0x0000007b
reg[04] = 0xbffdd670
reg[05] = 0xbffdd6e4
reg[06] = 0xbffdd638
reg[07] = 0xbffdd638
reg[08] = 0x00d25ff4
reg[09] = 0x00000000
reg[10] = 0xb75df4c4
reg[11] = 0x00000000
reg[12] = 0x0000000e
reg[13] = 0x00000006
reg[14] = 0x08049f49
reg[15] = 0x00000073
reg[16] = 0x00010246
reg[17] = 0xbffdd638
reg[18] = 0x0000007b
Stack trace:
1: 0x8049f49 <(null)+134520649> (./debug)
2: 0x8049fd9 <(null)+134520793> (./debug)
3: 0xc15e23 <__libc_start_main+211> (/lib/tls/libc.so.6)
End of stack trace
[huangyonggang@localhost bin.x86]$
### END LOG - DATE: 081025, TIME: 145652 ###
于是
反汇编[huangyonggang@localhost bin.x86]$ objdump -D debug &>log
或者[huangyonggang@localhost bin.x86]$ strace debug &>log1
可以得到后面的,根据上面的栈信息,可以找到8049f49地址就是挂掉的位置,这里就是die函数中的串拷贝,接着找8049fd9地址,就是die函数结束后PC的地址
......
08049f46 <die>:
8049f46: 55 push %ebp
8049f47: 89 e5 mov %esp,%ebp
8049f49: c7 05 00 00 00 00 64 movl $0x656964,0x0
8049f50: 69 65 00
8049f53: b8 00 00 00 00 mov $0x0,%eax
8049f58: c9 leave
8049f59: c3 ret
08049f5a <main>:
8049f5a: 55 push %ebp
8049f5b: 89 e5 mov %esp,%ebp
8049f5d: 83 ec 08 sub $0x8,%esp
8049f60: 83 e4 f0 and $0xfffffff0,%esp
8049f63: 83 ec 10 sub $0x10,%esp
8049f66: e8 82 08 00 00 call 804a7ed <setup_sigsegv>
8049f6b: 6a 00 push $0x0
8049f6d: 68 4b 97 04 08 push $0x804974b
8049f72: 6a 00 push $0x0
8049f74: 8d 45 fc lea 0xfffffffc(%ebp),%eax
8049f77: 50 push %eax
8049f78: e8 43 ef ff ff call 8048ec0 <pthread_create@plt>
8049f7d: 83 c4 10 add $0x10,%esp
8049f80: 85 c0 test %eax,%eax
8049f82: 79 1a jns 8049f9e <main+0x44>
8049f84: 83 ec 0c sub $0xc,%esp
8049f87: 50 push %eax
8049f88: e8 63 ef ff ff call 8048ef0 <strerror@plt>
8049f8d: 83 c4 08 add $0x8,%esp
8049f90: 50 push %eax
8049f91: 68 bf e3 04 08 push $0x804e3bf
8049f96: e8 63 f7 ff ff call 80496fe <printf>
8049f9b: 83 c4 10 add $0x10,%esp
8049f9e: 68 c9 e3 04 08 push $0x804e3c9
8049fa3: 68 79 9b 04 08 push $0x8049b79
8049fa8: 6a 00 push $0x0
8049faa: 8d 45 f8 lea 0xfffffff8(%ebp),%eax
8049fad: 50 push %eax
8049fae: e8 0d ef ff ff call 8048ec0 <pthread_create@plt>
8049fb3: 83 c4 10 add $0x10,%esp
8049fb6: 85 c0 test %eax,%eax
8049fb8: 79 1a jns 8049fd4 <main+0x7a>
8049fba: 83 ec 0c sub $0xc,%esp
8049fbd: 50 push %eax
8049fbe: e8 2d ef ff ff call 8048ef0 <strerror@plt>
8049fc3: 83 c4 08 add $0x8,%esp
8049fc6: 50 push %eax
8049fc7: 68 bf e3 04 08 push $0x804e3bf
8049fcc: e8 2d f7 ff ff call 80496fe <printf>
8049fd1: 83 c4 10 add $0x10,%esp
8049fd4: e8 6d ff ff ff call 8049f46 <die>
8049fd9: eb fe jmp 8049fd9 <main+0x7f>
8049fdb: 90 nop
......
更进一步的设计是把这些打印信息打到syslog放到储存中去,这样的话,系统crash过后,可以拿出来分析
3. sigsegv的实现
分析sigsegv的实现,SIGSEGV信号意味着指针所对应的地址是无效地址,没有物理内存对应该地址,绑定信号处理函数signal_segv,当段错误出现的时候会处理;实际应用中可能crash是因为其它比如信号SIGBUS,这个信号是因为试图访问一块无文件内容对应的内存区域,比如超过文件尾的内存区域;所以多绑定几个经常引起死机的信号是必需的
int setup_sigsegv() {
struct sigaction action;
memset(&action, 0, sizeof(action));
action.sa_sigaction = signal_segv;
action.sa_flags = SA_SIGINFO;
if(sigaction(SIGSEGV, &action, NULL) < 0) {
perror("sigaction");
return 0;
}
return 1;
}
4. 小结
嵌入式的ARM和PPC下sigsegv不好用,只可以到两层,不像x86可以到很多层,经常找不到crash问题