Asmtut 5: More snappy interaction

by
2012-11-04

This was originally posted on Google+, which has now been shut down. It was helpfully converted to Markdown by Robert Jacobson after which I adjusted it for reposting here. I have dated it at its original posting date, but it was posted here on 2019-09-07.


At this point, we are tired of the line-buffered interface. Let's make it more responsive!

Step 11: Cleanup

First, let's just clean up our code a bit by putting our constants on top:

; System defines:

SYS_write       equ 0x02000004
SYS_read        equ 0x02000003
SYS_exit        equ 0x02000001

STDIN_FILENO    equ 0
STDOUT_FILENO   equ 1

Substitute the values in your code accordingly.

You can give the program constants and macros a descriptive header, too: ; Program defines: Both of these blocks can go above the .data section.

Additionally, input_char is a bit weird, because we are specifying a value we are never actually reading. This dummy value is stored in the compiled executable, wasting a whole byte. In general, it is better to specify data where we don't care about the initial value in the .bss section. Here, you only reserve the necessary number of bytes you need for the data, rather than specifying the data, so instead of db we use resb for reserving bytes:

section .bss

input_char resb 1

Back in the old DOS days, this memory would be uninitialized. It would just contain whatever was left there by previously running processes. That allows leakage of potentially sensitive information, so in modern operating systems this memory gets zero initialized by the OS. The name of this section is an historical accident detailed on Wikipedia.

We have now cleaned up everything so it looks something like part1.asm.

Step 12: ioctl: Structs and bitmasks

We need to disable echoing and line buffering for standard in. We are going to implement something like the C versions here: http://www.glue.umd.edu/afs/glue.umd.edu/system/info/olh/Programming/Answers_to_Common_Questions_about_C/c_getch and http://www.glue.umd.edu/afs/glue.umd.edu/system/info/olh/Programming/Answers_to_Common_Questions_about_C/c_terminal_echo

We will need ioctl:

SYS_ioctl       equ 0x02000000 + 54

The first argument is the filedescriptor we want to control. STDIN_FILENO should be just what the doctor ordered.

The second argument is the request for ioctl. It specifies wihch underlying functionality we are actually looking for. It looks like ioctl is just an indirection step. The request values we need are symbolically TIOCGETP and TIOCSETP, and they seem to be defined in /usr/include/sys/ioctl_compat.h:

#define TIOCGETP    _IOR('t', 8,struct sgttyb)/* get parameters -- gtty */
#define TIOCSETP    _IOW('t', 9,struct sgttyb)/* set parameters -- stty */

The easiest way to evaluate these C macros is to make a tiny C program assigning these values to local variables and disassemble the result. Or print the values to standard output if you prefer. I found these values:

TIOCGETP        equ 0x40067408
TIOCSETP        equ 0x80067409

When using either of these with ioctl, we need to supply a struct sgttyb* as the third argument. Pointers are easy; that's just the same thing as an address. But what about the struct? Let's look at its definition in the same header file:

struct sgttyb {
    char    sg_ispeed;      /* input speed */
    char    sg_ospeed;      /* output speed */
    char    sg_erase;       /* erase character */
    char    sg_kill;        /* kill character */
    short   sg_flags;       /* mode flags */
};

While the precise meaning of this is platform defined, it is not hard to guess correctly what it boils down to. Each char is one byte, this is actually well defined, and the short is two bytes. In assembly-speak on x86, a 16 bit value is called a word, so a short is a word. Additionally, we have to consider that the values might be aligned to 32 bit addresses or something. The rules for this are defined in the ABI, but it is much quicker to test it in C and disassemble again :) The values are packed densely, so an instance of this struct translates to:

sgttyb_instance:
sg_ispeed   resb 1
sg_ospeed   resb 1
sg_erase    resb 1
sg_kill     resb 1
sg_flags    resw 1 ; Note the use of _reserve word_ here
sgttyb_instance_size equ $-sgttyb_instance

Now, we could give in the address of sgttyb_instance, and stuff would work!

However, let's abstract things up a notch and use NASM's built-in macros for working with structs. We can define a structure rather than just an instance of it, with struc and endstruc:

struc sgttyb
    .sg_ispeed: resb    1
    .sg_ospeed: resb    1
    .sg_erase:  resb    1
    .sg_kill:   resb    1
    .sg_flags:  resw    1
endstruc

This can go in the System defines-block, since it doesn't define or reserve any data. Instead, it gives us seven new symbols to work with; First, the offsets of each struct member:

; The struc-declaration above gives us these:
sgttyb.sg_ispeed    equ 0
sgttyb.sg_ospeed    equ 1
sgttyb.sg_erase     equ 2
sgttyb.sg_kill      equ 3
sgttyb.sg_flags     equ 4

The size of the struct:

sgttyb_size    equ 6

And finally, just to make things work properly for NASM:

sgttyb equ 0 ; This one is not interesting

So now, we can reserve the correct number of bytes for one sgttyb instance in the .bss section:

state   resb sgttyb_size ; Doesn't work in NASM 2.10.05 and earlier

Unfortunately, due to a bug in NASM, we need to allocate more space:

state   resb sgttyb_size * 2 ; *2 to work around bug :(

We can finally do the syscall:

mov rax, SYS_ioctl
mov rdi, STDIN_FILENO
mov rsi, TIOCGETP
mov rdx, state
syscall

If we assume that the syscall succeeded, we can jump right into manipulating sg_flags. We want to turn off echoing and line-buffering. Disabling echoing is done by turning off the ECHO flag. Disabling line-buffering is done by enabling "half-cooked" mode or turning on the CBREAK flag. You can read about the crazy naming on Wikipedia.

ECHO and CBREAK are defined in ioctl_compat.h:

CBREAK          equ 0x00000002  ; half-cooked mode
ECHO            equ 0x00000008  ; echo input

Now, we need to do the assembly equivalent of the C code:

state.sg_flags &= ~ECHO;
state.sg_flags |= CBREAK;

Even though it is not strictly necessary, we are going to load sg_flags into a register to work with it, instead of working directly on memory:

mov ax, [state + sgtty.sg_flags]

Now, fundamental arithmetics is pretty straightforward:

and ax, ~ECHO ; ax &= ~ECHO -- ~ECHO is evaluated by NASM
or ax, CBREAK ; ax |= CBREAK

And then, store it back into the struct:

mov [state + sgtty.sg_flags], ax

We are now ready to set this with ioctl:

mov rax, SYS_ioctl
mov rdi, STDIN_FILENO
mov rsi, TIOCSETP ; SET this time around
mov rdx, state
syscall

This should all go in main:, before .main_loop:, as it serves as initializing code.

When you have done this, the game should be slightly more interactive since all you have to do now is press a key to make things happen. Note that even though it is no longer line buffered, it is still blocking, so you do need to press buttons. Next time we will make it non-blocking and real time!

You might end up with echoing disabled in your shell after running this. Be aware that you can reset your terminal to a good state with the command reset.

Your code should now look similar to part2.asm.

Exercise: We should be good citizens and reset sg_flags to its initial value upon exit. Store the original sg_flags in a new variable and call ioctl to reset to this state in .exit:

Solution available in exercise.asm.

Next lesson: Asmtut 6: Live interaction

, 2012