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*3621Sab196087 * Common Development and Distribution License (the "License").
6*3621Sab196087 * 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 */
210Sstevel@tonic-gate /*
22*3621Sab196087 * Copyright 2007 Sun Microsystems, Inc. All rights reserved.
23211Smike_s * Use is subject to license terms.
240Sstevel@tonic-gate */
25211Smike_s
260Sstevel@tonic-gate #pragma ident "%Z%%M% %I% %E% SMI"
270Sstevel@tonic-gate
280Sstevel@tonic-gate /*
290Sstevel@tonic-gate * ELF support routines for processing versioned mon.out files.
300Sstevel@tonic-gate */
310Sstevel@tonic-gate
32211Smike_s #include <stdlib.h>
33211Smike_s #include <string.h>
340Sstevel@tonic-gate #include "profv.h"
350Sstevel@tonic-gate
360Sstevel@tonic-gate bool
is_shared_obj(char * name)370Sstevel@tonic-gate is_shared_obj(char *name)
380Sstevel@tonic-gate {
390Sstevel@tonic-gate int fd;
400Sstevel@tonic-gate Elf *elf;
410Sstevel@tonic-gate GElf_Ehdr ehdr;
420Sstevel@tonic-gate
430Sstevel@tonic-gate if ((fd = open(name, O_RDONLY)) == -1) {
44211Smike_s (void) fprintf(stderr, "%s: can't open `%s'\n", cmdname, name);
450Sstevel@tonic-gate exit(ERR_ELF);
460Sstevel@tonic-gate }
470Sstevel@tonic-gate
480Sstevel@tonic-gate if (elf_version(EV_CURRENT) == EV_NONE) {
49211Smike_s (void) fprintf(stderr, "%s: libelf out of date\n", cmdname);
500Sstevel@tonic-gate exit(ERR_ELF);
510Sstevel@tonic-gate }
520Sstevel@tonic-gate
530Sstevel@tonic-gate if ((elf = elf_begin(fd, ELF_C_READ, NULL)) == NULL) {
54211Smike_s (void) fprintf(stderr, "%s: elf_begin failed\n", cmdname);
550Sstevel@tonic-gate exit(ERR_ELF);
560Sstevel@tonic-gate }
570Sstevel@tonic-gate
580Sstevel@tonic-gate if (gelf_getehdr(elf, &ehdr) == NULL) {
59211Smike_s (void) fprintf(stderr, "%s: can't read ELF header of %s\n",
600Sstevel@tonic-gate cmdname, name);
610Sstevel@tonic-gate exit(ERR_ELF);
620Sstevel@tonic-gate }
630Sstevel@tonic-gate
64211Smike_s (void) elf_end(elf);
65211Smike_s (void) close(fd);
660Sstevel@tonic-gate
670Sstevel@tonic-gate if (ehdr.e_type == ET_DYN)
680Sstevel@tonic-gate return (TRUE);
690Sstevel@tonic-gate else
700Sstevel@tonic-gate return (FALSE);
710Sstevel@tonic-gate }
720Sstevel@tonic-gate
730Sstevel@tonic-gate static void
rm_dups(nltype * nl,size_t * nfuncs)740Sstevel@tonic-gate rm_dups(nltype *nl, size_t *nfuncs)
750Sstevel@tonic-gate {
760Sstevel@tonic-gate size_t i, prev = 0, ndx = 0;
770Sstevel@tonic-gate int prev_type, prev_bind, cur_type;
780Sstevel@tonic-gate
790Sstevel@tonic-gate for (i = 1; i < *nfuncs; i++) {
800Sstevel@tonic-gate /*
810Sstevel@tonic-gate * If current value is different from prev, proceed.
820Sstevel@tonic-gate */
830Sstevel@tonic-gate if (nl[prev].value < nl[i].value) {
840Sstevel@tonic-gate prev = i;
850Sstevel@tonic-gate continue;
860Sstevel@tonic-gate }
870Sstevel@tonic-gate
880Sstevel@tonic-gate /*
890Sstevel@tonic-gate * If current and prev have the syminfo, rm the latter.
900Sstevel@tonic-gate */
910Sstevel@tonic-gate if (nl[prev].info == nl[i].info) {
920Sstevel@tonic-gate nl[i].name = NULL;
930Sstevel@tonic-gate continue;
940Sstevel@tonic-gate }
950Sstevel@tonic-gate
960Sstevel@tonic-gate prev_type = ELF_ST_TYPE(nl[prev].info);
970Sstevel@tonic-gate prev_bind = ELF_ST_BIND(nl[prev].info);
980Sstevel@tonic-gate cur_type = ELF_ST_TYPE(nl[i].info);
990Sstevel@tonic-gate
1000Sstevel@tonic-gate /*
1010Sstevel@tonic-gate * Remove the one with STT_NOTYPE and keep the other.
1020Sstevel@tonic-gate */
1030Sstevel@tonic-gate if (prev_type != cur_type) {
1040Sstevel@tonic-gate if (prev_type != STT_NOTYPE)
1050Sstevel@tonic-gate nl[i].name = NULL;
1060Sstevel@tonic-gate else {
1070Sstevel@tonic-gate nl[prev].name = NULL;
1080Sstevel@tonic-gate prev = i;
1090Sstevel@tonic-gate }
1100Sstevel@tonic-gate continue;
1110Sstevel@tonic-gate }
1120Sstevel@tonic-gate
1130Sstevel@tonic-gate /*
1140Sstevel@tonic-gate * If they have the same type, take the stronger bound
1150Sstevel@tonic-gate * function
1160Sstevel@tonic-gate */
1170Sstevel@tonic-gate if (prev_bind != STB_WEAK)
1180Sstevel@tonic-gate nl[i].name = NULL;
1190Sstevel@tonic-gate else {
1200Sstevel@tonic-gate nl[prev].name = NULL;
1210Sstevel@tonic-gate prev = i;
1220Sstevel@tonic-gate }
1230Sstevel@tonic-gate }
1240Sstevel@tonic-gate
1250Sstevel@tonic-gate
1260Sstevel@tonic-gate /*
1270Sstevel@tonic-gate * Actually remove the cleared symbols from namelist. We're not
1280Sstevel@tonic-gate * truncating namelist by realloc, though.
1290Sstevel@tonic-gate */
1300Sstevel@tonic-gate for (i = 0; (i < *nfuncs) && (nl[i].name != NULL); i++)
1310Sstevel@tonic-gate ;
1320Sstevel@tonic-gate
1330Sstevel@tonic-gate ndx = i;
1340Sstevel@tonic-gate for (i = ndx + 1; i < *nfuncs; i++) {
1350Sstevel@tonic-gate if (nl[i].name) {
1360Sstevel@tonic-gate nl[ndx] = nl[i];
1370Sstevel@tonic-gate ndx++;
1380Sstevel@tonic-gate }
1390Sstevel@tonic-gate }
1400Sstevel@tonic-gate
1410Sstevel@tonic-gate *nfuncs = ndx;
1420Sstevel@tonic-gate }
1430Sstevel@tonic-gate
1440Sstevel@tonic-gate int
cmp_by_address(const void * arg1,const void * arg2)145211Smike_s cmp_by_address(const void *arg1, const void *arg2)
1460Sstevel@tonic-gate {
147211Smike_s nltype *a = (nltype *)arg1;
148211Smike_s nltype *b = (nltype *)arg2;
149211Smike_s
1500Sstevel@tonic-gate if (a->value < b->value)
1510Sstevel@tonic-gate return (-1);
1520Sstevel@tonic-gate else if (a->value > b->value)
1530Sstevel@tonic-gate return (1);
1540Sstevel@tonic-gate else
1550Sstevel@tonic-gate return (0);
1560Sstevel@tonic-gate }
1570Sstevel@tonic-gate
1580Sstevel@tonic-gate static int
is_function(Elf * elf,GElf_Sym * sym)1590Sstevel@tonic-gate is_function(Elf *elf, GElf_Sym *sym)
1600Sstevel@tonic-gate {
1610Sstevel@tonic-gate Elf_Scn *scn;
1620Sstevel@tonic-gate GElf_Shdr shdr;
1630Sstevel@tonic-gate
1640Sstevel@tonic-gate /*
1650Sstevel@tonic-gate * With dynamic linking, it is possible that certain undefined
1660Sstevel@tonic-gate * symbols exist in the objects. The actual definition will be
1670Sstevel@tonic-gate * found elsewhere, so we'll just skip it for this object.
1680Sstevel@tonic-gate */
1690Sstevel@tonic-gate if (sym->st_shndx == SHN_UNDEF)
1700Sstevel@tonic-gate return (0);
1710Sstevel@tonic-gate
1720Sstevel@tonic-gate if (GELF_ST_TYPE(sym->st_info) == STT_FUNC) {
1730Sstevel@tonic-gate if (GELF_ST_BIND(sym->st_info) == STB_GLOBAL)
1740Sstevel@tonic-gate return (1);
1750Sstevel@tonic-gate
1760Sstevel@tonic-gate if (GELF_ST_BIND(sym->st_info) == STB_WEAK)
1770Sstevel@tonic-gate return (1);
1780Sstevel@tonic-gate
1790Sstevel@tonic-gate if (gflag && GELF_ST_BIND(sym->st_info) == STB_LOCAL)
1800Sstevel@tonic-gate return (1);
1810Sstevel@tonic-gate }
1820Sstevel@tonic-gate
1830Sstevel@tonic-gate /*
1840Sstevel@tonic-gate * It's not a function; determine if it's in an executable section.
1850Sstevel@tonic-gate */
1860Sstevel@tonic-gate if (GELF_ST_TYPE(sym->st_info) != STT_NOTYPE)
1870Sstevel@tonic-gate return (0);
1880Sstevel@tonic-gate
1890Sstevel@tonic-gate /*
1900Sstevel@tonic-gate * If it isn't global, and it isn't weak, and it isn't
1910Sstevel@tonic-gate * a 'local with the gflag set', then get out.
1920Sstevel@tonic-gate */
1930Sstevel@tonic-gate if (GELF_ST_BIND(sym->st_info) != STB_GLOBAL &&
1940Sstevel@tonic-gate GELF_ST_BIND(sym->st_info) != STB_WEAK &&
1950Sstevel@tonic-gate !(gflag && GELF_ST_BIND(sym->st_info) == STB_LOCAL))
1960Sstevel@tonic-gate return (0);
1970Sstevel@tonic-gate
1980Sstevel@tonic-gate if (sym->st_shndx >= SHN_LORESERVE)
1990Sstevel@tonic-gate return (0);
2000Sstevel@tonic-gate
2010Sstevel@tonic-gate scn = elf_getscn(elf, sym->st_shndx);
202211Smike_s (void) gelf_getshdr(scn, &shdr);
2030Sstevel@tonic-gate
2040Sstevel@tonic-gate if (!(shdr.sh_flags & SHF_EXECINSTR))
2050Sstevel@tonic-gate return (0);
2060Sstevel@tonic-gate
2070Sstevel@tonic-gate return (1);
2080Sstevel@tonic-gate }
2090Sstevel@tonic-gate
2100Sstevel@tonic-gate static void
fetch_symtab(Elf * elf,char * filename,mod_info_t * module)2110Sstevel@tonic-gate fetch_symtab(Elf *elf, char *filename, mod_info_t *module)
2120Sstevel@tonic-gate {
213*3621Sab196087 Elf_Scn *scn = NULL, *sym_pri = NULL, *sym_aux = NULL;
2140Sstevel@tonic-gate GElf_Word strndx = 0;
2150Sstevel@tonic-gate size_t i, nsyms, nfuncs;
216*3621Sab196087 GElf_Xword nsyms_pri, nsyms_aux = 0;
217*3621Sab196087 Elf_Data *symdata_pri, *symdata_aux;
2180Sstevel@tonic-gate nltype *nl, *npe;
219*3621Sab196087 int symtab_found = 0;
2200Sstevel@tonic-gate
221*3621Sab196087
222*3621Sab196087 /*
223*3621Sab196087 * Scan the section headers looking for a symbol table. Our
224*3621Sab196087 * preference is to use .symtab, because it contains the full
225*3621Sab196087 * set of symbols. If we find it, we stop looking immediately
226*3621Sab196087 * and use it. In the absence of a .symtab section, we are
227*3621Sab196087 * willing to use the dynamic symbol table (.dynsym), possibly
228*3621Sab196087 * augmented by the .SUNW_ldynsym, which contains local symbols.
229*3621Sab196087 */
230*3621Sab196087 while ((symtab_found == 0) && ((scn = elf_nextscn(elf, scn)) != NULL)) {
2310Sstevel@tonic-gate
2320Sstevel@tonic-gate GElf_Shdr shdr;
2330Sstevel@tonic-gate
2340Sstevel@tonic-gate if (gelf_getshdr(scn, &shdr) == NULL)
2350Sstevel@tonic-gate continue;
2360Sstevel@tonic-gate
237*3621Sab196087 switch (shdr.sh_type) {
238*3621Sab196087 case SHT_SYMTAB:
239*3621Sab196087 nsyms_pri = shdr.sh_size / shdr.sh_entsize;
240*3621Sab196087 strndx = shdr.sh_link;
241*3621Sab196087 sym_pri = scn;
242*3621Sab196087 /* Throw away .SUNW_ldynsym. It is for .dynsym only */
243*3621Sab196087 nsyms_aux = 0;
244*3621Sab196087 sym_aux = NULL;
245*3621Sab196087 /* We have found the best symbol table. Stop looking */
246*3621Sab196087 symtab_found = 1;
247*3621Sab196087 break;
2480Sstevel@tonic-gate
249*3621Sab196087 case SHT_DYNSYM:
250*3621Sab196087 /* We will use .dynsym if no .symtab is found */
251*3621Sab196087 nsyms_pri = shdr.sh_size / shdr.sh_entsize;
2520Sstevel@tonic-gate strndx = shdr.sh_link;
253*3621Sab196087 sym_pri = scn;
254*3621Sab196087 break;
2550Sstevel@tonic-gate
256*3621Sab196087 case SHT_SUNW_LDYNSYM:
257*3621Sab196087 /* Auxiliary table, used with .dynsym */
258*3621Sab196087 nsyms_aux = shdr.sh_size / shdr.sh_entsize;
259*3621Sab196087 sym_aux = scn;
2600Sstevel@tonic-gate break;
261*3621Sab196087 }
2620Sstevel@tonic-gate }
2630Sstevel@tonic-gate
264*3621Sab196087 if (sym_pri == NULL || strndx == 0) {
265211Smike_s (void) fprintf(stderr, "%s: missing symbol table in %s\n",
2660Sstevel@tonic-gate cmdname, filename);
2670Sstevel@tonic-gate exit(ERR_ELF);
2680Sstevel@tonic-gate }
2690Sstevel@tonic-gate
270*3621Sab196087 nsyms = (size_t)(nsyms_pri + nsyms_aux);
271*3621Sab196087 if ((nsyms_pri + nsyms_aux) != (GElf_Xword)nsyms) {
272*3621Sab196087 (void) fprintf(stderr,
273*3621Sab196087 "%s: can't handle more than 2^32 symbols", cmdname);
274*3621Sab196087 exit(ERR_INPUT);
275*3621Sab196087 }
276*3621Sab196087
277*3621Sab196087 if ((symdata_pri = elf_getdata(sym_pri, NULL)) == NULL) {
278211Smike_s (void) fprintf(stderr, "%s: can't read symbol data from %s\n",
279*3621Sab196087 cmdname, filename);
280*3621Sab196087 exit(ERR_ELF);
281*3621Sab196087 }
282*3621Sab196087
283*3621Sab196087 if ((sym_aux != NULL) &&
284*3621Sab196087 ((symdata_aux = elf_getdata(sym_aux, NULL)) == NULL)) {
285*3621Sab196087 (void) fprintf(stderr,
286*3621Sab196087 "%s: can't read .SUNW_ldynsym symbol data from %s\n",
287*3621Sab196087 cmdname, filename);
2880Sstevel@tonic-gate exit(ERR_ELF);
2890Sstevel@tonic-gate }
2900Sstevel@tonic-gate
2910Sstevel@tonic-gate if ((npe = nl = (nltype *) calloc(nsyms, sizeof (nltype))) == NULL) {
292211Smike_s (void) fprintf(stderr, "%s: can't alloc %x bytes for symbols\n",
2930Sstevel@tonic-gate cmdname, nsyms * sizeof (nltype));
2940Sstevel@tonic-gate exit(ERR_ELF);
2950Sstevel@tonic-gate }
2960Sstevel@tonic-gate
2970Sstevel@tonic-gate /*
2980Sstevel@tonic-gate * Now we need to cruise through the symbol table eliminating
2990Sstevel@tonic-gate * all non-functions from consideration, and making strings
3000Sstevel@tonic-gate * real.
3010Sstevel@tonic-gate */
3020Sstevel@tonic-gate nfuncs = 0;
3030Sstevel@tonic-gate
3040Sstevel@tonic-gate for (i = 1; i < nsyms; i++) {
3050Sstevel@tonic-gate GElf_Sym gsym;
3060Sstevel@tonic-gate char *name;
3070Sstevel@tonic-gate
308*3621Sab196087 /*
309*3621Sab196087 * Look up the symbol. In the case where we have a
310*3621Sab196087 * .SUNW_ldynsym/.dynsym pair, we treat them as a single
311*3621Sab196087 * logical table, with the data in .SUNW_ldynsym coming
312*3621Sab196087 * before the data in .dynsym.
313*3621Sab196087 */
314*3621Sab196087 if (i >= nsyms_aux)
315*3621Sab196087 (void) gelf_getsym(symdata_pri, i - nsyms_aux, &gsym);
316*3621Sab196087 else
317*3621Sab196087 (void) gelf_getsym(symdata_aux, i, &gsym);
3180Sstevel@tonic-gate
3190Sstevel@tonic-gate name = elf_strptr(elf, strndx, gsym.st_name);
3200Sstevel@tonic-gate
3210Sstevel@tonic-gate /*
3220Sstevel@tonic-gate * We're interested in this symbol if it's a function
3230Sstevel@tonic-gate */
3240Sstevel@tonic-gate if (is_function(elf, &gsym)) {
3250Sstevel@tonic-gate
3260Sstevel@tonic-gate npe->name = name;
3270Sstevel@tonic-gate npe->value = gsym.st_value;
3280Sstevel@tonic-gate npe->size = gsym.st_size;
3290Sstevel@tonic-gate npe->info = gsym.st_info;
3300Sstevel@tonic-gate
3310Sstevel@tonic-gate npe++;
3320Sstevel@tonic-gate nfuncs++;
3330Sstevel@tonic-gate }
3340Sstevel@tonic-gate
3350Sstevel@tonic-gate if (strcmp(name, PRF_END) == 0)
3360Sstevel@tonic-gate module->data_end = gsym.st_value;
3370Sstevel@tonic-gate }
3380Sstevel@tonic-gate
3390Sstevel@tonic-gate if (npe == nl) {
340211Smike_s (void) fprintf(stderr, "%s: no valid functions in %s\n",
3410Sstevel@tonic-gate cmdname, filename);
3420Sstevel@tonic-gate exit(ERR_INPUT);
3430Sstevel@tonic-gate }
3440Sstevel@tonic-gate
3450Sstevel@tonic-gate /*
3460Sstevel@tonic-gate * And finally, sort the symbols by increasing address
3470Sstevel@tonic-gate * and remove the duplicates.
3480Sstevel@tonic-gate */
349211Smike_s qsort(nl, nfuncs, sizeof (nltype), cmp_by_address);
3500Sstevel@tonic-gate rm_dups(nl, &nfuncs);
3510Sstevel@tonic-gate
3520Sstevel@tonic-gate module->nl = nl;
3530Sstevel@tonic-gate module->nfuncs = nfuncs;
3540Sstevel@tonic-gate }
3550Sstevel@tonic-gate
3560Sstevel@tonic-gate static GElf_Addr
get_txtorigin(Elf * elf,char * filename)3570Sstevel@tonic-gate get_txtorigin(Elf *elf, char *filename)
3580Sstevel@tonic-gate {
3590Sstevel@tonic-gate GElf_Ehdr ehdr;
3600Sstevel@tonic-gate GElf_Phdr phdr;
3610Sstevel@tonic-gate GElf_Half ndx;
3620Sstevel@tonic-gate GElf_Addr txt_origin = 0;
3630Sstevel@tonic-gate bool first_load_seg = TRUE;
3640Sstevel@tonic-gate
3650Sstevel@tonic-gate if (gelf_getehdr(elf, &ehdr) == NULL) {
366211Smike_s (void) fprintf(stderr, "%s: can't read ELF header of %s\n",
3670Sstevel@tonic-gate cmdname, filename);
3680Sstevel@tonic-gate exit(ERR_ELF);
3690Sstevel@tonic-gate }
3700Sstevel@tonic-gate
3710Sstevel@tonic-gate for (ndx = 0; ndx < ehdr.e_phnum; ndx++) {
3720Sstevel@tonic-gate if (gelf_getphdr(elf, ndx, &phdr) == NULL)
3730Sstevel@tonic-gate continue;
3740Sstevel@tonic-gate
3750Sstevel@tonic-gate if ((phdr.p_type == PT_LOAD) && !(phdr.p_flags & PF_W)) {
3760Sstevel@tonic-gate if (first_load_seg || phdr.p_vaddr < txt_origin)
3770Sstevel@tonic-gate txt_origin = phdr.p_vaddr;
3780Sstevel@tonic-gate
3790Sstevel@tonic-gate if (first_load_seg)
3800Sstevel@tonic-gate first_load_seg = FALSE;
3810Sstevel@tonic-gate }
3820Sstevel@tonic-gate }
3830Sstevel@tonic-gate
3840Sstevel@tonic-gate return (txt_origin);
3850Sstevel@tonic-gate }
3860Sstevel@tonic-gate
3870Sstevel@tonic-gate void
get_syms(char * filename,mod_info_t * mi)3880Sstevel@tonic-gate get_syms(char *filename, mod_info_t *mi)
3890Sstevel@tonic-gate {
3900Sstevel@tonic-gate int fd;
3910Sstevel@tonic-gate Elf *elf;
3920Sstevel@tonic-gate
3930Sstevel@tonic-gate if ((fd = open(filename, O_RDONLY)) == -1) {
3940Sstevel@tonic-gate perror(filename);
3950Sstevel@tonic-gate exit(ERR_SYSCALL);
3960Sstevel@tonic-gate }
3970Sstevel@tonic-gate
3980Sstevel@tonic-gate if (elf_version(EV_CURRENT) == EV_NONE) {
399211Smike_s (void) fprintf(stderr, "%s: libelf out of date\n", cmdname);
4000Sstevel@tonic-gate exit(ERR_ELF);
4010Sstevel@tonic-gate }
4020Sstevel@tonic-gate
4030Sstevel@tonic-gate if ((elf = elf_begin(fd, ELF_C_READ, NULL)) == NULL) {
404211Smike_s (void) fprintf(stderr, "%s: elf_begin failed\n", cmdname);
4050Sstevel@tonic-gate exit(ERR_ELF);
4060Sstevel@tonic-gate }
4070Sstevel@tonic-gate
4080Sstevel@tonic-gate if (gelf_getclass(elf) != ELFCLASS64) {
409211Smike_s (void) fprintf(stderr, "%s: unsupported mon.out format for "
4100Sstevel@tonic-gate "this class of object\n", cmdname);
4110Sstevel@tonic-gate exit(ERR_ELF);
4120Sstevel@tonic-gate }
4130Sstevel@tonic-gate
4140Sstevel@tonic-gate mi->txt_origin = get_txtorigin(elf, filename);
4150Sstevel@tonic-gate
4160Sstevel@tonic-gate fetch_symtab(elf, filename, mi);
4170Sstevel@tonic-gate }
418