150
|
1 //===-- Materializer.cpp --------------------------------------------------===//
|
|
2 //
|
|
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
4 // See https://llvm.org/LICENSE.txt for license information.
|
|
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
6 //
|
|
7 //===----------------------------------------------------------------------===//
|
|
8
|
|
9 #include "lldb/Expression/Materializer.h"
|
|
10 #include "lldb/Core/DumpDataExtractor.h"
|
|
11 #include "lldb/Core/ValueObjectConstResult.h"
|
|
12 #include "lldb/Core/ValueObjectVariable.h"
|
|
13 #include "lldb/Expression/ExpressionVariable.h"
|
|
14 #include "lldb/Symbol/Symbol.h"
|
|
15 #include "lldb/Symbol/Type.h"
|
|
16 #include "lldb/Symbol/Variable.h"
|
|
17 #include "lldb/Target/ExecutionContext.h"
|
|
18 #include "lldb/Target/RegisterContext.h"
|
|
19 #include "lldb/Target/StackFrame.h"
|
|
20 #include "lldb/Target/Target.h"
|
|
21 #include "lldb/Target/Thread.h"
|
236
|
22 #include "lldb/Utility/LLDBLog.h"
|
150
|
23 #include "lldb/Utility/Log.h"
|
|
24 #include "lldb/Utility/RegisterValue.h"
|
236
|
25 #include "lldb/lldb-forward.h"
|
150
|
26
|
|
27 #include <memory>
|
252
|
28 #include <optional>
|
150
|
29
|
|
30 using namespace lldb_private;
|
|
31
|
236
|
32 // FIXME: these should be retrieved from the target
|
|
33 // instead of being hard-coded. Currently we
|
|
34 // assume that persistent vars are materialized
|
|
35 // as references, and thus pick the size of a
|
|
36 // 64-bit pointer.
|
|
37 static constexpr uint32_t g_default_var_alignment = 8;
|
|
38 static constexpr uint32_t g_default_var_byte_size = 8;
|
|
39
|
150
|
40 uint32_t Materializer::AddStructMember(Entity &entity) {
|
|
41 uint32_t size = entity.GetSize();
|
|
42 uint32_t alignment = entity.GetAlignment();
|
|
43
|
|
44 uint32_t ret;
|
|
45
|
|
46 if (m_current_offset == 0)
|
|
47 m_struct_alignment = alignment;
|
|
48
|
|
49 if (m_current_offset % alignment)
|
|
50 m_current_offset += (alignment - (m_current_offset % alignment));
|
|
51
|
|
52 ret = m_current_offset;
|
|
53
|
|
54 m_current_offset += size;
|
|
55
|
|
56 return ret;
|
|
57 }
|
|
58
|
|
59 class EntityPersistentVariable : public Materializer::Entity {
|
|
60 public:
|
|
61 EntityPersistentVariable(lldb::ExpressionVariableSP &persistent_variable_sp,
|
|
62 Materializer::PersistentVariableDelegate *delegate)
|
|
63 : Entity(), m_persistent_variable_sp(persistent_variable_sp),
|
|
64 m_delegate(delegate) {
|
|
65 // Hard-coding to maximum size of a pointer since persistent variables are
|
|
66 // materialized by reference
|
236
|
67 m_size = g_default_var_byte_size;
|
|
68 m_alignment = g_default_var_alignment;
|
150
|
69 }
|
|
70
|
|
71 void MakeAllocation(IRMemoryMap &map, Status &err) {
|
236
|
72 Log *log = GetLog(LLDBLog::Expressions);
|
150
|
73
|
|
74 // Allocate a spare memory area to store the persistent variable's
|
|
75 // contents.
|
|
76
|
|
77 Status allocate_error;
|
|
78 const bool zero_memory = false;
|
|
79
|
|
80 lldb::addr_t mem = map.Malloc(
|
236
|
81 m_persistent_variable_sp->GetByteSize().value_or(0), 8,
|
150
|
82 lldb::ePermissionsReadable | lldb::ePermissionsWritable,
|
|
83 IRMemoryMap::eAllocationPolicyMirror, zero_memory, allocate_error);
|
|
84
|
|
85 if (!allocate_error.Success()) {
|
|
86 err.SetErrorStringWithFormat(
|
|
87 "couldn't allocate a memory area to store %s: %s",
|
|
88 m_persistent_variable_sp->GetName().GetCString(),
|
|
89 allocate_error.AsCString());
|
|
90 return;
|
|
91 }
|
|
92
|
|
93 LLDB_LOGF(log, "Allocated %s (0x%" PRIx64 ") successfully",
|
|
94 m_persistent_variable_sp->GetName().GetCString(), mem);
|
|
95
|
|
96 // Put the location of the spare memory into the live data of the
|
|
97 // ValueObject.
|
|
98
|
|
99 m_persistent_variable_sp->m_live_sp = ValueObjectConstResult::Create(
|
|
100 map.GetBestExecutionContextScope(),
|
|
101 m_persistent_variable_sp->GetCompilerType(),
|
|
102 m_persistent_variable_sp->GetName(), mem, eAddressTypeLoad,
|
|
103 map.GetAddressByteSize());
|
|
104
|
|
105 // Clear the flag if the variable will never be deallocated.
|
|
106
|
|
107 if (m_persistent_variable_sp->m_flags &
|
|
108 ExpressionVariable::EVKeepInTarget) {
|
|
109 Status leak_error;
|
|
110 map.Leak(mem, leak_error);
|
|
111 m_persistent_variable_sp->m_flags &=
|
|
112 ~ExpressionVariable::EVNeedsAllocation;
|
|
113 }
|
|
114
|
|
115 // Write the contents of the variable to the area.
|
|
116
|
|
117 Status write_error;
|
|
118
|
|
119 map.WriteMemory(mem, m_persistent_variable_sp->GetValueBytes(),
|
236
|
120 m_persistent_variable_sp->GetByteSize().value_or(0),
|
221
|
121 write_error);
|
150
|
122
|
|
123 if (!write_error.Success()) {
|
|
124 err.SetErrorStringWithFormat(
|
|
125 "couldn't write %s to the target: %s",
|
|
126 m_persistent_variable_sp->GetName().AsCString(),
|
|
127 write_error.AsCString());
|
|
128 return;
|
|
129 }
|
|
130 }
|
|
131
|
|
132 void DestroyAllocation(IRMemoryMap &map, Status &err) {
|
|
133 Status deallocate_error;
|
|
134
|
|
135 map.Free((lldb::addr_t)m_persistent_variable_sp->m_live_sp->GetValue()
|
|
136 .GetScalar()
|
|
137 .ULongLong(),
|
|
138 deallocate_error);
|
|
139
|
|
140 m_persistent_variable_sp->m_live_sp.reset();
|
|
141
|
|
142 if (!deallocate_error.Success()) {
|
|
143 err.SetErrorStringWithFormat(
|
|
144 "couldn't deallocate memory for %s: %s",
|
|
145 m_persistent_variable_sp->GetName().GetCString(),
|
|
146 deallocate_error.AsCString());
|
|
147 }
|
|
148 }
|
|
149
|
|
150 void Materialize(lldb::StackFrameSP &frame_sp, IRMemoryMap &map,
|
|
151 lldb::addr_t process_address, Status &err) override {
|
236
|
152 Log *log = GetLog(LLDBLog::Expressions);
|
150
|
153
|
|
154 const lldb::addr_t load_addr = process_address + m_offset;
|
|
155
|
|
156 if (log) {
|
|
157 LLDB_LOGF(log,
|
|
158 "EntityPersistentVariable::Materialize [address = 0x%" PRIx64
|
|
159 ", m_name = %s, m_flags = 0x%hx]",
|
|
160 (uint64_t)load_addr,
|
|
161 m_persistent_variable_sp->GetName().AsCString(),
|
|
162 m_persistent_variable_sp->m_flags);
|
|
163 }
|
|
164
|
|
165 if (m_persistent_variable_sp->m_flags &
|
|
166 ExpressionVariable::EVNeedsAllocation) {
|
|
167 MakeAllocation(map, err);
|
|
168 m_persistent_variable_sp->m_flags |=
|
|
169 ExpressionVariable::EVIsLLDBAllocated;
|
|
170
|
|
171 if (!err.Success())
|
|
172 return;
|
|
173 }
|
|
174
|
|
175 if ((m_persistent_variable_sp->m_flags &
|
|
176 ExpressionVariable::EVIsProgramReference &&
|
|
177 m_persistent_variable_sp->m_live_sp) ||
|
|
178 m_persistent_variable_sp->m_flags &
|
|
179 ExpressionVariable::EVIsLLDBAllocated) {
|
|
180 Status write_error;
|
|
181
|
|
182 map.WriteScalarToMemory(
|
|
183 load_addr,
|
|
184 m_persistent_variable_sp->m_live_sp->GetValue().GetScalar(),
|
|
185 map.GetAddressByteSize(), write_error);
|
|
186
|
|
187 if (!write_error.Success()) {
|
|
188 err.SetErrorStringWithFormat(
|
|
189 "couldn't write the location of %s to memory: %s",
|
|
190 m_persistent_variable_sp->GetName().AsCString(),
|
|
191 write_error.AsCString());
|
|
192 }
|
|
193 } else {
|
|
194 err.SetErrorStringWithFormat(
|
|
195 "no materialization happened for persistent variable %s",
|
|
196 m_persistent_variable_sp->GetName().AsCString());
|
|
197 return;
|
|
198 }
|
|
199 }
|
|
200
|
|
201 void Dematerialize(lldb::StackFrameSP &frame_sp, IRMemoryMap &map,
|
|
202 lldb::addr_t process_address, lldb::addr_t frame_top,
|
|
203 lldb::addr_t frame_bottom, Status &err) override {
|
236
|
204 Log *log = GetLog(LLDBLog::Expressions);
|
150
|
205
|
|
206 const lldb::addr_t load_addr = process_address + m_offset;
|
|
207
|
|
208 if (log) {
|
|
209 LLDB_LOGF(log,
|
|
210 "EntityPersistentVariable::Dematerialize [address = 0x%" PRIx64
|
|
211 ", m_name = %s, m_flags = 0x%hx]",
|
|
212 (uint64_t)process_address + m_offset,
|
|
213 m_persistent_variable_sp->GetName().AsCString(),
|
|
214 m_persistent_variable_sp->m_flags);
|
|
215 }
|
|
216
|
|
217 if (m_delegate) {
|
|
218 m_delegate->DidDematerialize(m_persistent_variable_sp);
|
|
219 }
|
|
220
|
|
221 if ((m_persistent_variable_sp->m_flags &
|
|
222 ExpressionVariable::EVIsLLDBAllocated) ||
|
|
223 (m_persistent_variable_sp->m_flags &
|
|
224 ExpressionVariable::EVIsProgramReference)) {
|
|
225 if (m_persistent_variable_sp->m_flags &
|
|
226 ExpressionVariable::EVIsProgramReference &&
|
|
227 !m_persistent_variable_sp->m_live_sp) {
|
|
228 // If the reference comes from the program, then the
|
|
229 // ClangExpressionVariable's live variable data hasn't been set up yet.
|
|
230 // Do this now.
|
|
231
|
|
232 lldb::addr_t location;
|
|
233 Status read_error;
|
|
234
|
|
235 map.ReadPointerFromMemory(&location, load_addr, read_error);
|
|
236
|
|
237 if (!read_error.Success()) {
|
|
238 err.SetErrorStringWithFormat(
|
|
239 "couldn't read the address of program-allocated variable %s: %s",
|
|
240 m_persistent_variable_sp->GetName().GetCString(),
|
|
241 read_error.AsCString());
|
|
242 return;
|
|
243 }
|
|
244
|
|
245 m_persistent_variable_sp->m_live_sp = ValueObjectConstResult::Create(
|
|
246 map.GetBestExecutionContextScope(),
|
|
247 m_persistent_variable_sp.get()->GetCompilerType(),
|
|
248 m_persistent_variable_sp->GetName(), location, eAddressTypeLoad,
|
236
|
249 m_persistent_variable_sp->GetByteSize().value_or(0));
|
150
|
250
|
|
251 if (frame_top != LLDB_INVALID_ADDRESS &&
|
|
252 frame_bottom != LLDB_INVALID_ADDRESS && location >= frame_bottom &&
|
|
253 location <= frame_top) {
|
|
254 // If the variable is resident in the stack frame created by the
|
|
255 // expression, then it cannot be relied upon to stay around. We
|
|
256 // treat it as needing reallocation.
|
|
257 m_persistent_variable_sp->m_flags |=
|
|
258 ExpressionVariable::EVIsLLDBAllocated;
|
|
259 m_persistent_variable_sp->m_flags |=
|
|
260 ExpressionVariable::EVNeedsAllocation;
|
|
261 m_persistent_variable_sp->m_flags |=
|
|
262 ExpressionVariable::EVNeedsFreezeDry;
|
|
263 m_persistent_variable_sp->m_flags &=
|
|
264 ~ExpressionVariable::EVIsProgramReference;
|
|
265 }
|
|
266 }
|
|
267
|
|
268 lldb::addr_t mem = m_persistent_variable_sp->m_live_sp->GetValue()
|
|
269 .GetScalar()
|
|
270 .ULongLong();
|
|
271
|
|
272 if (!m_persistent_variable_sp->m_live_sp) {
|
|
273 err.SetErrorStringWithFormat(
|
|
274 "couldn't find the memory area used to store %s",
|
|
275 m_persistent_variable_sp->GetName().GetCString());
|
|
276 return;
|
|
277 }
|
|
278
|
|
279 if (m_persistent_variable_sp->m_live_sp->GetValue()
|
|
280 .GetValueAddressType() != eAddressTypeLoad) {
|
|
281 err.SetErrorStringWithFormat(
|
|
282 "the address of the memory area for %s is in an incorrect format",
|
|
283 m_persistent_variable_sp->GetName().GetCString());
|
|
284 return;
|
|
285 }
|
|
286
|
|
287 if (m_persistent_variable_sp->m_flags &
|
|
288 ExpressionVariable::EVNeedsFreezeDry ||
|
|
289 m_persistent_variable_sp->m_flags &
|
|
290 ExpressionVariable::EVKeepInTarget) {
|
|
291 LLDB_LOGF(log, "Dematerializing %s from 0x%" PRIx64 " (size = %llu)",
|
|
292 m_persistent_variable_sp->GetName().GetCString(),
|
|
293 (uint64_t)mem,
|
221
|
294 (unsigned long long)m_persistent_variable_sp->GetByteSize()
|
236
|
295 .value_or(0));
|
150
|
296
|
|
297 // Read the contents of the spare memory area
|
|
298
|
|
299 m_persistent_variable_sp->ValueUpdated();
|
|
300
|
|
301 Status read_error;
|
|
302
|
|
303 map.ReadMemory(m_persistent_variable_sp->GetValueBytes(), mem,
|
236
|
304 m_persistent_variable_sp->GetByteSize().value_or(0),
|
|
305 read_error);
|
150
|
306
|
|
307 if (!read_error.Success()) {
|
|
308 err.SetErrorStringWithFormat(
|
|
309 "couldn't read the contents of %s from memory: %s",
|
|
310 m_persistent_variable_sp->GetName().GetCString(),
|
|
311 read_error.AsCString());
|
|
312 return;
|
|
313 }
|
|
314
|
|
315 m_persistent_variable_sp->m_flags &=
|
|
316 ~ExpressionVariable::EVNeedsFreezeDry;
|
|
317 }
|
|
318 } else {
|
|
319 err.SetErrorStringWithFormat(
|
|
320 "no dematerialization happened for persistent variable %s",
|
|
321 m_persistent_variable_sp->GetName().AsCString());
|
|
322 return;
|
|
323 }
|
|
324
|
|
325 lldb::ProcessSP process_sp =
|
|
326 map.GetBestExecutionContextScope()->CalculateProcess();
|
|
327 if (!process_sp || !process_sp->CanJIT()) {
|
|
328 // Allocations are not persistent so persistent variables cannot stay
|
|
329 // materialized.
|
|
330
|
|
331 m_persistent_variable_sp->m_flags |=
|
|
332 ExpressionVariable::EVNeedsAllocation;
|
|
333
|
|
334 DestroyAllocation(map, err);
|
|
335 if (!err.Success())
|
|
336 return;
|
|
337 } else if (m_persistent_variable_sp->m_flags &
|
|
338 ExpressionVariable::EVNeedsAllocation &&
|
|
339 !(m_persistent_variable_sp->m_flags &
|
|
340 ExpressionVariable::EVKeepInTarget)) {
|
|
341 DestroyAllocation(map, err);
|
|
342 if (!err.Success())
|
|
343 return;
|
|
344 }
|
|
345 }
|
|
346
|
|
347 void DumpToLog(IRMemoryMap &map, lldb::addr_t process_address,
|
|
348 Log *log) override {
|
|
349 StreamString dump_stream;
|
|
350
|
|
351 Status err;
|
|
352
|
|
353 const lldb::addr_t load_addr = process_address + m_offset;
|
|
354
|
|
355 dump_stream.Printf("0x%" PRIx64 ": EntityPersistentVariable (%s)\n",
|
|
356 load_addr,
|
|
357 m_persistent_variable_sp->GetName().AsCString());
|
|
358
|
|
359 {
|
|
360 dump_stream.Printf("Pointer:\n");
|
|
361
|
|
362 DataBufferHeap data(m_size, 0);
|
|
363
|
|
364 map.ReadMemory(data.GetBytes(), load_addr, m_size, err);
|
|
365
|
|
366 if (!err.Success()) {
|
|
367 dump_stream.Printf(" <could not be read>\n");
|
|
368 } else {
|
|
369 DumpHexBytes(&dump_stream, data.GetBytes(), data.GetByteSize(), 16,
|
|
370 load_addr);
|
|
371
|
|
372 dump_stream.PutChar('\n');
|
|
373 }
|
|
374 }
|
|
375
|
|
376 {
|
|
377 dump_stream.Printf("Target:\n");
|
|
378
|
|
379 lldb::addr_t target_address;
|
|
380
|
|
381 map.ReadPointerFromMemory(&target_address, load_addr, err);
|
|
382
|
|
383 if (!err.Success()) {
|
|
384 dump_stream.Printf(" <could not be read>\n");
|
|
385 } else {
|
236
|
386 DataBufferHeap data(m_persistent_variable_sp->GetByteSize().value_or(0),
|
|
387 0);
|
150
|
388
|
|
389 map.ReadMemory(data.GetBytes(), target_address,
|
236
|
390 m_persistent_variable_sp->GetByteSize().value_or(0),
|
|
391 err);
|
150
|
392
|
|
393 if (!err.Success()) {
|
|
394 dump_stream.Printf(" <could not be read>\n");
|
|
395 } else {
|
|
396 DumpHexBytes(&dump_stream, data.GetBytes(), data.GetByteSize(), 16,
|
|
397 target_address);
|
|
398
|
|
399 dump_stream.PutChar('\n');
|
|
400 }
|
|
401 }
|
|
402 }
|
|
403
|
|
404 log->PutString(dump_stream.GetString());
|
|
405 }
|
|
406
|
|
407 void Wipe(IRMemoryMap &map, lldb::addr_t process_address) override {}
|
|
408
|
|
409 private:
|
|
410 lldb::ExpressionVariableSP m_persistent_variable_sp;
|
|
411 Materializer::PersistentVariableDelegate *m_delegate;
|
|
412 };
|
|
413
|
|
414 uint32_t Materializer::AddPersistentVariable(
|
|
415 lldb::ExpressionVariableSP &persistent_variable_sp,
|
|
416 PersistentVariableDelegate *delegate, Status &err) {
|
|
417 EntityVector::iterator iter = m_entities.insert(m_entities.end(), EntityUP());
|
221
|
418 *iter = std::make_unique<EntityPersistentVariable>(persistent_variable_sp,
|
|
419 delegate);
|
150
|
420 uint32_t ret = AddStructMember(**iter);
|
|
421 (*iter)->SetOffset(ret);
|
|
422 return ret;
|
|
423 }
|
|
424
|
236
|
425 /// Base class for materialization of Variables and ValueObjects.
|
|
426 ///
|
|
427 /// Subclasses specify how to obtain the Value which is to be
|
|
428 /// materialized.
|
|
429 class EntityVariableBase : public Materializer::Entity {
|
150
|
430 public:
|
236
|
431 virtual ~EntityVariableBase() = default;
|
|
432
|
|
433 EntityVariableBase() {
|
150
|
434 // Hard-coding to maximum size of a pointer since all variables are
|
|
435 // materialized by reference
|
236
|
436 m_size = g_default_var_byte_size;
|
|
437 m_alignment = g_default_var_alignment;
|
150
|
438 }
|
|
439
|
|
440 void Materialize(lldb::StackFrameSP &frame_sp, IRMemoryMap &map,
|
|
441 lldb::addr_t process_address, Status &err) override {
|
236
|
442 Log *log = GetLog(LLDBLog::Expressions);
|
150
|
443
|
|
444 const lldb::addr_t load_addr = process_address + m_offset;
|
|
445 if (log) {
|
|
446 LLDB_LOGF(log,
|
|
447 "EntityVariable::Materialize [address = 0x%" PRIx64
|
|
448 ", m_variable_sp = %s]",
|
236
|
449 (uint64_t)load_addr, GetName().GetCString());
|
150
|
450 }
|
|
451
|
|
452 ExecutionContextScope *scope = frame_sp.get();
|
|
453
|
|
454 if (!scope)
|
|
455 scope = map.GetBestExecutionContextScope();
|
|
456
|
236
|
457 lldb::ValueObjectSP valobj_sp = SetupValueObject(scope);
|
150
|
458
|
|
459 if (!valobj_sp) {
|
|
460 err.SetErrorStringWithFormat(
|
236
|
461 "couldn't get a value object for variable %s", GetName().AsCString());
|
150
|
462 return;
|
|
463 }
|
|
464
|
|
465 Status valobj_error = valobj_sp->GetError();
|
|
466
|
|
467 if (valobj_error.Fail()) {
|
|
468 err.SetErrorStringWithFormat("couldn't get the value of variable %s: %s",
|
236
|
469 GetName().AsCString(),
|
150
|
470 valobj_error.AsCString());
|
|
471 return;
|
|
472 }
|
|
473
|
|
474 if (m_is_reference) {
|
|
475 DataExtractor valobj_extractor;
|
|
476 Status extract_error;
|
|
477 valobj_sp->GetData(valobj_extractor, extract_error);
|
|
478
|
|
479 if (!extract_error.Success()) {
|
|
480 err.SetErrorStringWithFormat(
|
|
481 "couldn't read contents of reference variable %s: %s",
|
236
|
482 GetName().AsCString(), extract_error.AsCString());
|
150
|
483 return;
|
|
484 }
|
|
485
|
|
486 lldb::offset_t offset = 0;
|
|
487 lldb::addr_t reference_addr = valobj_extractor.GetAddress(&offset);
|
|
488
|
|
489 Status write_error;
|
|
490 map.WritePointerToMemory(load_addr, reference_addr, write_error);
|
|
491
|
|
492 if (!write_error.Success()) {
|
|
493 err.SetErrorStringWithFormat("couldn't write the contents of reference "
|
|
494 "variable %s to memory: %s",
|
236
|
495 GetName().AsCString(),
|
150
|
496 write_error.AsCString());
|
|
497 return;
|
|
498 }
|
|
499 } else {
|
|
500 AddressType address_type = eAddressTypeInvalid;
|
|
501 const bool scalar_is_load_address = false;
|
|
502 lldb::addr_t addr_of_valobj =
|
|
503 valobj_sp->GetAddressOf(scalar_is_load_address, &address_type);
|
|
504 if (addr_of_valobj != LLDB_INVALID_ADDRESS) {
|
|
505 Status write_error;
|
|
506 map.WritePointerToMemory(load_addr, addr_of_valobj, write_error);
|
|
507
|
|
508 if (!write_error.Success()) {
|
|
509 err.SetErrorStringWithFormat(
|
|
510 "couldn't write the address of variable %s to memory: %s",
|
236
|
511 GetName().AsCString(), write_error.AsCString());
|
150
|
512 return;
|
|
513 }
|
|
514 } else {
|
|
515 DataExtractor data;
|
|
516 Status extract_error;
|
|
517 valobj_sp->GetData(data, extract_error);
|
|
518 if (!extract_error.Success()) {
|
|
519 err.SetErrorStringWithFormat("couldn't get the value of %s: %s",
|
236
|
520 GetName().AsCString(),
|
150
|
521 extract_error.AsCString());
|
|
522 return;
|
|
523 }
|
|
524
|
|
525 if (m_temporary_allocation != LLDB_INVALID_ADDRESS) {
|
|
526 err.SetErrorStringWithFormat(
|
|
527 "trying to create a temporary region for %s but one exists",
|
236
|
528 GetName().AsCString());
|
150
|
529 return;
|
|
530 }
|
|
531
|
236
|
532 if (data.GetByteSize() < GetByteSize(scope)) {
|
|
533 if (data.GetByteSize() == 0 && !LocationExpressionIsValid()) {
|
150
|
534 err.SetErrorStringWithFormat("the variable '%s' has no location, "
|
|
535 "it may have been optimized out",
|
236
|
536 GetName().AsCString());
|
150
|
537 } else {
|
|
538 err.SetErrorStringWithFormat(
|
|
539 "size of variable %s (%" PRIu64
|
|
540 ") is larger than the ValueObject's size (%" PRIu64 ")",
|
236
|
541 GetName().AsCString(), GetByteSize(scope).value_or(0),
|
150
|
542 data.GetByteSize());
|
|
543 }
|
|
544 return;
|
|
545 }
|
|
546
|
252
|
547 std::optional<size_t> opt_bit_align = GetTypeBitAlign(scope);
|
150
|
548 if (!opt_bit_align) {
|
|
549 err.SetErrorStringWithFormat("can't get the type alignment for %s",
|
236
|
550 GetName().AsCString());
|
150
|
551 return;
|
|
552 }
|
|
553
|
|
554 size_t byte_align = (*opt_bit_align + 7) / 8;
|
|
555
|
|
556 Status alloc_error;
|
|
557 const bool zero_memory = false;
|
|
558
|
|
559 m_temporary_allocation = map.Malloc(
|
|
560 data.GetByteSize(), byte_align,
|
|
561 lldb::ePermissionsReadable | lldb::ePermissionsWritable,
|
|
562 IRMemoryMap::eAllocationPolicyMirror, zero_memory, alloc_error);
|
|
563
|
|
564 m_temporary_allocation_size = data.GetByteSize();
|
|
565
|
|
566 m_original_data = std::make_shared<DataBufferHeap>(data.GetDataStart(),
|
|
567 data.GetByteSize());
|
|
568
|
|
569 if (!alloc_error.Success()) {
|
|
570 err.SetErrorStringWithFormat(
|
|
571 "couldn't allocate a temporary region for %s: %s",
|
236
|
572 GetName().AsCString(), alloc_error.AsCString());
|
150
|
573 return;
|
|
574 }
|
|
575
|
|
576 Status write_error;
|
|
577
|
|
578 map.WriteMemory(m_temporary_allocation, data.GetDataStart(),
|
|
579 data.GetByteSize(), write_error);
|
|
580
|
|
581 if (!write_error.Success()) {
|
|
582 err.SetErrorStringWithFormat(
|
|
583 "couldn't write to the temporary region for %s: %s",
|
236
|
584 GetName().AsCString(), write_error.AsCString());
|
150
|
585 return;
|
|
586 }
|
|
587
|
|
588 Status pointer_write_error;
|
|
589
|
|
590 map.WritePointerToMemory(load_addr, m_temporary_allocation,
|
|
591 pointer_write_error);
|
|
592
|
|
593 if (!pointer_write_error.Success()) {
|
|
594 err.SetErrorStringWithFormat(
|
|
595 "couldn't write the address of the temporary region for %s: %s",
|
236
|
596 GetName().AsCString(), pointer_write_error.AsCString());
|
150
|
597 }
|
|
598 }
|
|
599 }
|
|
600 }
|
|
601
|
|
602 void Dematerialize(lldb::StackFrameSP &frame_sp, IRMemoryMap &map,
|
|
603 lldb::addr_t process_address, lldb::addr_t frame_top,
|
|
604 lldb::addr_t frame_bottom, Status &err) override {
|
236
|
605 Log *log = GetLog(LLDBLog::Expressions);
|
150
|
606
|
|
607 const lldb::addr_t load_addr = process_address + m_offset;
|
|
608 if (log) {
|
|
609 LLDB_LOGF(log,
|
|
610 "EntityVariable::Dematerialize [address = 0x%" PRIx64
|
|
611 ", m_variable_sp = %s]",
|
236
|
612 (uint64_t)load_addr, GetName().AsCString());
|
150
|
613 }
|
|
614
|
|
615 if (m_temporary_allocation != LLDB_INVALID_ADDRESS) {
|
|
616 ExecutionContextScope *scope = frame_sp.get();
|
|
617
|
|
618 if (!scope)
|
|
619 scope = map.GetBestExecutionContextScope();
|
|
620
|
236
|
621 lldb::ValueObjectSP valobj_sp = SetupValueObject(scope);
|
150
|
622
|
|
623 if (!valobj_sp) {
|
|
624 err.SetErrorStringWithFormat(
|
|
625 "couldn't get a value object for variable %s",
|
236
|
626 GetName().AsCString());
|
150
|
627 return;
|
|
628 }
|
|
629
|
|
630 lldb_private::DataExtractor data;
|
|
631
|
|
632 Status extract_error;
|
|
633
|
221
|
634 map.GetMemoryData(data, m_temporary_allocation,
|
236
|
635 valobj_sp->GetByteSize().value_or(0), extract_error);
|
150
|
636
|
|
637 if (!extract_error.Success()) {
|
|
638 err.SetErrorStringWithFormat("couldn't get the data for variable %s",
|
236
|
639 GetName().AsCString());
|
150
|
640 return;
|
|
641 }
|
|
642
|
|
643 bool actually_write = true;
|
|
644
|
|
645 if (m_original_data) {
|
|
646 if ((data.GetByteSize() == m_original_data->GetByteSize()) &&
|
|
647 !memcmp(m_original_data->GetBytes(), data.GetDataStart(),
|
|
648 data.GetByteSize())) {
|
|
649 actually_write = false;
|
|
650 }
|
|
651 }
|
|
652
|
|
653 Status set_error;
|
|
654
|
|
655 if (actually_write) {
|
|
656 valobj_sp->SetData(data, set_error);
|
|
657
|
|
658 if (!set_error.Success()) {
|
|
659 err.SetErrorStringWithFormat(
|
|
660 "couldn't write the new contents of %s back into the variable",
|
236
|
661 GetName().AsCString());
|
150
|
662 return;
|
|
663 }
|
|
664 }
|
|
665
|
|
666 Status free_error;
|
|
667
|
|
668 map.Free(m_temporary_allocation, free_error);
|
|
669
|
|
670 if (!free_error.Success()) {
|
|
671 err.SetErrorStringWithFormat(
|
|
672 "couldn't free the temporary region for %s: %s",
|
236
|
673 GetName().AsCString(), free_error.AsCString());
|
150
|
674 return;
|
|
675 }
|
|
676
|
|
677 m_original_data.reset();
|
|
678 m_temporary_allocation = LLDB_INVALID_ADDRESS;
|
|
679 m_temporary_allocation_size = 0;
|
|
680 }
|
|
681 }
|
|
682
|
|
683 void DumpToLog(IRMemoryMap &map, lldb::addr_t process_address,
|
|
684 Log *log) override {
|
|
685 StreamString dump_stream;
|
|
686
|
|
687 const lldb::addr_t load_addr = process_address + m_offset;
|
|
688 dump_stream.Printf("0x%" PRIx64 ": EntityVariable\n", load_addr);
|
|
689
|
|
690 Status err;
|
|
691
|
|
692 lldb::addr_t ptr = LLDB_INVALID_ADDRESS;
|
|
693
|
|
694 {
|
|
695 dump_stream.Printf("Pointer:\n");
|
|
696
|
|
697 DataBufferHeap data(m_size, 0);
|
|
698
|
|
699 map.ReadMemory(data.GetBytes(), load_addr, m_size, err);
|
|
700
|
|
701 if (!err.Success()) {
|
|
702 dump_stream.Printf(" <could not be read>\n");
|
|
703 } else {
|
|
704 DataExtractor extractor(data.GetBytes(), data.GetByteSize(),
|
|
705 map.GetByteOrder(), map.GetAddressByteSize());
|
|
706
|
|
707 DumpHexBytes(&dump_stream, data.GetBytes(), data.GetByteSize(), 16,
|
|
708 load_addr);
|
|
709
|
236
|
710 lldb::offset_t offset = 0;
|
150
|
711
|
173
|
712 ptr = extractor.GetAddress(&offset);
|
150
|
713
|
|
714 dump_stream.PutChar('\n');
|
|
715 }
|
|
716 }
|
|
717
|
|
718 if (m_temporary_allocation == LLDB_INVALID_ADDRESS) {
|
|
719 dump_stream.Printf("Points to process memory:\n");
|
|
720 } else {
|
|
721 dump_stream.Printf("Temporary allocation:\n");
|
|
722 }
|
|
723
|
|
724 if (ptr == LLDB_INVALID_ADDRESS) {
|
|
725 dump_stream.Printf(" <could not be be found>\n");
|
|
726 } else {
|
|
727 DataBufferHeap data(m_temporary_allocation_size, 0);
|
|
728
|
|
729 map.ReadMemory(data.GetBytes(), m_temporary_allocation,
|
|
730 m_temporary_allocation_size, err);
|
|
731
|
|
732 if (!err.Success()) {
|
|
733 dump_stream.Printf(" <could not be read>\n");
|
|
734 } else {
|
|
735 DumpHexBytes(&dump_stream, data.GetBytes(), data.GetByteSize(), 16,
|
|
736 load_addr);
|
|
737
|
|
738 dump_stream.PutChar('\n');
|
|
739 }
|
|
740 }
|
|
741
|
|
742 log->PutString(dump_stream.GetString());
|
|
743 }
|
|
744
|
|
745 void Wipe(IRMemoryMap &map, lldb::addr_t process_address) override {
|
|
746 if (m_temporary_allocation != LLDB_INVALID_ADDRESS) {
|
|
747 Status free_error;
|
|
748
|
|
749 map.Free(m_temporary_allocation, free_error);
|
|
750
|
|
751 m_temporary_allocation = LLDB_INVALID_ADDRESS;
|
|
752 m_temporary_allocation_size = 0;
|
|
753 }
|
|
754 }
|
|
755
|
|
756 private:
|
236
|
757 virtual ConstString GetName() const = 0;
|
|
758
|
|
759 /// Creates and returns ValueObject tied to this variable
|
|
760 /// and prepares Entity for materialization.
|
|
761 ///
|
|
762 /// Called each time the Materializer (de)materializes a
|
|
763 /// variable. We re-create the ValueObject based on the
|
|
764 /// current ExecutionContextScope since clients such as
|
|
765 /// conditional breakpoints may materialize the same
|
|
766 /// EntityVariable multiple times with different frames.
|
|
767 ///
|
|
768 /// Each subsequent use of the EntityVariableBase interface
|
|
769 /// will query the newly created ValueObject until this
|
|
770 /// function is called again.
|
|
771 virtual lldb::ValueObjectSP
|
|
772 SetupValueObject(ExecutionContextScope *scope) = 0;
|
|
773
|
|
774 /// Returns size in bytes of the type associated with this variable
|
|
775 ///
|
|
776 /// \returns On success, returns byte size of the type associated
|
252
|
777 /// with this variable. Returns std::nullopt otherwise.
|
|
778 virtual std::optional<uint64_t>
|
236
|
779 GetByteSize(ExecutionContextScope *scope) const = 0;
|
|
780
|
|
781 /// Returns 'true' if the location expression associated with this variable
|
|
782 /// is valid.
|
|
783 virtual bool LocationExpressionIsValid() const = 0;
|
|
784
|
|
785 /// Returns alignment of the type associated with this variable in bits.
|
|
786 ///
|
|
787 /// \returns On success, returns alignment in bits for the type associated
|
252
|
788 /// with this variable. Returns std::nullopt otherwise.
|
|
789 virtual std::optional<size_t>
|
236
|
790 GetTypeBitAlign(ExecutionContextScope *scope) const = 0;
|
|
791
|
|
792 protected:
|
|
793 bool m_is_reference = false;
|
|
794 lldb::addr_t m_temporary_allocation = LLDB_INVALID_ADDRESS;
|
|
795 size_t m_temporary_allocation_size = 0;
|
150
|
796 lldb::DataBufferSP m_original_data;
|
|
797 };
|
|
798
|
236
|
799 /// Represents an Entity constructed from a VariableSP.
|
|
800 ///
|
|
801 /// This class is used for materialization of variables for which
|
|
802 /// the user has a VariableSP on hand. The ValueObject is then
|
|
803 /// derived from the associated DWARF location expression when needed
|
|
804 /// by the Materializer.
|
|
805 class EntityVariable : public EntityVariableBase {
|
|
806 public:
|
|
807 EntityVariable(lldb::VariableSP &variable_sp) : m_variable_sp(variable_sp) {
|
|
808 m_is_reference =
|
|
809 m_variable_sp->GetType()->GetForwardCompilerType().IsReferenceType();
|
|
810 }
|
|
811
|
|
812 ConstString GetName() const override { return m_variable_sp->GetName(); }
|
|
813
|
|
814 lldb::ValueObjectSP SetupValueObject(ExecutionContextScope *scope) override {
|
|
815 assert(m_variable_sp != nullptr);
|
|
816 return ValueObjectVariable::Create(scope, m_variable_sp);
|
|
817 }
|
|
818
|
252
|
819 std::optional<uint64_t>
|
236
|
820 GetByteSize(ExecutionContextScope *scope) const override {
|
|
821 return m_variable_sp->GetType()->GetByteSize(scope);
|
|
822 }
|
|
823
|
|
824 bool LocationExpressionIsValid() const override {
|
|
825 return m_variable_sp->LocationExpressionList().IsValid();
|
|
826 }
|
|
827
|
252
|
828 std::optional<size_t>
|
236
|
829 GetTypeBitAlign(ExecutionContextScope *scope) const override {
|
|
830 return m_variable_sp->GetType()->GetLayoutCompilerType().GetTypeBitAlign(
|
|
831 scope);
|
|
832 }
|
|
833
|
|
834 private:
|
|
835 lldb::VariableSP m_variable_sp; ///< Variable that this entity is based on.
|
|
836 };
|
|
837
|
|
838 /// Represents an Entity constructed from a VariableSP.
|
|
839 ///
|
|
840 /// This class is used for materialization of variables for
|
|
841 /// which the user does not have a VariableSP available (e.g.,
|
|
842 /// when materializing ivars).
|
|
843 class EntityValueObject : public EntityVariableBase {
|
|
844 public:
|
|
845 EntityValueObject(ConstString name, ValueObjectProviderTy provider)
|
|
846 : m_name(name), m_valobj_provider(std::move(provider)) {
|
|
847 assert(m_valobj_provider);
|
|
848 }
|
|
849
|
|
850 ConstString GetName() const override { return m_name; }
|
|
851
|
|
852 lldb::ValueObjectSP SetupValueObject(ExecutionContextScope *scope) override {
|
|
853 m_valobj_sp =
|
|
854 m_valobj_provider(GetName(), scope->CalculateStackFrame().get());
|
|
855
|
|
856 if (m_valobj_sp)
|
|
857 m_is_reference = m_valobj_sp->GetCompilerType().IsReferenceType();
|
|
858
|
|
859 return m_valobj_sp;
|
|
860 }
|
|
861
|
252
|
862 std::optional<uint64_t>
|
236
|
863 GetByteSize(ExecutionContextScope *scope) const override {
|
|
864 if (m_valobj_sp)
|
|
865 return m_valobj_sp->GetCompilerType().GetByteSize(scope);
|
|
866
|
|
867 return {};
|
|
868 }
|
|
869
|
|
870 bool LocationExpressionIsValid() const override {
|
|
871 if (m_valobj_sp)
|
|
872 return m_valobj_sp->GetError().Success();
|
|
873
|
|
874 return false;
|
|
875 }
|
|
876
|
252
|
877 std::optional<size_t>
|
236
|
878 GetTypeBitAlign(ExecutionContextScope *scope) const override {
|
|
879 if (m_valobj_sp)
|
|
880 return m_valobj_sp->GetCompilerType().GetTypeBitAlign(scope);
|
|
881
|
|
882 return {};
|
|
883 }
|
|
884
|
|
885 private:
|
|
886 ConstString m_name;
|
|
887 lldb::ValueObjectSP m_valobj_sp;
|
|
888 ValueObjectProviderTy m_valobj_provider;
|
|
889 };
|
|
890
|
150
|
891 uint32_t Materializer::AddVariable(lldb::VariableSP &variable_sp, Status &err) {
|
|
892 EntityVector::iterator iter = m_entities.insert(m_entities.end(), EntityUP());
|
221
|
893 *iter = std::make_unique<EntityVariable>(variable_sp);
|
150
|
894 uint32_t ret = AddStructMember(**iter);
|
|
895 (*iter)->SetOffset(ret);
|
|
896 return ret;
|
|
897 }
|
|
898
|
236
|
899 uint32_t Materializer::AddValueObject(ConstString name,
|
|
900 ValueObjectProviderTy valobj_provider,
|
|
901 Status &err) {
|
|
902 assert(valobj_provider);
|
|
903 EntityVector::iterator iter = m_entities.insert(m_entities.end(), EntityUP());
|
|
904 *iter = std::make_unique<EntityValueObject>(name, std::move(valobj_provider));
|
|
905 uint32_t ret = AddStructMember(**iter);
|
|
906 (*iter)->SetOffset(ret);
|
|
907 return ret;
|
|
908 }
|
|
909
|
150
|
910 class EntityResultVariable : public Materializer::Entity {
|
|
911 public:
|
|
912 EntityResultVariable(const CompilerType &type, bool is_program_reference,
|
|
913 bool keep_in_memory,
|
|
914 Materializer::PersistentVariableDelegate *delegate)
|
|
915 : Entity(), m_type(type), m_is_program_reference(is_program_reference),
|
236
|
916 m_keep_in_memory(keep_in_memory), m_delegate(delegate) {
|
150
|
917 // Hard-coding to maximum size of a pointer since all results are
|
|
918 // materialized by reference
|
236
|
919 m_size = g_default_var_byte_size;
|
|
920 m_alignment = g_default_var_alignment;
|
150
|
921 }
|
|
922
|
|
923 void Materialize(lldb::StackFrameSP &frame_sp, IRMemoryMap &map,
|
|
924 lldb::addr_t process_address, Status &err) override {
|
|
925 if (!m_is_program_reference) {
|
|
926 if (m_temporary_allocation != LLDB_INVALID_ADDRESS) {
|
|
927 err.SetErrorString("Trying to create a temporary region for the result "
|
|
928 "but one exists");
|
|
929 return;
|
|
930 }
|
|
931
|
|
932 const lldb::addr_t load_addr = process_address + m_offset;
|
|
933
|
221
|
934 ExecutionContextScope *exe_scope = frame_sp.get();
|
|
935 if (!exe_scope)
|
|
936 exe_scope = map.GetBestExecutionContextScope();
|
150
|
937
|
252
|
938 std::optional<uint64_t> byte_size = m_type.GetByteSize(exe_scope);
|
150
|
939 if (!byte_size) {
|
223
|
940 err.SetErrorStringWithFormat("can't get size of type \"%s\"",
|
|
941 m_type.GetTypeName().AsCString());
|
150
|
942 return;
|
|
943 }
|
|
944
|
252
|
945 std::optional<size_t> opt_bit_align = m_type.GetTypeBitAlign(exe_scope);
|
150
|
946 if (!opt_bit_align) {
|
223
|
947 err.SetErrorStringWithFormat("can't get the alignment of type \"%s\"",
|
|
948 m_type.GetTypeName().AsCString());
|
150
|
949 return;
|
|
950 }
|
|
951
|
|
952 size_t byte_align = (*opt_bit_align + 7) / 8;
|
|
953
|
|
954 Status alloc_error;
|
|
955 const bool zero_memory = true;
|
|
956
|
|
957 m_temporary_allocation = map.Malloc(
|
|
958 *byte_size, byte_align,
|
|
959 lldb::ePermissionsReadable | lldb::ePermissionsWritable,
|
|
960 IRMemoryMap::eAllocationPolicyMirror, zero_memory, alloc_error);
|
|
961 m_temporary_allocation_size = *byte_size;
|
|
962
|
|
963 if (!alloc_error.Success()) {
|
|
964 err.SetErrorStringWithFormat(
|
|
965 "couldn't allocate a temporary region for the result: %s",
|
|
966 alloc_error.AsCString());
|
|
967 return;
|
|
968 }
|
|
969
|
|
970 Status pointer_write_error;
|
|
971
|
|
972 map.WritePointerToMemory(load_addr, m_temporary_allocation,
|
|
973 pointer_write_error);
|
|
974
|
|
975 if (!pointer_write_error.Success()) {
|
|
976 err.SetErrorStringWithFormat("couldn't write the address of the "
|
|
977 "temporary region for the result: %s",
|
|
978 pointer_write_error.AsCString());
|
|
979 }
|
|
980 }
|
|
981 }
|
|
982
|
|
983 void Dematerialize(lldb::StackFrameSP &frame_sp, IRMemoryMap &map,
|
|
984 lldb::addr_t process_address, lldb::addr_t frame_top,
|
|
985 lldb::addr_t frame_bottom, Status &err) override {
|
|
986 err.Clear();
|
|
987
|
221
|
988 ExecutionContextScope *exe_scope = frame_sp.get();
|
|
989 if (!exe_scope)
|
|
990 exe_scope = map.GetBestExecutionContextScope();
|
150
|
991
|
|
992 if (!exe_scope) {
|
|
993 err.SetErrorString("Couldn't dematerialize a result variable: invalid "
|
|
994 "execution context scope");
|
|
995 return;
|
|
996 }
|
|
997
|
|
998 lldb::addr_t address;
|
|
999 Status read_error;
|
|
1000 const lldb::addr_t load_addr = process_address + m_offset;
|
|
1001
|
|
1002 map.ReadPointerFromMemory(&address, load_addr, read_error);
|
|
1003
|
|
1004 if (!read_error.Success()) {
|
|
1005 err.SetErrorString("Couldn't dematerialize a result variable: couldn't "
|
|
1006 "read its address");
|
|
1007 return;
|
|
1008 }
|
|
1009
|
|
1010 lldb::TargetSP target_sp = exe_scope->CalculateTarget();
|
|
1011
|
|
1012 if (!target_sp) {
|
|
1013 err.SetErrorString("Couldn't dematerialize a result variable: no target");
|
|
1014 return;
|
|
1015 }
|
|
1016
|
|
1017 auto type_system_or_err =
|
|
1018 target_sp->GetScratchTypeSystemForLanguage(m_type.GetMinimumLanguage());
|
|
1019
|
|
1020 if (auto error = type_system_or_err.takeError()) {
|
|
1021 err.SetErrorStringWithFormat("Couldn't dematerialize a result variable: "
|
|
1022 "couldn't get the corresponding type "
|
|
1023 "system: %s",
|
|
1024 llvm::toString(std::move(error)).c_str());
|
|
1025 return;
|
|
1026 }
|
252
|
1027 auto ts = *type_system_or_err;
|
|
1028 if (!ts) {
|
|
1029 err.SetErrorStringWithFormat("Couldn't dematerialize a result variable: "
|
|
1030 "couldn't corresponding type system is "
|
|
1031 "no longer live.");
|
|
1032 return;
|
|
1033 }
|
150
|
1034 PersistentExpressionState *persistent_state =
|
252
|
1035 ts->GetPersistentExpressionState();
|
150
|
1036
|
|
1037 if (!persistent_state) {
|
|
1038 err.SetErrorString("Couldn't dematerialize a result variable: "
|
|
1039 "corresponding type system doesn't handle persistent "
|
|
1040 "variables");
|
|
1041 return;
|
|
1042 }
|
|
1043
|
173
|
1044 ConstString name = m_delegate
|
|
1045 ? m_delegate->GetName()
|
|
1046 : persistent_state->GetNextPersistentVariableName();
|
150
|
1047
|
|
1048 lldb::ExpressionVariableSP ret = persistent_state->CreatePersistentVariable(
|
|
1049 exe_scope, name, m_type, map.GetByteOrder(), map.GetAddressByteSize());
|
|
1050
|
|
1051 if (!ret) {
|
|
1052 err.SetErrorStringWithFormat("couldn't dematerialize a result variable: "
|
|
1053 "failed to make persistent variable %s",
|
|
1054 name.AsCString());
|
|
1055 return;
|
|
1056 }
|
|
1057
|
|
1058 lldb::ProcessSP process_sp =
|
|
1059 map.GetBestExecutionContextScope()->CalculateProcess();
|
|
1060
|
|
1061 if (m_delegate) {
|
|
1062 m_delegate->DidDematerialize(ret);
|
|
1063 }
|
|
1064
|
|
1065 bool can_persist =
|
|
1066 (m_is_program_reference && process_sp && process_sp->CanJIT() &&
|
|
1067 !(address >= frame_bottom && address < frame_top));
|
|
1068
|
|
1069 if (can_persist && m_keep_in_memory) {
|
|
1070 ret->m_live_sp = ValueObjectConstResult::Create(exe_scope, m_type, name,
|
|
1071 address, eAddressTypeLoad,
|
|
1072 map.GetAddressByteSize());
|
|
1073 }
|
|
1074
|
|
1075 ret->ValueUpdated();
|
|
1076
|
236
|
1077 const size_t pvar_byte_size = ret->GetByteSize().value_or(0);
|
150
|
1078 uint8_t *pvar_data = ret->GetValueBytes();
|
|
1079
|
|
1080 map.ReadMemory(pvar_data, address, pvar_byte_size, read_error);
|
|
1081
|
|
1082 if (!read_error.Success()) {
|
|
1083 err.SetErrorString(
|
|
1084 "Couldn't dematerialize a result variable: couldn't read its memory");
|
|
1085 return;
|
|
1086 }
|
|
1087
|
|
1088 if (!can_persist || !m_keep_in_memory) {
|
|
1089 ret->m_flags |= ExpressionVariable::EVNeedsAllocation;
|
|
1090
|
|
1091 if (m_temporary_allocation != LLDB_INVALID_ADDRESS) {
|
|
1092 Status free_error;
|
|
1093 map.Free(m_temporary_allocation, free_error);
|
|
1094 }
|
|
1095 } else {
|
|
1096 ret->m_flags |= ExpressionVariable::EVIsLLDBAllocated;
|
|
1097 }
|
|
1098
|
|
1099 m_temporary_allocation = LLDB_INVALID_ADDRESS;
|
|
1100 m_temporary_allocation_size = 0;
|
|
1101 }
|
|
1102
|
|
1103 void DumpToLog(IRMemoryMap &map, lldb::addr_t process_address,
|
|
1104 Log *log) override {
|
|
1105 StreamString dump_stream;
|
|
1106
|
|
1107 const lldb::addr_t load_addr = process_address + m_offset;
|
|
1108
|
|
1109 dump_stream.Printf("0x%" PRIx64 ": EntityResultVariable\n", load_addr);
|
|
1110
|
|
1111 Status err;
|
|
1112
|
|
1113 lldb::addr_t ptr = LLDB_INVALID_ADDRESS;
|
|
1114
|
|
1115 {
|
|
1116 dump_stream.Printf("Pointer:\n");
|
|
1117
|
|
1118 DataBufferHeap data(m_size, 0);
|
|
1119
|
|
1120 map.ReadMemory(data.GetBytes(), load_addr, m_size, err);
|
|
1121
|
|
1122 if (!err.Success()) {
|
|
1123 dump_stream.Printf(" <could not be read>\n");
|
|
1124 } else {
|
|
1125 DataExtractor extractor(data.GetBytes(), data.GetByteSize(),
|
|
1126 map.GetByteOrder(), map.GetAddressByteSize());
|
|
1127
|
|
1128 DumpHexBytes(&dump_stream, data.GetBytes(), data.GetByteSize(), 16,
|
|
1129 load_addr);
|
|
1130
|
236
|
1131 lldb::offset_t offset = 0;
|
150
|
1132
|
173
|
1133 ptr = extractor.GetAddress(&offset);
|
150
|
1134
|
|
1135 dump_stream.PutChar('\n');
|
|
1136 }
|
|
1137 }
|
|
1138
|
|
1139 if (m_temporary_allocation == LLDB_INVALID_ADDRESS) {
|
|
1140 dump_stream.Printf("Points to process memory:\n");
|
|
1141 } else {
|
|
1142 dump_stream.Printf("Temporary allocation:\n");
|
|
1143 }
|
|
1144
|
|
1145 if (ptr == LLDB_INVALID_ADDRESS) {
|
|
1146 dump_stream.Printf(" <could not be be found>\n");
|
|
1147 } else {
|
|
1148 DataBufferHeap data(m_temporary_allocation_size, 0);
|
|
1149
|
|
1150 map.ReadMemory(data.GetBytes(), m_temporary_allocation,
|
|
1151 m_temporary_allocation_size, err);
|
|
1152
|
|
1153 if (!err.Success()) {
|
|
1154 dump_stream.Printf(" <could not be read>\n");
|
|
1155 } else {
|
|
1156 DumpHexBytes(&dump_stream, data.GetBytes(), data.GetByteSize(), 16,
|
|
1157 load_addr);
|
|
1158
|
|
1159 dump_stream.PutChar('\n');
|
|
1160 }
|
|
1161 }
|
|
1162
|
|
1163 log->PutString(dump_stream.GetString());
|
|
1164 }
|
|
1165
|
|
1166 void Wipe(IRMemoryMap &map, lldb::addr_t process_address) override {
|
|
1167 if (!m_keep_in_memory && m_temporary_allocation != LLDB_INVALID_ADDRESS) {
|
|
1168 Status free_error;
|
|
1169
|
|
1170 map.Free(m_temporary_allocation, free_error);
|
|
1171 }
|
|
1172
|
|
1173 m_temporary_allocation = LLDB_INVALID_ADDRESS;
|
|
1174 m_temporary_allocation_size = 0;
|
|
1175 }
|
|
1176
|
|
1177 private:
|
|
1178 CompilerType m_type;
|
|
1179 bool m_is_program_reference;
|
|
1180 bool m_keep_in_memory;
|
|
1181
|
236
|
1182 lldb::addr_t m_temporary_allocation = LLDB_INVALID_ADDRESS;
|
|
1183 size_t m_temporary_allocation_size = 0;
|
150
|
1184 Materializer::PersistentVariableDelegate *m_delegate;
|
|
1185 };
|
|
1186
|
|
1187 uint32_t Materializer::AddResultVariable(const CompilerType &type,
|
|
1188 bool is_program_reference,
|
|
1189 bool keep_in_memory,
|
|
1190 PersistentVariableDelegate *delegate,
|
|
1191 Status &err) {
|
|
1192 EntityVector::iterator iter = m_entities.insert(m_entities.end(), EntityUP());
|
221
|
1193 *iter = std::make_unique<EntityResultVariable>(type, is_program_reference,
|
|
1194 keep_in_memory, delegate);
|
150
|
1195 uint32_t ret = AddStructMember(**iter);
|
|
1196 (*iter)->SetOffset(ret);
|
|
1197 return ret;
|
|
1198 }
|
|
1199
|
|
1200 class EntitySymbol : public Materializer::Entity {
|
|
1201 public:
|
|
1202 EntitySymbol(const Symbol &symbol) : Entity(), m_symbol(symbol) {
|
|
1203 // Hard-coding to maximum size of a symbol
|
236
|
1204 m_size = g_default_var_byte_size;
|
|
1205 m_alignment = g_default_var_alignment;
|
150
|
1206 }
|
|
1207
|
|
1208 void Materialize(lldb::StackFrameSP &frame_sp, IRMemoryMap &map,
|
|
1209 lldb::addr_t process_address, Status &err) override {
|
236
|
1210 Log *log = GetLog(LLDBLog::Expressions);
|
150
|
1211
|
|
1212 const lldb::addr_t load_addr = process_address + m_offset;
|
|
1213
|
|
1214 if (log) {
|
|
1215 LLDB_LOGF(log,
|
|
1216 "EntitySymbol::Materialize [address = 0x%" PRIx64
|
|
1217 ", m_symbol = %s]",
|
|
1218 (uint64_t)load_addr, m_symbol.GetName().AsCString());
|
|
1219 }
|
|
1220
|
|
1221 const Address sym_address = m_symbol.GetAddress();
|
|
1222
|
221
|
1223 ExecutionContextScope *exe_scope = frame_sp.get();
|
|
1224 if (!exe_scope)
|
|
1225 exe_scope = map.GetBestExecutionContextScope();
|
150
|
1226
|
|
1227 lldb::TargetSP target_sp;
|
|
1228
|
|
1229 if (exe_scope)
|
|
1230 target_sp = map.GetBestExecutionContextScope()->CalculateTarget();
|
|
1231
|
|
1232 if (!target_sp) {
|
|
1233 err.SetErrorStringWithFormat(
|
|
1234 "couldn't resolve symbol %s because there is no target",
|
|
1235 m_symbol.GetName().AsCString());
|
|
1236 return;
|
|
1237 }
|
|
1238
|
|
1239 lldb::addr_t resolved_address = sym_address.GetLoadAddress(target_sp.get());
|
|
1240
|
|
1241 if (resolved_address == LLDB_INVALID_ADDRESS)
|
|
1242 resolved_address = sym_address.GetFileAddress();
|
|
1243
|
|
1244 Status pointer_write_error;
|
|
1245
|
|
1246 map.WritePointerToMemory(load_addr, resolved_address, pointer_write_error);
|
|
1247
|
|
1248 if (!pointer_write_error.Success()) {
|
|
1249 err.SetErrorStringWithFormat(
|
|
1250 "couldn't write the address of symbol %s: %s",
|
|
1251 m_symbol.GetName().AsCString(), pointer_write_error.AsCString());
|
|
1252 return;
|
|
1253 }
|
|
1254 }
|
|
1255
|
|
1256 void Dematerialize(lldb::StackFrameSP &frame_sp, IRMemoryMap &map,
|
|
1257 lldb::addr_t process_address, lldb::addr_t frame_top,
|
|
1258 lldb::addr_t frame_bottom, Status &err) override {
|
236
|
1259 Log *log = GetLog(LLDBLog::Expressions);
|
150
|
1260
|
|
1261 const lldb::addr_t load_addr = process_address + m_offset;
|
|
1262
|
|
1263 if (log) {
|
|
1264 LLDB_LOGF(log,
|
|
1265 "EntitySymbol::Dematerialize [address = 0x%" PRIx64
|
|
1266 ", m_symbol = %s]",
|
|
1267 (uint64_t)load_addr, m_symbol.GetName().AsCString());
|
|
1268 }
|
|
1269
|
|
1270 // no work needs to be done
|
|
1271 }
|
|
1272
|
|
1273 void DumpToLog(IRMemoryMap &map, lldb::addr_t process_address,
|
|
1274 Log *log) override {
|
|
1275 StreamString dump_stream;
|
|
1276
|
|
1277 Status err;
|
|
1278
|
|
1279 const lldb::addr_t load_addr = process_address + m_offset;
|
|
1280
|
|
1281 dump_stream.Printf("0x%" PRIx64 ": EntitySymbol (%s)\n", load_addr,
|
|
1282 m_symbol.GetName().AsCString());
|
|
1283
|
|
1284 {
|
|
1285 dump_stream.Printf("Pointer:\n");
|
|
1286
|
|
1287 DataBufferHeap data(m_size, 0);
|
|
1288
|
|
1289 map.ReadMemory(data.GetBytes(), load_addr, m_size, err);
|
|
1290
|
|
1291 if (!err.Success()) {
|
|
1292 dump_stream.Printf(" <could not be read>\n");
|
|
1293 } else {
|
|
1294 DumpHexBytes(&dump_stream, data.GetBytes(), data.GetByteSize(), 16,
|
|
1295 load_addr);
|
|
1296
|
|
1297 dump_stream.PutChar('\n');
|
|
1298 }
|
|
1299 }
|
|
1300
|
|
1301 log->PutString(dump_stream.GetString());
|
|
1302 }
|
|
1303
|
|
1304 void Wipe(IRMemoryMap &map, lldb::addr_t process_address) override {}
|
|
1305
|
|
1306 private:
|
|
1307 Symbol m_symbol;
|
|
1308 };
|
|
1309
|
|
1310 uint32_t Materializer::AddSymbol(const Symbol &symbol_sp, Status &err) {
|
|
1311 EntityVector::iterator iter = m_entities.insert(m_entities.end(), EntityUP());
|
221
|
1312 *iter = std::make_unique<EntitySymbol>(symbol_sp);
|
150
|
1313 uint32_t ret = AddStructMember(**iter);
|
|
1314 (*iter)->SetOffset(ret);
|
|
1315 return ret;
|
|
1316 }
|
|
1317
|
|
1318 class EntityRegister : public Materializer::Entity {
|
|
1319 public:
|
|
1320 EntityRegister(const RegisterInfo ®ister_info)
|
|
1321 : Entity(), m_register_info(register_info) {
|
|
1322 // Hard-coding alignment conservatively
|
|
1323 m_size = m_register_info.byte_size;
|
|
1324 m_alignment = m_register_info.byte_size;
|
|
1325 }
|
|
1326
|
|
1327 void Materialize(lldb::StackFrameSP &frame_sp, IRMemoryMap &map,
|
|
1328 lldb::addr_t process_address, Status &err) override {
|
236
|
1329 Log *log = GetLog(LLDBLog::Expressions);
|
150
|
1330
|
|
1331 const lldb::addr_t load_addr = process_address + m_offset;
|
|
1332
|
|
1333 if (log) {
|
|
1334 LLDB_LOGF(log,
|
|
1335 "EntityRegister::Materialize [address = 0x%" PRIx64
|
|
1336 ", m_register_info = %s]",
|
|
1337 (uint64_t)load_addr, m_register_info.name);
|
|
1338 }
|
|
1339
|
|
1340 RegisterValue reg_value;
|
|
1341
|
|
1342 if (!frame_sp.get()) {
|
|
1343 err.SetErrorStringWithFormat(
|
|
1344 "couldn't materialize register %s without a stack frame",
|
|
1345 m_register_info.name);
|
|
1346 return;
|
|
1347 }
|
|
1348
|
|
1349 lldb::RegisterContextSP reg_context_sp = frame_sp->GetRegisterContext();
|
|
1350
|
|
1351 if (!reg_context_sp->ReadRegister(&m_register_info, reg_value)) {
|
|
1352 err.SetErrorStringWithFormat("couldn't read the value of register %s",
|
|
1353 m_register_info.name);
|
|
1354 return;
|
|
1355 }
|
|
1356
|
|
1357 DataExtractor register_data;
|
|
1358
|
|
1359 if (!reg_value.GetData(register_data)) {
|
|
1360 err.SetErrorStringWithFormat("couldn't get the data for register %s",
|
|
1361 m_register_info.name);
|
|
1362 return;
|
|
1363 }
|
|
1364
|
|
1365 if (register_data.GetByteSize() != m_register_info.byte_size) {
|
|
1366 err.SetErrorStringWithFormat(
|
|
1367 "data for register %s had size %llu but we expected %llu",
|
|
1368 m_register_info.name, (unsigned long long)register_data.GetByteSize(),
|
|
1369 (unsigned long long)m_register_info.byte_size);
|
|
1370 return;
|
|
1371 }
|
|
1372
|
|
1373 m_register_contents = std::make_shared<DataBufferHeap>(
|
|
1374 register_data.GetDataStart(), register_data.GetByteSize());
|
|
1375
|
|
1376 Status write_error;
|
|
1377
|
|
1378 map.WriteMemory(load_addr, register_data.GetDataStart(),
|
|
1379 register_data.GetByteSize(), write_error);
|
|
1380
|
|
1381 if (!write_error.Success()) {
|
|
1382 err.SetErrorStringWithFormat(
|
|
1383 "couldn't write the contents of register %s: %s",
|
|
1384 m_register_info.name, write_error.AsCString());
|
|
1385 return;
|
|
1386 }
|
|
1387 }
|
|
1388
|
|
1389 void Dematerialize(lldb::StackFrameSP &frame_sp, IRMemoryMap &map,
|
|
1390 lldb::addr_t process_address, lldb::addr_t frame_top,
|
|
1391 lldb::addr_t frame_bottom, Status &err) override {
|
236
|
1392 Log *log = GetLog(LLDBLog::Expressions);
|
150
|
1393
|
|
1394 const lldb::addr_t load_addr = process_address + m_offset;
|
|
1395
|
|
1396 if (log) {
|
|
1397 LLDB_LOGF(log,
|
|
1398 "EntityRegister::Dematerialize [address = 0x%" PRIx64
|
|
1399 ", m_register_info = %s]",
|
|
1400 (uint64_t)load_addr, m_register_info.name);
|
|
1401 }
|
|
1402
|
|
1403 Status extract_error;
|
|
1404
|
|
1405 DataExtractor register_data;
|
|
1406
|
|
1407 if (!frame_sp.get()) {
|
|
1408 err.SetErrorStringWithFormat(
|
|
1409 "couldn't dematerialize register %s without a stack frame",
|
|
1410 m_register_info.name);
|
|
1411 return;
|
|
1412 }
|
|
1413
|
|
1414 lldb::RegisterContextSP reg_context_sp = frame_sp->GetRegisterContext();
|
|
1415
|
|
1416 map.GetMemoryData(register_data, load_addr, m_register_info.byte_size,
|
|
1417 extract_error);
|
|
1418
|
|
1419 if (!extract_error.Success()) {
|
|
1420 err.SetErrorStringWithFormat("couldn't get the data for register %s: %s",
|
|
1421 m_register_info.name,
|
|
1422 extract_error.AsCString());
|
|
1423 return;
|
|
1424 }
|
|
1425
|
|
1426 if (!memcmp(register_data.GetDataStart(), m_register_contents->GetBytes(),
|
|
1427 register_data.GetByteSize())) {
|
|
1428 // No write required, and in particular we avoid errors if the register
|
|
1429 // wasn't writable
|
|
1430
|
|
1431 m_register_contents.reset();
|
|
1432 return;
|
|
1433 }
|
|
1434
|
|
1435 m_register_contents.reset();
|
|
1436
|
221
|
1437 RegisterValue register_value(register_data.GetData(),
|
|
1438 register_data.GetByteOrder());
|
150
|
1439
|
|
1440 if (!reg_context_sp->WriteRegister(&m_register_info, register_value)) {
|
|
1441 err.SetErrorStringWithFormat("couldn't write the value of register %s",
|
|
1442 m_register_info.name);
|
|
1443 return;
|
|
1444 }
|
|
1445 }
|
|
1446
|
|
1447 void DumpToLog(IRMemoryMap &map, lldb::addr_t process_address,
|
|
1448 Log *log) override {
|
|
1449 StreamString dump_stream;
|
|
1450
|
|
1451 Status err;
|
|
1452
|
|
1453 const lldb::addr_t load_addr = process_address + m_offset;
|
|
1454
|
|
1455 dump_stream.Printf("0x%" PRIx64 ": EntityRegister (%s)\n", load_addr,
|
|
1456 m_register_info.name);
|
|
1457
|
|
1458 {
|
|
1459 dump_stream.Printf("Value:\n");
|
|
1460
|
|
1461 DataBufferHeap data(m_size, 0);
|
|
1462
|
|
1463 map.ReadMemory(data.GetBytes(), load_addr, m_size, err);
|
|
1464
|
|
1465 if (!err.Success()) {
|
|
1466 dump_stream.Printf(" <could not be read>\n");
|
|
1467 } else {
|
|
1468 DumpHexBytes(&dump_stream, data.GetBytes(), data.GetByteSize(), 16,
|
|
1469 load_addr);
|
|
1470
|
|
1471 dump_stream.PutChar('\n');
|
|
1472 }
|
|
1473 }
|
|
1474
|
|
1475 log->PutString(dump_stream.GetString());
|
|
1476 }
|
|
1477
|
|
1478 void Wipe(IRMemoryMap &map, lldb::addr_t process_address) override {}
|
|
1479
|
|
1480 private:
|
|
1481 RegisterInfo m_register_info;
|
|
1482 lldb::DataBufferSP m_register_contents;
|
|
1483 };
|
|
1484
|
|
1485 uint32_t Materializer::AddRegister(const RegisterInfo ®ister_info,
|
|
1486 Status &err) {
|
|
1487 EntityVector::iterator iter = m_entities.insert(m_entities.end(), EntityUP());
|
221
|
1488 *iter = std::make_unique<EntityRegister>(register_info);
|
150
|
1489 uint32_t ret = AddStructMember(**iter);
|
|
1490 (*iter)->SetOffset(ret);
|
|
1491 return ret;
|
|
1492 }
|
|
1493
|
|
1494 Materializer::~Materializer() {
|
|
1495 DematerializerSP dematerializer_sp = m_dematerializer_wp.lock();
|
|
1496
|
|
1497 if (dematerializer_sp)
|
|
1498 dematerializer_sp->Wipe();
|
|
1499 }
|
|
1500
|
|
1501 Materializer::DematerializerSP
|
|
1502 Materializer::Materialize(lldb::StackFrameSP &frame_sp, IRMemoryMap &map,
|
|
1503 lldb::addr_t process_address, Status &error) {
|
|
1504 ExecutionContextScope *exe_scope = frame_sp.get();
|
|
1505 if (!exe_scope)
|
|
1506 exe_scope = map.GetBestExecutionContextScope();
|
|
1507
|
|
1508 DematerializerSP dematerializer_sp = m_dematerializer_wp.lock();
|
|
1509
|
|
1510 if (dematerializer_sp) {
|
|
1511 error.SetErrorToGenericError();
|
|
1512 error.SetErrorString("Couldn't materialize: already materialized");
|
|
1513 }
|
|
1514
|
|
1515 DematerializerSP ret(
|
|
1516 new Dematerializer(*this, frame_sp, map, process_address));
|
|
1517
|
|
1518 if (!exe_scope) {
|
|
1519 error.SetErrorToGenericError();
|
|
1520 error.SetErrorString("Couldn't materialize: target doesn't exist");
|
|
1521 }
|
|
1522
|
|
1523 for (EntityUP &entity_up : m_entities) {
|
|
1524 entity_up->Materialize(frame_sp, map, process_address, error);
|
|
1525
|
|
1526 if (!error.Success())
|
|
1527 return DematerializerSP();
|
|
1528 }
|
|
1529
|
236
|
1530 if (Log *log = GetLog(LLDBLog::Expressions)) {
|
150
|
1531 LLDB_LOGF(
|
|
1532 log,
|
|
1533 "Materializer::Materialize (frame_sp = %p, process_address = 0x%" PRIx64
|
|
1534 ") materialized:",
|
|
1535 static_cast<void *>(frame_sp.get()), process_address);
|
|
1536 for (EntityUP &entity_up : m_entities)
|
|
1537 entity_up->DumpToLog(map, process_address, log);
|
|
1538 }
|
|
1539
|
|
1540 m_dematerializer_wp = ret;
|
|
1541
|
|
1542 return ret;
|
|
1543 }
|
|
1544
|
|
1545 void Materializer::Dematerializer::Dematerialize(Status &error,
|
|
1546 lldb::addr_t frame_bottom,
|
|
1547 lldb::addr_t frame_top) {
|
|
1548 lldb::StackFrameSP frame_sp;
|
|
1549
|
|
1550 lldb::ThreadSP thread_sp = m_thread_wp.lock();
|
|
1551 if (thread_sp)
|
|
1552 frame_sp = thread_sp->GetFrameWithStackID(m_stack_id);
|
|
1553
|
221
|
1554 ExecutionContextScope *exe_scope = frame_sp.get();
|
|
1555 if (!exe_scope)
|
|
1556 exe_scope = m_map->GetBestExecutionContextScope();
|
150
|
1557
|
|
1558 if (!IsValid()) {
|
|
1559 error.SetErrorToGenericError();
|
|
1560 error.SetErrorString("Couldn't dematerialize: invalid dematerializer");
|
|
1561 }
|
|
1562
|
|
1563 if (!exe_scope) {
|
|
1564 error.SetErrorToGenericError();
|
|
1565 error.SetErrorString("Couldn't dematerialize: target is gone");
|
|
1566 } else {
|
236
|
1567 if (Log *log = GetLog(LLDBLog::Expressions)) {
|
150
|
1568 LLDB_LOGF(log,
|
|
1569 "Materializer::Dematerialize (frame_sp = %p, process_address "
|
|
1570 "= 0x%" PRIx64 ") about to dematerialize:",
|
|
1571 static_cast<void *>(frame_sp.get()), m_process_address);
|
|
1572 for (EntityUP &entity_up : m_materializer->m_entities)
|
|
1573 entity_up->DumpToLog(*m_map, m_process_address, log);
|
|
1574 }
|
|
1575
|
|
1576 for (EntityUP &entity_up : m_materializer->m_entities) {
|
|
1577 entity_up->Dematerialize(frame_sp, *m_map, m_process_address, frame_top,
|
|
1578 frame_bottom, error);
|
|
1579
|
|
1580 if (!error.Success())
|
|
1581 break;
|
|
1582 }
|
|
1583 }
|
|
1584
|
|
1585 Wipe();
|
|
1586 }
|
|
1587
|
|
1588 void Materializer::Dematerializer::Wipe() {
|
|
1589 if (!IsValid())
|
|
1590 return;
|
|
1591
|
|
1592 for (EntityUP &entity_up : m_materializer->m_entities) {
|
|
1593 entity_up->Wipe(*m_map, m_process_address);
|
|
1594 }
|
|
1595
|
|
1596 m_materializer = nullptr;
|
|
1597 m_map = nullptr;
|
|
1598 m_process_address = LLDB_INVALID_ADDRESS;
|
|
1599 }
|
|
1600
|
252
|
1601 Materializer::PersistentVariableDelegate::PersistentVariableDelegate() =
|
|
1602 default;
|
150
|
1603 Materializer::PersistentVariableDelegate::~PersistentVariableDelegate() =
|
|
1604 default;
|