Jumping into ARM assembly


The compare char in a loop, better understood. I hope.

Code:
	.text
	.align 2

	.global _start

_start:
       	ldr r8, =quit_char @ will load the address of quit_char into r8
	ldrb r8, [r8]      @ load 'q' ascii code (pointed to r8) to r8
        mov r2, #1         @ buffer length
        mov r7, #3         @ system call number, 3 is 'read'
        mov r0, #0         @ file descriptor 0 - stdin
        ldr r1, =input     @ r1 will point every input in the loop

_loop:
        svc 0         @ call the Linux kernel and wait for the input
        ldrb r9, [r1] @ load the ascii code of the input (pointed to r1) to r9

	cmp r9, r8    @ compare if the input ascii == q	ascii
	bne _loop     @ if not, come back to _loop

	mov r7, #1    @ exit
	svc 0

	.data
input:       .byte 0
quit_char:   .byte 'q'
I still don't understand why when the program stops, it's like i've pressed "enter" :

~/devel$ as hello.s -o hello.o ; ld -o hello hello.o ; ./hello
q
~/devel$
~/devel$
 
This is probably one of the best threads I've ever seen on OPB.  You guys rock!
 
I still don't understand why when the program stops, it's like i've pressed "enter" :

~/devel$ as hello.s -o hello.o ; ld -o hello hello.o ; ./hello

q

~/devel$

~/devel$

I think it's because the shell requires the enter to push the line to stdin, but your program never actually reads it off of stdin because it reads one character at a time and quits after the 'q'. So the enter is left for the shell to read.

You should be able to load the quit char directly, btw:

Code:
ldr r8, [=quit_char] @ will load the value at quit_char into r8
 
Last edited by a moderator:
Still, this enter annoys me :/ .
At least I don't have one enter for every "bad" char type.

hello.s:7: Error: ARM register expected -- `ldr r8,[=quit_char]'

Maybe it's another instruction ?
 
A little gdb entracte.
The init file is ~/.gdbinit and is simple to set for gaining some time.
Read the descriptions for ww and wx command effect inside the gdb shell.

Thanks to Jo !

Code:
set pagination off

define ww
  b _start
  run
  echo \n
end

document ww
  Breakpoint at _start, then runs the program.
end

define wx
  echo \n
  x/16i _start
  echo \n
  si
  info registers
  echo \n
end

document wx
  Step-by-step execution, displaying 16 lines of code, then registers.
end
 
Last edited by a moderator:
Yeah sorry, GCC's PC-relative load syntax is annoying and I forgot it... I think it's just this:

Code:
ldr r8, quit_char
 
hello.s:7: Error: internal_relocation (type: OFFSET_IMM) not fixed up

You broke my program :( .
Hopefully, I have backups.
 
Gah, LDR looks like a pain to use.  It's only a meta instruction that's translated into a series of actual ARM ops - mainly a MOV command.  It's certainly possible to write a PC-relative MOV instruction to do what you want - 'MOV R8, [PC, #offset]' but to do that you need to know the pipeline length and the addresses of both the instruction and the data to work out the offset.

Regarding the extra newline, to test it, can you type in 'qecho foo' into it when you run it, and see if it runs 'echo foo' after it finishes.  That would prove it's not consuming any subsequent characters, and they get consumed instead by the shell after exit.
 
Gah, LDR looks like a pain to use.  It's only a meta instruction that's translated into a series of actual ARM ops - mainly a MOV command.  It's certainly possible to write a PC-relative MOV instruction to do what you want - 'MOV R8, [PC, #offset]' but to do that you need to know the pipeline length and the addresses of both the instruction and the data to work out the offset.

It does still translate ldr instruction, and you don't need to know the pipeline length because PC is architecturally specified to have the same offset it had since the first ARMs. That is, if you really wanted to try to do it manually.

The problem here is I didn't notice that quit_char is all the way in another section (.data), which makes it too far away to access with a PC-relative load. But since it's a constant there's no reason why it can't be in .text instead, like right before or after the function, which would make it in range.

For that matter, there's no reason why it can't just be an immediate, ie:

.equ quit_char 'q'
mov r8, #quit_char

Although I don't know if .equ will accept const chars. I normally use GCC's preprocessor and .S files (note the capital) personally.

EDIT: This forum upgrade sucks. Doing code tags manually with stuff following after the tag doesn't seem to be viable anymore. And I can't see a non-wysiwyg editor option. Lame.
 
Last edited by a moderator:
You're right Levi !

$ as hello.s -o hello.o ; ld -o hello hello.o ; ./hello
qecho 'huhu'
$ echo 'huhu'
huhu
$

What would be a clean solution ?
 
Last edited by a moderator:
I always put constants into .text section. print macro is a good example.

Code:
.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
See the result with "objdump -D". You can benefit that PC is pointing two instructions ahead so you can access data using the PC register.
 
Code:
ldr r1,[pc]
b 1f
.word 0x1234   @ data to be loaded
1:
BTW, LDR does the very same. It puts .word constants into the .text section to be loaded using the PC register. You can 'control' the literal pool's placement using the .lpool directive. Sometimes GCC overwrite it but most of the times it works fine.

Back to the first example.
Also, "console" constant is defined at the beginning of the assembly file. If the constant fits in 8 bits (not just the lower 8 bits) then I always use constants and simple MOV instructions. There is an ARMv7 instruction "movw" and "movt" that loads 16 bit data into a register's high and low half.


Advice, never use the same register in the next instruction.
Code:
ldr r8,=somedata
ldrb r8,[r8]
These two lines halt one pipeline (or both if the following instruction in not pairable) for 1-3 clock ticks. If you insert an instruction between the two, like a "mov r2,#console" then this instruction will be executed parallel on the other pipeline. After an LDR you have to insert two or three instructions to be balanced.

editor sucks
over



 
 
Last edited by a moderator:
Exophase, tried with .equ, but I get :
hello.s:13: Error: expected comma after "quit_char"
hello.s:14: Error: undefined symbol quit_char used as an immediate value

Here's the compare char in a loop following dmarschal's advice :

Code:
	.text
	.align 2

	.global _start

_start:
       	ldr r8, =quit_char @ will load the address of quit_char into r8
	ldrb r9, [r8]      @ load 'q' ascii code (pointed by r8) to r9
	mov r2, #1         @ buffer length
	mov r7, #3         @ system call number, 3 is 'read'
	mov r0, #0         @ file descriptor 0 - stdin
	ldr r1, =input     @ r1 will point every input in the loop

_loop:
      	svc 0          @ call the Linux kernel and wait for the input
	ldrb r10, [r1] @ load the ascii code of the input (pointed to r1) to r10

	cmp r10, r9    @ compare if the input ascii == q ascii
	bne _loop      @ if not, come back to _loop

	mov r7, #1     @ exit
	svc 0

	.data
input:         .byte 0
quit_char:     .byte 'q'
 
Last edited by a moderator:
use

Code:
quit_char = 'q'
before declaring the .text section. and use it like this:
Code:
mov r9,#quit_char
advice, put all constants and structure offsets in a separate include file and ".include" it before the section declares.

declare syscalls like
 
Code:
sys_read=3
and use 
 
Code:
mov r7,#sys_read
in your example






 
 
Last edited by a moderator:
Linux-SWAT:
Using syscall 3, sys_read: It returns the character count in R0 so the next loop will read from 1,stdout, not from 0,stdin.

Always at least four-align your data. Use an align after a .char declaration OR pack four characters into one .word to save space.
 
 
$ as hello.s -o hello.o ; ld -o hello hello.o ; ./hello
qecho 'huhu'
$ echo 'huhu'
huhu
$

What would be a clean solution ?

Probably if you continue to call sys_read until you get a newline code back, after you've received a q.  Something like:

LDR R1, =input @Address
MOV R2, #1 @Length
MOV R7, #3 @Sys_Read
_getremainder:
MOV R0, #0 @Stdin
SVC 0
LDRB R10, [R1]
CMP R10, #10 @Newline ASCII code
BNE _getremainder
MOV R7, #1 @Exit
SVC 0

Some of that code can probably be eliminated depending on the prior setup of your registers.
 
Last edited by a moderator:
Code:
What does quit_char = 'q' before declaring the .text section do ?
Why is it different from previous propositions ?
 
Intermediate version handling the newline :

Code:
        .text
        .align 2

        .global _start

_start:
        ldr r8, =quit_char @ will load the address of quit_char into r8
        ldrb r9, [r8]      @ load 'q' ascii code (pointed to r8) into r9
        mov r2, #1         @ buffer length
        mov r7, #3         @ system call number, 3 is 'read'
        mov r0, #0         @ file descriptor 0 - stdin
        ldr r1, =input     @ r1 will point every input in the loop

_loop:
        svc 0          @ call the Linux kernel and wait for the input
        ldrb r10, [r1] @ load the ascii code of the input (pointed to r1) into r10

        cmp r10, r9    @ compare if the input ascii == q ascii
        bne _loop      @ if not, come back to _loop

_getremainder:
        svc 0
        ldrb r10, [r1]
        cmp r10, #10  @Newline ascii code
        bne _getremainder

        mov r7, #1    @ exit
        svc 0

        .data
input:       .byte 0
quit_char:   .byte 'q'
 
Last edited by a moderator:
Back
Top