Java Solaris 加入Sun中国技术社区 我的社区 注册说明
 
ISV Online
 
利用Discover检测内存错误
 
By Sun 中国工程研究院 Iris Zhu, 4/3/08  

Why Discover

 

比起Java语言,用C/C++语言编程在内存操作方面对程序员的要求更高。因为指针、数组等数据类型的使用既可以灵活的实现各种功能,又有可能稍不留神发生内存泄漏、内存越界访问,使系统发生严重错误甚至崩溃。下面的小程序总结了常见的几种内存错误,也是大家经常犯的,先来考考大家的眼力。


#include <stdio.h>

#include <stdlib.h>

int main()

{

         int *p;

         int *a=(int *)malloc(sizeof(int[5]));

         printf("a[5]=%d\n"a[5]);//ABR

         a[5]=5;//ABW

 

         p = (int * )malloc(0);//AZS

         p = (int * )malloc(sizeof(int));

 

         printf("*p=%d\n"*p);//UMR

 

         *(p+1)=1;//UAW

         free(p+1);//BFM

         free(p);

         *p=1;//FMW

         free(p);//DFM

}

<mem_err.c>

 

包括例子里面有的,常见的内存错误包括下面的很多种:

ABR/ABW

Array Bounds Read/Write

AZS

Allocation Zero Size

BFM

Freeing wrong Memory Block

BRP

Bad Realloc Parameter

DFM

Double Freeing Memory

FMR/FMW

Free Memory Read/Write

FRP

Freed Realloc Pointer

IMR/IMW

Invalid Memory Read/Write

PIR

Patially Initialized Read

UAR/UAW

UnAllocated Read/Write

UMR

Uninitialized Memory Read

 


如果你能轻易的说出上面的的英文简称代表的错误含义,并且和实际的代码立刻对应起来,那么恭喜你,事实证明你在内存使用方面有着良好的编码习惯和丰富的开发经验。不过很遗憾,即使这些问题没有发生在你负责的模块里,它们还是有可能出现在你所在的项目中。而且,实际的产品代码往往要比上面的例子复杂的多。如果产品带着这些错误进入系统测试阶段,即使测试人员和你一样有一双火眼金睛,要一下子把这些小地雷全挖出来也不那么容易了。

既然内存使用有这么多容易踩进去的陷阱,跟踪定位又这么辛苦,要是有个小工具既能帮助程序员及时检测出这些错误,又能准确定位到代码行就好了。假如这个工具“时髦”一点,不要钱,那就更合大家的胃口了。不错,这里要介绍的就是一个免费的小工具Discover


Discover
简介


这个工具的全称是Sun Memory Error Discovery Tool(简称Discover),主要用于侦测可执行文件中存在的内存错误。它是Sun公司发布的开发工具Sun Studio 12的附加软件,也可以作为运行于SPARC平台的GNU编译器-GCC的附加软件。目前只有SPARC平台的版本,先try try吧。


下载和安装Sun Studio


如果您已经安装了Sun Studio 11或者更低的版本,建议您先卸载然后安装Sun Studio 12。比如Sun Studio 11的卸载:

首先suroot用户,然后执行:

# cd /var/sadm/prod/com.sun.studio_11

# ./batch_uninstall_all

 

Sun Studio12的安装包从这里下载:
http://developers.sun.com/sunstudio/downloads/index.jsp
,可以选择直接下载tar包然后解压的安装方式,这样不是超户也能安装了。为了方便后面的安装,把Sun Studio安装程序的默认安装路径/opt作为installation_directory详细步骤如下:

  1. 下载 Sun Studio 12 tarfile ,并将其保存在临时目录(例如 /tmp)中。

  2. 通过键入以下命令,将 Sun Studio 12 tarfile 解压缩至选定的目录,并安装 Sun Studio 12 软件:

  3. bzcat download_directory/SunStudio12ml-solaris-sparc-200709-ii.tar.bz2 | /bin/tar -xf -

  4. 修改环境变量,将可执行文件的路径加入PATH中,manpage的路径加入MANPATH中。

  • 如果使用 C shell,请键入:

    • setenv PATH installation_directory/SUNWspro/bin:$PATH

    • setenv MANPATH installation_directory/SUNWspro/man:$MANPATH

       

  • 如果使用 Bourne shell Korn shell,请键入:

    PATH=installation_directory/SUNWspro/bin:$PATH; export PATH

    MANPATH=installation_directory/SUNWspro/man:$MANPATH; export MANPATH

     


下载和安装Discover

http://cooltools.sunsource.net/cmtdt/install.html页面选择SPARC版本,然后下载安装包SPROcmt.SPARCV9.tar。需要提供Sun Online Account 的用户名和密码,没有的话注册就可以了。

 

下面是安装步骤:

1.下载SPROcmt.SPARCV9.tar包到临时路径(比如/tmp),然后解压。解压缩后得到SPROprfns SPROcool两个文件夹,里面不仅包含了Discover,还包含CoolTools提供的其他一些工具。

         cd <my_download_dir>
         /bin/tar -xvf SPROcmt.SPARCV9.tar

2.安装需要root身份,所以请先转换为超户再执行后面的步骤。

3.pkgadd进行安装。作为Sun Studio12的附加工具,这两个文件夹中包含的各种工具将被安装到Sun Studio的默认安装路径/opt/SUNWspro的子目录中。

         pkgadd -d <my_download_dir> SPROprfns SPROcool

 

执行后Discover和其他工具都被安装在路径/opt/SUNWspro/extra/bin中,man pages 被放置在 /opt/SUNWspro/extra/man目录下。

4.修改环境变量如果使用 C shell,请键入:

    • setenv PATH /opt/SUNWspro/extra/bin:$PATH

    • setenv MANPATH /opt/SUNWspro/extra/man:$MANPATH

       

  • 如果使用 Bourne shell Korn shell,请键入:

    PATH=/opt/SUNWspro/extra/bin:$PATH; export PATH

    MANPATH=/opt/SUNWspro/extra/man:$MANPATH; export MANPATH

 

Discoverman page里还有三个实例可以参考。在线man page链接如下:

http://cooltools.sunsource.net/discover/discover.1.html


使用Discover

 

试试用Discover检测一下前面的例子程序。要使用Discover需要指定编译选项-xbinopt=prepare 和优化级别-xO[n],如果想在输出结果中看到代码,需要指定debug选项-g

         cc -g -xbinopt=prepare -xO1 mem_err.c

         discover a.out

         ./a.out

 

执行后得到后面的输出。进程中所有的内存访问和使用错误都会被明确的标识出来包括代码行、错误的简称和总的统计结果。为了方便大家理解,我们对第一个错误(ABR)的部分用斜体字添加了注解。

 

----------------侦测结果----------------------

ERROR (ABR): reading memory beyond array bounds at address 0x5001c (4 bytes) at:

<-------运行中发现错误(ERROR),错误代码为ABR,即读内存的越界访问。被访问的内存地址为0x5001c(位于heap)处的数据,越界4个字节(一个int型数组元素的长度)。紧接着是stacktracing信息,请按注解编号123顺序阅读。

 

         main() + 0x30000 [/home/lz195630/studio12/a.out:0x300c4]

<-------3)main函数地址为0x30000,越界访问发生在地址0x300c4(代码段)的地方,对应到代码行就是第7printf系统函数调用。

 

<mem_err.c:7>:

4: {

5:                   int *p;

6:                   int *a=(int *)malloc(sizeof(int[5]));

7:=>              printf("a[5]=%d\n"a[5]);//ABR

8:                   a[5]=5;//ABW

9:

10:                 p = (int * )malloc(0);//AZS

<------2)因为编译时指定了debug 选项-g,因此代码会被显示在输出中

 

_start() + 0x10994 [/home/lz195630/studio12/a.out:0x10a9c]

<------1)a.out执行的入口为_start(),地址为0x10994。运行至地址0x10a9c处调用了主函数main()

 

block at 0x50008 (20 bytes long) was allocated at:

malloc() + 0x188 [/opt/SUNWspro/prod/lib/postopt/libdiscover.so:0xf500]

main() + 0x30000 [/home/lz195630/studio12/a.out:0x30020]

<------这部分是关于分配内存的信息。分配内存发生在栈中地址0x30020处,即代码第6malloc系统调用。一共从内存地址0x50008处(位于heap)分配了20个字节(5int型数组元素)。而且,由于a.out已经被instrument,这里main函数在运行时首先在先调用了libdiscover中的malloc函数(地址为0x188),然后在libdiscover.so中运行到地址0xf500时真正调用了系统malloc函数。

 

