From patchwork Mon Mar 15 22:10:18 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Maxim Levitsky X-Patchwork-Id: 12140757 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.0 required=3.0 tests=BAYES_00,DKIMWL_WL_HIGH, DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_CR_TRAILER,INCLUDES_PATCH,MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 189F2C4332B for ; Mon, 15 Mar 2021 22:11:14 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id E11D964F5F for ; Mon, 15 Mar 2021 22:11:13 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232445AbhCOWKm (ORCPT ); Mon, 15 Mar 2021 18:10:42 -0400 Received: from us-smtp-delivery-124.mimecast.com ([63.128.21.124]:38779 "EHLO us-smtp-delivery-124.mimecast.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232626AbhCOWKg (ORCPT ); Mon, 15 Mar 2021 18:10:36 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1615846235; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=xs80L4S7if4oQ7sYgkwvzm47LtF8hDXaNV9qxN93CWM=; b=b8mnflWXZcOn24vmjjM1zqqmAo1SEIpzz3IZPaVVUVsPqOmtAUjek0vRCR28aCpETAvShM HhAkyg5B7t3zCTqz/AsNFFzp0g9vrLc8OP8qZ3h08fTS7KlUDuHSmYtQl4LGmzqzrWzSDC 6ylkUZbL+IcVAawzCudbYZHHl46Sbn8= Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-474-3JeqLqiZM1GMdXfm4fr1Wg-1; Mon, 15 Mar 2021 18:10:33 -0400 X-MC-Unique: 3JeqLqiZM1GMdXfm4fr1Wg-1 Received: from smtp.corp.redhat.com (int-mx06.intmail.prod.int.phx2.redhat.com [10.5.11.16]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 997BC87A826; Mon, 15 Mar 2021 22:10:31 +0000 (UTC) Received: from localhost.localdomain (unknown [10.35.207.30]) by smtp.corp.redhat.com (Postfix) with ESMTP id 416DA5E1A4; Mon, 15 Mar 2021 22:10:27 +0000 (UTC) From: Maxim Levitsky To: kvm@vger.kernel.org Cc: Vitaly Kuznetsov , linux-kernel@vger.kernel.org, Thomas Gleixner , Wanpeng Li , Kieran Bingham , Jessica Yu , Jan Kiszka , Andrew Morton , x86@kernel.org (maintainer:X86 ARCHITECTURE (32-BIT AND 64-BIT)), Joerg Roedel , Sean Christopherson , Jim Mattson , Borislav Petkov , Stefano Garzarella , Maxim Levitsky , "H. Peter Anvin" , Paolo Bonzini , Ingo Molnar Subject: [PATCH 1/3] scripts/gdb: rework lx-symbols gdb script Date: Tue, 16 Mar 2021 00:10:18 +0200 Message-Id: <20210315221020.661693-2-mlevitsk@redhat.com> In-Reply-To: <20210315221020.661693-1-mlevitsk@redhat.com> References: <20210315221020.661693-1-mlevitsk@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.16 Precedence: bulk List-ID: X-Mailing-List: kvm@vger.kernel.org Fix several issues that are present in lx-symbols script: * Track module unloads by placing another software breakpoint at 'free_module' (force uninline this symbol just in case), and use remove-symbol-file gdb command to unload the symobls of the module that is unloading. That gives the gdb a chance to mark all software breakpoints from this module as pending again. Also remove the module from the 'known' module list once it is unloaded. * Since we now track module unload, we don't need to reload all symbols anymore when 'known' module loaded again (that can't happen anymore). This allows reloading a module in the debugged kernel to finish much faster, while lx-symbols tracks module loads and unloads. * Disable/enable all gdb breakpoints on both module load and unload breakpoint hits, and not only in 'load_all_symbols' as was done before. (load_all_symbols is no longer called on breakpoint hit) That allows gdb to avoid getting confused about the state of the (now two) internal breakpoints we place. Otherwise it will leave them in the kernel code segment, when continuing which triggers a guest kernel panic as soon as it skips over the 'int3' instruction and executes the garbage tail of the optcode on which the breakpoint was placed. Signed-off-by: Maxim Levitsky --- kernel/module.c | 8 ++- scripts/gdb/linux/symbols.py | 106 +++++++++++++++++++++++++---------- 2 files changed, 83 insertions(+), 31 deletions(-) diff --git a/kernel/module.c b/kernel/module.c index 30479355ab850..ea81fc06ea1f5 100644 --- a/kernel/module.c +++ b/kernel/module.c @@ -901,8 +901,12 @@ int module_refcount(struct module *mod) } EXPORT_SYMBOL(module_refcount); -/* This exists whether we can unload or not */ -static void free_module(struct module *mod); +/* This exists whether we can unload or not + * Keep it uninlined to provide a reliable breakpoint target, + * e.g. for the gdb helper command 'lx-symbols'. + */ + +static noinline void free_module(struct module *mod); SYSCALL_DEFINE2(delete_module, const char __user *, name_user, unsigned int, flags) diff --git a/scripts/gdb/linux/symbols.py b/scripts/gdb/linux/symbols.py index 1be9763cf8bb2..4ce879548a1ae 100644 --- a/scripts/gdb/linux/symbols.py +++ b/scripts/gdb/linux/symbols.py @@ -17,6 +17,24 @@ import re from linux import modules, utils +def save_state(): + breakpoints = [] + if hasattr(gdb, 'breakpoints') and not gdb.breakpoints() is None: + for bp in gdb.breakpoints(): + breakpoints.append({'breakpoint': bp, 'enabled': bp.enabled}) + bp.enabled = False + + show_pagination = gdb.execute("show pagination", to_string=True) + pagination = show_pagination.endswith("on.\n") + gdb.execute("set pagination off") + + return {"breakpoints":breakpoints, "show_pagination": show_pagination} + +def load_state(state): + for breakpoint in state["breakpoints"]: + breakpoint['breakpoint'].enabled = breakpoint['enabled'] + gdb.execute("set pagination %s" % ("on" if state["show_pagination"] else "off")) + if hasattr(gdb, 'Breakpoint'): class LoadModuleBreakpoint(gdb.Breakpoint): @@ -30,26 +48,38 @@ if hasattr(gdb, 'Breakpoint'): module_name = module['name'].string() cmd = self.gdb_command + # module already loaded, false alarm + if module_name in cmd.loaded_modules: + return False + # enforce update if object file is not found cmd.module_files_updated = False # Disable pagination while reporting symbol (re-)loading. # The console input is blocked in this context so that we would # get stuck waiting for the user to acknowledge paged output. - show_pagination = gdb.execute("show pagination", to_string=True) - pagination = show_pagination.endswith("on.\n") - gdb.execute("set pagination off") + state = save_state() + cmd.load_module_symbols(module) + load_state(state) + return False - if module_name in cmd.loaded_modules: - gdb.write("refreshing all symbols to reload module " - "'{0}'\n".format(module_name)) - cmd.load_all_symbols() - else: - cmd.load_module_symbols(module) + class UnLoadModuleBreakpoint(gdb.Breakpoint): + def __init__(self, spec, gdb_command): + super(UnLoadModuleBreakpoint, self).__init__(spec, internal=True) + self.silent = True + self.gdb_command = gdb_command + + def stop(self): + module = gdb.parse_and_eval("mod") + module_name = module['name'].string() + cmd = self.gdb_command - # restore pagination state - gdb.execute("set pagination %s" % ("on" if pagination else "off")) + if not module_name in cmd.loaded_modules: + return False + state = save_state() + cmd.unload_module_symbols(module) + load_state(state) return False @@ -64,8 +94,9 @@ lx-symbols command.""" module_paths = [] module_files = [] module_files_updated = False - loaded_modules = [] - breakpoint = None + loaded_modules = {} + module_load_breakpoint = None + module_unload_breakpoint = None def __init__(self): super(LxSymbols, self).__init__("lx-symbols", gdb.COMMAND_FILES, @@ -129,21 +160,32 @@ lx-symbols command.""" filename=module_file, addr=module_addr, sections=self._section_arguments(module)) + gdb.execute(cmdline, to_string=True) - if module_name not in self.loaded_modules: - self.loaded_modules.append(module_name) + self.loaded_modules[module_name] = {"module_file": module_file, + "module_addr": module_addr} else: gdb.write("no module object found for '{0}'\n".format(module_name)) + def unload_module_symbols(self, module): + module_name = module['name'].string() + + module_file = self.loaded_modules[module_name]["module_file"] + module_addr = self.loaded_modules[module_name]["module_addr"] + + gdb.write("unloading @{addr}: {filename}\n".format( + addr=module_addr, filename=module_file)) + cmdline = "remove-symbol-file {filename}".format( + filename=module_file) + + gdb.execute(cmdline, to_string=True) + del self.loaded_modules[module_name] + + def load_all_symbols(self): gdb.write("loading vmlinux\n") - # Dropping symbols will disable all breakpoints. So save their states - # and restore them afterward. - saved_states = [] - if hasattr(gdb, 'breakpoints') and not gdb.breakpoints() is None: - for bp in gdb.breakpoints(): - saved_states.append({'breakpoint': bp, 'enabled': bp.enabled}) + state = save_state() # drop all current symbols and reload vmlinux orig_vmlinux = 'vmlinux' @@ -153,15 +195,14 @@ lx-symbols command.""" gdb.execute("symbol-file", to_string=True) gdb.execute("symbol-file {0}".format(orig_vmlinux)) - self.loaded_modules = [] + self.loaded_modules = {} module_list = modules.module_list() if not module_list: gdb.write("no modules found\n") else: [self.load_module_symbols(module) for module in module_list] - for saved_state in saved_states: - saved_state['breakpoint'].enabled = saved_state['enabled'] + load_state(state) def invoke(self, arg, from_tty): self.module_paths = [os.path.expanduser(p) for p in arg.split()] @@ -174,11 +215,18 @@ lx-symbols command.""" self.load_all_symbols() if hasattr(gdb, 'Breakpoint'): - if self.breakpoint is not None: - self.breakpoint.delete() - self.breakpoint = None - self.breakpoint = LoadModuleBreakpoint( - "kernel/module.c:do_init_module", self) + if self.module_load_breakpoint is not None: + self.module_load_breakpoint.delete() + self.module_load_breakpoint = None + self.module_load_breakpoint = \ + LoadModuleBreakpoint("kernel/module.c:do_init_module", self) + + if self.module_unload_breakpoint is not None: + self.module_unload_breakpoint.delete() + self.module_unload_breakpoint = None + self.module_unload_breakpoint = \ + UnLoadModuleBreakpoint("kernel/module.c:free_module", self) + else: gdb.write("Note: symbol update on module loading not supported " "with this gdb version\n") From patchwork Mon Mar 15 22:10:19 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Maxim Levitsky X-Patchwork-Id: 12140761 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.0 required=3.0 tests=BAYES_00,DKIMWL_WL_HIGH, DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_CR_TRAILER,INCLUDES_PATCH,MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id BC301C433E0 for ; Mon, 15 Mar 2021 22:11:45 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 89E6164F06 for ; Mon, 15 Mar 2021 22:11:45 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231613AbhCOWLP (ORCPT ); Mon, 15 Mar 2021 18:11:15 -0400 Received: from us-smtp-delivery-124.mimecast.com ([216.205.24.124]:40076 "EHLO us-smtp-delivery-124.mimecast.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232466AbhCOWKn (ORCPT ); Mon, 15 Mar 2021 18:10:43 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1615846242; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=G9NrupMURdEuUMDF610xSyzzhCAN0PBHUS0Y8SXmWZA=; b=SHKc/CrwAI3h/davTaO5K+vC5hS4x5PZubSQljVtizTQdLNeXF6QRwcyn8ZSZQNCR1rqdF 1UH4KMezvC6ac1rirmAziVjn5eL6Y2w3BJZ00H8TrLvG/mvGuiU1GNkthz6CgPxXxJwaqy PxxnJSZ7fxVajYkGmZ1LHL82Qef5CjY= Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-311-3Oks0f7rMSyKvgBBSD5aJg-1; Mon, 15 Mar 2021 18:10:40 -0400 X-MC-Unique: 3Oks0f7rMSyKvgBBSD5aJg-1 Received: from smtp.corp.redhat.com (int-mx06.intmail.prod.int.phx2.redhat.com [10.5.11.16]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 3AB18100C619; Mon, 15 Mar 2021 22:10:38 +0000 (UTC) Received: from localhost.localdomain (unknown [10.35.207.30]) by smtp.corp.redhat.com (Postfix) with ESMTP id 0264A5F706; Mon, 15 Mar 2021 22:10:31 +0000 (UTC) From: Maxim Levitsky To: kvm@vger.kernel.org Cc: Vitaly Kuznetsov , linux-kernel@vger.kernel.org, Thomas Gleixner , Wanpeng Li , Kieran Bingham , Jessica Yu , Jan Kiszka , Andrew Morton , x86@kernel.org (maintainer:X86 ARCHITECTURE (32-BIT AND 64-BIT)), Joerg Roedel , Sean Christopherson , Jim Mattson , Borislav Petkov , Stefano Garzarella , Maxim Levitsky , "H. Peter Anvin" , Paolo Bonzini , Ingo Molnar Subject: [PATCH 2/3] KVM: x86: guest debug: don't inject interrupts while single stepping Date: Tue, 16 Mar 2021 00:10:19 +0200 Message-Id: <20210315221020.661693-3-mlevitsk@redhat.com> In-Reply-To: <20210315221020.661693-1-mlevitsk@redhat.com> References: <20210315221020.661693-1-mlevitsk@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.16 Precedence: bulk List-ID: X-Mailing-List: kvm@vger.kernel.org This change greatly helps with two issues: * Resuming from a breakpoint is much more reliable. When resuming execution from a breakpoint, with interrupts enabled, more often than not, KVM would inject an interrupt and make the CPU jump immediately to the interrupt handler and eventually return to the breakpoint, to trigger it again. From the user point of view it looks like the CPU never executed a single instruction and in some cases that can even prevent forward progress, for example, when the breakpoint is placed by an automated script (e.g lx-symbols), which does something in response to the breakpoint and then continues the guest automatically. If the script execution takes enough time for another interrupt to arrive, the guest will be stuck on the same breakpoint RIP forever. * Normal single stepping is much more predictable, since it won't land the debugger into an interrupt handler, so it is much more usable. (If entry to an interrupt handler is desired, the user can still place a breakpoint at it and resume the guest, which won't activate this workaround and let the gdb still stop at the interrupt handler) Since this change is only active when guest is debugged, it won't affect KVM running normal 'production' VMs. Signed-off-by: Maxim Levitsky Tested-by: Stefano Garzarella --- arch/x86/kvm/x86.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c index a9d95f90a0487..b75d990fcf12b 100644 --- a/arch/x86/kvm/x86.c +++ b/arch/x86/kvm/x86.c @@ -8458,6 +8458,12 @@ static void inject_pending_event(struct kvm_vcpu *vcpu, bool *req_immediate_exit can_inject = false; } + /* + * Don't inject interrupts while single stepping to make guest debug easier + */ + if (vcpu->guest_debug & KVM_GUESTDBG_SINGLESTEP) + return; + /* * Finally, inject interrupt events. If an event cannot be injected * due to architectural conditions (e.g. IF=0) a window-open exit From patchwork Mon Mar 15 22:10:20 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Maxim Levitsky X-Patchwork-Id: 12140763 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.0 required=3.0 tests=BAYES_00,DKIMWL_WL_HIGH, DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_CR_TRAILER,INCLUDES_PATCH,MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 56308C433DB for ; Mon, 15 Mar 2021 22:11:46 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 1CA0C64F51 for ; Mon, 15 Mar 2021 22:11:46 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232691AbhCOWLQ (ORCPT ); Mon, 15 Mar 2021 18:11:16 -0400 Received: from us-smtp-delivery-124.mimecast.com ([216.205.24.124]:47047 "EHLO us-smtp-delivery-124.mimecast.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232605AbhCOWKz (ORCPT ); Mon, 15 Mar 2021 18:10:55 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1615846255; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=AoArXX9WPpL4SnAa/Ge0fyAJqtNqRN2jjUvZKB7Oaa0=; b=XFz/zrA2py5zpBvuH8+EO6Ww9km+/fuwui0p7eLeLG7E6ZNRL9G4Wr8WvJN3aAq3kK59eS OIXC3VJQbdczxAnZ/TpvK6OOvJaPv/Fv7I+Jh2O4nh/l8BVWTRcalYtEB9dfQpkqI+Nr/l vnFdBFNPO4z29/QXT4uhRbxb2Zmdf54= Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-539-o7X9-GtpMoyv7NehMQKMfQ-1; Mon, 15 Mar 2021 18:10:53 -0400 X-MC-Unique: o7X9-GtpMoyv7NehMQKMfQ-1 Received: from smtp.corp.redhat.com (int-mx06.intmail.prod.int.phx2.redhat.com [10.5.11.16]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id EB8DA107ACCA; Mon, 15 Mar 2021 22:10:50 +0000 (UTC) Received: from localhost.localdomain (unknown [10.35.207.30]) by smtp.corp.redhat.com (Postfix) with ESMTP id 960D45C261; Mon, 15 Mar 2021 22:10:38 +0000 (UTC) From: Maxim Levitsky To: kvm@vger.kernel.org Cc: Vitaly Kuznetsov , linux-kernel@vger.kernel.org, Thomas Gleixner , Wanpeng Li , Kieran Bingham , Jessica Yu , Jan Kiszka , Andrew Morton , x86@kernel.org (maintainer:X86 ARCHITECTURE (32-BIT AND 64-BIT)), Joerg Roedel , Sean Christopherson , Jim Mattson , Borislav Petkov , Stefano Garzarella , Maxim Levitsky , "H. Peter Anvin" , Paolo Bonzini , Ingo Molnar , Borislav Petkov Subject: [PATCH 3/3] KVM: SVM: allow to intercept all exceptions for debug Date: Tue, 16 Mar 2021 00:10:20 +0200 Message-Id: <20210315221020.661693-4-mlevitsk@redhat.com> In-Reply-To: <20210315221020.661693-1-mlevitsk@redhat.com> References: <20210315221020.661693-1-mlevitsk@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.16 Precedence: bulk List-ID: X-Mailing-List: kvm@vger.kernel.org Add a new debug module param 'debug_intercept_exceptions' which will allow the KVM to intercept any guest exception, and forward it to the guest. This can be very useful for guest debugging and/or KVM debugging with kvm trace. This is not intended to be used on production systems. This is based on an idea first shown here: https://patchwork.kernel.org/project/kvm/patch/20160301192822.GD22677@pd.tnic/ CC: Borislav Petkov Signed-off-by: Maxim Levitsky --- arch/x86/include/asm/kvm_host.h | 2 + arch/x86/kvm/svm/svm.c | 77 ++++++++++++++++++++++++++++++++- arch/x86/kvm/svm/svm.h | 5 ++- arch/x86/kvm/x86.c | 5 ++- 4 files changed, 85 insertions(+), 4 deletions(-) diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h index a52f973bdff6d..c8f44a88b3153 100644 --- a/arch/x86/include/asm/kvm_host.h +++ b/arch/x86/include/asm/kvm_host.h @@ -1564,6 +1564,8 @@ int kvm_emulate_rdpmc(struct kvm_vcpu *vcpu); void kvm_queue_exception(struct kvm_vcpu *vcpu, unsigned nr); void kvm_queue_exception_e(struct kvm_vcpu *vcpu, unsigned nr, u32 error_code); void kvm_queue_exception_p(struct kvm_vcpu *vcpu, unsigned nr, unsigned long payload); +void kvm_queue_exception_e_p(struct kvm_vcpu *vcpu, unsigned nr, + u32 error_code, unsigned long payload); void kvm_requeue_exception(struct kvm_vcpu *vcpu, unsigned nr); void kvm_requeue_exception_e(struct kvm_vcpu *vcpu, unsigned nr, u32 error_code); void kvm_inject_page_fault(struct kvm_vcpu *vcpu, struct x86_exception *fault); diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c index 271196400495f..94156a367a663 100644 --- a/arch/x86/kvm/svm/svm.c +++ b/arch/x86/kvm/svm/svm.c @@ -197,6 +197,9 @@ module_param(sev_es, int, 0444); bool __read_mostly dump_invalid_vmcb; module_param(dump_invalid_vmcb, bool, 0644); +uint debug_intercept_exceptions; +module_param(debug_intercept_exceptions, uint, 0444); + static bool svm_gp_erratum_intercept = true; static u8 rsm_ins_bytes[] = "\x0f\xaa"; @@ -220,6 +223,8 @@ static const u32 msrpm_ranges[] = {0, 0xc0000000, 0xc0010000}; #define MSRS_RANGE_SIZE 2048 #define MSRS_IN_RANGE (MSRS_RANGE_SIZE * 8 / 2) +static void init_debug_exceptions_intercept(struct vcpu_svm *svm); + u32 svm_msrpm_offset(u32 msr) { u32 offset; @@ -1137,6 +1142,8 @@ static void init_vmcb(struct kvm_vcpu *vcpu) set_exception_intercept(svm, MC_VECTOR); set_exception_intercept(svm, AC_VECTOR); set_exception_intercept(svm, DB_VECTOR); + + init_debug_exceptions_intercept(svm); /* * Guest access to VMware backdoor ports could legitimately * trigger #GP because of TSS I/O permission bitmap. @@ -1913,6 +1920,17 @@ static int pf_interception(struct kvm_vcpu *vcpu) u64 fault_address = svm->vmcb->control.exit_info_2; u64 error_code = svm->vmcb->control.exit_info_1; + if ((debug_intercept_exceptions & (1 << PF_VECTOR))) + if (npt_enabled && !vcpu->arch.apf.host_apf_flags) { + /* If #PF was only intercepted for debug, inject + * it directly to the guest, since the mmu code + * is not ready to deal with such page faults + */ + kvm_queue_exception_e_p(vcpu, PF_VECTOR, + error_code, fault_address); + return 1; + } + return kvm_handle_page_fault(vcpu, error_code, fault_address, static_cpu_has(X86_FEATURE_DECODEASSISTS) ? svm->vmcb->control.insn_bytes : NULL, @@ -3025,7 +3043,7 @@ static int invpcid_interception(struct kvm_vcpu *vcpu) return kvm_handle_invpcid(vcpu, type, gva); } -static int (*const svm_exit_handlers[])(struct kvm_vcpu *vcpu) = { +static int (*svm_exit_handlers[])(struct kvm_vcpu *vcpu) = { [SVM_EXIT_READ_CR0] = cr_interception, [SVM_EXIT_READ_CR3] = cr_interception, [SVM_EXIT_READ_CR4] = cr_interception, @@ -3099,6 +3117,63 @@ static int (*const svm_exit_handlers[])(struct kvm_vcpu *vcpu) = { [SVM_EXIT_VMGEXIT] = sev_handle_vmgexit, }; +static int generic_exception_interception(struct kvm_vcpu *vcpu) +{ + /* + * Generic exception handler which forwards a guest exception + * as-is to the guest. + * For exceptions that don't have a special intercept handler. + * + * Used for 'debug_intercept_exceptions' KVM debug feature only. + */ + struct vcpu_svm *svm = to_svm(vcpu); + int exc = svm->vmcb->control.exit_code - SVM_EXIT_EXCP_BASE; + + WARN_ON(exc < 0 || exc > 31); + + if (exc == TS_VECTOR) { + /* + * SVM doesn't provide us with an error code to be able to + * re-inject the #TS exception, so just disable its + * interception, and let the guest re-execute the instruction. + */ + vmcb_clr_intercept(&svm->vmcb01.ptr->control, + INTERCEPT_EXCEPTION_OFFSET + TS_VECTOR); + recalc_intercepts(svm); + return 1; + } else if (exc == DF_VECTOR) { + /* SVM doesn't provide us with an error code for the #DF */ + kvm_queue_exception_e(vcpu, exc, 0); + return 1; + } + + if (x86_exception_has_error_code(exc)) + kvm_queue_exception_e(vcpu, exc, svm->vmcb->control.exit_info_1); + else + kvm_queue_exception(vcpu, exc); + return 1; +} + +static void init_debug_exceptions_intercept(struct vcpu_svm *svm) +{ + int exc; + + for (exc = 0 ; exc < 32 ; exc++) { + if (!(debug_intercept_exceptions & (1 << exc))) + continue; + + /* Those are defined to have undefined behavior in the SVM spec */ + if (exc == 2 || exc == 9) + continue; + + set_exception_intercept(svm, exc); + + if (!svm_exit_handlers[SVM_EXIT_EXCP_BASE + exc]) + svm_exit_handlers[SVM_EXIT_EXCP_BASE + exc] = + generic_exception_interception; + } +} + static void dump_vmcb(struct kvm_vcpu *vcpu) { struct vcpu_svm *svm = to_svm(vcpu); diff --git a/arch/x86/kvm/svm/svm.h b/arch/x86/kvm/svm/svm.h index 8e276c4fb33df..e0ff9ca996df8 100644 --- a/arch/x86/kvm/svm/svm.h +++ b/arch/x86/kvm/svm/svm.h @@ -32,6 +32,7 @@ static const u32 host_save_user_msrs[] = { #define MSRPM_OFFSETS 16 extern u32 msrpm_offsets[MSRPM_OFFSETS] __read_mostly; extern bool npt_enabled; +extern uint debug_intercept_exceptions; enum { VMCB_INTERCEPTS, /* Intercept vectors, TSC offset, @@ -333,7 +334,9 @@ static inline void clr_exception_intercept(struct vcpu_svm *svm, u32 bit) struct vmcb *vmcb = svm->vmcb01.ptr; WARN_ON_ONCE(bit >= 32); - vmcb_clr_intercept(&vmcb->control, INTERCEPT_EXCEPTION_OFFSET + bit); + + if (!((1 << bit) & debug_intercept_exceptions)) + vmcb_clr_intercept(&vmcb->control, INTERCEPT_EXCEPTION_OFFSET + bit); recalc_intercepts(svm); } diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c index b75d990fcf12b..be509944622bd 100644 --- a/arch/x86/kvm/x86.c +++ b/arch/x86/kvm/x86.c @@ -627,12 +627,13 @@ void kvm_queue_exception_p(struct kvm_vcpu *vcpu, unsigned nr, } EXPORT_SYMBOL_GPL(kvm_queue_exception_p); -static void kvm_queue_exception_e_p(struct kvm_vcpu *vcpu, unsigned nr, - u32 error_code, unsigned long payload) +void kvm_queue_exception_e_p(struct kvm_vcpu *vcpu, unsigned nr, + u32 error_code, unsigned long payload) { kvm_multiple_exception(vcpu, nr, true, error_code, true, payload, false); } +EXPORT_SYMBOL_GPL(kvm_queue_exception_e_p); int kvm_complete_insn_gp(struct kvm_vcpu *vcpu, int err) {