【第 3 周】深入理解计算机系统共读心得体会

所在小组

第七组

组内昵称

陈盛华

你的心得体会

  • 用一个例子去理解汇编:
long mult2(long,long);

void multstore(long x,long y ,long *dest)
{
	long t = mult2(x,y);
	*dest = t;
}
  • 输出汇编文件。执行以下命令输出汇编文件mstore.s
root@server:~# gcc -Og -S mstore.c

以下是mstore.s的内容

    .file	"mstore.c"
	.text
	.globl	multstore
	.type	multstore, @function
multstore:
.LFB0:
	.cfi_startproc
	pushq	%rbx
	.cfi_def_cfa_offset 16
	.cfi_offset 3, -16
	movq	%rdx, %rbx
	call	mult2
	movq	%rax, (%rbx)
	popq	%rbx
	.cfi_def_cfa_offset 8
	ret
	.cfi_endproc
.LFE0:
	.size	multstore, .-multstore
	.ident	"GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609"
	.section	.note.GNU-stack,"",@progbits
  • “.”开头的,如“.file、.text”等都是指导汇编器和链接器工作的伪指令。

    去除伪指令后

    multstore:
    	pushq	%rbx
    	movq	%rdx, %rbx
    	call	mult2
    	movq	%rax, (%rbx)
    	popq	%rbx
    	ret
    
  • 输出二进制文件mstore.o

    root@server:~# gcc -Og -c mstore.c
    
  • 查看二进制文件mstore.o

    root@server:~# objdump -d mstore.o
    
    mstore.o:     file format elf64-x86-64
    
    
    Disassembly of section .text:
    
    0000000000000000 <multstore>:
       0:	53                   	push   %rbx
       1:	48 89 d3             	mov    %rdx,%rbx
       4:	e8 00 00 00 00       	callq  9 <multstore+0x9>
       9:	48 89 03             	mov    %rax,(%rbx)
       c:	5b                   	pop    %rbx
       d:	c3                   	retq   
    
  • 指令含义

    • 进栈指令 PUSH (push onto the stack)

    • 出栈指令 POP (pop from the stack)

    ​ 指令的汇编格式:PUSH SRC ;POP DST

    • 传送指令 MOV (move)

      指令的汇编格式:MOV DST,SRC

      指令的基本功能:(DST)<-(SRC) 将原操作数(字节或字)传送到目的地址。

    • CALL 过程调用

    • RET/RETF过程返回.

所在小组

静默组

组内昵称

清风环佩

心得体会

  1. 编程语言并非是只有一套语法规则,还需要完善的配套工具。以C语言代码为例,demo.c文件经过编译器的处理变成汇编demo.s,汇编代码demo.s再经过汇编器的处理编程对象程序demo.o,然后经过链接器将所需的静态库链接起来,最终组合成计算机的执行文件。
  2. 执行文件中存放的一堆CPU指令集,告诉CPU什么时候该做什么,汇编语言将指令集处理成人类能看得懂的语言,所以汇编语言是距离指令集也就是机器语言最近的一层。CPU指令是真正的具体落实操作的,不同CPU的指令集也不尽相同。就像Croei7引入的AVX指令可以提高浮点运算,但是Corei7仍然是大众CPU,在某些极端场景或许CPU会专门对一些指令做极致优化。
  3. x86-64的CPU有16个64位寄存器,64位即8字节,在使用时可以随意选择,汇编末尾的b为1字节,w为2字节,l为4字节,q为4字节,本章用了近150页来描述寄存器,足够晦涩抽象。
  4. 遇到2个实在不能自己解惑的问题,记录一下:
    • cs:app 第三版125页最下面倒数第二段,“指令2从内存中读出x,把它存放到寄存器%rax中”,联系上下文,这里的“读出x”是否是笔误,应该是“读出xp”?如果不是笔误,那就我对此段的理解出现了大偏差。
    • cs:app 第三版131页移位操作,“指令salb会移7位,salw会移15位,sall会移31位,而salq会移63位”,此处的“7、15、31、63”是怎么计算出来的?思考甚久,查无所得。
  1. 所在小组:第2组
  2. 组内昵称:跃山
  3. 心得体会:

程序的机器级标识

  1. 程序编码
  2. 数据格式
  3. 访问信息
  4. 算数和逻辑操作
  5. 控制
  6. 过程
  7. 数组分配和访问
  8. 异质的数据结构
  9. 在机器级程序中将控制和数据结合起来
  10. 浮点代码

深入理解计算机系统-程序的机器级表示

我们可以通过汇编代码来对系统进行优化,能够阅读和理解汇编代码是一项很重要的技能。

程序编码

  1. 机器级代码
    1. 利用计算机中多种不同形式的抽象模型实现程序细节,有两种模型
      1. 指令集。用来定义程序的格式和行为。
      2. 虚拟内存。机器级别程序使用的内存地址是虚拟地址。
    2. 使用指令集架构提供的指令,CPU顺序执行指令,指令会影响CPU、寄存器等的状态,也就是能够并行,也遵循顺序执行的模型。
    3. 使用虚拟内存地址
      1. PC寄存器
      2. 整数寄存器64位
      3. 条件码寄存器,存放最近的状态,实现if,while等条件。
      4. 向量寄存器,存储一组数据。
    4. 机器代码把内存看成一个很大、按字节寻址的数组,各种数据类型在机器代码中都是一组连续的字节表示,也不区分无符号、有符号整数,不区分指针和整数,不区分各种类型的指针。
  2. 关于格式的注解

数据格式

Intel规定一个字(word)等于16 位,两个字节。因此双字为32位,四字为64位。在64位机器中,指针就是四字。

  1. movb表示移动半个字,1个字节
  2. movw移动1个字,2个字节
  3. movl(long word)移动2个字
  4. movq移动4个字

访问信息

  1. 操作数指示符
    1. 立即数。$5就是一个直接数字,数字字面量。
    2. 绝对寻址。0x400001直接写死一个内存地址,没有$符号,表示拿出这个内存地址的值。
    3. ra。直接写某个寄存器,代码这个寄存器中的值R[ra](R 可以看成所有寄存器的数组,ra 是取值下标)**寄存器寻址
    4. (ra)。M[R[ra]]:在内存中取值寄存器中存储地址的值(M 代表内存)内存寻址;
  2. 数据传送指令(操作数指示符就相当于数据传送指令的参数,把一个地址的数复制到另一个地址。)
    1. mov S, D = Source -> Dest
    2. b、w、l、q 分别代表复制 8、16、32、64 个字节。
    3. 内存地址不能复制到内存地址,需要寄存器中转。
      1. movz 指令会对不够长度的源值在目的地址填充0。
      2. movs 系列指令会对不够长度的情况填充符号扩展。
  3. 压入和弹出栈数据
    1. push、pop 操作栈数据结构,会修改栈指针的值,其指向内存的某个区域。
    2. 根据惯例,栈向下生长,下面是栈底push 时栈指针值会减 8,也就是上面的地址大。
    3. 因为栈在内存中,也就是大数组的一部分,因此经常使用偏移栈指针的方式访问数据,例如:movq 8(%rsp), %rdx 将第二个四字从栈中复制到寄存器 %rdx。

算数和逻辑操作

  1. 加载有效地址
  2. 一元和二元操作
  3. 移位操作
  4. 特殊的算术操作

控制

  1. 条件码
    1. 常用条件码
      1. CF进位标志。
      2. ZF零标志。
      3. SF符合标志。
      4. OF溢出标志。
    2. 使用方式
      1. 组合条件码,将一个字节设置为0/1
      2. 跳转到程序的指定部分
      3. 有条件的传送数据
  2. 访问条件码
  3. 跳转指令
  4. 跳转指令的编码
  5. 条件控制来实现条件分支
  6. 条件传送来实现条件分支
    1. 条件传送不一定就能提高代码的效率,在处理带分支的代码时候,编译器并没有足够的信息来进行可靠的分支预测,GCC在只有当两个表达式都容易计算的时候才会使用条件传送;
  7. 循环语句
  8. switch语句

过程

  1. 运行时栈
  2. 转移控制
  3. 数据传送
  4. 栈上的局部转储
  5. 寄存器中的局部存储空间
  6. 递归过程

数组分配和访问

  1. 基本原则
  2. 指针运算
  3. 嵌套的数组
  4. 定长数组
  5. 变长数组

异质的数据结构

  1. 结构
  2. 联合
  3. 数据对齐

在机器级程序中将控制与数据结合起来

  1. 理解指针
  2. 使用GDB调试器
  3. 内存越界引用缓冲区溢出
  4. 对抗缓冲区溢出攻击
    1. 栈随机化
    2. 栈破坏检测
    3. 限制可执行代码区域
  5. 支持变长栈帧

浮点代码

  1. 浮点传送和转换操作
  2. 过程中的浮点代码
  3. 浮点运算操作
  4. 定义和使用浮点常数
  5. 在浮点代码中使用位级操作
  6. 浮点比较操作
  7. 对浮点代码的观察结论

所在小组

第六组

组内昵称

undefined

心得体会

关于cpu,不断的在晶体管、多核、指令集上进行优化,说明这3个是各个cpu性能考虑指标
关于寄存器,很有趣的兼容设计,从ax,bx,cx,dx,si,di,bp,sp开始,不断的进行扩充,特殊的还是条件码寄存器
关于指令,主要还是分为2类,计算指令(加减乘除位移等),逻辑指令(条件判断、调整)
下一章学习关于堆栈相关知识

课外知识

关于GCC的优化

1.-O,-O1:
这两个命令的效果是一样的,目的都是在不影响编译速度的前提下,尽量采用一些优化算法降低代码大小和可执行代码的运行速度。
2. -O2
该优化选项会牺牲部分编译速度,除了执行-O1所执行的所有优化之外,还会采用几乎所有的目标配置支持的优化算法,用以提高目标代码的运行速度。
3. -O3
该选项除了执行-O2所有的优化选项之外,一般都是采取很多向量化算法,提高代码的并行执行程度,利用现代CPU中的流水线,Cache等。
这个选项会提高执行代码的大小,当然会降低目标代码的执行时间。

4. -Os
这个优化标识和-O3有异曲同工之妙,当然两者的目标不一样,-O3的目标是宁愿增加目标代码的大小,也要拼命的提高运行速度,但是这个选项是在-O2的基础之上,尽量的降低目标代码的大小,这对于存储容量很小的设备来说非常重要。
5. -Ofast:
该选项将不会严格遵循语言标准,除了启用所有的-O3优化选项之外,也会针对某些语言启用部分优化。
6.-Og:
该标识会精心挑选部分与-g选项不冲突的优化选项,当然就能提供合理的优化水平,同时产生较好的可调试信息和对语言标准的遵循程度。

关于条件码

条件码寄存器中保存着单个位的条件码,由CPU维护,如:

CF:进位标志
ZF:零标志
SF:符号标志
OF:溢出标志

有几类指令能够修改条件码:

  1. 算术指令:既改变操作数,也有肯能改变条件码。
  2. CMP指令:右操作数减左操作数,只可能改变条件码。
  3. TEST指令:两操作数相与,只可能改变条件码。

条件码寄存器不能直接读取,有三种方法:

  • set指令:根据条件码,设置一个字节。
  • jump指令:根据条件码进行跳转,即控制的条件转移。
  • cmov条件传送指令:根据条件码决定是否进行mov操作(其性能要优于控制的条件转移P141)。

所在小组
第二组

组内昵称
梁广超

心得体会

1、几个比较特别的寄存器
%rsp 是栈寄存器,存储程序栈的栈顶
%rbx % rbp %12~%15被调用者保护(其余除了%rsp 都可以被其他过程修改)
%rax 是返回值

2、一个过程的通用栈帧结构包括
被保存的寄存器、局部变量、参数构造区以及可能存在的返回地址

3、一个函数的参数太多,不会被全部放入寄存器,没有放入寄存器的参数会被存储在调用者的栈帧

4、栈帧先进后出的形式符合程序的过程调用,可以让过程调用开始时分配资源,过程调用结束时释放资源

5、条件码寄存器 记录执行的算术或逻辑指令的状态信息,用来实现控制程序数据流的走向

所在小组

第七组

组内昵称

李佳

你的心得体会

  • 访问数据的三种存储位置(IA32为例):
    • 立即数:常数值
    • 寄存器:8个寄存器中的存储
    • 存储器:根据计算地址来访问某个存储器的值,地址的计算基于立即数或者寄存器中的值
  • 数据传送
    • MOV:将立即数写入寄存器
    • push压栈,pop出栈
  • 算术逻辑
    • INC,DEC在进位,退位时常用
    • ADD,SUB等二元操作数注意第二个操作数是源和目的
    • 移位操作要注意符号
  • 控制
    • 循环利用跳转jmp和判断条件来实现
    • switch通关跳转表实现
  • 函数
    • %esp栈指针,%ebp帧指针,单个过程有自己的帧栈

