Linux下编辑、编译、调试命令总结——gcc和gdb描述

GCChtml

  gcc是linux系统集成的编译器。在linux环境下编辑程序,首先须要克服的即是没有集成开发环境的一键式操做所带来的麻烦。这其中涉及命令行操做、编译选项的设定、文件依赖关系的书写(makefile)等问题。这里主要介绍的是关于gcc的经常使用命令行参数及其相应的做用。(若编译C++文件,则只需将下列命令的 gcc 换为 g++,源文件的后缀应为 .C/.cpp/.c++/.cc等)linux

基本格式:gcc [options] file1 file2...  //若不加入参数,则按默认参数依次执行编译、汇编和连接操做,生成的可执行文件名为 a.out
经常使用参数:-E                 //只执行预处理操做
     -S                 //只执行到编译操做完成,不进行汇编操做,生成的是汇编文件(.s 或 .asm),内容为汇编语言
     -c                 //执行编译和汇编,但不进行连接,即只生成可重定位目标文件(.o),为二进制文件,不生成完整的可执行文件
     -o filename        //将操做后的内容输出到filename指定的文件中
     -static            //对于支持动态连接的系统,使用静态连接而不是动态连接进行连接操做
     -g          //编译时生成debug有关的程序信息(供gdb使用)
     --save-temps       //生成编译过程的中间结果文件(包括预处理文件(x.ii)、汇编代码(x.s)、目标文件(x.o)和最终的可执行文件)

     -I PATH            //在PATH指定的目录下寻找相关的include文件,参数中间不加空格
     -lxx               //其中xx为指定函数库,对于Linux环境下的函数库,静态库后缀为.a,动态库后缀为.so,通常库名为libxx.a或libxx.so,如加入libm.so库,则使用参数-lm(去除lib和后缀.a\so)
     -L PATH            //在PATH指定的目录下寻找相关的库文件,即-lxx指定待连接的库,-L指定寻找该库的路径。不指定时搜索默认的库函数路径
     -std=xx //指定编译使用的语言标准,如 -std=c++11 使用 c++11 标准      -x language //指定待编译文件的语言,而不是由编译器根据文件后缀自行判断。即默认状况下gcc根据文件后缀判断使用的编程语言。例如使用文件名hello做为源文件名是不合适的,应使用hello.c            -Wall //输出一些简单的错误以及一些可能存在问题的警告      -Wextra //输出-Wall不包含的警告等      -Werror //将警告视为错误输出   
     -Wl,option          //经过该选项将参数 option 做为后续连接器 ld 使用的参数
     -Wl,rpath=/path/to/lib  //为连接器指定一个非默认的运行时库的搜索路径,运行采用了该选项编译的程序时,连接器会在-rpath 指定的目录中搜索所需的 so 库文件,以将其载入内存中
         -D name=definition //加入宏定义,若不指定def,则默认为1      -O一、-O2       //规定编译器的优化等级,优化级数越高执行效率通常越好,可是优化会改变原有程序结构,使得其汇编不易理解      //一些进行缓冲区溢出实验时可能须要的选项      -fstack-protector\-fno-stack-protector  //是否开启堆栈保护,这里的保护是在返回地址以前加入一个验证值来确保返回地址不被破坏      -z execstack                  //启用可执行栈,默认是禁用的      //(echo 0 >/proc/sys/kernel/randomize_va_space 关闭地址随机化,这是一个单独的命令,操做须要root权限)

  上述编译使用的参数通常在直接使用 gcc / g++ 时做为命令行参数指定。若是想要在某些调用了 gcc / g++ 的编译过程当中加入所需的编译参数,即没法直接经过命令行参数的方式指定编译参数时,能够经过全局变量的方式( Linux 环境下 )指定所需的编译参数。具体而言,使用 CXXFLAGS 指定 g++ 编译参数,使用 CFLAGS 指定 gcc 编译参数。c++

    export CXXFLAGS="-std=c++11"    //经过全局变量指定额外的编译参数
    export CFLAGS="-std=c99"

 

  举例说明程序员

  (1)将源文件编辑为可执行文件编程

    gcc hello.c  //默认生成名为a.out的可执行文件,这样若在同一文件夹下编译另外一个程序,则会a.out会被后来文件覆盖

  (2)编译文件,并输出到hello.s数组

    gcc -S -o hello.s  hello.c

  (3)生成两个可重定位目标文件dom

    gcc -c hello.c world.c    //生成hello.o与world.o,不进行连接操做,即仅进行预处理、编译、汇编,而不进行连接

  (4)对库文件、目标文件进行链接操做编程语言

    gcc -static hello.o world.o -lm -L /usr/lib     //以静态连接的方式,将hello.o、world.o以及libm.a库中的相关目标文件连接,在/usr/lib文件夹下寻找目标库

  

 

