汇编与C互操作
来源:互联网 发布:办公必备软件 编辑:程序博客网 时间:2024/05/22 10:50
Binhua Liu
代码下载
前言
本文介绍3种汇编和C互操作的方法:
C语言内嵌汇编代码
C语言调用汇编过程
汇编调用C语言过程
本文开始前,我们先介绍如何配置我们的编译环境:
1)打开VS2008,创建一个VC++空工程。
2)在Solution Explorer上右击工程名,选择Custom Build Rules…,在弹出的对话框上勾选Microsoft Macro Assembler。
3)在Solution Explorer上右击工程名,选择属性,在弹出的对话框上选择Configuration Properties->Linker->System->SubSystem,选择Console (/SUBSYSTEM:CONSOLE)
4)如果需要创建汇编源文件,在Solution Explorer上右击工程名->Add->New Item,在打开的对话框上选择C++ File(.cpp),命名为***.asm。
C语言内嵌汇编代码
在C语言中可以通过__asm伪指令插入汇编代码,语法如下
__asm 汇编指令
或者:
__asm{汇编指令汇编指令...}
我发现,VC编译器不会为在__asm段中用到的寄存器保存/恢复状态,这意味着什么呢?比如在__asm中使用了ECX寄存器,在函数前缀和后缀中很可能没有保存/恢复ECX的代码(除非函数的C代码部分用到了ECX),那么对于调用者来说,ECX寄存器的状态在调用该函数后被破坏了,所以我们最好自己在__asm段中保存/恢复用到的寄存器,看下面的例子:
int AddNumber(int number1,int number2){__asm{push ecxmov ecx,number1add ecx,number2mov eax,ecxpop ecx}}
C语言调用汇编过程
在进入正题前,我们需要先来简单讲解一下C语言的名字修饰规则。在C/C++编译时,VC编译器会把函数名根据一定规则进行修饰应用到生成的对象文件、列表文件等文件中。在链接时,链接器只知道函数的修饰名并用它进行链接。函数的修饰规则根据函数采用的调用约定不同而不同,我们介绍2中常用的调用约定的名字修饰:
C语言的__cdecl调用约定:在函数名前加下划线”_”。
C语言的__stdcall调用约定:在函数名前加下划线”_”,在后面加@,后面跟参数的字节数总和,如_GetStdHandle@4。
C++的名字修饰规则和C完全不同,我们这里不再具体说明,大家可以参考MSDN的介绍。
为什么要提到名字修饰呢?我们来考虑下面这种情形:在C中声明了采用__cdecl调用规范的外部过程AddNumber,这个外部过程是在汇编源文件中定义的。由于C语言采用了名字修饰,在生成的对象文件中AddNumber的修饰名为_AddNumber。然而,在汇编源文件中,过程没有使用名字修饰(我们也可以为汇编使用名字修饰,稍后再涉及),生成的对象文件中函数名依然为AddNumber,显然,这时候链接出错。解决的办法很简单,在汇编代码中,把函数名命名为_AddNumber即可。看下面的例子:
//main.cpp#include "iostream"using namespace std;extern "C" int __cdecl AddNumber(int number1,int number2);int main(){int i=AddNumber(1,3);cout<<i<<endl;return 0;}
;AddNumber.asmTITLE Add Number.386.MODEL flat.code_AddNumber PROCpush ebpmov ebp,espmov eax,[ebp+8]add eax,[ebp+12]pop ebpret_AddNumber ENDPEND
在main.cpp文件中,对引用的外部汇编过程AddNumber进行声明使用了extern “C”修饰符,这是由于VC编译器默认使用C++编译器,那就就会使用C++的名字修饰规则。extern “C”告诉编译器,AddNumber是一个C函数,编译时使用C的名字修饰规则。声明中的__cdecl可以省略,因为VC编译默认就是使用__cdecl调用约定的。
如果AddNumber采用__stdcall调用规范,则在main.cpp函数中声明为extern "C" int __stdcall AddNumber(int number1,int number2); AddNumber.asm中函数名修改为_AddNumber@8,函数返回指令修改为ret 8,代码如下:
;AddNumber.asmTITLE Add Number.386.MODEL flat.code_AddNumber@8 PROCpush ebpmov ebp,espmov eax,[ebp+8]add eax,[ebp+12]pop ebpret 8_AddNumber@8 ENDPEND
事实上,汇编代码也可以使用C调用约定和名字修饰规则,在代码中把模式定义为.MODEL flat,C,或者.MODEL flat,stdcall,汇编将使用__cdecl或者__stdcall调用约定和对应的修饰名。这样,在汇编代码中,我们只需要使用和C相同的原始的函数名称即可,而不用手工在源代码中使用修饰名了,代码如下(main.cpp代码不变):
;AddNumber.asmTITLE Add Number.386.MODEL flat,C.codeAddNumber PROC,number1:SDWORD,number2:SDWORDmov eax,number1add eax,number2retAddNumber ENDPEND
显然,这种方法最简单,不用去关心名字修饰规则。但是,通过了解名字修饰在其中的作用,我们可以知其然,也知其所以然。
汇编调用C语言过程
如果我们采取这样的调用方式:C主函数->汇编过程->C过程,那比较简单,我们只要处理好名字修饰的问题就可以了。本节主要讲解如何通过汇编主函数调用C过程,这种情况更复杂些,我们希望解决下面这些问题:
1)如何能在汇编主函数中能够调用C语言的库函数?
2)我们希望汇编主函数能够调用__stdcall和__cdelc两种调用约定的函数,那么我们只能把模式声明为.MODEL flat,而不是.MODEL flat,C,或者.MODEL flat,stdcall。
3)还有就是C语言运行时的入口点函数的问题,我们下面再讨论。
让我们先来看看代码:
;main.asmTITLE main .386.MODEL flat.datainput BYTE "the result is %d", 00HEXTRN_printf:PROCEXTRN _AddNumber:PROC.code_mainPROCpush ebpmov ebp,esppush 5push 4call _AddNumberadd esp,8pushEAXpushOFFSET inputcall_printfaddesp, 8pop ebpret_mainENDPEND
//AddNumber.cppint AddNumber(int number1,int number2){return number1+number2;}
以上代码除了前言中的配置外,还要进行以下配置才能运行:
1)在AddNumber.cpp上右击属性,在打开的对话框上选择C/C++->Advanced->Compile as,把Compile as C++ code修改为 Compile as C code。这就使用C而不是C++的名字修饰规则来编译AddNumber.cpp。
2)在工程上右击属性,在打开的对话框上选择Configuration Properties->Linker->Command Line,在文本框中添加:msvcrtd.lib,这样,程序链接时将链接C运行时库,就能使用C语言的库函数了。其实,这一步也可以省略,如果我们查看编译器用AddNumber.cpp生成的汇编代码,你会发现这样的语句:INCLUDELIB MSVCRTD。由于cpp源文件都会默认添加对C运行时库的引用,这就是为什么这步可以省略。但是,如果工程中没有任何引用C运行时库的源文件,而我们又希望使用C语言的库函数,那么就需要这一步配置。
现在,我们来研究我们上面的代码。我们通过”EXTRN”来声明外部过程,如C语言库函数printf,由于名字修饰,因此这里声明为_printf。
这里最关键的是程序的入口点问题。使用C运行时库的程序(也就是一般的C/C++程序),实际的程序入口点为mainRTSetup,然后mainRTSetup调用main函数或者wmain函数(如果是窗口程序而非控制台,则为WinMain或者wWinMain)。我们的工程由于使用了C运行时库,再考虑到名字修饰,因此主函数名必须是_main或者_wmain。在汇编工程中,我们会在源文件中用“END _main”结尾来标识_main为入口点函数,但是这个工程中_main并不是入口点函数,所以需要以END结尾。同样的道理,汇编工程中我们会以语句INVOKE ExitProcess,0来结束入口点函数,而这里_main函数则不需要。相反的,由于_main函数结束后会返回到mainRTSetup函数,因此_main应该加上必要的前缀和后缀来保存/恢复寄存器,而汇编工程的入口点函数我们不保存/恢复寄存器也没有关系。
或许,仍然有办法在使用C运行时库的情况下直接把_main直接设置为入口点函数,但是我的尝试都失败了,就不赘述。
TroubleShooting
作者在写本文时也遇到了许多问题,或许大家还会遇到一些其他问题,如果你的汇编源文件不能通过编译或链接,尝试这个办法:首先,写一个C语言版的源文件来替代汇编源文件,例如先用AddNumber.cpp取代AddNumber.asm,然后确保整个工程能够编译并运行。再右击AddNumber.cpp,打开属性对话框,选择C/C++->Output Files->Assembler Output,选择Assembly With Source Code (/FAs)。编译AddNumber.cpp,然后在AddNumber.cpp目录的debug子目录中你可以发现一个叫做AddNumber.asm的文件,这是编译器为AddNumber.cpp生成的汇编源文件,可以参考这个文件对你的汇编代码进行排错。
声明
本文为Binhua Liu原创作品。本文允许复制,修改,传递,但不允许用于商业用途。转载请注明出处。本文发表于2010年10月6日。
- 汇编与C互操作
- C与汇编互调实例
- 汇编与C....
- 汇编与C配合
- Keil c与汇编
- C语言与汇编
- 汇编与C语言
- 汇编学习-汇编指令与C语言
- 汇编与C/C++内联嵌入汇编
- C与ARM汇编之一
- Intel 汇编与C语言
- C与汇编混合编程
- C与汇编(转)
- c与汇编的关系
- C与汇编混合编程
- 汇编与C融合编程
- C与汇编混合编程
- c与汇编混合编程
- C/C++堆栈指引
- ANDROID L——Material Design详解(视图和阴影)
- [Windbg笔记] 调试偶发性Bug
- 为什么要用作文
- [汇编版]冒泡排序、快速排序、堆排序
- 汇编与C互操作
- 两道设计模式的面试题
- How To Create USB Dos Boot Disk
- VB Script 如何使用XSD验证XML文档格式
- 批处理:修改COM端口号
- 瘟神的尾行 -- Rootkit技术发展史 (转载)
- 在程序中设置读、写、执行的硬件断点
- 将托管dll注入到非托管进程中
- 提升进程权限-OpenProcessToken等函数的用法(转载)