程序人生P2P

计算机系统

大作业

题 目 程序人生-Hello’s P2P
专 业 计算机科学与技术
学   号 1172100222
班   级 1703007
学 生 陈泊翰    
指 导 教 师 郑贵滨

计算机科学与技术学院
2018年12月
摘 要
Hlloe world的一生由hello.c文件开始,经过被gcc整合的功能块cpp(预处理器),ccl(编译器),as(汇编器)之后变为可重定位的目标文件,再经由ld(链接器)的符号解析和重定位之后成功变为可执行目标文件。
我们在linux终端shell程序中输入./hello就会让shell程序解析我们输入的命令是否为内置命令,不是的话就为hello fork新进程,并领用execve来将可重定位目标文件载入,分配虚拟内存,在mmap的区域链表中依次将各区域链表指向文件中相应相应的段。之后根据程序入口进入main程序,将hello world的字符串输出到I/O文件的屏幕上,程序结束后父进程用waitpid来回收hello进程,删除在内存中为其储存的内存结构(例如task_struct任务条目,上下文的内容,文件的描述附表)

关键词:预处理;编译;汇编;链接,进程,虚拟内存

(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)

目 录

第1章 概述 - 4 -
1.1 HELLO简介 - 4 -
1.2 环境与工具 - 4 -
1.3 中间结果 - 4 -
1.4 本章小结 - 4 -
第2章 预处理 - 6 -
2.1 预处理的概念与作用 - 6 -
2.2在UBUNTU下预处理的命令 - 6 -
2.3 HELLO的预处理结果解析 - 6 -
2.4 本章小结 - 7 -
第3章 编译 - 9 -
3.1 编译的概念与作用 - 9 -
3.2 在UBUNTU下编译的命令 - 9 -
3.3 HELLO的编译结果解析 - 9 -
3.4 本章小结 - 15 -
第4章 汇编 - 16 -
4.1 汇编的概念与作用 - 16 -
4.2 在UBUNTU下汇编的命令 - 16 -
4.3 可重定位目标ELF格式 - 16 -
4.4 HELLO.O的结果解析 - 20 -
4.5 本章小结 - 21 -
第5章 链接 - 23 -
5.1 链接的概念与作用 - 23 -
5.2 在UBUNTU下链接的命令 - 23 -
5.3 可执行目标文件HELLO的格式 - 23 -
5.4 HELLO的虚拟地址空间 - 24 -
5.5 链接的重定位过程分析 - 25 -
5.6 HELLO的执行流程 - 27 -
5.7 HELLO的动态链接分析 - 28 -
5.8 本章小结 - 28 -
第6章 HELLO进程管理 - 33 -
6.1 进程的概念与作用 - 33 -
6.2 简述壳SHELL-BASH的作用与处理流程 - 33 -
6.3 HELLO的FORK进程创建过程 - 33 -
6.4 HELLO的EXECVE过程 - 36 -
6.5 HELLO的进程执行 - 37 -
6.6 HELLO的异常与信号处理 - 37 -
6.7本章小结 - 38 -
第7章 HELLO的存储管理 - 41 -
7.1 HELLO的存储器地址空间 - 41 -
7.2 INTEL逻辑地址到线性地址的变换-段式管理 - 41 -
7.3 HELLO的线性地址到物理地址的变换-页式管理 - 42 -
7.4 TLB与四级页表支持下的VA到PA的变换 - 46 -
7.5 三级CACHE支持下的物理内存访问 - 51 -
7.6 HELLO进程FORK时的内存映射 - 52 -
7.7 HELLO进程EXECVE时的内存映射 - 52 -
7.8 缺页故障与缺页中断处理 - 53 -
7.9动态存储分配管理 - 53 -
7.10本章小结 - 55 -
第8章 HELLO的IO管理 - 57 -
8.1 LINUX的IO设备管理方法 - 57 -
8.2 简述UNIX IO接口及其函数 - 57 -
8.3 PRINTF的实现分析 - 57 -
8.4 GETCHAR的实现分析 - 58 -
8.5本章小结 - 61 -
结论 - 61 -
附件 - 63 -
参考文献 - 64 -