<mem_err.c:6>:

3: int main()

4: {

5:                   int *p;

6:=>              int *a=(int *)malloc(sizeof(int[5]));

7:                   printf("a[5]=%d\n"a[5]);//ABR

8:                   a[5]=5;//ABW

9:

_start() + 0x10994 [/home/lz195630/studio12/a.out:0x10a9c]

--------------------ABR错误信息结束-------------------

 

ERROR (ABW): writing to memory beyond array bounds at address 0x5001c (4 bytes) at:

main() + 0x30000 [/home/lz195630/studio12/a.out:0x30148]

<mem_err.c:8>:

5:                   int *p;

6:                   int *a=(int *)malloc(sizeof(int[5]));

7:                   printf("a[5]=%d\n"a[5]);//ABR

8:=>              a[5]=5;//ABW

9:

10:                 p = (int * )malloc(0);//AZS

11:                 p = (int * )malloc(sizeof(int));

_start() + 0x10994 [/home/lz195630/studio12/a.out:0x10a9c]

block at 0x50008 (20 bytes long) was allocated at:

malloc() + 0x188 [/opt/SUNWspro/prod/lib/postopt/libdiscover.so:0xf500]

main() + 0x30000 [/home/lz195630/studio12/a.out:0x30020]

<mem_err.c:6>:

3:                   int main()

4:                   {

5:                            int *p;

6:=>                       int *a=(int *)malloc(sizeof(int[5]));

7:                            printf("a[5]=%d\n"a[5]);//ABR

8:                            a[5]=5;//ABW

9:

_start() + 0x10994 [/home/lz195630/studio12/a.out:0x10a9c]

WARNING (AZS): allocating zero size memory block at:

main() + 0x30000 [/home/lz195630/studio12/a.out:0x30150]

<mem_err.c:10>:

7:                            printf("a[5]=%d\n"a[5]);//ABR

8:                            a[5]=5;//ABW

9:

10:=>                    p = (int * )malloc(0);//AZS

11:                         p = (int * )malloc(sizeof(int));

12:

13:                         printf("*p=%d\n"*p);//UMR

_start() + 0x10994 [/home/lz195630/studio12/a.out:0x10a9c]

ERROR (UMR): accessing uninitialized data from address 0x520b0 (4 bytes) at:

main() + 0x30000 [/home/lz195630/studio12/a.out:0x30238]

<mem_err.c:13>:

10:                         p = (int * )malloc(0);//AZS

11:                         p = (int * )malloc(sizeof(int));

12:

13:=>                   printf("*p=%d\n"*p);//UMR

14:

15:                        *(p+1)=1;//UAW

16:                        free(p+1);//BFM

_start() + 0x10994 [/home/lz195630/studio12/a.out:0x10a9c]

block at 0x520b0 (4 bytes long) was allocated at:

malloc() + 0x188 [/opt/SUNWspro/prod/lib/postopt/libdiscover.so:0xf500]

main() + 0x30000 [/home/lz195630/studio12/a.out:0x30194]

<mem_err.c:11>:

8:                            a[5]=5;//ABW

9:

10:                          p = (int * )malloc(0);//AZS

11:=>                     p = (int * )malloc(sizeof(int));

12:

13:                          printf("*p=%d\n"*p);//UMR

14:

_start() + 0x10994 [/home/lz195630/studio12/a.out:0x10a9c]

ERROR (UAW): writing to unallocated memory at address 0x520b4 (4 bytes) at:

main() + 0x30000 [/home/lz195630/studio12/a.out:0x302bc]

<mem_err.c:15>:

12:

13:                          printf("*p=%d\n"*p);//UMR

14:

15:=>                     *(p+1)=1;//UAW

16:                          free(p+1);//BFM

17:                          free(p);

18:                          *p=1;//FMW

_start() + 0x10994 [/home/lz195630/studio12/a.out:0x10a9c]

ERROR (BFM): freeing wrong memory block at:

free() + 0xe8 [/opt/SUNWspro/prod/lib/postopt/libdiscover.so:0xf7d4]

main() + 0x30000 [/home/lz195630/studio12/a.out:0x302f8]

<mem_err.c:16>:

13:                            printf("*p=%d\n"*p);//UMR

14:

15:                            *(p+1)=1;//UAW

16:=>                       free(p+1);//BFM

17:                            free(p);

18:                            *p=1;//FMW

19:                            free(p);//DFM

_start() + 0x10994 [/home/lz195630/studio12/a.out:0x10a9c]

ERROR (FMW): writing to freed memory at address 0x520b0 (4 bytes) at:

main() + 0x30000 [/home/lz195630/studio12/a.out:0x303a4]

<mem_err.c:18>:

15:                            *(p+1)=1;//UAW

16:                            free(p+1);//BFM

17:                            free(p);

18:=>                       *p=1;//FMW

19:                            free(p);//DFM

20:          }

21:

_start() + 0x10994 [/home/lz195630/studio12/a.out:0x10a9c]

block at 0x520b0 (4 bytes long) was allocated at:

malloc() + 0x188 [/opt/SUNWspro/prod/lib/postopt/libdiscover.so:0xf500]

main() + 0x30000 [/home/lz195630/studio12/a.out:0x30194]

<mem_err.c:11>:

8:                            a[5]=5;//ABW

9:

10:                          p = (int * )malloc(0);//AZS

11:=>                     p = (int * )malloc(sizeof(int));

12:

13:                          printf("*p=%d\n"*p);//UMR

14:

_start() + 0x10994 [/home/lz195630/studio12/a.out:0x10a9c]

freed at:

__ped_free_handler() + 0x10b4c [/opt/SUNWspro/prod/lib/postopt/libdiscover.so:0x10d30]

free() + 0xe8 [/opt/SUNWspro/prod/lib/postopt/libdiscover.so:0xf7d4]

main() + 0x30000 [/home/lz195630/studio12/a.out:0x30334]

<mem_err.c:17>:

14:

15:                            *(p+1)=1;//UAW

16:                            free(p+1);//BFM

17:=>                      free(p);

18:                            *p=1;//FMW

19:                            free(p);//DFM

20:           }

