////////////////////////////////////////////////////////////////////////////////
// //
// SAMPLE BOOKMARK PLUGIN FOR OLLYDBG v2.01 //
// //
// 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 //
// preserved between sessions in the .udd files. //
// //
// 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. //
// //
////////////////////////////////////////////////////////////////////////////////
// VERY IMPORTANT NOTICE: PLUGINS ARE UNICODE LIBRARIES! COMPILE THEM WITH BYTE
// ALIGNMENT OF STRUCTURES AND DEFAULT UNSIGNED CHAR!
// 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 <winnt.h> // Only if you call ODBG2_Pluginmainloop
#include "plugin.h"
#define PLUGINNAME L"Bookmarks" // Unique plugin name
#define VERSION L"2.00.01" // Plugin version
HINSTANCE hdllinst; // Instance of plugin DLL
// Most of OllyDbg windows are the so called tables. A table consists of table
// descriptor (t_table) with embedded sorted data (t_table.sorted, unused in
// custom tables). If data is present, all data elements have the same size and
// begin with a 3-dword t_sorthdr: address, size, type. Data is kept sorted by
// address (in our case this is the index of the bookmark), overlapping is not
// allowed. Our bookmark table consists of elements of type t_bookmark.
typedef struct t_bookmark {
// Obligatory header, its layout _must_ coincide with t_sorthdr!
ulong index; // Bookmark index (0..9)
ulong size; // Size of index, always 1 in our case
ulong type; // Type of entry, TY_xxx
// Custom data follows header.
ulong addr; // Address of bookmark
} t_bookmark;
static t_table bookmark; // Bookmark table
static int showindisasm; // Option: show bookmarks in Disasm pane
////////////////////////////////////////////////////////////////////////////////
//////////////////////////////// BOOKMARK TABLE ////////////////////////////////
// Function that compares two bookmarks 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 Bookmarksortfunc(const t_sorthdr *sh1,const t_sorthdr *sh2,const int n) {
int i=0;
t_bookmark *bm1,*bm2;
bm1=(t_bookmark *)sh1;
bm2=(t_bookmark *)sh2;
if (n==1) { // Sort by address
if (bm1->addr<bm2->addr) i=-1;
else if (bm1->addr>bm2->addr) i=1; };
if (i==0) { // Additionally sort by index
if (bm1->index<bm2->index) i=-1;
else if (bm1->index>bm2->index) i=1; };
return i;
};
// Bookmark destructor, a function that frees resources allocated in the
// descriptor of the bookmark. There are no such resources, so this function
// could be NULL.
void Bookmarkdestfunc(t_sorthdr *sh) {
};
// Drawing function of Bookmarks window.
int Bookmarkdraw(wchar_t *s,uchar *mask,int *select,
t_table *pt,t_drawheader *ph,int column,void *cache) {
int m,n; // Number of symbols in the string
ulong length;
uchar cmd[MAXCMDSIZE];
t_bookmark *pmark;
t_disasm *pasm;
// 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.
pmark=(t_bookmark *)ph;
// Our cache is just a t_disasm. It is not NULL on the same conditions.
pasm=(t_disasm *)cache;
switch (column) {
case DF_CACHESIZE: // Request for draw cache size
// Columns 3 and 4 (disassembly and comment) both require calls to
// Disasm(). To accelerate processing, I call disassembler once per line
// and cache data between the calls. Here I inform the drawing routine
// how large the cache must be.
return sizeof(t_disasm);
case DF_FILLCACHE: // Request to fill draw cache
// We don't need to initialize cache when drawing begins. Note that cache
// is initially zeroed.
return 0;
case DF_FREECACHE: // Request to free cached resources
// We don't need to free cached resources when drawing ends.
return 0;
case DF_NEWROW: // Request to start new row in window
// New row starts. Let us disassemble the command at the pointed address.
// I assume that bookmarks can't be set on data. First of all, we need to
// read the contents of memory. Length of 80x86 commands is limited to
// MAXCMDSIZE bytes.
length=Readmemory(cmd,pmark->addr,sizeof(cmd),MM_SILENT|MM_PARTIAL);
if (length==0) {
// Memory is not readable.
StrcopyW(pasm->result,TEXTLEN,L"???");
StrcopyW(pasm->comment,TEXTLEN,L""); }
else
Disasm(cmd,length,pmark->addr,Finddecode(pmark->addr,NULL),pasm,
DA_TEXT|DA_OPCOMM|DA_MEMORY,NULL,NULL);
return 0;
case 0: // 0-based index
n=StrcopyW(s,TEXTLEN,L"Alt+");
memset(mask,DRAW_GRAY,n);
m=swprintf(s+n,L"%i",pmark->index);
memset(mask+n,DRAW_NORMAL,m);
n+=m;
*select|=DRAW_MASK;
break;
case 1: // Address of the bookmark
n=Simpleaddress(s,pmark->addr,mask,select);
break;
case 2: // Disassembled command
n=StrcopyW(s,TEXTLEN,pasm->result);
break;
case 3: // Comment
// User-defined comment has highest priority.
n=FindnameW(pmark->addr,NM_COMMENT,s,TEXTLEN);
// Comment created by Disasm() is on the second place.
if (n==0)
n=StrcopyW(s,TEXTLEN,pasm->comment);
// Analyser comment follows.
if (n==0)
n=Getanalysercomment(NULL,pmark->addr,s,TEXTLEN);
// Procedure comments have the lowest priority.
if (n==0)
n=Commentaddress(pmark->addr,COMM_MARK|COMM_PROC,s,TEXTLEN);
// User-defined comments may be written in Kanji or Chinese.
*select|=DRAW_VARWIDTH;
break;
default: break;
};
return n;
};
// Custom table function of bookmarks window. Here it is used only to process
// doubleclicks (custom message WM_USER_DBLCLK). This function is also 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 Bookmarkfunc(t_table *pt,HWND hw,UINT msg,WPARAM wp,LPARAM lp) {
t_bookmark *pmark;
switch (msg) {
case WM_USER_DBLCLK: // Doubleclick
// Get selection.
pmark=(t_bookmark *)Getsortedbyselection(
&(pt->sorted),pt->sorted.selected);
// Follow address in CPU Disassembler pane. Actual address is added to
// the history, so that user can easily return back to it.
if (pmark!=NULL) Setcpu(0,pmark->addr,0,0,0,
CPU_ASMHIST|CPU_ASMCENTER|CPU_ASMFOCUS);
return 1;
default: break;
};
return 0;
};
////////////////////////////////////////////////////////////////////////////////
////////////////// PLUGIN MENUS EMBEDDED INTO OLLYDBG WINDOWS //////////////////
// Menu processing functions are called twice. First time (mode=MENU_VERIFY)
// OllyDbg asks to verify whether corresponding menu item applies or not. If
// necessary, menu function may change menu text (parameter name, up to TEXTLEN
// UNICODE characters). It must return one of the following codes:
//
// MENU_ABSENT: menu item does not apply and should not be present in
// the menu;
// MENU_NORMAL: menu item appears in the menu;
// MENU_CHECKED: menu item appears in the menu and has attached checkmark;
// MENU_CHKPARENT: menu item appears in the menu and has attached checkmark.
// Additionally, attaches checkmark to the parent item in
// menu on the previous level. This feature does not work in
// the main menu;
// MENU_SHORTCUT: menu item does not appear in the menu but is active and
// participates in the search for keyboard shortcut;
// MENU_GRAYED: item is present in the menu but disabled. This style is
// not compatible with OllyDbg's look-and-feel, use it only
// if absolutely necessary due to the menu logic.
//
// When menu item is selected (mouseclick or keyboard shortcut), menu function
// is called for the second time (mode=MENU_EXECUTE, name is undefined). At
// this moment, all threads of the debugged application are suspended. Function
// must make all necessary actions and return one of the following codes:
//
// MENU_REDRAW: this action has global impact, all OllyDbg windows must
// be updated. OllyDbg broadcasts WM_USER_CHGALL;
// MENU_NOREDRAW: no redrawing is necessary.
//
// If processing is lengthy and application should continue execution, use
// Resumeallthreads() at entry to the MENU_EXECUTE block and Suspendallthreads()
// on exit. Note that MENU_ABSENT and MENU_NOREDRAW are interchangeable.
//
// Parameter index allows to use single menu function with several similar menu
// items.
//
// Note that OllyDbg uses menu structuress to process keyboard shortcuts. It is
// done automatically, without the need to pay additional attention.
// Menu function of main menu, opens or brings to top list of bookmarks.
static int Mopenbookmarks(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 (bookmark.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(&bookmark,0,bookmark.bar.nbar,NULL,L"ICO_P",PLUGINNAME);
else
Activatetablewindow(&bookmark);
return MENU_NOREDRAW;
};
return MENU_ABSENT;
};
// Menu function of main menu, shows or hides bookmark display in the
// Disassembler pane.
static int Mshowbookmarks(t_table *pt,wchar_t *name,ulong index,int mode) {
if (mode==MENU_VERIFY)
return (showindisasm?MENU_CHECKED:MENU_NORMAL);
else if (mode==MENU_EXECUTE) {
showindisasm=!showindisasm;
Writetoini(NULL,PLUGINNAME,L"Show bookmarks in Disassembler",L"%i",
showindisasm);
return MENU_REDRAW;
};
};
// 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"Bookmark 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!
n+=StrcopyW(s+n,TEXTLEN-n,L"\nCopyright (C) 2001-2012 Oleh Yuschuk");
// The conditionals below are here to verify that this plugin can be
// compiled with all supported compilers. They are not necessary in the
// final code.
#if defined(__BORLANDC__)
n+=StrcopyW(s+n,TEXTLEN-n,L"\n\nCompiled with Borland (R) ");
#elif defined(_MSC_VER)
n+=StrcopyW(s+n,TEXTLEN-n,L"\n\nCompiled with Microsoft (R) ");
#elif defined(__MINGW32__)
n+=StrcopyW(s+n,TEXTLEN-n,L"\n\nCompiled with MinGW32 ");
#else
n+=StrcopyW(s+n,TEXTLEN-n,L"\n\nCompiled with ");
#endif
#ifdef __cplusplus
StrcopyW(s+n,TEXTLEN-n,L"C++ compiler");
#else
StrcopyW(s+n,TEXTLEN-n,L"C compiler");
#endif
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 bookmarks",
L"Open Bookmarks window",
K_NONE, Mopenbookmarks, NULL, 0 },
{ L"Show bookmarks in Disassembler",
L"Mark bookmarked command in the Disassembler pane of CPU window",
K_NONE, Mshowbookmarks, NULL, 0 },
{ L"|About",
L"About Bookmarks plugin",
K_NONE, Mabout, NULL, 0 },
{ NULL, NULL, K_NONE, NULL, NULL, 0 }
};
// Menu function of Disassembler pane that creates new bookmark.
static int Msetmark(t_table *pt,wchar_t *name,ulong index,int mode) {
t_bookmark bmk,*pmark;
t_dump *pd;
// Get dump descriptor. This operation is common both for
// MENU_VERIFY and MENU_EXECUTE.
if (pt==NULL || pt->customdata==NULL)
return MENU_ABSENT;
pd=(t_dump *)pt->customdata;
if (mode==MENU_VERIFY) {
// Check whether selection is defined.
if (pd->sel0>=pd->sel1)
return MENU_ABSENT;
// Check whether bookmark with given index already exists.
pmark=(t_bookmark *)Findsorteddata(&(bookmark.sorted),index,0);
if (pmark!=NULL)
return MENU_ABSENT;
swprintf(name,L"Set bookmark %i",index);
return MENU_NORMAL; }
else if (mode==MENU_EXECUTE) {
bmk.index=index;
bmk.size=1;
bmk.type=0;
bmk.addr=pd->sel0;
Addsorteddata(&(bookmark.sorted),&bmk);
Pluginmodulechanged(pd->sel0);
return MENU_REDRAW; };
return MENU_ABSENT;
};
// Menu function of Disassembler pane that follows existing bookmark.
static int Mfollowmark(t_table *pt,wchar_t *name,ulong index,int mode) {
t_bookmark *pmark;
t_dump *pd;
// Get dump descriptor.
if (pt==NULL || pt->customdata==NULL)
return MENU_ABSENT;
pd=(t_dump *)pt->customdata;
// Check whether bookmark is defined.
pmark=(t_bookmark *)Findsorteddata(&(bookmark.sorted),index,0);
if (pmark==NULL)
return MENU_ABSENT;
if (mode==MENU_VERIFY) {
// If we are already at bookmark, precede its name with checkmark.
if (pd->sel0<pd->sel1 && pmark->addr==pd->sel0) {
swprintf(name,L"Bookmark %i",index);
return MENU_CHECKED; };
swprintf(name,L"Follow bookmark %i",index);
return MENU_NORMAL; }
else if (mode==MENU_EXECUTE) {
Setcpu(0,pmark->addr,0,0,0,
CPU_ASMHIST|CPU_ASMCENTER|CPU_ASMFOCUS);
return MENU_REDRAW; };
return MENU_ABSENT;
};
// Menu function of Disassembler pane that deletes existing bookmark.
static int Mdeletemark(t_table *pt,wchar_t *name,ulong index,int mode) {
t_bookmark *pmark;
t_dump *pd;
// Get dump descriptor.
if (pt==NULL || pt->customdata==NULL)
return MENU_ABSENT;
pd=(t_dump *)pt->customdata;
if (mode==MENU_VERIFY) {
// Check whether bookmark is defined.
pmark=(t_bookmark *)Findsorteddata(&(bookmark.sorted),index,0);
if (pmark==NULL)
return MENU_ABSENT;
swprintf(name,L"Delete bookmark %i",index);
// If we are already at bookmark, precede its name with checkmark.
if (pd->sel0<pd->sel1 && pmark->addr==pd->sel0) {
return MENU_CHECKED; };
return MENU_NORMAL; }
else if (mode==MENU_EXECUTE) {
Deletesorteddata(&(bookmark.sorted),index,0);
return MENU_REDRAW; };
return MENU_ABSENT;
};
// Plugin menu that will appear in the Disassembler pane of CPU window.
static t_menu disasmmenu[] = {
// Menu items that set new bookmarks. Note that their names will be created
// dynamically by Msetmark().
{ L"",
L"Set new bookmark at selection",
KK_DIRECT|KK_CTRL|'0', Msetmark, NULL, 0 },
{ L"",
L"Set new bookmark at selection",
KK_DIRECT|KK_CTRL|'1', Msetmark, NULL, 1 },
{ L"",
L"Set new bookmark at selection",
KK_DIRECT|KK_CTRL|'2', Msetmark, NULL, 2 },
{ L"",
L"Set new bookmark at selection",
KK_DIRECT|KK_CTRL|'3', Msetmark, NULL, 3 },
{ L"",
L"Set new bookmark at selection",
KK_DIRECT|KK_CTRL|'4', Msetmark, NULL, 4 },
{ L"",
L"Set new bookmark at selection",
KK_DIRECT|KK_CTRL|'5', Msetmark, NULL, 5 },
{ L"",
L"Set new bookmark at selection",
KK_DIRECT|KK_CTRL|'6', Msetmark, NULL, 6 },
{ L"",
L"Set new bookmark at selection",
KK_DIRECT|KK_CTRL|'7', Msetmark, NULL, 7 },
{ L"",
L"Set new bookmark at selection",
KK_DIRECT|KK_CTRL|'8', Msetmark, NULL, 8 },
{ L"",
L"Set new bookmark at selection",
KK_DIRECT|KK_CTRL|'9', Msetmark, NULL, 9 },
// Menu items that follow existing bookmarks. Their names will be created
// dynamically by Mfollowmark(). Note separator line introduced by the first
// item.
{ L"|",
L"Follow bookmark",
KK_DIRECT|KK_ALT|'0', Mfollowmark, NULL, 0 },
{ L"",
L"Follow bookmark",
KK_DIRECT|KK_ALT|'1', Mfollowmark, NULL, 1 },
{ L"",
L"Follow bookmark",
KK_DIRECT|KK_ALT|'2', Mfollowmark, NULL, 2 },
{ L"",
L"Follow bookmark",
KK_DIRECT|KK_ALT|'3', Mfollowmark, NULL, 3 },
{ L"",
L"Follow bookmark",
KK_DIRECT|KK_ALT|'4', Mfollowmark, NULL, 4 },
{ L"",
L"Follow bookmark",
KK_DIRECT|KK_ALT|'5', Mfollowmark, NULL, 5 },
{ L"",
L"Follow bookmark",
KK_DIRECT|KK_ALT|'6', Mfollowmark, NULL, 6 },
{ L"",
L"Follow bookmark",
KK_DIRECT|KK_ALT|'7', Mfollowmark, NULL, 7 },
{ L"",
L"Follow bookmark",
KK_DIRECT|KK_ALT|'8', Mfollowmark, NULL, 8 },
{ L"",
L"Follow bookmark",
KK_DIRECT|KK_ALT|'9', Mfollowmark, NULL, 9 },
// Menu items that delete existing bookmarks.
{ L"|",
L"Delete bookmark",
KK_DIRECT|KK_CTRL|KK_ALT|'0', Mdeletemark, NULL, 0 },
{ L"",
L"Delete bookmark",
KK_DIRECT|KK_CTRL|KK_ALT|'1', Mdeletemark, NULL, 1 },
{ L"",
L"Delete bookmark",
KK_DIRECT|KK_CTRL|KK_ALT|'2', Mdeletemark, NULL, 2 },
{ L"",
L"Delete bookmark",
KK_DIRECT|KK_CTRL|KK_ALT|'3', Mdeletemark, NULL, 3 },
{ L"",
L"Delete bookmark",
KK_DIRECT|KK_CTRL|KK_ALT|'4', Mdeletemark, NULL, 4 },
{ L"",
L"Delete bookmark",
KK_DIRECT|KK_CTRL|KK_ALT|'5', Mdeletemark, NULL, 5 },
{ L"",
L"Delete bookmark",
KK_DIRECT|KK_CTRL|KK_ALT|'6', Mdeletemark, NULL, 6 },
{ L"",
L"Delete bookmark",
KK_DIRECT|KK_CTRL|KK_ALT|'7', Mdeletemark, NULL, 7 },
{ L"",
L"Delete bookmark",
KK_DIRECT|KK_CTRL|KK_ALT|'8', Mdeletemark, NULL, 8 },
{ L"",
L"Delete bookmark",
KK_DIRECT|KK_CTRL|KK_ALT|'9', Mdeletemark, NULL, 9 },
// End of menu.
{ NULL, NULL, K_NONE, NULL, NULL, 0 }
};
// Adds items either to main OllyDbg menu (type=PWM_MAIN) or to popup menu in
// one of the standard OllyDbg windows, like PWM_DISASM or PWM_MEMORY. 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 * _export cdecl ODBG2_Pluginmenu(wchar_t *type) {
if (wcscmp(type,PWM_MAIN)==0)
// Main menu.
return mainmenu;
else if (wcscmp(type,PWM_DISASM)==0)
// Disassembler pane of CPU window.
return disasmmenu;
return NULL; // No menu
};
////////////////////////////////////////////////////////////////////////////////
/////////////////////////////// DUMP WINDOW HOOK ///////////////////////////////
// Dump windows display contents of memory or file as bytes, characters,
// integers, floats or disassembled commands. Plugins have the option to modify
// the contents of the dump windows. If ODBG2_Plugindump() is present and some
// dump window is being redrawn, this function is called first with column=
// DF_FILLCACHE, addr set to the address of the first visible element in the
// dump window and n to the estimated total size of the data displayed in the
// window (n may be significantly higher than real data size for disassembly).
// If plugin returns 0, there are no elements that will be modified by plugin
// and it will receive no other calls. If necessary, plugin may cache some data
// necessary later. OllyDbg guarantees that there are no calls to
// ODBG2_Plugindump() from other dump windows till the final call with
// DF_FREECACHE.
// When OllyDbg draws table, there is one call for each table cell (line/column
// pair). Parameters s (UNICODE), mask (DRAW_xxx) and select (extended DRAW_xxx
// set) contain description of the generated contents of length n. Plugin may
// modify it and return corrected length, or just return the original length.
// When table is completed, ODBG2_Plugindump() receives final call with
// column=DF_FREECACHE. This is the time to free resources allocated on
// DF_FILLCACHE. Returned value is ignored.
// Use this feature only if absolutely necessary, because it may strongly
// impair the responsiveness of the OllyDbg. Always make it switchable with
// default set to OFF!
extc int _export cdecl ODBG2_Plugindump(t_dump *pd,
wchar_t *s,uchar *mask,int n,int *select,ulong addr,int column) {
int i,j;
wchar_t w[TEXTLEN];
t_bookmark *pmark;
if (column==DF_FILLCACHE) {
// Check whether this feature is requested.
if (showindisasm==0)
return 0; // Turned off
// Check whether it's Disassembler pane of the CPU window.
if (pd==NULL || (pd->menutype & DMT_CPUMASK)!=DMT_CPUDASM)
return 0; // Not a Disassembler
// Just for the sake, assure that bookmarks apply: not a file dump, not a
// backup display
if (pd->filecopy!=NULL ||
(pd->dumptype & DU_TYPEMASK)!=DU_DISASM ||
(pd->dumptype & DU_BACKUP)!=0)
return 0; // Invalid dump type
// Check that there are bookmarks in the supplied memory range.
for (i=0; i<bookmark.sorted.n; i++) {
pmark=(t_bookmark *)Getsortedbyindex(&(bookmark.sorted),i);
if (pmark==NULL)
continue; // Must not happen!
if (pmark->addr>=addr && pmark->addr<addr+n)
return 1; // Bookmark to display
;
};
return 0; } // No bookmarks to display
else if (column==2) {
// Check whether there is a bookmark. Note that there may be several marks
// on the same address!
w[0]=L'['; j=1;
for (i=0; i<bookmark.sorted.n; i++) {
pmark=(t_bookmark *)Getsortedbyindex(&(bookmark.sorted),i);
if (pmark==NULL)
continue; // Must not happen!
if (pmark->addr!=addr)
continue;
if (j>1)
w[j++]=L','; // Comma between bookmarks
j+=swprintf(w+j,L"%i",pmark->index);
};
if (j==1)
return n; // No bookmarks on address
j+=StrcopyW(w+j,TEXTLEN-j,L"] ");
// Disassebly column is always drawn by mask (DRAW_MASK bit set). But this
// is a sample plugin and I show here what to do if this is not the case.
if ((*select & DRAW_MASK)==0) {
memset(mask,DRAW_NORMAL,n);
*select|=DRAW_MASK; };
// Skip graphical symbols (loop brackets).
for (i=0; i<n; i++) {
if ((mask[i] & DRAW_GRAPH)==0) break; };
// Insert text.
n+=j; if (n>TEXTLEN) n=TEXTLEN;
if (n>i+j) {
memmove(s+i+j,s+i,(n-i-j)*sizeof(wchar_t));
memmove(mask+i+j,mask+i,n-i-j); };
memcpy(s+i,w,j*sizeof(wchar_t));
memset(mask+i,DRAW_EIP,j-1);
mask[i+j-1]=DRAW_NORMAL; }
else if (column==DF_FREECACHE) {
// We have allocated no resources, so we have nothing to do here.
};
return n;
};
////////////////////////////////////////////////////////////////////////////////
//////////////////////////// PLUGIN INITIALIZATION /////////////////////////////
// If item name has form >STANDARD, >FULLCOPY or >APPEARANCE, this item is a
// forwarder to the standard OllyDbg table menu or its part. Use forwarders
// only in windows created by plugin, never in menus that plug into the native
// OllyDbg windows.
static t_menu bookmarkmenu[] = { // Menu of the bookmark window
{ L"|>STANDARD",
L"", // Forwarder to standard menus
K_NONE, NULL, NULL, 0
}
};
// 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 for initializations 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!
extc int _export 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 _export cdecl ODBG2_Plugininit(void) {
int restore;
// Initialize bookmark storage. Data contains no resources, so destructor is
// not necessary. (Destructor is called each time data item is removed from
// the sorted data). I add it pro forma, just to remind you of this option.
if (Createsorteddata(
&(bookmark.sorted), // Descriptor of sorted data
sizeof(t_bookmark), // Size of single data item
10, // Initial number of allocated items
(SORTFUNC *)Bookmarksortfunc, // Sorting function
(DESTFUNC *)Bookmarkdestfunc, // Data destructor
0)!=0) // Simple data, no special options
return -1;
// Initialize bookmark table. OllyDbg uses table name to save the appearance
// to the main initialization file. Keep this name unique, or else.
StrcopyW(bookmark.name,SHORTNAME,PLUGINNAME);
bookmark.mode=TABLE_SAVEALL; // Save complete appearance
bookmark.bar.visible=1; // By default, bar is visible
bookmark.bar.name[0]=L"Bookmark";
bookmark.bar.expl[0]=L"Bookmark index";
bookmark.bar.mode[0]=BAR_SORT;
bookmark.bar.defdx[0]=9;
bookmark.bar.name[1]=L"Address";
bookmark.bar.expl[1]=L"Bookmark address";
bookmark.bar.mode[1]=BAR_SORT;
bookmark.bar.defdx[1]=9;
bookmark.bar.name[2]=L"Disassembly";
bookmark.bar.expl[2]=L"Command at the bookmark address";
bookmark.bar.mode[2]=BAR_FLAT;
bookmark.bar.defdx[2]=24;
bookmark.bar.name[3]=L"Comments";
bookmark.bar.expl[3]=L"Comments";
bookmark.bar.mode[3]=BAR_FLAT;
bookmark.bar.defdx[3]=256;
bookmark.bar.nbar=4;
bookmark.tabfunc=Bookmarkfunc;
bookmark.custommode=0;
bookmark.customdata=NULL;
bookmark.updatefunc=NULL;
bookmark.drawfunc=(DRAWFUNC *)Bookmarkdraw;
bookmark.tableselfunc=NULL;
bookmark.menu=bookmarkmenu;
// Get initialization data.
showindisasm=0; // Default value
Getfromini(NULL,PLUGINNAME,L"Show bookmarks in Disassembler",L"%i",
&showindisasm);
// 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(&bookmark,0,bookmark.bar.nbar,NULL,L"ICO_P",PLUGINNAME);
;
};
// Report success.
return 0;
};
// Function is called when user opens new or restarts current application.
// Plugin should reset internal variables and data structures to the initial
// state.
extc void _export cdecl ODBG2_Pluginreset(void) {
Deletesorteddatarange(&(bookmark.sorted),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! Attention, don't make any
// unrecoverable actions for the case that some other plugin will decide that
// OllyDbg should continue running.
extc int _export cdecl ODBG2_Pluginclose(void) {
// For automatical restoring of open windows, mark in .ini file whether
// Bookmarks window is still open.
Writetoini(NULL,PLUGINNAME,L"Restore window",L"%i",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 etc.
extc void _export cdecl ODBG2_Plugindestroy(void) {
Destroysorteddata(&(bookmark.sorted));
};
////////////////////////////////////////////////////////////////////////////////
/////////////////////////// EVENTS AND NOTIFICATIONS ///////////////////////////
// If you define ODBG2_Pluginmainloop(), this function will be called each time
// from the main Windows loop in OllyDbg. If there is some (real) debug event
// from the debugged application, debugevent points to it, otherwise it's NULL.
// If fast command emulation is active, it does not receive all (emulated)
// exceptions, use ODBG2_Pluginexception() instead. Do not declare these two
// functions unnecessarily, as this may negatively influence the overall speed!
//
// extc void _export cdecl ODBG2_Pluginmainloop(DEBUG_EVENT *debugevent) {
// };
//
// extc int _export cdecl ODBG2_Pluginexception(t_run *prun,const t_disasm *da,
// t_thread *pthr,t_reg *preg,wchar_t *message) {
// };
//
// Optional entry, called each time OllyDbg analyses some module and analysis
// is finished. Plugin can make additional analysis steps. Debugged application
// is paused for the time of processing. Bookmark plugin, of course, does not
// analyse code. If you don't need this feature, remove ODBG2_Pluginanalyse()
// from the plugin code.
//
// extc void _export cdecl ODBG2_Pluginanalyse(t_module *pmod) {
// };
// Optional entry, notifies plugin on relatively infrequent events.
extc void _export cdecl ODBG2_Pluginnotify(int code,void *data,
ulong parm1,ulong parm2) {
int i;
t_bookmark *pmark;
t_module *pmod;
switch (code) {
case PN_ENDMOD: // Module is removed from the memory
// Module is unloaded from the memory. This notification comes after
// data was saved to the .udd file. All we need is to remove bookmarks
// from the table.
pmod=(t_module *)data;
if (pmod==NULL)
break; // Must not happen!
// As many scattered bookmarks may be deleted at once, I first mark them
// all as confirmed ...
Confirmsorteddata(&(bookmark.sorted),1);
// ... then remove confirmation flag from the bookmarks in the range ...
for (i=0; i<bookmark.sorted.n; i++) {
pmark=(t_bookmark *)Getsortedbyindex(&(bookmark.sorted),i);
if (pmark==NULL)
continue; // Must not happen!
if (pmark->addr>=pmod->base && pmark->addr<pmod->base+pmod->size)
pmark->type&=~TY_CONFIRMED;
;
};
// ... and finally delete all non-confirmed bookmarks. Note that
// Deletenonconfirmedsorteddata() acts on sorted data and will not
// redraw the table window.
if (Deletenonconfirmedsorteddata(&(bookmark.sorted))!=0 &&
bookmark.hw!=NULL)
InvalidateRect(bookmark.hw,NULL,FALSE);
break;
default: break; // No action necessary
};
};
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////// .UDD FILES //////////////////////////////////
// For each plugin, OllyDbg automatically creates file-unique block of .udd
// record types. They are indexed by plugin names. If there are two plugins
// with the same name, restored records may be sent to the wrong plugin.
// Therefore it is very important that plugin names are unique.
#define BOOKMARKTAG 0x0001 // Tag of bookmark record
typedef struct t_uddmark { // Bookmark in .udd file
int index; // Bookmark index
ulong offset; // Offset from module base
} t_uddmark;
// 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-related 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 ODBG2_Pluginsaveudd(t_uddsave *psave,
t_module *pmod,int ismainmodule) {
int i;
t_bookmark *pmark;
t_uddmark um;
for (i=0; i<bookmark.sorted.n; i++) {
pmark=(t_bookmark *)Getsortedbyindex(&(bookmark.sorted),i);
if (pmark==NULL)
continue; // Must not happen!
if (pmark->addr>=pmod->base && pmark->addr<pmod->base+pmod->size) {
// Bookmarks are saved with corresponding modules. If bookmark was set
// outside of any module, it will be lost.
um.index=pmark->index;
um.offset=pmark->addr-pmod->base;
Pluginsaverecord(psave,BOOKMARKTAG,sizeof(um),&um);
};
};
};
// OllyDbg restores data from .udd file and has encountered record belonging
// to our plugin. 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 void _export cdecl ODBG2_Pluginuddrecord(t_module *pmod,int ismainmodule,
ulong tag,ulong size,void *data) {
t_bookmark bmk;
t_uddmark *pum;
if (pmod==NULL || data==NULL)
return; // Must not happen!
if (size!=sizeof(t_uddmark))
return; // Invalid record size
if (tag!=BOOKMARKTAG)
return; // Invalid tag
pum=(t_uddmark *)data;
if (pum->index<0 || pum->index>9)
return; // Corrupted data
bmk.index=pum->index;
bmk.size=1;
bmk.type=0;
bmk.addr=pmod->base+pum->offset;
Addsorteddata(&(bookmark.sorted),&bmk);
};
See also: