HeadlinesBriefing favicon HeadlinesBriefing.com

Оптимизация стека трамплина вложенных функций GCC

Hacker News •
×

Оптимизация стека трамплина для вложенных функций GCC. Косвенный вызов вложенных функций в GCC без исполняемого стека. Мартин Уеккер, 2026-08-29. Мы обсуждали в прошлый раз, как можно использовать вложенные функции в GCC и Clang для обратных вызовов без необходимости исполняемого стека. Но что, если нужно поддерживать старые версии GCC? Конечно, можно просто принять исполняемый стек, но также можно избежать этого с помощью хака. GCC: Вложенные функции и трамплины. Давайте сначала обсудим, как GCC поддерживает взятие адреса вложенной функции. Наш игрушечный пример без использования новых макросов показан ниже. На x86_64 сгенерированный ассемблер помещает трамплин на стек и немедленно вызывает его через встроенную функцию baz. Трамплин — это короткая последовательность кода, которая загружает статический фреймовый регистр в структуру на стеке, содержащую захваченные переменные из родительской функции, а затем переходит к локальной функции. Если перевести константы -17591, -17847 и -1864106167 обратно в инструкции ассемблера, то получится следующий код x86_64: movq $bar.0, %r11, movq $frame, %r10, jump *%r11. И статическая цепочка, и адрес кода являются немедленными константами, используемыми инструкциями перемещения в коде трамплина. Вместо использования адреса трамплина для вызова функции, мы можем извлечь адрес кода и статическую цепочку из трамплина и использовать их с встроенной функцией __builtin_call_with_static_chain для прямого вызова локальной функции. Таким образом, мы можем использовать чтение этих двух значений указателей из трамплина как механизм отката в старых версиях GCC. Недостатки в том, что трамплин все равно создается, компилятор все равно не может девиртуализировать косвенный вызов, и стек все равно будет помечен как исполняемый. Так что же gewonnenо? Поскольку мы никогда не вызываем трамплин фактически, мы можем сделать стек снова неисполняемым с помощью следующей команды, что хотя бы решает проблемы безопасности этой функции. patchelf --clear-execstack program. Эта идея реализована в моей экспериментальной библиотеке noplate, где широкий указатель строится из адреса кода и статической цепочки. Трамплины как дескрипторы функций. Есть еще одна идея, которую я считаю стоящей исследования: можно также использовать сам трамплин как дескриптор функции. Вместо извлечения статической цепочки и указателя кода там, где создается трамплин, мы просто передаем адрес трамплина как обычно. Но везде, где мы могли бы вызвать трамплин, мы сначала проверяем, указывает ли указатель на трамплин, а затем извлекаем адрес кода и статическую цепочку для вызова вложенной функции напрямую с помощью __builtin_call_with_static_chain. В каком-то смысле можно сказать, что вместо вызова трамплина мы интерпретируем код трамплина в месте вызова с помощью сверхпростого интерпретатора, который может интерпретировать только эту конкретную последовательность кода и который настолько прост, что...