My colleague Cory Cohen recently asked me to take a look at some surprising behavior in Ghidra's decompiler. He had a decompiled function that looked like this:
/* WARNING: Globals starting with '_' overlap smaller symbols at the same address */
void HI_MPI_SVP_NNIE_LoadModel(model *model_buf,uint *out_buf,ulong param3)
{
uint64_t uVar1;
byte bVar2;
byte bVar3;
ushort uVar4;
ushort uVar5;
short sVar6;
long lVar7;
uint32_t error;
int iVar8;
int flag;
ulong uVar9;
ulong neg_counter;
byte *pbVar10;
byte *pbVar11;
uint uVar12;
ulong uVar13;
uint64_t vir_addr;
long lVar14;
uint uVar15;
ulong uVar16;
byte *local_e8;
uint local_d8;
uint local_d4;
header header_buf;
ulong counter;
uint size;
lVar7 = ___stack_chk_guard;
_header_buf = 0;
error = svp_nnie_check_loadmodel_param_user(model_buf,(long)out_buf);
if (error == 0) {
size = (uint)model_buf->size;
if (size < 0xc1) {
error = 0xa0338003;
fprintf(_stderr,
"[Func]:%s [Line]:%d [Info]:Error(%#x): model_buf->size(%u) must be greater than %u!\n "
,"hi_mpi_svp_nnie_load_model",0x345,0xa0338003,(ulong)size,0xc0);
}
else {
vir_addr = model_buf->vir_addr;
memcpy_s(&header_buf,0xc0,vir_addr);
if (false) { fprintf(_stderr,
"[Func]:%s [Line]:%d [Info]:Error(%#x): model_buf->size(%d) is less than %d!\n",
"svp_nnie_parse_model_header",0x1b2,0xa0338003);
}
else {
if (false) {
flag = -1;
}
else {
neg_counter = 0;
pbVar10 = (byte *)(vir_addr + 4);
do {
pbVar11 = pbVar10 + 1;
size = *(uint *)(&DAT_00105d70 + ((neg_counter ^ *pbVar10) & 0xff) * 4) ^
(uint)neg_counter >> 8;
neg_counter = (ulong)size;
pbVar10 = pbVar11;
} while ((byte *)(vir_addr + 0x100000000) != pbVar11);
flag = ~size;
}
if (header_buf.unk1 == flag) {
memset_s(out_buf,0x36a8,0);
if (true) {
fprintf(_stderr,
"[Func]:%s [Line]:%d [Info]:Error(%#x):model_version(%u) of input model should b e %d!\n"
,"svp_nnie_parse_model_header",0x1c3,0xa0338003,0,0xb);
}
else if (true) {
fprintf(_stderr,
"[Func]:%s [Line]:%d [Info]:Error(%#x):arch_version(%u) of input model should be %d!\n"
,"svp_nnie_parse_model_header",0x1c6,0xa0338003);
}
else {
*out_buf = 0;
if (false) {
fprintf(_stderr,
"[Func]:%s [Line]:%d [Info]:Error(%#x): the run_mode(%d) of input model should be %d!\n"
,"svp_nnie_parse_model_header",0x1d3,0xa0338003,0,0);
}
else {
out_buf[2] = 0;
if (true) {
fprintf(_stderr,
"[Func]:%s [Line]:%d [Info]:Error(%#x): the net_seg_num(%u) of input model s hould be (0,%d]!\n"
,"svp_nnie_parse_model_header",0x1d7,0xa0338003,0,8);
}
else {
out_buf[1] = 0;
if (true) {
fprintf(_stderr,
"[Func]:%s [Line]:%d [Info]:Error(%#x): the u32TemBufSize(%u) of input mod el can\'t be 0!\n"
,"svp_nnie_parse_model_header",0x1db,0xa0338003,0);
}
else {
if (true) {
pbVar10 = (byte *)model_buf->vir_addr;
bVar2 = *pbVar10;
size = *(uint *)(pbVar10 + 8);
out_buf[3] = (uint)bVar2;
bVar3 = pbVar10[1];
neg_counter = (ulong)bVar3;
*(ushort *)(out_buf + 4) = (ushort)bVar3;
*(ushort *)((long)out_buf + 0x12) = (ushort)pbVar10[2];
*(ushort *)(out_buf + 5) = (ushort)pbVar10[3];
uVar4 = *(ushort *)(pbVar10 + 4);
out_buf[6] = size;
size = *(uint *)(pbVar10 + 0xc);
*(ushort *)((long)out_buf + 0x16) = uVar4;
out_buf[7] = size;
if (bVar2 < 3) {
local_d8 = 0;
local_d4 = 0;
uVar15 = 0;
local_e8 = pbVar10;
if (bVar3 < 0x11) {
do {
counter = (ulong)local_d4;
lVar14 = counter * 0x69c;
uVar5 = *(ushort *)((long)out_buf + lVar14 + 0x12);
uVar9 = (ulong)uVar5;
if (0x10 < uVar5) {
fprintf(_stderr,
"[Func]:%s [Line]:%d [Info]:Error(%#x): model->seg[%d].dst_num(% u) can\'t be greater than %u!\n"
,"svp_nnie_parse_seg_header",0x20d,0xa0338003,(ulong)local_d4,
uVar9,0x10);
goto LAB_001024b8;
}
if (4 < (ushort)out_buf[counter * 0x1a7 + 5]) {
fprintf(_stderr,
"[Func]:%s [Line]:%d [Info]:Error(%#x): model->seg[%d].roi_pool_ num(%u) can\'t be greater than %u!\n"
,"svp_nnie_parse_seg_header",0x212,0xa0338003,(ulong)local_d4,
(ulong)(ushort)out_buf[counter * 0x1a7 + 5],4);
goto LAB_001024b8;
}
if (0x400 < uVar4) {
fprintf(_stderr,
"[Func]:%s [Line]:%d [Info]:Error(%#x): model->seg[%d].max_step( %u) can\'t be greater than %u!\n"
,"svp_nnie_parse_seg_header",0x216,0xa0338003,(ulong)local_d4,
(ulong)(uint)uVar4,0x400);
goto LAB_001024b8;
}
uVar13 = (ulong)out_buf[counter * 0x1a7 + 6];
if (out_buf[counter * 0x1a7 + 6] != 0) {
fprintf(_stderr,
"[Func]:%s [Line]:%d [Info]:Error(%#x): model->seg[%d].inst_offs et(%u) can\'t be greater than %u!\n"
,"svp_nnie_parse_seg_header",0x21c,0xa0338003,(ulong)local_d4,
uVar13,0);
goto LAB_001024b8;
}
if (uVar13 + size != 0) {
fprintf(_stderr,
"[Func]:%s [Line]:%d [Info]:Error(%#x): model->seg[%d].inst_offs et(%u)+model->seg[%d].inst_len(%u) can\'t be greater than %u!\n"
,"svp_nnie_parse_seg_header",0x222,0xa0338003,(ulong)local_d4,
uVar13,(ulong)local_d4,size,0);
goto LAB_001024b8;
}
if ((int)neg_counter != 0) {
uVar9 = (ulong)local_d4;
out_buf[uVar9 * 0x1a7 + 10] = *(uint *)(local_e8 + 0x10);
out_buf[uVar9 * 0x1a7 + 9] = *(uint *)(local_e8 + 0x14);
out_buf[uVar9 * 0x1a7 + 0xb] = *(uint *)(local_e8 + 0x18);
uVar4 = *(ushort *)(local_e8 + 0x1e);
neg_counter = (ulong)uVar4;
if ((uVar4 - 2 & 0xfffd) == 0 || 5 < uVar4) {
LAB_00102388:
fprintf(_stderr,
"[Func]:%s [Line]:%d [Info]:Error(%#x), the image type(%d) is not supported, image type can\'t be {%d, %d, %d}!\n"
,"svp_nnie_parse_src_node_info",0x248,0xa0338003,neg_counter,2
,4,6);
error = 0xa0338003;
fprintf(_stderr,
"[Func]:%s [Line]:%d [Info]:Error(%#x): parse %d-th seg src no de failed!\n"
,"hi_mpi_svp_nnie_load_model",0x358,0xa0338003,(ulong)local_d4
);
goto joined_r0x001020c4;
}
size = 0;
uVar13 = 0;
pbVar11 = local_e8 + 0x20;
while( true ) {
uVar12 = (uint)neg_counter;
if (uVar12 == 3) {
out_buf[uVar13 * 0xd + uVar9 * 0x1a7 + 8] = 2;
sVar6 = *(short *)(pbVar11 + -4);
}
else {
if (uVar12 == 5) {
out_buf[uVar13 * 0xd + uVar9 * 0x1a7 + 8] = 3;
}
else {
out_buf[uVar13 * 0xd + uVar9 * 0x1a7 + 8] = uVar12;
}
sVar6 = *(short *)(pbVar11 + -4);
}
if (sVar6 == 1) {
uVar12 = *(uint *)(pbVar11 + -8);
out_buf[uVar13 * 0xd + uVar9 * 0x1a7 + 8] = 4;
out_buf[uVar13 * 0xd + uVar9 * 0x1a7 + 9] = uVar12;
out_buf[uVar13 * 0xd + uVar9 * 0x1a7 + 0xb] =
*(uint *)(pbVar11 + -0xc);
}
if (size == 0 && out_buf[counter * 0x1a7 + 3] == 2) {
uVar12 = *(uint *)(pbVar11 + -8);
out_buf[uVar9 * 0x1a7 + 8] = 5;
out_buf[uVar9 * 0x1a7 + 9] = uVar12;
}
neg_counter = (ulong)size;
size = size + 1;
strncpy_s(out_buf + neg_counter * 0xd + counter * 0x1a7 + 0xd,0x20,
pbVar11,0x1f);
*(undefined1 *)((long)out_buf + uVar9 * 0x69c + uVar13 * 0x34 + 0x53)
= 0;
neg_counter = (ulong)(ushort)out_buf[counter * 0x1a7 + 4];
if ((ushort)out_buf[counter * 0x1a7 + 4] <= size) break;
uVar13 = (ulong)size;
out_buf[uVar13 * 0xd + uVar9 * 0x1a7 + 10] = *(uint *)(pbVar11 + 0x30)
;
out_buf[uVar13 * 0xd + uVar9 * 0x1a7 + 9] = *(uint *)(pbVar11 + 0x34);
out_buf[uVar13 * 0xd + uVar9 * 0x1a7 + 0xb] =
*(uint *)(pbVar11 + 0x38);
uVar4 = *(ushort *)(pbVar11 + 0x3e);
neg_counter = (ulong)uVar4;
pbVar11 = pbVar11 + 0x40;
if ((uVar4 - 2 & 0xfffd) == 0 || 5 < uVar4) goto LAB_00102388;
}
uVar9 = (ulong)*(ushort *)((long)out_buf + lVar14 + 0x12);
}
if ((int)uVar9 != 0) {
if (uVar15 < 0xffffffef) {
uVar13 = (ulong)local_d4;
pbVar11 = local_e8 + neg_counter * 0x40 + 0x20;
uVar9 = 0;
LAB_00101f54:
while (pbVar10[(ulong)uVar15 + 0x11] != 1) {
uVar15 = uVar15 + 0x30;
if (uVar15 != 0) {
fprintf(_stderr,
"[Func]:%s [Line]:%d [Info]:Error(%#x), layer_info_offset( %d) is greater than layer_info_len(%d)!\n"
,"svp_nnie_parse_dst_node_id",0x28e,0xa0338003,
(ulong)uVar15,0);
goto LAB_00102050;
}
}
uVar16 = (ulong)uVar15;
uVar15 = uVar15 + 0x30;
out_buf[uVar9 * 0xd + uVar13 * 0x1a7 + 0xdc] =
(uint)*(ushort *)(pbVar10 + uVar16 + 0x1a);
if (out_buf[counter * 0x1a7 + 3] == 2) {
if (*(short *)(pbVar11 + -4) != 1) {
fprintf(_stderr,
"[Func]:%s [Line]:%d [Info]:Error(%#x), if net type is %d, the %d-th report data should be tensor!\n"
,"svp_nnie_parse_dst_node_info",700,0,
(ulong)out_buf[counter * 0x1a7 + 3],uVar9);
uVar9 = (ulong)*(ushort *)((long)out_buf + lVar14 + 0x12);
sVar6 = (short)out_buf[counter * 0x1a7 + 5];
goto joined_r0x0010243c;
}
if (*(short *)(pbVar11 + -2) == 1) {
out_buf[uVar9 * 0xd + uVar13 * 0x1a7 + 0xd9] =
*(uint *)(pbVar11 + -0x10);
out_buf[uVar9 * 0xd + uVar13 * 0x1a7 + 0xd8] = 5;
}
else {
out_buf[uVar9 * 0xd + uVar13 * 0x1a7 + 0xda] = 1;
out_buf[uVar9 * 0xd + uVar13 * 0x1a7 + 0xdb] = 1;
out_buf[uVar9 * 0xd + uVar13 * 0x1a7 + 0xd8] = 4;
out_buf[uVar9 * 0xd + uVar13 * 0x1a7 + 0xd9] =
*(uint *)(pbVar11 + -0x10);
}
}
else {
out_buf[uVar9 * 0xd + uVar13 * 0x1a7 + 0xda] =
*(uint *)(pbVar11 + -0x10);
out_buf[uVar9 * 0xd + uVar13 * 0x1a7 + 0xd9] =
*(uint *)(pbVar11 + -0xc);
out_buf[uVar9 * 0xd + uVar13 * 0x1a7 + 0xdb] =
*(uint *)(pbVar11 + -8);
if (*(short *)(pbVar11 + -4) == 0) {
out_buf[uVar9 * 0xd + uVar13 * 0x1a7 + 0xdb] =
*(uint *)(pbVar11 + -0xc);
size = *(uint *)(pbVar11 + -8);
out_buf[uVar9 * 0xd + uVar13 * 0x1a7 + 0xd8] = 4;
out_buf[uVar9 * 0xd + uVar13 * 0x1a7 + 0xd9] = size;
}
else {
if (*(short *)(pbVar11 + -4) != 1) {
fprintf(_stderr,
"[Func]:%s [Line]:%d [Info]:Error(%#x), report node mode shoule be 0 or 1!\n"
,"svp_nnie_parse_dst_node_info",0x2d6,0);
uVar9 = (ulong)*(ushort *)((long)out_buf + lVar14 + 0x12);
goto LAB_0010218c;
}
out_buf[uVar9 * 0xd + uVar13 * 0x1a7 + 0xd8] = 0;
}
}
size = (int)uVar9 + 1;
uVar16 = (ulong)size;
strncpy_s(out_buf + uVar9 * 0xd + counter * 0x1a7 + 0xdd,0x20,pbVar11,
0x1f);
*(undefined1 *)((long)out_buf + uVar13 * 0x69c + uVar9 * 0x34 + 0x393)
= 0;
uVar4 = *(ushort *)((long)out_buf + lVar14 + 0x12);
uVar9 = (ulong)uVar4;
if (uVar4 <= size) goto LAB_0010218c;
pbVar11 = pbVar11 + 0x40;
uVar9 = uVar16;
if (0xffffffee < uVar15) goto LAB_0010201c;
goto LAB_00101f54;
}
uVar16 = 0;
LAB_0010201c:
fprintf(_stderr,
"[Func]:%s [Line]:%d [Info]:Error(%#x), layer_info_offset(%d) sh ould be less than %d!\n"
,"svp_nnie_parse_dst_node_id",0x288,0xa0338003,(ulong)uVar15,
0xffffffef);
uVar9 = uVar16;
LAB_00102050:
fprintf(_stderr,
"[Func]:%s [Line]:%d [Info]:Error(%#x), %d-th node, svp_nnie_par se_dst_node_id failed!\n"
,"svp_nnie_parse_dst_node_info",0x2b5,0xa0338003,uVar9);
error = 0xa0338003;
fprintf(_stderr,
"[Func]:%s [Line]:%d [Info]:Error(%#x): parse %d-th seg dst node failed!\n"
,"hi_mpi_svp_nnie_load_model",0x360,0xa0338003,(ulong)local_d4);
goto joined_r0x001020c4;
}
LAB_0010218c:
sVar6 = (short)out_buf[counter * 0x1a7 + 5];
joined_r0x0010243c:
lVar14 = uVar9 * 0x40 + neg_counter * 0x40 + 0x10;
if (sVar6 == 0) {
uVar4 = 0;
}
else {
neg_counter = 0;
pbVar11 = local_e8 + lVar14 + 0x40;
size = local_d8;
do {
out_buf[(ulong)local_d4 * 0x1a7 + neg_counter + 0x1a8] = size;
uVar12 = (int)neg_counter + 1;
neg_counter = (ulong)uVar12;
local_d8 = size + 1;
out_buf[(ulong)size * 0x1a + 0xd48] = *(uint *)(pbVar11 + -0x40);
out_buf[(ulong)size * 0x1a + 0xd3b] = *(uint *)(pbVar11 + -0x3c);
out_buf[(ulong)size * 0x1a + 0xd3c] = *(uint *)(pbVar11 + -0x38);
out_buf[(ulong)size * 0x1a + 0xd47] = *(uint *)(pbVar11 + -0x34);
out_buf[(ulong)size * 0x1a + 0xd3e] = *(uint *)(pbVar11 + -0x30);
out_buf[(ulong)size * 0x1a + 0xd3f] = *(uint *)(pbVar11 + -0x2c);
out_buf[(ulong)size * 0x1a + 0xd3d] = *(uint *)(pbVar11 + -0x28);
out_buf[(ulong)size * 0x1a + 0xd40] = *(uint *)(pbVar11 + -0x24);
out_buf[(ulong)size * 0x1a + 0xd46] = *(uint *)(pbVar11 + -0x20);
out_buf[(ulong)size * 0x1a + 0xd45] = *(uint *)(pbVar11 + -0x1c);
out_buf[(ulong)size * 0x1a + 0xd41] = *(uint *)(pbVar11 + -0x18);
out_buf[(ulong)size * 0x1a + 0xd42] = *(uint *)(pbVar11 + -0x14);
out_buf[(ulong)size * 0x1a + 0xd43] = *(uint *)(pbVar11 + -0x10);
out_buf[(ulong)size * 0x1a + 0xd44] = *(uint *)(pbVar11 + -0xc);
out_buf[(ulong)size * 0x1a + 0xd49] = (uint)pbVar11[-8];
out_buf[(ulong)size * 0x1a + 0xd4b] = (uint)pbVar11[-7];
out_buf[(ulong)size * 0x1a + 0xd4c] = (uint)pbVar11[-6];
out_buf[(ulong)size * 0x1a + 0xd4a] = (uint)pbVar11[-5];
strncpy_s(out_buf + (ulong)size * 0x1a + 0xd4d,0x20,pbVar11,0x1f);
*(undefined1 *)((long)out_buf + (ulong)size * 0x68 + 0x3553) = 0;
uVar4 = (ushort)out_buf[counter * 0x1a7 + 5];
pbVar11 = pbVar11 + 0x70;
size = local_d8;
} while (uVar12 < uVar4);
}
local_d4 = local_d4 + 1;
local_e8 = local_e8 + (int)((uint)uVar4 * 0x70) + lVar14;
if (out_buf[2] <= local_d4) {
error = 0;
vir_addr = model_buf->phy_addr;
uVar1 = model_buf->vir_addr;
out_buf[0xda8] = 0;
*(uint64_t *)(out_buf + 0xda4) = vir_addr;
*(uint64_t *)(out_buf + 0xda6) = uVar1;
iVar8 = svp_nnie_check_model_user(out_buf);
if (iVar8 != 0) {
error = 0xa0338003;
fprintf(_stderr,
"[Func]:%s [Line]:%d [Info]:Error(%#x), SVP_NNIE_CheckModelUse r failed!\n"
,"hi_mpi_svp_nnie_load_model",0x371,0xa0338003);
}
goto joined_r0x001020c4;
}
bVar2 = *local_e8;
out_buf[(ulong)local_d4 * 0x1a7 + 3] = (uint)bVar2;
*(ushort *)(out_buf + (ulong)local_d4 * 0x1a7 + 4) = (ushort)local_e8[1];
*(ushort *)((long)out_buf + (ulong)local_d4 * 0x69c + 0x12) =
(ushort)local_e8[2];
*(ushort *)(out_buf + (ulong)local_d4 * 0x1a7 + 5) = (ushort)local_e8[3];
uVar4 = *(ushort *)(local_e8 + 4);
*(ushort *)((long)out_buf + (ulong)local_d4 * 0x69c + 0x16) = uVar4;
out_buf[(ulong)local_d4 * 0x1a7 + 6] = *(uint *)(local_e8 + 8);
size = *(uint *)(local_e8 + 0xc);
out_buf[(ulong)local_d4 * 0x1a7 + 7] = size;
if (2 < bVar2) goto LAB_0010247c;
neg_counter = (ulong)(ushort)out_buf[(ulong)local_d4 * 0x1a7 + 4];
} while ((ushort)out_buf[(ulong)local_d4 * 0x1a7 + 4] < 0x11);
}
fprintf(_stderr,
"[Func]:%s [Line]:%d [Info]:Error(%#x): model->seg[%d].src_num(%u) can \'t be greater than %u!\n"
,"svp_nnie_parse_seg_header",0x209,0xa0338003,(ulong)local_d4,
neg_counter,0x10);
}
else {
local_d4 = 0;
LAB_0010247c:
fprintf(_stderr,
"[Func]:%s [Line]:%d [Info]:Error(%#x): model->seg[%d].net_type(%d) sh ould be [%d,%d)!\n"
,"svp_nnie_parse_seg_header",0x205,0xa0338003,(ulong)local_d4,
(ulong)bVar2,0,3);
}
LAB_001024b8:
error = 0xa0338003;
fprintf(_stderr,
"[Func]:%s [Line]:%d [Info]:Error(%#x): parse %d-th seg head info failed !\n"
,"hi_mpi_svp_nnie_load_model",0x352,0xa0338003,(ulong)local_d4);
goto joined_r0x001020c4;
}
fprintf(_stderr,
"[Func]:%s [Line]:%d [Info]:Error(%#x): model_buf->size(%u) can\'t be less than %llu!\n"
,"svp_nnie_parse_model_header",0x1e0,0xa0338003);
}
}
}
}
}
else {
fprintf(_stderr,"[Func]:%s [Line]:%d [Info]:Error(%#x): check file failed!\n",
"svp_nnie_parse_model_header",0x1b5,0xa0338003);
}
}
error = 0xa0338003;
fprintf(_stderr,"[Func]:%s [Line]:%d [Info]:Error(%#x): check model head info failed!\n",
"hi_mpi_svp_nnie_load_model",0x347,0xa0338003);
}
}
else {
fprintf(_stderr,"[Func]:%s [Line]:%d [Info]:Error(%#x): check model input param failed!\n",
"hi_mpi_svp_nnie_load_model",0x340,(ulong)error);
}
joined_r0x001020c4:
if (lVar7 == ___stack_chk_guard) {
return;
}
/* WARNING: Subroutine does not return */
__stack_chk_fail(error);
}Cory wanted to know why the decompiler had simplified certain branch conditions
to true or false, such as the one on line 47. Seeing true or false
branch conditions is somewhat rare in decompilers since compilers often perform
these types of optimizations. My first issue was that my decompilation output
looked much shorter than his:
/* WARNING: Removing unreachable block (ram,0x00101c60) */
/* WARNING: Removing unreachable block (ram,0x001026f8) */
/* WARNING: Removing unreachable block (ram,0x00101c6c) */
/* WARNING: Removing unreachable block (ram,0x0010272c) */
/* WARNING: Removing unreachable block (ram,0x00101c58) */
/* WARNING: Removing unreachable block (ram,0x001026c8) */
/* WARNING: Removing unreachable block (ram,0x00101c8c) */
/* WARNING: Removing unreachable block (ram,0x00101cb0) */
/* WARNING: Removing unreachable block (ram,0x00102478) */
/* WARNING: Removing unreachable block (ram,0x00101d0c) */
/* WARNING: Removing unreachable block (ram,0x00101d1c) */
/* WARNING: Removing unreachable block (ram,0x0010264c) */
/* WARNING: Removing unreachable block (ram,0x00101d38) */
/* WARNING: Removing unreachable block (ram,0x00102614) */
/* WARNING: Removing unreachable block (ram,0x00101d44) */
/* WARNING: Removing unreachable block (ram,0x001025d8) */
/* WARNING: Removing unreachable block (ram,0x00101d4c) */
/* WARNING: Removing unreachable block (ram,0x001025a0) */
/* WARNING: Removing unreachable block (ram,0x00101d5c) */
/* WARNING: Removing unreachable block (ram,0x00102560) */
/* WARNING: Removing unreachable block (ram,0x00101d6c) */
/* WARNING: Removing unreachable block (ram,0x00101d70) */
/* WARNING: Removing unreachable block (ram,0x00101db0) */
/* WARNING: Removing unreachable block (ram,0x00101db4) */
/* WARNING: Removing unreachable block (ram,0x00101db8) */
/* WARNING: Removing unreachable block (ram,0x00101e9c) */
/* WARNING: Removing unreachable block (ram,0x00101dd8) */
/* WARNING: Removing unreachable block (ram,0x00101ef0) */
/* WARNING: Removing unreachable block (ram,0x00101de0) */
/* WARNING: Removing unreachable block (ram,0x00101de4) */
/* WARNING: Removing unreachable block (ram,0x00101eb4) */
/* WARNING: Removing unreachable block (ram,0x00101ec8) */
/* WARNING: Removing unreachable block (ram,0x00101df0) */
/* WARNING: Removing unreachable block (ram,0x00101df8) */
/* WARNING: Removing unreachable block (ram,0x00101dfc) */
/* WARNING: Removing unreachable block (ram,0x00101e00) */
/* WARNING: Removing unreachable block (ram,0x00101e10) */
/* WARNING: Removing unreachable block (ram,0x00101efc) */
/* WARNING: Removing unreachable block (ram,0x00101f00) */
/* WARNING: Removing unreachable block (ram,0x00101f1c) */
/* WARNING: Removing unreachable block (ram,0x00101f2c) */
/* WARNING: Removing unreachable block (ram,0x00101f54) */
/* WARNING: Removing unreachable block (ram,0x00101f48) */
/* WARNING: Removing unreachable block (ram,0x001020d0) */
/* WARNING: Removing unreachable block (ram,0x00101f64) */
/* WARNING: Removing unreachable block (ram,0x00102118) */
/* WARNING: Removing unreachable block (ram,0x001023f8) */
/* WARNING: Removing unreachable block (ram,0x00102124) */
/* WARNING: Removing unreachable block (ram,0x0010214c) */
/* WARNING: Removing unreachable block (ram,0x00102130) */
/* WARNING: Removing unreachable block (ram,0x00101f90) */
/* WARNING: Removing unreachable block (ram,0x00102108) */
/* WARNING: Removing unreachable block (ram,0x00102160) */
/* WARNING: Removing unreachable block (ram,0x00102110) */
/* WARNING: Removing unreachable block (ram,0x00101fb0) */
/* WARNING: Removing unreachable block (ram,0x00101fc8) */
/* WARNING: Removing unreachable block (ram,0x0010200c) */
/* WARNING: Removing unreachable block (ram,0x00102558) */
/* WARNING: Removing unreachable block (ram,0x0010201c) */
/* WARNING: Removing unreachable block (ram,0x00102050) */
/* WARNING: Removing unreachable block (ram,0x0010218c) */
/* WARNING: Removing unreachable block (ram,0x00102440) */
/* WARNING: Removing unreachable block (ram,0x001021a4) */
/* WARNING: Removing unreachable block (ram,0x001021bc) */
/* WARNING: Removing unreachable block (ram,0x001022a8) */
/* WARNING: Removing unreachable block (ram,0x001024f0) */
/* WARNING: Removing unreachable block (ram,0x00102520) */
/* WARNING: Removing unreachable block (ram,0x001022d8) */
/* WARNING: Removing unreachable block (ram,0x0010247c) */
/* WARNING: Removing unreachable block (ram,0x00102338) */
/* WARNING: Removing unreachable block (ram,0x00101e50) */
/* WARNING: Removing unreachable block (ram,0x00101e94) */
/* WARNING: Removing unreachable block (ram,0x00101e98) */
/* WARNING: Removing unreachable block (ram,0x00102388) */
/* WARNING: Removing unreachable block (ram,0x00102348) */
/* WARNING: Removing unreachable block (ram,0x001024b8) */
/* WARNING: Removing unreachable block (ram,0x00102800) */
/* WARNING: Removing unreachable block (ram,0x00101c80) */
/* WARNING: Removing unreachable block (ram,0x001027cc) */
/* WARNING: Removing unreachable block (ram,0x00102688) */
/* WARNING: Removing unreachable block (ram,0x0010279c) */
/* WARNING: Globals starting with '_' overlap smaller symbols at the same address */
void HI_MPI_SVP_NNIE_LoadModel(model *model_buf,uint *out_buf,ulong param3)
{
long lVar1;
uint32_t error;
int flag;
ulong neg_counter;
byte *pbVar2;
uint64_t vir_addr;
header header_buf;
byte *pbVar3;
uint size;
lVar1 = ___stack_chk_guard;
_header_buf = 0;
error = svp_nnie_check_loadmodel_param_user(model_buf,(long)out_buf);
if (error == 0) {
size = (uint)model_buf->size;
if (size < 0xc1) {
error = 0xa0338003;
fprintf(_stderr,
"[Func]:%s [Line]:%d [Info]:Error(%#x): model_buf->size(%u) must be greater than %u!\n "
,"hi_mpi_svp_nnie_load_model",0x345,0xa0338003,(ulong)size,0xc0);
}
else {
vir_addr = model_buf->vir_addr;
memcpy_s(&header_buf,0xc0,vir_addr);
neg_counter = 0; pbVar3 = (byte *)(vir_addr + 4);
do {
pbVar2 = pbVar3 + 1;
size = *(uint *)(&DAT_00105d70 + ((neg_counter ^ *pbVar3) & 0xff) * 4) ^
(uint)neg_counter >> 8;
neg_counter = (ulong)size;
pbVar3 = pbVar2;
} while ((byte *)(vir_addr + 0x100000000) != pbVar2);
if (header_buf.unk1 == ~size) {
memset_s(out_buf,0x36a8,0);
fprintf(_stderr,
"[Func]:%s [Line]:%d [Info]:Error(%#x):model_version(%u) of input model should be %d !\n"
,"svp_nnie_parse_model_header",0x1c3,0xa0338003,0,0xb);
}
else {
fprintf(_stderr,"[Func]:%s [Line]:%d [Info]:Error(%#x): check file failed!\n",
"svp_nnie_parse_model_header",0x1b5,0xa0338003);
}
error = 0xa0338003;
fprintf(_stderr,"[Func]:%s [Line]:%d [Info]:Error(%#x): check model head info failed!\n",
"hi_mpi_svp_nnie_load_model",0x347,0xa0338003);
}
}
else {
fprintf(_stderr,"[Func]:%s [Line]:%d [Info]:Error(%#x): check model input param failed!\n",
"hi_mpi_svp_nnie_load_model",0x340,(ulong)error);
}
if (lVar1 == ___stack_chk_guard) {
return;
}
/* WARNING: Subroutine does not return */
__stack_chk_fail(error);
}We figured out that I had the "Eliminate unreachable code" option enabled under Ghidra's Options => Decompiler => Analysis options tab, but he did not.
if (false) { ... }
statement was unreachable code and removed it.
With that small mystery solved, I obtained the same decompilation as Cory, but we still didn't know why Ghidra had simplified away the condition. This task was made a lot more challenging since it was an ARM executable, and neither of us are very familiar with ARM assembly code. Cory eventually figured out that on the same decompiler settings tab, changing the "Alias Blocking" option from "Arrays and Structures" to "None" caused Ghidra to preserve the original condition.
Ghidra's documentation on Alias Blocking explains:
When deciding if an individual stack location has become dead, the Decompiler must consider aliases, pointers onto the stack that could be used to modify the location within a called function. One strong heuristic the Decompiler uses is: if the user has explicitly created a variable on the stack between the base location referenced by the pointer and the individual stack location, then the Decompiler can assume that the pointer is not an alias of the stack location. The alias is blocked by the explicit variable. However, if the user's explicit variable is labeling something that isn't really an explicit variable, like a field within a larger structure, for instance, the Decompiler may incorrectly consider the stack location as dead and start removing live code.
In order to support the exploratory labeling of stack locations, the user can use this setting to specify what data-types should be considered blocking. The four options are:
- None - No data-type is considered blocking
- Structures - Only structures are blocking
- Structures and Arrays - Only structures and arrays are blocking
- All Data-types - All data-types are blocking
Selecting None is the equivalent of turning off the heuristic. Selecting anything except All Data-types allows users to safely label small variables without knowing immediately if the stack location is part of a larger structure or array.
To better understand what is going on, here is one of the relevant snippets that went from false to a real condition:
else {
vir_addr = model_buf->vir_addr;
memcpy_s(&header_buf,0xc0,vir_addr);
size = local_98._4_4_ + (uint)uStack_90; if ((uint)model_buf->size < size) { fprintf(_stderr,
"[Func]:%s [Line]:%d [Info]:Error(%#x): model_buf->size(%d) is less than %d!\n",
"svp_nnie_parse_model_header",0x1b2,0xa0338003);
}Looking at the stack frame, we can start to piece together what happened:
local_98 and uStack_90 were initialized to zero.memcpy_s was called with a pointer to header_buf, which is a structure of
size 0xc0 bytes on the stack.header_buf is a structure, Alias Blocking with "Arrays and
Structures" enabled means that Ghidra assumes that local_98 and uStack_90
cannot be modified by the memcpy_s call, since they are not part of the
header_buf structure. Therefore, Ghidra concludes that they are unchanged,
size is always zero, and the condition is always false.Unfortunately for Cory, the header_buf structure he created was only four
bytes long. In reality, local_98 and uStack_90 are fields that are
overwritten by the memcpy_s call, and influence the computation of the
condition. But his artificially small structure misled Ghidra into thinking
they were unaffected.
I had never heard of the "Alias Blocking" option before, and I'm not aware of a
similar feature in other decompilers. It's an interesting idea though.
Basically, the assumption is that if you have enough information to type an
array or structure on the stack, then it is accurate enough to assume there
won't be pointers from that object that alias outside of that object. There's
also a number of other things that have to work correctly for this analysis to
be correct. If we had confused the calling convention of memcpy_s and did not
detect that it takes a pointer to the stack, then this analysis would also be
incorrect.
It's also interesting because it has the potential to both help and hurt decompilation quality, depending on the situation. Cory noted this functionality could be helpful for deobfuscating opaque predicates, for example. But Cory also noted that he often retypes structs incrementally as he learns more about them. Alias Blocking on structures would actually be harmful for this type of incremental typing.
Another interesting question is whether we can abuse this behavior to make it harder to analyze code. To answer this, I created a small test program that can exhibit the same behavior. Here it is (also on Godbolt):
#include <string.h>
#include <stdio.h>
struct foo {
int blue;
int red;
char green;
float two;
};
void go(struct foo* in) {
int above = -1;
struct foo myfoo;
myfoo.red = 0;
int below = 2;
memcpy(&myfoo, in, sizeof(myfoo));
if (myfoo.red != 10) {
printf("Wow!\n");
}
}
int main() {
struct foo mainfoo = {4, 5, 'a', 3.14};
}I found that I had to compile with gcc -fno-builtin or otherwise gcc would fairly aggressively optimize away the memcpy call!
Upon initially loading Ghidra, the decompilation of the go function looks like this:
/* WARNING: Unknown calling convention -- yet parameter storage is locked */
long go(void)
{
void *in_RDI;
long in_FS_OFFSET;
undefined1 local_28 [4];
int local_24;
long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
local_24 = 0;
memcpy(local_28,in_RDI,0x10);
if (local_24 != 10) {
printf("Wow!\n");
}
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return 0;
}For some reason undefined1[4] is not considered to be an Array type. But if
we manually retype local_28 to char[4] instead of undefined1[4], we see
the desired behavior:
/* WARNING: Unknown calling convention -- yet parameter storage is locked */
long go(void)
{
long lVar1;
void *in_RDI;
long in_FS_OFFSET;
char local_28 [4];
lVar1 = *(long *)(in_FS_OFFSET + 0x28);
memcpy(local_28,in_RDI,0x10);
if (true) {
printf("Wow!\n");
}
if (lVar1 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return 0;
}Of course, we could have made that condition appear to be false.
If we could nudge Ghidra to type local_28 as a char[4] array automatically,
or a structure, perhaps we could automatically cause Ghidra to misinterpret the
condition. The alias blocking documentation suggests that it only applies when a
user "explicitly" creates a variable on the stack, so this might prevent abuse
for anti-analysis. I leave this as an open question for the reader.
Many people seem to be unaware that decompilers have a decompilation export feature, which is particularly beneficial when you are trying to parse or recompile decompiled code.
Here is a random function from /bin/ls that I decompiled using Ghidra, and simply copied the output from the decompilation window:
void FUN_0010b1b0(undefined8 *param_1,undefined8 *param_2)
{
char *__s;
char *__s_00;
int iVar1;
char *__s1;
char *__s2;
int *piVar2;
__s = (char *)*param_2;
__s1 = strrchr(__s,0x2e);
__s_00 = (char *)*param_1;
__s2 = strrchr(__s_00,0x2e);
if (__s2 == (char *)0x0) {
__s2 = "";
}
if (__s1 == (char *)0x0) {
__s1 = "";
}
piVar2 = __errno_location();
*piVar2 = 0;
iVar1 = strcoll(__s1,__s2);
if (iVar1 == 0) {
strcoll(__s,__s_00);
return;
}
return;
}This code does not compile.
gcc -c a.c
a.c:1:19: error: unknown type name ‘undefined8’
1 | void FUN_0010b1b0(undefined8 *param_1,undefined8 *param_2)
| ^~~~~~~~~~
a.c:1:39: error: unknown type name ‘undefined8’
1 | void FUN_0010b1b0(undefined8 *param_1,undefined8 *param_2)
| ^~~~~~~~~~However, we can use Ghidra's decompiler exporter to decompile the function AND emit a header file that will define the types and declare the functions used in the decompiled code. Unfortunately, this is a little bit awkward to do for one function. I suggest the following process:
This will create a decompiled source file ls.c containing type definitions for types used in the decompiled code, such as undefined8.
And if you try to compile the generated ls.c file, it will compile successfully:
gcc -c ls.c
ls.c: In function ‘FUN_0010b1b0’:
ls.c:630:10: warning: implicit declaration of function ‘strrchr’ [-Wimplicit-function-declaration]
630 | __s1 = strrchr(__s,0x2e);
| ^~~~~~~
ls.c:1:1: note: include ‘<string.h>’ or provide a declaration of ‘strrchr’
+++ |+#include <string.h>
1 | typedef unsigned char undefined;
ls.c:630:10: warning: incompatible implicit declaration of built-in function ‘strrchr’ [-Wbuiltin-declaration-mismatch]
630 | __s1 = strrchr(__s,0x2e);
| ^~~~~~~
ls.c:630:10: note: include ‘<string.h>’ or provide a declaration of ‘strrchr’
ls.c:639:12: warning: implicit declaration of function ‘__errno_location’ [-Wimplicit-function-declaration]
639 | piVar2 = __errno_location();
| ^~~~~~~~~~~~~~~~
ls.c:639:10: warning: assignment to ‘int *’ from ‘int’ makes pointer from integer without a cast [-Wint-conversion]
639 | piVar2 = __errno_location();
| ^
ls.c:641:11: warning: implicit declaration of function ‘strcoll’ [-Wimplicit-function-declaration]
641 | iVar1 = strcoll(__s1,__s2);
| ^~~~~~~Hurray!
This is slightly oversimplified. You can see the compiler's warnings that we did not have declarations for strrchr, __errno_location, and strcoll. Ghidra can generate these declarations, but didn't because we used the "Selection Only" option. To get these declarations, you can use an approach like the following:
Unfortunately, this header file does not always compile. Here are some errors that I obtained when trying to compile the header for /bin/ls:
In file included from FUN_0010b1b0.c:1:
FUN_0010b1b0.h:671:6: warning: conflicting types for built-in function ‘__snprintf_chk’; expected ‘int(char *, long unsigned int, int, long unsigned int, const char *, ...)’ [-Wbuiltin-declaration-mismatch]
671 | void __snprintf_chk(void);
| ^~~~~~~~~~~~~~
FUN_0010b1b0.h:682:5: error: ‘sigaction’ redeclared as different kind of symbol
682 | int sigaction(int __sig,sigaction *__act,sigaction *__oact);
| ^~~~~~~~~
FUN_0010b1b0.h:294:26: note: previous declaration of ‘sigaction’ with type ‘sigaction’
294 | typedef struct sigaction sigaction, *Psigaction;
| ^~~~~~~~~
FUN_0010b1b0.h:695:6: warning: conflicting types for built-in function ‘dcgettext’; expected ‘char *(const char *, const char *, int)’ [-Wbuiltin-declaration-mismatch]
695 | void dcgettext(void);
| ^~~~~~~~~
[truncated]This is probably because Ghidra's type manager includes multiple definitions for
some types, such as sigaction. I have opened a Ghidra
issue for these
problems. Hopefully the Ghidra developers will commit to fixing these problems
in the header file, since it would significantly improve the usability of the
decompiler output.
Here is a ghidra-scala-loader script that can do the above steps for you from the command line.
IDA/Hex-Rays has a similar feature, but it is much more straightforward. Simply select File → Create C File, and it will generate a C file that includes all the necessary type definitions and function declarations. It does include defs.h, which is a file that contains type definitions for Hex-Rays. You can find that in the Hex-Rays SDK or elsewhere.
I'm trying to document a few things that I do infrequently enough that I tend to forget how to do them, and need to rediscover the process each time. Next in this series is debugging the Ghidra decompiler. This is one of the only resources I know of that discusses this!
The process is roughly:
cd /path/to/ghidra/Ghidra/Features/Decompiler/src/decompile/cppmake decomp_dbg
SLEIGHHOME=/path/to/ghidra ./decomp_dbg. You should now be in the
decomp_dbg interpreter.restore /path/to/debug.xmlload function target_functrace address insn_addressdecompileHere's a full example:
~/g/g/G/F/D/s/d/cpp $ env SLEIGHHOME=/home/ed/ghidra/ghidra_10.4_PUBLIC/ ./decomp_dbg
[decomp]> restore /tmp/mydebug.xml
/tmp/mydebug.xml successfully loaded: Intel/AMD 32-bit x86
[decomp]> load function main
Function main: 0x00411530
[decomp]> trace address 0x4115ec
OK (1 ranges)
[decomp]> decompile
Decompiling main
DEBUG 0: extrapopsetup
0x004115ec:200: **
0x004115ec:200: ESP(0x004115ec:200) = ESP(free) + #0x4
DEBUG 1: funclink
0x004115ec:a5: call fCls3:8(free)
0x004115ec:a5: EAX(0x004115ec:a5) = call fCls3:8(free)(ECX(free),u0x10000019:1(0x004115ec:212))
0x004115ec:211: **
0x004115ec:211: u0x10000015(0x004115ec:211) = ESP(free) + #0x0
0x004115ec:212: **
0x004115ec:212: u0x10000019:1(0x004115ec:212) = *(ram,u0x10000015(0x004115ec:211))
DEBUG 2: heritage
0x004115ec:248: **
0x004115ec:248: ECX(0x004115ec:248) = [create] i0x004115ec:a5:8(free)
0x004115ec:25a: **
0x004115ec:25a: EDX(0x004115ec:25a) = [create] i0x004115ec:a5:8(free)
0x004115ec:26f: **
0x004115ec:26f: CF(0x004115ec:26f) = CF(0x004115dd:96) [] i0x004115ec:a5:8(free)
0x004115ec:280: **
0x004115ec:280: PF(0x004115ec:280) = PF(0x004115dd:9e) [] i0x004115ec:a5:8(free)
0x004115ec:291: **
0x004115ec:291: ZF(0x004115ec:291) = ZF(0x004115dd:9a) [] i0x004115ec:a5:8(free)
0x004115ec:2a2: **
0x004115ec:2a2: SF(0x004115ec:2a2) = SF(0x004115dd:99) [] i0x004115ec:a5:8(free)
0x004115ec:2b3: **
0x004115ec:2b3: OF(0x004115ec:2b3) = OF(0x004115dd:97) [] i0x004115ec:a5:8(free)
0x004115ec:2c4: **
0x004115ec:2c4: EIP(0x004115ec:2c4) = EIP(0x004115c8:2c3) [] i0x004115ec:a5:8(free)
0x004115ec:a3: ESP(0x004115ec:a3) = ESP(free) - #0x4
0x004115ec:a3: ESP(0x004115ec:a3) = ESP(0x004115cd:86) - #0x4
0x004115ec:a4: *(ram,ESP(free)) = #0x4115f1
0x004115ec:a4: *(ram,ESP(0x004115ec:a3)) = #0x4115f1
0x004115ec:211: u0x10000015(0x004115ec:211) = ESP(free) + #0x0
0x004115ec:211: u0x10000015(0x004115ec:211) = ESP(0x004115ec:a3) + #0x0
0x004115ec:a5: EAX(0x004115ec:a5) = call fCls3:8(free)(ECX(free),u0x10000019:1(0x004115ec:212))
0x004115ec:a5: EAX(0x004115ec:a5) = call fCls3:8(free)(ECX(0x004115e6:a2),u0x10000019:1(0x004115ec:212))
0x004115ec:200: ESP(0x004115ec:200) = ESP(free) + #0x4
0x004115ec:200: ESP(0x004115ec:200) = ESP(0x004115ec:a3) + #0x4
DEBUG 3: deadcode
0x004115ec:248: ECX(0x004115ec:248) = [create] i0x004115ec:a5:8(free)
0x004115ec:248: **
0x004115ec:25a: EDX(0x004115ec:25a) = [create] i0x004115ec:a5:8(free)
0x004115ec:25a: **
0x004115ec:26f: CF(0x004115ec:26f) = CF(0x004115dd:96) [] i0x004115ec:a5:8(free)
0x004115ec:26f: **
0x004115ec:280: PF(0x004115ec:280) = PF(0x004115dd:9e) [] i0x004115ec:a5:8(free)
0x004115ec:280: **
0x004115ec:291: ZF(0x004115ec:291) = ZF(0x004115dd:9a) [] i0x004115ec:a5:8(free)
0x004115ec:291: **
0x004115ec:2a2: SF(0x004115ec:2a2) = SF(0x004115dd:99) [] i0x004115ec:a5:8(free)
0x004115ec:2a2: **
0x004115ec:2b3: OF(0x004115ec:2b3) = OF(0x004115dd:97) [] i0x004115ec:a5:8(free)
0x004115ec:2b3: **
0x004115ec:2c4: EIP(0x004115ec:2c4) = EIP(0x004115c8:2c3) [] i0x004115ec:a5:8(free)
0x004115ec:2c4: **
DEBUG 4: sub2add
0x004115ec:2e4: **
0x004115ec:2e4: u0x100000a0(0x004115ec:2e4) = #0x4 * #0xffffffff
0x004115ec:a3: ESP(0x004115ec:a3) = ESP(0x004115cd:86) - #0x4
0x004115ec:a3: ESP(0x004115ec:a3) = ESP(0x004115cd:86) + u0x100000a0(0x004115ec:2e4)
DEBUG 5: propagatecopy
0x004115ec:a5: EAX(0x004115ec:a5) = call fCls3:8(free)(ECX(0x004115e6:a2),u0x10000019:1(0x004115ec:212))
0x004115ec:a5: EAX(0x004115ec:a5) = call fCls3:8(free)(u0x00007a80(0x004115e6:a1),u0x10000019:1(0x004115ec:212))
DEBUG 6: identityel
0x004115ec:211: u0x10000015(0x004115ec:211) = ESP(0x004115ec:a3) + #0x0
0x004115ec:211: u0x10000015(0x004115ec:211) = ESP(0x004115ec:a3)
DEBUG 7: propagatecopy
0x004115ec:212: u0x10000019:1(0x004115ec:212) = *(ram,u0x10000015(0x004115ec:211))
0x004115ec:212: u0x10000019:1(0x004115ec:212) = *(ram,ESP(0x004115ec:a3))
DEBUG 8: collapseconstants
0x004115ec:2e4: u0x100000a0(0x004115ec:2e4) = #0x4 * #0xffffffff
0x004115ec:2e4: u0x100000a0(0x004115ec:2e4) = #0xfffffffc
DEBUG 9: propagatecopy
0x004115ec:a3: ESP(0x004115ec:a3) = ESP(0x004115cd:86) + u0x100000a0(0x004115ec:2e4)
0x004115ec:a3: ESP(0x004115ec:a3) = ESP(0x004115cd:86) + #0xfffffffc
DEBUG 10: addmultcollapse
0x004115ec:200: ESP(0x004115ec:200) = ESP(0x004115ec:a3) + #0x4
0x004115ec:200: ESP(0x004115ec:200) = ESP(0x004115cd:86) + #0x0
DEBUG 11: earlyremoval
0x004115ec:211: u0x10000015(0x004115ec:211) = ESP(0x004115ec:a3)
0x004115ec:211: **
DEBUG 12: earlyremoval
0x004115ec:2e4: u0x100000a0(0x004115ec:2e4) = #0xfffffffc
0x004115ec:2e4: **
DEBUG 13: addmultcollapse
0x004115ec:a3: ESP(0x004115ec:a3) = ESP(0x004115cd:86) + #0xfffffffc
0x004115ec:a3: ESP(0x004115ec:a3) = ESP(0x004115aa:26a) + #0xfffffffc
DEBUG 14: identityel
0x004115ec:200: ESP(0x004115ec:200) = ESP(0x004115cd:86) + #0x0
0x004115ec:200: ESP(0x004115ec:200) = ESP(0x004115cd:86)
DEBUG 15: multicollapse
0x004115ec:a3: ESP(0x004115ec:a3) = ESP(0x004115aa:26a) + #0xfffffffc
0x004115ec:a3: ESP(0x004115ec:a3) = ESP(0x00411574:4b) + #0xfffffffc
DEBUG 16: addmultcollapse
0x004115ec:a3: ESP(0x004115ec:a3) = ESP(0x00411574:4b) + #0xfffffffc
0x004115ec:a3: ESP(0x004115ec:a3) = ESP(i) + #0xfffffe94
DEBUG 17: earlyremoval
0x004115ec:200: ESP(0x004115ec:200) = ESP(0x004115cd:86)
0x004115ec:200: **
DEBUG 18: stackptrflow
0x004115ec:a3: ESP(0x004115ec:a3) = ESP(i) + #0xfffffe94
0x004115ec:a3: ESP(0x004115ec:a3) = ESP(i) + #0xfffffe94
DEBUG 19: storevarnode
0x004115ec:a4: *(ram,ESP(0x004115ec:a3)) = #0x4115f1
0x004115ec:a4: s0xfffffe94(0x004115ec:a4) = #0x4115f1
DEBUG 20: loadvarnode
0x004115ec:212: u0x10000019:1(0x004115ec:212) = *(ram,ESP(0x004115ec:a3))
0x004115ec:212: **
0x004115ec:a5: EAX(0x004115ec:a5) = call fCls3:8(free)(u0x00007a80(0x004115e6:a1),u0x10000019:1(0x004115ec:212))
0x004115ec:a5: EAX(0x004115ec:a5) = call fCls3:8(free)(u0x00007a80(0x004115e6:a1))
DEBUG 21: heritage
0x004115ec:2fc: **
0x004115ec:2fc: r0x0041a048(0x004115ec:2fc) = r0x0041a048(0x004115c8:2fb) [] i0x004115ec:a5:8(free)
0x004115ec:312: **
0x004115ec:312: s0xfffffe80(0x004115ec:312) = s0xfffffe80(0x004115c8:311) [] i0x004115ec:a5:8(free)
0x004115ec:325: **
0x004115ec:325: s0xfffffe84(0x004115ec:325) = s0xfffffe84(0x004115c8:324) [] i0x004115ec:a5:8(free)
0x004115ec:338: **
0x004115ec:338: s0xfffffe88(0x004115ec:338) = s0xfffffe88(0x004115c8:337) [] i0x004115ec:a5:8(free)
0x004115ec:34b: **
0x004115ec:34b: s0xfffffe8c(0x004115ec:34b) = s0xfffffe8c(0x004115c8:34a) [] i0x004115ec:a5:8(free)
0x004115ec:35e: **
0x004115ec:35e: s0xfffffe90(0x004115ec:35e) = s0xfffffe90(0x004115c8:35d) [] i0x004115ec:a5:8(free)
0x004115ec:371: **
0x004115ec:371: s0xfffffe94(0x004115ec:371) = s0xfffffe94(0x004115ec:a4) [] i0x004115ec:a5:8(free)
0x004115ec:384: **
0x004115ec:384: s0xfffffe98(0x004115ec:384) = s0xfffffe98(0x004115c8:383) [] i0x004115ec:a5:8(free)
0x004115ec:397: **
0x004115ec:397: s0xfffffe9c(0x004115ec:397) = s0xfffffe9c(0x004115c8:396) [] i0x004115ec:a5:8(free)
0x004115ec:3aa: **
0x004115ec:3aa: s0xfffffea0(0x004115ec:3aa) = s0xfffffea0(0x004115c8:3a9) [] i0x004115ec:a5:8(free)
0x004115ec:3bd: **
0x004115ec:3bd: s0xfffffea4(0x004115ec:3bd) = s0xfffffea4(0x004115c8:3bc) [] i0x004115ec:a5:8(free)
0x004115ec:3d0: **
0x004115ec:3d0: s0xfffffea8(0x004115ec:3d0) = s0xfffffea8(0x004115c8:3cf) [] i0x004115ec:a5:8(free)
0x004115ec:3e3: **
0x004115ec:3e3: s0xfffffeb0(0x004115ec:3e3) = s0xfffffeb0(0x004115c8:3e2) [] i0x004115ec:a5:8(free)
0x004115ec:3f6: **
0x004115ec:3f6: s0xfffffebc(0x004115ec:3f6) = s0xfffffebc(0x004115c8:3f5) [] i0x004115ec:a5:8(free)
0x004115ec:409: **
0x004115ec:409: s0xfffffec8(0x004115ec:409) = s0xfffffec8(0x004115c8:408) [] i0x004115ec:a5:8(free)
0x004115ec:41c: **
0x004115ec:41c: s0xfffffed4(0x004115ec:41c) = s0xfffffed4(0x004115d0:8f) [] i0x004115ec:a5:8(free)
0x004115ec:42f: **
0x004115ec:42f: s0xfffffee0(0x004115ec:42f) = s0xfffffee0(0x004115c8:42e) [] i0x004115ec:a5:8(free)
0x004115ec:442: **
0x004115ec:442: s0xfffffeec(0x004115ec:442) = s0xfffffeec(0x004115c8:441) [] i0x004115ec:a5:8(free)
0x004115ec:455: **
0x004115ec:455: s0xfffffef8(0x004115ec:455) = s0xfffffef8(0x004115c8:454) [] i0x004115ec:a5:8(free)
0x004115ec:468: **
0x004115ec:468: s0xffffff04(0x004115ec:468) = s0xffffff04(0x004115c8:467) [] i0x004115ec:a5:8(free)
0x004115ec:47b: **
0x004115ec:47b: s0xffffffd0(0x004115ec:47b) = s0xffffffd0(0x004115c8:47a) [] i0x004115ec:a5:8(free)
0x004115ec:48e: **
0x004115ec:48e: s0xffffffdc(0x004115ec:48e) = s0xffffffdc(0x004115c8:48d) [] i0x004115ec:a5:8(free)
0x004115ec:4a1: **
0x004115ec:4a1: s0xffffffe8(0x004115ec:4a1) = s0xffffffe8(0x004115c8:4a0) [] i0x004115ec:a5:8(free)
0x004115ec:4b4: **
0x004115ec:4b4: s0xfffffff0(0x004115ec:4b4) = s0xfffffff0(0x004115c8:4b3) [] i0x004115ec:a5:8(free)
0x004115ec:4c7: **
0x004115ec:4c7: s0xfffffff4(0x004115ec:4c7) = s0xfffffff4(0x004115c8:4c6) [] i0x004115ec:a5:8(free)
0x004115ec:4da: **
0x004115ec:4da: s0xfffffff8(0x004115ec:4da) = s0xfffffff8(0x004115d6:92) [] i0x004115ec:a5:8(free)
0x004115ec:4ed: **
0x004115ec:4ed: s0xfffffffc(0x004115ec:4ed) = s0xfffffffc(0x004115c8:4ec) [] i0x004115ec:a5:8(free)
DEBUG 22: deadcode
0x004115ec:a3: ESP(0x004115ec:a3) = ESP(i) + #0xfffffe94
0x004115ec:a3: **
0x004115ec:312: s0xfffffe80(0x004115ec:312) = s0xfffffe80(0x004115c8:311) [] i0x004115ec:a5:8(free)
0x004115ec:312: **
0x004115ec:325: s0xfffffe84(0x004115ec:325) = s0xfffffe84(0x004115c8:324) [] i0x004115ec:a5:8(free)
0x004115ec:325: **
0x004115ec:338: s0xfffffe88(0x004115ec:338) = s0xfffffe88(0x004115c8:337) [] i0x004115ec:a5:8(free)
0x004115ec:338: **
0x004115ec:34b: s0xfffffe8c(0x004115ec:34b) = s0xfffffe8c(0x004115c8:34a) [] i0x004115ec:a5:8(free)
0x004115ec:34b: **
0x004115ec:397: s0xfffffe9c(0x004115ec:397) = s0xfffffe9c(0x004115c8:396) [] i0x004115ec:a5:8(free)
0x004115ec:397: **
0x004115ec:3aa: s0xfffffea0(0x004115ec:3aa) = s0xfffffea0(0x004115c8:3a9) [] i0x004115ec:a5:8(free)
0x004115ec:3aa: **
0x004115ec:3bd: s0xfffffea4(0x004115ec:3bd) = s0xfffffea4(0x004115c8:3bc) [] i0x004115ec:a5:8(free)
0x004115ec:3bd: **
0x004115ec:409: s0xfffffec8(0x004115ec:409) = s0xfffffec8(0x004115c8:408) [] i0x004115ec:a5:8(free)
0x004115ec:409: **
0x004115ec:42f: s0xfffffee0(0x004115ec:42f) = s0xfffffee0(0x004115c8:42e) [] i0x004115ec:a5:8(free)
0x004115ec:42f: **
0x004115ec:442: s0xfffffeec(0x004115ec:442) = s0xfffffeec(0x004115c8:441) [] i0x004115ec:a5:8(free)
0x004115ec:442: **
0x004115ec:455: s0xfffffef8(0x004115ec:455) = s0xfffffef8(0x004115c8:454) [] i0x004115ec:a5:8(free)
0x004115ec:455: **
0x004115ec:468: s0xffffff04(0x004115ec:468) = s0xffffff04(0x004115c8:467) [] i0x004115ec:a5:8(free)
0x004115ec:468: **
0x004115ec:47b: s0xffffffd0(0x004115ec:47b) = s0xffffffd0(0x004115c8:47a) [] i0x004115ec:a5:8(free)
0x004115ec:47b: **
0x004115ec:48e: s0xffffffdc(0x004115ec:48e) = s0xffffffdc(0x004115c8:48d) [] i0x004115ec:a5:8(free)
0x004115ec:48e: **
0x004115ec:4ed: s0xfffffffc(0x004115ec:4ed) = s0xfffffffc(0x004115c8:4ec) [] i0x004115ec:a5:8(free)
0x004115ec:4ed: **
DEBUG 23: indirectcollapse
0x004115ec:384: s0xfffffe98(0x004115ec:384) = s0xfffffe98(0x004115c8:383) [] i0x004115ec:a5:8(free)
0x004115ec:384: s0xfffffe98(0x004115ec:384) = s0xfffffe98(0x00411563:3d) [] i0x004115ec:a5:8(free)
DEBUG 24: propagatecopy
0x004115ec:a5: EAX(0x004115ec:a5) = call fCls3:8(free)(u0x00007a80(0x004115e6:a1))
0x004115ec:a5: EAX(0x004115ec:a5) = call fCls3:8(free)(EAX(0x004115c8:83))
DEBUG 25: earlyremoval
0x004115ec:35e: s0xfffffe90(0x004115ec:35e) = s0xfffffe90(0x004115c8:35d) [] i0x004115ec:a5:8(free)
0x004115ec:35e: **
DEBUG 26: earlyremoval
0x004115ec:371: s0xfffffe94(0x004115ec:371) = s0xfffffe94(0x004115ec:a4) [] i0x004115ec:a5:8(free)
0x004115ec:371: **
DEBUG 27: indirectcollapse
0x004115ec:384: s0xfffffe98(0x004115ec:384) = s0xfffffe98(0x00411563:3d) [] i0x004115ec:a5:8(free)
0x004115ec:384: **
DEBUG 28: propagatecopy
0x004115ec:41c: s0xfffffed4(0x004115ec:41c) = s0xfffffed4(0x004115d0:8f) [] i0x004115ec:a5:8(free)
0x004115ec:41c: s0xfffffed4(0x004115ec:41c) = EAX(0x004115c8:83) [] i0x004115ec:a5:8(free)
DEBUG 29: earlyremoval
0x004115ec:a4: s0xfffffe94(0x004115ec:a4) = #0x4115f1
0x004115ec:a4: **
DEBUG 30: setcasts
0x004115ec:50b: **
0x004115ec:50b: EAX(0x004115ec:50b) = (cast) u0x10000129(0x004115ec:a5)
0x004115ec:a5: EAX(0x004115ec:a5) = call fCls3:8(free)(EAX(0x004115c8:50a))
0x004115ec:a5: u0x10000129(0x004115ec:a5) = call fCls3:8(free)(EAX(0x004115c8:50a))
Decompilation completeIn my case, I wanted to know why the function call at 0x4115ec thinks that EAX is being passed as an argument instead of ECX.
Here is ChatGPT's explanation. And it's right! ECX will be equal to the value of EAX at the time of the call.

list action shows the plan of actions/passesdebug action foo can enable extra debug info for some actions, such as inputprototypePowered with by Gatsby 5.0