(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
Wie kann gdb meinen Wert optimieren?
(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
Wie kann gdb meinen Wert optimieren?
Antworten:
gcc -O3
Dies bedeutet, dass Sie mit eg kompiliert haben und der gcc-Optimierer festgestellt hat, dass einige Ihrer Variablen auf irgendeine Weise redundant waren, sodass sie wegoptimiert werden konnten. In diesem speziellen Fall scheinen Sie drei Variablen a, b, c mit demselben Wert zu haben, und vermutlich können sie alle zu einer einzigen Variablen zusammengefasst werden. Kompilieren Sie mit deaktivierter Optimierung, z. B. gcc -O0
wenn Sie solche Variablen sehen möchten (dies ist in der Regel in jedem Fall eine gute Idee für Debug-Builds).
a
ist das nicht redundant, es muss später verwendet werden ..177 case 3 : a+=k[0]&0xffffff; break;
-Og
Es werden nur die Optimierungen angewendet, die die Debugbarkeit nicht beeinträchtigen - sehr nützlich (auch man gcc
für -gdwarf4
). Sie können die Variable, die Sie nicht verlieren möchten, auch vorübergehend als definieren volatile
, wenn Sie keine Compiler-Optimierungen wünschen, aber die Optimierung nicht für den gesamten Build deaktivieren möchten! Beide Informationen von hier: ask.xmodulo.com/print-optimized-out-value-gdb.html
-Og
Option könnte genau das Problem sein, das dazu führt, dass Variablen optimiert werden! Siehe meine Antwort hier: stackoverflow.com/a/63386263/4561887 . Also, wenn Sie irgendwelche Fehler haben, die sagen <optimized out>
oder Can't take address of "var" which isn't an lvalue.
, dann müssen Sie -O0
anstelle von verwenden -Og
!
Minimal lauffähiges Beispiel mit Demontageanalyse
Wie immer sehe ich gerne eine Demontage, um besser zu verstehen, was los ist.
In diesem Fall erhalten wir die Erkenntnis, dass, wenn eine Variable so optimiert ist, dass sie nur in einem Register und nicht im Stapel gespeichert wird , und das Register, in dem sie sich befand, überschrieben wird, dies <optimized out>
wie von R. erwähnt angezeigt wird .
Dies kann natürlich nur passieren, wenn die betreffende Variable nicht mehr benötigt wird, sonst würde das Programm seinen Wert verlieren. Daher kann es vorkommen, dass Sie am Anfang der Funktion den Variablenwert sehen können, am Ende jedoch <optimized out>
.
Ein typischer Fall, an dem wir oft interessiert sind, ist der der Funktionsargumente selbst, da dies sind:
Dieses Verständnis hat tatsächlich eine konkrete Anwendung: Wenn Sie das Reverse-Debugging verwenden , können Sie möglicherweise den Wert von interessierenden Variablen wiederherstellen, indem Sie einfach zu ihrem letzten Verwendungspunkt zurückkehren: Wie kann ich den Wert einer <optimierten> -Variablen in anzeigen? C ++?
Haupt 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;
}
Kompilieren und ausführen:
gcc -ggdb3 -O3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
gdb -q -nh main.out
Dann haben wir in GDB die folgende Sitzung:
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>
Um zu verstehen, was vor sich geht, beachten Sie die x86 Linux-Aufrufkonvention: Welche Aufrufkonventionen für UNIX- und Linux-Systemaufrufe auf i386 und x86-64 gelten , sollten Sie Folgendes wissen:
Daraus schließen wir:
add $0x1,%edi
entspricht dem:
i += 1;
da i
ist das erste Argument von f1
und daher in RDI gespeichert.
Nun, während wir bei beiden waren:
i += 1;
j += f2(i);
Der Wert von RDI wurde nicht geändert, und daher konnte GDB ihn jederzeit in diesen Zeilen abfragen.
Sobald der f2
Anruf getätigt wird:
i
wird im Programm nicht mehr benötigtlea 0x1(%rax),%edi
tut EDI = j + RAX + 1
, was beide:
j = 1
f2
Aufrufs von festRDI = j
Daher, wenn die folgende Zeile erreicht ist:
k += f2(j);
Beide der folgenden Anweisungen haben / haben möglicherweise RDI geändert. Dies ist der einzige Ort i
, an dem gespeichert wurde ( f2
möglicherweise wird es als Scratch-Register verwendet und lea
definitiv auf RAX + 1 gesetzt):
0x00005555555546c3 <+3>: callq 0x5555555546b0 <f2>
0x00005555555546c8 <+8>: lea 0x1(%rax),%edi
und so enthält RDI nicht mehr den Wert von i
. In der Tat war der Wert von i
völlig verloren! Daher ist das einzig mögliche Ergebnis:
$3 = <optimized out>
Ähnliches passiert mit dem Wert von j
, obwohl er j
erst eine Zeile später nach dem Aufruf unnötig wird k += f2(j);
.
Wenn j
wir darüber nachdenken, erhalten wir auch einen Einblick, wie intelligent GDB ist. Insbesondere war i += 1;
der Wert von j
noch in keinem Register oder in keiner Speicheradresse angegeben, und GDB muss ihn ausschließlich anhand von Debug-Informationsmetadaten gekannt haben.
-O0
Analyse
Wenn wir -O0
statt -O3
für die Kompilierung verwenden:
gcc -ggdb3 -O0 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
dann würde die Demontage wie folgt aussehen:
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
Aus dieser schrecklichen Demontage geht hervor, dass der Wert von RDI gleich zu Beginn der Programmausführung auf den Stapel verschoben wird:
mov %edi,-0x14(%rbp)
und es wird dann bei Bedarf aus dem Speicher in Register abgerufen, z.
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)
Das gleiche passiert im Grunde genommen, j
wenn es bei der Initialisierung sofort auf den Stapel verschoben wird:
0x000055555555467e <+11>: c7 45 f4 01 00 00 00 movl $0x1,-0xc(%rbp)
Daher ist es für GDB jederzeit einfach, die Werte dieser Variablen zu finden: Sie sind immer im Speicher vorhanden!
Dies gibt uns auch einen Einblick, warum es nicht möglich ist, <optimized out>
optimierten Code zu vermeiden : Da die Anzahl der Register begrenzt ist, besteht die einzige Möglichkeit darin, nicht benötigte Register tatsächlich in den Speicher zu verschieben, was den Vorteil von teilweise zunichte machen würde -O3
.
Verlängern Sie die Lebensdauer von i
Wenn wir bearbeitet haben f1
, um l + i
wie folgt zurückzukehren :
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;
}
dann stellen wir fest, dass dies die Sichtbarkeit i
bis zum Ende der Funktion effektiv erweitert .
Dies liegt daran, dass wir GCC dazu zwingen, eine zusätzliche Variable zu verwenden, i
um bis zum Ende zu bleiben :
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
Dies tut der Compiler, indem er i += i
beim ersten Befehl in RDX speichert .
Getestet in Ubuntu 18.04, GCC 7.4.0, GDB 8.1.0.
Es war nicht so. Ihr Compiler hat dies getan, aber es gibt immer noch ein Debug-Symbol für den ursprünglichen Variablennamen.
Von https://idlebox.net/2010/apidocs/gdb-7.0.zip/gdb_9.html
Die Werte von Argumenten, die nicht in ihren Stapelrahmen gespeichert wurden, werden als "Wert optimiert" angezeigt.
Ich vermute, Sie haben mit -O (ein Wert) kompiliert und greifen in einer Funktion, in der eine Optimierung stattgefunden hat, auf die Variablen a, b, c zu.
Sie müssen die Compileroptimierung deaktivieren.
Wenn Sie an einer bestimmten Variablen in gdb interessiert sind, können Sie die Variable als "flüchtig" löschen und den Code neu kompilieren. Dadurch deaktiviert der Compiler die Compileroptimierung für diese Variable.
flüchtige int Menge = 0;
Führen Sie einfach "export COPTS = '- g -O0';" und erstellen Sie Ihren Code neu. Debuggen Sie es nach dem Wiederherstellen mit gdb. Sie werden einen solchen Fehler nicht sehen. Vielen Dank.
COPTS
ist keine Umgebungsvariable, die gcc
akzeptiert, vorausgesetzt, sie gcc
wird verwendet.
$COPTS
an Ihren Kompilierungsbefehl anzuhängen .