2011-10-19 39 views
7

Tôi cần gỡ lỗi một thư viện C toán học xấu xí và lớn, có thể một khi được tạo bởi f2c. Mã đang lạm dụng các biến tĩnh cục bộ và không may ở đâu đó dường như khai thác thực tế là chúng được tự động khởi tạo thành 0. Nếu hàm nhập của nó được gọi với cùng một đầu vào hai lần, nó sẽ cho kết quả khác nhau. Nếu tôi dỡ bỏ thư viện và tải lại nó, nó hoạt động chính xác. Nó cần phải được nhanh chóng, vì vậy tôi muốn để thoát khỏi tải/dỡ bỏ.theo dõi các biến tĩnh chưa được khởi tạo

Câu hỏi của tôi là làm thế nào để phát hiện ra các lỗi này với valgrind hoặc bởi bất kỳ công cụ nào khác mà không cần phải tự đi qua toàn bộ mã.

Tôi đang tìm những nơi mà một biến tĩnh cục bộ được khai báo, đọc đầu tiên và chỉ được ghi sau. Vấn đề thậm chí còn phức tạp hơn nữa bởi thực tế là các biến tĩnh đôi khi được truyền qua con trỏ (yep - nó quá xấu xí).

Tôi hiểu rằng người ta có thể lập luận rằng những sai lầm như thế này không cần được phát hiện bởi một công cụ tự động, như trong một số trường hợp, đây chính là hành vi dự định. Tuy nhiên, có cách nào để làm cho các biến tĩnh cục bộ được tự động khởi tạo "bẩn" không?

+0

Câu hỏi thú vị, nhưng valgrind không có kiến ​​thức về "địa phương" hoặc "tuyên bố". Tôi nghĩ rằng điều này phải được thực hiện bằng cách phân tích mã, không phải phân tích thực thi. – aschepler

+0

chúng tôi sử dụng PC Lint, nhưng nó tạo ra rất nhiều cảnh báo để tìm các điểm nóng thực sự đôi khi giống như câu cá trong bóng tối. –

Trả lời

5

The devil là tại các chi tiết, nhưng điều này có thể làm việc cho bạn:

Đầu tiên, có được Frama-C. Nếu bạn đang sử dụng Unix, phân phối của bạn có thể có một gói. Các gói phần mềm sẽ không được phiên bản cuối cùng nhưng nó có thể là đủ tốt và nó sẽ giúp bạn tiết kiệm một số thời gian nếu bạn cài đặt nó theo cách này.

Nói ví dụ của bạn là như dưới đây, chỉ nên lớn hơn nhiều mà nó không rõ ràng những gì là sai:

int add(int x, int y) 
{ 
    static int state; 
    int result = x + y + state; // I tested it once and it worked. 
    state++; 
    return result; 
} 

Loại một lệnh như:

frama-c -lib-entry -main add -deps ugly.c 

Tùy chọn -lib-entry -main add có nghĩa là "nhìn vào chức năng add ". Tùy chọn -deps tính các phụ thuộc chức năng. Bạn sẽ tìm thấy những "phụ thuộc chức năng" trong nhật ký:

[from] Function add: 
    state FROM state; (and default:false) 
    \result FROM x; y; state; (and default:false) 

này liệt kê các thực tế đầu vào kết quả của add phụ thuộc vào, và thực tế quả tính toán từ các nguyên liệu đầu vào, bao gồm các biến tĩnh đọc từ và sửa đổi. Một biến tĩnh đã được khởi tạo đúng cách trước khi được sử dụng bình thường sẽ không xuất hiện như đầu vào, trừ khi máy phân tích không thể xác định rằng nó luôn được khởi tạo trước khi được đọc.

Nhật ký hiển thị state làm phụ thuộc của \result. Nếu bạn mong đợi kết quả trả về chỉ phụ thuộc vào các đối số (có nghĩa là hai cuộc gọi với cùng một đối số tạo ra cùng một kết quả), đó là một gợi ý có thể có điều gì sai ở đây, với biến số state.

Một gợi ý khác được hiển thị trong các dòng trên là chức năng sửa đổi state.

