在 gcc 中访问内联汇编中的字符串的地址

Posted

技术标签:

【中文标题】在 gcc 中访问内联汇编中的字符串的地址【英文标题】:Accessing address of a string in inline assembly in gcc 【发布时间】:2014-11-05 16:07:05 【问题描述】:

我已经编写了下面的汇编代码来将字符串从小写转换为大写,它不能完全工作,因为我无法访问我正在转换的字符串的地址。这段代码为什么不起作用?

  #include<stdio.h>
  int convert(char *str)
  
       char *ptr;
  __asm__ __volatile__ ( "movl (%1),%%ebx;"
                    "subl $1,%%ebx;"
                    "movl %%ebx,%0;"
            "REPEAT: addl $1,%%ebx;"
                    "testl %%ebx,%%ebx;"
                    "je END;"
                    "movzbl 0(%%ebx),%%ecx;"
                    "cmpl $97, %%ecx;"
                    "jb END;"
                    "cmpl $122,%%ecx;"
                    "ja END;"
                    "subb $32,0(%%ebx);"
                    "jmp REPEAT;"
              "END: movl %%ebx,(%0);"
                    :"=r" (ptr)
                    :"r"  (str)
                 );
   printf("converted string =%s\n", str);
 

  int main()
  
  int i;  
  char str[] = "convert";

  i = convert(str);
  return 0;

  

【问题讨论】:

您的问题是什么?请提出问题。 @FUZxxi:我无法访问字符串的地址,上面的代码不起作用! 您在什么时候分配给ptrstr str 是我的输入,所以是 %1,我希望 str 的地址在 ebx 中,最终输出在 ptr 中,基本上 ptr 只是为了输出目的。我想在 str 本身中将字符更改为大写。 那是因为你在哪里编译 amd64,而不是 i386。在 amd64 上,指针的大小为 64 位。当然,movl %rdi,%ebx 不是有效指令。您可能希望使用 cc -m32 编译 i386。 【参考方案1】:

接受的答案中的代码似乎有一些问题:

正如所写,此代码无法编译(当只有 1 个参数时它引用 %1),并且在第 4 个 asm 行缺少终止符。 此代码无法正确处理“aBc”等字符串。 此代码不使用“内存”破坏器,即使它修改了内存。 此代码(仍然)修改未破坏的寄存器 (ebx)。 不适用于 x64。

不如这样:

char *convert(char *str)

   char *res = str;
   char temp;

   __asm__ __volatile__ (
         "dec %[str]\n"
      "REPEAT:\n\t"    
         "inc %[str]\n\t"
         "movb (%[str]), %[temp]\n\t"  /* Read the next char */
         "testb %[temp], %[temp]\n\t"
         "jz END\n\t"                  /* Is the char null */
         "cmpb $97, %[temp]\n\t"       /* >= 'a'? */
         "jb REPEAT\n\t"
         "cmpb $122, %[temp]\n\t"      /* <= 'z'? */
         "ja REPEAT\n\t"
         "subb $32, %[temp]\n\t"       /* Perform lowercase */
         "mov %[temp], (%[str])\n\t"   /* Write back to memory */
         "jmp REPEAT\n" 
      "END:\n"
         : [str] "+r" (str), [temp] "=r" (temp)
         : /* no inputs */
         : "memory"
   );

   /* Note that at this point, str points to the null. 
      str - res is the length. */

   return res;

这段代码:

使用更少的寄存器(2 对 4)。 通过使用“=r”(temp),我们让编译器选择最佳寄存器用于暂存,而不是强制使用特定寄存器。 只读取内存一次(而不是两次)。 返回一个指向字符串的指针(而不是什么都不返回?)。 IMO,使用 %[temp] 和 %[src] 比 %1 更容易阅读。 使用\n\t(而不是;)使gcc -S 的输出更易于阅读。 此代码修改了 str(这就是它被列为“+r”的原因)。

或者如果你真的想花哨,把代码写在'c'中,然后使用gcc -O2 -S查看输出。

【讨论】:

【参考方案2】:

这是我的解决方案与上面的略有不同,感谢 FUZxxi 指出。我应该说检索程序集在很大程度上有帮助,它可能很难理解,但它给你带来了实际问题。如果有人想了解我想要实现的目标,我已经写了足够多的 cmets。

/* code to convert from lower case to upper case */
int convert(char *str)

   __asm__ __volatile__ ( "movl %1,%%ebx;"  // Get the address of str
                "subl $1,%%ebx;"     
        "REPEAT: addl $1,%%ebx;"    
                "movl 0(%%ebx),%%edx"  // Move the contents to edx
                "movzbl %%dl,%%ecx;"   // moving last character to ecx
                "testl %%ecx,%%ecx;"   // compare if it's null
                "je END;"              
                "cmpl $97, %%ecx;"     
                "jb END;"
                "cmpl $122,%%ecx;"
                "ja END;"
                "subb $32,(%%ebx);"  // if its lower case, subtract 32
                "jmp REPEAT;" 
          "END:;"
                :           // No output specified
                :"r"  (str) //input
                :"ecx","edx" //clobbers
             );
  printf("converted string =%s\n", str);

如果您使用“gcc -m32”选项编译,如果您正在为 amd64 编译,上述代码应该可以工作。我遇到了一个问题“致命错误:sys/cdefs.h:没有这样的文件或目录”

解决方法:安装这个包:libc6-dev-i386

【讨论】:

这个解决方案不是一个高效的解决方案,如果您想快速完成(主要是我们想要的),请使用矢量指令。

以上是关于在 gcc 中访问内联汇编中的字符串的地址的主要内容,如果未能解决你的问题,请参考以下文章

在gcc内联汇编中,双百分号(%%)有什么作用?

gcc 内联汇编中的 min

GCC 内联汇编中的标签

GCC内联汇编中的C数组?

ARM嵌入式开发中的GCC内联汇编__asm__

这个 GCC 内联汇编中的参数列表有啥问题?