文章目录
前言
- 机器指令是用二进制代码表示的CPU能直接识别和执行的一种指令系统的集合,不同的CPU架构有不同的机器指令。汇编指令是机器指令便于记忆的书写格式,汇编指令编写完成后通过汇编器将其翻译成机器指令供CPU执行,因此,汇编器的功能是将汇编指令翻译成机器指令。
- 同一条机器指令,可以用不同的汇编指令表达,只要汇编器最终能正确无误地翻译就可以。不同的汇编指令格式衍生出不同的汇编语法,针对每种汇编语法都有一个与之对应的汇编器。没有汇编器可以将所有类型的汇编语法都正确翻译成机器指令。因此,随着计算机的发展,不同厂家形成了自成一派的汇编语言,并有自己的汇编器。不同的汇编语言,实现相同的机器指令可能语法不一样。
- 常见的汇编器有
GNU Assembler (GAS),Microsoft Macro Assembler (MASM),Netwide Assembler (NASM),Flat Assembler (FASM)
等,不同汇编器有不同的汇编语言,GAS
汇编器使用AT&T
汇编语法,MASM
使用Intel
汇编语法,NASM
使用的汇编语法和Intel
汇编语法类似,但比Intel
的简单,每种汇编语法都有自己的手册可以查阅。GAS
的AT&T
语法可以查询GNU Tools Manuals;NASM
的语法可以查询NASM官网。 - 本文对比的是AT&T汇编语法和NASM汇编语法,除此之外,还介绍了内联汇编的基本语法
汇编(Assembly)
1.表达式
注释
ATT:# 开头
NASM:; 开头
立即数
ATT: 十六进制 $0xff
NASM:十六进制 0ffh
寄存器引用
ATT:引用寄存器需要加%
NASM:引用寄存器什么都不加
段寄存器引用
ATT:引用es:di中的值
%es:(%di)
NASM:引用es:di中的值
[es:di]
指定操作数长度
ATT:在指令后面加X字母
X可选值:
l - 32bit, w - 16bit, b - 8bit
NASM:在立即数或者其它不确定长度的内存地址前加单词X
X可选值:
dword - 32bit, word - 16bit, byte - 8bit
标号定义
定义一个16bit数据
ATT:
wSectorNo:
.word 0
NASM:
wSectorNo dw 0
取标号处的值
将标号wSectorNo处的值读取到ax中
ATT:
movw wSectorNo, %ax // 直接引用
NASM:
mov ax, [wSectorNo]
取标号处的地址
将标号2处的地址取出,保存到eax中
ATT:加'$'就是取地址
movl $2f, %eax //需要加$
2:
xxxxxxx
NASM:标号本身就是地址
mov dword eax, .2
.2:
xxxxxxx
2.数学指令
比较指令
比较标号bOdd处的1个字节是否为1
ATT:
movb bOdd, %al
cmpb $1, %al
NASM:
cmp byte [bOdd], 1
逻辑右移
ax右移4bit
ATT:
shrw $4, %ax
NASM:
shr ax, 4
3.数据传输
mov指令格式
ATT:
mov src, dst
NASM:
mov dst, src
4.执行流控制
相对跳转
语法相同
jmp label
绝对跳转
跳转到9000:100地址处,BaseOfLoader=0x9000,OffsetOfLoader=0x100
ATT:
ljmp $BaseOfLoader, $OffsetOfLoader
NASM:
jmp BaseOfLoader:OffsetOfLoader
跳转到标号处
ATT:
向前跳转到标号1处,f表示forward
jmp 1f
1:
xxxxxxx
向后跳转到标号2处,b表示back
2:
xxxxxxx
jmp 2b
NASM:
没有前后之分
跳转到标号.0处
.0:
xxxxxxx
jmp .0
长跳转
ATT:
把SEG32_MODE32_CS装入CS,0x12345装入EIP,并跳转到SEG32_MODE32_CS:$0x12345
ljmpl $SEG32_MODE32_CS, $0x12345
NASM:
把SelectorCode32装入CS,把0装入EIP,并跳转到 SelectorCode32:0 处
jmp dword SelectorCode32:0
5.宏定义
多行宏
ATT:
.macro name arg1, arg2, arg3...
宏定义内容
.endm
举例:段描述符
# 代码段/数据段描述符
# usage: Descriptor Base, Limit, Attr
# Base: .long 4字节长度
# Limit: .long 4字节长度
# Attr: .short 2字节长度
.macro Descriptor Base=0x0, Limit=0xffffffff, Attr
.short \Limit & 0xffff
.short \Base & 0xffff
.byte (\Base >> 16) & 0xff
.short ((\Limit >> 8) & 0xf00) | (\Attr & 0x0f0ff)
.byte (\Base >> 24) & 0xff
.endm
宏的参数可以设置默认值
宏的内部引用参数时,使用'\'表示引用的参数
NASM:
%macro name nargs
宏定义内容
%endmacro
举例:段描述符
; 描述符
; usage: Descriptor Base, Limit, Attr
; Base: dd
; Limit: dd (low 20 bits available)
; Attr: dw (lower 4 bits of higher byte are always 0)
%macro Descriptor 3
dw %2 & 0FFFFh ; 段界限1
dw %1 & 0FFFFh ; 段基址1
db (%1 >> 16) & 0FFh ; 段基址2
dw ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh) ; 属性1 + 段界限2 + 属性2
db (%1 >> 24) & 0FFh ; 段基址3
%endmacro ; 共 8 字节
宏的内部引用参数时,使用%n表示引用的参数
内联汇编(Inline Assembly in Linux C)
example
查看cpu信息
/* cpuid.c */
/* gcc -o cpuid cpuid.c */
#include <stdio.h>
#include <stdlib.h>
static inline void cpuid(unsigned int index,
unsigned int *eax,
unsigned int *ebx,
unsigned int *ecx,
unsigned int *edx)
{
asm("cpuid"
: "=a" (*eax), "=b" (*ebx), "=c" (*ecx), "=d" (*edx)
: "0" (index));
}
void main(void)
{
unsigned int eax, ebx, ecx, edx;
cpuid(0, &eax, &ebx, &ecx, &edx);
printf("eax = %x\n", eax);
printf("ebx = %x\n", ebx);
printf("ecx = %x\n", ecx);
printf("edx = %x\n", edx);
}
运行结果
结果分析参考intel手册volume 2,chapter 3,3.2章节中对CPUID指令的输出介绍
基本格式
asm [ volatile ] (
assembler template
[ : output operands ] /* optional */
[ : input operands ] /* optional */
[ : list of clobbered registers ] /* optional */
);
- asm
gcc关键字,表示接下来要嵌入汇编代码。为避免keyword asm与程序中其它部分产生命名冲突,gcc还支持__asm__关键字,与asm的作用等价 - volatile
可选,表示不需要gcc对下面的汇编代码做任何优化。同样出于避免命名冲突的原因,__volatile__也是gcc支持的与volatile等效的关键字 - output operands
可选,指明输出操作数,典型格式:
"=a" (output)
其中,"=a"指定output operand
的应遵守的约束(constraint
),output为存放指令结果的变量,通常是个C语言变量。"="是输出操作特有约束,表示操作数是只写的(write-only),表达式的意思是先将命令执行结果输入到eax寄存器中,然后再由寄存器eax更新位于内存中的output - input operands
可选,指明输出操作数,典型格式:
"constraint" (input)
其中,constraint
指定input operand
的应遵守的约束(constraint
),input为存放输入的变量,通常是个C语言变量。比如:
asm("cpuid" : "=a" (*eax), "=b" (*ebx), "=c" (*ecx), "=d" (*edx) : "0" (index));
表达式的 input operands是"0" (index),"0"是关联约束,在有些情况下,输入和输入需要用到同一个寄存器,就可以指定以matching constraint方式分配寄存器,此时,input 和 output共用一个中转寄存器。代码中指定输入匹配第0个输出,则用eax作为中转寄存器,那么index作为输入,在cpuid指令运行前,需要将其值写入eax中。
常用约束
- TODO