Расширения GCC: __builtin_return_address && __label__

Столкнулся с использование двух расширений в ядре. Файл include/linux/kernel.h

#define _RET_IP_     (unsigned long)__builtin_return_address(0)
#define _THIS_IP_  ({ __label__ __here; __here: (unsigned long)&&__here; })

Судя по названию, __builtin_return_address указывает адрес возврата из функции. То есть тот адрес, куда управление перейдёт после return. Так оно на самом деле и оказалось. Адрес перехода из функции хранится на стеке и используется инструкцией ret при выходе из функции. Помещается он туда ассемблерной инструкцией call. Соответственно, GCC позволяет получить адрес возврата из функции не прибегая к ассемблеру. Аргумент же у __builtin_return_address указывает instruction pointer адрес из какой функции вернуть: 0 - означает из текущей функции, 1 - из той, из которой вызвана текущая и т.д. То есть соответствует уровню вложенности вызовов функций на стеке.

Метки же позволено хранить в переменных. Соответственно и goto прыгать не просто на метку, но на переменную. Чтобы обратиться к переменной-метке надо использовать &&.

Написал пример для лучшего понимая механизмов этих расширений:

#include <stdio.h>

void *
test2(void)
{
   return __builtin_return_address(1);
}

void *
test1(void *( *func )( void ))
{
   return (func) ? func() :
                   __builtin_return_address(0);
}

int main(int argc, char **argv)
{
   __label__ l1, l2;
   void *test1_ra, *test2_ra;

   printf("Main addr: %p;\n", &main);
   printf("label1: %p; label2: %p;\n", &&l1, &&l2);

l1:   test1_ra = test1(NULL);
l2:   test2_ra = test1(&test2);

   printf("test1: %p; test2: %p;\n", test1_ra, test2_ra);

   return 0;
}

Результат работы:

Main addr: 0x400565;
label1: 0x4005a1; label2: 0x4005af;
test1: 0x4005ab; test2: 0x4005b9;

Дизассемблер:

Dump of assembler code for function test2:
   0x00400536 <+0>:     push   %rbp
   0x00400537 <+1>:     mov    %rsp,%rbp
   0x0040053a <+4>:     mov    0x0(%rbp),%rax // __builtin_return_address(0)
   0x0040053e <+8>:     mov    0x8(%rax),%rax // __builtin_return_address(1)
   0x00400542 <+12>:    pop    %rbp
   0x00400543 <+13>:    retq

Dump of assembler code for function test1:
   0x00400544 <+0>:     push   %rbp
   0x00400545 <+1>:     mov    %rsp,%rbp
   0x00400548 <+4>:     sub    $0x10,%rsp
   0x0040054c <+8>:     mov    %rdi,-0x8(%rbp)
   0x00400550 <+12>:    cmpq   $0x0,-0x8(%rbp)
   0x00400555 <+17>:    je     0x40055f <test1+27>
   0x00400557 <+19>:    mov    -0x8(%rbp),%rax
   0x0040055b <+23>:    callq  *%rax            // func()
   0x0040055d <+25>:    jmp    0x400563 <test1+31>
   0x0040055f <+27>:    mov    0x8(%rbp),%rax   // __builtin_return_address(0)
   0x00400563 <+31>:    leaveq 
   0x00400564 <+32>:    retq

Dump of assembler code for function main:
   0x00400565 <+0>:     push   %rbp
   0x00400566 <+1>:     mov    %rsp,%rbp
   0x00400569 <+4>:     sub    $0x20,%rsp
   0x0040056d <+8>:     mov    %edi,-0x14(%rbp)
   0x00400570 <+11>:    mov    %rsi,-0x20(%rbp)
   0x00400574 <+15>:    mov    $0x400565,%esi // address of main function
   0x00400579 <+20>:    mov    $0x400670,%edi
   0x0040057e <+25>:    mov    $0x0,%eax
   0x00400583 <+30>:    callq  0x400410 <[email protected]> // "Main addr:"
   0x00400588 <+35>:    mov    $0x4005af,%edx        // l2
   0x0040058d <+40>:    mov    $0x4005a1,%esi        // l1
   0x00400592 <+45>:    mov    $0x400680,%edi
   0x00400597 <+50>:    mov    $0x0,%eax
   0x0040059c <+55>:    callq  0x400410 <[email protected]> // "label1:"
   0x004005a1 <+60>:    mov    $0x0,%edi             // <== l1 points here
   0x004005a6 <+65>:    callq  0x400544 <test1>
   0x004005ab <+70>:    mov    %rax,-0x8(%rbp)       // test1 return address
   0x004005af <+74>:    mov    $0x400536,%edi        // <== l2 points here
   0x004005b4 <+79>:    callq  0x400544 <test1>
   0x004005b9 <+84>:    mov    %rax,-0x10(%rbp)      // test2 return address
   0x004005bd <+88>:    mov    -0x10(%rbp),%rdx
   0x004005c1 <+92>:    mov    -0x8(%rbp),%rax
   0x004005c5 <+96>:    mov    %rax,%rsi
   0x004005c8 <+99>:    mov    $0x400699,%edi
   0x004005cd <+104>:   mov    $0x0,%eax
   0x004005d2 <+109>:   callq  0x400410 <[email protected]> // "test1:"
   0x004005d7 <+114>:   mov    $0x0,%eax
   0x004005dc <+119>:   leaveq 
   0x004005dd <+120>:   retq

Пост перенесён из старого блога.

Comments

comments powered by Disqus