操作系统(2)应用眼中的操作系统:系统调用
作者:互联网
操作系统(2)应用眼中的操作系统:系统调用
什么是(应用)程序
- 可执行的文件(程序的二进制代码和数据)和其他数据文件
- Linux支持多种可执行文件·格式
- ELF(Executable Linkable Format)最常用
- 正在运行的称为进程
- 操作系统中有许多进程对象
- 在运行时,进程会
- 在CPU上执行,进行计算
- 使用操作系统API访问操作系统中的其他对象
-
系统中常见的应用程序
ELF二进制文件
- 文件处理常用命令:
- vi a.c 直接编辑文件。按i进入编辑模式,:q不保存退出,:wq或:x保存并退出
- file a.o 查看文件的信息。比如是什么格式的文件。
- cat a.c 查看文件的内容
- xxd a.o 二进制显示和处理文件工具
解析ELF文件
如何解析
readelf是专门解析EIF可执行文件的工具;我们需要关注:
- ELF文件的header(元数据):
- 文件内容的分布
- 指令集体系结构
- 入口地址
- …
- ELF的header:决定该文件如何被加载器加载(执行)
在c程序中引入elf.h,/usr/include/elf.h提供了必要的定义
readelf使用说明
- 使用man readelf可查看使用说明
-
readelf -h a.o查看头部信息
-
readelf -l a.out 显示程序头(段头)信息(如果有数据的话)。
-
readelf -S a.out 显示节头信息(如果有数据的话)
-
readelf -g a.out 显示节组信息(如果有数据的话)
Hello World眼中的操作系统
如何实现一个最简单的c语言程序?跟着大佬做出的尝试
失败的尝试1
如下图,这是一个简单的helloworld程序:
我们尝试进行编译:
一个c语言经历的过程:.c -> 预处理 -> .l -> 编译 -> .s -> 汇编 -> .o -> 链接 -> .out
从上面我们可以看出,该程序在链接时出现两处错误,大佬的解释(其实还有点懵,希望后面的学习能彻底懂):
失败的尝试2
做出修改后:
再次进行编译链接:
此时只出现了一处错误,为什么?还是连接器入口的问题,那我们指定一下入口,说不定可以?
终于链接成功了,但是又…运行失败!!!为什么。。。。
此时大佬带我们学习一个编译工具gdb。这样可以观察程序的执行!
常用的命令如下(后续我会学习一下gdb手册,专门写一篇博客介绍):
那么开始调试这个程序:
- 首先是gdb a.ouy进入调试:
- 接着是starti,让程序在第一条程序上停下来
- 接下来是layout asm,可以让我们更加方便地查看汇编代码:
- 接下来是info register,可以查看所有的寄存器
- 接下来是单步调试 si,按一次回车键可以单步一次,如图
从上图的调试中可以看到,return那里很奇怪,return指令是从栈上弹出返回的地址,和return配对的是call指令,当有一条call指令时,会把返回值放在堆栈上,然后跳转到main执行。而main函数又是谁调用的呢?真相只有一个,那就是操作系统帮我们加载了main
- 使用bt去打印系统调用栈可以看到main
所以继续执行会出现非法的地址访问这个错误
操作系统做了什么?
- 加载程序,并初始化运行环境(寄存器、代码、数据、堆栈)
- 从_start开始执行
成功的尝试——汇编语言
大佬的汇编语言版本(学完汇编一定亲自试一下):
整个运行过程的梳理:前面四条设置参数,然后syscall调用系统API,然后应用程序进入操作系统,执行很多指令,直到调用I/O输出hello,os world;最后三条指令是退出程序,设置返回值什么的。
- man 2 syscall 可以查看系统调用的手册
C语言版本:
运行:
- 使用objdump:查看目标文件的信息
- 使用gcc -g 命令编译成二进制代码,再使用objdump -S进行查看
结果:
main()之前发生了什么?
调试程序:
- 查看寄存器状态:
- 查看进程状态:info inferiors
- 查看暂停进程的地址空间的内容: !pmap [pid]
可以看到,系统帮我们加载了a.out的程序,然后再加载了ld-2.27.so初始的加载器。ld-2.27.so会帮我们加载libc,然后再调用libc初始化,最后调用main。
所以,main()开始之前:
main执行之前,发生了哪些API的调用?使用trace工具
一个练习的小demo:
- strace ./a.out 追踪api的调用
可以看到,第一个系统调用是加载a.out,然后后面的系统调用都是加载器和libc调用的
关于libc的简单介绍:
查看后半段的调用:
可以看到,执行第一个write的之后就调用I/O,在终端输出helloworld,这是分时系统的一个体现
- 将程序标准输出丢弃: strace ./a.out > /dev/null 输出到这里的内容都会被丢弃,后半段的内容变成了:
应用眼中的操作系统
本质上,所有的程序都和hello world类似
这些都是后面会学到的系统调用API。
demo:gcc
- strace -f gcc a.c 创建子进程时进行追踪
打印出了很长的系统调用序列
- strace -f gcc a.c 2>&1 |grep execve 查看 execve系统调用的情况
主要的系统调用都在上面可以看到,包括cc1,as,collect2,ld
各式各样的应用程序都是在这一套API上构建的!
标签:调用,操作系统,查看,程序,眼中,out,加载 来源: https://blog.csdn.net/weixin_45834777/article/details/114755427