Как найти указатель стека C, связанный с выполнением фрейма стека CPython - PullRequest
0 голосов
/ 04 декабря 2018

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

См.также примечание ниже об одном возможном подходе, использующем PyEval_SetTrace, хотя я надеялся, что они могли бы быть лучшим способом.В качестве другого примера, существует PyMain_GetArgcArgv, который бы сработал, но только , если интерпретатор Python был запущен из исполняемого файла python, а не встроен (что может быть приемлемым ограничением).Также PyMain_GetArgcArgv не задокументировано как часть API.


Я хотел бы иметь возможность найти адрес фрейма стека C (т. Е. __builtin_frame_address(0), как определено соответствующим образом для этой платформы)это наиболее тесно связано с фреймом стека Python.В частности, я хотел бы найти самый внешний фрейм - или близкий к нему - связанный с вызовом функции Python, который будет определен лучше ниже.

Вкратце, контекст заключается в том, что я 'm оборачивает библиотеку C, которая использует неясный сборщик мусора специального назначения, которому нужен указатель на дно стека - по крайней мере, так далеко назад, поскольку существуют локальные переменные, указывающие на объекты, которые должны отслеживаться GC.В идеале я мог бы разметить дно стека один раз;в этом случае, поскольку он упакован в модуль Python, достаточно перейти к самому внешнему фрейму стека Python.Наилучшей доступной альтернативой было бы вручную отмечать дно стека при каждом входе в библиотеку, но это не идеально, а также требовало бы исправления библиотеки (что может понадобиться в любом случае), поскольку в настоящее время это позволяет только устанавливать стекнижний адрес один раз, во время функции инициализации.

То, как именно фрейм стека Python связан с фреймом стека C, плохо определено, поскольку технически нет жесткой и быстрой связи между двумя,Тем не менее, для практической цели он будет на уровне или близко (в зависимости от оптимизации компилятора и т. Д.) К вызову PyEval_EvalFrameEx для выполняемого кадра (меня не интересуют кадры, которыев настоящее время не в стеке вызовов, поскольку в данном случае это, очевидно, бессмысленный вопрос.)

Это все явно зависит от CPython, и это нормально для моих целей.В этом случае технически нет причин, по которым реализация структуры CPython PyFrameObject не могла бы переносить такую ​​информацию на одного из своих членов, но, насколько я могу судить, на PyFrameObject s не хранится ничего, что позволило бы мнесвязать его с фреймом стека C.Например, моя задача была бы «решена» достаточно хорошо, для целей данного приложения, если бы было в PyFrameObject как f_cstack что-то вроде:

PyObject* _Py_HOT_FUNCTION
_PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag)
{
    ...
    f->f_executing = 1;
    f->f_cstack = &f;
    ...
}

Это сработало быAFAICT - даже несмотря на то, что f обычно передается в регистр, мой gcc будет обрабатывать такой код, помещая f в стек и сохраняя его адрес в стеке.К сожалению, в настоящее время я не могу найти ничего подобного.

Лучшая идея, которую я смог придумать, - это зарегистрировать обработчик PyEval_SetTrace, который будет вызыватьсявходя во фреймы стека Python и, таким образом, я получаю возможность рутяться вокруг стека оттуда.Но на самом деле для рассматриваемого приложения мне нужно только найти «самый внешний» вызов PyEval_EvalFrameEx, который будет один для любого работающего кода Python.Так что установка обратного вызова трассировки не обязательно даст мне это, и это дополнительные издержки, которые мне не нужны для каждого вызова функции.

Боюсь, в настоящее время нет хорошего решения для этого, хотя было бы удобно, если бы оно было.

(PS Меня также волнует только основной стек, а не потоки, хотя какое-либо решениеэто будет работать в основном потоке, вероятно, будет иметь аналогичное решение для вспомогательных потоков).

1 Ответ

0 голосов
/ 04 декабря 2018

В целом и в принципе вы, вероятно, не всегда можете делать то, что хотите (хорошо известно, что в некоторых случаях реализациям C может даже не понадобиться стек вызовов).Поскольку иногда компиляторы, такие как GCC (или Clang ), способны tail-call компилятор оптимизаций (что в сочетании с оптимизацией во время соединения), может дать удивительные результаты).Некоторые соглашения о вызовах или режимы компиляции (например, gcc -fomit-frame-pointer -m32 в 32 бит x86) затрудняют прохождение стека вызовов (по крайней мере, без дополнительные данные).

На практике вам следует исследовать с помощью функции GNU backtrace и еще лучше libbacktrace * Ian Taylor .Эта libbacktrace библиотека анализирует DWARF отладочную информацию (поэтому она может быть специфичной для Linux и, возможно, не будет работать в Windows).В Linux dladdr (3) может получить имя символа, близкое к указанному адресу.

Так что вам лучше скомпилировать как основную программу, так и среду выполнения Python (и, возможно, дополнительныебиблиотеки) с флагом -g, переданным gcc или g++ (чтобы получить отладочную информацию DWARF), затем используйте libbacktrace.Помните, что GCC может одновременно обрабатывать и -g, а также флаги оптимизации, такие как -O2.Производительность двоичного файла или библиотеки не страдает (поскольку оптимизация выполняется компилятором GCC).

Для поиска утечек памяти (что было косвенно упомянуто в некоторых комментариях, но не всам вопрос), некоторые инструменты доступны (например, valgrind ).Спросить, подходят ли они для смешанной программы на Python + C. Это другой вопрос.

Ошибки сборки мусора больно охотиться (и я сам написал несколько сборок мусора - особенно в моем устаревшем GCC MELT и в моем бисмоне - так я говорю на собственном опыте; прочитайте также руководство GC ).Смешивание GC с другим (механизм пересчета Python - это механизм GC) является болезненным и ломким. на практике может быть более разумным разделить ваше программное обеспечение на несколько процессов, используя средства межпроцессного взаимодействия (и это зависит от операционной системы).

С CPython - это свободное программное обеспечение , вы можете fork добавить в него поддержку libbacktrace внутри (и делать это должно быть достаточно просто, технически говоря).

...