Що означає <значення оптимізовано> у gdb?


80
(gdb) n
134   a = b = c = 0xdeadbeef + ((uint32_t)length) + initval;
(gdb) n
(gdb) p a
$30 = <value optimized out>
(gdb) p b
$31 = <value optimized out>
(gdb) p c
$32 = 3735928563

Як gdb може оптимізувати моє значення ??



Чи є a, b та c покажчиками ??
Darshan L

Відповіді:


76

Це означає, що ви скомпілювали з eg, gcc -O3і оптимізатор gcc виявив, що деякі ваші змінні якимось чином були зайвими, що дозволило їх оптимізувати. У цьому конкретному випадку у вас, здається, є три змінні a, b, c з однаковим значенням, імовірно, всі вони можуть бути псевдонімами до однієї змінної. Компілюйте з вимкненою оптимізацією, наприклад gcc -O0, якщо ви хочете бачити такі змінні (це, як правило, хороша ідея для налагоджувальних збірок у будь-якому випадку).


1
Але тут aце не зайве, його потрібно використовувати пізніше ..177 case 3 : a+=k[0]&0xffffff; break;
gdb

3
Вам потрібно розмістити весь відповідний код, якщо ви хочете подальший аналіз.
Paul R

1
Оптимізатор буде зберігати тимчасові змінні в реєстрах, де це можливо. Він також може псевдонім декількох змінних до одного і того ж реєстру, якщо всі вони мають одне і те ж значення, до моменту, коли одна з них буде модифікована, після чого вона може бути призначена іншому реєстру. Отже, термін служби ваших змінних в оптимізованому коді може відрізнятися від того, який він відображається у вихідному коді. Вимкніть оптимізацію, якщо не хочете заплутатися такою поведінкою.
Paul R

2
У нових версіях gcc є можливість -Ogзастосовувати лише ті оптимізації, які не погіршують налагодження - дуже корисно (також man gccдля -gdwarf4). Крім того, ви можете тимчасово визначити змінну, яку ви не хочете втратити volatile, якщо ви не хочете оптимізації компілятора щодо неї, але не хочете вимикати оптимізацію для всієї збірки! Обидві інформації звідси: ask.xmodulo.com/print-optimized-out-value-gdb.html
kavadias

3
@kavadias, -Ogваріант може бути саме тією проблемою, яка спричиняє оптимізацію змінних! Дивіться мою відповідь тут: stackoverflow.com/a/63386263/4561887 . Отже, якщо у вас є якісь помилки, які говорять <optimized out>або Can't take address of "var" which isn't an lvalue., тоді ви повинні використовувати -O0 замість -Og !
Габріель Стейплз

6

Приклад мінімального запуску з аналізом розбирання

Як зазвичай, я люблю розбирати, щоб краще зрозуміти, що відбувається.

У цьому випадку ми отримуємо розуміння того, що якщо змінна оптимізована для зберігання лише в регістрі, а не в стеку , а потім реєстр, в якому вона перебувала, перезаписується, тоді це відображається, <optimized out>як згадано R. .

Звичайно, це може статися, лише якщо дана змінна більше не потрібна, інакше програма втратить своє значення. Тому трапляється так, що на початку функції ви можете бачити значення змінної, але в кінці воно стає<optimized out> .

Типовим випадком, який нас часто цікавить, є сам аргумент функції, оскільки це:

  • завжди визначається на початку функції
  • може не звикнути до кінця функції, оскільки обчислюється більше проміжних значень.
  • як правило, перезаписуються подальшими функційними викликами, які повинні встановлювати однакові регістри, щоб задовольнити умову виклику

Це розуміння насправді має конкретне застосування: використовуючи зворотну налагодження , ви зможете відновити значення змінних, що цікавлять, просто повернувшись до останньої точки використання: Як мені переглянути значення змінної <optimized out> у C ++?

main.c

#include <stdio.h>

int __attribute__((noinline)) f3(int i) {
    return i + 1;
}

int __attribute__((noinline)) f2(int i) {
    return f3(i) + 1;
}

