Hiểu các lệnh gọi hệ thống Linux với lệnh strace | Học trực tuyến CNTT, học lập trình từ cơ bản đến nâng cao

Hiểu các lệnh gọi hệ thống Linux với lệnh strace

Chia sẻ kiến thức 11/06/2022

Mọi quy trình chạy trên desktop của bạn đều sử dụng các lệnh gọi hệ thống để giao tiếp với hệ điều hành. Sử dụng strace, bạn có thể theo dõi các lệnh gọi hệ thống như vậy một cách dễ dàng.

Khi các chương trình chạy trên Linux muốn sử dụng các tài nguyên do hệ điều hành quản lý (đọc tệp, tạo quy trình, v.v.), chúng sẽ thực hiện lệnh gọi hệ thống tới hệ điều hành. Các lệnh gọi hệ thống hoạt động ở cấp độ hạt nhân và thực hiện các hoạt động cần thiết, để lại quyền kiểm soát cho chương trình gọi. Công cụ strace cung cấp khả năng theo dõi các lệnh gọi hệ thống này trên Linux.

1. Sử dụng lệnh strace

Để giám sát các lệnh gọi của hệ thống cho một ứng dụng, chỉ cần gọi lệnh strace như sau: 

strace ls /tmp

Tuy nhiên, thường có các quy trình bắt đầu sớm hơn nhiều và tiếp tục hoạt động ở chế độ nền. Bạn có thể muốn thu thập thông tin bổ sung liên quan đến các quy trình đó. Bạn có thể đính kèm strace vào bất kỳ ứng dụng nào đang chạy bằng cách cung cấp ID tiến trình (process ID) của tiến trình cho tham số -p:

strace -p 2759

Đầu ra:

1.1 Theo dõi các luồng (thread) và nhánh (fork) của một ứng dụng

Với strace, bạn có thể kiểm tra tất cả các luồng (thread) và các quy trình con khác là một nhánh của ứng dụng bằng cách sử dụng cờ -f.

strace -f -p 2759

Đầu ra:

1.2 Kiểm tra một số cuộc gọi hệ thống với strace

Đôi khi, kết quả đầu ra mặc định có thể khá khó theo dõi. Nếu bạn chỉ muốn theo dõi các lệnh gọi hệ thống nhất định, bạn có thể làm như vậy với tham số -e:

strace -f -e trace=open,write,close,connect,select -p 19770

Để chỉ theo dõi các lệnh gọi hệ thống liên quan đến hoạt động tệp, hãy sử dụng -e trace=file:

strace -e trace=file -p 19770

Để chỉ lọc các cuộc gọi hệ thống liên quan đến mạng, hãy chỉ định -e trace=network trong lệnh:

strace -e trace=network -p 19770

1.3 Nhận thông tin thời gian tính bằng giây

Khi xuất lệnh gọi hệ thống, bạn có thể sử dụng tham số -t để nhận thông tin thời gian với độ chính xác tính bằng giây. Trong hầu hết các trường hợp, độ chính xác sẽ không đủ cho nhu cầu của bạn. Trong những tình huống như vậy, bạn có thể sử dụng tham số -tt để nhận thông tin thời gian với độ chính xác micro giây:

strace -tt ls /tmp

1.4 Thu thập số liệu thống kê về cuộc gọi hệ thống

Với tham số -c, bạn có thể thu thập số liệu thống kê về các lệnh gọi hệ thống miễn là bạn muốn:

strace -f -c -p 19770

1.5 Lưu nhật ký vào tệp

Nếu bạn chạy strace trong một thời gian dài và muốn kiểm tra các bản ghi kết quả chi tiết hơn sau đó, bạn sẽ cần phải lưu các bản ghi. Với tham số -o, bạn có thể chỉ định tệp mà strace sẽ lưu nhật ký:

strace -f -o /tmp/strace.log -e trace=file ls /tmp

2. Quy trình chặn ptrace

Sử dụng lệnh gọi hệ thống prctl, bất kỳ ứng dụng nào trong Linux đều có thể ngăn bản thân nó khỏi bị người dùng không phải root điều khiển bằng ptrace. Nếu ứng dụng xóa cờ PR_SET_DUMPABLE cho chính nó thông qua prctl, người dùng không phải root sẽ không thể kiểm soát ứng dụng này bằng ptrace.

Một trong những cách sử dụng điển hình nhất của tính năng này được thấy trong phần mềm tác nhân xác thực OpenSSH. Do đó, việc một ứng dụng này kiểm soát một ứng dụng khác với ptrace bị ngăn chặn tại xác thực người dùng.

ptrace và bảo mật

Do cơ sở ptrace được đặt trong mô hình quy trình Linux truyền thống, bất kỳ phần mềm nào bạn chạy trên hệ thống với người dùng của mình đều có quyền chèn mã độc hại vào đó. Từ công cụ xterm đơn giản nhất đến các ứng dụng trình duyệt web nâng cao, phần mềm độc hại như vậy có thể kiểm soát tất cả các ứng dụng đang chạy khác  — nhờ lệnh gọi hệ thống ptrace — và sao chép thông tin quan trọng mà bạn không hề hay biết.

Để đối phó với tình trạng này, một cơ chế bảo vệ đã được phát triển với mô-đun bảo mật có tên là Yama trong nhân Linux.

Bạn có thể kiểm soát phản hồi đối với lệnh gọi hệ thống ptrace thông qua tệp /proc/sys/kernel/yama/ptrace_scope. Theo mặc định, tệp này ghi giá trị bằng 0.

