Skip to content

Commit

Permalink
added hw_breakpoint.h
Browse files Browse the repository at this point in the history
  • Loading branch information
wc-duck committed Feb 23, 2017
1 parent 4809b51 commit 101f5dc
Show file tree
Hide file tree
Showing 4 changed files with 412 additions and 0 deletions.
2 changes: 2 additions & 0 deletions bam.lua
Expand Up @@ -120,6 +120,7 @@ local debugger_obj = Compile( settings, 'src/debugger.cpp' )
local callstack_obj = Compile( settings, 'src/callstack.cpp' )
local assert_obj = Compile( settings, 'src/assert.cpp' )
local fpe_ctrl_obj = Compile( settings, 'src/fpe_ctrl.cpp' )
local hw_breok_obj = Compile( settings, 'src/hw_breakpoint.cpp' )
Compile( settings, 'test/test_static_assert.c' )
Compile( settings, 'test/test_static_assert_cpp.cpp' )
Expand All @@ -129,3 +130,4 @@ Link( settings, 'test_callstack', callstack_obj, Compile( settings, 'test/te
Link( settings, 'test_callstack_cpp', callstack_obj, Compile( settings, 'test/test_callstack_cpp.cpp' ) )
Link( settings, 'test_assert', assert_obj, Compile( settings, 'test/test_assert.cpp' ) )
Link( settings, 'test_fpe_ctrl', fpe_ctrl_obj, Compile( settings, 'test/test_fpe_ctrl.cpp' ) )
Link( settings, 'test_hw_breakpoint', hw_breok_obj, Compile( settings, 'test/test_hw_breakpoint.c' ) )
97 changes: 97 additions & 0 deletions include/dbgtools/hw_breakpoint.h
@@ -0,0 +1,97 @@
/*
dbgtools - platform independent wrapping of "nice to have" debug functions.
version 0.1, october, 2013
https://github.com/wc-duck/dbgtools
Copyright (C) 2013- Fredrik Kihlander
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
Fredrik Kihlander
*/

#ifndef DBGTOOLS_HWBREAKPOINT_INCLUDED
#define DBGTOOLS_HWBREAKPOINT_INCLUDED

/**
* Breakpoint type used in hw_breakpoint_set()
*/
enum hw_breakpoint_type
{
HW_BREAKPOINT_READ,
HW_BREAKPOINT_WRITE,
HW_BREAKPOINT_READWRITE,
};

/**
* Error codes from hw_breakpoint_set()
*/
#define HW_BREAKPOINT_ERROR_INVALID_ARG (-1) // invalid argument was passed to hw_breakpoint_set such as a bad size.
#define HW_BREAKPOINT_ERROR_OUT_OF_SLOTS (-2) // to many hardware breakpoints are already set.
#define HW_BREAKPOINT_ERROR_UNKNOWN (-3) // unknown error! ( please report bug-report or bug-fix :) )
#define HW_BREAKPOINT_ERROR_NOT_SUPPORTED (-4) // hardware breakpoints aren't supported on this platform.

#ifdef __cplusplus
extern "C" {
#endif // __cplusplus

/**
* Set a hardware breakpoint at the specified memory-address that will trigger a SIGSEGV on access.
* @param address to break on.
* @param size of bytes to monitor, valid values are 1, 2, 4, 8
* @param type type of hw-breakpoint.
* @return value >= 0 on success, otherwise errorcode.
*/
int hw_breakpoint_set( void* address, unsigned int size, enum hw_breakpoint_type type );

/**
* Clear a previously set hardware breakpoint set with hw_breakpoint_set().
* @param hwbp value returned by hw_breakpoint_set().
*/
void hw_breakpoint_clear( int hwbp );

#ifdef __cplusplus
}
#endif // __cplusplus

#if defined(__cplusplus)

/**
* Utility class to handle scoped hw_breakpoint_set/hw_breakpoint_clear
*/
class hw_breakpoint_enable_scope
{
int hwbp;

hw_breakpoint_enable_scope( void* address, unsigned int size, enum hw_breakpoint_type type )
: hwbp( hw_breakpoint_set( address, size, type ) )
{
}

~hw_breakpoint_enable_scope()
{
if( hwbp >= 0 )
hw_breakpoint_clear( hwbp );
}
};

#endif // defined(__cplusplus)

#endif // DBGTOOLS_HWBREAKPOINT_INCLUDED

235 changes: 235 additions & 0 deletions src/hw_breakpoint.cpp
@@ -0,0 +1,235 @@
/*
dbgtools - platform independent wrapping of "nice to have" debug functions.
https://github.com/wc-duck/dbgtools
version 0.1, october, 2013
Copyright (C) 2013- Fredrik Kihlander
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
Fredrik Kihlander
*/

/**
* Based on:
* linux: https://gist.github.com/jld/5d292c2c48eb07980562
* windows: http://www.morearty.com/code/breakpoint
*/

#include <dbgtools/hw_breakpoint.h>

#if defined(__linux)

#include <fcntl.h>
#include <string.h>
#include <linux/hw_breakpoint.h>
#include <linux/perf_event.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <errno.h>
#include <sys/syscall.h>
#include <unistd.h>

extern "C" int hw_breakpoint_set( void* address, unsigned int size, enum hw_breakpoint_type type )
{
struct perf_event_attr attr;
struct f_owner_ex owner;
int fd;

memset(&attr, 0, sizeof(attr));
attr.size = sizeof(attr);
attr.type = PERF_TYPE_BREAKPOINT;

switch( type )
{
case HW_BREAKPOINT_READ: attr.bp_type = HW_BREAKPOINT_R; break;
case HW_BREAKPOINT_WRITE: attr.bp_type = HW_BREAKPOINT_W; break;
case HW_BREAKPOINT_READWRITE: attr.bp_type = HW_BREAKPOINT_RW; break;
}

attr.bp_addr = (uint64_t)address;

switch( size )
{
case 1:
case 2:
case 4:
case 8:
attr.bp_len = size;
break;
default:
return HW_BREAKPOINT_ERROR_INVALID_ARG;
}

attr.sample_period = 1;
attr.precise_ip = 2; // request synchronous delivery
attr.wakeup_events = 1;

fd = (int)syscall(__NR_perf_event_open, &attr, 0, -1, -1,
#if defined(PERF_FLAG_FD_CLOEXEC)
PERF_FLAG_FD_CLOEXEC
#else
0
#endif
);

if( fd < 0 )
{
if( errno == ENOSPC )
return HW_BREAKPOINT_ERROR_OUT_OF_SLOTS;
perror("perf_event_open");
return HW_BREAKPOINT_ERROR_UNKNOWN;
}

if (fcntl(fd, F_SETSIG, SIGSEGV) < 0)
{
perror("fcntl F_SETSIG");
close(fd);
return HW_BREAKPOINT_ERROR_UNKNOWN;
}

owner.type = F_OWNER_TID;
owner.pid = (pid_t)syscall(__NR_gettid);

if (fcntl(fd, F_SETOWN_EX, &owner) < 0)
{
perror("fcntl F_SETOWN_EX");
close(fd);
return HW_BREAKPOINT_ERROR_UNKNOWN;
}

if (fcntl(fd, F_SETFL, O_ASYNC) < 0)
{
perror("fcntl F_SETFL");
close(fd);
return HW_BREAKPOINT_ERROR_UNKNOWN;
}

return fd;
}

extern "C" void hw_breakpoint_clear( int hwbp )
{
close(hwbp);
}

#elif defined(_MSC_VER)

#include <windows.h>

inline DWORD64 hw_breakpoint_setbits(DWORD64 dw, int lowBit, int bits, int newValue)
{
int mask = (1 << bits) - 1;
return (dw & ~(mask << lowBit)) | (newValue << lowBit);
}

extern "C" int hw_breakpoint_set( void* address, unsigned int size, enum hw_breakpoint_type type )
{
switch( size )
{
case 1: size = 0; break; // 00
case 2: size = 1; break; // 01
case 4: size = 3; break; // 11
case 8: size = 2; break; // 10
break;
default:
return HW_BREAKPOINT_ERROR_INVALID_ARG;
}

CONTEXT cxt;
HANDLE thread = GetCurrentThread();
cxt.ContextFlags = CONTEXT_DEBUG_REGISTERS;

// Read the register values
if( !GetThreadContext(thread, &cxt) )
return HW_BREAKPOINT_ERROR_UNKNOWN;

int reg_index;
for( reg_index = 0; reg_index < 4; ++reg_index )
{
if( ( cxt.Dr7 & ( 1 << (reg_index * 2) ) ) == 0 )
break;
}

switch( reg_index )
{
case 0: cxt.Dr0 = (DWORD) address; break;
case 1: cxt.Dr1 = (DWORD) address; break;
case 2: cxt.Dr2 = (DWORD) address; break;
case 3: cxt.Dr3 = (DWORD) address; break;
default:
return HW_BREAKPOINT_ERROR_OUT_OF_SLOTS;
}

int when;
switch( type )
{
case HW_BREAKPOINT_WRITE: when = 1; break; // 01
case HW_BREAKPOINT_READ: when = 2; break; // 10
case HW_BREAKPOINT_READWRITE: when = 3; break; // 11
}

cxt.Dr7 = hw_breakpoint_setbits(cxt.Dr7, 16 + (reg_index * 4), 2, when);
cxt.Dr7 = hw_breakpoint_setbits(cxt.Dr7, 18 + (reg_index * 4), 2, size);
cxt.Dr7 = hw_breakpoint_setbits(cxt.Dr7, reg_index * 2, 1, 1);

if( !SetThreadContext(thread, &cxt) )
return HW_BREAKPOINT_ERROR_UNKNOWN;
return reg_index;
}

extern "C" void hw_breakpoint_clear( int hwbp )
{
CONTEXT cxt;
HANDLE thread = GetCurrentThread();
cxt.ContextFlags = CONTEXT_DEBUG_REGISTERS;

GetThreadContext(thread, &cxt);
cxt.Dr7 = hw_breakpoint_setbits(cxt.Dr7, hwbp * 2, 1, 0);
SetThreadContext(thread, &cxt);
}

#elif defined(__APPLE__)

extern "C" int hw_breakpoint_set( void* address, unsigned int size, enum hw_breakpoint_type type )
{
// I would love to implement this but I have no access to OSX to test it out, pull-requests are welcome!
// on x86 it should be implementable by something like this:
// http://stackoverflow.com/questions/2604439/how-do-i-write-x86-debug-registers-from-user-space-on-osx
return HW_BREAKPOINT_ERROR_NOT_SUPPORTED;
}

extern "C" void hw_breakpoint_clear( int hwbp )
{
}


#else

extern "C" int hw_breakpoint_set( void* address, unsigned int size, enum hw_breakpoint_type type )
{
return HW_BREAKPOINT_ERROR_NOT_SUPPORTED;
}

extern "C" void hw_breakpoint_clear( int hwbp )
{
}

#endif

0 comments on commit 101f5dc

Please sign in to comment.