2013-09-28 3 views
1

Используя скрипт Ulrich Drepper's relinfo.pl, можно легко подсчитать количество перемещений DSO, но оно не работает на .o файлах.Найти, где происходят перемещения,

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

+0

Что вы хотите узнать: объектный файл, который требует символ? Или объектный файл или разделяемая библиотека, содержащая символ? У вас много объектных файлов или у вас есть одна общая библиотека? –

+0

@MartinRosenau: Объектный файл, содержащий символ. И у меня есть разделяемая библиотека и файлы .o', с которой она была связана (у меня также есть источник, но grepping для 'static const char \ *. * []' Только доводит меня до сих пор ...). –

ответ

12

Короткий ответ: вместо этого используйте 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 

на 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* для undefined, .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.)

Если мы должны были скомпилировать код с включенными символами отладки, было бы легче определить, какая переменная использовалась для ссылки на какую строку, но я могу просто посмотреть фактическое содержимое при каждом значении перемещения (цели) чтобы посмотреть, какие ссылки на неизменяемые строки нуждаются в фиксации.

Команда Сочетание

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 chars (const char fmt[] = "\t%s\n";) или указателем константы на const chars (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 перемещений из кода примера, но я считаю это ошибкой.

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

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

2

На основании ответа 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 

Глядя эти символы в исходном файле, как правило, приводит к быстрому исправить, либо к открытию, что они не так легко поправимо.

Но я предполагаю, что я должен вернуться ответ NOMINAL Animal однажды кончатся .data.rel.ro.local с, чтобы исправить ...

 Смежные вопросы

  • Нет связанных вопросов^_^