尝试通过 int 16h 读取密钥以 VM 重启结束
Attempt to read key via int 16h ends in VM reboot
我试图将键盘交互添加到来自 this example 的代码中。考虑以下文件:
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 $[=12=], %ah\nint $[=12=]x16");
}
}
}
}
不幸的是,bootimage run
的尝试以卡在重启循环中的图像结束 - 如果我注释掉 asm!
调用,则不会发生这种情况。这是一个反汇编:
➜ 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
我做错了什么?
我想更详细地解释一下zx485的评论:
我经常看到汇编语言初学者对 int
(x86)、syscall
(MIPS) 或 SWI
(ARM) 等指令的实际作用感到困惑。
实际上,这些指令是 call
指令的一种特殊形式:它们调用一些通常位于操作系统中的子例程。
64 位 x86 CPUs 有不同的操作模式。其中一个名为 "real mode"。在此模式下 CPU 只能执行 16 位代码。
可使用int 0x16
调用的BIOS子程序仅在PC运行在"real mode"时有效。
您的程序是 64 位程序(使用 rax
之类的寄存器)这一事实告诉我们您的 CPU 在实模式下不是 运行。
如果您正在编写自己的操作系统,您可以定义自己的子例程,这些子例程由某些 int
指令调用:
您可以定义一个由 int 0x16
调用的读取键盘的子例程和另一个由 int 0x10
写入屏幕调用的子例程。
不过,您也可以在操作系统中自由定义 int 0x16
用于写入屏幕,int 0x10
用于硬盘访问。
并且在任何情况下,您都必须自己编写子程序,因为 BIOS 中现有的子程序不能用于 "real mode" 以外的任何其他操作模式。 (这就是 Ross Ridge 在他的评论中所指出的。)
我试图将键盘交互添加到来自 this example 的代码中。考虑以下文件:
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 $[=12=], %ah\nint $[=12=]x16");
}
}
}
}
不幸的是,bootimage run
的尝试以卡在重启循环中的图像结束 - 如果我注释掉 asm!
调用,则不会发生这种情况。这是一个反汇编:
➜ 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
我做错了什么?
我想更详细地解释一下zx485的评论:
我经常看到汇编语言初学者对 int
(x86)、syscall
(MIPS) 或 SWI
(ARM) 等指令的实际作用感到困惑。
实际上,这些指令是 call
指令的一种特殊形式:它们调用一些通常位于操作系统中的子例程。
64 位 x86 CPUs 有不同的操作模式。其中一个名为 "real mode"。在此模式下 CPU 只能执行 16 位代码。
可使用int 0x16
调用的BIOS子程序仅在PC运行在"real mode"时有效。
您的程序是 64 位程序(使用 rax
之类的寄存器)这一事实告诉我们您的 CPU 在实模式下不是 运行。
如果您正在编写自己的操作系统,您可以定义自己的子例程,这些子例程由某些 int
指令调用:
您可以定义一个由 int 0x16
调用的读取键盘的子例程和另一个由 int 0x10
写入屏幕调用的子例程。
不过,您也可以在操作系统中自由定义 int 0x16
用于写入屏幕,int 0x10
用于硬盘访问。
并且在任何情况下,您都必须自己编写子程序,因为 BIOS 中现有的子程序不能用于 "real mode" 以外的任何其他操作模式。 (这就是 Ross Ridge 在他的评论中所指出的。)