初识Linux汇编

在 Linux 内核混,不碰到汇编是不可能的,使用汇编是因为其有着高级语言(如 C)无法替代的优势:
1.直接访问硬件里的存储器或 I/O 口;
2.对生成的二进制代码进行控制,不受编译器优化处理;
3.对关键代码精准控制,避免多线程同时访问或设备资源共享所引起的死锁;
4.对特定操作的代码最优化,提高最终运行效率和速度;
5.最大限度发挥硬件的性能。
当然其也有如下一些缺点:
1.代码易读性差,不利于维护;
2.只能针对特定平台和处理器进行优化,不便移植;
3.开发效率低,易产生 BUG,调试难度高。
在Linux 环境下 GCC 编译可以让我们对汇编进行两种形式的支持:全汇编代码和嵌入 C 语言的内联汇编。下面将会对这两种形式进行说明,但在说明前还是要了解下 Linux 下常用的AT&T 格式与 Windows 下 Intel 格式的区别:
1.在使用 AT&T 的寄存器寻址时,需要在寄存器名前加%号,而 Intel 则不需要;
2.在使用 AT&T 的立即数时,需要在立即数前加$号,而 Intel 则不需要;
3.AT&T 的源操作数在左,目标操作数在右,与 Intel 刚好相反;
4.AT&T 的每个操作指令后都有标识操作数字长的后缀‘b’、‘w’、‘l’(分别代表字节byte,8bits;字 word,16bits;长字 long,32bits),而 Intel 中则用‘byte ptr’和‘word ptr’等前缀来表示;
5.在 AT&T 中,绝对跳转和调用指令(jump 和 call)的操作数前要加“*”作为前缀,Intel 则不需要;
6.远程调用和远程子过程调用指令,在 AT&T 里使用 ljump 和 lcall 指令,Intel 中则用 jmp far和 call far,相应的返回指令为 lret 和 ret;
7.内存寻址,在 AT&T 中使用 section:disp(base,index,scale)形式,Intel 使用 section  [base+index*scale+disp],即 AT&T 用圆(小)括号,Intel 用中(方)括号,其段内地址都为disp+base+index*scale。
有了上述的基础知识,接下来还是以 Hello xinu!为例(hello.S)展示下 Linux 下 AT&T 汇编的使用:

#hello.S
.data                                        #数据段声明
    msg : .string "Hello,xinu!\n" # 输出的字符串
    len = . - msg                        # 字符串长度
.text                                         # 代码段声明
.global _start                           # 指定程序入口
_start:
    movl $len, %edx                 # 参数三:字符串长度
    movl $msg, %ecx               # 参数二:要显示的字符串
    movl $1, %ebx                    # 参数一:文件描述符(stdout)
    movl $4, %eax                    # 系统调用号(sys_write)
    int $0x80                             # 触发系统调用
    movl $0, %ebx                    # 参数一:退出错误值
    movl $1, %eax                    # 系统调用号(sys_exit)
    int $0x80

对应的 Makefile 文件内容如下:

all:
    as -o hello.o hello.S
    ld -o hello hello.o
clean:
    rm -rf hello.o hello

  相应的源码文件目录树如下:

/home/xinu/xinu/asm/hello/
├── hello.S
└── Makefile

从汇编代码可以看到我们使用到了代码段和数据段,分别以.text 和.data 标号开头,可以看出 eax 保存系统调用号,ebx、ecx、edx 分别对应该系统调用的第一、二、三个参数,这些相关寄存器的值设置好后,再调用 int $0x80 指令执行第 80 号中断来最终调用 eax 对应的系统调用,该系统调用号在./arch/x86/syscalls/syscall_32.tbl 和 syscall_64.tbl 可以查到,其仅对应 x86 平台的32 和 64 位系统。在 syscall_32.tbl 有如下一行:

4 i386 write sys_write

相应的 sys_write 函数在 include/linux/syscalls.h 中有如下声明:

asmlinkage long sys_write(unsigned int fd, const char __user *buf, size_t count);

刚好 eax 设置为 4,ebx 设置为 1(fd,0:stdin,1:stdout,2:stderr),ecx 为msg(buf),edx 为 len(count)。

从Makefile 可以看到将汇编代码需要经过汇编 as 和链接 ld 两步才能生成最终的可执行文件。由于上面提到全汇编的代码虽然运行速度快,但开发速度慢,而 GCC 为我们提供了折衷的方法,即 GCC 内联汇编,在 C 代码里嵌入汇编代码,只在需要优化的关键代码段使用汇编。GCC 提供的内联汇编最基本的格式为:

__asm__(“asm statments”);

这样是为了与 ANSI C 兼容,如不考虑,则可直接使用 asm。从上面这格式可以看出问题来了,如何使用 C 里面的变量等值呢?故而还有如下格式:

__asm__(“asm statements” : outputs : inputs : registers-modified);

第一部分“asm statements”是内联汇编的指令部,其与上面说到的汇编格式上基本相同,其中使用%0、%1、......等序号来代码后面的 outputs 和 inputs 对应的参数和,从 outputs 开始排起直到 inputs 结束为止,那么之前的寄存器寻址就得使用%%eax 的形式了。

第二部分 outputs 是内联汇编的输出部,规定输出变量与上面的样板操作数(%0、%1、......)的对应条件,每个条件为一个“约束”,可包含多个,使用逗号分隔。每个输出约束都以'='开始,然后是操作数类型的说明符,接下来是对应的变量。

第三部分 inputs 是输入部,与输出部区别是少了'='号。

第四部分为表示 outputs 和 inputs 里会操作到的寄存器,让 GCC 采取措施,不让相应寄存器在该过程中被再次使用,以免产生副作用。

  接下来还是实例感受下内联汇编:

#include <stdio.h>
int main(void)
{
    int a = 15, b = 0;
    __asm__ __volatile__("movl %1, %%eax;\n\r"
                        "movl %%eax, %0;":"=r"(b)
                        :"r"(a)
                        :"%eax");
    printf("Result: %d, %d\n", a, b);
}

相应的 Makefile 文件内容如下:

all:
    gcc -o inlineasm inlineasm.c
clean:
    rm -rf inlineasm

  对应的源码文件目录树如下:

/home/xinu/xinu/asm/inline_asm_example/
├── inlineasm.c
└── Makefile

从源码里,可以看出变量 a 为输入操作数,对应%1,而变量 b 为输出操作数,对应%0,其都使用 r 约束,表示将变量 a 和 b 存储在寄存器中,而 eax 使用%%eax 而不是%eax,不然为与%就没法区分开来了,最后一部分告诉 GCC 我们会使用到寄存器 eax,让 GCC 编译成汇编时在该位置处不再使用 eax 来存储任何值。在指令部分(第一部分)的指令中引用操作数时总将其当作 32 位长字使用,而实际可能只用作字或字节,故而需要在约束中说明相应的限定符(如上面的 r),有如下参考值:

参考值

好了,关于 Linux 汇编,就先了解这些,为深入滓 Linux 内核作准备。
参考网址:
https://www.ibm.com/developerworks/cn/linux/l-assembly/
http://www.ibm.com/developerworks/cn/linux/sdk/assemble/inline/index.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

stxinu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值