GNU-Tutor
基础:C/C++的源文件编译过程
想要代码在操作系统上运行,需要进行以下编译步骤,从高级语言转机器语言,以下任务gcc/g++均能完成:
- 预编译(.c/cpp & .h —> .i ):*对应gcc/g++命令:
gcc -E [xxx] -o [output.i]
- 展开所有宏定义#define(字符替换);
- 处理所有条件预编译命令(#ifdef、#ifndef、#endif等);
- 处理#include,具体操作是将指向的文件直接插入到文件的这一行(严格遵循上一步的条件);
- 删除所有注释;
- 添加行号、文件标识,以便调试/编译出错时及时指出;
- 保留#pragma指令,以供编译器使用;
- 编译(.i —> .s ,即高级语言转汇编语言):对应gcc/g++命令:
gcc -S [xxx] -o [output.s]
- 词法分析、语法分析、语义分析(前端);
- 生成中间代码、优化,生成目标代码(解释型语言无需做此步,生成中间代码后直接对语义执行);
- 编译原理入门:https://www.cnblogs.com/fisherss/p/13905395.html
- 汇编(.s —> .o/obj,即汇编语言转机器语言):对应gcc/g++命令:
gcc -c [xxx] -o [output.o/obj]
- 类Unix系统下生成*.o,Windows系统下生成*.obj;
- 链接(.o —> .out/exe,链接各种需要的库和其他目标文件):对应gcc/g++命令:
gcc [xxx] -o [output]
- 生成调试文件(供gdb使用,对应gcc/g++命令:
gcc [xxx] -g -o [output]
)
- 也支持
-o
参数:-O
(默认)、-O1
(尝试减少代码体积和代码运行时间,但是并不执行会花费大量时间的优化操作)、-O2
(包含 -O1的优化并增加了不需要在目标文件大小和执行速度上进行折衷的优化,会花费更多的编译时间当然也会生成性能更好的代码)、-O3
(不建议使用)注:以上
[xxx]
内容表示gcc接受前面的任意类型文件(比如链接命令可以直接执行使*.cc—->*.out)。
基础:GNU及其相关概念
- GNU是由Richard Stallman在1983年9月27日公开发起的一项计划,目标是创建一套完全自由的操作系统,对标商业化的Unix系统。
- GNU软件可以自由地”使用、复制、修改和发布”,所有GNU软件都有一份在禁止其他人添加任何限制的情况下授权所有权利给任何人的协议条款,GNU通用公共许可证(GNU General Public License,GPL)。
- GNU及其创始人:开源运动的先驱
- GNU框架下的著名实现成果:GNU/Linux、GNU/GCC、GNU/make、GNU/GDB
- GCC(GNU Compiler Collection,GNU编译器集合),支持从预编译到链接过程的所有源码编译过程;
- MinGW(Minimalist GNU for Windows),可自由使用和自由发布的Windows特定头文件和使用GNU工具集导入库的集合,允许你在Windows平台生成本地的Windows程序而不需要第三方C运行时(C Runtime)库,可以理解为C/C++在Windows上的必要库的集合;
- MSVC:微软开发的VC运行库,附在Visual Studio上,默认使用MSVC编译器;
- MinGW 和 MSVC 的性质、完成目标都相同,只是开发者不同,选其一即可;
- LLVM:是构架编译器(compiler)的框架系统,以C++编写而成,用于优化以任意程序语言编写的程序的编译时间(compile-time)、链接时间(link-time)、运行时间(run-time)以及空闲时间(idle-time),对开发者保持开放,并兼容已有脚本。多用于Apple平台;
- make与CMake
make
出现背景:庞大项目下,一次次使用GCC编译无比麻烦,例如库的依赖关系链接和设定。需要采用:写一个文件指导编译过程的方法。make
的不足:对类Unix系统通用(不兼容Windows)、语法简单(功能受限)、不同编译器语法规则不同(适合GCC,却不适合MSVC);- 改进(
cmake
):make必须编写对应编译器的makefile来实现编译;而CMake则通过CMakeLists.txt
,结合当前系统环境、编译器构建合适的文件(可以针对make,也可以针对MSVC);
- GDB: 全称“GNU symbolic debugger”,
CMake 快速入门
基本步骤
- 写 CMake 配置文件 CMakeLists.txt 。
- 执行命令
cmake PATH
或者ccmake PATH
生成 Makefile(ccmake
和cmake
的区别在于前者提供了一个交互式的界面)。其中,PATH
是 CMakeLists.txt 所在的目录。 - 使用
make
/cmake --build
命令进行编译。
CMakeLists.txt
语法普通用法(不存在链接库)
1
2
3
4
5
6
7
8
9cmake_minimum_required (VERSION 2.8 [FATAL_ERROR])
# 项目名称
project (projectName [LANGUAGE CXX])
# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_SRCS 变量
aux_source_directory(. DIR_SRCS)
# 指定生成目标(也可以不用aux_source_directory,但是需要手动一个一个写)
# 如:add_executable (projectName sourceFile1 sourceFile2 ...)
add_executable(projectName ${DIR_SRCS})存在链接库的用法:情况1:要编译为动态链接库的文件放在另一个目录下。
- 在main存在的目录下的CMakeList.txt:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (projectName)
# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_SRCS 变量
aux_source_directory(. DIR_SRCS)
# 添加 subDirName 子目录(提示CMake查看该子目录下的CMakeLists.txt)
add_subdirectory(subDirName)
# 指定生成目标
add_executable(exeName main.cc)
# 添加链接库(提示CMake和后来的make,exeName需要链接dllName的链接库)
target_link_libraries(exeName dllName)- 在编译链接库的目录下的CMakeLists.txt:
1
2
3
4
5# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_LIB_SRCS 变量
aux_source_directory(. DIR_LIB_SRCS)
# 生成链接库
add_library (dllName ${DIR_LIB_SRCS})存在链接库的用法:情况2:要编译为动态链接库的文件放在同一个目录下。
1
2
3
4
5
6
7
8
9
10# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (projectName)
add_library (dllName dllSourceFiles)
add_executable (exeName sourceFiles)
target_link_libraries (exeName dllName)存在链接库的用法:情况3:第三方已编译的动态链接库(*.so/*.dll)
注:每个第三方库都必须经过如下设置
1
2
3
4
5
6
7
8
9
10add_library (libName SHARED IMPORTED)
set_target_properties (
libName
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/[libDIR]
)
...
target_link_libraries (exeName libName)
CMake定制安装规则:如果使用
make install
则会用到安装位置设置:
1
2
3
4# fileName(可以是链接库名,也可以是可执行文件名)安装到/usr/local/bin
install (TARGETS fileName DESTINATION bin)
# dll.h安装到/usr/local/include
install (FILES dll.h DESTINATION include)
CMake添加调试标记(供GDB使用)
1
2
3
4
5
6# 将编译选项切换为Debug(而非Release)
set (CMAKE_BUILD_TYPE DEBUG)
# 以下命令如果是针对C语言,请将CXX改为C(将Debug模式下的CXXFLAGS的CMake参数加上调试符号)
set (CMAKE_CXX_FLAGS_DEBUG "${CXXFLAGS} -O0 -Wall -ggdb")
# 一般对Release选项直接用O2优化编译即可,可以这么写:
set (CMAKE_CXX_FLAGS_RELEASE "${CXXFLAGS} -O2 -Wall")
GCC/G++快速入门
XP 看什么,在1. 中已经写了,详细见官方文档
GDB 快速入门(以Linux环境为例)
- 常用命令
命令名称 | 命令缩写 | 命令说明 |
---|---|---|
run | r | 运行一个待调试的程序 |
continue | c | 让暂停的程序继续运行(到下一个断点) |
next | n | 运行到下一行(逐过程) |
step | s | 单步执行,遇到函数会进入(逐步) |
until | u [num] | 运行到指定行停下来 |
finish | fi | 结束当前调用函数,回到上一层调用函数处(跳出) |
return | return [val] | 结束当前调用函数并返回指定值,到上一层函数调用处 |
jump | j [num] | 将当前程序执行流跳转到指定行或地址(VS移动执行点) |
p [var_name] | 打印变量或寄存器值 | |
backtrace | bt | 查看当前线程的调用堆栈 |
frame | f [stackName] | 切换到当前调用线程的指定堆栈 |
thread | thread | 切换到指定线程 |
break | b [line/funcName] | 添加断点(行数/函数入口) |
tbreak | tb | 添加临时断点(一次性断点) |
delete | d | 删除断点 |
enable | enable | 启用某个断点 |
disable | disable | 禁用某个断点 |
watch | watch | 监视某一个变量或内存地址的值是否发生变化 |
list | l | 显示源码 |
info | i | 查看断点 / 线程等信息 |
ptype | ptype | 查看变量类型 |
disassemble | dis | 查看汇编代码 |
set args | set args | 设置程序启动命令行参数 |
show args | show args | 查看设置的命令行参数 |
下面正式开始快速入门的使用介绍
提示:在GDB命令行中,直接回车表示执行上一条命令。
运行前部署
- 需要使用g++/gcc/make/cmake等编译(引导)方式来设置运行模式(debug),载入断点符号;
- 启用调试的三种方式
- 直接调试目标程序:gdb ./hello_server
- 附加进程id:gdb attach pid(此法在退出GDB前要detach pid)
- 调试core文件:gdb filename corename
- 启动待调试程序:
run
- 进行如上步骤后,会进入gdb交互命令行,此时如果是直接调试目标程序,则目标程序还未启动,可以:
- 设置运行参数:
set args [arg1 arg2 ..]
- 查看运行参数:
show args
- 查看运行路径:
show paths / pwd
- 设置当前环境变量:
set env name=value
- 查看环境变量:
show env [name]
- 设置运行参数:
设置断点(所有断点都有编号bID)
指定函数入口出设置断点:
break fucnName
如果此函数被重载,则会出现断点菜单。只需选择相应的重载函数设置即可(如果放弃填0;可多选,空格隔开)
指定行号设置断点:
break N
指定源文件的上述位置设置断点:
break fileName:xxx
指定当前行前/后设置断点:
break +/-N
在下一行设置断点:
break
条件断点:
break [...] if cond
([…]可以是上述参数,cond可以含有当前scope中的任意变量名,例如:break if i==100
)每个断点都有条件属性,只不过默认是true,只有if设置了才变成条件断点。
查询指定断点的信息:
info break [bID]
设置观察点
- 观察表达式,一有变化立即停止:
watch <expr>
,例如:watch i+k
- 观察变量,一旦被读,立即停止:
rwatch <var>
,例如:rwatch j
- 观察变量,一旦被访问(读或写),立即停止:
awatch <var>
- 查询观察点信息:
info watchpoints [wID]
- 观察表达式,一有变化立即停止:
设置捕捉点
设置事件捕获点:
catch <event>
event 可以是如下关键字:
throw:当程序抛出异常
catch:当程序捕获到异常
- exec:当程序调用系统shell执行
- fork:当程序调用fork
- vfork:当程序调用vfork
- load [libName]:当程序载入共享库
- unload [libName]:当程序卸载共享库
单次设置捕获点:
tcatch <event>
维护上述所有的停止点
- 清除停止点:
clear(全部) / clear funcName / clear N / clear fileName:xxx
- 清除断点:
delete [ID]
- 停用/启用停止点:
disable/enable [ID]
- 仅启用停止点一次:
enable [ID] once(会再次disable)/delete
- 清除停止点:
维护条件断点
- 修改指定的条件断点的条件:
condition [bID] <expr>
- 清除指定的条件断点的条件(变为条件true的普通断点):
condition [bID]
- 忽略指定的条件断点的条件若干次:
ignore [bID] <count>
- 修改指定的条件断点的条件:
停止点命令:自动化调试
和断点的条件属性一样,命令也可以是属性!在触发停止点停止后,会立即执行停止点命令
1
2
3
4
5
6// 格式:
commands [bID]
当前语言下的代码指令
[continue] // 如果希望执行完停止点命令后继续断电后的指令,
// 在命令后、end前写continue关键字即可
end恢复程序运行和单步调试
- 继续(可以指定其后忽略断点数):
continue [N=0]
- 逐步(step in,前提是调用函数在编译时也加了debug符号;count表示跳过的指令行数):
step [count=0]
- 逐过程(step over):
next [count=0]
- 跳出当前函数:
finish
- 设置机器码单步跟踪:
set step-mode on(default=off)
- 显示跟踪模式:
show step-mode
- 继续到指定行:
until [N]
- 逐机器码:
stepi [count=0]
- 继续(可以指定其后忽略断点数):
运行时数据查看:
print /f <expr>
请注意:GDB不能使用程序中的宏定义
前置知识:
@是GDB中与数组相关的操作符、
::是GDB中的作用域运算符、{type} addr 是指针相关操作符
expr为普通变量或静态数组:全局/静态全局/当前scope的局部变量都可以,也可指定
["fileName"::]["funcName"::]var
;expr为动态数组:使用
@
操作符,用法:第一个内存的地址的值(需要对指针取值,*)@ 显示元素长度例如程序定义了:
int* arr = new int[len] {0};
,那么可以使用:print /d *arr@len
来查看arr数组中的情况参数
/f
x 按十六进制格式显示变量
d 按十进制格式显示变量
u 按十六进制格式显示无符号整型
o 按八进制格式显示变量
t 按二进制格式显示变量
a 按十六进制格式显示变量
c 按字符格式显示变量
f 按浮点数格式显示变量设置显示选项(只讲述部分)
set print pretty on
:更优雅地显示结构体等变量;set print vtbl on
:更优雅地显示虚函数表;
查看内存:
examine [/n(内存长度Byte)f(同print含义)<u>(取地址中内容作变量值,可以取b-单字节数据,h-双字节数据,w-四字节数据,g-八字节数据)]
自动显示点(也有编号dID)
- 设置:
display /f <expr>
,程序停止时打印该表达式的值; - 删除:
delete [dID]
- 启用/禁用:
disable/enable [dID]
- 设置:
查看显示的历史记录
GDB将print过的表达式值编号,定义为环境变量:
$1、$2、$3、...
也可以用
show values [N]
查看历史设置、查看GDB环境变量
GDB环境变量不限类型,定义方式:
set $name = value
调用方式:
$name
查看所有环境变量:
show env
查看寄存器:
info [all-]registers
(不加all-,不包含浮点寄存器)
以下部分以后补充。https://www.cnblogs.com/lvdongjie/p/8994092.html
- 运行时变更执行
- 信号处理
- 线程调试
- 源码查看