GCC использует нечто, называемое батутом.
Информация: http://gcc.gnu.org/onlinedocs/gccint/Trampolines.html
Трамплин — это фрагмент кода, который GCC создает в стеке для использования, когда вам нужен указатель на вложенную функцию. В вашем коде батут необходим, потому что вы передаете g
в качестве параметра вызову функции. Батут инициализирует некоторые регистры, чтобы вложенная функция могла ссылаться на переменные во внешней функции, а затем переходит к самой вложенной функции. Батуты очень маленькие — вы «отскакиваете» от батута в тело вложенной функции.
Для такого использования вложенных функций требуется исполняемый стек, что в наши дни не рекомендуется. На самом деле нет никакого способа обойти это.
Разбор батута:
Вот пример вложенной функции в расширенном C GCC:
void func(int (*param)(int));
void outer(int x)
{
int nested(int y)
{
// If x is not used somewhere in here,
// then the function will be "lifted" into
// a normal, non-nested function.
return x + y;
}
func(nested);
}
Это очень просто, поэтому мы можем увидеть, как это работает. Вот результирующая сборка outer
, за вычетом кое-чего:
subq $40, %rsp
movl $nested.1594, %edx
movl %edi, (%rsp)
leaq 4(%rsp), %rdi
movw $-17599, 4(%rsp)
movq %rsp, 8(%rdi)
movl %edx, 2(%rdi)
movw $-17847, 6(%rdi)
movw $-183, 16(%rdi)
movb $-29, 18(%rdi)
call func
addq $40, %rsp
ret
Вы заметите, что большая часть того, что он делает, это запись регистров и констант в стек. Мы можем проследить и обнаружить, что в SP+4 он помещает 19-байтовый объект со следующими данными (в синтаксисе GAS):
.word -17599
.int $nested.1594
.word -17847
.quad %rsp
.word -183
.byte -29
Это достаточно легко запустить через дизассемблер. Предположим, что $nested.1594
равно 0x01234567
, а %rsp
равно 0x0123456789abcdef
. Итоговая разборка, предоставленная objdump
, такова:
0: 41 bb 67 45 23 01 mov $0x1234567,%r11d
6: 49 ba ef cd ab 89 67 mov $0x123456789abcdef,%r10
d: 45 23 01
10: 49 ff e3 rex.WB jmpq *%r11
Таким образом, батут загружает указатель стека внешней функции в %r10
и переходит к телу вложенной функции. Тело вложенной функции выглядит так:
movl (%r10), %eax
addl %edi, %eax
ret
Как видите, вложенная функция использует %r10
для доступа к переменным внешней функции.
Конечно, довольно глупо, что батут больше, чем сама вложенная функция. Вы могли бы легко сделать лучше. Но не очень многие люди используют эту функцию, и таким образом батут может оставаться одного размера (19 байт) независимо от того, насколько велика вложенная функция.
Заключительное примечание: в нижней части сборки есть последняя директива:
.section .note.GNU-stack,"x",@progbits
Это указывает компоновщику пометить стек как исполняемый.
person
Dietrich Epp
schedule
18.11.2011