浅谈X86汇编指令
大家好,这是我人生第一次写博客,许多不足之处还请多多包含。
分析X86汇编指令,我们需要一些基础知识
X86
X86是由Intel推出的一种复杂指令集,用于控制芯片的运行的程序,现在X86已经广泛运用到了家用PC领域。现在基本上用来指代32位操作系统。
相关寄存器
与计算机组成原理中的相关寄存器类似,只不过多出的E是Extended的缩写,代表是32位的寄存器。
EBP:扩展基址指针寄存器(Extended Base Pointer)。 其内存放一个指针,该指针指向系统栈最上面一个栈帧的底部,即指向当前活动记录的底部。
ESP:扩展堆栈指针寄存器(Extended stack pointer)。堆栈指针,用于指向栈的栈顶(指向下一个压入栈的活动记录的顶部)。
EIP:扩展指令指针寄存器(Extended Instruction Pointer)。存放当前指令的下一条指令的地址。CPU该执行哪条指令就是通过IP来指示的。
EAX:扩展累加器(Accumulator)。函数的返回值默认使用eax寄存器返回给上一级函数。
EBX:扩展数据寄存器(Data)。顾名思义,用来存放数据
相关汇编指令
与计算机组成原理中的汇编指令类似,指令结尾的“l”代表对32位寄存器的操作。寄存器前的“%”代表这是寄存器
movl %eax,%ebx寄存器寻址,代表只与寄存器打交道,与内存无关
movl $0x123,%edx立即寻址,$代表立即数,即将16进制的123赋给edx寄存器
movl 0x123,%edx直接寻址,将地址为123的内存中数据赋给edx
movl (%ebx),%edx间接寻址,将ebx中的值作为内存地址,再根据这个地址直接寻址。
movl 4(%ebx),%edx变址寻址,将ebx中的值+4,再间接寻址。
addl $0x123,%edx将edx的值+123
subl $0x123,%edx将edx的值-123
pushl %eax 将eax压入堆栈
popl%eax将eax压出堆栈
call 0x12345 等同于pushi %eip movl $0x12345,%eip这两步。注:程序员不能直接修改eip,只能通过特殊指令间接修改。即将当前指令的下一条指令地址压入堆栈,执行地址为12345的指令。
ret等同于 popl %eip,即还原当前指令的下一条指令地址。
enter等同于push %ebp movl %esp,%ebp 这两步,先将ebp压入堆栈,再将其指向esp,此时堆栈栈顶的指针和栈底的指针都指向一个区域,即目前堆栈为空栈。此命令是在初始化堆栈。
leave等同于movl %ebp,%esp popl %ebp 这两步,先将esp指向ebp,再ebp出栈,即ebp指向栈底,esp向栈底移动一个单元。此命令是在撤销堆栈。
ok下面我们分析一个简单的c程序
- int g(int x)
- {
- return 5 + 3;
- }
- int f(int x)
- {
- return g(x);
- }
- int main(void)
- {
- return f(10) + 1;
- }
.text
.globl g
.type g, @function
g:
.LFB0:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
movl 8(%ebp), %eax
addl $5, %eax
popl %ebp
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE0:
.size g, .-g
.globl f
.type f, @function
f:
.LFB1:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
subl $4, %esp
movl 8(%ebp), %eax
movl %eax, (%esp)
call g
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE1:
.size f, .-f
.globl main
.type main, @function
main:
.LFB2:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
subl $4, %esp
movl $10, (%esp)
call f
addl $1, %eax
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE2:
.size main, .-main
.ident "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"
.section .note.GNU-stack,"",@progbits
movl %esp, %ebp//ebp指向esp,即初始化堆栈
movl 8(%ebp), %eax//ebp-8,即ebp向上两个堆栈单元后,将所指的值,也就是10(此时esp之前有ebp和eip(指向leave 这条指令)),赋给eax
popl %ebp//ebp出栈,即指向当前活动的栈底(10所在堆栈单元)
ret //esp向栈底移动一个堆栈单元,eip(指向leave 这条指令)出栈,即eip指向leave
f: //f(x)
pushl %ebp//ebp压入堆栈
movl %esp, %ebp//ebp指向esp,即初始化堆栈
subl $4, %esp//初始化一个新的堆栈
movl 8(%ebp), %eax//ebp-8,即ebp向上两个堆栈单元后,将所指的值,也就是10(此时esp之前有ebp和eip(指向addl$1, %eax 这条指令),赋给eax
movl %eax, (%esp)//将eax的值,放到esp所指单元中,即将10放入目前的栈顶。
call g//将当前指令的下一条指令(即leave)地址压入堆栈,执行g指令(即调用g函数)。
leave //将esp指向ebp(此时ebp指向10所在单元),ebp清空,指向栈顶,esp向栈底移动一个堆栈单元。
ret //esp向栈底移动一个堆栈单元,eip(指向addl$1, %eax 这条指令),出栈,即eip指向addl$1, %eax。
main:
pushl %ebp//将ebp入栈
movl %esp, %ebp//将esp的内容放到ebp,即ebp指向esp,这两步是在初始化堆栈。
subl $4, %esp//esp的值-4,即初始化一个新的堆栈
movl $10, (%esp)//将10压栈
call f//将当前指令的下一条指令(即addl$1, %eax)地址压入堆栈,执行f指令(即调用f函数)。
addl $1, %eax//eax=eax+1,即eax=16
leave //堆栈撤销
这样以来,函数调用通过堆栈转换成了指令流,最终被cpu顺序执行。
张何灿 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000