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