Practical MIPS Assembly Language Programming In Linux
falcon <wuzhangjin@gmail.com>
2009-01-18
1. "hello, MIPS Assembly Language Programmer!"
hello, MIPS Assembly Language Programmer, I'm also a newbie of MIPS Assembly
Programmer, and here is the practical & "step by step" examples of MIPS
Assembly Lanaguage Programming in Linux. which will give us a quick start of
MIPS Assembly Programming and safely say goodbye to the other boring materials.
at first, I will say "hello" to you in our first MIPS assembly language
program. and also to the world of MIPS :-)
but how to say? we should prepare the compiling & executing environment of MIPS
Assembly Language programs first of all. and which environment? a real MIPS
machine, such as FULOONG MINI machine(loongson 2e/2f inside). a MIPS emulator,
such as qemu, gxemul, SPIM and so forth.
however, if there is no real MIPS machine there, the best choice should be
running the frequently used operating system(GNU/Linux) on the most popular
MIPS emualator(qemu).
here will give no introduction about how to install Linux on qemu and how to
use qemu, any more relative knowledge, please get the information from my
blog(http://falcon.oss.lzu.edu.cn) or google them.
although these examples are experimented in a linux on qemu, they should also
be usable in the Linux on a real MIPS machines.
now, let's say hello to the MIPS Assembly Language programming world:
# File: helloworld.s -- Say Hello to MIPS Assembly Language Programmer
# Author: falcon <wuzhangjin@gmail.com>, 2009/01/17
# Ref:
# [*] [url]http://www.tldp.org/HOWTO/Assembly-HOWTO/mips.html[/url]
# [*] MIPS Assembly Language Programmer's Guide
# [*] See MIPS Run Linux(second version)
# compile:
# $ gcc -o hello hello.s
# or
# $ as -o hello.o hello.s
# $ ld -e main -o hello hello.o
.text
.globl main
main:
.set noreorder
.cpload $gp # setup the pointer to global data
.set reorder
# print sth. via sys_write
li $a0, 1 # print to standard ouput
la $a1, stradr # set the string address
lw $a2, strlen # set the string length
li $v0, 4004 # index of sys_write:
# __NR_write in /usr/include/asm/unistd.h
syscall # causes a system call trap.
# exit via sys_exit
move $a0, $0 # exit status as 0
li $v0, 4001 # index of sys_exit
# __NR_exit in /usr/include/asm/unistd.h
syscall
.rdata
stradr: .asciiz "hello, world!\n"
strlen: .word . - stradr # current address - the string address
in this demo, we showed how to use system calls provided by linux ,
including the sys_write and sys_exit. and also introduced that there is
a need to include the following instructions in the MIPS Assebmly
Language program in Linux.
.set noreorder
.cpload $gp
.set reorder
we will introduce the system call usage in Linux/MIPS standalonely in the last
section.
2. play with hardware
2.1 play with memory: load & store
there are some load instructions for loading data from memory to registers,
such as lw, lh, lb. if without the u postfix, using sign extension.
here is a demo, load.s.
# File: load.s -- load data(w/hw/b) from memory to a temp register
.text
.globl main
main:
.set noreorder
.cpload $gp
.set reorder
lw $t0, memory
lh $t1, memory
lb $t2, memory
lhu $t3, memory
lbu $t4, memory
.rdata
.align 4
memory:
.word 0xABCDE080
now, let's have a look at the back of the load instructions as following.
$ echo $MACHTYPE // big endian, just like x86
# store.s -- swap data in the memory address: x & y
$ gcc -o store store.s -g
# move.s -- swap data in two registers with move instruction
pseudo instructions are also defined in the MIPS standard, which can be
# replace.s -- replace the low byte of $t0 by the low byte of $t1, leaving $t0
# xor.s -- swap two number in two registers, $t0 and $t1
# calc.s -- a not complex arithmatical operation
# fib.s -- compute the fibonacii numbers...
# copy.s -- Copying byte sequences via lb/sb is inefficient on von-Neumann machines
# copy_lsb.s -- copy a sequence of n bytes from address src to address dst
# bub.s -- Bubble sort is a simple sorting algorithm
# avr.s -- compute the average of two numbers
# rec.s -- Recursive binary search
# syscall.s -- using system call in Linux/i386
mips-unknown-linux-gnu
$ gcc -g -o load load.s // compile with debugging info
$ gdb ./load // trace the excuting procedure with gdb command
GNU gdb 6.8-debian
...
This GDB was configured as "mips-linux-gnu"...
(gdb) break main
Breakpoint 1 at 0x400678: file load.s, line 8.
(gdb) r // start running & stop before the first instruction
Starting program: /root/load
Breakpoint 1, main () at load.s:11
11 lw $t0, memory
Current language: auto; currently asm
(gdb) p/x memory // hex value in the memory address
$1 = 0xabcde080
(gdb) p/x $t0 // before excuting any instruction
$2 = 0x2ac4c2e4
(gdb) s // execute the first instruction: lw $t0, memory
12 lh $t1, memory
(gdb) p/x $t0
$3 = 0xabcde080
(gdb) s
13 lb $t2, memory
(gdb) p/x $t1
$4 = 0xffffabcd
(gdb) s
15 lhu $t3, memory
(gdb) p/x $t2
$5 = 0xffffffab
(gdb) s
16 lbu $t4, memory
(gdb) p/x $t3
$6 = 0xabcd
(gdb) s
0x004006cc in main ()
(gdb) p/x $t4
$7 = 0xab
.text
.globl main
main:
.set noreorder
.cpload $gp
.set reorder
lw $t0, y
lw $t1, x
sw $t0, x
sw $t1, y
.data
x:
.word 0x000000FF
y:
.word 0xABCDE080
$ gdb ./store
...
(gdb) break main
Breakpoint 1 at 0x400678: file store.s, line 8.
(gdb) r
Starting program: /root/store
Breakpoint 1, main () at store.s:11
11 lw $t0, y
Current language: auto; currently asm
(gdb) p/x x
$1 = 0xff
(gdb) p/x y
$2 = 0xabcde080
(gdb) s
12 lw $t1, x
(gdb) s
13 sw $t0, x
(gdb) s
14 sw $t1, y
(gdb) s
0x004006b8 in main ()
(gdb) p/x x
$3 = 0xabcde080
(gdb) p/x y
$4 = 0xff
2.2 play with registers
MIPS can move data between registers directly via the move instruction, in
fact, move is a pseudo instruction which equal to the real MIPS instruction:
move r, s <==> or r, s, $0
or is a logical operation which will be introduced in the next section, here is
a demo of using move instruction.
.text
.globl main
main:
.set noreorder
.cpload $gp
.set reorder
lw $t0, x # init
lw $t1, y
# swap
move $t2, $t0 # t0 -> t2
move $t0, $t1 # t1 -> t0
move $t1, $t2 # t2(t0) -> t1
.rdata
x:
.word 0x000000ff
y:
.word 0xabcde080
used by the assembly programmer and translated into real MIPS instructions via
the assemblers. for examples:
not r, s <==> nor r, s, $0
move r, s <==> or r, s, $0
li r, c <==> ori r, $0, c
here is a demo of using pseudo instruction.
# otherwise intact via using bitmasks and logical instructions
.text
.globl main
main:
li $t0, 0x11223344
li $t1, 0x88776655
# paste the low byte of $t1 into the low byte of $t0
# ($t0 = 0x11223355)
and $t0, $t0, 0xffffff00
and $t1, $t1, 0xff
or $t0, $t0, $t1
3. calculation
up to now, we just play with the hardwares, in reality, we need to do some real
things: calculation
3.1 logical calculation
at first, let's learn how to do some logical operation in MIPS. there are lots
of logical operating instructions, such as and,andi, or, ori, nor, xor, xori,
not.
this demo will show how to swap two number in two registers via xor instruction.
.text
.globl main
main:
.set noreorder
.cpload $gp
.set reorder
lw $t0, x
lw $t1, y
xor $t0, $t0, $t1
xor $t1, $t0, $t1
xor $t0, $t0, $t1
.rdata
x: .word 0x000000ff
y: .word 0xabcde080
3.2 arithmatical calculation
now, it's time to introduce the arithmatical calculation, which include
+,-,*,/, corresponding to add,addu,addi,addiu,sub,subu,mulo,mul,div,divu, and
of course, the other relative operations, such as abs, neg, negu, rem, remu,
sll, sllv, srl, srlv, sra, srav, rol, ror. some of these instructions also be
classified into bit operating instructions or shift/rotate instructions.
# (x^2 + y^2)/(x^2 - y^2):
# 1. $t0 <- x^2, $t1 <- y^2
# 2. $t2 <- $t0 + $t1, $t3 <- $t0 - $t1
# 3. $t4 <- $t2 / $t3 (quotient)
# 4. $t5 <- $t2 / $t3 (remainder)
.text
.globl main
main:
.set noreorder
.cpload $gp
.set reorder
li $t6, 3 # x
li $t7, 2 # y
mul $t0, $t6, $t6 # x^2
mul $t1, $t7, $t7 # y^2
add $t2, $t0, $t1 # x^2 + y^2
sub $t3, $t0, $t1 # x^2 - y^2
# (x^2+y^2)/(x^2-y^2)
div $t5, $t2, $t3 # remainder(lo)
mfhi $t4 # quotient(hi)
4. flow control
MIPS provide two methods to change the flow control, one is the
unconditionally, another is depending on the specified condition, e.g.
equality of two registers.
# $a0, parameter n
# $v0, last Fibonacci number computed so far(and result)
# t0, second last Fibonacci number computed so far
# t1, temporary scratch register
.text
.globl main
main:
.set noreorder
.cpload $gp
.set reorder
li $a0, 1 # fib(n): paramter n
move $v0, $a0 # n < 2 => fib(n) = n
blt $a0, 2, done
li $t0, 0
li $v0, 1
fib: add $t1, $t0, $v0
move $t0, $v0
move $v0, $t1
sub $a0, $a0, 1
bgt $a0, 1, fib
done: sw $v0, result
.data
result: .word 0x11111111
# booth.s -- multiply two's complement numbers, equivalent functionality is provided by MIPS instruction mult
.text
.globl main
main:
.set noreorder
.cpload $gp
.set reorder
li $a0, -5 # parameter A
li $a1, 7 # parameter C
li $v0, 0 # R <- 0
li $t1, 0 # A(-1) <- 0
li $t0, 32 # i <- n (32 bits)
booth:
and $t2, $a0, 0x00000001 # $t2 <- A0
sll $t2, $t2, 1
or $t2, $t2, $t1 # $t2 = A0,A(-1)
beq $t2, 2, case10 # $t2 = 10?
beq $t2, 1, case01 # $t2 = 01?
b shift # $t2 = 00 or $t2 = 11
case10: sub $v0, $v0, $a1 # R <- R - C
b shift
case01: add $v0, $v0, $a1 # R <- R + C
shift:
and $t1, $a0, 0x00000001 # A(-1) <- A0
and $t2, $v0, 0x00000001 # save R0
sll $t2, $t2, 31
srl $a0, $a0, 1 # shift right A
or $a0, $a0, $t2 # A31 <- R0
sra $v0, $v0, 1 # arithmetic shift right R
sub $t0,$t0, 1 # i <- i - 1
bnez $t0, booth # i = 0?
# result in $v0,$a0
5. memory addressing modes: access consecutive ranges of memory addresses
addressing modes include immediate address, IP relative address, direct,
indirect & indexed addressing.
Mode Example MIPS instruction(s) Remark [Address]
immediate andi $t0, $t0, 0x03 16-bit constant embedded in instruction
IP relative beqz $t0, done signed 16-bit jump offset o embedded in instruction [IP + 4 × o]
direct lw $t0, 0x11223344 lui $at, 0x1122 [0x11223344]
lw $t0, 0x3344($at)
indirect lw $t0, ($t1) lw $t0, 0($t1) [$t1]
indexed lw $t0, 0x11223344($t1) lui $at, 0x1122 [0x11223344 + $t1]
addu $at, $at, $t1
lw $t0, 0x3344($at)
here is the very classical example of copy data from the first memory area to
another.
.text
.globl main
main:
.set noreorder
.cpload $gp
.set reorder
li $a0, 11 # length n of byte sequence
la $a1, src # source address
la $a2, dst # destination address
and $t1, $a0, 0x03
srl $t0, $a0, 2
copy: beqz $t0, rest
lw $t2, ($a1)
sw $t2, ($a2)
add $a1, $a1, 4
add $a2, $a2, 4
sub $t0, $t0, 1
b copy
rest: beqz $t1, done
lb $t2, ($a1)
sb $t2, ($a2)
add $a1, $a1, 1
add $a2, $a2, 1
sub $t1, $t1, 1
b rest
done:
.data
.align 4
src: .byte 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA
.align 4
dst: .space 11
another short but not efficient implementation method is like this:
.text
.globl main
main:
.set noreorder
.cpload $gp
.set reorder
li $t0, 5
copy:
lb $t1, src($t0)
sb $t1, dst($t0)
sub $t0, $t0, 1
bgez $t0, copy
.data
src: .byte 0x11, 0x22, 0x33, 0x44, 0x55, 0x66
dst: .space 6
now, let's see the buble sort algorithm implmented in MIPS Assembly Language.
.text
.globl main
main:
.set noreorder
.cpload $gp
.set reorder
li $a0, 10 # parameter n
sll $a0, $a0, 2 # number of bytes in array A
outer:
sub $t0, $a0, 8 # $t0: j-1
li $t1, 0 # no swap yet
inner:
lw $t2, A+4($t0) # $t2 <- A[j]
lw $t3, A($t0) # $t3 <- A[j-1]
bgt $t2, $t3, no_swap # A[j] <= A[j-1]?
sw $t2, A($t0) # A[j-1] <- $t2 \ move bubble
sw $t3, A+4($t0) # A[j] <- $t3 / $t2 upwards
li $t1, 1 # swap occurred
no_swap:
sub $t0, $t0, 4 # next array element
bgez $t0, inner # more?
bnez $t1, outer # did we swap?
.data
A: # array A (sorted in-place)
.word 4,5,6,7,8,9,10,2,1,3
6. procedures(sub-routine)
the best solution to resolve the complex problem is dividing the big problem to
several parts. in programming, the relative solution is procedure(sub-routines).
and in MIPS, there is an instruction jal (jump and link, $ra<-IP+4, IP<-a),
which jumps to the given address (a procedure entry point) and records the
correct return address in register $ra. and in the calle, there is only a need
to execute "j $ra"(IP<-$ra) to return to the correct address in the caller.
here is a very simple demo for compute the average of two numbers:
.text
.globl main
main:
.set noreorder
.cpload $gp
.set reorder
li $a0, 9
li $a1, 1
jal average
sw $v0, result
average:
add $v0, $a0, $a1
sra $v0, $v0, 1
j $ra
.data
result: .word 0
as the above demo shows, jal & j is not powerful enough like the call & ret in
x86. before jumping to the target address, call instruction save the next
address in the stack, and relatively, when returning, the ret instruction get
the next address in the top of the stack and jump to it. but here, jal & j use
the $ra to save the next address, so, if we want to use recursion, there is a
need to save & restore the $ra ourselves.
the basic solution is like this:
MIPS X86 pseudo code stack
jal proc call proc push done \|/
done: jmp to proc done
<sys_exit>
proc:
subu $sp, $sp, 4
sw $ra, 4($sp)
...
jal proc return
return:
lw $ra, 4($sp)
addu $sp, $sp, 4 pop done
j $ra ret jmp to done /|\
okay, let's implement the binary search algorithm in MIPS Assembly Language.
.data
first: # sorted array of 32 bit words
.word 2, 3, 8, 10, 16, 21, 35, 42, 43, 50, 64, 69
.word 70, 77, 82, 83, 84, 90, 96, 99, 100, 105, 111, 120
last: # address just after sorted array
.text
.globl main
main:
# binary search in sorted array
# input: search value (needle) in $a0
# base address of array in $a1
# last address of array in $a2
# output: address of needle in $v0 if found,
# 0 in $v0 otherwise
.set noreorder
.cpload $gp
.set reorder
li $a0, 42 # needle value
la $a1, first # address of first array entry
la $a2, last - 4 # address of last array entry
jal binsearch # perform binary search
li $v0, 4001
syscall
binsearch:
subu $sp, $sp, 4 # allocate 4 bytes on stack
sw $ra, 4($sp) # save return address on stack
subu $t0, $a2, $a1 # $t0 <- size of array
bnez $t0, search # if size > 0, continue search
move $v0, $a1 # address of only entry in array
lw $t0, ($v0) # load the entry
beq $a0, $t0, return # equal to needle value? yes => return
li $v0, 0 # no => needle not in array
b return # done, return
search:
sra $t0, $t0, 3 # compute offset of middle entry m:
sll $t0, $t0, 2 # $t0 <- ($t0 / 8) * 4
addu $v0, $a1, $t0 # compute address of middle entry m
lw $t0, ($v0) # $t0 <- middle entry m
beq $a0, $t0, return # m = needle? yes => return
blt $a0, $t0, go_left # needle less than m? yes =>
# search continues left of m
go_right:
addu $a1, $v0, 4 # search continues right of m
jal binsearch # recursive call
b return # done, return
go_left:
move $a2, $v0 # search continues left of m
jal binsearch # recursive call
return:
lw $ra, 4($sp) # recover return address from stack
addu $sp, $sp, 4 # release 4 bytes on stack
j $ra # return to caller
7. system call
the system calls usage in Linux/MIPS is something like in Linux/i386, we
use sys_write as an examples for showing the "likeness":
Linux/MIPS Linux/i386
li $a0, 1 movl $1, %ebx # arg1
la $a1, stradr movl $stradr, %ecx # arg2
lw $a2, strlen movl $strlen, %edx # arg3
li $v0, 4004 movl $4, %eax # syscall no.
# defined in /usr/include/asm/unistd*.h
syscall int $0x80 # activate the system call and
# enter into the kernel space
here is a complete demo for showing how to use system call in Linux/i386.
.text
.globl main
main:
movl $1, %ebx
movl $stradr, %ecx
movl $strlen, %edx
movl $4, %eax # __NR_write in /usr/include/asm/unistd_32.h
int $0x80
movl $0, %ebx
movl $1, %eax # __NR_exit in /usr/include/asm/unistd_32.h
int $0x80
.data
stradr: .ascii "hello, world!\n\r"
strlen: .word . - stradr
References
[1] rs-05.pdf
http://www.inf.uni-konstanz.de/dbis/teaching/ws0304/computing-systems/download/rs-05.pdf
[2] MIPS Assembly Language Programmer's Guide.pdf
http://www.cs.unibo.it/~solmi/teaching/arch_2002-2003/AssemblyLanguageProgDoc.pdf
NOTE!
this document is not engough for mips assembly programming, but just
some practical examples, and because I'm also a newbie, I'm not sure
the explanation is true, any suggestion & comment are welcome!
and of course, there are some store instructions for storing data to the
memory, such as sw, sh, sb.
ÍÆ¼öÒ»¸öÖÐÎÄ×ÊÁÏ£º
MIPS»ã±àÓïÑÔµÄÌØµã£¬
http://www.eepw.com.cn/article/70171.htm
×÷Õß falcon — 29 ÆßÔÂ 2009, 11:55
note!!!
it seems that if we want to use the $at register in gcc, we must use $1 instead, not the name at, and we should use ".set noat" & ".set at" wrapper.
.set .noat
sw $1 sp
.set .at
×÷Õß falcon — 29 ÆßÔÂ 2009, 11:55