- Cpp2Rust là một trình dịch theo cú pháp dựa trên clang AST, tự động chuyển đổi đầu vào C++ thành mã Rust hoàn toàn an toàn
- Quá trình dịch phân tích tệp C++ bằng clang để tạo AST, duyệt AST và sinh mã Rust dưới dạng chuỗi, rồi xuất một tệp
.rs duy nhất bằng rustfmt
- Mặc định là mô hình đếm tham chiếu, và cũng có thể sinh unsafe Rust bằng
--model=unsafe để gỡ lỗi và so sánh hiệu năng
- Mã được sinh ra phụ thuộc vào thư viện runtime
libcc2rs; con trỏ C được chuyển đổi thành Ptr<T>, mô hình hóa null, phép toán số học trên con trỏ và aliasing
- Dịch toàn bộ chương trình cần
compile_commands.json; với dự án CMake có thể tạo bằng cờ CMAKE_EXPORT_COMPILE_COMMANDS=ON
Tự động chuyển đổi từ C++ sang Rust an toàn
Pipeline dịch
- Tệp C++ đầu vào trước tiên được phân tích bằng clang để tạo AST
- Sau đó duyệt AST và xuất mã Rust dưới dạng chuỗi
- Khi cần, chèn các lệnh gọi tới thư viện runtime
libcc2rs
- Ví dụ là xử lý raw pointer semantics
- Cuối cùng dùng
rustfmt để định dạng mã Rust thành một tệp .rs duy nhất
Mô hình Rust an toàn và unsafe Rust
- Hành vi mặc định sử dụng mô hình đếm tham chiếu và tạo đầu ra Rust hoàn toàn an toàn
- Cũng cung cấp bộ sinh unsafe Rust
- Đối số dòng lệnh là
--model=unsafe
- Mục đích là gỡ lỗi và so sánh hiệu năng
Thư viện runtime libcc2rs
- Mã được sinh ra phụ thuộc vào thư viện runtime được thiết kế để đơn giản hóa quá trình dịch
- Con trỏ C được chuyển đổi thành kiểu
Ptr<T> do libcc2rs cung cấp
Ptr<T> mô hình hóa ngữ nghĩa con trỏ C
- null
- phép toán số học trên con trỏ
- aliasing
- Borrow checker của Rust được thỏa mãn thông qua các thao tác runtime đã được kiểm tra
Luồng build và chạy
- Ví dụ cài đặt dependency trên Ubuntu bao gồm
libclang-22-dev, clang++-22, ninja-build, cmake, ruff
- Quá trình build diễn ra theo thứ tự
cmake -GNinja .., ninja, ninja check
- Lệnh dịch một tệp có dạng sau
./build/cpp2rust/cpp2rust --file=<file>.cpp -o=<file>.rs
- Để sinh unsafe Rust, chạy như sau
./build/cpp2rust/cpp2rust --file=<file>.cpp -o=<file>.rs --model=unsafe
Ví dụ tối thiểu và dịch toàn bộ chương trình
- Ví dụ
printf("hello world\n") trong hello.cpp được chuyển đổi thành mã bao gồm println!("hello world") của Rust
hello.rs sau khi chuyển đổi được biên dịch và chạy như sau
rustc hello.rs -L ../libcc2rs/target/debug
./hello
cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON ..
- Lệnh dịch toàn bộ chương trình có dạng sau
./build/cpp2rust/cpp2rust --dir=<dir> -o <output>.rs
<dir> phải là thư mục chứa compile_commands.json
Lệnh kiểm thử
- Chạy toàn bộ kiểm thử bằng
ninja check
- Chạy kiểm thử đơn vị bằng
ninja check-unit
- Chạy kiểm thử đơn vị của
libcc2rs bằng ninja check-libcc2rs
- Chạy kiểm thử đơn vị của
libcc2rs-macros bằng ninja check-libcc2rs-macros
- Nếu muốn tái tạo đầu ra kỳ vọng sau khi cố ý thay đổi, dùng
REPLACE_EXPECTED=1 ninja check-unit
1 bình luận
Ý kiến trên Lobste.rs
Tôi đã nghe bài trình bày của Dan Wallach vào tháng 2 năm nay, trong đó ông giới thiệu dự án TRACTOR (Translating All C to Rust) của DARPA [1] [2] [3]
Việc thử nghiệm và đánh giá chương trình nghiên cứu này hiện đang do nhóm tại MIT Lincoln Laboratory thực hiện
Tôi biết nó vẫn đang ở giai đoạn nghiên cứu, nhưng tôi rất quan tâm đến chủ đề này và khá tò mò xem những công cụ cũng như ý tưởng như thế này sẽ phát triển ra sao
Có vẻ như nó thực sự có thể loại bỏ nhiều lỗi “vặt” phát sinh từ quản lý bộ nhớ
[1] https://www.darpa.mil/research/programs/translating-all-c-to-rust
[2] https://github.com/DARPA-TRACTOR-Program
[3] https://ll.mit.edu/r-d/projects/…
Thú vị đấy. Sẽ rất tuyệt nếu có thể xem một hoặc hai bản demo thư viện hoặc ứng dụng mà chính tác giả đã chuyển đổi bằng công cụ này và cũng thấy đáng chú ý
Ví dụ, nếu xem https://github.com/Cpp2Rust/cpp2rust-testsuite/… thì có thể nói đó là Rust an toàn, nhưng chắc chắn khó mà gọi là Rust đúng phong cách
Tôi không thực sự hiểu vì sao cần bước chuyển đổi. Nếu đã có đủ thông tin để chuyển sang Rust an toàn bộ nhớ, thì chẳng phải cũng có đủ thông tin để đảm bảo C++ là an toàn bằng phân tích tĩnh hay sao?
Khoảng 70% lỗ hổng bảo mật trong phần mềm được phân phối rộng rãi xuất phát từ lỗi an toàn bộ nhớ trong các ngôn ngữ như C và C++, và dù đã đầu tư hàng chục năm vào các biện pháp giảm thiểu như phân tích tĩnh, sanitizer và cô lập phần cứng, kẻ tấn công vẫn tiếp tục khai thác các thao tác bộ nhớ không an toàn
Họ giải thích rằng về lâu dài, một giải pháp đầy hứa hẹn là chuyển các codebase C++ hiện có sang ngôn ngữ an toàn bộ nhớ như Rust, nhưng làm thủ công thì quá tốn kém và dễ phát sinh lỗi
Cpp2Rust được giới thiệu là hệ thống đầu tiên có thể tự động chuyển chương trình C++ thành mã Rust tương đương về chức năng và an toàn bộ nhớ
Bằng cách đánh đổi một phần hiệu năng lấy bảo mật, họ giải quyết sự không tương thích nền tảng giữa tham chiếu bí danh không bị hạn chế của C++ và mô hình sở hữu của Rust bằng cách chèn các kiểm tra quyền sở hữu và tính khả biến lúc chạy, từ đó bảo toàn ngữ nghĩa đồng thời đảm bảo an toàn
Để giảm chi phí của các kiểm tra động, họ cũng tạo ra tối ưu hóa liên thủ tục cho mã Rust, giúp loại bỏ các thao tác sở hữu dư thừa và khôi phục được phần lớn hiệu năng đã mất
Vì vậy, động cơ không chỉ đơn thuần là chứng minh tính an toàn, mà còn là chuyển sang Rust và giúp việc phát triển về sau bằng Rust trở nên dễ dàng hơn
Ý tưởng của các dự án kiểu này là tạo ra một điểm khởi đầu để tiếp tục refactor sang Rust đúng phong cách hơn, và khi đó mã sẽ trở nên có thể phân tích được
Hy vọng là từ mã đã chuyển đổi, người ta còn có thể loại bỏ các kiểm tra lúc chạy hoặc những phần không an toàn
Có thể bạn sẽ hỏi tại sao không refactor C++ thành “dạng giống Rust” để đạt khả năng phân tích tương tự, nhưng C++ có ngữ nghĩa khác và cho phép mức linh hoạt lớn hơn, khiến số đường đi cần phân tích tăng theo cấp số mũ hoặc dẫn phân tích tĩnh vào những ngõ cụt mơ hồ
Bạn sẽ phải hứa không làm những thứ gây khó cho bộ phân tích tĩnh và không khai thác các trường hợp biên; mà nếu vậy thì có thể tạo ra một phương ngữ C++ với ngữ nghĩa bị hạn chế hơn và các annotation bổ sung
Circle/Safe C++ đã cho thấy hướng này có tiềm năng, nhưng vì C++ WG đã phản đối mạnh mẽ hướng đó, nên rốt cuộc chỉ còn lựa chọn либо tự dùng một phương ngữ C++ giống Rust nhưng không được hỗ trợ, hoặc đơn giản là dùng Rust
Nếu chính mã đầu vào không an toàn, thì cụ thể nó sẽ hoạt động như thế nào?