linux汇编基础

简介

大多数情况下 Linux 程序员不需要使用汇编语言,因为即便是硬件驱动这样的底层程序在 Linux 操作系统中也可以用完全用 C 语言来实现,再加上 GCC 这一优秀的编译器目前已经能够对最终生成的代码进行很好的优化,的确有足够的理由让我们可以暂时将汇编语言抛在一边了。但实现情况是 Linux 程序员有时还是需要使用汇编,或者不得不使用汇编,理由很简单:精简、高效和 libc 无关性。假设要移植 Linux 到某一特定的嵌入式硬件环境下,首先必然面临如何减少系统大小、提高执行效率等问题,此时或许只有汇编语言能帮上忙了。

汇编语言直接同计算机的底层软件甚至硬件进行交互,它具有如下一些优点:
• 能够直接访问与硬件相关的存储器或 I/O 端口;
• 能够不受编译器的限制,对生成的二进制代码进行完全的控制;
• 能够对关键代码进行更准确的控制,避免因线程共同访问或者硬件设备共享引起的死锁;
• 能够根据特定的应用对代码做最佳的优化,提高运行速度;
• 能够最大限度地发挥硬件的功能。

linux汇编格式

绝大多数 Linux 程序员以前只接触过DOS/Windows 下的汇编语言,这些汇编代码都是 Intel 风格的。但在 Unix 和 Linux 系统中,更多采用的还是 AT&T 格式,两者在语法格式上有着很大的不同。

  1. 在 AT&T 汇编格式中,寄存器名要加上 ‘%’ 作为前缀;而在 Intel 汇编格式中,寄存器名不需要加前缀。例如:
AT&T 格式Intel 格式
pushl %eaxpush eax
  1. 在 AT&T 汇编格式中,用 ‘$’ 前缀表示一个立即操作数;而在 Intel 汇编格式中,立即数的表示不用带任何前缀。例如:
AT&T 格式Intel 格式
pushl $1pushl 1
  1. AT&T 和 Intel 格式中的源操作数和目标操作数的位置正好相反。在 Intel 汇编格式中,目标操作数在源操作数的左边;而在 AT&T 汇编格式中,目标操作数在源操作数的右边。例如:
    |AT&T 格式| Intel 格式 |
    |–|--|
    |addl $1, %eax |addl $1, %eax |
  2. 在 AT&T 汇编格式中,操作数的字长由操作符的最后一个字母决定,后缀’b’、‘w’、'l’分别表示操作数为字节(byte,8 比特)、字(word,16 比特)和长字(long,32比特);而在 Intel 汇编格式中,操作数的字长是用 “byte ptr” 和 “word ptr” 等前缀来表示的。例如:
AT&T 格式Intel 格式
movb val, %almov al, byte ptr val
  1. 在 AT&T 汇编格式中,绝对转移和调用指令(jump/call)的操作数前要加上’*'作为前缀,而在 Intel 格式中则不需要。 远程转移指令和远程子调用指令的操作码,在 AT&T 汇编格式中为 “ljump” 和 “lcall”,而在 Intel 汇编格式中则为 “jmp far” 和 “call far”,即:
AT&T 格式Intel 格式
ljump $section, $offsetljump $section, $offsetl
  1. 在 AT&T 汇编格式中,内存操作数的寻址方式是
    section:disp(base, index, scale)
    而在 Intel 汇编格式中,内存操作数的寻址方式为:
    section:[base + index*scale + disp]

  2. 实际例子

AT&T 格式Intel 格式
movl -4(%ebp), %eaxmov eax, [ebp - 4]
movl array(, %eax, 4), %eaxmov eax, [eax*4 + array]
movb $4, %fs:(%eax)mov fs:eax, 4
  1. 可能很多人都喜欢windows的intel格式,在linux下也可以对att格式做转换:
    set disassembly-flavor intel 转换为intel格式的汇编
    set disassembly-flavor att 转换为att格式的汇编

常用寄存器

这里主要讲解32bit的寄存器

  1. 通用寄存器
8位16位32位描述
AH ALAXEAX累加器
BH BLBXEBX基地址寄存器
CH CLCXECX计数寄存器
DH DLDXEDX数据寄存器
BPEBP堆栈基指针
SI DIESI EDI变址寄存器
SPESP堆栈栈顶寄存器
  1. 段寄存器
    代码段寄存器CS:存放当前执行的程序的段地址。
    数据段寄存器DS:存放当前执行的程序操作数的段地址。
    堆栈段寄存器SS:存放当前执行的程序所用堆栈的段地址。
    附加段寄存器ES:存放当前程序中一个辅助数据段的段地址
  2. 专用寄存器
    指令寄存器:16位:IP,32位:EIP
    用来存放将要执行的下一条指令地址的偏移量,它与段寄存器CS联合形成代码段中指令的物理地址
    标志寄存器:这是存放条件码标志、控制标志的16位寄存器

常用指令及寻址方式

  1. 寻址方式
指令含义寻址方式
movl %eax,%edxedx = eaxregister mode
movl $0x123,%edxedx = 0x123immediate
movl 0x123,%edxedx = (int32_t)0x123direct
movl (%ebx),%edxedx = (int32_t)ebxindirect
movl 4(%ebx),%edxedx = (int32_t)(ebx + 4)displaced
  1. 几个重要的汇编指令:
    pushl %eax:压栈(注意栈是向下增长的)
    相当于: subl $4,%esp
    movl %eax,(%esp)

popl %eax:出栈并将值赋给eax寄存器
相当于:movl (%esp),%eax
addl $4,%esp

call 0x12345:跳转指令,常用在调用函数中,用来保存当前地址及跳转
相当于: pushl %eip() ,movl $0x12345,%eip()
//打(**)表示程序员不能直接对eip寄存器操作

ret:返回指令,用在函数返回时获取返回地址
相当于: popl %eip(*)

leave:恢复堆栈指令,用在函数返回时用来恢复调用方的堆栈信息。
相当于:movl %ebp, %esp
popl %ebp
要想理解这一点得先明白,在进入一个新的函数时,一般都会执行下面两条指令:
pushl %ebp,movl %esp,%ebp
这两句的功能就是保存原来的堆栈信息,然后开辟新的堆栈。先将堆栈原先的基址(ebp)入栈,以保存之前任务的信息。然后将栈顶指针的值赋给ebp,将之前的栈顶作为新的基址(栈底),然后再这个基址上开辟相应的空间用作被调用函数的堆栈。
那么相应的leave指令就比较好理解了,函数返回后,从ebp中可取出之前的esp,使栈顶恢复函数调用前的位置,再从回复后的栈顶弹出之前的ebp值。这样ebp和esp就都恢复了调用前的位置,堆栈恢复函数调用前的状态。

  1. 其它指令
    可参考:https://blog.youkuaiyun.com/ldong2007/article/details/2873611

下一篇我将用汇编来详细分析变量初始化,参数传递,函数调用,函数返回的原理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值