[Linux PAL] Rewrite signal handling

This commit is contained in:
Chia-Che Tsai
2018-12-03 15:07:38 -08:00
committed by Chia-Che Tsai
parent e8f334b220
commit 8aaeafc0a3
9 changed files with 224 additions and 286 deletions
-1
View File
@@ -8,7 +8,6 @@ AR = ar rcs
LD = ld
CFLAGS = -Wall -fPIC -O2 -std=gnu99 -fgnu89-inline -U_FORTIFY_SOURCE \
-fno-omit-frame-pointer \
-fno-stack-protector -fno-builtin
ASFLAGS = -DPIC -DSHARED -fPIC -DASSEMBLER -Wa,--noexecstack \
-x assembler-with-cpp
+80 -147
View File
@@ -29,6 +29,7 @@
#include "pal.h"
#include "pal_internal.h"
#include "pal_linux.h"
#include "pal_debug.h"
#include "pal_error.h"
#include "pal_security.h"
#include "api.h"
@@ -114,14 +115,8 @@ typedef struct {
PAL_IDX event_num;
PAL_CONTEXT context;
ucontext_t * uc;
PAL_PTR eframe;
} PAL_EVENT;
#define save_return_point(ptr) \
asm volatile ("leaq 0(%%rip), %%rax\r\n" \
"movq %%rax, %0\r\n" \
: "=b"(ptr) :: "memory", "rax")
static int get_event_num (int signum)
{
switch(signum) {
@@ -136,8 +131,7 @@ static int get_event_num (int signum)
}
void _DkGenericEventTrigger (PAL_IDX event_num, PAL_EVENT_HANDLER upcall,
PAL_NUM arg, struct pal_frame * frame,
ucontext_t * uc, void * eframe)
PAL_NUM arg, ucontext_t * uc)
{
PAL_EVENT event;
event.event_num = event_num;
@@ -145,31 +139,13 @@ void _DkGenericEventTrigger (PAL_IDX event_num, PAL_EVENT_HANDLER upcall,
if (uc)
memcpy(&event.context, uc->uc_mcontext.gregs, sizeof(PAL_CONTEXT));
if (frame) {
event.context.r15 = frame->arch.r15;
event.context.r14 = frame->arch.r14;
event.context.r13 = frame->arch.r13;
event.context.r12 = frame->arch.r12;
event.context.rdi = frame->arch.rdi;
event.context.rsi = frame->arch.rsi;
event.context.rbx = frame->arch.rbx;
/* find last frame */
event.context.rsp = frame->arch.rbp + sizeof(unsigned long) * 2;
event.context.rbp = ((unsigned long *) frame->arch.rbp)[0];
event.context.rip = ((unsigned long *) frame->arch.rbp)[1];
/* making rax = 0 to tell the caller that this PAL call failed */
event.context.rax = 0;
}
event.uc = uc;
event.eframe = eframe;
(*upcall) ((PAL_PTR) &event, arg, &event.context);
}
static bool _DkGenericSignalHandle (int event_num, siginfo_t * info,
struct pal_frame * frame,
ucontext_t * uc, void * eframe)
ucontext_t * uc)
{
PAL_EVENT_HANDLER upcall = _DkGetExceptionHandler(event_num);
@@ -181,133 +157,113 @@ static bool _DkGenericSignalHandle (int event_num, siginfo_t * info,
event_num == PAL_EVENT_ILLEGAL)
arg = (PAL_NUM) (info ? info->si_addr : 0);
_DkGenericEventTrigger(event_num, upcall, arg, frame, uc, eframe);
_DkGenericEventTrigger(event_num, upcall, arg, uc);
return true;
}
return false;
}
#define ADDR_IN_PAL(addr) \
((void *) (addr) > TEXT_START && (void *) (addr) < TEXT_END)
/* This function walks the stack to find the PAL_FRAME
* that was saved upon entry to the PAL, if an exception/interrupt
* comes in during a PAL call. This is needed to support the behavior that an
* exception in the PAL has Unix-style, EAGAIN semantics.
*
* The PAL_FRAME is supposed to be in the first PAL frame, and we look for
* it by matching a special magic number, that should only appear on the stack
* once.
*
* If an exception comes in while we are not in the PAL, this PAL_FRAME won't
* exist, and it is ok to return NULL.
*/
static struct pal_frame * get_frame (ucontext_t * uc)
{
unsigned long rip = uc->uc_mcontext.gregs[REG_RIP];
unsigned long rbp = uc->uc_mcontext.gregs[REG_RBP];
unsigned long last_rbp = rbp - 64;
if (!ADDR_IN_PAL(rip))
return NULL;
while (ADDR_IN_PAL(((unsigned long *) rbp)[1])) {
last_rbp = rbp;
rbp = *(unsigned long *) rbp;
}
/* search frame record in the top frame of PAL */
for (unsigned long ptr = rbp - sizeof(unsigned long) ;
ptr > last_rbp ; ptr -= 8) {
struct pal_frame * frame = (struct pal_frame *) ptr;
if (frame->identifier == PAL_FRAME_IDENTIFIER)
return frame;
}
return NULL;
}
static void return_frame (struct pal_frame * frame, int err)
{
if (err)
_DkRaiseFailure(err);
__clear_frame(frame);
arch_restore_frame(&frame->arch);
asm volatile ("xor %rax, %rax\r\n"
"leaveq\r\n"
"retq\r\n");
}
#if BLOCK_SIGFAULT == 1
static char exception_msg[24] = "--- SIGSEGV --- [ ]\n";
static volatile bool cont_exec = false;
#endif
static void _DkGenericSighandler (int signum, siginfo_t * info,
struct ucontext * uc)
{
#if BLOCK_SIGFUALT == 1
/* reseurrect this code if signal handler if giving segmentation fault */
if (signum == SIGSEGV) {
int pid = INLINE_SYSCALL(getpid, 0);
exception_msg[17] = '0' + pid / 10000;
exception_msg[18] = '0' + (pid / 1000) % 10;
exception_msg[19] = '0' + (pid / 100) % 10;
exception_msg[20] = '0' + (pid / 10) % 10;
exception_msg[21] = '0' + pid % 10;
INLINE_SYSCALL(write, 3, 1, exception_msg, 24);
while(!cont_exec);
}
#endif
struct pal_frame * frame = get_frame(uc);
void * eframe;
if (signum == SIGCONT && frame && frame->func == DkObjectsWaitAny)
return;
asm volatile ("movq %%rbp, %0" : "=r"(eframe));
if (frame && frame->func != &_DkGenericSighandler &&
signum != SIGCONT &&
signum != SIGINT &&
signum != SIGTERM) {
return_frame(frame, PAL_ERROR_BADADDR);
return;
}
int event_num = get_event_num(signum);
if (event_num == -1)
return;
_DkGenericSignalHandle(event_num, info, frame, uc, eframe);
uintptr_t rip = uc->uc_mcontext.gregs[REG_RIP];
if (ADDR_IN_PAL(rip)) {
// We expect none of the memory faults, illegal instructions, or arithmetic exceptions
// will happen in PAL. If these exceptions happen in PAL, exit the thread with loud warning.
int pid = INLINE_SYSCALL(getpid, 0);
int tid = INLINE_SYSCALL(gettid, 0);
const char * name = "exception";
switch(event_num) {
case PAL_EVENT_DIVZERO: name = "div-by-zero exception"; break;
case PAL_EVENT_MEMFAULT: name = "memory fault"; break;
case PAL_EVENT_ILLEGAL: name = "illegal instruction"; break;
}
printf("*** An unexpected %s occurred inside PAL. Exiting the thread. "
"(PID = %d, TID = %d, RIP = +%p) ***\n",
name, pid, tid, rip - (uintptr_t) TEXT_START);
_DkThreadExit();
return;
}
_DkGenericSignalHandle(event_num, info, uc);
}
static void _DkTerminateSighandler (int signum, siginfo_t * info,
struct ucontext * uc)
{
struct pal_frame * frame = get_frame(uc);
void * eframe;
asm volatile ("movq %%rbp, %0" : "=r"(eframe));
int event_num = get_event_num(signum);
if (event_num == -1)
return;
if (!_DkGenericSignalHandle(event_num, NULL, frame, uc, eframe))
uintptr_t rip = uc->uc_mcontext.gregs[REG_RIP];
// If the signal arrives in the middle of a PAL call, add the event
// to pending in the current TCB.
if (ADDR_IN_PAL(rip)) {
PAL_TCB * tcb = get_tcb();
assert(tcb);
if (!tcb->pending_event) {
// Use the preserved pending event slot
tcb->pending_event = event_num;
} else {
// If there is already a pending event, add the new event to the queue.
// (a relatively rare case.)
struct event_queue * ev = malloc(sizeof(*ev));
if (!ev)
return;
INIT_LIST_HEAD(ev, list);
ev->event_num = event_num;
listp_add_tail(ev, &tcb->pending_queue, list);
}
return;
}
// Call the event handler. If there is no handler, terminate the thread
// unless it is a resuming event (then ignore the event).
if (!_DkGenericSignalHandle(event_num, NULL, uc) && event_num != PAL_EVENT_RESUME)
_DkThreadExit();
}
static void _DkPipeSighandler (int signum, siginfo_t * info,
struct ucontext * uc)
{
uintptr_t rip = uc->uc_mcontext.gregs[REG_RIP];
assert(ADDR_IN_PAL(rip)); // This signal can only happens inside PAL
return;
}
/*
* __check_pending_event(): checks the existence of a pending event in the TCB
* and handles the event consequently.
*/
void __check_pending_event (void)
{
PAL_TCB * tcb = get_tcb();
assert(tcb);
if (tcb->pending_event) {
int event = tcb->pending_event;
tcb->pending_event = 0;
_DkGenericSignalHandle(event, NULL, NULL);
if (!listp_empty(&tcb->pending_queue)) {
// If there are more than one pending events, process them from the queue
struct event_queue * ev, * n;
listp_for_each_entry_safe(ev, n, &tcb->pending_queue, list) {
listp_del(ev, &tcb->pending_queue, list);
_DkGenericSignalHandle(ev->event_num, NULL, NULL);
free(ev);
}
}
}
}
void _DkRaiseFailure (int error)
{
PAL_EVENT_HANDLER upcall = _DkGetExceptionHandler(PAL_EVENT_FAILURE);
@@ -318,7 +274,6 @@ void _DkRaiseFailure (int error)
PAL_EVENT event;
event.event_num = PAL_EVENT_FAILURE;
event.uc = NULL;
event.eframe = NULL;
(*upcall) ((PAL_PTR) &event, error, NULL);
}
@@ -340,7 +295,7 @@ struct signal_ops on_signals[] = {
[PAL_EVENT_SUSPEND] = { .signum = { SIGINT, 0 },
.handler = _DkTerminateSighandler },
[PAL_EVENT_RESUME] = { .signum = { SIGCONT, 0 },
.handler = _DkGenericSighandler },
.handler = _DkTerminateSighandler },
};
static int _DkPersistentSighandlerSetup (int event_num)
@@ -389,28 +344,6 @@ err:
void _DkExceptionReturn (void * event)
{
PAL_EVENT * e = event;
if (e->eframe) {
struct pal_frame * frame = (struct pal_frame *) e->eframe;
int err = 0;
switch (e->event_num) {
case PAL_EVENT_MEMFAULT:
err = PAL_ERROR_BADADDR;
break;
case PAL_EVENT_QUIT:
case PAL_EVENT_SUSPEND:
case PAL_EVENT_RESUME:
err = PAL_ERROR_INTERRUPTED;
break;
}
if (err)
_DkRaiseFailure(err);
__clear_frame(frame);
}
if (e->uc) {
/* copy the context back to ucontext */
memcpy(e->uc->uc_mcontext.gregs, &e->context, sizeof(PAL_CONTEXT));
+20 -4
View File
@@ -229,6 +229,26 @@ void pal_linux_main (void * args)
init_slab_mgr(pagesz);
first_thread = malloc(HANDLE_SIZE(thread));
if (!first_thread)
init_fail(PAL_ERROR_NOMEM, "Out of memory");
SET_HANDLE_TYPE(first_thread, thread);
first_thread->thread.tid = INLINE_SYSCALL(gettid, 0);
void * alt_stack = NULL;
_DkVirtualMemoryAlloc(&alt_stack, ALT_STACK_SIZE, 0, PAL_PROT_READ|PAL_PROT_WRITE);
if (!alt_stack)
init_fail(PAL_ERROR_NOMEM, "Out of memory");
// Initialize TCB at the top of the alternative stack.
PAL_TCB * tcb = alt_stack + ALT_STACK_SIZE - sizeof(PAL_TCB);
tcb->self = tcb;
tcb->handle = first_thread;
tcb->alt_stack = alt_stack; // Stack bottom
tcb->callback = NULL;
tcb->param = NULL;
pal_thread_init(tcb);
setup_pal_map(&pal_map);
#if USE_VDSO_GETTIME == 1
@@ -287,10 +307,6 @@ done_init:
return;
}
first_thread = malloc(HANDLE_SIZE(thread));
SET_HANDLE_TYPE(first_thread, thread);
first_thread->thread.tid = linux_state.pid;
signal_setup();
/* call to main function */
+3 -2
View File
@@ -186,7 +186,7 @@ int _DkSegmentRegisterSet (int reg, const void * addr)
if (reg == PAL_SEGMENT_FS) {
ret = INLINE_SYSCALL(arch_prctl, 2, ARCH_SET_FS, addr);
} else if (reg == PAL_SEGMENT_GS) {
ret = INLINE_SYSCALL(arch_prctl, 2, ARCH_SET_GS, addr);
return -PAL_ERROR_DENIED;
} else {
return -PAL_ERROR_INVAL;
}
@@ -216,7 +216,8 @@ int _DkSegmentRegisterGet (int reg, void ** addr)
if (reg == PAL_SEGMENT_FS) {
ret = INLINE_SYSCALL(arch_prctl, 2, ARCH_GET_FS, &ret_addr);
} else if (reg == PAL_SEGMENT_GS) {
ret = INLINE_SYSCALL(arch_prctl, 2, ARCH_GET_GS, &ret_addr);
// The GS segment is used for the internal TCB of PAL
return -PAL_ERROR_DENIED;
} else {
return -PAL_ERROR_INVAL;
}
+10
View File
@@ -197,6 +197,16 @@ void _DkMutexRelease (PAL_HANDLE handle)
return;
}
int _DkInternalLock (PAL_LOCK* lock) {
while (_DkMutexLock(lock) < 0); // Retry the lock if being interrupted by signals
return 0;
}
int _DkInternalUnlock (PAL_LOCK* lock) {
_DkMutexUnlock(lock);
return 0;
}
static int mutex_wait (PAL_HANDLE handle, uint64_t timeout)
{
return _DkMutexAcquireTimeout(handle, timeout);
+72 -22
View File
@@ -39,12 +39,46 @@
#include <linux/types.h>
#include <linux/wait.h>
/* default size of a new thread stack */
/* DEP 2/4/17: There is enough stuff allocated on the PAL stack now
* that we need two pages in Linux host mode. In SGX mode,
* the enclave/non-clave split makes 1 page in/1 out sufficient.
#if defined(__i386__)
#include <asm/ldt.h>
#else
#include <asm/prctl.h>
#endif
/*
* pal_thread_init(): An initialization wrapper of a newly-created thread (including
* the first thread). This function accepts a TCB pointer to be set to the GS register
* of the thread. The rest of the TCB is used as the alternative stack for signal
* handling.
*/
#define THREAD_STACK_SIZE (pal_state.alloc_align * 2)
int pal_thread_init (void * tcbptr)
{
PAL_TCB * tcb = tcbptr;
int ret;
ret = INLINE_SYSCALL(arch_prctl, 2, ARCH_SET_GS, tcb);
if (IS_ERR(ret))
return -ERRNO(ret);
if (tcb->alt_stack) {
// Align stack to 16 bytes
void * alt_stack_top = (void *) ((uint64_t) tcb & ~16);
assert(alt_stack_top > tcb->alt_stack);
stack_t ss;
ss.ss_sp = alt_stack_top;
ss.ss_flags = 0;
ss.ss_size = alt_stack_top - tcb->alt_stack;
ret = INLINE_SYSCALL(sigaltstack, 2, &ss, NULL);
if (IS_ERR(ret))
return -ERRNO(ret);
}
if (tcb->callback)
return (*tcb->callback) (tcb->param);
return 0;
}
/* _DkThreadCreate for internal use. Create an internal thread
inside the current process. The arguments callback and param
@@ -52,35 +86,51 @@
int _DkThreadCreate (PAL_HANDLE * handle, int (*callback) (void *),
const void * param, int flags)
{
void * child_stack = NULL;
void * stack = NULL;
int ret = _DkVirtualMemoryAlloc(&stack, THREAD_STACK_SIZE + ALT_STACK_SIZE,
0, PAL_PROT_READ|PAL_PROT_WRITE);
if (ret < 0)
return ret;
if (_DkVirtualMemoryAlloc(&child_stack, THREAD_STACK_SIZE, 0,
PAL_PROT_READ|PAL_PROT_WRITE) < 0)
return -PAL_ERROR_NOMEM;
void * child_stack = stack + THREAD_STACK_SIZE;
/* move child_stack to the top of stack. */
child_stack += THREAD_STACK_SIZE;
PAL_HANDLE hdl = malloc(HANDLE_SIZE(thread));
if (!hdl) {
ret = -ENOMEM;
goto err;
}
SET_HANDLE_TYPE(hdl, thread);
// Initialize TCB at the top of the alternative stack.
PAL_TCB * tcb = child_stack + ALT_STACK_SIZE - sizeof(PAL_TCB);
tcb->self = tcb;
tcb->handle = hdl;
tcb->alt_stack = child_stack; // Stack bottom
tcb->callback = callback;
tcb->param = (void *) param;
/* align child_stack to 16 */
child_stack = (void *) ((uintptr_t) child_stack & ~16);
int tid = 0;
int ret = clone(callback, child_stack,
ret = clone(pal_thread_init, child_stack,
CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SYSVSEM|
CLONE_THREAD|CLONE_SIGHAND|CLONE_PTRACE|
CLONE_PARENT_SETTID,
param, &tid, NULL);
if (IS_ERR(ret))
return -PAL_ERROR_DENIED;
(void *) tcb, &hdl->thread.tid, NULL);
if (IS_ERR(ret)) {
ret = -PAL_ERROR_DENIED;
goto err;
}
PAL_HANDLE hdl = malloc(HANDLE_SIZE(thread));
SET_HANDLE_TYPE(hdl, thread);
hdl->thread.tid = tid;
*handle = hdl;
/* _DkThreadAdd(tid); */
return 0;
err:
if (stack)
_DkVirtualMemoryFree(stack, THREAD_STACK_SIZE + ALT_STACK_SIZE);
if (hdl)
free(hdl);
return ret;
}
int _DkThreadDelayExecution (unsigned long * duration)
+4 -110
View File
@@ -53,9 +53,6 @@ typedef struct mutex_handle {
#define LOCK_INIT MUTEX_HANDLE_INIT
#define INIT_LOCK(lock) INIT_MUTEX_HANDLE(lock);
#define _DkInternalLock _DkMutexLock
#define _DkInternalUnlock _DkMutexUnlock
/* Locking and unlocking of Mutexes */
int _DkMutexLock (struct mutex_handle * mut);
int _DkMutexLockTimeout (struct mutex_handle * mut, uint64_t timeout);
@@ -183,115 +180,12 @@ typedef struct pal_handle
#define HANDLE_TYPE(handle) ((handle)->hdr.type)
struct arch_frame {
#ifdef __x86_64__
uint64_t rsp, rbp, rbx, rsi, rdi, r12, r13, r14, r15;
#else
# error "unsupported architecture"
#endif
};
extern void __check_pending_event (void);
#ifdef __x86_64__
# define store_register(reg, var) \
asm volatile ("movq %%" #reg ", %0" : "=a" (var) :: "memory");
#define LEAVE_PAL_CALL() do { __check_pending_event(); } while (0)
# define store_register_in_frame(reg, f) store_register(reg, (f)->reg)
# define arch_store_frame(f) \
store_register_in_frame(rsp, f) \
store_register_in_frame(rbp, f) \
store_register_in_frame(rbx, f) \
store_register_in_frame(rsi, f) \
store_register_in_frame(rdi, f) \
store_register_in_frame(r12, f) \
store_register_in_frame(r13, f) \
store_register_in_frame(r14, f) \
store_register_in_frame(r15, f)
# define restore_register(reg, var, clobber...) \
asm volatile ("movq %0, %%" #reg :: "g" (var) : "memory", ##clobber);
# define restore_register_in_frame(reg, f) \
restore_register(reg, (f)->reg, \
"r15", "r14", "r13", "r12", "rdi", "rsi", "rbx")
# define arch_restore_frame(f) \
restore_register_in_frame(r15, f) \
restore_register_in_frame(r14, f) \
restore_register_in_frame(r13, f) \
restore_register_in_frame(r12, f) \
restore_register_in_frame(rdi, f) \
restore_register_in_frame(rsi, f) \
restore_register_in_frame(rbx, f) \
restore_register_in_frame(rbp, f) \
restore_register_in_frame(rsp, f)
#else /* __x86_64__ */
# error "unsupported architecture"
#endif
#define PAL_FRAME_IDENTIFIER (0xdeaddeadbeefbeef)
struct pal_frame {
volatile uint64_t identifier;
void * func;
const char * funcname;
struct arch_frame arch;
};
/* When a PAL call is issued, a special PAL_FRAME is placed on the stack.
* This stores both a magic identifier, debugging information,
* as well as callee-saved state. This is used as a way to deal
* with PAL-internal failures where the goal is to exit the PAL and return a
* failure.
*
* Arguably, an alternative is to unwind the stack and handle error cases at
* each stage. In general, this is probably more robust, but would take work
* in the short term. The one exception where the current strategy is
* probably better is when the PAL gets in a state where the code is
* unrecoverable, but ideally, this shouldn't happen.
*/
/* DEP 12/25/17: This frame storage thing is important to mark volatile.
* The compiler should not optimize out any of these changes, and
* because some accesses can happen during an exception, these are not
* visible to the compiler in an otherwise stack-local variable (so the
* compiler will try to optimize out these assignments.
*/
static inline
void __store_frame (volatile struct pal_frame * frame,
void * func, const char * funcname)
{
arch_store_frame(&frame->arch)
frame->func = func;
frame->funcname = funcname;
asm volatile ("nop" ::: "memory");
frame->identifier = PAL_FRAME_IDENTIFIER;
}
#define ENTER_PAL_CALL(name) \
struct pal_frame frame; \
__store_frame(&frame, &(name), #name)
static inline
void __clear_frame (volatile struct pal_frame * frame)
{
if (frame->identifier == PAL_FRAME_IDENTIFIER) {
asm volatile ("nop" ::: "memory");
frame->identifier = 0;
}
}
#define LEAVE_PAL_CALL() \
do { \
__clear_frame(&frame); \
} while (0)
#define LEAVE_PAL_CALL_RETURN(retval) \
do { \
__clear_frame(&frame); \
return (retval); \
} while (0)
#define LEAVE_PAL_CALL_RETURN(retval) \
do { __check_pending_event(); return (retval); } while (0)
#if TRACE_HEAP_LEAK == 1
+32
View File
@@ -25,6 +25,7 @@
#include "pal.h"
#include "pal_internal.h"
#include "pal_linux_error.h"
#include "list.h"
#define PAL_LOADER RUNTIME_FILE("pal-Linux")
@@ -172,4 +173,35 @@ extern char __text_start, __text_end, __data_start, __data_end;
#define DATA_START (void *) (&__text_start)
#define DATA_END (void *) (&__text_end)
#define ADDR_IN_PAL(addr) \
((void *) (addr) > TEXT_START && (void *) (addr) < TEXT_END)
DEFINE_LIST(event_queue);
struct event_queue {
LIST_TYPE(event_queue) list;
int event_num;
};
DEFINE_LISTP(event_queue);
typedef struct pal_tcb {
struct pal_tcb * self;
int pending_event;
LISTP_TYPE(event_queue) pending_queue;
PAL_HANDLE handle;
void * alt_stack;
int (*callback) (void *);
void * param;
} PAL_TCB;
int pal_thread_init (void * tcbptr);
static inline PAL_TCB * get_tcb (void)
{
PAL_TCB * tcb;
asm ("movq %%gs:%c1,%q0"
: "=r" (tcb)
: "i" (offsetof(PAL_TCB, self)));
return tcb;
}
#endif /* PAL_LINUX_H */
+3
View File
@@ -7,6 +7,9 @@
#define USER_ADDRESS_RESERVED 0x100000000
#define USER_ADDRESS_LOWEST 0x10000
#define THREAD_STACK_SIZE (PRESET_PAGESIZE * 2)
#define ALT_STACK_SIZE (PRESET_PAGESIZE)
/* internal wrap native pipe inside pipe streams */
#define USE_PIPE_SYSCALL 0