traceapi.c - annotated code of API trace plugin

//                                                                            //
//              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.

#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)
  // 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.
      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)
    // Check whether code belongs to the system DLL.
    if ((pc->type & (CODE_USER|CODE_SYS))==0) {
      if (pmod==NULL) continue;        // Code without module?..
      if (pmod->type & MOD_SYSTEMDLL)
    if ((guardsys==0 && (pc->type & CODE_USER)!=0) ||
      (guardsys!=0 && (pc->type & CODE_SYS)!=0)
    ) {

// 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) {

// Unguards all code blocks.
static void Unguardall(void) {
  int i;
  t_memory *pmem;
  // 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

// 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");

////////////////////////////////// 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++) {
    pfreecall=pcall; };
  // Add extent to the chain of extents.
  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) {
    if (pfreecall==NULL) return NULL; };
  // Get free call record.
  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) {
    extentlist=pext; };
  // There are no more free call descriptors.
  // 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;

/////////////////////////////////// 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),
  if (papi==NULL)
    return MENU_ABSENT;                // No selection
  if (mode==MENU_VERIFY)
    return MENU_NORMAL;
  else if (mode==MENU_EXECUTE) {
    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),
  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)
      else {
        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,
      else answer=Condlogbreakpoint(pt->hw,&(papi->addr),1,0,NULL,
      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
  if (*addr==0)
    StrcopyW(s,TEXTLEN,L"<unknown location>");
  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),
  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) {
    if (papi->ncall==1 && pcall!=NULL && pcall->from!=0)
    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),
  if (papi==NULL)
    return MENU_ABSENT;                // No selection
  if (mode==MENU_VERIFY) {
    // Calculate how many calls already have breakpoints.
    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++; };
    // 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; };
      while (pcall!=NULL && nlist<papi->ncall) {
        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,
      else answer=Condlogbreakpoint(pt->hw,list,nlist,0,NULL,
      if (answer<0)
        Flash(L"Unable to set all breakpoints");
      Memfree(list); }
    else {
      while (pcall!=NULL) {
        if (index==0) answer=Setint3breakpoint(pcall->from,BP_MANUAL|BP_BREAK,
        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
    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;
  // 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
    case DF_FREECACHE:                 // Request to free cached resources
    case DF_NEWROW:                    // Request to start new row in window
    case 0:                            // API address
      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; };
    case 1:                            // API name
    case 2:                            // Hit count
    case 3:                            // Call locations
      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; };
        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
          color=DRAW_BREAK;            // Unconditional breakpoint
        if (m!=0) memset(mask+n,color,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
    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.
    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)
    case WM_USER_DBLCLK:               // Doubleclick
      // Get selection.
      papi=(t_api *)Getsortedbyselection(
      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.
          // Columns 0 to 2. Follow address of the API function in the CPU
          // Disassembler pane.
      return 1;
    default: break;
  return 0;

//////////////////////////////// PLUGIN OPTIONS ////////////////////////////////

static t_control traceapioptions[] = {
  { CA_COMMENT, -1,              0,   0,   0,   0, NULL,
                  NULL },
  { CA_TITLE,   OPT_TITLE,      80,   4, 160,  15, NULL,
                  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,

// 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.
  // Address of guarded memory block.
  // If guarded memory block is in the memory area opposite to expected, remove
  // guard. We didn't do it before to spare time.
  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; };
  // 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.
    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;
    // Unguard this block and guard user code.
    // Read stack and determine the caller.
    if (nstack==0)
    else {
      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
      pa=(t_api *)Addsorteddata(&(api.sorted),&a); };
    if (pa!=NULL) {                    // Opposite would mean severe error
      // Even when from is 0, I add it to the call list, as an indication that
      // call origin is unknown.
      while (1) {
        if (*ppcall!=NULL && (*ppcall)->from<from) {
          continue; };
        if (*ppcall!=NULL && (*ppcall)->from==from)
          break;                       // Call address is already in the chain
        if (pcall==NULL)
          break;                       // Low memory
    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) {
          pret=(t_ret *)Findsorteddata(&retlist,aret,0);
          if (pret==NULL) {
            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)
        else {
          for (i=0; i<NARG; i++) {
            if (i<nstack-1) {
              adec[i].value=stack[i+1]; }
            else {
        // Write name of function and, if available, call address to log.
        n=StrcopyW(s,TEXTLEN,L"Call to ");
        if (from!=0) {
          n+=StrcopyW(s+n,TEXTLEN-n,L" from ");
          Decodeaddress(from,0,DM_WIDEFORM|DM_MODNAME,s+n,TEXTLEN-n,NULL); };
        // 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",
  // 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.
    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)
      Addtolist(0,DRAW_NORMAL,L"  EAX = %08X  %s",preg->r[REG_EAX],s);
    if (pret->count==0)
    else {
      pret->type&=~RET_ACTIVE;         // Temp breakpoint is autoremoved
    }; }
  else if (trace==TRACE_TILLSYS)
  else if (trace==TRACE_TILLUSER)

// 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)
      else if (trace==TRACE_TILLUSER)
    case PN_ENDMOD:                    // Module is removed from the memory
      Deletesorteddatarange(&code,((t_module *)data)->base,
        ((t_module *)data)->base+((t_module *)data)->size);
    case PN_RUN:                       // User continues code execution
      if (trace!=TRACE_OFF && *((t_status *)data)==STAT_RUNNING)
        *((t_status *)data)=STAT_RUNTHR;
    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.
    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
      return MENU_ABSENT;              // Action is not allowed
    ; }
  else if (mode==MENU_EXECUTE) {
    if (index==0) {                    // Stop trace
      Settracestate(TRACE_OFF); }
    else {                             // Start trace
      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) {
    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.
    // 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");
    // 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");
      L"Bookmark plugin",MB_OK|MB_ICONINFORMATION);
    // Suspendallthreads() and Resumeallthreads() must be paired, even if they
    // are called in inverse order!
    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 },

// 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.
  if (osversion.dwPlatformId==VER_PLATFORM_WIN32_WINDOWS) {
    Addtolist(0,DRAW_HILITE,L"%s will not work on Win95-based systems",
    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) {
    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) {
    return -1; };
  // Restore options.
  Getfromini(NULL,PLUGINNAME,L"Protocol API calls",L"%i",&protocolapi);
  Getfromini(NULL,PLUGINNAME,L"Skip GDI32",L"%i",&skipgdi32);
  Getfromini(NULL,PLUGINNAME,L"Skip USER32",L"%i",&skipuser32);
  Getfromini(NULL,PLUGINNAME,L"Skip KERNEL32",L"%i",&skipkernel32);
  Getfromini(NULL,PLUGINNAME,L"Skip MSVC",L"%i",&skipmsvc);
  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.
  api.mode=TABLE_SAVEALL|TABLE_AUTOUPD;;[0]=L"Address";[0]=L"Address of API function";[0]=BAR_SORT;[0]=9;[1]=L"Name";[1]=L"Name of API function";[1]=BAR_SORT;[1]=24;[2]=L"Hits";[2]=L"Number of calls to API function";[2]=BAR_SORT;[2]=9;[3]=L"Called from";[3]=L"Locations this function was called from";[3]=BAR_FLAT;[3]=256;;
  api.drawfunc=(DRAWFUNC *)Apidraw;
  // 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)
  // Report success.
  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) {

// 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) {

See also:
Plugins, bookmark plugin