Memanfaatkan Kesalahan dalam Assembly

10 Nov 2011 Memanfaatkan Kesalahan dalam Assembly

Kesalahan adalah sampah yang tidak ada manfaatnya. Terlebih kesalahan fatal, yang ada hanyalah kerugian. Meskipun ada yang bilang bahwa kesalahan atau kegagalan adalah sebuah pelajaran berharga, namun harus kita hindari sejauh mungkin jangan sampai menimpa diri kita. Karena harga pelajaran gituan kadang nggak mampu kita bayar. Demikian setidaknya apa yang kita pahami dalam berbagai ajaran sosial, baik budipekerti maupun agama.

Ternyata ajaran semacam ini tidak selalu berlaku di dunia rekayasa IT, terutama OS dan middleware. Banyak kesalahan yang diplintir menjadi sebuah manfaat yang mengagumkan. Apa itu gerangan? Virtualisasi !!!

Virtualisasi Memori berawal dari kesalahan

Apa itu virtualisasi memori? Virtualisasi memori adalah sebuah teknologi yang memanfaatkan kesalahan akses memori. Bagaimana bisa? Secara sederhana virtualisasi memori adalah manajemen memori unttuk mewujudkan kapasitas memori sesuai dengan tingkat addressing-nya meskipun kapasitas yang sebenarnya kurang dari itu. Misalnya, untuk tingkat addressing 32bit, maka memori dikelola sedemikian rupa dimana dari address 00000000h sampai dengan FFFFFFFFh dapat diakses, meskipun fisiknya tidak sampai segitu. Artinya, secara virtual, kapasitas memori tersebut seolah-olah mencapai 4GB meskipun sebenarnya modul memori yang terpasang kurang dari 4GB. Apa yang dilakukan manajemen memori?

Intinya begini? Katakanlah memori yang terpasang di komputer kita hanya 1 GB. Sedangkan memory bus komputer kita 32bit. Berarti address yang tersedia hanya 00000000h sampai dengan 3FFFFFFFh. Address 40000000h sampai dengan FFFFFFFFh absen. Ketika ada program mengakses lokasi 4006C000h tentu akan “kejeglong” bak roda kereta anjlog ke luar rel. Kesalahan ini dinamakan addressing exception atau memory fault, dan tentu menimbulkan interrupsi. Interupsi inilah yang dimanfaatkan untuk memicu manajemen memori untuk memlintir alamat 4006C2A0h ke lokasi yang sedang tidak dipakai. Misalnya kapling memori yang paling santai adalah di lokasi 20007A00h. Lantas isi memori di loasi 20007A00h dipindahkan ke disk. Jika data yang dipindahkan ke disk sebesar 4KB atau 1000h, maka alamat 4006C2A0h sampai dengan 4006D29Fh dibelokkan ke 20007A00h sampai dengan 200089FFh. Untuk memudahkan pembelokkan, alamat yang kejeglong dan alamat pengganti ditabulasikan dalam tabel yang dinamakan translation lookaside buffer (TLB) yang digunakan sebagai kamus. .

Ketika pemilik lokasi 20007A00h mau memakainya, maka data sang peminjam yang ada disana digulung dan disimpan di disk. Data sang pemilik yang disimpan di diak dimuat kembali ke memori sebelum sang pemilik menyadarinya. Semua ini awalnya dikerjakan oleh OS.

Cara tersebut memang berhasil. Tetapi ternyata sangat overhead. Karena setiap kali ketemu dengan alamat yang salah harus me-lookup TLB. Dan ketika di TLB tidak ditemukan, maka OS harus mencari lokasi yang kosong byte demi byte. Setelah ditemukan, lantas disusun lagi kamus di TLB. Demikian seterusnya sepanjang perjalanan sistem. Mekanisme ini sangat memakan sumberdaya sistem.

