10Sstevel@tonic-gate /* 20Sstevel@tonic-gate * CDDL HEADER START 30Sstevel@tonic-gate * 40Sstevel@tonic-gate * The contents of this file are subject to the terms of the 5*11798SRoger.Faulkner@Sun.COM * Common Development and Distribution License (the "License"). 6*11798SRoger.Faulkner@Sun.COM * You may not use this file except in compliance with the License. 70Sstevel@tonic-gate * 80Sstevel@tonic-gate * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 90Sstevel@tonic-gate * or http://www.opensolaris.org/os/licensing. 100Sstevel@tonic-gate * See the License for the specific language governing permissions 110Sstevel@tonic-gate * and limitations under the License. 120Sstevel@tonic-gate * 130Sstevel@tonic-gate * When distributing Covered Code, include this CDDL HEADER in each 140Sstevel@tonic-gate * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 150Sstevel@tonic-gate * If applicable, add the following below this CDDL HEADER, with the 160Sstevel@tonic-gate * fields enclosed by brackets "[]" replaced with your own identifying 170Sstevel@tonic-gate * information: Portions Copyright [yyyy] [name of copyright owner] 180Sstevel@tonic-gate * 190Sstevel@tonic-gate * CDDL HEADER END 200Sstevel@tonic-gate */ 21*11798SRoger.Faulkner@Sun.COM 220Sstevel@tonic-gate /* 23*11798SRoger.Faulkner@Sun.COM * Copyright 2010 Sun Microsystems, Inc. All rights reserved. 240Sstevel@tonic-gate * Use is subject to license terms. 250Sstevel@tonic-gate */ 260Sstevel@tonic-gate 270Sstevel@tonic-gate /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ 280Sstevel@tonic-gate /* All Rights Reserved */ 290Sstevel@tonic-gate 300Sstevel@tonic-gate #include <stdio.h> 310Sstevel@tonic-gate #include <stdlib.h> 320Sstevel@tonic-gate #include <unistd.h> 330Sstevel@tonic-gate #include <ctype.h> 340Sstevel@tonic-gate #include <string.h> 350Sstevel@tonic-gate #include <signal.h> 360Sstevel@tonic-gate #include <stropts.h> 370Sstevel@tonic-gate #include <errno.h> 380Sstevel@tonic-gate #include <sys/types.h> 390Sstevel@tonic-gate #include <sys/termio.h> 400Sstevel@tonic-gate #include <libproc.h> 410Sstevel@tonic-gate #include "ramdata.h" 420Sstevel@tonic-gate #include "proto.h" 430Sstevel@tonic-gate 440Sstevel@tonic-gate /* 450Sstevel@tonic-gate * Routines related to interprocess communication 460Sstevel@tonic-gate * among the truss processes which are controlling 470Sstevel@tonic-gate * multiple traced processes. 480Sstevel@tonic-gate */ 490Sstevel@tonic-gate 500Sstevel@tonic-gate /* 510Sstevel@tonic-gate * Function prototypes for static routines in this module. 520Sstevel@tonic-gate */ 530Sstevel@tonic-gate void Ecritical(int); 540Sstevel@tonic-gate void Xcritical(int); 550Sstevel@tonic-gate 560Sstevel@tonic-gate /* 570Sstevel@tonic-gate * Ensure everyone keeps out of each other's way 580Sstevel@tonic-gate * while writing lines of trace output. 590Sstevel@tonic-gate */ 600Sstevel@tonic-gate void 610Sstevel@tonic-gate Flush() 620Sstevel@tonic-gate { 630Sstevel@tonic-gate /* 640Sstevel@tonic-gate * Except for regions bounded by Eserialize()/Xserialize(), 650Sstevel@tonic-gate * this is the only place anywhere in the program where a 660Sstevel@tonic-gate * write() to the trace output file takes place, so here 670Sstevel@tonic-gate * is where we detect errors writing to the output. 680Sstevel@tonic-gate */ 690Sstevel@tonic-gate 700Sstevel@tonic-gate errno = 0; 710Sstevel@tonic-gate 720Sstevel@tonic-gate Ecritical(0); 730Sstevel@tonic-gate (void) fflush(stdout); 740Sstevel@tonic-gate Xcritical(0); 750Sstevel@tonic-gate 760Sstevel@tonic-gate if (ferror(stdout) && errno) /* error on write(), probably EPIPE */ 770Sstevel@tonic-gate interrupt = SIGTERM; /* post an interrupt */ 780Sstevel@tonic-gate } 790Sstevel@tonic-gate 800Sstevel@tonic-gate /* 810Sstevel@tonic-gate * Eserialize() and Xserialize() are used to bracket 820Sstevel@tonic-gate * a region which may produce large amounts of output, 830Sstevel@tonic-gate * such as showargs()/dumpargs(). 840Sstevel@tonic-gate */ 850Sstevel@tonic-gate 860Sstevel@tonic-gate void 870Sstevel@tonic-gate Eserialize() 880Sstevel@tonic-gate { 890Sstevel@tonic-gate /* serialize output */ 900Sstevel@tonic-gate Ecritical(0); 910Sstevel@tonic-gate } 920Sstevel@tonic-gate 930Sstevel@tonic-gate void 940Sstevel@tonic-gate Xserialize() 950Sstevel@tonic-gate { 960Sstevel@tonic-gate (void) fflush(stdout); 970Sstevel@tonic-gate Xcritical(0); 980Sstevel@tonic-gate } 990Sstevel@tonic-gate 1000Sstevel@tonic-gate /* 1010Sstevel@tonic-gate * Enter critical region --- Wait on mutex, lock out other processes. 1020Sstevel@tonic-gate * Lock zero is used to serialize output in situations where multiple processes 1030Sstevel@tonic-gate * may be writing to stdout/stderr and order must be preserved. Most of these 1040Sstevel@tonic-gate * are in expound.c 1050Sstevel@tonic-gate * Lock one is used to protect the table of processes currently being traced 1060Sstevel@tonic-gate * every time a pid is added or removed from the table Ecritical(1)/Xcritical(1) 1070Sstevel@tonic-gate * get called. 1080Sstevel@tonic-gate */ 1090Sstevel@tonic-gate void 1100Sstevel@tonic-gate Ecritical(int num) 1110Sstevel@tonic-gate { 1120Sstevel@tonic-gate int rv; 1130Sstevel@tonic-gate 1140Sstevel@tonic-gate if (num == 0) 1150Sstevel@tonic-gate rv = mutex_lock(&gps->ps_mutex0); 1160Sstevel@tonic-gate else if (num == 1) 1170Sstevel@tonic-gate rv = mutex_lock(&gps->ps_mutex1); 1180Sstevel@tonic-gate else 1190Sstevel@tonic-gate abend("Invalid mutex specified", NULL); 1200Sstevel@tonic-gate 1210Sstevel@tonic-gate if (rv != 0) { 1220Sstevel@tonic-gate char mnum[2]; 1230Sstevel@tonic-gate mnum[0] = '0' + num; 1240Sstevel@tonic-gate mnum[1] = '\0'; 1250Sstevel@tonic-gate errno = rv; 1260Sstevel@tonic-gate perror(command); 1270Sstevel@tonic-gate errmsg("cannot grab mutex #", mnum); 1280Sstevel@tonic-gate } 1290Sstevel@tonic-gate } 1300Sstevel@tonic-gate 1310Sstevel@tonic-gate /* 1320Sstevel@tonic-gate * Exit critical region --- 1330Sstevel@tonic-gate * Release other processes waiting on mutex. 1340Sstevel@tonic-gate */ 1350Sstevel@tonic-gate void 1360Sstevel@tonic-gate Xcritical(int num) 1370Sstevel@tonic-gate { 1380Sstevel@tonic-gate int rv; 1390Sstevel@tonic-gate 1400Sstevel@tonic-gate if (num == 0) 1410Sstevel@tonic-gate rv = mutex_unlock(&gps->ps_mutex0); 1420Sstevel@tonic-gate else if (num == 1) 1430Sstevel@tonic-gate rv = mutex_unlock(&gps->ps_mutex1); 1440Sstevel@tonic-gate else 1450Sstevel@tonic-gate abend("Invalid mutex specified", NULL); 1460Sstevel@tonic-gate 1470Sstevel@tonic-gate 1480Sstevel@tonic-gate if (rv != 0) { 1490Sstevel@tonic-gate char mnum[2]; 1500Sstevel@tonic-gate mnum[0] = '0' + num; 1510Sstevel@tonic-gate mnum[1] = '\0'; 1520Sstevel@tonic-gate errno = rv; 1530Sstevel@tonic-gate perror(command); 1540Sstevel@tonic-gate errmsg("cannot release mutex #", mnum); 1550Sstevel@tonic-gate } 1560Sstevel@tonic-gate } 1570Sstevel@tonic-gate 1580Sstevel@tonic-gate /* 1590Sstevel@tonic-gate * Add process to set of those being traced. 1600Sstevel@tonic-gate */ 1610Sstevel@tonic-gate void 1620Sstevel@tonic-gate procadd(pid_t spid, const char *lwplist) 1630Sstevel@tonic-gate { 1640Sstevel@tonic-gate int i; 1650Sstevel@tonic-gate int j = -1; 1660Sstevel@tonic-gate 1670Sstevel@tonic-gate if (gps == NULL) 1680Sstevel@tonic-gate return; 1690Sstevel@tonic-gate 1700Sstevel@tonic-gate Ecritical(1); 1710Sstevel@tonic-gate for (i = 0; i < sizeof (gps->tpid) / sizeof (gps->tpid[0]); i++) { 1720Sstevel@tonic-gate if (gps->tpid[i] == 0) { 1730Sstevel@tonic-gate if (j == -1) /* remember first vacant slot */ 1740Sstevel@tonic-gate j = i; 1750Sstevel@tonic-gate if (gps->spid[i] == 0) /* this slot is better */ 1760Sstevel@tonic-gate break; 1770Sstevel@tonic-gate } 1780Sstevel@tonic-gate } 1790Sstevel@tonic-gate if (i < sizeof (gps->tpid) / sizeof (gps->tpid[0])) 1800Sstevel@tonic-gate j = i; 1810Sstevel@tonic-gate if (j >= 0) { 1820Sstevel@tonic-gate gps->tpid[j] = getpid(); 1830Sstevel@tonic-gate gps->spid[j] = spid; 1840Sstevel@tonic-gate gps->lwps[j] = lwplist; 1850Sstevel@tonic-gate } 1860Sstevel@tonic-gate Xcritical(1); 1870Sstevel@tonic-gate } 1880Sstevel@tonic-gate 1890Sstevel@tonic-gate /* 1900Sstevel@tonic-gate * Delete process from set of those being traced. 1910Sstevel@tonic-gate */ 1920Sstevel@tonic-gate void 1930Sstevel@tonic-gate procdel() 1940Sstevel@tonic-gate { 1950Sstevel@tonic-gate int i; 1960Sstevel@tonic-gate pid_t tpid; 1970Sstevel@tonic-gate 1980Sstevel@tonic-gate if (gps == NULL) 1990Sstevel@tonic-gate return; 2000Sstevel@tonic-gate 2010Sstevel@tonic-gate tpid = getpid(); 2020Sstevel@tonic-gate 2030Sstevel@tonic-gate Ecritical(1); 2040Sstevel@tonic-gate for (i = 0; i < sizeof (gps->tpid) / sizeof (gps->tpid[0]); i++) { 2050Sstevel@tonic-gate if (gps->tpid[i] == tpid) { 2060Sstevel@tonic-gate gps->tpid[i] = 0; 2070Sstevel@tonic-gate break; 2080Sstevel@tonic-gate } 2090Sstevel@tonic-gate } 2100Sstevel@tonic-gate Xcritical(1); 2110Sstevel@tonic-gate } 2120Sstevel@tonic-gate 2130Sstevel@tonic-gate /* 2140Sstevel@tonic-gate * Determine if the lwp for this process should be traced. 2150Sstevel@tonic-gate */ 2160Sstevel@tonic-gate int 2170Sstevel@tonic-gate lwptrace(pid_t spid, lwpid_t lwpid) 2180Sstevel@tonic-gate { 2190Sstevel@tonic-gate int i; 2200Sstevel@tonic-gate pid_t tpid; 2210Sstevel@tonic-gate const char *lwps; 2220Sstevel@tonic-gate 2230Sstevel@tonic-gate if (gps == NULL) 2240Sstevel@tonic-gate return (0); 2250Sstevel@tonic-gate 2260Sstevel@tonic-gate tpid = getpid(); 2270Sstevel@tonic-gate 2280Sstevel@tonic-gate Ecritical(1); 2290Sstevel@tonic-gate for (i = 0; i < sizeof (gps->tpid) / sizeof (gps->tpid[0]); i++) { 2300Sstevel@tonic-gate if (gps->tpid[i] == tpid && 2310Sstevel@tonic-gate gps->spid[i] == spid) 2320Sstevel@tonic-gate break; 2330Sstevel@tonic-gate } 2340Sstevel@tonic-gate lwps = gps->lwps[i]; 2350Sstevel@tonic-gate Xcritical(1); 2360Sstevel@tonic-gate 2370Sstevel@tonic-gate return (proc_lwp_in_set(lwps, lwpid)); 2380Sstevel@tonic-gate } 2390Sstevel@tonic-gate 2400Sstevel@tonic-gate /* 2410Sstevel@tonic-gate * Check for open of a /proc/nnnnn file. 2420Sstevel@tonic-gate * Return 0 if this is not an open of a /proc file. 2430Sstevel@tonic-gate * Return 1 if the process opened itself. 2440Sstevel@tonic-gate * Return 2 if the process failed to open another process 2450Sstevel@tonic-gate * in truss's set of controlled processes. 2460Sstevel@tonic-gate * Return 3 if the process successfully opened another process 2470Sstevel@tonic-gate * in truss's set of controlled processes. 2480Sstevel@tonic-gate * We notify and wait for the other controlling truss process 2490Sstevel@tonic-gate * to terminate before returning in cases 2 and 3. 2500Sstevel@tonic-gate */ 2510Sstevel@tonic-gate /* ARGSUSED */ 2520Sstevel@tonic-gate int 2530Sstevel@tonic-gate checkproc(private_t *pri) 2540Sstevel@tonic-gate { 2550Sstevel@tonic-gate char *path = pri->sys_path; 2560Sstevel@tonic-gate const pstatus_t *Psp = Pstatus(Proc); 2570Sstevel@tonic-gate struct ps_lwphandle *Lwp = pri->Lwp; 2580Sstevel@tonic-gate const lwpstatus_t *Lsp = pri->lwpstat; 259*11798SRoger.Faulkner@Sun.COM int what = Lsp->pr_what; /* one of the SYS_open* syscalls */ 2600Sstevel@tonic-gate int err = Lsp->pr_errno; 2610Sstevel@tonic-gate int pid; 2620Sstevel@tonic-gate int i; 2630Sstevel@tonic-gate const char *dirname; 2640Sstevel@tonic-gate char *next; 2650Sstevel@tonic-gate char *sp1; 2660Sstevel@tonic-gate char *sp2; 2670Sstevel@tonic-gate prgreg_t pc; 2680Sstevel@tonic-gate 2690Sstevel@tonic-gate /* 2700Sstevel@tonic-gate * A bit heuristic ... 2710Sstevel@tonic-gate * Test for the cases: 2720Sstevel@tonic-gate * 1234 2730Sstevel@tonic-gate * 1234/as 2740Sstevel@tonic-gate * 1234/ctl 2750Sstevel@tonic-gate * 1234/lwp/24/lwpctl 2760Sstevel@tonic-gate * .../1234 2770Sstevel@tonic-gate * .../1234/as 2780Sstevel@tonic-gate * .../1234/ctl 2790Sstevel@tonic-gate * .../1234/lwp/24/lwpctl 2800Sstevel@tonic-gate * Insert a '\0', if necessary, so the path becomes ".../1234". 2810Sstevel@tonic-gate * 2820Sstevel@tonic-gate * Along the way, watch out for /proc/self and /proc/1234/lwp/agent 2830Sstevel@tonic-gate */ 2840Sstevel@tonic-gate if ((sp1 = strrchr(path, '/')) == NULL) /* last component */ 2850Sstevel@tonic-gate /* EMPTY */; 2860Sstevel@tonic-gate else if (isdigit(*(sp1+1))) { 2870Sstevel@tonic-gate sp1 += strlen(sp1); 2880Sstevel@tonic-gate while (--sp1 > path && isdigit(*sp1)) 2890Sstevel@tonic-gate ; 2900Sstevel@tonic-gate if (*sp1 != '/') 2910Sstevel@tonic-gate return (0); 2920Sstevel@tonic-gate } else if (strcmp(sp1+1, "as") == 0 || 2930Sstevel@tonic-gate strcmp(sp1+1, "ctl") == 0) { 2940Sstevel@tonic-gate *sp1 = '\0'; 2950Sstevel@tonic-gate } else if (strcmp(sp1+1, "lwpctl") == 0) { 2960Sstevel@tonic-gate /* 2970Sstevel@tonic-gate * .../1234/lwp/24/lwpctl 2980Sstevel@tonic-gate * ............ ^-- sp1 2990Sstevel@tonic-gate */ 3000Sstevel@tonic-gate if (sp1-6 >= path && strncmp(sp1-6, "/agent", 6) == 0) 3010Sstevel@tonic-gate sp1 -= 6; 3020Sstevel@tonic-gate else { 3030Sstevel@tonic-gate while (--sp1 > path && isdigit(*sp1)) 3040Sstevel@tonic-gate ; 3050Sstevel@tonic-gate } 3060Sstevel@tonic-gate if (*sp1 != '/' || 3070Sstevel@tonic-gate (sp1 -= 4) <= path || 3080Sstevel@tonic-gate strncmp(sp1, "/lwp", 4) != 0) 3090Sstevel@tonic-gate return (0); 3100Sstevel@tonic-gate *sp1 = '\0'; 3110Sstevel@tonic-gate } else if (strcmp(sp1+1, "self") != 0) { 3120Sstevel@tonic-gate return (0); 3130Sstevel@tonic-gate } 3140Sstevel@tonic-gate 3150Sstevel@tonic-gate if ((sp2 = strrchr(path, '/')) == NULL) 3160Sstevel@tonic-gate dirname = path; 3170Sstevel@tonic-gate else 3180Sstevel@tonic-gate dirname = sp2 + 1; 3190Sstevel@tonic-gate 3200Sstevel@tonic-gate if (strcmp(dirname, "self") == 0) { 3210Sstevel@tonic-gate pid = Psp->pr_pid; 3220Sstevel@tonic-gate } else if ((pid = strtol(dirname, &next, 10)) < 0 || 3230Sstevel@tonic-gate *next != '\0') { /* dirname not a number */ 3240Sstevel@tonic-gate if (sp1 != NULL) 3250Sstevel@tonic-gate *sp1 = '/'; 3260Sstevel@tonic-gate return (0); 3270Sstevel@tonic-gate } 3280Sstevel@tonic-gate if (sp2 == NULL) 3290Sstevel@tonic-gate dirname = "."; 3300Sstevel@tonic-gate else { 3310Sstevel@tonic-gate *sp2 = '\0'; 3320Sstevel@tonic-gate dirname = path; 3330Sstevel@tonic-gate } 3340Sstevel@tonic-gate 3350Sstevel@tonic-gate if (!Pisprocdir(Proc, dirname) || /* file not in a /proc directory */ 3360Sstevel@tonic-gate pid == getpid() || /* process opened truss's /proc file */ 3370Sstevel@tonic-gate pid == 0) { /* process opened process 0 */ 3380Sstevel@tonic-gate if (sp1 != NULL) 3390Sstevel@tonic-gate *sp1 = '/'; 3400Sstevel@tonic-gate if (sp2 != NULL) 3410Sstevel@tonic-gate *sp2 = '/'; 3420Sstevel@tonic-gate return (0); 3430Sstevel@tonic-gate } 3440Sstevel@tonic-gate if (sp1 != NULL) 3450Sstevel@tonic-gate *sp1 = '/'; 3460Sstevel@tonic-gate if (sp2 != NULL) 3470Sstevel@tonic-gate *sp2 = '/'; 3480Sstevel@tonic-gate 3490Sstevel@tonic-gate /* 3500Sstevel@tonic-gate * Process did open a /proc file --- 3510Sstevel@tonic-gate */ 3520Sstevel@tonic-gate if (pid == Psp->pr_pid) { /* process opened its own /proc file */ 3530Sstevel@tonic-gate /* 3540Sstevel@tonic-gate * In SunOS 5.6 and beyond, self-opens always succeed. 3550Sstevel@tonic-gate */ 3560Sstevel@tonic-gate return (1); 3570Sstevel@tonic-gate } 3580Sstevel@tonic-gate 3590Sstevel@tonic-gate /* 3600Sstevel@tonic-gate * Search for a matching pid in our set of controlled processes. 3610Sstevel@tonic-gate */ 3620Sstevel@tonic-gate for (i = 0; i < sizeof (gps->tpid)/sizeof (gps->tpid[0]); i++) { 3630Sstevel@tonic-gate if (gps->spid[i] == pid) { 3640Sstevel@tonic-gate pid = gps->tpid[i]; 3650Sstevel@tonic-gate break; 3660Sstevel@tonic-gate } 3670Sstevel@tonic-gate } 3680Sstevel@tonic-gate if (i >= sizeof (gps->tpid) / sizeof (gps->tpid[0])) { 3690Sstevel@tonic-gate /* 3700Sstevel@tonic-gate * The process opened a /proc file, but not one we care about. 3710Sstevel@tonic-gate */ 3720Sstevel@tonic-gate return (0); 3730Sstevel@tonic-gate } 3740Sstevel@tonic-gate 3750Sstevel@tonic-gate /* 3760Sstevel@tonic-gate * Notify and wait for the controlling process to terminate. 3770Sstevel@tonic-gate */ 3780Sstevel@tonic-gate while (pid && gps->tpid[i] == pid) { 3790Sstevel@tonic-gate if (kill(pid, SIGUSR1) == -1) 3800Sstevel@tonic-gate break; 3810Sstevel@tonic-gate (void) usleep(1000000); 3820Sstevel@tonic-gate } 3830Sstevel@tonic-gate Ecritical(1); 3840Sstevel@tonic-gate if (gps->tpid[i] == 0) 3850Sstevel@tonic-gate gps->spid[i] = 0; 3860Sstevel@tonic-gate Xcritical(1); 3870Sstevel@tonic-gate 3880Sstevel@tonic-gate if (err) { /* prepare to reissue the failed open() system call */ 3890Sstevel@tonic-gate #if defined(__sparc) 3900Sstevel@tonic-gate (void) Lgetareg(Lwp, R_PC, &pc); 3910Sstevel@tonic-gate if (pri->sys_indirect) { 3920Sstevel@tonic-gate (void) Lputareg(Lwp, R_G1, (prgreg_t)SYS_syscall); 3930Sstevel@tonic-gate (void) Lputareg(Lwp, R_O0, (prgreg_t)what); 3940Sstevel@tonic-gate for (i = 0; i < 5; i++) 3950Sstevel@tonic-gate (void) Lputareg(Lwp, R_O1+i, pri->sys_args[i]); 3960Sstevel@tonic-gate } else { 3970Sstevel@tonic-gate (void) Lputareg(Lwp, R_G1, (prgreg_t)what); 3980Sstevel@tonic-gate for (i = 0; i < 6; i++) 3990Sstevel@tonic-gate (void) Lputareg(Lwp, R_O0+i, pri->sys_args[i]); 4000Sstevel@tonic-gate } 4010Sstevel@tonic-gate (void) Lputareg(Lwp, R_nPC, pc); 4020Sstevel@tonic-gate #elif defined(__amd64) 4030Sstevel@tonic-gate (void) Lgetareg(Lwp, R_PC, &pc); 4040Sstevel@tonic-gate (void) Lputareg(Lwp, REG_RAX, (prgreg_t)what); 4050Sstevel@tonic-gate #elif defined(__i386) 4060Sstevel@tonic-gate (void) Lgetareg(Lwp, R_PC, &pc); 4070Sstevel@tonic-gate (void) Lputareg(Lwp, EAX, (prgreg_t)what); 4080Sstevel@tonic-gate #else 4090Sstevel@tonic-gate #error "unrecognized architecture" 4100Sstevel@tonic-gate #endif 4110Sstevel@tonic-gate (void) Pissyscall_prev(Proc, pc, (uintptr_t *)&pc); 4120Sstevel@tonic-gate (void) Lputareg(Lwp, R_PC, pc); 4130Sstevel@tonic-gate return (2); 4140Sstevel@tonic-gate } 4150Sstevel@tonic-gate 4160Sstevel@tonic-gate return (3); 4170Sstevel@tonic-gate } 418