Jump to content

_GUICtrlTab_GetItemText the target crashes on dissimilar architecture


Go to solution Solved by pixelsearch,

Recommended Posts

Posted

The problem I am facing is

I'm running the example 'C:\Program Files (x86)\AutoIt3\Examples\Helpfile\GUICtrlCreateTab.au3'  as the target application,
and with the main script I'm trying to read the tab names

the issue is that the main script runs on X64 and the target on X32

up to Local $hParentWnd = _WinAPI_GetParent($hCtrl)  everything goes well,

but if I try with  _GUICtrlTab_GetItemText($hCtrl, $x) , then the target crashes

I took a look in #include <GuiTab.au3>
and I realized that it has something to do with the SendMessage  and the architectural incompatibility

main script

#Region ;**** Directives created by AutoIt3Wrapper_GUI ****
#AutoIt3Wrapper_UseX64=y
#EndRegion ;**** Directives created by AutoIt3Wrapper_GUI ****
#include <GuiTab.au3>

If Not WinExists("My GUI Tab") Then Exit

Local $hWnd = WinWait("My GUI Tab", "", 1)
ConsoleWrite("$hWnd=" & $hWnd & @CRLF)

Local $hCtrl = ControlGetHandle($hWnd, "", "SysTabControl321")
ConsoleWrite("$hCtrl=" & $hCtrl & @CRLF)

Local $hParentWnd = _WinAPI_GetParent($hCtrl)
ConsoleWrite("$hParentWnd=" & $hParentWnd & @CRLF)

; Show number of tabs
Local $iTabCnt = _GUICtrlTab_GetItemCount($hCtrl)
ConsoleWrite("Number of tabs: " & $iTabCnt & @CRLF & @CRLF)
Local $sItemText

; With this part the target crashes
;~ For $x = 0 To $iTabCnt - 1
;~  $sItemText = _GUICtrlTab_GetItemText($hCtrl, $x)
;~  ConsoleWrite($x & ") " & $sItemText & @CRLF)
;~ Next

the easy solution I thought of is to make a helper script like a bridge

But if there is a way to avoid this, I would like to know
Thanks a lot

 

I know that I know nothing

  • Solution
Posted


@ioa747 Hi

When you call _GUICtrlTab_GetItemText, then  _GUICtrlTab_GetItem is called

And _GuiCtrlTab_GetItem had issues as seen on Trac Ticket 3903 and in this post.
In the post, @Danyfirex worked on this x86-x64 issue and maybe he could give an advice here if he got time ?

Also I notice code in _GUICtrlTab_GetItem has been updated in new release 3.3.17.1 (beta) . Did you check your script with 3.3.17.1 , maybe it's solved with the new release ?

Fingers crossed & good luck :)

"I think you are searching a bug where there is no bug... don't listen to bad advice."

Posted (edited)

pixelsearch, analytical and comprehensive as always, thank you very much
The solution proposed by Danyfirex 🏆, in this particular post is completely functional

 

#Region ;**** Directives created by AutoIt3Wrapper_GUI ****
#AutoIt3Wrapper_UseX64=y
#EndRegion ;**** Directives created by AutoIt3Wrapper_GUI ****
#include <GuiTab.au3>

;>> Target: C:\Program Files (x86)\AutoIt3\Examples\Helpfile\GUICtrlCreateTab.au3

If Not WinExists("My GUI Tab") Then Exit

Local $hWnd = WinWait("My GUI Tab", "", 1)
ConsoleWrite("$hWnd=" & $hWnd & @CRLF)

Local $hCtrl = ControlGetHandle($hWnd, "", "SysTabControl321")
ConsoleWrite("$hCtrl=" & $hCtrl & @CRLF)

Local $hParentWnd = _WinAPI_GetParent($hCtrl)
ConsoleWrite("$hParentWnd=" & $hParentWnd & @CRLF)

; Show number of tabs
Local $iTabCnt = _GUICtrlTab_GetItemCount($hCtrl)
ConsoleWrite("Number of tabs: " & $iTabCnt & @CRLF & @CRLF)
Local $sItemText, $aItem

; Now is working !!
For $x = 0 To $iTabCnt - 1
    $aItem = _GUICtrlTab_GetItemEx($hCtrl, $x)
    $sItemText = $aItem[1]
    ConsoleWrite("$sItemText=" & $sItemText & @CRLF)
Next


