tarzan
22
所在小组
第七组
组内昵称
陈盛华
你的心得体会
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 SRC ;POP DST
- 所在小组:第2组
- 组内昵称:跃山
- 心得体会:
程序的机器级标识
- 程序编码
- 数据格式
- 访问信息
- 算数和逻辑操作
- 控制
- 过程
- 数组分配和访问
- 异质的数据结构
- 在机器级程序中将控制和数据结合起来
- 浮点代码
深入理解计算机系统-程序的机器级表示
![]()
我们可以通过汇编代码来对系统进行优化,能够阅读和理解汇编代码是一项很重要的技能。
程序编码
- 机器级代码
- 利用计算机中多种不同形式的抽象模型实现程序细节,有两种模型
-
指令集
。用来定义程序的格式和行为。
-
虚拟内存
。机器级别程序使用的内存地址是虚拟地址。
- 使用指令集架构提供的指令,CPU顺序执行指令,指令会影响CPU、寄存器等的状态,也就是能够并行,也遵循顺序执行的模型。
- 使用虚拟内存地址
- PC寄存器
- 整数寄存器64位
- 条件码寄存器,存放最近的状态,实现if,while等条件。
- 向量寄存器,存储一组数据。
- 机器代码把内存看成一个很大、按字节寻址的数组,各种数据类型在机器代码中都是一组连续的字节表示,也不区分无符号、有符号整数,不区分指针和整数,不区分各种类型的指针。
- 关于格式的注解
数据格式
Intel规定一个字(word)等于16 位,两个字节。因此双字为32位,四字为64位。在64位机器中,指针就是四字。
-
movb
表示移动半个字,1个字节
-
movw
移动1个字,2个字节
-
movl(long word)
移动2个字
-
movq
移动4个字
访问信息
- 操作数指示符
-
立即数
。$5就是一个直接数字,数字字面量。
-
绝对寻址
。0x400001直接写死一个内存地址,没有$符号,表示拿出这个内存地址的值。
-
ra
。直接写某个寄存器,代码这个寄存器中的值R[ra](R 可以看成所有寄存器的数组,ra 是取值下标)**寄存器寻址
;
-
(ra)
。M[R[ra]]:在内存中取值寄存器中存储地址的值(M 代表内存)内存寻址;
- 数据传送指令(操作数指示符就相当于数据传送指令的参数,把一个地址的数复制到另一个地址。)
- mov S, D = Source -> Dest
- b、w、l、q 分别代表复制 8、16、32、64 个字节。
- 内存地址不能复制到内存地址,需要寄存器中转。
- movz 指令会对不够长度的源值在目的地址填充0。
- movs 系列指令会对不够长度的情况填充符号扩展。
- 压入和弹出栈数据
- push、pop 操作栈数据结构,会修改栈指针的值,其指向内存的某个区域。
- 根据惯例,栈向下生长,下面是栈底push 时栈指针值会减 8,也就是上面的地址大。
- 因为栈在内存中,也就是大数组的一部分,因此经常使用偏移栈指针的方式访问数据,例如:movq 8(%rsp), %rdx 将第二个四字从栈中复制到寄存器 %rdx。
算数和逻辑操作
- 加载有效地址
- 一元和二元操作
- 移位操作
- 特殊的算术操作
控制
- 条件码
- 常用条件码
-
CF
进位标志。
-
ZF
零标志。
-
SF
符合标志。
-
OF
溢出标志。
- 使用方式
- 组合条件码,将一个字节设置为0/1
- 跳转到程序的指定部分
- 有条件的传送数据
- 访问条件码
- 跳转指令
- 跳转指令的编码
- 用
条件控制
来实现条件分支
- 用
条件传送
来实现条件分支
- 条件传送不一定就能提高代码的效率,在处理带分支的代码时候,编译器并没有足够的信息来进行可靠的分支预测,GCC在只有当两个表达式都容易计算的时候才会使用条件传送;
- 循环语句
- switch语句
过程
- 运行时栈
- 转移控制
- 数据传送
- 栈上的局部转储
- 寄存器中的局部存储空间
- 递归过程
数组分配和访问
- 基本原则
- 指针运算
- 嵌套的数组
- 定长数组
- 变长数组
异质的数据结构
- 结构
- 联合
- 数据对齐
在机器级程序中将控制与数据结合起来
- 理解指针
- 使用GDB调试器
-
内存越界引用
和缓冲区溢出
- 对抗缓冲区溢出攻击
- 栈随机化
- 栈破坏检测
- 限制可执行代码区域
- 支持变长栈帧
浮点代码
- 浮点传送和转换操作
- 过程中的浮点代码
- 浮点运算操作
- 定义和使用浮点常数
- 在浮点代码中使用位级操作
- 浮点比较操作
- 对浮点代码的观察结论
所在小组
第六组
组内昵称
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:溢出标志
有几类指令能够修改条件码:
- 算术指令:既改变操作数,也有肯能改变条件码。
- CMP指令:右操作数减左操作数,只可能改变条件码。
- TEST指令:两操作数相与,只可能改变条件码。
条件码寄存器不能直接读取,有三种方法:
- set指令:根据条件码,设置一个字节。
- jump指令:根据条件码进行跳转,即控制的条件转移。
- cmov条件传送指令:根据条件码决定是否进行mov操作(其性能要优于控制的条件转移P141)。
所在小组
第二组
组内昵称
梁广超
心得体会
1、几个比较特别的寄存器
%rsp 是栈寄存器,存储程序栈的栈顶
%rbx % rbp %12~%15被调用者保护(其余除了%rsp 都可以被其他过程修改)
%rax 是返回值
2、一个过程的通用栈帧结构包括
被保存的寄存器、局部变量、参数构造区以及可能存在的返回地址
3、一个函数的参数太多,不会被全部放入寄存器,没有放入寄存器的参数会被存储在调用者的栈帧
4、栈帧先进后出的形式符合程序的过程调用,可以让过程调用开始时分配资源,过程调用结束时释放资源
5、条件码寄存器 记录执行的算术或逻辑指令的状态信息,用来实现控制程序数据流的走向
zsy619
31
所在小组
第六组
组内昵称
慎思明辨笃行
你的心得体会
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:溢出标志
blue
33
所在小组:第一组
组内昵称:bluewhale
心得体会:
gcc main.c -o main
gcc命令调用了一整套程序,将源码转换成可执行代码
- 预处理 插入#include命令指定的文件
- 编译 生成汇编文件
- 汇编 将汇编生成二进制代码
- 链接 目标代码文件与实现库合并
操作数指示符
多数指令会有一个或多个操作数,其类型包括三种:
- 立即数 表示常数值,用$后面跟一个整数表示
- 寄存器 表示某个寄存器的内容
- 内存引用 会根据计算出来的地址访问某个内存位置
数据传送指令
将数据从一个位置复制到另一个位置的指令。
源操作数指定的值是一个立即数,存储在寄存器或者内存中。目的操作数指定一个位置,可以是寄存器或者内存地址。
规定传送指令的两个操作数不能都指向内存位置
当将较小的源值复制到较大的目的时使用零扩展数据传送指令。
- MOVZ 把目的中剩余的字节填充为0
- MOVS 通过符号扩展来进行填充,把源操作的最高位进行复制
压入和弹出栈数据
程序栈存放在内存中的某个位置,向下增长,栈顶元素的地址就是所有栈中元素最低的。其中寄存器%rsp
指向栈顶元素的地址。
- pushq 实现将数据压入栈
- 首先将栈顶指针地址减8
- 将数据写入栈顶地址指向的位置
- popq 实现将数据弹出栈
算数和逻辑操作
每个指令类都对应有四种不同大小数据的指令。
运行时栈
x86-64过程需要的存储空间超出寄存器大小后,会在栈上分配空间,这个过程称为栈帧
以过程P调用Q,Q执行后返回P为例
P调用Q会将Q返回后下一条执行的P的代码作为P栈帧的一部分。Q则会扩展当前栈的边界,分配其栈帧所需要的空间。
寄存器中的局部存储空间
寄存器组是唯一被所有过程共享的资源。我们需要确保一个过程(调用者)调用另一个过程(被调用者)时,被调用者不会覆盖调用者稍后会使用的寄存器的值。
寄存器 %rbx %rbp 和 %r12 ~ %r15被划分为被调用者保存寄存器 当过程P调用过程Q时,Q必须保存这些寄存器的值,保证在Q返回到P时不变。
sadame
35
所在小组
第一组
组内昵称
SADAME
心得体会
-
C 语言代码(da.c, wang.c)经过编译器的处理(gcc -0g -S
)成为汇编代码(da.s, wang.s)
-
汇编代码(da.s, wang.s)经过汇编器的处理(gcc
或 as
)成为对象程序(da.o, wang.o)
-
对象程序(da.o, wang.o)以及所需静态库(lib.a)经过链接器的处理(gcc
或 ld
)最终成为计算机可执行的程序
-
程序计数器(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指令的使用在返回前,既重置了栈指针,也重置了基址指针。
RockLD
38
所在小组
第二组
组内昵称
文弱书生
你的心得体会
数据格式:
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.可以有条件的传送数据
直接跳转和间接跳转
过程调用过程涉及 传递控制、 传递数据、 分配和释放内存过程
栈数据结构提供的 后进先出 的内存管理原则。
we8105
41
所在小组
第二组
组内昵称
陶鑫
你的心得体会
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 汇编中没有相应的指令存在,可以用条件测试和跳转组合起来实现循环的效果。