Các giá trị sau được chấp nhận:

Giá trị Ý nghĩa
0 Hành vi thông thường: Tất cả các ứng dụng có quyền ptrace đều có thể được kiểm tra.
1 Ptrace bị hạn chế: Chỉ ứng dụng mẹ (parent) trực tiếp của ứng dụng hoặc ứng dụng gỡ lỗi (debug app) được ứng dụng có tùy chọn PR_SET_PTRACER cho phép mới có quyền kiểm soát. Do đó, việc sử dụng gdb program_name và strace program_name sẽ tiếp tục hoạt động, nhưng bạn sẽ không thể đính kèm ứng dụng đang chạy sau đó.
2 Ptrace cho người quản trị hệ thống : Chỉ các ứng dụng có thuộc tính CAP_SYS_PTRACE đã xác định hoặc các quy trình con xác định tùy chọn PTRACE_TRACEME với prctl mới có thể được kiểm soát.
3 Hoàn toàn bị vô hiệu hóa: Không cho phép ptrace trong bất kỳ trường hợp nào. Nếu thuộc tính này được xác định một lần, bạn không thể thay đổi lại nó trong thời gian chạy.

Nhiều nhà phát triển không biết rằng các ứng dụng có thể tự vô hiệu hóa ptrace thông qua prctl, ngoại trừ người dùng root. Mặc dù phần mềm liên quan đến bảo mật như tác nhân OpenSSH thực hiện các hoạt động này, nhưng bạn không nên mong đợi hành vi giống nhau từ tất cả phần mềm đang chạy trên hệ thống.

Gần đây, một số bản phân phối Linux đã bắt đầu đặt giá trị mặc định của tệp ptrace_scope, được mô tả ở trên, thành 1 . Do đó, với các hoạt động ptrace bị hạn chế, một môi trường làm việc an toàn hơn sẽ được cung cấp trên toàn hệ thống.

3. Sử dụng một đoạn ví dụ

Đăng ký ứng dụng mẫu dưới đây với tên domainsrace.c. Sau đó, bạn có thể biên dịch nó bằng lệnh sau:

gcc -o ministrace ministrace.c

Code:

#include <sys/ptrace.h>
#include <sys/reg.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h> 
int wait_for_syscall (pid_t child)
	{
		int status;
		while (1) {
			ptrace(PTRACE_SYSCALL, child, 0, 0);
			waitpid(child, &status, 0);
			if (WIFSTOPPED(status) && WSTOPSIG(status) & 0x80)
				return 0;
			if (WIFEXITED(status))
				return 1;
		}
	}
	
int do_child (int argc, char **argv)
	{
		char *args [argc+1];
		memcpy(args, argv, argc * sizeof(char*));
		args[argc] = NULL;
		ptrace(PTRACE_TRACEME);
		kill(getpid(), SIGSTOP);
		return execvp(args[0], args);
	}
	
int do_trace (pid_t child)
	{
		int status, syscall, retval;
		waitpid(child, &status, 0);
		ptrace(PTRACE_SETOPTIONS, child, 0, PTRACE_O_TRACESYSGOOD);
		while(1) {
			if (wait_for_syscall(child) != 0) break;
			
			syscall = ptrace(PTRACE_PEEKUSER, child, sizeof(long)*ORIG_RAX);
			fprintf(stderr, "syscall(%d) = ", syscall);
			
			if (wait_for_syscall(child) != 0) break;
			
			retval = ptrace(PTRACE_PEEKUSER, child, sizeof(long)*RAX);
			fprintf(stderr, "%d
", retval);
		}
		return 0;
	}
int main (int argc, char **argv)
	{
		if (argc < 2) {
			fprintf(stderr, "Usage: %s prog args
", argv[0]);
			exit(1);
		}
		pid_t child = fork();
		if (child == 0) {
			return do_child(argc-1, argv+1);
		} else {
			return do_trace(child);
		}
	}

Sau khi biên dịch ứng dụng, bạn có thể chạy bất kỳ lệnh nào với Ministrace và kiểm tra đầu ra:

4. Bạn có thể sử dụng strace cho nhiều mục đích

strace có thể giúp tìm ra lỗi trong các chương trình sử dụng tài nguyên hệ thống một cách không cần thiết. Tương tự như vậy, đặc điểm mà một chương trình thể hiện trong khi sử dụng tài nguyên hệ điều hành cũng có thể bị tiết lộ với strace.

Tương tự, strace giúp bạn hiểu lý do tại sao một chương trình kết thúc bất ngờ. Do đó, việc làm quen với strace là rất quan trọng trong việc phát triển nhân Linux và quản trị hệ thống.

>>> Nếu bạn đang có nhu cầu học lập trình trực tuyến, tìm hiểu ngay tại đây:

>>> Xem thêm các chủ đề hữu ích:

 

Vân Nguyễn

Dịch từ: https://www.makeuseof.com/strace-command-linux/

ĐĂNG KÝ TƯ VẤN HỌC LẬP TRÌNH TẠI FUNiX

Bình luận (
0
)

Bài liên quan

  • Tầng 0, tòa nhà FPT, 17 Duy Tân, Q. Cầu Giấy, Hà Nội
  • info@funix.edu.vn
  • 0782313602 (Zalo, Viber)        
Chat Button
FUNiX V2 GenAI Chatbot ×

yêu cầu gọi lại