博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
计算机是如何工作的——汇编代码分析
阅读量:2255 次
发布时间:2019-05-09

本文共 2505 字,大约阅读时间需要 8 分钟。

谭东旭 原创作品转载请注明出处 《Linux内核分析》MOOC课程

汇编小知识

在用高级语言如C语言编程时,我们被屏蔽了程序的具体的机器实现。相比之下,在用汇编代码编写程序时候,程序员必须明确指定程序该如何管理存储器和用来执行计算的低级指令。我想作为一个严谨的程序员来说,要想了解程序的运行效率以及更好地提高程序效率,我认为能够阅读和理解汇编代码仍是一项很重要的技能。

我们知道汇编格式有两种:AT&T汇编格式与Intel汇编格式。linux GCC采用的是AT&T的汇编格式, 而微软采用Intel的汇编格式。下面我们就稍微介绍一下他们的一些不同之处:

格式 寄存器命名 源/目的操作数顺序 常数/立即数的格式
AT&T %eax movl %eax, %ebx movl $_value,%ebx
Intel eax mov ebx, eax mov eax,_value
说明 Intel的不带百分号 AT&T目的操作数在后,源操作数在前 Intel立即数前面不带$符号

还有一点不同的是,在AT&T的格式中, 每个操作符都有一个字符后缀,例如 movl 传送双字(32位),movw 传送字(16位),movb 传送字节(8位)。因为在许多机器上, 32位数都称为长字(long word), 这是沿用以16位字为标准的时代的历史习惯造成的.

下面我们也给出一些常见的汇编指令的含义:

指令 含义 说明
movl %eax,%edx edx=eax 寄存器寻址
movl $0x123,%edx edx=0x123 立即寻址
movl 0x123,%edx edx=*(int32_t*)0x123 直接寻址
movl (%ebx),%edx edx=*(int32_t*)ebx 间接寻址
movl 4(%ebx),%edx edx=*(int32_t*)(ebx+4) 变址寻址

汇编代码的工作过程中堆栈的变化

下面给出示例代码:

#include
int g(int x){ return x + 2;}int f(int x){ return g(x);}int main(void){ return f(7) + 5;}

上述是很简单的一个小程序,我的实验环境为所提供的虚拟机环境,使用命令gcc –S –o main.s main.c -m32生成汇编代码,注意该命令是在实验楼64位Linux虚拟机环境下适用,32位Linux环境可能会稍有不同。所得到的汇编代码如下:

g:    pushl   %ebp    movl    %esp, %ebp    movl    8(%ebp), %eax    addl    $2, %eax    popl    %ebp    retf:    pushl   %ebp    movl    %esp, %ebp    subl    $4, %esp    movl    8(%ebp), %eax    movl    %eax, (%esp)    call    g    leave    retmain:    pushl   %ebp    movl    %esp, %ebp    subl    $4, %esp    movl    $7, (%esp)    call    f    addl    $5, %eax    leave    ret

有了汇编代码,但是我们不一定能看得懂,我们知道main中调用了函数f,f中调用了g,但是函数返回时为什么能返回到正确的位置?其实这个过程中用栈来保存参数及返回地址,函数返回时再从栈中取出保存的地址,这样就不影响主程序的继续执行。下面我就给出其中一些汇编指令的等价指令,让我们更能轻易理解这一代段汇编代码。

指令 what it does
pushl %eax subl $4,%esp ; movl %eax,(%esp)
popl %eax movl (%esp),%eax ; addl $4,%esp
call 0x12345 pushl %eip ; movl $0x12345,%eip
ret popl %eip
enter pushl %ebp ; movl %esp,%ebp
leave movl %ebp,%esp ; popl %ebp

好了,有了上面的一些解释,我们就可以一步一步对代码进行分析。首先下面我先看看栈在计算中是如何操作变化的,ebp为栈的基址寄存器,esp为栈顶指针,栈是向低地址方向增长的。

这里写图片描述
我们回到汇编代码,看到在每个汇编代码块的开始都有两句一样的汇编指令:

pushl   %ebpmovl    %esp, %ebp

其实这两句可以理解是保存上一个函数的栈底指针,然后从新开始一个新的栈。现在从main函数开始我们就以图的形式来描述汇编在栈中的变化(在这里我们假设栈的起始地址为0x114)。

这里写图片描述

当mian函数运行到call指令时候,就跳转到了f函数的代码块,现在下图给出了f代码块的栈变化过程。

这里写图片描述

当f函数运行到call指令时候,就跳转到了g函数的代码块,下图给出了g代码块的栈变化过程。

这里写图片描述

上述g函数代码块已经执行完毕,即f函数call执行完毕,计算机接着出栈16,运行到f函数中call的下一条指令即leave指令,leav指令在上面已经给出解释。leave指令后ebp指向0x114,esp指向0x10c,接着运行g函数ret,esp执行出栈,回到了main函数的call的下一条指令,即addl $5, %eax,此时eax=eax+5,最终eax=14.最后两条指令用法和上边的一样。esp和ebp回到初态,最后整个程序通过eax返回。

总结

总的来说计算机的运行就是存储器和cpu之间不停的取指令和运行指令的过程,准确的理解汇编代码的含义,对程序员来说能更好的深入理解计算机是如何工作的,编写代码过程中才能透过高级语言的现象看到计算机的机器语言的本质。

你可能感兴趣的文章
ISO14443 Type A类型卡的防碰撞过程以及命令解析
查看>>
ISO14443 Type B类型卡的防碰撞过程以及命令解析
查看>>
双向链表实现的消息队列
查看>>
数据结构——双向链表(C语言实现)
查看>>
ISO15693类型的命令解析以及防碰撞过程
查看>>
void在C语言中特殊使用
查看>>
防止头文件被重复包含的两种方式#pragma once 与 #ifdef 的区别
查看>>
推挽输出、开漏输出、复用开漏输出、复用推挽输出以及上拉输入、下拉输入、浮空输入、模拟输入区别
查看>>
Log打印技巧(C语言实现)
查看>>
普通GPIO模拟SPI通信协议(软件SPI)
查看>>
USART配置成SPI实例代码
查看>>
延时函数sleep和delay的区别
查看>>
C语言字符串的处理
查看>>
什么是达夫设备(Duff's Device)
查看>>
J-Link v8固件丢失修复
查看>>
pgsql查询所有表名
查看>>
Linux内核设计与实现——7 中断与中断处理(6)——中断上下文
查看>>
linux下如何恢复rm命令删除的文件
查看>>
Linux即输出到屏幕,又保存到文件
查看>>
裸设备绑定出现“Cannot open master raw device '/dev/rawctl' (No such file or directory)”
查看>>