From patchwork Mon Nov 21 17:56:39 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andrew Burgess X-Patchwork-Id: 60937 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 4CB8A384F491 for ; Mon, 21 Nov 2022 17:57:26 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 4CB8A384F491 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=sourceware.org; s=default; t=1669053446; bh=yUmd8z7Xcor/WZTxt7Qz0Pv03hWwgvUPqztG9s45RoI=; h=To:Cc:Subject:Date:In-Reply-To:References:List-Id: List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe: From:Reply-To:From; b=ae29bkGyTXpkpVPPkKyXWkdjew4Q0jTuD7lnq/welpxGFvLKrn1FhrxHnqbnNtlPl KaKaDTO06eVkN5DXtJkMfqxhG1+LoUqtZB8HgzMpTohtWv/CMq+3WBnTVzxNmMR7Ht 1bwvWTMMc43HyQy+j70YORKtc4lhP9x38phn2ijQ= 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 ESMTPS id 19B993852222 for ; Mon, 21 Nov 2022 17:56:46 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.1 sourceware.org 19B993852222 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_128_GCM_SHA256) id us-mta-113-NZi9tjQPPJCLyJ8gEtQzuA-1; Mon, 21 Nov 2022 12:56:44 -0500 X-MC-Unique: NZi9tjQPPJCLyJ8gEtQzuA-1 Received: by mail-wm1-f71.google.com with SMTP id i133-20020a1c3b8b000000b003cffc0a69afso7242729wma.9 for ; Mon, 21 Nov 2022 09:56:44 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; 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=yUmd8z7Xcor/WZTxt7Qz0Pv03hWwgvUPqztG9s45RoI=; b=K7FPOQlD6PwYPhCXvqQpxgORajyvDtWB7rIy0/0XmHoHo6IHugQE4ltuHmyfYtLfN3 FZI5rBmL6ToGUh+YRb4TfwwkZTQx5mod9+nxNhSNe3KsvXMiNiv5SOKo7ozChf7GABGZ ve97lLbWlXzqaHXVWHgaa55qDCpxk8tush91rNo0hs6+Oklh839b0sTlwyX58kZGWdXb Q74vlEGZ6jVMS+65ZbLMpIEKxGHQJdCTKVg1gBvN6GVuZ4vpUbxO/6wMsS4xWtudhift 3gFx0PH7WWXudfZ7XF2WWbPg4t+1VeVbCtyrv1k+YICOI4NZDf/wDNNmBVbZ8PIey98C Dh+A== X-Gm-Message-State: ANoB5pllUddZ3MAtGik0x5llFrEvpgc6vaD0zHKgphwtzYRTYKpFt8EX UeAeiQCdUCACn68MZEnfAiHvGmiro0kEupSBSzdmshxUy9LfZRLg9DQPowdQMigezHt2xT8SM7c udxCNU667O1W59QwGNu7s7n2mIxxJMYLBQLshPa8Wohu98Qb+BTlBrtiKHwjzLanL+v01ZvnMXA == X-Received: by 2002:adf:ed4b:0:b0:241:d4c2:e741 with SMTP id u11-20020adfed4b000000b00241d4c2e741mr3946746wro.628.1669053402719; Mon, 21 Nov 2022 09:56:42 -0800 (PST) X-Google-Smtp-Source: AA0mqf4Toe4hn++/JHBDF6+wegFWztzWbI/JrMW8zU+YNWpV7bjMdfj35li9K+AAFSJAkIfMR3N0Rg== X-Received: by 2002:adf:ed4b:0:b0:241:d4c2:e741 with SMTP id u11-20020adfed4b000000b00241d4c2e741mr3946719wro.628.1669053402143; Mon, 21 Nov 2022 09:56:42 -0800 (PST) Received: from localhost ([31.111.84.238]) by smtp.gmail.com with ESMTPSA id u16-20020a05600c19d000b003cf37c5ddc0sm15539841wmq.22.2022.11.21.09.56.41 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 21 Nov 2022 09:56:41 -0800 (PST) To: gdb-patches@sourceware.org Cc: Dr Lancelot SIX , Asaf Fisher , Andrew Burgess Subject: [PATCHv4] gdb: handle loading shared libraries from /proc/self/fd/ Date: Mon, 21 Nov 2022 17:56:39 +0000 Message-Id: <96170aac143fdbcd13595e3285e125e62fe800c8.1669052725.git.aburgess@redhat.com> X-Mailer: git-send-email 2.25.4 In-Reply-To: <87zgck7c8j.fsf@redhat.com> References: <87zgck7c8j.fsf@redhat.com> MIME-Version: 1.0 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-11.7 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.29 Precedence: list List-Id: Gdb-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Andrew Burgess via Gdb-patches From: Andrew Burgess Reply-To: Andrew Burgess Errors-To: gdb-patches-bounces+patchwork=sourceware.org@sourceware.org Sender: "Gdb-patches" From: Asaf Fisher via Gdb-patches Lancelot, I dug into why the interesting shared library event is sometime the first stop, and sometimes the second stop. I think it depends on whether the stap probes based mechanism is used for handling shared library events or not. When the probes are not used then the library is reported at the first event (though if you continue again there is another stop, this time with no new libraries reported). If you are using stap probes then the library is reported at the second stop. I was able to reproduce the behaviour when I use a different Fedora version to my default. What I haven't looked into yet is why one install uses probes, but the other does not. I'll take a look at that tomorrow. However, I don't think that matters for this patch, probes or non-probes are both valid, so I've updated the test such that it should handle both cases fine. Changes since v3: - Fixed NULL/nullptr issues Lancelot pointed out, - Updated the test case to take account of non-probes based shared library event handling. Thanks, Andrew --- Bug PR gdb/29586 describes a situations where a shared library is created in memory, then written to a memory mapped file. The memory mapped file will show up as a file descriptor within /proc/self/fd/, and this path is then used with dlopen in order to call functions within the in-memory shared library. When attempting to debug this GDB hangs. The problem is that, GDB stops at the shared-library event, reads the shared library path from the inferior, which is "/proc/self/fd/", and then GDB attempts to open this file. Unfortunately, this means GDB tries to open a file within GDB's /proc/self/fd/ directory, not within the inferior's directory. In the case of our hang it turns out that the file descriptor that is opened is a pipe, and GDB hangs trying to read from the pipe. However, the behaviour is really just undefined, depending on which file descriptor the inferior tries to open, GDB will open, or fail to open, random files within its /proc/self/fd directory. The solution proposed in this commit is to hook into solib_find_1, and spot when GDB is looking for any file in /proc/self/, if this is the case, then the filename is rewritten as /proc/, where is the process-id of the current inferior. The test passes for the unix, native-gdbserver, and native-extended-gdbserver targets. When testing with either of the gdbserver targets, the test is run using the default empty sysroot, and also using the 'target:' sysroot. Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=29586 Co-authored-by: Andrew Burgess --- gdb/solib.c | 38 ++++- gdb/testsuite/gdb.base/solib-proc-self.cc | 73 +++++++++ gdb/testsuite/gdb.base/solib-proc-self.exp | 175 +++++++++++++++++++++ 3 files changed, 282 insertions(+), 4 deletions(-) create mode 100644 gdb/testsuite/gdb.base/solib-proc-self.cc create mode 100644 gdb/testsuite/gdb.base/solib-proc-self.exp base-commit: 84f9fbe90e5429adb9dee68f04f44c92fa9e2345 diff --git a/gdb/solib.c b/gdb/solib.c index 7cfdd81114c..6aabcf0d68d 100644 --- a/gdb/solib.c +++ b/gdb/solib.c @@ -50,6 +50,7 @@ #include "gdb_bfd.h" #include "gdbsupport/filestuff.h" #include "gdbsupport/scoped_fd.h" +#include "gdbsupport/pathstuff.h" #include "debuginfod-support.h" #include "source.h" #include "cli/cli-style.h" @@ -83,6 +84,34 @@ show_solib_search_path (struct ui_file *file, int from_tty, # define DOS_BASED_FILE_SYSTEM 0 #endif +/* Fix references to files in /proc/self/fd/ when opening a shared library. + + SO_NAME is the name of the shared library being loaded. This function + returns a possibly modified name which should be used as the path to the + shared library. + + If SO_NAME starts with /proc/self, then the returned name will be + modified to start with /proc/PID where 'PID' is the pid of the current + inferior. */ + +static gdb::unique_xmalloc_ptr +filter_proc_self_filenames (gdb::unique_xmalloc_ptr so_name) +{ + static const char *proc_self_prefix = "/proc/self"; + + /* Is the path really a /proc/self? */ + if (!startswith (so_name.get (), proc_self_prefix)) + return so_name; + + /* Get the part of the path after /proc/self. For example given + '/proc/self/fd' we find the '/fd' part. */ + const char *tail = so_name.get () + strlen (proc_self_prefix); + + /* Build a replacement path. */ + int inferior_pid = inferior_ptid.pid (); + return xstrprintf ("/proc/%d%s", inferior_pid, tail); +} + /* Return the full pathname of a binary file (the main executable or a shared library file), or NULL if not found. If FD is non-NULL, *FD is set to either -1 or an open file handle for the binary file. @@ -172,6 +201,9 @@ solib_find_1 (const char *in_pathname, int *fd, bool is_solib) } } + temp_pathname.reset (xstrdup (in_pathname)); + temp_pathname = filter_proc_self_filenames (std::move (temp_pathname)); + /* Note, we're interested in IS_TARGET_ABSOLUTE_PATH, not IS_ABSOLUTE_PATH. The latter is for host paths only, while IN_PATHNAME is a target path. For example, if we're supposed to @@ -184,9 +216,7 @@ solib_find_1 (const char *in_pathname, int *fd, bool is_solib) 3rd attempt, c:/foo/bar.dll ==> /sysroot/foo/bar.dll */ - if (!IS_TARGET_ABSOLUTE_PATH (fskind, in_pathname) || sysroot == NULL) - temp_pathname.reset (xstrdup (in_pathname)); - else + if (IS_TARGET_ABSOLUTE_PATH (fskind, in_pathname) && sysroot != nullptr) { bool need_dir_separator; @@ -213,7 +243,7 @@ solib_find_1 (const char *in_pathname, int *fd, bool is_solib) /* Cat the prefixed pathname together. */ temp_pathname.reset (concat (sysroot, need_dir_separator ? SLASH_STRING : "", - in_pathname, (char *) NULL)); + temp_pathname.get (), nullptr)); } /* Handle files to be accessed via the target. */ diff --git a/gdb/testsuite/gdb.base/solib-proc-self.cc b/gdb/testsuite/gdb.base/solib-proc-self.cc new file mode 100644 index 00000000000..f2439d738a3 --- /dev/null +++ b/gdb/testsuite/gdb.base/solib-proc-self.cc @@ -0,0 +1,73 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2022 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 +#include +#include +#include +#include +#include +#include +#include + +#ifdef __WIN32__ +#include +#define dlopen(name, mode) LoadLibrary (name) +#define dlclose(handle) FreeLibrary (handle) +#define dlerror() "an error occurred" +#else +#include +#endif + +int +main () +{ + /* Read the shared libraries contents into a buffer. */ + std::ifstream read_so_file = std::ifstream (SHLIB_NAME); + read_so_file.seekg (0, std::ios::end); + std::streamsize size = read_so_file.tellg (); + read_so_file.seekg (0, std::ios::beg); + std::vector buffer (size); + if (!read_so_file.read (buffer.data (), size)) + { + fprintf (stderr, "Failed to load solib\n"); + exit (1); + } + + /* Create a memory mapped file, then write the shared library to that + new memory mapped file. */ + int mem_fd = memfd_create ("test", 0); + write (mem_fd, buffer.data (), buffer.size ()); + + /* Generate the /proc/self/fd/[num] path for the memory mapped file. */ + std::string proc_self_fd_path; /* break-here */ + std::stringstream proc_self_fd_path_stream + = std::stringstream (proc_self_fd_path); + proc_self_fd_path_stream << "/proc/self/fd/" << mem_fd; + + /* Call dlopen on it. */ + void *handle = dlopen (proc_self_fd_path_stream.str ().c_str (), RTLD_LAZY); + if (!handle) + { + fprintf (stderr, "%s\n", dlerror ()); + exit (1); + } + /* It worked. */ + dlclose (handle); + + return 0; +} diff --git a/gdb/testsuite/gdb.base/solib-proc-self.exp b/gdb/testsuite/gdb.base/solib-proc-self.exp new file mode 100644 index 00000000000..794a03556ff --- /dev/null +++ b/gdb/testsuite/gdb.base/solib-proc-self.exp @@ -0,0 +1,175 @@ +# Copyright 2022 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 . */ + +# Test connecting and disconnecting at shared library events. + +if {[skip_shlib_tests]} { + untested "could not run to main" + return 0 +} + +standard_testfile .cc + +# Reuse an existing library, we don't care about the library contents +# for this test. +set libfile so-disc-shr +set libsrc "${srcdir}/${subdir}/${libfile}.c" +set libname "${libfile}.so" +set libobj [standard_output_file ${libname}] + +# Compile the shared library. +if { [gdb_compile_shlib $libsrc $libobj {debug}] != ""} { + return -1 +} + +# Compile the test executable. +if [ build_executable "failed to prepare" $testfile $srcfile \ + [list shlib_load debug c++ additional_flags=-DSHLIB_NAME="${libobj}"]] { + return -1 +} + +# Start GDB and run to the point where the test program tries to dlopen a file +# from within /proc/self/fd/. Catch the shared library event and check that +# we actually try to load a file from /proc//fd/. +# +# If SYSROOT is not the empty string, then this is set as the value of GDB's +# sysroot immediately after starting GDB. The only value that is (currently) +# supported, other than the empty string, is 'target:'. +proc do_test { {sysroot ""} } { + clean_restart $::binfile + + if {$sysroot != ""} { + gdb_test_no_output "set sysroot ${sysroot}" + } + + gdb_load_shlib $::libobj + + if ![runto_main] then { + return 0 + } + + # Get inferior's PID for later. + set inferior_pid [get_inferior_pid] + + # Turn on the solib-events so we can see that gdb resolves everything + # correctly. + gdb_test_no_output "set stop-on-solib-events 1" + + # Run to the 'break-here' marker. + gdb_breakpoint [gdb_get_line_number "break-here"] + gdb_continue_to_breakpoint "break-here" ".* break-here .*" + + set memfd "" + gdb_test_multiple "p mem_fd" "Get file descriptor" { + -re -wrap "\\\$$::decimal = (\[^\r\n\]*)" { + set memfd $expect_out(1,string) + pass $gdb_test_name + } + } + + # Now continue forward until the solib event is detected, and + # check that the loaded library is found through the /proc/PID/fd + # rather than /proc/self/fd. + # + # We need to handle the possibility of the interesting event + # showing the first, or second time we stop, as depending on which + # mechanism GDB is using to handle the shared library events (the + # newer probes based interface, or the old non-probes interface), + # the library will be reported at the first or second stop. + set saw_expected_event false + set saw_no_event_stop false + gdb_test_multiple "continue" "continue to solib evnt" { + -re "^continue\r\n" { + exp_continue + } + + -re "^Continuing\\.\r\n" { + exp_continue + } + + -early -re "Stopped due to shared library event \\(no libraries added or removed\\)\r\n" { + # This non-interesting event shows up first when using the + # probes based mechanism for dealing with shared library + # events. + # + # We set a flag here, and, once the prompt has appeared, + # we send another continue, the next event will contain + # the information we want. + set saw_no_event_stop true + exp_continue + } + + -re "Stopped due to shared library event:\r\n Inferior loaded (?:target:)?/proc/${inferior_pid}/fd/$memfd\r\n" { + # This event, which includes the information we are + # looking for, occurs first when using the non-probes + # based mechanism for handling shared library events, and + # occurs second when using the probes mechanism. + # + # Either way, record here that we say the output we expect. + set saw_expected_event true + exp_continue + } + + -re "$::gdb_prompt $" { + if {$saw_no_event_stop} { + set saw_no_event_stop false + send_gdb "continue\n" + exp_continue + } else { + gdb_assert {$saw_expected_event} $gdb_test_name + } + } + } +} + +# First run of the test. +do_test + +# Possible second run of the test. If we are using a remote target then we +# should consider setting the sysroot to 'target:' and re-running the test. +if {[target_info exists gdb_protocol] + && ([target_info gdb_protocol] == "remote" + || [target_info gdb_protocol] == "extended-remote")} { + # GDB will already be running after the first call to do_test, so we can + # take a peek at the current sysroot setting, and decide if we should + # repeat the test with a different setting. + + set new_sysroot "" + gdb_test_multiple "show sysroot" "" { + -wrap -re "The current system root is \"\"\\." { + pass $gdb_test_name + + # Repeat the test with 'target:' sysroot. + set new_sysroot "target:" + } + -wrap -re "The current system root is \"target:\"\\." { + pass $gdb_test_name + + # Nothing else to do, we already tested with target: sysroot. + } + -re "$gdb_prompt $" { + pass $gdb_test_name + + # If already testing with any other sysroot, we probably should + # not try to adjust things, so don't do any further testing. + } + } + + with_test_prefix "sysroot $new_sysroot" { + if { $new_sysroot != "" } { + do_test $new_sysroot + } + } +}