NASM汇编随笔

编译链接
nasm -f elf helloworld.asm
ld -m elf_i386 helloworld.o -o helloworld
./helloworld
符号约定
入口

类似于其他语言的main函数,gloabl _start是约定的NASM汇编代码入口:

SECTION .text
global _start

_start:
	; other codes
常量
org 0x7C00 ; 约定的引导扇区首地址
dw 0xAA55  ; 约定的引导扇区标志
0x0A	   ; 换行\n,即LF
0x0D	   ; 回车\r,即CR
系统调用
名称eaxebxecxedx
sys_exit调用号1错误码
int err_code
sys_read调用号3文件描述符
unsigned int fd
文件地址
char __user *buf
文件长度
size_t count
sys_write调用号4文件描述符
unsigned int fd
文件地址
const char __user *buf
文件长度
size_t count
sys_execve调用号11待执行文件
char __user *
参数数组
char __user * __user *
环境变量数组
char __user * __user *
sys_fork调用号2[核心栈]
[struct pt_regs*]
sys_time调用号13time_t __user *tloc
sys_create调用号8文件路径
const char __user *path
文件权限
int mode
sys_open调用号5文件
const char __user *fd
读写模式
int flags

int mode
sys_close调用号6文件描述符
unsigned int fd
sys_unlink调用号10文件路径
const char __user *path

__user表明参数是一个用户空间(user-space)的指针,不能在kernel代码中直接访问,需要经过检查;因为用户空间的内存是不可靠的。

char __user * __user *代表的是指向一个用户空间指针的一个用户空间指针。其实简单说来,就是char**,一个二维指针。

sys_write

STDOUT的文件描述符fd是1,算是一个约定吧。所以如果要在控制台输出,相当于是把输出的内容“写”到STDOUT这个“文件”里(大概这就是文件抽象的好处,设备可以看成文件,用统一的方式进行操作):

MOV ebx, 1
sys_read

同样的,STDIN的文件描述符fd是0:

MOV ebx, 0
sys_execve

注意,在传递参数数组的时候,需要将command包括进去:

SECTION .data
command         db      '/bin/echo', 0h
arg1            db      'Hello World!', 0h
arguments       dd      command
                dd      arg1
                dd      0h
environment     dd      0h                  
 
SECTION .text
global  _start

_start:
    mov     edx, environment
    mov     ecx, arguments
    mov     ebx, command
    mov     eax, 11
    int     80h
sys_fork

父进程中eax不为0,子进程中eax为0。

sys_create

返回的文件名放在寄存器eax里。

sys_open
含义描述标志
O_RDONLYopen file in read only mode0
O_WRONLYopen file in write only mode1
O_RDWRopen file in read and write mode2
寄存器
数据寄存器

AX = AH(Higher) + AL(Lower),Accumulator累加器(数值运算的默认寄存器)

BX = BH + BL,Base基址

CX = CH + CL,Counter计数器

DX = DH + DL,Data数据(一般用于存放除法的余数)

栈指针寄存器

SP 栈顶指针

BP 栈底指针

索引寄存器

SI 源寄存器

DI 目标寄存器

段寄存器

CS 代码段

DS 数据段

SS 堆栈段

ES 附加段

状态标志寄存器FLAG

进位标志CF(Carry Flag)

奇偶标志PF(Parity Flag)

奇偶标志PF用于反映运算结果中“1”的个数的奇偶性。如果“1”的个数为偶数,则PF的值为1,否则其值为0。利用PF可进行奇偶校验检查

零标志ZF(Zero Flag)

符号标志SF(Sign Flag)

符号标志SF用来反映运算结果的符号位,它与运算结果的最高位相同。运算结果为正数时,SF的值为0,否则其值为1。

溢出标志OF(Overflow Flag)

几个指令

EQU定义常量,类似于C中的#define

%include引入外部文件实现模块化,类似于#include

%include "another-module.asm"
地址抽象

