(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 може оптимізувати моє значення ??
(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 може оптимізувати моє значення ??
Відповіді:
Це означає, що ви скомпілювали з eg, gcc -O3
і оптимізатор gcc виявив, що деякі ваші змінні якимось чином були зайвими, що дозволило їх оптимізувати. У цьому конкретному випадку у вас, здається, є три змінні a, b, c з однаковим значенням, імовірно, всі вони можуть бути псевдонімами до однієї змінної. Компілюйте з вимкненою оптимізацією, наприклад gcc -O0
, якщо ви хочете бачити такі змінні (це, як правило, хороша ідея для налагоджувальних збірок у будь-якому випадку).
a
це не зайве, його потрібно використовувати пізніше ..177 case 3 : a+=k[0]&0xffffff; break;
-Og
застосовувати лише ті оптимізації, які не погіршують налагодження - дуже корисно (також man gcc
для -gdwarf4
). Крім того, ви можете тимчасово визначити змінну, яку ви не хочете втратити volatile
, якщо ви не хочете оптимізації компілятора щодо неї, але не хочете вимикати оптимізацію для всієї збірки! Обидві інформації звідси: ask.xmodulo.com/print-optimized-out-value-gdb.html
-Og
варіант може бути саме тією проблемою, яка спричиняє оптимізацію змінних! Дивіться мою відповідь тут: stackoverflow.com/a/63386263/4561887 . Отже, якщо у вас є якісь помилки, які говорять <optimized out>
або Can't take address of "var" which isn't an lvalue.
, тоді ви повинні використовувати -O0
замість -Og
!
Приклад мінімального запуску з аналізом розбирання
Як зазвичай, я люблю розбирати, щоб краще зрозуміти, що відбувається.
У цьому випадку ми отримуємо розуміння того, що якщо змінна оптимізована для зберігання лише в регістрі, а не в стеку , а потім реєстр, в якому вона перебувала, перезаписується, тоді це відображається, <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 ви повинні знати, що:
З цього випливає, що:
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.
Це не так. Ваш компілятор це зробив, але все ще існує символ налагодження для оригінального імені змінної.
З https://idlebox.net/2010/apidocs/gdb-7.0.zip/gdb_9.html
Значення аргументів, які не були збережені в їх фреймах стека, відображаються як `значення оптимізовано '.
Я припускаю, що ви скомпілювали з -O (somevalue) і отримуєте доступ до змінних a, b, c у функції, де відбулася оптимізація.
Вам потрібно вимкнути оптимізацію компілятора.
Якщо вас цікавить певна змінна в gdb, ви можете відкласти змінну як "мінливу" та перекомпілювати код. Це змусить компілятор вимкнути оптимізацію компілятора для цієї змінної.
летюча кількість int = 0;
Просто запустіть "експортувати COPTS = '- g -O0';" і відновіть свій код. Після відновлення налагодьте його за допомогою gdb. Ви не побачите такої помилки. Дякую.
COPTS
не є змінною середовища, яка gcc
приймає, припускаючи gcc
, що використовується.
$COPTS
команду компіляції.