Jumping into ARM assembly


Note 1:
I didn't set r0 inside the loop, as the program works, and I don't see where is the problem.

Note 2:
The program doesn't work as intended as if I have 'q' anywhere in a string, it quits :/

Code:
$ as hello.s -o hello.o ; ld -o hello hello.o ; ./hello
q456
$ as hello.s -o hello.o ; ld -o hello hello.o ; ./hello
456q
$
 
Hmm, can you run it in gdb and see what r0 is set to after the SVC sys_read call?  Notaz and dmarshal suggest its a return value, and dmarshal suggests it ought to be 1 which would break the next SVC call.

It quits with any q in the string is because you loop over all the non-q characters until you hit a q.  In your second example, in _loop you consume the '456' and the 'q', then in _getremainder you consume the newline.  In the first example you consume the 'q' in _loop and '456<enter>' in _getremainder.
 
Yup I just saw it changes to 1. But why is it working if it reads from stdout ?

Not that I'm a naysayer, but I really don't want to go fast.

Here's the corrected code :

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'
        ldr r1, =input     @ r1 will point every input in the loop

_loop:
        mov r0, #0     @ file descriptor 0 - stdin
        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:
        mov r0, #0     @ file descriptor 0 - stdin
        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'
 
It quits with any q in the string is because you loop over all the non-q characters until you hit a q.  In your second example, in _loop you consume the '456' and the 'q', then in _getremainder you consume the newline.  In the first example you consume the 'q' in _loop and '456<enter>' in _getremainder.

I thought I was looping inputs, looking for the first char, as the size of the input is 1, defined with mov r2, #1         @ buffer length
 
Last edited by a moderator:
Um, I've no idea why it works if R0 points at stdout the second time around, but as you demonstrated, it does seem to work, even with R0 setting outside of the loop.

In _loop you're looping over subsequent characters entered to stdin.  You're calling sys_read on stdin multiple times.  That'll eat the '456' in the second example before it eats the 'q'.

You'll need to define exactly what behaviour you're trying to achieve here.  I'd tend to think you actually want something a bit more complex - to parse lines separated by newlines, and quit if the first character of a message is a 'q'.  To do that you'd need to do something like block if a non-q is received first until you get a newline, which allows you to accept a 'q' again.  Something like:

_testexit:
sys_read
if result='q' branch to _getremainder
_blockloop:
sys_read
if result!='\n' branch to _blockloop else branch to _testexit
_getremainder:
...as before...

If you want to accept only 'q' as the quit command, not 'q' followed by some stuff, you'd need to test for 'q' followed by a newline.  To transform that code, replace _getremainder with a block that tests to a newline, and exits if it gets ones, else it branches back to _blockloop, I think.  You won't need the old _getremainder code since any remainder which isn't a newline wouldn't count as a valid quit command.

But if you want to accept 'qfoo' as a valid quit command, just try something like I suggested above.
 
I want ^q$ as the quit command !

This loop thing puzzles me. I reorganized the .gdbinit above and will fire it.
 
Then you don't want to compare as you input, what you want to do is turn each input into a string, join then all together, then when you register an 0x0D or 0x0A (CR/LF) then you want a "strcmp" function which takes two pointers, compares the length, and if equal, check each character from the same position in each string together, returning false if they are different
 
Well, that's how you'd do it as a c coder, but since assembler has no inbuilt strcmp function you'd need to craft your own, and define memory blocks to store the messages, and guard against overflows and so on.  My design's a little more like you'd design a state machine in C, and since it seems possible to do it fairly simply and cheaply as I've outlined, I wouldn't go to the expense of string handling personally.  If you wanted more complicated stuff later, it might be worth writing all that code, but I believe in delaying such complexity until you absolutely need it.  I suspect you'll only actually need full string handling capacility if you ever try to implement a command that comes after other parameters.

