如何find全局静态初始化

我刚刚阅读这个优秀的文章: http : //neugierig.org/software/chromium/notes/2011/08/static-initializers.html ,然后我尝试: https : //gcc.gnu.org/onlinedocs/gccint/Initialization html的

但是,关于查找初始化程序的说法并不适用于我。 .ctors部分不可用,但是我可以find.init_array (另请参阅无法在二进制文件中find.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 ... 

(假设我有debugging符号可用)。 有这样的工具吗? 如果不是,那么怎么写呢? .init_array部分是否包含指向可以通过一些DWARF魔术翻译到上面的代码的指针?

正如您已经观察到的,构造函数/初始化函数的实现细节高度依赖于编译器(版本)。 虽然我不知道这个工具,但是目前的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获取一个全零值项,在加载该库时会被初始值设定项的最终地址覆盖。 所以这个脚本会输出一个全零的地址。

加载ELF对象时执行多个事情,而不仅仅是.init_array 。 为了得到一个概述,我建议看看libc的加载器的来源 ,尤其是_dl_init()call_init()