第1章 概述
1.1 Hello简介
Hlloe world的一生由hello.c文件开始,经过被gcc整合的功能块cpp(预处理器),ccl(编译器),as(汇编器)之后变为可重定位的目标文件,再经由ld(链接器)的符号解析和重定位之后成功变为可执行目标文件。
我们在linux终端shell程序中输入./hello就会让shell程序解析我们输入的命令是否为内置命令,不是的话就为hello fork新进程,并领用execve来将可重定位目标文件载入,分配虚拟内存,在mmap的区域链表中依次将各区域链表指向文件中相应相应的段。之后根据程序入口进入main程序,将hello world的字符串输出到I/O文件的屏幕上,程序结束后父进程用waitpid来回收hello进程,删除在内存中为其储存的内存结构(例如task_struct任务条目,上下文的内容,文件的描述附表)
1.2 环境与工具
1.2.1 硬件环境
X64 CPU;2.50GHz;8G RAM;256GHD Disk
1.2.2 软件环境
Windows10 64位;VirtualBox;Ubuntu 18.04
1.2.3 开发工具
Visual Studio 2017 64位;CodeBlocks;vim,gpedit+gcc,as,ld,edb,readelf,HexEdit
1.3 中间结果
hello.i 预处理之后文本文件
hello.s 编译之后的汇编文件
hello.o 汇编之后的可重定位目标执行
hello 链接之后的可执行目标文件
hello2.c 测试程序代码
hello2 测试程序
hello.objdmp Hello.o的反汇编代码
hello.elf Hello.o的ELF格式
hello.objdmp Hello的反汇编代码
hello.elf Hellode ELF格式
tmp.txt 存放临时数据
1.4 本章小结
本章简要的概括了hello world一生的两个阶段:p2p(From Program to Process)和020( From Zero-0 to Zero-0)
(第1章0.5分)

第2章 预处理
2.1 预处理的概念与作用
概念:预处理器cpp根据以字符#开头的命令(宏定义、条件编译),修改原始的C程序,将引用的所有库展开合并成为一个完整的文本文件。
作用:
处理源文件中以“#”开头的预编译指令,包括:
– 删除“#define”并展开所定义的宏
– 处理所有条件预编译指令,如“#if”,“#ifdef”, “#endif”等
– 插入头文件到“#include”处,可以递归方式进行处理
– 删除所有的注释“//”和“/* */”
– 添加行号和文件名标识,以便编译时编译器产生调试用的行号信息
– 保留所有#pragma编译指令(编译器需要用)
2.2在Ubuntu下预处理的命令
– $gcc –E hello.c –o hello.i
– $cpp hello.c > hello.i

2.3 Hello的预处理结果解析

预处理器cpp会根据默认的目录地址来搜索文件中的三个头文件

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
之后将他们插入到hello.i之中,在头文件中将#define的宏定义删除并展开,删除所有的注释“//”和“/* */”等一系列上述的cpp会进行的#相关的工作
2.4 本章小结
本章介绍了预处理阶段时cpp会做些什么以及怎么利用gcc来实现预处理这一步骤,并利用hello.c预处理成为hello.i这一过程直观的感受了cpp的作用,和处理后的效果。

(第2章0.5分)

第3章 编译
3.1 编译的概念与作用
概念:编译过程就是将预处理后得到的预处理文件(如hello.i)进行词法分析、语法分析、语义分析、优化后,生成汇编代码文件
作用:
1.词法分析器,用于将字符串转化成内部的表示结构。
2.语法分析器,将词法分析得到的标记流(token)生成一棵语法树。
3.目标代码的生成,将语法树转化成目标代码。

3.2 在Ubuntu下编译的命令
– $gcc –S hello.i –o hello.s
– $gcc –S hello.c –o hello.s
– $/user/lib/gcc/i486-linux-gnu/4.1/cc1 hello.c

3.3 Hello的编译结果解析
3.3.0 汇编指令

指令 含义
.file 声明源文件
.text 以下是代码段
.section .rodata 以下是rodata节
.globl 声明一个全局变量
.type 用来指定是函数类型或是对象类型
.size 声明大小
.long、.string 声明一个long、string类型
.align 声明对指令或者数据的存放地址进行对齐的方式

3.3.1 数据
hello.s中用到的C数据类型有:整数、字符串、数组。
• 字符串
程序中的字符串分别是:

  1. “Usage: Hello 学号 姓名!\n”,第一个printf传入的输出格式化参数,在hello.s中声明如图3.2,可以发现字符串被编码成UTF-8格式,一个汉字在utf-8编码中占三个字节,一个\代表一个字节。

  2. “Hello %s %s\n”,第二个printf传入的输出格式化参数,在hello.s中声明如图3.2。
    其中后两个字符串都声明在了.rodata只读数据节。

    图3.2 hello.s中声明在.LC0和.LC1段中的字符串

• 整数
程序中涉及的整数有:

  1. int sleepsecs:sleepsecs在C程序中被声明为全局变量,且已经被赋值,编译器处理时在.data节声明该变量,.data节存放已经初始化的全局和静态C变量。在图3.3中,可以看到,编译器首先将sleepsecs在.text代码段中声明为全局变量,其次在.data段中,设置对齐方式为4、设置类型为对象、设置大小为4字节、设置为long类型其值为2(long类型在linux下与int相同为4B,将int声明为long应该是编译器偏好)。

    图3.3 hello.s中sleepsecs的声明

  2. int i:编译器将局部变量存储在寄存器或者栈空间中,在hello.s中编译器将i存储在栈上空间-4(%rbp)中,可以看出i占据了栈中的4B。

  3. int argc:作为第一个参数传入。

  4. 立即数:其他整形数据的出现都是以立即数的形式出现的,直接硬编码在汇编代码中。

