1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright (c) 1994, by Sun Microsytems, Inc. 24 */ 25 26 #pragma ident "%Z%%M% %I% %E% SMI" 27 28 /* 29 * Utility functions to initialize tnfctl handle, find functions that 30 * can be plugged into probes, find trace file information, and create 31 * a trace file for process tracing. 32 */ 33 34 #ifndef DEBUG 35 #define NDEBUG 1 36 #endif 37 38 #include "tnfctl_int.h" 39 #include "dbg.h" 40 41 #include <assert.h> 42 #include <errno.h> 43 #include <stdio.h> 44 #include <string.h> 45 #include <unistd.h> 46 #include <stdlib.h> 47 #include <fcntl.h> 48 #include <sys/param.h> 49 50 #include "tnf_buf.h" 51 /* 52 * Defines - Project private interfaces in libtnfprobe.so 53 */ 54 55 #define TRACEFILE_NAME "tnf_trace_file_name" 56 #define TRACEFILE_SIZE "tnf_trace_file_size" 57 #define TRACEFILE_MIN "tnf_trace_file_min" 58 #define TRACE_ERROR "_tnfw_b_control" 59 60 #define TRACE_ALLOC "tnf_trace_alloc" 61 #define TRACE_COMMIT "tnf_trace_commit" 62 #define TRACE_ROLLBACK "tnf_trace_rollback" 63 #define DEBUG_ENTRY "tnf_probe_debug" 64 65 #define PROBE_LIST_HEAD "__tnf_probe_list_head" 66 #define PROBE_LIST_VALID "__tnf_probe_list_valid" 67 68 #define NONTHREAD_TEST "tnf_non_threaded_test_addr" 69 #define THREAD_TEST "tnf_threaded_test_addr" 70 #define PROBE_THR_SYNC "__tnf_probe_thr_sync" 71 72 #define MEMSEG_PTR "__tnf_probe_memseg_p" 73 74 /* Project private interfaces in libthread.so */ 75 #define LIBTHREAD_PRESENT "thr_probe_getfunc_addr" 76 77 /* 78 * Local declarations 79 */ 80 81 static tnfctl_errcode_t find_test_func(tnfctl_handle_t *hndl); 82 static tnfctl_errcode_t find_target_syms(tnfctl_handle_t *hndl); 83 static tnfctl_errcode_t find_trace_file_info(tnfctl_handle_t *hndl); 84 static tnfctl_errcode_t check_trace_error(tnfctl_handle_t *hndl); 85 86 /* 87 * _tnfctl_refresh_process() - search for new shared objects. If any 88 * found, discover probes in new shared objects. 89 * NOT to be called in kernel mode. 90 */ 91 92 tnfctl_errcode_t 93 _tnfctl_refresh_process(tnfctl_handle_t *hndl, boolean_t *lmap_ok, 94 enum event_op_t *dl_evt) 95 { 96 tnfctl_errcode_t prexstat = TNFCTL_ERR_NONE; 97 boolean_t release_lock; 98 99 assert(hndl->mode != KERNEL_MODE); 100 101 /*LINTED statement has no consequent: else*/ 102 LOCK(hndl, prexstat, release_lock); 103 104 prexstat = check_trace_error(hndl); 105 if (prexstat) 106 goto finish_func; 107 108 /* 109 * update the link map. caller decides what to do on 110 * inconsistent link map 111 */ 112 prexstat = _tnfctl_lmap_update(hndl, lmap_ok, dl_evt); 113 if (prexstat) 114 goto finish_func; 115 116 /* link map is ok now */ 117 prexstat = find_test_func(hndl); 118 if (prexstat) 119 goto finish_func; 120 if (*dl_evt != EVT_NONE) { 121 prexstat = _tnfctl_find_all_probes(hndl); 122 if (prexstat) 123 goto finish_func; 124 } 125 126 finish_func: 127 /*LINTED statement has no consequent: else*/ 128 UNLOCK(hndl, release_lock); 129 130 return (prexstat); 131 } 132 133 /* 134 * initialize tnfctl handle for a new target 135 */ 136 tnfctl_errcode_t 137 _tnfctl_set_state(tnfctl_handle_t *hndl) 138 { 139 tnfctl_errcode_t prexstat = TNFCTL_ERR_NONE; 140 boolean_t lmap_ok; 141 enum event_op_t dl_evt; 142 boolean_t release_lock; 143 144 hndl->targ_pid = hndl->p_getpid(hndl->proc_p); 145 146 /*LINTED statement has no consequent: else*/ 147 LOCK(hndl, prexstat, release_lock); 148 149 /* 150 * initialize the link map table. If link map is not ok, it is an 151 * error. 152 */ 153 prexstat = _tnfctl_lmap_update(hndl, &lmap_ok, &dl_evt); 154 if (prexstat) 155 goto end_func; 156 157 /* find the needed target symbols */ 158 prexstat = find_target_syms(hndl); 159 if (prexstat) { 160 /* is libtnfprobe.so loaded in target ? */ 161 goto end_func; 162 } 163 164 prexstat = find_trace_file_info(hndl); 165 if (prexstat) 166 goto end_func; 167 168 prexstat = find_test_func(hndl); 169 if (prexstat) 170 goto end_func; 171 172 prexstat = _tnfctl_find_all_probes(hndl); 173 if (prexstat) 174 goto end_func; 175 176 prexstat = check_trace_error(hndl); 177 /* fall into end_func */ 178 179 end_func: 180 /*LINTED statement has no consequent: else*/ 181 UNLOCK(hndl, release_lock); 182 183 return (prexstat); 184 } 185 186 /* 187 * find the test function for a probe. The test function could change 188 * with time, so we have to repeatedly check for the test function to use 189 */ 190 static tnfctl_errcode_t 191 find_test_func(tnfctl_handle_t *hndl) 192 { 193 long thr_sync; 194 int miscstat; 195 196 if (hndl->mt_target == B_FALSE) { 197 /* no libthread linked in */ 198 hndl->testfunc = hndl->nonthread_test; 199 } else { 200 /* 201 * check whether libthread/libtnfw have synced up. 202 * If not yet synced up, use non-threaded test function 203 */ 204 205 /* assume we are going to use threaded test */ 206 hndl->testfunc = hndl->thread_test; 207 miscstat = hndl->p_read(hndl->proc_p, hndl->thread_sync, 208 &thr_sync, sizeof (thr_sync)); 209 if (miscstat != 0) 210 return (TNFCTL_ERR_INTERNAL); 211 /* if not yet synced up, change test func to non-threaded one */ 212 if (thr_sync == 0) { 213 hndl->testfunc = hndl->nonthread_test; 214 } 215 } 216 217 /* 218 * Note: the testfunc in the target can change underneath us because 219 * in an MT program the init section of libthread changes all the 220 * test functions from the non-threaded one to the threaded one. 221 * So, every time we write out a probe, we have to make sure that 222 * we are using the correct test function by not trusting the test 223 * function in our copy of the probe. A more fool-proof solution 224 * which will allow other fields in the probe to change internally 225 * is to refresh every probe on a _tnfctl_refresh_process() 226 */ 227 return (TNFCTL_ERR_NONE); 228 } 229 230 /* 231 * check_trace_error() - checks whether there was an error in tracing 232 * side effects trace_buf_state and trace_state in hndl 233 * note: call this function only after trace_file_name is set up 234 * in hndl 235 */ 236 tnfctl_errcode_t 237 check_trace_error(tnfctl_handle_t *hndl) 238 { 239 int miscstat; 240 uintptr_t trace_error_ptr; 241 TNFW_B_CONTROL trace_error_rec; 242 243 /* read in the value of the control structure pointer */ 244 miscstat = hndl->p_read(hndl->proc_p, hndl->trace_error, 245 &trace_error_ptr, sizeof (trace_error_ptr)); 246 if (miscstat != 0) 247 return (TNFCTL_ERR_INTERNAL); 248 249 /* read in the value of the control structure */ 250 miscstat = hndl->p_read(hndl->proc_p, trace_error_ptr, &trace_error_rec, 251 sizeof (trace_error_rec)); 252 if (miscstat != 0) 253 return (TNFCTL_ERR_INTERNAL); 254 255 if (trace_error_rec.tnf_state == TNFW_B_NOBUFFER) { 256 /* 257 * massage into correct state for caller - the target might 258 * not have hit the first probe and hence we got "no buffer". 259 * So, if the user had given a file name, return BUF_OK. 260 */ 261 if (hndl->trace_file_name == NULL) 262 hndl->trace_buf_state = TNFCTL_BUF_NONE; 263 else 264 hndl->trace_buf_state = TNFCTL_BUF_OK; 265 } else if (trace_error_rec.tnf_state == TNFW_B_BROKEN) { 266 hndl->trace_buf_state = TNFCTL_BUF_BROKEN; 267 } else { 268 hndl->trace_buf_state = TNFCTL_BUF_OK; 269 } 270 271 if (TNFW_B_IS_STOPPED(trace_error_rec.tnf_state)) 272 hndl->trace_state = B_FALSE; 273 else 274 hndl->trace_state = B_TRUE; 275 276 return (TNFCTL_ERR_NONE); 277 278 } /* end find_alloc_func */ 279 280 /* 281 * find_target_syms() - finds needed target functions 282 * sideffects allocfunc, commitfunc, endfunc, rollbackfunc in hndl 283 */ 284 static tnfctl_errcode_t 285 find_target_syms(tnfctl_handle_t *hndl) 286 { 287 tnfctl_errcode_t prexstat; 288 uintptr_t temp_addr; 289 int miscstat; 290 291 prexstat = _tnfctl_sym_find(hndl, TRACE_ALLOC, &hndl->allocfunc); 292 if (prexstat) 293 goto end_of_func; 294 295 prexstat = _tnfctl_sym_find(hndl, TRACE_COMMIT, &hndl->commitfunc); 296 if (prexstat) 297 goto end_of_func; 298 299 prexstat = _tnfctl_sym_find(hndl, TRACE_END_FUNC, &hndl->endfunc); 300 if (prexstat) 301 goto end_of_func; 302 303 prexstat = _tnfctl_sym_find(hndl, TRACE_ROLLBACK, &hndl->rollbackfunc); 304 if (prexstat) 305 goto end_of_func; 306 307 prexstat = _tnfctl_sym_find(hndl, PROBE_LIST_HEAD, 308 &hndl->probelist_head); 309 if (prexstat) 310 goto end_of_func; 311 312 prexstat = _tnfctl_sym_find(hndl, TRACE_ERROR, &hndl->trace_error); 313 if (prexstat) 314 goto end_of_func; 315 316 prexstat = _tnfctl_sym_find(hndl, MEMSEG_PTR, &temp_addr); 317 if (prexstat) 318 goto end_of_func; 319 320 /* dereference to get the actual address of structure */ 321 miscstat = hndl->p_read(hndl->proc_p, temp_addr, &hndl->memseg_p, 322 sizeof (hndl->memseg_p)); 323 if (miscstat != 0) 324 return (TNFCTL_ERR_INTERNAL); 325 326 prexstat = _tnfctl_sym_find(hndl, PROBE_LIST_VALID, 327 &hndl->probelist_valid); 328 if (prexstat) 329 goto end_of_func; 330 331 prexstat = _tnfctl_sym_find(hndl, NONTHREAD_TEST, &temp_addr); 332 if (prexstat) 333 goto end_of_func; 334 335 /* dereference to get the actual function address */ 336 miscstat = hndl->p_read(hndl->proc_p, temp_addr, &hndl->nonthread_test, 337 sizeof (hndl->nonthread_test)); 338 if (miscstat != 0) 339 return (TNFCTL_ERR_INTERNAL); 340 341 prexstat = _tnfctl_sym_find(hndl, THREAD_TEST, &temp_addr); 342 if (prexstat) 343 goto end_of_func; 344 345 /* dereference to get the actual function address */ 346 miscstat = hndl->p_read(hndl->proc_p, temp_addr, &hndl->thread_test, 347 sizeof (hndl->thread_test)); 348 if (miscstat != 0) 349 return (TNFCTL_ERR_INTERNAL); 350 351 prexstat = _tnfctl_sym_find(hndl, PROBE_THR_SYNC, &hndl->thread_sync); 352 if (prexstat) 353 goto end_of_func; 354 355 prexstat = _tnfctl_sym_find(hndl, LIBTHREAD_PRESENT, &temp_addr); 356 if (prexstat) { 357 if (prexstat == TNFCTL_ERR_BADARG) { 358 /* no libthread linked in */ 359 hndl->mt_target = B_FALSE; 360 /* this is not an error condition */ 361 prexstat = TNFCTL_ERR_NONE; 362 } else { 363 return (prexstat); 364 } 365 } else { 366 hndl->mt_target = B_TRUE; 367 } 368 369 end_of_func: 370 if (prexstat == TNFCTL_ERR_BADARG) 371 prexstat = TNFCTL_ERR_NOLIBTNFPROBE; 372 373 return (prexstat); 374 } 375 376 /* 377 * _tnfctl_create_tracefile() - initializes tracefile, sets the tracefile name 378 * and size 379 * side effects trace_file_name and trace_buf_size in hndl 380 */ 381 382 #define ZBUFSZ (64 * 1024) 383 384 tnfctl_errcode_t 385 _tnfctl_create_tracefile(tnfctl_handle_t *hndl, const char *trace_file_name, 386 uint_t trace_file_size) 387 { 388 char *preexisting; 389 tnfctl_errcode_t prexstat; 390 int miscstat; 391 char path[MAXPATHLEN]; 392 uintptr_t name_addr, size_addr; 393 uint_t outsize; 394 char zerobuf[ZBUFSZ]; 395 int fd, sz, i; 396 397 /* find the neccessary symbols in the target */ 398 prexstat = _tnfctl_sym_find(hndl, TRACEFILE_NAME, &name_addr); 399 if (prexstat) { 400 if (prexstat == TNFCTL_ERR_BADARG) 401 prexstat = TNFCTL_ERR_INTERNAL; 402 return (prexstat); 403 } 404 prexstat = _tnfctl_sym_find(hndl, TRACEFILE_SIZE, &size_addr); 405 if (prexstat) { 406 if (prexstat == TNFCTL_ERR_BADARG) 407 prexstat = TNFCTL_ERR_INTERNAL; 408 return (prexstat); 409 } 410 411 /* Double check that a file name doesn't already exist */ 412 preexisting = NULL; 413 prexstat = _tnfctl_readstr_targ(hndl, name_addr, &preexisting); 414 if (prexstat) { 415 if (preexisting) 416 free(preexisting); 417 return (prexstat); 418 } 419 420 /* There better not be a file name there yet */ 421 assert(preexisting[0] == '\0'); 422 423 /* paranoia - for optimized compilation */ 424 if (preexisting[0] != '\0') 425 return (TNFCTL_ERR_BUFEXISTS); 426 427 /* free memory in preexisting string */ 428 if (preexisting) 429 free(preexisting); 430 431 if (trace_file_size < hndl->trace_min_size) { 432 return (TNFCTL_ERR_SIZETOOSMALL); 433 } 434 435 /* do we have an absolute, relative or no pathname specified? */ 436 if (trace_file_name == NULL) { 437 return (TNFCTL_ERR_BADARG); 438 } 439 if (trace_file_name[0] == '/') { 440 /* absolute path to tracefile specified */ 441 if ((strlen(trace_file_name) + 1) > (size_t) MAXPATHLEN) { 442 /* directory specification too long */ 443 return (TNFCTL_ERR_BADARG); 444 } 445 (void) strcpy(path, trace_file_name); 446 } else { 447 char *cwd; 448 449 /* relative path to tracefile specified */ 450 cwd = getcwd(NULL, MAXPATHLEN); 451 if (!cwd) { 452 return (tnfctl_status_map(errno)); 453 } 454 if ((strlen(cwd) + 1 + strlen(trace_file_name) + 1) > 455 (size_t) MAXPATHLEN) { 456 /* path name too long */ 457 return (TNFCTL_ERR_BADARG); 458 } 459 (void) sprintf(path, "%s/%s", cwd, trace_file_name); 460 461 free(cwd); 462 } 463 464 outsize = trace_file_size; 465 466 DBG_TNF_PROBE_2(_tnfctl_create_tracefile_1, "libtnfctl", 467 "sunw%verbosity 1; sunw%debug 'setting trace file name'", 468 tnf_string, tracefile_name, path, 469 tnf_long, tracefile_size, outsize); 470 471 /* unlink a previous tracefile (if one exists) */ 472 (void) unlink(path); 473 474 /* create the new tracefile */ 475 fd = open(path, O_CREAT | O_RDWR | O_TRUNC, 0644); 476 if (fd < 0) { 477 return (tnfctl_status_map(errno)); 478 } 479 480 /* zero fill the file */ 481 (void) memset(zerobuf, 0, ZBUFSZ); 482 sz = ZBUFSZ; 483 for (i = 0; i < outsize; i += sz) { 484 ulong_t retval; 485 486 sz = ((outsize - i) > ZBUFSZ) ? ZBUFSZ : (outsize - i); 487 retval = write(fd, zerobuf, sz); 488 if (retval != sz) { 489 /* trouble zeroing tracefile */ 490 return (tnfctl_status_map(errno)); 491 } 492 } 493 494 /* close the file */ 495 (void) close(fd); 496 497 /* write the tracefile name and size into the target process */ 498 miscstat = hndl->p_write(hndl->proc_p, name_addr, path, 499 strlen(path) + 1); 500 if (miscstat != 0) 501 return (TNFCTL_ERR_INTERNAL); 502 miscstat = hndl->p_write(hndl->proc_p, size_addr, &outsize, 503 sizeof (outsize)); 504 if (miscstat != 0) 505 return (TNFCTL_ERR_INTERNAL); 506 507 hndl->trace_file_name = strdup(path); 508 if (hndl->trace_file_name == NULL) 509 return (TNFCTL_ERR_ALLOCFAIL); 510 hndl->trace_buf_size = outsize; 511 hndl->trace_buf_state = TNFCTL_BUF_OK; 512 return (TNFCTL_ERR_NONE); 513 } /* end _tnfctl_create_tracefile */ 514 515 /* 516 * find_trace_file_info() 517 * finds out information about the trace file. 518 * side effects trace_buf_size, trace_min_size, trace_file_name in hndl 519 */ 520 521 static tnfctl_errcode_t 522 find_trace_file_info(tnfctl_handle_t *hndl) 523 { 524 tnfctl_errcode_t prexstat; 525 int miscstat; 526 char *preexisting; 527 uintptr_t name_addr, size_addr, min_addr; 528 uint_t outsize, minoutsize; 529 530 /* find the neccessary symbols in the target */ 531 prexstat = _tnfctl_sym_find(hndl, TRACEFILE_NAME, &name_addr); 532 if (prexstat) { 533 if (prexstat == TNFCTL_ERR_BADARG) 534 prexstat = TNFCTL_ERR_INTERNAL; 535 return (prexstat); 536 } 537 prexstat = _tnfctl_sym_find(hndl, TRACEFILE_SIZE, &size_addr); 538 if (prexstat) { 539 if (prexstat == TNFCTL_ERR_BADARG) 540 prexstat = TNFCTL_ERR_INTERNAL; 541 return (prexstat); 542 } 543 prexstat = _tnfctl_sym_find(hndl, TRACEFILE_MIN, &min_addr); 544 if (prexstat) { 545 if (prexstat == TNFCTL_ERR_BADARG) 546 prexstat = TNFCTL_ERR_INTERNAL; 547 return (prexstat); 548 } 549 550 /* read file name */ 551 preexisting = NULL; 552 prexstat = _tnfctl_readstr_targ(hndl, name_addr, &preexisting); 553 if (prexstat) { 554 if (preexisting) 555 free(preexisting); 556 return (prexstat); 557 } 558 559 /* read the minimum file size from the target */ 560 miscstat = hndl->p_read(hndl->proc_p, min_addr, &minoutsize, 561 sizeof (minoutsize)); 562 if (miscstat != 0) 563 return (TNFCTL_ERR_INTERNAL); 564 hndl->trace_min_size = minoutsize; 565 566 /* if there is no filename, we are done */ 567 if (preexisting[0] == '\0') { 568 hndl->trace_file_name = NULL; 569 hndl->trace_buf_size = 0; 570 } else { 571 hndl->trace_file_name = preexisting; 572 /* read size of file */ 573 miscstat = hndl->p_read(hndl->proc_p, size_addr, 574 &outsize, sizeof (outsize)); 575 if (miscstat != 0) 576 return (TNFCTL_ERR_INTERNAL); 577 hndl->trace_buf_size = outsize; 578 } 579 580 return (TNFCTL_ERR_NONE); 581 } /* end find_trace_file_info */ 582 583 /* 584 * wrapper functions over native /proc functions implemented by proc 585 * layer 586 */ 587 int 588 _tnfctl_read_targ(void *proc_p, uintptr_t addr, void *buf, size_t size) 589 { 590 return (prb_proc_read(proc_p, addr, buf, size)); 591 } 592 593 int 594 _tnfctl_write_targ(void *proc_p, uintptr_t addr, void *buf, size_t size) 595 { 596 return (prb_proc_write(proc_p, addr, buf, size)); 597 } 598 599 int 600 _tnfctl_loadobj_iter(void *proc_p, tnfctl_ind_obj_f *func, void *client_data) 601 { 602 prb_loadobj_f *same_func = (prb_loadobj_f *) func; 603 604 return (prb_loadobj_iter(proc_p, same_func, client_data)); 605 } 606 607 pid_t 608 _tnfctl_pid_get(void *proc_p) 609 { 610 return (prb_proc_pid_get(proc_p)); 611 } 612 613 /* 614 * _tnfctl_readstr_targ() - dereferences a string in the target 615 * NOTE: There is a similar routine called prb_proc_readstr() 616 * used by proc layer. It would be better if there was only 617 * one of these functions defined. 618 */ 619 620 #define BUFSZ 256 621 622 tnfctl_errcode_t 623 _tnfctl_readstr_targ(tnfctl_handle_t *hndl, uintptr_t addr, char **outstr_pp) 624 { 625 int retstat; 626 int bufsz = BUFSZ; 627 char buffer[BUFSZ + 1]; 628 offset_t offset; 629 char *ptr, *orig_ptr; 630 631 *outstr_pp = NULL; 632 offset = 0; 633 634 /* allocate an inital return buffer */ 635 ptr = (char *) malloc(BUFSZ); 636 if (!ptr) { 637 DBG((void) fprintf(stderr, 638 "_tnfctl_readstr_targ: malloc failed\n")); 639 return (TNFCTL_ERR_ALLOCFAIL); 640 } 641 /*LINTED constant in conditional context*/ 642 while (1) { 643 int i; 644 645 /* read a chunk into our buffer */ 646 retstat = hndl->p_read(hndl->proc_p, addr + offset, buffer, 647 bufsz); 648 if (retstat != 0) { 649 650 /* 651 * if we get into trouble with a large read, try again 652 * with a single byte. Subsequent failiure is real ... 653 */ 654 if (bufsz > 1) { 655 bufsz = 1; 656 continue; 657 } 658 659 DBG((void) fprintf(stderr, 660 "_tnfctl_readstr_targ: target read failed: \n")); 661 free(ptr); 662 return (TNFCTL_ERR_INTERNAL); 663 } 664 /* copy the chracters into the return buffer */ 665 for (i = 0; i < bufsz; i++) { 666 char c = buffer[i]; 667 668 ptr[offset + i] = c; 669 if (c == '\0') { 670 /* hooray! we saw the end of the string */ 671 *outstr_pp = ptr; 672 return (TNFCTL_ERR_NONE); 673 } 674 } 675 676 /* bummer, need to grab another bufsz characters */ 677 offset += bufsz; 678 orig_ptr = ptr; 679 ptr = (char *) realloc(ptr, offset + bufsz); 680 if (!ptr) { 681 free(orig_ptr); 682 DBG((void) fprintf(stderr, 683 "_tnfctl_readstr_targ: realloc failed\n")); 684 return (TNFCTL_ERR_ALLOCFAIL); 685 } 686 } 687 688 #if defined(lint) 689 return (TNFCTL_ERR_NONE); 690 #endif 691 692 } 693