程序崩了,咋办?

1. 内存溢出是啥?

举个栈溢出的例子。所有的在函数内部申请的局部变量都是保存在栈中的。比如:

 
 
  1. #include <string.h> 
  2.  
  3. void fn(void
  4.     char a[100]; 
  5.     char *p = a; 
  6.     bzero(p, 1000); 
  7.  
  8. int main(int argc, char *argv[]) 
  9.     fn(); 
  10.     return 0; 

这里,数组a就会保存在栈中。当栈溢出时,最容易出现的问题是返回指针被修改,进而函数返回时会发现返回的代码段指针错误,提示:“stack smashing detected...":

 
 
  1. peter@ubuntu-910:~/codes/testspace$ ./testspace  
  2. *** stack smashing detected ***: <unknown> terminated 
  3. ======= Backtrace: ========= 
  4. /lib/tls/i686/cmov/libc.so.6(__fortify_fail+0x48)[0x2f7008] 
  5. /lib/tls/i686/cmov/libc.so.6(__fortify_fail+0x0)[0x2f6fc0] 
  6. [0x80484b2] 
  7. [0x0] 
  8. ======= Memory map: ======== 
  9. 00215000-00216000 r-xp 00000000 00:00 0          [vdso] 
  10. 00216000-00354000 r-xp 00000000 08:07 5206       /lib/tls/i686/cmov/libc-2.10.1.so 
  11. 00354000-00355000 ---p 0013e000 08:07 5206       /lib/tls/i686/cmov/libc-2.10.1.so 
  12. 00355000-00357000 r--p 0013e000 08:07 5206       /lib/tls/i686/cmov/libc-2.10.1.so 
  13. 00357000-00358000 rw-p 00140000 08:07 5206       /lib/tls/i686/cmov/libc-2.10.1.so 
  14. 00358000-0035b000 rw-p 00000000 00:00 0  
  15. 00c38000-00c4d000 r-xp 00000000 08:07 5220       /lib/tls/i686/cmov/libpthread-2.10.1.so 
  16. 00c4d000-00c4e000 r--p 00014000 08:07 5220       /lib/tls/i686/cmov/libpthread-2.10.1.so 
  17. 00c4e000-00c4f000 rw-p 00015000 08:07 5220       /lib/tls/i686/cmov/libpthread-2.10.1.so 
  18. 00c4f000-00c51000 rw-p 00000000 00:00 0  
  19. 00cfc000-00d18000 r-xp 00000000 08:07 4652       /lib/libgcc_s.so.1 
  20. 00d18000-00d19000 r--p 0001b000 08:07 4652       /lib/libgcc_s.so.1 
  21. 00d19000-00d1a000 rw-p 0001c000 08:07 4652       /lib/libgcc_s.so.1 
  22. 00f63000-00f7e000 r-xp 00000000 08:07 5168       /lib/ld-2.10.1.so 
  23. 00f7e000-00f7f000 r--p 0001a000 08:07 5168       /lib/ld-2.10.1.so 
  24. 00f7f000-00f80000 rw-p 0001b000 08:07 5168       /lib/ld-2.10.1.so 
  25. 08048000-08049000 r-xp 00000000 08:08 264941     /home/peter/codes/testspace/testspace 
  26. 08049000-0804a000 r--p 00000000 08:08 264941     /home/peter/codes/testspace/testspace 
  27. 0804a000-0804b000 rw-p 00001000 08:08 264941     /home/peter/codes/testspace/testspace 
  28. 08a74000-08a95000 rw-p 00000000 00:00 0          [heap] 
  29. b785e000-b7860000 rw-p 00000000 00:00 0  
  30. b7874000-b7876000 rw-p 00000000 00:00 0  
  31. bffad000-bffc2000 rw-p 00000000 00:00 0          [stack] 
  32. 已放弃 

这类问题其实比较简单,起码在linux系统中,在程序崩溃的同时,系统往往会打印出一些backtrace和memory map之类的东西,其中backtrace可以非常有效的让我们发现栈溢出发生的函数位置。如果函数比较深(比如我们这种情况),或者系统没有打印bt的信息,而是直接段错误了,可以用gdb跟踪,然后用backtrace命令看:

 
 
  1. peter@ubuntu-910:~/codes/testspace$ gdb 
  2. GNU gdb (GDB) 7.0-ubuntu 
  3. Copyright (C) 2009 Free Software Foundation, Inc. 
  4. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> 
  5. This is free software: you are free to change and redistribute it. 
  6. There is NO WARRANTY, to the extent permitted by law.  Type "show copying" 
  7. and "show warranty" for details. 
  8. This GDB was configured as "i486-linux-gnu"
  9. For bug reporting instructions, please see: 
  10. <http://www.gnu.org/software/gdb/bugs/>. 
  11. (gdb) file testspace  
  12. Reading symbols from /home/peter/codes/testspace/testspace...done. 
  13. (gdb) r 
  14. Starting program: /home/peter/codes/testspace/testspace  
  15. [Thread debugging using libthread_db enabled] 
  16. *** stack smashing detected ***: <unknown> terminated 
  17. ======= Backtrace: ========= 
  18. /lib/tls/i686/cmov/libc.so.6(__fortify_fail+0x48)[0x228008] 
  19. /lib/tls/i686/cmov/libc.so.6(__fortify_fail+0x0)[0x227fc0] 
  20. [0x80484b2] 
  21. [0x0] 
  22. ======= Memory map: ======== 
  23. 00110000-0012b000 r-xp 00000000 08:07 5168       /lib/ld-2.10.1.so 
  24. 0012b000-0012c000 r--p 0001a000 08:07 5168       /lib/ld-2.10.1.so 
  25. 0012c000-0012d000 rw-p 0001b000 08:07 5168       /lib/ld-2.10.1.so 
  26. 0012d000-0012e000 r-xp 00000000 00:00 0          [vdso] 
  27. 0012e000-00143000 r-xp 00000000 08:07 5220       /lib/tls/i686/cmov/libpthread-2.10.1.so 
  28. 00143000-00144000 r--p 00014000 08:07 5220       /lib/tls/i686/cmov/libpthread-2.10.1.so 
  29. 00144000-00145000 rw-p 00015000 08:07 5220       /lib/tls/i686/cmov/libpthread-2.10.1.so 
  30. 00145000-00147000 rw-p 00000000 00:00 0  
  31. 00147000-00285000 r-xp 00000000 08:07 5206       /lib/tls/i686/cmov/libc-2.10.1.so 
  32. 00285000-00286000 ---p 0013e000 08:07 5206       /lib/tls/i686/cmov/libc-2.10.1.so 
  33. 00286000-00288000 r--p 0013e000 08:07 5206       /lib/tls/i686/cmov/libc-2.10.1.so 
  34. 00288000-00289000 rw-p 00140000 08:07 5206       /lib/tls/i686/cmov/libc-2.10.1.so 
  35. 00289000-0028c000 rw-p 00000000 00:00 0  
  36. 0028c000-002a8000 r-xp 00000000 08:07 4652       /lib/libgcc_s.so.1 
  37. 002a8000-002a9000 r--p 0001b000 08:07 4652       /lib/libgcc_s.so.1 
  38. 002a9000-002aa000 rw-p 0001c000 08:07 4652       /lib/libgcc_s.so.1 
  39. 08048000-08049000 r-xp 00000000 08:08 264941     /home/peter/codes/testspace/testspace 
  40. 08049000-0804a000 r--p 00000000 08:08 264941     /home/peter/codes/testspace/testspace 
  41. 0804a000-0804b000 rw-p 00001000 08:08 264941     /home/peter/codes/testspace/testspace 
  42. 0804b000-0806c000 rw-p 00000000 00:00 0          [heap] 
  43. b7fe8000-b7fea000 rw-p 00000000 00:00 0  
  44. b7ffe000-b8000000 rw-p 00000000 00:00 0  
  45. bffeb000-c0000000 rw-p 00000000 00:00 0          [stack] 
  46.  
  47. Program received signal SIGABRT, Aborted. 
  48. 0x0012d422 in __kernel_vsyscall () 
  49. (gdb) bt 
  50. #0  0x0012d422 in __kernel_vsyscall () 
  51. #1  0x001714d1 in raise () from /lib/tls/i686/cmov/libc.so.6 
  52. #2  0x00174932 in abort () from /lib/tls/i686/cmov/libc.so.6 
  53. #3  0x001a7fc5 in ?? () from /lib/tls/i686/cmov/libc.so.6 
  54. #4  0x00228008 in __fortify_fail () from /lib/tls/i686/cmov/libc.so.6 
  55. #5  0x00227fc0 in __stack_chk_fail () from /lib/tls/i686/cmov/libc.so.6 
  56. #6  0x080484b2 in fn () at test.c:8 
  57. #7  0x00000000 in ?? () 

这里便看到了:

 
 
  1. # #6  0x080484b2 in fn () at test.c:8  

以便我们锁定问题。

很多时候,当内存溢出问题不严重时,并不会直接终止我们程序的运行。但是,我们会在调试程序中碰到非常奇怪的问题,比如某一个变量无缘无故变成乱码,不管是在堆中,还是栈中。这便很有可能是指针的错误使用导致的。这种情况出现时,一种调试方法是:使用gdb加载程序,并用watch锁定被改成乱码的变量。这样,如果这个变量被修改,程序便会停下来,我们就可以看到底是哪条语句修改了这个程序。

2. 内存泄漏

内存泄漏只会是在堆中申请的内存没有释放而导致的。也就是,我们在malloc()后没有及时的进行free()。这里,可以利用现有的一些软件帮助我们调试,如Valgrind(http://valgrind.org)。使用方法请参见其主页的帮助文档。

3. 缓冲区:能大就大点

很多内存溢出的问题都是因为缓冲区不够大。因此,我们在开辟缓冲区的时候,一定要给使用打出余量,不能每次想申请多少就申请多少,要想到这部分内存的用途,并进行上限估计。估不出来的时候尽量放大点。

当然,不能随便的放大,可能会出现问题,比如:栈内申请空间过大,程序一使用变量直接段错误。

4. snprintf比sprintf好,那么strncpy就比strcpy好?!

有经验的前辈总是这样说:”小同志,不要随便用sprintf(),要用snprintf(),这样如果打印的数据溢出了可以保护呀!“我们发现,这样做虽然要多写一个参数,但是的确比原来的程序安全了!何乐不为。

之后,我们又看到了strncpy(),一看就高兴!又带一个n!马上用了一下:

 
 
  1. #include <stdio.h> 
  2. #include <string.h> 
  3.  
  4. void fn(void
  5.     char a[10]; 
  6.     strncpy(a, "hello", 100); 
  7.  
  8. int main(int argc, char *argv[]) 
  9.     fn(); 
  10.     return 0; 
  11. }

很好,程序崩了。

有心的人早就发现了,长度100明显不对阿。可是有人也就想了,为啥10个字节还不够放"hello"这些玩意呢?man一下才知道:

 
 
  1. STRCPY(3)                                              Linux Programmer's Manual                                              STRCPY(3) 
  2.  
  3. NAME 
  4.        strcpy, strncpy - copy a string 
  5.  
  6. SYNOPSIS 
  7.        #include <string.h> 
  8.  
  9.        char *strcpy(char *dest, const char *src); 
  10.  
  11.        char *strncpy(char *dest, const char *src, size_t n); 
  12.  
  13. DESCRIPTION 
  14.        The  strcpy() function copies the string pointed to by src, including the terminating null byte ('\0'), to the buffer pointed to 
  15.        by dest.  The strings may not overlap, and the destination string dest must be large enough to receive the copy. 
  16.  
  17.        The strncpy() function is similar, except that at most n bytes of src are copied.  Warning: If there is no null byte  among  the 
  18.        first n bytes of src, the string placed in dest will not be null terminated. 
  19.  
  20.        If the length of src is less than n, strncpy() pads the remainder of dest with null bytes. 
  21.  
  22.        A simple implementation of strncpy() might be: 
  23.  
  24.            char
  25.            strncpy(char *dest, const char *src, size_t n){ 
  26.                size_t i; 
  27.  
  28.                for (i = 0 ; i < n && src[i] != '\0' ; i++) 
  29.                    dest[i] = src[i]; 
  30.                for ( ; i < n ; i++) 
  31.                    dest[i] = '\0'
  32.  
  33.                return dest; 
  34.            } 
关键是最后的一句:
 
 
  1. "If the length of src is less than n, strncpy() pads the remainder of dest 
  2. with null bytes. " 

也就是说,strncpy并不仅仅是做一个n长度的保护,而会把剩下的字符清为0x00。要知道,snprintf()是没这档子事情的。所以,我们要记住:

snprintf()总是比sprintf()安全,但是strncpy()和strcpy()比就不一定了。 


总之,程序出问题是怎么也避免不了的。特别是出现诡异的问题的时候,要学会冷静分析产生问题的结果。往往这些问题都是我们编程过程中的错误导致的,而不是我们见鬼了。要对自己解决问题的能力有信心嘛!

程序这东西就是这样,用好了,越用越顺手;用不好,死都不知道怎么死的。

本文出自 “LoudMouth Peter” 博客,请务必保留此出处http://xzpeter.blog.51cto.com/783279/329052

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值