C/C++与汇编混合编程中的函数声明

该文章根据 CC-BY-4.0 协议发表,转载请遵循该协议。
本文地址:https://fenying.net/post/2014/08/04/missing-reserving-registers-when-mixing-c-and-assembly/

最近写一个C小程序时尝试了下用汇编写一部分代码,封装成函数供 VC (2013)调用。Debug模式一切正常,但是到了Release模式下就直接崩溃了。

举个例子,memcpy 函数原型:

 1MemCpyB	PROC stdcall, pDst: DWORD, pSrc: DWORD, dwItems: DWORD
 2
 3	cld
 4
 5	mov esi, pSrc
 6
 7	mov edi, pDst
 8
 9	mov ecx, dwItems
10
11	rep movsb
12
13	mov eax, pDst
14
15	ret
16
17MemCpyB ENDP

一段示例代码:

1#include <stdio.h>
2extern "C" unsigned int __stdcall MemCpyB(void *pDst, void *pSrc, unsigned int uBytes);
3#pragma comment(lib, "mem.lib") /*把汇编代码编译成 lib 使用*/
4int main() {
5    char k[255];
6    MemCpyB(k, "Hello World!", 13);
7    printf("%s\n", k);
8    return 0;
9}

这个函数在静态栈里面工作完全正常,Release 模式也一样。但是如果是动态内存那就不一样了,比如:

 1#include <stdio.h>
 2extern "C" unsigned int __stdcall MemCpyB(void *pDst, void *pSrc, unsigned int uBytes);
 3
 4#pragma comment(lib, "mem.lib") /*把汇编代码编译成 lib 使用*/
 5
 6int main() {
 7    char *k;
 8    k = (char *)malloc(255);
 9    MemCpyB(k, "Hello World!", 13);
10    printf("%s\n", k);
11    free(k);
12    return 0;
13}

查看了下VC的汇编输出,如下图:

 1; Listing generated by Microsoft (R) Optimizing Compiler Version 18.00.30501.0 
 2
 3	TITLE	E:\Coding\test\test\test.cpp
 4	.686P
 5	.XMM
 6	include listing.inc
 7	.model	flat
 8
 9INCLUDELIB OLDNAMES
10
11PUBLIC	??_C@_05CNOPHDHD@?$CF08x?6?$AA@			; `string'
12PUBLIC	??_C@_0N@GCDOMLDM@Hello?5World?$CB?$AA@		; `string'
13PUBLIC	??_C@_03OFAPEBGM@?$CFs?6?$AA@			; `string'
14EXTRN	_MemCpyB@12:PROC
15EXTRN	__imp__printf:PROC
16EXTRN	__imp__malloc:PROC
17EXTRN	__imp__free:PROC
18;	COMDAT ??_C@_03OFAPEBGM@?$CFs?6?$AA@
19CONST	SEGMENT
20??_C@_03OFAPEBGM@?$CFs?6?$AA@ DB '%s', 0aH, 00H		; `string'
21CONST	ENDS
22;	COMDAT ??_C@_0N@GCDOMLDM@Hello?5World?$CB?$AA@
23CONST	SEGMENT
24??_C@_0N@GCDOMLDM@Hello?5World?$CB?$AA@ DB 'Hello World!', 00H ; `string'
25CONST	ENDS
26;	COMDAT ??_C@_05CNOPHDHD@?$CF08x?6?$AA@
27CONST	SEGMENT
28??_C@_05CNOPHDHD@?$CF08x?6?$AA@ DB '%08x', 0aH, 00H	; `string'
29CONST	ENDS
30PUBLIC	_main
31; Function compile flags: /Ogtp
32; File e:\coding\test\test\test.cpp
33;	COMDAT _main
34_TEXT	SEGMENT
35_main	PROC						; COMDAT
36
37; 8    : int main() {
38
39  00000	56		 push	 esi
40  00001	57		 push	 edi
41
42; 9    : 	char *k;
43; 10   : 	k = (char *)malloc(255);
44
45  00002	68 ff 00 00 00	 push	 255			; 000000ffH
46  00007	ff 15 00 00 00
47	00		 call	 DWORD PTR __imp__malloc
48
49; 11   : 	printf("%08x\n", k);
50
51  0000d	8b 35 00 00 00
52	00		 mov	 esi, DWORD PTR __imp__printf
53  00013	8b f8		 mov	 edi, eax
54  00015	57		 push	 edi
55  00016	68 00 00 00 00	 push	 OFFSET ??_C@_05CNOPHDHD@?$CF08x?6?$AA@
56  0001b	ff d6		 call	 esi
57  0001d	83 c4 0c	 add	 esp, 12			; 0000000cH
58
59; 12   : 	MemCpyB(k, "Hello World!", 13);
60
61  00020	6a 0d		 push	 13			; 0000000dH
62  00022	68 00 00 00 00	 push	 OFFSET ??_C@_0N@GCDOMLDM@Hello?5World?$CB?$AA@
63  00027	57		 push	 edi
64  00028	e8 00 00 00 00	 call	 _MemCpyB@12
65
66; 13   : 	printf("%08x\n", k);
67
68  0002d	57		 push	 edi
69  0002e	68 00 00 00 00	 push	 OFFSET ??_C@_05CNOPHDHD@?$CF08x?6?$AA@
70  00033	ff d6		 call	 esi
71
72; 14   : 	printf("%s\n", k);
73
74  00035	57		 push	 edi
75  00036	68 00 00 00 00	 push	 OFFSET ??_C@_03OFAPEBGM@?$CFs?6?$AA@
76  0003b	ff d6		 call	 esi
77
78; 15   : 	free(k);
79
80  0003d	57		 push	 edi
81  0003e	ff 15 00 00 00
82	00		 call	 DWORD PTR __imp__free
83  00044	83 c4 14	 add	 esp, 20			; 00000014H
84
85; 16   : 	return 0;
86
87  00047	33 c0		 xor	 eax, eax
88  00049	5f		 pop	 edi
89  0004a	5e		 pop	 esi
90
91; 17   : }
92
93  0004b	c3		 ret	 0
94_main	ENDP
95_TEXT	ENDS
96END

Release版本的代码是经过优化的,所以很多地方已经看不出原来的痕迹了。但是注意到一个很关键的东西,那就是edi, esi寄存器的使用。在我的函数里也用到了它,显然修改了它们的值又没有恢复回去,于是程序本身的节奏就错乱了。这些寄存器都应该在函数入口处保护好,如下:

 1MemCpyB	PROC stdcall USES edi esi ecx , pDst: DWORD, pSrc: DWORD, dwItems: DWORD
 2
 3	cld
 4
 5	mov esi, pSrc
 6
 7	mov edi, pDst
 8
 9	mov ecx, dwItems
10
11	rep movsb
12
13	mov eax, pDst
14
15	ret
16
17MemCpyB ENDP
comments powered by Disqus