Linux下编辑、编译、调试命令总结——gcc和gdb描述
GCC
  gcc是linux系统集成的编译器。在linux环境下编辑程序,⾸先需要克服的便是没有集成开发环境的⼀键式操作所带来的⿇烦。这其中涉及命令⾏操作、编译选项的设定、⽂件依赖关系的书写(makefile)等问题。这⾥主要介绍的是关于gcc的常⽤命令⾏参数及其相应的作⽤。(若编译C++⽂件,则只需将下列命令的 gcc 换为 g++,源⽂件的后缀应为 .C/.cpp/.c++/.cc等)
基本格式:gcc [options]   //若不加⼊参数,则按默认参数依次执⾏编译、汇编和链接操作,⽣成的可执⾏⽂件名为 a.out
常⽤参数:-E                //只执⾏预处理操作,直接输出到标准输出(可通过 -o 命令指定输出到⽂件中 ),可配合 -v 命令使⽤,查看头⽂件的搜索⽬录,即使⽤ -E -v 编译选项
     -S                //只执⾏到编译操作完成,不进⾏汇编操作,⽣成的是汇编⽂件(.s 或 .asm),内容为汇编语⾔
     -c                //执⾏编译和汇编,但不进⾏链接,即只⽣成可重定位⽬标⽂件(.o),为⼆进制⽂件,不⽣成完整的可执⾏⽂件
     -o filename        //将操作后的内容输出到filename指定的⽂件中
     -static            //对于⽀持动态链接的系统,使⽤静态链接⽽不是动态链接进⾏链接操作
     -g         //编译时⽣成debug有关的程序信息(供gdb使⽤)
     --save-temps      //⽣成编译过程的中间结果⽂件(包括预处理⽂件(x.ii)、汇编代码(x.s)、⽬标⽂件(x.o)和最终的可执⾏⽂件)
     -IPATH            //在PATH指定的⽬录下寻相关的include⽂件,参数中间不加空格
     -lxx              //其中xx为指定函数库,对于Linux环境下的函数库,静态库后缀为.a,动态库后缀为.so,⼀般库名为libxx.a或libxx.so,如加⼊libm.so库,则使⽤参数-lm(去除lib和后缀.a\so)
     -LPATH            //在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
     -O1、-O2       //规定编译器的优化等级,优化级数越⾼执⾏效率⼀般越好,但是优化会改变原有程序结构,使得其汇编不易理解
     //⼀些进⾏缓冲区溢出实验时可能需要的选项
     -fstack-protector\-fno-stack-protector  //是否开启堆栈保护,这⾥的保护是在返回地址之前加⼊⼀个验证值来确保返回地址不被破坏
     -z execstack                  //启⽤可执⾏栈,默认是禁⽤的
     //(echo 0 >/proc/sys/kernel/randomize_va_space 关闭地址随机化,这是⼀个单独的命令,操作需要root权限)
     //编译动态链接库的选项
     -shared -fPIC          //笔者⽤到的编译动态链接库即 .so 库的选项,其中 -shared 表⽰⽣成⼀个共享⽬标⽂件(shared object),-fPIC 表⽰忽略 PIC(position-independent code)
                       //对应的还有个选项 -fpic,其会⽣成 PIC,详细内容可参见 man gcc
  上述编译使⽤的参数⼀般在直接使⽤ gcc / g++ 时作为命令⾏参数指定。如果想要在某些调⽤了 gcc / g++ 的编译过程中加⼊所需的编译参数,即⽆法直接通过命令⾏参数的⽅式指定编译参数时,可以通过全局变量的⽅式( Linux 环境下 )指定所需的编译参数。具体⽽⾔,使⽤ CXXFLAGS 指定 g++ 编译参数,使⽤ CFLAGS 指定 gcc 编译参数。
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)⽣成两个可重定位⽬标⽂件
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⽂件夹下寻⽬标库
GDB
  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官⽅⽂档。
  为了更好的使⽤gdb的调试功能,在编译程序时需加⼊ -g 选项,由编译器⽣成某些⽤于调试的信息。
  GDB常⽤命令(此部分译⾃,细节有改动)
  开始/结束gdb
  使⽤ gdb filename 启动gdb,其中 filename 应为可执⾏⽂件。
gdb a.out    //使⽤gdb对a.out进⾏调试 
linux下gcc编译的四个步骤  对于需要使⽤启动参数的程序,可以使⽤ gdb --args 加⼊运⾏参数。
gdb --args a.out arg1 arg2
  gdb以命令⾏环境运⾏,进⼊gdb后,程序会等待⽤户的指令并执⾏,直⾄⽤户选择退出。使⽤ q 或 Ctrl + d 退出。
  运⾏(r)指令
  使⽤命令 r 运⾏(run)程序,另外也可以加⼊程序运⾏所需要的参数,若原命令⾏模式下的运⾏指令为 ./a.out > ,则在gdb运⾏时应为 r > 。且如果在同⼀调试过程中需要多次运⾏程序(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 10if a<10   //可以在断点中加⼊中断执⾏的条件,表⽰当a < 10 时才会中断程序执⾏
  在断点处检查完毕后,可以使⽤ c 指定继续指令的执⾏。使⽤指令 disable/enable 断点号可以启⽤/停⽤某断点。使⽤指令 d 可删除所有的断点,d 1 删除breakpoint 1.
disable/enable    n        //停⽤/启⽤编号为n的断点
d                //删除所有断点
d    n          //删除标号为n的断点
  ⽤户可以通过 command 命令设置遇到断点后执⾏的命令,通过 command n 的⽅式为断点 n 设置遇到断点后执⾏的⼀系列操作,使⽤ end 结束。
  观测点(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会⾃动更新程序状态,这样可以节约时间。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。