当前位置:文档之家› gdb调试实例

gdb调试实例

gdb调试实例
gdb调试实例

1. 查看gdb命令 (2)

2. 程序堆栈布局 (3)

3. 调试演示 (5)

a) 堆栈信息 (5)

b) 调试多进程 (6)

c) 无效的内存地址 (7)

d) 不对齐的内存地址 (8)

e) 缓冲区溢出 (9)

f) 堆栈溢出 (10)

4. 调试原理& 调试信息 (14)

a) 调试原理 (14)

b) 调试信息 (14)

1. 查看gdb命令

(gdb) help

List of classes of commands:

STM -- STMicroelectronics specific target commands

aliases -- Aliases of other commands

breakpoints -- Making program stop at certain points

data -- Examining data

files -- Specifying and examining files

internals -- Maintenance commands

obscure -- Obscure features

running -- Running the program

stack -- Examining the stack

status -- Status inquiries

support -- Support facilities

tracepoints -- Tracing of program execution without stopping the program

user-defined -- User-defined commands

Type "help" followed by a class name for a list of commands in that class. Type "help all" for the list of all commands.

Type "help" followed by command name for full documentation.

Type "apropos word" to search for commands related to "word".

Command name abbreviations are allowed if unambiguous.

(gdb) help stack

Examining the stack.

The stack is made up of stack frames. Gdb assigns numbers to stack frames counting from zero for the innermost (currently executing) frame.

At any time gdb identifies one frame as the "selected" frame.

Variable lookups are done with respect to the selected frame.

When the program being debugged stops, gdb selects the innermost frame. The commands below can be used to select other frames by number or address. List of commands:

backtrace -- Print backtrace of all stack frames

bt -- Print backtrace of all stack frames

down -- Select and print stack frame called by this one

frame -- Select and print a stack frame

return -- Make selected stack frame return to its caller

select-frame -- Select a stack frame without printing anything

up -- Select and print stack frame that called this one

Type "help" followed by command name for full documentation.

Type "apropos word" to search for commands related to "word".

Command name abbreviations are allowed if unambiguous.

(gdb) help bt

Print backtrace of all stack frames, or innermost COUNT frames.

With a negative argument, print outermost -COUNT frames.

Use of the 'full' qualifier also prints the values of the local variables.

(gdb) help frame

Select and print a stack frame.

With no argument, print the selected stack frame. (See also "info frame").

An argument specifies the frame to select.

It can be a stack frame number or the address of the frame.

With argument, nothing is printed if input is coming from

a command file or a user-defined command.

2. 程序堆栈布局

frame是函数调用时,在堆栈上记录的数据信息,包括寄存器,局部变量,函数参数等,每个函数被调用都会在堆栈上记录自己的信息,于是形成了如图显示的frame stack:

frame 的组成:在函数的入口处,首先保存那些在本函数里被使用的寄存器(D),函数退出时,恢复它们,其中就包含函数调用的返回地址,然后给函数内的局部变量分配空间(C),再然后给alloca 调用或者动态数组分配空间(B),如果有调用子函数,而且子函数的参数个数过多,无法完全通过寄存器传递,那么就需要借助堆栈传递(A)。

函数出口处,堆栈释放是分配的逆过程,A-->B-->C-->D ,在D 中得到函数的返回地址,于是当前函数调用完成,返回到调用它的上一级函数体内。

补充说明:不是每个函数的frame 都包含ABCD ,可能只包含一部分,比如只有CD ,分配的时候D-->C ,释放的时候C-->D ,这个过程是完全对称的。

pop A pop B pop C

pop D, get ret addr then ret to parent jump to child ret addr

jump to child ret addr jump to child ret addr

push D, save ret addr push C push B push A run

push D, save ret addr push C push B push A run

push D, save ret addr push C push B push A run

pop A pop B pop C

pop D, get ret addr then ret to parent

pop A pop B pop C

pop D, get ret addr then ret to parent

push 寄存器保存到桟里,局部变量分配桟空间,其他临时桟空间使用的分配

pop 从桟里恢复寄存器,局部变量释放桟空间,其他临时桟空间使用的释放

红色箭头子函数调用

绿色箭头从子函数调用返回

蓝色箭头函数内部指令执行

图中反映了三个函数之间的调用关系,函数入口处压桟,函数出口处退桟,对于A,B,C可能在函数执行过程中发生,不一定在函数的入口处,比如程序在某个if语句块里定义的局部变量。我们在利用堆栈信息反推函数调用时,通过递增当前的堆栈指针,模拟函数调用返回的过程,就可以知道每个函数的stack frame范围,从中提取需要的信息,比如获取返回地址的值,就知道当前函数的上一级函数是谁,重复此过程便可以推出完整的函数调用关系。

3. 调试演示

a) 堆栈信息

盒子端:gdbserver 192.168.111.114:1234 ./testcases 0

192.168.111.114是盒子的ip地址

1234是盒子端用于调试的端口号,电脑端连接时参数就要指定这个端口

./testcases 被调试的程序

0 参数,传递给testcases,共6个测试项,对应0~5

电脑端:sh4-linux-uclibc-gdb

(gdb) set solib-absolute-prefix /mnt/sdb/PRJ/Hathway_STH273/nfs/ //共享库的查找路径(gdb) file testcases // 指定调试对象

Reading symbols from /home/wucong/gdb/testcases...done.

(gdb) target remote 192.168.111.114:1234 // 盒子的地址以及调试端口号

Remote debugging using 192.168.111.114:1234

Reading symbols from /mnt/sdb/PRJ/Hathway_STH273/nfs/lib/ld-uClibc.so.0...(no debugging symbols found)...done.

Loaded symbols for /mnt/sdb/PRJ/Hathway_STH273/nfs/lib/ld-uClibc.so.0

0x29559104 in _start () from /mnt/sdb/PRJ/Hathway_STH273/nfs/lib/ld-uClibc.so.0 (gdb) b dump

Breakpoint 1 at 0x4006c0: file testcases.c, line 7.

(gdb) c

Continuing.

Breakpoint 1, dump (a=0, b=1, c=2, d=3, e=4, f=5) at testcases.c:7

7 printf("dump %d %d %d %d %d %d\n", a, b, c, d, e, f);

(gdb) bt

#0 dump (a=0, b=1, c=2, d=3, e=4, f=5) at testcases.c:7 // stack frame

#1 0x00400726 in testcase0 () at testcases.c:12 // stack frame

#2 0x004008e0 in main (argc=2, argv=0x7bd10e74) at testcases.c:87 // stack frame (gdb) disassemble testcase0 // 反汇编

Dump of assembler code for function testcase0:

0x00400708 <+0>: mov.l r14,@-r15 // 函数的入口,寄存器压桟

0x0040070a <+2>: sts.l pr,@-r15 // pr 返回地址入桟

0x0040070c <+4>: add #-8,r15 // 调用dump时有两个参数借助桟传递,分配空间0x0040070e <+6>: mov r15,r14

0x00400710 <+8>: mov #4,r1

0x00400712 <+10>: mov.l r1,@r15

0x00400714 <+12>: mov #5,r1

0x00400716 <+14>: mov.l r1,@(4,r15)

0x00400718 <+16>: mov #0,r4

0x0040071a <+18>: mov #1,r5

0x0040071c <+20>: mov #2,r6

0x0040071e <+22>: mov #3,r7

0x00400720 <+24>: mov.l 0x400734 ,r1 ! 0x4006a0 0x00400722 <+26>: jsr @r1

0x00400724 <+28>: nop

0x00400726 <+30>: add #8,r14 // 函数返回,开始释放桟空间,首先退dump参数0x00400728 <+32>: mov r14,r15 // 恢复stack pointer

0x0040072a <+34>: lds.l @r15+,pr // 恢复pr,函数返回地址

0x0040072c <+36>: mov.l @r15+,r14 // 恢复其他寄存器,释放桟空间

0x0040072e <+38>: rts // 函数返回指令,拷贝pr到pc,从pc处开始执行

0x00400730 <+40>: nop

0x00400732 <+42>: nop

0x00400734 <+44>: .word 0x06a0

0x00400736 <+46>: .word 0x0040

b) 调试多进程

方法1:分别attach父子进程

# gdbserver --attach 192.168.111.114:1234 919 // 调试父进程

# gdbserver --attach 192.168.111.114:1235 922 // 调试子进程

919是父进程的id,922是子进程的id,通过两个终端进行调试,比如登陆两个telnet,然后分别执行上面的命令

方法2:通过配置gdb参数,调试父子进程

设置方法:

set follow-fork-mode [parent|child]

set detach-on-fork [on|off]

follow-fork-mode detach-on-fork

parent on 只调试主进程(GDB默认)

child on 只调试子进程

parent off 同时调试两个进程,gdb跟主进程,子进程block在fork位置child off 同时调试两个进程,gdb跟子进程,主进程block在fork位置

说明:如果只需要跟踪调试父子进程中的一个,第二种方式比较方便,如果要同时调试父子进程,建议使用第一种方式,因为第二种方式限制了父子进程只能有一个运行,另一个处于休眠状态,实际应用当中,父子进程很可能都要运行的。另外gdbserver不支持第二种方式。

# gdb --args ./testcases 1 // 如果不带参数,gdb ./testcases

Reading symbols from /NDS/lib/module/testcases...done.

(gdb) set follow-fork-mode child // 选择子进程作为当前调试对象

(gdb) set detach-on-fork off // 父子进程都可被调试

(gdb) b 20 // 20行加断点

Breakpoint 1 at 0x40079a: file testcases.c, line 20.

(gdb) r // run

Starting program: /NDS/lib/module/testcases 1

[New process 922]

[Switching to process 922]

Breakpoint 1, testcase1 () at testcases.c:20

20 testcases.c: No such file or directory.

(gdb) p /x pid // pid是0,说明是子进程

$1 = 0x0

(gdb) info inferiors // 查看gdb的调试对象,带*说明是当前调试对象,其他对象处于休眠Num Description Executable

* 2 process 922 /NDS/lib/module/testcases

1 process 919 /NDS/lib/module/testcases

(gdb) inferior 1 // 切换到对象1,对象1变成当前调试对象,对象2进入休眠[Switching to inferior 1 [process 919] (/NDS/lib/module/testcases)]

[Switching to thread 1 (process 919)]

#0 0x295da752 in fork () from /lib/libc.so.0

(gdb) c // continue继续运行

Continuing.

Breakpoint 1, testcase1 () at testcases.c:20

20 testcases.c: No such file or directory.

(gdb) p /x pid // pid非0,说明是父进程

$2 = 0x39a

(gdb) info inferiors // 对象1带*

Num Description Executable

2 process 922 /NDS/lib/module/testcases

* 1 process 919 /NDS/lib/module/testcases

c) 无效的内存地址

盒子端:gdbserver 192.168.111.114:1234 ./testcases 2

电脑端:sh4-linux-uclibc-gdb

(gdb) set solib-absolute-prefix /mnt/sdb/PRJ/Hathway_STH273/nfs/

(gdb) file testcases

Reading symbols from testcases...done.

(gdb) target remote 192.168.111.114:1234

Remote debugging using 192.168.111.114:1234

Reading symbols from /mnt/sdb/PRJ/Hathway_STH273/nfs/lib/ld-uClibc.so.0...(no debugging symbols found)...done.

Loaded symbols for /mnt/sdb/PRJ/Hathway_STH273/nfs/lib/ld-uClibc.so.0

0x29559104 in _start () from /mnt/sdb/PRJ/Hathway_STH273/nfs/lib/ld-uClibc.so.0 (gdb) c

Continuing.

Program received signal SIGSEGV, Segmentation fault.

0x004007fa in testcase2 () at testcases.c:34

34 str[0] = 0;

(gdb) p /x str // 0x1234是无效的地址,产生段错误

$1 = 0x1234

d) 不对齐的内存地址

盒子端:gdbserver 192.168.111.114:1234 ./testcases 3

电脑端:sh4-linux-uclibc-gdb

(gdb) set solib-absolute-prefix /mnt/sdb/PRJ/Hathway_STH273/nfs/

(gdb) file testcases

Reading symbols from testcases...done.

(gdb) target remote 192.168.111.114:1234

Remote debugging using 192.168.111.114:1234

Reading symbols from /mnt/sdb/PRJ/Hathway_STH273/nfs/lib/ld-uClibc.so.0...(no debugging symbols found)...done.

Loaded symbols for /mnt/sdb/PRJ/Hathway_STH273/nfs/lib/ld-uClibc.so.0

0x29559104 in _start () from /mnt/sdb/PRJ/Hathway_STH273/nfs/lib/ld-uClibc.so.0 (gdb) c

Continuing.

Program received signal SIGBUS, Bus error.

0x0040081e in testcase3 () at testcases.c:41

41 *v1 = 0;

(gdb) p /x v1 // v1是int *,尝试给它指向的内存赋值,因为int占用4字节,使用的mov.l 指令要求地址按4对齐,0x1233不是4对齐,所以产生总线错误。实际上linux kernel对不对齐的地址会尝试按字节访问,如果地址仍然非法,才会报bus error,否则只会打印一条警告“Unaligned userspace access in "testcases" pid=933 pc=0x0040081e ins=0x2122”,发现这种警告时,应该尽量把它修复,因为它说明代码逻辑有问题。可以通过“echo 4 > /proc/cpu/alignment”关闭内核修复处理,就可以生成core dump了

$1 = 0x1233

e) 缓冲区溢出

盒子端:gdbserver 192.168.111.114:1234 ./testcases 4

电脑端:sh4-linux-uclibc-gdb

(gdb) set solib-absolute-prefix /mnt/sdb/PRJ/Hathway_STH273/nfs/

(gdb) file testcases

Reading symbols from testcases...done.

(gdb) target remote 192.168.111.114:1234

Remote debugging using 192.168.111.114:1234

Reading symbols from /mnt/sdb/PRJ/Hathway_STH273/nfs/lib/ld-uClibc.so.0...(no debugging symbols found)...done.

Loaded symbols for /mnt/sdb/PRJ/Hathway_STH273/nfs/lib/ld-uClibc.so.0

0x29559104 in _start () from /mnt/sdb/PRJ/Hathway_STH273/nfs/lib/ld-uClibc.so.0 (gdb) b 48

Breakpoint 1 at 0x400842: file testcases.c, line 48.

(gdb) b 49

Breakpoint 2 at 0x400848: file testcases.c, line 49.

(gdb) c

Continuing.

Breakpoint 1, testcase4 () at testcases.c:48

48 *(int *)(&v1) = 0;

(gdb) p /x v1

$1 = 0x1

(gdb) p /x v2

$2 = 0x2

(gdb) n // 执行*(int *)(&v1) = 0

Breakpoint 2, testcase4 () at testcases.c:49

49 printf("v1: %d v2: %d\n", v1, v2);

(gdb) p /x v1 // v1等于0

$3 = 0x0

(gdb) p /x v2 // v2也等于0

$4 = 0x0

程序只对v1赋值0,为什么v2也变成0了?因为v1是short,占2个字节,赋值时强制转化成int,写了4个字节,多写的2个字节就是v2的位置,所以v2也被写了。

这种情况多发生在函数调用时,参数传递的情况下,比如一个函数需要一个int *的参数,而调用这个函数的地方,把一个short变量的地址作为了参数。

为了避免这种情况,在进行类型强制转换时,如果转换前后的对象大小不一致,就要格外小心,当然对于指针类型,我们要比较指针所指的对象类型大小,而不是指针类型本身,因为无论什么类型的指针变量占用的空间大小是一样的,而程序通过指针变量修改的是它指向的内存对象,所以要关注这个内存对象的大小。

f) 堆栈溢出

盒子端:gdbserver 192.168.111.114:1234 ./testcases 5

电脑端:sh4-linux-uclibc-gdb

(gdb) set solib-absolute-prefix /mnt/sdb/PRJ/Hathway_STH273/nfs/

(gdb) file testcases

Reading symbols from testcases...done.

(gdb) target remote 192.168.111.114:1234

Remote debugging using 192.168.111.114:1234

Reading symbols from /mnt/sdb/PRJ/Hathway_STH273/nfs/lib/ld-uClibc.so.0...(no debugging symbols found)...done.

Loaded symbols for /mnt/sdb/PRJ/Hathway_STH273/nfs/lib/ld-uClibc.so.0

0x29559104 in _start () from /mnt/sdb/PRJ/Hathway_STH273/nfs/lib/ld-uClibc.so.0 (gdb) c

Continuing.

Program received signal SIGSEGV, Segmentation fault.

0x00000000 in ?? ()

(gdb) p /x $pr // 函数调用的返回地址

$7 = 0x0

(gdb) p /x $pc // 当前执行指令的地址

