Jumping into ARM assembly


How should I do ?

--EDIT : ok I moved that out !
 
Last edited by a moderator:
Here's the code, thanks to M-HT. Now I have to figure out how to display the real counter.

Code:
        .text
        .align 2

        .macro syswrite
                mov r0, #1              @ file descriptor 1 - stdout
                svc 0                   @ call the Linux kernel and print the text
                ldr r1, =newline        @ buffer to write, a newline
                mov r2, #2              @ buffer length
                mov r0, #1              @ file descriptor 1 - stdout
                svc 0                   @ call the Linux kernel and print the newline
        .endm

        .global _start
_start:

        mov r7, #4              @ system call number, 4 is 'write'
        mov r5, #5              @ set the counter to 5
_loop:

        ldr r1, =datacounter
        mov r2, #1      @ buffer length
        syswrite

        ldr r1, =datacounter
        ldr r2, [r1]
        sub r2, r2, #1
        str r2, [r1]

        ldr r1, =text   @ buffer to write
        mov r2, #11     @ buffer length
        syswrite

        subs r5, #1     @ decrement the counter
        bne _loop       @ if not zero, come back to _loop

        mov r7, #1      @ exit
        svc 0

        .data
text:                   .asciz "hello world!"
newline:                .asciz "\n"
datacounter:            .word 53        @ ascii code for the number 5
 
Last edited by a moderator:
Edit: Ah, interesting, I didn't know the system marked the data section as readonly.  Presumably that's an MMU hack that stops it being written to.

Edit2: To correct myself, it does need writing back, as that syswrite call uses a buffer in memory indexed by address, and doesn't write out a byte stored in a register.  I'd still be tempted to compute it by adding 48 to the counter, but only now because it saves you encoding the same data two ways.

Edit3: Looking at the code now, what's that addrdatacounter for? I can't see it referenced anywhere.
 
Last edited by a moderator:
To avoid editing my message yet again, I'll doublepost instead ;)

I see you're using .asciz directives to store text, but then not using the null terminated byte at the end for anything - you're wasting that byte.  Instead, use the .ascii directive to store it without a null byte at the end.

It might also be nicer if you didn't have to embed the length of the string in code.  You could do that I think by having a label immediately after the string, and computing the length at build time: 'mov R2, endtextlabel-textlabel' should do it I think (though I've not tested that yet - must get round to setting up a xcompile env here).  Then, if you change the text, it gets automatically updated when you assemble the code.

Alternatively you could store it as a null terminated .asciz string, then compute the length by iterating over subsequent addresses while counting, until you find the null byte.  But personally I wouldn't bother with null-terminated strings unless you're planning to interface with C code.
 
I'll have to meditate some weeks about your comments ^^.

Indeed, it's useless I'll remove it.
 
Edit2: To correct myself, it does need writing back, as that syswrite call uses a buffer in memory indexed by address, and doesn't write out a byte stored in a register.  I'd still be tempted to compute it by adding 48 to the counter, but only now because it saves you encoding the same data two ways.

I don't understand this _at all_ :/

Also, what is a null terminated byte ?
 
Last edited by a moderator:
Edit: Ah, interesting, I didn't know the system marked the data section as readonly.  Presumably that's an MMU hack that stops it being written to.

Code section is (by default) readable and executable, but not writable. Data section is (by default) readable and writable, but not executable.

Edit2: To correct myself, it does need writing back, as that syswrite call uses a buffer in memory indexed by address, and doesn't write out a byte stored in a register.  I'd still be tempted to compute it by adding 48 to the counter, but only now because it saves you encoding the same data two ways.

I don't understand this _at all_ :/

Also, what is a null terminated byte ?
At least a partial explanation - 48 is the numeric code of the character zero in ascii (49 = '1', ..., 57 = '9'). So if your counter has a value 0-9, by adding 48 you convert it to the ascii representation of the number, which you can print.

Null terminated byte - he meant null terminating byte (in null terminated string).
 
you can _start your code in the .data section. here is an example:

Code:
.include "macro.mac"

.globl _start
.data

_start:
 print "hello\n"

 mov r4,#5
1:
 adr r1,toprintout
 mov r0,#console
 mov r2,#2
  invoke sys_write

 ldrb r0,toprintout
 inc r0
 strb r0,toprintout

regloop r4,1b

 exit 0

toprintout: .ascii "0\n"
.align

.bss
.end
 
you can _start your code in the .data section. here is an example:

Really? Does that work? M-HT said what I know is true for x86...

Code section is (by default) readable and executable, but not writable. Data section is (by default) readable and writable, but not executable.
 
It works, but it's weird, because like I said before, the .data section is marked as readable and writable, but not executable.

I'm not sure it's safe to depend on this behavior.
 
I finally made my loop with display of the value.


It's limited to one digit, but np ^^ :


.text
.align 2