Func _GUICtrlTab_GetItemEx($hWnd, $iIndex)
    If Not IsHWnd($hWnd) Then $hWnd = GUICtrlGetHandle($hWnd)

    Local $tagTCITEMEx = $tagTCITEM & ";ptr Filler" ; strange the Filler is erased by TCM_GETITEM : MS Bug!!!
    If Not _WinAPI_InProcess($hWnd, $__g_hGUICtrl_LastWnd) Then
        ;x86 read remote x64
        If (Not @AutoItX64) And (Not _WinAPI_IsWow64Process(DllCall("user32.dll", "dword", "GetWindowThreadProcessId", "hwnd", $hWnd, "dword*", 0)[2])) Then
            $tagTCITEMEx = StringReplace($tagTCITEMEx, "ptr", "INT64")
        EndIf

        ;x64 read remote x86
        If (@AutoItX64) And (_WinAPI_IsWow64Process(DllCall("user32.dll", "dword", "GetWindowThreadProcessId", "hwnd", $hWnd, "dword*", 0)[2])) Then
            $tagTCITEMEx = StringReplace($tagTCITEMEx, "ptr", "ULONG")
        EndIf
    EndIf

    Local $tItem = DllStructCreate($tagTCITEMEx)
    DllStructSetData($tItem, "Mask", $TCIF_ALLDATA)
    DllStructSetData($tItem, "StateMask", BitOR($TCIS_HIGHLIGHTED, $TCIS_BUTTONPRESSED))
    Local $tBuffer, $iMsg
    If _GUICtrlTab_GetUnicodeFormat($hWnd) Then
        $tBuffer = $__g_tTabBuffer
        $iMsg = $TCM_GETITEMW
    Else
        $tBuffer = $__g_tTabBufferANSI
        $iMsg = $TCM_GETITEMA
    EndIf
    Local $iRet = __GUICtrl_SendMsg($hWnd, $iMsg, $iIndex, $tItem, $tBuffer, True, 4, True)

    Local $aItem[4]
    $aItem[0] = DllStructGetData($tItem, "State")
    $aItem[1] = DllStructGetData($tBuffer, "Text")
    $aItem[2] = DllStructGetData($tItem, "Image")
    $aItem[3] = DllStructGetData($tItem, "Param")

    Return SetError($iRet = 0, 0, $aItem)
EndFunc   ;==>_GUICtrlTab_GetItem

This way, I avoid having to make a helper script like a bridge, I just add the function.
Mission accomplished. 

Thank you very much :)

 

Edited by ioa747

I know that I know nothing

  • 6 months later...
Posted

I'm getting back on topic.

because I encountered a problem with _GUICtrlTab_GetItemText again
this time only in the case  x64 Caller -> x64 Target

I encountered the problem in  scite_overlaytab project

and it was about whether scite runs on x64 architecture and my script also on x64, sometimes (not always) my script crashes with

AutoIt3 ended. rc:-1073740940

Looking to find what's wrong, I came across _GUICtrlTab_GetItem which internally calls __GUICtrl_TagOutProcess

; AutoIt Version : 3.3.18.0

; GuiTab.au3
Func _GUICtrlTab_GetItem($hWnd, $iIndex)
    If Not IsHWnd($hWnd) Then $hWnd = GUICtrlGetHandle($hWnd)

    Local $tagTCITEMEx = $tagTCITEM
    If IsHWnd($hWnd) And DllCall("user32.dll", "dword", "GetWindowThreadProcessId", "hwnd", $hWnd, "dword*", 0)[2] <> @AutoItPID Then ; Not _WinAPI_InProcess
        __GUICtrl_TagOutProcess($hWnd, $tagTCITEMEx)
    EndIf
    Local $tItem = DllStructCreate($tagTCITEMEx)

    DllStructSetData($tItem, "Mask", $TCIF_ALLDATA)
    DllStructSetData($tItem, "StateMask", BitOR($TCIS_HIGHLIGHTED, $TCIS_BUTTONPRESSED))
    Local $tBuffer, $iMsg
    If _GUICtrlTab_GetUnicodeFormat($hWnd) Then
        $tBuffer = $__g_tTabBuffer
        $iMsg = $TCM_GETITEMW
    Else
        $tBuffer = $__g_tTabBufferANSI
        $iMsg = $TCM_GETITEMA
    EndIf

    Local $iRet = __GUICtrl_SendMsg($hWnd, $iMsg, $iIndex, $tItem, $tBuffer, True, 4, True) ; will set "TextMax"

    Local $aItem[4]
    $aItem[0] = DllStructGetData($tItem, "State")
    $aItem[1] = DllStructGetData($tBuffer, "Text")
    $aItem[2] = DllStructGetData($tItem, "Image")
    $aItem[3] = DllStructGetData($tItem, "Param")

    Return SetError($iRet = 0, 0, $aItem)
EndFunc   ;==>_GUICtrlTab_GetItem

