寄存器(内存访问)---汇编学习笔记

寄存器(内存访问)


序言

第二章,我们主要从CPU如何执行指令的角度讲解了8086CPU的逻辑结构、形成物理地址的方法、相关的寄存器以及一些指令

这一章,我们从访问内存的角度继续学习几个寄存器。


3.1 内存中字的存储

CPU中,用16位寄存器来存储一个字。字是有2个内存单元组成。高8位存放高位字节,低8位存放低位字节

例如:问题 3.1 所描述的。
这里写图片描述
(1) 0 地址单元中存放的字节型数据是多少?
(2) 0 地址字单元中存放的字型数据是多少?
(3) 2 地址单元中存放的字节型数据是多少?
(4) 2 地址字单元中存放的字型数据是多少?
(5) 1 地址字单元中存放的字型数据是多少?

答案一目了然,分别是20H、4E20H、12H、0012H、124E。


3.2 DS和[address]

CPU要读写一个内存单元时,必须给出这个内存单元的地址,内存地址由段地址和偏移地址组成DS 存放要访问数据的段地址[address] 中address是偏移地址并且是一个具体的数

这里注意,DS并不能直接给定一个数值。比如 mov ds,1000H 语句在8086CPU中是错的。我们只能通过寄存器去改变ds的值,比如 mov ds,ax 语句。

问题 3.2

写几条指令,将 al 中的数据送入内存单元 10000H 中。

mov ax,1000H    ;为了让ds为1000H,先让ax为1000H
mov ds,ax       ;ds只接受寄存器向它传送的数据
mov al,[0]      ;[0]是偏移地址,1000:0的内存单元是10000H

3.3 字的传送

8086CPU是 16 位结构,有16根数据线,所以,可以一次性传送16位数据,也就是一个字。

问题 3.3

内存中的情况如下图所示,写出下面指令执行后寄存器ax,bx,cx中的值。
这里写图片描述

mov ax,1000H    ;AX = 1000H
mov ds,ax       ;DS = 1000H
mov ax,[0]      ;由于传送的是一个字数据,所以AX = 1123H
mov bx,[2]      ;同上,BX = 6622H
mov cx,[1]      ;CX = 2211H
add bx,[1]      ;BX = 8833H
add cx,[2]      ;CX = 8833H

问题 3.4

内存中的情况如图所示,写出下面指令执行后内存中的值。
这里写图片描述

mov ax,1000H    ;AX = 1000H
mov ds,ax       ;DS = 1000H
mov ax,11316    ;AX = 11316 = 0x2C34H
mov [0],ax      ;1000:0 = 34 , 1000:1 = 2C
mov bx,[0]      ;BX = 2C34
sub bx,[2]      ;BX = 1B12
mov [2],bx      ;1000:2 = 12 , 1000:3 = 1B

我们知道高字节放入高地址,低字节放入低地址。也就是[0]的高地址为1000:1,低地址为1000:0。


3.4 mov、add、sub指令

我们在没什么了解的情况之前就是是要几个了mov、add、sub等指令。

先了解mov指令的几种形式:

mov 寄存器,数据
mov 寄存器,寄存器
mov 寄存器,内存单元
mov 内存单元,寄存器
mov 段寄存器,寄存器

(1)我们猜想,既然有 mov 段寄存器,寄存器 指令,那么会有mov 寄存器,段寄存器 指令这样的相反通路吗?

实验如下(再此声明一次:借用了实验楼的环境):
这里写图片描述
如图所示:

  • 第一个红框的 AX 为 0000H;
  • 第二个红框是 DS 为 1000H;
  • 第三个红框发生AX的值改变。

由此可知,mov 寄存器,段寄存器指令是可用的

(2)同样,既然有 mov 寄存器,内存单元 ,会拥有 mov 内存单元,寄存器 指令吗?

实验如下:
这里写图片描述
如图所示,内存单元 1000:0 和 1000:1 发生变化,也就是1000:[0]发生变化。

