Mengenal Assembly

27 Oct 2011 Mengenal Assembly

Bahasa komputer adalah penyimbolan atau pengkodean proses komputasi menggunakan text berbahasa manusia untuk mempermudah penyusunan program komputer. Setiap kode atau simbol proses bisa mewakili satu atau lebih instruksi mesin komputer dan ditulis dalam text, baik berupa kalimat pernyataan maupun expresi matematis. Misalnya

     x = a + b * c;

Expresi di atas kelihatannya satu instruksi. Tetapi sebenarnya mesin komputer tidak bisa mengerjakannya sekaligus dalam satu instruksi. Bahkan text tersebut hanya dianggap sebagai data dan komputer tidak mengerti apa maksudnya. Agar bisa diexekusi sebagai program, text di atas harus terlebih dulu dikonversi menjadi susunan instruksi mesin komputer yang tentu saja penulisannya dalam kode biner. Konversi dilakukan menggunakan program. Jaman dulu program-program semacam ini yang paling populer antara lain C, Fortran, Basic, Cobol, Pascal dan PL/1. Selain C, Basic dan Cobol, rata-rata sudah tenggelam. C ketambahan C++ dan Basic juga menjadi VB. Yang ngetop sekarang Java, PHP, Python dan Rexx karena banyak menyediakan fitur-fitur pendukung aplikasi web.

Namanya juga, program, tentu metoda dan gaya sangat tergantung pembuatnya. Dari sisi metoda ada yang konversinya nyicil diexekusi. Program semacam ini disebut interpreter. Basic, Java, PHP, Python dan Rexx termasuk interpreter. Ada pula metoda borongan, dimana konversi dilakukan seluruhnya dan obyek yang dihasilkan disimpan dalam file terpisah yang dinamakan object codes. Program ini dinamakan compiler. Yang termasuk compiler antara lain C/C++, Fortran, Cobol, Pascal, PL/1, Basic dan Rexx. Berarti Basic dan Rexx termasuk interpreter sekaligus compiler.

Dari sisi gaya, ada yang tidak mempedulikan kaidah matematika, sehingga penggarapannya a + b dulu dan hasilnya baru dikali c. Ada juga yang mengikuti kaidah matematika, tetapi menyalahkan gara-gara ada tanda titik-koma (;) di akhir expresi. Yang termasuk gaya ini antara lain Basic dan Fortran. Bahkan Cobol tidak mengenal instruksi di atas karena tidak diawali dengan kata COMPUTE seperti di bawah ini:

     COMPUTE x = a + b * c

Perbedaan gaya-gaya tersebut yang akhirnya mirip dialek dalam bahasa, sehingga setiap gaya dinamakan bahasa. Source codes yang menggunakan gaya Cobol disebut program berbahasa Cobol dan hanya bisa dikonversi oleh Cobol untuk menjadi program komputer. Yang mengikuti gaya Basic dinamakan program berbahasa Basic dan hanya bisa dikonversi oleh Basic untuk menjadi program komputer. Berikut ini contoh program sederhana berbahasa C++:

   #include <iostream.h>
   int main()
   {
      int x, y, z;
      cout << "Masukkan 2 angka.n";
      cout << "Pertama: ";
      cin >> x;
      cout << "nKedua: ";
      cin >> y;
      cout << "n";
      if (x > y)
        z = x;
      else
        z = y;
      cout << "z: " << z;
      cout << "n";
      z =  (x > y) ? x : y;
      cout << "z: " << z;
      cout << "n";
      return 0;
  }

Untuk maksud yang sama, jika ditulis dalam bahasa Rexx, program di atas menjadi sebagai berikut:

   say "Masukkan 2 angka.";
   say "Pertama: ";
   pull x; 
   say;
   say "Kedua: ";
   pull y;
   if (x > y) then,
        z = x;
   else,
        z = y;
   say;
   say "z: " z;
   z = max(x,y);
   say "z: " z;
   exit

Ketika diexekusi, program di atas, baik yang berbahasa C++ maupun Rexx, melakukan interaksi yang sama dengan kita melalui console yang rekaman keseluruhannya hingga berakhir adalah sbb:

Output: Masukkan 2 angka.
Pertama: 5

Kedua: 8

z: 8
z: 8

Apa pula Assembly?

Assembly bisa dikategorikan sebagai bahasa komputer, tetapi yang paling sederhana. Karena Assembly hanya penyimbolan atau pengkodean langsung instruksi mesin dengan text sekedar untuk mempermudah penyusunan program komputer. Expresi semacam x = a + b * c di atas tidak bisa dilakukan dalam Assembly. Karena expresi tersebut setidaknya melibatkan 2 instruksi, yaitu pengali (multiplier) untuk mengerjakan b * c dan penambah (adder) untuk mengerjakan a + hasil b * c. Bahkan agar persis seperti expresi di atas dimana hasilnya ditampung di variable x, maka harus ada satu instruksi lagi, bisa di awal atau di akhir. Sehingga dalam gaya Assembly, expresi tunggal x = a + b * c ditulis menjadi 3 instruksi sbb:

   salin  x,c
   kali   x,b
   tambah x,a

Gaya Assembly memang seperti itu. Instruksi terdiri dari operation code atau opcode dan diikuti oleh operand, bisa satu atau lebih atau bahkan tanpa operand. Hasil proses umumnya disimpan di operand pertama.

Assembly sangat berorientasi arsitektur mesin komputer. Karena memang setiap arsitektur komputer memiliki himpunan instruksi (instructions set) sendiri-sendiri yang tidak dijamin ada kesamaan satu dengan lainnya. Maka beda arsitektur beda pula Assembly-nya. Untuk arsitektur mainframe, contoh di atas harus ditulis sbb:

   l    0,c 
   ms   0,b
   a    0,a
   st   0,x

