+3

Đơn giản hoá việc theo dõi external program cho developer. Part 1: Linux

1. Giới thiệu

Chúc mừng năm mới các bạn, mình hy vọng các bạn đã có 1 kì nghỉ Tết vui vẻ, hôm nay mình sẽ chỉ các bạn cách áp dụng lệnh strace để theo dõi khi chương trình gặp issue mà liên quan tới các chương trình khác. Bình thường thì muốn debug chương trình cần có mã nguồn, bạn sẽ phải đọc và hiểu code, debug chạy trên đó và tìm ra lỗi sai. Tuy nhiên sẽ ra sao nếu issue liên quan tới chương trình khác mà bạn không có mã nguồn?

Ví dụ bạn code Java thì xài tool khác để build project, xài NodeJS thì có npm làm package manager, khi các tool đó break hay ra error messages không rõ ràng, quá chung chung, hoặc search không ra, thì bạn sẽ gặp rắc rối. Bài viết này sẽ giúp các bạn có thể dễ dàng theo dõi hệ thống Linux dễ dàng khi có các issue này xảy ra

Bài viết được viết với hy vọng các bạn sẽ thấy hữu ích và có thể dùng nó để phục vụ công việc của mình, mình không hy vọng các bạn sẽ đi phá người khác vì tool này cũng khá OP.

2. Giải thích strace

strace là 1 phần mềm dùng để theo dõi tín hiệu syscall trên Linux.

syscall là 1 thành phần của hệ điều hành giúp user space code có thể request các đoạn service code từ kernel như truy cập các thiết bị phần cứng, quản lý file, tiến trình, cấp phát bộ nhớ, cũng như các thao tác low level mà user space code không thể tự làm được.

Để dễ hình dung thì phần lớn application các bạn viết sẽ được run ở chế độ user space với quyền giới hạn, và khi nó cần làm thao tác low level nào của hệ điều hành, nó sẽ gọi syscall tới kernel của OS. Kernel space sẽ có toàn quyền trên cả hệ thống, cung cấp các hàm thông dụng

vd về syscall là lệnh open để mở file, write để ghi vào file.

3. Lợi ích của việc tách user space code và kernel space code

3.1 Bảo vệ dữ liệu nhạy cảm:

Nếu chương trình nào cũng có full quyền trên hệ thống, được thao tác hết toàn bộ sẽ là 1 rủi ro lớn. Nên việc kernel isolate các chương trình với nhau, có vùng nhớ riêng sẽ bảo mật hơn

Ví dụ: bạn có ứng dụng ngân hàng, ứng dụng thì lưu dữ liệu trên RAM, nếu như 1 chương trình khác không liên quan, không thuộc ngân hàng, mà có thể đọc vùng nhớ của ứng dụng ngân hàng lấy credit card và dữ liệu nhạy cảm của người dùng lúc app run thì ngân hàng sẽ chịu thiệt hại rất lớn. Nên giải pháp là isolate các ứng dụng đó đi, app A sẽ không được đọc vùng nhớ của app B nếu như chúng không có liên hệ gì với nhau

3.2 Độ tin cậy và ổn định cao:

Vì code của kernel là low level, nên việc coding sẽ khó hơn và khả năng gây bug cao hơn, nên nếu app nào cũng có full quyền thì cái OS sẽ không ổn định vì bất kỳ application nào cũng có thể tự nghĩ ra 'low level' trick của mình, và thường thì các trick đó sẽ chống lại nhau. Đó là lý do bạn không nên cài 2 trình chống virus trong cùng 1 máy, vì thường chúng sẽ employ low level trick hoàn toàn khác nhau.

vd: app A xâm nhập vào hàm mở file để check file, và lưu con trỏ của hàm open file cũ để restore lại, app B không hề biết sự có mặt của con trỏ app A đã setup và ghi đè lên nó, kết quả là cả 1 OS crash

vd 2: app A có address space từ 1000->2000, app B có address space từ 3000->4000, thì 1 vài software bug từ app A có thể ghi đè lên vùng address space (ex: 3200) của app B, sẽ rất bất ổn khi dev phải tính toán toàn bộ bug có thể gây ra do chương trình bên ngoài, nếu app A chỉ thấy 1000->2000 thôi, mà không thấy 3000->4000, thì nếu nó ghi vào vùng 3200 chỉ có app A crash

3.3 Portability:

Vì code low level của kernel đã cung cấp interface abstract các common function, nên user space code sẽ đơn giản hơn, và hệ điều hành version sau chỉ cần implement y chang các syscall là support được all user space programs

3.4 Phân quyền:

syscall có thể phân quyền cho user, app, process có thể làm việc A mà không thể làm việc B, thì chúng ta có thể setup các thư viện 3rd party quyền thấp hơn code chúng ta develop. Vd: Sẽ rất vô lý nếu thư viện toán học xoá folder /opt của bạn randomly lúc run, nếu nó là thư viện đáng tin cậy

3.5 Giải quyết lỗi và quản lý tài nguyên:

syscall có thể cung cấp các hàm kiểm tra lỗi, quản lý tài nguyên trước khi làm 1 thao tác gì đó, nên phần lớn việc bạn đưa data sai sẽ bị chặn từ đầu thay vì nó crash OS.

4. Dùng strace để theo dõi open file

Lập trình viên chúng ta khi lập trình, sẽ làm việc với file, các biến đọc từ config file, chương trình đọc source code/executable file để chạy. Nên nếu có issue xảy ra là do các bạn config sai ở 1 file nào đó. Nên việc theo dõi file nào 1 chương trình mở khi run khá quan trọng.

4.1 Use case 1: launch program with strace:

Thay vì bạn run:

<command program with args>

strace sẽ cho phép bạn theo dõi xem <command program with args> đã mở file nào, để thực hiện điều này bằng cách thêm vào đầu command syntax sau:

strace -f -e trace=file [-o logfile] <command program with args>

Giải thích: -f nghĩa là follow fork, Linux khi tạo 1 tiến trình khác sẽ gọi syscall fork, bằng việc áp dụng -f, bạn có complete overview cả 1 command làm gì

vd bạn chạy file .sh, file sh run command, thì bản thân file sh nó không làm gì nhiều, mà chính command bên trong nó sẽ thao tác, việc prefix every command sẽ rất mất công, chưa kể command nó có điều kiện OR và AND, PIPE thì bạn sẽ mệt, -f sẽ follow hết tất cả mọi processes con được tạo từ process cha

-e trace=file: nghĩa là set tham số trace value file, nghĩa là kêu strace sẽ đọc những lệnh nào liên quan tới file nếu bạn dùng -o <logfile> thì strace sẽ ghi vào file log cho bạn xem sau, còn nếu không nó sẽ output ra terminal

vd: mình muốn run script.sh sau

#!/bin/bash

