目录
3.2.5 栈到底是什么玩意儿
本节🍊:熟了自然就理解了,这只是时间问题。(知识的掌握离不开实践)
所以不要害怕困难,要勇往直前💪
CPU 中有栈段 SS 寄存器和栈指针 SP 寄存器,它们是用来指定当前使用的栈的物理地址。换句话说,要想让 CPU 运行,必须得有栈。(栈在 CPU 运行时起啥具体作用啊???)
在用户进程空间中,堆是堆,栈是栈,但堆栈却是人们常说的栈,和堆没关系。
栈是线性表的一种,线性就是具有线的性质,就像一条线一样,连续性强,从一个方向到另一个方向。栈只是一种抽象概念,是一种虚拟出来的数据存取方法,其实现形式是不限的,只要满足栈的定义就可以:两个条件,一个是线性结构,另一个是在栈顶对数据存取。
把数据结构中的栈的概念用物理硬件来实现:给栈指定一片内存区域,区域的起始地址作为栈基址,存入栈基址寄存器 SS 中,另一端是动态变化的,用栈指针寄存器来指定。栈在使用过程中是向下扩展的(但栈也是内存,访问内存依然是从低地址往高地址),所以栈顶地址肯定小于栈底地址;栈操作的单位是字长;字长是指 CPU 字长,即一次可处理的数据的长度,在实模式下的字长是 16 位,也就是 2 字节。
3.2.6 实模式下的 ret
call 指令用来执行一段新的代码。让 CPU 踏上新的征途,为避免这是一条不归路,还需要返回指令 ret 来帮忙。凡是调用由 call 指令,CPU 就要找地方(栈)把返回地址存起来以备将来函数执行时有路可以返回。
ret (return )指令的功能是在栈顶(寄存器 ss: sp 所指向的地址)弹出 2 字节的内容来替换 IP 寄存器,ret 只置换了 IP 寄存器,也就是说,不用换段基址,属于近返回。retf (return far )是从栈顶取得 4 字节,栈顶处的 2 字节用来替换 IP 寄存器,另外的 2 宇节用来替换 CS 寄存器。
call 和 ret 是一对配合,用于近调用和近返回,call far 和 retf 是一对配合,用于远调用和远返回。
3.2.7 实模式下的 call
call 指令用于执行完一段分支后再回来的情况,当然它能回来还是需要用 ret,retf 来配合。
16 位实模式相对近调用
call (near) 立即数
"近" 就是指在同一段内,不用切换段,不用换段基址,只需给出段内偏移地址。near 可省略,默认取两个字节。
"相对" 是指在编译后的机器码的操作数中,它是 call 指令相对于目标地址的偏移量,是个地址差。
首先用目标函数的地址减去当前 call 指令的地址,所得的差再减去此 call 指令机器码的大小,最终的结果便是 call 指令中的操作数,即与目标地址的相对地址增量。(在同一段内的函数调用——近调用,必须要用相对地址的形式,这是硬件设计的问题)
程序中用的绝对地址都是给程序员看的,编译器会将地址转换成相对地址,CPU 执行时又会将其恢复成绝对地址。恢复的方法是:当前的 IP 指针+操作数+机器码大小=目标函数绝对地址。
call 相对近调用发生时 CPU 当前 IP 寄存器值压入栈,再把上面计算出的绝对地址载入 IP 寄存器,CPU 的航线立马被改变到目标地址处。
🤔️???
当前的 IP 指针 这里 IP 寄存器中存放的不是下一条指令的有效地址了嘛???
还是说,只有当前指令执行结束了,才会开始自动更新IP寄存器的值指向下一条指令?
16 位实模式间接绝对近调用
“间接” 是指目标函数的地址并没有直接给出,地址要么在寄存器中,要么在内存中,总之不以立即 数的形式出现。
“绝对” 是指目标函数的地址是绝对地址,不像 “16 位相对近调用” 中的那样是相对地址。
指令的一般形式是 “call 寄存器寻址” 或 “call 内存寻址”。
16 位实模式直接绝对远调用
直接就是操作数在指令中直接给出,是立即数。
在各种转移指令中,
凡是包含“直接”,都意指不需要经过寄存器或内存,操作数以立即数的形式给出;
凡是包含“远”,就意指要跨段,目标函数和当前指令不在同一个段中。
指令的一般形式是: call far 段基址(立即数):段内偏移地址(立即数);对应的机器码中偏移地址在前,段基址在后。
16 位实模式间接绝对远调用
“间接” 就是说,段基址和段内偏移地址,都不是立即数,要么在内存中,要么在寄存器中。为省寄存器资源,这种方式不支持寄存器寻址,只支持内存寻址,即段基址和段内偏移地址在内存中。
指令格式是: call far 内存寻址(将默认的段基址寄存器是 ds)
3.2.8 实模式下的 jmp
无条件跳转,是指“生硬地”改变 CPU 航线,将程序流转移到新的位置。和 call 一样,按远近 (是否跨段) 来划分,大致分为两类:近转移,远转移,不过在转移方式中,还有个更近的,叫短转移。
16 位实模式相对短转移
jmp (short) 立即数地址
相对短转移中的 “短” 体现在操作数中,即跳转的范围只能是一字节有符号数所表示的范围,即-128~127。
编译器处理:用跳转后的目标地址减去当前地址,所得的差再减去此种 jmp 指令的大小2字节,最终的结果便 jmp 相对短转移的操作数。CPU 是要用绝对地址来寻址的,方法是将此 jmp 的操作数加上寄存器 IP 的值,再加上2字节,所得的结果便是目标地址的绝对地址,这样的地址 CPU 才能用。关键字 short 可以省略,但省略后并不能保证 nasm 依然把它编译成相对短转移的形式,这将取决于操作数的大小。
16 位实模式相对近转移
jmp (near) 立即数地址
相对近转移和相对短转移相比,就是操作数范围增大了,由 8 位宽度变成了 16 位宽度,操作数依然是地址相对量,可正可负,范围是-32768~32767。
相对短转移和相对近转移,其中的 short, near 如果省略,nasm 编译器会根据目的地址和当前地址的偏移量大小来自行判断,若偏移量属于范围 -128~127,则编译 short 短转移形式,若超过了短转移的范围就编译为 near 近转移形式。
16 位实模式间接绝对近转移
jmp near 寄存器寻址,或者 jmp near 内存寻址
间接,是指操作数并不直接给出,而是存储在寄存器或内存中。
绝对地址顾名思义,就是段内偏移地址,指的是 “CS: IP” 中的 IP 值,偏移地址是 16 位。
所以,“间接绝对近转移” 就是指段内转移,转移的地址 16 位宽度,也就是 2 字节,地址要么在寄存器中,要么在内存中。关键宇 near 来修饰,表示在内存或寄存器中取 2 字节,这是一种数据类型转换,在此,near 依然可以省略,nasm 编译器默认在地址处取 2 字节。
若操作数在内存中,在不使用段跨越前缀的情况下,内存寻址的段基址寄存器是 DS。
near 和 short 就是nasm数据类型伪指令
16 位实模式直接绝对远转移
jmp 立即数形式的段基址 : 立即数形式的段内偏移地址
“直接”是指操作数不仅是立即数,而且 CPU 直接拿来就用,不用再转换。
“绝对”是指提供的操作数是绝对地址。
“远”是指目的地址和当前指令不是一个段,有跨段的需求,所以要操作数要包括新的段基址和段内偏移。
16 位实模式间接绝对远转移
jmp far 内存寻址(内存中前两个字节是段内偏移地址,后两个字节是段基址)
操作数不是直接给出的,即段基址和段内偏移都不是立即数的形式,操作数只放在内存中。
只要不是间接,操作数都以立即数的形式给出,无论是相对还是绝对。
🍊🍊🍊有时间要好好总结一下🍊🍊🍊
3.2.9 标志寄存器标志
实模式下标志寄存器是 16 位的 flags,在 32 位保护模式下,扩展( extend )了标志寄存器,成为 32 位的 eflags。
用于判断的条件,本质上是上一条指令执行的结果。flags 寄存器中存储的信息,只是结果的特征,即标志,并不是真正的结果,结果可以存储在内存中。这些标志告诉大家,为了产生这个结果,机器都做了什么。(这些标志位的作用,书中都介绍得很详细)
本节🍊:越底层的东西往往灵活性越强,提供的信息越丰富,它不单纯记录结果,而且还能告诉你这个结果来的过程。
所以要打牢地基,房子🏠才能建得高!!!
3.2.10 有条件转移
有条件转移不只是简单的一个指令,它是一个指令族,我们在此简单称 jxx。
其格式为:jxx 目标地址。若条件满足则跳转到目标地址,否则顺序执行下一条指令。其中,目标地址只能是段内偏移地址。条件转移指令,判断的就是上一条指令对标志位的“影响”,这些“影响”就是条件。
条件转移指令中所说的条件就是指标志寄存器中的标志位,jxx 中的 xx 就是各种条件的分类,每种 条件有不同的转移指令。
🍊经验表明,即使不理解的东西,时间一长也会理所当然就这样,对于新生事物,只是时间问题。——所以不要害怕
3.2.11 实模式小结
终于到小节了,呜呜呜呜
实模式被保护模式淘汰的原因,最主要是安全隐患。
在实模式下,用户程序和操作系统可以说是同一特权的程序,因为实模式下没有特权级,它处处和操作系统平起平坐,所以可以执行一些具有破坏性的指令。程序可以随意修改自己的段基址,可以随意访问任意物理内存,包括访问操作系统所在的内存数据。