2

Ulrich Drepper のスクリプトを使用すると、DSO の再配置の数を簡単に数えることができますが、ファイルrelinfo.plでは機能しません。.o

大規模な共有ライブラリがあり、その再配置の数に満足していないとします。それらがどこから来たのか (シンボル、または少なくとも ) を見つけて、簡単に修正できるタイプ (例: -> ).oであるかどうかを確認する方法はありますか?const char * str = "Hello World";'const char str[] = "Hello World";

4

2 に答える 2

12

簡単な答え:代わりにobjdumporを使用してください。readelf

長い答え: 実際の例を見てみましょう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、実行可能ファイル (および動的オブジェクト ファイル) の同じ情報をダンプしたりできます。使用する

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の下の で説明されています。-t2 番目の「列」は実際には固定幅であることに注意してください。幅は 7 文字で、オブジェクトの型を記述します。3 番目の列は、*UND*未定義、.textコード、.rodata読み取り専用 (不変) データ、.data初期化された可変データ、初期化されていない可変データなどのセクション名.bssです。

上記のシンボル テーブルからlocal4local5local6local7local8、およびlocal9変数が実際にはシンボル テーブルのエントリをまったく取得していないことがわかります。これは、それらが に対してローカルであるためmain()です。.dataそれらが参照する文字列の内容は、コンパイラが最適と見なすものに応じて、 orに格納されます.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.rodatachar global7[] = "char []";.data.rodata

デバッグ シンボルを有効にしてコードをコンパイルすると、どの変数がどの文字列を参照するために使用されたかを判断しやすくなりますが、各再配置値 (ターゲット) の実際の内容を調べて、どの参照が不変の文字列を修正する必要があります。

コマンドの組み合わせ

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

target ごとの再配置の数、その後にターゲットセクション、セクション内のターゲットオフセットが続き、最後に再配置で最も多く発生するターゲットでソートされて出力されます。つまり、上記の出力の最後の行は、集中する必要がある行です。私にとって、私は得る

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 回使用される 1 つのターゲットがあることを示しています: セクション.rodataオフセット0xb0(コンパイル時に最適化が有効になっている場合はオフセット) .rodata.str1.10x8

それが `"\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 chars ( ) の配列または const chars ( ) への const ポインターに置き換えるconst char *const fmt = "\t%s\n";- どちらの場合も、コンテンツを.rodataセクションに配置し、読み取り専用にし、ポインター/配列参照自体も不変です - 効果的で安全なようです私への戦略。

さらに、文字列リテラルから不変の文字列ポインターまたは char 配列への変換は、完全にソースレベルのタスクです。つまり、上記の方法を使用してすべての文字列リテラルを変換すると、文字列リテラルごとに少なくとも 1 つの再配置をなくすことができます。

実際、ここでは、オブジェクト レベルの分析がどのように役立つかわかりません。もちろん、変更によって必要な再配置の数が減るかどうかがわかります。

上記の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()最後に、直接呼び出す代わりに関数ポインターを使用すると、サンプル コードからさらに 18 個の再配置が削減されることに気付くかもしれませんがprintf()、それは間違いだと思います。

コードの場合、間接関数呼び出し (関数ポインターを介した呼び出し) は直接呼び出しよりもはるかに遅いため、再配置が必要です。簡単に言えば、これらの再配置により、関数とサブルーチンの呼び出しがはるかに高速になるため、それらを保持することは間違いありません。

長い回答で申し訳ありません。これが役に立つことを願っています。質問?

于 2013-09-29T02:41:37.270 に答える