Вопрос по python – Как сделать встроенный модуль Python для удаленного выполнения песочницы?

3

Я пытаюсь динамически добавить код Python в модуль песочницы для выполнения на удаленной машине. У меня возникла проблема с тем, как бороться с импортированными методами. Например, часто можно увидеть написанные сценарии, такие как:

<code> from test_module import g
 import other_module

 def f():
     g()
     other_module.z()
</code>

Я знаю, что могу мариновать f с помощью g и, возможно, z, но как мне сохранить «другой_модуль»? область для Z? Если я помещу и f, и g в песочницу, то z не будет разрешен должным образом при вызове f. Можно ли использовать какой-либо тип встроенного модуля для правильного разрешения z, т.е. sandbox.other_module?

Моя цель загрузки удаленного кода в песочницу - не загрязнять глобальное пространство имен. Например, если другой удаленный метод вызывается с его собственным графом зависимостей, то он не должен мешать другому набору удаленного кода. Реально ли ожидать стабильности Python при входе и выходе из модулей песочницы? Я говорю это из-за этого поста: Как выгрузить (перезагрузить) модуль Python? что заставляет меня чувствовать, что в этом случае могут возникнуть проблемы с удалением таких модулей, как разные песочницы.

Следует за вопросомHow to pickle a python function with it's dependencies? hynekcer

Ваш Ответ

3   ответа
1
  • You probably do not want to serialize imported functions from Python library e.g. mathematical functions or big packages that are mixed Python + C, but your code serializes it. It can cause unnecessary problems that they have no func_code attribute etc.
  • You need not to serialize repeatedly functions that has been serialized previously. You can send full name and import them according this. This is the reason why you had it multiple times in the memory.
  • The original format <module_name> <serialized_func1> <serialized_func2>... is not general enough. A function can be on the local machine imported under different names by "... import ... as ..." clause. You can serialize list of tuples of mixed strings and code objects.

намек

def some_filter(module_name):
    mod_path = sys.modules[module_name].__file__
    # or if getattr(sys.modules[module_name], 'some_my_attr', None)
    return not mod_path.startswith('/usr/lib/python2.7/')

dumped_funcs = {}

