系统调用工具
OS ——对象 API
什么是应用程序
什么是软件?
可执行的文件和其他数据文件: ELF(Executable Linkable Format)
vi elf.c //写入空的main函数
gcc -c elf.c // gcc -c 编译,生成目标文件elf.o
man gcc //查看帮助文档
file elf.o //查看目标文件, ELF 64位可重定位的文件
gcc elf.o // 链接程序,生成a.out
./a.out //运行的程序 —— 进程
file a.out // 查看文件信息 ELF 64 LSB shared
进程:运行的程序
ps ax | less // 查看进程
os中有很多进程对象
运行时,进程会在CPU上执行,进行计算;使用OS的API访问OS中其他对象
应用程序实例:
coreutils —— UNIX核心工具包
系统/工具程序: bash apt ssh vim tmux jdk python
apt —— 把文件复制到指定的地方,有时执行脚本/trigger
软件包:Ubuntu Packages
ELF二进制文件
可执行文件也是普通的文件,都是字节的序列
OS提供API打开、读取、改写文件
vim /bin/ls // 查看文件,二进制部分显示异常,字符串常量原样保存
cat //
xxd // 二进制文件分析工具,二进制文件的字节
解析ELF文件:头(元数据)和字节流
man readelf //查看信息
readelf -h /bin/ls // 头部信息,存储方式
readelf -l /bin/ls // 文件加载方式 LOAD 地址0,地址空间随机化,加载到内存任意位置
<elf.h> //头文件,可在c中直接引用
# include <elf.h>
Hello World程序的编译/链接/加载
失败的尝试1
// hello.c
int main()
{
printf("Hello World!");
}
gcc hello.c
./a.out
// output: Hello World!
运行流程:
gcc -c hello.c // 编译为hello.o
ls -l //查看
file hollo.o // 查看ELF
ld hello.o // 链接
链接错误
错误原因:有的是"puts",gcc - o0 在不优化的选项下也会进行一定的优化 —— 导致一些编译器bugs的源头
puts是库函数(libc)
main: _start是链接器默认的入口。将main改为_start 或者 ld -e main hello.o 指定入口
// null.c
int main()
{
}
gcc -c null.c
objdump -d hello.o // 寄存器操作,显示如下
ld -e main null.o // 不报错
file a.out // ELF
./a.out // segmentation fault
一个简单的程序的运行也是非常复杂。 —— 观察程序执行过程(调试gdb)
STFW/RTFM
man gdb // 手册,都要去读读
gdb a.out //调试
starti // 在第一条指令开始执行程序
layout asm // 查看汇编代码
info register // 查看寄存器
si // 单步执行
bt // 打印栈上内容
OS加载main函数。
成功的尝试:汇编
main函数确实运行了,只是在返回时crash了
syscall : 调用系统的API,进入操作系统。。。
man 2 syscall // 系统调用的ABI(Application Binary Interface),详细介绍系统调用的过程
# include <unistd.h>
# include <sys/syscall.h>
# define LENGTH(arr) (sizeof(arr) / sizeof(arr[0]))
const char hello[] = "\033[01;31mHello,OS World\033[0m\n";
//try : const char *hello =
int main()
{
syscall(SYS_write, 1, hello, LENGTH(hello));
// 调用SYS_write系统调用,往编号为1的问价描述符中写入地址为hello缓冲区上的大小为LENGTH字节的数据
syscall(SYS_exit, 1);
} ~
syscall代码在哪里??
man objdump // 显示目标文件的信息
objdump -d a.out // 打印汇编
objdump -S a.out // 打印汇编语言
gcc -g syscall-demo.c // 源代码信息包装在文件中
—— main()函数之前发生了什么?
普通C程序第一条指令在哪里?
libc的_start?
调试找答案:
gdb a.out
starti
info inferior // 显示当前进程信息
!ls // !运行系统命令
!pmap 770(process id) // 打印该进程此时的地址空间中的内容
main开始前,OS已经加载好了一些文件 —— a.out, 加载了ld,再调用libc,再调用main函数
trace工具—— 追踪程序执行信息
system call trace
strace // 使用ptrace系统调用实现的
man strace // 追踪系统调用和信号
strace ./a.out //打印系统调用序列
strace ./a.out > /dev/null // 丢弃标准输出
其他系统调用例子
gcc 调用哪些系统调用
.c -> (preprocess) -> .i -> (compile) -> .s -> (assembly) -> .o -> (link) -> a.out
man strace
strace -f gcc a.c // gcc 会启动其他进程,追踪子进程
strace -f gcc a.c 2&1 | grep execve | less // 重定向标准输出,并查看execve的系统调用
调用:
cc1:调用编译器,从c到汇编的过程
as:若干路径上的as,直到成功为止,从汇编到ELF
collect2: 链接。链接之前先收集构造函数和析构函数,生成一个它们的调用函数 —— 非main函数中的部分需要被调用
ld: 链接
图形界面程序(xedit)
实际上是调用系统中其它程序,进行程序之间的通信。
总结
所有的应用程序都是在一套操作系统的API上构建的!!