gpt4 book ai didi

c - 如何在 GCC 内联汇编中使用标签?

转载 作者:行者123 更新时间:2023-12-02 06:14:23 28 4
gpt4 key购买 nike

我正在尝试学习 x86-64 内联汇编,并决定实现这个非常简单的交换方法,只需命令 ab按升序排列:

#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;
}

当我使用以下命令运行上面的代码时,它按预期工作:
> gcc main.c
> ./a.out
> 3 2 1
> 1 2 3

但是,一旦打开优化,我就会收到以下错误消息:
> 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在程序集文件中多次定义。

我试图找到这个问题的答案,但似乎没有任何效果。在 this previusly asked question建议改用本地标签,我也尝试过:
#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");
}

但是当试图运行程序时,我现在得到了一个段错误:
> gcc -O2 main.c
> ./a.out
> 3 2 1
> Segmentation fault

我还尝试了 this previusly asked question 的建议解决方案并改名 .L1CustomLabel1万一会发生名称冲突,但它仍然给我旧的错误:
> 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

最后我也试过 this suggestion :
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");
}

但是后来我得到了这些错误:
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

所以,我的问题是:

如何在内联汇编中使用标签?

这是优化版本的反汇编输出:
> 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

最佳答案

有很多教程 - 包括 this one (可能是我所知道的最好的),以及一些关于 operand size modifiers 的信息.

这是第一个实现 - swap_2 :

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 IA32 和 x86-64 ABI 都是 32 位。
  • %= token 为我们生成一个唯一的标签。顺便说一句,跳转语法 <label>f表示向前跳跃,<label>b意味着回来。
  • 为了正确起见,我们需要 "memory"因为编译器无法知道取消引用指针的值是否已更改。这可能是由 C 代码包围的更复杂的内联汇编中的一个问题,因为它会使内存中当前保存的所有值无效 - 而且通常是一种大锤方法。以这种方式出现在函数的末尾,这不会成为问题 - 但您可以阅读更多内容 here (参见:Clobbers)
  • "cc" flags register clobber 在同一节中有详细说明。在 x86 上,它什么也不做。一些作者为了清楚起见将其包括在内,但由于实际上所有重要的asm语句会影响标志寄存器,它只是假设默认情况下被破坏。

  • 这是 C 实现 - swap_1 :
    void swap_1 (int *a, int *b)
    {
    if (*a > *b)
    {
    int t = *a; *a = *b; *b = t;
    }
    }

    使用 gcc -O2 编译对于 x86-64 ELF,我得到相同的代码。编译器选择了 tmp0 有点幸运。和 tmp1为临时使用相同的免费寄存器...消除噪音,如 .cfi 指令等,给出:
    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 ABI 在堆栈上传递参数,而 x86-64 ABI 在 %rdi 中传递前两个参数和 %rsi分别。

    治疗 (a)(b)仅作为输入 - swap_3 :
    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_1 相同的代码。和 swap_2 .

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

    关于c - 如何在 GCC 内联汇编中使用标签?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42681720/

    28 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com