首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >如何在GCC内嵌组件中使用标签?

如何在GCC内嵌组件中使用标签?
EN

Stack Overflow用户
提问于 2017-03-08 20:53:06
回答 3查看 7.5K关注 0票数 3

我正在尝试学习x86-64内联程序集,并决定实现这个非常简单的交换方法,该方法简单地按升序对ab进行排序:

代码语言:javascript
运行
复制
#include <stdio.h>

void swap(int* a, int* b)
{
    asm(".intel_syntax noprefix");
    asm("mov    eax, DWORD PTR [rdi]");
    asm("mov    ebx, DWORD PTR [rsi]");
    asm("cmp    eax, ebx");
    asm("jle    .L1");
    asm("mov    DWORD PTR [rdi], ebx");
    asm("mov    DWORD PTR [rsi], eax");
    asm(".L1:");
    asm(".att_syntax noprefix");
}

int main()
{
    int input[3];

    scanf("%d%d%d", &input[0], &input[1], &input[2]);

    swap(&input[0], &input[1]);
    swap(&input[1], &input[2]);
    swap(&input[0], &input[1]);

    printf("%d %d %d\n", input[0], input[1], input[2]);

    return 0;
}

当我使用以下命令运行它时,上面的代码就像预期的那样工作:

代码语言:javascript
运行
复制
> gcc main.c
> ./a.out
> 3 2 1
> 1 2 3

但是,一旦启动优化,就会收到以下错误消息:

代码语言:javascript
运行
复制
> gcc -O2 main.c
> main.c: Assembler messages:
> main.c:12: Error: symbol `.L1' is already defined
> main.c:12: Error: symbol `.L1' is already defined
> main.c:12: Error: symbol `.L1' is already defined

如果我正确理解它,这是因为gcc试图在打开优化时内联我的swap函数,导致在程序集文件中多次定义标签.L1

我试图找出这个问题的答案,但似乎没有什么效果。在这个先前提出的问题中,建议使用本地标签,我也尝试过这样做:

代码语言:javascript
运行
复制
#include <stdio.h>

void swap(int* a, int* b)
{
    asm(".intel_syntax noprefix");
    asm("mov    eax, DWORD PTR [rdi]");
    asm("mov    ebx, DWORD PTR [rsi]");
    asm("cmp    eax, ebx");
    asm("jle    1f");
    asm("mov    DWORD PTR [rdi], ebx");
    asm("mov    DWORD PTR [rsi], eax");
    asm("1:");
    asm(".att_syntax noprefix");
}

但是,当尝试运行程序时,我现在得到了一个分段错误:

代码语言:javascript
运行
复制
> gcc -O2 main.c
> ./a.out
> 3 2 1
> Segmentation fault

我还尝试了对这个先前提出的问题的建议解决方案,并将名称.L1更改为CustomLabel1,以防出现名称冲突,但它仍然给我带来了旧的错误:

代码语言:javascript
运行
复制
> gcc -O2 main.c
> main.c: Assembler messages:
> main.c:12: Error: symbol `CustomLabel1' is already defined
> main.c:12: Error: symbol `CustomLabel1' is already defined
> main.c:12: Error: symbol `CustomLabel1' is already defined

最后,我也尝试了这一建议

代码语言:javascript
运行
复制
void swap(int* a, int* b)
{
    asm(".intel_syntax noprefix");
    asm("mov    eax, DWORD PTR [rdi]");
    asm("mov    ebx, DWORD PTR [rsi]");
    asm("cmp    eax, ebx");
    asm("jle    label%=");
    asm("mov    DWORD PTR [rdi], ebx");
    asm("mov    DWORD PTR [rsi], eax");
    asm("label%=:");
    asm(".att_syntax noprefix");
}

但我却得到了这些错误:

代码语言:javascript
运行
复制
main.c: Assembler messages:
main.c:9: Error: invalid character '=' in operand 1
main.c:12: Error: invalid character '%' in mnemonic
main.c:9: Error: invalid character '=' in operand 1
main.c:12: Error: invalid character '%' in mnemonic
main.c:9: Error: invalid character '=' in operand 1
main.c:12: Error: invalid character '%' in mnemonic
main.c:9: Error: invalid character '=' in operand 1
main.c:12: Error: invalid character '%' in mnemonic

所以,我的问题是:

如何在内联程序集中使用标签?

这是优化版本的反汇编输出:

代码语言:javascript
运行
复制
> gcc -O2 -S main.c

    .file   "main.c"
    .section    .text.unlikely,"ax",@progbits
.LCOLDB0:
    .text
.LHOTB0:
    .p2align 4,,15
    .globl  swap
    .type   swap, @function
swap:
.LFB23:
    .cfi_startproc
#APP
# 5 "main.c" 1
    .intel_syntax noprefix
# 0 "" 2
# 6 "main.c" 1
    mov eax, DWORD PTR [rdi]
# 0 "" 2
# 7 "main.c" 1
    mov ebx, DWORD PTR [rsi]
# 0 "" 2
# 8 "main.c" 1
    cmp eax, ebx
# 0 "" 2
# 9 "main.c" 1
    jle 1f
# 0 "" 2
# 10 "main.c" 1
    mov DWORD PTR [rdi], ebx
# 0 "" 2
# 11 "main.c" 1
    mov DWORD PTR [rsi], eax
# 0 "" 2
# 12 "main.c" 1
    1:
# 0 "" 2
# 13 "main.c" 1
    .att_syntax noprefix
# 0 "" 2
#NO_APP
    ret
    .cfi_endproc
.LFE23:
    .size   swap, .-swap
    .section    .text.unlikely
.LCOLDE0:
    .text
.LHOTE0:
    .section    .rodata.str1.1,"aMS",@progbits,1
.LC1:
    .string "%d%d%d"
.LC2:
    .string "%d %d %d\n"
    .section    .text.unlikely
.LCOLDB3:
    .section    .text.startup,"ax",@progbits
.LHOTB3:
    .p2align 4,,15
    .globl  main
    .type   main, @function
main:
.LFB24:
    .cfi_startproc
    subq    $40, %rsp
    .cfi_def_cfa_offset 48
    movl    $.LC1, %edi
    movq    %fs:40, %rax
    movq    %rax, 24(%rsp)
    xorl    %eax, %eax
    leaq    8(%rsp), %rcx
    leaq    4(%rsp), %rdx
    movq    %rsp, %rsi
    call    __isoc99_scanf
#APP
# 5 "main.c" 1
    .intel_syntax noprefix
# 0 "" 2
# 6 "main.c" 1
    mov eax, DWORD PTR [rdi]
# 0 "" 2
# 7 "main.c" 1
    mov ebx, DWORD PTR [rsi]
# 0 "" 2
# 8 "main.c" 1
    cmp eax, ebx
# 0 "" 2
# 9 "main.c" 1
    jle 1f
# 0 "" 2
# 10 "main.c" 1
    mov DWORD PTR [rdi], ebx
# 0 "" 2
# 11 "main.c" 1
    mov DWORD PTR [rsi], eax
# 0 "" 2
# 12 "main.c" 1
    1:
# 0 "" 2
# 13 "main.c" 1
    .att_syntax noprefix
# 0 "" 2
# 5 "main.c" 1
    .intel_syntax noprefix
# 0 "" 2
# 6 "main.c" 1
    mov eax, DWORD PTR [rdi]
# 0 "" 2
# 7 "main.c" 1
    mov ebx, DWORD PTR [rsi]
# 0 "" 2
# 8 "main.c" 1
    cmp eax, ebx
# 0 "" 2
# 9 "main.c" 1
    jle 1f
# 0 "" 2
# 10 "main.c" 1
    mov DWORD PTR [rdi], ebx
# 0 "" 2
# 11 "main.c" 1
    mov DWORD PTR [rsi], eax
# 0 "" 2
# 12 "main.c" 1
    1:
# 0 "" 2
# 13 "main.c" 1
    .att_syntax noprefix
# 0 "" 2
# 5 "main.c" 1
    .intel_syntax noprefix
# 0 "" 2
# 6 "main.c" 1
    mov eax, DWORD PTR [rdi]
# 0 "" 2
# 7 "main.c" 1
    mov ebx, DWORD PTR [rsi]
# 0 "" 2
# 8 "main.c" 1
    cmp eax, ebx
# 0 "" 2
# 9 "main.c" 1
    jle 1f
# 0 "" 2
# 10 "main.c" 1
    mov DWORD PTR [rdi], ebx
# 0 "" 2
# 11 "main.c" 1
    mov DWORD PTR [rsi], eax
# 0 "" 2
# 12 "main.c" 1
    1:
# 0 "" 2
# 13 "main.c" 1
    .att_syntax noprefix
# 0 "" 2
#NO_APP
    movl    8(%rsp), %r8d
    movl    4(%rsp), %ecx
    movl    $.LC2, %esi
    movl    (%rsp), %edx
    xorl    %eax, %eax
    movl    $1, %edi
    call    __printf_chk
    movq    24(%rsp), %rsi
    xorq    %fs:40, %rsi
    jne .L6
    xorl    %eax, %eax
    addq    $40, %rsp
    .cfi_remember_state
    .cfi_def_cfa_offset 8
    ret
.L6:
    .cfi_restore_state
    call    __stack_chk_fail
    .cfi_endproc
.LFE24:
    .size   main, .-main
    .section    .text.unlikely
.LCOLDE3:
    .section    .text.startup
.LHOTE3:
    .ident  "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609"
    .section    .note.GNU-stack,"",@progbits
EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2017-03-09 03:14:44

有很多教程--包括这一个 (可能是我所知道的最好的),以及一些关于操作数大小修饰符的信息。

下面是第一个实现-- swap_2

代码语言:javascript
运行
复制
void swap_2 (int *a, int *b)
{
    int tmp0, tmp1;

    __asm__ volatile (
        "movl (%0), %k2\n\t" /* %2 (tmp0) = (*a) */
        "movl (%1), %k3\n\t" /* %3 (tmp1) = (*b) */
        "cmpl %k3, %k2\n\t"
        "jle  %=f\n\t"       /* if (%2 <= %3) (at&t!) */
        "movl %k3, (%0)\n\t"
        "movl %k2, (%1)\n\t"
        "%=:\n\t"

        : "+r" (a), "+r" (b), "=r" (tmp0), "=r" (tmp1) :
        : "memory" /* "cc" */ );
}

几个音符

  • volatile (或__volatile__)是必需的,因为编译器只看到(a)(b) (而且不知道您可能正在交换它们的内容),否则就可以自由地优化整个asm语句--否则,tmp0tmp1也会被视为未使用的变量。
  • "+r"意味着这是一个可能被修改的输入和输出;只是在这种情况下它不是,而且它们可以严格地仅是输入-更详细地介绍一下.
  • 'movl‘上的'l’后缀不是真正必要的;寄存器的'k‘(32位)长度修饰符也不是必要的。由于您使用的是Linux (ELF) ABI,对于int和x86-64-abi,IA32都是32位。
  • %=令牌为我们生成一个唯一的标签。顺便说一句,跳转语法<label>f表示向前跳转,<label>b表示后退。
  • 为了正确起见,我们需要"memory",因为编译器无法知道取消引用指针的值是否已被更改。在被C代码包围的更复杂的内联asm中,这可能是一个问题,因为它使内存中所有当前持有的值无效--而且常常是一种大麻烦的方法。以这种方式出现在函数的末尾,这将不是一个问题--但是您可以阅读更多关于它的这里 (参见: Clobbers)。
  • "cc"标志注册器将在同一节中详细说明。在x86上,它什么也不做。为了清晰起见,有些作者将其包括在内,但是由于几乎所有非平凡的asm语句都会影响标志寄存器,因此默认情况下它被假定为失败。

下面是C实现- swap_1