def dump_module(...
    ...
    data = []
    for func_name, func_obj in functions_list:
        if some_filter(func_obj.__module__) and not func_obj in dumped_funcs and \
                    hasattr(func_obj, 'func_code'):
            data.append((func_name, func_obj.func_code))
            dumped_funcs[func_obj] = True  # maybe later will be saved package.mod.fname
        else:
            data.append((func_name, '%s.%s' % (func_obj.__module__, \
                                               func_obj.func_code.co_name)))
    marshal.dump(data, funcfile)

Теперь нет важной разницы между песочницей и другим сериализованным модулем. Условия & quot; если песочница & quot; может быть скоро удален.

Это сработало хорошо. Это было именно то, что мне нужно было сделать! Благодарю. Ryan R.
1

Текущий подход, который я использую для включения как & import x & apos; и "из x import y"; пакетирование зависимостей. Одним из недостатков этой текущей реализации является то, что она создает копии методов в каждом используемом модуле, в отличие от источника кода, где каждое использование является просто ссылкой на один и тот же метод в памяти (хотя у меня есть противоречивые результаты здесь - смотрите раздел после кода).

/// analysis_script.py /// (для краткости исключены зависимости)

import test_module
from third_level_module import z

def f():
    for i in range(1,5):
        test_module.g('blah string used by g')
        z()

/// driver.py ///

import modutil
import analysis_script

modutil.serialize_module_with_dependencies(analysis_script)

/// modutil.py ///

import sys
import modulefinder
import os
import inspect
import marshal

def dump_module(funcfile, name, module):
    functions_list = [o for o in inspect.getmembers(module) if inspect.isfunction(o[1])]
    print 'module name:' + name
    marshal.dump(name, funcfile)
    for func in functions_list:
       print func
       marshal.dump(func[1].func_code, funcfile)

def serialize_module_with_dependencies(module):

    python_path = os.environ['PYTHONPATH'].split(os.pathsep)
    module_path = os.path.dirname(module.__file__)

    #planning to search for modules only on this python path and under the current scripts working directory
    #standard libraries should be expected to be installed on the target platform
    search_dir = [python_path, module_path]

    mf = modulefinder.ModuleFinder(search_dir)

    #__file__ returns the pyc after first run
    #in this case we use replace to get the py file since we need that for our call to       mf.run_script
    src_file = module.__file__
    if '.pyc' in src_file:
        src_file = src_file.replace('.pyc', '.py')

    mf.run_script(src_file)

    funcfile = open("functions.pickle", "wb")

    dump_module(funcfile, 'sandbox', module)

    for name, mod in mf.modules.iteritems():
        #the sys module is included by default but has no file and we don't want it anyway, i.e. should
        #be on the remote systems path. __main__ we also don't want since it should be virtual empty and
        #just used to invoke this function.
        if not name == 'sys' and not name == '__main__':
            dump_module(funcfile, name, sys.modules[name])

    funcfile.close()

/// sandbox_reader.py ///

import marshal
import types
import imp

sandbox_module = imp.new_module('sandbox')

dynamic_modules = {}
current_module = ''
with open("functions.pickle", "rb") as funcfile:
    while True:
        try:
            code = marshal.load(funcfile)
        except EOFError:
             break

        if isinstance(code,types.StringType):
            print "module name:" + code
            if code == 'sandbox':
                current_module = "sandbox"
            else:
                current_module = imp.new_module(code)
                dynamic_modules[code] = current_module
                exec 'import '+code in sandbox_module.__dict__
        elif isinstance(code,types.CodeType):
            print "func"
            if current_module == "sandbox":
                func = types.FunctionType(code, sandbox_module.__dict__, code.co_name)
                setattr(sandbox_module, code.co_name, func)
            else:
                func = types.FunctionType(code, current_module.__dict__, code.co_name)
                setattr(current_module, code.co_name, func)
        else:
            raise Exception( "unknown type received")

#yaa! actually invoke the method
sandbox_module.f()
del sandbox_module

Например, график функции выглядит так до сериализации:

 module name:sandbox
 ('f', <function f at 0x15e07d0>)
 ('z', <function z at 0x7f47d719ade8>)
 module name:test_module
 ('g', <function g at 0x15e0758>)
 ('z', <function z at 0x7f47d719ade8>)
 module name:third_level_module
 ('z', <function z at 0x7f47d719ade8>)

В частности, глядя на функцию z, мы видим, что все ссылки указывают на один и тот же адрес, то есть 0x7f47d719ade8.

На удаленном процессе после реконструкции песочницы имеем:

 print sandbox_module.z 
 <function z at 0x1a071b8>
 print sandbox_module.third_level_module.z 
 <function z at 0x1a072a8>
 print sandbox_module.test_module.z 
 <function z at 0x1a072a8>

Это поражает меня! Я бы подумал, что все адреса здесь будут уникальными после реконструкции, но по какой-то причине sandbox_module.test_module.z и sandbox_module.third_level_module.z имеют один и тот же адрес?

Оба ваших ответа были очень полезны! Дал +1 на обоих и галочку для этой темы. Ryan R.
Если я импортирую имена из обычного модуля, я также должен обновлять ссылки на имена для перезагруженного объекта по отдельности. Какую разницу вы обнаружили между «нормальной» и «песочницей»? в использовании памяти? Btw. Как вы думаете, мой оригинальный ответ на травление был для вас полезным или вы не уверены? (Кнопка)
Я думаю, что это не создает больше копий, но больше ссылок на эту функцию. Однако исходные функции не удаляются из памяти, пока все ссылки не будут удалены или заменены. Вот почему я предпочел позвонить "some_mod.f" вместо вызова "f".
Добавлена информация о путанице по восстановленным ссылкам на функции. Ryan R.
Этот подход, размещенный здесь, сбрасывает весь импорт в модуль песочницы. Это не хорошо. Правильный способ сделать это состоит в том, чтобы восстановить импорт по модулю в соответствии с тем, как он существует в процессе отправки. Ryan R.
1

Другие модули могут быть импортированы вsandbox (вы имеете в виду модули, которые создаются динамически во время выполнения)

    sandbox.other_module = __import__('other_module')

или же:

    exec 'import other_module' in sandbox.__dict__

Если вы позвоните в «песочницу» модули из других модулей или других модулей песочницы, и если вы захотите перезагрузить новый код позже, проще импортировать только модуль, а не имена из него, например & quot; из импорта из песочницы f & quot; и вызывать & quot; sandbox.f & quot; не "е". Затем перезагружается легко. (но, естественно, команда перезагрузки для этого не нужна)


Классы

>>> class A(object): pass
... 
>>> a = A()
>>> A.f = lambda self, x: 2 * x  # or a pickled function
>>> a.f(1)
2
>>> A.f = lambda self, x: 3 * x
>>> a.f(1)
3

Кажется, что методы перезагрузки могут быть простыми. Я помню, что перезагрузка классов, определенных в модифицированном исходном коде, может быть сложной, потому что старый код класса может удерживаться некоторым экземпляром. Код экземпляра может / должен быть обновлен индивидуально в худшем случае:

    some_instance.__class__ = sandbox.SomeClass  # that means the same reloaded class

Я использовал последний с сервисом python, доступ к которому осуществлялся с помощью автоматизации win32com, и перезагрузка кода классов прошла успешно без потери данных экземпляров.

Похожие вопросы