Was bedeutet <Wert optimiert aus> in GDB?


77
(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?


1
Mögliches Duplikat des GDB-Verhaltens: Wert optimiert
Benutzer

Sind a, b und c Zeiger?
Darshan L

Antworten:


74

gcc -O3Dies 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 -O0wenn Sie solche Variablen sehen möchten (dies ist in der Regel in jedem Fall eine gute Idee für Debug-Builds).


1
Aber hier aist das nicht redundant, es muss später verwendet werden ..177 case 3 : a+=k[0]&0xffffff; break;
GDB

3
Sie müssen den gesamten relevanten Code veröffentlichen, wenn Sie weitere Analysen wünschen.
Paul R

1
Der Optimierer speichert temporäre Variablen nach Möglichkeit in Registern. Es kann auch mehrere Variablen auf dasselbe Register aliasen, wenn sie alle denselben Wert haben, bis zu einem Punkt, an dem eine von ihnen geändert wird. An diesem Punkt kann sie dann einem anderen Register zugewiesen werden. Daher kann die Lebensdauer Ihrer Variablen im optimierten Code anders sein als im Quellcode. Deaktivieren Sie die Optimierung, wenn Sie durch diese Art von Verhalten nicht verwirrt werden möchten.
Paul R

2
Neuere gccs haben eine Option. -OgEs werden nur die Optimierungen angewendet, die die Debugbarkeit nicht beeinträchtigen - sehr nützlich (auch man gccfü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
kavadias

3
@kavadias, die -OgOption 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 !
Gabriel Staples

6

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:

  • immer zu Beginn der Funktion definiert
  • wird möglicherweise gegen Ende der Funktion nicht verwendet, da mehr Zwischenwerte berechnet werden.
  • neigen dazu, durch weitere Funktionsunteraufrufe überschrieben zu werden, die genau dieselben Register einrichten müssen, um die aufrufende Konvention zu erfüllen

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:

  • RDI enthält das erste Argument
  • RDI kann bei Funktionsaufrufen zerstört werden
  • RAX enthält den Rückgabewert

Daraus schließen wir:

add    $0x1,%edi

entspricht dem:

i += 1;

da iist das erste Argument von f1und 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 f2Anruf getätigt wird:

  • Der Wert von iwird im Programm nicht mehr benötigt
  • lea 0x1(%rax),%editut EDI = j + RAX + 1, was beide:
    • initialisiert j = 1
    • Legt das erste Argument des nächsten f2Aufrufs 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 ( f2möglicherweise wird es als Scratch-Register verwendet und leadefinitiv 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 ivöllig verloren! Daher ist das einzig mögliche Ergebnis:

$3 = <optimized out>

Ähnliches passiert mit dem Wert von j, obwohl er jerst eine Zeile später nach dem Aufruf unnötig wird k += f2(j);.

Wenn jwir darüber nachdenken, erhalten wir auch einen Einblick, wie intelligent GDB ist. Insbesondere war i += 1;der Wert von jnoch in keinem Register oder in keiner Speicheradresse angegeben, und GDB muss ihn ausschließlich anhand von Debug-Informationsmetadaten gekannt haben.

-O0 Analyse

Wenn wir -O0statt -O3fü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, jwenn 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 + iwie 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 ibis zum Ende der Funktion effektiv erweitert .

Dies liegt daran, dass wir GCC dazu zwingen, eine zusätzliche Variable zu verwenden, ium 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 += ibeim ersten Befehl in RDX speichert .

Getestet in Ubuntu 18.04, GCC 7.4.0, GDB 8.1.0.



4

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.


1

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;


-1

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.


AFAICT COPTSist keine Umgebungsvariable, die gccakzeptiert, vorausgesetzt, sie gccwird verwendet.
Sappjw

Vergessen Sie nicht, $COPTSan Ihren Kompilierungsbefehl anzuhängen .
Akib Azmain
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.