(3)那么 mov 段寄存器,内存单元 也应该可以。

实验如下:
这里写图片描述
发现指令出错

同mov一样, add 和 sub 也有以下几种形式:

add 寄存器,数据
add 寄存器,寄存器
add 寄存器,内存单元
add 内存单元,寄存器

sub 寄存器,数据
sub 寄存器,寄存器
sub 寄存器,内存单元
sub 内存单元,寄存器

我们尝试一下 add 段寄存器,ax 指令,看看可行吗?

实验如下:
这里写图片描述
指令出错


3.5 数据段

前面提到,可以根据需要,将一组内存单元定义为一个我们可以将一组的长度为N(N<=64KB)、地址连续、起始地址为16的倍数的内存单元当作专门存储数据的内存空间

例如将 123B0H~123B9H 的内存单元定义为数据段。现在要累加这个数据段中的前3个单元中的数据,如下:

mov ax,123BH
mov ds,ax
mov al,0        ;注意,由于题目要求是"单元",而不是"字",所以采用al
add al,[0]
add al,[1]
add al,[2]

问题 3.5

写几条指令,累加数据段中的前3个字型数据。代码如下:

mov ax,123BH
mov ds,ax
mov ax,[0]      ;因为这里采用的是"字"
add ax,[2]
add ax,[4]

3.1~3.5 小结

(1)字在内存中存储时,采用两个地址连续的内存单元来存放,字的低位字节存放在低地址单元中,高位字节存放在高地址单元中。

(2)用 mov 指令访问内存单元,可以在 mov 指令中只给出单元的偏移地址,此时,段地址默认在 DS 寄存器中。

(3)[address] 表示一个偏移地址为 address 的内存单元。

(4)在内存和寄存器之间传送字型数据时,高地址单元和高8位寄存器、低地址单元和低8位寄存器相对应。

(5)mov、add、sub 是具有两个操作对象的指令。jmp 是具有一个操作对象的指令。

(6)可以根据自己的推测,在 Debug 中实验指令的新格式。

其实,最麻烦的就是第6点,每种CPU的汇编指令都会有一些不同,所以在针对不同的CPU时需要去猜测所谓的“新格式”


检测点 3.1

(1)在 Debug 中,用“d 0:0 1f”查看内存,结果如下。
这里写图片描述
写出下面每条汇编指令执行后寄存器的值。

mov ax,1        ;AX = 0001H
mov ds,ax       ;DS = 0001H
mov ax,[0000]   ;AX = 2662H
mov bx,[0001]   ;BX = E626H
mov ax,bx       ;AX = E626H
mov ax,[0000]   ;AX = 2662H
mov bx,[0002]   ;BX = D6E6H
add ax,bx       ;AX = FD48H
add ax,[0004]   ;AX = 2C14H
mov ax,0        ;AX = 0000H
mov al,[0002]   ;AL = E6H
mov bx,0        ;BX = 0000H
mov bl,[000c]   ;BL = 26H
add al,bl       ;AL = 0CH

(2)内存中的情况如图 3.6 所示。
这里写图片描述
各寄存器的初始值:CS=2000H , IP=0 , DS=1000H , AX=0 , BX=0;

  1. 写出CPU执行的指令序列(用汇编指令写出)。
  2. 写出CPU执行每条指令后,CS、IP和相关寄存器中的数值。
  3. 再次体会:数据和程序有区别吗?如何确定内存中的信息哪些是数据,哪些是程序?

①:我们知道CS=2000H,IP=0,所以从2000:0000处开始执行。因此,代码如下。

mov ax,6622H
jmp 0FF0:0100   ;0FF0:0100 == 1000:0
mov ax,2000H
mov ds,ax
mov ax,[0008]
mov ax,[0002]

②:CS:IP的值如下。

指令CSIPaxds
mov ax,66222000H0003H6622H1000H
jmp 0FF0:0100OFFOH0100H6622H1000H
mov ax,2000H0FF0H0103H2000H1000H
mov ds,ax0FF0H0105H2000H2000H
mov ax,[0008]0FF0H0108HC389H2000H
mov ax,[0002]OFFOH010BHEA66H2000H

③:初步猜测,我认为数据和指令无区别。原因,是因为可以将mov bx,ax当做数据传送到[0008]内存中。那我们如何确定是数据还是指令呢?当然是通过我们的CS:IP来确认咯,IP指向的第一个值是我们的指令,例如A1是为 mov ax,内存地址 的指令。执行指令后,IP根据指令来确定向后移动几个位置。(以上是本人的猜想,不知道是否成立)

猜想:CP是时钟脉冲,以A1为例子,我们知道读取一个数据需要一个脉冲,A1是需要3个脉冲时间才能完成的操作?

以上是本人猜想,未经过验证。


3.6 栈

栈是一种具有特殊访问方式的存储空间。特殊点在于,数据入栈出栈的次序是“先进后出”或者说“后进先出”(LIFO,Last In First Out)。拥有两种操作:入栈(PUSH)出栈(POP)


3.7 CPU 提供的栈机制

CPU 提供相关的指令来以栈的方式访问内存空间。说明了,我们可以将一段内存当做栈来使用。(其实我们编程在写递归的时候,总是很经常没有考虑递归基的情况就运行程序,导致内存溢出。我们通过前面所说内存被当做栈来使用,推导出:其实就是栈内存被使用了无限次,导致的内存溢出结果。)

如下所示是栈的操作:
这里写图片描述
可以看出高地址单元存放高8位,低地址单元存放低8位。

在这里,我们会疑惑!总结一下,大概是两个问题。

  1. CPU如何知道10000H~1000FH这段空间被当做栈来使用?
  2. push和pop在执行的时候,如何知道哪个单元是栈顶单元?

这里给出答案,8086CPU 中,有两个寄存器SS、IP分别是栈的段地址和偏移地址。在任意时刻,SS:IP 指向栈顶元素

我们来看看PUSH操作的过程,如图:
这里写图片描述
反之,则是POP操作过程,如图:
这里写图片描述
我们看到,POP操作并没有将栈顶之前的值 清 0

问题 3.6

如果将 10000H~1000FH 这段空间当做栈,初始状态栈是空的,此时,SS=1000H,SP=?

答案显然是最高地址的下一个单元,即10010H。


3.8 栈顶超界的问题

我们知道栈顶的指向有 SS:IP 控制。但并没有控制这栈空间pop次数过多导致下溢以及push多次导致上溢检测。这样会出现一个问题,我们脑海中的栈空间之外的数据会被栈的pop或者push操作给覆盖,从而导致一系列的错误发生。这非常严重,试想一下,如果C语言编写的一段递归程序没有递归基就运行之后,秒级的时候就会讲内存占满,从而导致RAM死机,计算机只能重启。这也是为什么VC++给程序只分配了4K空间的原因(我记得是4K,若有错误请指正)。

因此,我们在编写汇编程序的时候,需要注意我们的PUSH和POP操作不会导致上溢以及下溢。


3.9 push、pop 指令

push和pop 指令的格式可以是如下形式:

push 寄存器    ;将一个寄存器中的数据入栈
pop  寄存器    ;出栈,用一个寄存器接受出栈的数据
push 段寄存器
pop  段寄存器
push 内存单元
pop  内存单元

问题 3.7
编程,将 10000H~1000FH 这段空间当做栈,初始状态栈是空的,将 AX、BX、DS 中的数据入栈。

mov ax,1000H
mov ss,ax       ;由于是段寄存器,所以需要ax来中转
mov sp,0010H    ;由于栈是由高地址开始
push ax
push bx
push ds

问题 3.8

编程:
(1)将 10000H~1000FH 这段空间当作栈,初始状态栈是空的;
(2)设置 AX=001AH,BX=001BH;
(3)将 AX、BX 中的数据入栈;
(4)然后将AX、BX清零;
(5)从栈中恢复AX、BX原来的内容。

