【问题标题】:What's the most concise way to reverse a string using x86 or x86_64 assembly?使用 x86 或 x86_64 程序集反转字符串的最简洁方法是什么?
【发布时间】:2019-12-27 04:48:53
【问题描述】:

我希望用尽可能少的汇编代码来反转字符串。

由于缺乏 Unicorn 支持,我只能使用 SSSE3 扩展或更少。我试过访问 ymm & zmm 指令,但每次都会中断。

尽管 SSSE3 指令更简洁,但用于字节反转 128 位 XMM 寄存器的 16 字节 pshufb 控制向量仍然占用 16 字节并使其更长。我愿意接受任何想法,但以下是我的最佳尝试。

我需要 32 个字节或更少,越小越好。到目前为止,我得到的最好的是 42,但那是我假设 rdx 中字符串的大小(或者 ecx,如果使用 x86) 是 30。

理想情况下,它可以通过检查空终止符来动态获取大小。

字符串地址位于 rdx 内部(如果使用 x86,则为 ecx)。

附加限制:不使用堆栈空间。此代码块必须在没有 RSP 指向可用堆栈内存的情况下运行。


标准 x86 / 64 - 42 字节

; get values in registers
mov rax, [rdx]            
mov rbx, [rdx + 8]
mov rcx, [rdx + 16]
mov r8, [rdx + 24]

; swap bytes around
bswap rax
bswap rbx
bswap rcx
bswap r8

; shift it right by 2 because of the nulls
sar r8, 16

; put it back
mov [rdx], r8
mov [rdx + 0x6], rcx
mov [rdx + 0xE], rbx
mov [rdx + 0x16], rax

SSE3 - 62字节(因为是字节数组,否则是46)

movdqu xmm3, [rip + 0x27]
movdqu xmm0, [rdx]
movdqu xmm1, [rdx] + 0x10
pshufb xmm0,xmm3
pshufb xmm1,xmm3


movdqu [rdx], xmm1
movdqu xmm1, [rdx+0x2]
movdqu [rdx], xmm1
movdqu [rdx+0xE], xmm0
hlt

; this would be tacked on to the end of the assembly as the rip + 0x27 value 
\x00\x0F\x0E\x0D\x0C\x0B\x0A\x09\x08\x07\x06\x05\x04\x03\x02\x01

【问题讨论】:

  • 如果您优化最小代码大小而不考虑性能,标量字节循环几乎肯定是最好的(可能使用lodsbstosb 用于前向加载/存储)。如果codegolf.stackexchange.com 上已经有一个有效的 x86 机器代码答案,我不会感到惊讶,您可以将其用于字符串反转问题。另见Tips for golfing in x86/x64 machine code。但是,考虑到您可以选择使用 64 位操作数大小的完全展开或 SSSE3,您可能正在寻找大小和性能的平衡点。
  • 这是一个 32 字节的 x86-64 void strrev(char* p),它适用于任何字符串长度 except 0 并遵守 System V ABI。如果这是 x86,它将使用更少的字节。也许您可以弄清楚如何以某种方式在其中塞入空字符串测试。
  • @IwillnotexistIdonotexist:这看起来像是一个答案;继续并发布它IMO。提示:2x dec reg 仅适用于具有 1 字节 dec 的 32 位代码,而不是具有 3 字节 dec r64 的 64 位代码。 sub rdi, 2 会更好,长度与std/scasw/cld 相同。通过节省 2 个字节,您将有空间让 JRCXZ 跳过循环。此外,您可以删除 not ecx / shr ecx,1 上的 REX 前缀以假设字符串长度小于 4GiB。
  • @PeterCordes 我最后没有使用jrcxz,而是通过避免计算~strlen() >> 1节省了几个字节,并用指针比较src <= dst替换了循环测试。
  • @IwillnotexistIdonotexist:你可以用push/pop而不是mov复制一个2字节的64位寄存器。 push/pop 的默认操作数大小为 64 位,与 REX 前缀无关。

标签: assembly x86-64 micro-optimization code-size


【解决方案1】:

void strrev(char* p) 的以下 31 个字节的 x86-64 汇编代码将就地反转任意长度的字符串(包括空字符串),只使用基本指令集。

但是,该例程需要指向寄存器rdi(与System V ABI 一致)中的字符串的指针,而不是rdxmov rdi, rdx 将花费 3 个字节。另外,由于使用了两个隐式锁定的xchg,性能会很糟糕。

小尺寸部分是由于创造性地使用了单字节stosb/lodsb指令的副作用,分别根据方向标志读取和递增/递减rdirsi,可以通过单字节指令std/cld设置和清除。

如果代码是 x86-32 或可以将自身限制为

0000000000000000 <strrev>:
   0:   31 c0                   xor    eax,eax
   2:   48 8d 48 ff             lea    rcx,[rax-0x1]
   6:   48 89 fe                mov    rsi,rdi
   9:   f2 ae                   repnz scas al,BYTE PTR es:[rdi]
   b:   48 83 ef 02             sub    rdi,0x2
   f:   48 39 f7                cmp    rdi,rsi
  12:   7e 0a                   jle    1e <strrev+0x1e>
  14:   86 07                   xchg   BYTE PTR [rdi],al
  16:   86 06                   xchg   BYTE PTR [rsi],al
  18:   fd                      std
  19:   aa                      stos   BYTE PTR es:[rdi],al
  1a:   fc                      cld
  1b:   ac                      lods   al,BYTE PTR ds:[rsi]
  1c:   eb f1                   jmp    f <strrev+0xf>
  1e:   c3                      ret

【讨论】:

    【解决方案2】:

    反转字符串的最简洁方法是将“字符串”定义为一个 1 字节的“方向和长度”字节,后跟最多 127 个字节的字符。这允许您使用单个 neg byte [rdx] 指令(仅花费 2 个字节!)来反转字符串。

    示例(针对 NASM):

    myString:
        db myString.end - myString.start
    .start:
        db "Hello World!"
    .end:
    
    ;Reverse a string
    ;
    ;Input
    ; rdx   Address of string to reverse
    
    reverseString:
        neg byte [rdx]
        ret
    

    当然,您必须编写其他例程来处理这种字符串格式。例如:

    ;Print a string
    ;
    ;Input
    ; rsi   Address of string to print
    
    printString:
        movsz rcx,byte [rsi]    ;rcx = "direction and length" value
        inc rsi
        cmp rcx,0
        jg .l1
        je .done
        std
        neg rcx
    .l1:
        lodsb
        call printChar          ;Print the character in AL
        loop .l1
        cld
    .done:
        ret
    
    ;Get length of a string (in bytes)
    ;
    ;Input
    ; rsi   Address of string
    ;
    ;Output
    ; rcx   Length of string
    
    getStringLength:
        movsz rcx,byte [rsi]    ;rcx = "direction and length" value
        cmp rcx,0
        jge .l1
        neg rcx
    .l1:
        ret
    

    【讨论】:

    • 我没有找到任何示例。你以前做过吗?如果是这样,这通常是如何布局的?
    • @kr1tzb1tz:我从来不需要反转字符串。无论如何添加了一些示例代码.. :-)
    猜你喜欢
    • 2011-04-10
    • 2010-10-05
    • 1970-01-01
    • 2020-03-21
    • 1970-01-01
    • 1970-01-01
    • 2021-03-16
    • 1970-01-01
    • 2021-06-25
    相关资源
    最近更新 更多