我正在学习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操作系统。当我用$ nasm -f elf m.asm && gcc -Wall -m32 m.o p.c && ./a.out
编译并链接这两个文件时,当Times
的值小于34000000时,它可以正常工作。当它更大时,_myMemcpy
只将源的最后一个字节复制到目标。我不知道问题出在哪里。每一个建议都是有用的。
您正在对指针进行签名比较;不要那样做。在这种情况下使用jne
,因为您将始终在出口点达到完全相等
或者,如果您想要用指针进行关系比较,通常像jb
和jae
这样的无符号条件最有意义。(将虚拟地址空间视为平坦的线性4GiB是正常的,最低地址为0,因此需要在该范围的中间进行增量才能工作(。
对于大于300iB大小的数组,以及PIE可执行文件的默认链接器脚本,显然其中一个数组将跨越带正负号的2GiB边界1。所以你计算的结束指针将是";否定的";如果你把它当作一个有符号的整数。(与x86-64不同,在x86-64中,跨越虚拟地址空间中间的非规范"洞"意味着数组永远无法跨越有符号环绕边界:指针比较在64位x86中应该是有符号的还是无符号的?-有时在那里使用有符号比较是有意义的。(
如果您单步执行并查看指针值,以及使用size += dest
(add [esp + 12], eax
(创建的内存值,则应该使用调试器看到这一点。作为一个带符号的操作,它溢出以创建一个负的end_pointer,而起始指针仍然是正的。pos < neg
在第一次迭代中为false,因此循环退出,您可以在单步执行时看到这一点。
脚注1:在我的系统上,在GDB(禁用ASLR(下,在start
将可执行文件映射到Linux的PIE默认基本地址(地址空间的下半部分的2/3,即0x5555…(之后,我用您的测试用例检查了地址:
sr
在0x56559040
ds
在0x6a998d40
ds
在p /x sizeof(ds) + ds
处结束=0x7edd8a40
因此,如果它大得多,它将穿过0x80000000
。这就是为什么340000000
避免了你的错误,但更大的尺寸会暴露它
顺便说一句,在32位内核下,Linux默认内核和用户空间之间的地址空间为3:1,所以即使在那里也有可能发生这种情况。但在64位内核下,32位进程可以拥有整个4GiB的地址空间。(除了内核保留的一两个页面:另请参阅为什么在64位内核上的32位Linux进程中可以';t I mmap(MAP_FIXED(最高的虚拟页面?(?。这也意味着,像你正在做的那样(ISO C承诺这样做是有效的(,形成一个指向任何数组的过去一端的指针,不会换行,并且仍然会在指针上方与对象进行比较。(
在64位模式下不会发生这种情况:有足够的地址空间在用户和内核之间均匀分配,并且在高和低范围之间存在一个巨大的非规范漏洞。