# Check if the directory path is provided as an argument
if [ $# -ne 1 ]; then
    echo "Usage: $0 <directory>"
    exit 1
fi

# Get the directory path from the first argument
directory="$1"

# Check if the directory exists
if [ ! -d "$directory" ]; then
    echo "Directory '$directory' not found."
    exit 1
fi

# Counter for ID
id=1

# Go to the directory
cd "$directory" || exit

# Loop through each file in the directory
for file in *; do
    # Check if the item is a file
    if [ -f "$file" ]; then
        # Extract file extension
        extension="${file##*.}"

        # Add prefix ID to the file name
        new_filename="${id}-$file"
        mv "$file" "$new_filename"

        echo "Renamed: $file -> $new_filename"

        # Increment ID
        id=$((id+1))
    fi
done

Để theo dõi xem script ./script.sh đã thao tác file như thế nào mà không cần hiểu source code, ta chạy lệnh:

strace -f -e trace=file -o bash_strace.log sh script.sh

Oh, chương trình cần pass 1 directory, mình sẽ tạo 1 folder Pictures, copy file vào và pass vào script

strace -f -e trace=file -o bash_strace.log sh script.sh Pictures

Kết quả cho thấy command đã thao tác nhiều file, vì hệ điều hành hiện đại rất phức tạp, tuy nhiên chúng ta có thể search những file liên quan tới đường dẫn ta pass vào Pictures, xem log syscall các bạn sẽ phải cần chắc lọc những đoạn log nào liên quan tới task mình làm, và filter các đoạn không cần thiết:

log strace sẽ có output là:

<pid> <syscall_name>(<args1>, <args2>, ...) = <return_code>

Mình sẽ đọc 1 vài line nổi bật trong log mình:

line 1: 37238 execve("/usr/bin/sh", ["sh", "script.sh", "Pictures"], 
0x7ffcf79ad4d8 /* 25 vars */) = 0
# line này nói pid 37238 gọi syscall execve, 
args1="/usr/bin/sh", args2=["sh", "script.sh", "Pictures"]
nếu bạn đọc Linux syscall reference các bạn sẽ thấy đây 
là syscall dùng để execute command, script gọi command sh và 
pass argument là "sh", "script.sh", "Pictures"

line 8: 37238 chdir("/home/tnhphuc/temp/Pictures") = 0
# line này gọi syscall chdir và cd tới thư mục của mình, 
nó giống như bạn gõ lệnh 'cd' trong Terminal

line 30: 37239 renameat2(AT_FDCWD, "Screenshot from 2023-08-14 21-48-29.png", 
AT_FDCWD, "1-Screenshot from 2023-08-14 21-48-29.png", RENAME_NOREPLACE) = 0
# line này chỉ việc rename file: "Screenshot from 2023-08-14 21-48-29.png" 
thành "1-Screenshot from 2023-08-14 21-48-29.png"
...
39 lines tương tự, mình sẽ chỉ nói line cuối:
line 771: 37287 renameat2(AT_FDCWD, "Screenshot from 2023-11-12 16-17-49.png", 
AT_FDCWD, "40-Screenshot from 2023-11-12 16-17-49.png", RENAME_NOREPLACE) = 0

Vậy là folder mình có 40 files, các file được đánh số 1-40, nghĩa là script này prefix cái ID tăng dần từ 1 và rename các file tương ứng

Bằng 1 command monitor đơn giản, chúng ta có thể theo dõi được chương trình làm gì mà không cần có source code

Nếu bạn chỉ quan tâm chương trình mở file gì, thì bạn chỉ cần trace syscall open, openat. Thay vì -e trace=file bạn đặt -e trace=open -e trace=openat, (specify nhiều trace argument sẽ trace nhiều syscall), vì khi mở file là đã có parameter là mở nó sẽ có option ghi hay để đọc, nên syscall open sẽ capture phần lớn các file activity

4.2 Use case 2: Attach vô tiến trình đang chạy with strace:

Giả sử chương trình bạn đã chạy rồi hay bạn không thể đổi command và run app, vd Desktop UI process, bạn khó có thể stop cái process và start cái mới mà không có giao diện, thì bạn có thể get PID của nó và attach vào. Do strace là tool official của Linux nên sẽ ít issue xảy ra khiến nó gắn không được hay hư OS. Bạn có thể cài top hay htop để get process PID, hay dùng lệnh pgrep <process_name> để lấy PID, sau đó pass vô strace:

pgrep <process_name>
strace -f -e trace=file [-o <log file>] -p <pid>

4.3 Use case 3: Monitor network activity

Bây giờ 1 ngày đẹp trời bỗng dưng chương trình của bạn, vd: apt update, ra lỗi, và không update được. Chương trình này chỉ truy cập internet, nên không có file nào được tạo ra. Bây giờ bạn có thể thay -e trace=file thành -e trace=network. Nhìn chung quy thì OS giao tiếp internet thông qua socket, socket có 2 đầu, máy bạn và máy người khác. Về flow thì Rest API thì là máy bạn request tới server người khác lấy data, còn webhook là server người khác khi nào có update mới send cho bạn, web brower cũng dùng socket, phần lớn tất cả đều được thể hiện bởi socket:

Các bạn có thể xem giải thích về socket ở link này: https://www.geeksforgeeks.org/socket-programming-python/

Thông thường socket flow sẽ có các bước sau:

Server:

  1. Tạo socket
  2. Bind vào port
  3. Listen vào 1 port

Client:

  1. Tạo socket
  2. Connect vào 1 IP address và port

Sau đó server và client sẽ có lệnh send hay recv để gửi hay nhận data

Ok, bây giờ giả sử chúng ta cài pandas với lệnh "pip install pandas" và bị lỗi không thể connect được. Trong bài viết này thì pip ship với default Ubuntu 20.04 không có in ra địa chỉ server mà fail connection. Nên chúng ta sẽ trace network process để biết web nào đang bị không vô được, thông thường nếu server down bạn sẽ không thể làm gì nhưng đôi khi lỗi là do ở firewall, VPN nên việc bỏ chặn IP đó đi bạn sẽ access được link đó để cài được phần mềm. Mình pass –no-cache-dir để khỏi dùng lại cái cache và force download lại để tiện cho việc demo

image.png

Ok bây giờ ta prefix command với -e trace=network

strace -f -e trace=network -o pandas.log pip install pandas

mở file log ra thì chúng ta thấy các đoạn nổi bật:

Line 22: socket(AF_INET, SOCKET_DGRAM|SOCK_CLOEXEC, IPPROTO_IP) = 3
Line 22 dùng syscall socket để tạo 1 socket, 
AF_INET specify Ipv4 Protocol Address, 
SOCKET_DGRAM chỉ socket type là UDP, 
nó trả về id = 3 là socket id

Line 23: connect(3, {sa_family=AF_INET, sin_port=htons(443), sin_addr=inet_address("151.101.128.223")} = 0
Line 23 dùng syscall connect tới socket id 3, với sin_port=443, 
vì network packet dùng big endian order, 
x86 PC dùng little endian nên hton chỉ convert port 443 sang big endian, 
sin_addr là 151.101.128.223 chỉ IP mà line này muốn access vào. 

Các bạn có thế biết chi tiết về socket option hơn ở trong document này:
https://docs.python.org/3/library/socket.html

Vậy là các bạn đã tìm ra các IP address mà pip đã fail connect vào. Mình sẽ báo cho các bạn 1 bí mật là mình đã cố tình add firewall rule chặn subnet 151.101.0.0/16 để simulate case này, tuy nhiên trong môi trường working mình đã gặp issue firewall này rồi, đợi issue xuất hiện không biết khi nào. Anyways khi đã biết bạn có firewall rule chặn IP này việc tiếp theo bạn làm là remove cái rule nó ra thôi.

5. Tóm tắt

Bài viết này đã giải thích cho bạn biết strace là gì, syscall là gì, cũng như tại sao có syscall, cách monitor file và network activity cho external program nếu không biết source code. Nếu có thời gian bài viết sau mình sẽ viết Part 2 về môi trường Windows.

Chúc các bạn thành công trong công việc của mình.

6. Tham khảo

6.1 Linux syscall table references (các bạn xem mục x86-64):

syscall thì có nhiều vô số kể, nên các bạn có thể xem link qua web sau: https://chromium.googlesource.com/chromiumos/docs/+/master/constants/syscalls.md

6.2 Đào sâu hơn vào strace:

https://funix.edu.vn/chia-se-kien-thuc/hieu-cac-lenh-goi-he-thong-linux-voi-lenh-strace/


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí