在csapp中的新理解:
(1)栈向下增长,栈顶元素的地址是所有栈中元素地址中最低的
(2)因为栈和程序代码以及其他形式的程序数据都是放在同一内存中,所以程序可以用标准内存寻址方法访问栈内的任意位置
(3)加载有效地址(load effective address)指令leap实际上是movq指令的变形,它的指令形式是从内存读数据到寄存器,但实际上它根本就没有引用内存。
例:leap (%rdx),%rax
它的第一个操作数看上去是一个内存引用,但该指令并不是从指定位置读入数据,而是将有效地址写入到目的操作数。这条指令可以为后面的内存引用产生指针。目的操作数必须是一个寄存器。
书上内容的总结:
3.1 内存中字的存储
- 字单元:即存放一个字型数据(16位)的内存单元,由两个地址连续的内存单元组成。
- 小端法:高地址内存单元中存放字型数据的高位字节,低地址内存单元中存放字型数据的地位字节
- 取低地址的内存单元作为字数据地址
- N地址字单元:起始地址为N的字单元
- 任何两个地址连续的内存单元,N号单元和N+1号单元,可以将他看成两个内存单元,也可看成一个地址为N的字单元的高位字节单元和地位字节单元
3.2 DS和[address]
- DS段寄存器:用来存放要访问数据的段地址
- ds的赋值:(8086不支持将数据直接送入段寄存器的操作,只好用一个寄存器来进行中转)
mov bx,1000Hmov ds,bx
- [...]表示一个内存单元,[...]中的数字表示内存单元的偏移地址
- 8086CPU自动取ds中的数据为内存单元的段地址
3.3 字的传送
- 8086是16位结构,有16根数据线,可以一次性传送16位的数据,即可以一次性传送一个字
- 传送字型数据的实例:
mov ax,1000hmov ds,ax ;改变ds的值mov ax,[0] ;将地址为10000的字数据送入axmov bx,[2] ;将地址为10002的字数据送入bxmov cx,[4] ;将地址为10004的字数据送入cx
3.4 mov、add、sub指令
- mov、add、sub指令的总结:
- 注意事项:
(1)指令中两个操作数的长度要一样(mov ax,cl mov al,300 这都是错误的)
(2)关于立即数:不能作为第一个操作数(目的操作数);在完整的汇编程序中,作第二个操作数(源操作数)时,若最高位是十六进制的a~f,前面要加零
(3)两个内存单元之间不能直接传送数据
(4)不能使用mov指令直接改变cs和ip的值
(5)关于段寄存器:两个段寄存器之间不能直接传送;不能把常数送入段寄存器
3.5 数据段
- 可以将一组长度为N(N<=64KB)、地址连续,起始地址为16的倍数的内存单元当作专门存储数据的内存空间——定义数据段
- 用ds存放数据段的段地址
3.6 栈
- 后进先出(LIFO):最后进入这个空间的数据,最先出去
- 两个基本操作:入栈(PUSH)和出栈(POP)
push ax ;表示将寄存器ax中的数据送入栈中pop ax ;表示从栈顶取出数据送ax
(push 内存单元和pop 内存单元 实际上实现了从内存单元到内存单元的传送)
- 注意事项:
(1)操作对象不能是常数
(2)pop 段寄存器 中,段寄存器不能是CS和SS
(3)出栈和入栈以字为单位
3.7 CPU提供的栈机制
- 段寄存器SS:栈段段寄存器 寄存器SP:栈指针寄存器
- 栈顶的段地址存放在SS中,偏移地址存放在SP中
- 任意时刻,SS:SP指向栈顶元素
- 栈顶的物理地址:SS*16+SP
- push的执行:(入栈时,栈顶从高地址向低地址方向增长)
(1)先SP=SP-2 (2)再将ax中的内容送入SS:SP指向的内存单元处
- pop的执行:
(1)先将SS:SP指向的内存单元处的数据送入ax中 (2)再SP=SP+2
- 栈为空时,就相当于栈中唯一的元素出栈,此时栈顶指向的单元的偏移地址为栈最底部的字单元的偏移地址+2
- pop操作前的栈顶元素,在pop以后,依然存在,但是它已不在栈中,当再次执行push时,它将被覆盖
3.8 栈顶超界问题
- 当栈空时,再使用pop出栈
- 当栈满时,再使用push入栈
- 有可能取到栈以外未知的值,若栈段的大小为64KB,会使栈指针重新指向最后进栈的元素的地址,重新又执行指令
- 栈空间的大小我们要自己管理
3.9 栈段
- 一个栈段最大可以设为64KB
(push,pop等栈操作,修改的只是SP,也就是说,栈顶的变化范围最大为:0~FFFFH)
- 初始化栈段:
;将10000h~1000fh这段空间当作栈mov ax,1000hmov ss,ax ;设置栈的段地址,ss=1000hmov sp,0010h ;设置栈顶的偏移地址,因栈为空,所以sp=0010h
3.10 利用栈保护现场和实现数据交换
- 保护现场:
mov ax,1000hmov ss,axmov sp,0010h ;初始化栈顶mov ax,001Ahmov bx,001Bhpush axpush bxpop bx ;从栈中恢复ax,bx原来的值pop ax
- 实现数据交换:
;初始化的栈段和ax,bx的值的代码同上push axpush bxpop ax ;当前栈顶是bx中原来的数据:001Bh,此时使ax=001Bhpop bx ;此时栈顶使ax中原来的数据:001Ah,此时使bx=001Ah
课后思考:
思考了一下,如何使用栈操作实现字节数据的交换?
我认为首先要把字节数据扩展成字数据,再进行栈操作实现字节数据的交换。
在csapp中介绍了一系列零扩展和符号扩展的指令(AT&T汇编),其中,movzbw则是将做了零扩展的字节传送到字,也许可以如下进行交换:
movzbw %al,%eax ;将al中的字节数据做零扩展送到ax中movzbw %bl,%ebxpushl %eaxpushl %ebxpopl %eaxpopl %ebx