Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

DebugBIF - debug built-in functions (was DllCallDebugger)


  • Please log in to reply
14 replies to this topic
Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
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.
; 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:
; 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):
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.

; 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:
; 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:
[*:3f23cumj]Updated for AutoHotkey_L31 compatibility.
[*:3f23cumj]Calling DebugBif() with no parameters a second time no longer attempts to re-hook the functions.
[*:3f23cumj]Added ListLines On/Off to prevent most of the debug code from appearing in ListLines.


Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
Wonderful, much needed function! Thanks for sharing it. Keep up the good work!

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
Updated with default handler and to allow inspection of parameters.

majkinetor
  • Moderators
  • 4512 posts
  • Last active: May 20 2019 07:41 AM
  • Joined: 24 May 2006
So nice.

Thx.
Posted Image

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
Still no plans to support the latest AHK beta?

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
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].

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
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]

Joy2DWorld
  • Members
  • 562 posts
  • Last active: Jun 30 2014 07:48 PM
  • Joined: 04 Dec 2006
(just saw this). incredible.
Joyce Jamce

fincs
  • Moderators
  • 1662 posts
  • Last active:
  • Joined: 05 May 2007
I have a question, why are you FileAppending to NUL?

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
Those lines essentially do nothing, but are easily visible in ListLines.

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006

2009/09/27:
[*:33lwtgks]Updated for AutoHotkey_L31 compatibility.
[*:33lwtgks]Calling DebugBif() with no parameters a second time no longer attempts to re-hook the functions.
[*:33lwtgks]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.

tinku99
  • Members
  • 560 posts
  • Last active: Feb 08 2015 12:54 AM
  • Joined: 03 Aug 2007
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.
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 ?

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
That seems very awkward. See my most recent post in the LowLevel thread for a cleaner, more direct solution.

HotKeyIt
  • Moderators
  • 7439 posts
  • Last active: Jun 22 2016 09:14 PM
  • Joined: 18 Jun 2008
Thanks for this Lexikos, I can't believe I have missed that, this is a must have function :oops:

fincs
  • Moderators
  • 1662 posts
  • Last active:
  • Joined: 05 May 2007
Or use the built-in variadic support:
myFunc(2, 3, 4)

myFunc(prm*)
{
    for k,v in prm
        msgbox %v%
}