课程主页:https://courses.cs.washington.edu/courses/cse351/16sp/

课程资料:

实验部分:https://github.com/vuquangtrong/HW-SW_Interface_Course

实验说明:https://courses.cs.washington.edu/courses/cse351/13sp/lab-0.html

课件:http://academictorrents.com/details/b63a566df824b39740eb9754e4fe4c0140306f4b

课程视频:https://www.bilibili.com/video/BV1Zt411s7Gg?from=search&seid=8781593976070799647

这次回顾第四部分,这部分介绍了汇编码。

第4节:x86汇编编程

移动指令、寄存器和操作数

三种基本指令
  • 内存与寄存器之间的数据传输
    • 将数据从内存加载到寄存器
      • $\%\text{reg = Mem[address]}$
    • 将寄存器数据存入内存
      • $\text{Mem[address] = }\%\text{reg}$
  • 对寄存器或内存数据执行算术函数
    • $\text{c=a+b}$
  • 控制转移
    • 无条件跳转
    • 条件分支
移动数据
  • 移动数据

    movq Source, Dest:
  • 操作数类型

    • 立即数(Immediate):常量整型数据
      • 例如:$$ 0 \times 400, $-533$
      • 类似于C常量,但以$$$开头
      • 1,2,或4个字节编码
    • 寄存器(Register):16个整数寄存器之一
      • 例子:%rax, %r13
      • 但是%rsp保留作特殊用途
    • 内存(Memory):在寄存器指定的地址处连续8个字节的内存
      • 最简单的例子:(%rax)
      • 其他各种“地址模式”

各种组合如下:

内存寻址方式

完整的地址寻址模式
  • 最一般的形式

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

    • D:常量“位移” ,$ 1,2,$或4个字节
    • Rb:基地址寄存器:16个整数寄存器中的任何一个
    • Ri:索引寄存器:任何,除了%rsp
    • S:比例系数:$ 1,2,4$或$8$
  • 特殊情形

全部情形如下:

例子

32位 vs 64位操作

  • 4比特使用l$\leftrightarrow$8比特使用q
  • 新指令:
    • $\text{movl}\to\text{movq}$
    • $\text{addl}\to\text{addq}$
    • $\text{sall}\to\text{salq}$
  • x86-64仍然可以使用32位指令来生成32位结果
    • 目的寄存器高位被置0。
    • 示例:$\text{addl}$

算术运算

地址计算指令
  • leaq Src,Dst
    • Src是地址模式表达式
    • 将Dst设置为表达式表示的地址
  • 用途
    • 在没有内存引用的情况下计算地址
      • 例如,翻译$ p = \& x [i] $
    • 计算形式为$ x + k * y $的算术表达式
      • $ k = 1,2,4,$或8

例子:

long m12(long x) 
{
	return x*12; 
}

转换成汇编语言得到

leaq (%rdi,%rdi,2), %rax # t <- x+x*2
salq $2, %rax 			# return t<<2
某些算数指令
  • 二元指令

  • 一元指令

条件码

条件码(隐式设置)
  • 单比特寄存器

    • CF:进位标志(无符号)
    • SF:符号标志(有符号)
    • ZF:零标记
    • OF:溢出标志(有符号)
  • 通过算术运算隐式设置

    • 例如:addq Src, Dest $ \leftrightarrow t = a + b $

    • 如果最高有效位进位,则CF置为1(无符号溢出)

    • 如果$ t == 0 $,则ZF置为1

    • 如果$ t <0 $,则SF置为1

    • 如果

      则OF置为1

  • leaq指令不会改变条件码

条件码(显式设置)
  • 通过比较指令进行显式设置

    • cmpq Src2,Src1

    • cmpq b,类似于计算a-b而不设置

    • 如果最高有效位进位,则CF置为1(无符号溢出)

    • 如果$ a == b $,则ZF置为1

    • 如果$ (a-b) <0 $,则SF置为1

    • 如果

      则OF置为1

  • 通过测试指令进行显式设置

    • testq Src2,Src1
      • testq b,a类似于计算$a \& b$而不设置dest
    • 根据$ \operatorname {Src} 1\& \operatorname {Src} 2 $的值设置条件码
    • 使一个操作数成为掩码很有用
    • 当$ a \& b == 0 $时设置ZF为1
    • 当$a \& b <0 $时设置SF为1

完整比较测试指令如下:

读取条件码
  • SetX指令
    • 根据条件码的组合,将dest的低位字节设置为0或1
    • 不会更改剩余的7个字节

来看一个具体例子:

int gt (long x, long y)
{
	return x > y;
}

汇编代码为

cmpq %rsi, %rdi 	# Compare x:y
setg %al 		    # Set when >
movzbl %al, %eax     # Zero rest of %rax
ret

变量和寄存器的对应关系为

条件分支和无条件分支

跳转指令

条件分支例子

考虑如下例子:

long absdiff(long x, long y)
{
    long result;
    if (x > y)
    	result = x-y;
    else
    	result = y-x;
    return result;
}

为了和汇编码格风格相近,首先将上述代码改写为goto的格式:

long absdiff_j(long x, long y)
{
    long result;
    int ntest = x <= y;
    if (ntest) goto Else;
    result = x-y;
    goto Done;
 Else:
    result = y-x;
 Done:
    return result;
}

汇编码为

absdiff: 
    cmpq %rsi, %rdi # x:y
    jle .L4
    movq %rdi, %rax
    subq %rsi, %rax
    ret
.L4: # x <= y
    movq %rsi, %rax
    subq
    ret

变量和寄存器的对应关系为

一般情形如下:

C语言:

val = Test ? Then_Expr : Else_Expr;

汇编码:

    ntest = !Test; 
    if (ntest) goto Else;
    val = Then_Expr;
    goto Done;
Else: 
	val = Else_Expr;
Done:
	. . .

循环

汇编语言的循环都是goto模式,所以C中的循环首先要转换为goto模式,转换方法如后续所述,这里给出汇编语言循环的例子:

C代码

int fact_do(int x)
{
  int result = 1;
  do {
    result *= x;
    x = x-1;
  } while (x > 1);
  return result;
}

Goto版本

int fact_goto(int x)
{
  int result = 1;
loop:
  result *= x;
  x = x-1;
  if (x > 1) goto loop;
  return result;
}

汇编代码

fact_goto:
	pushl %ebp		 	# Setup
	movl %esp,%ebp	 	# Setup
	movl $1,%eax	 	# eax = 1
	movl 8(%ebp),%edx	 # edx = x

.L11:
	imull %edx,%eax		# result *= x
	decl %edx			# x--
	cmpl $1,%edx		# Compare x : 1
	jg .L11				# if > goto loop

	movl %ebp,%esp		# Finish
	popl %ebp			# Finish
	ret					# Finish
一般的”Do-While”翻译

Do-While代码

do
    Body
    while (Test);

Goto版本

loop:
    Body
    if (Test)
    	goto loop

其中Body的形式如下

{ Statement1; Statement2; … Statementn; }
一般的”While”翻译

While代码

while (Test)
	Body

Goto版本

	goto test;
loop:
	Body
test:
    if (Test)
    	goto loop;
done:

另一种方法是将While修改为Do-While

if (!Test)
	goto done;
do
    Body
    while(Test);
done:

Goto版本

if (!Test)
	goto done;
loop:
	Body
	if (Test)
		goto loop;
done:
一般的”For”循环翻译

For循环形式

for (Init; Test; Update )
	Body

修改为While循环

Init;
while (Test) {
    Body
    Update;
}

Switch语句

跳转表结构

Switch分支使用了跳转表结构:

跳转表将分支的条件转换为0到n,然后补齐中间缺失的部分。

例子

C代码

long switch_eg (long x, long y, long z)
{ long w = 1;
    switch(x) { 
    case 1:				//.L3
        w = y*z;
        break; 
    case 2:				//.L5
        w = y/z;
        /* Fall Through */ 
    case 3:				//.L9
    	w += z; 
    	break;
    case 5:				
    case 6: 			//.L7
    	w -= z;
    	break; 
    default:			//.L8
    	w = 2;
    } 
    return w;
}

跳转表

.section .rodata
	.align 8
.L4:
    .quad .L8 # x = 0
    .quad .L3 # x = 1
    .quad .L5 # x = 2
    .quad .L9 # x = 3
    .quad .L8 # x = 4
    .quad .L7 # x = 5
    .quad .L7 # x = 6
汇编码例子

汇编码

switch_eg:
  . . .
	ja    .L61	# if > goto default
	jmp   *.L62(,%edx,4)	# goto JTab[x]

反汇编结果

08048610 <switch_eg>:
 . . .
08048622:  77 0c                   ja     8048630 
08048624:  ff 24 95 dc 88 04 08    jmp    *0x80488dc(,%edx,4)

 8048630:       bb 02 00 00 00          mov    $0x2,%ebx
 8048635:       89 d8                   mov    %ebx,%eax
 8048637:       5b                      pop    %ebx
 8048638:       c9                      leave
 8048639:       c3                      ret
 804863a:       8b 45 0c                mov    0xc(%ebp),%eax
 804863d:       99                      cltd
 804863e:       f7 f9                   idiv   %ecx
 8048640:       89 c3                   mov    %eax,%ebx
 8048642:       01 cb                   add    %ecx,%ebx
 8048644:       89 d8                   mov    %ebx,%eax
 8048646:       5b                      pop    %ebx
 8048647:       c9                      leave
 8048648:       c3                      ret
 8048649:       29 cb                   sub    %ecx,%ebx
 804864b:       89 d8                   mov    %ebx,%eax
 804864d:       5b                      pop    %ebx
 804864e:       c9                      leave
 804864f:       c3                      ret
 8048650:       8b 5d 0c                mov    0xc(%ebp),%ebx
 8048653:       0f af d9                imul   %ecx,%ebx
 8048656:       89 d8                   mov    %ebx,%eax
 8048658:       5b                      pop    %ebx
 8048659:       c9                      leave
 804865a:       c3                      ret

查看跳转表的方法

gdb asm-cntl
(gdb) x/7xw 0x080488dc

0x080488dc:
  0x08048630
  0x08048650
  0x0804863a
  0x08048642
  0x08048630
  0x08048649
  0x08048649