Page 1 of 1

NASM - Linux Terminal Input/Output w/int 80H

#1 GunnerInc  Icon User is online

  • "Hurry up and wait"
  • member icon




Reputation: 876
  • View blog
  • Posts: 2,312
  • Joined: 28-March 11

Post icon  Posted 18 July 2012 - 06:30 PM

I will show how to read/write from the Terminal using int 80H system calls

input.asm
sys_exit        equ     1
sys_read        equ     3
sys_write       equ     4
stdin           equ     0
stdout          equ     1
stderr          equ     3

SECTION     .data
szName          db      "What is your name? "
Name_Len        equ     $-szName
szHello         db      "Hello ", 0
Hello_Len       equ     $-szHello

SECTION     .bss
lpBuffer        resb    7
Buf_Len         equ     $-lpBuffer
    
SECTION     .text
global      _start
    
_start:
    mov     ecx, szName
    mov     edx, Name_Len
    call    DisplayText

    mov     ecx, lpBuffer
    mov     edx, Buf_Len
    call    ReadText
    push    eax
    
    mov     ecx, szHello
    mov     edx, Hello_Len
    call    DisplayText
    
    pop     edx
    mov     ecx, lpBuffer
    call    DisplayText
    jmp     Exit
    
DisplayText:
    mov     eax, sys_write
    mov     ebx, stdout
    int     80H 
    ret
    
ReadText:
    mov     ebx, stdin
    mov     eax, sys_read
    int     80H
    ret

Exit:  
    mov     eax, sys_exit
    xor     ebx, ebx
    int     80H


To read from Terminal:

Quote

ssize_t sys_read(unsigned int fd, char * buf, size_t count)

ebx = file descriptor
ecx = pointer to buffer
edx = buffer size
eax = system call - sys_read
On return, eax contains bytes read

Ok, so we need 3 things to use sys_read:
1. A file descriptor
2. A pointer to a buffer to store typed characters
3. Size of the buffer including the EOL character - 0xA

1. A file descriptor? Yes a "file". Almost everything in *NIX is a file. To quote Wiki:

Quote

Generally, a file descriptor is an index for an entry in a kernel-resident data structure containing the details of all open files

So, when we set eax to 3, the OS look in the file descriptor table and knows we want to read from the Terminal.

2. A buffer to store typed characters. Our buffer in this sample is 7 bytes long (Gunner is 6 + 0xa)

3. Size of buffer. This should be how big our buffer is. Linux will only put this many characters in our buffer.

To write to Terminal:

Quote

ssize_t sys_write(unsigned int fd, const char * buf, size_t count)

ebx = file descriptor
ecx = pointer to buffer
edx = count of characters to write
eax = system call - sys_write
On return, eax contains bytes written

Again, we need 3 things to write to the Terminal, same as reading but count is the amount of characters to write to the Terminal.

Ok, lets run it to see what happens:

Attached Image

What the hell?!?! Why is "who else" not in the correct place? This is a common problem and question! Technically there is nothing wrong with this. We told the OS to read 7 bytes from the terminal and put those bytes into our buffer, and that is what it did. Problem is, we typed more than 7 characters into the terminal and hit enter, so there are still bytes left in stdin ("who else") which bash reads.

Ok, so how do we fix this? For one, we could use a buffer larger than 7 bytes which you would do in a normal program; 7 is just to show this problem, or we could check to see if there are anymore characters in stdin and if there are, read them from stdin and do nothing with them.

Attached Image

Ah, that is much better! So how did I fix it? Added a new function called ClearTerminal:

ClearTerminal:   
    mov     edx, 1
    mov     ecx, Buf
    mov     ebx, stdin
    mov     eax, sys_read
    int     80h     
    cmp     byte [ecx + edx - 1], 10
    jne     ClearTerminal
    ret

Here I use a 1 byte buffer and tell the OS to read 1 character, after the sys_read call, I check to see if the character is the EOL character (0xa), if it isn't loop back and get and test the next character until we are at the EOL and return.

Here is the fixed code:
sys_exit        equ     1
sys_read        equ     3
sys_write       equ     4
stdin           equ     0
stdout          equ     1
stderr          equ     3

SECTION     .data
szName          db      "What is your name? "
Name_Len        equ     $-szName
szHello         db      "Hello ", 0
Hello_Len       equ     $-szHello

SECTION     .bss
lpBuffer        resb    7
Buf_Len         equ     $-lpBuffer

Buf				resb	1

SECTION     .text
global      _start
    
_start:
    mov     ecx, szName
    mov     edx, Name_Len
    call    DisplayText

    mov     ecx, lpBuffer
    mov     edx, Buf_Len
    call    ReadText
    push    eax
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;    
    cmp		eax, Buf_Len
    jl		Good

	cmp 	byte [ecx + edx - 1], 10
	je		Good
	mov		byte [ecx + edx - 1], 10
	call	ClearTerminal
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 	
Good:
    mov     ecx, szHello
    mov     edx, Hello_Len
    call    DisplayText
    
    pop    	edx
    mov     ecx, lpBuffer
    call    DisplayText
    
    jmp     Exit
    
ClearTerminal:   
    mov 	edx, 1
	mov 	ecx, Buf
    mov 	ebx, stdin
    mov 	eax, sys_read
    int 	80h 	
	cmp		byte [ecx + edx - 1], 10
    jne 	ClearTerminal
   	ret
	
DisplayText:
    mov     eax, sys_write
    mov     ebx, stdout
    int     80H 
    ret
    
ReadText:
    mov     ebx, stdin
    mov     eax, sys_read
    int     80H
    ret

Exit:  
    mov     eax, sys_exit
    xor     ebx, ebx
    int     80H


This is what we added:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    cmp		eax, Buf_Len
    jl		Good

	cmp 	byte [ecx + edx - 1], 10
	je		Good
	mov		byte [ecx + edx - 1], 10
	call	ClearTerminal
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


DisplayText returns the number of characters read from stdin in eax, we compare this value to the size of our buffer and if eax is less than our buffer size, we continue on to the Good label since our buffer will contain the EOL character.

If eax is NOT less than our buffer size, meaning sys_write filled up our buffer completely, we check to see if the last character is the EOL, and if it is we continue on to Good

Hmm, the last character was not the EOL which means there is more data in stdin, so we put an EOL character in the last place of our buffer and clear the terminal buffer before continuing.

Attached File(s)



Is This A Good Question/Topic? 2
  • +

Replies To: NASM - Linux Terminal Input/Output w/int 80H

#2 danzar  Icon User is offline

  • D.I.C Head
  • member icon

Reputation: 8
  • View blog
  • Posts: 108
  • Joined: 10-December 08

Posted 19 July 2012 - 11:19 AM

Thank you, Love these...
Was This Post Helpful? 0
  • +
  • -

#3 RobertoSpartan  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 8
  • Joined: 07-June 10

Posted 22 August 2012 - 05:44 PM

...

This post has been edited by GunnerInc: 22 August 2012 - 05:46 PM
Reason for edit:: Removed quote of complete first post.

Was This Post Helpful? 0
  • +
  • -

#4 GunnerInc  Icon User is online

  • "Hurry up and wait"
  • member icon




Reputation: 876
  • View blog
  • Posts: 2,312
  • Joined: 28-March 11

Posted 22 August 2012 - 05:47 PM

Did you forget to post something besides quoting the whole first post?
Was This Post Helpful? 0
  • +
  • -

#5 RobertoSpartan  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 8
  • Joined: 07-June 10

Posted 22 August 2012 - 06:01 PM

At last!! thanks to you I've got a better understanding of how to program in Linux with Nasm on a terminal. I have had troubles finding out how to fix the issue you explained and how to solve.
I've added a 64 bit code here below and will mention your solution on my website (which still has to be done :) but I don't like html nor webapps). However I don't understand why you coded in the procedure ClearTerminal [ecx+edx-1].
Since edx remains 1 during the entire routine, we can solve that [ecx+edx-1] equals [ecx], so I've changed it in my code. Also I don't add a extra 0x0a but a 0x0d, so the program writes the message plus the inputted text and sets the prompt on a new line.
BUT hey guy, thanks a lot, I really apreciate your sharing and so will I (sharing not apreciating myself)

