michael@0: /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* michael@0: typeinfo.cpp michael@0: michael@0: Speculatively use RTTI on a random object. If it contains a pointer at offset 0 michael@0: that is in the current process' address space, and that so on, then attempt to michael@0: use C++ RTTI's typeid operation to obtain the name of the type. michael@0: michael@0: by Patrick C. Beard. michael@0: */ michael@0: michael@0: #include michael@0: #include michael@0: michael@0: #include "nsTypeInfo.h" michael@0: michael@0: extern "C" void NS_TraceMallocShutdown(); michael@0: michael@0: struct TraceMallocShutdown { michael@0: TraceMallocShutdown() {} michael@0: ~TraceMallocShutdown() { michael@0: NS_TraceMallocShutdown(); michael@0: } michael@0: }; michael@0: michael@0: extern "C" void RegisterTraceMallocShutdown() { michael@0: // This instanciates the dummy class above, and will trigger the class michael@0: // destructor when libxul is unloaded. This is equivalent to atexit(), michael@0: // but gracefully handles dlclose(). michael@0: static TraceMallocShutdown t; michael@0: } michael@0: michael@0: class IUnknown { michael@0: public: michael@0: virtual long QueryInterface() = 0; michael@0: virtual long AddRef() = 0; michael@0: virtual long Release() = 0; michael@0: }; michael@0: michael@0: #if defined(MACOS) michael@0: michael@0: #include michael@0: michael@0: class AddressSpace { michael@0: public: michael@0: AddressSpace(); michael@0: Boolean contains(void* ptr); michael@0: private: michael@0: ProcessInfoRec mInfo; michael@0: }; michael@0: michael@0: AddressSpace::AddressSpace() michael@0: { michael@0: ProcessSerialNumber psn = { 0, kCurrentProcess }; michael@0: mInfo.processInfoLength = sizeof(mInfo); michael@0: ::GetProcessInformation(&psn, &mInfo); michael@0: } michael@0: michael@0: Boolean AddressSpace::contains(void* ptr) michael@0: { michael@0: UInt32 start = UInt32(mInfo.processLocation); michael@0: return (UInt32(ptr) >= start && UInt32(ptr) < (start + mInfo.processSize)); michael@0: } michael@0: michael@0: const char* nsGetTypeName(void* ptr) michael@0: { michael@0: // construct only one of these per process. michael@0: static AddressSpace space; michael@0: michael@0: // sanity check the vtable pointer, before trying to use RTTI on the object. michael@0: void** vt = *(void***)ptr; michael@0: if (vt && !(unsigned(vt) & 0x3) && space.contains(vt) && space.contains(*vt)) { michael@0: IUnknown* u = static_cast(ptr); michael@0: const char* type = typeid(*u).name(); michael@0: // make sure it looks like a C++ identifier. michael@0: if (type && (isalnum(type[0]) || type[0] == '_')) michael@0: return type; michael@0: } michael@0: return "void*"; michael@0: } michael@0: michael@0: #endif michael@0: michael@0: // New, more "portable" Linux code is below, but this might be a useful michael@0: // model for other platforms, so keeping. michael@0: //#if defined(linux) michael@0: #if 0 michael@0: michael@0: #include michael@0: #include michael@0: michael@0: static jmp_buf context; michael@0: michael@0: static void handler(int signum) michael@0: { michael@0: longjmp(context, signum); michael@0: } michael@0: michael@0: #define attempt() setjmp(context) michael@0: michael@0: class Signaller { michael@0: public: michael@0: Signaller(int signum); michael@0: ~Signaller(); michael@0: michael@0: private: michael@0: typedef void (*handler_t) (int signum); michael@0: int mSignal; michael@0: handler_t mOldHandler; michael@0: }; michael@0: michael@0: Signaller::Signaller(int signum) michael@0: : mSignal(signum), mOldHandler(signal(signum, &handler)) michael@0: { michael@0: } michael@0: michael@0: Signaller::~Signaller() michael@0: { michael@0: signal(mSignal, mOldHandler); michael@0: } michael@0: michael@0: // The following are pointers that bamboozle our otherwise feeble michael@0: // attempts to "safely" collect type names. michael@0: // michael@0: // XXX this kind of sucks because it means that anyone trying to use michael@0: // this without NSPR will get unresolved symbols when this library michael@0: // loads. It's also not very extensible. Oh well: FIX ME! michael@0: extern "C" { michael@0: // from nsprpub/pr/src/io/priometh.c (libnspr4.so) michael@0: extern void* _pr_faulty_methods; michael@0: }; michael@0: michael@0: static inline int michael@0: sanity_check_vtable_i386(void** vt) michael@0: { michael@0: // Now that we're "safe" inside the signal handler, we can michael@0: // start poking around. If we're really an object with michael@0: // RTTI, then the second entry in the vtable should point michael@0: // to a function. michael@0: // michael@0: // Let's see if the second entry: michael@0: // michael@0: // 1) looks like a 4-byte aligned pointer michael@0: // michael@0: // 2) points to something that looks like the following michael@0: // i386 instructions: michael@0: // michael@0: // 55 push %ebp michael@0: // 89e5 mov %esp,%ebp michael@0: // 53 push %ebx michael@0: // michael@0: // or michael@0: // michael@0: // 55 push %ebp michael@0: // 89e5 mov %esp,%ebp michael@0: // 56 push %esi michael@0: // michael@0: // (which is the standard function prologue generated michael@0: // by egcs, plus a ``signature'' instruction that appears michael@0: // in the typeid() function's implementation). michael@0: unsigned char** fp1 = reinterpret_cast(vt) + 1; michael@0: michael@0: // Does it look like an address? michael@0: unsigned char* ip = *fp1; michael@0: if ((unsigned(ip) & 3) != 0) michael@0: return 0; michael@0: michael@0: // Does it look like it refers to the standard prologue? michael@0: static unsigned char prologue[] = { 0x55, 0x89, 0xE5 }; michael@0: for (unsigned i = 0; i < sizeof(prologue); ++i) michael@0: if (*ip++ != prologue[i]) michael@0: return 0; michael@0: michael@0: // Is the next instruction a `push %ebx' or `push %esi'? michael@0: if (*ip == 0x53 || *ip == 0x56) { michael@0: return 1; michael@0: } michael@0: michael@0: // Nope. There's another variant that has a `sub' instruction, michael@0: // followed by a `cmpl' and a `jne'. Check for that. michael@0: if (ip[0] == 0x83 && ip[1] == 0xec // sub michael@0: && ip[3] == 0x83 && ip[4] == 0x3d // cmpl michael@0: && ip[10] == 0x75 // jne michael@0: ) { michael@0: return 1; michael@0: } michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: static inline int michael@0: sanity_check_vtable_ppc(void** vt) michael@0: { michael@0: // XXX write me! michael@0: return 1; michael@0: } michael@0: michael@0: #if defined(__i386) michael@0: # define SANITY_CHECK_VTABLE(vt) (sanity_check_vtable_i386(vt)) michael@0: #elif defined(PPC) michael@0: # define SANITY_CHECK_VTABLE(vt) (sanity_check_vtable_ppc(vt)) michael@0: #else michael@0: # define SANITY_CHECK_VTABLE(vt) (1) michael@0: #endif michael@0: michael@0: const char* nsGetTypeName(void* ptr) michael@0: { michael@0: // sanity check the vtable pointer, before trying to use RTTI on the object. michael@0: void** vt = *(void***)ptr; michael@0: if (vt && !(unsigned(vt) & 3) && (vt != &_pr_faulty_methods)) { michael@0: Signaller s1(SIGSEGV); michael@0: if (attempt() == 0) { michael@0: if (SANITY_CHECK_VTABLE(vt)) { michael@0: // Looks like a function: what the hell, let's call it. michael@0: IUnknown* u = static_cast(ptr); michael@0: const char* type = typeid(*u).name(); michael@0: // EGCS seems to prefix a length string. michael@0: while (isdigit(*type)) ++type; michael@0: return type; michael@0: } michael@0: } michael@0: } michael@0: return "void*"; michael@0: } michael@0: michael@0: #endif michael@0: michael@0: #if defined(linux) || defined(XP_MACOSX) michael@0: michael@0: #define __USE_GNU michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: const char* nsGetTypeName(void* ptr) michael@0: { michael@0: #if defined(__GXX_ABI_VERSION) && __GXX_ABI_VERSION >= 100 /* G++ V3 ABI */ michael@0: const int expected_offset = 8; michael@0: const char vtable_sym_start[] = "_ZTV"; michael@0: const int vtable_sym_start_length = sizeof(vtable_sym_start) - 1; michael@0: #else michael@0: const int expected_offset = 0; michael@0: const char vtable_sym_start[] = "__vt_"; michael@0: const int vtable_sym_start_length = sizeof(vtable_sym_start) - 1; michael@0: #endif michael@0: void* vt = *(void**)ptr; michael@0: Dl_info info; michael@0: // If dladdr fails, if we're not at the expected offset in the vtable, michael@0: // or if the symbol name isn't a vtable symbol name, return "void*". michael@0: if ( !dladdr(vt, &info) || michael@0: ((char*)info.dli_saddr) + expected_offset != vt || michael@0: !info.dli_sname || michael@0: strncmp(info.dli_sname, vtable_sym_start, vtable_sym_start_length)) michael@0: return "void*"; michael@0: michael@0: // skip the garbage at the beginning of things like michael@0: // __vt_14nsRootBoxFrame (gcc 2.96) or _ZTV14nsRootBoxFrame (gcc 3.0) michael@0: const char* rv = info.dli_sname + vtable_sym_start_length; michael@0: while (*rv && isdigit(*rv)) michael@0: ++rv; michael@0: return rv; michael@0: } michael@0: michael@0: #endif michael@0: michael@0: #ifdef XP_WIN32 michael@0: const char* nsGetTypeName(void* ptr) michael@0: { michael@0: //TODO: COMPLETE THIS michael@0: return "void*"; michael@0: } michael@0: michael@0: #endif //XP_WIN32