Let’s say you found a promising-looking string in the binary, followed the cross reference to the function using it, then decompiled it to see how the string is used, only to see no signs of it in the pseudocode. What’s happening?
In such situation it often helps to set up two synchronized disassembly<->pseudocode views and scroll through them looking for oddities. As a rule of thumb, most pseudocode lines should map to one or few assembly instructions and most assembly instructions (except skippable ones such as function prolog or epilog) should map to some line in pseudocode.
Here’s an example of a strange case: a single function call in pseudocode maps to not only the call instruction but also a bunch of seemingly unrelated instructions after it:
It is then followed by instructions which do not have any mapping in the pseudocode:
Hovering the mouse on the call gives a clue: it’s been marked as no-return:
Since there’s obviously some valid code after it, it seems to be a false positive. Removing the
__noreturn attribute from the prototype (hint: Y shortcut) brings back the missing code and more regular instruction mapping:
NB: in some cases you may have to clear the noret flag in function’s properties in addition to fixing the prototype, or the
__noreturn attribute will keep coming back.
The reasons why the function could be incorrectly marked as no-ret are numerous; for example, it may have been found to end in an infinite loop or have code paths only leading to other no-ret functions due to other issues. It may be worth investigating such functions more closely, especially if you discover multiple instances of them.
Note that in some cases you may see valid-looking code after a function call even though the function really does not return. This could be caused by:
- the compiler not deducing that the function does not return
- old compiler which does not perform dead code removal
- optimization settings during compilation
- other reasons (e.g. incorrect assumptions by the decompiler)