Disassembler - Debugger - Breakpoints

Breakpoints are used to stop the target process at certain points or when the target process reads, writes, or accesses certain addresses. Breakpoints that stop on read, write, or access are commonly called watchpoints.

 

Adding Breakpoints

Right-click in the Disassembler on the address at which you want to place a breakpoint and select the Breakpoints menu item, then select the type of breakpoint you want to add.

To add a breakpoint by manually setting its parameters, select the Add Breakpoint Here menu item at the bottom.

 

Breakpoints are added to the Breakpoints tab in the Helper. Here you can toggle breakpoints on and off, modify them, remove them, save them, and load more breakpoints from files.

This tab shows the address of the breakpoint, its name, and the functions it will call when hit, if any.

 
Key

: The breakpoint is active.
: The breakpoint is inactive. No action is performed when the breakpoint is hit.
: The breakpoint is active. This is a hardware breakpoint.
: The breakpoint is inactive. This is a hardware breakpoint.

 

Hardware vs. Software

There are several important differences between hardware and software breakpoints. It can be very helpful to use the correct one for the correct situation. The following table illustrates the differences and shows when you would want to use each type.

Type

Pros

Cons Suggestion
Hardware

Access and write breaks are very fast.

RAM is not modified; they are harder to detect.

Can only have 4 per thread maximum.

The break happens on the next instruction, instead of on the instruction that caused the break.

Access/write breakpoints can only span one address (software read/write breakpoints can cover large ranges).

Use hardware breakpoints for access/write operations unless you need a breakpoint to cover a large range.
Software

Any number may be set.

Execution breaks are very fast.

Read/write breakpoints can cover a large range.

Read/write breakpoints are very slow; the game typically can not run at a reasonable frame rate while these are set.

RAM is modified, so detactability is higher.

Always use software breakpoints when setting executable breakpoints unless you absolutely must avoid modifying the game RAM.

 

Creating/Modifying Breakpoints

To create a new breakpoint, right-click a line in the Disassembler and select the Breakpoints/Add Breakpoint Here menu item. To modify an existing one, right-click it in the Breakpoints tab in the Helper and select the Modify menu item from the popup.

Setting

Description

Name

The name or description of the breakpoint. Required.

Address The address of the breakpoint.
Active Whether or not the breakpoint is active. If inactive, it performs no operations when hit, but it is not removed from the target process.
Break On Execute The target process will be stopped when the specified address is executed.
Read The target process will be stopped when any address from Address to (Address + Coverage) is read.
Write The target process will be stopped when any address from Address to (Address + Coverage) is written, except when hardware breakpoints are used, in which case the break happens only on Address.
Access The target process will be stopped when the exact address in Address is read or written.
Hardware Determines if the breakpoint should be hardware or not. When enabled, the Read button becomes Access.
Coverage While using software read or write breakpoints, this specifies the range of addresses to include in the breakpoint. If any address from Address to (Address + Coverage) is read or written, a break will occur.
Prolog Function The first function to call when the breakpoint is hit. The function is called before the breakpoint has been unset and before the EIP has been adjusted.
Callback Function The second function to call when the breakpoint is hit. The function is called after the breakpoint has been unset; the original instruction has been written back to RAM (if software executable breakpoint), and the EIP has been adjusted.
Epilog Function The third function to call when the breakpoint is hit. The function is called after the break has ended and the target process has been resumed. This is used the least among the 3, direct access to the thread context is not supplied to breakpoint handlers.
Parm A numeric parameter to pass to its respective function. Among the built-in functions, only the Sys Beep and Script Function use this, but all custom functions may also use this if desired.
Condition An optional condition to apply to the breakpoint. When using a condition, the breakpoint will only call the Prolog, Callback, and Epilog functions when the condition evaluates to a non-zero value.
Use Condition Determines whether or nor the applied condition is to be used. If not, the breakpoint acts as if it has no condition and always breaks.

Press OK to add the breakpoint.

 