$8 = 0x0

(gdb) p /x $r15 // 查看当前堆栈指针

$1 = 0x7bc78cb8

(gdb) p /x *0x7bc78cb8@32 // 打印堆栈的信息,由于pr,pc都是无效的,所以只能从堆栈中找调用信息,实际上就是寻找保存在堆栈上的函数返回地址,0x7b开头都是落在堆栈的空间范围,不可能对应函数,所以跳过

$2 = {0x7bc78e74, 0x2, 0x40087c, 0x5, 0x295dc1d2, 0x0, 0x7bc78f78, 0x7bc78e74, 0x2, 0x4008a4, 0x7bc78f78, 0x40043c, 0x7bc78d30, 0xfff8b1a8, 0x296322f8, 0x0, 0x0,

0x7bc78cd4, 0x295dc1a2, 0x29560448, 0x80000, 0x0 }

(gdb) info symbol 0x40087c // 地址对应testcase5

testcase5 in section .text of ./testcases

(gdb) p testcase5 // 打印testcase5的入口地址,和上面的地址一样,如果是testcase5里面调用子函数,压桟的返回地址不可能是testcase5的入口地址,所以这不是个有效的返回地址,可能只是作为参数,被保存到桟里了

$5 = {void (void)} 0x40087c

(gdb) info symbol 0x295dc1d2 // 地址对应__uClibc_main+534,这可能是个有效返回地址__uClibc_main + 534 in section .text of /mnt/sdb/PRJ/Hathway_STH273/nfs/lib/libc.so.0 (gdb) info symbol 0x4008a4 // 地址对应main

main in section .text of ./testcases

(gdb) p main // 同testcase5分析,这不是一个有效的返回地址

$6 = {int (int, char **)} 0x4008a4

从可能的返回地址__uClibc_main+534开始分析,__uClibc_main会调用main,返回地址会保存到main的stack frame里面,__uClibc_main+534是不是有效的返回地址呢?(gdb) disassemble 0x295dc1d2-8,0x295dc1d2+8

Dump of assembler code from 0x295dc1ca to 0x295dc1da:

0x295dc1ca <__uClibc_main+526>: mov.l @r1,r6

0x295dc1cc <__uClibc_main+528>: mov.l @(8,r15),r1

0x295dc1ce <__uClibc_main+530>: jsr @r1

0x295dc1d0 <__uClibc_main+532>: mov.l @r15,r5 // delay slot,它跟跳转指令是一体的,它的下一条指令的地址,才是本次跳转的返回地址

0x295dc1d2 <__uClibc_main+534>: bra 0x295dc206 <__uClibc_main+586>

0x295dc1d4 <__uClibc_main+536>: mov r0,r4

0x295dc1d6 <__uClibc_main+538>: mov.l 0x295dc230 <__uClibc_main+628>,r8 ! 0x59e8

0x295dc1d8 <__uClibc_main+540>: mov.w 0x295dc214 <__uClibc_main+600>,r1 ! 0xbc

__uClibc_main+530处发生了跳转,所以__uClibc_main+534是个有效的返回地址,现在可以假设当前位于main的stack frame里,反汇编main,分析它的stack frame的结构(gdb) disassemble main

Dump of assembler code for function main:

0x004008a4 <+0>: mov.l r8,@-r15 // r8入桟

0x004008a6 <+2>: mov.l r14,@-r15 // r14入桟

0x004008a8 <+4>: sts.l pr,@-r15 // pr入桟

0x004008aa <+6>: add #-16,r15 // 局部变量分配桟空间

0x004008ac <+8>: mov r15,r14 // r14拷贝r15,后面通过r14访问桟

0x004008ae <+10>: mov r14,r1

0x004008b0 <+12>: add #-48,r1

0x004008b2 <+14>: mov.l r4,@(52,r1)

0x004008b4 <+16>: mov r14,r1

0x004008b6 <+18>: add #-48,r1

0x004008b8 <+20>: mov.l r5,@(48,r1)

0x004008ba <+22>: mov r14,r1

0x004008bc <+24>: add #-48,r1

0x004008be <+26>: mov.l @(52,r1),r2

0x004008c0 <+28>: mov #2,r1

0x004008c2 <+30>: cmp/eq r1,r2

0x004008c4 <+32>: bt 0x4008d6

0x004008c6 <+34>: mov.l 0x400950 ,r1 ! 0x4009e4

0x004008c8 <+36>: mov r1,r4

0x004008ca <+38>: mov.l 0x400954 ,r1 ! 0x4004c0 0x004008cc <+40>: jsr @r1

0x004008ce <+42>: nop

0x004008d0 <+44>: mov #-1,r1

0x004008d2 <+46>: bra 0x40093a

0x004008d4 <+48>: nop

0x004008d6 <+50>: mov r14,r1

0x004008d8 <+52>: add #-48,r1

0x004008da <+54>: mov.l @(48,r1),r1

0x004008dc <+56>: add #4,r1

0x004008de <+58>: mov.l @r1,r1

0x004008e0 <+60>: mov r14,r8 // 桟指针

0x004008e2 <+62>: add #-48,r8 // -48

0x004008e4 <+64>: mov r1,r4

0x004008e6 <+66>: mov.l 0x400958 ,r1 ! 0x400568 0x004008e8 <+68>: jsr @r1 // 把测试号从argv里保存的字符串转成int

0x004008ea <+70>: nop

0x004008ec <+72>: lds r0,fpul

0x004008ee <+74>: fsts fpul,fr1

0x004008f0 <+76>: flds fr1,fpul

0x004008f2 <+78>: sts fpul,r1

0x004008f4 <+80>: mov.l r1,@(60,r8) // 测试号保存在(60-48)的桟位置

0x004008f6 <+82>: mov r14,r1

0x004008f8 <+84>: add #-48,r1

0x004008fa <+86>: mov.l @(60,r1),r2

0x004008fc <+88>: mov #5,r1

0x004008fe <+90>: cmp/hi r1,r2

0x00400900 <+92>: bf 0x40091a

0x00400902 <+94>: mov.l 0x40095c ,r2 ! 0x4009f8

0x00400904 <+96>: mov r14,r1

0x00400906 <+98>: add #-48,r1

0x00400908 <+100>: mov.l @(60,r1),r1

0x0040090a <+102>: mov r2,r4

0x0040090c <+104>: mov r1,r5

0x0040090e <+106>: mov.l 0x400960 ,r1 ! 0x400488 0x00400910 <+108>: jsr @r1

0x00400912 <+110>: nop

0x00400914 <+112>: mov #-1,r1

0x00400916 <+114>: bra 0x40093a

0x00400918 <+116>: nop

0x0040091a <+118>: mov.l 0x400964 ,r2 ! 0x410ae4 0x0040091c <+120>: mov r14,r1

0x0040091e <+122>: add #-48,r1

0x00400920 <+124>: mov.l @(60,r1),r1

0x00400922 <+126>: shll2 r1

0x00400924 <+128>: add r2,r1

0x00400926 <+130>: mov.l @r1,r2

0x00400928 <+132>: mov r14,r1

0x0040092a <+134>: add #-48,r1

0x0040092c <+136>: mov.l r2,@(56,r1) // 测试号是testcases数组的索引,从数

组中定位测试子函数的入口地址,并保存到(56-48)的桟位置

0x0040092e <+138>: mov r14,r1

0x00400930 <+140>: add #-48,r1

0x00400932 <+142>: mov.l @(56,r1),r1

0x00400934 <+144>: jsr @r1 // 从(56-48)位置取测试子函数的入口地址,开始执行0x00400936 <+146>: nop

0x00400938 <+148>: mov #0,r1

0x0040093a <+150>: lds r1,fpul

0x0040093c <+152>: fsts fpul,fr1

0x0040093e <+154>: flds fr1,fpul

0x00400940 <+156>: sts fpul,r0

0x00400942 <+158>: add #16,r14

0x00400944 <+160>: mov r14,r15

0x00400946 <+162>: lds.l @r15+,pr

0x00400948 <+164>: mov.l @r15+,r14

0x0040094a <+166>: mov.l @r15+,r8

0x0040094c <+168>: rts

0x0040094e <+170>: nop

(gdb) p /x *($r15+16) // 打印pr,入口时局部变量占用16字节,所以这里加16字节,就是保存pr的地方

$9 = 0x295dc1d2 // 就是__uClibc_main+534

(gdb) p /x *($r15+56-48) // 测试子函数的入口地址

