blob: dc1c84b0e6493c506d5f43a5892fb6321a0f63f9 [file] [log] [blame]
Chris Lattner24943d22010-06-08 16:52:24 +00001//===-- 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
27using namespace lldb;
28using namespace lldb_private;
29
30//-------------------------------------------------------------------------
31// CommandObjectBreakpointCommandAdd::CommandOptions
32//-------------------------------------------------------------------------
33
34CommandObjectBreakpointCommandAdd::CommandOptions::CommandOptions () :
35 Options ()
36{
37 BuildValidOptionSets();
38}
39
40CommandObjectBreakpointCommandAdd::CommandOptions::~CommandOptions ()
41{
42}
43
44lldb::OptionDefinition
45CommandObjectBreakpointCommandAdd::CommandOptions::g_option_table[] =
46{
Jim Ingham34e9a982010-06-15 18:47:14 +000047 { LLDB_OPT_SET_1, true, "script", 's', no_argument, NULL, 0, NULL,
Chris Lattner24943d22010-06-08 16:52:24 +000048 "Write the breakpoint command script in the default scripting language."},
49
Jim Ingham34e9a982010-06-15 18:47:14 +000050 { LLDB_OPT_SET_2, true, "python", 'p', no_argument, NULL, 0, NULL,
Chris Lattner24943d22010-06-08 16:52:24 +000051 "Write the breakpoint command script in the Python scripting language."},
52
Jim Ingham34e9a982010-06-15 18:47:14 +000053 { LLDB_OPT_SET_3, true, "commands", 'c', no_argument, NULL, 0, NULL,
Chris Lattner24943d22010-06-08 16:52:24 +000054 "Write the breakpoint command script using the command line commands."},
55
56 { 0, false, NULL, 0, 0, NULL, 0, NULL, NULL }
57};
58
59const lldb::OptionDefinition*
60CommandObjectBreakpointCommandAdd::CommandOptions::GetDefinitions ()
61{
62 return g_option_table;
63}
64
65
66Error
67CommandObjectBreakpointCommandAdd::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
99void
100CommandObjectBreakpointCommandAdd::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
114CommandObjectBreakpointCommandAdd::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\
123This command will cause you to be prompted to enter the command or set \n\
124of commands you wish to be executed when the specified breakpoint is \n\
125hit. You will be told to enter your command(s), and will see a '> ' \n\
126prompt. Because you can enter one or many commands to be executed when \n\
127a breakpoint is hit, you will continue to be prompted after each \n\
128new-line that you enter, until you enter the word 'DONE', which will \n\
129cause the commands you have entered to be stored with the breakpoint \n\
130and executed when the breakpoint is hit. \n\
131 \n\
132Syntax checking is not necessarily done when breakpoint commands are \n\
133entered. An improperly written breakpoint command will attempt to get \n\
134executed when the breakpoint gets hit, and usually silently fail. If \n\
135your breakpoint command does not appear to be getting executed, go \n\
136back and check your syntax. \n\
137 \n\
138 \n\
139Special information about PYTHON breakpoint commands \n\
140---------------------------------------------------- \n\
141 \n\
142You 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\
144single 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\
146definitions, they will be passed to the Python interpreter as soon as \n\
147you finish entering the breakpoint command, and they can be called \n\
148later (don't forget to add calls to them, if you want them called when \n\
149the breakpoint is hit). If you enter multiple lines of Python that \n\
150are not function definitions, they will be collected into a new, \n\
151automatically generated Python function, and a call to the newly \n\
152generated function will be attached to the breakpoint. Important \n\
153Note: Because loose Python code gets collected into functions, if you \n\
154want to access global variables in the 'loose' code, you need to \n\
155specify that they are global, using the 'global' keyword. Be sure to \n\
156use correct Python syntax, including indentation, when entering Python \n\
157breakpoint commands. \n\
158 \n\
159Example Python one-line breakpoint command: \n\
160 \n\
161(lldb) breakpoint command add -p 1 \n\
162Enter your Python command(s). Type 'DONE' to end. \n\
163> print \"Hit this breakpoint!\" \n\
164> DONE \n\
165 \n\
166Example multiple line Python breakpoint command, using function definition: \n\
167 \n\
168(lldb) breakpoint command add -p 1 \n\
169Enter 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\
178Example multiple line Python breakpoint command, using 'loose' Python: \n\
179 \n\
180(lldb) breakpoint command add -p 1 \n\
181Enter 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\
187In 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\
189initialized: \n\
190 \n\
191(lldb) script \n\
192>>> bp_count = 0 \n\
193>>> quit() \n\
194 \n\
195(lldb) \n\
196 \n\
197Special information debugger command breakpoint commands \n\
198--------------------------------------------------------- \n\
199 \n\
200You may enter any debugger command, exactly as you would at the \n\
201debugger prompt. You may enter as many debugger commands as you like, \n\
202but do NOT enter more than one command per line. \n" );
203}
204
205CommandObjectBreakpointCommandAdd::~CommandObjectBreakpointCommandAdd ()
206{
207}
208
209bool
210CommandObjectBreakpointCommandAdd::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
290Options *
291CommandObjectBreakpointCommandAdd::GetOptions ()
292{
293 return &m_options;
294}
295
296const char *g_reader_instructions = "Enter your debugger command(s). Type 'DONE' to end.";
297
298void
299CommandObjectBreakpointCommandAdd::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
337size_t
338CommandObjectBreakpointCommandAdd::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
395CommandObjectBreakpointCommandRemove::CommandObjectBreakpointCommandRemove () :
396 CommandObject ("remove",
397 "Remove the set of commands from a breakpoint.",
398 "breakpoint command remove <breakpoint-id>")
399{
400}
401
402CommandObjectBreakpointCommandRemove::~CommandObjectBreakpointCommandRemove ()
403{
404}
405
406bool
407CommandObjectBreakpointCommandRemove::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
478CommandObjectBreakpointCommandList::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
485CommandObjectBreakpointCommandList::~CommandObjectBreakpointCommandList ()
486{
487}
488
489bool
490CommandObjectBreakpointCommandList::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
591CommandObjectBreakpointCommand::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
611CommandObjectBreakpointCommand::~CommandObjectBreakpointCommand ()
612{
613}
614
615bool
616CommandObjectBreakpointCommand::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