🔧 AI Nachrichten Major AI platforms go down in unprecedented simultaneous outage(03.09.2026 um 17:34 Uhr)
🔧 AI Nachrichten ChatGPT, Claude, and Grok Down? Users Report Widespread Outages(03.09.2026 um 19:14 Uhr)
🔧 AI Nachrichten OpenAI Launches GPT-6 Astra, Says We May Have Entered the AGI Era(03.09.2026 um 22:08 Uhr)
🔧 AI Nachrichten Claude Comes to CarPlay as Fifth Major AI Chatbot App(05.09.2026 um 05:31 Uhr)
🔧 AI Nachrichten OpenAI’s GPT-6 Astra Is AGI, Says NVIDIA CEO Jensen Huang(07.09.2026 um 06:31 Uhr)
🔧 AI Nachrichten Blame AI companies for Mac mini and Mac Studio shortage(31.08.2026 um 10:32 Uhr)
🔧 AI Nachrichten Major AI platforms go down in unprecedented simultaneous outage(03.09.2026 um 17:34 Uhr)
🔧 AI Nachrichten ChatGPT, Claude, and Grok Down? Users Report Widespread Outages(03.09.2026 um 19:14 Uhr)
🔧 AI Nachrichten OpenAI Launches GPT-6 Astra, Says We May Have Entered the AGI Era(03.09.2026 um 22:08 Uhr)
🔧 AI Nachrichten Claude Comes to CarPlay as Fifth Major AI Chatbot App(05.09.2026 um 05:31 Uhr)
🔧 AI Nachrichten OpenAI’s GPT-6 Astra Is AGI, Says NVIDIA CEO Jensen Huang(07.09.2026 um 06:31 Uhr)
🔧 AI Nachrichten Blame AI companies for Mac mini and Mac Studio shortage(31.08.2026 um 10:32 Uhr)

🔧 Programmierung 🕛 kürzlich 8 Min Lesezeit
0

Porting my Rust KVM hypervisor to ARM64: Running the binary

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht




Recap



In the :




CODE
    case KVM_SET_ONE_REG:
case KVM_GET_ONE_REG: {
struct kvm_one_reg reg;

r = -ENOEXEC;
if (unlikely(!kvm_vcpu_initialized(vcpu)))
break;






This is exactly the error we were getting and we immediately found what was

missing: vCPU initialization. Actually, if we take a closer look at the code we

will see the comment kernel developers have left us:




CODE
int kvm_arch_vcpu_create(struct kvm_vcpu *vcpu)
{
int err;

/* Force users to call KVM_ARM_VCPU_INIT */
vcpu->arch.target = -1;









Initializing the vCPU



Following the lead I looked for KVM_ARM_VCPU_INIT in the original strace log

and found:




CODE
137152 ioctl(12<anon_inode:kvm-vcpu:0>, 0x4020aeae /* KVM_ARM_VCPU_INIT */, 0x7fff57e028) = 0
137156 ioctl(10<anon_inode:kvm-vcpu:0>, 0x4020aeae /* KVM_ARM_VCPU_INIT */, 0x7f82f70e68) = 0
137152 ioctl(10<anon_inode:kvm-vcpu:0>, 0x4020aeae /* KVM_ARM_VCPU_INIT */, 0x7fff57dd78) = 0
137152 ioctl(10<anon_inode:kvm-vcpu:0>, 0x4020aeae /* KVM_ARM_VCPU_INIT */, 0x7fff57e048) = 0






This wasn't really helpful: we again only see addresses, but we see no hints of

underlying structure. So let's first take a look at how KVM_ARM_VCPU_INIT is

defined in the Linux headers:




CODE
$ grep -Rn KVM_ARM_VCPU_INIT /usr/include/
/usr/include/linux/kvm.h:1622:#define KVM_ARM_VCPU_INIT _IOW(KVMIO, 0xae, struct kvm_vcpu_init)






A quick search for struct kvm_vcpu_init revealed:




CODE
$ grep -Rn 'struct kvm_vcpu_init' /usr/include/ -A3
/usr/include/aarch64-linux-gnu/asm/kvm.h:112:struct kvm_vcpu_init {
/usr/include/aarch64-linux-gnu/asm/kvm.h-113- __u32 target;
/usr/include/aarch64-linux-gnu/asm/kvm.h-114- __u32 features[7];
/usr/include/aarch64-linux-gnu/asm/kvm.h-115-};









Getting struct data with Python GDB



I didn't want to spend much time understanding every target and feature flag. My

goal was simply to reproduce QEMU's behavior as quickly as possible (and I

didn't even have to understand it completely). Luckily, this structure was

really simple (basically an array of eight 32-bit integers), so I decided to

tackle this with Python GDB automation. I created a breakpoint to stop exactly

on ioctl call when we are in KVM_ARM_VCPU_INIT:




CODE
import gdb
import struct

def log(line):
print(line)
with open("gdb-debug.out", "a") as f:
f.write(line + "\n")

class IoctlBreakpoint(gdb.Breakpoint):

def stop(self):
if x1 == 0x4020aeae: # KVM_ARM_VCPU_INIT
ptr = int(gdb.parse_and_eval("$x2"))
vcpu_init_mem = gdb.selected_inferior().read_memory(ptr, 32) # u32 + 7 * u32
target_features = struct.unpack("<8I", bytes(vcpu_init_mem))
log(f"KVM_ARM_VCPU_INIT: target={target_features[0]} features={target_features[1:]}")
return False

if __name__ == "__main__":
tracer = IoctlBreakpoint("ioctl")
gdb.execute("handle SIGUSR1 SIGUSR2 nostop noprint pass")
gdb.execute("run")






I ran my QEMU command with GDB:




CODE
gdb -ex 'source ./trace-arm.py' ./start-qemu.sh






This has automatically yielded great results:




CODE
KVM_ARM_VCPU_INIT: target=5 features=(0, 0, 0, 0, 0, 0, 0)
KVM_ARM_VCPU_INIT: target=5 features=(12, 0, 0, 0, 0, 0, 0)
KVM_ARM_VCPU_INIT: target=5 features=(12, 0, 0, 0, 0, 0, 0)
KVM_ARM_VCPU_INIT: target=5 features=(12, 0, 0, 0, 0, 0, 0)






For now I want to try just setting target, but leave features zeroed out. I

could immediately reconstruct this in Rust:




CODE
impl VCPU {
pub fn arm_vcpu_init(&self) -> io::Result<()> {
let mut vcpu_init : kvm_vcpu_init = unsafe { std::mem::zeroed() };
// KVM_ARM_VCPU_INIT: target=5 features=(0, 0, 0, 0, 0, 0, 0)
vcpu_init.target = 5;
let ret = unsafe { libc::ioctl(self.fd, KVM_ARM_VCPU_INIT, &mut vcpu_init) };
if ret < 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}
}









Third roadblock: No KVM exit



Soon after this I was able to get and set registers properly. The first ARM test

binary I compiled was based on working UART code from my hobby operating system

I started on Raspberry Pi. I expected at least a fault exit, if not an MMIO exit

when UART code was called. However, KVM_RUN never returned.



This is where I was stuck for a while. I considered creating a separate thread

that repeatedly queried KVM_GET_ONE_REG for PC, but from my own working

experience I knew that calling KVM ioctl calls asynchronously was a really bad

idea. I also considered adding hardware breakpoint capabilities to at least

trigger KVM_EXIT_DEBUG, but this would require more infrastructure for just

checking simple binary.





Writing a simple binary



I decided to try adding a signal handler instead. If I interrupted KVM_RUN

call with SIGINT maybe I could get registers then synchronously. I lowered

my expectations for initial binary and just created something really simple:




CODE
.section .text

.macro curr_el_to reg
mrs \reg, CurrentEL
lsr \reg, \reg, #2
and \reg, \reg, #0xf
.endm

.globl _start
_start:
mrs x0, mpidr_el1
and x0, x0, #0xFF
cbz x0, control
b .

control:
curr_el_to x0
b .






It looks like much, but this code will just load current CPU privilege level to

x0 register (being that level EL=1 is operating-system level, x0 should be

set to 0x1). The mpidr_el1 code is for checking if we are on control CPU

(this should be unnecessary here, but I added this out of paranoia caused by

real-life issues I had on ARM).





Installing signal handler



Finally, I added a signal handler:




CODE
extern "C" fn handler(_sig: libc::c_int) { }

unsafe fn install_interrupt_signal() {
let mut sa: libc::sigaction = std::mem::zeroed();
sa.sa_sigaction = handler as *const() as usize;
libc::sigemptyset(&mut sa.sa_mask);
sa.sa_flags = 0;
libc::sigaction(libc::SIGINT, &sa, std::ptr::null_mut());
}






The handler itself does nothing. I only needed the signal to interrupt the blocking KVM_RUN call. So my final KVM loop looked like:




CODE
    unsafe { install_interrupt_signal() };

let run = vcpu.kvm_run_mem as *mut kvm_run;

loop {
let ret = unsafe { libc::ioctl(vcpu.fd, KVM_RUN, 0usize) };
if ret < 0 {
if io::Error::last_os_error().raw_os_error() == Some(libc::EINTR) {
vcpu.print_regs()?;
}
return Err(io::Error::last_os_error());
}

let exit_reason = unsafe { (*run).exit_reason };









Running the binary



Finally, it was time to run the gues:




CODE
pi@raspberrypi:~ $ ./rust hello-world-arm.img
x0 = 0x0
pc = 0x1000
Loading "hello-world-arm.img" to 0x1000...
^Cx0 = 0x1
pc = 0x101c
Error: Os { code: 4, kind: Interrupted, message: "Interrupted system call" }






We can see that PC did change and that x0 really is set to current CPU

privilege level: 0x1. Why it didn't work for my UART code is probably that the

guest itself requires more initialization than I though (x86 went more smoothly

and felt more plug-and-play).






Conclusion



Although x86 and ARM64 share the same KVM interface at a high level, their

userspace APIs differ significantly. Porting my hypervisor turned out to involve

much more than renaming registers: ARM64 uses per-register ids instead of a

single register structure, requires explicit vCPU initialization through

KVM_ARM_VCPU_INIT and demanded a fair amount of reverse engineering before I

could successfully execute guest code. Fortunately, the same workflow that

worked on x86 like combining strace, GDB and the Linux kernel source proved just

as effective on ARM64 (although required a bit more effort).



The code from this article can be found on my GitHub page:



https://github.com/StjepanPoljak/kvm-rust/tree/kvm-arm-part2-code

Vollständiger Original-Bericht
Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 0%
🟡 In Evaluierung 0%
🟢 Keine Auswirkung 0%
Spannende Innovation 0%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
3 Quellen
GPT-6 Astra Release Today? OpenAI’s Next Major AI Model Is Almost Here
1 Quelle
Apple accuses OpenAI of destroying evidence as trade-secrets fight intensifies
1 Quelle
Major AI platforms go down in unprecedented simultaneous outage
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Porting my Rust KVM hypervisor to ARM64: Running the binary

Thematisch verwandte Begriffe: Porting, Rust, hypervisor, ARM64 · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...