-
GAS(GNU Assembler)
- 设计目标:GAS 是 GNU 工具链的一部分,设计用于与 GNU 编译器集合(GCC)紧密集成。它支持多种平台和处理器架构,包括 x86、ARM、MIPS 等。
- 使用场景:主要用于与 GCC 一起使用,编写嵌入式系统、操作系统内核、以及其他需要与 GCC 集成的低级编程任务。它的设计使得它适合与 GNU 工具链中的其他工具(如
ld
链接器)一起使用。
-
NASM(Netwide Assembler)
- 设计目标:NASM 是一个专注于 x86 和 x86-64 架构的汇编器,设计简单,语法直观。它强调与现代汇编语言语法的一致性,并提供了丰富的宏功能。
- 使用场景:适用于需要对 x86 架构进行细粒度控制的任务,如编写操作系统、引导程序、以及其他需要直接控制硬件的低级编程。它可以单独使用,也可以与不同的工具链一起使用(如使用
ld
链接器或其他链接器)。
GNU 汇编器(GAS)和 NASM 汇编器在定义变量和数据的方式上有一些重要的区别。以下是这两种汇编器在定义变量时的一些主要区别:
1. 数据段(Section)
- GNU 汇编器(GAS) 使用
.data
,.bss
, 和.rodata
段来定义数据。 - NASM 汇编器 使用
section .data
,section .bss
, 和section .rodata
来定义数据。
示例:定义数据段
GAS
.section .data
my_var: .long 0x12345678 ; 定义一个32位整数
.section .bss
uninit_var: .skip 4 ; 预留4字节空间
NASM
section .data
my_var dd 0x12345678 ; 定义一个32位整数
section .bss
uninit_var resb 4 ; 预留4字节空间
2. 数据定义伪指令
- GNU 汇编器(GAS) 使用
.byte
,.short
,.long
,.quad
,.fill
等伪指令。 - NASM 汇编器 使用
db
,dw
,dd
,dq
,resb
,resw
,resd
,resq
等指令。
示例:定义数据
GAS
.section .data
byte_var: .byte 0x1 ; 定义一个字节
short_var: .short 0x1234 ; 定义一个16位整数
long_var: .long 0x12345678 ; 定义一个32位整数
NASM
section .data
byte_var db 0x1 ; 定义一个字节
short_var dw 0x1234 ; 定义一个16位整数
long_var dd 0x12345678 ; 定义一个32位整数
3. 未初始化数据
- GNU 汇编器(GAS) 使用
.bss
段来定义未初始化的数据。 - NASM 汇编器 使用
section .bss
和resb
,resw
,resd
,resq
来预留空间。
示例:未初始化数据
GAS
.section .bss
buffer: .skip 1024 ; 预留1024字节的空间
NASM
section .bss
buffer resb 1024 ; 预留1024字节的空间
4. 只读数据
- GNU 汇编器(GAS) 使用
.rodata
段来定义只读数据。 - NASM 汇编器 也使用
section .rodata
来定义只读数据。
示例:只读数据
GAS
.section .rodata
message: .asciz "Hello, World!" ; 定义一个以0结尾的字符串
NASM
section .rodata
message db 'Hello, World!', 0 ; 定义一个以0结尾的字符串
5. 符号导出
- GNU 汇编器(GAS) 使用
.global
指令将符号声明为全局。 - NASM 汇编器 使用
global
指令将符号声明为全局。
示例:定义全局符号
GAS
.section .data
.global my_var
my_var: .long 0x12345678
NASM
section .data
global my_var
my_var dd 0x12345678
总结
- 数据段声明:GAS 使用
.section
,NASM 使用section
。 - 数据定义伪指令:GAS 使用
.byte
,.short
,.long
,.fill
,NASM 使用db
,dw
,dd
,resb
等。 - 未初始化数据:GAS 使用
.bss
,NASM 使用section .bss
和resb
,resw
,resd
,resq
。 - 只读数据:GAS 使用
.rodata
,NASM 使用section .rodata
。 - 全局符号:GAS 使用
.global
,NASM 使用global
GAS示例代码:
linkage.h
#ifndef _LINUX_LINKAGE_H
#define _LINUX_LINKAGE_H
#define ENTRY(name) \
.globl name; \
ALIGN; \
name:
#endif
start.S
.text
#include "linkage.h"
.section ".data.pg_dir","w"
ENTRY(swapper_pg_dir)
.long 100
.fill 1023,4,0
main.c
#include <stdio.h>
// 声明swapper_pg_dir符号,它在外部定义
extern unsigned int swapper_pg_dir[];
int main() {
// 输出swapper_pg_dir的地址
printf("Address of swapper_pg_dir: %p\n", swapper_pg_dir);
// 访问swapper_pg_dir中的第一个值(应该是0)
printf("First value in swapper_pg_dir: %u\n", swapper_pg_dir[0]);
return 0;
}
gcc -c start.S -o start.o
gcc -c main.c -o main.o
gcc start.o main.o -o test
执行结果:
./test
Address of swapper_pg_dir: 0x55e20ee01010
First value in swapper_pg_dir: 100
代码解释
.section ".data.pg_dir","w"
.globl swapper_pg_dir
swapper_pg_dir:
.long 100
.fill 1023,4,0
1. .section ".data.pg_dir","w"
.section
:这条伪指令指定当前段的名称为.data.pg_dir
,并且该段是可写的("w"
)。如果你使用".data"
作为段名,它将是.data
段的一个变体,但在此示例中,.data.pg_dir
是一个自定义段名。.data
段通常用于已初始化的数据,但自定义名称可以用于组织不同类型的数据。
2. .globl swapper_pg_dir
.globl
:这条伪指令将swapper_pg_dir
符号声明为全局符号,使其可以在其他文件或模块中引用。
3. swapper_pg_dir:
swapper_pg_dir:
:定义了一个标签swapper_pg_dir
,用于在内存中标识数据的开始位置。
4. .long 100
.long
:将一个32位的整数 100 存储在swapper_pg_dir
标签所在的内存位置。
5. .fill 1023,4,0
.fill
:用于在内存中填充数据。1023
:填充的数量,即填充 1023 个 4 字节(总共 4092 字节)的数据。4
:每个填充项的大小,以字节为单位,这里是 4 字节。0
:填充的值,这里是 0。