所在小组

第三组

组内昵称

kippa

心得体会

程序编译

C 预处理器扩展源代码,将 #include 指定的文件插入代码,扩展#define声明指定的宏
编译器产生汇编代码 hello.s
汇编器将汇编代码转化成二进制目标文件,包含所有指令的二进制表示,但是还没有填入全局值的地址
链接器将目标文件与库函数代码合并,产生最终的可执行代码文件。

  • 操作系统负责管理虚拟地址空间,将虚拟地址翻译成实际处理器内存中的物理地址

操作数指示符

  • 寻址方式

  1. 立即数,表示常数值,只能作为 source,不能作为 dest
  2. 寄存器,表示寄存器的内容,可以分别取寄存器的1、2、4、8 字节作为操作数,分别对应 rax、eax、ax、al
  3. 内存引用,根据计算出来的地址(通常称为有效地址)访问某个内存位置

数据传送指令

指令 效果 描述
MOV S,D D←S 传送
movb 传送字(双字节)
movw 传送双字(四字节)
movl 传送四字(八字节)
movq 传送字节
movabsq I,R R←I 传送绝对的四字(八字结)

数据传送示例

  • “指针”就是地址,间接引用指针就是将该指针放在一个寄存器中,然后在内存引用中使用这个寄存器。
  • 局部变量通常保存在寄存器中,如movq (%rdi), %r8,即表示将一个指针存放在 rdi 寄存器的变量的值(内存寻址)赋值给 r8寄存器,其反汇编语句为long xp = *xp

push/pop 栈数据

  • x86-64中,栈向低地址方向增长,所以压栈是减小栈指针
  • push:栈顶指针减 8,然后将值写入新的栈顶地址;
  • pop:从栈顶读取数据,然后栈顶指针加 8

算术和逻辑运算指令

  • 大多数操作数都分成了指令类,除了 leaq 之外都有不同大小操作数的变种,如 add 有 addb、 addw、 addl、 addq

所在小组

第六组

组内昵称

吴彬

你的心得体会

  1. .开头的行都是知道汇编器和连接器工作的伪指令,通常可以忽略
  2. a开头的寄存器是16位,e开头的32位,64位的r开头
    3、数据传送指令MOV(movb,movw,movl,movq) MOV S, D D<-S
    4、压栈和出栈: pushq,popq
    5、算术逻辑运算:INC、DEC、ADD、SUB、SAL、SHL
    6、加载有效地址:leaq

所在小组

第五组

组内昵称

陈松彬

心得体会

Chapter3 section1

Author: @陈松彬

section1 主要是3.1 ~3.7

Date: 2020-10-11

一、c程序中使用汇编代码

  1. 书中网络旁注有一个很有意思的例子,通过在c语言中,将某块逻辑使用汇编代码重写,提高了执行效率
  2. 缺点:使得代码与特定机器相关,迁移到其他机器,不能通过简单的重新编译代码实现,需要重写
  3. 优点:对某块核心代码(调用次数很多)的优化,可以使得整个系统得到加速
  4. 最后,我认为在云原生的时代,数据中心的CPU应该都是使用同一套指令集架构(eg:x86-64),因此这里的缺点感觉可以忽略

二、学习程序机器代码的意义?

  1. 对某些基础库的优化有很大的帮助。可以通过分析某一块逻辑的汇编代码,发掘一些优化的点。
  2. 可以尝试自己解决一些问题,可以通过查看编译之后的汇编代码找到答案。eg:Golang中range遍历数组与index遍历数组哪个效率更高

三、再次回顾CPU cache

  • block:k个字节,Cache与主存交互的基本单位
  • cache:cache中的tag表示该block在主存中的位置
  • 目前主流的CPU Cache的Cache Line大小都是64Bytes
  • CPU cache之False Sharing 1)在多线程同时对同一个cache line频繁读写的时候,由于需要保持一致性,会不断来回同步,导致效率低 2)如果不同线程读写不同的cache line,就可以避免此问题,提高效率
  • 内存对齐?只是影响内存?还是会影响效率?是否会影响CPU cache?
  • 行遍历 与 列遍历二维数组 哪个效率高 与cpu cache的关系?
1 个赞

所在小组

第六组

组内昵称

慎思明辨笃行

你的心得体会

3.1 到 3.7 总结

1、 x86 与 x64 的发展历程

