[置顶] 泰晓 RISC-V 实验箱,配套 30+ 讲嵌入式 Linux 系统开发公开课
MIPS / Linux 汇编语言编程实例
By Falcon of TinyLab.org 2009-01-18
Hello, MIPS Assembly Programmer!
Hello, MIPS Assembly 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) or a MIPS emulator, such as qemu, gxemul, SPIM and so forth, or even a cross compiler with qemu-user-static.
To install MIPSel debian on Qemu, please read Debian on an emulated MIPS(EL) machine or if want to use the method about qemu-user-static, please take a look at Linux Assembly Language Quick Start.
Some of the examples are on tested in a MIPS/Linux system on qemu, so, they may not work with qemu-user-static, but they should be available in the Linux on a real MIPS machine.
Now, Let’s say hello to the MIPS Assembly Language programming world:
# File: hello.s -- Say Hello to MIPS Assembly Language Programmer
#
# Author: falcon <wuzhangjin@gmail.com>, 2009/01/17
#
# Ref:
# * http://www.tldp.org/HOWTO/Assembly-HOWTO/mips.html
# * MIPS Assembly Language Programmer's Guide
# * See MIPS Run Linux(second version)
# Compile:
# $ sudo apt-get install gcc-4.3-mipsel-linux-gnu qemu-user-static
# $ mipsel-linux-gnu-gcc hello.s -static
# $ ./a.out
.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 MIPS/Linux system call usage standalonely in the last section.
Operate hardware
Operate 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 take a look at the back of the load instructions as following (This example is on a real MIPS machine).
$ echo $MACHTYPE // big endian, just like x86
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
And of course, there are some store instructions for storing data to the
memory, such as sw
, sh
, sb
:
# store.s -- swap data in the memory address: x & y
.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
And the debug information on a real MIPS machine:
$ gcc -o store store.s -g
$ 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
Operate 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.
# move.s -- swap data in two registers with 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
Pseudo instructions are also defined in the MIPS standard, which can be used by the assembly programmer and translated into the 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.
# replace.s -- replace the low byte of $t0 by the low byte of $t1, leaving $t0
# 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
Calculation
Up to now, we just play with the hardwares, in reality, we need to do some real things: calculation
Logical calculation
At first, let’s learn how to do some logical operations 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.
# xor.s -- swap two number in two registers, $t0 and $t1
.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
Arithmetical calculation
Now, it’s time to introduce the arithmatical calculation, which include
+
,-
,*
,/
, concretely, add
,addu
,addi
,addiu
,sub
,subu
,mulo
,mul
,div
,divu
, and
of course, the other related 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.
# calc.s -- a not complex arithmetical operations
#
# (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)
Flow control
MIPS provides two methods to change the flow control, one is the unconditionally, another is depending on the specified condition, e.g. equality of two registers.
fib.s:
# fib.s -- compute the fibonacii numbers...
#
# $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:
# 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
Memory addressing modes: access consecutive ranges of memory addresses
Addressing modes include immediate address, IP related address, direct, indirect & indexed addressing.
Mode | Example | MIPS instruction(s) | Remark [Address] |
---|---|---|---|
immediate | andi $t0, $t0, 0x03 | 16-bit constant embedded in instruction | |
IP related | 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.
# copy.s -- Copying byte sequences via lb/sb is inefficient on von-Neumann machines
.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:
# copy_lsb.s -- copy a sequence of n bytes from address src to address dst
.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.
# bub.s -- Bubble sort is a simple sorting algorithm
.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
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:
# avr.s -- 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, the call
instruction save the next
address in the stack, and accordingly, 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 | |
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 | /\ |
ok, let’s implement the binary search algorithm in MIPS Assembly Language.
# rec.s -- Recursive binary search
.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
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.
# syscall.s -- using 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
猜你喜欢:
- 我要投稿:发表原创技术文章,收获福利、挚友与行业影响力
- 知识星球:独家 Linux 实战经验与技巧,订阅「Linux知识星球」
- 视频频道:泰晓学院,B 站,发布各类 Linux 视频课
- 开源小店:欢迎光临泰晓科技自营店,购物支持泰晓原创
- 技术交流:Linux 用户技术交流微信群,联系微信号:tinylab
支付宝打赏 ¥9.68元 | 微信打赏 ¥9.68元 | |
请作者喝杯咖啡吧 |