This is the annotated code of sample bookmark plugin. I place it here so that you can get quick help on all referenced functions.
////////////////////////////////////////////////////////////////////////////////
// //
// SAMPLE PLUGIN FOR OLLYDBG //
// //
// This plugin allows to set up to 10 code bookmarks using keyboard shortcuts //
// or popup menus in Disassembler and then quickly return to one of the //
// bookmarks using shortcuts, popup menu or Bookmark window. Bookmarks //
// are kept between sessions in .udd file. //
// //
////////////////////////////////////////////////////////////////////////////////
// VERY IMPORTANT NOTICE: COMPILE THIS DLL WITH BYTE ALIGNMENT OF STRUCTURES
// AND UNSIGNED CHAR!
#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <dir.h>
#include "plugin.h"
HINSTANCE hinst; // DLL instance
HWND hwmain; // Handle of main OllyDbg window
char bookmarkwinclass[32]; // Name of bookmark window class
// OllyDbg supports and makes extensive use of special kind of data collections
// called sorted tables. A table consists of descriptor (t_table) and data. All
// data elements has same size and begin with a 3-dword header: address, size
// and type. Table automatically sorts items by address, overlapping is not
// allowed. Our bookmark table consists of elements of type t_bookmark.
typedef struct t_bookmark {
ulong index; // Bookmark index (0..9)
ulong size; // Size of index, always 1 in our case
ulong type; // Type of entry, always 0
ulong addr; // Address of bookmark
} t_bookmark;
t_table bookmark; // Bookmark table
// Functions in this file are placed in more or less "chronological" order,
// i.e. order in which they will be called by OllyDbg. This requires forward
// referencing.
int Bookmarksortfunc(t_bookmark *b1,t_bookmark *b2,int sort);
LRESULT CALLBACK Bookmarkwinproc(HWND hw,UINT msg,WPARAM wp,LPARAM lp);
int Bookmarkgettext(char *s,char *mask,int *select,t_sortheader *ph,int column);
void Createbookmarkwindow(void);
// Entry point into a plugin DLL. Many system calls require DLL instance
// which is passed to DllEntryPoint() as one of parameters. Remember it.
// Preferrable way is to place initializations into ODBG_Plugininit() and
// cleanup in ODBG_Plugindestroy().
BOOL WINAPI DllEntryPoint(HINSTANCE hi,DWORD reason,LPVOID reserved) {
if (reason==DLL_PROCESS_ATTACH)
hinst=hi; // Mark plugin instance
return 1; // Report success
};
// ODBG_Plugindata() is a "must" for valid OllyDbg plugin. It must fill in
// plugin name and return version of plugin interface. If function is absent,
// or version is not compatible, plugin will be not installed. Short name
// identifies it in the Plugins menu. This name is max. 31 alphanumerical
// characters or spaces + terminating '\0' long. To keep life easy for users,
// this name should be descriptive and correlate with the name of DLL.
extc int _export cdecl ODBG_Plugindata(char shortname[32]) {
strcpy(shortname,"Bookmarks"); // Name of plugin
return PLUGIN_VERSION;
};
// OllyDbg calls this obligatory function once during startup. Place all
// one-time initializations here. If all resources are successfully allocated,
// function must return 0. On error, it must free partially allocated resources
// and return -1, in this case plugin will be removed. Parameter ollydbgversion
// is the version of OllyDbg, use it to assure that it is compatible with your
// plugin; hw is the handle of main OllyDbg window, keep it if necessary.
// Parameter features is reserved for future extentions, do not use it.
extc int _export cdecl ODBG_Plugininit(
int ollydbgversion,HWND hw,ulong *features) {
// Check that version of OllyDbg is correct.
if (ollydbgversion<PLUGIN_VERSION)
return -1;
// Keep handle of main OllyDbg window. This handle is necessary, for example,
// to display message box.
hwmain=hw;
// Initialize bookmark data. Data consists of elements of type t_bookmark,
// we reserve space for 10 elements. If necessary, table will allocate more
// space, but in our case maximal number of bookmarks is 10. Elements do not
// allocate memory or other resources, so destructor is not necessary.
if (Createsorteddata(&(bookmark.data),"Bookmarks",
sizeof(t_bookmark),10,(SORTFUNC *)Bookmarksortfunc,NULL)!=0)
return -1; // Unable to allocate bookmark data
// Register window class for MDI window that will display plugins. Please
// note that formally this class belongs to instance of main OllyDbg program,
// not a plugin DLL. String bookmarkwinclass gets unique name of new class.
// Keep it to create window and unregister on shutdown.
if (Registerpluginclass(bookmarkwinclass,NULL,hinst,Bookmarkwinproc)<0) {
// Failure! Destroy sorted data and exit.
Destroysorteddata(&(bookmark.data));
return -1; };
// Plugin successfully initialized. Now is the best time to report this fact
// to the log window. To conform OllyDbg look and feel, please use two lines.
// The first, in black, should describe plugin, the second, gray and indented
// by two characters, bears copyright notice.
Addtolist(0,0,"Bookmarks sample plugin v1.10 (plugin demo)");
Addtolist(0,-1," Copyright (C) 2001-2004 Oleh Yuschuk");
// 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. I keep information whether window was open when
// OllyDbg terminated also in ollydbg.ini. This information is saved in
// ODBG_Pluginclose. To conform to OllyDbg norms, window is restored only
// if corresponding option is enabled.
if (Plugingetvalue(VAL_RESTOREWINDOWPOS)!=0 &&
Pluginreadintfromini(hinst,"Restore bookmarks window",0)!=0)
Createbookmarkwindow();
return 0;
};
// To sort sorted data by some criterium, one must supply sort function that
// returns -1 if first element is less than second, 1 if first element is
// greater and 0 if elements are equal according to criterium sort. Usually
// this criterium is the zero-based index of the column in window.
int Bookmarksortfunc(t_bookmark *b1,t_bookmark *b2,int sort) {
int i=0;
if (sort==1) { // Sort by address of bookmark
if (b1->addr<b2->addr) i=-1;
else if (b1->addr>b2->addr) i=1; };
// If elements are equal or sorting is by the first column, sort by index.
if (i==0) {
if (b1->index<b2->index) i=-1;
else if (b1->index>b2->index) i=1; };
return i;
};
// Each window class needs its own window procedure. Both standard and custom
// OllyDbg windows must pass some system and OllyDbg-defined messages to
// Tablefunction(). See description of Tablefunction() for more details.
LRESULT CALLBACK Bookmarkwinproc(HWND hw,UINT msg,WPARAM wp,LPARAM lp) {
int i,shiftkey,controlkey;
HMENU menu;
t_bookmark *pb;
switch (msg) {
// Standard messages. You can process them, but - unless absolutely sure -
// always pass them to Tablefunction().
case WM_DESTROY:
case WM_MOUSEMOVE:
case WM_LBUTTONDOWN:
case WM_LBUTTONDBLCLK:
case WM_LBUTTONUP:
case WM_RBUTTONDOWN:
case WM_RBUTTONDBLCLK:
case WM_HSCROLL:
case WM_VSCROLL:
case WM_TIMER:
case WM_SYSKEYDOWN:
Tablefunction(&bookmark,hw,msg,wp,lp);
break; // Pass message to DefMDIChildProc()
// Custom messages responsible for scrolling and selection. User-drawn
// windows must process them, standard OllyDbg windows without extra
// functionality pass them to Tablefunction().
case WM_USER_SCR:
case WM_USER_VABS:
case WM_USER_VREL:
case WM_USER_VBYTE:
case WM_USER_STS:
case WM_USER_CNTS:
case WM_USER_CHGS:
return Tablefunction(&bookmark,hw,msg,wp,lp);
// If window should support TABLE_ONTOP ("Always on top" mode), it must pass
// WM_WINDOWPOSCHANGED to Tablefunction().
case WM_WINDOWPOSCHANGED:
return Tablefunction(&bookmark,hw,msg,wp,lp);
case WM_USER_MENU:
menu=CreatePopupMenu();
// Find selected bookmark. Any operations with bookmarks make sense only
// if at least one bookmark exists and is selected. Note that sorted data
// has special sort index table which is updated only when necessary.
// Getsortedbyselection() does this; some other sorted data functions
// don't and you must call Sortsorteddata(). Read documentation!
pb=(t_bookmark *)Getsortedbyselection(
&(bookmark.data),bookmark.data.selected);
if (menu!=NULL && pb!=NULL) {
AppendMenu(menu,MF_STRING,1,"&Follow\tEnter");
AppendMenu(menu,MF_STRING,2,"&Delete\tDel"); };
// Even when menu is NULL, call to Tablefunction is still meaningful.
i=Tablefunction(&bookmark,hw,WM_USER_MENU,0,(LPARAM)menu);
if (menu!=NULL) DestroyMenu(menu);
if (i==1) // Follow bookmark in Disassembler
Setcpu(0,pb->addr,0,0,CPU_ASMHIST|CPU_ASMCENTER|CPU_ASMFOCUS);
else if (i==2) { // Delete bookmark
Deletesorteddata(&(bookmark.data),pb->index);
// There is no automatical window update, do it yourself.
InvalidateRect(hw,NULL,FALSE); };
return 0;
case WM_KEYDOWN:
// Processing of WM_KEYDOWN messages is - surprise, surprise - very
// similar to that of corresponding menu entries.
shiftkey=GetKeyState(VK_SHIFT) & 0x8000;
controlkey=GetKeyState(VK_CONTROL) & 0x8000;
if (wp==VK_RETURN && shiftkey==0 && controlkey==0) {
// Return key follows bookmark in Disassembler.
pb=(t_bookmark *)Getsortedbyselection(
&(bookmark.data),bookmark.data.selected);
if (pb!=NULL)
Setcpu(0,pb->addr,0,0,CPU_ASMHIST|CPU_ASMCENTER|CPU_ASMFOCUS);
; }
else if (wp==VK_DELETE && shiftkey==0 && controlkey==0) {
// DEL key deletes bookmark.
pb=(t_bookmark *)Getsortedbyselection(
&(bookmark.data),bookmark.data.selected);
if (pb!=NULL) {
Deletesorteddata(&(bookmark.data),pb->index);
InvalidateRect(hw,NULL,FALSE);
}; }
else
// Add all this arrow, home and pageup functionality.
Tablefunction(&bookmark,hw,msg,wp,lp);
break;
case WM_USER_DBLCLK:
// Doubleclicking row follows bookmark in Disassembler.
pb=(t_bookmark *)Getsortedbyselection(
&(bookmark.data),bookmark.data.selected);
if (pb!=NULL)
Setcpu(0,pb->addr,0,0,CPU_ASMHIST|CPU_ASMCENTER|CPU_ASMFOCUS);
return 1; // Doubleclick processed
case WM_USER_CHALL:
case WM_USER_CHMEM:
// Something is changed, redraw window.
InvalidateRect(hw,NULL,FALSE);
return 0;
case WM_PAINT:
// Painting of all OllyDbg windows is done by Painttable(). Make custom
// drawing only if you have important reasons to do this.
Painttable(hw,&bookmark,Bookmarkgettext);
return 0;
default: break;
};
return DefMDIChildProc(hw,msg,wp,lp);
};
// If you define ODBG_Pluginmainloop, this function will be called each time
// from the main Windows loop in OllyDbg. If there is some debug event from
// the debugged application, debugevent points to it, otherwise it is NULL. Do
// not declare this function unnecessarily, as this may negatively influence
// the overall speed!
extc void _export cdecl ODBG_Pluginmainloop(DEBUG_EVENT *debugevent) {
};
// Record types must be unique among OllyDbg and all plugins. The best way to
// assure this is to register record type by OllDbg (Oleh Yuschuk). Registration
// is absolutely free of charge, except for email costs :)
#define TAG_BOOKMARK 0x236D420AL // Bookmark record type in .udd file
// Time to save data to .udd file! This is done by calling Pluginsaverecord()
// for each data item that must be saved. Global, process-oriented data must
// be saved in main .udd file (named by .exe); module-relevant data must be
// saved in module files. Don't forget to save all addresses relative to
// module's base, so that data will be restored correctly even when module is
// relocated.
extc void _export cdecl ODBG_Pluginsaveudd(t_module *pmod,int ismainmodule) {
int i;
ulong data[2];
t_bookmark *pb;
if (ismainmodule==0)
return; // Save bookmarks to main file only
pb=(t_bookmark *)bookmark.data.data;
for (i=0; i<bookmark.data.n; i++,pb++) {
data[0]=pb->index;
data[1]=pb->addr;
Pluginsaverecord(TAG_BOOKMARK,2*sizeof(ulong),data);
};
};
// OllyDbg restores data from .udd file. If record belongs to plugin, it must
// process record and return 1, otherwise it must return 0 to pass record to
// other plugins. Note that module descriptor pointed to by pmod can be
// incomplete, i.e. does not necessarily contain all informations, especially
// that from .udd file.
extc int _export cdecl ODBG_Pluginuddrecord(t_module *pmod,int ismainmodule,
ulong tag,ulong size,void *data) {
t_bookmark mark;
if (ismainmodule==0)
return 0; // Bookmarks saved in main file only
if (tag!=TAG_BOOKMARK)
return 0; // Tag is not recognized
mark.index=((ulong *)data)[0];
mark.size=1;
mark.type=0;
mark.addr=((ulong *)data)[1];
Addsorteddata(&(bookmark.data),&mark);
return 1; // Record processed
};
// Function adds items either to main OllyDbg menu (origin=PM_MAIN) or to popup
// menu in one of standard OllyDbg windows. When plugin wants to add own menu
// items, it gathers menu pattern in data and returns 1, otherwise it must
// return 0. Except for static main menu, plugin must not add inactive items.
// Item indices must range in 0..63. Duplicated indices are explicitly allowed.
extc int _export cdecl ODBG_Pluginmenu(int origin,char data[4096],void *item) {
int i,n;
t_bookmark *pb;
t_dump *pd;
switch (origin) {
// Menu creation is very simple. You just fill in data with menu pattern.
// Some examples:
// 0 Aaa,2 Bbb|3 Ccc|,, - linear menu with 3 items, relative IDs 0, 2 and
// 3, separator between second and third item, last
// separator and commas are ignored;
// #A{0Aaa,B{1Bbb|2Ccc}} - unconditional separator, followed by popup menu
// A with two elements, second is popup with two
// elements and separator inbetween.
case PM_MAIN: // Plugin menu in main window
strcpy(data,"0 &Bookmarks|1 &About");
// If your plugin is more than trivial, I also recommend to include Help.
return 1;
case PM_DISASM: // Popup menu in Disassembler
// First check that menu applies.
pd=(t_dump *)item;
if (pd==NULL || pd->size==0)
return 0; // Window empty, don't add
// Start second-level popup menu.
n=sprintf(data,"Bookmark{");
// Add item "Insert bookmark n" if there are free bookmarks and some part
// of Disassembler is selected. Note that OllyDbg correctly interpretes
// superfluos commas, separators and, to some extent, missed braces.
pb=(t_bookmark *)bookmark.data.data;
for (i=0; i<bookmark.data.n; i++)
if (pb[i].index!=(ulong)i) break;
if (i<10 && pd->sel1>pd->sel0)
n+=sprintf(data+n,"%i &Insert bookmark %i\tAlt+Shift+%i,",i,i,i);
// Add item "Delete bookmark n" for each available bookmark. Menu
// identifiers are not necessarily consecutive.
for (i=0; i<bookmark.data.n; i++) {
n+=sprintf(data+n,"%i Delete bookmark %i,",pb[i].index+10,pb[i].index);
};
// Add separator to menu.
data[n++]='|';
// Add item "Go to bookmark n" for each available bookmark. Bookmarks
// set at selected command are not shown.
for (i=0; i<bookmark.data.n; i++) {
if (pb[i].addr==pd->sel0) continue;
n+=sprintf(data+n,"%i Go to bookmark %i\tAlt+%i,",
pb[i].index+20,pb[i].index,pb[i].index);
;
};
// Close popup. If you forget to do this, OllyDbg will try to correct
// your error.
sprintf(data+n,"}");
return 1;
default: break; // Any other window
};
return 0; // Window not supported by plugin
};
// This optional function receives commands from plugin menu in window of type
// origin. Argument action is menu identifier from ODBG_Pluginmenu(). If user
// activates automatically created entry in main menu, action is 0.
extc void _export cdecl ODBG_Pluginaction(int origin,int action,void *item) {
t_bookmark mark,*pb;
t_dump *pd;
if (origin==PM_MAIN) {
switch (action) {
case 0:
// Menu item "Bookmarks", creates bookmark window.
Createbookmarkwindow();
break;
case 1:
// Menu item "About", displays plugin info.
MessageBox(hwmain,
"Bookmark plugin v1.10\n"
"(demonstration of plugin capabilities)\n"
"Copyright (C) 2001-2004 Oleh Yuschuk",
"Bookmark plugin",MB_OK|MB_ICONINFORMATION);
break;
default: break;
}; }
else if (origin==PM_DISASM) {
pd=(t_dump *)item;
if (action>=0 && action<10) { // Insert bookmark
mark.index=action;
mark.size=1;
mark.type=0;
mark.addr=pd->sel0;
Addsorteddata(&(bookmark.data),&mark);
if (bookmark.hw!=NULL) InvalidateRect(bookmark.hw,NULL,FALSE); }
else if (action>=10 && action<20) {// Delete bookmark
pb=(t_bookmark *)Findsorteddata(&(bookmark.data),action-10);
if (pb!=NULL) {
Deletesorteddata(&(bookmark.data),action-10);
if (bookmark.hw!=NULL) InvalidateRect(bookmark.hw,NULL,FALSE);
}; }
else if (action>=20 && action<30) {//Go to bookmark
pb=(t_bookmark *)Findsorteddata(&(bookmark.data),action-20);
if (pb!=NULL) {
Setcpu(0,pb->addr,0,0,CPU_ASMHIST|CPU_ASMCENTER|CPU_ASMFOCUS);
};
};
};
};
// Standard function Painttable() makes most of OllyDbg windows redrawing. You
// only need to supply another function that prepares text strings and
// optionally colours them. Case of custom windows is a bit more complicated,
// please read documentation.
int Bookmarkgettext(char *s,char *mask,int *select,
t_sortheader *ph,int column) {
int n;
ulong cmdsize,decodesize;
char cmd[MAXCMDSIZE],*pdecode;
t_memory *pmem;
t_disasm da;
t_bookmark *pb=(t_bookmark *)ph;
if (column==0) { // Name of bookmark
// Column 0 contains name of bookmark in form "Alt+n", where n is the
// digit from 0 to 9. Mainly for demonstration purposes, I display prefix
// "Alt+" in grayed and digit in normal text. Standard table windows do
// not need to bother about selection.
n=sprintf(s,"Alt+%i",pb->index);
*select=DRAW_MASK;
memset(mask,DRAW_GRAY,4);
mask[4]=DRAW_NORMAL; }
else if (column==1) // Address of bookmark
n=sprintf(s,"%08X",pb->addr);
else if (column==2) { // Disassembled command
// Function Disasm() requires that calling routine supplies code to be
// disassembled. Read this code from memory. First determine possible
// code size.
pmem=Findmemory(pb->addr); // Find memory block containing code
if (pmem==NULL) {
*select=DRAW_GRAY; return sprintf(s,"???"); };
cmdsize=pmem->base+pmem->size-pb->addr;
if (cmdsize>MAXCMDSIZE)
cmdsize=MAXCMDSIZE;
if (Readmemory(cmd,pb->addr,cmdsize,MM_RESTORE|MM_SILENT)!=cmdsize) {
*select=DRAW_GRAY; return sprintf(s,"???"); };
pdecode=Finddecode(pb->addr,&decodesize);
if (decodesize<cmdsize) pdecode=NULL;
Disasm(cmd,cmdsize,pb->addr,pdecode,&da,DISASM_CODE,0);
strcpy(s,da.result);
n=strlen(s); }
else if (column==3) // Comment
// Only user-defined comments are displayed here.
n=Findname(pb->addr,NM_COMMENT,s);
else n=0; // s is not necessarily 0-terminated
return n;
};
// OllyDbg makes most of work when creating standard MDI window. Plugin must
// only describe number of columns, their properties and properties of window
// as a whole.
void Createbookmarkwindow(void) {
// Describe table columns. Note that column names are pointers, so strings
// must exist as long as table itself.
if (bookmark.bar.nbar==0) {
// Bar still uninitialized.
bookmark.bar.name[0]="Bookmark"; // Name of bookmark
bookmark.bar.defdx[0]=9;
bookmark.bar.mode[0]=0;
bookmark.bar.name[1]="Address"; // Bookmark address
bookmark.bar.defdx[1]=9;
bookmark.bar.mode[1]=0;
bookmark.bar.name[2]="Disassembly";// Disassembled command
bookmark.bar.defdx[2]=32;
bookmark.bar.mode[2]=BAR_NOSORT;
bookmark.bar.name[3]="Comment"; // Comment
bookmark.bar.defdx[3]=256;
bookmark.bar.mode[3]=BAR_NOSORT;
bookmark.bar.nbar=4;
bookmark.mode= // Note: new option TABLE_ONTOP
TABLE_COPYMENU|TABLE_SORTMENU|TABLE_APPMENU|TABLE_SAVEPOS|TABLE_ONTOP;
bookmark.drawfunc=Bookmarkgettext; };
// If window already exists, Quicktablewindow() does not create new window,
// but restores and brings to top existing. This is the simplest way,
// Newtablewindow() is more flexible but more complicated. I do not recommend
// custom (plugin-drawn) windows without very important reasons to do this.
Quicktablewindow(&bookmark,15,4,bookmarkwinclass,"Bookmarks");
};
// This function receives possible keyboard shortcuts from standard OllyDbg
// windows. If it recognizes shortcut, it must process it and return 1,
// otherwise it returns 0.
extc int _export cdecl ODBG_Pluginshortcut(
int origin,int ctrl,int alt,int shift,int key,void *item) {
t_dump *pd;
t_bookmark mark,*pm;
// Plugin accepts shortcuts in form Alt+x or Shift+Alt+x, where x is a key
// '0'..'9'. Shifted shortcut sets bookmark (only in Disassembler),
// non-shifted jumps to bookmark from everywhere.
if (ctrl==0 && alt!=0 && key>='0' && key<='9') {
if (shift!=0 && origin==PM_DISASM && item!=NULL) {
// Set new or replace existing bookmark.
pd=(t_dump *)item;
mark.index=key-'0';
mark.size=1;
mark.type=0;
mark.addr=pd->sel0;
Addsorteddata(&(bookmark.data),&mark);
if (bookmark.hw!=NULL) InvalidateRect(bookmark.hw,NULL,FALSE);
return 1; } // Shortcut recognized
else if (shift==0) {
// Jump to existing bookmark (from any window).
pm=Findsorteddata(&(bookmark.data),key-'0');
if (pm==NULL)
Flash("Undefined bookmark");
else
Setcpu(0,pm->addr,0,0,CPU_ASMHIST|CPU_ASMCENTER|CPU_ASMFOCUS);
return 1; // Shortcut recognized
};
};
return 0; // Shortcut not recognized
};
// Function is called when user opens new or restarts current application.
// Plugin should reset internal variables and data structures to initial state.
extc void _export cdecl ODBG_Pluginreset(void) {
Deletesorteddatarange(&(bookmark.data),0,0xFFFFFFFF);
};
// 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!
extc int _export cdecl ODBG_Pluginclose(void) {
// For automatical restoring of open windows, mark in .ini file whether
// Bookmarks window is still open.
Pluginwriteinttoini(hinst,"Restore bookmarks window",bookmark.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 and so on.
extc void _export cdecl ODBG_Plugindestroy(void) {
Unregisterpluginclass(bookmarkwinclass);
Destroysorteddata(&(bookmark.data));
};