Jumping into ARM assembly


That ANDS instruction will only set the Z (zero) flag, so you test that with either EQ or NE - NE if it is set, EQ if not.

FWIW, assuming you're writing those numbers out in the standard human readable form - with the largest bit first, on a little-endian system like the ARM, bit 0 is the right-most bit and you count left. So to be eye, if you ANDS by 0b1000 you're testing the fourth bit, bit 3. But provided you use the same number in both the ANDS and the ORR, it'll work whichever bit you set.
 
Do not use AND to work with a flag container.

AND will clear other bits as well and sets ZERO flag to EQ or NZ if the whole register is 0 or 1.

To manipulate/test bit(s) in a word use the following:
Code:
set:   ORR R9,#0b100   @ OR
flip:  EOR R9,#0b100   @ Exclusive OR, same a XOR on x86
clear: BIC R9,#0b100   @ BIt Clear
test:
TST R9,#0b100
  MOVEQ R0,#'0'
  MOVNE R0,#'1'
 
Last edited:
Ah yes, I forgot about TST. The ARM docs describe it as doing the same as an ANDS, but discarding the result, so that seems ideal. And I'd forgotten about BIC as well, thanks.

Edit: I'm not a particular fan of this two argument notation for mnemonics that take 3 arguments in the machine code. It seems it can make you tend to forget that you don't have to modify your inputs, and can output to a different register than you input to. I prefer to be explicit when I am modifying my inputs.
 
Here's a little example of flag checks :
Code:
  .text
  .align 2

  flag1=0b1000  @
  flag2=0b0100  @ set flags names and positions

  .global _start

_start:
  mov r3, #0xF  @
  mov r4, #0xF  @ set r3 and r4 to an arbitrary value

  mov r0, #0b1010 @ set the flag register

  mov r1, #flag1  @ a bit to test
  mov r2, #flag2  @ another one

  tst r0, r1
  moveq r3,#0
  movne r3,#1  @ this flag is set

  tst r0, r2
  moveq r4,#0  @ this flag is not set
  movne r4,#1

  mov r7, #1  @ exit
  svc 0  @ call the Linux kernel and exit
 
Yeah, that looks good. Not sure why you're setting r1 and r2 - you can use constant values with most mnemonics, including TST. Anything documented as having a flexible second operand can take an immediate value or a register value (optionally shifted). But it's a clear example as is, thanks for that.
 
But r1 and r2 aren't altered anywhere - are you thinking of r3 and r4?

The way I see it you would keep the definitions of flag1 and flag2, but eliminate the two mov's where you put them into registers, and just TST, r0, #flag1 the first time, and TST r0, #flag2 the second.
 
Oh I got it ! Thx indeed I overcomplicated things again...
Simpler code :

Code:
  .text
  .align 2

  flag1=0b1000  @
  flag2=0b0100  @ set flags names and positions

  .global _start

_start:
  mov r3, #0xF  @
  mov r4, #0xF  @ set r3 and r4 to an arbitrary value

  mov r0, #0b1010 @ set the flag register

  tst r0, #flag1
  moveq r3,#0
  movne r3,#1  @ this flag is set

  tst r0, #flag2
  moveq r4,#0  @ this flag is not set
  movne r4,#1

  mov r7, #1  @ exit
  svc 0  @ call the Linux kernel and exit

I guess I now have to initialize the display then draw a pixel.
Before any code, any explanation about how this works ?
Notaz told me a while ago that the screen is considered part of memory adress.
 
Last edited:
Here is a short piece of code that draws two pixels on the screen. I added comments and a new macro 'open' that should be added to macro.mac

Code:
.macro open ffname,ffmode
mov r0,pc          @ PC points 2 instructions ahead, to the file name
b 999f              @                                              |
.asciz "\ffname"    @ <<< here ------------------------------------/
.align
999:
mov r1,#\ffmode    @ set file mode attributes
  invoke sys_open   @ and open it
.endm

Code:
.include "macro.mac"
.globl _start

@ define screen size.
@ 800 x 480, 16BPP.
screensize=480*800*2  

.text
_start:
bl fbinit
@ fbinit will return 0 in R0 if some error occured or
@ the frame buffer memory address if all went fine

cmp r0,#0
bne fbok

print "unable to open/mmap fbdev\n"
exit 255

fbok:

@ draw two white pixels on screen if everything was okay
mvn r1,#0
str r1,[r0]

@ munmap the memory block and close the file
bl fbclose

exit 0

@ initialize framebuffer device and mmap to a memory address
fbinit:
push {r1-r5,lr}

@ open /dev/fb0 device for read/write
open "/dev/fb0", o_rdwr

@ store frame buffer file descriptor in R4
subs r4,r0,#0

clr r0
@ return 0 if the device was not opened.
bmi fbinitret

@ we use mmap2, defined in sys/mman.h
@void *mmap2(void *addr, size_t length,
@   int prot, int flags, int fd, off_t pgoffset);
@ this is EABI calling convention that uses R4 and R5 as well.
@ OABI needs R4 and R5 pushed on stack.
ldr r1,=screensize
mov r2,#prot_read | prot_write    @ defined in macro.mac
mov r3,#map_shared                @ defined in macro.mac
@ r4=fd
@ r0=0
mov r5,r0
  invoke sys_mmap2

@ load offset of fbp and fbfd
ldr r1,=fbp

@ it gives back 0 if the syscall was not successfull
@ and we use R0=0 for return error code.
cmp r0,#0
beq fbinitret

