浅谈X86汇编指令

来源:互联网 发布:数据库备份与恢复策略 编辑:程序博客网 时间:2024/06/15 13:56

大家好,这是我人生第一次写博客,许多不足之处还请多多包含。


分析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程序

  1. int g(int x)
  2. {
  3.   return 5 + 3;
  4. }
  5.  
  6. int f(int x)
  7. {
  8.   return g(x);
  9. }
  10.  
  11. int main(void)
  12. {
  13.   return f(10) + 1;
  14. }
使用gcc –S –o main.s main.c -m32 命令,将其反汇编成汇编语言,结果如下

.file"main.c"
.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

其中,所有以点开头的都是 链接时需要的汇编信息,我们分析代码时可以忽略。
我们分析代码时,从main开始。
g: //g(x)
pushl%ebp//ebp压入堆栈
movl %esp, %ebp//ebp指向esp,即初始化堆栈
movl 8(%ebp), %eax//ebp-8,即ebp向上两个堆栈单元后,将所指的值,也就是10(此时esp之前有ebp和eip(指向leave 这条指令)),赋给eax
addl$5, %eax//eax=eax+5,即10+5=15
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

原创粉丝点击