From 3ba7253294eaf1128d18e651be5519a110c03d3e Mon Sep 17 00:00:00 2001 From: Arnaud Diederen Date: Mon, 13 Jan 2020 09:23:59 +0100 Subject: [PATCH] Backported fixes --- idapyswitch.cpp | 1 + idapyswitch_win.cpp | 113 ++++++++++++++++++++++++++- idapython.cpp | 16 +++- out_of_tree/parsed_notifications.zip | Bin 4187862 -> 4187862 bytes python/idc.py | 2 +- pywraps.hpp | 1 + 6 files changed, 125 insertions(+), 8 deletions(-) diff --git a/idapyswitch.cpp b/idapyswitch.cpp index e428390..d1de4a7 100644 --- a/idapyswitch.cpp +++ b/idapyswitch.cpp @@ -16,6 +16,7 @@ //lint -esym(1788, iinc) is referenced only by its constructor or destructor #include +#include #include #include #include diff --git a/idapyswitch_win.cpp b/idapyswitch_win.cpp index 22fe902..ebb5804 100644 --- a/idapyswitch_win.cpp +++ b/idapyswitch_win.cpp @@ -97,6 +97,94 @@ static bool extract_version_from_path( return true; } + +#pragma comment(lib, "version.lib") +//------------------------------------------------------------------------- +DWORD GetFileVersionNumber(const char *filename, DWORD *pdwMSVer, DWORD *pdwLSVer) +{ + DWORD dwResult = NOERROR; + unsigned uiSize; + DWORD dwVerInfoSize; + DWORD dwHandle; + PBYTE prgbVersionInfo = NULL; + VS_FIXEDFILEINFO *lpVSFixedFileInfo = NULL; + + DWORD dwMSVer = 0xffffffff; + DWORD dwLSVer = 0xffffffff; + + qwstring wfilename; + if ( !utf8_utf16(&wfilename, filename) ) + { + dwResult = ERROR_INVALID_PARAMETER; + goto Finish; + } + + dwVerInfoSize = GetFileVersionInfoSizeW(wfilename.c_str(), &dwHandle); + if ( dwVerInfoSize != 0 ) + { + prgbVersionInfo = (PBYTE)qalloc(dwVerInfoSize); + if ( prgbVersionInfo == NULL ) + { + dwResult = ERROR_NOT_ENOUGH_MEMORY; + goto Finish; + } + + // Read version stamping info + if ( GetFileVersionInfoW(wfilename.c_str(), dwHandle, dwVerInfoSize, prgbVersionInfo) ) + { + // get the value for VS_FIXEDFILEINFO + if ( VerQueryValueW(prgbVersionInfo, L"\\", (LPVOID*)&lpVSFixedFileInfo, &uiSize) && (uiSize != 0) ) + { + dwMSVer = lpVSFixedFileInfo->dwFileVersionMS; + dwLSVer = lpVSFixedFileInfo->dwFileVersionLS; + } + } + else + { + dwResult = GetLastError(); + goto Finish; + } + } + else + { + dwResult = GetLastError(); + } + + out_verb("%s is version %d.%d.%d.%d\n", filename, HIWORD(dwMSVer), LOWORD(dwMSVer), HIWORD(dwLSVer), LOWORD(dwLSVer)); + +Finish: + if ( prgbVersionInfo != NULL ) + qfree(prgbVersionInfo); + if ( pdwMSVer != NULL ) + *pdwMSVer = dwMSVer; + if ( pdwLSVer != NULL ) + *pdwLSVer = dwLSVer; + + return dwResult; +} +//------------------------------------------------------------------------- +static bool extract_version_from_dll( + pylib_version_t *out, + const char *fname) +{ + DWORD dwMSVer, dwLSVer; + DWORD res = GetFileVersionNumber(fname, &dwMSVer, &dwLSVer); + if ( res == NOERROR ) + { + out->raw.sprnt("%d.%d.%d.%d", HIWORD(dwMSVer), LOWORD(dwMSVer), HIWORD(dwLSVer), LOWORD(dwLSVer)); + // Python DLL versions look like 3.7.4150.1013 -> 3.7.4 + out->major = HIWORD(dwMSVer); + out->minor = LOWORD(dwMSVer); + out->revision = HIWORD(dwLSVer) / 1000; + return true; + } + else + { + out_verb("error getting version of \"%s\": %s\n", fname, winerr(res)); + return false; + } +} + //------------------------------------------------------------------------- static bool is_python3Y_dll_file_name(const char *fname) { @@ -138,6 +226,9 @@ static bool probe_python_install_dir_from_dll_path( { char dll_path[QMAXPATH]; qmakepath(dll_path, sizeof(dll_path), dir, fb.ff_name, nullptr); + pylib_version_t tmp; + if ( extract_version_from_dll(&tmp, path) ) + *out_version = tmp; out_verb("Found: \"%s\" (version: %s)\n", dll_path, out_version->str(&verbuf)); out_paths->push_back(dll_path); @@ -392,10 +483,24 @@ void pyver_tool_t::do_find_python_libs(pylib_entries_t *result) const out("IDA previously used: \"%s\" (guessed version: %s). " "Making this the preferred version.\n", existing.c_str(), version.str(&verbuf)); - pylib_entry_t e(version); - e.paths.swap(paths); - e.preferred = true; - result->entries.push_back(e); + // do we have it in the list? + bool found = false; + for ( pylib_entry_t &e : result->entries ) + { + if ( e.paths.has(existing) ) + { + found = true; + e.preferred = true; + } + } + if ( !found ) + { + // add a new one + pylib_entry_t e(version); + e.paths.swap(paths); + e.preferred = true; + result->entries.push_back(e); + } } else { diff --git a/idapython.cpp b/idapython.cpp index 11f5cc5..f1f3507 100644 --- a/idapython.cpp +++ b/idapython.cpp @@ -840,7 +840,7 @@ static bool return_python_result( // We use the Python function to execute the script because it knows how to deal with // module reloading. static bool IDAPython_ExecFile( - const char *FileName, + const char *path, PyObject *globals, qstring *errbuf, const char *idaapi_script = S_IDAAPI_EXECSCRIPT, @@ -856,12 +856,22 @@ static bool IDAPython_ExecFile( } char script[MAXSTR]; - qstrncpy(script, FileName, sizeof(script)); + qstrncpy(script, path, sizeof(script)); strrpl(script, '\\', '/'); + newref_t py_script(IDAPyStr_FromUTF8(script)); if ( globals == NULL ) globals = get_module_globals(); - newref_t py_script(IDAPyStr_FromUTF8(script)); + if ( globals != get_module_globals() ) + { + // Executions that take place in the scope of their own module, + // should have the '__file__' attribute properly set (so that + // it doesn't just get temporarily set and then removed by + // `ida_idaapi.IDAPython_ExecScript`. + newref_t py_file_key(IDAPyStr_FromUTF8(S_FILE)); + if ( !PyDict_Contains(globals, py_file_key.o) ) + PyDict_SetItem(globals, py_file_key.o, py_script.o); + } borref_t py_false(Py_False); newref_t py_ret(PyObject_CallFunctionObjArgs( py_execscript.o, diff --git a/out_of_tree/parsed_notifications.zip b/out_of_tree/parsed_notifications.zip index 0779c5a0087ccc261d9756204ca0d95cce2c3069..71480dcb636bf9d55889f9b53d51f9aa5a4e860a 100644 GIT binary patch delta 10241 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 N*O0#W<)e(?{{ygIDewRQ delta 10266 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∨S#zy$34d`1TtO_zbbQl$;%mlYA=ubm6wkqD@O%tWsQ>> zorx9WvZUz1hJhQv;a*Ri3FyxG;-Y|#4S%!2_1K1SPaxFHwsDX6SCKV!;WW5QTz81= zT+g_-puA*o+&)D2>`8G)0JO=pI0QQ6jMLx5CGZa@oI>Wrm2riLEairiB|lEGXZe!2 z8o+p&gIZ%ey;Nf)WX}*L5rQC z(A@at&fh@`FLv1UpaM-<{T>$)IH}OxbAfO4kx_-*%nr8ot)tGHxR1sOWwG0GpP%Kf+YD zY;(_(gtg0kj(SOMA+z)BhWjFpNK+mqbkK^m^+ipK}_UeIK zs)eULbMMC2)R*q{$cOhox+V30A@kt&mRJbw2>sj$^_=6T6NttF9oBkqvqG(mo++F` zk&TGV^qa+V5}fad@Syq+TzrR7QH*CGKCpeaIBys?SQ0&F;NmRjO7iR1&=bVN5DjYO zu|a;{wDSammN}h03mS0QvjQ1695ix+r#Zl%*yb6Hm9NM%0VnqF^;`tMxd%PH@b%?! zj|pV({}x0QdoETD02SFVD}oHqJSb_2{n=zJpfvv5gX#xkrj;K(>jA2fGG!u99(1~T z%2{k)R3qgpXi(!ykv!}ZZ%Q_Vy}d!oI;`5YPN|OSfXB7`G&ZW>WCZ?pLWf4CNLBmo zF)7!iy z-D=ey?Hz;UxNWkxfb$<1J_#c0X4Et=+gpW0parwNi{J=6vDcaF{SN}^izVJr=|0s^ z;Oz@e*yx;%p|*IxfLmYM@6A!k%F9jS-Pf$k9Dm$f=M^{P1C%Oh(tRX#EM!!8DzyZl znJIjfI<^H@Qs060yql?I7;dot8AQ;^=c)KRiO41g>{j+wY9ol1-FO`U=>;EBe+1}Z z3g0Y1c~`;L2m`H#QNBnXCq&j^htrg5z5&?sG|pE90PAY{YyiqsqJfQ^^ZNF~MrQkb ze^lq(jLYyXg?24{eNstp8R)|$2I{vh!+m`al}zJ(X)wl7b9^iC&a%)a-E;;1-$LyF zEki+gPJu5MfuZtdUyW#vt<4#q5tL@0^KF5|%r|_&cxV6F2fd2yb}fQ-{N`H+LuSz- zoQ6I2N#nkQWj<*Je*T3I=@Qq;f4%bE0`jZxe7F(d*RJE`{-qFxRqd~go}R0_h25V6 zAGjOu#}x{(vPKWqg53TL9P8)v&xQ%VYUIBL8D41O@5qsf?504VI<5T8I2$4xznN)O zJO5nRF-KItlYcve@gm)S9;*|B{i6Uhb(~)gCDfnb_X7FkY5toKOZS<6Dc_dn_}46m-?kU2t|SaAcV#z64zGw&tf9MT)<5GxA;e*xMGD%FYvbTuzxxb zg`e<$1Zk1y{gT^_D)sNezJzOjDUhgc`RjoU*1J_x$bK(3Wq