前情提要
每个搞过竞赛的同学可能都想象过通过内嵌汇编来提高程序效率,然而对于绝大多数同学来说,可能也只是开开玩笑而已,毕竟CCF的考纲里面明确写了不许内嵌汇编。
我就是众多只在嘴上说说的人之一,反正中学六年我最终还是没有写过一句内嵌汇编。
当然这只是因为我基本不会汇编(捂脸)
今天由于某些特殊原因(解释起来太长),我又一次想到了在编写C程序的时候使用汇编,然后就开始了今天晚上的人在囧途之旅~
为方便叙述,本文中代码一律使用C而不是C++,系统环境采用Linux(Windows下出问题别来找我)
内嵌汇编?
纵使我是一个渣渣,我也知道想要在C中使用汇编有两种方式:
- 在C的代码里面内嵌汇编
- 把汇编编译成单独文件,然后再链接起来
本着从易到难的原则,我决定先从内嵌汇编试起。
可是谁告诉你内嵌汇编简单了?
查完了某度,刚准备动手,猛然注意到文章中说的都是VC。常识告诉我,巨硬会用它自己的汇编器MASM,这显然会与我现在使用的gcc有区别。
于是又一次某度过后,我发现gcc的汇编器GAS用的是AT&T风格的汇编,跟我略微会那么一点点的x86汇编完全不一样。
没有办法,硬着头皮凭借自己瞎撸的本事靠感觉写出几行AT&T风格的汇编。
结果呢?
结果还用问吗?!!当然是CE啊!
链接汇编
使用NASM
在意识到我根本就没有办法在短时间内写出正确的AT&T风格汇编代码之后,我只能问某度怎么把x86汇编配合gcc使用,然后就看到一个老朋友——NASM。
讲真,NASM虽然和MASM有点不同,可它至少是x86汇编器啊,我看到了希望。
原理很简单,用NASM编译汇编代码,再把它链接到C编译出来的.o文件上就可以了。
在C中使用汇编中的函数
实现混合编程的一个重要方面就是要实现C代码和汇编代码的数据、函数共享。对于我来说,最重要的就是能让C访问汇编的函数,这样就可以通过汇编来提高程序效率。
在C代码中需要写的确实很简单,只要像往常一样extern以下就可以了。举个例子:
extern int add(int a,int b);
在汇编中我们需要做的就是将标签声明成global的:
global add
接下来的一个问题是,C中调用函数的参数是怎样传给汇编的?
说实话我根本不知道……只能百度。在被各种信息坑了无数次后,终于得出结论:
- C在调用函数时,先将参数逆序压栈,再转到函数的地址上
讲真,具体是啥样的我们还是下次再研究吧
所以说,我们在汇编中的函数所要做的第一件事就是把参数从栈中取出来。
注意一下32位和64位可能不兼容的问题……
如果使用的是64位系统,在链接的时候出问题几乎是必然的。原因在于,nasm是32位的汇编器,而gcc在64位系统下会生成64位的代码。32位和64位的程序是无法链接在一起的。
解决方法有两个:
- 换用64位汇编器(同时也要写64位汇编)
- 让gcc用32位模式编译
平心而论,最佳选择应该是第一个。然而我太懒……就选择了后者。后一种方式就是在编译的时候加上-m32而已。
当然,你可能还是会面临一堆报错,那是因为你的gcc没有32位的依赖文件。安装一个依赖包就可以了。
sudo apt-get install libc6-dev-i386
代码
其实这才是最重要的东西是吧
仅仅只是实现了一个输出字符串的功能
//1.c
#include <stdio.h>
#include <string.h>
extern void dispstr(const char*,const int);
void puts_asm(const char* str){
dispstr(str,strlen(str));
}
int main(){
puts_asm("This is a message printed by asm.\n");
puts("This is a message printed by C.");
return 0;
}
;dispstr.asm
[section .text]
global dispstr
dispstr:
push ebp
mov ebp,esp
sub esp,0F0h
mov edx,[ebp+12] ;字符串的长度
mov ecx,[ebp+8] ;首字符的地址
;因为寄存器中存的是地址,所以要加方括号
mov ebx,1
mov eax,4
int 80h
leave ;作用等于下面两句
;mov esp,ebp
;pop ebp
ret
编译方法:
all:
nasm -f elf dispstr.asm -o dispstr.o
gcc -o 1 1.c dispstr.o -m32
运行结果:
后记
- 其实我对汇编一窍不通,写代码纯靠百度大法
- 虽然编程作业还没写,但却有一种谜一般的从容
- 虽然周三数分小测我还没复习,但却有一种谜一般的从容