您的位置:首页 > 运维架构 > Linux

Loongson1B板上GDB调试段错误方法

2011-11-30 13:07 176 查看

文档简介

项目有出现段错误BUGS,经过Step by step探究段错误原因,虽然并没有从根本上解决QT4段错误的问题,但是总结出了一种比较有效的跟踪段错误的方法,本文档的目的在于介绍利用gdb工具远程调试Loongson 1B板上段错误的方法,共享出来希望对大家能够有所帮助。

文档总体来说分为四个部分:

A.      段错误的产生

B.      段错误调试方案

C.      编译相关工具

D.      调试跟踪过程

 

1. 段错误的产生

段错误产生的原因是访问的内存超出了系统给这个程序所设定的内存空间,一旦一个程序发生了越界访问,cpu就会产生相应的异常保护,于是Segmentation Fault就出现了,下面列举了出现段错误的几个例子:

A.      访问不存在的内存地址

#include<stdio.h>

#include<stdlib.h>

void main()

{

int *ptr = NULL;

*ptr = 0; //段错误位置

}


B.      访问系统保护的内存地址

#include<stdio.h>

#include<stdlib.h>

void main()

{

int *ptr = (int *)0;

*ptr = 100; ;//段错误位置

}


C.      访问只读的内存地址

#include<stdio.h>

#include<stdlib.h>

#include<string.h>

void main()

{

char *ptr = "test";

strcpy(ptr, "TEST");//段错误位置

}


D.      栈溢出

#include<stdio.h>

#include<stdlib.h>

void main()

{

main();//段错误位置

}

 

2. 段错误调试方案

2.1 常用方法

一般情况我们都在程序中会打印一些调试语句以方便定位及分析程序的执行情况,可以使用条件编译指令#ifdef DEBUG和#endif把printf、perror、qDebug(QT)等函数包含起来。这样在程序编译时,如果加上-DDEBUG参数就能查看调试信息;否则不加该参数就不会显示调试信息。这样通过打印调试信息可以定位到程序代码中出现段错误大概位置。

但是有时候一个相对庞大的程序,这种做法显然效率较低,这里我们采用一个常用的工具来跟踪段错误:gdb调试工具。

2.2 GDB调试

GNU工具链中的GDB可以读linux开发的应用程序进行调试,它能够控制程序的执行、查看和改变程序的变量、分析程序的内在结构、研究崩溃程序的核心文件等等。在很多情况下,用户需要对一个应用程序进行反复调试,特别是复杂的程序,都可以采用GDB方法调试,但是由于嵌入式系统资源有限,一般不能直接在目标系统上进行调试,通常采用gdb+gdbserver的方式进行调试。gdbserver在目标系统中运行,而gdb则在linux服务器上运行。

除了采用gdb+gdbserver的调试方法,我们还可以利用系统段错误会触发SIGSEGV信号,通过man 7 signal,可以看到SIGSEGV默认的handler会打印段错误出错信息,并产生core文件,由此我们可以借助于程序异常退出时生成的core文件中的调试信息,使用gdb工具来调试程序中的段错误。

下边我们会介绍如何交叉编译LOONGSON-MIPS平台的gdb调试工具。

 

3. 编译GDB调试工具

一般Linux发行版中都有一个可以运行的gdb,但由于我们需要调试的是嵌入式开发板上的应用程序,所以不能直接使用该发行版中的gdb来做远程调试,而要获取gdb的源代码包,针对LOONGSON-MIPS平台作一个简单配置,重新交叉编译得到相应gdb调试工具。

3.1 编译环境

操作系统:ubuntu11.04

编译工具链:Gcc-cross-3.4.6-2f

3.2 编译过程

gdb的源代码包可以在公司服务器中的软件文件夹中找到gdb-7.3版本,也直接到GNU的网站上下载:

http://www.gnu.org/software/gdb/download/

下载了gdb-7.3版本,然后解压到任意目录,例如:/home/loongson。

进入/home/loongson目录,配置编译步骤如下:

#cd gdb-7.3

 

#./configure --target=mipsel-linux --prefix=/opt/mipsel-linux-gdb -v

 

#make

 

#make install

 

#export PATH=$PATH: /opt/mipsel-linux-gdb

 

