Index: compiler-rt/trunk/include/CMakeLists.txt =================================================================== --- compiler-rt/trunk/include/CMakeLists.txt +++ compiler-rt/trunk/include/CMakeLists.txt @@ -10,6 +10,7 @@ sanitizer/linux_syscall_hooks.h sanitizer/lsan_interface.h sanitizer/msan_interface.h + sanitizer/netbsd_syscall_hooks.h sanitizer/scudo_interface.h sanitizer/tsan_interface.h sanitizer/tsan_interface_atomic.h) Index: compiler-rt/trunk/include/sanitizer/netbsd_syscall_hooks.h =================================================================== --- compiler-rt/trunk/include/sanitizer/netbsd_syscall_hooks.h +++ compiler-rt/trunk/include/sanitizer/netbsd_syscall_hooks.h @@ -0,0 +1,45 @@ +//===-- netbsd_syscall_hooks.h --------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of public sanitizer interface. +// +// System call handlers. +// +// Interface methods declared in this header implement pre- and post- syscall +// actions for the active sanitizer. +// Usage: +// __sanitizer_syscall_pre_getfoo(...args...); +// long long res = syscall(SYS_getfoo, ...args...); +// __sanitizer_syscall_post_getfoo(res, ...args...); +// +// DO NOT EDIT! THIS FILE HAS BEEN GENERATED! +// +// Generated with: generate_netbsd_syscalls.awk +// Generated date: 2018-02-15 +// Generated from: syscalls.master,v 1.291 2018/01/06 16:41:23 kamil Exp +// +//===----------------------------------------------------------------------===// +#ifndef SANITIZER_NETBSD_SYSCALL_HOOKS_H +#define SANITIZER_NETBSD_SYSCALL_HOOKS_H + +#ifdef __cplusplus +extern "C" { +#endif + +// Private declarations. Do not call directly from user code. Use macros above. + +// DO NOT EDIT! THIS FILE HAS BEEN GENERATED! + +#ifdef __cplusplus +} // extern "C" +#endif + +// DO NOT EDIT! THIS FILE HAS BEEN GENERATED! + +#endif // SANITIZER_NETBSD_SYSCALL_HOOKS_H Index: compiler-rt/trunk/lib/asan/asan_interceptors.cc =================================================================== --- compiler-rt/trunk/lib/asan/asan_interceptors.cc +++ compiler-rt/trunk/lib/asan/asan_interceptors.cc @@ -178,6 +178,7 @@ (void)(s); \ } while (false) #include "sanitizer_common/sanitizer_common_syscalls.inc" +#include "sanitizer_common/sanitizer_syscalls_netbsd.inc" struct ThreadStartParam { atomic_uintptr_t t; Index: compiler-rt/trunk/lib/esan/esan_interceptors.cpp =================================================================== --- compiler-rt/trunk/lib/esan/esan_interceptors.cpp +++ compiler-rt/trunk/lib/esan/esan_interceptors.cpp @@ -232,6 +232,7 @@ } while (false) #include "sanitizer_common/sanitizer_common_syscalls.inc" +#include "sanitizer_common/sanitizer_syscalls_netbsd.inc" //===----------------------------------------------------------------------===// // Custom interceptors Index: compiler-rt/trunk/lib/hwasan/hwasan_interceptors.cc =================================================================== --- compiler-rt/trunk/lib/hwasan/hwasan_interceptors.cc +++ compiler-rt/trunk/lib/hwasan/hwasan_interceptors.cc @@ -457,6 +457,7 @@ (void)(s); \ } while (false) #include "sanitizer_common/sanitizer_common_syscalls.inc" +#include "sanitizer_common/sanitizer_syscalls_netbsd.inc" Index: compiler-rt/trunk/lib/msan/msan_interceptors.cc =================================================================== --- compiler-rt/trunk/lib/msan/msan_interceptors.cc +++ compiler-rt/trunk/lib/msan/msan_interceptors.cc @@ -1392,6 +1392,7 @@ } while (false) #define COMMON_SYSCALL_POST_WRITE_RANGE(p, s) __msan_unpoison(p, s) #include "sanitizer_common/sanitizer_common_syscalls.inc" +#include "sanitizer_common/sanitizer_syscalls_netbsd.inc" struct dlinfo { char *dli_fname; Index: compiler-rt/trunk/lib/sanitizer_common/CMakeLists.txt =================================================================== --- compiler-rt/trunk/lib/sanitizer_common/CMakeLists.txt +++ compiler-rt/trunk/lib/sanitizer_common/CMakeLists.txt @@ -143,6 +143,7 @@ sanitizer_syscall_generic.inc sanitizer_syscall_linux_x86_64.inc sanitizer_syscall_linux_aarch64.inc + sanitizer_syscalls_netbsd.inc sanitizer_thread_registry.h sanitizer_vector.h sanitizer_win.h) Index: compiler-rt/trunk/lib/sanitizer_common/sanitizer_syscalls_netbsd.inc =================================================================== --- compiler-rt/trunk/lib/sanitizer_common/sanitizer_syscalls_netbsd.inc +++ compiler-rt/trunk/lib/sanitizer_common/sanitizer_syscalls_netbsd.inc @@ -0,0 +1,107 @@ +//===-- sanitizer_syscalls_netbsd.inc ---------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Common syscalls handlers for tools like AddressSanitizer, +// ThreadSanitizer, MemorySanitizer, etc. +// +// This file should be included into the tool's interceptor file, +// which has to define it's own macros: +// COMMON_SYSCALL_PRE_READ_RANGE +// Called in prehook for regions that will be read by the kernel and +// must be initialized. +// COMMON_SYSCALL_PRE_WRITE_RANGE +// Called in prehook for regions that will be written to by the kernel +// and must be addressable. The actual write range may be smaller than +// reported in the prehook. See POST_WRITE_RANGE. +// COMMON_SYSCALL_POST_READ_RANGE +// Called in posthook for regions that were read by the kernel. Does +// not make much sense. +// COMMON_SYSCALL_POST_WRITE_RANGE +// Called in posthook for regions that were written to by the kernel +// and are now initialized. +// COMMON_SYSCALL_ACQUIRE(addr) +// Acquire memory visibility from addr. +// COMMON_SYSCALL_RELEASE(addr) +// Release memory visibility to addr. +// COMMON_SYSCALL_FD_CLOSE(fd) +// Called before closing file descriptor fd. +// COMMON_SYSCALL_FD_ACQUIRE(fd) +// Acquire memory visibility from fd. +// COMMON_SYSCALL_FD_RELEASE(fd) +// Release memory visibility to fd. +// COMMON_SYSCALL_PRE_FORK() +// Called before fork syscall. +// COMMON_SYSCALL_POST_FORK(long long res) +// Called after fork syscall. +// +// DO NOT EDIT! THIS FILE HAS BEEN GENERATED! +// +// Generated with: generate_netbsd_syscalls.awk +// Generated date: 2018-02-15 +// Generated from: syscalls.master,v 1.291 2018/01/06 16:41:23 kamil Exp +// +//===----------------------------------------------------------------------===// + +#include "sanitizer_platform.h" +#if SANITIZER_NETBSD + +#include "sanitizer_libc.h" + +#define PRE_SYSCALL(name) \ + SANITIZER_INTERFACE_ATTRIBUTE void __sanitizer_syscall_pre_impl_##name +#define PRE_READ(p, s) COMMON_SYSCALL_PRE_READ_RANGE(p, s) +#define PRE_WRITE(p, s) COMMON_SYSCALL_PRE_WRITE_RANGE(p, s) + +#define POST_SYSCALL(name) \ + SANITIZER_INTERFACE_ATTRIBUTE void __sanitizer_syscall_post_impl_##name +#define POST_READ(p, s) COMMON_SYSCALL_POST_READ_RANGE(p, s) +#define POST_WRITE(p, s) COMMON_SYSCALL_POST_WRITE_RANGE(p, s) + +#ifndef COMMON_SYSCALL_ACQUIRE +#define COMMON_SYSCALL_ACQUIRE(addr) ((void)(addr)) +#endif + +#ifndef COMMON_SYSCALL_RELEASE +#define COMMON_SYSCALL_RELEASE(addr) ((void)(addr)) +#endif + +#ifndef COMMON_SYSCALL_FD_CLOSE +#define COMMON_SYSCALL_FD_CLOSE(fd) ((void)(fd)) +#endif + +#ifndef COMMON_SYSCALL_FD_ACQUIRE +#define COMMON_SYSCALL_FD_ACQUIRE(fd) ((void)(fd)) +#endif + +#ifndef COMMON_SYSCALL_FD_RELEASE +#define COMMON_SYSCALL_FD_RELEASE(fd) ((void)(fd)) +#endif + +#ifndef COMMON_SYSCALL_PRE_FORK +#define COMMON_SYSCALL_PRE_FORK() \ + {} +#endif + +#ifndef COMMON_SYSCALL_POST_FORK +#define COMMON_SYSCALL_POST_FORK(res) \ + {} +#endif + +// FIXME: do some kind of PRE_READ for all syscall arguments (int(s) and such). + +extern "C" {} // extern "C" + +#undef PRE_SYSCALL +#undef PRE_READ +#undef PRE_WRITE +#undef POST_SYSCALL +#undef POST_READ +#undef POST_WRITE + +#endif // SANITIZER_NETBSD Index: compiler-rt/trunk/lib/tsan/rtl/tsan_interceptors.cc =================================================================== --- compiler-rt/trunk/lib/tsan/rtl/tsan_interceptors.cc +++ compiler-rt/trunk/lib/tsan/rtl/tsan_interceptors.cc @@ -2517,6 +2517,7 @@ syscall_post_fork(GET_CALLER_PC(), res) #include "sanitizer_common/sanitizer_common_syscalls.inc" +#include "sanitizer_common/sanitizer_syscalls_netbsd.inc" #ifdef NEED_TLS_GET_ADDR // Define own interceptor instead of sanitizer_common's for three reasons: Index: compiler-rt/trunk/utils/generate_netbsd_syscalls.awk =================================================================== --- compiler-rt/trunk/utils/generate_netbsd_syscalls.awk +++ compiler-rt/trunk/utils/generate_netbsd_syscalls.awk @@ -0,0 +1,416 @@ +#!/usr/bin/awk -f + +#===-- generate_netbsd_syscalls.awk ----------------------------------------===# +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +#===------------------------------------------------------------------------===# +# +# This file is a generator of: +# - include/sanitizer/netbsd_syscall_hooks.h +# - lib/sanitizer_common/sanitizer_syscalls_netbsd.inc +# +# This script accepts on the input syscalls.master by default located in the +# /usr/src/sys/kern/syscalls.master path in the NetBSD distribution. +# +# NetBSD version 8.0. +# +#===------------------------------------------------------------------------===# + +BEGIN { + # harcode the script name + script_name = "generate_netbsd_syscalls.awk" + outputh = "../include/sanitizer/netbsd_syscall_hooks.h" + outputinc = "../lib/sanitizer_common/sanitizer_syscalls_netbsd.inc" + + # assert that we are in the directory with scripts + in_utils = system("test -f " script_name " && exit 1 || exit 0") + if (in_utils == 0) { + usage() + } + + # assert 1 argument passed + if (ARGC != 2) { + usage() + } + + # assert argument is a valid file path to syscall.master + if (system("test -f " ARGV[1]) != 0) { + usage() + } + + # sanity check that the path ends with "syscall.master" + if (ARGV[1] !~ /syscalls\.master$/) { + usage() + } + + # accept overloading CLANGFORMAT from environment + clangformat = "clang-format" + if ("CLANGFORMAT" in ENVIRON) { + clangformat = ENVIRON["CLANGFORMAT"] + } + + # parsing specific symbols + parsingheader=1 + + parsedsyscalls=0 + + # Hardcoded in algorithm + SYS_MAXSYSARGS=8 +} + +# Parse the RCS ID from syscall.master +parsingheader == 1 && NR == 1 { + if (match($0, /\$[^$]+\$/)) { + # trim initial 'NetBSD: ' and trailing ' $' + syscallmasterversion = substr($0, RSTART + 9, RLENGTH - 11) + } else { + # wrong file? + usage() + } +} + +# skip the following lines +# - empty +NF == 0 { + next +} +# - comment +$1 == ";" { + next +} + +# separator between the header and table with syscalls +$0 == "%%" { + parsingheader = 0 + next +} + +# preserve 'if/elif/else/endif' C preprocessor as-is +parsingheader == 0 && $0 ~ /^#/ { + if (parsedsyscalls in ifelifelseendif) { + ifelifelseendif[parsedsyscalls] = ifelifelseendif[parsedsyscalls] "\n" $0 + } else { + ifelifelseendif[parsedsyscalls] = $0 + } + next +} + +# parsing of syscall definitions +parsingheader == 0 && $1 ~ /^[0-9]+$/ { + # first join multiple lines into single one + while (sub(/\\$/, "")) { + getline line + $0 = $0 "" line + } + + # Skip unwanted syscalls + skip=0 + if ($0 ~ /OBSOL/ || $0 ~ /EXCL/ || $0 ~ /UNIMPL/) { + skip=1 + } + + # Compose the syscall name + # - compat? + compat="" + if (match($0, /COMPAT_[0-9]+/)) { + compat = tolower(substr($0, RSTART, RLENGTH)) + } + # - alias name? + alias="" + if ($(NF) != "}" && !skip) { + alias = alias "" $(NF) + } + # - compat version? + compatver="" + if (match($0, /\|[0-9]+\|/)) { + compatver = tolower(substr($0, RSTART + 1, RLENGTH - 2)) + } + # - basename? + basename="" + if (skip) { + basename = $1 + } else { + if (match($0, /\|[_a-z0-9]+\(/)) { + basename = tolower(substr($0, RSTART + 1, RLENGTH - 2)) + } + } + + syscallname="" + + if (skip) { + syscallname= syscallname "$" + } + + if (length(compat) > 0) { + syscallname = syscallname "" compat "_" + } + if (length(alias) > 0) { + syscallname = syscallname "" alias + } else { + if (length(compatver) > 0) { + syscallname = syscallname "__" basename "" compatver + } else { + syscallname = syscallname "" basename + } + } + + # Store the syscallname + syscalls[parsedsyscalls]=syscallname + + # Extract syscall arguments + if (match($0, /\([^)]+\)/)) { + args = substr($0, RSTART + 1, RLENGTH - 2) + + if (args == "void") { + syscallargs[parsedsyscalls] = "void" + syscallfullargs[parsedsyscalls] = "void" + } else { + # Normalize 'type * argument' to 'type *argument' + gsub("\\*[ \t]+", "*", args) + + n = split(args, a, ",") + + # Handle the first argument + match(a[1], /[*_a-z0-9\[\]]+$/) + syscallfullargs[parsedsyscalls] = substr(a[1], RSTART) "_" + + gsub(".+[ *]", "", a[1]) + syscallargs[parsedsyscalls] = a[1] + + # Handle the rest of arguments + for (i = 2; i <= n; i++) { + match(a[i], /[*_a-zA-Z0-9\[\]]+$/) + fs = substr(a[i], RSTART) + if (fs ~ /\[/) { + sub(/\[/, "_[", fs) + } else { + fs = fs "_" + } + syscallfullargs[parsedsyscalls] = syscallfullargs[parsedsyscalls] "$" fs + gsub(".+[ *]", "", a[i]) + syscallargs[parsedsyscalls] = syscallargs[parsedsyscalls] "$" a[i] + } + + # Handle array arguments for syscall(2) and __syscall(2) + nargs = "arg0$arg1$arg2$arg3$arg4$arg5$arg6$arg7" + gsub(/args\[SYS_MAXSYSARGS\]/, nargs, syscallargs[parsedsyscalls]) + } + } + + parsedsyscalls++ + + # Done with this line + next +} + + +END { + # empty file? + if (NR < 1 && !abnormal_exit) { + usage() + } + + # Handle abnormal exit + if (abnormal_exit) { + exit(abnormal_exit) + } + + # Generate sanitizer_syscalls_netbsd.inc + + # open pipe + cmd = clangformat " > " outputh + + pcmd("//===-- netbsd_syscall_hooks.h --------------------------------------------===//") + pcmd("//") + pcmd("// The LLVM Compiler Infrastructure") + pcmd("//") + pcmd("// This file is distributed under the University of Illinois Open Source") + pcmd("// License. See LICENSE.TXT for details.") + pcmd("//") + pcmd("//===----------------------------------------------------------------------===//") + pcmd("//") + pcmd("// This file is a part of public sanitizer interface.") + pcmd("//") + pcmd("// System call handlers.") + pcmd("//") + pcmd("// Interface methods declared in this header implement pre- and post- syscall") + pcmd("// actions for the active sanitizer.") + pcmd("// Usage:") + pcmd("// __sanitizer_syscall_pre_getfoo(...args...);") + pcmd("// long long res = syscall(SYS_getfoo, ...args...);") + pcmd("// __sanitizer_syscall_post_getfoo(res, ...args...);") + pcmd("//") + pcmd("// DO NOT EDIT! THIS FILE HAS BEEN GENERATED!") + pcmd("//") + pcmd("// Generated with: " script_name) + pcmd("// Generated date: " strftime("%F")) + pcmd("// Generated from: " syscallmasterversion) + pcmd("//") + pcmd("//===----------------------------------------------------------------------===//") + pcmd("#ifndef SANITIZER_NETBSD_SYSCALL_HOOKS_H") + pcmd("#define SANITIZER_NETBSD_SYSCALL_HOOKS_H") + pcmd("") + + # TODO + + pcmd("") + pcmd("#ifdef __cplusplus") + pcmd("extern \"C\" {") + pcmd("#endif") + pcmd("") + pcmd("// Private declarations. Do not call directly from user code. Use macros above.") + pcmd("") + pcmd("// DO NOT EDIT! THIS FILE HAS BEEN GENERATED!") + pcmd("") + + # TODO + + pcmd("") + pcmd("#ifdef __cplusplus") + pcmd("} // extern \"C\"") + pcmd("#endif") + + pcmd("") + pcmd("// DO NOT EDIT! THIS FILE HAS BEEN GENERATED!") + pcmd("") + + pcmd("#endif // SANITIZER_NETBSD_SYSCALL_HOOKS_H") + + close(cmd) + + # Generate sanitizer_syscalls_netbsd.inc + + # open pipe + cmd = clangformat " > " outputinc + + pcmd("//===-- sanitizer_syscalls_netbsd.inc ---------------------------*- C++ -*-===//") + pcmd("//") + pcmd("// The LLVM Compiler Infrastructure") + pcmd("//") + pcmd("// This file is distributed under the University of Illinois Open Source") + pcmd("// License. See LICENSE.TXT for details.") + pcmd("//") + pcmd("//===----------------------------------------------------------------------===//") + pcmd("//") + pcmd("// Common syscalls handlers for tools like AddressSanitizer,") + pcmd("// ThreadSanitizer, MemorySanitizer, etc.") + pcmd("//") + pcmd("// This file should be included into the tool's interceptor file,") + pcmd("// which has to define it's own macros:") + pcmd("// COMMON_SYSCALL_PRE_READ_RANGE") + pcmd("// Called in prehook for regions that will be read by the kernel and") + pcmd("// must be initialized.") + pcmd("// COMMON_SYSCALL_PRE_WRITE_RANGE") + pcmd("// Called in prehook for regions that will be written to by the kernel") + pcmd("// and must be addressable. The actual write range may be smaller than") + pcmd("// reported in the prehook. See POST_WRITE_RANGE.") + pcmd("// COMMON_SYSCALL_POST_READ_RANGE") + pcmd("// Called in posthook for regions that were read by the kernel. Does") + pcmd("// not make much sense.") + pcmd("// COMMON_SYSCALL_POST_WRITE_RANGE") + pcmd("// Called in posthook for regions that were written to by the kernel") + pcmd("// and are now initialized.") + pcmd("// COMMON_SYSCALL_ACQUIRE(addr)") + pcmd("// Acquire memory visibility from addr.") + pcmd("// COMMON_SYSCALL_RELEASE(addr)") + pcmd("// Release memory visibility to addr.") + pcmd("// COMMON_SYSCALL_FD_CLOSE(fd)") + pcmd("// Called before closing file descriptor fd.") + pcmd("// COMMON_SYSCALL_FD_ACQUIRE(fd)") + pcmd("// Acquire memory visibility from fd.") + pcmd("// COMMON_SYSCALL_FD_RELEASE(fd)") + pcmd("// Release memory visibility to fd.") + pcmd("// COMMON_SYSCALL_PRE_FORK()") + pcmd("// Called before fork syscall.") + pcmd("// COMMON_SYSCALL_POST_FORK(long long res)") + pcmd("// Called after fork syscall.") + pcmd("//") + pcmd("// DO NOT EDIT! THIS FILE HAS BEEN GENERATED!") + pcmd("//") + pcmd("// Generated with: " script_name) + pcmd("// Generated date: " strftime("%F")) + pcmd("// Generated from: " syscallmasterversion) + pcmd("//") + pcmd("//===----------------------------------------------------------------------===//") + pcmd("") + pcmd("#include \"sanitizer_platform.h\"") + pcmd("#if SANITIZER_NETBSD") + pcmd("") + pcmd("#include \"sanitizer_libc.h\"") + pcmd("") + pcmd("#define PRE_SYSCALL(name) \\") + pcmd(" SANITIZER_INTERFACE_ATTRIBUTE void __sanitizer_syscall_pre_impl_##name") + pcmd("#define PRE_READ(p, s) COMMON_SYSCALL_PRE_READ_RANGE(p, s)") + pcmd("#define PRE_WRITE(p, s) COMMON_SYSCALL_PRE_WRITE_RANGE(p, s)") + pcmd("") + pcmd("#define POST_SYSCALL(name) \\") + pcmd(" SANITIZER_INTERFACE_ATTRIBUTE void __sanitizer_syscall_post_impl_##name") + pcmd("#define POST_READ(p, s) COMMON_SYSCALL_POST_READ_RANGE(p, s)") + pcmd("#define POST_WRITE(p, s) COMMON_SYSCALL_POST_WRITE_RANGE(p, s)") + pcmd("") + pcmd("#ifndef COMMON_SYSCALL_ACQUIRE") + pcmd("# define COMMON_SYSCALL_ACQUIRE(addr) ((void)(addr))") + pcmd("#endif") + pcmd("") + pcmd("#ifndef COMMON_SYSCALL_RELEASE") + pcmd("# define COMMON_SYSCALL_RELEASE(addr) ((void)(addr))") + pcmd("#endif") + pcmd("") + pcmd("#ifndef COMMON_SYSCALL_FD_CLOSE") + pcmd("# define COMMON_SYSCALL_FD_CLOSE(fd) ((void)(fd))") + pcmd("#endif") + pcmd("") + pcmd("#ifndef COMMON_SYSCALL_FD_ACQUIRE") + pcmd("# define COMMON_SYSCALL_FD_ACQUIRE(fd) ((void)(fd))") + pcmd("#endif") + pcmd("") + pcmd("#ifndef COMMON_SYSCALL_FD_RELEASE") + pcmd("# define COMMON_SYSCALL_FD_RELEASE(fd) ((void)(fd))") + pcmd("#endif") + pcmd("") + pcmd("#ifndef COMMON_SYSCALL_PRE_FORK") + pcmd("# define COMMON_SYSCALL_PRE_FORK() {}") + pcmd("#endif") + pcmd("") + pcmd("#ifndef COMMON_SYSCALL_POST_FORK") + pcmd("# define COMMON_SYSCALL_POST_FORK(res) {}") + pcmd("#endif") + pcmd("") + pcmd("// FIXME: do some kind of PRE_READ for all syscall arguments (int(s) and such).") + pcmd("") + pcmd("extern \"C\" {") + + # TODO + + pcmd("} // extern \"C\"") + pcmd("") + pcmd("#undef PRE_SYSCALL") + pcmd("#undef PRE_READ") + pcmd("#undef PRE_WRITE") + pcmd("#undef POST_SYSCALL") + pcmd("#undef POST_READ") + pcmd("#undef POST_WRITE") + pcmd("") + pcmd("#endif // SANITIZER_NETBSD") + + close(cmd) + + # Hack for preprocessed code + system("sed -i 's,^ \\([^ ]\\), \\1,' " outputinc) +} + +function usage() +{ + print "Usage: " script_name " syscalls.master" + abnormal_exit = 1 + exit 1 +} + +function pcmd(string) +{ + print string | cmd +}