将指令从 eax 移到 es 寄存器会出错
move instruction from eax to es register gives error
我想不出一种方法将代码从内存中的一个位置移动到另一个位置
所以我输入了类似这样的东西,但它不起作用
extern _transfer_code_segment
extern _kernel_segment
extern _kernel_reloc
extern _kernel_reloc_segment
extern _kernel_para_size
section .text16
global transfer_to_kernel
transfer_to_kernel:
;cld
;
; Turn off interrupts -- the stack gets destroyed during this routine.
; kernel must set up its own stack.
;
;cli
; stack for only for this function
push ebp
mov ebp, esp
mov eax, _kernel_segment ; source segment
mov ebx, _kernel_reloc_segment ; dest segment
mov ecx, _kernel_para_size
.loop:
; XXX: Will changing the segment registers this many times have
; acceptable performance?
mov ds, eax ;this the place where the error
mov es, ebx ; this to
xor esi, esi
xor edi, edi
movsd
movsd
movsd
movsd
inc eax
inc ebx
dec ecx
jnz .loop
leave
ret
有没有其他方法可以解决这个问题,或者我该如何解决这个问题
段寄存器的大小都是16位。将其与大小为 32 位的 e?x
寄存器进行比较。显然,这两个东西的大小不一样,提示你的汇编程序产生一个 "operand size mismatch" 错误——两个操作数的大小不匹配。
据推测,你想用寄存器的低 16 位初始化段寄存器,所以你会做这样的事情:
mov ds, ax
mov es, bx
此外,不,您实际上不需要在循环的每次迭代中初始化段寄存器。您现在所做的是递增 段 并将偏移量强制为 0,然后复制 4 个 DWORD。您应该做的是不理会该段,只增加 offset(MOVSD
指令隐式执行)。
mov eax, _kernel_segment ; TODO: see why these segment values are not
mov ebx, _kernel_reloc_segment ; already stored as 16 bit values
mov ecx, _kernel_para_size
mov ds, ax
mov es, bx
xor esi, esi
xor edi, edi
.loop:
movsd
movsd
movsd
movsd
dec ecx
jnz .loop
但请注意,将 REP
prefix 添加到 MOVSD
指令中可以让您更有效地执行此操作。这基本上总共 MOVSD
次 ECX
次。例如:
mov ds, ax
mov es, bx
xor esi, esi
xor edi, edi
shl ecx, 2 ; adjust size since we're doing 1 MOVSD for each ECX, rather than 4
rep movsd
有点违反直觉,如果您的处理器实现了 (Intel Ivy Bridge 及更高版本),REP MOVSB
实际上可能比 REP MOVSD
更快,因此您可以:
mov ds, ax
mov es, bx
xor esi, esi
xor edi, edi
shl ecx, 4
rep movsb
最后,虽然您已经在代码中注释掉了 CLD
指令,但您确实需要这样做以确保移动按计划进行。您不能依赖具有特定值的方向标志;您需要自己将其初始化为您想要的值。
(另一种选择是流式传输 SIMD 指令甚至浮点存储,它们都不会关心方向标志。这具有增加内存复制带宽的优势,因为您将执行 64 位、128位,或一次更大的副本,但引入了其他缺点。在内核中,我会坚持使用 MOVSD
/MOVSB
除非你能证明这不是一个重要的瓶颈 and/or你想为不同的处理器优化路径。)
那将有可怕的表现。 Agner Fog 说 mov sr, r
在 Nehalem 上每 13 个周期有一个吞吐量,我猜如果有的话,最近的 CPU 会更糟,因为分段已经过时了。 Agner 在 Nehalem 之后停止测试 mov to/from 段寄存器性能。
您这样做是为了让您总共复制超过 64kiB 的文件吗?如果是这样,在更改段寄存器之前至少复制完整的 64kiB。
我认为您可以使用 32 位寻址模式来避免弄乱段,但是您在 16 位模式下设置的段隐式具有 64k 的 "limit"。 (即 mov eax, [esi]
在 16 位模式下是可编码的,具有操作数大小和地址大小前缀。但是 esi 中的值超过 0xFFFF,我认为违反 ds
段限制。) 下面的 osdev link 更多。
正如 Cody 所说,使用 rep movsd
让 CPU 使用优化的微编码 memcpy
。 (。
实际上,,所以总是使用 rep movsd
可能是最简单的。但 IvyBridge 可能不会。)它 比单独的 movsd
指令快 很多 (比单独的 mov
loads/stores 慢)。在某些 CPU 上,使用 SSE 16B 向量 loads/stores 的循环可能几乎与 rep movsd
一样快,但您不能在 16 位模式下将 AVX 用于 32B 向量。
大副本的另一种选择:巨大的虚幻模式
在 32 位保护模式下,您放入段中的值是描述符,而不是实际的段基本身。 mov es, ax
触发 CPU 将该值用作 GDT 或 LDT 的索引,并从那里获取段基数/限制。
如果您在 32 位模式下执行此操作,然后切换回 16 位模式,您将处于巨大的虚幻模式,其段可能大于 64k。段 base/limit/permissions 保持缓存,直到某些东西以 16 位模式写入段寄存器并将其放回通常的 16*seg
和 64k 限制。 (如果我描述正确的话)。有关更多信息,请参阅 http://wiki.osdev.org/Unreal_Mode。
那么您可以在 16 位模式下使用 rep movsd
操作数大小和地址大小前缀,这样您就可以一次复制超过 64kiB。
这对 ds
和 es
很有效,但是 ,所以这对大平面代码地址 space 不方便,只是数据。
我想不出一种方法将代码从内存中的一个位置移动到另一个位置
所以我输入了类似这样的东西,但它不起作用
extern _transfer_code_segment
extern _kernel_segment
extern _kernel_reloc
extern _kernel_reloc_segment
extern _kernel_para_size
section .text16
global transfer_to_kernel
transfer_to_kernel:
;cld
;
; Turn off interrupts -- the stack gets destroyed during this routine.
; kernel must set up its own stack.
;
;cli
; stack for only for this function
push ebp
mov ebp, esp
mov eax, _kernel_segment ; source segment
mov ebx, _kernel_reloc_segment ; dest segment
mov ecx, _kernel_para_size
.loop:
; XXX: Will changing the segment registers this many times have
; acceptable performance?
mov ds, eax ;this the place where the error
mov es, ebx ; this to
xor esi, esi
xor edi, edi
movsd
movsd
movsd
movsd
inc eax
inc ebx
dec ecx
jnz .loop
leave
ret
有没有其他方法可以解决这个问题,或者我该如何解决这个问题
段寄存器的大小都是16位。将其与大小为 32 位的 e?x
寄存器进行比较。显然,这两个东西的大小不一样,提示你的汇编程序产生一个 "operand size mismatch" 错误——两个操作数的大小不匹配。
据推测,你想用寄存器的低 16 位初始化段寄存器,所以你会做这样的事情:
mov ds, ax
mov es, bx
此外,不,您实际上不需要在循环的每次迭代中初始化段寄存器。您现在所做的是递增 段 并将偏移量强制为 0,然后复制 4 个 DWORD。您应该做的是不理会该段,只增加 offset(MOVSD
指令隐式执行)。
mov eax, _kernel_segment ; TODO: see why these segment values are not
mov ebx, _kernel_reloc_segment ; already stored as 16 bit values
mov ecx, _kernel_para_size
mov ds, ax
mov es, bx
xor esi, esi
xor edi, edi
.loop:
movsd
movsd
movsd
movsd
dec ecx
jnz .loop
但请注意,将 REP
prefix 添加到 MOVSD
指令中可以让您更有效地执行此操作。这基本上总共 MOVSD
次 ECX
次。例如:
mov ds, ax
mov es, bx
xor esi, esi
xor edi, edi
shl ecx, 2 ; adjust size since we're doing 1 MOVSD for each ECX, rather than 4
rep movsd
有点违反直觉,如果您的处理器实现了 REP MOVSB
实际上可能比 REP MOVSD
更快,因此您可以:
mov ds, ax
mov es, bx
xor esi, esi
xor edi, edi
shl ecx, 4
rep movsb
最后,虽然您已经在代码中注释掉了 CLD
指令,但您确实需要这样做以确保移动按计划进行。您不能依赖具有特定值的方向标志;您需要自己将其初始化为您想要的值。
(另一种选择是流式传输 SIMD 指令甚至浮点存储,它们都不会关心方向标志。这具有增加内存复制带宽的优势,因为您将执行 64 位、128位,或一次更大的副本,但引入了其他缺点。在内核中,我会坚持使用 MOVSD
/MOVSB
除非你能证明这不是一个重要的瓶颈 and/or你想为不同的处理器优化路径。)
那将有可怕的表现。 Agner Fog 说 mov sr, r
在 Nehalem 上每 13 个周期有一个吞吐量,我猜如果有的话,最近的 CPU 会更糟,因为分段已经过时了。 Agner 在 Nehalem 之后停止测试 mov to/from 段寄存器性能。
您这样做是为了让您总共复制超过 64kiB 的文件吗?如果是这样,在更改段寄存器之前至少复制完整的 64kiB。
我认为您可以使用 32 位寻址模式来避免弄乱段,但是您在 16 位模式下设置的段隐式具有 64k 的 "limit"。 (即 mov eax, [esi]
在 16 位模式下是可编码的,具有操作数大小和地址大小前缀。但是 esi 中的值超过 0xFFFF,我认为违反 ds
段限制。) 下面的 osdev link 更多。
正如 Cody 所说,使用 rep movsd
让 CPU 使用优化的微编码 memcpy
。 (rep movsd
可能是最简单的。但 IvyBridge 可能不会。)它 比单独的 movsd
指令快 很多 (比单独的 mov
loads/stores 慢)。在某些 CPU 上,使用 SSE 16B 向量 loads/stores 的循环可能几乎与 rep movsd
一样快,但您不能在 16 位模式下将 AVX 用于 32B 向量。
大副本的另一种选择:巨大的虚幻模式
在 32 位保护模式下,您放入段中的值是描述符,而不是实际的段基本身。 mov es, ax
触发 CPU 将该值用作 GDT 或 LDT 的索引,并从那里获取段基数/限制。
如果您在 32 位模式下执行此操作,然后切换回 16 位模式,您将处于巨大的虚幻模式,其段可能大于 64k。段 base/limit/permissions 保持缓存,直到某些东西以 16 位模式写入段寄存器并将其放回通常的 16*seg
和 64k 限制。 (如果我描述正确的话)。有关更多信息,请参阅 http://wiki.osdev.org/Unreal_Mode。
那么您可以在 16 位模式下使用 rep movsd
操作数大小和地址大小前缀,这样您就可以一次复制超过 64kiB。
这对 ds
和 es
很有效,但是