gcc 的参数
- -Wall 打开所有的警告。
- -g 只是在编译的时候,产生调试信息。
- -v 查看完整和详细编译的过程
- -E 把源文件预处理一下。
例如:
(1)把用户定义的所有的预定义#define展开。
> 举个例子,如果用户有以下代码:
#define Max(a, b) ((a)>(b)?(a):(b))
...
int c = Max(a, b);
经过gcc预处理后,预定义都将展开成如下样子:
int c = ((a)>(b)?(a):(b));
(2)把所有的#include的文件内容加载进来。
> 举个例子,如下:
预处理后程序会用stdio.h的内容替换掉#include <stdio.h>,
最后的程序中,就找不到<>这样的include,
更多的是#include "/usr/include/stdio.h"之类的。
这样就能知道
a)stdio.h在linux下的存放路径,
b)FILE结构体的摸样,
c)size_t类型的定义,
d)其他很多你想知道的
(3)处理条件编译,将不符合条件的代码段删除。
> 举个例子,如果用户有以下代码:
#ifdef M
printf("M");
#else
printf("NM");
#endif
如果没有定义M,预处理后程序将变成
printf("M");
例如编译01_hello.c :
gcc -g -Wall -v 01_hello.c -o hello
readelf : 查看二进制ELF可执行程序的格式。
例如:readelf -a hello
·
其中涉及的段:
1. debug : 用于保存调试信息
2. dynamic:用于保存动态链接信息
3. fini: 用于保存进程退出时的执行程序。进程结束的时候,系统会自动执行这部分。
4. init:用于保存进程启动时的执行程序。当进程启动时候,系统会自动执行者部分。
5. redata:用于保存只读数据,例如定义的const 的全局变量,字符串变量等。
6. symtab:用于保存符号表。
注意:
1. 如果不使用-g编译,就不会生成相应的调试段。
2. 可以使用strip去掉一些非必要的段,用来减小程序占用的空间,比如符号信息。
strace 跟踪系统调用程序
strace ./hello 输出信息如下:
1. 执行命令的时候,由shell调用fork,然后在子进程真正执行这个命令。
2. 先调用execve加载hello。
3. ld分别检查ld.so.nohwcap和ld.so.preload。
4. 如果ld.so.nohwcap存在,则ld加载未优化的库,如果ld.so.preload存在,则ld加载其中的库。
(可以使用这种机制替换系统调用或者C库,利用LD_PRELOAD实现)
5. 使用mmap将ld.so.cache映射到内存。
6. 使用ld加载C库libc.so.6
7. 使用mmap和mprotect设置程序各个内存区域。
8. write(1,"hello world!\n",13hello world!
) = 13
write向文件描述符1(就是标准输出)输出"hello world!\n",返回值为13,成功的字符个数。
9. 调用exit_group退出程序,参数为0,表示退出状态。
概念介绍
1. 系统调用时操作系统提供的服务,是应用程序与内核通信的接口。
2. 用户空间的程序默认是通过栈来传递参数的,内核态和用户态使用的是不同的栈,系统调用的参数只能通过寄存器的方式传递。
3. 编译器实现了圧栈,出栈,保存返回地址等操作,
4. C库函数位于用户态
5. linux平台下,系统调用使用寄存器eax来传递系统调用的。
6. 可重入函数一定是线程安全的,而线程安全函数不一定是可重入函数。
7. 使用锁,尤其是互斥锁,该函数是不可重入的。
8. 函数使用静态变量,并且依赖于静态变量,函数也是不可重入的。
9. 阻塞的系统调用是指,当进行系统调用时,除非出错(被信号打断也是视为出错),进程会一直陷入内核态直到调用完成。非阻塞的系统调用是指无论I/O操作成功与否,调用都会立即返回。
10. 同步既可以是阻塞的,也可以是非阻塞的。比如系统调用的read函数