int 3
instruction in those places where we would like to stop the execution of the program. When the processor hits this instruction, it will generate a corresponding interrupt, which the OS kernel will process and turn, for example, under Linux into a SIGTRAP signal. A free-running program will simply crash when it encounters int 3
, but the debugger will catch this signal, stop the program and allow it to investigate its state.int 3
is encoded with one byte ( 0xCC
) and not two, like the rest of the instructions for generating a program interrupt int X
( 0xCD imm8
) - otherwise int 3
would not be suitable for substituting single-byte instructions.nop
( 0x90
) instruction.void SetAccessBreak(void* addr);
dr0
debugging registers dr0
, dr1
, dr2
, dr3
and their uncle Chernomor dr7
, containing control flags.dr0
- dr3
registers dr0
load the address to be monitored, and in dr7
set the appropriate checkboxes to determine whether the corresponding register breakpoint is activated or not, it monitors the event (execution / read / read-or-write at this address), data size (1 byte, 2 bytes, 4 bytes, 8 bytes). In order not to waste space on vague verbal explanations of flag encoding rules, I will immediately cite two utility functions: MakeFlags
, which encodes flags for a given debug register in the format used in dr7
, and MakeMask
, which for a given register calculates a bit mask covering all flags related to this register (a similar mask is needed if we want to reset all flags).enum DebugRegister { <br/>
kDR0 = 0 ,<br/>
kDR1 = 2 ,<br/>
kDR2 = 4 ,<br/>
kDR3 = 6 <br/>
} ; <br/>
<br/>
enum BreakState { <br/>
kDisabled = 0 , // disabled - 00 <br/>
kEnabledLocally = 1 , // task local - 01 <br/>
kEnabledGlobally = 2 , // global - 10 <br/>
kBreakStateMask = 3 // mask 11 <br/>
} ; <br/>
<br/>
enum Condition { <br/>
kWhenExecuted = 0 , // on execution - 00 <br/>
kWhenWritten = 1 , // on write - 01 <br/>
kWhenWrittenOrReaden = 3 , // on read or write - 11 <br/>
kConditionMask = 3 // mask 11 <br/>
} ; <br/>
<br/>
enum Size { <br/>
kByte = 0 , // 1 byte - 00 <br/>
kHalfWord = 1 , // 2 bytes - 01 <br/>
kWord = 3 , // 4 bytes - 11 <br/>
kDoubleWord = 2 , // 5 bytes - 10 <br/>
kSizeMask = 3 // mask 11 <br/>
} ; <br/>
<br/>
<br/>
uint32_t MakeFlags ( DebugRegister reg, BreakState state, Condition cond, Size size ) { <br/>
return ( state | cond << 16 | size << 24 ) << reg ; <br/>
} <br/>
<br/>
<br/>
uint32_t MakeMask ( DebugRegister reg ) { <br/>
return MakeFlags ( reg, kBreakStateMask, kConditionMask, kSizeMask ) ; <br/>
} <br/>
SetAccessBreak
with a simple inline assembler:bool SetAccessBreak ( void * addr,<br/>
DebugRegister reg,<br/>
Condition cond,<br/>
Size size ) { <br/>
const uint32_t control = MakeFlags ( reg, kEnabledLocally, cond, size ) ; <br/>
__asm__ ( "movl %0, %%dr0 \n " <br/>
"movl %1, %%dr7 \n " : : "r" ( addr ) , "r" ( control ) : ) ; <br/>
}
thread_get_state
/ thread_set_state
. Having got access to the necessary registers through them, we easily implement SetAccessBreak
:bool SetAccessBreak ( pthread_t target_thread,<br/>
void * addr,<br/>
DebugRegister reg,<br/>
Condition cond,<br/>
Size size ) { <br/>
x86_debug_state dr ; <br/>
mach_msg_type_number_t dr_count = x86_DEBUG_STATE_COUNT ; <br/>
<br/>
// POSIX MACH . <br/>
mach_port_t target_mach_thread = pthread_mach_thread_np ( target_thread ) ; <br/>
<br/>
// . <br/>
kern_return_t rc = thread_get_state ( target_mach_thread,<br/>
x86_DEBUG_STATE,<br/>
reinterpret_cast < thread_state_t > ( & dr ) ,<br/>
& dr_count ) ; <br/>
<br/>
// <br/>
if ( rc ! = KERN_SUCCESS ) return false ; <br/>
<br/>
// , . <br/>
switch ( reg ) { <br/>
case kDR0 : dr. uds . ds32 .__dr0 = reinterpret_cast < unsigned int > ( addr ) ; break ; <br/>
case kDR1 : dr. uds . ds32 .__dr1 = reinterpret_cast < unsigned int > ( addr ) ; break ; <br/>
case kDR2 : dr. uds . ds32 .__dr2 = reinterpret_cast < unsigned int > ( addr ) ; break ; <br/>
case kDR3 : dr. uds . ds32 .__dr3 = reinterpret_cast < unsigned int > ( addr ) ; break ; <br/>
} <br/>
<br/>
// . <br/>
dr. uds . ds32 .__dr7 & = ~MakeMask ( reg ) ; <br/>
<br/>
// . <br/>
dr. uds . ds32 .__dr7 | = MakeFlags ( reg, kEnabledLocally, cond, size ) ; <br/>
<br/>
// . <br/>
rc = thread_set_state ( target_mach_thread,<br/>
x86_DEBUG_STATE,<br/>
reinterpret_cast < thread_state_t > ( & dr ) ,<br/>
dr_count ) ; <br/>
<br/>
// . <br/>
if ( rc ! = KERN_SUCCESS ) return false ; <br/>
<br/>
// . <br/>
return true ; <br/>
}
static int16_t foo = 0 ; <br/>
static int32_t bar = 0 ; <br/>
<br/>
int main ( int argc, char * argv [ ] ) { <br/>
foo = 1 ; <br/>
bar = 1 ; <br/>
SetAccessBreak ( pthread_self ( ) , & bar, kDR0, kWhenWritten, kWord ) ; <br/>
foo = 2 ; <br/>
bar = 2 ; <br/>
SetAccessBreak ( pthread_self ( ) , & foo, kDR0, kWhenWritten, kHalfWord ) ; <br/>
foo = 3 ; <br/>
bar = 3 ; <br/>
return 0 ; <br/>
} <br/>
(gdb) r
Starting program: /Users/mraleph/test
Reading symbols for shared libraries +++. done
Program received signal SIGTRAP, Trace/breakpoint trap.
main (argc=1, argv=0xbffff9f8) at test.cc:107
106 bar = 2; <= triggered SIGTRAP -- mr.aleph
107 SetAccessBreak(pthread_self(), &foo, kDR0, kWhenWritten, kHalfWord);
(gdb) c
Continuing.
Program received signal SIGTRAP, Trace/breakpoint trap.
main (argc=1, argv=0xbffff9f8) at test.cc:109
108 foo = 3; <= triggered SIGTRAP -- mr.aleph
109 bar = 3;
(gdb) c
Continuing.
Program exited normally.
Source: https://habr.com/ru/post/103073/
All Articles