Yes, if you want ^q$ as the quit command, you only need a single loop to loop over characters that aren't q first, or aren't newline (aka \n, \a, \d, OxA, OxD, #10 or #13 - but on Linux, \d, 0xD or 0x13 aren't part of the line ending code IIRC).  First, check if you get a q, then check if you get a newline.  If in either case you don't you'll need to go into a loop that keeps checking characters until you get a newline, to indicate a new command.

The loops aren't that complicated.  Each time it goes round one of your loops, it accepts a single character in.  I'm not sure if linux passes the key strokes in as you type them, or a whole line of them once you hit enter (I suspect the latter though), but as you're only accepting them one at a time, you only get one each turn of the loop.

You could accept two characters at a time, and check if the first is q and the second is a newline, but without prototyping it out I can't say whether that would introduce further corner cases to think about.
 
Well, that's how you'd do it as a c coder, but since assembler has no inbuilt strcmp function you'd need to craft your own, and define memory blocks to store the messages, and guard against overflows and so on.

A strcmp function is easy in Assembler, I could write you one quite quickly - the catch would be x86 assembler rather than ARM.

 

Code:
strlen	PROC	strptr:DWORD
	push	esi
	push	ecx
	
	mov	esi, strptr
	xor	ecx, ecx
findend:
	lodsb
	or	ah, ah		; Or is this al? Been a while
	jz	foundend
	inc	ecx
	jmp	findend
	
foundend:
	mov	eax, ecx
	pop	ecx
	pop	esi
	ret
strlen	ENDP


strcmp	PROC	astrptr:DWORD, bstrptr:DWORD
	push	astrptr
	call	strlen
	mov	eax, ecx
	push	bstrptr
	call	strlen
	cmp	eax, ecx
	jne	notmatching
	
	mov	esi, astrptr
	mov	edi, bstrptr
	; ECX already holds string length, and both strings must match length, as checked above
	
finddifferences:
	cmpsb
	jc	notmatching
	loop finddifferences

matching:
	mov	eax, -1
	jmp leavestrcmp
notmatching:
	xor	eax, eax
leavestrcmp:
	ret
strcmp	ENDP
 
Linux-SWAT,
As others suggested, why don't you allocate, say 80 bytes and read a full line to the buffer then search for letter 'q'. If it was not typed then print out a message saying something motivating and start it over.
 

Code:
.arm
.title "input"
.globl _start

cmp_char='q'

sys_exit=1
sys_read=3
sys_write=4
 stdin=0
 console=1

.macro invoke ssyscall
  mov r7,#\ssyscall
  swi 0
 .endm

.macro regloop rreg,oofs
 subs \rreg,#1
 bne \oofs
.endm

.macro exit rretn
 mov r0,#\rretn
  invoke sys_exit
.endm

.macro print sstr
 mov r0,#console
 mov r2,#92f-91f
 mov r1,pc
 b 93f
 91: .ascii "\sstr"
 92: .align
 93: invoke sys_write
 .endm

.data
kbbuf: .fill 80,1,0
kbbuflen=.-kbbuf
 
.text
_start:
 print "enter a line\n: "

read_loop:
 ldr r1,=kbbuf
 mov r0,#stdin
 mov r2,#kbbuflen
  invoke sys_read
 
 subs r5,r0,#1
           @ holds the string length
  beq emptyline

cmp_loop:
 ldrb r3,[r1,r5]

 cmp r3,#cmp_char
  beq cmp_ok

regloop r5,cmp_loop

b emptyline     @ or q was not there

cmp_ok:
@ 'q' was found, be happy
 print "letter \'q\' was typed\nbye\n"

exit 0

emptyline:
 print "try again.\n"
b _start

 .bss
 .end
 
 
Note 1:
I didn't set r0 inside the loop, as the program works, and I don't see where is the problem.

Note 2:
The program doesn't work as intended as if I have 'q' anywhere in a string, it quits :/

$ as hello.s -o hello.o ; ld -o hello hello.o ; ./hello
q456
$ as hello.s -o hello.o ; ld -o hello hello.o ; ./hello
456q
$


On Note 1,
The code works but try the following:
open a file or device, like '/dev/input/event0' and try to read 3 bytes in your example. The first file descriptor on a 'clean' system is 3 so the second loop will read from the event device if more than 3 characters were typed.

like this:
 

Code:
 mov r0,#2  @ RDONLY
 mov r1,pc
 b 1f
evdev: .asciz "/dev/input/event0"
.align
1:
 mov r7,#sys_open
  swi 0
 @ assume all is okay. skip error check

 mov r0,#console
 ldr r1,=charbuf
 mov r2,#5
 mov r7,#sys_read

2: 
  swi 0
@ now type 2 characters
@ returns 3 in R0 (0xa added)

3:
 ldrb r4,[r1]!
 cmp r4,#'q'
bne 3b
@ and the second loop will read from the event device and will wait for input
@ because O_NONBLOCK was not used when we opened the file.
 
.bss

charbuf: .fill 16,1,0
 
Last edited by a moderator:
Linux-SWAT,
As others suggested, why don't you allocate, say 80 bytes and read a full line to the buffer then search for letter 'q'. If it was not typed then print out a message saying something motivating and start it over.

As Linux-SWAT clarified, he wants to react to just the letter 'q' on its own, so you don't need a loop to seach for it I think.  I'm not sure how sysread reacts; I assume it blocks until enter is pressed, then sends up to buflen characters to your buffer.

In this case, since the maximum valid command length is 1, there's no chance a valid command could split over 80 character buffers, so that's fine.  It does seem something of a waste to me, but I guess a svc call is relatively slow, so getting as many characters at a time might have some additional value over my design.  You could just ignore the buffer if the returned number of characters got is not 1 though, which would be neat.

I suppose it does have a theoretical bug though, even if you fix it to only react to a single character q; enter 80 characters of gibberish followed by a q, then enter.  That shouldn't make it quit, but I suspect it would if you used an 80 character buffer (or any other length, for that matter).

Cool example of macros though.  I've not used them before.  And I hope all these different examples are of some use to LinuxSwat.
 
I really have no clue and haven't followed closely, but don't you need to set the terminal to raw mode to read single key presses?
 
I really have no clue and haven't followed closely, but don't you need to set the terminal to raw mode to read single key presses?

No, you have to alter terminfo settings if you read input events directly. Also, you have to use O_NONBLOCK | O_RD flags when the event device is opened.
I have an example somewhere.  I will post it soon.
 
Last edited by a moderator:
Up to now we were reading stdin, not the terminal directly, and that is line buffered. I think that should be enough for Linux-SWAT's needs, no need to make things complex.
 
Well said !

I'll be busy the next few days but I'll try to find time to better understand, if not just understand most of things said here.

It's rather incredible how assembly is both simple and complex at the same time.
 
notaz was right. This should be enough for learning the basics.

For me, keeping the sources readable is very important. I think following a 'coding style' from the beginning helps to understand and develop the sources for a long time. I still maintain and debug 15 years old x86 assembly codes.
 
Indeed, I think that's essential for x86 code.  ARM code on the other hand I find much easier to read.  I have had much longer to study it I guess, but it is a more modern design with less historical cruft than IA32 has.

I guess I should take a look at 64bit x86, since they had a chance there to design a logical system from the ground up.

I personally think Linux-Swat's approach of commenting each line is enough for the level he's at.  He's still to try the various ways of laying out his code to find a standard he's happy to settle with.
 
While on trip, I wanted to print the iteration number during the loop, and didn't succeeded.
I don't have the codes here.

The most constructive (IMHO) try was creating a .byte thing, and copy the iterator's register content inside, then try to display it.
 
 
To print your iterator, you'll need to convert the value to a string, rather than just outputting the conents of your register.

So you'd need to create a string long enough to hold the ASCII version of your number (or use 8 characters if you're going to display in Hex for a 32bit value). Then process your nybble/10s units and add them to the ASCII value of '0'

