为什么我自己写在NASM上的memcpy不能复制超过340000000字节?

Why my own memcpy written on NASM can not copy more than 340000000 bytes?

我正在学习nasm。我写了一个简单的函数,将内存从源复制到目标。我在 C 中测试。

            section .text
            global _myMemcpy

_myMemcpy:
            mov eax, [esp + 4]
            mov ecx, [esp + 8]
            add [esp + 12], eax
            lp:
                   mov dl, [ecx]
                   mov [eax], dl

                   inc eax
                   inc ecx
                   cmp eax, [esp + 12]
                   jl lp
            endlp:
                    mov eax, [esp + 4]
                    ret

C 程序:

#include <string.h>
#define Times 340000000
extern void* _myMemcpy(void* dest, void* src, size_t size);
char sr[Times];
char ds[Times];
int main(void)
{
    memset(sr, 'a', Times);
    _myMemcpy(ds, sr, Times);
    return 0;
}

我目前正在使用 Ubuntu OS。当我编译和 link 这两个带有 $ nasm -f elf m.asm && gcc -Wall -m32 m.o p.c && ./a.out 的文件时,当 Times 的值小于 340000000 时它工作正常。当它更大时,_myMemcpy 只复制第一个字节的来源到目的地。我不知道问题出在哪里。每个建议都会有用。

您正在对指针进行有符号比较;不要那样做。在这种情况下使用 jne,因为您将始终在退出点达到完全相等。

或者,如果您想要与指针进行关系比较,通常 jbjae 等无符号条件最有意义。 (将虚拟地址 space 视为最低地址为 0 的平面线性 4GiB 是正常的,因此您需要在该范围的中间递增才能工作)。

对于大于 ~300MiB 大小的数组,以及 PIE 可执行文件的默认链接描述文件,显然其中之一将跨越有符号正数和有符号负数之间的 2GiB 边界1.因此,如果您将其视为有符号整数,则您计算的结束指针将为“负”。 (与 x86-64 不同,其中跨越虚拟地址中间的非规范“孔”-space 意味着数组永远不能跨越有符号环绕边界: - 有时它 在那里使用带符号的比较是否有意义。)

如果您单步执行并查看指针值,以及您使用 size += dest (add [esp + 12], eax) 创建的内存值,您应该会在调试器中看到这一点。作为带符号的操作,它会溢出以创建一个负数 end_pointer,而起始指针仍为正数。 pos < neg 在第一次迭代时为 false,因此您的循环退出,您可以在单步执行时看到这一点。


脚注 1:在我的系统上,在 GDB(禁用 ASLR)下,在 start 之后将可执行文件映射到 Linux 的默认值PIE 的基地址(进入地址低半部分的 2/3 space,即 0x5555...),我用你的测试用例检查了地址:

  • sr0x56559040
  • ds0x6a998d40
  • ds 结束于 p /x sizeof(ds) + ds = 0x7edd8a40

所以如果它大得多,它会穿过 0x80000000。这就是为什么 340000000 避免了你的错误,但更大的尺寸揭示了它。

顺便说一句,在 32 位内核下,Linux 默认为内核和用户 space 之间地址 space 的 3:1 分割,所以即使在那里有可能发生这种情况。但是在 64 位内核下,32 位进程可以拥有整个 4 GiB 地址 space。 (内核保留的一两页除外:另请参阅 。这也意味着像您正在做的那样形成指向任何数组的尾数的指针(ISO C 承诺是有效的) ), 不会回绕并且仍然会在指向对象的指针上方进行比较。)

这不会在 64 位模式下发生:有足够的地址 space 可以在用户和内核之间平均分配它,并且在高范围和低范围之间有一个巨大的非规范孔.