; GuiCtrlInternals.au3
Func __GUICtrl_TagOutProcess($hWnd, ByRef $sTag)
    Local $bIsWow64 = __GUICtrl_IsWow64Process($hWnd)
    ;x86 read remote x64
    If Not (@AutoItX64 Or $bIsWow64) Then
        $sTag = StringRegExpReplace($sTag, "(dword_ptr)|(uint_ptr)|(int_ptr)|(ptr)|(lparam)|(wparam)|(hwnd)|(handle)", "UINT64")
    EndIf

    ;x64 read remote x86
    If @AutoItX64 And $bIsWow64 Then
        $sTag = StringRegExpReplace($sTag, "(dword_ptr)|(uint_ptr)|(int_ptr)|(ptr)|(lparam)|(wparam)|(hwnd)|(handle)", "UINT")
    EndIf
EndFunc   ;==>__GUICtrl_TagOutProcess


While the function correctly scales pointers to UINT64 for x64 targets,
it completely ignores Structure Alignment/Padding requirements.

In case 64-bit script -> 64-bit target, the function does nothing. It leaves $tagTCITEM as is.

When an x86 process (or even an x64 process) queries an x64 external control (like SciTE's TabControl)
using a structure like TCITEM, the pointer field (pszText) is expected at Offset 16, not Offset 12.


In x64 architecture, a 64-bit pointer must be aligned to an 8-byte boundary.

  • Current Logic: uint Mask (4); uint State (4); uint StateMask (4); ptr pszText (8) -> Puts pszText at Offset 12.
  • Windows x64 Requirement: Requires 4 bytes of padding after StateMask to align pszText at Offset 16.

Result: _GUICtrlTab_GetItem returns an empty string or garbage because it reads from the wrong memory offset.


If I understood what I read correctly, then the __GUICtrl_TagOutProcess  should be something like this

Func __GUICtrl_TagOutProcess($hWnd, ByRef $sTag)
    Local $bTargetIsWow64 = __GUICtrl_IsWow64Process($hWnd)
    Local $bCurrentIs64 = @AutoItX64

    ; x64 Caller -> x64 Target (Critical Fix for Padding)
    If $bCurrentIs64 And (Not $bTargetIsWow64) Then
        ; If structure starts with 12 bytes of UINTS (TCITEM layout),
        ; we must inject 4 bytes of padding to align the next pointer to offset 16.
        If StringInStr($sTag, "uint Mask;uint State;uint StateMask") Then
            $sTag = StringReplace($sTag, "StateMask;", "StateMask;uint AlignmentPadding;")
        EndIf
    EndIf

    ; x86 Caller -> x64 Target
    If (Not $bCurrentIs64) And (Not $bTargetIsWow64) Then
        ; Scale pointers to 64-bit integers
        $sTag = StringRegExpReplace($sTag, "(?i)(dword_ptr|uint_ptr|int_ptr|ptr|lparam|wparam|hwnd|handle)", "UINT64")

        ; Apply x64 padding for the 64-bit target process
        If StringInStr($sTag, "uint Mask;uint State;uint StateMask") Then
            $sTag = StringReplace($sTag, "StateMask;", "StateMask;uint AlignmentPadding;")
        EndIf
    EndIf

    ; x64 Caller -> x86 Target (WOW64)
    If $bCurrentIs64 And $bTargetIsWow64 Then
        ; Scale pointers down to 32-bit integers
        $sTag = StringRegExpReplace($sTag, "(?i)(dword_ptr|uint_ptr|int_ptr|ptr|lparam|wparam|hwnd|handle)", "UINT")
    EndIf
EndFunc   ;==>__GUICtrl_TagOutProcess


but since it is beyond the limits of my knowledge I leave it to the experts,

I am simply mentioning it in case anyone sees it.  :)

 

I know that I know nothing

Posted

@ioa747 It doesn't appear to be the structure fault.   It is automatically padded for right alignment.

image.png.f565c57a13b32fc6cca26ad478cf4809.png

I tried digging to find what could cause the crash, but without success.  It happens (most of the time) on return from __GUICtrl_SendMsg.
My take is a memory allocation issue.  Although all informations are correctly gathered within the function, it crashes on return, like memory has been corrupted somewhere.  Need to investigate.

ps. even padding manually it still get crash.  The strange thing is sometimes the script finishes all the way, but crashes on Exit.  Really seems to be a memory corruption.

Posted (edited)

Alright.  Tested all four possible architecture combinations and it works correctly now.

The elusive bug is that it was writing a few more bytes beyond the end of the $tItem structure causing memory corruption.

You need to replace the line in __GUICtrl_SendMsg :

If $bRetItem Then _MemRead($tMemMap, $pMemory, $tItem, $iItem)

with

If $bRetItem Then _MemRead($tMemMap, $pMemory, $tItem, DllStructGetSize($tItem))

Still a bit strange that only x64 vs x64 shows the issue. 

Ticket.

Edited by Nine

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...