(1),首先预处理器对源代码进行处理,在gcc中,预处理的指令为 gcc -E test.c -o test.i,预处理后的文件后缀名为.i
(2),编译器对预处理后的源代码进行处理,生成汇编代码,指令为gcc -S test.i -o test.S ,汇编文件的后缀为.S
(3), 汇编器对汇编代码进行处理生成目标文件,执行指令为, gcc -c test.S -o test .o,文件后缀为.o,编译汇编指定的源文件但是不进行链接。
(4),链接器连接库生成可执行文件.out,指令为gcc test.o -o test.out;
-g:在编译的时候生成调试信息
-D:在程序编译的时候指定一个宏
-w:不生成任何警告信息
-l:在程序编译的时候指定使用的库
gcc
gcc编译可执行程序四步骤
预处理、编译、汇编、链接
gcc常用参数
gcc -v / --v / --version 查看gcc版本号
-I 目录,指定头文件目录,注意目录名和I之间没有空格,当头文件与源文件不在同一个目录时使用
-c 只编译生成.o文件不进行链接
-g 包含调试信息,以便于后续使用gdb进行调试
-On ,n=0~3,n越大优化的越多
-Wall 显示所有警告信息
-D<DEF> 编译时定义宏,注意-D与<DEF>之间没有空格
gdb调试工具
常用指令
1 基本指令
set args xxx 传递参数xxxset var 变量名=val 修改变量的值file xxx 载入目标程序xxxattach pid 通过PID链接程序start/s 启动运行 程序会停在main()函数的开始处continue/c 继续运行run/r 运行程序quit/q 退出gdb
2 调试相关
指令 说明next 进行单步跟踪,后面带数字,表示命令运行的次数step 跟踪进入函数体内,后面带数字,代表需要运行的次数list 显示跟踪点附件的代码until 不带参数,和next效果一样,带参数数字,代表直接跳到我们希望的停止点jump 强制跳转执行finish 运行完直接返回return 在某函数内直接返回,带参数,表示要返回的数值info registers/reg 查看寄存器的值info all-registers/all-reg 查看全部寄存器的值info locals 查看所有局部变量值info args 查看函数参数的值info frame 查看当前帧的信息info stack 查看栈信息info variables 查看程序中的变量符号info functions 查看程序中的函数符号info address symbol 查看symbol的地址info symbal addr 查看存储在地址addr中的符号名称where 查看栈信息bt 查看程序的调用栈bt n 查看最里n帧栈的信息bt -n 查看最外n帧栈的信息bt full 查看栈和局部变量的信息frame n 切换栈帧到编号为n中去print 查看变量的值或者地址display 查看变量的值或者地址whatis xxx 查看程序符号表ptype xxx 查看程序符号表
3 断点相关
指令 说明break 设置软件断点tbreak 设置一次有效软件断点rbreak 后跟参数,和参数匹配的所有函数上设置断点info breakpoints 查看断点delete 删除断点enable 使能断点disable 使断点无效hbreak 设置硬断点thbreak 设置一次有效硬断点watch 设置数据断点catch 设置事件断点
断点分类:
断点根据类型的不同,分为软断点、硬断点、内存断点、事件断点
软断点:当处理器在运行过程中,如果碰到了非法或者无效的指令,就会出现异常中断,软件程序断点就是利用这个特性来实现的。当设置一个软件程序断点时,调试工具就在我们所想设置的内存位置上放置了一条非法的指令,同时将被替换的指令保留起来。当程序运行到了被非法指令替换的地方时候,处理器所产生的异常中断一方面在中断服务程序中恢复被替换的指令,另一方面将控制权交给调试工具。
设置软断点的方式:
通过函数名来设置断点; 通过行号的方式来设置断点; 通过文件名和行号的方式来设置断点。
硬断点
在嵌入式系统中,如果想调试的程序不是位于内存中,而是位于像闪存这样的存储器中,此时就无法使用软件程序断点了,因为闪存中的内容并不像内存那样方便更改。
此时只能使用硬件程序断点来调试程序。硬件程序断点的实现原理与软件程序断点完全不同,断点是通过配置处理器的断点寄存器的方式来实现的。
当处理器运行到断点寄存器所指示位置的指令时就会产生中断,调试工具通过该中断使我们获得干预的机会。处理器所能设置的硬件程序断点数量是有限的,可能最多也就4个。
内存(数据)断点
当调试程序时,如果发现所定义的一个数据结构中的某一变量总是被意外地改变,查出这类问题的根源并不容易。如果处理器能提供一种功能----当某一变量的值被改动时能自动停下来就好了,这样就可以通过调用栈找到问题的根源。这就是引入数据断点的目的。数据断点与硬件断点很相似,需要在处理器的寄存器中设置所监视数据变量的内存地址。当被监视的内存单元被修改时处理器将产生中断,调试工具利用这一中断让我们获得检查程序的机会。与硬件程序断点一样,数据断点的个数也很有限。
事件断点
4 线程相关
指令 说明thread thread-id 切换thread-id为当前线程info threads [id] 查询指定id线程或全部线程信息thread name [name] 为当前线程设置一个名称thread find [regexp] 查找与regexp匹配的线程信息break location thread thread-id [ if cond ] 在location指定的位置处建立断点,断点的作用范围为指定ID的线程内thread apply [thread-id-list / all] args 将args命令(next/continue/silent/quiet等)作用于指定的线程或者全部线程set scheduler-locking mode 线程锁定模式
-g: 使用该参数编译可执行文件,得到调试表。gcc -g main.c -o main
gdb 可执行文件名:开始调试
list / l : list 1 列出源码。根据源码指定行号设置断点
b : b 20 在20行位置设置断点
run / r:运行程序
n / next :下一条指令(会越过函数,但是仍会显示函数执行结果)
s / step :下一条指令(会进入函数)
print /fmt expression:可以查看某个变量的值或者地址。 下面是fmt的可取值
指令 | 说明 |
---|---|
x | 16进制 |
d | 有符号的10进制,默认格式 |
u | 无符号的10进制 |
o | 8进制 |
t | 2进制 |
a | 地址 print /a &i :查看i的地址 |
c | 字符 |
f | 浮点 |
s | 字符串 |
continue :继续执行断点后续指令
quit : 退出gdb当前调试。
其他指令:
处理段错误:进入gdb调试,直接run,程序会在出现段错误的地方停止。
finish:结束当前函数调用,返回函数调用点
set args 参数1 参数2 参数3 :设置main函数命令行参数
run 参数1 参数2 参数3 :设置main函数命令行参数
info b:查看断点设置信息
设置条件断点:b 41 if i=5
ptype i :查看i变量的类型
backtrace / bt :查看函数的调用栈帧和层级关系
frame:根据栈帧编号,切换函数的栈帧
display 变量名:设置跟踪变量
undisplay 跟踪变量的编号 :取消设置的跟踪变量
使用 gdb 调试的时候,gdb 只能跟踪一个进程。可以在 fork 函数调用之前,通过指令设
置 gdb 调试工具跟踪父进程或者是跟踪子进程。默认跟踪父进程。
set follow-fork-mode child 命令设置 gdb 在 fork 之后跟踪子进程。
set follow-fork-mode parent 设置跟踪父进程。
注意,一定要在 fork 函数调用之前设置才有效