Xác định dòng mã gây ra lỗi phân đoạn?


151

Làm thế nào để xác định lỗi ở đâu trong mã gây ra lỗi phân đoạn ?

Trình biên dịch của tôi ( gcc) có thể hiển thị vị trí của lỗi trong chương trình không?


5
Không có gcc / gdb không thể. Bạn có thể tìm ra nơi xảy ra segfault, nhưng lỗi thực tế có thể ở một vị trí hoàn toàn khác.

Câu trả lời:


218

GCC không thể làm điều đó nhưng GDB ( trình gỡ lỗi ) chắc chắn có thể. Biên dịch chương trình của bạn bằng cách sử dụng công -gtắc, như thế này:

gcc program.c -g

Sau đó sử dụng gdb:

$ gdb ./a.out
(gdb) run
<segfault happens here>
(gdb) backtrace
<offending code is shown here>

Đây là một hướng dẫn tốt để bạn bắt đầu với GDB.

Trường hợp segfault xảy ra thường chỉ là một đầu mối về "lỗi gây ra" trong mã. Vị trí nhất định không nhất thiết là nơi xảy ra sự cố.


28
Lưu ý rằng nơi segfault xảy ra thường chỉ là một đầu mối về "lỗi gây ra" trong mã. Một manh mối quan trọng, nhưng nó không nhất thiết là vấn đề nằm ở đâu.
mpez0

9
Bạn cũng có thể sử dụng (bt đầy đủ) để biết thêm chi tiết.
ant2009

1
Tôi thấy điều này hữu ích: gnu.org/software/gcc/bugs/segfault.html
Yêu xác suất

2
Sử dụng btnhư một tốc ký cho backtrace.
rustyx

43

Ngoài ra, bạn có thể valgrinddùng thử: nếu bạn cài đặt valgrindvà chạy

valgrind --leak-check=full <program>

sau đó nó sẽ chạy chương trình của bạn và hiển thị dấu vết ngăn xếp cho bất kỳ segfaults nào, cũng như bất kỳ bộ nhớ không hợp lệ nào đọc hoặc ghi và rò rỉ bộ nhớ. Nó thực sự khá hữu ích.


2
+1, Valgrind nhanh hơn / dễ sử dụng hơn để phát hiện lỗi bộ nhớ. Trên các bản dựng không được tối ưu hóa với các biểu tượng gỡ lỗi, nó cho bạn biết chính xác nơi xảy ra lỗi segfault và tại sao.
Tim Post

1
Đáng buồn là segfault của tôi biến mất khi biên dịch với -g -O0 và kết hợp với valgrind.
JohnMudd

2
--leak-check=fullsẽ không giúp gỡ lỗi segfaults. Nó chỉ hữu ích để gỡ lỗi rò rỉ bộ nhớ.
ks1322

@JohnMudd Tôi có một segfault chỉ xuất hiện khoảng 1% các tệp đầu vào được kiểm tra, nếu bạn lặp lại đầu vào thất bại, nó sẽ không thất bại. Vấn đề của tôi là do đa luồng. Cho đến nay tôi vẫn chưa tìm ra dòng mã gây ra vấn đề này. Tôi đang sử dụng thử lại để che giấu vấn đề này bây giờ. Nếu sử dụng tùy chọn -g, lỗi sẽ biến mất!
Kemin Zhou

18

Bạn cũng có thể sử dụng kết xuất lõi và sau đó kiểm tra nó với gdb. Để có được thông tin hữu ích, bạn cũng cần phải biên dịch với-g cờ.

Bất cứ khi nào bạn nhận được tin nhắn:

 Segmentation fault (core dumped)

một tập tin cốt lõi được ghi vào thư mục hiện tại của bạn. Và bạn có thể kiểm tra nó bằng lệnh

 gdb your_program core_file

Tệp chứa trạng thái của bộ nhớ khi chương trình bị hỏng. Kết xuất lõi có thể hữu ích trong quá trình triển khai phần mềm của bạn.

Đảm bảo hệ thống của bạn không đặt kích thước tệp kết xuất lõi thành 0. Bạn có thể đặt thành không giới hạn với:

ulimit -c unlimited

Mặc dù cẩn thận! những bãi rác cốt lõi có thể trở nên khổng lồ.


Tôi đã chuyển sang arch-linux gần đây. Thư mục hiện tại của tôi không chứa tệp kết xuất lõi. Làm thế nào tôi có thể tạo ra nó?
Abhinav

Bạn không tạo ra nó; Linux nào. Các bãi rác cốt lõi được lưu trữ tại các vị trí khác nhau trên Linuces differnt - Google xung quanh. Đối với Arch Linux, hãy đọc wiki.archlinux.org/index.php/Core_dump
Mawg nói rằng phục hồi Monica

7

Có một số công cụ có sẵn giúp gỡ lỗi các lỗi phân đoạn và tôi muốn thêm công cụ yêu thích của mình vào danh sách: Vệ sinh Địa chỉ (thường được viết tắt là ASAN) .

Trình biên dịch Modern đi kèm với tiện dụng -fsanitize=address cờ , thêm một số thời gian biên dịch và chạy trên đầu thời gian để kiểm tra lỗi nhiều hơn.

Theo tài liệu, các kiểm tra này bao gồm bắt lỗi phân đoạn theo mặc định. Ưu điểm ở đây là bạn có được một dấu vết ngăn xếp tương tự như đầu ra của gdb, nhưng không chạy chương trình bên trong trình gỡ lỗi. Một ví dụ:

int main() {
  volatile int *ptr = (int*)0;
  *ptr = 0;
}
$ gcc -g -fsanitize=address main.c
$ ./a.out
AddressSanitizer:DEADLYSIGNAL
=================================================================
==4848==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x5654348db1a0 bp 0x7ffc05e39240 sp 0x7ffc05e39230 T0)
==4848==The signal is caused by a WRITE memory access.
==4848==Hint: address points to the zero page.
    #0 0x5654348db19f in main /tmp/tmp.s3gwjqb8zT/main.c:3
    #1 0x7f0e5a052b6a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x26b6a)
    #2 0x5654348db099 in _start (/tmp/tmp.s3gwjqb8zT/a.out+0x1099)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV /tmp/tmp.s3gwjqb8zT/main.c:3 in main
==4848==ABORTING

Đầu ra phức tạp hơn một chút so với những gì gdb sẽ tạo ra nhưng có những mặt tăng:

  • Không cần phải tái tạo vấn đề để nhận được dấu vết ngăn xếp. Đơn giản chỉ cần cho phép cờ trong quá trình phát triển là đủ.

  • ASAN bắt nhiều hơn chỉ là lỗi phân khúc. Nhiều truy cập ngoài giới hạn sẽ bị bắt ngay cả khi vùng nhớ đó có thể truy cập được vào quy trình.


Đó là Clang 3.1+GCC 4.8+ .


Điều này là hữu ích nhất đối với tôi. Tôi có một lỗi rất tinh vi xảy ra ngẫu nhiên với tần suất khoảng 1%. Tôi xử lý số lượng lớn các tệp đầu vào với (16 bước chính; mỗi bước được thực hiện bởi một nhị phân C hoặc C ++ khác nhau). Một bước sau sẽ kích hoạt lỗi phân đoạn chỉ ngẫu nhiên vì đa luồng. Thật khó để gỡ lỗi. Tùy chọn này đã kích hoạt đầu ra thông tin gỡ lỗi ít nhất nó đã cho tôi một điểm bắt đầu để xem xét mã để tìm vị trí của lỗi.
Kemin Zhou

2

Câu trả lời của Lucas về bãi rác cốt lõi là tốt. Trong .cshrc của tôi, tôi có:

alias core 'ls -lt core; echo where | gdb -core=core -silent; echo "\n"'

để hiển thị backtrace bằng cách nhập 'core'. Và dấu ngày, để đảm bảo tôi đang xem đúng tệp :(.

Đã thêm : Nếu có lỗi tham nhũng ngăn xếp , thì backtrace được áp dụng cho kết xuất lõi thường là rác. Trong trường hợp này, chạy chương trình trong gdb có thể cho kết quả tốt hơn, theo câu trả lời được chấp nhận (giả sử lỗi có thể tái tạo dễ dàng). Và cũng hãy cẩn thận với nhiều quá trình bán phá giá lõi đồng thời; Một số hệ điều hành thêm PID vào tên của tệp lõi.


4
và đừng quên ulimit -c unlimitedkích hoạt các bãi rác cốt lõi ở nơi đầu tiên.
James Morris

@James: Đúng. Lucas đã đề cập đến điều này. Và đối với những người trong chúng ta vẫn bị mắc kẹt trong csh, hãy sử dụng 'giới hạn'. Và tôi chưa bao giờ có thể đọc các ngăn xếp CYGWIN (nhưng tôi đã không thử trong 2 hoặc 3 năm).
Joseph Quinsey

2

Tất cả các câu trả lời trên là chính xác và được đề nghị; câu trả lời này chỉ nhằm mục đích cuối cùng nếu không có phương pháp nào nói trên có thể được sử dụng.

Nếu vẫn thất bại, bạn luôn có thể biên dịch lại chương trình của mình bằng các câu lệnh gỡ lỗi tạm thời khác nhau (ví dụ fprintf(stderr, "CHECKPOINT REACHED @ %s:%i\n", __FILE__, __LINE__);) được rắc khắp những gì bạn tin là các phần có liên quan trong mã của mình. Sau đó chạy chương trình và quan sát bản in gỡ lỗi cuối cùng được in ngay trước khi sự cố xảy ra - bạn biết chương trình của bạn đã đi xa đến mức đó, vì vậy sự cố phải xảy ra sau thời điểm đó. Thêm hoặc xóa bản in gỡ lỗi, biên dịch lại và chạy thử nghiệm cho đến khi bạn thu hẹp nó xuống một dòng mã. Tại thời điểm đó, bạn có thể sửa lỗi và xóa tất cả các bản in gỡ lỗi tạm thời.

Điều này khá tẻ nhạt, nhưng nó có lợi thế là hoạt động ở mọi nơi - điều duy nhất có thể là nếu bạn không có quyền truy cập vào thiết bị xuất chuẩn hoặc thiết bị lỗi chuẩn vì một số lý do hoặc nếu lỗi bạn đang cố gắng khắc phục là một cuộc đua -condition có hành vi thay đổi khi thời gian của chương trình thay đổi (vì bản in gỡ lỗi sẽ làm chậm chương trình và thay đổi thời gian của chương trình)

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.