;第一题答案
mov ax,1000H
mov ss,ax
mov sp,0010H

;第二题答案
mov ax,001AH
mov bx,001BH

;第三题答案
push ax
push bx

;第四题答案
mov ax,0000H
mov bx,0000H

;第五题答案
pop bx  ;出栈顺序是LIFO
pop ax  ;同上

问题 3.9
编程:
(1)将 10000H~1000FH 这段空间当做栈,初始状态为空;
(2)设置 AX=001AH,BX=001BH;
(3)利用栈,交换 AX 和 BX 中的数据。

;第一题答案
mov ax,1000H
mov ss,ax
mov sp,0010H

;第二题答案
mov ax,001AH
mov bx,001BH

;第三题答案
push ax
push bx
pop ax
pop bx

问题 3.10

如果要在 10000H 处写入字型数据 2266H,可以用以下代码完成:

mov ax,1000H
mov ds,ax
mov ax,2266H
mov [0],ax

补全下面的代码,实现功能:在 10000H 处写入字型数据 2266H 。
要求:不能使用“mov 内存单元,寄存器”这类指令

;要求三条指令搞定
mov ax,1000H
mov ss,ax
mov sp,2H       ;push是先将sp-=2后再传送数据给SS:SP内存单元中

;题目的代码
mov ax,2266H    
push ax         

栈的综述

(1)8086CPU 提供了栈操作机制,方案如下。

  • 在 SS、IP 中存放栈顶的段地址和偏移地址;
  • 提供入栈和出栈指令,它们根据 SS:SP 指示的地址,按照栈的方式访问内存单元。

(2)push 指令的执行步骤:

  • SP=SP-2;
  • 向SS:SP指向的字单元中送入数据。

(3)pop 指令的执行步骤:

  • 从 SS:SP 指向的子单元中读取数据;
  • SP=SP+2。

(4)任意时刻,SS:SP 指向栈顶元素。
(5)8086CPU 只记录栈顶,栈空间的大小我们要自己管理。
(6)用栈来暂存以后需要回复的寄存器的内容时,寄存器出栈的顺序要和入栈的顺序相反。
(7)push、pop 实质上是一种内存传送指令,注意它们的灵活应用。

栈是一种非常重要的机制,一定要深入理解,灵活掌握。


3.10 栈段

在编程时,我们可以根据需要,将一组内存单元定义为一个段。我们要注意控制栈不会超界。

问题 3.11

如果将 10000H~1FFFFH 这段空间当作栈段,初始状态栈是空的,此时,SS=1000H,SP=?

答案很显然,SP= FFFF+1 = 10000H = 0000H。这是一个64K大小的栈段。

问题 3.12
一个栈最大可以设为多少?为什么?

最大可以设为64K,因为SP的范围只能是 0000H~FFFFH 之间。


段的综述

  • 数据段,段地址放在DS中,偏移地址[address]。
  • 代码段,段地址放在CS中,偏移地址IP。
  • 栈段,段地址放在SS中,偏移地址SP。

例如一段代码,假设CS=1000H,IP=0000H:

mov ax,1000H
mov ss,ax
mov sp,0020H
mov ax,cs
mov ds,ax
mov ax,[0]
add ax,[2]
mov bx,[4]
add bx,[6]
push ax
push bx
pop ax
pop bx

这段代码给我们的信息是:在 10000H~1001FH这段内存中,既是代码段,又是栈段和数据段。这样的代码可能会出现数据发生错误,尽量让一段内存当做三者中的一种段。


检测点 3.2

(1)补全下面的程序,使其可以将 10000H~1000FH 中的 8 个字,逆序复制到 20000H~2000FH 中。逆序复制的含义如下图所示。
这里写图片描述

;题目代码
mov ax,1000H
mov ds,ax

;三行代码
mov ax,2000H
mov ss,ax
mov sp,0010H

;题目代码
push [0]
push [2]
push [4]
push [6]
push [8]
push [A]
push [C]
push [E]

