我正在尝试学习x86-64内联程序集,并决定实现这个非常简单的交换方法,该方法简单地按升序对a和b进行排序:
#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。
我试图找出这个问题的答案,但似乎没有什么效果。在这个先前提出的问题中,建议使用本地标签,我也尝试过这样做:
#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我还尝试了对这个先前提出的问题的建议解决方案,并将名称.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最后,我也尝试了这一建议
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发布于 2017-03-09 03:14:44
有很多教程--包括这一个 (可能是我所知道的最好的),以及一些关于操作数大小修饰符的信息。
下面是第一个实现-- 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语句--否则,tmp0和tmp1也会被视为未使用的变量。"+r"意味着这是一个可能被修改的输入和输出;只是在这种情况下它不是,而且它们可以严格地仅是输入-更详细地介绍一下.int和x86-64-abi,IA32都是32位。%=令牌为我们生成一个唯一的标签。顺便说一句,跳转语法<label>f表示向前跳转,<label>b表示后退。"memory",因为编译器无法知道取消引用指针的值是否已被更改。在被C代码包围的更复杂的内联asm中,这可能是一个问题,因为它使内存中所有当前持有的值无效--而且常常是一种大麻烦的方法。以这种方式出现在函数的末尾,这将不是一个问题--但是您可以阅读更多关于它的这里 (参见: Clobbers)。"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-64ELF,我得到了相同的代码.幸运的是,编译器选择了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在堆栈上传递参数,而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的特性的了解。
发布于 2017-03-08 21:43:15
您不能就这样将一堆asm语句内联起来。优化器可以根据它知道的约束重新排序、复制和删除它们。(就你的情况而言,它一无所知。)
因此,首先,您应该将asm合并在一起,并具有适当的读/写/重击约束。其次,还有一个特殊的asm goto表单,它将程序集提供给C级标签.
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;
}https://stackoverflow.com/questions/42681720
复制相似问题