GCC 內聯彙編

zengmuansha發表於2024-01-19

LINUX下的彙編入門

AT&T風格 彙編 和GCC風格彙編

彙編程式碼的除錯

前面寫了三篇,是自我摸索三篇,摸著石頭過河,有些或許是錯誤的細節,不必在意!  今天我們直接用GCC編譯C語言程式碼,且在C語言裡面內嵌AT&T風格的彙編! 前三篇大家瞭解即可,我們重點放在內嵌彙編裡,簡單快捷舒服!

GCC支援在C/C++程式碼中嵌入彙編程式碼,這些彙編程式碼被稱作GCC Inline ASM——GCC內聯彙編。

這是一個非常有用的功能,有利於我們將一些C/C++語法無法表達的指令直接潛入C/C++程式碼中,另外也允許我們直接寫C/C++程式碼中使用匯編編寫簡潔高效的程式碼。其實為了裝逼和護城河!


gcc 編譯器支援  2 種形式的內聯 asm 程式碼:

  1. 基本 asm 格式:不支援運算元;

  2. 擴充套件 asm 格式:支援運算元;

1. 語法規則

asm [volatile] ("彙編指令")

  1. 所有指令,必須用雙引號包裹起來;

  2. 超過一條指令,必須用\n分隔符進行分割,為了排版,一般會加上\t;

  3. 多條彙編指令,可以寫在一行,也可以寫在多行;

  4. 關鍵字 asm 可以使用  asm 來替換;

  5. volatile 是可選的,編譯器有可能對彙編程式碼進行最佳化,使用 volatile 關鍵字之後,告訴編譯器不要最佳化手寫的內聯彙編程式碼。


