Linux C/C++开发

首先就是要熟练在vim里面写代码(更:VScode ssh写就完事),其实就是没有提示和自动补全了(更:可以用插件),这个问题并不大。

我服务器gcc版本是4.8.5,所以就按照这个来了 https://gcc.gnu.org/onlinedocs/gcc-4.8.5/gcc/

其实我的开发者环境是最新的9.1.0,非常不建议哦。生产环境和开发环境尽量相同,不同的话一定要进行大量的测试

然后就是编译,先cd到工程文件夹,然后使用编译命令编译

一、编译

编译:当前源代码编译成二进制目标文件(.obj文件)

链接(link):将生成的.obj文件与库文件.lib等文件链接,生成可执行文件

一个现代编译器的主要工作流程如下:

源程序(source code)→预处理器(preprocessor)→编译器(compiler)→汇编程序(assembler)→目标程序(object code)→连接器(链接器,Linker)→可执行程序(executables)

执行过程 虽然我们称gcc是C语言的编译器,但使用gcc由C语言源代码文件生成可执行文件的过程不仅仅是编译的过程,而是要经历四个相互关联的步骤∶

1.预处理(也称预编译,Preprocessing):命令gcc首先调用cpp进行预处理,在预处理过程中,对源代码文件中的文件包含(include)、预编译语句(如宏定义define等)进行分析。

2.编译(Compilation):接着调用cc1进行编译,这个阶段根据输入文件生成以.o为后缀的目标文件。

3.汇编(Assembly):汇编过程是针对汇编语言的步骤,调用as进行工作,一般来讲,.S为后缀的汇编语言源代码文件、.s为后缀的汇编语言文件经过预编译和汇编之后都生成以.o为后缀的目标文件。 

4.链接(Linking):当所有的目标文件都生成之后,gcc就调用ld来完成最后的关键性工作,这个阶段就是连接。在连接阶段,所有的目标文件被安排在可执行程序中的恰当的位置,同时,该程序所调用到 的库函数也从各自所在的档案库中连到合适的地方。 

实例:

1.编写hello.c文件

2.预编译过程:

gcc -E ./hello.c -o hello.i //.i 为后缀的文件,是已经预处理过的C源代码文件,可以省略这一步。

cat hellp.c | wc -l //查看hello.c文件内容的行数。

cat hellp.i | wc -l //查看hello.i文件内容的行数。

3.汇编过程:

gcc -S hello.i -o hello.s //.s为后缀的文件,是汇编语言源代码文件;可以省略这一步。

4.编译过程

gcc -c ./hello.c //在当前文件夹下生成hello.o .o为后缀的文件,是编译后的目标文件;

gcc -c hello.c -o hello.o //在当前文件夹下生成hello.o

5.链接过程:

gcc hello.o -o hello

6.直接在终端输入文件路径或者把hello文件拖动到终端即可执行

用g++编译c++源程序

用g++编译c++源程序和c语言类似,可将gcc改为g++逐个尝试。以下只提供一些简单介绍:

-E Preprocess only; do not compile, assemble or link

-S Compile only; do not assemble or link

-c Compile and assemble, but do not link

-o Place the output into

-g Use of extra debugging information

-w 关闭编译时的警告

-o 参数谨慎使用,也许开了编译优化会出现问题,但是开了编译优化代码真的会变快,做好测试就行

也可以直接一步到位,默认是位置无关,readelf后Type为DYN (Shared object file),-no-pie可以关掉位置无关

gcc addr.c
gcc -no-pie addr.c
readelf -h a.out

可以输出main地址看看 printf("%p\n",main);

二、gdb调试

assert断言函数 如果参数expression等于零,一个错误消息将会写入到设备的标准错误集并且会调用abort函数,就会结束程序的执行。这个虽然可以找到错误,不知道哪里错就要用gdb了

gdb的其实是一个可执行文件,所以我们需要先编译出这个文件

#include <stdio.h>
int main()
{
    int a = 0;
    printf("%d\n", a++);
    printf("%d\n", a--);
    printf("%d\n", ++a);
    printf("%d\n", --a);
}
A.c
gcc -g A.c -o A

命令中出现了-g参数,这个不仅可以创建符号表,符号表包含了程序中使用的变量名称的列表,而且可以关闭所有的优化机制,以便程序执行过程中严格按照原来的C代码进行。

一定要记得加入这个参数

输入gdb就可以有了,当然你可能不想看到那一坨,可以加-q参数得到一个清爽的界面

gdb后可以file 跟上文件名,也可以进入之后再指定

[root@BobHuang ~]# gdb -q A

Reading symbols from /root/A...done.

(gdb) file A

想回过头看看以前的代码,就用list,一次可以显示十行,继续list可以显示接下来的十行

(gdb) list

1 #include<stdio.h>

