diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index bf210b9..3a39cd7 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -6,10 +6,24 @@ on: jobs: build-cpp: - name: Build C++ Project + name: Build C++ Project (${{ matrix.os }}) if: >- github.event_name == 'pull_request' - runs-on: windows-latest + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: windows-latest + configure_args: -A x64 -DCMAKE_BUILD_TYPE=Release + build_args: --config Release --parallel + artifact_name: tenbox-build-windows + artifact_path: build/Release/*.exe + - os: ubuntu-latest + configure_args: -DCMAKE_BUILD_TYPE=Release + build_args: --parallel + artifact_name: tenbox-build-linux + artifact_path: build/tenbox-vm-runtime steps: - uses: actions/checkout@v4 @@ -25,18 +39,18 @@ jobs: - name: Configure CMake if: steps.changes.outputs.src == 'true' - run: cmake -B build -A x64 -DCMAKE_BUILD_TYPE=Release + run: cmake -B build ${{ matrix.configure_args }} - name: Build if: steps.changes.outputs.src == 'true' - run: cmake --build build --config Release --parallel + run: cmake --build build ${{ matrix.build_args }} - name: Upload build artifacts if: steps.changes.outputs.src == 'true' uses: actions/upload-artifact@v4 with: - name: tenbox-build - path: build/Release/*.exe + name: ${{ matrix.artifact_name }} + path: ${{ matrix.artifact_path }} - name: Skip notice if: steps.changes.outputs.src != 'true' diff --git a/scripts/docker/build.sh b/scripts/docker/build.sh index b0711ae..1df11af 100755 --- a/scripts/docker/build.sh +++ b/scripts/docker/build.sh @@ -114,7 +114,21 @@ fi WORK_DIR="/tmp/tenbox-${ARCH}-${TARGET}" +# Rootfs builds require loop-mounting rootfs.raw inside the container. +# Even with --privileged, Docker does not share the host's /dev by default, +# so losetup inside the container may fail to create/allocate loop devices. +# Bind-mount /dev and ensure the loop module is available on the host. +DEV_ARGS=() +if needs_qemu_img "$TARGET"; then + if [ ! -e /dev/loop-control ]; then + echo "Loading 'loop' kernel module on host..." + sudo modprobe loop || true + fi + DEV_ARGS=(-v /dev:/dev) +fi + exec docker run --rm --privileged \ + "${DEV_ARGS[@]}" \ -v "$PROJECT_ROOT:/workspace" \ -e ROOT_PASSWORD="${ROOT_PASSWORD:-tenbox}" \ -e USER_NAME="${USER_NAME:-tenbox}" \ diff --git a/src/core/arch/aarch64/aarch64_machine.cpp b/src/core/arch/aarch64/aarch64_machine.cpp index f053c20..265297f 100644 --- a/src/core/arch/aarch64/aarch64_machine.cpp +++ b/src/core/arch/aarch64/aarch64_machine.cpp @@ -2,6 +2,7 @@ #include "core/arch/aarch64/fdt_builder.h" #include "core/device/irq/gicv3.h" #include "core/vmm/vm.h" +#include #include #include #include @@ -47,8 +48,9 @@ bool Aarch64Machine::SetupPlatformDevices( auto* hvf = dynamic_cast(hv_vm); if (hvf && hvf->UsesSoftGic()) { hvf->GetSoftGic()->RegisterDevices(addr_space, kGicDistBase, kGicRedistBase); - LOG_INFO("aarch64: software GICv3 MMIO registered at dist=0x%llx redist=0x%llx", - (unsigned long long)kGicDistBase, (unsigned long long)kGicRedistBase); + LOG_INFO("aarch64: software GICv3 MMIO registered at dist=0x%" PRIx64 + " redist=0x%" PRIx64, + (uint64_t)kGicDistBase, (uint64_t)kGicRedistBase); } } #endif @@ -126,17 +128,16 @@ bool Aarch64Machine::LoadKernel( fclose(fp); if (read != static_cast(initrd_size)) { - LOG_ERROR("aarch64: short initrd read (%zu / %llu)", read, - (unsigned long long)initrd_size); + LOG_ERROR("aarch64: short initrd read (%zu / %" PRIu64 ")", read, + initrd_size); return false; } initrd_start = Layout::kRamBase + initrd_offset; initrd_end = initrd_start + initrd_size; - LOG_INFO("aarch64: initrd loaded at GPA 0x%llx (%llu bytes)", - (unsigned long long)initrd_start, - (unsigned long long)initrd_size); + LOG_INFO("aarch64: initrd loaded at GPA 0x%" PRIx64 " (%" PRIu64 " bytes)", + (uint64_t)initrd_start, initrd_size); } // Build FDT @@ -162,8 +163,8 @@ bool Aarch64Machine::LoadKernel( fdt.AddPropertyU64("linux,initrd-end", initrd_end); } char stdout_path[64]; - snprintf(stdout_path, sizeof(stdout_path), "/pl011@%llx", - (unsigned long long)kUartBase); + snprintf(stdout_path, sizeof(stdout_path), "/pl011@%" PRIx64, + (uint64_t)kUartBase); fdt.AddPropertyString("stdout-path", stdout_path); // Provide host entropy so the guest kernel can initialize CRNG @@ -182,8 +183,8 @@ bool Aarch64Machine::LoadKernel( // /memory char mem_name[32]; - snprintf(mem_name, sizeof(mem_name), "memory@%llx", - (unsigned long long)Layout::kRamBase); + snprintf(mem_name, sizeof(mem_name), "memory@%" PRIx64, + (uint64_t)Layout::kRamBase); fdt.BeginNode(mem_name); fdt.AddPropertyString("device_type", "memory"); fdt.AddPropertyCells("reg", { @@ -249,8 +250,8 @@ bool Aarch64Machine::LoadKernel( #endif char gic_name[64]; - snprintf(gic_name, sizeof(gic_name), "intc@%llx", - (unsigned long long)kGicDistBase); + snprintf(gic_name, sizeof(gic_name), "intc@%" PRIx64, + (uint64_t)kGicDistBase); fdt.BeginNode(gic_name); fdt.AddPropertyString("compatible", "arm,gic-v3"); fdt.AddPropertyU32("#interrupt-cells", 3); @@ -278,8 +279,8 @@ bool Aarch64Machine::LoadKernel( // /pl011 UART (AMBA PL011 — needs arm,primecell compat and clocks) char uart_name[64]; - snprintf(uart_name, sizeof(uart_name), "pl011@%llx", - (unsigned long long)kUartBase); + snprintf(uart_name, sizeof(uart_name), "pl011@%" PRIx64, + (uint64_t)kUartBase); fdt.BeginNode(uart_name); { const char* compat[] = {"arm,pl011", "arm,primecell"}; @@ -307,8 +308,8 @@ bool Aarch64Machine::LoadKernel( // PL031 RTC (arm,pl031 — PrimeCell, needs clocks like PL011) { char rtc_name[64]; - snprintf(rtc_name, sizeof(rtc_name), "pl031@%llx", - (unsigned long long)kRtcBase); + snprintf(rtc_name, sizeof(rtc_name), "pl031@%" PRIx64, + (uint64_t)kRtcBase); fdt.BeginNode(rtc_name); { const char* compat[] = {"arm,pl031", "arm,primecell"}; @@ -335,8 +336,8 @@ bool Aarch64Machine::LoadKernel( for (size_t i = 0; i < virtio_slots.size(); i++) { const auto& slot = virtio_slots[i]; char name[64]; - snprintf(name, sizeof(name), "virtio_mmio@%llx", - (unsigned long long)slot.mmio_base); + snprintf(name, sizeof(name), "virtio_mmio@%" PRIx64, + (uint64_t)slot.mmio_base); fdt.BeginNode(name); fdt.AddPropertyString("compatible", "virtio,mmio"); fdt.AddPropertyCells("reg", { @@ -357,14 +358,14 @@ bool Aarch64Machine::LoadKernel( // Place FDT at the start of guest RAM fdt_gpa_ = Layout::kFdtBase; if (dtb.size() > Layout::kFdtMaxSize) { - LOG_ERROR("aarch64: FDT too large (%zu bytes, max %llu)", - dtb.size(), (unsigned long long)Layout::kFdtMaxSize); + LOG_ERROR("aarch64: FDT too large (%zu bytes, max %" PRIu64 ")", + dtb.size(), (uint64_t)Layout::kFdtMaxSize); return false; } memcpy(mem.base, dtb.data(), dtb.size()); - LOG_INFO("aarch64: FDT placed at GPA 0x%llx (%zu bytes)", - (unsigned long long)fdt_gpa_, dtb.size()); + LOG_INFO("aarch64: FDT placed at GPA 0x%" PRIx64 " (%zu bytes)", + (uint64_t)fdt_gpa_, dtb.size()); return true; } diff --git a/src/core/arch/aarch64/boot.cpp b/src/core/arch/aarch64/boot.cpp index c9425fb..caa1928 100644 --- a/src/core/arch/aarch64/boot.cpp +++ b/src/core/arch/aarch64/boot.cpp @@ -59,9 +59,8 @@ GPA LoadLinuxImage(BootConfig& config) { return 0; } - LOG_INFO("aarch64: kernel loaded at GPA 0x%llx (%ld bytes, text_offset=0x%llx)", - (unsigned long long)kernel_gpa, file_size, - (unsigned long long)text_offset); + LOG_INFO("aarch64: kernel loaded at GPA 0x%" PRIx64 " (%ld bytes, text_offset=0x%" PRIx64 ")", + (uint64_t)kernel_gpa, file_size, text_offset); return kernel_gpa; } diff --git a/src/core/arch/x86_64/acpi.cpp b/src/core/arch/x86_64/acpi.cpp index 2c3f88d..e9a523f 100644 --- a/src/core/arch/x86_64/acpi.cpp +++ b/src/core/arch/x86_64/acpi.cpp @@ -400,8 +400,9 @@ GPA BuildAcpiTables(uint8_t* ram, uint32_t num_cpus, rsdp->checksum = AcpiChecksum(rsdp_base, 20); rsdp->extended_checksum = AcpiChecksum(rsdp_base, sizeof(AcpiRsdp)); - LOG_INFO("ACPI tables: RSDP@0x%llX XSDT@0x%llX MADT@0x%llX " - "FADT@0x%llX DSDT@0x%llX (%u virtio-mmio dev%s)", + LOG_INFO("ACPI tables: RSDP@0x%" PRIX64 " XSDT@0x%" PRIX64 + " MADT@0x%" PRIX64 " FADT@0x%" PRIX64 " DSDT@0x%" PRIX64 + " (%u virtio-mmio dev%s)", AcpiLayout::kRsdp, AcpiLayout::kXsdt, AcpiLayout::kMadt, AcpiLayout::kFadt, AcpiLayout::kDsdt, (uint32_t)virtio_devs.size(), diff --git a/src/core/arch/x86_64/boot.cpp b/src/core/arch/x86_64/boot.cpp index 604ee1b..66cb941 100644 --- a/src/core/arch/x86_64/boot.cpp +++ b/src/core/arch/x86_64/boot.cpp @@ -56,8 +56,8 @@ uint64_t LoadLinuxKernel(const BootConfig& config) { // Copy protected-mode kernel to 1MB memcpy(ram + Layout::kKernelBase, kernel.data() + setup_size, kernel_size); - LOG_INFO("Kernel loaded at GPA 0x%llX (%u bytes)", - Layout::kKernelBase, kernel_size); + LOG_INFO("Kernel loaded at GPA 0x%" PRIX64 " (%u bytes)", + (uint64_t)Layout::kKernelBase, kernel_size); // --- Set up boot_params (zero page) --- uint8_t* bp = ram + Layout::kBootParams; @@ -110,7 +110,7 @@ uint64_t LoadLinuxKernel(const BootConfig& config) { *reinterpret_cast(bp + BootOffset::kRamdiskSize) = static_cast(initrd.size()); - LOG_INFO("Initrd loaded at GPA 0x%llX (%zu bytes)", + LOG_INFO("Initrd loaded at GPA 0x%" PRIX64 " (%zu bytes)", initrd_addr, initrd.size()); } @@ -126,12 +126,12 @@ uint64_t LoadLinuxKernel(const BootConfig& config) { // Entry 2 (optional): high memory above 4 GiB if (mem.high_size > 0) { e820[e820_count++] = {mem.high_base, mem.high_size, kE820Ram}; - LOG_INFO("E820: [0-0x9FFFF RAM] [0x100000-0x%llX RAM] " - "[0x%llX-0x%llX RAM]", + LOG_INFO("E820: [0-0x9FFFF RAM] [0x100000-0x%" PRIX64 " RAM] " + "[0x%" PRIX64 "-0x%" PRIX64 " RAM]", mem.low_size - 1, mem.high_base, mem.high_base + mem.high_size - 1); } else { - LOG_INFO("E820: [0-0x9FFFF RAM] [0x100000-0x%llX RAM]", + LOG_INFO("E820: [0-0x9FFFF RAM] [0x100000-0x%" PRIX64 " RAM]", mem.low_size - 1); } bp[BootOffset::kE820Entries] = e820_count; diff --git a/src/core/arch/x86_64/x86_machine.cpp b/src/core/arch/x86_64/x86_machine.cpp index fcc0799..1849d3a 100644 --- a/src/core/arch/x86_64/x86_machine.cpp +++ b/src/core/arch/x86_64/x86_machine.cpp @@ -110,6 +110,16 @@ bool X86Machine::SetupBootVCpu(HypervisorVCpu* vcpu, uint8_t* ram) { } void X86Machine::InjectIrq(HypervisorVm* hv_vm, uint8_t irq) { + // Hypervisors with an in-kernel irqchip (KVM) handle the IOAPIC/PIC/LAPIC + // in the kernel. InjectIrq() has pulse semantics (one-shot edge IRQ), so + // pulse the GSI line: assert then de-assert. Without the de-assert, an + // edge-triggered line stays latched high and no further IRQs re-fire on + // the same GSI (breaks UART THRE, PIT tick, etc.). + if (hv_vm->AssertIrq(irq, true)) { + hv_vm->AssertIrq(irq, false); + return; + } + uint64_t rte = 0; if (!ioapic_.GetRedirEntry(irq, &rte)) return; @@ -129,6 +139,7 @@ void X86Machine::InjectIrq(HypervisorVm* hv_vm, uint8_t irq) { } void X86Machine::SetIrqLevel(HypervisorVm* hv_vm, uint8_t irq, bool asserted) { + if (hv_vm->AssertIrq(irq, asserted)) return; if (asserted) { InjectIrq(hv_vm, irq); } diff --git a/src/core/device/acpi/acpi_pm.cpp b/src/core/device/acpi/acpi_pm.cpp index 9b98d2b..3293e45 100644 --- a/src/core/device/acpi/acpi_pm.cpp +++ b/src/core/device/acpi/acpi_pm.cpp @@ -9,9 +9,12 @@ #ifdef _WIN32 #define NOMINMAX #include -#else +#elif defined(__APPLE__) #include #include +#else +#include +#include #endif AcpiPm::AcpiPm() { @@ -26,7 +29,7 @@ AcpiPm::AcpiPm() { double elapsed = static_cast(qpc_end.QuadPart - qpc_start.QuadPart) / qpf.QuadPart; -#else +#elif defined(__APPLE__) mach_timebase_info_data_t tb; mach_timebase_info(&tb); uint64_t mach_start = mach_absolute_time(); @@ -36,6 +39,16 @@ AcpiPm::AcpiPm() { uint64_t mach_end = mach_absolute_time(); double elapsed = static_cast(mach_end - mach_start) * tb.numer / tb.denom / 1e9; +#else + struct timespec ts_start, ts_end; + clock_gettime(CLOCK_MONOTONIC, &ts_start); + uint64_t tsc_start = __rdtsc(); + usleep(50000); + uint64_t tsc_end = __rdtsc(); + clock_gettime(CLOCK_MONOTONIC, &ts_end); + + double elapsed = static_cast(ts_end.tv_sec - ts_start.tv_sec) + + static_cast(ts_end.tv_nsec - ts_start.tv_nsec) / 1e9; #endif tsc_freq_ = static_cast((tsc_end - tsc_start) / elapsed); tsc_base_ = __rdtsc(); diff --git a/src/core/device/serial/uart_16550.cpp b/src/core/device/serial/uart_16550.cpp index e479542..9439107 100644 --- a/src/core/device/serial/uart_16550.cpp +++ b/src/core/device/serial/uart_16550.cpp @@ -56,6 +56,11 @@ void Uart16550::PioRead(uint16_t offset, uint8_t size, uint32_t* value) { } else { val = 0x01; // No interrupt pending } + // Bits 7:6 = 11 when FIFO is enabled: 16550A identification signal. + // Linux 8250 autoconfig uses (IIR & 0xC0) == 0xC0 to detect 16550A. + if (fifo_enabled_) { + val |= 0xC0; + } break; case kLCR: val = lcr_; @@ -109,6 +114,17 @@ void Uart16550::PioWrite(uint16_t offset, uint8_t size, uint32_t value) { break; } case kFCR: + // FCR bit 0: FIFO enable. Bit 1: clear RX FIFO. Bit 2: clear TX + // FIFO (TX drains synchronously through tx_callback_, so nothing to + // clear). Bits 6:7 set the RX trigger level; we don't model the + // threshold separately since RX is a ring consumed on demand. + fifo_enabled_ = (val & 0x01) != 0; + if (val & 0x02) { + std::lock_guard lock(rx_mutex_); + rx_head_ = 0; + rx_tail_ = 0; + rx_count_ = 0; + } break; case kLCR: lcr_ = val; diff --git a/src/core/device/serial/uart_16550.h b/src/core/device/serial/uart_16550.h index ec311a2..ef0b116 100644 --- a/src/core/device/serial/uart_16550.h +++ b/src/core/device/serial/uart_16550.h @@ -47,6 +47,10 @@ class Uart16550 : public Device { uint8_t dll_ = 0; uint8_t dlh_ = 0; bool thre_pending_ = false; + // Guest has enabled the 16550A FIFO via FCR bit 0. Reported back in + // IIR[7:6] so Linux's 8250 driver identifies the UART as 16550A and + // switches its TX path to burst up to 16 bytes per THRE IRQ roundtrip. + bool fifo_enabled_ = false; IrqCallback irq_callback_; TxCallback tx_callback_; diff --git a/src/core/device/timer/i8254_pit.cpp b/src/core/device/timer/i8254_pit.cpp index 640d025..db32730 100644 --- a/src/core/device/timer/i8254_pit.cpp +++ b/src/core/device/timer/i8254_pit.cpp @@ -3,10 +3,14 @@ #ifdef _WIN32 #define NOMINMAX #include -#else +#elif defined(__APPLE__) #include #include #include +#else +#include +#include +#include #endif uint64_t I8254Pit::MeasureTscFrequency() { @@ -23,7 +27,7 @@ uint64_t I8254Pit::MeasureTscFrequency() { uint32_t crystal = static_cast(info[2]); if (denom && numer && crystal) { uint64_t freq = static_cast(crystal) * numer / denom; - LOG_INFO("TSC frequency from CPUID 0x15: %llu Hz", freq); + LOG_INFO("TSC frequency from CPUID 0x15: %" PRIu64 " Hz", freq); return freq; } @@ -38,7 +42,7 @@ uint64_t I8254Pit::MeasureTscFrequency() { double elapsed = static_cast(qpc_end.QuadPart - qpc_start.QuadPart) / qpf.QuadPart; -#else +#elif defined(__APPLE__) mach_timebase_info_data_t tb; mach_timebase_info(&tb); uint64_t mach_start = mach_absolute_time(); @@ -48,9 +52,19 @@ uint64_t I8254Pit::MeasureTscFrequency() { uint64_t mach_end = mach_absolute_time(); double elapsed = static_cast(mach_end - mach_start) * tb.numer / tb.denom / 1e9; +#else + struct timespec ts_start, ts_end; + clock_gettime(CLOCK_MONOTONIC, &ts_start); + uint64_t tsc_start = __rdtsc(); + usleep(50000); + uint64_t tsc_end = __rdtsc(); + clock_gettime(CLOCK_MONOTONIC, &ts_end); + + double elapsed = static_cast(ts_end.tv_sec - ts_start.tv_sec) + + static_cast(ts_end.tv_nsec - ts_start.tv_nsec) / 1e9; #endif uint64_t freq = static_cast((tsc_end - tsc_start) / elapsed); - LOG_INFO("TSC frequency measured: %llu Hz", freq); + LOG_INFO("TSC frequency measured: %" PRIu64 " Hz", freq); return freq; } diff --git a/src/core/device/virtio/virtio_blk.cpp b/src/core/device/virtio/virtio_blk.cpp index 999d22b..2775b9c 100644 --- a/src/core/device/virtio/virtio_blk.cpp +++ b/src/core/device/virtio/virtio_blk.cpp @@ -28,7 +28,7 @@ bool VirtioBlkDevice::Open(const std::string& path) { config_.max_write_zeroes_seg = 1; config_.write_zeroes_may_unmap = is_qcow2_ ? 1 : 0; - LOG_INFO("VirtIO block: %s, %llu sectors (%llu MB), %u queues", + LOG_INFO("VirtIO block: %s, %" PRIu64 " sectors (%" PRIu64 " MB), %u queues", path.c_str(), config_.capacity, disk_size / (1024 * 1024), num_queues_); return true; diff --git a/src/core/device/virtio/virtio_fs.cpp b/src/core/device/virtio/virtio_fs.cpp index 149d765..bb04765 100644 --- a/src/core/device/virtio/virtio_fs.cpp +++ b/src/core/device/virtio/virtio_fs.cpp @@ -28,6 +28,14 @@ static std::string WideToUtf8(const std::wstring& wide) { #include #include #include + +#if defined(__linux__) +// Linux uses st_atim/st_mtim/st_ctim (POSIX.1-2008) rather than the legacy +// BSD/macOS st_atimespec spelling this file originally targeted. +#define st_atimespec st_atim +#define st_mtimespec st_mtim +#define st_ctimespec st_ctim +#endif #include #include #endif @@ -126,7 +134,7 @@ bool VirtioFsDevice::AddShare(const std::string& tag, const std::string& host_pa shares_version_++; virtual_root_mtime_ = static_cast(time(nullptr)); - LOG_INFO("VirtIO FS: added share '%s' -> '%s' (readonly=%s, inode=%llu)", + LOG_INFO("VirtIO FS: added share '%s' -> '%s' (readonly=%s, inode=%" PRIu64 ")", tag.c_str(), host_path.c_str(), readonly ? "true" : "false", share_root_inode); return true; } @@ -629,7 +637,13 @@ void VirtioFsDevice::HandleSetAttr(const FuseInHeader* in_hdr, const uint8_t* in CloseHandle(h); } #else - ::truncate(path.c_str(), static_cast(setattr_in->size)); + // GCC's warn_unused_result on truncate() survives a plain (void) + // cast, so stash the result in a discarded local. A short-write + // failure here is reported to the guest implicitly via the next + // getattr; nothing actionable to do at this point. + int truncate_rc = ::truncate(path.c_str(), + static_cast(setattr_in->size)); + (void)truncate_rc; #endif } diff --git a/src/core/device/virtio/virtio_mmio.cpp b/src/core/device/virtio/virtio_mmio.cpp index 0e149ae..07a027d 100644 --- a/src/core/device/virtio/virtio_mmio.cpp +++ b/src/core/device/virtio/virtio_mmio.cpp @@ -134,7 +134,8 @@ void VirtioMmioDevice::MmioWrite(uint64_t offset, uint8_t size, vq.SetDriverAddr(cfg.driver_addr); vq.SetDeviceAddr(cfg.device_addr); vq.SetReady(true); - LOG_DEBUG("VirtIO queue %u ready: size=%u desc=0x%llX driver=0x%llX device=0x%llX", + LOG_DEBUG("VirtIO queue %u ready: size=%u desc=0x%" PRIX64 + " driver=0x%" PRIX64 " device=0x%" PRIX64, queue_sel_, qs, cfg.desc_addr, cfg.driver_addr, cfg.device_addr); } else { queues_[queue_sel_].SetReady(false); diff --git a/src/core/device/virtio/virtqueue.cpp b/src/core/device/virtio/virtqueue.cpp index ca3f305..c351ddc 100644 --- a/src/core/device/virtio/virtqueue.cpp +++ b/src/core/device/virtio/virtqueue.cpp @@ -98,7 +98,7 @@ bool VirtQueue::WalkChain(uint16_t head_idx, } else { uint8_t* hva = GpaToHva(desc->addr); if (!hva) { - LOG_ERROR("VirtQueue: bad GPA 0x%llX in descriptor %u", + LOG_ERROR("VirtQueue: bad GPA 0x%" PRIX64 " in descriptor %u", desc->addr, idx); return false; } @@ -130,7 +130,7 @@ bool VirtQueue::WalkIndirect(uint64_t table_gpa, uint32_t table_len, auto* table = reinterpret_cast(GpaToHva(table_gpa)); if (!table) { - LOG_ERROR("VirtQueue: bad GPA 0x%llX for indirect table", table_gpa); + LOG_ERROR("VirtQueue: bad GPA 0x%" PRIX64 " for indirect table", table_gpa); return false; } @@ -145,7 +145,7 @@ bool VirtQueue::WalkIndirect(uint64_t table_gpa, uint32_t table_len, uint8_t* hva = GpaToHva(d->addr); if (!hva) { - LOG_ERROR("VirtQueue: bad GPA 0x%llX in indirect descriptor %u", + LOG_ERROR("VirtQueue: bad GPA 0x%" PRIX64 " in indirect descriptor %u", d->addr, i); return false; } diff --git a/src/core/disk/qcow2.cpp b/src/core/disk/qcow2.cpp index fb56ced..3a2d48a 100644 --- a/src/core/disk/qcow2.cpp +++ b/src/core/disk/qcow2.cpp @@ -91,9 +91,9 @@ bool Qcow2DiskImage::Open(const std::string& path) { // Align to cluster boundary file_end_ = (file_end_ + cluster_size_ - 1) & ~(static_cast(cluster_size_) - 1); - LOG_INFO("Qcow2: %s, version %u, cluster_size %u, virtual_size %llu MB, " - "l1_size %u, refcount_table 0x%llX (%u clusters), " - "file_end 0x%llX, compression %s", + LOG_INFO("Qcow2: %s, version %u, cluster_size %u, virtual_size %" PRIu64 + " MB, l1_size %u, refcount_table 0x%" PRIX64 + " (%u clusters), file_end 0x%" PRIX64 ", compression %s", path.c_str(), version_, cluster_size_, virtual_size_ / (1024 * 1024), l1_size_, refcount_table_offset_, refcount_table_clusters_, @@ -164,10 +164,9 @@ bool Qcow2DiskImage::ReadHeader() { (1ULL << 3); // compression type uint64_t incompat = Be64(hdr.incompatible_features); if (incompat & ~kSupportedIncompat) { - LOG_ERROR("Qcow2: unsupported incompatible features 0x%llX " - "(supported mask 0x%llX)", - (unsigned long long)incompat, - (unsigned long long)kSupportedIncompat); + LOG_ERROR("Qcow2: unsupported incompatible features 0x%" PRIX64 + " (supported mask 0x%" PRIX64 ")", + incompat, kSupportedIncompat); return false; } if (incompat & (1ULL << 0)) { @@ -233,7 +232,7 @@ bool Qcow2DiskImage::ReadL1Table() { _fseeki64(file_, l1_table_offset_, SEEK_SET); size_t bytes = l1_size_ * sizeof(uint64_t); if (fread(l1_table_.data(), 1, bytes, file_) != bytes) { - LOG_ERROR("Qcow2: failed to read L1 table (%u entries at 0x%llX)", + LOG_ERROR("Qcow2: failed to read L1 table (%u entries at 0x%" PRIX64 ")", l1_size_, l1_table_offset_); return false; } @@ -252,7 +251,7 @@ bool Qcow2DiskImage::ReadRefcountTable() { _fseeki64(file_, refcount_table_offset_, SEEK_SET); if (fread(refcount_table_.data(), 1, table_bytes, file_) != table_bytes) { - LOG_ERROR("Qcow2: failed to read refcount table at 0x%llX (%zu bytes)", + LOG_ERROR("Qcow2: failed to read refcount table at 0x%" PRIX64 " (%zu bytes)", refcount_table_offset_, table_bytes); return false; } @@ -291,7 +290,7 @@ uint64_t* Qcow2DiskImage::GetL2Table(uint64_t l2_offset) { _fseeki64(file_, l2_offset, SEEK_SET); size_t bytes = l2_entries_ * sizeof(uint64_t); if (fread(entry.data.data(), 1, bytes, file_) != bytes) { - LOG_ERROR("Qcow2: failed to read L2 table at 0x%llX", l2_offset); + LOG_ERROR("Qcow2: failed to read L2 table at 0x%" PRIX64, l2_offset); return nullptr; } @@ -317,7 +316,7 @@ void Qcow2DiskImage::EvictL2Cache() { size_t bytes = l2_entries_ * sizeof(uint64_t); _fseeki64(file_, victim.l2_offset, SEEK_SET); if (fwrite(be_data.data(), 1, bytes, file_) != bytes) { - LOG_ERROR("Qcow2: failed to evict L2 table at 0x%llX", victim.l2_offset); + LOG_ERROR("Qcow2: failed to evict L2 table at 0x%" PRIX64, victim.l2_offset); } } @@ -336,7 +335,7 @@ uint16_t* Qcow2DiskImage::GetRefcountBlock(uint64_t cluster_index, if (rft_index >= refcount_table_.size()) { if (!allocate) return nullptr; if (!GrowRefcountTable(cluster_index)) { - LOG_ERROR("Qcow2: failed to grow refcount table for cluster %llu", cluster_index); + LOG_ERROR("Qcow2: failed to grow refcount table for cluster %" PRIu64, cluster_index); return nullptr; } // rft_index is now valid after grow @@ -356,7 +355,7 @@ uint16_t* Qcow2DiskImage::GetRefcountBlock(uint64_t cluster_index, std::vector zeros(cluster_size_, 0); _fseeki64(file_, block_offset, SEEK_SET); if (fwrite(zeros.data(), 1, cluster_size_, file_) != cluster_size_) { - LOG_ERROR("Qcow2: failed to write new refcount block at 0x%llX", block_offset); + LOG_ERROR("Qcow2: failed to write new refcount block at 0x%" PRIX64, block_offset); return nullptr; } @@ -398,7 +397,7 @@ uint16_t* Qcow2DiskImage::GetRefcountBlock(uint64_t cluster_index, _fseeki64(file_, block_offset, SEEK_SET); size_t bytes = rfb_entries_ * sizeof(uint16_t); if (fread(entry.data.data(), 1, bytes, file_) != bytes) { - LOG_ERROR("Qcow2: failed to read refcount block at 0x%llX", block_offset); + LOG_ERROR("Qcow2: failed to read refcount block at 0x%" PRIX64, block_offset); return nullptr; } @@ -423,7 +422,7 @@ void Qcow2DiskImage::EvictRfbCache() { size_t bytes = rfb_entries_ * sizeof(uint16_t); _fseeki64(file_, victim.offset_in_file, SEEK_SET); if (fwrite(be_data.data(), 1, bytes, file_) != bytes) { - LOG_ERROR("Qcow2: failed to evict refcount block at 0x%llX", + LOG_ERROR("Qcow2: failed to evict refcount block at 0x%" PRIX64, victim.offset_in_file); } } @@ -494,7 +493,7 @@ bool Qcow2DiskImage::GrowRefcountTable(uint64_t min_cluster_index) { } _fseeki64(file_, new_table_offset, SEEK_SET); if (fwrite(be_table.data(), 1, new_byte_size, file_) != new_byte_size) { - LOG_ERROR("Qcow2: failed to write new refcount table at 0x%llX", new_table_offset); + LOG_ERROR("Qcow2: failed to write new refcount table at 0x%" PRIX64, new_table_offset); return false; } @@ -594,7 +593,7 @@ bool Qcow2DiskImage::GrowRefcountTable(uint64_t min_cluster_index) { FreeCluster(old_table_offset + static_cast(i) * cluster_size_); } - LOG_INFO("Qcow2: grew refcount table to %u clusters (%zu entries) at 0x%llX", + LOG_INFO("Qcow2: grew refcount table to %u clusters (%zu entries) at 0x%" PRIX64, new_clusters, new_entries, new_table_offset); return true; } @@ -671,7 +670,7 @@ bool Qcow2DiskImage::ReadCompressedCluster(uint64_t comp_host_off, std::vector comp_buf(comp_size); _fseeki64(file_, comp_host_off, SEEK_SET); if (fread(comp_buf.data(), 1, comp_size, file_) != comp_size) { - LOG_ERROR("Qcow2: failed to read compressed data at 0x%llX (%u bytes)", + LOG_ERROR("Qcow2: failed to read compressed data at 0x%" PRIX64 " (%u bytes)", comp_host_off, comp_size); return false; } @@ -719,7 +718,7 @@ bool Qcow2DiskImage::ReadCompressedCluster(uint64_t comp_host_off, inflateEnd(&strm); if (ret != Z_STREAM_END && ret != Z_OK) { - LOG_ERROR("Qcow2: inflate failed (%d) for cluster at 0x%llX", + LOG_ERROR("Qcow2: inflate failed (%d) for cluster at 0x%" PRIX64, ret, comp_host_off); return false; } @@ -751,8 +750,7 @@ bool Qcow2DiskImage::CheckMetadataOverlap(uint64_t offset, uint64_t size) { // Header cluster (cluster 0) if (offset < cluster_size_) { - LOG_ERROR("Qcow2: overlap with header at offset 0x%llX", - (unsigned long long)offset); + LOG_ERROR("Qcow2: overlap with header at offset 0x%" PRIX64, offset); return false; } @@ -760,8 +758,7 @@ bool Qcow2DiskImage::CheckMetadataOverlap(uint64_t offset, uint64_t size) { uint64_t l1_end = l1_table_offset_ + static_cast(l1_size_) * sizeof(uint64_t); if (offset < l1_end && end > l1_table_offset_) { - LOG_ERROR("Qcow2: overlap with L1 table at offset 0x%llX", - (unsigned long long)offset); + LOG_ERROR("Qcow2: overlap with L1 table at offset 0x%" PRIX64, offset); return false; } @@ -769,8 +766,7 @@ bool Qcow2DiskImage::CheckMetadataOverlap(uint64_t offset, uint64_t size) { uint64_t rft_end = refcount_table_offset_ + static_cast(refcount_table_clusters_) * cluster_size_; if (offset < rft_end && end > refcount_table_offset_) { - LOG_ERROR("Qcow2: overlap with refcount table at offset 0x%llX", - (unsigned long long)offset); + LOG_ERROR("Qcow2: overlap with refcount table at offset 0x%" PRIX64, offset); return false; } @@ -782,8 +778,8 @@ bool Qcow2DiskImage::CheckMetadataOverlap(uint64_t offset, uint64_t size) { if (rft_i < refcount_table_.size()) { uint64_t blk = refcount_table_[rft_i]; if (blk != 0 && offset < blk + cluster_size_ && end > blk) { - LOG_ERROR("Qcow2: overlap with refcount block at offset 0x%llX", - (unsigned long long)offset); + LOG_ERROR("Qcow2: overlap with refcount block at offset 0x%" PRIX64, + offset); return false; } } @@ -837,7 +833,7 @@ uint64_t Qcow2DiskImage::AllocateCluster() { std::vector zeros(cluster_size_, 0); _fseeki64(file_, offset, SEEK_SET); if (fwrite(zeros.data(), 1, cluster_size_, file_) != cluster_size_) { - LOG_ERROR("Qcow2: failed to extend file at 0x%llX", offset); + LOG_ERROR("Qcow2: failed to extend file at 0x%" PRIX64, offset); return 0; } file_end_ = offset + cluster_size_; @@ -851,13 +847,13 @@ void Qcow2DiskImage::FreeCluster(uint64_t host_offset) { uint32_t rfb_index; uint16_t* rfb = GetRefcountBlock(cluster_index, &rfb_index, false); if (!rfb) { - LOG_ERROR("Qcow2: FreeCluster: no refcount block for offset 0x%llX", + LOG_ERROR("Qcow2: FreeCluster: no refcount block for offset 0x%" PRIX64, host_offset); return; } if (rfb[rfb_index] == 0) { - LOG_ERROR("Qcow2: FreeCluster: refcount already 0 for offset 0x%llX", + LOG_ERROR("Qcow2: FreeCluster: refcount already 0 for offset 0x%" PRIX64, host_offset); return; } @@ -918,8 +914,7 @@ uint64_t* Qcow2DiskImage::EnsureL2Table(uint32_t l1_idx) { std::vector zeros(cluster_size_, 0); _fseeki64(file_, new_l2_off, SEEK_SET); if (fwrite(zeros.data(), 1, cluster_size_, file_) != cluster_size_) { - LOG_ERROR("Qcow2: failed to zero L2 table at 0x%llX", - (unsigned long long)new_l2_off); + LOG_ERROR("Qcow2: failed to zero L2 table at 0x%" PRIX64, new_l2_off); return nullptr; } @@ -1156,7 +1151,7 @@ bool Qcow2DiskImage::Flush() { size_t bytes = l2_entries_ * sizeof(uint64_t); _fseeki64(file_, entry.l2_offset, SEEK_SET); if (fwrite(be_data.data(), 1, bytes, file_) != bytes) { - LOG_ERROR("Qcow2: Flush: failed to write L2 table at 0x%llX", + LOG_ERROR("Qcow2: Flush: failed to write L2 table at 0x%" PRIX64, entry.l2_offset); } entry.dirty = false; @@ -1172,7 +1167,7 @@ bool Qcow2DiskImage::Flush() { size_t bytes = rfb_entries_ * sizeof(uint16_t); _fseeki64(file_, entry.offset_in_file, SEEK_SET); if (fwrite(be_data.data(), 1, bytes, file_) != bytes) { - LOG_ERROR("Qcow2: Flush: failed to write refcount block at 0x%llX", + LOG_ERROR("Qcow2: Flush: failed to write refcount block at 0x%" PRIX64, entry.offset_in_file); } entry.dirty = false; diff --git a/src/core/disk/qcow2_check.cpp b/src/core/disk/qcow2_check.cpp index c811ee5..bd30a2f 100644 --- a/src/core/disk/qcow2_check.cpp +++ b/src/core/disk/qcow2_check.cpp @@ -64,8 +64,7 @@ int Qcow2DiskImage::RepairLeaks(bool fix) { _fseeki64(file_, l2_offset, SEEK_SET); size_t l2_bytes = l2_entries_ * sizeof(uint64_t); if (fread(l2_raw.data(), 1, l2_bytes, file_) != l2_bytes) { - LOG_ERROR("Qcow2: Check: failed to read L2 table at 0x%llX", - (unsigned long long)l2_offset); + LOG_ERROR("Qcow2: Check: failed to read L2 table at 0x%" PRIX64, l2_offset); continue; } @@ -131,12 +130,12 @@ int Qcow2DiskImage::RepairLeaks(bool fix) { for (uint64_t idx = 0; idx < total_clusters; idx++) { if (stored[idx] > computed[idx]) { - LOG_INFO("Leaked cluster %llu refcount=%u reference=%u", - (unsigned long long)idx, stored[idx], computed[idx]); + LOG_INFO("Leaked cluster %" PRIu64 " refcount=%u reference=%u", + idx, stored[idx], computed[idx]); leaked++; } else if (stored[idx] < computed[idx]) { - LOG_ERROR("ERROR cluster %llu refcount=%u reference=%u", - (unsigned long long)idx, stored[idx], computed[idx]); + LOG_ERROR("ERROR cluster %" PRIu64 " refcount=%u reference=%u", + idx, stored[idx], computed[idx]); errors++; } } @@ -154,16 +153,16 @@ int Qcow2DiskImage::RepairLeaks(bool fix) { } uint64_t total_guest_clusters = virtual_size_ / cluster_size_; - LOG_INFO("%llu/%llu = %.2f%% allocated, %.2f%% fragmented, %.2f%% compressed clusters", - (unsigned long long)allocated_clusters, - (unsigned long long)total_guest_clusters, + LOG_INFO("%" PRIu64 "/%" PRIu64 " = %.2f%% allocated, %.2f%% fragmented, %.2f%% compressed clusters", + allocated_clusters, + total_guest_clusters, total_guest_clusters > 0 ? 100.0 * allocated_clusters / total_guest_clusters : 0.0, allocated_clusters > 0 ? 100.0 * fragmented_clusters / allocated_clusters : 0.0, allocated_clusters > 0 ? 100.0 * compressed_clusters / allocated_clusters : 0.0); - LOG_INFO("Image end offset: %llu", (unsigned long long)file_end_); + LOG_INFO("Image end offset: %" PRIu64, file_end_); // Phase 3: fix leaked clusters only (stored > computed). // Errors (stored < computed) indicate possible corruption — never @@ -202,7 +201,7 @@ int Qcow2DiskImage::RepairLeaks(bool fix) { _fseeki64(file_, block_offset, SEEK_SET); if (fwrite(block.data(), 1, bytes, file_) != bytes) { LOG_ERROR("Qcow2: RepairLeaks: failed to write refcount block " - "at 0x%llX", (unsigned long long)block_offset); + "at 0x%" PRIX64, block_offset); } } } diff --git a/src/core/disk/raw_image.cpp b/src/core/disk/raw_image.cpp index baf033c..d06c772 100644 --- a/src/core/disk/raw_image.cpp +++ b/src/core/disk/raw_image.cpp @@ -30,13 +30,13 @@ bool RawDiskImage::Open(const std::string& path) { _fseeki64(file_, 0, SEEK_SET); if (disk_size_ < 512) { - LOG_ERROR("RawDiskImage: image too small (%llu bytes)", disk_size_); + LOG_ERROR("RawDiskImage: image too small (%" PRIu64 " bytes)", disk_size_); fclose(file_); file_ = nullptr; return false; } - LOG_INFO("RawDiskImage: %s, %llu bytes (%llu MB)", + LOG_INFO("RawDiskImage: %s, %" PRIu64 " bytes (%" PRIu64 " MB)", path.c_str(), disk_size_, disk_size_ / (1024 * 1024)); return true; } diff --git a/src/core/guest_agent/guest_agent_handler.cpp b/src/core/guest_agent/guest_agent_handler.cpp index 21e308e..3f1073b 100644 --- a/src/core/guest_agent/guest_agent_handler.cpp +++ b/src/core/guest_agent/guest_agent_handler.cpp @@ -103,7 +103,7 @@ void GuestAgentHandler::StartSyncHandshake() { oss << '\n'; SendRaw(oss.str()); - LOG_INFO("GuestAgent: sent guest-sync-delimited id=%lld", (long long)id); + LOG_INFO("GuestAgent: sent guest-sync-delimited id=%" PRId64, id); } void GuestAgentHandler::OnDataReceived(const uint8_t* data, size_t len) { @@ -192,7 +192,7 @@ void GuestAgentHandler::SendCommand(const std::string& command) { oss << R"({"execute":")" << JsonEscape(command) << R"(","id":)" << id << "}\n"; - LOG_INFO("GuestAgent: sending %s (id=%llu)", command.c_str(), (unsigned long long)id); + LOG_INFO("GuestAgent: sending %s (id=%" PRIu64 ")", command.c_str(), id); SendRaw(oss.str()); } @@ -209,7 +209,7 @@ void GuestAgentHandler::SendCommand(const std::string& command, << R"(","arguments":)" << arguments_json << R"(,"id":)" << id << "}\n"; - LOG_INFO("GuestAgent: sending %s (id=%llu)", command.c_str(), (unsigned long long)id); + LOG_INFO("GuestAgent: sending %s (id=%" PRIu64 ")", command.c_str(), id); SendRaw(oss.str()); } diff --git a/src/core/vmm/address_space.cpp b/src/core/vmm/address_space.cpp index a1b2ab6..6f099ec 100644 --- a/src/core/vmm/address_space.cpp +++ b/src/core/vmm/address_space.cpp @@ -70,7 +70,7 @@ bool AddressSpace::HandleMmioRead(uint64_t addr, uint8_t size, return true; } *value = 0; - LOG_DEBUG("Unhandled MMIO read: addr=0x%llX size=%u", addr, size); + LOG_DEBUG("Unhandled MMIO read: addr=0x%" PRIX64 " size=%u", addr, size); return false; } @@ -83,7 +83,7 @@ bool AddressSpace::HandleMmioWrite(uint64_t addr, uint8_t size, dev->MmioWrite(offset, size, value); return true; } - LOG_DEBUG("Unhandled MMIO write: addr=0x%llX size=%u val=0x%llX", + LOG_DEBUG("Unhandled MMIO write: addr=0x%" PRIX64 " size=%u val=0x%" PRIX64, addr, size, value); return false; } diff --git a/src/core/vmm/hypervisor_vm.h b/src/core/vmm/hypervisor_vm.h index 3cb6fa2..edf9e4d 100644 --- a/src/core/vmm/hypervisor_vm.h +++ b/src/core/vmm/hypervisor_vm.h @@ -26,6 +26,12 @@ class HypervisorVm { virtual void RequestInterrupt(const InterruptRequest& req) = 0; + // Optional hook for hypervisors with an in-kernel irqchip (e.g. KVM). + // Returns true if the IRQ was injected through the hypervisor's own + // interrupt controller and the generic userspace IOAPIC path must be + // skipped. Default returns false so HVF / WHVP keep their current path. + virtual bool AssertIrq(uint32_t /*gsi*/, bool /*level*/) { return false; } + virtual void SetGuestMemMap(const GuestMemMap*) {} virtual void QueueInterrupt(uint32_t vector, uint32_t dest_vcpu) { diff --git a/src/core/vmm/types.h b/src/core/vmm/types.h index 7b19106..4bcb418 100644 --- a/src/core/vmm/types.h +++ b/src/core/vmm/types.h @@ -6,6 +6,7 @@ #include #include +#include // PRIu64 / PRIx64 / PRIX64 / PRId64 for portable uint64_t printf #include #include #include diff --git a/src/core/vmm/vm.cpp b/src/core/vmm/vm.cpp index b45302f..8783987 100644 --- a/src/core/vmm/vm.cpp +++ b/src/core/vmm/vm.cpp @@ -11,10 +11,12 @@ #include "platform/macos/hypervisor/aarch64/hvf_vm.h" #elif defined(_WIN32) #include "core/arch/x86_64/x86_machine.h" +#elif defined(__linux__) && defined(__x86_64__) +#include "core/arch/x86_64/x86_machine.h" #endif static std::unique_ptr CreateMachineModel() { -#if defined(_WIN32) || (defined(__APPLE__) && defined(__x86_64__)) +#if defined(_WIN32) || (defined(__APPLE__) && defined(__x86_64__)) || (defined(__linux__) && defined(__x86_64__)) return std::make_unique(); #elif defined(__APPLE__) && defined(__aarch64__) return std::make_unique(); @@ -42,6 +44,9 @@ static std::string GetDefaultCmdline(bool debug_mode) { Vm::~Vm() { running_ = false; + if (console_input_thread_.joinable()) { + console_input_thread_.join(); + } for (auto& t : vcpu_threads_) { if (t.joinable()) t.join(); } @@ -84,6 +89,9 @@ std::unique_ptr Vm::Create(const VmConfig& config) { vm->audio_port_ = config.audio_port; if (!vm->console_port_ && config.interactive) { vm->console_port_ = VmPlatform::CreateConsolePort(); + // We own the console port: no external IPC controller will pump stdin + // for us, so we need to run our own input thread. + vm->owned_console_input_ = true; } vm->machine_ = CreateMachineModel(); @@ -111,7 +119,7 @@ std::unique_ptr Vm::Create(const VmConfig& config) { vm->vcpu_startup_[i] = std::make_unique(); } -#if defined(_WIN32) || (defined(__APPLE__) && defined(__x86_64__)) +#if defined(_WIN32) || (defined(__APPLE__) && defined(__x86_64__)) || (defined(__linux__) && defined(__x86_64__)) { auto* x86m = dynamic_cast(vm->machine_.get()); if (x86m) { @@ -229,7 +237,7 @@ void Vm::SetupVCpuCallbacks(uint32_t vcpu_index) { } void Vm::FinalizeBoot(const VmConfig& config) { -#if defined(_WIN32) || (defined(__APPLE__) && defined(__x86_64__)) +#if defined(_WIN32) || (defined(__APPLE__) && defined(__x86_64__)) || (defined(__linux__) && defined(__x86_64__)) { auto* x86m = dynamic_cast(machine_.get()); if (x86m) { @@ -258,8 +266,8 @@ bool Vm::AllocateMemory(uint64_t size) { uint8_t* base = VmPlatform::AllocateRam(alloc); if (!base) { - LOG_ERROR("Failed to allocate %llu MB guest RAM", - (unsigned long long)(alloc / (1024 * 1024))); + LOG_ERROR("Failed to allocate %" PRIu64 " MB guest RAM", + alloc / (1024 * 1024)); return false; } @@ -284,15 +292,16 @@ bool Vm::AllocateMemory(uint64_t size) { if (!hv_vm_->MapMemory(mmio_gap_end, base + mem_.low_size, mem_.high_size, true)) return false; - LOG_INFO("Guest RAM: %llu MB [0-0x%llX] + [0x%llX-0x%llX] at HVA %p", - (unsigned long long)(alloc / (1024 * 1024)), - (unsigned long long)(mem_.low_size - 1), - (unsigned long long)mmio_gap_end, - (unsigned long long)(mmio_gap_end + mem_.high_size - 1), + LOG_INFO("Guest RAM: %" PRIu64 " MB [0-0x%" PRIX64 "] + " + "[0x%" PRIX64 "-0x%" PRIX64 "] at HVA %p", + alloc / (1024 * 1024), + mem_.low_size - 1, + mmio_gap_end, + mmio_gap_end + mem_.high_size - 1, base); } else { - LOG_INFO("Guest RAM: %llu MB at HVA %p", - (unsigned long long)(alloc / (1024 * 1024)), base); + LOG_INFO("Guest RAM: %" PRIu64 " MB at HVA %p", + alloc / (1024 * 1024), base); } } else { // ARM-style layout: RAM starts at a high base, MMIO below @@ -303,9 +312,8 @@ bool Vm::AllocateMemory(uint64_t size) { if (!hv_vm_->MapMemory(ram_base, base, alloc, true)) return false; - LOG_INFO("Guest RAM: %llu MB at GPA 0x%llX, HVA %p", - (unsigned long long)(alloc / (1024 * 1024)), - (unsigned long long)ram_base, base); + LOG_INFO("Guest RAM: %" PRIu64 " MB at GPA 0x%" PRIX64 ", HVA %p", + alloc / (1024 * 1024), ram_base, base); } return true; } @@ -569,28 +577,40 @@ void Vm::VCpuThreadFunc(uint32_t vcpu_index) { break; case VCpuExitAction::kShutdown: - LOG_INFO("vCPU %u: shutdown (after %llu exits)", - vcpu_index, (unsigned long long)exit_count); + LOG_INFO("vCPU %u: shutdown (after %" PRIu64 " exits)", + vcpu_index, exit_count); RequestStop(); return; case VCpuExitAction::kError: - LOG_ERROR("vCPU %u: error (after %llu exits)", - vcpu_index, (unsigned long long)exit_count); + LOG_ERROR("vCPU %u: error (after %" PRIu64 " exits)", + vcpu_index, exit_count); exit_code_.store(1); RequestStop(); return; } } - LOG_INFO("vCPU %u stopped (total exits: %llu)", - vcpu_index, (unsigned long long)exit_count); + LOG_INFO("vCPU %u stopped (total exits: %" PRIu64 ")", + vcpu_index, exit_count); } int Vm::Run() { running_ = true; LOG_INFO("Starting VM execution..."); + if (owned_console_input_ && console_port_) { + console_input_thread_ = std::thread([this]() { + uint8_t buf[64]; + while (running_.load()) { + size_t n = console_port_->Read(buf, sizeof(buf)); + if (n > 0) { + InjectConsoleBytes(buf, n); + } + } + }); + } + // Phase 1: launch threads; each creates its vCPU then signals ready. for (uint32_t i = 0; i < cpu_count_; i++) { vcpu_threads_.emplace_back(&Vm::VCpuThreadFunc, this, i); diff --git a/src/core/vmm/vm.h b/src/core/vmm/vm.h index 679dff4..a48cd72 100644 --- a/src/core/vmm/vm.h +++ b/src/core/vmm/vm.h @@ -168,4 +168,10 @@ class Vm { uint32_t inject_prev_buttons_ = 0; std::vector> vcpu_startup_; + + // Input pump thread for locally-created ConsolePort (CLI interactive mode). + // Inactive when running under an external IPC controller that injects + // console bytes itself. + std::thread console_input_thread_; + bool owned_console_input_ = false; }; diff --git a/src/manager/llm_proxy.cpp b/src/manager/llm_proxy.cpp index d3269ee..2a700ea 100644 --- a/src/manager/llm_proxy.cpp +++ b/src/manager/llm_proxy.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -659,10 +660,10 @@ bool LlmProxyService::ForwardToUpstream(uintptr_t client_sock, out_stream_end.wsa_error = client_err; char detail[256]; snprintf(detail, sizeof(detail), - "VM client closed after %llu ms (%llu bytes, %llu chunks); wsa=%d %s", - static_cast(elapsed_ms), - static_cast(bytes_total), - static_cast(chunk_count), + "VM client closed after %" PRIu64 " ms (%" PRIu64 " bytes, %" PRIu64 " chunks); wsa=%d %s", + elapsed_ms, + bytes_total, + chunk_count, static_cast(client_err), WsaErrorName(static_cast(client_err))); out_stream_end.detail = detail; @@ -674,11 +675,11 @@ bool LlmProxyService::ForwardToUpstream(uintptr_t client_sock, out_stream_end.reason = saw_done ? "upstream_done" : "upstream_closed_no_done"; char detail[256]; snprintf(detail, sizeof(detail), - "upstream %s after %llu ms (%llu bytes, %llu chunks)", + "upstream %s after %" PRIu64 " ms (%" PRIu64 " bytes, %" PRIu64 " chunks)", saw_done ? "sent [DONE]" : "closed without [DONE]", - static_cast(elapsed_ms), - static_cast(bytes_total), - static_cast(chunk_count)); + elapsed_ms, + bytes_total, + chunk_count); out_stream_end.detail = detail; // "Closed without [DONE]" is abnormal; surface it as error. // The happy path ([DONE]) is intentionally not logged. @@ -692,12 +693,12 @@ bool LlmProxyService::ForwardToUpstream(uintptr_t client_sock, out_stream_end.winhttp_error = last_err; char detail[320]; snprintf(detail, sizeof(detail), - "upstream read failed after %llu ms (%llu bytes, %llu chunks, " - "gap=%llu ms, done=%s); winhttp=%lu %s", - static_cast(elapsed_ms), - static_cast(bytes_total), - static_cast(chunk_count), - static_cast(gap_ms), + "upstream read failed after %" PRIu64 " ms (%" PRIu64 " bytes, %" PRIu64 " chunks, " + "gap=%" PRIu64 " ms, done=%s); winhttp=%lu %s", + elapsed_ms, + bytes_total, + chunk_count, + gap_ms, saw_done ? "true" : "false", static_cast(last_err), WinHttpErrorName(last_err)); diff --git a/src/manager/ui/create_vm_dialog.cpp b/src/manager/ui/create_vm_dialog.cpp index 7a96c49..3a6b7c2 100644 --- a/src/manager/ui/create_vm_dialog.cpp +++ b/src/manager/ui/create_vm_dialog.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include #include @@ -148,8 +149,8 @@ static std::string FormatSize(uint64_t bytes) { } char buf[64]; if (unit == 0) { - snprintf(buf, sizeof(buf), "%llu %s", - static_cast(bytes), kUnits[unit]); + snprintf(buf, sizeof(buf), "%" PRIu64 " %s", + bytes, kUnits[unit]); } else { snprintf(buf, sizeof(buf), "%.1f %s", value, kUnits[unit]); } diff --git a/src/manager/ui/settings_dialog.cpp b/src/manager/ui/settings_dialog.cpp index bef600f..ca01179 100644 --- a/src/manager/ui/settings_dialog.cpp +++ b/src/manager/ui/settings_dialog.cpp @@ -8,6 +8,7 @@ #define WIN32_LEAN_AND_MEAN #include +#include #include #include @@ -40,7 +41,7 @@ static std::string FormatSize(uint64_t bytes) { else if (bytes >= 1024) snprintf(buf, sizeof(buf), "%.0f KB", bytes / 1024.0); else - snprintf(buf, sizeof(buf), "%llu B", static_cast(bytes)); + snprintf(buf, sizeof(buf), "%" PRIu64 " B", bytes); return buf; } diff --git a/src/platform/CMakeLists.txt b/src/platform/CMakeLists.txt index 85d186f..d611fe3 100644 --- a/src/platform/CMakeLists.txt +++ b/src/platform/CMakeLists.txt @@ -23,7 +23,7 @@ if(WIN32) elseif(APPLE) set(HVF_COMMON_SOURCES ${CMAKE_SOURCE_DIR}/src/platform/macos/hypervisor/hvf_platform.cpp - ${CMAKE_SOURCE_DIR}/src/platform/macos/console/posix_console_port.cpp + ${CMAKE_SOURCE_DIR}/src/platform/posix/console/posix_console_port.cpp ${CMAKE_SOURCE_DIR}/src/platform/macos/vm_platform_macos.cpp ) @@ -53,4 +53,26 @@ elseif(APPLE) tenbox_core "-framework Hypervisor" ) +elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(KVM_SOURCES + ${CMAKE_SOURCE_DIR}/src/platform/linux/hypervisor/kvm_platform.cpp + ${CMAKE_SOURCE_DIR}/src/platform/linux/hypervisor/x86_64/kvm_vm.cpp + ${CMAKE_SOURCE_DIR}/src/platform/linux/hypervisor/x86_64/kvm_vcpu.cpp + ${CMAKE_SOURCE_DIR}/src/platform/linux/vm_platform_linux.cpp + ${CMAKE_SOURCE_DIR}/src/platform/posix/console/posix_console_port.cpp + ) + + add_library(tenbox_platform STATIC ${KVM_SOURCES}) + + target_include_directories(tenbox_platform + PUBLIC + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_BINARY_DIR} + ) + + target_link_libraries(tenbox_platform + PUBLIC + tenbox_core + pthread + ) endif() diff --git a/src/platform/linux/hypervisor/kvm_platform.cpp b/src/platform/linux/hypervisor/kvm_platform.cpp new file mode 100644 index 0000000..efa8462 --- /dev/null +++ b/src/platform/linux/hypervisor/kvm_platform.cpp @@ -0,0 +1,50 @@ +#include "platform/linux/hypervisor/kvm_platform.h" +#include "core/vmm/types.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace kvm { + +namespace { +std::mutex& KvmFdMutex() { + static std::mutex m; + return m; +} +int g_kvm_fd = -1; +} + +int GetKvmFd() { + std::lock_guard lock(KvmFdMutex()); + if (g_kvm_fd >= 0) return g_kvm_fd; + + int fd = ::open("/dev/kvm", O_RDWR | O_CLOEXEC); + if (fd < 0) { + LOG_ERROR("kvm: open(/dev/kvm) failed: %s", strerror(errno)); + return -1; + } + + int api = ::ioctl(fd, KVM_GET_API_VERSION, 0); + if (api != KVM_API_VERSION) { + LOG_ERROR("kvm: KVM_GET_API_VERSION=%d, expected %d", api, KVM_API_VERSION); + ::close(fd); + return -1; + } + + g_kvm_fd = fd; + return g_kvm_fd; +} + +bool IsHypervisorPresent() { + if (::access("/dev/kvm", R_OK | W_OK) != 0) { + return false; + } + return GetKvmFd() >= 0; +} + +} // namespace kvm diff --git a/src/platform/linux/hypervisor/kvm_platform.h b/src/platform/linux/hypervisor/kvm_platform.h new file mode 100644 index 0000000..8ade1d5 --- /dev/null +++ b/src/platform/linux/hypervisor/kvm_platform.h @@ -0,0 +1,12 @@ +#pragma once + +namespace kvm { + +// Returns true if /dev/kvm is usable and the API version matches. +bool IsHypervisorPresent(); + +// Open /dev/kvm once and cache the fd; returned fd is owned by this module and +// must NOT be closed by the caller. +int GetKvmFd(); + +} // namespace kvm diff --git a/src/platform/linux/hypervisor/x86_64/kvm_vcpu.cpp b/src/platform/linux/hypervisor/x86_64/kvm_vcpu.cpp new file mode 100644 index 0000000..95912fb --- /dev/null +++ b/src/platform/linux/hypervisor/x86_64/kvm_vcpu.cpp @@ -0,0 +1,334 @@ +#include "platform/linux/hypervisor/x86_64/kvm_vcpu.h" +#include "platform/linux/hypervisor/x86_64/kvm_vm.h" +#include "platform/linux/hypervisor/kvm_platform.h" +#include "core/arch/x86_64/boot.h" +#include "core/device/irq/local_apic.h" +#include "core/vmm/types.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace kvm { + +// Signal used to kick a vCPU out of KVM_RUN. The handler does nothing — +// arriving in userspace with a pending signal is enough for KVM to bail out +// of the ioctl with KVM_EXIT_INTR or -EINTR. +static constexpr int kCancelSignal = SIGUSR1; + +static void CancelSignalHandler(int /*sig*/) { + // Intentionally empty; see above. +} + +static void InstallCancelSignalHandler() { + static bool installed = false; + static std::mutex m; + std::lock_guard lock(m); + if (installed) return; + + struct sigaction sa{}; + sa.sa_handler = CancelSignalHandler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; // no SA_RESTART: we want KVM_RUN to return EINTR + ::sigaction(kCancelSignal, &sa, nullptr); + installed = true; +} + +KvmVCpu::~KvmVCpu() { + if (run_) { + ::munmap(run_, run_size_); + run_ = nullptr; + } + if (vcpu_fd_ >= 0) { + ::close(vcpu_fd_); + vcpu_fd_ = -1; + } +} + +std::unique_ptr KvmVCpu::Create(KvmVm& vm, uint32_t index, + AddressSpace* addr_space) { + auto vcpu = std::unique_ptr(new KvmVCpu()); + vcpu->index_ = index; + vcpu->addr_space_ = addr_space; + + vcpu->vcpu_fd_ = ::ioctl(vm.VmFd(), KVM_CREATE_VCPU, (unsigned long)index); + if (vcpu->vcpu_fd_ < 0) { + LOG_ERROR("kvm: KVM_CREATE_VCPU(%u) failed: %s", index, strerror(errno)); + return nullptr; + } + + vcpu->run_size_ = vm.VcpuMmapSize(); + void* run = ::mmap(nullptr, vcpu->run_size_, PROT_READ | PROT_WRITE, + MAP_SHARED, vcpu->vcpu_fd_, 0); + if (run == MAP_FAILED) { + LOG_ERROR("kvm: mmap kvm_run for vCPU %u failed: %s", + index, strerror(errno)); + return nullptr; + } + vcpu->run_ = static_cast(run); + + if (!vcpu->SetupCpuid()) { + return nullptr; + } + + LOG_INFO("kvm: vCPU %u created", index); + return vcpu; +} + +bool KvmVCpu::SetupCpuid() { + // We need the KVM top-level fd to query the supported CPUID list. + int kvm_fd = GetKvmFd(); + if (kvm_fd < 0) return false; + + constexpr uint32_t kMaxEntries = 256; + size_t bytes = sizeof(struct kvm_cpuid2) + + kMaxEntries * sizeof(struct kvm_cpuid_entry2); + auto buf = std::unique_ptr(new uint8_t[bytes]()); + auto* cpuid = reinterpret_cast(buf.get()); + cpuid->nent = kMaxEntries; + + if (::ioctl(kvm_fd, KVM_GET_SUPPORTED_CPUID, cpuid) < 0) { + LOG_ERROR("kvm: KVM_GET_SUPPORTED_CPUID failed: %s", strerror(errno)); + return false; + } + + for (uint32_t i = 0; i < cpuid->nent; i++) { + auto& e = cpuid->entries[i]; + if (e.function == 1) { + // Mask MONITOR/MWAIT (ECX bit 3) and TSC-deadline (ECX bit 24) to + // stay in line with the userspace-LAPIC backends. Patch + // EBX[31:24] with this vCPU's APIC ID (= index). + constexpr uint32_t kMaskOutEcx = (1u << 3) | (1u << 24); + e.ecx &= ~kMaskOutEcx; + // Advertise that we are running under a hypervisor. KVM does NOT + // set this bit in KVM_GET_SUPPORTED_CPUID (it mirrors raw host + // CPUID, where the bit is usually 0); userspace must OR it in. + // Without it, Linux's init_hypervisor_platform() short-circuits + // before scanning the 0x40000000 "KVMKVMKVM" signature, and none + // of KVM's paravirt features (kvm-clock, PV-EOI, async PF, steal + // time, PV UNHALT) get enabled. + e.ecx |= (1u << 31); + e.ebx = (e.ebx & 0x00FFFFFFu) | (index_ << 24); + } else if (e.function == 0xB || e.function == 0x1F) { + // x2APIC topology leaves: EDX = x2APIC ID. + e.edx = index_; + } + } + + if (::ioctl(vcpu_fd_, KVM_SET_CPUID2, cpuid) < 0) { + LOG_ERROR("kvm: KVM_SET_CPUID2 failed: %s", strerror(errno)); + return false; + } + return true; +} + +void KvmVCpu::OnThreadInit() { + LocalApic::SetCurrentCpu(index_); + InstallCancelSignalHandler(); + + // Unblock the cancel signal on this (vCPU worker) thread, in case it was + // inherited-blocked. + sigset_t set; + sigemptyset(&set); + sigaddset(&set, kCancelSignal); + pthread_sigmask(SIG_UNBLOCK, &set, nullptr); + + thread_id_.store(static_cast(pthread_self()), + std::memory_order_release); +} + +bool KvmVCpu::SetupBootRegisters(uint8_t* ram) { + using x86::GdtEntry; + namespace Layout = x86::Layout; + + // Write GDT into guest memory. + auto* gdt = reinterpret_cast(ram + Layout::kGdtBase); + gdt->null = 0x0000000000000000ULL; + gdt->unused = 0x0000000000000000ULL; + gdt->code32 = 0x00CF9B000000FFFFULL; // 32-bit code, flat, DPL0 + gdt->data32 = 0x00CF93000000FFFFULL; // 32-bit data, flat, DPL0 + + struct kvm_sregs sregs{}; + if (::ioctl(vcpu_fd_, KVM_GET_SREGS, &sregs) < 0) { + LOG_ERROR("kvm: KVM_GET_SREGS failed: %s", strerror(errno)); + return false; + } + + auto make_seg = [](uint16_t selector, uint32_t base, uint32_t limit, + uint8_t type, uint8_t s, uint8_t dpl, uint8_t present, + uint8_t db, uint8_t l, uint8_t g) { + struct kvm_segment s_out{}; + s_out.base = base; + s_out.limit = limit; + s_out.selector = selector; + s_out.type = type; + s_out.present = present; + s_out.dpl = dpl; + s_out.db = db; + s_out.s = s; + s_out.l = l; + s_out.g = g; + s_out.avl = 0; + s_out.unusable = 0; + s_out.padding = 0; + return s_out; + }; + + // CS selector 0x10: 32-bit code, flat + sregs.cs = make_seg(0x10, 0, 0xFFFFFFFF, /*type=*/0xB, /*s=*/1, /*dpl=*/0, + /*present=*/1, /*db=*/1, /*l=*/0, /*g=*/1); + // DS/ES/SS selector 0x18: 32-bit data, flat + struct kvm_segment data = make_seg(0x18, 0, 0xFFFFFFFF, /*type=*/0x3, /*s=*/1, + /*dpl=*/0, /*present=*/1, /*db=*/1, + /*l=*/0, /*g=*/1); + sregs.ds = data; + sregs.es = data; + sregs.ss = data; + + // FS / GS null + struct kvm_segment null_seg{}; + null_seg.unusable = 1; + sregs.fs = null_seg; + sregs.gs = null_seg; + + // TR (required to be valid for VMX entry). + sregs.tr = make_seg(0, 0, 0xFFFF, /*type=*/0xB, /*s=*/0, /*dpl=*/0, + /*present=*/1, /*db=*/0, /*l=*/0, /*g=*/0); + // LDTR: unusable / present type=LDT + sregs.ldt = make_seg(0, 0, 0xFFFF, /*type=*/0x2, /*s=*/0, /*dpl=*/0, + /*present=*/1, /*db=*/0, /*l=*/0, /*g=*/0); + + sregs.gdt.base = Layout::kGdtBase; + sregs.gdt.limit = sizeof(GdtEntry) - 1; + sregs.idt.base = 0; + sregs.idt.limit = 0; + + // CR0 = PE | ET (protected mode, FPU present). Keep paging off. + sregs.cr0 = (sregs.cr0 & ~0x80000001ULL) | 0x11; + sregs.cr2 = 0; + sregs.cr3 = 0; + sregs.cr4 = sregs.cr4 & 0; // clear PAE/etc. while we're in 32-bit mode + sregs.efer = 0; + + if (::ioctl(vcpu_fd_, KVM_SET_SREGS, &sregs) < 0) { + LOG_ERROR("kvm: KVM_SET_SREGS failed: %s", strerror(errno)); + return false; + } + + struct kvm_regs regs{}; + regs.rip = Layout::kKernelBase; + regs.rsi = Layout::kBootParams; + regs.rflags = 0x2; + if (::ioctl(vcpu_fd_, KVM_SET_REGS, ®s) < 0) { + LOG_ERROR("kvm: KVM_SET_REGS failed: %s", strerror(errno)); + return false; + } + + return true; +} + +VCpuExitAction KvmVCpu::RunOnce() { + int rc = ::ioctl(vcpu_fd_, KVM_RUN, 0); + if (rc < 0) { + if (errno == EINTR || errno == EAGAIN) { + // Signal or immediate_exit request. + run_->immediate_exit = 0; + return VCpuExitAction::kContinue; + } + LOG_ERROR("kvm: KVM_RUN failed: %s", strerror(errno)); + return VCpuExitAction::kError; + } + + switch (run_->exit_reason) { + case KVM_EXIT_IO: { + auto& io = run_->io; + uint8_t* base = reinterpret_cast(run_) + io.data_offset; + for (uint32_t i = 0; i < io.count; i++) { + uint8_t* buf = base + i * io.size; + if (io.direction == KVM_EXIT_IO_OUT) { + uint32_t val = 0; + ::memcpy(&val, buf, io.size); + addr_space_->HandlePortOut(io.port, io.size, val); + } else { + uint32_t val = 0; + addr_space_->HandlePortIn(io.port, io.size, &val); + ::memcpy(buf, &val, io.size); + } + } + return VCpuExitAction::kContinue; + } + + case KVM_EXIT_MMIO: { + auto& mmio = run_->mmio; + if (mmio.is_write) { + uint64_t val = 0; + ::memcpy(&val, mmio.data, mmio.len); + addr_space_->HandleMmioWrite(mmio.phys_addr, mmio.len, val); + } else { + uint64_t val = 0; + addr_space_->HandleMmioRead(mmio.phys_addr, mmio.len, &val); + ::memcpy(mmio.data, &val, mmio.len); + } + return VCpuExitAction::kContinue; + } + + case KVM_EXIT_HLT: + return VCpuExitAction::kHalt; + + case KVM_EXIT_INTR: + return VCpuExitAction::kContinue; + + case KVM_EXIT_SHUTDOWN: + LOG_INFO("kvm: vCPU %u KVM_EXIT_SHUTDOWN", index_); + return VCpuExitAction::kShutdown; + + case KVM_EXIT_FAIL_ENTRY: + LOG_ERROR("kvm: KVM_EXIT_FAIL_ENTRY reason=0x%" PRIx64 " cpu=%u", + (uint64_t)run_->fail_entry.hardware_entry_failure_reason, + run_->fail_entry.cpu); + return VCpuExitAction::kError; + + case KVM_EXIT_INTERNAL_ERROR: + LOG_ERROR("kvm: KVM_EXIT_INTERNAL_ERROR suberror=%u", + run_->internal.suberror); + return VCpuExitAction::kError; + + case KVM_EXIT_SYSTEM_EVENT: + LOG_INFO("kvm: KVM_EXIT_SYSTEM_EVENT type=%u", run_->system_event.type); + return VCpuExitAction::kShutdown; + + case KVM_EXIT_IRQ_WINDOW_OPEN: + case KVM_EXIT_UNKNOWN: + return VCpuExitAction::kContinue; + + default: + LOG_WARN("kvm: unhandled exit reason %u", run_->exit_reason); + return VCpuExitAction::kContinue; + } +} + +void KvmVCpu::CancelRun() { + if (run_) { + run_->immediate_exit = 1; + } + unsigned long tid = thread_id_.load(std::memory_order_acquire); + if (tid) { + ::pthread_kill(static_cast(tid), kCancelSignal); + } +} + +bool KvmVCpu::WaitForInterrupt(uint32_t timeout_ms) { + // With an in-kernel LAPIC, HLT is normally handled inside KVM and we do + // not surface KVM_EXIT_HLT. If we do get here, just sleep briefly so the + // run loop keeps responsive to CancelRun. + if (timeout_ms == 0) timeout_ms = 1; + ::usleep(static_cast(timeout_ms) * 1000); + return false; +} + +} // namespace kvm diff --git a/src/platform/linux/hypervisor/x86_64/kvm_vcpu.h b/src/platform/linux/hypervisor/x86_64/kvm_vcpu.h new file mode 100644 index 0000000..c3c8618 --- /dev/null +++ b/src/platform/linux/hypervisor/x86_64/kvm_vcpu.h @@ -0,0 +1,61 @@ +#pragma once + +#include "core/vmm/address_space.h" +#include "core/vmm/hypervisor_vcpu.h" + +#include +#include +#include + +struct kvm_run; + +namespace kvm { + +class KvmVm; + +class KvmVCpu final : public HypervisorVCpu { +public: + ~KvmVCpu() override; + + static std::unique_ptr Create(KvmVm& vm, uint32_t index, + AddressSpace* addr_space); + + VCpuExitAction RunOnce() override; + void CancelRun() override; + uint32_t Index() const override { return index_; } + + bool SetupBootRegisters(uint8_t* ram) override; + + void OnThreadInit() override; + + bool WaitForInterrupt(uint32_t timeout_ms) override; + + // KVM delivers IPIs through the in-kernel LAPIC, so these are no-ops. + void OnStartup(const VCpuStartupState&) override {} + + // KVM's in-kernel LAPIC intercepts BSP's ICR writes and handles the + // INIT-SIPI-SIPI sequence entirely in the kernel: APs start in + // MP_STATE_UNINITIALIZED and KVM_RUN blocks in-kernel until SIPI arrives, + // at which point KVM sets CS:IP to the SIPI vector itself. Userspace + // SIPI callback never fires, so the generic wait would deadlock. + bool NeedsStartupWait() const override { return false; } + +private: + KvmVCpu() = default; + + bool SetupCpuid(); + + uint32_t index_ = 0; + int vcpu_fd_ = -1; + struct kvm_run* run_ = nullptr; + size_t run_size_ = 0; + + AddressSpace* addr_space_ = nullptr; + + // CancelRun writes immediate_exit = 1 and raises SIGUSR1 on the vCPU + // thread. OnThreadInit stashes the pthread id so that CancelRun, which + // can be invoked from any thread, can deliver the signal to the right one. + std::atomic thread_id_{0}; +}; + +} // namespace kvm diff --git a/src/platform/linux/hypervisor/x86_64/kvm_vm.cpp b/src/platform/linux/hypervisor/x86_64/kvm_vm.cpp new file mode 100644 index 0000000..6f9996a --- /dev/null +++ b/src/platform/linux/hypervisor/x86_64/kvm_vm.cpp @@ -0,0 +1,133 @@ +#include "platform/linux/hypervisor/x86_64/kvm_vm.h" +#include "platform/linux/hypervisor/x86_64/kvm_vcpu.h" +#include "platform/linux/hypervisor/kvm_platform.h" + +#include +#include +#include +#include +#include + +namespace kvm { + +// KVM requires us to pick fixed GPAs for the TSS region and the identity +// page table on Intel. These are the standard QEMU values and live inside +// the 0xFE000000+ reserved window (above 4 GiB mapped RAM gap). +static constexpr uint64_t kTssAddr = 0xfffbd000ULL; +static constexpr uint64_t kIdentityMapAddr = 0xfeffc000ULL; + +KvmVm::~KvmVm() { + if (vm_fd_ >= 0) { + ::close(vm_fd_); + vm_fd_ = -1; + } + // kvm_fd_ is owned by kvm_platform.cpp; do not close. +} + +std::unique_ptr KvmVm::Create(uint32_t cpu_count) { + auto vm = std::unique_ptr(new KvmVm()); + vm->cpu_count_ = cpu_count; + + vm->kvm_fd_ = GetKvmFd(); + if (vm->kvm_fd_ < 0) { + LOG_ERROR("kvm: /dev/kvm not available"); + return nullptr; + } + + int vcpu_mmap_size = ::ioctl(vm->kvm_fd_, KVM_GET_VCPU_MMAP_SIZE, 0); + if (vcpu_mmap_size < (int)sizeof(struct kvm_run)) { + LOG_ERROR("kvm: KVM_GET_VCPU_MMAP_SIZE failed (%d): %s", + vcpu_mmap_size, strerror(errno)); + return nullptr; + } + vm->vcpu_mmap_size_ = static_cast(vcpu_mmap_size); + + vm->vm_fd_ = ::ioctl(vm->kvm_fd_, KVM_CREATE_VM, 0); + if (vm->vm_fd_ < 0) { + LOG_ERROR("kvm: KVM_CREATE_VM failed: %s", strerror(errno)); + return nullptr; + } + + if (::ioctl(vm->vm_fd_, KVM_SET_TSS_ADDR, (unsigned long)kTssAddr) < 0) { + LOG_WARN("kvm: KVM_SET_TSS_ADDR failed: %s (non-fatal)", strerror(errno)); + } + + uint64_t idmap = kIdentityMapAddr; + if (::ioctl(vm->vm_fd_, KVM_SET_IDENTITY_MAP_ADDR, &idmap) < 0) { + LOG_WARN("kvm: KVM_SET_IDENTITY_MAP_ADDR failed: %s (non-fatal)", + strerror(errno)); + } + + if (::ioctl(vm->vm_fd_, KVM_CREATE_IRQCHIP, 0) < 0) { + LOG_ERROR("kvm: KVM_CREATE_IRQCHIP failed: %s", strerror(errno)); + return nullptr; + } + + struct kvm_pit_config pit_config{}; + pit_config.flags = KVM_PIT_SPEAKER_DUMMY; + if (::ioctl(vm->vm_fd_, KVM_CREATE_PIT2, &pit_config) < 0) { + LOG_WARN("kvm: KVM_CREATE_PIT2 failed: %s (non-fatal)", strerror(errno)); + } + + LOG_INFO("kvm: x86_64 VM created (%u vCPUs, mmap_size=%zu)", + cpu_count, vm->vcpu_mmap_size_); + return vm; +} + +bool KvmVm::MapMemory(GPA gpa, void* hva, uint64_t size, bool writable) { + uint32_t slot; + { + std::lock_guard lock(slot_mutex_); + slot = next_slot_++; + } + + struct kvm_userspace_memory_region region{}; + region.slot = slot; + region.flags = writable ? 0 : KVM_MEM_READONLY; + region.guest_phys_addr = gpa; + region.memory_size = size; + region.userspace_addr = reinterpret_cast(hva); + + if (::ioctl(vm_fd_, KVM_SET_USER_MEMORY_REGION, ®ion) < 0) { + LOG_ERROR("kvm: KVM_SET_USER_MEMORY_REGION(slot=%u gpa=0x%" PRIx64 + " size=0x%" PRIx64 ") failed: %s", + slot, gpa, size, strerror(errno)); + return false; + } + + LOG_INFO("kvm: mapped slot=%u GPA=0x%" PRIx64 " size=0x%" PRIx64 " HVA=%p%s", + slot, gpa, size, hva, writable ? "" : " [RO]"); + return true; +} + +bool KvmVm::UnmapMemory(GPA /*gpa*/, uint64_t /*size*/) { + // Not exercised by the current VM lifecycle (RAM is torn down with the + // process). Implementing this cleanly requires tracking slot IDs per GPA. + LOG_WARN("kvm: UnmapMemory not implemented"); + return false; +} + +std::unique_ptr KvmVm::CreateVCpu( + uint32_t index, AddressSpace* addr_space) { + return KvmVCpu::Create(*this, index, addr_space); +} + +void KvmVm::RequestInterrupt(const InterruptRequest& /*req*/) { + // With an in-kernel LAPIC, guest-generated IPIs are handled inside KVM. + // This path is only exercised by the userspace LAPIC used by HVF/WHVP. + LOG_WARN("kvm: RequestInterrupt called (no-op with in-kernel LAPIC)"); +} + +bool KvmVm::AssertIrq(uint32_t gsi, bool level) { + struct kvm_irq_level irq{}; + irq.irq = gsi; + irq.level = level ? 1 : 0; + if (::ioctl(vm_fd_, KVM_IRQ_LINE, &irq) < 0) { + LOG_WARN("kvm: KVM_IRQ_LINE(gsi=%u level=%d) failed: %s", + gsi, (int)level, strerror(errno)); + return true; // still consumed: don't fall through to userspace IOAPIC + } + return true; +} + +} // namespace kvm diff --git a/src/platform/linux/hypervisor/x86_64/kvm_vm.h b/src/platform/linux/hypervisor/x86_64/kvm_vm.h new file mode 100644 index 0000000..042f17b --- /dev/null +++ b/src/platform/linux/hypervisor/x86_64/kvm_vm.h @@ -0,0 +1,50 @@ +#pragma once + +#include "core/vmm/hypervisor_vm.h" +#include +#include +#include +#include + +namespace kvm { + +class KvmVCpu; + +class KvmVm final : public HypervisorVm { +public: + ~KvmVm() override; + + static std::unique_ptr Create(uint32_t cpu_count); + + bool MapMemory(GPA gpa, void* hva, uint64_t size, bool writable) override; + bool UnmapMemory(GPA gpa, uint64_t size) override; + + std::unique_ptr CreateVCpu( + uint32_t index, AddressSpace* addr_space) override; + + void RequestInterrupt(const InterruptRequest& req) override; + + // KVM has an in-kernel irqchip: IRQ lines go through KVM_IRQ_LINE. + bool AssertIrq(uint32_t gsi, bool level) override; + + void SetGuestMemMap(const GuestMemMap* mem) override { guest_mem_ = mem; } + + int VmFd() const { return vm_fd_; } + int KvmFd() const { return kvm_fd_; } + size_t VcpuMmapSize() const { return vcpu_mmap_size_; } + +private: + KvmVm() = default; + + int kvm_fd_ = -1; + int vm_fd_ = -1; + uint32_t cpu_count_ = 0; + size_t vcpu_mmap_size_ = 0; + + const GuestMemMap* guest_mem_ = nullptr; + + std::mutex slot_mutex_; + uint32_t next_slot_ = 0; +}; + +} // namespace kvm diff --git a/src/platform/linux/vm_platform_linux.cpp b/src/platform/linux/vm_platform_linux.cpp new file mode 100644 index 0000000..38f72b3 --- /dev/null +++ b/src/platform/linux/vm_platform_linux.cpp @@ -0,0 +1,42 @@ +#include "core/vmm/vm_platform.h" +#include "platform/linux/hypervisor/kvm_platform.h" +#include "platform/linux/hypervisor/x86_64/kvm_vm.h" +#include "platform/posix/console/posix_console_port.h" + +#include +#include +#include + +bool VmPlatform::IsHypervisorPresent() { + return kvm::IsHypervisorPresent(); +} + +std::unique_ptr VmPlatform::CreateHypervisor(uint32_t cpu_count) { + return kvm::KvmVm::Create(cpu_count); +} + +uint8_t* VmPlatform::AllocateRam(uint64_t size) { + void* ptr = ::mmap(nullptr, size, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (ptr == MAP_FAILED) return nullptr; + return static_cast(ptr); +} + +void VmPlatform::FreeRam(uint8_t* base, uint64_t size) { + if (base) { + ::munmap(base, size); + } +} + +std::shared_ptr VmPlatform::CreateConsolePort() { + return std::make_shared(); +} + +void VmPlatform::YieldCpu() { + ::sched_yield(); +} + +void VmPlatform::SleepMs(uint32_t ms) { + ::usleep(static_cast(ms) * 1000); +} diff --git a/src/platform/macos/hypervisor/aarch64/hvf_mmio_decode.cpp b/src/platform/macos/hypervisor/aarch64/hvf_mmio_decode.cpp index a13a9e0..f7c1d2c 100644 --- a/src/platform/macos/hypervisor/aarch64/hvf_mmio_decode.cpp +++ b/src/platform/macos/hypervisor/aarch64/hvf_mmio_decode.cpp @@ -94,8 +94,8 @@ bool DecodeMmioInstruction(uint32_t insn, uint64_t syndrome, return true; } - LOG_WARN("MMIO decode: unsupported instruction 0x%08x, syndrome 0x%llx", - insn, (unsigned long long)syndrome); + LOG_WARN("MMIO decode: unsupported instruction 0x%08x, syndrome 0x%" PRIx64, + insn, (uint64_t)syndrome); return false; } diff --git a/src/platform/macos/hypervisor/aarch64/hvf_vcpu.cpp b/src/platform/macos/hypervisor/aarch64/hvf_vcpu.cpp index 2badf4e..71934ab 100644 --- a/src/platform/macos/hypervisor/aarch64/hvf_vcpu.cpp +++ b/src/platform/macos/hypervisor/aarch64/hvf_vcpu.cpp @@ -4,6 +4,7 @@ #include "core/device/irq/gicv3_regs.h" #include "core/vmm/types.h" #include +#include #include #include @@ -31,9 +32,9 @@ static void SanitizeIdRegistersForSoftGic(hv_vcpu_t vcpu, uint32_t index) { uint64_t masked = isar1 & ~kPacMask1; if (masked != isar1) { ret = hv_vcpu_set_sys_reg(vcpu, HV_SYS_REG_ID_AA64ISAR1_EL1, masked); - LOG_INFO("hvf: vCPU %u ID_AA64ISAR1_EL1: 0x%llx -> 0x%llx (PAC masked, ret=%d)", - index, (unsigned long long)isar1, - (unsigned long long)masked, (int)ret); + LOG_INFO("hvf: vCPU %u ID_AA64ISAR1_EL1: 0x%" PRIx64 " -> 0x%" PRIx64 " (PAC masked, ret=%d)", + index, isar1, + masked, (int)ret); } } @@ -48,9 +49,9 @@ static void SanitizeIdRegistersForSoftGic(hv_vcpu_t vcpu, uint32_t index) { uint64_t masked = isar2 & ~kPacMask2; if (masked != isar2) { ret = hv_vcpu_set_sys_reg(vcpu, kIdAa64Isar2El1, masked); - LOG_INFO("hvf: vCPU %u ID_AA64ISAR2_EL1: 0x%llx -> 0x%llx (PAC masked, ret=%d)", - index, (unsigned long long)isar2, - (unsigned long long)masked, (int)ret); + LOG_INFO("hvf: vCPU %u ID_AA64ISAR2_EL1: 0x%" PRIx64 " -> 0x%" PRIx64 " (PAC masked, ret=%d)", + index, isar2, + masked, (int)ret); } } } @@ -224,9 +225,9 @@ bool HvfVCpu::SetupAarch64Boot(uint64_t entry_pc, uint64_t fdt_addr) { return false; } - LOG_INFO("hvf: vCPU %u ARM64 boot: PC=0x%llx, X0(FDT)=0x%llx", - index_, (unsigned long long)entry_pc, - (unsigned long long)fdt_addr); + LOG_INFO("hvf: vCPU %u ARM64 boot: PC=0x%" PRIx64 ", X0(FDT)=0x%" PRIx64, + index_, entry_pc, + fdt_addr); return true; } @@ -234,9 +235,9 @@ bool HvfVCpu::SetupSecondaryCpu(uint64_t entry_pc, uint64_t context_id) { hv_vcpu_set_reg(vcpu_, HV_REG_PC, entry_pc); hv_vcpu_set_reg(vcpu_, HV_REG_X0, context_id); hv_vcpu_set_reg(vcpu_, HV_REG_CPSR, 0x3C5); - LOG_INFO("hvf: vCPU %u secondary boot: PC=0x%llx, X0=0x%llx", - index_, (unsigned long long)entry_pc, - (unsigned long long)context_id); + LOG_INFO("hvf: vCPU %u secondary boot: PC=0x%" PRIx64 ", X0=0x%" PRIx64, + index_, entry_pc, + context_id); return true; } @@ -284,8 +285,8 @@ VCpuExitAction HvfVCpu::HandleException() { { s_stats_.ec_brk.fetch_add(1, std::memory_order_relaxed); uint16_t imm = syndrome & 0xFFFF; - LOG_WARN("hvf: vCPU %u BRK #%u (syndrome=0x%llx) — skipping", - index_, imm, (unsigned long long)syndrome); + LOG_WARN("hvf: vCPU %u BRK #%u (syndrome=0x%" PRIx64 ") — skipping", + index_, imm, syndrome); uint64_t pc; hv_vcpu_get_reg(vcpu_, HV_REG_PC, &pc); hv_vcpu_set_reg(vcpu_, HV_REG_PC, pc + 4); @@ -294,12 +295,12 @@ VCpuExitAction HvfVCpu::HandleException() { default: s_stats_.ec_other.fetch_add(1, std::memory_order_relaxed); - LOG_ERROR("hvf: vCPU %u unhandled EC=0x%02x (syndrome=0x%llx, " - "VA=0x%llx, IPA=0x%llx)", + LOG_ERROR("hvf: vCPU %u unhandled EC=0x%02x (syndrome=0x%" PRIx64 ", " + "VA=0x%" PRIx64 ", IPA=0x%" PRIx64 ")", index_, ec, - (unsigned long long)syndrome, - (unsigned long long)vcpu_exit_->exception.virtual_address, - (unsigned long long)vcpu_exit_->exception.physical_address); + syndrome, + vcpu_exit_->exception.virtual_address, + vcpu_exit_->exception.physical_address); return VCpuExitAction::kError; } } @@ -421,10 +422,10 @@ VCpuExitAction HvfVCpu::HandleSysReg(uint64_t syndrome) { } } - LOG_WARN("hvf: vCPU %u unhandled sysreg %s S%u_%u_C%u_C%u_%u (Xt=x%u) at PC=0x%llx", + LOG_WARN("hvf: vCPU %u unhandled sysreg %s S%u_%u_C%u_C%u_%u (Xt=x%u) at PC=0x%" PRIx64, index_, is_read ? "MRS" : "MSR", Op0, Op1, CRn, CRm, Op2, rt, - (unsigned long long)pc); + pc); } hv_vcpu_set_reg(vcpu_, HV_REG_PC, pc + 4); @@ -457,9 +458,9 @@ VCpuExitAction HvfVCpu::HandleDataAbort(uint64_t syndrome) { uint64_t far_el2 = vcpu_exit_->exception.virtual_address; (void)far_el2; - LOG_WARN("hvf: vCPU %u DABT without ISV at GPA=0x%llx — " + LOG_WARN("hvf: vCPU %u DABT without ISV at GPA=0x%" PRIx64 " — " "instruction decode not yet fully implemented", - index_, (unsigned long long)gpa); + index_, gpa); hv_vcpu_set_reg(vcpu_, HV_REG_PC, pc + 4); return VCpuExitAction::kContinue; @@ -469,15 +470,15 @@ VCpuExitAction HvfVCpu::HandleDataAbort(uint64_t syndrome) { s_stats_.dabt_write.fetch_add(1, std::memory_order_relaxed); uint64_t value = decode.write_value; if (!addr_space_->HandleMmioWrite(gpa, decode.access_size, value)) { - LOG_WARN("hvf: unhandled MMIO write at GPA=0x%llx size=%u", - (unsigned long long)gpa, decode.access_size); + LOG_WARN("hvf: unhandled MMIO write at GPA=0x%" PRIx64 " size=%u", + gpa, decode.access_size); } } else { s_stats_.dabt_read.fetch_add(1, std::memory_order_relaxed); uint64_t value = 0; if (!addr_space_->HandleMmioRead(gpa, decode.access_size, &value)) { - LOG_WARN("hvf: unhandled MMIO read at GPA=0x%llx size=%u", - (unsigned long long)gpa, decode.access_size); + LOG_WARN("hvf: unhandled MMIO read at GPA=0x%" PRIx64 " size=%u", + gpa, decode.access_size); } if (decode.reg < 31) { hv_vcpu_set_reg(vcpu_, static_cast(HV_REG_X0 + decode.reg), value); @@ -511,44 +512,44 @@ void HvfVCpu::PrintExitStats() { uint64_t canceled = s_stats_.canceled.load(std::memory_order_relaxed); printf("\n========== aarch64 Exit Statistics (per second) ==========\n"); - printf("Total exits: %llu\n", (unsigned long long)total); - printf(" Exception: %llu\n", (unsigned long long)exception); - printf(" VTimer: %llu\n", (unsigned long long)vtimer); - printf(" Canceled: %llu\n", (unsigned long long)canceled); + printf("Total exits: %" PRIu64 "\n", total); + printf(" Exception: %" PRIu64 "\n", exception); + printf(" VTimer: %" PRIu64 "\n", vtimer); + printf(" Canceled: %" PRIu64 "\n", canceled); printf("\n--- Exception Classes ---\n"); - printf(" WFI/WFE: %llu\n", (unsigned long long)s_stats_.ec_wfi_wfe.load(std::memory_order_relaxed)); - printf(" HVC64: %llu\n", (unsigned long long)s_stats_.ec_hvc64.load(std::memory_order_relaxed)); - printf(" SMC64: %llu\n", (unsigned long long)s_stats_.ec_smc64.load(std::memory_order_relaxed)); - printf(" SysReg: %llu\n", (unsigned long long)s_stats_.ec_sysreg.load(std::memory_order_relaxed)); - printf(" DABT Lower: %llu\n", (unsigned long long)s_stats_.ec_dabt_lower.load(std::memory_order_relaxed)); - printf(" DABT Curr: %llu\n", (unsigned long long)s_stats_.ec_dabt_curr.load(std::memory_order_relaxed)); - printf(" BRK: %llu\n", (unsigned long long)s_stats_.ec_brk.load(std::memory_order_relaxed)); - printf(" Other: %llu\n", (unsigned long long)s_stats_.ec_other.load(std::memory_order_relaxed)); + printf(" WFI/WFE: %" PRIu64 "\n", s_stats_.ec_wfi_wfe.load(std::memory_order_relaxed)); + printf(" HVC64: %" PRIu64 "\n", s_stats_.ec_hvc64.load(std::memory_order_relaxed)); + printf(" SMC64: %" PRIu64 "\n", s_stats_.ec_smc64.load(std::memory_order_relaxed)); + printf(" SysReg: %" PRIu64 "\n", s_stats_.ec_sysreg.load(std::memory_order_relaxed)); + printf(" DABT Lower: %" PRIu64 "\n", s_stats_.ec_dabt_lower.load(std::memory_order_relaxed)); + printf(" DABT Curr: %" PRIu64 "\n", s_stats_.ec_dabt_curr.load(std::memory_order_relaxed)); + printf(" BRK: %" PRIu64 "\n", s_stats_.ec_brk.load(std::memory_order_relaxed)); + printf(" Other: %" PRIu64 "\n", s_stats_.ec_other.load(std::memory_order_relaxed)); uint64_t total_dabt = s_stats_.ec_dabt_lower.load(std::memory_order_relaxed) + s_stats_.ec_dabt_curr.load(std::memory_order_relaxed); if (total_dabt > 0) { printf("\n--- Data Abort Details ---\n"); - printf(" Total DABT: %llu\n", (unsigned long long)total_dabt); - printf(" Read: %llu\n", (unsigned long long)s_stats_.dabt_read.load(std::memory_order_relaxed)); - printf(" Write: %llu\n", (unsigned long long)s_stats_.dabt_write.load(std::memory_order_relaxed)); - printf(" ISV Valid: %llu\n", (unsigned long long)s_stats_.dabt_isv_valid.load(std::memory_order_relaxed)); - printf(" ISV Invalid: %llu\n", (unsigned long long)s_stats_.dabt_isv_invalid.load(std::memory_order_relaxed)); + printf(" Total DABT: %" PRIu64 "\n", total_dabt); + printf(" Read: %" PRIu64 "\n", s_stats_.dabt_read.load(std::memory_order_relaxed)); + printf(" Write: %" PRIu64 "\n", s_stats_.dabt_write.load(std::memory_order_relaxed)); + printf(" ISV Valid: %" PRIu64 "\n", s_stats_.dabt_isv_valid.load(std::memory_order_relaxed)); + printf(" ISV Invalid: %" PRIu64 "\n", s_stats_.dabt_isv_invalid.load(std::memory_order_relaxed)); } uint64_t total_hvc = s_stats_.ec_hvc64.load(std::memory_order_relaxed) + s_stats_.ec_smc64.load(std::memory_order_relaxed); if (total_hvc > 0) { printf("\n--- HVC/SMC Details ---\n"); - printf(" Total HVC/SMC: %llu\n", (unsigned long long)total_hvc); - printf(" PSCI Version: %llu\n", (unsigned long long)s_stats_.hvc_version.load(std::memory_order_relaxed)); - printf(" PSCI Features: %llu\n", (unsigned long long)s_stats_.hvc_features.load(std::memory_order_relaxed)); - printf(" PSCI CPU_ON: %llu\n", (unsigned long long)s_stats_.hvc_cpu_on.load(std::memory_order_relaxed)); - printf(" PSCI CPU_OFF: %llu\n", (unsigned long long)s_stats_.hvc_cpu_off.load(std::memory_order_relaxed)); - printf(" PSCI SYS_OFF: %llu\n", (unsigned long long)s_stats_.hvc_system_off.load(std::memory_order_relaxed)); - printf(" PSCI SYS_RST: %llu\n", (unsigned long long)s_stats_.hvc_system_reset.load(std::memory_order_relaxed)); - printf(" Unknown: %llu\n", (unsigned long long)s_stats_.hvc_unknown.load(std::memory_order_relaxed)); + printf(" Total HVC/SMC: %" PRIu64 "\n", total_hvc); + printf(" PSCI Version: %" PRIu64 "\n", s_stats_.hvc_version.load(std::memory_order_relaxed)); + printf(" PSCI Features: %" PRIu64 "\n", s_stats_.hvc_features.load(std::memory_order_relaxed)); + printf(" PSCI CPU_ON: %" PRIu64 "\n", s_stats_.hvc_cpu_on.load(std::memory_order_relaxed)); + printf(" PSCI CPU_OFF: %" PRIu64 "\n", s_stats_.hvc_cpu_off.load(std::memory_order_relaxed)); + printf(" PSCI SYS_OFF: %" PRIu64 "\n", s_stats_.hvc_system_off.load(std::memory_order_relaxed)); + printf(" PSCI SYS_RST: %" PRIu64 "\n", s_stats_.hvc_system_reset.load(std::memory_order_relaxed)); + printf(" Unknown: %" PRIu64 "\n", s_stats_.hvc_unknown.load(std::memory_order_relaxed)); } printf("==========================================================\n\n"); diff --git a/src/platform/macos/hypervisor/aarch64/hvf_vm.cpp b/src/platform/macos/hypervisor/aarch64/hvf_vm.cpp index b269b25..7729218 100644 --- a/src/platform/macos/hypervisor/aarch64/hvf_vm.cpp +++ b/src/platform/macos/hypervisor/aarch64/hvf_vm.cpp @@ -2,6 +2,7 @@ #include "platform/macos/hypervisor/aarch64/hvf_vcpu.h" #include "core/device/irq/gicv3.h" #include "core/vmm/types.h" +#include #include #include @@ -58,9 +59,10 @@ static bool CreateVmWithHwGic(HvfVm* vm, uint32_t cpu_count, actual_redist_base = (actual_redist_base + redist_align - 1) & ~(redist_align - 1); } - LOG_INFO("hvf: GIC layout: dist=0x%llx[0x%zx] redist=0x%llx[region=0x%zx per_cpu=0x%zx * %u]", - (unsigned long long)kGicDistBase, dist_size, - (unsigned long long)actual_redist_base, redist_region_size, + LOG_INFO("hvf: GIC layout: dist=0x%" PRIx64 "[0x%zx] redist=0x%" PRIx64 + "[region=0x%zx per_cpu=0x%zx * %u]", + (uint64_t)kGicDistBase, dist_size, + (uint64_t)actual_redist_base, redist_region_size, redist_per_cpu, cpu_count); hv_return_t ret; @@ -82,7 +84,7 @@ static bool CreateVmWithHwGic(HvfVm* vm, uint32_t cpu_count, if (msi_align_val > 0 && msi_base % msi_align_val != 0) { msi_base = (msi_base + msi_align_val - 1) & ~(msi_align_val - 1); } - LOG_INFO("hvf: GIC MSI at 0x%llx[0x%zx]", (unsigned long long)msi_base, msi_size); + LOG_INFO("hvf: GIC MSI at 0x%" PRIx64 "[0x%zx]", (uint64_t)msi_base, msi_size); hv_gic_config_set_msi_region_base(gic_config, msi_base); hv_gic_config_set_msi_interrupt_range(gic_config, 64, 64); } @@ -163,8 +165,8 @@ bool HvfVm::MapMemory(GPA gpa, void* hva, uint64_t size, bool writable) { hv_return_t ret = hv_vm_map(hva, gpa, size, flags); if (ret != HV_SUCCESS) { - LOG_ERROR("hvf: hv_vm_map(GPA=0x%llx, size=0x%llx) failed: %d", - (unsigned long long)gpa, (unsigned long long)size, (int)ret); + LOG_ERROR("hvf: hv_vm_map(GPA=0x%" PRIx64 ", size=0x%" PRIx64 ") failed: %d", + gpa, size, (int)ret); return false; } return true; @@ -173,8 +175,8 @@ bool HvfVm::MapMemory(GPA gpa, void* hva, uint64_t size, bool writable) { bool HvfVm::UnmapMemory(GPA gpa, uint64_t size) { hv_return_t ret = hv_vm_unmap(gpa, size); if (ret != HV_SUCCESS) { - LOG_ERROR("hvf: hv_vm_unmap(GPA=0x%llx) failed: %d", - (unsigned long long)gpa, (int)ret); + LOG_ERROR("hvf: hv_vm_unmap(GPA=0x%" PRIx64 ") failed: %d", + gpa, (int)ret); return false; } return true; diff --git a/src/platform/macos/hypervisor/x86_64/hvf_vcpu.cpp b/src/platform/macos/hypervisor/x86_64/hvf_vcpu.cpp index 00b8e48..b712e7c 100644 --- a/src/platform/macos/hypervisor/x86_64/hvf_vcpu.cpp +++ b/src/platform/macos/hypervisor/x86_64/hvf_vcpu.cpp @@ -2,6 +2,7 @@ #include "core/arch/x86_64/boot.h" #include "core/device/irq/local_apic.h" #include "core/vmm/types.h" +#include #include #include @@ -280,9 +281,9 @@ bool HvfVCpu::SetupBootRegisters(uint8_t* ram) { hv_vcpu_write_register(vcpuid_, HV_X86_RBP, 0); hv_vcpu_write_register(vcpuid_, HV_X86_RSP, 0); - LOG_INFO("hvf: vCPU %u boot: 32-bit protected mode, RIP=0x%llx RSI=0x%llx", - index_, (unsigned long long)Layout::kKernelBase, - (unsigned long long)Layout::kBootParams); + LOG_INFO("hvf: vCPU %u boot: 32-bit protected mode, RIP=0x%" PRIx64 " RSI=0x%" PRIx64, + index_, (uint64_t)Layout::kKernelBase, + (uint64_t)Layout::kBootParams); return true; } @@ -497,26 +498,26 @@ void HvfVCpu::PrintExitStats() { uint32_t top_msr = s.wrmsr_top_msr.exchange(0, std::memory_order_relaxed); double rate = total / elapsed_s; - LOG_INFO("=== VM exit stats (%.1fs, %.0f/s, total %llu) ===", elapsed_s, rate, total); - LOG_INFO(" IRQ:%-7llu IRQ_WND:%-7llu HLT:%-7llu IO:%-7llu EPT:%-7llu CPUID:%-5llu", + LOG_INFO("=== VM exit stats (%.1fs, %.0f/s, total %" PRIu64 ") ===", elapsed_s, rate, total); + LOG_INFO(" IRQ:%-7" PRIu64 " IRQ_WND:%-7" PRIu64 " HLT:%-7" PRIu64 " IO:%-7" PRIu64 " EPT:%-7" PRIu64 " CPUID:%-5" PRIu64, irq, irq_wnd, hlt, io, ept, cpuid); - LOG_INFO(" RDMSR:%-5llu WRMSR:%-7llu CR:%-5llu XSETBV:%-3llu OTHER:%-3llu", + LOG_INFO(" RDMSR:%-5" PRIu64 " WRMSR:%-7" PRIu64 " CR:%-5" PRIu64 " XSETBV:%-3" PRIu64 " OTHER:%-3" PRIu64, rdmsr, wrmsr, cr, xsetbv, other); if (io > 0) - LOG_INFO(" IO: UART:%-5llu PIT:%-5llu ACPI:%-5llu PCI:%-5llu PIC:%-4llu RTC:%-3llu SINK:%-3llu OTHER:%-3llu", + LOG_INFO(" IO: UART:%-5" PRIu64 " PIT:%-5" PRIu64 " ACPI:%-5" PRIu64 " PCI:%-5" PRIu64 " PIC:%-4" PRIu64 " RTC:%-3" PRIu64 " SINK:%-3" PRIu64 " OTHER:%-3" PRIu64, io_uart, io_pit, io_acpi, io_pci, io_pic, io_rtc, io_sink, io_other); if (ept > 0) - LOG_INFO(" EPT: EOI:%-5llu TPR:%-5llu ICR:%-5llu TIMER:%-5llu LAPIC_X:%-4llu IOAPIC:%-4llu OTHER:%-4llu", + LOG_INFO(" EPT: EOI:%-5" PRIu64 " TPR:%-5" PRIu64 " ICR:%-5" PRIu64 " TIMER:%-5" PRIu64 " LAPIC_X:%-4" PRIu64 " IOAPIC:%-4" PRIu64 " OTHER:%-4" PRIu64, ept_lapic_eoi, ept_lapic_tpr, ept_lapic_icr, ept_lapic_timer, ept_lapic_other, ept_ioapic, ept_other); if (cr > 0) - LOG_INFO(" CR: CR0:%-5llu CR3:%-5llu CR4:%-5llu CR8:%-5llu OTHER:%-3llu", + LOG_INFO(" CR: CR0:%-5" PRIu64 " CR3:%-5" PRIu64 " CR4:%-5" PRIu64 " CR8:%-5" PRIu64 " OTHER:%-3" PRIu64, cr0, cr3, cr4, cr8, cr_other); if (wrmsr > 0) { - LOG_INFO(" WRMSR: KVMCLK:%-7llu WALL:%-3llu POLL:%-3llu EFER:%-3llu APIC:%-3llu OTHER:%-5llu", + LOG_INFO(" WRMSR: KVMCLK:%-7" PRIu64 " WALL:%-3" PRIu64 " POLL:%-3" PRIu64 " EFER:%-3" PRIu64 " APIC:%-3" PRIu64 " OTHER:%-5" PRIu64, wrmsr_kvmclock, wrmsr_wallclock, wrmsr_poll, wrmsr_efer, wrmsr_apicbase, wrmsr_other); if (wrmsr_other > 0) - LOG_INFO(" WRMSR top: MSR 0x%X x%llu", top_msr, wrmsr_top_count); + LOG_INFO(" WRMSR top: MSR 0x%X x%" PRIu64, top_msr, wrmsr_top_count); } } @@ -609,25 +610,25 @@ VCpuExitAction HvfVCpu::RunOnce() { hv_vmx_vcpu_read_vmcs(vcpuid_, VMCS_GUEST_TR, &tr); hv_vmx_vcpu_read_vmcs(vcpuid_, VMCS_GUEST_TR_AR, &tr_ar); hv_vmx_vcpu_read_vmcs(vcpuid_, VMCS_GUEST_TR_BASE, &tr_base); - LOG_ERROR("hvf: vCPU %u triple fault — RIP=0x%llx RSP=0x%llx RFLAGS=0x%llx " - "CR0=0x%llx CR3=0x%llx CR4=0x%llx CS=0x%llx(base=0x%llx lim=0x%llx ar=0x%llx)", - index_, (unsigned long long)rip, (unsigned long long)rsp, - (unsigned long long)rflags, (unsigned long long)cr0, - (unsigned long long)cr3, (unsigned long long)cr4, - (unsigned long long)cs, (unsigned long long)cs_base, - (unsigned long long)cs_limit, (unsigned long long)cs_ar); - LOG_ERROR("hvf: vCPU %u IDTR=0x%llx:0x%llx GDTR=0x%llx:0x%llx EFER=0x%llx entry_ctrl=0x%llx " - "SS=0x%llx(ar=0x%llx) TR=0x%llx(ar=0x%llx base=0x%llx)", - index_, (unsigned long long)idtr_base, (unsigned long long)idtr_limit, - (unsigned long long)gdtr_base, (unsigned long long)gdtr_limit, - (unsigned long long)efer, (unsigned long long)entry_ctrl, - (unsigned long long)ss, (unsigned long long)ss_ar, - (unsigned long long)tr, (unsigned long long)tr_ar, - (unsigned long long)tr_base); + LOG_ERROR("hvf: vCPU %u triple fault — RIP=0x%" PRIx64 " RSP=0x%" PRIx64 " RFLAGS=0x%" PRIx64 " " + "CR0=0x%" PRIx64 " CR3=0x%" PRIx64 " CR4=0x%" PRIx64 " CS=0x%" PRIx64 "(base=0x%" PRIx64 " lim=0x%" PRIx64 " ar=0x%" PRIx64 ")", + index_, rip, rsp, + rflags, cr0, + cr3, cr4, + cs, cs_base, + cs_limit, cs_ar); + LOG_ERROR("hvf: vCPU %u IDTR=0x%" PRIx64 ":0x%" PRIx64 " GDTR=0x%" PRIx64 ":0x%" PRIx64 " EFER=0x%" PRIx64 " entry_ctrl=0x%" PRIx64 " " + "SS=0x%" PRIx64 "(ar=0x%" PRIx64 ") TR=0x%" PRIx64 "(ar=0x%" PRIx64 " base=0x%" PRIx64 ")", + index_, idtr_base, idtr_limit, + gdtr_base, gdtr_limit, + efer, entry_ctrl, + ss, ss_ar, + tr, tr_ar, + tr_base); // Dump last EPT violation details - LOG_ERROR("hvf: vCPU %u last EPT GPA=0x%llx decode_fail_count=%u", - index_, (unsigned long long)last_ept_gpa_, last_decode_fail_count_); + LOG_ERROR("hvf: vCPU %u last EPT GPA=0x%" PRIx64 " decode_fail_count=%u", + index_, last_ept_gpa_, last_decode_fail_count_); return VCpuExitAction::kError; } @@ -670,16 +671,16 @@ VCpuExitAction HvfVCpu::RunOnce() { hv_vmx_vcpu_read_vmcs(vcpuid_, VMCS_GUEST_TR_AR, &tr_ar); hv_vmx_vcpu_read_vmcs(vcpuid_, VMCS_GUEST_ACTIVITY_STATE, &act_state); LOG_ERROR("hvf: vCPU %u VM-entry failure (invalid guest state): " - "RIP=0x%llx RFLAGS=0x%llx CR0=0x%llx CR4=0x%llx EFER=0x%llx " - "entry_ctrl=0x%llx proc2=0x%llx CS=%04llx base=0x%llx limit=0x%llx ar=0x%llx " - "SS_ar=0x%llx TR_ar=0x%llx activity=%llu", - index_, (unsigned long long)rip, (unsigned long long)rflags, - (unsigned long long)cr0, (unsigned long long)cr4, - (unsigned long long)efer, (unsigned long long)entry_ctrl, - (unsigned long long)proc2, (unsigned long long)cs, - (unsigned long long)cs_base, (unsigned long long)cs_limit, - (unsigned long long)cs_ar, (unsigned long long)ss_ar, - (unsigned long long)tr_ar, (unsigned long long)act_state); + "RIP=0x%" PRIx64 " RFLAGS=0x%" PRIx64 " CR0=0x%" PRIx64 " CR4=0x%" PRIx64 " EFER=0x%" PRIx64 " " + "entry_ctrl=0x%" PRIx64 " proc2=0x%" PRIx64 " CS=%04" PRIx64 " base=0x%" PRIx64 " limit=0x%" PRIx64 " ar=0x%" PRIx64 " " + "SS_ar=0x%" PRIx64 " TR_ar=0x%" PRIx64 " activity=%" PRIu64, + index_, rip, rflags, + cr0, cr4, + efer, entry_ctrl, + proc2, cs, + cs_base, cs_limit, + cs_ar, ss_ar, + tr_ar, act_state); return VCpuExitAction::kError; } @@ -688,8 +689,8 @@ VCpuExitAction HvfVCpu::RunOnce() { s_stats_.other.fetch_add(1, std::memory_order_relaxed); uint64_t rip = 0; hv_vcpu_read_register(vcpuid_, HV_X86_RIP, &rip); - LOG_WARN("hvf: vCPU %u unhandled exit reason %llu at RIP=0x%llx", - index_, (unsigned long long)exit_reason, (unsigned long long)rip); + LOG_WARN("hvf: vCPU %u unhandled exit reason %" PRIu64 " at RIP=0x%" PRIx64, + index_, exit_reason, rip); return VCpuExitAction::kError; } } @@ -1149,13 +1150,13 @@ VCpuExitAction HvfVCpu::HandleEptViolation(uint64_t exit_qual) { if (rip_translated) { uint8_t* code = GpaToHost(rip_gpa, 8); if (code) { - LOG_WARN("hvf: vCPU %u MMIO decode fail GPA=0x%llx RIP=0x%llx rip_gpa=0x%llx " - "bytes=%02x %02x %02x %02x %02x %02x %02x %02x is_write=%d insn_len=%llu", - index_, (unsigned long long)gpa, (unsigned long long)rip, - (unsigned long long)rip_gpa, + LOG_WARN("hvf: vCPU %u MMIO decode fail GPA=0x%" PRIx64 " RIP=0x%" PRIx64 " rip_gpa=0x%" PRIx64 " " + "bytes=%02x %02x %02x %02x %02x %02x %02x %02x is_write=%d insn_len=%" PRIu64, + index_, gpa, rip, + rip_gpa, code[0], code[1], code[2], code[3], code[4], code[5], code[6], code[7], - (int)is_write, (unsigned long long)insn_len); + (int)is_write, insn_len); } } uint8_t skip = (insn_len > 0 && insn_len <= 15) ? (uint8_t)insn_len : 0; @@ -1493,8 +1494,8 @@ VCpuExitAction HvfVCpu::HandleMsr(bool is_write) { __sync_synchronize(); wc->version = 2u; - LOG_INFO("kvmclock: wall_clock GPA=0x%llx sec=%u nsec=%u", - (unsigned long long)gpa, wc->sec, wc->nsec); + LOG_INFO("kvmclock: wall_clock GPA=0x%" PRIx64 " sec=%u nsec=%u", + gpa, wc->sec, wc->nsec); } } else if (msr == MSR_KVM_SYSTEM_TIME_NEW) { bool enable = val & 1; @@ -1503,8 +1504,8 @@ VCpuExitAction HvfVCpu::HandleMsr(bool is_write) { kvmclock_system_time_gpa_ = gpa; kvmclock_enabled_ = true; UpdateKvmclock(gpa); - LOG_INFO("kvmclock: system_time enabled GPA=0x%llx tsc_freq=%llu", - (unsigned long long)gpa, (unsigned long long)tsc_freq_); + LOG_INFO("kvmclock: system_time enabled GPA=0x%" PRIx64 " tsc_freq=%" PRIu64, + gpa, tsc_freq_); } else { kvmclock_enabled_ = false; LOG_INFO("kvmclock: system_time disabled"); @@ -1522,8 +1523,8 @@ VCpuExitAction HvfVCpu::HandleMsr(bool is_write) { } else { hv_return_t ret = hv_vcpu_write_msr(vcpuid_, msr, val); if (ret != HV_SUCCESS) { - LOG_DEBUG("hvf: MSR write 0x%X = 0x%llx unsupported", - msr, (unsigned long long)val); + LOG_DEBUG("hvf: MSR write 0x%X = 0x%" PRIx64 " unsupported", + msr, val); } } } else { diff --git a/src/platform/macos/hypervisor/x86_64/hvf_vm.cpp b/src/platform/macos/hypervisor/x86_64/hvf_vm.cpp index 1b7a53f..fecae47 100644 --- a/src/platform/macos/hypervisor/x86_64/hvf_vm.cpp +++ b/src/platform/macos/hypervisor/x86_64/hvf_vm.cpp @@ -1,6 +1,7 @@ #include "platform/macos/hypervisor/x86_64/hvf_vm.h" #include "platform/macos/hypervisor/x86_64/hvf_vcpu.h" #include "core/vmm/types.h" +#include #include @@ -37,19 +38,18 @@ bool HvfVm::MapMemory(GPA gpa, void* hva, uint64_t size, bool writable) { hv_return_t ret = hv_vm_map(hva, gpa, size, flags); if (ret != HV_SUCCESS) { - LOG_ERROR("hvf: hv_vm_map(GPA=0x%llx, size=0x%llx, flags=0x%llx) failed: %d", - (unsigned long long)gpa, (unsigned long long)size, - (unsigned long long)flags, (int)ret); + LOG_ERROR("hvf: hv_vm_map(GPA=0x%" PRIx64 ", size=0x%" PRIx64 ", flags=0x%" PRIx64 ") failed: %d", + gpa, size, (uint64_t)flags, (int)ret); return false; } - LOG_INFO("hvf: mapped GPA=0x%llx size=0x%llx HVA=%p flags=0x%llx", - (unsigned long long)gpa, (unsigned long long)size, hva, (unsigned long long)flags); + LOG_INFO("hvf: mapped GPA=0x%" PRIx64 " size=0x%" PRIx64 " HVA=%p flags=0x%" PRIx64, + gpa, size, hva, (uint64_t)flags); // Verify the mapping by re-protecting with full RWX ret = hv_vm_protect(gpa, size, flags); if (ret != HV_SUCCESS) { - LOG_WARN("hvf: hv_vm_protect(GPA=0x%llx) failed: %d", - (unsigned long long)gpa, (int)ret); + LOG_WARN("hvf: hv_vm_protect(GPA=0x%" PRIx64 ") failed: %d", + gpa, (int)ret); } return true; @@ -58,8 +58,8 @@ bool HvfVm::MapMemory(GPA gpa, void* hva, uint64_t size, bool writable) { bool HvfVm::UnmapMemory(GPA gpa, uint64_t size) { hv_return_t ret = hv_vm_unmap(gpa, size); if (ret != HV_SUCCESS) { - LOG_ERROR("hvf: hv_vm_unmap(GPA=0x%llx) failed: %d", - (unsigned long long)gpa, (int)ret); + LOG_ERROR("hvf: hv_vm_unmap(GPA=0x%" PRIx64 ") failed: %d", + gpa, (int)ret); return false; } return true; diff --git a/src/platform/macos/vm_platform_macos.cpp b/src/platform/macos/vm_platform_macos.cpp index 1f79091..2acab11 100644 --- a/src/platform/macos/vm_platform_macos.cpp +++ b/src/platform/macos/vm_platform_macos.cpp @@ -1,5 +1,5 @@ #include "core/vmm/vm_platform.h" -#include "platform/macos/console/posix_console_port.h" +#include "platform/posix/console/posix_console_port.h" #include "platform/macos/hypervisor/hvf_platform.h" #ifdef __aarch64__ diff --git a/src/platform/macos/console/posix_console_port.cpp b/src/platform/posix/console/posix_console_port.cpp similarity index 65% rename from src/platform/macos/console/posix_console_port.cpp rename to src/platform/posix/console/posix_console_port.cpp index 8256fa8..d1cc4ed 100644 --- a/src/platform/macos/console/posix_console_port.cpp +++ b/src/platform/posix/console/posix_console_port.cpp @@ -1,7 +1,8 @@ -#include "platform/macos/console/posix_console_port.h" +#include "platform/posix/console/posix_console_port.h" #include "core/vmm/types.h" #include #include +#include #include PosixConsolePort::PosixConsolePort() { @@ -17,7 +18,6 @@ PosixConsolePort::PosixConsolePort() { raw_mode_ = true; } - // Enable UTF-8 output (macOS terminals are UTF-8 by default) setvbuf(stdout, nullptr, _IONBF, 0); } @@ -30,7 +30,20 @@ PosixConsolePort::~PosixConsolePort() { void PosixConsolePort::Write(const uint8_t* data, size_t size) { if (!data || size == 0) return; std::lock_guard lock(GetStdoutMutex()); - ::write(STDOUT_FILENO, data, size); + // write(2) may return a short count on a pipe or be interrupted by a + // signal; loop until all bytes are out (or a hard error is hit) so we + // don't silently drop guest console output. + size_t off = 0; + while (off < size) { + ssize_t n = ::write(STDOUT_FILENO, data + off, size - off); + if (n > 0) { + off += static_cast(n); + } else if (n < 0 && errno == EINTR) { + continue; + } else { + break; + } + } } size_t PosixConsolePort::Read(uint8_t* out, size_t size) { @@ -38,7 +51,7 @@ size_t PosixConsolePort::Read(uint8_t* out, size_t size) { pfd.fd = STDIN_FILENO; pfd.events = POLLIN; - int ret = poll(&pfd, 1, 50); // 50ms timeout + int ret = poll(&pfd, 1, 50); if (ret <= 0) return 0; ssize_t n = ::read(STDIN_FILENO, out, size); diff --git a/src/platform/macos/console/posix_console_port.h b/src/platform/posix/console/posix_console_port.h similarity index 100% rename from src/platform/macos/console/posix_console_port.h rename to src/platform/posix/console/posix_console_port.h diff --git a/src/platform/windows/hypervisor/whvp_vcpu.cpp b/src/platform/windows/hypervisor/whvp_vcpu.cpp index 1c9c91b..92626a3 100644 --- a/src/platform/windows/hypervisor/whvp_vcpu.cpp +++ b/src/platform/windows/hypervisor/whvp_vcpu.cpp @@ -379,7 +379,7 @@ VCpuExitAction WhvpVCpu::RunOnce() { case WHvRunVpExitReasonUnsupportedFeature: if (stats) s_stats_.unsupported.fetch_add(1, std::memory_order_relaxed); - LOG_WARN("Unsupported feature at RIP=0x%llX (feature=%u)", + LOG_WARN("Unsupported feature at RIP=0x%" PRIX64 " (feature=%u)", exit_ctx.VpContext.Rip, exit_ctx.UnsupportedFeature.FeatureCode); return VCpuExitAction::kContinue; @@ -390,13 +390,13 @@ VCpuExitAction WhvpVCpu::RunOnce() { case WHvRunVpExitReasonUnrecoverableException: if (stats) s_stats_.exception.fetch_add(1, std::memory_order_relaxed); - LOG_ERROR("Unrecoverable guest exception at RIP=0x%llX", + LOG_ERROR("Unrecoverable guest exception at RIP=0x%" PRIX64, exit_ctx.VpContext.Rip); return VCpuExitAction::kError; case WHvRunVpExitReasonInvalidVpRegisterValue: if (stats) s_stats_.invalid_reg.fetch_add(1, std::memory_order_relaxed); - LOG_ERROR("Invalid VP register value at RIP=0x%llX", + LOG_ERROR("Invalid VP register value at RIP=0x%" PRIX64, exit_ctx.VpContext.Rip); return VCpuExitAction::kError; @@ -480,7 +480,7 @@ VCpuExitAction WhvpVCpu::RunOnce() { } } } - LOG_DEBUG("MSR write: 0x%X = 0x%llX", msr.MsrNumber, + LOG_DEBUG("MSR write: 0x%X = 0x%" PRIX64, msr.MsrNumber, (msr.Rdx << 32) | (msr.Rax & 0xFFFFFFFF)); SetRegisters(&rip_name, &rip_val, 1); } @@ -489,7 +489,7 @@ VCpuExitAction WhvpVCpu::RunOnce() { default: if (stats) s_stats_.other.fetch_add(1, std::memory_order_relaxed); - LOG_WARN("Unhandled VM exit reason: 0x%X at RIP=0x%llX", + LOG_WARN("Unhandled VM exit reason: 0x%X at RIP=0x%" PRIX64, exit_ctx.ExitReason, exit_ctx.VpContext.Rip); return VCpuExitAction::kError; } @@ -523,7 +523,7 @@ VCpuExitAction WhvpVCpu::HandleMmio( emulator_, this, &vp_ctx, &mem, &status); if (FAILED(hr) || !status.EmulationSuccessful) { - LOG_WARN("MMIO emulation failed: gpa=0x%llX hr=0x%08lX success=%d", + LOG_WARN("MMIO emulation failed: gpa=0x%" PRIX64 " hr=0x%08lX success=%d", mem.Gpa, hr, status.EmulationSuccessful); WHV_REGISTER_NAME name = WHvX64RegisterRip; WHV_REGISTER_VALUE val{}; diff --git a/src/platform/windows/hypervisor/whvp_vm.cpp b/src/platform/windows/hypervisor/whvp_vm.cpp index b8f672f..fe835d6 100644 --- a/src/platform/windows/hypervisor/whvp_vm.cpp +++ b/src/platform/windows/hypervisor/whvp_vm.cpp @@ -1,5 +1,6 @@ #include "platform/windows/hypervisor/whvp_vm.h" #include "platform/windows/hypervisor/whvp_vcpu.h" +#include #include namespace whvp { @@ -44,12 +45,12 @@ std::unique_ptr WhvpVm::Create(uint32_t cpu_count) { hr = WHvGetCapability(WHvCapabilityCodeProcessorClockFrequency, &proc_freq, sizeof(proc_freq), nullptr); if (SUCCEEDED(hr) && proc_freq) { - LOG_INFO("WHVP ProcessorClockFrequency: %llu Hz", proc_freq); + LOG_INFO("WHVP ProcessorClockFrequency: %" PRIu64 " Hz", proc_freq); } hr = WHvGetCapability(WHvCapabilityCodeInterruptClockFrequency, &intr_freq, sizeof(intr_freq), nullptr); if (SUCCEEDED(hr) && intr_freq) { - LOG_INFO("WHVP InterruptClockFrequency: %llu Hz", intr_freq); + LOG_INFO("WHVP InterruptClockFrequency: %" PRIu64 " Hz", intr_freq); } // Build CPUID override list: 0x15 (TSC freq) + 0x01 (features). @@ -69,7 +70,7 @@ std::unique_ptr WhvpVm::Create(uint32_t cpu_count) { // Intel CPU with native CPUID 0x15 support. if (crystal == 0) crystal = 38400000; // 38.4 MHz for modern Intel uint64_t tsc_freq = static_cast(crystal) * numer / denom; - LOG_INFO("CPUID 0x15 (native): crystal=%u Hz, TSC=%llu Hz", crystal, tsc_freq); + LOG_INFO("CPUID 0x15 (native): crystal=%u Hz, TSC=%" PRIu64 " Hz", crystal, tsc_freq); } else { // AMD or older CPU without CPUID 0x15 - synthesize it from QPC measurement. LARGE_INTEGER qpf, qpc_start, qpc_end; @@ -89,7 +90,7 @@ std::unique_ptr WhvpVm::Create(uint32_t cpu_count) { crystal = 1000000; numer = static_cast(tsc_freq / 1000000); denom = 1; - LOG_INFO("CPUID 0x15 (synthesized): crystal=%u Hz, TSC=%llu Hz", crystal, tsc_freq); + LOG_INFO("CPUID 0x15 (synthesized): crystal=%u Hz, TSC=%" PRIu64 " Hz", crystal, tsc_freq); } auto& o = cpuid_overrides[num_overrides++]; @@ -167,7 +168,7 @@ bool WhvpVm::MapMemory(GPA gpa, void* hva, uint64_t size, bool writable) { HRESULT hr = WHvMapGpaRange(partition_, hva, gpa, size, flags); if (FAILED(hr)) { - LOG_ERROR("WHvMapGpaRange(gpa=0x%llX, size=0x%llX) failed: 0x%08lX", + LOG_ERROR("WHvMapGpaRange(gpa=0x%" PRIX64 ", size=0x%" PRIX64 ") failed: 0x%08lX", gpa, size, hr); return false; } diff --git a/src/runtime/CMakeLists.txt b/src/runtime/CMakeLists.txt index a2103da..507afff 100644 --- a/src/runtime/CMakeLists.txt +++ b/src/runtime/CMakeLists.txt @@ -18,12 +18,27 @@ target_include_directories(tenbox-vm-runtime ${CMAKE_BINARY_DIR} ) -target_link_libraries(tenbox-vm-runtime - PRIVATE - tenbox_platform - tenbox_core - tenbox_ipc -) +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + # GNU ld resolves static archives left-to-right with no rescan. Our core + # library references VmPlatform symbols defined in tenbox_platform, which + # in turn depends on tenbox_core, so wrap the pair in --start-group to + # allow cross-archive symbol resolution. + target_link_libraries(tenbox-vm-runtime + PRIVATE + -Wl,--start-group + tenbox_platform + tenbox_core + -Wl,--end-group + tenbox_ipc + ) +else() + target_link_libraries(tenbox-vm-runtime + PRIVATE + tenbox_platform + tenbox_core + tenbox_ipc + ) +endif() if(WIN32) target_link_libraries(tenbox-vm-runtime PRIVATE ws2_32) @@ -40,4 +55,6 @@ elseif(APPLE) "$" COMMENT "Signing tenbox-vm-runtime with hypervisor entitlement" ) +elseif(UNIX) + target_link_libraries(tenbox-vm-runtime PRIVATE pthread) endif() diff --git a/tests/test_qcow2.cpp b/tests/test_qcow2.cpp index ff7e24d..4b967a1 100644 --- a/tests/test_qcow2.cpp +++ b/tests/test_qcow2.cpp @@ -9,6 +9,7 @@ #include "core/disk/qcow2.h" #include +#include #include #include #include @@ -169,7 +170,8 @@ static uint64_t ReadBe64(const std::string& path, long offset) { if (!f) return 0; uint64_t val = 0; fseek(f, offset, SEEK_SET); - fread(&val, sizeof(val), 1, f); + size_t n = fread(&val, sizeof(val), 1, f); + (void)n; fclose(f); return be64(val); } @@ -179,7 +181,8 @@ static uint32_t ReadBe32(const std::string& path, long offset) { if (!f) return 0; uint32_t val = 0; fseek(f, offset, SEEK_SET); - fread(&val, sizeof(val), 1, f); + size_t n = fread(&val, sizeof(val), 1, f); + (void)n; fclose(f); return be32(val); } @@ -252,8 +255,8 @@ static bool TestGrowRefcountTable() { // Record initial refcount table offset & cluster count uint64_t orig_rft_off = ReadBe64(path, 48); uint32_t orig_rft_clusters = ReadBe32(path, 56); - fprintf(stdout, " initial rft_off=0x%llX rft_clusters=%u\n", - (unsigned long long)orig_rft_off, orig_rft_clusters); + fprintf(stdout, " initial rft_off=0x%" PRIX64 " rft_clusters=%u\n", + orig_rft_off, orig_rft_clusters); // Phase 1: write enough data to trigger GrowRefcountTable // With 512-byte clusters, 16384 clusters = 8 MB. @@ -280,8 +283,8 @@ static bool TestGrowRefcountTable() { // Verify refcount table actually grew uint64_t new_rft_off = ReadBe64(path, 48); uint32_t new_rft_clusters = ReadBe32(path, 56); - fprintf(stdout, " after growth rft_off=0x%llX rft_clusters=%u\n", - (unsigned long long)new_rft_off, new_rft_clusters); + fprintf(stdout, " after growth rft_off=0x%" PRIX64 " rft_clusters=%u\n", + new_rft_off, new_rft_clusters); TEST_ASSERT(new_rft_off != orig_rft_off || new_rft_clusters > orig_rft_clusters, "refcount table did not grow");