gdb调试(c语言和汇编指令) 您所在的位置:网站首页 gdb显示汇编代码 gdb调试(c语言和汇编指令)

gdb调试(c语言和汇编指令)

2023-10-13 18:40| 来源: 网络整理| 查看: 265

环境: ubuntu 18.04 gdb 背景:调试Intel ipp库

目录 一、gcc的安装二、icc的安装三、编译器的使用3.1 基本用法3.1.1 查看命令文档3.1.2 基本的编译命令3.1.3 运行目标文件 3.2 多文件编译3.2.1 源码3.2.2 编译命令3.2.3 运行命令 3.3 编译双线性变换demo3.3.1 编译命令3.3.2 运行命令 3.4 编译金字塔demo3.4.1 编译命令3.4.2 运行命令 三、gdb的安装方法1 (推荐)apt安装gdb方法2 源码安装测试 四、gdb的使用4.1 gdb命令行调试c程序4.1.1 gdb启动命令4.1.2 gdb断点命令(b/break)断点命令介绍示例其它关于断点的命令 4.1.3 gdb运行命令(r/run)4.1.4 gdb查看代码命令(l/list)4.1.5 查看当前代码运行位置(bt/backtrace)4.1.6 gdb打印变量命令(p/print)4.1.7 gdb下一行代码命令(n/next)4.1.8 gdb继续执行命令(c/continue)4.1.9 gdb进入函数命令(s/step)4.1.6 其它命令 4.2 使用gdb的tui调试c程序`写在前面`4.2.1 启动tui4.2.2 tui的退出4.2.3 tui的一些设置 4.2 gdb命令行调试汇编程序4.2.1 启动gdb4.2.2 启动tui的汇编窗口4.2.3 设置汇编风格为intel4.2.4 汇编级别的跳转指令4.2.5 tui显示寄存器的值4.2.6 打印寄存器的值4.2.5 打印内存中的值4.2.6 反汇编某个函数4.2.7 在根据指令地址设置断点

一、gcc的安装

直接apt安装(build-essential包里面包含了gcc,g++之类的工具)

sudo apt install build-essential

测试

gcc -v 二、icc的安装

icc全称是Intel C++ Compiler,intel的文档中是使用的icc这个名称,但是实际的编译器可执行文件不叫icc,而是icx(不过好像在别的平台也有其它的编译器),在我这个文档里icc=icx

参考ipp的安装的文档

三、编译器的使用

gcc和icx都是编译器,不过编译ippi代码时,使用gcc编译的话,有时会有问题(应该是c语言运行库不同),因此还是使用icx来编译吧(gcc作为一个通用的编译器来使用)

3.1 基本用法 3.1.1 查看命令文档

文档内容很多,不需要全部了解

icx --help 3.1.2 基本的编译命令 icx helloworld.c -o helloworld

命令说明:helloworld.c为源文件,helloworld为目标文件

3.1.3 运行目标文件 ./helloworld 3.2 多文件编译 3.2.1 源码

编写三个文件main.c,foo.h,foo.c main.c

#include #include "foo.h" int main() { int a = 1, b = 2, c = 3; int res = foo(a, b, c); printf("a+b+c=%d\n", foo(a, b, c)); return 0; }

foo.h

#ifndef __FOO_H__ #define __FOO_H__ #include "foo.h" int foo(int a, int b, int c); #endif // __FOO_H__

foo.c

#include "foo.h" int foo(int a, int b, int c){ return a+b+c; } 3.2.2 编译命令 gcc main.c foo.c -g -o main 3.2.3 运行命令 ./main 3.3 编译双线性变换demo

以intel提供的GetBilinearTransform.c为例

3.3.1 编译命令 icx GetBilinearTransform.c -g -static -o GetBilinearTransform -I$IPPROOT/include -L$IPPROOT/lib/intel64 -lippi -lipps -lippcore

命令说明:

