HITICS 大作业 by LS

HITICS 深入理解计算机系统大作业

摘要

本文简单介绍了hello从.c文件经历了.i,.s,.o等中间文件形式,被转换成可执行目标文件的过程。又简述了shell如何通过创建子进程运行这个可执行目标文件,一直到这个程序运行结束的过程,可谓是“hello的程序人生”
关键词:程序;预处理;编译;汇编;链接;异常;虚拟内存;Unix IO

第1章 概述

Hello简介

P2P:hello.c经过cpp,ccl,as,ld的处理由一个源代码文件转换成了一个可执行目标文件,shell fork子进程运行这个可执行目标文件

020:子进程通过execve执行这个可执行目标文件,中间经过异常、信号、虚拟内存管理等等步骤,最终运行结束,被父进程回收,抹去存在的痕迹。

1.1环境与工具

XPS 15 9570
VMWARE
UBUNTU
WINDOWS
EDB
NOTEPAD++
VIM
READELF

1.2 中间结果

文件 功能
hello.i 预处理后的ASCII码文件
hello.o 汇编文件
hello.elf 可重定位目标文件
helloasm.txt hello.o 反汇编代码
hello 最终的可执行目标文件
helloasmm.txt hello 反汇编代码
helloelf_lk.txt hello的部分elf内容
elfall.txt hello的全部elf内容

1.3 本章小结

这篇论文要开始了,介绍的hello的程序人生。

第2章 预处理

预处理的概念与作用

C语言标准规定,预处理是指前4个编译阶段(phases of translation)。
1.三字符组与双字符组的替换
2.行拼接(Line splicing): 把物理源码行(Physical source line)中的换行符转义字符处理为普通的换行符,从而把源程序处理为逻辑行的顺序集合。
3.单词化(Tokenization): 处理每行的空白、注释等,使每行成为token的顺序集。
4.宏扩展与预处理指令(directive)处理.

2.2 在Ubuntu下预处理的命令

cpp hello.c > hello.i

预处理命令

2.3 Hello的预处理结果解析

预处理文件
在这里可以看到.i文件变成了一个3188行的大文件,前面就是各种库的展开。
比如这里
在这里插入图片描述
是stdlib.h,里面有很熟悉的老朋友:
在这里插入图片描述
其中还有很多#ifdef,cpp会根据#ifdef后面的值到底有没有定义过来决定是否编译其中的语句。

2.4 本章小结

本章主要介绍了预处理的概念与作用,给出了预处理命令,对hello.c预处理之后的hello.i文件进行解析。
课程当中并没有着重讲过预处理这个环节,仅在链接开头提过,因此限于本人水平问题,不给出详细说明。

第3章 编译

3.1 编译的概念与作用

在整个编译过程中,编译器会完成大部分的工作,将把C语言提供的相对比较抽象的执行模型表示的程序转化成处理器执行的非常基本的指令。

3.2 在Ubuntu下编译的命令

gcc -s hello.i -o hello.s

在这里插入图片描述

3.3 Hello的编译结果解析

3.3.0 伪指令

在这里插入图片描述
所有以’.’开头的行都是指导汇编器和连接器工作的伪指令。(我们通常可以忽略这些行。)但在这里不能忽略……
其中:
.file声明了源文件的名字
.text声明代码段
.data rodata 声明只读数据段
.globl声明一个全局变量
.type声明变量类型(function or object)
.size声明变量大小
.long 声明一个long类型
.string 声明一个string类型
.align声明对齐大小要求

3.3.1 数据

Hello.s用到的数据类型有整数(int argc, int i, int sleepsecs,immediate), 字符串(“Usage: Hello 学号 姓名!\n”,“Hello %s %s\n”),数组(char *argv[])

3.3.1.1 整数

Hello.c里用到的整数有:int argc, int i, int sleepsecs,其中:

  1. argc是main传进来的参数,代表的是命令行中的参数个数。
  2. i是定义的局部变量,存储在栈中
    在这里插入图片描述
    如图所示,因为在一开始定义的时候并未初始化,所以没有立即将i的值压入栈中,在for循环中定义了i从初值为0开始循环,此时将0压入栈中
  3. sleepsecs是定义的全局变量(int sleepsecs = 2.5)
    在这里插入图片描述

可以看出sleepsecs是一个全局变量,对齐要求是4字节,是一个对象类型的变量,大小是4个字节,定义为一个long类型的变量,值为2,因为linux系统里面int跟long都是4个字节,所以可以互换地使用。

4.Immediates,例如判断是否进入if分支中的立即数3:

3.3.1.2 字符串

Hello.c用到了两个字符串(前面已列出)
在这里插入图片描述
都是定义在.rodata节中,表明是只读不可修改,都是用于printf输出的字符串

3.3.1.3 数组

用到的数组是char *argv[],是命令行传进来的参数,argv作为第二个参数传进来,所以存在%rsi中。
在这里插入图片描述

Argv是命令行传入的参数,数组中的每个元素都指向一个字符串,一般来说argv[0]指向的是执行的文件的名字。
在这里插入图片描述

3.3.2 赋值操作&逗号操作

程序中涉及到了’=’赋值操作和逗号操作符

