Привет, хабрахабр!
Столкнулся я с необходимостью модифицировать поведение одной чужой программы, написанной на языке Python. Казалось бы что сложного, Python ведь, бери исходник да модифицируй сколько влезет. Но не тут-то было. Дело осложнялось тем, что программа не просто была написана на Python, она была ещё и откомпилирована при помощи IronPython и никаких исходников не имелось. Для обычного, так скажем, канонического CPython существует Over 9000 различных декомпайлеров байткода из файлов .pyc обратно в .py, например [мой любимый] uncompyle2 и другие, а для IronPython ничего подобного я не нашёл. То ли плохо искал, то ли таковых действительно не существует. Пришлось разбираться самому. Говорю сразу, процесс я не автоматизировал, всё так сказать hand made.
После скармливания исходного файла IronPython делает из него практически native executable в формате .Net (я говорю «практически» потому что делает он исполняемый файл для .Net, а не приложение с конкретным машинным кодом для конкретного процессора). А это значит что для первичного разбора полученного файла можно использовать какой-нибудь из существующих декомпайлеров .Net.
Для изучения во что же IronPython превращает исходный питоновский файл я не придумал ничего лучшего, как скормить ему простейшую (специально не оптимизированную, для более полного изучения) программу вычисления первых десяти чисел Фибоначчи:
BUGFIXED 19 мая 2015
Конечно же вместо
должно быть
но исправлять уже не буду, слишком много за этим потянется.
IronPython создал два файла: .exe с маленьким запускным стабом и .dll с основной программой. .exe был неинтересен т.к. кроме загрузки .dll с основной программой и передачи ей управления ничего не делал, поэтому всё дальнейшее относится к .dll
Для всей программы IronPython создаёт класс
в котором имеется лишь один метод типа
содержащий __main__. Все остальные методы класса DLRCachedCode имеют тип
В них содержатся функции, классы, лямбды, генераторы и пр. исходной питоновской программы.
Перед методом, содержащим __main__, через объявление атрибута CachedOptimizedCode описывается глобальное пространство имён программы:
В самом начале метода __main__ строится массив всех возможных локальных для данной программы сущностей: всех функций, всех классов, всех compiler generated лямбд и вспомогательных внутренних функций IronPython, через которые runtime библиотека IronPython осуществляет работу с питоновскими списками, словарями, слайсами. И даже все математические/логические операции и операции сравнения тоже производятся посредством вызова внутренних вспомогательных функций IronPython runtime:
Видно что функции из исходного питоновского текста строятся посредством вызова MakeFunctionCode из пространства имён PythonOps IronPython (да, забыл отметить что IronPython это компилятор с открытым исходным текстом, написан на C#, и исходники любой его версии можно свободно скачать из интернета по этой ссылке), имеющего следующую сигнатуру:
То есть мы видим что наша
тут объявлена как
имеет реальное имя «fib», строчку документации «This function does the main work to calculate Fibonacci numbers»,, получает аргумент с именем «n», начинается в исходном файле «fibonacci.py» в строке 3 на столбце 1, завершается в строке 17 на столбце 19, в сгенерённом IronPython коде она будет именоваться как «fib$2», у неё пустые freeVars, names и cellVars (я не буду вдаваться в подробности что это такое, читатели хаба Python наверняка знакомы с этими понятиями), использует локальные переменные (параметры, кстати, трактуются как локальные переменные) «n», «fib1», «fib2», «i» и «fib_sum» и всего локальных переменных 5 штук.
Все возможные вызовы внутренних вспомогательных функций IronPython runtime заносятся в этот массив локальных переменных как шаблонные вызовы CallSite (опять не буду вдаваться в подробности, эта статья озаглавлена как «Поверхностный реверс инжиниринг IronPython», а не «Доскональный разбор»).
После этого производится модификация некоторых глобальных переменных чтобы они содержали актуальную информацию:
(как видно меняются переменные "__file__" и "__name__", установленнные ранее через атрибут CachedOptimizedCode)
и начинается выполнение самой программы:
Весь «бред» выше это то, во что IronPython превратил далеко не всю программу, а лишь
Чтобы сделать это более-менее понятным был написан скрипт (его не привожу, он тривиальнейший), заменяющий имена переменных вида globalArrayFromContext[7], strongBox.Value[3].Target.Invoke и подобных на их «очеловеченные» имена из ранее построенных IronPython массивов глобальных и локальных переменных. Получается уже не компилируемый, то есть csc.exe его «не возьмёт», но гораздо более читабельный человеком код:
IMHO видеть L[PythonOps.MakeGetAction($globalContext, «format», false)]), G[i].get_CurrentValue() и подобные вместо strongBox.Value[4]).Target.Invoke и globalArrayFromContext[7].get_CurrentValue() понятнее и приятнее.
Т.к. повторюсь, процесс я не автоматизировал (работа была штучная и изготавливать конвейерно-поточные специнструменты не было необходимости), то дальше производится разбор руками, полный hand made. Берём, к примеру, функцию fib:
и начинаем. В самом начале создаются три объекта: result (имя говорит само за себя, там будет то, что функция возвращает), obj2 и obj.
Если просто создаётся objНОМЕР как выше:
то это как правило вспомогательные объекты, используемые как временные хранилища для временных результатов if, enumerable и т.д. Если же объект инициализируется во время создания:
то это локальная переменная. Имя локальной переменной узнать довольно просто (есть нюансы, но они довольно редки и я не буду на них заострять внимание). Мы уже видели ранее что наша функция «fib» была объявлена как:
Отбрасываем аргумент «n» и получаем что первый инициализируемый при создании объект, в данном случае obj3, имеет в оригинальном коде имя «fib1». Второй инициализируемый при создании объект, obj4, называется «fib2». Третий, obj5, соответственно «в девичестве» имел имя «i».
После создания объектов идёт какой-то ужас:
Убираем в текстовом редакторе «шум», форматируем «лесенкой» для лучшей читаемости и получаем:
Если кто не видит в этом коде явного
тому осенью на переэкзаменовку ;-) Или менять профессию.
То есть наш
мы благополучно нашли. Идём дальше.
Это само собой
(переменные с именем «num» содержат номер строки исходного файла, IronPython так всегда делает, на этом заострять внимание не надо)
Дальше
Вот obj6 в данном случае не временная, хотя не инициализированная (я выше говорил про «есть нюансы», вот это один из них) переменная, на самом деле это переменная «fib_sum» из исходного текста. Т.к. присвоение ей (и начальная инициализация) идёт внутри цикла, а значение после используется вне цикла, поэтому IronPython был вынужден объявить её тут.
Дальше:
Мысленно «переписываем» это как
(это просто на самом деле, лично я терялся только первые минут 40-50 [интересующая программа была большая и циклы/блоки там были гораздо побольше, я её дней пять так вот сидел переводил], после уже тьфу, раз плюнуть получалось)
и с учётом того, что (см. выше) мы уже знаем кто такие на самом деле (в исходном файле то есть) ob3, obj4, obj5 и obj6 пишем:
Ого! Похоже мы получили вполне себе
И последний штрих:
То есть переменной «result» присваивается «fib_sum» и это дело возвращается. We did it! We HAVE DONE it!!!
На try в начале методов и catch в конце методов внимания обращать тоже не надо, это чисто IronPytonовские штуки и к нам отношения не имеют. Хотя питоновские исключения IronPython обрабатывает почти так же, но в данном случае pure Python exceptions нет, это ловятся исключения самого IronPython.
Ну вот как-то так.
Столкнулся я с необходимостью модифицировать поведение одной чужой программы, написанной на языке Python. Казалось бы что сложного, Python ведь, бери исходник да модифицируй сколько влезет. Но не тут-то было. Дело осложнялось тем, что программа не просто была написана на Python, она была ещё и откомпилирована при помощи IronPython и никаких исходников не имелось. Для обычного, так скажем, канонического CPython существует Over 9000 различных декомпайлеров байткода из файлов .pyc обратно в .py, например [мой любимый] uncompyle2 и другие, а для IronPython ничего подобного я не нашёл. То ли плохо искал, то ли таковых действительно не существует. Пришлось разбираться самому. Говорю сразу, процесс я не автоматизировал, всё так сказать hand made.
После скармливания исходного файла IronPython делает из него практически native executable в формате .Net (я говорю «практически» потому что делает он исполняемый файл для .Net, а не приложение с конкретным машинным кодом для конкретного процессора). А это значит что для первичного разбора полученного файла можно использовать какой-нибудь из существующих декомпайлеров .Net.
Небольшое лирическое отступление про разные декомпиляторы .Net и их Addin'ы
Да, я нашёл, нагуглил IronPython Reflector Addin для .NET Reflector, но то, что он генерил, меня совсем не порадовало и не устроило. Во-первых, код получался абсолютно нечитаемым и во-вторых, сам .NET Reflector не мог толком декомпилировать исходный файл, постоянно спотыкался через метод-через два и жаловался
dotPeek вообще не смог декомпилировать ни один метод, Telerik JustDecompile как и .NET Reflector спотыкался через раз, как всегда лучшим и единственным нормально работающим вариантом оказался ILSpy
dotPeek вообще не смог декомпилировать ни один метод, Telerik JustDecompile как и .NET Reflector спотыкался через раз, как всегда лучшим и единственным нормально работающим вариантом оказался ILSpy
Для изучения во что же IronPython превращает исходный питоновский файл я не придумал ничего лучшего, как скормить ему простейшую (специально не оптимизированную, для более полного изучения) программу вычисления первых десяти чисел Фибоначчи:
"""This proggy calculates first 10 Fibonacci numbers"""
def fib(n):
"""This function does the main work to calculate Fibonacci numbers"""
if n == 0 or n == 1 or n == 2:
return 1
fib1 = 1
fib2 = 1
i = 2
while i < n:
fib_sum = fib2 + fib1
fib1 = fib2
fib2 = fib_sum
i += 1
return fib_sum
for i in xrange(10):
print("n is {0} for step {1}".format(fib(i), i))
BUGFIXED 19 мая 2015
Конечно же вместо
if n == 0 or n == 1 or n == 2:
return 1
должно быть
if n == 0:
return 0
elif n == 1 or n == 2:
return 1
но исправлять уже не буду, слишком много за этим потянется.
IronPython создал два файла: .exe с маленьким запускным стабом и .dll с основной программой. .exe был неинтересен т.к. кроме загрузки .dll с основной программой и передачи ей управления ничего не делал, поэтому всё дальнейшее относится к .dll
Небольшое лирическое отступление про разные версии IronPython
Начинал изучение я с последней на сей момент версии IronPython 2.7.5. Декомпилировал созданный им fibonacci.dll в C# при помощи ILSpy и для начала решил сравнить полученный код с кодом той программы, которая меня интересовала изначально. Естественно не «байт в байт», функциональность ведь разная, а просто на предмет возможных одинаковых инициализаций, общих вызовов, etc. И сразу же обнаружил различия, небольшие, но всё-таки явные различия. Решил попробовать другие версии IronPython, более старые. Выкачивал очередную (вернее предыдущую) версию IronPython, устанавливал, скармливал ему свой fibonacci.py, загружал полученный fibonacci.dll в ILSpy, визуально сравнивал, опять видел отличия и лез выкачивать следующую версию. Наконец методом таких вот проб и ошибок нашёл что интересующая меня программа была скорее всего скомпилирована при помощи IronPython 2.6, различия между общим C# кодом интересующей программы и полученным fibonacci.dll были минимальны. На IronPython 2.6 и остановился.
Для всей программы IronPython создаёт класс
public class DLRCachedCode
в котором имеется лишь один метод типа
public static
содержащий __main__. Все остальные методы класса DLRCachedCode имеют тип
private static
В них содержатся функции, классы, лямбды, генераторы и пр. исходной питоновской программы.
Перед методом, содержащим __main__, через объявление атрибута CachedOptimizedCode описывается глобальное пространство имён программы:
[CachedOptimizedCode(new string[]
{
"__name__",
"__file__",
"__doc__",
"__path__",
"__builtins__",
"__package__",
"fib",
"i",
"xrange"
})]
В самом начале метода __main__ строится массив всех возможных локальных для данной программы сущностей: всех функций, всех классов, всех compiler generated лямбд и вспомогательных внутренних функций IronPython, через которые runtime библиотека IronPython осуществляет работу с питоновскими списками, словарями, слайсами. И даже все математические/логические операции и операции сравнения тоже производятся посредством вызова внутренних вспомогательных функций IronPython runtime:
public static object __main__$1(CodeContext $globalContext, FunctionCode functionCode)
{
object[] expr_06 = new object[1];
StrongBox<object[]> strongBox = expr_06[0] = new StrongBox<object[]>();
object[] array = expr_06;
object[] value = new object[]
{
PythonOps.MakeFunctionCode($globalContext, "fib", "This function does the main work to calculate Fibonacci numbers", new string[]
{
"n"
}, 0, new SourceSpan(new SourceLocation(59, 3, 1), new SourceLocation(373, 17, 19)), "fibonacci.py", new Func<PythonFunction, object, object>(new Closure(null, array).fib$2), null, null, null, new string[]
{
"n",
"fib1",
"fib2",
"i",
"fib_sum"
}, 5),
CallSite<Func<CallSite, object, KeyValuePair<IEnumerator, IDisposable>>>.Create(PythonOps.MakeOperationAction($globalContext, 18)),
CallSite<Func<CallSite, CodeContext, object, int, object>>.Create(PythonOps.MakeInvokeAction($globalContext, new CallSignature(1))),
CallSite<Func<CallSite, CodeContext, object, object, object, object>>.Create(PythonOps.MakeInvokeAction($globalContext, new CallSignature(2))),
CallSite<Func<CallSite, string, CodeContext, object>>.Create(PythonOps.MakeGetAction($globalContext, "format", false)),
CallSite<Func<CallSite, CodeContext, object, object, object>>.Create(PythonOps.MakeInvokeAction($globalContext, new CallSignature(1))),
CallSite<Func<CallSite, object, bool>>.Create(PythonOps.MakeConversionAction($globalContext, typeof(bool), 1)),
CallSite<Func<CallSite, object, bool>>.Create(PythonOps.MakeConversionAction($globalContext, typeof(bool), 1)),
CallSite<Func<CallSite, object, bool>>.Create(PythonOps.MakeConversionAction($globalContext, typeof(bool), 1)),
CallSite<Func<CallSite, object, int, object>>.Create(PythonOps.MakeBinaryOperationAction($globalContext, 13)),
CallSite<Func<CallSite, object, int, object>>.Create(PythonOps.MakeBinaryOperationAction($globalContext, 13)),
CallSite<Func<CallSite, object, int, object>>.Create(PythonOps.MakeBinaryOperationAction($globalContext, 13)),
CallSite<Func<CallSite, object, object, bool>>.Create(PythonOps.MakeComboAction($globalContext, PythonOps.MakeBinaryOperationAction($globalContext, 20), PythonOps.MakeConversionAction($globalContext, typeof(bool), 1))),
CallSite<Func<CallSite, object, object, object>>.Create(PythonOps.MakeBinaryOperationAction($globalContext, 0)),
CallSite<Func<CallSite, object, int, object>>.Create(PythonOps.MakeBinaryOperationAction($globalContext, 63))
};
Видно что функции из исходного питоновского текста строятся посредством вызова MakeFunctionCode из пространства имён PythonOps IronPython (да, забыл отметить что IronPython это компилятор с открытым исходным текстом, написан на C#, и исходники любой его версии можно свободно скачать из интернета по этой ссылке), имеющего следующую сигнатуру:
public static FunctionCode MakeFunctionCode(CodeContext context, string name, string documentation, string[] argNames, FunctionAttributes flags, SourceSpan span, string path, Delegate code, string[] freeVars, string[] names, string[] cellVars, string[] varNames, int localCount)
То есть мы видим что наша
def fib(n):
"""This function does the main work to calculate Fibonacci numbers"""
....
тут объявлена как
PythonOps.MakeFunctionCode($globalContext, "fib", "This function does the main work to calculate Fibonacci numbers", new string[]
{
"n"
}, 0, new SourceSpan(new SourceLocation(59, 3, 1), new SourceLocation(373, 17, 19)), "fibonacci.py", new Func<PythonFunction, object, object>(new Closure(null, array).fib$2), null, null, null, new string[]
{
"n",
"fib1",
"fib2",
"i",
"fib_sum"
}, 5),
имеет реальное имя «fib», строчку документации «This function does the main work to calculate Fibonacci numbers»,, получает аргумент с именем «n», начинается в исходном файле «fibonacci.py» в строке 3 на столбце 1, завершается в строке 17 на столбце 19, в сгенерённом IronPython коде она будет именоваться как «fib$2», у неё пустые freeVars, names и cellVars (я не буду вдаваться в подробности что это такое, читатели хаба Python наверняка знакомы с этими понятиями), использует локальные переменные (параметры, кстати, трактуются как локальные переменные) «n», «fib1», «fib2», «i» и «fib_sum» и всего локальных переменных 5 штук.
Все возможные вызовы внутренних вспомогательных функций IronPython runtime заносятся в этот массив локальных переменных как шаблонные вызовы CallSite (опять не буду вдаваться в подробности, эта статья озаглавлена как «Поверхностный реверс инжиниринг IronPython», а не «Доскональный разбор»).
После этого производится модификация некоторых глобальных переменных чтобы они содержали актуальную информацию:
globalArrayFromContext[1].set_CurrentValue((object)"fibonacci.py");
globalArrayFromContext[0].set_CurrentValue((object)"__main__");
(как видно меняются переменные "__file__" и "__name__", установленнные ранее через атрибут CachedOptimizedCode)
и начинается выполнение самой программы:
globalArrayFromContext[2].set_CurrentValue((object)"This proggy calculates first 10 Fibonacci numbers");
int num = 1;
globalArrayFromContext[6].set_CurrentValue(PythonOps.MakeFunction($globalContext, (FunctionCode)strongBox.Value[0], globalArrayFromContext[0].get_RawValue(), null));
num = 19;
CallSite<Func<CallSite, object, KeyValuePair<IEnumerator, IDisposable>>> callSite;
CallSite<Func<CallSite, CodeContext, object, int, object>> callSite2;
KeyValuePair<IEnumerator, IDisposable> keyValuePair = (callSite = (CallSite<Func<CallSite, object, KeyValuePair<IEnumerator, IDisposable>>>)strongBox.Value[1]).Target.Invoke(callSite, (callSite2 = (CallSite<Func<CallSite, CodeContext, object, int, object>>)strongBox.Value[2]).Target.Invoke(callSite2, $globalContext, globalArrayFromContext[8].get_CurrentValue(), 10));
try
{
while (true)
{
bool flag = keyValuePair.Key.MoveNext();
if (!flag)
{
break;
}
globalArrayFromContext[7].set_CurrentValue(keyValuePair.Key.Current);
num = 20;
CallSite<Func<CallSite, CodeContext, object, object, object, object>> callSite3;
CallSite<Func<CallSite, string, CodeContext, object>> callSite4;
CallSite<Func<CallSite, CodeContext, object, object, object>> callSite5;
PythonOps.Print($globalContext, (callSite3 = (CallSite<Func<CallSite, CodeContext, object, object, object, object>>)strongBox.Value[3]).Target.Invoke(callSite3, $globalContext, (callSite4 = (CallSite<Func<CallSite, string, CodeContext, object>>)strongBox.Value[4]).Target.Invoke(callSite4, "n is {0} for step {1}", $globalContext), (callSite5 = (CallSite<Func<CallSite, CodeContext, object, object, object>>)strongBox.Value[5]).Target.Invoke(callSite5, $globalContext, globalArrayFromContext[6].get_CurrentValue(), globalArrayFromContext[7].get_CurrentValue()), globalArrayFromContext[7].get_CurrentValue()));
num = 19;
}
}
finally
{
PythonOps.ForLoopDispose(keyValuePair);
}
Весь «бред» выше это то, во что IronPython превратил далеко не всю программу, а лишь
for i in xrange(10):
print("n is {0} for step {1}".format(fib(i), i))
Чтобы сделать это более-менее понятным был написан скрипт (его не привожу, он тривиальнейший), заменяющий имена переменных вида globalArrayFromContext[7], strongBox.Value[3].Target.Invoke и подобных на их «очеловеченные» имена из ранее построенных IronPython массивов глобальных и локальных переменных. Получается уже не компилируемый, то есть csc.exe его «не возьмёт», но гораздо более читабельный человеком код:
try
{
while (true)
{
bool flag = keyValuePair.Key.MoveNext();
if (!flag)
{
break;
}
G[i].set_CurrentValue(keyValuePair.Key.Current);
num = 20;
CallSite<Func<CallSite, CodeContext, object, object, object, object>> callSite3;
CallSite<Func<CallSite, string, CodeContext, object>> callSite4;
CallSite<Func<CallSite, CodeContext, object, object, object>> callSite5;
PythonOps.Print($globalContext, (callSite3 = (CallSite<Func<CallSite, CodeContext, object, object, object, object>>)L[PythonOps.MakeInvokeAction($globalContext, new CallSignature(2))])(callSite3, $globalContext, (callSite4 = (CallSite<Func<CallSite, string, CodeContext, object>>)L[PythonOps.MakeGetAction($globalContext, "format", false)])(callSite4, "n is {0} for step {1}", $globalContext), (callSite5 = (CallSite<Func<CallSite, CodeContext, object, object, object>>)L[PythonOps.MakeInvokeAction($globalContext, new CallSignature(1))])(callSite5, $globalContext, G[fib].get_CurrentValue(), G[i].get_CurrentValue()), G[i].get_CurrentValue()));
num = 19;
}
}
finally
{
PythonOps.ForLoopDispose(keyValuePair);
}
IMHO видеть L[PythonOps.MakeGetAction($globalContext, «format», false)]), G[i].get_CurrentValue() и подобные вместо strongBox.Value[4]).Target.Invoke и globalArrayFromContext[7].get_CurrentValue() понятнее и приятнее.
Т.к. повторюсь, процесс я не автоматизировал (работа была штучная и изготавливать конвейерно-поточные специнструменты не было необходимости), то дальше производится разбор руками, полный hand made. Берём, к примеру, функцию fib:
private static object fib$2(Closure closure, PythonFunction $function, object n)
{
object[] locals = closure.Locals;
StrongBox<object[]> strongBox = (StrongBox<object[]>)locals[0];
CodeContext globalContext = PythonOps.GetGlobalContext(PythonOps.GetParentContextFromFunction($function));
PythonGlobal[] globalArrayFromContext = PythonOps.GetGlobalArrayFromContext(globalContext);
object result;
try
{
int num = 5;
CallSite<Func<CallSite, object, bool>> callSite;
CallSite<Func<CallSite, object, bool>> callSite2;
CallSite<Func<CallSite, object, bool>> callSite3;
CallSite<Func<CallSite, object, int, object>> callSite4;
object obj2;
CallSite<Func<CallSite, object, int, object>> callSite5;
object obj;
CallSite<Func<CallSite, object, int, object>> callSite6;
if ((callSite = (CallSite<Func<CallSite, object, bool>>)L[PythonOps.MakeConversionAction($globalContext, typeof(bool), 1)])(callSite, (!(callSite2 = (CallSite<Func<CallSite, object, bool>>)L[PythonOps.MakeConversionAction($globalContext, typeof(bool), 1)])(callSite2, obj = ((!(callSite3 = (CallSite<Func<CallSite, object, bool>>)L[PythonOps.MakeConversionAction($globalContext, typeof(bool), 1)])(callSite3, obj2 = (callSite4 = (CallSite<Func<CallSite, object, int, object>>)L[PythonOps.MakeBinaryOperationAction($globalContext, /* Equal */ 13)])(callSite4, n, 0))) ? (callSite5 = (CallSite<Func<CallSite, object, int, object>>)L[PythonOps.MakeBinaryOperationAction($globalContext, /* Equal */ 13)])(callSite5, n, 1) : obj2))) ? (callSite6 = (CallSite<Func<CallSite, object, int, object>>)L[PythonOps.MakeBinaryOperationAction($globalContext, /* Equal */ 13)])(callSite6, n, 2) : obj))
{
result = ScriptingRuntimeHelpers.Int32ToObject(1);
}
else
{
num = 8;
object obj3 = ScriptingRuntimeHelpers.Int32ToObject(1);
num = 9;
object obj4 = ScriptingRuntimeHelpers.Int32ToObject(1);
num = 11;
object obj5 = ScriptingRuntimeHelpers.Int32ToObject(2);
num = 12;
object obj6;
while (true)
{
CallSite<Func<CallSite, object, object, bool>> callSite7;
bool flag = (callSite7 = (CallSite<Func<CallSite, object, object, bool>>)L[PythonOps.MakeComboAction($globalContext, PythonOps.MakeBinaryOperationAction($globalContext, /* LessThan */ 20), PythonOps.MakeConversionAction($globalContext, typeof(bool), 1))])(callSite7, obj5, n);
if (!flag)
{
break;
}
num = 13;
CallSite<Func<CallSite, object, object, object>> callSite8;
obj6 = (callSite8 = (CallSite<Func<CallSite, object, object, object>>)L[PythonOps.MakeBinaryOperationAction($globalContext, /* Add */ 0)])(callSite8, obj4, obj3);
num = 14;
obj3 = obj4;
num = 15;
obj4 = obj6;
num = 16;
CallSite<Func<CallSite, object, int, object>> callSite9;
obj5 = (callSite9 = (CallSite<Func<CallSite, object, int, object>>)L[PythonOps.MakeBinaryOperationAction($globalContext, /* AddAssign */ 63)])(callSite9, obj5, 1);
}
result = obj6;
}
}
catch (Exception)
{
int num;
PythonOps.UpdateStackTrace(globalContext, (FunctionCode)L[fib$2], MethodBase.GetCurrentMethod(), "fib", "fibonacci.py", num);
throw;
}
return result;
}
и начинаем. В самом начале создаются три объекта: result (имя говорит само за себя, там будет то, что функция возвращает), obj2 и obj.
Ещё одно лирическое отступление про эти objНОМЕР. В спойлер прятать не буду, это важное отступление
Если просто создаётся objНОМЕР как выше:
object obj2;
object obj;
то это как правило вспомогательные объекты, используемые как временные хранилища для временных результатов if, enumerable и т.д. Если же объект инициализируется во время создания:
object obj3 = ScriptingRuntimeHelpers.Int32ToObject(1);
object obj4 = ScriptingRuntimeHelpers.Int32ToObject(1);
object obj5 = ScriptingRuntimeHelpers.Int32ToObject(2);
то это локальная переменная. Имя локальной переменной узнать довольно просто (есть нюансы, но они довольно редки и я не буду на них заострять внимание). Мы уже видели ранее что наша функция «fib» была объявлена как:
PythonOps.MakeFunctionCode($globalContext, "fib", "This function does the main work to calculate Fibonacci numbers", new string[]
{
"n"
}, 0, new SourceSpan(new SourceLocation(59, 3, 1), new SourceLocation(373, 17, 19)), "fibonacci.py", new Func<PythonFunction, object, object>(new Closure(null, array).fib$2), null, null, null, new string[]
{
"n",
"fib1",
"fib2",
"i",
"fib_sum"
}, 5),
Отбрасываем аргумент «n» и получаем что первый инициализируемый при создании объект, в данном случае obj3, имеет в оригинальном коде имя «fib1». Второй инициализируемый при создании объект, obj4, называется «fib2». Третий, obj5, соответственно «в девичестве» имел имя «i».
После создания объектов идёт какой-то ужас:
if ((callSite = (CallSite<Func<CallSite, object, bool>>)L[PythonOps.MakeConversionAction($globalContext, typeof(bool), 1)])(callSite, (!(callSite2 = (CallSite<Func<CallSite, object, bool>>)L[PythonOps.MakeConversionAction($globalContext, typeof(bool), 1)])(callSite2, obj = ((!(callSite3 = (CallSite<Func<CallSite, object, bool>>)L[PythonOps.MakeConversionAction($globalContext, typeof(bool), 1)])(callSite3, obj2 = (callSite4 = (CallSite<Func<CallSite, object, int, object>>)L[PythonOps.MakeBinaryOperationAction($globalContext, /* Equal */ 13)])(callSite4, n, 0))) ? (callSite5 = (CallSite<Func<CallSite, object, int, object>>)L[PythonOps.MakeBinaryOperationAction($globalContext, /* Equal */ 13)])(callSite5, n, 1) : obj2))) ? (callSite6 = (CallSite<Func<CallSite, object, int, object>>)L[PythonOps.MakeBinaryOperationAction($globalContext, /* Equal */ 13)])(callSite6, n, 2) : obj))
{
result = ScriptingRuntimeHelpers.Int32ToObject(1);
}
Убираем в текстовом редакторе «шум», форматируем «лесенкой» для лучшей читаемости и получаем:
if ((callSite = L[PythonOps.MakeConversionAction($globalContext, typeof(bool), 1)])(callSite, (!(callSite2 =
L[PythonOps.MakeConversionAction($globalContext, typeof(bool), 1)])(callSite2, obj = ((!(callSite3 =
L[PythonOps.MakeConversionAction($globalContext, typeof(bool), 1)])(callSite3, obj2 = (callSite4 =
L[PythonOps.MakeBinaryOperationAction($globalContext, /* Equal */ 13)])(callSite4, n, 0)))
? (callSite5 = L[PythonOps.MakeBinaryOperationAction($globalContext, /* Equal */ 13)])(callSite5, n, 1)
: obj2)))
? (callSite6 = L[PythonOps.MakeBinaryOperationAction($globalContext, /* Equal */ 13)])(callSite6, n, 2)
: obj))
{
result = ScriptingRuntimeHelpers.Int32ToObject(1);
}
Если кто не видит в этом коде явного
if ((n == 0) || (n == 1) || (n == 2))
{
result = ScriptingRuntimeHelpers.Int32ToObject(1);
}
тому осенью на переэкзаменовку ;-) Или менять профессию.
То есть наш
if n == 0 or n == 1 or n == 2:
return 1
мы благополучно нашли. Идём дальше.
num = 8;
object obj3 = ScriptingRuntimeHelpers.Int32ToObject(1);
num = 9;
object obj4 = ScriptingRuntimeHelpers.Int32ToObject(1);
num = 11;
object obj5 = ScriptingRuntimeHelpers.Int32ToObject(2);
Это само собой
fib1 = 1
fib2 = 1
i = 2
(переменные с именем «num» содержат номер строки исходного файла, IronPython так всегда делает, на этом заострять внимание не надо)
Дальше
num = 12;
object obj6;
Вот obj6 в данном случае не временная, хотя не инициализированная (я выше говорил про «есть нюансы», вот это один из них) переменная, на самом деле это переменная «fib_sum» из исходного текста. Т.к. присвоение ей (и начальная инициализация) идёт внутри цикла, а значение после используется вне цикла, поэтому IronPython был вынужден объявить её тут.
Дальше:
while (true)
{
CallSite<Func<CallSite, object, object, bool>> callSite7;
bool flag = (callSite7 = (CallSite<Func<CallSite, object, object, bool>>)L[PythonOps.MakeComboAction($globalContext, PythonOps.MakeBinaryOperationAction($globalContext, /* LessThan */ 20), PythonOps.MakeConversionAction($globalContext, typeof(bool), 1))])(callSite7, obj5, n);
if (!flag)
{
break;
}
num = 13;
CallSite<Func<CallSite, object, object, object>> callSite8;
obj6 = (callSite8 = (CallSite<Func<CallSite, object, object, object>>)L[PythonOps.MakeBinaryOperationAction($globalContext, /* Add */ 0)])(callSite8, obj4, obj3);
num = 14;
obj3 = obj4;
num = 15;
obj4 = obj6;
num = 16;
CallSite<Func<CallSite, object, int, object>> callSite9;
obj5 = (callSite9 = (CallSite<Func<CallSite, object, int, object>>)L[PythonOps.MakeBinaryOperationAction($globalContext, /* AddAssign */ 63)])(callSite9, obj5, 1);
}
Мысленно «переписываем» это как
while (true)
{
bool flag = (obj5 < n);
if (!flag)
{
break;
}
obj6 = obj4 + obj3;
obj3 = obj4;
obj4 = obj6;
obj5 = obj5 + 1;
}
(это просто на самом деле, лично я терялся только первые минут 40-50 [интересующая программа была большая и циклы/блоки там были гораздо побольше, я её дней пять так вот сидел переводил], после уже тьфу, раз плюнуть получалось)
и с учётом того, что (см. выше) мы уже знаем кто такие на самом деле (в исходном файле то есть) ob3, obj4, obj5 и obj6 пишем:
while (true)
{
bool flag = (i < n);
if (!flag)
{
break;
}
fib_sum = fib2 + fib1;
fib1 = fib2;
fib2 = fib_sum;
i = i + 1;
}
Ого! Похоже мы получили вполне себе
while i < n:
fib_sum = fib2 + fib1
fib1 = fib2
fib2 = fib_sum
i += 1
И последний штрих:
result = obj6;
}
}
catch (Exception)
{
int num;
PythonOps.UpdateStackTrace(globalContext, (FunctionCode)L[fib$2], MethodBase.GetCurrentMethod(), "fib", "fibonacci.py", num);
throw;
}
return result;
}
То есть переменной «result» присваивается «fib_sum» и это дело возвращается. We did it! We HAVE DONE it!!!
На try в начале методов и catch в конце методов внимания обращать тоже не надо, это чисто IronPytonовские штуки и к нам отношения не имеют. Хотя питоновские исключения IronPython обрабатывает почти так же, но в данном случае pure Python exceptions нет, это ловятся исключения самого IronPython.
Ну вот как-то так.
Egor3f
Даже интересно стало сравнить производительность полученной программы: чистый python vs IronPython. Сам .net быстр, но тут тааакой салат на выходе получается, думаю, скорость падает, а потребление памяти растёт изрядно.
pfemidi Автор
Так ведь этот «салат на выходе» JIT от .Net предварительно в нативный код преобразует. Так что не думаю что скорость сильно упадёт.
pfemidi Автор
Вот.
obiwanus
Сорри, honestly собирался прочесть, но блин, я не able переварить такую кашу из русского и инглиш.
pfemidi Автор
Сорри, но я не миксовал рашн и английские вордсы. Фор икземпл, плиз, где конкретно это хэппенс?
obiwanus
Блин, ответил ниже случайно.
obiwanus
существует Over 9000 различных декомпайлеров байткода
делает из него практически native executable
процесс я не автоматизировал, всё так сказать hand made
и это дело возвращается. We did it! We HAVE DONE it!!!
Евери факин вере!