分享
 
 
 

Linux 的 x86 汇编程序设计

王朝system·作者佚名  2006-11-24
窄屏简体版  字體: |||超大  

本质上来说, 这篇文章是把我最感兴趣的两样编程东西: 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 的例子[url=http://www.pccode.net].net" class="wordstyle"源码中用了 INT 80, 注释是 "sys_write(ebx, ecx, ed

x)".

这个函数是 Linux 系统调用接口的一部分, 意思是 INT 80 必须是到达系统调用服务

的门户. 在 Linux [url=http://www.pccode.net].net" class="wordstyle"源码中到处看时(忽略从不要使用 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( "%dn", curr_PID);

也要注意结束符在汇编里不一定可靠, 我常用十六进制(0Ah, 0Dh)代表 CRLF.

;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 代码.

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有