Lesson 04-Input and Output

Console I/O operations involve a program interacting with the user through standard input (stdin), standard output (stdout), and standard error (stderr). The core objective is to convert user input into data that the program can process and to convert program output into human-readable information.

The Essence of Input and Output

  • Input: Reading data from external devices (keyboard, mouse) and converting it into a binary representation in memory.
  • Output: Converting binary data in memory into signals recognizable by external devices (monitor, printer).

Cross-Platform Differences

Different operating systems implement console I/O differently:

  • Linux/macOS: Based on POSIX standards, using system calls (e.g., read, write) to operate on terminal devices (/dev/tty).
  • Windows: Uses Win32 API functions (e.g., ReadConsole, WriteConsole) to manipulate console handles (HANDLE).
  • Real Mode (BIOS): Early x86 systems directly manipulated hardware via BIOS interrupts (e.g., int 0x10, int 0x16).

Linux System Calls

sys_write (System Call #4)

section .data
    msg db 'Hello, World!', 0xA     ; String + newline
    len equ $ - msg                 ; Calculate string length

section .text
global _start
_start:
    ; System call number (4) → eax
    mov eax, 4
    ; File descriptor (1 = stdout) → ebx
    mov ebx, 1
    ; Buffer address → ecx
    mov ecx, msg
    ; Length → edx
    mov edx, len
    ; Trigger system call
    int 0x80

    ; Exit program (system call #1)
    mov eax, 1
    xor ebx, ebx        ; Return code 0
    int 0x80

sys_read (System Call #3)

section .bss
    buffer resb 100     ; Reserve 100-byte buffer

section .text
global _start
_start:
    mov eax, 3          ; sys_read
    mov ebx, 0          ; stdin file descriptor
    mov ecx, buffer     ; Buffer address
    mov edx, 100        ; Maximum read length
    int 0x80            ; eax now contains actual bytes read

    ; Process input data here...

    mov eax, 1          ; sys_exit
    xor ebx, ebx        ; Return code 0
    int 0x80

Windows API Calls (Comparison)

; MASM syntax example
extrn ExitProcess:PROC
extrn GetStdHandle:PROC
extrn WriteConsoleA:PROC

.data
    msg      db 'Hello, Windows!', 0xD, 0xA, 0
    msg_len  dd $ - msg
    std_out  dd -11     ; STDOUT handle constant

.code
main proc
    ; Get standard output handle
    push std_out
    call GetStdHandle
    mov ebx, eax        ; Save handle

    ; Write to console
    push 0              ; lpReserved
    push offset msg_len
    push offset msg
    push ebx            ; Handle
    call WriteConsoleA

    ; Exit program
    push 0
    call ExitProcess
main endp

BIOS Interrupt Services

The BIOS (Basic Input/Output System) is firmware in real-mode x86 architecture that provides hardware-agnostic low-level services. BIOS services are invoked via interrupt instructions (int) to directly operate devices like keyboards and displays.

Core Mechanism of BIOS Interrupts

  • Interrupt Vector Table (IVT): Memory region 0x00000–0x003FF storing 4-byte entry addresses for each interrupt handler.
  • Interrupt Number: 8-bit value (0–255); key console-related interrupts include:
    • 0x10: Video services
    • 0x16: Keyboard services
    • 0x21: DOS services

Video Services (int 0x10)

int 0x10 provides BIOS video display services. Functionality is determined by the AH register function code.

AH Function CodeDescriptionInput RegistersOutput Registers
0x00Set video mode (e.g., 80×25 text mode)AL = mode (0x03 = 80×25 color)—
0x02Set cursor positionDH = row (0–24), DL = column (0–79)—
0x0ETeletype output (echo character, auto-move cursor)AL = char code, BH = page (0)—
0x13Display string (with attributes)AL = mode (0=normal, 1=attr first)
BL = attribute (color)
ES:BP = string address
—

Example: Output “Hello, BIOS!” (80×25 Text Mode)

org 0x7C00      ; Boot sector load address
bits 16

start:
    ; Initialize video mode (80×25 color text)
    mov ah, 0x00
    mov al, 0x03
    int 0x10

    ; Set cursor to (0,0)
    mov ah, 0x02
    xor dh, dh      ; Row 0
    xor dl, dl      ; Column 0
    mov bh, 0       ; Page 0
    int 0x10

    ; Output string "Hello, BIOS!"
    mov ah, 0x0E
    mov si, msg     ; DS:SI points to string
    mov cx, len     ; String length

print_loop:
    lodsb           ; AL = [DS:SI++], SI auto-increments
    int 0x10        ; Output character in AL
    loop print_loop

    hlt             ; Halt CPU

msg db 'Hello, BIOS!', 0
len equ $ - msg

Keyboard Services (int 0x16)

int 0x16 provides BIOS keyboard input services. Functionality is controlled by the AH register function code.

AH Function CodeDescriptionInput RegistersOutput Registers
0x00Wait for key press (blocking)—AL = ASCII code, AH = scan code
0x01Check for key press (non-blocking)—ZF=1 if no key; else AL = ASCII
0x02Read extended key (e.g., F-keys, arrows)—AL = extended code, AH = scan code

Example: Read and Echo Keystrokes

org 0x7C00
bits 16

start:
    ; Initialize video mode
    mov ah, 0x00
    mov al, 0x03
    int 0x10

wait_key:
    mov ah, 0x00
    int 0x16        ; Block until key pressed

    ; Echo character (AL holds ASCII)
    mov ah, 0x0E
    int 0x10

    ; Exit if 'q' pressed (ASCII 0x71)
    cmp al, 'q'
    je exit
    jmp wait_key

exit:
    hlt

Tip

Note: Keyboard scan code vs. ASCII code (partial mapping):
For example, the ‘A’ key has scan code 0x1E and ASCII code 0x41 (uppercase).

DOS Interrupt Services

DOS (Disk Operating System) was the dominant PC OS in early days, providing services via int 0x21. Functionality is selected by the AH register function code.

AH Function CodeDescriptionInput RegistersOutput Registers
0x01Read char from stdin (echoed)—AL = char code
0x02Write char to stdoutDL = char code—
0x09Write string to stdout ($-terminated)DX = string address—
0x3CCreate fileCX = attributes (0=normal)
DX = filename (ASCII-Z)
AX = file handle (0=fail)
0x3DOpen fileAL = access mode (0=read, 1=write, 2=r/w)
DX = filename
AX = file handle (0=fail)
0x3EClose fileBX = file handleAX = error code (0=success)

Example: Create and Write File “test.txt”

org 0x7C00
bits 16

start:
    ; Initialize DOS
    mov ax, 0x0000
    int 0x21

    ; Create file "test.txt" (attribute: normal)
    mov ah, 0x3C
    xor cx, cx              ; Attribute 0 (normal)
    mov dx, filename        ; DX → filename (null-terminated)
    int 0x21
    jc error                ; Jump if carry set (AX ≠ 0 → fail)

    ; Write string "Hello, DOS!"
    mov ah, 0x40            ; Write file function
    mov bx, ax              ; BX = file handle (from AX)
    mov cx, len             ; Write length
    mov dx, msg             ; DX → data buffer
    int 0x21
    jc error                ; Jump if write failed

    ; Close file
    mov ah, 0x3E
    mov bx, ax              ; BX = file handle
    int 0x21

    ; Print success message
    mov ah, 0x09
    mov dx, success_msg
    int 0x21
    jmp exit

error:
    ; Print error message
    mov ah, 0x09
    mov dx, error_msg
    int 0x21

exit:
    ; Exit to DOS
    mov ah, 0x4C
    int 0x21

filename      db 'test.txt', 0
msg           db 'Hello, DOS!', 0
len           equ $ - msg
success_msg   db 'File created successfully!$', 
error_msg     db 'Error creating file!$'

Modern OS File Operations

As operating systems evolved into protected mode (x86-32/x86-64), BIOS interrupts were replaced by system calls and APIs, which provide safer and more efficient I/O through kernel/user mode isolation.

Linux File System Calls

Linux follows POSIX standards, triggering system calls via syscall or int 0x80. Key I/O and file system calls include:

Syscall #FunctionParameters
sys_readRead from file descriptorfd, buf, count
sys_writeWrite to file descriptorfd, buf, count
sys_openOpen/create filefilename, flags, mode
sys_closeClose file descriptorfd
sys_exitTerminate processstatus

Example: Read File and Output to Console (C)

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

int main() {
    int fd = open("test.txt", O_RDONLY); // Open read-only
    if (fd < 0) {
        perror("open failed");
        return 1;
    }

    char buf[1024];
    ssize_t bytes_read = read(fd, buf, sizeof(buf));
    if (bytes_read < 0) {
        perror("read failed");
        close(fd);
        return 1;
    }

    write(STDOUT_FILENO, buf, bytes_read); // Output to stdout
    close(fd);
    return 0;
}

sys_open (System Call #5)

section .data
    filename db 'test.txt', 0
    flags    dd 2           ; O_RDWR = 2
    mode     dd 0x1FF       ; 0644 permissions

section .text
global _start
_start:
    mov eax, 5              ; sys_open
    mov ebx, filename
    mov ecx, [flags]
    mov edx, [mode]
    int 0x80
    mov [file_desc], eax    ; Save file descriptor

    ; ... file read/write operations ...

    mov eax, 6              ; sys_close
    mov ebx, [file_desc]
    int 0x80

    mov eax, 1              ; sys_exit
    xor ebx, ebx
    int 0x80

section .bss
    file_desc resd 1

sys_read / sys_write Example

; Read from file
mov eax, 3          ; sys_read
mov ebx, [file_desc]
mov ecx, buffer
mov edx, 100        ; Max read length
int 0x80

; Write to file
mov eax, 4          ; sys_write
mov ebx, [file_desc]
mov ecx, buffer
mov edx, eax        ; Use actual bytes read from last sys_read
int 0x80

Windows File API (Comparison)

Windows uses Win32 APIs for file operations. Core functions correspond to Linux syscalls but differ in interface:

FunctionLinux SyscallWindows APIKey Parameter Differences
Open filesys_openCreateFilelpFileName (wide char), dwDesiredAccess
Read filesys_readReadFilehFile (handle), lpBuffer, nNumberOfBytesToRead
Write filesys_writeWriteFilehFile, lpBuffer, nNumberOfBytesToWrite
Close filesys_closeCloseHandlehObject (handle)

Example: Read File and Output to Console (C on Windows)

#include <windows.h>
#include <stdio.h>

int main() {
    HANDLE hFile = CreateFile(
        L"test.txt",            // Filename (wide char)
        GENERIC_READ,           // Read-only access
        FILE_SHARE_READ,        // Share for reading
        NULL,                   // Default security
        OPEN_EXISTING,          // Open existing file
        FILE_ATTRIBUTE_NORMAL,  // Normal attribute
        NULL                    // No template
    );
    if (hFile == INVALID_HANDLE_VALUE) {
        printf("CreateFile failed: %d", GetLastError());
        return 1;
    }

    char buf[1024];
    DWORD bytes_read;
    if (!ReadFile(hFile, buf, sizeof(buf), &bytes_read, NULL)) {
        printf("ReadFile failed: %d", GetLastError());
        CloseHandle(hFile);
        return 1;
    }

    DWORD bytes_written;
    WriteFile(GetStdHandle(STD_OUTPUT_HANDLE),
              buf, bytes_read, &bytes_written, NULL); // Output to console

    CloseHandle(hFile);
    return 0;
}

Assembly Example (Windows API)

extrn CreateFileA:PROC
extrn ReadFile:PROC
extrn WriteFile:PROC
extrn CloseHandle:PROC

.data
    filename db 'test.txt', 0
    hFile    dd ?

.code
open_file:
    push 0
    push 0
    push 3                  ; GENERIC_READ | GENERIC_WRITE
    push 0
    push 0                  ; No sharing
    push 0x40000000         ; CREATE_ALWAYS
    push offset filename
    call CreateFileA
    mov [hFile], eax

    ; ... file operations ...

close_file:
    push [hFile]
    call CloseHandle

Formatted Output Implementation

Formatted output (e.g., printf("%d %s", 123, "hello")) converts data into a formatted string by parsing format specifiers and converting arguments to strings.

Core Steps of Formatted Output

  1. Parse format string: Scan for ordinary characters and format specifiers (e.g., %d, %s).
  2. Handle format specifiers: Based on type (int, string, float), fetch corresponding argument from variable argument list.
  3. Convert to string: Transform argument into required string format (e.g., integer → decimal string).
  4. Output result: Write resulting string to console or file.

Simple printf Implementation Idea

Decimal Integer Output Example

; Input: EAX = integer to print
print_int:
    push eax
    push ebx
    push ecx
    push edx

    mov ebx, 10             ; Divisor
    mov ecx, 0              ; Digit counter
    mov edi, buffer + 9     ; Point to end of buffer
    mov byte [edi], 0       ; Null-terminate

.convert_loop:
    xor edx, edx            ; Clear EDX for division
    div ebx                 ; EAX = quotient, EDX = remainder
    add dl, '0'             ; Convert to ASCII
    dec edi
    mov [edi], dl
    inc ecx
    test eax, eax
    jnz .convert_loop

    ; Now EDI points to start of number string
    mov eax, 4              ; sys_write
    mov ebx, 1              ; stdout
    mov ecx, edi
    mov edx, buffer + 10
    sub edx, ecx            ; Compute string length
    int 0x80

    pop edx
    pop ecx
    pop ebx
    pop eax
    ret

section .bss
    buffer resb 10          ; Enough for 32-bit integer string

Formatted String Output

; Simple implementation:
; mov eax, number
; push format_string
; call print_formatted

print_formatted:
    push ebp
    mov ebp, esp
    push eax
    push ebx
    push ecx
    push edx
    push esi
    push edi

    ; Parse format string (simplified)
    mov esi, [ebp+8]        ; Format string address
    mov edi, buffer
    mov ecx, 256            ; Buffer size

.format_loop:
    lodsb                   ; Load next char
    test al, al
    jz .done                ; End of string

    cmp al, '%'
    jne .copy_char          ; Not a format specifier

    lodsb                   ; Load format specifier
    cmp al, 'd'
    je .print_int
    cmp al, 's'
    je .print_string
    ; ... other formats ...

.copy_char:
    mov [edi], al
    inc edi
    jmp .format_loop

.print_int:
    pop eax                 ; Get previously pushed EAX
    ; ... call print_int subroutine ...
    jmp .format_loop

.print_string:
    ; Handle string argument...
    jmp .format_loop

.done:
    mov byte [edi], 0       ; Null-terminate

    ; Output buffer
    mov eax, 4
    mov ebx, 1
    mov ecx, buffer
    mov edx, edi
    sub edx, buffer
    int 0x80

    pop edi
    pop esi
    pop edx
    pop ecx
    pop ebx
    pop eax
    pop ebp
    ret

section .bss
    buffer resb 256

Comprehensive Application Example

Complete Console Program

section .data
    prompt   db 'Enter your name: ', 0
    greeting db 'Hello, ', 0
    newline  db 0xA, 0xD, 0

section .bss
    name_buffer resb 50
    name_len    resb 1

section .text
global _start
_start:
    ; Display prompt
    mov eax, 4
    mov ebx, 1
    mov ecx, prompt
    mov edx, 17             ; String length
    int 0x80

    ; Read user input
    mov eax, 3
    mov ebx, 0
    mov ecx, name_buffer
    mov edx, 50
    int 0x80
    mov [name_len], al      ; Save actual bytes read

    ; Display greeting
    mov eax, 4
    mov ebx, 1
    mov ecx, greeting
    mov edx, 7
    int 0x80

    ; Display user's name
    mov eax, 4
    mov ebx, 1
    mov ecx, name_buffer
    mov edx, [name_len]
    int 0x80

    ; Display newline
    mov eax, 4
    mov ebx, 1
    mov ecx, newline
    mov edx, 2
    int 0x80

    ; Exit
    mov eax, 1
    xor ebx, ebx
    int 0x80

By mastering these I/O techniques, you can:

  • Develop low-level system tools
  • Understand how operating systems work
  • Write cross-platform compatible code
  • Perform embedded system development
  • Implement high-performance I/O operations

Share your love