3.3.2.1 赋值操作

程序中的赋值操作用“=”来完成
全局变量sleepsecs的赋值直接在.data段的声明中完成
在这里插入图片描述
局部变量i的赋值用汇编指令mov来完成
在这里插入图片描述

3.3.2.2 逗号操作符

逗号操作符是指在C语言中,多个表达式可以用逗号分开,其中用逗号分开的表达式的值分别结算,但整个表达式的值是最后一个表达式的值。在这里面是printf中的分别打印各自指定的值
在这里插入图片描述

3.3.3 类型转换

涉及到的类型转换是int sleepsecs = 2.5,因为是个int(long)类型,所以直接就将小数点之后抹去了,声明的时候也是声明为2.

3.3.4 算术操作

程序中C代码涉及到的算术操作有:++
汇编代码中涉及到的算术操作有:subq,addq,leaq

3.3.4.1 C代码中的算术操作

1.i++,即自增操作,约等价于i= i+1,先引用后自增、

3.3.4.2 汇编代码中的算术操作

1.subq A,B
表示B-A,结果存放在B中,A,B可以是内存位置、立即数、寄存器,但不能同时是内存位置
在这里插入图片描述
2.addq A, B
表示A+B,结果存放在B中,A,B可以是内存位置、立即数、寄存器,但不能同时是内存位置
在这里插入图片描述
3.leaq A,B
本身表示加载有效地址,把A的地址加载到B中,但因为是做加减乘混合运算,有时也可以用于算术运算

3.3.5 关系操作

程序中C代码涉及的关系操作有:!=,<
汇编代码中涉及的关系操作有:cmp

3.3.5.1程序C代码中涉及的关系操作

程序中C代码涉及的关系操作有:!=,<
汇编代码中涉及的关系操作有:cmp
1.!=
在这里插入图片描述
用于判断是否进入分支
2.<
在这里插入图片描述
用于判断是否进入/跳出循环

3.3.5.2程序汇编代码中涉及的关系操作

1.cmp A, B
执行隐含的减法操作,B-A但不保存结果,根据结果设置标志位,修改OF,SF,ZF,CF,AF,PF
在这里插入图片描述

3.3.6 控制转移

程序C代码涉及的控制转移有:if语句,for循环
程序汇编代码中涉及的控制转移有:jmp类

3.3.6.1 程序C代码涉及的控制转移

1.if语句
在这里插入图片描述
2.for循环

在这里插入图片描述

3.3.6.1 程序汇编代码涉及的控制转移

1.je

Cmpl将压入栈中的第二个参数与3作比较,并设置条件码,如果ZF = 1就跳转,否则执行下一条指令
2.jmp
在这里插入图片描述
无条件跳转,跳到.L3标记的位置
3.jle
在这里插入图片描述
Cmpl设置条件码,将9和局部变量i进行比较,如果ZF = 1或OF = 1就进行跳转,否则继续执行下条指令

3.3.7 函数操作

函数操作本质上是一个用户层面的过程调用,涉及到参数传递,函数调用,函数返回

3.3.7.1 参数传递

X86-64在调用函数之前,会将前六个参数传递给寄存器,剩下的参数(若有)压入栈中
在这里插入图片描述
如图所示,调用sleep函数之前将需要睡眠的秒数通过寄存器%rdi传递给了函数sleep,因为sleep是动态链接库中的函数,所以需要使用PLT定位,此为链接内容,后话……后话……

3.3.7.2 函数调用

在函数调用之前,会将返回地址(即当前pc值)压入栈中,使得函数调用结束之后能够返回。

3.3.7.3函数返回

在调用函数执行结束之后,若无返回值则直接返回,若有返回值则将返回值放入%rax寄存器之后返回

3.3.7.3 程序中涉及的函数调用

1.main
参数传递:外部调用会将argv和argc分别通过寄存器%rdi和%rsi传入main函数
函数调用:一个新的进程开始执行某一可执行目标文件时会先调用__libc_start_main,是系统启动时调用的函数,call指令会将返回地址压入栈中,然后调用main函数
函数返回:通常来说main函数会返回0
2.printf
参数传递:main函数中将第一条要打印的字符串的地址传入%rdi
在这里插入图片描述
因为只有一个字符串所以使用puts
在这里插入图片描述
函数调用:因为是动态链接库的函数,需要使用位置无关代码,因此需要GOT和PLT交互在运行时确定函数的地址,所以这里是call puts@PLT/printf@PLT
函数返回:没有返回值

3.exit
参数传递:无
函数调用:因为是动态链接库里的函数,需要使用位置无关代码因此需要GOT和PLT交互在运行时确定函数的地址,所以这里是call exit@PLT
函数返回:不返回,直接退出(这好像是要系统调用)
在这里插入图片描述
4.sleep
参数传递:在调用sleep函数之前先将要睡眠的秒数传入寄存器%edi中
函数调用:call指令会在调用之前把返回地址压入栈中,因为是动态链接库里的函数,需要使用位置无关代码因此需要GOT和PLT交互在运行时确定函数的地址,所以这里是call sleep@PLT
函数返回:如果被信号打断,会返回睡眠剩余的秒数,否则就

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值