NASM学习(二)——从命令行获取参数
学习汇编的目的是增强自己对硬件体系的熟悉与编译、链接等等阶段的熟悉
上文我们已经学会了怎么使用NASM来打印HELLO,WORLD了,总结一下,主要是使用系统调用来进行输出
甚至,程序的退出也是使用系统调用exit()
——因为我们的程序是一个单独的进程,当进程要结束的时候应当主动调用进程销毁调用,否则就会引发错误
但是,还有一些不好的地方:
- 注意到,每次使用系统调用的时候都需要手写一大段调用准备,不利于程序的调试和维护,如果可以把它们封装起来,效果更好
- 注意到,
msg db 'hello, world', 0x0a
中,手动输入了换行符。但是在具体的场景中,我们可能希望根据具体条件进行换行与否——总之,写死就是不对的
在上文的基础上,我们分别对这两个问题进行解决:
include引用
使用%include
进行文件的引用
首先,对一些函数进行封装
; int slen(string msg)
slen:
push ebx ; 保存接下来将使用的寄存器
mov ebx, eax; 将msg的地址复制到ebx中
nextchar:
cmp byte [eax], 0 ; 将*eax和'\0'进行比较
jz finished ; 若等于'\0',则跳转到结束
inc eax ; eax ++
jmp nextchar
finished:
sub eax, ebx ; 将结果保存在eax寄存器中
pop ebx ; 恢复ebx寄存器
ret ; 返回
quit:
mov ebx, 0 ; 出错数:0
mov eax, 1 ; 使用1号调用
int 0x80
ret
接下来,封装打印函数:
; 打印函数
; void sprint(string msg)
%include 'slen.s'
sprint:
push edx
push ecx
push ebx
push eax ; 保存寄存器现场
call slen ; 调用slen函数,结果保存在eax中
mov edx, eax; 将字符长度复制到edx寄存器中,作为参数
pop eax ; 恢复eax寄存器
mov ecx, eax; 将字符串地址当作参数输入
mov ebx, 1 ; 输出到stdout
mov eax, 4 ; 使用4号调用
int 0x80 ; 调用系统调用
pop ebx
pop ecx
pop edx
ret ; 恢复寄存器现场
使用%include
就可以引用其他文件的内容
换行问题
为了程序的可维护性,我们可以写个函数,在字符串后输出'\n'
主要思路是,输出字符串后,再输出一个回车
主要问题是sys_write
系统调用需要获取字符的地址和长度
我们可以使用栈来完成
ESP
指针的特点是,每当使用PUSH
操作后,它都会指向栈中最新的数据的地址
因此,我们可以把'\n'
压入栈,然后通过ESP
获取其地址
; void sprintLF(string msg)
%include 'sprint.s'
sprintLF:
call sprint ; 将数据保存在缓存中,因为没有碰到回车,所以不会立即输出
push eax ; 保存现场
mov eax, 0x0a ; 将'\n'压入栈
push eax
mov eax, esp ; 获取'\n'地址
call sprint ; 输出
pop eax ; '\n'
pop eax ; 恢复现场
ret
注意,PUSH操作压入4个字节,而'\n'
仅仅只有一个字节,因此被压入的内容是0x0a 0x00 0x00 0x00
(地址从低到高),因此slen
返回1
为什么在sprintLF
中调用了两次sprint
,但是只输出了一次?
理由:Linux中,输出函数使用行缓存,未碰到回车的时候并不会输出
详见这篇文章
参数
如何使用汇编程序读取参数呢?
在C语言中,main
函数的参数可以表示为int argc, char *argv[]
实际上,在程序开始运行的时候,会将参数逆序压入栈,然后再压入程序的名字(或者说运行程序的方式),最后压入所有参数的个数(程序的名字也算参数)
例如:
参数N
参数N-1
……
参数1
程序名
参数个数:N+1
因此,使用POP
方式就可以轻易获取参数
以下是一个打印所有参数的例程
%include 'sprintLF.s'
%include 'quit.s'
SECTION .text ; 代码段开始
global _start ; 程序入口
_start:
pop ecx ; 栈中的第一个值是参数的数量
nextArg:
cmp ecx, 0x00 ; 检查是否还有参数
jz noMoreArgs ; 没有参数了,退出
pop eax ; 将参数加载到eax中
call sprintLF ; 进行打印
dec ecx ; ecx --
jmp nextArg
noMoreArgs:
call quit