Inline CFString constants
This simple improvement can substantially speed up analysis of Objective C code: CFString
text constants are immediately visible in the output.
Psedudocode v1.5
void *__cdecl _PMProxy_showHistroyWindow__()
{
void *v0; // eax@1
v0 = objc_msgSend("NSWorkspace", "sharedWorkspace");
return objc_msgSend(v0, "openFile:withApplication:", &cfstr_VarLogCupsErro, &cfstr_Console);
}
Pseudocode v1.6
void *__cdecl _PMProxy_showHistroyWindow__()
{
void *v0; // eax@1
v0 = objc_msgSend("NSWorkspace", "sharedWorkspace");
return objc_msgSend(v0, "openFile:withApplication:", CFSTR("/var/log/cups/error_log"), CFSTR("Console"));
}
More humanly if-conditions
The decompiler generates much more readable text by dividing complex
conditions into smaller chunks. The output is longer but hey, sometimes
it makes sense to be verbose! :)
Psedudocode v1.5
pos1 = *mark1;
err.x = parseMark(_viewer, &_text, mark1, _pInfo, &_iLineNo, &point1);
if ( err.x
|| mark2 && (pos2 = *mark2, (err.x = parseMark(_viewer, &pageNo2, mark2, _pInfo, &lineNo2, &err)) != 0)
|| pRects && ((curPage = _viewer->nPage, pageNo2 < curPage) || _text > curPage) )
return err.x;
Pseudocode v1.6
pos1 = *mark1;
err.x = parseMark(_viewer, &_text, mark1, _pInfo, &_iLineNo, &point1);
if ( err.x )
return err.x;
if ( mark2 )
{
pos2 = *mark2;
err.x = parseMark(_viewer, &pageNo2, mark2, _pInfo, &lineNo2, &err);
if ( err.x )
return err.x;
}
if ( pRects )
{
curPage = _viewer->nPage;
if ( pageNo2 < curPage || _text > curPage )
return err.x;
}
Support for CONTAINING_RECORD macro
iv class="cmptell">
The decompiler knows how to use the
CONTAINING_RECORD macro
it the output to get rid of typecasts.
As soon as the variable types are correctly set,
it replaces casts with a simple macro call.
Psedudocode v1.5
_HW_STREAM_OBJECT *HwStreamObject;
_STREAM_OBJECT *StreamObject; // esi@1
StreamObject = (_STREAM_OBJECT *)((char *)&HwStreamObject[-2] - 36);
Pseudocode v1.6
_HW_STREAM_OBJECT *HwStreamObject;
_STREAM_OBJECT *StreamObject; // esi@1
StreamObject = CONTAINING_RECORD(HwStreamObject, _STREAM_OBJECT, HwStreamObject);
Support for LIST_ENTRY macros
We added recognition of LIST_ENTRY macros.
While not all cases are handled yet, it usually works quite well. Saves you from the mental
effort of recognizing these macros yourself.
Psedudocode v1.5
int __stdcall SCStartRequestOnStream(_STREAM_OBJECT *a1, _DEVICE_EXTENSION *a2)
{
char *v2; // ebx@1
int result; // eax@6
v2 = (char *)&a2->SpinLock;
KefAcquireSpinLockAtDpcLevel(&a2->SpinLock);
if ( a1->ReadyForNextDataReq && a1->DataPendingQueue.Flink != &a1->DataPendingQueue )
{
SCDequeueAndStartStreamDataRequest(a1);
KefAcquireSpinLockAtDpcLevel(v2);
}
if ( a1->ReadyForNextControlReq && a1->ControlPendingQueue.Flink != &a1->ControlPendingQueue )
result = SCDequeueAndStartStreamControlRequest(a1);
else
result = KefReleaseSpinLockFromDpcLevel(v2);
return result;
}
Pseudocode v1.6
void __stdcall SCStartRequestOnStream(_STREAM_OBJECT *a1, _DEVICE_EXTENSION *a2)
{
KSPIN_LOCK *v2; // ebx@1
v2 = &a2->SpinLock;
KefAcquireSpinLockAtDpcLevel(&a2->SpinLock);
if ( a1->ReadyForNextDataReq && !IsListEmpty(&a1->DataPendingQueue) )
{
SCDequeueAndStartStreamDataRequest(a1);
KefAcquireSpinLockAtDpcLevel(v2);
}
if ( !a1->ReadyForNextControlReq || IsListEmpty(&a1->ControlPendingQueue) )
KefReleaseSpinLockFromDpcLevel(v2);
else
SCDequeueAndStartStreamControlRequest(a1);
}
Better tail call recognition
A much better recognition of tail call optimization leads to less JUMPOUT() calls
in the output. The call arguments are recognized correctly. The function
return value is not lost anymore.
Psedudocode v1.5
BOOL __stdcall BaseDllInitialize(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
{
if ( fdwReason == 1 )
__security_init_cookie();
JUMPOUT(*(int *)_BaseDllInitialize);
}
Pseudocode v1.6
BOOL __stdcall BaseDllInitialize(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
{
if ( fdwReason == 1 )
__security_init_cookie();
return _BaseDllInitialize(hinstDLL, fdwReason, lpReserved);
}
Improved memset recognition
Six non-trivial lines of code have been collapsed into one simple line. We are happy with this improvement!
Psedudocode v1.5
v15 = v10->cbSize;
v16 = v10->cbSize >> 2;
memset(v10, 0, 4 * v16);
v17 = (int)((char *)v10 + 4 * v16);
for ( i = v15 & 3; i; --i )
*(_BYTE *)v17++ = 0;
Pseudocode v1.6
memset(v10, 0, v10->cbSize);
Support for TEB/KPCR references
No more cryptic offsets anymore. As an additional bonus, the decompiler
automatically determines variable types because it can use the
TEB layout.
By the way, KPCR fields are recognized too, you just need to load the corresponding til file!
Psedudocode v1.5
SessionId = *(_DWORD *)(*(_DWORD *)(__readfsdword(24) + 48) + 468);
Pseudocode v1.6
SessionId = NtCurrentTeb()->ProcessEnvironmentBlock->SessionId;
Better char/short variable recognition
The previous version of the decompiler failed to create a 16-bit variable
that was stored by the compiler in bx
.
This had some very nasty consequences: the function prototype
had an incorrect input argument (ebx) and the calling convention was wrong.
While it was possible to correct it by specifying the function prototype
manually, the new version lifts this burden from you.
The new version takes care of this situation much better. It uses a more fine-grained approach to variable allocation.
It created a small 16-bit variable v4
. No more ugly LOWORD() macro,
the output is cleaner. The correctly determined function prototype will help
when decompiling other functions as well because there will be less parasitic
arguments and less confusion.
Psedudocode v1.5
signed int __userpurge sub_600112F7<eax>(int a1<ecx>, int a2<ebx>, int a3, int a4)
{
v10 = a1;
LOWORD(a2) = *(_WORD *)(a3 + 12);
Pseudocode v1.6
signed int __thiscall sub_600112F7(void *this, int a2, int a3)
{
unsigned __int16 v4; // bx@1
v10 = this;
v4 = *(_WORD *)(a2 + 12);
Better recognition of inline functions
Sorry for a long sample, the previous version of the decompiler was not handling
strlen() well enough. It is a never ending fight and perfection is impossible,
but we still continue to work on it.
Recognition of inline functions is an incredibly hard problem,
but the new version has a better engine to recognize them. There is plenty of room for improvement, to put it mildly.
Psedudocode v1.5
v27 = (char *)operator new(v24);
v28 = v27;
strcpy(v27, *v3);
v29 = -1;
v30 = " ";
do
{
if ( !v29 )
break;
v12 = *v30++ == 0;
--v29;
}
while ( !v12 );
v31 = ~v29;
v32 = v31;
v33 = &v30[-v31];
v34 = -1;
v35 = v27;
do
{
if ( !v34 )
break;
v12 = *v35++ == 0;
--v34;
}
while ( !v12 );
memcpy(v35 - 1, v33, v32);
v36 = v3[1];
v37 = -1;
do
{
if ( !v37 )
break;
v12 = *v36++ == 0;
--v37;
}
while ( !v12 );
v38 = ~v37;
v39 = &v36[-v38];
v40 = v38;
v41 = v27;
v42 = -1;
do
{
if ( !v42 )
break;
v12 = *v41++ == 0;
--v42;
}
while ( !v12 );
memcpy(v41 - 1, v39, v40);
v43 = " ";
v44 = -1;
do
{
if ( !v44 )
break;
v12 = *v43++ == 0;
--v44;
}
while ( !v12 );
v45 = ~v44;
v46 = &v43[-v45];
v47 = v45;
v48 = v27;
v49 = -1;
do
{
if ( !v49 )
break;
v12 = *v48++ == 0;
--v49;
}
while ( !v12 );
memcpy(v48 - 1, v46, v47);
v50 = v3[2];
v51 = -1;
do
{
if ( !v51 )
break;
v12 = *v50++ == 0;
--v51;
}
while ( !v12 );
v52 = ~v51;
v53 = &v50[-v52];
v54 = v52;
v55 = v27;
v56 = -1;
do
{
if ( !v56 )
break;
v12 = *v55++ == 0;
--v56;
}
while ( !v12 );
memcpy(v55 - 1, v53, v54);
if ( v3[3] )
{
argca = v3 + 3;
v91 = v3 + 3;
do
{
v57 = " \"";
v58 = -1;
do
{
if ( !v58 )
break;
v12 = *v57++ == 0;
--v58;
}
while ( !v12 );
v59 = ~v58;
v60 = &v57[-v59];
v61 = v59;
v62 = v27;
v63 = -1;
do
{
if ( !v63 )
break;
v12 = *v62++ == 0;
--v63;
}
while ( !v12 );
memcpy(v62 - 1, v60, v61);
v64 = *argca;
v65 = -1;
do
{
if ( !v65 )
break;
v12 = *v64++ == 0;
--v65;
}
while ( !v12 );
v66 = ~v65;
v67 = &v64[-v66];
v68 = v66;
v69 = v27;
v70 = -1;
do
{
if ( !v70 )
break;
v12 = *v69++ == 0;
--v70;
}
while ( !v12 );
memcpy(v69 - 1, v67, v68);
if ( (*argca)[strlen(*argca) - 1] == 92 )
{
v71 = "\\";
v72 = -1;
do
{
if ( !v72 )
break;
v12 = *v71++ == 0;
--v72;
}
while ( !v12 );
v73 = ~v72;
v74 = &v71[-v73];
v75 = v73;
v76 = v27;
v77 = -1;
do
{
if ( !v77 )
break;
v12 = *v76++ == 0;
--v77;
}
while ( !v12 );
memcpy(v76 - 1, v74, v75);
}
v78 = "\"";
v79 = -1;
do
{
if ( !v79 )
break;
v12 = *v78++ == 0;
--v79;
}
while ( !v12 );
v80 = ~v79;
v81 = &v78[-v80];
v82 = v80;
v83 = v27;
v84 = -1;
do
{
if ( !v84 )
break;
v12 = *v83++ == 0;
--v84;
}
while ( !v12 );
memcpy(v83 - 1, v81, v82);
++v91;
argca = v91;
}
while ( *v91 );
}
Pseudocode v1.6
v18 = (char *)operator new(v15);
strcpy(v18, *v3);
strcat(v18, " ");
strcat(v18, v3[1]);
strcat(v18, " ");
strcat(v18, v3[2]);
if ( v3[3] )
{
argca = v3 + 3;
v25 = v3 + 3;
do
{
strcat(v18, " \"");
strcat(v18, *argca);
if ( (*argca)[strlen(*argca) - 1] == 92 )
strcat(v18, "\\");
strcat(v18, "\"");
++v25;
argca = v25;
}
while ( *v25 );
}
Structure copying - 1
While it is not pure C, we feel that using C++ style structure copying
operations in the output adds to clarity. The above sample is almost perfect, this only
possible improvement is to
map _this
to this
.
The new decompiler can do that, read our blog post for more info.
Psedudocode v1.5
void __thiscall FileInfo::InitTime(FileInfo *this)
{
FileInfo *_this; // ST08_4@1
_SYSTEMTIME st; // [sp+4h] [bp-18h]@1
_FILETIME ft; // [sp+14h] [bp-8h]@1
_this = this;
GetSystemTime(&st);
SystemTimeToFileTime(&st, &ft);
_this->m_CreateTime.dwLowDateTime = ft.dwLowDateTime;
_this->m_CreateTime.dwHighDateTime = ft.dwHighDateTime;
_this->m_AccessTime.dwLowDateTime = ft.dwLowDateTime;
_this->m_AccessTime.dwHighDateTime = ft.dwHighDateTime;
_this->m_WriteTime.dwLowDateTime = ft.dwLowDateTime;
_this->m_WriteTime.dwHighDateTime = ft.dwHighDateTime;
}
Pseudocode v1.6
void __thiscall FileInfo::InitTime(FileInfo *this)
{
FileInfo *_this; // ST08_4@1
_SYSTEMTIME st; // [sp+4h] [bp-18h]@1
_FILETIME ft; // [sp+14h] [bp-8h]@1
_this = this;
GetSystemTime(&st);
SystemTimeToFileTime(&st, &ft);
_this->m_CreateTime = ft;
_this->m_AccessTime = ft;
_this->m_WriteTime = ft;
}
Structure copying - 2
Please note that after collapsing several assignments into one we also got rid
of the intermediary v30
variable. Very good!
Psedudocode v1.5
v30 = &(*pRects)[roff / 0x10u];
v30->x = r.x;
v30->y = r.y;
v30->width = r.width;
v30->height = r.height;
Pseudocode v1.6
(*pRects)[roff / 0x10u] = r;
Structure copying - 3
Sometimes compilers copy structures by DWORD's, regardless of member types.
The previous version of the decompiler diligently represented these assignments in the best form it could.
However, using a structure copy operation is much better, it is concise and precise.
Psedudocode v1.5
_this = &machine->pCallObject->this;
_hostobj = &machine->curHostObject;
machine->curHostObject.pNext = _this->pNext;
*(_DWORD *)&_hostobj->nSymID = *(_DWORD *)&_this->nSymID;
_hostobj->data = _this->data;
_hostobj->data2 = _this->data2;
Pseudocode v1.6
_hostobj = &machine->curHostObject;
*_hostobj = machine->pCallObject->this;
Support for union fields
Finally the decompiler has proper support for union fields. Previously
analysing code with unions could quickly turn into a nightmare because
the decompiler would just use the first union field and would not allow you to
change it. The code, while it had the field names, was very misleading
because these names could be completely wrong. This is what we have on the left sample.
The new version is much better in this aspect. First, it tries to determine
the best union field using several heuristic rules. It checks the disassembly listing for 'structure offset'
operands, checks the current context to select the best fit union field. In many cases no user intervention is
required. However, if the decompiler fails to pick the corrent union field,
the user can always correct it by selecting the desired union field manually.
Even complex situations like a union with another nested union or structure
are supported. Anonymous nested unions are represented correctly too.
Psedudocode v1.5
PIO_STACK_LOCATION _stacklocation; // [sp+10h] [bp-14h]@1
if ( *&stacklocation->Parameters.Create.FileAttributes == 0x224010 )
{
v8 = stacklocation->Parameters.Create.Options == 20;
Semaphore = 0;
if ( !v8 )
goto LABEL_18;
if ( stacklocation->Parameters.Create.SecurityContext < 1 )
goto LABEL_87;
v23 = Irp->AssociatedIrp.MasterIrp;
v33 = &Semaphore;
v32 = stacklocation->FileObject;
memcpy(&v27, v23, 0x14u);
DeviceObjecta = ChanMgrGetByHandleAndFileObject(v27, v28, v29, v30, v31, v32, &Semaphore);
if ( DeviceObjecta < 0 )
goto LABEL_92;
v24 = Irp->AssociatedIrp.MasterIrp;
v33 = &v36;
v32 = stacklocation->Parameters.Create.SecurityContext;
v6 = ChannelIRead(Semaphore, v24, v32, &v36);
LABEL_90:
v33 = Semaphore;
goto LABEL_91;
}
if ( *&stacklocation->Parameters.Create.FileAttributes == 2244628 )
{
v8 = stacklocation->Parameters.Create.Options == 20;
Semaphore = 0;
if ( v8 && stacklocation->Parameters.Create.SecurityContext == 1 )
{
v22 = Irp->AssociatedIrp.MasterIrp;
Pseudocode v1.6
PIO_STACK_LOCATION _stacklocation; // [sp+10h] [bp-14h]@1
if ( stacklocation->Parameters.DeviceIoControl.IoControlCode == 0x224010 )
{
v8 = stacklocation->Parameters.Create.Options == 20;
Semaphore = 0;
if ( !v8 )
goto LABEL_18;
if ( stacklocation->Parameters.Read.Length < 1 )
goto LABEL_87;
buf = Irp->AssociatedIrp.SystemBuffer;
v33 = &Semaphore;
v32 = stacklocation->FileObject;
memcpy(&v27, buf, 0x14u);
DeviceObjecta = ChanMgrGetByHandleAndFileObject(v27, v28, v29, v30, v31, v32, &Semaphore);
if ( DeviceObjecta < 0 )
goto LABEL_92;
v24 = Irp->AssociatedIrp.SystemBuffer;
v33 = &v36;
v32 = stacklocation->Parameters.DeviceIoControl.OutputBufferLength;
v6 = ChannelIRead(Semaphore, v24, v32, &v36);
LABEL_90:
v33 = Semaphore;
goto LABEL_91;
}
if ( stacklocation->Parameters.DeviceIoControl.IoControlCode == 2244628 )
{
v8 = stacklocation->Parameters.DeviceIoControl.InputBufferLength == 20;
Semaphore = 0;
if ( v8 && stacklocation->Parameters.DeviceIoControl.OutputBufferLength == 1 )
{
v22 = Irp->AssociatedIrp.SystemBuffer;
Support for merged calls
There is a common compiler optimization that reuses the same call instruction
for different calls. Of the left side, the WinHelpA() call is used in two
different situations. The decompiler had to use a goto
statement
because it could not represent the code with structured programming constructs.
The new version unmerged the call and got rid of the goto
statement.
Isn't it nice?
Psedudocode v1.5
INT_PTR __stdcall EditBinaryValueDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPHELPINFO hinfo)
{
LONG v5; // eax@8
HWND v6; // [sp-10h] [bp-14h]@10
const WCHAR *v7; // [sp-Ch] [bp-10h]@10
UINT v8; // [sp-8h] [bp-Ch]@10
ULONG_PTR v9; // [sp-4h] [bp-8h]@10
if ( uMsg == WM_HELP )
{
v9 = (ULONG_PTR)s_EditBinaryValueHelpIDs;
v8 = HELP_WM_HELP;
v7 = g_pHelpFileName;
v6 = hinfo->hItemHandle;
goto LABEL_12;
}
if ( uMsg == WM_CONTEXTMENU )
{
v9 = (ULONG_PTR)s_EditBinaryValueHelpIDs;
v8 = HELP_CONTEXTMENU;
v7 = g_pHelpFileName;
v6 = (HWND)wParam;
LABEL_12:
WinHelpW(v6, v7, v8, v9);
return 1;
}
if ( uMsg == WM_INITDIALOG )
return EditBinaryValue_OnInitDialog(hwndDlg, wParam, (LONG)hinfo);
if ( uMsg != 273 || (signed int)(unsigned __int16)wParam <= 0 || (signed int)(unsigned __int16)wParam > 2 )
return 0;
v5 = GetWindowLongW(hwndDlg, 8);
*(_DWORD *)(v5 + 8) = dword_105A048;
*(_DWORD *)(v5 + 4) = hMem;
hMem = 0;
EndDialog(hwndDlg, (unsigned __int16)wParam);
return 1;
}
Pseudocode v1.6
INT_PTR __stdcall EditBinaryValueDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPHELPINFO hinfo)
{
LONG v5; // eax@8
if ( uMsg == WM_HELP )
{
WinHelpW(hinfo->hItemHandle, g_pHelpFileName, HELP_WM_HELP, (ULONG_PTR)s_EditBinaryValueHelpIDs);
}
else
{
if ( uMsg == WM_CONTEXTMENU )
{
WinHelpW((HWND)wParam, g_pHelpFileName, HELP_CONTEXTMENU, (ULONG_PTR)s_EditBinaryValueHelpIDs);
}
else
{
if ( uMsg == WM_INITDIALOG )
return EditBinaryValue_OnInitDialog(hwndDlg, wParam, (LONG)hinfo);
if ( uMsg != 273 || (signed int)(unsigned __int16)wParam <= 0 || (signed int)(unsigned __int16)wParam > 2 )
return 0;
v5 = GetWindowLongW(hwndDlg, 8);
*(_DWORD *)(v5 + 8) = dword_105A048;
*(_DWORD *)(v5 + 4) = hMem;
hMem = 0;
EndDialog(hwndDlg, (unsigned __int16)wParam);
}
}
return 1;
}