From patchwork Sun Oct 27 09:10:52 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andrew Burgess X-Patchwork-Id: 99670 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 1355E3858027 for ; Sun, 27 Oct 2024 09:11:47 +0000 (GMT) X-Original-To: gdb-patches@sourceware.org Delivered-To: gdb-patches@sourceware.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by sourceware.org (Postfix) with ESMTP id C2D703858405 for ; Sun, 27 Oct 2024 09:11:12 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org C2D703858405 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org C2D703858405 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1730020274; cv=none; b=wdLGTUt8KxfwSYq4K0SBC/1qBnUwK0MXFrn9P+KxhwJfbA+7epJzU+KItXTVhGKRYfTxx0h425i6xW+puGco6e2OuJQOBbCbSHrl9AYDHqdXHEgs6qO4P3JKPqIeJTulEz8EKRKbMlAB3+IiSwDMXFBe4dmLue29bzBerBE+Mtk= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1730020274; c=relaxed/simple; bh=Ywy9k14gDEETjwjNVlGMw6piJtp37NDqzGhz/50e8Vw=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=InGBJ+V0huACEE3jY94+p1vxSjJVqMxUItHUSsyIByKcngDWYLUgVksKQhc7VxlRIELMm6NBQZ0NcVIkmO2tuQXV0FROjLPVghL0+U4CWHJp1wNXCdXeiGNSSN6BB5gZCql7VdoQdsbtikE71Uedeg4xIgbUXHo0UE87VKBZzjU= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1730020272; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=uBGuSkRu1JxDe7G4omALM7Mowc+1OHkfZ8q1ll1JY4c=; b=cfRM/hFu3t4LNxxFaANO1EyHwTiSIK+IMcHo4ZuITM0dG2lxOiyd77NGWstNj1EdW+uGYE +lsXPBLe6WZ3zXDSgrJRRFFrCN+8IM0/hEiNItrPbz/2iJH2Bp27DtjU/lxqNNhH/LQyME U8ddtx+gMec3p3ToDMfKFtxCYEyQ1yo= Received: from mail-wm1-f69.google.com (mail-wm1-f69.google.com [209.85.128.69]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-441-HO8uWvCvMU-fgOXuWNKF_A-1; Sun, 27 Oct 2024 05:11:03 -0400 X-MC-Unique: HO8uWvCvMU-fgOXuWNKF_A-1 Received: by mail-wm1-f69.google.com with SMTP id 5b1f17b1804b1-4316300bb15so24270115e9.2 for ; Sun, 27 Oct 2024 02:11:03 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1730020261; x=1730625061; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=uBGuSkRu1JxDe7G4omALM7Mowc+1OHkfZ8q1ll1JY4c=; b=UiAiUcJsCV1h5Wk/OQpcDjBH2yOh85mHWmFX2rc8cm/gH3MOOXSBn0Du6W8QRDegG9 oJasfDGj3TeDfRt6VY+rD4H5ECUnyXhmQUWD/SgFyPdNxuv+BbqyoHzYU+yREB5NbjQW YQGDrjdSflK+tND/H712sWxixrFEWYgvG7s91LfHBgp+Av3KcQk56gnpwCk8Mvgc14oE c5WSYbfXFwLbvKoZpkUIvihcANXzv/6VUVZjSt4ezmnQjbPul3QCMCmk9uHUmoupnjWp ToPZecgL/T4pMpXQiInPncIlj13Q2Yuyipo0fRjFB0SCkvc60gIJb4lOFRdCAZ+4EDgC A1sA== X-Gm-Message-State: AOJu0Yzjcc23d1CwZA/Og/4k3PAqP2Di3ykCKBBFYvmcuB19SLOc+QaE PduTfxB+hGNbX37Ac1S6SgQf2IGr7fTRasQSPe6dU40/lnkLTXyS3iP9zBekU571ET9/z3Ed06b n9DzLFpk5+S/OQiqcPNdBUjeb5K5F3q+fxZ0EvzoIrCDJA9HAdZnNNPMMO6nZMetlKkHcEsnp/T VNvVHWkCs3pzT4RCRwZGEDOkFVnS/pT6Ju/bhxaRnCr3I= X-Received: by 2002:a05:600c:3b1f:b0:42f:310f:de9 with SMTP id 5b1f17b1804b1-4319acac7fdmr32564255e9.15.1730020261012; Sun, 27 Oct 2024 02:11:01 -0700 (PDT) X-Google-Smtp-Source: AGHT+IEtpTnc+1nGE/KZqc5eW6u0M0/PgIg7tIqs4/6GvYHCV+7a6iyeQQ0TkXmgjAg8N/IS6qXJQg== X-Received: by 2002:a05:600c:3b1f:b0:42f:310f:de9 with SMTP id 5b1f17b1804b1-4319acac7fdmr32564025e9.15.1730020260425; Sun, 27 Oct 2024 02:11:00 -0700 (PDT) Received: from localhost (197.209.200.146.dyn.plus.net. [146.200.209.197]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-4319360c0besm69264165e9.41.2024.10.27.02.10.58 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 27 Oct 2024 02:10:58 -0700 (PDT) From: Andrew Burgess To: gdb-patches@sourceware.org Cc: Andrew Burgess , Tom Tromey Subject: [PATCHv2 1/4] gdb: use mapped file information to improve debuginfod text Date: Sun, 27 Oct 2024 09:10:52 +0000 Message-Id: <70e631978c2cee367380b20984c86ed59a3ec96b.1730020155.git.aburgess@redhat.com> X-Mailer: git-send-email 2.25.4 In-Reply-To: References: MIME-Version: 1.0 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-11.9 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H2, SPF_HELO_NONE, SPF_NONE, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: gdb-patches@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gdb-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gdb-patches-bounces~patchwork=sourceware.org@sourceware.org When opening a core-file GDB is able to use debuginfod to download the executable that matches the core-file if GDB can find a build-id for the executable in the core-file. In this case GDB calls debuginfod_exec_query to download the executable and GDB prints a message like: Downloading executable for /path/to/core-file... which makes sense in that case. For a long time GDB has also had the ability to download memory-mapped files and shared libraries when opening a core-file. However, recent commits have made these cases more likely to trigger, which is a good thing, but the messaging from GDB in these cases is not ideal. When downloading a memory-mapped file GDB prints: Downloading executable for /path/to/memory-mapped-file And for a shared library: Downloading executable for /path/to/libfoo.so These last two messages could, I think, be improved. I propose making two changes. First, I suggest instead of using /path/to/core-file in the first case, we use the name of the executable that GDB is fetching. This makes the messaging consistent in that we print the name of the file we're fetching rather than the name of the file we're fetching something for. I further propose that we replace 'executable for' with the more generic word 'file'. The messages will then become: Downloading file /path/to/exec-file... Downloading file /path/to/memory-mapped-file... Downloading file /path/to/libfoo.so... I think these messages are clearer than what we used to have, and they are consistent in that we name the thing being downloaded in all cases. There is one tiny problem. The first case relies on GDB knowing the name of the executable it wants to download. The only place we can currently get that from is, I think, the memory-mapped file list. [ ASIDE: There is `bfd_core_file_failing_command` which reports the executable and argument list from the core file, but this information is not ideal for this task. First, the executable and arguments are merged into a single string, and second, the string is a relatively short, fixed length string, so the executable name is often truncated. For these reasons I don't consider fetching the executable name using this bfd function as a solution. ] We do have to consider the case that the core file does not have any mapped file information. This shouldn't ever be the case for a Linux target, but it's worth considering. [ ASIDE: I mention Linux specifically because this only becomes a problem if we try to do a lookup via debuginfod, which requires that we have build-ids available. Linux has special support for embedding build-ids into the core file, but I'm not sure if other kernels do this. ] For the unlikely edge case of a core-file that has build-ids, but doesn't have any mapped file information then I propose that we synthesis a filename like: 'with build-id xxxxxx'. We would then see a message like: Downloading file with build-id xxxxxx... Where 'xxxxxx' would be replaced by the actual build-id. This isn't ideal, but I think is good enough, and, as I said, I think this case is not going to be hit very often, or maybe at all. We already had some tests that emitted two of the above messages, which I've updated, these cover the mapped-file and shared library case. The message about downloading the exec for the core-file is actually really hard to trigger now as usually the exec will also appear in the memory-mapped file list and GDB will download the file at this stage. Then when GDB needs the executable for loading the symbols it'll ask debuginfod, and debuginfod will find the file in its cache, and so no message will be printed. If anyone has any ideas about how to trigger this case then I'm happy to add additional tests. Approved-By: Tom Tromey --- gdb/corelow.c | 59 ++++++++++++++++++- gdb/debuginfod-support.c | 4 +- .../gdb.debuginfod/corefile-mapped-file.exp | 2 +- .../gdb.debuginfod/solib-with-soname.exp | 2 +- 4 files changed, 60 insertions(+), 7 deletions(-) diff --git a/gdb/corelow.c b/gdb/corelow.c index 5820ffed332..87ce04d6852 100644 --- a/gdb/corelow.c +++ b/gdb/corelow.c @@ -255,6 +255,27 @@ class core_target final : public process_stratum_target return m_mapped_file_info.lookup (filename, addr); } + /* Return a string containing the expected executable filename obtained + from the mapped file information within the core file. The filename + returned will be for the mapped file whose ELF headers are mapped at + the lowest address (i.e. which GDB encounters first). + + If no suitable filename can be found then the returned string will be + empty. + + If there are no build-ids embedded into the core file then the + returned string will be empty. + + If a non-empty string is returned then there is no guarantee that the + named file exists on disk, or if it does exist on disk, then the + on-disk file might have a different build-id to the desired + build-id. */ + const std::string & + expected_exec_filename () const + { + return m_expected_exec_filename; + } + private: /* per-core data */ /* Get rid of the core inferior. */ @@ -289,6 +310,11 @@ class core_target final : public process_stratum_target /* FIXME: kettenis/20031023: Eventually this field should disappear. */ struct gdbarch *m_core_gdbarch = NULL; + + /* If not empty then this contains the name of the executable discovered + when processing the memory-mapped file information. This will only + be set if we find a mapped with a suitable build-id. */ + std::string m_expected_exec_filename; }; core_target::core_target () @@ -418,11 +444,23 @@ core_target::build_file_mappings () } }); + /* Get the build-id of the core file. */ + const bfd_build_id *core_build_id + = build_id_bfd_get (current_program_space->core_bfd ()); + for (const auto &iter : mapped_files) { const std::string &filename = iter.first; const mapped_file &file_data = iter.second; + /* If this mapped file has the same build-id as was discovered for + the core-file itself, then we assume this is the main + executable. Record the filename as we can use this later. */ + if (file_data.build_id != nullptr + && m_expected_exec_filename.empty () + && build_id_equal (file_data.build_id, core_build_id)) + m_expected_exec_filename = filename; + /* Use exec_file_find() to do sysroot expansion. It'll also strip the potential sysroot "target:" prefix. If there is no sysroot, an equivalent (possibly more @@ -832,14 +870,29 @@ rename_vmcore_idle_reg_sections (bfd *abfd, inferior *inf) BFD ABFD. */ static void -locate_exec_from_corefile_build_id (bfd *abfd, int from_tty) +locate_exec_from_corefile_build_id (bfd *abfd, core_target *target, + int from_tty) { const bfd_build_id *build_id = build_id_bfd_get (abfd); if (build_id == nullptr) return; + /* The filename used for the find_objfile_by_build_id call. */ + std::string filename; + + if (!target->expected_exec_filename ().empty ()) + filename = target->expected_exec_filename (); + else + { + /* We didn't find an executable name from the mapped file + information, so as a stand-in build a string based on the + build-id. */ + std::string build_id_hex_str = bin2hex (build_id->data, build_id->size); + filename = string_printf ("with build-id %s", build_id_hex_str.c_str ()); + } + gdb_bfd_ref_ptr execbfd - = find_objfile_by_build_id (build_id, abfd->filename); + = find_objfile_by_build_id (build_id, filename.c_str ()); if (execbfd != nullptr) { @@ -972,7 +1025,7 @@ core_target_open (const char *arg, int from_tty) if (current_program_space->exec_bfd () == nullptr) locate_exec_from_corefile_build_id (current_program_space->core_bfd (), - from_tty); + target, from_tty); post_create_inferior (from_tty); diff --git a/gdb/debuginfod-support.c b/gdb/debuginfod-support.c index 841b6f2078c..9460ae18dd6 100644 --- a/gdb/debuginfod-support.c +++ b/gdb/debuginfod-support.c @@ -409,7 +409,7 @@ debuginfod_exec_query (const unsigned char *build_id, std::optional term_state; { - user_data data ("executable for", filename); + user_data data ("file", filename); debuginfod_set_user_data (c, &data); if (target_supports_terminal_ours ()) @@ -423,7 +423,7 @@ debuginfod_exec_query (const unsigned char *build_id, debuginfod_set_user_data (c, nullptr); } - print_outcome (fd.get (), "executable for", filename); + print_outcome (fd.get (), "file", filename); if (fd.get () >= 0) destname->reset (dname); diff --git a/gdb/testsuite/gdb.debuginfod/corefile-mapped-file.exp b/gdb/testsuite/gdb.debuginfod/corefile-mapped-file.exp index cf96b41ac9a..cad70aaa3c6 100644 --- a/gdb/testsuite/gdb.debuginfod/corefile-mapped-file.exp +++ b/gdb/testsuite/gdb.debuginfod/corefile-mapped-file.exp @@ -370,7 +370,7 @@ with_debuginfod_env $cache { gdb_load $binfile load_core_file "load corefile, download library from debuginfod" \ - "Downloading\[^\r\n\]* executable for [string_to_regexp $library_filename]\\.\\.\\." + "Downloading\[^\r\n\]* file [string_to_regexp $library_filename]\\.\\.\\." set ptr_value [read_ptr_value] gdb_assert { $ptr_value == $ptr_expected_value } \ diff --git a/gdb/testsuite/gdb.debuginfod/solib-with-soname.exp b/gdb/testsuite/gdb.debuginfod/solib-with-soname.exp index 9ef12041dc6..31ca7181af6 100644 --- a/gdb/testsuite/gdb.debuginfod/solib-with-soname.exp +++ b/gdb/testsuite/gdb.debuginfod/solib-with-soname.exp @@ -161,7 +161,7 @@ proc load_exec_and_core_file { expect_warning expect_download testname \ set saw_warning true exp_continue } - -re "^Downloading executable for \[^\r\n\]+/libfoo_1\\.so\\.\\.\\.\r\n" { + -re "^Downloading file \[^\r\n\]+/libfoo_1\\.so\\.\\.\\.\r\n" { set saw_download true exp_continue } From patchwork Sun Oct 27 09:10:53 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andrew Burgess X-Patchwork-Id: 99671 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 30BDD3858C60 for ; Sun, 27 Oct 2024 09:11:49 +0000 (GMT) X-Original-To: gdb-patches@sourceware.org Delivered-To: gdb-patches@sourceware.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by sourceware.org (Postfix) with ESMTP id C4A7D3858C41 for ; Sun, 27 Oct 2024 09:11:15 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org C4A7D3858C41 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org C4A7D3858C41 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1730020279; cv=none; b=AYrsgMN3rsiQxqvVw4YEt5rkJnCcXGHPxfLA/8O+1kmnafp67E5jDFZ2Zh0lbCTrqmPwQoSi83D6mgs87/Vt/6vkWdMqC1yQWLhgzNEtjOEyRc5xQjD/oSgQ9x8UeE56PYVNrOdI9ZqTDQ3Xps91pmOge0ThGy+DRksH7LUpu6o= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1730020279; c=relaxed/simple; bh=/3U5GR+Je55MSAgqiYZ95GeMLUc5C5GRyHHeT2bacXo=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=jxP2qDji1qAhxjbbOAzjbLneMiM8kgD0wtiUrRX7s6AaK9wAwv9EvKPTGLIW9T+/sS16QmrPBwe1CUyT3J26fzu+RAa4gDgR3gE5Fad3M5/rt2Zq6G62T5MbARk/0PfKoqtKXCwf11aPjlGHc+GQ3Bno0d0zyfnFW7YDy2vfzew= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1730020275; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=3TxM8Yok/233zogT3zrpolRUlowR8YJnxD6eqfFXu/E=; b=gXrM/0WwrpwfdtWSWQ3fRlbUBIneVujmHnMAH2q9mWFmnLa2Ze3G1R8yzyblW8C7QfW9U8 LtWbm7vgx0nUZYmwY7pdWOhoZGxiztJ9v7VwIiN8ZRhUe2eFmyARwPcPNhxX8nmYbELtB2 96q6pkbCH37VPgSYNv+bi9Uymxf4k7A= Received: from mail-wm1-f72.google.com (mail-wm1-f72.google.com [209.85.128.72]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-609-G8X5-FpiObmOLBbjyWm6uw-1; Sun, 27 Oct 2024 05:11:05 -0400 X-MC-Unique: G8X5-FpiObmOLBbjyWm6uw-1 Received: by mail-wm1-f72.google.com with SMTP id 5b1f17b1804b1-43151a9ea95so22403685e9.1 for ; Sun, 27 Oct 2024 02:11:05 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1730020263; x=1730625063; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=3TxM8Yok/233zogT3zrpolRUlowR8YJnxD6eqfFXu/E=; b=q5hM2YPlW2tiMqLscN7G+kRTiw47/wEB2DhcEO18Ecnw7h4wQbRblG86y4w5AQW1OO HciFUGmKIS1b8dDbTrDwvKbvqVTr3z5wZU0j3PsZUZHKUUUjrsrC1hHCVoscmQ/HqSfj xciaeGe0vTAwDPL3YNbnV5S6kq/QCzpTQZIpwr0kGIKvte83WJ3yb5HtDtlT7MjjoU5C /ZbUW93B8wgVX2MXImxBNHs43BZyyaxqQWD5q79HI6ZMbxr3UBudC9snwb5ay8UvOrXy uYkqXhel7vroA+tojnCjFEw1+aB2ybj4dY8oSG2L46BqUGdEOzuyQ5QSGjM942LJQE3x GAaw== X-Gm-Message-State: AOJu0YwTrEUIFMSGOQbW8cIxsUv7zLTwbmE5DbuGutmLOj9a54yDKRbC qi4lEE8EZDkbs6Ugl54W07ZGcoNhD96jaoNkrTJG7SBCCseiKD0yDrC7ru1kt1Eq1OecP6D4ci1 r0zson5XWBI8qaE34HxVB/+SU9np6t2//YrTSl3yV4kfNd9LDuVRwbEblmIbjNmQ2T5mGi1EElH J+6CcCI8netA54a9S96Wen3Usy/Jva2A+fJlE6FdJSkZ8= X-Received: by 2002:a05:600c:4e8c:b0:431:7c25:8600 with SMTP id 5b1f17b1804b1-4318b587dd1mr79437945e9.2.1730020263389; Sun, 27 Oct 2024 02:11:03 -0700 (PDT) X-Google-Smtp-Source: AGHT+IHRFm2ROrgMkbiWO1kvaqgAjYD1RTZW4cW1CwH3aAkVOzo9agJ/fuGUnKHazUPvhsUDHNP7/w== X-Received: by 2002:a05:600c:4e8c:b0:431:7c25:8600 with SMTP id 5b1f17b1804b1-4318b587dd1mr79437745e9.2.1730020262865; Sun, 27 Oct 2024 02:11:02 -0700 (PDT) Received: from localhost (197.209.200.146.dyn.plus.net. [146.200.209.197]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-4319357291bsm70522095e9.2.2024.10.27.02.11.01 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 27 Oct 2024 02:11:01 -0700 (PDT) From: Andrew Burgess To: gdb-patches@sourceware.org Cc: Andrew Burgess , Tom Tromey Subject: [PATCHv2 2/4] gdb: rename ext_lang_missing_debuginfo_result Date: Sun, 27 Oct 2024 09:10:53 +0000 Message-Id: <2c6a5eeb3d43b327b23e4f55b9eff2c29d1b95bd.1730020155.git.aburgess@redhat.com> X-Mailer: git-send-email 2.25.4 In-Reply-To: References: MIME-Version: 1.0 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-11.9 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H2, SPF_HELO_NONE, SPF_NONE, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: gdb-patches@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gdb-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gdb-patches-bounces~patchwork=sourceware.org@sourceware.org In preparation for later commits in this series, rename ext_lang_missing_debuginfo_result to ext_lang_missing_file_result. A later commit will add additional Python APIs to handle different types of missing files beyond just debuginfo. This is just a rename commit, there should be no functional changes after this commit. Approved-By: Tom Tromey --- gdb/extension-priv.h | 2 +- gdb/extension.c | 4 ++-- gdb/extension.h | 10 +++++----- gdb/python/python.c | 12 ++++++------ gdb/symfile-debug.c | 2 +- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/gdb/extension-priv.h b/gdb/extension-priv.h index 653fd51e2f1..90810e4bbc8 100644 --- a/gdb/extension-priv.h +++ b/gdb/extension-priv.h @@ -292,7 +292,7 @@ struct extension_language_ops /* Give extension languages a chance to deal with missing debug information. OBJFILE is the file for which GDB was unable to find any debug information. */ - ext_lang_missing_debuginfo_result + ext_lang_missing_file_result (*handle_missing_debuginfo) (const struct extension_language_defn *, struct objfile *objfile); }; diff --git a/gdb/extension.c b/gdb/extension.c index 897bf25dad4..15e1f11d619 100644 --- a/gdb/extension.c +++ b/gdb/extension.c @@ -1053,7 +1053,7 @@ ext_lang_print_insn (struct gdbarch *gdbarch, CORE_ADDR address, /* See extension.h. */ -ext_lang_missing_debuginfo_result +ext_lang_missing_file_result ext_lang_handle_missing_debuginfo (struct objfile *objfile) { for (const struct extension_language_defn *extlang : extension_languages) @@ -1061,7 +1061,7 @@ ext_lang_handle_missing_debuginfo (struct objfile *objfile) if (extlang->ops == nullptr || extlang->ops->handle_missing_debuginfo == nullptr) continue; - ext_lang_missing_debuginfo_result result + ext_lang_missing_file_result result = extlang->ops->handle_missing_debuginfo (extlang, objfile); if (!result.filename ().empty () || result.try_again ()) return result; diff --git a/gdb/extension.h b/gdb/extension.h index 5b0830b6675..2a749161508 100644 --- a/gdb/extension.h +++ b/gdb/extension.h @@ -361,23 +361,23 @@ extern std::optional ext_lang_print_insn it. And the third option is for the extension to just return a null result, indication there is nothing the extension can do to provide the missing debug information. */ -struct ext_lang_missing_debuginfo_result +struct ext_lang_missing_file_result { /* Default result. The extension was unable to provide the missing debug info. */ - ext_lang_missing_debuginfo_result () + ext_lang_missing_file_result () { /* Nothing. */ } /* When TRY_AGAIN is true GDB should try searching again, the extension may have installed the missing debug info into a suitable location. When TRY_AGAIN is false this is equivalent to the default, no argument, constructor. */ - ext_lang_missing_debuginfo_result (bool try_again) + ext_lang_missing_file_result (bool try_again) : m_try_again (try_again) { /* Nothing. */ } /* Look in FILENAME for the missing debug info. */ - ext_lang_missing_debuginfo_result (std::string &&filename) + ext_lang_missing_file_result (std::string &&filename) : m_filename (std::move (filename)) { /* Nothing. */ } @@ -407,7 +407,7 @@ struct ext_lang_missing_debuginfo_result /* Called when GDB failed to find any debug information for OBJFILE. */ -extern ext_lang_missing_debuginfo_result ext_lang_handle_missing_debuginfo +extern ext_lang_missing_file_result ext_lang_handle_missing_debuginfo (struct objfile *objfile); #if GDB_SELF_TEST diff --git a/gdb/python/python.c b/gdb/python/python.c index cc06526ffcf..cceb7aa01c3 100644 --- a/gdb/python/python.c +++ b/gdb/python/python.c @@ -128,7 +128,7 @@ static std::optional gdbpy_colorize (const std::string &filename, const std::string &contents); static std::optional gdbpy_colorize_disasm (const std::string &content, gdbarch *gdbarch); -static ext_lang_missing_debuginfo_result gdbpy_handle_missing_debuginfo +static ext_lang_missing_file_result gdbpy_handle_missing_debuginfo (const struct extension_language_defn *extlang, struct objfile *objfile); /* The interface between gdb proper and loading of python scripts. */ @@ -1755,10 +1755,10 @@ gdbpy_get_current_objfile (PyObject *unused1, PyObject *unused2) /* Implement the 'handle_missing_debuginfo' hook for Python. GDB has failed to find any debug information for OBJFILE. The extension has a chance to record this, or even install the required debug information. - See the description of ext_lang_missing_debuginfo_result in - extension-priv.h for details of the return value. */ + See the description of ext_lang_missing_file_result in extension-priv.h + for details of the return value. */ -static ext_lang_missing_debuginfo_result +static ext_lang_missing_file_result gdbpy_handle_missing_debuginfo (const struct extension_language_defn *extlang, struct objfile *objfile) { @@ -1806,7 +1806,7 @@ gdbpy_handle_missing_debuginfo (const struct extension_language_defn *extlang, if (PyBool_Check (pyo_execute_ret.get ())) { bool try_again = PyObject_IsTrue (pyo_execute_ret.get ()); - return ext_lang_missing_debuginfo_result (try_again); + return ext_lang_missing_file_result (try_again); } if (!gdbpy_is_string (pyo_execute_ret.get ())) @@ -1826,7 +1826,7 @@ gdbpy_handle_missing_debuginfo (const struct extension_language_defn *extlang, return {}; } - return ext_lang_missing_debuginfo_result (std::string (filename.get ())); + return ext_lang_missing_file_result (std::string (filename.get ())); } /* Compute the list of active python type printers and store them in diff --git a/gdb/symfile-debug.c b/gdb/symfile-debug.c index 6bf8dc3cd82..b21a5a433f3 100644 --- a/gdb/symfile-debug.c +++ b/gdb/symfile-debug.c @@ -630,7 +630,7 @@ objfile::find_and_add_separate_symbol_file (symfile_add_flags symfile_flags) the user a system specific message that guides them to finding the missing debug info. */ - ext_lang_missing_debuginfo_result ext_result + ext_lang_missing_file_result ext_result = ext_lang_handle_missing_debuginfo (this); if (!ext_result.filename ().empty ()) { From patchwork Sun Oct 27 09:10:54 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andrew Burgess X-Patchwork-Id: 99673 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 6CC423858403 for ; Sun, 27 Oct 2024 09:13:53 +0000 (GMT) X-Original-To: gdb-patches@sourceware.org Delivered-To: gdb-patches@sourceware.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by sourceware.org (Postfix) with ESMTP id 70EFA385840D for ; Sun, 27 Oct 2024 09:11:13 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 70EFA385840D Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 70EFA385840D Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1730020280; cv=none; b=SXAonV6GxF+/FrFTQxaR45q+O2fKlFdm5KXOM4EZ9gaeFxl450SHOqFkEtsGgia+x3S8KyfGVGZdEVbiJxbS1g8rR8OPXLmkNyTNbPVZO4fuoRHUkUKMKmQo1tjwtM8KCxJx+1Oger9t8OnUVWq6cYs9a9kOJi4/773CliuE5GA= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1730020280; c=relaxed/simple; bh=UgmV8gV3SwokTzQeq2Dr7VzDIxbNM61/iNiBr6EVIbE=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=jirtREurx+M8vndgAb0Xci9fbtAIltGPyo14ZpxgK9VxOHvMIhdlHIrgxrhzxwnzo8DhQKSshaKH1jzWZWkKlJbGV9Kgu6MBjKmhwFHQpTynBuZs+t3zbF1LC/pb1ppc2CWfoxYSZq6TZRSS5GC2uP7czMX+Mqv4yLAI3BZ7YA0= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1730020273; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=bWKRMoDCSR5HBMty5tJzC1SywY0VO0Cbvgx78RkaeIc=; b=YNiq9dpU2IpuA4tga2Ttv6xqI0uw+6f7eP590AF6KtaHvvXie252WGumAvCVqlMsXsiAdz 8JVv4LdGqB11qMWRgbR917z7txIReqbF5YTSi1MlxhxHUmUrAgzqIf5GK6YokVQbVRM/ck 7lErrJK6uMrJ+008KME/tLD01+E/Qkk= Received: from mail-wr1-f71.google.com (mail-wr1-f71.google.com [209.85.221.71]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-591-lTlEpD-jNK2D3-tnVUHbZQ-1; Sun, 27 Oct 2024 05:11:07 -0400 X-MC-Unique: lTlEpD-jNK2D3-tnVUHbZQ-1 Received: by mail-wr1-f71.google.com with SMTP id ffacd0b85a97d-37d5a3afa84so1859270f8f.3 for ; Sun, 27 Oct 2024 02:11:06 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1730020265; x=1730625065; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=bWKRMoDCSR5HBMty5tJzC1SywY0VO0Cbvgx78RkaeIc=; b=bHpDtKqL+DuSj+t+jt9Lc3y+tOjBumgsFHk+xuxITlHvwxMAO64Tuo49yoEJ1cAfSr YqmcHQo2E/aejZO1IJj+aQXVPK2AcExE1654UbAg62zZHWT9zmZ6kocIebujcUytQl3A azkdqo9JlznjMdlYC1sw0HRvcGT4wHkdV6Otu6Y0Tjic3u4GNRPBW3GxvDhLavo7lKOQ 8o26/zCCk7cdo9aoumNVPFxgwqDnZQkSaN6Al8Mn9M9YKPEMjUPdxW7zxKnMUUHIznSZ tWo6dtbjZ3LAgmiW6R1ZnioOyhFj1i7qRl+L89PCk5MZPOrrTKekV5LiMfMO6YUFHpA6 pC4A== X-Gm-Message-State: AOJu0YxCUH2d9Zyup/j1B85/FIFl6HQvOmPAulOh60NnUuziLF3dwYxo FoYEhk7HLqwdSUeSSiLbZL2aRXu3K6/eABQwEsqqd3zTeVjDQfy2Yv6Sv/TCIgLPzZ3zQKZES/F CiG+otJMfXjsf1YyZCIKC0lhusPMhwWYrnbspvJYMqgxaDap6Y6xK12U4l55JysFDwMcOWA/a4+ FcaDIIxkqvDBHC47zauEcj07VlbEtNrR0fLG+vDbF/kPs= X-Received: by 2002:adf:a348:0:b0:37d:4e20:9be0 with SMTP id ffacd0b85a97d-380611f5e23mr3131589f8f.51.1730020265294; Sun, 27 Oct 2024 02:11:05 -0700 (PDT) X-Google-Smtp-Source: AGHT+IEmme7l0Lje8jC+KbI+dFm1FLC6aecquvRddPNm3OT3auM++K+pZ2PzW38uh3oJDGKyPdPoWg== X-Received: by 2002:adf:a348:0:b0:37d:4e20:9be0 with SMTP id ffacd0b85a97d-380611f5e23mr3131566f8f.51.1730020264795; Sun, 27 Oct 2024 02:11:04 -0700 (PDT) Received: from localhost (197.209.200.146.dyn.plus.net. [146.200.209.197]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-43193595baesm69180415e9.17.2024.10.27.02.11.03 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 27 Oct 2024 02:11:03 -0700 (PDT) From: Andrew Burgess To: gdb-patches@sourceware.org Cc: Andrew Burgess , Tom Tromey Subject: [PATCHv2 3/4] gdb: add extension hook ext_lang_find_objfile_from_buildid Date: Sun, 27 Oct 2024 09:10:54 +0000 Message-Id: <2b5bd393b94fdfff7844ecea6e168ee07c46f5af.1730020155.git.aburgess@redhat.com> X-Mailer: git-send-email 2.25.4 In-Reply-To: References: MIME-Version: 1.0 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-12.0 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H2, SPF_HELO_NONE, SPF_NONE, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: gdb-patches@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gdb-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gdb-patches-bounces~patchwork=sourceware.org@sourceware.org Add a new ext_lang_find_objfile_from_buildid function which is called from find_objfile_by_build_id and gives extension languages a chance to find missing objfiles. This commit adds the ext_lang_find_objfile_from_buildid function and the extension_language_ops::find_objfile_from_buildid() hook, but does not implement the hook for any extension languages, that will come in the next commit. This commit does rewrite find_objfile_by_build_id (build-id.c) to call the new hook though. The basic steps of find_objfile_by_build_id are now this: 1. Try to find the missing objfile using the build-id by looking in the debug-file-directory's .build-id/ sub-directory. If we find the file then we're done. 2. Ask debuginfod to download the missing file for us. If we download the file successfully then we're done. 3. Ask the extension language hook to find the file for us. If the extension language asks us to try again then we repeat step (1) only and if we still don't have the file, we move to step (4). If the extension language told us where the file is then we use that file and we're done. 4. We didn't find the file. Carry on without it. Only step (3) is new in this logic, everything else was already done. There are no tests added here as we can't currently write an extension language callback. The next commit will add the tests. Approved-By: Tom Tromey --- gdb/build-id.c | 88 ++++++++++++++++++++++++++++++++------------ gdb/build-id.h | 3 +- gdb/corelow.c | 6 ++- gdb/extension-priv.h | 13 +++++++ gdb/extension.c | 22 +++++++++++ gdb/extension.h | 20 ++++++++++ gdb/solib.c | 3 +- 7 files changed, 128 insertions(+), 27 deletions(-) diff --git a/gdb/build-id.c b/gdb/build-id.c index 6266be1bd6d..9d4b005489d 100644 --- a/gdb/build-id.c +++ b/gdb/build-id.c @@ -28,6 +28,7 @@ #include "cli/cli-style.h" #include "gdbsupport/scoped_fd.h" #include "debuginfod-support.h" +#include "extension.h" /* See build-id.h. */ @@ -343,32 +344,73 @@ find_separate_debug_file_by_buildid (struct objfile *objfile, /* See build-id.h. */ gdb_bfd_ref_ptr -find_objfile_by_build_id (const bfd_build_id *build_id, +find_objfile_by_build_id (program_space *pspace, + const bfd_build_id *build_id, const char *expected_filename) { - /* Try to find the executable (or shared object) by looking for a - (sym)link on disk from the build-id to the object file. */ - gdb_bfd_ref_ptr abfd = build_id_to_exec_bfd (build_id->size, - build_id->data); - - if (abfd != nullptr) - return abfd; - - /* Attempt to query debuginfod for the executable. */ - gdb::unique_xmalloc_ptr path; - scoped_fd fd = debuginfod_exec_query (build_id->data, build_id->size, - expected_filename, &path); - if (fd.get () >= 0) + gdb_bfd_ref_ptr abfd; + + for (unsigned attempt = 0, max_attempts = 1; + attempt < max_attempts && abfd == nullptr; + ++attempt) { - abfd = gdb_bfd_open (path.get (), gnutarget); - - if (abfd == nullptr) - warning (_("\"%ps\" from debuginfod cannot be opened as bfd: %s"), - styled_string (file_name_style.style (), path.get ()), - gdb_bfd_errmsg (bfd_get_error (), nullptr).c_str ()); - else if (!build_id_verify (abfd.get (), build_id->size, - build_id->data)) - abfd = nullptr; + /* Try to find the executable (or shared object) by looking for a + (sym)link on disk from the build-id to the object file. */ + abfd = build_id_to_exec_bfd (build_id->size, build_id->data); + + if (abfd != nullptr || attempt > 0) + break; + + /* Attempt to query debuginfod for the executable. This will only + get run during the first attempt, if an extension language hook + (see below) asked for a second attempt then we will have already + broken out of the loop above. */ + gdb::unique_xmalloc_ptr path; + scoped_fd fd = debuginfod_exec_query (build_id->data, build_id->size, + expected_filename, &path); + if (fd.get () >= 0) + { + abfd = gdb_bfd_open (path.get (), gnutarget); + + if (abfd == nullptr) + warning (_("\"%ps\" from debuginfod cannot be opened as bfd: %s"), + styled_string (file_name_style.style (), path.get ()), + gdb_bfd_errmsg (bfd_get_error (), nullptr).c_str ()); + else if (!build_id_verify (abfd.get (), build_id->size, + build_id->data)) + abfd = nullptr; + } + + if (abfd != nullptr) + break; + + ext_lang_missing_file_result ext_result + = ext_lang_find_objfile_from_buildid (pspace, build_id, + expected_filename); + if (!ext_result.filename ().empty ()) + { + /* The extension identified the file for us. */ + abfd = gdb_bfd_open (ext_result.filename ().c_str (), gnutarget); + if (abfd == nullptr) + { + warning (_("\"%ps\" from extension cannot be opened as bfd: %s"), + styled_string (file_name_style.style (), + ext_result.filename ().c_str ()), + gdb_bfd_errmsg (bfd_get_error (), nullptr).c_str ()); + break; + } + + /* If the extension gave us a path to a file then we always + assume that it is the correct file, we do no additional check + of its build-id. */ + } + else if (ext_result.try_again ()) + { + /* The extension might have installed the file in the expected + location, we should try again. */ + max_attempts = 2; + continue; + } } return abfd; diff --git a/gdb/build-id.h b/gdb/build-id.h index 1d2e7891f34..0174412c5bd 100644 --- a/gdb/build-id.h +++ b/gdb/build-id.h @@ -66,7 +66,8 @@ extern std::string find_separate_debug_file_by_buildid should be the file we were looking for but couldn't find. */ extern gdb_bfd_ref_ptr find_objfile_by_build_id - (const bfd_build_id *build_id, const char *expected_filename); + (struct program_space *pspace, const bfd_build_id *build_id, + const char *expected_filename); /* Return an hex-string representation of BUILD_ID. */ diff --git a/gdb/corelow.c b/gdb/corelow.c index 87ce04d6852..25de755a1dd 100644 --- a/gdb/corelow.c +++ b/gdb/corelow.c @@ -506,7 +506,8 @@ core_target::build_file_mappings () || !bfd_check_format (abfd.get (), bfd_object)) && file_data.build_id != nullptr) { - abfd = find_objfile_by_build_id (file_data.build_id, + abfd = find_objfile_by_build_id (current_program_space, + file_data.build_id, filename.c_str ()); if (abfd != nullptr) @@ -892,7 +893,8 @@ locate_exec_from_corefile_build_id (bfd *abfd, core_target *target, } gdb_bfd_ref_ptr execbfd - = find_objfile_by_build_id (build_id, filename.c_str ()); + = find_objfile_by_build_id (current_program_space, build_id, + filename.c_str ()); if (execbfd != nullptr) { diff --git a/gdb/extension-priv.h b/gdb/extension-priv.h index 90810e4bbc8..a31283f831c 100644 --- a/gdb/extension-priv.h +++ b/gdb/extension-priv.h @@ -295,6 +295,19 @@ struct extension_language_ops ext_lang_missing_file_result (*handle_missing_debuginfo) (const struct extension_language_defn *, struct objfile *objfile); + + /* Give extension languages a chance to deal with missing objfiles. + PSPACE is the program space in which GDB is searching for a missing + objfile, and will not be NULL. BUILD_ID is the build-id of the + objfile we're looking for, and will not be NULL. FILENAME is the name + of the file we're looking for, and will not be NULL. See + ext_lang_find_objfile_from_buildid for some additional information + about the meaning of FILENAME. */ + ext_lang_missing_file_result + (*find_objfile_from_buildid) (const struct extension_language_defn *, + program_space *pspace, + const struct bfd_build_id *build_id, + const char *filename); }; /* State necessary to restore a signal handler to its previous value. */ diff --git a/gdb/extension.c b/gdb/extension.c index 15e1f11d619..ec1aa138872 100644 --- a/gdb/extension.c +++ b/gdb/extension.c @@ -1070,6 +1070,28 @@ ext_lang_handle_missing_debuginfo (struct objfile *objfile) return {}; } +/* See extension.h. */ + +ext_lang_missing_file_result +ext_lang_find_objfile_from_buildid (program_space *pspace, + const struct bfd_build_id *build_id, + const char *filename) +{ + for (const struct extension_language_defn *extlang : extension_languages) + { + if (extlang->ops == nullptr + || extlang->ops->find_objfile_from_buildid == nullptr) + continue; + ext_lang_missing_file_result result + = extlang->ops->find_objfile_from_buildid (extlang, pspace, build_id, + filename); + if (!result.filename ().empty () || result.try_again ()) + return result; + } + + return {}; +} + /* Called via an observer before gdb prints its prompt. Iterate over the extension languages giving them a chance to change the prompt. The first one to change the prompt wins, diff --git a/gdb/extension.h b/gdb/extension.h index 2a749161508..ad3b4e6102d 100644 --- a/gdb/extension.h +++ b/gdb/extension.h @@ -36,6 +36,7 @@ struct ui_file; struct ui_out; struct value; struct value_print_options; +struct program_space; /* A function to load and process a script file. The file has been opened and is ready to be read from the beginning. @@ -410,6 +411,25 @@ struct ext_lang_missing_file_result extern ext_lang_missing_file_result ext_lang_handle_missing_debuginfo (struct objfile *objfile); +/* Called when GDB opens a core-file to find any object files for which a + build-id could be extracted from the core-file, but the matching file + could not otherwise be found by GDB. + + PSPACE is the program space in which GDB is opening the core-file and + is looking for a missing object file. BUILD_ID is the build-id of the + file being looked for, and will not be NULL. FILENAME is the name of + the file GDB is looking for, this will not be NULL. The FILENAME is + provided only for creating helpful messages for the user. FILENAME + might already exist on disk but have the wrong build-id, of FILENAME + might not exist on disk. If the missing objfile can be found then it + does not have to be placed at the location FILENAME. + + The returned object indicates if the file could be found or not. */ + +extern ext_lang_missing_file_result ext_lang_find_objfile_from_buildid + (program_space *pspace, const struct bfd_build_id *build_id, + const char *filename); + #if GDB_SELF_TEST namespace selftests { extern void (*hook_set_active_ext_lang) (); diff --git a/gdb/solib.c b/gdb/solib.c index 5c926def715..dfcee4309bd 100644 --- a/gdb/solib.c +++ b/gdb/solib.c @@ -525,7 +525,8 @@ solib_map_sections (solib &so) abfd = nullptr; if (abfd == nullptr) - abfd = find_objfile_by_build_id (mapped_file_info->build_id (), + abfd = find_objfile_by_build_id (current_program_space, + mapped_file_info->build_id (), so.so_name.c_str ()); if (abfd == nullptr && mismatch) From patchwork Sun Oct 27 09:10:55 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andrew Burgess X-Patchwork-Id: 99672 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id E84913858402 for ; Sun, 27 Oct 2024 09:13:11 +0000 (GMT) X-Original-To: gdb-patches@sourceware.org Delivered-To: gdb-patches@sourceware.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by sourceware.org (Postfix) with ESMTP id 0350B385800F for ; Sun, 27 Oct 2024 09:11:26 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 0350B385800F Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 0350B385800F Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1730020296; cv=none; b=kug7eWOKcTsNVW37wCXMOJ6RRtrBYHQWttYIU+O38lSDsTZbvWUSHgAiA4Dh/fPo8lKdUcLe0+6sz0HhHP7ihEUnTJwTWSNx4sWRjBY7m2XSuoOP0lC1nJrQK267niKZRck3OSfUX1bzfd4KGRKwhnX8nnD8eAZMT80q/jNDyRw= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1730020296; c=relaxed/simple; bh=ZS9Zy5z5f+uoSS/LIHnfS2F21UVvJHISdvOfa0dvEx8=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=SHZf7QvLN77u0AP1JkmHsN2mMUB4c2L0MeET8GpGJf9H7opYfLdg5ni1vnvRnqu4zxuV4sKbTmmH0SAsSz/NdCmsLQ3GjKWd+XElf1nUl1Ep62mrFyaU4lAQWXRdPJr5IplwrQqVwKlqZeWoMlw6Z+3NzRdw1RHu0LoDe3CQYHE= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1730020286; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=8SEwHmcmzpgjkgGlAi4DByvFx8A7PKRQj85hsoBuMrs=; b=bg0ieAZXg7pVU0jK2Up7RNnbkCYuyG0WdMP3wYcc3IHzG6sdspzEDderPXh1La1CHOHeid eZ5eG8lWL6OowcnPvhhYqNEdnH/CYWRYeF67OIvrLSEjW5QoO8X3GnhDbhHX262yGh/hV4 t7UuZG/vgSEXlYp5ubcX4emCY2CWDoc= Received: from mail-wm1-f71.google.com (mail-wm1-f71.google.com [209.85.128.71]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-225-p5hhj7PwP-a5l0j9Tq-2NA-1; Sun, 27 Oct 2024 05:11:11 -0400 X-MC-Unique: p5hhj7PwP-a5l0j9Tq-2NA-1 Received: by mail-wm1-f71.google.com with SMTP id 5b1f17b1804b1-4315dd8fe7fso31035505e9.3 for ; Sun, 27 Oct 2024 02:11:11 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1730020270; x=1730625070; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=8SEwHmcmzpgjkgGlAi4DByvFx8A7PKRQj85hsoBuMrs=; b=A60RB7XgXWIyqBKbHryg+rEkOtGt+P2fHg3ZB+oQzszWUXBv6dqka6SlPMXroFZXDv J+AyWZ0GZaA2qvkDdIvmXswJDDPnBil90gaI5Lp/N8h9PG2yd3rIMvscAtkE81p4QajF k18+N9Ib//4R0l618u3jfxp/WeB2/noDHVb0c6sQkH0S8LRMvcTYjpcEVMuOvKc8uNnT FJcCj6QbvNVDDlSWS0JFXEW1oMF7ueqUq3+Xsy6iyprn6FqqdWKngh+TxnnORSfnAgr9 WXiOx4jW2K4dz88X0wmEG3TFmyryxJZaN1j2WEyl6QM0uDrVwT9AbPIA/aOBGlXMifzr 05CA== X-Gm-Message-State: AOJu0YwMeVOJ6SiZQ5VTVtVQZlMmBpx5r2VtIdJlos3V7cbhDTnb72sw YhDdAjEUoxcN4lcL7dQdwzSw8ZUbOFiQDw9RTG88YCcYq8Fu5QkunUr36kx+iem+OGWE8S5OLEu 0bX4M2FfGWX6dauP/S4s5Fv428V3sIRt2mOBA93tKMDGLxcVrRluioOWrK2u+gv2q6CeJ6P8AKR wvPS51TJXpYe8Y9ApaE9QsYYcOjeXU/PaKroFRry+xADA= X-Received: by 2002:a05:600c:3512:b0:42c:b508:750e with SMTP id 5b1f17b1804b1-4319ac9a785mr42259035e9.11.1730020267701; Sun, 27 Oct 2024 02:11:07 -0700 (PDT) X-Google-Smtp-Source: AGHT+IF80Fb5bMDQBW43Fi0FI/0B/hhzQxnsGAJRu4bYxq9pU7WTaIZKkxnKSmd8sWF5ckJ+Jg/7iQ== X-Received: by 2002:a05:600c:3512:b0:42c:b508:750e with SMTP id 5b1f17b1804b1-4319ac9a785mr42258055e9.11.1730020265939; Sun, 27 Oct 2024 02:11:05 -0700 (PDT) Received: from localhost (197.209.200.146.dyn.plus.net. [146.200.209.197]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-38058b93006sm6268025f8f.93.2024.10.27.02.11.05 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 27 Oct 2024 02:11:05 -0700 (PDT) From: Andrew Burgess To: gdb-patches@sourceware.org Cc: Andrew Burgess , Eli Zaretskii Subject: [PATCHv2 4/4] gdb/python: implement Python find_exec_by_build_id hook Date: Sun, 27 Oct 2024 09:10:55 +0000 Message-Id: <065c0444e7c2a5adaa687e5969030b0ad7793e62.1730020155.git.aburgess@redhat.com> X-Mailer: git-send-email 2.25.4 In-Reply-To: References: MIME-Version: 1.0 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-11.9 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H2, SPF_HELO_NONE, SPF_NONE, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: gdb-patches@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gdb-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gdb-patches-bounces~patchwork=sourceware.org@sourceware.org Implement extension_language_ops::find_objfile_from_buildid within GDB's Python API. Doing this allows users to write Python extensions that can help locate missing objfiles when GDB opens a core file. A handler might perform some project- or site-specific actions to find a missing objfile. Or might provide some project- or site-specific advice to the user on how they can obtain the missing objfile. The implementation is very similar to the approach taken in: commit 8f6c452b5a4e50fbb55ff1d13328b392ad1fd416 Date: Sun Oct 15 22:48:42 2023 +0100 gdb: implement missing debug handler hook for Python The following new commands are added as commands implemented in Python, this is similar to how the Python missing debug and unwinder commands are implemented: info missing-objfile-handlers enable missing-objfile-handler LOCUS HANDLER disable missing-objfile-handler LOCUS HANDLER To make use of this extension hook a user will create missing objfile handler objects, and registers these handlers with GDB. When GDB opens a core file and encounters a missing objfile each handler is called in turn until one is able to help. Here is a minimal handler that does nothing useful: import gdb import gdb.missing_objfile class MyFirstHandler(gdb.missing_objfile.MissingObjfileHandler): def __init__(self): super().__init__("my_first_handler") def __call__(self, pspace, build_id, filename): # This handler does nothing useful. return None gdb.missing_objfile.register_handler(None, MyFirstHandler()) Returning None from the __call__ method tells GDB that this handler was unable to find the missing objfile, and GDB should ask any other registered handlers. Possible return values from a handler: - None: This means the handler couldn't help. GDB will call other registered handlers to see if they can help instead. - False: The handler has done all it can, but the objfile couldn't be found. GDB will not call any other handlers, and will continue without the objfile. - True: The handler has installed the objfile into a location where GDB would normally expect to find it. GDB should repeat its normal lookup process and the objfile should now be found. - A string: The handler can return a filename, which is the missing objfile. GDB will load this file. Handlers can be registered globally, or per program space. GDB checks the handlers for the current program space first, and then all of the global handles. The first handler that returns a value that is not None, has "handled" the missing objfile, at which point GDB continues. The implementation of this feature is mostly straight forward. I have reworked some of the missing debug file related code so that it can be shared with this feature. E.g. gdb/python/lib/gdb/missing_files.py is mostly content moved from gdb/python/lib/gdb/missing_debug.py, but updated to be more generic. Now gdb/python/lib/gdb/missing_debug.py and the new file gdb/python/lib/gdb/missing_objfile.py both call into the missing_files.py file. For gdb/python/lib/gdb/command/missing_files.py this is even more extreme, gdb/python/lib/gdb/command/missing_debug.py is completely gone now and gdb/python/lib/gdb/command/missing_files.py provides all of the new commands in a generic way. I have made one change to the existing Python API, I renamed the attribute Progspace.missing_debug_handlers to Progspace.missing_file_handlers. I don't see this as too problematic. This attribute was only used to implement the missing debug feature and was never documented beyond the fact that it existed. There was no reason for users to be touching this attribute. Reviewed-By: Eli Zaretskii --- gdb/NEWS | 24 + gdb/data-directory/Makefile.in | 4 +- gdb/doc/gdb.texinfo | 1 + gdb/doc/python.texi | 194 ++++++- gdb/python/lib/gdb/__init__.py | 110 +++- .../{missing_debug.py => missing_files.py} | 135 +++-- gdb/python/lib/gdb/missing_debug.py | 161 +----- gdb/python/lib/gdb/missing_files.py | 204 +++++++ gdb/python/lib/gdb/missing_objfile.py | 67 +++ gdb/python/py-progspace.c | 26 +- gdb/python/python.c | 108 +++- .../gdb.python/py-missing-objfile-lib.c | 35 ++ gdb/testsuite/gdb.python/py-missing-objfile.c | 49 ++ .../gdb.python/py-missing-objfile.exp | 544 ++++++++++++++++++ .../gdb.python/py-missing-objfile.py | 158 +++++ 15 files changed, 1585 insertions(+), 235 deletions(-) rename gdb/python/lib/gdb/command/{missing_debug.py => missing_files.py} (54%) create mode 100644 gdb/python/lib/gdb/missing_files.py create mode 100644 gdb/python/lib/gdb/missing_objfile.py create mode 100644 gdb/testsuite/gdb.python/py-missing-objfile-lib.c create mode 100644 gdb/testsuite/gdb.python/py-missing-objfile.c create mode 100644 gdb/testsuite/gdb.python/py-missing-objfile.exp create mode 100644 gdb/testsuite/gdb.python/py-missing-objfile.py diff --git a/gdb/NEWS b/gdb/NEWS index 42b8a88fd8a..9e0e526179d 100644 --- a/gdb/NEWS +++ b/gdb/NEWS @@ -56,6 +56,17 @@ ** Added gdb.record.clear. Clears the trace data of the current recording. This forces re-decoding of the trace for successive commands. + ** New module gdb.missing_objfile that facilitates dealing with + missing objfiles when opening a core-file. + + ** New function gdb.missing_objfile.register_handler that can + register an instance of a sub-class of + gdb.missing_debug.MissingObjfileHandler as a handler for missing + objfiles. + + ** New class gdb.missing_objfile.MissingObjfileHandler which can be + sub-classed to create handlers for missing objfiles. + * Debugger Adapter Protocol changes ** The "scopes" request will now return a scope holding global @@ -83,6 +94,19 @@ maintenance info blocks [ADDRESS] are listed starting at the inner global block out to the most inner block. +info missing-objfile-handlers + List all the registered missing-objfile handlers. + +enable missing-objfile-handler LOCUS HANDLER +disable missing-objfile-handler LOCUS HANDLER + Enable or disable a missing-objfile handler with a name matching the + regular expression HANDLER, in LOCUS. + + LOCUS can be 'global' to operate on global missing-objfile handler, + 'progspace' to operate on handlers within the current program space, + or can be a regular expression which is matched against the filename + of the primary executable in each program space. + * Changed commands remove-symbol-file diff --git a/gdb/data-directory/Makefile.in b/gdb/data-directory/Makefile.in index 1a17d23c132..287dc7ff739 100644 --- a/gdb/data-directory/Makefile.in +++ b/gdb/data-directory/Makefile.in @@ -77,6 +77,8 @@ PYTHON_FILE_LIST = \ gdb/FrameIterator.py \ gdb/frames.py \ gdb/missing_debug.py \ + gdb/missing_objfile.py \ + gdb/missing_files.py \ gdb/printing.py \ gdb/prompt.py \ gdb/ptwrite.py \ @@ -87,7 +89,7 @@ PYTHON_FILE_LIST = \ gdb/command/__init__.py \ gdb/command/explore.py \ gdb/command/frame_filters.py \ - gdb/command/missing_debug.py \ + gdb/command/missing_files.py \ gdb/command/pretty_printers.py \ gdb/command/prompt.py \ gdb/command/type_printers.py \ diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index eb2aff974bb..844e66046c6 100644 --- a/gdb/doc/gdb.texinfo +++ b/gdb/doc/gdb.texinfo @@ -21792,6 +21792,7 @@ @c (eg rooted in val of env var GDBSYMS) could exist for mappable symbol @c files. +@anchor{core-file command} @kindex core-file @item core-file @r{[}@var{filename}@r{]} @itemx core diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi index 22f0e6c6d0a..3a83791aec7 100644 --- a/gdb/doc/python.texi +++ b/gdb/doc/python.texi @@ -231,6 +231,7 @@ * TUI Windows In Python:: Implementing new TUI windows. * Disassembly In Python:: Instruction Disassembly In Python * Missing Debug Info In Python:: Handle missing debug info from Python. +* Missing Objfiles In Python:: Handle objfiles from Python. @end menu @node Basic Python @@ -5388,10 +5389,11 @@ objects. @xref{Frame Filter API}, for more information. @end defvar -@defvar Progspace.missing_debug_handlers -The @code{missing_debug_handlers} attribute is a list of the missing -debug handler objects for this program space. @xref{Missing Debug -Info In Python}, for more information. +@defvar Progspace.missing_file_handlers +The @code{missing_file_handlers} attribute is a list of tuples. Each +tuple holds a missing-file handler object for this program space. For +more information, @pxref{Missing Debug Info In Python}, and +@ref{Missing Objfiles In Python}. @end defvar A program space has the following methods: @@ -5567,6 +5569,7 @@ @code{gdb.Objfile.add_separate_debug_file} method, described below. @end defvar +@anchor{Objfile.build_id} @defvar Objfile.build_id The build ID of the objfile as a string. If the objfile does not have a build ID then the value is @code{None}. @@ -8155,6 +8158,189 @@ for this objfile. @end defun +@node Missing Objfiles In Python +@subsubsection Missing Objfiles In Python +@cindex python, handle missing objfiles + +When @value{GDBN} opens a core file, for example with the +@kbd{core-file} command (@pxref{core-file command}), @value{GDBN} will +attempt to load the corresponding executable and shared libraries. +Often these files can be found on the local machine, but sometimes +these files cannot be found, in which case the debugging experience +will be restricted. + +If @value{GDBN} fails to locate a particular file then there is an +opportunity for a Python extension to step in. A Python extension can +potentially locate the missing file using some platform- or +project-specific steps, and inform @value{GDBN} of its location. Or a +Python extension might provide some platform- or project-specific +advice to the user about how to obtain the missing file. + +A missing-objfile Python extension consists of a handler object which +has the @code{name} and @code{enabled} attributes, and implements the +@code{__call__} method. When @value{GDBN} encounters a situation +where a file cannot be found, but the build-id (@pxref{build ID}) for +the missing file is known, then the @code{__call__} method is invoked +to try and find the file. Full details of how handlers are written +can be found below. + +@subheading The @code{gdb.missing_objfile} Module + +@value{GDBN} comes with a @code{gdb.missing_objfile} module which +contains the following class and global function: + +@deftp{class} gdb.missing_objfile.MissingObjfileHandler + +@code{MissingObjfileHandler} is a base class from which user-created +handlers can derive, though it is not required that handlers derive +from this class, so long as any user created handler has the +@code{name} and @code{enabled} attributes, and implements the +@code{__call__} method. + +@defun MissingObjfileHandler.__init__ (name) +The @var{name} is a string used to reference this missing-objfile +handler within some @value{GDBN} commands. Valid names consist of the +characters @samp{[-_a-zA-Z0-9]}, creating a handler with an invalid +name raises a @code{ValueError} exception. +@end defun + +@defun MissingObjfileHandler.__call__ (pspace, build_id, filename) + +Sub-classes must override the @code{__call__} method. The +@var{pspace} argument will be a @code{gdb.Progspace} +(@pxref{Progspaces In Python}), this is the program space in which +@value{GDBN} is looking for the missing file. + +The @var{build_id} argument is a string containing the build-id of the +file that is missing, this will be in the same format as returned by +@code{Objfile.build_id} (@pxref{Objfile.build_id}). + +The @var{filename} argument contains the name of the file that +@value{GDBN} is looking for. This information is provided to allow +handlers to generate informative messages for the user. A handler is +not required to place the missing file at this location. There might +already be a file present at this location, but it might not match the +required build-id, in which case @value{GDBN} will have ignored it. +In some limited cases @value{GDBN} might not be able to establish the +@var{filename} of the file it is searching for, in this case +@value{GDBN} will use a string @samp{with build-id @var{build_id}} as a +replacement. + +The return value from the @code{__call__} method indicates what +@value{GDBN} should do next. The possible return values are: + +@itemize @bullet +@item @code{None} + +This indicates that this handler could not locate the missing file and +@value{GDBN} should call any other registered handlers. + +@item @code{True} + +This indicates that this handler has installed the missing file into a +location where @value{GDBN} would normally expect to find it. The +only location in which @value{GDBN} will look is within the +@file{.build-id} sub-directory within the @var{debug-file-directory} +(@pxref{debug-file-directory}). + +@value{GDBN} will repeat the normal lookup process, which should now +find the previously missing file. + +If @value{GDBN} still doesn't find file after this second attempt, +then the Python missing-objfile handlers are not invoked a second +time, this prevents a badly behaved handler causing @value{GDBN} to +get stuck in a loop. @value{GDBN} will continue without the missing +file, though this will degrade the debugging experience. + +@item @code{False} + +This indicates that this handler has done everything that it intends +to do but the missing file could not be found. @value{GDBN} will not +call any other registered handlers to look for the missing file. +@value{GDBN} will continue without the missing file, though this will +degrade the debugging experience. + +@item A string + +The returned string should contain a filename. @value{GDBN} will not +call any further registered handlers, and will instead use the +returned filename as the missing file. +@end itemize + +Invoking the @code{__call__} method from this base class will raise a +@code{NotImplementedError} exception. +@end defun + +@defvar MissingObjfileHandler.name +A read-only attribute which is a string, the name of this handler +passed to the @code{__init__} method. +@end defvar + +@defvar MissingObjfileHandler.enabled +A modifiable attribute containing a boolean; when @code{True}, the +handler is enabled, and will be used by @value{GDBN}. When +@code{False}, the handler has been disabled, and will not be used. +@end defvar +@end deftp + +@defun gdb.missing_objfile.register_handler (locus, handler, replace=@code{False}) +Register a new missing-objfile handler with @value{GDBN}. + +@var{handler} is an instance of a sub-class of +@code{MissingObjfileHandler}, or at least an instance of an object that +has the same attributes and methods as @code{MissingObjfileHandler}. + +@var{locus} specifies to which handler list to prepend @var{handler}. +It can be either a @code{gdb.Progspace} (@pxref{Progspaces In Python}) +or @code{None}, in which case the handler is registered globally. The +newly registered @var{handler} will be called before any other handler +from the same locus. Two handlers in the same locus cannot have the +same name, an attempt to add a handler with an already existing name +raises an exception unless @var{replace} is @code{True}, in which case +the old handler is deleted and the new handler is prepended to the +selected handler list. + +@value{GDBN} first calls the handlers for the current program space, +and then the globally registered handlers. As soon as a handler +returns a value other than @code{None}, no further handlers are +called. +@end defun + +@subheading Managing Missing-Objfile Handlers + +@value{GDBN} defines the following commands to manage registered +missing-objfile handlers: + +@table @code + +@kindex info missing-objfile-handlers +@item info missing-objfile-handlers @r{[} @var{locus} @r{[} @var{name-regexp} @r{]} @r{]} +Lists all registered missing-objfile handlers. Arguments @var{locus} +and @var{name-regexp} are both optional and can be used to filter +which handlers are listed. + +The @var{locus} argument should be either @kbd{global}, +@kbd{progspace}, or the name of an object file. Only handlers +registered for the specified locus will be listed. + +The @var{name-regexp} is a regular expression used to match against +handler names. + +@kindex disable missing-objfile-handler +@item disable missing-objfile-handler @r{[} @var{locus} @r{[} @var{name-regexp} @r{]} @r{]} +The @var{locus} and @var{name-regexp} are interpreted as in @kbd{info +missing-objfile-handlers} above, but instead of listing the matching +handlers, all of the matching handlers are disabled. The +@code{enabled} field of each matching handler is set to @code{False}. + +@kindex enable missing-objfile-handler +@item enable missing-objfile-handler @r{[} @var{locus} @r{[} @var{name-regexp} @r{]} @r{]} +The @var{locus} and @var{name-regexp} are interpreted as in @kbd{info +missing-objfile-handlers} above, but instead of listing the matching +handlers, all of the matching handlers are enabled. The +@code{enabled} field of each matching handler is set to @code{True}. +@end table + @node Python Auto-loading @subsection Python Auto-loading @cindex Python auto-loading diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py index 6c3e2419a40..146a963044f 100644 --- a/gdb/python/lib/gdb/__init__.py +++ b/gdb/python/lib/gdb/__init__.py @@ -87,8 +87,9 @@ xmethods = [] frame_filters = {} # Initial frame unwinders. frame_unwinders = [] -# Initial missing debug handlers. -missing_debug_handlers = [] +# The missing file handlers. Each item is a tuple with the form +# (TYPE, HANDLER) where TYPE is a string either 'debug' or 'objfile'. +missing_file_handlers = [] def _execute_unwinders(pending_frame): @@ -271,6 +272,61 @@ class Thread(threading.Thread): super().start() +def _filter_missing_file_handlers(handlers, handler_type): + """Each list of missing file handlers is a list of tuples, the first + item in the tuple is a string either 'debug' or 'objfile' to + indicate what type of handler it is. The second item in the tuple + is the actual handler object. + + This function takes HANDLER_TYPE which is a string, either 'debug' + or 'objfile' and HANDLERS, a list of tuples. The function returns + an iterable over all of the handler objects (extracted from the + tuples) which match HANDLER_TYPE. + """ + + return map(lambda t: t[1], filter(lambda t: t[0] == handler_type, handlers)) + + +def _handle_missing_files(pspace, handler_type, cb): + """Helper for _handle_missing_debuginfo and _handle_missing_objfile. + + Arguments: + pspace: The gdb.Progspace in which we're operating. Used to + lookup program space specific handlers. + handler_type: A string, either 'debug' or 'objfile', this is the + type of handler we're looking for. + cb: A callback which takes a handler and returns the result of + calling the handler. + + Returns: + None: No suitable file could be found. + False: A handler has decided that the requested file cannot be + found, and no further searching should be done. + True: The file has been found and installed in a location + where GDB would normally look for it. GDB should + repeat its lookup process, the file should now be in + place. + A string: This is the filename of where the missing file can + be found. + """ + + for handler in _filter_missing_file_handlers( + pspace.missing_file_handlers, handler_type + ): + if handler.enabled: + result = cb(handler) + if result is not None: + return result + + for handler in _filter_missing_file_handlers(missing_file_handlers, handler_type): + if handler.enabled: + result = cb(handler) + if result is not None: + return result + + return None + + def _handle_missing_debuginfo(objfile): """Internal function called from GDB to execute missing debug handlers. @@ -293,18 +349,46 @@ def _handle_missing_debuginfo(objfile): A string: This is the filename of a file containing the required debug information. """ + pspace = objfile.progspace - for handler in pspace.missing_debug_handlers: - if handler.enabled: - result = handler(objfile) - if result is not None: - return result + return _handle_missing_files(pspace, "debug", lambda h: h(objfile)) - for handler in missing_debug_handlers: - if handler.enabled: - result = handler(objfile) - if result is not None: - return result - return None +def _handle_missing_objfile(pspace, buildid, filename): + """Internal function called from GDB to execute missing objfile + handlers. + + Run each of the currently registered, and enabled missing objfile + handler objects for the gdb.Progspace passed in as an argument, + and then from the global list. Stop after the first handler that + returns a result other than None. + + Arguments: + pspace: A gdb.Progspace for which the missing objfile handlers + should be run. This is the program space in which an + objfile was found to be missing. + buildid: A string containing the build-id we're looking for. + filename: The filename of the file GDB tried to find but + couldn't. This is not where the file should be + placed if found, in fact, this file might already + exist on disk but have the wrong build-id. This is + mostly provided in order to be used in messages to + the user. + + Returns: + None: No objfile could be found for this build-id. + False: A handler has done all it can with for this build-id, + but no objfile could be found. + True: An objfile might have been installed by a handler, GDB + should check again. The only place GDB checks is within + the .build-id sub-directory within the + debug-file-directory. If the required file was not + installed there then GDB will not find it. + A string: This is the filename of a file containing the + missing objfile. + """ + + return _handle_missing_files( + pspace, "objfile", lambda h: h(pspace, buildid, filename) + ) diff --git a/gdb/python/lib/gdb/command/missing_debug.py b/gdb/python/lib/gdb/command/missing_files.py similarity index 54% rename from gdb/python/lib/gdb/command/missing_debug.py rename to gdb/python/lib/gdb/command/missing_files.py index 313b88cf8c3..463853b8cec 100644 --- a/gdb/python/lib/gdb/command/missing_debug.py +++ b/gdb/python/lib/gdb/command/missing_files.py @@ -1,4 +1,4 @@ -# Missing debug related commands. +# Missing debug and objfile related commands. # # Copyright 2023-2024 Free Software Foundation, Inc. # @@ -21,7 +21,7 @@ import gdb def validate_regexp(exp, idstring): - """Compile exp into a compiler regular expression object. + """Compile exp into a compiled regular expression object. Arguments: exp: The string to compile into a re.Pattern object. @@ -33,14 +33,15 @@ def validate_regexp(exp, idstring): Raises: SyntaxError: If exp is an invalid regexp. """ + try: return re.compile(exp) except SyntaxError: raise SyntaxError("Invalid %s regexp: %s." % (idstring, exp)) -def parse_missing_debug_command_args(arg): - """Internal utility to parse missing debug handler command argv. +def parse_missing_file_command_args(arg): + """Internal utility to parse missing file handler command argv. Arguments: arg: The arguments to the command. The format is: @@ -52,6 +53,7 @@ def parse_missing_debug_command_args(arg): Raises: SyntaxError: an error processing ARG """ + argv = gdb.string_to_argv(arg) argc = len(argv) if argc > 2: @@ -68,10 +70,10 @@ def parse_missing_debug_command_args(arg): ) -class InfoMissingDebugHanders(gdb.Command): - """GDB command to list missing debug handlers. +class InfoMissingFileHandlers(gdb.Command): + """GDB command to list missing HTYPE handlers. - Usage: info missing-debug-handlers [LOCUS-REGEXP [NAME-REGEXP]] + Usage: info missing-HTYPE-handlers [LOCUS-REGEXP [NAME-REGEXP]] LOCUS-REGEXP is a regular expression matching the location of the handler. If it is omitted, all registered handlers from all @@ -79,38 +81,47 @@ class InfoMissingDebugHanders(gdb.Command): the handlers from the current progspace, or a regular expression matching filenames of progspaces. - NAME-REGEXP is a regular expression to filter missing debug + NAME-REGEXP is a regular expression to filter missing HTYPE handler names. If this omitted for a specified locus, then all registered handlers in the locus are listed. """ - def __init__(self): - super().__init__("info missing-debug-handlers", gdb.COMMAND_FILES) + def __init__(self, handler_type): + # Update the doc string before calling the parent constructor, + # replacing the string 'HTYPE' with the value of HANDLER_TYPE. + # The parent constructor will grab a copy of this string to + # use as the commands help text. + self.__doc__ = self.__doc__.replace("HTYPE", handler_type) + super().__init__( + "info missing-" + handler_type + "-handlers", gdb.COMMAND_FILES + ) + self.handler_type = handler_type def list_handlers(self, title, handlers, name_re): - """Lists the missing debug handlers whose name matches regexp. + """Lists the missing file handlers whose name matches regexp. Arguments: title: The line to print before the list. - handlers: The list of the missing debug handlers. + handlers: The list of the missing file handlers. name_re: handler name filter. """ + if not handlers: return print(title) - for handler in handlers: + for handler in gdb._filter_missing_file_handlers(handlers, self.handler_type): if name_re.match(handler.name): print( " %s%s" % (handler.name, "" if handler.enabled else " [disabled]") ) def invoke(self, arg, from_tty): - locus_re, name_re = parse_missing_debug_command_args(arg) + locus_re, name_re = parse_missing_file_command_args(arg) if locus_re.match("progspace") and locus_re.pattern != "": cp = gdb.current_progspace() self.list_handlers( - "Progspace %s:" % cp.filename, cp.missing_debug_handlers, name_re + "Progspace %s:" % cp.filename, cp.missing_file_handlers, name_re ) for progspace in gdb.progspaces(): @@ -125,58 +136,71 @@ class InfoMissingDebugHanders(gdb.Command): msg = "Progspace %s:" % filename self.list_handlers( msg, - progspace.missing_debug_handlers, + progspace.missing_file_handlers, name_re, ) # Print global handlers last, as these are invoked last. if locus_re.match("global"): - self.list_handlers("Global:", gdb.missing_debug_handlers, name_re) + self.list_handlers("Global:", gdb.missing_file_handlers, name_re) -def do_enable_handler1(handlers, name_re, flag): - """Enable/disable missing debug handlers whose names match given regex. +def do_enable_handler1(handlers, name_re, flag, handler_type): + """Enable/disable missing file handlers whose names match given regex. Arguments: - handlers: The list of missing debug handlers. + handlers: The list of missing file handlers. name_re: Handler name filter. flag: A boolean indicating if we should enable or disable. + handler_type: A string, either 'debug' or 'objfile', use to control + which handlers are modified. Returns: The number of handlers affected. """ + total = 0 - for handler in handlers: + for handler in gdb._filter_missing_file_handlers(handlers, handler_type): if name_re.match(handler.name) and handler.enabled != flag: handler.enabled = flag total += 1 return total -def do_enable_handler(arg, flag): - """Enable or disable missing debug handlers.""" - (locus_re, name_re) = parse_missing_debug_command_args(arg) +def do_enable_handler(arg, flag, handler_type): + """Enable or disable missing file handlers.""" + + (locus_re, name_re) = parse_missing_file_command_args(arg) total = 0 if locus_re.match("global"): - total += do_enable_handler1(gdb.missing_debug_handlers, name_re, flag) + total += do_enable_handler1( + gdb.missing_file_handlers, name_re, flag, handler_type + ) if locus_re.match("progspace") and locus_re.pattern != "": total += do_enable_handler1( - gdb.current_progspace().missing_debug_handlers, name_re, flag + gdb.current_progspace().missing_file_handlers, name_re, flag, handler_type ) for progspace in gdb.progspaces(): filename = progspace.filename or "" if locus_re.match(filename): - total += do_enable_handler1(progspace.missing_debug_handlers, name_re, flag) + total += do_enable_handler1( + progspace.missing_file_handlers, name_re, flag, handler_type + ) print( - "%d missing debug handler%s %s" - % (total, "" if total == 1 else "s", "enabled" if flag else "disabled") + "%d missing %s handler%s %s" + % ( + total, + handler_type, + "" if total == 1 else "s", + "enabled" if flag else "disabled", + ) ) -class EnableMissingDebugHandler(gdb.Command): - """GDB command to enable missing debug handlers. +class EnableMissingFileHandler(gdb.Command): + """GDB command to enable missing HTYPE handlers. - Usage: enable missing-debug-handler [LOCUS-REGEXP [NAME-REGEXP]] + Usage: enable missing-HTYPE-handler [LOCUS-REGEXP [NAME-REGEXP]] LOCUS-REGEXP is a regular expression specifying the handlers to enable. It can be 'global', 'progspace' for the current @@ -187,18 +211,26 @@ class EnableMissingDebugHandler(gdb.Command): in the locus are affected. """ - def __init__(self): - super().__init__("enable missing-debug-handler", gdb.COMMAND_FILES) + def __init__(self, handler_type): + # Update the doc string before calling the parent constructor, + # replacing the string 'HTYPE' with the value of HANDLER_TYPE. + # The parent constructor will grab a copy of this string to + # use as the commands help text. + self.__doc__ = self.__doc__.replace("HTYPE", handler_type) + super().__init__( + "enable missing-" + handler_type + "-handler", gdb.COMMAND_FILES + ) + self.handler_type = handler_type def invoke(self, arg, from_tty): """GDB calls this to perform the command.""" - do_enable_handler(arg, True) + do_enable_handler(arg, True, self.handler_type) -class DisableMissingDebugHandler(gdb.Command): - """GDB command to disable missing debug handlers. +class DisableMissingFileHandler(gdb.Command): + """GDB command to disable missing HTYPE handlers. - Usage: disable missing-debug-handler [LOCUS-REGEXP [NAME-REGEXP]] + Usage: disable missing-HTYPE-handler [LOCUS-REGEXP [NAME-REGEXP]] LOCUS-REGEXP is a regular expression specifying the handlers to enable. It can be 'global', 'progspace' for the current @@ -209,19 +241,28 @@ class DisableMissingDebugHandler(gdb.Command): in the locus are affected. """ - def __init__(self): - super().__init__("disable missing-debug-handler", gdb.COMMAND_FILES) + def __init__(self, handler_type): + # Update the doc string before calling the parent constructor, + # replacing the string 'HTYPE' with the value of HANDLER_TYPE. + # The parent constructor will grab a copy of this string to + # use as the commands help text. + self.__doc__ = self.__doc__.replace("HTYPE", handler_type) + super().__init__( + "disable missing-" + handler_type + "-handler", gdb.COMMAND_FILES + ) + self.handler_type = handler_type def invoke(self, arg, from_tty): """GDB calls this to perform the command.""" - do_enable_handler(arg, False) + do_enable_handler(arg, False, self.handler_type) -def register_missing_debug_handler_commands(): - """Installs the missing debug handler commands.""" - InfoMissingDebugHanders() - EnableMissingDebugHandler() - DisableMissingDebugHandler() +def register_missing_file_handler_commands(): + """Installs the missing file handler commands.""" + for handler_type in ["debug", "objfile"]: + InfoMissingFileHandlers(handler_type) + EnableMissingFileHandler(handler_type) + DisableMissingFileHandler(handler_type) -register_missing_debug_handler_commands() +register_missing_file_handler_commands() diff --git a/gdb/python/lib/gdb/missing_debug.py b/gdb/python/lib/gdb/missing_debug.py index 7ccc4fe496d..2c2cebacaf8 100644 --- a/gdb/python/lib/gdb/missing_debug.py +++ b/gdb/python/lib/gdb/missing_debug.py @@ -17,72 +17,11 @@ MissingDebugHandler base class, and register_handler function. """ -import sys - import gdb +from gdb.missing_files import MissingFileHandler -if sys.version_info >= (3, 7): - # Functions str.isascii() and str.isalnum are available starting Python - # 3.7. - def isascii(ch): - return ch.isascii() - - def isalnum(ch): - return ch.isalnum() - -else: - # Older version of Python doesn't have str.isascii() and - # str.isalnum() so provide our own. - # - # We could import isalnum() and isascii() from the curses library, - # but that adds an extra dependency. Given these functions are - # both small and trivial lets implement them here. - # - # These definitions are based on those in the curses library, but - # simplified as we know C will always be a single character 'str'. - - def isdigit(c): - return 48 <= ord(c) <= 57 - - def islower(c): - return 97 <= ord(c) <= 122 - - def isupper(c): - return 65 <= ord(c) <= 90 - - def isalpha(c): - return isupper(c) or islower(c) - - def isalnum(c): - return isalpha(c) or isdigit(c) - - def isascii(c): - return 0 <= ord(c) <= 127 - -def _validate_name(name): - """Validate a missing debug handler name string. - - If name is valid as a missing debug handler name, then this - function does nothing. If name is not valid then an exception is - raised. - - Arguments: - name: A string, the name of a missing debug handler. - - Returns: - Nothing. - - Raises: - ValueError: If name is invalid as a missing debug handler - name. - """ - for ch in name: - if not isascii(ch) or not (isalnum(ch) or ch in "_-"): - raise ValueError("invalid character '%s' in handler name: %s" % (ch, name)) - - -class MissingDebugHandler(object): +class MissingDebugHandler(MissingFileHandler): """Base class for missing debug handlers written in Python. A missing debug handler has a single method __call__ along with @@ -93,41 +32,8 @@ class MissingDebugHandler(object): enabled: When true this handler is enabled. """ - def __init__(self, name): - """Constructor. - - Args: - name: An identifying name for this handler. - - Raises: - TypeError: name is not a string. - ValueError: name contains invalid characters. - """ - - if not isinstance(name, str): - raise TypeError("incorrect type for name: %s" % type(name)) - - _validate_name(name) - - self._name = name - self._enabled = True - - @property - def name(self): - return self._name - - @property - def enabled(self): - return self._enabled - - @enabled.setter - def enabled(self, value): - if not isinstance(value, bool): - raise TypeError("incorrect type for enabled attribute: %s" % type(value)) - self._enabled = value - def __call__(self, objfile): - """GDB handle missing debug information for an objfile. + """Handle missing debug information for an objfile. Arguments: objfile: A gdb.Objfile for which GDB could not find any @@ -148,62 +54,5 @@ class MissingDebugHandler(object): def register_handler(locus, handler, replace=False): - """Register handler in given locus. - - The handler is prepended to the locus's missing debug handlers - list. The name of handler should be unique (or replace must be - True). - - Arguments: - locus: Either a progspace, or None (in which case the unwinder - is registered globally). - handler: An object of a gdb.MissingDebugHandler subclass. - - replace: If True, replaces existing handler with the same name - within locus. Otherwise, raises RuntimeException if - unwinder with the same name already exists. - - Returns: - Nothing. - - Raises: - RuntimeError: The name of handler is not unique. - TypeError: Bad locus type. - AttributeError: Required attributes of handler are missing. - """ - - if locus is None: - if gdb.parameter("verbose"): - gdb.write("Registering global %s handler ...\n" % handler.name) - locus = gdb - elif isinstance(locus, gdb.Progspace): - if gdb.parameter("verbose"): - gdb.write( - "Registering %s handler for %s ...\n" % (handler.name, locus.filename) - ) - else: - raise TypeError("locus should be gdb.Progspace or None") - - # Some sanity checks on HANDLER. Calling getattr will raise an - # exception if the attribute doesn't exist, which is what we want. - # These checks are not exhaustive; we don't check the attributes - # have the correct types, or the method has the correct signature, - # but this should catch some basic mistakes. - getattr(handler, "name") - getattr(handler, "enabled") - call_method = getattr(handler, "__call__") - if not callable(call_method): - raise AttributeError( - "'%s' object's '__call__' attribute is not callable" - % type(handler).__name__ - ) - - i = 0 - for needle in locus.missing_debug_handlers: - if needle.name == handler.name: - if replace: - del locus.missing_debug_handlers[i] - else: - raise RuntimeError("Handler %s already exists." % handler.name) - i += 1 - locus.missing_debug_handlers.insert(0, handler) + """See gdb.missing_files.register_handler.""" + gdb.missing_files.register_handler("debug", locus, handler, replace) diff --git a/gdb/python/lib/gdb/missing_files.py b/gdb/python/lib/gdb/missing_files.py new file mode 100644 index 00000000000..5f2df88c728 --- /dev/null +++ b/gdb/python/lib/gdb/missing_files.py @@ -0,0 +1,204 @@ +# Copyright (C) 2023-2024 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +""" +MissingFileHandler base class, and support functions used by the +missing_debug.py and missing_objfile.py modules. +""" + +import sys + +import gdb + +if sys.version_info >= (3, 7): + # Functions str.isascii() and str.isalnum are available starting Python + # 3.7. + def isascii(ch): + return ch.isascii() + + def isalnum(ch): + return ch.isalnum() + +else: + # Older version of Python doesn't have str.isascii() and + # str.isalnum() so provide our own. + # + # We could import isalnum() and isascii() from the curses library, + # but that adds an extra dependency. Given these functions are + # both small and trivial lets implement them here. + # + # These definitions are based on those in the curses library, but + # simplified as we know C will always be a single character 'str'. + + def isdigit(c): + return 48 <= ord(c) <= 57 + + def islower(c): + return 97 <= ord(c) <= 122 + + def isupper(c): + return 65 <= ord(c) <= 90 + + def isalpha(c): + return isupper(c) or islower(c) + + def isalnum(c): + return isalpha(c) or isdigit(c) + + def isascii(c): + return 0 <= ord(c) <= 127 + + +def _validate_name(name): + """Validate a missing file handler name string. + + If name is valid as a missing file handler name, then this + function does nothing. If name is not valid then an exception is + raised. + + Arguments: + name: A string, the name of a missing file handler. + + Returns: + Nothing. + + Raises: + ValueError: If name is invalid as a missing file handler + name. + """ + + for ch in name: + if not isascii(ch) or not (isalnum(ch) or ch in "_-"): + raise ValueError("invalid character '%s' in handler name: %s" % (ch, name)) + + +class MissingFileHandler(object): + """Base class for missing file handlers written in Python. + + A missing file handler has a single method __call__ along with the + read/write attribute enabled, and a read-only attribute name. The + attributes are provided by this class while the __call__ method is + provided by a sub-class. Each sub-classes __call__ method will + have a different signature. + + Attributes: + name: Read-only attribute, the name of this handler. + enabled: When true this handler is enabled. + """ + + def __init__(self, name): + """Constructor. + + Args: + name: An identifying name for this handler. + + Raises: + TypeError: name is not a string. + ValueError: name contains invalid characters. + """ + + if not isinstance(name, str): + raise TypeError("incorrect type for name: %s" % type(name)) + + _validate_name(name) + + self._name = name + self._enabled = True + + @property + def name(self): + return self._name + + @property + def enabled(self): + return self._enabled + + @enabled.setter + def enabled(self, value): + if not isinstance(value, bool): + raise TypeError("incorrect type for enabled attribute: %s" % type(value)) + self._enabled = value + + +def register_handler(handler_type, locus, handler, replace=False): + """Register handler in given locus. + + The handler is prepended to the locus's missing file handlers + list. The name of handler should be unique (or replace must be + True), and the name must pass the _validate_name check. + + Arguments: + handler_type: A string, either 'debug' or 'objfile' indicating the + type of handler to be registered. + locus: Either a progspace, or None (in which case the unwinder + is registered globally). + handler: An object used as a missing file handler. Usually a + sub-class of MissingFileHandler. + replace: If True, replaces existing handler with the same name + within locus. Otherwise, raises RuntimeException if + unwinder with the same name already exists. + + Returns: + Nothing. + + Raises: + RuntimeError: The name of handler is not unique. + TypeError: Bad locus type. + AttributeError: Required attributes of handler are missing. + ValueError: If the name of the handler is invalid, or if + handler_type is neither 'debug' or 'objfile'. + """ + + if handler_type != "debug" and handler_type != "objfile": + raise ValueError("handler_type must be 'debug' or 'objfile'") + + if locus is None: + if gdb.parameter("verbose"): + gdb.write("Registering global %s handler ...\n" % handler.name) + locus = gdb + elif isinstance(locus, gdb.Progspace): + if gdb.parameter("verbose"): + gdb.write( + "Registering %s handler for %s ...\n" % (handler.name, locus.filename) + ) + else: + raise TypeError("locus should be gdb.Progspace or None") + + # Some sanity checks on HANDLER. Calling getattr will raise an + # exception if the attribute doesn't exist, which is what we want. + # These checks are not exhaustive; we don't check the attributes + # have the correct types, or the method has the correct signature, + # but this should catch some basic mistakes. + name = getattr(handler, "name") + _validate_name(name) + + getattr(handler, "enabled") + + call_method = getattr(handler, "__call__") + if not callable(call_method): + raise AttributeError( + "'%s' object's '__call__' attribute is not callable" + % type(handler).__name__ + ) + + i = 0 + for needle in locus.missing_file_handlers: + if needle[0] == handler_type and needle[1].name == handler.name: + if replace: + del locus.missing_file_handlers[i] + else: + raise RuntimeError("Handler %s already exists." % handler.name) + i += 1 + locus.missing_file_handlers.insert(0, (handler_type, handler)) diff --git a/gdb/python/lib/gdb/missing_objfile.py b/gdb/python/lib/gdb/missing_objfile.py new file mode 100644 index 00000000000..ace0e1315b5 --- /dev/null +++ b/gdb/python/lib/gdb/missing_objfile.py @@ -0,0 +1,67 @@ +# Copyright (C) 2024 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +""" +MissingObjfileHandler base class, and register_handler function. +""" + +import gdb +from gdb.missing_files import MissingFileHandler + + +class MissingObjfileHandler(MissingFileHandler): + """Base class for missing objfile handlers written in Python. + + A missing objfile handler has a single method __call__ along with + the read/write attribute enabled, and a read-only attribute name. + + Attributes: + name: Read-only attribute, the name of this handler. + enabled: When true this handler is enabled. + """ + + def __call__(self, buildid, filename): + """Handle a missing objfile when GDB can knows the build-id. + + Arguments: + + buildid: A string containing the build-id for the objfile + GDB is searching for. + filename: A string containing the name of the file GDB is + searching for. This is provided only for the purpose + of creating diagnostic messages. If the file is found + it does not have to be placed here, and this file + might already exist but GDB has determined it is not + suitable for use, e.g. if the build-id doesn't match. + + Returns: + + True: GDB should try again to locate the missing objfile, + the handler may have installed the missing file. + False: GDB should move on without the objfile. The + handler has determined that this objfile is not + available. + A string: GDB should load the file at the given path; it + contains the requested objfile. + None: This handler can't help with this objfile. GDB + should try any other registered handlers. + + """ + raise NotImplementedError("MissingObjfileHandler.__call__()") + + +def register_handler(locus, handler, replace=False): + """See gdb.missing_files.register_handler.""" + gdb.missing_files.register_handler("objfile", locus, handler, replace) diff --git a/gdb/python/py-progspace.c b/gdb/python/py-progspace.c index aa1e713cc53..bb44aa42567 100644 --- a/gdb/python/py-progspace.c +++ b/gdb/python/py-progspace.c @@ -55,8 +55,8 @@ struct pspace_object /* The debug method list. */ PyObject *xmethods; - /* The missing debug handler list. */ - PyObject *missing_debug_handlers; + /* The missing file handler list. */ + PyObject *missing_file_handlers; }; extern PyTypeObject pspace_object_type @@ -166,7 +166,7 @@ pspy_dealloc (PyObject *self) Py_XDECREF (ps_self->frame_unwinders); Py_XDECREF (ps_self->type_printers); Py_XDECREF (ps_self->xmethods); - Py_XDECREF (ps_self->missing_debug_handlers); + Py_XDECREF (ps_self->missing_file_handlers); Py_TYPE (self)->tp_free (self); } @@ -202,8 +202,8 @@ pspy_initialize (pspace_object *self) if (self->xmethods == NULL) return 0; - self->missing_debug_handlers = PyList_New (0); - if (self->missing_debug_handlers == nullptr) + self->missing_file_handlers = PyList_New (0); + if (self->missing_file_handlers == nullptr) return 0; return 1; @@ -349,18 +349,18 @@ pspy_get_xmethods (PyObject *o, void *ignore) /* Return the list of missing debug handlers for this program space. */ static PyObject * -pspy_get_missing_debug_handlers (PyObject *o, void *ignore) +pspy_get_missing_file_handlers (PyObject *o, void *ignore) { pspace_object *self = (pspace_object *) o; - Py_INCREF (self->missing_debug_handlers); - return self->missing_debug_handlers; + Py_INCREF (self->missing_file_handlers); + return self->missing_file_handlers; } /* Set this program space's list of missing debug handlers to HANDLERS. */ static int -pspy_set_missing_debug_handlers (PyObject *o, PyObject *handlers, +pspy_set_missing_file_handlers (PyObject *o, PyObject *handlers, void *ignore) { pspace_object *self = (pspace_object *) o; @@ -380,9 +380,9 @@ pspy_set_missing_debug_handlers (PyObject *o, PyObject *handlers, } /* Take care in case the LHS and RHS are related somehow. */ - gdbpy_ref<> tmp (self->missing_debug_handlers); + gdbpy_ref<> tmp (self->missing_file_handlers); Py_INCREF (handlers); - self->missing_debug_handlers = handlers; + self->missing_file_handlers = handlers; return 0; } @@ -778,8 +778,8 @@ static gdb_PyGetSetDef pspace_getset[] = "Type printers.", NULL }, { "xmethods", pspy_get_xmethods, NULL, "Debug methods.", NULL }, - { "missing_debug_handlers", pspy_get_missing_debug_handlers, - pspy_set_missing_debug_handlers, "Missing debug handlers.", NULL }, + { "missing_file_handlers", pspy_get_missing_file_handlers, + pspy_set_missing_file_handlers, "Missing file handlers.", NULL }, { NULL } }; diff --git a/gdb/python/python.c b/gdb/python/python.c index cceb7aa01c3..b0de48d4e1a 100644 --- a/gdb/python/python.c +++ b/gdb/python/python.c @@ -35,6 +35,7 @@ #include "location.h" #include "run-on-main-thread.h" #include "observable.h" +#include "build-id.h" #if GDB_SELF_TEST #include "gdbsupport/selftest.h" @@ -130,6 +131,9 @@ static std::optional gdbpy_colorize_disasm (const std::string &content, gdbarch *gdbarch); static ext_lang_missing_file_result gdbpy_handle_missing_debuginfo (const struct extension_language_defn *extlang, struct objfile *objfile); +static ext_lang_missing_file_result gdbpy_find_objfile_from_buildid + (const struct extension_language_defn *extlang, program_space *pspace, + const struct bfd_build_id *build_id, const char *missing_filename); /* The interface between gdb proper and loading of python scripts. */ @@ -179,7 +183,8 @@ static const struct extension_language_ops python_extension_ops = gdbpy_print_insn, - gdbpy_handle_missing_debuginfo + gdbpy_handle_missing_debuginfo, + gdbpy_find_objfile_from_buildid }; #endif /* HAVE_PYTHON */ @@ -1829,6 +1834,107 @@ gdbpy_handle_missing_debuginfo (const struct extension_language_defn *extlang, return ext_lang_missing_file_result (std::string (filename.get ())); } +/* Implement the find_objfile_from_buildid hook for Python. PSPACE is the + program space in which GDB is trying to find an objfile, BUILD_ID is the + build-id for the missing objfile, and EXPECTED_FILENAME is a non-NULL + string which can be used (if needed) in messages to the user, and + represents the file GDB is looking for. */ + +static ext_lang_missing_file_result +gdbpy_find_objfile_from_buildid (const struct extension_language_defn *extlang, + program_space *pspace, + const struct bfd_build_id *build_id, + const char *missing_filename) +{ + gdb_assert (pspace != nullptr); + gdb_assert (build_id != nullptr); + gdb_assert (missing_filename != nullptr); + + /* Early exit if Python is not initialised. */ + if (!gdb_python_initialized || gdb_python_module == nullptr) + return {}; + + gdbpy_enter enter_py; + + /* Convert BUILD_ID into a Python object. */ + std::string hex_form = bin2hex (build_id->data, build_id->size); + gdbpy_ref<> pyo_buildid = host_string_to_python_string (hex_form.c_str ()); + if (pyo_buildid == nullptr) + { + gdbpy_print_stack (); + return {}; + } + + /* Convert MISSING_FILENAME to a Python object. */ + gdbpy_ref<> pyo_filename = host_string_to_python_string (missing_filename); + if (pyo_filename == nullptr) + { + gdbpy_print_stack (); + return {}; + } + + /* Convert PSPACE to a Python object. */ + gdbpy_ref<> pyo_pspace = pspace_to_pspace_object (pspace); + if (pyo_pspace == nullptr) + { + gdbpy_print_stack (); + return {}; + } + + /* Lookup the helper function within the GDB module. */ + gdbpy_ref<> pyo_handler + (PyObject_GetAttrString (gdb_python_module, "_handle_missing_objfile")); + if (pyo_handler == nullptr) + { + gdbpy_print_stack (); + return {}; + } + + /* Call the function, passing in the Python objfile object. */ + gdbpy_ref<> pyo_execute_ret + (PyObject_CallFunctionObjArgs (pyo_handler.get (), pyo_pspace.get (), + pyo_buildid.get (), pyo_filename.get (), + nullptr)); + if (pyo_execute_ret == nullptr) + { + /* If the handler is cancelled due to a Ctrl-C, then propagate + the Ctrl-C as a GDB exception instead of swallowing it. */ + gdbpy_print_stack_or_quit (); + return {}; + } + + /* Parse the result, and convert it back to the C++ object. */ + if (pyo_execute_ret == Py_None) + return {}; + + if (PyBool_Check (pyo_execute_ret.get ())) + { + /* We know the value is a bool, so it must be either Py_True or + Py_False. Anything else would not get past the above check. */ + bool try_again = pyo_execute_ret.get () == Py_True; + return ext_lang_missing_file_result (try_again); + } + + if (!gdbpy_is_string (pyo_execute_ret.get ())) + { + PyErr_SetString (PyExc_ValueError, + "return value from _find_objfile_by_buildid should " + "be None, a bool, or a str"); + gdbpy_print_stack (); + return {}; + } + + gdb::unique_xmalloc_ptr filename + = python_string_to_host_string (pyo_execute_ret.get ()); + if (filename == nullptr) + { + gdbpy_print_stack (); + return {}; + } + + return ext_lang_missing_file_result (std::string (filename.get ())); +} + /* Compute the list of active python type printers and store them in EXT_PRINTERS->py_type_printers. The product of this function is used by gdbpy_apply_type_printers, and freed by gdbpy_free_type_printers. diff --git a/gdb/testsuite/gdb.python/py-missing-objfile-lib.c b/gdb/testsuite/gdb.python/py-missing-objfile-lib.c new file mode 100644 index 00000000000..8d740b4bb71 --- /dev/null +++ b/gdb/testsuite/gdb.python/py-missing-objfile-lib.c @@ -0,0 +1,35 @@ +/* This test program is part of GDB, the GNU debugger. + + Copyright 2024 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +struct lib_type +{ + int a; + int b; +}; + +volatile struct lib_type global_lib_var = { 0, 0 }; + +int +foo (void) +{ + int res = 0; + + res += global_lib_var.a; + res += global_lib_var.b; + + return 0; +} diff --git a/gdb/testsuite/gdb.python/py-missing-objfile.c b/gdb/testsuite/gdb.python/py-missing-objfile.c new file mode 100644 index 00000000000..953e1c0fb5b --- /dev/null +++ b/gdb/testsuite/gdb.python/py-missing-objfile.c @@ -0,0 +1,49 @@ +/* This test program is part of GDB, the GNU debugger. + + Copyright 2024 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#include + +struct exec_type +{ + int a; + int b; + int c; +}; + +volatile struct exec_type global_exec_var = { 0, 0, 0 }; + +extern int foo (void); + +void +dump_core (void) +{ + abort (); +} + +int +main (void) +{ + int res = foo (); + + res += global_exec_var.a; + res += global_exec_var.b; + res += global_exec_var.c; + + dump_core (); + + return res; +} diff --git a/gdb/testsuite/gdb.python/py-missing-objfile.exp b/gdb/testsuite/gdb.python/py-missing-objfile.exp new file mode 100644 index 00000000000..51027c9ca80 --- /dev/null +++ b/gdb/testsuite/gdb.python/py-missing-objfile.exp @@ -0,0 +1,544 @@ +# Copyright (C) 2024 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +load_lib gdb-python.exp + +require allow_python_tests + +standard_testfile .c -lib.c + +# Build the library. +set libname ${testfile}-lib +set libfile [standard_output_file $libname] +if { [build_executable "build shlib" $libfile $srcfile2 \ + {debug shlib build-id}] == -1} { + return +} + +# Build the executable. +set opts [list debug build-id shlib=${libfile}] +if { [build_executable "build exec" $binfile $srcfile $opts] == -1} { + return +} + +set remote_python_file \ + [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py] + +# Generate a core file. +set corefile [core_find $binfile {}] +if {$corefile == ""} { + unsupport "core file not generated" + return 0 +} + +# Create a directory named DIRNAME for use as the +# debug-file-directory. Populate the directory with links (based on +# the build-ids) to each file in the list FILES. +# +# Return the full filename of DIRNAME on the host. +proc setup_debugdir { dirname files } { + set debugdir [host_standard_output_file $dirname] + + # Create basic empty directory structure (in case FILES is empty). + remote_exec host "mkdir -p $debugdir/.build-id/" + + foreach file $files { + set build_id_filename [build_id_debug_filename_get $file ""] + + remote_exec host "mkdir -p $debugdir/[file dirname $build_id_filename]" + remote_exec host "ln -s $file $debugdir/$build_id_filename" + } + + return $debugdir +} + +# Query some symbols in the inferior to see if GDB managed to find the +# executable (when EXEC_LOADED is true) and/or the library (when LIB_LOADED +# is true). +proc check_loaded_debug { exec_loaded lib_loaded } { + if { $exec_loaded } { + gdb_test "whatis global_exec_var" "^type = volatile struct exec_type" + + if { $lib_loaded } { + gdb_test "whatis global_lib_var" "^type = volatile struct lib_type" + } else { + gdb_test "whatis global_lib_var" \ + "^No symbol \"global_lib_var\" in current context\\." + } + } else { + gdb_test "whatis global_exec_var" \ + "^No symbol table is loaded\\. Use the \"file\" command\\." + gdb_test "whatis global_lib_var" \ + "^No symbol table is loaded\\. Use the \"file\" command\\." + } +} + +# Load the global corefile. The EXTRA_RE is checked for prior to GDB +# announcing that the core-file has been loaded. +proc load_core_file { {extra_re ".*"} } { + gdb_test "core-file $::corefile" \ + [multi_line \ + "$extra_re" \ + "Core was generated by \[^\r\n\]+" \ + "Program terminated with signal SIGABRT, Aborted\\." \ + "\[^\r\n\]+(?:\r\n\[^\r\n\]+)?"] \ + "loaded the core file" +} + +# Set the debug-file-directory to DIRNAME. +proc set_debug_file_dir { dirname } { + gdb_test_no_output "set debug-file-directory $dirname" \ + "set debug-file-directory" +} + +# Restart GDB and load the support Python script. +proc clean_restart_load_python {} { + clean_restart + gdb_test "source $::remote_python_file" "^Success" \ + "load python script" +} + +# For sanity, lets check that we can load the specify the executable +# and then load the core-file the easy way. +with_test_prefix "initial sanity check" { + clean_restart $binfile + load_core_file + check_loaded_debug true true +} + +# Move the executable and library into a location that the core-file +# can't possibly know about. After this the only way GDB can track +# down these files will be by looking in the debug-file-directory. +set hidden_dir [host_standard_output_file "hidden"] +set hidden_binfile "$hidden_dir/$testfile" +set hidden_libfile "$hidden_dir/$libname" +remote_exec host "mkdir -p $hidden_dir" +remote_exec host "mv $libfile $hidden_libfile" +remote_exec host "mv $binfile $hidden_binfile" + +with_test_prefix "no objfiles, no debug-file-directory" { + clean_restart + load_core_file + check_loaded_debug false false +} + +# Setup some debug-file-directories. +set debugdir_no_lib \ + [setup_debugdir "debugdir.no-lib" [list "$hidden_binfile"]] +set debugdir_empty \ + [setup_debugdir "debugdir.empty" {}] +set debugdir_all \ + [setup_debugdir "debugdir.all" [list "$hidden_libfile" \ + "$hidden_binfile"]] + +with_test_prefix "no objfiles available" { + # Another sanity check that GDB can find the files via the + # debug-file-directory. + clean_restart + set_debug_file_dir $debugdir_empty + load_core_file + check_loaded_debug false false +} + +with_test_prefix "all objfiles available" { + # Another sanity check that GDB can find the files via the + # debug-file-directory. + set_debug_file_dir $debugdir_all + load_core_file + check_loaded_debug true true +} + +with_test_prefix "lib objfile missing" { + # Another sanity check that GDB can find the files via the + # debug-file-directory. + set_debug_file_dir $debugdir_no_lib + load_core_file + check_loaded_debug true false +} + +with_test_prefix "all objfiles missing, handler returns None" { + clean_restart_load_python + gdb_test_no_output \ + "python gdb.missing_objfile.register_handler(None, handler_obj)" \ + "register initial handler" + load_core_file + + check_loaded_debug false false + + # The handler should be called three times, once for the + # mapped-file, once for the core-file's exec, and once for the + # shared library. + gdb_test "python print(handler_obj.call_count)" "^3" \ + "check handler was called three times" +} + +with_test_prefix "lib objfile missing, handler returns None" { + # Reset handler_obj. + gdb_test_no_output "python handler_obj.set_mode(Mode.RETURN_NONE)" + + set_debug_file_dir $debugdir_no_lib + load_core_file + check_loaded_debug true false + + # The handler will be called twice, once when GDB tries to + # load the shared library during the memory-mapped file phase, + # then again for the shared library loading. + gdb_test "python print(handler_obj.call_count)" "^2" \ + "check handler was called three times" +} + +with_test_prefix "handler installs lib objfile" { + set build_id_filename [build_id_debug_filename_get \ + $hidden_libfile ""] + remote_exec host \ + "mkdir -p $debugdir_no_lib/[file dirname $build_id_filename]" + gdb_test_no_output "python handler_obj.set_mode(Mode.RETURN_TRUE, \ + \"$hidden_libfile\", \"$debugdir_no_lib/$build_id_filename\")" \ + "configure handler" + + load_core_file + check_loaded_debug true true + + # Cleanup so the test can be reproduced again later if needed. + remote_exec host "rm $debugdir_no_lib/$build_id_filename" +} + +with_test_prefix "handler points to lib objfile" { + set build_id_filename [build_id_debug_filename_get \ + $hidden_libfile ""] + remote_exec host \ + "mkdir -p $debugdir_no_lib/[file dirname $build_id_filename]" + gdb_test_no_output "python handler_obj.set_mode(Mode.RETURN_STRING, \ + \"$hidden_libfile\")" \ + "configure handler" + + load_core_file + check_loaded_debug true true + + # Cleanup so the test can be reproduced again later if needed. + remote_exec host "rm $debugdir_no_lib/$build_id_filename" + + # The handler will only have been called once when loading the + # memory-mapped file. GDB is smart enough to reuse the previously + # discovered BFD object as the shared library. + gdb_test "python print(handler_obj.call_count)" "^1" \ + "check good handler hasn't been called again" + + # Validate the filename and build-id arguments passed to the handler. + set expected_buildid [get_build_id $hidden_libfile] + gdb_test "python print(handler_last_buildid)" "^$expected_buildid" + gdb_test "python print(handler_last_filename)" \ + "^[string_to_regexp $libfile]" +} + +# Register another global handler, this one raises an exception. Reload the +# core-file, the bad handler should be invoked first, which raises an +# excetption, at which point GDB should skip further Python handlers. +with_test_prefix "handler raises an exception" { + gdb_test_no_output \ + "python gdb.missing_objfile.register_handler(None, rhandler)" + + foreach_with_prefix exception_type {gdb.GdbError TypeError} { + gdb_test_no_output \ + "python rhandler.exception_type = $exception_type" + + # Load the core file. We expect the exception message to appear at + # least once in the output. + set re [string_to_regexp \ + "Python Exception : message"] + load_core_file "${re}.*" + + # Our original handler is still registered, but should not have been + # called again (as the exception occurs first). + gdb_test "python print(handler_obj.call_count)" "^1" \ + "check good handler hasn't been called again" + } +} + +# Re-start GDB. +clean_restart_load_python + +# Attempt to register a missing-debug-handler with NAME. The expectation is +# that this should fail as NAME contains some invalid characters. +proc check_bad_name {name} { + set name_re [string_to_regexp $name] + set re \ + [multi_line \ + "ValueError.*: invalid character '.' in handler name: $name_re" \ + "Error occurred in Python.*"] + + gdb_test "python register(\"$name\")" $re \ + "check that '$name' is not accepted" +} + +# We don't attempt to be exhaustive here, just check a few random examples +# of invalid names. +check_bad_name "!! Bad Name" +check_bad_name "Bad Name" +check_bad_name "(Bad Name)" +check_bad_name "Bad \[Name\]" +check_bad_name "Bad,Name" +check_bad_name "Bad;Name" + +# Check that there are no handlers registered. +gdb_test_no_output "info missing-objfile-handlers" \ + "check no handlers are registered" + +# Grab the current program space object, used for registering handler later. +gdb_test_no_output "python pspace = gdb.selected_inferior().progspace" + +# Now register some handlers. +foreach hspec {{\"Foo\" None} + {\"-bar\" None} + {\"baz-\" pspace} + {\"abc-def\" pspace}} { + lassign $hspec name locus + gdb_test "python register($name, $locus)" +} + +with_test_prefix "all handlers enabled" { + gdb_test "info missing-objfile-handlers" \ + [multi_line \ + "Current Progspace:" \ + " abc-def" \ + " baz-" \ + "Global:" \ + " -bar" \ + " Foo"] + + set_debug_file_dir $debugdir_no_lib + load_core_file + + # As we perform two look ups, first for the mapped-file then for the + # shared library, each handler will be called twice. + gdb_test "python print(handler_call_log)" \ + [string_to_regexp {['abc-def', 'baz-', '-bar', 'Foo', 'abc-def', 'baz-', '-bar', 'Foo']}] + gdb_test_no_output "python handler_call_log = \[\]" \ + "reset call log" +} + +with_test_prefix "disable 'baz-'" { + gdb_test "disable missing-objfile-handler progspace baz-" \ + "^1 missing objfile handler disabled" + + gdb_test "info missing-objfile-handlers" \ + [multi_line \ + "Progspace \[^\r\n\]+:" \ + " abc-def" \ + " baz- \\\[disabled\\\]" \ + "Global:" \ + " -bar" \ + " Foo"] + + load_core_file + gdb_test "python print(handler_call_log)" \ + [string_to_regexp {['abc-def', '-bar', 'Foo', 'abc-def', '-bar', 'Foo']}] + gdb_test_no_output "python handler_call_log = \[\]" \ + "reset call log" +} + +with_test_prefix "disable 'Foo'" { + gdb_test "disable missing-objfile-handler .* Foo" \ + "^1 missing objfile handler disabled" + + gdb_test "info missing-objfile-handlers" \ + [multi_line \ + "Progspace \[^\r\n\]+:" \ + " abc-def" \ + " baz- \\\[disabled\\\]" \ + "Global:" \ + " -bar" \ + " Foo \\\[disabled\\\]"] + + load_core_file + gdb_test "python print(handler_call_log)" \ + [string_to_regexp {['abc-def', '-bar', 'abc-def', '-bar']}] + gdb_test_no_output "python handler_call_log = \[\]" \ + "reset call log" +} + +with_test_prefix "disable everything" { + gdb_test "disable missing-objfile-handler .* .*" \ + "^2 missing objfile handlers disabled" + + gdb_test "info missing-objfile-handlers" \ + [multi_line \ + "Progspace \[^\r\n\]+:" \ + " abc-def \\\[disabled\\\]" \ + " baz- \\\[disabled\\\]" \ + "Global:" \ + " -bar \\\[disabled\\\]" \ + " Foo \\\[disabled\\\]"] + + load_core_file + gdb_test "python print(handler_call_log)" \ + [string_to_regexp {[]}] + gdb_test_no_output "python handler_call_log = \[\]" \ + "reset call log" +} + +with_test_prefix "enable 'abc-def'" { + set re [string_to_regexp $hidden_binfile] + + gdb_test "enable missing-objfile-handler \"$re\" abc-def" \ + "^1 missing objfile handler enabled" \ + "enable missing-objfile-handler" + + gdb_test "info missing-objfile-handlers" \ + [multi_line \ + "Progspace \[^\r\n\]+:" \ + " abc-def" \ + " baz- \\\[disabled\\\]" \ + "Global:" \ + " -bar \\\[disabled\\\]" \ + " Foo \\\[disabled\\\]"] + + load_core_file + gdb_test "python print(handler_call_log)" \ + [string_to_regexp {['abc-def', 'abc-def']}] + gdb_test_no_output "python handler_call_log = \[\]" \ + "reset call log" +} + +with_test_prefix "enable global handlers" { + gdb_test "enable missing-objfile-handler global" \ + "^2 missing objfile handlers enabled" + + gdb_test "info missing-objfile-handlers" \ + [multi_line \ + "Progspace \[^\r\n\]+:" \ + " abc-def" \ + " baz- \\\[disabled\\\]" \ + "Global:" \ + " -bar" \ + " Foo"] + + load_core_file + gdb_test "python print(handler_call_log)" \ + [string_to_regexp {['abc-def', '-bar', 'Foo', 'abc-def', '-bar', 'Foo']}] + gdb_test_no_output "python handler_call_log = \[\]" \ + "reset call log" +} + +# Add handler_obj to the global handler list, and configure it to +# return False. We should call all of the program space specific +# handlers (which return None), and then call handler_obj from the +# global list, which returns False, at which point we shouldn't call +# anyone else. +with_test_prefix "return False handler in global list" { + gdb_test "enable missing-objfile-handler progspace" \ + "^1 missing objfile handler enabled" + + gdb_test_no_output \ + "python gdb.missing_objfile.register_handler(None, handler_obj)" \ + "register handler_obj in global list" + + gdb_test "info missing-objfile-handlers" \ + [multi_line \ + "Progspace \[^\r\n\]+:" \ + " abc-def" \ + " baz-" \ + "Global:" \ + " handler" \ + " -bar" \ + " Foo"] + + gdb_test_no_output "python handler_obj.set_mode(Mode.RETURN_FALSE)" \ + "confirgure handler" + + load_core_file + gdb_test "python print(handler_call_log)" \ + [string_to_regexp {['abc-def', 'baz-', 'handler', 'abc-def', 'baz-', 'handler']}] + gdb_test_no_output "python handler_call_log = \[\]" \ + "reset call log" +} + +# Now add handler_obj to the current program space's handler list. We +# use the same handler object here, that's fine. We should only see a +# call to the first handler object in the call log. +with_test_prefix "return False handler in progspace list" { + gdb_test_no_output \ + "python gdb.missing_objfile.register_handler(pspace, handler_obj)" \ + "register handler_obj in progspace list" + + gdb_test "info missing-objfile-handlers" \ + [multi_line \ + "Progspace \[^\r\n\]+:" \ + " handler" \ + " abc-def" \ + " baz-" \ + "Global:" \ + " handler" \ + " -bar" \ + " Foo"] + + load_core_file + gdb_test "python print(handler_call_log)" \ + [string_to_regexp {['handler', 'handler']}] + gdb_test_no_output "python handler_call_log = \[\]" \ + "reset call log" +} + +with_test_prefix "check handler replacement" { + # First, check we can have the same name appear in both program + # space and global lists without giving an error. + gdb_test_no_output "python register(\"Foo\", pspace)" + + gdb_test "info missing-objfile-handlers" \ + [multi_line \ + "Progspace \[^\r\n\]+:" \ + " Foo" \ + " handler" \ + " abc-def" \ + " baz-" \ + "Global:" \ + " handler" \ + " -bar" \ + " Foo"] + + # Now check that we get an error if we try to add a handler with + # the same name. + gdb_test "python gdb.missing_objfile.register_handler(pspace, log_handler(\"Foo\"))" \ + [multi_line \ + "RuntimeError.*: Handler Foo already exists\\." \ + "Error occurred in Python.*"] + + gdb_test "python gdb.missing_objfile.register_handler(handler=log_handler(\"Foo\"), locus=pspace)" \ + [multi_line \ + "RuntimeError.*: Handler Foo already exists\\." \ + "Error occurred in Python.*"] + + # And now try again, but this time with 'replace=True', we + # shouldn't get an error in this case. + gdb_test_no_output \ + "python gdb.missing_objfile.register_handler(pspace, log_handler(\"Foo\"), replace=True)" + + gdb_test_no_output \ + "python gdb.missing_objfile.register_handler(handler=log_handler(\"Foo\"), locus=None, replace=True)" + + # Now disable a handler and check we still need to use 'replace=True'. + gdb_test "disable missing-objfile-handler progspace Foo" \ + "^1 missing objfile handler disabled" + + gdb_test "python gdb.missing_objfile.register_handler(pspace, log_handler(\"Foo\"))" \ + [multi_line \ + "RuntimeError.*: Handler Foo already exists\\." \ + "Error occurred in Python.*"] \ + "still get an error when handler is disabled" + + gdb_test_no_output \ + "python gdb.missing_objfile.register_handler(pspace, log_handler(\"Foo\"), replace=True)" \ + "can replace a disabled handler" +} diff --git a/gdb/testsuite/gdb.python/py-missing-objfile.py b/gdb/testsuite/gdb.python/py-missing-objfile.py new file mode 100644 index 00000000000..0e5f91e9b32 --- /dev/null +++ b/gdb/testsuite/gdb.python/py-missing-objfile.py @@ -0,0 +1,158 @@ +# Copyright (C) 2024 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import shutil +from enum import Enum + +import gdb +from gdb.missing_objfile import MissingObjfileHandler + +# A global log that is filled in by instances of the LOG_HANDLER class +# when they are called. +handler_call_log = [] + +# A global holding a string, the build-id of the last missing objfile +# which triggered the 'handler' class below. This is set in the +# __call__ method of the 'handler' class and then checked from the +# expect script. +handler_last_buildid = None + + +# A global holding a string, the filename of the last missing objfile +# which triggered the 'handler' class below. This is set in the +# __call__ method of the 'handler' class and then checked from the +# expect script. +handler_last_filename = None + + +# A helper function that makes some assertions about the arguments +# passed to a MissingObjfileHandler.__call__() method. +def check_args(pspace, buildid, filename): + assert type(filename) == str + assert filename != "" + assert type(pspace) == gdb.Progspace + assert type(buildid) == str + assert buildid != "" + + +# Enum used to configure the 'handler' class from the test script. +class Mode(Enum): + RETURN_NONE = 0 + RETURN_TRUE = 1 + RETURN_FALSE = 2 + RETURN_STRING = 3 + + +# A missing objfile handler which can be configured to return each of +# the different possible return types. +class handler(MissingObjfileHandler): + def __init__(self): + super().__init__("handler") + self._call_count = 0 + self._mode = Mode.RETURN_NONE + + def __call__(self, pspace, buildid, filename): + global handler_call_log, handler_last_buildid, handler_last_filename + check_args(pspace, buildid, filename) + handler_call_log.append(self.name) + handler_last_buildid = buildid + handler_last_filename = filename + self._call_count += 1 + if self._mode == Mode.RETURN_NONE: + return None + + if self._mode == Mode.RETURN_TRUE: + shutil.copy(self._src, self._dest) + return True + + if self._mode == Mode.RETURN_FALSE: + return False + + if self._mode == Mode.RETURN_STRING: + return self._dest + + assert False + + @property + def call_count(self): + """Return a count, the number of calls to __call__ since the last + call to set_mode. + """ + return self._call_count + + def set_mode(self, mode, *args): + self._call_count = 0 + self._mode = mode + + if mode == Mode.RETURN_NONE: + assert len(args) == 0 + return + + if mode == Mode.RETURN_TRUE: + assert len(args) == 2 + self._src = args[0] + self._dest = args[1] + return + + if mode == Mode.RETURN_FALSE: + assert len(args) == 0 + return + + if mode == Mode.RETURN_STRING: + assert len(args) == 1 + self._dest = args[0] + return + + assert False + + +# A missing objfile handler which raises an exception. The type of +# exception to be raised is configured from the test script. +class exception_handler(MissingObjfileHandler): + def __init__(self): + super().__init__("exception_handler") + self.exception_type = None + + def __call__(self, pspace, buildid, filename): + global handler_call_log + check_args(pspace, buildid, filename) + handler_call_log.append(self.name) + assert self.exception_type is not None + raise self.exception_type("message") + + +# A very simple logging missing objfile handler. Always returns None +# so that GDB will try any other registered handlers, but first logs +# the name of this handler into the global HANDLER_CALL_LOG, which can +# then be checked from the test script. +class log_handler(MissingObjfileHandler): + def __call__(self, pspace, buildid, filename): + global handler_call_log + check_args(pspace, buildid, filename) + handler_call_log.append(self.name) + return None + + +# A basic helper function, this keeps lines shorter in the TCL script. +def register(name, locus=None): + gdb.missing_objfile.register_handler(locus, log_handler(name)) + + +# Create instances of the handlers, but don't install any. We install +# these as needed from the TCL script. +rhandler = exception_handler() +handler_obj = handler() + +print("Success")