Debugging Linux Nâng cao: Truy tìm Bug ngầm với strace và ltrace

Linux tutorial - IT technology blog
Linux tutorial - IT technology blog

Khi Log im hơi lặng tiếng: Bài toán “Hộp đen”

Tất cả chúng ta đều đã từng gặp tình huống này. Bạn có một file binary Go hoặc script Python chạy hoàn hảo trên laptop, nhưng ngay khi đưa lên staging server, nó bỗng dưng bị treo. Không có log lỗi, không có stack trace. Mức sử dụng CPU đứng yên ở mức 0%. Thậm chí lệnh tail -f /var/log/syslog cũng trống trơn một cách bực mình. Đây chính là kịch bản “hộp đen”, nơi ứng dụng ngừng phản hồi với người dùng và âm thầm “vật lộn” với hệ điều hành.

Các trình debugger tiêu chuẩn như GDB rất mạnh mẽ, nhưng đôi khi lại quá mức cần thiết. Nếu bạn thiếu debug symbols hoặc vấn đề nằm ở cách ứng dụng tương tác với kernel, GDB sẽ không giúp ích được nhiều. Thông thường, khi một ứng dụng “không làm gì cả”, thực chất nó đang bị kẹt để chờ một tài nguyên nào đó. Đó có thể là một file descriptor không thể mở, một network socket bị timeout, hoặc tình trạng deadlock giữa các thread. Để tìm ra thủ phạm, bạn cần xem ứng dụng đang yêu cầu kernel thực hiện những gì.

Giao tiếp hệ thống: System Call đối đầu Library Call

Để khắc phục một lỗi ngầm, bạn phải giám sát hai cách mà một process tương tác với thế giới bên ngoài. Đầu tiên là System Calls (syscalls). Đây là các yêu cầu phần cứng, như đọc một file từ ổ cứng NVMe hoặc gửi một gói tin qua Ethernet. Thứ hai là Library Calls. Những lệnh này nhắm vào các thư viện chia sẻ (shared libraries) như libc hoặc openssl để xử lý các logic phức tạp như định dạng chuỗi hoặc mã hóa.

Hầu hết các tình trạng treo máy đều xảy ra tại các ranh giới này. Nếu bạn có thể thấy chính xác yêu cầu được gửi đến kernel ngay trước khi process bị đóng băng, bạn đã tìm thấy “tang chứng vật chứng”. Đây chính là lúc straceltrace trở thành những công cụ giá trị nhất của bạn.

Vạch trần giao tiếp Kernel với strace

strace chặn (intercept) cuộc hội thoại giữa chương trình của bạn và Linux kernel. Nó không chỉ cho bạn biết một file không mở được; nó hiển thị chính xác đường dẫn đã thử và mã lỗi cụ thể, chẳng hạn như ENOENT (File not found) hoặc Permission denied.

Sử dụng strace trong thực tế

Nếu một chương trình có tên api_engine đang hoạt động bất thường, hãy bắt đầu bằng cách chạy nó trực tiếp dưới quyền của strace:

strace ./api_engine

Lệnh này sẽ hiển thị mọi syscall ra terminal của bạn. Với một ứng dụng bận rộn, dữ liệu có thể trôi qua với tốc độ 500 dòng mỗi giây. Để giữ bình tĩnh, hãy lọc các hoạt động cụ thể. Ví dụ, nếu bạn nghi ngờ có vấn đề về cấu hình hoặc timeout mạng, hãy sử dụng các bộ lọc sau:

# Chỉ hiển thị các lệnh gọi liên quan đến file (open, stat, v.v.)
strace -e trace=file ./api_engine

# Tập trung vào hoạt động mạng như connect và sendto
strace -e trace=network ./api_engine

Sửa lỗi một Process đang chạy

Bạn không cần phải khởi động lại ứng dụng để debug. Nếu một service đang bị treo trên production, hãy tìm Process ID (PID) của nó và gắn (attach) strace ngay lập tức:

ps aux | grep api_engine
sudo strace -p 1234

Gần đây tôi đã sử dụng cách này trên một server Ubuntu 22.04 để giải quyết vấn đề “zombie” process. Ứng dụng bị kẹt trong một vòng lặp 100ms khi gọi fstat trên một file .env bị thiếu. Bằng cách attach vào PID đang chạy, tôi đã thấy vòng lặp vô tận đó ngay lập tức. Việc thêm file còn thiếu đã giúp giảm tải CPU từ 15% xuống gần bằng 0.

Nhìn thấu Shared Library với ltrace

Đôi khi strace không hiển thị gì, nhưng CPU vẫn bị chiếm dụng 100%. Điều này thường có nghĩa là ứng dụng đang bị kẹt trong một hàm thư viện, như vòng lặp while vô tận hoặc một thao tác malloc nặng nề. ltrace theo dõi các library call trong không gian người dùng (user-space) này.

Cú pháp gần như giống hệt strace, nhưng thông tin chi tiết lại khác:

ltrace ./api_engine

Nếu bạn muốn tìm các điểm nghẽn hiệu năng, hãy sử dụng flag tóm tắt. Nó sẽ tạo ra một biểu đồ (histogram) cho thấy hàm thư viện nào đang chiếm dụng thời gian thực thi:

ltrace -c ./api_engine

Bạn có thể phát hiện ra rằng ứng dụng dành 40% thời gian cho hàm strlen(). Đó là dấu hiệu rõ ràng cho thấy bạn nên cache độ dài chuỗi thay vì tính toán lại chúng trong vòng lặp.

So sánh công cụ: strace vs. ltrace

Tính năng strace ltrace
Đối tượng System Call của Kernel Library Call trong User-space
Ảnh hưởng hiệu năng Cao (chậm hơn 10x – 50x) Rất cao
Phù hợp nhất cho Quyền hạn, I/O, lỗi mạng Lỗi logic, nghẽn thư viện
Chỉ số quan trọng Mã trả về (VD: -1 EPERM) Tham số hàm và thời gian thực thi

Quy trình Debug chuyên nghiệp

Đừng chỉ nhìn chằm chằm vào dòng chữ đang trôi. Hãy làm theo cách tiếp cận có hệ thống này để cô lập bug trong vài phút thay vì vài giờ.

1. Bắt đầu với cái nhìn tổng thể

Hãy chạy một bản tóm tắt trước. Điều này làm nổi bật các syscall thường xuyên nhất và những syscall tốn nhiều thời gian nhất để hoàn thành:

strace -c ./api_engine

Nếu futex là thứ tiêu tốn thời gian nhất, bạn đang gặp vấn đề về khóa luồng (thread locking). Nếu read hoặc select đứng đầu, ứng dụng của bạn có khả năng đang chờ một database chậm hoặc một network socket bị chặn.

2. Theo dõi các Process con

Các ứng dụng hiện đại sử dụng nhiều thread hoặc process con. Theo mặc định, strace chỉ theo dõi process chính. Sử dụng flag -f để theo dõi mọi thread mà ứng dụng tạo ra:

strace -f -o trace_logs.txt ./api_engine

Flag -o lưu kết quả ra một file. Việc cố gắng đọc dữ liệu xen kẽ từ 10 thread khác nhau trên terminal thực sự là một cơn ác mộng.

3. Sử dụng Timestamp chính xác

Để tìm hiểu lý do tại sao một ứng dụng bị treo đúng 30 giây mỗi giờ, bạn cần dữ liệu về thời gian. Flag -ttt thêm timestamp đến mức micro giây, trong khi -T hiển thị thời gian thực thi của từng lệnh gọi:

strace -ttt -T ./api_engine

Điều này giúp dễ dàng phát hiện một lệnh gọi connect() mất chính xác 5.000 giây để thất bại, chỉ thẳng tới vấn đề firewall timeout.

Cảnh báo khi dùng trên Production

straceltrace dựa trên ptrace, công cụ này sẽ tạm dừng process cho mỗi sự kiện. Điều này tạo ra một overhead (chi phí tài nguyên) khổng lồ. Trong môi trường production có lưu lượng truy cập cao, strace có thể làm chậm process tới 10 lần hoặc hơn. Hãy luôn attach trong thời gian ngắn nhất có thể để thu thập dữ liệu, sau đó detach ngay. Nếu bạn cần giám sát các hệ thống hiệu năng cao liên tục, hãy tìm hiểu các công cụ eBPF như bpftrace, vốn có overhead thấp hơn nhiều.

Share: