2 điểm bởi GN⁺ 2025-09-24 | 2 bình luận | Chia sẻ qua WhatsApp
  • Ngôn ngữ Go đã chính thức bổ sung hỗ trợ Valgrind
  • Thay đổi này giúp tăng cường khả năng phát hiện lỗi bộ nhớgỡ lỗi
  • Nhà phát triển có thể dễ dàng phát hiện hơn rò rỉ bộ nhớ và lỗi truy cập
  • Việc cải thiện khả năng tương thích với Valgrind giúp công việc porting và bảo trì trở nên hiệu quả hơn
  • Việc đánh giá độ ổn định của mã Go trên nhiều nền tảng trở nên dễ dàng hơn

Tầm quan trọng của việc đưa hỗ trợ Valgrind vào Go

  • Go đã được bổ sung hỗ trợ Valgrind, cho phép nhà phát triển chính thức sử dụng công cụ phát hiện lỗi bộ nhớ
  • Thay đổi này cho phép xác định các vấn đề như use-after-free, rò rỉ bộ nhớ, truy cập bộ nhớ không hợp lệ trong mã Go
  • Valgrind được sử dụng rộng rãi để phát hiện các vấn đề bộ nhớ trong nhiều ngôn ngữ, và với cộng đồng Go đây là một thay đổi quan trọng để tăng cường độ tin cậy và độ bền vững
  • Tính năng được bổ sung giúp việc gỡ lỗi, kiểm chứng chất lượng, đánh giá độ ổn định của chương trình Go trên nhiều nền tảng trở nên thuận tiện hơn
  • Ý nghĩa chính của bản cập nhật này là lớp runtime của Go nay đã bao gồm mã instrumentation cho Valgrind

Valgrind là gì?

  • Valgrind là một công cụ phát triển mã nguồn mở dùng để kiểm tra lỗi bộ nhớ, lỗi luồng, rò rỉ bộ nhớ, v.v.
  • Công cụ này được sử dụng rộng rãi trong các ngôn ngữ lập trình hệ thống như C, C++, và cung cấp khả năng phát hiện chính xác các vấn đề quản lý bộ nhớ

Tóm tắt về lần bổ sung tính năng này

  • Instrumentation mã do thay đổi lần này mang lại là tính năng cho phép Valgrind theo dõi chính xác các sự kiện liên quan đến bộ nhớ được cấp phát động trong runtime Go
  • Nhà phát triển có thể chạy chương trình Go bằng Valgrind để chẩn đoán hiệu quả các vấn đề bộ nhớ tiềm ẩn hoặc truy cập con trỏ sai
  • Kết quả là, hạ tầng hoặc dịch vụ dựa trên Go sẽ có lợi thế trong việc duy trì mã chất lượng cao và phòng ngừa sự cố từ sớm

Hiệu quả được kỳ vọng từ thay đổi này

  • Quy trình phát hiện lỗi bộ nhớcải thiện chất lượng mã trong các dự án Go được kỳ vọng sẽ trở nên chính xác hơn
  • Việc đảm bảo khả năng tương thích và độ tin cậy của mã Go được triển khai trên nhiều nền tảng được dự đoán sẽ trở nên dễ dàng hơn

2 bình luận

 
taptaps 2025-09-25

Mỗi lần xem các bài đăng về ngôn ngữ Go, tôi luôn có cảm giác trong phần bình luận thế nào cũng sẽ có mấy ý kiểu
'Rust thì đâu có thế', 'Rust thì đâu cần như vậy' xuất hiện, haha

 
GN⁺ 2025-09-24
Ý kiến Hacker News
  • Tôi là tác giả của CL này; với cải tiến lần này, tôi muốn thử tận dụng tính năng theo dõi khởi tạo bộ nhớ để kiểm tra xem mã crypto có chạy trong thời gian cố định hay không (kiểm thử constant-time), theo cách tương tự đề xuất của agl khoảng 15 năm trước và cách BoringSSL đã làm liên kết liên quan, vì đây là một thuộc tính thực sự rất khó kiểm thử. Tôi cũng kỳ vọng sẽ có nhiều hiệu ứng thú vị khác, như tiếp tục bật khả năng dùng Valgrind trong Go và khám phá xem có thể theo dõi đúng cách việc xử lý bộ nhớ của runtime hay không. Tuy nhiên cần nhấn mạnh rằng hỗ trợ này vẫn còn ở giai đoạn thử nghiệm; tôi chưa thể chắc chắn 100% rằng mọi cấu hình đều hoạt động tốt, và có thể sẽ xuất hiện thêm các cảnh báo khó hiểu.
    • Tôi tự hỏi liệu cộng đồng có chỗ nào có thể hỗ trợ phát triển thêm không.
    • Tôi nghĩ đây là một tính năng thực sự tuyệt vời, và hy vọng nhờ đó có thể tìm ra thêm những vấn đề khác của Go. Nhưng tôi tự hỏi liệu có nhất thiết phải theo dõi phức tạp như vậy không; chẳng hạn chỉ cần đưa nhiều đầu vào khác nhau vào hàm mã hóa rồi đo trực tiếp thời gian thực thi, nếu đều như nhau thì có thể kiểm tra được tính constant-time chăng. Ví dụ, ngay cả khi có Garbage Collection hay nhiễu từ OS, nếu đưa nhiều input vào rồi đo thời gian và tất cả nằm trong sai số epsilon thì có vẻ cũng đủ tốt. Ngoài ra, một số CPU có bộ đếm nhánh điều kiện, rr debugger cũng dùng nó; có thể so sánh số lần rẽ nhánh trước và sau giải mã bằng giá trị đó (liên quan đến lập luận trong bài của agl rằng số nhánh phải giống nhau thì mới có constant-time). Cũng có thể lấy thời gian tối đa của 10 lần giải mã đầu tiên rồi cộng thêm một chút biên độ, sau đó thêm time padding ở mỗi lần giải mã tiếp theo (chẳng hạn chạy noop) để ép thời gian bằng nhau. Thậm chí có thể đặt assert để chương trình crash nếu vượt quá ngưỡng trên. Chỉ là nếu bị OS tước lịch chạy hoặc có nhiễu thời gian khác thì cách này sẽ khó áp dụng.
  • Tôi rất thích việc thay vì thêm header của Valgrind vào cây mã rồi dùng cgo để gọi đủ loại macro client request của Valgrind, anh lại chọn cách dùng một hàm assembly duy nhất để phát trực tiếp lệnh cần thiết và kích hoạt client request. Với toolchain bootstrap, đây đúng là cách tiếp cận đáng mong muốn: chỉ tạo ra những building block tối thiểu và xử lý mọi thứ còn lại ở cấp độ ngôn ngữ.
    • Nếu không chọn cách này mà dùng cách cũ hoặc né sang một phương án khác, thì anh sẽ phải làm thế nào để vẫn giữ quy trình đơn giản như Go nhưng hiệu năng gần tương đương? Tôi nghĩ đây là một bài toán vẫn còn phải tiếp tục giải.
  • Thật vui khi thấy rsc vẫn đang đóng góp rất tích cực, đặc biệt là việc còn để lại cả bình luận trong commit message. Càng lớn tuổi tôi càng thấy commit message quan trọng; nếu chỉ ghi đơn giản kiểu "thêm valgrind" thì sau này khi tra lại kho lưu trữ sẽ chẳng giúp ích được bao nhiêu.
    • rsc đúng là lập trình viên tầm rockstar. Giờ ông ấy cũng đang thử nhiều hướng mới như dùng AI để quản lý issue hay PR, và tôi kỳ vọng sẽ đạt được khá nhiều kết quả.
  • Tính năng này chỉ hiệu quả khi test được áp dụng trên mọi package; nếu không thì nó sẽ bị chôn vùi giữa các cảnh báo không liên quan. Chính vì vậy mà rất khó dùng Valgrind với mã Python.
    • Nếu điều đó đúng thì lẽ ra C và C++ cũng phải như vậy. Trường hợp của tôi là dùng Valgrind cho một chương trình hybrid Python + Boost C++, và chỉ cần dành một tiếng làm file suppressions là đã có thể dùng ổn.
    • Có vẻ local LLM sẽ hữu ích để sắp xếp và tóm tắt lượng thông tin quá lớn như thế, nên vai trò của một toolchain có sẵn pin như Valgrind là rất quan trọng.
    • Tôi muốn hỏi trong môi trường Go, "mọi package đều được test" cụ thể có nghĩa là gì.
  • Valgrind là một siêu năng lực bị ẩn giấu. Với hầu hết phần mềm tôi viết, tôi chạy test case bằng make check, rồi chạy lại y hệt bằng make check-valgrind trong môi trường Valgrind. Cái sau chỉ dùng trên máy của lập trình viên. Làm như vậy thường xuyên giúp phát hiện rò rỉ bộ nhớ hoặc những lỗi rất tinh vi.
    • Tôi đồng ý một phần, nhưng khi bước vào multithreading (thứ Go dùng rất nhiều) thì lớp trừu tượng của Valgrind hoạt động không tốt. Đây là cảm nhận từ lần cuối tôi đào khá sâu vào mã C++: vì nó dùng scheduler riêng nên các vấn đề về đồng thời thực tế, race condition, v.v. trong tình huống thật lại không lộ ra rõ trên Valgrind. Và nói chung mức giảm hiệu năng khá nặng. Dù vậy, nó đúng là đã nhiều lần giúp ích rất lớn, và tôi biết ơn vì nó vẫn tồn tại.
  • Hỗ trợ lần này thực sự rất ngầu, và nhờ đó có lẽ sẽ lộ ra thêm một số bug. Điều tôi thắc mắc là tại sao lại chọn Valgrind. Tôi nghĩ Clang AddressSanitizer (asan) và MemorySanitizer (msan) tìm được nhiều loại lỗi hơn (ví dụ use-after-return), lại còn nhanh hơn rất nhiều.
    • Go không dùng clang/llvm nên các công cụ đó không áp dụng được.
    • Go thực ra đã có hỗ trợ msan/asan riêng từ vài năm trước rồi.
    • Valgrind nhanh hơn rất nhiều, và còn có thể attach vào chương trình đang chạy.
    • Valgrind còn có nhiều chức năng như theo dõi bộ nhớ, profiling bộ nhớ, v.v., nên xét từ góc độ theo dõi hiệu năng thì cũng cực kỳ xuất sắc.
  • Rất ấn tượng. Một trong những vấn đề lớn nhất của Go là profiling và hiện tượng rò rỉ/áp lực bộ nhớ xảy ra thường xuyên, mà hiện tại tôi không biết rõ có công cụ thay thế nào để xử lý tốt chuyện này.
    • Tôi muốn nghe chi tiết hơn: cụ thể anh đang gặp vấn đề profiling gì? Nếu inuse memory profile không đủ để lần ra memory leak (không biết anh đã dùng gorefs chưa) thì loại áp lực bộ nhớ nào đang gây vấn đề? Nếu anh chia sẻ rõ hơn thì sẽ hữu ích repo goref, nhân tiện tôi đang làm continuous profiling ở Datadog và vẫn đều đặn đóng góp cho profiling của Go runtime.
    • Tôi thắc mắc trong một ngôn ngữ có GC thì "rò rỉ bộ nhớ kéo dài" xảy ra như thế nào.
    • Lý tưởng nhất thì đáng ra phải cho phép kiểm soát tường minh cái gì được đặt trên stack như ở các ngôn ngữ khác (thay vì chỉ trông vào escape analysis); hiện tại phải chỉnh bằng các kiểu tùy chọn như -gcflags -m=3 hoặc các thiết lập trong plugin Go của VSCode như ui.codelenses, ui.diagnostic.annotations, nên khá bất tiện.
    • Về chuyện "rò rỉ/áp lực bộ nhớ kéo dài", trong Go thì không nên tạo goroutine nếu bạn không biết chắc cách cleanup nó.
    • pprof cũng hoạt động khá tốt; tôi tò mò anh còn muốn thêm tính năng gì nữa.
  • Nỗ lực lần này có vẻ ổn. Nếu cơ chế client request sau này thay đổi thì có rủi ro, nhưng header thì hầu như không đổi (đặc biệt là khi thêm nền tảng mới). Go lại chỉ xử lý amd64 và arm64 nên càng ít vấn đề hơn. Điểm mạnh thực sự của cải tiến này không hẳn là chống memory leak mà là nhận diện chính xác bộ nhớ chưa được khởi tạo, vì khi bộ nhớ được tái sử dụng mà không được "poisoning" đúng cách thì việc phân tích sẽ rất khó. Tính năng này cũng khá hữu ích cho các công cụ khác ngoài cachegrind và callgrind.
  • Với tôi, thay vì là một lợi ích, hỗ trợ này hơi giống một thất bại nhỏ của hệ sinh thái Go. Tôi thực sự rất thích Valgrind và đã dùng nó thường xuyên khi còn là lập trình viên C. Nhưng việc Go lại cần đến Valgrind khiến tôi có cảm giác ngôn ngữ hay hệ sinh thái vẫn còn điều gì đó chưa ổn. Tôi đã dùng Rust khoảng 6 năm và chưa từng cảm thấy cần Valgrind, ngoại trừ một lần đồng đội dùng tới. Dù tôi biết cảm giác này có lẽ đến từ cgo, nhưng vẫn khó tránh cảm giác như đang thụt lùi.
    • Tôi không hiểu vì sao các bình luận top về Go lúc nào cũng phải nhắc đến Rust theo kiểu mỉa mai như vậy. Nó ngày càng tạo cảm giác vừa phòng thủ vừa pha chút tự cho mình hơn người.
    • Tính năng lần này chủ yếu nhắm đến việc dùng cho kiểm thử mã constant-time hơn là theo dõi bộ nhớ thuần túy, giải thích liên quan vẫn còn chút thông tin về chuyện đó.
    • Tôi cũng từng dùng Valgrind một chút với Rust. Không phải lúc nào cũng cần nhiều, nhưng rõ ràng vẫn có lúc cần. Rust là ngôn ngữ được dùng trong rất nhiều môi trường khác nhau, từ mã hàm mức cao đến mã nhúng kiểu C rất thấp tầng; có nơi hoàn toàn không dùng unsafe, nhưng cũng có nơi bắt buộc phải dùng. Việc dùng FFI với C cũng rất phổ biến, nên sớm muộn gì cũng có nhu cầu. Trước đây khi tôi làm một module Rust cho nginx (thời chưa có binding chính thức hay không chính thức), tôi hay mắc lỗi nên từng nhận được nhiều trợ giúp từ valgrind.
    • Tần suất dùng phụ thuộc vào việc bạn viết bao nhiêu mã unsafe, dùng bao nhiêu unsafe crate, hay liên kết với thư viện C/C++ ở mức nào. Ngay cả với Java, .NET hay Node, đôi khi vì phụ thuộc bên ngoài mà vẫn phát sinh nhu cầu như vậy.