学了C语言,编译成可执行程序,就能运行了。对其能运行很是困惑。
最近闲来有时间,下定决心要将其理清楚。
手上电脑刚好装的是Linux系统,就在Linux上研究一下。
先熟悉可执行文件的格式 - ELF格式。Linux中,readelf可以查看ELF格式的关键信息。
readelf -h elffile可以查看elf头信息
ELF格式中,最关键的是ELF头,程序(Segment)头, 节头,程序(Segment)。
如上图所见,ELF头中描述了程序起始地址,程序头,节头等信息。数据格式如下:
找了一段汇编代码,编译出程序,用xxd start查看二进制,start为编译出的可执行程序:
根据ELF格式解析与readelf解析出来的,是一致的。
程序(Segment)头就是用于描述程序(Segment)部分的信息,其作用是指导内核,如何分配虚拟内存以及如何将elf格式文件加载到内存。
程序头的数据结构:
再次用xxd查看start文件:
该程序头描述的是:类型为,00000005说明是text程序段。虚拟地址,物理地址都为0x00400000起始地址,整个段的大小为d2,对齐方式为0x200000即2M对齐。
再看一个数据段的文件内容:
数据从0x00000078开始. 类型为,0x00000006说明是data段。虚拟地址,物理地址都为0x006000D2起始地址,整个段的大小为0f,对齐方式为0x200000即2M对齐。
elf格式的最基本内容已经了解清楚,接下来,需要查看程序在内存中的分布。
先了解一下查看内存分布的工具pmap,的用法:
最基本的命令格式就是 pmap PID
将刚才的程序运行起来,查看其内存布局:
正如程序段头的描述有2个段0x400000和0x600000,0x400000就是程序段,0x600000为数据段,还有1个136K的栈,以及3个匿名区域。
汇编代码:
#hello.s
.data # 数据段声明
msg : .string "Hello, world!\n" # 要输出的字符串
len = . - msg # 字串长度
.text # 代码段声明
.global _start # 指定入口函数
_start: # 在屏幕上显示一个字符串
movl $len, %edx # 参数三:字符串长度
movl $msg, %ecx # 参数二:要显示的字符串
movl $1, %ebx # 参数一:文件描述符(stdout)
movl $4, %eax # 系统调用号(sys_write)
int $0x80 # 调用内核功能
_loop:
nop
jmp _loop
# 退出程序
movl $0,%ebx # 参数一:退出代码
movl $1,%eax # 系统调用号(sys_exit)
int $0x80 # 调用内核功能
编译的Makefile内容:
start : start.S
as -o start.o start.S
ld -o start start.o
.PHONY:clean
clean :
rm start start.o