// SPDX-License-Identifier: GPL-2.0 /* * A test for GUEST_PRINTF * * Copyright 2022, Google, Inc. and/or its affiliates. */ #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/ioctl.h> #include "test_util.h" #include "kvm_util.h" #include "processor.h" struct guest_vals { uint64_t a; uint64_t b; uint64_t type; }; static struct guest_vals vals; /* GUEST_PRINTF()/GUEST_ASSERT_FMT() does not support float or double. */ #define TYPE_LIST \ TYPE(test_type_i64, I64, "%ld", int64_t) \ TYPE(test_type_u64, U64u, "%lu", uint64_t) \ TYPE(test_type_x64, U64x, "0x%lx", uint64_t) \ TYPE(test_type_X64, U64X, "0x%lX", uint64_t) \ TYPE(test_type_u32, U32u, "%u", uint32_t) \ TYPE(test_type_x32, U32x, "0x%x", uint32_t) \ TYPE(test_type_X32, U32X, "0x%X", uint32_t) \ TYPE(test_type_int, INT, "%d", int) \ TYPE(test_type_char, CHAR, "%c", char) \ TYPE(test_type_str, STR, "'%s'", const char *) \ TYPE(test_type_ptr, PTR, "%p", uintptr_t) enum args_type { #define TYPE(fn, ext, fmt_t, T) TYPE_##ext, TYPE_LIST #undef TYPE }; static void run_test(struct kvm_vcpu *vcpu, const char *expected_printf, const char *expected_assert); #define BUILD_TYPE_STRINGS_AND_HELPER(fn, ext, fmt_t, T) \ const char *PRINTF_FMT_##ext = "Got params a = " fmt_t " and b = " fmt_t; \ const char *ASSERT_FMT_##ext = "Expected " fmt_t ", got " fmt_t " instead"; \ static void fn(struct kvm_vcpu *vcpu, T a, T b) \ { \ char expected_printf[UCALL_BUFFER_LEN]; \ char expected_assert[UCALL_BUFFER_LEN]; \ \ snprintf(expected_printf, UCALL_BUFFER_LEN, PRINTF_FMT_##ext, a, b); \ snprintf(expected_assert, UCALL_BUFFER_LEN, ASSERT_FMT_##ext, a, b); \ vals = (struct guest_vals){ (uint64_t)a, (uint64_t)b, TYPE_##ext }; \ sync_global_to_guest(vcpu->vm, vals); \ run_test(vcpu, expected_printf, expected_assert); \ } #define TYPE(fn, ext, fmt_t, T) \ BUILD_TYPE_STRINGS_AND_HELPER(fn, ext, fmt_t, T) TYPE_LIST #undef TYPE static void guest_code(void) { while (1) { switch (vals.type) { #define TYPE(fn, ext, fmt_t, T) \ case TYPE_##ext: \ GUEST_PRINTF(PRINTF_FMT_##ext, vals.a, vals.b); \ __GUEST_ASSERT(vals.a == vals.b, \ ASSERT_FMT_##ext, vals.a, vals.b); \ break; TYPE_LIST #undef TYPE default: GUEST_SYNC(vals.type); } GUEST_DONE(); } } /* * Unfortunately this gets a little messy because 'assert_msg' doesn't * just contains the matching string, it also contains additional assert * info. Fortunately the part that matches should be at the very end of * 'assert_msg'. */ static void ucall_abort(const char *assert_msg, const char *expected_assert_msg) { int len_str = strlen(assert_msg); int len_substr = strlen(expected_assert_msg); int offset = len_str - len_substr; TEST_ASSERT(len_substr <= len_str, "Expected '%s' to be a substring of '%s'\n", assert_msg, expected_assert_msg); TEST_ASSERT(strcmp(&assert_msg[offset], expected_assert_msg) == 0, "Unexpected mismatch. Expected: '%s', got: '%s'", expected_assert_msg, &assert_msg[offset]); } static void run_test(struct kvm_vcpu *vcpu, const char *expected_printf, const char *expected_assert) { struct kvm_run *run = vcpu->run; struct ucall uc; while (1) { vcpu_run(vcpu); TEST_ASSERT(run->exit_reason == UCALL_EXIT_REASON, "Unexpected exit reason: %u (%s),\n", run->exit_reason, exit_reason_str(run->exit_reason)); switch (get_ucall(vcpu, &uc)) { case UCALL_SYNC: TEST_FAIL("Unknown 'args_type' = %lu", uc.args[1]); break; case UCALL_PRINTF: TEST_ASSERT(strcmp(uc.buffer, expected_printf) == 0, "Unexpected mismatch. Expected: '%s', got: '%s'", expected_printf, uc.buffer); break; case UCALL_ABORT: ucall_abort(uc.buffer, expected_assert); break; case UCALL_DONE: return; default: TEST_FAIL("Unknown ucall %lu", uc.cmd); } } } static void guest_code_limits(void) { char test_str[UCALL_BUFFER_LEN + 10]; memset(test_str, 'a', sizeof(test_str)); test_str[sizeof(test_str) - 1] = 0; GUEST_PRINTF("%s", test_str); } static void test_limits(void) { struct kvm_vcpu *vcpu; struct kvm_run *run; struct kvm_vm *vm; struct ucall uc; vm = vm_create_with_one_vcpu(&vcpu, guest_code_limits); run = vcpu->run; vcpu_run(vcpu); TEST_ASSERT(run->exit_reason == UCALL_EXIT_REASON, "Unexpected exit reason: %u (%s),\n", run->exit_reason, exit_reason_str(run->exit_reason)); TEST_ASSERT(get_ucall(vcpu, &uc) == UCALL_ABORT, "Unexpected ucall command: %lu, Expected: %u (UCALL_ABORT)\n", uc.cmd, UCALL_ABORT); kvm_vm_free(vm); } int main(int argc, char *argv[]) { struct kvm_vcpu *vcpu; struct kvm_vm *vm; vm = vm_create_with_one_vcpu(&vcpu, guest_code); test_type_i64(vcpu, -1, -1); test_type_i64(vcpu, -1, 1); test_type_i64(vcpu, 0x1234567890abcdef, 0x1234567890abcdef); test_type_i64(vcpu, 0x1234567890abcdef, 0x1234567890abcdee); test_type_u64(vcpu, 0x1234567890abcdef, 0x1234567890abcdef); test_type_u64(vcpu, 0x1234567890abcdef, 0x1234567890abcdee); test_type_x64(vcpu, 0x1234567890abcdef, 0x1234567890abcdef); test_type_x64(vcpu, 0x1234567890abcdef, 0x1234567890abcdee); test_type_X64(vcpu, 0x1234567890abcdef, 0x1234567890abcdef); test_type_X64(vcpu, 0x1234567890abcdef, 0x1234567890abcdee); test_type_u32(vcpu, 0x90abcdef, 0x90abcdef); test_type_u32(vcpu, 0x90abcdef, 0x90abcdee); test_type_x32(vcpu, 0x90abcdef, 0x90abcdef); test_type_x32(vcpu, 0x90abcdef, 0x90abcdee); test_type_X32(vcpu, 0x90abcdef, 0x90abcdef); test_type_X32(vcpu, 0x90abcdef, 0x90abcdee); test_type_int(vcpu, -1, -1); test_type_int(vcpu, -1, 1); test_type_int(vcpu, 1, 1); test_type_char(vcpu, 'a', 'a'); test_type_char(vcpu, 'a', 'A'); test_type_char(vcpu, 'a', 'b'); test_type_str(vcpu, "foo", "foo"); test_type_str(vcpu, "foo", "bar"); test_type_ptr(vcpu, 0x1234567890abcdef, 0x1234567890abcdef); test_type_ptr(vcpu, 0x1234567890abcdef, 0x1234567890abcdee); kvm_vm_free(vm); test_limits(); return 0; }