程序人生-Hello’s P2P CSAPP HIT

第1章 概述

1.1 Hello简介

Hello是每个程序员最先接触到的程序,大部分IDE是直接默认生成的,程序员需要做的只需要按两下鼠标,完成编译,就可以运行了。屏幕上出现hello。。。
但实际上的过程不能用轻松两个字来描述。Hello.c是用高级语言C编写的,我们要经过预处理,编译,汇编等过程,才能作为机器能读懂的机器代码储存在磁盘中。Hello现在的状态叫程序(Program),用户通过shell,调用一系列函数将hello运行在内存中。他是通过一种叫做进程(Process)的抽象来实现的。
Execve函数将hello加载至内存,顺着逻辑控制流,hello在硬件中驰骋,最终出现在屏幕上。最终程序终止,shell将子进程回收。尘归尘,土归土,一切复原。

1.2 环境与工具

硬件环境:Intel Core i7-6700HQ x64CPU,8G RAM,256G SSD
软件环境:VMware Workstation Ubuntu18.04.1 LTS
开发调试工具:vim,gcc,as,ld,edb,readelf,hexedit

1.3 中间结果

在这里插入图片描述

1.4 本章小结

本章主要简单介绍了 hello 的 p2p,020 过程,列出了本次实验信息:环境、中间结果。

第2章 预处理

2.1 预处理的概念与作用

概念:预处理(或称预编译)是指在进行编译的第一遍扫描(词法扫描和语法分析)之前所作的工作。
预处理器(cpp)根据以字符#开头的命令(宏定义(#define)、文件包含(#include)、条件编译(#ifdef)),修改原始的C程序。比如将头文件从库中提取出来,然后插入到程序文本中,得到一个完整的源程序,通常以.i作为文件扩展名。
注意:宏定义的用法有许多,许多函数可以考虑通过宏定义的方式来实现,效率更高。条件编译和文件包含也有许多用法,自己拓展。
https://www.cnblogs.com/clover-toeic/p/3851102.html
一些常用的预处理命令
在这里插入图片描述

2.2在Ubuntu下预处理的命令

命令:cpp hello.c > hello.i
gcc -E test.c -o test.i
图2.1  cpp命令预处理hello.c

2.3 Hello的预处理结果解析

Cpp命令处理后的hello.c文件得到hello.i文件,可以看到hello.i也是一个文本文件,用gedit打开之后看到原先一二十行的代码被扩展到3118行增加了许多内容。
用vim打开hello.i,发现main函数在文本的最末端
在这里插入图片描述
但是程序之前的#include<stdlib.h><unistd.h><stdio.h>不见了。
以stdlib.h为例,cpp是如何将头文件展开的呢?
先看看头文件的组成:
在这里插入图片描述在这里插入图片描述

Cpp到默认的环境变量下寻找stdlib.h,打开/usr/include/stdlib.h,其中可能仍然会有#define语句,cpp对此进行递归展开,最终hello.i文件中只有对外部变量的声明,函数声明,没有宏定义。

2.4 本章小结

本章介绍了hello.c程序在编译之前需要做的准备工作,我们每次在写main函数之前在文件头添加的头文件和宏定义其实都需要经过比较复杂的处理。简单的一个hello程序,也是有很长的代码去实现的,只不过前人帮我们做好的准备工作。

第3章 编译

3.1 编译的概念与作用

概念:指将预处理后的程序转化成特定的汇编程序的过程。编译器将我们用高级语言C写的程序按照一定的语法规则分方法翻译成汇编代码,汇编语言一般包括mov赋值指令,jg条件跳转,comjg条件转移,逻辑运算指令等等。在用编译器编译C程序的时候可以指定编译器的优化等级,不同的优化等级对程序等处理不同,优化等级越高,程序越符合机器的思维方式,能够最大化利用CPU,但是不利于人的理解。
编译器的输入时预处理后的.i文件,输出是.s文件。
值得注意的是我们在学习CSAPP中做各种实验用到了一种很强的的反汇编工具——objdump,他可以将可执行文件(二进制文件)反汇编,得到汇编代码,但是两者的格式还是不尽相同,注意区别。
编译一般分为三个步骤:
1、 词法分析:编译器将程序中的字符串分离出来,转化成内部标准的表示结构。
2、 语法分析:将先前词法分析达到的token生成一个抽象语法树。
3、 优化和目标代码生成;编译器的后端会负责对代码进行优化,比如公共子式提取,循环优化,删除无用代码,得到目标代码。
编译过程涉及到许多复杂的概念以及相关知识,有兴趣可以去提前看看编译原理。

3.2 在Ubuntu下编译的命令

命令:gcc -S hello.i -o hello.s
图3.1  gcc编译hello.c

3.3 Hello的编译结果解析

