使用Ulrich Drepper的relinfo.pl
脚本,可以很容易地计算DSO重定位的次数,但它不适用于.o
文件。找到重定位的起始位置
假设我有一个很大的共享库,我对它的重定位次数不满意。有没有办法找出它们来自哪里(符号,或至少.o
),以检查它们是否是易于修复的类型(例如:const char * str = "Hello World";'
- >const char str[] = "Hello World";
)?
使用Ulrich Drepper的relinfo.pl
脚本,可以很容易地计算DSO重定位的次数,但它不适用于.o
文件。找到重定位的起始位置
假设我有一个很大的共享库,我对它的重定位次数不满意。有没有办法找出它们来自哪里(符号,或至少.o
),以检查它们是否是易于修复的类型(例如:const char * str = "Hello World";'
- >const char str[] = "Hello World";
)?
龙答:让我们来看一个实际的例子情况下,example.c
:
#include <stdio.h>
static const char global1[] = "static const char []";
static const char *global2 = "static const char *";
static const char *const global3 = "static const char *const";
const char global4[] = "const char []";
const char *global5 = "const char *";
const char *const global6 = "const char *const";
char global7[] = "char []";
char *global8 = "char *";
char *const global9 = "char *const";
int main(void)
{
static const char local1[] = "static const char []";
static const char *local2 = "static const char *";
static const char *const local3 = "static const char *const";
const char local4[] = "const char []";
const char *local5 = "const char *";
const char *const local6 = "const char *const";
char local7[] = "char []";
char *local8 = "char *";
char *const local9 = "char *const";
printf("Global:\n");
printf("\t%s\n", global1);
printf("\t%s\n", global2);
printf("\t%s\n", global3);
printf("\t%s\n", global4);
printf("\t%s\n", global5);
printf("\t%s\n", global6);
printf("\t%s\n", global7);
printf("\t%s\n", global8);
printf("\t%s\n", global9);
printf("\n");
printf("Local:\n");
printf("\t%s\n", local1);
printf("\t%s\n", local2);
printf("\t%s\n", local3);
printf("\t%s\n", local4);
printf("\t%s\n", local5);
printf("\t%s\n", local6);
printf("\t%s\n", local7);
printf("\t%s\n", local8);
printf("\t%s\n", local9);
return 0;
}
你可以把它编译成使用例如一个目标文件
gcc -W -Wall -c example.c
以及使用
gcc -W -Wall example.c -o example
可执行可以使用objdump -tr example.o
转储的(非动态)目标文件中的符号和重定位信息,或objdump -TtRr example
转储相同的可执行文件(和动态目标文件)。在X86-64我得到
example.o: file format elf64-x86-64
SYMBOL TABLE:
0000000000000000 l df *ABS* 0000000000000000 example.c
0000000000000000 l d .text 0000000000000000 .text
0000000000000000 l d .data 0000000000000000 .data
0000000000000000 l d .bss 0000000000000000 .bss
0000000000000000 l d .rodata 0000000000000000 .rodata
0000000000000000 l O .rodata 0000000000000015 global1
0000000000000000 l O .data 0000000000000008 global2
0000000000000048 l O .rodata 0000000000000008 global3
00000000000000c0 l O .rodata 0000000000000015 local1.2053
0000000000000020 l O .data 0000000000000008 local2.2054
00000000000000d8 l O .rodata 0000000000000008 local3.2055
0000000000000000 l d .note.GNU-stack 0000000000000000 .note.GNU-stack
0000000000000000 l d .eh_frame 0000000000000000 .eh_frame
0000000000000000 l d .comment 0000000000000000 .comment
0000000000000050 g O .rodata 000000000000000e global4
0000000000000008 g O .data 0000000000000008 global5
0000000000000080 g O .rodata 0000000000000008 global6
0000000000000010 g O .data 0000000000000008 global7
0000000000000018 g O .data 0000000000000008 global8
00000000000000a0 g O .rodata 0000000000000008 global9
0000000000000000 g F .text 000000000000027a main
0000000000000000 *UND* 0000000000000000 puts
0000000000000000 *UND* 0000000000000000 printf
0000000000000000 *UND* 0000000000000000 putchar
0000000000000000 *UND* 0000000000000000 __stack_chk_fail
输出在man 1 objdump
描述的那样,-t
标题下使用
objdump -t example.o
。请注意,第二个“列”实际上是固定宽度:七个字符宽,描述对象的类型。第三列是代码段名称,*UND*
代表未定义,.text
代码,.rodata
代表只读(不可变)数据,.data
代表初始化的可变数据,.bss
代表未初始化的可变数据,等等。
我们可以从上面的符号表中看到,local4
,local5
,local6
,local7
,local8
和local9
变量实际上并没有在符号表中获得条目的。这是因为他们在main()
。它们所指向的字符串的内容存储在.data
或.rodata
(或即时构建)中,具体取决于编译器认为最佳的内容。
下面我们来看看重定位记录。使用
objdump -r example.o
我得到
example.o: file format elf64-x86-64
RELOCATION RECORDS FOR [.text]:
OFFSET TYPE VALUE
0000000000000037 R_X86_64_32S .rodata+0x000000000000005e
0000000000000040 R_X86_64_32S .rodata+0x000000000000006b
0000000000000059 R_X86_64_32S .rodata+0x0000000000000088
0000000000000062 R_X86_64_32S .rodata+0x000000000000008f
0000000000000067 R_X86_64_32 .rodata+0x00000000000000a8
000000000000006c R_X86_64_PC32 puts-0x0000000000000004
0000000000000071 R_X86_64_32 .rodata+0x00000000000000b0
0000000000000076 R_X86_64_32 .rodata
0000000000000083 R_X86_64_PC32 printf-0x0000000000000004
000000000000008a R_X86_64_PC32 .data-0x0000000000000004
000000000000008f R_X86_64_32 .rodata+0x00000000000000b0
000000000000009f R_X86_64_PC32 printf-0x0000000000000004
00000000000000a6 R_X86_64_PC32 .rodata+0x0000000000000044
00000000000000ab R_X86_64_32 .rodata+0x00000000000000b0
00000000000000bb R_X86_64_PC32 printf-0x0000000000000004
00000000000000c0 R_X86_64_32 .rodata+0x00000000000000b0
00000000000000c5 R_X86_64_32 global4
00000000000000d2 R_X86_64_PC32 printf-0x0000000000000004
00000000000000d9 R_X86_64_PC32 global5-0x0000000000000004
00000000000000de R_X86_64_32 .rodata+0x00000000000000b0
00000000000000ee R_X86_64_PC32 printf-0x0000000000000004
00000000000000f5 R_X86_64_PC32 global6-0x0000000000000004
00000000000000fa R_X86_64_32 .rodata+0x00000000000000b0
000000000000010a R_X86_64_PC32 printf-0x0000000000000004
000000000000010f R_X86_64_32 .rodata+0x00000000000000b0
0000000000000114 R_X86_64_32 global7
0000000000000121 R_X86_64_PC32 printf-0x0000000000000004
0000000000000128 R_X86_64_PC32 global8-0x0000000000000004
000000000000012d R_X86_64_32 .rodata+0x00000000000000b0
000000000000013d R_X86_64_PC32 printf-0x0000000000000004
0000000000000144 R_X86_64_PC32 global9-0x0000000000000004
0000000000000149 R_X86_64_32 .rodata+0x00000000000000b0
0000000000000159 R_X86_64_PC32 printf-0x0000000000000004
0000000000000163 R_X86_64_PC32 putchar-0x0000000000000004
0000000000000168 R_X86_64_32 .rodata+0x00000000000000b5
000000000000016d R_X86_64_PC32 puts-0x0000000000000004
0000000000000172 R_X86_64_32 .rodata+0x00000000000000b0
0000000000000177 R_X86_64_32 .rodata+0x00000000000000c0
0000000000000184 R_X86_64_PC32 printf-0x0000000000000004
000000000000018b R_X86_64_PC32 .data+0x000000000000001c
0000000000000190 R_X86_64_32 .rodata+0x00000000000000b0
00000000000001a0 R_X86_64_PC32 printf-0x0000000000000004
00000000000001a7 R_X86_64_PC32 .rodata+0x00000000000000d4
00000000000001ac R_X86_64_32 .rodata+0x00000000000000b0
00000000000001bc R_X86_64_PC32 printf-0x0000000000000004
00000000000001c1 R_X86_64_32 .rodata+0x00000000000000b0
00000000000001d6 R_X86_64_PC32 printf-0x0000000000000004
00000000000001db R_X86_64_32 .rodata+0x00000000000000b0
00000000000001ef R_X86_64_PC32 printf-0x0000000000000004
00000000000001f4 R_X86_64_32 .rodata+0x00000000000000b0
0000000000000209 R_X86_64_PC32 printf-0x0000000000000004
000000000000020e R_X86_64_32 .rodata+0x00000000000000b0
0000000000000223 R_X86_64_PC32 printf-0x0000000000000004
0000000000000228 R_X86_64_32 .rodata+0x00000000000000b0
000000000000023d R_X86_64_PC32 printf-0x0000000000000004
0000000000000242 R_X86_64_32 .rodata+0x00000000000000b0
0000000000000257 R_X86_64_PC32 printf-0x0000000000000004
0000000000000271 R_X86_64_PC32 __stack_chk_fail-0x0000000000000004
RELOCATION RECORDS FOR [.data]:
OFFSET TYPE VALUE
0000000000000000 R_X86_64_64 .rodata+0x0000000000000015
0000000000000008 R_X86_64_64 .rodata+0x000000000000005e
0000000000000018 R_X86_64_64 .rodata+0x0000000000000088
0000000000000020 R_X86_64_64 .rodata+0x0000000000000015
RELOCATION RECORDS FOR [.rodata]:
OFFSET TYPE VALUE
0000000000000048 R_X86_64_64 .rodata+0x0000000000000029
0000000000000080 R_X86_64_64 .rodata+0x000000000000006b
00000000000000a0 R_X86_64_64 .rodata+0x000000000000008f
00000000000000d8 R_X86_64_64 .rodata+0x0000000000000029
RELOCATION RECORDS FOR [.eh_frame]:
OFFSET TYPE VALUE
0000000000000020 R_X86_64_PC32 .text
搬迁记录由他们搬迁驻留在节编组。因为字符串内容在.data
或.rodata
章节中,我们可以限制自己,看看搬迁其中VALUE
以.data
或.rodata
开头。 (可变字符串,如char global7[] = "char []";
,存储在.data
中,以及.rodata
中的不可变字符串和字符串文字。)
如果我们要编译启用了调试符号的代码,可以更容易地确定哪个变量用于引用哪个字符串,但我可能只是查看每个重定位值(target)处的实际内容,看哪个引用以不可变的字符串需要修复。
发生最在最后重定位命令组合
objdump -r example.o | awk '($3 ~ /^\..*\+/) { t = $3; sub(/\+/, " ", t); n[t]++ } END { for (r in n) printf "%d %s\n", n[r], r }' | sort -g
将输出每个目标重定位的数目,随后由目标部分,之后该目标部分中的偏移量,与目标排序。也就是说,上面输出的最后一行是你需要关注的。对于我来说,我得到
1 .rodata
1 .rodata 0x0000000000000044
1 .rodata 0x00000000000000a8
1 .rodata 0x00000000000000b5
1 .rodata 0x00000000000000c0
1 .rodata 0x00000000000000d4
2 .rodata 0x0000000000000015
2 .rodata 0x0000000000000029
2 .rodata 0x000000000000005e
2 .rodata 0x000000000000006b
2 .rodata 0x0000000000000088
2 .rodata 0x000000000000008f
18 .rodata 0x00000000000000b0
如果我添加优化(gcc -W -Wall -O3 -fomit-frame-pointer -c example.c
),其结果是
1 .rodata 0x0000000000000020
1 .rodata 0x0000000000000040
1 .rodata.str1.1
1 .rodata.str1.1 0x0000000000000058
2 .rodata.str1.1 0x000000000000000d
2 .rodata.str1.1 0x0000000000000021
2 .rodata.str1.1 0x000000000000005f
2 .rodata.str1.1 0x000000000000006c
3 .rodata.str1.1 0x000000000000003a
3 .rodata.str1.1 0x000000000000004c
18 .rodata.str1.1 0x0000000000000008
这表明编译器选项确实有很大的影响,但有一个目标那就是反正使用了18次:部分.rodata
偏移量0xb0
(.rodata.str1.1
偏移量0x8
如果在编译时启用优化)。
这就是'\ t%s \ n“字符串。
修改原始程序进入
char *local8 = "char *";
char *const local9 = "char *const";
const char *const fmt = "\t%s\n";
printf("Global:\n");
printf(fmt, global1);
printf(fmt, global2);
等,具有一个不可变字符串指针fmt
替换格式字符串,完全消除那些18重定位。 (也可以使用等效的const char fmt[] = "\t%s\n";
,当然。)
上述分析表明,至少与GCC-4.6.3,大多数可以避免迁移的由(重复使用)字符串文字引起的。用常量字符数组(const char fmt[] = "\t%s\n";
)或常量指针指向常量字符(const char *const fmt = "\t%s\n";
)替换它们 - 这两种情况都将内容置于.rodata
节,只读,指针/数组引用本身也是不可变的 - 似乎是有效和安全的战略给我。
此外,将字符串文字转换为不可变字符串指针或字符数组完全是源代码级任务。也就是说,如果使用上述方法转换所有字符串文字,则可以消除每个字符串文字至少一次重定位。
事实上,我不明白对象级别的分析对你有多大帮助。它会告诉你,如果你的修改减少了所需的重定位数量,当然。
以上awk
节可以扩展到输出的正偏移动态引用常量字符串的函数:
#!/bin/bash
if [ $# -ne 1 ] || [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
exec >&2
echo ""
echo "Usage: %s [ -h | --help ]"
echo " %s object.o"
echo ""
exit 1
fi
export LANG=C LC_ALL=C
objdump -wr "$1" | awk '
BEGIN {
RS = "[\t\v\f ]*[\r\n][\t\n\v\f\r ]*"
FS = "[\t\v\f ]+"
}
$1 ~ /^[0-9A-Fa-f]+/ {
n[$3]++
}
END {
for (s in n)
printf "%d %s\n", n[s], s
}
' | sort -g | gawk -v filename="$1" '
BEGIN {
RS = "[\t\v\f ]*[\r\n][\t\n\v\f\r ]*"
FS = "[\t\v\f ]+"
cmd = "objdump --file-offsets -ws " filename
while ((cmd | getline) > 0)
if ($3 == "section") {
s = $4
sub(/:$/, "", s)
o = $NF
sub(/\)$/, "", o)
start[s] = strtonum(o)
}
close(cmd)
}
{
if ($2 ~ /\..*\+/) {
s = $2
o = $2
sub(/\+.*$/, "", s)
sub(/^[^\+]*\+/, "", o)
o = strtonum(o) + start[s]
cmd = "dd if=\"" filename "\" of=/dev/stdout bs=1 skip=" o " count=256"
OLDRS = RS
RS = "\0"
cmd | getline hex
close(cmd)
RS = OLDRS
gsub(/\\/, "\\\\", hex)
gsub(/\t/, "\\t", hex)
gsub(/\n/, "\\n", hex)
gsub(/\r/, "\\r", hex)
gsub(/\"/, "\\\"", hex)
if (hex ~ /[\x00-\x1F\x7F-\x9F\xFE\xFF]/ || length(hex) < 1)
printf "%s\n", $0
else
printf "%s = \"%s\"\n", $0, hex
} else
print $0
}
'
这是一个有点粗糙,只是拼凑,所以我不知道如何它是便携式。在我的机器上,它似乎找到了我尝试使用的少数测试用例的字符串文字;你应该重写它以符合你自己的需求。甚至可以使用ELF支持的实际编程语言直接检查目标文件。
对于以上(之前的修改建议,以减少迁移的数量)所示的例子程序,没有优化编译,上面的脚本产生的输出
1 .data+0x000000000000001c = ""
1 .data-0x0000000000000004
1 .rodata
1 .rodata+0x0000000000000044 = ""
1 .rodata+0x00000000000000a8 = "Global:"
1 .rodata+0x00000000000000b5 = "Local:"
1 .rodata+0x00000000000000c0 = "static const char []"
1 .rodata+0x00000000000000d4 = ""
1 .text
1 __stack_chk_fail-0x0000000000000004
1 format
1 global4
1 global5-0x0000000000000004
1 global6-0x0000000000000004
1 global7
1 global8-0x0000000000000004
1 global9-0x0000000000000004
1 putchar-0x0000000000000004
2 .rodata+0x0000000000000015 = "static const char *"
2 .rodata+0x0000000000000029 = "static const char *const"
2 .rodata+0x000000000000005e = "const char *"
2 .rodata+0x000000000000006b = "const char *const"
2 .rodata+0x0000000000000088 = "char *"
2 .rodata+0x000000000000008f = "char *const"
2 puts-0x0000000000000004
18 .rodata+0x00000000000000b0 = "\t%s\n"
18 printf-0x0000000000000004
最后,你可能会发现,使用函数指针printf()
而不是直接调用printf()
会减少另外18个示例代码的重定位,但我会认为这是一个错误。
对于代码,您需要想要重定位,因为间接函数调用(通过函数指针调用)比直接调用慢得多。简而言之,这些重定位使函数和子程序调用速度更快,所以你绝对要保留这些。
道歉的长答案;希望您觉得这个有帮助。有问题吗?
基于Nomainal动物的答案,我还是要完全消化掉,我想出了以下简单的shell脚本,这似乎寻找什么,我称之为“容易可以解决的”各种工作:
for i in path/to/*.o ; do
REL="$(objdump -TtRr "$i" 2>/dev/null | grep '.data.rel.ro.local[^]+-]')"
if [ -n "$REL" ]; then
echo "$(basename "$i"):"
echo "$REL" | c++filt
echo
fi
done
样本输出(用于QtGui库):
qimagereader.o:
0000000000000000 l O .data.rel.ro.local 00000000000000c0 _qt_BuiltInFormats
0000000000000000 l d .data.rel.ro.local 0000000000000000 .data.rel.ro.local
qopenglengineshadermanager.o:
0000000000000000 l O .data.rel.ro.local 0000000000000090 QOpenGLEngineShaderManager::getUniformLocation(QOpenGLEngineShaderManager::Uniform)::uniformNames
0000000000000000 l d .data.rel.ro.local 0000000000000000 .data.rel.ro.local
qopenglpaintengine.o:
0000000000000000 l O .data.rel.ro.local 0000000000000020 vtable for (anonymous namespace)::QOpenGLStaticTextUserData
0000000000000000 l d .data.rel.ro.local 0000000000000000 .data.rel.ro.local
qtexthtmlparser.o:
0000000000000000 l O .data.rel.ro.local 00000000000003b0 elements
0000000000000000 l d .data.rel.ro.local 0000000000000000 .data.rel.ro.local
仰望源文件中的这些符号通常会很快导致了修复,要不,他们是不会轻易可以解决的发现。
但我想我得一次我跑出来的.data.rel.ro.local
s到修复重温标称动物的答案...
你有什么想知道的:需要符号的目标文件?或者包含符号的目标文件或共享库?你有很多目标文件还是你有一个共享库? –
@MartinRosenau:包含符号的对象文件。而且我有共享库和连接它的'.o'文件(我也有源代码,但是对'static const char * *。* []'''grepping'只会让我到目前为止......)。 –