GetBilinearTransform.c为源文件-g表示为调试添加符号信息 -static表示静态编译(静态编译的话,调试起来方便一些,不过不是必须的)-o GetBilinearTransform 表示目标文件为GetBilinearTransform,-I$IPPROOT/include表示添加头文件搜索路径-L$IPPROOT/lib/intel64表示添加库搜索路径-lippi,-lipps和-lippcore表示链接libippi.so,libipps.so,libippcore.so这三个库文件 3.3.2 运行命令 ./GetBilinearTransform 3.4 编译金字塔demo 3.4.1 编译命令 icx Pyramid.c -g -static -o Pyramid -IPPROOT/include -LPPROOT/lib/intel64 -lippi -lipps -lippcore -lippcv

命令说明:

Pyramid.c为源文件-g表示为调试添加符号信息 -static表示静态编译(静态编译的话,调试起来方便一些,不过不是必须的)-o Pyramid表示目标文件为Pyramid-I$IPPROOT/include表示添加头文件搜索路径-L$IPPROOT/lib/intel64表示添加库搜索路径-lippi,-lipps,-lippcore和-lippcv表示链接libippi.so,libipps.so,libippcore.so,libippcv.so,这四个库文件 3.4.2 运行命令 ./Pyramid 三、gdb的安装

两种方法,源码安装和apt安装

方法1 (推荐)apt安装gdb sudo apt install gdb 方法2 源码安装

参考gdb 10.2的安装

测试 gdb -v 四、gdb的使用 4.1 gdb命令行调试c程序 4.1.1 gdb启动命令

直接gdb 目标文件即可,以前面编译的main为例

gdb main 4.1.2 gdb断点命令(b/break) 断点命令介绍

gdb中设置断点的命令为b[reak],break是完整命令,不过一般是使用简称b代替。 gdb中有很多种断点(参考链接),这里只介绍常用的几个

根据函数名设置断点。命令为b 函数名和b *函数名, 需要注意的是,b *函数名和b 函数名在c语言级别好像没区别,但是在汇编级别是有区别的。简单来说就是b *函数名要比b 函数名的断点更靠前,这个实际运行一下就知道了。根据行号设断点。b linenum 或者break filename:linenum。filename为文件名,为空表示当前文件,linenum为行号。根据指令地址设置断点。b *addr,其中addr为指令地址(如果动态链接的动态库还没有加载的话,可能会报错)。 示例

函数断点最常用,因此在这里演示函数断点。继续前面的操作,在gdb中输出命令 b foo 如图: 在这里插入图片描述

其它关于断点的命令 info b, 简写为i b 查看断点信息,打印的信息中包括断点的编号,断点类型,是否一直生效,是否启用,断点地址,断点位置等。disable b,简写为dis b num,将编号为num的断点暂时禁用enable b,简写为ena b num,将编号为num的断点启用clear和delete,删除所有断点 除此之外,还有条件断点、监控断点等等,此处略。 4.1.3 gdb运行命令(r/run)

运行命令为r[un],简称为r 继续上面的操作,键入命令r,如下图 在这里插入图片描述 可以看到程序在foo函数处停下来了,且上面显示了foo函数的参数。

4.1.4 gdb查看代码命令(l/list)

如图 在这里插入图片描述

4.1.5 查看当前代码运行位置(bt/backtrace)

查看当前代码位置,也即查看调用堆栈,可以使用命令backtrace,简称bt,如图,可以看到当前位置,栈1为main.c的第8行,栈0为foo.c的第4行 在这里插入图片描述 补充:如果堆栈很长,可以使用命令bt n;如果想要打印局部变量,可以使用bt full。 例如:bt 3,只打印前3层的堆栈信息;bt full打印堆栈并打印各自的局部变量;bt full 3打印前3层的堆栈信息,并打印各自的局部变量。

4.1.6 gdb打印变量命令(p/print)

