From: Aaron Merey Date: Fri, 19 Feb 2021 03:58:25 +0000 (-0500) Subject: PR432215 Add debuginfod functionality X-Git-Tag: VALGRIND_3_17_0~37 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=fd4e3fb0ffa3f751f0e7f2e7f4639d4c3773305d;p=thirdparty%2Fvalgrind.git PR432215 Add debuginfod functionality debuginfod is an HTTP server for distributing ELF/DWARF debugging information. When a debuginfo file cannot be found locally, Valgrind is able to query debuginfod servers for the file using its build-id. readelf.c: Add debuginfod_find_debug_file(). Spawns a child process to exec `debuginfod-find` in order to query servers for the debuginfo file. Also add helper debuginfod_find_path(). pub_core_pathscan.h: Moved from priv_initimg_pathscan.h in order to use VG_(find_executable)() in readelf.c. docs: Add information regarding debuginfod to valgrind.1 memcheck/tests/linux: Add new test debuginfod-check. tests/vg_regtest.in: Clear $DEBUGINFOD_URLS before running any tests. https://bugs.kde.org/show_bug.cgi?id=432215 --- diff --git a/.gitignore b/.gitignore index 3afc37f4cd..c22c0315a2 100644 --- a/.gitignore +++ b/.gitignore @@ -1065,6 +1065,9 @@ /memcheck/tests/linux/sys-execveat /memcheck/tests/linux/check_execveat /memcheck/tests/linux/enomem +/memcheck/tests/linux/debuginfod-check +/memcheck/tests/linux/debuginfod-check.vgtest +/memcheck/tests/linux/.debuginfod/ # /memcheck/tests/mips32/ /memcheck/tests/mips32/*.stderr.diff diff --git a/NEWS b/NEWS index a20f96f0c6..3a34269869 100644 --- a/NEWS +++ b/NEWS @@ -35,6 +35,12 @@ support for X86/macOS 10.13, AMD64/macOS 10.13 and nanoMIPS/Linux. Valgrind. The vector-packed-decimal facility is currently not exploited by the standard toolchain and libraries. +* Valgrind now supports debuginfod, an HTTP server for distributing + ELF/DWARF debugging information. When a debuginfo file cannot be + found locally, Valgrind is able to query debuginfod servers for the + file using its build-id. See the user manual for more information + about debuginfod support. + * ==================== TOOL CHANGES ==================== * DHAT: @@ -125,6 +131,7 @@ where XXXXXX is the bug number as listed below. 432672 vg_regtest: test-specific environment variables not reset between tests 432809 VEX should support REX.W + POPF 432861 PPC modsw and modsd give incorrect results for 1 mod 12 +432215 Add debuginfod functionality n-i-bz helgrind: If hg_cli__realloc fails, return NULL. 429352 PPC ISA 3.1 support is missing, part 7 diff --git a/configure.ac b/configure.ac index 4648450fe3..595003b46d 100755 --- a/configure.ac +++ b/configure.ac @@ -4903,6 +4903,7 @@ AC_CONFIG_FILES([ memcheck/tests/amd64/Makefile memcheck/tests/x86/Makefile memcheck/tests/linux/Makefile + memcheck/tests/linux/debuginfod-check.vgtest memcheck/tests/darwin/Makefile memcheck/tests/solaris/Makefile memcheck/tests/amd64-linux/Makefile diff --git a/coregrind/Makefile.am b/coregrind/Makefile.am index 1753fb633c..be097f44f5 100644 --- a/coregrind/Makefile.am +++ b/coregrind/Makefile.am @@ -212,6 +212,7 @@ noinst_HEADERS = \ pub_core_mallocfree.h \ pub_core_options.h \ pub_core_oset.h \ + pub_core_pathscan.h \ pub_core_poolalloc.h \ pub_core_rangemap.h \ pub_core_redir.h \ @@ -267,7 +268,6 @@ noinst_HEADERS = \ m_gdbserver/target.h \ m_gdbserver/valgrind_low.h \ m_gdbserver/gdb/signals.h \ - m_initimg/priv_initimg_pathscan.h \ m_scheduler/priv_sema.h \ m_scheduler/priv_sched-lock.h \ m_scheduler/priv_sched-lock-impl.h \ @@ -317,6 +317,7 @@ COREGRIND_SOURCES_COMMON = \ m_mallocfree.c \ m_options.c \ m_oset.c \ + m_pathscan.c \ m_poolalloc.c \ m_rangemap.c \ m_redir.c \ @@ -403,7 +404,6 @@ COREGRIND_SOURCES_COMMON = \ m_initimg/initimg-linux.c \ m_initimg/initimg-darwin.c \ m_initimg/initimg-solaris.c \ - m_initimg/initimg-pathscan.c \ m_mach/mach_basics.c \ m_mach/mach_msg.c \ m_mach/mach_traps-x86-darwin.S \ diff --git a/coregrind/m_debuginfo/readelf.c b/coregrind/m_debuginfo/readelf.c index 404034df06..3f5f48c703 100644 --- a/coregrind/m_debuginfo/readelf.c +++ b/coregrind/m_debuginfo/readelf.c @@ -36,9 +36,12 @@ #include "pub_core_libcbase.h" #include "pub_core_libcprint.h" #include "pub_core_libcassert.h" +#include "pub_core_libcfile.h" +#include "pub_core_libcproc.h" #include "pub_core_machine.h" /* VG_ELF_CLASS */ #include "pub_core_options.h" #include "pub_core_oset.h" +#include "pub_core_pathscan.h" /* find_executable */ #include "pub_core_syscall.h" #include "pub_core_tooliface.h" /* VG_(needs) */ #include "pub_core_xarray.h" @@ -1281,6 +1284,135 @@ DiImage* open_debug_file( const HChar* name, const HChar* buildid, UInt crc, return dimg; } +#if defined(VGO_linux) +/* Return the path of the debuginfod-find executable. */ +static +const HChar* debuginfod_find_path( void ) +{ + static const HChar* path = (const HChar*) -1; + + if (path == (const HChar*) -1) { + if (VG_(getenv)("DEBUGINFOD_URLS") == NULL + || VG_(strcmp)("", VG_(getenv("DEBUGINFOD_URLS"))) == 0) + path = NULL; + else + path = VG_(find_executable)("debuginfod-find"); + } + + return path; +} + +/* Try to find a separate debug file with |buildid| via debuginfod. If found, + return its DiImage. Searches for a local debuginfod-find executable and + runs it in a child process in order to download the debug file. */ +static +DiImage* find_debug_file_debuginfod( const HChar* objpath, + HChar** debugpath, + const HChar* buildid, + const UInt crc, Bool rel_ok ) +{ +# define BUF_SIZE 4096 + Int out_fds[2], err_fds[2]; /* pipe fds */ + DiImage* dimg = NULL; /* the img we found */ + HChar buf[BUF_SIZE]; /* executable output buffer */ + const HChar* path; /* executable path */ + SizeT len; /* number of bytes read int buf */ + Int ret; /* result of read call */ + + if (buildid == NULL) + return NULL; + + if ((path = debuginfod_find_path()) == NULL) + return NULL; + + if (VG_(pipe)(out_fds) != 0 + || VG_(pipe)(err_fds) != 0) + return NULL; + + if (VG_(clo_verbosity) > 1) + VG_(umsg)("Downloading debug info for %s...\n", objpath); + + /* Run debuginfod-find to query servers for the debuginfo. */ + Int pid = VG_(fork)(); + if (pid == 0) { + const HChar *argv[4] = { path, "debuginfo", buildid, (HChar*)0 }; + + /* Redirect stdout and stderr */ + SysRes res = VG_(dup2)(out_fds[1], 1); + if (sr_Res(res) < 0) + VG_(exit)(1); + + res = VG_(dup2)(err_fds[1], 2); + if (sr_Res(res) < 0) + VG_(exit)(1); + + /* Disable extra stderr output since it does not play well with umesg */ + VG_(env_unsetenv)(VG_(client_envp), "DEBUGINFOD_VERBOSE", NULL); + VG_(env_unsetenv)(VG_(client_envp), "DEBUGINFOD_PROGRESS", NULL); + + VG_(close)(out_fds[0]); + VG_(close)(err_fds[0]); + VG_(execv)(argv[0], argv); + + /* If we are still alive here, execv failed. */ + VG_(exit)(1); + } + + VG_(close)(out_fds[1]); + VG_(close)(err_fds[1]); + + if (pid < 0) { + if (VG_(clo_verbosity) > 1) + VG_(umsg)("Server Error\n"); + goto out; + } + VG_(waitpid)(pid, NULL, 0); + + /* Set dimg if download was successful. */ + len = 0; + ret = -1; + while (len >= 0 && len < BUF_SIZE) { + ret = VG_(read)(out_fds[0], buf + len, BUF_SIZE - len); + if (ret <= 0) + break; + len += ret; + } + if (ret >= 0 && len > 0 + && buf[0] == '/' && buf[len-1] == '\n') { + + /* Remove newline from filename before trying to open debug file */ + buf[len-1] = '\0'; + dimg = open_debug_file(buf, buildid, crc, rel_ok, NULL); + if (dimg != NULL) { + /* Success */ + if (*debugpath) + ML_(dinfo_free)(*debugpath); + + *debugpath = ML_(dinfo_strdup)("di.fdfd.1", buf); + if (VG_(clo_verbosity) > 1) + VG_(umsg)("Successfully downloaded debug file for %s\n", + objpath); + goto out; + } + } + + /* Download failed so try to print error message. */ + HChar* nl; + if (VG_(read)(err_fds[0], buf, BUF_SIZE) > 0 + && (nl = VG_(strchr)(buf, '\n'))) { + *nl = '\0'; + if (VG_(clo_verbosity) > 1) + VG_(umsg)("%s\n", buf); + } else + if (VG_(clo_verbosity) > 1) + VG_(umsg)("Server Error\n"); + +out: + VG_(close)(out_fds[0]); + VG_(close)(err_fds[0]); + return dimg; +} +#endif /* Try to find a separate debug file for a given object file. If found, return its DiImage, which should be freed by the caller. If @@ -1381,6 +1513,11 @@ DiImage* find_debug_file( struct _DebugInfo* di, ML_(dinfo_free)(objdir); } +# if defined(VGO_linux) + if (dimg == NULL) + dimg = find_debug_file_debuginfod(objpath, &debugpath, buildid, crc, rel_ok); +# endif + if (dimg != NULL) { vg_assert(debugpath); TRACE_SYMTAB("\n"); diff --git a/coregrind/m_initimg/initimg-darwin.c b/coregrind/m_initimg/initimg-darwin.c index e2662476ad..564d2fdaa7 100644 --- a/coregrind/m_initimg/initimg-darwin.c +++ b/coregrind/m_initimg/initimg-darwin.c @@ -46,7 +46,7 @@ #include "pub_core_options.h" #include "pub_core_tooliface.h" /* VG_TRACK */ #include "pub_core_threadstate.h" /* ThreadArchState */ -#include "priv_initimg_pathscan.h" +#include "pub_core_pathscan.h" /* find_executable */ #include "pub_core_initimg.h" /* self */ @@ -64,7 +64,7 @@ static void load_client ( /*OUT*/ExeInfo* info, SysRes res; vg_assert( VG_(args_the_exename) != NULL); - exe_name = ML_(find_executable)( VG_(args_the_exename) ); + exe_name = VG_(find_executable)( VG_(args_the_exename) ); if (!exe_name) { VG_(printf)("valgrind: %s: command not found\n", VG_(args_the_exename)); diff --git a/coregrind/m_initimg/initimg-linux.c b/coregrind/m_initimg/initimg-linux.c index cb7902446a..73dab34363 100644 --- a/coregrind/m_initimg/initimg-linux.c +++ b/coregrind/m_initimg/initimg-linux.c @@ -47,7 +47,7 @@ #include "pub_core_syscall.h" #include "pub_core_tooliface.h" /* VG_TRACK */ #include "pub_core_threadstate.h" /* ThreadArchState */ -#include "priv_initimg_pathscan.h" +#include "pub_core_pathscan.h" /* find_executable */ #include "pub_core_initimg.h" /* self */ /* --- !!! --- EXTERNAL HEADERS start --- !!! --- */ @@ -73,7 +73,7 @@ static void load_client ( /*MOD*/ExeInfo* info, SysRes res; vg_assert( VG_(args_the_exename) != NULL); - exe_name = ML_(find_executable)( VG_(args_the_exename) ); + exe_name = VG_(find_executable)( VG_(args_the_exename) ); if (!exe_name) { VG_(printf)("valgrind: %s: command not found\n", VG_(args_the_exename)); diff --git a/coregrind/m_initimg/initimg-solaris.c b/coregrind/m_initimg/initimg-solaris.c index 0a6c385b76..3e473c8803 100644 --- a/coregrind/m_initimg/initimg-solaris.c +++ b/coregrind/m_initimg/initimg-solaris.c @@ -51,7 +51,7 @@ #include "pub_core_syswrap.h" #include "pub_core_tooliface.h" /* VG_TRACK */ #include "pub_core_threadstate.h" /* ThreadArchState */ -#include "priv_initimg_pathscan.h" +#include "pub_core_pathscan.h" /* find_executable */ #include "pub_core_initimg.h" /* self */ @@ -68,7 +68,7 @@ static void load_client(/*OUT*/ExeInfo *info, SysRes res; vg_assert(VG_(args_the_exename)); - exe_name = ML_(find_executable)(VG_(args_the_exename)); + exe_name = VG_(find_executable)(VG_(args_the_exename)); if (!exe_name) { VG_(printf)("valgrind: %s: command not found\n", VG_(args_the_exename)); diff --git a/coregrind/m_initimg/initimg-pathscan.c b/coregrind/m_pathscan.c similarity index 95% rename from coregrind/m_initimg/initimg-pathscan.c rename to coregrind/m_pathscan.c index f5f825f3b1..02b371486e 100644 --- a/coregrind/m_initimg/initimg-pathscan.c +++ b/coregrind/m_pathscan.c @@ -1,6 +1,6 @@ /*--------------------------------------------------------------------*/ -/*--- Startup: search PATH for an executable initimg-pathscan.c ---*/ +/*--- search PATH for an executable m_pathscan.c ---*/ /*--------------------------------------------------------------------*/ /* @@ -33,9 +33,7 @@ #include "pub_core_libcproc.h" #include "pub_core_libcprint.h" #include "pub_core_mallocfree.h" -#include "pub_core_initimg.h" /* self */ - -#include "priv_initimg_pathscan.h" +#include "pub_core_pathscan.h" /* self */ /*====================================================================*/ @@ -117,7 +115,7 @@ static Bool match_executable(const HChar *entry) } // Returns NULL if it wasn't found. -const HChar* ML_(find_executable) ( const HChar* exec ) +const HChar* VG_(find_executable) ( const HChar* exec ) { vg_assert(NULL != exec); diff --git a/coregrind/m_initimg/priv_initimg_pathscan.h b/coregrind/pub_core_pathscan.h similarity index 78% rename from coregrind/m_initimg/priv_initimg_pathscan.h rename to coregrind/pub_core_pathscan.h index e6ffb4a8b0..d84953ab21 100644 --- a/coregrind/m_initimg/priv_initimg_pathscan.h +++ b/coregrind/pub_core_pathscan.h @@ -1,7 +1,7 @@ /*--------------------------------------------------------------------*/ -/*--- Startup: search PATH for an executable ---*/ -/*--- priv_initimg-pathscan.h ---*/ +/*--- Search PATH for an executable ---*/ +/*--- m_pathscan.h ---*/ /*--------------------------------------------------------------------*/ /* @@ -27,11 +27,11 @@ The GNU General Public License is contained in the file COPYING. */ -#ifndef __PRIV_INITIMG_PATHSCAN_H -#define __PRIV_INITIMG_PATHSCAN_H +#ifndef __PUB_CORE_PATHSCAN_H +#define __PUB_CORE_PATHSCAN_H #include "pub_core_basics.h" // HChar -extern const HChar* ML_(find_executable) ( const HChar* exec ); +extern const HChar* VG_(find_executable) ( const HChar* exec ); -#endif // ndef __PRIV_INITIMG_PATHSCAN_H +#endif // ndef __PUB_CORE_PATHSCAN_H diff --git a/docs/xml/manual-core.xml b/docs/xml/manual-core.xml index 5d52d2e3be..dc33e12696 100644 --- a/docs/xml/manual-core.xml +++ b/docs/xml/manual-core.xml @@ -592,6 +592,30 @@ to malloc.. + +Debuginfod + +Valgrind supports the downloading of debuginfo +files via debuginfod, an HTTP server for distributing ELF/DWARF debugging +information. When a debuginfo file cannot be found locally, Valgrind is able +to query debuginfod servers for the file using its build-id. + +In order to use this feature +debuginfod-find must be installed and +$DEBUGINFOD_URLS must contain URLs of +debuginfod servers. Valgrind does not support +debuginfod-find verbose output that is +normally enabled with $DEBUGINFOD_PROGRESS +and $DEBUGINFOD_VERBOSE. These environment +variables will be ignored. + +For more information regarding debuginfod, see +https://sourceware.org/elfutils/Debuginfod.html + + + + Core Command-line Options @@ -1462,6 +1486,10 @@ that can report errors, e.g. Memcheck, but not Cachegrind. you want to use it, you will have to recompile it by hand using the command shown at the top of auxprogs/valgrind-di-server.c. + + Valgrind can also download debuginfo via debuginfod. See the + DEBUGINFOD section for more information. + diff --git a/docs/xml/valgrind-manpage.xml b/docs/xml/valgrind-manpage.xml index 3c893c680e..5c16c6eb35 100644 --- a/docs/xml/valgrind-manpage.xml +++ b/docs/xml/valgrind-manpage.xml @@ -221,6 +221,19 @@ system: &vg-docs-path;, or online: + +Debuginfod + + + + + See Also diff --git a/memcheck/tests/linux/Makefile.am b/memcheck/tests/linux/Makefile.am index 7e796aa7fa..90a24e4abe 100644 --- a/memcheck/tests/linux/Makefile.am +++ b/memcheck/tests/linux/Makefile.am @@ -6,6 +6,7 @@ dist_noinst_SCRIPTS = filter_stderr EXTRA_DIST = \ brk.stderr.exp brk.vgtest \ capget.vgtest capget.stderr.exp capget.stderr.exp2 capget.stderr.exp3 \ + debuginfod-check.stderr.exp debuginfod-check.vgtest.in \ dlclose_leak-no-keep.stderr.exp dlclose_leak-no-keep.stdout.exp \ dlclose_leak-no-keep.vgtest \ dlclose_leak.stderr.exp dlclose_leak.stdout.exp \ @@ -37,6 +38,7 @@ check_PROGRAMS = \ brk \ capget \ check_preadv2_pwritev2 \ + debuginfod-check \ dlclose_leak dlclose_leak_so.so \ ioctl-tiocsig \ getregset \ diff --git a/memcheck/tests/linux/debuginfod-check.c b/memcheck/tests/linux/debuginfod-check.c new file mode 100644 index 0000000000..185139a8d8 --- /dev/null +++ b/memcheck/tests/linux/debuginfod-check.c @@ -0,0 +1,7 @@ +#include + +int main() { + char *p = malloc(1); + p[-1] = 0; + return 0; +} diff --git a/memcheck/tests/linux/debuginfod-check.pl b/memcheck/tests/linux/debuginfod-check.pl new file mode 100755 index 0000000000..4a2c1c1e1d --- /dev/null +++ b/memcheck/tests/linux/debuginfod-check.pl @@ -0,0 +1,116 @@ +#! /usr/bin/perl +use warnings; +use strict; +use Cwd; +use IPC::Open3; + +our $dir = Cwd::realpath("./.debuginfod"); +our $pid = 0; + +# Kill the server and remove temporary files. +sub cleanup_and_exit($) { + my $exit_code = $_[0]; + mysystem("rm -rf $dir"); + if ($pid != 0) { + kill "INT", $pid; + } + exit $exit_code; +} + +# Propagate Ctrl-C and exit if command results in an error. +sub mysystem($) +{ + my $exit_code = system($_[0]); + ($exit_code == 2) and cleanup_and_exit(1); # Exit if SIGINT + if ($exit_code != 0) { + #warn "Error while executing: $_[0]"; + cleanup_and_exit(1); + } + return $exit_code; +} + +# Check that debuginfod and debuginfod-find can be found +mysystem("debuginfod --help > /dev/null"); +mysystem("debuginfod-find --help > /dev/null"); + +$SIG{'INT'} = sub { cleanup_and_exit(1) }; + +my $testname = "debuginfod-check"; +my $tmp = "$dir/debuginfod_test.tmp"; +my $dbg = "$dir/dbg"; +mysystem("rm -rf $dir"); +mysystem("mkdir -p $dbg"); + +# Compile the test executable, strip its debuginfo and store it so +# that valgrind cannot find it without debuginfod. +mysystem("gcc -O0 -g -o $testname $testname.c"); +mysystem("objcopy --only-keep-debug $testname $testname.debug"); +mysystem("objcopy --strip-unneeded $testname"); +mysystem("objcopy --add-gnu-debuglink=$testname.debug $testname"); +mysystem("mv $testname.debug $dbg"); +mysystem("readelf -n $testname > $tmp 2>&1"); + +my $buildid = ""; +open(TMP, '<', $tmp); +while (my $out = ) { + if ($out =~ /Build ID: ([0-9a-f]*)/) { + $buildid = $1; + } +} + +if ($buildid eq "") { + warn "can't find $testname build-id"; + cleanup_and_exit(1); +} + +my $found_port = 0; +my $port = 7999; + +# Find an unused port +while ($found_port == 0 and $port < 65536) { + $port++; + $pid = open3(undef, "TMP", undef, + "debuginfod", "-d", "$dir/db", '-F', "$dbg", "--port=$port"); + for (my $i = 0; $i < 5 and $found_port == 0; $i++) { + while (my $got = ) { + if ($got =~ /Failed to bind/) { + last; + } elsif ($got =~ /started http server/) { + $found_port = 1; + last; + } + } + } +} + +if ($port == 65536) { + warn "No available ports"; + cleanup_and_exit(1); +} + +my $server_ready = 0; + +# Confirm that the server is ready to be queried +for (my $i = 0; $i < 10 and $server_ready == 0; $i++) { + sleep 1; + my $got = `curl -s http://localhost:$port/metrics`; + if ($got =~ /ready 1/ + and $got =~ /thread_work_total\{role=\"traverse\"\} 1/ + and $got =~ /thread_work_pending\{role=\"scan\"\} 0/ + and $got =~ /thread_busy\{role=\"scan\"\} 0/) { + $server_ready = 1; + } +} + +if ($server_ready == 0) { + warn "Can't start debuginfod server"; + cleanup_and_exit(1); +} + +# Query the server and store the debuginfo in the client cache for valgrind to find. +my $myres = mysystem("DEBUGINFOD_CACHE_PATH=$dir DEBUGINFOD_URLS=http://localhost:$port debuginfod-find debuginfo $buildid > /dev/null 2>&1"); +if ($myres != 0) { + cleanup_and_exit(1); +} +kill "INT", $pid; +exit 0; diff --git a/memcheck/tests/linux/debuginfod-check.stderr.exp b/memcheck/tests/linux/debuginfod-check.stderr.exp new file mode 100644 index 0000000000..98236beeb3 --- /dev/null +++ b/memcheck/tests/linux/debuginfod-check.stderr.exp @@ -0,0 +1,6 @@ +Invalid write of size 1 + at 0x........: main (debuginfod-check.c:5) + Address 0x........ is 1 bytes before a block of size 1 alloc'd + at 0x........: malloc (vg_replace_malloc.c:...) + by 0x........: main (debuginfod-check.c:4) + diff --git a/memcheck/tests/linux/debuginfod-check.vgtest.in b/memcheck/tests/linux/debuginfod-check.vgtest.in new file mode 100644 index 0000000000..308e16ca9b --- /dev/null +++ b/memcheck/tests/linux/debuginfod-check.vgtest.in @@ -0,0 +1,4 @@ +prereq: ./debuginfod-check.pl +prog: debuginfod-check +vgopts: -q +env: DEBUGINFOD_URLS=localhost DEBUGINFOD_CACHE_PATH=@abs_top_builddir@/memcheck/tests/linux/.debuginfod diff --git a/tests/vg_regtest.in b/tests/vg_regtest.in index c7cc60124b..0fe63411a1 100755 --- a/tests/vg_regtest.in +++ b/tests/vg_regtest.in @@ -699,6 +699,8 @@ sub warn_about_EXTRA_REGTEST_OPTS() # nuke VALGRIND_OPTS $ENV{"VALGRIND_OPTS"} = ""; +# nuke DEBUGINFOD_URLS +$ENV{"DEBUGINFOD_URLS"} = ""; if ($ENV{"EXTRA_REGTEST_OPTS"}) { print "\n";