1 //===-- CommandObjectBreakpointCommand.cpp ----------------------*- C++ -*-===// 2 // 3 // The LLVM Compiler Infrastructure 4 // 5 // This file is distributed under the University of Illinois Open Source 6 // License. See LICENSE.TXT for details. 7 // 8 //===----------------------------------------------------------------------===// 9 10 // C Includes 11 // C++ Includes 12 13 14 #include "CommandObjectBreakpointCommand.h" 15 #include "CommandObjectBreakpoint.h" 16 17 #include "lldb/Interpreter/CommandInterpreter.h" 18 #include "lldb/Interpreter/CommandReturnObject.h" 19 #include "lldb/Target/Target.h" 20 #include "lldb/Target/Thread.h" 21 #include "lldb/Breakpoint/BreakpointIDList.h" 22 #include "lldb/Breakpoint/Breakpoint.h" 23 #include "lldb/Breakpoint/BreakpointLocation.h" 24 #include "lldb/Breakpoint/StoppointCallbackContext.h" 25 #include "lldb/Core/State.h" 26 27 using namespace lldb; 28 using namespace lldb_private; 29 30 //------------------------------------------------------------------------- 31 // CommandObjectBreakpointCommandAdd::CommandOptions 32 //------------------------------------------------------------------------- 33 34 CommandObjectBreakpointCommandAdd::CommandOptions::CommandOptions () : 35 Options () 36 { 37 } 38 39 CommandObjectBreakpointCommandAdd::CommandOptions::~CommandOptions () 40 { 41 } 42 43 lldb::OptionDefinition 44 CommandObjectBreakpointCommandAdd::CommandOptions::g_option_table[] = 45 { 46 { LLDB_OPT_SET_1, true, "script", 's', no_argument, NULL, 0, NULL, 47 "Write the breakpoint command script in the default scripting language."}, 48 49 { LLDB_OPT_SET_2, true, "python", 'p', no_argument, NULL, 0, NULL, 50 "Write the breakpoint command script in the Python scripting language."}, 51 52 { LLDB_OPT_SET_3, true, "commands", 'c', no_argument, NULL, 0, NULL, 53 "Write the breakpoint command script using standard debugger commands."}, 54 55 { 0, false, NULL, 0, 0, NULL, 0, NULL, NULL } 56 }; 57 58 const lldb::OptionDefinition* 59 CommandObjectBreakpointCommandAdd::CommandOptions::GetDefinitions () 60 { 61 return g_option_table; 62 } 63 64 65 Error 66 CommandObjectBreakpointCommandAdd::CommandOptions::SetOptionValue 67 ( 68 int option_idx, 69 const char *option_arg 70 ) 71 { 72 Error error; 73 char short_option = (char) m_getopt_table[option_idx].val; 74 75 switch (short_option) 76 { 77 case 's': 78 m_use_commands = false; 79 m_use_script_language = true; 80 m_script_language = eScriptLanguageDefault; 81 break; 82 case 'p': 83 m_use_commands = false; 84 m_use_script_language = true; 85 m_script_language = eScriptLanguagePython; 86 break; 87 case 'c': 88 m_use_commands = true; 89 m_use_script_language = false; 90 m_script_language = eScriptLanguageNone; 91 break; 92 default: 93 break; 94 } 95 return error; 96 } 97 98 void 99 CommandObjectBreakpointCommandAdd::CommandOptions::ResetOptionValues () 100 { 101 Options::ResetOptionValues(); 102 103 m_use_commands = false; 104 m_use_script_language = false; 105 m_script_language = eScriptLanguageNone; 106 } 107 108 //------------------------------------------------------------------------- 109 // CommandObjectBreakpointCommandAdd 110 //------------------------------------------------------------------------- 111 112 113 CommandObjectBreakpointCommandAdd::CommandObjectBreakpointCommandAdd () : 114 CommandObject ("add", 115 "Add a set of commands to a breakpoint, to be executed whenever the breakpoint is hit.", 116 "breakpoint command add <cmd-options> <breakpoint-id>") 117 { 118 SetHelpLong ( 119 "\nGeneral information about entering breakpoint commands \n\ 120 ------------------------------------------------------ \n\ 121 \n\ 122 This command will cause you to be prompted to enter the command or set \n\ 123 of commands you wish to be executed when the specified breakpoint is \n\ 124 hit. You will be told to enter your command(s), and will see a '> ' \n\ 125 prompt. Because you can enter one or many commands to be executed when \n\ 126 a breakpoint is hit, you will continue to be prompted after each \n\ 127 new-line that you enter, until you enter the word 'DONE', which will \n\ 128 cause the commands you have entered to be stored with the breakpoint \n\ 129 and executed when the breakpoint is hit. \n\ 130 \n\ 131 Syntax checking is not necessarily done when breakpoint commands are \n\ 132 entered. An improperly written breakpoint command will attempt to get \n\ 133 executed when the breakpoint gets hit, and usually silently fail. If \n\ 134 your breakpoint command does not appear to be getting executed, go \n\ 135 back and check your syntax. \n\ 136 \n\ 137 \n\ 138 Special information about PYTHON breakpoint commands \n\ 139 ---------------------------------------------------- \n\ 140 \n\ 141 You may enter either one line of Python or multiple lines of Python \n\ 142 (including defining whole functions, if desired). If you enter a \n\ 143 single line of Python, that will be passed to the Python interpreter \n\ 144 'as is' when the breakpoint gets hit. If you enter function \n\ 145 definitions, they will be passed to the Python interpreter as soon as \n\ 146 you finish entering the breakpoint command, and they can be called \n\ 147 later (don't forget to add calls to them, if you want them called when \n\ 148 the breakpoint is hit). If you enter multiple lines of Python that \n\ 149 are not function definitions, they will be collected into a new, \n\ 150 automatically generated Python function, and a call to the newly \n\ 151 generated function will be attached to the breakpoint. Important \n\ 152 Note: Because loose Python code gets collected into functions, if you \n\ 153 want to access global variables in the 'loose' code, you need to \n\ 154 specify that they are global, using the 'global' keyword. Be sure to \n\ 155 use correct Python syntax, including indentation, when entering Python \n\ 156 breakpoint commands. \n\ 157 \n\ 158 Example Python one-line breakpoint command: \n\ 159 \n\ 160 (lldb) breakpoint command add -p 1 \n\ 161 Enter your Python command(s). Type 'DONE' to end. \n\ 162 > print \"Hit this breakpoint!\" \n\ 163 > DONE \n\ 164 \n\ 165 Example multiple line Python breakpoint command, using function definition: \n\ 166 \n\ 167 (lldb) breakpoint command add -p 1 \n\ 168 Enter your Python command(s). Type 'DONE' to end. \n\ 169 > def breakpoint_output (bp_no): \n\ 170 > out_string = \"Hit breakpoint number \" + repr (bp_no) \n\ 171 > print out_string \n\ 172 > return True \n\ 173 > breakpoint_output (1) \n\ 174 > DONE \n\ 175 \n\ 176 \n\ 177 Example multiple line Python breakpoint command, using 'loose' Python: \n\ 178 \n\ 179 (lldb) breakpoint command add -p 1 \n\ 180 Enter your Python command(s). Type 'DONE' to end. \n\ 181 > global bp_count \n\ 182 > bp_count = bp_count + 1 \n\ 183 > print \"Hit this breakpoint \" + repr(bp_count) + \" times!\" \n\ 184 > DONE \n\ 185 \n\ 186 In this case, since there is a reference to a global variable, \n\ 187 'bp_count', you will also need to make sure 'bp_count' exists and is \n\ 188 initialized: \n\ 189 \n\ 190 (lldb) script \n\ 191 >>> bp_count = 0 \n\ 192 >>> quit() \n\ 193 \n\ 194 (lldb) \n\ 195 \n\ 196 Special information about debugger command breakpoint commands \n\ 197 -------------------------------------------------------------- \n\ 198 \n\ 199 You may enter any debugger command, exactly as you would at the \n\ 200 debugger prompt. You may enter as many debugger commands as you like, \n\ 201 but do NOT enter more than one command per line. \n" ); 202 } 203 204 CommandObjectBreakpointCommandAdd::~CommandObjectBreakpointCommandAdd () 205 { 206 } 207 208 bool 209 CommandObjectBreakpointCommandAdd::Execute 210 ( 211 CommandInterpreter &interpreter, 212 Args& command, 213 CommandReturnObject &result 214 ) 215 { 216 Target *target = interpreter.GetDebugger().GetSelectedTarget().get(); 217 218 if (target == NULL) 219 { 220 result.AppendError ("There is not a current executable; there are no breakpoints to which to add commands"); 221 result.SetStatus (eReturnStatusFailed); 222 return false; 223 } 224 225 const BreakpointList &breakpoints = target->GetBreakpointList(); 226 size_t num_breakpoints = breakpoints.GetSize(); 227 228 if (num_breakpoints == 0) 229 { 230 result.AppendError ("No breakpoints exist to have commands added"); 231 result.SetStatus (eReturnStatusFailed); 232 return false; 233 } 234 235 if (command.GetArgumentCount() == 0) 236 { 237 result.AppendError ("No breakpoint specified to which to add the commands"); 238 result.SetStatus (eReturnStatusFailed); 239 return false; 240 } 241 242 BreakpointIDList valid_bp_ids; 243 CommandObjectMultiwordBreakpoint::VerifyBreakpointIDs (command, target, result, &valid_bp_ids); 244 245 if (result.Succeeded()) 246 { 247 const size_t count = valid_bp_ids.GetSize(); 248 for (size_t i = 0; i < count; ++i) 249 { 250 BreakpointID cur_bp_id = valid_bp_ids.GetBreakpointIDAtIndex (i); 251 if (cur_bp_id.GetBreakpointID() != LLDB_INVALID_BREAK_ID) 252 { 253 Breakpoint *bp = target->GetBreakpointByID (cur_bp_id.GetBreakpointID()).get(); 254 if (cur_bp_id.GetLocationID() != LLDB_INVALID_BREAK_ID) 255 { 256 BreakpointLocationSP bp_loc_sp(bp->FindLocationByID (cur_bp_id.GetLocationID())); 257 if (bp_loc_sp) 258 { 259 if (m_options.m_use_script_language) 260 { 261 interpreter.GetScriptInterpreter()->CollectDataForBreakpointCommandCallback (interpreter, 262 bp_loc_sp->GetLocationOptions(), 263 result); 264 } 265 else 266 { 267 CollectDataForBreakpointCommandCallback (interpreter, 268 bp_loc_sp->GetLocationOptions(), 269 result); 270 } 271 } 272 } 273 else 274 { 275 if (m_options.m_use_script_language) 276 { 277 interpreter.GetScriptInterpreter()->CollectDataForBreakpointCommandCallback (interpreter, 278 bp->GetOptions(), 279 result); 280 } 281 else 282 { 283 CollectDataForBreakpointCommandCallback (interpreter, 284 bp->GetOptions(), 285 result); 286 } 287 } 288 } 289 } 290 } 291 292 return result.Succeeded(); 293 } 294 295 Options * 296 CommandObjectBreakpointCommandAdd::GetOptions () 297 { 298 return &m_options; 299 } 300 301 const char *g_reader_instructions = "Enter your debugger command(s). Type 'DONE' to end."; 302 303 void 304 CommandObjectBreakpointCommandAdd::CollectDataForBreakpointCommandCallback 305 ( 306 CommandInterpreter &interpreter, 307 BreakpointOptions *bp_options, 308 CommandReturnObject &result 309 ) 310 { 311 InputReaderSP reader_sp (new InputReader(interpreter.GetDebugger())); 312 std::auto_ptr<BreakpointOptions::CommandData> data_ap(new BreakpointOptions::CommandData()); 313 if (reader_sp && data_ap.get()) 314 { 315 BatonSP baton_sp (new BreakpointOptions::CommandBaton (data_ap.release())); 316 bp_options->SetCallback (CommandObjectBreakpointCommand::BreakpointOptionsCallbackFunction, baton_sp); 317 318 Error err (reader_sp->Initialize (CommandObjectBreakpointCommandAdd::GenerateBreakpointCommandCallback, 319 bp_options, // baton 320 eInputReaderGranularityLine, // token size, to pass to callback function 321 "DONE", // end token 322 "> ", // prompt 323 true)); // echo input 324 if (err.Success()) 325 { 326 interpreter.GetDebugger().PushInputReader (reader_sp); 327 result.SetStatus (eReturnStatusSuccessFinishNoResult); 328 } 329 else 330 { 331 result.AppendError (err.AsCString()); 332 result.SetStatus (eReturnStatusFailed); 333 } 334 } 335 else 336 { 337 result.AppendError("out of memory"); 338 result.SetStatus (eReturnStatusFailed); 339 } 340 341 } 342 343 size_t 344 CommandObjectBreakpointCommandAdd::GenerateBreakpointCommandCallback 345 ( 346 void *baton, 347 InputReader &reader, 348 lldb::InputReaderAction notification, 349 const char *bytes, 350 size_t bytes_len 351 ) 352 { 353 FILE *out_fh = reader.GetDebugger().GetOutputFileHandle(); 354 355 switch (notification) 356 { 357 case eInputReaderActivate: 358 if (out_fh) 359 { 360 ::fprintf (out_fh, "%s\n", g_reader_instructions); 361 if (reader.GetPrompt()) 362 ::fprintf (out_fh, "%s", reader.GetPrompt()); 363 } 364 break; 365 366 case eInputReaderDeactivate: 367 break; 368 369 case eInputReaderReactivate: 370 if (out_fh && reader.GetPrompt()) 371 ::fprintf (out_fh, "%s", reader.GetPrompt()); 372 break; 373 374 case eInputReaderGotToken: 375 if (bytes && bytes_len && baton) 376 { 377 BreakpointOptions *bp_options = (BreakpointOptions *) baton; 378 if (bp_options) 379 { 380 Baton *bp_options_baton = bp_options->GetBaton(); 381 if (bp_options_baton) 382 ((BreakpointOptions::CommandData *)bp_options_baton->m_data)->user_source.AppendString (bytes, bytes_len); 383 } 384 } 385 if (out_fh && !reader.IsDone() && reader.GetPrompt()) 386 ::fprintf (out_fh, "%s", reader.GetPrompt()); 387 break; 388 389 case eInputReaderDone: 390 break; 391 } 392 393 return bytes_len; 394 } 395 396 397 //------------------------------------------------------------------------- 398 // CommandObjectBreakpointCommandRemove 399 //------------------------------------------------------------------------- 400 401 CommandObjectBreakpointCommandRemove::CommandObjectBreakpointCommandRemove () : 402 CommandObject ("remove", 403 "Remove the set of commands from a breakpoint.", 404 "breakpoint command remove <breakpoint-id>") 405 { 406 } 407 408 CommandObjectBreakpointCommandRemove::~CommandObjectBreakpointCommandRemove () 409 { 410 } 411 412 bool 413 CommandObjectBreakpointCommandRemove::Execute 414 ( 415 CommandInterpreter &interpreter, 416 Args& command, 417 CommandReturnObject &result 418 ) 419 { 420 Target *target = interpreter.GetDebugger().GetSelectedTarget().get(); 421 422 if (target == NULL) 423 { 424 result.AppendError ("There is not a current executable; there are no breakpoints from which to remove commands"); 425 result.SetStatus (eReturnStatusFailed); 426 return false; 427 } 428 429 const BreakpointList &breakpoints = target->GetBreakpointList(); 430 size_t num_breakpoints = breakpoints.GetSize(); 431 432 if (num_breakpoints == 0) 433 { 434 result.AppendError ("No breakpoints exist to have commands removed"); 435 result.SetStatus (eReturnStatusFailed); 436 return false; 437 } 438 439 if (command.GetArgumentCount() == 0) 440 { 441 result.AppendError ("No breakpoint specified from which to remove the commands"); 442 result.SetStatus (eReturnStatusFailed); 443 return false; 444 } 445 446 BreakpointIDList valid_bp_ids; 447 CommandObjectMultiwordBreakpoint::VerifyBreakpointIDs (command, target, result, &valid_bp_ids); 448 449 if (result.Succeeded()) 450 { 451 const size_t count = valid_bp_ids.GetSize(); 452 for (size_t i = 0; i < count; ++i) 453 { 454 BreakpointID cur_bp_id = valid_bp_ids.GetBreakpointIDAtIndex (i); 455 if (cur_bp_id.GetBreakpointID() != LLDB_INVALID_BREAK_ID) 456 { 457 Breakpoint *bp = target->GetBreakpointByID (cur_bp_id.GetBreakpointID()).get(); 458 if (cur_bp_id.GetLocationID() != LLDB_INVALID_BREAK_ID) 459 { 460 BreakpointLocationSP bp_loc_sp (bp->FindLocationByID (cur_bp_id.GetLocationID())); 461 if (bp_loc_sp) 462 bp_loc_sp->ClearCallback(); 463 else 464 { 465 result.AppendErrorWithFormat("Invalid breakpoint ID: %u.%u.\n", 466 cur_bp_id.GetBreakpointID(), 467 cur_bp_id.GetLocationID()); 468 result.SetStatus (eReturnStatusFailed); 469 return false; 470 } 471 } 472 else 473 { 474 bp->ClearCallback(); 475 } 476 } 477 } 478 } 479 return result.Succeeded(); 480 } 481 482 483 //------------------------------------------------------------------------- 484 // CommandObjectBreakpointCommandList 485 //------------------------------------------------------------------------- 486 487 CommandObjectBreakpointCommandList::CommandObjectBreakpointCommandList () : 488 CommandObject ("List", 489 "List the script or set of commands to be executed when the breakpoint is hit.", 490 "breakpoint command list <breakpoint-id>") 491 { 492 } 493 494 CommandObjectBreakpointCommandList::~CommandObjectBreakpointCommandList () 495 { 496 } 497 498 bool 499 CommandObjectBreakpointCommandList::Execute 500 ( 501 CommandInterpreter &interpreter, 502 Args& command, 503 CommandReturnObject &result 504 ) 505 { 506 Target *target = interpreter.GetDebugger().GetSelectedTarget().get(); 507 508 if (target == NULL) 509 { 510 result.AppendError ("There is not a current executable; there are no breakpoints for which to list commands"); 511 result.SetStatus (eReturnStatusFailed); 512 return false; 513 } 514 515 const BreakpointList &breakpoints = target->GetBreakpointList(); 516 size_t num_breakpoints = breakpoints.GetSize(); 517 518 if (num_breakpoints == 0) 519 { 520 result.AppendError ("No breakpoints exist for which to list commands"); 521 result.SetStatus (eReturnStatusFailed); 522 return false; 523 } 524 525 if (command.GetArgumentCount() == 0) 526 { 527 result.AppendError ("No breakpoint specified for which to list the commands"); 528 result.SetStatus (eReturnStatusFailed); 529 return false; 530 } 531 532 BreakpointIDList valid_bp_ids; 533 CommandObjectMultiwordBreakpoint::VerifyBreakpointIDs (command, target, result, &valid_bp_ids); 534 535 if (result.Succeeded()) 536 { 537 const size_t count = valid_bp_ids.GetSize(); 538 for (size_t i = 0; i < count; ++i) 539 { 540 BreakpointID cur_bp_id = valid_bp_ids.GetBreakpointIDAtIndex (i); 541 if (cur_bp_id.GetBreakpointID() != LLDB_INVALID_BREAK_ID) 542 { 543 Breakpoint *bp = target->GetBreakpointByID (cur_bp_id.GetBreakpointID()).get(); 544 545 if (bp) 546 { 547 const BreakpointOptions *bp_options = NULL; 548 if (cur_bp_id.GetLocationID() != LLDB_INVALID_BREAK_ID) 549 { 550 BreakpointLocationSP bp_loc_sp(bp->FindLocationByID (cur_bp_id.GetLocationID())); 551 if (bp_loc_sp) 552 bp_options = bp_loc_sp->GetOptionsNoCreate(); 553 else 554 { 555 result.AppendErrorWithFormat("Invalid breakpoint ID: %u.%u.\n", 556 cur_bp_id.GetBreakpointID(), 557 cur_bp_id.GetLocationID()); 558 result.SetStatus (eReturnStatusFailed); 559 return false; 560 } 561 } 562 else 563 { 564 bp_options = bp->GetOptions(); 565 } 566 567 if (bp_options) 568 { 569 StreamString id_str; 570 BreakpointID::GetCanonicalReference (&id_str, cur_bp_id.GetBreakpointID(), cur_bp_id.GetLocationID()); 571 const Baton *baton = bp_options->GetBaton(); 572 if (baton) 573 { 574 result.GetOutputStream().Printf ("Breakpoint %s:\n", id_str.GetData()); 575 result.GetOutputStream().IndentMore (); 576 baton->GetDescription(&result.GetOutputStream(), eDescriptionLevelFull); 577 result.GetOutputStream().IndentLess (); 578 } 579 else 580 { 581 result.AppendMessageWithFormat ("Breakpoint %s does not have an associated command.\n", id_str.GetData()); 582 } 583 } 584 result.SetStatus (eReturnStatusSuccessFinishResult); 585 } 586 else 587 { 588 result.AppendErrorWithFormat("Invalid breakpoint ID: %u.\n", cur_bp_id.GetBreakpointID()); 589 result.SetStatus (eReturnStatusFailed); 590 } 591 592 } 593 } 594 } 595 596 return result.Succeeded(); 597 } 598 599 //------------------------------------------------------------------------- 600 // CommandObjectBreakpointCommand 601 //------------------------------------------------------------------------- 602 603 CommandObjectBreakpointCommand::CommandObjectBreakpointCommand (CommandInterpreter &interpreter) : 604 CommandObjectMultiword ("command", 605 "A set of commands for adding, removing and examining bits of code to be executed when the breakpoint is hit (breakpoint 'commmands').", 606 "command <sub-command> [<sub-command-options>] <breakpoint-id>") 607 { 608 bool status; 609 CommandObjectSP add_command_object (new CommandObjectBreakpointCommandAdd ()); 610 CommandObjectSP remove_command_object (new CommandObjectBreakpointCommandRemove ()); 611 CommandObjectSP list_command_object (new CommandObjectBreakpointCommandList ()); 612 613 add_command_object->SetCommandName ("breakpoint command add"); 614 remove_command_object->SetCommandName ("breakpoint command remove"); 615 list_command_object->SetCommandName ("breakpoint command list"); 616 617 status = LoadSubCommand (interpreter, "add", add_command_object); 618 status = LoadSubCommand (interpreter, "remove", remove_command_object); 619 status = LoadSubCommand (interpreter, "list", list_command_object); 620 } 621 622 623 CommandObjectBreakpointCommand::~CommandObjectBreakpointCommand () 624 { 625 } 626 627 bool 628 CommandObjectBreakpointCommand::BreakpointOptionsCallbackFunction 629 ( 630 void *baton, 631 StoppointCallbackContext *context, 632 lldb::user_id_t break_id, 633 lldb::user_id_t break_loc_id 634 ) 635 { 636 bool ret_value = true; 637 if (baton == NULL) 638 return true; 639 640 641 BreakpointOptions::CommandData *data = (BreakpointOptions::CommandData *) baton; 642 StringList &commands = data->user_source; 643 644 if (commands.GetSize() > 0) 645 { 646 uint32_t num_commands = commands.GetSize(); 647 CommandReturnObject result; 648 if (context->exe_ctx.target) 649 { 650 651 Debugger &debugger = context->exe_ctx.target->GetDebugger(); 652 CommandInterpreter &interpreter = debugger.GetCommandInterpreter(); 653 654 FILE *out_fh = debugger.GetOutputFileHandle(); 655 FILE *err_fh = debugger.GetErrorFileHandle(); 656 657 uint32_t i; 658 for (i = 0; i < num_commands; ++i) 659 { 660 661 // First time through we use the context from the stoppoint, after that we use whatever 662 // has been set by the previous command. 663 664 if (!interpreter.HandleCommand (commands.GetStringAtIndex(i), false, result, &context->exe_ctx)) 665 break; 666 667 // FIXME: This isn't really the right way to do this. We should be able to peek at the public 668 // to see if there is any new events, but that is racey, since the internal process thread has to run and 669 // deliver the event to the public queue before a run will show up. So for now we check 670 // the internal thread state. 671 672 lldb::StateType internal_state = context->exe_ctx.process->GetPrivateState(); 673 if (internal_state != eStateStopped) 674 { 675 if (i < num_commands - 1) 676 { 677 if (out_fh) 678 ::fprintf (out_fh, "Short-circuiting command execution because target state changed to %s." 679 " last command: \"%s\"\n", StateAsCString(internal_state), 680 commands.GetStringAtIndex(i)); 681 } 682 break; 683 } 684 685 if (out_fh) 686 ::fprintf (out_fh, "%s", result.GetErrorStream().GetData()); 687 if (err_fh) 688 ::fprintf (err_fh, "%s", result.GetOutputStream().GetData()); 689 result.Clear(); 690 result.SetStatus (eReturnStatusSuccessFinishNoResult); 691 } 692 693 if (err_fh && !result.Succeeded() && i < num_commands) 694 ::fprintf (err_fh, "Attempt to execute '%s' failed.\n", commands.GetStringAtIndex(i)); 695 696 if (out_fh) 697 ::fprintf (out_fh, "%s", result.GetErrorStream().GetData()); 698 699 if (err_fh) 700 ::fprintf (err_fh, "%s", result.GetOutputStream().GetData()); 701 } 702 } 703 return ret_value; 704 } 705 706