2、摩尔定律(Moore 定律)

摩尔定律是英特尔创始人之一戈登·摩尔的经验之谈,其核心内容为:集成电路上可以容纳的晶体管数目在大约每经过 24 个月便会增加一倍。换言之,处理器的性能每隔两年翻一倍。

被称为计算机第一定律的摩尔定律是指 IC 上可容纳的晶体管数目,约每隔 24 个月便会增加一倍,性能也将提升一倍。摩尔定律是由英特尔(lnte)名誉董事长戈登·摩尔( Gordon moore)经过长期观察总结的经验。

归纳起来,“摩尔定律”主要有以下 3 种“版本”:

  • 集成电路芯片上所集成的电路的数目,每隔 18 个月就翻一番;

  • 微处理器的性能每隔 18 个月提高一倍,而价格下降一半;

  • 用一美元所能买到的计算机性能,每隔 18 个月翻两番。

3、数据格式

数位扩展:16 位(字 word)->32 位(双字 double words)->64 位(四字 quad words)

4、数据传输指令

mov 类:movb、movw、movl 和 movq

5、条件码

CF:进位标志

ZF:零标志

SF:符号标志

OF:溢出标志

所在小组

第三组

组内昵称

晴天

心得体会

  1. 32位机器只能大概使用4GB(2^32字节)的随机访问存储器;64位机器能够使用多达16EB(2^64字节)的内存空间

  2. 命令 gcc 指的是GCC C编译器。调用gcc命令调用了一整套程序,将源代码转换成可执行的文件。

    1. C预处理器拓展源代码,插入所有用#include命令指定的文件,并拓展所有用#define声明制定的宏
    2. 编译器产生两个源文件的汇编代码,文件名后缀为.s
    3. 汇编器将汇编代码转化为二进制目标代码文件,文件名后缀为.o
    4. 链接器将两个目标代码文件与实现库函数代码合并,产生可执行的代码文件
  3. 对于机器级编程,两种抽象思维:

    1. 指令集系统结构或指令集架构(ISA)来定义机器级程序的格式和行为,定义了处理器状态,指令的格式,以及每条指令对状态的影响。大多数ISA,将程序的行为描述成好像每条指令都是按顺序完成的,一条指令结束后,另一条指令再开始。处理器的硬件远比描述的精细复杂,它们能够并发执行多条命令,但是可以采取措施保证整体行为与ISA指定的顺序执行的行为完全一致。
    2. 机器级程序使用的内存地址是虚拟地址,提供的内存模型是一个非常大的字节数组。在内存中声明和分配各种数据类型和对象,但是机器代码只是简单地把内存看成是按字节寻址的,很大的数组。(虚拟地址和物理地址是如何映射的?)
  4. x86-64的CPU包含一组16个存储64位值得通用目的寄存器。指令可以访问到16个寄存器中不同字节的数据。比如16位操作符可以访问最低位的2个字节,64位操作符可以访问整个寄存器。

  5. 大多数指令有一个或者多个操作数,操作数有3种,立即数(用于表示常数值),寄存器和内存引用。

  6. 汇编中使用MOV将数据从一个位置复制到另一个位置。当两个操作数都是内存地址时,需要使用两条指令完成复制。先将源值加载到寄存器中,再讲寄存器值写入目的位置。

  7. 栈在处理过程调用中起到至关重要的作用。在x86-64中,程序栈存放在内存中某个区域。栈向下增长,向低地址方向增长, 栈顶元素的地址是所有栈中元素地址中最低的,%rsp 保存着栈顶元素的地址。

  8. 控制在程序逻辑实现中非常重要,毕竟代码不都是顺序执行的。用jump命令可以改变一组机器代码指令的执行顺序。条件码寄存器(CF、ZF、SF、OF)是实现控制的基础。

所在小组:第一组
组内昵称:bluewhale
心得体会

gcc main.c -o main

gcc命令调用了一整套程序,将源码转换成可执行代码

  • 预处理 插入#include命令指定的文件
  • 编译 生成汇编文件
  • 汇编 将汇编生成二进制代码
  • 链接 目标代码文件与实现库合并

操作数指示符

多数指令会有一个或多个操作数,其类型包括三种:

  • 立即数 表示常数值,用$后面跟一个整数表示
  • 寄存器 表示某个寄存器的内容
  • 内存引用 会根据计算出来的地址访问某个内存位置

