Assemble 关键部分学习

October 30th, 2013 by JasonLe's Tech Leave a reply »

参考http://www.codeproject.com/Articles/5318/Extended-Inline-Assembly-in-GCC
参考http://www.cs.mun.ca/~rod/winter2004/cs3724/notes/asm.html
参考http://www.cnblogs.com/lxgeek/archive/2011/01/01/1923738.html
最近在学习assmble language,由于之前学的,最底层的语言只是C了,觉得自己学到的浮于表面,所以特下决心学了大概一周的汇编,恶补一下自己的不足。其实我们平时一般用不到这么底层的东西,但是在学习kernel的时候,从BIOS加电到载入内核这段时间,汇编仍然发挥很重要的作用。 我主要学习的是IA32,下面那个表主要是后缀,比如 mov后面会有movb movw mobl之分。

C声明 GAS后缀 大小(字节)
char b 1
short w 2
(unsigned) int / long / char* l 4

操作数格式:

格式 操作数值 名称 样例(GAS = C语言)
$Imm Imm 立即数寻址 $1 = 1
Ea R[Ea] 寄存器寻址 %eax = eax
Imm M[Imm] 绝对寻址 0x104 = *0x104
(Ea) M[R[Ea]] 间接寻址 (%eax)= *eax
Imm(Ea) M[Imm+R[Ea]] (基址+偏移量)寻址 4(%eax) = *(4+eax)
(Ea,Eb) M[R[Ea]+R[Eb]] 变址 (%eax,%ebx) = *(eax+ebx)
Imm(Ea,Eb) M[Imm+R[Ea]+R[Eb]] 寻址 9(%eax,%ebx)= *(9+eax+ebx)
(,Ea,s) M[R[Ea]*s] 伸缩化变址寻址 (,%eax,4)= *(eax*4)
Imm(,Ea,s) M[Imm+R[Ea]*s] 伸缩化变址寻址 0xfc(,%eax,4)= *(0xfc+eax*4)
(Ea,Eb,s) M(R[Ea]+R[Eb]*s) 伸缩化变址寻址 (%eax,%ebx,4) = *(eax+ebx*4)
Imm(Ea,Eb,s) M(Imm+R[Ea]+R[Eb]*s) 伸缩化变址寻址 8(%eax,%ebx,4) = *(8+eax+ebx*4)

注:M[xx]表示在存储器中xx地址的值,R[xx]表示寄存器xx的值,这种表示方法将寄存器、内存都看出一个大数组的形式。

数据传送指令:

指令 效果 描述
movl S,D D <– S 传双字
movw S,D D <– S 传字
movb S,D D <– S 传字节
movsbl S,D D <– 符号扩展S 符号位填充(字节->双字)
movzbl S,D D <– 零扩展S 零填充(字节->双字)
pushl S R[%esp] <– R[%esp] – 4;M[R[%esp]] <– S 压栈
popl D D <– M[R[%esp]];R[%esp] <– R[%esp] + 4; 出栈

注:均假设栈往低地址扩展。

算数和逻辑操作地址:

指令 效果 描述
leal S,D D = &S movl地版,S地址入D,D仅能是寄存器
incl D D++ 加1
decl D D– 减1
negl D D = -D 取负
notl D D = ~D 取反
addl S,D D = D + S
subl S,D D = D – S
imull S,D D = D*S
xorl S,D D = D ^ S 异或
orl S,D D = D | S
andl S,D D = D & S
sall k,D D = D << k 左移
shll k,D D = D << k 左移(同sall)
sarl k,D D = D >> k 算数右移
shrl k,D D = D >> k 逻辑右移

这里提一下,如果只有一个参数,就代表默认移动一位。

特殊算术操作:

指令 效果 描述
imull S R[%edx]:R[%eax] = S * R[%eax] 无符号64位乘
mull S R[%edx]:R[%eax] = S * R[%eax] 有符号64位乘
cltd S R[%edx]:R[%eax] = 符号位扩展R[%eax] 转换为4字节
idivl S R[%edx] = R[%edx]:R[%eax] % S;R[%eax] = R[%edx]:R[%eax] / S; 有符号除法,保存余数和商
divl S R[%edx] = R[%edx]:R[%eax] % S;R[%eax] = R[%edx]:R[%eax] / S; 无符号除法,保存余数和商

注:64位数通常存储为,高32位放在edx,低32位放在eax。

比较指令:

指令 基于 描述
cmpb S2,S1 S1 – S2 比较字节,差关系
testb S2,S1 S1 & S2 测试字节,与关系
cmpw S2,S1 S1 – S2 比较字,差关系
testw S2,S1 S1 & S2 测试字,与关系
cmpl S2,S1 S1 – S2 比较双字,差关系
testl S2,S1 S1 & S2 测试双字,与关系

访问条件码指令:

指令 同义名 效果 设置条件
sete D setz D = ZF 相等/零
setne D setnz D = ~ZF 不等/非零
sets D D = SF 负数
setns D D = ~SF 非负数
setg D setnle D = ~(SF ^OF) & ZF 大于(有符号>)
setge D setnl D = ~(SF ^OF) 小于等于(有符号>=)
setl D setnge D = SF ^ OF 小于(有符号<)
setle D setng D = (SF ^ OF) | ZF 小于等于(有符号<=)
seta D setnbe D = ~CF & ~ZF 超过(无符号>)
setae D setnb D = ~CF 超过或等于(无符号>=)
setb D setnae D = CF 低于(无符号<)
setbe D setna D = CF | ZF 低于或等于(无符号<=)

 跳转指令:

指令 同义名 跳转条件 描述
jmp   Label   1 直接跳转
jmp   *Operand   1 间接跳转
je     Label jz ZF 等于/零
jne    Label jnz ~ZF 不等/非零
js     Label SF 负数
jnz    Label ~SF 非负数
jg     Label jnle ~(SF^OF) & ~ZF 大于(有符号>)
jge    Label jnl ~(SF ^ OF) 大于等于(有符号>=)
jl     Label jnge SF ^ OF 小于(有符号<)
jle     Label jng (SF ^ OF) | ZF 小于等于(有符号<=)
ja     Label jnbe ~CF & ~ZF 超过(无符号>)
jae    Label jnb ~CF 超过或等于(无符号>=)
jb     Label jnae CF 低于(无符号<)
jbe    Label jna CF | ZF 低于或等于(无符号<=)

转移控制指令:(函数调用):

指令 描述
call    Label 过程调用,返回地址入栈,跳转到调用过程起始处,返回地址是call后面那条指令的地址
call    *Operand
leave 为返回准备好栈,为ret准备好栈,主要是弹出函数内的栈使用及%ebp

另外我看书的时候遇到了一个难点,就是对于switch case 的反汇编,这个的汇编实现与if else的想法不同。

switch case 在汇编实现中常用的技巧是”跳转表”,GCC会根据开关数量和稀少程度选择是否使用 跳转表 来翻译开关语句。跳转表是一个数组,表项i是代码短的地址,其执行时间与开关情况的数量无关。
我编写了一个switch函数

int switch_eg(int x, int n){
    int result = x;
    switch(n){
        case 100:
            result *= 13;
            break;
        case 102:
            result += 10;
        case 103:
            result += 11;
            break;
        case 104:
        case 106:
            result *= result;
            break;
        default:
            result = 0;
    }
    return result;
}

当我们编译不链接的时候,产生是.s的汇编文件

.cfi_startproc
    movl    4(%esp), %eax
    movl    8(%esp), %edx
    subl    $100, %edx
    cmpl    $6, %edx
    ja  .L8
    jmp *.L7(,%edx,4)
    .section    .rodata
    .align 4
    .align 4
.L7:
    .long   .L3
    .long   .L8       //case 101: default
    .long   .L4
    .long   .L5
    .long   .L6
    .long   .L8       //case 105: default
    .long   .L6
    .text
.L3:                                     //case 100: result *= 13
    leal    (%eax,%eax,2), %edx   // get 3*x
    leal    (%eax,%edx,4), %eax   //get x+4*(3x)= 13*x
    ret
.L4:                                     //case 102: result += 10
    addl    $10, %eax
.L5:                                     //case 103: result += 11
    addl    $11, %eax
    ret
.L6:                                     //case 104/106: result *= result
    imull   %eax, %eax
    ret
.L8:                                     //default: result = 0
    movl    $0, %eax
    ret
    .cfi_endproc

我们通过看L7来找到要跳转的汇编语句,因为有的case中没有break,依照C的定义,就是顺序向下执行。

ja  .L8
jmp *.L7(,%edx,4)

我们可以看到L8是default,然后我们查看.L7 看到case中的数值并不是连续的。有一些是自动默认跳转.L8

明天继续看call指令!