我正在尝试学习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
被多次定义汇编文件。
我试图找到这个问题的答案,但似乎没有任何效果。在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
答案 0 :(得分:7)
有很多教程 - 包括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__
)是必需的,因为编译器只会看到&#39; (a)
和(b)
(并且不知道您可能会交换他们的内容),否则可以自由优化整个asm
tmp0
和tmp1
} statement off - "+r"
和int
也会被视为未使用的变量。
%=
表示这是一个可以修改的输入和输出;在这种情况下,只有不是,并且它们可以严格地仅输入 - 稍微多一点......
&#39; l&#39;后缀&#39; movl&#39;并非真的有必要;也不是&#39; k&#39;寄存器的(32位)长度修改器。由于您使用的是Linux(ELF)ABI,因此IA32和x86-64 ABI的<label>f
为32位。
<label>b
令牌为我们生成一个唯一的标签。顺便说一句,跳转语法"memory"
表示转发跳转,而"cc"
表示返回。
为了正确,我们需要asm
,因为编译器无法知道来自解引用指针的值是否已被更改。这可能是由C代码包围的更复杂的内联asm中的问题,因为它使内存中所有当前保持的值无效 - 并且通常是大锤方法。以这种方式出现在函数的末尾,它不会成为一个问题 - 但你可以阅读更多信息here(参见: Clobbers )
swap_1
标志寄存器clobber在同一节中详述。在x86上,它确实没有。一些作者为了清楚起见而将其包含在内,但由于几乎所有非平凡的void swap_1 (int *a, int *b)
{
if (*a > *b)
{
int t = *a; *a = *b; *b = t;
}
}
语句都会影响标志寄存器,因此默认情况下它只是被假定为。
这是C实现 - gcc -O2
:
tmp0
使用tmp1
编译x86-64 ELF,我得到相同的代码。运气好,编译器选择swap_2:
movl (%rdi), %eax
movl (%rsi), %edx
cmpl %edx, %eax
jle 21f
movl %edx, (%rdi)
movl %eax, (%rsi)
21:
ret
和swap_1
来使用相同的临时寄存器...切断噪声,如.cfi指令等,给出:
.L1
如上所述,-m32
代码是相同的,只是编译器为其跳转标签选择了%rdi
。使用%rsi
编译代码生成相同的代码(除了以不同的顺序使用tmp寄存器)。由于IA32 ELF ABI在堆栈上传递参数,因此有更多开销,而x86-64 ABI分别传递(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" */ );
}
视为输入 - (a)
:
(b)
我已经废除了&#39; l&#39;后缀和&#39; k&#39;这里有修饰符,因为它们不需要。我还使用了这个符号名称&#39;操作数的语法,因为它通常有助于使代码更具可读性。
"=&r"
和&
现在确实是仅输入寄存器。那么swap_1
语法是什么意思呢? swap_2
表示早期clobber 操作数。在这种情况下,可以在使用输入操作数之前将值写入,因此编译器必须选择与为输入操作数选择的寄存器不同的寄存器。
编译器再次生成与$(document).ready(function(){
if($('.txt-input').val().length === 0){
$('.erase').hide();
}
else{
$('.erase').show();
};
$(".erase").click(function(){
$('.txt-input').val('');
});
});
和<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<form>
<input type="text" class="txt-input"/>
</form>
<p class="erase">clear</p>
相同的代码。
我写的方式比我在这个答案上的计划更多,但正如你所看到的,很难保持对编译器必须了解的所有信息的认识,以及每条指令的特性。 set(ISA)和ABI。
答案 1 :(得分:3)
你不能像这样内联一堆asm
语句。优化器可以根据它知道的约束自由地重新排序,复制和删除它们。 (在你的情况下,它不知道。)
首先,您应该使用正确的读/写/ clobber约束将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)
);
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;
}
答案 2 :(得分:0)