_start() + 0x10994 [/home/lz195630/studio12/a.out:0x10a9c]

ERROR (DFM): double freeing memory at:

__ped_free_handler() + 0x10b4c [/opt/SUNWspro/prod/lib/postopt/libdiscover.so:0x10d0c]

free() + 0xe8 [/opt/SUNWspro/prod/lib/postopt/libdiscover.so:0xf7d4]

main() + 0x30000 [/home/lz195630/studio12/a.out:0x303dc]

<mem_err.c:19>:

16:                            free(p+1);//BFM

17:                            free(p);

18:                            *p=1;//FMW

19:=>                      free(p);//DFM

20: }

21:

22:

_start() + 0x10994 [/home/lz195630/studio12/a.out:0x10a9c]

DISCOVER SUMMARY:

unique errors : 7 (7 total)

unique warnings : 1 (1 total)

 

----------------程序运行结果---------------------

a[5]=0

*p=336056

 

可以看见例子程序中的各种错误类型,Discover工具都准确的定位在相应的代码行了!

 


总结

程序开发人员可以通过这个免费的小工具,在SPARC平台上快速的扫描代码,揪出内存访问和使用错误并准确定位代码行。结合Sun 公司发布的集成开发工具Sun Studio,高效率的生产出更健壮的代码。

 

 

几点说明

话说回来,Discover也不是包治百病。这个工具用于发现动态运行的程序中的错误。如果一段没有被执行的代码中存在内存错误,那这个小工具也无能为力了。另外,如果你使用的不是malloc/callocopertor newoperator delete等常用的方法进行动态内存的分配和释放,比如,你用的是brk/brk2直接改变用户进程中已经分配的空间大小,或者用 mmap/shmget系统函数来访问内存。 那么对不起,这个小工具恐怕也帮不上多少忙了。要想解决这些问题,Solaris 10上提供的动态跟踪工具DtraceDynamic Tracing)可以帮上您的忙。

 

有用的链接
Cool Tools
主页
http://cooltools.sunsource.net/index.html
Discover
使用手册
http://cooltools.sunsource.net/discover/DISCOVER_users_guide.pdf