NASM - The Netwide Assembler
NASM Forum => Other Discussion => Topic started by: isc.jcl.apex on May 05, 2016, 03:41:22 AM
-
Good night, I need some help.
I won't lie, this is an assignment for my university, but I haven't found anything related to.
What I need it's to print messages to the terminal (in a loop style) until the user "halts" the execution pressing ESC key.
This is what I have so far, I'm not asking for anyone to solve my issues, but it would be nice to point me into the right direction.
section .data
ICANON: equ 1<<1
TCGETS: equ 0x5401
TCSETS equ 0x5402
sys_exit: equ 60
sys_read: equ 0
sys_write: equ 1
sys_open: equ 2
sys_close: equ 3
sys_ioctl: equ 16
stdin: equ 0
stdout: equ 1
s1: db "Cadena: 1", 10
s1l: equ $-s1
s2: db "Cadena: 2", 10
s2l: equ $-s2
section .bss
buffer: resb 2
termios: resb 36
section .text
write_stdout:
mov rax, sys_write
mov rdi, stdout
syscall
ret
get_keycode:
mov rax, sys_read
mov rdi, stdin
mov rsi, buffer
mov rdx, 1
syscall
ret
write_stdin_termios:
mov rax, sys_ioctl
mov rdi, stdin
mov rsi, TCSETS
mov rdx, termios
syscall
ret
read_stdin_termios:
mov rax, sys_ioctl
mov rdi, stdin
mov rsi, TCGETS
mov rdx, termios
syscall
ret
canonical_off:
call read_stdin_termios
mov rax, ICANON
not rax
and [termios+12], rax
call write_stdin_termios
ret
canonical_on:
call read_stdin_termios
or dword [termios+12], ICANON
call write_stdin_termios
ret
GLOBAL _start
_start:
nop
nop
call canonical_off
xor rsi, rsi
read_code:
mov rsi, s1
mov rdx, s1l
call write_stdout
cmp rsi, 24
je end
call get_keycode
mov al, byte[buffer]
cmp al, "e"
je end
jmp read_code
end:
mov rax, sys_exit
mov rdi, 0
syscall
Thanks in advance.
-
Hi isc.jcl.apex,
Welcome to the Forum. I'm not very familiar with 64-bit code, and can't test it for ya. I've done similar things in 32-bit code. I don't understand this part:
_start:
nop
nop
call canonical_off
xor rsi, rsi
read_code:
mov rsi, s1
mov rdx, s1l
call write_stdout
cmp rsi, 24
je end
What's 24? I don't think rsi is likely to have that value. So you go on to check for the letter 'e'. That part looks like it should work. Do you need this to be ESC instead of 'e'? Look for 0x1B or 27 decimal. ESC may generate more than one character. I don't know what that's going to do with I_CANON turned off. You might need to flush STDIN to clean up any mess. You may need to turn I_CANON back on before you exit (I think it 'll be alright?). It looks like you're off to a pretty good start.
Tell us more about what you need this to do... or not do... Sorry I can't test it...
Best,
Frank
-
That bit of a part is a victim of copy/paste (my fault, here's where I took almost all of the code http://forum.nasm.us/index.php?topic=1714.0 (http://forum.nasm.us/index.php?topic=1714.0)).
By now, the "e" key was just to test if it worked, ESC key returns me "1b" so that's what finally I would be looking for.
What this "program" needs to do (in it's fullness) it's:
1° Read two 8 bit binary numbers (Something like 10101010).
2° Realize bitwise operations between them
3° Print result to terminal in a loop way (printing the same results over and over)
4° Halt execution when the user hits ESC key
By now, my code is working in the other topics (I know how to read the numbers, translate them to real binary digits, do bitwise operations and convert them again to ASCII chars), but the issue here it's what I don't know how to "print" and "wait for user input".
I've been reading that I need to get terminal in RAW mode, so that way I implement my own "key handler", in this way, all I'd need is wait for the "ESC" keycode and halt execution.
Last night (I'm from méxico, time now is 8:22 AM) I keep up with something and looked up how to put terminal in RAW mode in C (Because C it's like a not-so-high abstraction of assembly and linux syscalls are just like in C) and I achieved how to put the terminal in RAW mode and print in a loop style, now my issue trascends to bringing back the terminal to normal state.
Here's my last updated code
section .data
ICANON: equ 1<<1
TCGETS: equ 0x5401
TCSETS: equ 0x5402
TCGETA: equ 0x5405
TCSETA: equ 0x5406
sys_exit: equ 60
sys_read: equ 0
sys_write: equ 1
sys_open: equ 2
sys_close: equ 3
sys_ioctl: equ 16
stdin: equ 0
stdout: equ 1
s1: db "Cadena: 1", 10
s1l: equ $-s1
s2: db "Cadena: 2", 10
s2l: equ $-s2
section .bss
buffer: resb 2
termios: resb 36
section .text
write_stdout:
mov rax, sys_write
mov rdi, stdout
syscall
ret
get_keycode:
mov rax, sys_read
mov rdi, stdin
mov rsi, buffer
mov rdx, 1
syscall
ret
write_stdin_termios:
mov rax, sys_ioctl
mov rdi, stdin
mov rsi, TCSETA
mov rdx, termios
syscall
ret
read_stdin_termios:
mov rax, sys_ioctl
mov rdi, stdin
mov rsi, TCGETS
mov rdx, termios
syscall
ret
canonical_off:
call read_stdin_termios
mov rax, ICANON
not rax
and [termios+12], rax
call write_stdin_termios
ret
canonical_on:
call read_stdin_termios
or dword [termios+12], ICANON
call write_stdin_termios
ret
GLOBAL _start
_start:
nop
nop
call canonical_off
read_code:
mov rsi, s1
mov rdx, s1l
call write_stdout
call get_keycode
mov al, byte[buffer]
cmp al, "e"
je end
jmp read_code
end:
call canonical_on
mov rax, sys_exit
mov rdi, 0
syscall
By now, it prints to STDOUT and no matter which key you press (unless it's e) it keeps going, so when you press e, it stops, but it lefts the terminal in RAW and ECHO OFF.
Here you can download my code and a Makefile so you can assembly/link it to test. https://drive.google.com/file/d/0B2iYnMM5TaIUbWVpM3h1Q2YzNEk/view?usp=sharing (https://drive.google.com/file/d/0B2iYnMM5TaIUbWVpM3h1Q2YzNEk/view?usp=sharing)
-
So... is your problem that canonical_on isn't working? Or do you need it to keep printing without having to press a key? (which does not seem useful, but seems to be what you describe) I think for that, you need sys_fcntl to make stdin non-blocking. I don't remember exactly how I did that, but I remember it was a mess not to undo it! This used to be very easy in DOS, but a PITA in Linux.
Best,
Frank
-
I need it to keep printing until the user presses ESC (I know it's not useful, but whatever the teacher wants....).
I'll read about sys_fcntl to see if that's what I should be doing.
As a side note, I've been testing other stuff and I think I've found something, in C you can get a struct of termios, that represents practically the configuration of the tty, and there are two values, specifically at termios.c_cc[VTIME] and termios.c_cc[VMIN] that can be configured to specify time and bytes needed to return after a read call, but so far, I haven't found how to set up this values in assembly.
But thank you very much for helping me, and yes, it really is a PITA.
-
Well, I solved my problems.
I find out how to modify correctly the termios struct after all, I just needed a sample C program, a debugger and some insight...
Well, in case someone else searches for this, here's what I've found.
(http://i.imgur.com/DAp3j6I.png)
In termios(3), we can find this:
MIN == 0, TIME == 0 (polling read)
If data is available, read(2) returns immediately, with the lesser of the number of bytes available, or the number of bytes
requested. If no data is available, read(2) returns 0.
That be it, we only need to fetch the termios struct for our current tty, modify these values, and we get a looping console (because read will always return even if there were no bytes read).
Here's my final code, working, and ready to deliver.
PS: Sorry for spanish comments, I have to deliver this and others in a couple of hours.
; IMPORTANTE:
; http://stackoverflow.com/questions/3305005/how-do-i-read-single-character-input-from-keyboard-using-nasm-assembly-under-u
; https://filippo.io/linux-syscall-table/
; http://forum.nasm.us/index.php?topic=1714.0
; https://msdn.microsoft.com/en-us/library/windows/hardware/ff561499(v=vs.85).aspx
; https://github.com/torvalds/linux/blob/9256d5a308c95a50c6e85d682492ae1f86a70f9b/include/uapi/asm-generic/ioctls.h
; Constantes definidas que no se tienen porque guardar en memoria
%define SYS_READ 0
%define SYS_WRITE 1
%define SYS_EXIT 0x3c
%define STDIN 0
%define STDOUT 1
%define IOCTL 16
; T (0x54) = Terminal | GET STRUCT / SET STRUCT = GETS /SETS
%define TCGETS 0x5401
%define TCSETS 0x5402
%define ICANON 2 ; Para la bandera canonical
%define ECHO 4 ; Para la bandera de echo
%define VTIME 5 ; Posición de VTIME en CC_C
%define VMIN 6 ; Posición de VMIN en CC_C
%define CC_C 18 ; Offset para localizar CC_C
; Para más información, ver termios(3)
section .data
b1mes: db "Número binario 1:",10
b1mesl: equ $-b1mes
b2mes: db "Número binario 2:", 10
b2mesl: equ $-b2mes
ormsg: db "Operación OR: 00000000",10
ormgsl: equ $-ormsg
andmsg: db "Operación AND: 00000000",10
andmgsl: equ $-andmsg
xormsg: db "Operación XOR: 00000000",10
xormsgl: equ $-xormsg
notmsg: db "Operación NOT (1): 00000000", 10
notmsgl: equ $-notmsg
notmsg2: db "Operación NOT (2): 00000000", 10
notmsgl2: equ $-notmsg2
splitter: db "-----------------------------", 10
splitterl: equ $-splitter
section .bss
; El número es de 8 bits, pero cada caracter es 1 byte
b1: resb 9 ; Necesitamos 1 byte por cada caracter
b2: resb 9 ; por eso reservamos 8
size: equ 9
; Se están leyendo 9 caracteres, ya que se tiene que procesar el LF
; de caso contrario, al hacer otra llamada a READ se leerá el LF y se
; entenderá como que ya fue otra linea, aunque no fue así.
buffer: resb 2
termios: resb 46
section .text
GLOBAL _start
; Procedimientos tomados del primer enlace en el encabezado.
; Gracias a los usuarios de StackOverflow: Callum y Richard Fearn
canonical_off:
call read_stdin_termios
; Limpiamos la bandera de ICANON
push rax
mov rax, ICANON
not rax
and [termios+12], rax
mov byte[termios+CC_C+VTIME], 0 ; Ponemos el tiempo de espera a 0
mov byte[termios+CC_C+VMIN], 0 ; Ponemos la cantidad mínima a 0
pop rax
call write_stdin_termios
ret
echo_off:
call read_stdin_termios
; Limpiamos la bandera de ECHO
push rax
mov rax, ECHO
not rax
and [termios+12], rax
pop rax
call write_stdin_termios
ret
canonical_on:
call read_stdin_termios
; Restauramos la bandera de CANONICAL
or dword [termios+12], ICANON
mov byte[termios+CC_C+VTIME], 0 ; Tiempo espera = 0
mov byte[termios+CC_C+VMIN], 1 ; Bytes mínimos = 1(default)
call write_stdin_termios
ret
echo_on:
call read_stdin_termios
; Restauramos la bandera de ECHO
or dword [termios+12], ECHO
call write_stdin_termios
ret
read_stdin_termios:
push rax
push rbx
push rcx
push rdx
mov rax, IOCTL
mov rdi, STDIN
mov rsi, TCGETS
mov rdx, termios
syscall
pop rdx
pop rcx
pop rbx
pop rax
ret
write_stdin_termios:
push rax
push rbx
push rcx
push rdx
mov rax, IOCTL
mov rdi, STDIN
mov rsi, TCSETS
mov rdx, termios
syscall
pop rdx
pop rcx
pop rbx
pop rax
ret
; Se encargará de hacer las operaciones entre los números
; guardarlos en memoria para después imprimir de manera cíclica
; Recibe: r12 = Numero 1
; r13 = Número 2
; Modifica: rax, rbx, rcx
bitwise:
; OR
mov rax, r12 ; Movemos el número designado para la operación
or rax, r13 ; 1er Operación: OR Número 1 con Número 2
push r12 ; Guardamos los valores, ya que es un estandar el guardar
push r13 ;los registros que usamos antes de ejecutar un proc.
mov rbx, ormsg ; Movemos la cadena en la cual se guardará el número
mov rcx, ormgsl ; Indicamos el tamaño de la cadena
call number_to_memory ; Llamamos el procedimiento.
pop r13
pop r12
; AND - Se repite el procedimiento, pero ahora para la instrucción AND
mov rax, r12 ; Movemos el número designado para la operación
and rax, r13 ; 2da Operación: AND Número 1 con Número 2
push r12 ; Guardamos los valores, ya que es un estandar el guardar
push r13 ;los registros que usamos antes de ejecutar un proc.
mov rbx, andmsg ; Movemos la cadena en la cual se guardará el número
mov rcx, andmgsl ; Indicamos el tamaño de la cadena
call number_to_memory ; Llamamos el procedimiento.
pop r13
pop r12
; XOR
mov rax, r12 ; Movemos el número designado para la operación
xor rax, r13 ; 3er Operación: XOR Número 1 con Número 2
push r12 ; Guardamos los valores, ya que es un estandar el guardar
push r13 ;los registros que usamos antes de ejecutar un proc.
mov rbx, xormsg ; Movemos la cadena en la cual se guardará el número
mov rcx, xormsgl ; Indicamos el tamaño de la cadena
call number_to_memory ; Llamamos el procedimiento.
pop r13
pop r12
; NOT Número 1
mov rax, r12 ; Movemos el número designado para la operación
not rax ; NOT Número 1
push r12 ; Guardamos los valores, ya que es un estandar el guardar
push r13 ;los registros que usamos antes de ejecutar un proc.
mov rbx, notmsg ; Movemos la cadena en la cual se guardará el número
mov rcx, notmsgl ; Indicamos el tamaño de la cadena
call number_to_memory ; Llamamos el procedimiento.
pop r13
pop r12
; NOT Número 2
mov rax, r13 ; Movemos el número designado para la operación
not rax ; NOT Número 2
push r12 ; Guardamos los valores, ya que es un estandar el guardar
push r13 ;los registros que usamos antes de ejecutar un proc.
mov rbx, notmsg2 ; Movemos la cadena en la cual se guardará el número
mov rcx, notmsgl2 ; Indicamos el tamaño de la cadena
call number_to_memory ; Llamamos el procedimiento.
pop r13
pop r12
ret
; Mueve el resultado a la cadena en memoria especificada
; El tamaño es fijo a 8 bytes
; Recibe: RAX = Número, RBX = Cadena en memoria, RCX = Tamaño cadena
; Regresa: Nada
; Modifica: RAX, RBX, RCX, R14
number_to_memory:
add rbx, rcx ; Le sumamos el tamaño de la cadena
dec rbx ; Para omitir el salto de línea
mov rcx, 0 ; Inicializamos el desplazamiento
mov r14, rax ; Movemos el valor para respaldarlo
cicle:
inc rcx ; Incrementamos el contador para la posición
and rax, 00000001b ; Una máscara para sólo obtener el primer número
dec rbx ; Le restamos 1 a la posición en memoria
add byte[rbx], al ; Le sumamos el valor al caracter en memoria
shr r14, 1 ; Eliminamos el número que ya pasamos a memoria
mov rax, r14 ; Movemos el nuevo número al registro r14
cmp rcx, 8
jne cicle ; Mientras no sean 8, seguimos pasando a memoria
ret
; Para leer los caracteres
get_keycode:
mov rax, SYS_READ
mov rdi, STDIN
mov rsi, buffer
mov rdx, 1
syscall
ret
; Imprime un mensaje a STDOUT
;
; Registros:
; r12 - Direccio? Memoria Mensaje
; r13 - Longitud mensaje
;
print:
mov rax, SYS_WRITE ; Syscall 1 = Escritura
mov rdi, STDOUT ; FD: Descriptor a Salida Estandar
mov rsi, r12 ; Apuntador al buffer
mov rdx, r13 ; Apuntador al tamaño del buffer
syscall
ret
; Lee desde STDIN un valor, el cual se guarda en el buffer designado
; Registros:
; r12 - Dirección del buffer
; r13 - Cantidad de bytes a leer
read_stdint:
mov rax, SYS_READ ; RAX 0 = Syscall (Read)
mov rdi, STDIN ; RDI 0 = FD (STDIN)
mov rsi, r12 ; RDI = dirección buffer
mov rdx, r13 ; RDX = Bytes a leer
syscall
ret
; Convierte un número ascii (representación binaria) a su forma correcta
; Registros:
; r12 - Dirección del número
; r13 - Tamaño
; Regresa:
; rax = numero en binario
default_size: equ 8 ; Un tamaño estandar para este programa
ascii_to_binary: ; Guardamos los registros
xor rax, rax ; Limpiamos RAX
xor rbx, rbx ; Limpiamos RBX
xor rcx, rcx ; Ponemos en 0 el valor que servirá para offset
iter:
shl rbx, 1 ; Hacemos un shift izquierdo (para posicionar
mov al, byte[r12+rcx] ; Movemos de memoria el dígito
sub rax, 30h ; Restamos 30h = Valor del 0 en ASCII
add rbx, rax ; Movemos el nuevo valor a RBX
inc rcx ; correctamente el bit)
cmp rcx, r13
jne iter ; Verifica si terminó de leer el buffer
mov rax, rbx
ret
_start:
nop
nop
; Primero hay que mostrar el primer mensaje
mov r12, b1mes ; Ponemos en los registros los valores esperados
mov r13, b1mesl ; los cuales son solicitados por el procedimiento
call print ; llamado print.
; Leemos el primer número
mov r12, b1 ; Dirección memoria para el primer número
mov r13, size ; Leeremos 8 bytes
call read_stdint ; Leemos
; Segundo número
mov r12, b2mes ; Ponemos en los registros los valores esperados
mov r13, b2mesl ; los cuales son solicitados por el procedimiento
call print ; llamado print.
; Leemos el primer número
mov r12, b2 ; Dirección memoria para el primer número
mov r13, size ; Leeremos 8 bytes
call read_stdint ; Leemos
; Conversion
mov r12, b1
mov r13, default_size
call ascii_to_binary
; Guardamos el número y convertimos el siguiente
push rax
mov r12, b2
mov r13, default_size
call ascii_to_binary
pop r12 ; r12 = Número 1
mov r13, rax ; r13 = Número 2
call bitwise
mov r12, splitter ; Imprimimos el divisor de línea
mov r13, splitterl
call print
call canonical_off
call main_cicle
; Ahora que tenemos ambos, procedemos a hacer la comparación
jmp end ; Saltamos a la salida del programa
; Aquí se imprimirán los números a consola
; esperando a que el usuario detenga la impresión al presionar la tecla ESC
main_cicle:
mov r12, ormsg
mov r13, ormgsl
call print
mov r12, andmsg
mov r13, andmgsl
call print
mov r12, xormsg
mov r13, xormsgl
call print
mov r12, notmsg
mov r13, notmsgl
call print
mov r12, notmsg2
mov r13, notmsgl2
call print
; Listen for ESC char
call get_keycode
mov al, byte[buffer]
cmp al, 0x1b
je end
jmp main_cicle
ret
end:
call canonical_on
mov rax, SYS_EXIT
mov rdi, 0
syscall
-
Sounds good. If it works, don't worry about what language the comments are in!
Best,
Frank