NASM 简介

NASM 全称 The Netwide Assembler

,是一款基于 x86 平台的汇编语言编译程序,其设计初衷是为了实现编译器程序跨平台和模块化的特性。 NASM 支持大量的文件格式,包括

Linux , BSD , a.out , ELF , COFF , Mach−O , Microsoft 16−bit OBJ , Win32

以及 Win64 ,同时也支持简单的二进制文件生成。它的语法被设计的简单易懂,相较 Intel 的语法更为简单,支持目前已知的所有 x86

架构之上的扩展语法,同时也拥有对宏命令的良好支持。

用 NASM 编写 Linux 下的 hello world 示例程序 hello.nasm 如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51


    GLOBAL _start


   


    [SECTION .TEXT]


    _start:


        MOV EAX, 4          ; write


        MOV EBX, 1          ; stdout


        MOV ECX, msg


        MOV EDX, len


        INT 0x80            ; write(stdout, msg, len)


   


        MOV EAX, 1          ; exit


        MOV EBX, 0


        INT 0x80            ; exit(0)


   


    [SECTION .DATA]


        msg: DB  "Hello, world!", 10


        len: EQU $-msg

编译和运行的命令如下( Debian-8.4-amd64 环境下):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12


    $ nasm -f elf32 -o hello.o hello.nasm


    $ ld -m elf_i386 -o hello hello.o


    $ ./hello


    Hello, world!

Linux 32位可执行程序中,用 “INT 0x80” 指令来执行一个系统调用,用 “EAX” 指定系统调用编号,用 “EBX,

ECX, EDX” 来传递系统调用需要的参数。上面这段汇编代码中,首先执行了编号为 4 的系统调用(write),向 stdout 写了一个长为

len 的字符串(msg),之后,执行编号为 1 的系统调用(exit)。

NASM 拥有对宏命令的良好支持,可以简化很多重复代码的编写。对于上面这个程序,可以编写两个名为 print 和 exit 的宏用来重复使用。新建一个 macro.inc 文件,内容如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66


    %MACRO print 1


        [SECTION .DATA]


            %%STRING:   DB %1, 10


            %%LEN:      EQU $-%%STRING


        [SECTION .TEXT]


            MOV EAX, 4          ; write


            MOV EBX, 1          ; stdout


            MOV ECX, %%STRING


            MOV EDX, %%LEN


            INT 0x80            ; write(stdout, %%STRING, %%LEN)


    %ENDMACRO


   


    %MACRO exit 1


        MOV EAX, 1


        MOV EBX, %1


        INT 0x80


    %ENDMACRO


   


    GLOBAL _start


   


    [SECTION .TEXT]


    _start:

新的 hello.nasm 如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15


    %include "macro.inc"


   


    print "Hello world!"


    print "Hello again!"


    exit 0

后面这段代码够简洁吧。

上面这段代码中的 %include 命令和 C 语言中的 #inlucde 的作用是一样的,就是把 %include 后面的文件名对应的文件的内容原样的拷贝进来。

下面再来解释一下 NASM 宏的使用。首先看简单一点的 exit 宏。 NASM 中: %MACRO 是宏定义的开始; %MACRO

后面接宏的名称;此处是 “exit” ;宏名后面是宏的参数数量,此处是 “1” ,表示该宏带有一个参数,宏内部中可以用 “%1, %2, %3,

…” 来引用宏的第 1 、 2 、 3 、 … 个参数; %ENDMACRO 是宏定义的结束。

宏定义好后,若后面的代码中遇到这个宏,则会用宏定义中的内容来替换这个宏。如 hello.nasm 中的 第 5 行 “exit 0”,会被替换成:

1
2
3
4
5
6
7
8
9


    MOV EAX, 1


    MOV EBX, 0


    INT 0x80

注意宏定义中的 %1 将被替换为 exit 后面的参数 0

print 宏定义稍微复杂一点,多了 %%STRING 和 %%LEN ,它们可以看成是宏定义中的局部名称,在每个 print 宏被展开的时候, NASM 会为这种类型的名称生成一个唯一的标志符。我们可以用 nasm -e hello.nasm 来查看 hello.nasm 文件经过预处理后的代码,如下(以下代码经过的适当的缩进和注释处理):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93


    [global _start]


   


    [SECTION .TEXT]


    _start:


   


    ; print "Hello world!"


    [SECTION .DATA]


        ..@1.STRING: DB "Hello world!", 10


        ..@1.LEN: EQU $-..@1.STRING


    [SECTION .TEXT]


        MOV EAX, 4


        MOV EBX, 1


        MOV ECX, ..@1.STRING


        MOV EDX, ..@1.LEN


        INT 0x80


   


    ; print "Hello again" 


    [SECTION .DATA]


        ..@2.STRING: DB "Hello again!", 10


        ..@2.LEN: EQU $-..@2.STRING


    [SECTION .TEXT]


        MOV EAX, 4


        MOV EBX, 1


        MOV ECX, ..@2.STRING


        MOV EDX, ..@2.LEN


        INT 0x80


   


    ; exit 0


    MOV EAX, 1


    MOV EBX, 0


    INT 0x80

可以看到,在 ‘print “Hello world!”’ 宏中, %%STRING 被展开为 ..@1.STRING ,而在 ‘print “Hello again!”’ 宏中, %%STRING 被展开为 ..@2.STRING 。