当前位置:Linux教程 - Linux资讯 - Linux下的汇编器

Linux下的汇编器

  Linux 下两个最主要的汇编器是 Nasm(free, Netwide Assembler)和 GAS(free, Gnu A     ssembler),   后一个和 GCC 结合在一起. 在这篇文章里我将集中在 Nasm 上, 把 GAS 放在后面,   因为它使用 AT&T 的语法, 需要一个长的介绍.   Nasm 调用时应该带上 ELF 格式选项("nasm -f elf hello.asm"); 产生的目标文件用     GCC 来链接("gcc hello.o"), 产生最终的 ELF 二进制代码. 下面的这个脚本可用来   编译 ASM 的模块; 我尽量把它写得简单, 所以所有它做的就是接受传给它的第一个   文件名, 用 Nasm 编译, 用 GCC 来链接.   #!/bin/sh   # assemble.sh =========================================================   outfile=${1%%.*}   tempfile=asmtemp.o   nasm -o $tempfile -f elf $1   gcc $tempfile -o $outfile   rm $tempfile -f   #EOF ==================================================================   基本知识:   ----------   当然最好的就是在了解系统细节之前从一个例子开始. 这里是一个最基本的   "hello-Word" 形式的程序:   ; asmhello.asm ========================================================   global main   extern printf   section .data   msg db "Helloooooo, nurse!",0Dh,0Ah,0   section .text   main:   push dword msg   call printf   pop eax   ret   ; EOF =================================================================   纲要: "global main" 必须声明为全局的(global) -- 并且既然我们用 GCC 来链接,   进入点必须以 "main" 来命名 -- 从而装入系统. "extern printf" 只是一个声明,   为以后在程序中调用; 注意这是必须的; 参数的大小不需要声明. 我已经把这个   例子用标准的 .data, .text 分节, 但这不是严格必须的 -- 可能只需要一个 .text   段, 就像在 DOS 下一样.   在代码的主体部分, 你必须把参数压栈来传递给调用. 在 Nasm 里, 你必须声明   所有不明确数据的大小; 因此就有 "dword" 这个限定词. 注意和其他汇编器一样,   Nasm 假设所有的内存/标号的引用都指的是内存地址或者标号, 而不是它的内容.   因而, 指明字符串 'msg' 的地址, 你应该使用 'push dword msg', 指明字符串 'msg'       的内容, 应该用 'push dword [msg]' (这只能包含 'msg' 的前四个字节). 因为 prin     tf   需要一个指向字符串的指针, 我们应该指明 'msg' 的地址.   调用 printf 非常的直接. 注意每一次调用后你必须把栈清除(见下); 所以 PUSH 了一     个   dword 后, 我从栈里把一个 dword POP 进一个无用的寄存器. Linux 程序只简单的用一       个 RET 来返回系统, 由于每个进程都是 shell(或者是 PID)的产物, 所以程序结束后把       控制权还给它.   注意到在 Linux 下, 你是在 "API" 或中断服务的场所里使用系统带来的标准共享库.     所有   的外部引用由 GCC 管理, 它给 asm 程序员节省了大部分的工作. 一旦你习惯了基本的     技   巧, Linux 下的汇编编程实际上要比 DOS 简单的多.   C 调用的语法   --------------------   Linux 使用 C 的调用模式 -- 意味着参数以相反的顺序进栈(最后一个最先), 调用者必     须清   除栈. 你可以从栈里把值 pop 出来:   push dword szText   call puts   pop ecx   或者直接修改 ESP:   push dword szText   call puts   add esp, 4   调用的返回值在 eax 或 edx:eax 如果值大于 32 位的话. EBP, ESI, EDI, EBX 由调用     者   保存和恢复. 你必须保存你要使用的寄存器, 像下面这样:   ; loop.asm =================================================================       global main   extern printf   section .text   msg db "HoodooVoodoo WeedooVoodoo",0Dh,0Ah,0   main:   mov ecx, 0Ah   push dword msg   looper:   call printf   loop looper   pop eax   ret   ; EOF ======================================================================       粗一看, 非常简单: 因为你在 10 个 printf() 调用用的是同一个字符串, 你不需要清       除栈. 但当你编译以后, 循环不会停止. 为什么? 因为 printf() 里什么地方用了 ECX       但没有保存. 使你的循环正确的工作, 你必须在调用之前保存 ECX 的值, 调用之后   恢复它, 像这样:   ; loop.asm ================================================================     global main   extern printf   section .text   msg db "HoodooVoodoo WeedooVoodoo",0Dh,0Ah,0   main:   mov ecx, 0Ah   looper:   push ecx ;save Count   push dword msg   call printf   pop eax ;cleanup stack   pop ecx ;restore Count   loop looper   ret   ; EOF ======================================================================       I/O 端口编程   --------------------   但直接访问硬件会怎么样呢? 在 Linux 下你需要一个核心模式的驱动程序来做这些   工作... 这意味着你的程序必须分成两个部分, 一个核心模式提供硬件直接操作的功   能, 其他的用户模式提供接口. 一个好消息就是你仍然可以在用户模式的程序中使用   IN/OUT 来访问端口.   要访问端口你的程序必须取得系统的同意; 要做这个, 你必须调用 ioperm(). 这个函     数只能被有 root 权限的用户使用, 所以你必须用 setuid() 使程序到 root 或者直接       运行在 root 下. ioperm() 的语法是这样:   ioperm( long StartingPort#, long #Ports, BOOL ToggleOn-Off)   'StartingPort#' 指明要访问的第一个端口值(0 是端口 0h, 40h 是端口 40h, 等等),     '#Ports'   指明要访问多少个端口(也就是说, 'StartingPort# = 30h', '#Port = 10', 可以访问     端口   30h - 39h), 'ToggleOn-Off' 如果是 TRUE(1) 就能够访问, 是 FALSE(0) 就不能访问     .   一旦调用了 ioperm(), 要求的端口就和平常一样访问. 程序可以调用 ioperm() 任意多     次,   而不需要在后来调用 ioperm()(但下面的例子这样做了), 因为系统会处理这些.   ; io.asm ===================================================================     =   BITS 32   GLOBAL szHello   GLOBAL main   EXTERN printf   EXTERN ioperm   SECTION .data   szText1 db 'Enabling I/O Port Access',0Ah,0Dh,0   szText2 db 'Disabling I/O Port Acess',0Ah,0Dh,0   szDone db 'Done!',0Ah,0Dh,0   szError db 'Error in ioperm() call!',0Ah,0Dh,0   szEqual db 'Output/Input bytes are equal.',0Ah,0Dh,0   szChange db 'Output/Input bytes changed.',0Ah,0Dh,0   SECTION .text   main:   push dword szText1   call printf   pop ecx   enable_IO:   push word 1 ; enable mode   push dword 04h ; four ports   push dword 40h ; start with port 40   call ioperm ; Must be SUID "root" for this call!   add ESP, 10 ; cleanup stack (method 1)   cmp eax, 0 ; check ioperm() results   jne Error   ;---------------------------------------Port Programming Part--------------     SetControl:   mov al, 96 ; R/W low byte of Counter2, mode 3   out 43h, al ; port 43h = control register   WritePort:   mov bl, 0EEh ; value to send to speaker timer   mov al, bl   out 42h, al ; port 42h = speaker timer   ReadPort:   in al, 42h   cmp al, bl ; byte should have changed--this IS a timer   jne ByteChanged   BytesEqual:   push dword szEqual   call printf   pop ecx   jmp disable_IO   ByteChanged:   push dword szChange   call printf   pop ecx   ;---------------------------------------End Port Programming Part----------     disable_IO:   push dword szText2   call printf   pop ecx   push word 0 ; disable mode   push dword 04h ; four ports   push dword 40h ; start with port 40h   call ioperm   pop ecx ;cleanup stack (method 2)   pop ecx   pop cx   cmp eax, 0 ; check ioperm() results   jne Error   jmp Exit   Error:   push dword szError   call printf   pop ecx   Exit:   ret   ; EOF ===
[1] [2] 下一页 

(出处:http://www.sheup.com)


上一页 [1] [2]