Как найти глобальные статические инициализации

Я только что прочитал эту прекрасную статью: http://neugierig.org/software/chromium/notes/2011/08/static-initializers.html, а затем я попробовал: https://gcc.gnu.org/onlinedocs/gccint/Initialization.html

То, что он говорит о поиске инициализаторов, у меня не работает. Раздел .ctors недоступен, но я смог найти .init_array (см. также Не могу найти .dtors и .ctors в двоичном формате). Но как мне интерпретировать вывод? Я имею в виду, что суммирование размера страниц также может быть обработано командой size и ее столбцом .bss - или я что-то упустил?

Кроме того, nm не сообщает ни о каких *_GLOBAL__I_* символах, только о *_GLOBAL__N_* функциях и, что более интересно, о _GLOBAL__sub_I_somefile.cpp записях. Последнее, вероятно, указывает на файлы с глобальной инициализацией. Но можно ли как-то получить список запущенных конструкторов? В идеале инструмент должен был дать мне список

Foo::Foo in file1.cpp:12
Bar::Bar in file2.cpp:45
...

(при условии, что у меня есть символы отладки). Есть такой инструмент? Если нет, то как это можно было написать? Содержит ли секция .init_array указатели на код, который можно преобразовать с помощью магии DWARF в приведенный выше код?


person milianw    schedule 23.01.2015    source источник


Ответы (2)


Как вы уже заметили, детали реализации конструкторов/функций инициализации сильно зависят от компилятора (версии). Хотя мне неизвестен инструмент для этого, то, что делают текущие версии GCC/clang, достаточно просто, чтобы позволить небольшому сценарию выполнить эту работу: .init_array — это просто список точек входа. objdump -s можно использовать для загрузки списка, а nm — для поиска имен символов. Вот скрипт Python, который делает это. Он должен работать для любого двоичного файла, созданного указанными компиляторами:

#!/usr/bin/env python
import os
import sys

# Load .init_array section
objdump_output = os.popen("objdump -s '%s' -j .init_array" % (sys.argv[1].replace("'", r"\'"),)).read()
is_64bit = "x86-64" in objdump_output
init_array = objdump_output[objdump_output.find("Contents of section .init_array:") + 33:]
initializers = []
for line in init_array.split("\n"):
    parts = line.split()
    if not parts:
        continue
    parts.pop(0)  # Remove offset
    parts.pop(-1) # Remove ascii representation

    if is_64bit:
        # 64bit pointers are 8 bytes long
        parts = [ "".join(parts[i:i+2]) for i in range(0, len(parts), 2) ]

    # Fix endianess
    parts = [ "".join(reversed([ x[i:i+2] for i in range(0, len(x), 2) ])) for x in parts ]

    initializers += parts

# Load disassembly for c++ constructors
dis_output = os.popen("objdump -d '%s' | c++filt" % (sys.argv[1].replace("'", r"\'"), )).read()
def find_associated_constructor(disassembly, symbol):
    # Find associated __static_initialization function
    loc = disassembly.find("<%s>" % symbol)
    if loc < 0:
        return False
    loc = disassembly.find(" <", loc)
    if loc < 0:
        return False
    symbol = disassembly[loc+2:disassembly.find("\n", loc)][:-1]
    if symbol[:23] != "__static_initialization":
        return False
    address = disassembly[disassembly.rfind(" ", 0, loc)+1:loc]
    loc = disassembly.find("%s <%s>" % (address, symbol))
    if loc < 0:
        return False
    # Find all callq's in that function
    end_of_function = disassembly.find("\n\n", loc)
    symbols = []
    while loc < end_of_function:
        loc = disassembly.find("callq", loc)
        if loc < 0 or loc > end_of_function:
            break
        loc = disassembly.find("<", loc)
        symbols.append(disassembly[loc+1:disassembly.find("\n", loc)][:-1])
    return symbols

# Load symbol names, if available
nm_output = os.popen("nm '%s'" % (sys.argv[1].replace("'", r"\'"), )).read()
nm_symbols = {}
for line in nm_output.split("\n"):
    parts = line.split()
    if not parts:
        continue
    nm_symbols[parts[0]] = parts[-1]

# Output a list of initializers
print("Initializers:")
for initializer in initializers:
    symbol = nm_symbols[initializer] if initializer in nm_symbols else "???"
    constructor = find_associated_constructor(dis_output, symbol)
    if constructor:
        for function in constructor:
            print("%s %s -> %s" % (initializer, symbol, function))
    else:
        print("%s %s" % (initializer, symbol))

