dtrace_subr.c revision 236567
1179237Sjb/* 2179237Sjb * CDDL HEADER START 3179237Sjb * 4179237Sjb * The contents of this file are subject to the terms of the 5179237Sjb * Common Development and Distribution License, Version 1.0 only 6179237Sjb * (the "License"). You may not use this file except in compliance 7179237Sjb * with the License. 8179237Sjb * 9179237Sjb * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10179237Sjb * or http://www.opensolaris.org/os/licensing. 11179237Sjb * See the License for the specific language governing permissions 12179237Sjb * and limitations under the License. 13179237Sjb * 14179237Sjb * When distributing Covered Code, include this CDDL HEADER in each 15179237Sjb * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16179237Sjb * If applicable, add the following below this CDDL HEADER, with the 17179237Sjb * fields enclosed by brackets "[]" replaced with your own identifying 18179237Sjb * information: Portions Copyright [yyyy] [name of copyright owner] 19179237Sjb * 20179237Sjb * CDDL HEADER END 21179237Sjb * 22179237Sjb * $FreeBSD: head/sys/cddl/dev/dtrace/amd64/dtrace_subr.c 236567 2012-06-04 16:15:40Z gnn $ 23179237Sjb * 24179237Sjb */ 25179237Sjb/* 26179237Sjb * Copyright 2005 Sun Microsystems, Inc. All rights reserved. 27179237Sjb * Use is subject to license terms. 28179237Sjb */ 29179237Sjb 30236567Sgnn/* 31236567Sgnn * Copyright (c) 2011, Joyent, Inc. All rights reserved. 32236567Sgnn */ 33236567Sgnn 34179237Sjb#include <sys/param.h> 35179237Sjb#include <sys/systm.h> 36179237Sjb#include <sys/types.h> 37179237Sjb#include <sys/kernel.h> 38179237Sjb#include <sys/malloc.h> 39179237Sjb#include <sys/kmem.h> 40179237Sjb#include <sys/smp.h> 41179237Sjb#include <sys/dtrace_impl.h> 42179237Sjb#include <sys/dtrace_bsd.h> 43179237Sjb#include <machine/clock.h> 44179237Sjb#include <machine/frame.h> 45179237Sjb#include <vm/pmap.h> 46179237Sjb 47179237Sjbextern uintptr_t dtrace_in_probe_addr; 48179237Sjbextern int dtrace_in_probe; 49179237Sjb 50179237Sjbint dtrace_invop(uintptr_t, uintptr_t *, uintptr_t); 51179237Sjb 52179237Sjbtypedef struct dtrace_invop_hdlr { 53179237Sjb int (*dtih_func)(uintptr_t, uintptr_t *, uintptr_t); 54179237Sjb struct dtrace_invop_hdlr *dtih_next; 55179237Sjb} dtrace_invop_hdlr_t; 56179237Sjb 57179237Sjbdtrace_invop_hdlr_t *dtrace_invop_hdlr; 58179237Sjb 59179237Sjbint 60179237Sjbdtrace_invop(uintptr_t addr, uintptr_t *stack, uintptr_t eax) 61179237Sjb{ 62179237Sjb dtrace_invop_hdlr_t *hdlr; 63179237Sjb int rval; 64179237Sjb 65179237Sjb for (hdlr = dtrace_invop_hdlr; hdlr != NULL; hdlr = hdlr->dtih_next) 66179237Sjb if ((rval = hdlr->dtih_func(addr, stack, eax)) != 0) 67179237Sjb return (rval); 68179237Sjb 69179237Sjb return (0); 70179237Sjb} 71179237Sjb 72179237Sjbvoid 73179237Sjbdtrace_invop_add(int (*func)(uintptr_t, uintptr_t *, uintptr_t)) 74179237Sjb{ 75179237Sjb dtrace_invop_hdlr_t *hdlr; 76179237Sjb 77179237Sjb hdlr = kmem_alloc(sizeof (dtrace_invop_hdlr_t), KM_SLEEP); 78179237Sjb hdlr->dtih_func = func; 79179237Sjb hdlr->dtih_next = dtrace_invop_hdlr; 80179237Sjb dtrace_invop_hdlr = hdlr; 81179237Sjb} 82179237Sjb 83179237Sjbvoid 84179237Sjbdtrace_invop_remove(int (*func)(uintptr_t, uintptr_t *, uintptr_t)) 85179237Sjb{ 86179237Sjb dtrace_invop_hdlr_t *hdlr = dtrace_invop_hdlr, *prev = NULL; 87179237Sjb 88179237Sjb for (;;) { 89179237Sjb if (hdlr == NULL) 90179237Sjb panic("attempt to remove non-existent invop handler"); 91179237Sjb 92179237Sjb if (hdlr->dtih_func == func) 93179237Sjb break; 94179237Sjb 95179237Sjb prev = hdlr; 96179237Sjb hdlr = hdlr->dtih_next; 97179237Sjb } 98179237Sjb 99179237Sjb if (prev == NULL) { 100179237Sjb ASSERT(dtrace_invop_hdlr == hdlr); 101179237Sjb dtrace_invop_hdlr = hdlr->dtih_next; 102179237Sjb } else { 103179237Sjb ASSERT(dtrace_invop_hdlr != hdlr); 104179237Sjb prev->dtih_next = hdlr->dtih_next; 105179237Sjb } 106179237Sjb 107179237Sjb kmem_free(hdlr, 0); 108179237Sjb} 109179237Sjb 110179237Sjb/*ARGSUSED*/ 111179237Sjbvoid 112179237Sjbdtrace_toxic_ranges(void (*func)(uintptr_t base, uintptr_t limit)) 113179237Sjb{ 114179237Sjb (*func)(0, (uintptr_t) addr_PTmap); 115179237Sjb} 116179237Sjb 117179237Sjbvoid 118179237Sjbdtrace_xcall(processorid_t cpu, dtrace_xcall_t func, void *arg) 119179237Sjb{ 120222813Sattilio cpuset_t cpus; 121179237Sjb 122179237Sjb if (cpu == DTRACE_CPUALL) 123179237Sjb cpus = all_cpus; 124179237Sjb else 125222813Sattilio CPU_SETOF(cpu, &cpus); 126179237Sjb 127216251Savg smp_rendezvous_cpus(cpus, smp_no_rendevous_barrier, func, 128216251Savg smp_no_rendevous_barrier, arg); 129179237Sjb} 130179237Sjb 131179237Sjbstatic void 132179237Sjbdtrace_sync_func(void) 133179237Sjb{ 134179237Sjb} 135179237Sjb 136179237Sjbvoid 137179237Sjbdtrace_sync(void) 138179237Sjb{ 139179237Sjb dtrace_xcall(DTRACE_CPUALL, (dtrace_xcall_t)dtrace_sync_func, NULL); 140179237Sjb} 141179237Sjb 142179237Sjb#ifdef notyet 143179237Sjbint (*dtrace_fasttrap_probe_ptr)(struct regs *); 144179237Sjbint (*dtrace_pid_probe_ptr)(struct regs *); 145179237Sjbint (*dtrace_return_probe_ptr)(struct regs *); 146179237Sjb 147179237Sjbvoid 148179237Sjbdtrace_user_probe(struct regs *rp, caddr_t addr, processorid_t cpuid) 149179237Sjb{ 150179237Sjb krwlock_t *rwp; 151179237Sjb proc_t *p = curproc; 152179237Sjb extern void trap(struct regs *, caddr_t, processorid_t); 153179237Sjb 154179237Sjb if (USERMODE(rp->r_cs) || (rp->r_ps & PS_VM)) { 155179237Sjb if (curthread->t_cred != p->p_cred) { 156179237Sjb cred_t *oldcred = curthread->t_cred; 157179237Sjb /* 158179237Sjb * DTrace accesses t_cred in probe context. t_cred 159179237Sjb * must always be either NULL, or point to a valid, 160179237Sjb * allocated cred structure. 161179237Sjb */ 162179237Sjb curthread->t_cred = crgetcred(); 163179237Sjb crfree(oldcred); 164179237Sjb } 165179237Sjb } 166179237Sjb 167179237Sjb if (rp->r_trapno == T_DTRACE_RET) { 168179237Sjb uint8_t step = curthread->t_dtrace_step; 169179237Sjb uint8_t ret = curthread->t_dtrace_ret; 170179237Sjb uintptr_t npc = curthread->t_dtrace_npc; 171179237Sjb 172179237Sjb if (curthread->t_dtrace_ast) { 173179237Sjb aston(curthread); 174179237Sjb curthread->t_sig_check = 1; 175179237Sjb } 176179237Sjb 177179237Sjb /* 178179237Sjb * Clear all user tracing flags. 179179237Sjb */ 180179237Sjb curthread->t_dtrace_ft = 0; 181179237Sjb 182179237Sjb /* 183179237Sjb * If we weren't expecting to take a return probe trap, kill 184179237Sjb * the process as though it had just executed an unassigned 185179237Sjb * trap instruction. 186179237Sjb */ 187179237Sjb if (step == 0) { 188179237Sjb tsignal(curthread, SIGILL); 189179237Sjb return; 190179237Sjb } 191179237Sjb 192179237Sjb /* 193179237Sjb * If we hit this trap unrelated to a return probe, we're 194179237Sjb * just here to reset the AST flag since we deferred a signal 195179237Sjb * until after we logically single-stepped the instruction we 196179237Sjb * copied out. 197179237Sjb */ 198179237Sjb if (ret == 0) { 199179237Sjb rp->r_pc = npc; 200179237Sjb return; 201179237Sjb } 202179237Sjb 203179237Sjb /* 204179237Sjb * We need to wait until after we've called the 205179237Sjb * dtrace_return_probe_ptr function pointer to set %pc. 206179237Sjb */ 207179237Sjb rwp = &CPU->cpu_ft_lock; 208179237Sjb rw_enter(rwp, RW_READER); 209179237Sjb if (dtrace_return_probe_ptr != NULL) 210179237Sjb (void) (*dtrace_return_probe_ptr)(rp); 211179237Sjb rw_exit(rwp); 212179237Sjb rp->r_pc = npc; 213179237Sjb 214179237Sjb } else if (rp->r_trapno == T_DTRACE_PROBE) { 215179237Sjb rwp = &CPU->cpu_ft_lock; 216179237Sjb rw_enter(rwp, RW_READER); 217179237Sjb if (dtrace_fasttrap_probe_ptr != NULL) 218179237Sjb (void) (*dtrace_fasttrap_probe_ptr)(rp); 219179237Sjb rw_exit(rwp); 220179237Sjb 221179237Sjb } else if (rp->r_trapno == T_BPTFLT) { 222179237Sjb uint8_t instr; 223179237Sjb rwp = &CPU->cpu_ft_lock; 224179237Sjb 225179237Sjb /* 226179237Sjb * The DTrace fasttrap provider uses the breakpoint trap 227179237Sjb * (int 3). We let DTrace take the first crack at handling 228179237Sjb * this trap; if it's not a probe that DTrace knowns about, 229179237Sjb * we call into the trap() routine to handle it like a 230179237Sjb * breakpoint placed by a conventional debugger. 231179237Sjb */ 232179237Sjb rw_enter(rwp, RW_READER); 233179237Sjb if (dtrace_pid_probe_ptr != NULL && 234179237Sjb (*dtrace_pid_probe_ptr)(rp) == 0) { 235179237Sjb rw_exit(rwp); 236179237Sjb return; 237179237Sjb } 238179237Sjb rw_exit(rwp); 239179237Sjb 240179237Sjb /* 241179237Sjb * If the instruction that caused the breakpoint trap doesn't 242179237Sjb * look like an int 3 anymore, it may be that this tracepoint 243179237Sjb * was removed just after the user thread executed it. In 244179237Sjb * that case, return to user land to retry the instuction. 245179237Sjb */ 246179237Sjb if (fuword8((void *)(rp->r_pc - 1), &instr) == 0 && 247179237Sjb instr != FASTTRAP_INSTR) { 248179237Sjb rp->r_pc--; 249179237Sjb return; 250179237Sjb } 251179237Sjb 252179237Sjb trap(rp, addr, cpuid); 253179237Sjb 254179237Sjb } else { 255179237Sjb trap(rp, addr, cpuid); 256179237Sjb } 257179237Sjb} 258179237Sjb 259179237Sjbvoid 260179237Sjbdtrace_safe_synchronous_signal(void) 261179237Sjb{ 262179237Sjb kthread_t *t = curthread; 263179237Sjb struct regs *rp = lwptoregs(ttolwp(t)); 264179237Sjb size_t isz = t->t_dtrace_npc - t->t_dtrace_pc; 265179237Sjb 266179237Sjb ASSERT(t->t_dtrace_on); 267179237Sjb 268179237Sjb /* 269179237Sjb * If we're not in the range of scratch addresses, we're not actually 270179237Sjb * tracing user instructions so turn off the flags. If the instruction 271179237Sjb * we copied out caused a synchonous trap, reset the pc back to its 272179237Sjb * original value and turn off the flags. 273179237Sjb */ 274179237Sjb if (rp->r_pc < t->t_dtrace_scrpc || 275179237Sjb rp->r_pc > t->t_dtrace_astpc + isz) { 276179237Sjb t->t_dtrace_ft = 0; 277179237Sjb } else if (rp->r_pc == t->t_dtrace_scrpc || 278179237Sjb rp->r_pc == t->t_dtrace_astpc) { 279179237Sjb rp->r_pc = t->t_dtrace_pc; 280179237Sjb t->t_dtrace_ft = 0; 281179237Sjb } 282179237Sjb} 283179237Sjb 284179237Sjbint 285179237Sjbdtrace_safe_defer_signal(void) 286179237Sjb{ 287179237Sjb kthread_t *t = curthread; 288179237Sjb struct regs *rp = lwptoregs(ttolwp(t)); 289179237Sjb size_t isz = t->t_dtrace_npc - t->t_dtrace_pc; 290179237Sjb 291179237Sjb ASSERT(t->t_dtrace_on); 292179237Sjb 293179237Sjb /* 294179237Sjb * If we're not in the range of scratch addresses, we're not actually 295179237Sjb * tracing user instructions so turn off the flags. 296179237Sjb */ 297179237Sjb if (rp->r_pc < t->t_dtrace_scrpc || 298179237Sjb rp->r_pc > t->t_dtrace_astpc + isz) { 299179237Sjb t->t_dtrace_ft = 0; 300179237Sjb return (0); 301179237Sjb } 302179237Sjb 303179237Sjb /* 304236567Sgnn * If we have executed the original instruction, but we have performed 305236567Sgnn * neither the jmp back to t->t_dtrace_npc nor the clean up of any 306236567Sgnn * registers used to emulate %rip-relative instructions in 64-bit mode, 307236567Sgnn * we'll save ourselves some effort by doing that here and taking the 308236567Sgnn * signal right away. We detect this condition by seeing if the program 309236567Sgnn * counter is the range [scrpc + isz, astpc). 310179237Sjb */ 311236567Sgnn if (rp->r_pc >= t->t_dtrace_scrpc + isz && 312236567Sgnn rp->r_pc < t->t_dtrace_astpc) { 313179237Sjb#ifdef __amd64 314179237Sjb /* 315179237Sjb * If there is a scratch register and we're on the 316179237Sjb * instruction immediately after the modified instruction, 317179237Sjb * restore the value of that scratch register. 318179237Sjb */ 319179237Sjb if (t->t_dtrace_reg != 0 && 320179237Sjb rp->r_pc == t->t_dtrace_scrpc + isz) { 321179237Sjb switch (t->t_dtrace_reg) { 322179237Sjb case REG_RAX: 323179237Sjb rp->r_rax = t->t_dtrace_regv; 324179237Sjb break; 325179237Sjb case REG_RCX: 326179237Sjb rp->r_rcx = t->t_dtrace_regv; 327179237Sjb break; 328179237Sjb case REG_R8: 329179237Sjb rp->r_r8 = t->t_dtrace_regv; 330179237Sjb break; 331179237Sjb case REG_R9: 332179237Sjb rp->r_r9 = t->t_dtrace_regv; 333179237Sjb break; 334179237Sjb } 335179237Sjb } 336179237Sjb#endif 337179237Sjb rp->r_pc = t->t_dtrace_npc; 338179237Sjb t->t_dtrace_ft = 0; 339179237Sjb return (0); 340179237Sjb } 341179237Sjb 342179237Sjb /* 343179237Sjb * Otherwise, make sure we'll return to the kernel after executing 344179237Sjb * the copied out instruction and defer the signal. 345179237Sjb */ 346179237Sjb if (!t->t_dtrace_step) { 347179237Sjb ASSERT(rp->r_pc < t->t_dtrace_astpc); 348179237Sjb rp->r_pc += t->t_dtrace_astpc - t->t_dtrace_scrpc; 349179237Sjb t->t_dtrace_step = 1; 350179237Sjb } 351179237Sjb 352179237Sjb t->t_dtrace_ast = 1; 353179237Sjb 354179237Sjb return (1); 355179237Sjb} 356179237Sjb#endif 357179237Sjb 358179237Sjbstatic int64_t tgt_cpu_tsc; 359179237Sjbstatic int64_t hst_cpu_tsc; 360179237Sjbstatic int64_t tsc_skew[MAXCPU]; 361195710Savgstatic uint64_t nsec_scale; 362179237Sjb 363195710Savg/* See below for the explanation of this macro. */ 364195710Savg#define SCALE_SHIFT 28 365195710Savg 366179237Sjbstatic void 367179237Sjbdtrace_gethrtime_init_cpu(void *arg) 368179237Sjb{ 369179237Sjb uintptr_t cpu = (uintptr_t) arg; 370179237Sjb 371179237Sjb if (cpu == curcpu) 372179237Sjb tgt_cpu_tsc = rdtsc(); 373179237Sjb else 374179237Sjb hst_cpu_tsc = rdtsc(); 375179237Sjb} 376179237Sjb 377179237Sjbstatic void 378179237Sjbdtrace_gethrtime_init(void *arg) 379179237Sjb{ 380216250Savg struct pcpu *pc; 381195710Savg uint64_t tsc_f; 382222813Sattilio cpuset_t map; 383179237Sjb int i; 384179237Sjb 385195710Savg /* 386195710Savg * Get TSC frequency known at this moment. 387195710Savg * This should be constant if TSC is invariant. 388195710Savg * Otherwise tick->time conversion will be inaccurate, but 389195710Savg * will preserve monotonic property of TSC. 390195710Savg */ 391220433Sjkim tsc_f = atomic_load_acq_64(&tsc_freq); 392195710Savg 393195710Savg /* 394195710Savg * The following line checks that nsec_scale calculated below 395195710Savg * doesn't overflow 32-bit unsigned integer, so that it can multiply 396195710Savg * another 32-bit integer without overflowing 64-bit. 397195710Savg * Thus minimum supported TSC frequency is 62.5MHz. 398195710Savg */ 399195710Savg KASSERT(tsc_f > (NANOSEC >> (32 - SCALE_SHIFT)), ("TSC frequency is too low")); 400195710Savg 401195710Savg /* 402195710Savg * We scale up NANOSEC/tsc_f ratio to preserve as much precision 403195710Savg * as possible. 404195710Savg * 2^28 factor was chosen quite arbitrarily from practical 405195710Savg * considerations: 406195710Savg * - it supports TSC frequencies as low as 62.5MHz (see above); 407195710Savg * - it provides quite good precision (e < 0.01%) up to THz 408195710Savg * (terahertz) values; 409195710Savg */ 410195710Savg nsec_scale = ((uint64_t)NANOSEC << SCALE_SHIFT) / tsc_f; 411195710Savg 412179237Sjb /* The current CPU is the reference one. */ 413216250Savg sched_pin(); 414179237Sjb tsc_skew[curcpu] = 0; 415209059Sjhb CPU_FOREACH(i) { 416179237Sjb if (i == curcpu) 417179237Sjb continue; 418179237Sjb 419216250Savg pc = pcpu_find(i); 420223758Sattilio CPU_SETOF(PCPU_GET(cpuid), &map); 421223758Sattilio CPU_SET(pc->pc_cpuid, &map); 422179237Sjb 423221740Savg smp_rendezvous_cpus(map, NULL, 424179237Sjb dtrace_gethrtime_init_cpu, 425179237Sjb smp_no_rendevous_barrier, (void *)(uintptr_t) i); 426179237Sjb 427179237Sjb tsc_skew[i] = tgt_cpu_tsc - hst_cpu_tsc; 428179237Sjb } 429216250Savg sched_unpin(); 430179237Sjb} 431179237Sjb 432179237SjbSYSINIT(dtrace_gethrtime_init, SI_SUB_SMP, SI_ORDER_ANY, dtrace_gethrtime_init, NULL); 433179237Sjb 434179237Sjb/* 435179237Sjb * DTrace needs a high resolution time function which can 436179237Sjb * be called from a probe context and guaranteed not to have 437179237Sjb * instrumented with probes itself. 438179237Sjb * 439179237Sjb * Returns nanoseconds since boot. 440179237Sjb */ 441179237Sjbuint64_t 442179237Sjbdtrace_gethrtime() 443179237Sjb{ 444195710Savg uint64_t tsc; 445195710Savg uint32_t lo; 446195710Savg uint32_t hi; 447195710Savg 448195710Savg /* 449195710Savg * We split TSC value into lower and higher 32-bit halves and separately 450195710Savg * scale them with nsec_scale, then we scale them down by 2^28 451195710Savg * (see nsec_scale calculations) taking into account 32-bit shift of 452195710Savg * the higher half and finally add. 453195710Savg */ 454236566Szml tsc = rdtsc() - tsc_skew[curcpu]; 455195710Savg lo = tsc; 456195710Savg hi = tsc >> 32; 457195710Savg return (((lo * nsec_scale) >> SCALE_SHIFT) + 458195710Savg ((hi * nsec_scale) << (32 - SCALE_SHIFT))); 459179237Sjb} 460179237Sjb 461179237Sjbuint64_t 462179237Sjbdtrace_gethrestime(void) 463179237Sjb{ 464179237Sjb printf("%s(%d): XXX\n",__func__,__LINE__); 465179237Sjb return (0); 466179237Sjb} 467179237Sjb 468179237Sjb/* Function to handle DTrace traps during probes. See amd64/amd64/trap.c */ 469179237Sjbint 470179237Sjbdtrace_trap(struct trapframe *frame, u_int type) 471179237Sjb{ 472179237Sjb /* 473179237Sjb * A trap can occur while DTrace executes a probe. Before 474179237Sjb * executing the probe, DTrace blocks re-scheduling and sets 475179237Sjb * a flag in it's per-cpu flags to indicate that it doesn't 476218909Sbrucec * want to fault. On returning from the probe, the no-fault 477179237Sjb * flag is cleared and finally re-scheduling is enabled. 478179237Sjb * 479179237Sjb * Check if DTrace has enabled 'no-fault' mode: 480179237Sjb * 481179237Sjb */ 482179237Sjb if ((cpu_core[curcpu].cpuc_dtrace_flags & CPU_DTRACE_NOFAULT) != 0) { 483179237Sjb /* 484179237Sjb * There are only a couple of trap types that are expected. 485179237Sjb * All the rest will be handled in the usual way. 486179237Sjb */ 487179237Sjb switch (type) { 488179237Sjb /* Privilieged instruction fault. */ 489179237Sjb case T_PRIVINFLT: 490179237Sjb break; 491179237Sjb /* General protection fault. */ 492179237Sjb case T_PROTFLT: 493179237Sjb /* Flag an illegal operation. */ 494179237Sjb cpu_core[curcpu].cpuc_dtrace_flags |= CPU_DTRACE_ILLOP; 495179237Sjb 496179237Sjb /* 497179237Sjb * Offset the instruction pointer to the instruction 498179237Sjb * following the one causing the fault. 499179237Sjb */ 500179237Sjb frame->tf_rip += dtrace_instr_size((u_char *) frame->tf_rip); 501179237Sjb return (1); 502179237Sjb /* Page fault. */ 503179237Sjb case T_PAGEFLT: 504179237Sjb /* Flag a bad address. */ 505179237Sjb cpu_core[curcpu].cpuc_dtrace_flags |= CPU_DTRACE_BADADDR; 506179237Sjb cpu_core[curcpu].cpuc_dtrace_illval = frame->tf_addr; 507179237Sjb 508179237Sjb /* 509179237Sjb * Offset the instruction pointer to the instruction 510179237Sjb * following the one causing the fault. 511179237Sjb */ 512179237Sjb frame->tf_rip += dtrace_instr_size((u_char *) frame->tf_rip); 513179237Sjb return (1); 514179237Sjb default: 515179237Sjb /* Handle all other traps in the usual way. */ 516179237Sjb break; 517179237Sjb } 518179237Sjb } 519179237Sjb 520179237Sjb /* Handle the trap in the usual way. */ 521179237Sjb return (0); 522179237Sjb} 523