Перейти к содержанию
    

GCC: статические конструкторы, __attribute__((constructor))

Здравствуйте.

 

предистория такая, собираюсь сдать статью в известный в нашем круги журнал электронного толка, по ходу статьи приводится мой код и соответственно пример можно будет скачать для повторения экспериментов. Встал вопрос - нада допилить его так чтоб нестыдно было людям показывать - лишнее выкинуть, камменты добавить и вообще ... и покатилось. Нашел много не точтобы ошибок - код то работает но..

 

один из обнаруженных косяков - решил в коде для красоты и стройности заиспользовать функцию с атрибутом __attribute__((constructor)), компиляю запускаю не работает :( емаЁ! пришлось разобратся в том что вроде мутно понимал но невлазил детально. теперь залез и с фонарем.

 

Итак тема

Статические конструкторы/деструкторы объектов С++ и С++/С-функции с атрибутом __attribute__((constructor)) __attribute__((destructor)) - КАК ЭТО РАБОТАЕТ В GCC

 

я работаю с GCC 4.6 но сведения актуально вроде бы для весей четвертой ветке которой уже несколько лет.

 

1. статические конструкторы/деструкторы это вызовы котрые создают и уничтожают глобальные объекты, тоесть не созданные в куче и на стеке. Причем особенность этих объектов такова что они должны быть созданы до входа в main() и уничтожены посе выхода из нее.

 

2. а также функции с атрибутом __attribute__((constructor)) __attribute__((destructor)) аналогично должны быть вызваны до и после исполненя main()

 

3. исходя из вышеописанных требований очевидно что должен сущесвовать какойто метод автоматически вызывать эти конструкторы и функции автоматически без явного кода написанного программистом.

 

4. Про то что такое вообще конструктры и тд я полагаю что читающие этот пост знают все и поэтому сразу перехожу к описанию как это GCC релизует.

 

5. Расмотрим пример:

C++:

class A
{
    private:
       int a;
    public:    
       A(int init_val) {  a = init_val; }
       ~A() {};        
}

A a_hinst(12); // статическое объявление объекта

 

C:

int val;
void _init() {}
void init_func() __attribute__ ((constructor)) 
{
}
void deinit_func() __attribute__ ((destructor)) 
{
}

6. теперь откомпилируем эти исходники в объектники и посмотрим содержание c утилиты objdump. Что кроме стандартных секций появились такие интересные секции:

.init_array

.fini_array

что это такое? при встрече c/с++ компилятора с глобальным объявлением обектов равно как функций с вышеопределенными атрибутами он проделывает следующую манипуляцию - создает две специальную секцию - .init_array .fini_array в которых помещает массив адресов. это адреса функций с атрибутом конструктора, для объектов чучуть сложнее - в массив кладется адрес специально сгенерированного кода который вызывает конструктруктор с нужными параметрами(как минимум это параметр this - указатель на адрес куска памяти отведенное для экземпляра объекта). это сделано так потому что массиве может быть толко указатели на функции типа void func(void), такое ограничение вызвано тем что ТО что будет вызывать функции из этого массива ничего не может знать о них, а тем более о параметрах которые можно былобы туда передать.

.fini_array для деструкторов функций с атрубутом деструктор, ситуация аналогичная но только сзади :). На этом реакция компллера ограничивается.

 

7. А теперь шачинается шаманство :)

а кто собсно должен вызвать это добро на исполнеее? эта функция видимо по стандарту возложена на реализацию libc. существует станадртная функция в libc c именем __libc_init_array(), ее код и дергает адреса из этих специальных секций. в моем случае это NewLib вот ее код ./newlib/libc/misc/init.c :

#include <sys/types.h>

#ifdef HAVE_INITFINI_ARRAY

/* These magic symbols are provided by the linker.  */
extern void (*__preinit_array_start []) (void) __attribute__((weak));
extern void (*__preinit_array_end []) (void) __attribute__((weak));
extern void (*__init_array_start []) (void) __attribute__((weak));
extern void (*__init_array_end []) (void) __attribute__((weak));
extern void (*__fini_array_start []) (void) __attribute__((weak));
extern void (*__fini_array_end []) (void) __attribute__((weak));

extern void _init (void);
extern void _fini (void);

/* Iterate over all the init routines.  */
void
__libc_init_array (void)
{
  size_t count;
  size_t i;

  count = __preinit_array_end - __preinit_array_start;
  for (i = 0; i < count; i++)
    __preinit_array_start[i] ();

  _init ();

  count = __init_array_end - __init_array_start;
  for (i = 0; i < count; i++)
    __init_array_start[i] ();
}

/* Run all the cleanup routines.  */
void
__libc_fini_array (void)
{
  size_t count;
  size_t i;
  
  count = __fini_array_end - __fini_array_start;
  for (i = count; i > 0; i--)
    __fini_array_start[i-1] ();

  _fini ();
}
#endif

