MOV dan bentuk operan

Tiga bentuk operan MOV, yaitu immediate, register, dan memori, beserta batasan ukurannya.

Daftar isi

Topik ini membahas MOV dari sisi operannya, yaitu dari mana data diambil dan ke mana data ditulis. Ketiga bentuk operan yang diterima MOV diperkenalkan satu per satu, kemudian digabungkan dalam satu program kecil yang diperiksa lewat gdb. Batasan ukuran operan, larangan pemindahan memori ke memori, dan perlakuan khusus immediate 64-bit dibuktikan dengan keluaran asli assembler dan debugger.

Tiga bentuk operan#

MOV menyalin data dari satu tempat ke tempat lain tanpa mengubah nilainya, dan inilah instruksi yang paling sering muncul dalam kode assembly. Bentuk umumnya adalah mov tujuan, sumber menurut sintaks Intel yang digunakan NASM, sehingga arah perpindahan dibaca dari kiri ke kanan. Tujuan dan sumber hanya boleh berasal dari tiga jenis tempat, dan setiap instruksi MOV paling banyak memuat satu operan memori.

Immediate adalah konstanta yang tertanam langsung di dalam kode instruksi, misalnya angka 42 pada mov rax, 42. Operan register menyalin data antar register, misalnya mov rbx, rax, dan tidak menyentuh memori sama sekali. Operan memori ditulis dalam tanda kurung siku seperti mov [hasil], rbx, dan alamatnya dihitung pada saat instruksi dijalankan.

BentukContohPerpindahan
immediatemov rax, 42konstanta ke register
registermov rbx, raxregister ke register
memorimov [hasil], rbxregister ke memori

Ketiga bentuk dapat dicampur dengan bebas selama aturan satu memori dipatuhi. Konstanta dapat ditulis ke register maupun ke memori, register dapat disalin ke register lain maupun ke memori, tetapi memori hanya boleh menjadi tujuan atau sumber tunggal pada satu instruksi. Dengan demikian, mov [a], 5 dan mov [a], rbx adalah bentuk yang sah, sedangkan bentuk yang menyebut dua alamat memori sekaligus tidak pernah ada pada MOV.

Ketiga bentuk dalam satu program#

Program berikut memindahkan satu nilai melalui ketiga bentuk operan secara berurutan. Nilai 42 dimuat dari immediate ke rax, disalin ke rbx, lalu disimpan ke label hasil pada section .data. gdb menghentikan program setelah ketiga MOV selesai dan membaca isi memori pada alamat hasil.

nasm operan-mov.asm
default rel

section .data
hasil:  dq 0

section .text
global _start

_start:
    mov  rax, 42         ; immediate ke register
    mov  rbx, rax        ; register ke register
    mov  [hasil], rbx    ; register ke memori
    mov  rdi, rbx
    mov  eax, 60
    syscall
bash
# rakit dan tautkan program
nasm -f elf64 operan-mov.asm -o operan-mov.o
ld operan-mov.o -o operan-mov

# isi memori pada label hasil setelah ketiga MOV dijalankan
gdb -batch -ex 'break _start' -ex 'run' -ex 'stepi 3' -ex 'x/1gx &hasil' ./operan-mov
text Keluaran
Breakpoint 1 at 0x401000

Breakpoint 1, 0x0000000000401000 in _start ()
0x000000000040100f in _start ()
0x402000:	0x000000000000002a

Keluaran menampilkan satu baris berupa alamat 0x402000 beserta isinya, yaitu 0x2a atau 42 dalam heksadesimal. Alamat tersebut tetap sama karena executable statik yang dihasilkan ld menempatkan section .data pada posisi yang sama setiap kali program dijalankan. Penanda x/1gx membaca 8 byte sekaligus, sehingga seluruh isi hasil terlihat dalam satu baris keluaran. Tiga perpindahan tersebut melibatkan dua register dan satu slot memori tanpa mengubah nilai di sepanjang jalurnya. Dua instruksi terakhir pada program hanya menyiapkan kode keluar, sehingga perhatian tetap tertuju pada tiga MOV pertama.

Larangan memori ke memori#

MOV tidak menyediakan bentuk yang menyalin langsung dari memori ke memori. Batasan ini berasal dari encoding x86, yang hanya menyediakan tempat bagi satu operan memori pada satu instruksi MOV, sehingga assembler apa pun akan menolak permintaan yang sama. Permintaan yang melanggar aturan itu ditolak NASM dengan pesan error, sebagaimana ditunjukkan rangkaian perintah berikut.

bash
# NASM menolak MOV dengan dua operan memori
cat > dua-memori.asm <<'EOF'
section .data
a: dq 1
b: dq 2

section .text
global _start

_start:
    mov [a], [b]
EOF

nasm -f elf64 dua-memori.asm -o dua-memori.o
echo "exit=$?"
text Keluaran
dua-memori.asm:9: error: invalid combination of opcode and operands
exit=1

Pesan error menyebut baris 9 sebagai lokasinya dan kombinasi opcode serta operan yang tidak sah sebagai penyebabnya, sedangkan kode keluar 1 menandakan perakitan gagal total. Tidak ada berkas objek yang dihasilkan, sehingga langkah penautan tidak mungkin dilanjutkan. Jalur yang benar menyalin lewat register sebagai perantara, sebagaimana pola berikut.

nasm Pola perantara register untuk salinan memori ke memori
mov  rax, [sumber]
mov  [tujuan], rax