基本內聯彙編的格式是

    __asm__ __volatile__("Instruction List");


    可選風格 有比較多種,不過測試了下 就下面程式碼的風格支援得好,

    每行彙編命令 要加冒號和\N\T 確實麻煩. 不過可以使用軟體前後追加特定符號就行了.

    下面是基本格式 不支援運算元,它必須使用全域性變數,這限制太麻煩了

    
    
    #include <stdio.h>
    

    int a =  1;
    int b =  2;
    int c;

    int  main ()
    {
         asm  volatile  (
             "movl a, %eax\n\t"
             "addl b, %eax\n\t"
             "movl %eax, c"
            )
    ;
         printf( "c = %d \n", c);
         return  0;
    }

    圖片

    其實彙編 內嵌 相當於呼叫個 C函式 而已, 不過這C函式是彙編函式而已

    
    
    # include  <stdio.h>

    int  main ()
    {
         int data1 =  1;
         int data2 =  2;
         int data3;

         asm  volatile
         (
             "movl %%ebx, %%eax\n\t"
             "addl %%ecx, %%eax"
            :  "=a"(data3) :  "b"(data1), "c"(data2)
    )
    ;

    /*asm [volatile] ("彙編指令\n\t" : "輸出運算元列表" : "輸入運算元列表" : "改動的暫存器")*/
         printf( "data3 = %d \n", data3);
         return  0;
    }

    這裡必須使用擴充套件ASM格式,才能不使用全域性變數當作引數傳入彙編函式里

    所以彙編命令 暫存器要多個%號 "movl %%ebx,%%eax" 把EBX的值覆蓋進 EAX 裡.

    所有彙編命令結束後 最後個小掛號前 開始定義我們的函式引數
    第一個冒開始是 輸出引數   :"=a"(data3)
    第二個冒號開始是 輸入引數 有多個引數用逗號分隔
    第三個冒號是不要最佳化暫存器列表

    其中    : "=a " (data3 ) 的 data3 是C的變數 用小掛號保護起來, 前面的=A
    叫做 "修飾符" 

    對輸出暫存器或記憶體地址提供 額外的說明,包括下面4個修飾符:

    1. +:被修飾的運算元可以讀取,可以寫入;

    2. =:被修飾的運算元只能寫入;

    3. %:被修飾的運算元可以和下一個運算元互換;

    4. &:在行內函數完成之前,可以刪除或者重新使用被修飾的運算元;

    其中A 使用暫存器的別名 "=a" 表示只能寫A的寄出器(EAX)
     通俗講下面的 叫約束

    a: 使用 eax/ax/al 暫存器;

    b: 使用  ebx /bx/bl 暫存器;

    c: 使用 ecx/cx/cl 暫存器;

    d: 使用 edx/dx/dl 暫存器;

    r: 使用任何可用的通用暫存器;

    m: 使用變數的記憶體位置;

    b(data1)表示 data1變數的值複製到B寄出器裡

    在內聯彙編程式碼中,沒有宣告“改動的暫存器”列表,也就是說可以省略掉(前面的冒號也不需要);


    使用佔位符來代替暫存器名稱

    如果運算元有 很多,那麼在內聯彙編程式碼中去寫每個暫存器的名稱,就顯得 很不方便。佔位符有點類似於批處理指令碼中,利用   2...來引用輸入引數一樣,內聯彙編程式碼中的佔位符,從 輸出運算元列表中的暫存器開始從  0 編號,一直編號到 輸入運算元列表中的所有暫存器。

    
    
    #
    include 
    <stdio.h>
    
    int  main ()
    {
         int data1 =  1;
         int data2 =  2;
         int data3;

         asm(          "movl %1, %%eax\n\t"
             "addl %2, %0"
              :  "=r" (data3) :  "r" (data1), "r" (data2)         );

         printf( "data3 = %d \n", data3);
         return  0;
    }


    %0 是輸入引數  依次是 兩個輸入引數 %1 %2

    內聯彙編的C語言 正常編譯就好了

    
    [root@dsmart=>LINUX_ASM]
    $gcc main_add.c -o main.add.exe
    
    [root@dsmart=>LINUX_ASM]$./main.add.exe 
    data3 = 3 

    我們可以檢視下GCC 彙編的程式碼

      [root@dsmart=>LINUX_ASM]$gcc -S main_add.c -o main_add.asm[root@dsmart=>LINUX_ASM]$vim main_add.asm

      下面是我們GCC把MAIN_ADD.C全部翻譯成了彙編程式碼,而我們重點的內嵌彙編用藍色註解#APP----#NOAPP 範圍內

      
      
              .file   
      "main_add.c"
      
              .section        .rodata
      .LC0:
              .string  "data3 = %d \n"
              .text
              .globl  main
              . type    main, @ function
      main:
      .LFB0:
              .cfi_startproc
              pushq   %rbp
              .cfi_def_cfa_offset 16
              .cfi_offset 6, -16
              movq    %rsp, %rbp
              .cfi_def_cfa_register 6
              subq     $16 , %rsp
              movl     $1 , -4(%rbp)
              movl     $2 , -8(%rbp)
              movl    -4(%rbp), %eax
              movl    -8(%rbp), %edx
      #APP
      # 9 "main_add.c" 1
              movl %eax, %eax
              addl %edx, %eax

      # 0 "" 2
      #NO_APP
              movl    %eax, -12(%rbp)
              movl    -12(%rbp), %eax
              movl    %eax, %esi
              movl    $.LC0, %edi
              movl     $0 , %eax
              call     printf
              movl     $0 , %eax
              leave
              .cfi_def_cfa 7, 8
              ret
              .cfi_endproc
      .LFE0:
              .size   main, .-main
              .ident   "GCC: (GNU) 5.5.0"    
                        .section        .note.GNU-stack,"",@progbits

      裡面這段程式碼不是%1了,被具體替換成了寄出器名.

      這段是C語言呼叫匯編函式進行引數壓棧操作,分別把引數1,2壓入棧底
                movl    $1, -4(%rbp)        movl    $2, -8(%rbp)        movl    -4(%rbp), %eax        movl    -8(%rbp), %edx
        rsp : 棧指標 暫存器,指向棧頂
        rbp : 棧基址暫存器,指向棧底

        返回引數:
                  movl    %eax, -12(%rbp)        movl    -12(%rbp), %eax
          把結果 壓入棧底 -12位置,然後出棧 把RBP棧的值 返回給EAX
          好像這有點多餘
          下面準備呼叫PRINTF函式 edi : 函式引數
          rsi/esi : 函式引數
          下面我們進行O3最佳化下看
            [root@dsmart=>LINUX_ASM]$gcc main_add.c -S -O3 -o main_add.asm
            程式碼確實少了些
              
                      .file   
              "main_add.c"
              
                      .section        .rodata.str1.
              1,
              "aMS",@progbits,
              1
              
              .LC
              0:
              
                      .string 
              "data3 = %d \n"
              
                      .section        .text.unlikely,
              "ax",@progbits
              
              .LCOLDB1:
              
                      .section        .text.startup,
              "ax",@progbits
              
              .LHOTB1:
              
                      .p2align 
              4,,
              15
              
                      .globl  main
              
                      .type   main, @function
              
              main:
              
              .LFB11:
              
                      .cfi_startproc
              
                      subq    $8, %rsp
              
                      .cfi_def_cfa_offset 
              16
              
                      movl    $2, %esi
              
                      movl    $1, %eax
              
              
              #APP
              
              
              # 9 "main_add.c" 1
              
                      movl %eax, %eax
              
                      addl %esi, %esi
              
              
              
              # 0 "" 2 #NO_APP        movl    $.LC 0, %edi        xorl    %eax, %eax        call     printf        xorl    %eax, %eax        addq    $8, %rsp        .cfi_def_cfa_offset 8        ret        .cfi_endproc .LFE11:        .size   main, .-main        .section        .text.unlikely .LCOLDE1:        .section        .text.startup .LHOTE1:        .ident   "GCC: (GNU) 5.5.0"         .section        .note.GNU-stack, "",@progbits
                      
              結果最佳化的不像人樣了, volatile 也無法禁止最佳化內嵌彙編!


              來自 “ ITPUB部落格 ” ,連結:https://blog.itpub.net/680758/viewspace-3004498/,如需轉載,請註明出處,否則將追究法律責任。

              相關文章