zoukankan      html  css  js  c++  java
  • gdb调试

    1. gdb

    GDB是一个由GNU开源组织发布的、UNIX/LINUX操作系统下的、基于命令行的、功能强大的程序调试工具。 对于一名Linux下工作的c++程序员,gdb是必不可少的工具;

    1.1. 启动gdb

    对C/C++程序的调试,需要在编译前就加上-g选项:

    $g++ -g hello.cpp -o hello

    调试可执行文件:

    $gdb <program>

    program也就是你的执行文件,一般在当前目录下。

    调试core文件(core是程序非法执行后core dump后产生的文件):

    $gdb <program> <core dump file>

    $gdb program core.11127

    调试服务程序:

    $gdb <program> <PID>

    $gdb hello 11127

    如果你的程序是一个服务程序,那么你可以指定这个服务程序运行时的进程ID。gdb会自动attach上去,并调试他。program应该在PATH环境变量中搜索得到。

    1.2. gdb交互命令

    启动gdb后,进入到交互模式,通过以下命令完成对程序的调试;注意高频使用的命令一般都会有缩写,熟练使用这些缩写命令能提高调试的效率;

    运行

    • run:简记为 r ,其作用是运行程序,当遇到断点后,程序会在断点处停止运行,等待用户输入下一步的命令。
    • continue (简写c ):继续执行,到下一个断点处(或运行结束)
    • next:(简写 n),单步跟踪程序,当遇到函数调用时,也不进入此函数体;此命令同 step 的主要区别是,step 遇到用户自定义的函数,将步进到函数中去运行,而 next 则直接调用函数,不会进入到函数体内。
    • step (简写s):单步调试如果有函数调用,则进入函数;与命令n不同,n是不进入调用的函数的
    • until:当你厌倦了在一个循环体内单步跟踪时,这个命令可以运行程序直到退出循环体。
    • until+行号: 运行至某行,不仅仅用来跳出循环
    • finish: 运行程序,直到当前函数完成返回,并打印函数返回时的堆栈地址和返回值及参数值等信息。
    • call 函数(参数):调用程序中可见的函数,并传递"参数",如:call gdb_test(55)
    • quit:简记为 q ,退出gdb

    设置断点

    • break n (简写b n):在第n行处设置断点

      (可以带上代码路径和代码名称: b OAGUPDATE.cpp:578)

    • b fn1 if a>b:条件断点设置
    • break func(break缩写为b):在函数func()的入口处设置断点,如:break cb_button
    • delete 断点号n:删除第n个断点
    • disable 断点号n:暂停第n个断点
    • enable 断点号n:开启第n个断点
    • clear 行号n:清除第n行的断点
    • info b (info breakpoints) :显示当前程序的断点设置情况
    • delete breakpoints:清除所有断点:

    查看源代码

    • list :简记为 l ,其作用就是列出程序的源代码,默认每次显示10行。
    • list 行号:将显示当前文件以"行号"为中心的前后10行代码,如:list 12
    • list 函数名:将显示"函数名"所在函数的源代码,如:list main
    • list :不带参数,将接着上一次 list 命令的,输出下边的内容。
    • list -:显示当前行前面的源程序

    打印表达式

    • print 表达式:简记为 p ,其中"表达式"可以是任何当前正在被测试程序的有效表达式,比如当前正在调试C语言的程序,那么"表达式"可以是任何C语言的有效表达式,包括数字,变量甚至是函数调用。
    • print a:将显示整数 a 的值print可以指定显示的格式,这里用'/x'表示16进制的格式。 
      可以支持的变量显示格式有: 
      x  按十六进制格式显示变量。 
      d  按十进制格式显示变量。 
      u  按十六进制格式显示无符号整型。 
      o  按八进制格式显示变量。 
      t  按二进制格式显示变量。 
      a  按十六进制格式显示变量。 
      c  按字符格式显示变量。 
      f  按浮点数格式显示变量。 
    • print ++a:将把 a 中的值加1,并显示出来
    • print name:将显示字符串 name 的值
    • print gdb_test(22):将以整数22作为参数调用 gdb_test() 函数
    • print gdb_test(a):将以变量 a 作为参数调用 gdb_test() 函数
    • display 表达式:在单步运行时将非常有用,使用display命令设置一个表达式后,它将在每次单步进行指令后,紧接着输出被设置的表达式及值。如: display a
    • watch 表达式:设置一个监视点,一旦被监视的"表达式"的值改变,gdb将强行终止正在被调试的程序。如: watch a
    • whatis :查询变量或函数类型
    • info function: 查询函数
    • 扩展info locals: 显示当前堆栈页的所有变量

    查询运行信息

    • where/bt :当前运行的堆栈列表;
    • bt backtrace 显示当前调用堆栈
    • up/down 改变堆栈显示的深度
    • set args 参数:指定运行时的参数
    • show args:查看设置好的参数
    • info program: 来查看程序的是否在运行,进程号,被暂停的原因。

    分割窗口

    • layout:用于分割窗口,可以一边查看代码,一边测试:
    • layout src:显示源代码窗口
    • layout asm:显示反汇编窗口
    • layout regs:显示源代码/反汇编和CPU寄存器窗口
    • layout split:显示源代码和反汇编窗口
    • Ctrl + L:刷新窗口

    注解

    交互模式下直接回车的作用是重复上一指令,对于单步调试非常方便;

    修改变量

    修改被调试程序运行时的变量值,在GDB中很容易实现,使用GDBprint命令即可完成。如:(gdb) print x=4x=4这个表达式是C/C++的语法,意为把变量x的值修改为4,如果你当前调试的语言是Pascal,那么你可以使用Pascal的语法:x:=4

    回退调试

    • reverse-continue:反向运行程序知道遇到一个能使程序中断的事件(比如断点,观察点,异常)。
    • reverse-step:反向运行程序到上一次被执行的源代码行。
    • reverse-stepi:反向运行程序到上一条机器指令
    • reverse-next:反向运行到上一次被执行的源代码行,但是不进入函数。
    • reverse-nexti:反向运行到上一条机器指令,除非这条指令用来返回一个函数调用、整个函数将会被反向执行。
    • reverse-finish:反向运行程序回到调用当前函数的地方。
    • set exec-direction [forward | reverse]:设置程序运行方向,可以用平常的命令step和continue等来执行反向的调试命令。

    跟踪子进程

    使用gdb调试的时候,gdb只能跟踪一个进程。

    可以在fork函数调用之前,通过指令设置gdb调试工具跟踪父进程或子进程。

    默认情况下gdb是跟踪父进程的。

    • set follow-fork-mode child 命令设置gdb在fork之后跟踪子进程。
    • set follow-fork-mode parent设置跟踪父进程。

    注意:一定要在fork函数调用之前设置。

    打印内存地址信息

    x/ <n/f/u> <addr> ,n、f、u是可选的参数。addr表示待查看的内存地址。n是一个正整数,表示显示内存的长度,也就是说从当前地址向后显示几个地址(units)的内容。f表示显示的格式(format)。默认初始使用十六进制格式。u表示(the unit size)从当前地址往后请求的位宽大小。如果不指定的话,GDB默认是4个bytes。u参数可以用下面的字符串来代替,b表示单字节,h表示双字节,w表示四字节,g表示八字节。当我们指定了位宽长度后,GDB会从指内存的内存地址开始,读写指定位宽大小,并把其当作一个值取出来。

    GDB打印数据显示格式:

    x(hexadecimal)按照十六进制格式显示变量

    d(signed decimal)按照十进制格式显示变量

    u(unsigned decimal)按十进制格式显示无符号整型

    o(octal)按八进制格式显示变量

    t(binary)按二进制格式显示变量

    a(address)按十六进制格式显示地址,并显示距离前继符号的偏移量(offset)。常用于定位未知地址(变量)。

    c(character)按字符格式显示变量

    f(floating)按浮点数格式显示变量

    s(string)显示该地址的字符串

    查看函数调用帧

    • bt <n>打印当前的函数调用栈的所有信息。n如果是整数,表示只打印栈顶上n层的栈信息。n表示一个负整数,表示只打印栈底下n层的栈信息。
    • 查看当前栈层的信息,可以使用GDB命令

      frame或f

    • 会打印出这些信息:栈的层编号,当前的函数名,函数参数值,函数所在文件及行号,函数执行到的语句。
    • info args:打印出当前函数的参数名及其值
    • info locals:打印出当前函数所有局部变量及其值

    跳出循环

    可以通过finish来结束当前的函数栈帧,或者使用until NUM, NUM为大于循环的行号

     

    打印寄存器的信息

    • info registers 显示所有的寄存器。可以简写为i r
    • info registers eax只显示eas。
    • info all-registers 查看更多寄存器

     

    多线程调试

    • info threads:显示当前可调试的所有线程。每个线程会有gdb为其分配的ID,后面的操作会用到这个ID。前面带'*'号的是当前正在调试的线程。
    • thread ID:切换当前调试的线程为指定ID的线程。
    • break thread test.c:123 thread all 在所有线程中相应的行上设置断点
    • thread apply all command 让所有被调试线程执行gdb命令command
    • thread apply ID1 ID2 command 让一个或多个线程滞后性gdb命令command
    • set scheduler-locking [off|on|step], 值得注意的是,在使用step或者continue命令调试当前被调试线程的时候,其他线程也是同时执行的,怎么只让被调试程序执行呢?通过这个命令就可以实现这个需求。

      off: 不锁定任何线程,也就是所有线程都执行,这是默认值

      on:只有当前被调试程序会执行

      step:在单步的时候,除了nexgt过一个函数的情况(熟悉情况的人可能会知道,这其实是一个设置断点然后continue的行为)以外,只有当前线程会执

    • show scheduler-locking:查看当前锁定线程的模式。

       

    QEMU调试(查看CR3寄存器)

    f you are using qemu, you can get all registers states (even control ones) by issuing theses two commands in the shell you start qemu:

    • ctrl-a c (to switch to qemu command interface),再按一下就返回了
    • info registers (to get registers states)

    QEMU

    QEMU includes a built-in monitor that can inspect and modify the machine state in useful ways. To enter the monitor, press Ctrl-a c in the terminal running QEMU. Press Ctrl-a c again to switch back to the serial console.

    For a complete reference to the monitor commands, see the QEMU manual. Here are some particularly useful commands:

    xp/Nx paddr

    Display a hex dump of N words starting at physical address paddr. If N is omitted, it defaults to 1. This is the physical memory analogue of GDB's x command.

    info registers

    Display a full dump of the machine's internal register state. In particular, this includes the machine's hidden segment state for the segment selectors and the local, global, and interrupt descriptor tables, plus the task register. This hidden state is the information the virtual CPU read from the GDT/LDT when the segment selector was loaded. Here's the CS when running in the JOS kernel in lab 1 and the meaning of each field:

    CS =0008 10000000 ffffffff 10cf9a00 DPL=0 CS32 [-R-]
    

    CS =0008

    The visible part of the code selector. We're using segment 0x8. This also tells us we're referring to the global descriptor table (0x8&4=0), and our CPL (current privilege level) is 0x8&3=0.

    10000000

    The base of this segment. Linear address = logical address + 0x10000000.

    ffffffff

    The limit of this segment. Linear addresses above 0xffffffff will result in segment violation exceptions.

    10cf9a00

    The raw flags of this segment, which QEMU helpfully decodes for us in the next few fields.

    DPL=0

    The privilege level of this segment. Only code running with privilege level 0 can load this segment.

    CS32

    This is a 32-bit code segment. Other values include DS for data segments (not to be confused with the DS register), and LDT for local descriptor tables.

    [-R-]

    This segment is read-only.

    info mem

    (Lab 2+) Display mapped virtual memory and permissions. For example,

    ef7c0000-ef800000 00040000 urw
    
    efbf8000-efc00000 00008000 -rw
    

    tells us that the 0x00040000 bytes of memory from 0xef7c0000 to 0xef800000 are mapped read/write and user-accessible, while the memory from 0xefbf8000 to 0xefc00000 is mapped read/write, but only kernel-accessible.

    info pg

    (Lab 2+) Display the current page table structure. The output is similar to info mem, but distinguishes page directory entries and page table entries and gives the permissions for each separately. Repeated PTE's and entire page tables are folded up into a single line. For example,

    VPN range     Entry         Flags        Physical page
    
    [00000-003ff]  PDE[000]     -------UWP
    
      [00200-00233]  PTE[200-233] -------U-P 00380 0037e 0037d 0037c 0037b 0037a ..
    
    [00800-00bff]  PDE[002]     ----A--UWP
    
      [00800-00801]  PTE[000-001] ----A--U-P 0034b 00349
    
      [00802-00802]  PTE[002]     -------U-P 00348
    

    This shows two page directory entries, spanning virtual addresses 0x00000000 to 0x003fffff and 0x00800000 to 0x00bfffff, respectively. Both PDE's are present, writable, and user and the second PDE is also accessed. The second of these page tables maps three pages, spanning virtual addresses 0x00800000 through 0x00802fff, of which the first two are present, user, and accessed and the third is only present and user. The first of these PTE's maps physical page 0x34b.

    QEMU also takes some useful command line arguments, which can be passed into the JOS makefile using the QEMUEXTRA variable.

    make QEMUEXTRA='-d int' ...

    Log all interrupts, along with a full register dump, to qemu.log. You can ignore the first two log entries, "SMM: enter" and "SMM: after RMS", as these are generated before entering the boot loader. After this, log entries look like

         4: v=30 e=0000 i=1 cpl=3 IP=001b:00800e2e pc=00800e2e SP=0023:eebfdf28 EAX=00000005
    
    EAX=00000005 EBX=00001002 ECX=00200000 EDX=00000000
    
    ESI=00000805 EDI=00200000 EBP=eebfdf60 ESP=eebfdf28
    
    ...
    

    The first line describes the interrupt. The 4: is just a log record counter. v gives the vector number in hex. e gives the error code. i=1 indicates that this was produced by an int instruction (versus a hardware interrupt). The rest of the line should be self-explanatory. See info registers for a description of the register dump that follows.

    Note: If you're running a pre-0.15 version of QEMU, the log will be written to /tmp instead of the current directory.

    1.3. 更强大的工具

    cgdb

    cgdb可以看作gdb的界面增强版,用来替代gdb的 gdb -tui。cgdb主要功能是在调试时进行代码的同步显示,这无疑增加了调试的方便性,提高了调试效率。界面类似vi,符合unix/linux下开发人员习惯;如果熟悉gdb和vi,几乎可以立即使用cgdb。

  • 相关阅读:
    第四章 变量的三大特征,垃圾回收机制,可变类型和不可变类型的简单表述
    第三章 有关变量的理解
    第二章 编程语言的分类及优缺点的分析
    IIS拓展访问的文件格式
    JS之HTTP请求
    HTTP请求
    HtmlAgilityPack解析html
    支付宝支付功能开发
    Web.config配置ActiveReports
    数据库连接字符串
  • 原文地址:https://www.cnblogs.com/kexinxin/p/9939131.html
Copyright © 2011-2022 走看看