汇编(六)栈段、第一个汇编程序

简介: 3.10 栈段 我们可以根据需要,将一组内存单元定义为一个段 我们可以将长度为n(n

3.10 栈段

  • 我们可以根据需要,将一组内存单元定义为一个段
  • 我们可以将长度为n(n<=64k)的一组地址连续。起始地址为16的倍数的内存单元,当做栈来使用,从而定义了一个栈段
  • 将内存当做栈栈,仅仅是我们再编程时的一种安排。CPU并不会由于这种安排,就在执行push、pop等栈操作指令时就自动的将我们定义的栈段当做栈空间来访问
  • 如果我们将10000~1FFFF这段空间当做栈段。初始状态是空的,此时ss=1000,sp=?
    • 栈最底部的内存单元为1000:FFFE
    • 任意时刻,ss:sp执行栈顶,当栈中只有一个元素的时候,ss=1000, sp=FFFEH
    • 栈为空的,就相当于与栈中唯一的元素出栈, 出栈后,sp=sp+2

    FFFE + 2 = 10000 因为进位存不下的问题,又用到了前面相加的知识,所以当这段栈为空的时候:SS=1000H,SP=0

  • 一个栈段的最大内容为多少?
    • 栈顶的变化范围是0~FFFF, 为64k,从栈空的时候sp=0, 一直压栈,直到栈满时, sp=0, 如果再次压栈, 栈顶将环绕,覆盖原来栈中的内容
  • 栈的存在主要是来临时存放东西,当一个函数被调用时还会返回,保存函数的返回地址,防止数据丢失,不管任何函数调用都是一样, 就递归来说是最典型的例子
  • 我们可以将一段内存定义为一个段,用一个段地址指示daunt, 用偏移地址访问段内单元, 这里完全是我们自己安排的:

    1. 用一个段存放数据,将它定义为”数据段“

      • 对于数据段,将它的段地址存放在ds中, 用mov、add、sub等访问内存但由于的指令时,CPU就将我们定义的数据段中的内容当做数据段来访问
    2. 用一个段存放代码,将它定义为”代码段“

      • 对于代码段,将他的段地址存放在cs中,将段中第一条指令的偏移放在ip中, 这样CPU就将执行我们定义的代码段
    3. 用一个段当做栈, 将它定义为”栈段“

      • 对于栈段, 将它的段地址存放在ss中, 将栈顶单元的偏移地址放在sp中, 这样CPU在需要进行 栈操作的时候, 比如执行push, pop指令等, 就 将我们定义的 栈当做栈空间来使用
  • 一段内存,可以时代码 的存储空间, 又是数据的存储空间,还可以是栈空间, 也可以什么都不是
  • 关键在于CS、IP、SS、SP、DS的指向

_

mov ax, 1000
mov ds, ax
mov bx, 2000
mov ss, bx
mov sp, 10
push [0]
push [2]
push [4]
push [6]
push [8]
push [a]
push [c]
push [e]

_2_

mov ax, 2000
mov ds, ax
mov ax, 1000
mov ss, ax
mov sp, 10
pop [e]
pop [c]
pop [a]
pop [8]
pop [6]
pop [4]
pop [2]
pop [0]

4.1 一个源程序从写入到执行的过程

_

  • 一个汇编语言程序从写入到最终执行简要过程:

    1. 编写

      • sublimtext、nodepad++、ultraEdit,用汇编语言写汇编源程序
    2. 编译和链接

      • 使用汇编语言程序(MASM.EXE)对源程序文件进行编译,产生目标文件(.obj)
      • 再用链接程序(LINK.EXE)对目标文件进行链接, 生成可在操作系统中直接运行的可执行文件
      • 可执行文件

        • 程序(从源程序中编译指令翻译过来的机器码)
        • 数据 (源程序中定义的数据)
        • 相关 描述的信息(比如:程序大小、占用内存空间)
    3. 执行

      • 操作系统依照可执行卫建中的信息, 将执行文件中的机器码和数据加载到内存, 并进行相关的初始化, (比如:设置CS:IP)指向第一条执行的命令, 然后由CPU执行程序

4.2 源程序

  assume cs:codesg # 假设代码段名称为codesg
  codesg segment  # 段的名字为codesg
  start: mov ax, 0123
         mvo bx, 0456
         add ax, bx
         add ax, ax
       
       
         mov ax, 4c00
         init 21h
  codesg ends
  end
  • sement和ends是成对出现的:

    • 功能:定义以一个段, segment说明一个段的开始, ends表示结束
    • 语法:段的名字 segment, 段的名字 ends
    • 一个段必须有一个名称表示
  • 一个汇编程序由多个段组成, 这些段被用来存放代码、数据、或栈空间使用
  • 一个有意义的汇编程序至少要有一个段、用来存放代码
  • end:

    • 是一个汇编程序的结束符, 编译器在编译汇编程序过程中, 如果遇到了end,就表示结束对源程序的编译
    • 如果程序写完了。要在结尾处加上end, 否则编译器无法知道程序在何处结束
    • end是结束, ends是段的结束
  • assume:

    • 含义为“假设” 编译器会将codesg处理为一个地址,默认为cs代码段地址
    • 它假设某一段寄存器和程序中的 某一个segment 。。。ends 定义的段相关联
    • 通过assume说明这种关联,在需要的情况下,编译程序可以将段寄存器和某一个具体的段相联系
  • 汇编源程序:

    • 伪指令(编译器处理)(上面的几个都属于伪指令)
    • 汇编指令(编译为机器码)
    • 源程序最终由计算机执行, 处理的指令和数据
  • 标号:

    • 一个标号指代了一个地址
    • codesg:放在segment的前面,作为一个段的名称,这个名称最终被编译、链接程序 处理为一个段的地址
  • 程序的返回:

    • _

       mov ax, 4c00
      int 21  # int:中断标志 第21号中断
-   一个程序结束后,将 CPU的控制权交还给使他得以运行的程序(执行它的程序,如:dos窗口), 我们成这个过程为:**程序返回**

-   **应该在程序的末尾添加返回的程序段**:

-   

-   这两条指令所实现的功能就是程序返回

4.3 编辑源程序

_

4.4 编译和 链接

Kapture_2019_09_09_at_0_11_24

  • 编译和链接的作用是什么?

    • 链接的作用有以下几个

      1. 当源程序很大时。可以将它分为多个源程序文件编译,每个源程序编译成为目标文件后,在用链接程序将他们链接到一起, 生成一个可执行文件
      2. 程序中调用了某些库文件中的子程序,需要将这个库文件和改程序生成的目标文件链接到一起, 生成一个可执行文件
      3. 一个源程序编译后,得到了有机器码的目标文件, 目标文件中的有些内容还不能直接用来生成可执行文件, 链接程序将此内容出来了为最终的可执行信息, 所以,在只有一个源程序文件, 而又不需要调用 某些库中子程序的情况下, 也必须用链接程序对目标文件处理, 生成可执行文件

4.5 可执行文件中的程序装入内存并运行的原理

_

  • 操作系统的外壳:

    • 操作系统是由于多个功能模块组成的庞大、复杂的软件系统, 任何通用的操作系统,都要提供一个称为shell(外壳)的程序,用户使用这个程序来操作计算机系统工作
  • 为了观察程序的运行过程,我们可以使用dug

    • debug将程序加载到内存, 设置cs:ip的指向, 三debug不会放弃对CPU的控制, 这样就可以使用debug的相关命令来单步执行程序, 查看每条命令执行的结果

4.6 程序执行过程的跟踪

debug_

  • 用debug打开程序跟踪

    • 可以看到debug将程序从可执行卫建加载入内存后, cx中存放的是程序的长度, 2.exe 程序的机器码共有15个字节
  • 现在程序从2.exe中装入内存, 接下来我们查看一样他的内容,查看哪里的内容?
  • 程序被装入内存的什么地方
  • 我们如何得知?
  • 在dos系统中 .exe文件中的程序加载过程如下:

    • _
  • 总结
    1. 程序加载后,ds中存放着程序所在内存区的段地址, 这个内存区的偏移地址为0, 则程序所在的内存区地址为 ds:0
    2. 这个内存区的前256个字节中存放的是psp, dos用来和程序进行通信
    3. 所以256字节处往后的空间存放的是程序
    4. 所以我们从ds中可以得到psp的段地址 sa, psp的偏移地址为0,则物理地址为 sa*16+0
    5. 因为psp占256字节, 所以程序的物理地址是:sa16+0+256=sa16+1616=(sa+16)16+0
    6. 可用段地址和偏移地址表示为:SA+10:0
  • 用T命令单步执行程序中的每一条指令, 并观察每条指令的执行结果
  • 到了int21  我们要用p命令执行
相关文章
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
1078 9
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
314 59
|
6月前
|
编译器 C语言 C++
栈区的非法访问导致的死循环(x64)
这段内容主要分析了一段C语言代码在VS2022中形成死循环的原因,涉及栈区内存布局和数组越界问题。代码中`arr[15]`越界访问,修改了变量`i`的值,导致`for`循环条件始终为真,形成死循环。原因是VS2022栈区从低地址到高地址分配内存,`arr`数组与`i`相邻,`arr[15]`恰好覆盖`i`的地址。而在VS2019中,栈区先分配高地址再分配低地址,因此相同代码表现不同。这说明编译器对栈区内存分配顺序的实现差异会导致程序行为不一致,需避免数组越界以确保代码健壮性。
147 0
栈区的非法访问导致的死循环(x64)
232.用栈实现队列,225. 用队列实现栈
在232题中,通过两个栈(`stIn`和`stOut`)模拟队列的先入先出(FIFO)行为。`push`操作将元素压入`stIn`,`pop`和`peek`操作则通过将`stIn`的元素转移到`stOut`来实现队列的顺序访问。 225题则是利用单个队列(`que`)模拟栈的后入先出(LIFO)特性。通过多次调整队列头部元素的位置,确保弹出顺序符合栈的要求。`top`操作直接返回队列尾部元素,`empty`判断队列是否为空。 两题均仅使用基础数据结构操作,展示了栈与队列之间的转换逻辑。
|
11月前
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
565 77
|
10月前
|
算法 调度 C++
STL——栈和队列和优先队列
通过以上对栈、队列和优先队列的详细解释和示例,希望能帮助读者更好地理解和应用这些重要的数据结构。
267 11
|
11月前
|
存储 C++ 索引
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】初始化队列、销毁队列、判断队列是否为空、进队列、出队列等。本关任务:编写一个程序实现环形队列的基本运算。(6)出队列序列:yzopq2*(5)依次进队列元素:opq2*(6)出队列序列:bcdef。(2)依次进队列元素:abc。(5)依次进队列元素:def。(2)依次进队列元素:xyz。开始你的任务吧,祝你成功!(4)出队一个元素a。(4)出队一个元素x。
465 13
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
|
10月前
|
DataX
☀☀☀☀☀☀☀有关栈和队列应用的oj题讲解☼☼☼☼☼☼☼
### 简介 本文介绍了三种数据结构的实现方法:用两个队列实现栈、用两个栈实现队列以及设计循环队列。具体思路如下: 1. **用两个队列实现栈**: - 插入元素时,选择非空队列进行插入。 - 移除栈顶元素时,将非空队列中的元素依次转移到另一个队列,直到只剩下一个元素,然后弹出该元素。 - 判空条件为两个队列均为空。 2. **用两个栈实现队列**: - 插入元素时,选择非空栈进行插入。 - 移除队首元素时,将非空栈中的元素依次转移到另一个栈,再将这些元素重新放回原栈以保持顺序。 - 判空条件为两个栈均为空。
|
11月前
|
存储 C语言 C++
【C++数据结构——栈与队列】链栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现链栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储整数,最大
256 9

热门文章

最新文章