32位汇编第七讲,混合编程
混合编程的概念,有时候我们会想,C语言可不可以调用汇编的函数,或者反过来调用
或者说C语言里面内部直接内联汇编去编写.
可以实现,静看怎么实现
一丶C语言调用汇编语言的函数
1.创建工程和代码
①创建VC 控制台程序
FILE(文件) - NEW (新建)
然后我们打开源文件,一级一级展开,找到我们的main函数
那么我们现在要调用汇编写的,那么我们用汇编写一个代码
②,创建汇编程序
创建汇编程序,这个比较简单,我们新建个文件夹,里面新建一个文本文档,后缀名改为ASM,然后用RadAsm打开,开始编写代码
使用RadAsm编写,这样比较快
编写我们的汇编代码
,注意下方的end结束符号,我们并没有指明开始位置是MyAdd,也就是说这个汇编程序,只能编译
编译出的OBJ 和我们上边写的程序的OBJ 一起连接(上面的程序也是编译,不连接)
看下汇编代码
代码语言:javascript复制.386
.model flat,stdcall
option casemap:none
.const
.data
.code
MyAdd proc c ,n1:DWORD,n2:DWORD
mov eax,n1
add eax,n2
ret
MyAdd endp
End
③丶C/cpp文件调用汇编中的MyAdd函数
1.先编译汇编程序,产生obj文件
将此obj文件复制到我们的C/c 的目录下
2.修改C/c 程序,调用我们的增加函数
C/C 代码如下,注意这样写你只能编译,不能连接,只能先生成OBJ
2.连接程序使用的几个步骤
我们要想使用上面几个程序,有多中方式去掉用,分别是
①丶手工编译C/C 程序,产生.obj文件,然后和汇编的.obj连接起来
编译我们的.cpp文件,产生.boj文件
命令是 cl /c 文件名
,然后把上面的MyAdd.obj(汇编程序编译的)
汇编程序的编译可以通过RadAsm,如果配置好了就直接F5编译即可,如果没配置好,可以手工用命令行编译,这里不讲解了,以前课程都有讲怎么编译,还有配置RadAsm
现在我们拿到了两个.obj文件
开始连接成一个.exe文件
手工连接
Link C/C 文件名.obj 汇编文件名.obj
理论上来说连个obj文件谁在前谁在后都没有区别,但是现在是我们的C/C 程序要调用 MyAdd,所以C/C 在前
打开CMD定位到我们的目录下(怎么打开百度搜索)
你可以直接文件夹上面输入CMD 回车,则会在当前的目录下,注意这里为了演示命令的截图
把两个obj文件拷贝了出来
开始连接
成功生成
打开程序校验一下
可以调用了
②丶将汇编程序的.obj文件,放到C/C 工程的目录下,利用工程特性,直接连接
我们可以把obj放到VC 中,这样我们可以直接编译连接使用,不用手工编译连接了
因为VC 6.0的Bug,我使用了一个插件修复,本来可以直接在 File(文件) - > Open(打开)的
解决VC的Bug,这里我直接提供一个Dll,把Dll放在VC 的目录的上一层,Addins中
操作步骤
1.右键属性打开文件位置
2.返回上一层目录
3.进入Addins文件夹下,把FileTool.dll拷贝进去
拷贝FileTool.dll
4.重新打开VC 6.0(注意管理员权限打开)
在菜单中点击 Tools(工具) -> Customize(定制) -> Add-ins And Macro Files
选中即可,如果再有问题,可以百度搜索,DLL会打包
上面解决了一个BUG,那么现在看下我们的工程中是否有了MyAdd.obj
现在编译连接则可以执行
③将obj定义为lib去使用
上面我们直接使用的obj,但是这样不太好,因为obj一多,工程文件就多了,不好维护(当然目的不再这里)
那么我们把obj定义为lib
怎么定义?,可以使用vc 自带的lib工具,如果配置了环境变量,则直接输入cmd,跳转到目录下,把MyAdd.obj生成为lib
输入lib 则会出现这个帮助,如果没有配置环境变量,那么输出lib则会出错,不过一般默认配置了,如果不会配置,请看前边的配置环境,RadAsm IDE的配置,里面内容一样
先介绍一下Lib工具的使用把
这个工具很简单, lib 选项(可选) 文件名(可选)
例如我们要把MyAdd.obj变为lib,则语法是
Lib MyAdd.obj...... (...代表了有多个obj 依次后面填写即可,注意中间不要加逗号,隔开即可)
生成了
遍历lib 看下有多少obj,着用list语法
语法
Lib /list lib名称
,,为了测试C2ASM我也打包了
现在我们可以使用Lib去编程了
现在只需要我们的工程中包含这个lib则可以使用,不用再把MyAdd.obj添加到工程中了
二丶汇编调用C写的函数
汇编调用C写的函数,那么是一样的,因为.obj都是一样的
首先看下汇编代码,汇编代码应该挺熟悉了,(不熟悉,看下以前的)
代码语言:javascript复制.386
.model flat,stdcall
option casemap:none
include msvcrt.inc
includelib msvcrt.lib ;C库的动态的静态使用
includelib MyAdd.lib ;lib调用,加载自己的lib
MyAdd proto c n1:dword, n2:dword
.const
g_szFmt db "1 2=%d", 0dh, 0ah, 0
g_szPause db "pause", 0
.data
.code
main proc c argc:dword, argv:dword
invoke MyAdd, 1, 2
invoke crt_printf, offset g_szFmt, eax
invoke crt_system, offset g_szPause
mov eax, 0
ret
main endp
end main
注意,因为我们这里使用printf了,我们是动态的静态使用,什么是静态的动态使用,前边已经说过了,不会的请看下前边的文章
那么现在我们新建个.c文件,里面单独写一个函数(不用工程了)
编译这个文件,生成.obj,然后和汇编程序的.obj连接,但是注意现在是汇编程序的.obj在前
因为汇编调用这个的obj
当然这两个obj我们也可以打包成lib使用,上面的汇编程序就是用的打包好的lib
所以这几个步骤就不写了,生成lib给汇编程序使用,至于手工的编译汇编程序,连接汇编程序其实不建议去用了,随着编译器的提升,以后加的选项越来越多
手工生成lib
Lib MyAdd.obj (MyAdd.obj 是手工编译的MyAdd.c的文件)
使用RadAsm编译连接1.asm程序
三丶汇编DLL的使用
像我们上面的生成的lib只能给C/C 使用,但是别的程序不见得能使用
所以我们写一个汇编的DLL,给C/C 程序使用
至于C/C 调用dll,那么有两种方式
一种是使用静态方式,就是加载dll的静态lib库(这个lib库中保存的是dll名称)
然后添加个头文件去使用
另一种是动态的loadlibrary,和GetprocessAddress去使用,动态的加载dll去使用
这里简单说下第一种,至于动态使用有开发知识的应该会调用,如果没开发知识也没有关系
因为咱们这个是汇编,不是再讲开发,(虽然开发很重要),但是咱们今天的主要内容不是上面所有的,压轴的在最下面
1.首先利用RadAsm新建一个dll的空工程,填写以下代码
汇编代码:
代码语言:javascript复制.386
.model flat,stdcall
option casemap:none
include windows.inc
.const
.data
.code
MyAdd Proc n1:DWORD, n2:DWORD
mov eax, n1
add eax, n2 ;写一个我们的MyAdd函数
ret
MyAdd endp
DllMain proc hModule:HINSTANCE , dwReason:DWORD, lpvReserved:LPVOID
mov eax, TRUE
ret
DllMain endp
end DllMain ;注意DLL的入口点要指定
然后在DEF文件导出我们的定义的代码
编译连接之后则会生成DLL,和保存DLL信息的lib
那么我们的工程可以使用了
静态使用
结果
至于代码,会上传课堂资料中
四丶压轴的内联汇编
1.内联汇编简单了解
首先我们会想,上面虽然完成的 汇编和C的互相调用,也解决的跨语言的DLL调用
但是觉着还是不好,为什么,因为可能我想写的汇编代码就那么一点,我还得生成DLL
或者生成lib
那么我们突发奇想,可不可以在C/C 中写汇编代码
比如我们写个int 3的中断指令
C/C 代码
代码语言:javascript复制#include "stdafx.h"
typedef int (*PFN)(int n1,int n2); //定义函数指针
int main(int argc, char* argv[])
{
//写二进制代码
unsigned char Code[] = {0xCC, 0x55, 0x8B, 0xEC, 0x8B,
0x45, 0x08, 0x03, 0x45, 0x0c, 0xc9, 0xC3};
int result = ((PFN)(void*)Code)(1, 2);
printf("%d",result);
return 0;
}
看到这个代码是不是晕了,没关系,谁叫我们是学汇编的,用OD调试看下
因为是Dbg程序,所以int 3指令对齐了,我们发现确实是断点到这里停止了,我们需要价格ret
直接打开int 3.exe看看是否会崩溃,如果崩溃则用OD调试,看下到底出现了什么情况
调试看看
发现是int3断点断下来了,我们发现,刚在我们写入的代码其实是二进制代码我们把它当做函数执行,也就是Call一下,我们写入的是一个加法的函数
难道汇编代码都要这样写吗
所以VC 6.0为我们提供了一个语法,叫做
_asm 注意前边是一个下划线
也可以加块语句去写
但是一般我们不这样写,因为这样会破坏寄存器环境所以开始和结束我们要保存一下寄存器的环境
Pushad 和push s是保存所有寄存器环境,和所有标志寄存器标志
我们看下VC 6.0的汇编到底做了什么
(在VC 6.0中内联汇编,可以下短点,然后ALT 8跳转到VC的汇编中查看)
是一样的
2.内联汇编调用函数
一丶普通调用的无参数调用
上面我们知道的怎么写内联汇编了,那么下边我们则可以把这个内联汇编定位为函数
写个ADD函数把
首先我们工程封装成一个函数
我们可以直接这样写,因为编译器内部已经帮我们压栈,平栈...各种东西都帮我们做了
我们一会ALT 8看下
现在我们要调用了,因为返回值问题,是怎么返回我们不知道,虽然我们知道是放在eax中
但是如果你改成int,那么我们要写为 return eax?显然是不可以的,而如果在_asm中
写ret,那么这个函数不知道你返回了所以先定义为void,我们一会解决返回值问题
调用:
我们要自己push,自己Call,又因为MyAdd是C调用约定,所以我们要自己平栈,
我们看下汇编代码
这个是我们调用的代码
我们看下MyAdd的时候里面做了什么
我们发现其实我们的核心代码就是两句,但是编译器帮我们做了很多事
从第一个循环申请局部变量上面就不说了,前边讲过了
(保存栈底,开辟局部空间,保存环境.....)
主要看下面,恢复完寄存器信息之后就开始释放局部变量空间,然后在Debug版本下会检测栈
是否平衡,如果不平衡,就弹个错误框,最后ret的时候,因为压入了两个参数还没有平栈
所以上面我们需要自己平栈,一个参数4个字节,所以 8
看下结果
2.解决普通的调用有返回值的问题
上面我们如果调用,那么就要自己内联,自己调用,但是很不方便,所以我们加个返回值
直接调用也可以,编译器智慧给警告,因为编译器支持这个语法
调用
直接调用即可
看下结果
那么就完美解决了
3.(inline)裸函数
我们这样想,上面编译器帮我们做了很多事情,我们不知道,我们有的时候这个函数就行自己用我们的写的代码
那么这个时候就可以用裸函数了,有关键字
_declspec(naked) ....
看下反汇编是什么
可以看到,明显的编译器没有帮我们做申请局部变量空间....等等一系列的操作
但是我们就要自己去写了
看下结果
4.内联调用API
如果内联了,那么就不支持invoke这种伪指令去操作了,都是真实的去写汇编代码
调用其实挺简单,加上数据段,和函数名就可以的,但是注意函数的头文件要包含(Windows.h)
5.内联寻找函数的参数
我们上面调用一个Add函数,自己还要计算
mov eax,[ebp 8]
Sub eax,[ebp 0ch]
但是其实这些我们的函数有参数了,我们可以使用参数来弄
比如
Mov eax,n1
Sub eax,n2 这样去写就行
反正怎么像伪指令怎么写,不支持也要想办法优化.
不然参数多了就容易混乱
课堂资料:
链接:http://pan.baidu.com/s/1jIJzsyE 密码:dtu4