//============================================================
//
// winmain.c - Win32 main program
//
// Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team.
// Visit http://mamedev.org for licensing and usage restrictions.
//
//============================================================
// standard windows headers
#define _WIN32_WINNT 0x0501
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <commctrl.h>
#include <mmsystem.h>
#include <tchar.h>
// standard C headers
#include <ctype.h>
#include <stdarg.h>
// MAME headers
#include "driver.h"
#include "clifront.h"
// MAMEOS headers
#include "winmain.h"
#include "window.h"
#include "video.h"
#include "sound.h"
#include "input.h"
#include "output.h"
#include "config.h"
#include "osdepend.h"
#include "strconv.h"
#include "winutf8.h"
#include "winutil.h"
#define ENABLE_PROFILER 0
#define DEBUG_SLOW_LOCKS 0
//============================================================
// TYPE DEFINITIONS
//============================================================
#ifdef UNICODE
#define UNICODE_POSTFIX "W"
#else
#define UNICODE_POSTFIX "A"
#endif
typedef BOOL (WINAPI *try_enter_critical_section_ptr)(LPCRITICAL_SECTION lpCriticalSection);
typedef HANDLE (WINAPI *av_set_mm_thread_characteristics_ptr)(LPCTSTR TaskName, LPDWORD TaskIndex);
typedef HANDLE (WINAPI *av_set_mm_max_thread_characteristics_ptr)(LPCTSTR FirstTask, LPCTSTR SecondTask, LPDWORD TaskIndex);
typedef BOOL (WINAPI *av_revert_mm_thread_characteristics_ptr)(HANDLE AvrtHandle);
//============================================================
// GLOBAL VARIABLES
//============================================================
// this line prevents globbing on the command line
int _CRT_glob = 0;
//============================================================
// LOCAL VARIABLES
//============================================================
static try_enter_critical_section_ptr try_enter_critical_section;
static av_set_mm_thread_characteristics_ptr av_set_mm_thread_characteristics;
static av_set_mm_max_thread_characteristics_ptr av_set_mm_max_thread_characteristics;
static av_revert_mm_thread_characteristics_ptr av_revert_mm_thread_characteristics;
static char mapfile_name[MAX_PATH];
static LPTOP_LEVEL_EXCEPTION_FILTER pass_thru_filter;
#ifndef MESS
static const TCHAR helpfile[] = TEXT("docs\\windows.txt");
#else
static const TCHAR helpfile[] = TEXT("mess.chm");
#endif
//static HANDLE mm_task = NULL;
//static DWORD task_index = 0;
static int timeresult;
//static MMRESULT result;
static TIMECAPS caps;
//============================================================
// PROTOTYPES
//============================================================
static void osd_exit(running_machine *machine);
static void soft_link_functions(void);
static int is_double_click_start(int argc);
static LONG CALLBACK exception_filter(struct _EXCEPTION_POINTERS *info);
static const char *lookup_symbol(UINT32 address);
static int get_code_base_size(UINT32 *base, UINT32 *size);
static void start_profiler(void);
static void stop_profiler(void);
//============================================================
// OPTIONS
//============================================================
// struct definitions
const options_entry mame_win_options[] =
{
// debugging options
{ NULL, NULL, OPTION_HEADER, "WINDOWS DEBUGGING OPTIONS" },
{ "oslog", "0", OPTION_BOOLEAN, "output error.log data to the system debugger" },
// performance options
{ NULL, NULL, OPTION_HEADER, "WINDOWS PERFORMANCE OPTIONS" },
{ "priority(-15-1)", "0", 0, "thread priority for the main game thread; range from -15 to 1" },
{ "multithreading;mt", "0", OPTION_BOOLEAN, "enable multithreading; this enables rendering and blitting on a separate thread" },
// video options
{ NULL, NULL, OPTION_HEADER, "WINDOWS VIDEO OPTIONS" },
{ "video", "d3d", 0, "video output method: none, gdi, ddraw, or d3d" },
{ "numscreens(1-4)", "1", 0, "number of screens to create; usually, you want just one" },
{ "window;w", "0", OPTION_BOOLEAN, "enable window mode; otherwise, full screen mode is assumed" },
{ "maximize;max", "1", OPTION_BOOLEAN, "default to maximized windows; otherwise, windows will be minimized" },
{ "keepaspect;ka", "1", OPTION_BOOLEAN, "constrain to the proper aspect ratio" },
{ "prescale", "1", 0, "scale screen rendering by this amount in software" },
{ "effect", "none", 0, "name of a PNG file to use for visual effects, or 'none'" },
{ "waitvsync", "0", OPTION_BOOLEAN, "enable waiting for the start of VBLANK before flipping screens; reduces tearing effects" },
{ "syncrefresh", "0", OPTION_BOOLEAN, "enable using the start of VBLANK for throttling instead of the game time" },
// DirectDraw-specific options
{ NULL, NULL, OPTION_HEADER, "DIRECTDRAW-SPECIFIC OPTIONS" },
{ "hwstretch;hws", "1", OPTION_BOOLEAN, "enable hardware stretching" },
// Direct3D-specific options
{ NULL, NULL, OPTION_HEADER, "DIRECT3D-SPECIFIC OPTIONS" },
{ "d3dversion(8-9)", "9", 0, "specify the preferred Direct3D version (8 or 9)" },
{ "filter;d3dfilter;flt", "1", OPTION_BOOLEAN, "enable bilinear filtering on screen output" },
// per-window options
{ NULL, NULL, OPTION_HEADER, "PER-WINDOW VIDEO OPTIONS" },
{ "screen", "auto", 0, "explicit name of all screens; 'auto' here will try to make a best guess" },
{ "aspect;screen_aspect", "auto", 0, "aspect ratio for all screens; 'auto' here will try to make a best guess" },
{ "resolution;r", "auto", 0, "preferred resolution for all screens; format is <width>x<height>[@<refreshrate>] or 'auto'" },
{ "view", "auto", 0, "preferred view for all screens" },
{ "screen0", "auto", 0, "explicit name of the first screen; 'auto' here will try to make a best guess" },
{ "aspect0", "auto", 0, "aspect ratio of the first screen; 'auto' here will try to make a best guess" },
{ "resolution0;r0", "auto", 0, "preferred resolution of the first screen; format is <width>x<height>[@<refreshrate>] or 'auto'" },
{ "view0", "auto", 0, "preferred view for the first screen" },
{ "screen1", "auto", 0, "explicit name of the second screen; 'auto' here will try to make a best guess" },
{ "aspect1", "auto", 0, "aspect ratio of the second screen; 'auto' here will try to make a best guess" },
{ "resolution1;r1", "auto", 0, "preferred resolution of the second screen; format is <width>x<height>[@<refreshrate>] or 'auto'" },
{ "view1", "auto", 0, "preferred view for the second screen" },
{ "screen2", "auto", 0, "explicit name of the third screen; 'auto' here will try to make a best guess" },
{ "aspect2", "auto", 0, "aspect ratio of the third screen; 'auto' here will try to make a best guess" },
{ "resolution2;r2", "auto", 0, "preferred resolution of the third screen; format is <width>x<height>[@<refreshrate>] or 'auto'" },
{ "view2", "auto", 0, "preferred view for the third screen" },
{ "screen3", "auto", 0, "explicit name of the fourth screen; 'auto' here will try to make a best guess" },
{ "aspect3", "auto", 0, "aspect ratio of the fourth screen; 'auto' here will try to make a best guess" },
{ "resolution3;r3", "auto", 0, "preferred resolution of the fourth screen; format is <width>x<height>[@<refreshrate>] or 'auto'" },
{ "view3", "auto", 0, "preferred view for the fourth screen" },
// full screen options
{ NULL, NULL, OPTION_HEADER, "FULL SCREEN OPTIONS" },
{ "triplebuffer;tb", "0", OPTION_BOOLEAN, "enable triple buffering" },
{ "switchres", "0", OPTION_BOOLEAN, "enable resolution switching" },
{ "full_screen_brightness;fsb(0.1-2.0)","1.0", 0, "brightness value in full screen mode" },
{ "full_screen_contrast;fsc(0.1-2.0)", "1.0", 0, "contrast value in full screen mode" },
{ "full_screen_gamma;fsg(0.1-3.0)", "1.0", 0, "gamma value in full screen mode" },
// sound options
{ NULL, NULL, OPTION_HEADER, "WINDOWS SOUND OPTIONS" },
{ "audio_latency(1-5)", "2", 0, "set audio latency (increase to reduce glitches)" },
// input options
{ NULL, NULL, OPTION_HEADER, "INPUT DEVICE OPTIONS" },
{ "dual_lightgun;dual", "0", OPTION_BOOLEAN, "enable dual lightgun input" },
{ NULL }
};
//============================================================
// winui_output_error
//============================================================
static void winui_output_error(void *param, const char *format, va_list argptr)
{
char buffer[1024];
// if we are in fullscreen mode, go to windowed mode
if ((video_config.windowed == 0) && (win_window_list != NULL))
winwindow_toggle_full_screen();
vsnprintf(buffer, ARRAY_LENGTH(buffer), format, argptr);
win_message_box_utf8(win_window_list ? win_window_list->hwnd : NULL, buffer, APPNAME, MB_OK);
}
//============================================================
// utf8_main
//============================================================
int utf8_main(int argc, char **argv)
{
char *ext;
// initialize common controls
InitCommonControls();
// set up exception handling
pass_thru_filter = SetUnhandledExceptionFilter(exception_filter);
// if we're a GUI app, out errors to message boxes
if (win_is_gui_application() || is_double_click_start(argc))
{
// if we are a GUI app, output errors to message boxes
mame_set_output_channel(OUTPUT_CHANNEL_ERROR, winui_output_error, NULL, NULL, NULL);
// make sure any console window that opened on our behalf is nuked
FreeConsole();
}
// soft-link optional functions
soft_link_functions();
strcpy(mapfile_name, argv[0]);
ext = strchr(mapfile_name, '.');
if (ext)
strcpy(ext, ".map");
else
strcat(mapfile_name, ".map");
// parse config and cmdline options
return cli_execute(argc, argv, mame_win_options);
}
//============================================================
// output_oslog
//============================================================
static void output_oslog(running_machine *machine, const char *buffer)
{
win_output_debug_string_utf8(buffer);
}
//============================================================
// osd_init
//============================================================
void osd_init(running_machine *machine)
{
// thread priority
if (!options_get_bool(mame_options(), OPTION_DEBUG))
SetThreadPriority(GetCurrentThread(), options_get_int(mame_options(), WINOPTION_PRIORITY));
// ensure we get called on the way out
add_exit_callback(machine, osd_exit);
// initialize the subsystems
winvideo_init(machine);
winsound_init(machine);
wininput_init(machine);
winoutput_init(machine);
// hook up the debugger log
if (options_get_bool(mame_options(), WINOPTION_OSLOG))
add_logerror_callback(machine, output_oslog);
// crank up the multimedia timer resolution to its max
// this gives the system much finer timeslices
timeresult = timeGetDevCaps(&caps, sizeof(caps));
if (timeresult == TIMERR_NOERROR)
timeBeginPeriod(caps.wPeriodMin);
// set our multimedia tasks if we can
// if (av_set_mm_thread_characteristics != NULL)
// mm_task = (*av_set_mm_thread_characteristics)(TEXT("Playback"), &task_index);
start_profiler();
}
static void osd_exit(running_machine *machine)
{
stop_profiler();
// turn off our multimedia tasks
// if (av_revert_mm_thread_characteristics != NULL)
// (*av_revert_mm_thread_characteristics)(mm_task);
// restore the timer resolution
if (timeresult == TIMERR_NOERROR)
timeEndPeriod(caps.wPeriodMin);
// one last pass at events
winwindow_process_events(0);
}
//============================================================
// soft_link_functions
//============================================================
static void soft_link_functions(void)
{
HMODULE library;
// see if we can use TryEnterCriticalSection
try_enter_critical_section = NULL;
library = LoadLibrary(TEXT("kernel32.dll"));
if (library != NULL)
try_enter_critical_section = (try_enter_critical_section_ptr)GetProcAddress(library, "TryEnterCriticalSection");
// see if we can use the multimedia scheduling functions in Vista
av_set_mm_thread_characteristics = NULL;
av_set_mm_max_thread_characteristics = NULL;
av_revert_mm_thread_characteristics = NULL;
library = LoadLibrary(TEXT("avrt.dll"));
if (library != NULL)
{
av_set_mm_thread_characteristics = (av_set_mm_thread_characteristics_ptr)GetProcAddress(library, "AvSetMmThreadCharacteristics" UNICODE_POSTFIX);
av_set_mm_max_thread_characteristics = (av_set_mm_max_thread_characteristics_ptr)GetProcAddress(library, "AvSetMmMaxThreadCharacteristics" UNICODE_POSTFIX);
av_revert_mm_thread_characteristics = (av_revert_mm_thread_characteristics_ptr)GetProcAddress(library, "AvRevertMmThreadCharacteristics");
}
}
//============================================================
// check_for_double_click_start
//============================================================
static int is_double_click_start(int argc)
{
STARTUPINFO startup_info = { sizeof(STARTUPINFO) };
// determine our startup information
GetStartupInfo(&startup_info);
// try to determine if MAME was simply double-clicked
return (argc <= 1 && startup_info.dwFlags && !(startup_info.dwFlags & STARTF_USESTDHANDLES));
}
//============================================================
// exception_filter
//============================================================
static LONG CALLBACK exception_filter(struct _EXCEPTION_POINTERS *info)
{
static const struct
{
DWORD code;
const char *string;
} exception_table[] =
{
{ EXCEPTION_ACCESS_VIOLATION, "ACCESS VIOLATION" },
{ EXCEPTION_DATATYPE_MISALIGNMENT, "DATATYPE MISALIGNMENT" },
{ EXCEPTION_BREAKPOINT, "BREAKPOINT" },
{ EXCEPTION_SINGLE_STEP, "SINGLE STEP" },
{ EXCEPTION_ARRAY_BOUNDS_EXCEEDED, "ARRAY BOUNDS EXCEEDED" },
{ EXCEPTION_FLT_DENORMAL_OPERAND, "FLOAT DENORMAL OPERAND" },
{ EXCEPTION_FLT_DIVIDE_BY_ZERO, "FLOAT DIVIDE BY ZERO" },
{ EXCEPTION_FLT_INEXACT_RESULT, "FLOAT INEXACT RESULT" },
{ EXCEPTION_FLT_INVALID_OPERATION, "FLOAT INVALID OPERATION" },
{ EXCEPTION_FLT_OVERFLOW, "FLOAT OVERFLOW" },
{ EXCEPTION_FLT_STACK_CHECK, "FLOAT STACK CHECK" },
{ EXCEPTION_FLT_UNDERFLOW, "FLOAT UNDERFLOW" },
{ EXCEPTION_INT_DIVIDE_BY_ZERO, "INTEGER DIVIDE BY ZERO" },
{ EXCEPTION_INT_OVERFLOW, "INTEGER OVERFLOW" },
{ EXCEPTION_PRIV_INSTRUCTION, "PRIVILEGED INSTRUCTION" },
{ EXCEPTION_IN_PAGE_ERROR, "IN PAGE ERROR" },
{ EXCEPTION_ILLEGAL_INSTRUCTION, "ILLEGAL INSTRUCTION" },
{ EXCEPTION_NONCONTINUABLE_EXCEPTION,"NONCONTINUABLE EXCEPTION" },
{ EXCEPTION_STACK_OVERFLOW, "STACK OVERFLOW" },
{ EXCEPTION_INVALID_DISPOSITION, "INVALID DISPOSITION" },
{ EXCEPTION_GUARD_PAGE, "GUARD PAGE VIOLATION" },
{ EXCEPTION_INVALID_HANDLE, "INVALID HANDLE" },
{ 0, "UNKNOWN EXCEPTION" }
};
static int already_hit = 0;
#ifndef PTR64
UINT32 code_start, code_size;
#endif
int i;
// if we're hitting this recursively, just exit
if (already_hit)
ExitProcess(100);
already_hit = 1;
#ifdef MAME_DEBUG
{
extern void debug_flush_traces(void);
debug_flush_traces();
}
#endif
// find our man
for (i = 0; exception_table[i].code != 0; i++)
if (info->ExceptionRecord->ExceptionCode == exception_table[i].code)
break;
// print the exception type and address
fprintf(stderr, "\n-----------------------------------------------------\n");
fprintf(stderr, "Exception at EIP=%p%s: %s\n", info->ExceptionRecord->ExceptionAddress,
lookup_symbol((FPTR)info->ExceptionRecord->ExceptionAddress), exception_table[i].string);
// for access violations, print more info
if (info->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION)
fprintf(stderr, "While attempting to %s memory at %08X\n",
info->ExceptionRecord->ExceptionInformation[0] ? "write" : "read",
(UINT32)info->ExceptionRecord->ExceptionInformation[1]);
// print the state of the CPU
fprintf(stderr, "-----------------------------------------------------\n");
#ifdef PTR64
fprintf(stderr, "RAX=%p RBX=%p RCX=%p RDX=%p\n",
(void *)info->ContextRecord->Rax,
(void *)info->ContextRecord->Rbx,
(void *)info->ContextRecord->Rcx,
(void *)info->ContextRecord->Rdx);
fprintf(stderr, "RSI=%p RDI=%p RBP=%p RSP=%p\n",
(void *)info->ContextRecord->Rsi,
(void *)info->ContextRecord->Rdi,
(void *)info->ContextRecord->Rbp,
(void *)info->ContextRecord->Rsp);
fprintf(stderr, " R8=%p R9=%p R10=%p R11=%p\n",
(void *)info->ContextRecord->R8,
(void *)info->ContextRecord->R9,
(void *)info->ContextRecord->R10,
(void *)info->ContextRecord->R11);
fprintf(stderr, "R12=%p R13=%p R14=%p R15=%p\n",
(void *)info->ContextRecord->R12,
(void *)info->ContextRecord->R13,
(void *)info->ContextRecord->R14,
(void *)info->ContextRecord->R15);
#else
fprintf(stderr, "EAX=%p EBX=%p ECX=%p EDX=%p\n",
(void *)info->ContextRecord->Eax,
(void *)info->ContextRecord->Ebx,
(void *)info->ContextRecord->Ecx,
(void *)info->ContextRecord->Edx);
fprintf(stderr, "ESI=%p EDI=%p EBP=%p ESP=%p\n",
(void *)info->ContextRecord->Esi,
(void *)info->ContextRecord->Edi,
(void *)info->ContextRecord->Ebp,
(void *)info->ContextRecord->Esp);
#endif
#ifndef PTR64
// crawl the stack for a while
if (get_code_base_size(&code_start, &code_size))
{
char prev_symbol[1024], curr_symbol[1024];
UINT32 last_call = (UINT32)info->ExceptionRecord->ExceptionAddress;
UINT32 esp_start = info->ContextRecord->Esp;
UINT32 esp_end = (esp_start | 0xffff) + 1;
UINT32 esp;
// reprint the actual exception address
fprintf(stderr, "-----------------------------------------------------\n");
fprintf(stderr, "Stack crawl:\n");
fprintf(stderr, "exception-> %08X%s\n", last_call, strcpy(prev_symbol, lookup_symbol(last_call)));
// crawl the stack until we hit the next 64k boundary
for (esp = esp_start; esp < esp_end; esp += 4)
{
UINT32 stack_val = *(UINT32 *)esp;
// if the value on the stack points within the code block, check it out
if (stack_val >= code_start && stack_val < code_start + code_size)
{
UINT8 *return_addr = (UINT8 *)stack_val;
UINT32 call_target = 0;
// make sure the code that we think got us here is actually a CALL instruction
if (return_addr[-5] == 0xe8)
call_target = stack_val - 5 + *(INT32 *)&return_addr[-4];
if ((return_addr[-2] == 0xff && (return_addr[-1] & 0x38) == 0x10) ||
(return_addr[-3] == 0xff && (return_addr[-2] & 0x38) == 0x10) ||
(return_addr[-4] == 0xff && (return_addr[-3] & 0x38) == 0x10) ||
(return_addr[-5] == 0xff && (return_addr[-4] & 0x38) == 0x10) ||
(return_addr[-6] == 0xff && (return_addr[-5] & 0x38) == 0x10) ||
(return_addr[-7] == 0xff && (return_addr[-6] & 0x38) == 0x10))
call_target = 1;
// make sure it points somewhere a little before the last call
if (call_target == 1 || (call_target < last_call && call_target >= last_call - 0x1000))
{
char *stop_compare = strchr(prev_symbol, '+');
// don't print duplicate hits in the same routine
strcpy(curr_symbol, lookup_symbol(stack_val));
if (stop_compare == NULL || strncmp(curr_symbol, prev_symbol, stop_compare - prev_symbol))
{
strcpy(prev_symbol, curr_symbol);
fprintf(stderr, " %08X: %08X%s\n", esp, stack_val, curr_symbol);
last_call = stack_val;
}
}
}
}
}
#endif
// exit
ExitProcess(100);
}
//============================================================
// lookup_symbol
//============================================================
static const char *lookup_symbol(UINT32 address)
{
static char buffer[1024];
FILE * map = fopen(mapfile_name, "r");
char symbol[1024], best_symbol[1024];
UINT32 addr, best_addr = 0;
char line[1024];
// if no file, return nothing
if (map == NULL)
return "";
// reset the bests
*best_symbol = 0;
best_addr = 0;
// parse the file, looking for map entries
while (fgets(line, sizeof(line) - 1, map))
if (!strncmp(line, " 0x", 18))
if (sscanf(line, " 0x%08x %s", &addr, symbol) == 2)
if (addr <= address && addr > best_addr)
{
best_addr = addr;
strcpy(best_symbol, symbol);
}
// create the final result
if (address - best_addr > 0x10000)
return "";
sprintf(buffer, " (%s+0x%04x)", best_symbol, address - best_addr);
return buffer;
}
//============================================================
// get_code_base_size
//============================================================
static int get_code_base_size(UINT32 *base, UINT32 *size)
{
FILE * map = fopen(mapfile_name, "r");
char line[1024];
// if no file, return nothing
if (map == NULL)
return 0;
// parse the file, looking for .text entry
while (fgets(line, sizeof(line) - 1, map))
if (!strncmp(line, ".text 0x", 18))
if (sscanf(line, ".text 0x%08x 0x%x", base, size) == 2)
return 1;
return 0;
}
#if ENABLE_PROFILER
//============================================================
//
// profiler.c - Sampling profiler
//
//============================================================
//============================================================
// TYPE DEFINITIONS
//============================================================
#define MAX_SYMBOLS 65536
typedef struct _map_entry map_entry;
struct _map_entry
{
UINT32 start;
UINT32 end;
UINT32 hits;
char *name;
};
//============================================================
// LOCAL VARIABLES
//============================================================
static map_entry symbol_map[MAX_SYMBOLS];
static int map_entries;
static HANDLE profiler_thread;
static DWORD profiler_thread_id;
static volatile UINT8 profiler_thread_exit;
//============================================================
// compare_base
// compare_hits -- qsort callbacks to sort on
//============================================================
static int CLIB_DECL compare_start(const void *item1, const void *item2)
{
return ((const map_entry *)item1)->start - ((const map_entry *)item2)->start;
}
static int CLIB_DECL compare_hits(const void *item1, const void *item2)
{
return ((const map_entry *)item2)->hits - ((const map_entry *)item1)->hits;
}
//============================================================
// add_symbol_map_entry
// parse_map_file
//============================================================
static void add_symbol_map_entry(UINT32 start, const char *name)
{
if (map_entries == MAX_SYMBOLS)
fatalerror("Symbol table full");
symbol_map[map_entries].start = start;
symbol_map[map_entries].name = core_strdup(name);
if (symbol_map[map_entries].name == NULL)
fatalerror("Out of memory");
map_entries++;
}
static void parse_map_file(void)
{
int got_text = 0;
char line[1024];
FILE *map;
int i;
// open the map file
map = fopen(mapfile_name, "r");
if (!map)
return;
// parse out the various symbols into map entries
map_entries = 0;
while (fgets(line, sizeof(line) - 1, map))
{
/* look for the code boundaries */
if (!got_text && !strncmp(line, ".text 0x", 18))
{
UINT32 base, size;
if (sscanf(line, ".text 0x%08x 0x%x", &base, &size) == 2)
{
add_symbol_map_entry(base, "Code start");
add_symbol_map_entry(base+size, "Other");
got_text = 1;
}
}
/* look for symbols */
else if (!strncmp(line, " 0x", 18))
{
char symbol[1024];
UINT32 addr;
if (sscanf(line, " 0x%08x %s", &addr, symbol) == 2)
{
add_symbol_map_entry(addr, symbol);
}
}
}
/* add a symbol for end-of-memory */
add_symbol_map_entry(~0, "<end>");
map_entries--;
/* close the file */
fclose(map);
/* sort by address */
qsort(symbol_map, map_entries, sizeof(symbol_map[0]), compare_start);
/* fill in the end of each bucket */
for (i = 0; i < map_entries; i++)
symbol_map[i].end = symbol_map[i+1].start ? (symbol_map[i+1].start - 1) : 0;
}
//============================================================
// free_symbol_map
//============================================================
static void free_symbol_map(void)
{
int i;
for (i = 0; i <= map_entries; i++)
{
free(symbol_map[i].name);
symbol_map[i].name = NULL;
}
}
//============================================================
// output_symbol_list
//============================================================
static void output_symbol_list(FILE *f)
{
map_entry *entry;
int i;
/* sort by hits */
qsort(symbol_map, map_entries, sizeof(symbol_map[0]), compare_hits);
for (i = 0, entry = symbol_map; i < map_entries; i++, entry++)
if (entry->hits > 0)
fprintf(f, "%10d %08X-%08X %s\n", entry->hits, entry->start, entry->end, entry->name);
}
//============================================================
// increment_bucket
//============================================================
static void increment_bucket(UINT32 addr)
{
int i;
for (i = 0; i < map_entries; i++)
if (addr <= symbol_map[i].end)
{
symbol_map[i].hits++;
return;
}
}
//============================================================
// profiler_thread
//============================================================
static DWORD WINAPI profiler_thread_entry(LPVOID lpParameter)
{
HANDLE mainThread = (HANDLE)lpParameter;
CONTEXT context;
/* loop until done */
memset(&context, 0, sizeof(context));
while (!profiler_thread_exit)
{
/* pause the main thread and get its context */
SuspendThread(mainThread);
context.ContextFlags = CONTEXT_FULL;
GetThreadContext(mainThread, &context);
ResumeThread(mainThread);
/* add to the bucket */
increment_bucket(context.Eip);
/* sleep */
Sleep(1);
}
return 0;
}
//============================================================
// start_profiler
//============================================================
static void start_profiler(void)
{
HANDLE currentThread;
BOOL result;
// parse the map file, if present
parse_map_file();
/* do the dance to get a handle to ourself */
result = DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), ¤tThread,
THREAD_GET_CONTEXT | THREAD_SUSPEND_RESUME | THREAD_QUERY_INFORMATION, FALSE, 0);
assert_always(result, "Failed to get thread handle for main thread");
profiler_thread_exit = 0;
/* start the thread */
profiler_thread = CreateThread(NULL, 0, profiler_thread_entry, (LPVOID)currentThread, 0, &profiler_thread_id);
assert_always(profiler_thread, "Failed to create profiler thread\n");
/* max out the priority */
SetThreadPriority(profiler_thread, THREAD_PRIORITY_TIME_CRITICAL);
}
//============================================================
// stop_profiler
//============================================================
static void stop_profiler(void)
{
profiler_thread_exit = 1;
WaitForSingleObject(profiler_thread, 2000);
output_symbol_list(stderr);
free_symbol_map();
}
#else
static void start_profiler(void) {}
static void stop_profiler(void) {}
#endif