C语言与汇编混合编程

extern声明外部变量

我们在c1.c文件中定义一个变量

int i = 3;

在c2.c文件中使用这个变量

#include<stdio.h>
 
int main()
{
    printf("%d\n",i);
}

编译c2.c的时候会报错,因为在编译阶段,可见性仍局限于各自的文件,编译c2.c文件时觉察不到c1.c文件中已经定义了i。
虽然编译器的目光不够长远,但是我们可以给它提示,帮助它来解决上面出现的问题。这就是extern的作用了。

#include<stdio.h>
extern int i;
 
int main()
{
    printf("%d\n",i);
}

extern的原理很简单,就是告诉编译器:“你现在编译的文件中,有一个标识符虽然没有在本文件中定义,但是它是在别的文件中定义的全局变量,你要放行!”

gcc -c c1.c
gcc -c c2.c
gcc c1.o c2.o -o test

程序正常运行。

C语言与汇编相互调用

基于上面的原理,我们完全可以将一个变量或一个函数用汇编语言写,在c语言中用extern声明一下,就可以调用啦。

C和汇编参数传递规则:

  1. 规定参数在4个以内,依次对应r0-r3寄存器。参数在4个以外,用栈(满减栈)传递。
  2. 返回值传递,规定使用r0寄存器。

举个例子,在a.s中用汇编定义一个函数:

.globl _add
_add:
	pushq	%rbp
	movq	%rsp, %rbp
	movl	%edi, -4(%rbp)
	movl	%esi, -8(%rbp)
	movl	-4(%rbp), %eax
	addl	-8(%rbp), %eax
	popq	%rbp
	retq

然后在b.c中用extern声明add是外部函数:

#include<stdio.h>
extern int add(int a,int b);
 
int main()
{
      int i;
      i = add(5,6);
      printf("%d\n",i);
}

编译

gcc -c a.s
gcc -c b.c
gcc a.o b.o -o test

程序可以正常执行。

如果你有疑惑,可以将b.c转成汇编:

gcc -S -masm=intel -fno-asynchronous-unwind-tables  b.c -o b.s

在汇编中可以看到,c语言调用add函数无非就是call _add,若用汇编实现这个函数,只需要汇编的标号为_add即可。

	.section	__TEXT,__text,regular,pure_instructions
	.build_version macos, 10, 16	sdk_version 11, 0
	.intel_syntax noprefix
	.globl	_main                   ## -- Begin function main
	.p2align	4, 0x90
_main:                                  ## @main
## %bb.0:
	push	rbp
	mov	rbp, rsp
	sub	rsp, 16
	mov	edi, 5
	mov	esi, 6
	call	_add
	mov	dword ptr [rbp - 4], eax
	mov	esi, dword ptr [rbp - 4]
	lea	rdi, [rip + L_.str]
	mov	al, 0
	call	_printf
	xor	ecx, ecx
	mov	dword ptr [rbp - 8], eax ## 4-byte Spill
	mov	eax, ecx
	add	rsp, 16
	pop	rbp
	ret
                                        ## -- End function
	.section	__TEXT,__cstring,cstring_literals
L_.str:                                 ## @.str
	.asciz	"%d\n"

.subsections_via_symbols

同样的,汇编调用C语言也是这个道理。

内嵌汇编

GCC 提供了内联汇编语法,可以直接在C语言中使用汇编指令。GCC扩展内联汇编的基本格式是:

asm volatile (
   Assembler Template
   : Output Operands       /* optional */
   : Input Operands        /* optional */
)

例如:

int main()
{
	int a=10, b;

	asm volatile (
		"movl %1, %%eax; \n"
		"movl %%eax, %0;"
		:"=r"(b)        /* output */
		:"r"(a)         /* input */
	); 
}

汇编的意思是将变量a的值放到eax寄存器,然后将eax的值放到b变量中。

%0,%1指输入输出列表中的变量,本例中%0代表b,%1代表a。由于这些样板操作数的前缀使用了“%”,因此,在用到具体的寄存器时就在前面加两个“%”,如%%eax。

输入输出列表中的r是指约束条件,或者说是“变量类型”,输出的约束条件前面要加"=",常用约束条件列表:

字母 含义
m 内存单元
i 常数(0~31)
a, b, c, d 寄存器eax, ebx, ecx,edx
q,r 动态分配的寄存器
g eax,ebx,ecx,edx或内存变量
数字"0" 指定与第0个变量相同的约束

最后,Intel汇编与AT&T汇编格式不同,他们是可以替换的,这里给出参考:

+------------------------------+------------------------------------+
|       Intel Code             |      AT&T Code                     |
+------------------------------+------------------------------------+
| mov     eax,1                |  movl    $1,%eax                   |   
| mov     ebx,0ffh             |  movl    $0xff,%ebx                |   
| int     80h                  |  int     $0x80                     |   
| mov     ebx, eax             |  movl    %eax, %ebx                |
| mov     eax,[ecx]            |  movl    (%ecx),%eax               |
| mov     eax,[ebx+3]          |  movl    3(%ebx),%eax              | 
| mov     eax,[ebx+20h]        |  movl    0x20(%ebx),%eax           |
| add     eax,[ebx+ecx*2h]     |  addl    (%ebx,%ecx,0x2),%eax      |
| lea     eax,[ebx+ecx]        |  leal    (%ebx,%ecx),%eax          |
| sub     eax,[ebx+ecx*4h-20h] |  subl    -0x20(%ebx,%ecx,0x4),%eax |
+------------------------------+------------------------------------+
posted @ 2021-03-05 16:32:01
评论加载中...
发表评论