将指令从eax移动到es寄存器会产生错误

时间:2017-07-08 16:15:05

标签: assembly x86 nasm

我无法找到一种方法将代码从一个位置移动到内存中的其他位置

所以我提出了一些像这样的事情,但它不起作用

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

有没有其他方法可以做到这一点或如何解决这个问题

2 个答案:

答案 0 :(得分:3)

段寄存器的大小都是16位。将其与e?x寄存器进行比较,寄存器的大小为32位。显然,这两个东西的大小不一样,促使汇编程序生成"操作数大小不匹配"错误 - 两个操作数的大小不匹配。

据推测,您希望使用寄存器的低16位初始化段寄存器,因此您可以执行以下操作:

mov  ds, ax
mov  es, bx

另外,不,你实际上并不需要在循环的每次迭代中初始化段寄存器。您现在正在做的是递增并强制将偏移量设置为0,然后复制4个DWORD。你应该做的是单独留下段而只是递增偏移量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

有点违反直觉,如果你的处理器实现the ERMSB optimization(英特尔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,除非你能证明不是重大的瓶颈和/或者您希望为不同的处理器提供优化路径。)

答案 1 :(得分:1)

那会有糟糕的表现。 Agner Fogmov sr, r在Nehalem上每13个周期有一个吞吐量,并且我猜测如果在最近的CPU上情况更糟,因为细分已经过时了。在Nehalem之后,Agner停止了对段寄存器性能的测试。

你这样做是为了让你复制超过64kiB吗?如果是这样,至少在更改段寄存器之前复制一个完整的64kiB。

我认为您可以使用32位寻址模式以避免混淆段,但是您在16位模式下设置的段隐含地具有"限制" 64k。 (即mov eax, [esi]在16位模式下可编码,带有操作数大小和地址大小前缀。但是如果esi值大于0xFFFF,我认为违反ds会出错segment limit。)下面的osdev链接了解更多。

正如Cody所说,使用rep movsd让CPU使用优化的微编码memcpy。 (or rep movsb, but only on CPUs with the ERMSB feature。 在实践中,most CPUs that support ERMSB give the same performance benefit for rep movsd too,因此最简单的方法就是始终使用rep movsd。但IvyBridge可能没有。)它比单独的movsd指令(比单独的mov加载/存储更慢)更快 。在某些CPU上,具有SSE 16B向量加载/存储的循环可能几乎与rep movsd一样快,但是您无法在16位模式下将AVX用于32B向量。

大型副本的另一个选择:巨大的虚幻模式

在32位保护模式下,您放入段中的值是描述符,而不是实际的段基本身。 mov es, ax触发CPU将该值用作GDT或LDT的索引,并从那里获取段基/限制。

如果在32位模式下执行此操作然后切换回16位模式,则会处于巨大的虚幻模式,其中段可能大于64k。段基/限制/权限保持缓存,直到某些内容以16位模式写入段寄存器并将其放回到具有64k限制的通常16*seg。 (如果我正确地描述了这一点)。有关详情,请参阅http://wiki.osdev.org/Unreal_Mode

然后你可以在16位模式下使用rep movsd操作数大小和地址大小的前缀,这样你就可以一次复制超过64kiB。

这适用于dses,但interrupts will set cs:ip,因此对于大型平面代码地址空间,只是数据而言,这不方便。