为什么在下面的代码中我们推入代码段(PUSH CS),然后将其弹出到数据段(POP DS)中?
我将这些行明确地指定为line1和line2。请让我知道MOVSW在这里如何运作。
IF HIGHMEMORY
PUSH DS
MOV BX, DS
ADD BX, 10H
MOV ES, BX
PUSH CS. ;line1
POP DS. ;line2
XOR SI, SI
MOV DI, SI
MOV CX, OFFSET SYSSIZE + 1
SHR CX, 1
REP MOVSW. ;line3
POP DS
PUSH ES
MOV AX, OFFSET SECONDRELOCATION
PUSH AX
AAA PROC FAR
RET
AAA ENDP
SECONDRELOCATION:
more code here..............
答案 0 :(得分:5)
暂时设置DS = CS,然后恢复它似乎是对rep movsw
使用CS覆盖前缀的一种无效选择。
A segment override can change the source for movsw
从DS:SI
到CS:SI
。 (无法覆盖ES:DI
的目的地)。
(更新:在原始的8086/8088上,存在硬件“错误” /异常:在从REP字符串指令期间发生的中断恢复后,IP将指向 last 前缀指令,而不是第一条指令。因此,根据编码,cs rep movsw
可以解码为rep movsw
或cs movsw
。请参阅@MichaelPetch的注释,以及https://www.pcjs.org/pubs/pc/reference/intel/8086/以获取更多8086勘误表以及后来的x86 CPU中已修复的异常。)
此代码正在执行memcpy(dst, code_segment, sizeof(code_segment))
,其中dst
segment:offset为(BX + 16):0
。 rep movsw
之前的指令设置DS = BX + 16并设置DI = 0。
然后,代码在推入目标段(ES)和其中的偏移量之后,使用很远的ret
跳转到新位置。 (效率低下:push offset SECONDRELOCATION
应该可以正常工作。)
显然,此汇编器不支持ret far
或retf
之类的语法,因此他们必须通过在{{1周围声明一个ret
来汇编一段远距离的proc far
指令。 }}指令。 ret
是该过程的一个非常奇怪的名称,因为aaa
is also a valid x86 instruction mnemonic (ASCII Adjust after Addition)。
因此,执行将继续在我们刚刚编写的代码副本中的AAA
标签处。
SECONDRELOCATION:
会四舍五入为整数,除非大小自动换行,否则它将复制零字节而不是64k。 (与(size+1) / 2
不同,loop
在执行一次 之前检查计数。)
在运行时执行rep
也是很愚蠢的,并且可以在汇编时使用shr
之类的方法来完成。 (您可能无法将mov cx, (offset endcode - startcode + 1) / 2
的结果除以2,但是可以在汇编时找到同一节中两个标签之间的距离。)
无论如何,可能的关键是将代码重新放置到HIGHMEM中,从而使低内存可供不使用HIMEM的程序使用。
答案 1 :(得分:0)
序列push cs
,pop ds
只是一种将数据段设置为与代码段相同的值的方法。
这类似于使用push ax
,pop bx
而不是mov bx, ax
,除了它使用内存和可能对某些标志有不同的影响之外,当我的意图只是提供示例时,我不会打扰我:-)
您执行此操作的一个原因可以追溯到x86分段体系结构的早期(相对于更现代的选择器而言),这种情况如今已很少使用。 x86具有各种内存模型,如微型,小型,紧凑型,中型,大型和大型。
这些基本上是您可以使用的代码和数据段的大小和数量的变化,从内存来看,微小的含义是您有一个一个段,既包含代码又包含数据。
因此cs
和ds
应该设置为相同的值,以便所有指令默认在该段上进行操作。
在特定情况下,您要保存ds
,将其设置为与cs
相同的值,然后将其还原。有关原因的更可能解释,请参见下文。
关于movsw
的工作原理,它只是简单地从ds:si
的内存中复制一个单词值到地址es:di
,然后更新指针(递增或递减,具体取决于设置方向标志)。
rep
前缀会循环执行,将cx
递减,直到达到零为止。
因此,它只是大容量存储副本。
现在,由于repsw
的来源是根据ds
段指定的,因此您看到push/pop
暂时设置ds
的真正原因就变成了清楚-这是因为数据源显然位于代码段中。