1/* Copyright (C) 2009, 2010, 2011 Free Software Foundation, Inc. 2 3 This file is part of GDB. 4 5 This program is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as published by 7 the Free Software Foundation; either version 3 of the License, or 8 (at your option) any later version. 9 10 This program is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with this program. If not, see <http://www.gnu.org/licenses/>. */ 17 18#include "server.h" 19#include "target.h" 20#include "lynx-low.h" 21 22#include <limits.h> 23#include <sys/ptrace.h> 24#include <sys/piddef.h> /* Provides PIDGET, TIDGET, BUILDPID, etc. */ 25#include <unistd.h> 26#include <sys/ioctl.h> 27#include <sys/types.h> 28#include <sys/wait.h> 29#include <signal.h> 30 31int using_threads = 1; 32 33/* Print a debug trace on standard output if debug_threads is set. */ 34 35static void 36lynx_debug (char *string, ...) 37{ 38 va_list args; 39 40 if (!debug_threads) 41 return; 42 43 va_start (args, string); 44 fprintf (stderr, "DEBUG(lynx): "); 45 vfprintf (stderr, string, args); 46 fprintf (stderr, "\n"); 47 va_end (args); 48} 49 50/* Build a ptid_t given a PID and a LynxOS TID. */ 51 52static ptid_t 53lynx_ptid_build (int pid, long tid) 54{ 55 /* brobecker/2010-06-21: It looks like the LWP field in ptids 56 should be distinct for each thread (see write_ptid where it 57 writes the thread ID from the LWP). So instead of storing 58 the LynxOS tid in the tid field of the ptid, we store it in 59 the lwp field. */ 60 return ptid_build (pid, tid, 0); 61} 62 63/* Return the process ID of the given PTID. 64 65 This function has little reason to exist, it's just a wrapper around 66 ptid_get_pid. But since we have a getter function for the lynxos 67 ptid, it feels cleaner to have a getter for the pid as well. */ 68 69static int 70lynx_ptid_get_pid (ptid_t ptid) 71{ 72 return ptid_get_pid (ptid); 73} 74 75/* Return the LynxOS tid of the given PTID. */ 76 77static long 78lynx_ptid_get_tid (ptid_t ptid) 79{ 80 /* See lynx_ptid_build: The LynxOS tid is stored inside the lwp field 81 of the ptid. */ 82 return ptid_get_lwp (ptid); 83} 84 85/* For a given PTID, return the associated PID as known by the LynxOS 86 ptrace layer. */ 87 88static int 89lynx_ptrace_pid_from_ptid (ptid_t ptid) 90{ 91 return BUILDPID (lynx_ptid_get_pid (ptid), lynx_ptid_get_tid (ptid)); 92} 93 94/* Return a string image of the ptrace REQUEST number. */ 95 96static char * 97ptrace_request_to_str (int request) 98{ 99 switch (request) 100 { 101 case PTRACE_TRACEME: 102 return "PTRACE_TRACEME"; 103 break; 104 case PTRACE_PEEKTEXT: 105 return "PTRACE_PEEKTEXT"; 106 break; 107 case PTRACE_PEEKDATA: 108 return "PTRACE_PEEKDATA"; 109 break; 110 case PTRACE_PEEKUSER: 111 return "PTRACE_PEEKUSER"; 112 break; 113 case PTRACE_POKETEXT: 114 return "PTRACE_POKETEXT"; 115 break; 116 case PTRACE_POKEDATA: 117 return "PTRACE_POKEDATA"; 118 break; 119 case PTRACE_POKEUSER: 120 return "PTRACE_POKEUSER"; 121 break; 122 case PTRACE_CONT: 123 return "PTRACE_CONT"; 124 break; 125 case PTRACE_KILL: 126 return "PTRACE_KILL"; 127 break; 128 case PTRACE_SINGLESTEP: 129 return "PTRACE_SINGLESTEP"; 130 break; 131 case PTRACE_ATTACH: 132 return "PTRACE_ATTACH"; 133 break; 134 case PTRACE_DETACH: 135 return "PTRACE_DETACH"; 136 break; 137 case PTRACE_GETREGS: 138 return "PTRACE_GETREGS"; 139 break; 140 case PTRACE_SETREGS: 141 return "PTRACE_SETREGS"; 142 break; 143 case PTRACE_GETFPREGS: 144 return "PTRACE_GETFPREGS"; 145 break; 146 case PTRACE_SETFPREGS: 147 return "PTRACE_SETFPREGS"; 148 break; 149 case PTRACE_READDATA: 150 return "PTRACE_READDATA"; 151 break; 152 case PTRACE_WRITEDATA: 153 return "PTRACE_WRITEDATA"; 154 break; 155 case PTRACE_READTEXT: 156 return "PTRACE_READTEXT"; 157 break; 158 case PTRACE_WRITETEXT: 159 return "PTRACE_WRITETEXT"; 160 break; 161 case PTRACE_GETFPAREGS: 162 return "PTRACE_GETFPAREGS"; 163 break; 164 case PTRACE_SETFPAREGS: 165 return "PTRACE_SETFPAREGS"; 166 break; 167 case PTRACE_GETWINDOW: 168 return "PTRACE_GETWINDOW"; 169 break; 170 case PTRACE_SETWINDOW: 171 return "PTRACE_SETWINDOW"; 172 break; 173 case PTRACE_SYSCALL: 174 return "PTRACE_SYSCALL"; 175 break; 176 case PTRACE_DUMPCORE: 177 return "PTRACE_DUMPCORE"; 178 break; 179 case PTRACE_SETWRBKPT: 180 return "PTRACE_SETWRBKPT"; 181 break; 182 case PTRACE_SETACBKPT: 183 return "PTRACE_SETACBKPT"; 184 break; 185 case PTRACE_CLRBKPT: 186 return "PTRACE_CLRBKPT"; 187 break; 188 case PTRACE_GET_UCODE: 189 return "PTRACE_GET_UCODE"; 190 break; 191#ifdef PT_READ_GPR 192 case PT_READ_GPR: 193 return "PT_READ_GPR"; 194 break; 195#endif 196#ifdef PT_WRITE_GPR 197 case PT_WRITE_GPR: 198 return "PT_WRITE_GPR"; 199 break; 200#endif 201#ifdef PT_READ_FPR 202 case PT_READ_FPR: 203 return "PT_READ_FPR"; 204 break; 205#endif 206#ifdef PT_WRITE_FPR 207 case PT_WRITE_FPR: 208 return "PT_WRITE_FPR"; 209 break; 210#endif 211#ifdef PT_READ_VPR 212 case PT_READ_VPR: 213 return "PT_READ_VPR"; 214 break; 215#endif 216#ifdef PT_WRITE_VPR 217 case PT_WRITE_VPR: 218 return "PT_WRITE_VPR"; 219 break; 220#endif 221#ifdef PTRACE_PEEKUSP 222 case PTRACE_PEEKUSP: 223 return "PTRACE_PEEKUSP"; 224 break; 225#endif 226#ifdef PTRACE_POKEUSP 227 case PTRACE_POKEUSP: 228 return "PTRACE_POKEUSP"; 229 break; 230#endif 231 case PTRACE_PEEKTHREAD: 232 return "PTRACE_PEEKTHREAD"; 233 break; 234 case PTRACE_THREADUSER: 235 return "PTRACE_THREADUSER"; 236 break; 237 case PTRACE_FPREAD: 238 return "PTRACE_FPREAD"; 239 break; 240 case PTRACE_FPWRITE: 241 return "PTRACE_FPWRITE"; 242 break; 243 case PTRACE_SETSIG: 244 return "PTRACE_SETSIG"; 245 break; 246 case PTRACE_CONT_ONE: 247 return "PTRACE_CONT_ONE"; 248 break; 249 case PTRACE_KILL_ONE: 250 return "PTRACE_KILL_ONE"; 251 break; 252 case PTRACE_SINGLESTEP_ONE: 253 return "PTRACE_SINGLESTEP_ONE"; 254 break; 255 case PTRACE_GETLOADINFO: 256 return "PTRACE_GETLOADINFO"; 257 break; 258 case PTRACE_GETTHREADLIST: 259 return "PTRACE_GETTHREADLIST"; 260 break; 261 } 262 return "<unknown-request>"; 263} 264 265/* A wrapper around ptrace that allows us to print debug traces of 266 ptrace calls if debug traces are activated. */ 267 268static int 269lynx_ptrace (int request, ptid_t ptid, int addr, int data, int addr2) 270{ 271 int result; 272 const int pid = lynx_ptrace_pid_from_ptid (ptid); 273 int saved_errno; 274 275 if (debug_threads) 276 fprintf (stderr, "PTRACE (%s, pid=%d(pid=%d, tid=%d), addr=0x%x, " 277 "data=0x%x, addr2=0x%x)", 278 ptrace_request_to_str (request), pid, PIDGET (pid), TIDGET (pid), 279 addr, data, addr2); 280 result = ptrace (request, pid, addr, data, addr2); 281 saved_errno = errno; 282 if (debug_threads) 283 fprintf (stderr, " -> %d (=0x%x)\n", result, result); 284 285 errno = saved_errno; 286 return result; 287} 288 289/* Implement the create_inferior method of the target_ops vector. */ 290 291static int 292lynx_create_inferior (char *program, char **allargs) 293{ 294 struct process_info *new_process; 295 int pid; 296 297 lynx_debug ("lynx_create_inferior ()"); 298 299 pid = fork (); 300 if (pid < 0) 301 perror_with_name ("fork"); 302 303 if (pid == 0) 304 { 305 int pgrp; 306 307 /* Switch child to its own process group so that signals won't 308 directly affect gdbserver. */ 309 pgrp = getpid(); 310 setpgid (0, pgrp); 311 ioctl (0, TIOCSPGRP, &pgrp); 312 lynx_ptrace (PTRACE_TRACEME, null_ptid, 0, 0, 0); 313 execv (program, allargs); 314 fprintf (stderr, "Cannot exec %s: %s.\n", program, strerror (errno)); 315 fflush (stderr); 316 _exit (0177); 317 } 318 319 new_process = add_process (pid, 0); 320 /* Do not add the process thread just yet, as we do not know its tid. 321 We will add it later, during the wait for the STOP event corresponding 322 to the lynx_ptrace (PTRACE_TRACEME) call above. */ 323 return pid; 324} 325 326/* Implement the attach target_ops method. */ 327 328static int 329lynx_attach (unsigned long pid) 330{ 331 struct process_info *new_process; 332 ptid_t ptid = lynx_ptid_build (pid, 0); 333 334 if (lynx_ptrace (PTRACE_ATTACH, ptid, 0, 0, 0) != 0) 335 error ("Cannot attach to process %lu: %s (%d)\n", pid, 336 strerror (errno), errno); 337 338 new_process = add_process (pid, 1); 339 add_thread (ptid, NULL); 340 341 return 0; 342} 343 344/* Implement the resume target_ops method. */ 345 346static void 347lynx_resume (struct thread_resume *resume_info, size_t n) 348{ 349 ptid_t inferior_ptid = thread_to_gdb_id (current_inferior); 350 /* FIXME: Assume for now that n == 1. */ 351 const int request = (resume_info[0].kind == resume_step 352 ? PTRACE_SINGLESTEP : PTRACE_CONT); 353 const int signal = resume_info[0].sig; 354 int ret; 355 356 regcache_invalidate (); 357 ret = lynx_ptrace (request, inferior_ptid, 1, signal, 0); 358} 359 360/* Resume the execution of the given PTID. */ 361 362static void 363lynx_continue (ptid_t ptid) 364{ 365 struct thread_resume resume_info; 366 367 resume_info.thread = ptid; 368 resume_info.kind = resume_continue; 369 resume_info.sig = 0; 370 371 lynx_resume (&resume_info, 1); 372} 373 374/* Remove all inferiors and associated threads. */ 375 376static void 377lynx_clear_inferiors (void) 378{ 379 /* We do not use private data, so nothing much to do except calling 380 clear_inferiors. */ 381 clear_inferiors (); 382} 383 384/* A wrapper around waitpid that handles the various idiosyncrasies 385 of LynxOS' waitpid. */ 386 387static int 388lynx_waitpid (int pid, int *stat_loc) 389{ 390 int ret = 0; 391 392 while (1) 393 { 394 ret = waitpid (pid, stat_loc, WNOHANG); 395 if (ret < 0) 396 { 397 /* An ECHILD error is not indicative of a real problem. 398 It happens for instance while waiting for the inferior 399 to stop after attaching to it. */ 400 if (errno != ECHILD) 401 perror_with_name ("waitpid (WNOHANG)"); 402 } 403 if (ret > 0) 404 break; 405 /* No event with WNOHANG. See if there is one with WUNTRACED. */ 406 ret = waitpid (pid, stat_loc, WNOHANG | WUNTRACED); 407 if (ret < 0) 408 { 409 /* An ECHILD error is not indicative of a real problem. 410 It happens for instance while waiting for the inferior 411 to stop after attaching to it. */ 412 if (errno != ECHILD) 413 perror_with_name ("waitpid (WNOHANG|WUNTRACED)"); 414 } 415 if (ret > 0) 416 break; 417 usleep (1000); 418 } 419 return ret; 420} 421 422/* Implement the wait target_ops method. */ 423 424static ptid_t 425lynx_wait_1 (ptid_t ptid, struct target_waitstatus *status, int options) 426{ 427 int pid; 428 int ret; 429 int wstat; 430 ptid_t new_ptid; 431 432 if (ptid_equal (ptid, minus_one_ptid)) 433 pid = lynx_ptid_get_pid (thread_to_gdb_id (current_inferior)); 434 else 435 pid = BUILDPID (lynx_ptid_get_pid (ptid), lynx_ptid_get_tid (ptid)); 436 437retry: 438 439 ret = lynx_waitpid (pid, &wstat); 440 new_ptid = lynx_ptid_build (ret, ((union wait *) &wstat)->w_tid); 441 442 /* If this is a new thread, then add it now. The reason why we do 443 this here instead of when handling new-thread events is because 444 we need to add the thread associated to the "main" thread - even 445 for non-threaded applications where the new-thread events are not 446 generated. */ 447 if (!find_thread_ptid (new_ptid)) 448 add_thread (new_ptid, NULL); 449 450 if (WIFSTOPPED (wstat)) 451 { 452 status->kind = TARGET_WAITKIND_STOPPED; 453 status->value.integer = target_signal_from_host (WSTOPSIG (wstat)); 454 lynx_debug ("process stopped with signal: %d", 455 status->value.integer); 456 } 457 else if (WIFEXITED (wstat)) 458 { 459 status->kind = TARGET_WAITKIND_EXITED; 460 status->value.integer = WEXITSTATUS (wstat); 461 lynx_debug ("process exited with code: %d", status->value.integer); 462 } 463 else if (WIFSIGNALED (wstat)) 464 { 465 status->kind = TARGET_WAITKIND_SIGNALLED; 466 status->value.integer = target_signal_from_host (WTERMSIG (wstat)); 467 lynx_debug ("process terminated with code: %d", 468 status->value.integer); 469 } 470 else 471 { 472 /* Not sure what happened if we get here, or whether we can 473 in fact get here. But if we do, handle the event the best 474 we can. */ 475 status->kind = TARGET_WAITKIND_STOPPED; 476 status->value.integer = target_signal_from_host (0); 477 lynx_debug ("unknown event ????"); 478 } 479 480 /* SIGTRAP events are generated for situations other than single-step/ 481 breakpoint events (Eg. new-thread events). Handle those other types 482 of events, and resume the execution if necessary. */ 483 if (status->kind == TARGET_WAITKIND_STOPPED 484 && status->value.integer == TARGET_SIGNAL_TRAP) 485 { 486 const int realsig = lynx_ptrace (PTRACE_GETTRACESIG, new_ptid, 0, 0, 0); 487 488 lynx_debug ("(realsig = %d)", realsig); 489 switch (realsig) 490 { 491 case SIGNEWTHREAD: 492 /* We just added the new thread above. No need to do anything 493 further. Just resume the execution again. */ 494 lynx_continue (ptid); 495 goto retry; 496 497 case SIGTHREADEXIT: 498 remove_thread (find_thread_ptid (new_ptid)); 499 lynx_continue (ptid); 500 goto retry; 501 } 502 } 503 504 return new_ptid; 505} 506 507/* A wrapper around lynx_wait_1 that also prints debug traces when 508 such debug traces have been activated. */ 509 510static ptid_t 511lynx_wait (ptid_t ptid, struct target_waitstatus *status, int options) 512{ 513 ptid_t new_ptid; 514 515 lynx_debug ("lynx_wait (pid = %d, tid = %ld)", 516 lynx_ptid_get_pid (ptid), lynx_ptid_get_tid (ptid)); 517 new_ptid = lynx_wait_1 (ptid, status, options); 518 lynx_debug (" -> (pid=%d, tid=%ld, status->kind = %d)", 519 lynx_ptid_get_pid (new_ptid), lynx_ptid_get_tid (new_ptid), 520 status->kind); 521 return new_ptid; 522} 523 524/* Implement the kill target_ops method. */ 525 526static int 527lynx_kill (int pid) 528{ 529 ptid_t ptid = lynx_ptid_build (pid, 0); 530 struct target_waitstatus status; 531 struct process_info *process; 532 533 process = find_process_pid (pid); 534 if (process == NULL) 535 return -1; 536 537 lynx_ptrace (PTRACE_KILL, ptid, 0, 0, 0); 538 lynx_wait (ptid, &status, 0); 539 the_target->mourn (process); 540 return 0; 541} 542 543/* Implement the detach target_ops method. */ 544 545static int 546lynx_detach (int pid) 547{ 548 ptid_t ptid = lynx_ptid_build (pid, 0); 549 struct process_info *process; 550 551 process = find_process_pid (pid); 552 if (process == NULL) 553 return -1; 554 555 lynx_ptrace (PTRACE_DETACH, ptid, 0, 0, 0); 556 the_target->mourn (process); 557 return 0; 558} 559 560/* Implement the mourn target_ops method. */ 561 562static void 563lynx_mourn (struct process_info *proc) 564{ 565 lynx_clear_inferiors (); 566} 567 568/* Implement the join target_ops method. */ 569 570static void 571lynx_join (int pid) 572{ 573 /* The PTRACE_DETACH is sufficient to detach from the process. 574 So no need to do anything extra. */ 575} 576 577/* Implement the thread_alive target_ops method. */ 578 579static int 580lynx_thread_alive (ptid_t ptid) 581{ 582 /* The list of threads is updated at the end of each wait, so it 583 should be up to date. No need to re-fetch it. */ 584 return (find_thread_ptid (ptid) != NULL); 585} 586 587/* Implement the fetch_registers target_ops method. */ 588 589static void 590lynx_fetch_registers (struct regcache *regcache, int regno) 591{ 592 struct lynx_regset_info *regset = lynx_target_regsets; 593 ptid_t inferior_ptid = thread_to_gdb_id (current_inferior); 594 595 lynx_debug ("lynx_fetch_registers (regno = %d)", regno); 596 597 while (regset->size >= 0) 598 { 599 char *buf; 600 int res; 601 602 buf = xmalloc (regset->size); 603 res = lynx_ptrace (regset->get_request, inferior_ptid, (int) buf, 0, 0); 604 if (res < 0) 605 perror ("ptrace"); 606 regset->store_function (regcache, buf); 607 free (buf); 608 regset++; 609 } 610} 611 612/* Implement the store_registers target_ops method. */ 613 614static void 615lynx_store_registers (struct regcache *regcache, int regno) 616{ 617 struct lynx_regset_info *regset = lynx_target_regsets; 618 ptid_t inferior_ptid = thread_to_gdb_id (current_inferior); 619 620 lynx_debug ("lynx_store_registers (regno = %d)", regno); 621 622 while (regset->size >= 0) 623 { 624 char *buf; 625 int res; 626 627 buf = xmalloc (regset->size); 628 res = lynx_ptrace (regset->get_request, inferior_ptid, (int) buf, 0, 0); 629 if (res == 0) 630 { 631 /* Then overlay our cached registers on that. */ 632 regset->fill_function (regcache, buf); 633 /* Only now do we write the register set. */ 634 res = lynx_ptrace (regset->set_request, inferior_ptid, (int) buf, 635 0, 0); 636 } 637 if (res < 0) 638 perror ("ptrace"); 639 free (buf); 640 regset++; 641 } 642} 643 644/* Implement the read_memory target_ops method. */ 645 646static int 647lynx_read_memory (CORE_ADDR memaddr, unsigned char *myaddr, int len) 648{ 649 /* On LynxOS, memory reads needs to be performed in chunks the size 650 of int types, and they should also be aligned accordingly. */ 651 int buf; 652 const int xfer_size = sizeof (buf); 653 CORE_ADDR addr = memaddr & -(CORE_ADDR) xfer_size; 654 ptid_t inferior_ptid = thread_to_gdb_id (current_inferior); 655 656 while (addr < memaddr + len) 657 { 658 int skip = 0; 659 int truncate = 0; 660 661 errno = 0; 662 if (addr < memaddr) 663 skip = memaddr - addr; 664 if (addr + xfer_size > memaddr + len) 665 truncate = addr + xfer_size - memaddr - len; 666 buf = lynx_ptrace (PTRACE_PEEKTEXT, inferior_ptid, addr, 0, 0); 667 if (errno) 668 return errno; 669 memcpy (myaddr + (addr - memaddr) + skip, (gdb_byte *) &buf + skip, 670 xfer_size - skip - truncate); 671 addr += xfer_size; 672 } 673 674 return 0; 675} 676 677/* Implement the write_memory target_ops method. */ 678 679static int 680lynx_write_memory (CORE_ADDR memaddr, const unsigned char *myaddr, int len) 681{ 682 /* On LynxOS, memory writes needs to be performed in chunks the size 683 of int types, and they should also be aligned accordingly. */ 684 int buf; 685 const int xfer_size = sizeof (buf); 686 CORE_ADDR addr = memaddr & -(CORE_ADDR) xfer_size; 687 ptid_t inferior_ptid = thread_to_gdb_id (current_inferior); 688 689 while (addr < memaddr + len) 690 { 691 int skip = 0; 692 int truncate = 0; 693 694 if (addr < memaddr) 695 skip = memaddr - addr; 696 if (addr + xfer_size > memaddr + len) 697 truncate = addr + xfer_size - memaddr - len; 698 if (skip > 0 || truncate > 0) 699 /* We need to read the memory at this address in order to preserve 700 the data that we are not overwriting. */ 701 lynx_read_memory (addr, (unsigned char *) &buf, xfer_size); 702 if (errno) 703 return errno; 704 memcpy ((gdb_byte *) &buf + skip, myaddr + (addr - memaddr) + skip, 705 xfer_size - skip - truncate); 706 errno = 0; 707 lynx_ptrace (PTRACE_POKETEXT, inferior_ptid, addr, buf, 0); 708 if (errno) 709 return errno; 710 addr += xfer_size; 711 } 712 713 return 0; 714} 715 716/* Implement the kill_request target_ops method. */ 717 718static void 719lynx_request_interrupt (void) 720{ 721 ptid_t inferior_ptid = thread_to_gdb_id (current_inferior); 722 723 kill (lynx_ptid_get_pid (inferior_ptid), SIGINT); 724} 725 726/* The LynxOS target_ops vector. */ 727 728static struct target_ops lynx_target_ops = { 729 lynx_create_inferior, 730 lynx_attach, 731 lynx_kill, 732 lynx_detach, 733 lynx_mourn, 734 lynx_join, 735 lynx_thread_alive, 736 lynx_resume, 737 lynx_wait, 738 lynx_fetch_registers, 739 lynx_store_registers, 740 NULL, /* prepare_to_access_memory */ 741 NULL, /* done_accessing_memory */ 742 lynx_read_memory, 743 lynx_write_memory, 744 NULL, /* look_up_symbols */ 745 lynx_request_interrupt, 746 NULL, /* read_auxv */ 747 NULL, /* insert_point */ 748 NULL, /* remove_point */ 749 NULL, /* stopped_by_watchpoint */ 750 NULL, /* stopped_data_address */ 751 NULL, /* read_offsets */ 752 NULL, /* get_tls_address */ 753 NULL, /* qxfer_spu */ 754 NULL, /* hostio_last_error */ 755 NULL, /* qxfer_osdata */ 756 NULL, /* qxfer_siginfo */ 757 NULL, /* supports_non_stop */ 758 NULL, /* async */ 759 NULL, /* start_non_stop */ 760 NULL, /* supports_multi_process */ 761 NULL, /* handle_monitor_command */ 762}; 763 764void 765initialize_low (void) 766{ 767 set_target_ops (&lynx_target_ops); 768 the_low_target.arch_setup (); 769} 770 771