Gỡ lỗi ngược hoạt động như thế nào?


82

GDB có một phiên bản mới hỗ trợ gỡ lỗi ngược (xem http://www.gnu.org/software/gdb/news/reversible.html ). Tôi đã tự hỏi làm thế nào nó hoạt động.

Để gỡ lỗi ngược hoạt động, tôi thấy rằng bạn cần lưu trữ toàn bộ trạng thái máy bao gồm cả bộ nhớ cho mỗi bước. Điều này sẽ làm cho hiệu suất cực kỳ chậm, chưa kể đến việc sử dụng nhiều bộ nhớ. Những vấn đề này được giải quyết như thế nào?


4
Tôi tưởng tượng bạn có thể có được bằng cách lưu trữ các delta trạng thái thay vì toàn bộ trạng thái, nhưng có vẻ như nó vẫn có thể tốn kém.
tiêu 24/09/09


Việc lưu các delta thực sự có thể hoạt động rất tốt và thực sự cần thiết cho một giải pháp hoàn nguyên toàn hệ thống hiệu quả.
jakobengblom2

Câu trả lời:


131

Tôi là người bảo trì gdb và là một trong những tác giả của gỡ lỗi ngược mới. Tôi rất vui khi nói về cách nó hoạt động. Như nhiều người đã suy đoán, bạn cần lưu đủ trạng thái máy để có thể khôi phục sau. Có một số lược đồ, một trong số đó là chỉ cần lưu các thanh ghi hoặc vị trí bộ nhớ được sửa đổi theo từng lệnh máy. Sau đó, để "hoàn tác" lệnh đó, bạn chỉ cần hoàn nguyên dữ liệu trong các thanh ghi hoặc vị trí bộ nhớ đó.

Đúng, nó đắt, nhưng cpu hiện đại quá nhanh nên dù sao khi bạn đang tương tác (thực hiện bước hoặc điểm ngắt), bạn thực sự không nhận thấy nó nhiều.


4
Nhưng gỡ lỗi ngược chỉ cho phép bạn quay lại nextstepcác lệnh bạn đã nhập hay nó cho phép bạn hoàn tác bất kỳ số lượng hướng dẫn nào? Ví dụ: nếu tôi đặt một điểm ngắt trên một lệnh và để nó chạy cho đến lúc đó, thì tôi có thể quay lại lệnh trước đó, mặc dù tôi đã bỏ qua nó không?
Nathan Fellman

10
> Nhưng gỡ lỗi ngược chỉ cho phép bạn quay lại các lệnh tiếp theo và bước mà bạn đã nhập hay nó có cho phép bạn hoàn tác bất kỳ số lượng hướng dẫn nào Bạn có thể hoàn tác bất kỳ số lượng hướng dẫn nào. Bạn không bị hạn chế, chẳng hạn như chỉ dừng lại ở những điểm mà bạn đã dừng lại khi đi tiếp. Bạn có thể đặt một điểm ngắt mới và chạy ngược lại nó> Ví dụ: nếu tôi đặt một điểm ngắt trên một lệnh và để nó chạy cho đến lúc đó, sau đó tôi có thể quay lại hướng dẫn trước đó không, mặc dù tôi đã bỏ qua nó. bật chế độ ghi âm trước khi bạn chạy đến breakpoint
Michael Snyder

3
Xin lỗi vì văn bản chưa được định dạng, không biết có vấn đề gì không.
Michael Snyder

10
Tôi lo lắng rằng gỡ lỗi ngược có thể hoàn tác thời gian và khiến chúng ta quay trở lại những năm 60 hoặc 70. Tôi không muốn mặc quần ống suông và để tóc dài trở lại.
the Tin Man

3
Và các cuộc gọi hệ thống thay đổi trạng thái trong hệ điều hành? Điều đó chỉ không hoạt động đúng cách? Còn khi nó sửa đổi một tay cầm mờ đục thì sao?
Adrian

12

Lưu ý rằng bạn không được quên việc sử dụng trình mô phỏng, máy ảo và bộ ghi phần cứng để thực hiện ngược lại.

Một giải pháp khác để thực hiện nó là theo dõi việc thực thi trên phần cứng vật lý, chẳng hạn như được thực hiện bởi GreenHills và Lauterbach trong trình gỡ lỗi dựa trên phần cứng của họ. Dựa trên dấu vết cố định này của hành động của từng lệnh, sau đó bạn có thể di chuyển đến bất kỳ điểm nào trong dấu vết bằng cách loại bỏ lần lượt các hiệu ứng của từng lệnh. Lưu ý rằng điều này giả định rằng bạn có thể theo dõi tất cả những thứ ảnh hưởng đến trạng thái hiển thị trong trình gỡ lỗi.

Một cách khác là sử dụng phương thức kiểm tra + thực thi lại, được sử dụng bởi VmWare Workstation 6.5 và Virtutech Simics 3.0 (và mới hơn), và dường như sẽ xuất hiện với Visual Studio 2010. Ở đây, bạn sử dụng máy ảo hoặc trình mô phỏng để có được mức chuyển hướng về việc thực thi hệ thống. Bạn thường xuyên kết xuất toàn bộ trạng thái vào đĩa hoặc bộ nhớ, sau đó dựa vào trình mô phỏng có thể thực thi lại một cách xác định đường dẫn chương trình chính xác.

Đơn giản hóa, nó hoạt động như thế này: nói rằng bạn đang ở thời điểm T đang thực hiện một hệ thống. Để đến thời gian T-1, bạn chọn một số điểm kiểm tra từ điểm t <T, và sau đó thực hiện các chu kỳ (Tt-1) để kết thúc một chu kỳ trước khi bạn ở đó. Điều này có thể được thực hiện để hoạt động rất tốt và áp dụng ngay cả cho khối lượng công việc thực hiện IO đĩa, bao gồm mã cấp hạt nhân và thực hiện công việc trình điều khiển thiết bị. Điều quan trọng là phải có một trình mô phỏng chứa toàn bộ hệ thống đích, với tất cả các bộ xử lý, thiết bị, bộ nhớ và IO của nó. Xem các mailinglist gdb và các cuộc thảo luận sau đó trong danh sách gửi thư gdb để biết thêm chi tiết. Bản thân tôi sử dụng phương pháp này khá thường xuyên để gỡ lỗi mã phức tạp, đặc biệt là trong trình điều khiển thiết bị và khởi động hệ điều hành sớm.

Một nguồn thông tin khác là sách trắng của Virtutech về kiểm soát (mà tôi đã viết, tiết lộ đầy đủ).


Ngoài ra, hãy xem jakob.engbloms.se/archives/1547 và hai bài đăng blog sau của nó để biết hướng dẫn kỹ lưỡng hơn về các kỹ thuật gỡ lỗi ngược.
jakobengblom2

Làm thế nào về khả năng "thiết lập điểm lưu" thay vì thực hiện bước ngược lại. Vì vậy, bạn gỡ lỗi và tại một thời điểm nào đó, bạn có thể chọn bước hiện tại là "điểm lưu", và sau đó, bạn có thể quay trở lại điểm lưu đó và bước tiếp, chỉnh sửa các biến của bạn nếu cần. Đại loại như "ảnh chụp nhanh" cho máy ảo hoặc "điểm khôi phục" cho hệ điều hành.
Rolf

9

Trong phiên EclipseCon, chúng tôi cũng đã hỏi cách họ thực hiện điều này với Trình gỡ lỗi Chronon cho Java. Một trong đó không cho phép bạn thực sự lùi lại, nhưng có thể chơi lại một thực hiện chương trình ghi lại trong một cách mà nó cảm thấy như gỡ lỗi ngược lại. (Sự khác biệt chính là bạn không thể thay đổi chương trình đang chạy trong trình gỡ lỗi Chronon, trong khi bạn có thể làm điều đó trong hầu hết các trình gỡ lỗi Java khác.)

Nếu tôi hiểu nó một cách chính xác, nó thao tác mã byte của chương trình đang chạy, sao cho mọi thay đổi trạng thái bên trong của chương trình đều được ghi lại. Trạng thái bên ngoài không cần phải được ghi lại. Nếu chúng ảnh hưởng đến chương trình của bạn theo một cách nào đó, thì bạn phải có một biến bên trong khớp với trạng thái bên ngoài đó (và do đó, biến nội bộ đó là đủ).

Trong thời gian phát lại, về cơ bản chúng có thể tạo lại mọi trạng thái của chương trình đang chạy từ những thay đổi trạng thái đã ghi.

Điều thú vị là các thay đổi trạng thái nhỏ hơn nhiều so với những gì người ta mong đợi ở cái nhìn đầu tiên. Vì vậy, nếu bạn có câu lệnh "if" có điều kiện, bạn sẽ nghĩ rằng bạn cần ít nhất một bit để ghi lại chương trình sử dụng câu lệnh then- hay else-. Trong nhiều trường hợp, bạn thậm chí có thể tránh điều đó, chẳng hạn như trong trường hợp các nhánh khác nhau đó chứa giá trị trả về. Sau đó, chỉ cần ghi lại giá trị trả về (dù sao cũng cần thiết) và tính toán lại quyết định về nhánh được thực thi từ chính giá trị trả về.


8

Mặc dù câu hỏi này đã cũ, nhưng hầu hết các câu trả lời đều quá, và vẫn là một chủ đề thú vị, tôi đang đăng một câu trả lời năm 2015. Chương 1 và 2 trong luận văn Thạc sĩ của tôi, Kết hợp gỡ lỗi ngược và lập trình trực tiếp theo hướng tư duy trực quan trong lập trình máy tính , bao gồm một số cách tiếp cận lịch sử để gỡ lỗi ngược (đặc biệt tập trung vào cách tiếp cận snapshot- (hoặc checkpoint) -và phát lại), và giải thích sự khác biệt giữa nó và gỡ lỗi toàn trí:

Máy tính, sau khi thực thi chương trình tới một thời điểm nào đó, sẽ thực sự có thể cung cấp cho chúng tôi thông tin về nó. Một cải tiến như vậy là có thể, và được tìm thấy trong cái được gọi là trình gỡ rối toàn trí. Chúng thường được phân loại là trình gỡ lỗi ngược, mặc dù chúng có thể được mô tả chính xác hơn là trình gỡ rối "ghi nhật ký lịch sử", vì chúng chỉ ghi lại thông tin trong quá trình thực thi để xem hoặc truy vấn sau đó, thay vì cho phép lập trình viên thực sự lùi lại thời gian trong một chương trình đang thực thi . "Toàn bộ" xuất phát từ thực tế là toàn bộ lịch sử trạng thái của chương trình, đã được ghi lại, có sẵn cho trình gỡ lỗi sau khi thực thi. Sau đó không cần chạy lại chương trình và không cần thiết bị đo mã thủ công.

Gỡ lỗi toàn bộ dựa trên phần mềm bắt đầu với hệ thống EXDAMS năm 1969, nơi nó được gọi là "phát lại lịch sử thời gian gỡ lỗi". Trình gỡ lỗi GNU, GDB, đã hỗ trợ gỡ lỗi toàn bộ từ năm 2009, với tính năng 'ghi lại quá trình và phát lại'. TotalView, UndoDB và Chronon dường như là những trình gỡ rối toàn trí tốt nhất hiện có, nhưng là các hệ thống thương mại. TOD, đối với Java, dường như là giải pháp thay thế mã nguồn mở tốt nhất, sử dụng tính năng phát lại từng phần xác định, cũng như thu thập dấu vết một phần và cơ sở dữ liệu phân tán để cho phép ghi lại khối lượng lớn thông tin liên quan.

Các trình gỡ lỗi không chỉ cho phép điều hướng bản ghi mà thực sự có thể lùi lại thời gian thực thi, cũng tồn tại. Chúng có thể được mô tả chính xác hơn là trình gỡ lỗi ngược thời gian, du hành thời gian, hai chiều hoặc đảo ngược.

Hệ thống đầu tiên như vậy là nguyên mẫu COPE năm 1981 ...


4

mozilla rrlà một giải pháp thay thế mạnh mẽ hơn cho gỡ lỗi ngược GDB

https://github.com/mozilla/rr

Bản ghi và phát lại tích hợp của GDB có những hạn chế nghiêm trọng, chẳng hạn như không hỗ trợ hướng dẫn AVX: gỡ lỗi ngược gdb không thành công với "Bản ghi quy trình không hỗ trợ lệnh 0xf0d tại địa chỉ"

Mặt trước của rr:

  • đáng tin cậy hơn nhiều hiện tại. Tôi đã thử nghiệm nó chạy tương đối lâu một số phần mềm phức tạp.
  • cũng cung cấp giao diện GDB với giao thức gdbserver, làm cho nó trở thành một sự thay thế tuyệt vời
  • giảm hiệu suất nhỏ đối với hầu hết các chương trình, bản thân tôi đã không nhận thấy điều đó nếu không thực hiện các phép đo
  • các dấu vết được tạo nhỏ trên đĩa vì chỉ có rất ít sự kiện không xác định được ghi lại, tôi chưa bao giờ phải lo lắng về kích thước của chúng cho đến nay

rr đạt được điều này bằng cách đầu tiên chạy chương trình theo cách ghi lại những gì đã xảy ra trên mọi sự kiện không xác định, chẳng hạn như chuyển đổi luồng.

Sau đó, trong lần chạy phát lại thứ hai, nó sử dụng tệp theo dõi đó, nhỏ đáng ngạc nhiên, để tái tạo lại chính xác những gì đã xảy ra trên lần chạy không xác định ban đầu nhưng theo cách xác định, tiến hoặc lùi.

rr ban đầu được phát triển bởi Mozilla để giúp họ tái tạo các lỗi thời gian xuất hiện trong bài kiểm tra hàng đêm của họ vào ngày hôm sau. Nhưng khía cạnh gỡ lỗi ngược cũng là cơ bản khi bạn gặp lỗi chỉ xảy ra hàng giờ trong quá trình thực thi, vì bạn thường muốn lùi lại để kiểm tra trạng thái trước đó dẫn đến lỗi sau này.

Ví dụ sau đây giới thiệu một số tính năng của nó, đặc biệt là reverse-next, reverse-stepreverse-continuelệnh.

Cài đặt trên Ubuntu 18.04:

sudo apt-get install rr linux-tools-common linux-tools-generic linux-cloud-tools-generic
sudo cpupower frequency-set -g performance
# Overcome "rr needs /proc/sys/kernel/perf_event_paranoid <= 1, but it is 3."
echo 'kernel.perf_event_paranoid=1' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

Chương trình kiểm tra:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int f() {
    int i;
    i = 0;
    i = 1;
    i = 2;
    return i;
}

int main(void) {
    int i;

    i = 0;
    i = 1;
    i = 2;

    /* Local call. */
    f();

    printf("i = %d\n", i);

    /* Is randomness completely removed?
     * Recently fixed: https://github.com/mozilla/rr/issues/2088 */
    i = time(NULL);
    printf("time(NULL) = %d\n", i);

    return EXIT_SUCCESS;
}

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

gcc -O0 -ggdb3 -o reverse.out -std=c89 -Wextra reverse.c
rr record ./reverse.out
rr replay

Bây giờ bạn ở bên trong phiên GDB và bạn có thể đảo ngược gỡ lỗi đúng cách:

(rr) break main
Breakpoint 1 at 0x55da250e96b0: file a.c, line 16.
(rr) continue
Continuing.

Breakpoint 1, main () at a.c:16
16          i = 0;
(rr) next
17          i = 1;
(rr) print i
$1 = 0
(rr) next
18          i = 2;
(rr) print i
$2 = 1
(rr) reverse-next
17          i = 1;
(rr) print i
$3 = 0
(rr) next
18          i = 2;
(rr) print i
$4 = 1
(rr) next
21          f();
(rr) step
f () at a.c:7
7           i = 0;
(rr) reverse-step
main () at a.c:21
21          f();
(rr) next
23          printf("i = %d\n", i);
(rr) next
i = 2
27          i = time(NULL);
(rr) reverse-next
23          printf("i = %d\n", i);
(rr) next
i = 2
27          i = time(NULL);
(rr) next
28          printf("time(NULL) = %d\n", i);
(rr) print i
$5 = 1509245372
(rr) reverse-next
27          i = time(NULL);
(rr) next
28          printf("time(NULL) = %d\n", i);
(rr) print i
$6 = 1509245372
(rr) reverse-continue
Continuing.

Breakpoint 1, main () at a.c:16
16          i = 0;

Khi gỡ lỗi phần mềm phức tạp, bạn có thể sẽ gặp sự cố và sau đó rơi vào một khung hình sâu. Trong trường hợp đó, đừng quên rằng đối reverse-nextvới các khung hình cao hơn, trước tiên bạn phải:

reverse-finish

lên đến khung đó, chỉ làm thông thường uplà không đủ.

Những hạn chế nghiêm trọng nhất của rr theo ý kiến ​​của tôi là:

UndoDB là một giải pháp thay thế thương mại cho rr: https://undo.io Cả hai đều dựa trên dấu vết / phát lại, nhưng tôi không chắc chúng so sánh như thế nào về tính năng và hiệu suất.


Bạn có biết làm thế nào tôi có thể làm điều này với ddd? Cảm ơn
spff

@spraff Tôi không chắc, nhưng có thể. Trước tiên hãy thử kết nối ddd với gdbserver. Nếu điều đó hoạt động, nó cũng sẽ hoạt động với rr.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

1
@spraff tuy nhiên, không sử dụng ddd, hãy sử dụng bảng điều khiển gdb ;-) stackoverflow.com/questions/10115540/gdb-split-view-with-code/… Điều này chắc chắn sẽ hoạt động vì nó chỉ là GDB thông thường.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

3

Nathan Fellman đã viết:

Nhưng gỡ lỗi ngược chỉ cho phép bạn quay lại các lệnh tiếp theo và bước mà bạn đã nhập, hay nó cho phép bạn hoàn tác bất kỳ số lượng hướng dẫn nào?

Bạn có thể hoàn tác bất kỳ số lượng hướng dẫn nào. Ví dụ: bạn không bị giới hạn, chỉ dừng lại ở những điểm mà bạn đã dừng lại khi đi tiếp. Bạn có thể đặt một điểm ngắt mới và chạy ngược lại điểm đó.

Ví dụ: nếu tôi đặt một điểm ngắt trên một lệnh và để nó chạy cho đến lúc đó, sau đó tôi có thể quay lại lệnh trước đó, mặc dù tôi đã bỏ qua nó không?

Đúng. Miễn là bạn đã bật chế độ ghi trước khi chạy đến điểm ngắt.


2
Một phần quan trọng của bất kỳ giải pháp đảo ngược nào là bạn bật nó vào một thời điểm nào đó và chỉ có thể đảo ngược cho đến thời điểm đó. Không có phép thuật nào có thể chạy ngược lại một cỗ máy và tìm ra những gì đã xảy ra trước đó mà không có một số loại ghi chép về những gì đã xảy ra.
jakobengblom2

2

Đây là cách hoạt động của một trình gỡ lỗi ngược khác có tên là ODB. Trích xuất:

Gỡ lỗi toàn diện là ý tưởng thu thập "tem thời gian" tại mỗi "điểm ưa thích" (đặt giá trị, thực hiện lệnh gọi phương thức, ném / bắt một ngoại lệ) trong một chương trình và sau đó cho phép lập trình viên sử dụng các dấu thời gian đó để khám phá lịch sử chạy chương trình đó.

ODB ... chèn mã vào các lớp của chương trình khi chúng được tải và khi chương trình chạy, các sự kiện được ghi lại.

Tôi đoán gdb một hoạt động theo cùng một cách.


Vì vậy, điều này sẽ yêu cầu các chỉ thị trong mã để cho trình biên dịch và trình gỡ lỗi biết những điểm thú vị đó ở đâu?
Nathan Fellman, 24/09/09

Không. Có một bản trình diễn Java Web Start trên www.LambdaCS.com/debugger/debugger.html cho bạn thấy nó hoạt động như thế nào. Nó trông giống như một chương trình bình thường. Đó là ODB anyway, không biết về gdb. Nó rất mát mặc dù :)
demoncodemonkey

Lưu ý rằng giải pháp gdb KHÔNG thay đổi chương trình đích theo bất kỳ cách nào. Nếu bạn phải thiết bị một chương trình để gỡ lỗi nó, bạn có khả năng sự cố biến mất do chênh lệch thời gian và các sự cố khác. Tất cả các công cụ Revexec thương mại đều dựa trên một số dạng bản ghi bên ngoài không thay đổi mã của chính chương trình.
jakobengblom2

@ jakobengblom2: Tôi nghĩ rằng bạn đang quá chú trọng vào sự khác biệt giữa việc thay đổi mục tiêu bằng cách ghi vào bộ nhớ của nó, mô phỏng thực thi hoặc đơn giản là thêm các điểm ngắt phần cứng. Tất cả đều thay đổi thời gian. Trên thực tế, thiết bị đo mục tiêu có thể thay đổi thời gian ít nhất.
Ben Voigt 24/09/13

2

Gỡ lỗi ngược có nghĩa là bạn có thể chạy ngược chương trình, điều này rất hữu ích để theo dõi nguyên nhân của sự cố.

Bạn không cần phải lưu trữ trạng thái máy hoàn chỉnh cho mỗi bước, chỉ cần thay đổi. Nó có lẽ vẫn còn khá đắt.


Tôi hiểu rồi, nhưng bạn vẫn cần ngắt thực thi ở mỗi thay đổi để lưu các thay đổi.
Nathan Fellman 24/09/09

Đúng, đúng vậy, nhưng máy móc hiện nay khá nhanh, và về mặt con người, tôi không tin rằng tốc độ chậm là không thể chấp nhận được. Nó có thể so sánh với valgrind, có thể không chậm như valgrind.
Michael Snyder
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.