开源编译器 NASM 使用(一)

NASM (Netwide Assembler),是一个 8086 和 x86-64 平台的汇编器,支持多种文件格式( a.out、ELF、Mach-O 和 COFF)。还可以输出二进制文件(bin)和 Intel 汇编十六进等格式,这一篇用来记录 NASM 的基本知识。

常用选项

NASM 使用如下格式来进行汇编

nasm -f <format> <filename> [-o <output>]

比如

nasm -f bin myfile.asm -o myfile.com

会将源文件 myfile.com 汇编为二进制文件 myfile.com。

-o

默认下 NASM 会根据 -f 选项选择合适的文件名,如 微软文件格式 (obj, win32 和 win64)会删除 .asm 后缀并添加 .ojb 后缀;对于 Unix 目标格式( aout、elf32 和 elf64 等)会使用 .o 进行替换;对于 bin 格式会直接删除 .asm 后缀名。

若工作目录下有同名文件 NASM 会直接覆盖,可以使用 -o 选项改变此行为。

-f

NASM 默认 bin 为输出文件格式。并且类似于 -o 选项,可以忽略选项和值之间的空格

nasm -fbin demo.asm -oprogram

-l

如果使用了 -l 选项,并且跟着一个文件名,NASM 会输出一个源文件十六进制格式的列表文件,其中地址和代码在左,源码(包括宏)在右。

nasm -f elf myfile.asm -l myfile.lst

可以在源代码中使用 [list -] 来去掉不需要显示的段。

-M

该选项可以向标准输出产生 makefile 依赖关系,可以重定位到一个文件中做进一步处理。

-F

类似于 -f 选项,但是产生的是附带指定调试格式的输出文件。使用

nasm -F <format> -y

来得到具体格式的可调试文件。

-i

NASM 允许源文件中有 %include 操作符,制定后不仅会在当前工作目录下搜索文件想要导入的文件,还会使用 -i 列出的路径。注意,NASM 只会将 -i 后面的路径加上 %include 构成完整文件名,所以在最后添加斜杠或者反斜杠是必要的(新版本会在 -i 选项后自动加上 /)。

-d, -u

-d 预定义一个宏,等价于 %define 操作符,可以方便 debug。相反,-u 取消一个宏。

-E

该选项命令 NASM 仅预处理,并将输出打印在标准输出上,-o 选项指定时保存为文件。但是,该选项在源文件需要计算表达式时会出错。

assign tablesize ($-tablestart) 会在 -e 选项下出错。

-On

指定多遍优化。如果有些复杂源文件需要多于两边的汇编,需要使用该选项。

-w

使汇编警告信息有效或者无效。

NASMENV 环境变量

系统中如果定义了环境变量 NASMENV,汇编器会将其视为对应值的命令行参数。通过在值开头写一个非减号字符,NASM 会将其视为选项间的分隔符,如

!-s !-ic:\nasmlib\ 等价于 -s -ic:\nasmlib\

NASM 指令

NASM 指令格式如下

label: instruction operands ;comment
  1. NASM 使用 / 来作为续行符。
  2. NASM 中 label 前后可以添加空格,冒号也是可选的
  3. label 中的字符为字母、数字、-、\$、#、@、~、.、和 ?,只有字母、.、下划线和问号可以开头。使用 \$ 开头则会将其作为标识符而不是保留字处理。($eax 便是合法的标识符)。
  4. 对于指令,可以使用指令前缀 LOCK、REP、REPE/REPZ、REPNE/REPNZ。也可以添加段寄存器作为前缀,如

    es mov [bx], ax ; ==> mov [es:bx] ax
  5. 操作数可以使用寄存器(eax,非 gas 格式)、有效地址、常数或者表达式。
  6. 对于 x87 浮点指令集,NASM 支持多种语法

    fadd st1 ; st0 := st0 + st1
    fadd st0, st1   ; 同上
    fadd to   st1   ; st1 := st0 + st1

伪指令

NASM 支持了一部分伪指令

  1. 初始化数据: DB、DW、DD、DQ、DT

    db 0x55 ; byte 0x55
    db 'hello', 13, 10, '$' ; char constants
    dw 0x1234 ; 0x34, 0x12( in order)
    dd 1.234567e20 ; single float
    dq 1.234567e20 ; double float
    dt 1.234567e20 ; extended-presion float

    注意 dq 和 dt 不支持数值常数或者字符串常数为操作数。

    为了方便存放连续的重复数据,NASM 还提供一种 DUP 修饰,如

    db %('AA')
    db 6 dup dword ('a', word 'b', 'c'); 'a','c'以 四字节存放,'b' 以两字节存放
  2. 存放未初始化数据:RESB、RESW、RESD、RESQ 和 REST

    buffer : resb 64 ; reserve 64 bytes
    word   : resbw 1 ; reserve 1  word
  3. 包含二进制文件:INCBIN

    将一个二进制文件逐字包含到输出文件中,如

    incbin "file.data" ; import whole file
    incbin "file.data" 1024, 512 ; skip first 1024 bytes and import at most 512 btyes
  4. 定义常数:EQU

    equ 指令用来定义一个符号,该指令要求源文件该行必须包含一个 label,如

    message db 'hello world'
    msglen  equ $-message

    上面两句指令使得 msglen 定义为常量 12,并且不能再被重定义。

  5. 重复:TIMES

    TIMES 前缀可以使得某一句指令被汇编多次,如

    zerobuf: times 64 db 0

    将连续占用 64 字节。类似与 equ、resb,times 的操作数也是关键表达式(critical expressions)。类似的功能还有预处理指令 %rep

    TIMES 不可被用在宏上,原因是 times 在预处理后才被解析

