Trước hết, tôi nhận ra đây không phải là một câu hỏi kiểu hỏi đáp hoàn hảo với một câu trả lời tuyệt đối, nhưng tôi không thể nghĩ ra bất kỳ từ ngữ nào để làm cho nó hoạt động tốt hơn. Tôi không nghĩ có một giải pháp tuyệt đối cho vấn đề này và đây là một trong những lý do tại sao tôi đăng nó ở đây thay vì Stack Overflow.
Trong tháng trước tôi đã viết lại một đoạn mã máy chủ khá cũ (mmorpg) để hiện đại hơn và dễ dàng mở rộng / mod hơn. Tôi bắt đầu với phần mạng và triển khai thư viện bên thứ 3 (miễn cưỡng) để xử lý công cụ cho tôi. Với tất cả các thay đổi mã và bao thanh toán, tôi đã giới thiệu tham nhũng bộ nhớ ở đâu đó và tôi đã phải vật lộn để tìm ra nơi nó xảy ra.
Tôi dường như không thể tái tạo nó một cách đáng tin cậy trên môi trường dev / test của mình, ngay cả khi triển khai các bot nguyên thủy để mô phỏng một số tải tôi không gặp sự cố nữa (Tôi đã khắc phục sự cố không đáng có gây ra một số nội dung)
Tôi đã thử cho đến nay:
Đánh bại địa ngục khỏi nó - Không có ghi không hợp lệ cho đến khi sự cố xảy ra (có thể mất hơn 1 ngày sản xuất .. hoặc chỉ một giờ) thực sự gây trở ngại cho tôi, chắc chắn đến một lúc nào đó, nó sẽ truy cập vào bộ nhớ không hợp lệ và không ghi đè lên công cụ cơ hội? (Có cách nào để "dàn trải" phạm vi địa chỉ không?)
Các công cụ phân tích mã, cụ thể là độ phủ và cppcheck. Trong khi họ đã chỉ ra một số trường hợp khó chịu và cạnh trong mã thì không có gì nghiêm trọng.
Ghi lại quá trình cho đến khi nó gặp sự cố với gdb (thông qua undodb) và sau đó làm việc theo cách của tôi ngược lại. Điều này / âm thanh / giống như nó có thể thực hiện được, nhưng tôi sẽ kết thúc sự cố gdb bằng cách sử dụng tính năng tự động hoàn thành hoặc tôi kết thúc trong một cấu trúc tự nguyện nội bộ nơi tôi bị lạc vì có quá nhiều nhánh có thể xảy ra (một lỗi gây ra lỗi khác và do đó trên). Tôi đoán sẽ thật tuyệt nếu tôi có thể thấy con trỏ ban đầu thuộc về / nơi nó được phân bổ, điều đó sẽ loại bỏ hầu hết các vấn đề phân nhánh. Tôi không thể chạy valgrind với undodb, và tôi bản ghi gdb bình thường chậm một cách bất thường (nếu điều đó thậm chí hoạt động kết hợp với valgrind).
Xem lại mã! Một mình (triệt để) và có một số người bạn xem qua mã của tôi, mặc dù tôi nghi ngờ nó đã đủ kỹ lưỡng. Tôi đã suy nghĩ về việc có thể thuê một nhà phát triển để thực hiện một số đánh giá / gỡ lỗi mã với tôi, nhưng tôi không đủ khả năng để bỏ quá nhiều tiền vào đó và tôi không biết tìm ai đó sẵn sàng làm việc cho ít- không - không có tiền nếu anh ta không tìm thấy vấn đề hoặc bất cứ ai đủ điều kiện.
Tôi cũng nên lưu ý: tôi thường nhận được backtraces nhất quán. Có một vài nơi xảy ra sự cố, chủ yếu liên quan đến lớp socket bị hỏng theo cách nào đó. Có thể là một con trỏ không hợp lệ chỉ vào một cái gì đó không phải là một socket hoặc chính lớp socket bị ghi đè lên (một phần?) Với sự vô nghĩa. Mặc dù tôi nghi ngờ nó bị sập ở đó nhiều nhất vì đó là một trong những bộ phận được sử dụng nhiều nhất, vì vậy đó là bộ nhớ bị hỏng đầu tiên được sử dụng.
Tất cả trong tất cả các vấn đề này đã khiến tôi bận rộn gần 2 tháng (nhiều lần và nhiều hơn, một dự án sở thích) và thực sự làm tôi thất vọng đến mức tôi trở thành IRL gắt gỏng và nghĩ về việc từ bỏ. Tôi chỉ không thể nghĩ về những gì tôi phải làm để tìm ra vấn đề.
Có bất kỳ kỹ thuật hữu ích tôi đã bỏ lỡ? Làm thế nào để bạn đối phó với điều đó? (Không thể phổ biến vì không có nhiều thông tin về điều này .. hoặc tôi thực sự bị mù?)
Chỉnh sửa:
Một số thông số kỹ thuật trong trường hợp nó quan trọng:
Sử dụng c ++ (11) qua gcc 4.7 (phiên bản được cung cấp bởi debian wheezy)
Các codebase là khoảng 150k dòng
Chỉnh sửa để phản hồi bài đăng của david.pfx: (xin lỗi vì phản hồi chậm)
Bạn đang giữ hồ sơ cẩn thận của các sự cố, để tìm kiếm các mẫu?
Vâng, tôi vẫn còn những đống đổ vỡ gần đây
Là một vài nơi thực sự giống nhau? Bằng cách nào?
Chà, trong phiên bản gần đây nhất (chúng dường như thay đổi bất cứ khi nào tôi thêm / xóa mã hoặc thay đổi cấu trúc liên quan), nó sẽ luôn bị bắt trong một phương thức hẹn giờ vật phẩm. Về cơ bản một mặt hàng có một thời gian cụ thể sau đó nó hết hạn và nó sẽ gửi thông tin cập nhật cho khách hàng. Con trỏ socket không hợp lệ sẽ ở trong lớp (vẫn hợp lệ theo như tôi có thể nói) lớp Player, chủ yếu liên quan đến điều đó. Tôi cũng đang trải qua vô số sự cố trong giai đoạn dọn dẹp, sau khi tắt máy bình thường, nơi nó phá hủy tất cả các lớp tĩnh chưa bị phá hủy rõ ràng ( __run_exit_handlers
trong backtrace). Chủ yếu liên quan đến std::map
một lớp, đoán rằng đó chỉ là điều đầu tiên xuất hiện.
Dữ liệu tham nhũng trông như thế nào? Số không? Thăng thiên? Hoa văn?
Tôi chưa tìm thấy bất kỳ mẫu nào, dường như hơi ngẫu nhiên đối với tôi. Thật khó để nói vì tôi không biết tham nhũng bắt đầu từ đâu.
Có liên quan đến đống?
Nó hoàn toàn liên quan đến đống (Tôi đã kích hoạt bảo vệ ngăn xếp của gcc và điều đó không bắt được gì).
Có tham nhũng xảy ra sau một
free()
?
Bạn sẽ phải giải thích một chút về điều đó. Bạn có nghĩa là có con trỏ của các đối tượng đã được tự do nằm xung quanh? Tôi đang đặt mọi tham chiếu thành null khi đối tượng bị phá hủy, vì vậy trừ khi tôi bỏ lỡ điều gì đó ở đâu đó, không. Điều đó sẽ xuất hiện trong valgrind mặc dù điều đó đã không xảy ra.
Có điều gì đặc biệt về lưu lượng mạng (kích thước bộ đệm, chu kỳ phục hồi) không?
Lưu lượng mạng bao gồm dữ liệu thô. Vì vậy, các mảng char, (u) intX_t hoặc các cấu trúc đóng gói (để loại bỏ phần đệm) cho những thứ phức tạp hơn, mỗi gói có một tiêu đề bao gồm một id và chính kích thước gói được xác nhận theo kích thước dự kiến. Chúng có dung lượng khoảng 10-60byte với gói lớn nhất (boot bootup 'nội bộ, được bắn một lần khi khởi động) có kích thước vài Mb.
Rất nhiều và rất nhiều khẳng định sản xuất. Sụp đổ sớm và dự đoán trước khi thiệt hại lan truyền.
Tôi đã từng gặp một sự cố liên quan đến std::map
tham nhũng, mỗi thực thể có một bản đồ về "chế độ xem", mỗi thực thể có thể nhìn thấy nó và ngược lại là trong đó. Tôi đã thêm một bộ đệm 200byte ở phía trước và sau, điền vào nó bằng 0x33 và kiểm tra nó trước mỗi lần truy cập. Tham nhũng đã biến mất một cách kỳ diệu, tôi phải di chuyển thứ gì đó khiến nó bị hỏng thứ khác.
Ghi nhật ký chiến lược, để bạn biết chính xác những gì đang xảy ra trước đó. Thêm vào đăng nhập khi bạn đến gần hơn với một câu trả lời.
Nó hoạt động .. đến một phần mở rộng.
Trong tuyệt vọng, bạn có thể lưu trạng thái và tự động khởi động lại không? Tôi có thể nghĩ về một vài phần mềm sản xuất làm điều đó.
Tôi phần nào làm điều đó. Phần mềm này bao gồm một quá trình "bộ đệm" chính và một số công nhân khác mà tất cả đều truy cập vào bộ đệm để lấy và lưu nội dung. Vì vậy, mỗi lần đổ vỡ tôi không mất nhiều tiến bộ, nó vẫn ngắt kết nối tất cả người dùng và cứ thế, đó chắc chắn không phải là một giải pháp.
Đồng thời: luồng, điều kiện cuộc đua, vv
Có một luồng mysql để thực hiện các truy vấn "không đồng bộ", tất cả đều chưa được xử lý và chỉ chia sẻ thông tin cho lớp cơ sở dữ liệu thông qua các hàm với tất cả các khóa.
Ngắt
Có một bộ đếm thời gian ngắt để ngăn nó khóa mà chỉ hủy bỏ nếu nó không hoàn thành một chu kỳ trong 30 giây, mã đó vẫn an toàn:
if (!tics) {
abort();
} else
tics = 0;
Tics được volatile int tics = 0;
tăng lên mỗi khi một chu kỳ được hoàn thành. Mã cũ cũng vậy.
sự kiện / cuộc gọi lại / trường hợp ngoại lệ: trạng thái hỏng hoặc ngăn xếp không thể đoán trước
Rất nhiều cuộc gọi lại đang được sử dụng (I / O mạng không đồng bộ, bộ hẹn giờ), nhưng chúng không nên làm gì xấu.
Dữ liệu bất thường: dữ liệu đầu vào bất thường / thời gian / trạng thái
Tôi đã có một vài trường hợp cạnh liên quan đến điều đó. Ngắt kết nối ổ cắm trong khi các gói vẫn đang được xử lý dẫn đến việc truy cập nullptr và như vậy, nhưng chúng dễ dàng được phát hiện cho đến nay vì mọi tham chiếu đều được dọn sạch ngay sau khi nói với lớp đó. (Bản thân sự hủy diệt được xử lý bằng một vòng lặp xóa tất cả các đối tượng bị phá hủy trong mỗi chu kỳ)
Sự phụ thuộc vào một quá trình bên ngoài không đồng bộ.
Quan tâm đến công phu? Đây là một số trường hợp, quá trình bộ nhớ cache được đề cập ở trên. Điều duy nhất tôi có thể tưởng tượng ra khỏi đỉnh đầu là nó sẽ không hoàn thành đủ nhanh và sử dụng dữ liệu rác, nhưng đó không phải là trường hợp vì điều đó cũng sử dụng mạng. Mô hình gói giống nhau.
/analyze
) và bộ bảo vệ Malloc và Scribble của Apple. Bạn cũng nên sử dụng càng nhiều trình biên dịch càng tốt bằng cách sử dụng càng nhiều tiêu chuẩn càng tốt bởi vì các cảnh báo của trình biên dịch là một chẩn đoán và chúng sẽ tốt hơn theo thời gian. Không có viên đạn bạc, và một kích thước không phù hợp với tất cả. Càng sử dụng nhiều công cụ và trình biên dịch, phạm vi bảo hiểm càng đầy đủ vì mỗi công cụ đều có điểm mạnh và điểm yếu.