fig*_*n93 2 c assembly gcc inline-assembly
我正在尝试学习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;
}
Run Code Online (Sandbox Code Playgroud)
当我使用此命令运行它时,上面的代码按预期工作:
> gcc main.c
> ./a.out
> 3 2 1
> 1 2 3
Run Code Online (Sandbox Code Playgroud)
但是,一旦我转向优化,我就会收到以下错误消息:
> 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
Run Code Online (Sandbox Code Playgroud)
如果我理解正确,这是因为在打开优化时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");
}
Run Code Online (Sandbox Code Playgroud)
但是在尝试运行程序时,我现在得到了一个分段错误:
> gcc -O2 main.c
> ./a.out
> 3 2 1
> Segmentation fault
Run Code Online (Sandbox Code Playgroud)
我也尝试了建议的解决方案,这previusly被问到的问题,改变了名称.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
Run Code Online (Sandbox Code Playgroud)
最后我也尝试了这个建议:
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");
}
Run Code Online (Sandbox Code Playgroud)
但后来我得到了这些错误:
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
Run Code Online (Sandbox Code Playgroud)
所以,我的问题是:
如何在内联装配中使用标签?
这是优化版本的反汇编输出:
> 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
Run Code Online (Sandbox Code Playgroud)
有很多教程 - 包括这个(可能是我所知道的最好的),以及操作数大小修饰符的一些信息.
这是第一个实现 - 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" */ );
}
Run Code Online (Sandbox Code Playgroud)
几点说明:
volatile
(或__volatile__
)是必需的,因为只有编译器"看到" (a)
和(b)
(和不"知道"你有可能交换它们的内容),以及原本可以自由地优化整个asm
陈述了- tmp0
和tmp1
否则将被视为未使用的变量太.
"+r"
意味着这既是可以修改的输入和输出; 只有它不是在这种情况下,他们可以严格只输入 - 更多的是在一点点......
'movl'上的'l'后缀并不是必需的; 也不是寄存器的'k'(32位)长度修饰符.由于您使用的是Linux(ELF)ABI,int
因此IA32和x86-64 ABI都是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;
}
}
Run Code Online (Sandbox Code Playgroud)
使用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
Run Code Online (Sandbox Code Playgroud)
如上所述,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" */ );
}
Run Code Online (Sandbox Code Playgroud)
我已经废除了'l'后缀和'k'修饰符,因为它们不需要.我还为操作数使用了"符号名称"语法,因为它通常有助于使代码更具可读性.
(a)
而(b)
现在确实只输入寄存器.那么"=&r"
语法是什么意思呢?所述&
表示早期撞操作数.在这种情况下,可以在我们使用输入操作数之前写入该值,因此编译器必须选择与为输入操作数选择的寄存器不同的寄存器.
再次,编译器生成,因为它没有对相同的代码swap_1
和swap_2
.
我写的方式比我在这个答案上的计划更多,但正如你所看到的,很难保持对编译器必须了解的所有信息的认识,以及每个指令集(ISA)和ABI的特性.
归档时间: |
|
查看次数: |
3902 次 |
最近记录: |