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?
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?
Câu trả lời:
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 -g
tắ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ố.
bt
như một tốc ký cho backtrace
.
Ngoài ra, bạn có thể valgrind
dùng thử: nếu bạn cài đặt valgrind
và 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.
--leak-check=full
sẽ không giúp gỡ lỗi segfaults. Nó chỉ hữu ích để gỡ lỗi rò rỉ bộ nhớ.
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ồ.
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+ và GCC 4.8+ .
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.
ulimit -c unlimited
kích hoạt các bãi rác cốt lõi ở nơi đầu tiên.
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)