Built-In Breakpoint Functions

  • Do Nothing: Performs no action. Default for Prolog and Epilog. Parm is not used.
  • Sys Beep: Beeps for Parm milliseconds.
  • Single Step: Shows the Disassembler and allows the user to single step through the code. Default for Callback. Parm is not used.
  • Set CF: If Parm is 0, this sets the CF flag to 0, otherwise it sets it to 1.
  • Set PF: If Parm is 0, this sets the PF flag to 0, otherwise it sets it to 1.
  • Set AF: If Parm is 0, this sets the AF flag to 0, otherwise it sets it to 1.
  • Set ZF: If Parm is 0, this sets the ZF flag to 0, otherwise it sets it to 1.
  • Set SF: If Parm is 0, this sets the SF flag to 0, otherwise it sets it to 1.
  • Set OF: If Parm is 0, this sets the OF flag to 0, otherwise it sets it to 1.
  • Script Function: Calls a script function where Parm is used to determine which script function to call. The function called will be named On_BP_Parm() where Parm in the function name is the number specified in Parm in the dialog box.
  • Cur Proc Script Function: Same as above, except that the function called is in the format On_BP_CURPROC_EXE_Parm(), where CURPROC_EXE is the name of the current process (in all caps, with special characters replaced by underscores), and Parm is again the numeric value specified in the dialog. Because the name of the current process is in the script function, the script function will only be called for that process.
  • Custom Function…: Not actually a function itself. Select this to open the Add Function dialog.

 

Script Function

You can call your own script functions when a breakpoint is hit. This is usually preferred over creating breakpoint-handling DLL’s because of the ease in writing the functions and calling them through breakpoints.

Script Events offer the same functionality as most of the 10 optional functions listed above. Use On_BeginDebug in place of the DLL function MHS_StartUp (or StartUp), and On_EndDebug in place of the DLL function MHS_CleanUp (or CleanUp). Use On_OpenProcess in place of MHS_ProcessAttach and On_CloseProcess in place of MHS_ProcessDetach, but note however that these functions are called regardless of whether the Debugger is active or not.

To call a script function when a breakpoint is hit, name the function On_BP_#, where # is any decimal number. Then, in the Add Breakpoint dialog, set the Prolog, Callback, or Epilog function Parm to the number used in the script function name.


For example, if your script function is named On_BP_34, you can assign this script function to be called as the Callback Function of a breakpoint by setting the Callback Function to Script Function and setting its Parm to 34.

Alternatively, you can name the function On_BP_*_#, where * is the name of the process (with every letter capitalized and every special character changed to an underscore) and # is any decimal number. Then select the Cur Proc Script Function function from the list.

This allows you to write per-process breakpoint routines. So, for example, if you wanted to write a script routine that works only in Gamex86.exe, you could write a function named On_BP_GAMEX86_EXE_2, set the Callback Function to Cur Proc Script Function, and set the Parm to 2. Your function will only be called while debugging Gamex86.exe. This allows you to use the same Parm value but call a different function for each different process. If you intend to share your scripts, this method should always be used to ensure your breakpoint script functions do not clash with others’.

To apply the name of the process to your function, capitalize every letter and change all non-alphanumeric characters to underscores. Include the extension. For example, gamex86.exe becomes GAMEX86_EXE, and Super Fun Game!!.exe becomes SUPER_FUN_GAME___EXE.

 

The script function has one of the following prototypes:

VOID On_BP_#( LPVOID lpvAddress, LPPROC_INFO_MHS lpProcInfo );
VOID On_BP_*_#( LPVOID lpvAddress, LPPROC_INFO_MHS lpProcInfo );

This parameters are exactly the same as with DLL breakpoint-handler functions, except that the PROC_INFO_MHS structure is passed as a reference to DLL functions but as a pointer to script functions.

This is an example of a script function used as a breakpoint handler:

INT g_iCount = 0;
VOID On_BP_10( LPVOID lpvAddress, LPPROC_INFO_MHS lpProcInfo ) {
    if ( g_iCount++ % 2 == 0 ) {
        lpProcInfo->pcContext->Eax |= 0x40;
        lpProcInfo->bSetContext = TRUE;
        //PrintF( "%X", lpProcInfo->pcContext->Eax );
    }
    else {
        lpProcInfo->pcContext->Eax |= 0x80;
        lpProcInfo->bSetContext = TRUE;
    }
}

As shown in this snippet, when changing registers via lpProcInfo->pcContext, set lpProcInfo->bSetContext to TRUE rather than calling SetThreadContext() directly. When lpProcInfo->bSetContext is set to TRUE, MHS will call SetThreadContext() itself after the function returns. This is slightly faster than calling SetThreadContext() from within the script.