数据传送指令

将数据从一个位置复制到另一个位置的指令。

源操作数指定的值是一个立即数,存储在寄存器或者内存中。目的操作数指定一个位置,可以是寄存器或者内存地址。

规定传送指令的两个操作数不能都指向内存位置
当将较小的源值复制到较大的目的时使用零扩展数据传送指令。

  • MOVZ 把目的中剩余的字节填充为0
  • MOVS 通过符号扩展来进行填充,把源操作的最高位进行复制

压入和弹出栈数据

程序栈存放在内存中的某个位置,向下增长,栈顶元素的地址就是所有栈中元素最低的。其中寄存器%rsp指向栈顶元素的地址。

  • pushq 实现将数据压入栈
    • 首先将栈顶指针地址减8
    • 将数据写入栈顶地址指向的位置
  • popq 实现将数据弹出栈
    • 先将栈顶指针指向的数据取出
    • 栈顶指针地址加8

算数和逻辑操作

每个指令类都对应有四种不同大小数据的指令。

运行时栈

x86-64过程需要的存储空间超出寄存器大小后,会在栈上分配空间,这个过程称为栈帧

以过程P调用Q,Q执行后返回P为例

P调用Q会将Q返回后下一条执行的P的代码作为P栈帧的一部分。Q则会扩展当前栈的边界,分配其栈帧所需要的空间。

寄存器中的局部存储空间

寄存器组是唯一被所有过程共享的资源。我们需要确保一个过程(调用者)调用另一个过程(被调用者)时,被调用者不会覆盖调用者稍后会使用的寄存器的值。

寄存器 %rbx %rbp 和 %r12 ~ %r15被划分为被调用者保存寄存器 当过程P调用过程Q时,Q必须保存这些寄存器的值,保证在Q返回到P时不变。

所在小组
第一组

组内昵称
张仁杰

你的心得体会

  1. 能够从汇编代码中理解程序是如何处理数据的。
  2. 16个寄存器有:rax、rbx、rcx、rdx 、rsp(栈指针操作相关)、rbp(基址指针)、rdi、rsi,其他便是r8-r15。
  3. 左移指令有两个名字SAL和SHL,效果是一样的,右边填上0;SAR是算数移位和SHR逻辑移位。
  4. 基于条件数据传送的代码与比基于条件控制转移的代码性能要好,是通过重叠连续指令的步骤获得高性能。处理器采用了非常精密的分支预测逻辑来猜测每条跳转指令是否会执行,这是基于条件控制转移的代码性能更好的原因。

所在小组

第一组

组内昵称

SADAME

心得体会

  • C 语言代码(da.c, wang.c)经过编译器的处理(gcc -0g -S)成为汇编代码(da.s, wang.s)

  • 汇编代码(da.s, wang.s)经过汇编器的处理(gccas)成为对象程序(da.o, wang.o)

  • 对象程序(da.o, wang.o)以及所需静态库(lib.a)经过链接器的处理(gccld)最终成为计算机可执行的程序

  • 程序计数器(PC, Program counter) - 存着下一条指令的地址,在 x86-64 中称为 RIP

  • 寄存器(Register) - 用来存储数据以便操作

  • 条件代码(Codition codes) - 通常保存最近的算术或逻辑操作的信息,用来做条件跳转

汇编中括号代表寻址,即取地址操作
前六个寄存器(%rax, %rbx, %rcx, %rdx, %rsi, %rdi)称为通用寄存器,有其『特定』的用途:

  • %rax(%eax) 用于做累加
  • %rcx(%ecx) 用于计数
  • %rdx(%edx) 用于保存数据
  • %rbx(%ebx) 用于做内存查找的基础地址
  • %rsi(%esi) 用于保存源索引值
  • %rdi(%edi) 用于保存目标索引值
    而 %rsp(%esp) 和 %rbp(%ebp) 则是作为栈指针和基指针来使用的。
    操作数的三种基本类型:立即数(Imm)、寄存器值(Reg)和内存值(Mem)。

所在小组 :第六组
组内昵称 :杨凯伟
心得体会

数据格式和访问信息

术语 “字(word)” 表示 16 位数据类型。所以 32 位数为双字,64 位数为四字。

汇编语言中只用整型和浮点两种数据类型,整型分为1(char),2(short),4(int),8(long)等四种字节长度,浮点分为单精度(4(float))和双精度(8(double))两种类型.

汇编语言可以通过 3 种方式来读写数据:立即数,寄存器和内存。

  • 立即数:相当于硬编码在程序中,如$1等
  • 寄存器:可以通过寄存器的名字来访问,如%rax, %r12等
  • 内存引用:会根据计算出来的地址(通常称为有效地址)访问某个内存位置

算术和逻辑操作

主要分为 加载有效地址、一元操作、二元操作、移位 四个部分。

加载有效地址

加载有效地址(load effective address)指令 leaq 实际上是 movq 指令的变形。另外它还可以简洁地描述普通的算术操作。

leaq src, dst: 本作为地址计算,但gcc常常用来优化代数计算,如leaq (%rdi,%rdi,2), %rax用来计算t = x+2*x

移位

sarq是算术右移(Arithmetic Shift),shrq是逻辑右移(Logical Shift)

左移是算术左移(sal)和逻辑左移(shl)的效果是一样的,都是将右边填上0。而算术右移是填上符号位,逻辑右移是填上0;

控制

条件控制

C 语言中 if 语句是利用测试语句(compare)和跳转语句(jump)实现。测试语句不保存计算结果,只影响状态位。

条件转移

C 语言中 if-else 语句和 ?: 表达式还可以对应一种叫作条件转移的汇编实现。

这种实现提前计算好每个分支的值,然后根据测试值才决定采用哪一个值。

循环

  • do while
    这是汇编实现最简单的循环结构,只需要在循环体尾部增加测试语句。
  • while
    有两种翻译方式,一是(jump to middle),它首先执行一个无条件跳转跳到循环结尾处的测试,二是(guarded-do)基于 do…while 语句实现,但在进入循环时先进行一次测试,条件不成立就跳出循环。
  • for
    在 while 语句的基础上,开头先初始化表达式(init-expr)求值,循环体后对更新表达式(update-expr)求值。

switch

switch 语句通过一种叫作跳转表(Jump Table)的数据结构来实现这多种特性。跳转表实际上是一个数组,x 的取值作为下标,各个 case 代码的地址作为元素。

所在小组

第一组

组内昵称

成都-郝立鹏

心得体会

寄存器使用惯例

程序寄存器组是唯一能被所有函数共享的资源。

虽然在给定时刻只能有一个函数是活动的,但是我们必须保证当一个函数调用另一个函数时,被调用者不会覆盖某个调用者稍后会用到的值。为此,IA32采用了一组统一的寄存器使用规则,所有的函数都必须遵守,包括程序库中的函数。

根据惯例:寄存器%eax、%edx、%ecx被划分为调用者保存寄存器。当过程P调用Q时,Q可以覆盖这些寄存器,不会破坏任何P所需要的数据。

另一方面,寄存器%ebx、%esi、%edi被划分为被调用者寄存器。这意味着Q必须在覆盖这些寄存器的值之前,先把它们保存到栈中,并在返回前恢复它们。此外还必须保持寄存器%ebp和%esp。

转移控制

call Label 过程调用

call *Operand 过程调用

leave 为返回准备栈

ret 从过程调用中返回

call指令的效果是将返回地址入栈,并跳转到被调用过程的起始处。(返回地址是在程序正文中紧跟在call后面的那条指令的地址,这样当被调用过程返回时,执行流会从此处继续。)

ret指令从栈中弹出地址,并跳转到这个位置。(使用这条指令前,要使栈做好准备,栈顶指针要指向前面call指令存储返回地址的位置)

leave指令 使栈做好返回的准备。它等价于:

movl %ebp, %esp ; 把寄存器%ebp中的值复制到寄存器%esp中(回收本函数的栈空间)

popl %ebp

leave指令的使用在返回前,既重置了栈指针,也重置了基址指针。

所在小组
第二组

组内昵称
文弱书生

你的心得体会
数据格式:
b、w、l、q
数据访问:
立即数:常数值,在$后面跟一个整数比如 $0x1F
寄存器:表示某个寄存器的内容。16个寄存器中的低位 1字节(8位)、2字节(16位)、4字节(32位)或8字节(64位) 中的一个 作为操作数
内存引用:会根基计算出来的地址访问某个内存位置。
不同的寻址模式
立即数、寄存器、绝对、简介、变址、比例变址、基址+偏移量
数据传送指令:
MOV、PUSH

算术逻辑指令
INC,DEC,ADD,SUB,IMUL,XOR,OR,AND,SAL
条件码常用的读取方法:
1.可以根据条件码的某种组合,将一个字节设置为0或者1 SET类指令
2.可以条件跳转到程序的某个其他的部分
3.可以有条件的传送数据
直接跳转和间接跳转
过程调用过程涉及 传递控制、 传递数据、 分配和释放内存过程
栈数据结构提供的 后进先出 的内存管理原则。

所在小组

第六组

组内昵称

利健锋

你的心得体会

  1. 机器代码
  • 指令集架构
  • 虚拟内存
  • 汇编可见
    • 程序计数器
    • 整数寄存器
    • 条件码寄存器
    • 向量寄存器

操作系统负责管理虚拟内存空间并翻译成实际处理器内存的物理地址

  1. 数据格式和访问信息
  • 字(word),32 位数为双字(double words),64 位数位四字(quad words)
  • 1、2 字节数字指令生成时,剩下的字节保持不变
  • 4 字节数字生成时,高位 4 字节置 0

所在小组:静默组
组内昵称:维钢、
心得体会

  • 通过阅读程序的汇编代码,我们可以理解编译器的优化能力,并分析代码中隐含的低效率。对于现如今的程序员来说,需要能够阅读和理解编译器产生的代码。
  • Intel用"字(word)"来表示16位数据类型,也就是一个“字”表示两个字节的大小。大多数GCC生成的汇编代码指令都有一个字符的后缀表示操作数的大小。其中b表示字节(8位),w表示字(16位),l表示双字(32位),q表示四字(64位)。
  • 通用目的寄存器主要用来存储整数数据和指针。
  • 大多数指令有一个或多个操作数,指示出执行一个操作要使用的原数据值和放置结果的位置。操作数的类型有三种:一是立即数,用来表示常数值;二是寄存器,表示某个寄存器的内容;三是内存引用,它会根据计算出来的有效地址访问某个内存位置。
  • 最简单的数据传输类指令–MOV类,分别为:movb、movw、movl和movq,主要区别是操作的数据大小不同,分别是1、2、4和8字节。
  • %rsp指向的地址总是栈顶。

所在小组

第二组

组内昵称

陶鑫

你的心得体会

1、计算机执行机器代码,用字节序列编码低级的操作,包括处理数据、管理内存、读写存储设备上的数据,已经利用网络通信。
2、以适当的命令行输出汇编代码,然后阅读代码对于深入研究底层很有作用。
3、moore定律:集成电路上可以容纳的晶体管数目在大约每经过26个月便会增加一倍。
4、gcc -0g -o p p1.c p2.c 其中-og编译器生成符合原始C代码的优化等级。在命令行上面使用“-S”选项,就可以看到C语言编译器产生的汇编代码。
5、程序内存包括:程序的可执行机器代码,操作系统需要的一些信息,用来管理过程调用和返回的运行时栈,以及用户分配的内存块。
6、数据结构:字节(b)、字(w)、双字(l)、四字(q)。
7、一个CPU包含一组16个存储64位值的通用目的寄存器。这些寄存器用来存储整数数据和指针。 %rax 返回值、%rbx被调用者保存、%rdi 第一个参数、%rsi 第二个参数、%rbp 被调用者保存 、%rsp 栈指针。
8、大多数指令有一个或多个操作数,指示出执行一个操作中要使用的源数据值,以及放置结果的目的位置。操作数分为三种类型:第一种类型是立即数,表示常数。第二种类型是寄存器,表示某个寄存器的内容。第三类操作数是内存引用,根据计算出来的地址访问某个内存位置。
9、数据传送指令,MOV类 movb、movw、movl、movq、movabsq。
10、压入和弹出栈数据指令,pushq、popq。
11、算数逻辑类指令,例如ADD类 addb、addw、addl、addq。又被分为四组。加载有效地址指令:leaq、一元操作、二元操作、移位操作。
12、机器代码提供两种基本的低级机制来实现有条件的行为:测试数据值,然后根据测试的结果来改变控制流或者数据流。
13、常用的条件码:CF,进位标志 t<a、ZF,零标志 t=0、SF,符号标志t<0、OF,溢出标志。
14、跳转指令 jmp 无条件跳转,是直接跳转。jmp *Operand 间接跳转。
15、C语言中的while、for 汇编中没有相应的指令存在,可以用条件测试和跳转组合起来实现循环的效果。