1 //===-- CommandObjectMemory.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 #include "lldb/lldb-python.h"
11
12 #include "CommandObjectMemory.h"
13
14 // C Includes
15 // C++ Includes
16 // Other libraries and framework includes
17 // Project includes
18 #include "lldb/Core/DataBufferHeap.h"
19 #include "lldb/Core/DataExtractor.h"
20 #include "lldb/Core/Debugger.h"
21 #include "lldb/Core/Module.h"
22 #include "lldb/Core/StreamString.h"
23 #include "lldb/Core/ValueObjectMemory.h"
24 #include "lldb/Interpreter/Args.h"
25 #include "lldb/Interpreter/CommandReturnObject.h"
26 #include "lldb/Interpreter/CommandInterpreter.h"
27 #include "lldb/Interpreter/Options.h"
28 #include "lldb/Interpreter/OptionGroupFormat.h"
29 #include "lldb/Interpreter/OptionGroupOutputFile.h"
30 #include "lldb/Interpreter/OptionGroupValueObjectDisplay.h"
31 #include "lldb/Interpreter/OptionValueString.h"
32 #include "lldb/Symbol/TypeList.h"
33 #include "lldb/Target/Process.h"
34 #include "lldb/Target/StackFrame.h"
35
36 using namespace lldb;
37 using namespace lldb_private;
38
39 static OptionDefinition
40 g_option_table[] =
41 {
42 { LLDB_OPT_SET_1, false, "num-per-line" ,'l', required_argument, NULL, 0, eArgTypeNumberPerLine ,"The number of items per line to display."},
43 { LLDB_OPT_SET_2, false, "binary" ,'b', no_argument , NULL, 0, eArgTypeNone ,"If true, memory will be saved as binary. If false, the memory is saved save as an ASCII dump that uses the format, size, count and number per line settings."},
44 { LLDB_OPT_SET_3, true , "type" ,'t', required_argument, NULL, 0, eArgTypeNone ,"The name of a type to view memory as."},
45 { LLDB_OPT_SET_1|
46 LLDB_OPT_SET_2|
47 LLDB_OPT_SET_3, false, "force" ,'r', no_argument, NULL, 0, eArgTypeNone ,"Necessary if reading over target.max-memory-read-size bytes."},
48 };
49
50
51
52 class OptionGroupReadMemory : public OptionGroup
53 {
54 public:
55
OptionGroupReadMemory()56 OptionGroupReadMemory () :
57 m_num_per_line (1,1),
58 m_output_as_binary (false),
59 m_view_as_type()
60 {
61 }
62
63 virtual
~OptionGroupReadMemory()64 ~OptionGroupReadMemory ()
65 {
66 }
67
68
69 virtual uint32_t
GetNumDefinitions()70 GetNumDefinitions ()
71 {
72 return sizeof (g_option_table) / sizeof (OptionDefinition);
73 }
74
75 virtual const OptionDefinition*
GetDefinitions()76 GetDefinitions ()
77 {
78 return g_option_table;
79 }
80
81 virtual Error
SetOptionValue(CommandInterpreter & interpreter,uint32_t option_idx,const char * option_arg)82 SetOptionValue (CommandInterpreter &interpreter,
83 uint32_t option_idx,
84 const char *option_arg)
85 {
86 Error error;
87 const int short_option = g_option_table[option_idx].short_option;
88
89 switch (short_option)
90 {
91 case 'l':
92 error = m_num_per_line.SetValueFromCString (option_arg);
93 if (m_num_per_line.GetCurrentValue() == 0)
94 error.SetErrorStringWithFormat("invalid value for --num-per-line option '%s'", option_arg);
95 break;
96
97 case 'b':
98 m_output_as_binary = true;
99 break;
100
101 case 't':
102 error = m_view_as_type.SetValueFromCString (option_arg);
103 break;
104
105 case 'r':
106 m_force = true;
107 break;
108
109 default:
110 error.SetErrorStringWithFormat("unrecognized short option '%c'", short_option);
111 break;
112 }
113 return error;
114 }
115
116 virtual void
OptionParsingStarting(CommandInterpreter & interpreter)117 OptionParsingStarting (CommandInterpreter &interpreter)
118 {
119 m_num_per_line.Clear();
120 m_output_as_binary = false;
121 m_view_as_type.Clear();
122 m_force = false;
123 }
124
125 Error
FinalizeSettings(Target * target,OptionGroupFormat & format_options)126 FinalizeSettings (Target *target, OptionGroupFormat& format_options)
127 {
128 Error error;
129 OptionValueUInt64 &byte_size_value = format_options.GetByteSizeValue();
130 OptionValueUInt64 &count_value = format_options.GetCountValue();
131 const bool byte_size_option_set = byte_size_value.OptionWasSet();
132 const bool num_per_line_option_set = m_num_per_line.OptionWasSet();
133 const bool count_option_set = format_options.GetCountValue().OptionWasSet();
134
135 switch (format_options.GetFormat())
136 {
137 default:
138 break;
139
140 case eFormatBoolean:
141 if (!byte_size_option_set)
142 byte_size_value = 1;
143 if (!num_per_line_option_set)
144 m_num_per_line = 1;
145 if (!count_option_set)
146 format_options.GetCountValue() = 8;
147 break;
148
149 case eFormatCString:
150 break;
151
152 case eFormatInstruction:
153 if (count_option_set)
154 byte_size_value = target->GetArchitecture().GetMaximumOpcodeByteSize();
155 m_num_per_line = 1;
156 break;
157
158 case eFormatAddressInfo:
159 if (!byte_size_option_set)
160 byte_size_value = target->GetArchitecture().GetAddressByteSize();
161 m_num_per_line = 1;
162 if (!count_option_set)
163 format_options.GetCountValue() = 8;
164 break;
165
166 case eFormatPointer:
167 byte_size_value = target->GetArchitecture().GetAddressByteSize();
168 if (!num_per_line_option_set)
169 m_num_per_line = 4;
170 if (!count_option_set)
171 format_options.GetCountValue() = 8;
172 break;
173
174 case eFormatBinary:
175 case eFormatFloat:
176 case eFormatOctal:
177 case eFormatDecimal:
178 case eFormatEnum:
179 case eFormatUnicode16:
180 case eFormatUnicode32:
181 case eFormatUnsigned:
182 case eFormatHexFloat:
183 if (!byte_size_option_set)
184 byte_size_value = 4;
185 if (!num_per_line_option_set)
186 m_num_per_line = 1;
187 if (!count_option_set)
188 format_options.GetCountValue() = 8;
189 break;
190
191 case eFormatBytes:
192 case eFormatBytesWithASCII:
193 if (byte_size_option_set)
194 {
195 if (byte_size_value > 1)
196 error.SetErrorStringWithFormat ("display format (bytes/bytes with ascii) conflicts with the specified byte size %" PRIu64 "\n"
197 "\tconsider using a different display format or don't specify the byte size",
198 byte_size_value.GetCurrentValue());
199 }
200 else
201 byte_size_value = 1;
202 if (!num_per_line_option_set)
203 m_num_per_line = 16;
204 if (!count_option_set)
205 format_options.GetCountValue() = 32;
206 break;
207 case eFormatCharArray:
208 case eFormatChar:
209 case eFormatCharPrintable:
210 if (!byte_size_option_set)
211 byte_size_value = 1;
212 if (!num_per_line_option_set)
213 m_num_per_line = 32;
214 if (!count_option_set)
215 format_options.GetCountValue() = 64;
216 break;
217 case eFormatComplex:
218 if (!byte_size_option_set)
219 byte_size_value = 8;
220 if (!num_per_line_option_set)
221 m_num_per_line = 1;
222 if (!count_option_set)
223 format_options.GetCountValue() = 8;
224 break;
225 case eFormatComplexInteger:
226 if (!byte_size_option_set)
227 byte_size_value = 8;
228 if (!num_per_line_option_set)
229 m_num_per_line = 1;
230 if (!count_option_set)
231 format_options.GetCountValue() = 8;
232 break;
233 case eFormatHex:
234 if (!byte_size_option_set)
235 byte_size_value = 4;
236 if (!num_per_line_option_set)
237 {
238 switch (byte_size_value)
239 {
240 case 1:
241 case 2:
242 m_num_per_line = 8;
243 break;
244 case 4:
245 m_num_per_line = 4;
246 break;
247 case 8:
248 m_num_per_line = 2;
249 break;
250 default:
251 m_num_per_line = 1;
252 break;
253 }
254 }
255 if (!count_option_set)
256 count_value = 8;
257 break;
258
259 case eFormatVectorOfChar:
260 case eFormatVectorOfSInt8:
261 case eFormatVectorOfUInt8:
262 case eFormatVectorOfSInt16:
263 case eFormatVectorOfUInt16:
264 case eFormatVectorOfSInt32:
265 case eFormatVectorOfUInt32:
266 case eFormatVectorOfSInt64:
267 case eFormatVectorOfUInt64:
268 case eFormatVectorOfFloat32:
269 case eFormatVectorOfFloat64:
270 case eFormatVectorOfUInt128:
271 if (!byte_size_option_set)
272 byte_size_value = 128;
273 if (!num_per_line_option_set)
274 m_num_per_line = 1;
275 if (!count_option_set)
276 count_value = 4;
277 break;
278 }
279 return error;
280 }
281
282 bool
AnyOptionWasSet() const283 AnyOptionWasSet () const
284 {
285 return m_num_per_line.OptionWasSet() ||
286 m_output_as_binary ||
287 m_view_as_type.OptionWasSet();
288 }
289
290 OptionValueUInt64 m_num_per_line;
291 bool m_output_as_binary;
292 OptionValueString m_view_as_type;
293 bool m_force;
294 };
295
296
297
298 //----------------------------------------------------------------------
299 // Read memory from the inferior process
300 //----------------------------------------------------------------------
301 class CommandObjectMemoryRead : public CommandObjectParsed
302 {
303 public:
304
CommandObjectMemoryRead(CommandInterpreter & interpreter)305 CommandObjectMemoryRead (CommandInterpreter &interpreter) :
306 CommandObjectParsed (interpreter,
307 "memory read",
308 "Read from the memory of the process being debugged.",
309 NULL,
310 eFlagRequiresTarget | eFlagProcessMustBePaused),
311 m_option_group (interpreter),
312 m_format_options (eFormatBytesWithASCII, 1, 8),
313 m_memory_options (),
314 m_outfile_options (),
315 m_varobj_options(),
316 m_next_addr(LLDB_INVALID_ADDRESS),
317 m_prev_byte_size(0),
318 m_prev_format_options (eFormatBytesWithASCII, 1, 8),
319 m_prev_memory_options (),
320 m_prev_outfile_options (),
321 m_prev_varobj_options()
322 {
323 CommandArgumentEntry arg1;
324 CommandArgumentEntry arg2;
325 CommandArgumentData start_addr_arg;
326 CommandArgumentData end_addr_arg;
327
328 // Define the first (and only) variant of this arg.
329 start_addr_arg.arg_type = eArgTypeAddressOrExpression;
330 start_addr_arg.arg_repetition = eArgRepeatPlain;
331
332 // There is only one variant this argument could be; put it into the argument entry.
333 arg1.push_back (start_addr_arg);
334
335 // Define the first (and only) variant of this arg.
336 end_addr_arg.arg_type = eArgTypeAddressOrExpression;
337 end_addr_arg.arg_repetition = eArgRepeatOptional;
338
339 // There is only one variant this argument could be; put it into the argument entry.
340 arg2.push_back (end_addr_arg);
341
342 // Push the data for the first argument into the m_arguments vector.
343 m_arguments.push_back (arg1);
344 m_arguments.push_back (arg2);
345
346 // Add the "--format" and "--count" options to group 1 and 3
347 m_option_group.Append (&m_format_options,
348 OptionGroupFormat::OPTION_GROUP_FORMAT | OptionGroupFormat::OPTION_GROUP_COUNT,
349 LLDB_OPT_SET_1 | LLDB_OPT_SET_2 | LLDB_OPT_SET_3);
350 m_option_group.Append (&m_format_options,
351 OptionGroupFormat::OPTION_GROUP_GDB_FMT,
352 LLDB_OPT_SET_1 | LLDB_OPT_SET_3);
353 // Add the "--size" option to group 1 and 2
354 m_option_group.Append (&m_format_options,
355 OptionGroupFormat::OPTION_GROUP_SIZE,
356 LLDB_OPT_SET_1 | LLDB_OPT_SET_2);
357 m_option_group.Append (&m_memory_options);
358 m_option_group.Append (&m_outfile_options, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1 | LLDB_OPT_SET_2 | LLDB_OPT_SET_3);
359 m_option_group.Append (&m_varobj_options, LLDB_OPT_SET_ALL, LLDB_OPT_SET_3);
360 m_option_group.Finalize();
361 }
362
363 virtual
~CommandObjectMemoryRead()364 ~CommandObjectMemoryRead ()
365 {
366 }
367
368 Options *
GetOptions()369 GetOptions ()
370 {
371 return &m_option_group;
372 }
373
GetRepeatCommand(Args & current_command_args,uint32_t index)374 virtual const char *GetRepeatCommand (Args ¤t_command_args, uint32_t index)
375 {
376 return m_cmd_name.c_str();
377 }
378
379 protected:
380 virtual bool
DoExecute(Args & command,CommandReturnObject & result)381 DoExecute (Args& command, CommandReturnObject &result)
382 {
383 // No need to check "target" for validity as eFlagRequiresTarget ensures it is valid
384 Target *target = m_exe_ctx.GetTargetPtr();
385
386 const size_t argc = command.GetArgumentCount();
387
388 if ((argc == 0 && m_next_addr == LLDB_INVALID_ADDRESS) || argc > 2)
389 {
390 result.AppendErrorWithFormat ("%s takes a start address expression with an optional end address expression.\n", m_cmd_name.c_str());
391 result.AppendRawWarning("Expressions should be quoted if they contain spaces or other special characters.\n");
392 result.SetStatus(eReturnStatusFailed);
393 return false;
394 }
395
396 ClangASTType clang_ast_type;
397 Error error;
398
399 const char *view_as_type_cstr = m_memory_options.m_view_as_type.GetCurrentValue();
400 if (view_as_type_cstr && view_as_type_cstr[0])
401 {
402 // We are viewing memory as a type
403
404 SymbolContext sc;
405 const bool exact_match = false;
406 TypeList type_list;
407 uint32_t reference_count = 0;
408 uint32_t pointer_count = 0;
409 size_t idx;
410
411 #define ALL_KEYWORDS \
412 KEYWORD("const") \
413 KEYWORD("volatile") \
414 KEYWORD("restrict") \
415 KEYWORD("struct") \
416 KEYWORD("class") \
417 KEYWORD("union")
418
419 #define KEYWORD(s) s,
420 static const char *g_keywords[] =
421 {
422 ALL_KEYWORDS
423 };
424 #undef KEYWORD
425
426 #define KEYWORD(s) (sizeof(s) - 1),
427 static const int g_keyword_lengths[] =
428 {
429 ALL_KEYWORDS
430 };
431 #undef KEYWORD
432
433 #undef ALL_KEYWORDS
434
435 static size_t g_num_keywords = sizeof(g_keywords) / sizeof(const char *);
436 std::string type_str(view_as_type_cstr);
437
438 // Remove all instances of g_keywords that are followed by spaces
439 for (size_t i = 0; i < g_num_keywords; ++i)
440 {
441 const char *keyword = g_keywords[i];
442 int keyword_len = g_keyword_lengths[i];
443
444 idx = 0;
445 while ((idx = type_str.find (keyword, idx)) != std::string::npos)
446 {
447 if (type_str[idx + keyword_len] == ' ' || type_str[idx + keyword_len] == '\t')
448 {
449 type_str.erase(idx, keyword_len+1);
450 idx = 0;
451 }
452 else
453 {
454 idx += keyword_len;
455 }
456 }
457 }
458 bool done = type_str.empty();
459 //
460 idx = type_str.find_first_not_of (" \t");
461 if (idx > 0 && idx != std::string::npos)
462 type_str.erase (0, idx);
463 while (!done)
464 {
465 // Strip trailing spaces
466 if (type_str.empty())
467 done = true;
468 else
469 {
470 switch (type_str[type_str.size()-1])
471 {
472 case '*':
473 ++pointer_count;
474 // fall through...
475 case ' ':
476 case '\t':
477 type_str.erase(type_str.size()-1);
478 break;
479
480 case '&':
481 if (reference_count == 0)
482 {
483 reference_count = 1;
484 type_str.erase(type_str.size()-1);
485 }
486 else
487 {
488 result.AppendErrorWithFormat ("invalid type string: '%s'\n", view_as_type_cstr);
489 result.SetStatus(eReturnStatusFailed);
490 return false;
491 }
492 break;
493
494 default:
495 done = true;
496 break;
497 }
498 }
499 }
500
501 ConstString lookup_type_name(type_str.c_str());
502 StackFrame *frame = m_exe_ctx.GetFramePtr();
503 if (frame)
504 {
505 sc = frame->GetSymbolContext (eSymbolContextModule);
506 if (sc.module_sp)
507 {
508 sc.module_sp->FindTypes (sc,
509 lookup_type_name,
510 exact_match,
511 1,
512 type_list);
513 }
514 }
515 if (type_list.GetSize() == 0)
516 {
517 target->GetImages().FindTypes (sc,
518 lookup_type_name,
519 exact_match,
520 1,
521 type_list);
522 }
523
524 if (type_list.GetSize() == 0 && lookup_type_name.GetCString() && *lookup_type_name.GetCString() == '$')
525 {
526 clang::TypeDecl *tdecl = target->GetPersistentVariables().GetPersistentType(ConstString(lookup_type_name));
527 if (tdecl)
528 {
529 clang_ast_type.SetClangType(&tdecl->getASTContext(),(lldb::clang_type_t)tdecl->getTypeForDecl());
530 }
531 }
532
533 if (clang_ast_type.IsValid() == false)
534 {
535 if (type_list.GetSize() == 0)
536 {
537 result.AppendErrorWithFormat ("unable to find any types that match the raw type '%s' for full type '%s'\n",
538 lookup_type_name.GetCString(),
539 view_as_type_cstr);
540 result.SetStatus(eReturnStatusFailed);
541 return false;
542 }
543 else
544 {
545 TypeSP type_sp (type_list.GetTypeAtIndex(0));
546 clang_ast_type = type_sp->GetClangFullType();
547 }
548 }
549
550 while (pointer_count > 0)
551 {
552 ClangASTType pointer_type = clang_ast_type.GetPointerType();
553 if (pointer_type.IsValid())
554 clang_ast_type = pointer_type;
555 else
556 {
557 result.AppendError ("unable make a pointer type\n");
558 result.SetStatus(eReturnStatusFailed);
559 return false;
560 }
561 --pointer_count;
562 }
563
564 m_format_options.GetByteSizeValue() = clang_ast_type.GetByteSize();
565
566 if (m_format_options.GetByteSizeValue() == 0)
567 {
568 result.AppendErrorWithFormat ("unable to get the byte size of the type '%s'\n",
569 view_as_type_cstr);
570 result.SetStatus(eReturnStatusFailed);
571 return false;
572 }
573
574 if (!m_format_options.GetCountValue().OptionWasSet())
575 m_format_options.GetCountValue() = 1;
576 }
577 else
578 {
579 error = m_memory_options.FinalizeSettings (target, m_format_options);
580 }
581
582 // Look for invalid combinations of settings
583 if (error.Fail())
584 {
585 result.AppendError(error.AsCString());
586 result.SetStatus(eReturnStatusFailed);
587 return false;
588 }
589
590 lldb::addr_t addr;
591 size_t total_byte_size = 0;
592 if (argc == 0)
593 {
594 // Use the last address and byte size and all options as they were
595 // if no options have been set
596 addr = m_next_addr;
597 total_byte_size = m_prev_byte_size;
598 clang_ast_type = m_prev_clang_ast_type;
599 if (!m_format_options.AnyOptionWasSet() &&
600 !m_memory_options.AnyOptionWasSet() &&
601 !m_outfile_options.AnyOptionWasSet() &&
602 !m_varobj_options.AnyOptionWasSet())
603 {
604 m_format_options = m_prev_format_options;
605 m_memory_options = m_prev_memory_options;
606 m_outfile_options = m_prev_outfile_options;
607 m_varobj_options = m_prev_varobj_options;
608 }
609 }
610
611 size_t item_count = m_format_options.GetCountValue().GetCurrentValue();
612 size_t item_byte_size = m_format_options.GetByteSizeValue().GetCurrentValue();
613 const size_t num_per_line = m_memory_options.m_num_per_line.GetCurrentValue();
614
615 if (total_byte_size == 0)
616 {
617 total_byte_size = item_count * item_byte_size;
618 if (total_byte_size == 0)
619 total_byte_size = 32;
620 }
621
622 if (argc > 0)
623 addr = Args::StringToAddress(&m_exe_ctx, command.GetArgumentAtIndex(0), LLDB_INVALID_ADDRESS, &error);
624
625 if (addr == LLDB_INVALID_ADDRESS)
626 {
627 result.AppendError("invalid start address expression.");
628 result.AppendError(error.AsCString());
629 result.SetStatus(eReturnStatusFailed);
630 return false;
631 }
632
633 if (argc == 2)
634 {
635 lldb::addr_t end_addr = Args::StringToAddress(&m_exe_ctx, command.GetArgumentAtIndex(1), LLDB_INVALID_ADDRESS, 0);
636 if (end_addr == LLDB_INVALID_ADDRESS)
637 {
638 result.AppendError("invalid end address expression.");
639 result.AppendError(error.AsCString());
640 result.SetStatus(eReturnStatusFailed);
641 return false;
642 }
643 else if (end_addr <= addr)
644 {
645 result.AppendErrorWithFormat("end address (0x%" PRIx64 ") must be greater that the start address (0x%" PRIx64 ").\n", end_addr, addr);
646 result.SetStatus(eReturnStatusFailed);
647 return false;
648 }
649 else if (m_format_options.GetCountValue().OptionWasSet())
650 {
651 result.AppendErrorWithFormat("specify either the end address (0x%" PRIx64 ") or the count (--count %lu), not both.\n", end_addr, item_count);
652 result.SetStatus(eReturnStatusFailed);
653 return false;
654 }
655
656 total_byte_size = end_addr - addr;
657 item_count = total_byte_size / item_byte_size;
658 }
659
660 uint32_t max_unforced_size = target->GetMaximumMemReadSize();
661
662 if (total_byte_size > max_unforced_size && !m_memory_options.m_force)
663 {
664 result.AppendErrorWithFormat("Normally, \'memory read\' will not read over %" PRIu32 " bytes of data.\n",max_unforced_size);
665 result.AppendErrorWithFormat("Please use --force to override this restriction just once.\n");
666 result.AppendErrorWithFormat("or set target.max-memory-read-size if you will often need a larger limit.\n");
667 return false;
668 }
669
670 DataBufferSP data_sp;
671 size_t bytes_read = 0;
672 if (clang_ast_type.GetOpaqueQualType())
673 {
674 // Make sure we don't display our type as ASCII bytes like the default memory read
675 if (m_format_options.GetFormatValue().OptionWasSet() == false)
676 m_format_options.GetFormatValue().SetCurrentValue(eFormatDefault);
677
678 bytes_read = clang_ast_type.GetByteSize() * m_format_options.GetCountValue().GetCurrentValue();
679 }
680 else if (m_format_options.GetFormatValue().GetCurrentValue() != eFormatCString)
681 {
682 data_sp.reset (new DataBufferHeap (total_byte_size, '\0'));
683 if (data_sp->GetBytes() == NULL)
684 {
685 result.AppendErrorWithFormat ("can't allocate 0x%zx bytes for the memory read buffer, specify a smaller size to read", total_byte_size);
686 result.SetStatus(eReturnStatusFailed);
687 return false;
688 }
689
690 Address address(addr, NULL);
691 bytes_read = target->ReadMemory(address, false, data_sp->GetBytes (), data_sp->GetByteSize(), error);
692 if (bytes_read == 0)
693 {
694 const char *error_cstr = error.AsCString();
695 if (error_cstr && error_cstr[0])
696 {
697 result.AppendError(error_cstr);
698 }
699 else
700 {
701 result.AppendErrorWithFormat("failed to read memory from 0x%" PRIx64 ".\n", addr);
702 }
703 result.SetStatus(eReturnStatusFailed);
704 return false;
705 }
706
707 if (bytes_read < total_byte_size)
708 result.AppendWarningWithFormat("Not all bytes (%lu/%lu) were able to be read from 0x%" PRIx64 ".\n", bytes_read, total_byte_size, addr);
709 }
710 else
711 {
712 // we treat c-strings as a special case because they do not have a fixed size
713 if (m_format_options.GetByteSizeValue().OptionWasSet() && !m_format_options.HasGDBFormat())
714 item_byte_size = m_format_options.GetByteSizeValue().GetCurrentValue();
715 else
716 item_byte_size = target->GetMaximumSizeOfStringSummary();
717 if (!m_format_options.GetCountValue().OptionWasSet())
718 item_count = 1;
719 data_sp.reset (new DataBufferHeap ((item_byte_size+1) * item_count, '\0')); // account for NULLs as necessary
720 if (data_sp->GetBytes() == NULL)
721 {
722 result.AppendErrorWithFormat ("can't allocate 0x%" PRIx64 " bytes for the memory read buffer, specify a smaller size to read", (uint64_t)((item_byte_size+1) * item_count));
723 result.SetStatus(eReturnStatusFailed);
724 return false;
725 }
726 uint8_t *data_ptr = data_sp->GetBytes();
727 auto data_addr = addr;
728 auto count = item_count;
729 item_count = 0;
730 while (item_count < count)
731 {
732 std::string buffer;
733 buffer.resize(item_byte_size+1,0);
734 Error error;
735 size_t read = target->ReadCStringFromMemory(data_addr, &buffer[0], item_byte_size+1, error);
736 if (error.Fail())
737 {
738 result.AppendErrorWithFormat("failed to read memory from 0x%" PRIx64 ".\n", addr);
739 result.SetStatus(eReturnStatusFailed);
740 return false;
741 }
742 if (item_byte_size == read)
743 {
744 result.AppendWarningWithFormat("unable to find a NULL terminated string at 0x%" PRIx64 ".Consider increasing the maximum read length.\n", data_addr);
745 break;
746 }
747 read+=1; // account for final NULL byte
748 memcpy(data_ptr, &buffer[0], read);
749 data_ptr += read;
750 data_addr += read;
751 bytes_read += read;
752 item_count++; // if we break early we know we only read item_count strings
753 }
754 data_sp.reset(new DataBufferHeap(data_sp->GetBytes(),bytes_read+1));
755 }
756
757 m_next_addr = addr + bytes_read;
758 m_prev_byte_size = bytes_read;
759 m_prev_format_options = m_format_options;
760 m_prev_memory_options = m_memory_options;
761 m_prev_outfile_options = m_outfile_options;
762 m_prev_varobj_options = m_varobj_options;
763 m_prev_clang_ast_type = clang_ast_type;
764
765 StreamFile outfile_stream;
766 Stream *output_stream = NULL;
767 const FileSpec &outfile_spec = m_outfile_options.GetFile().GetCurrentValue();
768 if (outfile_spec)
769 {
770 char path[PATH_MAX];
771 outfile_spec.GetPath (path, sizeof(path));
772
773 uint32_t open_options = File::eOpenOptionWrite | File::eOpenOptionCanCreate;
774 const bool append = m_outfile_options.GetAppend().GetCurrentValue();
775 if (append)
776 open_options |= File::eOpenOptionAppend;
777
778 if (outfile_stream.GetFile ().Open (path, open_options).Success())
779 {
780 if (m_memory_options.m_output_as_binary)
781 {
782 const size_t bytes_written = outfile_stream.Write (data_sp->GetBytes(), bytes_read);
783 if (bytes_written > 0)
784 {
785 result.GetOutputStream().Printf ("%zi bytes %s to '%s'\n",
786 bytes_written,
787 append ? "appended" : "written",
788 path);
789 return true;
790 }
791 else
792 {
793 result.AppendErrorWithFormat("Failed to write %" PRIu64 " bytes to '%s'.\n", (uint64_t)bytes_read, path);
794 result.SetStatus(eReturnStatusFailed);
795 return false;
796 }
797 }
798 else
799 {
800 // We are going to write ASCII to the file just point the
801 // output_stream to our outfile_stream...
802 output_stream = &outfile_stream;
803 }
804 }
805 else
806 {
807 result.AppendErrorWithFormat("Failed to open file '%s' for %s.\n", path, append ? "append" : "write");
808 result.SetStatus(eReturnStatusFailed);
809 return false;
810 }
811 }
812 else
813 {
814 output_stream = &result.GetOutputStream();
815 }
816
817
818 ExecutionContextScope *exe_scope = m_exe_ctx.GetBestExecutionContextScope();
819 if (clang_ast_type.GetOpaqueQualType())
820 {
821 for (uint32_t i = 0; i<item_count; ++i)
822 {
823 addr_t item_addr = addr + (i * item_byte_size);
824 Address address (item_addr);
825 StreamString name_strm;
826 name_strm.Printf ("0x%" PRIx64, item_addr);
827 ValueObjectSP valobj_sp (ValueObjectMemory::Create (exe_scope,
828 name_strm.GetString().c_str(),
829 address,
830 clang_ast_type));
831 if (valobj_sp)
832 {
833 Format format = m_format_options.GetFormat();
834 if (format != eFormatDefault)
835 valobj_sp->SetFormat (format);
836
837 ValueObject::DumpValueObjectOptions options(m_varobj_options.GetAsDumpOptions(false,format));
838
839 ValueObject::DumpValueObject (*output_stream,
840 valobj_sp.get(),
841 options);
842 }
843 else
844 {
845 result.AppendErrorWithFormat ("failed to create a value object for: (%s) %s\n",
846 view_as_type_cstr,
847 name_strm.GetString().c_str());
848 result.SetStatus(eReturnStatusFailed);
849 return false;
850 }
851 }
852 return true;
853 }
854
855 result.SetStatus(eReturnStatusSuccessFinishResult);
856 DataExtractor data (data_sp,
857 target->GetArchitecture().GetByteOrder(),
858 target->GetArchitecture().GetAddressByteSize());
859
860 Format format = m_format_options.GetFormat();
861 if ( ( (format == eFormatChar) || (format == eFormatCharPrintable) )
862 && (item_byte_size != 1)
863 && (item_count == 1))
864 {
865 // this turns requests such as
866 // memory read -fc -s10 -c1 *charPtrPtr
867 // which make no sense (what is a char of size 10?)
868 // into a request for fetching 10 chars of size 1 from the same memory location
869 format = eFormatCharArray;
870 item_count = item_byte_size;
871 item_byte_size = 1;
872 }
873
874 assert (output_stream);
875 size_t bytes_dumped = data.Dump (output_stream,
876 0,
877 format,
878 item_byte_size,
879 item_count,
880 num_per_line,
881 addr,
882 0,
883 0,
884 exe_scope);
885 m_next_addr = addr + bytes_dumped;
886 output_stream->EOL();
887 return true;
888 }
889
890 OptionGroupOptions m_option_group;
891 OptionGroupFormat m_format_options;
892 OptionGroupReadMemory m_memory_options;
893 OptionGroupOutputFile m_outfile_options;
894 OptionGroupValueObjectDisplay m_varobj_options;
895 lldb::addr_t m_next_addr;
896 lldb::addr_t m_prev_byte_size;
897 OptionGroupFormat m_prev_format_options;
898 OptionGroupReadMemory m_prev_memory_options;
899 OptionGroupOutputFile m_prev_outfile_options;
900 OptionGroupValueObjectDisplay m_prev_varobj_options;
901 ClangASTType m_prev_clang_ast_type;
902 };
903
904
905 OptionDefinition
906 g_memory_write_option_table[] =
907 {
908 { LLDB_OPT_SET_1, true, "infile", 'i', required_argument, NULL, 0, eArgTypeFilename, "Write memory using the contents of a file."},
909 { LLDB_OPT_SET_1, false, "offset", 'o', required_argument, NULL, 0, eArgTypeOffset, "Start writng bytes from an offset within the input file."},
910 };
911
912
913 //----------------------------------------------------------------------
914 // Write memory to the inferior process
915 //----------------------------------------------------------------------
916 class CommandObjectMemoryWrite : public CommandObjectParsed
917 {
918 public:
919
920 class OptionGroupWriteMemory : public OptionGroup
921 {
922 public:
OptionGroupWriteMemory()923 OptionGroupWriteMemory () :
924 OptionGroup()
925 {
926 }
927
928 virtual
~OptionGroupWriteMemory()929 ~OptionGroupWriteMemory ()
930 {
931 }
932
933 virtual uint32_t
GetNumDefinitions()934 GetNumDefinitions ()
935 {
936 return sizeof (g_memory_write_option_table) / sizeof (OptionDefinition);
937 }
938
939 virtual const OptionDefinition*
GetDefinitions()940 GetDefinitions ()
941 {
942 return g_memory_write_option_table;
943 }
944
945 virtual Error
SetOptionValue(CommandInterpreter & interpreter,uint32_t option_idx,const char * option_arg)946 SetOptionValue (CommandInterpreter &interpreter,
947 uint32_t option_idx,
948 const char *option_arg)
949 {
950 Error error;
951 const int short_option = g_memory_write_option_table[option_idx].short_option;
952
953 switch (short_option)
954 {
955 case 'i':
956 m_infile.SetFile (option_arg, true);
957 if (!m_infile.Exists())
958 {
959 m_infile.Clear();
960 error.SetErrorStringWithFormat("input file does not exist: '%s'", option_arg);
961 }
962 break;
963
964 case 'o':
965 {
966 bool success;
967 m_infile_offset = Args::StringToUInt64(option_arg, 0, 0, &success);
968 if (!success)
969 {
970 error.SetErrorStringWithFormat("invalid offset string '%s'", option_arg);
971 }
972 }
973 break;
974
975 default:
976 error.SetErrorStringWithFormat("unrecognized short option '%c'", short_option);
977 break;
978 }
979 return error;
980 }
981
982 virtual void
OptionParsingStarting(CommandInterpreter & interpreter)983 OptionParsingStarting (CommandInterpreter &interpreter)
984 {
985 m_infile.Clear();
986 m_infile_offset = 0;
987 }
988
989 FileSpec m_infile;
990 off_t m_infile_offset;
991 };
992
CommandObjectMemoryWrite(CommandInterpreter & interpreter)993 CommandObjectMemoryWrite (CommandInterpreter &interpreter) :
994 CommandObjectParsed (interpreter,
995 "memory write",
996 "Write to the memory of the process being debugged.",
997 NULL,
998 eFlagRequiresProcess | eFlagProcessMustBeLaunched),
999 m_option_group (interpreter),
1000 m_format_options (eFormatBytes, 1, UINT64_MAX),
1001 m_memory_options ()
1002 {
1003 CommandArgumentEntry arg1;
1004 CommandArgumentEntry arg2;
1005 CommandArgumentData addr_arg;
1006 CommandArgumentData value_arg;
1007
1008 // Define the first (and only) variant of this arg.
1009 addr_arg.arg_type = eArgTypeAddress;
1010 addr_arg.arg_repetition = eArgRepeatPlain;
1011
1012 // There is only one variant this argument could be; put it into the argument entry.
1013 arg1.push_back (addr_arg);
1014
1015 // Define the first (and only) variant of this arg.
1016 value_arg.arg_type = eArgTypeValue;
1017 value_arg.arg_repetition = eArgRepeatPlus;
1018
1019 // There is only one variant this argument could be; put it into the argument entry.
1020 arg2.push_back (value_arg);
1021
1022 // Push the data for the first argument into the m_arguments vector.
1023 m_arguments.push_back (arg1);
1024 m_arguments.push_back (arg2);
1025
1026 m_option_group.Append (&m_format_options, OptionGroupFormat::OPTION_GROUP_FORMAT, LLDB_OPT_SET_1);
1027 m_option_group.Append (&m_format_options, OptionGroupFormat::OPTION_GROUP_SIZE , LLDB_OPT_SET_1|LLDB_OPT_SET_2);
1028 m_option_group.Append (&m_memory_options, LLDB_OPT_SET_ALL, LLDB_OPT_SET_2);
1029 m_option_group.Finalize();
1030
1031 }
1032
1033 virtual
~CommandObjectMemoryWrite()1034 ~CommandObjectMemoryWrite ()
1035 {
1036 }
1037
1038 Options *
GetOptions()1039 GetOptions ()
1040 {
1041 return &m_option_group;
1042 }
1043
1044 bool
UIntValueIsValidForSize(uint64_t uval64,size_t total_byte_size)1045 UIntValueIsValidForSize (uint64_t uval64, size_t total_byte_size)
1046 {
1047 if (total_byte_size > 8)
1048 return false;
1049
1050 if (total_byte_size == 8)
1051 return true;
1052
1053 const uint64_t max = ((uint64_t)1 << (uint64_t)(total_byte_size * 8)) - 1;
1054 return uval64 <= max;
1055 }
1056
1057 bool
SIntValueIsValidForSize(int64_t sval64,size_t total_byte_size)1058 SIntValueIsValidForSize (int64_t sval64, size_t total_byte_size)
1059 {
1060 if (total_byte_size > 8)
1061 return false;
1062
1063 if (total_byte_size == 8)
1064 return true;
1065
1066 const int64_t max = ((int64_t)1 << (uint64_t)(total_byte_size * 8 - 1)) - 1;
1067 const int64_t min = ~(max);
1068 return min <= sval64 && sval64 <= max;
1069 }
1070
1071 protected:
1072 virtual bool
DoExecute(Args & command,CommandReturnObject & result)1073 DoExecute (Args& command, CommandReturnObject &result)
1074 {
1075 // No need to check "process" for validity as eFlagRequiresProcess ensures it is valid
1076 Process *process = m_exe_ctx.GetProcessPtr();
1077
1078 const size_t argc = command.GetArgumentCount();
1079
1080 if (m_memory_options.m_infile)
1081 {
1082 if (argc < 1)
1083 {
1084 result.AppendErrorWithFormat ("%s takes a destination address when writing file contents.\n", m_cmd_name.c_str());
1085 result.SetStatus(eReturnStatusFailed);
1086 return false;
1087 }
1088 }
1089 else if (argc < 2)
1090 {
1091 result.AppendErrorWithFormat ("%s takes a destination address and at least one value.\n", m_cmd_name.c_str());
1092 result.SetStatus(eReturnStatusFailed);
1093 return false;
1094 }
1095
1096 StreamString buffer (Stream::eBinary,
1097 process->GetTarget().GetArchitecture().GetAddressByteSize(),
1098 process->GetTarget().GetArchitecture().GetByteOrder());
1099
1100 OptionValueUInt64 &byte_size_value = m_format_options.GetByteSizeValue();
1101 size_t item_byte_size = byte_size_value.GetCurrentValue();
1102
1103 Error error;
1104 lldb::addr_t addr = Args::StringToAddress (&m_exe_ctx,
1105 command.GetArgumentAtIndex(0),
1106 LLDB_INVALID_ADDRESS,
1107 &error);
1108
1109 if (addr == LLDB_INVALID_ADDRESS)
1110 {
1111 result.AppendError("invalid address expression\n");
1112 result.AppendError(error.AsCString());
1113 result.SetStatus(eReturnStatusFailed);
1114 return false;
1115 }
1116
1117 if (m_memory_options.m_infile)
1118 {
1119 size_t length = SIZE_MAX;
1120 if (item_byte_size > 0)
1121 length = item_byte_size;
1122 lldb::DataBufferSP data_sp (m_memory_options.m_infile.ReadFileContents (m_memory_options.m_infile_offset, length));
1123 if (data_sp)
1124 {
1125 length = data_sp->GetByteSize();
1126 if (length > 0)
1127 {
1128 Error error;
1129 size_t bytes_written = process->WriteMemory (addr, data_sp->GetBytes(), length, error);
1130
1131 if (bytes_written == length)
1132 {
1133 // All bytes written
1134 result.GetOutputStream().Printf("%" PRIu64 " bytes were written to 0x%" PRIx64 "\n", (uint64_t)bytes_written, addr);
1135 result.SetStatus(eReturnStatusSuccessFinishResult);
1136 }
1137 else if (bytes_written > 0)
1138 {
1139 // Some byte written
1140 result.GetOutputStream().Printf("%" PRIu64 " bytes of %" PRIu64 " requested were written to 0x%" PRIx64 "\n", (uint64_t)bytes_written, (uint64_t)length, addr);
1141 result.SetStatus(eReturnStatusSuccessFinishResult);
1142 }
1143 else
1144 {
1145 result.AppendErrorWithFormat ("Memory write to 0x%" PRIx64 " failed: %s.\n", addr, error.AsCString());
1146 result.SetStatus(eReturnStatusFailed);
1147 }
1148 }
1149 }
1150 else
1151 {
1152 result.AppendErrorWithFormat ("Unable to read contents of file.\n");
1153 result.SetStatus(eReturnStatusFailed);
1154 }
1155 return result.Succeeded();
1156 }
1157 else if (item_byte_size == 0)
1158 {
1159 if (m_format_options.GetFormat() == eFormatPointer)
1160 item_byte_size = buffer.GetAddressByteSize();
1161 else
1162 item_byte_size = 1;
1163 }
1164
1165 command.Shift(); // shift off the address argument
1166 uint64_t uval64;
1167 int64_t sval64;
1168 bool success = false;
1169 const size_t num_value_args = command.GetArgumentCount();
1170 for (size_t i=0; i<num_value_args; ++i)
1171 {
1172 const char *value_str = command.GetArgumentAtIndex(i);
1173
1174 switch (m_format_options.GetFormat())
1175 {
1176 case kNumFormats:
1177 case eFormatFloat: // TODO: add support for floats soon
1178 case eFormatCharPrintable:
1179 case eFormatBytesWithASCII:
1180 case eFormatComplex:
1181 case eFormatEnum:
1182 case eFormatUnicode16:
1183 case eFormatUnicode32:
1184 case eFormatVectorOfChar:
1185 case eFormatVectorOfSInt8:
1186 case eFormatVectorOfUInt8:
1187 case eFormatVectorOfSInt16:
1188 case eFormatVectorOfUInt16:
1189 case eFormatVectorOfSInt32:
1190 case eFormatVectorOfUInt32:
1191 case eFormatVectorOfSInt64:
1192 case eFormatVectorOfUInt64:
1193 case eFormatVectorOfFloat32:
1194 case eFormatVectorOfFloat64:
1195 case eFormatVectorOfUInt128:
1196 case eFormatOSType:
1197 case eFormatComplexInteger:
1198 case eFormatAddressInfo:
1199 case eFormatHexFloat:
1200 case eFormatInstruction:
1201 case eFormatVoid:
1202 result.AppendError("unsupported format for writing memory");
1203 result.SetStatus(eReturnStatusFailed);
1204 return false;
1205
1206 case eFormatDefault:
1207 case eFormatBytes:
1208 case eFormatHex:
1209 case eFormatHexUppercase:
1210 case eFormatPointer:
1211
1212 // Decode hex bytes
1213 uval64 = Args::StringToUInt64(value_str, UINT64_MAX, 16, &success);
1214 if (!success)
1215 {
1216 result.AppendErrorWithFormat ("'%s' is not a valid hex string value.\n", value_str);
1217 result.SetStatus(eReturnStatusFailed);
1218 return false;
1219 }
1220 else if (!UIntValueIsValidForSize (uval64, item_byte_size))
1221 {
1222 result.AppendErrorWithFormat ("Value 0x%" PRIx64 " is too large to fit in a %lu byte unsigned integer value.\n", uval64, item_byte_size);
1223 result.SetStatus(eReturnStatusFailed);
1224 return false;
1225 }
1226 buffer.PutMaxHex64 (uval64, item_byte_size);
1227 break;
1228
1229 case eFormatBoolean:
1230 uval64 = Args::StringToBoolean(value_str, false, &success);
1231 if (!success)
1232 {
1233 result.AppendErrorWithFormat ("'%s' is not a valid boolean string value.\n", value_str);
1234 result.SetStatus(eReturnStatusFailed);
1235 return false;
1236 }
1237 buffer.PutMaxHex64 (uval64, item_byte_size);
1238 break;
1239
1240 case eFormatBinary:
1241 uval64 = Args::StringToUInt64(value_str, UINT64_MAX, 2, &success);
1242 if (!success)
1243 {
1244 result.AppendErrorWithFormat ("'%s' is not a valid binary string value.\n", value_str);
1245 result.SetStatus(eReturnStatusFailed);
1246 return false;
1247 }
1248 else if (!UIntValueIsValidForSize (uval64, item_byte_size))
1249 {
1250 result.AppendErrorWithFormat ("Value 0x%" PRIx64 " is too large to fit in a %lu byte unsigned integer value.\n", uval64, item_byte_size);
1251 result.SetStatus(eReturnStatusFailed);
1252 return false;
1253 }
1254 buffer.PutMaxHex64 (uval64, item_byte_size);
1255 break;
1256
1257 case eFormatCharArray:
1258 case eFormatChar:
1259 case eFormatCString:
1260 if (value_str[0])
1261 {
1262 size_t len = strlen (value_str);
1263 // Include the NULL for C strings...
1264 if (m_format_options.GetFormat() == eFormatCString)
1265 ++len;
1266 Error error;
1267 if (process->WriteMemory (addr, value_str, len, error) == len)
1268 {
1269 addr += len;
1270 }
1271 else
1272 {
1273 result.AppendErrorWithFormat ("Memory write to 0x%" PRIx64 " failed: %s.\n", addr, error.AsCString());
1274 result.SetStatus(eReturnStatusFailed);
1275 return false;
1276 }
1277 }
1278 break;
1279
1280 case eFormatDecimal:
1281 sval64 = Args::StringToSInt64(value_str, INT64_MAX, 0, &success);
1282 if (!success)
1283 {
1284 result.AppendErrorWithFormat ("'%s' is not a valid signed decimal value.\n", value_str);
1285 result.SetStatus(eReturnStatusFailed);
1286 return false;
1287 }
1288 else if (!SIntValueIsValidForSize (sval64, item_byte_size))
1289 {
1290 result.AppendErrorWithFormat ("Value %" PRIi64 " is too large or small to fit in a %lu byte signed integer value.\n", sval64, item_byte_size);
1291 result.SetStatus(eReturnStatusFailed);
1292 return false;
1293 }
1294 buffer.PutMaxHex64 (sval64, item_byte_size);
1295 break;
1296
1297 case eFormatUnsigned:
1298 uval64 = Args::StringToUInt64(value_str, UINT64_MAX, 0, &success);
1299 if (!success)
1300 {
1301 result.AppendErrorWithFormat ("'%s' is not a valid unsigned decimal string value.\n", value_str);
1302 result.SetStatus(eReturnStatusFailed);
1303 return false;
1304 }
1305 else if (!UIntValueIsValidForSize (uval64, item_byte_size))
1306 {
1307 result.AppendErrorWithFormat ("Value %" PRIu64 " is too large to fit in a %lu byte unsigned integer value.\n", uval64, item_byte_size);
1308 result.SetStatus(eReturnStatusFailed);
1309 return false;
1310 }
1311 buffer.PutMaxHex64 (uval64, item_byte_size);
1312 break;
1313
1314 case eFormatOctal:
1315 uval64 = Args::StringToUInt64(value_str, UINT64_MAX, 8, &success);
1316 if (!success)
1317 {
1318 result.AppendErrorWithFormat ("'%s' is not a valid octal string value.\n", value_str);
1319 result.SetStatus(eReturnStatusFailed);
1320 return false;
1321 }
1322 else if (!UIntValueIsValidForSize (uval64, item_byte_size))
1323 {
1324 result.AppendErrorWithFormat ("Value %" PRIo64 " is too large to fit in a %lu byte unsigned integer value.\n", uval64, item_byte_size);
1325 result.SetStatus(eReturnStatusFailed);
1326 return false;
1327 }
1328 buffer.PutMaxHex64 (uval64, item_byte_size);
1329 break;
1330 }
1331 }
1332
1333 if (!buffer.GetString().empty())
1334 {
1335 Error error;
1336 if (process->WriteMemory (addr, buffer.GetString().c_str(), buffer.GetString().size(), error) == buffer.GetString().size())
1337 return true;
1338 else
1339 {
1340 result.AppendErrorWithFormat ("Memory write to 0x%" PRIx64 " failed: %s.\n", addr, error.AsCString());
1341 result.SetStatus(eReturnStatusFailed);
1342 return false;
1343 }
1344 }
1345 return true;
1346 }
1347
1348 OptionGroupOptions m_option_group;
1349 OptionGroupFormat m_format_options;
1350 OptionGroupWriteMemory m_memory_options;
1351 };
1352
1353
1354 //-------------------------------------------------------------------------
1355 // CommandObjectMemory
1356 //-------------------------------------------------------------------------
1357
CommandObjectMemory(CommandInterpreter & interpreter)1358 CommandObjectMemory::CommandObjectMemory (CommandInterpreter &interpreter) :
1359 CommandObjectMultiword (interpreter,
1360 "memory",
1361 "A set of commands for operating on memory.",
1362 "memory <subcommand> [<subcommand-options>]")
1363 {
1364 LoadSubCommand ("read", CommandObjectSP (new CommandObjectMemoryRead (interpreter)));
1365 LoadSubCommand ("write", CommandObjectSP (new CommandObjectMemoryWrite (interpreter)));
1366 }
1367
~CommandObjectMemory()1368 CommandObjectMemory::~CommandObjectMemory ()
1369 {
1370 }
1371