2017-03-08 55 views
1

我努力學習的x86-64內聯彙編,並決定實施這個非常簡單的交換方法簡單單ab按升序排列:如何在GCC內聯裝配中使用標籤?

#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 
+0

你試過命名沒有點的標籤嗎?從C生成的裝配標籤通常具有圓點形式。我認爲它可能是保留的。 – PSkocik

+0

後一種解決方案是正確的語法。只是你的代碼無法正常工作而崩潰。 –

+0

@PSkocik是的,我嘗試命名它「CustomLabel1」(沒有點),它仍然給我同樣的錯誤。 –

回答

6

有很多教程 - 包括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,因此對於IA32和x86-64 ABI,int均爲32位。

  • %=令牌爲我們生成一個唯一的標籤。 BTW,跳轉語法<label>f表示轉發跳轉,而<label>b表示返回

  • 爲了正確,我們需要"memory",因爲編譯器無法知道取消引用的指針的值是否已更改。這可能是由C代碼包圍的更復雜的內聯asm中的一個問題,因爲它會使內存中所有當前保存的值無效 - 並且通常是大錘方法。在這種方式的函數的最後面,它不會是一個問題 - 但你可以閱讀更多關於它here(參見:則會覆蓋

  • "cc"標誌寄存器撞是在同一節詳細。在x86上,它沒有沒有。有些作者爲了清楚起見而將其包括在內,但由於幾乎所有不重要的語句都會影響標誌寄存器,所以它只是假設在默認情況下被破壞。

這裏的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_1swap_2相同的代碼。


我寫的比我計劃這個答案的方式較多,但你可以看到,這是非常難以維持的編譯器必須知道的所有信息意識,以及每個指令的特質設置(ISA)和ABI。

+0

非常詳細的解釋和代碼工作沒有任何segfaults併產生正確的輸出,所以我會選擇這一個作爲答案。雖然,我可以問,爲什麼編譯器不會自動執行此操作?編譯器不應該能夠讀取我們編寫的asm指令,並理解哪些寄存器和內存位置已被更改?我也可以問,這在英特爾語法中看起來如何?再次感謝您的詳細解答。 –

+0

@ fighting_falcon93根據內嵌彙編程序模板的複雜性,代碼分析工具可能無法準確確定彙編程序模板中的變化以及副作用。 GCC開發者並沒有在可能無法工作的東西上浪費自己的努力,而是強迫開發者明確並強制他們準確地描述輸入,輸出,破壞者和其他副作用。 –

3

你不能只是把一堆asm報表內聯這樣的。優化器可以根據知道的約束條件自由地重新排序,複製和刪除它們。 (在你的情況下,它什麼都不知道)。

所以首先,你應該把asm合併在一起,並且要有正確的讀/寫/ clobber約束。其次,還有一個特殊的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; 
} 
+1

我試過了你的建議,但是它在用'-O2'編譯時仍然會產生分段錯誤,但是它沒有任何優化就可以正常運行。 –

+0

@ fighting_falcon93也許試着編譯成彙編代碼(gcc -S),這樣你就可以看看編譯器實際在做什麼 – immibis

+3

我可能試過類似的東西(沒有goto);避免「記憶」闖入;並讓彙編程序選擇臨時寄存器:'int temp1,temp2; __asm__( 「MOV%[A],%[TMP1] \ n上\ t」 的 「MOV%[B],%[TMP2] \ n上\ t」 的 「CMP%[TMP2],%[TMP1] \ n「t」 「mov%[tmp1],%[b] \ n \ t」 「mov%[tmp2],%[a] \ n \ t」 「 1:\ n「 :[a]」+ m「(* a),[b]」+ m「(* b),[tmp1]」= r「(temp1),[tmp2]」= r「( temp2) : :「cc」 );' –

-2

以下提示將允許您成功使用內聯裝配中的標籤。 (我沒有調試彙編代碼,而且我知道它是錯誤的,但這不是問題的一部分。)

  • 請勿使用像.L1這樣的標籤,因爲它可能會與GCC生成的標籤發生衝突。
  • 您的標籤應該包含%= token,這樣如果GCC在多個地方使用asm語句,每個版本都會有自己的唯一標籤。
  • 您需要降低你的代碼只是有一個asm聲明,因爲%=生成每個asm聲明它是在一個不同的唯一編號。
  • 最後,我不知道爲什麼,但是你需要把在你的asm語句結尾處冒號,否則GCC不會擴展其中的任何%=標記。

這裏是代碼,使用GCC編譯成功在x86_64(儘管它在運行時崩潰):

#include <stdio.h> 

void swap(int* a, int* b) 
{ 
    asm(
     ".intel_syntax noprefix\n\t" 
     "mov eax, DWORD PTR [rdi]\n\t" 
     "mov ebx, DWORD PTR [rsi]\n\t" 
     "cmp eax, ebx\n\t" 
     "jle swap_end_%=\n\t" 
     "mov DWORD PTR [rdi], ebx\n\t" 
     "mov DWORD PTR [rsi], eax\n\t" 
     "swap_end_%=:\n\t" 
     ".att_syntax noprefix\n\t" 
    : 
    ); 
} 

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; 
} 
+0

我嘗試了你的建議,但是當使用-O2編譯時它仍然會產生分段錯誤,但是它運行良好,沒有任何優化。 –

+0

asm語句需要指定它的操作數以及它的破壞程度。 – interjay

+0

fighting_falcon93:我回答了您的問題,即「我如何在內聯裝配中使用標籤?」。我沒有調試過你的彙編代碼。如果您仍然需要幫助,應該將其作爲一個新問題提出。 (問題很可能是由於你認爲交換函數的參數存儲在特定的寄存器中,你可以使用匯編操作數來讓GCC選擇使用哪個寄存器來代替對它們進行硬編碼。) –

0

你不能假設值是在你的彙編代碼中的任何特定的寄存器 - 你需要使用約束來告訴gcc你想讀取和寫入什麼值,並讓它告訴你他們在哪個寄存器中。gcc docs告訴你你需要知道的大部分內容,但是非常密集。也有教程在那裏,你可以很容易地在網上搜索(herehere)找到

相關問題