首页 > 解决方案 > 如何将 64 位函数转换为 32 位到 16 位到 8 位?

问题描述

我是汇编新手,我不知道如何将 64 位函数转换为 32 位到 16 位到 8 位

以下函数的目的是打印数字并返回其中的位数。

64位:

          global print_uint64
    section .text
        print_uint64:

          mov rax,rdi
          mov rdi,10
          mov rsi,rsp

       while:
          xor  rdx  ,rdx
          div  rdi
          add  rdx  ,48
          dec  rsi
          mov  [rsi],dl
          cmp  rax  ,0
          jne  while 

          mov rax,1
          mov rdi,1
          lea rdx,[rsp]
          sub rdx,rsi
          syscall

          lea rax,[rsp]
          sub rax,rsi
          ret

this works fine 

32位:

      global print_uint32
section .text
    print_uint32:

      mov eax,edi
      mov edi,10
      mov rsi,rsp

   while:
      xor  edx  ,edx
      div  edi
      add  edx  ,48
      dec  rsi
      mov  [rsi],dl
      cmp  eax  ,0
      jne  while 

      mov eax,1
      mov edi,1
      lea edx,[rsp]
      sub edx,esi
      syscall

      lea eax,[rsp]
      sub eax,esi
      ret

这很好用

16 位:

      global print_uint16
section .text
    print_uint16:

      mov ax,di
      mov di,10
      mov rsi,rsp

   while:
      xor  dx  ,dx
      div  di
      add  dx  ,48
      dec  rsi
      mov  [rsi],dl
      cmp  ax  ,0
      jne  while 

      mov ax,1
      mov di,1
      lea dx,[rsp]
      sub dx,si
      syscall

      lea ax,[rsp]
      sub ax,si
      ret

但这没有用

我研究了关于这个问题的堆栈溢出的一些问题,我的理解是我不能将 rsp 更改为 esp,因为 esp 将高 32 位设置为零,所以当我们在访问时使用 [] 时,没有分配给该程序的内存所以它引发段错误。

我的问题是:

1)64位到32位到16位到8位转换的基本规则是什么。

标签: assemblyx86-64

解决方案


基本规则是,无论数据宽度如何,指针仍然是 64 位的。就像在 C 中一样,sizeof(int*)并且sizeof(char*)是相同的(在普通系统上)。

这就是为什么您的所有版本都必须使用dec rsiand mov [rsi],dl:RSP 拥有一个 64 位指针。将其截断为 32 位不会产生有效的指针。

此外,系统调用编号和fd仍然是相同的大小;mov ax,1并将mov di,1垃圾留在寄存器的高字节中。用于strace ./my_program解码您实际传递给syscall.


窄版本可以将其输入零扩展为 32 位并跳转到 32 位版本。

但除此之外,基本规则是尽可能使用 32 位操作数大小;这是 x86-64 的自然大小(在 x86-64 中使用 32 位寄存器/指令的优点)。例如,RDX/EDX/DX 始终为零,带有xor edx,edx.

写入 32 位寄存器零扩展为 64 位,与 8/16 不同,它只是合并到旧值中。
为什么 32 位寄存器上的 x86-64 指令会将完整 64 位寄存器的上部归零?使用 16 位mov reg,imm16并留下大量垃圾可能是您的系统调用不起作用的原因。
为什么 GCC 不使用部分寄存器?

值得注意的是,lea dx,[rsp]/sub dx,si可能会在 RDX 的高位(即write系统调用的 arg)中留下垃圾。

这是一个指针减法,计算char缓冲区中的元素数。根据输入数字的大小为此选择操作数大小是没有意义的。只要确保结果为零扩展到 RDX,实际上就可以进行窄减,因为在这种情况下,您知道位数最多为 19(对于 64 位版本),因为那是多长时间2^64-1 以 10 为底。

所以mov edx, esp/sub edx, esi是你在所有版本中应该做的。由于完整的 RSP 和 RSI 就在附近,因此它们的差异很小。在减法之前截断输入而不是在减法之后截断结果不会改变结果;进位从低位传播到高位。如果只需要结果的低部分,请参阅哪些 2 的补码整数运算可以在不将输入中的高位归零的情况下使用?

使用 LEA 复制寄存器效率不高; lea dx, [rsp]架构上相同,mov dx, sp但速度较慢。


推荐阅读