bahasa c dan assembly

Click here to load reader

Post on 28-Jun-2015

353 views

Category:

Documents

5 download

Embed Size (px)

TRANSCRIPT

Tutorial Pemrograman Bahasa C dan Assembly

Tutorial Pemrograman Bahasa C dan AssemblyDaftar Isi

Pengetahuan Dasar Pemrograman Assembly dan C Pemrograman Bahasa C o Alasan Penggunaan Bahasa C o Pengenalan Sintaks Bahasa C

o

Lebih Lanjut Tentang Fungsi Arsitektur Driver Windows 2000/XP

Preprocessor dan Macro Compiler Linker Pernyataan Variabel Operator Percabangan Perulangan Fungsi dan Struktur Program C Fungsi untuk Input dan Output Pointer dan Array Struct dan Union

Pemrograman Bahasa Assembly o Arsitektur Microprocessor x86

o o

Arsitektur x86 Modern Sintaks Bahasa Assembly x86 Program Assembly Sederhana

Arsitektur Dasar Mode Kerja Microprocessor x86

Penggunaan Assembly dan C dalam satu Program Beberapa Sumber Informasi Credits

1

Tutorial Pemrograman Bahasa C dan Assembly

Artikel ini pada dasarnya adalah follow up dari artikel Tutorial Membuat Patch dalam Bentuk Driver pada Windows 2000/XP. Sebagian source code yang akan dipelajari pada tulisan ini adalah source code dari artikel tersebut. Tujuan pembuatan tutorial ini adalah 'Bringing System Level Programming to the Mass'. Tutorial ini sendiri akan terbagi menjadi dua bagian besar, yaitu pemrogaman bahasa C dan pemrograman bahasa assembly, namun pada bagian akhir akan kita bahas penggunaan bahasa assembly yang terintegrasi dalam sebuah source code bahasa C, yang lazim disebut inline assembly. Penulis mengikutsertakan bagian yang agak "advanced" ini karena pada source code Tutorial Membuat Patch dalam bentuk Driver pada Windows 2000/XP teknik ini dimanfaatkan. Perlu anda ketahui bahwa bahasa assembly yang akan dibahas pada tulisan ini adalah bahasa assembly untuk arsitektur hardware x86(PC) dan tidak dapat diaplikasikan pada arsitektur hardware lainnya. Petunjuk: Bagi yang sudah pernah menggunakan bahasa C sebelumnya, penulis menyarankan untuk langsung membaca bagian Pointer dan Array , kemudian bagian Struct dan Union dan dilanjutkan sampai bagian akhir Pemrograman C.

2

Tutorial Pemrograman Bahasa C dan Assembly

Pengetahuan Dasar Pemrograman C dan AssemblyBagian ini diperuntukkan bagi pembaca yang masih belum mengetahui pemrograman sama sekali atau yang sudah tahu tetapi ingin menyegarkan kembali ingatannya. Kita akan memulai dengan sistem bilangan yang digunakan pada komputer. Komputer hanya mengenal nilai 0 dan 1, oleh karena itu digunakanlah beberapa sistem bilangan untuk mempermudah. Sistem bilangan tersebut antara lain:

1.

Bilangan Biner (binary), yaitu bilangan basis 2, sehingga nilai yang dikenal hanya 0 dan 1. Contoh : 101biner bernilai sama dengan 5desimal, sebab 101biner = 1 x 20 + 0 x 21 + 1 x 22 atau 5desimal. Operasi matematis lainnya sama dengan bilangan desimal, hanya di sini bilangan yang digunakan adalah basis 2. Bilangan Oktal (octal), yaitu bilangan basis 8, sehingga nilai yang dikenal hanya 0 s/d 7. Contoh : 072octal (prefix 0 digunakan pada bahasa pemrograman C) bernilai sama dengan 58desimal, sebab 072octal = 2 x 80 + 7 x 81 atau 58desimal. Operasi matematis lainnya sama dengan bilangan desimal, hanya di sini bilangan yang digunakan adalah basis 8. Bilangan Heksadesimal (Hexadecimal), yaitu bilangan basis 16, sehingga nilai yang dikenal hanya 0 s/d 9 dan huruf A s/d F melambangkan 10desimal s/d 15 desimal. Contoh : 0x72hexadecimal (prefix 0x digunakan pada bahasa pemrograman C) bernilai sama dengan 114desimal, sebab 0x72hexadecimal = 2 x 160 + 7 x 161 atau 114desimal. Operasi matematis lainnya sama dengan bilangan desimal, hanya di sini bilangan yang digunakan adalah basis 16. Ada satu hal yang perlu anda perhatikan, yaitu konversi dari bilangan biner ke hexadecimal adalah suatu operasi yang "agak natural", sebab anda tinggal memecah bilangan hexadecimal tersebut menjadi elemen-elemennya kemudian setiap elemen direpresentasikan dengan 4 bilangan biner, maka anda telah memperoleh bilangan biner yang bernilai sama dengan bilangan hexadecimal tersebut. Contoh: 0xA2 = ... biner, solusi: pertama pecah menjadi elemennya , kita peroleh A dan 2. A jika direpresentasikan dalam 4 angka biner adalah 1010 (10desimal) dan 2 jika direpresentasikan dalam 4 angka biner adalah 0010 sehingga kita peroleh 0xA2 = 1010 0010 biner. Kemudahan operasi ini akan membantu anda saat berurusan dengan pemrograman yang mengolah informasi bilangan biner, jadi sangat perlu untuk dipahami.

2.

3.

Pemrograman atau programming secara umum dilakukan untuk membuat sekumpulan instruksi yang dapat dieksekusi (dijalankan) pada komputer. Jadi,

3

Tutorial Pemrograman Bahasa C dan Assembly

instruksi-instruksi yang dapat dijalankan (executable) tersebut merupakan hasil akhir yang kita inginkan. Kumpulan instruksi-instruksi itulah yang disebut software. Instruksi yang dihasilkan biasanya hanya dapat dieksekusi pada satu arsitektur komputer. Instruksi yang dimaksud adalah "machine code" atau "bahasa mesin", bahasa mesin ini tidak lebih dari kumpulan bit-bit 0 dan 1 yang dapat dipahami oleh sebuah komputer. Perbedaan satu arsitektur komputer (misalnya x861) dengan arsitektur lain (misalnya Sparc2) adalah bagaimana bitbit tersebut diorganisasikan, hal inilah yang menyebabkan machine code untuk satu macam arsitektur tidak dapat dieksekusi pada arsitektur yang lain. Prosesnya kira-kira seperti ini:

Pembuatan Machine Code --> Machine Code --> Eksekusi pada KomputerCatatan: 1 x86 adalah keluarga microprocessor yang digunakan oleh para pemakai PC, yang termasuk ke dalam keluarga ini antara lain: Intel 80286, 80386, 80486, Pentium (i586), PentiumPro (i686),Pentium 4 (i786); AMD K6, K6-2, K6-3, Athlon (K7), Duron; Via Cyrix III; Transmeta Crusoe, dan lain-lain. 2 Sparc adalah keluarga microprocessor yang digunakan pada Serverserver Sun Microsystem, yang termasuk ke dalamnya antara lain: UltraSparc II, IIIi, III. Teknik pemrograman merupakan teknik yang digunakan untuk menghasilkan kumpulan machine code tadi. Ketika komputer digital pertama kali muncul (komputer ENIAC), untuk membuat program, orang harus langsung memasukkan bit-bit machine code tadi ke dalam komputer melalui pengaturan saklar-saklar dan punch cards (kartu yang dilubangi). Perkembangan selanjutnya adalah orang tidak perlu lagi pusing dengan bit-bit program yang sangat mudah salah (sebab anda langsung bekerja dengan angka 0 dan 1 dalam jumlah yang sangat besar), muncul lah apa yang disebut assembler, yaitu program yang dapat mengubah token-token (potongan kata-kata tertentu yang dapat dipahami oleh assembler) sederhana menjadi machine code. Karena adanya assembler, orang mulai mengenal apa yang dinamakan bahasa assembly, yaitu bahasa yang menggunakan token-token yang dapat dikenali oleh assembler, jadi bahasa assembly satu level lebih maju dibanding bahasa mesin atau machine code. Sejak saat inilah orang mulai mengenal apa yang dikatakan source code , yaitu bentuk program yang belum diolah oleh sebuah bahasa pemrograman menjadi bentuk yang dapat dieksekusi pada komputer. Source code biasanya berbentuk file yang dapat di edit. Perlu anda ketahui, bahwa saat ini pun anda dapat memprogram dalam machine code jika anda memang benar-benar menginginkannya. Caranya mudah, anda tinggal mencari program hexeditor, misalnya Hexworkshop kemudian membuat file yang berisi machine code dalam hexadesimal (bilangan

4

Tutorial Pemrograman Bahasa C dan Assembly

basis 16). Penulis beberapa kali melakukan hal ini karena belum mampu menggunakan assembler dengan baik (output file biner yang dihasilkan oleh assembler tidak sesuai dengan yang diharapkan). Sebenarnya jika anda membaca dan mencoba trik ke-3 pada artikel Trik Modifikasi Bios, anda telah memprogram dengan menggunakan machine code untuk microprocessor keluarga x86. Jadi, cukup mudah bukan :). Dalam tutorial ini kita akan belajar tentang assembler. Assembler pada dasarnya bekerja dengan cara "mencocokkan (matching)". Setiap baris perintah yang anda tulis dalam bahasa assembly akan di asosiasikan dengan satu machine code tertentu, sehingga pada assembler setiap baris perintah yang anda ketikkan akan menghasilkan satu machine code. Jadi jika anda menggunakan assembler, prosesnya akan kurang lebih seperti ini (tentang linker akan dijelaskan lebih lanjut):

Pembuatan Source Assembly | V Source Code Assembly | V Assembler | V library,object Linker file--> | V Machine Code | V Eksekusi pada komputer

Code

Perkembangan selanjutnya adalah bahasa tingkat menengah, yaitu bahasa pemrograman C. Pada bahasa pemrograman ini, machine code dihasilkan melalui tahap yang lebih panjang. Mungkin anda bertanya, kalau orang sudah bisa membuat program dengan assembler, mengapa harus ada bahasa C ? ,

5

Tutorial Pemrograman Bahasa C dan Assembly

jawabannya adalah bahasa assembly masih terlalu "machine oriented", sukar dipahami dan bahasanya lebih dekat kepada machine code daripada ke bahasa manusia, alasan lain adalah karena dengan membuat bahasa yang levelnya lebih tinggi (lebih dekat ke bahasa manusia) maka pengembangan perangkat lunak (software) akan lebih cepat, masih ada satu alasan lagi dan mungkin yang terpenting yaitu bahasa assembly hanya dapat dieksekusi oleh satu macam arsitektur komputer saja seperti yang disebutkan sebelumnya. Jika kita dapat membuat program yang dapat berjalan di berbagai macam arsitektur komputer maka itu akan mempercepat pengembangan software. Namun demikian, itu tidak berarti bahwa setiap program yang ditulis dengan bahasa C akan dapat berjalan pada semua mesin. Misalnya, program driver yang di buat pada Tutorial Membuat Patch ... yang akan kita bahas adalah program C yang tidak portable (dapat digunakan pada komputer dengan arsitektur yang berbeda), karena tidak dapat dieksekusi di luar arsitektur komputer x86 yang menggunakan system bus PCI compliant. Software yang dijamin portable biasanya adalah software yang tidak mengutak-atik sistem, atau yang menggunakan API(Application Programming Interface) standar dan tersedia di berbagai arsitektur komputer misalnya software yang menggunakan OpenGL atau yang dibuat dengan kriteria ANSI C. Berikut ini adalah langkah-langkah pembuatan program dengan bahasa C (tentang preprocessor, compiler & linker akan dijelaskan lebih lanjut):

Pembuatan Source Code C | V Source Code C | V Preprocessor | V Compiler | V Assembler | V library,object Linker file--> | V Machine Code

6

Tutorial Pemrograman Bahasa C dan Assembly

| V Eksekusi pada komputer

