前言
从本博文开始,我将主要学习NASM的语法格式,辅以MASM语法的相关了解
一个最简单的helloword
; 功能描述 helloword org 07c00h ; 告诉编译器程序加载到7c00处,否则strMess位置的计算会出错的 section .data strMess: db "Hello, OS world!" strlen equ $-strMess ; 计算 strmess的长度
section .text mov ax, strMess mov bp, ax ; ES:BP = 串地址 mov cx, strlen ; CX = 串长度 mov ax, 01301h ; AH = 13, AL = 01h mov bx, 000ch ; 页号为0(BH = 0) 黑底红字(BL = 0Ch,高亮) mov dl, 0 int 10h ; 10h 号中断 ret jmp $ ; 无限循环
流程控制
1. if-else
if-else 语句其实就是 cmp + 条件判断语句(如, je,ja,jb jne...)语句的组合
原理: 1. 使用cmp来调整标记寄存器的的标记位
2. 使用对应的条件判断指令来实现条件转跳,条件转跳指令会根据CMP指令对标志寄存器进行判断从而根据结果进行转跳
例子:
mov ax, 2 cmp ax, 2 je a ; if( eax = 2) jmp exit ;else a: cmp ax, 1 ja b ; if(eax > 1) jmp exit ;else b: cmp ax, 3 jb c ; if(eax < 3) jmp exit ;else c:
2. while 和 for 循环
while是使用了loop指令,loop指令就是判断CX是否为0,如果cx=0,那么啥都不做,如果cx 不等于 0 则 cx-- 然后转跳
例子
; while循环
mov cx, 10 mov bx, 0 add: add bx,1 loop add ; while(ecx != 0)
调用子程序
调用子程序其实不像C/C++那样有明确的界限,你可以使用任意的过程转跳指令去调用子程序,可以用非条件转移指令jmp也可以使用条件转移指令 je ja jb 等等,我现在所说的就是一般意义上用到的子程序调用指令 call和ret、retf。
Call和jmp一样也分为段内和段间调用,对应的返回函数就分别是ret和retf,ret是段内返回,retf就是段间返回。
Call和jmp的区别在于 call指令在jmp之前将当前地址给压栈了。
;段内调用 Call: Push ip Jmp newip Ret pop ip ;段间转移 Call: Push cs Push ip Jmpnewcs:newip Retf: pop ip Pop cs
具体语法MASM和NASM是略有不同的
MASM:
段内: Call sub1 ; sub1是子过程 Call bx Call [bx] Call var ; var是变量 段间: Call sub1 Call far ptr sub1 Call dword ptr [bx] Call var
masm 有proc伪指令,用于声明过程调用语句,具体语法如下
过程调用名 PROC [NEAR|FAR]
....
过程调用名 ENDP
所以MASM可以显式的指明过程调用需要的是段内还是段间调用。
NASM
段内: Call sub1 Call 立即数 Call bx Call var 段间: Call far [bx] Call 立即数:立即数 Call dword var
我不清楚nasm是否能针对标签识别出是段内和段间调用,但是我没发现显式声明使用标签的段间调用指令,但是我看到有博客说,nasm的16位汇编模式是没有段这个概念。我猜意思可能是段这个概念需要编程者自己去管理,nasm认为所有代码都在同一个段内。
参数传递
汇编的参数传递是很灵活,具体只要调用方和被调用方约定好基本是随便传递,基本有三种方式
1. 寄存器传递参数
2.内存传递参数
3.堆栈传递参数
其中有几点需要注意
1. 环境的保护,意思是在子过程中肯定要用到标志寄存器和一些其他寄存器,但是如果这里面有调用者重要的信息的话数据就被破坏了会导致调用者的异常。
2. 数据的清理,比如使用堆栈传递数据,使用完之后由调用者清理还是被调用者清理。
具体可以看一下c/c++的几个调用约定,stdcall fastcall 等等。。
最后引用一段从网上的一个文档中看到的话,不过seg和wrt只有在编译格式是obj的时候才能用。
《nasm中文手册》
3.6 `SEG'和`WRT' 当写很大的16位程序时,必须把它分成很多段,这时,引用段内一个符号的 地址的能力是非常有必要的,NASM提供了'SEG'操作符来实现这个功能。 'SEG'操作符返回符号所在的首选段的段基址,即一个段基址,当符号的偏 移地址以它为参考时,是有效的,所以,代码: mov ax,seg symbol mov es,ax mov bx,symbol 总是在'ES:BX'中载入一个指向符号'symbol'的有效指针。 而事情往往可能比这还要复杂些:因为16位的段与组是可以相互重叠的, 你通常可能需要通过不同的段基址,而不是首选的段基址来引用一个符 号,NASM可以让你这样做,通过使用'WRT'关键字,你可以这样写: mov ax,weird_seg ; weird_seg is a segment base mov es,ax mov bx,symbol wrt weird_seg 会在'ES:BX'中载入一个不同的,但功能上却是相同的指向'symbol'的指 针。