Lantas dicari cara yang lebih optimal. Memori disusun segmentasi berjenjang sesuai dengan kemampuan mesin dan karakteristik beban yang paling lazim. Untuk arsitektur mainframe karena kinerja I/O-nya sangat kuat maka untuk mengejar efisiensi proses segmentasinya dimulai dari 4KB dan jenjang berikutnya 1MB. Segmen yang 4KB dinamakan page dan yang 1MB dinamakan segment. Pembelokan hanya dilakukan pada bit-bit alamat di tingkat page dan segment seperti pada diagram sebelah. Sehingga 12bit paling belakang tidak mengalamai translasi. Sehingga alamat 4006C2A0h tidak mungkin diplintir menjadi 20007A00h. Real address yang didapat pasti XXXXX2A0h, dimana offset 2A0h tetap. Proses translasi menjadi lebih cepat dan penggunakan YLB lebih efisien.

Gambaran ini mencoba menyederhanakan mekanisme yang sebenarnya dan mengambil contoh addressing 32bit supaya tidak kepanjangan tapi tidak terlalu kuno seperti 16bit dan 24bit. Bit paling depan dikosongkan agar tidak aneh jika dibaca orang mainframe. Karena di mainframe ketika masih 32bit, bit yang pertama digunakan untuk indikator guna mengakomodir program-program yang masih 24bit. Jika bit pertama nyala, berarti seluruh bit berikutnya (31bit) digunakan untuk addressing. Jika bit pertama mati, maka hanya 24bit paling belakang yang digunakan untuk addressing. Rugi 2GB tapi menjamin kompatibilitas 100% dengan generasi awal. Setelah 64bit, untuk program 64bit tidak ada bit yang dikorbankan. Tetapi untuk program 32bit, tetap berlaku 31bit/24bit. Sehingga secara keseluruhan, kompatibilitas masih tetap dijamin 100%, baik 31bit maupun 24bit.

Dalam implementasi yang sebenarnya, untuk arsitektur mainframe masih ada 2 translasi lain di atas jenjang segmen, yaitu region dan address space (ASN). Bahkan untuk logical partition dan/atau virtual machine, masih ditambah lagi prefixing (PFX). Jenjang ASN dan PFX sudah tidak linier lagi dengan addressing. ASN untuk multiple address spaces guna menyelenggarakan virtual memori ganda. Penyelenggaraannya pun tidak lagi sepenuhnya ditangani oleh OS, tetapi oleh sejumlah komponen hardware pendukung guna menekan overhead, antara lain dynamic address translation (DAT). OS hanya mempersiapkan tabel-tabel translasi di memori di awal. Pemicu translasi pun bukan lagi interupsi kesalahan addressing. Karena sejak ada jenjang ASN, setiap task yang memasuki periode exekusi, oleh OS dipersiapkan memori address space sebesar region yang dipesan pada saat task pertama kali dijalankan. Dengan demikian, tidak akan terjadi page fault kecuali task tersebut memakan memori lebih dari region yang dimintanya.

Intel pun sejak IA-32 sudah ada model segmentasi berjenjang seperti pada mainframe. Istilah address space juga diperkenalkan, tetapi tidak sama dengan ASN di mainframe. Satu ASN di mainframe setara dengan satu linear address space di Intel. Tetapi mainframe bisa menyelenggarakan multiple ASN secara simultan dan setiap task menempati satu linear address space mulai dari address 0 hingga 16 MB untuk 24bit mode, 2 GB untuk 31bit mode, atau 16,777,216 TB untuk 64bit mode. Sedangkan Intel hanya menyelenggarakan satu linear address space saja. Dalam segmented mode, di dalam linear address space bisa dibangkitkan beberapa segment yang terisolasi satu sama lain, mirip subspace di mainframe, tetapi banyak yang bersifat special purpose seperti untuk stack, task-states saving dll mirip control block di mainframe.

Kesalahan addressing masih dimanfaatkan

Virtualisasi sudah tidak lagi berbasis interupsi kesalahan addressing, setidaknya pada arsitektur mainframe. Selama DAT aktif, OS beserta seluruh program aplikasi bermain di virtual memori diselenggarakan oleh DAT dan seluruh komponen pendukungnya. OS hanya mempersiapkan tabel-tabel di awal dan memicu swapping setiap siklus task dispatching dan paging setiap ada permintaan memori dari task yang tidak terpenuhi. Namun bukan berarti kesalahan addressing sudah tidak ada mainfaatnya.

Sebuah middleware seperti database engine atau transaction service (misalnya CICS), adalah sebuah framework untuk menjalankan program-program lain yang menjadi aplikasi dari middleware tersebut. Program-program aplikasi tersebut bisa merupakan bagian dari middleware tersebut ataupun program orang lain. Program aplikasi DB2 adalah program orang lain. Program-program aplikasi CICS sebagian program orang lain (users) dan sebagian merupakan bagian dari CICS, misalnya CEMT, CEDA, CECI dll.

Di dalam framework CICS ada beberapa services, antara lain memory services. Program aplikasi dilarang meminta memori langsung ke OS, melainkan harus memintanya kepada CICS dengan perintah EXEC CICS GETMAIN. Ini membuktikan bahwa CICS menyediakan manajemen memori sendiri.

Nah… kita tahu bahwa CICS hanyalah middleware, bukan OS. Tidak mungkin CICS mengendalikan DAT, TLB dan tabel-table segmen seperti OS. Lantas apa yang dilakukan CICS dalam mengelola memori untuk program-program aplikasinya? Persisnya saya tidak tahu karena saya tidak pernah membedah jeroan CICS. Tetapi secara logika saya menduga awalnya paling hanya alokasi statik dari kapling memori yang diberikan oleh OS sesuai dengan spesifikasi region yang tertera dalam prosedur JCL-nya. Berarti layanan memori akan berhenti manakala seluruh kapling sudah teralokasi ke program transaksi yang jalan saat itu. Namun kian hari pengguna menuntut CICS untuk menjalankan lebih banyak program transaksi. Akhirnya manajemen memori diperbaiki menjadi dinamik. Artinya, alokasi memori baru diberikan kepada program transaksi ketika benar-benar diperlukan dan dilepas lagi manakala sudah tidak diperlukan. Secara logika, yang paling mudah adalah alokasi berbasis event (event-driven allocation). Nah event yang digunakan yang paling mudah adalah kesalahan addressing.

Ketimbang menduga-duga CICS, mending ngomongin zJOS-XDI yang benar-benar pasti karena bikinan saya sendiri. zJOS-XDI adalah paket otomasi yang di dalamnya ada 3 produk, Sekar, Puspa dan AutoXfer. Tiga-tiganya jalan dalam satu address space zJOS-XDI. Secara teknis, zJOS-XDI sebenarnya adalah middleware, dimana Sekar, Puspa dan AutoXfer adalah aplikasinya. Orang lain juga bisa bikin program aplikasi sesuai dengan fungsi-fungsi yang tersedia dalam framework. Bahkan karena sebagian fungsinya saya sediakan dalam bentuk fungsi-fungsi Rexx, maka tanpa harus mengetahui arsitektur lengkap zJOS-XDI, orang lain bisa bikin aplikasi dengan Rexx. Baik Sekar, Puspa, AutoXfer maupun program aplikasi lain, tentu memerlukan memori. Sehingga zJOS-XDI harus menyediakan layanan memori sendiri mirip CICS. Sebagian besar alokasi statik karena saya tidak merencanakan zJOS-XDI untuk menjalankan aplikasi sebanyak yang dilakukan CICS. Paling hanya Sekar, Puspa, AutoXfer dan satu atau dua program aplikasi bikinan user. Kenyataannya di user site, dalam kondisi paling peak, Sekar hanya sekitar 5 subtask, Puspa 30an subtask dan AutoXfer selalu 1 subtask. Ditambah program aplikasi lain, total paling banyak hanya sekitar 50an subtask. Sehingga alokasi statik pun sudah sangat cukup. Namun demikian, saya sudah menyiapkan fitur event-driven allocation yang berbasis abend 0C4. Caranya, dalam recovery routine disalin abend-code dan rekaman PSW saat kesalahan terjadi, seperti potingan rutin berikut ini. Ini saya salin langsung dari salah satu modul zJOS-XDI. Tidak ada yang rahasia, karena semua jago systems programming pasti tahu teknik ini.


