如何在x86程序集中正确交换2个字节?

问题描述 投票:0回答:3

我有一个缓冲区,其中包含:'bac\n',我正在尝试交换字母'b''a'

我检查了调试器,它在ebp指向的地址打印了4个字节:'bac\n'

  1. ebp - 缓冲区的地址
  2. eax - 偏移量(当前为0),以便ebp + eax指向缓冲区中的'b'
  3. ebx - 包含'b'
  4. edi - 包含'a'

问题是当我运行应该用'b'覆盖缓冲区中的'a'的指令时:

mov [ebp + eax], edi

...然后当我打印缓冲区时,它现在包含:'ac\n''b'在哪里?如果我运行下一个应该用'a'覆盖缓冲区中的'b'的指令,那么完成交换:

mov  [ebp + eax + 1], ebx

...然后缓冲区现在包含:'abac'而不是'abc\n'

任何人都可以解释这里发生了什么?

assembly syntax x86 swap
3个回答
2
投票

我想你之前已经复制了整个32位寄存器,在你没有显示的部分

; copy 4 bytes from ebp + eax to ebp + eax + 3
mov ebx, [ebp + eax]     ; ebx = 'bac\n'
mov edi, [ebp + eax + 1] ; edi = 'ac\n<garbage>', which is ebp + eax + 1 to ebp + eax + 4

因此将字符移回内存后

mov [ebp + eax], edi     ; the string now becomes 'ac\n<garbage>'
mov [ebp + eax + 1], ebx ;                        'abac\n' (5 bytes)

这就是你所看到的。您必须只复制一个字节,而不是一个双字。但DI没有相应的字节寄存器名称,因此您应该将寄存器使用重新排列到低字节寄存器,例如CL / BL,例如

mov  bl, [ebp + eax]
xchg [ebp + eax + 1], bl ; simple, but not efficient
mov  [ebp + eax], bl

如果你没有剩余的免费注册,你将不得不使用按位操作

如果你使用x86_64那么DI的低部分可以被评估为DIL,但是它会长一个字节


1
投票

OP的关键问题是EDI只能作为32位值移动到内存。他需要一个可以作为8位值移动的寄存器,正如Luu建议的那样;这将是AH,AL,BH,....... AL最容易使用,它是EAX的“组成部分”。

如果OP修改他的代码以便EDI包含偏移量,并且EAX包含该字符,那么遵循Luu的建议会很容易。然后存储一个字节所需的指令是

mov byte ptr [ebp+edi],al

“byte ptr”告诉汇编器你期望移动8而不是32位;这在技术上是不必要的,因为AL的使用清楚地表明只使用8位,但它对读者有帮助。


1
投票

您可以使用16位rotate-by8交换内存中的两个字节:

rol  word ptr [ebp], 8     ;  byte [ebp] becomes byte [ebp+1], and vice verse

但是如果你已经在寄存器中有字节(例如因为你加载它们以便你可以比较它们),那么最好从寄存器中存储它们。

由于您只需要存储寄存器的低字节,您需要使用albl的字节存储而不是edi的双字存储!更改寄存器分配,以便在AL,BL,CL或DL之一中使用字节,而不是在EDI的低字节中。只有x86-64才能使EDI的低字节可访问(如DIL)。使用EDI作为索引。 (然后将EDI命名为Destination Index)。或者代替base + index,使用指针增量,因此EDI指向您可能想要交换的当前字节或字节对。

从而:

     movzx    eax, byte ptr [edi + ebp]      ; load the 1st byte
     movzx    edx, byte ptr [edi + ebp + 1]  ; load the 2nd byte
     cmp      al, dl
     jae   noswap

     mov      [edi+ebp],   dl        ; opposite of how you loaded them
     mov      [edi+ebp+1], al
noswap:
     inc      edi

     ... loop logic

movzx避免了对不使用整个EAX分别重命名AL的CPU上的EAX旧值的错误依赖。如果你已经完成了mov al, [edi + ebp],一些CPU会将旧的EAX值作为该指令的另一个输入依赖项。

请注意,如果您实际实现冒泡排序(eww,yuck),则每次迭代只需要执行一次加载。您总是要在寄存器中比较两个值中的一个。您可以使用循环外的负载设置第一次迭代。


如果您正在进行16位加载,然后仅比较低字节(例如,作为排序的一部分),那么您可以完美地设置为交换低2字节的ebx并将其存储回来:

    movzx   eax,  word ptr [edi+ebp]
    cmp     ah, al       ; compare the low 2 bytes of EAX with each other
    jae    noswap

    rol     ax, 8        ; swap AL with AH.  This is more efficient than xchg al,ah or two MOV stores.
    mov     [edi+ebp], ax
noswap:

这本身就很好,但是与下一个16位负载部分重叠的16位存储有点糟糕。 (商店转发摊位)。只将新字节加载到AH中(将旧字节保存在AL中)也不是很好;当在现代Intel CPU上读取AH时,这将停止合并循环,并创建依赖链。

为什么我使用[edi+ebp]而不是[ebp+edi]?它保存一个字节以使EBP成为索引寄存器,因为没有位移的[EBP + EDI*1]不可编码。 NASM和YASM不会替换你,因为base = EBP意味着SS段。但假设平坦的内存模型并不重要,因此我们可以手动进行优化。


有关:

32位qazxsw poi元素的冒泡排序。 qazxsw poi

BubbleSort的8位元素,19字节的16位x86机器代码:int,以及JumpDown类型的32位元素。

Bubble sort in x86 (masm32), the sort I wrote doesn't work(对字符串中的字符进行排序)

https://codegolf.stackexchange.com/questions/77836/sort-an-integer-list/149038#149038

一旦了解了x86部分寄存器的工作原理,在8位与32位元素之间进行更改只需更改寄存器名称并将增量从1更改为4。

© www.soinside.com 2019 - 2024. All rights reserved.