2 int main()

3 {

4     int a=0;

5     printf("%d\n",a++);

6     printf("%d\n",a--);

7     printf("%d\n",++a);

8     printf("%d\n",--a);

9 }

10

list默认参数可以用show listsize来查看,如果感觉10行太多或者太少,还可以用set listsize <count>来更改。

但是我有时候只是想部分,就可以给list加上参数 

list 还可以加上其他参数,比如:
list 5,10   显示第5行到第10行的代码;

list func   显示func函数周围的代码,显示范围和list参数有关;

list test.c:5,10  显示源文件test.c第5行到第10行的代码,一般用于调试含多个源文件的程序。

gdb 还支持字符串查找,search str,从当前行开始,向前查找含str的字符串;

reverse-search str,从当前行开始,向后查找含str的字符串。

在gdb里也可以使用shell+命令,比如

shell clear

就会完成清屏

然后就可以设置断点了,和在图形界面类似,可以设置在某一行断点。甚至可以直接写一个判断表达式

(gdb) break 5

Breakpoint 1 at 0x40052c: file A.c, line 5.

(gdb) break 6 if a==1

Breakpoint 2 at 0x400546: file A.c, line 6.

(gdb) break 7 if a==1

Breakpoint 3 at 0x400560: file A.c, line 7.

然后可以通过info breakpoints来查看断点

(gdb) info breakpoints

Num     Type           Disp Enb Address            What

1       breakpoint     keep y   0x000000000040052c in main at A.c:5

2       breakpoint     keep y   0x0000000000400546 in main at A.c:6

stop only if a==1

3       breakpoint     keep y   0x0000000000400560 in main at A.c:7

stop only if a==1

 

Num表示断点的编号;Type表示断点的断点的类型,第二个断点类型还加上了条件;Disp表示中断点在执行一次之后是否失去作用,dis为是,keep为不是;Enb表示当前中断点是否有效,y为是,n为否;Address表示中断点所处的内存地址;What指出断点所处的位置。 

(gdb) run

Starting program: /root/A 

 

Breakpoint 1, main () at A.c:5

5     printf("%d\n",a++);

Missing separate debuginfos, use: debuginfo-install glibc-2.17-260.el7_6.5.x86_64

(gdb) continue

Continuing.

0

 

Breakpoint 2, main () at A.c:6

6     printf("%d\n",a--);

(gdb) continue

Continuing.

1

1

0

[Inferior 1 (process 32035) exited with code 02]

但是他提示我软件没装啊,我们装一下

(gdb) shell debuginfo-install glibc-2.17-260.el7_6.5.x86_64

再次运行,没有变化,也就是没有经过breakpoint3,也就证明了,执行断点3时a!=1,所以这个判断非常好用啊,能检测出某些异常,不过bug复现是不太好实现

接下来就是删除断点了。如果不需要程序在该断点暂停时,有两种方法,一种是使该断点失效,一种是直接删除该断点。使断点失效用的是Num,删除断点用的是行,这样就巧妙完成了需求

(gdb) disable 2

(gdb) info breakpoints

Num     Type           Disp Enb Address            What

2       breakpoint     keep n   0x000000000040052c in main at A.c:5

(gdb) clear 5

Deleted breakpoint 1 

delete命令后面的参数也为Num;可以一次删除多个断点,断点编号之间用空格隔开;如果delete后没有参数,默认删除所有断点,会给出提示选择是否操作。

上面虽然展示了一下,但是我们需要更多的演示,才展示他的强大

run,开始运行程序;

continue,程序暂停时继续运行程序的命令;

print 变量名或表达式,打印该变量或者该表达式的值。whatis 变量名或者表达式,可以显示该变量或表达式的数据类型。

print  变量=值,这种形式还可以给对应的变量赋值;类似的还有set variable 变量=值。作用和用print赋值相同。

next,继续执行下一条语句;

还有一条命令step,与之类似,不同的是,当下一条语句遇到函数调用的时候,next不会跟踪进入函数,而是继续执行下面的语句,而step命令则会跟踪进入函数内部。

(gdb) run
Starting program: /root/A 

Breakpoint 2, main () at A.c:5
5        printf("%d\n",a++);
(gdb)  next        //继续执行下一条语句,只执行一条
0
6        printf("%d\n",a--);
(gdb) continue    //让程序继续运行,直到下个断点或者结束
Continuing.    
1
1
0
[Inferior 1 (process 6553) exited with code 02]

直接赋值的结果

(gdb) run
Starting program: /root/A 

Breakpoint 2, main () at A.c:5
5        printf("%d\n",a++);
(gdb) print a=10
$1 = 10
(gdb) continue
Continuing.
10
11
11
10
[Inferior 1 (process 6693) exited with code 03]