Inilah garis besar dari apa yang harus kita lakukan untuk menghasilkan sebuah program dalam bahasa C. Anda tidak perlu khawatir dengan langkah-langkah yang begitu banyak, sebab dalam sebagian besar kasus, kita hanya perlu membuat source code kemudian tools programming yang kita gunakan akan mengerjakan langkah -langkah selanjutnya sampai sebuah file executable dihasilkan. Bahasa pemrograman yang lain sebagian besar melakukan pembuatan program seperti C namun dengan beberapa langkah tambahan, tetapi ada juga bahasa pemrograman yang menggunakan cara yang agak berbeda, misalnya Java. Untuk mengetahui bagaimana cara kerja bahasa tingkat tinggi lainnya anda perlu membaca buku atau tutorial dalam bahasa yang bersangkutan.

7

Tutorial Pemrograman Bahasa C dan Assembly

Pemrograman Bahasa CSetelah membaca ulasan di atas, selnjutnya kita akan membahas tentang bahasa C. Bahasa ini adalah salah satu bahasa pemrograman yang andal dan banyak digunakan. Seperti yang telah disebutkan sebelumnya, bahasa C dapat berjalan pada berbagai platform, jadi dengan mempelajari bahasa ini, pengetahuan yang anda proleh dapat digunakan untuk mmeprogram di komputer dengan arsitektur selain x86.

Alasan Penggunaan Bahasa CBahasa C saat ini masih merupakan bahasa pemrograman yang banyak digunakan dan powerful. Sebelum melangkah lebih jauh, penulis akan menjelaskan beberapa alasan penggunaan bahasa C. 1. Dalam beberapa aplikasi pemrograman pada sistem operasi windows, kita tidak dapat atau sangat sulit menggunakan bahasa pemrograman selain C, misalnya untuk mengaplikasikan sebuah User Interface yang belum memiliki dukungan library untuk C++ pada visual C++ atau dalam pembuatan driver. Penulis menyebutkan sangat sulit berarti tidak menutup kemungkinan menggunakan bahasa lain, bahasa lain tersebut adalah C++, namun dukungan ofisial dari microsoft untuk penggunaan C++ pada kasus yang disebutkan sebelumnya belum ada sehingga anda harus membuat semacam "wrapper" sendiri jika ingin menggunakan C++ dan hal ini sulit dilakukan bagi pemrogram pemula. Perlu diketahui bahwa Windows saat ini belum merupakan sistem operasi yang benar-benar object oriented (C++ adalah bahasa pemrograman yang object oriented) dan "core" dari sistem operasinya sendiri masih diimplementasikan dalam bahasa C, sehingga fleksibilitas terbesar dalam membuat software untuk windows akan kita peroleh jika kita menggunakan bahasa C. Namun demikian, cara ini juga merupakan salah satu cara yang cukup sulit bagi para pemrogram pemula, namun anda tidak perlu khawatir, sebab dalam tutorial ini akan dijelaskan prinsip dasarnya. Beberapa software membutuhkan kinerja yang tinggi dari segi kecepatan, hal ini bisa dicapai dengan mudah jika kita menggunakan bahasa pemrograman yang "sederhana" seperti C. Penulis sendiri telah membuat beberapa software dalam dua versi, yaitu C "murni" dan C++ , kemudian membandingkan kinerjanya, ternyata software yang diimplementasikan dengan C memiliki kinerja yang lebih tinggi. Kelemahan dari bahasa C yang penulis ketahui sampai saat ini adalah dari segi kompleksitas pemeliharaan dan pengembangan software yang kita buat, jika software tersebut sudah cukup kompleks. Sebagai contoh, software ExploChip pada artikel Tutorial Membuat Patch...

2.

3.

8

Tutorial Pemrograman Bahasa C dan Assembly

dibuat dengan C++, C dan Assembly, namun C++ merupakan bahasa yang terbanyak digunakan untuk memudahkan pemeliharaan dan pengembangan.

Pengenalan Sintaks Bahasa CSintaks adalah suatu bentuk dasar (biasanya kata) yang dapat dipahami dan diolah oleh compiler. Pada bagian ini akan di bahas beberapa sintaks yang umum digunakan dalam bahasa C, selain itu akan dijelaskan bagaimana cara kerja compiler C secara umum. Sebelum melangkah lebih jauh, perlu anda ketahui bahwa C adalah bahasa pemrograman yang case sensitive, sehingga var, Var, dan VAR adalah tiga hal yang berbeda pada bahasa C.

Preprocessor dan MacroPreprocessor3 adalah bagian dari sebuah software development tool4 untuk bahasa C yang bertugas untuk melakukan pengolahan source code sebelum diberikan kepada compiler untuk diolah lebih lanjut. Preprocessor pada dasarnya menerjemahkan source code yang kita buat ke bentuk yang dapat dikenali oleh compiler. Dalam bahasa C, ada beberapa keyword5 yang sebenarnya tidak dikenali oleh compiler, umumnya keyword ini diawali dengan #, misalnya #macro, #define, #include, #pragma dan lain-lain, keyword inilah yang diolah oleh preprocessor. Preprocessor merupakan salah satu bagian Software Development Tool yang tergantung kepada vendor yang membuat tool tersebut, namun demikian, kita akan membahas beberapa keyword yang umum diolah oleh preprocessor (telah di standarisasi). Keyword yang diolah oleh preprocessor antara lain: Catatan: 3 Preprocessor juga ada dalam bahasa lain, tetapi yang kita maksud di sini adalah preprocessor untuk bahasa C. 4 Software development Tool misalnya Turbo C, Visual C++, Borland C++ Builder, GNU C dan lain-lain yang dapat mengolah source code C. 5 Kata-kata yang mempunyai arti khusus dan digunakan secara internal oleh compiler.

#include,yang

keyword ini membuat kita seakan-akan telah mengetik isi dari file dicantumkan sesudah keyword tersebut. Misalnya: #include < stdio.h > akan membuat preprocessor mengekspansikan file stdio.h pada tempat keyword #include tadi diketikkan. #include mempunyai dua macam bentuk yaitu #include dan #include "..." , titik-titik tersebut adalah nama file. #include akan membuat preprocessor mencari file yang namanya dicantumkan di dalam kurung pada direktori-

9

Tutorial Pemrograman Bahasa C dan Assembly

direktori yang telah didefinisikan oleh software development tool yang kita gunakan, misalnya pada direktory INC, INCLUDE, dan lain-lain. #include "..." akan membuat preprocessor mencari file yang namanya dicantumkan di dalam tanda petik ganda pada direktori file yang memiliki keyword #include tersebut. Misalnya anda mengerjakan file test.c yang ada pada direktori bernama test, dan anda mengetikkan #include "test.h", maka preprocessor akan mencari file test.h pada direktori test.

#define, keyword ini mempunyai format: #define identifier token-stringopt. Identifier adalah nama sebuah konstanta yang akan kita gunakan dalam program kita, dan token-string adalah nilai dari identifier tersebut (nilai ini harus dapat dikenali oleh compiler), token-string dapatmerupakan sebuah ekspresi6. Preprocessor akan mengganti setiap kemunculan identifier dengan nilai pada token-string. Jika token-string dikosongkan, maka identifier tadi akan hilang dari source code program kita (pada baris-baris selanjutnya, identifier menjadi tidak dikenali sebab tidak memiliki nilai lagi). Contoh: #define a 0xff akan membuat setiap kemunculan a dalam program, misalnya Function1(a) diganti dengan 0xff (255 desimal), dan jika sesudah baris Function1(a) ada baris #define a , maka a sudah tidak akan dikenali lagi pada bagian selanjutnya. Dengan demikian penggunaan a pada baris-baris selanjutnya adalah ilegal (compiler akan memberikan pesan kesalahan saat program di buat/ di-compile). Jika pada #define identifier token-stringopt, token-string merupakan sebuah ekspresi, maka identifier tersebut disebut sebagai makro. Makro akan diolah preprocessor, setiap kemunculan identifier pada source code yang akan diganti dengan ekspresi token-string (jika baris source code tsb tidak mengandung # sebagai karakter pertamanya), ekspresi ini kemudian diolah oleh compiler. Catatan: 6 Ekspresi adalah sekumpulan operator (misalnya +, -, dll) dan operand (variabel yang dikenai operasi) yang mengerjakan salah satu kombinasi aktivitas berikut: Menghitung nilai, memindahkan nilai ke dalam suatu objek atau fungsi (fungsi akan dijelaskan lebih lanjut), atau menghasilkan efek samping (misalnya membuat sebuah baris program dieksekusi atau tidak). Contoh: makro #define a ((c)*(d)) akan mengganti setiap kemunculan a dengan nilai hasil perkalian variabel c dan d. Tanda kurung yang banyak untuk memastikan bahwa makro kita dieksekusi sesuai dengan yang kita inginkan, sebab ada yang disebut dengan operator precedence, yaitu urutan pengerjaan operator jika berbagai operator muncul dalam sebuah pernyataan. Misalnya: a*b+c , pada ekspresi ini, yang akan diolah terlebih dulu adalah perkalian a

10

Tutorial Pemrograman Bahasa C dan Assembly

dan b, kemudian hasilnya dijumlahkan dengan makro dapat mempunyai parameter(nilai input). Berikut ini contohnya:

c.

#define .... int y = int z = int x =

kali(a,b) ((a)*(b)) 20; 90; kali(y,z);

Pada source code di atas, int adalah tipe data variabel, kita akan belajar lebih lanjut tentang hal ini. Saat preprocessor menemukan ekspresi kali(y,z) , maka nilai y dan z dari baris-baris sebelumnya akan menggantikan y dan z pada kali(y,z), kemudian kali(y,z) berubah menjadi ((y)*(z)) sehingga diperoleh 20x90 = 1800, kemudian hasil ini dipindahkan ke x (tanda = artinya pindahkan nilai di sebelah kanan tanda ini ke variabel yang ada di sebelah kiri tanda ini).

#if,

keyword ini digunakan untuk menentukan pengolahan baris-baris source code sesudahnya, sampai dengan keyword #endif. Formatnya adalah : #if expression , expression adalah sebuah ekspresi yang valid (dapat diolah oleh compiler). Jika ekspresi tersebut benar maka baris-baris source code sesudah #if akan diolah dan sebaliknya jika ekspresi tersebut bernilai salah. Keyword ini harus digunakan bersama dengan keyword #endif untuk menandakan batas penggunaan keyword tersebut dalam source code. Contoh:

#define TEST 2 #if (TEST > 0) ... baris source code; #endifPada potongan program diatas, baris source code akan diolah sebab ekspresi TEST > 0 adalah benar.

#ifdef, keyword ini penggunaannya sama dengan #if. Formatnya: #ifdef identifier, identifier adalah sebuah konstanta. Jika identifier telah didefinisikan sebelumnya (dengan keyword #define) maka baris-baris source code sesudah #ifdef akan diolah oleh kompiler, demikian pula sebaliknya. Keyword ini harus digunakan bersama dengan keyword #endif untukmenandakan batas penggunaan keyword tersebut dalam source code

11

Tutorial Pemrograman Bahasa C dan Assembly

#ifndef,

keyword ini mempunyai format yang sama dengan #ifdef, tetapi cara kerjanya adalah kebalikan dari #ifdef, sehingga jika baris- baris program sesudahnya justru diolah jika identifier tidak didefinisikan sebelumnya. Keyword ini harus digunakan bersama dengan keyword #endif untuk menandakan batas penggunaan keyword tersebut dalam source code

#endif

, keyword ini sebagai pembatas untuk menadakan bagian akhir dari source code yang akan dikenai efek jika keyword #if, #ifdef dan #ifndef dievaluasi.

#undef,

adalah keyword untuk menonaktifkan identifier yang telah didefinisikan dengan #define, efeknya sama dengan #define identifier . Dengan demikian kita dapat mendefinisikan kembali identifier tadi setelah #undef.

#else,

keyword ini digunakan di antara keyword #if, #ifdef, #ifndef dengan keyword #endif. Jika ekspresi pada keyword #if, #ifdef, #ifndef benar maka source code sesudah #else, tidak akan dieksekusi,demikian pula sebaliknya.Contoh:

#define TEST 0 #if (TEST > 0) ... baris source code1; #else ... baris source code2; #endif Pada potongan program diatas, baris source code2; akan diolah sebab ekspresi TEST > 0 adalah salah.

CompilerCompiler adalah bagian dari Software Development Tool yang kita gunakan, yang bertugas menerjemahkan source code yang telah diolah oleh prepocessor menjadi bahasa assembly yang selanjutnya akan diolah oleh assembler untuk dijadikan machine code yang dapat dieksekusi. Bahasa C dikatakan sebagai bahasa pemrograman yang portable, sebab dengan memberikan switch tertentu kepada compiler C pada saat kompilasi dilakukan, compiler akan menghasilkan file assembly yang berbeda, sesuai dengan tipe arsitektur yang kita pilih (dengan menggunakan switch tersebut), namun kemampuan ini juga tergantung kepada Software development Tool yang kita gunakan.

12

Tutorial Pemrograman Bahasa C dan Assembly

LinkerLinker adalah bagian dari Software development Tool yang bertugas mengubah format machine code yang dihasilkan oleh assembler menjadi instruksi yang dapat dieksekusi. Jadi sebenarnya assembler tidak menghasilkan sebuah program yang dapat dieksekusi. Sebuah file yang dapat dieksekusi mempunyai format header tertentu, dan dari satu sistem operasi ke sistem operasi yang lain formatnya berbeda-beda. Linker juga mempunyai fungsi lain, yaitu menyatukan komponen-komponen dari software yang kita buat ke dalam file executable yang nantinya akan dihasilkan setelah melalui linker, misalnya software tersebut mempunyai komponen eksternal seperti resource pada windows, resource sebenarnya bukan bagian dari program yang dieksekusi tetapi ikut disertakan dalam program karena dibutuhkan , contoh resource: ikon yang akan ditampilkan pada sebuah program. Komponen lain yang kadang-kadang disatukan oleh linker adalah dll (dynamic link library) yang disatukan secara "static" ke dalam file executable, selain itu linker juga menyatukan file objek (obj) diluar file objek yang kita hasilkan jika dibutuhkan oleh program kita ke dalam executable yang akan dibentuk.

PernyataanDalam bahasa C adalah sebuah baris program yang dapat diproses oleh compiler. Pernyataan diakhiri dengan tanda ; (titik-koma). Setiap pernyataan dapat berisi beberapa ekspresi, operator maupun operand. Contoh: return; Sekelompok pernyataan yang disatukan dalam sebuah kurung kurawal (kadang di sebut sebagai block of statement atau blok pernyataan ) juga diperlakukan seperti sebuah pernyataan biasa, hal ini terutama berguna saat anda akan mengatur eksekusi sekelompok pernyataan dalam sebuah percabangan seperti contoh di bawah ini:

if (a > b) { pernyataan 1; pernyataan 2; pernyataan 3; }jika ekspresi didalam kurung sesudah if ( ekspresi a > b) bernilai benar maka seluruh pernyataan di dalam kurung kurawal akan dieksekusi. Sebenarnya keyword if hanya dapat mengeksekusi satu pernyataan sesudah ekspresi yang diuji olehnya ( pada contoh di atas ekspresi a > b), namun dengan adanya kurung kurawal, pernyataan 1 s/d pernyataan 3 seolah-olah

13

Tutorial Pemrograman Bahasa C dan Assembly

dianggap

satu

pernyataan

saja

oleh

compiler

yang

kita

gunakan.

Variabeladalah sebuah simbol yang mewakili sebuah alamat di memory yang nilainya dapat dimanipulasi melalui nama tersebut dan mempunyai ukuran tertentu. Ukuran ini disebut tipe data. Tipe data yang berbeda kemungkinan mempunyai ukuran yang berbeda. Sintaks untuk mendeklarasikan (menyatakan pertama kali adanya sebuah variabel) adalah:

tipe-data NamaVariabel Contoh: int variabel1;Berikut ini adalah tipe data dan ukurannya.

Tipe data bool int, int char, char long, long unsigned unsigned unsigned

Ukuran Contoh (byte) 1 4 1 4 bool a = true ; bool b = false int a = -255 ; unsigned int b = 10 char a = -1 ; unsigned char b = 1 long c = 0xFF long d = 0xFFC ; unsigned

short, 2 unsigned short float double long double 4 8 8

short e = 0xFFF ; unsigned short f = 067 float g = 0.001 double h = 1.02e8 long double h = 2.42e10

Variabel bertipe bool hanya mempunyai dua nilai yaitu true atau false, jadi anda hanya dapat mengisinya dengan nilai ini. Perlu diperhatikan bahwa variabel bertipe bool dianggap bernilai false jika nilainya 0 , dan benar jika nilainya >= 1. Mungkin anda bertanya, mengapa untuk dua alternatif saja kita menggunakan 1 byte (8 bit) padahal untuk 2 alternatif hanya dibutuhkan 1 bit, hal ini disebabkan oleh arsitektur kebanyakan microprocessor saat ini adalah "byte addessable", maksudnya nilai variabel terkecil yang dapat ditangani oleh

14

Tutorial Pemrograman Bahasa C dan Assembly

microprocessor tersebut adalah byte. Variabel bertipe int (integer/bilangan bulat) dan unsigned int (unsigned integer/bilangan bulat positif) hanya dapat diisi dengan bilangan yang bernilai bulat (tidak ada nilai di belakang koma atau pecahan) termasuk 0. Pada sistem operasi windows variabel dengan tipe ini memiliki nilai 32 bit (4 byte), sehingga untuk unsigned int nilai maksimumnya adalah 232 -1 (karena ada 0), int nilainya berada pada kisaran (216) sampai 216-1 (karena ada 0). Untuk variabel yang tipenya lain tentunya anda sudah dapat menghitung sendiri nilai kisarannya, karena semuanya analog dengan tipe data int. Anda hanya perlu mengetahui jumlah bit yang digunakan variabel tersebut dan untuk yang signed, nilai negatif yang paling kecilnya adalah - (2(jumlah bit tipe data tsb/2)) dan nilai maksimum positifnya adalah 2 (jumlah bit tipe data tsb/2) -1 (karena ada 0). Namun demikian, ada tiga tipe variabel yang tidak termasuk ke dalam golongan yang memenuhi aturan ini, yaitu float, double dan long double. Ketiga tipe ini adalah tipe variabel floating point, yaitu variable yang dinyatakan dalam mantissa dan eksponen. Mantissa adalah faktor pengali, dan eksponen adalah bilangan berpangkat 10 yang dikalikan dengan mantissa tersebut. Misalnya 1,045x10e5 , pada bilangan ini mantissa adalah 1,045 sedangkan eksponennya adalah 10e5 (105). Pada prakteknya variabel tipe floating point ditangani oleh bagian FPU (Floating Point Unit/Math Coprocessor) pada microprocessor kita. Pada variabel floating point anda dapat memasukan data dengan nilai pecahan yang dikalikan dengan eksponesial, misalnya 1,5x1032.Tipe data floating point nilai maksimum dan minimumnya tergantung pada Software Development Tool yang kita gunakan, pada Visual C+ + , kisarannya sebagai berikut: float mempunyai nilai antara 10-308 s/d 10308, double mempunyai nilai antara -1.79769313486231x10308 s/d -324 -4.94065645841247x10 untuk bilangan negatif, dan 4.94065645841247x10324 s/d 1.79769313486231x10308 untuk bilangan positif, dan 0. Pada beberapa sistem long double dan double mempunyai ukuran berbeda, tetapi pada Microsoft Visual C++, keduanya adalah identik. Pada saat menentukan tipe data sebuah variabel yang akan anda gunakan anda harus memperhatikan nilai maksimum atau minimum yang dapat ditampung oleh tipe data tersebut agar tidak terjadi kesalahan perhitungan, nilai dari variabel tersebut harus kurang dari atau sama dengan nilai maksimum yang dapat ditampung oleh tipe data tersebut. Anda juga dapat memasukkan bilangan bukan desimal(basis 10) ke dalam variabel yang bukan floating point. Misalnya, anda dapat memasukkan nilai heksa desimal (basis 16) seperti 0xFF, bilangan octal (basis 8) seperti 0223. Selain sifat ini, variabel dengan tipe data char mempunyai sifat tersendiri, yaitu anda dapat memasukkan sekumpulan ke dalam variabel tersebut, misalnya pada potongan source code berikut:

char message = "Ini adalah string"variabel char dengan deklarasi seperti di atas sering di sebut string, sebab menyimpan sekumpulan karakter, nilai variabel ini tidak begitu penting sebab yang kita simpan bukan merupakan nilai yang akan dioperasikan. Variabel memiliki apa yang disebut variable scope. Variable scope adalah daerah

15

Tutorial Pemrograman Bahasa C dan Assembly

di dalam source code tempat sebuah variabel masih dikenali/terlihat oleh compiler. Misalnya seperti ini: anda mendeklarasikan sebuah variabel di dalam sebuah fungsi di luar fungsi main(), maka variabel tersebut hanya memiliki scope pada fungsi itu saja, sebab di luar fungsi tersebut variabel tersebut tidak akan dikenali oleh compiler. Variabel seperti ini biasa disebut variabel lokal. Kasus kedua adalah variabel yang dideklarasikan diluar semua fungsi, variabel seperti ini mempunyai scope dikeseluruhan source code yang kita miliki. Variabel seperti ini biasa juga disebut variabel global sebab variabel ini dikenali di dalam fungsi manapun dalam program kita, termasuk fungsi main(). Implikasi dari adanya variabel global dan lokal adalah: 1. Anda tidak dapat menggunakan nama variabel yang sama untuk sebuah variabel global dan lokal sebab compiler akan menganggap variabel tersebut dideklarasikan 2 kali dan hal ini tidak dibolehkan oleh compiler C. Anda tidak dapat menggunakan variabel lokal di luar fungsi tempat variabel tersebut dideklarasikan, sebab variabel tersebut tidak akan dikenali oleh compiler.

2.

Untuk memperjelas, akan diberikan contoh sebagai berikut:

#include #define UINT unsigned int /* deklarasi fungsi kali */ UINT kali(UINT a, UINT b); UINT var_test; void main() { UINT faktor1 = 10; UINT faktor2 = 20; UINT hasil ; hasil = kali(faktor1,faktor2); printf("%d \n",hasil); } UINT kali(UINT a, UINT b) { UINT hasil = 30; UINT c = a*b; return c; }

16

Tutorial Pemrograman Bahasa C dan Assembly

Pada source code di atas, var_test adalah variabel global, sebab variabel ini dideklarasikan di luar semua fungsi. Variabel faktor1, faktor2 dan hasil adalah variabel lokal pada fungsi main(). Variabel hasil yang ada pada fungsi kali adalah variabel lokal, yang berbeda dengan variabel hasil yang ada pada fungsi main() sebab scope kedua variabel tersebut adalah berbeda. Anda perlu memperhatikan hal seperti ini saat anda membuat program, sebab jika anda dengan seenaknya memberi nama pada variabel yang anda gunakan tanpa memperhatikan scope dari variabel tersebut, bisa-bisa program anda tidak dapat di-compile.

OperatorOperator adalah karakter atau kumpulan karakter yang digunakan untuk memanipulasi variabel. Karakter atau kumpulan karakter ini dikenali secara spesifik oleh compiler sehingga variabel yang diubah-ubah nilainya(operand) akan dikenai operasi sesuai dengan definisi operasi yang dimiliki oleh operator tadi. Dalam bahasa C ada tiga golongan besar operator, yaitu: unary operator, operator ini hanya bekerja pada satu operand. Unary operator antara lain:

Operator Kegunaan Unary - ~ !Operator ini melakukan operasi negasi, yaitu merubah nilai operand menjadi nilai yang berlawanan tanda. Disebut juga Negation atau complement operator Operator ini menaikkan nilai operand 1 satuan, disebut juga increment operator. Operator ini menurunkan nilai operand 1 satuan, disebut juga decrement operator. Operator ini memberikan nilai yang tersimpan pada alamat memory yang ditunjuk oleh operand, disebut juga indirection operator. Biasanya digunakan dalam operasi dengan pointer (akan dijelaskan lebih lanjut) Operator ini memberikan alamat memory operand. Biasanya digunakan dalam operasi dengan pointer (akan dijelaskan lebih lanjut). Disebut juga address of operator.

++ --

*

&

17

Tutorial Pemrograman Bahasa C dan Assembly

sizeof Operator ini memberikan ukuran memory yang (operand digunakan operand dalam byte. operand adalah ) operand yang ukurannya akan dicari.

binary operator, operator ini bekerja pada dua operand sekaligus. Operator binary antara lain:

Operator Kegunaan Binary Operator Matematis + * /Operator ini menjumlahkan nilai operand di sebelah kiri dengan nilai operand di sebeleh kanan Operator ini mengurangi nilai operand di sebelah kiri dengan nilai operand di sebeleh kanan Operator ini mengalikan nilai operand sebelah kiri dengan nilai operand di sebelah kanan Operator ini membagi nilai operand sebelah kiri dengan nilai operand di sebelah kanan Operator ini disebut juga modulo operator. Nilai Operand di sebelah kiri akan dibagi dengan nilai operand di sebelah kanan, kemudian hasil operasinya adalah sisa dari pembagian tersebut. Misalnya: 3%2 akan menghasilkan 1. Operator ini disebut operator logical and. Cara kerjanya adalah mengetes ekspresi disebelah kanan dan kirinya, jika keduanya benar maka nilai yang dihasilkan 1 (true), jika tidak maka nilai yang dihasilkan adalah 0 (false) Operator ini disebut operator logical or. Cara kerjanya adalah mengetes ekspresi disebelah kanan dan kirinya, jika salah satunya benar maka nilai yang dihasilkan 1, jika tidak maka nilai yang dihasilkan adalah 0 Operator disebut juga operator bitwise and. Cara

%

Operator Logic (Logical Operator)

&&

||

Operator Operasi Bit (Bitwise Operator) &

18

Tutorial Pemrograman Bahasa C dan Assembly

kerjanya adalah melakukan operasi AND (&)pada setiap bit kedua operand. misalnya: 0xA & 0xC akan menghasilkan 0x8, perhatikan bahwa 0xA = 1010biner dan 0xC = 1100biner. Sehingga 1010 & 1100 = 1000biner atau 0x8heksadesimal sebab 0&1 = 0 , 1&0 = 0, 0&0 = 0, 1&1 = 1. Operator ini disebut juga operator bitwise or. Cara kerjanya adalah melakukan operasi OR (|) pada setiap bit kedua operand. misalnya: 0xA | 0xC akan menghasilkan 0xE, perhatikan bahwa 0xA = 1010 biner dan 0xC = 1100biner. Sehingga 1010 | 1100 = 1110biner atau 0xE heksadesimal sebab 0|1 = 1 , 1|0 = 1,

|

0|0 = 0, 1|1 = 1.

^

Operator ini disebut juga operator bitwise xor. Cara kerjanya adalah melakukan operasi XOR (^) pada setiap bit kedua operand. misalnya: 0xA ^ 0xC akan menghasilkan 0x6, perhatikan bahwa 0xA = 1010biner dan 0xC = 1100biner. Sehingga 1010 | 1100 = 0110biner atau 0x6heksadesimal sebab 0^1 = 1 , 1^0 = 1, 0^0 = 0, 1^1 =

0.

Operator Penggeseran Bit (Bit Shift Operator)Operator ini disebut operator geser kanan (right shift). Cara kerjanya adalah melakukan penggeseran bit ke kanan pada nilai operand sejumlah bit yang ada di sebelah kanan operator ini. Misalnya: 0xA >> 3 akan menghasilkan 0x1, perhatikan bahwa 0xA = 1010biner, sehingga dengan menggeser bitnya 1 kali ke kanan diperoleh 101biner, dan jika digeser 3 kali akan diperoleh 1biner atau 0x1heksadesimal Operator ini disebut operator geser kiri (left shift). Cara kerjanya adalah melakukan penggeseran bit ke kiri pada nilai operand sejumlah bit yang ada di sebelah kanan operator ini. Misalnya: 0xA >

=

A B if(A ... else ...

= = >= code2

4; 2; B) code1; ;

pada source code ini ...code1 akan di eksekusi, sebab A>=B bernilai 1 (true).

#define UINT unsigned int /* deklarasi fungsi kali */ UINT kali(UINT a, UINT b); void { UINT UINT UINT main() faktor1; faktor2; hasil ;

printf("Ketikkan nilai untuk faktor1 "); scanf("%d", &faktor1); if(faktor1 >= 0 && faktor1 = 0 && faktor2 = 0 && faktor1 = 0 && faktor2 = 0 && faktor1 #define UINT unsigned int void tukar1(UINT* pVar1, UINT* pVar2); void tukar2(UINT Var1, UINT Var2); int main() { UINT var1 = 4; UINT var2 = 8; UINT * pVar1 = &var1; printf("nilai var1 saat ini : %d \n", var1); printf("nilai var2 saat ini : %d \n", var2); tukar2 (var1,var2); printf("nilai var1 setelah tukar2: %d \n",var1); printf("nilai var2 setelah tukar2: %d \n",var2); tukar1(pVar1, &var2); printf("nilai var1 setelah tukar1: %d \n", var1); printf("nilai var2 setelah tukar1: %d \n", var2); return 0; } void tukar2(UINT Var1, UINT Var2) { /* karena fungsi ini passing by value maka yang kita rubah nilainya saat ini adalah copy variabel var1 dan var2 yang ada di stack */ UINT temp; temp = Var1; /* copy isi var1 pada temp */ Var1 = Var2; /* copy isi var2 ke var1 */ Var2 = temp; /* copy isi temp ke var2 */ } void tukar1(UINT* pVar1, UINT* pVar2) { UINT temp; temp = (*pVar1); /* copy isi var1 pada temp*/ (*pVar1) = (*pVar2); /* copy isi var2 ke var1 */ *pVar2 = temp; /* copy isi temp ke var2 */ }

36

Tutorial Pemrograman Bahasa C dan Assembly

Jika anda mengeksekusi program di atas, akan diperoleh output sebagai berikut:

nilai nilai nilai nilai nilai nilai

var1 var2 var1 var2 var1 var2

saat ini : 4 saat ini : 8 setelah tukar2: setelah tukar2: setelah tukar1: setelah tukar1:

4 8 8 4

Dari output ini tampak jelas bahwa fungsi tukar1 benar-benar mempertukarkan isi dari var1 dan var2 , sementara fungsi tukar2 tidak demikian. Di sini lah kegunaan pointer mulai tampak. Pada fungsi tukar1, parameter-parameter yang digunakan adalah alamat variabel-variabel yang ingin kita rubah. Anda juga telah membaca sebelumnya tentang bagaimana sebuah fungsi bekerja, dari uraian tsb diketahui bahwa saat kita tidak menggunakan pointer sebagai parameter fungsi (biasa disebut passing by value) maka yang diubah bukan parameter yang kita masukkan, melainkan "kopian" dari parameter tersebut yang ada di stack, sehingga pada saat fungsi selesai dieksekusi parameter semula tidak berubah. Tidak demikian hal nya jika kita menggunakan pointer sebagai parameter (biasa disebut passing by pointer), karena yang kita jadikan paramater adalah alamat dari variabel tersebut, maka saat kita menggunakan indirection operator, variabel yang kita peroleh adalah variabel aslinya, and dapat melihat aplikasinya pada fungsi tukar1. Fungsi ini tidak penulis jelaskan lebih jauh sebab comment yang diberikan serta penjelassan sintaks pointer sebelumnya penulis anggap sudah memadai. Karena keterbatasan waktu, penjelasan tentang pointer hanya sampai di sini. Sekarang kita akan membahas mengenai Array. Array adalah sekelompok variabel yang berbeda, dengan tipe data yang sama yang diperlakukan sebagai satu variabel dan dapat diakses dengan menggunakan nama dari array tersebut. Sintaks untuk mendeklarasikan array adalah:

tipe_data nama_array[jumlah_komponen]; tipe_data adalah tipe data dari variabel yang menjadi komponen array tersebut. nama_array adalah nama yang kita berikan untuk array tersebut. jumlah_komponen adalah jumlah komponen (variabel) yang dimiliki oleharray tersebut. Berikut ini contoh penggunaannya:

#include < stdio.h > #define UINT unsigned int void main() { UINT komponen = 5; UINT arr_Test[10]; UINT c; for( c = 0 ; c < 10 ; c++)

37

Tutorial Pemrograman Bahasa C dan Assembly

{ arr_Test[c] = komponen; printf("Isi dari komponen ke[%d], adalah: %d \n",c,arr_Test[c]); komponen++; }}output dari program di atas adalah:

Isi Isi Isi Isi Isi Isi Isi Isi Isi Isi

dari dari dari dari dari dari dari dari dari dari

komponen komponen komponen komponen komponen komponen komponen komponen komponen komponen

ke[0], ke[1], ke[2], ke[3], ke[4], ke[5], ke[6], ke[7], ke[8], ke[9],

adalah: adalah: adalah: adalah: adalah: adalah: adalah: adalah: adalah: adalah:

5 6 7 8 9 10 11 12 13 14

Pada program di atas, kita mendeklarasikan array dengan tipe data unsigned int, dengan jumlah komponen sebanyak 10 buah. Kemudian komponenkomponen dari array tersebut diisi dengan nilai variabel komponen dalam sebuah perulangan menggunakan for. Perlu anda ketahui bahwa setiap komponen array dapat diakses dengan sebuah nilai yang disebut indeks. Indeks menunjukkan letak komponen tersebut di dalam array. Indeks paling kecil adalah 0, yaitu indeks yang menunjukkan komponen pertama dari array. Indeks 1 menunjukkan komponen ke-2, dan seterusnya. Perhatikan bahwa untuk mengakses komponen pertama dari array arr_Test, digunakan sintaks arr_Test[0] (sebab nilai awal variabel c adalah 0), dan seterusnya. Satu hal yang perlu anda waspadai saat menggunakan array, yaitu pastikan bahwa anda tidak mengakses indeks array yang melebihi jumlah komponen array tersebut, sebab hal ini akan menjadi bug pada program anda. Misalnya anda mendeklarasikan sebuah array mempunyai 9 komponen, maka indeks maksimum yang dapat anda gunakan adalah nama_array[8], sebab indeks dimulai dari 0. Nama sebuah array sebenarnya merupakan pointer ke elemen pertama dari array tersebut, sehingga anda dapat mengakses elemen array tesebut dengan sebuah trik yang disebut dengan pointer arithmetic. Trik ini memanfaatkan nama array yang diperlakukan sebagai pointer. Berikut contohnya: (program sebelumnya diubah untuk memberikan output yg sama).

38

Tutorial Pemrograman Bahasa C dan Assembly

#include < stdio.h > #define UINT unsigned int void main() { UINT komponen = 5; UINT arr_Test[10]; UINT* pArr = arr_Test; UINT c; for( c = 0 ; c < 10 ; c++) { *pArr = komponen; printf("Isi dari komponen ke[%d], adalah: %d \n",c,arr_Test[c]); pArr++; komponen++; } }Output yang dihasilkan oleh program ini sama dengan output program sebelumnya. Perhatikan bahwa untuk menunjuk dan memanipulasi komponen dari arr_Test kita menggunakan pointer pArr. Sintaks pArr++ artinya naikkan nilai alamat memori yang ditunjukkan pArr satu satuan, satu satuan di sini adalah sejumlah byte dari ukuran tipe data pointer tersebut, karena tipe datanya integer maka alamat yang ditunjuk pointer tersebut dinaikkan sebanyak 4 byte (pada windows). Menaikkan nilai pArr satu satuan sama saja artinya dengan menunjuk ke komponen selanjutnya dalam sebuah array, inilah yang disebut pointer arithmetic. Komponen pembentuk sebuah array diletakkan pada alamat yang berurutan di memory, hal inilah yangmenyebabkan kita dapat melakukan pointer arithmetic. Sampai di sini anda telah mengerti tentang pointer dan array, tidak terlalu sulit bukan ? Obrolan bebas: "Anda mungkin sudah lelah mengikuti tutorial ini, ... apalagi penulis :(. Jadi... kita break sebentar. Penulis ingin ngobrol sesuatu soal programming. Mungkin anda mengenal John Carmack, programer yang ada di balik ID Software, pembuat game Quake III Arena, RTCW (Return to The Castle Wolfenstein) dan Doom III. Kadang penulis berpikir, bahasa dan tool seperti apa yang digunakan John Carmack untuk membuat game-nya ? sebab sebagian besar atau malah semua game buatan ID software tersedia untuk hampir semua platform komputer, mulai dari Apple sampai PC. Kalau kita pikir, pastilah 'code base' dari semua game itu sama, tidak mungkin dia membuat game dengan 'code base' yang berbeda-beda, sebab biaya pengembangannya akan sangat mahal. Jadi ... apa jawabannya ? "

39

Tutorial Pemrograman Bahasa C dan Assembly