3.3.0 汇编指令
编译后的得到的hello.s文件也是文本文件,但是内容比hello.i小了很多。
查看hello.i,在main函数之前多了一段程序头:
图3.2  hello.s程序注意汇编程序由三个不同的元素组成:
指示(Directives) 以点号开始,用来指示对编译器,连接器,调试器有用的结构信息。指示本身不是汇编指令。例如,.file 只是记录原始源文件名。.data表示数据段(section)的开始地址, 而 .text 表示实际程序代码的起始。.string 表示数据段中的字符串常量。 .globl main指明标签main是一个可以在其它模块的代码中被访问的全局符号 。至于其它的指示你可以忽略。
标签(Labels) 以冒号结尾,用来把标签名和标签出现的位置关联起来。例如,标签.LC0:表示紧接着的字符串的名称是 .LC0. 标签main:表示指令 pushq %rbp是main函数的第一个指令。按照惯例, 以点号开始的标签都是编译器生成的临时局部标签,其它标签则是用户可见的函数和全局变量名称。
指令(Instructions) 实际的汇编代码 (pushq %rbp), 一般都会缩进,以便和指示及标签区分开来。
(参考:https://blog.csdn.net/pro_technician/article/details/78173777)
在这里插入图片描述3.3.1数据
hello.c文件中涉及到的C数据类型有整型,数组,字符串。下面具体分析。
一、 字符串
程序中的字符串分别是:
1)“Usage: Hello 学号 姓名!\n”,printf传入的格式化参数。在hello.s中声明如下图3.3,注意到字符串使用UTF-8的格式编码的,一个汉字在UTF-8中占三个字节。
2)“Hello %s %s\n”,仍然是由printf函数传入的格式化参数,hello.s声明如下。
可以看到,两个字符串都被存放在.rodata段,作为全局变量。(和书上说的完全一样)
图3.3 hello.s字符串声明

二、 整型
程序中设计的整型数有四处:
1)int sleepsec :sleepsec在C程序中被声明成为全局变量,并且已经被赋值。编译器处理时,在.data段声明该变量,.data节存放已经初始化的全局和静态C变量。图3.4中,编译器先在.text段中声明sleepsec为全局变量,之后在.data段设置对齐方式为4字节,,类型为对象,大小为四字节,设置为long类型其值为2。(可能是编译器偏好,linux下long和int大小相同)
图3.4 hello.s中sleepsec的声明

2)int i :编译器将局部变量存放在栈和寄存器中,hello.s程序中i存放在-4(%rbp),占据4字节。
3)int argv :记录传入参数个数
4)立即数:C程序中有许多直接给出的比如3,0,10等,在汇编中直接以立即数形式给出。
三、数组
hello.c中涉及数组只有一个char argv[],函数执行的命令行参数。
argv是一个字符串数组,作为第二个参数传入。Linux中所有指针类型包括char
都为8字节大小,栈中一般为char*[]分配了一段连续的空间来存放相关内容,并且数组一般都是从栈底指针开始分配。函数传参的时候按逆序将参数压入栈中,这样取参数就是正序(只需要将rsp指针上移即可),如图3.5。

3.5 argv[]地址
3.3.2赋值
程序中涉及的赋值操作有两处:
1) int sleepsec=2.5 注意这里涉及到类型转换,将浮点数转换成整型,如图3.4所示,编译器将2赋给sleepsec,并且调整为long类型。
2) 对循环变量i的赋值:直接立即数赋值。Mov指令:
在这里插入图片描述

图3.6 赋值操作

3.3.3类型转换
对int sleepsec的赋值中涉及到隐式类型转换。
实际上类型转换是不改变值在内存中的存储内容,而是按照不同的读取原则对存储内容进行位截断,位扩展等。一般来说,浮点型的默认类型是double,向整型转换遵循向零舍入的原则。
3.3.4 算术操作
图3.7.1 整数算术操作
图3.7.2 特殊的算术操作
程序中涉及到的算术操作有:

  1. 加法操作add:在对计数器加一时addq,注意对于具体的数据类型,add操作还分为字节加法,字加法,双字加法,四字加法。其他操作类似。
  2. 减法操作sub:为main函数开辟栈帧是将栈顶指针-0x32。
  3. 加载有效地址:将LC0的有效地址传送给%rdi。
    图3.8 hello.s中的算术操作

3.3.5关系操作

机器代码提供两种基本的低级机制来实现有条件的行为:测试数据值。Cpu维护着一组单个位的条件码,用来描述最近的算术操作或逻辑操作。图3.7.1中所有的操作都会相应设置条件码,除此以外还有两种指令CMP和TEST指令,只设置条件码而不改变寄存器的值,汇编中条件转移和条件传送也是以此为基础实现的。

图3.9 汇编的关系操作

程序中涉及的关系运算:

  1. if(argc!=3) 判断输入参数的个数是否正确,正确则打印对应的学号和姓名,否则打印提示字符串后退出。
  2. for(i=0;i<10;i++),判断
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值