help p可以看到p的使用方法,这里也只说几种比较常见的用法。 示例: p /t var, 以二进制打印var p /d var, 以十进制打印var p /x var, 以十六进制打印var p /f var, 以浮点数形式打印var 在这里插入图片描述 另外,p命令里面也可以直接进行运算,比如加减乘除 p /d 0x10+0x20 p /d a-0x20 p /d 0x10*b p /d a/b

4.1.7 gdb下一行代码命令(n/next)

下一行命令为n[ext],简称为n 单独的n,表示跳到下一行 当然也可以一次跳很多行,n num,表示跳num行代码 如图,我们从foo.c的第四行跳到了foo.c的第5行 在这里插入图片描述

4.1.8 gdb继续执行命令(c/continue)

继续执行命令为c[ontinue],简称为c,继续执行直到遇到下一个断点。

4.1.9 gdb进入函数命令(s/step)

进入函数指令为s[tep],简写s 与n的区别就是,当当前代码为函数调用时,n不会进入函数内部,s会进入函数内部

4.1.6 其它命令

finish 退出函数体 unitl 退出循环 set $i=1 设置临时变量,也可以用来动态修改寄存器的值,比如可以修改x86的eflags寄存器来改变程序的运行流程(参考)

4.2 使用gdb的tui调试c程序

tui命令参考

写在前面

gdb的tui图形化有bug,有时屏幕会花掉,这个时候不用慌,只要刷新一下即可,快捷键是ctrl+L

4.2.1 启动tui

虽然一般使用gdb都是直接使用命令行的,不过gdb也提供了tui来实现简单的可视化,启动tui的方法有很多,这里只介绍我觉得比较方便的。

方式1:在gdb里面输入命令tui enable方式2:在gdb里面输入命令layout src(这里的src表示源码显示)

tui的初始界面如下图所示 在这里插入图片描述 图中的箭头指示的就是当前代码的位置,b+表示的是断点位置,当前是两个位置重合了。

4.2.2 tui的退出

退出tui,即返回命令行界面

方式一:使用组合快捷键, ctrl+x,a(操作就是先按ctrl+x,然后再按a)方式二:输入命令tui disable 4.2.3 tui的一些设置 focus cmd tui的一些默认设置我觉得很难受,比如进入tui之后,默认选中的是代码区域(比如前面图中,可以看到代码窗口(src window)周围有一圈蓝色,这表示代码区域被选中)。代码窗口被选中的情况下,键盘的上下左右键就变成了移动代码窗口,你输入命令的时候甚至不能往左边移动光标(虽然快捷键ctrl+b可以做到,但是也太反人类了),总之这样很不方便。解决办法就是输入focus cmd命令来选中命令窗口,这样就ok了,如下图 在这里插入图片描述 4.2 gdb命令行调试汇编程序

依然以上面的main程序为例,但是我们现在对它进行汇编级调试,为了直观,我们启用tui。

4.2.1 启动gdb

方法和前面相同,此处设置了三个断点main、foo、*foo。(注意,foo的断点在*foo断点之后,具体原理应该和堆栈有关,略)

ubuntu@WZY:~/test$ gdb main GNU gdb (GDB) 10.2 Copyright (C) 2021 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 "x86_64-pc-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see: . Find the GDB manual and other documentation resources online at: . For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from main... (gdb) b main Breakpoint 1 at 0x652: file main.c, line 7. (gdb) b foo Breakpoint 2 at 0x6b5: file foo.c, line 4. (gdb) b *foo Breakpoint 3 at 0x6a8: file foo.c, line 3. (gdb) i b Num Type Disp Enb Address What 1 breakpoint keep y 0x0000000000000652 in main at main.c:7 2 breakpoint keep y 0x00000000000006b5 in foo at foo.c:4 3 breakpoint keep y 0x00000000000006a8 in foo at foo.c:3 4.2.2 启动tui的汇编窗口

与前面不同,这里的tui启动命令改为layout asm,其中asm表示汇编,如下图所示,显示的就是汇编代码了 在这里插入图片描述

4.2.3 设置汇编风格为intel

个人习惯intel的汇编风格,因为AT的汇编中mov的目标寄存器在右侧,实在不习惯,但是gdb的默认汇编风格为AT,因此要手动进行切换。切换命令为

set disassembly-flavor intel 4.2.4 汇编级别的跳转指令 跳到下一条指令, nexti,简写ni进入函数调用,stepi,简称si其它的跳转指令,和c语言级别的一样,略 4.2.5 tui显示寄存器的值

tui除了之前提到的src和asm窗口外,还有一个reg窗口,这个窗口会显示寄存器的值。 显示命令如下

tui reg general 显示通用寄存器tui reg vector 显示向量寄存器tui reg all 显示所有寄存器其它类型可以自行查看,方法是键入tui reg ,然后按两下tab键会显示命令补全。

tui reg general 如下图 在这里插入图片描述

4.2.6 打印寄存器的值

p $reg 可以打印某个寄存器的值,其中p命令的参数和前面提到的一致。 例如:

(gdb) p $rsp $1 = (void *) 0x7ffffffec698

特别的,打印xmm寄存器时,默认输出内容很多。比如下面所示(我把xmm1寄存器的每个字节从低地址到高地址设置成了{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})

(gdb) p $xmm1 $2 = {v8_bfloat16 = {9.477e-38, 1.54e-36, 2.501e-35, 4.063e-34, 6.596e-33, 1.071e-31, 1.738e-30, 2.82e-29}, v4_float = { 1.53998961e-36, 4.06321607e-34, 1.07111903e-31, 2.82126017e-29}, v2_double = {5.447603722011605e-270, 2.500364306227096e-231}, v16_int8 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, v8_int16 = {513, 1027, 1541, 2055, 2569, 3083, 3597, 4111}, v4_int32 = {67305985, 134678021, 202050057, 269422093}, v2_int64 = {578437695752307201, 1157159078456920585}, uint128 = 21345817372864405881847059188222722561}

这是因为xmm寄存器里面的值可以以不同形式来读取,如果你知道你的xmm0寄存器里面的值具体类型的话,可以直接输入对应类型,如下

(gdb) p $xmm1.v16_int8 $3 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} (gdb) p $xmm1.v16_int8[1] $4 = 2 (gdb) p $xmm1.v16_int8[10] $11 = 11 (gdb) p $xmm1.v8_int16 $7 = {513, 1027, 1541, 2055, 2569, 3083, 3597, 4111} (gdb) p /x $xmm1.v8_int16 $8 = {0x201, 0x403, 0x605, 0x807, 0xa09, 0xc0b, 0xe0d, 0x100f} (gdb) p /f $xmm1.v2_double $10 = {5.447603722011605e-270, 2.500364306227096e-231}

另外,wsl好像不能成功识别ymm寄存器,因此无法看到ymm寄存器里面的值。我在我真实的linux上跑的时候,可以正常看到ymm寄存器 如果想要看ymm寄存器的值的话,可以装个linux系统,虚拟机没试过,不过感觉应该也可以

4.2.5 打印内存中的值

使用x命令,help x可以看到x命令的使用方法

(gdb) help x Examine memory: x/FMT ADDRESS. ADDRESS is an expression for the memory address to examine. FMT is a repeat count followed by a format letter and a size letter. Format letters are o(octal), x(hex), d(decimal), u(unsigned decimal), t(binary), f(float), a(address), i(instruction), c(char), s(string) and z(hex, zero padded on the left). Size letters are b(byte), h(halfword), w(word), g(giant, 8 bytes). The specified number of objects of the specified size are printed according to the format. If a negative number is specified, memory is examined backward from the address. Defaults for format and size letters are those previously used. Default count is 1. Default address is following last thing printed with this command or "print".

