updated to 3 may
[deliverable/binutils-gdb.git] / gdb / remote-vx.c
CommitLineData
dd3b648e 1/* Memory-access and commands for remote VxWorks processes, for GDB.
e17960fb 2 Copyright 1990, 1991, 1992 Free Software Foundation, Inc.
dd3b648e
RP
3 Contributed by Wind River Systems and Cygnus Support.
4
5This file is part of GDB.
6
99a7de40 7This program is free software; you can redistribute it and/or modify
dd3b648e 8it under the terms of the GNU General Public License as published by
99a7de40
JG
9the Free Software Foundation; either version 2 of the License, or
10(at your option) any later version.
dd3b648e 11
99a7de40 12This program is distributed in the hope that it will be useful,
dd3b648e
RP
13but WITHOUT ANY WARRANTY; without even the implied warranty of
14MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15GNU General Public License for more details.
16
17You should have received a copy of the GNU General Public License
99a7de40
JG
18along with this program; if not, write to the Free Software
19Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
dd3b648e
RP
20
21#include "defs.h"
dd3b648e
RP
22#include "frame.h"
23#include "inferior.h"
24#include "wait.h"
25#include "target.h"
26#include "gdbcore.h"
27#include "command.h"
28#include "symtab.h"
29#include "symfile.h" /* for struct complaint */
30
dd3b648e
RP
31#include <string.h>
32#include <errno.h>
33#include <signal.h>
34#include <fcntl.h>
35#include <sys/types.h>
36#include <sys/time.h>
37#include <sys/socket.h>
38#define free bogon_free /* Sun claims "int free()" not void */
39#include <rpc/rpc.h>
40#undef free
41#include <sys/time.h> /* UTek's <rpc/rpc.h> doesn't #incl this */
42#include <netdb.h>
43#include <ptrace.h>
44#include "xdr_ptrace.h"
45#include "xdr_ld.h"
46#include "xdr_rdb.h"
47#include "dbgRpcLib.h"
48
dd3b648e 49#include <symtab.h>
1ab3bf1b 50
dd3b648e 51extern void symbol_file_command ();
dd3b648e 52extern int stop_soon_quietly; /* for wait_for_inferior */
8616205a
SG
53extern void host_convert_to_virtual ();
54extern void host_convert_from_virtual ();
dd3b648e
RP
55
56static int net_ptrace_clnt_call (); /* Forward decl */
57static enum clnt_stat net_clnt_call (); /* Forward decl */
58extern struct target_ops vx_ops, vx_run_ops; /* Forward declaration */
59
60/* Saved name of target host and called function for "info files".
61 Both malloc'd. */
62
63static char *vx_host;
64static char *vx_running; /* Called function */
65
66/* Nonzero means target that is being debugged remotely has a floating
67 point processor. */
68
69static int target_has_fp;
70
71/* Default error message when the network is forking up. */
72
73static const char rpcerr[] = "network target debugging: rpc error";
74
75CLIENT *pClient; /* client used in net debugging */
76static int ptraceSock = RPC_ANYSOCK;
77
78enum clnt_stat net_clnt_call();
79static void parse_args ();
80
81static struct timeval rpcTimeout = { 10, 0 };
82
83static char *skip_white_space ();
84static char *find_white_space ();
85
86/* Tell the VxWorks target system to download a file.
87 The load addresses of the text, data, and bss segments are
88 stored in pTextAddr, pDataAddr, and *pBssAddr (respectively).
89 Returns 0 for success, -1 for failure. */
90
91static int
92net_load (filename, pTextAddr, pDataAddr, pBssAddr)
93 char *filename;
94 CORE_ADDR *pTextAddr;
95 CORE_ADDR *pDataAddr;
96 CORE_ADDR *pBssAddr;
97 {
98 enum clnt_stat status;
99 struct ldfile ldstruct;
100 struct timeval load_timeout;
101
102 bzero ((char *) &ldstruct, sizeof (ldstruct));
103
104 /* We invoke clnt_call () here directly, instead of through
105 net_clnt_call (), because we need to set a large timeout value.
106 The load on the target side can take quite a while, easily
107 more than 10 seconds. The user can kill this call by typing
108 CTRL-C if there really is a problem with the load.
109
110 Do not change the tv_sec value without checking -- select() imposes
111 a limit of 10**8 on it for no good reason that I can see... */
112
113 load_timeout.tv_sec = 99999999; /* A large number, effectively inf. */
114 load_timeout.tv_usec = 0;
115
116 status = clnt_call (pClient, VX_LOAD, xdr_wrapstring, &filename, xdr_ldfile,
117 &ldstruct, load_timeout);
118
119 if (status == RPC_SUCCESS)
120 {
121 if (*ldstruct.name == NULL) /* load failed on VxWorks side */
122 return -1;
123 *pTextAddr = ldstruct.txt_addr;
124 *pDataAddr = ldstruct.data_addr;
125 *pBssAddr = ldstruct.bss_addr;
126 return 0;
127 }
128 else
129 return -1;
130 }
131
132/* returns 0 if successful, errno if RPC failed or VxWorks complains. */
133
134static int
135net_break (addr, procnum)
136 int addr;
137 u_long procnum;
138 {
139 enum clnt_stat status;
140 int break_status;
141 Rptrace ptrace_in; /* XXX This is stupid. It doesn't need to be a ptrace
142 structure. How about something smaller? */
143
144 bzero ((char *) &ptrace_in, sizeof (ptrace_in));
145 break_status = 0;
146
147 ptrace_in.addr = addr;
148 ptrace_in.pid = inferior_pid;
149
150 status = net_clnt_call (procnum, xdr_rptrace, &ptrace_in, xdr_int,
151 &break_status);
152
153 if (status != RPC_SUCCESS)
154 return errno;
155
156 if (break_status == -1)
157 return ENOMEM;
158 return break_status; /* probably (FIXME) zero */
159 }
160
161/* returns 0 if successful, errno otherwise */
162
8616205a 163static int
dd3b648e
RP
164vx_insert_breakpoint (addr)
165 int addr;
166 {
167 return net_break (addr, VX_BREAK_ADD);
168 }
169
170/* returns 0 if successful, errno otherwise */
171
8616205a 172static int
dd3b648e
RP
173vx_remove_breakpoint (addr)
174 int addr;
175 {
176 return net_break (addr, VX_BREAK_DELETE);
177 }
178
dd3b648e
RP
179/* Start an inferior process and sets inferior_pid to its pid.
180 EXEC_FILE is the file to run.
181 ALLARGS is a string containing the arguments to the program.
182 ENV is the environment vector to pass.
183 Returns process id. Errors reported with error().
184 On VxWorks, we ignore exec_file. */
185
8616205a 186static void
dd3b648e
RP
187vx_create_inferior (exec_file, args, env)
188 char *exec_file;
189 char *args;
190 char **env;
191{
192 enum clnt_stat status;
193 arg_array passArgs;
194 TASK_START taskStart;
195
196 bzero ((char *) &passArgs, sizeof (passArgs));
197 bzero ((char *) &taskStart, sizeof (taskStart));
198
199 /* parse arguments, put them in passArgs */
200
201 parse_args (args, &passArgs);
202
203 if (passArgs.arg_array_len == 0)
204 error ("You must specify a function name to run, and arguments if any");
205
206 status = net_clnt_call (PROCESS_START, xdr_arg_array, &passArgs,
207 xdr_TASK_START, &taskStart);
208
209 if ((status != RPC_SUCCESS) || (taskStart.status == -1))
210 error ("Can't create process on remote target machine");
211
212 /* Save the name of the running function */
213 vx_running = savestring (passArgs.arg_array_val[0],
214 strlen (passArgs.arg_array_val[0]));
215
216#ifdef CREATE_INFERIOR_HOOK
217 CREATE_INFERIOR_HOOK (pid);
218#endif
219
220 push_target (&vx_run_ops);
221 inferior_pid = taskStart.pid;
222
dd3b648e
RP
223 /* We will get a trace trap after one instruction.
224 Insert breakpoints and continue. */
225
226 init_wait_for_inferior ();
227
228 /* Set up the "saved terminal modes" of the inferior
229 based on what modes we are starting it with. */
230 target_terminal_init ();
231
232 /* Install inferior's terminal modes. */
233 target_terminal_inferior ();
234
dd3b648e
RP
235 stop_soon_quietly = 1;
236 wait_for_inferior (); /* Get the task spawn event */
237 stop_soon_quietly = 0;
238
239 /* insert_step_breakpoint (); FIXME, do we need this? */
240 proceed(-1, -1, 0);
241}
242
243/* Fill ARGSTRUCT in argc/argv form with the arguments from the
244 argument string ARGSTRING. */
245
246static void
247parse_args (arg_string, arg_struct)
248 register char *arg_string;
249 arg_array *arg_struct;
250{
251 register int arg_count = 0; /* number of arguments */
252 register int arg_index = 0;
253 register char *p0;
254
255 bzero ((char *) arg_struct, sizeof (arg_array));
256
257 /* first count how many arguments there are */
258
259 p0 = arg_string;
260 while (*p0 != '\0')
261 {
262 if (*(p0 = skip_white_space (p0)) == '\0')
263 break;
264 p0 = find_white_space (p0);
265 arg_count++;
266 }
267
268 arg_struct->arg_array_len = arg_count;
269 arg_struct->arg_array_val = (char **) xmalloc ((arg_count + 1)
270 * sizeof (char *));
271
272 /* now copy argument strings into arg_struct. */
273
274 while (*(arg_string = skip_white_space (arg_string)))
275 {
276 p0 = find_white_space (arg_string);
277 arg_struct->arg_array_val[arg_index++] = savestring (arg_string,
278 p0 - arg_string);
279 arg_string = p0;
280 }
281
282 arg_struct->arg_array_val[arg_count] = NULL;
283}
284
285/* Advance a string pointer across whitespace and return a pointer
286 to the first non-white character. */
287
288static char *
289skip_white_space (p)
290 register char *p;
291{
292 while (*p == ' ' || *p == '\t')
293 p++;
294 return p;
295}
296
297/* Search for the first unquoted whitespace character in a string.
298 Returns a pointer to the character, or to the null terminator
299 if no whitespace is found. */
300
301static char *
302find_white_space (p)
303 register char *p;
304{
305 register int c;
306
307 while ((c = *p) != ' ' && c != '\t' && c)
308 {
309 if (c == '\'' || c == '"')
310 {
311 while (*++p != c && *p)
312 {
313 if (*p == '\\')
314 p++;
315 }
316 if (!*p)
317 break;
318 }
319 p++;
320 }
321 return p;
322}
323
324/* Poll the VxWorks target system for an event related
325 to the debugged task.
326 Returns -1 if remote wait failed, task status otherwise. */
327
8616205a 328static int
dd3b648e
RP
329net_wait (pEvent)
330 RDB_EVENT *pEvent;
331{
332 int pid;
333 enum clnt_stat status;
334
335 bzero ((char *) pEvent, sizeof (RDB_EVENT));
336
337 pid = inferior_pid;
338 status = net_clnt_call (PROCESS_WAIT, xdr_int, &pid, xdr_RDB_EVENT, pEvent);
339
340 return (status == RPC_SUCCESS)? pEvent->status: -1;
341}
342
343/* Suspend the remote task.
344 Returns -1 if suspend fails on target system, 0 otherwise. */
345
8616205a 346static int
dd3b648e
RP
347net_quit ()
348{
349 int pid;
350 int quit_status;
351 enum clnt_stat status;
352
353 quit_status = 0;
354
355 /* don't let rdbTask suspend itself by passing a pid of 0 */
356
357 if ((pid = inferior_pid) == 0)
358 return -1;
359
360 status = net_clnt_call (VX_TASK_SUSPEND, xdr_int, &pid, xdr_int,
361 &quit_status);
362
363 return (status == RPC_SUCCESS)? quit_status: -1;
364}
365
366/* Read a register or registers from the remote system. */
367
8616205a 368static void
dd3b648e
RP
369vx_read_register (regno)
370 int regno;
371{
372 int status;
373 Rptrace ptrace_in;
374 Ptrace_return ptrace_out;
8616205a
SG
375 C_bytes in_data;
376 C_bytes out_data;
dd3b648e
RP
377 extern char registers[];
378
379 bzero ((char *) &ptrace_in, sizeof (ptrace_in));
380 bzero ((char *) &ptrace_out, sizeof (ptrace_out));
381
382 /* FIXME, eventually only get the ones we need. */
383 registers_fetched ();
384
385 ptrace_in.pid = inferior_pid;
8616205a
SG
386 ptrace_out.info.more_data = (caddr_t) &out_data;
387 out_data.len = 18 * REGISTER_RAW_SIZE (0); /* FIXME 68k hack */
388 out_data.bytes = (caddr_t) registers;
389
dd3b648e
RP
390 status = net_ptrace_clnt_call (PTRACE_GETREGS, &ptrace_in, &ptrace_out);
391 if (status)
392 error (rpcerr);
393 if (ptrace_out.status == -1)
394 {
395 errno = ptrace_out.errno;
8616205a 396 perror_with_name ("net_ptrace_clnt_call(PTRACE_GETREGS)");
dd3b648e
RP
397 }
398
399#ifdef I80960
400
401 bcopy ((char *) inferior_registers.r_lreg,
402 &registers[REGISTER_BYTE (R0_REGNUM)], 16 * sizeof (int));
403 bcopy ((char *) inferior_registers.r_greg,
404 &registers[REGISTER_BYTE (G0_REGNUM)], 16 * sizeof (int));
405
406 /* Don't assume that a location in registers[] is properly aligned. */
407
408 bcopy ((char *) &inferior_registers.r_pcw,
409 &registers[REGISTER_BYTE (PCW_REGNUM)], sizeof (int));
410 bcopy ((char *) &inferior_registers.r_acw,
411 &registers[REGISTER_BYTE (ACW_REGNUM)], sizeof (int));
412 bcopy ((char *) &inferior_registers.r_lreg[2], /* r2 (RIP) -> IP */
413 &registers[REGISTER_BYTE (IP_REGNUM)], sizeof (int));
414 bcopy ((char *) &inferior_registers.r_tcw,
415 &registers[REGISTER_BYTE (TCW_REGNUM)], sizeof (int));
416
417 /* If the target has floating point registers, fetch them.
418 Otherwise, zero the floating point register values in
419 registers[] for good measure, even though we might not
420 need to. */
421
422 if (target_has_fp)
423 {
424 ptrace_in.pid = inferior_pid;
425 ptrace_out.info.more_data = (caddr_t) &inferior_fp_registers;
426 status = net_ptrace_clnt_call (PTRACE_GETFPREGS, &ptrace_in, &ptrace_out);
427 if (status)
428 error (rpcerr);
429 if (ptrace_out.status == -1)
430 {
431 errno = ptrace_out.errno;
8616205a 432 perror_with_name ("net_ptrace_clnt_call(PTRACE_GETFPREGS)");
dd3b648e
RP
433 }
434
435 bcopy (&inferior_fp_registers, &registers[REGISTER_BYTE (FP0_REGNUM)],
436 REGISTER_RAW_SIZE (FP0_REGNUM) * 4);
437 }
438 else
439 {
440 bzero ((char *) &registers[REGISTER_BYTE (FP0_REGNUM)],
441 REGISTER_RAW_SIZE (FP0_REGNUM) * 4);
442 }
443
444#else /* not 960, thus must be 68000: FIXME! */
445
dd3b648e
RP
446 if (target_has_fp)
447 {
448 ptrace_in.pid = inferior_pid;
8616205a
SG
449 ptrace_out.info.more_data = (caddr_t) &out_data;
450 out_data.len = 8 * REGISTER_RAW_SIZE (FP0_REGNUM) /* FIXME */
451 + (3 * sizeof (REGISTER_TYPE));
452 out_data.bytes = (caddr_t) &registers[REGISTER_BYTE (FP0_REGNUM)];
453
dd3b648e
RP
454 status = net_ptrace_clnt_call (PTRACE_GETFPREGS, &ptrace_in, &ptrace_out);
455 if (status)
456 error (rpcerr);
457 if (ptrace_out.status == -1)
458 {
459 errno = ptrace_out.errno;
8616205a 460 perror_with_name ("net_ptrace_clnt_call(PTRACE_GETFPREGS)");
dd3b648e 461 }
dd3b648e
RP
462 }
463 else
464 {
465 bzero (&registers[REGISTER_BYTE (FP0_REGNUM)],
8616205a 466 8 * REGISTER_RAW_SIZE (FP0_REGNUM));
dd3b648e 467 bzero (&registers[REGISTER_BYTE (FPC_REGNUM)],
8616205a 468 3 * sizeof (REGISTER_TYPE));
dd3b648e
RP
469 }
470#endif /* various architectures */
dd3b648e
RP
471}
472
473/* Prepare to store registers. Since we will store all of them,
474 read out their current values now. */
475
8616205a 476static void
dd3b648e
RP
477vx_prepare_to_store ()
478{
479 vx_read_register (-1);
480}
481
482
483/* Store our register values back into the inferior.
484 If REGNO is -1, do this for all registers.
485 Otherwise, REGNO specifies which register (so we can save time). */
486 /* FIXME, look at REGNO to save time here */
487
8616205a 488static void
dd3b648e
RP
489vx_write_register (regno)
490 int regno;
491{
8616205a
SG
492 C_bytes in_data;
493 C_bytes out_data;
dd3b648e
RP
494 extern char registers[];
495 int status;
496 Rptrace ptrace_in;
497 Ptrace_return ptrace_out;
498
499 bzero ((char *) &ptrace_in, sizeof (ptrace_in));
500 bzero ((char *) &ptrace_out, sizeof (ptrace_out));
501
8616205a
SG
502 ptrace_in.pid = inferior_pid;
503 ptrace_in.info.ttype = DATA;
504 ptrace_in.info.more_data = (caddr_t) &in_data;
505
506 in_data.bytes = registers;
507
dd3b648e
RP
508#ifdef I80960
509
8616205a 510 /* FIXME */
dd3b648e
RP
511 bcopy (&registers[REGISTER_BYTE (R0_REGNUM)],
512 (char *) inferior_registers.r_lreg, 16 * sizeof (int));
513 bcopy (&registers[REGISTER_BYTE (G0_REGNUM)],
514 (char *) inferior_registers.r_greg, 16 * sizeof (int));
515
516 /* Don't assume that a location in registers[] is properly aligned. */
517
518 bcopy (&registers[REGISTER_BYTE (PCW_REGNUM)],
519 (char *) &inferior_registers.r_pcw, sizeof (int));
520 bcopy (&registers[REGISTER_BYTE (ACW_REGNUM)],
521 (char *) &inferior_registers.r_acw, sizeof (int));
522 bcopy (&registers[REGISTER_BYTE (TCW_REGNUM)],
523 (char *) &inferior_registers.r_tcw, sizeof (int));
524
525#else /* not 960 -- assume 68k -- FIXME */
526
8616205a 527 in_data.len = 18 * sizeof (REGISTER_TYPE);
dd3b648e
RP
528
529#endif /* Different register sets */
530
dd3b648e
RP
531 /* XXX change second param to be a proc number */
532 status = net_ptrace_clnt_call (PTRACE_SETREGS, &ptrace_in, &ptrace_out);
533 if (status)
534 error (rpcerr);
535 if (ptrace_out.status == -1)
536 {
537 errno = ptrace_out.errno;
8616205a 538 perror_with_name ("net_ptrace_clnt_call(PTRACE_SETREGS)");
dd3b648e
RP
539 }
540
541 /* Store floating point registers if the target has them. */
542
543 if (target_has_fp)
544 {
8616205a
SG
545 ptrace_in.pid = inferior_pid;
546 ptrace_in.info.ttype = DATA;
547 ptrace_in.info.more_data = (caddr_t) &in_data;
548
549
dd3b648e
RP
550#ifdef I80960
551
552 bcopy (&registers[REGISTER_BYTE (FP0_REGNUM)], &inferior_fp_registers,
553 sizeof inferior_fp_registers.fps_regs);
554
555#else /* not 960 -- assume 68k -- FIXME */
556
8616205a
SG
557 in_data.bytes = &registers[REGISTER_BYTE (FP0_REGNUM)];
558 in_data.len = (8 * REGISTER_RAW_SIZE (FP0_REGNUM)
559 + (3 * sizeof (REGISTER_TYPE)));
dd3b648e
RP
560
561#endif /* Different register sets */
562
dd3b648e
RP
563 status = net_ptrace_clnt_call (PTRACE_SETFPREGS, &ptrace_in, &ptrace_out);
564 if (status)
565 error (rpcerr);
566 if (ptrace_out.status == -1)
567 {
568 errno = ptrace_out.errno;
8616205a 569 perror_with_name ("net_ptrace_clnt_call(PTRACE_SETFPREGS)");
dd3b648e
RP
570 }
571 }
dd3b648e
RP
572}
573
574/* Copy LEN bytes to or from remote inferior's memory starting at MEMADDR
575 to debugger memory starting at MYADDR. WRITE is true if writing to the
576 inferior.
577 Result is the number of bytes written or read (zero if error). The
578 protocol allows us to return a negative count, indicating that we can't
579 handle the current address but can handle one N bytes further, but
580 vxworks doesn't give us that information. */
581
8616205a 582static int
8f1f2a72 583vx_xfer_memory (memaddr, myaddr, len, write, target)
dd3b648e
RP
584 CORE_ADDR memaddr;
585 char *myaddr;
586 int len;
8f1f2a72
JG
587 int write;
588 struct target_ops *target; /* ignored */
dd3b648e
RP
589{
590 int status;
591 Rptrace ptrace_in;
592 Ptrace_return ptrace_out;
593 C_bytes data;
594
595 bzero ((char *) &ptrace_in, sizeof (ptrace_in));
596 bzero ((char *) &ptrace_out, sizeof (ptrace_out));
597
598 ptrace_in.pid = inferior_pid; /* XXX pid unnecessary for READDATA */
599 ptrace_in.addr = (int) memaddr; /* Where from */
600 ptrace_in.data = len; /* How many bytes */
601
602 if (write)
603 {
604 ptrace_in.info.ttype = DATA;
605 ptrace_in.info.more_data = (caddr_t) &data;
606
607 data.bytes = (caddr_t) myaddr; /* Where from */
608 data.len = len; /* How many bytes (again, for XDR) */
609
610 /* XXX change second param to be a proc number */
611 status = net_ptrace_clnt_call (PTRACE_WRITEDATA, &ptrace_in, &ptrace_out);
612 }
613 else
614 {
615 ptrace_out.info.more_data = (caddr_t) &data;
616 data.bytes = myaddr; /* Where to */
617 data.len = len; /* How many (again, for XDR) */
618
619 /* XXX change second param to be a proc number */
620 status = net_ptrace_clnt_call (PTRACE_READDATA, &ptrace_in, &ptrace_out);
621 }
622
623 if (status)
624 error (rpcerr);
625 if (ptrace_out.status == -1)
626 {
627 return 0; /* No bytes moved */
628 }
629 return len; /* Moved *all* the bytes */
630}
631
8616205a 632static void
dd3b648e
RP
633vx_files_info ()
634{
635 printf ("\tAttached to host `%s'", vx_host);
636 printf (", which has %sfloating point", target_has_fp? "": "no ");
637 printf (".\n");
638}
639
8616205a 640static void
dd3b648e
RP
641vx_run_files_info ()
642{
e3af0493 643 printf ("\tRunning %s VxWorks process %s",
dd3b648e 644 vx_running? "child": "attached",
e3af0493 645 local_hex_string(inferior_pid));
dd3b648e
RP
646 if (vx_running)
647 printf (", function `%s'", vx_running);
648 printf(".\n");
649}
650
8616205a 651static void
dd3b648e
RP
652vx_resume (step, siggnal)
653 int step;
654 int siggnal;
655{
656 int status;
657 Rptrace ptrace_in;
658 Ptrace_return ptrace_out;
659
660 if (siggnal != 0 && siggnal != stop_signal)
661 error ("Cannot send signals to VxWorks processes");
662
663 bzero ((char *) &ptrace_in, sizeof (ptrace_in));
664 bzero ((char *) &ptrace_out, sizeof (ptrace_out));
665
666 ptrace_in.pid = inferior_pid;
667 ptrace_in.addr = 1; /* Target side insists on this, or it panics. */
668
669 /* XXX change second param to be a proc number */
670 status = net_ptrace_clnt_call (step? PTRACE_SINGLESTEP: PTRACE_CONT,
671 &ptrace_in, &ptrace_out);
672 if (status)
673 error (rpcerr);
674 if (ptrace_out.status == -1)
675 {
676 errno = ptrace_out.errno;
677 perror_with_name ("Resuming remote process");
678 }
679}
680
8616205a 681static void
dd3b648e
RP
682vx_mourn_inferior ()
683{
684 pop_target (); /* Pop back to no-child state */
685 generic_mourn_inferior ();
686}
687
688\f
689/* This function allows the addition of incrementally linked object files. */
690
8616205a 691static void
dd3b648e
RP
692vx_load_command (arg_string, from_tty)
693 char* arg_string;
694 int from_tty;
695{
696 CORE_ADDR text_addr;
697 CORE_ADDR data_addr;
698 CORE_ADDR bss_addr;
699
700 if (arg_string == 0)
701 error ("The load command takes a file name");
702
703 arg_string = tilde_expand (arg_string);
704 make_cleanup (free, arg_string);
705
706 dont_repeat ();
707
708 QUIT;
709 immediate_quit++;
710 if (net_load (arg_string, &text_addr, &data_addr, &bss_addr) == -1)
711 error ("Load failed on target machine");
712 immediate_quit--;
713
714 /* FIXME, for now we ignore data_addr and bss_addr. */
b0246b3b 715 (void) symbol_file_add (arg_string, from_tty, text_addr, 0, 0);
dd3b648e
RP
716}
717
718#ifdef FIXME /* Not ready for prime time */
719/* Single step the target program at the source or machine level.
720 Takes an error exit if rpc fails.
721 Returns -1 if remote single-step operation fails, else 0. */
722
723static int
724net_step ()
725{
726 enum clnt_stat status;
727 int step_status;
728 SOURCE_STEP source_step;
729
730 source_step.taskId = inferior_pid;
731
732 if (step_range_end)
733 {
734 source_step.startAddr = step_range_start;
735 source_step.endAddr = step_range_end;
736 }
737 else
738 {
739 source_step.startAddr = 0;
740 source_step.endAddr = 0;
741 }
742
743 status = net_clnt_call (VX_SOURCE_STEP, xdr_SOURCE_STEP, &source_step,
744 xdr_int, &step_status);
745
746 if (status == RPC_SUCCESS)
747 return step_status;
748 else
749 error (rpcerr);
750}
751#endif
752
753/* Emulate ptrace using RPC calls to the VxWorks target system.
754 Returns nonzero (-1) if RPC status to VxWorks is bad, 0 otherwise. */
755
756static int
757net_ptrace_clnt_call (request, pPtraceIn, pPtraceOut)
758 enum ptracereq request;
759 Rptrace *pPtraceIn;
760 Ptrace_return *pPtraceOut;
761{
762 enum clnt_stat status;
763
764 status = net_clnt_call (request, xdr_rptrace, pPtraceIn, xdr_ptrace_return,
765 pPtraceOut);
766
767 if (status != RPC_SUCCESS)
768 return -1;
769
770 return 0;
771}
772
773/* Query the target for the name of the file from which VxWorks was
774 booted. pBootFile is the address of a pointer to the buffer to
775 receive the file name; if the pointer pointed to by pBootFile is
776 NULL, memory for the buffer will be allocated by XDR.
777 Returns -1 if rpc failed, 0 otherwise. */
778
8616205a 779static int
dd3b648e
RP
780net_get_boot_file (pBootFile)
781 char **pBootFile;
782{
783 enum clnt_stat status;
784
785 status = net_clnt_call (VX_BOOT_FILE_INQ, xdr_void, (char *) 0,
786 xdr_wrapstring, pBootFile);
787 return (status == RPC_SUCCESS) ? 0 : -1;
788}
789
790/* Fetch a list of loaded object modules from the VxWorks target.
791 Returns -1 if rpc failed, 0 otherwise
792 There's no way to check if the returned loadTable is correct.
793 VxWorks doesn't check it. */
794
8616205a 795static int
dd3b648e
RP
796net_get_symbols (pLoadTable)
797 ldtabl *pLoadTable; /* return pointer to ldtabl here */
798{
799 enum clnt_stat status;
800
801 bzero ((char *) pLoadTable, sizeof (struct ldtabl));
802
803 status = net_clnt_call (VX_STATE_INQ, xdr_void, 0, xdr_ldtabl, pLoadTable);
804 return (status == RPC_SUCCESS) ? 0 : -1;
805}
806
807/* Look up a symbol in the VxWorks target's symbol table.
808 Returns status of symbol read on target side (0=success, -1=fail)
809 Returns -1 and complain()s if rpc fails. */
810
811struct complaint cant_contact_target =
812 {"Lost contact with VxWorks target", 0, 0};
813
8616205a 814static int
dd3b648e
RP
815vx_lookup_symbol (name, pAddr)
816 char *name; /* symbol name */
817 CORE_ADDR *pAddr;
818{
819 enum clnt_stat status;
820 SYMBOL_ADDR symbolAddr;
821
822 *pAddr = 0;
823 bzero ((char *) &symbolAddr, sizeof (symbolAddr));
824
825 status = net_clnt_call (VX_SYMBOL_INQ, xdr_wrapstring, &name,
826 xdr_SYMBOL_ADDR, &symbolAddr);
827 if (status != RPC_SUCCESS) {
828 complain (&cant_contact_target, 0);
829 return -1;
830 }
831
832 *pAddr = symbolAddr.addr;
833 return symbolAddr.status;
834}
835
836/* Check to see if the VxWorks target has a floating point coprocessor.
837 Returns 1 if target has floating point processor, 0 otherwise.
838 Calls error() if rpc fails. */
839
8616205a 840static int
dd3b648e
RP
841net_check_for_fp ()
842{
843 enum clnt_stat status;
844 bool_t fp = 0; /* true if fp processor is present on target board */
845
846 status = net_clnt_call (VX_FP_INQUIRE, xdr_void, 0, xdr_bool, &fp);
847 if (status != RPC_SUCCESS)
848 error (rpcerr);
849
850 return (int) fp;
851}
852
853/* Establish an RPC connection with the VxWorks target system.
854 Calls error () if unable to establish connection. */
855
8616205a 856static void
dd3b648e
RP
857net_connect (host)
858 char *host;
859{
860 struct sockaddr_in destAddr;
861 struct hostent *destHost;
862
863 /* get the internet address for the given host */
864
865 if ((destHost = (struct hostent *) gethostbyname (host)) == NULL)
866 error ("Invalid hostname. Couldn't find remote host address.");
867
868 bzero (&destAddr, sizeof (destAddr));
869
870 destAddr.sin_addr.s_addr = * (u_long *) destHost->h_addr;
871 destAddr.sin_family = AF_INET;
872 destAddr.sin_port = 0; /* set to actual port that remote
873 ptrace is listening on. */
874
875 /* Create a tcp client transport on which to issue
876 calls to the remote ptrace server. */
877
878 ptraceSock = RPC_ANYSOCK;
879 pClient = clnttcp_create (&destAddr, RDBPROG, RDBVERS, &ptraceSock, 0, 0);
880 /* FIXME, here is where we deal with different version numbers of the proto */
881
882 if (pClient == NULL)
883 {
884 clnt_pcreateerror ("\tnet_connect");
885 error ("Couldn't connect to remote target.");
886 }
887}
888\f
889/* Sleep for the specified number of milliseconds
890 * (assumed to be less than 1000).
891 * If select () is interrupted, returns immediately;
892 * takes an error exit if select () fails for some other reason.
893 */
894
895static void
896sleep_ms (ms)
897 long ms;
898{
899 struct timeval select_timeout;
900 int status;
901
902 select_timeout.tv_sec = 0;
903 select_timeout.tv_usec = ms * 1000;
904
905 status = select (0, (fd_set *) 0, (fd_set *) 0, (fd_set *) 0, &select_timeout);
906
907 if (status < 0 && errno != EINTR)
908 perror_with_name ("select");
909}
910
911/* Wait for control to return from inferior to debugger.
912 If inferior gets a signal, we may decide to start it up again
913 instead of returning. That is why there is a loop in this function.
914 When this function actually returns it means the inferior
915 should be left stopped and GDB should read more commands. */
916
917/* For network debugging with VxWorks.
918 * VxWorks knows when tasks hit breakpoints, receive signals, exit, etc,
919 * so vx_wait() receives this information directly from
920 * VxWorks instead of trying to figure out what happenned via a wait() call.
921 */
922
923static int
924vx_wait (status)
925 int *status;
926{
927 register int pid;
928 WAITTYPE w;
929 RDB_EVENT rdbEvent;
930 int quit_failed;
931
932 do
933 {
934 /* If CTRL-C is hit during this loop,
935 suspend the inferior process. */
936
937 quit_failed = 0;
938 if (quit_flag)
939 {
940 quit_failed = (net_quit () == -1);
941 quit_flag = 0;
942 }
943
944 /* If a net_quit () or net_wait () call has failed,
945 allow the user to break the connection with the target.
946 We can't simply error () out of this loop, since the
947 data structures representing the state of the inferior
948 are in an inconsistent state. */
949
950 if (quit_failed || net_wait (&rdbEvent) == -1)
951 {
952 terminal_ours ();
953 if (query ("Can't %s. Disconnect from target system? ",
954 (quit_failed) ? "suspend remote task"
955 : "get status of remote task"))
956 {
957 target_mourn_inferior();
958 error ("Use the \"target\" command to reconnect.");
959 }
960 else
961 {
962 terminal_inferior ();
963 continue;
964 }
965 }
966
967 pid = rdbEvent.taskId;
968 if (pid == 0)
969 {
970 sleep_ms (200); /* FIXME Don't kill the network too badly */
971 }
972 else if (pid != inferior_pid)
e3af0493 973 fatal ("Bad pid for debugged task: %s\n", local_hex_string(pid));
dd3b648e
RP
974 } while (pid == 0);
975
976 /* FIXME, eventually do more then SIGTRAP on everything... */
977 switch (rdbEvent.eventType)
978 {
979 case EVENT_EXIT:
980 WSETEXIT (w, 0);
981 /* FIXME is it possible to distinguish between a
982 XXX normal vs abnormal exit in VxWorks? */
983 break;
984
985 case EVENT_START: /* Task was just started. */
986 WSETSTOP (w, SIGTRAP);
987 break;
988
989 case EVENT_STOP:
990 WSETSTOP (w, SIGTRAP);
991 /* XXX was it stopped by a signal? act accordingly */
992 break;
993
994 case EVENT_BREAK: /* Breakpoint was hit. */
995 WSETSTOP (w, SIGTRAP);
996 break;
997
998 case EVENT_SUSPEND: /* Task was suspended, probably by ^C. */
999 WSETSTOP (w, SIGINT);
1000 break;
1001
1002 case EVENT_BUS_ERR: /* Task made evil nasty reference. */
1003 WSETSTOP (w, SIGBUS);
1004 break;
1005
1006 case EVENT_ZERO_DIV: /* Division by zero */
1007 WSETSTOP (w, SIGFPE); /* Like Unix, call it a float exception. */
021959e2 1008 break;
dd3b648e
RP
1009
1010 case EVENT_SIGNAL:
1011 /* The target is not running Unix, and its
1012 faults/traces do not map nicely into Unix signals.
1013 Make sure they do not get confused with Unix signals
1014 by numbering them with values higher than the highest
1015 legal Unix signal. code in the arch-dependent PRINT_RANDOM_SIGNAL
1016 routine will interpret the value for wait_for_inferior. */
1017 WSETSTOP (w, rdbEvent.sigType + NSIG);
1018 break;
1019 } /* switch */
1020 *status = *(int *)&w; /* Grumble union wait crap Grumble */
1021 return pid;
1022}
1023\f
1024static int
1025symbol_stub (arg)
bdbd5f50 1026 char *arg;
dd3b648e 1027{
bdbd5f50 1028 symbol_file_command (arg, 0);
dd3b648e
RP
1029 return 1;
1030}
1031
1032static int
1033add_symbol_stub (arg)
bdbd5f50 1034 char *arg;
dd3b648e
RP
1035{
1036 struct ldfile *pLoadFile = (struct ldfile *)arg;
1037
1038 printf("\t%s: ", pLoadFile->name);
b0246b3b 1039 (void) symbol_file_add (pLoadFile->name, 0, pLoadFile->txt_addr, 0, 0);
dd3b648e
RP
1040 printf ("ok\n");
1041 return 1;
1042}
1043/* Target command for VxWorks target systems.
1044
1045 Used in vxgdb. Takes the name of a remote target machine
1046 running vxWorks and connects to it to initialize remote network
1047 debugging. */
1048
1049static void
1050vx_open (args, from_tty)
1051 char *args;
1052 int from_tty;
1053{
1054 extern int close ();
1055 char *bootFile;
1056 extern char *source_path;
1057 struct ldtabl loadTable;
1058 struct ldfile *pLoadFile;
1059 int i;
1060 extern CLIENT *pClient;
1061
1062 if (!args)
1063 error_no_arg ("target machine name");
1064
70dcc196
JK
1065 target_preopen (from_tty);
1066
dd3b648e
RP
1067 unpush_target (&vx_ops);
1068 printf ("Attaching remote machine across net...\n");
1069 fflush (stdout);
1070
1071 /* Allow the user to kill the connect attempt by typing ^C.
1072 Wait until the call to target_has_fp () completes before
1073 disallowing an immediate quit, since even if net_connect ()
1074 is successful, the remote debug server might be hung. */
1075
1076 immediate_quit++;
1077
1078 net_connect (args);
1079 target_has_fp = net_check_for_fp ();
1080 printf_filtered ("Connected to %s.\n", args);
1081
1082 immediate_quit--;
1083
1084 push_target (&vx_ops);
1085
1086 /* Save a copy of the target host's name. */
1087 vx_host = savestring (args, strlen (args));
1088
1089 /* Find out the name of the file from which the target was booted
1090 and load its symbol table. */
1091
1092 printf_filtered ("Looking in Unix path for all loaded modules:\n");
1093 bootFile = NULL;
1094 if (!net_get_boot_file (&bootFile))
1095 {
1096 if (*bootFile) {
1097 printf_filtered ("\t%s: ", bootFile);
bdbd5f50 1098 if (catch_errors (symbol_stub, bootFile,
06b6c733 1099 "Error while reading symbols from boot file:\n"))
dd3b648e
RP
1100 puts_filtered ("ok\n");
1101 } else if (from_tty)
1102 printf ("VxWorks kernel symbols not loaded.\n");
1103 }
1104 else
1105 error ("Can't retrieve boot file name from target machine.");
1106
1107 clnt_freeres (pClient, xdr_wrapstring, &bootFile);
1108
1109 if (net_get_symbols (&loadTable) != 0)
1110 error ("Can't read loaded modules from target machine");
1111
1112 i = 0-1;
1113 while (++i < loadTable.tbl_size)
1114 {
1115 QUIT; /* FIXME, avoids clnt_freeres below: mem leak */
1116 pLoadFile = &loadTable.tbl_ent [i];
1117#ifdef WRS_ORIG
1118 {
1119 register int desc;
1120 struct cleanup *old_chain;
1121 char *fullname = NULL;
1122
1123 desc = openp (source_path, 0, pLoadFile->name, O_RDONLY, 0, &fullname);
1124 if (desc < 0)
1125 perror_with_name (pLoadFile->name);
1126 old_chain = make_cleanup (close, desc);
1127 add_file_at_addr (fullname, desc, pLoadFile->txt_addr, pLoadFile->data_addr,
1128 pLoadFile->bss_addr);
1129 do_cleanups (old_chain);
1130 }
1131#else
1132 /* Botches, FIXME:
1133 (1) Searches the PATH, not the source path.
1134 (2) data and bss are assumed to be at the usual offsets from text. */
bdbd5f50 1135 catch_errors (add_symbol_stub, (char *)pLoadFile, (char *)0);
dd3b648e
RP
1136#endif
1137 }
1138 printf_filtered ("Done.\n");
1139
1140 clnt_freeres (pClient, xdr_ldtabl, &loadTable);
1141}
1142\f
1143/* attach_command --
1144 takes a task started up outside of gdb and ``attaches'' to it.
1145 This stops it cold in its tracks and allows us to start tracing it. */
1146
1147static void
1148vx_attach (args, from_tty)
1149 char *args;
1150 int from_tty;
1151{
1152 int pid;
1153 char *cptr = 0;
1154 Rptrace ptrace_in;
1155 Ptrace_return ptrace_out;
1156 int status;
1157
1158 dont_repeat();
1159
1160 if (!args)
1161 error_no_arg ("process-id to attach");
1162
1163 pid = strtol (args, &cptr, 0);
1164 if ((cptr == args) || (*cptr != '\0'))
1165 error ("Invalid process-id -- give a single number in decimal or 0xhex");
1166
1167 if (from_tty)
e3af0493 1168 printf ("Attaching pid %s.\n", local_hex_string(pid));
dd3b648e
RP
1169
1170 bzero ((char *)&ptrace_in, sizeof (ptrace_in));
1171 bzero ((char *)&ptrace_out, sizeof (ptrace_out));
1172 ptrace_in.pid = pid;
1173
1174 status = net_ptrace_clnt_call (PTRACE_ATTACH, &ptrace_in, &ptrace_out);
1175 if (status == -1)
1176 error (rpcerr);
1177 if (ptrace_out.status == -1)
1178 {
1179 errno = ptrace_out.errno;
1180 perror_with_name ("Attaching remote process");
1181 }
1182
1183 /* It worked... */
1184 push_target (&vx_run_ops);
1185 inferior_pid = pid;
1186 vx_running = 0;
1187
dd3b648e
RP
1188 mark_breakpoints_out ();
1189
1190 /* Set up the "saved terminal modes" of the inferior
1191 based on what modes we are starting it with. */
1192 target_terminal_init ();
1193
1194 /* Install inferior's terminal modes. */
1195 target_terminal_inferior ();
1196
1197 /* We will get a task spawn event immediately. */
1198 init_wait_for_inferior ();
1199 clear_proceed_status ();
1200 stop_soon_quietly = 1;
1201 wait_for_inferior ();
1202 stop_soon_quietly = 0;
1203 normal_stop ();
1204}
1205
1206
1207/* detach_command --
1208 takes a program previously attached to and detaches it.
1209 The program resumes execution and will no longer stop
1210 on signals, etc. We better not have left any breakpoints
1211 in the program or it'll die when it hits one. For this
1212 to work, it may be necessary for the process to have been
1213 previously attached. It *might* work if the program was
1214 started via the normal ptrace (PTRACE_TRACEME). */
1215
1216static void
1217vx_detach (args, from_tty)
1218 char *args;
1219 int from_tty;
1220{
1221 Rptrace ptrace_in;
1222 Ptrace_return ptrace_out;
1223 int signal = 0;
1224 int status;
1225
1226 if (args)
1227 error ("Argument given to VxWorks \"detach\".");
1228
1229 if (from_tty)
e3af0493 1230 printf ("Detaching pid %s.\n", local_hex_string(inferior_pid));
dd3b648e
RP
1231
1232 if (args) /* FIXME, should be possible to leave suspended */
1233 signal = atoi (args);
1234
1235 bzero ((char *)&ptrace_in, sizeof (ptrace_in));
1236 bzero ((char *)&ptrace_out, sizeof (ptrace_out));
1237 ptrace_in.pid = inferior_pid;
1238
1239 status = net_ptrace_clnt_call (PTRACE_DETACH, &ptrace_in, &ptrace_out);
1240 if (status == -1)
1241 error (rpcerr);
1242 if (ptrace_out.status == -1)
1243 {
1244 errno = ptrace_out.errno;
1245 perror_with_name ("Detaching VxWorks process");
1246 }
1247
1248 inferior_pid = 0;
1249 pop_target (); /* go back to non-executing VxWorks connection */
1250}
1251
1252/* vx_kill -- takes a running task and wipes it out. */
1253
1254static void
1255vx_kill (args, from_tty)
1256 char *args;
1257 int from_tty;
1258{
1259 Rptrace ptrace_in;
1260 Ptrace_return ptrace_out;
1261 int status;
1262
1263 if (args)
1264 error ("Argument given to VxWorks \"kill\".");
1265
1266 if (from_tty)
e3af0493 1267 printf ("Killing pid %s.\n", local_hex_string(inferior_pid));
dd3b648e
RP
1268
1269 bzero ((char *)&ptrace_in, sizeof (ptrace_in));
1270 bzero ((char *)&ptrace_out, sizeof (ptrace_out));
1271 ptrace_in.pid = inferior_pid;
1272
1273 status = net_ptrace_clnt_call (PTRACE_KILL, &ptrace_in, &ptrace_out);
1274 if (status == -1)
1275 error (rpcerr);
1276 if (ptrace_out.status == -1)
1277 {
1278 errno = ptrace_out.errno;
1279 perror_with_name ("Killing VxWorks process");
1280 }
1281
1282 /* If it gives good status, the process is *gone*, no events remain. */
1283 inferior_pid = 0;
1284 pop_target (); /* go back to non-executing VxWorks connection */
1285}
1286
1287/* Clean up from the VxWorks process target as it goes away. */
1288
8616205a 1289static void
dd3b648e
RP
1290vx_proc_close (quitting)
1291 int quitting;
1292{
1293 inferior_pid = 0; /* No longer have a process. */
1294 if (vx_running)
1295 free (vx_running);
1296 vx_running = 0;
1297}
1298\f
dd3b648e
RP
1299/* Make an RPC call to the VxWorks target.
1300 Returns RPC status. */
1301
1302static enum clnt_stat
1303net_clnt_call (procNum, inProc, in, outProc, out)
1304 enum ptracereq procNum;
1305 xdrproc_t inProc;
1306 char *in;
1307 xdrproc_t outProc;
1308 char *out;
1309{
1310 enum clnt_stat status;
1311
1312 status = clnt_call (pClient, procNum, inProc, in, outProc, out, rpcTimeout);
1313
1314 if (status != RPC_SUCCESS)
1315 clnt_perrno (status);
1316
1317 return status;
1318}
1319
1320/* Clean up before losing control. */
1321
8616205a 1322static void
dd3b648e
RP
1323vx_close (quitting)
1324 int quitting;
1325{
1326 if (pClient)
1327 clnt_destroy (pClient); /* The net connection */
1328 pClient = 0;
1329
1330 if (vx_host)
1331 free (vx_host); /* The hostname */
1332 vx_host = 0;
1333}
1334
70dcc196
JK
1335/* A vxprocess target should be started via "run" not "target". */
1336/*ARGSUSED*/
1337static void
1338vx_proc_open (name, from_tty)
1339 char *name;
1340 int from_tty;
1341{
1342 error ("Use the \"run\" command to start a VxWorks process.");
1343}
dd3b648e
RP
1344
1345/* Target ops structure for accessing memory and such over the net */
1346
1347struct target_ops vx_ops = {
1348 "vxworks", "VxWorks target memory via RPC over TCP/IP",
70dcc196
JK
1349 "Use VxWorks target memory. \n\
1350Specify the name of the machine to connect to.",
dd3b648e
RP
1351 vx_open, vx_close, vx_attach, 0, /* vx_detach, */
1352 0, 0, /* resume, wait */
1353 0, 0, /* read_reg, write_reg */
8616205a 1354 0, host_convert_to_virtual, host_convert_from_virtual, /* prep_to_store, */
dd3b648e
RP
1355 vx_xfer_memory, vx_files_info,
1356 0, 0, /* insert_breakpoint, remove_breakpoint */
1357 0, 0, 0, 0, 0, /* terminal stuff */
1358 0, /* vx_kill, */
8f1f2a72 1359 vx_load_command,
dd3b648e
RP
1360 vx_lookup_symbol,
1361 vx_create_inferior, 0, /* mourn_inferior */
1362 core_stratum, 0, /* next */
1363 1, 1, 0, 0, 0, /* all mem, mem, stack, regs, exec */
8f1f2a72 1364 0, 0, /* Section pointers */
dd3b648e
RP
1365 OPS_MAGIC, /* Always the last thing */
1366};
1367
1368/* Target ops structure for accessing VxWorks child processes over the net */
1369
1370struct target_ops vx_run_ops = {
1371 "vxprocess", "VxWorks process",
70dcc196
JK
1372 "VxWorks process, started by the \"run\" command.",
1373 vx_proc_open, vx_proc_close, 0, vx_detach, /* vx_attach */
dd3b648e
RP
1374 vx_resume, vx_wait,
1375 vx_read_register, vx_write_register,
8616205a 1376 vx_prepare_to_store, host_convert_to_virtual, host_convert_from_virtual,
dd3b648e
RP
1377 vx_xfer_memory, vx_run_files_info,
1378 vx_insert_breakpoint, vx_remove_breakpoint,
1379 0, 0, 0, 0, 0, /* terminal stuff */
1380 vx_kill,
8f1f2a72 1381 vx_load_command,
dd3b648e
RP
1382 vx_lookup_symbol,
1383 0, vx_mourn_inferior,
1384 process_stratum, 0, /* next */
1385 0, 1, 1, 1, 1, /* all mem, mem, stack, regs, exec */
1386 /* all_mem is off to avoid spurious msg in "i files" */
8f1f2a72 1387 0, 0, /* Section pointers */
dd3b648e
RP
1388 OPS_MAGIC, /* Always the last thing */
1389};
1390/* ==> Remember when reading at end of file, there are two "ops" structs here. */
1391\f
1392void
1393_initialize_vx ()
1394{
1395 add_target (&vx_ops);
1396 add_target (&vx_run_ops);
1397}
This page took 0.115319 seconds and 4 git commands to generate.