Comments (20)
I tried it and I got the following trace log:
Jupyter QtConsole 5.4.0
Python 3.8.10 (tags/v3.8.10:3d8993a, May 3 2021, 11:48:03) [MSC v.1928 64 bit (AMD64)]
Type 'copyright', 'credits' or 'license' for more information
IPython 8.9.0 -- An enhanced Interactive Python. Type '?' for help.
database.functions.list()
---------------------------------------------------------------------------
DisassemblerError Traceback (most recent call last)
Cell In[1], line 1
----> 1 database.functions.list()
File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\_utils.py:694, in database.list(*arguments, **keywords)
691 # Now we have a matching callable for the user's parameters, and we just need
692 # to unpack our individual parameters and dispatch to the callable with them.
693 parameters, wild_parameters, keyword_parameters = result_parameters
--> 694 return result_callable(*itertools.chain(parameters, wild_parameters), **keyword_parameters)
File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\database.py:598, in functions.list(cls, **type)
596 '''List all of the functions in the database with a glob that matches `string`.'''
597 return cls.list(like=string)
--> 598 @utils.multicase()
599 @classmethod
600 @utils.string.decorate_arguments('name', 'like', 'regex')
601 def list(cls, **type):
602 '''List all of the functions in the database that match the keyword specified by `type`.'''
603 listable = []
File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\_utils.py:1994, in transform.<locals>.wrapper(F, *rargs, **rkwds)
1992 cls = E.__class__
1993 raise cls("{!s}: Exception raised while transforming parameter `{:s}` with value {!r}".format('.'.join([f.__module__, f.__name__]), argname, kwds[argname]))
-> 1994 return F(*res, **kwds)
File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\database.py:633, in functions.list(cls, **type)
631 refs = max(len(xref.up(ea)), refs)
632 lvars = max(Fcount_lvars(func) if idaapi.get_frame(ea) else 0, lvars)
--> 633 avars = max(Fcount_avars(func), avars)
635 listable.append(ea)
637 # Collect the number of digits for everything from the first pass
File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\_utils.py:56, in <lambda>(*a)
54 first, second, third, last = operator.itemgetter(0), operator.itemgetter(1), operator.itemgetter(2), operator.itemgetter(-1)
55 # return a closure that executes a list of functions one after another from left-to-right.
---> 56 fcompose = lambda *Fa: functools.reduce(lambda F1, F2: lambda *a: F1(F2(*a)), builtins.reversed(Fa))
57 # return a closure that executes function `F` whilst discarding any arguments passed to it.
58 fdiscard = lambda F, *a, **k: lambda *ap, **kp: F(*a, **k)
File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\_utils.py:56, in <lambda>(*a)
54 first, second, third, last = operator.itemgetter(0), operator.itemgetter(1), operator.itemgetter(2), operator.itemgetter(-1)
55 # return a closure that executes a list of functions one after another from left-to-right.
---> 56 fcompose = lambda *Fa: functools.reduce(lambda F1, F2: lambda *a: F1(F2(*a)), builtins.reversed(Fa))
57 # return a closure that executes function `F` whilst discarding any arguments passed to it.
58 fdiscard = lambda F, *a, **k: lambda *ap, **kp: F(*a, **k)
File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\_utils.py:56, in <lambda>(*a)
54 first, second, third, last = operator.itemgetter(0), operator.itemgetter(1), operator.itemgetter(2), operator.itemgetter(-1)
55 # return a closure that executes a list of functions one after another from left-to-right.
---> 56 fcompose = lambda *Fa: functools.reduce(lambda F1, F2: lambda *a: F1(F2(*a)), builtins.reversed(Fa))
57 # return a closure that executes function `F` whilst discarding any arguments passed to it.
58 fdiscard = lambda F, *a, **k: lambda *ap, **kp: F(*a, **k)
File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\function.py:2650, in frame.arguments.iterate(cls, func)
2646 return
2648 # If we got here, then we have type information that we can grab out
2649 # of the given address. Once we have it, rip the details out o it.
-> 2650 tinfo = type(ea)
2651 _, ftd = interface.tinfo.function_details(ea, tinfo)
2653 # Now we just need to iterate through our parameters collecting the
2654 # raw location information for all of them. We preserve the type
2655 # information in case we're unable to find the argument in a member.
File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\_utils.py:694, in function.__new__(*arguments, **keywords)
691 # Now we have a matching callable for the user's parameters, and we just need
692 # to unpack our individual parameters and dispatch to the callable with them.
693 parameters, wild_parameters, keyword_parameters = result_parameters
--> 694 return result_callable(*itertools.chain(parameters, wild_parameters), **keyword_parameters)
File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\function.py:3232, in type.__new__(cls, func)
3229 # If we weren't able to create the missing type, then we need to abort. This shouldn't
3230 # be possible at all whatsoever, but perhaps some database state is preventing us.
3231 if not missing:
-> 3232 raise E.DisassemblerError(u"{:s}({:#x}) : Unable to create a dummy function type to return for the for the specified function ({:#x}).".format('.'.join([__name__, cls.__name__]), ea, ea))
3234 logging.warning(u"{:s}({:#x}) : Unable to guess the missing type for the function at {:#x} due to error ({:d}) which will result in an empty function type (\"{:s}\") being returned.".format('.'.join([__name__, cls.__name__]), ea, ea, idaapi.GUESS_FUNC_FAILED, utils.string.escape("{!s}".format(missing), '"')))
3235 return missing
DisassemblerError: function.type(0x401029) : Unable to create a dummy function type to return for the for the specified function (0x401029).
from ida-minsc.
I found the problem and it was in one of my scripts (that I didn't knew was loaded). It was this line:
setattr(ida_typeinf.tinfo_t, '__len__', lambda self: self.get_size() if self.get_size() != ida_typeinf.BADSIZE else 0)
That is messing up the if-statment in
# Then we'll return each type that we fetched while prioritizing the one that is
# stored in NSUP_, then the guessed one, and then falling back to the missing one.
if nsupped or guessed:
return nsupped or guessed # nsupped is OK here but the __len__() returns 0 and that makes the if nsupped evaluate to false.
tinfo_t with function prototypes have a size of 0 and when there is no bool it checks for len and if that returns 0 then the if statement fails.
Sorry to take up your time!
Now is the database.functions.list() also working as expected <3
from ida-minsc.
I added
setattr(ida_typeinf.tinfo_t, '__bool__', lambda self: self.is_well_defined())
to my code and now it all works well.
from ida-minsc.
Lol. And that, hands-down, is why Python is a terrible programming language.
from ida-minsc.
I closed the PR, and will close this issue. If you feel that is in error, let me know and I'll re-open.
Btw, you should consider the "persistence-refactor" branch if you believe the things in this plugin are actually useful to you. It's development is still ongoing, but it significantly improves structure arithmetic, all the matchers allow iterable types, tagging and searches are improved, operand references can be used for referencing specific operands, all references now bundle their access ('rwx'), etc. There's quite a lot. The "master" branch is about a year behind, and is before i decided to go "all-in" on some of the original features.
As an example, since you're looking at database.functions.list
, you can do.
db.functions.list(ea=[addr1, addr2, addr3])
# now capture the results
addrs = db.functions(ea=[addr1, addr2, addr3])
# feed them back into the matchers
refs = [func.up(ea) for ea in addrs]
db.functions.list(ea=itertools.chain(*refs), like='CCmdTarget::*')
# consolidate into one line, and just list all functions that reference a function that references your target function
db.functions.list(ea=itertools.chain(*map(func.up, db.functions(ea=[addr1, addr2, addr2]))))
Or if you want to distinguish calls in the current function that dereference an address.
for ref in func.calls():
if '&' in ref: print(db.disasm(ref))
# something with registers being written to
for ref in func.registers(ins.reg.eax):
if 'w' in ref: print(db.disasm(ref))
If you have any questions, feel free to ask in the discussions.
from ida-minsc.
What version of IDA are you using, and can you include the full backtrace? I'm thinking that it's happening at line 683 or 633, but the line number you provided (line 595) doesn't show which callable in the function
module is actually failing (the one that uses internal.interface.tinfo.function_details
).
Line 595 in af07f16
Specifically to reproduce your issue, you should be able to use db.functions.list(ea=0x401029)
which should exclude this line number from your backtrace and result in the same problem.
So... I'm guessing here, but there's a chance that the type of your function at 0x401029 is missing.. This is actually strange, because on IDA 7.7SP1 (on Windows), when a type is not applied to a function...it should always be guessing the type. This is the same on all of the instances of IDA that I currently have available. However, I'm sure the full backtrace of the exception that gets raised would give me a better idea of what is happening anyways.
Example of what it should look like on 7.7 SP1 (windows)
.text:0000000180007170 ; =============== S U B R O U T I N E =======================================
.text:0000000180007170
.text:0000000180007170
.text:0000000180007170 sub_180007170 proc near ; DATA XREF: .data:00000001800D3D08↓o
.text:0000000180007170 ; .pdata:00000001800DB210↓o
.text:0000000180007170
.text:0000000180007170 var_88 = dword ptr -88h
.text:0000000180007170 var_80 = dword ptr -80h
.text:0000000180007170 var_78 = dword ptr -78h
Python>func.t()
__int64 __fastcall()
Same thing on 8.3 (non-windows)
.text:10001B80 ; =============== S U B R O U T I N E =======================================
.text:10001B80
.text:10001B80
.text:10001B80 sub_10001B80 proc near ; CODE XREF: sub_10001000+BC↑p
.text:10001B80 ; sub_10001A60+3C↑p
.text:10001B80
.text:10001B80 var_10 = dword ptr -10h
Python>func.t()
int __cdecl(int, int)
So, because of this guess...can you print the type of the function in your database that is raising the exception with the following? I might have more questions after this once I can see exactly which line is causing the issue, but I'm theorizing that the following line is returning None
in your environment for some reason.
print(function.type(0x401029))
Also, if you're able to transfer me your database (or just a database with that function in it), that would allow me to identify what specifically about that function is causing the issue...but I understand if that's not something that you're able to do.
from ida-minsc.
So I cannot send the IDA database but I can give you a full trace log and I can send the file that I used to test it on. It's an old 32-bit PE file that is a very easy crackme (not mine)
Jupyter QtConsole 5.4.0
Python 3.8.10 (tags/v3.8.10:3d8993a, May 3 2021, 11:48:03) [MSC v.1928 64 bit (AMD64)]
Type 'copyright', 'credits' or 'license' for more information
IPython 8.9.0 -- An enhanced Interactive Python. Type '?' for help.
database.functions.list()
---------------------------------------------------------------------------
InvalidTypeOrValueError Traceback (most recent call last)
Cell In[1], line 1
----> 1 database.functions.list()
File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\_utils.py:694, in database.list(*arguments, **keywords)
691 # Now we have a matching callable for the user's parameters, and we just need
692 # to unpack our individual parameters and dispatch to the callable with them.
693 parameters, wild_parameters, keyword_parameters = result_parameters
--> 694 return result_callable(*itertools.chain(parameters, wild_parameters), **keyword_parameters)
File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\database.py:598, in functions.list(cls, **type)
596 '''List all of the functions in the database with a glob that matches `string`.'''
597 return cls.list(like=string)
--> 598 @utils.multicase()
599 @classmethod
600 @utils.string.decorate_arguments('name', 'like', 'regex')
601 def list(cls, **type):
602 '''List all of the functions in the database that match the keyword specified by `type`.'''
603 listable = []
File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\_utils.py:1994, in transform.<locals>.wrapper(F, *rargs, **rkwds)
1992 cls = E.__class__
1993 raise cls("{!s}: Exception raised while transforming parameter `{:s}` with value {!r}".format('.'.join([f.__module__, f.__name__]), argname, kwds[argname]))
-> 1994 return F(*res, **kwds)
File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\database.py:633, in functions.list(cls, **type)
631 refs = max(len(xref.up(ea)), refs)
632 lvars = max(Fcount_lvars(func) if idaapi.get_frame(ea) else 0, lvars)
--> 633 avars = max(Fcount_avars(func), avars)
635 listable.append(ea)
637 # Collect the number of digits for everything from the first pass
File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\_utils.py:56, in <lambda>(*a)
54 first, second, third, last = operator.itemgetter(0), operator.itemgetter(1), operator.itemgetter(2), operator.itemgetter(-1)
55 # return a closure that executes a list of functions one after another from left-to-right.
---> 56 fcompose = lambda *Fa: functools.reduce(lambda F1, F2: lambda *a: F1(F2(*a)), builtins.reversed(Fa))
57 # return a closure that executes function `F` whilst discarding any arguments passed to it.
58 fdiscard = lambda F, *a, **k: lambda *ap, **kp: F(*a, **k)
File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\_utils.py:56, in <lambda>(*a)
54 first, second, third, last = operator.itemgetter(0), operator.itemgetter(1), operator.itemgetter(2), operator.itemgetter(-1)
55 # return a closure that executes a list of functions one after another from left-to-right.
---> 56 fcompose = lambda *Fa: functools.reduce(lambda F1, F2: lambda *a: F1(F2(*a)), builtins.reversed(Fa))
57 # return a closure that executes function `F` whilst discarding any arguments passed to it.
58 fdiscard = lambda F, *a, **k: lambda *ap, **kp: F(*a, **k)
File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\_utils.py:56, in <lambda>(*a)
54 first, second, third, last = operator.itemgetter(0), operator.itemgetter(1), operator.itemgetter(2), operator.itemgetter(-1)
55 # return a closure that executes a list of functions one after another from left-to-right.
---> 56 fcompose = lambda *Fa: functools.reduce(lambda F1, F2: lambda *a: F1(F2(*a)), builtins.reversed(Fa))
57 # return a closure that executes function `F` whilst discarding any arguments passed to it.
58 fdiscard = lambda F, *a, **k: lambda *ap, **kp: F(*a, **k)
File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\function.py:2651, in frame.arguments.iterate(cls, func)
2648 # If we got here, then we have type information that we can grab out
2649 # of the given address. Once we have it, rip the details out o it.
2650 tinfo = type(ea)
-> 2651 _, ftd = interface.tinfo.function_details(ea, tinfo)
2653 # Now we just need to iterate through our parameters collecting the
2654 # raw location information for all of them. We preserve the type
2655 # information in case we're unable to find the argument in a member.
2656 items = []
File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\_interface.py:2797, in tinfo.function_details(cls, func, ti)
2793 tinfo = ti
2795 # Anything else is a type error that we need to raise to the user.
2796 else:
-> 2797 raise internal.exceptions.InvalidTypeOrValueError(u"{:s}.function_details({:#x}, {!r}) : The type that was received ({!r}) for the specified function ({:#x}) was not a function type.".format('.'.join([__name__, cls.__name__]), ea, "{!s}".format(ti), "{!s}".format(ti), ea))
2799 # Now we can check to see if the type has details that we can grab the
2800 # argument type out of. If there are no details, then we raise an
2801 # exception informing the user.
2802 if not tinfo.has_details():
InvalidTypeOrValueError: internal.interface.tinfo.function_details(0x401029, '') : The type that was received ('') for the specified function (0x401029) was not a function type.
database.functions.list(ea=0x401029)
---------------------------------------------------------------------------
InvalidTypeOrValueError Traceback (most recent call last)
Cell In[2], line 1
----> 1 database.functions.list(ea=0x401029)
File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\_utils.py:694, in database.list(*arguments, **keywords)
691 # Now we have a matching callable for the user's parameters, and we just need
692 # to unpack our individual parameters and dispatch to the callable with them.
693 parameters, wild_parameters, keyword_parameters = result_parameters
--> 694 return result_callable(*itertools.chain(parameters, wild_parameters), **keyword_parameters)
File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\database.py:598, in functions.list(cls, **type)
596 '''List all of the functions in the database with a glob that matches `string`.'''
597 return cls.list(like=string)
--> 598 @utils.multicase()
599 @classmethod
600 @utils.string.decorate_arguments('name', 'like', 'regex')
601 def list(cls, **type):
602 '''List all of the functions in the database that match the keyword specified by `type`.'''
603 listable = []
File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\_utils.py:1994, in transform.<locals>.wrapper(F, *rargs, **rkwds)
1992 cls = E.__class__
1993 raise cls("{!s}: Exception raised while transforming parameter `{:s}` with value {!r}".format('.'.join([f.__module__, f.__name__]), argname, kwds[argname]))
-> 1994 return F(*res, **kwds)
File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\database.py:633, in functions.list(cls, **type)
631 refs = max(len(xref.up(ea)), refs)
632 lvars = max(Fcount_lvars(func) if idaapi.get_frame(ea) else 0, lvars)
--> 633 avars = max(Fcount_avars(func), avars)
635 listable.append(ea)
637 # Collect the number of digits for everything from the first pass
File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\_utils.py:56, in <lambda>(*a)
54 first, second, third, last = operator.itemgetter(0), operator.itemgetter(1), operator.itemgetter(2), operator.itemgetter(-1)
55 # return a closure that executes a list of functions one after another from left-to-right.
---> 56 fcompose = lambda *Fa: functools.reduce(lambda F1, F2: lambda *a: F1(F2(*a)), builtins.reversed(Fa))
57 # return a closure that executes function `F` whilst discarding any arguments passed to it.
58 fdiscard = lambda F, *a, **k: lambda *ap, **kp: F(*a, **k)
File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\_utils.py:56, in <lambda>(*a)
54 first, second, third, last = operator.itemgetter(0), operator.itemgetter(1), operator.itemgetter(2), operator.itemgetter(-1)
55 # return a closure that executes a list of functions one after another from left-to-right.
---> 56 fcompose = lambda *Fa: functools.reduce(lambda F1, F2: lambda *a: F1(F2(*a)), builtins.reversed(Fa))
57 # return a closure that executes function `F` whilst discarding any arguments passed to it.
58 fdiscard = lambda F, *a, **k: lambda *ap, **kp: F(*a, **k)
File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\_utils.py:56, in <lambda>(*a)
54 first, second, third, last = operator.itemgetter(0), operator.itemgetter(1), operator.itemgetter(2), operator.itemgetter(-1)
55 # return a closure that executes a list of functions one after another from left-to-right.
---> 56 fcompose = lambda *Fa: functools.reduce(lambda F1, F2: lambda *a: F1(F2(*a)), builtins.reversed(Fa))
57 # return a closure that executes function `F` whilst discarding any arguments passed to it.
58 fdiscard = lambda F, *a, **k: lambda *ap, **kp: F(*a, **k)
File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\function.py:2651, in frame.arguments.iterate(cls, func)
2648 # If we got here, then we have type information that we can grab out
2649 # of the given address. Once we have it, rip the details out o it.
2650 tinfo = type(ea)
-> 2651 _, ftd = interface.tinfo.function_details(ea, tinfo)
2653 # Now we just need to iterate through our parameters collecting the
2654 # raw location information for all of them. We preserve the type
2655 # information in case we're unable to find the argument in a member.
2656 items = []
File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\_interface.py:2797, in tinfo.function_details(cls, func, ti)
2793 tinfo = ti
2795 # Anything else is a type error that we need to raise to the user.
2796 else:
-> 2797 raise internal.exceptions.InvalidTypeOrValueError(u"{:s}.function_details({:#x}, {!r}) : The type that was received ({!r}) for the specified function ({:#x}) was not a function type.".format('.'.join([__name__, cls.__name__]), ea, "{!s}".format(ti), "{!s}".format(ti), ea))
2799 # Now we can check to see if the type has details that we can grab the
2800 # argument type out of. If there are no details, then we raise an
2801 # exception informing the user.
2802 if not tinfo.has_details():
InvalidTypeOrValueError: internal.interface.tinfo.function_details(0x401029, '') : The type that was received ('') for the specified function (0x401029) was not a function type.
print(database.function.type(0x401029))
<< note from Harding, there is nothing here. >>
database.function.type(0x401029)
Out[4]: <class 'ida_typeinf.tinfo_t'> which looks like:
<< note from Harding, this an empty tinfo_t >>
The crackme can be found here: https://hardingonline.se/vault.zip
from ida-minsc.
Using version 8.3.230608 Windows x64 (64-bit address size)
I just opened the file in IDA64 (even if it is a 32-bit since that is the new work flow in IDA)
and I am using ipyida.
I'm getting the same error without ipyida.
from ida-minsc.
I get the correct tinfo_t with the following code:
type_at_addr = ida_typeinf.print_type(0x401029, ida_typeinf.PRTYPE_1LINE | ida_typeinf.PRTYPE_SEMI)
print(type_at_addr)
INT_PTR __userpurge DialogFunc@<eax>(INT_PTR@<eax>, HWND hDlg, UINT, WPARAM, LPARAM);
from ida-minsc.
Hmm. Trying to build a fresh database from the binary results in the following type being applied by default. For some reason, that also returns a valid type using your unit-test. :-/
.text:00401029
.text:00401029 ; Attributes: bp-based frame
.text:00401029
.text:00401029 ; INT_PTR __stdcall DialogFunc(HWND, UINT, WPARAM, LPARAM)
.text:00401029 DialogFunc proc near ; DATA XREF: start+E↑o
.text:00401029
.text:00401029 hDlg = dword ptr 8
.text:00401029 arg_4 = dword ptr 0Ch
.text:00401029 arg_8 = dword ptr 10h
Python>function.type(0x401029)
INT_PTR __stdcall(HWND, UINT, WPARAM, LPARAM)
Applying the __userpurge
type directly and trying again still returns the same....
.text:00401029 ; Attributes: bp-based frame
.text:00401029
.text:00401029 ; INT_PTR __userpurge DialogFunc@<eax>(INT_PTR@<eax>, HWND hDlg, UINT, WPARAM, LPARAM)
.text:00401029 DialogFunc proc near ; DATA XREF: start+E↑o
.text:00401029
.text:00401029 hDlg = dword ptr 8
Python>function.type(0x401029)
INT_PTR __userpurge@<eax>(INT_PTR@<eax>, HWND hDlg, UINT, WPARAM, LPARAM)
However, thanks to the backtrace that you gave me, that should be enough to identify the issue. Appreciate it.
Also, this isn't that important, but it's worth keeping in mind that database.function.type
only works because the function
module is being imported into the database with its full name. It's not always guaranteed to have that name, and the correct way to ensure it always exists is to use the function
module or its alias, func
. So if that module ends up getting a function
namespace added to it, your code might break. Hence it's recommended to use function.type
, function.t
, or func.t
in the future.
print(database.function.type(0x401029))
<< note from Harding, there is nothing here. >>
database.function.type(0x401029)
Out[4]: <class 'ida_typeinf.tinfo_t'> which looks like:
<< note from Harding, this an empty tinfo_t >>
from ida-minsc.
Okay, so the issue has got to be that IDAPython's idaapi.guess_tinfo(ti, ea)
is failing resulting in the type not being populated (https://github.com/arizvisa/ida-minsc/blob/master/base/function.py#L3209). It actually debug logs and returns an empty type. The sanity checks inside internal.interface.tinfo.function_details
that verify you don't give it bunk data are raising the exception.
Anyways, am currently working on a fix for it which ensures that function.type
cannot possibly fail.
from ida-minsc.
Okay, I think this does a better job at guarding against your situation. To be fair, I'm actually not sure what causes your situation because the logic that is happening is to literally use idaapi.get_tinfo
to fetch the type for an address, and if that fails for some reason to then ask the disassembler to guess the function type with idaapi.guess_tinfo
. If for some reason the disassembly cannot guess the function type, then database.type
demangles the function name and then asks the disassembler to attempt parsing it for the type.
This means that in your situation, both idaapi.get_tinfo
and idaapi.guess_tinfo
are failing. In terms of reproducing your issue, the only thing I can think of is that perhaps the AFL_TI
flag is clear or NSUP_TINFO
for that function is missing. I took a 5-minute look at the implementation of print_type
, but it actually uses get_opinfo
followed by a call to calc_c_cpp_name
for rendering it to a string. So, I'm not sure why the database.type
function is failing on your system since it's doing practically the exact same thing for that address. :-/
Anyways, try out PR #187 and let me know if that remedies it for you in the "master" branch.
from ida-minsc.
Wait, what!? There is absolutely no reason that should fail? It seems the code for creating a function type within your type library doesn't work?
+ missing, ftd = idaapi.tinfo_t(), idaapi.func_type_data_t()
+ ftd.rettype = idaapi.tinfo_t(idaapi.BT_VOID if fn and fn.flags & idaapi.FUNC_NORET else idaapi.BT_INT)
+ ftd.cc = idaapi.CM_CC_UNKNOWN | idaapi.BFA_NORET if fn and fn.flags & idaapi.FUNC_NORET else idaapi.CM_CC_UNKNOWN
+ missing = missing if missing.create_func(ftd) else None
Perhaps if you modify base/function.py
at line number 3211 and change idaapi.CM_CC_UNKNOWN
to idaapi.CM_CC_CDECL
? I'm shooting in the dark here...
The logic that should be happening is literally the following:
ti = idaapi.tinfo_t()
if idaapi.get_tinfo(ti, ea):
return ti
ti = idaapi.tinfo_t()
error = idaapi.guess_tinfo(ti, ea)
if error != idaapi.GUESS_FUNC_FAILED:
return ti
# the following works without a database being loaded.
missing, ftd = idaapi.tinfo_t(), idaapi.func_type_data_t()
ftd.rettype = idaapi.tinfo_t(idaapi.BT_INT)
ftd.cc = idaapi.CM_CC_UNKNOWN
if not missing.create_func(ftd):
raise Exception
return missing
I do not have any ideas why the last thing should fail, because it doesn't depend on anything within a database. Actually, you can run that entire last section without a database being open and it should work.
from ida-minsc.
I was stepping your code and you are correct that it is idaapi.guess_type() that fails. I tried some other database and I got some very strange results that the guess_tinfo was wrong even if the function prototype was set. I don't know what to make out of it.
from ida-minsc.
From my VERY limited testing, it seems that the guess_tinfo() ignores what the type is set to and only use "what IDA thinks".
I used the following code and did it on some different functions that had no types, type from the decompiler, manually set type (in the disassembly window)
import ida_typeinf
def get_type(arg_ea: int) -> ida_typeinf.tinfo_t:
type_as_str = ida_typeinf.print_type(arg_ea, ida_typeinf.PRTYPE_1LINE | ida_typeinf.PRTYPE_SEMI)
res = ida_typeinf.tinfo_t()
ida_typeinf.parse_decl(res, None, type_as_str, ida_typeinf.PT_SIL) # PT_SIL == SILENT, meaning no popup that there was any problems
return res
def guess_type(arg_ea: int) -> ida_typeinf.tinfo_t:
res = ida_typeinf.tinfo_t()
idaapi.guess_tinfo(res, arg_ea)
return res
from ida-minsc.
From my VERY limited testing, it seems that the guess_tinfo() ignores what the type is set to and only use "what IDA thinks". I used the following code and did it on some different functions that had no types, type from the decompiler, manually set type (in the disassembly window)
Yeah, it's okay if guess_tinfo
returns the wrong type. This is due to it only being a fallback in case get_tinfo
fails to get the type the user specified. The get_tinfo(ti, 0x401029)
call shouldn't fail to begin with, but that's the reason we are here.
So this following code, (done by database.type.__new__
), simply fetches the type from an address. This should be the type that you manually apply, or that the disassembler/decompiler has applied. If it's successful, it should return a function prototype, otherwise it falls through to the next conditional.
ti = idaapi.tinfo_t()
if idaapi.get_tinfo(ti, ea):
# assert(ti.is_func()), 'should be a func prototype'
return ti
This next code is essentially the guess (it is used by function.type.__new__
). This is a failure case for when there wasn't a type attached to a function. Its goal is just to return anything that looks like a function type. So, to clarify, it's ok if the type returned from guess_tinfo
is wrong because our previous idaapi.get_tinfo
told us there wasn't a type at the address to begin with.
ti = idaapi.tinfo_t()
error = idaapi.guess_tinfo(ti, ea)
if error != idaapi.GUESS_FUNC_FAILED:
# assert(ti.is_func()), 'should be a func prototype'
return ti
Then there is the final snippet, this is the one that raised your latest exception after I created the PR. This is an emergency case for when both get_tinfo
and guess_tinfo
fails. Essentially, if we couldn't get the type for an address or guess the type for the address, then this creates a dummy type for when everything is missing.
# the following works without a database being loaded.
missing, ftd = idaapi.tinfo_t(), idaapi.func_type_data_t()
ftd.rettype = idaapi.tinfo_t(idaapi.BT_INT)
ftd.cc = idaapi.CM_CC_UNKNOWN
if not missing.create_func(ftd):
raise Exception
assert(missing.is_func()), 'should be a func prototype'
return missing
What this is doing is creating a func_type_data_t
where the result is an integer and there are no parameters (the prototype should look like int missing()
). This is intended to always work. I'm explicitly checking the return value as a sanity check, and yet on your system the exception being raised suggests that it's unable to create this emergency type using tinfo_t.create_func(ftd)
.
On my systems, that last snippet always succeeds...regardless of there being an open database. If you even google-search, tinfo_t.create_func
, nobody is checking its result because they also assume that it always works. The condition on your system is essentially hitting every single failure path. So that means that idaapi.get_tinfo
can't get the type, then idaapi.guess_tinfo
is unable to guess the type, then tinfo_t.create_func
is unable to create the function type.
Since the intent of function.type
is literally to return a function type at all costs, and apparently tinfo_t.create_func
cannot create a function type, I'm unsure how to proceed other than reverse-engineer IDA, or ask support@ about the conditions that can result in the symptoms that are occurring on your system.
I guess the final thing I can do is to hardcode the type by deserializing it with something like:
ALWAYS_BE_A_FUNCTION_TYPE_PLZ = (b'\x0c\x10\x07\x01', None, None)
ti = idaapi.tinfo_t()
if not ti.deserialize(idaapi.get_idati(), *ALWAYS_BE_A_FUNCTION_TYPE_PLZ):
raise Exception
from ida-minsc.
Can you confirm that the following doesn't work. Maybe add it to the base/database.py
module, in a function of your choosing. This way you can call it as ti = database.blah()
.
def blah():
missing, ftd = idaapi.tinfo_t(), idaapi.func_type_data_t()
ftd.rettype = idaapi.tinfo_t(idaapi.BT_INT)
ftd.cc = idaapi.CM_CC_UNKNOWN
if not missing.create_func(ftd):
raise Exception("{!s}".format(missing))
assert(missing.is_func())
return missing
Then do something similar with the following function. Insert it into base/database.py
, and then try to call it as ti = database.blah2()
.
def blah2():
ALWAYS_BE_A_FUNCTION_TYPE_PLZ = (b'\x0c\x10\x07\x01', None, None)
ti = idaapi.tinfo_t()
if not ti.deserialize(idaapi.get_idati(), *ALWAYS_BE_A_FUNCTION_TYPE_PLZ):
raise Exception("{!s}".format(ti))
assert(ti.is_func())
return ti
If the first blah()
fails, then that's the symptom you're encountering and we are actually expecting that. If both of them inside base/database.py
fail, then go ahead and define both of those functions in your IPyIDA instance or whatever you're using (you can also use shift+f2), and then try calling both of them from there. If they still fail, then something is going on with your instance of IDAPython. If they succeed, then something else entirely is going on...
from ida-minsc.
I have to go to bed now but I tried database.type(0x00401029) and that gave the correct tinfo_t:
database.type(0x00401029)
Out[29]:
<class 'ida_typeinf.tinfo_t'> which looks like:
INT_PTR __stdcall(HWND, UINT, WPARAM, LPARAM)
while database.function.type(0x00401029) failed.
database.function.type(0x00401029)
---------------------------------------------------------------------------
DisassemblerError Traceback (most recent call last)
Cell In[35], line 1
----> 1 database.function.type(0x00401029)
File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\_utils.py:694, in function.__new__(*arguments, **keywords)
691 # Now we have a matching callable for the user's parameters, and we just need
692 # to unpack our individual parameters and dispatch to the callable with them.
693 parameters, wild_parameters, keyword_parameters = result_parameters
--> 694 return result_callable(*itertools.chain(parameters, wild_parameters), **keyword_parameters)
File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\function.py:3232, in type.__new__(cls, func)
3229 # If we weren't able to create the missing type, then we need to abort. This shouldn't
3230 # be possible at all whatsoever, but perhaps some database state is preventing us.
3231 if not missing:
-> 3232 raise E.DisassemblerError(u"{:s}({:#x}) : Unable to create a dummy function type to return for the for the specified function ({:#x}).".format('.'.join([__name__, cls.__name__]), ea, ea))
3234 logging.warning(u"{:s}({:#x}) : Unable to guess the missing type for the function at {:#x} due to error ({:d}) which will result in an empty function type (\"{:s}\") being returned.".format('.'.join([__name__, cls.__name__]), ea, ea, idaapi.GUESS_FUNC_FAILED, utils.string.escape("{!s}".format(missing), '"')))
3235 return missing
DisassemblerError: function.type(0x401029) : Unable to create a dummy function type to return for the for the specified function (0x401029).
This worked:
In [25]: ALWAYS_BE_A_FUNCTION_TYPE_PLZ = (b'\x0c\x10\x07\x01', None, None)
ti = idaapi.tinfo_t()
if not ti.deserialize(idaapi.get_idati(), *ALWAYS_BE_A_FUNCTION_TYPE_PLZ):
raise Exception
Out[26]:
<class 'ida_typeinf.tinfo_t'> which looks like:
int()
Your blah() function worked also:
def blah():
missing, ftd = idaapi.tinfo_t(), idaapi.func_type_data_t()
ftd.rettype = idaapi.tinfo_t(idaapi.BT_INT)
ftd.cc = idaapi.CM_CC_UNKNOWN
if not missing.create_func(ftd):
raise Exception("{!s}".format(missing))
assert(missing.is_func())
return missing
blah()
Out[28]:
<class 'ida_typeinf.tinfo_t'> which looks like:
int()
from ida-minsc.
Hold on, something else strange is going on. I have to do a fresh install in a virtual machine and see if there might be some other scripts that are interfering. Don't spend more time on this right now. <3
I REALLY gotta sleep now tho so that's gotta be done tomorrow.
from ida-minsc.
Closing this issue. If you feel this is in error, feel free to let me know and I'll re-open.
from ida-minsc.
Related Issues (20)
- Minor readability improvement of bool HOT 3
- Rename argument name "direction" to something better like "options" HOT 5
- Do not replace the os.sep HOT 5
- Suggestion: When doing the refactoring, drop support for Python 2 and older IDA HOT 3
- Using the name "binary" for a program is confusing when there is a file format in IDA named "binary file" HOT 4
- Suggestion: Add the name DLL as that is what IDA calls the shared objects HOT 2
- Add a comment that this value will be updated when a new file is loaded HOT 1
- Suggestion: If you drop support for older Python, then you can add type hints HOT 3
- Minor doc change: says "all" but only 1 is returned HOT 3
- Maybe duplication of code? bits() vs bitsize() HOT 3
- Wrong path on windows HOT 2
- Using double quotation marks makes this work on Windows also HOT 2
- The instruction namespace should return a ida_ua.insn_t() object instead of a string HOT 3
- Docs: Quick start is wrong, the example code throws exception HOT 1
- Suggestion: Add documention on how to use IDA-minsc together with the plugin ipyida HOT 7
- Rename go --> jumpto (since the term go usually means "let the debugger run" in RE) HOT 3
- Rename argument "none" to a better name since it is very easy to missread it as None HOT 3
- Replace idaapi.read_selection() with ida_kernwin.read_range_selection() HOT 3
- Inability to access the attributes of netnodes that use a tag of 0. HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from ida-minsc.