| 1 | /* Target-vector operations for controlling win32 child processes, for GDB. |
| 2 | Copyright 1995 |
| 3 | Free Software Foundation, Inc. |
| 4 | |
| 5 | Contributed by Cygnus Support. |
| 6 | This file is part of GDB. |
| 7 | |
| 8 | This program is free software; you can redistribute it and/or modify |
| 9 | it under the terms of the GNU General Public License as published by |
| 10 | the Free Software Foundation; either version 2 of the License, or |
| 11 | (at your option) any later version. |
| 12 | |
| 13 | This program is distributed in the hope that it will be useful, |
| 14 | but WITHOUT ANY WARRANTY; without eve nthe implied warranty of |
| 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 16 | GNU General Public License for more details. |
| 17 | |
| 18 | You should have received a copy of the GNU General Public License |
| 19 | along with this program; if not, write to the Free Software |
| 20 | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ |
| 21 | |
| 22 | /* by Steve Chamberlain, sac@cygnus.com */ |
| 23 | |
| 24 | #include "defs.h" |
| 25 | #include "frame.h" /* required by inferior.h */ |
| 26 | #include "inferior.h" |
| 27 | #include "target.h" |
| 28 | #include "wait.h" |
| 29 | #include "gdbcore.h" |
| 30 | #include "command.h" |
| 31 | #include <signal.h> |
| 32 | #include <sys/types.h> |
| 33 | #include <fcntl.h> |
| 34 | #include <windows.h> |
| 35 | #include "buildsym.h" |
| 36 | #include "gdb_string.h" |
| 37 | #include "thread.h" |
| 38 | #include "gdbcmd.h" |
| 39 | |
| 40 | #define CHECK(x) check (x, __FILE__,__LINE__) |
| 41 | #define DEBUG(x) if (remote_debug) printf x |
| 42 | |
| 43 | |
| 44 | /* Forward declaration */ |
| 45 | extern struct target_ops child_ops; |
| 46 | |
| 47 | /* The most recently read context. Inspect ContextFlags to see what |
| 48 | bits are valid. */ |
| 49 | |
| 50 | static CONTEXT context; |
| 51 | |
| 52 | /* The process and thread handles for the above context. */ |
| 53 | |
| 54 | static HANDLE current_process; |
| 55 | static HANDLE current_thread; |
| 56 | static int current_process_id; |
| 57 | static int current_thread_id; |
| 58 | |
| 59 | /* Counts of things. */ |
| 60 | static int exception_count = 0; |
| 61 | static int event_count = 0; |
| 62 | |
| 63 | /* User options. */ |
| 64 | static int new_console = 0; |
| 65 | static int new_group = 0; |
| 66 | |
| 67 | /* This vector maps GDB's idea of a register's number into an address |
| 68 | in the win32 exception context vector. |
| 69 | |
| 70 | It also contains the bit mask needed to load the register in question. |
| 71 | |
| 72 | One day we could read a reg, we could inspect the context we |
| 73 | already have loaded, if it doesn't have the bit set that we need, |
| 74 | we read that set of registers in using GetThreadContext. If the |
| 75 | context already contains what we need, we just unpack it. Then to |
| 76 | write a register, first we have to ensure that the context contains |
| 77 | the other regs of the group, and then we copy the info in and set |
| 78 | out bit. */ |
| 79 | |
| 80 | struct regmappings |
| 81 | { |
| 82 | char *incontext; |
| 83 | int mask; |
| 84 | }; |
| 85 | |
| 86 | static const struct regmappings |
| 87 | mappings[] = |
| 88 | { |
| 89 | {(char *) &context.Eax, CONTEXT_INTEGER}, |
| 90 | {(char *) &context.Ecx, CONTEXT_INTEGER}, |
| 91 | {(char *) &context.Edx, CONTEXT_INTEGER}, |
| 92 | {(char *) &context.Ebx, CONTEXT_INTEGER}, |
| 93 | {(char *) &context.Esp, CONTEXT_CONTROL}, |
| 94 | {(char *) &context.Ebp, CONTEXT_CONTROL}, |
| 95 | {(char *) &context.Esi, CONTEXT_INTEGER}, |
| 96 | {(char *) &context.Edi, CONTEXT_INTEGER}, |
| 97 | {(char *) &context.Eip, CONTEXT_CONTROL}, |
| 98 | {(char *) &context.EFlags, CONTEXT_CONTROL}, |
| 99 | {(char *) &context.SegCs, CONTEXT_SEGMENTS}, |
| 100 | {(char *) &context.SegSs, CONTEXT_SEGMENTS}, |
| 101 | {(char *) &context.SegDs, CONTEXT_SEGMENTS}, |
| 102 | {(char *) &context.SegEs, CONTEXT_SEGMENTS}, |
| 103 | {(char *) &context.SegFs, CONTEXT_SEGMENTS}, |
| 104 | {(char *) &context.SegGs, CONTEXT_SEGMENTS}, |
| 105 | {&context.FloatSave.RegisterArea[0 * 10], CONTEXT_FLOATING_POINT}, |
| 106 | {&context.FloatSave.RegisterArea[1 * 10], CONTEXT_FLOATING_POINT}, |
| 107 | {&context.FloatSave.RegisterArea[2 * 10], CONTEXT_FLOATING_POINT}, |
| 108 | {&context.FloatSave.RegisterArea[3 * 10], CONTEXT_FLOATING_POINT}, |
| 109 | {&context.FloatSave.RegisterArea[4 * 10], CONTEXT_FLOATING_POINT}, |
| 110 | {&context.FloatSave.RegisterArea[5 * 10], CONTEXT_FLOATING_POINT}, |
| 111 | {&context.FloatSave.RegisterArea[6 * 10], CONTEXT_FLOATING_POINT}, |
| 112 | {&context.FloatSave.RegisterArea[7 * 10], CONTEXT_FLOATING_POINT}, |
| 113 | }; |
| 114 | |
| 115 | |
| 116 | /* This vector maps the target's idea of an exception (extracted |
| 117 | from the DEBUG_EVENT structure) to GDB's idea. */ |
| 118 | |
| 119 | struct xlate_exception |
| 120 | { |
| 121 | int them; |
| 122 | enum target_signal us; |
| 123 | }; |
| 124 | |
| 125 | |
| 126 | static const struct xlate_exception |
| 127 | xlate[] = |
| 128 | { |
| 129 | {EXCEPTION_ACCESS_VIOLATION, TARGET_SIGNAL_SEGV}, |
| 130 | {STATUS_STACK_OVERFLOW, TARGET_SIGNAL_SEGV}, |
| 131 | {EXCEPTION_BREAKPOINT, TARGET_SIGNAL_TRAP}, |
| 132 | {DBG_CONTROL_C, TARGET_SIGNAL_INT}, |
| 133 | {EXCEPTION_SINGLE_STEP, TARGET_SIGNAL_TRAP}, |
| 134 | {-1, -1}}; |
| 135 | |
| 136 | |
| 137 | static void |
| 138 | check (BOOL ok, const char *file, int line) |
| 139 | { |
| 140 | if (!ok) |
| 141 | printf_filtered ("error return %s:%d was %d\n", file, line, GetLastError ()); |
| 142 | } |
| 143 | |
| 144 | static void |
| 145 | child_fetch_inferior_registers (int r) |
| 146 | { |
| 147 | if (r < 0) |
| 148 | { |
| 149 | for (r = 0; r < NUM_REGS; r++) |
| 150 | child_fetch_inferior_registers (r); |
| 151 | } |
| 152 | else |
| 153 | { |
| 154 | supply_register (r, mappings[r].incontext); |
| 155 | } |
| 156 | } |
| 157 | |
| 158 | static void |
| 159 | child_store_inferior_registers (int r) |
| 160 | { |
| 161 | if (r < 0) |
| 162 | { |
| 163 | for (r = 0; r < NUM_REGS; r++) |
| 164 | child_store_inferior_registers (r); |
| 165 | } |
| 166 | else |
| 167 | { |
| 168 | read_register_gen (r, mappings[r].incontext); |
| 169 | } |
| 170 | } |
| 171 | |
| 172 | |
| 173 | /* Wait for child to do something. Return pid of child, or -1 in case |
| 174 | of error; store status through argument pointer OURSTATUS. */ |
| 175 | |
| 176 | |
| 177 | static void |
| 178 | handle_load_dll (DEBUG_EVENT * event) |
| 179 | { |
| 180 | DWORD dll_name_ptr; |
| 181 | DWORD done; |
| 182 | |
| 183 | ReadProcessMemory (current_process, |
| 184 | (DWORD) event->u.LoadDll.lpImageName, |
| 185 | (char *) &dll_name_ptr, |
| 186 | sizeof (dll_name_ptr), &done); |
| 187 | |
| 188 | /* See if we could read the address of a string, and that the |
| 189 | address isn't null. */ |
| 190 | |
| 191 | if (done == sizeof (dll_name_ptr) && dll_name_ptr) |
| 192 | { |
| 193 | char *dll_name; |
| 194 | int size = event->u.LoadDll.fUnicode ? sizeof (WCHAR) : sizeof (char); |
| 195 | int len = 0; |
| 196 | char b[2]; |
| 197 | do |
| 198 | { |
| 199 | ReadProcessMemory (current_process, |
| 200 | dll_name_ptr + len * size, |
| 201 | &b, |
| 202 | size, |
| 203 | &done); |
| 204 | len++; |
| 205 | } |
| 206 | while ((b[0] != 0 || b[size - 1] != 0) && done == size); |
| 207 | |
| 208 | |
| 209 | dll_name = alloca (len); |
| 210 | |
| 211 | if (event->u.LoadDll.fUnicode) |
| 212 | { |
| 213 | WCHAR *unicode_dll_name = (WCHAR *) alloca (len * sizeof (WCHAR)); |
| 214 | ReadProcessMemory (current_process, |
| 215 | dll_name_ptr, |
| 216 | unicode_dll_name, |
| 217 | len * sizeof (WCHAR), |
| 218 | &done); |
| 219 | |
| 220 | WideCharToMultiByte (CP_ACP, 0, |
| 221 | unicode_dll_name, len, |
| 222 | dll_name, len, 0, 0); |
| 223 | } |
| 224 | else |
| 225 | { |
| 226 | ReadProcessMemory (current_process, |
| 227 | dll_name_ptr, |
| 228 | dll_name, |
| 229 | len, |
| 230 | &done); |
| 231 | } |
| 232 | |
| 233 | /* FIXME!! It would be nice to define one symbol which pointed to the |
| 234 | front of the dll if we can't find any symbols. */ |
| 235 | |
| 236 | context.ContextFlags = CONTEXT_FULL; |
| 237 | GetThreadContext (current_thread, &context); |
| 238 | |
| 239 | symbol_file_add (dll_name, 0, (int) event->u.LoadDll.lpBaseOfDll, 0, 0, 0); |
| 240 | |
| 241 | /* We strip off the path of the dll for tidiness. */ |
| 242 | if (strrchr (dll_name, '\\')) |
| 243 | dll_name = strrchr (dll_name, '\\') + 1; |
| 244 | printf_unfiltered ("%x:%s\n", event->u.LoadDll.lpBaseOfDll, dll_name); |
| 245 | } |
| 246 | } |
| 247 | |
| 248 | |
| 249 | static void |
| 250 | handle_exception (DEBUG_EVENT * event, struct target_waitstatus *ourstatus) |
| 251 | { |
| 252 | int i; |
| 253 | int done = 0; |
| 254 | ourstatus->kind = TARGET_WAITKIND_STOPPED; |
| 255 | |
| 256 | for (i = 0; !done && xlate[i].us > 0; i++) |
| 257 | { |
| 258 | if (xlate[i].them == event->u.Exception.ExceptionRecord.ExceptionCode) |
| 259 | { |
| 260 | ourstatus->value.sig = xlate[i].us; |
| 261 | done = 1; |
| 262 | break; |
| 263 | } |
| 264 | } |
| 265 | |
| 266 | if (!done) |
| 267 | { |
| 268 | printf_unfiltered ("Want to know about exception code %08x\n", |
| 269 | event->u.Exception.ExceptionRecord.ExceptionCode); |
| 270 | ourstatus->value.sig = TARGET_SIGNAL_UNKNOWN; |
| 271 | } |
| 272 | context.ContextFlags = CONTEXT_FULL; |
| 273 | GetThreadContext (current_thread, &context); |
| 274 | exception_count++; |
| 275 | } |
| 276 | |
| 277 | static int |
| 278 | child_wait (int pid, struct target_waitstatus *ourstatus) |
| 279 | { |
| 280 | /* We loop when we get a non-standard exception rather than return |
| 281 | with a SPURIOUS because resume can try and step or modify things, |
| 282 | which needs a current_thread. But some of these exceptions mark |
| 283 | the birth or death of threads, which mean that the current thread |
| 284 | isn't necessarily what you think it is. */ |
| 285 | |
| 286 | while (1) |
| 287 | { |
| 288 | DEBUG_EVENT event; |
| 289 | BOOL t = WaitForDebugEvent (&event, INFINITE); |
| 290 | |
| 291 | DEBUG (("%d = WaitForDebugEvent() code=%d pid=%d tid=%d)\n", |
| 292 | t, |
| 293 | event.dwDebugEventCode, |
| 294 | event.dwProcessId, |
| 295 | event.dwThreadId)); |
| 296 | |
| 297 | event_count++; |
| 298 | |
| 299 | current_thread_id = event.dwThreadId; |
| 300 | current_process_id = event.dwProcessId; |
| 301 | |
| 302 | switch (event.dwDebugEventCode) |
| 303 | { |
| 304 | case CREATE_THREAD_DEBUG_EVENT: |
| 305 | case EXIT_THREAD_DEBUG_EVENT: |
| 306 | case CREATE_PROCESS_DEBUG_EVENT: |
| 307 | break; |
| 308 | |
| 309 | case EXIT_PROCESS_DEBUG_EVENT: |
| 310 | ourstatus->kind = TARGET_WAITKIND_EXITED; |
| 311 | ourstatus->value.integer = event.u.ExitProcess.dwExitCode; |
| 312 | CloseHandle (current_process); |
| 313 | CloseHandle (current_thread); |
| 314 | return current_process_id; |
| 315 | break; |
| 316 | |
| 317 | case LOAD_DLL_DEBUG_EVENT: |
| 318 | handle_load_dll (&event); |
| 319 | break; |
| 320 | case EXCEPTION_DEBUG_EVENT: |
| 321 | handle_exception (&event, ourstatus); |
| 322 | return current_process_id; |
| 323 | default: |
| 324 | printf_unfiltered ("waitfor it %d %d %d %d\n", t, |
| 325 | event.dwDebugEventCode, |
| 326 | event.dwProcessId, |
| 327 | event.dwThreadId); |
| 328 | break; |
| 329 | } |
| 330 | CHECK (ContinueDebugEvent (current_process_id, |
| 331 | current_thread_id, |
| 332 | DBG_CONTINUE)); |
| 333 | } |
| 334 | } |
| 335 | |
| 336 | |
| 337 | |
| 338 | |
| 339 | /* Attach to process PID, then initialize for debugging it. */ |
| 340 | |
| 341 | static void |
| 342 | child_attach (args, from_tty) |
| 343 | char *args; |
| 344 | int from_tty; |
| 345 | { |
| 346 | BOOL ok; |
| 347 | |
| 348 | if (!args) |
| 349 | error_no_arg ("process-id to attach"); |
| 350 | |
| 351 | current_process_id = strtoul (args, 0, 0); |
| 352 | |
| 353 | ok = DebugActiveProcess (current_process_id); |
| 354 | |
| 355 | if (!ok) |
| 356 | error ("Can't attach to process."); |
| 357 | |
| 358 | |
| 359 | exception_count = 0; |
| 360 | event_count = 0; |
| 361 | |
| 362 | if (from_tty) |
| 363 | { |
| 364 | char *exec_file = (char *) get_exec_file (0); |
| 365 | |
| 366 | if (exec_file) |
| 367 | printf_unfiltered ("Attaching to program `%s', %s\n", exec_file, |
| 368 | target_pid_to_str (current_process_id)); |
| 369 | else |
| 370 | printf_unfiltered ("Attaching to %s\n", |
| 371 | target_pid_to_str (current_process_id)); |
| 372 | |
| 373 | gdb_flush (gdb_stdout); |
| 374 | } |
| 375 | |
| 376 | inferior_pid = current_process_id; |
| 377 | push_target (&child_ops); |
| 378 | } |
| 379 | |
| 380 | |
| 381 | static void |
| 382 | child_detach (args, from_tty) |
| 383 | char *args; |
| 384 | int from_tty; |
| 385 | { |
| 386 | if (from_tty) |
| 387 | { |
| 388 | char *exec_file = get_exec_file (0); |
| 389 | if (exec_file == 0) |
| 390 | exec_file = ""; |
| 391 | printf_unfiltered ("Detaching from program: %s %s\n", exec_file, |
| 392 | target_pid_to_str (inferior_pid)); |
| 393 | gdb_flush (gdb_stdout); |
| 394 | } |
| 395 | inferior_pid = 0; |
| 396 | unpush_target (&child_ops); |
| 397 | } |
| 398 | |
| 399 | |
| 400 | /* Print status information about what we're accessing. */ |
| 401 | |
| 402 | static void |
| 403 | child_files_info (ignore) |
| 404 | struct target_ops *ignore; |
| 405 | { |
| 406 | printf_unfiltered ("\tUsing the running image of %s %s.\n", |
| 407 | attach_flag ? "attached" : "child", target_pid_to_str (inferior_pid)); |
| 408 | } |
| 409 | |
| 410 | /* ARGSUSED */ |
| 411 | static void |
| 412 | child_open (arg, from_tty) |
| 413 | char *arg; |
| 414 | int from_tty; |
| 415 | { |
| 416 | error ("Use the \"run\" command to start a Unix child process."); |
| 417 | } |
| 418 | |
| 419 | |
| 420 | /* Start an inferior win32 child process and sets inferior_pid to its pid. |
| 421 | EXEC_FILE is the file to run. |
| 422 | ALLARGS is a string containing the arguments to the program. |
| 423 | ENV is the environment vector to pass. Errors reported with error(). */ |
| 424 | |
| 425 | |
| 426 | static void |
| 427 | child_create_inferior (exec_file, allargs, env) |
| 428 | char *exec_file; |
| 429 | char *allargs; |
| 430 | char **env; |
| 431 | { |
| 432 | char *real_path; |
| 433 | STARTUPINFO si; |
| 434 | PROCESS_INFORMATION pi; |
| 435 | struct target_waitstatus dummy; |
| 436 | BOOL ret; |
| 437 | DWORD flags; |
| 438 | char *args; |
| 439 | |
| 440 | if (!exec_file) |
| 441 | { |
| 442 | error ("No executable specified, use `target exec'.\n"); |
| 443 | } |
| 444 | |
| 445 | memset (&si, 0, sizeof (si)); |
| 446 | si.cb = sizeof (si); |
| 447 | |
| 448 | /* A realpath is always the same size, or a bit shorter than a nice path. */ |
| 449 | real_path = alloca (strlen (exec_file) + 1); |
| 450 | path_to_real_path (exec_file, real_path); |
| 451 | |
| 452 | flags = DEBUG_ONLY_THIS_PROCESS | DEBUG_PROCESS; |
| 453 | |
| 454 | if (new_group) |
| 455 | flags |= CREATE_NEW_PROCESS_GROUP; |
| 456 | |
| 457 | if (new_console) |
| 458 | flags |= CREATE_NEW_CONSOLE; |
| 459 | |
| 460 | args = alloca (strlen (exec_file) + strlen (allargs) + 2); |
| 461 | |
| 462 | strcpy (args, exec_file); |
| 463 | strcat (args, " "); |
| 464 | strcat (args, allargs); |
| 465 | |
| 466 | ret = CreateProcess (real_path, |
| 467 | args, |
| 468 | NULL, /* Security */ |
| 469 | NULL, /* thread */ |
| 470 | TRUE, /* inherit handles */ |
| 471 | flags, /* start flags */ |
| 472 | env, |
| 473 | NULL, /* current directory */ |
| 474 | &si, |
| 475 | &pi); |
| 476 | if (!ret) |
| 477 | error ("Error creating process %s, (error %d)\n", exec_file, GetLastError()); |
| 478 | |
| 479 | exception_count = 0; |
| 480 | event_count = 0; |
| 481 | |
| 482 | inferior_pid = pi.dwProcessId; |
| 483 | current_process = pi.hProcess; |
| 484 | current_thread = pi.hThread; |
| 485 | current_process_id = pi.dwProcessId; |
| 486 | current_thread_id = pi.dwThreadId; |
| 487 | push_target (&child_ops); |
| 488 | init_thread_list (); |
| 489 | init_wait_for_inferior (); |
| 490 | clear_proceed_status (); |
| 491 | target_terminal_init (); |
| 492 | target_terminal_inferior (); |
| 493 | |
| 494 | /* Ignore the first trap */ |
| 495 | child_wait (inferior_pid, &dummy); |
| 496 | |
| 497 | proceed ((CORE_ADDR) - 1, TARGET_SIGNAL_0, 0); |
| 498 | } |
| 499 | |
| 500 | static void |
| 501 | child_mourn_inferior () |
| 502 | { |
| 503 | unpush_target (&child_ops); |
| 504 | generic_mourn_inferior (); |
| 505 | } |
| 506 | |
| 507 | |
| 508 | /* Send a SIGINT to the process group. This acts just like the user typed a |
| 509 | ^C on the controlling terminal. */ |
| 510 | |
| 511 | void |
| 512 | child_stop () |
| 513 | { |
| 514 | CHECK (GenerateConsoleCtrlEvent (CTRL_C_EVENT, 0)); |
| 515 | } |
| 516 | |
| 517 | int |
| 518 | child_xfer_memory (CORE_ADDR memaddr, char *our, int len, |
| 519 | int write, struct target_ops *target) |
| 520 | { |
| 521 | DWORD done; |
| 522 | if (write) |
| 523 | { |
| 524 | WriteProcessMemory (current_process, memaddr, our, len, &done); |
| 525 | FlushInstructionCache (current_process, memaddr, len); |
| 526 | } |
| 527 | else |
| 528 | { |
| 529 | ReadProcessMemory (current_process, memaddr, our, len, &done); |
| 530 | } |
| 531 | return done; |
| 532 | } |
| 533 | |
| 534 | void |
| 535 | child_kill_inferior (void) |
| 536 | { |
| 537 | CHECK (TerminateProcess (current_process, 0)); |
| 538 | CHECK (CloseHandle (current_process)); |
| 539 | CHECK (CloseHandle (current_thread)); |
| 540 | } |
| 541 | |
| 542 | void |
| 543 | child_resume (int pid, int step, enum target_signal signal) |
| 544 | { |
| 545 | DEBUG (("child_resume (%d, %d, %d);\n", pid, step, signal)); |
| 546 | |
| 547 | if (step) |
| 548 | { |
| 549 | /* Single step by setting t bit */ |
| 550 | child_fetch_inferior_registers (PS_REGNUM); |
| 551 | context.EFlags |= FLAG_TRACE_BIT; |
| 552 | } |
| 553 | |
| 554 | if (context.ContextFlags) |
| 555 | { |
| 556 | CHECK (SetThreadContext (current_thread, &context)); |
| 557 | context.ContextFlags = 0; |
| 558 | } |
| 559 | |
| 560 | if (signal) |
| 561 | { |
| 562 | fprintf_unfiltered (gdb_stderr, "Can't send signals to the child.\n"); |
| 563 | } |
| 564 | |
| 565 | CHECK (ContinueDebugEvent (current_process_id, |
| 566 | current_thread_id, |
| 567 | DBG_CONTINUE)); |
| 568 | } |
| 569 | |
| 570 | static void |
| 571 | child_prepare_to_store () |
| 572 | { |
| 573 | /* Do nothing, since we can store individual regs */ |
| 574 | } |
| 575 | |
| 576 | static int |
| 577 | child_can_run () |
| 578 | { |
| 579 | return 1; |
| 580 | } |
| 581 | |
| 582 | static void |
| 583 | child_close () |
| 584 | { |
| 585 | |
| 586 | } |
| 587 | struct target_ops child_ops = |
| 588 | { |
| 589 | "child", /* to_shortname */ |
| 590 | "Win32 child process", /* to_longname */ |
| 591 | "Win32 child process (started by the \"run\" command).", /* to_doc */ |
| 592 | child_open, /* to_open */ |
| 593 | child_close, /* to_close */ |
| 594 | child_attach, /* to_attach */ |
| 595 | child_detach, /* to_detach */ |
| 596 | child_resume, /* to_resume */ |
| 597 | child_wait, /* to_wait */ |
| 598 | child_fetch_inferior_registers,/* to_fetch_registers */ |
| 599 | child_store_inferior_registers,/* to_store_registers */ |
| 600 | child_prepare_to_store, /* to_child_prepare_to_store */ |
| 601 | child_xfer_memory, /* to_xfer_memory */ |
| 602 | child_files_info, /* to_files_info */ |
| 603 | memory_insert_breakpoint, /* to_insert_breakpoint */ |
| 604 | memory_remove_breakpoint, /* to_remove_breakpoint */ |
| 605 | terminal_init_inferior, /* to_terminal_init */ |
| 606 | terminal_inferior, /* to_terminal_inferior */ |
| 607 | terminal_ours_for_output, /* to_terminal_ours_for_output */ |
| 608 | terminal_ours, /* to_terminal_ours */ |
| 609 | child_terminal_info, /* to_terminal_info */ |
| 610 | child_kill_inferior, /* to_kill */ |
| 611 | 0, /* to_load */ |
| 612 | 0, /* to_lookup_symbol */ |
| 613 | child_create_inferior, /* to_create_inferior */ |
| 614 | child_mourn_inferior, /* to_mourn_inferior */ |
| 615 | child_can_run, /* to_can_run */ |
| 616 | 0, /* to_notice_signals */ |
| 617 | 0, /* to_thread_alive */ |
| 618 | child_stop, /* to_stop */ |
| 619 | process_stratum, /* to_stratum */ |
| 620 | 0, /* to_next */ |
| 621 | 1, /* to_has_all_memory */ |
| 622 | 1, /* to_has_memory */ |
| 623 | 1, /* to_has_stack */ |
| 624 | 1, /* to_has_registers */ |
| 625 | 1, /* to_has_execution */ |
| 626 | 0, /* to_sections */ |
| 627 | 0, /* to_sections_end */ |
| 628 | OPS_MAGIC /* to_magic */ |
| 629 | }; |
| 630 | |
| 631 | void |
| 632 | _initialize_inftarg () |
| 633 | { |
| 634 | add_show_from_set |
| 635 | (add_set_cmd ("new-console", class_support, var_boolean, |
| 636 | (char *) &new_console, |
| 637 | "Set creation of new console when creating child process.", |
| 638 | &setlist), |
| 639 | &showlist); |
| 640 | |
| 641 | add_show_from_set |
| 642 | (add_set_cmd ("new-group", class_support, var_boolean, |
| 643 | (char *) &new_group, |
| 644 | "Set creation of new group when creating child process.", |
| 645 | &setlist), |
| 646 | &showlist); |
| 647 | |
| 648 | add_target (&child_ops); |
| 649 | } |