1.段寄存器在程序中应用
提问:
汇编指令中,伪指令 assume cs:code,ds:data 有什么用?我发现不写ds:data对程序的运行不会有任何影响,书上说段名(也就是code,data)相当于一个段地址,原本我以为 ds:data 这条语句是给ds赋值,结果发现并不能给ds赋值,而且代码段中总会有:mov ax,data;mov ds,ax;通过这样的语句给ds赋值,那cs:code,ds:data岂不是没有任何用处?并且我还在书上看到这样一句话:“assume cs:code,ds:data,ss:stack将cs,ds和ss分别与code,data,stack段相连”请问,相连又是什么意思?相连后不能赋值又有什么意义呢?编译器是如何处理这一段伪指令的?
为了运用所有的内存空间,8086设定了四个段寄存器,专门用来保存段地址:
CS(Code Segment):代码段寄存器;
DS(Data Segment):数据段寄存器;
SS(Stack Segment):堆栈段寄存器;
ES(Extra Segment):附加段寄存器。
当一个程序要执行时,就要决定程序代码、数据和堆栈各要用到内存的哪些位置,通过设定段寄存器 CS,DS,SS 来指向这些起始位置。通常是将DS固定,而根据需要修改CS。所以,程序可以在可寻址空间小于64K的情况下被写成任意大小。
要用assume把段跟段寄存器对应起来的原因是原来的DOS找到的空闲内存的地址不是固定的,无法找到一个地址在任何时候都是空闲的。于是DOS需要可以重定位的程序,而当时的定位方式就是设置段寄存器的值使该程序能在可分配(空闲)的内存中可用。那就需要知道某个段被重定位时候需要修改哪个段寄存器的值才能正确执行。assume提供这种段和重定位代码时需要对应修改的寄存器的关系给编译器,编译器再这个信息写到二进制文件中去。比如DOS下的exe程序记录在文件头中。
任何段寄存器都不能直接赋值 这是规定 不要管他有没有意义 你理解成C的函数声明就OK 作用就是找到它的地址。
例题
将内存ffff:0-ffff:f 内存单元中的数据复制到0:200-0:20f;
分析:
ds寄存器:记录数据从哪里来;
es寄存器:记录数据到哪里去。
assume cs:code
code segment
mov ax,0ffffh
mov ds,ax ; 数据从ds中来
mov ax,20h
mov es,ax ; 数据到es中去
mov bx,0 ; 0-16
mov cx,16 ; loop 计数器
setNumber:
mov dl,ds:[bx]
mov es:[bx],dl
inc bx
loop setNumber
mov ax,4c00h
int 21h
code ends
end
可以继续优化,方法如下;
assume cs:code
code segment
mov ax,0ffffh
mov ds,ax ; 数据从ds中来
mov ax,20h
mov es,ax ; 数据到es中去
mov bx,0 ; 0-16
mov cx,8 ; loop 计数器
setNumber:
mov dx,ds:[bx] ;dx两个字节
mov es:[bx],dx
add bx,2
loop setNumber
mov ax,4c00h
int 21h
code ends
end
2.通用寄存器在寄存器中应用
EAX 是"累加器"(accumulator), 它是很多加法乘法指令的缺省寄存器。
EBX 是"基地址"(base)寄存器, 在内存寻址时存放基地址。
ECX 是计数器(counter), 是重复(REP)前缀指令和LOOP指令的内定计数器。
EDX 则总是被用来放整数除法产生的余数。
ESI/EDI 分别叫做"源/目标索引寄存器"(source/destination index),因为在很多字符串操作指令中, DS:ESI指向源串,而ES:EDI指向目标串.
EBP 是"基址指针"(BASE POINTER), 它最经常被用作高级语言函数调用的"框架指针"(frame pointer). 在破解的时候,经常可以看见一个标准的函数起始代码:
push ebp ; 保存当前ebp
mov ebp,esp ; EBP设为当前堆栈指针
sub esp, xxx ; 预留xxx字节给函数临时变量.
...
这样一来,EBP 构成了该函数的一个框架, 在EBP上方分别是原来的EBP, 返回地址和参数. EBP下方则是临时变量. 函数返回时作 mov esp,ebp/pop ebp/ret 即可.
ESP 专门用作堆栈指针,被形象地称为栈顶指针,堆栈的顶部是地址小的区域,压入堆栈的数据越多,ESP也就越来越小。在32位平台上,ESP每次减少4字节。
IP/EIP存当前指令的地址(EIP8个字节)。一般使用EBP访问位于堆栈中的参数。ESP+8->第一个参数,ESP->12第二个参数……
3.stosb / stosw / stosd
STOSB、STOSW 和 STOSD 指令分别将 AL/AX/EAX 的内容存入由 EDI 中偏移量指向的内存位置。EDI 根据方向标志位的状态递增或递减。
与 REP 前缀组合使用时,这些指令实现用同一个值填充字符串或数组的全部元素。例如,下面的代码就把 string1 中的每一个字节都初始化为 OFFh:
.data
Count = 100
string1 BYTE Count DUP(?)
.code
mov al, OFFh ;要保存的数值
mov edi,OFFSET string1 ;EDI指向目标字符串
mov ecx,Count ;字符计数器
cld ;DF置0,di向前移动。std指令DF置1,告诉程序si,di向后移动。
rep stosb ;用 AL 的内容实现填充
4.pushad / popad
PUSHAD
本指令将EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI 这8个32位通用寄存器依次压入堆栈,其中SP的值是在此条件指令未执行之前的值.压入堆栈之后,ESP-32–>ESP.
POPAD
本指令依次弹出堆栈中的32位字到 EDI,ESI,EBP,ESP,EBX,EDX,ECX,EAX中,弹出堆栈之后,ESP+32–>ESP.