程序人生-Hello’s P2P
计算机系统
大作业
题 目 程序人生-Hello’s P2P
专业 计算机科学与技术
学 号 1180300107
班 级 1803001
学生 胡云帆
指导教师 郑贵滨
计算机科学与技术学院
2019年12月
摘要
hello基本上是所有程序员最先接触到的程序,程序员通过hello接触了一门又一门的计算机语言,但很少有人能理解hello程序运行时背后的机制。本文以C程序为例,简要介绍了源程序hello.c从预处理、编译、汇编、链接成为程序的过程,并介绍了hello从程序到进程的进程管理、内存管理、Linux I/O等过程的实现细节。
关键词:编译过程;进程管理;
目录
第1章 概述 - 4 -
1.1 Hello简介 - 4 -
1.2环境与工具 - 4 -
1.3中间结果 - 4 -
1.4本章小结 - 4 -
第2章 预处理 - 5 -
2.1预处理的概念与作用 - 5 -
2.2在Ubuntu下预处理的命令 - 5 -
2.3 Hello的预处理结果解析 - 5 -
2.4本章小结 - 5 -
第3章 编译 - 6 -
3.1编译的概念与作用 - 6 -
3.2在Ubuntu下编译的命令 - 6 -
3.3 Hello的编译结果解析 - 6 -
3.4本章小结 - 6 -
第4章 汇编 - 7 -
4.1汇编的概念与作用 - 7 -
4.2在Ubuntu下汇编的命令 - 7 -
4.3可重定位目标elf格式 - 7 -
4.4 Hello.o的结果解析 - 7 -
4.5本章小结 - 7 -
第5章 链接 - 8 -
5.1链接的概念与作用 - 8 -
5.2在Ubuntu下链接的命令 - 8 -
5.3可执行目标文件hello的格式 - 8 -
5.4 hello的虚拟地址空间 - 8 -
5.5链接的重定位过程分析 - 8 -
5.6 hello的执行流程 - 8 -
5.7 Hello的动态链接分析 - 8 -
5.8本章小结 - 9 -
第6章 hello进程管理 - 10 -
6.1进程的概念与作用 - 10 -
6.2简述壳Shell-bash的作用与处理流程 - 10 -
6.3 Hello的fork进程创建过程 - 10 -
6.4 Hello的execve过程 - 10 -
6.5 Hello的进程执行 - 10 -
6.6 hello的异常与信号处理 - 10 -
6.7本章小结 - 10 -
第7章 hello的存储管理 - 11 -
7.1 hello的存储器地址空间 - 11 -
7.2 Intel逻辑地址到线性地址的变换-段式管理 - 11 -
7.3 Hello的线性地址到物理地址的变换-页式管理 - 11 -
7.4 TLB与四级页表支持下的VA到PA的变换 - 11 -
7.5三级Cache支持下的物理内存访问 - 11 -
7.6 hello进程fork时的内存映射 - 11 -
7.7 hello进程execve时的内存映射 - 11 -
7.8缺页故障与缺页中断处理 - 11 -
7.9动态存储分配管理 - 11 -
7.10本章小结 - 12 -
第8章 hello的IO管理 - 13 -
8.1 Linux的IO设备管理方法 - 13 -
8.2简述Unix IO接口及其函数 - 13 -
8.3 printf的实现分析 - 13 -
8.4 getchar的实现分析 - 13 -
8.5本章小结 - 13 -
结论 - 14 -
附件 - 15 -
参考文献 - 16 -
第1章概述
1.1 Hello简介
我是Hello,程序员用C语言创造了我,当程序员使用gcc编译我的时候,经历了预处理,编译,链接,汇编四个阶段后,我就变成了程序(program),后来,用户在shell中使用./hello ……运行了我,shell调用fork函数,并用execve函数运行了我,我便变成了进程(process)
自我出生,我就在磁盘中呆着,在shell调用execve函数运行我的时候,我才从0开始映射到了自己的虚拟内存,CPU为我分配了时间片来运行我,随着运行缺页,我被加载到了物理内存,当我运行完,shell进程便来回收我,我便回归为0
1.2 环境与工具
Intel® Core™ i5-8300 CPU 2.30GHz
Ubuntu 18.04
gcc,vim,readelf,objdump,gedit,gdb,code::block 16.01,edb……1.3 中间结果
hello.i预处理文件,hello预处理后得到的结果,为下一步汇编提供遍历
hello.s汇编代码文件,hello完成编译阶段后生成的汇编代码,机器级代码
hello.o可重定位目标文件,hello完成汇编阶段生成的二进制代码
hellodump.txt hello使用objdump反汇编后得到的汇编代码
helloelf.txt hello.o使用readelf得到的结果
helloelf_e.txt hello 使用readelf得到的结果
1.4 本章小结
本章是hello的简介,简要介绍了Hello的p2p,o2o过程,并且展示了hello运行环境,处理hello使用的工具,以及中间的结果等
(第1章0.5分)
第2章预处理
#2.1 预处理的概念与作用
预处理指的是C语言程序在编译之前所做的工作,是C语言程序编译过程中的重要的一步
预处理的作用作用
删除“#define”并展开所定义的宏
处理所有条件预编译指令,如#ifdef, #ifndef,#endif等
插入头文件到“#include”处,可以递归插入
删除所有注释
添加行号和文件名标识,以便编译时编译器产生调试用的行号信息
保留所有#pragma编译指令
预处理作用:
将.c文件转化成.i文件,方便后续编译器的工作(代码的转化)。
2.2在Ubuntu下预处理的命令
cpp hello.c -o hello.i
gcc -E hello.c -o hello.i
2.3 Hello的预处理结果解析
删除“#define”并展开所定义的宏
处理所有条件预编译指令,如#ifdef, #ifndef,#endif等
插入头文件到“#include”处,可以递归插入
删除所有注释
添加行号和文件名标识,以便编译时编译器产生调试用的行号信息
对hello.c进行预处理,将stdio.h,unistd.h,stdlib.h三个头文件插入到#include处
处理后得到的代码行数暴增,但是仍然是可读的C代码。
2.4 本章小结 预处理阶段将hello.c处理成hello.i文件,对程序进行进一步的简化,便于后续程序的运行
(第2章0.5分)
第3章编译
3.1 编译的概念与作用
编译:将预处理文件后的文件(如hello.i)进行词法分析,语法分析,语义分析,和优化,生成汇编代码文件的过程
作用:将.i文件转换成.s文件,.s文件是机器级代码,经过简单的处理就可以转化成机器可以执行的代码
3.2 在Ubuntu下编译的命令
gcc -S hello. -o hello.s
3.3 Hello的编译结果解析
3.3.1常量和常量表达式
C语言中的常量和常量表达式在处理后直接放入代码段中或数据段中。如果是全局初始化的常量(不含0)会被放入数据段,函数中的常量会体现在代码段中
const常量如果是在全局范围内定义的将会放入.rodata
3.3.2全局变量
C语言中的全局变量和静态变量放在数据段中,在汇编码中可以看到.data
3.3.3局部变量
C语言中的局部变量在栈中管理,具体行为在汇编代码中体现
3.3.4类型
在汇编语言中不存在数据类型,在转化为汇编代码的过程中已经做好处理,具体可以按照指令中的b w l q确定操作数的大小其中,b代表1Byte,w代表一个字(2Byte),l代表双字(4Byte)q代表四字(8Byte)
3.3.5宏
C语言的宏在预处理阶段就已经进行了替换,不在编译阶段处理
3.3.6赋值表达式
一般用mov指令实现,具体有movb,movw,movl,movq指令,分别传送一Byte,一字,二字,四字
3.3.7逗号
定义中的逗号代表定义多个类型相同的符号
表达式间当顺序点用,结合顺序是从左至右,用来顺序求值,完毕之后整个表达式的值是最后一个表达式的值。
在汇编的层面上是多条汇编指令分别处理
3.3.8赋初值
不赋初值的全局变量当做0处理,赋初值的变量有初值,栈中为赋初值的局部变量的值不确定
3.3.9类型转换
隐式类型转换:在运算的过程中要向上进行类型转换
显示类型转换:程序员明确指明要进行类型转换
显示类型转换和隐式类型转换的过程都没有真正的进行类型转换,因为汇编中是没有类型的概念的,只是编译器在这里要按照显示规定的类型或隐式要求的类型进行处理,生成的汇编代码的根据是转换后的类型
在汇编代码里的一些条件传送指令可以支持扩展,如movzbl,movslq,z代表0扩展,s代表符号扩展,有些转换可以通过这些扩展实现。另movl指令对低位赋值的时候,高位会自动清为0
3.3.10算数操作
在C语言代码中,一元运算符++,–,-,+是右结合的,其它是左结合的,且括号优先级最大,然后是%*/,最后是±,
在汇编代码中,通过 inc,dec,neg,not,add,sub,imul,xor,or,and,sal,shl,sar,shr等等指令完成这一过程
3.3.11逻辑运算
C语言中逻辑运算是短路运算
从左向右运算,遇到条件符合的停止运算
!运算只对0作用,其它均返回0
3.3.12位运算
位运算有直接对应的汇编指令,如AND,OR,NOT,XOR,SAL,SHL,SAR,SHR
3.3.13复合操作
复合操作在这个过程中被拆解,拆解成多个普通的操作
3.3.14关系操作
关系操作如果与控制转移语句一起使用,一般由test,cmp指令与jx指令联合使用进行处理
3.3.15数组
数组可能是全局的或局部的,全局的在.data中分配相应的空间,局部的在栈上分配空间.数组操作的寻址方式一般采用的是比例变址寻址法。即I(rA,rB,d)得到的地址为rA + d * rB + I
3.3.16指针
指针一般存放的是虚拟地址的值,&指令是取变量的虚拟地址,*变量是取虚拟地址中对应的值指针可以通过取地址实现
3.3.17结构操作
用户自己定义的结构只是被看成了连续的空间,不过这里有对齐的需求,每个元素按其大小对齐,结构整体按最大元素对齐,寻址方式和数组类似。
3.3.18控制转移
由jx指令组成,有jmp无条件跳转,还有ja,jae,jb,jbe,jnbe,j,ae,jg,jl,jge,jle,jnge,jnle,je,jne等等指令组成
3.3.19函数操作
参数传递:%rdi,%rsi,%rdx,%rcx,%r8,%r9分别放置前6个参数,其余放在栈里,7~n由低地址到高地址排列
函数调用call,先将返回地址入栈,PC相对寻址,将PC的值指向调用函数的地址,调用后,压栈旧的%rbp
函数返回return,取出%rbp,根据返回地址跳转返回,%rax用来存放函数的返回值
3.4 本章小结
本章讲述了hello在编译的过程中对其各种数据与操作进行处理的策略与方法
(第3章2分)
第4章汇编
4.1 汇编的概念与作用
概念:汇编是将.s文件转化为二进制机器指令的过程
作用:汇编将.s代码转化为机器可以直接处理的机器指令以便后序链接生成可执行文件。
4.2 在Ubuntu下汇编的命令
gcc -c hello.s -o hello.o
as hello.s -o hello.o
4.3 可重定位目标elf格式
ELF头:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
类别: ELF64
数据: 2补码,小端序 (little endian)
版本: 1 (current)
OS/ABI: UNIX - System V
ABI版本: 0
类型: REL (可重定位文件)
系统架构: Advanced Micro Devices X86-64
版本: 0x1
入口点地址:0x0
程序头起点:0 (bytes into file)
Start of section headers: 1152 (bytes into file)
标志:0x0
本头的大小:64 (字节)
程序头大小:0 (字节)
Number of program headers: 0
节头大小:64 (字节)
节头数量:13
字符串表索引节头:12
魔数的第2~4字符是ELF的ASCII码,说明了文件的类型,说明了其为二进制补码表示,小端法
ELF头中说明了节头表位置,每项大小,项数等信息,也列出了程序头的位置,每项大小,项数等信息。
节头:
[号] 名称 类型 地址 偏移量
大小全体大小旗标链接信息对齐
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000000000 00000040
0000000000000081 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 00000340
00000000000000c0 0000000000000018 I 10 1 8
[ 3] .data PROGBITS 0000000000000000 000000c4
0000000000000004 0000000000000000 WA 0 0 4
[ 4] .bss NOBITS 0000000000000000 000000c8
0000000000000000 0000000000000000 WA 0 0 1
[ 5] .rodata PROGBITS 0000000000000000 000000c8
000000000000002b 0000000000000000 A 0 0 1
[ 6] .comment PROGBITS 0000000000000000 000000f3
000000000000002c 0000000000000001 MS 0 0 1
[ 7] .note.GNU-stack PROGBITS 0000000000000000 0000011f
0000000000000000 0000000000000000 0 0 1
[ 8] .eh_frame PROGBITS 0000000000000000 00000120
0000000000000038 0000000000000000 A 0 0 8
[ 9] .rela.eh_frame RELA 0000000000000000 00000400
0000000000000018 0000000000000018 I 10 8 8,
[10] .symtab SYMTAB 0000000000000000 00000158
0000000000000198 0000000000000018 11 9 8
[11] .strtab STRTAB 0000000000000000 000002f0
000000000000004d 0000000000000000 0 0 1
[12] .shstrtab STRTAB 0000000000000000 00000418
0000000000000061 0000000000000000 0 0 1
通过READELF工具,得到了各个节的名称,大小,类型,偏移等信息,注意由于还没有进行重定位,地址信息尚且无法确定
偏移量信息类型符号值符号名称+加数
000000000018 000500000002 R_X86_64_PC32 0000000000000000 .rodata - 4
00000000001d 000c00000004 R_X86_64_PLT32 0000000000000000 puts - 4
000000000027 000d00000004 R_X86_64_PLT32 0000000000000000 exit - 4
000000000050 000500000002 R_X86_64_PC32 0000000000000000 .rodata + 1a
00000000005a 000e00000004 R_X86_64_PLT32 0000000000000000 printf - 4
000000000060 000900000002 R_X86_64_PC32 0000000000000000 sleepsecs - 4
000000000067 000f00000004 R_X86_64_PLT32 0000000000000000 sleep - 4
000000000076 001000000004 R_X86_64_PLT32 0000000000000000 getchar - 4
如图为.rel.text中的信息,第一条偏移量是指的在.text节中的信息,类型是指重定位信息,名称是其在符号表中的名称,后边加数是重定位的过程中所要修改的信息,重定位的过程中需要根据符号表修改这里的部分信息,完成重定位后这个节将会被舍弃
4.4 Hello.o的结果解析
机器语言由二进制数构成,二进制数对应于指令和数据,在变长指令集中,二进制指令的长度不是固定的。每一句二进制指令和一句汇编指令构成一一对应的关系,机器语言中的操作数与汇编语言不一致,是由于采用了PC相对寻址,PC相对寻址中地址加上当前PC的值(下一条语句)就可以得到与汇编语言一样的结果
4.5 本章小结
本章讲述了汇编的过程与命令,使用readelf和objdump两种工具,对指汇编代码,机器代码,ELF格式进行了分析。
(第4章1分)
第5章链接
5.1 链接的概念与作用
链接是指将可重定位目标文件合并生成可执行目标文件的过程
作用:通过符号解析和重定位,将.o文件合并,形成可执行的目标文件代码
5.2 在Ubuntu下链接的命令
ld -o hello -dynamic-linker /lib64/ld-linux-x86-64.so.2 /usr/lib/x86_64-linux-gnu/crt1.o /usr/lib/x86_64-linux-gnu/crti.o hello.o /usr/lib/x86_64-linux-gnu/libc.so /usr/lib/x86_64-linux-gnu/crtn.o
5.3 可执行目标文件hello的格式
ELF 头:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
类别: ELF64
数据: 2 补码,小端序 (little endian)
版本: 1 (current)
OS/ABI: UNIX - System V
ABI 版本: 0
类型: EXEC (可执行文件)
系统架构: Advanced Micro Devices X86-64
版本: 0x1
入口点地址:0x400500
程序头起点:64 (bytes into file)
Start of section headers: 5928 (bytes into file)
标志:0x0
本头的大小:64 (字节)
程序头大小:56 (字节)
Number of program headers: 8
节头大小:64 (字节)
节头数量:25
字符串表索引节头:24
ELF头中,魔数列出了这个文件的基本信息,列出了节头表,程序头表的位置,表项数,表项大小等信息,还列出了目标文件类型。由于是可执行文件,ELF头中列出了入口点的地址。
节头:
[号] 名称 类型 地址 偏移量
大小全体大小旗标链接信息对齐
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 0000000000400200 00000200
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.ABI-tag NOTE 000000000040021c 0000021c
0000000000000020 0000000000000000 A 0 0 4
[ 3] .hash HASH 0000000000400240 00000240
0000000000000034 0000000000000004 A 5 0 8
[ 4] .gnu.hash GNU_HASH 0000000000400278 00000278
000000000000001c 0000000000000000 A 5 0 8
[ 5] .dynsym DYNSYM 0000000000400298 00000298
00000000000000c0 0000000000000018 A 6 1 8
[ 6] .dynstr STRTAB 0000000000400358 00000358
0000000000000057 0000000000000000 A 0 0 1
[ 7] .gnu.version VERSYM 00000000004003b0 000003b0
ps in th 0000000000000010 0000000000000002 A 5 0 2
[ 8] .gnu.version_r VERNEED 00000000004003c0 000003c0
0000000000000020 0000000000000000 A 6 1 8
[ 9] .rela.dyn RELA 00000000004003e0 000003e0
0000000000000030 0000000000000018 A 5 0 8
[10] .rela.plt RELA 0000000000400410 00000410
0000000000000078 0000000000000018 AI 5 19 8
[11] .init PROGBITS 0000000000400488 00000488
0000000000000017 0000000000000000 AX 0 0 4
[12] .plt PROGBITS 00000000004004a0 000004a0
0000000000000060 0000000000000010 AX 0 0 16
[13] .text PROGBITS 0000000000400500 00000500
0000000000000132 0000000000000000 AX 0 0 16
[14] .fini PROGBITS 0000000000400634 00000634
0000000000000009 0000000000000000 AX 0 0 4
[15] .rodata PROGBITS 0000000000400640 00000640
000000000000002f 0000000000000000 A 0 0 4
[16] .eh_frame PROGBITS 0000000000400670 00000670
00000000000000fc 0000000000000000 A 0 0 8
[17] .dynamic DYNAMIC 0000000000600e50 00000e50
00000000000001a0 0000000000000010 WA 6 0 8
[18] .got PROGBITS 0000000000600ff0 00000ff0
0000000000000010 0000000000000008 WA 0 0 8
[19] .got.plt PROGBITS 0000000000601000 00001000
0000000000000040 0000000000000008 WA 0 0 8
[20] .data PROGBITS 0000000000601040 00001040
0000000000000008 0000000000000000 WA 0 0 4
[21] .comment PROGBITS 0000000000000000 00001048
000000000000002b 0000000000000001 MS 0 0 1
[22] .symtab SYMTAB 0000000000000000 00001078
0000000000000498 0000000000000018 23 28 8
[23] .strtab STRTAB 0000000000000000 00001510
0000000000000150 0000000000000000 0 0 1
[24] .shstrtab STRTAB 0000000000000000 00001660
00000000000000c5 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
l (large), p (processor specific)
There are no section groups in this file.
节头表列出了各个节的信息,可以看到,多了一个.init节,少了.rel.text,.rel.data这两个节。
程序头:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040
0x00000000000001c0 0x00000000000001c0 R 0x8
INTERP 0x0000000000000200 0x0000000000400200 0x0000000000400200
0x000000000000001c 0x000000000000001c R 0x1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x000000000000076c 0x000000000000076c R E 0x200000
LOAD 0x0000000000000e50 0x0000000000600e50 0x0000000000600e50
0x00000000000001f8 0x00000000000001f8 RW 0x200000
DYNAMIC 0x0000000000000e50 0x0000000000600e50 0x0000000000600e50
0x00000000000001a0 0x00000000000001a0 RW 0x8
NOTE 0x000000000000021c 0x000000000040021c 0x000000000040021c
0x0000000000000020 0x0000000000000020 R 0x4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 0x10
GNU_RELRO 0x0000000000000e50 0x0000000000600e50 0x0000000000600e50
0x00000000000001b0 0x00000000000001b0 R 0x1
程序头表中,INTERP是用于动态链接的段,两个LOAD分别是代码段和数据段
Section to Segment mapping:
段节…
00
01 .interp
02 .interp .note.ABI-tag .hash .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame
03 .dynamic .got .got.plt .data
04 .dynamic
05 .note.ABI-tag
06
07 .dynamic .got
Dynamic section at offset 0xe50 contains 21 entries:
标记类型名称/值
0x0000000000000001 (NEEDED) 共享库:[libc.so.6]
0x000000000000000c (INIT) 0x400488
0x000000000000000d (FINI) 0x400634
0x0000000000000004 (HASH) 0x400240
0x000000006ffffef5 (GNU_HASH) 0x400278
0x0000000000000005 (STRTAB) 0x400358
0x0000000000000006 (SYMTAB) 0x400298
0x000000000000000a (STRSZ) 87 (bytes)
0x000000000000000b (SYMENT) 24 (bytes)
0x0000000000000015 (DEBUG) 0x0
0x0000000000000003 (PLTGOT) 0x601000
0x0000000000000002 (PLTRELSZ) 120 (bytes)
0x0000000000000014 (PLTREL) RELA
0x0000000000000017 (JMPREL) 0x400410
0x0000000000000007 (RELA) 0x4003e0
0x0000000000000008 (RELASZ) 48 (bytes)
0x0000000000000009 (RELAENT) 24 (bytes)
0x000000006ffffffe (VERNEED) 0x4003c0
0x000000006fffffff (VERNEEDNUM) 1
0x000000006ffffff0 (VERSYM) 0x4003b0
0x0000000000000000 (NULL) 0x0
重定位节’.rela.dyn’ at offset 0x3e0 contains 2 entries:
偏移量信息类型符号值符号名称+ 加数
000000600ff0 000300000006 R_X86_64_GLOB_DAT 0000000000000000 __libc_start_main@GLIBC_2.2.5 + 0
000000600ff8 000500000006 R_X86_64_GLOB_DAT 0000000000000000 gmon_start + 0
重定位节’.rela.plt’ at offset 0x410 contains 5 entries:
偏移量信息类型符号值符号名称+ 加数
000000601018 000100000007 R_X86_64_JUMP_SLO 0000000000000000 puts@GLIBC_2.2.5 + 0
000000601020 000200000007 R_X86_64_JUMP_SLO 0000000000000000 printf@GLIBC_2.2.5 + 0
000000601028 000400000007 R_X86_64_JUMP_SLO 0000000000000000 getchar@GLIBC_2.2.5 + 0
000000601030 000600000007 R_X86_64_JUMP_SLO 0000000000000000 exit@GLIBC_2.2.5 + 0
000000601038 000700000007 R_X86_64_JUMP_SLO 0000000000000000 sleep@GLIBC_2.2.5 + 0
The decoding of unwind sections for machine type Advanced Micro Devices X86-64 is not currently supported.
Symbol table ‘.dynsym’ contains 8 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5 (2)
2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2.5 (2)
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2)
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND getchar@GLIBC_2.2.5 (2)
5: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND exit@GLIBC_2.2.5 (2)
7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND sleep@GLIBC_2.2.5 (2)
Symbol table ‘.symtab’ contains 49 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000400200 0 SECTION LOCAL DEFAULT 1
2: 000000000040021c 0 SECTION LOCAL DEFAULT 2
3: 0000000000400240 0 SECTION LOCAL DEFAULT 3
4: 0000000000400278 0 SECTION LOCAL DEFAULT 4
5: 0000000000400298 0 SECTION LOCAL DEFAULT 5
6: 0000000000400358 0 SECTION LOCAL DEFAULT 6
7: 00000000004003b0 0 SECTION LOCAL DEFAULT 7
8: 00000000004003c0 0 SECTION LOCAL DEFAULT 8
9: 00000000004003e0 0 SECTION LOCAL DEFAULT 9
10: 0000000000400410 0 SECTION LOCAL DEFAULT 10
11: 0000000000400488 0 SECTION LOCAL DEFAULT 11
12: 00000000004004a0 0 SECTION LOCAL DEFAULT 12
13: 0000000000400500 0 SECTION LOCAL DEFAULT 13
14: 0000000000400634 0 SECTION LOCAL DEFAULT 14
15: 0000000000400640 0 SECTION LOCAL DEFAULT 15
16: 0000000000400670 0 SECTION LOCAL DEFAULT 16
17: 0000000000600e50 0 SECTION LOCAL DEFAULT 17
18: 0000000000600ff0 0 SECTION LOCAL DEFAULT 18
19: 0000000000601000 0 SECTION LOCAL DEFAULT 19
20: 0000000000601040 0 SECTION LOCAL DEFAULT 20
21: 0000000000000000 0 SECTION LOCAL DEFAULT 21
22: 0000000000000000 0 FILE LOCAL DEFAULT ABS hello.c
23: 0000000000000000 0 FILE LOCAL DEFAULT ABS
24: 0000000000600e50 0 NOTYPE LOCAL DEFAULT 17 __init_array_end
25: 0000000000600e50 0 OBJECT LOCAL DEFAULT 17 _DYNAMIC
26: 0000000000600e50 0 NOTYPE LOCAL DEFAULT 17 __init_array_start
27: 0000000000601000 0 OBJECT LOCAL DEFAULT 19 _GLOBAL_OFFSET_TABLE_
28: 0000000000400630 2 FUNC GLOBAL DEFAULT 13 __libc_csu_fini
29: 0000000000601040 0 NOTYPE WEAK DEFAULT 20 data_start
30: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@@GLIBC_2.2.5
31: 0000000000601044 4 OBJECT GLOBAL DEFAULT 20 sleepsecs
32: 0000000000601048 0 NOTYPE GLOBAL DEFAULT 20 _edata
33: 0000000000400634 0 FUNC GLOBAL DEFAULT 14 _fini
34: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@@GLIBC_2.2.5
35: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@@GLIBC_
36: 0000000000601040 0 NOTYPE GLOBAL DEFAULT 20 __data_start
37: 0000000000000000 0 FUNC GLOBAL DEFAULT UND getchar@@GLIBC_2.2.5
38: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
39: 0000000000400640 4 OBJECT GLOBAL DEFAULT 15 _IO_stdin_used
40: 00000000004005c0 101 FUNC GLOBAL DEFAULT 13 __libc_csu_init
41: 0000000000601048 0 NOTYPE GLOBAL DEFAULT 20 _end
42: 0000000000400530 2 FUNC GLOBAL HIDDEN 13 _dl_relocate_static_pie
43: 0000000000400500 43 FUNC GLOBAL DEFAULT 13 _start
44: 0000000000601048 0 NOTYPE GLOBAL DEFAULT 20 __bss_start
45: 0000000000400532 129 FUNC GLOBAL DEFAULT 13 main
46: 0000000000000000 0 FUNC GLOBAL DEFAULT UND exit@@GLIBC_2.2.5
47: 0000000000000000 0 FUNC GLOBAL DEFAULT UND sleep@@GLIBC_2.2.5
48: 0000000000400488 0 FUNC GLOBAL DEFAULT 11 _init
符号表中列出了程序中所有符号的信息,标明了符号的类型和全局/局部属性,给处所有符号的符号名和地址
Histogram for bucket list length (total of 3 buckets):
Length Number % of total Coverage
0 0 ( 0.0%)
1 0 ( 0.0%) 0.0%
2 2 ( 66.7%) 57.1%
3 1 ( 33.3%) 100.0%
Version symbols section ‘.gnu.version’ contains 8 entries:
地址:00000000004003b0 Offset: 0x0003b0 Link: 5 (.dynsym)
000: 0 (本地) 2 (GLIBC_2.2.5) 2 (GLIBC_2.2.5) 2 (GLIBC_2.2.5)
004: 2 (GLIBC_2.2.5) 0 (本地) 2 (GLIBC_2.2.5) 2 (GLIBC_2.2.5)
Version needs section ‘.gnu.version_r’ contains 1 entry:
地址:0x00000000004003c0 Offset: 0x0003c0 Link: 6 (.dynstr)
000000: Version: 1 文件:libc.so.6 计数:1
0x0010: Name: GLIBC_2.2.5 标志:无 版本:2
Displaying notes found in: .note.ABI-tag
所有者Data size Description
GNU 0x00000010 NT_GNU_ABI_TAG (ABI version tag)
OS: Linux, ABI: 3.2.0
5.4 hello的虚拟地址空间
代码段从0x400000开始
数据段从0x600e50开始
使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。
5.5 链接的重定位过程分析
链接过程首先进行符号解析,将每个符号引用与一个符号定义唯一的关联起来,然后进行重定位过程,将hello.o与库的代码段和数据段合并,给每个定义的符号唯一确定的地址,利用重定位算法修改符号引用处的值
符号引用的值在重定位条目中有所体现,具体过程根据一下重定位算法
foreach section s
{
foreach relocation entry r
{
refptr = s+r.offset;
if(r.type == R_X86_64_PC32)
{
refaddr = ADDR(s)+r.offset;
*refptr = ADDR(r.symbol) + r.addend - refaddr;
}
if(r.type == R_X86_64_32)
*refptr = ADDR(r.symbol) + r.addend;
}
}
即:
链接过程:
符号解析:
将每一个符号引用与唯一的符号定义关联起来
重定位:
1)将所有相同的节合并,形成一个大的数据段,代码段
[if !supportLists]2) [endif]确定所有符号的地址
[if !supportLists]3) [endif]把所有符号引用处的地址填入适当的内容
5.6 hello的执行流程
ld-2.27.so! dl_start0x7ffee5aca680
ld-2.27.so!dl_init0x7f9f48629630
hello!_start0x0x400500
ld-2.27.so!_libc_start_main0x7f9f48249ab0
libc-2.27.so! cxa_atexit0x7f4523fd6af7
libc-2.27.so!lll_look_wait_private0x7f4523ff8471
libc-2.27.so!_new_exitfn0x7f87ff534220
hello!_libc_csu_init0x7f87ff512b26
libc-2.27.so!_sigsetjmp0x7f87ff52fc12
libc-2.27.so!__sigjmp_save0x7f87ff52fbc3
hello_main0x400532
exit@plt0x4004e0hello!
printf@plt0x400587hello!
sleep@plt0x400594hello!
getchar@plt0x4005a3
dl_runtime_resolve0x7f169ad84750
libc-2.27.so!exit0x7fce8c889128
5.7 Hello的动态链接分析
动态链接是由PLT和GOT合作完成的,GOT原来指向的是PLT对应表项的下一行的地址,具体行为是将调用的号码压栈,跳到PLT[0].PLT[0]将GOT[1]压栈,调用GOT2,这样控制交给了动态链接器,动态链接器完成链接后会修改相应的GOT表项,修改后再次调用是通过GOT表项可直接跳到链接后对应的地址。
首先,找到PLT段的内容,根据节头表可以确定其地址为0x4004a0
每个PLT表项指向一个GOT表项,在动态链接的过程中,PLT表项不会发生变化,发生改变的将是GOT表项,GOT表项原来指向的就是PLT表项的下一句,在发生动态链接后,指向的将是链接后函数的地址
5.8 本章小结
本章主要讲述了链接的过程,包括链接的概念,过程,重定位过程分析,虚拟地址空间的分析,执行流程的分析,动态链接过程的分析等
(第5章1分)
第6章hello进程管理
6.1 进程的概念与作用
概念:一个执行中程序的实例
作用:可以使一个CPU通过将时间分成时间片,并发完成不同任务,从而实现同时运行多个进程;使内存管理更加高效,便于内存共享等
(以下格式自行编排,编辑时删除)
6.2 简述壳Shell-bash的作用与处理流程
shell进行操作系统与用户之间的交互,解释用户的命令,将命令翻译后交由操作系统执行,
首先,当shell程序开始运行,打印shell前置的信息,等待用户输入命令
用户输入命令后,shell程序解析命令
首先判断用户输入的是不是内置命令,如果是内置命令,则直接调用相关处理函数进行处理
如果不是内置命令,那么认为用户要运行一个程序,首先解析命令行,获取用户所需要的程序,获取待创建的进程是进程还是后台进程,如果程序不存在,则报错并返回,如果程序存在,阻塞sigchld,sigint,sigtstp信号,调用fork函数,子进程调用execve函数,shell阻塞所有信号,将其加入到任务数组中,如果是前台进程,shell要等待其结束,如果是后台进程,shell不等待,直接进入下一轮循环
6.3 Hello的fork进程创建过程
当在命令行中输入./hello时,shell发现它不是内置命令,于是利用fork函数创建一个子进程。fork调用一次,返回两次,也就是说产生了一个子进程,内核为新进程创建各种数据结构,并给它分配一个唯一的pid,创建了当前进mm_struct,区域结构和页表的原样副本,将两个进程中每一个页面都标为只读,两个进程中每一个结构都标为写时复制。fork函数在父进程中返回PID,子进程中返回0
6.4 Hello的execve过程
调用execve函数时,首先清空原来的信息,删除当前进程虚拟地址的用户部分中已存在的数据结构,根据执行的程序创建新的数据结构。所有区域都是私有的、写时复制的,代码、数据区域映射到a.out文件中的.text,.data,bss映射到匿名文件,堆栈请求二进制0,初始长度为0。对于共享库区域,动态链接后映射,最后,设置当前上下文的程序计数器,指向当前进程代码段的入口处,CPU分配时间片给此进程,开始执行
6.5 Hello的进程执行
上下文:进程正确运行所需的状态,包括存放在内存中的程序代码和数据,它的栈,通用目的寄存器,程序计数器,环境变量,以及打开的文件描述符的集合
进程时间片:进程运行的一段时间
进程调度:进程被分配时间片
用户态,内核态:处理器通常用某个控制寄存器的一个模式位来提供用户模式和内核模式的功能。设置了模式位时,进程就运行在内核模式中,该进程可以执行指令集中的任何指令,可以访问系统中的任何内存位置。没有设置模式位时,进程就运行在用户模式中,用户模式中的进程不允许执行特权指令。
进程运行时,要抢占上一个进程的运行。操作系统进入内核态,检查有无待处理信号,然后CPU给进程分配时间片,就说CPU调度了hello这个进程。此时加载Hello的上下文,包括恢复其各种寄存器,PC等,然后进入用户态,hello进程开始执行
6.6 hello的异常与信号处理
类别原因同步/异步返回行为备注
中断来自外部I/O设备的信号异步
下一条
处理器芯片引脚信号,总线异常号
陷阱有意的异常
同步
系统调用
故障潜在的可恢复错误
本条
终止不可恢复错误不返回
信号有操作系统函数(内核代码)处理,如果用户绑定了信号处理代码,那么将由用户自定义的信号处理函数处理
随便乱按,不会产生异常
ctrl+c进程收到sigint信号,终止,由shell回收
ctrl+z进程收到sigtstp信号,停止,在后台
ps可以查看进程的信息
jobs可以查看作业
pstree可以查看所有进程的进程树
fg可以让后台进程到前台运行kill可以给进程发指定的信号 kill -9 发的是sigkill 信号
(以下格式自行编排,编辑时删除)
hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。
6.7本章小结
本章介绍的进程有关的概念,给出了进程的创建,执行,异常等机制
(第6章1分)
第7章hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:Intel为了兼容,保留了IA-32的段式管理方式。逻辑地址是由一个段标识符加上一个指定段内相对地址的偏移量组成的,表示为段标识符:段内偏移量.
线性地址:线性地址指的是连续的地址空间组成的地址
虚拟地址:虚拟地址只的是虚拟内存中的地址,从程序员的视角看到的都是虚拟地址
物理地址:物理地址指的是在主存中的真实地址,一个页只有被加载到主存中,才会有物理地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
一个逻辑地址由两部分组成,段标识符和段内偏移量
虚拟内存可以分成很多的段,每个段都有段描述符,这个描述符是记录在段描述符表里边的,段描述符记录了段的起始地址。
段描述符表有全局描述符表和局部描述符表之分,而段基址在全局描述符表中还是局部描述符表中是由段标示符中的一个字段(一位)确定的。
首先我们通过段标示符中的确定在全局描述符表还是局部描述符表的那一个字段,确定这个地址所对应的段信息是在全局描述符表中还是局部描述符表中
然后根据相应的寄存器得到表的基地址,根据偏移量确定表项,在表项中可以得到段的基地址,
然后根据段中的基地址加上相应的段内偏移量,就是线性地址。
7.3 Hello的线性地址到物理地址的变换-页式管理
虚拟内存相对与物理内存很大,虚拟内存分页,动态的载入到物理内存中。页表记录了虚拟页在不在物理内存中的信息。虚拟内存用4KB划分,每4KB为一页,物理内存也有同样的划分方式,即二者的页大小一定是一致的
有一个寄存器CR3,专门记录了页表的基址,每个进程有独立的页表,基址也就不一样,所以上下文切换的过程中CR3要动态更新。
得到虚拟地址后,根据虚拟地址的高位VPN到页表中寻找对应的项,如果页表项已经载入,那么直接得到对应的PPN与VPO拼接则得到物理地址;如果页表项没有载入,那么发生缺页故障,由缺页异常处理程序进行处理。
7.4 TLB与四级页表支持下的VA到PA的变换
VPN:虚拟页号
VPO:虚拟页面偏移
PPN:物理页号
PPO:物理页面偏移
在地址翻译的过程中,VPN和PPN代表的内容是相同的。
首先将VA分成VPN和VPO,用VPN到TLB中找对应的表项,过程类似于高速缓存的过程。如果TLB命中,可以直接得到PPN,如果TLB不命中,则以VPN作为索引到页表中取相应的项,将VPN分成四个部分,分别对应四级页表的索引,前边几级得到的是下一级页表的索引,最后一级得到的是相应的PPN。如果任意一级页表中有效位为0,那么这个页都不在物理内存中,此时,要交由缺页异常处理程序处理,修改页表对应的值,重新执行导致缺页异常的指令
7.5 三级Cache支持下的物理内存访问
首先,通过页表的查询,由VA可以得到PA,然后就可以通过PA访问cache,先访问L1cache,首先将PA分成CT CI CO 三个部分,通过CI找到对应的组,然后通过CT和cache中的tag匹配寻找相应的项,如果命中,则可以以CO作为偏移得到数据,如果不命中,则继续向L2L3cache发出请求,如果cache都不命中就到主存中请求相应数据
7.6 hello进程fork时的内存映射
当调用fork()时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID。为了给这个新进程创建虚拟内存,它创建了当前程序的mm_struct,区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中每个区域结构都标记为只读、私有的写时复制。当发生写的时候,会引起相应的保护故障,有操作系统函数来进行复制,并修改权限,此时再写就可以不会引发故障,这样的机制节省了宝贵的物理内存资源
7.7 hello进程execve时的内存映射
(1)删除已存在的用户区域。即当前虚拟地址用户部分已存在的结构
(2)映射私有区域。为新程序代码,数据,和堆栈区域创建新的区域结构。
(3)映射共享区域。如果与共享对象链接比如libc.so,那么这些对象都是动态链接到这个程序的,然后在映射到用户虚拟地址的共享区域。
(4)设置程序计数器。设置当前进程的PC,使之指向代码区域的入口。
7.8 缺页故障与缺页中断处理
当页表中有效位为0,引发了缺页异常,此时触发了缺页异常处理程序。异常处理程序会选择一个牺牲页,如果这个牺牲页被修改过,那么就将这个页写回内存,然后换入新的页面,更新页表中相应的内容。CPU重新运行相应的代码,就不会发生缺页异常了
7.9动态存储分配管理
动态内存分配是由内存分配器实现的。内存分配器维护着堆区域。内核维护着一个变量brk指向堆顶。分配器把堆视为一组大小不相等的块的集合。每个块都是一个连续的虚拟内存空间,是已分配的或空闲的。已分配的块显示的保留为供应程序使用。空闲块可以用来分配。一个已分配的块保持已分配状态直到它被释放。
对于隐式空闲链表的实现,动态内存分配器有三种分配策略:首次适配,下一次适配和最佳适配。首次适配就是从头开始搜索,选择第一个合适的空闲块;下一次适配是从上一次链表查询结束的地方开始搜索;最佳适配是搜索一遍找到最合适的块,时间代价比较高
7.10本章小结
本章介绍了hello的内存管理,包括地址空间、段页式存储、cache访问、内存映射、缺页故障处理、动态内存分配等内容
(第7章 2分)
第8章hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件
在Linux系统中,所有对I/O设备的访问都抽象为文件操作。文件包括普通文件、目录和套接字。
设备管理:unix io接口
Linux内核引出一个简单、低级的应用接口,叫Unix I/O
打开文件:一个应用程序通过要求内核打开相应的文件,来宣告想要访问一个I/O设备。内核返回一个小的非负整数,叫做描述符,它在后序对此文件的所有操作中标示这个文件,内核记录有关这个文件的打开信息。Linux shell创建的进程每个进程开始时都有三个打开的文件:标准输入、标准输出、标准错误。
读写文件:一个读操作就是从文件复制个n个字节到内存,从当前文件位置k开始,然后将k增加到k+n。写操作就是从内存复制n>0个字节到一个文件,从当前文件位置k开始,然后更新k。
关闭文件:当应用完成了对文件的访问之后,它就通知内核关闭这个文件
8.2 简述Unix IO接口及其函数
Unix I/O是简单、低级的应用接口,使得所有输入和输出都能通过统一且一致的方式进行。
int open(char *filename,int flags,mode_t mode);
int close(int fd);
ssize_t read(int fd,void *buf,size_t n);
ssize_t write(int fd,const void *buf,size_t n);
off_t lseek(int fd, off_t offset , int whence)
8.2.1 open
int open(char *filename,int flags,mode_t mode);
open函数将filename转换为一个文件描述符,并且返回描述符的数字,返回的描述符总是当前没有打开的最小描述符。flags参数指明了进程打算如何访问这个文件,mode参数指明了访问权限位
8.2.2 close
int close(int fd);
关闭一个打开的文件,fd是文件描述符
8.2.3 read
ssize_t read(int fd,void *buf,size_t n);
read函数从描述符为fd的当前文件位置复制最多n个字节到内存位置buf,返回值为-1表示错误,返回值为0表示EOF,否则,返回值表示的是实际的传送字节数量
8.2.4 write
ssize_t write(int fd,const void *buf,size_t n);
write函数从内存位置buf复制至多n个字节到描述符fd的当前位置
8.3 printf的实现分析
int printf(const char *fmt, ...)
{
int i;
char buf[256];
va_list arg = (va_list)((char*)(&fmt) + 4);
i = vsprintf(buf, fmt, arg); //返回字符串的长度
write(buf, i); //写我们的字符串
return i;
}
解析
va_list arg = (va_list)((char*)(&fmt) + 4);
由于printf函数的参数位置不确定,我们要根据格式串确定参数数量,这句语句实际上是在确定参数的地址
vsprintf(buf, fmt, arg);
vsprintf的作用就是格式化。它接受确定输出格式的格式字符串fmt。用格式字符串对个数变化的参数进行格式化,产生格式化输出。
write(buf, i);
i是返回字符串的长度,这里是要将buf中的字符串写向终端。write是系统函数,这里使用int 0x80或syscall陷入内核
打印的过程是由字符串显示子程序运行的。字符显示驱动子程序从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
getchar函数的源代码大致如下
int getchar(void)
{
static char buf[BUFSIZ];
static char* bb=buf;
static int n=0;
if(n==0)
{
n=read(0,buf,BUFSIZ);
bb=buf;
}
return(--n>=0)?(unsigned char)*bb++:EOF;
}
用户从键盘输入后,通过CPU的引脚信号,发现键盘处的异常(异步异常)。当发现用户输入的符号是Enter的时候,getchar函数将调用系统函数read将用户输入的内容全部读到缓冲区中,并只从缓冲区中读一个元素,如果发现缓冲区中为空,则返回EOF。
8.5本章小结
本章介绍了Linux I/O管理和printf,getchar的实现方法。
(以下格式自行编排,编辑时删除)
(第8章1分)
结论
hello被程序员编写后,经过预处理、编译、汇编、链接阶段后成为程序,在用户命令下由shell程序fork并execve后成为进程。在预处理阶段,其所有的宏被替换;编译阶段,被转化成汇编指令,汇编阶段转化成机器码,链接阶段与静态库链接形成可执行程序,成为进程后,有独自的虚拟地址空间,动态链接为其链接动态库,其代码和数据在虚拟内存的机制下在物理内存中换入换出,并有cache为其寻址提供缓存。Linux的文件机制为其提供简单的I/O,运行的最后,shell提供回收机制将其回收。
(结论0分,缺失 -1分,根据内容酌情加分)
附件
列出所有的中间产物的文件名,并予以说明起作用。
hello.i预处理文件,hello预处理后得到的结果,为下一步汇编提供遍历
hello.s汇编代码文件,hello完成编译阶段后生成的汇编代码,机器级代码
hello.o可重定位目标文件,hello完成汇编阶段生成的二进制代码
hellodump.txt hello使用objdump反汇编后得到的汇编代码
helloelf.txt hello.o使用readelf得到的结果
helloelf_e.txt hello 使用readelf得到的结果
(附件0分,缺失 -1分)
参考文献
为完成本次大作业你翻阅的书籍与网站等
[1] Randall E.Bryant, David R. O’Hallaron Computer System A programer’s Perspective Third Edition ISBN 978-7-111-54493-7[2016]
[2] 袁春风计算机系统基础机械工业出版社2014.5
[3] https://blog.csdn.net/weixin_33868027/article/details/93492905
[4] https://www.cnblogs.com/edver/p/8419807.html
[5] https://www.cnblogs.com/pianist/p/3315801.html