进入gdbserver目录(在gdb-7.3/gdb/gdbserver):

 

#./configure --target=mipsel-linux --host=mipsel-linux

 

#make CC=/home/cpu/gcc-3.4.6-2f/bin/mipsel-linux-gcc

 

(这一步要指定mipsel-linux-gcc的位置,具体的问题根据自己系统中的交叉编译工具链修改这个命令行)

编译的过程可能会遇到一下几个错误:

hostio.c:67: 错误: `PATH_MAX' undeclared (first use in this function)

 

thread-db.c:704: 错误: `PATH_MAX' undeclared (first use in this function)

 

需要在hostio.c和thread-db.c文件中添加一个宏定义

#define PATH_MAX 1024 就能解决问题。

 

gdbreplay.c: In function `main':

gdbreplay.c:372: 警告: 'fromgdb' might be used uninitialized in this function

 

解决方法为

#vi gdbreplay.c +372

然后将int fromgdb;修改成int fromgdb=0;即可解决问题

 

编译完成后在本文件夹可以看到生成gdbserver可执行二进制文件,使用

#mipsel-linux-readelf  -d  gdbserver

查看其需要的动态链接库,通过nfs或者tftp将gdbserver及其所需要的库移植目标板,下面就可以用gdb+gdbserver调试我们开发板上的程序了。

 

4. GDB调试过程

Gdb与gdbserver之间通讯是通过网络来实现远程调试,所以我们需要根据目标板与linxu服务器端的IP地址来建立连接。查询到目标板IP:192.168.20.210, linux服务器IP:192.168.20.105。

PS.命令行为:[LOONSON@Loongson-gz:/]#则表示在目标板上执行;#表示在PC端执行。

 4.1 编译测试程序

下边我们编写一个有段错误的小程序来测试一下过程,程序如下:

1 #include<stdio.h>

2 #include<stdlib.h>

3

4 dummy_function (void)

5 {

6         unsigned char *ptr = 0x00;

7         *ptr = 0x00;

8 }

9

10 int main (void)

11 {

12         dummy_function ();

13         return 0;

14 }


交叉编译该程序的时候需注意gcc要加"-g -rdynamic"参数编译,选项-g是以便后面可以对编译的程序进行GDB调试选项,而-rdynamic 用来通知链接器将所有符号添加到动态符号表中。

编译该测试小程序:

# mipsel-linux-gcc -g -rdynamic -o test  test.c

将test可执行文件及其动态库移植到目标板子上,然后开始使用gdb调试该特使程序。

4.2 建立远程调试网络连接

要进行gdb调试,首先要在目标系统上启动gdbserver服务。在gdbserver所在目录下输入命令:

在目标板上执行:

[LOONSON@Loongson-gz:/]#gdbserver  192.168.20.105:15000 /test

表示目标板上的gdbserver接收来自linux服务器192.168.20.105的调试指令,gdbserver监听15000 端口,然后启动test程序;

出现提示:

Process /test created; pid = 300

Listening on port 15000

 

(然后转到linux服务器上)

 

进入测试程序目录中执行一下命令:

# /opt/mipsel-linux-gdb/bin/mipsel-linux-gdb  ./test  

命令行输入后,出现如下提示:

GNU gdb (GDB) 7.3

Copyright (C) 2011 Free Software Foundation, Inc.

License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software: you are free to change and redistribute it.

There is NO WARRANTY, to the extent permitted by law.  Type "show copying"

and "show warranty" for details.

This GDB was configured as "--host=i686-pc-linux-gnu --target=mipsel-linux".

For bug reporting instructions, please see:

<http://www.gnu.org/software/gdb/bugs/>...

Reading symbols from /home/ethan/Desktop/test/test...done.

(gdb)

然后在gdb中接着执行:

(gdb)target remote 192.168.20.210:15000

里边的192.168.20.210是目标板上的IP。

然后linux服务器端出现一下提示:

(gdb) target remote 192.168.20.210:15000

Remote debugging using 192.168.20.210:15000

warning: Unable to find dynamic linker breakpoint function.

GDB will be unable to debug shared library initializers

and track explicitly loaded dynamic code.

0x2aaa87b0 in ?? ()

转到目标板上,也同时出现以下提示:

[LOONSON@Loongson-gz:/]#gdbserver  192.168.20.105:15000 /test

Process /test created; pid = 300

Listening on port 15000

Remote debugging from host 192.168.20.105

这个时候linux服务器的gdb与目标板的gdbserver之间就已经建立连接了,只要我们在linux服务器端发送相关的调试指令,目标板的gdbserver就会接收到相应指令执行相应动作,同时将调试信息反馈会linxu服务器端。

4.3 调试程序

现在调试的是我们编写的测试程序,在linux服务器端gdb中执行一下调试指令可以得到相关调试信息如下:(gdb相关调试指令可输入helo all查询)

(gdb) l                       //list :显示程序中的代码

3  

4   dummy_function (void)

5   {

6           unsigned char *ptr = 0x00;

7           *ptr = 0x00;

8   }

9  

10  int main (void)

11  {

12     dummy_function ();

(gdb)                          //同上一个命令

13          return 0;

14  }

(gdb) b 6                      //break :在程序中设置断点

Breakpoint 1 at 0x400918: file test.c, line 6.

(gdb) c                        //continue :使程序继续运行

Continuing.

warning: Could not load shared library symbols for 3 libraries, e.g. /lib/libgcc_s.so.1.

Use the "info sharedlibrary" command to see the complete listing.

Do you need "set solib-search-path" or "set sysroot"?

Breakpoint 1, dummy_function () at test.c:6

6           unsigned char *ptr = 0x00;

(gdb) s                      //step :单步执行语句

7           *ptr = 0x00;

(gdb) s                      // step :单步执行语句

Program received signal SIGSEGV, Segmentation fault.

0x00400924 in dummy_function () at test.c:7

7           *ptr = 0x00;

(gdb) s                     // step :单步执行语句

Program terminated with signal SIGSEGV, Segmentation fault.

The program no longer exists.

(gdb) s                     // step :单步执行语句

The program is not being run.

(gdb)

到这里就很清楚的能看到程序出现Segmentation fault,而且提示段错误出现的位置为dummy_function ()函数中的*ptr = 0x00;在.c实现文件中的位置为第七行。

我们回过头来分析这个位置发生段错误的原因:

6         unsigned char *ptr = 0x00;

7         *ptr = 0x00;

可以很容易看出其是在操作内存为0的内存区域,这个内存区域通常是系统不可访问的禁区,所以就出现段错误了。

继续执行where指令(显示当前程序Call stack中的函数调用顺序,或者说是frame的顺序。命令where和info stack和bt是一样的。):

(gdb) where

#0  0x00400924 in dummy_function () at test.c:7

#1  0x0040096c in main () at test.c:12

(gdb) frame 1                    //frame :查看帧(#开头的行称为帧)

#1  0x0040096c in main () at test.c:12

12     dummy_function ();

(gdb) frame 0                  

#0  0x00400924 in dummy_function () at test.c:7

7           *ptr = 0x00;

可以得到test程序出错前函数调用的寄存器地址,可以看出程序的函数调用关系为:main()->dummy_function (),同时我们可以通过反汇编test二进制文件,定位0x00400924:

#mipsel-linux-objdump  -d  test >test.S

#vim test.S

00400900 <dummy_function>:

  400900:       3c1c0005        lui     gp,0x5

  400904:       279c82f0        addiu   gp,gp,-32016

  400908:       0399e021        addu    gp,gp,t9

  40090c:       27bdffe8        addiu   sp,sp,-24

  400910:       afbe0010        sw      s8,16(sp)

  400914:       03a0f021        move    s8,sp

  400918:       afc00008        sw      zero,8(s8)

  40091c:       8fc20008        lw      v0,8(s8)

  400920:       00000000        nop

  400924:       a0400000        sb      zero,0(v0)

  400928:       03c0e821        move    sp,s8

  40092c:       8fbe0010        lw      s8,16(sp)

  400930:       27bd0018        addiu   sp,sp,24

  400934:       03e00008        jr      ra 

  400938:       00000000        nop

sb指令:把一个字节的数据从寄存器存储到存储器中——SB R1, 0(R2)

这里是将0保存到寄存器v0地址指向的内存空间,再看回上边两句语句,将zero赋给v0,也就是说寄存器v0的值是0,那么sb指令试图在0地址操作,当然就出错了。

通过调试信息,我们发现程序还收到SIGSEGV信号,然后打印再出Segmentation fault的提示信息。

由于在linux系统内,内核收到SIGSEGV信号默认的handler的动作是打印”段错误"的出错信息,而且当程序中出现内存操作错误时,会发生崩溃并产生核心文件(core文件)。这样就能使用GDB可以对产生的核心文件进行分析,找出程序是在什么时候崩溃的和在崩溃之前程序都做了些什么。那么我们退出gdb去看一下系统是否有生成这样的core文件,结果是当前目录并没有发现core文件生成,原因是系统默认是限制生成文件。

所以我们要使用这种方式,首先要启动linux内核提供核心转储(core dump)机制:

在目标板命令行执行:

[LOONSON@Loongson-gz:/]#ulimit

[LOONSON@Loongson-gz:/]#ulimit -c

0

[LOONSON@Loongson-gz:/]#ulimit -c 10000

[LOONSON@Loongson-gz:/]#ulimit -c

10000

这样就将core文件的生成大小限制在10000KB以内,然后我们接着在目标板上直接运行测试程序

[LOONSON@Loongson-gz:/]#./test

Segmentation fault (core dumped)

同时发现在程序同一目录下,生成了一个文件名为 core的文件,即核心文件。

将core文件下载到PC端,然后使用gdb调试提示如下:

# /opt/mipsel-linux-gdb/bin/mipsel-linux-gdb  ./test /home/ethan/tftpboot/core

GNU gdb (GDB) 7.3

Copyright (C) 2011 Free Software Foundation, Inc.

License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software: you are free to change and redistribute it.

There is NO WARRANTY, to the extent permitted by law.  Type "show copying"

and "show warranty" for details.

This GDB was configured as "--host=i686-pc-linux-gnu --target=mipsel-linux".

For bug reporting instructions, please see:

<http://www.gnu.org/software/gdb/bugs/>...

Reading symbols from /home/ethan/Desktop/test/test...done.

[New LWP 301]

 

warning: Could not load shared library symbols for 3 libraries, e.g. /lib/libgcc_s.so.1.

Use the "info sharedlibrary" command to see the complete listing.

Do you need "set solib-search-path" or "set sysroot"?

Core was generated by `./test'.