из кода все понятно как работает.

 

8. теперь самое главное - та часть которой мы управляем и пишем ручками - скрип линкера, это заключительный элемент цепочки котрый требуется для реализации этого прекрасного метода. Буду полагать что если не писать то хотябы читать скрипты GNU ld читаещие умеют, поэтому распишем только то что нужно дописать до стандарного который обычно имеется.

 

а) нада объявить секции .init_array .fini_array

б) окружить их адресными счетчиками чтоб компилятор мог при компиляции вычислить реальные адреса и размер массивом указателей в этих секциях.

 

вот кусок скрипта:

    .text :                                
    {
        _text_start_ = .;
        /* вызов статических конструкторов */
        __preinit_array_start = .;
        KEEP(*(.preinit_array*))
        __preinit_array_end = .;
        __init_array_start  = .;
        KEEP(*(.init_array*))
        __init_array_end    = .;
        *(.text)
        *(.text*)        
        *(.rodata)
        *(.rodata*)
        *(.glue_7)
        *(.glue_7t)
        *(.gnu*)
        *(.gcc*)
        __fini_array_start = .;
        KEEP(*(.fini_array*))
        __fini_array_end   = .;
        . = ALIGN(8);
        _text_end_ = .;                    /* define a global symbol _etext just after the last code byte */
    } >flash                            /* put all the above into FLASH */

9. И последний мазок кистью - в crt коде перед вызовом main добавить вызов __libc_init_array() а после нее __libc_fini_array. ФИСЕ.

теперь с точки прикладного программиста конструкторы будут вызыватся сами волшебным образом автоматически ровно как и деструкторы :)

void Reset_Handler(void) // вызывается при сбросе проца
{
      crt_init();
   __libc_init_array();
       main();
   __libc_fini_array();

}

10. ну и наконец про то что мы всетаки эмбэдеры - все что относится к __libc_fini_array - можно смело выкинуть из скрипта линкера потому что мы эмбедеры и наша жизнь бесконечна в отличии от других яйцеголовых - наш main если не болен никогда некончается! покрайней мере пока есть элекричество в батарейке, это в частности приведет также к тому что с удалением секций из скрипта линкер выкинет из выходного файла и ненужный код деструкторов.

 

Итак, для прораммиста небходимо сделать модификацию crt кода и скрипта линкера, остальное сделает компиллер.

 

ps. я думаю что на русском языке подробнее об этом Вы непрочитаете, также надеюсь что мой труд не содержит банальных сведений и будет полезен. Я специально полопатил примеры FreeRTOS, демок к платкам Olimex. STM32 Primer и тд, нигде тема не раскрыта - там конструктры не работают :(

 

 

 

 

 

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

А не проще ли изначально писать на С++ и не мучиться так? :)

Или в чем заключается отличие от процедуры создания глобального объекта?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Я специально полопатил примеры FreeRTOS, демок к платкам Olimex. STM32 Primer и тд, нигде тема не раскрыта - там конструктры не работают :(

 

В примерах к scmRTOS с этим всё пучком:) Хотя, там конструкторы вызываются напрямую из стартапа, без использования newlib-овской __libc_init_array().

Но в любом случае - большое спасибо за информацию!

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

А что могут дать эти конструкторы обычным смертным, вроде меня?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Например, автоматически запускаемая до вызова main() функция инициализации периферии...

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Например, автоматически запускаемая до вызова main() функция инициализации периферии...

А смысл?

Тем более, что её, периферию, с т.з. надёжности рекомендуется время от времени "встряхивать" во время работы, мало ли какой битик в регистре вспрыгнет или упадёт через N суток непрерывной работы. А конструкторы вроде как уже не вызовешь из мэйна. Так что в си пока не вижу особого смысла...

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

А смысл?

 

 

плохо что смысл непонятен.

смысл в том что это и есть стандартный механизм C/C++. отличия програмированяи для embedded в том что стартап и скрипт линкера пишется самим програмистом в отличие от больших машин где у програмиста даже нет нужды знать как это работает - там сразу все работает, а у нас это нада заставить работать. всеголиш это я и хотел сказаь. но подробно

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

