Hex-Rays ARM64 Decompiler v2.4 Comparison Page

Welcome to the Hex-Rays ARM64 Decompiler v2.4 comparison page! Below you will find side-by-side comparisons of disassembly and decompiler. Please maximize the window too see both columns simultaneously.

The following original exhibits are displayed on this page:

  1. Simple case
  2. Floating arithmetics
  3. Magic multiplication/division operations
  4. Conditional instructions
  5. Syscalls
  6. Variadic functions
  7. NEON intrinsic functions
  8. Objective-C functions
Simple case
; void __fastcall __acrt_stdio_free_stream(__crt_stdio_stream stream) ___acrt_stdio_free_stream__YAXV__crt_stdio_stream___Z ; CODE XREF: _fclose_nolock+7C^Xp ; fclose+40^Xp var_10 = -0x10 SUB SP, SP, #0x10 STR X0, [SP,#0x10+var_10] STR XZR, [X0] LDR X8, [SP,#0x10+var_10] STR XZR, [X8,#8] LDR X9, [SP,#0x10+var_10] STR WZR, [X9,#0x10] LDR X8, [SP,#0x10+var_10] MOV W9, #0xFFFFFFFF STR W9, [X8,#0x18] LDR X8, [SP,#0x10+var_10] STR WZR, [X8,#0x1C] LDR X9, [SP,#0x10+var_10] STR WZR, [X9,#0x20] LDR X8, [SP,#0x10+var_10] STR XZR, [X8,#0x28] LDR X9, [SP,#0x10+var_10] ADD X8, X9, #0x14 STLR WZR, [X8] DMB ISH ADD SP, SP, #0x10 RET ; End of function __acrt_stdio_free_stream(__crt_stdio_stream) void __fastcall __acrt_stdio_free_stream(__crt_stdio_stream stream) { stream._stream->_public_file._Placeholder = 0i64; stream._stream->_base = 0i64; stream._stream->_cnt = 0; stream._stream->_file = -1; stream._stream->_charbuf = 0; stream._stream->_bufsiz = 0; stream._stream->_tmpfname = 0i64; __stlr(0, (unsigned int *)&stream._stream->_flags); __dmb(_ARM64_BARRIER_ISH); }
This simple function accepts a structure and fills another structure pointed by a member of the input structure. Also we can see here that instruction STLR and DMB are translated into intrinsic function calls __stlr() and __dmb() respectively. While the function logic is obvious by just looking at the decompiler output, the assembly listing has too much noise and requires studying it.

The decompiler saves your time and allows you to concentrate on more exciting aspects of reverse engineering.
Floating arithmetics
__x2y2m1f ; CODE XREF: clogf+1AC^Xp ; catanf:loc_28D88^Xp ... FCVT D0, S0 FMOV D2, #1.0 FCVT D1, S1 FSUB D3, D0, D2 FMUL D1, D1, D1 FADD D2, D0, D2 FMADD D0, D3, D2, D1 FCVT S0, D0 RET ; End of function __x2y2m1f float __fastcall _x2y2m1f(float a1, float a2) { return a1 * a2 + (a1 - 1.0) * (a1 + 1.0); }
The assembly listing does not look very long and complicated until you take a look at the decompiler output.
Magic multiplication/division operations
; __int64 int_u_mod_10(void) EXPORT _Z12int_u_mod_10v _Z12int_u_mod_10v var_s0 = 0 STP X29, X30, [SP,#-0x10+var_s0]! MOV X29, SP BL _Z1uv MOV W2, W0 MOV W0, #0xCCCD MOVK W0, #0xCCCC,LSL#16 UMADDL X0, W2, W0, XZR UBFM X0, X0, #0x20, #0x3F UBFM W1, W0, #3, #0x1F MOV W0, W1 UBFM W0, W0, #0x1E, #0x1D ADD W0, W0, W1 UBFM W0, W0, #0x1F, #0x1E SUB W1, W2, W0 MOV W0, W1 LDP X29, X30, [SP+var_s0],#0x10 RET ; End of function int_u_mod_10(void) __int64 int_u_mod_10(void) { return u() % 0xA; }
Compilers can decompose a multiplication/division instruction into a sequence of cheaper instructions (additions, shifts, etc). This example demonstrates how the decompiler recognizes them and coagulates back to the original operation.
Conditional instructions
; int __cdecl cmp_s_i_bits() EXPORT _Z12cmp_s_i_bitsv _Z12cmp_s_i_bitsv var_20 = -0x20 var_10 = -0x10 STP X29, X30, [SP,#var_20]! MOV X29, SP STR X19, [SP,#0x20+var_10] BL _Z1sv ; s(void) SBFM W19, W0, #0, #0xF BL _Z1iv ; i(void) TST W0, #0xFFFFFFFC B.NE loc_108 AND W1, W0, #2 AND W0, W0, #1 CMP W1, WZR MOV W1, #2 CSEL W0, W0, W1, EQ LDR X19, [SP,#0x20+var_10] LDP X29, X30, [SP+0x20+var_20],#0x20 RET ; --------------------------------------------------------------------------- loc_108 ; CODE XREF: cmp_s_i_bits(void)+1C^Xj CMP W19, W0 B.EQ loc_124 MOV W0, #0xFFFFFFFF LDR X19, [SP,#0x20+var_10] CSINC W0, W0, WZR, GT LDP X29, X30, [SP+0x20+var_20],#0x20 RET ; --------------------------------------------------------------------------- loc_124 ; CODE XREF: cmp_s_i_bits(void)+44^Xj MOV W0, #0 LDR X19, [SP,#0x20+var_10] LDP X29, X30, [SP+0x20+var_20],#0x20 RET ; End of function cmp_s_i_bits(void) int __cdecl cmp_s_i_bits() { int v0; // [email protected] int v1; // [email protected] int v2; // [email protected] int result; // [email protected] v0 = s(); v1 = i(); if ( v1 & 0xFFFFFFFC ) { if ( v0 == v1 ) { result = 0; } else if ( v0 <= v1 ) { result = 1; } else { result = -1; } } else { v2 = v1 & 2; result = v1 & 1; if ( v2 ) result = 2; } return result; }
The decompiler output is not so shorter as in the previous example but is more readable because it is structured and we have no need to examine branches and special instructions like CSEL and CSINC.
WEAK kill kill ; CODE XREF: killpg+8^Xj ; exec_comm+21C^Yp MOV X8, #0x81 SVC 0 CMN X0, #0xFFF B.CS loc_323A4 RET ; --------------------------------------------------------------------------- loc_323A4 ; CODE XREF: kill+C^Xj B __GI___syscall_error ; End of function kill unsigned __int64 __fastcall kill(__pid_t a1, int a2) { unsigned int result;// [email protected] result = linux_eabi_syscall(__NR_kill, a1, a2); if ( result >= 0xFFFFFFFFFFFFF001LL ) result = _GI___syscall_error(); return result; }
In the above example the decompiler converts SVC instruction into a corresponding system call with a symbolic syscall code and appropriate arguments if possible. So in most cases you do not need to figure out which system function is called and which arguments in which registers it expects.
Variadic functions
EXPORT fd5 fd5 var_50 = -0x50 var_40 = -0x40 var_30 = -0x30 var_20 = -0x20 var_18 = -0x18 var_10 = -0x10 var_8 = -8 var_4 = -4 var_s0 = 0 var_s10 = 0x10 var_s20 = 0x20 var_s30 = 0x30 var_s40 = 0x40 var_s50 = 0x50 var_s60 = 0x60 var_s70 = 0x70 var_s88 = 0x88 var_s90 = 0x90 var_s98 = 0x98 STP X29, X30, [SP,#-0xA0+var_50]! ADD W0, W5, W0 MOV X29, SP ADD X1, X29, #0xF0 STR Q0, [X29,#0x50+var_s0] STR X1, [X29,#0x50+var_20] STR X1, [X29,#0x50+var_18] ADD X1, X29, #0xD0 STR X1, [X29,#0x50+var_10] MOV W1, #0xFFFFFF80 STR W1, [X29,#0x50+var_4] MOV W1, #0xFFFFFFF0 STR W1, [X29,#0x50+var_8] ADD X1, X29, #0x10 LDP X2, X3, [X29,#0x50+var_20] STP X2, X3, [X29,#0x50+var_40] LDP X2, X3, [X29,#0x50+var_10] STR X5, [X29,#0x50+var_s88] STR X6, [X29,#0x50+var_s90] STR X7, [X29,#0x50+var_s98] STR Q1, [X29,#0x50+var_s10] STR Q2, [X29,#0x50+var_s20] STR Q3, [X29,#0x50+var_s30] STR Q4, [X29,#0x50+var_s40] STR Q5, [X29,#0x50+var_s50] STR Q6, [X29,#0x50+var_s60] STR Q7, [X29,#0x50+var_s70] STP X2, X3, [X29,#0x50+var_30] BL fv SBFM X0, X0, #0, #0x1F LDP X29, X30, [SP+0x50+var_50],#0xF0 RET ; End of function fd5 _int64 fd5(int a1, __int64 a2, __int64 a3, __int64 a4, __int64 a5, ...) { va_list va; // [xsp+10h] [xbp+10h]@1 va_list va1; // [xsp+30h] [xbp+30h]@1 __int64 vars88; // [xsp+D8h] [xbp+D8h]@1 va_start(va1, a5); vars88 = va_arg(va1, _QWORD); va_copy(va, va1); return (signed int)fv((unsigned int)(vars88 + a1), va); }
64bit gcc defines quite sophisticated va_list structure, so it is not so easy to recognize it just looking at the disassembler code. And you don't have to because the decompiler can do this job for you.
NEON intrinsic functions
; call_vcage_f32(__Float32x2_t, __Float32x2_t) EXPORT _Z14call_vcage_f3213__Float32x2_tS_ _Z14call_vcage_f3213__Float32x2_tS_ var_30 = -0x30 var_28 = -0x28 var_20 = -0x20 var_18 = -0x18 var_10 = -0x10 var_8 = -8 SUB SP, SP, #-0x30 SUB SP, SP, #0x30 ADD X0, SP, #0x30+var_28 STR D0, [X0] MOV X0, SP STR D1, [X0,#0x30+var_30] ADD X0, SP, #0x30+var_8 ADD X1, SP, #0x30+var_28 LDR D0, [X1] UMOV X1, V0.D[0] NS 0.D[0], X1 STR D0, [X0] ADD X0, SP, #0x30+var_10 MOV X1, SP LDR D0, [X1,#0x30+var_30] UMOV X1, V0.D[0] INS V0.D[0], X1 STR D0, [X0] ADD X0, SP, #0x30+var_18 ADD X1, SP, #0x30+var_8 LDR D0, [X1] UMOV X1, V0.D[0] INS V0.D[0], X1 STR D0, [X0] ADD X0, SP, #0x30+var_18 LDR D0, [X0] UMOV X0, V0.D[0] INS V0.D[0], X0 FABS V0.2S, V0.2S UMOV X1, V0.D[0] ADD X0, SP, #0x30+var_28 ADD X2, SP, #0x30+var_10 LDR D0, [X2] UMOV X2, V0.D[0] INS V0.D[0], X2 STR D0, [X0] ADD X0, SP, #0x30+var_28 LDR D0, [X0] UMOV X0, V0.D[0] INS V0.D[0], X0 FABS V0.2S, V0.2S UMOV X0, V0.D[0] INS V0.D[0], X1 INS V1.D[0], X0 FCMGE V0.2S, V0.2S, V1.2S NOP ADD SP, SP, #0x30 RET ; End of function call_vcage_f32(__Float32x2_t,__Float32x2_t) int32x2_t __fastcall call_vcage_f32(float32x2_t a1, float32x2_t a2) { return vcge_f32(vabs_f32(a1), vabs_f32(a2)); }
This example illustrates how the decompiler translates NEON instructions into corresponding intrinsic function calls. The list of NEON SIMD intrinsics supported by the decompiler can be found at http://infocenter.arm.com/help/topic/com.arm.doc.ihi0073a/IHI0073A_arm_neon_intrinsics_ref.pdf.
Objective-C functions
; id __cdecl -[ABContactViewController contactView](struct ABContactViewController *self, SEL) __ABContactViewController_contactView_ ; DATA XREF: __objc_const:0000000000151030^Yo var_10 = -0x10 var_s0 = 0 INS V1.D[0], X0 STP X29, X30, [SP,#-0x10+var_s0]! MOV X29, SP STP X20, X19, [SP,#var_10]! MOV X19, X0 ADRP X8, #selRef_isEditing@PAGE ADD X8, X8, #selRef_isEditing@PAGEOFF LDR X1, [X8] BL _objc_msgSend CBZ W0, loc_17670 ADRP X8, #selRef_editingContactView@PAGE ADD X8, X8, #selRef_editingContactView@PAGEOFF B loc_17678 ; --------------------------------------------------------------------------- loc_17670 ; CODE XREF: -[ABContactViewController contactView]+20^Xj ADRP X8, #selRef_displayContactView@PAGE ADD X8, X8, #selRef_displayContactView@PAGEOFF loc_17678 ; CODE XREF: -[ABContactViewController contactView]+2C^Xj LDR X1, [X8] MOV X0, X19 LDP X20, X19, [SP+0x10+var_10],#0x10 LDP X29, X30, [SP+var_s0],#0x10 B _objc_msgSend ; End of function -[ABContactViewController contactView] id __cdecl -[ABContactViewController contactView](struct ABContactViewController *self, SEL a2) { struct ABContactViewController *v2; // [email protected] char **v3; // [email protected] v2 = self; if ( (unsigned int)objc_msgSend(self, "isEditing") ) v3 = &selRef_editingContactView; else v3 = &selRef_displayContactView; return (id)objc_msgSend(v2, *v3); }
In this short example you can see the decompiler output for an Objective-C function. It's obvious that it is much clearer than the assembly listing. Just imagine the difference for a function having tens or even hundreds of objc_msgSend() calls.

NOTE: these are just some selected examples that can be illustrated as side-by-side differences. There are lots of features that are not mentioned on this page - simply because there was nothing to compare them with. The ARM64 decompiler have more intrinsic functions, can handle indirect calls (the user can control the call arguments), structures passed and returned by value, user defined calling conventions, etc.

Hex-Rays ARM64 Decompiler v2.4 is capable of handling real world compiler generated code.

This is all for the moment. Please come back for more examples!