Initial commit of Command & Conquer Red Alert source code.
This commit is contained in:
298
WIN32LIB/PROFILE/APROFILE.ASM
Normal file
298
WIN32LIB/PROFILE/APROFILE.ASM
Normal file
@@ -0,0 +1,298 @@
|
||||
;
|
||||
; 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 : Profiler *
|
||||
;* *
|
||||
;* File Name : APROFILE.ASM *
|
||||
;* *
|
||||
;* Programmer : Steve Tall *
|
||||
;* *
|
||||
;* Start Date : November 17th, 1995 *
|
||||
;* *
|
||||
;* Last Update : November 20th, 1995 [ST] *
|
||||
;* *
|
||||
;*-------------------------------------------------------------------------*
|
||||
;* Functions: *
|
||||
;* __PRO -- must be called at the beginning of each function *
|
||||
;* __EPI -- must be called at the end of each function *
|
||||
;* Copy_CHL -- initialise the profiler asm data *
|
||||
;* Profiler_Callback -- windows callback for millisecond timer *
|
||||
;* *
|
||||
;* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *
|
||||
|
||||
|
||||
|
||||
p386
|
||||
model flat
|
||||
ideal
|
||||
jumps
|
||||
|
||||
|
||||
MAX_PROFILE_TIME = 60*1 ;1 minute(s)
|
||||
PROFILE_RATE = 1000 ;1000 samples per sec
|
||||
|
||||
|
||||
;
|
||||
; Externs
|
||||
;
|
||||
;
|
||||
global C ProfileFunctionAddress:dword
|
||||
global C ProfilePtr:dword
|
||||
global C ProfileList:dword
|
||||
global C Stop_Profiler:near
|
||||
global New_Profiler_Callback_:near
|
||||
global Old_Profiler_Callback_:near
|
||||
global C Profile_Init:near
|
||||
global C Profile_End:near
|
||||
global ___begtext:near
|
||||
global BaseAddress:dword
|
||||
global __PRO:near
|
||||
global __EPI:near
|
||||
global MyStack:dword
|
||||
global MyStackPtr:dword
|
||||
global ProAddress:dword
|
||||
global EpiAddress:dword
|
||||
|
||||
|
||||
codeseg
|
||||
|
||||
|
||||
;*********************************************************************************************
|
||||
;* __PRO -- registers the current procedure *
|
||||
;* *
|
||||
;* INPUT: Nothing *
|
||||
;* *
|
||||
;* OUTPUT: none *
|
||||
;* *
|
||||
;* Warnings: *
|
||||
;* Assumes that ss:Esp points to return address in function to be registered *
|
||||
;* *
|
||||
;* HISTORY: *
|
||||
;* 11/20/95 4:39PM ST : Created. *
|
||||
;*===========================================================================================*
|
||||
|
||||
proc __PRO near
|
||||
|
||||
jmp [ProAddress]
|
||||
; safe version of prologue code
|
||||
Pro_Start: push eax
|
||||
mov eax,[MyStackPtr]
|
||||
push [ProfileFunctionAddress]
|
||||
pop [eax*4+MyStack]
|
||||
inc [MyStackPtr]
|
||||
pop eax
|
||||
push [dword ss:esp]
|
||||
pop [ProfileFunctionAddress]
|
||||
Pro_End: ret
|
||||
|
||||
; unsafe (but much faster) prologue code
|
||||
; pop [ProfileFunctionAddress]
|
||||
; jmp [ProfileFunctionAddress]
|
||||
|
||||
endp __PRO
|
||||
|
||||
|
||||
;*********************************************************************************************
|
||||
;* __EPI -- Registers the privious procedure as current again *
|
||||
;* *
|
||||
;* INPUT: Nothing *
|
||||
;* *
|
||||
;* OUTPUT: none *
|
||||
;* *
|
||||
;* Warnings: *
|
||||
;* Assumes that calling procedure will pop ebp immediately on return so we dont have to *
|
||||
;* preserve it. *
|
||||
;* *
|
||||
;* HISTORY: *
|
||||
;* 11/20/95 4:42PM ST : Created. *
|
||||
;*===========================================================================================*
|
||||
|
||||
proc __EPI near
|
||||
|
||||
jmp [EpiAddress]
|
||||
; Safe version of epilogue code. Uncomment the push and pop for ultimate safety
|
||||
Epi_Start: dec [MyStackPtr]
|
||||
; push ebp
|
||||
mov ebp,[MyStackPtr]
|
||||
mov ebp,[ebp*4+MyStack]
|
||||
mov [ProfileFunctionAddress],ebp
|
||||
; pop ebp
|
||||
Epi_End: ret
|
||||
|
||||
; Unsafe (but much faster) epilogue code. Makes lots of assumptions.
|
||||
; push [dword esp+8]
|
||||
; pop [ProfileFunctionAddress]
|
||||
; ret
|
||||
|
||||
endp __EPI
|
||||
|
||||
|
||||
|
||||
;*********************************************************************************************
|
||||
;* Profile_Init -- Initialises the .asm data required for profile session *
|
||||
;* *
|
||||
;* INPUT: Nothing *
|
||||
;* *
|
||||
;* OUTPUT: none *
|
||||
;* *
|
||||
;* Warnings: *
|
||||
;* Assumes that '___begtext' is the first label in the code segment and that its *
|
||||
;* address is within 15 bytes of the start of the code segment *
|
||||
;* *
|
||||
;* HISTORY: *
|
||||
;* 11/20/95 4:44PM ST : Created. *
|
||||
;*===========================================================================================*
|
||||
|
||||
proc Profile_Init C near
|
||||
|
||||
mov eax,offset ___begtext
|
||||
and eax,0fffffff0h
|
||||
mov [BaseAddress],eax
|
||||
;mov [MyStackPtr],0
|
||||
mov [ProfileList],PROFILE_RATE
|
||||
mov [ProfilePtr],1
|
||||
mov [ProAddress],offset Pro_Start
|
||||
mov [EpiAddress],offset Epi_Start
|
||||
ret
|
||||
|
||||
endp Profile_Init
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
;*********************************************************************************************
|
||||
;* Profile_End -- disables the __PRO and __EPI procedures *
|
||||
;* *
|
||||
;* INPUT: Nothing *
|
||||
;* *
|
||||
;* OUTPUT: none *
|
||||
;* *
|
||||
;* Warnings: *
|
||||
;* *
|
||||
;* HISTORY: *
|
||||
;* 11/20/95 4:44PM ST : Created. *
|
||||
;*===========================================================================================*
|
||||
|
||||
proc Profile_End C near
|
||||
|
||||
mov [ProAddress],offset Pro_End
|
||||
mov [EpiAddress],offset Epi_End
|
||||
ret
|
||||
|
||||
endp Profile_End
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
;*********************************************************************************************
|
||||
;* New_Profiler_Callback -- Windows callback used to register function hits *
|
||||
;* *
|
||||
;* INPUT: Nothing *
|
||||
;* *
|
||||
;* OUTPUT: none *
|
||||
;* *
|
||||
;* Note: *
|
||||
;* The frequency that this is called depends on MAX_PROFILE_RATE defined here and in *
|
||||
;* profile.h *
|
||||
;* *
|
||||
;* HISTORY: *
|
||||
;* 11/20/95 4:47PM ST : Created. *
|
||||
;*===========================================================================================*
|
||||
proc New_Profiler_Callback_ near
|
||||
|
||||
push eax
|
||||
push esi
|
||||
mov esi,[ProfilePtr]
|
||||
cmp esi,MAX_PROFILE_TIME*PROFILE_RATE
|
||||
jge @@out
|
||||
mov eax,[ProfileFunctionAddress]
|
||||
sub eax,[BaseAddress]
|
||||
mov [ProfileList+esi*4],eax
|
||||
inc [ProfilePtr]
|
||||
pop esi
|
||||
pop eax
|
||||
ret
|
||||
|
||||
@@out: call Stop_Profiler
|
||||
pop esi
|
||||
pop eax
|
||||
ret
|
||||
|
||||
endp New_Profiler_Callback_
|
||||
|
||||
|
||||
|
||||
;*********************************************************************************************
|
||||
;* Old_Profiler_Callback -- Windows callback used to register function hits *
|
||||
;* *
|
||||
;* INPUT: Windows timer callback stuff - not used *
|
||||
;* *
|
||||
;* OUTPUT: none *
|
||||
;* *
|
||||
;* Note: *
|
||||
;* The frequency that this is called depends on MAX_PROFILE_RATE defined here and in *
|
||||
;* profile.h *
|
||||
;* *
|
||||
;* HISTORY: *
|
||||
;* 11/20/95 4:47PM ST : Created. *
|
||||
;*===========================================================================================*
|
||||
|
||||
proc Old_Profiler_Callback_ near
|
||||
|
||||
push eax
|
||||
push esi
|
||||
mov esi,[ProfilePtr]
|
||||
cmp esi,MAX_PROFILE_TIME*PROFILE_RATE
|
||||
jge @@out
|
||||
mov eax,[ProfileFunctionAddress]
|
||||
sub eax,[BaseAddress]
|
||||
mov [ProfileList+esi*4],eax
|
||||
inc [ProfilePtr]
|
||||
pop esi
|
||||
pop eax
|
||||
ret 14h
|
||||
|
||||
@@out: call Stop_Profiler
|
||||
pop esi
|
||||
pop eax
|
||||
ret 14h
|
||||
|
||||
endp Old_Profiler_Callback_
|
||||
|
||||
|
||||
|
||||
dataseg
|
||||
align 4
|
||||
|
||||
ProfileFunctionAddress dd 0 ;Ptr to function we are currently in
|
||||
BaseAddress dd 0 ;Address of the code segment start
|
||||
MyStackPtr dd 0 ;offset into my stack table
|
||||
ProAddress dd Pro_Start ;jmp ptr for __PRO procedure
|
||||
EpiAddress dd Epi_Start ;jmp ptr for __EPI procedure
|
||||
|
||||
label MyStack dword ;my stack table
|
||||
dd 16*1024 dup (?)
|
||||
|
||||
end
|
179
WIN32LIB/PROFILE/MAKEFILE
Normal file
179
WIN32LIB/PROFILE/MAKEFILE
Normal file
@@ -0,0 +1,179 @@
|
||||
#
|
||||
# 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 : Westwood Library .LIB makefile *
|
||||
#* *
|
||||
#* File Name : MAKEFILE *
|
||||
#* *
|
||||
#* Programmer : Julio R. Jerez *
|
||||
#* *
|
||||
#* Start Date : Jan 26, 1995 *
|
||||
#* *
|
||||
#* *
|
||||
#*-------------------------------------------------------------------------*
|
||||
#* *
|
||||
#* Required environment variables: *
|
||||
#* WIN32LIB = your root WIN32LIB path *
|
||||
#* WIN32VCS = root directory for wwlib version control archive *
|
||||
#* COMPILER = your Watcom installation path *
|
||||
#* *
|
||||
#* Required changes to makefile: *
|
||||
#* PROJ_NAME = name of the library you're building *
|
||||
#* OBJECTS = list of objects in your library *
|
||||
#* *
|
||||
#* Optional changes to makefile: *
|
||||
#* PROJ_DIR = full pathname of your working directory *
|
||||
#* .path.xxx = full pathname where various file types live *
|
||||
#* *
|
||||
#***************************************************************************
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Verify user's environment
|
||||
#---------------------------------------------------------------------------
|
||||
!ifndef %WIN32LIB
|
||||
!error WIN32LIB Environment var not configured.
|
||||
!endif
|
||||
|
||||
!ifndef %WIN32VCS
|
||||
!error WIN32VCS Environment var not configured.
|
||||
!endif
|
||||
|
||||
!ifndef %WATCOM
|
||||
!error WATCOM Environment var not configured.
|
||||
!endif
|
||||
|
||||
|
||||
#===========================================================================
|
||||
# User-defined section: the user should tailor this section for each project
|
||||
#===========================================================================
|
||||
|
||||
PROJ_NAME = profile
|
||||
PROJ_DIR = $(%WIN32LIB)\$(PROJ_NAME)
|
||||
LIB_DIR = $(%WIN32LIB)\lib
|
||||
|
||||
!include $(%WIN32LIB)\project.cfg
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Project-dependent variables
|
||||
#---------------------------------------------------------------------------
|
||||
OBJECTS = &
|
||||
wprofile.obj &
|
||||
aprofile.obj
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Path macros: one path for each file type.
|
||||
# These paths are used to tell make where to find/put each file type.
|
||||
#---------------------------------------------------------------------------
|
||||
.asm: $(PROJ_DIR)
|
||||
.c: $(PROJ_DIR)
|
||||
.cpp: $(PROJ_DIR)
|
||||
.h: $(PROJ_DIR)
|
||||
.obj: $(PROJ_DIR)
|
||||
.lib: $(WWLIB)\lib
|
||||
.exe: $(PROJ_DIR)
|
||||
|
||||
#===========================================================================
|
||||
# Pre-defined section: there should be little need to modify this section.
|
||||
#===========================================================================
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Tools/commands
|
||||
#---------------------------------------------------------------------------
|
||||
C_CMD = wcc386
|
||||
CPP_CMD = wpp386
|
||||
LIB_CMD = wlib
|
||||
LINK_CMD = wlink
|
||||
ASM_CMD = tasm
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Include & library paths
|
||||
# If LIB & INCLUDE are already defined, they are used in addition to the
|
||||
# WWLIB32 lib & include; otherwise, they're constructed from
|
||||
# BCDIR & TNTDIR
|
||||
#---------------------------------------------------------------------------
|
||||
LIBPATH = $(%WIN32LIB)\LIB;$(%WATCOM)\LIB
|
||||
INCLUDEPATH = $(%WIN32LIB)\INCLUDE;$(%WATCOM)\H
|
||||
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Implicit rules
|
||||
# Compiler:
|
||||
# ($< = full dependent with path)
|
||||
# Assembler:
|
||||
# output obj's are constructed from .obj: & the $& macro
|
||||
# ($< = full dependent with path)
|
||||
# tasm's cfg file is not invoked as a response file.
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
|
||||
.c.obj: $(%WIN32LIB)\project.cfg .AUTODEPEND
|
||||
*$(C_CMD) $(CC_CFG) $<
|
||||
|
||||
.cpp.obj: $(%WIN32LIB)\project.cfg .AUTODEPEND
|
||||
*$(CPP_CMD) $(CC_CFG) $(PROJ_DIR)\$^*.cpp
|
||||
|
||||
.asm.obj: $(%WIN32LIB)\project.cfg
|
||||
$(ASM_CMD) $(ASM_CFG) $<
|
||||
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Default target: configuration files & library (in that order)
|
||||
#---------------------------------------------------------------------------
|
||||
all: $(LIB_DIR)\$(PROJ_NAME).lib .SYMBOLIC
|
||||
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Build the library
|
||||
# The original library is deleted by the librarian
|
||||
# Lib objects & -+ commands are constructed by substituting within the
|
||||
# $^@ macro (which expands to all target dependents, separated with
|
||||
# spaces)
|
||||
# Tlib's cfg file is not invoked as a response file.
|
||||
# All headers & source files are copied into WIN32LIB\SRCDEBUG, for debugging
|
||||
#---------------------------------------------------------------------------
|
||||
$(LIB_DIR)\$(PROJ_NAME).lib: $(OBJECTS) objects.lbc
|
||||
copy *.h $(%WIN32LIB)\include
|
||||
copy *.inc $(%WIN32LIB)\include
|
||||
copy *.cpp $(%WIN32LIB)\srcdebug
|
||||
copy *.asm $(%WIN32LIB)\srcdebug
|
||||
$(LIB_CMD) $(LIB_CFG) $^@ @objects.lbc
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Objects now have a link file which is NOT generated everytime. Instead
|
||||
# it just has its own dependacy rule.
|
||||
#---------------------------------------------------------------------------
|
||||
objects.lbc : $(OBJECTS)
|
||||
%create $^@
|
||||
for %index in ($(OBJECTS)) do %append $^@ +%index
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Create the test directory and make it.
|
||||
#---------------------------------------------------------------------------
|
||||
test:
|
||||
mkdir test
|
||||
cd test
|
||||
copy $(%WWVCS)\$(PROJ_NAME)\test\vcs.cfg
|
||||
update
|
||||
wmake
|
||||
cd ..
|
||||
|
||||
#**************************** End of makefile ******************************
|
96
WIN32LIB/PROFILE/PROFILE.CPP
Normal file
96
WIN32LIB/PROFILE/PROFILE.CPP
Normal file
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
** 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 : PROFILE.CPP *
|
||||
* *
|
||||
* Programmer : Steve Tall *
|
||||
* *
|
||||
* Start Date : 11/17/95 *
|
||||
* *
|
||||
* Last Update : November 20th 1995 [ST] *
|
||||
* *
|
||||
*---------------------------------------------------------------------------------------------*
|
||||
* Overview: *
|
||||
* 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 *
|
||||
* *
|
||||
*---------------------------------------------------------------------------------------------*
|
||||
* *
|
||||
* 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"
|
||||
|
||||
extern "C"{
|
||||
unsigned ProfileList [PROFILE_RATE*60*MAX_PROFILE_TIME];
|
||||
unsigned ProfilePtr;
|
||||
}
|
||||
|
||||
extern "C" void Profiler_Callback ( UINT, UINT , DWORD, DWORD, DWORD );
|
||||
|
||||
unsigned ProfilerEvent;
|
||||
|
||||
void Start_Profiler (void)
|
||||
{
|
||||
memset (&ProfileList[0],-1,PROFILE_RATE*60*MAX_PROFILE_TIME*4);
|
||||
Copy_CHK();
|
||||
ProfilerEvent = timeSetEvent (1000/PROFILE_RATE , 1 , (void CALLBACK (UINT,UINT,DWORD,DWORD,DWORD))Profiler_Callback , 0 , TIME_PERIODIC);
|
||||
}
|
||||
|
||||
void Stop_Profiler (void)
|
||||
{
|
||||
if (ProfilerEvent){
|
||||
timeKillEvent(ProfilerEvent);
|
||||
ProfilerEvent=NULL;
|
||||
|
||||
int handle = Open_File ( "profile.bin" , WRITE );
|
||||
if (handle != WW_ERROR){
|
||||
Write_File (handle , &ProfileList[0] , ProfilePtr*4);
|
||||
Close_File (handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
105
WIN32LIB/PROFILE/PROFILE.H
Normal file
105
WIN32LIB/PROFILE/PROFILE.H
Normal file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
** 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 : PROFILE.H *
|
||||
* *
|
||||
* 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: *
|
||||
* *
|
||||
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
|
||||
#define MAX_PROFILE_TIME 60*1 //1 minute(s) @ 14.4 Mb per hour
|
||||
#define PROFILE_RATE 1000 //samples per sec (max 1000)
|
||||
|
||||
|
||||
/*
|
||||
* Defines for choosing between the old and new profiler system
|
||||
*
|
||||
*/
|
||||
|
||||
#define OLD_PROFILE_SYSTEM 1
|
||||
#define NEW_PROFILE_SYSTEM 2
|
||||
|
||||
//#define PROFILE_SYSTEM OLD_PROFILE_SYSTEM
|
||||
#define PROFILE_SYSTEM NEW_PROFILE_SYSTEM
|
||||
|
||||
|
||||
|
||||
extern "C"{
|
||||
void __cdecl Profile_Init(void);
|
||||
void __cdecl Profile_End(void);
|
||||
void __cdecl Start_Profiler(void);
|
||||
void __cdecl Stop_Profiler(void);
|
||||
}
|
||||
|
44
WIN32LIB/PROFILE/PROFILE.INC
Normal file
44
WIN32LIB/PROFILE/PROFILE.INC
Normal file
@@ -0,0 +1,44 @@
|
||||
;
|
||||
; 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/>.
|
||||
;
|
||||
|
||||
|
||||
;USE_PROFILER =1
|
||||
|
||||
|
||||
macro prologue
|
||||
Ifdef USE_PROFILER
|
||||
global __PRO:near
|
||||
|
||||
call __PRO
|
||||
endif ;USE_PROFILER
|
||||
endm
|
||||
|
||||
|
||||
|
||||
macro epilogue
|
||||
ifdef USE_PROFILER
|
||||
global __EPI:near
|
||||
|
||||
push ebp
|
||||
call __EPI
|
||||
pop ebp
|
||||
endif ;USE_PROFILER
|
||||
endm
|
||||
|
||||
|
||||
|
891
WIN32LIB/PROFILE/UTIL/PROFILE.CPP
Normal file
891
WIN32LIB/PROFILE/UTIL/PROFILE.CPP
Normal file
@@ -0,0 +1,891 @@
|
||||
/*
|
||||
** 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 : PROFILE.CPP *
|
||||
* *
|
||||
* Programmer : Steve Tall *
|
||||
* *
|
||||
* Start Date : 11/17/95 *
|
||||
* *
|
||||
* Last Update : November 20th 1995 [ST] *
|
||||
* *
|
||||
*---------------------------------------------------------------------------------------------*
|
||||
* Overview: *
|
||||
* Uses a map file to match addresses of functions in the sample file with their names *
|
||||
* *
|
||||
* *
|
||||
*---------------------------------------------------------------------------------------------*
|
||||
* *
|
||||
* Functions: *
|
||||
* Start_Profiler -- initialises the profiler data and starts gathering data *
|
||||
* Stop_Profiler -- stops the timer and writes the profile data to disk *
|
||||
* *
|
||||
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <sys\types.h>
|
||||
#include <sys\stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <malloc.h>
|
||||
#include <io.h>
|
||||
#include <conio.h>
|
||||
|
||||
#define bool int
|
||||
#define true 1
|
||||
#define false 0
|
||||
#define NAME_TABLE_SIZE 1000000 //Storage space for function names
|
||||
#define SAMPLE_START 1 //Offset (in dwords) of sample data in sample file
|
||||
|
||||
|
||||
/*
|
||||
** Function prototypes
|
||||
*/
|
||||
void Print_My_Name(void);
|
||||
void Print_Usage(void);
|
||||
int Load_File(char *file_name , unsigned *file_ptr , unsigned mode);
|
||||
bool Extract_Function_Addresses(void);
|
||||
unsigned Get_Hex (char string[] , int length);
|
||||
char *Search_For_Char (char character , char buffer_ptr[] , int buffer_length);
|
||||
char *Search_For_String (char *string , char *buffer_ptr , int buffer_length);
|
||||
void Map_Profiler_Hits (void);
|
||||
void Sort_Functions(void);
|
||||
void Sort_Functions_Again(void);
|
||||
void Output_Profile(void);
|
||||
|
||||
char *SampleFile; //Ptr to sample file name
|
||||
char *MapFile; //Ptr to map file name
|
||||
unsigned *SampleFileBuffer; //Ptr to buffer that sample file is loaded in to
|
||||
char *MapFileBuffer; //Ptr to buffer that map file is loaded in to
|
||||
unsigned SampleFileLength; //Length of sample file
|
||||
unsigned MapFileLength; //Length of map file
|
||||
char FunctionNames[NAME_TABLE_SIZE]; //Buffer to store function names in
|
||||
char *FunctionNamePtr=&FunctionNames[0]; //Ptr to end of last function name in buffer
|
||||
int TotalFunctions; //Total number of functions extracted from map file
|
||||
int SampleRate; //Number of samples/sec that data was collected at
|
||||
unsigned EndCodeSegment; //Length of the sampled programs code segments
|
||||
|
||||
/*
|
||||
** Structure for collating function data
|
||||
*/
|
||||
typedef struct tFunction {
|
||||
unsigned FunctionAddress; //Address of function relative to start of code seg
|
||||
char *FunctionName; //Ptr to name of function in FunctionNames buffer
|
||||
int Hits; //Number of times function was 'hit' when sampling
|
||||
} Function;
|
||||
|
||||
Function FunctionList[10000]; //max 10,000 functions in map file.
|
||||
|
||||
|
||||
|
||||
|
||||
/***********************************************************************************************
|
||||
* main -- program entry point *
|
||||
* *
|
||||
* *
|
||||
* *
|
||||
* INPUT: argc , argv *
|
||||
* *
|
||||
* OUTPUT: 0 *
|
||||
* *
|
||||
* WARNINGS: None *
|
||||
* *
|
||||
* HISTORY: *
|
||||
* 11/20/95 5:21PM ST : Created *
|
||||
*=============================================================================================*/
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
|
||||
{
|
||||
Print_My_Name(); // print the programs name
|
||||
|
||||
|
||||
/*
|
||||
** If the arguments dont make sense then print the usage
|
||||
*/
|
||||
if (argc!=3 ||
|
||||
!strcmpi(argv[1],"/?") ||
|
||||
!strcmpi(argv[1],"/h") ||
|
||||
!strcmpi(argv[1],"/help") ||
|
||||
!strcmpi(argv[1],"-?") ||
|
||||
!strcmpi(argv[1],"-h") ||
|
||||
!strcmpi(argv[1],"-help") ||
|
||||
!strcmpi(argv[1],"?") ||
|
||||
!strcmpi(argv[1],"h") ||
|
||||
!strcmpi(argv[1],"help")){
|
||||
Print_Usage();
|
||||
return(0);
|
||||
}
|
||||
|
||||
/*
|
||||
** Get the names of the files to load
|
||||
*/
|
||||
SampleFile=argv[1];
|
||||
MapFile=argv[2];
|
||||
|
||||
/*
|
||||
** Load the profile sample file
|
||||
*/
|
||||
SampleFileLength = Load_File (SampleFile , (unsigned*)&SampleFileBuffer , O_BINARY);
|
||||
if (!SampleFileLength) return(0);
|
||||
|
||||
/*
|
||||
** The sample rate is the 1st dword in the file
|
||||
*/
|
||||
SampleRate=*SampleFileBuffer;
|
||||
|
||||
/*
|
||||
** Load the .map file
|
||||
*/
|
||||
MapFileLength = Load_File (MapFile , (unsigned*)&MapFileBuffer , O_BINARY);
|
||||
if (!MapFileLength){
|
||||
free (SampleFileBuffer);
|
||||
return(0);
|
||||
}
|
||||
|
||||
/*
|
||||
** Get the function names from the map file
|
||||
*/
|
||||
cprintf ("Extracting function data from map file.\n");
|
||||
if (!Extract_Function_Addresses()){
|
||||
cprintf ("Error parsing .MAP file - aborting\n\n");
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
** Sort the functions into address order to make it easier to map the functions
|
||||
*/
|
||||
cprintf ("Sorting function list by address");
|
||||
Sort_Functions();
|
||||
|
||||
/*
|
||||
** Map the addresses in the sample file to the function addresses
|
||||
*/
|
||||
cprintf ("\nMapping profiler hits to functions");
|
||||
Map_Profiler_Hits();
|
||||
|
||||
/*
|
||||
** Sort the functions into order of usage for output
|
||||
*/
|
||||
cprintf ("\nSorting function list by activity");
|
||||
Sort_Functions_Again();
|
||||
cprintf ("\n\n");
|
||||
|
||||
/*
|
||||
** Print the function usage statistics
|
||||
*/
|
||||
Output_Profile();
|
||||
|
||||
/*
|
||||
** Cleanup and out
|
||||
*/
|
||||
free (SampleFileBuffer);
|
||||
free (MapFileBuffer);
|
||||
return(0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***********************************************************************************************
|
||||
* Print_My_Name -- print the programs name and version *
|
||||
* *
|
||||
* *
|
||||
* INPUT: Nothing *
|
||||
* *
|
||||
* OUTPUT: Nothing *
|
||||
* *
|
||||
* WARNINGS: None *
|
||||
* *
|
||||
* HISTORY: *
|
||||
* 11/20/95 5:25PM ST : Created *
|
||||
*=============================================================================================*/
|
||||
|
||||
void Print_My_Name(void)
|
||||
{
|
||||
cprintf("Westwood profile data analyzer.\n");
|
||||
cprintf("V 1.0 - 11/17/95\n");
|
||||
cprintf("Programmer - Steve Tall.\n\n");
|
||||
}
|
||||
|
||||
|
||||
/***********************************************************************************************
|
||||
* Print_Usage -- print the instructions *
|
||||
* *
|
||||
* *
|
||||
* INPUT: Nothing *
|
||||
* *
|
||||
* OUTPUT: Nothing *
|
||||
* *
|
||||
* WARNINGS: None *
|
||||
* *
|
||||
* HISTORY: *
|
||||
* 11/20/95 5:26PM ST : Created *
|
||||
*=============================================================================================*/
|
||||
|
||||
void Print_Usage (void)
|
||||
{
|
||||
cprintf("Usage: PROFILE <sample_file> <map_file)\n\n");
|
||||
}
|
||||
|
||||
|
||||
/***********************************************************************************************
|
||||
* File_Error -- display a file error message *
|
||||
* *
|
||||
* *
|
||||
* INPUT: name of file error occurred on *
|
||||
* *
|
||||
* OUTPUT: Nothing *
|
||||
* *
|
||||
* WARNINGS: None *
|
||||
* *
|
||||
* HISTORY: *
|
||||
* 11/20/95 5:26PM ST : Created *
|
||||
*=============================================================================================*/
|
||||
|
||||
void File_Error (char *file_name)
|
||||
{
|
||||
cprintf ("Error reading file:%s - aborting\n",file_name);
|
||||
}
|
||||
|
||||
|
||||
/***********************************************************************************************
|
||||
* Memory_Error -- display an out of memory message *
|
||||
* *
|
||||
* *
|
||||
* *
|
||||
* INPUT: Nothing *
|
||||
* *
|
||||
* OUTPUT: Nothing *
|
||||
* *
|
||||
* WARNINGS: None *
|
||||
* *
|
||||
* HISTORY: *
|
||||
* 11/20/95 5:27PM ST : Created *
|
||||
*=============================================================================================*/
|
||||
|
||||
void Memory_Error (void)
|
||||
{
|
||||
cprintf ("Error - insufficient memory - aborting\n");
|
||||
}
|
||||
|
||||
|
||||
/***********************************************************************************************
|
||||
* Load_File -- load an entire file into memory *
|
||||
* *
|
||||
* *
|
||||
* *
|
||||
* INPUT: File name *
|
||||
* address to load at *
|
||||
* read mode (text or binary) *
|
||||
* *
|
||||
* OUTPUT: number of bytes read *
|
||||
* *
|
||||
* WARNINGS: None *
|
||||
* *
|
||||
* HISTORY: *
|
||||
* 11/20/95 5:27PM ST : Created *
|
||||
*=============================================================================================*/
|
||||
|
||||
int Load_File(char *file_name , unsigned *load_addr , unsigned mode)
|
||||
{
|
||||
int handle;
|
||||
unsigned file_length;
|
||||
void *buffer;
|
||||
|
||||
handle=open (file_name , O_RDONLY | mode);
|
||||
|
||||
if (handle==-1){
|
||||
File_Error(file_name);
|
||||
return (false);
|
||||
}
|
||||
|
||||
file_length = filelength(handle);
|
||||
|
||||
if (file_length==-1) return (false);
|
||||
|
||||
buffer = malloc (file_length+10);
|
||||
|
||||
if (!buffer){
|
||||
Memory_Error();
|
||||
return (false);
|
||||
}
|
||||
|
||||
if (read (handle , buffer , file_length)!=file_length){
|
||||
File_Error(file_name);
|
||||
free(buffer);
|
||||
return (false);
|
||||
}
|
||||
|
||||
close (handle);
|
||||
*load_addr = (unsigned)buffer;
|
||||
return (file_length);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/***********************************************************************************************
|
||||
* Map_Profiler_Hits -- map function hits from sample file to functions in map file *
|
||||
* *
|
||||
* *
|
||||
* *
|
||||
* INPUT: Nothing *
|
||||
* *
|
||||
* OUTPUT: Nothing *
|
||||
* *
|
||||
* WARNINGS: Map file functions must be sorted into address order 1st *
|
||||
* *
|
||||
* HISTORY: *
|
||||
* 11/20/95 5:28PM ST : Created *
|
||||
*=============================================================================================*/
|
||||
|
||||
void Map_Profiler_Hits (void)
|
||||
{
|
||||
unsigned *samples=(unsigned*)SampleFileBuffer;
|
||||
unsigned function_hit;
|
||||
|
||||
for (int i=SAMPLE_START ; i<SampleFileLength/4 ; i++){
|
||||
|
||||
function_hit=*(samples+i);
|
||||
if (1023==(1023 & i)){
|
||||
cprintf (".");
|
||||
}
|
||||
|
||||
|
||||
for (int j=TotalFunctions-1 ; j>=0 ; j--){
|
||||
if (FunctionList[j].FunctionAddress < function_hit){
|
||||
FunctionList[j].Hits++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/***********************************************************************************************
|
||||
* Sort_Functions -- hideous bubble sort of functions into address order *
|
||||
* *
|
||||
* *
|
||||
* INPUT: Nothing *
|
||||
* *
|
||||
* OUTPUT: Nothing *
|
||||
* *
|
||||
* WARNINGS: None *
|
||||
* *
|
||||
* HISTORY: *
|
||||
* 11/20/95 5:29PM ST : Created *
|
||||
*=============================================================================================*/
|
||||
|
||||
void Sort_Functions (void)
|
||||
{
|
||||
Function address_swap;
|
||||
|
||||
if (TotalFunctions>1){
|
||||
|
||||
for (int outer=0 ; outer <TotalFunctions ; outer++){
|
||||
|
||||
address_swap.FunctionAddress=0;
|
||||
|
||||
if (127==(127 & outer)){
|
||||
cprintf (".");
|
||||
}
|
||||
|
||||
for (int inner=0 ; inner < TotalFunctions-1 ; inner++){
|
||||
|
||||
if (FunctionList[inner].FunctionAddress > FunctionList[inner+1].FunctionAddress ){
|
||||
|
||||
memcpy (&address_swap , &FunctionList[inner] , sizeof(Function));
|
||||
memcpy (&FunctionList[inner] , &FunctionList[inner+1] , sizeof(Function));
|
||||
memcpy (&FunctionList[inner+1] , &address_swap , sizeof(Function));
|
||||
}
|
||||
}
|
||||
|
||||
if (!address_swap.FunctionAddress) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/***********************************************************************************************
|
||||
* Sort_Functions -- hideous bubble sort of functions into usage order *
|
||||
* *
|
||||
* *
|
||||
* INPUT: Nothing *
|
||||
* *
|
||||
* OUTPUT: Nothing *
|
||||
* *
|
||||
* WARNINGS: None *
|
||||
* *
|
||||
* HISTORY: *
|
||||
* 11/20/95 5:29PM ST : Created *
|
||||
*=============================================================================================*/
|
||||
|
||||
void Sort_Functions_Again (void)
|
||||
{
|
||||
Function address_swap;
|
||||
|
||||
if (TotalFunctions>1){
|
||||
|
||||
for (int outer=0 ; outer <TotalFunctions ; outer++){
|
||||
|
||||
address_swap.FunctionAddress=0;
|
||||
|
||||
if (127==(127 & outer)){
|
||||
cprintf (".");
|
||||
}
|
||||
|
||||
for (int inner=0 ; inner < TotalFunctions-1 ; inner++){
|
||||
|
||||
if (FunctionList[inner].Hits < FunctionList[inner+1].Hits ){
|
||||
|
||||
memcpy (&address_swap , &FunctionList[inner] , sizeof(Function));
|
||||
memcpy (&FunctionList[inner] , &FunctionList[inner+1] , sizeof(Function));
|
||||
memcpy (&FunctionList[inner+1] , &address_swap , sizeof(Function));
|
||||
}
|
||||
}
|
||||
|
||||
if (!address_swap.FunctionAddress) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/***********************************************************************************************
|
||||
* Output_Profile -- output the function data to the screen *
|
||||
* *
|
||||
* *
|
||||
* INPUT: Nothing *
|
||||
* *
|
||||
* OUTPUT: Nothing *
|
||||
* *
|
||||
* WARNINGS: None *
|
||||
* *
|
||||
* HISTORY: *
|
||||
* 11/20/95 5:31PM ST : Created *
|
||||
*=============================================================================================*/
|
||||
|
||||
void Output_Profile(void)
|
||||
{
|
||||
double period=(((double)SampleFileLength/(double)4) - (double)SAMPLE_START) / (double)SampleRate;
|
||||
double percentage;
|
||||
|
||||
printf ( "\n Profile information from %s and %s.\n\n",SampleFile,MapFile);
|
||||
|
||||
printf ( "Samples collected:%d\n" , SampleFileLength/4-SAMPLE_START);
|
||||
printf ( "Sample rate :%d samples per second\n",SampleRate);
|
||||
printf ( "Sample period :%f seconds\n\n\n" , period);
|
||||
|
||||
printf ( "Hits %% Function\n");
|
||||
printf ( "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
|
||||
|
||||
for (int i=0 ; i<TotalFunctions ; i++){
|
||||
|
||||
if (FunctionList[i].Hits){
|
||||
|
||||
percentage= ((double)FunctionList[i].Hits*(double)100) / ((double)SampleFileLength/(double)4-(double)SAMPLE_START);
|
||||
|
||||
printf ("%-6d %-3.3f%% %s\n",FunctionList[i].Hits , percentage , FunctionList[i].FunctionName);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/***********************************************************************************************
|
||||
* Extract_Function_Addresses -- gets the addresses of all global functions from a map file *
|
||||
* *
|
||||
* *
|
||||
* INPUT: Nothing *
|
||||
* *
|
||||
* OUTPUT: Nothing *
|
||||
* *
|
||||
* WARNINGS: true if successfully extracted *
|
||||
* *
|
||||
* HISTORY: *
|
||||
* 11/20/95 5:31PM ST : Created *
|
||||
*=============================================================================================*/
|
||||
|
||||
bool Extract_Function_Addresses(void)
|
||||
{
|
||||
char *map_ptr;
|
||||
char *segment_ptr;
|
||||
char *end_str_ptr;
|
||||
unsigned chars_left=MapFileLength;
|
||||
int function_name_length;
|
||||
unsigned end_of_last_code_segment;
|
||||
unsigned code_segment_start;
|
||||
unsigned code_segment_size;
|
||||
char unknown[]={"Windows API or system code."};
|
||||
|
||||
/*
|
||||
** Clear out the list of functions
|
||||
*/
|
||||
memset (&FunctionNames[0] , 0 , NAME_TABLE_SIZE);
|
||||
|
||||
/*
|
||||
** Search for the 'Segments' header in the memory map
|
||||
*/
|
||||
segment_ptr = Search_For_String ("Segments" , MapFileBuffer , chars_left);
|
||||
if (!segment_ptr) return (false);
|
||||
chars_left = MapFileLength - ( (unsigned)segment_ptr - (unsigned)MapFileBuffer );
|
||||
segment_ptr = Search_For_String ("+-----" , segment_ptr , chars_left);
|
||||
segment_ptr +=2;
|
||||
chars_left = MapFileLength - ( (unsigned)segment_ptr - (unsigned)MapFileBuffer );
|
||||
|
||||
/*
|
||||
** Get the length of the segment section by searching for the start of the next section
|
||||
*/
|
||||
end_str_ptr = Search_For_String ("+-----" , segment_ptr , chars_left);
|
||||
if (end_str_ptr){
|
||||
chars_left = end_str_ptr - segment_ptr;
|
||||
} else {
|
||||
return (false);
|
||||
}
|
||||
|
||||
EndCodeSegment = 0;
|
||||
|
||||
/*
|
||||
** Find the end of the last code segment
|
||||
*/
|
||||
do {
|
||||
/*
|
||||
** Search for a code segment identifier
|
||||
*/
|
||||
chars_left = end_str_ptr - segment_ptr;
|
||||
segment_ptr = Search_For_String ("CODE" , segment_ptr , chars_left);
|
||||
if (!segment_ptr) break; //No more code segments so break
|
||||
|
||||
/*
|
||||
** Search for the segment address which should always be 0001
|
||||
*/
|
||||
chars_left = end_str_ptr - segment_ptr;
|
||||
segment_ptr = Search_For_String ("0001:" , segment_ptr , chars_left);
|
||||
if (!segment_ptr) return (false); //Couldnt find the segment address - must be a problem so abort
|
||||
|
||||
/*
|
||||
** Get the start address and length of the segment
|
||||
*/
|
||||
code_segment_start = Get_Hex(segment_ptr+5,8);
|
||||
code_segment_size = Get_Hex(segment_ptr+16,8);
|
||||
|
||||
/*
|
||||
** If this segment ends higher in memory than the previous highest then
|
||||
** we have a new last segment
|
||||
*/
|
||||
if (code_segment_start+code_segment_size > EndCodeSegment){
|
||||
EndCodeSegment = code_segment_start+code_segment_size;
|
||||
}
|
||||
|
||||
chars_left = end_str_ptr - segment_ptr;
|
||||
segment_ptr = Search_For_Char ( 13 , segment_ptr , chars_left );
|
||||
chars_left = end_str_ptr - segment_ptr;
|
||||
|
||||
} while (chars_left > 0);
|
||||
|
||||
|
||||
|
||||
chars_left=MapFileLength;
|
||||
/*
|
||||
** Search for the 'Memory Map' segment of the map file
|
||||
*/
|
||||
map_ptr = Search_For_String ("Memory Map" , MapFileBuffer , chars_left);
|
||||
if (!map_ptr){
|
||||
return (false);
|
||||
}
|
||||
chars_left = MapFileLength - ( (unsigned)map_ptr - (unsigned)MapFileBuffer );
|
||||
|
||||
/*
|
||||
** Get the length of the memory map segment by searching for the start of the next segment
|
||||
*/
|
||||
end_str_ptr = Search_For_String ("+-----" , map_ptr , chars_left);
|
||||
if (end_str_ptr){
|
||||
MapFileLength = ((unsigned)MapFileBuffer + MapFileLength) - (unsigned)end_str_ptr;
|
||||
}
|
||||
chars_left = MapFileLength - ( (unsigned)map_ptr - (unsigned)MapFileBuffer );
|
||||
|
||||
/*
|
||||
** Reset the total number of functions found
|
||||
*/
|
||||
TotalFunctions = 0;
|
||||
|
||||
/*
|
||||
**
|
||||
** Find each occurrence of 0001: as all the functions we want are in the 1st segment
|
||||
**
|
||||
*/
|
||||
do {
|
||||
/*
|
||||
** Find '0001:'
|
||||
*/
|
||||
map_ptr = Search_For_String ("0001:" , map_ptr , chars_left);
|
||||
if (!map_ptr){
|
||||
break;
|
||||
}
|
||||
chars_left = MapFileLength - ( (unsigned)map_ptr - (unsigned)MapFileBuffer );
|
||||
|
||||
/*
|
||||
** Skip the '0001:' portion of the address and get the hext offset of the function
|
||||
*/
|
||||
map_ptr+=5;
|
||||
FunctionList[TotalFunctions].FunctionAddress=Get_Hex(map_ptr,8);
|
||||
|
||||
/*
|
||||
** Skip to the function name and get its length by searching for the end of the line
|
||||
*/
|
||||
map_ptr+=10;
|
||||
chars_left = MapFileLength - ( (unsigned)map_ptr - (unsigned)MapFileBuffer );
|
||||
end_str_ptr = Search_For_Char (13 , map_ptr , chars_left);
|
||||
if (!end_str_ptr){
|
||||
break;
|
||||
}
|
||||
function_name_length = (unsigned)end_str_ptr - (unsigned)map_ptr;
|
||||
|
||||
/*
|
||||
** Copy the function name into the name list and keep a pointer to it
|
||||
*/
|
||||
memcpy (FunctionNamePtr , map_ptr , function_name_length);
|
||||
FunctionList[TotalFunctions].FunctionName = FunctionNamePtr;
|
||||
FunctionNamePtr += function_name_length+1; //Leave an extra 0 on the end as a terminator
|
||||
FunctionList[TotalFunctions].Hits = 0; //We dont yet know how many times we hit it
|
||||
TotalFunctions++;
|
||||
|
||||
} while (1);
|
||||
|
||||
|
||||
/*
|
||||
** Add in a dummy function at the highest address to represent unknown code hits
|
||||
*/
|
||||
FunctionList[TotalFunctions].FunctionAddress = EndCodeSegment;
|
||||
memcpy (FunctionNamePtr , &unknown , sizeof (unknown));
|
||||
FunctionList[TotalFunctions].FunctionName = FunctionNamePtr;
|
||||
FunctionNamePtr += sizeof (unknown);
|
||||
FunctionList[TotalFunctions].Hits = 0;
|
||||
TotalFunctions++;
|
||||
|
||||
return (true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/***********************************************************************************************
|
||||
* Get_Hex -- nasty function to convert an ascii hex number to an unsigned int *
|
||||
* I'm sure there must be a lovely 'c' way of doing this but I dont know what it is *
|
||||
* *
|
||||
* *
|
||||
* INPUT: ptr to ascii hex string , number of digits in string *
|
||||
* *
|
||||
* OUTPUT: value of hex string *
|
||||
* *
|
||||
* WARNINGS: None *
|
||||
* *
|
||||
* HISTORY: *
|
||||
* 11/20/95 5:39PM ST : Created *
|
||||
*=============================================================================================*/
|
||||
|
||||
unsigned Get_Hex (char string[] , int length)
|
||||
{
|
||||
unsigned hex_val=0;
|
||||
int multiplier=1;
|
||||
char hex_char;
|
||||
|
||||
for (int i=0 ; i<length ; i++){
|
||||
hex_char=string[length-1-i];
|
||||
|
||||
switch (hex_char){
|
||||
case '0':
|
||||
hex_char=0;
|
||||
break;
|
||||
|
||||
case '1':
|
||||
hex_char=1;
|
||||
break;
|
||||
|
||||
case '2':
|
||||
hex_char=2;
|
||||
break;
|
||||
|
||||
case '3':
|
||||
hex_char=3;
|
||||
break;
|
||||
|
||||
case '4':
|
||||
hex_char=4;
|
||||
break;
|
||||
|
||||
case '5':
|
||||
hex_char=5;
|
||||
break;
|
||||
|
||||
case '6':
|
||||
hex_char=6;
|
||||
break;
|
||||
|
||||
case '7':
|
||||
hex_char=7;
|
||||
break;
|
||||
|
||||
case '8':
|
||||
hex_char=8;
|
||||
break;
|
||||
|
||||
case '9':
|
||||
hex_char=9;
|
||||
break;
|
||||
|
||||
case 'A':
|
||||
hex_char=10;
|
||||
break;
|
||||
|
||||
case 'B':
|
||||
hex_char=11;
|
||||
break;
|
||||
|
||||
case 'C':
|
||||
hex_char=12;
|
||||
break;
|
||||
|
||||
case 'D':
|
||||
hex_char=13;
|
||||
break;
|
||||
|
||||
case 'E':
|
||||
hex_char=14;
|
||||
break;
|
||||
|
||||
case 'F':
|
||||
hex_char=15;
|
||||
break;
|
||||
|
||||
case 'a':
|
||||
hex_char=10;
|
||||
break;
|
||||
|
||||
case 'b':
|
||||
hex_char=11;
|
||||
break;
|
||||
|
||||
case 'c':
|
||||
hex_char=12;
|
||||
break;
|
||||
|
||||
case 'd':
|
||||
hex_char=13;
|
||||
break;
|
||||
|
||||
case 'e':
|
||||
hex_char=14;
|
||||
break;
|
||||
|
||||
case 'f':
|
||||
hex_char=15;
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
hex_val += hex_char * multiplier;
|
||||
multiplier = multiplier<<4;
|
||||
}
|
||||
return (hex_val);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***********************************************************************************************
|
||||
* Search_For_Char -- search through ascii data for a particular character *
|
||||
* *
|
||||
* *
|
||||
* *
|
||||
* INPUT: character *
|
||||
* ptr to buffer *
|
||||
* length of buffer *
|
||||
* *
|
||||
* OUTPUT: ptr to char in buffer or NULL if not found *
|
||||
* *
|
||||
* WARNINGS: None *
|
||||
* *
|
||||
* HISTORY: *
|
||||
* 11/20/95 5:41PM ST : Created *
|
||||
*=============================================================================================*/
|
||||
|
||||
char *Search_For_Char (char character , char buffer_ptr[] , int buffer_length)
|
||||
{
|
||||
|
||||
for ( unsigned i=0 ; i<buffer_length ; i++){
|
||||
|
||||
if (buffer_ptr[i]==character){
|
||||
return ((char*) (unsigned)buffer_ptr+i);
|
||||
}
|
||||
}
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***********************************************************************************************
|
||||
* Search_For_String -- search for a string of chars within a buffer *
|
||||
* *
|
||||
* *
|
||||
* *
|
||||
* INPUT: string *
|
||||
* ptr to buffer to search in *
|
||||
* length of buffer *
|
||||
* *
|
||||
* OUTPUT: ptr to string in buffer or NULL if not found *
|
||||
* *
|
||||
* WARNINGS: None *
|
||||
* *
|
||||
* HISTORY: *
|
||||
* 11/20/95 5:42PM ST : Created *
|
||||
*=============================================================================================*/
|
||||
|
||||
char *Search_For_String (char *string , char *buffer_ptr , int buffer_length)
|
||||
{
|
||||
int j;
|
||||
int string_length=strlen(string);
|
||||
|
||||
for (int i=0 ; i<buffer_length-string_length ; i++){
|
||||
|
||||
for (j=0 ; j<string_length ; j++){
|
||||
if ( *(string+j) != *(buffer_ptr+i+j)) break;
|
||||
}
|
||||
if (j==string_length) return buffer_ptr+i;
|
||||
}
|
||||
|
||||
return (NULL);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
272
WIN32LIB/PROFILE/WPROFILE.CPP
Normal file
272
WIN32LIB/PROFILE/WPROFILE.CPP
Normal file
@@ -0,0 +1,272 @@
|
||||
/*
|
||||
** 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user