@ store the frame buffer address to "fbp"
@ and the file handle to "fbfd" in .data section for later use
str r0,[r1]
str r4,[r1,#4]

fbinitret:
pop {r1-r5,pc}

fbclose:
push {r1,r2,lr}

@ load offset of fbp and fbfd
ldr r2,=fbp

@ fisrt, munmap the memory block
ldr r0,[r2]
ldr r1,=screensize
@ arg1=memory block address
@ arg2=length
  invoke sys_munmap

@ then close frambuffer file handle
ldr r0,[r2,#4]
  invoke sys_close

pop {r1,r2,pc}

.data

fbp:  .word 0
fbfd: .word 0

.bss
.end
 
Last edited:
Stopped a bit as I'm out of time these days.
Anyway there's something I don't get: fbp and fbfd.
I tried to mix keyboard and screen handling but when I try to write again on screen, loading back fbd and write, nothing is drawn.
 
With a tremendous effort of will, I'm jumping back into ASM.
Within an infinite loop, I'm reworking the little examples to refresh my memory.

I would like to do a counter with steps of four.
The idea is to load the binary 11101110111011101110111011101110 in a register, then do a ROR.
After 4 ROR, the LSB is 0 so i can compare it and branch if 0=0.

So a few questions here :
1- isn't there a better "trick" ?
2- how to compare the LSB ?
3- how to "create" this binary, because it can't be loaded as immediate. I thought about BFC/BFI, but I wonder if there's something neater.
 
1- isn't there a better "trick" ?

Better how? Faster? Less code? It really depends on everything else you're doing and what processor you're running it on. The way you're proposing is not very intuitive but there are circumstances where it could be a good idea.

2- how to compare the LSB ?

If you perform a rotate with flags set the carry flag will reflect the bit that was last shifted out. So you could do:

Code:
movs r0, r0, ror #1
bcs loop_start

You just have to be aware that the carry flag would reflect what the LSB was before the rotation, not after. So you'd want to structure your code/constant accordingly.

3- how to "create" this binary, because it can't be loaded as immediate. I thought about BFC/BFI, but I wonder if there's something neater.

On ARMv6 and higher a pair of movw and movt is the most straightforward way to create an arbitrary 32-bit value:

Code:
movw r0, #0xEEEE
movt r0, #0xEEEE

Since the top and bottom half are the same you could do other things to duplicate it but it's not really worth bothering with.
 
1- well, less lines/more easy to understand as it's for tutorial purposes. That's the first way that came to my mind (except a count 3->0 ->branch/do something and reset the counter to 3, but it's longer to write I think).
Could you share what comes to yours ?

2-3- thanks, I'll use that.
 
I'd tend to think doing it with nested loops might be clearer to explain, but what you've got there is a pretty neat hack.

Lets see, to do it using a nested loop you'd do:
.outer_loop
MOV Rloop_counter, #4 (may be #3, I forget how SUBS actually sets flags)
.inner_loop
do_stuf
SUBS Rloop_counter, #1
BNE inner_loop
B outer_loop

That's four by my count, which I think is one less than Exo's solution, but in my solution you need to reset the counter every four goes, while Exo's only has a maximum of three instructions inside the outer loop, so should run faster than mine. You could also add to a number starting at 0 and branch out whenever the bottom two bits are 0, but you would need to do an explicit ANDS test to test that, so that'd be slower too. Congrats, looks like you an Exo came up with a pretty optimal solution between you from the get-go!
 
Here's a new version of the binary display. It now add commas between 4bits groups.
0000,0000,0000,0000,0000,0001,1111,1111

I'm rewriting every tutos to use minimal external macro.mac files. I'll put them on the wiki soon.
Any comment welcome.

binary2.s :
Code:
.text
.align 2
.include "macro.mac"

.global _start

_start:
  mov r2, #1       @ buffer length, to write one character, or a newline
  ldr r3, =511       @ set the number
  mov r4, #1       @ load a 0b1 mask to isolate bit segments
  mov r5, #32       @ number of rotations
  movw r6, #0x8888   @ put 1000100010001000 in lower 16 bit @ a 1 will display a comma
  movt r6, #0x0888   @ put 0000100010001000 in upper 16 bit @ between each 4 bits group

_loop:
  ror r3, #31       @ rotate left the value
  ands r1, r3, r4   @ and "mask" on the higher bit segment, and so on
  ldreq r1, =asciizero
  ldrne r1, =asciione
  syscall sys_write

  movs r6, r6, ror #1   @ rotate the comma mask
  bcc _nocomma       @ if the old lsb was 1, then display a comma
  ldr r1, =asciicomma
  syscall sys_write

_nocomma:
  subs r5, #1       @ decrease the rotation counter
  bne _loop

_end:           @ the binary number is now assembled
  ldr r1, =asciinewline   @ point on the buffer to write, \n, a newline
  syscall sys_write

  syscall sys_exit

.data
  asciizero:   .ascii "0"
  asciione:   .ascii "1"
  asciicomma:   .ascii ","
  asciinewline:   .ascii "\n"  @ \n is a newline

macro.mac
Code:
sys_exit   =1
sys_write   =4
 stdout   =1
 
.macro syscall iint
 mov r7,#\iint
 swi 0
.endm
 
That 'ror' instruction is a new one on me - I guess it's an assembler macro for mov r#, r# ror # or something. I'm surprised there isn't an 'rol' macro or instruction either way - you can certainly do mov r#, r# rol # iirc.

Other than that it all looks reasonable to me. There are probably places it can be optimised, but I personally doubt it can be done without making the code harder to understand.
 
Back
Top