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>。
--------------------------------------------------------------------------------
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: ;system call
int 0x25
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,0x3 ;system call number (sys_write)
call _syscall ;call kernel
add esp,12 ;clean stack (3 * 4)
push dword 0 ;exit code
mov eax,0x3f ;system call number (sys_exit)
call _syscall ;call kernel
;no need to clean stack
--------------------------------------------------------------------------------
2.6 创建可执行代码
创建可执行代码分为两个阶段:编译和链接。按照下面的步骤进行:
--------------------------------------------------------------------------------
$ nasm -f elf hello.asm # this will produce hello.o object file
$ ld -s -o hello hello.o # this will produce hello executable
--------------------------------------------------------------------------------
这样就很容易的产生了可执行代码,然后使用命令./hello就可以执行程序了。
3. 汇编资源
若希望深入了解Linux汇编,可以参考站点Linux的内容,并且下载asmutils,其包含了很多示例代码。也可以参考Linux Assembly HOWTO..下面是一些Linux汇编的一些资源链接。
工程:
;各种使用汇编编写的LInux/UNIX工程,是很好的示例资源
name short description platform OS assembler
asmutils miscellaneous utilities, small libc IA32 Linux, FreeBSD (BeOS) nasm
libASM assembly library (lots of various routines) IA32 Linux nasm
e3 WordStar-like text editor IA32 Linux, FreeBSD, BeOS nasm
ec64 Commodore C64 emulator IA32 Linux nasm
ELF kickers ELF kickers and tiny Linux executables IA32 Linux nasm
BLAS basic linear algebra subroutines Alpha Linux, Digital UNIX, WinNT gas
ASMIX several commandline utilities IA32 Linux, FreeBSD gas
asm-toys few utilities IA32 Linux gas
cpuburn CPU loading utililties IA32 Linux, FreeBSD gas
acid small textmode intro IA32, ARM Linux nasm, gas
eforth Linux Forth IA32 Linux gas
smallutils few small utils in assembly and C IA32, SPARC Linux gas
还有很多C-汇编混合工程,如 Linux kernel, GNU MP Library, GNU libc, OpenGUI, FreeAmp。
文档:
Linux Assembly HOWTO
Using the GNU Assembler (GAS manuals)
List of Linux/i386 system calls: this one and this one
Digital UNIX assembly
; Articles
Startup state of Linux/i386 ELF binary
Self-modifying code under Linux
; Books
The Art Of Assembly
by Randall Hyde. Classic book; general assembly programming, for newbies.
PC Assembly Language
by Paul Carter. 32bit protected mode programming, Windows & Linux, for newbies.
Assembler for DOS, Windows and UNIX
by Sergey Zubkov. ISBN 5-89818-019-2, 637 pages, 1999. In Russian language.
Assembly Language Step-By-Step; Programming with DOS and Linux with CDROM
by Jeff Duntemann. ISBN: 0471375233, 612 pages, 2000; John Wiley & Sons
Linux Assembly Programming
by me. Not available yet.
; CPU 手册及汇编编程导引
IA-32 (x86): sandpile.org, x86.org, Intel, AMD, Cyrix, x86 bugs
IA-64: Intel IA-64 manuals
Alpha: Digital Alpha papers, Digital Documentation Library, more manuals
SPARC: SPARC International Standard Documents Repository, Technical SPARC CPU Resources
MIPS: MIPS Online Publications Library
PPC: Beginners Guide to PowerPC Assembly Language
新手入门指引:
Introduction to UNIX assembly programming (Linux, FreeBSD, BeOS)
Using x86 assembly code in BeOS (NASM, linking to GCC)
Linux assembly tutorial (GAS and GDB)
DJGPP QuickAsm Programming Guide (GAS and GCC inline assembly)
SPARC assembly "Hello world" (NetBSD, SunOS, Solaris)
A Whirlwind Tutorial on Creating Really Teensy ELF Executables for Linux
工具:
NASM portable x86 assembler with Intel syntax
NASM 0.98e extented NASM version
BIEW portable console hex viewer/editor with built-in disassembler
UPX portable executable packer for several formats
Intel2gas converter between AT&T and Intel assembler syntax
A2I converter from AT&T to Intel assembler syntax
TA2AS converter from TASM to AT&T assembler syntax
SPARC SPARC v8 assembler & disassembler
站点:
APJ Assembly Programming Journal
H-Peter Recktenwald's site "The Int80h page"
Jan's Linux & Assembler page mostly about assembly programming with libc
Bruce Ediger's page SPARC assembly related materia