IA32架构CPU下的汇编编程学习
通常掌握某一特定机器的汇编编程技术需要一定的时间。然而,如果掌握其他处理器的汇编编程(例如ARM,MIPS等),那么学习IA32结构CPU下的汇编编程将会省去很多时间。以下对IA32架构下的汇编语言编程进行简单的总结,方便以后回头来温习
1.指令语法
关于IA32架构的CPU的汇编语言的语法和表示有两种约定:Intel和AT&T,大多数的文件使用Intel的约定,而GNU汇编器使用AT&T的约定,这两种约定之间主要的不同如下表所示:
GNU 语法(AT&T) | Intel | |
立即数 | 前面需要加上"$" 例如:push $4 movl $0xd00a, %eax |
没有限制 例如:push 4 mov ebx d00ah |
寄存器 | 前面需要加上“%” 例如:%eax |
无限制 例如:eax |
参数顺序(例如: 把C变量“foo”加到 寄存器EAX中) |
source1, [source2,] dest 例如:addl $_foo, %eax |
des, source1 [,source2] 例如:add eax, _foo |
单操作数 | 指明操作数的大小(用{b, w, l}) 例如:movb foo, %al |
用寄存器名间接的指明操作数的大小, byte ptr, word ptr 和dword ptr 例如:mov al, foo |
寻址一个C变量"foo" | _foo | [_foo] |
寻址寄存器指定的内存(例如,EAX) | (%eax) | [eax] |
寻址寄存器中值的变量偏移值 | _foo(%eax) | [eax + _foo] |
寻址32-bit整数的数组"foo"中的值 | _foo(,%eax,4) | [eax * 4 + foo] |
等效于C代码中的*(p +1) | 1(%eax) | 如果EAX中存储的是p的值,则[eax + 1] |
2. 内存操作
IA32处理器使用分段内存架构,也就是说内存位置需要通过段选择子和偏移来确定。
3.经常使用的指令
下表中所示是一些经常使用的指令。
类别 | 指令 | 解释 |
数据转移 |
mov{l,w,b} source, dest |
移动source到dest |
xchg{l,w,b} dest1, dest2 |
交换 |
|
cmpxchg{l,w,b} dest1, dest2 |
比较和交换 |
|
push/pop{l,w} |
入栈/出栈 |
|
movsb |
移动DS:(E)SI 的bytes到地址ES:(E)DI处,典型的使用前缀rep |
|
算术 |
add/sub{l,w,b} source, dest |
加减 |
imul/mul{l,w,b} formats |
有符号/无符号乘 |
|
idiv/div{l,w,b} dest |
有符号/无符号除 |
|
inc/dec/neg{l,w,b} dest |
增1/减1/取负 |
|
cmp{l,w,b} source1, source2 |
比较 |
|
逻辑 |
and/or/xor/not{l,w,b} source, dest |
逻辑与/或/异或/非操作 |
sal/sar{l,w,b} formats |
算术左移/右移 |
|
shl/shr{l,w,b} formats |
逻辑左移/右移 |
|
控制转移 |
jmp address |
无条件跳转 |
call address |
保存EIP到堆栈中,然后跳转到address处 |
|
ret |
返回到通过call调用保存的EIP处 |
|
leave |
从堆栈中恢复EBP; pop off the stack frame |
|
j{e,ne,l,le,g,ge} address |
如果{=,!=,<,<=,>,>=},跳转到address处 |
|
loop address |
对ECX或 CX进行减1操作;如果 = 0,则跳转 |
|
rep |
重发串操作的前缀 |
|
int number |
软件中断 |
|
iret |
中断返回,EFLAGS 从堆栈中出栈 |
另外,用于长跳转的名字为ljmp,长调用为lcall
这里只列了一小部分的命令,IA32 Intel Architecture Software Developer's Manual, Volume 2的3.2节对IA32的所有指令进行了详细的描述,在Intel手册中使用的指令名字是按照Intel汇编语法约定的。
4.汇编指令
GNU汇编器指令名字以句点“.”开始,然后剩下的是小写字母,以下是常用的一些指令:
.ascii "string foo" 定义了ASCII串“string foo”
.asciz "string foo" 定义了ASCII串“string foo”,以0结尾
.string "string foo" 与.asicz "string foo"相同
.align 4 以双字边界来进行内存对齐
.byte 10, 13, 0 定义了三个字节
.word 0x0456, 0x1234 定义了两个字
.long 0x001234, 0x12345 定义了两个长字
.equ STACK_SEGMENT, 0X9000 设置符号STACK_SEGMENT的值为0x9000
.globl symbol 让“symbol”全局可见(对于定义全局标签和程序名字非常有用)
.code16 告诉汇编器插入适合的重写前缀以其运行在实模式下
5. 内联汇编(Inline Assembly)
通过gcc编译器生成的内联汇编代码的最基本的格式如下:
asm volatile("assembly-instruction");
assembly-instruction将会内联 到asm语句所在的地方,关键字volatile是可选的。该关键字告诉gcc编译器不要去优化该条指令。对于没有寄存器的内联汇编指令写起来非常的方便,例如:
asm volatile("cli");
用于禁止中断
asm volatile("sti");
用于开启中断
在C代码中编写内联汇编代码的通用格式如下:
asm [volatile] ("statements": output_regs: input_regs: used_regs);
其中statements是汇编指令, 如果有多于一条指令,可以使用“\n\t”来分开它们,让其看起来舒服点,
“input_regs”告诉gcc编译器哪个C变量移动到哪个寄存器,例如,想加载变量"foo"到寄存器EAX中,以及变量"bar"到寄存器ECX,可以些微:
:“a”(foo), "c"(bar)
gcc使用单个字母来表示所有的寄存器
单个字母 | 寄存器 |
a | eax |
b | ebx |
c | ecx |
d | edx |
S | esi |
D | edi |
I | 常数值(0到31) |
q | 从EAX,EBX,ECX,EDX分配一个寄存器 |
r | 从EAX,EBX,ECX,EDX,ESI,EDI中分配一个寄存器 |
"output_regs"提供了输出寄存器,一种方便的方法是让gcc编译器来选择寄存器,只需要指定“=q”或“=r”来让gcc编译器选择寄存器,可以通过利用“%0”来指定第一个分配的寄存器,“%1”指定第二个寄存器,等等。在汇编指令中,如果在输入寄存器列表中指定了寄存器。可以只需要“0”或“1”,不要前缀“%”
“used_regs”列出了在汇编代码中使用的寄存器。
下面给出了一段内联汇编包含在C代码中的例子。
asm ("leal (%1,%1,4), %0"
: "=r" (x_times_5)
: "r" (x) );
和
asm ("leal (%0,%0,4), %0"
: "=r" (x)
: "0" (x) );
6. 汇编程序结构和调用约定
最简单的学习汇编编程的方法是把简单的C程序编译为汇编源代码,该源代码将会显示常用的操作码,指令和寻址语法。这是一种有效的方法来学习汇编编程。
下面通过一个例如来说明汇编程序结构和调用约定,考虑如下的C程序hello.c
#include
static char buf[4096];
int foo(int n)
{
return n - 1;
}
int main(void)
{
printf("Hello world\n");
return f00(5);
}
gcc -S hello.c
gcc编译器将会编译hello.c为汇编源代码hello.s,
以下是hello.s的源代码
.file "hello.c"
.local buf
.comm buf,4096,32
.text
.globl foo
.type foo, @function
foo:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
movq %rsp, %rbp
.cfi_offset 6, -16
.cfi_def_cfa_register 6
movl %edi, -4(%rbp)
movl -4(%rbp), %eax
subl $1, %eax
leave
ret
.cfi_endproc
.LFE0:
.size foo, .-foo
.section .rodata
.LC0:
.string "Hello world"
.text
.globl main
.type main, @function
main:
.LFB1:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
movq %rsp, %rbp
.cfi_offset 6, -16
.cfi_def_cfa_register 6
movl $.LC0, %edi
call puts
movl $5, %edi
movl $0, %eax
call f00
leave
ret
.cfi_endproc
.LFE1:
.size main, .-main
.ident "GCC: (Ubuntu 4.4.3-4ubuntu5.1) 4.4.3"
.section .note.GNU-stack,"",@progbits
可以编写一些其他的简单C代码,然后编译为汇编代码来查看汇编源代码
来自于http://zoo.cs.yale.edu/classes/cs422/2011/ref/pc-arch#memory