汇编学习记录(一)

本文介绍了汇编语言的基础知识,包括数据在处理器和内存中的小端序存储、汇编程序结构、语句类型(指令、伪操作、宏定义)、以及MOV指令的应用。重点讲解了在nasm环境下,64位程序的数据段、text段划分和如何为初始化/未初始化变量分配空间,以及声明常量的EQU、%assign%和%define方法。
摘要由CSDN通过智能技术生成

汇编学习笔记(一)


注意: 下面的所有代码,都是在nasm环境下完成,遵循nasm的指令集,且汇编程序都为64位程序。

数据在处理器和内存中的存储方式

处理器中:它在获取内存中数据时是从高位到低位获取的,即需要对内存中数据进行一个翻转。如下图,数据是0725h

内存中: 将数据0725H存储在内存中,低位存在低地址,高位存在高地址。

在这里插入图片描述

这种存储方式就是小端序存储!

编写一个简单汇编程序

汇编程序结构

一个简单汇编程序分为三段:

  • data段:用来声明初始化数据或常量,这些数据在运行是不允许被改变。
  • bss段:用来声明变量。
  • text段:真正的代码执行段,用来执行指令。

汇编程序语句

汇编语句有三种类型:

  • 可执行指令或者简单指令:我们也可以它们为opcode(operation code)。当计算机需要运行一个汇编程序的时候,这个汇编文件先会变成二进制文件,随后链接为可执行文件。在变成二进制文件时,每一个指令(opcode)都会被翻译成机器语言,即二进制代码(我不想叫它二进制代码,但是找不到名词形容它了)。
  • 汇编指令或伪操作:翻译是这样翻译的,但是不知道为啥叫汇编指令。据我理解,这部分的代码实际上不会被翻译为机器语言。类似于C语言中的#include<stdio.h>,它告诉编译器需要包含stdio.h这个头文件,让它在真正执行可执行代码前导入这个头文件,而不是作为具体的指令去执行一些操作。
  • 宏定义:和C语言实际上来说时一样的。是一种文本替换机制。

汇编基础语法

[label]   mnemonic   [operands]   [;comment]

这是汇编语法的基本结构,这个看着挺奇怪的,实际上看到真正的汇编代码就能理解语法是怎么样的了。注意,汇编语言里面的注释符号是;这个后面的语句将会被注释(行注释)。

第一个汇编程序:

bits 64
default rel

segment .data
    msg db "Hello world!", 0xd, 0xa, 0 ;0xd和0xa加在一起换一行,0代表字符串以0结尾

segment .text
global main
extern ExitProcess

extern printf

main:
    push    rbp
    mov     rbp, rsp
    sub     rsp, 32  ;提升堆栈空间

    lea     rcx, [msg]
    call    printf   ;printf 这个函数通过rcx传参

    xor     rax, rax  ;rax传参,以0退出
    call    ExitProcess

这是一个用来打印hello world的汇编程序,这个其实非常的清楚。第一段为.data段,用来存一些变量和常量。.text段中是需要执行的代码。

MOV指令

接下来我将用一个程序演示mov指令和调用printf需要注意的事项

bits 64
default rel

segment .data
    msg db "Hello world!", 0xd, 0xa, 0
    b db 1,2,3,4
    c dw 100,200,300,400
    format1 db "%d", 0    
    format2 db "%f", 0
segment .text
global main
extern ExitProcess
extern scanf
extern printf

main:
    push    rbp
    mov     rbp, rsp
    sub     rsp, 32

    lea     rcx,[format1]
    movzx   rdx,byte b[0]
    add     rdx,10h
    call    printf

    xor     rax, rax
    call    ExitProcess

分析:

  • bits 64default rel 是汇编指令,它们设置了编译器编译代码时需要使用的一些属性。这里是它们的详细说明:
  1. bits 64:

这是一个用于设置目标操作系统架构(指令集)的编译指令。bits 64 表示我们正在为一个 64 位操作系统(例如 x86-64 体系结构)编写汇编代码。同样,如果我们需要为一个 32 位操作系统编写代码,应使用 bits 32。这个指令确定了指令寄存器和地址的宽度。

  1. default rel:

这个编译指令用于设置 x86-64 下默认的代码和数据标签寻址方式。rel 是 “relative”(相对)的缩写。在64位汇编中,推荐使用相对寻址方式,不仅因为这样更简洁,还因为它与被称为在 Position Independent Code (PIC,位置无关代码) 的代码模型兼容。

default rel 指定了在64位模式下,没有特定寻址前缀的 jxx, call, jmp 等跳转指令默认使用相对寻址。同样地,一些数据访问指令(mov, lea 等)也会默认使用相对寻址。

简而言之,bits 64 指定我们在编写在 64 位环境下运行的代码,而 default rel 指导编译器在64位模式下,默认使用相对寻址方法。这两个指令常用于 64 位汇编代码中来配置编译器的行为。

  • printf需要格式化输出,所以需要传递它的第一个参数format。然后再传递第二个参数,即需要打印的值。
  • movzx表示高位用0补齐,rax实际大小为64位,而这个数组为byte类型所以需要高位补零。

以下是mov的指令形式:

MOV  register, register
MOV  register, immediate
MOV  memory, immediate
MOV  register, memory
MOV  memory, register ;register是寄存器,immediate是立即数,也可以看作为常量,memory是内存
变量
为初始化的变量分配空间

定义变量的格式

[variable-name]    define-directive    initial-value   [,initial-value]...

这是由NASM提供的用于定义变量的指令格式,这段指令写在data段中,实际上,上面的两个代码已经写过类似的格式,比如:

b db 1,2,3,4

这就是一个定义数组的指令,定义了一个byte类型的b[4],b[4] = {1,2,3,4}。在定义这个数组后,这个数组名就是该数组首地址的offset,在这里,我们都采用相对地址寻址。

变量的可定义大小(五种):

在这里插入图片描述

为未初始化的变量分配空间

预留空间指令:

segment .data
    ; 预留变量空间
    var1      RESB    1        ; 预留 1 个字节 (8位) 的空间
    var2      RESW    1        ; 预留 1 个字 (16位,即2字节) 的空间
    var3      RESD    1        ; 预留 1 个双字 (32位,即4字节) 的空间
    var4      RESQ    1        ; 预留 1 个四字 (64位,即8字节) 的空间
    var5      REST    1        ; 预留 1 个十字 (80位,即10字节) 的空间

多次定义与多次初始化:

choice	  DB 	'Y' 		 ;ASCII of y = 79H
number1	  DW 	12345 	 ;12345D = 3039H
number2    DD  12345679  ;123456789D = 75BCD15H

这是多次定义,这些数据在data段中是连续的。

marks  TIMES  9  DW  0

times代表重复初始化,这里的意思是marks这一个dword类型的数组重复9次初始化0,即marks[0]到marks[8]都为0。

常量

下面,我将介绍一些NASM里面提供的用来声明常量的语法。

  • EQU
  • %assign
  • %define
EQU
CONSTANT_NAME EQU expression

还是很容易看懂这玩意的,常量名 + EQU + 表达式,这种定义方法类似于C语言里面的const int a = xxx。当然,严谨一点来说数据类型不一定是int。

下面是一个代码示例,需要注意的是,这个常量不是定义在数据段中的。

bits 64
default rel

TESTT equ 5000

segment .data
       format db "%d"
segment .text
global main
extern ExitProcess
extern scanf
extern printf

main:
    push    rbp
    mov     rbp, rsp
    sub     rsp, 32

    lea     rcx,[format]
    mov   rdx,TESTT
    call    printf

    xor     rax, rax
    call    ExitProcess

提一个坑点,这个常量的名字不能是TEST因为和某个指令名称冲突了。

%assign
%assign CONSTANT_NAME EXPRESSION

注意,和EQU不同的是,%assign可以运行重复定义,而且这个指令是大小写敏感的。如下:

%assign CONSTANT_NAME 10
%assign CONSTANT_NAME 20

可以在一个写一个汇编程序时同时存在这行代码,如下程序:

bits 64
default rel

%assign CONSTANT_NAME 10
%assign CONSTANT_NAME 20
segment .data
       format db "%d"
segment .text
global main
extern ExitProcess
extern scanf
extern printf

main:
    push    rbp
    mov     rbp, rsp
    sub     rsp, 32

    lea     rcx,[format]
    mov   rdx,CONSTANT_NAME
    call    printf

    xor     rax, rax
    call    ExitProcess

最后打印出来的值是 20。

%define

和C语言中的#define是类似的,可以定义常量也可以定义字符串。注意它可以重定义也是对大小写完全敏感的

bits 64
default rel

%define CONSTANT_NAME 10
%define CONSTANT_NAME 20
%define dati mov
segment .data
       format db "%d"
segment .text
global main
extern ExitProcess
extern scanf
extern printf

main:
    push    rbp
    mov     rbp, rsp
    sub     rsp, 32

    lea     rcx,[format]
    dati   rdx,CONSTANT_NAME ;类似于文本替代,学过C语言的实际上能够很容易看懂
    call    printf

    xor     rax, rax
    call    ExitProcess

总结

  • 目前使用的编译器是NASM,系统是Windows,所有汇编程序都是64位程序。
  • 从现在开始,终于开始正式学习汇编了,进度比想象的慢,希望之后能加快进度。
  • 如果有时间会不定期得更新汇编学习的博客,如有问题欢迎提出。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值