【问题标题】:move instruction from eax to es register gives error将指令从 eax 移动到 es 寄存器给出错误
【发布时间】:2017-12-12 18:45:40
【问题描述】:

我想不出一种方法将代码从内存中的一个位置移动到另一个位置

所以我做了这样的事情,但它不起作用

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

还有其他方法可以解决这个问题,或者我该如何解决这个问题

【问题讨论】:

    标签: assembly x86 nasm


    【解决方案1】:

    段寄存器的大小都是 16 位。将其与大小为 32 位的 e?x 寄存器进行比较。很明显,这两个东西大小不一样,提示你的汇编器产生“operand size mismatch”错误——两个操作数的大小不匹配。

    大概,你想用寄存器的低 16 位初始化段寄存器,所以你会这样做:

    mov  ds, ax
    mov  es, bx
    

    另外,不,您实际上不需要在循环的每次迭代中初始化段寄存器。您现在正在做的是增加 segment 并将偏移量强制为 0,然后复制 4 个 DWORD。您应该做的是不理会该段并仅增加 offsetMOVSD 指令隐式执行)。

        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(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 除非你可以证明不是一个重要的瓶颈和/或你想要为不同的优化路径处理器。)

    【讨论】:

    • _kernel_segment 值是 32 位地址,所以如果我在“mov ds, ax”中使用像你这样的低 16 位,那将是一个问题
    • 为什么是 32 位值?段只有 16 位。好吧,我想您必须自己进行分段算术;见:thestarman.pcministry.com/asm/debug/Segments.html
    • 虽然合法,但 32 位值 esi, edi and ecx 并不重要。即使您确实将 ECX 设置为 1FC00,假设,指针只会在每 65536 次迭代或 32768(如果移动字)或 16384(如果移动双字)上简单地环绕。除非有一些逻辑可以相应地调整段寄存器,否则没有办法避免这种情况。
    • 我的意思是,例如取 0xc0000000,即使我将其右移 4 以删除最后一位我以无法以 16 位存储的值结尾
    • rep movsb 仅在 Intel IvB 及更高版本上更快。因此,您可能应该定义“现代”的含义。 rep movsb 在 Sandybridge 和更早版本上明显比 rep movsd 差,因为微码针对小副本进行了优化。了解 AMD 的工作。
    【解决方案2】:

    那将有可怕的表现。 Agner Fogmov sr, r 在 Nehalem 上有一个每 13 个周期的吞吐量,我猜想如果有的话,在更新的 CPU 上情况会更糟,因为分段已经过时了。在 Nehalem 之后,Agner 停止测试 mov 到/从段寄存器的性能。

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

    我认为您可以使用 32 位寻址模式来避免与段混淆,但是您在 16 位模式下设置的段隐式地具有 64k 的“限制”。 (即mov eax, [esi] 可在16 位模式下编码,具有操作数大小和地址大小前缀。但是esi 中的值超过0xFFFF,我认为违反ds 段限制会出错。)更多信息请点击下面的 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 位模式写入段寄存器并将其放回通常的 16*seg 并具有 64k 限制。 (如果我描述正确的话)。请参阅http://wiki.osdev.org/Unreal_Mode 了解更多信息。

    那么您也许可以在 16 位模式下使用带有操作数大小和地址大小前缀的 rep movsd,这样您就可以一次复制超过 64kiB 的数据。

    这适用于dses,但interrupts will set cs:ip,所以这对于大的平面代码地址空间不方便,只是数据。

    【讨论】:

    • 谢谢,大实模式有帮助,但是当我从函数返回时它没有返回
    • @sakura:我对过时的 16 位模式几乎没有兴趣。我一直在 SO questions 中阅读它,这是我能够写出这个答案的唯一原因(很高兴它有帮助,顺便说一句)。我只能推荐一个好的调试器,例如BOCHS 内置的一个,因此您可以单步执行内核。
    • 在关于这个问题的更多细节变得清晰之后,我不禁想知道为什么您不只是切换到 32 位保护模式在进行此复制之前。大概这就是内核最终要做的事情。为什么要切换回实模式?据我所知,即使在“虚幻模式”下,你也不能超过 1 MB,所以这并不是一个灵丹妙药的解决方案。 @樱花
    • @CodyGray:根据 wiki 页面,您可以进行设置,以便使用 32 位寻址模式并访问 4G 数据,但仍然只有 64k 代码:“ DS 和 ES 段寄存器设置为 0,因此 C 指针可以作为平面 32 位物理地址和地址数据或内存映射设备在前 4GB 内存中的任何位置工作。“但是,是的,似乎只是切换到 32 位模式会更明智。如果您不想,不必立即启用分页。
    • @PeterCordes :您可以像数据段一样设置限制为 4gb(或 64kb 以外的大小)的 16 位代码段。不幸的是,您必须在这样的环境中处理问题,例如仅保存 CS:IP 而不是 CS:EIP 的中断。这种环境的绰号通常是巨大的虚幻模式
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-30
    • 2016-03-11
    • 1970-01-01
    • 2013-05-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多