GDBide

  gdb是Linux下一款功能强大的调试工具,它既能在反汇编过程当中充当一件称手的工具,也能在程序debug过程当中为为程序员提供帮助,其惟一美中不足的是在Linux环境下没有图像界面(固然没有功能的封装也是其功能强大的缘由之一,并且如今的ddd也提供了GUI)。这里主要记录笔者从一些学习指导中学习的关于gdb命令和用法的总结。函数

  

  为何要使用GDB?

  1.在Windows环境下,许多IDE以图形界面提供相似gdb的功能,通常也较为好用。可是一方面,gdb提供给使用者更大的自由,另外一方面gdb也是目前几乎全部Linux发行版本的自带软件,简单易得;

  2.调试程序时尽可能减小对诸如printf等输出函数的依赖。许多做者给出的解释是从新修改代码和编译是一件麻烦的差事。这一点笔者起初也并不理解,以为上述操做确实不算麻烦(...)。后来发现,对于一个单一文件,代码不超过100行的文件,上述操做确实在可接受范围。但对于文件众多,工程量巨大的项目,修改代码、从新编译文件是一件极其耗时且麻烦的操做。若是在Windows环境下进行大工程的debug所须要的修改、重编译所带来的频繁鼠标或快捷键操做还不能使你回心转意的话,相信我,在Linux的命令行模式下进行相同的操做会让你有所改变的;

  3.习惯是逐渐养成的,不论好坏都是。或许只有逐渐在看起来不那么方便的GDB中锻炼起来,你才能在不管什么编译环境中debug的驾轻就熟,可能那时,你会嫌弃图形界面提供的工具不够给力的;

  

  调试策略

  不管进行何种调试工做,大致的调试策略都相似:使用二分法的方式对错误地点进行定位;使用断点(breakpoint),使程序运行至断点处时中止以便观察程序状态;使用单步执行,使程序运行一条指令后中止,从而观察数据的变化状况和程序控制流;对一个变量预设特定的值,跟踪其在程序运行中的变化规律等等。根据二八定律,使用20%的GDB指令,通常就能够解决80%的程序bug。这里介绍的是可以常规使用GDB的命令,更多高级或特殊指令,能够参考GDB官方文档Degugging with GDB

  为了更好的使用gdb的调试功能,在编译程序时需加入 -g 选项,由编译器生成某些用于调试的信息。

  

  GDB经常使用命令(此部分译自 Guide to Faster,Less Frustrating Debugging,细节有改动)

  开始/结束gdb

  使用 gdb filename 启动gdb,其中 filename 应为可执行文件。

    gdb a.out    //使用gdb对a.out进行调试  

  gdb以命令行环境运行,进入gdb后,程序会等待用户的指令并执行,直至用户选择退出。使用 q 或 Ctrl + d 退出。

  

  运行(r)指令

  使用命令 r 运行(run)程序,另外也能够加入程序运行所须要的参数,若原命令行模式下的运行指令为 ./a.out > test.txt ,则在gdb运行时应为 r > test.txt。且若是在同一调试过程当中须要屡次运行程序(run),后续再执行时即可直接使用 r 指令,系统会默认使用以前的参数。

  r              //运行程序
  r [options] arguments  //带参数运行程序,参数与命令行环境下一致,使用 r 替换源程序文件便可

 

  List( l )指令

  可使用指令 l 来列出源文件中的部分源代码。(须要编译时加入 -g 选项生成对应的编译符号)

     l 10   //输出源程序10行及先后几行的源码,能够方便进行调试。若要继续查看,按回车键会继续向下显示。

  对于多个文件的而言,能够经过 l source_file_name.c:col (l 源文件名:行号)来指定所需查看的源代码

     l hello.c:10  //输出hello.c在10行先后的代码

  也能够以函数为总体进行输出,命令格式为 l function_name

     l main     //输出main函数的源代码

  

  断点(b)和继续执行(c)指令

  指令 b 能够在须要地方放置断点,使得程序在指令的位置中止运行,指令格式为 b 断点位置。其中,断点位置能够是行号,也能够是函数名(指定方式与 l 指令相似),也能够是地址。

  b 10        //在源代码10行处放置断点
  b main       //在main函数开始处放置断点
  b *0x80480000   //在存放在0x80480000处的指令处放置断点,直接使用地址时须要使用 *地址 的格式 
  b 10 if a<10    //能够在断点中加入中断执行的条件,表示当a < 10 时才会中断程序执行

  在断点处检查完毕后,可使用 c 指定继续指令的执行。使用指令 disable/enable 断点号 能够启用/停用某断点。使用指令 d 可删除全部的断点,d 1 删除breakpoint 1.

    disable/enable    n        //停用/启用编号为n的断点
    d                //删除全部断点
    d    n           //删除标号为n的断点

 

  观测点(watch)指令

  指令watch能够为某一表达式设置观察点,当程序执行过程当中,当表达式的值发生改变时,则 gdb 会中断程序执行,并显示表达式的变化状况。

  watch a      //当变量 a 的值发生变化时,中断程序执行
  watch -l a    // watch指令指定了 -l 参数时,会将指令所接的表达式的计算结果做为地址,观察该地址处的值的变化状况
  rwatch a      // 当 a 的值被读取时,中断表达式的执行

  

  显示(disp)和打印(p)指令

  disp指令(display)能够在每次程序暂定时显示指定变量的值,指令格式为 disp 变量名。若输入的变量为数组名,则每次显示数组的全部元素,若为结构体,则输出结构体的全部成员的值。

  disp temp     //在每次程序暂停时输出指定的变量的值(确保程序在指定变量的做用域内执行,如某个在特定函数中的局部变量在程序进入该函数执行以前是没法被显示的)
  undisplay     //取消全部disp指定的自动显示变量

  p指定(print)一样将变量的值打印出来,用法与diap相似,但结果只显示一次。

  除变量外,p指令还能够输出给定寄存器、给定地址处的值。同时,能够经过一些参数对打印格式进行规定,如 /x 表示以16进制格式打印值,/t表示以二进制格式打印值。

  p $eax       //打印寄存器%eax存储的值,注意使用$标志寄存器名称
  p /x ($ebp + 8)       //以十六进制的格式打印%ebp + 8 的值
  p /t 100         //以二进制格式输出100的值
  p *0x08048000   //输出位于0x08048000处的数据(此处实际存放的是机器代码),注意地址需使用 * 标志,不然会被默认为常数
  p *(int *)0xxxxxxxx  //将指定地址处数据按照整数格式输出,这里通常须要指出指针类型方便gdb解释数据

  

  其余显示类info命令

  info reg       //输出全部寄存器的当前值
  info frame      //输出栈帧的使用状况
  info  b n         //其中 n 为指定的断点号,显示指定断点的状态信息,不加参数 n 时,会显示全部的断点的信息

  

  内存检查(examine)指令

  x 指令用于检查内存中某一区域的值,指令格式为 :x fmt address 。其中address为内存地址的表达式,fmt由 /重复次数+格式化字符+尺寸字符 组成。格式化字符有o(octal,八进制),x(hex,十六进制), d(decimal,十进制),u(unsigned decimal,无符号十进制),t(binary,二进制),f(float,浮点),a(address,地址),i(instruction,指令),c(char,字符),s(string,字符串).尺寸字符有 b(byte),h(halfword), w(word), g(giant, 8 bytes)

    x /4xb *0xxxxxx  //将指定地址区域连续的四个字节以十六进制的格式输出,通常内存地址均使用 * 标识

 

  格式化输出(printf)指令

  该指令的使用方法与C语言中的格式化输出函数类似

    printf" %d , %d \n",X,Y  //对于两个变量整形X,Y进行输出

  使用指令whatis能够方便的得知所需对象的类型,如 whatis temp 会显示出temp的类型定义,在调试时有用。

  

  执行(s与n)指令

  s 与 n 指令都是表示执行下一条指令指令的意思。可是,当遇到函数调用时,s 指令会进入函数调用内部进行执行,即下一步为被调函数的第一指令,而 n 指令不进入函数调用内部,会将整个函数的执行过程看成一步执行。

 

  回溯(bt)指令

  回溯指令(backtrace)能够查看程序内存访问越界等错误信息,显示程序出错的位置,从而帮助定位程序错误。

 

  设置(set)指令

  设置指令 set 能够将指定的变量的值修改成调试所须要的值。如对于一个int型的变量X,可使用 set X = 12 将变量的值进行设置。

 

  使用宏定义

  可使用宏定义对一些经常使用指令进行定义。指令格式 :define 宏名,并根据提示输入宏定义,以end做为结尾标志。

  

  另外,在使用gdb进行调试过程当中,可能免不了须要从新编译程序,这时没必要将gdb退出,只需待程序从新编译后使用 r 指令从新运行程序,gdb会自动更新程序状态,这样能够节约时间。

相关文章
相关标签/搜索