int __attribute__((noinline)) f1(int i) {
    int j = 1, k = 2, l = 3;
    i += 1;
    j += f2(i);
    k += f2(j);
    l += f2(k);
    return l;
}

int main(int argc, char *argv[]) {
    printf("%d\n", f1(argc));
    return 0;
}

Скомпілюйте та запустіть:

gcc -ggdb3 -O3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
gdb -q -nh main.out

Тоді всередині GDB ми маємо наступний сеанс:

Breakpoint 1, f1 (i=1) at main.c:13
13          i += 1;
(gdb) disas
Dump of assembler code for function f1:
=> 0x00005555555546c0 <+0>:     add    $0x1,%edi
   0x00005555555546c3 <+3>:     callq  0x5555555546b0 <f2>
   0x00005555555546c8 <+8>:     lea    0x1(%rax),%edi
   0x00005555555546cb <+11>:    callq  0x5555555546b0 <f2>
   0x00005555555546d0 <+16>:    lea    0x2(%rax),%edi
   0x00005555555546d3 <+19>:    callq  0x5555555546b0 <f2>
   0x00005555555546d8 <+24>:    add    $0x3,%eax
   0x00005555555546db <+27>:    retq   
End of assembler dump.
(gdb) p i
$1 = 1
(gdb) p j
$2 = 1
(gdb) n
14          j += f2(i);
(gdb) disas
Dump of assembler code for function f1:
   0x00005555555546c0 <+0>:     add    $0x1,%edi
=> 0x00005555555546c3 <+3>:     callq  0x5555555546b0 <f2>
   0x00005555555546c8 <+8>:     lea    0x1(%rax),%edi
   0x00005555555546cb <+11>:    callq  0x5555555546b0 <f2>
   0x00005555555546d0 <+16>:    lea    0x2(%rax),%edi
   0x00005555555546d3 <+19>:    callq  0x5555555546b0 <f2>
   0x00005555555546d8 <+24>:    add    $0x3,%eax
   0x00005555555546db <+27>:    retq   
End of assembler dump.
(gdb) p i
$3 = 2
(gdb) p j
$4 = 1
(gdb) n
15          k += f2(j);
(gdb) disas
Dump of assembler code for function f1:
   0x00005555555546c0 <+0>:     add    $0x1,%edi
   0x00005555555546c3 <+3>:     callq  0x5555555546b0 <f2>
   0x00005555555546c8 <+8>:     lea    0x1(%rax),%edi
=> 0x00005555555546cb <+11>:    callq  0x5555555546b0 <f2>
   0x00005555555546d0 <+16>:    lea    0x2(%rax),%edi
   0x00005555555546d3 <+19>:    callq  0x5555555546b0 <f2>
   0x00005555555546d8 <+24>:    add    $0x3,%eax
   0x00005555555546db <+27>:    retq   
End of assembler dump.
(gdb) p i
$5 = <optimized out>
(gdb) p j
$6 = 5
(gdb) n
16          l += f2(k);
(gdb) disas
Dump of assembler code for function f1:
   0x00005555555546c0 <+0>:     add    $0x1,%edi
   0x00005555555546c3 <+3>:     callq  0x5555555546b0 <f2>
   0x00005555555546c8 <+8>:     lea    0x1(%rax),%edi
   0x00005555555546cb <+11>:    callq  0x5555555546b0 <f2>
   0x00005555555546d0 <+16>:    lea    0x2(%rax),%edi
=> 0x00005555555546d3 <+19>:    callq  0x5555555546b0 <f2>
   0x00005555555546d8 <+24>:    add    $0x3,%eax
   0x00005555555546db <+27>:    retq   
End of assembler dump.
(gdb) p i
$7 = <optimized out>
(gdb) p j
$8 = <optimized out>

Щоб зрозуміти, що відбувається, пам’ятайте зі згоди x86 Linux щодо викликів: які правила викликів для системних викликів UNIX та Linux на i386 та x86-64 ви повинні знати, що:

  • RDI містить перший аргумент
  • RDI може бути знищений під час викликів функцій
  • RAX містить повернене значення

