循环看起来不错,但很笨重。如果您的程序崩溃,请使用调试器。有关链接,请参阅x86和 asm 的 gdb 快速介绍。
我认为您argv[1]
已正确加载。(请注意,这是第一个命令行参数。 argv[0]
是命令名称。) https://en.wikibooks.org/wiki/X86_Disassembly/Functions_and_Stack_Frames说这ebp+12
是第二个参数到 32 位函数的常见位置设置堆栈帧。
Michael Petch 评论了 Simon 删除的答案,即 asm_io 库具有 print_int、print_string、print_char 和 print_nl 例程等。因此,大概您是指向其中一个函数的缓冲区的指针,并称其为一天。或者您可以sys_write(2)
直接使用int 0x80
指令调用,因为您不需要进行任何字符串格式化并且您已经有了长度。
您可以对两个数组使用相同的索引,而不是为两个数组单独递增,并为负载使用索引寻址模式。
;; main (int argc ([esp+4]), char **argv ([esp+8]))
... some code you didn't show that I guess pushes some more stuff on the stack
mov eax, dword [ebp+12] ; load argv
;; eax + 4 is &argv[1], the address of the 1st cmdline arg (not counting the command name)
mov esi, dword [eax + 4] ; esi holds 2nd arg, which is pointer to string
xor ecx, ecx
copystring:
mov al, [esi + ecx]
mov [X + ecx], al
inc ecx
test al, al
jnz copystring
我将注释更改为“cmdline arg”,以区分这些和“函数参数”。
当它不需要任何额外的指令时,使用esi
源指针,edi
目标指针,以提高可读性。
检查 ABI 哪些寄存器可以在不保存/恢复的情况下使用(至少 eax、ecx 和 edx。这可能是 32 位 x86 的全部内容。)。如果您想使用其他寄存器,则必须保存/恢复它们。至少,如果您正在制作遵循通常 ABI 的功能。在 asm 中你可以做你喜欢的事,只要你不告诉 C 编译器调用非标准函数。
还要注意循环结束时的改进。单个jnz
to 循环比jz break / jmp
.
这应该在 Intel 上以每个字节一个周期运行,因为test/jnz
宏融合到一个 uop 中。负载为 1 uop,存储微熔为 1 uop。 inc
也是一个uop。自 Core2 以来的英特尔 CPU 为 4 宽:每个时钟发出 4 微指令。
您的原始循环以该速度的一半运行。由于它是 6 微指令,因此发出一个迭代需要 2 个时钟周期。
另一种解决方法是获取另一个寄存器之间的偏移量X
,ebx
因此其中一个有效地址可以使用单寄存器寻址模式,即使 dest 不是静态数组。
mov [X + ebx + ecx], al
. (其中 ecx = X - start_of_src_buf)。但理想情况下,您应该使存储成为使用单寄存器寻址模式的存储,除非加载是 ALU 指令的内存操作数,可以对其进行微融合。在 dest 是静态缓冲区的情况下,这种地址不同的 hack 根本没有用。
您不能使用rep
字符串指令(如rep movsb
)为隐式长度字符串实现 strcpy(C 以 null 结尾,而不是使用单独存储的长度)。好吧,您可以,但只扫描源两次:一次用于查找长度,再次用于 memcpy。
为了比一个字节时钟快,您必须使用向量指令来并行测试 16 个位置中的任何一个处的空字节。例如,谷歌优化了 strcpy 实现。可能使用pcmpeqb
全零向量寄存器。