$10 = 0x40087c // 就是testcase5的入口地址

(gdb) p /x *($r15+60-48) // 测试号

$11 = 0x5 // 测试5

以上三点,都支持了上面的假设,当前stack frame位于main,main正在进行测试5,从测试5返回时,返回地址异常,推测保存返回地址的桟空间被破坏,而返回地址是在testcase5的入口处被压桟的,所以可能是testcase5运行时破坏了桟,返回时从桟里取到了无效的返回地址

void testcase5(void)

{

char buf[4];

memset(buf, 0, 8); // 多memset了4个字节

}

(gdb) disassemble testcase5

Dump of assembler code for function testcase5:

0x0040087c <+0>: mov.l r14,@-r15

0x0040087e <+2>: sts.l pr,@-r15 // 返回地址

0x00400880 <+4>: add #-4,r15 // buf,buf后面的4个字节存的就是pr

0x00400882 <+6>: mov r15,r14

0x00400884 <+8>: mov r14,r1

0x00400886 <+10>: mov r1,r4

0x00400888 <+12>: mov #0,r5

0x0040088a <+14>: mov #8,r6

0x0040088c <+16>: mov.l 0x4008a0 ,r1 ! 0x40054c

0x0040088e <+18>: jsr @r1

0x00400890 <+20>: nop

0x00400892 <+22>: add #4,r14

0x00400894 <+24>: mov r14,r15

0x00400896 <+26>: lds.l @r15+,pr

0x00400898 <+28>: mov.l @r15+,r14

0x0040089a <+30>: rts

0x0040089c <+32>: nop

4. 调试原理& 调试信息

a) 调试原理

设置断点时,把目标位置的指令替换成特殊的指令,特殊指令被执行时,会产生软中断,被调试的程序中断运行,然后系统通知gdb,被调试的程序触发了断点,于是gdb可以开始调试了

ST的断点指令是0xc3c3

b) 调试信息

sh4-linux-uclibc-objdump -h testcases:

22 .debug_aranges 00000020 00000000 00000000 00000b55 2**0

CONTENTS, READONLY, DEBUGGING

23 .debug_info 00000294 00000000 00000000 00000b75 2**0

CONTENTS, READONLY, DEBUGGING

24 .debug_abbrev 00000134 00000000 00000000 00000e09 2**0

CONTENTS, READONLY, DEBUGGING

25 .debug_line 00000070 00000000 00000000 00000f3d 2**0

CONTENTS, READONLY, DEBUGGING

26 .debug_frame 00000118 00000000 00000000 00000fb0 2**2

CONTENTS, READONLY, DEBUGGING

27 .debug_str 00000129 00000000 00000000 000010c8 2**0

CONTENTS, READONLY, DEBUGGING

28 .debug_loc 00000220 00000000 00000000 000011f1 2**0

CONTENTS, READONLY, DEBUGGING

程序的调试信息就存在这些段里,通过这些信息,可以把一个地址转化成符号,定位这个符号位于哪个文件哪一行等。

调试信息的格式是DWARF,全称是"Debugging With Attributed Record Formats"。

sh4-linux-uclibc-objdump --dwarf=info testcases:

<1><199>: Abbrev Number: 14 (DW_TAG_subprogram) // 函数testcase4

<19a> DW_AT_external : 1

<19b> DW_AT_name : (indirect string, offset: 0x8d): testcase4

<19f> DW_AT_decl_file : 1 // 索引,可以查找文件名

<1a0> DW_AT_decl_line : 38 // 位于文件中的行数

<1a1> DW_AT_prototyped : 1

<1a2> DW_AT_low_pc : 0x40077c // 函数汇编指令的开始地址

<1a6> DW_AT_high_pc : 0x4007a4 // 函数汇编指令的结束为止

<1aa> DW_AT_frame_base : 0x13c (location list)

<1ae> DW_AT_GNU_all_tail_call_sites: 1

<1af> DW_AT_sibling : <0x1c2>

<2><1b3>: Abbrev Number: 12 (DW_TAG_variable) // 局部变量buf

<1b4> DW_AT_name : buf

<1b8> DW_AT_decl_file : 1

<1b9> DW_AT_decl_line : 40 // 位于文件中的行数

<1ba> DW_AT_type : <0x81>

<1be> DW_AT_location : 2 byte block: 91 74 (DW_OP_fbreg: -12) // buf在stack frame中的位置

<1><1c2>: Abbrev Number: 15 (DW_TAG_subprogram) // 函数main

<1c3> DW_AT_external : 1

<1c4> DW_AT_name : (indirect string, offset: 0x3c): main

<1c8> DW_AT_decl_file : 1

<1c9> DW_AT_decl_line : 54

<1ca> DW_AT_prototyped : 1

<1cb> DW_AT_type : <0x4f>

<1cf> DW_AT_low_pc : 0x4007a4 // 通过这个地址,可以定位到main位于文件testcases.c的55行

<1d3> DW_AT_high_pc : 0x400868

<1d7> DW_AT_frame_base : 0x180 (location list)

<1db> DW_AT_GNU_all_tail_call_sites: 1

<1dc> DW_AT_sibling : <0x228>

sh4-linux-uclibc-objdump --dwarf=decodedline testcases:

文件行号地址

testcases.c 33 0x400742 testcases.c 34 0x400748 testcases.c 35 0x400766 testcases.c 39 0x40077c testcases.c 42 0x400784 testcases.c 43 0x400792 testcases.c 55 0x4007a4 testcases.c 59 0x4007ba

testcases.c 61 0x4007c6 testcases.c 62 0x4007d0 testcases.c 65 0x4007d6 testcases.c 66 0x4007f6 testcases.c 68 0x400802 testcases.c 69 0x400814 testcases.c 71 0x40081a testcases.c 72 0x40082e testcases.c 74 0x400838 testcases.c 75 0x40083e

GDB基本使用方法

GDB基本使用方法 GDB是用来调试用户态程序的一款工具,可以追踪程序运行轨迹,打出调用栈,寄存器内容,查看内存等等 首先在编译时,我们必须要把调试信息加到可执行文件中。使用编译器(cc/gcc/g++)的-g 参数可以做到这一点。如果没有-g,你将看不见程序的函数名、变量名,所代替的全是运行时的内存地址。 启动GDB 直接找到gdb的路径执行就ok,进入GDB后可以输入help命令查看帮助信息 加载可执行文件启动 gdb executable-file set args 参数列表 以上两步等同于 gdb –args executable-file 参数列表 run或者start都可以启动调试 多用于调试启动阶段就异常的程序 调试正在运行的程序 以下三种形式都可以attach到正在运行的程序上调试 ps -ef | grep http www-data 24470 1 0 Jan17 ? 00:00:14 /usr/sbin/lighttpd gdb attach 24470 gdb --pid 24470 gdb -p 24470 设置断点 break -- Set breakpoint at specified line or function b func1 break func1 设置在func1处 b file:line 设置在文件的第几行处 b *0x指令地址设置在具体的某条汇编指令处 设置断点后,代码执行到func1处会被断住,方便我们查看当时的信息 打印调用栈 backtrace bt 如果你要查看栈下面层的详细信息 frame 栈中的层编号 查看所有断点 info break 删除断点 delete 断点号 如果不加断点号为删除全部断点 禁用断点 disable 断点号 启用断点

利用gdb调试core文件

利用gdb调试core文件 什么是core dump core dump又叫核心转储, 当程序运行过程中发生异常, 程序异常退出时, 由操作系统把程序当前的内存状况存储在一个core文件中, 叫core dump。(linux中如果内存越界会收到SIGSEGV信号,然后就会core dump) 在程序运行的过程中,有的时候我们会遇到Segment fault(段错误)这样的错误。这种看起来比较困难,因为没有任何的栈、trace信息输出。该种类型的错误往往与指针操作相关。往往可以通过这样的方式进行定位。 造成segment fault,产生core dump的可能原因 1、内存访问越界 a) 由于使用错误的下标,导致数组访问越界 b) 搜索字符串时,依靠字符串结束符来判断字符串是否结束,但是字符串没有正常的使用结束符 c) 使用strcpy, strcat, sprintf, strcmp, strcasecmp等字符串操作函数,将目标字符串读/写爆。应该使用strncpy, strlcpy, strncat, strlcat, snprintf, strncmp, strncasecmp等函数防止读写越界。 2、多线程程序使用了线程不安全的函数。 3、多线程读写的数据未加锁保护。 对于会被多个线程同时访问的全局数据,应该注意加锁保护,否则很容易造成core dump 4、非法指针 a) 使用空指针 b) 随意使用指针转换。一个指向一段内存的指针,除非确定这段内存原先就分配为某种结构或类型,或者这种结构或类型的数组,否则不要将它转换为这种结构或类型的指针,而应该将这段内存拷贝到一个这种结构或类型中,再访问这个结构或类型。这是因为如果这段内