Điều này có thể hữu ích hay không. Tùy chọn có nghĩa là trình phân tích không cho rằng bất kỳ biến tĩnh không const nào đã giữ giá trị của nó tại thời điểm hàm phân tích được gọi, do đó có thể quá thiếu chính xác cho mã của bạn. Có những cách xung quanh đó, nhưng sau đó nó là vào bạn cho dù bạn muốn đánh bạc thời gian cần để tìm hiểu những cách này.

EDIT: đây là một ví dụ phức tạp hơn:

void initialize_1(int *p) 
{ 
    *p = 0; 
} 

void initialize_2(int *p) 
{ 
    *p; // I made a mistake here. 
} 

int add(int x, int y) 
{ 
    static int state1; 
    static int state2; 

    initialize_1(&state1); 
    initialize_2(&state2); 

    // This is safe because I have initialized state1 and state2: 
    int result = x + y + state1 + state2; 

    state1++; 
    state2++; 
    return result; 
} 

On ví dụ này, lệnh cùng tạo ra kết quả:

[from] Function initialize_1: 
     state1 FROM p 
[from] Function initialize_2: 
[from] Function add: 
     state1 FROM \nothing 
     state2 FROM state2 
     \result FROM x; y; state2 

gì bạn nhìn thấy cho initialize_2 là một danh sách trống phụ thuộc, nghĩa là hàm gán không có gì. Tôi sẽ làm cho trường hợp này rõ ràng hơn bằng cách hiển thị một thông điệp rõ ràng hơn là một danh sách trống. Nếu bạn biết bất kỳ chức năng nào trong số các chức năng initialize_1, initialize_2 hoặc add có nghĩa vụ phải làm, bạn có thể so sánh kiến ​​thức này với kết quả phân tích và thấy rằng có điều gì đó sai cho initialize_2add.

EDIT SECOND: và bây giờ ví dụ của tôi cho thấy một cái gì đó kỳ lạ cho initialize_1, vì vậy có lẽ tôi nên giải thích điều đó. Biến số state1 phụ thuộc vào p theo nghĩa là p được sử dụng để ghi vào state1 và nếu p khác nhau, thì giá trị cuối cùng của state1 sẽ khác. Dưới đây là một ví dụ cuối cùng:

int t[10]; 

void initialize_index(int i) 
{ 
    t[i] = 1; 
} 

int main(int argc, char **argv) 
{ 
    initialize_index(argv[1][0]-'0'); 
} 

Với lệnh frama-c -deps t.c, sự phụ thuộc tính cho initialize_index là:

[from] Function initialize_index: 
     t[0..9] FROM i (and SELF) 

Điều này có nghĩa rằng mỗi người trong các tế bào phụ thuộc vào i (nó có thể được sửa đổi nếu i là chỉ mục của ô cụ thể đó). Mỗi ô cũng có thể giữ giá trị của nó (nếu i cho biết một ô khác): điều này được biểu thị bằng số (and SELF) đề cập trong phiên bản mới nhất và được biểu thị bằng một số ít (and default:true) trong các phiên bản trước.

+0

Cảm ơn! Điều này nghe có ích; Tôi sẽ không thử nó mặc dù, như tôi đã tìm thấy lỗi của tôi với phương pháp nguyên thủy nhất dưới đây. BTW, có thể Frama-C theo dõi các biến được truyền qua con trỏ? Trong trường hợp của tôi, các số liệu thống kê được gửi đi xung quanh, và chúng có thể được khởi tạo trong một chức năng khác. – takbal

+0

@takbal Có, nó có thể, nhưng trong sự hiện diện của con trỏ, bạn có thể cần phải mô tả trạng thái bộ nhớ trong đó các hàm được gọi chính xác hơn. Tôi sẽ chỉnh sửa câu trả lời của mình với một ví dụ hoàn chỉnh hơn. Trong phần http://frama-c.com/download/frama-c-value-analysis.pdf phần 2.5.2, phân tích này được sử dụng để kiểm tra các phụ thuộc của hàm băm mật mã hoàn chỉnh, với con trỏ và tất cả (nhưng không phải là ví dụ nổi bật nhất kể từ khi mã được sạch sẽ và không có nhiều điều để nói. –

1

Tôi không biết thư viện nào thực hiện việc này cho bạn, nhưng tôi sẽ xem xét sử dụng cụm từ thông dụng để tìm chúng. Một cái gì đó như

