1. This site uses cookies. By continuing to use this site, you are agreeing to our use of cookies. Learn More.
  2. Dismiss Notice

Jumping into ARM assembly

Discussion in 'General Discussions' started by Linux-SWAT, Oct 2, 2015.

  1. Linux-SWAT

    Linux-SWAT Advanced Member

    Joined:
    Feb 13, 2010
    Messages:
    7,858
    Hi !

    I finally decided myself to practice a bit of ARM assembly.
    I already coded a bit of asm using the HP-48's Saturn CPU and I began to read some ARM docs.

    I would like to know how to natively run some asm on the Pandora, beginning with some ADD and memory operations, but I'm not sure where to begin with compiler/tools.
    I'm a visual guy, I need to see register states and use step by step execution.

    My first long-term objective is to write a black and white tron game, as I did on HP-48.
     
    Tags:
  2. notaz

    notaz Certified Guru

    Joined:
    Aug 23, 2005
    Messages:
    4,886
    Location:
    Lithuania
    You're in luck as every pandora already comes equipped with asm programming tools (binutils).

    Here is a small example to get you started, "hello.s":

    Code:
    .text
    .align 2
    
    .global _start
    _start:
      mov r0, #1    @ file descriptor 1 - stdout
      ldr r1, =text @ buffer to write
      mov r2, #13   @ buffer length
      mov r7, #4    @ system call number, 4 is 'write'
      swi 0         @ call the Linux kernel
    
      mov r7, #1    @ exit
      swi 0
    
    .data
    text: .asciz "hello world!\n"
    
    Compile, link and run:
    Code:
    $ as hello.s -o hello.o
    $ ld -o hello hello.o
    $ ./hello
    hello world!
    
    For stepping, just use gdb:
    Code:
    # gdb ./hello
    ...
    Reading symbols from /home/notaz/stuff/asm/hello...(no debugging symbols found)...done.
    (gdb) b _start
    Breakpoint 1 at 0x8074
    (gdb) run
    Starting program: /home/notaz/stuff/asm/hello
    
    Breakpoint 1, 0x00008074 in _start ()
    
    (gdb) x/7i _start
    => 0x8074 <_start>:    mov    r0, #1
       0x8078 <_start+4>:    ldr    r1, [pc, #16]    ; 0x8090 <_start+28>
       0x807c <_start+8>:    mov    r2, #13
       0x8080 <_start+12>:    mov    r7, #4
       0x8084 <_start+16>:    svc    0x00000000
       0x8088 <_start+20>:    mov    r7, #1
       0x808c <_start+24>:    svc    0x00000000
    (gdb) si
    0x00008078 in _start ()
    (gdb) x/7i _start
       0x8074 <_start>:    mov    r0, #1
    => 0x8078 <_start+4>:    ldr    r1, [pc, #16]    ; 0x8090 <_start+28>
       0x807c <_start+8>:    mov    r2, #13
       0x8080 <_start+12>:    mov    r7, #4
       0x8084 <_start+16>:    svc    0x00000000
       0x8088 <_start+20>:    mov    r7, #1
       0x808c <_start+24>:    svc    0x00000000
    
    You can use "info registers" to see all the registers and/or use any other feature of gdb.
     
     
    Last edited by a moderator: Oct 2, 2015
    _jr_, Elw3, Neelix and 2 others like this.
  3. TrashyMG

    TrashyMG Moderator Staff Member

    Joined:
    Jan 18, 2010
    Messages:
    10,004
    Thanks for the example, I've also toyed with the idea of jumping into ARM assembly for sometime. 
     
  4. Linux-SWAT

    Linux-SWAT Advanced Member

    Joined:
    Feb 13, 2010
    Messages:
    7,858
    Thanks, will toy with it until I fully understand it.
     
  5. Ziz

    Ziz Advanced Member

    Joined:
    Sep 10, 2006
    Messages:
    1,584
    If you want to use Assembler inside C code I would suggest to use "asm volatile" like this. I use a bit asm code in sparrow3d to fill a big memory chunk with the same int using stmia on arm devices.
    The first parameter is the string containing your asm code. The second parameter are output variables, which are mapped to registers and the third parameter input variables, which are used to init registers. The last parameter tells gcc, which registers you will touch in your code (so which should not be used anymore).
    The output and input registers are accesable via %0, %1, %2 usw. Read more here.
     
  6. levi

    levi Still fresh, damnit!

    Joined:
    Oct 6, 2008
    Messages:
    7,896
    Location:
    Somewhere off the coast of the EU
    Interesting that you need to call system call 7 to quit the program - on older ARM systems I've used you just let PC exceed the end of the program and the OS would tidy it up for you; for that reason you generally have a label after any data to perform any manual cleanup before it quits.
     
  7. notaz

    notaz Certified Guru

    Joined:
    Aug 23, 2005
    Messages:
    4,886
    Location:
    Lithuania
    I guess you linked the C library or libgcc (done automatically if you compile with gcc instead of as+ld like I showed above), which added init and cleanup code for you, and your program would run into the next function after your's which would eventually return and that would take you to the cleanup code.

    My example has absolutely no other code, it would run out to the other section of ELF file that's loaded in memory and crash. There is no way out, you have to explicitly ask the kernel to stop the process.
     
    _jr_ and levi like this.
  8. Linux-SWAT

    Linux-SWAT Advanced Member

    Joined:
    Feb 13, 2010
    Messages:
    7,858
    Have many questions, but for now, I just wanted to print "Hello World" twice :
     

    Code:
            .text
            .align 2
    
            .global _start
    _start:
            mov r0, #1    @ file descriptor 1 - stdout
            ldr r1, =text @ buffer to write
            mov r2, #13   @ buffer length
            mov r7, #4    @ system call number, 4 is 'write'
            swi 0         @ call the Linux kernel
    
            mov r0, #1    @ file descriptor 1 - stdout
            swi 0         @ call the Linux kernel
    
            mov r7, #1    @ exit
            swi 0
    
            .data
    text:    .asciz "hello world!\n"
    
    Is it the right way to do it ?

     
     
  9. levi

    levi Still fresh, damnit!

    Joined:
    Oct 6, 2008
    Messages:
    7,896
    Location:
    Somewhere off the coast of the EU
    I haven't seen the documentation for system call 4, so it's possible r1, r2 and r7 are modified by the call, so you'll need to reset those too.

    Also, it's possible none of them are modified, so setting r0 again has no effect.  You could test to see if your registers have been modified, but really you should try to find the documentation of this call so you can be sure you're not relying on unspecified behaviour which may change in the future.

    Also, your indentation is significantly different to the example notaz gave.  I'm not sure if that matters with the assembler you're using or not.

    Also, from what I can tell in later revisions, the SWI mnemonic has been replaced by one called 'SVC'.  But as long as the assembler understands it, both will correspond to the correct machine code - it's just a name change, I think.
     
  10. Linux-SWAT

    Linux-SWAT Advanced Member

    Joined:
    Feb 13, 2010
    Messages:
    7,858
    The way I did worked.
    According to gdb, I had to "mov r0, #1" as the register was reset by the first call.

    Meanwhile I'm looking a way to do a loop like while i != 0 then print "hello world", i-- .
     
    Last edited by a moderator: Oct 3, 2015
  11. Linux-SWAT

    Linux-SWAT Advanced Member

    Joined:
    Feb 13, 2010
    Messages:
    7,858
    Here's my little loop ! Feel free to correct/comment if something's not ok there :
     

    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'
    
            mov r5, #0    @	set the counter to zero
    _loop:
    
            mov r0, #1    @ file descriptor 1 - stdout
            svc 0         @ call the Linux kernel and print	the text
    
            add r5, #1    @	increment the counter
            cmp r5, #5    @ compare if the counter == 5
            bne _loop     @	if not,	come back to _loop
    
            mov r7, #1    @ exit
            svc 0
    
    
            .data
    text:    .asciz "hello world!\n"
    
     
    Last edited by a moderator: Oct 3, 2015
  12. notaz

    notaz Certified Guru

    Joined:
    Aug 23, 2005
    Messages:
    4,886
    Location:
    Lithuania
    Yup you get return code there, which is how many bytes were actually written, or negative error code for error.

    It doesn't.

    Right, it's my old habit from GP2X times.

    BTW for system call numbers, it's easiest to look at the kernel's source:
    http://git.openpandora.org/cgi-bin/gitweb.cgi?p=pandora-kernel.git;a=blob;f=arch/arm/include/asm/unistd.h;h=4a1123783806b79ef559ed51a093353feb67070e;hb=HEAD
    As for documentation, it's in standard Linux manpages, like "man 2 write" (have to install manpages-dev package or similar, not available on pandora though). First argument is r0, second in r1 and so on, and syscall number in r7.
     
    Last edited by a moderator: Oct 3, 2015
    _jr_ likes this.
  13. Linux-SWAT

    Linux-SWAT Advanced Member

    Joined:
    Feb 13, 2010
    Messages:
    7,858
    --Edited : this code reads a character on the command line, then displays it

    Code:
            .text
            .align 2
    
            .global _start
    _start:
    
            mov r0, #0    @ file descriptor 0 - stdin
            mov r7, #3    @ system call number, 3 is 'read'
            mov r2, #1    @ buffer length
            ldr r1, =input
            svc 0
    
            mov r0, #1    @ file descriptor 1 - stdout
            mov r7, #4    @ system call number, 4 is 'write'
            svc 0         @ call the Linux kernel and print the text
    
            mov r7, #1    @ exit
            svc 0
    
    
            .data
    input:   .asciz "%d"
     
    Last edited by a moderator: Oct 3, 2015
  14. notaz

    notaz Certified Guru

    Joined:
    Aug 23, 2005
    Messages:
    4,886
    Location:
    Lithuania
    You forgot to do svc to call the kernel to do the read, and use "input: .byte 0", format strings like "%d" are for C library functions, kernel does not support those.
     
    Last edited by a moderator: Oct 3, 2015
    _jr_ likes this.
  15. Linux-SWAT

    Linux-SWAT Advanced Member

    Joined:
    Feb 13, 2010
    Messages:
    7,858
    It was working with %d. Anyway, I applied your change, here's the updated code.

    So, read a character from the CLI, then displays it :

    Code:
            .text
            .align 2
    
            .global _start
    _start:
    
            mov r0, #0    @ file descriptor 0 - stdin
            mov r7, #3    @ system call number, 3 is 'read'
            mov r2, #1    @ buffer length
            ldr r1, =input @ will load the input into r1
    	svc 0              @ call the Linux kernel and wait for the input
    
            mov r0, #1    @ file descriptor 1 - stdout
            mov r7, #4    @ system call number, 4 is 'write'
            svc 0         @ call the Linux kernel and print the text
    
            mov r7, #1    @ exit
            svc 0
    
    
            .data
    input:  .byte 0
     
    Last edited by a moderator: Oct 3, 2015
  16. Linux-SWAT

    Linux-SWAT Advanced Member

    Joined:
    Feb 13, 2010
    Messages:
    7,858
    I was working on a 5432 board, maybe that's why it worked.

    Ok, now, how do I read presses on the Pandora D-pad ?
     
    Last edited by a moderator: Oct 3, 2015
  17. levi

    levi Still fresh, damnit!

    Joined:
    Oct 6, 2008
    Messages:
    7,896
    Location:
    Somewhere off the coast of the EU
    Looks fine.  Just a note that it's always better to count down in loops than to count up, because you can test for zero without doing a direct CMP - the EQ conditional tests for zero after a SUBS (subtract with status flags):

    MOV R5, #5
    _loop:
    @ Do stuff
    SUBS R5, #1
    BNE _loop @ Branch if not zero
    _endloop:

    (at least I think it's the EQ/NE conditionals you need after an ADDS/SUBS, but it's been a while)
    LinuxSWAT figured out that R1 and R2 are either restored or simply not touched after a SVC #0 call, but unless I see it documented somewhere that's that's by design, not coincidence, I'd be tempted to set all of the relevant registers each time to be safe.

    Edit: And while I'm asking, where can you find out what r7 numbers correspond to which manpage?
     
    Last edited by a moderator: Oct 3, 2015
  18. notaz

    notaz Certified Guru

    Joined:
    Aug 23, 2005
    Messages:
    4,886
    Location:
    Lithuania
    It's a bit complicated, you have to open /dev/input/event4 and read the file descriptor you get, then in the memory you read at certain offset there will be a keycode and event value where 1 means key down, 0 key up. The memory is described by this C structure:
    http://git.openpandora.org/cgi-bin/gitweb.cgi?p=pandora-kernel.git;a=blob;f=include/linux/input.h;h=3862e32c4eeb38076d4768c70a958a7b58dd6cf8;hb=HEAD#l26
    Alternatively you can use some library like SDL, but then it's even more work when you do it from asm.

    I think it should be safe to rely on it not changing the registers as it has no need to do it, the kernel has it's own register state and stack.

    See the link in post 12 of this thread.
     
    Last edited by a moderator: Oct 4, 2015
  19. levi

    levi Still fresh, damnit!

    Joined:
    Oct 6, 2008
    Messages:
    7,896
    Location:
    Somewhere off the coast of the EU
    Ah, thanks, missed that.

    As far as I can tell, no processor mode banks R0-R7.  R8-R12 are only banked in FIQ mode.  SVC mode gets a new stack pointer and link register, but you need what's in the latter IIRC.  You can just STM all the registers onto a stack and LDM them back out at the end - but that costs cycles, and on ARM's old RISC OS, some specified registers were generally trashed after an SWI call (depending on which call it was you made), but I guess that's the difference between a hokey old microcomputer OS and a server OS.
     
  20. dmarschal

    dmarschal Member

    Joined:
    Feb 13, 2009
    Messages:
    116
    Location:
    Hungary
    Hello,
    I've written and posted here a full game written in pure ASM language. Only syscalls and /dev/input/event reads are used.
    Feel free to look at the sources.

    One advice, use macros. They make your coding life much much easier. Like this ones:

    to increment a register's value:

    Code:
    .macro inc rreg
     add \rreg,\rreg,#1
    .endm 
    usage:
    Code:
     inc r5
    
    [FONT=sans-serif]to do simple loops:[/FONT]
    
    Code:
    .macro regloop rreg,oofs
     subs \rreg,\rreg,#1
     bne \oofs
    .endm 
    
    .macro print str
     mov r0,#console
     adr r1,1f
     mov r2,#3f-1f
     b 2f
    1: .ascii "\str"
    3: .align
    2: invoke sys_write
    .endm 
    
    usage:
    Code:
     mov r5,#5
    1:
     print "hello\n"
    regloop r5,1b
    and so on. I included a few into the macro.mac file in the archive.

    Happy coding!
     
     
    Last edited by a moderator: Feb 8, 2016

Share This Page

Loading...