zoukankan      html  css  js  c++  java
  • 如何在Linux下写汇编

    本文为翻译文章,原文参见:http://docs.cs.up.ac.za/programming/asm/derick_tut/

    1.NASM编译器

    目前Linux下的汇编器主要有:as、as86和gas,但是本文使用的是NASM(The Netwide Assembler)。它使用Intel形式的汇编格式,和Intel形式相对的是AT&T形式的汇编格式。

    2.Linux下汇编介绍

    2.1DOS和Linux下汇编的主要不同

    (1)DOS下的汇编,主要通过 int 21h 中断来实现各种DOS功能调用,而BIOS调用则是主要通过 int 10h 和 int 16h 中断来实现。但是在Linux中, 所有以上的功能调用都是通过内核来实现的。因此所有的功能都是通过“系统调用”来实现,而我们可以通过使用 int 80h 中断来实现系统调用。其中,Linux大约有190个左右的系统调用,比DOS下的要少。

    (2)Linux是一个真正32位保护模式的操作系统,因此我们使用的是32为的汇编程序。32位汇编程序运行我们使用全部的内存(4G),这意味着我们不用在考虑段基址了,也不用在修改和操作段寄存器了,从某种程度上来说,变的更容易了。

    (3)在32为汇编程序中,我们可以使用32位的寄存器 EAX、EBX、ECX等代替传统的16位寄存器 AX、BX、CX等寄存器。

    2.2Linux下汇编的编写

    (1)数据段 (.data section)

    数据段主要用来“声明初始化数据”,换句话说,是用来定义常“变量”的,这个“变量”主要是指在程序中不会一直变化,定义后就会保持不变。通常数据段都是用来定义常用的标号,比如:文件名、缓存大小等等,当然,你也可以使用 EQU 这个指令来实现。定义常“变量”可以使用的指令有:DB、DW、DD、DQ和DT,例如:

    section    .data
        message:        db    'Hello World!'    ;声明一个字节类型(byte)的字符数组
        msglength:    equ    12            ;声明字节数组的长度
        buffersiz:        dw    1024            ;声明一个1024大小(字)的缓存

    (2)变量段(.bss section)

    该段主要是用来定义变量的,这里可以使用的指令有:RESB、RESW、EWSD、EWSQ和REST等,这些指令可以用来预留一些内存空间给定义的变量。例如:

    section    .bss
        filename:        resb    255    ;定义255个字节的内存空间
        number:        resb    1    
        bignum:        resw    1    ;定义1个字的内存空间(1字=2字节)
        realarray:        resq    10    ;定义10个reals大小的数组

    (3)代码段(.text section)

    这部分主要是用来写汇编代码的,通常,代码段必须以 global _start(_start为标号,自定义)开头,他的主要含义是告诉内核程序是从这里开始的,内核在看到这部分变编译后的信息,就会将相关的CS:IP指向这里,然后开始执行程序。就和C函数中的main()函数类似。例如:

    section    .text
        global    _start
    
        _start:
            pop    ebx    ;这里是程序开始执行的入口
            .
            .
            .

    2.3 Linux下的系统调用

    Linux下的系统调用和DOS下的系统调用类似,主要通过以下几个步骤:

    (1)将你的系统调用号放进EAX中(因为我们是在32位下,所以使用32位的EAX寄存器)。

    (2)设置系统调用参数,并且依次将参数放进EBX、ECX、EDX、ESX、EDI和EBP。

    (3)调用相关中断(对应Linux来说是 80h;对于DO来说是 21h)。

    (4)最后的调用结果会返回到EAX中保存。

    说明:第二步中的参数是按照顺序放置的,比如第一个参数放在EBX中,第二个参数放在ECX中,…第5个参数放在EDI中。但是只有Linux2.4以后的版本才至此第6个参数EBP,以上的版本只支持前5个参数。如果有多于6个参数,则EBX用来存放参数列表在内存中的位置,但是通常情况下是不会多于6个参数的。

    例如:

    mov    eax,1    ;exit系统调用
        mov    ebx,0    ;返回参数是0
        int    80h    ;使用80h中断,然后系统内核便开始调用函数

    从上述例子我们可以看出,exit()函数的系统调用号是1,但是我们怎么知道其他的系统调用号呢?并且他们的参数是如何?首先,所有的系统调用都位于 /usr/inlucde/(asm/asm-generic)/unistd.h中,其中也包含了他们的系统调用号(比如exit对应的是1)。但是为了方便,我们可以在下面的Linxu系统调用表中查看系统调用所对应的编号。当然,我们可以通过man 2 系统调用函数(man 2 exit)来查看该系统调用的具体情况。

    2.4 “Hello Word!”

    还是经典的“Hello Word!”,为了将其打印出来,我们使用标准输出(STDOUT),其文件描述符为1,以下是完整的程序:

    section    .data
        hello:    db    'Hello World!'    ;字符
        helloLen    equ    $-hello            ;字符的长度
    
    section    .text
        global    _start
    
    _start:
        mov eax,4    ;sys_write的系统调用
        mov ebx,1    ;参数1,文件描述符,stdout是1
        mov ecx,hello    ;字符的起始地址
        mov edx,helloLen    ;字符的长度
    
        int 80h        ;系统调用
    
        mov eax,1    ;sys_exit的系统调用
        mov ebx,0    ;sys_exit的返回参数0,表示无错误
        int 80h

    我们在vim中书写上述代码,然后将其保存为 hello.asm。

    2.5 编译与连接

    (1)打开控制终端

    (2)将当前目录设置成 hello.asm同一目录

    (3)使用NASM编译 hello.asm程序: nasm –f elf hello.asm

    (4)使用 ld –s –o hello hello.o 指令连接程序

    (5)运行程序 ./hello

    程序将会输出:“Hello World!”

    3 进阶

    3.1 命令行参数和栈

    从DOS程序中获取命令行参数是一件痛苦的经理,因为这要考虑到PSP和段寄存器。但是,在Linux中,这一切都显示那么简单,但程序开始运行时,所有的参数都会被放到栈中,如果我们想要获取他们,只需pop即可。

    假设现在有一个program程序,他有三个参数:

    ./program foo bar 42

    则现在程序的栈是如下形式:

    image

    现在,我们可以写一个获取其三个参数的程序出来:

    section .text
        global _start
    
    _start:
        pop eax    ;获取参数个数:3
        pop ebx    ;获取程序名称:program
        pop ebx    ;获取第一个参数:foo
        pop ecx    ;获取第二个参数:bar
        pop edx    ;获取第三个参数:42
    
        mov eax,1
        mov ebx,0
        
        int 80h    ;退出

    3.2 “过程”和跳转

    NASM中没有TASM中的过程定义,但是可以使用标号来代替。例如:

    image

    其中有一点需要注意:

    你可以 jump 到一个标号,但是你必须 call 一个 过程。

    假设现在有如下代码:

    if(AX == 'w') {
     writeFile();
    } else {
     doSomethingElse();
    }

    那么其对应的汇编将是如下形式:

    cmp    AX,'w'        ;
    jne    skipWrite        ;
    call    writeFile        ;
    jmp    outOfThisMess    ;
    
    skipWrite:
        call doSomethingElse
    outOfThisMess:
        ...        ;

    3.3 Linux和DOS汇编程序对比

    3.3.1 “Hello World”程序输出对比

    image

    对比说明:

    (1)DOS下的前三行,在Linux中是不需要的,因为Linux是一个32位的保护模式的操作系统,因此所有的寄存器和分页都已经是32位的,无需特殊处理,因此就不需要段寄存器,并且也不需要设置栈的大小。

    (2)Linux下的NASM和DOS下的TASM/MASM的语法区别:

    1) NASM使用SECTION .DATA而不是MASM中的.DATA。

    2) NASM运行我们直接使用EQU定义常量,例如:bufferlen : equ 400,那么在程序中bufferlen就等于400,这意味着我们不必使用括号来去该地址的内容。

    3) NASM使用$表示当前行偏移SECTION的地址,如:hello: db 'Hello world!',10 helloLen: equ $-hello 中的$就表示当前行的偏移地址,$-hello表示当前行和上一行相差的距离,即字符的长度。

    4) Linux下可以直接使用字符长度打印出字符,不必像DOS那样采用$-terminated的形式。

    5) 为了打印出字符后换行,在Linux中需要加一个linefeed character(10),但是在DOS下,必须使用一个linefeed character(10)和一个carriage return(13)。

    6) Linux下的代码段叫.TEXT,而DOS下的叫.CODE

    7) Linux下的代码段,必须以GLOBAL XXX开始,这样做的目的是告诉系统内核程序开始执行的地址。

    8) 由于Linux下无需考虑段寄存器,所以Linux下的汇编代码没有ASSUME关键字。

    9) Linux下代码段(.text section)的结束不需要想DOS那样 END XXX。

    (3) DOS下汇编程序中,START标签的前两句主要表示:DS寄存器指向 data segment;CS寄存器指向code segment。Linux直接使用32位寄存器,所以不需要这么设置。

    (4) 在16位的DOS汇编程序中,我们使用16位的寄存器 AX、BX、CX、DX等。但是在32位的Linux中,我们使用扩展寄存器 EAX、EBX、ECX、EDX等。其中 AX 是 EAX 的低16位,AH 是 AX的高8位,AL 是 AX 的底8位,没有EAL这种寄存器。

    (5) 在DOS中,如果我们想将变量的地址放进寄存器中,我们必须使用offset关键字来将其放进正确的段寄存器中。但是在Linux中不需要如此。

    (6) 在DOS中,我们使用 int 21h 中断来调用 DOS 服务来打印字符。但是在Linux中,我们使用 int 80h 中断来调用 系统调用 来打印字符。并且其参数放置位置不同(参见3.1)。

    (7) 退出调用的不同之处和(6)类似。

    3.3.2 命令参数及文件写入对比

    image

    image

    image

  • 相关阅读:
    navicat连接mysql报错1251解决方案
    ubuntu 安装nodejs/npm
    sync-settings(vscode)
    ubuntu远程桌面连接windows系统
    three.js中点生成矩阵方法
    threeJs中旋转位移等操作坐标系
    ubuntu查看进程端口号及运行的程序
    Ubuntu终端远程连接linux服务器
    THREE.OrbitControls参数控制
    canvas设置长宽
  • 原文地址:https://www.cnblogs.com/fingertouch/p/3054721.html
Copyright © 2011-2022 走看看