如文档所示,x的format可以指定解析方式和size。其中size的类型有b(byte), h(halfword), w(word), g(giant, 8 bytes),在这里一个word为32位(值得一提的是,intel的汇编里面一个word为16位,double word为32位,quad word为64位,不要弄混,我建议是不要去记gdb里面的word宽度,知道w是32位,g是64位就差不多了) 直接举例子吧, x /xw 0x123456 以16进制打印,地址为0x123456,数据宽度为32位(word) x /xg 0x123456 以16进制打印,地址为0x123456,数据宽度为64位(8 bytes) x /fg $rsp+0x18 以浮点数打印,地址为$rsp+0x18,数据宽度为64位(8 bytes),64位的浮点数也就等价于c语言中的double x /fw $rsp+0x18 以浮点数打印,地址为$rsp+0x18,数据宽度为32位(4 bytes),32位的浮点数也就等价于c语言中的float x /4xb $rsp-0x18 以16进制打印,地址为$rsp-0x18,数据宽度为8位(byte),连着打印4个 x /8dh $rsp-0x18 以10进制打印,地址为$rsp-0x18,数据宽度为16位(halfword),连着打印8个 另外,

如果你手误写重复了,gdb好像默认是第一个选项 比如x /xdf $rsp与x /4x $rsp一样,x /xbwg $rsp与x /xb $rsp一样如果你没有设置格式,默认和上次的格式一致 4.2.6 反汇编某个函数

使用gdb的disassemble可以得到某个函数的反汇编代码

(gdb) disassemble main Dump of assembler code for function main: 0x000000000000064a : push %rbp 0x000000000000064b : mov %rsp,%rbp 0x000000000000064e : sub $0x10,%rsp 0x0000000000000652 : movl $0x1,-0x10(%rbp) 0x0000000000000659 : movl $0x2,-0xc(%rbp) 0x0000000000000660 : movl $0x3,-0x8(%rbp) 0x0000000000000667 : mov -0x8(%rbp),%edx 0x000000000000066a : mov -0xc(%rbp),%ecx 0x000000000000066d : mov -0x10(%rbp),%eax 0x0000000000000670 : mov %ecx,%esi 0x0000000000000672 : mov %eax,%edi 0x0000000000000674 : call 0x6a8 0x0000000000000679 : mov %eax,-0x4(%rbp) 0x000000000000067c : mov -0x8(%rbp),%edx 0x000000000000067f : mov -0xc(%rbp),%ecx 0x0000000000000682 : mov -0x10(%rbp),%eax 0x0000000000000685 : mov %ecx,%esi 0x0000000000000687 : mov %eax,%edi 0x0000000000000689 : call 0x6a8 0x000000000000068e : mov %eax,%esi 0x0000000000000690 : lea 0xbd(%rip),%rdi # 0x754 0x0000000000000697 : mov $0x0,%eax 0x000000000000069c : call 0x520 0x00000000000006a1 : mov $0x0,%eax 0x00000000000006a6 : leave 0x00000000000006a7 : ret End of assembler dump.

多啰嗦一句 直接从终端复制反汇编代码的话,格式比较乱,我建议在gdb中使用管道命令把反汇编代码直接重定向到本地文件中,具体命令为pipe disassemble main | tee main.asm,其中main.asm为目标文件名

4.2.7 在根据指令地址设置断点

这个功能听着挺实用的,因为它能最准确地跳到你想调试的地方,设置的方法很简单,前面也提到过,就是,其中addr为指令地址(b *func和这个原理应该是一样的)。 但是用起来会有蛮多限制条件,一不小心就容易报错。

1、动态链接库的符号加载,动态链接库在没有加载之前是没有实际地址的,因此设置断点时会出现下面这个错误

(gdb) b *l9_ownpi_WarpBilinearBack + 4 No symbol "l9_ownpi_WarpBilinearBack" in current context.

当然你可以在main函数处设置断点,然后运行到断点处,此时由于动态库的符号表已经加载进来所以,可以成功设断点。但是当你r之后,会发现断点又失效了,解决办法是先disable断点,然后r,等进入main函数后再enable断点,然后c,就又可以成功了(不知道有没有其它更好的办法),整个流程如下