Rutin Assembly mainframe di atas malah terpaksa 4 instruksi karena dengan asumsi a, b, c dan x adalah variable integer 32 bit bertanda, dimana arsitektur mainframe tidak menyediakan instruksi aritmetik untuk memproses variable integer 32 bit langsung di memori. Maka harus menggunakan register. Mainframe menyediakan 16 general purpose register (GPR) dengan nama 0 sampai dengan 15 masing-masing berukuran 64bit. Kita boleh pilih yang mana saja, dalam hal ini kita pilih GPR 0 Karena aritmetik ini untuk integer 32 bit bertanda, maka 64-bit GPR0 hanya dipake separoh yang bagian kanan, yaitu bit 32-63. Instruksi l 0,c adalah untuk menyalin c ke GPR 0 pada posisi bit 32-63. Instruksi ms 0,b untuk mengalikan isi GPR 0 bit 32-63 dengan b dan hasilnya menimpuk GPR 0 bit 32-63. Instruksi a 0,a untuk menambahkan a ke GPR 0 bit 32-63. Dan instruksi st 0,x untuk menyalin isi GPR 0 bit 32-63 ke x di memori.

Assembly Intel lain lagi ceritanya. Register dalam arsitektur Intel punya tugas sendiri-sendiri. Untuk Intel yang versi 16bit disediakan AX untuk accumulator, BX untuk base addressing, CX untuk counter dan DX untuk displacement. Untuk yang versi 32bit namanya diawali dengan E, yaitu EAX, EBX, ECX dan EDX. Kali ini kita menggunakan versi 32bit, tentunya register yang cocok untuk contoh ini EAX. Sehingga rutin di atas ditulis dalam gaya Intel menjadi sbb:


   mov   eax, c 
   mul   b
   add   eax, a
   mov   x, eax

Ditilik dari sisi penggunaan register, Intel lebih terkekang. Kasus contoh ini register yang digunakan harus EAX atau AX. Sehingga tidak memungkinkan kita menggunakan register untuk menyimpan sementara hasil perhitungan ketika masih ada perhitungan lain yang sedang berlangsung. Sedangkan di mainframe, tidak ada pembagian tugas khusus untuk 16 GPR. Umumnya GPR 12 digunakan sebagai base register seperti BX atau EBX di Intel. Cara lama GPR 13 untuk menyimpan save area address. Tapi semua itu hanya konvensi. Sejak generasi S/390 bahkan sudah disediakan state stacking agar kita tidak diribetkan dengan save area yang menyita keleluasaan penggunaan GPR.

Namun demikian, dari sisi keluwesan instruksi, Intel lebih luwes. Jumlah instruksi lebih sedikit karena satu instruksi bisa menangani berbagai kombinasi operand. Instruksi MOV bisa digunakan untuk menyalin isi memori ke register atau sebaliknya. Sedangkan di mainframe, setiap instruksi hanya satu format. Bahkan kebalikannya, ada satu fungsi yang bisa dilakukan oleh beberapa instruksi. Misalnya menyalin memori ke register bisa menggunakan instruksi L atau ICM. L lebih cepat, tetapi dengan ICM kita bisa sekaligus menyalin dan menguji hasilnya apakah 0, negatif atau positif. Hal ini bisa dimaklumkan karena mainframe memang untuk menangani aplikasi mission-critical dimana kecepatan dan akurasi merupakan faktor paling utama disamping keamanan.

Dalam mesin komputer, menempatkan instruksi dan data bilangan integer di memori, juga meloncat alamat (branch atau jump), tidak boleh sembarangan. Isi memori yang akan diexekusi sebagai instruksi maupun diproses sebagai bilangan integer harus mengikuti aturan pelurusan sesuai mekansime memory interleaving. Umumnya kelipatan 2, termasuk mainframe maupun Intel. Karena itu panjang instruksi selalu kelipatan 2. Alamat bilangan integer 16bit harus kelipatan 2. Sedangkan integer 32bit harus kelipatan 4. Sehingga alamat variable a tidak boleh hexadecimal 0000A003 atau 0FA34126.

Untuk lebih jelasnya, kita ambil contoh kasus di atas. Misalnya alamat variable a, b, c dan x berturut-turut hexadecimal 10B0, 10B4, 10B8 dan 10BC. Base register GPR 12 isinya 1000. Opcode L adalah hexadecimal 58, sehingga instruksi L 0,c dalam kode mesin adalah hexadecimal 5800C0B8. Jika instruksi tersebut berada di alamat yang benar (kelipatan 2) maka instruction register (IR) akan memuat 5800C0B8 untuk di-decode lantas diexekusi. Hasilnya adalah sebuah proses exekusi instruksi L 0,c.

Tetapi jika instruksi tersebut berada di lokasi ganjil, maka IR tidak bisa mendapatkan 5800C0B8, karena cara kerja IR berdasarkan memory interleaving yang selalu kelipatan 2. .Sehingga yang didapatkan IR bisa maju atau mundur 1 byte. Artinya, IR akan berisi 00C0B8xx atau xx5800C0 yang tentunya bukan L 0,c. Hasilnya bisa operation exception karena 00 atau xx bukan opcode sebuah instruksi, atau specification exception bila ada instruksi yang opcode-nya 00 atau xx, tetapi susunan operand-nya salah.

Demikian pula dengan branching atau jumping atau goto. Instruksi branch itu sendiri tidak memasalahkan alamat yang dituju, karena dia hanya memuat data yang ditunjuk operand ke register pengendali exekusi yang dalam arsitetur Intel dinamakan instruction pointer (EIP). Di mainframe EIP merupakan bagian dari program status word (PSW). PSW di mainframe mirip gabungan EFLAGS + EIP di Intel. Bedanya, EFLAGS di Intel memuat seluruh kode pengendalian proses. Sedangkan mainframe karena jauh lebih komplex, maka meskipun PSW berukuran 128bit tidak akan mencakup. Sehingga harus didampingi dengan 16 control register (CR) yang masing-masing berukuran 64bit.

Assembler

