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)

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



Joined: 17 Oct 2006
Posts: 4468
Location: Qld, Australia

PostPosted: Mon Oct 13, 2008 1: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 2: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: 4514
Location: Boulder, CO

PostPosted: Mon Oct 13, 2008 2: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: 4468
Location: Qld, Australia

PostPosted: Thu Jan 15, 2009 11: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: 4114
Location: Belgrade

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

So nice.

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



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

PostPosted: Fri Jan 16, 2009 1: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: 4468
Location: Qld, Australia

PostPosted: Fri Jan 16, 2009 9: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: 4468
Location: Qld, Australia

PostPosted: Fri Jan 23, 2009 3: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: 537
Location: Galil, Israel

PostPosted: Fri Mar 20, 2009 2: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: 473
Location: Seville, Spain

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

I have a question, why are you FileAppending to NUL?
_________________
fincs
SciTE4AutoHotkey v2 script editor
[AutoHotkeyCE] [AutoHotkey_L]
Back to top
View user's profile Send private message
Lexikos



Joined: 17 Oct 2006
Posts: 4468
Location: Qld, Australia

PostPosted: Sat Mar 21, 2009 12:54 am    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: 4468
Location: Qld, Australia

PostPosted: Sun Sep 27, 2009 2: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
Display posts from previous:   
Post new topic   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