gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-11-30 16:28:09 24 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 的建议解决方案并将名称 .L1 更改为 CustomLabel1 以防出现名称冲突,但它仍然给我带来旧的错误:

> 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 (参见:破坏者)

  • "cc"标志寄存器破坏器在同一部分中有详细介绍。在 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,我得到相同的代码。只是运气好,编译器选择了 tmp0tmp1使用相同的免费寄存器进行临时...消除噪音,如 .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" 是什么?语法是什么意思? &表示早期破坏操作数。在这种情况下,在我们使用完输入操作数之前,该值可能会被写入,因此编译器必须选择与为输入操作数选择的寄存器不同的寄存器。

编译器再次生成与 swap_1 相同的代码。和swap_2 .

<小时/>

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

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

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