З цього випливає, що:

add    $0x1,%edi

відповідає:

i += 1;

оскільки iє першим аргументом f1, і тому зберігається в RDI.

Тепер, поки ми були в обох:

i += 1;
j += f2(i);

значення RDI не було змінено, і тому GDB може просто запитувати його в будь-який час у цих рядках.

Однак, як тільки f2дзвінок зроблено:

  • значення програми iбільше не потрібно
  • lea 0x1(%rax),%ediробить EDI = j + RAX + 1, що обидва:
    • ініціалізується j = 1
    • встановлює перший аргумент наступного f2викликуRDI = j

Отже, коли досягається такий рядок:

k += f2(j);

обидві наведені нижче інструкції мають / можуть змінити RDI, який є єдиним місцем iзберігання ( f2може використовувати його як регістр подряпин і, leaбезумовно, встановити на RAX + 1):

   0x00005555555546c3 <+3>:     callq  0x5555555546b0 <f2>
   0x00005555555546c8 <+8>:     lea    0x1(%rax),%edi

і тому RDI більше не містить значення i. Насправді цінність iбуло повністю втрачено! Тому єдино можливим результатом є:

$3 = <optimized out>

Подібне трапляється зі значенням j, хоча jлише стає непотрібним на один рядок пізніше після дзвінка k += f2(j);.

Роздуми jтакож дають нам деяке розуміння того, наскільки розумним є GDB. Варто відзначити, що i += 1;, значення, jще не втілилося в жодному регістрі чи адресі пам'яті, і GDB, мабуть, знав це на основі виключно метаданих інформації про налагодження.

-O0 аналіз

Якщо ми використовуємо -O0замість -O3для компіляції:

gcc -ggdb3 -O0 -std=c99 -Wall -Wextra -pedantic -o main.out main.c

тоді розбирання буде виглядати так:

11      int __attribute__((noinline)) f1(int i) {
=> 0x0000555555554673 <+0>:     55      push   %rbp
   0x0000555555554674 <+1>:     48 89 e5        mov    %rsp,%rbp
   0x0000555555554677 <+4>:     48 83 ec 18     sub    $0x18,%rsp
   0x000055555555467b <+8>:     89 7d ec        mov    %edi,-0x14(%rbp)

12          int j = 1, k = 2, l = 3;
   0x000055555555467e <+11>:    c7 45 f4 01 00 00 00    movl   $0x1,-0xc(%rbp)
   0x0000555555554685 <+18>:    c7 45 f8 02 00 00 00    movl   $0x2,-0x8(%rbp)
   0x000055555555468c <+25>:    c7 45 fc 03 00 00 00    movl   $0x3,-0x4(%rbp)

13          i += 1;
   0x0000555555554693 <+32>:    83 45 ec 01     addl   $0x1,-0x14(%rbp)

14          j += f2(i);
   0x0000555555554697 <+36>:    8b 45 ec        mov    -0x14(%rbp),%eax
   0x000055555555469a <+39>:    89 c7   mov    %eax,%edi
   0x000055555555469c <+41>:    e8 b8 ff ff ff  callq  0x555555554659 <f2>
   0x00005555555546a1 <+46>:    01 45 f4        add    %eax,-0xc(%rbp)

15          k += f2(j);
   0x00005555555546a4 <+49>:    8b 45 f4        mov    -0xc(%rbp),%eax
   0x00005555555546a7 <+52>:    89 c7   mov    %eax,%edi
   0x00005555555546a9 <+54>:    e8 ab ff ff ff  callq  0x555555554659 <f2>
   0x00005555555546ae <+59>:    01 45 f8        add    %eax,-0x8(%rbp)

16          l += f2(k);
   0x00005555555546b1 <+62>:    8b 45 f8        mov    -0x8(%rbp),%eax
   0x00005555555546b4 <+65>:    89 c7   mov    %eax,%edi
   0x00005555555546b6 <+67>:    e8 9e ff ff ff  callq  0x555555554659 <f2>
   0x00005555555546bb <+72>:    01 45 fc        add    %eax,-0x4(%rbp)

17          return l;
   0x00005555555546be <+75>:    8b 45 fc        mov    -0x4(%rbp),%eax

18      }
   0x00005555555546c1 <+78>:    c9      leaveq 
   0x00005555555546c2 <+79>:    c3      retq 

