/* ** Command & Conquer Generals Zero Hour(tm) ** Copyright 2025 Electronic Arts Inc. ** ** This program is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program. If not, see . */ /////////////////////////////////////////////////////////////////////////EA-V1 // $File: //depot/GeneralsMD/Staging/code/Libraries/Source/profile/profile_funclevel.cpp $ // $Author: mhoffe $ // $Revision: #4 $ // $DateTime: 2003/08/14 13:43:29 $ // // ©2003 Electronic Arts // // Function level profiling ////////////////////////////////////////////////////////////////////////////// #include "_pch.h" #include "../debug/debug.h" #include #ifdef HAS_PROFILE // TLS index (-1 if not yet initialized) static int TLSIndex=-1; // our own fast critical section static ProfileFastCS cs; // Unfortunately VC 6 doesn't support _pleave (or _pexit) so // we have to come up with our own type of implementation here... static void __declspec(naked) _pleave(void) { ProfileFuncLevelTracer *p; unsigned curESP,leaveAddr; _asm { push ebp // this will be overwritten down there... push ebp // setup standard stack frame push eax mov ebp,esp mov eax,esp sub esp,3*4 // 3 local DWord vars pushad mov [curESP],eax } curESP+=8; p=(ProfileFuncLevelTracer *)TlsGetValue(TLSIndex); leaveAddr=p->Leave(curESP); *(unsigned *)(curESP)=leaveAddr; _asm { popad add esp,3*4 // must match sub esp above pop eax pop ebp ret } } extern "C" void __declspec(naked) _cdecl _penter(void) { unsigned callerFunc,ESPonReturn,callerRet; ProfileFuncLevelTracer *p; _asm { push ebp push eax mov ebp,esp mov eax,esp sub esp,4*4 // 4 local DWord vars pushad // calc return address add eax,4+4 // account for push ebp and push eax mov ebx,[eax] // grab return address mov [callerFunc],ebx // get some more stuff add eax,4 mov [ESPonReturn],eax mov ebx,[eax] mov [callerRet],ebx // jam in our exit code mov dword ptr [eax],offset _pleave } // do we need a new stack tracer? if (TLSIndex==-1) TLSIndex=TlsAlloc(); p=(ProfileFuncLevelTracer *)TlsGetValue(TLSIndex); if (!p) { p=(ProfileFuncLevelTracer *)ProfileAllocMemory(sizeof(ProfileFuncLevelTracer)); new (p) ProfileFuncLevelTracer; TlsSetValue(TLSIndex,p); } // enter function p->Enter(callerFunc-5,ESPonReturn,callerRet); // cleanup _asm { popad add esp,4*4 // must match sub esp above pop eax pop ebp ret } } ProfileFuncLevelTracer *ProfileFuncLevelTracer::head=NULL; bool ProfileFuncLevelTracer::shuttingDown=false; int ProfileFuncLevelTracer::curFrame=0; unsigned ProfileFuncLevelTracer::frameRecordMask; bool ProfileFuncLevelTracer::recordCaller=false; ProfileFuncLevelTracer::ProfileFuncLevelTracer(void): stack(NULL), usedStack(0), totalStack(0), maxDepth(0) { ProfileFastCS::Lock lock(cs); next=head; head=this; } ProfileFuncLevelTracer::~ProfileFuncLevelTracer() { // yes, I know we leak... } void ProfileFuncLevelTracer::Enter(unsigned addr, unsigned esp, unsigned ret) { // must stack grow? if (usedStack>=totalStack) stack=(StackEntry *)ProfileReAllocMemory(stack,(totalStack+=100)*sizeof(StackEntry)); // save info Function *f=func.Find(addr); if (!f) { // new function f=(Function *)ProfileAllocMemory(sizeof(Function)); new (f) Function(this); f->addr=addr; f->glob.callCount=f->glob.tickPure=f->glob.tickTotal=0; for (int i=0;icur[i].callCount=f->cur[i].tickPure=f->cur[i].tickTotal=0; f->depth=0; func.Insert(f); } StackEntry &s=stack[usedStack++]; s.func=f; s.esp=esp; s.retVal=ret; ProfileGetTime(s.tickEnter); s.tickSubTime=0; f->depth++; // new max depth? if (usedStack>=maxDepth) maxDepth=usedStack; DLOG_GROUP(profile_stack,Debug::RepeatChar(' ',usedStack-1) << Debug::Hex() << this << " Enter " << Debug::Width(8) << addr << " ESP " << Debug::Width(8) << esp << " return " << Debug::Width(8) << ret << " level " << Debug::Dec() << usedStack ); } unsigned ProfileFuncLevelTracer::Leave(unsigned esp) { // get current "time" __int64 cur; ProfileGetTime(cur); while (usedStack>0) { // leave current function usedStack--; StackEntry &s=stack[usedStack], &sPrev=stack[usedStack-1]; Function *f=s.func; // decrease call depth // note: add global time only if call depth is 0 f->depth--; // insert caller if (recordCaller&&usedStack) f->glob.caller.Insert(sPrev.func->addr,1); // inc call counter f->glob.callCount++; // add total time __int64 delta=cur-s.tickEnter; if (!f->depth) f->glob.tickTotal+=delta; // add pure time f->glob.tickPure+=delta-s.tickSubTime; // add sub time for higher function if (usedStack) sPrev.tickSubTime+=delta; // frame based profiling? if (frameRecordMask) { unsigned mask=frameRecordMask; for (unsigned i=0;i0) f->cur[i].caller.Insert(sPrev.func->addr,1); f->cur[i].callCount++; if (!f->depth) f->cur[i].tickTotal+=delta; f->cur[i].tickPure+=delta-s.tickSubTime; } if (!(mask>>=1)) break; } } // exit if address match (somewhat...) if (s.esp==esp) break; // catching those nasty ret... if (s.esp>Dec()); } DLOG_GROUP(profile_stack,Debug::RepeatChar(' ',usedStack-1) << Debug::Hex() << this << " Leave " << Debug::Width(8) << "" << " ESP " << Debug::Width(8) << stack[usedStack].esp << " return " << Debug::Width(8) << stack[usedStack].retVal << " level " << Debug::Dec() << usedStack ); return stack[usedStack].retVal; } void ProfileFuncLevelTracer::Shutdown(void) { if (frameRecordMask) { for (unsigned i=0;inext) p->FrameEnd(i,-1); } } int ProfileFuncLevelTracer::FrameStart(void) { ProfileFastCS::Lock lock(cs); for (unsigned i=0;inext) { Function *f; for (int k=0;(f=p->func.Enumerate(k))!=NULL;k++) { Profile &p=f->cur[i]; p.caller.Clear(); p.callCount=p.tickPure=p.tickTotal=0; } } frameRecordMask|=1<=MAX_FRAME_RECORDS) return; DFAIL_IF(!(frameRecordMask&(1<=curFrame) return; ProfileFastCS::Lock lock(cs); frameRecordMask^=1<next) { Function *f; for (int k=0;(f=p->func.Enumerate(k))!=NULL;k++) { Profile &p=f->cur[which]; if (p.callCount) { if (mixIndex<0) f->frame.Append(curFrame,p); else f->frame.MixIn(mixIndex,p); } p.caller.Clear(); p.callCount=p.tickPure=p.tickTotal=0; } } } void ProfileFuncLevelTracer::ClearTotals(void) { ProfileFastCS::Lock lock(cs); for (ProfileFuncLevelTracer *p=head;p;p=p->next) { Function *f; for (int k=0;(f=p->func.Enumerate(k))!=NULL;k++) { f->glob.caller.Clear(); f->glob.callCount=0; f->glob.tickPure=0; f->glob.tickTotal=0; } } } ProfileFuncLevelTracer::UnsignedMap::UnsignedMap(void): e(NULL), alloc(0), used(0), writeLock(false) { memset(hash,0,sizeof(hash)); } ProfileFuncLevelTracer::UnsignedMap::~UnsignedMap() { Clear(); } void ProfileFuncLevelTracer::UnsignedMap::Clear(void) { ProfileFreeMemory(e); e=NULL; alloc=used=0; memset(hash,0,sizeof(hash)); } void ProfileFuncLevelTracer::UnsignedMap::_Insert(unsigned at, unsigned val, int countAdd) { DFAIL_IF(writeLock) return; // realloc list? if (used==alloc) { // must fixup pointers... unsigned delta=unsigned(e); e=(Entry *)ProfileReAllocMemory(e,((alloc+=64)*sizeof(Entry))); delta=unsigned(e)-delta; if (used&&delta) { for (unsigned k=0;k=(int)used) return 0; return e[index].val; } unsigned ProfileFuncLevelTracer::UnsignedMap::GetCount(int index) { if (index<0||index>=(int)used) return 0; return e[index].count; } void ProfileFuncLevelTracer::UnsignedMap::Copy(const UnsignedMap &src) { Clear(); if (src.e) { alloc=used=src.used; e=(Entry *)ProfileAllocMemory(alloc*sizeof(Entry)); memcpy(e,src.e,alloc*sizeof(Entry)); writeLock=true; } } void ProfileFuncLevelTracer::UnsignedMap::MixIn(const UnsignedMap &src) { writeLock=false; for (unsigned k=0;knext; root->~List(); ProfileFreeMemory(root); root=next; } } ProfileFuncLevelTracer::Profile *ProfileFuncLevelTracer::ProfileMap::Find(int frame) { for (List *p=root;p&&p->framenext); return p&&p->frame==frame?&p->p:NULL; } void ProfileFuncLevelTracer::ProfileMap::Append(int frame, const Profile &p) { List *newEntry=(List *)ProfileAllocMemory(sizeof(List)); new (newEntry) List; newEntry->frame=frame; newEntry->p.Copy(p); newEntry->next=NULL; *tail=newEntry; tail=&newEntry->next; } void ProfileFuncLevelTracer::ProfileMap::MixIn(int frame, const Profile &p) { // search correct list entry for (List *oldEntry=root;oldEntry;oldEntry=oldEntry->next) if (oldEntry->frame==frame) break; if (!oldEntry) Append(frame,p); else oldEntry->p.MixIn(p); } ProfileFuncLevelTracer::FunctionMap::FunctionMap(void): e(NULL), alloc(0), used(0) { memset(hash,0,sizeof(hash)); } ProfileFuncLevelTracer::FunctionMap::~FunctionMap() { if (e) { for (unsigned k=0;k~Function(); ProfileFreeMemory(e[k].funcPtr); } ProfileFreeMemory(e); } } void ProfileFuncLevelTracer::FunctionMap::Insert(Function *funcPtr) { // realloc list? if (used==alloc) { // must fixup pointers... unsigned delta=unsigned(e); e=(Entry *)ProfileReAllocMemory(e,(alloc+=1024)*sizeof(Entry)); delta=unsigned(e)-delta; if (used&&delta) { for (unsigned k=0;kaddr/16)%HASH_SIZE; e[used].funcPtr=funcPtr; e[used].next=hash[at]; hash[at]=e+used++; } ProfileFuncLevelTracer::Function *ProfileFuncLevelTracer::FunctionMap::Enumerate(int index) { if (index<0||index>=(int)used) return NULL; return e[index].funcPtr; } bool ProfileFuncLevel::IdList::Enum(unsigned index, Id &id, unsigned *countPtr) const { if (!m_ptr) return false; ProfileFuncLevelTracer::Profile &prof=*(ProfileFuncLevelTracer::Profile *)m_ptr; unsigned addr; if ((addr=prof.caller.Enumerate(index))) { id.m_funcPtr=prof.tracer->FindFunction(addr); if (countPtr) *countPtr=prof.caller.GetCount(index); return true; } else return false; } const char *ProfileFuncLevel::Id::GetSource(void) const { if (!m_funcPtr) return NULL; ProfileFuncLevelTracer::Function *func=(ProfileFuncLevelTracer::Function *)m_funcPtr; if (!func->funcSource) { char helpFunc[256],helpFile[256]; unsigned ofsFunc; DebugStackwalk::Signature::GetSymbol(func->addr, NULL,0,NULL, helpFunc,sizeof(helpFunc),&ofsFunc, helpFile,sizeof(helpFile),&func->funcLine,NULL); char help[300]; wsprintf(help,ofsFunc?"%s+0x%x":"%s",helpFunc,ofsFunc); func->funcSource=(char *)ProfileAllocMemory(strlen(helpFile)+1); strcpy(func->funcSource,helpFile); func->funcName=(char *)ProfileAllocMemory(strlen(help)+1); strcpy(func->funcName,help); } return func->funcSource; } const char *ProfileFuncLevel::Id::GetFunction(void) const { if (!m_funcPtr) return NULL; ProfileFuncLevelTracer::Function *func=(ProfileFuncLevelTracer::Function *)m_funcPtr; if (!func->funcSource) GetSource(); return func->funcName; } unsigned ProfileFuncLevel::Id::GetAddress(void) const { if (!m_funcPtr) return 0; ProfileFuncLevelTracer::Function *func=(ProfileFuncLevelTracer::Function *)m_funcPtr; return func->addr; } unsigned ProfileFuncLevel::Id::GetLine(void) const { if (!m_funcPtr) return NULL; ProfileFuncLevelTracer::Function *func=(ProfileFuncLevelTracer::Function *)m_funcPtr; if (!func->funcSource) GetSource(); return func->funcLine; } unsigned _int64 ProfileFuncLevel::Id::GetCalls(unsigned frame) const { if (!m_funcPtr) return 0; ProfileFuncLevelTracer::Function &func=*(ProfileFuncLevelTracer::Function *)m_funcPtr; switch(frame) { case Total: return func.glob.callCount; default: ProfileFuncLevelTracer::Profile *prof=func.frame.Find(frame); return prof?prof->callCount:0; } } unsigned _int64 ProfileFuncLevel::Id::GetTime(unsigned frame) const { if (!m_funcPtr) return 0; ProfileFuncLevelTracer::Function &func=*(ProfileFuncLevelTracer::Function *)m_funcPtr; switch(frame) { case Total: return func.glob.tickTotal; default: ProfileFuncLevelTracer::Profile *prof=func.frame.Find(frame); return prof?prof->tickTotal:0; } } unsigned _int64 ProfileFuncLevel::Id::GetFunctionTime(unsigned frame) const { if (!m_funcPtr) return 0; ProfileFuncLevelTracer::Function &func=*(ProfileFuncLevelTracer::Function *)m_funcPtr; switch(frame) { case Total: return func.glob.tickPure; default: ProfileFuncLevelTracer::Profile *prof=func.frame.Find(frame); return prof?prof->tickPure:0; } } ProfileFuncLevel::IdList ProfileFuncLevel::Id::GetCaller(unsigned frame) const { if (!m_funcPtr) return IdList(); ProfileFuncLevelTracer::Function &func=*(ProfileFuncLevelTracer::Function *)m_funcPtr; IdList ret; switch(frame) { case Total: ret.m_ptr=&func.glob; break; default: ProfileFuncLevelTracer::Profile *prof=func.frame.Find(frame); if (prof) ret.m_ptr=prof; } return ret; } bool ProfileFuncLevel::Thread::EnumProfile(unsigned index, Id &id) const { if (!m_threadID) return false; ProfileFastCS::Lock lock(cs); ProfileFuncLevelTracer::Function *f=m_threadID->EnumFunction(index); if (f) { id.m_funcPtr=f; return true; } else return false; } bool ProfileFuncLevel::EnumThreads(unsigned index, Thread &thread) { ProfileFastCS::Lock lock(cs); for (ProfileFuncLevelTracer *p=ProfileFuncLevelTracer::GetFirst();p;p=p->GetNext()) if (!index--) break; if (p) { thread.m_threadID=p; return true; } else return false; } ProfileFuncLevel::ProfileFuncLevel(void) { } #else // !defined HAS_PROFILE bool ProfileFuncLevel::IdList::Enum(unsigned index, Id &id, unsigned *) const { return false; } const char *ProfileFuncLevel::Id::GetSource(void) const { return NULL; } const char *ProfileFuncLevel::Id::GetFunction(void) const { return NULL; } unsigned ProfileFuncLevel::Id::GetAddress(void) const { return 0; } unsigned ProfileFuncLevel::Id::GetLine(void) const { return 0; } unsigned _int64 ProfileFuncLevel::Id::GetCalls(unsigned frame) const { return 0; } unsigned _int64 ProfileFuncLevel::Id::GetTime(unsigned frame) const { return 0; } unsigned _int64 ProfileFuncLevel::Id::GetFunctionTime(unsigned frame) const { return 0; } ProfileFuncLevel::IdList ProfileFuncLevel::Id::GetCaller(unsigned frame) const { return ProfileFuncLevel::IdList(); } bool ProfileFuncLevel::Thread::EnumProfile(unsigned index, Id &id) const { return false; } bool ProfileFuncLevel::EnumThreads(unsigned index, Thread &thread) { return false; } ProfileFuncLevel::ProfileFuncLevel(void) { } #endif // !defined HAS_PROFILE ProfileFuncLevel ProfileFuncLevel::Instance; HANDLE ProfileFastCS::testEvent=::CreateEvent(NULL,FALSE,FALSE,"");