Jumping into ARM assembly


printhex:
stmdb sp!, {r0-r3,r7,lr} @ save regs

mov r2,#7 @ print out 8 numbers
sub sp,sp,#8 @ make room for the ascii string on stack

1:
and r3,r0,#0xf @ pick the lower nibble 0..f
mov r0,r0, ror #4 @ get the next nibble

cmp r3,#0xa
addlt r3,r3,#'0' @ convert to ascii 0..9 -> '0'..'9'
addge r3,r3,#'A'-0xa @ 0xa..0xf -> 'A'..'F'

strb r3,[sp,r2] @ store it on stack (reverse order!)

subs r2,r2,#1 @ do the 8 char loop
bpl 1b

mov r0,#console @ and write it on the console
mov r1,sp
mov r2,#8
invoke sys_write

add sp,sp,#8
ldmia sp!, {r0-r3,r7,pc} @ return

and here is a printdec routine:
 
Code:
div10: .word 0x1999999a       @ 2^32 / 10
printdec:
 stmdb sp!, {r1-r5,r7,lr}     
                 @ there is no DIV instruction for the ARM family
                 @ so here is the method:
 clr r4          @ x/10 = x * (2^n/10) then ASR n (or use the upper 32 bits when n=32)
 mov r5,sp
 ldr r3,div10

 cmp r0,r4
 sub sp,sp,#12
  rsblt r0,r0,r4
  movlt r4,#'-'

11:
 umull r1,r2,r0,r3
 add r1,r2,r2, lsl #2
 sub r1,r0,r1, lsl #1

 dec r5
 orr r1,r1,#'0'
 strb r1,[r5]

 movs r0,r2
bne 11b

 cmp r4,#'-'
  subeq r5,r5,#1
  streqb r4,[r5]

 mov r1,r5
 add r2,sp,#12
 mov r0,#console
 sub r2,r2,r5
  invoke sys_write

 add sp,sp,#12
ldmia sp!, {r1-r5,r7,pc}

Please define
"console", 
"clr" macro,
"sys_write" and 
'invoke" macro before calling the codes. You can find them in my earlier posts.

printdec handles negative numbers as well.

clr macro is here:
Code:
.macro clr rreg
 mov \rreg,#0
.endm
Here is an example how to call printdec from your code:
 
Code:
.globl _start
.include "macro.mac"
.data
.text

_start:
 mov r0,#-15
 bl printdec      @ stores return address in LR
 
 exit 0
 
 .bss
 .end






Have fun!




 

View attachment 12398
 
Last edited by a moderator:
Edit: Eww, is this how you do call/ret in ARM assembler? With bl, push and pop?! Seems needless complicated

Well, technically you don't need to push and pop, but in practice that solves the problem having to shuffle round the return address if you call a subroutine from within your routine.  Bl branches while recording the address to branch back to in r14 aka. lr, as well as making sure you don't accidentally trash any other registers your user was using.  Pop that, along with any working registers you plan to use on the stack, then pull them off at the end, except you pull off what you stored as lr into pc (the program counter, aka r15), causing the code to return to the next instruction after your function was called originally.

If you knew you weren't going to be calling bl yourself (and thus losing what you had in r14), you could have a defined space to store the working registers in before you trash them, and then just 'mov pc, lr' at the end to return, but using a stack you don't have to define your own spaces to park registers.  You could also park r14, and be able to call subroutines, but note that to get the address of your parkspace and put things into it is two instructions, and you have to trash one register as the park address, while if you use push and pop, the stack register is already set with a space to use, so it's just one instruction.

All that said, I don't know if the stack pointer will be set to something valid by your toolchain.  When I was coding ARM from within BBC BASIC on RISC OS, you had to define a block of memory and set your stack pointer yourself at the head.  Looks like the modern RVCT toolchain does set up a stack - not sure about GNU or anything else.