(2)补全下面的代码,使其可以将 10000H~1000FH 中的 8 个字,逆序复制到 20000H~2000FH 中。

;题目代码
mov ax,2000H
mov ds,ax

;三行代码
mov ax,1000H
mov ss,ax
mov sp,0000H

;题目代码
pop [E]
pop [C]
pop [A]
pop [8]
pop [6]
pop [4]
pop [2]
pop [0]

实验 2 用机器指令和汇编指令编程


1. 预备知识:Debug 的使用

  • D命令,查看内存数据。
  • E命令,修改内存数据。
  • A命令,输入汇编指令。
  • U命令,查看汇编程序。

2. 实验任务

(1)使用 Debug,将上面的程序段写入内存,逐条执行,根据指令执行后的实际运行情况填空。

mov ax,FFFF
mov ds,ax
mov ax,2200
mov ss,ax
mov sp,0100

;填空
mov ax,[0]      ;我们不考虑AX与BX的值
add ax,[2]
mov bx,[4]
add bx,[6]

push ax         ;研究SP的变化,SP = 00FEH
push bx         ;SP = 00FCH
pop ax          ;SP = 00FEH
pop bx          ;SP = 0100H

push [4]        ;SP = 00FEH
push [6]        ;SP = 00FCH

(2)仔细观察下图的实验过程,然后分析:为什么 2000:0~2000:f 中的内容会发生改变?
这里写图片描述
我们发现指令 mov ss,ax 被执行后,下一条指令是 mov ax,3123 也就是说 一次T指令对ss操作连同sp也执行了。这变相的说明ss和sp需要连续的改变,也就是mov ss,ax之后必须是mov sp,10。

那我们从中寻找原因:如果ss执行后,不立马执行sp会发生什么情况?

