vtable bug with COM interfaces--a possible hint at the cause

Benoit Goudreault-Emond bge@crosswinds.net
Mon May 1 12:47:00 GMT 2000


-----BEGIN PGP SIGNED MESSAGE-----


OK, I've poked around a bit in the gcc -S output, and I believe I have found
what ``triggers'' the vtable bug for COM interfaces on Win32/i386 platforms.

In summary, this bug happens when we compile using -fvtable-thunks and then try
to use a COM interface in the C++ compiler (the COM interface has
__attribute__((com_interface)) attached to it).  The behaviour seen by the
user of the code will usually be random crashes; if the user is the one who
supplies the interface, the latter will realize the wrong method of the
interface gets called.  This behaviour does not occur when the code is
re-written in pure C (using the ugly ->lpVtbl syntax, for those familiar with
this dark area of Win32 programming)

Code was compiled with:

g++ -fvtable-thunks -fno-exceptions -fno-rtti -o mkscut++ mkscut++.cc \
    -lshell32 -lole32 -luuid

It seems to happen when one uses an interface that's not directly derived from
IUnknown.  In my case, it was IPersistFile, which is declared like this in
objidl.h:

EXTERN_C const IID IID_IPersist;
#undef INTERFACE
#define INTERFACE IPersist
DECLARE_INTERFACE_(IPersist,IUnknown)
{
	STDMETHOD(QueryInterface)(THIS_ REFIID,PVOID*) PURE;
	STDMETHOD_(ULONG,AddRef)(THIS) PURE;
	STDMETHOD_(ULONG,Release)(THIS) PURE;
	STDMETHOD(GetClassID)(THIS_ LPCLSID) PURE;
};

EXTERN_C const IID IID_IPersistFile;
#undef INTERFACE
#define INTERFACE IPersistFile
DECLARE_INTERFACE_(IPersistFile,IPersist)
{
	STDMETHOD(QueryInterface)(THIS_ REFIID,PVOID*) PURE;
	STDMETHOD_(ULONG,AddRef)(THIS) PURE;
	STDMETHOD_(ULONG,Release)(THIS) PURE;
	STDMETHOD(GetClassID)(THIS_ CLSID) PURE;
	STDMETHOD(IsDirty)(THIS) PURE;
	STDMETHOD(Load)(THIS_ LPCOLESTR,DWORD) PURE;
	STDMETHOD(Save)(THIS_ LPCOLESTR,BOOL) PURE;
	STDMETHOD(SaveCompleted)(THIS_ LPCOLESTR) PURE;
	STDMETHOD(GetCurFile)(THIS_ LPOLESTR*) PURE;
};

(NOTE: for those unfamiliar with win32 programming, in C++,
- - DECLARE_INTERFACE_(IA, IB) maps to
  struct __attribute__((cominterface)) IA : public IB

- - STDMETHOD(A) maps to
  virtual HRESULT __attribute__((stdcall)) A

- - STDMETHOD_(A, B) maps to
  virtual B __attribute__((stdcall)) A

- - THIS maps to ``void'', and THIS_ maps to nothing
- - EXTERN_C maps to ``extern "C"''
- - PURE maps to ``=0''
- - HRESULT is really an unsigned long
- - LPCOLESTR is a const wchar_t*
- - DWORD is an unsigned long, BOOL is an int)

In my code, I call IPersistFile::Save.  Here's a relevant part of the assembly
code (this snippet is generated from lines 103-104 of mkscut++.cc):

	addl $16,%esp
	addl $-8,%esp
	movl -4(%ebp),%edx
	movl (%edx),%eax
	addl $28,%eax
	pushl $LC6
	movl -4(%ebp),%edx
	pushl %edx
	movl (%eax),%ebx
	call *%ebx

The *same* code in C (using the ->lpVtbl syntax) yields the following (from
lines 104-106 of mkscut.c):

	addl $16,%esp
	addl $-8,%esp
	addl $-4,%esp
	movl -8(%ebp),%edx
	movl (%edx),%eax
	pushl $1
	pushl $_w_path.12
	movl -8(%ebp),%edx
	pushl %edx
	movl 24(%eax),%ebx
	call *%ebx

G++ seems to offset the vtbl pointer through an add instruction, whereas GCC
does it directly in the movl instruction (since for GCC it's simply a
structure member lookup).  Notice how the movl 24(%eax),%ebx turns into an 
addl $28,%eax --- IOW, one ``slot'' too far in the vtable.

If we change the IPersistFile declaration to derive from IUnknown in objidl.h,
*the problem goes away*.  It seems G++ thinks there should be an extra 4 byte
offset because of this.

Unfortunately, I have no patch to fix this, only a reliable way of reproducing
the problem.  Hopefully, this should help the relevant people figure out what
is going on in there.  Of course, my suggestion of what's wrong with it may be
off the wall, but since changing the derivation from IPersist to IUnknown fixes
the problem, I think I'm on to something.

I'm including 2 files, all compressed with gzip -9: the original source for the
C++ version, and the original source only for the C version.  The compressed,
pre-processed source is big, and the mailing list won't accept it, so I
unfortunately can't include it.  I've got it on disk, though, so just drop me a
line if you believe it would help track down the bug.

The relevant code is in function makeShortcut().  You may disregard the
printf() statements; they're leftovers from me trying to figure out what
had failed.

Please CC any further questions you may have to my e-mail address, as I'm not
on the gcc-bugs list.

- -- 
Benoit Goudreault-Emond -- Reply to: bge@crosswinds.net
CoFounder, KMS Group.  Student, B. CompEng, Concordia University.
A proud user of Linux---I'd rather work than nursemaid my computer.
My homepage (such as it is): http://www.crosswinds.net/~bge

-----BEGIN PGP SIGNATURE-----
Version: 2.6.3ia
Charset: noconv

iQCVAwUBOQ3gE1qhoy6gYXzFAQEVSAQAhdZ2JdAt11SLk/C5wAFvMdO2d2HioBpU
3G+E6L9WeHeGwCGGUtb7YcmArDidwJ6uVcWQp252azDEGUrQq3CXwXhvfs18t0aX
OB2e/2dGOSqftiKannivPSjrXX1pwoXpFe7erl+piIrSy/sEef2PYCwH5W3wCrWU
I9fZ14WfWWk=
=xnmO
-----END PGP SIGNATURE-----
-------------- next part --------------
A non-text attachment was scrubbed...
Name: mkscut++.cc.gz
Type: application/x-gzip
Size: 1256 bytes
Desc: not available
URL: <http://gcc.gnu.org/pipermail/gcc-bugs/attachments/20000501/1d3fc1a7/attachment.bin>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: mkscut.c.gz
Type: application/x-gzip
Size: 1303 bytes
Desc: not available
URL: <http://gcc.gnu.org/pipermail/gcc-bugs/attachments/20000501/1d3fc1a7/attachment-0001.bin>


More information about the Gcc-bugs mailing list