用GDB调试程序

用GDB调试程序 GDB概述 ———— GDB是GNU开源组织发布的一个强大的UNIX下的程序调试工具。或许,各位比较喜欢那种图形界面方式的,像VC、BCB等IDE的调试,但如果你是在UNIX平台下做软件,你会发现GDB 这个调试工具有比VC、BCB的图形化调试器更强大的功能。所谓“寸有所长,尺有所短”就是这个道理。 一般来说,GDB主要帮忙你完成下面四个方面的功能: 1、启动你的程序,可以按照你的自定义的要求随心所欲的运行程序。 2、可让被调试的程序在你所指定的调置的断点处停住。(断点可以是条件表达式) 3、当程序被停住时,可以检查此时你的程序中所发生的事。 4、动态的改变你程序的执行环境。 从上面看来,GDB和一般的调试工具没有什么两样,基本上也是完成这些功能,不过在细节上,你会发现GDB这个调试工具的强大,大家可能比较习惯了图形化的调试工具,但有时候,命令行的调试工具却有着图形化工具所不能完成的功能。让我们一一看来。 一个调试示例 —————— 源程序:tst.c 1 #include 2 3 int func(int n) 4 { 5 int sum=0,i; 6 for(i=0; i

21 } 22 23 printf("result[1-100] = %d \n", result ); 24 printf("result[1-250] = %d \n", func(250) ); 25 } 编译生成执行文件:(Linux下) hchen/test> cc -g tst.c -o tst 使用GDB调试: hchen/test> gdb tst <---------- 启动GDB GNU gdb 5.1.1 Copyright 2002 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i386-suse-linux"... (gdb) l <-------------------- l命令相当于list,从第一行开始例出原码。 1 #include 2 3 int func(int n) 4 { 5 int sum=0,i; 6 for(i=0; i

实例—使用gdb调试器

2.4 实例—使用gdb调试器 1.编写实例程序gcctest.c,见2.2小节的开头部分 2.编译 3.启动GDB,执行程序 启动gdb,进入gdb调试环境,可以使用gdb的命令对程序进行调试。 [root@localhost gdbtest txt]# gdb //启动gdb GNU gdb Fedora (6.8-27.el5) Copyright (C) 2008 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later 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 "i386-redhat-linux-gnu". (gdb) run gcctest //在gdb中,运行程序使用r或是run命令,注意,gcctest没有调试信息Starting program: gcctest No executable file specified. Use the "file" or "exec-file" command. //要使用file或exec-file命令指出要运行的程序 (gdb) file gcctest //使用file命令指出要运行的程序gcctest,注意,对gdb命令也可以使用Tab gcctest gcctest.c gcctestg (gdb) file gcctest //使用file命令指出要运行的程序gcctest Reading symbols from /root/Desktop/gdbtest txt/gcctest...(no debugging symbols found)...done. (gdb) r //在gdb中,运行程序使用r或是run命令 Starting program: /root/Desktop/gdbtest txt/gcctest gcctest (no debugging symbols found) (no debugging symbols found) (no debugging symbols found) hello in main hello 1 hello 2 sum=54125560035401396161080590579269632.000000 Program exited with code 057. (gdb) file gcctestg //使用file命令指出要运行的程序gcctestg Reading symbols from /root/Desktop/gdbtest txt/gcctestg...done. (gdb) r //在gdb中,运行程序使用r或是run命令 Starting program: /root/Desktop/gdbtest txt/gcctestg gcctest hello in main hello 1 hello 2 sum=54125560035401396161080590579269632.000000 Program exited with code 057. (gdb) q //使用q或是quit命令退出gdb [root@localhost gdbtest txt]# 4.GDB命令简介

嵌入式调试技术

嵌入式调试技术 嵌入式开发中,主流的调试技术包括模拟(Simulation),在线仿真(In-Circuit Emulation),片上调试(On-Chip Debugging),插桩(Stub) 模拟(Simulation)是在 PC 机上通过软件虚拟出一个包含核心处理 器和外围电路的目标机系统,并且在该系统上模拟执行指令的过程。如国内有名的开源项目SkyEye就是一款指令级模拟器,能够模拟多种嵌入式开发板,可支持多种 CPU指令集,在 SkyEye 上运行的操作系统意识不到它是在一个虚拟的环境中运行,而且开发人员可以通过SkyEye 调试操作系统和系统软件。由于SkyEye的目标不是验证硬件逻辑,而是协助开发、调试和学习系统软件,所以在实现上 SkyEye与真实的硬件环境相比还是有一定差别的。 SkyEye在时钟节拍的时序上不保证与硬件完全相同,对软件透明的一些硬件仿真进行了一定的简化。 在线仿真(In-Circuit Emulation)是指利用在线仿真器,又称 ICE (In-Circuit Emulator),取代目标机处理器,进行程序调试的过程。ICE 一端与调试主机相连,另一端经仿真探头接在目标机处理器的底座上,通过自带的处理器模拟目标机的运行,其表现与目标机处理器相同,但开发者通过ICE可设置断点,查看寄存器,读写内存,进行单步操作。ICE 的优势在于能够完全取代目标机,实现全真模拟,但由于仿真探头不能随意更换,不利于普及,而且随着CPU主频的提高和处理器芯片表贴化封装的趋势,仿真探头的实现越来越困 难。 片上调试(On-Chip Debugging)正是为了解决在线仿真面临的问题而出现的技术。处理器厂商在芯片内部加入专门的调试控制逻辑,用以捕获处理器的总线数据和引脚信号,并让处理器核心在调试时钟下运行。用户通过串行接口与调试控制逻辑通信,控制CPU的运行,实现单步、断点等功能,并通过串行接口获得 CPU 运行状态,读取寄存器和内存信息。由于不受 CPU 主频和封装的限制,OCD在嵌入式处理器中被广泛采用,最典型的Motorola公司的 BDM(Backgroud Debug Mode)调试接口和联合测试行动小组提出的 JTAG 串行调试接口。早期的OCD技术没有实时跟踪功能,实现运行控制,如ARM7DMI处理器中的JTAG接口,只能实现单步、断点、读写寄存器、读写内存等基本的运行控制功能;而ARM920T 处理器中则引入了 E-Trace 实时跟踪单元,能够实时监控和跟踪 CPU的运行状态。 插桩(Stub)是内核调试和应用调试常用的方法。插桩调试需要提前下载一段调试桩(Stub)或者服务程序(Server)到目标机系统,并通过网络或者串口与调试主机上的客户端建立远程连接。客户端接受用户的调试命令,转发给目标机端的调试桩(或服务程序);调试桩(或服务程序)根据调试命令控制被调试程序的运行,并将调试信息反馈给主机上的调试客户端。GDB远程调试正是采用了这种技术,用户可以通过gdbstub调试操作系统内核,或者使用gdbserver 调试应用程序。插桩的优势在于软件灵活性软强, gdbstub和gdbserver 已经被移植到 ARM、PowerPC、X86、SARC等平台,不仅能够实现调

国嵌视频教程下载

嵌入式Linux视频教程 相关搜索:简体中文, 学习方法, 视频教程, 普通话, 嵌入式 中文名: 嵌入式Linux视频教程 资源格式: 光盘镜像 学校: 成都国嵌嵌入式培训中心版本: 成都国嵌嵌入式培训中心的基于广州友善之发行日期: 2010年 地区: 大陆 对白语言: 普通话 文字语言: 简体中文 视频光盘目录结构 国嵌视频1.iso -学习方法与课程体系介绍(学前必看) -学习方法介绍.avi -国嵌嵌入式课程体系.pdf -嵌入式Linux学习方法.pdf -国嵌课程1-嵌入式入门体验班(上) -第1天(嵌入式系统概述) -国嵌体验入门班-1-1(嵌入式系统概述).avi -国嵌体验入门班-1-2(ARM概述).avi -国嵌体验入门班-1-3(嵌入式Linux概述).avi -国嵌体验入门班-1-4(2440开发板介绍).avi -国嵌体验入门班-1-5(软硬件环境搭建).avi -第2天(开发板快乐体验) -国嵌体验入门班-2-1(开发板系统安装).avi -国嵌体验入门班-2-1(开发板系统安装-Jlink方式).avi -国嵌体验入门班-2-1(开发板系统安装-并口方式).avi -国嵌体验入门班-2-2(裸机程序体验).avi -国嵌体验入门班-2-3(QT系统体验).avi -国嵌体验入门班-2-4(Android系统体验).avi 国嵌视频2.iso