Struct dan UnionStruct adalah bentuk yang sangat penting anda pahami jika anda ingin memprogram menggunakan C pada sistem operasi windows, sebab sebagian besar komponen windows mulai dari user interface sampai driver menggunakan bentuk ini, dan sebagian lagi memanfaatkan union. Pada bagian sebelumnya kita telah mengenal bentuk penyimpanan data dalam C yaitu dengan menggunakan variabel dan dengan menggunakan array. Sekarang kita akan mempelajari metode penyimpanan data yang lebih umum yaitu struct dan union. Pada penyimpanan data dengan array yang kita pelajari sebelumnya, kita hanya dapat menyimpan data dengan menggunakan 1 macam tipe data yaitu tipe data dari array tersebut. Penyimpanan data menggunakan struct memberi kita fleksibilitas yang lebih baik, karena kita dapat menyimpan data dengan tipe data yang berbeda ke dalam 1 struct. Struct pada dasarnya merupakan sebuah tipe data baru yang didefinisikan oleh programer yang membuatnya. Sintaks yang digunakan untuk mendeklarasikan sebuah struct adalah:

struct nama_struct{ tipe_data elemen_1; tipe_data elemen_2; ... tipe_data elemen_n;}variabel_struct1,variabel_struct2,.. ;pada sintaks di atas, tipe_data adalah tipe data dari elemen struct, yang telah dikenali secara internal oleh compiler, misalnya int. Pada sintaks di atas, variabel_struct1 dan variabel_struct2 adalah variabel dengan tipe data struct nama_struct, variabel ini tidak wajib, jika anda lebih suka membuat variabel dengan tipe data struct ini di tempat lain, anda dapat membuatnya pada baris program sesudah deklarasi struct ini. Anda harus menggunakan sintaks:

struct nama_struct variabel_struct;Anda dapat menggunakan sintaks yang mempersingkat pembuatan variabel bertipe struct tertentu dengan menggunakan keyword typedef12. Dengan keyword ini, anda tidak perlu lagi memberikan kata struct di depan nama struct tersebut. Sintaksnya sbb:

typedef struct nama_struct{ tipe_data nama_elemen_1; tipe_data nama_elemen_2; ...

40

Tutorial Pemrograman Bahasa C dan Assembly

tipe_data nama_elemen_n;}nama_alias, *nama_alias_pointer;dengan teknik ini anda tinggal menggunakan sintaks:

nama_alias variabel_struct;untuk membuat sebuah variabel dengan tipe-data struct nama_struct. Pada sintaks di atas *nama_alias_pointer adalah opsional, keyword ini dapat anda gunakan jika anda ingin membuat variabel bertipe pointer ke struct nama_struct. Untuk melakukannya, anda tinggal menggunakan sintaks:

nama_alias_pointer pointer_struct;Sintaks seperti ini banyak digunakan dalam pemrograman C pada windows. Untuk mengakses isi dari elemen sebuah struct anda dapat menggunakan member of operator , yaitu: . (titik). Misalnya untuk mengakses elemen_1 pada variabel variabel_struct1 anda akan menggunakan sintaks:

variabel_struct1.elemen_1jika variabel yang anda miliki ternyata adalah sebuah pointer, misalnya: pointer_struc maka untuk mengakses elemennya, anda harus menggunakan member of operator dalam bentuk lain, yaitu ->. Sebagai contoh, untuk mengakses elemen_2 pada variabel pointer_struct anda akan menggunakan sintaks:

pointer_struct->elemen_2Hal yang lain yang perlu anda perhatikan adalah, anda tidak dapat langsung menggunakan sebuah struct tanpa membuat objek dari struct tersebut, sebab struct nama_struct juga adalah tipe data seperti halnya int, bukan merupakan variabel, jadi seperti pada int, anda harus membuat variabel dulu sebelum menggunakannya. typedef dapat digunakan untuk mengganti makro #define untuk mempersingkat penulisan tipe data, misalnya, sintaks yang ekivalen dengan12

#define UINT unsigned intadalah:

typedef unsigned int UINT ;

41

Tutorial Pemrograman Bahasa C dan Assembly

Selanjutnya kita akan membahas sebuah program yang memanfaatkan struct. Berikut ini source code program tersebut:

#include < stdio.h > #include < malloc.h > #include < string.h > typedef unsigned int UINT; typedef struct Microprocessor{ UINT kecepatan; UINT harga; char nama[20];} uP, *puP; /* deklarasi struktur */ void harga_per_MHz(puP pProc); /* deklarasi fungsi */ void main() { puP Duron = malloc(sizeof(uP));/* alokasikan memori */ uP Celeron; Duron->harga = 350000; Duron->kecepatan = 1100; strcpy(Duron->nama, "Duron"); Celeron.harga = 700000; Celeron.kecepatan = 2000; strcpy(Celeron.nama, "Celeron"); harga_per_MHz(Duron); harga_per_MHz(&Celeron); free(Duron);/* bebaskan memori yang dialokasikan */ } void harga_per_MHz(puP pProc) /* definisi fungsi */ { UINT harga_MHz = (pProc->harga)/(pProc->kecepatan) ; printf("harga per MHz dari %s adalah %d \n", pProc->nama, harga_MHz); }

42

Tutorial Pemrograman Bahasa C dan Assembly

Output dari program di atas adalah:

harga per MHz dari Duron adalah 318 harga per MHz dari Celeron adalah 350Pada source code di atas ada dua file header yang baru kita gunakan, yaitu malloc.h dan string.h. File malloc.h digunakan untuk menyediakan kemampuan bagi program kita untuk mengalokasikan memori secara dinamis menggunakan fungsi malloc() dan free. Fungsi malloc(size_t size) adalah fungsi untuk mengalokasikan memori sebesar size byte. Alokasi memori dinamis ini diperlukan karena puP Duron adalah sebuah pointer, dan seperti yang dijelaskan sebelumya, setiap pointer harus diinisialisasi untuk menunjuk alamat memori tertentu atau diinisialisasi dengan 0 (dideklarasikan dengan nilai nol) agar pointer tersebut tidak menjadi stray pointer, yang dapat menimbulkan bug. Memori yang dialokasikan secara dinamis harus "dibebaskan" setelah digunakan dan tidak diperlukan lagi dengan fungsi free(), jika tidak maka memori tersebut tidak akan dapat dipakai lagi selama program kita masih berjalan, hal ini lah yang disebut memory leak. Memory leak adalah salah satu bug yang paling berbahaya, apalagi jika bug ini terjadi pada blok pernyataan yang mengalami perulangan, karena akan membuat sistem kita kehilangan "free memory" (memory yang dapat dipakai oleh aplikasi lain) dalam jumlah besar, dan bisa menimbulkan hang. Jadi berhati- hatilah saat menggunakan alokasi memori dinamis, pastikan bahwa setiap fungsi malloc(size_t size) diimbangi oleh fungsi free(). File string.h digunakan untuk menyediakan kemampuan memindahkan string (kelompok karakter) dari satu variabel ke variabel lain atau dari sebuah string ke variabel yang dapat menyimpan string. Hal ini dilakukan dengan fungsi strcpy(). Pada sourcecode di atas kita memindahkan string ke elemen nama dari variabel bertipe struct Microprocessor yang kita buat. Sekarang kita akan membahas source code di atas dengan lebih detail. Pada bagian awal program, kita mendeklarasikan sebuah struct dengan nama Microprocessor, struct ini mengandung 3 elemen, yaitu kecepatan, harga dan nama. Pada deklarasi struct itu juga kita memberi 2 nama alias untuk struct ini, yaitu uP yang merupakan nama alias "biasa", dan puP yang merupakan pointer ke struct dengan tipe Microprocessor. Setelah itu dideklarasikan sebuah fungsi tanpa return value yang mempunyai satu parameter dengan tipe pointer ke struct Microprocessor (fungsi harga_per_MHz), fungsi ini menggunakan passing by pointer seperti yang sudah kita bahas sebelumnya. Pada definisi fungsi ini tampak bahwa fungsi ini akan menghitung harga per-MHz untuk setiap variabel (bertipe struct Microprocessor) melalui pointer ke variabel tersebut yang menjadi parameter inputnya. Hasil perhitungan ini kemudian di tampilkan ke user. Pada fungsi main(), kita mendeklarasikan 2 buah variabel bertipe struct Microprocessor, 1 secara langsung, yaitu Celeron, dan satu secara tidak langsung, yaitu Duron.

43

Tutorial Pemrograman Bahasa C dan Assembly

Kemudian kedua variabel ini diisi seluruh elemennya dengan nilai yang kita inginkan dengan menggunakan operator yang sesuai, setelah itu fungsi harga_per_MHz(...) dipanggil untuk menampilkan harga per MHz dari setiap variabel ini. Fungsi harga_per_MHz menggunakan parameter bertipe pointer ke struct, itulah sebabnya saat kita memanggil fungsi ini pada variabel Celeron, harus digunakan & (address of operator) agar kita memberikan masukan yang tepat ke fungsi tersebut, yaitu pointer ke struct yang kita miliki. Address of operator akan memberikan alamat dari suatu variabel, hal ini sama saja artinya dengan memberikan pointer ke variabel yang kita inginkan. Pembahasan mengenai struct hanya sampai di sini saja, untuk mengetahui lebih lanjut anda dapat membaca referensi yang ada pada akhir tulisan ini. Sekarang kita akan membahas sebuah bentuk yang dinamakan union. Union pada dasarnya mirip dengan struct, bahkan sintaks untuk deklarasinya sama saja. Yang membedakan struct dan union adalah cara kerjanya. Dalam sebuah struct, seluruh elemen struct tersebut berisi sesuatu pada suatu saat, sedangkan dalam sebuah union, pada satu saat hanya ada ada 1 elemen yang dapat digunakan. Dalam sebuah union biasanya elemennya mempunyai tipe data yang berbeda-beda. Bagi anda yang pernah menggunakan bahasa pemrograman Visual Basic, union akan tampak analog dengan variant. Union mempunyai cara kerja yang mirip "variabel template", karena kita dapat memasukkan variabel dengan tipe data yang telah kita definisikan sebagai elemen union tersebut ke dalam union tersebut pada saat run time (saat program sedang berjalan) dan pada satu saat hanya satu elemen saja yang dapat digunakan. Di bawah ini adalah sintaks untuk mendeklarasikan union :

union nama_union{ tipe_data elemen_1; tipe_data elemen_2; ... tipe_data elemen_n;}variabel_union1,variabel_union2,.. ; atau dengan menggunakan keyword typedef: typedef union nama_union{ tipe_data elemen_1; tipe_data elemen_2; ... tipe_data elemen_n;}nama_alias, *nama_alias_ponter;Catatan: nama_union maupun nama_struct pada deklarasi sebuah struct atau union adalah opsional, anda dapat menggunakan sebuah nama untuk sebuah struktur dan dapat mengabaikannya, tergantung dari kemauan anda.

44

Tutorial Pemrograman Bahasa C dan Assembly

Selanjutnya kita akan membahas sebuah penggunaan union. Berikut ini adalah sebuah contoh implementasinya.

#include < stdio.h > #define CHARACTER 'C' #define INTEGER 'I' #define FLOAT 'F' typedef struct data_holder{ char type; union shared_tag { char c; int i; float f; } shared; }generic_tag; void print_function( generic_tag generic ); void main() { generic_tag var; var.type = CHARACTER; var.shared.c = '$'; print_function( var ); var.type = FLOAT; var.shared.f = (float) 12345.67890; print_function( var ); var.type = 'x'; var.shared.i = 111; print_function( var ); } void print_function( generic_tag generic ) { printf("\n\nThe generic value is..."); switch( generic.type ) { case CHARACTER: printf("%c", generic.shared.c); break;

45

Tutorial Pemrograman Bahasa C dan Assembly

case INTEGER: printf("%d", generic.shared.i); break; case FLOAT: printf("%f", generic.shared.f); break; default: printf("an unknown type: %c\n", generic.type); break; } }Output dari program di atas adalah:

The generic value is...$ The generic value is...12345.678711 The generic value is...an unknown type: xContoh di atas adalah contoh penggunaan union yang umum, yaitu sebagai variabel template. Pada contoh di atas, dideklarasikan sebuah struct dengan nama data_holder dan nama alias generic_tag yang salah satu komponennya adalah union dengan nama shared_tag, union ini dapat menyimpan data dengan tipe char, float atau integer (int). Didefinisikan beberapa konstanta yang digunakan sebagai "pengenal" untuk data yang dimasukkan ke union ini, yaitu CHARACTER, FLOAT, INTEGER. Pada fungsi main(), didefinisikan sebuah variabel struct data_holder dengan nama var. Elemen type dari struct ini kemudian isinya diubah-ubah pada saat yang bersamaan, tergantung data yang dimasukkan ke elemen shared yang merupakan sebuah variabel union. Setelah itu, isi dari union yang ada pada var ditampilkan kepada user dengan menggunakan fungsi print_function. Fungsi ini bekerja dengan cara mengecek tipe data yang telah dimasukkan ke var melalui elemen type yang dimiliki var saat ini. Pengecekan ini dilakukan melalui sebuah peryataan menggunakan switch. Jika anda masih kesulitan memahami penggunaan union, hal itu tidak terlalu mengganggu, sebab feature ini jarang digunakan, tujuan penulis hanya ingin memperkenalkan union kepada anda :).

Lebih Lanjut Tentang FungsiPada bagian ini kita akan membahas penggunaan fungsi dengan memanfaatkan pointer. Nama sebuah fungsi pada dasarnya adalah pointer ke alamat fungsi tersebut. Untuk mempermudah pemahaman konsep ini, marilah kita coba implementasi berikut:

46

Tutorial Pemrograman Bahasa C dan Assembly

#include < stdio.h > #define FALSE 0 #define TRUE 1 void tukar(int * pX, int * pY); void kuadrat(int * pX, int * pY); void reset(int * pX, int * pY); void main() { int keluar = FALSE; int inrange = TRUE; int var1; int var2; int pilihan; void (*ptr_func) (int * , int *); while (keluar != TRUE) { if (keluar == TRUE) break; printf("variabel pertama : "); scanf("%d", &var1); printf("variabel pertama : "); scanf("%d", &var2); printf("Ketikkan fungsi yang anda pilih (1-3) "); scanf("%d", &pilihan); switch(pilihan) { case 1: ptr_func = tukar ; inrange = TRUE; break; case 2: ptr_func = kuadrat; inrange = TRUE; break; case 3: ptr_func = reset; inrange = TRUE; break; default:

47

Tutorial Pemrograman Bahasa C dan Assembly

printf("Fungsi yang anda pilih tidak didefinisikan\n"); inrange = FALSE; break; } if(inrange) { printf("Variabel 1 dan 2 sebelum fungsi di panggil: \n"); printf("Variabel 1: %d ; variabel 2: %d \n\n", var1, var2); ptr_func(&var1, &var2); printf("Variabel 1 dan 2 setelah fungsi di panggil: \n"); printf("Variabel 1: %d ; variabel 2: %d \n\n", var1, var2); } printf("Apakah anda ingin mengakhiri program (1 = ya, 0 = tidak) "); scanf("%d",&keluar); } } void tukar(int * pX, int * pY) { int temp; printf("tukar function called \n"); printf("Menukar variabel 1 dan variabel 2\n"); temp = *pX; *pX = *pY; *pY = temp; } void kuadrat(int * pX, int * pY) { printf("kuadrat function called \n"); *pX = (*pX)*(*pX); *pY = (*pY)*(*pY);

48

Tutorial Pemrograman Bahasa C dan Assembly

} void reset(int * pX, int * pY) { printf("reset function called \n"); *pX = 0; *pY = 0; }Contoh output program di atas adalah:

variabel pertama : 3 variabel pertama : 4 Ketikkan fungsi yang anda pilih (1-3) 1 Variabel 1 dan 2 sebelum fungsi di panggil: Variabel 1: 3 ; variabel 2: 4 tukar function called Menukar variabel 1 dan variabel 2 Variabel 1 dan 2 setelah fungsi di panggil: Variabel 1: 4 ; variabel 2: 3 Apakah anda ingin mengakhiri program (1 = ya, 0 = tidak) 1Peringatan ! Berhati-hatilah saat menjalankan program di atas, sebab program ini tidak bug free, masukkan input sesuai dengan rentang nilai yang diminta. Jika anda menyalahi aturan ini maka bisa saja muncul bug yang akan membuat komputer anda hang. Contoh di atas mengeksploitasi kemampuan bahasa C menggunakan pointer fungsi. Pointer fungsi seperti yang telah dijelaskan diatas, adalah pointer juga, tetapi alamat yang ditunjuk oleh pointer tersebut bukan alamat variabel, tetapi alamat sebuah fungsi. Format deklarasi sebuah pointer fungsi adalah:

return_type (*pointer_fungsi) (tipe_data1 , tipe_data2 ); return_typeadalah tipe data dari return value (nilai yang dikembalikan/ dihasilkan sebuah fungsi, anda masih ingat kan ?), pointer_fungsi adalah nama dari pointer fungsi yang diinginkan, tipe_data1 adalah tipe data dari parameter pertama fungsi yang ditunjuk oleh pointer fungsi yang kita deklarasikan, tipe_data2 adalah tipe data parameter kedua dari fungsi yang ditunjuk oleh pointer fungsi yang kita deklarasikan. Jumlah tipe data parameter yang digunakan bisa saja 0 atau bukan 2, tergantung dari format fungsi yang ditunjuk oleh pointer fungsi yang kita buat. Pointer fungsi dapat menunjuk ke

49

Tutorial Pemrograman Bahasa C dan Assembly

berbagai macam fungsi, asalkan fungsi-fungsi tersebut mempunyai return type dan tipe data parameter yang sama persis, seperti yang diperlihatkan pada contoh di atas, pointer ke fungsi yang diberi nama ptr_func menunjuk ke tiga macam fungsi yang berbeda, yaitu fungsi tukar, kuadrat dan reset, tergantung pilihan yang diketikkan user. Mungkin anda bertanya-tanya mengapa begitu banyak tanda kurung, hal ini dibutuhkan, sebab jika anda menuliskannya seperti ini

return_type * pointer_fungsi (tipe_data1 , tipe_data2 );maka and mendeklarasikan sebuah fungsi dengan return value bertipe pointer ke return_type, bukan pointer fungsi seperti yang kita inginkan. Anda juga mungkin bertanya, mengapa kita tidak perlu menggunakan indirection operator (*) untuk memanggil fungsi yang kita inginkan dari pointer-nya. Anda bisa saj melakukan- nya jika anda menginginkannya, seperti ini:

(*ptr_func)(&var1,&var2);namun hal ini tidak ada bedanya, sebab keduanya diperlakukan sama saja oleh bagi compiler C. Contoh di atas mungkin cukup sulit dipahami, anda perlu melihat kembali bagian pointer jika masih bingung dengan sintaks yang digunakan. Sintaks *pX = (*pX)*(*pX); maksudnya adalah kalikan nilai yang ditunjuk oleh pointer pX dengan nilainya sendiri, kemudian kopi nilai tersebut ke variabel itu sendiri (variabel yang ditunjuk oleh pX). Secara garis besar, program di atas mula-mula mengeset variabel keluar menjadi FALSE sehingga saat perulangan while(keluar != TRUE) mulai dieksekusi, program akan berulang-ulang menampilkan tampilan yang ada di dalam while, sampai user memilih untuk keluar (keluar == TRUE) , pada saat itu, perintah break yang ada di dalam looping while (di bawah if (keluar == TRUE)) akan dieksekusi, sehingga program keluar dari looping tersebut. Di dalam looping inilah terdapat penggunaan pointer fungsi. Obrolan bebas: "Sampai saat ini kita telah mempelajari banyak konsep yang sangat penting, mulai dari variabel 'biasa' sampai pointer fungsi, dan penulis yakin pasti cukup melelahkan dan membingungkan :( . Tapi anda tidak perlu merasa tertekan , karena konsep itu semua digunakan jika anda berniat menjadi programer yang hebat. Setelah memahami tutorial ini, mudah-mudahan anda tidak bermasalah lagi dengan apa yang disebut pemrograman C. Setelah membaca tutorial assembly nanti, pemahaman anda tentang bahasa C akan menjadi setingkat lebih tinggi sebab banyak hal, seperti pointer fungsi yang baru saja di bahas sebenarnya datang dari penggunaannya dalam pemrograman assembly. Mungkin anda juga penasaran, mengapa tutorial ini belum

50

Tutorial Pemrograman Bahasa C dan Assembly

juga masuk ke pemrograman windows. Anda perlu bersabar sebelum sampai ke sana, sebab hal itu adalah salah satu hal yang penulis sendiri rasakan sangat sulit dipahami jika konsep-konsep seperti yang dijelaskan di sini tidak dikuasai dengan baik. penulis sendiri butuh waktu 1 bulan untuk benar-benar memahami apa yang dilakukan setiap baris dari pemrograman dengan bahasa C pada windows dengan menggunakan windows API :( . So keep fighting till the end" Selanjutnya kita akan membahas sebuah program yang menggunakan pointer ke fungsi yang mirip dengan yang banyak digunakan dalam pemrograman windows.

#include < stdio.h > typedef unsigned int UINT; typedef UINT (*FPTR) (UINT var1, UINT var2); typedef struct Input { FPTR pFunc; UINT param1; UINT param2;} INPUT ; UINT kali(UINT faktor1, UINT faktor2) { return (faktor1 * faktor2); } void Hitung(INPUT input) { int hasil = input.pFunc(input.param1, input.param2); printf("Hasil perhitungan adalah: %d \n", hasil); } void main() { INPUT in; in.pFunc = kali; in.param1 = 10; in.param2 = 20; Hitung(in); }Output dari program di atas adalah:

Hasil perhitungan adalah: 200

51

Tutorial Pemrograman Bahasa C dan Assembly

Program di atas menggunakan beberapa sintaks yang "baru" misalnya sintaks untuk mempersingkat penulisan pointer fungsi, yaitu:

typedef UINT (*FPTR) (UINT var1, UINT var2);sintaks ini maksudnya adalah, FPTR adalah sebuah sintaks untuk menyatakan pointer fungsi yang return value-nya bertipe UINT dan memiliki dua parameter bertipe UINT. Selanjutnya dideklarasikan sebuah struct dengan nama Input dengan elemen-elemen: 1. 2. Sebuah variabel bertipe pointer ke fungsi (FPTR) Dua buah variabel bertipe UINT (unsigned int)

Kemudian dideklarasikan dua buah fungsi yaitu fungsi Kali dan Hitung. Fungsi Kali bekerja dengan mengalikan kedua parameter inputnya. Fungsi Hitung adalah sebuah fungsi yang menerima input berupa objek bertipe struct Input, mengalikan kedua elemen struct tersebut yang bertipe UINT dan kemudian menampilkan hasilnya. Pada fungsi main(), dibuat sebuah objek bertipe struct Input, kemudian objek tersebut dijadikan parameter untuk memanggil fungsi Hitung. Sampai di sini anda sudah memiliki sebagian besar skill yang dibutuhkan untuk melakukan pemrograman C pada Windows dengan menggunakan Windows API (Windows Application Programming Interface).

Arsitektur Driver Windows 2000/XPPenulis mohon maaf karena semestinya ada bagian pendahuluan yang menerangkan tentang Windows API (Application Programming Interface) sebelum masuk kepada bagian ini, tetapi akibat keterbatasan waktu maka bagian tersebut belum sempat disertakan. Anda tidak perlu khawatir sebab hal tersebut tidak akan mengurangi pemahaman anda terhadap source code driver yang dibahas. Dalam beberapa saat ke depan mudah-mudahan bagian tersebut dapat disertakan dalam tulisan ini setelah tulisan ini diupdate :). Berikut ini kita akan membahas source code driver pada artikel Tutorial Membuat Patch dalam bentuk Driver Pada Windows 2000/XP.

/* patch.c -- prototipe Patch project started 9 Feb 2003 by - Pinczakko */

52

Tutorial Pemrograman Bahasa C dan Assembly

#include "ntddk.h" #define #define #define #define #define FILE_DEVICE_UNKNOWN 0x00000022 PCI_ADDR_PORT 0xCF8 PCI_DATA_PORT 0xCFC REG_ADDR 0x80000050 MASK 0x00000040

