Các lệnh
Bây giờ bạn đã hiểu về các thanh ghi và vùng nhớ của sBPF, hãy cùng xem xét các lệnh để thao tác với chúng.
Các lệnh là những thao tác cơ bản mà chương trình của bạn thực hiện—cộng các số, tải từ bộ nhớ, hoặc nhảy đến các vị trí khác nhau.
Lệnh là gì?
Các lệnh là những khối xây dựng cơ bản của chương trình. Hãy coi chúng như những mệnh lệnh chỉ dẫn chính xác cho bộ xử lý phải làm gì:
add64 r1, r2: "Cộng các giá trị trong thanh ghir1vàr2, lưu kết quả vàor1"ldxdw r0, [r10 - 8]: "Tải 8 byte từ bộ nhớ stack vào thanh ghir0"jeq r1, 42, +3: "Nếur1bằng 42, nhảy tiến 3 lệnh"
Mỗi lệnh thực hiện chính xác một thao tác và được mã hóa thành chính xác 8 byte dữ liệu để VM giải mã ngay lập tức.
Các lệnh sBPF làm việc với các kích thước dữ liệu khác nhau:
byte = 8 bit (1 byte)
halfword = 16 bit (2 byte)
word = 32 bit (4 byte)
doubleword = 64 bit (8 byte)
Hầu hết các thao tác sBPF sử dụng giá trị 64-bit (doublewords) vì các thanh ghi là 64 bit, nhưng bạn có thể tải và lưu trữ các kích thước nhỏ hơn khi cần thiết để tăng hiệu quả.
Danh mục và định dạng các lệnh
Khi bạn biên dịch mã Rust, C, hoặc assembly, toolchain sẽ tạo ra một luồng các lệnh có kích thước cố định 8-byte được đóng gói vào phần .text của ELF.
Mỗi lệnh tuân theo một cấu trúc nhất quán mà VM có thể giải mã trong một lần duyệt:
1 byte 4 bits 4 bits 2 bytes 4 bytes
┌──────────┬────────┬────────┬──────────────┬──────────────────┐
│ opcode │ dst │ src │ offset │ imm │
└──────────┴────────┴────────┴──────────────┴──────────────────┘opcode: Xác định loại thao tác. 3 bit cao nhất xác định lớp lệnh (số học, bộ nhớ, nhảy, gọi, thoát), trong khi 5 bit thấp hơn chỉ định chính xác biến thể (cộng, nhân, tải, nhảy-nếu-bằng).dst: Số thanh ghi đích (r0–r10) nơi kết quả được lưu trữ—kết quả số học, giá trị đã tải, hoặc giá trị trả về từ hàm bổ trợ.src: Thanh ghi nguồn cung cấp đầu vào. Đối với số học hai toán hạng (add r1, r2), nó cung cấp giá trị thứ hai. Đối với các thao tác bộ nhớ, nó có thể cung cấp địa chỉ cơ sở. Đối với các biến thể tức thời (add r1, 10), 4 bit này được gộp vào mã lệnh.offset: Một số nguyên nhỏ điều chỉnh hành vi lệnh. Đối với tải/lưu, nó được thêm vào địa chỉ nguồn để đạt đến[src + offset]. Đối với các lệnh nhảy, đó là mục tiêu nhánh tương đối được đo bằng số lệnh.imm: Trường giá trị tức thời. Các thao tác số học sử dụng nó cho hằng số (add r1, 42),CALLsử dụng nó cho số syscall (sol_log = 16), và các thao tác bộ nhớ có thể coi nó như một con trỏ tuyệt đối.
Các danh mục lệnh
Các loại lệnh khác nhau sử dụng các trường này theo những cách cụ thể:
Di chuyển dữ liệu: Di chuyển giá trị giữa các thanh ghi và bộ nhớ:
mov64 r1, 42 // Put immediate value 42 into r1
// opcode=move_imm, dst=1, src=unused, imm=42
ldxdw r0, [r10 - 8] // Load 8 bytes from stack into r0
// opcode=load64, dst=0, src=10, offset=-8, imm=unused
stxdw [r1 + 16], r0 // Store r0 to memory at [r1 + 16]
// opcode=store64, dst=1, src=0, offset=16, imm=unusedSố học: Thực hiện các phép toán học:
add64 r1, r2 // r1 = r1 + r2
// opcode=add_reg, dst=1, src=2, offset=unused, imm=unused
add64 r1, 100 // r1 = r1 + 100
// opcode=add_imm, dst=1, src=unused, offset=unused, imm=100Luồng điều khiển: Thay đổi trình tự thực thi:
ja +5 // Jump forward 5 instructions unconditionally
// opcode=jump, dst=unused, src=unused, offset=5, imm=unused
jeq r1, r2, +3 // If r1 == r2, jump forward 3 instructions
// opcode=jump_eq_reg, dst=1, src=2, offset=3, imm=unused
jeq r1, 42, +3 // If r1 == 42, jump forward 3 instructions
// opcode=jump_eq_imm, dst=1, src=unused, offset=3, imm=42Mã hóa opcode
Mã hóa opcode nắm bắt nhiều thông tin ngoài loại thao tác:
Lớp lệnh: Số học, bộ nhớ, nhảy, gọi, v.v.
Kích thước thao tác: các thao tác 32-bit hay 64-bit
Loại nguồn: Thanh ghi hay giá trị tức thời
Thao tác cụ thể: Cộng hay trừ, tải so hay lưu trữ, v.v.
Việc này tạo ra các mã thao tác riêng biệt cho các biến thể lệnh. Ví dụ, add64 r1, r2 (nguồn thanh ghi) sử dụng mã thao tác khác với add64 r1, 42 (nguồn giá trị tức thời). Tương tự, add64 và add32 có các mã thao tác khác nhau cho các kích thước thao tác khác nhau.
Các thao tác số học phân biệt thêm giữa các biến thể có dấu và không dấu. udiv64 coi các giá trị là không dấu (0 đến 18 nghìn tỉ), trong khi sdiv64 xử lý các giá trị có dấu (-9 nghìn tỉ đến +9 nghìn tỉ).
Thực thi lệnh
Mã opcode xác định cách VM diễn giải các trường còn lại.
Khi VM gặp add64 r1, r2, nó đọc opcode và nhận ra đây là một thao tác số học 64-bit sử dụng hai thanh ghi:
Trường dst chỉ ra kết quả lưu vào r1, trường src chỉ định r2 là toán hạng thứ hai, và các trường offset và immediate bị bỏ qua.
Đối với add64 r1, 42, opcode thay đổi để chỉ ra một thao tác tức thời. Bây giờ dst vẫn trỏ đến r1, nhưng src trở nên vô nghĩa, và trường immediate cung cấp toán hạng thứ hai (42).
Các thao tác bộ nhớ kết hợp nhiều trường một cách có ý nghĩa:
Đối với ldxdw r1, [r2+8], opcode chỉ ra một thao tác tải bộ nhớ 64-bit, dst nhận giá trị được tải, src cung cấp địa chỉ cơ sở, và offset (8) được thêm vào để tạo địa chỉ cuối cùng r2 + 8.
Các lệnh điều khiển luồng tuân theo cùng một mẫu:
Khi bạn viết jeq r1, r2, +5, mã opcode mã hóa một lệnh nhảy có điều kiện so sánh hai thanh ghi. Nếu r1 bằng r2, VM sẽ thêm offset (5) vào bộ đếm chương trình, nhảy về phía trước 5 lệnh.
Lệnh gọi hàm và Syscall
Cơ chế gọi của sBPF đã phát triển qua nhiều phiên bản để có được sự rõ ràng và độ bảo mật tốt hơn. Cho đến sBPF v3, call imm phục vụ hai mục đích: giá trị tức thời xác định liệu bạn đang gọi một hàm nội bộ hay đang gọi một syscall.
Runtime phân biệt giữa các loại này dựa trên phạm vi giá trị tức thời, với các số syscall thường là các số nguyên dương nhỏ như 16 cho sol_log.
Từ sBPF v3 trở đi, các lệnh được tách biệt để có hành vi rõ ràng. call giờ đây xử lý các lệnh gọi hàm nội bộ bằng cách sử dụng offset tương đối, trong khi syscall imm gọi rõ ràng các hàm runtime. Sự tách biệt này làm rõ ý định của bytecode và cho phép xác minh tốt hơn.
Các lệnh gọi gián tiếp thông qua callx cũng đã phát triển. Các phiên bản trước mã hóa thanh ghi đích trong trường tức thời, nhưng từ v2 trở đi, nó được mã hóa trong trường thanh ghi nguồn để đảm bảo tính nhất quán với định dạng lệnh chung.