AutoHotkey Community

It is currently May 27th, 2012, 12:17 pm

All times are UTC [ DST ]




Post new topic Reply to topic  [ 15 posts ] 
Author Message
PostPosted: October 13th, 2008, 1:13 pm 
Offline

Joined: October 17th, 2006, 4:15 pm
Posts: 7503
Location: Australia
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 September 27th, 2009, 2:31 am, edited 7 times in total.

Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: October 13th, 2008, 2:39 pm 
Offline

Joined: February 14th, 2005, 4:05 pm
Posts: 4710
Location: Boulder, CO
Wonderful, much needed function! Thanks for sharing it. Keep up the good work!


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: January 15th, 2009, 11:10 pm 
Offline

Joined: October 17th, 2006, 4:15 pm
Posts: 7503
Location: Australia
Updated with default handler and to allow inspection of parameters.


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: January 16th, 2009, 12:30 am 
Offline

Joined: May 24th, 2006, 2:49 pm
Posts: 4511
Location: Belgrade
So nice.

Thx.

_________________
Image


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: January 16th, 2009, 1:27 am 
Offline

Joined: February 14th, 2005, 4:05 pm
Posts: 4710
Location: Boulder, CO
Still no plans to support the latest AHK beta?


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: January 16th, 2009, 9:37 am 
Offline

Joined: October 17th, 2006, 4:15 pm
Posts: 7503
Location: Australia
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].


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: January 23rd, 2009, 3:49 pm 
Offline

Joined: October 17th, 2006, 4:15 pm
Posts: 7503
Location: Australia
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.]


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: March 20th, 2009, 2:41 am 
Offline

Joined: December 4th, 2006, 10:35 am
Posts: 561
Location: Galil, Israel
(just saw this). incredible.

_________________
Joyce Jamce


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: March 20th, 2009, 10:07 pm 
Online
User avatar

Joined: May 5th, 2007, 7:24 pm
Posts: 1240
Location: Seville, Spain
I have a question, why are you FileAppending to NUL?

_________________
fincs
Highly recommended: AutoHotkey_L (see why) (all my code snippets require it)
Formal request to polyethene - I support the unity of the AutoHotkey community
Get SciTE4AutoHotkey v3.0.00 (Release Candidate)
[My project list]


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: March 21st, 2009, 12:54 am 
Offline

Joined: October 17th, 2006, 4:15 pm
Posts: 7503
Location: Australia
Those lines essentially do nothing, but are easily visible in ListLines.


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: September 27th, 2009, 2:33 am 
Offline

Joined: October 17th, 2006, 4:15 pm
Posts: 7503
Location: Australia
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.


Report this post
Top
 Profile  
Reply with quote  
PostPosted: June 23rd, 2010, 6:21 am 
Offline

Joined: August 3rd, 2007, 8:01 am
Posts: 555
Location: Houston, TX
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 ?


Report this post
Top
 Profile  
Reply with quote  
PostPosted: June 23rd, 2010, 2:49 pm 
Offline

Joined: October 17th, 2006, 4:15 pm
Posts: 7503
Location: Australia
That seems very awkward. See my most recent post in the LowLevel thread for a cleaner, more direct solution.


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: November 16th, 2010, 7:18 pm 
Offline

Joined: June 18th, 2008, 8:36 am
Posts: 4923
Location: AHK Forum
Thanks for this Lexikos, I can't believe I have missed that, this is a must have function :oops:

_________________
AHK_H (2alpha) AHF TT _Struct WatchDir Yaml _Input ObjTree RapidHotkey DynaRun :wink:


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: November 16th, 2010, 9:31 pm 
Online
User avatar

Joined: May 5th, 2007, 7:24 pm
Posts: 1240
Location: Seville, Spain
Or use the built-in variadic support:
Code:
myFunc(2, 3, 4)

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

_________________
fincs
Highly recommended: AutoHotkey_L (see why) (all my code snippets require it)
Formal request to polyethene - I support the unity of the AutoHotkey community
Get SciTE4AutoHotkey v3.0.00 (Release Candidate)
[My project list]


Report this post
Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 15 posts ] 

All times are UTC [ DST ]


Who is online

Users browsing this forum: No registered users and 5 guests


You can post new topics in this forum
You can reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Powered by phpBB® Forum Software © phpBB Group