Нахождение источника перемещения

Использование Ульриха Дреппера relinfo.pl сценарий, можно легко посчитать количество перемещений DSO, но это не работает на .o файлы.

Скажем, у меня есть большая общая библиотека, и я не доволен количеством ее перемещений. Есть ли способ узнать, откуда они берутся (символ или, по крайней мере, .o), чтобы проверить, легко ли их исправить (например, const char * str = "Hello World";' -> const char str[] = "Hello World";)?

1

Решение

Краткий ответ: Использование objdump или же 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

на х86-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 для неинициализированных изменяемых данных и т. д.

Из приведенной таблицы символов видно, что 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-0x0000000000000004RELOCATION 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+0x0000000000000015RELOCATION 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+0x0000000000000029RELOCATION 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 %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 section, только для чтения, и сама ссылка на указатель / массив тоже неизменна — мне кажется эффективной и безопасной стратегией.

Кроме того, преобразование строковых литералов в неизменяемые строковые указатели или массивы символов полностью является задачей исходного уровня. То есть, если вы преобразуете все строковые литералы с использованием вышеуказанного метода, вы можете исключить как минимум одно перемещение для каждого строкового литерала.

На самом деле, я не вижу, как анализ на уровне объектов поможет вам здесь. Конечно, он сообщит вам, уменьшат ли ваши изменения количество необходимых перемещений.

Выше 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 перемещений из примера кода, но я бы посчитал это ошибкой.

Для кода вы хочу перемещения, так как косвенные вызовы функций (вызовы через указатели функций) намного медленнее, чем прямые вызовы. Проще говоря, эти перемещения значительно ускоряют вызовы функций и подпрограмм, поэтому вы наверняка захотите их сохранить.

Извиняюсь за длинный ответ; Надеюсь, вы найдете это полезным. Вопросы?

12

Другие решения

Основываясь на ответе Nomainal Animals, который мне еще предстоит полностью переварить, я пришел к следующему простому сценарию оболочки, который, кажется, работает для нахождения того, что я назвал «легко исправимым» разнообразием:

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с исправить …

2

По вопросам рекламы [email protected]