C和汇编混合编译

发布时间 2023-12-21 23:27:09作者: c3n1g

有时候在写C语言项目的时候会需要用到汇编代码直接操作寄存器、栈之类更加底层的东西,所以在这里写一下C和汇编混合编程的几种方法(Windows和Linux需要分开讨论)

Windows

Visual Studio

Windows下常用的开发环境是Visual Studio,对于x86来说vs默认支持内联汇编,直接通过__asm关键词即可在函数体内嵌入汇编,例如

int add(int a, int b) {
    __asm {
        mov eax, a
        mov ebx, b
        add eax, ebx
    }
}

int main() {
    add(1, 2);
    return 0;
}

直接编译是没有错误的,而且x86还支持通过__declspec(naked)指定裸函数,正常的函数都是有函数序言和函数尾声的(即push ebp、pop ebp之类的),有时候我们并不需要这些东西,就可以声明裸函数,有我们自己来操控栈平衡

int __declspec(naked) add(int a, int b) {
    __asm {
        mov eax, a
        mov ebx, b
        add eax, ebx
        ret
    }
}

int main() {
    add(1, 2);
    return 0;
}

直接编译也没有任何问题

而且反编译来看确实没有乱七八糟的东西存在了。

但是从x64开始,VS就不再支持内联汇编了,而且也不支持naked关键字了。这里就可以切换项目的Platform Toolset为clang,clang默认支持x64内联汇编。Platform Toolset默认情况下是VS

点开来找到LLVM clang-cl

如果没有这个选项,可以打开vs安装工具,直接在单个组件里搜索LLVM,将两个组件都安装即可

然后直接切换为x64编译,是没有问题的

但是不支持裸函数。这里只能采取另一种方法,即额外创建一个asm,然后将asm文件链接到项目里。首先创建一个asm文件

extern mySub : PROC ; 引入C文件中的函数和全局变量
extern num : dw

.code
myAdd PROC
        call mySub
        mov ecx, dword ptr [num]
        add eax, ecx
        ret
myAdd ENDP
end

再创建一个C文件

extern int myAdd(int a, int b); // 引入汇编文件中的函数

int num = 0;

int mySub(int a, int b) {
    return a - b;
}

int main() {
    myAdd(1, 2);
    return 0;
}

可以看到两者之间互相引用是通过extern关键字来实现的。然后设置汇编文件的属性

再General中设置Item Type为自定义构建工具,点击应用后再去自定构建工具的General中设置如下两个东西

一个是命令行,一个是输出路径

ml64 $(InDir)%(fileName).obj /c %(fileName).asm
$(InDir)%(fileName).obj;$(Outputs)

注意这里Platform Toolset设置为默认的vs或者clang-cl都可以,直接编译可以看到没有报错

同时在反汇编中可以看到我们用汇编编写的函数

这种混合编程x86也适用,只不过需要将ml64改为ml

Clion

我个人更喜欢用Clion来编写C项目,这里Toolchains用的是VS的。x86内联汇编不说了,默认支持,主要来看x64的配置。如果要实现内联汇编,需要设置Toolchains为clang

这里clang-cl可以是vs的,也可以是另外安装的,然后添加CMake的选项让cmake用clang-cl编译

编译是没有问题的

如果要用混合编程,即创建一个asm文件链接到项目里,首先在CMakeLists.txt添加以下内容

主要是为了让cmake开启ASM_MASM支持,然后在Cmake设置选项,指定CMAKE_ASM_MASM_COMPILE为ml64

然后编译就没有任何问题