还有nexti和stepi命令,这两个是单步执行一条机器指令,比如(i=0;i<n;i++)这条语句需要输入多个nexti才能执行完;两个的区别和上面相同。

quit,退出gdb调试,如果调试中想要退出,可以直接输入该命令,会出现提示选择是否退出。kill命令,结束当前程序的调试,(不会退出gdb)。

三、gdb调试进阶(2023年更)

 进入gdb的常见命令有

# gdb本身是可执行
gdb
# gdb "program"这个可执行程序
gdb program
# 将 core 文件作为调试输入,允许你分析程序崩溃时的状态
gdb program core
# gdb gcc,且带上-O2 -c foo.c。相当于gdb "gcc -O2 -c foo.c"这条命令
gdb --args gcc -O2 -c foo.c
# 启动gdb,不显示额外信息
gdb --silent

在gdb中的常见命令有

run : 开始运行

start : 运行并停在main函数上

continue: 继续运行程序到下一断点或结束

skip : 忽略某函数或文件

checkpoint : 设置书签,保留快照(状态)信息,可以快速返回再开始调试

next:单步运行指令,直接下一行

step:  单步运行指令,会进入子函数内部

skip的语法如下

skip -file <file>
skip -gfile <file_glob_pattern>
skip -function <linespec>
skip -rfunction <regexp>
info skip [range]

checkpoint的语法如下

checkpoint
restart checkpoint-id
info checkpoints
delete checkpoint checkpoint-id

设置断点有以下几种方法

break
b
break [Function Name]
break [File Name]:[Line Number]
break [Line Number]
break *[Address]
break [...] if [Condition]
break [...] thread [Thread-id]
b [...]
rbreak [regexp]
rbreak [File Name]:[regexp]
tbreak [args]

rbreak为匹配regexp的函数名处都设置断点,也就是正则断点,tbreak为只生效一次临时断点

断点管理,[breakpoints]表示断点编号

# 查看断点,建议使用i b缩写
info break

# location可以是function, file:func, linenum, file:linenum
clear localtion

# [breakpoints] 是可选项,也可以全删除
delete [breakpoints] [range...]

# 禁用|启用断点
disable|enable [breakpoints] [range...]

# 启用断点一次
enable [breakpoints] once range...

# 启用断点cnt次
enable [breakpoints] once range...

# 临时启用断点,一旦被激活就会把删除,和tbreak相似
enable [breakpoints] delete range... 

保存恢复断点,保存到个人目录下的 gdb.bp中

(gdb) save breakpoint ~/gdb.bp
Saved to file '/root/gdb.bp'.

启动加载断点,使用-x参数

gdb -x ~/gdb.bp --args gcc -O2 -c foo.c

查看过程中的信息,主要是 i b,i f ,bt

# 查看断点
info break
# 简写
i b

# print打印
print a
# 修改
p a=10
# 打印
p a
# print 同样支持[]下标运算符,@ 可以输出多个
print OutVals[0]
print OutVals@2
# print LLVM里都有的dump方法
print ...->dump()

# 显示当前程序堆栈函数调用信息
bt
# 只显示5个
bt 5

# 显示程序运行到此处时,附近的变量值
info local

# 显示变量,这是自动显示的,下次还会出现
display $var

# 设置watch监视点,检测表达式变化则停住
watch i == 1

# 显示当前程序用到的寄存器信息
info registers

# 显示当前函数栈帧
info frame

可能有用的,gdb汇编,可以用ni下一条。以下语句的意思是显示程序执行到当前代码位置之前的 20 条指令,PC偏移40字节

display /20i $pc-40

四、makefile编写

已无人再写makefile,现在是CMke的天下,作者的新博客

makefile带来直接好处就是——“自动化编译”。一旦写好,只需要一个make命令,整个工程完全自动编译,所以十分方便。而Makefile文件就是告诉make命令怎么样地去编译和链接程序。但是想要比较灵活的运用它,还是先要熟悉一些关于系统对程序编译和链接的知识。

楼下补充的好,现在一般都是使用cmake去生成的

make时使用VERBOSE=1可以查看中间过程,一般用来给makefile查错,-j使用多线程更快编译

 make -j VERBOSE=1

附录一、参考

1、gdb深入学习手册

附录二、gdbinit配置

gdb启动会加载~/.gdbinit,所以新建这个文件放入如下内容就好了

set style address foreground red
set print element 0
set pagination off
set print array-indexes on
set print pretty on
set print object on
set print static-members on
set print demangle on
set print sevenbit-strings off

python
import sys
sys.path.insert(0, '/usr/share/gcc-11/python')
from libstdcxx.v6.printers import register_libstdcxx_printers
register_libstdcxx_printers (None)
end

 

posted @ 2019-07-28 08:49  暴力都不会的蒟蒻  阅读(6231)  评论(2编辑  收藏  举报