6 điểm bởi GN⁺ 23 ngày trước | 2 bình luận | Chia sẻ qua WhatsApp
  • Định nghĩa initrd là một đơn vị chương trình mà kernel trực tiếp diễn giải và thực thi, từ đó diễn giải lại Linux như một trình thông dịch
  • Dùng kexec, base64, cpio để tạo nên một bản phân phối Linux đệ quy tự khởi động lại chính nó, trong đó initrd tự thực thi lại chính mình
  • Nếu script /init được để xuất ra ảnh cpio của chính nó thì sẽ hình thành một initrd tự sao chép kiểu Quine
  • Giải thích cấu trúc tầng lớp trình thông dịch kéo dài tới tận kernel thông qua cấu trúc thực thi ELF, ld.so, và binfmt_misc
  • Có thể dùng kexec hoặc QEMU để chạy một Linux khác theo kiểu đệ quy đuôi trên Linux, qua đó mở rộng ranh giới giữa kernel, ảo hóa và trình thông dịch theo cách thực nghiệm

Phân tích ngược rkx.gz và cấu trúc initrd tự đệ quy

  • Lệnh curl https://astrid.tech/rkx.gz | gunzip | sudo sh sẽ tải về và chạy một script shell được mã hóa base64 có kích thước 20MB
    • Script kiểm tra quyền root và xác nhận sự tồn tại của kexec, base64, cpio
    • Giải mã dữ liệu base64 để tạo một archive cpio tên r, rồi trích xuất từ đó ảnh kernel tên k
    • Dùng kexec để nạp k làm kernel và r làm ramdisk rồi thực thi
  • Bên trong r.cpio có các tệp /bin, /init, k; trong đó kảnh kernel Linux 6.18.18, còn /init là một script shell
    • /init mount /proc, đóng gói hệ thống tệp hiện tại thành cpio tại /r, rồi dùng kexec để chạy lại /k/r
    • Kết quả là tạo thành một bản phân phối Linux đệ quy liên tục tự khởi động lại chính nó

Góc nhìn coi kernel Linux là trình thông dịch

  • Initrd không chỉ là ramdisk phục vụ khởi động, mà có thể xem là một chương trình được kernel Linux diễn giải và thực thi
    • Tương tự curl | sh hay python3 script.py, initrd cũng là một chương trình đầu vào được kernel thực thi
    • Vì vậy, kernel Linux hoạt động như một trình thông dịch diễn giải initrd
  • Cấu trúc này tương tự tối ưu hóa đệ quy đuôi (tail-call optimization)
    • kexec nạp và chạy trong không gian bộ nhớ mới thay vì ghi đè kernel trước đó
    • Mỗi kernel không giữ lại trạng thái trước, mà được thay thế bằng một “stack frame” mới

Quine và sự tự sao chép của initrd

  • Quine là một chương trình tự in ra chính nó
    • Nếu script /init thực hiện cat /r ở cuối, nó sẽ xuất ra cpio giống hệt chính nó
    • Khi đó sẽ hình thành một Quine của trình thông dịch initrd Linux
    • Vì mọi tệp đều tồn tại trên tmpfs trong RAM, nên không phát sinh I/O đĩa thực tế

ELF, ld.so và các tầng lớp trình thông dịch

  • Tệp thực thi ELF chứa đường dẫn trình thông dịch (ld-linux-x86-64.so.2) trong header
    • Khi thực thi, kernel chạy ld.so trước, rồi ld.so nạp các thư viện động của ELF và chạy chương trình
    • Vì vậy ELF cũng có thể được xem là một dạng ngôn ngữ được thông dịch
  • /bin/sh được ld.so diễn giải, còn ld.so thì được kernel trực tiếp diễn giải
    • ld.so là ELF liên kết tĩnh nên kernel có thể chạy trực tiếp
    • Từ đó hình thành trường hợp cơ sở (base case) của tầng lớp trình thông dịch

Thực thi CPIO thông qua binfmt_misc

  • Dùng binfmt_misc có thể thực thi tệp có magic byte nhất định bằng trình thông dịch được chỉ định
    • Ví dụ lệnh đăng ký:
      echo ':cpio:M::\x30\x37\x30\x37\x30\x31::/path/to/my/script.sh:' > /proc/sys/fs/binfmt_misc/register
      
    • Với cấu hình này, có thể chạy trực tiếp tệp CPIO đã chmod +x
  • Có thể đăng ký một script dùng QEMU để chạy CPIO như initrd làm trình thông dịch
    • QEMU khởi động máy ảo bằng kernel và initrd được chỉ định
    • Kết quả là trình thông dịch của tệp CPIO sẽ là kernel Linux đang được QEMU chạy

Trình thông dịch đệ quy và “vòng lặp kỳ lạ nhất”

  • Trình thông dịch dựa trên QEMU tạo ra một stack frame môi trường Linux mới
    • Đây là cấu trúc chạy một Linux khác trên Linux, có thể lồng đến giới hạn bộ nhớ
    • Nếu thay bằng trình thông dịch dựa trên kexec thì có thể thực thi Linux đệ quy với tối ưu hóa gọi đuôi
  • Nếu trong /init đăng ký binfmt_misc và cấu hình để chạy /r initrd tự thực thi chính nó sẽ được hoàn thiện
    • /r là tiến trình init tiếp theo ở định dạng CPIO, và khi chạy sẽ tiếp tục tự diễn giải lại chính mình

Kết luận

  • Initrd không chỉ là công cụ khởi động đơn thuần, mà là một đơn vị chương trình được kernel Linux diễn giải
  • Với kexecbinfmt_misc, có thể chạy đệ quy chính Linux như một trình thông dịch
  • Cấu trúc này là một khái niệm thử nghiệm phá vỡ ranh giới giữa kernel, ảo hóa, trình thông dịch và chương trình tự sao chép
  • Mã nguồn liên quan được công bố tại kho GitHub ifd3f/rekexec

2 bình luận

 

Đúng là thiếu hiểu biết thì lại càng liều.. Mong là nên tránh những bài viết như thế này.

 
Ý kiến trên Hacker News
  • Tôi thấy rất khổ sở khi đọc bài này vì có quá nhiều hiểu nhầm
    archive cpio không phải là filesystem. Tác giả đang dùng initramfs, vốn dựa trên tmpfs. Linux có thể giải nén cpio vào tmpfs. Một archive chứa file và thư mục tự nó không phải là chương trình
    Chỉ vì thứ gì đó trông giống nhau không có nghĩa là chúng giống hệt nhau. Chương trình nhị phân được chạy trên CPU; nếu có một interpreter thì nó ẩn trong môi trường phần cứng. Điều đó nằm ngoài phạm vi của kernel
    Để chạy shell script, bạn cần một shell để diễn giải script đó. Tác giả đã lược bỏ phần này và nhầm lẫn kernel với chương trình shell
    Linux có thể được biên dịch mà không cần initramfs hay ramdisk, và vẫn có thể chạy userland của filesystem
    Cách nói “Linux initrd interpreter” thực sự là một mô tả sai

    • File ELF cũng có thể tự nó không phải là chương trình. Một số ELF là thư viện động nên không có entry point. Cũng như chỉ một phần ELF là có thể thực thi, ta cũng có thể xem chỉ một phần CPIO là có thể thực thi. Xét cho cùng, ld.so giải nén ELF vào bộ nhớ rồi chạy entry point, còn kernel giải nén initramfs rồi chạy entry point, về mặt khái niệm là tương tự nhau
    • File init trong cpio mới là chương trình thực sự được diễn giải, còn các file còn lại đóng vai trò như bộ nhớ để chương trình đó sử dụng
    • Chương trình nhị phân chạy trên CPU, nhưng bản thân file chương trình là một cấu trúc archive gồm nhiều section. CPU không thể trực tiếp hiểu file chương trình. Linux thiết lập không gian địa chỉ nơi chương trình sẽ chạy, rồi sau đó nhảy tới địa chỉ mà program counter trỏ tới. Các section metadata của ELF định nghĩa quá trình này
    • Ít ra thì cũng an ủi ở chỗ đây không phải bài do AI viết
  • Có phải mọi OS đều đóng vai trò interpreter mã máy với đặc quyền kernel không?

    • Tôi không nghĩ vậy. OS không trực tiếp diễn giải từng lệnh, mà giao chúng cho CPU để thực thi
    • OS là một giao diện cho phép sử dụng tài nguyên hệ thống. CPU diễn giải mã máy, còn OS có thể chỉ thị cho CPU biết chạy cái gì
    • Trong trường hợp này thì có thể xem nó là interpreter cho file CPIO
  • Bài này ổn nếu xem “Linux là một interpreter” như một mô hình tinh thần, nhưng nếu hiểu theo nghĩa đen thì sai
    Nếu không xét ở mức lệnh CPU, mà xem kernel là thứ điều phối các định dạng thực thi như ELF, script shebang và initramfs, thì hợp lý hơn. Có vẻ sự nhầm lẫn bắt nguồn từ việc trộn lẫn hai nghĩa của từ ‘interpreter’

  • Điều cốt lõi không phải là phép so sánh có đúng hay không, mà là nó cho thấy khái niệm ‘thực thi’ phụ thuộc vào môi trường đến mức nào

  • “Mọi thứ đều là interpreter?”

    • Đúng vậy, nhưng compiler là ngoại lệ
  • Theta Combinator của Turing

    • Tôi không rõ nó liên quan gì đến bài này. Tôi không quen với các khái niệm lập trình hàm
  • Trong bài trước của loạt bài này, tác giả nói rằng họ không muốn dùng object storage của Contabo nên đã tự tạo image VPS
    Tôi nghĩ giữa hai cực: bỏ ra 50 giờ để tiết kiệm 1,50 USD mỗi tháng, và tiêu 250.000 USD cho token, có một điểm cân bằng.
    Nếu không kham nổi chi phí hạ tầng, thì có lẽ vấn đề là ở yếu tố xã hội hơn là năng lực kỹ thuật. Việc ám ảnh chuyện chạy Doom bằng curl có vẻ không phải là điều năng suất

    • Tôi cũng từng như vậy. Hồi trước 5 euro mỗi tháng cho VPS là quá đắt, nên tôi thường tắt instance và sao lưu root filesystem sang laptop của mẹ cho đến khi có tiền. Sau đó tôi còn cài Terminal IDE lên Kindle để nghịch với busybox và gcc. Cảm ơn Spartacus Rex vì đã tạo nên khởi đầu sự nghiệp của tôi
    • Ý của tác giả là nói đùa. Lý do thật nằm ngay ở đoạn sau — “Tôi nghĩ đó là một mẹo thú vị, và nếu đăng lên blog thì vừa giúp tôi học, độc giả cũng học được, lại còn kiếm được internet point, một win-win
    • Điều trông có vẻ không năng suất với người này, với người khác lại là cách tận hưởng sở thích đặc biệt, điều rất quan trọng cho sức khỏe tinh thần. Với tôi là người ADHD, đó thậm chí còn là hoạt động cần thiết
    • Câu “không trả nổi 1,50 USD thì không phải dân chuyên” nghe rất kỳ. Một professional được định nghĩa bởi việc họ có được trả tiền hay không, chứ không phải bởi mức chi tiêu
  • Nếu xem man ld.so, sẽ thấy có ghi rõ dynamic linker được lưu trong section .interp của ELF sẽ được chạy. Bản thân tên section này đã rất thú vị

  • Linux rất hữu ích như một giao diện có thể lập trình. Windows cũng làm được, nhưng tôi cảm thấy Linux phù hợp hơn
    Tôi nghĩ GUI trên Windows tốt hơn, nhưng GNOME hay KDE cũng bất tiện. Vì vậy tôi dùng fluxbox, icewm, đôi khi là xfce hoặc mate-desktop. Dạo này tôi thích môi trường đơn giản và nhanh. Phần lớn công việc của tôi được xử lý bằng command line và chỉnh sửa mã

    • Nếu muốn một môi trường nhanh và đơn giản, tổ hợp Sway + foot rất tốt. Nếu bố trí workspace bằng keybind thì có thể dùng rất thoải mái mà không cần desktop
    • Tôi không đồng ý rằng GUI của Windows tốt hơn. GNOME, KDE cũng không hay, nhưng với Windows thì bạn không thể thoát khỏi WM phức tạp của Microsoft. Cá nhân tôi thấy các giao diện kiểu mpx/mux (ví dụ: 9wm, cwm, dwm) tốt hơn hẳn kiểu Xerox. Chúng gần với triết lý của Engelbart hơn và nhìn chung gọn gàng hơn nhiều