Предположим, что у вас есть не только исполняемый файл, но и файлы исходного кода.
Моя задача — рассчитать правильный размер стека запуска процесса только для локальных переменных, обратного адреса, передачи аргумента. Я пытался использовать VMMap
разработанный MS. Потому что он может поймать выделенную память в системе с такими категориями, как стек. Тем не менее, он также содержит защитную страницу, файл (ы) подкачки и так далее. Таким образом, размер стека от VMMap
был переоценен.
Я хотел бы изменить способ решения проблемы. Я буду отслеживать стек, чтобы нарисовать фактическое дерево вызовов, используя StackWalker64
WinAPI и получить таблицу символов из исполняемого или исходного кода. Но есть проблема в том, что таблица символов из исполняемого файла, такого как ELF, не читается.
Теперь я планирую подать заявку doxygen
который является проектом с открытым исходным кодом с лексером компилятора. Так как doxygen
только предоставляет список функций с их типом возвращаемого значения и аргументом функции, я не знаю о локальных переменных. Итак, мне также нужен лексер, чтобы сделать полную таблицу символов в качестве предварительной обработки. Но это довольно сложно. Я не уверен, что это лучшее решение.
Есть ли лучший способ решить?
OP хочет статически вычислить глубину стека в худшем случае.
Это очень сложно сделать. Для этого ему необходимо:
«Таблица символов» должна быть точной для компилятора, чтобы процесс оценки знал, какие объявленные данные (концептуально) попадают в стек. OP понадобится то, что составляет полноценный интерфейс компилятора для его конкретного диалекта C ++. [ОП ошибочно полагает, что «лексер» даст ему таблицу символов. Я не буду].
Теперь рассмотрим построение глобального графа вызовов. Основы кажутся простыми; если функция «bar» содержит «foo (x)», то «bar» вызывает «foo». Но есть много сложностей: перегрузка, виртуальные функции, косвенные вызовы и приведение типов. Предполагается, что перегрузка разрешается с помощью разрешения имен и типов; это становится грязным перед лицом шаблонов (рассмотрите SFINAE). Виртуальные функции и косвенные вызовы вынуждают строить консервативный анализатор точек; достаточно уродливый бросок может заставить предположение вызова любой совместимая с аргументами функция. Анализаторы «точка-точка» имеют разную степень точности; низкая точность может привести к появлению графа вызовов со множеством фиктивных (консервативных) ребер, которые отбросят оценку размера.
Компилятор не предоставит этот глобальный граф вызовов, поскольку он работает только с единичными модулями компиляции.
Наконец, мы можем рассмотреть построение оценки размера стека. Здесь необходимо понять размеры и выравнивания, используемые для представления каждого объявленного типа данных, и то, как конкретный интересующий компилятор выделяет локальные переменные в стек. Обычно последовательные блоки кодирования {….} {…} перекрывают местоположения стека. Также необходимо понять, как оцениваются выражения и как передаются аргументы, так как они влияют на использование стека. Наконец, нужно понять, как компилятор распределяет регистры и какие оптимизации компилятор может / делал (?) Применять, поскольку такие оптимизации будут влиять на использование стека выражений, а также на то, сколько локальных переменных фактически выделено для стека. Это очень много нужно знать, и единственным заслуживающим доверия источником знаний является сам компилятор. Вместо того, чтобы пытаться реплицировать весь этот механизм, вероятно, лучше либо заставить компилятор обеспечить фактическое распределение размера стека для каждой функции (я верю, что GCC сделает это), либо отказаться от получения точного результата и консервативно оценить потребность в стеке с помощью предполагая, что каждый объявленный локальный объект использует пространство стека (в этом случае неясно, что нужно сделать для оценки использования стека выражений; можно предположить, что каждая переменная и промежуточный результат выражения занимают пространство стека в соответствии с его типом).
С оценками стекового пространства для каждой функции и графом вызовов простой анализ графа вызовов может вывести требования стека на цепочку вызовов из корня. Максимум из них — необходимая оценка стека. (Примечание: это предполагает, что каждая функция использует свое полное требование стека для каждого вызова; это явно консервативная оценка). Эта часть довольно проста.
В целом это сложный анализ. В идеале вы можете получить компилятор для предоставления оценок размера стека и основных фактов о графах вызовов. Построение анализа точек сложно и очень сложно, так как размер приложения становится большим.
Возможно, вы можете согнуть GCC, чтобы помочь предоставить данные уровня модуля компиляции. Предполагается, что Clang предназначен для предоставления тех же данных. Насколько мне известно, ни один из них не предлагает конкретной поддержки для анализа глобальных точек. Неясно, GCC и Clang обрабатывают диалекты Windows C ++; они могут.
Наш инструментарий реинжиниринга программного обеспечения DMS и его интерфейс C ++ предназначены для предоставления таблиц символов, результата разрешения имен (например, разрешения перегрузок) и могут легко извлекать факты локальных вызовов. Он обрабатывает как GCC, так и MS диалекты C ++. DMS также предоставляет поддержку для построения глобального анализа точек и глобальной диаграммы вызовов; хотя мы не использовали это специально для C ++, мы использовали его для обработки приложений на C примерно 16 миллионов строк.
Вся эта трудность объясняет, почему люди часто разбиваются и пытаются понять, насколько большой стек использует динамический анализ. Если ОП хочет статический анализатор, он должен быть готов приложить значительные усилия для его получения.
Других решений пока нет …