Skip to content

Commit

Permalink
Fixed ControlGet-ListView across 32/64-bit boundary; removed Win9x code.
Browse files Browse the repository at this point in the history
  • Loading branch information
Lexikos committed Feb 22, 2012
1 parent 33b7f44 commit c19397d
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 83 deletions.
103 changes: 74 additions & 29 deletions source/script2.cpp
Expand Up @@ -2749,21 +2749,75 @@ ResultType Line::ControlGetListView(Var &aOutputVar, HWND aHwnd, LPTSTR aOptions
// FINAL CHECKS
if (row_count < 1 || !col_count) // But don't return when col_count == -1 (i.e. always make the attempt when col count is undetermined).
return g_ErrorLevel->Assign(ERRORLEVEL_NONE); // No text in the control, so indicate success.

// Notes about the following struct definitions: The layout of LVITEM depends on
// which platform THIS executable was compiled for, but we need it to match what
// the TARGET process expects. If the target process is 32-bit and we are 64-bit
// or vice versa, LVITEM can't be used. The following structs are copies of
// LVITEM with UINT (32-bit) or UINT64 (64-bit) in place of the pointer fields.
struct LVITEM32
{
UINT mask;
int iItem;
int iSubItem;
UINT state;
UINT stateMask;
UINT pszText;
int cchTextMax;
int iImage;
UINT lParam;
int iIndent;
int iGroupId;
UINT cColumns;
UINT puColumns;
UINT piColFmt;
int iGroup;
};
struct LVITEM64
{
UINT mask;
int iItem;
int iSubItem;
UINT state;
UINT stateMask;
UINT64 pszText;
int cchTextMax;
int iImage;
UINT64 lParam;
int iIndent;
int iGroupId;
UINT cColumns;
UINT64 puColumns;
UINT64 piColFmt;
int iGroup;
};
union
{
LVITEM32 i32;
LVITEM64 i64;
} local_lvi;

// ALLOCATE INTERPROCESS MEMORY FOR TEXT RETRIEVAL
HANDLE handle;
LPVOID p_remote_lvi; // Not of type LPLVITEM to help catch bugs where p_remote_lvi->member is wrongly accessed here in our process.
if ( !(p_remote_lvi = AllocInterProcMem(handle, LV_REMOTE_BUF_SIZE + sizeof(LVITEM), aHwnd)) ) // Allocate the right type of memory (depending on OS type). Allocate both the LVITEM struct and its internal string buffer in one go because MyVirtualAllocEx() is probably a high overhead call.
if ( !(p_remote_lvi = AllocInterProcMem(handle, sizeof(local_lvi) + _TSIZE(LV_REMOTE_BUF_SIZE), aHwnd, PROCESS_QUERY_INFORMATION)) ) // Allocate both the LVITEM struct and its internal string buffer in one go because VirtualAllocEx() is probably a high overhead call.
return SetErrorLevelOrThrow();
bool is_win9x = g_os.IsWin9x(); // Resolve once for possible slight perf./code size benefit.

LPVOID p_remote_text = (LPVOID)((UINT_PTR)p_remote_lvi + sizeof(local_lvi)); // The next buffer is the memory area adjacent to, but after the struct.
// PREPARE LVI STRUCT MEMBERS FOR TEXT RETRIEVAL
LVITEM lvi_for_nt; // Only used for NT/2k/XP method.
LVITEM &local_lvi = is_win9x ? *(LPLVITEM)p_remote_lvi : lvi_for_nt; // Local is the same as remote for Win9x.
// Subtract 1 because of that nagging doubt about size vs. length. Some MSDN examples subtract one,
// such as TabCtrl_GetItem()'s cchTextMax:
local_lvi.cchTextMax = LV_REMOTE_BUF_SIZE - 1; // Note that LVM_GETITEM doesn't update this member to reflect the new length.
local_lvi.pszText = (LPTSTR)p_remote_lvi + sizeof(LVITEM); // The next buffer is the memory area adjacent to, but after the struct.
if (IsProcess64Bit(handle))
{
// See the section below for comments.
local_lvi.i64.cchTextMax = LV_REMOTE_BUF_SIZE - 1;
local_lvi.i64.pszText = (UINT64)p_remote_text;
}
else
{
// Subtract 1 because of that nagging doubt about size vs. length. Some MSDN examples subtract one,
// such as TabCtrl_GetItem()'s cchTextMax:
local_lvi.i32.cchTextMax = LV_REMOTE_BUF_SIZE - 1; // Note that LVM_GETITEM doesn't update this member to reflect the new length.
local_lvi.i32.pszText = (UINT)p_remote_text;
}

LRESULT i, next, length, total_length;
bool is_selective = include_focused_only || include_selected_only;
Expand Down Expand Up @@ -2795,11 +2849,11 @@ ResultType Line::ControlGetListView(Var &aOutputVar, HWND aHwnd, LPTSTR aOptions
}
else
next = i;
for (local_lvi.iSubItem = (requested_col > -1) ? requested_col : 0 // iSubItem is which field to fetch. If it's zero, the item vs. subitem will be fetched.
; col_count == -1 || local_lvi.iSubItem < col_count // If column count is undetermined (-1), always make the attempt.
; ++local_lvi.iSubItem) // For each column:
for (local_lvi.i32.iSubItem = (requested_col > -1) ? requested_col : 0 // iSubItem is which field to fetch. If it's zero, the item vs. subitem will be fetched.
; col_count == -1 || local_lvi.i32.iSubItem < col_count // If column count is undetermined (-1), always make the attempt.
; ++local_lvi.i32.iSubItem) // For each column:
{
if ((is_win9x || WriteProcessMemory(handle, p_remote_lvi, &local_lvi, sizeof(LVITEM), NULL)) // Relies on short-circuit boolean order.
if (WriteProcessMemory(handle, p_remote_lvi, &local_lvi, sizeof(local_lvi), NULL)
&& SendMessageTimeout(aHwnd, LVM_GETITEMTEXT, next, (LPARAM)p_remote_lvi, SMTO_ABORTIFHUNG, 2000, (PDWORD_PTR)&length))
total_length += length;
//else timed out or failed, don't include the length in the estimate. Instead, the
Expand Down Expand Up @@ -2843,18 +2897,18 @@ ResultType Line::ControlGetListView(Var &aOutputVar, HWND aHwnd, LPTSTR aOptions
}

// iSubItem is which field to fetch. If it's zero, the item vs. subitem will be fetched:
for (local_lvi.iSubItem = (requested_col > -1) ? requested_col : 0
; col_count == -1 || local_lvi.iSubItem < col_count // If column count is undetermined (-1), always make the attempt.
; ++local_lvi.iSubItem) // For each column:
for (local_lvi.i32.iSubItem = (requested_col > -1) ? requested_col : 0
; col_count == -1 || local_lvi.i32.iSubItem < col_count // If column count is undetermined (-1), always make the attempt.
; ++local_lvi.i32.iSubItem) // For each column:
{
// Insert a tab before each column except the first and except when in single-column mode:
if (!single_col_mode && local_lvi.iSubItem && total_length < capacity) // If we're at capacity, it will exit the loops when the next field is read.
if (!single_col_mode && local_lvi.i32.iSubItem && total_length < capacity) // If we're at capacity, it will exit the loops when the next field is read.
{
*contents++ = '\t';
++total_length;
}

if (!(is_win9x || WriteProcessMemory(handle, p_remote_lvi, &local_lvi, sizeof(LVITEM), NULL)) // Relies on short-circuit boolean order.
if (!WriteProcessMemory(handle, p_remote_lvi, &local_lvi, sizeof(local_lvi), NULL)
|| !SendMessageTimeout(aHwnd, LVM_GETITEMTEXT, next, (LPARAM)p_remote_lvi, SMTO_ABORTIFHUNG, 2000, (PDWORD_PTR)&length))
continue; // Timed out or failed. It seems more useful to continue getting text rather than aborting the operation.

Expand All @@ -2872,21 +2926,12 @@ ResultType Line::ControlGetListView(Var &aOutputVar, HWND aHwnd, LPTSTR aOptions
// should not assume that the text will necessarily be placed in the specified
// buffer. The control may instead change the pszText member of the structure
// to point to the new text, rather than place it in the buffer."
if (is_win9x)
if (ReadProcessMemory(handle, p_remote_text, contents, length * sizeof(TCHAR), NULL))
{
tmemcpy(contents, local_lvi.pszText, length); // Usually benches a little faster than _tcscpy().
contents += length; // Point it to the position where the next char will be written.
total_length += length; // Recalculate length in case its different than the estimate (for any reason).
}
else
{
if (ReadProcessMemory(handle, local_lvi.pszText, contents, length * sizeof(TCHAR), NULL)) // local_lvi.pszText == p_remote_lvi->pszText
{
contents += length; // Point it to the position where the next char will be written.
total_length += length; // Recalculate length in case its different than the estimate (for any reason).
}
//else it failed; but even so, continue on to put in a tab (if called for).
}
//else it failed; but even so, continue on to put in a tab (if called for).
}
//else length is zero; but even so, continue on to put in a tab (if called for).
if (single_col_mode)
Expand Down
94 changes: 53 additions & 41 deletions source/util.cpp
Expand Up @@ -1716,58 +1716,70 @@ DWORD ReadRegString(HKEY aRootKey, LPTSTR aSubkey, LPTSTR aValueName, LPTSTR aBu



LPVOID AllocInterProcMem(HANDLE &aHandle, DWORD aSize, HWND aHwnd)
// aHandle is an output parameter that holds the mapping for Win9x and the process handle for NT.
// Returns NULL on failure (in which case caller should ignore the value of aHandle).
BOOL IsProcess64Bit(HANDLE aHandle)
{
// ALLOCATE APPROPRIATE TYPE OF MEMORY (depending on OS type)
LPVOID mem;
if (g_os.IsWin9x()) // Use file-mapping method.
{
if ( !(aHandle = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, aSize, NULL)) )
return NULL;
mem = MapViewOfFile(aHandle, FILE_MAP_ALL_ACCESS, 0, 0, 0);
}
else // NT/2k/XP/2003 or later. Use the VirtualAllocEx() so that caller can use Read/WriteProcessMemory().
BOOL is32on64;
#ifdef _WIN64
// No need to load the function dynamically in this case since it should exist on all OSes
// which are able to load 64-bit executables (such as ours):
if (IsWow64Process(aHandle, &is32on64))
return !is32on64; // 64-bit if not running under WOW64.
// Since above didn't return, an error occurred. MSDN isn't clear about what conditions can
// cause this, so for simplicity just assume the target process is 64-bit (like this one).
return TRUE;
#else
// Load function dynamically to allow the program to launch on Win2k/XPSP1:
typedef BOOL (WINAPI *MyIsWow64ProcessType)(HANDLE, PBOOL);
static MyIsWow64ProcessType MyIsWow64Process = (MyIsWow64ProcessType)GetProcAddress(GetModuleHandle(_T("kernel32"))
, "IsWow64Process");
if (MyIsWow64Process && MyIsWow64Process(GetCurrentProcess(), &is32on64))
{
DWORD pid;
GetWindowThreadProcessId(aHwnd, &pid);
// Even if the PID is our own, open the process anyway to simplify the code. After all, it would be
// pretty silly for a script to access its own ListViews via this method.
if ( !(aHandle = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE, FALSE, pid)) )
return NULL; // Let ErrorLevel tell the story.
// Load function dynamically to allow program to launch on win9x:
typedef LPVOID (WINAPI *MyVirtualAllocExType)(HANDLE, LPVOID, SIZE_T, DWORD, DWORD);
static MyVirtualAllocExType MyVirtualAllocEx = (MyVirtualAllocExType)GetProcAddress(GetModuleHandle(_T("kernel32"))
, "VirtualAllocEx");
// Reason for using VirtualAllocEx(): When sending LVITEM structures to a control in a remote process, the
// structure and its pszText buffer must both be memory inside the remote process rather than in our own.
mem = MyVirtualAllocEx(aHandle, NULL, aSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
if (is32on64)
{
// We're running under WOW64. Since WOW64 only exists on 64-bit systems and on such systems
// 32-bit processes can run ONLY under WOW64, if the target process is also running under
// WOW64 it must be 32-bit; otherwise it must be 64-bit.
if (MyIsWow64Process(aHandle, &is32on64))
return !is32on64;
}
}
// Since above didn't return, one of the following is true:
// a) IsWow64Process doesn't exist, so the OS and all running processes must be 32-bit.
// b) IsWow64Process failed on the first or second call. MSDN isn't clear about what conditions
// can cause this, so for simplicity just assume the target process is 32-bit (like this one).
// c) The current process is not running under WOW64. Since we know it is 32-bit (due to our use
// of conditional compilation), the OS and all running processes must be 32-bit.
return FALSE;
#endif
}



LPVOID AllocInterProcMem(HANDLE &aHandle, DWORD aSize, HWND aHwnd, DWORD aExtraAccess)
// aHandle is an output parameter that receives the process handle.
// Returns NULL on failure (in which case caller should ignore the value of aHandle).
{
LPVOID mem;
DWORD pid;
GetWindowThreadProcessId(aHwnd, &pid);
// Even if the PID is our own, open the process anyway to simplify the code. After all, it would be
// pretty silly for a script to access its own ListViews via this method.
if ( !(aHandle = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE | aExtraAccess, FALSE, pid)) )
return NULL; // Let ErrorLevel tell the story.
// Reason for using VirtualAllocEx(): When sending LVITEM structures to a control in a remote process, the
// structure and its pszText buffer must both be memory inside the remote process rather than in our own.
mem = VirtualAllocEx(aHandle, NULL, aSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
if (!mem)
CloseHandle(aHandle); // Closes the mapping for Win9x and the process handle for other OSes. Caller should ignore the value of aHandle when return value is NULL.
//else leave the handle open (required for both methods). It's the caller's responsibility to close it.
CloseHandle(aHandle); // Caller should ignore the value of aHandle when return value is NULL.
//else leave the handle open. It's the caller's responsibility to close it.
return mem;
}



void FreeInterProcMem(HANDLE aHandle, LPVOID aMem)
// Caller has ensured that aMem is a file-mapping for Win9x and a VirtualAllocEx block for NT/2k/XP+.
// Similarly, it has ensured that aHandle is a file-mapping handle for Win9x and a process handle for NT/2k/XP+.
{
if (g_os.IsWin9x())
UnmapViewOfFile(aMem);
else
{
// Load function dynamically to allow program to launch on win9x:
typedef BOOL (WINAPI *MyVirtualFreeExType)(HANDLE, LPVOID, SIZE_T, DWORD);
static MyVirtualFreeExType MyVirtualFreeEx = (MyVirtualFreeExType)GetProcAddress(GetModuleHandle(_T("kernel32"))
, "VirtualFreeEx");
MyVirtualFreeEx(aHandle, aMem, 0, MEM_RELEASE); // Size 0 is used with MEM_RELEASE.
}
// The following closes either the mapping or the process handle, depending on OS type.
// But close it only after the above is done using it.
VirtualFreeEx(aHandle, aMem, 0, MEM_RELEASE); // Size 0 is used with MEM_RELEASE.
CloseHandle(aHandle);
}

Expand Down
3 changes: 2 additions & 1 deletion source/util.h
Expand Up @@ -749,7 +749,8 @@ void ScreenToWindow(POINT &aPoint, HWND aHwnd);
void CoordToScreen(int &aX, int &aY, int aWhichMode);
void CoordToScreen(POINT &aPoint, int aWhichMode);
void GetVirtualDesktopRect(RECT &aRect);
LPVOID AllocInterProcMem(HANDLE &aHandle, DWORD aSize, HWND aHwnd);
BOOL IsProcess64Bit(HANDLE aHandle);
LPVOID AllocInterProcMem(HANDLE &aHandle, DWORD aSize, HWND aHwnd, DWORD aExtraAccess = 0);
void FreeInterProcMem(HANDLE aHandle, LPVOID aMem);

DWORD GetEnvVarReliable(LPCTSTR aEnvVarName, LPTSTR aBuf);
Expand Down
18 changes: 6 additions & 12 deletions source/window.cpp
Expand Up @@ -784,9 +784,7 @@ ResultType StatusBarUtil(Var *aOutputVar, HWND aBarHwnd, int aPartNumber, LPTSTR
|| !(remote_buf = AllocInterProcMem(handle, _TSIZE(WINDOW_TEXT_SIZE + 1), aBarHwnd))) // Alloc mem last.
goto error;

TCHAR buf_for_nt[WINDOW_TEXT_SIZE + 1]; // Needed only for NT/2k/XP: the local counterpart to the buf allocated remotely above.
bool is_win9x = g_os.IsWin9x();
LPTSTR local_buf = is_win9x ? (LPTSTR)remote_buf : buf_for_nt; // Local is the same as remote for Win9x.
TCHAR local_buf[WINDOW_TEXT_SIZE + 1]; // The local counterpart to the buf allocated remotely above.

DWORD_PTR result, start_time;
--aPartNumber; // Convert to zero-based for use below.
Expand All @@ -805,16 +803,12 @@ ResultType StatusBarUtil(Var *aOutputVar, HWND aBarHwnd, int aPartNumber, LPTSTR
// Retrieve the bar's text:
if (SendMessageTimeout(aBarHwnd, SB_GETTEXT, aPartNumber, (LPARAM)remote_buf, SMTO_ABORTIFHUNG, SB_TIMEOUT, &result))
{
if (!is_win9x)
if (!ReadProcessMemory(handle, remote_buf, local_buf, _TSIZE(LOWORD(result) + 1), NULL)) // +1 to include the terminator (verified: length doesn't include zero terminator).
{
if (!ReadProcessMemory(handle, remote_buf, local_buf, _TSIZE(LOWORD(result) + 1), NULL)) // +1 to include the terminator (verified: length doesn't include zero terminator).
{
// Fairly critical error (though rare) so seems best to abort.
*local_buf = '\0'; // In case it changed the buf before failing.
break;
}
// Fairly critical error (though rare) so seems best to abort.
*local_buf = '\0'; // In case it changed the buf before failing.
break;
}
//else Win9x, in which case the local and remote buffers are the same (no copying is needed).

// Check if the retrieved text matches the caller's criteria. In addition to
// normal/intuitive matching, a match is also achieved if both are empty strings.
Expand Down Expand Up @@ -857,7 +851,7 @@ ResultType StatusBarUtil(Var *aOutputVar, HWND aBarHwnd, int aPartNumber, LPTSTR
// Note we use a temp buf rather than writing directly to the var contents above, because
// we don't know how long the text will be until after the above operation finishes.
ResultType result_to_return = aOutputVar ? aOutputVar->Assign(local_buf) : OK;
FreeInterProcMem(handle, remote_buf); // Don't free until after the above because above needs file mapping for Win9x.
FreeInterProcMem(handle, remote_buf);
return result_to_return;

error:
Expand Down

0 comments on commit c19397d

Please sign in to comment.