Fenying

Angus’ Home.


04 Aug 2014

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

该文章迁移自作者的旧博客站点。
源地址:http://fenying.blog.163.com/blog/static/102055993201474111228639/

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

举个例子,memcpy 函数原型:

MemCpyB	PROC stdcall, pDst: DWORD, pSrc: DWORD, dwItems: DWORD

	cld

	mov esi, pSrc

	mov edi, pDst

	mov ecx, dwItems

	rep movsb

	mov eax, pDst

	ret

MemCpyB ENDP

一段示例代码:

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

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

#include <stdio.h>
extern "C" unsigned int __stdcall MemCpyB(void *pDst, void *pSrc, unsigned int uBytes);

#pragma comment(lib, "mem.lib") /*把汇编代码编译成 lib 使用*/

int main() {
    char *k;
    k = (char *)malloc(255);
    MemCpyB(k, "Hello World!", 13);
    printf("%s\n", k);
    free(k);
    return 0;
}

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

; Listing generated by Microsoft (R) Optimizing Compiler Version 18.00.30501.0 

	TITLE	E:\Coding\test\test\test.cpp
	.686P
	.XMM
	include listing.inc
	.model	flat

INCLUDELIB OLDNAMES

PUBLIC	??_C@_05CNOPHDHD@?$CF08x?6?$AA@			; `string'
PUBLIC	??_C@_0N@GCDOMLDM@Hello?5World?$CB?$AA@		; `string'
PUBLIC	??_C@_03OFAPEBGM@?$CFs?6?$AA@			; `string'
EXTRN	_MemCpyB@12:PROC
EXTRN	__imp__printf:PROC
EXTRN	__imp__malloc:PROC
EXTRN	__imp__free:PROC
;	COMDAT ??_C@_03OFAPEBGM@?$CFs?6?$AA@
CONST	SEGMENT
??_C@_03OFAPEBGM@?$CFs?6?$AA@ DB '%s', 0aH, 00H		; `string'
CONST	ENDS
;	COMDAT ??_C@_0N@GCDOMLDM@Hello?5World?$CB?$AA@
CONST	SEGMENT
??_C@_0N@GCDOMLDM@Hello?5World?$CB?$AA@ DB 'Hello World!', 00H ; `string'
CONST	ENDS
;	COMDAT ??_C@_05CNOPHDHD@?$CF08x?6?$AA@
CONST	SEGMENT
??_C@_05CNOPHDHD@?$CF08x?6?$AA@ DB '%08x', 0aH, 00H	; `string'
CONST	ENDS
PUBLIC	_main
; Function compile flags: /Ogtp
; File e:\coding\test\test\test.cpp
;	COMDAT _main
_TEXT	SEGMENT
_main	PROC						; COMDAT

; 8    : int main() {

  00000	56		 push	 esi
  00001	57		 push	 edi

; 9    : 	char *k;
; 10   : 	k = (char *)malloc(255);

  00002	68 ff 00 00 00	 push	 255			; 000000ffH
  00007	ff 15 00 00 00
	00		 call	 DWORD PTR __imp__malloc

; 11   : 	printf("%08x\n", k);

  0000d	8b 35 00 00 00
	00		 mov	 esi, DWORD PTR __imp__printf
  00013	8b f8		 mov	 edi, eax
  00015	57		 push	 edi
  00016	68 00 00 00 00	 push	 OFFSET ??_C@_05CNOPHDHD@?$CF08x?6?$AA@
  0001b	ff d6		 call	 esi
  0001d	83 c4 0c	 add	 esp, 12			; 0000000cH

; 12   : 	MemCpyB(k, "Hello World!", 13);

  00020	6a 0d		 push	 13			; 0000000dH
  00022	68 00 00 00 00	 push	 OFFSET ??_C@_0N@GCDOMLDM@Hello?5World?$CB?$AA@
  00027	57		 push	 edi
  00028	e8 00 00 00 00	 call	 _MemCpyB@12

; 13   : 	printf("%08x\n", k);

  0002d	57		 push	 edi
  0002e	68 00 00 00 00	 push	 OFFSET ??_C@_05CNOPHDHD@?$CF08x?6?$AA@
  00033	ff d6		 call	 esi

; 14   : 	printf("%s\n", k);

  00035	57		 push	 edi
  00036	68 00 00 00 00	 push	 OFFSET ??_C@_03OFAPEBGM@?$CFs?6?$AA@
  0003b	ff d6		 call	 esi

; 15   : 	free(k);

  0003d	57		 push	 edi
  0003e	ff 15 00 00 00
	00		 call	 DWORD PTR __imp__free
  00044	83 c4 14	 add	 esp, 20			; 00000014H

; 16   : 	return 0;

  00047	33 c0		 xor	 eax, eax
  00049	5f		 pop	 edi
  0004a	5e		 pop	 esi

; 17   : }

  0004b	c3		 ret	 0
_main	ENDP
_TEXT	ENDS
END

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

MemCpyB	PROC stdcall USES edi esi ecx , pDst: DWORD, pSrc: DWORD, dwItems: DWORD

	cld

	mov esi, pSrc

	mov edi, pDst

	mov ecx, dwItems

	rep movsb

	mov eax, pDst

	ret

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

comments powered by Disqus