-国嵌课程1-嵌入式入门体验班(下) -第3天(Linux系统体验) -国嵌体验入门班-3-1(Linux定制安装).avi -国嵌体验入门班-3-2(Linux命令).avi -国嵌体验入门班-3-3(VI使用).avi -国嵌体验入门班-3-4(Linux系统管理).avi -国嵌体验入门班-3-5(Shell编程).avi -国嵌体验入门班-3-6(Qcd功能演示).avi -国嵌体验入门班-3-7(必修实验).avi -国嵌课程2-嵌入式Linux应用开发班 -第1天(编程基础) -国嵌应用班-1-1(GCC程序编译).avi -国嵌应用班-1-2(GDB程序调试).avi -国嵌应用班-1-3(makefile工程管理).avi -国嵌应用班-1-4(必修实验).avi -第2天(文件时间编程) -国嵌应用班-2-1(系统调用方式访问文件).avi -国嵌应用班-2-2(库函数访问文件).avi -国嵌应用班-2-3(时间编程).avi -国嵌应用班-2-4(必修实验).avi -第3天(多进程程序设计) -国嵌应用班-3-1(进程控制原理).avi -国嵌应用班-3-2(进程控制程序设计).avi -国嵌应用班-3-3(必修实验).avi -第4天(进程间通讯) -国嵌应用班-4-1(进程间通讯概述).avi -国嵌应用班-4-2(管道通讯).avi -国嵌应用班-4-3(信号通讯).avi -国嵌应用班-4-4(共享内存通讯).avi -国嵌应用班-4-5(必修实验).avi -第5天(进程间通讯) -国嵌应用班-5-1(消息队列).avi

gdb单步调试(中)

一、设置断点(BreakPoint ) 我们用break 命令来设置断点。正面有几点设置断点的方法: break 在进入指定函数时停住。C++ 中可以使用class::function 或function(type,type) 格式来指定函数名。 break 在指定行号停住。 break +offset break -offset 在当前行号的前面或后面的offset 行停住。offiset 为自然数。 break filename:linenum 在源文件filename 的linenum 行处停住。 break filename:function 在源文件filename 的function 函数的入口处停住。 break *address 在程序运行的内存地址处停住。 break break 命令没有参数时,表示在下一条指令处停住。 break ... if ... 可以是上述的参数,condition 表示条件,在条件成立时停住。比如在循环境体中,可以设置break if i=100 ,表示当i 为100 时停住程序。 查看断点时,可使用info 命令,如下所示:(注:n 表示断点号) info breakpoints [n] info break [n] 二、设置观察点(WatchPoint ) 观察点一般来观察某个表达式(变量也是一种表达式)的值是否有变化了,如果有变化,马上停住程序。我们有下面的几种方法来设置观察点: watch 为表达式(变量)expr 设置一个观察点。一量表达式值有变化时,马上停住程序。 rwatch

GDB调试精粹及使用实例

GDB调试精粹及使用实例 一:列文件清单 1. List (gdb) list line1,line2 二:执行程序 要想运行准备调试的程序,可使用run命令,在它后面可以跟随发给该程序的任何参数,包括标准输入和标准输出说明符(<和>)和外壳通配符(*、?、[、])在内。 如果你使用不带参数的run命令,gdb就再次使用你给予前一条run命令的参数,这是很有用的。 利用set args 命令就可以修改发送给程序的参数,而使用show args 命令就可以查看其缺省参数的列表。 (gdb)set args –b –x (gdb) show args backtrace命令为堆栈提供向后跟踪功能。 Backtrace 命令产生一张列表,包含着从最近的过程开始的所以有效过程和调用这些过程的参数。 三:显示数据 利用print 命令可以检查各个变量的值。 (gdb) print p (p为变量名) whatis 命令可以显示某个变量的类型 (gdb) whatis p type = int * print 是gdb的一个功能很强的命令,利用它可以显示被调试的语言中任何有效的表达式。表达式除了包含你程序中的变量外,还可以包含以下内容: l 对程序中函数的调用 (gdb) print find_entry(1,0) l 数据结构和其他复杂对象 (gdb) print *table_start $8={e=reference=’\000’,location=0x0,next=0x0} l 值的历史成分 (gdb)print $1 ($1为历史记录变量,在以后可以直接引用 $1 的值) l 人为数组 人为数组提供了一种去显示存储器块(数组节或动态分配的存储区)内容的方法。早期的调试程序没有很好的方法将任意的指针换成一个数组。就像对待参数一样,让我们查看内存中在变量h后面的10个整数,一个动态数组的语法如下所示: base@length 因此,要想显示在h后面的10个元素,可以使用h@10: (gdb)print h@10 $13=(-1,345,23,-234,0,0,0,98,345,10)

FreeBSD下GDB调试

2.6 调试 2.6.1 调试器 FreeBSD 自带的调试器叫 gdb (GNU debugger)。要运行,输入 %gdb progname 然而大多数人喜欢在Emacs中运行这个命令。可以这样来起动这个命令: M-x gdb RET progname RET 调试器能让你在一个可控制的环境中运行一个程序。例如,你可以一次运行程序的一行代码,检查变量的值,改变这些值,或者让程序运行到某个定点然后停止等等。你甚至可以调试内核,当然这样会比我们将要讨论的问题要多一点点技巧。 gdb 有非常棒的在线帮助,还有同样棒的 info 页面。因此这一章我们会把注意力集中到一些基本的命令上。 最后,如果你不习惯这个命令的命令行界面,在 Ports 中还有一个它的图形前端 (devel/xxgdb)。 这一章准备只介绍 gdb 的使用方法,而不会牵涉到特殊的问题比如调试内核。 2.6.2 在调试器中运行一个程序 要最大限度的利用 gdb,需要使用 -g 这个选项来编译你的程序。如果你没有这样做,那么你只会看到你正在调试的函数名字,而不是它的源代码。如果gdb起动时提示: ... (no debugging symbols found) ... 你就知道你的程序在编译的时候没有使用 -g 选项。 当 gdb 给出提示符,输入 break main。这就是告诉调试器你对正在运行的程序中预先设置的代码没有兴趣,并且调试器应该停在你的代码的开头。然后输入 run来开始你的程序──这会从预先设置的代码开始然后在调试器调用 main() 的时候就停下来。(如果你曾迷惑 main() 是在哪里被调用的,现在应该明白了吧!)

GDB调试及实例

GDB调试及实例 一:列文件清单 1.List (gdb) list line1,line2 二:执行程序 要想运行准备调试的程序,可使用run命令,在它后面可以跟随发给该程序的任何参数,包括标准输入和标准输出说明符(<和>)和外壳通配符(*、?、[、])在内。 如果你使用不带参数的run命令,gdb就再次使用你给予前一条run命令的参数,这是很有用的。 利用set args 命令就可以修改发送给程序的参数,而使用show args 命令就可以查看其缺省参数的列表。 (gdb)set args –b –x (gdb) show args backtrace命令为堆栈提供向后跟踪功能。 Backtrace 命令产生一张列表,包含着从最近的过程开始的所以有效过程和调用这些过程的参数。 三:显示数据 利用print 命令可以检查各个变量的值。 (gdb) print p (p为变量名) whatis 命令可以显示某个变量的类型 (gdb) whatis p type = int * print 是gdb的一个功能很强的命令,利用它可以显示被调试的语言中任何有效的表达式。表达式除了包含你程序中的变量外,还可以包含以下内容: l 对程序中函数的调用 (gdb) print find_entry(1,0) l 数据结构和其他复杂对象 (gdb) print *table_start $8={e=reference=’\000’,location=0x0,next=0x0} l 值的历史成分 (gdb)print $1 ($1为历史记录变量,在以后可以直接引用$1 的值) l 人为数组 人为数组提供了一种去显示存储器块(数组节或动态分配的存储区)内容的方法。早期的调试程序没有很好的方法将任意的指针换成一个数组。就像对待参数一样,让我们查看内存中在变量h后面的10个整数,一个动态数组的语法如下所示: base@length 因此,要想显示在h后面的10个元素,可以使用h@10: (gdb)print h@10 $13=(-1,345,23,-234,0,0,0,98,345,10)

