Debug là một phần không thể thiếu trong phát triển phần mềm, cũng giống như thịt chó không thể thiếu mắm tôm, đi ị thì không thể không đái vậy. Và có nhiều khi thời gian chúng ta ngồi debug còn nhiều hơn thời gian code :). Debug là một công việc thú vị, ảo diệu nhưng đầy thách thức, đôi khi rất khó hiểu, gây cho chúng ta sự bực bội, điên tiết và chửi thề như chí phèo (kiểu như “cái đkm, đéo hiểu, ảo vc, cái nồi gì vậy…etc”). Chính vì vậy nên nếu chúng ta muốn giảm thời gian debug, giảm thiếu sự ức chế do debug gây ra thì chúng ta phải có kỹ năng, phải có phương pháp khi debug. Show
Trong phạm vi bài viết này Cpp•Developer sẽ chia sẻ với anh em một số kỹ năng debug trên Visual Studio. 1. Sử dụng Call StackCall Stack dùng để xem các lời gọi hàm hoặc thủ tục hiện có trong Stack. Để mở cửa sổ Call Stack thì khi đang debug, chọn menu Debug → Windows → Call Stack (xem hình dưới) Mở của sổ Call Stack Cửa sổ sẽ Call Stack hiển thị tên của từng hàm kèm theo danh sách tham số, dòng code đang chạy, tên của ngôn ngữ lập trình… Thông tin trên Call Stack Mũi tên màu vàng chỉ ra Stack Frame nơi con trỏ thực thi đang nằm. Theo mặc định thì các thông tin liên quan của Frame này sẽ hiển thị trong các cửa sổ Disassembly, Locals, Watch, và Autos. 2. Xem giá trị biến, biểu thức nhanh bằng chuộtKhi debug chúng ta luôn luôn phải chạy vào các hàm khả nghi để tìm hiểu xem cái quái gì đang xảy ra, lướt qua Call Stack để xem một số giá trị mờ ám bắt nguồn từ đâu … Những lúc như vậy, việc thêm biến/biểu thức vào danh sách theo dõi (watch) hoặc xem qua danh sách locals có thể mất khá nhiều thời gian. Rất may là Visual Studio có tính năng hỗ trợ để mọi thứ trở nên dễ dàng hơn. Nếu bạn chỏ chuột vào một biến mà bạn quan tâm thì giá trị của biến đó hoặc giá trị của tất cả các trường của nó (đối với class/struct) sẽ được show ra giúp bạn có thể kiểm tra một cách nhanh chóng và thuận tiện. Di chuột vào biến testStruct để xem giá trị các trường của nó 3. Thay đổi giá trị biến trực tiếp khi đang chạy (on-the-fly)Debugger (công cụ debug) không phải chỉ dùng để điều tra và tìm lỗi khi đã có lỗi xảy ra rồi. Nhiều lỗi có thể được ngăn chặn ngay từ lúc code bằng cách chạy thử qua hàm mới được viết và kiểm tra xem nó có hoạt động như mong đợi hay không. Đôi khi, bạn muốn biết hàm có chạy đúng nếu một điều kiện nào đó là “true” hoặc “false” hay không ? Hoặc hàm có chạy đúng nếu một biến nào đó nhận một giá trị cụ thể nào đó hay không ? Hầu hết những trường hợp như vậy bạn có thể test được luôn trong lúc debug mà không cần sửa code và chạy lại. Chỉ cần di chuột vào biến, nhấp đúp vào giá trị, nhập vào giá trị mà bạn muốn, enter và sau đó tiếp tục debug. 4. Chạy dòng lệnh được chỉ địnhMột trường hợp thường hay gặp khi debug là phân tích lý do tại sao một hàm lại trả về lỗi bằng cách chạy các hàm từng step một. Và thật là thốn nếu chúng ta phát hiện ra rằng một hàm vừa call một hàm khác và hàm đó là hàm trả về lỗi ? Khi ta vừa phát hiện ra hàm functionA trả về false nhưng lại lỡ chạy qua hàm đó mất rồi. Thốn vãi ! Chả nhẽ lại chạy lại và debug lại để phi vào cái hàm vừa trả về lỗi xem nó làm cái lồi gì mà lỗi ? Vâng, rất may là Visual Studio thấu hiểu anh em chúng ta và cung cấp cho chúng ta phương pháp dễ chịu hơn rất nhiều. Chúng ta chỉ cần kéo mũi tên màu vàng vào dòng code mà mình muốn chạy (kéo luôn vào cái dòng gọi cái hàm ngu ngu vừa trả về lỗi chứ còn gì nữa), sau đó F11 để phi vào hàm đó mà check. Nuột phải không anh em ? Kéo mũi tên màu vàng vào dòng gọi hàm functionA và F11 để debug vào trong hàm 5. Sửa code và build không cần restart lại debugKhi đang debug một chương trình phức tạp, bug khù khoằm ? Chúng ta đã tìm ra nguyên nhân ở đâu, nhưng không muốn mất thời gian stop chương trình, build lại và chạy lại (vì để tái hiện lại hiện trường là khá phức tạp). Không sao, chỉ cần fix bug tại chỗ và tiếp tục debug. Visual Studio sẽ build chương trình, áp dụng code mới và tiếp tục debug mà không cần phải khởi động lại. Tuy nhiên để làm việc này thì cần một số điều kiện sau:
Chuột phải vào project trên Solution Explorer → Properties (hoặc bấm Alt + Enter) Tiếp theo, chọn C/C++ → General → Debug Information Format, chọn “Program Database for Edit And Continue (/ZI)”
6. Sử dụng Watch WindowWatch Window là cửa sổ sử dụng để theo dõi sự thay đổi giá trị của các biến (local hay global đều được hết). Để mở Watch Window có 2 cách
Để theo dõi một biến ở Watch Window bạn có 2 cách
Ngoài ra, thông tin có thể xem được từ Watch Window không giới hạn ở các biến bình thường. Bạn có thể nhập tên của một số biến đặc biệt để theo dõi, ví dụ:
7. Sử dụng Threads WindowThreads Window khá hữu ích khi debug các ứng dụng đa luồng (multi-threading). Nó cho chúng ta thông tin về các Threads đang chạy, điểm Breakpoint đang dừng đang được thực thi trên Thread nào (đánh dấu bằng mũi tên màu vàng) Để mở Threads Window có 2 cách
8. Conditional BreakpointsNếu bạn muốn dừng chương trình tại một line code nào đó nhưng bạn không muốn cứ chạy qua đấy là dừng mà cần phải có điều kiện nào đó mới dừng thì bạn có thể làm điều đó bằng cách tạo Conditional Breakpoint. Giả sử có vòng lặp đơn giản như sau, i chạy từ 0 đến 99 Nếu ta muốn trình debug dừng lại tại dòng printf(“i = %d\n”, i); chỉ khi nào i đang có giá trị là 5, ta làm như sau: Đặt Breakpoint tại dòng đó → click chuột phải vào breakpoint (màu đỏ) → chon Conditions… Nhập “i == 5” như hình dướiChạy debug và chương trình sẽ dừng tại Breakpoint khi i có giá trị 59. Sử dụng Memory WindowCó những bug liên quan đến căn chỉnh bộ nhớ, buffer overflow đòi hỏi chúng ta phải theo dõi giá trị của memory một cách chi tiết và sát sao mới có thể phát hiện ra. Trong những tình huống như vậy chúng ta sẽ cần đến Memory Window. Để mở Memory Window có 2 cách
Nếu muốn vùng nhớ nào đó hiển thị lên trên cùng của Memory Window cho tiện theo dõi thì có thể nhập địa chỉ của vùng nhớ đó vào ô “Address”, xem hình dưới 10. Data BreakpointsTrong các chương trình phức tạp, đôi khi chúng ta gặp trường hợp giá trị của một biến hay một vùng nhớ nào đó bị thay đổi nhưng có quá nhiều đoạn code truy cập đến thay đổi biến/vùng nhớ đó. Chúng ta sẽ rất khó khăn để debug theo cách thông thường vì méo biết đặt debug vào đâu. Trong những trường hợp như vậy thì Data Breakpoints là sự lựa chọn hoàn hảo. Mục đích của Data Breakpoints là làm cho trình debug dừng lại khi một vùng nhớ tại một địa chỉ cụ thể (do chúng ta chỉ định) có sự thay đổi. Ví dụ có chương trình sau → Có một biến toàn cục gTest kiểu int (4 bytes). Có 2 hàm cùng thay đổi giá trị của gTest là functionA và functionB, 2 hàm này được chạy trên 2 threads khác nhau. Bây giờ tôi muốn chương trình sẽ tạm dừng khi gTest bị thay đổi, tôi sẽ làm như sau
→ Paste địa chỉ của gTest vào ô Address, chọn số byte của vùng nhớ (ở đây là 4) → OK
→ Bấm OK, vào phi vào Call Stack xem hàm nào thay đổi giá trị của gTest → Nhìn vào Call Stack thì thấy hàm functionA chính là thủ phạm. Việc bây giờ khá đơn giản, phi vào functionA xem nó đang chạy ở dòng nào. Đây chỉ là một ví dụ đơn giản để demo cách sử dụng Data Breakpoint. Các bạn hãy áp dụng linh hoạt vào những trường hợp cụ thể mà mình gặp phải, sẽ rất hữu ích đấy. |