avr-gcc складывает С++ - ное и С-шное с __attribute__ ((constructor)) __attribute__ ((destructor)) в секции .ctros и .dtors соответственно, а код их обработки не в вызываемых функциях __libc_init_array (void)/__libc_fini_array (void), а в секцих .init6 .fini6 встроен "инлайн" порядком линковки в стартап

  .text   :
 {
   *(.vectors)
   KEEP(*(.vectors))
   /* For data that needs to reside in the lower 64k of progmem.  */
   *(.progmem.gcc*)
   *(.progmem*)
   . = ALIGN(2);
    __trampolines_start = . ;
   /* The jump trampolines for the 16-bit limited relocs will reside here.  */
   *(.trampolines)
   *(.trampolines*)
    __trampolines_end = . ;
   /* For future tablejump instruction arrays for 3 byte pc devices.
      We don't relax jump/call instructions within these sections.  */
   *(.jumptables)
   *(.jumptables*)
   /* For code that needs to reside in the lower 128k progmem.  */
   *(.lowtext)
   *(.lowtext*)
    __ctors_start = . ;
    *(.ctors)
    __ctors_end = . ;
    __dtors_start = . ;
    *(.dtors)
    __dtors_end = . ;
   KEEP(SORT(*)(.ctors))
   KEEP(SORT(*)(.dtors))
   /* From this point on, we don't bother about wether the insns are
      below or above the 16 bits boundary.  */
   *(.init0)  /* Start here after reset.  */
   KEEP (*(.init0))
   *(.init1)
   KEEP (*(.init1))
   *(.init2)  /* Clear __zero_reg__, set up stack pointer.  */
   KEEP (*(.init2))
   *(.init3)
   KEEP (*(.init3))
   *(.init4)  /* Initialize data and BSS.  */
   KEEP (*(.init4))
   *(.init5)
   KEEP (*(.init5))
   *(.init6)  /* C++ constructors.  */
   KEEP (*(.init6))
   *(.init7)
   KEEP (*(.init7))
   *(.init8)
   KEEP (*(.init8))
   *(.init9)  /* Call main().  */
   KEEP (*(.init9))
   *(.text)
   . = ALIGN(2);
   *(.text.*)
   . = ALIGN(2);
   *(.fini9)  /* _exit() starts here.  */
   KEEP (*(.fini9))
   *(.fini8)
   KEEP (*(.fini8))
   *(.fini7)
   KEEP (*(.fini7))
   *(.fini6)  /* C++ destructors.  */
   KEEP (*(.fini6))
   *(.fini5)
   KEEP (*(.fini5))
   *(.fini4)
   KEEP (*(.fini4))
   *(.fini3)
   KEEP (*(.fini3))
   *(.fini2)
   KEEP (*(.fini2))
   *(.fini1)
   KEEP (*(.fini1))
   *(.fini0)  /* Infinite loop after program termination.  */
   KEEP (*(.fini0))
    _etext = . ;
 }  > text

Метки __do_global_ctors / __do_global_dtors есть, но не для вызова по call, а для того, чтобы нужный кусок просто был прилинкован в линейный код секций .init* / .fini*

    .global __do_global_ctors; вот и попросили линкер прилинковать
    .section .ctors,"a",@progbits
    .word    gs(init_func)
    .global __do_global_dtors
    .section .dtors,"a",@progbits
    .word    gs(deinit_func)

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Кстати да, секции .init_array и .fini_array - это не универсальная штука. Они появились в gcc для ARM при переходе на EABI. Вроде как EABI это рекомендует.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

У меня конструкторы вызываются из crt.S без библиотечных функций:

//     Call C++ constructors
        LDR     R0, =__ctors_start
        LDR     R1, =__ctors_end
ctor_loop:
        CMP     R0, R1
        BEQ     ctor_end
        LDR     R2, [R0], #4
        STMFD   SP!, {R0,R1}
        MOV     LR, PC
        BX      R2                      // some constructors can be in THUMB mode
        LDMFD   SP!, {R0,R1}
        B       ctor_loop
ctor_end:

Ну и скрипт линкера универсальный для EABI/pre-EABI:

  .text :
  {
    __ctors_start = .;
    KEEP(SORT(*)(.ctors))
    KEEP(SORT(*)(.init_array))
     __ctors_end = .;

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

насчет интерфейса EABI замечание ценное и существенное, я забыл про это сказать, соответственно прорт для AVR делает это по старому. но как я понял новая методика рекомендуется для ркализации вместо старой независимо от платформы.

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

но как я понял новая методика рекомендуется для реализации вместо старой независимо от платформы.
А в чем ее новость кроме изменения имени секции? У меня весь старый код пошел после добавления в скрипт секции .init_array.

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

..зря полчаса потратил, нада было спать идти сразу а не писать пост - матерые волки это и так знают а новичкам это не интересно знать. нового ничего не обещал - хотябы в хорошо забытом старом разобратся бы... понятно что надо было добавить секцию, но человеку который непонимает механизма эта мысль не возникнет, вот я решил это осветить чтоб никто не тратил время то которое я уже потратил когдато.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

..зря полчаса потратил, нада было спать идти сразу а не писать пост - матерые волки это и так знают а новичкам это не интересно знать.

Ничего не зря. Новички тоже иногда становятся матёрыми волками. И тогда они вспомнят - а где-то я это видел...

Ну и вообще, приятно послушать, как умные дяденьки общаются.

Я вот половину буков не понял, зато видно, куда стремиться

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

А я, например, не знал, как оно для ARM сделано.

Когда-то давно в MSP заглядывал, но уже не помню, как это там было.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...