Also, fwiw, I think those 'ldmia' and 'stmdb' opcodes are directly equivalent to the pop and push instructions in the arm docs linked my pmprog. 'ldm' and 'stm' are certainly used to push and pop, before the push and pop meta-instructions came to exist (or maybe they only exist in ARM's assembler, not checked).  The IA/DB pairs are used if the stack grows downwards, and the stack pointer points to next free element (I think), but it's possible to have stacks that grow any way up, and have a stack pointer that points to either the next free or the last set element, it's up to you.  I don't know which combination push and pop are equivalent to that's all.
 
and then just 'mov pc, lr' at the end to return, but using a stack you don't have to define your own spaces to park registers. 

You can "mov" into pc? Interesting. The x86 didn't allow direct access to the program counter. Though call and ret literally pushed and popped the pc for you.

 

Also, fwiw, I think those 'ldmia' and 'stmdb' opcodes are directly equivalent to the pop and push instructions in the arm docs linked my pmprog. 'ldm' and 'stm' are certainly used to push and pop, before the push and pop meta-instructions came to exist (or maybe they only exist in ARM's assembler, not checked).  The IA/DB pairs are used if the stack grows downwards, and the stack pointer points to next free element (I think), but it's possible to have stacks that grow any way up, and have a stack pointer that points to either the next free or the last set element, it's up to you.  I don't know which combination push and pop are equivalent to that's all.

Sounds likes there's some nifty features in there, I might add ARM ASM onto my list of potential future projects :) I also don't remember being yours, and would think my wife would have something to say about that ;)

 
 
Here is how LDM/STM works:
LDMIA - LDM Increment After
LDMDA - LDM Decrement After
LDMIB - LDM Increment Before
LDMDB - LDM Decrement Before

x86 PUSH = ARM STMDB
x86 POP = ARM LDMIA


To see how segments are allocated under Linux I created a short example.
 

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

.data
datasegm=.

.text
codesegm=.

_start:
 print "code segment: 0x"

 ldr r0,=codesegm
 bl printhex

 print "\ndata segment: 0x"

 ldr r0,=datasegm
 bl printhex

 print "\nstack pointer: 0x"

 mov r0,sp
 bl printhex

 print "\n"
exit 0

.include "printhex.inc"

.bss
.end



You need to save the above 'printhex' routine as printhex.inc and include it to the source.

Results on a Zaurus 860 with Cacko Linux:
code segment: 0x00008074
data segment: 0x0001016C
stack pointer: 0xBFFFFC40




 
 
and then just 'mov pc, lr' at the end to return, but using a stack you don't have to define your own spaces to park registers. 

You can "mov" into pc? Interesting. The x86 didn't allow direct access to the program counter. Though call and ret literally pushed and popped the pc for you.
Yes, you can.  Although I've read up since writing that that these days it's not advised and instead you should branch to the address in the register, as the ARM branch predictor doesn't spot moves into PC.  On the old 26-bit PC ARM chips, ORRing with PC was the only way to set certain processor flags though.

I'm not aware of any older processors that let you poke at the program counter yourself.  In ARM is was always part of the design though - if you can write to PC like any other register, you don't need so many special commands to manipulate it.  Ditto all the other named registers - they all also have numbers, and you can use them like any other register should you need to (except for R15/PC, cos if you set that you cause a branch).

In fact, I think in the ARM instruction set, the only reason it has a B instruction rather than a MOV PC instruction is that the MOV instruction can only take 8 significant bits of address as an immediate operand, while B embeds 24 bits of PC-relative address.  In other words, with MOV you can only branch to the first 128 words of RAM, or the first 256 bytes if an even address, or the first 512 bytes if 128-bit aligned (you lose one of those 8 bits as the number is stored in 2's complement, so half the addresses you could mov to are negative, and as such invalid - in practice you'd be much better off ADDing to PC to do an 8-bit relative branch).  With B you can branch to 8 megawords (aka 32MB) forwards or back.

I think in the ARM design, only R14 (in the case of doing a bl branch) and R15 actually have defined purposes.  Further regarding the stack pointer, you have to specify it as the first argument to your ldm/stm commands.  As far as I can tell, GNU AS doesn't implement the push and pop meta-commands which are replacements for STMDB sp and LDMIA sp as far as I've read.

STMIA and LDMDB implement an incrementing stack where the pointer points to next free.
STMDA and LDMIB implement a falling stack where the pointer points to next free.
STMDB and LDMIA implement a falling stack where the pointer points the last element.
STMIB and LDMDA implement an incrementing stack where the points to the last element.

As you can see you always need to pair up the opposite configuration letters in your LDM as you put in your STM - IA pairs with DB, DA with IB.

I'm not familiar with the ! used in dmarshall's 'STMDB sp!,...' though.  That's new syntax to me.
 
Last edited by a moderator:
Levi:
>> I'm not familiar with the ! used in dmarshall's 'STMDB sp!,...' though.  That's new syntax to me


The "!" means the new SP (after store) is written back to SP. If you leave it out, like
STMDB SP,{R4,R5}
then SP will point to the same address as before and another call may destroy your data.

Newer binutils have a push/pop macro to save time with LDM/STMs.

View attachment macro.mac
 
Last edited by a moderator:
I'm afraid I can't yet understand/use your code dmarschal.
As I kickstart, maybe should I try to only display the hexa.

On a positive side, I now see that using macros is mandatory, even for the simplest tasks.

Also, which registers can I freely use without interfering with system calls or whatever ?
 
You're generally free to use any register from r0-r12.  r13 is the standard stack pointer, so if you use any code that relies on that you'd best leave that alone.  And r14 is the link register, overwritten when you bl anywhere.

A linux syscall can overwrite r0, but you can use that in between syscalls provided you finish using it before your next syscall (which you'll pretty much have to do, as before a syscall r0 becomes where you put your first argument).

And regarding access to PC/R15 in my last megapost, I'll just mention that I've been reading up on AArch64, and there general access to PC is not permitted any more (although all the practical uses it had in AArch32 are all still valid, such as PC relative addressing).
 
Last edited by a moderator:
You're generally free to use any register from r0-r12.  r13 is the standard stack pointer, so if you use any code that relies on that you'd best leave that alone.  And r14 is the link register, overwritten when you bl anywhere.

According to that url I posted before about bl, you should preserve r4 to r11

because the ARM procedure call standard specifies that r4-r11 must be preserved between function calls and that the called function is responsible for that preservation
 
I think the ARM procedure call standard assumes you're mixing C (or any other high level code) and ASM code. If you're writing pure ASM code then you don't have to follow this rule. Just preserve what you need.

GCC can optimize your register usage when the ASM code is inlined so you don't have to manually push/pop all in/out parameters and temporary registers.
http://wiki.osdev.org/Inline_Assembly

Also there's a difference between OABI and EABI calling conventions regarding register usage. OABI syscalls take parameters in registers only. For example, sys_mmap2 expects all parameters passed in r0..r5. On an EABI system you have to use r0..r3 and push {r4,r5} on stack to make it right.
 
 
Thanks, it's much more clear now.

Another question, reading mainly asm docs, not processor whitepapers, I've found no information about internal cache memory, is it the stack or heap ?
 
The cache is not a stack.  It's the same as the way PCs implement caches as far as I know; not an independent heap, just something that overlays specific pages of main memory as needed.

The only time you can see the effects of the cache are if you're trying to write self-modifying code; modern arm processors run a 'harvard scheme' cache where it has two caches; one for data and one for instructions.  if you write some data that you expect to be an ARM opcode that you expect the processor to run, you may need to reload the instruction cache (and write out the data cache) if the instruction cache has already cached the old value.

But self-modifying code is a very hairy optimisation, and in practice I've most often seen it in copy protection routines trying to obfuscate their function.  Don't do that, and you'll be fine, AFAIK.
 
How about using .rept directive to make a loop ?

Code:
        .text
        .align 2

        .global _start
_start:

        ldr r1, =text @ buffer to write
        mov r2, #13   @ buffer length
        mov r7, #4    @ system call number, 4 is 'write'
.rept 5
        mov r0, #1    @ file descriptor 1 - stdout
        svc 0         @ call the Linux kernel and print the text
.endr
        mov r7, #1    @ exit
        svc 0

        .data
text:      .asciz "hello world!\n"
 
Here's some ugly 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'
datacounter: .word 53 @ ascii code for the number 5
mov r5, #5 @ set the counter to 5
_loop:

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

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"
addrdatacounter: .word datacounter
Which produces :

5
hello world
5
hello world
5
hello world
5
hello world
5
hello world
But I haven't figured out how to decrease datacounter in order to get :

Code:
5
hello world
4
hello world
3
hello world
2
hello world
1
hello world
 
To decrease datacounter you need to load the value from memory to register, decrease the value in register and store the value back to memory.

Something like this:

Code:
ldr r1, =datacounter
ldr r2, [r1]
sub r2, r2, #1
str r2, [r1]
 
Ah, that's because you have datacounter in the code segment/section (and therefore readonly) and not in the data segment.
 
Back
Top