ubuntu@WZY:~/ipp/gdb$ gdb-oneapi gdb_WarpBilinearBack ...... For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from gdb_WarpBilinearBack... (gdb) b *l9_ownpi_WarpBilinearBack + 4 No symbol "l9_ownpi_WarpBilinearBack" in current context. (gdb) b main Breakpoint 1 at 0x400f9b: file gdb_WarpBilinearBack.c, line 84. (gdb) c The program is not being run. (gdb) r Starting program: /home/ubuntu/ipp/gdb/gdb_WarpBilinearBack Breakpoint 1, main (argv=1, args=0x7ffffffec748) at gdb_WarpBilinearBack.c:84 84 IppStatus status = ippStsNoErr; (gdb) b *l9_ownpi_WarpBilinearBack + 4 Breakpoint 2 at 0x7ffffc555044 (gdb) c Continuing. Breakpoint 2, 0x00007ffffc555044 in l9_ownpi_WarpBilinearBack () from /opt/intel/oneapi/ipp/2021.1.1/lib/intel64/libippil9.so.10.0 (gdb) c Continuing. Exit status 0 (ippStsNoErr: No errors) [Inferior 1 (process 18633) exited normally] (gdb) r Starting program: /home/ubuntu/ipp/gdb/gdb_WarpBilinearBack Error in re-setting breakpoint 2: No symbol "l9_ownpi_WarpBilinearBack" in current context. Error in re-setting breakpoint 2: No symbol "l9_ownpi_WarpBilinearBack" in current context. Error in re-setting breakpoint 2: No symbol "l9_ownpi_WarpBilinearBack" in current context. Error in re-setting breakpoint 2: No symbol "l9_ownpi_WarpBilinearBack" in current context. Error in re-setting breakpoint 2: No symbol "l9_ownpi_WarpBilinearBack" in current context. Breakpoint 1, main (argv=1, args=0x7ffffffec748) at gdb_WarpBilinearBack.c:84 84 IppStatus status = ippStsNoErr; (gdb) c Continuing. Exit status 0 (ippStsNoErr: No errors) [Inferior 1 (process 18637) exited normally] (gdb) info b Num Type Disp Enb Address What 1 breakpoint keep y 0x0000000000400f9b in main at gdb_WarpBilinearBack.c:84 breakpoint already hit 1 time 2 breakpoint keep n 0x00007ffffc555044 (gdb) dis 2 (gdb) r Starting program: /home/ubuntu/ipp/gdb/gdb_WarpBilinearBack Error in re-setting breakpoint 2: No symbol "l9_ownpi_WarpBilinearBack" in current context. Error in re-setting breakpoint 2: No symbol "l9_ownpi_WarpBilinearBack" in current context. Error in re-setting breakpoint 2: No symbol "l9_ownpi_WarpBilinearBack" in current context. Error in re-setting breakpoint 2: No symbol "l9_ownpi_WarpBilinearBack" in current context. Error in re-setting breakpoint 2: No symbol "l9_ownpi_WarpBilinearBack" in current context. Breakpoint 1, main (argv=1, args=0x7ffffffec748) at gdb_WarpBilinearBack.c:84 84 IppStatus status = ippStsNoErr; (gdb) en 2 (gdb) c Continuing. Breakpoint 2, 0x00007ffffc555044 in l9_ownpi_WarpBilinearBack () from /opt/intel/oneapi/ipp/2021.1.1/lib/intel64/libippil9.so.10.0 (gdb)

2、静态编译出来的程序使用这种断点目前没问题,但是我也不清楚以后会不会有别的问题。成功示例如下(使用静态编译的程序)

(gdb) b *l9_ownpi_WarpBilinearBack + 4 Breakpoint 1 at 0x4b58a4 (gdb) r Starting program: /home/ubuntu/ipp/gdb/gdb_WarpBilinearBackStatic Breakpoint 1, 0x00000000004b58a4 in l9_ownpi_WarpBilinearBack () (gdb)


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有