////////////////////////////////////////////////////////////////////////////////
// //
// SAMPLE API CALLS TRACE PLUGIN FOR OLLYDBG v2.01 //
// //
// This plugin allows to trace all calls to system DLLs in a single thread by //
// setting one-time memory breakpoints. //
// //
// API trace can be started only if process is paused. Plugin sets break on //
// access on user code. When user code is reached, it removes break on access //
// from user code and sets it on system code, and so on. //
// //
// Of course, it is possible that user code accesses data in the system area //
// or vice versa. In this case I step over this command and restore //
// breakpoint. Such cases are rare. //
// //
// This plugin is by no means ideal. It runs only single thread, and there //
// may be problems if program calls ZwContinue(). If DLL unloads, plugin //
// doesn't delete call records. It doesn't check whether one-time breakpoints //
// are already set by user. It doesn't allow to protocol only selected APIs, //
// and so on. //
// //
// This code is distributed "as is", without warranty of any kind, expressed //
// or implied, including, but not limited to warranty of fitness for any //
// particular purpose. In no event will Oleh Yuschuk be liable to you for any //
// special, incidental, indirect, consequential or any other damages caused //
// by the use, misuse, or the inability to use of this code, including any //
// lost profits or lost savings, even if Oleh Yuschuk has been advised of the //
// possibility of such damages. Or, translated into English: use at your own //
// risk! //
// //
// This code is free. You can modify it, include parts of it into your own //
// programs and redistribute modified code provided that you remove all //
// copyright messages or substitute them with your own copyright. //
// //
////////////////////////////////////////////////////////////////////////////////
// Microsoft compilers hate (and maybe justifiably) old-school functions like
// wcscpy() that may cause buffer overflow and all related dangers. Still, I
// don't want to see all these bloody warnings.
#define _CRT_SECURE_NO_DEPRECATE
#include <windows.h>
#include <stdio.h>
#include <string.h>
#include "plugin.h"
#define PLUGINNAME L"Trace API" // Unique plugin name
#define VERSION L"2.00.00" // Plugin version
#define NCALL 1023 // Size of call data extent, calls
// Current state of the API trace.
#define TRACE_OFF 0 // No trace
#define TRACE_TILLUSER 1 // Wait till user code is reached
#define TRACE_TILLSYS 2 // Wait till system code is called
#define CODE_GUARDED 0x00010000 // Whether code block is guarded
#define CODE_USER 0x00020000 // Whether code block is user
#define CODE_SYS 0x00040000 // Whether code block is system
// Descriptor of call location. Calls are chained in the order of ascending
// addresses.
typedef struct t_call { // Call location
ulong from; // Address of call command
struct t_call *next; // Next location or NULL
} t_call;
// API list consists of elements of type t_api.
typedef struct t_api { // Descriptor of API function
// Obligatory header, its layout _must_ coincide with t_sorthdr!
ulong addr; // Address of the API entry point
ulong size; // Always 1
ulong type; // Currently unused
// Custom data follows header.
ulong count; // Number of hits
struct t_call *call; // Pointer to chain of call locations
int ncall; // Number of elements in call chain
} t_api;
#define RET_ACTIVE 0x00010000 // Temporary breakpoint is set
typedef struct t_ret { // Descriptor of return address
ulong addr; // Return address
ulong size; // Always 1
ulong type; // One of RET_xxx
ulong aapi; // Address of the called function
ulong from; // Call address
int count; // Number of recursive breaks
} t_ret;
// List of code blocks consists of elements of type t_code.
typedef struct t_code {
ulong base; // Base address of code block
ulong size; // Size of code block
ulong type; // Additional information, CODE_xxx
} t_code;
static HINSTANCE hdllinst; // Instance of plugin DLL
static int trace; // Trace state, one of TRACE_xxx
static ulong threadid; // ID of the traced thread
static t_table api; // List of APIs that were hit
static t_sorted code; // List of code blocks
static t_sorted retlist; // List of return addresses
static t_call *pfreecall; // Pointer to list of unused calls
static int restoretempbp; // Some temp breakpoint must be restored
static int protocolapi; // Protocol API calls
static int skipgdi32; // Don't protocol GDI32 APIs
static int skipuser32; // Don't protocol USER32 APIs
static int skipkernel32; // Don't protocol KERNEL32 APIs
static int skipmsvc; // Don't protocol MSVC* APIs
static int protocolret; // Protocol returned values
static int autoupdate; // Whether window in autoupdate mode
////////////////////////////////////////////////////////////////////////////////
///////////////////////// ONE-SHOT MEMORY BREAKPOINTS //////////////////////////
// Guarding memory breakpoints are one-shot access breakpoints supported
// directly by Windows. If program accesses guarded memory, operating system
// removes guarding and raises STATUS_GUARD_PAGE exception.
//
// Routines in this section set and remove guarding breakpoints on the code
// sections of the loaded modules. Such manipulations are time-consuming. To
// accelerate process, plugin keeps list of code sections in the sorted data
// named code. if code section is guarded, it is marked as CODE_GUARDED.
//
// If module loads or unloads, ODBG2_Pluginnotify() receives PN_NEWMOD or
// PN_ENDMOD. In the first case it reinstalls guards by calling Setguard().
// In the second case, it removes module from the list of code sections.
// Guards all system (guardsys=1) or user code (guardsys=0). If flag force is
// set, guarding is forced dependless on its current state.
static void Setguard(int guardsys,int force) {
int i;
t_memory *pmem;
t_module *pmod;
t_code c,*pc;
if (force)
Deletesorteddatarange(&code,0x00000000,0xFFFFFFFF);
// Walk all memory blocks. Note that this function is called when process is
// paused, so memory list can't change.
for (i=0; i<memory.sorted.n; i++) {
// Get next memory block.
pmem=(t_memory *)Getsortedbyindex(&memory.sorted,i);
if (pmem==NULL)
continue; // In fact, severe internal error
if ((pmem->type & MEM_GAP)!=0)
continue; // Unallocated memory
// Check whether it contains executable code.
if ((pmem->type & (MEM_CODE|MEM_SFX))==0)
continue; // Not a code
// Get code descriptor, or, if descriptor is missing, create new one.
pc=(t_code *)Findsorteddata(&code,pmem->base,0);
if (pc==NULL || pc->base!=pmem->base || pc->size!=pmem->size) {
// Note that it makes no harm if guarding remains set.
memset(&c,0,sizeof(c));
c.base=pmem->base;
c.size=pmem->size;
pc=(t_code *)Addsorteddata(&code,&c);
if (pc==NULL) continue; }; // Low memory or internal error
// If memory block is already guarded, do nothing.
if (pc->type & CODE_GUARDED)
continue;
// Check whether code belongs to the system DLL.
if ((pc->type & (CODE_USER|CODE_SYS))==0) {
pmod=Findmodule(pmem->base);
if (pmod==NULL) continue; // Code without module?..
if (pmod->type & MOD_SYSTEMDLL)
pc->type|=CODE_SYS;
else
pc->type|=CODE_USER;
;
};
if ((guardsys==0 && (pc->type & CODE_USER)!=0) ||
(guardsys!=0 && (pc->type & CODE_SYS)!=0)
) {
Guardmemory(pmem->base,pmem->size,1);
pc->type|=CODE_GUARDED;
};
};
};
// Unguards code block containing specified address.
static void Unguardblock(ulong addr) {
t_code *pc;
pc=(t_code *)Findsorteddata(&code,addr,0);
if (pc!=NULL && (pc->type & CODE_GUARDED)!=0) {
Guardmemory(pc->base,pc->size,0);
pc->type&=~CODE_GUARDED;
};
};
// Unguards all code blocks.
static void Unguardall(void) {
int i;
t_memory *pmem;
Deletesorteddatarange(&code,0x00000000,0xFFFFFFFF);
Deletesorteddatarange(&retlist,0x00000000,0xFFFFFFFF);
restoretempbp=0;
// Walk all memory blocks. Note that this function is called when process is
// paused, so memory list can't change.
for (i=0; i<memory.sorted.n; i++) {
// Get next memory block.
pmem=(t_memory *)Getsortedbyindex(&memory.sorted,i);
if (pmem==NULL)
continue; // In fact, severe internal error
if ((pmem->type & MEM_GAP)!=0)
continue; // Unallocated memory
// Check whether it contains executable code.
if ((pmem->type & (MEM_CODE|MEM_SFX))==0)
continue; // Not a code
Guardmemory(pmem->base,pmem->size,0);
};
};
// Sets state of the variable trace. If state changes from TRACE_OFF to any
// other, or back to TRACE_OFF, writes message to log.
static void Settracestate(int state) {
if (trace==TRACE_OFF && state!=TRACE_OFF)
Addtolist(0,DRAW_NORMAL,L"API trace is ON");
else if (trace!=TRACE_OFF && state==TRACE_OFF)
Addtolist(0,DRAW_NORMAL,L"API trace is OFF");
trace=state;
};
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////// CALL DATA ///////////////////////////////////
// The user usually wants to know not only which API functions are called, but
// even more from which locations. Most APIs are called from one or two places,
// but in extreme cases (EnterCriticalSection(), SelectObject()) there may be
// hundreds. Therefore list of fixed size is not an option. I use linked lists
// of records of type t_call. Free records reside in a separate list pointed to
// by pfreecall. If there are no nore free call records, I allocate new extent
// containing NCALL records at once. Extents (t_cext) are also chained.
//
// After call location is added, plugin keeps it in the list till application
// terminates. Therefore there are no routines that return call locations back
// to the list of free calls, the only option is to discard all data at once.
// Descriptor of call data extent. If there are no more free call locations,
// plugin allocates next block and places it before the previous.
typedef struct t_cext { // Call data extent
t_call call[NCALL]; // Block of call addresses
struct t_cext *nextextent; // Pointer to next data extent or NULL
} t_cext;
static t_cext *extentlist; // Pointer to first call data extent
// Allocates new call data extent and adds all its elements to the chain of
// free calls. Returns 0 on success and -1 on error.
static int Allocatenewextent(void) {
int i;
t_cext *pext;
t_call *pcall;
// Allocate new data extent.
pext=(t_cext *)Memalloc(sizeof(t_cext),ZEROINIT);
if (pext==NULL)
return -1; // Low memory
// Add elements to the chain of free calls.
for (i=0,pcall=pext->call; i<NCALL; i++,pcall++) {
pcall->next=pfreecall;
pfreecall=pcall; };
// Add extent to the chain of extents.
pext->nextextent=extentlist;
extentlist=pext;
return 0;
};
// Gets free call descriptor. On success, removes it from the queue of free
// calls and returns pointer. On error, returns NULL.
static t_call *Getfreecall(void) {
t_call *pcall;
// Assure that we have free call records.
if (pfreecall==NULL) {
Allocatenewextent();
if (pfreecall==NULL) return NULL; };
// Get free call record.
pcall=pfreecall;
pfreecall=pcall->next;
pcall->next=NULL; // Just a precaution
return pcall;
};
// Frees all call data.
static void Discardcalldata(void) {
int i;
t_cext *pext;
t_api *pa;
// Walk list of extents and free all data extents.
while (extentlist!=NULL) {
pext=extentlist->nextextent;
Memfree(extentlist);
extentlist=pext; };
// There are no more free call descriptors.
pfreecall=NULL;
// For the case that API list is not yet freed, discard call data in the list.
for (i=0; i<api.sorted.n; i++) {
pa=(t_api *)Getsortedbyindex(&(api.sorted),i);
if (pa==NULL) continue;
pa->call=NULL;
};
};
////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////// API LIST ///////////////////////////////////
// Information about called API functions is kept in the table api. This
// section contains code necessary to maintain and display API list window.
// Menu function that processes command "Follow API in Disassembler".
static int Mfollowapi(t_table *pt,wchar_t *name,ulong index,int mode) {
t_api *papi;
papi=(t_api *)Getsortedbyselection(&(api.sorted),
api.sorted.selected);
if (papi==NULL)
return MENU_ABSENT; // No selection
if (mode==MENU_VERIFY)
return MENU_NORMAL;
else if (mode==MENU_EXECUTE) {
Setcpu(0,papi->addr,0,0,0,CPU_ASMHIST|CPU_ASMCENTER|CPU_ASMFOCUS);
return MENU_REDRAW; };
return MENU_ABSENT;
};
// Menu function that processes INT3 breakpoint-related commands "Toggle"
// (index=0), "Conditional" (index=1) and "Conditional log" (index=2). Note
// that found locations are sure code, we don't need to verify them.
static int Mbreakpoint(t_table *pt,wchar_t *name,ulong index,int mode) {
int answer;
t_api *papi;
t_bpoint *pbrk;
POINT coord;
papi=(t_api *)Getsortedbyselection(&(api.sorted),
api.sorted.selected);
if (papi==NULL)
return MENU_ABSENT; // No selection
if (mode==MENU_VERIFY)
return MENU_NORMAL;
else if (mode==MENU_EXECUTE) {
pbrk=(t_bpoint *)Findsorteddata(&(bpoint.sorted),papi->addr,0);
if (index==0) {
// Toggle unconditional breakpoint.
if (pbrk!=NULL && pbrk->type & BP_MANUAL)
Removeint3breakpoint(papi->addr,BP_MANUAL);
else {
answer=Setint3breakpoint(papi->addr,BP_MANUAL|BP_BREAK,
0,0,0,0,L"",L"",L"");
if (answer!=0) {
Flash(L"Unable to set breakpoint");
return MENU_NOREDRAW;
};
}; }
else {
// Set conditional breakpoint (simple and full forms).
if (Gettableselectionxy(&api,2,&coord)<0)
coord.x=coord.y=-1; // Unknown coordinates, use defaults
if (index==1) answer=Condbreakpoint(pt->hw,&(papi->addr),1,NULL,
coord.x,coord.y,api.font);
else answer=Condlogbreakpoint(pt->hw,&(papi->addr),1,0,NULL,
coord.x,coord.y,api.font);
if (answer<0) Flash(L"Unable to set breakpoint");
if (answer<=0) return MENU_NOREDRAW; };
return MENU_REDRAW; };
return MENU_ABSENT;
};
// Callback function that fills Browse code location dialog with the list of
// call locations.
int Browsecallcallback(int index,void *data,ulong *addr,wchar_t *s) {
t_call **pcall;
if (index<0 || data==NULL || addr==NULL || s==NULL)
return 0; // Error in input parameters
pcall=(t_call **)data;
if (*pcall==NULL)
return 0; // No more call locations
*addr=(*pcall)->from;
if (*addr==0)
StrcopyW(s,TEXTLEN,L"<unknown location>");
else
Decoderelativeoffset(*addr,DM_MODNAME,s,TEXTLEN);
*pcall=(*pcall)->next;
return 1; // Code location is available
};
// Menu function that processes command "Follow call in Disassembler" (index=0)
// and "Browse calls in Disassembler" (index=1).
static int Mfollowcall(t_table *pt,wchar_t *name,ulong index,int mode) {
t_api *papi;
t_call *pcall;
papi=(t_api *)Getsortedbyselection(&(api.sorted),
api.sorted.selected);
if (papi==NULL)
return MENU_ABSENT; // No selection
if (mode==MENU_VERIFY) {
if (papi->ncall==1 && index==0)
return MENU_NORMAL;
if (papi->ncall>1 && index==1)
return MENU_NORMAL;
return MENU_ABSENT; }
else if (mode==MENU_EXECUTE) {
pcall=papi->call;
if (papi->ncall==1 && pcall!=NULL && pcall->from!=0)
Setcpu(0,pcall->from,0,0,0,CPU_ASMHIST|CPU_ASMCENTER|CPU_ASMFOCUS);
else if (papi->ncall>1)
Browsecodelocations(pt->hparent,L"Select call location",
Browsecallcallback,(void *)&pcall);
return MENU_REDRAW; };
return MENU_ABSENT;
};
// Menu function that processes breakpoint-related commands "Breakpoint on all
// calls" (index=0), "Conditional breakpoint on all calls" (index=1),
// "Conditional log breakpoint on all calls" (index=2) and "Remove breakpoint
// from all calls" (index=3).
static int Mbreakpointall(t_table *pt,wchar_t *name,ulong index,int mode) {
int nlist,hasbp,nobp,ncond,success,answer;
ulong *list;
t_api *papi;
t_call *pcall;
t_bpoint *pbrk;
POINT coord;
papi=(t_api *)Getsortedbyselection(&(api.sorted),
api.sorted.selected);
if (papi==NULL)
return MENU_ABSENT; // No selection
if (mode==MENU_VERIFY) {
// Calculate how many calls already have breakpoints.
hasbp=nobp=ncond=0;
pcall=papi->call;
while (pcall!=NULL) {
pbrk=(t_bpoint *)Findsorteddata(&(bpoint.sorted),pcall->from,0);
if (pbrk==NULL || (pbrk->type & BP_MANUAL)==0)
nobp++; // No breakpoint
else {
hasbp++; // Breakpoint set
if (pbrk->type & BP_COND) ncond++; };
pcall=pcall->next;
};
// Check whether menu item applies.
if (index==0 && nobp==0 && ncond==0)
return MENU_ABSENT;
if ((index==1 || index==2) && hasbp+nobp==0)
return MENU_ABSENT;
if (index==3 && hasbp==0)
return MENU_ABSENT;
return MENU_NORMAL; }
else if (mode==MENU_EXECUTE) {
if (index==1 || index==2) {
list=(ulong *)Memalloc(papi->ncall*sizeof(ulong),SILENT|ZEROINIT);
if (list==NULL) {
Flash(L"Low memory"); return MENU_NOREDRAW; };
nlist=0;
pcall=papi->call;
while (pcall!=NULL && nlist<papi->ncall) {
list[nlist++]=pcall->from;
pcall=pcall->next; };
if (Gettableselectionxy(pt,2,&coord)<0)
coord.x=coord.y=-1; // Unknown coordinates, use defaults
if (index==1) answer=Condbreakpoint(pt->hw,list,nlist,NULL,
coord.x,coord.y,pt->font);
else answer=Condlogbreakpoint(pt->hw,list,nlist,0,NULL,
coord.x,coord.y,pt->font);
if (answer<0)
Flash(L"Unable to set all breakpoints");
Memfree(list); }
else {
pcall=papi->call;
success=1;
while (pcall!=NULL) {
if (index==0) answer=Setint3breakpoint(pcall->from,BP_MANUAL|BP_BREAK,
0,0,0,0,L"",L"",L"");
else
answer=Removeint3breakpoint(pcall->from,BP_MANUAL);
if (answer!=0) success=0;
pcall=pcall->next; };
if (success==0) {
if (index==0) Flash(L"Unable to set all breakpoints");
else Flash(L"Unable to remove all breakpoints");
};
};
return MENU_REDRAW; };
return MENU_ABSENT;
};
static t_menu apimenu[] = { // Menu of the API list window
{ L"Follow API in Disassembler",
L"Follow address of selected API function in CPU Disassembler",
K_NONE, Mfollowapi, NULL, 0 },
{ L"|Toggle breakpoint on API",
L"Toggle unconditional breakpoint on selected API function",
K_BREAK, Mbreakpoint, NULL, 0 },
{ L"Conditional breakpoint on API...",
L"Set or edit conditional breakpoint on selected API function",
K_CONDBREAK, Mbreakpoint, NULL, 1 },
{ L"Conditional log on API...",
L"Set or edit conditional logging breakpoint on selected API function",
K_LOGBREAK, Mbreakpoint, NULL, 2 },
{ L"|Follow call in Disassembler",
L"Go to location where API selected function was called",
K_FOLLOWDASM, Mfollowcall, NULL, 0 },
{ L"Browse calls in Disassembler",
L"Show list of locations from which selected API function was called",
K_FOLLOWDASM, Mfollowcall, NULL, 1 },
{ L"|Breakpoint on all calls",
L"Set unconditional breakpoint on all call locations",
K_BREAKALL, Mbreakpointall, 0, 0 },
{ L"Conditional breakpoint on all calls",
L"Set or edit conditional breakpoint on all call locations",
K_CONDBPALL, Mbreakpointall, 0, 1 },
{ L"Conditional log on all calls",
L"Set or edit conditional logging breakpoint on all call locations",
K_LOGBPALL, Mbreakpointall, 0, 2 },
{ L"Remove breakpoint from all calls",
L"Remove breakpoint from all call locations",
K_DELBPALL, Mbreakpointall, 0, 3 },
{ L"|>STANDARD",
L"", // Forwarder to standard menus
K_NONE, NULL, NULL, 0
}
};
// Function that compares two APIs according to the specified criterium.
// Returns -1 if first item is "lower" (should precede second on the screen),
// +1 if first item is "higher" and 0 if they are equal. Criterium n is the
// index of the column in the table.
int Apisortfunc(const t_sorthdr *sh1,const t_sorthdr *sh2,const int n) {
int i=0;
wchar_t s1[TEXTLEN],s2[TEXTLEN];
t_api *a1,*a2;
a1=(t_api *)sh1;
a2=(t_api *)sh2;
if (n==1) { // Sort by name, may be time-consuming
Decodeaddress(a1->addr,a1->addr,
DM_VALID|DM_INMOD|DM_NOMODNAME,s1,TEXTLEN,NULL);
Decodeaddress(a2->addr,a2->addr,
DM_VALID|DM_INMOD|DM_NOMODNAME,s2,TEXTLEN,NULL);
i=_wcsnicmp(s1,s2,TEXTLEN); }
else if (n==2) { // Sort by hit count
if (a1->count>a2->count) i=-1;
else if (a1->count<a2->count) i=1; };
if (i==0) { // Additionally sort by API address
if (a1->addr<a2->addr) i=-1;
else if (a1->addr>a2->addr) i=1; };
return i;
};
// Drawing function of API list window.
int Apidraw(wchar_t *s,uchar *mask,int *select,
t_table *pt,t_drawheader *ph,int column,void *cache) {
int m,n,color;
ulong mode;
wchar_t text[TEXTLEN];
t_bpoint *pbrk;
t_api *papi;
t_call *pcall;
n=0;
// For simple tables, t_drawheader is the pointer to the data element. It
// can't be NULL, except in DF_CACHESIZE, DF_FILLCACHE and DF_FREECACHE.
papi=(t_api *)ph;
switch (column) {
case DF_CACHESIZE: // Request for draw cache size
return 0;
case DF_FILLCACHE: // Request to fill draw cache
break;
case DF_FREECACHE: // Request to free cached resources
break;
case DF_NEWROW: // Request to start new row in window
break;
case 0: // API address
mode=ADDR_HEXONLY|ADDR_CHECKEIP;
pbrk=(t_bpoint *)Findsorteddata(&(bpoint.sorted),papi->addr,0);
if (pbrk!=NULL && (pbrk->type & BP_MANUAL)!=0) {
if (pbrk->type & BP_DISABLED) mode|=ADDR_DISBRK;
else if (pbrk->type & BP_COND) mode|=ADDR_CONDBRK;
else mode|=ADDR_BREAK; };
n=Labeladdress(s,papi->addr,0,REG_UNDEF,0,mask,select,mode);
break;
case 1: // API name
n=Decodeaddress(papi->addr,papi->addr,
DM_VALID|DM_INMOD|DM_MODNAME,s,TEXTLEN,NULL);
break;
case 2: // Hit count
n=swprintf(s,L"%i.",papi->count);
break;
case 3: // Call locations
pcall=papi->call;
while (pcall!=NULL && n<TEXTLEN-1) {
if (n>0) {
m=StrcopyW(s+n,TEXTLEN-n,L", ");
if (m!=0) memset(mask+n,DRAW_NORMAL,m);
n+=m; };
Hexprint8W(text,pcall->from);
m=StrcopyW(s+n,TEXTLEN-n,text);
pbrk=(t_bpoint *)Findsorteddata(&(bpoint.sorted),pcall->from,0);
if (pbrk==NULL || (pbrk->type & BP_MANUAL)==0)
color=DRAW_NORMAL; // No user INT3 breakpoint
else if (pbrk->type & BP_DISABLED)
color=DRAW_BDIS; // Disabled breakpoint
else if (pbrk->type & BP_COND)
color=DRAW_COND; // Conditional breakpoint
else
color=DRAW_BREAK; // Unconditional breakpoint
if (m!=0) memset(mask+n,color,m);
n+=m;
pcall=pcall->next; };
if (n>=TEXTLEN-1) {
s[n-1]=L'.'; mask[n-1]=DRAW_NORMAL;
s[n-2]=L'.'; mask[n-2]=DRAW_NORMAL;
s[n-3]=L'.'; mask[n-3]=DRAW_NORMAL; };
*select|=DRAW_MASK; // Uses mask
break;
default: break;
};
return n;
};
// Custom table function of API list window. This function is called on
// WM_DESTROY, WM_CLOSE (by returning -1, you can prevent window from closing),
// WM_SIZE (custom tables only), WM_CHAR (only if TABLE_WANTCHAR is set) and
// different custom messages WM_USER_xxx (depending on table type). See
// documentation for details.
long Apifunc(t_table *pt,HWND hw,UINT msg,WPARAM wp,LPARAM lp) {
t_api *papi;
t_call *pcall;
switch (msg) {
case WM_USER_CREATE:
// Start autoupdating of the API list window. This means that OllyDbg
// will periodically send WM_USER_UPD messages. Whether the window will
// be repainted or not depends on the reaction on this message.
Setautoupdate(&api,1);
break;
case WM_USER_UPD: // Autoupdate contents of the window
if (trace!=TRACE_OFF && run.status!=STAT_IDLE &&
run.status!=STAT_PAUSED && run.status!=STAT_FINISHED)
InvalidateRect(pt->hw,NULL,FALSE);
break;
case WM_USER_DBLCLK: // Doubleclick
// Get selection.
papi=(t_api *)Getsortedbyselection(
&(pt->sorted),pt->sorted.selected);
if (papi!=NULL) {
if (HIWORD(wp)==3)
// Doubleclick in the column 3 (list of call locations). If there is
// only one call location, follow it in CPU Disassembler pane. If
// there are several locations, open browsing dialog. I don't want
// to repeat the functionality of Mfollowcall(), therefore I reuse
// menu function.
Callmenufunction(&api,apimenu,Mfollowcall,papi->ncall==1?0:1);
else
// Columns 0 to 2. Follow address of the API function in the CPU
// Disassembler pane.
Setcpu(0,papi->addr,0,0,0,CPU_ASMHIST|CPU_ASMCENTER|CPU_ASMFOCUS);
;
};
return 1;
default: break;
};
return 0;
};
////////////////////////////////////////////////////////////////////////////////
//////////////////////////////// PLUGIN OPTIONS ////////////////////////////////
static t_control traceapioptions[] = {
{ CA_COMMENT, -1, 0, 0, 0, 0, NULL,
PLUGINNAME,
NULL },
{ CA_TITLE, OPT_TITLE, 80, 4, 160, 15, NULL,
PLUGINNAME,
NULL },
{ CA_CHECK, OPT_1, 90, 26, 120, 10, &protocolapi,
L"Protocol API calls",
L"Protocol calls and call arguments to the log" },
{ CA_CHECK, OPT_2, 105, 38, 160, 10, &skipgdi32,
L"Don't protocol GDI32",
L"Ignore all APIs that reside in GDI32" },
{ CA_CHECK, OPT_3, 105, 50, 160, 10, &skipuser32,
L"Don't protocol USER32",
L"Ignore all APIs that reside in USER32" },
{ CA_CHECK, OPT_4, 105, 62, 160, 10, &skipkernel32,
L"Don't protocol KERNEL32",
L"Ignore all APIs that reside in KERNEL32" },
{ CA_CHECK, OPT_5, 105, 74, 160, 10, &skipmsvc,
L"Don't protocol MSVC*",
L"Ignore all APIs that reside in Visual C libraries" },
{ CA_CHECK, OPT_6, 105, 86, 160, 10, &protocolret,
L"Protocol returned values",
L"Protocol values that called API function returned" },
{ CA_END, -1, 0, 0, 0, 0, NULL,
NULL,
NULL
}
};
// Optional entry, implements plugin's options page in the Plugin options
// dialog. First time this function is called with msg=WM_INITDIALOG, wp=0 and
// lp=0. Note that this is the artificial message. If plugin wants to insert
// its own page (or even several pages), it must return pointer to page
// description (array of structures t_control). Each new page must begin with
// CA_COMMENT. When user makes any actions and dialog receives WM_COMMAND, it
// passes this message here. Returned value is ignored. Finally, after user
// pressed OK or Cancel, this entry is called for the last time with
// msg=WM_CLOSE and wp=1 on OK and 0 on cancel. This is the best time for
// plugin to adopt new settings. Do not forget to undo custom controls on
// Cancel!
extc t_control * __cdecl ODBG2_Pluginoptions(UINT msg,WPARAM wp,LPARAM lp) {
if (msg==WM_CLOSE && wp!=0) {
// User pressed OK in the Plugin options dialog. Options are updated, save
// them to the .ini file.
Writetoini(NULL,PLUGINNAME,L"Protocol API calls",L"%i",protocolapi);
Writetoini(NULL,PLUGINNAME,L"Skip GDI32",L"%i",skipgdi32);
Writetoini(NULL,PLUGINNAME,L"Skip USER32",L"%i",skipuser32);
Writetoini(NULL,PLUGINNAME,L"Skip KERNEL32",L"%i",skipkernel32);
Writetoini(NULL,PLUGINNAME,L"Skip MSVC",L"%i",skipmsvc);
Writetoini(NULL,PLUGINNAME,L"Protocol returned values",L"%i",protocolret);
};
// It makes no harm to return page descriptor on all messages.
return traceapioptions;
};
////////////////////////////////////////////////////////////////////////////////
/////////////////////////// EVENTS AND NOTIFICATIONS ///////////////////////////
// When trace starts, plugin guards user code and assumes that debuggee is in
// the system code. When guarded page is hit, OllyDbg passes event to
// ODBG2_Pluginexception(). Let's assume that thread is in the user code now.
// Plugin should guard system code and unguard user. But such actions are
// time-consuming, so plugin assumes that code is usually compact and removes
// protection only from the code block that was hit. Now plugin guards system
// code and returns PE_CONTINUE (continue execution, keep run status).
//
// When debuggee calls API function, another exception occurs. Plugin
// determines call and return addresses and adds them to the list. If plugin
// is requested to protocol returned values, it also sets temporary breakpoint
// with action flag BA_PLUGIN (report break to plugins) on the address of
// return. Some other plugins may also set breakpoint here. To be sure, plugin
// adds this address to the sorted data retlist.
//
// Why is retlist necessary? Were it not sufficient to save return address to
// some variable? No. Consider following scenario: Debugged application calls
// DispatchMessage(). This API function passes message to the user-defined
// window function. Function calls another API, say, EnumChildWindows(). This
// calls supplied callback which calls third API, GetWindowText(). Now we have
// three nested APIs, and in theory there is no depth limit. Even recursive
// calls are possible!
//
// Temporary breakpoint (BP_TEMP) is automatically removed when hit. If there
// is a recursive call, return values from the previous calls will not be
// protocolled. To avoid this, retlist items count calls and mark removed
// breakpoints. They are restored when execution returns into the system code.
//
// But what if we are waiting for system code and user code accesses data in
// the code block of system code, or vice versa? Breaks on access do not
// distinguish between execution, reading or writing. We can't restore break
// instantly, first we must execute the command. What should we do? In this
// case ODBG2_Pluginexception() returns PE_STEP. OllyDbg sets temporary
// breakpoint (INT3 or hardware) with action BA_PLUGIN on the next command and
// plugin gets chance to restore protection when breakpoint is hit and
// ODBG2_Plugintempbreakpoint() is called.
//
// When there are several concurrent threads, it may happen that one thread is
// in the user code and another in system. The resulting chaos will make
// reliable trace impossible. Therefore only one thread is allowed. When user
// runs application (STAT_RUNNING), ODBG2_Pluginnotify() intercepts this action
// and substitutes with STAT_RUNTHR.
// Optional entry, called when Windows report exception (debugging event of
// type EXCEPTION_DEBUG_EVENT in the debugged application).
extc int __cdecl ODBG2_Pluginexception(t_run *prun,const t_disasm *da,
t_thread *pthr,t_reg *preg,wchar_t *message) {
int i,n,nstack,narg;
ulong addr,amem,aret,acall,size,from,stack[NARG+1];
wchar_t name[TEXTLEN],s[TEXTLEN];
t_module *pmod;
t_argdec adec[NARG];
t_memory *pmem;
t_ret ret,*pret;
t_api a,*pa;
t_call *pcall,**ppcall;
// If trace is inactive, or there is an error in parameters, do nothing.
if (trace==TRACE_OFF || prun==NULL || da==NULL || preg==NULL)
return PE_IGNORED;
// Traceapi processes only guarded page exceptions.
if (prun->de.u.Exception.ExceptionRecord.ExceptionCode!=EXCEPTION_GUARD_PAGE)
return PE_IGNORED;
// Address of command that caused exception.
addr=(ulong)prun->de.u.Exception.ExceptionRecord.ExceptionAddress;
// Address of guarded memory block.
amem=(ulong)prun->de.u.Exception.ExceptionRecord.ExceptionInformation[1];
// If guarded memory block is in the memory area opposite to expected, remove
// guard. We didn't do it before to spare time.
pmod=Findmodule(amem);
if (pmod==NULL)
return PE_IGNORED; // Better ideas?
if (((pmod->type & MOD_SYSTEMDLL)!=0 && trace==TRACE_TILLUSER) ||
((pmod->type & MOD_SYSTEMDLL)==0 && trace==TRACE_TILLSYS)
) {
Unguardblock(amem); return PE_CONTINUE; };
pmod=Findmodule(addr);
// If address is in the wrong area, most probably command accesses protected
// data and must be stepped. Note that we must restore guarding after
// temporary breakpoint is hit.
if (trace==TRACE_TILLUSER && pmod!=NULL && (pmod->type & MOD_SYSTEMDLL)!=0)
return PE_STEP; // User code protected but break in sys
if (trace==TRACE_TILLSYS && (pmod==NULL || (pmod->type & MOD_SYSTEMDLL)==0))
return PE_STEP; // Sys code protected but break in user
if (trace==TRACE_TILLUSER) {
// We have reached user code. Unguard user code block and guard system code.
Unguardblock(addr);
Setguard(1,0);
Settracestate(TRACE_TILLSYS); }
else {
// We have reached system code. If necessary, restore temporary breakpoints
// on return address.
if (restoretempbp) { // Rare event
for (i=0; i<retlist.n; i++) {
pret=(t_ret *)Getsortedbyindex(&retlist,i);
if (pret==NULL || (pret->type & RET_ACTIVE)!=0) continue;
Setint3breakpoint(pret->addr,BP_TEMP,-1,-1,-1,BA_PLUGIN,NULL,NULL,NULL);
pret->type|=RET_ACTIVE;
};
restoretempbp=0;
};
// Unguard this block and guard user code.
Unguardblock(addr);
Setguard(0,0);
Settracestate(TRACE_TILLUSER);
// Read stack and determine the caller.
size=Readmemory(stack,preg->r[REG_ESP],sizeof(stack),MM_SILENT|MM_PARTIAL);
nstack=size/sizeof(ulong);
if (nstack==0)
aret=from=0;
else {
aret=stack[0];
from=Isretaddr(aret,NULL);
if (from==0) aret=0; };
// Add entry to API list.
pa=(t_api *)Findsorteddata(&(api.sorted),addr,0);
if (pa==NULL) { // First time the API is called
memset(&a,0,sizeof(a));
a.addr=addr;
a.size=1;
pa=(t_api *)Addsorteddata(&(api.sorted),&a); };
if (pa!=NULL) { // Opposite would mean severe error
pa->count++;
// Even when from is 0, I add it to the call list, as an indication that
// call origin is unknown.
ppcall=&(pa->call);
while (1) {
if (*ppcall!=NULL && (*ppcall)->from<from) {
ppcall=&((*ppcall)->next);
continue; };
if (*ppcall!=NULL && (*ppcall)->from==from)
break; // Call address is already in the chain
pcall=Getfreecall();
if (pcall==NULL)
break; // Low memory
pcall->from=from;
pcall->next=*ppcall;
*ppcall=pcall;
pa->ncall++;
break;
};
};
if (protocolapi) {
// If requested not to protocol some functions, check whether this is
// the skipped function.
if ((skipgdi32!=0 && _wcsicmp(pmod->modname,L"GDI32")==0) ||
(skipuser32!=0 && _wcsicmp(pmod->modname,L"USER32")==0) ||
(skipkernel32!=0 && _wcsicmp(pmod->modname,L"KERNEL32")==0) ||
(skipmsvc!=0 && _wcsnicmp(pmod->modname,L"MSVC",4)==0)) ;
else {
// If requested, set temporary breakpoint on return address to protocol
// the value returned by the API. I add this address to retlist so that
// I know that this is my breakpoint. Note possible source of errors:
// I silently assume that if call is recursive, it always calls the
// same API function.
if (protocolret!=0 && aret!=0 && from!=0) {
Setint3breakpoint(aret,BP_TEMP,-1,-1,-1,BA_PLUGIN,NULL,NULL,NULL);
pret=(t_ret *)Findsorteddata(&retlist,aret,0);
if (pret==NULL) {
ret.addr=aret;
ret.size=1;
ret.type=0;
ret.aapi=addr;
ret.from=from;
ret.count=0;
pret=(t_ret *)Addsorteddata(&retlist,&ret); };
if (pret!=NULL) {
pret->type|=RET_ACTIVE; // BP_TEMP breakpoint set
pret->count++; // Recursive if count>1
};
};
// Decode arguments. Note that first doubleword in the stack is return
// address.
if (nstack<=1)
narg=0;
else {
for (i=0; i<NARG; i++) {
if (i<nstack-1) {
adec[i].mode=ADEC_VALID|ADEC_CHGNAME;
adec[i].value=stack[i+1]; }
else {
adec[i].mode=0;
adec[i].value=0;
};
adec[i].pushaddr=0;
};
narg=Decodeknownbyaddr(addr,NULL,adec,NULL,name,0,1);
};
// Write name of function and, if available, call address to log.
n=StrcopyW(s,TEXTLEN,L"Call to ");
n+=Decodeaddress(addr,0,DM_WIDEFORM|DM_MODNAME,s+n,TEXTLEN-n,NULL);
if (from!=0) {
n+=StrcopyW(s+n,TEXTLEN-n,L" from ");
Decodeaddress(from,0,DM_WIDEFORM|DM_MODNAME,s+n,TEXTLEN-n,NULL); };
Addtolist(from,DRAW_HILITE,L"%s",s);
// Write arguments to log.
for (i=0; i<narg; i++) {
if (adec[i].name[0]==L'=') // Substituted argument without name
Addtolist(0,DRAW_NORMAL,L" %08X %s",adec[i].value,adec[i].text);
else {
Addtolist(0,DRAW_NORMAL,L" %08X %s = %s",
adec[i].value,adec[i].name,adec[i].text);
};
};
};
};
};
// Continue execution.
return PE_CONTINUE;
};
// Optional entry, called when temporary breakpoint BP_TEMP with action flag
// BA_PLUGIN is hit. Note that several plugins may set temporary breakpoint
// on the same command. Each BA_PLUGIN breakpoint is reported to all plugins.
extc void __cdecl ODBG2_Plugintempbreakpoint(ulong addr,const t_disasm * da,
t_thread *pthr,t_reg *preg) {
int n;
wchar_t s[TEXTLEN],type[TEXTLEN];
t_ret *pret;
pret=(t_ret *)Findsorteddata(&retlist,addr,0);
if (pret!=NULL) {
// Write name of called function to log.
Decodeaddress(pret->aapi,0,DM_WIDEFORM|DM_MODNAME,s,TEXTLEN,NULL);
Addtolist(addr,DRAW_HILITE,L"Return from %s",s);
if (preg!=NULL) {
// For known functions, Analyser saves type of return in the prefixed
// name of type NM_RETTYPE. Refer to the documentation to learn what
// prefix is.
if (FindnameW(addr,NM_RETTYPE,type,TEXTLEN)!=0)
Decodeargument(NULL,type,preg->r+REG_EAX,sizeof(ulong),
s,TEXTLEN,NULL);
else
s[0]=L'\0';
Addtolist(0,DRAW_NORMAL,L" EAX = %08X %s",preg->r[REG_EAX],s);
};
pret->count--;
if (pret->count==0)
Deletesorteddata(&retlist,addr,0);
else {
pret->type&=~RET_ACTIVE; // Temp breakpoint is autoremoved
restoretempbp=1;
}; }
else if (trace==TRACE_TILLSYS)
Setguard(1,1);
else if (trace==TRACE_TILLUSER)
Setguard(0,1);
;
};
// Optional entry, notifies plugin on relatively infrequent events.
extc void __cdecl ODBG2_Pluginnotify(int action,void *data,ulong p1,ulong p2) {
switch (action) {
case PN_NEWMOD: // New module is added to the table
if (trace==TRACE_TILLSYS)
Setguard(1,1);
else if (trace==TRACE_TILLUSER)
Setguard(0,1);
break;
case PN_ENDMOD: // Module is removed from the memory
Deletesorteddatarange(&code,((t_module *)data)->base,
((t_module *)data)->base+((t_module *)data)->size);
break;
case PN_RUN: // User continues code execution
if (trace!=TRACE_OFF && *((t_status *)data)==STAT_RUNNING)
*((t_status *)data)=STAT_RUNTHR;
break;
default: break; // No action necessary
};
};
////////////////////////////////////////////////////////////////////////////////
////////////////// PLUGIN MENUS EMBEDDED INTO OLLYDBG WINDOWS //////////////////
// Menu function of main menu, opens or brings to top API list.
static int Mopenapilist(t_table *pt,wchar_t *name,ulong index,int mode) {
if (mode==MENU_VERIFY)
return MENU_NORMAL; // Always available
else if (mode==MENU_EXECUTE) {
if (api.hw==NULL)
// Create table window. Third parameter (ncolumn) is the number of
// visible columns in the newly created window (ignored if appearance is
// restored from the initialization file). If it's lower than the total
// number of columns, remaining columns are initially invisible. Fourth
// parameter is the name of icon - as OllyDbg resource.
Createtablewindow(&api,0,api.bar.nbar,NULL,L"ICO_PLUGIN",PLUGINNAME);
else
Activatetablewindow(&api);
return MENU_NOREDRAW;
};
return MENU_ABSENT;
};
// Menu function of main menu, starts or stops API trace.
static int Mstarttrace(t_table *pt,wchar_t *name,ulong index,int mode) {
if (mode==MENU_VERIFY) {
if (run.status==STAT_IDLE || run.status==STAT_FINISHED)
return MENU_ABSENT; // Application is no more
else if (index==0 && trace!=TRACE_OFF)
return MENU_NORMAL; // OK to stop trace
else if (index==1 && trace==TRACE_OFF && run.status!=STAT_RUNNING)
return MENU_NORMAL; // OK to start trace
else
return MENU_ABSENT; // Action is not allowed
; }
else if (mode==MENU_EXECUTE) {
if (index==0) { // Stop trace
Unguardall();
Settracestate(TRACE_OFF); }
else { // Start trace
Setguard(0,1);
Settracestate(TRACE_TILLUSER); };
return MENU_NOREDRAW;
};
return MENU_ABSENT;
};
// Menu function of main menu, opens plugin-related options.
static int Mopenoptions(t_table *pt,wchar_t *name,ulong index,int mode) {
if (mode==MENU_VERIFY)
return MENU_NORMAL; // Always available
else if (mode==MENU_EXECUTE) {
Pluginshowoptions(traceapioptions);
return MENU_REDRAW;
};
return MENU_ABSENT;
};
// Menu function of main menu, displays About dialog.
static int Mabout(t_table *pt,wchar_t *name,ulong index,int mode) {
int n;
wchar_t s[TEXTLEN];
if (mode==MENU_VERIFY)
return MENU_NORMAL; // Always available
else if (mode==MENU_EXECUTE) {
// Debuggee should continue execution while message box is displayed.
Resumeallthreads();
// In this case, Swprintf() would be as good as a sequence of StrcopyW(),
// but secure copy makes buffer overflow impossible.
n=StrcopyW(s,TEXTLEN,L"Trace API plugin v");
n+=StrcopyW(s+n,TEXTLEN-n,VERSION);
// COPYRIGHT POLICY: This bookmark plugin is an open-source freeware. It's
// just a sample. The copyright below is also just a sample and applies to
// the unmodified sample code only. Replace or remove copyright message if
// you make ANY changes to this code!
StrcopyW(s+n,TEXTLEN-n,L"\nCopyright (C) 2012 Oleh Yuschuk");
MessageBox(hwollymain,s,
L"Bookmark plugin",MB_OK|MB_ICONINFORMATION);
// Suspendallthreads() and Resumeallthreads() must be paired, even if they
// are called in inverse order!
Suspendallthreads();
return MENU_NOREDRAW;
};
return MENU_ABSENT;
};
// Plugin menu that will appear in the main OllyDbg menu. Note that this menu
// must be static and must be kept for the whole duration of the debugging
// session.
static t_menu mainmenu[] = {
{ L"Open API list",
L"Open API list window",
K_NONE, Mopenapilist, NULL, 0 },
{ L"Start API trace",
L"Start API trace",
K_NONE, Mstarttrace, NULL, 1 },
{ L"Stop API trace",
L"Stop API trace",
K_NONE, Mstarttrace, NULL, 0 },
{ L"|Options",
L"Open plugin-related options",
K_NONE, Mopenoptions, NULL, 0 },
{ L"|About",
L"About Trace API plugin",
K_NONE, Mabout, NULL, 0 },
{ NULL, NULL, K_NONE, NULL, NULL, 0 }
};
// Optional entry, adds items either to main OllyDbg menu (type=PWM_MAIN) or to
// popup menu in one of the standard OllyDbg windows, like PWM_DISASM. When
// type matches, plugin should return address of menu. When there is no menu of
// given type, it must return NULL. If menu includes single item, it will
// appear directly in menu, otherwise OllyDbg will create a submenu with the
// name of plugin. Therefore, if there is only one item, make its name as
// descriptive as possible.
extc t_menu * __cdecl ODBG2_Pluginmenu(wchar_t *type) {
if (wcscmp(type,PWM_MAIN)==0) // Main menu
return mainmenu;
return NULL; // No menu
};
////////////////////////////////////////////////////////////////////////////////
//////////////////////////// PLUGIN INITIALIZATION /////////////////////////////
// Entry point of the plugin DLL. Many system calls require DLL instance
// which is passed to DllEntryPoint() as one of parameters. Remember it. Do
// not make any initializations here, preferrable way is to place them into
// ODBG_Plugininit() and cleanup in ODBG_Plugindestroy().
BOOL WINAPI DllEntryPoint(HINSTANCE hi,DWORD reason,LPVOID reserved) {
if (reason==DLL_PROCESS_ATTACH)
hdllinst=hi; // Mark plugin instance
return 1; // Report success
};
// ODBG2_Pluginquery() is a "must" for valid OllyDbg plugin. It must check
// whether given OllyDbg version is correctly supported, and return 0 if not.
// Then it should fill plugin name and plugin version (as UNICODE strings) and
// return version of expected plugin interface. If OllyDbg decides that this
// plugin is not compatible, it will be unloaded. Plugin name identifies it
// in the Plugins menu. This name is max. 31 alphanumerical UNICODE characters
// or spaces + terminating L'\0' long. To keep life easy for users, name must
// be descriptive and correlate with the name of DLL. Parameter features is
// reserved for the future. I plan that features[0] will contain the number
// of additional entries in features[]. Attention, this function should not
// call any API functions: they may be incompatible with the version of plugin!
// (Windows' APIs are OK).
extc int __cdecl ODBG2_Pluginquery(int ollydbgversion,ulong *features,
wchar_t pluginname[SHORTNAME],wchar_t pluginversion[SHORTNAME]) {
// Check whether OllyDbg has compatible version. This plugin uses only the
// most basic functions, so this check is done pro forma, just to remind of
// this option.
if (ollydbgversion<201)
return 0;
// Report name and version to OllyDbg.
wcscpy(pluginname,PLUGINNAME); // Name of plugin
wcscpy(pluginversion,VERSION); // Version of plugin
return PLUGIN_VERSION; // Expected API version
};
// Optional entry, called immediately after ODBG2_Pluginquery(). Plugin should
// make one-time initializations and allocate resources. On error, it must
// clean up and return -1. On success, it must return 0.
extc int __cdecl ODBG2_Plugininit(void) {
int restore;
OSVERSIONINFO osversion;
// Check Windows version. If somebody dares to start this plugin on Win95,
// tell user that this OS does not support one-time memory breakpoints.
osversion.dwOSVersionInfoSize=sizeof(OSVERSIONINFO);
GetVersionEx(&osversion);
if (osversion.dwPlatformId==VER_PLATFORM_WIN32_WINDOWS) {
Addtolist(0,DRAW_HILITE,L"%s will not work on Win95-based systems",
PLUGINNAME);
return -1; };
// Initialize API list.
if (Createsorteddata(&(api.sorted),sizeof(t_api),256,
(SORTFUNC *)Apisortfunc,NULL,0)!=0)
return -1;
// Initialize list of code blocks.
if (Createsorteddata(&code,sizeof(t_code),128,NULL,NULL,0)!=0) {
Destroysorteddata(&(api.sorted));
return -1; };
// Initialize list of return addresses. I don't expect much data here.
if (Createsorteddata(&retlist,sizeof(t_ret),32,NULL,NULL,0)!=0) {
Destroysorteddata(&(api.sorted));
Destroysorteddata(&code);
return -1; };
restoretempbp=0;
// Restore options.
protocolapi=0;
Getfromini(NULL,PLUGINNAME,L"Protocol API calls",L"%i",&protocolapi);
skipgdi32=1;
Getfromini(NULL,PLUGINNAME,L"Skip GDI32",L"%i",&skipgdi32);
skipuser32=0;
Getfromini(NULL,PLUGINNAME,L"Skip USER32",L"%i",&skipuser32);
skipkernel32=0;
Getfromini(NULL,PLUGINNAME,L"Skip KERNEL32",L"%i",&skipkernel32);
skipmsvc=1;
Getfromini(NULL,PLUGINNAME,L"Skip MSVC",L"%i",&skipmsvc);
protocolret=1;
Getfromini(NULL,PLUGINNAME,L"Protocol returned values",L"%i",&protocolret);
// Initialize API table. OllyDbg uses table name to save the appearance to
// the main initialization file. Keep this name unique, or else.
StrcopyW(api.name,SHORTNAME,PLUGINNAME);
api.mode=TABLE_SAVEALL|TABLE_AUTOUPD;
api.bar.visible=1;
api.bar.name[0]=L"Address";
api.bar.expl[0]=L"Address of API function";
api.bar.mode[0]=BAR_SORT;
api.bar.defdx[0]=9;
api.bar.name[1]=L"Name";
api.bar.expl[1]=L"Name of API function";
api.bar.mode[1]=BAR_SORT;
api.bar.defdx[1]=24;
api.bar.name[2]=L"Hits";
api.bar.expl[2]=L"Number of calls to API function";
api.bar.mode[2]=BAR_SORT;
api.bar.defdx[2]=9;
api.bar.name[3]=L"Called from";
api.bar.expl[3]=L"Locations this function was called from";
api.bar.mode[3]=BAR_FLAT;
api.bar.defdx[3]=256;
api.bar.nbar=4;
api.tabfunc=Apifunc;
api.custommode=0;
api.customdata=NULL;
api.updatefunc=NULL;
api.drawfunc=(DRAWFUNC *)Apidraw;
api.tableselfunc=NULL;
api.menu=apimenu;
// OllyDbg saves positions of plugin windows with attribute TABLE_SAVEPOS to
// the .ini file but does not automatically restore them. Let us add this
// functionality here. To conform to OllyDbg norms, window is restored only
// if corresponding option is enabled.
if (restorewinpos!=0) {
restore=0; // Default
Getfromini(NULL,PLUGINNAME,L"Restore window",L"%i",&restore);
if (restore)
Createtablewindow(&api,0,api.bar.nbar,NULL,L"ICO_PLUGIN",PLUGINNAME);
;
};
// Report success.
Settracestate(TRACE_OFF);
return 0;
};
// Optional entry, called when user opens new or restarts current application.
// Plugin should reset internal variables and data structures to the initial
// state.
extc void __cdecl ODBG2_Pluginreset(void) {
Deletesorteddatarange(&code,0x00000000,0xFFFFFFFF);
Deletesorteddatarange(&retlist,0x00000000,0xFFFFFFFF);
restoretempbp=0;
Deletesorteddatarange(&(api.sorted),0x00000000,0xFFFFFFFF);
Discardcalldata();
Settracestate(TRACE_OFF);
};
// OllyDbg calls this optional function when user wants to terminate OllyDbg.
// All MDI windows created by plugins still exist. Function must return 0 if
// it is safe to terminate. Any non-zero return will stop closing sequence. Do
// not misuse this possibility! Always inform user about the reasons why
// termination is not good and ask for his decision! Attention, don't make any
// unrecoverable actions for the case that some other plugin will decide that
// OllyDbg should continue running.
extc int __cdecl ODBG2_Pluginclose(void) {
// For automatical restoring of open windows, mark in .ini file whether
// API list is still open.
Writetoini(NULL,PLUGINNAME,L"Restore window",L"%i",api.hw!=NULL);
return 0;
};
// OllyDbg calls this optional function once on exit. At this moment, all MDI
// windows created by plugin are already destroyed (and received WM_DESTROY
// messages). Function must free all internally allocated resources, like
// window classes, files, memory etc.
extc void __cdecl ODBG2_Plugindestroy(void) {
Destroysorteddata(&code);
Destroysorteddata(&retlist);
Destroysorteddata(&(api.sorted));
Discardcalldata();
};
See also: