寻找搬迁的起源

Finding where relocations originate

本文关键字:起源 寻找      更新时间:2023-10-16

使用 Ulrich Drepper 的relinfo.pl脚本,可以轻松计算 DSO 的重定位次数,但它不适用于.o文件。

假设我有一个大型共享图书馆,我对它的搬迁数量不满意。 有没有办法找出它们的来源(符号,或至少.o(,以检查它们是否属于易于修复的类型(例如:const char * str = "Hello World";' -> const char str[] = "Hello World"; (?

简短回答:改用objdumpreadelf

长答案:让我们看一个实际的例子,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%sn", global1);
    printf("t%sn", global2);
    printf("t%sn", global3);
    printf("t%sn", global4);
    printf("t%sn", global5);
    printf("t%sn", global6);
    printf("t%sn", global7);
    printf("t%sn", global8);
    printf("t%sn", global9);
    printf("n");
    printf("Local:n");
    printf("t%sn", local1);
    printf("t%sn", local2);
    printf("t%sn", local3);
    printf("t%sn", local4);
    printf("t%sn", local5);
    printf("t%sn", local6);
    printf("t%sn", local7);
    printf("t%sn", local8);
    printf("t%sn", local9);
    return 0;
}

您可以将其编译为目标文件

,例如
gcc -W -Wall -c example.c

以及使用

gcc -W -Wall example.c -o example

可以使用objdump -tr example.o转储(非动态(对象文件的符号和重定位信息,也可以objdump -TtRr example转储可执行文件(和动态对象文件(的符号和重定位信息。用

objdump -t example.o

在 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标题下描述。请注意,第二个"列"实际上是固定宽度的:七个字符宽,描述对象的类型。第三列是节名称,*UND* 表示未定义,.text 表示代码,.rodata 列表示只读(不可变(数据,.data 列表示初始化的可变数据,.bss用于未初始化的可变数据,依此类推。

从上面的符号表中我们可以看到,local4local5local6local7local8local9变量实际上根本没有在符号表中获取条目。这是因为它们是本地的 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中。

如果我们要在启用调试符号的情况下编译代码,则更容易确定使用哪个变量来引用哪个字符串,但我可能只查看每个重定位值(目标(的实际内容,以查看哪些对不可变字符串的引用需要修复。

命令组合

objdump -r example.o | awk '($3 ~ /^..*+/) { t = $3; sub(/+/, " ", t); n[t]++ } END { for (r in n) printf "%d %sn", 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(如果在编译时启用了优化,则偏移量0x8 .rodata.str1.1(。

这就是"\t%s"字符串文字。

将原始程序修改为

    char *local8 = "char *";
    char *const local9 = "char *const";
    const char *const fmt = "t%sn";
    printf("Global:n");
    printf(fmt, global1);
    printf(fmt, global2);

依此类推,将格式字符串替换为不可变的字符串指针fmt,完全消除了这 18 次重定位。(当然,您也可以使用等效的const char fmt[] = "t%sn";

上面的分析表明,至少在GCC-4.6.3中,大多数可避免的重定位是由(重复使用(字符串文字引起的。用常量字符数组(const char fmt[] = "t%sn";(或指向常量字符的常量指针(const char *const fmt = "t%sn";(替换它们 - 这两种情况都将内容放在.rodata节中,只读,并且指针/数组引用本身也是不可变的 - 对我来说似乎是一种有效和安全的策略。

此外,将字符串文本转换为不可变的字符串指针或 char 数组完全是源代码级任务。也就是说,如果使用上述方法转换所有字符串文本,则可以消除每个字符串文本至少一次重定位。

事实上,我看不出对象级分析对你有多大帮助。当然,它会告诉您您的修改是否减少了所需的搬迁次数。

上述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 = "[tvf ]*[rn][tnvfr ]*"
        FS = "[tvf ]+"
    }
    $1 ~ /^[0-9A-Fa-f]+/ {
        n[$3]++
    }
    END {
        for (s in n)
            printf "%d %sn", n[s], s
    }
' | sort -g | gawk -v filename="$1" '
    BEGIN {
        RS = "[tvf ]*[rn][tnvfr ]*"
        FS = "[tvf ]+"
        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 = ""
            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-x1Fx7F-x9FxFExFF]/ || length(hex) < 1)
                printf "%sn", $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%sn"
18 printf-0x0000000000000004

最后,您可能会注意到,使用指向 printf() 的函数指针而不是直接调用 printf() 将减少示例代码中的另外 18 次重定位,但我认为这是一个错误。

对于代码,您需要重新定位,因为间接函数调用(通过函数指针调用(比直接调用慢得多。简而言之,这些重定位使函数和子例程调用更快,因此您绝对希望保留它们。

冗长的答案表示歉意;希望你觉得这有用。问题?

基于Nomainal Animals的答案,我仍然需要完全消化,我想出了以下简单的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来修复,我将不得不重新审视名义动物的答案......