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

Я только что прочитал эту прекрасную статью: 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 выше?

4

Решение

Как вы уже заметили, детали реализации конструкторов / функций инициализации сильно зависят от компилятора (версии). Хотя я не знаю инструмента для этого, то, что делают текущие версии 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 получает нулевую запись, которая перезаписывается конечным адресом инициализатора при загрузке библиотеки. Таким образом, этот скрипт выведет адрес со всеми нулями.

3

Другие решения

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

1