Linux 汇编语言开发指南

  汇编语言优点是速度快可以直接对硬件进行操作这对诸如图形处理等关键应用是非常重要Linux 是个用 C 语言开发操作系统这使得很多员开始忘记在 Linux 中还可以直接使用汇编这底层语言来优化性能本文为那些在Linux 平台上编写汇编代码员提供指南介绍 Linux 汇编语言语法格式和开发工具并辅以具体例子讲述如何开发实用Linux 汇编

  、介绍

  作为最基本编程语言的汇编语言虽然应用范围不算很广但重要性却勿庸置疑它能够完成许多其它语言所无法完成功能就拿 Linux 内核来讲虽然绝大部分代码是用 C 语言编写但仍然不可避免地在某些关键地方使用了汇编代码其中主要是在 Linux 启动部分由于这部分代码和硬件关系非常密切即使是 C 语言也会有些力不从心而汇编语言则能够很好扬长避短最大限度地发挥硬件性能

  大多数情况下 Linux 员不需要使用汇编语言即便是硬件驱动这样底层在 Linux 操作系统中也可以用完全用 C 语言来实现再加上 GCC 这优秀编译器目前已经能够对最终生成代码进行很好优化确有足够理由让我们可以暂时将汇编语言抛在边了但实现情况是 Linux 员有时还是需要使用汇编或者不得不使用汇编理由很简单:精简、高效和 libc 无关性假设要移植 Linux 到某特定嵌入式硬件环境下首先必然面临如何减少系统大小、提高执行效率等问题此时或许只有汇编语言能帮上忙了

  汇编语言直接同计算机底层软件Software甚至硬件进行交互它具有如下些优点:


  • 能够直接访问和硬件相关存储器或 I/O 端口;
  • 能够不受编译器限制对生成 2进制代码进行完全控制;
  • 能够对关键代码进行更准确控制避免因线程共同访问或者硬件设备共享引起死锁;
  • 能够根据特定应用对代码做最佳优化提高运行速度;
  • 能够最大限度地发挥硬件功能
  •   同时还应该认识到汇编语言是种层次非常低语言它仅仅高于直接手工编写 2进制机器指令码因此不可避免地存在些缺点:


  • 编写代码非常难懂不好维护;
  • 很容易产生 bug难于调试;
  • 只能针对特定体系结构和处理器进行优化;
  • 开发效率很低时间长且单调
  •   Linux 下用汇编语言编写代码具有两种区别形式种是完全汇编代码是整个全部用汇编语言编写尽管是完全汇编代码Linux 平台下汇编工具也吸收了 C 语言长处使得员可以使用 #、#def 等预处理指令并能够通过宏定义来简化代码第 2种是内嵌汇编代码是可以嵌入到C语言汇编代码片段虽然 ANSI C 语言标准中没有有关内嵌汇编代码相应规定但各种实际使用 C 编译器都做了这方面扩充这其中当然就包括 Linux 平台下 GCC

       2、Linux 汇编语法格式

      绝大多数 Linux 员以前只接触过DOS/Windows 下汇编语言这些汇编代码都是 Intel 风格但在 Unix 和 Linux 系统中更多采用还是 AT&T 格式两者在语法格式上有着很大区别:


  •   在 AT&T 汇编格式中寄存器名要加上 '%' 作为前缀;而在 Intel 汇编格式中寄存器名不需要加前缀例如:

       AT&T 格式 Intel 格式
    pushl %eax push eax



  •   在 AT&T 汇编格式中用 '$' 前缀表示个立即操作数;而在 Intel 汇编格式中立即数表示不用带任何前缀例如:

       AT&T 格式 Intel 格式
    pushl $1 push 1



  •   AT&T 和 Intel 格式中源操作数和目标操作数位置正好相反在 Intel 汇编格式中目标操作数在源操作数左边;而在 AT&T 汇编格式中目标操作数在源操作数右边例如:

       AT&T 格式 Intel 格式
    addl $1, %eax add eax, 1



  •   在 AT&T 汇编格式中操作数字长由操作符最后个字母决定后缀'b'、'w'、'l'分别表示操作数为字节(8 比特)、字(word16 比特)和长字(long32比特);而在 Intel 汇编格式中操作数字长是用 " ptr" 和 "word ptr" 等前缀来表示例如:

       AT&T 格式 Intel 格式
    movb val, %al mov al, ptr val



  • 在 AT&T 汇编格式中绝对转移和指令(jump/call)操作数前要加上'*'作为前缀而在 Intel 格式中则不需要
  •   远程转移指令和远程子指令操作码在 AT&T 汇编格式中为 "ljump" 和 "lcall"而在 Intel 汇编格式中则为 "jmp far" 和 "call far"即:

       AT&T 格式 Intel 格式
    ljump $section, $off jmp far section:off
    lcall $section, $off call far section:off



      和的相应远程返回指令则为:

       AT&T 格式 Intel 格式
    lret $stack_adjust ret far stack_adjust



  •   在 AT&T 汇编格式中内存操作数寻址方式是

    section:disp(base, index, scale)


      而在 Intel 汇编格式中内存操作数寻址方式为:

    section:[base + index*scale + disp]


      由于 Linux 工作在保护模式下是 32 位线性地址所以在计算地址时不用考虑段基址和偏移量而是采用如下地址计算思路方法:

    disp + base + index * scale


      下面是些内存操作数例子:

       AT&T 格式 Intel 格式
    movl -4(%ebp), %eax mov eax, [ebp - 4]
    movl .gif' />(, %eax, 4), %eax mov eax, [eax*4 + .gif' />]
    movw .gif' />(%ebx, %eax, 4), %cx mov cx, [ebx + 4*eax + .gif' />]
    movb $4, %fs:(%eax) mov fs:eax, 4



  •    3、Hello World!

      真不知道打破这个传统会带来什么样后果但既然所有设计语言个例子都是在屏幕上打印串 "Hello World!"那我们也以这种方式来开始介绍 Linux 下汇编语言设计

      在 Linux 操作系统中你有很多办法可以实现在屏幕上显示但最简洁方式是使用 Linux 内核提供系统使用这种思路方法最大好处是可以直接和操作系统内核进行通讯不需要链接诸如 libc 这样也不需要使用 ELF 解释器因而代码尺寸小且执行速度快

      Linux 是个运行在保护模式下 32 位操作系统采用 flat memory 模式目前最常用到是 ELF 格式 2进制代码个 ELF 格式可执行通常划分为如下几个部分:.text、.data 和 .bss其中 .text 是只读代码区.data 是可读可写数据区而 .bss 则是可读可写且没有数据区代码区和数据区在 ELF 中统称为 section根据实际需要你可以使用其它标准 section也可以添加自定义 section个 ELF 可执行至少应该有个 .text 部分下面给出我们个汇编是 AT&T 汇编语言格式:

      例1. AT&T 格式

    #hello.s
    .data          # 数据段声明
        msg : . "Hello, world!\n" # 要输出
        len = . - msg          # 字串长度
    .text          # 代码段声明
    .global _start      # 指定入口
        
    _start:         # 在屏幕上显示
        movl $len, %edx # 参数 3:串长度
        movl $msg, %ecx # 参数 2:要显示
        movl $1, %ebx  # 参数:文件描述符(stdout)
        movl $4, %eax  # 系统号(sys_write)
         $0x80    # 内核功能
        
                 # 退出
        movl $0,%ebx   # 参数:退出代码
        movl $1,%eax   # 系统号(sys_exit)
         $0x80    # 内核功能



      初次接触到 AT&T 格式汇编代码时很多员都认为太晦涩难懂了没有关系在 Linux 平台上你同样可以使用 Intel 格式来编写汇编:

      例2. Intel 格式

    ; hello.asm
    section .data      ; 数据段声明
        msg db "Hello, world!", 0xA   ; 要输出
        len equ $ - msg         ; 字串长度
    section .text      ; 代码段声明
    global _start      ; 指定入口
    _start:         ; 在屏幕上显示
        mov edx, len   ; 参数 3:串长度
        mov ecx, msg   ; 参数 2:要显示
        mov ebx, 1    ; 参数:文件描述符(stdout)
        mov eax, 4    ; 系统号(sys_write)
         0x80     ; 内核功能
                 ; 退出
        mov ebx, 0    ; 参数:退出代码
        mov eax, 1    ; 系统号(sys_exit)
         0x80     ; 内核功能



      上面两个汇编采用语法虽然完全区别但功能却都是 Linux 内核提供 sys_write 来显示然后再 sys_exit 退出在 Linux 内核源文件 /asm-i386/unistd.h 中可以找到所有系统定义

       4、Linux 汇编工具

      Linux 平台下汇编工具虽然种类很多但同 DOS/Windows 最基本仍然是汇编器、连接器和调试器

      1.汇编器

      汇编器(assembler)作用是将用汇编语言编写转换成 2进制形式目标代码Linux 平台标准汇编器是 GAS它是 GCC 所依赖后台汇编工具通常包含在 binutils 软件Software包中GAS 使用标准 AT&T 汇编语法可以用来汇编用 AT&T 格式编写:

       [xiaowp@gary code]$ as -o hello.o hello.s




      Linux 平台上另个经常用到汇编器是 NASM它提供了很好宏指令功能并能够支持相当多目标代码格式包括 bin、a.out、coff、elf、rdf 等NASM 采用是人工编写语法分析器因而执行速度要比 GAS 快很多更重要是它使用是 Intel 汇编语法可以用来编译用 Intel 语法格式编写汇编:

       [xiaowp@gary code]$ nasm -f elf hello.asm




      2.链接器

      由汇编器产生目标代码是不能直接在计算机上运行它必须经过链接器处理才能生成可执行代码链接器通常用来将多个目标代码连接成个可执行代码这样可以先将整个分成几个模块来单独开发然后才将它们组合(链接)成个应用 Linux 使用 ld 作为标准链接它同样也包含在 binutils 软件Software包中汇编在成功通过 GAS 或 NASM 编译并生成目标代码后就可以使用 ld 将其链接成可执行了:

       [xiaowp@gary code]$ ld -s -o hello hello.o




      3.调试器

      有人说不是编出来而是调出来足见调试在软件Software开发中重要作用在用汇编语言编写时尤其如此Linux 下调试汇编代码既可以用 GDB、DDD 这类通用调试器也可以使用专门用来调试汇编代码 ALD(Assembly Language Debugger)

      从调试角度来看使用 GAS 好处是可以在生成目标代码中包含符号表(symbol table)这样就可以使用 GDB 和 DDD 来进行源码级调试了要在生成可执行中包含符号表可以采用下面方式进行编译和链接:

       [xiaowp@gary code]$ as --gstabs -o hello.o hello.s
    [xiaowp@gary code]$ ld -o hello hello.o





      执行 as 命令时带上参数 --gstabs 可以告诉汇编器在生成目标代码中加上符号表同时需要注意在用 ld 命令进行链接时不要加上 -s 参数否则目标代码中符号表在链接时将被删去

      在 GDB 和 DDD 中调试汇编代码和调试 C 语言代码是你可以通过设置断点来中断运行查看变量和寄存器当前值并可以对代码进行单步跟踪图1 是在 DDD 中调试汇编代码时情景:

      意义
    "m"、"v"、"o" 内存单元
    "r" 任何寄存器
    "q" 寄存器eax、ebx、ecx、edx的
    "i"、"h" 直接操作数
    "E"和"F" 浮点数
    "g" 任意
    "a"、"b"、"c"、"d" 分别表示寄存器eax、ebx、ecx和edx
    "S"和"D" 寄存器esi、edi
    "I" 常数(0至31)



       8、小结

      Linux操作系统是用C语言编写汇编只在必要时候才被人们想到但它却是减少代码尺寸和优化代码性能种非常重要手段特别是在和硬件直接交互时候汇编可以说是最佳选择Linux提供了非常优秀工具来支持汇编开发使用GCC内联汇编能够充分地发挥C语言和汇编语言各自优点

    Tags: 

    延伸阅读

    最新评论

    发表评论