本节内容概要:
- 一个最小的不依赖任何库函数的程序
- 经过编译、链接,被操作系统加载
- 调用操作系统API(系统调用)
- 粗浅地讲解了应用程序使用何种API实现
- 编译器、图形界面程序等
什么是程序?
可执行文件(程序的二进制代码和数据)和其他数据文件
Linux支持多重可执行文件格式,ELF二进制文件是其中最常用的
file 命令查看文件信息
❯ vim a.c
❯ gcc -c a.c
❯ ls
a.c a.o
❯ file a.c
a.c: C source, ASCII text
❯ file a.o
a.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
❯ gcc a.o
❯ file a.out
a.out: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=e4a5d3ec4e9319f61211fa13d0e1c32a7db7374d, for GNU/Linux 3.2.0, not stripped
运行中的程序成为进(正在运行的)程(程序)
- 操作系统中会有很多进程对象
ps命令查看进程
- 在运行时,进程会
- 在CPU上执行,进行计算
- 使用操作系统的API访问操作系统中的其他对象
ELF二进制文件
可执行文件也是普通的文件
-
操作系统的一个对象
-
一个存储在文件系统上的字节序列
-
和文本文件没有本质区别
-
操作系统提供API打开、读取、改写(都需要相应的权限)
-
因此我们可以用xxd、 vim 、 cat 等命令查看可执行文件
- 在vim中打开,二进制部分显示异常,但可以看到字符串常量(vim /bin/ls)
- 使用xxd(命令分析工具)可以看到文件以 “\x7f” “ELF”开头
❯ xxd /bin/ls | less 00000000: 7f45 4c46 0201 0100 0000 0000 0000 0000 .ELF............ 00000010: 0300 3e00 0100 0000 1068 0000 0000 0000 ..>......h...... 00000020: 4000 0000 0000 0000 1834 0200 0000 0000 @........4...... ...
-
解析ELF文件
readelf 是专门解析ELF可执行文件工具;我们主要关注:
- header(元数据)
- 文件内容分布
- 指令集体系结构
- 入口地址
- ELF的program headers 决定了ELF如何被加载
❯ readelf -h /bin/ls
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: DYN (Shared object file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x6810 # 第一条指令的位置
Start of program headers: 64 (bytes into file)
Start of section headers: 144408 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 13
Size of section headers: 64 (bytes)
Number of section headers: 31
Section header string table index: 30
/usr/include/elf.h
提供了必要的定义
应用程序如何调用操作系统?
失败的尝试#1
假设我们要写一个最小的程序,只输出一句话
#include<stdio.h>
int main(){
printf("hello world!\n");
}
❯ gcc -c hello.c
❯ ld hello.o
ld: warning: cannot find entry symbol _start; defaulting to 0000000000401000
ld: hello.o: in function `main':
hello.c:(.text+0x10): undefined reference to `puts'
# 我们已经引入了相关的库了,为什么还报错?而且为什么是 puts 不是 printf ?
为什么是puts?
- gcc 在 -00 选项下依然会进行一定程度的编译优化
- 这是导致一些编译器bug 的源头
undefined reference to `puts’
- puts是库函数
- 把库函数也链接进来就不是我们要写的最小的程序了
- 放弃这个写法
cannot find entry symbol _start
- _start是连接器默认的入口
- 可以用 -e 指定 比如 -e main
失败的尝试#2
我们连这一句话也不输出了
int main(){
}
这样总行了吧?
❯ vim hello.c
❯ gcc -c hello.c
❯ objdump -d hello.o
hello.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <main>:
0: f3 0f 1e fa endbr64
4: 55 push %rbp
5: 48 89 e5 mov %rsp,%rbp
8: b8 00 00 00 00 mov $0x0,%eax
d: 5d pop %rbp
e: c3 retq
❯ ld -e main hello.o
❯ ./a.out
[1]