代码语言:javascript
运行
复制
void swap_1 (int *a, int *b)
{
    if (*a > *b)
    {
        int t = *a; *a = *b; *b = t;
    }
}

gcc -O2编译x86-64ELF,我得到了相同的代码.幸运的是,编译器选择了tmp0tmp1来使用相同的临时寄存器.消除噪音,如.cfi指令等,会给出:

代码语言:javascript
运行
复制
swap_2:
        movl (%rdi), %eax
        movl (%rsi), %edx
        cmpl %edx, %eax
        jle  21f
        movl %edx, (%rdi)
        movl %eax, (%rsi)
        21:
        ret

如前所述,swap_1代码是相同的,只是编译器选择了.L1作为跳转标签。用-m32编译代码会生成相同的代码(除了以不同的顺序使用tmp寄存器之外)。有更多的开销,因为IA32 ELF在堆栈上传递参数,而x86-64 ABI分别传递%rdi%rsi中的前两个参数。

(a)(b)仅视为输入- swap_3

代码语言:javascript
运行
复制
void swap_3 (int *a, int *b)
{
    int tmp0, tmp1;

    __asm__ volatile (
        "mov (%[a]), %[x]\n\t" /* x = (*a) */
        "mov (%[b]), %[y]\n\t" /* y = (*b) */
        "cmp %[y], %[x]\n\t"
        "jle  %=f\n\t"         /* if (x <= y) (at&t!) */
        "mov %[y], (%[a])\n\t"
        "mov %[x], (%[b])\n\t"
        "%=:\n\t"

        : [x] "=&r" (tmp0), [y] "=&r" (tmp1)
        : [a] "r" (a), [b] "r" (b) : "memory" /* "cc" */ );
}

我已经删除了'l‘后缀和'k’修饰符,因为它们是不需要的。我还对操作数使用了“符号名”语法,因为它通常有助于提高代码的可读性。

(a)(b)现在确实是只输入寄存器.那么"=&r"语法是什么意思呢?&表示早期的clobber操作数。在这种情况下,在我们使用输入操作数之前可以将值写入,因此编译器必须选择与为输入操作数选择的寄存器不同的寄存器。

再次,编译器生成与swap_1swap_2相同的代码。

我在这个答案上写的比我计划的要多,但正如你所看到的,很难保持对编译器必须知道的所有信息以及每个指令集(ISA)和ABI的特性的了解。

票数 9
EN

Stack Overflow用户

发布于 2017-03-08 21:43:15

您不能就这样将一堆asm语句内联起来。优化器可以根据它知道的约束重新排序、复制和删除它们。(就你的情况而言,它一无所知。)

因此,首先,您应该将asm合并在一起,并具有适当的读/写/重击约束。其次,还有一个特殊的asm goto表单,它将程序集提供给C级标签.

代码语言:javascript
运行
复制
void swap(int *a, int *b) {
    int tmp1, tmp2;
    asm(
        "mov (%2), %0\n"
        "mov (%3), %1\n"
        : "=r" (tmp1), "=r" (tmp2)
        : "r" (a), "r" (b)
        : "memory"   // pointer in register doesn't imply that the pointed-to memory has to be "in sync"
        // or use "m" memory source operands to let the compiler pick the addressing mode
    );
    asm goto(
        "cmp %1, %0\n"
        "jle %l4\n"
        "mov %1, (%2)\n"
        "mov %0, (%3)\n"
        :
        : "r" (tmp1), "r" (tmp2), "r" (a), "r" (b)
        : "cc", "memory"
        : L1
    );
L1:
    return;
}
票数 3
EN

Stack Overflow用户

发布于 2017-03-09 01:32:28

您不能假设值在asm代码中的任何特定寄存器中--您需要使用约束来告诉gcc您想要读写哪些值,并让它告诉您它们在哪个寄存器中。gcc博士告诉你大部分你需要知道的东西,但是非常密集。还有一些教程可以很容易地通过网络搜索(这里这里)找到。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/42681720

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档