Program terminated with signal 11, Segmentation fault.

#0  0x00400924 in dummy_function () at test.c:7

7           *ptr = 0x00;

(gdb) where

#0  0x00400924 in dummy_function () at test.c:7

#1  0x0040096c in main () at test.c:12

(gdb)

这样打印出来的信息跟使用gdb+gdbserver远程调试打印的信息是一致的,这样方法也是一种调试程序段错误等BUGS的有效的方法。

当然,这个仅仅是定位跟踪到段错误,并且能够或许到寄存器地址相关的调试信息,至于如何分析段错误产生的原因,还得结合具体系统现场及程序调用来分析原因了。

另外gdb还有很多命令能够巧妙的结合使用,有需要的可以查新相关gdb命令使用技巧。

这里同时介绍几个常用的调试工具:

1.    strace(直接用于目标板)

strace 命令是一种强大的工具, 能够显示任何由用户空间程式发出的系统调用。 strace 显示这些调用的参数并返回符号形式的值。 strace 从内核接收信息, 而且无需以任何特别的方式来构建内核。 strace 的每一行输出包括系统调用名称, 然后是参数和返回值。

2. Valgrind(在PC端检测程序中的内存管理问题)

Valgrind是一个GPL的软件,用于Linux程序的内存调试和代码剖析。你可以在它的环境中运行你的程序来监视内存的使用情况,比如C 语言中的malloc和free或者 C++中的new和 delete。使用Valgrind的工具包可以自动的检测许多内存管理和线程的bug,避免花费太多的时间在bug寻找上。

2.    Memwatch (可以通过添加到程序代码工程里边使用)

Memwatch能检测出未释放的内存、同一段内存被释放多次、位址存取错误及不当使用未分配之内存区域,但是查阅一些资料发现其在c++里不是很好用,但是在c是绝对可用的,而且跨平台使用起来非常方便。

以上几个工具都能很容易在网上查到使用方法,这里就不再赘述。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息