AutoHotkey Homepage AutoHotkey Community
Let's help each other out
 
 FAQFAQ   SearchSearch   MemberlistMemberlist   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

DebugBIF - debug built-in functions (was DllCallDebugger)

 
Reply to topic    AutoHotkey Community Forum Index -> Scripts & Functions
View previous topic :: View next topic  
Author Message
Lexikos



Joined: 17 Oct 2006
Posts: 7282
Location: Australia

PostPosted: Mon Oct 13, 2008 12:13 pm    Post subject: DebugBIF - debug built-in functions (was DllCallDebugger) Reply with quote

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
View user's profile Send private message Visit poster's website
Laszlo



Joined: 14 Feb 2005
Posts: 4710
Location: Boulder, CO

PostPosted: Mon Oct 13, 2008 1:39 pm    Post subject: Reply with quote

Wonderful, much needed function! Thanks for sharing it. Keep up the good work!
Back to top
View user's profile Send private message
Lexikos



Joined: 17 Oct 2006
Posts: 7282
Location: Australia

PostPosted: Thu Jan 15, 2009 10:10 pm    Post subject: Reply with quote

Updated with default handler and to allow inspection of parameters.
Back to top
View user's profile Send private message Visit poster's website
majkinetor



Joined: 24 May 2006
Posts: 4511
Location: Belgrade

PostPosted: Thu Jan 15, 2009 11:30 pm    Post subject: Reply with quote

So nice.

Thx.
_________________
Back to top
View user's profile Send private message
Laszlo



Joined: 14 Feb 2005
Posts: 4710
Location: Boulder, CO

PostPosted: Fri Jan 16, 2009 12:27 am    Post subject: Reply with quote

Still no plans to support the latest AHK beta?
Back to top
View user's profile Send private message
Lexikos



Joined: 17 Oct 2006
Posts: 7282
Location: Australia

PostPosted: Fri Jan 16, 2009 8:37 am    Post subject: Reply with quote

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
View user's profile Send private message Visit poster's website
Lexikos



Joined: 17 Oct 2006
Posts: 7282
Location: Australia

PostPosted: Fri Jan 23, 2009 2:49 pm    Post subject: Reply with quote

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
View user's profile Send private message Visit poster's website
Joy2DWorld



Joined: 04 Dec 2006
Posts: 561
Location: Galil, Israel

PostPosted: Fri Mar 20, 2009 1:41 am    Post subject: Reply with quote

(just saw this). incredible.
_________________
Joyce Jamce
Back to top
View user's profile Send private message
fincs



Joined: 05 May 2007
Posts: 1158
Location: Seville, Spain

PostPosted: Fri Mar 20, 2009 9:07 pm    Post subject: Reply with quote

I have a question, why are you FileAppending to NUL?
_________________
fincs
Get SciTE4AutoHotkey v3.0.00 (Release Candidate)
[My project list]
Back to top
View user's profile Send private message
Lexikos



Joined: 17 Oct 2006
Posts: 7282
Location: Australia

PostPosted: Fri Mar 20, 2009 11:54 pm    Post subject: Reply with quote

Those lines essentially do nothing, but are easily visible in ListLines.
Back to top
View user's profile Send private message Visit poster's website
Lexikos



Joined: 17 Oct 2006
Posts: 7282
Location: Australia

PostPosted: Sun Sep 27, 2009 1:33 am    Post subject: Reply with quote

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
View user's profile Send private message Visit poster's website
tinku99



Joined: 03 Aug 2007
Posts: 513
Location: Houston, TX

PostPosted: Wed Jun 23, 2010 5:21 am    Post subject: using debugbif to simulate variadic functions Reply with quote

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
View user's profile Send private message Send e-mail Visit poster's website
Lexikos



Joined: 17 Oct 2006
Posts: 7282
Location: Australia

PostPosted: Wed Jun 23, 2010 1:49 pm    Post subject: Re: using debugbif to simulate variadic functions Reply with quote

That seems very awkward. See my most recent post in the LowLevel thread for a cleaner, more direct solution.
Back to top
View user's profile Send private message Visit poster's website
HotKeyIt



Joined: 18 Jun 2008
Posts: 4641
Location: AHK Forum

PostPosted: Tue Nov 16, 2010 6:18 pm    Post subject: Reply with quote

Thanks for this Lexikos, I can't believe I have missed that, this is a must have function Embarassed
_________________
AHK_H (2alpha) AHF TT _Struct WatchDir Yaml _Input ObjTree RapidHotkey DynaRun Wink
Back to top
View user's profile Send private message
fincs



Joined: 05 May 2007
Posts: 1158
Location: Seville, Spain

PostPosted: Tue Nov 16, 2010 8:31 pm    Post subject: Reply with quote

Or use the built-in variadic support:
Code:
myFunc(2, 3, 4)

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

_________________
fincs
Get SciTE4AutoHotkey v3.0.00 (Release Candidate)
[My project list]
Back to top
View user's profile Send private message
Display posts from previous:   
Reply to topic    AutoHotkey Community Forum Index -> Scripts & Functions All times are GMT
Page 1 of 1

 
Jump to:  
You can post new topics in this forum
You can reply to topics in this forum


Powered by phpBB © 2001, 2005 phpBB Group