本文起源:想看看C/C++中的Strict Aliasing,可是找到的资料几乎都用C/C++对应的汇编代码来解释一些概念。看不懂这些汇编,只好先简单整理一点汇编相关的资料了。
找几个编译器,找个最最简单的C程序,动手试试,于是...
生成汇编代码
比如,一个简单的函数:
// hello.c void func(){}
如何得到相应的汇编代码呢?
gcc -S hello.c | hello.s |
clang -S hello.c | hello.s |
gcc -S -masm=intel hello.c | hello.s |
cl /FAs /C hello.c | hello.asm |
看些结果:
- gcc 4.5.2, Ubuntu11.04 X86 默认的汇编
.globl func .type func, @function func: pushl %ebp movl %esp, %ebp popl %ebp ret
- clang 2.8, Ubuntu11.04 X86
.globl func .align 16, 0x90 .type func,@function func: pushl %ebp movl %esp, %ebp popl %ebp ret .Ltmp0: .size func, .Ltmp0-func
- gcc 4.5.2, Ubuntu11.04 X86 Intel格式的汇编
.globl func .type func, @function func: push ebp mov ebp, esp pop ebp ret
- cl 16 (即VC10), Windows Xp
_func PROC ; 1 : void func(){} push ebp mov ebp, esp pop ebp ret 0 _func ENDP
- 顺便看一个Sun Studio 11: C 5.8 compiler,SPARC架构(服务器来自unix-center)
.global func .type func,#function func: save %sp,-96,%sp jmp %i7+8 restore ...
- 以及GCC 4.0.1,SPARC架构(服务器来自unix-center)
.global func func: retl ! Result = nop .type func,2 .size func,(.-func) ...
这些东西太复杂了,关注点只能限制到X86架构了。先看一下X86下常用的两种汇编有哪些不同。
X86汇编语言 差异
X86 下常用的汇编有 AT&T 与 Intel 两种(fix me?),二者在语法上有一定的差异:
-
注:本节内容来自http://oss.org.cn,文字和格式进行了重新整理。
前缀/后缀
区别:
Intel语法 | AT&T语法 | ||
寄存器 | 加前缀 % | ||
立即数 | 加前缀 $ | ||
十六进制 | 加后缀h | 数字前加 0x | |
二进制 | 加后缀b |
例子:
Intel语法 | AT&T语法 |
mov eax,8 | movl $8,%eax |
mov ebx,0ffffh | movl $0xffff,%ebx |
int 80h | int $0x80 |
操作数的方向
- 二者方向正好相反
- Intel语法,第一个是目的操作数,第二个是源操作数。
-
AT&T中,第一个数是源操作数,第二个数是目的操作数。(更符合阅读习惯哈)
例子:
Intel语法(<==) | AT&T语法(==>) |
mov eax,[ecx] | movl (%ecx),%eax |
内存单元操作数
- 在Intel的语法中,基寄存器用“[]”括起来
-
而在AT&T中,用“()”括起来。
Intel | AT&T |
mov eax,[ebx+5] | movl 5(%ebx),%eax |
间接寻址方式
-
Intel的指令格式是segreg:[base+index*scale+disp]
-
AT&T的格式是%segreg:disp(base,index,scale)
Intel语法 | AT&T语法 |
指令 foo,segreg:[base+index*scale+disp] | 指令 %segreg:disp(base,index,scale),foo |
mov eax,[ebx+20h] | Movl 0x20(%ebx),%eax |
add eax,[ebx+ecx*2h] | Addl (%ebx,%ecx,0x2),%eax |
lea eax,[ebx+ecx] | Leal (%ebx,%ecx),%eax |
sub eax,[ebx+ecx*4h-20h] | Subl -0x20(%ebx,%ecx,0x4),%eax |
操作码的后缀
-
在AT&T的操作码后加后缀,“l”(long,32位),“w”(word,16位),“b”(byte,8位)
- 在Intel的语法中,在操作数的前加byte ptr、 word ptr 或 dword ptr
例子:
Intel语法 | AT&T语法 |
Mov al,bl | movb %bl,%al |
Mov ax,bx | movw %bx,%ax |
Mov eax,ebx | movl %ebx,%eax |
Mov eax, dword ptr [ebx] | movl (%ebx),%eax |
找个例子看看
- hello.c
int func(int i) { return 2 * i; } int main() { int s = func(255); return 0; }
- hello.s (gcc4.5.2, x86/ubuntu11.04)
func: | |
pushl %ebp | 将ebp内容压栈保存 |
movl %esp, %ebp | |
movl 8(%ebp), %eax | 注意到立即数255在main中压栈后,先后有IP和本函数内的ebp压栈,故,栈顶+8指向立即数255 |
addl %eax, %eax | 乘法操作变成了加法,eax存放返回值 |
popl %ebp | ebp出栈 |
ret | 函数返回,(IP出栈) |
main: | |
pushl %ebp | 将ebp内容压栈保存, |
movl %esp, %ebp | 将栈顶保存到ebp中 |
subl $20, %esp | 栈顶下移20字节,用来保存局部变量 |
movl $255, (%esp) | 将立即数255放入栈顶所指位置 |
call func | 调用函数func,(此时将下条指令地址IP压入栈中) |
movl %eax, -4(%ebp) | 将func的返回值放入局部变量中(-4(%ebp)就是变量s的位置) |
movl $0, %eax | 0送入eax,准备返回值 |
leave | leave等价于 movl %ebp,%esp 和 popl %ebp |
ret |