Использование Ульриха Дреппера relinfo.pl
сценарий, можно легко посчитать количество перемещений DSO, но это не работает на .o
файлы.
Скажем, у меня есть большая общая библиотека, и я не доволен количеством ее перемещений. Есть ли способ узнать, откуда они берутся (символ или, по крайней мере, .o
), чтобы проверить, легко ли их исправить (например, const char * str = "Hello World";'
-> const char str[] = "Hello World";
)?
Краткий ответ: Использование 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 перемещений из примера кода, но я бы посчитал это ошибкой.
Для кода вы хочу перемещения, так как косвенные вызовы функций (вызовы через указатели функций) намного медленнее, чем прямые вызовы. Проще говоря, эти перемещения значительно ускоряют вызовы функций и подпрограмм, поэтому вы наверняка захотите их сохранить.
Извиняюсь за длинный ответ; Надеюсь, вы найдете это полезным. Вопросы?
Основываясь на ответе 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
с исправить …