
Cara Menulis Program Solana dengan SBPF Assembly
Daftar Isi
- Arsitektur Mesin Virtual sBPF
- Arsitektur Set Instruksi sBPF
- Model Memori sBPF
- Syscall Solana dalam sBPF
- Siapkan Lingkungan Anda
- Siapkan Proyek Anda
- Contoh Memo sBPF Assembly
- Bangun dan Terapkan Program Anda
- Berinteraksi dengan Program Anda
- Validasi Input
- Pemeriksaan Batas Memori
- Pengelolaan Register
- Keamanan Aritmetika
- Validasi Parameter Syscall
Saat ini, ekosistem Solana tengah mengalami perlombaan paralel untuk mencapai level terendah dalam pengoptimalan program.
Pada tingkat tinggi, library seperti Pinocchio merevolusi pengembangan Rust dengan meningkatkan efisiensi komputasi hingga berkali-kali lipat. Sementara itu, pada tingkat yang benar-benar paling rendah, sekelompok developer berdedikasi yang dipersatukan oleh ketidaksukaan mereka terhadap compiler melangkah lebih jauh. Alih-alih menulis program Solana dalam bahasa yang dikompilasi seperti Rust atau C, mereka berfokus menyusun bytecode secara manual dengan cermat untuk memaksimalkan performa dari setiap instruksi.
Peningkatan pada tingkat rendah ini hanya dapat dicapai jika kita memberikan instruksi langsung kepada VM dalam bahasa native-nya: sBPF Assembly, varian extended Berkeley Packet Filter (eBPF) milik Solana, yaitu bytecode yang digunakan dan dieksekusi dalam setiap program on-chain.
Menulis assembly sBPF memberi developer akses langsung ke antarmuka tingkat terendah Solana Virtual Machine. Compiler Rust dan LLVM memang mencoba melakukan pengoptimalan. Namun, karena sintaks bahasa tidak cukup eksplisit atau konteksnya tidak memadai untuk menentukan pilihan kompilasi yang lebih baik, keduanya sering menghasilkan bytecode yang kurang optimal dibandingkan hasil developer berpengalaman yang memiliki kontrol penuh pada tingkat instruksi melalui assembly.
Meskipun kontrol tambahan ini mengorbankan kemudahan penggunaan, penghematan dalam pemakaian compute unit dan ukuran binary—yang berarti juga biaya sewa—sangatlah besar. Penghematan ini menjadi sangat penting dalam operasi yang sangat kompetitif, diperebutkan, dan sangat bergantung pada performa.
Pada saat yang sama, ada pula alasan kuat bahwa tidak semua program seharusnya ditulis dalam assembly.
Walaupun kondisinya sudah jauh membaik, secara historis tooling yang tersedia terbatas. Yang lebih penting, peningkatan performa sering disertai konsekuensi besar berupa verifikasi kebenaran secara manual dan biaya audit yang lebih tinggi. Penyebabnya adalah kurangnya tooling otomatis serta sintaks yang lebih sulit dibaca, ditulis, dan dipahami.
Sebaliknya, bahasa yang dikompilasi juga dapat dianggap sebagai kotak hitam yang menyembunyikan pilihan compiler. Karena itu, transparansi dan kontrol tambahan dari assembly sering kali dapat mengungkap hal-hal yang sulit terlihat saat menggunakan bahasa yang dikompilasi. Faktanya, sebagian besar terobosan performa terbaru dalam SDK Rust kami ditemukan dan dipandu oleh kesadaran bahwa kami dapat menyusun bytecode yang lebih efisien secara manual daripada compiler.
Dalam artikel ini, Anda akan mempelajari:
- Apa itu sBPF Assembly dan bagaimana teknologi ini memberikan kontrol langsung atas mesin virtual
- Evolusi dari Berkeley Packet Filter menjadi eBPF dan alasan Solana mengadopsinya
- Arsitektur mesin virtual, set instruksi, dan model memori sBPF
- Cara menyiapkan lingkungan pengembangan dan membangun program sBPF
- Pemrograman assembly langkah demi langkah melalui contoh memo praktis
- Pertimbangan keamanan penting saat menulis kode tingkat rendah
Apa Itu Assembly?
Assembly adalah varian machine code yang dapat dibaca manusia: bahasa pemrograman tingkat terendah yang berkaitan langsung dengan set instruksi CPU atau VM.
Alih-alih variabel dan fungsi, assembly menggunakan register (lokasi penyimpanan sementara yang cepat di CPU), alamat memori (lokasi fisik di RAM atau disk), dan operasi dasar seperti load (membaca dari memori), store (menyimpan ke memori), aritmetika, serta jump (alur kontrol).
Setiap instruksi dalam assembly dipetakan satu banding satu ke instruksi yang setara dalam machine code.
Pemetaan satu banding satu ini memungkinkan programmer mengontrol secara presisi apa yang dieksekusi prosesor, termasuk register mana yang menyimpan data, cara memori diakses, dan urutan operasi yang tepat.
Berbeda dari bahasa tingkat tinggi, yang satu fungsinya dapat menghasilkan puluhan instruksi, assembly menawarkan transparansi dan kontrol penuh atas perilaku mesin tanpa abstraksi yang tidak transparan.
Apa Itu Berkeley Packet Filter (BPF) dan eBPF?
Berkeley Packet Filter (BPF) dibuat pada tahun 1992 sebagai mesin virtual untuk memfilter paket jaringan secara efisien dalam kernel Unix. BPF awal menggunakan set instruksi sederhana dan arsitektur berbasis register yang dapat menjalankan kode secara aman dalam sandbox di dalam kernel.
Extended Berkeley Packet Filter (eBPF) memodernisasi konsep ini dengan berkembang dari filter paket menjadi mesin virtual serbaguna. eBPF memperkenalkan arsitektur 64-bit, lebih banyak register, dan set instruksi yang lebih kaya sehingga program kompleks dapat berjalan dengan aman di ruang kernel untuk jaringan, keamanan, dan pemantauan sistem.
Solana mengadopsi eBPF karena teknologi ini menyediakan lingkungan eksekusi aman yang telah teruji dengan sandboxing bawaan. Sandboxing mencegah program mengakses resource sistem, menyebabkan node berhenti beroperasi, atau mengganggu program lain. Sementara itu, eksekusi deterministik memastikan semua validator menghasilkan keluaran yang identik.
Selain itu, arsitektur berbasis register dan toolchain yang matang menjadikannya ideal untuk eksekusi on-chain berperforma tinggi, sedangkan backend LLVM yang telah tersedia memungkinkan developer melakukan kompilasi dari bahasa tingkat tinggi seperti Rust.
Arsitektur Mesin Virtual sBPF
Saat program Solana dieksekusi, runtime memuat bytecode sBPF ke dalam memori, melakukan verifikasi statis untuk memastikan keamanannya—dengan memeriksa loop tak terbatas, akses memori yang tidak valid, dan penggunaan instruksi yang benar—lalu mengeksekusinya di dalam mesin virtual.
VM menyediakan lingkungan eksekusi 64-bit terkontrol yang memungkinkan program berjalan sepenuhnya terisolasi dari sistem host dan program lain, dengan semua akses resource dimediasi melalui runtime.
Arsitektur Set Instruksi sBPF
sBPF beroperasi dengan sebelas register 64-bit (r0-r10), dengan r10 sebagai frame pointer read-only dan r0 sebagai register pengembalian.
Instruksi mengikuti format konsisten dengan opcode yang menentukan operasi (aritmetika, logika, akses memori, jump) dan operand yang menunjukkan register sumber/tujuan, offset, dan/atau nilai langsung.
Kategori instruksi utama mencakup operasi ALU (penjumlahan, pengurangan, bitwise), operasi memori (load/store), dan alur kontrol (jump kondisional/tanpa syarat).
Model Memori sBPF
Program sBPF beroperasi dalam tata letak memori terstruktur: stack 4 KB untuk variabel lokal dan pemanggilan fungsi, heap untuk alokasi dinamis, data program read-only yang berisi bytecode dan konstanta, serta region data account yang dipetakan ke account Solana dan dapat diakses program selama eksekusi.
Semua akses memori diperiksa batasnya, dan program tidak dapat mengakses memori di luar region yang ditetapkan.
Syscall Solana dalam sBPF
Program sBPF tidak dapat mengakses resource sistem atau melakukan operasi I/O secara langsung. Sebagai gantinya, program meminta layanan melalui syscall, yaitu instruksi khusus yang mengalihkan kontrol ke runtime Solana.
Dalam assembly sBPF, syscall dipanggil menggunakan instruksi call dan simbol call yang diubah menjadi target call oleh compiler saat proses assembly. Saat ini, syscall dipanggil melalui relokasi dinamis berbasis teks, yaitu sistem tabel pencarian string kompleks yang memetakan simbol ke hash Mumur3 32-bit saat kompilasi JIT. Namun, terdapat proposal aktif untuk menggantinya dengan syscall statis sehingga konvensi pemanggilan menjadi jauh lebih sederhana. Saat syscall dipanggil, argumen diteruskan melalui register 1 hingga 5, dengan register 5 terkadang berfungsi sebagai stack spill, sedangkan nilai pengembalian ditulis kembali ke r0.
Syscall yang umum mencakup operasi memori (sol_memcpy, sol_memcmp), fungsi kriptografis (hashing, verifikasi tanda tangan), logging, dan pemanggilan lintas program.
Meskipun semua instruksi sBPF biasa memiliki biaya 1 CU, penghitungan compute unit untuk syscall dilakukan dengan cara yang sangat berbeda. Saat ditambahkan ke protokol, setiap syscall di-benchmark untuk menentukan biaya dasar pemanggilan (misalnya, pemanggilan CPI dikenai biaya 1000). Dalam beberapa kasus, biaya variabel tambahan juga diterapkan berdasarkan jumlah data yang digunakan.
Tutorial SBPF Assembly
Siapkan Lingkungan Anda
Secara tradisional, penulisan assembly sBPF memerlukan seluruh toolchain Solana: proses yang besar, kompleks, dan bergantung pada platform.
Karena itu, Dean Little membuat sBPF SDK yang menyediakan solusi menyeluruh untuk melakukan bootstrap, membangun, mengompilasi, menguji, dan menerapkan program sBPF.
Anda dapat menginstal SDK di sistem operasi apa pun menggunakan Cargo:
cargo install --git https://github.com/blueshift-gg/sbpf.gitSebelum mulai menulis kode, sebaiknya instal juga ekstensi VS Code sBPF Assembly untuk penyorotan sintaks, pelengkapan otomatis, dan deteksi error.
Siapkan Proyek Anda
Buat scaffold proyek baru dengan:
sbpf init <name_of_the_project>Perintah ini membuat proyek dengan pengujian Rust Mollusk.
Jika ingin membuat scaffold dengan pengujian TypeScript, Anda dapat menggunakan perintah berikut untuk menginisialisasi scaffold baru dengan pengujian TypeScript:
sbpf init <name_of_the_project> --ts-tests Contoh Memo sBPF Assembly
Sulit membayangkan program yang lebih sederhana daripada memo. Inilah alasan memo menjadi pengantar sempurna untuk assembly sBPF.
Program ini hanya melakukan satu hal: menerima data instruksi apa pun yang Anda kirimkan dan mencatatnya ke blockchain. Tanpa account dan tanpa logika kompleks, hanya kontrol murni pada tingkat instruksi atas Solana Virtual Machine.
Mari kita mulai dengan mempelajari program lengkapnya:
.equ NUM_ACCOUNTS, 0x00
.equ DATA_LEN, 0x08
.equ DATA, 0x10
.globl entrypoint
entrypoint:
ldxdw r0, [r1+NUM_ACCOUNTS]
ldxdw r2, [r1+DATA_LEN]
add64 r1, DATA
call sol_log_
exitTentukan Konstanta Anda
Program dimulai dengan menentukan tiga konstanta yang memetakan struktur region input berseri dari runtime Solana:
.equ NUM_ACCOUNTS, 0x00 // Account count offset
.equ DATA_LEN, 0x08 // Data length offset
.equ DATA, 0x10 // Data start offsetOffset ini sesuai dengan lokasi runtime Solana mengemas data instruksi dalam memori.
Saat VM memanggil program kita, VM memberikan buffer terstruktur dalam register r1, dan konstanta ini memungkinkan kita menavigasi struktur tersebut.
Tool seperti sbpf.xyz dapat menghitung offset ini secara otomatis berdasarkan tata letak data account dan instruksi Anda.
Buat Entrypoint
Kemudian, kita membuat entrypoint dan validasi untuk program kita.
.globl entrypoint
entrypoint:
ldxdw r0, [r1+NUM_ACCOUNTS]Direktif entrypoint .globl memberi tahu linker agar simbol entrypoint dapat dilihat secara global. Runtime Solana mencari simbol ini untuk mengetahui dari mana eksekusi program harus dimulai. Fakta menarik: Meskipun biasanya disebut entrypoint, simbol ini sebenarnya dapat diberi nama apa saja!
Instruksi pertama menggunakan teknik validasi cerdas yang khusus untuk assembly: karena r0 adalah register pengembalian kita dan setiap nilai pengembalian selain 0 berfungsi sebagai kode error, memuat jumlah account langsung ke r0 akan memaksa program keluar dengan kode error bukan nol jika lebih dari 0 account diteruskan.
Dengan demikian, kita tidak perlu melewati secara manual setiap account input untuk memvalidasi offset data instruksi dalam VM.
Panggil Syscall sol_log
Terakhir, kita dapat menjalankan syscall sol_log_:
ldxdw r2, [r1+DATA_LEN] ; Load memo length
add64 r1, DATA ; Point r1 to memo data
call sol_log_ ; Log the memo
exit ; Exit with r0 valueSyscall sol_log_ mengharapkan panjang pesan dalam r2 dan pointer ke pesan yang akan dicatat dalam r1. Untungnya, di region input berseri, runtime secara otomatis menambahkan penghitung panjang 64-bit di awal data instruksi. Karena itu, kita dapat memanggil syscall sol_log_ hanya dengan:
- Memuat nilai pada offset DATA_LEN ke r2
- Mengarahkan r1 ke offset awal data instruksi kita
Setelah kedua register mengarah ke nilai yang benar, kita cukup memanggil syscall, lalu keluar dengan nilai apa pun yang ada di r0.
Bangun dan Terapkan Program Anda
Anda dapat membangun program menggunakan assembler bawaan SBPF yang ditulis oleh Claire Fan; binary Rust berukuran 5 MB yang menggantikan toolchain LLVM berukuran >2 GB yang harus digunakan bersama tool platform Solana.
Untuk menjalankan build, cukup eksekusi:
sbpf buildSetelah program selesai dibangun, Anda dapat menerapkannya dengan:
sbpf deployBerinteraksi dengan Program Anda
Untuk menguji program menggunakan pengujian yang telah disiapkan dalam scaffold, Anda dapat menjalankan sbpf test. Atau, jika diinginkan, Anda dapat menjalankan seluruh pipeline dengan sbpf e2e untuk membangun, menerapkan, dan menguji program dalam satu perintah.
Pertimbangan Keamanan dalam sBPF Assembly
Menulis kode assembly berarti bertanggung jawab penuh atas keamanan: tidak ada compiler yang akan menemukan kesalahan Anda. Setiap instruksi secara langsung memengaruhi keamanan program sehingga prinsip-prinsip keamanan inti berikut sangatlah penting.
Validasi Input
Program assembly harus memvalidasi semua input secara manual. Selalu verifikasi jumlah account, panjang data, dan ukuran buffer sebelum digunakan.
Sebagai contoh, dalam program memo kita, memuat jumlah account ke r0 menghasilkan validasi otomatis: jika account diteruskan secara keliru, program akan gagal. Untuk pemrosesan data, periksa panjangnya terhadap rentang yang diharapkan sebelum mengakses memori.
Pemeriksaan Batas Memori
sBPF tidak menyediakan pemeriksaan batas otomatis. Sebelum mengakses array atau buffer, verifikasi secara manual bahwa operasi baca/tulis Anda tetap berada dalam batas yang dialokasikan. Pemeriksaan batas sederhana sebelum mengakses memori dapat mencegah crash dan kerusakan data:
jgt r2, MAX_LENGTH, error # Check if length exceeds limit
ldxb r3, [r1+r2] # Safe to load if check passesPengelolaan Register
Register menyimpan state program yang penting. Saat memanggil fungsi atau syscall, pertahankan nilai penting dengan menyimpannya ke stack atau register lain.
Frame pointer (r10) dan nilai pengembalian dalam r0 memerlukan perhatian khusus: kerusakan pada keduanya dapat menyebabkan program berhenti beroperasi atau menimbulkan kerentanan keamanan.
Keamanan Aritmetika
Deteksi overflow secara manual sangat penting untuk operasi aritmetika. Sebelum menjumlahkan nilai besar, periksa apakah hasilnya dapat melampaui batas 64-bit.
Operasi pembagian memerlukan pemeriksaan nol secara eksplisit untuk mencegah error runtime.
Validasi Parameter Syscall
Syscall mengharapkan parameter yang valid dan akan gagal jika menerima input yang tidak valid. Sebelum memanggil syscall, pastikan register memuat pointer, panjang, dan nilai yang tepat. Parameter yang tidak valid bukan hanya menyebabkan kegagalan, tetapi juga menghabiskan compute unit tanpa perlu.
Kesimpulan
Assembly sBPF bukan untuk semua orang, dan memang itulah intinya.
Sebagian besar developer sebaiknya tetap menggunakan Rust dan membiarkan compiler menangani pengoptimalan. Namun, bagi mereka yang mengoptimalkan setiap compute unit terakhir atau membangun infrastruktur yang sangat bergantung pada performa, assembly menawarkan sesuatu yang tidak dapat diberikan bahasa tingkat tinggi: kontrol penuh.
Kita telah membahas dasar-dasarnya, mulai dari memahami posisi sBPF dalam arsitektur Solana hingga membangun program memo pertama Anda.
Contoh memo tersebut mungkin tampak sederhana, tetapi menunjukkan prinsip-prinsip inti yang akan Anda gunakan dalam program yang lebih kompleks:
- Manipulasi register langsung
- Pengelolaan memori manual
- Penanganan syscall secara eksplisit
Peningkatan ini memiliki konsekuensi: Anda menukar jaring pengaman dengan kecepatan dan abstraksi dengan kontrol. Anda sebaiknya memilih assembly setelah mengoptimalkan kode Rust tetapi masih membutuhkan performa lebih tinggi, ketika membangun infrastruktur yang menganggap setiap mikrodetik berharga, atau ketika perlu melakukan sesuatu yang tidak dapat dioptimalkan dengan baik oleh compiler.
Untuk kebutuhan lainnya, hindari kerumitan tersebut dan tetap gunakan Rust.
Tool yang tersedia semakin baik, komunitasnya terus berkembang, dan manfaat performanya sudah terbukti. Namun, ingat: “kekuatan besar membawa tanggung jawab besar untuk tidak merusak apa pun”.
Jika ingin membaca konten tambahan tentang cara menggunakan assembly sBPF, pelajari Kursus Pengantar Assembly di Blueshift dan uji kemampuan Anda melalui berbagai tantangan yang tersedia di sana!
Artikel Terkait
Berlangganan Helius
Ikuti perkembangan terbaru dalam pengembangan Solana dan dapatkan pembaruan saat kami memublikasikan postingan