I could write some code for you if you want, though it'd be in x86 assembler.

Edit: Eww, is this how you do call/ret in ARM assembler? With bl, push and pop?! Seems needless complicated

Edit 2: x86 Register To ASCII

Code:
	.code
	
start:
	call clearval

	mov	eax, DEADBEEFh
	call toascii
	
	jmp $				; Replace with exit code

clearval:
	mov	al, '0'	; AL = ascii of "0"
	mov	ecx, 8	; 8 characters long
	lea	edi, hexval	; EDI points to string
	rep	stosb		; Fill 8 characters at hexval with "0"
	ret					; Finished proc
	
toascii:		; EAX has value to convert
	lea	edi, hexval
	add edi, 7	; Point to the last nybble in string
	mov	ecx, 8
toasciiloop:
	push	eax	; Save EAX so we can process the other nybbles
	
	and	eax, 0000000Fh	; Mask off the bottom nybble
	
	lea esi, hexlist	; Point ESI at hex list
	add esi, eax			; ESI points to hexlist character for value 0-F
	
	lodsb		; Reads byte from ESI pointer into AL (I think it's AL)
	mov byte ptr [edi], al	; Stores ASCII value into the EDI pointer
	
	pop	eax		; Restore EAX value
	shr	eax, 4	; Drop last nybble (as we've just processed it)
	dec	edi		; Move pointer to previous character
	loop	toasciiloop		; Loop round, while ECX > 0
	
	ret

	.data
	
hexval		db "00000000", 0Dh, 0Ah, 00h
hexlist:	db "0123456789ABCDEF"
 
Last edited by a moderator:
Back
Top