From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 29018 invoked by alias); 28 Oct 2019 19:06:22 -0000 Mailing-List: contact elfutils-devel-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Post: List-Help: List-Subscribe: Sender: elfutils-devel-owner@sourceware.org Received: (qmail 29002 invoked by uid 89); 28 Oct 2019 19:06:21 -0000 Authentication-Results: sourceware.org; auth=none X-Virus-Checked: by ClamAV 0.100.3 on sourceware.org X-Virus-Found: No X-Spam-SWARE-Status: No, score=-12.7 required=5.0 tests=AWL,BAYES_05,GIT_PATCH_0,GIT_PATCH_1,GIT_PATCH_2,GIT_PATCH_3,KAM_ASCII_DIVIDERS,KAM_SHORT,UNSUBSCRIBE_BODY autolearn=ham version=3.3.1 spammy=offered, zoo, trustworthy, exports X-Spam-Status: No, score=-12.7 required=5.0 tests=AWL,BAYES_05,GIT_PATCH_0,GIT_PATCH_1,GIT_PATCH_2,GIT_PATCH_3,KAM_ASCII_DIVIDERS,KAM_SHORT,UNSUBSCRIBE_BODY autolearn=ham version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on sourceware.org X-Spam-Level: X-HELO: us-smtp-1.mimecast.com Received: from us-smtp-delivery-1.mimecast.com (HELO us-smtp-1.mimecast.com) (207.211.31.120) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with ESMTP; Mon, 28 Oct 2019 19:06:12 +0000 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1572289570; 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=Niupt5o2hYG46nmlhMK6AYTYcD07YbpRPpBiuSJRwq4=; b=bCRn50YvAesf0Lbrg4LPUmGTa9/BT4Gnrll2OzlLFphy3lZQEpPTHkpBQup4EL5KUXVjL6 Ffq6r4yMnoQbhUrNKmeGTGmgc9aRccUz0kmS9FFFxj71+ow4oL5yZx4yJW9EkpWXJ48uqT qqRWB6OaEN5Ey/bUZuO9EDLGWFEW/50= Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-313-M5Yw0dZMP8apvP0ne5aJug-1; Mon, 28 Oct 2019 15:06:05 -0400 Received: from smtp.corp.redhat.com (int-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.12]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id E268885B6EE for ; Mon, 28 Oct 2019 19:06:04 +0000 (UTC) Received: from redhat.com (ovpn-116-53.phx2.redhat.com [10.3.116.53]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 6760E60C4B; Mon, 28 Oct 2019 19:06:04 +0000 (UTC) Received: from fche by redhat.com with local (Exim 4.92) (envelope-from ) id 1iPAL4-0000Pd-QB; Mon, 28 Oct 2019 15:06:02 -0400 Date: Mon, 28 Oct 2019 19:06:00 -0000 From: "Frank Ch. Eigler" To: elfutils-devel@sourceware.org Cc: fche@redhat.com, amerey@redhat.com Subject: patch 1/2 debuginfod client Message-ID: <20191028190602.GD14349@redhat.com> References: <20191028190438.GC14349@redhat.com> MIME-Version: 1.0 In-Reply-To: <20191028190438.GC14349@redhat.com> User-Agent: Mutt/1.12.0 (2019-05-25) X-Scanned-By: MIMEDefang 2.79 on 10.5.11.12 X-MC-Unique: M5Yw0dZMP8apvP0ne5aJug-1 X-Mimecast-Spam-Score: 0 Content-Type: text/plain; charset=us-ascii Content-Transfer-Encoding: quoted-printable Content-Disposition: inline X-IsSubscribed: yes X-SW-Source: 2019-q4/txt/msg00065.txt.bz2 =46rom 3e1f8d93569004d06902459d84baceb636e139d5 Mon Sep 17 00:00:00 2001 From: Aaron Merey Date: Mon, 28 Oct 2019 13:29:26 -0400 Subject: [PATCH 1/2] debuginfod 1/2: client side Introduce the debuginfod/ subdirectory, containing the client for a new debuginfo-over-http service, in shared-library and command-line forms. Two functions in libdwfl make calls into the client library to fetch elf/dwarf files by buildid, as a fallback. Instead of normal dynamic linking (thus pulling in a variety of curl dependencies), the libdwfl hooks use dlopen/dlsym. Server & tests coming in patch 2. Signed-off-by: Aaron Merey Signed-off-by: Frank Ch. Eigler --- ChangeLog | 6 + Makefile.am | 4 + config/Makefile.am | 2 +- config/libdebuginfod.pc.in | 12 + configure.ac | 23 +- debuginfod/ChangeLog | 9 + debuginfod/Makefile.am | 116 +++++ debuginfod/debuginfod-client.c | 624 ++++++++++++++++++++++++ debuginfod/debuginfod-find.1 | 131 +++++ debuginfod/debuginfod-find.c | 108 ++++ debuginfod/debuginfod.h | 69 +++ debuginfod/debuginfod_find_debuginfo.3 | 173 +++++++ debuginfod/debuginfod_find_executable.3 | 1 + debuginfod/debuginfod_find_source.3 | 1 + debuginfod/libdebuginfod.map | 7 + libdw/ChangeLog | 4 + libdw/Makefile.am | 2 +- libdwfl/ChangeLog | 6 + libdwfl/Makefile.am | 3 +- libdwfl/dwfl_build_id_find_elf.c | 30 +- libdwfl/find-debuginfo.c | 31 +- m4/ChangeLog | 4 + m4/Makefile.am | 2 +- m4/ax_check_compile_flag.m4 | 74 +++ m4/ax_cxx_compile_stdcxx.m4 | 556 +++++++++++++++++++++ 25 files changed, 1989 insertions(+), 9 deletions(-) create mode 100644 config/libdebuginfod.pc.in create mode 100644 debuginfod/ChangeLog create mode 100644 debuginfod/Makefile.am create mode 100644 debuginfod/debuginfod-client.c create mode 100644 debuginfod/debuginfod-find.1 create mode 100644 debuginfod/debuginfod-find.c create mode 100644 debuginfod/debuginfod.h create mode 100644 debuginfod/debuginfod_find_debuginfo.3 create mode 100644 debuginfod/debuginfod_find_executable.3 create mode 100644 debuginfod/debuginfod_find_source.3 create mode 100644 debuginfod/libdebuginfod.map create mode 100644 m4/ax_check_compile_flag.m4 create mode 100644 m4/ax_cxx_compile_stdcxx.m4 diff --git a/ChangeLog b/ChangeLog index 911cf35432c9..4f33657df976 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +2019-10-28 Aaron Merey + + * debuginfod/: New directory for debuginfod code. + * Makefile.am (SUBDIRS): Recurse there. + * configure.ac (--enable-debuginfod): New flag & checks. + 2019-07-05 Omar Sandoval =20 * configure.ac: Get rid of --enable-libebl-subdir. diff --git a/Makefile.am b/Makefile.am index 52f64fc904f4..bd8926b52344 100644 --- a/Makefile.am +++ b/Makefile.am @@ -29,6 +29,10 @@ pkginclude_HEADERS =3D version.h SUBDIRS =3D config m4 lib libelf libcpu backends libebl libdwelf libdwfl l= ibdw \ libasm src po doc tests =20 +if DEBUGINFOD +SUBDIRS +=3D debuginfod +endif + EXTRA_DIST =3D elfutils.spec GPG-KEY NOTES CONTRIBUTING \ COPYING COPYING-GPLV2 COPYING-LGPLV3 =20 diff --git a/config/Makefile.am b/config/Makefile.am index 9d292cee66c8..6425718efc54 100644 --- a/config/Makefile.am +++ b/config/Makefile.am @@ -32,7 +32,7 @@ EXTRA_DIST =3D elfutils.spec.in known-dwarf.awk 10-defaul= t-yama-scope.conf libelf.pc.in libdw.pc.in =20 pkgconfigdir =3D $(libdir)/pkgconfig -pkgconfig_DATA =3D libelf.pc libdw.pc +pkgconfig_DATA =3D libelf.pc libdw.pc libdebuginfod.pc =20 if MAINTAINER_MODE $(srcdir)/elfutils.spec.in: $(top_srcdir)/NEWS diff --git a/config/libdebuginfod.pc.in b/config/libdebuginfod.pc.in new file mode 100644 index 000000000000..46722a76b593 --- /dev/null +++ b/config/libdebuginfod.pc.in @@ -0,0 +1,12 @@ +prefix=3D@prefix@ +exec_prefix=3D@exec_prefix@ +libdir=3D@libdir@ +includedir=3D@includedir@ + +Name: debuginfod +Description: elfutils library to query debuginfo files from debuginfod ser= vers +Version: @VERSION@ +URL: http://elfutils.org/ + +Libs: -L${libdir} -ldebuginfod +Cflags: -I${includedir} diff --git a/configure.ac b/configure.ac index 9be34d124387..738a96f18bf5 100644 --- a/configure.ac +++ b/configure.ac @@ -60,6 +60,8 @@ AC_CONFIG_FILES([m4/Makefile]) dnl The RPM spec file. We substitute a few values in the file. AC_CONFIG_FILES([elfutils.spec:config/elfutils.spec.in]) =20 +dnl debuginfo-server client & server parts. +AC_CONFIG_FILES([debuginfod/Makefile]) =20 AC_CANONICAL_HOST =20 @@ -86,6 +88,8 @@ AS_IF([test "$use_locks" =3D yes], AH_TEMPLATE([USE_LOCKS], [Defined if libraries should be thread-safe.]) =20 AC_PROG_CC +AC_PROG_CXX +AX_CXX_COMPILE_STDCXX(11, noext, optional) AC_PROG_RANLIB AC_PROG_YACC AM_PROG_LEX @@ -538,7 +542,7 @@ AM_CONDITIONAL(STANDALONE, false)dnl Used in tests/Make= file.am, which see. AC_CONFIG_FILES([tests/Makefile]) =20 dnl pkgconfig files -AC_CONFIG_FILES([config/libelf.pc config/libdw.pc]) +AC_CONFIG_FILES([config/libelf.pc config/libdw.pc config/libdebuginfod.pc]) =20 # Get the definitions necessary to create the Makefiles in the po # subdirectories. This is a small subset of the gettext rules. @@ -641,6 +645,22 @@ if test "$HAVE_BUNZIP2" =3D "no"; then AC_MSG_WARN([No bunzip2, needed to run make check]) fi =20 +# Look for libmicrohttpd, libcurl, libarchive, sqlite for debuginfo server +# minimum versions as per rhel7. Single --enable-* option arranges to bui= ld +# both client libs and server process. + +PKG_PROG_PKG_CONFIG +AC_ARG_ENABLE([debuginfod], AC_HELP_STRING([--enable-debuginfod], [Build d= ebuginfo server and client solib])) +AS_IF([test "x$enable_debuginfod" =3D "xyes"], [ + AC_DEFINE([ENABLE_DEBUGINFOD],[1],[Build debuginfo-server]) + PKG_CHECK_MODULES([libmicrohttpd],[libmicrohttpd >=3D 0.9.33]) + PKG_CHECK_MODULES([libcurl],[libcurl >=3D 7.29.0]) + PKG_CHECK_MODULES([sqlite3],[sqlite3 >=3D 3.7.17]) + PKG_CHECK_MODULES([libarchive],[libarchive >=3D 3.1.2]) +], [enable_debuginfod=3D"no"]) +AM_CONDITIONAL([DEBUGINFOD],[test "x$enable_debuginfod" =3D "xyes"]) + + AC_OUTPUT =20 AC_MSG_NOTICE([ @@ -669,6 +689,7 @@ AC_MSG_NOTICE([ OTHER FEATURES Deterministic archives by default : ${default_ar_deterministic} Native language support : ${USE_NLS} + Debuginfo server support : ${enable_debuginfod} =20 EXTRA TEST FEATURES (used with make check) have bunzip2 installed (required) : ${HAVE_BUNZIP2} diff --git a/debuginfod/ChangeLog b/debuginfod/ChangeLog new file mode 100644 index 000000000000..1a31cf6f4e27 --- /dev/null +++ b/debuginfod/ChangeLog @@ -0,0 +1,9 @@ +2019-10-28 Aaron Merey + + * debuginfod-client.c: New file: debuginfod client library. + * debuginfod.h: New file: header for same. + * libdebuginfod.map: New file: govern its solib exports. + * debuginfod-find.c: New file: command line frontend. + * debuginfod-find.1, debuginfod_find_source.3, + debuginfod_find_executable.3, debuginfod_find_debuginfo.3: + New man pages. diff --git a/debuginfod/Makefile.am b/debuginfod/Makefile.am new file mode 100644 index 000000000000..3a4e94da2ad0 --- /dev/null +++ b/debuginfod/Makefile.am @@ -0,0 +1,116 @@ +## Makefile.am for libdebuginfod library subdirectory in elfutils. +## +## Process this file with automake to create Makefile.in +## +## Copyright (C) 2019 Red Hat, Inc. +## This file is part of elfutils. +## +## This file is free software; you can redistribute it and/or modify +## it under the terms of either +## +## * the GNU Lesser General Public License as published by the Free +## Software Foundation; either version 3 of the License, or (at +## your option) any later version +## +## or +## +## * the GNU General Public License as published by the Free +## Software Foundation; either version 2 of the License, or (at +## your option) any later version +## +## or both in parallel, as here. +## +## elfutils 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 copies of the GNU General Public License and +## the GNU Lesser General Public License along with this program. If +## not, see . +## +include $(top_srcdir)/config/eu.am +AM_CPPFLAGS +=3D -I$(srcdir) -I$(srcdir)/../libelf -I$(srcdir)/../libebl \ + -I$(srcdir)/../libdw -I$(srcdir)/../libdwelf +VERSION =3D 1 + +# Disable eu- prefixing for artifacts (binaries & man pages) in this +# directory, since they do not conflict with binutils tools. +program_prefix=3D +program_transform_name =3D s,x,x, + +if BUILD_STATIC +libasm =3D ../libasm/libasm.a +libdw =3D ../libdw/libdw.a -lz $(zip_LIBS) $(libelf) $(libebl) -ldl +libelf =3D ../libelf/libelf.a -lz +libdebuginfod =3D ./libdebuginfod.a +else +libasm =3D ../libasm/libasm.so +libdw =3D ../libdw/libdw.so +libelf =3D ../libelf/libelf.so +libdebuginfod =3D ./libdebuginfod.so +endif +libebl =3D ../libebl/libebl.a +libeu =3D ../lib/libeu.a + +AM_LDFLAGS =3D -Wl,-rpath-link,../libelf:../libdw:. + +bin_PROGRAMS =3D debuginfod-find +dist_man3_MANS =3D debuginfod_find_debuginfo.3 debuginfod_find_source.3 de= buginfod_find_executable.3 +dist_man1_MANS =3D debuginfod-find.1 + +debuginfod_find_SOURCES =3D debuginfod-find.c +debuginfod_find_LDADD =3D $(libeu) $(libdebuginfod) + +noinst_LIBRARIES =3D libdebuginfod.a +noinst_LIBRARIES +=3D libdebuginfod_pic.a + +libdebuginfod_a_SOURCES =3D debuginfod-client.c +libdebuginfod_pic_a_SOURCES =3D debuginfod-client.c +am_libdebuginfod_pic_a_OBJECTS =3D $(libdebuginfod_a_SOURCES:.c=3D.os) + +pkginclude_HEADERS =3D debuginfod.h + +libdebuginfod_so_LIBS =3D libdebuginfod_pic.a +libdebuginfod_so_LDLIBS =3D $(libcurl_LIBS) +libdebuginfod.so$(EXEEXT): $(srcdir)/libdebuginfod.map $(libdebuginfod_so_= LIBS) + $(AM_V_CCLD)$(LINK) $(dso_LDFLAGS) -o $@ \ + -Wl,--soname,$@.$(VERSION) \ + -Wl,--version-script,$<,--no-undefined \ + -Wl,--whole-archive $(libdebuginfod_so_LIBS) -Wl,--no-whole-archive \ + $(libdebuginfod_so_LDLIBS) + @$(textrel_check) + $(AM_V_at)ln -fs $@ $@.$(VERSION) + +install: install-am libdebuginfod.so + $(mkinstalldirs) $(DESTDIR)$(libdir) + $(INSTALL_PROGRAM) libdebuginfod.so $(DESTDIR)$(libdir)/libdebuginfod-$(P= ACKAGE_VERSION).so + ln -fs libdebuginfod-$(PACKAGE_VERSION).so $(DESTDIR)$(libdir)/libdebugin= fod.so.$(VERSION) + ln -fs libdebuginfod.so.$(VERSION) $(DESTDIR)$(libdir)/libdebuginfod.so + +uninstall: uninstall-am + rm -f $(DESTDIR)$(libdir)/libdebuginfod-$(PACKAGE_VERSION).so + rm -f $(DESTDIR)$(libdir)/libdebuginfod.so.$(VERSION) + rm -f $(DESTDIR)$(libdir)/libdebuginfod.so + rmdir --ignore-fail-on-non-empty $(DESTDIR)$(includedir)/elfutils + +EXTRA_DIST =3D libdebuginfod.map +MOSTLYCLEANFILES =3D $(am_libdebuginfod_pic_a_OBJECTS) libdebuginfod.so.$(= VERSION) +CLEANFILES +=3D $(am_libdebuginfod_pic_a_OBJECTS) libdebuginfod.so + +# automake std-options override: arrange to pass LD_LIBRARY_PATH +installcheck-binPROGRAMS: $(bin_PROGRAMS) + bad=3D0; pid=3D$$$$; list=3D"$(bin_PROGRAMS)"; for p in $$list; do \ + case ' $(AM_INSTALLCHECK_STD_OPTIONS_EXEMPT) ' in \ + *" $$p "* | *" $(srcdir)/$$p "*) continue;; \ + esac; \ + f=3D`echo "$$p" | \ + sed 's,^.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/'`; \ + for opt in --help --version; do \ + if LD_LIBRARY_PATH=3D$(DESTDIR)$(libdir) \ + $(DESTDIR)$(bindir)/$$f $$opt > c$${pid}_.out 2> c$${pid}_.err \ + && test -n "`cat c$${pid}_.out`" \ + && test -z "`cat c$${pid}_.err`"; then :; \ + else echo "$$f does not support $$opt" 1>&2; bad=3D1; fi; \ + done; \ + done; rm -f c$${pid}_.???; exit $$bad diff --git a/debuginfod/debuginfod-client.c b/debuginfod/debuginfod-client.c new file mode 100644 index 000000000000..2b91bb8bb1d2 --- /dev/null +++ b/debuginfod/debuginfod-client.c @@ -0,0 +1,624 @@ +/* Retrieve ELF / DWARF / source files from the debuginfod. + Copyright (C) 2019 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of either + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at + your option) any later version + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at + your option) any later version + + or both in parallel, as here. + + elfutils 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 copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see . */ + +#include "config.h" +#include "debuginfod.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const int max_build_id_bytes =3D 256; /* typical: 40 for gnu C tool= chain */ + + +/* The cache_clean_interval_s file within the debuginfod cache specifies + how frequently the cache should be cleaned. The file's st_mtime represe= nts + the time of last cleaning. */ +static const char *cache_clean_interval_filename =3D "cache_clean_interval= _s"; +static const time_t cache_clean_default_interval_s =3D 600; + +/* The cache_max_unused_age_s file within the debuginfod cache specifies t= he + the maximum time since last access that a file will remain in the cache= . */ +static const char *cache_max_unused_age_filename =3D "max_unused_age_s"; +static const time_t cache_default_max_unused_age_s =3D 600; + +/* Location of the cache of files downloaded from debuginfods. + The default parent directory is $HOME, or '/' if $HOME doesn't exist. = */ +static const char *cache_default_name =3D ".debuginfod_client_cache"; +static const char *cache_path_envvar =3D DEBUGINFOD_CACHE_PATH_ENV_VAR; + +/* URLs of debuginfods, separated by url_delim. + This env var must be set for debuginfod-client to run. */ +static const char *server_urls_envvar =3D DEBUGINFOD_URLS_ENV_VAR; +static const char *url_delim =3D " "; +static const char url_delim_char =3D ' '; + +/* Timeout for debuginfods, in seconds. + This env var must be set for debuginfod-client to run. */ +static const char *server_timeout_envvar =3D DEBUGINFOD_TIMEOUT_ENV_VAR; +static int server_timeout =3D 5; + +/* Data associated with a particular CURL easy handle. Passed to + the write callback. */ +struct handle_data +{ + /* Cache file to be written to in case query is successful. */ + int fd; + + /* URL queried by this handle. */ + char url[PATH_MAX]; + + /* This handle. */ + CURL *handle; + + /* Pointer to handle that should write to fd. Initially points to NULL, + then points to the first handle that begins writing the target file + to the cache. Used to ensure that a file is not downloaded from + multiple servers unnecessarily. */ + CURL **target_handle; +}; + +static size_t +debuginfod_write_callback (char *ptr, size_t size, size_t nmemb, void *dat= a) +{ + ssize_t count =3D size * nmemb; + + struct handle_data *d =3D (struct handle_data*)data; + + /* Indicate to other handles that they can abort their transfer. */ + if (*d->target_handle =3D=3D NULL) + *d->target_handle =3D d->handle; + + /* If this handle isn't the target handle, abort transfer. */ + if (*d->target_handle !=3D d->handle) + return -1; + + return (size_t) write(d->fd, (void*)ptr, count); +} + +/* Create the cache and interval file if they do not already exist. + Return 0 if cache and config file are initialized, otherwise return + the appropriate error code. */ +static int +debuginfod_init_cache (char *cache_path, char *interval_path, char *maxage= _path) +{ + struct stat st; + + /* If the cache and config file already exist then we are done. */ + if (stat(cache_path, &st) =3D=3D 0 && stat(interval_path, &st) =3D=3D 0) + return 0; + + /* Create the cache and config files as necessary. */ + if (stat(cache_path, &st) !=3D 0 && mkdir(cache_path, 0777) < 0) + return -errno; + + int fd =3D -1; + + /* init cleaning interval config file. */ + fd =3D open(interval_path, O_CREAT | O_RDWR, 0666); + if (fd < 0) + return -errno; + + if (dprintf(fd, "%ld", cache_clean_default_interval_s) < 0) + return -errno; + + /* init max age config file. */ + if (stat(maxage_path, &st) !=3D 0 + && (fd =3D open(maxage_path, O_CREAT | O_RDWR, 0666)) < 0) + return -errno; + + if (dprintf(fd, "%ld", cache_default_max_unused_age_s) < 0) + return -errno; + + return 0; +} + + +/* Delete any files that have been unmodied for a period + longer than $DEBUGINFOD_CACHE_CLEAN_INTERVAL_S. */ +static int +debuginfod_clean_cache(char *cache_path, char *interval_path, char *max_un= used_path) +{ + struct stat st; + FILE *interval_file; + FILE *max_unused_file; + + if (stat(interval_path, &st) =3D=3D -1) + { + /* Create new interval file. */ + interval_file =3D fopen(interval_path, "w"); + + if (interval_file =3D=3D NULL) + return -errno; + + int rc =3D fprintf(interval_file, "%ld", cache_clean_default_interva= l_s); + fclose(interval_file); + + if (rc < 0) + return -errno; + } + + /* Check timestamp of interval file to see whether cleaning is necessary= . */ + time_t clean_interval; + interval_file =3D fopen(interval_path, "r"); + if (fscanf(interval_file, "%ld", &clean_interval) !=3D 1) + clean_interval =3D cache_clean_default_interval_s; + fclose(interval_file); + + if (time(NULL) - st.st_mtime < clean_interval) + /* Interval has not passed, skip cleaning. */ + return 0; + + /* Read max unused age value from config file. */ + time_t max_unused_age; + max_unused_file =3D fopen(max_unused_path, "r"); + if (max_unused_file) + { + if (fscanf(max_unused_file, "%ld", &max_unused_age) !=3D 1) + max_unused_age =3D cache_default_max_unused_age_s; + fclose(max_unused_file); + } + else + max_unused_age =3D cache_default_max_unused_age_s; + + char * const dirs[] =3D { cache_path, NULL, }; + + FTS *fts =3D fts_open(dirs, 0, NULL); + if (fts =3D=3D NULL) + return -errno; + + FTSENT *f; + while ((f =3D fts_read(fts)) !=3D NULL) + { + switch (f->fts_info) + { + case FTS_F: + /* delete file if max_unused_age has been met or exceeded. */ + /* XXX consider extra effort to clean up old tmp files */ + if (time(NULL) - f->fts_statp->st_atime >=3D max_unused_age) + unlink (f->fts_path); + break; + + case FTS_DP: + /* Remove if empty. */ + (void) rmdir (f->fts_path); + break; + + default: + ; + } + } + fts_close(fts); + + /* Update timestamp representing when the cache was last cleaned. */ + utime (interval_path, NULL); + return 0; +} + +/* Query each of the server URLs found in $DEBUGINFOD_URLS for the file + with the specified build-id, type (debuginfo, executable or source) + and filename. filename may be NULL. If found, return a file + descriptor for the target, otherwise return an error code. */ +static int +debuginfod_query_server (const unsigned char *build_id, + int build_id_len, + const char *type, + const char *filename, + char **path) +{ + char *urls_envvar; + char *server_urls; + char cache_path[PATH_MAX]; + char maxage_path[PATH_MAX*3]; /* These *3 multipliers are to shut up gcc= -Wformat-truncation */ + char interval_path[PATH_MAX*4]; + char target_cache_dir[PATH_MAX*2]; + char target_cache_path[PATH_MAX*3]; + char target_cache_tmppath[PATH_MAX*4]; + char suffix[PATH_MAX]; + char build_id_bytes[max_build_id_bytes * 2 + 1]; + + /* Copy lowercase hex representation of build_id into buf. */ + if ((build_id_len >=3D max_build_id_bytes) || + (build_id_len =3D=3D 0 && + sizeof(build_id_bytes) > max_build_id_bytes*2 + 1)) + return -EINVAL; + if (build_id_len =3D=3D 0) /* expect clean hexadecimal */ + strcpy (build_id_bytes, (const char *) build_id); + else + for (int i =3D 0; i < build_id_len; i++) + sprintf(build_id_bytes + (i * 2), "%02x", build_id[i]); + + unsigned q =3D 0; + if (filename !=3D NULL) + { + if (filename[0] !=3D '/') // must start with / + return -EINVAL; + + /* copy the filename to suffix, s,/,#,g */ + for (q=3D0; q=3D 0) + { + /* Success!!!! */ + if (path !=3D NULL) + *path =3D strdup(target_cache_path); + return fd; + } + + + urls_envvar =3D getenv(server_urls_envvar); + if (urls_envvar =3D=3D NULL || urls_envvar[0] =3D=3D '\0') + { + rc =3D -ENOSYS; + goto out; + } + + if (getenv(server_timeout_envvar)) + server_timeout =3D atoi (getenv(server_timeout_envvar)); + + /* make a copy of the envvar so it can be safely modified. */ + server_urls =3D strdup(urls_envvar); + if (server_urls =3D=3D NULL) + { + rc =3D -ENOMEM; + goto out; + } + /* thereafter, goto out0 on error*/ + + /* create target directory in cache if not found. */ + struct stat st; + if (stat(target_cache_dir, &st) =3D=3D -1 && mkdir(target_cache_dir, 070= 0) < 0) + { + rc =3D -errno; + goto out0; + } + + /* NB: write to a temporary file first, to avoid race condition of + multiple clients checking the cache, while a partially-written or emp= ty + file is in there, being written from libcurl. */ + fd =3D mkstemp (target_cache_tmppath); + if (fd < 0) + { + rc =3D -errno; + goto out0; + } + + /* Count number of URLs. */ + int num_urls =3D 0; + for (int i =3D 0; server_urls[i] !=3D '\0'; i++) + if (server_urls[i] !=3D url_delim_char + && (server_urls[i - 1] =3D=3D url_delim_char || i =3D=3D 0)) + num_urls++; + + /* Tracks which handle should write to fd. Set to the first + handle that is ready to write the target file to the cache. */ + CURL *target_handle =3D NULL; + struct handle_data *data =3D malloc(sizeof(struct handle_data) * num_url= s); + + /* Initalize handle_data with default values. */ + for (int i =3D 0; i < num_urls; i++) + { + data[i].handle =3D NULL; + data[i].fd =3D -1; + } + + CURLM *curlm =3D curl_multi_init(); + if (curlm =3D=3D NULL) + { + rc =3D -ENETUNREACH; + goto out0; + } + /* thereafter, goto out1 on error. */ + + char *strtok_saveptr; + char *server_url =3D strtok_r(server_urls, url_delim, &strtok_saveptr); + + /* Initialize each handle. */ + for (int i =3D 0; i < num_urls && server_url !=3D NULL; i++) + { + data[i].fd =3D fd; + data[i].target_handle =3D &target_handle; + data[i].handle =3D curl_easy_init(); + + if (data[i].handle =3D=3D NULL) + { + rc =3D -ENETUNREACH; + goto out1; + } + + /* Build handle url. Tolerate both http://foo:999 and + http://foo:999/ forms */ + char *slashbuildid; + if (strlen(server_url) > 1 && server_url[strlen(server_url)-1] =3D= =3D '/') + slashbuildid =3D "buildid"; + else + slashbuildid =3D "/buildid"; + + if (filename) /* must start with / */ + snprintf(data[i].url, PATH_MAX, "%s%s/%s/%s%s", server_url, + slashbuildid, build_id_bytes, type, filename); + else + snprintf(data[i].url, PATH_MAX, "%s%s/%s/%s", server_url, + slashbuildid, build_id_bytes, type); + + curl_easy_setopt(data[i].handle, CURLOPT_URL, data[i].url); + curl_easy_setopt(data[i].handle, + CURLOPT_WRITEFUNCTION, + debuginfod_write_callback); + curl_easy_setopt(data[i].handle, CURLOPT_WRITEDATA, (void*)&data[i]); + curl_easy_setopt(data[i].handle, CURLOPT_TIMEOUT, (long) server_time= out); + curl_easy_setopt(data[i].handle, CURLOPT_FILETIME, (long) 1); + curl_easy_setopt(data[i].handle, CURLOPT_FOLLOWLOCATION, (long) 1); + curl_easy_setopt(data[i].handle, CURLOPT_FAILONERROR, (long) 1); + curl_easy_setopt(data[i].handle, CURLOPT_AUTOREFERER, (long) 1); + curl_easy_setopt(data[i].handle, CURLOPT_ACCEPT_ENCODING, ""); + curl_easy_setopt(data[i].handle, CURLOPT_USERAGENT, (void*) PACKAGE_= STRING); + + curl_multi_add_handle(curlm, data[i].handle); + server_url =3D strtok_r(NULL, url_delim, &strtok_saveptr); + } + + /* Query servers in parallel. */ + int still_running; + do + { + CURLMcode curl_res; + + /* Wait 1 second, the minimum DEBUGINFOD_TIMEOUT. */ + curl_multi_wait(curlm, NULL, 0, 1000, NULL); + + /* If the target file has been found, abort the other queries. */ + if (target_handle !=3D NULL) + for (int i =3D 0; i < num_urls; i++) + if (data[i].handle !=3D target_handle) + curl_multi_remove_handle(curlm, data[i].handle); + + curl_res =3D curl_multi_perform(curlm, &still_running); + if (curl_res !=3D CURLM_OK) + { + switch (curl_res) + { + case CURLM_CALL_MULTI_PERFORM: continue; + case CURLM_OUT_OF_MEMORY: rc =3D -ENOMEM; break; + default: rc =3D -ENETUNREACH; break; + } + goto out1; + } + } while (still_running); + + /* Check whether a query was successful. If so, assign its handle + to verified_handle. */ + int num_msg; + rc =3D -ENOENT; + CURL *verified_handle =3D NULL; + do + { + CURLMsg *msg; + + msg =3D curl_multi_info_read(curlm, &num_msg); + if (msg !=3D NULL && msg->msg =3D=3D CURLMSG_DONE) + { + if (msg->data.result !=3D CURLE_OK) + { + /* Unsucessful query, determine error code. */ + switch (msg->data.result) + { + case CURLE_COULDNT_RESOLVE_HOST: rc =3D -EHOSTUNREACH; bre= ak; // no NXDOMAIN + case CURLE_URL_MALFORMAT: rc =3D -EINVAL; break; + case CURLE_COULDNT_CONNECT: rc =3D -ECONNREFUSED; break; + case CURLE_REMOTE_ACCESS_DENIED: rc =3D -EACCES; break; + case CURLE_WRITE_ERROR: rc =3D -EIO; break; + case CURLE_OUT_OF_MEMORY: rc =3D -ENOMEM; break; + case CURLE_TOO_MANY_REDIRECTS: rc =3D -EMLINK; break; + case CURLE_SEND_ERROR: rc =3D -ECONNRESET; break; + case CURLE_RECV_ERROR: rc =3D -ECONNRESET; break; + case CURLE_OPERATION_TIMEDOUT: rc =3D -ETIME; break; + default: rc =3D -ENOENT; break; + } + } + else + { + /* Query completed without an error. Confirm that the + response code is 200 and set verified_handle. */ + long resp_code =3D 500; + CURLcode curl_res; + + curl_res =3D curl_easy_getinfo(target_handle, + CURLINFO_RESPONSE_CODE, + &resp_code); + + if (curl_res =3D=3D CURLE_OK + && resp_code =3D=3D 200 + && msg->easy_handle !=3D NULL) + { + verified_handle =3D msg->easy_handle; + break; + } + } + } + } while (num_msg > 0); + + if (verified_handle =3D=3D NULL) + goto out1; + + /* we've got one!!!! */ + time_t mtime; + CURLcode curl_res =3D curl_easy_getinfo(verified_handle, CURLINFO_FILETI= ME, (void*) &mtime); + if (curl_res !=3D CURLE_OK) + mtime =3D time(NULL); /* fall back to current time */ + + struct timeval tvs[2]; + tvs[0].tv_sec =3D tvs[1].tv_sec =3D mtime; + tvs[0].tv_usec =3D tvs[1].tv_usec =3D 0; + (void) futimes (fd, tvs); /* best effort */ + + /* rename tmp->real */ + rc =3D rename (target_cache_tmppath, target_cache_path); + if (rc < 0) + { + rc =3D -errno; + goto out1; + /* Perhaps we need not give up right away; could retry or something = ... */ + } + + /* Success!!!! */ + for (int i =3D 0; i < num_urls; i++) + curl_easy_cleanup(data[i].handle); + + curl_multi_cleanup (curlm); + free (data); + free (server_urls); + /* don't close fd - we're returning it */ + /* don't unlink the tmppath; it's already been renamed. */ + if (path !=3D NULL) + *path =3D strdup(target_cache_path); + + return fd; + +/* error exits */ + out1: + for (int i =3D 0; i < num_urls; i++) + curl_easy_cleanup(data[i].handle); + + curl_multi_cleanup(curlm); + unlink (target_cache_tmppath); + (void) rmdir (target_cache_dir); /* nop if not empty */ + free(data); + close (fd); + + out0: + free (server_urls); + + out: + return rc; +} + + +/* See debuginfod.h */ +int +debuginfod_find_debuginfo (const unsigned char *build_id, int build_id_len, + char **path) +{ + return debuginfod_query_server(build_id, build_id_len, + "debuginfo", NULL, path); +} + + +/* See debuginfod.h */ +int +debuginfod_find_executable(const unsigned char *build_id, int build_id_len, + char **path) +{ + return debuginfod_query_server(build_id, build_id_len, + "executable", NULL, path); +} + +/* See debuginfod.h */ +int debuginfod_find_source(const unsigned char *build_id, + int build_id_len, + const char *filename, + char **path) +{ + return debuginfod_query_server(build_id, build_id_len, + "source", filename, path); +} + + + +/* NB: these are thread-unsafe. */ +__attribute__((constructor)) attribute_hidden void libdebuginfod_ctor(void) +{ + curl_global_init(CURL_GLOBAL_DEFAULT); +} + +/* NB: this is very thread-unsafe: it breaks other threads that are still = in libcurl */ +__attribute__((destructor)) attribute_hidden void libdebuginfod_dtor(void) +{ + /* ... so don't do this: */ + /* curl_global_cleanup(); */ +} diff --git a/debuginfod/debuginfod-find.1 b/debuginfod/debuginfod-find.1 new file mode 100644 index 000000000000..6d2251662a57 --- /dev/null +++ b/debuginfod/debuginfod-find.1 @@ -0,0 +1,131 @@ +'\"! tbl | nroff \-man +'\" t macro stdmacro + +.de SAMPLE +.br +.RS 0 +.nf +.nh +.. +.de ESAMPLE +.hy +.fi +.RE +.. + +.TH DEBUGINFOD-FIND 1 +.SH NAME +debuginfod-find \- request debuginfo-related data + +.SH SYNOPSIS +.B debuginfod-find debuginfo \fIBUILDID\fP + +.B debuginfod-find executable \fIBUILDID\fP + +.B debuginfod-find source \fIBUILDID\fP \fI/FILENAME\fP + +.SH DESCRIPTION +\fBdebuginfod-find\fP queries one or more \fBdebuginfod\fP servers for +debuginfo-related data. In case of a match, it saves the the +requested file into a local cache, prints the file name to standard +output, and exits with a success status of 0. In case of any error, +it exits with a failure status and an error message to standard error. + +.\" Much of the following text is duplicated with debuginfod.8 + +The debuginfod system uses buildids to identify debuginfo-related data. +These are stored as binary notes in ELF/DWARF files, and are +represented as lowercase hexadecimal. For example, for a program +/bin/ls, look at the ELF note GNU_BUILD_ID: + +.SAMPLE +% readelf -n /bin/ls | grep -A4 build.id +Note section [ 4] '.note.gnu.buildid' of 36 bytes at offset 0x340: +Owner Data size Type +GNU 20 GNU_BUILD_ID +Build ID: 8713b9c3fb8a720137a4a08b325905c7aaf8429d +.ESAMPLE + +Then the hexadecimal BUILDID is simply: + +.SAMPLE +8713b9c3fb8a720137a4a08b325905c7aaf8429d +.ESAMPLE + +.SS debuginfo \fIBUILDID\fP + +If the given buildid is known to a server, this request will result +in a binary object that contains the customary \fB.*debug_*\fP +sections. This may be a split debuginfo file as created by +\fBstrip\fP, or it may be an original unstripped executable. + +.SS executable \fIBUILDID\fP + +If the given buildid is known to the server, this request will result +in a binary object that contains the normal executable segments. This +may be a executable stripped by \fBstrip\fP, or it may be an original +unstripped executable. \fBET_DYN\fP shared libraries are considered +to be a type of executable. + +.SS source \fIBUILDID\fP \fI/SOURCE/FILE\fP + +If the given buildid is known to the server, this request will result +in a binary object that contains the source file mentioned. The path +should be absolute. Relative path names commonly appear in the DWARF +file's source directory, but these paths are relative to +individual compilation unit AT_comp_dir paths, and yet an executable +is made up of multiple CUs. Therefore, to disambiguate, debuginfod +expects source queries to prefix relative path names with the CU +compilation-directory. + +Note: you should not elide \fB../\fP or \fB/./\fP sorts of relative +path components in the directory names, because if this is how those +names appear in the DWARF files, that is what debuginfod needs to see +too. + +For example: +.TS +l l. +#include source BUILDID /usr/include/stdio.h +/path/to/foo.c source BUILDID /path/to/foo.c +\../bar/foo.c AT_comp_dir=3D/zoo source BUILDID /zoo/../bar/foo.c +.TE + +.SH "SECURITY" + +debuginfod-find \fBdoes not\fP include any particular security +features. It trusts that the binaries returned by the debuginfod(s) +are accurate. Therefore, the list of servers should include only +trustworthy ones. If accessed across HTTP rather than HTTPS, the +network should be trustworthy. + +.SH "ENVIRONMENT VARIABLES" + +.TP 21 +.B DEBUGINFOD_URLS +This environment variable contains a list of URL prefixes for trusted +debuginfod instances. Alternate URL prefixes are separated by space. + +.TP 21 +.B DEBUGINFOD_TIMEOUT +This environment variable governs the timeout for each debuginfod HTTP +connection. A server that fails to respond within this many seconds +is skipped. The default is 5. + +.TP 21 +.B DEBUGINFOD_CACHE_PATH +This environment variable governs the location of the cache where +downloaded files are kept. It is cleaned periodically as this +program is reexecuted. The default is $HOME/.debuginfod_client_cache. +.\" XXX describe cache eviction policy + +.SH "FILES" +.LP +.PD .1v +.TP 20 +.B $HOME/.debuginfod_client_cache +Default cache directory. +.PD + +.SH "SEE ALSO" +.I "debuginfod(8)" diff --git a/debuginfod/debuginfod-find.c b/debuginfod/debuginfod-find.c new file mode 100644 index 000000000000..78322fc6cd29 --- /dev/null +++ b/debuginfod/debuginfod-find.c @@ -0,0 +1,108 @@ +/* Command-line frontend for retrieving ELF / DWARF / source files + from the debuginfod. + Copyright (C) 2019 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of either + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at + your option) any later version + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at + your option) any later version + + or both in parallel, as here. + + elfutils 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 copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see . */ + +#include "config.h" +#include "printversion.h" +#include "debuginfod.h" +#include +#include +#include +#include + + +/* Name and version of program. */ +ARGP_PROGRAM_VERSION_HOOK_DEF =3D print_version; + +/* Bug report address. */ +ARGP_PROGRAM_BUG_ADDRESS_DEF =3D PACKAGE_BUGREPORT; + +/* Short description of program. */ +static const char doc[] =3D N_("Request debuginfo-related content " + "from debuginfods listed in $" DEBUGINFOD_URL= S_ENV_VAR "."); + +/* Strings for arguments in help texts. */ +static const char args_doc[] =3D N_("debuginfo BUILDID\n" + "executable BUILDID\n" + "source BUILDID /FILENAME"); + +/* Data structure to communicate with argp functions. */ +static struct argp argp =3D + { + NULL, NULL, args_doc, doc, NULL, NULL, NULL + }; + + + +int +main(int argc, char** argv) +{ + int remaining; + (void) argp_parse (&argp, argc, argv, ARGP_IN_ORDER|ARGP_NO_ARGS, &remai= ning, NULL); + + if (argc < 2 || remaining+1 =3D=3D argc) /* no arguments or at least two= non-option words */ + { + argp_help (&argp, stderr, ARGP_HELP_USAGE, argv[0]); + return 1; + } +=20=20 + int rc; + char *cache_name; + + /* Check whether FILETYPE is valid and call the appropriate + debuginfod_find_* function. If FILETYPE is "source" + then ensure a FILENAME was also supplied as an argument. */ + if (strcmp(argv[remaining], "debuginfo") =3D=3D 0) + rc =3D debuginfod_find_debuginfo((unsigned char *)argv[remaining+1], 0= , &cache_name); + else if (strcmp(argv[remaining], "executable") =3D=3D 0) + rc =3D debuginfod_find_executable((unsigned char *)argv[remaining+1], = 0, &cache_name); + else if (strcmp(argv[remaining], "source") =3D=3D 0) + { + if (remaining+2 =3D=3D argc || argv[3][0] !=3D '/') + { + fprintf(stderr, "If FILETYPE is \"source\" then absolute /FILENA= ME must be given\n"); + return 1; + } + rc =3D debuginfod_find_source((unsigned char *)argv[remaining+1], 0, + argv[remaining+2], &cache_name); + } + else + { + argp_help (&argp, stderr, ARGP_HELP_USAGE, argv[0]); + return 1; + } + + if (rc < 0) + { + fprintf(stderr, "Server query failed: %s\n", strerror(-rc)); + return 1; + } + + printf("%s\n", cache_name); + return 0; +} diff --git a/debuginfod/debuginfod.h b/debuginfod/debuginfod.h new file mode 100644 index 000000000000..c268ffebcdb6 --- /dev/null +++ b/debuginfod/debuginfod.h @@ -0,0 +1,69 @@ +/* External declarations for the libdebuginfod client library. + Copyright (C) 2019 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of either + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at + your option) any later version + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at + your option) any later version + + or both in parallel, as here. + + elfutils 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 copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see . */ + +#ifndef _LIBBGSERVER_CLIENT_H +#define _LIBBGSERVER_CLIENT_H 1 + +/* Names of environment variables that control the client logic. */ +#define DEBUGINFOD_URLS_ENV_VAR "DEBUGINFOD_URLS" +#define DEBUGINFOD_CACHE_PATH_ENV_VAR "DEBUGINFOD_CACHE_PATH" +#define DEBUGINFOD_TIMEOUT_ENV_VAR "DEBUGINFOD_TIMEOUT" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Query the urls contained in $DEBUGINFOD_URLS for a file with + the specified type and build id. If build_id_len =3D=3D 0, the + build_id is supplied as a lowercase hexadecimal string; otherwise + it is a binary blob of given legnth. + + If successful, return a file descriptor to the target, otherwise + return a posix error code. If successful, set *path to a + strdup'd copy of the name of the same file in the cache. + Caller must free() it later. */ +=20=20 +int debuginfod_find_debuginfo (const unsigned char *build_id, + int build_id_len, + char **path); + +int debuginfod_find_executable (const unsigned char *build_id, + int build_id_len, + char **path); + +int debuginfod_find_source (const unsigned char *build_id, + int build_id_len, + const char *filename, + char **path); + +#ifdef __cplusplus +} +#endif + + +#endif /* _LIBBGSERVER_CLIENT_H */ diff --git a/debuginfod/debuginfod_find_debuginfo.3 b/debuginfod/debuginfod= _find_debuginfo.3 new file mode 100644 index 000000000000..18caec5576ba --- /dev/null +++ b/debuginfod/debuginfod_find_debuginfo.3 @@ -0,0 +1,173 @@ +'\"! tbl | nroff \-man +'\" t macro stdmacro + +.de SAMPLE +.br +.RS 0 +.nf +.nh +.. +.de ESAMPLE +.hy +.fi +.RE +.. + +.TH DEBUGINFOD_FIND_DEBUGINFO 3 +.SH NAME +debuginfod_find_debuginfo \- request debuginfo from debuginfod + +.SH SYNOPSIS +.nf +.B #include +.PP +.BI "debuginfod_find_debuginfo(const unsigned char *" build_id ", int " bu= ild_id_len ", char ** " path ");" +.BI "debuginfod_find_executable(const unsigned char *" build_id ", int " b= uild_id_len ", char ** " path ");" +.BI "debuginfod_find_source(const unsigned char *" build_id ", int " build= _id_len ", const char *" filename ", char ** " path ");" + +.SH DESCRIPTION +.BR debuginfod_find_debuginfo (), +.BR debuginfod_find_executable (), +and +.BR debuginfod_find_source () +query the debuginfod server URLs contained in +.BR $DEBUGINFOD_URLS +(see below) for the debuginfo, executable or source file with the +given \fIbuild_id\fP. \fIbuild_id\fP should be a pointer to either +a null-terminated, lowercase hex string or a binary blob. If +\fIbuild_id\fP is given as a hex string, \fIbuild_id_len\fP should +be 0. Otherwise \fIbuild_id_len\fP should be the number of bytes in +the binary blob. + +.BR debuginfod_find_source () +also requries a \fIfilename\fP in order to specify a particular +source file. \fIfilename\fP should be an absolute path that includes +the compilation directory of the CU associated with the source file. +Relative path names commonly appear in the DWARF file's source directory, +but these paths are relative to individual compilation unit AT_comp_dir +paths, and yet an executable is made up of multiple CUs. Therefore, to +disambiguate, debuginfod expects source queries to prefix relative path +names with the CU compilation-directory. + +If \fIpath\fP is not NULL and the query is successful, \fIpath\fP is set +to the path of the file in the cache. The caller must \fBfree\fP() this va= lue. + +The URLs in \fB$DEBUGINFOD_URLS\fP are queried in parallel. As soon as a +debuginfod server begins transfering the target file all of the connections +to the other servers are closed. + +.SH "CACHE" +If the query is successful, the \fBdebuginfod_find_*\fP() functions save +the target file to a local cache. The location of the cache is controlled +by the \fB$DEBUGINFOD_CACHE_PATH\fP environment variable (see below). +Cleaning of the cache is controlled by the \fIcache_clean_interval_s\fP +and \fImax_unused_age_s\fP files, which are found in the +\fB$DEBUGINFOD_CACHE_PATH\fP directory. \fIcache_clean_interval_s\fP contr= ols +how frequently the cache is traversed for cleaning and \fImax_unused_age_s= \fP +controls how long a file can go unused before its removed from the cache +during cleaning. These files should contain only an ASCII integer +representing the interval or max unused age in seconds. The files contain a +default value of 600. + +.SH "SECURITY" +.BR debuginfod_find_debuginfo (), +.BR debuginfod_find_executable (), +and +.BR debuginfod_find_source () +\fBdo not\fP include any particular security +features. They trust that the binaries returned by the debuginfod(s) +are accurate. Therefore, the list of servers should include only +trustworthy ones. If accessed across HTTP rather than HTTPS, the +network should be trustworthy. + +.SH "ENVIRONMENT VARIABLES" + +.TP 21 +.B DEBUGINFOD_URLS +This environment variable contains a list of URL prefixes for trusted +debuginfod instances. Alternate URL prefixes are separated by space. + +.TP 21 +.B DEBUGINFOD_TIMEOUT +This environment variable governs the timeout for each debuginfod HTTP +connection. A server that fails to respond within this many seconds +is skipped. The default is 5. + +.TP 21 +.B DEBUGINFOD_CACHE_PATH +This environment variable governs the location of the cache where +downloaded files are kept. It is cleaned periodically as this +program is reexecuted. The default is $HOME/.debuginfod_client_cache. + +.SH "RETURN VALUE" +If the query is successful, these functions save the target file +to the client cache and return a file descriptor to that file. +Otherwise an error code is returned. + +.SH "ERRORS" +The following list is not comprehensive. Error codes may also +originate from calls to various C Library functions. + +.TP +.BR EACCESS +Denied access to resource located at the URL. + +.TP +.BR ECONNREFUSED +Unable to connect to remote host. + +.TP +.BR ECONNRESET +Unable to either send or recieve network data. + +.TP +.BR EHOSTUNREACH +Unable to resolve remote host. + +.TP +.BR EINVAL +One or more arguments are incorrectly formatted. \fIbuild_id\fP may +be too long (greater than 256 characters), \fIfilename\fP may not +be an absolute path or a debuginfod URL is malformed. + +.TP +.BR EIO +Unable to write data received from server to local file. + +.TP +.BR EMLINK +Too many HTTP redirects. + +.TP +.BR ENETUNREACH +Unable to initialize network connection. + +.TP +.BR ENOENT +Could not find the resource located at URL. Often this error code +indicates that a debuginfod server was successfully contacted but +the server could not find the target file. + +.TP +.BR ENOMEM +System is unable to allocate resources. + +.TP +.BR ENOSYS +\fB$DEBUGINFOD_URLS\fP is not defined. + +.TP +.BR ETIME +Query failed due to timeout. \fB$DEBUGINFOD_TIMEOUT\fP controls +the timeout duration. See debuginfod(8) for more information. + +.SH "FILES" +.LP +.PD .1v +.TP 20 +.B $HOME/.debuginfod_client_cache +Default cache directory. +.PD + +.SH "SEE ALSO" +.I "debuginfod(8)" diff --git a/debuginfod/debuginfod_find_executable.3 b/debuginfod/debuginfo= d_find_executable.3 new file mode 100644 index 000000000000..16279936e2ea --- /dev/null +++ b/debuginfod/debuginfod_find_executable.3 @@ -0,0 +1 @@ +.so man3/debuginfod_find_debuginfo.3 diff --git a/debuginfod/debuginfod_find_source.3 b/debuginfod/debuginfod_fi= nd_source.3 new file mode 100644 index 000000000000..16279936e2ea --- /dev/null +++ b/debuginfod/debuginfod_find_source.3 @@ -0,0 +1 @@ +.so man3/debuginfod_find_debuginfo.3 diff --git a/debuginfod/libdebuginfod.map b/debuginfod/libdebuginfod.map new file mode 100644 index 000000000000..50d6fbaec639 --- /dev/null +++ b/debuginfod/libdebuginfod.map @@ -0,0 +1,7 @@ +ELFUTILS_0 { }; +ELFUTILS_0.177 { + global: + debuginfod_find_debuginfo; + debuginfod_find_executable; + debuginfod_find_source; +} ELFUTILS_0; diff --git a/libdw/ChangeLog b/libdw/ChangeLog index 394c0df293f0..f0af348aff6e 100644 --- a/libdw/ChangeLog +++ b/libdw/ChangeLog @@ -1,3 +1,7 @@ +2019-10-28 Aaron Merey + + * Makefile.am (libdw_so_LDLIBS): Add -ldl for libdebuginfod.so dlopen. + 2019-08-26 Jonathon Anderson =20 * libdw_alloc.c (__libdw_allocate): Added thread-safe stack allocator. diff --git a/libdw/Makefile.am b/libdw/Makefile.am index ce793e903b88..33b5838dc4e1 100644 --- a/libdw/Makefile.am +++ b/libdw/Makefile.am @@ -109,7 +109,7 @@ libdw_so_LIBS =3D ../libebl/libebl_pic.a ../backends/li= bebl_backends_pic.a \ ../libcpu/libcpu_pic.a libdw_pic.a ../libdwelf/libdwelf_pic.a \ ../libdwfl/libdwfl_pic.a libdw_so_DEPS =3D ../lib/libeu.a ../libelf/libelf.so -libdw_so_LDLIBS =3D $(libdw_so_DEPS) -lz $(argp_LDADD) $(zip_LIBS) -pthread +libdw_so_LDLIBS =3D $(libdw_so_DEPS) -ldl -lz $(argp_LDADD) $(zip_LIBS) -p= thread libdw_so_SOURCES =3D libdw.so$(EXEEXT): $(srcdir)/libdw.map $(libdw_so_LIBS) $(libdw_so_DEPS) $(AM_V_CCLD)$(LINK) $(dso_LDFLAGS) -o $@ \ diff --git a/libdwfl/ChangeLog b/libdwfl/ChangeLog index 04a39637e9a4..be29cc00bc7e 100644 --- a/libdwfl/ChangeLog +++ b/libdwfl/ChangeLog @@ -1,3 +1,9 @@ +2019-10-28 Aaron Merey + + * dwfl_build_id_find_elf.c (dwfl_build_id_find_elf): Call debuginfod + client functions via dlopen to look for elf/dwarf files as fallback. + * find-debuginfo.c (dwfl_standard_find_debuginfo): Ditto. + 2019-08-12 Mark Wielaard =20 * gzip.c (open_stream): Return DWFL_E_ERRNO on bad file operation. diff --git a/libdwfl/Makefile.am b/libdwfl/Makefile.am index 89ca92ed8110..29046e9e5e85 100644 --- a/libdwfl/Makefile.am +++ b/libdwfl/Makefile.am @@ -31,7 +31,7 @@ ## include $(top_srcdir)/config/eu.am AM_CPPFLAGS +=3D -I$(srcdir) -I$(srcdir)/../libelf -I$(srcdir)/../libebl \ - -I$(srcdir)/../libdw -I$(srcdir)/../libdwelf + -I$(srcdir)/../libdw -I$(srcdir)/../libdwelf -I$(srcdir)/../debuginfod VERSION =3D 1 =20 noinst_LIBRARIES =3D libdwfl.a @@ -39,6 +39,7 @@ noinst_LIBRARIES +=3D libdwfl_pic.a =20 pkginclude_HEADERS =3D libdwfl.h =20 + libdwfl_a_SOURCES =3D dwfl_begin.c dwfl_end.c dwfl_error.c dwfl_version.c \ dwfl_module.c dwfl_report_elf.c relocate.c \ dwfl_module_build_id.c dwfl_module_report_build_id.c \ diff --git a/libdwfl/dwfl_build_id_find_elf.c b/libdwfl/dwfl_build_id_find_= elf.c index cc6c3f62d276..1f3834180c4a 100644 --- a/libdwfl/dwfl_build_id_find_elf.c +++ b/libdwfl/dwfl_build_id_find_elf.c @@ -1,5 +1,5 @@ /* Find an ELF file for a module from its build ID. - Copyright (C) 2007-2010, 2014, 2015 Red Hat, Inc. + Copyright (C) 2007-2010, 2014, 2015, 2019 Red Hat, Inc. This file is part of elfutils. =20 This file is free software; you can redistribute it and/or modify @@ -34,7 +34,9 @@ #include #include #include +#include #include "system.h" +#include "debuginfod.h" =20 =20 int @@ -187,7 +189,31 @@ dwfl_build_id_find_elf (Dwfl_Module *mod, free (*file_name); *file_name =3D NULL; } - else if (errno =3D=3D 0 && mod->build_id_len > 0) +#if ENABLE_DEBUGINFOD + else { + static void *debuginfod_so; + static __typeof__ (debuginfod_find_executable) *fp_debuginfod_find_exe= cutable; + + if (debuginfod_so =3D=3D NULL) + debuginfod_so =3D dlopen("libdebuginfod-" VERSION ".so", RTLD_LAZY); + if (debuginfod_so =3D=3D NULL) + debuginfod_so =3D dlopen("libdebuginfod.so", RTLD_LAZY); + if (debuginfod_so !=3D NULL && fp_debuginfod_find_executable =3D=3D NU= LL) + fp_debuginfod_find_executable =3D dlsym (debuginfod_so, "debuginfod_= find_executable"); + + if (fp_debuginfod_find_executable !=3D NULL) + { + /* If all else fails and a build-id is available, query the + debuginfo-server if enabled. */ + if (fd < 0 && mod->build_id_len > 0) + fd =3D (*fp_debuginfod_find_executable) (mod->build_id_bits, + mod->build_id_len, + NULL); + } + } +#endif /* ENABLE_DEBUGINFOD */ + + if (fd < 0 && errno =3D=3D 0 && mod->build_id_len > 0) /* Setting this with no file yet loaded is a marker that the build ID is authoritative even if we also know a putative *FILE_NAME. */ diff --git a/libdwfl/find-debuginfo.c b/libdwfl/find-debuginfo.c index 9267788d2d19..d36ec3cc39b8 100644 --- a/libdwfl/find-debuginfo.c +++ b/libdwfl/find-debuginfo.c @@ -1,5 +1,5 @@ /* Standard find_debuginfo callback for libdwfl. - Copyright (C) 2005-2010, 2014, 2015 Red Hat, Inc. + Copyright (C) 2005-2010, 2014, 2015, 2019 Red Hat, Inc. This file is part of elfutils. =20 This file is free software; you can redistribute it and/or modify @@ -31,9 +31,13 @@ #endif =20 #include "libdwflP.h" +#ifdef ENABLE_DEBUGINFOD +#include "debuginfod.h" +#endif #include #include #include +#include #include #include "system.h" =20 @@ -359,7 +363,8 @@ dwfl_standard_find_debuginfo (Dwfl_Module *mod, other than just by finding nothing, that's all we do. */ const unsigned char *bits; GElf_Addr vaddr; - if (INTUSE(dwfl_module_build_id) (mod, &bits, &vaddr) > 0) + int bits_len; + if ((bits_len =3D INTUSE(dwfl_module_build_id) (mod, &bits, &vaddr)) > 0) { /* Dropping most arguments means we cannot rely on them in dwfl_build_id_find_debuginfo. But leave it that way since @@ -397,6 +402,28 @@ dwfl_standard_find_debuginfo (Dwfl_Module *mod, free (canon); } =20 +#if ENABLE_DEBUGINFOD + { + static void *debuginfod_so; + static __typeof__ (debuginfod_find_debuginfo) *fp_debuginfod_find_debu= ginfo; + + if (debuginfod_so =3D=3D NULL) + debuginfod_so =3D dlopen("libdebuginfod-" VERSION ".so", RTLD_LAZY); + if (debuginfod_so =3D=3D NULL) + debuginfod_so =3D dlopen("libdebuginfod.so", RTLD_LAZY); + if (debuginfod_so !=3D NULL && fp_debuginfod_find_debuginfo =3D=3D NUL= L) + fp_debuginfod_find_debuginfo =3D dlsym (debuginfod_so, "debuginfod_f= ind_debuginfo"); + + if (fp_debuginfod_find_debuginfo !=3D NULL) + { + /* If all else fails and a build-id is available, query the + debuginfo-server if enabled. */ + if (fd < 0 && bits_len > 0) + fd =3D (*fp_debuginfod_find_debuginfo) (bits, bits_len, NULL); + } + } +#endif /* ENABLE_DEBUGINFOD */ + return fd; } INTDEF (dwfl_standard_find_debuginfo) diff --git a/m4/ChangeLog b/m4/ChangeLog index 9ee06d750a1e..8ab0ff39610a 100644 --- a/m4/ChangeLog +++ b/m4/ChangeLog @@ -1,3 +1,7 @@ +2019-10-28 Aaron Merey + + * ax_check_compile_flag.m4, ax_cxx_compile_stdcxx.m4: New files. + 2015-05-01 Mark Wielaard =20 * zip.m4: Explicitly set with_ to no, if not yes. diff --git a/m4/Makefile.am b/m4/Makefile.am index 3b0e11458748..ae7a565777e8 100644 --- a/m4/Makefile.am +++ b/m4/Makefile.am @@ -18,4 +18,4 @@ ## =20 ##m4-files-begin -EXTRA_DIST =3D codeset.m4 gettext.m4 iconv.m4 lcmessage.m4 progtest.m4 zip= .m4 +EXTRA_DIST =3D codeset.m4 gettext.m4 iconv.m4 lcmessage.m4 progtest.m4 zip= .m4 ax_check_compile_flag.m4 ax_cxx_compile_stdcxx.m4 diff --git a/m4/ax_check_compile_flag.m4 b/m4/ax_check_compile_flag.m4 new file mode 100644 index 000000000000..ca3639715e72 --- /dev/null +++ b/m4/ax_check_compile_flag.m4 @@ -0,0 +1,74 @@ +# =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D +# http://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html +# =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D +# +# SYNOPSIS +# +# AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA= -FLAGS], [INPUT]) +# +# DESCRIPTION +# +# Check whether the given FLAG works with the current language's compiler +# or gives an error. (Warnings, however, are ignored) +# +# ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on +# success/failure. +# +# If EXTRA-FLAGS is defined, it is added to the current language's defau= lt +# flags (e.g. CFLAGS) when the check is done. The check is thus made wi= th +# the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to +# force the compiler to issue an error when a bad flag is given. +# +# INPUT gives an alternative input source to AC_COMPILE_IFELSE. +# +# NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this +# macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG. +# +# LICENSE +# +# Copyright (c) 2008 Guido U. Draheim +# Copyright (c) 2011 Maarten Bosmans +# +# 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 Gener= al +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 4 + +AC_DEFUN([AX_CHECK_COMPILE_FLAG], +[AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF +AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl +AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [ + ax_check_save_flags=3D$[]_AC_LANG_PREFIX[]FLAGS + _AC_LANG_PREFIX[]FLAGS=3D"$[]_AC_LANG_PREFIX[]FLAGS $4 $1" + AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])], + [AS_VAR_SET(CACHEVAR,[yes])], + [AS_VAR_SET(CACHEVAR,[no])]) + _AC_LANG_PREFIX[]FLAGS=3D$ax_check_save_flags]) +AS_VAR_IF(CACHEVAR,yes, + [m4_default([$2], :)], + [m4_default([$3], :)]) +AS_VAR_POPDEF([CACHEVAR])dnl +])dnl AX_CHECK_COMPILE_FLAGS diff --git a/m4/ax_cxx_compile_stdcxx.m4 b/m4/ax_cxx_compile_stdcxx.m4 new file mode 100644 index 000000000000..8adc76569aa7 --- /dev/null +++ b/m4/ax_cxx_compile_stdcxx.m4 @@ -0,0 +1,556 @@ +# =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D +# http://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx.html +# =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D +# +# SYNOPSIS +# +# AX_CXX_COMPILE_STDCXX(VERSION, [ext|noext], [mandatory|optional]) +# +# DESCRIPTION +# +# Check for baseline language coverage in the compiler for the specified +# version of the C++ standard. If necessary, add switches to CXX to +# enable support. VERSION may be '11' (for the C++11 standard) or '14' +# (for the C++14 standard). +# +# The second argument, if specified, indicates whether you insist on an +# extended mode (e.g. -std=3Dgnu++11) or a strict conformance mode (e.g. +# -std=3Dc++11). If neither is specified, you get whatever works, with +# preference for an extended mode. +# +# The third argument, if specified 'mandatory' or if left unspecified, +# indicates that baseline support for the specified C++ standard is +# required and that the macro should error out if no mode with that +# support is found. If specified 'optional', then configuration proceeds +# regardless, after defining HAVE_CXX${VERSION} if and only if a +# supporting mode is found. +# +# LICENSE +# +# Copyright (c) 2008 Benjamin Kosnik +# Copyright (c) 2012 Zack Weinberg +# Copyright (c) 2013 Roy Stogner +# Copyright (c) 2014, 2015 Google Inc.; contributed by Alexey Sokolov +# Copyright (c) 2015 Paul Norman +# Copyright (c) 2015 Moritz Klammler +# +# Copying and distribution of this file, with or without modification, a= re +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 3 + +dnl This macro is based on the code from the AX_CXX_COMPILE_STDCXX_11 mac= ro +dnl (serial version number 13). + +AC_DEFUN([AX_CXX_COMPILE_STDCXX], [dnl + m4_if([$1], [11], [], + [$1], [14], [], + [$1], [17], [m4_fatal([support for C++17 not yet implemented in AX= _CXX_COMPILE_STDCXX])], + [m4_fatal([invalid first argument `$1' to AX_CXX_COMPILE_STDCXX])]= )dnl + m4_if([$2], [], [], + [$2], [ext], [], + [$2], [noext], [], + [m4_fatal([invalid second argument `$2' to AX_CXX_COMPILE_STDCXX])= ])dnl + m4_if([$3], [], [ax_cxx_compile_cxx$1_required=3Dtrue], + [$3], [mandatory], [ax_cxx_compile_cxx$1_required=3Dtrue], + [$3], [optional], [ax_cxx_compile_cxx$1_required=3Dfalse], + [m4_fatal([invalid third argument `$3' to AX_CXX_COMPILE_STDCXX])]) + AC_LANG_PUSH([C++])dnl + ac_success=3Dno + AC_CACHE_CHECK(whether $CXX supports C++$1 features by default, + ax_cv_cxx_compile_cxx$1, + [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])= ], + [ax_cv_cxx_compile_cxx$1=3Dyes], + [ax_cv_cxx_compile_cxx$1=3Dno])]) + if test x$ax_cv_cxx_compile_cxx$1 =3D xyes; then + ac_success=3Dyes + fi + + m4_if([$2], [noext], [], [dnl + if test x$ac_success =3D xno; then + for switch in -std=3Dgnu++$1 -std=3Dgnu++0x; do + cachevar=3DAS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch]) + AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch, + $cachevar, + [ac_save_CXX=3D"$CXX" + CXX=3D"$CXX $switch" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbod= y_$1])], + [eval $cachevar=3Dyes], + [eval $cachevar=3Dno]) + CXX=3D"$ac_save_CXX"]) + if eval test x\$$cachevar =3D xyes; then + CXX=3D"$CXX $switch" + ac_success=3Dyes + break + fi + done + fi]) + + m4_if([$2], [ext], [], [dnl + if test x$ac_success =3D xno; then + dnl HP's aCC needs +std=3Dc++11 according to: + dnl http://h21007.www2.hp.com/portal/download/files/unprot/aCxx/PDF_Re= lease_Notes/769149-001.pdf + dnl Cray's crayCC needs "-h std=3Dc++11" + for switch in -std=3Dc++$1 -std=3Dc++0x +std=3Dc++$1 "-h std=3Dc++$1";= do + cachevar=3DAS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch]) + AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch, + $cachevar, + [ac_save_CXX=3D"$CXX" + CXX=3D"$CXX $switch" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbod= y_$1])], + [eval $cachevar=3Dyes], + [eval $cachevar=3Dno]) + CXX=3D"$ac_save_CXX"]) + if eval test x\$$cachevar =3D xyes; then + CXX=3D"$CXX $switch" + ac_success=3Dyes + break + fi + done + fi]) + AC_LANG_POP([C++]) + if test x$ax_cxx_compile_cxx$1_required =3D xtrue; then + if test x$ac_success =3D xno; then + AC_MSG_ERROR([*** A compiler with support for C++$1 language feature= s is required.]) + fi + fi + if test x$ac_success =3D xno; then + HAVE_CXX$1=3D0 + AC_MSG_NOTICE([No compiler with C++$1 support was found]) + else + HAVE_CXX$1=3D1 + AC_DEFINE(HAVE_CXX$1,1, + [define if the compiler supports basic C++$1 syntax]) + fi + AC_SUBST(HAVE_CXX$1) +]) + + +dnl Test body for checking C++11 support + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_11], + _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 +) + + +dnl Test body for checking C++14 support + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_14], + _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 +) + + +dnl Tests for new features in C++11 + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_11], [[ + +// If the compiler admits that it is not ready for C++11, why torture it? +// Hopefully, this will speed up the test. + +#ifndef __cplusplus + +#error "This is not a C++ compiler" + +#elif __cplusplus < 201103L + +#error "This is not a C++11 compiler" + +#else + +namespace cxx11 +{ + + namespace test_static_assert + { + + template + struct check + { + static_assert(sizeof(int) <=3D sizeof(T), "not big enough"); + }; + + } + + namespace test_final_override + { + + struct Base + { + virtual void f() {} + }; + + struct Derived : public Base + { + virtual void f() override {} + }; + + } + + namespace test_double_right_angle_brackets + { + + template < typename T > + struct check {}; + + typedef check single_type; + typedef check> double_type; + typedef check>> triple_type; + typedef check>>> quadruple_type; + + } + + namespace test_decltype + { + + int + f() + { + int a =3D 1; + decltype(a) b =3D 2; + return a + b; + } + + } + + namespace test_type_deduction + { + + template < typename T1, typename T2 > + struct is_same + { + static const bool value =3D false; + }; + + template < typename T > + struct is_same + { + static const bool value =3D true; + }; + + template < typename T1, typename T2 > + auto + add(T1 a1, T2 a2) -> decltype(a1 + a2) + { + return a1 + a2; + } + + int + test(const int c, volatile int v) + { + static_assert(is_same::value =3D=3D true, ""); + static_assert(is_same::value =3D=3D false, ""); + static_assert(is_same::value =3D=3D false, ""); + auto ac =3D c; + auto av =3D v; + auto sumi =3D ac + av + 'x'; + auto sumf =3D ac + av + 1.0; + static_assert(is_same::value =3D=3D true, ""); + static_assert(is_same::value =3D=3D true, ""); + static_assert(is_same::value =3D=3D true, ""); + static_assert(is_same::value =3D=3D false, ""); + static_assert(is_same::value =3D=3D true, = ""); + return (sumf > 0.0) ? sumi : add(c, v); + } + + } + + namespace test_noexcept + { + + int f() { return 0; } + int g() noexcept { return 0; } + + static_assert(noexcept(f()) =3D=3D false, ""); + static_assert(noexcept(g()) =3D=3D true, ""); + + } + + namespace test_constexpr + { + + template < typename CharT > + unsigned long constexpr + strlen_c_r(const CharT *const s, const unsigned long acc) noexcept + { + return *s ? strlen_c_r(s + 1, acc + 1) : acc; + } + + template < typename CharT > + unsigned long constexpr + strlen_c(const CharT *const s) noexcept + { + return strlen_c_r(s, 0UL); + } + + static_assert(strlen_c("") =3D=3D 0UL, ""); + static_assert(strlen_c("1") =3D=3D 1UL, ""); + static_assert(strlen_c("example") =3D=3D 7UL, ""); + static_assert(strlen_c("another\0example") =3D=3D 7UL, ""); + + } + + namespace test_rvalue_references + { + + template < int N > + struct answer + { + static constexpr int value =3D N; + }; + + answer<1> f(int&) { return answer<1>(); } + answer<2> f(const int&) { return answer<2>(); } + answer<3> f(int&&) { return answer<3>(); } + + void + test() + { + int i =3D 0; + const int c =3D 0; + static_assert(decltype(f(i))::value =3D=3D 1, ""); + static_assert(decltype(f(c))::value =3D=3D 2, ""); + static_assert(decltype(f(0))::value =3D=3D 3, ""); + } + + } + + namespace test_uniform_initialization + { + + struct test + { + static const int zero {}; + static const int one {1}; + }; + + static_assert(test::zero =3D=3D 0, ""); + static_assert(test::one =3D=3D 1, ""); + + } + + namespace test_lambdas + { + + void + test1() + { + auto lambda1 =3D [](){}; + auto lambda2 =3D lambda1; + lambda1(); + lambda2(); + } + + int + test2() + { + auto a =3D [](int i, int j){ return i + j; }(1, 2); + auto b =3D []() -> int { return '0'; }(); + auto c =3D [=3D](){ return a + b; }(); + auto d =3D [&](){ return c; }(); + auto e =3D [a, &b](int x) mutable { + const auto identity =3D [](int y){ return y; }; + for (auto i =3D 0; i < a; ++i) + a +=3D b--; + return x + identity(a + b); + }(0); + return a + b + c + d + e; + } + + int + test3() + { + const auto nullary =3D [](){ return 0; }; + const auto unary =3D [](int x){ return x; }; + using nullary_t =3D decltype(nullary); + using unary_t =3D decltype(unary); + const auto higher1st =3D [](nullary_t f){ return f(); }; + const auto higher2nd =3D [unary](nullary_t f1){ + return [unary, f1](unary_t f2){ return f2(unary(f1())); }; + }; + return higher1st(nullary) + higher2nd(nullary)(unary); + } + + } + + namespace test_variadic_templates + { + + template + struct sum; + + template + struct sum + { + static constexpr auto value =3D N0 + sum::value; + }; + + template <> + struct sum<> + { + static constexpr auto value =3D 0; + }; + + static_assert(sum<>::value =3D=3D 0, ""); + static_assert(sum<1>::value =3D=3D 1, ""); + static_assert(sum<23>::value =3D=3D 23, ""); + static_assert(sum<1, 2>::value =3D=3D 3, ""); + static_assert(sum<5, 5, 11>::value =3D=3D 21, ""); + static_assert(sum<2, 3, 5, 7, 11, 13>::value =3D=3D 41, ""); + + } + + // http://stackoverflow.com/questions/13728184/template-aliases-and-sfin= ae + // Clang 3.1 fails with headers of libstd++ 4.8.3 when using std::functi= on + // because of this. + namespace test_template_alias_sfinae + { + + struct foo {}; + + template + using member =3D typename T::member_type; + + template + void func(...) {} + + template + void func(member*) {} + + void test(); + + void test() { func(0); } + + } + +} // namespace cxx11 + +#endif // __cplusplus >=3D 201103L + +]]) + + +dnl Tests for new features in C++14 + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_14], [[ + +// If the compiler admits that it is not ready for C++14, why torture it? +// Hopefully, this will speed up the test. + +#ifndef __cplusplus + +#error "This is not a C++ compiler" + +#elif __cplusplus < 201402L + +#error "This is not a C++14 compiler" + +#else + +namespace cxx14 +{ + + namespace test_polymorphic_lambdas + { + + int + test() + { + const auto lambda =3D [](auto&&... args){ + const auto istiny =3D [](auto x){ + return (sizeof(x) =3D=3D 1UL) ? 1 : 0; + }; + const int aretiny[] =3D { istiny(args)... }; + return aretiny[0]; + }; + return lambda(1, 1L, 1.0f, '1'); + } + + } + + namespace test_binary_literals + { + + constexpr auto ivii =3D 0b0000000000101010; + static_assert(ivii =3D=3D 42, "wrong value"); + + } + + namespace test_generalized_constexpr + { + + template < typename CharT > + constexpr unsigned long + strlen_c(const CharT *const s) noexcept + { + auto length =3D 0UL; + for (auto p =3D s; *p; ++p) + ++length; + return length; + } + + static_assert(strlen_c("") =3D=3D 0UL, ""); + static_assert(strlen_c("x") =3D=3D 1UL, ""); + static_assert(strlen_c("test") =3D=3D 4UL, ""); + static_assert(strlen_c("another\0test") =3D=3D 7UL, ""); + + } + + namespace test_lambda_init_capture + { + + int + test() + { + auto x =3D 0; + const auto lambda1 =3D [a =3D x](int b){ return a + b; }; + const auto lambda2 =3D [a =3D lambda1(x)](){ return a; }; + return lambda2(); + } + + } + + namespace test_digit_seperators + { + + constexpr auto ten_million =3D 100'000'000; + static_assert(ten_million =3D=3D 100000000, ""); + + } + + namespace test_return_type_deduction + { + + auto f(int& x) { return x; } + decltype(auto) g(int& x) { return x; } + + template < typename T1, typename T2 > + struct is_same + { + static constexpr auto value =3D false; + }; + + template < typename T > + struct is_same + { + static constexpr auto value =3D true; + }; + + int + test() + { + auto x =3D 0; + static_assert(is_same::value, ""); + static_assert(is_same::value, ""); + return x; + } + + } + +} // namespace cxx14 + +#endif // __cplusplus >=3D 201402L + +]]) --=20 2.21.0