here's the code.
%ifdef COMMENT
Name:		sys_read
Build:		see makefile
Run:			./sys_read
Description:	The program asks for your name and writes it to STDOUT.
Remark:		1 -	If more characters are putted in than BUFFERLENGTH the program writes
				first number of characters to the buffer and the rest of them after the
				prompt, no check has be done here. This has to be done, but more specialized
				programs will not use the syscall read like in this demonstration.
			2 -	The write syscall is not putted into a subroutine, not that I don't like them
				but	a: the program isn't shorter with a subroutine.
					b: the program is faster without a subroutine.
			3 -	For the benefit of read improvements variables wich equals zero like STDIN
				are still used to be assigned to a register. Better and shorter will be xor<reg>,<reg>
				to immediatelly put a zero into that register.(shorter program)
%endif

BITS 64

section .bss
section .data
	%include "sys_read.inc"
	
	startMsg			db		"What's your name? "
	startMsgLength		equ		$-startMsg
  
	endMsg			db		"Hello "
	buffer			times	BUFFERLENGTH+1 db 0
	dummyBuffer		db		0				; help buffer to clear STDIN on buffer overflow
	endMsgLength		equ		$-endMsg
	

section .text
global _start

_start:

	; print the message
	mov		rsi,startMsg			; start of message
	mov		rdx,startMsgLength		; the length of the message to display
	push		STDOUT				; stdout in ...
	pop		rdi					; ...rdi
	push		SYS_WRITE				; syscall for write...
	pop		rax					; ...in rax
	syscall						; call kernel
	
	; read the answer
	push		STDIN				; standard input device
	pop		rdi					; ... in rdi
	mov		rsi,buffer			; offset of the buffer to store input
	xor		rdx,rdx				; zero rdx
	mov		dl,BUFFERLENGTH+1		; the length of the buffer !carefull BUFFERLENGTH is not always a byte
	push		SYS_READ				; syscall for read...
	pop		rax					; ...in rax
	syscall						; call kernel
	
	; check if more characters are given then the length of the buffer
	; from here on the code is inspired by GunnerInc
	push		rax
	cmp		rax,BUFFERLENGTH+1
	jl		writeMessage
	cmp		byte[rsi+rdx-1],10
	je		writeMessage
	mov		byte[rsi+rdx-1],13
clearSTDIN:
	push		STDIN
	pop		rdi
	mov		rsi,dummyBuffer
	xor		rdx,rdx
	inc		rdx
	push		SYS_READ
	pop		rax
	syscall
	cmp		byte [rsi+rdx-1], 10
	jne		clearSTDIN

writeMessage:	
	mov		rsi,endMsg			; start of message
	mov		rdx,endMsgLength		; the length of the message to display
	push		STDOUT				; stdout in ...
	pop		rdi					; ...rdi
	push		SYS_WRITE				; syscall for write...
	pop		rax					; ...in rax
	syscall						; call kernel
	
	; unless the values of STDIN or STDOUT are tampered, we espect no error
	push		ENOERR				; Exit with return code of 0 (no error)
	pop		rdi					; in rdi
	push		SYS_EXIT				; syscall for exit...
	pop		rax					; ...in rax
	syscall						; call kernel


don't read the comments on top of the file, there are already solved. Also sorry for not seeing that there was a "add a code snippet" It's my first time here.

This post has been edited by GunnerInc: 22 August 2012 - 06:40 PM
Reason for edit:: Added code tags

Was This Post Helpful? 0
  • +
  • -

#6 GunnerInc  Icon User is online

  • "Hurry up and wait"
  • member icon




Reputation: 876
  • View blog
  • Posts: 2,312
  • Joined: 28-March 11

Posted 22 August 2012 - 06:39 PM

Glad I could help!

When posting code, please use code tags!
Posted Image
Was This Post Helpful? 0
  • +
  • -

#7 RobertoSpartan  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 8
  • Joined: 07-June 10

Posted 23 August 2012 - 04:52 AM

Indeed, I discovered it one minute to late.
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1