(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 = 1f2виклику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команду компіляції.