• <ruby id="5koa6"></ruby>
    <ruby id="5koa6"><option id="5koa6"><thead id="5koa6"></thead></option></ruby>

    <progress id="5koa6"></progress>

  • <strong id="5koa6"></strong>
    • 軟件測試技術
    • 軟件測試博客
    • 軟件測試視頻
    • 開源軟件測試技術
    • 軟件測試論壇
    • 軟件測試沙龍
    • 軟件測試資料下載
    • 軟件測試雜志
    • 軟件測試人才招聘
      暫時沒有公告

    字號: | 推薦給好友 上一篇 | 下一篇

    Linux 的 x86 匯編程序設計

    發布: 2007-5-26 11:31 | 作者: 佚名 | 來源: 互連網 | 查看: 23次 | 進入軟件測試論壇討論

    領測軟件測試網  本質上來說, 這篇文章是把我最感興趣的兩樣編程東西: Linux 操作系統和匯編語言程序設計結合在一起. 這兩個都不(或者說應該不)需要介紹; 像 Win32 的匯編,Linux 的匯編運行在 32 位的保護模式下...但它又有一個截然不同的優勢就是它允許你調用 C 的標準庫函數和 Linux 的共享庫函數. 我開始給 Linux 下的匯編語言編程來個簡要介紹; 為了更好讀一點, 你可能要跳過這個基本的小節.

    編譯和鏈接
    ---------------------
    Linux 下兩個最主要的匯編器是 Nasm(free, Netwide Assembler)和 GAS(free, Gnu Assembler),
    后一個和 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 的前四個字節). 因為 printf
    需要一個指向字符串的指針, 我們應該指明 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 ======================================================================

    在 Linux 下使用中斷
    -------------------------
    Linux 是一個運行在保護模式下的共享庫的環境, 意味著沒有中斷服務, Right?
    錯了. 我注意到在 GAS 的例子源碼中用了 INT 80, 注釋是 "sys_write(ebx, ecx, ed
    x)".
    這個函數是 Linux 系統調用接口的一部分, 意思是 INT 80 必須是到達系統調用服務
    的門戶. 在 Linux 源碼中到處看時(忽略從不要使用 INT 80 接口的警告, 因為函數號

    可能隨時改變), 我發現 "系統調用號(system call numbers)" -- 就是說, 傳給 INT
    80
    的 # 對應著一個系統調用子程序 -- 在 UNISTD.H 中. 一共有 189 個, 所以我不會在

    這里列出來...但如果你在 Linux 做匯編, 給自己做個好事, 打印出來吧.
    當調用 INT 80 時, eax 設為用調用的功能號. 傳給系統調用則程序的參數必須按順序

    放在下列寄存器中:
    ebx, ecx, edx, esi, edi
    這樣, 第一個參數就在 ebx 里, 第二個在 ecx 里... 注意在一個系統調用程序里, 不

    用棧來傳遞參數. 調用的返回值在 eax 里.
    還有, INT 80 接口和一般的調用一樣. 下面的這個程序就演示了 INT 80h 的使用. 這

    程序檢查并顯示了它自己的 PID. 注意 使用 printf() 格式化字符串 -- 這個調用的
    C 結構
    是:
    printf( "%d\n", curr_PID);
    也要注意結束符在匯編里不一定可靠, 我常用十六進制(0Ah, 0Dh)代表 CR\LF.
    ;pid.asm====================================================================

    BITS 32
    GLOBAL main
    EXTERN printf
    SECTION .data
    szText1 db Getting Current Process ID...,0Ah,0Dh,0
    szDone db Done!,0Ah,0Dh,0
    szError db Error in int 80!,0Ah,0Dh,0
    szOutput db \%d,0Ah,0Dh,0 ;printf() 的格式字符串
    SECTION .text
    main:
    push dword szText1 ;開始信息
    call printf
    pop ecx
    GetPID:
    mov eax, dword 20 ; getpid() 系統調用
    int 80h ; 系統調用中斷
    cmp eax, 0 ; 沒有 PID 0 !
    jb Error
    push eax ; 把返回值傳遞給 printf
    push dword szOutput ; 把格式字符串傳遞給 printf
    call printf
    pop ecx ; 清除棧
    pop ecx
    push dword szDone ; 結束信息
    call printf
    pop ecx
    jmp Exit
    Error:
    push dword szError
    call printf
    pop ecx
    Exit:
    ret
    ; EOF =====================================================================
    最后的話
    -----------
    大多數的麻煩來自對 Nasm 的習慣上. 而 nasm 帶有手冊, 但缺省是不安裝的,
    所以你必須把它從
    /user/local/bin/nasm-0.97/nasm.man
    移(cp 或 mv)到
    /usr/local/man/man1/nasm.man.
    格式有點亂, 可以很簡單的用 nroff 指示符來解決. 但它不會給你 Nasm 的整個文
    檔; 要解決這個問題, 把 nasmdoc.txt 從
    /usr/local/bin/nasm-0.97/doc/nasmdoc.txt
    拷貝到
    /usr/local/man/man1/nasmdoc.man
    現在你可以用 man nasm, man nasmdoc 來看 nasm 的手冊和文檔了
    想得到更多的信息, 查查這里:
    Linux Assembly Language HOWTO (Linux 匯編語言 HOWTO)
    Linux I/O Port Programming Mini-HOWTO (Linux I/O 端口編程 Mini-HOWTO)
    Jans Linux & Assembler HomePage (
    http://www.bewoner.dma.be/JanW/eng.html)
    我也要感謝 Jeff Weeks(
    http://gameprog.com/codex), 在我找到 Jan 的網頁之前
    給了我一些 GAS 的 hello-world 代碼.

    延伸閱讀

    文章來源于領測軟件測試網 http://www.kjueaiud.com/


    關于領測軟件測試網 | 領測軟件測試網合作伙伴 | 廣告服務 | 投稿指南 | 聯系我們 | 網站地圖 | 友情鏈接
    版權所有(C) 2003-2010 TestAge(領測軟件測試網)|領測國際科技(北京)有限公司|軟件測試工程師培訓網 All Rights Reserved
    北京市海淀區中關村南大街9號北京理工科技大廈1402室 京ICP備10010545號-5
    技術支持和業務聯系:info@testage.com.cn 電話:010-51297073

    軟件測試 | 領測國際ISTQBISTQB官網TMMiTMMi認證國際軟件測試工程師認證領測軟件測試網

    老湿亚洲永久精品ww47香蕉图片_日韩欧美中文字幕北美法律_国产AV永久无码天堂影院_久久婷婷综合色丁香五月

  • <ruby id="5koa6"></ruby>
    <ruby id="5koa6"><option id="5koa6"><thead id="5koa6"></thead></option></ruby>

    <progress id="5koa6"></progress>

  • <strong id="5koa6"></strong>