 |
AutoHotkey Community Let's help each other out
|
| View previous topic :: View next topic |
| Author |
Message |
Lexikos
Joined: 17 Oct 2006 Posts: 7282 Location: Australia
|
Posted: Mon Oct 13, 2008 12:13 pm Post subject: DebugBIF - debug built-in functions (was DllCallDebugger) |
|
|
DebugBIF( [ func_to_debug, func_to_call, event_info ] ) for AutoHotkey v1.0.48.01-04
Provides an effective way to debug a script's use of built-in functions.
| Code: | ; Requires AutoHotkey v1.0.48 and the appropriate version of LowLevel,
; which can be found here: http://www.autohotkey.com/forum/topic26300.html
/*
Function: DebugBIF
Registers a user-defined function to be called when a built-in function either
sets ErrorLevel to a non-zero value or returns an empty string, as appropriate.
Syntax:
DebugBIF( [ func_to_debug, func_to_call, event_info ] )
Parameters:
func_to_debug - The name of the built-in function to debug.
If omitted or empty, all applicable functions are affected.
func_to_call - The name of a user-defined function to call.
If omitted or empty, DebugBIF_DefaultHandler is used.
event_info - An integer to pass via A_EventInfo.
If omitted or empty, A_EventInfo contains a boolean value
indicating whether ErrorLevel triggered the current call.
*/
DebugBIF(func_to_debug="", func_to_call="", event_info="")
{
static DebugErrorLevel, DebugResult, DebugInitd
if func_to_call =
func_to_call = DebugBIF_DefaultHandler
if func_to_debug =
{
if DebugInitd
return
func_to_debug = RegExMatch,RegExReplace,NumGet,NumPut,GetKeyState
,Mod,ASin,ACos,Sqrt,Log,Ln,RegisterCallback,DllCall
Loop, Parse, func_to_debug, `,
DebugBIF(A_LoopField, func_to_call)
DebugInitd := true
return
}
ListLines Off
if func_to_debug in DllCall,RegExMatch,RegExReplace
use_error_level := true
else
use_error_level := false
if event_info =
event_info := use_error_level
LowLevel_init()
if !(bif := __findFunc(func_to_debug)) || !NumGet(bif+49,0,"char")
|| !(callback := RegisterCallback(func_to_call,"C F",4,event_info))
{
ListLines On
return
}
bif_native := NumGet(bif+4)
if !VarSetCapacity(DebugErrorLevel)
{
VarSetCapacity(DebugErrorLevel, 56), NumPut(0xC35D5E5F, NumPut(0x10C4830C, NumPut(0x55FF5756, NumPut(0x1C75FF20, NumPut(0x75FF0E74, NumPut(0x3038800C, NumPut(0xC4830840, NumPut(0x8B08458B, NumPut(0x1055FF56, NumPut(0x1C75FF3E, NumPut(0x8B2075FF, NumPut(0x5718758B, NumPut(0x56EC8B55, NumPut(&DebugErrorLevel+4, DebugErrorLevel))))))))))))))
VarSetCapacity(DebugResult, 60), NumPut(0x0000C35D, NumPut(0x5E5F10C4, NumPut(0x830855FF, NumPut(0x57561875, NumPut(0xFF1C75FF, NumPut(0x0E750038, NumPut(0x80068B15, NumPut(0x7500087E, NumPut(0x830CC483, NumPut(0x0C55FF56, NumPut(0x1875FF3E, NumPut(0x8B1C75FF, NumPut(0x5714758B, NumPut(0x56EC8B55, NumPut(&DebugResult+4, DebugResult)))))))))))))))
}
if use_error_level
__mcode(func_to_debug, ""
. "68" __mcodeptr(bif_native)
. "68" __mcodeptr(callback)
. "68" __mcodeptr(__getVar(ErrorLevel))
. "FF15" __mcodeptr(&DebugErrorLevel)
. "83C40C"
. "C3")
else
__mcode(func_to_debug, ""
. "68" __mcodeptr(bif_native)
. "68" __mcodeptr(callback)
. "FF15" __mcodeptr(&DebugResult)
. "83C408"
. "C3")
ListLines On
}
DebugBIF_DefaultHandler(aName, aResultToken, aParam, aParamCount)
{
static IgnoreRegisterCallback_DllCall:=2
ListLines Off
RealErrorLevel := ErrorLevel
; If the first two errors are RegisterCallback("DllCall"), assume it is due to the
; usual workings of LowLevel.ahk, and ignore them.
if IgnoreRegisterCallback_DllCall
if __str(aName)="RegisterCallback" && __getTokenValue(NumGet(aParam+0))="DllCall"
{
ListLines On
return "", --IgnoreRegisterCallback_DllCall
}
else
IgnoreRegisterCallback_DllCall := 0
ListLines
;FileAppend, BEGIN DEBUG CODE ~~~~~~~~~~~~~~~~~~~~, NUL
s := __str(aName)
Loop % aParamCount
{
t := NumGet(aParam+(A_Index-1)*4)
sym := NumGet(t+8)
s .= "`n [" A_Index "]: "
if sym = 3 ; SYM_VAR
s .= __str(NumGet(NumGet(t+0)+24)) " := "
v := __getTokenValue(t+0)
if (sym=0 || sym=3 || (sym=4 && !NumGet(t+4)))
{
StringReplace, v, v, ", "", All
v = "%v%"
}
s .= v
}
v := __getTokenValue(aResultToken+0)
if v !=
{
sym := NumGet(aResultToken+8)
if (sym=0 || (sym=4 && !NumGet(aResultToken+4)))
{
StringReplace, v, v, ", "", All
v = "%v%"
}
s .= "`n ret: " v
}
else if !A_EventInfo
s .= "`n`nReturned an empty string."
if RealErrorLevel && A_EventInfo
s .= "`n`nErrorLevel = " RealErrorLevel
MsgBox, 16, %A_ScriptName% - Built-in Function Error, %s%
ErrorLevel := RealErrorLevel
;FileAppend, ~~~~~~~~~~~~~~~~~~~~ END DEBUG CODE, NUL
ListLines On
} |
Example: | Code: | ; Simplest usage:
DebugBIF()
; Examples that should trigger a debug message:
RegExMatch("abc", "(") ; Regex compile error.
NumPut(0, "") ; Invalid second parameter.
NumGet(var, 1) ; Invalid parameters.
Mod(1,0) ; Divide-by-zero. |
The script contains code compiled from the following C++ source (plus some AutoHotkey type definitions):
| Code: | typedef void (* DebugCallbackType)(char *aFuncName, ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount);
MCODE_API void DebugBif1( Var *aErrorLevel // Pointer to ErrorLevel Var.
, DebugCallbackType aOnErrorLevel // Callback to call if ErrorLevel != 0.
, BuiltInFunctionType aBIF // BIF to call
, void *aReturnAddress // Addressed pushed by expression evaluation, left on stack to reduce code size.
, ExprTokenType &aResultToken // Token used to pass func name/receive result.
, ExprTokenType *aParam[] // Parameters of this function call.
, int aParamCount ) // Number of parameters.
{
// On entry, aResultToken.marker holds the name of the built-in function.
// This value is overwritten within each built-in function, so save it now.
char *bif_name = aResultToken.marker;
// Call the built-in function.
aBIF(aResultToken, aParam, aParamCount);
// RegExMatch, RegExReplace and DllCall use non-zero ErrorLevel to indicate an error.
if (*aErrorLevel->mContents != '0')
aOnErrorLevel(bif_name, aResultToken, aParam, aParamCount);
}
MCODE_API void DebugBif2( DebugCallbackType aOnBlankResult // Callback to call if result is "".
, BuiltInFunctionType aBIF
, void *aReturnAddress
, ExprTokenType &aResultToken
, ExprTokenType *aParam[]
, int aParamCount )
{
char *bif_name = aResultToken.marker;
aBIF(aResultToken, aParam, aParamCount);
// Several functions use a blank result to indicate an error.
if (aResultToken.symbol == SYM_STRING && !*aResultToken.marker)
aOnBlankResult(bif_name, aResultToken, aParam, aParamCount);
} |
DllCallDebugger( ErrorCallbackName ) for AutoHotkey v1.0.47.06
DllCallDebugger hooks DllCall with machine code in order to detect non-zero ErrorLevels. Since it is implemented in native code which only calls back to script if an error occurs, it should have little effect on performance.
| Code: | ; Requires AutoHotkey v1.0.47.06 and the appropriate version of LowLevel,
; which can be found here: http://www.autohotkey.com/forum/topic26300.html
DllCallDebugger(ErrorCallbackName="DllCallDebugger_DefaultHandler")
{
static ErrorCallback
if ErrorCallback
return
if !(ErrorCallback := RegisterCallback(ErrorCallbackName,"C F",3))
return
LowLevel_init()
DllCallFunc := __findFunc("DllCall")
DllCallBif := NumGet(DllCallFunc+4)
ErrorLevelVar := __getVar(ErrorLevel)
pbin:=__mcode("DllCall"
; void DllCall(ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount)
; {
; DllCallBif(aResultToken, aParam, aParamCount);
, "FF74240C" ; push [esp+12]
. "FF74240C" ; push [esp+12]
. "FF74240C" ; push [esp+12]
. "B9" DllCallDebugger_FormatPtr(DllCallBif) ; mov ecx, DllCallBif
. "FFD1" ; call ecx
; if (*ErrorLevelVar->mContents != '0')
. "B9" DllCallDebugger_FormatPtr(ErrorLevelVar) ; mov ecx, ErrorLevelVar
. "8B09" ; mov ecx, [ecx]
. "0FBE09" ; movsx ecx, byte ptr [ecx]
. "83F930" ; cmp ecx, '0'
. "7407" ; je +7
; ErrorCallback();
. "B9" DllCallDebugger_FormatPtr(ErrorCallback) ; mov ecx, ErrorCallback
. "FFD1" ; call ecx
; }
. "83C40C" ; add esp, 12
. "C3") ; ret
pbin2:=__mcode("DllCallDebugger_TokenValue"
/*
ExprTokenType *token = (ExprTokenType*) aParam[0]->value_int64;
if (token->symbol == SYM_VAR)
{
aResultToken.symbol = SYM_OPERAND;
aResultToken.marker = token->var->mContents;
}
else
{
aResultToken.symbol = token->symbol;
aResultToken.value_int64 = token->value_int64;
}
*/ , "8B4424088B088B018B50088B4C240483FA03750B428951088B108B028901C38951088B1089118B4004894104C3")
; WARNING: This will affect the entire page(s) containing our generated code.
; Make our code executable (for systems with Data Execution Prevention enabled):
DllCall("VirtualProtect","uint",pbin,"uint",22,"uint",0x40,"uint*",0)
DllCall("VirtualProtect","uint",pbin2,"uint",22,"uint",0x40,"uint*",0)
}
DllCallDebugger_FormatPtr(ptr) {
FI := A_FormatInteger
SetFormat, Integer, H
ptr := RegExReplace(SubStr("00000000" SubStr(ptr+0, 3), -7), "(..)(..)(..)(..)", "$4$3$2$1")
SetFormat, Integer, %FI%
return ptr
}
; token MUST be the immediate integer result of a math expression or built-in function.
DllCallDebugger_TokenValue(token) {
; Implemented in machine code; see DllCallDebugger().
}
DllCallDebugger_DefaultHandler(aResultToken, aParam, aParamCount)
{
ListLines
Call = DllCall(
Loop % aParamCount
P:=NumGet(aParam+A_Index*4-4)
,V:=DllCallDebugger_TokenValue(P+0)
,T:=NumGet(P+8)
,Call.=(A_Index>1 ? ", ":"") . (T=1||T=2 ? V : """" V """")
Call.=")"
MsgBox DllCall failed with ErrorLevel = %ErrorLevel%`n`n Specifically: %Call%
} |
Example: | Code: | ; Initialize/set our callback.
DllCallDebugger("OnDllCallFail")
; Should always succeed:
DllCall("MulDiv","int",1,"int",1,"int",1)
if ErrorLevel = 0
MsgBox DllCall succeeded
; Should always fail:
DllCall("foo")
DllCall("MulDiv","int",1,"int",1)
OnDllCallFail() {
MsgBox DllCall failed: %ErrorLevel%
} |
For licensing terms, see Lexikos' default copyright license.
2009/09/27:
- Updated for AutoHotkey_L31 compatibility.
- Calling DebugBif() with no parameters a second time no longer attempts to re-hook the functions.
- Added ListLines On/Off to prevent most of the debug code from appearing in ListLines.
Last edited by Lexikos on Sun Sep 27, 2009 1:31 am; edited 7 times in total |
|
| Back to top |
|
 |
Laszlo
Joined: 14 Feb 2005 Posts: 4710 Location: Boulder, CO
|
Posted: Mon Oct 13, 2008 1:39 pm Post subject: |
|
|
| Wonderful, much needed function! Thanks for sharing it. Keep up the good work! |
|
| Back to top |
|
 |
Lexikos
Joined: 17 Oct 2006 Posts: 7282 Location: Australia
|
Posted: Thu Jan 15, 2009 10:10 pm Post subject: |
|
|
| Updated with default handler and to allow inspection of parameters. |
|
| Back to top |
|
 |
majkinetor
Joined: 24 May 2006 Posts: 4511 Location: Belgrade
|
Posted: Thu Jan 15, 2009 11:30 pm Post subject: |
|
|
So nice.
Thx. _________________
 |
|
| Back to top |
|
 |
Laszlo
Joined: 14 Feb 2005 Posts: 4710 Location: Boulder, CO
|
Posted: Fri Jan 16, 2009 12:27 am Post subject: |
|
|
| Still no plans to support the latest AHK beta? |
|
| Back to top |
|
 |
Lexikos
Joined: 17 Oct 2006 Posts: 7282 Location: Australia
|
Posted: Fri Jan 16, 2009 8:37 am Post subject: |
|
|
Before I posted, I started updating LowLevel to support the beta. Apart from __expr, which will not be supported, there aren't many differences. ArgStruct has one extra field at the end (ExprTokenType *postfix) and Var has an extra union at the beginning (__int64 mContentsInt64, double mContentsDouble). Within DllCallDebugger, both machine code functions must be adjusted - Var::mContents is now at offset 8, from 0. Unfortunately, even after doing this, an access violation occurs at movsx ecx, byte ptr [ecx]. I may be missing something...
I actually updated DllCallDebugger months ago, but hadn't been motivated to post it until [the time of my previous post]. |
|
| Back to top |
|
 |
Lexikos
Joined: 17 Oct 2006 Posts: 7282 Location: Australia
|
Posted: Fri Jan 23, 2009 2:49 pm Post subject: |
|
|
Having updated LowLevel to support the pre-v1.0.48 beta of AutoHotkey, I've also updated my first post in this thread with a much more generic debugging function. Of course, the pre-v1.0.48 beta of AutoHotkey and appropriate version of LowLevel are required.
[Edit: Tested and compatible with v1.0.48 final.] |
|
| Back to top |
|
 |
Joy2DWorld
Joined: 04 Dec 2006 Posts: 561 Location: Galil, Israel
|
Posted: Fri Mar 20, 2009 1:41 am Post subject: |
|
|
(just saw this). incredible. _________________ Joyce Jamce |
|
| Back to top |
|
 |
fincs
Joined: 05 May 2007 Posts: 1158 Location: Seville, Spain
|
|
| Back to top |
|
 |
Lexikos
Joined: 17 Oct 2006 Posts: 7282 Location: Australia
|
Posted: Fri Mar 20, 2009 11:54 pm Post subject: |
|
|
| Those lines essentially do nothing, but are easily visible in ListLines. |
|
| Back to top |
|
 |
Lexikos
Joined: 17 Oct 2006 Posts: 7282 Location: Australia
|
Posted: Sun Sep 27, 2009 1:33 am Post subject: |
|
|
| Quote: | 2009/09/27:
- Updated for AutoHotkey_L31 compatibility.
- Calling DebugBif() with no parameters a second time no longer attempts to re-hook the functions.
- Added ListLines On/Off to prevent most of the debug code from appearing in ListLines.
| I also disabled the FileAppend -> NUL lines since ListLines On/Off makes them unnecessary. |
|
| Back to top |
|
 |
tinku99
Joined: 03 Aug 2007 Posts: 513 Location: Houston, TX
|
Posted: Wed Jun 23, 2010 5:21 am Post subject: using debugbif to simulate variadic functions |
|
|
fragman recently asked if it would be possible to make richObject() behave like object().
Unfortunately, user defined functions don't support unnamed optional parameters.
Variadic functions could be simulated by calling a variadic bif (such as "dllcall") with one bogus argument plus the arguments meant for the user variadic. | Code: | | dllcall(0, param1, param2...) | However, DebugBIF_DefaultHandler falls down on objects.
Also, how do you get the result of the DebugBIF_DefaultHandler back to the caller of the intentionally faulty bif ? |
|
| Back to top |
|
 |
Lexikos
Joined: 17 Oct 2006 Posts: 7282 Location: Australia
|
Posted: Wed Jun 23, 2010 1:49 pm Post subject: Re: using debugbif to simulate variadic functions |
|
|
| That seems very awkward. See my most recent post in the LowLevel thread for a cleaner, more direct solution. |
|
| Back to top |
|
 |
HotKeyIt
Joined: 18 Jun 2008 Posts: 4641 Location: AHK Forum
|
|
| Back to top |
|
 |
fincs
Joined: 05 May 2007 Posts: 1158 Location: Seville, Spain
|
|
| Back to top |
|
 |
|
|
You can post new topics in this forum You can reply to topics in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|