• [原]nasm语法


    工具:
    nasm 汇编
    gcc  编译c
    ld    进行链接
    kscope 查看源代码
    make 工程管理
    khexedit  分析二进制文件

    一:
    nasm源文件布局:
    像其他汇编器一样, nasm源文件包含四个域的组合。(除了宏, 或者预编译器指示, 或者汇编指示 )
    label标号: 指令 操作数 ;注释
    通常, 这些域是可选的。 当然, 操作数域是根据指令的要求来放置,或者去掉的。

    nasm使用/作为行链接符, 如果一行以/结尾, 下一行认为是本行的继续.

    nasm对于空格没有限制; 标号可以在前面包含空格, 或者指令前可以没有空格. 标号后的冒号是可选的. (可以使用-w+orphan-label选项来提示某行只有标号).

    标号中的字符包括字母, 数字, _$#@~.?  可以字母开头,  . (.有特殊的含义!), _和? 也可以作为头部. 一个标识符可能也有$前缀, 表明是作为标识符读取的, 并不是保留字; 因此, 如果你链接的其他模块定义了符号eax, 你可以通过$eax去区分符号和寄存器. 标识符最大的长度为4095.

    指令域可能包含任何机器指令: 奔腾指令, 奔6指令, FPU指令, MMX指令, 甚至未文档化的指令,都可以被支持. 指令可能包含前缀LOCK, REP, REPE, REPZ, 或者REPNZ, REPNE. 显式的地址大小和操作数大小前缀A16, A32, A64, O16 和O32, O64 也被提供了. 一个使用他们的例子在10章.
    你可以使用段寄存器名字作为指令前缀: 编码 es mov [bx], ax 等价于 mov [es:bx], ax.  但是对于LODSB等类似指令, 没有操作数, 但是也需要一个段寄存器, 除了es lodsb 没有其他办法.

    一个指令一下情况需要前缀: 前缀是cs, a32, lock, repe可以独立出现在一行, nasm会生成带前缀的代码.

    另外, 对于实际的机器指令, nasm支持伪指令.

    指令操作数有很多形式: 寄存器, 有效地址, 常数, 或者表达式.

    对于x87浮点指令, nasm接受叫大范围的语法, 使用双操作数, 或者使用nasm本地单操作数.例如
    fadd st1
    fadd st0, st1
    fadd st1, st0
    fadd to st1
    几乎所有x87引用内存的浮点指令都使用前缀DWORD, QWORD, TWORD, 表明内存操作数的大小.

    2伪指令:
     DB DW DD DQ DT DY; 他们的未初始化对应物是RESB RESW RESD RESQ REST RESO和 RESY, INCBIN命令, EQU命令, TIMES前缀.

    2.1DB声明初始化数据
    db  0x55   db 0x55, 0x56, 0x57
    db 'a', 0x55
    db 'hello', 13, 10, '$'
    dw 0x1234
    dw 'a'   ;数字
    dw 'abc' ;对其二字节 
    dd 0x12345678
    dd 1.234567e20
    dq 0x1234567abc
    dq 1.23456e20
    dt  1.2345e20
    DT, DO, DY 不接受数字常量作为操作数
    2.2RESB  声明为初始化数据
    RESB RESW RESQ RESD REST RESO RESY 用来声明模块的BSS区域: 声明为初始化的存储区域(block start by symbol). 每个产生一个操作, 字节数, 字数, 双字数, 或者其他要保留的大小. RESB类型伪指令是一个重要的表达式.
    例如:
    buffer:  resb 64
    wordvar: resw 1
    realarray: resq 10
    ymmval: resy  1 在ymm寄存器中

    2.3INCBIN包含外部二进制文件:
    INCBIN从早期的DevPac汇编器中借来的: 包含一个二进制文件verbatim到输出文件中去. 这可以便于包含图形文件, 声音文件直接到一个游戏执行文件中去. 可以有一下3种调用方式:
    incbin "file.dat"   整个文件
    incbin "file.dat", 1024  忽略头1024字节
    incbin "file.dat", 1024, 512 忽略头1024字节, 最多包含512字节
    INCBIN即是指示, 也是宏.

    2.4EQU:定义常数
    EQU定义一个符号赋值常数: 当使用EQU, 源代码必须包含标号.
    message  db  'hello'
    mslen   equ   $-message

    2.5TIMES: 重复指令或者数据
    TIMES前缀引起指令汇编多次. 等价于DUP
    zerobuf:   times  64  db 0
    或者类似; 但是TIMES功能更多. times的参数不仅仅是数字常量, 也可以是表达式
    buffer: db  'hello'
    times  64  -$+buffer  db ' '
    将会保存足够的空间, 使buffer达到64. 最后, times可以应用到一般指令, 你可以编码平凡非回转循环在其中:
    times  100  movsb
    注意到: times 100 resb  1 和 resb 100没有区别, 除了后者可以更快的汇编.
    times的操作数是重要的表达式.
    注意到times不能应用到宏中: 原因是times在宏阶段后处理, 允许times使用参数包含表达式, 例如64-$+buffer.
    为了重复多余一行的代码, 或者复杂宏, 使用预编译指令%rep

    3有效地址:
    一个有效地址是任何操作数, 在指令中引用内存. 有效地址, 在nasm中有很简单的语法: 由表达式组成, 等价于期望的地址, 被方括号包围:
    wordvar  dw 123
    mov   ax , [wordvar]
    mov   ax, [word+1]
    mov   ax, [es:wordvar +bx]
    任何不符合这个规则的东西都不是有效的内存引用.例如: es:wordvar[bx]
    更复杂的有效地址, 例如包含多个寄存器:
    mov  eax , [ebx*2 + ecx + offset]
    mov ax, [bp + di + 8]
    nasm有能力对这些有效地址作代数运算, 因此这些看起来不合法的表达式, 是正确的:
    mov  eax, [ebx*5] ;汇编为 ebx*4 + ebx
    mov  eax, [label1*2-label2] ;汇编为: label1 + (label1 - label2)
    一些形式是有效地址, 含有超过一种汇编形式; 大多数情况下, nasm会生成最小的形式. 例如对于[eax*2 + 0]和[eax + eax], 有两种不同的汇编形式, nasm会生成后者.

    nasm有一个提示机制, 可以使[eax+ebx] 和[ebx+eax]生成不同的代码; 这个偶尔有用, 因为[esi+ebp]和[ebp+esi]有不同的默认段寄存器.

    然而, 你可以强制nasm生成有效地址以特殊形式, 通过使用关键字BYTE, WORD, DWORD, NOSPLIT. 如果你使用[eax+3]汇编成双字偏移, 而不是单字节, 你可以使用[dword eax+3].
    类似的, 你可以强制nasm使用单字节偏移对一个小的数值, 没有在第一遍中发现.,[byte eax+offset].
    作为特例, [byte eax]编码[eax+0], 使用一个字节偏移0, [dword eax]编码双字偏移. 正常形式[eax]没有偏移域.

    以上描述的在你从16位代码中访问32位段中的数据时也是有用的. 可以查看10.2混合大小寻址章.
    特别地, 如果访问已知偏移的数据比当前16位值大, 如果你不指明这是个dword偏移, nasm将会使偏移的高字节丢失.

    类似的, nasm会分割[eax*2]为[eax+eax]因为允许偏移部分被省略, 空间节省; 事实上, 他也会分割[eax*2 + offset]为[eax+eax +offset]. 你可以通过使用NOSPLIT来抵抗这种行为: [nosplit eax*2]强制按字面生成[eax*2 + 0].

    64位模式下, nasm会默认生成绝对地址. rel关键字使之生成rip相关地址.

    3.5常数:
    nasm理解四种形式的常数:数字, 字符, 字符串, 浮点数.
    4.1数字常数:
    nasm允许你指定数字以各种进制: 你可以添加后缀H, X, Q, O, B用于16进制, 8进制, 2进制, 你也可以使用0x, 或者前缀$.
    当前nasm使用0h 0o 0q 0b只是16, 8 ,2进制.
    例如:
    mov  ax  , 200
    mov  ax, 0200
    mov  ax, 0200d
    mov ax, 0d200
    mov ax, 0c8h
    mov ax, $0c8
    mov ax 310o
    mov ax, 11001000b

    4.2字符串:
    字符串由"", '', ``包含. 单引号, 双引号等价的, 反引号用于特殊字符.
    db  `/u263a`
    db `/xe2/x98/xba`
    db 0e2h, 098h, 0bah

    4.3字符常量
    mov  eax, 'abcd'

    4.4字符串常量
    db  'hello'

    4.6浮点数
    db  -0.2
    dw -0.5
    dd  1.2
    dd 1.222222222
    dd 0x1p+2
    dq 1.e10

    5表达式:
    nasm表达式类似于c. 表达式等价于64为整数, 将会在后来被调整到合适大小.
    nasm支持两种表达式词法记号, 允许在当前汇编位置计算: $和$$词法记号. $计算汇编位置, 在包含行的开始; 所以你可以编码无限循环 JMP $. $$计算当前段开始; 所以你可以通过$-$$得知在当前段中的位置.

    nasm提供算术操作:
    |位或, ^异或 &位与 << >>位移 + - 加减, * / // % %% 乘法和除法
    /无符号乘法, //有符号除法, %无符号, %%有符号求模操作.

    nasm不提供对于有符号的求模操作符的强力操作的保证.

    因为%字符被宏预编译扩展使用, 你应该保证被空格后接有符号, 无符号求模操作符.
    单操作符: + - ~ ! SEG
    ~求补, !求反, SEG求操作数段地址.

    6SEG WRT
    当写16位大程序, 必须被分割为多个小段, 因此很有必要去定位段的地址的一部分, 通过一个符号. NASM支持seg操作符, 来完成这个功能.

    seg操作符返回符号所在的段的基址, 定义段基地址相对于符号的偏移是有意义的. 因此代码:
    mov  ax, seg symbol
    mov  es, ax
    mov bx, symbol
    将会加载ES:BX指向符号的有效指针.

    事情可能会比这个复杂: 因为16位段和组可能会重叠, 你可能偶尔希望通过不同的段基地址来引用一些符号. nasm允许这样作, 通过使用wrt关键字, 你可以这样做:
    mov  ax, werid_seg
    mov  es, ax
    mov  bx, symbol wrt werid_seg
    去用不同的, 但是功能等价的指针指向符号symbol 加载到es:bx.(with reference to)

    nasm支持段内的远调用, 和远跳转, 通过call segment:offset, segment, 和offset均代表立即数值. 因此要远调用一个过程, 你可能编码如下:
    call    (seg  procedure): procedure
    call    weird_seg : (procedure wrt weird_seg)
    加入括号是为了清楚, 展示以上指令语法分析的目的.

    nasm支持语法call far procedure 作为上面第一种的同义词. jmp在以上例子中功效等同于call:

    为了声明一个对数据的远指针在数据段中, 你应该这样:
    dw   symbol,  seg  symbol
    nasm没有类似的同义语, 虽然你可以使用宏来发明一个.

    7抑制优化 STRICT
    当在2或以上优化选项下, 汇编代码, nasm会使用大小限定符, 但是会给他们最小的大小. 关键字strict可以用于抑制这种优化.
    例如: 在16位模式下:
    push dword 33
    被编码为66 6a 21, 而  push dword 33编码为 66 68 21 00 00 00

    8关键表达式:
    尽管nasm有可选的多边优化, 有若干表达式必须在第一遍中被解析, 这些称为关键表达式.

    第一遍用于决定所有汇编代码的大小, 在第二遍, 当生成所有的代码时, 知道所有的代码引用的符号地址. 因此, nasm不能处理的事是代码大小决定于后面声明的符号的值, 在代码被提问时. 例如:
    times (label-$) db 0
    label: db  'where am i'
    这种情况下times的参数合法的等价于任何值; nasm将会拒绝这种情况, 因为他不能在首次见到时知道times行的大小.
    times (label-$+1) db 0
    label: db "now where am i"
    这种情况下, 任何值都错.

    nasm拒绝这些例子, 通过一个叫做关键表达式的概念, 被定以为一个表达式, 其值需要在第一遍中计算出来, 因此只能依赖于前面定义的符号. times的参数是关键表达式.

    本地变量:
    对于以.开始的符号, nasm给予特殊的对待. 一个标号以一个原点开始, 被认为是局部变量, 意味着和以前的非局域变量存在联系, 例如:
    label
    .loop
    jne  .loop
    ret
    label2
    .loop
    jne .loop
    ret
    在上面的代码片段中, 每个jne指令跳转到前面的行, 因为两个.loop的定义是隔离的通过前面的非局部变量的联系的效力.

    这种局部标号的处理借鉴于amiga汇编器; 然而, nasm更进了一步, 允许访问局部变量从代码的另一个部分. 这通过定义局部变量根据前面的非局部变量: 第一个.loop的定义实际是定以为label1.loop, 第二个是label2.loop, 因此, 你如果真的需要
    label3
    jmp label1.loop
    有时是有用的, 在宏里, 例如, 可以定义一个标号可以从任何地方引用, 但是不会干预正常的局部标号机制. 这种标号不能是非局部的, 因为定义他的宏不知道label的全名. nasm因此引入了第三类标号, 可能值在宏中使用: 如果以..@开始的一个标号, 不会对局部标号产生作用:
    label
    .local:
    ..@foo:
    label2:
    .local:
    jmp ..@foo
    nasm有能力去定义其他特殊符号, 以..开始例如
    ..start, 用来指定obj文件的入口.









     


     












    作者:liyonghelpme 发表于2010/6/12 0:19:00 原文链接
    阅读:3319 评论:0 查看评论
  • 相关阅读:
    activiti实战系列之动态表单 formService 自定义变量类型
    js向一个数组中插入元素的几个方法-性能比较
    Mac系统安装和配置tomcat步骤详解
    Spring注解之@validated的使用
    Spring注解之@Lazy注解
    centos命令行系列之升级glibc到
    docker实战系列之docker 端口映射错误解决方法
    centos命令行系列之centos6防火墙的关闭以及开启
    docker实战系列之搭建rabbitmq
    运营型模型
  • 原文地址:https://www.cnblogs.com/liyonghelpme/p/4273547.html
Copyright © 2020-2023  润新知