Как вы уже заметили, детали реализации функций конструкторов/инициализации сильно зависят от компилятора (версии). Хотя я не знаю об этом инструменте, то, что делают текущие версии 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
получает запись с нулевым номером, которая перезаписывается конечным адресом инициализатора при загрузке библиотеки. Таким образом, этот скрипт выдаст адрес со всеми нулями.
Спасибо! Но где преимущество этого в сравнении с «nm -a binaryOrSharedLibrary | grep GLOBAL__'? Это намного проще и генерирует для меня тот же результат. Символу не так сложно достать. Скорее, я хотел бы узнать файл/строку, чтобы найти то, что мне нужно изменить. – milianw
Кроме того, в обоих случаях я получаю '_GLOBAL__sub_I_filename.cpp' вместо« правильных »символов для ctors, которые запускаются в этом файле. – milianw
Символы могут, и часто в не-отладочных сборках, лишены. Это также удаляет символы «GLOBAL__». С другой стороны, обычно присутствует '.init_array'. Вторая причина, по которой я предпочитаю использовать '.init_array', состоит в том, что она более общая: функции инициализатора C (функции, имеющие' __attribute __ ((конструктор))) также используют этот механизм, но нет отдельного 'GLOBAL__' созданный для них символ. Что касается имени символа, то создается сгенерированная промежуточная функция. Найдите '__static_initialization_and_destruction_' в' objdump -d'. – Phillip