using Recovery,r12 Map of the routine
using DERSVA,r9 Map of SVA
Recovery equ *
lr r12,r15 establish routine addressability
ch r0,=h'12' have SDWA?
be rec_nosdwa no, handle it
space ,
*
* +--------------------------------+
* | Recovery with SDWA supplied |
* +--------------------------------+
*
space ,
stm r14,r12,12(r13) save all regs
lr r8,r1 get SDWA address
l r9,SDWAPARM establish SVA of abending pgm
icm r11,15,SVAcom establish COM addressability
bz rec_start go this way
clc =c'DERCOM',COMname is it correct?
bne rec_start no, skip below
lh r15,COMabend_ctr abend counter
la r15,1(r15) incremented
sth r15,COMabend_ctr update abend counter
rec_start equ *
tm SDWAERRD,SDWACLUP is retry allowed?
bo rec_percolate no, percolate it
mvc
SVAabcode,SDWAABCC get abend code (SDWACMPC)
mvc
SVAecpsw,SDWAEC1 get PSW
mvc SVAabregs,SDWAGRSV copy all saved regs
l r1,SDWAXPAD address of pointer directory
using SDWAPTRS,r1 establish dir addressability
l r1,SDWASRVP address of SDWA extension 1
drop r1
using SDWARC1,r1 establish ext 1 addressability
mvc SVArscode,SDWACRC get reason code
drop r1
mvc SDWASR12,SVArecov1 update SDWA, reg 12 as base 1
mvc SDWASR15,SVArecov2 update SDWA, reg 15 as base 2
rec_retry equ *
l r2,SVArecov1 get retry address
SETRP WKAREA=(8),RC=4,RETADDR=(2),FRESDWA=YES,RETREGS=YES, +
REGS=(14),dump=NO,record=NO

Yang berwarna merah, SVAabcode dan SVAerpsw adalah salinan abend code dan PSW dari kernel ke wilayah lokal zJOS-XDI. Final dari recovery routine di atas adalah untuk kembali ke lokasi TKP setelah exekusi SETRP. Sebelum benar-benar memasuki TKP, terlebih dulu SVAabcode dievaluasi apakah abend-code-nya 0C4.(memory fault). Jika iya, lantas salinan PSW dievaluasi guna menemukan kapling memori mana yang kekurangan, seperti contoh di bawah ini. Mohon maaf tidak saya beberkan apa adanya disini karena meskipun blog ini berbahasa lokal, namun tidak kurang mesin penterjemah. .


...
l r1,
SVAabcode salin abend code ke reg 1
sll r1,8 geser 8 bit reg 1 ke kiri
srl r1,20 geser 20 bit reg 1 ke kanan
ch r1,=x'00C4' benarkah abend 0C4?
bne ordinary_retry tidak, menyimpang ke ordinary_retry
l r1,
SVAerpsw+4 ya, salin alamat dimana abend terjadi
<cari lokasi terjadinya memory>
<alokasikan ukuran yg lebih besar>
<salin data ke lokasi baru>
<alihkan pointer dan putus lokasi lama>

...

Teknik ini merupakan salah satu contoh bukti manfaat kesalahan addressing untuk mengelola memori di tingkat middleware. Mungkin masih ada manfaat lain yang di luar pemikiran saya saat ini.

Hypervisor juga memanfaatkan kesalahan