rgrep "static \ s * int" đường dẫn/đến/src/root | grep -v = | grep -v "("

Điều đó sẽ trả về tất cả các biến tĩnh int được khai báo mà không có dấu bằng, và đường ống cuối cùng sẽ loại bỏ bất kỳ thứ gì có dấu ngoặc đơn trong chúng (loại bỏ funcion). làm việc chính xác cho bạn, nhưng chơi đùa với grep có thể là cách nhanh nhất để bạn theo dõi điều này.

Tất nhiên, khi bạn tìm được một công trình, bạn có thể thay thế int bằng tất cả các loại biến khác để tìm kiếm HTH

+0

Một sự xấu hổ nhưng thực tế tất cả các biến được khai báo tĩnh mà không có init ... nó có lẽ là hành vi sai trái của f2c. – takbal

0

Câu hỏi của tôi là cách phát hiện ra các lỗi này ...

Nhưng các lỗi này không phải là lỗi: kỳ vọng rằng biến tĩnh được khởi tạo thành 0 là hoàn toàn hợp lệ, như gán một số giá trị khác cho nó.

Vì vậy, yêu cầu một công cụ sẽ tự động tìm kiếm không -lỗi khó có khả năng tạo ra kết quả thỏa mãn.

Từ mô tả của bạn, có vẻ như somefunc() trả lại kết quả chính xác lần đầu tiên được gọi và kết quả không đúng về các cuộc gọi tiếp theo. Cách đơn giản nhất để gỡ lỗi các vấn đề như vậy là có hai phiên GDB song song: một phiên mới được nạp (sẽ tính toán câu trả lời đúng) và một với "lần lặp thứ hai" (sẽ tính toán câu trả lời sai). Sau đó, bước qua cả hai phiên "song song", và xem nơi tính toán hoặc luồng điều khiển của chúng bắt đầu phân kỳ.

Vì bạn thường có thể phân chia vấn đề một nửa, nên thường không mất nhiều thời gian để tìm lỗi. Lỗi rằng luôn luôn sao chép là những cách dễ nhất để tìm. Cứ làm đi.

+0

Như tôi đã nói trong bài viết gốc, tôi hiểu rằng từ quan điểm của trình biên dịch/liên kết, nó không phải là một lỗi. Câu hỏi này giống như cách biến loại sử dụng này thành một lỗi có thể được phát hiện tự động. – takbal

1

Công cụ phân tích mã tĩnh khá tốt trong việc tìm các lỗi lập trình điển hình như sử dụng các biến chưa được khởi tạo.Here là danh sách các công cụ miễn phí thực hiện việc này cho C.

Rất tiếc, tôi không thể đề xuất bất kỳ công cụ nào trong danh sách. Tôi chỉ quen thuộc với hai sản phẩm thương mại, CoverityKlocwork. Độ che phủ rất tốt (và đắt tiền). Klocwork là như vậy (nhưng ít tốn kém).

+0

Tôi đã thử nẹp trước đó nhưng không nói gì hữu ích. – takbal

+0

Tại sao bạn không chỉ khởi tạo tất cả các biến cục bộ về 0 khi chúng được khai báo? Đây có phải là cơ sở mã lớn không? – Miguel

+0

Vâng, nó rất lớn. Bạn đúng, tôi chắc chắn có thể làm một số thủ thuật regexp để biến tất cả các định nghĩa thành init dựa trên 0, hoặc thậm chí NaN để tăng gấp đôi. Đây là một con đường tôi đã không cố gắng cuối cùng. – takbal

1

Điều tôi đã làm cuối cùng sẽ bị xóa tất cả các vòng loại tĩnh khỏi mã bằng '#define static'. Điều này biến việc sử dụng tĩnh chưa được khởi tạo thành sử dụng không hợp lệ và loại lạm dụng mà tôi đang săn có thể bị các công cụ phát hiện. Trong trường hợp thực tế của tôi, điều này là đủ để xác định vị trí của lỗi, nhưng trong một tình huống chung hơn, nó sẽ được tinh chỉnh nếu tĩnh thực sự làm điều gì đó quan trọng, bằng cách dần dần thêm lại 'tĩnh' khi mã không thành công tiếp tục.

Các vấn đề liên quan