有效地址

NASM 中地址使用比较灵活,可以在中括号内使用一些代数表达式,比如

wordvar dw 123 ; reserve a word
mov     ax, [wordwar] ; as --> [wordwar]
mov     ax, [es:wordwav + bx] ;
mov     ax, [ax * 5]  ; suppoesed to be ax * 4 + ax
mov     ax, [ label1 * 2 - label2] ; label1 + (label1 - label2)

许多有效地址实际上等价,NASM 会自动产生最小化形式,如

[eax * 2 + 0]  ; == [eax+eax], NASM 会输出后一种
;可以使用 nosplit 关键词来强制 NASM 不做拆分
[nosplit eax*2]; != [eax+eax]

如果向强制输出定长的地址偏移,可以使用 BTYE、WORD、DWOR。如

mov ax, [dword eax + 3]

[eax] 和 [byte eax] 输出并不同,原因是后者会产生一个一个字节偏移 0。如果在 16 位模式下使用了一个 16 位地址,如果不是用长短关键字,则会丢失高位部分。

在 64 位模式中,NASM 默认产生的是绝对地址,可以使用 REL 关键字来产生 IP 相对地址。

常数

NASM 有四种不同类型常数:数字、字符、字符串和浮点数。

  1. 数字。可以使用 H、Q、B 来指定十六进制、八进制和二进制数。或者使用 0x 表示十六进制,或者使用 $ 前缀表示十六进制数(使用 $ 前缀需要紧跟数字),可以使用下划线来分隔过长的数字常量。
    mov ax, 0c8h
  2. 字符常数。使用单引号(双)最多包含八个字符,允许中间出现双引号。多个字符同时存在时,会按照小端法存取。

    mov eax, 'abcd'; --> 0x64636261

C 风格的转义字符也是支持的

  1. 字符串常量。字符串常量是出现在伪指令中的字符串,如 dx 指令。此外,NASM 还支持 Unicode 字符串。

  2. 浮点数常量

浮点数还有一些其它需要解释的语法。上面使用了 dd 和 dq 来分别存储单精度和双精度的浮点数,然而还可以使用 db(1:4:3) 和 dw( IEEE 754r half precision)来存储,代价是精度较小,可以使用的格式如下

dw -0.5
dd 1.222_222_222
dq 0x1p+32 ; 1^32
dq 1.e-10  ; 1^(-10)

可以使用一些特殊的运算符来编码特定长度的浮点数,如

mov rax, __?float64?__(3.141592653589793238462)

上面可以生成 64 位长度的浮点数,还有 __?float128h?__ 和 __?float128l?__ 分别生成 128 位浮点数的高 64 位和低 64 位。此外,还有符号 __?Infinity?__ 、__?QNan?__、__?SNan?__ 来分别生成无穷大QNAN(未定义算数结果)SNAN(未初始化),这种标记经常用于宏定义。

需要说明,NASM 由于可移植性不支持对浮点数进行运算。

表达式

NASM 支持和 C 类似的表达式记法,汇编时会计算为 64 bit 的整数,然后存储时会被截断为合适的长度,位运算和逻辑运算同样是支持的。

SEG 和 WRT

编写 16 位程序时,能够得到不同符号的段基址和段偏移是很有用的,NASM 提供了 SEG 和 WRT 运算符来获取上述地址。

mov ax, seg symbol
mov es, ax
mov bx, symbol

上述代码会使得 ES:BX 指向符号 symbol。如果想要该符号在另一个段下的偏移,可以使用 WRT(with reference to)

mov ax, symbol2
mov es, ax
mov bx, symbol wrt symbol2

这样段基址不同,但是最终还是指向同一个符号。

STRICT

当开启多遍优化时(O2 或者更高级优化),NASM 会使用最小的大小而不是指定的关键字来对数据进行编码,可以使用 STRICT 来强制生成指定的大小。

push strict dowrd 33; 带有优化选项时,不使用 strict 会编码为 3 个字节,使用 strict 会编码为 6 个字节

关键表达式

NASM 拥有一个可以进行多遍的优化器,可以是第一遍计算各个标签的地址,第二遍开始编址,等等。然而,对于某些指令来说,需要在第一遍时就需要计算出结果,这里使用的表达式我们便称为关键表达式。

times (label-$+1) db 0
label: db 'Where ?'

上面 times 伪指令操作数引用了一个对于该行还暂时不知道的地址 label,因此 NASM 会直接拒绝这样的写法。

本地标记

NASM 使用一种以句点 . 为前缀的本地标签声明的方法,比如

label1 ;
...
.label 
...
   jne .label
   ret

label2 ;
...
.label 
...
   jne .label
   ret

上面的 jne 指令都只会跳转到对应的 label 标签。

作 者:Owmaker
来 源:www.owmaker.cn
链 接:https://www.owmaker.cn/programming/skills/asm/nasm-1/
版 权 声 明:本博客所有文章除特别声明外,均采用CC BY-NC-SA 4.0许可协议。文章版权归作者所有,未经允许请勿转载!
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