gpt4 book ai didi

loops - 如何增加寄存器中的每个字节? (64位,Linux,NASM)

转载 作者:行者123 更新时间:2023-12-01 23:36:38 25 4
gpt4 key购买 nike

我最近开始学习汇编,并且为自己建立了一个小项目。目标是使用循环。我想将0x414141移至RAX,然后在RAX上循环,并递增每个字节,以便RAX在代码末尾包含0x424242。

我曾尝试增加字节rax,但是在尝试编译时总是会从NASM收到错误消息。当前,我有最后的工作代码,它将使RAX递增等于0x414144。我似乎找不到任何看起来/听起来与我想做的接近的东西。 (但是有多难,对吧?)

global _start

section .text
_start:
mov rax, 0x414141
mov rcx, 3
strLoop:
inc rax
loop strLoop

mov rax, 60
mov rdi, 0
syscall
; ^ exit

当我在GDB中查看RAX时,在这段代码中,我希望它是0x414144。但是,我想使代码达到0x424242的水平,我想这将是该项目的预期结果。

最佳答案

像通常的asm一样,有很多好的方法来实现您想要的。最重要的问题是字节之间的进位传播是否可能是一个问题。

选项1(带进位传播的简单加法)

如果只关心64位RAX的低4字节,则可能只应将EAX用于32位操作数大小。 (写32位寄存器零扩展到完整的64位寄存器中,这不同于您写入8位或16位寄存器时的情况。)

因此,正如评论中提到的,这是对您的问题的一种解释的窍门。

 add   eax, 0x010101

如果您确实想要RAX的每个字节,则为8个字节。但是,只有 mov支持64位立即数,而不支持 add。您可以在另一个寄存器中创建一个常量:
 mov   rdx, 0x0101010101010101
add rax, rdx

上面使用单个宽 add的方法的缺点是 某个字节中的溢出会传播到下一个较高的。因此,这并不是真正的4或8个独立字节的加法运算,除非您知道每个单独的字节都不会溢出并进入下一个字节。 (即 SWAR)

例如:如果您有 eax = 0x010101FF并从上面添加常数,则不会得到 0x02020200,而是 0x02020300(最低有效字节溢出到第二最低有效字节中)。

选项2(无进位传播的循环)

由于您指示使用 来表示问题来解决您的问题,因此可能的方法也只需要两个寄存器,它是:
[global func]
func:
mov rax, 0x4141414141414141

mov rcx, 8
.func_loop: ; NASM local .label is good style within a function
inc al ; modify low byte of RAX without affecting others
rol rax, 8
dec rcx
jne .func_loop
; RAX has been rotated 8 times, back to its original layout

ret

这将增加rax的最低有效字节(不影响rax的其他位),然后将rax向左旋转8位,然后重复。

您可以旋转16位(4次)并执行
inc ah           ; doing AH first happens to be better with Skylake's partial-register handling: inc al can run in parallel with this once AH is already renamed separately.
inc al
rol rax, 16

作为循环体,但修改AH通常比修改AL更为worse for partial-register slowdowns,尽管它可以减少Ryzen之类的CPU的开销,这些CPU不会将RAX单独重命名为AH。 (有趣的事实:在Skylake上,此延迟在inc al; inc ah顺序较慢的情况下可以达到收支平衡,因为inc ah直到inc al之后才能启动,因为modern Intel CPUs don't rename the low-8 partial registers与完整reg分开,只有高8。

请注意,loop指令在Intel CPU上为slow,在功能上与此等效(但不修改标志):
dec rcx
jne func_loop

还要注意,在某些系统上执行add al, 1实际上可能比执行inc al稍快一些,如here所述。

(编者注:除了rol以外的其他1只需要修改CF,而inc / dec只需修改其他标记(SPAZO)。因此,如果使用部分标记重命名inc / rol / dec不会使inc / rol耦合依赖关系链进入dec循环计数器依赖关系链,并使其变得比需要的慢(在Skylake上进行了测试,实际上对于较大的循环计数,它确实以2个周期/迭代吞吐量运行)。但是dec会是一个问题Silvermont,其中inc / dec确实合并为FLAGS。将其中之一设为subadd会破坏通过FLAGS的依赖链。)

选项3(不带进位传播的SIMD加法)

使用专用的SSE2 SIMD指令
可能是实现此溢出行为的最有效方法是:
default rel        ; use RIP-relative addressing by default

section .rodata
align 16 ; without AVX, 16-byte memory operands must be aligned
vec1: times 8 db 0x01
dq 0

section .text
[global func]
func:
mov rax, 0x4141414141414141

movq xmm0, rax
paddb xmm0, [vec1] ; packed-integer add of byte elements
movq rax, xmm0

ret

这会将rax的值移到xmm0的下半部分,对预定义的常数(按字节顺序添加128位长,但高64位与我们无关,因此为零)进行字节加法,然后将结果写回再次输入rax

输出符合预期:rax = 0x01010101010101FF产生0x0202020202020200(最低有效字节溢出)。

请注意,通过整数添加(而不是mov -immediate)也可以使用内存中的常量。

MMX只允许使用8字节的内存操作数,但是在返回之前,您需要EMMS; x86-64 System V ABI指定FPU在 call /重拨时应处于x87模式。

您可以使用一种技巧而不是从内存中加载常量来动态生成它。用pcmpeqd xmm1, xmm1生成一个全 vector 是有效的。但是如何使用它来添加1呢? SIMD右移仅适用于word(16位)或更大的元素,因此需要几个指令将其转换为0x0101...的 vector 。 Or SSSE3 pabsb

诀窍是,添加1与减去-1相同,而全1是二进制补码-1
    movq     xmm0, rax
pcmpeqd xmm1, xmm1 ; set1( -1 )
psubb xmm0, xmm1 ; packed-integer sub of (-1) byte elements
movq rax, xmm0

请注意,SSE2还具有用于使加法和减法饱和的指令,其中 paddsb psubsb表示带符号的饱和度, paddusb psubusb表示无符号的饱和度。 (对于无符号饱和度,您不能使用减-1技巧;它总是会饱和为0,而不是回绕到原始值之上的1。)

关于loops - 如何增加寄存器中的每个字节? (64位,Linux,NASM),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57648305/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com