百度了一下,回答是这样的,我们的 T指令 是中断指令,也就是 T指令 执行后需要把相关寄存器信息压入栈中。那么,我们就知道了,SS之后必须跟SP,否则会出现错误。执行后还需要把 T指令 相关的东西压入栈中,所以出现了一堆数据。比如:0B39 是 DS 的值等。

  • 10
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
[TakeLionOS汇编与C语言自学笔记-第五课] 版权所有 2021年 江志剑 按照《21天学通C语言》(第七版),有如下二个例子。 一个例子输出一段话,另一个例子计算年份: //1、files:hello1.c hello1no.s hello1.exe #include <stdio.h> int main(void) { printf("This is an example of sometjing printed!"); return 0; } //2、files:helloyear.c helloyearno.s helloyear.exe #include <stdio.h> #define TARGET_AGE 88 int year1, year2; int calcYear(int year1); int main(void) { printf("What year was the subject born?"); printf("Enter as a 4-digit year (YYYY):"); scanf("%d", &year1); year2 = calcYear(year1); printf("Someone born in %d will be %d in %d.", year1, TARGET_AGE, year2); return 0; } int calcYear(int year1) { return (year1 + TARGET_AGE); } 我是用GCC 8.1.0在命令行窗口运行的。 GCC编译时候,其实有四个步骤: 1、预处理:gcc -E hello.c -o hello.i 2、编译生成汇编语言:gcc -S hello.i -o hello.s 3、汇编:gcc -c hello.s -o hello.o 4、链接生成可执行文件:gcc hello.o -o hello 这时候,我想看一下第二步生成的汇编语言代码。 因为本身就是研究操作系统的一部分,所以要研究底层的汇编语言。 结果发现是AT&T格式的汇编语言风格,而且大量使用汇编宏。 所以启用了GCC的编译选项-fno-asynchronous-unwind-tables,直接预处理为cfi宏的汇编指令。 这里就是包含*no.s形式的文件。 比较发现,第一个文件和第二个文件,关键看寄存器的使用以及子函数的调用。 还有就是全局变量的使用。 我们学习汇编语言,因为零基础不知道学习什么?所以先有个概念。 比如这时候,几节课下来,就知道了该认真学习寄存器的用法了。 这是一个基本概念,现在反复用到了。 最后的LEAVE指令,等效于“MOV ESP,EBP”和“POP EBP”两条指令。 这个指令调整了数据栈指针ESP,并将EBP的数恢复到调用这个函数之前的初始状态。 毕竟函数要在退出之前恢复这些寄存器
学习C语言,首先要明确地告诉自己:C语言是世界上最学习的语言。如今决定学习任何一门其他语言之前,都要先考察这个语言有什么成功项目吗?唯独C语言没有必要问,因为世界上所有最最重要的的系统中,都必然有C语言的身影。比如操作系统内核、高级语言底层等等。为什么C最适合担任系统核心?主要原因可能是C语言具有和汇编语言的对应性,一条C语言代码,对有经验的人来说,可以转换为一条或多条汇编代码,也就是说编译结果具有可预测性。C代码不会在被编译时被加上奇奇怪怪的修饰,也不会在二进制兼容性方面带来大问题。这种稳稳当当的语言,一切都在掌握之中,一切荣耀归属于coder,一切错误也归罪于coder。学习C语言的同时,就自然而然的理解了硬件与操作系统的浅表一层。比如int有几个字节的问题,已经是QQ加群验证是否是程序员的一个标准了(笑),这从侧面说明了学习C语言时确实会关心底层软硬件的实现。C语言简单的基于类型的数据类型体系(引用靠指针,指针本身也是类型),保持了和硬件底层的一致性(即内存、缓存、寄存器只能保存),不会出现其他语言比如C#那样既有又有引用反而对理解底层实现制造了障碍。从某些角度上讲,C语言从设计上来说并不是为了让我们更好的写逻辑代码而设计的,它真的是“只能是这样”,没有对错好坏之分。因为计算机体系结构就是这样的、汇编就是这样的,所以C语言大致也只能是这样的,谁也不能随意捏造它。它足够简单,评论它的设计优劣意义不大。废了这么多口舌,是为了坚定思想,端正态度,只有抱着“最好”的态度,才能排除杂念,勇往直前。时代在进步,某些东西易逝,而另一些东西持久。很多编程大师都用亲身经历告诉你,C语言永远得用最纯粹的态度去学习
嗨!对于逆向学习汇编语言的学习笔记,我可以给你一些基本的指导。首先,汇编语言是一种低级语言,它与计算机的底层硬件密切相关。逆向工程则是通过分析和理解已编译的程序来获取程序的内部信息。 以下是一些学习汇编语言和逆向工程的建议: 1. 学习基础知识:了解计算机体系结构、寄存器内存和指令集等基础概念是必要的。可以先阅读相关的书籍或在线教程,掌握这些基本概念。 2. 掌握汇编语言的语法和指令集:每种计算机体系结构都有自己的汇编语言语法和指令集。选择一种你感兴趣的体系结构(如x86、ARM等),并学习它的汇编语言。 3. 练习编写和调试汇编代码:通过编写简单的汇编代码来熟悉语法和指令集。使用调试器来单步执行代码并观察寄存器内存的变化。 4. 分析已编译程序:选择一个目标程序进行逆向分析。使用反汇编器将程序转换为汇编代码,并分析代码的逻辑和功能。这有助于理解程序的结构和运行过程。 5. 使用调试器进行动态分析:通过调试器来动态地执行程序,并观察程序在运行时的行为。使用断点、内存查看器和寄存器查看器等工具来分析程序的状态和数据。 6. 学习逆向工程工具和技术:了解常用的逆向工程工具和技术,如IDA Pro、OllyDbg、Ghidra等。掌握这些工具的使用可以提高你的逆向分析能力。 7. 参考优秀资源:阅读与逆向工程和汇编语言相关的书籍、论文和博客,关注相关的社区和论坛。与其他逆向工程师交流经验也是很有帮助的。 记住,逆向工程是一个需要耐心和实践的过程。持续学习和实践将帮助你提高逆向分析的技能。祝你在学习汇编语言和逆向工程的过程中取得好成果!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值