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

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

一、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

扫描二维码关注公众号,回复: 16214998 查看本文章
#include <stdio.h>

#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:linenumfilename为文件名,为空表示当前文件,linenum为行号。
  • 根据指令地址设置断点。b *addr,其中addr为指令地址(如果动态链接的动态库还没有加载的话,可能会报错)。

示例

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

其它关于断点的命令

  • info b, 简写为i b 查看断点信息,打印的信息中包括断点的编号,断点类型,是否一直生效,是否启用,断点地址,断点位置等。
  • disable b,简写为dis b num,将编号为num的断点暂时禁用
  • enable b,简写为ena b num,将编号为num的断点启用
  • cleardelete,删除所有断点
    除此之外,还有条件断点监控断点等等,此处略。

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+xa(操作就是先按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

方法和前面相同,此处设置了三个断点mainfoo*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 <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 "x86_64-pc-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

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除了之前提到的srcasm窗口外,还有一个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 $rspx /4x $rsp一样,x /xbwg $rspx /xb $rsp一样
  • 如果你没有设置格式,默认和上次的格式一致

4.2.6 反汇编某个函数

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

(gdb) disassemble main
Dump of assembler code for function main:
   0x000000000000064a <+0>:     push   %rbp
   0x000000000000064b <+1>:     mov    %rsp,%rbp
   0x000000000000064e <+4>:     sub    $0x10,%rsp
   0x0000000000000652 <+8>:     movl   $0x1,-0x10(%rbp)
   0x0000000000000659 <+15>:    movl   $0x2,-0xc(%rbp)
   0x0000000000000660 <+22>:    movl   $0x3,-0x8(%rbp)
   0x0000000000000667 <+29>:    mov    -0x8(%rbp),%edx
   0x000000000000066a <+32>:    mov    -0xc(%rbp),%ecx
   0x000000000000066d <+35>:    mov    -0x10(%rbp),%eax
   0x0000000000000670 <+38>:    mov    %ecx,%esi
   0x0000000000000672 <+40>:    mov    %eax,%edi
   0x0000000000000674 <+42>:    call   0x6a8 <foo>
   0x0000000000000679 <+47>:    mov    %eax,-0x4(%rbp)
   0x000000000000067c <+50>:    mov    -0x8(%rbp),%edx
   0x000000000000067f <+53>:    mov    -0xc(%rbp),%ecx
   0x0000000000000682 <+56>:    mov    -0x10(%rbp),%eax
   0x0000000000000685 <+59>:    mov    %ecx,%esi
   0x0000000000000687 <+61>:    mov    %eax,%edi
   0x0000000000000689 <+63>:    call   0x6a8 <foo>
   0x000000000000068e <+68>:    mov    %eax,%esi
   0x0000000000000690 <+70>:    lea    0xbd(%rip),%rdi        # 0x754
   0x0000000000000697 <+77>:    mov    $0x0,%eax
   0x000000000000069c <+82>:    call   0x520 <printf@plt>
   0x00000000000006a1 <+87>:    mov    $0x0,%eax
   0x00000000000006a6 <+92>:    leave  
   0x00000000000006a7 <+93>:    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 <l9_ownpi_WarpBilinearBack+4>
(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) 

猜你喜欢

转载自blog.csdn.net/qq_29809823/article/details/118900119