2012-11-12 5 views
7

Dự án kế thừa mà tôi đang thực hiện bao gồm một số thư viện bên ngoài dưới dạng tập hợp các tệp jar nhị phân. Chúng tôi quyết định rằng để phân tích và vá tiềm năng, chúng tôi muốn nhận các nguồn của thư viện này, sử dụng chúng để xây dựng các tệp nhị phân mới và sau khi chuyển đổi kiểm tra hồi quy đủ chi tiết và đủ dài cho các tệp nhị phân này.Cách kiểm tra xem các tệp nhị phân có được xây dựng từ các nguồn cụ thể hay không

Giả sử rằng chúng tôi đã truy xuất và xây dựng các nguồn (tôi thực sự đang trong giai đoạn lập kế hoạch). Trước khi thử nghiệm thực sự, tôi muốn thực hiện một số "kiểm tra khả năng tương thích" để loại trừ khả năng rằng các nguồn đại diện cho một cái gì đó đáng kể khác với những gì có trong các tệp nhị phân "cũ".

Sử dụng công cụ javap Tôi đã có thể trích xuất phiên bản JDK được sử dụng để biên soạn (ít nhất tôi tin rằng đó là phiên bản của JDK). Nó nói rằng, các tập tin nhị phân được xây dựng bằng cách sử dụng phiên bản 46 lớn và nhỏ 0. Theo this article nó ánh xạ tới JDK 1.2.

Giả sử rằng cùng một JDK sẽ được sử dụng để biên dịch nguồn.

Câu hỏi là: Có phương pháp xác minh đáng tin cậy và có thể hiệu quả nếu cả hai tệp nhị phân này được tạo từ cùng một nguồn không? Tôi muốn biết nếu tất cả các chữ ký phương thức và các định nghĩa lớp là giống nhau và nếu hầu hết hoặc có thể tất cả các phương thức triển khai thực hiện giống hệt nhau/tương tự nhau.

Thư viện là khá lớn, vì vậy tôi nghĩ rằng phân tích chi tiết các tệp nhị phân bị phân tích có thể không phải là một tùy chọn.

+0

Reflection ('java.lang.reflect') nên làm cho chữ ký lớp học và phương pháp, nhưng không thực hiện. – SJuan76

+0

Điều gì về việc so sánh băm MD5 của hai tập tin nhị phân? – sp00m

+1

Để tham khảo trong tương lai, cách dễ nhất để tìm ra điều này là không sử dụng hệ thống điều khiển phiên bản như Git, Subversion hoặc Mercurial, sau đó bao gồm số sửa đổi và/hoặc ID thay đổi trong jar, chẳng hạn như trong tệp kê khai. – Brian

Trả lời

1

Tôi đề xuất quy trình nhiều giai đoạn:

Áp dụng báo cáo được đề xuất trước đó hoặc tương tự để xem có sự khác biệt về API hay không. Nếu có thể, hãy chọn một công cụ có tùy chọn để báo cáo các phương thức riêng tư. Trong thực tế, bất kỳ thay đổi thực hiện đáng kể nào trong Java đều có khả năng thay đổi một số phương thức và lớp, ngay cả khi API công cộng không thay đổi.

Nếu bạn có đối sánh API, hãy biên dịch một vài tệp được chọn ngẫu nhiên với trình biên dịch được chỉ định, dịch ngược kết quả và tệp lớp gốc và so sánh kết quả. Nếu chúng khớp nhau, hãy áp dụng cùng một quy trình cho các mã lớn hơn và lớn hơn cho đến khi bạn tìm thấy sự không phù hợp hoặc đã kiểm tra mọi thứ.

Sự khác biệt của mã bị biên dịch có nhiều khả năng cung cấp cho bạn manh mối về bản chất của sự khác biệt và dễ lọc hơn những khác biệt không quan trọng, so với tệp lớp thực tế.

