• C/C++与汇编混合编程


    1. C/C++调用汇编

    C/C++想调用汇编代码必须要注意名称修饰的问题

    名称修饰(name decoration): 一种标准的C/C++编译技术, 通过添加字符来修改函数名, 添加的字符指明了每个函数参数的确切类型。主要是为了支持函数重载, 但对于汇编来说其问题在于, C/C++编译器让链接器去找被修饰过的名称而非原始名称

    名称修饰说白了就是C/C++源代码经过编译器编译后, 函数和变量名称发生了变化, 链接器会去找变化后的名字而非源码中定义的名字。
    影响名称修饰的主要因素如下:

    • 编程语言, 比如C和C++名称修饰就不同
    • 调用约定, 比如C和STDCALL的名称修饰不同

    举个例子, C和C++之间的名称修饰就是不同

    a. C语言中的名称修饰格式以及C与汇编混合编程

    源码如下:
    在这里插入图片描述
    看一下其生成obj文件:
    在这里插入图片描述
    发现经过VS的C编译器编译后main和Add名称前多了下划线。由于C语言默认的函数调用约定是C调用约定。所以这就是C语言使用C调用约定下的名称修饰。

    _name

    接下去将Add的调用约定改成STDCALL
    在这里插入图片描述
    再来查看一下生成的obj文件, 发现其名称修饰方式发生了改变:
    在这里插入图片描述
    所以C语言STDCALL调用约定下的名称修饰方式是:

    _name@n

    这里的n代表压入栈帧参数的大小, 由于Add参数参别是2个int类型, 在x86下也就是8字节大小, 所以经过C编译器名称修饰后就变成了

    _Add@8

    总结一下, C编译器编译C源码时, 默认情况下函数使用C调用约定, 除非强制指定其他调用约定, 其编译后生成的obj文件中, 名称修饰的方法如下:

    C stdcall: _name@n
    C cdecl: _name

    接下来看一下汇编语言, 首先使用C调用约定, 经过汇编生成obj文件。
    在这里插入图片描述
    查看winhex发现:
    在这里插入图片描述
    不出乎意料, FindMax和main函数都使用了C调用方式的名称修饰, 但是ExitProcess却使用了STDCALL的名称修饰规则, 这很正常, 因为微软的API都使用STDCALL。
    由于C语言和汇编生成的obj目标文件中的名称修饰都是相同的, 所以可以得出结论:

    C可以直接调用使用C调用约定下的汇编代码, 无需任何改变

    下面实验一下:
    这是汇编源码:
    在这里插入图片描述
    将汇编生成的obj文件包含到项目内, 测试发现C语言调用C调用约定下的汇编没有问题:
    在这里插入图片描述
    如果说汇编使用STDCALL调用约定汇编生成对应的obj文件, 然后让C来进行调用, 结果就会发现这个熟悉的链接错误:
    在这里插入图片描述
    这个原因已经很明显了, 因为C语言默认使用C调用约定, 编译后生成的是_name格式的函数名, 当汇编使用STDCALL调用约定时, 生成的是_name@n格式的函数名, 当链接时自然就无法找到名称了。
    所以可以得出结论:

    C可以直接调用使用STDCALL调用约定下的汇编代码, 会出现无法解析外部符号的链接错误

    b. C++中的名称修饰格式以及C++与汇编混合编程

    使用C++编写一个程序, C++默认也使用C调用约定:
    在这里插入图片描述
    查看其生成的obj目标文件
    在这里插入图片描述
    可以发现经过C++编译器编译后, C调用约定下Add函数生成了一种非常奇怪的形式, 这主要是为了实现重载而做的。而main函数永远是C调用约定不会被C++编译器改变。

    这也就意味着C和C++其实也使不可以互相调用的

    因为如果你要用C++调用C代码。假设都使用C调用约定, C代码经C编译器生成了obj文件, 里面的名称修饰是:

    _name

    而C++编译器编译代码生成的obj里面使用的名称修饰是:

    ?Add@@YAHHH@Z

    当链接时, C++去找**?Add@@YAHHH@Z**结果只有_Add, 这是肯定不可能找到的。

    下面把Add函数变成STDCALL函数调用约定试试看:
    在这里插入图片描述
    可以发现其名称修饰变成了如下:

    ?Add@@YGHHH@Z // stdcall
    ?Add@@YAHHH@Z // C

    得出结论:

    C++下C和STDCALL调用约定有区别, 但是区别不大, 把A变成了G

    如果想要在C++下调用汇编代码, 只要把C++的名称修饰转换成C的就可以了。
    所以只要C++能调用C, 也就意味着C++可以调用汇编, 事实上也确实如此, 在C++源文件中, 函数添加extern "C"即可让C++函数使用C的名称修饰方法
    在这里插入图片描述
    C++源码生成obj, 放入winhex内便可发现, C++使用了C的名称修饰方式, 不管是什么调用约定, extern "C"都会让C++使用C的调用约定。
    在这里插入图片描述
    使用同样的方法就可以实现C++调用汇编代码了。
    在这里插入图片描述

    2. C/C++调用汇编的另一种方式: 内联汇编

    这种方式只能在x86下进行, 并没有什么特别的地方:
    在这里插入图片描述
    这里给一个例子, 是一个对称xor加密的小例子:

    #include 
    #include 
    #include 
    
    #define FILEBLK		(0x1000)
    
    using namespace std;
    
    VOID CryptoBlock(PBYTE pbBuf, DWORD dwBufSize, UCHAR bKey)
    {
    	__asm
    	{
    		mov esi, pbBuf
    		mov ecx, dwBufSize 
    		mov bl, bKey
    	L0:
    		xor BYTE PTR [esi], bl
    		inc esi 
    		loop L0
    	}
    	return;
    }
    
    BOOLEAN SymFileCrypto(LPCSTR pcszFilePathName, UCHAR bKey)
    {
    	HANDLE hFile = INVALID_HANDLE_VALUE;
    	HANDLE hNewFile = INVALID_HANDLE_VALUE;
    	BOOLEAN fOk = FALSE;
    	LARGE_INTEGER liFileSize = { 0 };
    	int iTotalBlk = 0;
    	BYTE bBuf[FILEBLK] = { 0 };
    	char szTmpFileName[] = "TmpFile";
    
    	DWORD dwReaded = 0;
    	DWORD dwWritten = 0;
    
    	__asm
    	{
    		// 参数检测
    		mov esi, pcszFilePathName
    		test esi, esi
    		jz Ending
    		// 打开文件
    		push NULL
    		push FILE_ATTRIBUTE_NORMAL
    		push OPEN_EXISTING
    		push NULL
    		push 0
    		push FILE_ALL_ACCESS
    		push esi
    		call CreateFile
    		// 文件句柄检查
    		cmp eax, -1
    		jne Next1 
    		jmp Ending 
    	Next1:
    		// 保存文件句柄
    		mov hFile, eax
    		// 获取文件大小
    		lea eax, liFileSize
    		push eax
    		push hFile
    		call GetFileSizeEx
    		// 计算总块数
    		mov eax, liFileSize.LowPart
    		mov edx, liFileSize.HighPart
    		mov ebx, FILEBLK
    		div ebx
    		test edx, edx 
    		jz Next2
    		inc eax 
    	Next2:
    		mov iTotalBlk, eax
    		// 创建新文件
    		push NULL
    		push FILE_ATTRIBUTE_NORMAL
    		push CREATE_ALWAYS
    		push NULL
    		push 0
    		push FILE_ALL_ACCESS
    		lea eax, szTmpFileName
    		push eax
    		call CreateFile
    		// 文件句柄检查
    		cmp eax, -1
    		jne Next3
    		jmp Ending
    	Next3:
    		mov hNewFile, eax 
    	Crypto:
    		// 读取文件内容
    		push NULL 
    		lea eax, dwReaded
    		push eax 
    		mov eax, FILEBLK
    		push eax 
    		lea eax, bBuf 
    		push eax
    		push hFile 
    		call ReadFile 
    		test eax, eax 
    		jz Ending 
    		// 加解密
    		xor eax, eax 
    		mov al, bKey 
    		push eax 
    		push dwReaded 
    		lea eax, bBuf
    		push eax
    		call CryptoBlock
    		// 写入文件内容
    		push NULL
    		lea eax, dwWritten 
    		push eax 
    		push dwReaded 
    		lea eax, bBuf 
    		push eax
    		push hNewFile 
    		call WriteFile
    		test eax, eax 
    		jz Ending 
    		mov ecx, iTotalBlk
    		dec ecx 
    		mov iTotalBlk, ecx
    		test ecx, ecx 
    		jnz Crypto
    		mov fOk, TRUE
    	}
    
    Ending:
    	__asm
    	{
    		cmp hNewFile, -1
    		jz Next4 
    		push hNewFile 
    		call CloseHandle 
    		mov hNewFile, NULL
    		Next4:
    		cmp hFile, -1
    		jz Next5
    		push hFile 
    		call CloseHandle 
    		mov hFile, NULL
    	Next5:
    		xor eax, eax 
    		mov al, fOk
    		test eax, eax 
    		jz Next6
    		// 删除源文件
    		mov eax, pcszFilePathName
    		push eax
    		call DeleteFile 
    		// 改名
    		mov eax, pcszFilePathName
    		push eax 
    		lea eax, szTmpFileName
    		push eax 
    		call MoveFile 
    		Next6:
    		mov esp, ebp
    		pop ebp 
    		ret 
    	}
    }
    
    
    int main(int argc, char **argv)
    {
    	if (argc != 3)
    	{
    		printf("usage: %s file key\r\n", argv[0]);
    		return(-1);
    	}
    
    	if (SymFileCrypto(argv[1], argv[2][0]))
    	{
    		printf("成功\r\n");
    	}
    	else
    	{
    		printf("失败\r\n");
    	}
    
    	return(0);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185

    (完)

  • 相关阅读:
    ASTM D 3801固体塑料垂直燃烧试验
    镍氢充电管理芯片-IC AH2185
    【案例实战】SpringBoot整合阿里云文件上传OSS
    Python运算符 成员运算符、身份运算符,三目运算符
    java编程基础总结——31.多线程编程相关知识
    OpenGL纹理转换谜团:纹理写入FRAMEBUFFER后的镜像现象
    金仓数据库KStudio使用手册(4. sql编辑)
    python基础之函数__name__属性
    Scala / Java - Guava Sets 高效实践
    【Javascript】数组的基本操作
  • 原文地址:https://blog.csdn.net/qq_37232329/article/details/133460128