Assembler adalah program untuk memproses program yang disusun dalam Assemnbly menjadi object codes. Mirip dengan compiler. Bedanya, proses assembling jauh lebih sederhana, hanya menterjemahkan setiap instruksi Assembly menjadi kode mesin. Tidak ada proses penataan seperti yang dilakukan compiler dimana satu instruksi bisa menjadi lebih dari satu instruksi mesin. Berikut ini garis besar tahapan-tahapan proses assembling ditayangkan dalam slides untuk memudahkan pemahaman.

Memory word dan access interleaving

Pada dasarnya, semua yang termuat dalam memori komputer adalah data dan formatnya pasti biner. Sering ada yang mengatakan bahwa format data tidak hanya biner, melainkan ada juga karakter, pointer, text dll. Semua itu salah kaprah akibat mendalami bahasa komputer tanpa mengerti arsitektur komputer. Karakter atau text adalah data biner yang match dengan tabel encoding system seperti ASCII, EBCDIC dll yang dianut oleh hardware platform tertentu untuk memvisualisasikannya. Yang membedakan data dengan data lainnya hanyalah penempatannya di memori dan panjangnya. Data yang penempatanya mengikuti mekanisme memory interleaving dibedakan atas alamat lokasi kelipatan 2, 4 dan 8.

Untuk sistem mainframe, data yang alamat lokasinya kelipatan 2 dan panjangnya 2byte disebut halfword, gunanya untuk menyimpan integer 16bit. Yang alamatnya kelipatan 4 dan panjangnya 4byte disebut fullword, gunanya untuk menyimpan integer atau floating-point 32bit. Yang alamatnya kelipatan 8 dan panjangnya 8byte disebut doubleword, gunanya untuk menyimpan integer atau floating-point 64bit Untuk floating-point 128bit cukup dengan alamat kelipatan 8 tapi panjangnya 16byte.

Intel beda lagi. Data yang alamat lokasinya kelipatan 2 dan panjangnya 2byte disebut word, gunanya untuk menyimpan integer 16bit. Yang alamatnya kelipatan 4 dan panjangnya 4byte disebut doubleword, gunanya untuk menyimpan integer atau floating-point 32bit. Yang alamatnya kelipatan 8 dan panjangnya 8byte disebut quadword, gunanya untuk menyimpan integer atau floating-point 64bit Agak membingungkan, word Intel setengah word mainframe. Hal ini disebabkan default memory interleaving pada arsitektur mainframe 4byte karena kebanyakan instruksi mainframe panjangnya 4byte. Sehingga memory word pada arsitektur mainframe ditetapkan 4byte. Sedangkan Intel defaultnya 2byte, sehingga memory word pada arsitektur Intel 2byte.

Pengetahuan tidak terlalu penting dalam programming. Karena semua ditangani compiler atau interpreter dengan baik meskipun kita menyusun struktur data sembarangan. Namun pada saat debugging, kita akan bingung karena ada data (biasanya null) disisipkan dalam struktur data kita bila kita tidak memperhatikan tatanan ini. Sebagai contoh kita lihat struktur data dalam bahasa C di bawah ini:


   struct ABC { 
     short int A ; 
     int       B ; 
     double    C ;
     };

Maunya kita struktur ABC di atas terdiri dari integer A 2byte, B 4byte dan C 8byte, sehingga total 14byte dimana offset B 2byte dan offset C 6byte.. Katakanlah isi A, B dan C masing-masing 5, 12 dan 20, maka memory dump untuk 14byte ABC akan diharapkan menampilkan 00050000000C0000000000000014h jika komputer tersebut menganut big endian. Ternyata di memori tidak selalu demikian. Jika lokasi struktur ABC di 1000h, maka yang muncul 000500000000000C000000000000h. Sehingga jika kita baca apa adanya sepertinya A = 0005h, B = 00000000h dan C = 000C000000000000 h (=3377699720527872). Hal ini dikarenakan integer B mengikuti batas pembulatan 4, sehingga B tidak mau ditempatkan persis setelah A, yaitu di 1002h, melainkan mlorot 2byte lagi ke 1004h. Sedangkan C mengikuti pembulatan 8 dan kebetulan terpenuhi persis setelah B, yaitu di 1008h. Sehingga total memori untuk ABC bukan 14byte, melainkan 16byte dan isinya 000500000000000C0000000000000014h. Lokasi A di1000h isinya 0005h, lokasi B di 1004h isinya 0000000Ch (=12) dan lokasi C di 1008h isinya 0000000000000014h (=20).

Namun demikian, jika lokasi struktur ABC di 2002h, memory dump akan menampilkan 00050000000C0000000000000014h sesuai yang diharapkan. Lokasi A di2002h isinya 0005h, lokasi B di 2004h isinya 0000000Ch (=12) dan lokasi C di 2008h isinya 0000000000000014h (=20). Sehingga offset B 2byte dan offset C 6byte seperti yang diharapkan. Sepertinya tidak pasti yak? Tanpa memahami betul soal memory alignment, kita bisa konyol.

Pengetahuan ini seharusnya mengawal kita untuk lebih waspada dalam menyusun struktur data agar tidak terjadi kekonyolan seperti kasus ABC di atas. Jika cara menyusunnya dibalik yang besar di dahulukan, maka kekonyolan tidak akan terjadi. Misalnya ABC saya balik menjadi CBA seperti di bawah ini:


   struct CBA { 
      double    C ;
      int       B ; 
      short int A ; 
     };

Untuk isi A, B dan C yang sama, panjang CBA akan selalu 14byte dan dump-nya akan selalu 00000000000000140000000C0005h. Karena compiler akan selalu menempatkannya di lokasi yang sesuai dengan pembulatan batas field pertama, yaitu C.

Definisi dan struktur data

Assembler kadang tidak secanggih compiler yang setidaknya pintar menempatkan struktur data sesuai dengan pembulatan batas field awal. Kita perlu lebih hati-hati. Seperti telah dijelaskan di atas bahwa semua data adalah biner, sama juga program. Yang membedakan adalah relatif penempatannya terhadap memory word untuk kepentingan mekanisme proses. Berikut ini perintah-perintah Assembly Intel-32 untuk mendefiniskan data:

  • BYTE dan SBTYE — 8bit integer tak bertande dan bertanda
  • WORD dan SWORD — 16bit integer tak bertande dan bertanda dan lokasinya kelpatan 2
  • DWORD dan SDWORD — 32bit integer tak bertande dan bertanda dan lokasinya kelipatan 4
  • QWORD — 64bit integer dan lokasinya kelipatan 8
  • TBYTE — 80bit integer
  • REAL4 — 32bit real
  • REAL8 — 64bit real
  • REAL10 — 80bit real

Seksi data diawali dengan label .DATA. Dengan demikian, contoh data dalam struktur data CBA di atas dalam Assembly Intel ditulis sebagai berikut:


    .data  
    C  qword  ?
    B  dword  ? 
    A  word   ?

Untuk memberikan nilai awal A, B dan C di atas dengan 5, 12 dan 20, cukup dengan menggantikan tanda ? dengan nilai-nilai awal tersebut:


    .data  
    C  qword 20
    B  dword 12 
    A  word   5

Untuk membangun struktur persis seperti CBA dalam Assembly Intel dinyatakan sbb:


    cba STRUCT
       C qword ?
       B dword ? 
       A word  ?
    cba ENDS

Berikut ini struktur data dalam C


   struct personalia { 
      char nama[20];
      int  nopeg;  
      char kelamin; 
      char statnikah;
      char pangkat;
      char karir; 
      int  gajipokok;
      int  tunjangan; 
     };

Struktur di atas dalam Assembly Intel dinyatakan sbb:


   personalia STRUCT
      nama      byte  20 DUP(?)
      nopeg     dword ? 
      kelamin   byte  ?
      statnikah byte  ?
      pangkat   byte  ?
      karir     byte  ?
      gajipokok dword ?
      tunjangan dword ?
   personalia ENDS

DUP () (pada variable nama) adalah untuk menyatakan pengulangan. Sedangkan argumen DUP adalah nilai awal. BYTE 20 DUP (?) artinya data jenis BYTE diulang 20 kali, masing-masing tidak ada nilai awal. .
Seperti halnya dalam bahasa komputer lainnya, struktur adalah peta data. Sebagai contoh, struktur personalia di atas digunakan untuk memetakan variable pemimpin dan wakil dalam segmen data sbb:


  .data
   kodebagian   word ?
   kodeunit     word ? 
   pemimpin     personalia <>
   wakil        personalia 2 DUP (<>)

Panjang variable pemimpin dan wakil masing-masing sama dengan panjang struktur personalia, 36byte. Tentu saja soal memory word harus benar-benar diperhatikan. DUP pada variable wakil menyatakan untuk diulang 2 kali dan argumen <> menyatakan nilai awal sesuai dengan yang ada di definisi struktur. Untuk menyatakan porsi atau member variable yang dipetakan dengan struktur adalah dengan mengkualifikasi nama varibale dan nama member struktur, seperti contoh di bawah ini:


      mov eax, pemimpin.nopeg

pemimpin.nopeg adalah member nopek yang ada di variabel pemimpin yang terpetakan dalam struktur personalia. Dan memang yang dimaksudkan menyalin nopeg-nya pemimpin ke register EAX. .

Lain lagi dengan Assembly mainframe. Word mainframe adalah 4byte dan lokasinya juga harus kelipatan 4 setara dengan doubleword Intel. Jenis data di Assembly mainframe cukup banyak antara lain yang dipaparkan di bawah. Catatan: Halfword berarti panjang 16bit dan lokasinya kelipatan 2, fullword berarti panjang 32bit dan lokasinya kelipatan 4, doubleword berarti panjang 64bit dan lokasinya kelipatan 8, dan quadword berarti panjang 128bit dan lokasinya kelipatan 16

  • A — fullword address atau pointer
  • AD — doubleword address atau pointer
  • V — fullword relocatable address
  • Y — halfword address atau pointer
  • H — halfword untuk integer bertande
  • F — fullword untuk integer bertande
  • FD — doubleword untuk integer bertanda
  • C — byte untuk karakter dan panjangnya bebas
  • B — byte untuk biner dan panjangnya bebas
  • P — byte decinal packed dan panjangnya bebas
  • X — byte untuk hexadecimal dan panjangnya bebas
  • dll termasuk beragam floating point dari fullword hingga quadword.

Menyatakannya dengan DS untuk hanya memesan kapling memori, atau DC untuk memesan kapling memori dan memberi nilai awal. Definisi variable dalam struktur data CBA di atas ditulis sebagai berikut:


      C  ds fd
      B  ds f 
      A  dc h

Soal penyusunan struktur, Assembly mainframe kalah canggih dengan Intel. Tentu saja karena Assembly Intel disini adalah berbasis Microsoft Assembler, bukan aslinya buatan Intel. Bahkan ada beberapa vendor lain yang ikut-ikutan membuat Assembler untuk Intel, termasuk kelompok GNU. Masing-masing menawarkan kelebihannya dalam memudahkan pemakai.

Sedangkan Assembly mainframe, satu-satunya assembler yang ada hanya buatan IBM. Tentu saja sangat baku. Untuk menyusun struktur, kita menggunakan dummy section (DSECT). Sehingga untuk membangun struktur persis CBA di atas, ditulis sbb:


      cba DSECT
      C  ds fd
      B  ds f 
      A  ds h

Operator DS dan DSECT tidak bisa untuk memberi nilai awal. Kita harus gunakan DC dan tidak boleh berada di wilayah DSECT, seperti contoh di bawah ini.


      C  dc fd'20'
      B  dc f'12'
      A  dc h'5'

Tidak seperti Assembly Intel dimana data harus berada di segmen terpisah dari program, pada meinframe data dan program bisa nyampur dalam satu seksi. Yang penting mengatur branching-nya agar EIP tidak kebablasan menuju wilayah data. Yang harus terpisah adalah seksi dummy (DSECT), karena apapun yang berada di wilayah DSECT dianggap tidak ada.

Untuk memetakan data ke dalam struktur tertentu, harus dikendalikan dengan perintah Assembler USING. Untuk menjelaskannya, kita coba membuat susunan dan struktur data personalia seperti apa yang sudah kita bikin pada Intel di atas. Mula-mula disusun struktur personalia menggunakan DSECT sebagai berikut.


   personalia DSECT
   nama       ds  cl20
   nopeg      ds  f 
   kelamin    ds  c
   statnikah  ds  c
   pangkat    ds  c
   karir      ds  c
   gajipokok  ds  f
   tunjangan  ds  f

Data yang akan dipetakan dengan DSECT personalia harus disusun lengkap sebagai berikut:


   kodebagian ds  h
   kodeunit   ds  h 
   pemimpin   ds  cl36
   wakil      ds  2cl36

Pemetaan tidak langsung seperti pada Intel. Pemetaan dilakukan dalam wilayah program menggunakan perintah Assembler USING sebagai berikut


   boss   using personalia,1
   vice   using personalia,2 
          la    1,pemimpin
          la    2,wakil     

He he kampungan memang!!! USING yang pertama berlabel boss dan menggunakan GRP 1 sebagai base register. USING berikutnya berlabel vice dan menggunakan GPR 2 sebagai base register. Rupanya USING saja tidak cukup karena struktur personalia belum terkait dengan data yang akan dipetakan. Maka lokasi variable pemimpin harus ditunjuk oleh GPR 1 dan wakil haris ditunjuk oleh GPR 2 sebagai masing-masing base-nya. Nah, anehnya lagi untuk menunjuk member struktur tertentu pada data yang dipetakan, kualifikasinya bukan berdasarkan nama variable tersebut, tetapi nama label USING-nya. Misalnya, untuk menunjuk nopeg pemimpin bukan pemimpin.nopeg seperti pada Intel, tetap boss.nopeg. Untuk menyalin nopeg nya pemimpin ke GPR 5 ditulis sbb:


          L   5,boss.nopeg

Memori dan Register

Bermain Assembly berarti kontak langsung dengan hardware. Jadi mau nggak mau kita harus mengerti onderdil hardware, terutama yang menjadi sarana programming yaitu memori dan regiister. Ibarat orang makan, memori itu piringnya dan register itu sendok dan garpunya. Obyek yang kita cermati masih Intel dan mainframe.

Baik memori maupun register, sebagian digunakan untuk kepentingan logik program dan sebagian untuk kepentingan kontrol atau pengendalian hardware. Untuk logik program, Intel-32 menyediakan 2 macam memori dan 8 register umum. Memori dibedakan antara memori biasa dan stack. Memori biasa ya biasa lah … yang alamat lokasi dan isinya byte demi byte bisa kita manipulasi langsung dengan program. Sedangkan stack adalah memori yang sudah dibangun semacam umpuk. Secara awam kita tidak bisa memanipulasi langsung. Kita harus belet (PUSH) data untuk mengisi stack dari memori dan betot (POP) data untuk menyallin isi stack ke memori. Manipulasinya dilakukan di luar stack.

Register umum yang masing-masing berukuran 32bit, yaitu

  • EAX — accumulator
  • EBX — base register
  • ECX — counter
  • EDX — displacement
  • EBP — frame pointer
  • ESP — stack pointer
  • EDI — data index
  • ESI — segment index.

EAX, EBX, ECX dan EDX bisa dipakai sepotong-sepotong. Potongan 16bit paroh bawah mereka masing-masing AX, BX, CX dan DX. Masing-masing masih bisa dipotong lagi menjadi potongan perempat atas (H) dan perempat bawah (L). AX menjadi AH dan AL, BX menjadi BH dan BL CX menjadi CH dan CL, dan DX menjadi DH dan DL. Pemakaian E-X, -X, -H dan -L disesuaikan dengan keperitingannya.

Berikut ini contoh menghitung faktorial. x! = x (x -1) (x – 2) … 1. Untuk perkalian kita gunakan instruksi MUL. Yang dikalikan dan hasil kali selalu dalam register EAX atau AX tergantung ukurannya. Pengali kita gunakan counter ECX. Oleh karena itu EAX kita inisialisasi dengan argumen (x) dan ECX dengan x-1. Hasil akhir (x!) kita simpan di variable fact.


           mov  EAX, argumen 
           mov  ECX, EAX 
           dec  ECX
 iterate:  mul  ECX  
           loop iterate
           mov  fact, EAX
           ... 

Mula-mula argumen x diisikan ke akumulator EAX dengan instruksi MOV EAX,argumen. Lantas MOV ECX,EAX untuk menyalin EAX ke counter ECX. Instruksi DEC ECX sehingga x menjadi x-1. Selanjutnya MUL ECX untuk mengkalikan x di EAX dengan x-1 di ECX. Instruksi LOOP interate artinya kurangi x dengan 1 (dalam C x–) dan belok ke lokasi berlabel iterate selama x > 0. Setelah x = 0, lantas MOV fact,EAX untuk menyimpan hasil x! ke varibale fact.
.
Selain register umum, Intel juga menyediakan 6 segment register masing-masing berukuran 16bit sbb:

  • CS — code segment
  • DS — data segment
  • SS — stack segment
  • ES, FS dan GS — untuk segmen lain

Untuk arsitektur mainframe, register umum atau GPR yang benar-benar bebas.ada 16 biji, 0, 1 s/d 15. Namun ada register umum pendamping untuk addressing 3 dimensi yang dinamakan access register (AR), jumlahnya juga 16 biji. Semua GPR berukuran masing-mainsg 64bit dan semua AR masing-mainsg.32bit. Untuk perhitungan faktorial seperti pada contoh di atas, dapat dilakukan sebagai berikut:


           la   2,argumen 
           lr   3,2 
           bctr 3,0
 iterate   msr  2,3   
           bct  3,iterate
           st   2,fact
           ... 

GPR 2 berperan sebagai EAX dan GPR 3 sebagai ECX.

Memori dan Register Kontrol

Memori yang digunakan untuk kontrol ada 2 macam. Yang pertama adalah yang ditugaskan langsung oleh hardware, biasanya berada di low-real-address atau lokasi awal. Sehingga berapapun ukuran memorinya, kapling ini akan selalu ada. Fungsi utamanya untuk mengatus seluruh mekanisme interupsi. Dalam terminologi mainframe, kapling ini dinamakan prefix storage area (PSA) Dalam terminologi Intel dinamakan interrupt descriptor table (IDT) dan setiap entry-nya dinamakan interrupt vector. Kaplling ini diinisialisasi oleh loader yang dijalankan melalui proses bootstrap. Memang tergantung disain OS-nya, namun pada umumnya loader tersebut sekaligus mengangkut OS ke memori dan menyerahkan kontrol sepenuhnya kepada OS setelah loading selesai. Pada saat boot, PSA atau IDT diisi dengan pointerpointer dan catatan status milik loader sesuai keperluan pengendalian sepanjang proses bootstrap. Setelah bootstrap selesai, pointerpointer tersebut dibelokkan ke rutin-rutin first-level interrupt handler (FLIH) milik OS dan catatan statusnya juga biasanya langsung di gunakan oleh OS.

Tapi ada juga loader yang memberi kesempatan kita untuk memilih nucleus atau kernel OS yang mana yang akan diaktifkan. Loader yang berdisri sendiri semacam ini dinamakan standalone program loader (SAPL). SAPL harus memilik ragami FLIH yang mirip OS. Ketika bootstrap selesai, PSA atau IDT masih dikuasai SAPL dan SAPL tampil mirip OS sangat sederhana tetapi sudah memiliki console untuk menampilkan beberapa nucleus atau kernel OS yang ada dan bisa kita pilih.

Yang kedua adalah kaplilng memori wilayah kontrol yang ditugaskan oleh OS. Kapling memori ini dipilih dan diinisialisasi oleh OS ketika pertama kali OS diaktifkan. Kapling ini biasanya digunakan untuk mengendalikan virtualisasi memori dan .

Register juga sebagian untuk keperluan logik program dan sebagian untuk kontrol, Register yang digunakan untuk kontrol biasanya dibagi dalam 2 kelompok, yaitu kontrol exekusi program dan kontrol aktivitas hardware. Kelompok kontrol exekusi program terdiri dari bagian kontrol dan catatan status dan bagian kontrol navigasi. Ibarat mobil, bagian kontrol/catatan status mirip panel-panel dashboard dan kabin, untuk mengatur AC, melihat kecepatan, mengatur kecepatan dll. Bagian ini di Intel dinamakan EFLAGS. Sedangkan bagian kontrol navigasi atau petunjuk exekusi intruksi, ibarat mobil adalah setir. Di Intel dinamakan execution instruction pointer (EIP). Di mainframe EFLAGS dan EIP berada dalam satu register berukuran 128bit yang dinamakan program status word (PSW).

Di mainframe, mengemudikan operasi dilakukan dengan memanipulasi PSW. Disediakan instruksi EPSW, LPSW dan LPSWE. EPSW untuk menyalin EFLAGS (pinjam istilah Intel) ke 2 GPR sekaligus. Sedangkan LPSW atau LPSWE untuk menyalin isi memori ke PSW dan langsung efektif. LPSW atau LPSWE hanya bisa diexekusi dalam privileged mode. Mengexekusi LPSW atau LPSWE adalah merampas kewenangan OS, makanya jangan lakukan kecuali kita tahu benar mekanismenya dan bertanggungjawab mengembalikan wewenang itu kepada OS. Dalam Assembly mainframe caranya sebagai berikut:


          EPSW   1,2
          ... 
          < manipulasi isi GPR 1 dan 2 > 
          ...
          stm    1,2,kendalikita  
          ...
          < siapkan bagian EIP nya >
          ...
          LPSW   kendalikita

Mula-mula salin PSW ke memori. Lantas dimanipulasi bit-bit kendali di bagian EFLAGS-nya dan load hasil manipulasi ke PSW. Bagian EIP di kendalikita menunjuk ke rutin program kita yang harus sudah loaded di memori. Rutin tersebut harus bertanggungjawab mengembalikan PSW ke OS. OS pun cara melakukannya ya seperti itu.

Di Intel, EFLAGS dan EIP berada di register terpisah. Menyalin EFLAGS ke memori dan sebaliknya hanya bisa dilakukan melalui stack. Untuk menyalin EFLAGS ke stack, disediakan instruksi PUSHF, PUSHFD dan PUSHFQ, masing-masing untuk 16bit, 32bit dan 64bit EFLAGS. Sebaliknya, untuk menyalin stack ke EFLAGS disediakan instruksi POPF, POPFD dan POPFQ. Sedangkan untuk merubah EIP bisa dilakukan dengan instruksi branching (JMP) atau simulasi interrupsi.

Terus terang saya belum pernah melakukan sejauh itu dengan Intel. Maklum biasa ngunyek-unyek dinosaur ngelihat kambing kurang gairah ha ha ha 🙂 Maaf becanda. Alasan sebenarnya ya karena belum melihat tantangan aja. Namun bisa dipastikan kurang lebih caranya sebagai berikut:


          PUSHFD
          POP   kendalikita
          ... 
          < manipulasi isi kendalikita > 
          ...
          PUSH  kendalikita  
          POPFD   

