歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux綜合 >> Linux資訊 >> 更多Linux >> Linux下的匯編器

Linux下的匯編器

日期:2017/2/27 9:33:39   编辑:更多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 ===




Copyright © Linux教程網 All Rights Reserved