Nếu bạn không khớp, hãy phân tích nó. Nó có thể là do một cái gì đó bạn không quan tâm. Nếu vậy, hãy thử xây dựng một tập lệnh sẽ xóa hình thức khác biệt đó và tiếp tục quá trình biên dịch và so sánh. Nếu bạn gặp sự không tương thích rộng rãi, hãy thử nghiệm với các tham số của trình biên dịch như tối ưu hóa. Nếu điều chỉnh các thông số trình biên dịch loại bỏ sự khác biệt, tiếp tục với so sánh số lượng lớn. Mục tiêu trong giai đoạn này là tìm sự kết hợp các tham số của trình biên dịch và các bộ lọc mã được biên dịch để tạo ra một kết quả phù hợp trên các tệp mẫu và áp dụng chúng để so sánh hàng loạt thư viện.

Nếu bạn không thể nhận được kết quả trùng khớp gần đúng trong mã bị giải mã, có thể bạn không có mã nguồn phù hợp. Mặc dù vậy, nếu bạn có một kết hợp API nó có thể có giá trị xây dựng hệ thống của bạn và chạy thử nghiệm của bạn bằng cách sử dụng kết quả của việc biên dịch. Nếu các thử nghiệm của bạn chạy ít nhất là tốt với phiên bản bạn đã tạo từ nguồn, hãy tiếp tục sử dụng nó.

+0

Tôi quyết định sử dụng hầu hết các đề xuất của bạn. Cảm ơn bạn :) –

0

Có nhiều công cụ so sánh JAR khác nhau. Một ứng dụng được sử dụng khá tốt là Jardiff. Tôi đã không sử dụng nó trong một thời gian nhưng tôi chắc chắn nó vẫn còn có sẵn. Ngoài ra còn có một số dịch vụ thương mại trong cùng một không gian có thể phù hợp với nhu cầu của bạn.

0

Cảnh báo rằng Nhận thức được đề cập là một khởi đầu tốt, tuy nhiên không có cách nào để làm điều đó 100% phần trăm chắc chắn về mặt lý thuyết. Điều này là do cùng một nguồn có thể được biên dịch với các trình biên dịch khác nhau và các cấu hình trình biên dịch khác nhau và các mức tối ưu hóa. Vì vậy, không có cách nào để so sánh mã nhị phân (bytecode) ngoài chữ ký lớp và phương thức.

Bạn có ý nghĩa gì với "triển khai tương tự" của một phương pháp? Giả sử rằng một trình biên dịch thông minh giảm một trường hợp else vì nó chỉ ra rằng điều kiện có thể không đúng. Hai cái này có giống nhau không? Có và không .. :-)

Cách tốt nhất để truy cập IMHO là thiết lập các trường hợp kiểm tra hồi quy rất tốt để kiểm tra mọi tính năng chính của thư viện của bạn. Điều này có thể là một nỗi kinh hoàng, nhưng về lâu dài có thể rẻ hơn săn bắt bọ. Tất cả phụ thuộc vào kế hoạch tương lai của bạn trong dự án này. Không phải là một quyết định dễ dàng tầm thường.

0

Đối với chữ ký phương thức, hãy sử dụng công cụ như cảnh báo.

Để thực hiện tương tự, bạn phải quay lại dự đoán tự nhiên. So sánh bytecode trên mức Opcode có thể phụ thuộc vào trình biên dịch và dẫn đến một số lượng lớn các âm bản sai. Nếu trường hợp này xảy ra, bạn có thể quay lại để so sánh các phương thức của một lớp bằng cách sử dụng LineNumberTable.

Nó cung cấp cho bạn danh sách số dòng cho mỗi phương pháp (miễn là tệp lớp đã được biên dịch với cờ gỡ lỗi, thường bị thiếu trong thư viện rất cũ hoặc thương mại).

Nếu hai tệp lớp được biên dịch từ cùng một mã nguồn, thì ít nhất số dòng của mỗi phương thức phải khớp chính xác.

Bạn có thể sử dụng một thư viện như Apache BCEL để lấy LineNumberTable:

// import org.apache.bcel.classfile.ClassParser; 
    JavaClass fooClazz = new ClassParser("Foo.class").parse(); 
    for(Method m : fooClazz.getMethods()) 
    { 
    LineNumberTable lnt = m.getLineNumberTable(); 
    LineNumber[] tab = lnt.getLineNumberTable(); 
    for(LineNumber ln : tab) 
    { 
     System.out.println(ln.getLineNumber()); 
    } 
    }