• 数组
程序中涉及数组的是:char argv[] main,函数执行时输入的命令行,argv作为存放char指针的数组同时是第二个参数传入。
argv单个元素char
大小为8B,argv指针指向已经分配好的、一片存放着字符指针的连续空间,起始地址为argv,main函数中访问数组元素argv[1],argv[2]时,按照起始地址argv大小8B计算数据地址取数据,在hello.s中,使用两次(%rax)(两次rax分别为argv[1]和argv[2]的地址)取出其值。如图3.4。

图3.4 计算地址取出数组值
3.3.2 赋值
程序中涉及的赋值操作有:

  1. int sleepsecs=2.5 :因为sleepsecs是全局变量,所以直接在.data节中将sleepsecs声明为值2的long类型数据。
  2. i=0:整型数据的赋值使用mov指令完成,根据数据的大小不同使用不同后缀,分别为:
    指令 b w l q
    大小 8b (1B) 16b (2B) 32b (4B) 64b (8B)
    因为i是4B的int类型,所以使用movl进行赋值,汇编代码如图3.5。

图3.5 hello.s中变量i的赋值
3.3.3 类型转换
程序中涉及隐式类型转换的是:int sleepsecs=2.5,将浮点数类型的2.5转换为int类型。
当在double或float向int进行类型转换的时候,程序改变数值和位模式的原则是:值会向零舍入。例如1.999将被转换成1,-1.999将被转换成-1。进一步来讲,可能会产生值溢出的情况,与Intel兼容的微处理器指定位模式[10…000]为整数不确定值,一个浮点数到整数的转换,如果不能为该浮点数找到一个合适的整数近似值,就会产生一个整数不确定值。
浮点数默认类型为double,所以上述强制转化是double强制转化为int类型。遵从向零舍入的原则,将2.5舍入为2。
3.3.4算数操作
进行数据算数操作的汇编指令有:

指令 效果
leaq S,D D=&S
INC D D+=1
DEC D D-=1
NEG D D=-D
ADD S,D D=D+S
SUB S,D D=D-S
IMULQ S R[%rdx]:R[%rax]=SR[%rax](有符号)
MULQ S R[%rdx]:R[%rax]=S
R[%rax](无符号)
IDIVQ S R[%rdx]=R[%rdx]:R[%rax] mod S(有符号)
R[%rax]=R[%rdx]:R[%rax] div S
DIVQ S R[%rdx]=R[%rdx]:R[%rax] mod S(无符号)
R[%rax]=R[%rdx]:R[%rax] div S

   程序中涉及的算数操作有:
  1. i++,对计数器i自增,使用程序指令addl,后缀l代表操作数是一个4B大小的数据。

  2. 汇编中使用leaq .LC1(%rip),%rdi,使用了加载有效地址指令leaq计算LC1的段地址%rip+.LC1并传递给%rdi。
    3.3.5 关系操作
    进行关系操作的汇编指令有:
    指令 效果 描述
    CMP S1,S2 S2-S1 比较-设置条件码
    TEST S1,S2 S1&S2 测试-设置条件码
    SET** D D=** 按照将条件码设置D
    J
    —— 根据**与条件码进行跳转

    程序中涉及的关系运算为:

  3. argc!=3:判断argc不等于3。hello.s中使用cmpl $3,-20(%rbp),计算argc-3然后设置条件码,为下一步je利用条件码进行跳转作准备。

  4. i<10:判断i小于10。hello.s中使用cmpl $9,-4(%rbp),计算i-9然后设置条件码,为下一步jle利用条件码进行跳转做准备。
    3.3.6 控制转移
    程序中涉及的控制转移有:

  5. if (argv!=3):当argv不等于3的时候执行程序段中的代码。如图3.6,对于if判断,编译器使用跳转指令实现,首先cmpl比较argv和3,设置条件码,使用je判断ZF标志位,如果为0,说明argv-3=0 argv==3,则不执行if中的代码直接跳转到.L2,否则顺序执行下一条语句,即执行if中的代码。

    图3.6 if语句的编译

  6. for(i=0;i<10;i++):使用计数变量i循环10次。如图3.7,编译器的编译逻辑是,首先无条件跳转到位于循环体.L4之后的比较代码,使用cmpl进行比较,如果i<=9,则跳入.L4 for循环体执行,否则说明循环结束,顺序执行for之后的逻辑。

    图3.7 for循环的编译
    3.3.7 函数操作
    函数是一种过程,过程提供了一种封装代码的方式,用一组指定的参数和可选的返回值实现某种功能。P中调用函数Q包含以下动作:

  7. 传递控制:进行过程Q的时候,程序计数器必须设置为Q的代码的起始地址,然后在返回时,要把程序计数器设置为P中调用Q后面那条指令的地址。

  8. 传递数据:P必须能够向Q提供一个或多个参数,Q必须能够向P中返回一个值。

  9. 分配和释放内存:在开始时,Q可能需要为局部变量分配空间,而在返回前,又必须释放这些空间。

64位程序参数存储顺序(浮点数使用xmm,不包含):
1 2 3 4 5 6 7
%rdi %rsi %rdx %rcx %r8 %r9 栈空间

程序中涉及函数操作的有ÿ

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值