• 软件测试技术
  • 软件测试博客
  • 软件测试视频
  • 开源软件测试技术
  • 软件测试论坛
  • 软件测试沙龙
  • 软件测试资料下载
  • 软件测试杂志
  • 软件测试人才招聘
    暂时没有公告

字号: | 推荐给好友 上一篇 | 下一篇

Linux下的汇编器

发布: 2007-7-04 12:06 | 作者: admin | 来源:  网友评论 | 查看: 17次 | 进入软件测试论坛讨论

领测软件测试网
  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 ======================================================================
  
  
  在 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( "%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)
  Jan's Linux & Assembler HomePage (http://www.bewoner.dma.be/JanW/eng.html'

文章来源于领测软件测试网 https://www.ltesting.net/


关于领测软件测试网 | 领测软件测试网合作伙伴 | 广告服务 | 投稿指南 | 联系我们 | 网站地图 | 友情链接
版权所有(C) 2003-2010 TestAge(领测软件测试网)|领测国际科技(北京)有限公司|软件测试工程师培训网 All Rights Reserved
北京市海淀区中关村南大街9号北京理工科技大厦1402室 京ICP备2023014753号-2
技术支持和业务联系:info@testage.com.cn 电话:010-51297073

软件测试 | 领测国际ISTQBISTQB官网TMMiTMMi认证国际软件测试工程师认证领测软件测试网