d33tah
d33tah

Reputation: 11561

Attempt to read key via int 16h ends in VM reboot

I was trying to add keyboard interaction to code from this example. Consider the following files:

Cargo.toml

[package]
name = "kernelhello"
version = "0.0.1"

[dependencies]
bootloader = "0.3.12"

[package.metadata.bootimage]
default-target = "build.json"

build.json

{
  "llvm-target": "x86_64-unknown-none",
  "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
  "arch": "x86_64",
  "target-endian": "little",
  "target-pointer-width": "64",
  "target-c-int-width": "32",
  "os": "none",
  "executables": true,
  "linker-flavor": "ld.lld",
  "linker": "rust-lld",
  "panic-strategy": "abort",
  "disable-redzone": true,
  "features": "-mmx,-sse,+soft-float"
}

src/main.rs

// src/main.rs
#![feature(asm)]

#![no_std] // don't link the Rust standard library
#![no_main] // disable all Rust-level entry points

use core::panic::PanicInfo;

/// This function is called on panic.
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
    loop {}
}


#[no_mangle]
pub extern "C" fn _start() -> ! {
    let mut HELLO: &mut [u8] = &mut b"Hello World!".clone();
    let vga_buffer = 0xb8000 as *mut u8;

    let mut z = 0;
    loop {
        for (i, byte) in HELLO.iter_mut().enumerate() {
            unsafe {
                z += 14;
                z %= 4000;
                *vga_buffer.offset(z + i as isize * 2) = *byte;
                *vga_buffer.offset(z + i as isize * 2 + 1) = 0xa;
                asm!("mov $$0, %ah\nint $$0x16");
            }
        }
    }
}

Unfortunately, an attempt to do bootimage run ends with an image that is stuck in a reboot loop - which doesn't happen if I comment out the asm! call. Here's a disassembly:

➜  rust-kernel-hello objdump -D -b binary -Mintel,x86-64  -m i386 target/build/debug/bootimage-kernelhello.bin | grep -C5 'ef01'
    eef0:       00 
    eef1:       48 8b 84 24 d0 00 00    mov    rax,QWORD PTR [rsp+0xd0]
    eef8:       00 
    eef9:       48 89 84 24 00 01 00    mov    QWORD PTR [rsp+0x100],rax
    ef00:       00 
    ef01:       48 8d bc 24 f0 00 00    lea    rdi,[rsp+0xf0]
    ef08:       00 
    ef09:       e8 72 fe ff ff          call   0xed80
    ef0e:       48 89 94 24 20 01 00    mov    QWORD PTR [rsp+0x120],rdx
    ef15:       00 
    ef16:       48 89 84 24 18 01 00    mov    QWORD PTR [rsp+0x118],rax
--
    f119:       48 89 04 24             mov    QWORD PTR [rsp],rax
    f11d:       48 8b 04 24             mov    rax,QWORD PTR [rsp]
    f121:       c6 00 0a                mov    BYTE PTR [rax],0xa
    f124:       b4 00                   mov    ah,0x0
    f126:       cd 16                   int    0x16
    f128:       e9 d4 fd ff ff          jmp    0xef01
    f12d:       48 8d 3d cc 0a 00 00    lea    rdi,[rip+0xacc]        # 0xfc00
    f134:       e8 07 04 00 00          call   0xf540
    f139:       0f 0b                   ud2    
    f13b:       48 8d 3d e6 0a 00 00    lea    rdi,[rip+0xae6]        # 0xfc28
    f142:       e8 f9 03 00 00          call   0xf540

What am I doing wrong?

Upvotes: 4

Views: 168

Answers (1)

Martin Rosenau
Martin Rosenau

Reputation: 18503

I'd like to explain zx485's comment in more detail:

I've often seen that assembly language beginners are confused about what instructions like int (x86), syscall (MIPS) or SWI (ARM) actually do.

Actually, these instructions are a special form of call instruction: They call some sub-routine which is typically located in the operating system.

64-bit x86 CPUs have different operating modes. One of them is named "real mode". In this mode the CPU can only execute 16-bit code.

The BIOS sub-routine that can be called using int 0x16 only works when the PC is operating in "real mode".

The fact that your program is a 64-bit program (registers like rax are used) tells us that your CPU is not running in real mode.

If you are writing your own operating system, you can define own sub-routines that are called by certain int instructions:

You can define a sub-routine that is called by int 0x16 which reads the keyboard and another one called by int 0x10 writing to the screen.

However, you are also free to define that int 0x16 is used for writing to the screen and int 0x10 is used for hard disk access in your operating system.

And in every case you'll have to write the sub-routines yourself because the existing sub-routines in the BIOS cannot be used in any other operating mode than "real mode". (This is what Ross Ridge is indicating in his comment.)

Upvotes: 4

Related Questions