gdb调试

今天碰到了一个bug,服务器在运行时会core dump在一个很灵异的地方,排除这个错误的过程,以及最后发现的错误结果很具有典型性,所牵涉到的技术也很多,拿来作为Linux调试的课程挺好的。:-P 整个里面假设读者已经知道怎么用gdb,如果不知道,请参见GDB Manual 首先,很幸运的是,这个问题是可以很容易重现的,而且更重要的,有core dump。 拿到core dump之后,惯例是查看一下调用栈:(为了避免泄漏商业秘密,所有函数名,文件名什么的都用foo啊,bar啊,foobar啊,blabla啊等等代替)。 (gdb) bt #0 0x000000eb in ?? () #1 0x3aa1d941 in ?? () #2 0x000001f8 in ?? () #3 0x080cf888 in foo (range=10000) at foo/foo.c:18 #4 0x080c1f29 in bar () at bar/bar.c:423 [....] (gdb) info f 0 Stack frame at 0xbfc42548: eip = 0xeb; saved eip 0x3aa1d941 called by frame at 0xbfc4254c Arglist at 0xbfc42540, args: Locals at 0xbfc42540, Previous frame's sp is 0xbfc42548 Saved registers: eip at 0xbfc42544 (gdb) f 3 #3 0x080cf888 in foo (range=10000) at foo/foo.c:18 18 return ((u32)random()) % range; 相当的灵异,栈上的0,1,2都是,一个返回地址怎么可能是0×1f8,而且,core dump的原因是因为eip 跑飞到了0xeb。到frame 3的时候看起来正常了,但是出错的地方在random这种简单的库函数上。不

使用QEMU+GDB调试Linux内核

使用QEMU调试Linux内核 一.使用QEMU安装Ubuntu10.04 1.安装qemu ubuntu下使用sudo apt-get install 安装的qemu版本是0.12.3,该版本中存在bug,使得无法在断点处停下;因此需要在qemu官方网站https://www.doczj.com/doc/c29918310.html,/Download上下载最新的版本qemu-0.12.5.tar.gz的源代码包自己进行编译安装: ●Sudo apt-get install zlib1g-dev libsdl-dev ●解压源代码后,进入源代码所在目录执行./confingure ●执行make ●执行sudo make install 2.创建QEMU格式的硬盘 qemu-img create –f qcow2name.img size 例如:qemu-img create –f qcow2 ubuntu10.04.img 4GB 3.在创建的硬盘上安装操作系统 qemu–hdaname.img–cdrom ~/Download/ubuntu10.04.iso –boot d 说明:使用hda指定硬盘镜像,使用CDROM选定光驱。-boot d 指从cdrom启动,-boot a是软盘,-boot c 是硬盘;使用qemu或qemu-system-x86_64(64为机子),有时安装系统会很慢,这是可以考虑使用kvm来代替。 例如:kvm–hda ubuntu10.04.img –cdrom ./ubuntu-10.04.iso -boot

d 4.从已经装好操作系统的硬盘启动 qemu–hda ubuntu10.04.img 5.在64位的主机上要使用qemu-system-x86_64命令来代替qemu 二.自己编译内核 现将Linux的编译调节过程简述为: 1. 下载自己要调试的Linux内核的源代码,这个可以从Linux内 核的官方网站上得到:https://www.doczj.com/doc/c29918310.html, 2. 编译内核最主要的便是配置文件.config,为了能够准确的得到 结果(第一次不要求编译时间),将本机的config文件直接拷 贝到解压后的源代码中。 3.然后进行make操作,结束后将产生的bzImage文件拷到boot 目录下,重启,选择自己编译的内核,这样一般不会出问题,但时间较慢,大约编译一次需要40分钟。 3.1以前编译内核是为调试内核服务的,现在做华为的项目, 发现需要在实际的机器上运行自己编译的内核,参考网站: https://www.doczj.com/doc/c29918310.html,/tips/compiling-linux-kernel-26.html 4.为了降低编译时间,就需要对配置文件进行裁剪,在配置文件 中有好多是本机不需要的模块,参考: https://www.doczj.com/doc/c29918310.html,/Linux/kernel_options.html。另外调试内 核与模块无关,所以辨识为M的直接可以不选。 5.剪裁的时候采用“逐步瘦身”法,先剪裁掉某个或某几个模块, 然后在进行编译,若没错,在进行模块裁剪,这样可以最大程

如何在Linux中使用gdb调试C程序

如何在Linux中使用gdb调试C程序 无论多么有经验的程序员,开发的任何软件都不可能完全没有bug。因此,排查及修复bug 成为软件开发周期中最重要的任务之一。有许多办法可以排查bug(测试、代码自审等等),但是还有一些专用软件(称为调试器)可以帮助准确定位问题的所在,以便进行修复。 如果你是C/C++ 程序员,或者使用Fortran 和Modula-2 编程语言开发软件,那么你将会很乐意知道有这么一款优秀的调试器- GDB - 可以帮你更轻松地调试代码bug 以及其它问题。在这篇文章中,我们将讨论一下GDB 调试器的基础知识,包括它提供的一些有用的功能/选项。 在我们开始之前,值得一提的是,文章中的所有说明和示例都已经在Ubuntu 14.04 LTS 中测试过。教程中的示例代码都是 C 语言写的;使用的shell 为bash(4.3.11);GDB 版本为7.7.1。 GDB 调试器基础 通俗的讲,GDB 可以让你看到程序在执行过程时的内部流程,并帮你明确问题的所在。我们将在下一节通过一个有效的示例来讨论GDB 调试器的用法,但在此之前,我们先来探讨一些之后对你有帮助的基本要点。 首先,为了能够顺利使用类似GDB 这样的调试器,你必须以指定的方式编译程序,让编译器产生调试器所需的调试信息。例如,在使用gcc 编译器(我们将在本教程之后的章节用它来编译C 程序示例)编译代码的时候,你需要使用 -g 命令行选项。 IT网,http://it 想要了解gcc 编译器手册页中关于 -g 命令行选项相关的内容,请看这里。 下一步,确保在你的系统中已经安装GDB 调试器。如果没有安装,而且你使用的是基于Debian 的系统(如Ubuntu),那么你就可以使用以下命令轻松安装该工具: sudo apt-get install gdb 在其他发行版上的安装方法,请看这里。 现在,当你按照上述的方式编译完程序(gcc -g 命令行选项),同时也已经安装好GDB 调

嵌入式Linux 应用开发教程 用gdb调试程序的bug

计算机工程系实验报告 课程名称:嵌入式Linux 应用开发教程 班级:姓名:学号: 实验目的: 通过调试一个有问题的程序,进一步熟练使用vi操作,而且熟练掌握gcc编译命令及gdb的调试命令,通过对有问题程序的跟踪调试,进一步提高发现问题和解决问题的能力。 实验环境: linux 编译器gcc 调试器gdb vi编辑器 实验内容及过程: (1)使用vi编辑器,将一下代码输入到名为greet.c的文件中。此代码的原意为输出倒序main函数中定义的字符串,但结果显示没有输出,代码如下所示。 #include int display1(char *string); int display2(char *string); int main() { char string[]= “Embedded Linux”; display1 (string); display2 (string); } Int display1 (char *string) { printf (“The original string is %s \n”, string); } Int display2 (char *string1) { char *string2; int size,i; size = strlen (string1); string = (char *) malloc (size + 1); for (i = 0;i < size;i++) { String2[size - i] =string1[i]; } String[size+1] = ‘’; printf(“The string afterward is %s\n”,string2);

gdb调试工具