.macro syswrite @ print the content of r1, must be ascii, buffer length r2 must be set

mov r0, #1 @ file descriptor 1 - stdout
svc 0 @ call the Linux kernel and print the ascii text

ldr r1, =newline @ point on the buffer to write, \n, a newline
mov r2, #2 @ newline buffer length
mov r0, #1 @ file descriptor 1 - stdout
svc 0 @ call the Linux kernel and print the newline
.endm

.macro displaycounter
ldr r1, =asciicounter @ point on =asciicounter
str r5, [r1] @ store the counter value in =asciicounter
ldr r2, [r1] @ copy the value
add r2, #48 @ convert it to ascii by adding 48
str r2, [r1] @ store ascii in r1 to be displayed
mov r2, #1 @ buffer length
syswrite @ display counter state
.endm

.global _start
_start:

mov r7, #4 @ system call number, 4 is 'write'
mov r5, #9 @ set the counter to 9
_loop:

displaycounter @ display from 9 to 1

subs r5, #1 @ decrement the counter
bne _loop @ if not zero, come back to _loop

displaycounter @ display 0

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

.data
asciicounter: .word 0 @ must be initialized
newline: .asciz "\n" @ \n is a newline


Output :

Code:
$ ./hello
9
8
7
6
5
4
3
2
1
0
 
Last edited by a moderator:
That's looking a lot neater now, cool.