NOTE: If possible, the breakpoint will put a lock on the script system. This lock allows the breakpoint to maintain a ready-to-go script thread that can directly execute the script function. Without locking the script system, a script thread must be allocated and initialized, and then the function address must be found from a table for the script thread to execute.
The lock is made when the script function already exists within the compiled scripts when the breakpoint is created. This is normally the case.
If a lock is made, the script function can be called roughly 10 times faster than without a lock. This improves the performance of the breakpoint—and thusly the Debugger—substantially.
However when the script system is locked it can not be recompiled. In order to recompile the scripts, the breakpoint must lose its lock on the script system. This can be achieved by setting the breakpoint function to Do Nothing temporarily. After recompiling the scripts you can set the breakpoint function back to Script Function. You will not need to modify the breakpoint Parm values.
The lock has no other effects on the script system.

 

Conditions

Conditions allow you to decide under what circumstances the breakpoint will be “hit”. If the conditional statement evaluates to 0, the target process resumes without the breakpoint performing any actions. If the conditional statement evaluates to any other number, the breakpoint performs normal processing, calling its prolog, callback, and epilog functions.

The conditional statement may include all operators recognized by the Expression Evaluator.

The following lists the registers that may be used:

  • EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI
  • AX, CX, DX, BX, SP, BP, SI, DI
  • AL, CL, DL, BL, AH, CH, DH, BH
  • ES, CS, SS, DS, FS, GS
  • DR0, DR1, DR2, DR3, DR6, DR7
  • ST0, ST1, ST2, ST3, ST4, ST5, ST6, ST7

As well, keyword HC may be used to retreive the hit count of the breakpoint.

Any other non-recognized words are treated as module names, making the expression [game.exe+5334h] valid if the current process is game.exe.

Ensure that the Use Condition check is checked when creating or modifying the breakpoint. This toggle allows you to keep the condition but disable it at times.

Here are examples of valid expressions:

  • eax: Breaks if register EAX is not 0.
  • [EBP+Ch] == 32: Breaks if the value at address EBP+Ch (the 2nd arguement pass to the function) is equal to 32.
  • (HC >= 1 && HC < 100): Breaks after the breakpoint has been hit once but not after the breakpoint has been hit 100 times.

 

Miscellaneous

This section covers the miscellaneous issues regarding breakpoints.

  • Software executable breakpoints modify one byte each of the RAM of the target process. If this is a dire problem, use hardware executable breakpoints instead.
  • Software read/write breakpoints change page protections in the target process. If this is a dire problem, use hardware access/write breakpoints instead.
  • The Debugger does not have to be enabled for breakpoints to be set, but does have to be enabled for breakpoints to be applied. If the Debugger is not active, no RAM is modified and no page properties are changed.
  • Breakpoints are organized into layers. All breakpoints created by the user are loaded to the User Breakpoint Layer. Breakpoints created by the Auto-Hack feature are loaded to the Auto-Hack Layer. Only one breakpoint layer may be active at once. This means user breakpoints will not operate while the Auto-Hack feature is active. To enable user breakpoints again, stop the Auto-Hack session by opening the Helper window Auto-Hack tab and pressing Stop or Clear All.
  • The current version of MHS does not offer 100% backwards compability with MHPB/MHBPL breakpoint files. Specifically, hardware read breakpoints no longer exist. All other features are compatible.
  • While software read/write breakpoints are set, some features of MHS may not function 100% correctly. Specifically, the RAM Watcher and Hex Editor may flicker and some values in the main list may become “Unobtainable”. This behavior is normal; it is caused by the fact that software read/write breakpoints change the page properties in the target process to inaccessible. Inaccessible RAM appears red in the Hex Editor, N/A in the RAM Watcher, and Unobtainable in the main list. Flickering appears when the page of RAM is read or written, causing the page properties to be temporarily set back to normal and then set back to inaccessible.
  • If the target process is a full-screen game, do not set breakpoints that cause single-stepping. This is a common problem shared between all ring-3 debuggers. When the breakpoint is hit the game will be completely paused and will be unable to minimize (because minimizing would require it to execute code, which, being completely paused, will not happen). If a breakpoint, or anything else, pauses the game, you will be forced to reboot your computer.
Copyright © 2006 Shawn (L. Spiro) Wilcoxen