Berbicara soal hypervisor adalah berbicara tentang virtualisasi mesin. Apabila EIP sebuah CPU menunjuk ke suatu lokasi tertentu di memori, berarti lokasi tersebut akan segera diexekusi. Bahkan jika yang sedang ongoing saat itu bukan exekusi instruksi branching, bisa dipastikan isi lokasi yang ditunjuk EIP sudah disalin di processor cache atau high speed buffer (HSB). Oleh karena itu, data yang ada di lokasi tersebut harus instruksi mesin yang sah. Jika tidak, maka processor akan mengalami salah satu dari 3 kemungkinan kegagalan exekusi:

  1. Operation exception, jika tidak ada opcode yang match dengan byte awal di lokasi tersebut, atau
  2. Specification exception, jika byte awal kebetulan match dengan salah satu opcode, tetapi tidak diikuti susunan operand yang sah, atau
  3. Nowhere, jika byte awal kebetulan match dengan salah satu opcode dan kebetulan pula diikuti data yang mirip susunan operand yang sah padahal yang dimaksudkan bukan instruksi.

Ketiga kesalahan di atas hanya mungkin terjadi untuk program yang disusun dalam assembly. Program yang disusun dalam HLL seperti C/C++, Cobol, Rexx dll tidak mungkin mengalami hal tersebut karena program hanyalah data text yang mirip bahasa Inggeris, yang tentu semuanya salah total jika diexekusi oleh processor. Yang menyusun instruksi mesin adalah compiler atau interpreter dari hasil kompilasi data text yang ditulis programmer. Tentu saja compiler atau interpreter dilengkapi tabel opcode dan format instruksi agar tidak pernah membuat kesalahan yang tidak perlu.

Kegagalan exekusi bisa tersamar tanpa kita sadari. Misalnya program branch ke lokasi yang salah seperti contoh berikut ini:


...
using *,r12 menunjuk register 12 sebagai base register
bas r1,tahap1 loncat ke tahap1 dan r1 = lokasi setelah BAS
dc c'JAKARTA' data JAKARTA
tahap1 mvc kota,0(r1) salin data dari lokasi setelah BAS ke kota
...
kota ds cl10 lokasi variable kota
...

Contoh di atas sepintas tidak ada yang salah. Tetapi jika diexekusi pasti abend dengan kode 0C1h karena operation exception atau 0C6h karena specification exception. Kenapa demikian? Mari kita cermati. Misalkan instruksi BAS r1,tahap1 berada di lokasi 1000h. Panjang instruksi tersebut 4. Berarti data text JAKARTA berada pada lokasi 1004h sepanjang 7. Sehingga instruksi MVC kota,0(r1) yang berlabel tahap1 berada di lokasi 1011 atau 100Bh. Seperti yang telah dijelaskan pada posting terdahulu tentang Mengenal Assembly, bahwa processor bekerja mengikuti memory word alignment, yang rata-rata kelipatan 2, termasuk mainframe. Karena aturan tersebut, titik exekusi tidak mungkin jatuh di lokasi 100Bh, melainkan bergeser mlorot ke 100Ch. Sehingga instruksi MVC kota,0(r1) akan terbaca bergenjang 8bit. Untuk lebih mudah mencermati, contoh ini kita tulis ulang langsung dalam kode-kode mesin dengan asumsi variable kota berada di lokasi 1200h.


...
1000h dc x'4D10C200' bas r1,tahap1
1004h dc c'JAKARTA'
100Bh tahap1 dc x'D2
9C200C004' mvc kota,0(r1)
1011h ...
1200h kota ds cl10 lokasi variable kota
...

Instruksi MVC kota,0(r1) dalam kode mesin D29C200C004h di lokasi 100Bh. Namun exekusi akan genjang 8bit ke lokasi 100Ch, sehingga yang diexekusi bukan D29C200C004h, melainkan 9C200C004h. Maka CPU akan memanggil instruksi dengan opcode 9C. Jika tidak ada maka dicari opcode 9C2. Jika ini juga tidak ada, maka dicari opcode 9C20. Jika tetap tidak ada, maka exekusi digagalkan dengan interupsi dengan kode 0C1h. Ternyata memang tidak ada instruksi dengan opcode 9C, 9C2 maupun 0C20. Sehingga program abend dengan kode 0C1h.

Oke, lain kali saya bikinkan contoh yang abend 0C6h atau bahkan yang tidak abend tapi nowhere. Kali ini mau ngomongin hypervisor dulu.

Pada tahun 1960an ada pihak yang menggagas untuk memanfaatkan kesalahan-kesalahan seperti abend 0C1h untuk membangun virtualisasi mesin komputer. Gagasan tersebut menjadi kenyataan pada tahun 1967, yaitu sebuah produk OS pembangkit mesin virtual pada arsitektur mainframe dengan nama CP-67. OS ini lantas dibeli IBM dan dikembangkan menjadi VM/370 pada tahun 1970an dan terus berkembang hingga hari ini menjadi z/VM. Apa kaitannya dengan abend 0C1h?

Ketika itu, awal dekade 1970an, prefixing belum ada. Satu komputer hanya menyediakan memori fisik dimana real addressing sama persis dengan absolute addressing. Tabel interrupt vector di PSA dikuasai oleh VM/370 yang berperan sebagai host OS. PSA guest OS adalah virtual PSA yang lokasinya di virtual memori di luar low core yang sebenarnya. Dengan demikian, setiap interupsi selalu ditangani oleh host OS, baru kemudian diserahkan kepada guest OS dengan cara mengexekusi LPSW data yang telah disiapkan guest OS di virtual PSA-nya. OS seperti MVS tidak dirancang untuk menjadi guest OS di VM/370, maka jika dipaksakan menjadi guest OS tetap saja bertingkah laku seperti bukan guest OS, yaitu melakukan virtualisasi memori sendiri dan paging/swapping sendiri. Tentu sangat overhead. Tetapi OS seperti VSE dirancang ulang untuk memiliki fitur opsi sebagai guest OS. Sedangkan GCS dan CMS memang dirancang hanya sebagai guest OS. OS yang memiliki fitur guest mode tidak melakukan virtualisasi memori maupun paging/swapping ketika running sebagai guest OS. Tentu sangat mengurangi overhead.

Nah, OS yang dirancang memiliki fitur guest mode bisa berkomunikasi langsung dengan host OS untuk meminta layanan virtualisasi yang selanjutnya dinamakan layanan hypervisor. Sementara CPU tidak menyediakan instruksi khusus untuk memanggil hypervisor. Maka dipilihlah instruksi dengan opcode 00 yang sebenarnya tidak ada. Begitu ketemu opcode 00, maka CPU akan terinterupsi karena operation exception. Interupsi ini tentu direspon oleh interrupt handler milik host OS. Namun kali ini bukan meng-abend-kan program yang terputus oleh interupsi, melainkan untuk memberikan layanan-layanan khusus yang dimintanya sesuai dengan informasi beberapa bit yang mengikuti opcode 00 tersebut. .

Kini VM/370 telah menjadi z/VM yang jauh lebih canggih. Fitus guest mode juga sudah tidak terlalu penting, karena mainframe System z sudah dilengkapi dengan berbagai fungsi virtualisasi yang terimplementasi secara hardware. Bahkan tanpa z/VM pun, System z dapat dibagi-bagi dalam beberapa partisi logik (LPAR) untuk menjalankan beberapa OS. Namun demikian, opcode 00 masih digunakan baik oleh OS maupun program aplikasi untuk memanggil hypervisor.

BERSAMBUNG …

Topik-topik terkait

  1. Mengenal Assembly
  2. Systems programming
  3. Systems programming #2 (lanjutan)
mm
Deru Sudibyo
deru.sudibyo@gmail.com
2 Comments
  • Dino Eko Abdul Rahman
    Posted at 19:26h, 30 July Reply

    Om Deru … dapet salam dari Suhu
    (Pak Mego)

    Dino.

    • mm
      Deru Sudibyo
      Posted at 19:55h, 30 July Reply

      Walaikum salaam…
      Kok nggak nulis sendiri njeng Sunan Mego Honggowongso 🙂

Post A Comment