One question about your displaycounter macro - you seem to be saving r5 to RAM then loading it back into R2 before overwriting it with the value +48 before calling the syswrite macro, to actually use that value.  Why not just MOV R5 into R2 (MOV R2, R5) - or even better, just add 48 to R5 and store it in R2 (and then write that to the pointer in R1) - you can do that in one instruction (ADD R2, R5, #48) and eliminate three lines, cutting that macro down to 4 instructions and a syswrite.
 
Thanks !


Here's the corrected code to display a one-digit counter :


.text
.align 2

.macro syswrite @ print the content of r1, must be ascii, buffer length r2 must be set

mov r0, #1 @ file descriptor 1 - stdout
svc 0 @ call the Linux kernel and print the ascii text

ldr r1, =newline @ point on the buffer to write, \n, a newline
mov r2, #2 @ newline buffer length
mov r0, #1 @ file descriptor 1 - stdout
svc 0 @ call the Linux kernel and print the newline
.endm

.macro displaycounter
ldr r1, =asciicounter @ point on =asciicounter
add r2, r5, #48 @ convert it to ascii by adding 48
str r2, [r1] @ store ascii in r1 to be displayed
mov r2, #1 @ buffer length
syswrite @ display counter state
.endm

.global _start
_start:

mov r7, #4 @ system call number, 4 is 'write'
mov r5, #9 @ set the counter to 9
_loop:

displaycounter @ display from 9 to 1

subs r5, #1 @ decrement the counter
bne _loop @ if not zero, come back to _loop

displaycounter @ display 0

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

.data
asciicounter: .word 0 @ must be initialized
newline: .asciz "\n" @ \n is a newline





How to get the number of r5 digits ?
 
Last edited by a moderator:
I don't really understand why the asciicounter label is needed at all.   Seems to me that the real counter is being kept in r5, and the code should work the same even if the lines with the reference to asciicounter were removed.  Am I missing something?


-Neelix
 
You need the asciicounter because the syswrite call depends on R1 pointing to something in RAM that represents an ascii value.  You could only rely on that and iterate from 57 to 48, and in this example that might actually be slightly more efficient, but you'd still have to store something in memroy.  Hmm, let's see:


With separate register and ascii memory counters (as above):


displaycounter requires one loadregister, one add, one store, and one move (then a syswrite, which will be the same between designs).  The main loop needs one subtract (with state) and one (conditional) branch.


With only the ascii memory counter:


displaycounter requires one loadregister, and one move.  The main loop needs a subtract, a store and a compare, then a branch.


To compare, they both perform one store per iteration (just in a different place), and you trade an add in displaycounter for a compare in the main loop body.  Everything else is equivalent, or exactly the same in terms of CPU ticks.  The store is costly, but you still need to do that once per loop.


So it looks like it's exactly as efficient, but I prefer it this way round as it means everything do with displaying the number and converting it to ascii is in displaycounter, and your main loop looks shorter.


@Linux-SWAT - to be able to print out numbers with more than on digit, I'm afraid you'll have to do a lot more work.  If you want it left aligned you'll need to work out which type of digit to print first - hundreds, tens or ones.  If not you could hard code it to start at hundreds, and omit to print out a 0 if the hundreds or tens evaluate to that value.  Then, to actually work out the digits you'll need to divide the number by 10 repeatedly.


To do it in pseudo-code:


@ Work out first digits to print


set counter to 0


.loop


increment counter


if number>10:


  divide number by 10


  store modulo


  branch to loop


endif


@ display number


show final number as first digit


.loop2


decrement counter


if counter>1:


  get last stored modulo


  print modulo


  branch to loop2


get last stored modulo (by now this is also the first one you stored)


print modulo


Done!


To implement this, you'll need to create a chunk of memory to store the decoded digits in as you decode them in reverse order.  If you use counter as a index to a block pointed to by pointer, it'll start at pointer+1, the way I've mocked that up, which may or may not be a problem, but needs consideration at least.  And I assume modern ARM chips have a integer divide instruction, but I've not looked into that, and I don't know how you get the modulo out of them, which is important to this.


To roll an old-fashioned divide you'll need to right shift once (to divide by 2) then divide by 5, which you'll need a loop to do (keep adding five until your number exceeds the one you're trying to divide)


So you can see how assembly code quickly gets quite long, and how writing structured code gets quite important.
 
And I assume modern ARM chips have a integer divide instruction, but I've not looked into that, and I don't know how you get the modulo out of them, which is important to this.



Cortex-A8 and therefore Pandora does not. It wasn't added to ARMv7a CPUs until Cortex-A15/Cortex-A7.


Division by constants can be synthesized with multiplications and shifts. Depending on the input range, you may need to use multi-precision. This will work for all unsigned 32-bit inputs:


ldr r1, =div_10_reciprocal
umull r3, r2, r0, r1 @ r2 = r0 / 10, r3 is overwritten

...

div_10_reciprocal:
.word 429496730 @ ceil((2^32)/10)




If you need smaller values you can do it with a 32-bit multiplication and a shift.


a % b (modulo) is then performed as (a - (b * (a/b)). Here I'll use the mls (multiply and subtract) instruction that actually is available on Cortex-A8:

Code:
mov r3, #10
mls r3, r2, r3, r0        @ r3 = r0 - ((r0/10) * 10)
 
Last edited by a moderator:
I'm trying to count the /10 iterations because I was stuck a bunch of hours...


And this code doesn't work as intended :


.text
.align 2

.macro syswrite @ print the content of r1, must be ascii, buffer length r2 must be set
mov r0, #1 @ file descriptor 1 - stdout
svc 0 @ call the Linux kernel and print the ascii text
.endm

.macro newline
ldr r1, =newline @ point on the buffer to write, \n, a newline
mov r2, #2 @ newline buffer length
syswrite
.endm

.global _start
_start:

mov r7, #4 @ system call number, 4 is 'write'
mov r5, #16384 @ set the number
mov r11, #0 @ set the digit counter
_loop:

adds r11, #1 @ increment the digit counter

cmp r5, #10 @ compare the number with 10
ldr r10, =div_10_reciprocal @ load the magic number for /10
umull r3, r2, r5, r10 @ r2 = r5 / 10, r3 is overwritten
mov r5, r2 @ save the divided value
ldr r1, =asciicounter @ point on =asciicounter
add r6, r11, #48 @ convert the digit counter to ascii
str r6, [r1] @ store it to be displayed
mov r2, #1 @ buffer length
syswrite
newline
bgt _loop @ if r5 was > 10, loop

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

.data
asciicounter: .word 0 @ must be initialized
newline: .asciz "\n" @ \n is a newline
div_10_reciprocal: .word 429496730 @ ceil((2^32)/10)




Gdb shows that the division seems to fail (well, my code fails ^^) :

Code:
0x00010090 in _loop ()
r0             0x0	0
r1             0xbefff58f	3204445583
r2             0x0	0
r3             0x80368000	2151055360
r4             0x0	0
r5             0x4000	16384
r6             0x0	0
r7             0x4	4
r8             0x0	0
r9             0x0	0
r10            0x200da	131290
r11            0x1	1
r12            0x0	0
sp             0xbefff450	0xbefff450
lr             0x0	0
pc             0x10090	0x10090 <_loop+16>
cpsr           0x20000010	536870928
 
I don't know exactly what's going on with your code, but there's some oddities I'll point out that might make things clearer.


Firstly, you invoke ADDS on the first line after .loop, then followed by a CMP which will overwrite that status change.  I'm not sure if you needed that status, or if the S in ADDS was just a mistake, but it's unclear code.


Then you don't do anything with the CMPed status until you've done some division and called some sysops.  I'm not sure if any of those commands will change the status flags, but I wouldn't trust them to be preserved either side of a sysop personally.


That add r6, r11, #48 looks like a bug.  Why R11?  Is that a typo?  Looks like you're computing an unset value there, so it'll end up printing out gibberish.


And overall, I don't understand your algorithm.  You only seem to be doing one division here; but to compute the digit to print as well as the remaining digits to print, you'll need to do a division, as well as finding out the modulo of that division.  The former will be the digits to pass through to the next iteration of the loop, while the latter is the digit to print.


I can't help on the specific instruction you've used to divide, as I don't really understand how umull works yet.  But hopefully those other issues are still worth fixing.
 
Back
Top