汇编语言和C语言混合编程和内联汇编

汇编和C语言和混合编程

汇编和C语言和混合编程可分为2大类:

  • 单独的汇编文件和单独的C语言文件分别编译成目标文件后,一起链接成为可执行目标文件
  • 在C语言中嵌入汇编代码, 直接编译生成可执行程序
section .data
    str: db "asm_print says hello world!", 0xa, 0
    str_len equ $-str

section .text
    extern c_print

    global _start
    _start:
        push str
        call c_print
        add esp, 4

        mov eax, 1      ; 1号子功能表示exit
        int 0x80        ; 发起系统调用

    global asm_print
    asm_print:
        push ebp
        mov ebp, esp
            
        mov ebx, 1                ; 1 标准输出
        mov ecx, [ebp+8]          ; 第1个参数
        mov edx, [ebp+12]        ; 第2个参数

        mov eax, 4           ; 4号子功能表示write
        int 0x80             ; 发起系统调用

        pop ebp
        ret
extern void asm_print(char*, int);

void c_print(char *str)
{
    int len = 28;
    asm_print(str, len);
}
编译和链接
nasm -f elf asm_print.s -o asm_print.o    (生成elf可重定位文件)
gcc -m32 -c c_print.c -o c_print.o        (生成elf可重定位文件)

ld -m elf_i386 c_print.o asm_print.o -o a.out
image.png

所以:

  • 在汇编代码中导出符号供外部引用用关键字 global,引用外部文件的符号是用的关键字 extern
  • 在C语言中只要符号定义为全局就可以被外部引用,引用外部符号用 extern 声明

上面第一种介绍的是C语言和汇编语言写在不同的文件,分别编译,最后通过链接的方式结合在一起形成可执行文件。下面要说的是第2种: C语言中嵌入汇编代码, 直接编译生成可执行程序。

基本内联汇编

内联汇编称为 inline assembly , GCC 支持 C 代码中直接嵌入汇编代码,所以称为 GCC inline assembly。C语言不支持寄存器操作,汇编语言可以,所以在C语言中嵌入内联汇编用以提升效率。
内联汇编中所用的汇编语言是AT&T,并不是intel汇编语法,因为GCC只支持AT&T汇编。

内联汇编按格式分为2部分大类: 1 是最简单的基本内联汇编,2 复杂一些的扩展内联汇编。

asm [volatitle] ("assembly code")

asm关键字 用于声明内联汇编表达式。
volatitle关键字 是可选项,它告诉GCC,"不要修改我写的汇编代码,请原样保留"。
assembly code 表示用户写的汇编代码。

char* str = "hello world\n";
int count = 0;

int main()
{
    asm ("pusha;\
                    movl $4, %eax;\
                    movl $1, %ebx;\
                    movl str, %ecx;\
                    movl $12, %edx;\
                    movl $4, %eax;\
                    int $0x80;\
                    mov %eax, count;\
                    popa \
                    ");

}

gcc -m32 inlineAMS.c -o a.out (-m32 编译生成的32位汇编程序,先执行"sudo apt-get install g++-multilib libc6-dev-i386"下载gcc的32位兼容包)

使用内联汇编代码直接调用"系统调用write" 打印字符串。这里我们把strcount定义为全局变量是因为:在基本内联汇编中,若要引用C变量,必须将其定义为全局变量,否则链接时会找不到这两个符号。

扩展内联汇编

由于基本内联汇编功能太薄弱了,所以才对它进行了扩展使其强大,不过复杂度也大大提高了。

gcc 本身是个C编译器,要让其支持汇编语言,那么会有2个问题:

  • 在内联汇编代码前的C代码,其编译后也会分配寄存器等资源,那么插入的汇编代码也会使用寄存器,这是否会造成资源冲突。
  • 汇编语言如何访问C代码中的变量

所以为了解决这些问题,我们事前约定,让编译器提供给我们一个模板,让用户在模板中提出要求,其余工作由它负责实现。

asm [volatile] ("assembly code" : output : input : clobber/modify)

output 用来指定汇编代码的数据如何传输给C代码使用,格式:"操作数修饰符约束名"(C变量名)
input 用来指定C中数据如何输入给汇编使用,格式:"操作数修饰符 约束名"(C变量)
clobber/modify 汇编代码执行后会破坏一些内存资源或寄存器资源,通过此项告诉编译器,可能造成寄存器或内存数据的破坏。

  • 寄存器约束
    寄存器约束就是要求gcc 使用哪个寄存器,将 input 或 output 中变量约束在某个寄存器中。

    • a:表示寄存器 eax / ax / al
    • b:表示寄存器 ebx / bx / bl
    • c:表示寄存器 ecx / cx / cl
    • d:表示寄存器 edx / dx / dl
    • D : 表示寄存器 edi / di
    • S : 表示寄存器 esi / si
    • q : 表示任意4个通用寄存器之一: eax / ebx /ecx / edx
    • r : 表示任意6个通用寄存器之一: eax / ebx /ecx / edx / esi / edi
    • g :表示可以存放到任意地点(寄存器和内存中)
    #include 
    int main()
    {
        int in_a = 1, in_b = 2, out_sum;
        /* out_sum = in_a + in_b  (扩展内联汇编寄存器的前缀是%%) */
        asm("addl %%ebx, %%eax": "=a"(out_sum): "a"(in_a), "b"(in_b));
        printf("sum is %d\n", out_sum);
    }
    

    in_ain_b 是在 input部分 中输入的,用约束名 a 为 C语言变量in_a 指定了用寄存器eax,同理用b 为 C语言变量 in_b 指定了用寄存器ebx;
    在 output部分 中用约束名a 把寄存器eax的值存储到C语言变量out_sum中,outsum中的"="是操作数的类型修饰符,表示只写。

  • 内存约束
    内存约束是要求 gcc 直接将位于 input 和 output 中的C变量内存地址作为内联汇编代码的操作数,不需要寄存器做中转,直接进行内存读写,也就是汇编代码的操作数是C变量指针。

    • m: 表示操作数可以使用任意一种内存形式。
    • o: 操作数为内存变量,但访问它是通过偏移量的形式访问。
    #include 
    int main()
    {
        int in_a = 1,  in_b = 2;
    
        printf("in_b is %d\n", in_b);
        asm("movb %b0, %1;" : : "a"(in_a), "m"(in_b));        // in_b = in_a
        printf("in_b now is %d\n", in_b);                                           
    }
    

    这里用约束名 a 为 C语言变量in_a 指定了用寄存器eax。
    对in_b施加内存约束m,表示把变量 in_b 作为内联代码的操作数。
    %0 和 %1 表示序号占位符,表示eax 和 in_b的内存地址(指针)。
    b表示用32位中的低8位,这里也就是al。

  • 立即数约束
    此约束要求 gcc 在传值的时候不通过内存和寄存器,直接作为立即数传给汇编代码,只能放在 input 中。

    • i: 表示操作数为 整数 立即数
    • F: 表示操作数为 浮点数 立即数
    • I: 表示操作数为 0 - 31 之间的立即数
    • J: 表示操作数为 0 - 63 之间的立即数
    • N: 表示操作数为 0 - 255 之间的立即数
    • O: 表示操作数为 0 - 32 之间的立即数
    • X: 表示操作数为 任何类型 的立即数
  • 通用约束

  • 占位符
    为方便对操作数的引用,扩展内联汇编提供了占位符,它的作用是代表约束指定的操作数(寄存器,内存,立即数),占位符分为序号占位符和名称占位符

    • 序号占位符
      序号占位符是对在 output 和 input 中的操作数,按照他们从左到右出现的次序从0开始编号,最大为9。
      所在在 assembly code中,引用它们的格式为 %0 ~ %9。
      asm ("addl %2, %1": "=a"(out_sum) : "a"(in_a), "b"(in_b));
      
      "=a"(out_sum)序号为0,%0对应的是 eax
      "a"(in_a)序号为1,%1对应的是 eax
      "b"(in_b)序号为2,%2对应的是 ebx
      
    • 名称占位符
      名称占位符与序号占位符不一样,序号占位符靠本身出现在 output 和 input 中的位置就能被编译器编译出来,而名称占位符需要在 output 和 input 中把操作数显式地起个名字,它用[名称]"约束名"(C语言变量)来标识。
      #include 
      int main()
      {
          int in_a = 18, in_b = 3, out = 0;
          asm("divb %[divisor]; movb %%al, %[result]"  \
              : [result]"=m"(out)  \   
              : "a"(in_a), [divisor]"m"(in_b)"  \
              );
          printf("result is %d\n", out);
      }
      

你可能感兴趣的:(汇编语言和C语言混合编程和内联汇编)