搭建环境
# 安装C/C++的编译工具包,包括gcc与make等工具.使用`$ apt depends build-essential`查看该包详情
$ sudo apt-get install build-essential
#[可选安装包]简化Debian软件包维护者的工作的脚本工具
sudo apt-get install devscripts equivs
#[可选安装包]安装多重库。既能编译32位的目标对象,又能编译64位的目标对象;
$ sudo apt-get install gcc-multilib g++-multilib
#[可选安装包]安装模块助手包,编译Linux内核模块需要;
$ sudo apt-get install module-assistant
sudo apt-get install gcc-10 g+±10
ENV CC=gcc-10 AR=gcc-ar-10 NM=gcc-nm-10 RANLIB=gcc-ranlib-10
CFLAGS=“-pipe -O3 -march=native”
编译器(GCC)
常用选项
$ gcc -S -masm=intel sample.c -o sample.s
# 编译动态库
gcc test1.c test2.c -shared -fPIC -o libtest.so
# 使用动态库
gcc main.c -L. -ltest -o a.out
# -L : 表示需要库的路径
# -l:表示需要库的名称,如libtest.so,名称则为test
# 编译静态库
gcc -c test1.c test2.c
ar -r libtest.a test1.o test2.o
# 使用静态库
gcc main.c -static -L. -ltest -o a.out
选项 | 含义 |
---|---|
-v | 查看gcc编译器的版本,显示gcc执行时的详细过程; |
-o | 指定输出文件名为file,这个名称不能跟源文件名同名; |
-E | 只预处理,不会编译、汇编、链接; |
-S | 只编译,不会汇编、链接; |
-c | 编译可重定位文件(REL); |
–static | 链接静态库,如果不使用这个参数,而静态库与动态库同名的话,会优先使用动态库 |
-shared | 生成一个共享对象,然后可以与其他对象链接对象以形成可执行文件。并不是所有的系统都支持这一点选择,还需指定相同的内容用于生成代码的选项集’ -fpic’。即使不加fPIC也可以生成.so文件,但是对于源文件有要求,因为不加fPIC编译的so必须要在加载到用户程序的地址空间时重定向所有表目,所以在它里面不能引用其它地方的代码 |
-fPIC | 产生位置无关代码(Position-Independent Code),不加fPIC编译出来的so,是要再加载时根据加载到的位置再次重定位 |
-g | 产生调试工具(GNU的gdb)所必要的符号信息,想要对源代码进行调试,就必须加入这个选项; |
-m32 | 编译出32位目标文件; |
-masm=intel | 输出intel格式汇编;默认是输出AT&T格式汇编 |
-std | 指定c语言标准,默认ANSI |
-M | 自动找寻源文件中包含的头文件 |
-MM | 自动找寻源文件中包含的头文件,不包括系统头文件 |
-I | 指定自动检索头文件的目录 |
-include | 指定头文件,一般情况在代码文件中#include(尖括号系统默认路径,引号当前目录优先)头文件 |
-L | 指定库文件所在的目录 |
-l | 指定库,参数紧接着就是库名(不是库路径,库的路径可能是-L指定或系统默认的库路径) |
-Wl | 告诉编译器将后面的参数传递给链接器,多个参数用逗号分开 |
-On | 优化编译 n为等级,调试的时候最好不使用任何优化选项 |
– | – |
-march=native | 相当于LLVM的rustc或swiftc |
-mcpu=type | CPU指令,type可为i386、i486、pentium及i686等 |
-mieee-fp | 使用IEEE标准进行浮点数的比较 |
-mno-ieee-fp | 不使用IEEE标准进行浮点数的比较 |
-msoft-float | 输出包含浮点库调用的目标代码 |
-mshort | 把int类型作为16位处理,相当于short int |
-mrtd | 强行将函数参数个数固定的函数用ret NUM返回,节省调用函数的一条指令 |
– | – |
-ansi | 支持符合ANSI标准的C程序 |
-pedantic | 允许发出ANSI C标准所列的全部警告信息 |
-pedantic-error | 允许发出ANSI C标准所列的全部错误信息 |
-w | 关闭所有告警 |
-Wall | 允许发出Gcc提供的所有有用的报警信息 |
-Werror | 把所有的告警信息转化为错误信息,并在告警发生时终止编译过程 |
环境变量
- $CC:用来指定c编译器。
- $CPP:该环境变量是c=语言程序的预处理器
- $CPPFLAGS:该环境变量是传递给C预处理器
- $CFLAGS:该环境变量是传递给c语言程序的编译器选项,同–cflags选项。例如有一个头文件在非标准目录
- $LD_LIBRARY_PATH:加载器库目录
CFLAGS=-I/path/to/include //(大写的i)
上行设置可传递给c编译器,当其在标准目录中找不到头文件时,使其到/path/to/include目录中去寻找
连接器(ld)
GNU程序链接器(The GNU linker) 详情可以通过 man ld
查阅。
环境变量
LIBS:传递给连接器的选项,同-libs选项。例如告诉链接器要链接哪些库文件。 -l,例如:-lm 表示程序指定的链接库名是m,-l参数就是用来指定程序要链接的库,-l参数紧接着就是库名,就拿数学库来说,他的库名是m,他的库文件名是libm.so,很容易看出,把库文件名的头lib和尾.so去掉就是库名了。
LDFLAGS:传递给连接器的选项。例如告诉链接器从哪里寻找库文件 -L
如果库文件没放在/lib和/usr/lib和/usr/local/lib这三个目录里,而是位于非标准的库路径,那就需要告诉连接器到哪一个目录下去链接该库。比如我们把libtest.so放在/path/to/library目录下,那链接参数就是
LDFLAGS=-L/path/to/library -ltest
有时候LDFLAGS指定-L虽然能让链接器找到库进行链接,但是运行时链接器却找不到这个库,如果要让软件运行时库文件的路径也得到扩展,那么我们需要增加这两个库给"-Wl,R":
LDFLAGS = -L/var/xxx/lib -L/opt/mysql/lib -Wl,R/var/xxx/lib -Wl,R/opt/mysql/lib
链接阶段查询库文件
ld.so通过依次读取
- -rpath连接器选项,rpath链接选项主要有两个功能:
- 程序运行时,优先到rpath指定的目录去寻找依赖库,因为连接器将其指定的路径编码到目标文件中了。
- 程序链接时,用于指定间接引用的库位置,链接时作用等同且优先于
-Wl,-rpath-link
。
通过readelf查看被硬编码的-rpath:
$ readelf -d <可执行文件名> # Display the dynamic section (if present)
通过ldd
命令查看共享库文件依赖
$ ldd /bin/ls # Print shared object dependencies
- -rpath-link连接器选项,指定的目录只在链接阶段生效(链接器没有将库的路径包含进目标文件中)
程序链接时使用-Wl,-rpath-link ,用于指定间接引用的库位置。比如链接a库,a库依赖b库和c库,此场景下,a为直接引用, b和c为间接引用,编译程序时必须显式指定的指定间接引用:
gcc -o test test.c -I. -L. -la -lb -lc
使用 --rpath-link,即可避免显式的指定间接引用:
gcc -o test test.c -I. -L. -la -Wl,-rpath=.
- LD_RUN_PATH连接器环境变量
- -L 选项,用于指定链接库时的搜索路径目录
- 默认的库文件目录/lib、/usr/lib/gcc/ 和/usr/lib/
运行阶段查询库文件:
- etc/ld.so.cache
- /etc/ld.so.conf
动态链接库的配置工具管理ldconfig
在系统库文件默认目录/lib和/usr/lib以及动态库配置文件/etc/ld.so.conf中搜索出可共享的动态链接库(格式如lib*.so*),生成动态装入程序(ld.so)所需的连接和缓存文件。
缓存文件默认为/etc/ld.so.cache,此文件保存已排好序的动态链接库名字列表
ldconfig -p :打印当前缓存所保存的所有库的名字
编译实用工具pkg-config
在编译时需要包含头文件,链接时需要库文件,但安装一些第三方库之后不知道对应的include文件和lib文件的位置,此时就需要用到pkg-config这个实用工具程序。pkg-config 通过/usr/lib目录与PKG_CONFIG_PATH环境变量定义的目录下的所有*.pc文件来提供如下信息:
- C/C++编译器需要的输入参数;
- 链接器需要的输入参数;
- 已安装软件包的版本信息。
使用示例:
pkg-config --list-all
# 输出pkg-config可列出信息的库
gcc -o test test.c `pkg-config --libs --cflags glib-2.0`
# --libs 输出库文件项
# --cflags 输出头文件项
环境变量
- $PKG_CONFIG 该环境变量是pkg-config 工具程序的所在路径
- $PKG_CONFIG_PATH 默认是/usr/lib/pkgconfig目录,可以通过该环境变量指定目录。
pc文件是文本文件,扩展名是.pc,里面定义开发包的安装路径,Libs参数和Cflags参数等。 - $PKG_CONFIG_LIBDIR
该环境变量覆盖pkg-config的内置搜索路径的路径
构建工具
cmake
autoconf:用于依据 configure.in生成 configure 脚本。在目录中运行 ./buildconf即可。
automake
make -j $(nproc)
调试
GDB 是Linux 平台下的调试器,可完成如下的调试任务:
- 设置断点;
- 监视程序变量的值;
- 程序的单步执行;
- 修改变量的值。
命令 | 含义 |
---|---|
set disassembly-flavor intel | 设置环境变量,输出intel格式汇编代码; |
disass main | 查看main函数汇编代码; |
示例:查看函数调用
# gdb -ex "set pagination 0" -ex "thread apply all bt" --batch -p 22222
--ex
执行后面跟着的GDB命令set paginagtion 0
不使用分页输出thread apply all bt
bt 是backtrace的简写,即对所有线程使用backtrace命令--batch
使用批处理模式,在所有的命令执行完成后退出,正确返回0,错误为非0-p 22222
指定attach哪个进程的ID
详情链接
语言
引号
双引号返回字符串存储后的结果,即将存储后的指针返回。
char * str="a";//将a与'\0'存储后返回指针给变量str
printf("%s\r\n", str);
char * null="\0";
printf("%s\r\n", null);
char * empty="";
printf("%s\r\n", empty);
单引号返回处理前的结果,即将存储之前转换的整形返回。
char c='h';
printf("%c\r\n", c);
int i='h';
printf("%c\r\n", );
错误示例:
char c="h";
printf("%c\r\n", c);
编译结果
main.c: In function ‘main’:
main.c:13:16: warning: initialization of ‘char’ from ‘char *’ makes integer from pointer without a cast [-Wint-conversion]
13 | char str="h";
| ^~~
双引号可以用转义或单引号括起来的方式打印输出,单引号只能用转义字符来打印。
待整理
/usr/include/elf.h文件定义了ELF 文件头的结构和相关常数,文中的e_type定义了 ELF文件类型,主要有以下几种:
- 可重定位文件(Relocatable File) 包含代码和数据,可用来连接成可执行文件或共享目标文件,对应于Linux 中的.o ,Windows 的 .obj。
静态库(current ar archive)就是将可重定位文件归档打包
- 可执行文件(ELF 64-bit LSB executable) 包含了可以直接执行的程序,它的代表就是ELF 可执行文件,他们一般没有扩展名。比如/bin/bash ,Windows 下的 .exe
- 共享目标文件(ELF 64-bit LSB shared object ) 包含代码和数据,链接器可以使用这种文件跟其他可重定位文件的共享目标文件链接,产生新的目标文件。另外是动态链接器可以将几个这种共享目标文件与可执行文件结合,作为进程映像来运行。对应于Linux 中的 .so,Windows 中的 DLL
- 核心转储文件(Core Dump File) 当进程意外终止,系统可以将该进程地址空间的内容及终止时的一些信息转存到核心转储文件。 对应 Linux 下的core dump。
ELF 文件头的结构和相关常数一般定义在了 /usr/include/elf.h 中
gcc ./main.c -L /home/whois/temp/cc/libtest/ -ltest -I/home/whois/temp/cc/libtest/ -Wl,-rpath=/home/whois/temp/cc/libtest/ -o main
预处理语句#include
- 系统自带的头文件用尖括号括起来,这样编译器会在系统文件目录下查找。
- 用户自定义的头文件用双引号括起来,编译器首先会在用户目录下查找,然后再到 C的安装目录(Linux 中可以通过环境变量来设定)中查找,最后在系统文件中查找。
cpu_relax()锁一个最典型的要使用pu_relax()锁的场景是忙等待(也就是死循环等一个事情的发生)
while (p->on_cpu)
cpu_relax();
全局变量虽然属于静态存储方式,但并不是静态变量。全局变量的作用域是整个源程序,当一个源程序由多个源文件组成时,全局变量在各个源文件中都是有效的。有时候,我们希望函数中局部变量的值在函数调用结束之后不会消失,而仍然保留其原值。即它所占用的存储单元不释放,在下一次调用该函数时,其局部变量的值仍然存在,也就是上一次函数调用结束时的值。这时候,我们就应该将该局部变量用关键字 static 声明为“静态局部变量”。
当将局部变量声明为静态局部变量的时候,也就改变了局部变量的存储位置,即从原来的栈中存放改为静态存储区存放。这让它看起来很像全局变量,其实静态局部变量与全局变量的主要区别就在于可见性,静态局部变量只在其被声明的代码块中是可见的。
把依赖的包先给编译一份出来并且装上
$ mk-build-deps -s sudo -i
打包
$ dpkg-buildpackage -uc -us -b -j$(nproc)
标准与规范
- K&R” C
- ANSI C(c89)
定义了 C 标准库;
新的预处理命令和特性;
函数原型(prototype);
新关键字:const、volatile、signed;
宽字符、宽字符串和多字节字符;
转化规则、声明(declaration)、类型检查的改变。 - ISO 采纳了c89成为国际标准ISO/IEC 9899:1990(c90)
- C95
3个新标准头文件:iso646.h、wctype.h、wchar.h;
一些新的标记(token)和宏(macro);
一些新的 printf/scanf 系列函数的格式符;
增加了大量的宽字符和多字节字符函数、常数和类型。 - C99
复数(complex);整数(integer)类型扩展;变长数组;Boolean 类型;非英语字符集的更好支持;浮点类型的更好支持;提供全部类型的数学函数;C++ 风格注释(//)。 - C11 标准新引入的特征尽管没 C99 相对 C90 引入的那么多,但是这些也都十分有用,比如:字节对齐说明符、泛型机制(generic selection)、对多线程的支持、静态断言、原子操作以及对 Unicode 的支持。
- C17(也被称为为 C18)是于2018年6月发布的 ISO/IEC 9899:2018 的非正式名称,也是目前(截止到2020年6月)为止最新的 C语言编程标准,被用来替代 C11 标准。
C17 没有引入新的语言特性,只对 C11 进行了补充和修正。
ISO/IEC 9899:2018
MISRA C:2012 Third Edition, First Revision
SEI CERT C Coding Standard
CWE
二进制文件工具
更多工具点击(GNU Binutils)
readelf显示elf格式文件的信息;
hexdump,以各种进制编码的方式呈现二进制文件;
strings打印文件中可打印字符的字符串;
objdump读取可执行文件并将汇编语言指令转储到屏幕或文件;
$ objdump -d sample.o
$objdump -S main.o -S>main.dis
size,查看对象或归档映像时的内存段大小列表
nm,列出二进制目标文件的符号表,包括符号地址、符号类型、符号名等;
file,确定文件类型;
ldd,查看共享对象(动态链接库)依赖关系;
Itrace,显示运行时从库中调用的所有函数;
strace,显示运行时从系统中调用的所有函数;
例如 sudo strace -p <PID>
strace -fvttTyyx -s 4096 -e socket,bind,listen npm run dev
strace -tt -T -e trace=all -p
ar,查看函数库里的详细情况和用多个对象文件生成一个库文件
**trap -lp 与 kill **,信号量管理;
lsof, lsof -p <PID>
hexdump 以十六进制编码显示二进制文件
vim 查看与编辑二进制文件
#打开二进制文件
vim -d ./main
#将进制转换到十六进制显示,具体可查看xxd命令
:%!xxd -g 2
#将十六进制恢复到二进制显示
:%!xxd -r
#保存
:wq!
比较两个二进制文件
vim -bd main1 main2
打开后就可以在两个窗口里面显示两个文件
ctrl + W +L 把输入焦点切换到右边的窗口,激活右边的窗口后输入的命令就是针对右窗口了
:%!xxd -g 1 切换成十六进制的一个字节的模式
ctrl + W +H 把输入焦点切换到左边的窗口
:%!xxd -g 1
] + c 查找上一个不同点
[ + c 查找下一个不同点
hexdump+sed+xxd 三个命令批量替换二进制文件示例
#摘抄于 https://www.zhihu.com/question/19703679
hexdump -ve '1/1 "%.2x"' ora10g_mytab_us7ascii.dmp | sed "s/0001000107d00001/0001035407d00001/" |xxd -r -p > ora10g_mytab_zhs16gbk.dmp
addr2line 将地址转换成文件名与行号