Biaya tambahannya adalah satu instruksi, dan pola inilah yang digunakan di mana saja ketika data harus berpindah antar alamat memori. Ukuran salinannya mengikuti nama register perantara, sehingga penyalinan 8 byte menggunakan rax seperti pada pola di atas. Urutan muat dari memori, mengolah, lalu menyimpan kembali juga menjadi kerangka dasar hampir semua operasi memori pada x86.

Ukuran operan#

Ukuran perpindahan mengikuti nama register yang digunakan. Menulis ke rax berarti memindahkan 8 byte, ke eax 4 byte, ke ax 2 byte, dan ke al 1 byte. Apabila tidak ada register yang menyatakan ukuran, misalnya ketika konstanta ditulis langsung ke memori, ukurannya harus dinyatakan secara eksplisit dengan penanda ukuran.

nasm Penanda ukuran pada operan memori
mov  byte  [hasil], 5     ; 1 byte
mov  word  [hasil], 5     ; 2 byte
mov  dword [hasil], 5     ; 4 byte
mov  qword [hasil], 5     ; 8 byte

Tanpa penanda, NASM menolak instruksi itu dengan pesan operation size not specified, karena tanda kurung siku saja tidak menyatakan berapa byte yang ditulis. Penanda ukuran juga berguna ketika operan memori berpasangan dengan register, misalnya untuk membatasi penulisan ke satu byte dari alamat yang sama. Penanda yang dipilih harus sesuai maksud program, karena menulis byte pada slot 8 byte yang dituju meninggalkan tujuh byte tidak tersentuh.

Immediate 64-bit dan movabs#

Immediate pada kebanyakan encoding MOV dibatasi hingga 32 bit. Pada register 64 bit, nilai 32 bit itu diperluas tanda sehingga nilai kecil maupun negatif tetap didukung, sedangkan nilai yang tidak muat dalam 32 bit memerlukan encoding khusus bernama movabs. NASM memilih encoding itu secara otomatis setiap kali immediate melebihi 32 bit, sehingga kode sumber tetap menulis MOV seperti biasa. Nilai yang muat dalam 32 bit, termasuk nilai negatif seperti -1, cukup dikodekan pendek karena diperluas tanda pada saat dijalankan.

Program berikut memuat konstanta 64-bit ke rax, lalu nilai akhirnya diperiksa lewat gdb. Perintah objdump di akhir memperlihatkan encoding yang benar-benar dipilih NASM.

nasm immediate-64.asm
default rel

section .text
global _start

_start:
    mov  rax, 0x1122334455667788  ; immediate 64-bit, dikodekan sebagai movabs
    mov  edi, 0
    mov  eax, 60
    syscall
bash
# rakit dan tautkan program
nasm -f elf64 immediate-64.asm -o immediate-64.o
ld immediate-64.o -o immediate-64

# nilai penuh rax setelah MOV immediate 64-bit
gdb -batch -ex 'break _start' -ex 'run' -ex 'stepi' -ex 'info registers rax' ./immediate-64

# bentuk encoding yang dipilih NASM dapat dilihat lewat objdump
objdump -d -M intel immediate-64.o
text Keluaran
Breakpoint 1 at 0x401000

Breakpoint 1, 0x0000000000401000 in _start ()
0x000000000040100a in _start ()
rax            0x1122334455667788  1234605616436508552

immediate-64.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <_start>:
   0:	48 b8 88 77 66 55 44 	movabs rax,0x1122334455667788
   7:	33 22 11 
   a:	bf 00 00 00 00       	mov    edi,0x0
   f:	b8 3c 00 00 00       	mov    eax,0x3c
  14:	0f 05                	syscall

Nilai rax terbaca 0x1122334455667788 utuh beserta representasi desimalnya. objdump menunjukkan instruksi pertama dikodekan sebagai movabs dengan panjang 10 byte, karena konstanta 8 byte tertanam utuh di dalam instruksi tersebut, sedangkan mov eax, 0x3c pada baris berikutnya hanya membutuhkan 5 byte. Konstanta sebesar ini mahal pada ukuran kode, sehingga compiler menghindarinya bila nilainya masih dapat dipecah ke instruksi yang lebih pendek.

Kesalahan umum#

Lupa penanda ukuran saat menulis konstanta ke memori. Instruksi mov [hasil], 5 ditolak NASM dengan pesan operation size not specified, karena tanda kurung siku tidak menyatakan berapa byte yang dimaksud. Tambahkan penanda ukuran seperti pada mov qword [hasil], 5, dan instruksi yang sama langsung dirakit.

Membalik arah operan. Sintaks Intel menempatkan tujuan di kiri, sehingga mov rax, rbx menyalin rbx ke rax dan bukan sebaliknya. Kesalahan ini lazim pada pembaca yang baru pindah dari sintaks AT&T yang menempatkan sumber di kiri, dan gejalanya adalah dua register yang nilainya tertukar saat debug.

Memercayai immediate 64-bit yang ditulis langsung ke memori. Perakitan berhasil karena NASM hanya memberi warning, tetapi nilainya telah dipotong menjadi 32 bit sebelum disimpan. Selalu muat konstanta 64-bit ke register terlebih dahulu, lalu simpan register itu ke tujuan memori.

Ringkasan#

  • MOV menerima tiga bentuk operan, yaitu immediate, register, dan memori, dengan tujuan selalu di sebelah kiri menurut sintaks Intel.
  • Perpindahan memori ke memori tidak disediakan dan harus dipecah menjadi dua MOV melalui register perantara.
  • Ukuran perpindahan mengikuti nama register, atau dinyatakan dengan byte, word, dword, dan qword ketika operan memori berdiri sendiri.
  • Immediate 64-bit memerlukan encoding movabs berukuran 10 byte yang dipilih NASM secara otomatis.

Referensi#