Unix汇编语言简介[1]

[入库:2005年8月19日] [更新:2007年3月24日]

本文简介:选择自 zysno1 的 blog

 

unix汇编语言简介

 

unix汇编语言简介
作者:konstantin boldyshev   编译:ideal
v0.5, june 01, 2000 


--------------------------------------------------------------------------------

本文章重要介绍如何在la-32(i386)平台下uinx系统下编写简单的汇编程序,这里的材料并不一定适用于其他的平台,本文说明了程序风格、系统调用惯例和编译过程。更加详细的信息可以参考linux assembly howto。


--------------------------------------------------------------------------------

1. 简介
1.1 版权声明
copyright © 1999-2000 konstantin boldyshev. 本文章遵从gnufree documentation license.

1.2 获得本文档
本文章的最新版本可以在 http://linuxassembly.org/intro.html下载。

1.3 需要的工具
为了验证本文中的程序示例,需要以下几个开发工具。 

首先,需要一个编译器.当前,unix发布一般都带有gas(gnu assemble),但是这里的例子都使用另外一个编译器—nasm (netwide assembler)。它的源代码也是开放的,可以从nasm page下载该程序。一般linux发布都预装了nasm,所以首先查看你的系统。 

其次,需要一个链接器—ld,由于一个nasm仅仅产生目标代码,所以需要链接器来链接目标代码,一般linux发布都带有ld程序。


--------------------------------------------------------------------------------

2. hello, world!
下面,我们将编写一个打印"hello,world!"的汇编程序(hello.asm)。你可以从这里下载院代码和二进制代码。

2.1 system calls 
除非一个汇编程序仅仅进行数学运算,否则其必须进行输入输出及退出等操作,而要进行这些操作就需要调用操作系统的服务。实际上,除了使用系统的服务以外,不同操作系统的汇编编程是很类似的。

在unix系统下有两种方式实现对系统调用的使用:通过经过封装的c库(libc)或者直接调用。

在汇编程序中是否使用libc不仅仅是一个编程风格的问题,而libc封装用来确保当系统调用接口发生变化时,无须对使用libc的程序进行修改,和用来提供posix兼容的接口。而unix内核调用往往是和posix兼容的,这意味着大多数libc的系统调用的语法完全和真正的内核系统调用相匹配的(或者相反)。但是不通过libc调用系统服务的缺点是会丢失若干个不仅仅是系统调用封装的函数如:printf(), malloc()

这篇文章来说明如何直接调用内核调用,因为这是调用内核服务的效率最高的方式,我们的程序不与任何库链接,而是直接和内核通信。

不同的unix内核的系统调用函数集合及系统调用惯例都是不大相同的,但是它们往往都是posix兼容的操作系统,所以往往都有很明显的共同点。

2.2 程序风格
当前ia-32 unix都是32bit的操作系统,具有以下特点:运行在保护模式下,具有"平坦"(flat)内存地址空间,使用elf二进制代码格式。一个程序可以划分为不同的段:.text是程序执行代码(只读),.data是程序的数据部分(读写),.bss是没有经过初始化的数据(read-write);同时还可以具有其他一些标准的段及用户自定义段,但是这种情况很少被应用。而一个程序至少具有.text段。

2.3 linux 
linux环境下的系统调用是通过int 0x80来实现的。(实际上有一个内核补丁允许在新的cpu上通过系统调用sysenter指令来使用系统调用,但是这仍然处于实验阶段)

linux在调用惯例上和其他的unix不大一样,对于系统调用具有一个"fastcall"惯例(和dos倒是挺类似的)。系统函数号由ea被传递,参数则通过寄存器被传递,而不是通过堆栈。一共可以具有5个参数,顺序存放在ebx, ecx, edx, esi, edi。如果调用具有5个以上的参数,它们则简单的以结构的形式作为第一个参数传递。返回结果在ea中被返回,堆栈则根本没有被建立。

系统调用函数号包含在/sys/syscall.h中,而实际上是定义在asm/unistd.h中。系统调用的man文档有的包含在man的第二部分,例如man 2 write才能看到write的man文档。

下面,我们的linux汇编程序如下:


--------------------------------------------------------------------------------

section .text

    global _start                       ;must be declared for linker (ld)



msg     db      'hello, world!',0xa     ;our dear string

len     equ     $ - msg                 ;length of our dear string



_start:                                 ;tell linker entry point



        mov     edx,len ;message length

        mov     ecx,msg ;message to write

        mov     ebx,1   ;file descriptor (stdout)

        mov     eax,4   ;system call number (sys_write)

        int     0x80    ;call kernel



        mov     eax,1   ;system call number (sys_exit)

        int     0x80    ;call kernel



--------------------------------------------------------------------------------

正如你将要了解的那样,linux系统调用惯例是最紧凑的一种了。 

内核资源参考 

arch/i386/kernel/entry.s 
include/asm-i386/unistd.h 
include/linux/sys.h 
2.4 freebsd 
freebsd是具有常见的调用惯例,其系统调用号存放在eax中,参数存放在堆栈中(第一个参数最后被推入栈中), 一个系统调用通过一个对一个包含int 0x80和ret的函数的调用来进行的,而不仅仅是int 0x80。(在int 0x80发出之前,返回地址必须已经存放在堆栈之中)。调用结束以后调用者必须清除堆栈。调用返回结果一般存放在eax中。

有另外一个可选的方法来使用call 7:0来替代int 0x80。最后结果是一样的。但是call 7:0的方法将增加程序大小,因为之前用户必须使用push eax命令。

系统调用号存放在sys/syscall.h中。系统调用的man文档在man的第二部分。

程序如下所示:


--------------------------------------------------------------------------------

section .text

    global _start                       ;must be declared for linker (ld)



msg     db      "hello, world!",0xa     ;our dear string

len     equ     $ - msg                 ;length of our dear string



_syscall:               

        int     0x80            ;system call

        ret



_start:                         ;tell linker entry point



        push    dword len       ;message length

        push    dword msg       ;message to write

        push    dword 1         ;file descriptor (stdout)

        mov     eax,0x4         ;system call number (sys_write)

        call    _syscall        ;call kernel



                                ;the alternate way to call kernel:

                                ;push   eax

                                ;call   7:0



        add     esp,12          ;clean stack (3 arguments * 4)



        push    dword 0         ;exit code

        mov     eax,0x1         ;system call number (sys_exit)

        call    _syscall        ;call kernel



                                ;we do not return from sys_exit,

                                ;there's no need to clean stack



--------------------------------------------------------------------------------

内核资源参考: 

i386/i386/exception.s 
i386/i386/trap.c 
sys/syscall.h 
 

2.5 beos 
beos内核同样使用通用的unix调用惯例,其和freebsd的区别是使用:int 0x25。

对于到哪里查找系统调用号和其他感兴趣的细节,查看os_beos.inc文件。

为了使用nasm正确的编译beos程序,需要在float.h中加入#include "nasm.h" ,和在nasm.h中加入#include <stdio.h>。


本文关键:Unix汇编语言简介
 

本站最佳浏览方式为 分辨率 1024x768 IE 6.0(或更高版本的 IE浏览器)

go top