<Giá trị được tối ưu hóa> có nghĩa là gì trong gdb?


79
(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

Làm cách nào để gdb có thể tối ưu hóa giá trị của tôi ??



A, b và c có phải là con trỏ không ??
Darshan L

Câu trả lời:


75

Nó có nghĩa là bạn đã biên dịch với ví dụ gcc -O3và trình tối ưu hóa gcc nhận thấy rằng một số biến của bạn bị thừa theo một cách nào đó cho phép chúng được tối ưu hóa đi. Trong trường hợp cụ thể này, bạn dường như có ba biến a, b, c có cùng giá trị và có lẽ tất cả chúng đều có thể được gán cho một biến duy nhất. Biên dịch với tính năng tối ưu hóa bị vô hiệu hóa, ví dụ gcc -O0, nếu bạn muốn xem các biến như vậy (đây thường là một ý tưởng tốt cho các bản dựng gỡ lỗi trong mọi trường hợp).


1
Nhưng ở đây athì không thừa, cần dùng sau ..177 case 3 : a+=k[0]&0xffffff; break;
gdb

3
Bạn cần đăng tất cả mã có liên quan nếu muốn phân tích thêm.
Paul R

1
Trình tối ưu hóa sẽ giữ các biến tạm thời trong thanh ghi bất cứ khi nào có thể. Nó cũng có thể là bí danh của một số biến cho cùng một thanh ghi nếu tất cả chúng đều có cùng giá trị, cho đến khi một trong số chúng được sửa đổi, lúc đó nó có thể được cấp cho một thanh ghi khác. Vì vậy, thời gian tồn tại của các biến trong mã được tối ưu hóa có thể khác với thời gian tồn tại trong mã nguồn. Tắt tối ưu hóa nếu bạn không muốn bị nhầm lẫn bởi loại hành vi này.
Paul R,

2
Các gcc mới hơn có một tùy chọn. -OgNó chỉ áp dụng những tối ưu hóa không làm giảm khả năng gỡ lỗi - rất hữu ích (cũng man gcccho -gdwarf4). Ngoài ra, bạn có thể tạm thời xác định biến mà bạn không muốn mất volatile, nếu bạn không muốn tối ưu hóa trình biên dịch về nó, nhưng không muốn tắt tối ưu hóa cho toàn bộ bản dựng! Cả hai thông tin từ đây: ask.xmodulo.com/print-optimized-out-value-gdb.html
kavadias

3
@kavadias, -Ogtùy chọn có thể là chính xác vấn đề khiến các biến bị tối ưu hóa! Xem câu trả lời của tôi tại đây: stackoverflow.com/a/63386263/4561887 . Vì vậy, nếu bạn gặp bất kỳ lỗi nào nói <optimized out>hoặc Can't take address of "var" which isn't an lvalue., thì bạn phải sử dụng -O0 thay vì -Og !
Gabriel Staples

6

Ví dụ tối thiểu có thể chạy được với phân tích tháo gỡ

Như thường lệ, tôi muốn xem một số thao tác tháo gỡ để hiểu rõ hơn về những gì đang diễn ra.

Trong trường hợp này, thông tin chi tiết mà chúng tôi thu được là nếu một biến được tối ưu hóa để chỉ được lưu trữ trong một thanh ghi thay vì ngăn xếp , và sau đó đăng ký nó đang ở bị ghi đè, sau đó nó cho thấy là <optimized out>như được đề cập bởi R. .

Tất nhiên, điều này chỉ có thể xảy ra nếu biến được đề cập không còn cần thiết nữa, nếu không chương trình sẽ mất giá trị. Do đó, nó có xu hướng xảy ra rằng khi bắt đầu hàm, bạn có thể thấy giá trị biến, nhưng sau đó ở cuối hàm đó trở thành <optimized out>.

Một trường hợp điển hình mà chúng tôi thường quan tâm đến điều này là chính các đối số của hàm, vì đây là:

  • luôn được xác định khi bắt đầu hàm
  • có thể không được sử dụng vào cuối hàm vì nhiều giá trị trung gian hơn được tính toán.
  • có xu hướng bị ghi đè bởi các cuộc gọi con chức năng khác mà phải thiết lập các thanh ghi giống hệt nhau để đáp ứng quy ước gọi

Sự hiểu biết này thực sự có một ứng dụng cụ thể: khi sử dụng gỡ lỗi ngược , bạn có thể khôi phục giá trị của các biến quan tâm chỉ đơn giản bằng cách quay lại điểm sử dụng cuối cùng của chúng: Làm cách nào để xem giá trị của biến <tối ưu hóa> trong C ++?

C chính

#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;
}

Biên dịch và chạy:

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

Sau đó, bên trong GDB, chúng ta có phiên sau:

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>

Để hiểu điều gì đang xảy ra, hãy nhớ quy ước gọi Linux x86: Quy ước gọi cho các lệnh gọi hệ thống UNIX & Linux trên i386 và x86-64 là gì, bạn nên biết rằng:

  • RDI chứa đối số đầu tiên
  • RDI có thể bị hủy trong các lệnh gọi hàm
  • RAX chứa giá trị trả về

Từ đó chúng tôi suy ra rằng:

add    $0x1,%edi

Tương ứng với:

i += 1;

ilà đối số đầu tiên của f1, và do đó được lưu trữ trong RDI.

Bây giờ, khi chúng tôi ở cả hai:

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

giá trị của RDI không được sửa đổi và do đó GDB có thể truy vấn nó bất cứ lúc nào trong các dòng đó.

Tuy nhiên, ngay sau khi f2cuộc gọi được thực hiện:

  • giá trị của i không cần thiết nữa trong chương trình
  • lea 0x1(%rax),%edi làm EDI = j + RAX + 1 , mà cả hai:
    • khởi tạo j = 1
    • thiết lập đối số đầu tiên của lệnh f2gọi tiếp theo tớiRDI = j

Do đó, khi đạt đến dòng sau:

k += f2(j);

cả hai hướng dẫn sau đều có / có thể đã sửa đổi RDI, đây là nơi duy nhất iđược lưu trữ ( f2có thể sử dụng nó làm sổ đăng ký cào và leachắc chắn đặt nó thành RAX + 1):

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

và do đó RDI không chứa giá trị của inữa. Trong thực tế, giá trị của iđã hoàn toàn bị mất! Do đó, kết quả duy nhất có thể xảy ra là:

$3 = <optimized out>

Điều tương tự cũng xảy ra với giá trị của j, mặc dù jchỉ trở nên không cần thiết sau một dòng sau khi gửi lệnh gọi đến k += f2(j);.

Suy nghĩ về nó jcũng cho chúng ta một số hiểu biết về GDB thông minh như thế nào. Đáng chú ý, tại i += 1;, giá trị của jvẫn chưa được thực hiện trong bất kỳ địa chỉ thanh ghi hoặc bộ nhớ nào và GDB phải biết nó chỉ dựa trên siêu dữ liệu thông tin gỡ lỗi.

-O0 phân tích

Nếu chúng tôi sử dụng -O0thay vì -O3để biên dịch:

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

thì quá trình tháo rời sẽ giống như sau:

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 

Từ việc tháo gỡ khủng khiếp này, chúng ta thấy rằng giá trị của RDI được chuyển đến ngăn xếp khi bắt đầu thực hiện chương trình tại:

mov    %edi,-0x14(%rbp)

và sau đó nó được truy xuất từ ​​bộ nhớ vào thanh ghi bất cứ khi nào cần, ví dụ tại:

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)

Về cơ bản, điều tương tự cũng xảy ra với jcái được đẩy ngay vào ngăn xếp khi nó được khởi tạo:

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

Do đó, GDB có thể dễ dàng tìm thấy giá trị của các biến đó bất cứ lúc nào: chúng luôn hiện diện trong bộ nhớ!

Điều này cũng cung cấp cho chúng tôi một số thông tin chi tiết về lý do tại sao không thể tránh <optimized out>trong mã được tối ưu hóa: vì số lượng thanh ghi có giới hạn, cách duy nhất để làm điều đó là thực sự đẩy các thanh ghi không cần thiết vào bộ nhớ, điều này sẽ làm mất đi phần nào lợi ích của-O3 .

Kéo dài thời gian tồn tại của i

Nếu chúng tôi chỉnh sửa f1để trả lại l + inhư trong:

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;
}

thì chúng tôi nhận thấy rằng điều này mở rộng hiệu quả khả năng hiển thị của i cho đến khi kết thúc chức năng.

Điều này là do với điều này, chúng tôi buộc GCC phải sử dụng một biến phụ để giữ icho đến cuố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

mà trình biên dịch thực hiện bằng cách lưu trữ i += itrong RDX ở lệnh đầu tiên.

Đã thử nghiệm trong Ubuntu 18.04, GCC 7.4.0, GDB 8.1.0.



4

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

Giá trị của các đối số không được lưu trong khung ngăn xếp của chúng được hiển thị là `` giá trị được tối ưu hóa ''.

Tôi đoán bạn đã biên dịch với -O (giá trị nào đó) và đang truy cập các biến a, b, c trong một hàm mà tối ưu hóa đã xảy ra.


1

Bạn cần tắt tối ưu hóa trình biên dịch.

Nếu bạn quan tâm đến một biến cụ thể trong gdb, bạn có thể xóa biến đó là "biến động" và biên dịch lại mã. Điều này sẽ làm cho trình biên dịch tắt tối ưu hóa trình biên dịch cho biến đó.

số lượng int biến động = 0;


-1

Chỉ cần chạy "export COPTS = '- g -O0';" và xây dựng lại mã của bạn. Sau khi xây dựng lại, hãy gỡ lỗi nó bằng gdb. Bạn sẽ không thấy lỗi như vậy. Cảm ơn.


AFAICT COPTSkhông phải là một biến môi trường gccchấp nhận, giả sử gccđang được sử dụng.
sappjw

Đừng quên thêm $COPTSvào lệnh biên dịch của bạn.
Akib Azmain
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.