З цієї жахливої ​​розбірки ми бачимо, що значення RDI переміщується в стек на самому початку виконання програми за адресою:

mov    %edi,-0x14(%rbp)

і він потім отримується з пам'яті у регістри, коли це потрібно, наприклад за адресою:

14          j += f2(i);
   0x0000555555554697 <+36>:    8b 45 ec        mov    -0x14(%rbp),%eax
   0x000055555555469a <+39>:    89 c7   mov    %eax,%edi
   0x000055555555469c <+41>:    e8 b8 ff ff ff  callq  0x555555554659 <f2>
   0x00005555555546a1 <+46>:    01 45 f4        add    %eax,-0xc(%rbp)

В основному трапляється те саме, jщо одразу ж виштовхується в стек, коли він ініціалізується:

   0x000055555555467e <+11>:    c7 45 f4 01 00 00 00    movl   $0x1,-0xc(%rbp)

Тому GDB легко знайти значення цих змінних у будь-який час: вони завжди присутні в пам'яті!

Це також дає нам деяке розуміння того, чому неможливо уникнути <optimized out>в оптимізованому коді: оскільки кількість регістрів обмежена, єдиним способом цього буде фактично виштовхування непотрібних регістрів до пам'яті, що частково переможе перевагу -O3.

Подовжити термін служби i

Якщо ми відредагували, f1щоб повернути l + iяк у:

int __attribute__((noinline)) f1(int i) {
    int j = 1, k = 2, l = 3;
    i += 1;
    j += f2(i);
    k += f2(j);
    l += f2(k);
    return l + i;
}

тоді ми спостерігаємо, що це ефективно розширює видимість iдо кінця функції.

Це тому, що цим ми змушуємо GCC використовувати додаткову змінну, щоб триматися iдо кінця:

   0x00005555555546c0 <+0>:     lea    0x1(%rdi),%edx
   0x00005555555546c3 <+3>:     mov    %edx,%edi
   0x00005555555546c5 <+5>:     callq  0x5555555546b0 <f2>
   0x00005555555546ca <+10>:    lea    0x1(%rax),%edi
   0x00005555555546cd <+13>:    callq  0x5555555546b0 <f2>
   0x00005555555546d2 <+18>:    lea    0x2(%rax),%edi
   0x00005555555546d5 <+21>:    callq  0x5555555546b0 <f2>
   0x00005555555546da <+26>:    lea    0x3(%rdx,%rax,1),%eax
   0x00005555555546de <+30>:    retq

що компілятор робить, зберігаючи i += iв RDX за найпершою інструкцією.

Перевірено в Ubuntu 18.04, GCC 7.4.0, GDB 8.1.0.



4

З https://idlebox.net/2010/apidocs/gdb-7.0.zip/gdb_9.html

Значення аргументів, які не були збережені в їх фреймах стека, відображаються як `значення оптимізовано '.

Я припускаю, що ви скомпілювали з -O (somevalue) і отримуєте доступ до змінних a, b, c у функції, де відбулася оптимізація.


1

Вам потрібно вимкнути оптимізацію компілятора.

Якщо вас цікавить певна змінна в gdb, ви можете відкласти змінну як "мінливу" та перекомпілювати код. Це змусить компілятор вимкнути оптимізацію компілятора для цієї змінної.

летюча кількість int = 0;


-1

Просто запустіть "експортувати COPTS = '- g -O0';" і відновіть свій код. Після відновлення налагодьте його за допомогою gdb. Ви не побачите такої помилки. Дякую.


AFAICT COPTSне є змінною середовища, яка gccприймає, припускаючи gcc, що використовується.
sappjw

Не забудьте додати $COPTSкоманду компіляції.
Акіб Азмайн,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.