273 lines
15 KiB
C++
273 lines
15 KiB
C++
/*
|
|
** Command & Conquer Red Alert(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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/***********************************************************************************************
|
|
*** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ***
|
|
***********************************************************************************************
|
|
* *
|
|
* Project Name : Library profiler *
|
|
* *
|
|
* File Name : WPROFILE.CPP *
|
|
* *
|
|
* Programmer : Steve Tall *
|
|
* *
|
|
* Start Date : 11/17/95 *
|
|
* *
|
|
* Last Update : November 20th 1995 [ST] *
|
|
* *
|
|
*---------------------------------------------------------------------------------------------*
|
|
* Overview: *
|
|
* *
|
|
* New System *
|
|
* ~~~~~~~~~~~ *
|
|
* *
|
|
* The new profiler system creates a seperate thread and then starts a timer off there. The *
|
|
* timer in the second thread uses GetThreadContext to sample the IP address of each user *
|
|
* thread. This system has the advantage of being able to sample what is happening in all the *
|
|
* threads we own not just the main thread. Another advantage is that it doesnt require a *
|
|
* major recompilation. *
|
|
* The disadvantage is that we dont really know what is going on when the IP is outside the *
|
|
* scope of our threads - We could be in direct draw, direct sound or even something like the *
|
|
* VMM and there is no way to tell. *
|
|
* *
|
|
* *
|
|
* *
|
|
* Old System *
|
|
* ~~~~~~~~~~~ *
|
|
* *
|
|
* The profiler works by using the function prologue and epilogue hooks available in Watcom *
|
|
* to register the current functions address in a global variable and then sampling the *
|
|
* contents of the variable using a windows timer which runs at up to 1000 samples per second.*
|
|
* *
|
|
* Compile the code to be sampled with the -ep and -ee flags to enable the prologue (__PRO) *
|
|
* and epilogue (__EPI) calls to be generated. *
|
|
* At the beginning of the section to be profiled (just before main loop normally) call the *
|
|
* Start_Profiler function to start sampling. At the end of the section, call Stop_Profiler *
|
|
* which will stop the timer and write the profile data to disk in the PROFILE.BIN file. *
|
|
* *
|
|
* Use PROFILE.EXE to view the results of the session. *
|
|
* *
|
|
* The addition of prologue and epilogue code will slow down the product and the profiler *
|
|
* allocates a huge buffer for data so it should not be linked in unless it is going to be *
|
|
* used. *
|
|
* *
|
|
* The advantage of the prologue/epilogue approach is that all samples represent valid *
|
|
* addresses within our code so we get valid results we can use even when the IP is in system *
|
|
* code. *
|
|
* *
|
|
*---------------------------------------------------------------------------------------------*
|
|
* *
|
|
* Functions: *
|
|
* Start_Profiler -- initialises the profiler data and starts gathering data *
|
|
* Stop_Profiler -- stops the timer and writes the profile data to disk *
|
|
* *
|
|
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
|
|
#define WIN32
|
|
#ifndef _WIN32 // Denzil 6/2/98 Watcom 11.0 complains without this check
|
|
#define _WIN32
|
|
#endif // _WIN32
|
|
#include <windows.h>
|
|
#include <windowsx.h>
|
|
#include <wwstd.h>
|
|
#include <rawfile.h>
|
|
#include <file.h>
|
|
#include "profile.h"
|
|
#include <vdmdbg.h>
|
|
#include <timer.h>
|
|
|
|
#define PROFILE
|
|
|
|
extern "C"{
|
|
#ifdef PROFILE
|
|
unsigned ProfileList [PROFILE_RATE*MAX_PROFILE_TIME];
|
|
#else
|
|
unsigned ProfileList [2];
|
|
#endif
|
|
unsigned ProfilePtr;
|
|
}
|
|
|
|
extern "C" void Old_Profiler_Callback ( UINT, UINT , DWORD, DWORD, DWORD );
|
|
extern "C" void New_Profiler_Callback (void);
|
|
extern "C" {
|
|
extern unsigned ProfileFunctionAddress;
|
|
}
|
|
|
|
unsigned long ProfilerEvent; //Handle for profiler callback
|
|
unsigned long ProfilerThread; //Handle for profiler thread
|
|
|
|
|
|
HANDLE CCThreadHandle;
|
|
CONTEXT ThreadContext;
|
|
|
|
|
|
#if (PROFILE_SYSTEM == NEW_PROFILE_SYSTEM)
|
|
|
|
/***********************************************************************************************
|
|
* Thread_Callback -- gets the IP address of our thread and registers it *
|
|
* *
|
|
* *
|
|
* *
|
|
* INPUT: Windows timer callback parms - not used *
|
|
* *
|
|
* OUTPUT: Nothing *
|
|
* *
|
|
* WARNINGS: None *
|
|
* *
|
|
* HISTORY: *
|
|
* 1/2/96 6:37AM ST : Created *
|
|
*=============================================================================================*/
|
|
|
|
void CALLBACK Thread_Callback (UINT,UINT,DWORD,DWORD,DWORD)
|
|
{
|
|
ThreadContext.ContextFlags = VDMCONTEXT_CONTROL;
|
|
if (!InTimerCallback){
|
|
GetThreadContext ( CCThreadHandle , &ThreadContext );
|
|
}else{
|
|
GetThreadContext (TimerThreadHandle , &ThreadContext);
|
|
}
|
|
|
|
ProfileFunctionAddress = ThreadContext.Eip;
|
|
New_Profiler_Callback();
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Profile_Thread -- this is the thread our profiler runs in. It just starts off a timer and *
|
|
* then buggers off into an infinite message loop. We shouldnt get messages *
|
|
* here as this isnt our primary thread *
|
|
* *
|
|
* INPUT: Nothing *
|
|
* *
|
|
* OUTPUT: Nothing *
|
|
* *
|
|
* WARNINGS: None *
|
|
* *
|
|
* HISTORY: *
|
|
* 1/2/96 6:39AM ST : Created *
|
|
*=============================================================================================*/
|
|
void Profile_Thread (void)
|
|
{
|
|
MSG msg;
|
|
ProfilerEvent = timeSetEvent (1000/PROFILE_RATE , 1 , Thread_Callback , 0 , TIME_PERIODIC);
|
|
//ProfilerEvent = timeSetEvent (100 , 1 , Thread_Callback , 0 , TIME_ONESHOT);
|
|
do {
|
|
GetMessage(&msg,NULL,0,0);
|
|
} while(1);
|
|
}
|
|
|
|
#endif //(PROFILE_SYSTEM == OLD_PROFILE_SYSTEM)
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Start_Profiler -- initialises the profiler system and starts sampling *
|
|
* *
|
|
* *
|
|
* *
|
|
* INPUT: Nothing *
|
|
* *
|
|
* OUTPUT: Nothing *
|
|
* *
|
|
* WARNINGS: There may be a pause when sampling starts as Win95 does some VMM stuff *
|
|
* *
|
|
* HISTORY: *
|
|
* 11/20/95 5:12PM ST : Created *
|
|
*=============================================================================================*/
|
|
|
|
void __cdecl Start_Profiler (void)
|
|
{
|
|
#ifdef PROFILE
|
|
if (!ProfilerEvent){
|
|
memset (&ProfileList[0],-1,PROFILE_RATE*MAX_PROFILE_TIME*4);
|
|
}
|
|
|
|
Profile_Init();
|
|
|
|
if (!ProfilerEvent){
|
|
|
|
#if (PROFILE_SYSTEM == OLD_PROFILE_SYSTEM)
|
|
/*
|
|
** Old profile system - just set up a timer to monitor the global variable based on
|
|
** the last place __PRO was called from
|
|
*/
|
|
ProfilerEvent = timeSetEvent (1000/PROFILE_RATE , 1 , (void CALLBACK (UINT,UINT,DWORD,DWORD,DWORD))Old_Profiler_Callback , 0 , TIME_PERIODIC);
|
|
#else
|
|
/*
|
|
** New profile system - create a second thread that will do all the profiling
|
|
** using GetThreadContext
|
|
*/
|
|
if ( DuplicateHandle( GetCurrentProcess(), GetCurrentThread() , GetCurrentProcess() ,&CCThreadHandle , 0 , TRUE , DUPLICATE_SAME_ACCESS) ){
|
|
ProfilerEvent= (unsigned)CreateThread(NULL,2048,(LPTHREAD_START_ROUTINE)&Profile_Thread,NULL,0,&ProfilerThread);
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
#else
|
|
ProfilerEvent = 0;
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Stop_Profiler -- stops the sampling timer and writes the colledted data to disk *
|
|
* *
|
|
* *
|
|
* *
|
|
* INPUT: Nothing *
|
|
* *
|
|
* OUTPUT: Nothing *
|
|
* *
|
|
* WARNINGS: Writes to file PROFILE.BIN *
|
|
* *
|
|
* HISTORY: *
|
|
* 11/20/95 5:13PM ST : Created *
|
|
*=============================================================================================*/
|
|
|
|
void __cdecl Stop_Profiler (void)
|
|
{
|
|
if (ProfilerEvent){
|
|
|
|
#if (PROFILE_SYSTEM == OLD_PROFILE_SYSTEM)
|
|
//
|
|
// Old system - just remove the timer event
|
|
//
|
|
timeKillEvent(ProfilerEvent);
|
|
#else
|
|
//
|
|
// New system - kill the profiling thread
|
|
//
|
|
TerminateThread((HANDLE)ProfilerThread,0);
|
|
#endif
|
|
|
|
ProfilerEvent=NULL;
|
|
|
|
Profile_End();
|
|
|
|
int handle = Open_File ( "profile.bin" , WRITE );
|
|
if (handle != WW_ERROR){
|
|
Write_File (handle , &ProfileList[0] , ProfilePtr*4);
|
|
Close_File (handle);
|
|
}
|
|
}
|
|
}
|
|
|
|
|