用 GDB 调试程序 原著:Rick McMullin 用 gdb 调试 GCC 程序 Linux 包含了一个叫 gdb 的 GNU 调试程序. gdb 是一个用来调试 C 和 C++ 程序的强力调试器. 它使你能在程序运行时观察程序的内部结构和内存的使用情况. 以下是 gdb 所提供的一些功能: 它使你能监视你程序中变量的值. 它使你能设置断点以使程序在指定的代码行上停止执行. 它使你能一行行的执行你的代码. 在命令行上键入 gdb 并按回车键就可以运行 gdb 了, 如果一切正常的话, gdb 将被启动并且你将在屏幕上看到类似的内容: GDB is free software and you are welcome to distribute copies of it under certain conditions; type "show copying" to see the conditions. There is absolutely no warranty for GDB; type "show warranty" for details. GDB 4.14 (i486-slakware-linux), Copyright 1995 Free Software Foundation, Inc. (gdb) 当你启动 gdb 后, 你能在命令行上指定很多的选项. 你也可以以下面的方式来运行gdb : gdb 当你用这种方式运行 gdb , 你能直接指定想要调试的程序. 这将告诉gdb 装入名为fname 的可执行文件. 你也可以用 gdb 去检查一个因程序异常终止而产生的 core 文件, 或者与一个正在运行的程序相连. 你可以参考 gdb 指南页或在命令行上键入 gdb -h 得到一个有关这些选项的说明的简单列表. 为调试编译代码(Compiling Code for Debugging) 为了使 gdb 正常工作, 你必须使你的程序在编译时包含调试信息. 调试信息包含你程序里的每个变量的类型和在可执行文件里的地址映射以及源代码的行号. gdb 利用这些信息使源代码和机器码相关联. 在编译时用 -g 选项打开调试选项. gdb 基本命令 gdb 支持很多的命令使你能实现不同的功能. 这些命令从简单的文件装入到允许你检查所调用的堆栈内容的复杂命令, 表27.1列出了你在用 gdb 调试时会用到的一些命令. 想了解 gdb 的详细使用请参考 gdb 的指南页.

用GDB调试程序--调试器GDB常用功能

一,GDB调试器简介 GDB是Linux下的常用调试器,主要用于调试源代码、调试运行中的进程和查看core dump文件。Linux下的调试器主要有gdb、cgdb、ddd、eclipse。GDB调试器的运行速度快、能够进行源代码同步显示。 使用-tui 选项开启gdb命令输入和源代码的分屏显示,tui即Terminal User Interface。 二,GDB常用调试命令 a)调试可执行文件 以源代码为/home/zebra/目录下的test.c文件产生的可执行文件test为例(可执行文件使用gcc进行编译,并使用-g选项以产生调试信息),进行命令的说明(详细源代码参见第三部分:三,调试实例分析 )。 gdb调试源代码流程: 1,开启gdb程序,即运行如下命令:gdb -q (-q用以使得gdb不输出gdb程序的版本等信息)2,指定调试的二进制文件:file test 3,list查看源代码 4,设定断点breakpoint main breakpoint sub 上述分别在main函数和sub函数处设定了断点。 断点可以设置在任意已知源代码文件的某一行,某一个函数,同时可以指定是设置在哪个/哪些线程上(见下边描述)。 5,运行可执行文件: run 6,这样程序会运行到断点处停止下来,gdb会打印当前断点的信息。 7,使用s 或者n进行单步调试。 s即step,n即next都是执行一条语句,然后停下来。 如果想执行一条汇编语句,则可以使用si ,ni,即step instruction,next instruction。 8,bt命令查看当前的调用栈,bt即backtrace。 9,info frame查看函数帧信息。 10,frame n 进入某个函数帧(编号为n) 11,info 命令可以对当前的函数帧的寄存器、局部变量、函数的参数进行查看。 info register;info local;info args。 12,disassemble对当前函数对应的二进制进行反汇编。 13,x/nfu address 查看内存其中address是内存开始的地址,从该地址向高地址增加, x是examinate的缩写,n表示重复次数,f表示输出格式,u表示内存大小的单位(默认是字,即4个字节)。 一般我都用x/nx address,即打印n个从address开始的内存,每个是4字节,以十六进制打印。14,continue,执行至该函数退出 15,info threads,显示当前可调试的所有线程 16,thread ,切换当前调试的线程为指定ID的线程break File:LineNumber thread if x==y。 17,thread apply command让一个/多个/所有线程执行GDB的命令command。

linux下的静态库与动态库的区别,Gdb调试段错误,自动生成Makefile

linux下的静态库与动态库的区别 1.什么是库 在windows平台和linux平台下都大量存在着库。 本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。 由于windows和linux的本质不同,因此二者库的二进制是不兼容的。 2.库的种类 linux下的库有两种:静态库和共享库(动态库)。 二者的不同点在于代码被载入的时刻不同。 静态库的代码在编译过程中已经被载入可执行程序,因此体积较大。 共享库的代码是在可执行程序运行时才载入内存的,在编译过程中仅简单的引用,因此代码体积较小。 3.库存在的意义 库是别人写好的现有的,成熟的,可以复用的代码,你可以使用但要记得遵守许可协议。现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。 共享库的好处是,不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例。 4.库文件是如何产生的在linux下 静态库的后缀是.a,它的产生分两步 Step 1.由源文件编译生成一堆.o,每个.o里都包含这个编译单元的符号表 Step 2.ar命令将很多.o转换成.a,成文静态库 动态库的后缀是.so,它由gcc加特定参数编译产生。 例如: $ gcc -fPIC -c *.c $ gcc -shared -Wl,-soname, libfoo.so.1 -o libfoo.so.1.0 *. 5.库文件是如何命名的,有没有什么规范 在linux下,库文件一般放在/usr/lib /lib下, 静态库的名字一般为libxxxx.a,其中xxxx是该lib的名称 动态库的名字一般为libxxxx.so.major.minor,xxxx是该lib的名称,major是主版本号,minor是副版本号 6.如何知道一个可执行程序依赖哪些库 ldd命令可以查看一个可执行程序依赖的共享库, 例如# ldd /bin/lnlibc.so.6 => /lib/libc.so.6 (0×40021000)/lib/ld-linux.so.2 => /lib/ld- linux.so.2 (0×40000000) 可以看到ln命令依赖于libc库和ld-linux库 Gdb调试段错误 1.段错误是什么

GDB基本命令

1. 本文介绍使用gdb调试程序的常用命令。 主要内容: [简介] [举例] [其他] [简介] ============= GDB是GNU开源组织发布的一个强大的UNIX下的程序调试工具。如果你是在UNIX平台下做软件,你会发现GDB这个调试工具有比VC、BCB的图形化调试器更强大的功能。同时GDB也具有例如ddd这样的图形化的调试端。 一般来说,GDB主要完成下面四个方面的功能: (1)启动你的程序,可以按照你的自定义的要求随心所欲的运行程序。 (2)可让被调试的程序在你所指定的调置的断点处停住。(断点可以是条件表达式) (3)当程序被停住时,可以检查此时你的程序中所发生的事。 (4)动态的改变你程序的执行环境。 [举例] ============= *启动gdb $gdb 这样可以和gdb进行交互了。 *启动gdb,并且分屏显示源代码: $gdb -tui 这样,使用了'-tui'选项,启动可以直接将屏幕分成两个部分,上面显示源代码,比用list方便多了。这时候使用上下方向键可以查看源代码,想要命令行使用上下键就用[Ctrl]n和[Ctrl]p. *启动gdb调试指定程序app: $gdb app 这样就在启动gdb之后直接载入了app可执行程序,需要注意的是,载入的app程序必须在编译的

时候有gdb调试选项,例如'gcc -g app app.c',注意,如果修改了程序的源代码,但是没有编译,那么在gdb中显示的会是改动后的源代码,但是运行的是改动前的程序,这样会导致跟踪错乱的。 *启动程序之后,再用gdb调试: $gdb 这里,是程序的可执行文件名,是要调试程序的PID.如果你的程序是一个服务程序,那么你可以指定这个服务程序运行时的进程ID。gdb会自动attach上去,并调试他。program应该在PATH环境变量中搜索得到。 *启动程序之后,再启动gdb调试: $gdb 这里,程序是一个服务程序,那么你可以指定这个服务程序运行时的进程ID,是要调试程序的PID.这样gdb就附加到程序上了,但是现在还没法查看源代码,用file命令指明可执行文件就可以显示源代码了。 **启动gdb之后的交互命令: 交互命令支持[Tab]补全。 *显示帮助信息: (gdb) help *载入指定的程序: (gdb) file app 这样在gdb中载入想要调试的可执行程序app。如果刚开始运行gdb而不是用gdb app启动的话可以这样载入app程序,当然编译app的时候要加入-g调试选项。 *重新运行调试的程序: (gdb) run 要想运行准备调试的程序,可使用run命令,在它后面可以跟随发给该程序的任何参数,包括标准输入和标准输出说明符(<和> )和shell通配符(*、?、[、])在内。

相关主题
文本预览
相关文档 最新文档