(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 ??
(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 ??
Câu trả lời:
Nó có nghĩa là bạn đã biên dịch với ví dụ gcc -O3
và 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).
a
thì không thừa, cần dùng sau ..177 case 3 : a+=k[0]&0xffffff; break;
-Og
Nó 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 gcc
cho -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
-Og
tù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
!
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à:
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:
Từ đó chúng tôi suy ra rằng:
add $0x1,%edi
Tương ứng với:
i += 1;
vì i
là đố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 f2
cuộc gọi được thực hiện:
i
không cần thiết nữa trong chương trìnhlea 0x1(%rax),%edi
làm EDI = j + RAX + 1
, mà cả hai:
j = 1
f2
gọ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ữ ( f2
có thể sử dụng nó làm sổ đăng ký cào và lea
chắ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 i
nữ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ù j
chỉ 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ó j
cũ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 j
vẫ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 -O0
thay 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 j
cá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 + i
như 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ữ i
cho đế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 += i
trong RDX ở lệnh đầu tiên.
Đã thử nghiệm trong Ubuntu 18.04, GCC 7.4.0, GDB 8.1.0.
Nó đã không. Trình biên dịch của bạn đã làm được, nhưng vẫn có một biểu tượng gỡ lỗi cho tên biến ban đầu.
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.
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;
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.
COPTS
không phải là một biến môi trường gcc
chấp nhận, giả sử gcc
đang được sử dụng.
$COPTS
vào lệnh biên dịch của bạn.