CMU15213_Lecture_06_Machine Level Programming II: Arithmetic & Control

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u014485485/article/details/83183982

试图还原老师讲课的思路。

 

这节课就是讲汇编的。

Complete addressing mode, address computation

Complete Memory Addressing Modes:

D(Rb,Ri,S)    Mem[Reg[Rb]+S*Reg[Ri]+ D]

举个例子,

(%edx,%ecx,4)  0xf000 + 4*0x100  0xf400

0x80(,%edx,2)  2*0xf000 + 0x80   0x1e080

 

lea指令的功能是将一个内存地址直接赋给目的操作数,mov指令的功能是传送数据

 

lea eax,[ebx+8]就是将ebx+8这个值直接赋给eax,

mov eax,[ebx+8]则是把内存地址为ebx+8处的数据赋给eax。

 

movl  v, %eax     传送v值到eax

(movl  $v, %edi     传送v地址到edi)

 

mov %eax,%esp

This copies the value in %eax into %esp.

 

mov %eax,(%esp)

This copies the value from %eax to the location in memory that %esp points to.

举个例子,

 

lea:Load Effective Address,加载有效地址

Sal:Shift Arithmetic Left,算术左移

 

Arithmetic operations

 

重点:一个汇编的案例

预备知识:

mov和lea的区别;
ESP(STACK POINTER)是栈顶指针,EBP(BASE POINTER)是存取堆栈指针

下面是按调用约定__stdcall 调用函数test(int p1,int p2)的汇编代码:

假设执行函数前堆栈指针ESP为NN

push   p2    ;参数2入栈, ESP -= 4h , ESP = NN - 4h

push   p1    ;参数1入栈, ESP -= 4h , ESP = NN - 8h

call test    ;压入返回地址 ESP -= 4h, ESP = NN - 0Ch 

//进入函数内

{

push   ebp                        ;保护先前EBP指针, EBP入栈, ESP-=4h, ESP = NN - 10h

mov    ebp, esp                   ;设置EBP指针指向栈顶 NN-10h

mov    eax, dword ptr  [ebp+0ch]  ;ebp+0ch为NN-4h,即参数2的位置

mov    ebx, dword ptr  [ebp+08h]  ;ebp+08h为NN-8h,即参数1的位置

sub    esp, 8                     ;局部变量所占空间ESP-=8, ESP = NN-18h

...

add    esp, 8                     ;释放局部变量, ESP+=8, ESP = NN-10h

pop    ebp                        ;出栈,恢复EBP, ESP+=4, ESP = NN-0Ch

ret    8                          ;ret返回,弹出返回地址,ESP+=4, ESP=NN-08h, 后面加操作数8为平衡堆栈,ESP+=8,ESP=NN, 恢复进入函数前的堆栈.

}

 

前两句类似于初始化,让ebp指向当前栈顶,地址加4,那么x地址为8,y为12,z为16,也就是说把ebp+8就找到x了。此时我们看Body部分

第一句,movl 8(%ebp), %ecx,此时地址为ebp+8,取的是x,存到ecx中;

第二句,movl 12(%ebp), %edx,把y存到edx;

第三句,leal (%edx,%edx,2), %eax,将edx+2*edx,也就是3*edx存到eax中,

第四句,sall $4, %eax,将eax左移4位,效果就是3edx*2^4=48edx,也就是y*48;

第五句,leal 4(%ecx,%eax), %eax,就是ecx+eax+4就是x+4+48y,就是t3+t4,存到eax中,就是变量t5;

第六句,addl %ecx, %edx,就是ecx+edx=x+y,存到edx中;

第七句,addl 16(%ebp), %edx,就是(ebp+16)+ edx,就是z+(x+y),就是变量t2;

第八句,imull %edx, %eax,就是edx*eax,就是t2*t5=(x+y+z)*(x+4+48*y);

第九句,返回rval。

over。

 

不过,我有一个疑问,编译器怎么做计算上的微调的,那个移位还能理解,为了减少计算量,那个x+48y+4为什么这么调整?

 

存放好局部变量后的栈:

 

 

另一个汇编的案例

Body部分:

movl 12(%ebp),%eax # eax = y

xorl 8(%ebp),%eax # eax = x^y (t1)

sarl $17,%eax # eax = t1>>17 (t2)

andl $8185,%eax # eax = t2 & mask (rval)    2^13 – 7 = 8185

 

 

Control: Condition codes

 

这一节谈谈状态码。

CF Carry Flag (for unsigned)

SF Sign Flag (for signed)

ZF Zero Flag

OF Overflow Flag (for signed)

 

举两个例子:

第一个:

Implicit Setting(think of it as side effect):

addl/addq Src,Dest↔ t = a+b

CF set if carry out from most significant bit (unsigned overflow)

ZF set if t == 0

SF set if t < 0 (as signed)

OF set if two’s-complement (signed) overflow (a>0 && b>0 && t=0)

 

第二个:

Explicit Setting: Compare

cmpl b,a like computing a-b without setting destination

CF set if carry out from most significant bit (used for unsigned comparisons)

ZF set if a == b

SF set if (a-b) < 0 (as signed)

OF set if two’s-complement (signed) overflow (a>0 && b0 && (a-b)>0)

 

读取状态码

set指令

只改变最低位,其他三位不变。

 

举个例子:

movl 12(%ebp),%eax # eax = y

cmpl %eax,8(%ebp) # Compare x : y

setg %al # al = x > y

movzbl %al,%eax # Zero rest of %eax 这个指令的意思是move zero-extended byte to long

 

Conditional branches

Jumping

跳转到代码不同部分

 

还是举两个例子实在

例子1:

 

 

在C语言里这段代码可以转换成

这样对应就好理解汇编代码了。goto和jump是直接关联的。

edx里存x,eax里存y,然后比较x和y,less or equal的话y-x,否则x-y,最终结果都存在eax中。最终返回eax。

 

 

条件表达式:val = Test ? Then_Expr : Else_Expr;

一般转换:
 

可以看出这里有分支,只会执行一个分支。

这样不利于指令的流水,而且需要control transfer。所以IA32 & x86-64会采用下面这种Conditional Moves的方式:

 

下面就Conditional Moves我们再举一个x86-64指令集下的例子:

最后依据指令的意思是move if greater。

 

由于Conditional Moves是把所有情形都算出来,所以有时候会带来一些副作用,我们在使用时要避免。

1.分支计算太复杂。

val = Test(x) ? Hard1(x) : Hard2(x);

这样的话还不如用条件判断,只计算一个分支。

2.计算时会有风险。

val = p ? *p : 0;

指针p的有效的则获取其值,如果p为0 不就获取了错误的值吗,需谨慎处理

3.计算带有副作用。

val = x > 0 ? x*=7 : x+=3;

这里同时会改变x和val的值,所以要谨慎。

 

 

Loops

1.“Do-While”

 

 

body必执行一次。

 

2.“While”

 

3.“For”

 

执行流程:

 

for和while是可以互换的。

 

由此顺利得到其goto version:

 

这里唯一需要强调的点是,在编译时期,i=0,WSIZE>0,编译器可以确定i<WSIZE,所以将其优化掉了。

汇编代码:

 movl 8(%ebp), %edi   #x装进edi,这是变量x

 movl $0, %eax        #0装进eax,这个变量是result

 movl $0, %ecx        #0装进ecx  这个是变量i

 movl $1, %edx        #1装进edx

.L13:

 movl %edx, %esi      #1赋给esi

 sall %cl, %esi       #%cl是%cx的低8位,这里的意思是将1算数左移i位,这是变量mask

 testl %edi, %esi     #效果等同于x & mask,改变了标志位

 setne %bl            #这里其实是读取zero flag,如果不为0,bl就存1

 movl %ebx, %esi      #将bl的结果赋给esi

 andl $255, %esi      #esi的结果还是bl

 addl %esi, %eax      #把这个结果加上result

 addl $1, %ecx        #i++

 cmpl $32, %ecx       #把i个8*4=32比较

 jne .L13             #非0就跳转,由于i是从0开始增长的,所以i-32是从负数增长到0的

 

 

注:

80386有如下寄存器:

1、8个32-bit寄存器 %eax,%ebx,%ecx,%edx,%edi,%esi,%ebp,%esp; 

2、8个16-bit寄存器,它们事实上是上面8个32-bit寄存器的低16位:%ax,%bx,%cx,%dx,%di,%si,%bp,%sp; 

3、8个8-bit寄存器:%ah,%al,%bh,%bl,%ch,%cl,%dh,%dl。它们事实上是寄存器%ax,%bx,%cx,%dx的高8位和低8位; 

4、6个段寄存器:%cs(code),%ds(data),%ss(stack), %es,%fs,%gs; 

5、3个控制寄存器:%cr0,%cr2,%cr3; 

6、6个debug寄存器:%db0,%db1,%db2,%db3,%db6,%db7; 

7、2个测试寄存器:%tr6,%tr7; 

8、8个浮点寄存器栈:%st(0),%st(1),%st(2),%st(3),%st(4),%st(5),%st(6),%st(7)。

 

test和cmp指令运行后都会设置标志位。

test属于逻辑运算指令

功能:执行BIT与BIT之间的逻辑运算,测试(两操作数作与运算,仅修改标志位,不回送结果)。

Test对两个参数(目标,源)执行AND逻辑操作,并根据结果设置标志寄存器,结果本身不会保存。TEST AX, BX 与 AND AX, BX 命令有相同效果。

Test的一个非常普遍的用法是用来测试一方寄存器是否为空:

test ecx, ecx

jz somewhere

 

CMP属于算术运算指令

功能: 比较两个值(寄存器,内存,直接数值)

CMP比较.(两操作数作减法,仅修改标志位,不回送结果)。

cmp实际上是只设置标志不保存结构的减法,并设置Z-flag(零标志)。

 

 

这节课讲了地址的概念,地址的计算,操作符,控制语句(状态码),条件分支,条件move,循环(do…while,while,for),下一节课将switch,stack,call/return。

 

 

猜你喜欢

转载自blog.csdn.net/u014485485/article/details/83183982
今日推荐