程序人生-Hello’s P2P
Posted Mikrokosmos72
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了程序人生-Hello’s P2P相关的知识,希望对你有一定的参考价值。
计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 计算机科学与技术
学 号 120L021927
班 级 2003006
学 生 李欣桐
指 导 教 师 吴锐
计算机科学与技术学院
2021年5月
本文对hello.c程序编写成功后在Linux下如何顺利运行做出了详细的分析。hello.c源程序经过预处理、编译、汇编、链接、进程管理、存储管理、IO管理一系列操作完成了自己的生命周期。而我们可以通过一些工具清晰地观察它的一生,如gdb,edb等。
关键词:预处理;编译;汇编;链接;进程;存储;I/O;Linux
(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)
目 录
第1章 概述
1.1 Hello简介
根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。
P2P:在编辑器中编写好程序创建hello.c源文件,经过cpp预处理生成hello.i文件,编译器ccl编译生成汇编文件hello.s,然后通过汇编器as生成二进制可重定位文件hello.o,最后链接器ld将hello.o与引用到的库函数组合起来生成二进制可执行文件hello。在shell中输入命令,shell通过fork函数创建了一个子进程,再调用execve映射虚拟内存,把程序加载到进程中,开始运行。程序运行结束时,父进程回收hello进程和它创建的子进程,操作系统内核删除相关数据结构。
020:shell为hello可执行程序进行执行,execve映射虚拟内存,进入程序入口后程序开始载入物理内存,然后进入main函数执行目标代码,CPU为运行的hello分配时间片执行逻辑控制流。程序运行结束后,shell回收进程,释放虚拟地址空间,删除有关进程上下文。
1.2 环境与工具
硬件环境:X64 CPU;2GHz;2G RAM;256GHD Disk 以上
软件环境:Windows7/10 64位以上;VirtualBox/Vmware 11以上;Ubuntu 16.04 LTS 64位/ 优麒麟 64位以上
开发与调试工具:vi/vim/gedit+gcc;gdb;objdump;edb;readelf
1.3 中间结果
文件名 | 文件作用 |
hello.c | 源程序 |
hello.i | 预处理后的文件 |
hello.s | 编译后的汇编文件 |
hello.o | 汇编之后的可重定位目标文件 |
hello | 链接后的可执行目标文件 |
hello.elf | hello.o的ELF格式 |
hello1.elf | hello的ELF格式 |
hello1.txt | hello.o的反汇编 |
hello2.txt | hello的反汇编 |
1.4 本章小结
本章概括地介绍了hello的一生,介绍了P2P、020的过程,同时介绍了实验进行时使用的软硬件环境,以及开发与调试工具。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
概念:预处理器(cpp)根据以字符#开头的命令,修改原始的C程序。
作用:将源文件中以”include”格式包含的文件复制到编译的源文件中;用实际值替换用“#define”定义的字符串;根据“#if”后面的条件决定需要编译的代码,得到另一个C程序.i。
2.2在Ubuntu下预处理的命令
命令:gcc -E hello.c -o hello.i
图2.2.1 Ubuntu预处理的命令
图2.2.2 预处理生成hello.i文件
2.3 Hello的预处理结果解析
图2.3.1 hello.i文件部分截图
图2.3.2 hello.i文件中main函数部分
打开预处理后生成的hello.i文件,我们可以观察到该文件共有3060行代码,较源程序内容大大增多,而源程序中的main函数出现在文件的最后部分,没有发生变化。这是因为在预处理的过程中预处理器对源程序的头文件#include <stdio.h>、#include <unistd.h> 、#include <stdlib.h>进行了展开,文件加入了许多宏定义,并且将其放在了文件前面。
2.4 本章小结
本章介绍了预处理的概念和作用,进行了在Ubuntu中预处理的操作,并浏览分析了生成的hello.i文件,通过代码分析了预处理的工作机制。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
概念:编辑器(ccl)将文本文件.i翻译成文本文件.s,它包含一个汇编语言程序。
作用:把源程序翻译成目标汇编程序,进行词法分析和语法分析,分析过程中发现有语法错误,给出提示信息(无法进行后续动作),是高级语言向机器代码转换中重要的中间过程,生成.s文件。
3.2 在Ubuntu下编译的命令
命令:gcc -S hello.i -o hello.s
图3.2.1 Ubuntu下编译的命令
图3.2.2 编译生成的hello.s文件
3.3 Hello的编译结果解析
此部分是重点,说明编译器是怎么处理C语言的各个数据类型以及各类操作的。应分3.3.1~ 3.3.x等按照类型和操作进行分析,只要hello.s中出现的属于大作业PPT中P4给出的参考C数据与操作,都应解析。
3.3.1 文件
图3.3.1.1 hello.s文件部分代码
.file 声明源文件
.text 代码段
.section .rodata 只读数据段
.align 声明指令或者数据的存放地址的对齐方式
.string 字符串
.global 全局变量
.type 声明一个标识符是函数类型还是数据类型
3.3.2 数据
一、整型常量
图3.3.2.1 常量的保存
if(argc!=4)语句中,整型常量4的值被保存在.text中,作为指令的一部分。同理,for(i=0;i<8;i++)中的整型常量0、8也被保存在.text中,如下图所示。
图3.3.2.2 常量的保存
二、字符串
图3.3.2.3程序中字符串
printf()、scanf()中的字符串被存储在.rodata节中,如上图所示。hello.c中唯一的数组是main函数的第二个参数,数组中每个元素都是指向字符类型的指针。数组两次被调用将参数传递给printf函数。
图3.3.2.4 两字符串作为printf函数的参数
三、局部变量
局部变量存储在寄存器或栈中。
该程序中,局部变量i储存在-4(%rbp)中。
图3.3.2.5 局部变量
3.3.3 赋值
赋值操作利用mov语句。如果是局部变量不赋初值,则在汇编代码中没有体现,只在使用并赋值时才用寄存器等来存储;如果是全局或静态变量变量不赋初值,存放在.bss段;如果是已初始化的全局变量,存放在.data段。
mov指令根据操作数的字节大小分为:
movb:一个字节
movw:字
movl:双字
movq:四字
例如i = 0赋值,如下图所示。
图3.3.3.1 赋值操作
3.3.4 算术操作
在循环中,程序使用了++操作符:for(i=0;i<8;i++),对应的汇编代码如下图所示。
图3.3.4.1 算数操作
3.3.5 关系操作
- 程序中argc!=4是条件判断语句,汇编代码为cmpl $4,-20(%rbp),比较后设置条件码,根据条件码判断是否跳转。
- 程序中i < 8是循环判断条件,汇编代码为cmpl $7, -4(%rbp),比较后设置条件码,根据条件码判断是否跳转。
详情如下图所示。
图3.3.5.1 argc!=4
图3.3.5.2 i < 8
3.3.6 数组/指针/结构操作
主函数main的参数中含有指针数组char *argv[],int main(int argc,char *argv[])。该数组中,argv[0]指向输入程序的路径和名称,argv[1]和argv[2]分别指向printf()、scanf()中的字符串。
数组地址存储在栈中,可以利用栈指针的偏移量来读取。
图3.3.6.1 栈指针偏移
3.3.7 控制转移
汇编语言中先设置条件码,然后根据条件码来进行控制转移。
- if(argc!=4),判断argc是否等于4,如果等于4,则不执行if语句,等于4则执行if语句,对应的汇编代码如下:
图3.3.7.1 if(argc!=4)
2.for(i=0;i<8;i++),每次判断i是否小于8来判断是否退出循环,对应的汇编代码如下:
图3.3.7.2 for(i=0;i<8;i++)
图3.3.7.3 for(i=0;i<8;i++)
3.3.8 函数操作
调用函数时,有以下几个过程。首先要将程序计数器(%rip)设为被调用函数代码的起始地址,在返回时需要将%rip设为调用函数中调用被调用函数后面那条指令的地址。之后要进行数据传递,调用函数向被调用函数提供参数,被调用函数返回相应的返回值给调用函数,同时还要分配和释放相应内存。
该程序中的函数操作:
函数 | 参数 | 返回值 |
main() | int argc,char *argv[] | 0 |
printf() | argv[1],argv[2] | / |
exit() | 1 | / |
sleep() | atoi(argv[3]) | / |
getchar() | 无 | / |
atoi() | argv[3] | int值 |
3.4 本章小结
本章介绍了编译的概念和作用,进行了在Ubuntu中编译的操作,并细致地从几个方面分析了汇编代码以及其与源程序代码之间的联系,借此了解了编译器编译的机制。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
概念:汇编器(as)将.s汇编程序翻译成机器语言指令,把这些指令打包成一种叫做可重定位目标程序的格式,并将结果保存在目标文件.o中。
作用:将汇编代码转换为机器指令,并将其打包生成.o文件,为后续的程序的链接和运行做准备。
4.2 在Ubuntu下汇编的命令
命令:gcc -c -o hello.o hello.s
图4.2.1 Ubuntu下汇编的命令
图4.2.2 汇编生成的hello.o文件
4.3 可重定位目标elf格式
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。
Linux下生成hello.o文件的elf格式命令:readelf -a hello.o > hello.elf
- ELF头
图4.3.1 ELF头
ELF头以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。ELF头剩下的部分包含了帮助链接器语法分析和解释目标文件的信息,其中包括ELF头的大小、目标文件的类型、机器类型、节头部表的文件偏移,以及节头部表中条目的大小和数量。不同节的位置和大小是有节头部表描述的,其中目标文件中每个节都有一个固定大小的条目。
2.节头
图4.3.2 节头
描述.o文件中出现的各节名称、类型、地址、偏移量、大小等信息。
3.重定位节
当汇编器生成一个目标模块时,它并不知道数据和代码在内存中具体位置,也不知道该模块引用的外部函数和全局变量的位置。所以当汇编器遇到最终位置未知的目标引用时,就会生成一个重定位条目,告诉链接器在合并目标文件时如何修改引用。代码的重定位条目放在.rel.text中。已初始化数据的重定位条目放在.rel.data中。
图4.3.3 重定位节
.rela.text中保存的是.text节中需要通过重定位被修改的信息:任何调用外部函数或者引用全局变量的指令,调用外部函数的指令,引用全局变量的指令。不需要重定位的信息:调用局部函数的指令,在可执行目标文件中不存在。hello.o中的.rodata中的模式串、puts、exit、printf、slepsecs、sleep,getchar等符号。
.rela.eh_frame节是.eh_frame节重定位信息。
4.符号表
图4.3.4 符号表
.symtab存放在程序中定义和引用的函数和全局变量的信息。
4.4 Hello.o的结果解析
objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。
说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。
命令:objdump -d -r hello.o > hello1.txt
图4.4.1 反汇编命令
图4.4.2 hello.o的反汇编
与hello.s进行对比分析:
(1)数值表示 :hello.s中的操作数是以十进制表示的,而hello.o的反汇编代码中的操作数是以十六进制表示的,因为16进制与2进制之间的转换比十进制更加方便。
图4.4.3 hello.s数值表示
图4.4.4 hello1.txt数值表示
(2)控制转移:hello.s中使用.L2等段名称进行跳转,而hello.o的反汇编代码使用目标代码的虚拟地址,即主函数+段内偏移量。
以上是关于程序人生-Hello’s P2P的主要内容,如果未能解决你的问题,请参考以下文章
程序人生——Hello‘s P2P(HIT CSAPP大作业)