Статические инициализаторы C++ вызываются не напрямую, а через две сгенерированные функции, _GLOBAL__sub_I_.. и __static_initialization... Сценарий использует дизассемблирование этих функций, чтобы получить имя фактического конструктора. Вам понадобится инструмент c++filt, чтобы разобрать имена или удалить вызов из скрипта, чтобы увидеть необработанное имя символа.

Общие библиотеки могут иметь свои собственные списки инициализаторов, которые не будут отображаться этим сценарием. Здесь ситуация немного сложнее: для нестатических инициализаторов .init_array получает запись со всеми нулями, которая перезаписывается конечным адресом инициализатора при загрузке библиотеки. Таким образом, этот скрипт будет выводить адрес со всеми нулями.

person Phillip    schedule 26.01.2015
comment
Спасибо! Но в чем преимущество этого по сравнению с nm -a binaryOrSharedLibrary | grep GLOBAL__? Это намного проще и дает тот же результат для меня. Символ не так сложно получить. Скорее, я хотел бы знать файл/строку, чтобы найти то, что мне нужно отредактировать. - person milianw; 26.01.2015
comment
Кроме того, в обоих случаях я получаю только _GLOBAL__sub_I_filename.cpp вместо правильных символов для ctors, которые запускаются в этом файле. - person milianw; 26.01.2015
comment
Символы могут быть удалены, и часто в сборках без отладки. Это также удаляет символы GLOBAL__. С другой стороны, .init_array обычно присутствует. Вторая причина, по которой я бы предпочел использовать .init_array, заключается в том, что он более общий: функции-инициализаторы C (функции, имеющие __attribute__((constructor))) также используют этот механизм, но для них не генерируется отдельный символ GLOBAL__. Что касается имени символа, то здесь есть сгенерированная промежуточная функция. Найдите __static_initialization_and_destruction_ в objdump -d. - person Phillip; 26.01.2015
comment
Отлично, совет по просмотру дизассемблированного вывода __static_initialization_and_destruction_ действительно помогает. С этим я мог бы написать сценарий, который сопоставляет _GLOBAL__sub_I_filename.cpp с идентификаторами, которые я получаю из инструкций callq в __static_initialization_and_destruction_! Конечно, это будет работать только для полной отладки без встроенной сборки общей библиотеки. Но достаточно хорошо для моего варианта использования! - person milianw; 26.01.2015
comment
Я обновил скрипт, чтобы он также следовал сгенерированному коду С++ к фактическому конструктору. - person Phillip; 26.01.2015
comment
Вы сообразительны :) Но код показывает не более одного ctor, верно? Зачастую их больше. Возьмем следующий пример кода: paste.kde.org/pn4pbkl5h Ваш инструмент находит только std::ios_base::Init::Init()@plt, но не std::allocator<int>::allocator() и не std::vector<int, std::allocator<int> >::vector(std::initializer_list<int>, std::allocator<int> const&). Не могли бы вы продлить его? Тогда я приму ваш ответ :) - person milianw; 26.01.2015
comment
Верно. Видимо все конструкторы собраны в одну функцию. Я обновил скрипт, чтобы вывести все callq в функции. - person Phillip; 26.01.2015
comment
Большое спасибо! Теперь мне нужно только адаптировать его для работы с несколькими файлами, связанными вместе в общую библиотеку (в настоящее время вы, кажется, используете самый первый __static_initialization_and_destruction? always, i.e. do symbol-name lookup instead of using the address of that functions callq` в функции _GLOBAL__sub_I_file.cpp. Во всяком случае, это показывает, что это выполнимо, и я приму ваш ответ. Если бы вы могли расширить его для работы с .so - еще лучше :) В остальном я воспринимаю это как упражнение для себя. - person milianw; 26.01.2015
comment
Интересно. Я был совершенно уверен, что для второго объекта будет использоваться другое имя. Я изменил скрипт, чтобы включить адрес в поиск. - person Phillip; 26.01.2015
comment
Это прекрасно работает: paste.kde.org/pdojyvaqg - большое спасибо! Я принял ваш ответ и награжу вас наградой, когда смогу это сделать (осталось 2 часа). Спасибо! - person milianw; 26.01.2015

При загрузке объекта ELF выполняется несколько вещей, а не только .init_array. Чтобы получить общее представление, я предлагаю просмотреть исходники libc. загрузчик, особенно _dl_init() и call_init().

person Thomas McGuire    schedule 26.01.2015