void tUnloadDriver(PDRIVER_OBJECT DriverObject); NTSTATUS tDispatchCreate(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp); NTSTATUS tDispatchClose(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp); void PatchPCI(ULONG reg_addr, ULONG mask); NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) /*++ Routine Description: This routine is called when the driver is loaded by NT. Arguments: DriverObject - Pointer to driver object created by system. RegistryPath - Pointer to the name of the services node for this driver. Return Value: The function value is the final status from the initialization operation.

53

Tutorial Pemrograman Bahasa C dan Assembly

--*/ { NTSTATUS ntStatus; UNICODE_STRING uszDriverString; UNICODE_STRING uszDeviceString; PDEVICE_OBJECT pDeviceObject; // Point uszDriverString at the driver name RtlInitUnicodeString(&uszDriverString, L"\\Device\\Patch"); // Create and initialize device object ntStatus = IoCreateDevice(DriverObject, 0, &uszDriverString, FILE_DEVICE_UNKNOWN, 0, FALSE, &pDeviceObject); if(ntStatus != STATUS_SUCCESS) return ntStatus; // Point uszDeviceString at the device name RtlInitUnicodeString(&uszDeviceString, L"\\DosDevices\\Patch"); // Create symbolic link to the user-visible name ntStatus = IoCreateSymbolicLink(&uszDeviceString, &uszDriverString); if(ntStatus != STATUS_SUCCESS) { // Delete device object if not successful IoDeleteDevice(pDeviceObject); return ntStatus; }

//Patch the chipset on driver loading stage

54

Tutorial Pemrograman Bahasa C dan Assembly

PatchPCI(REG_ADDR ,MASK); // Load structure to point to IRP handlers... DriverObject->DriverUnload = tUnloadDriver; DriverObject->MajorFunction[IRP_MJ_CREATE] = tDispatchCreate; DriverObject->MajorFunction[IRP_MJ_CLOSE] = tDispatchClose; // Return success return ntStatus; }

NTSTATUS tDispatchCreate(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information=0; IoCompleteRequest(Irp, IO_NO_INCREMENT); return(STATUS_SUCCESS); }

NTSTATUS tDispatchClose(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information=0; IoCompleteRequest(Irp, IO_NO_INCREMENT); return(STATUS_SUCCESS); } void tUnloadDriver(PDRIVER_OBJECT DriverObject) { UNICODE_STRING uszDeviceString;

55

Tutorial Pemrograman Bahasa C dan Assembly

IoDeleteDevice(DriverObject->DeviceObject); RtlInitUnicodeString(&uszDeviceString, L"\\DosDevices\\Patch"); IoDeleteSymbolicLink(&uszDeviceString); } void PatchPCI(ULONG reg_addr, ULONG mask) { //Patch the chipset __asm { pushfd ;//save all flags and regs pushad mov eax,reg_addr ;//fetch the address of the regs to be patched mov dx,PCI_ADDR_PORT ;//fetch the input port addr of PCI cfg space out dx,eax mov dx,PCI_DATA_PORT in eax,dx or eax,mask ;//mask the regs value (activate certn. bits) out dx,eax popad ;//restore all flags popfd } }Warning ! Jangan mencoba menginstalasi driver yang dihasilkan oleh source code di atas pada sistem anda, karena dapat membuat sistem anda rusak. Pada program di atas, entry point program ada pada fungsi DriverEntry , fungsi ini dipanggil oleh Windows saat driver yang dihasilkan dari source code ini di-

56

Tutorial Pemrograman Bahasa C dan Assembly

load oleh windows saat start-up. Windows memanggil fungsi ini dengan menyertakan beberapa parameter, yaitu: sebuah struct dengan tipe PDRIVER_OBJECT dan sebuah struct dengan tipe PUNICODE_STRING. Keyword IN sebenarnya diabaikan oleh compiler saat source code ini di-compile. Keyword tersebut hanya digunakan untuk memperjelas saat kita membaca source code. Kita akan membahas Source code ini mulai dari bagian awal. Pada awal source code terdapat beberapa definisi konstanta, tentang arti definisi ini telah dijelaskan pada artikel Tutorial Membuat Patch dalam bentuk Driver Pada Windows 2000/XP. Namun, konstanta: FILE_DEVICE_UNKNOWN perlu dijelaskan di sini. Konstanta ini digunakan sebagai pengenal bagi driver yang kita buat, kita menggunakan FILE_DEVICE_UNKNOWN karena driver kita bukan merupakan driver spesifik untuk device tertentu yang telah dikenali secara internal oleh Windows, misalnya Hard drive atau mouse. Selanjutnya 3 buah fungsi minimum yang harus dimiliki oleh sebuah driver, nama dari fungsi-fungsi ini tidak harus seperti yang diperlihatkan pada source code di atas, sebab ketiga fungsi ini ditunjuk oleh pointer yang ada dalam fungsi DriverEntry , jadi jika anda merubah namanya, anda hanya perlu merubah nama pointer fungsi yang ada pada fungsi DriverEntry. Selanjutnya kita melangkah ke inti dari program ini yaitu fungsi DriverEntry. Fungsi ini sebenarnya adalah fungsi inisialisasi driver. Inisialisasi tersebut kita lakukan dengan cara melakukan apa yang kita inginkan pada fungsi ini, saat membuat patch, kita ingin mem-patch beberapa register, dan hal itulah yang kita lakukan pada source code ini dengan memanggil fungsi PatchPCI. Pada awal fungsi ini didefinisikan beberapa variabel, yaitu:

NTSTATUS ntStatus; UNICODE_STRING uszDriverString; UNICODE_STRING uszDeviceString; PDEVICE_OBJECT pDeviceObjectNTSTATUS adalah sebuah tipe data yang digunakan secara internal oleh Windows untuk menangani fungsi-fungsi yang berkaitan dengan driver, tipe data ini adalah tipe data 32 bit. Variabel ntStatus adalah variabel yang digunakan untuk mengecek apakah proses loading (inisialisasi) driver ini berhasil, jika berhasil maka variabel ini akan bernilai STATUS_SUCCESS setelah fungsi IoCreateDevice dipanggil. Sedangkan kedua variabel bertipe UNICODE_STRING digunakan untuk mengatur nama driver ini yang akan muncul di registry saat driver ini telah berhasil di-load. Variabel pDeviceObject adalah variabel yang digunakan untuk menyimpan pointer ke struct bertipe DEVICE_OBJECT yang dihasilkan setelah kita berhasil me-load driver kita (ntStatus bernilai STATUS_SUCCESS), atau dengan kata lain pointer ke driver kita. Fungsi IoCreateDevice adalah fungsi inti yang menginisialisasi driver ini. Fungsi ini digunakan untuk "membuat driver", cara kerjanya adalah sebagai berikut:

57

Tutorial Pemrograman Bahasa C dan Assembly

1.

Parameter pertama dari fungsi ini adalah "template driver" yang disediakan oleh windows bagi driver yang akan "dibuat". "template driver" ini disediakan windows dalam bentuk parameter pertama bagi fungsi DriverEntry yang kita buat (yang akan dipanggil oleh windows saat loading driver). "template driver" ini sebenarnya sebuah struct yang isinya pointer ke fungsi yang kita definisikan sendiri pada barisbaris program selanjutnya. "template driver" inilah yang kita modifikasi pada baris :

DriverObject->DriverUnload = tUnloadDriver; DriverObject->MajorFunction[IRP_MJ_CREATE] = tDispatchCreate; DriverObject->MajorFunction[IRP_MJ_CLOSE] = tDispatchClose;dengan source code ini, kita membuat "template driver" yang diberikan oleh windows menunjuk ke fungsi-fungsi yang kita inginkan. Fungsi tUnloadDriver dipanggil saat driver kita akan di-unload, saat windows akan shutdown, pada fungsi ini yang kita lakukan hanyalah cleanup memory dan resources yang digunakan oleh driver ini. Fungsi tDispatchCreate dipanggil pada saat ada software pada user mode yang membuka akses ke driver kita, karena kita tidak beururusan dengan software di user mode maka dalam driver ini, fungsi ini hanya mengembalikan nilai STATUS_SUCCESS. Fungsi tDispatchClose dipanggil pada saat ada software pada user mode yang menutup akses ke driver kita, karena kita tidak beururusan dengan software di user mode maka dalam driver ini, fungsi ini hanya mengembalikan nilai STATUS_SUCCESS 2. 3. 4. Parameter kedua adalah jumlah memory yang dibutuhkan oleh driver, namun kita tidak membutuhkan memory apapun untuk driver ini sehingga parameter tersebut 0. Parameter ke-3 adalah nama dari driver kita yang akan tampak oleh user mode application. Dalam kasus ini kita tidak perlu membahasnya karena tidak ada user mode application yang menggunakan driver kita. Parameter ke-4 adalah tipe device dari driver kita, karena driver ini tidak memiliki device maka digunakan FILE_DEVICE_UNKNOWN. Jika device yang kita buat adalah device yang dikenali windows, misalnya hard drive maka nilai variabel ini akan berbeda. Untuk penjelasan lebih lanjut silahkan membaca file help Windows 2000 Driver Development Kit. Parameter ke-5 adalah karakteristi device yang secara internal dikenali oleh windows, tetapi device kita tidak dikenali, sehingga harus diberi nilai 0. Parameter ke-6 adalah parameter yang berkaitan dengan kemampuan driver kita diakses dari usermode, jika dapat diakses oleh beberapa

5. 6.

58

Tutorial Pemrograman Bahasa C dan Assembly

7.

aplikasi pada saat bersamaan maka nilainya FALSE. Sebenarnya dalam kasus seperti ini, nilai parameter ini tidak punya pengaruh sebab kita tidak berurusan dengan software di user mode. Parameter terakhir adalah pointer ke driver yang dihasilkan oleh fungsi ini. Pointer ini adalah pointer ke struct DEVICE_OBJECT yang dihasilkan oleh fungsi ini. Struct ini adalah struct yang menyimpan karakteristik driver kita. Fungsi PatchPCI akan anda pahami setelah membaca bagian Tutorial Bahasa Assembly, jadi tidak akan dibahas di sini. Fungsi RtlInitUnicodeString digunakan untuk menyalin string, mirip fungsi strcpy pada pemrograman C yang telah kita pelajari. Dengan penjelasan ini semoga anda sudah memahami arsitektur driver windows 2000/XP secara umum.

Obrolan bebas: "Selamat. Sekarang anda telah memiliki sebagian besar kemampuan dasar yang dibutuhkan untuk menjadi seorang programer bahasa C yang profesional, yang anda butuhkan adalah berlatih membuat beberapa program agar anda semakin memahami bagaimana bahasa ini digunakan :). Bagian selanjutnya akan mengajari anda cara memprogram dengan bahasa assembly. Ada mitos yang mengatakan bahwa programming menggunakan asssembly sangat susah. Menurut penulis, mitos tersebut adalah keliru. Jika anda mau berusaha dan membuang jauhjauh mitos itu, maka belajar assembly akan lebih mudah dibandingkan belajar bahasa C, sebab sintaks yang harus kita pelajari lebih sedikit. Satusatunya hal yang akan menjadi tantangan adalah anda harus berusaha memahami arsitektur microprocessornya. Jadi... selamat berjuang and always have fun :)"

59

Tutorial Pemrograman Bahasa C dan Assembly

Pemrograman Bahasa AssemblyPemrograman bahasa assembly sangat erat kaitannya dengan hardware yang digunakan, oleh karena itu tutorial ini diawali dengan penjelasan tentang arsitektur x86. Kita tidak akan mempelajari pemrograman assembly yang rumit, yang akan kita pelajari hanya bagaimana cara membuat file *.com atau yang biasa disebut file flat binary.

Arsitektur Microprocessor x86Pada bagian ini kita akan membedah bagian-bagian Microprocessor x86 secara fungsional dan mempelajari bagaimana bagian-bagian tersebut bekerja.

Arsitektur DasarDalam sebuah arsitektur komputer ada filosofi tertentu yang digunakan, misalnya dalam hal menentukan apakah kita menggunakan bus yang sama untuk data dan kode program atau keduanya terpisah, dan lain-lain. Secara umum saat ini arsitektur komputer yang ada menggunakan salah satu dari dua "aliran" arsitektur komputer, yaitu Von Neumann Architecture (VNA) dan Harvard Architecture. Pada VNA, bus untuk data dan program adalah sama, sedangkan pada Harvard Architecture, bus untuk data dan program adalah berbeda. Keluarga x86 menggunakan menggunakan VNA yang secara skematik digambarkan sbb:

CPU pada gambar di atas adalah microprocessor yang kita gunakan, memory adalah RAM , dan I/O (Input/Output)devices adalah periferal lainnya, misalnya Video Card, Chipset dan lain-lain. Garis merah tebal pada gambar di atas menunjukkan system bus dari x86, sistem bus ini terbagi menjadi tiga komponen, yaitu data bus, address bus dan control bus. Dari gambar di atas dapat anda lihat bahwa periferal I/O termasuk chipset yang ada di motherboard kita menggunakan bus yang sama dengan bus memory untuk berhubungan dengan microprocessor (system bus). Teknik seperti ini biasa disebut memory

60

Tutorial Pemrograman Bahasa C dan Assembly

mapped I/O, sebab periferal I/O diakses seperti hal-nya kita mengakses memory. Barangkali anda bertanya-tanya, mengapa northbridge 'hilang' dari skema di atas, northbridge sebenarnya ada pada pertemuan garis merah tebal dari memory dan I/O devices , chip ini tidak digambarkan sebab chip tersebut "transparan" terhadap program yang kita buat. Maksudnya, chip tersebut tidak akan "mengubah" data apapun yang kita lewatkan melaluinya. Di bawah ini adalah yang skema yang lebih jelas tentang hubungan antara system bus dan microprocessor.

Pada skema di atas anda dapat melihat bahwa system bus terbagi menjadi tiga bagian, yaitu data bus yang digunakan untuk mengirim data dari/ke memory maupun I/O devices, address bus yang digunakan untuk menunjukkan alamat memory atau alamat I/O yang dituju/asal data yang dikirimkan/diterima tersebut tersebut, dan control bus yang digunakan untuk mengatur "status" dari data bus dan address bus, misalnya apakah saat ini kita sedang menulis data ke RAM atau ke I/O devices, apakah kita sedang membaca data dari RAM atau dari I/O devices, dan lain-lain. BIU pada skema di atas adalah Bus Interface Unit, yang mengatur komunikasi microprocessor dengan system bus. ALU adalah Arithmetic and Logic Unit yang berfungsi sebagai komponen untuk melakukan operasi aritmetika dan logika. Registers adalah register-register dari x86, yaitu tempat penyimpanan data sementara (semacam memori) dengan kecepatan sangat tinggi. Register inilah yang akan digunakan dalam pengolahan data di dalam microprocessor. Control Unit adalah komponen yang bertugas mengatur lalu lintas data antara ALU, BIU dan register-register yang ada di dalam microprocessor. Data di dalam sebuah x86 dapat direpresentasikan dalam bentuk byte (8 bit), word (16 bit) atau dword/double word (32 bit). Ketika mengeksekusi sebuah program, yang terjadi sebenarnya adalah pemindahan data dari memori utama (RAM) ke microprocessor x86, kemudian data ini diolah pada microprocessor dan kemudian di pindahkan ke bagian output yang kita inginkan, misalnya ke ram dari video card atau memori utama, dan lain-lain. Dalam bahasa assembly, kita akan lebih banyak berurusan dengan pengolahan data di dalam microprocessor. Pengolahan data ini melibatkan bagian microprocessor yang disebut register. Register sebenarnya merupakan tempat penyimpanan dan pengolahan data yang ukurannya sangat kecil, tetapi kecepatan akses-nya sangat tinggi. Operasi-operasi yang terjadi terhadap data yang ada pada register-register ini terjadi pada ALU kemudian hasilnya di tempatkan ke register, alamat memori atau alamat I/O, tergantung instruksi yang kita berikan. Jika data yang diolah bertipe "floating point", maka pengolahannya dapat menggunakan FPU (Floating Point Unit), namun hal ini juga tergantung instruksi bahasa assembly yang kita gunakan. Berikut ini adalah skema dari "isi"

61

Tutorial Pemrograman Bahasa C dan Assembly

sebuah microprocessor x86 generasi ke 6 (i686 yaitu PentiumPro, Pentium II, Celeron, Pentium III, Celeron II):

Pada gambar di atas, bagian kiri yang diberi nama ... register adalah isi dari microprosesor itu sendiri dan yang di sebelah kanan, yang diberi label address space adalah memori utama (RAM). Antara microprocessor dan memory utama (RAM) sebenarnya ada yang disebut Bus Interface Unit yang menghubungkan microprocessor dengan bus memory pada Northbridge dan ada pula Northbridge dari motherboard, namun kedua komponen tersebut "transparan" terhadap program assembly yang kita buat, maksudnya, instruksi yang kita buat akan dieksekusi sesuai dengan alamat-alamat yang kita "minta", jadi kita tidak perlu mengubah-ubah setting dari kedua komponen itu. Gambar di atas menampilkan arsitektur dari x86 secara fungsional. Agar lebih jelas, maka register-register yang ada di bagian kiri gambar di atas di tampilkan lebih rinci :

Dalam pemrograman assembly tingkat awal, kita hanya akan berurusan dengan register-register yang ada di sebelah kiri, sedangkan memori utama yang ada di sebelah kanan tidak terlalu berperan. Tutorial ini hanya akan berurusan dengan General Purpose Register (GPR), tidak akan berurusan dengan Segment Register, sebab hal tersebut telah diatur oleh Software Development Tool yang kita gunakan (Visual C++, Masm atau Nasm). Namun demikian akan tetap ada sedikit penjelasan tentang segment register. Di bawah ini adalah registerregister yang termasuk GPR:

Register-register di atas mempunyai fungsi khusus yang agak berbeda. Berikut ini ringkasannya. 1. Register EAX , berfungsi sebagai "akumulator" yaitu register utama yang digunakan untuk menyimpan data sementara yang akan diolah

62

Tutorial Pemrograman Bahasa C dan Assembly

pada ALU atau FPU, register ini juga digunakan sebagai tempat penyimpanan hasil pengolahan data tersebut. EAX dapat mengakses semua register lainnya dan merupakan register dengan akses tercepat, jadi sebisa mungkin kita meletakkan data pada register ini. 2. Register EBX, berfungsi sebagai pointer ke data di memory yang berada pada segmen yang sedang ditunjuk oleh register DS. 3. Register ECX, berfungsi sebagai "counter" (bilangan yang nilainya berubah saat sebuah operasi telah dilakukan) dalam operasi menggunakan string atau dalam perulangan. 4. Register EDX, berfungsi sebagai register untuk melakukan komunikasi data dengan alamat I/O. 5. Register ESI, berfungsi sebagai pointer ke data pada segmen yang sedang ditunjuk oleh register DS. Juga berfungsi sebagai pointer alamat sumber string dalam operasi string. 6. Register EDI, berfungsi sebagai pointer ke data pada segmen yang sedang ditunjuk oleh register ES. Juga berfungsi sebagai pointer ke alamat tujuan dalam operasi string. 7. Register ESP, berfungsi sebagai pointer ke stack (pada segmen yang sedang ditunjuk oleh register SS). 8. Register EBP, berfungsi sebagai pointer ke data yang ada pada stack(pada segmen yang sedang ditunjuk oleh register SS). 9. Register SS (Stack Segment), berfungsi sebagai register yang menyimpan alamat segment stack saat ini. Stack adalah bagian memori utama tempat penyimpanan data sementara jika sebuah fungsi dieksekusi atau sebuah interupsi sedang berlangsung atau pada saat sebuah prosedur dipanggil atau pada keadaan lain saat sebuah program dengan sengaja menyimpan datanya di stack. Biasanya stack berada pada bagian akhir memory (alamat terbesar) dan pointer ke stack (SP) dikurangi setiap kali ada data baru yang di simpan di stack, sesuai dengan ukuran data yang disimpan Register CS (Code Segment), berfungsi menyimpan alamat segmen tempat kode program yang akan dieksekusi di simpan. 10. Register DS (Data Segment), berfungsi menyimpan alamat segmen tempat data program yang akan dieksekusi di simpan. Selain fungsi khusus di atas, GPR juga dapat digunakan dalam operasi pengolahan data biasa yang terjadi antara GPR tersebut dengan ALU. Pada tutorial ini, yang akan digunakan secara intensif hanya 4 GPR yang pertama, yaitu EAX, EBX, ECX dan EDX.

Mode Kerja Microprocessor x86Microprocessor x86 sejak i386 mempunyai 3 macam mode operasi. Mode operasi adalah kondisi operasi Microprocessor tersebut yang menentukan fasilitas apa saja dari Microprocessor yang dapat diakses, misalnya instruksi apa

63

Tutorial Pemrograman Bahasa C dan Assembly

saja yang dapat digunakan, jumlah memory yang dapat diakses dan lain-lain. Mode operasi tersebut adalah (>= i386) :

1.

Real Address Mode atau Real Mode. Mode ini hanya digunakan pada saat komputer sedang boot atau saat tidak ada sistem operasi pada komputer yang kita miliki dan hanya bios yang dapat bekerja atau kita sedang menjalankan sistem operasi yang hanya dapat berjalan pada real mode misalnya DOS. Pada mode, tidak semua feature yang dimiliki Microprocessor x86 (>=i386) dapat digunakan. Pada mode ini akses ke hardware secara langsung bebas dilakukan, dan alamat memory yang menyimpan kode sistem operasi juga dapat ditulisi, sehingga sistem dapat dengan mudah crash. Pada mode ini, ukuran register yang digunakan secara default adalah 16 bit, misalnya AX, CX, BX , dan lain-lain. Protected Mode. Mode ini adalah mode yang digunakan saat komputer kita berada dalam sistem operasi protected mode, misalnya windows atau Linux. Pada mode ini seluruh feature x86 (>=i386) dapat digunakan, dan alamat kode program yang "berbahaya", misalnya sistem operasi tidak dapat diakses, selain itu akses langsung ke hardware tidak diperbolehkan kecuali program kita mendapat ijin dari sistem operasi, misalnya untuk program yang menggunakan device driver. Pada mode ini, ukuran register yang digunakan secara default adalah 32 bit, misalnya EAX, ECX, EBX , dan lain-lain System Management Mode. Mode ini adalah mode power management untuk penghematan daya.

2.

3.

Programer assembly konvensional biasanya menjalankan programnya pada real mode. Namun kita tidak perlu menjalankan komputer kita dengan sistem operasi real mode seperti DOS untuk mencoba program yang seharusnya berjalan pada real mode, sebab pada protected mode (misalnya saat anda sedang menjalankan windows 2000) ada yang disebut virtual-8086 mode, sehingga program yang anda buat "seakan-akan" berjalan pada real mode. Mode ini pada dasarnya adalah emulasi sehingga tidak dijamin semua instruksi dapat dijalankan sesuai dengan yang kita inginkan. Virtual-8086 mode dapat digunakan dengan mudah, yaitu anda tinggal membuat source code assembly kemudian buat executable dan jalankan pada console windows, program itu akan otomatis berjalan pada mode ini jika memang program tersebut memanfaatkan instruksi-instruksi real mode.

Arsitektur x86 ModernModern Microprocessor x86 modern seperti Athlon, Pentium III atau Pentium 4 memiliki arsitektur internal yang sedikit berbeda dengan apa yang dideskripsikan di sini sebab microprocessor tersebut sudah begitu kompleks dengan jumlah register yang jauh lebih banyak dan memanfaatkan apa yang

64

Tutorial Pemrograman Bahasa C dan Assembly

disebut superscalar operation, pada salah satu gambar di atas anda melihat ada yang di sebut execution unit, Pentium III mempunyai 3 execution unit semacam ini, Athlon juga mempunyai 3 execution unit seperti ini, jadai anda dapat membayangkan, jika ada 3 instruksi yang tidak saling berhubungan, maka ketiga instruksi tersebut dapat dieksekusi hanya dalam 1 cycle (cycle adalah clock yang dibutuhkan untuk menyelesaikan eksekusi 1 instruksi). Selain itu, pada Microprocessor modern terdapat floating point unit yang mempunyai sangat banyak register, misalnya untuk SSE (Streaming SIMD) terdapat 8 register floating point. Di luar itu semua masih banyak lagi fasilitas yang sebenarnya ada di dalam microprocessor tersebut namun sangat jarang atau bahkan tidak dimanfaatkan, misalnya: sejak microprocessor generasi i686 (PentiumPro), kemampuan mengalamatkan memori yang ada sebenarnya sudah mencapai 64 GB dengan menggunakan apa yang disebut Physical Address Extension (PAE), bukan 4 GB seperti yang saat ini sedang banyak diributkan orang, hanya saja sistem operasi yang benar-benar memberikan dukungan pada kemampuan ini hanya Linux dan Windows 2000 edisi tertentu. Namun kita tidak perlu khawatir bahwa program yang kita buat tidak akan berjalan pada "mesin-mesin" tersebut, sebab semuanya kompatibel ke belakang dengan instruksi yang dibuat untuk "mesin-mesin" generasi sebelumnya.

Sintak