Perbedaan yang sangat mencolok antara Intel dan mainframe terutama adalah di seputar EFLAGS vs PSW. Keduanya adalah bagian sensitif dalam CPU. PSW di mainframe tidak bisa dimanipulasi kecuali program running dalam privileged node. Program akan dihentikan tidak normal (abend). Sedangkan EFLAHS di Intel bisa dimanipulasi oleh sembarang program. Bedanya, jika program tidak running dalam privileged node, program tidak dihentikan. Tetapi hanya bagian yang tidak sensitif yang berubah. Yang sensitif tidak ikut berubah.. Bagian yang paling sensitif EFLAGS system flags dan I/O privilege level (IOPL).
.
Kelompok aktivitas hardware dinamakan control register (CR). Intel dan mainframe sama saja. Bedanya, Intel hanya memiliki 8 CR yang dalam Assembly dikodekan sebagai CR0, CR1, s/d CR4 dan task register (TR), IDT register (IDTR) dan global descriptor table register (GDTR), masing-masing berukuran 32bit. Sedangkan mainframe memiliki 16 CR, masing-masing berukuran 64bit dan tidak dikodekan dalam Assembly, sehingga tetap menggunakan alamatnya 0, 1 s.d 15.

Pada arsitektur mainframe, fisik memori tersusun dalam beberapa segmen fisik yang real address-nya mulai dari 0.. Artinya, setiap segmen memiliki PSA sendiri-sendiri yang bisa dipakai untuk mengatur mekanisme interupsi. Arsitektur menyediakan prefix register (PFX) yang berfungsi untuk mengkoreksi setiap real address 0 di setiap segmen fisik dan mengurutkan relatif fisik tersebut dengan absolute addressing linier mulai dari segmen yang paling awal hingga yang terakhir. Ketika interupsi muncul, semua PSA mendapat sinyal. Tetapi FLIH yang merespon hanya FLIH yang dicantol interrupt vector pada PSA yang sedang ditunjuk oleh PFX. FLIH PSA lainnya menunggu giliran PFX. Teknologi inilah yang digunakan untuk menyelenggarakan logical partition (LPAR) maupun virtual machine (VM). Karuan saja virtualisasi di mainframe nyaris tanpa overhead 🙂

Sebenarnya saya sangat doyan ngalor-ngidul ngomongin bagian kontrol. Bukan hanya karena tahu, tetapi memang bekerja di bidang itu. Sebagai pembuat automation tools, zJOS-XDI, tentu kontrol merupakan makanan sehari-hari. Tetapi saya yakin kalo kepanjangan membahas kontrol tidak lagi sesuai dengan judulnya yang hanya mengenalkan Assembly.

Aneka proses sederhana

Berikut ini proses-proses sederhana yang mungkin sering kita jumpai sehari-hari, antara lain pembandingan, loop atau iterasi dan aritmetika.

Pembandingan

Membandingkan x dan y dan keduanya integer 16bit tak bertanda.


 if (x > y) {   
   x = 1;
   y = 0;
   }
 else { 
   x = 0;
   y = 1;
   }

Dalam Assembly Intel, pembandingan menggunakan instruksi CMP. Salah satu atau kedua operand harus dalam register

 
         mov  AX,x              ; set reg AX = x
         cmp  AX,y              ; if (AX > y)
         ja   lebihbesar        ;    goto lebihbesar
         mov  AX,0              ; set AX = 0
         mov  x,AX              ; x = AX
         inc  AX                ; AX++
         mov  y,AX              ; y = AX
         jmp  beres             ; goto beres
  lebihbesar:
         mov  AX,1              ; set AX = 1
         mov  x,AX              ; x = AX
         dec  AX                ; AX--
         mov  y,AX              ; y = AX
  beres:
         ...

Mainframe memiliki beberapa instruksi, antara lain CLC, CL, C, CH, CP dll tergantung apa yang akan diperbandingkan. Operand ada yang boleh dalam register maupun memori, baik dua-duanya maupun salah satu. Tapi CLC tidak berlaku untuk integer bertanda, sehingga tidak boleh dua-duanya dalam memori. Berikut ini contoh pembandingan dengan CLC dimana dua-duanya dalam memori.

 
         clc  x,y               * if (x > y) 
         bh   lebihbesar        *   goto lebihbesar; 
         xc   x,x               * x = 0;
         mvc  y,=h'1'           * y = 1;
         b    beres             * goto beres;
  lebihbesar equ *
         mvc  x,=h'1'           * x = 1;
         xc   y,y               * y = 0
  beres  equ *
         ...

Looping

Looping hitung mundur.


    int counter = 100   
   loop: counter--;
      ...;
      ...;
      if (counter > 0)  
         goto loop;
      ...   

Dalam Assembly Intel,

  
         mov  CX,100            ; set reg CX = 100 (counter) 
   loop: ...
         ...
         loop loop              ; CX-- ;  if (CX > 0) goto loop; 
         ...

Dalam Assembly mainframe,

 
         la   5,100             * set GPR5 = 100 (counter)
   loop  equ  *
         ...
         ...
         bct  5,loop            * GPR5-- ; if (GPR5 > 0) goto loop;
         ...

Looping hitung mundur berkondisi.


    int counter = 100   
    int x = 0
    while (counter > 0) {
       ...;
      x++
      if (x == counter)  
         break;
      counter--
     }
      ...   

Dalam Assembly Intel,

 
         mov  CX,100        ; set counter reg = 100
         mov  AX,0          ; set accum reg = 0 
   loop: ...
         ...
         inc  AX            ; AX++
         cmp  AX,CX         ; if (AX == CX) 
         je   break         ;    goto break;
         loop loop          ; CX--; if (CX > 0) goto loop
   break: 
         ...

Dalam Assembly mainframe,

 
         la   5,100         * set GPR5 = 100 (sebagai coounter) 
         la   2,0           * set GPR2 = 0   (sebagai x)
   loop  equ  *
         ...
         la   2,1(2)        * GPR2 ++
         cr   2,5           * if (GRP2 == GPR5)
         be   break         *   goto break
         bct  5,loop        * GPR5--; if (GPR5 > 0) goto loop)
  break  equ *
         ...

Manfaat Assembly

Saking primitif-nya, program yang sederhana bisa menjadi runyam jika ditulis dalam Assembly. Orang-orang yang bombas bahkan mengatakan kita mundur lagi kalo seneng berkutet dengan Assembly. Memang sekilas demikian. Jika kita bikin payroll dengan Assembly memang lama-lama kita rugi waktu. Tapi mempelajari sesuatu pengetahuan atau ketrampilan tidak ada rumusnya mundur. Jika pengetahuan dan ketrampilan kempo kita pelajari dengan benar, tentu tidak mungkin kita menggunakan uwa uke tzuki ketika ada anak balita meraih kacamata yang sedang kita pakai. Demikian pula jika pengetahuan dan ketrampilan Assembly kita pelajari dengan benar, tentu tidak mungkin kita bikin payroll dengan Assembly.

Assembly sangat menuntun kita mempelajari arsitektur komputer dan internal OS. Belajar arsitektur komputer tanpa pengetahuan dan praktek ketrampilan Assembly, sama seperti membahas akhirat dalam pelajaran agama. Belajar OS tanpa ketrampilan Assembly juga tidak lebih hanya operator + sysadmin . Kenapa demikian? Karena ilmu komputer adalah ilmu teknik yang tidak bisa diproyeksikan dalam gambar. Gambar-gambar diagram logika dalam ilmu komputer semuanya hanya ilustrasi yang belum tentu bisa dipahami tanpa bimbingan. Bahkan meskipun ada bimbingan dan ilustrasinya didukung dengan animasi pun, belum tentu memberi gambaran yang benar jika tidak dipraktekkan. Padahal Assembly satu-satunya cara untuk mempraktekannya.

Assembly vs C/C++

C/C++ adalah bahasa komputer yang cukup prowerful. Bini program sistem bisa, aplikasi juga bisa. Tapi tanpa ketrampilan Assembly, kita sulit membayangkan kerja mesin hanya dengan logika C/C++. Sebagai HLL, bermain C/C++ tentu tidak menyentuh register apalagi register kontrol. Urusan hardware tersembunyi di balik pernyataan dan expresi C/C++.. .

Misalnya, ketika kita ingin mempelajari OS yang non-opensouorce, tidak ada secuilpun sourcecodes yang bisa kita dapatkan selain exit routine sample. Buku-buku panduan juga tidak mungkin sampai membahas jeroan, kecuali ada relawan yang mau menuliskan hasil risetnya. Apa yang bisa kita pelajari selain menghafal seabreg command dan prosedur? Apalagi OS full GUI seperti Windows. Hanya prosedur yang bisa kita pelajari. Lantas… ketrampilan apa yang bisa kita harapkan selain operator dan sysadmin? Lantas apa yang harus kita lakukan untuk mempelajari jeroannya?

Yang musti kita lakukan adalah riset berbekal ketrampilan sysadmin dan teori OS dan arsitektur komputer. Bagaimana risetnya? Nah ini tergantung gaya masing-masing individu sesuai dengan bekalnya. Saya merasa tidak cukup bekal, maka yang saya lakukan mula-mula membuat tiruan komponen OS yang pasif, misalnya utilitas atau command copy, delete dsb, tapi murni dengan logik saya sendiri. Artinya, proses read, write dsb saya bikin sendiri, tidak memanfaatkan header dan macro milik OS, apalagi modul mateng yang di kernel OS. Melainkan langsung berinteraksi dengan silinder dan track disk. Nah yang ginian akan sangat sulit jika kita bikin dengan C/C++. Terlebih pasti akan ketemu mentok karena interupsinya tidak dikirim ke program kita. Masih pusing lagi bagaimana agar interupsinya bisa diterima program kita. Nah disini saatnya kita hack OS yaitu membelokkan interrupt vector ke program kita. Tentu harus tahu prosedur bagaimana memberi privilege program kita. Sampai disini ternyata belum juga sukses. I/O memang bisa kita tangkap hasilnya. Tetapi program lain yang melakukan I/O jadi bengiong. Selang berapa langkah program kita juga bengong. Ternyata OS juga bengong. Kenapa? Oh.. ternyata kita harus mengembalikan interrupt vector OS secepatnya. Dari sini kita dapat hikmah bagaimana menyusun interrupt handler dalam 2 tingkat, first level interrupt handler (FLIH) yang jalan dengan disable mode dan second level interrupt handler (SLIH) yang jalan dengan enable mode. FLIH harus secepat mungkin agar tidak keburu ada interupsi lain yang tertahan.

Setelah beberapa komponen pasif kita bedah, lantas mencoba bikin standalone program (SA), artinya program yang jalan tanpa OS. Sesederhana apapun, SA pasti melibatkan bootstrap processing dan interrupt handling yang langkap meskipun aksinya hanya dummy process. Kali ini mungkin sudah bisa bagi-bagi mana yang harus Assembly dan mana yang bisa digantikan dengan C/C++. Namun saya memilih total Assembly karena selain sudah lebih trampil dengan Assembly, juga pada saat men-debug yang muncul kan mnemonic Assembly dan instruksi dalam notasi hexadesimal.

Setelah SA berhasil baru masuk lagi ke OS untuk membedah komponen-komponen yang aktif dan sensitif, misalnya event traffic, security path dll. Berdasarkan pengalaman menyusun komunikasi antar fungsi-fungsi dalam SA, kita mencoba mencegat lalulintas event di OS. Kembali jatuh bangun lagi dan bolak-balik debug. Kuncinya jangan mudah frustrasi. . Toh akhirnya berhasil juga 🙂 Nah .. semuan ini hemat saya sangat sulit dicerna jika hanya ngoceh teori tanpa mengalami sendiri. Dan Assembly terasa sangat menolong.

Semoga tulisan ini membantu memberi gairah pada para pembaca yang ingin dan sedang mempelajari Assembly dan arsitektur komputer. Tulisan ini hanyalah pancilngan. Kita perlu mencari buku-buku dan peralatan pendukung yang cukup untuk memulainya.

BERSAMBUNG

Topik-topik terkait

  1. Memanfaatkan kesalahan dalam Assembly
  2. Systems programming
  3. Systems programming #2 (lanjutan)
mm
Deru Sudibyo
deru.sudibyo@gmail.com
6 Comments

Post A Reply to Damar Pramudito Nurjati Cancel Reply