标签(LABEL)和变量名、函数名都是地址(地址抽象?),直接导致取变量(在SECTION .data中声明,出现在寄存器里的不是变量,是常量)的值需要解引用(通过[]运算符)。可以显式声明其类型。比如:

byte[eax]

是用来获取eax寄存器里的数据的,eax里存的是byte类型的数据。

times

用times指令来进行指令的“循环”的经典代码,用0填充剩下的空间,直到510字节为止(一般用于引导扇区):

times 510-($-$$) db 0

概括下来,times的格式大概是这样:

times [次数] [指令]
条件跳转

类似于if的效果。事实上,这些是跟寄存器息息相关的:

单操作数
指令条件解释
JZ结果为0零标志寄存器ZF置位
JC结果产生进位进位标志寄存器CF置位
JP结果的二进制表示中的“1”的个数为偶数奇偶标志寄存器PF置位
JO结果产生溢出溢出标志寄存器OF置位
双操作数
指令条件解释
JE相等Equals
JA(无符号)大于Above
JB(无符号)小于Below
JG(有符号)大于Greater
JL(有符号)小于Lesser

还有对应的NOT指令,在中间加个N即可(比如JNZ表示结果非0)。

注意事项
程序崩溃

调用sys_exit了吗?

Program received signal SIGSEGV, Segmentation fault

原因是引用了不存在的地址。

  • 调用sys_exit了吗?

  • 是不是把常量当成了指针?比如:

    mov eax, 0x30	; eax='0'
    mov edx, 1		
    mov ecx, eax	; ERROR
    mov ebx, 1		; STDOUT
    mov eax, 4		; sys_write
    int 0x80
    

    正确的做法应该是将常量压入栈中,引用常量的地址:

    mov eax, 0x30	; eax='0'
    mov edx, 1		
    push eax
    mov ecx, esp	; here esp refers to the address of '0x30'
    mov ebx, 1		; STDOUT
    mov eax, 4		; sys_write
    int 0x80
    
push不改变源寄存器的值

PUSH指令只会往栈中压入一个值,并不会影响原寄存器的值。比如这条指令:

PUSH eax

eax并不会因为被push了就被清空,或者变成别的什么值。这一步唯一的影响,就是栈里多了一个等同于eax的值。

参数传递

可以通过数据寄存器(比如eax)来进行参数传递;如果参数多了,超过了数据寄存器的数量,可以考虑用栈进行参数传递,这个时候就需要用到栈顶指针esp了。

字符串结尾的空字符

因为在内存中数据是连续排列的,没有空字符无法区分两个字符串,所以C风格字符串需要'\0'作为字符串结尾,这是来自汇编的legacy。

数值运算

加减法都是两个操作数,但乘除法是一个操作数。

什么意思呢?看一下例子就知道了:

add	eax, ebx	; eax + ebx
sub eax, ebx	; eax - ebx
mul ebx			; eax * ebx
div ebx			; eax / ebx

这些运算有有符号和无符号之分。以除法为例,div是无符号除法,idiv是有符号除法。

除法还有一个特殊之处,就是默认将eax里的值当成被除数,并且将商放在eax里,余数放在edx里。这算是一个约定,就像乘法默认把结果放到eax里一样。

除零异常

有时候编译时会提示除零异常,但代码逻辑上是完全没有错误的。这个时候可能需要检查一下在除法运算前是否清空了edx寄存器。如果没有清空edx寄存器,可能就会导致除零异常。

至于原因,就得涉及到计算机组成原理了。

被除数在eax里,余数在edx里,计算的时候会进行拼接,变成edx:eax进行除法运算。所以,如果edx没有清空,会造成不可预料的计算结果。

A、M、Q寄存器,被除数

清空寄存器

下面两行代码都能清空ebx寄存器。它们有区别吗?

xor	ebx, ebx
mov	ebx, 0

显然是有区别的。xor只需要2字节(操作码1字节,寄存器地址1字节),但mov需要5字节(操作码1字节,2个立即数4字节)。

既然都用汇编了,为什么不用内存占用更小的指令呢?

字符串也是立即数

我觉得是因为字符也是数字的一种。

数组名是地址

因为数组事实上是连续排列的一块内存,数组名只是指向首地址的指针罢了。

同样的,二维数组的名称就是指向指针的指针。这样做只是为了从逻辑上分开,但在内存里没啥区别,还是连续的一大块地址。

需要注意的是,数组的结尾也需要是一个空字符(0H)。

代码风格
  • 所有指令和寄存器名称都小写
  • 立即数的后缀大写
  • 0x中的x小写
  • 全局label驼峰命名
  • 局部label全小写
汇编IDE——SASM

高配版notepad++。

编译和链接配置

In Linux, just replace “gcc” to “ld”.

This linker options should be replaced by “$PROGRAM.OBJ$ -g -o $PROGRAM$”.

这是官方文档的说法,可能稍微有点抽象。具体的使用方法,进入设置->构建,然后按照上面的要求改一下就行了。参考配置如下图:
在这里插入图片描述
至于设置在哪里?可以熟悉一下Ubuntu的使用方法(

在这里使用到的变量的含义如下:

$SOURCE$ - 源代码文件

$LSTOUTPUT$ - 用于调试的文件

$PROGRAM.OBJ$ - 生成的.o(或者.obj)文件

$PROGRAM$ - 生成的可执行文件

如果要在64位上运行,需要将汇编选项里的elf32改成elf64,保存设置后重启SASM。如果不重启,可能无法应用设置。

2019.10.17 增加了官方文档地址

可以参考官方文档。地址:https://github.com/Dman95/SASM/wiki/Help-(English)#building-system-settings

调试

和别的IDE基本相同,也有断点和单步;除非你像我一样至今不会debug。

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
NASM开发了IDE,听起来很复杂,但并不像看上去那么复杂。虽然汇编编程不是那么容易的微分方程(可能是一个坏例子)。该IDE设计重量轻,但同时使用方便。因为汇编编程对于所有的命令行来说都是一件痛苦的事情。现在有人可能会说记事本++可以完成这项工作,但事实是记事本++只是一个具有可扩展插件的高级源代码编辑器。 特点:创建和打开一个项目(就像在Visual Studio中那样,但要容易得多)。项目格式基于XML,类似于Visual Studio解决方案 ;能够 突出显示语法的源编辑器,具有 调试/运行和构建(编译、链接)、输出窗口(用于错误和警告) 、编译后实时突出显示错误,日志记录系统,其中一个方便的日志文件在应用程序旁边,以防最坏的情况发生——应用程序由于灾难性的未散列错误而崩溃。 自动更新功能,允许您在IDE中更新,无需从源代码重新生成或下载并安装任何东西。能够组装COM文件。需要一台可以运行旧MS-DOS代码的计算机。 添加了对PowerShell脚本和批处理脚本的内部脚本支持。对于高级用户。单击文档链接, 了解如何工作:NASM开发IDE高级功能指南:使用自定义脚本功能构建所有功能都可以在程序中完成。记住,只是一个简单易用的图形界面。最好的是不需要NASM命令行经验。不支持C,请记住,我设想这个项目是一个简单易用的NASM IDE,如果您想开发C应用程序,它们是许多专有的IDE,如Visual Studio,甚至是开源的。这意味着没有C源文件。 但是,如果你真的非常需要它,它不使学习NASM或其他低级汇编语言变得容易。它只是让构建NASM应用程序变得更容易,而不必反复输入无聊的命令;或者反复运行相同的脚本。尽管我可能会添加几个从小型到中型的NASM示例项目,但只要第一个预览版本发布了。如果社区想要捐赠优秀的示范项目,那就加入这个项目吧。 截至2012年11月17日,第一个测试版已经发布了许多错误修复、功能和改进。官方支持Windows 8!
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值