2010-06-09 00:52:24 +08:00
|
|
|
//===-- Block.cpp -----------------------------------------------*- C++ -*-===//
|
|
|
|
//
|
|
|
|
// The LLVM Compiler Infrastructure
|
|
|
|
//
|
|
|
|
// This file is distributed under the University of Illinois Open Source
|
|
|
|
// License. See LICENSE.TXT for details.
|
|
|
|
//
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
|
|
|
#include "lldb/Symbol/Block.h"
|
|
|
|
#include "lldb/Symbol/Function.h"
|
|
|
|
#include "lldb/Core/Module.h"
|
|
|
|
#include "lldb/Core/Section.h"
|
|
|
|
#include "lldb/Symbol/SymbolVendor.h"
|
|
|
|
#include "lldb/Symbol/VariableList.h"
|
|
|
|
|
|
|
|
using namespace lldb;
|
|
|
|
using namespace lldb_private;
|
|
|
|
|
2010-08-21 10:22:51 +08:00
|
|
|
Block::Block(lldb::user_id_t uid) :
|
2010-06-09 00:52:24 +08:00
|
|
|
UserID(uid),
|
2010-08-21 10:22:51 +08:00
|
|
|
m_parent_scope (NULL),
|
|
|
|
m_sibling (NULL),
|
|
|
|
m_children (),
|
|
|
|
m_ranges (),
|
|
|
|
m_inlineInfoSP (),
|
2010-08-25 05:05:24 +08:00
|
|
|
m_variable_list_sp (),
|
2010-08-21 10:22:51 +08:00
|
|
|
m_parsed_block_info (false),
|
|
|
|
m_parsed_block_variables (false),
|
|
|
|
m_parsed_child_blocks (false)
|
2010-06-09 00:52:24 +08:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
Block::~Block ()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2010-06-29 05:30:43 +08:00
|
|
|
void
|
2010-09-15 07:36:40 +08:00
|
|
|
Block::GetDescription(Stream *s, Function *function, lldb::DescriptionLevel level, Target *target) const
|
2010-06-29 05:30:43 +08:00
|
|
|
{
|
2010-09-10 09:30:46 +08:00
|
|
|
*s << "id = " << ((const UserID&)*this);
|
|
|
|
|
2010-06-29 05:30:43 +08:00
|
|
|
size_t num_ranges = m_ranges.size();
|
|
|
|
if (num_ranges)
|
|
|
|
{
|
|
|
|
|
|
|
|
addr_t base_addr = LLDB_INVALID_ADDRESS;
|
2010-09-15 07:36:40 +08:00
|
|
|
if (target)
|
|
|
|
base_addr = function->GetAddressRange().GetBaseAddress().GetLoadAddress(target);
|
2010-06-29 05:30:43 +08:00
|
|
|
if (base_addr == LLDB_INVALID_ADDRESS)
|
2010-08-21 10:22:51 +08:00
|
|
|
base_addr = function->GetAddressRange().GetBaseAddress().GetFileAddress();
|
2010-06-29 05:30:43 +08:00
|
|
|
|
2010-09-10 09:30:46 +08:00
|
|
|
s->Printf(", range%s = ", num_ranges > 1 ? "s" : "");
|
2010-06-29 05:30:43 +08:00
|
|
|
std::vector<VMRange>::const_iterator pos, end = m_ranges.end();
|
|
|
|
for (pos = m_ranges.begin(); pos != end; ++pos)
|
|
|
|
pos->Dump(s, base_addr, 4);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_inlineInfoSP.get() != NULL)
|
2010-09-15 13:51:24 +08:00
|
|
|
{
|
|
|
|
bool show_fullpaths = (level == eDescriptionLevelVerbose);
|
|
|
|
m_inlineInfoSP->Dump(s, show_fullpaths);
|
|
|
|
}
|
2010-06-29 05:30:43 +08:00
|
|
|
}
|
|
|
|
|
2010-06-09 00:52:24 +08:00
|
|
|
void
|
|
|
|
Block::Dump(Stream *s, addr_t base_addr, int32_t depth, bool show_context) const
|
|
|
|
{
|
|
|
|
if (depth < 0)
|
|
|
|
{
|
2010-08-21 10:22:51 +08:00
|
|
|
Block *parent = GetParent();
|
|
|
|
if (parent)
|
|
|
|
{
|
|
|
|
// We have a depth that is less than zero, print our parent blocks
|
|
|
|
// first
|
|
|
|
parent->Dump(s, base_addr, depth + 1, show_context);
|
|
|
|
}
|
2010-06-09 00:52:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
s->Printf("%.*p: ", (int)sizeof(void*) * 2, this);
|
|
|
|
s->Indent();
|
|
|
|
*s << "Block" << ((const UserID&)*this);
|
2010-06-29 05:30:43 +08:00
|
|
|
const Block* parent_block = GetParent();
|
|
|
|
if (parent_block)
|
2010-06-09 00:52:24 +08:00
|
|
|
{
|
2010-06-29 05:30:43 +08:00
|
|
|
s->Printf(", parent = {0x%8.8x}", parent_block->GetID());
|
2010-06-09 00:52:24 +08:00
|
|
|
}
|
|
|
|
if (m_inlineInfoSP.get() != NULL)
|
2010-09-15 13:51:24 +08:00
|
|
|
{
|
|
|
|
bool show_fullpaths = false;
|
|
|
|
m_inlineInfoSP->Dump(s, show_fullpaths);
|
|
|
|
}
|
2010-06-09 00:52:24 +08:00
|
|
|
|
|
|
|
if (!m_ranges.empty())
|
|
|
|
{
|
|
|
|
*s << ", ranges =";
|
|
|
|
std::vector<VMRange>::const_iterator pos;
|
|
|
|
std::vector<VMRange>::const_iterator end = m_ranges.end();
|
|
|
|
for (pos = m_ranges.begin(); pos != end; ++pos)
|
|
|
|
{
|
|
|
|
if (parent_block != NULL && parent_block->Contains(*pos) == false)
|
|
|
|
*s << '!';
|
|
|
|
else
|
|
|
|
*s << ' ';
|
|
|
|
pos->Dump(s, base_addr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
s->EOL();
|
|
|
|
|
|
|
|
if (depth > 0)
|
|
|
|
{
|
|
|
|
s->IndentMore();
|
|
|
|
|
2010-08-25 05:05:24 +08:00
|
|
|
if (m_variable_list_sp.get())
|
2010-06-09 00:52:24 +08:00
|
|
|
{
|
2010-08-25 05:05:24 +08:00
|
|
|
m_variable_list_sp->Dump(s, show_context);
|
2010-06-09 00:52:24 +08:00
|
|
|
}
|
|
|
|
|
2010-08-21 10:22:51 +08:00
|
|
|
for (Block *child_block = GetFirstChild(); child_block != NULL; child_block = child_block->GetSibling())
|
2010-06-09 00:52:24 +08:00
|
|
|
{
|
2010-08-21 10:22:51 +08:00
|
|
|
child_block->Dump(s, base_addr, depth - 1, show_context);
|
2010-06-09 00:52:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
s->IndentLess();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-08-21 10:22:51 +08:00
|
|
|
Block *
|
|
|
|
Block::FindBlockByID (user_id_t block_id)
|
|
|
|
{
|
|
|
|
if (block_id == GetID())
|
|
|
|
return this;
|
|
|
|
|
|
|
|
Block *matching_block = NULL;
|
|
|
|
for (Block *child_block = GetFirstChild(); child_block != NULL; child_block = child_block->GetSibling())
|
|
|
|
{
|
|
|
|
matching_block = child_block->FindBlockByID (block_id);
|
|
|
|
if (matching_block)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return matching_block;
|
|
|
|
}
|
|
|
|
|
2010-06-09 00:52:24 +08:00
|
|
|
void
|
2010-08-25 05:05:24 +08:00
|
|
|
Block::CalculateSymbolContext (SymbolContext* sc)
|
2010-06-09 00:52:24 +08:00
|
|
|
{
|
2010-08-21 10:22:51 +08:00
|
|
|
if (m_parent_scope)
|
|
|
|
m_parent_scope->CalculateSymbolContext(sc);
|
2010-06-09 00:52:24 +08:00
|
|
|
sc->block = this;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2010-09-07 12:20:48 +08:00
|
|
|
Block::DumpStopContext
|
|
|
|
(
|
|
|
|
Stream *s,
|
|
|
|
const SymbolContext *sc_ptr,
|
|
|
|
const Declaration *child_inline_call_site,
|
|
|
|
bool show_fullpaths,
|
|
|
|
bool show_inline_blocks)
|
2010-06-09 00:52:24 +08:00
|
|
|
{
|
2011-04-23 10:04:55 +08:00
|
|
|
const InlineFunctionInfo* inline_info = NULL;
|
|
|
|
Block* inlined_block;
|
|
|
|
if (sc_ptr)
|
|
|
|
inlined_block = GetContainingInlinedBlock ();
|
|
|
|
else
|
|
|
|
inlined_block = GetInlinedParent();
|
2010-06-09 00:52:24 +08:00
|
|
|
|
2011-04-23 10:04:55 +08:00
|
|
|
if (inlined_block)
|
|
|
|
inline_info = inlined_block->GetInlinedFunctionInfo();
|
2010-09-07 12:20:48 +08:00
|
|
|
const Declaration *inline_call_site = child_inline_call_site;
|
2010-06-09 00:52:24 +08:00
|
|
|
if (inline_info)
|
|
|
|
{
|
2010-09-07 12:20:48 +08:00
|
|
|
inline_call_site = &inline_info->GetCallSite();
|
|
|
|
if (sc_ptr)
|
2010-06-09 00:52:24 +08:00
|
|
|
{
|
2010-09-07 12:20:48 +08:00
|
|
|
// First frame in a frame with inlined functions
|
2010-06-09 00:52:24 +08:00
|
|
|
s->PutCString (" [inlined]");
|
|
|
|
}
|
2011-02-08 10:40:32 +08:00
|
|
|
if (show_inline_blocks && child_inline_call_site)
|
2010-09-07 12:20:48 +08:00
|
|
|
s->EOL();
|
|
|
|
else
|
|
|
|
s->PutChar(' ');
|
2011-04-23 10:04:55 +08:00
|
|
|
|
|
|
|
if (sc_ptr == NULL)
|
|
|
|
s->Indent();
|
|
|
|
|
2010-09-07 12:20:48 +08:00
|
|
|
s->PutCString(inline_info->GetName ().AsCString());
|
|
|
|
|
|
|
|
if (child_inline_call_site && child_inline_call_site->IsValid())
|
2010-06-09 00:52:24 +08:00
|
|
|
{
|
2010-09-07 12:20:48 +08:00
|
|
|
s->PutCString(" at ");
|
|
|
|
child_inline_call_site->DumpStopContext (s, show_fullpaths);
|
2010-06-09 00:52:24 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-04-23 10:04:55 +08:00
|
|
|
// The first call to this function from something that has a symbol
|
|
|
|
// context will pass in a valid sc_ptr. Subsequent calls to this function
|
|
|
|
// from this function for inline purposes will NULL out sc_ptr. So on the
|
|
|
|
// first time through we dump the line table entry (which is always at the
|
|
|
|
// deepest inline code block). And subsequent calls to this function we
|
|
|
|
// will use hte inline call site information to print line numbers.
|
2010-09-07 12:20:48 +08:00
|
|
|
if (sc_ptr)
|
2010-06-09 00:52:24 +08:00
|
|
|
{
|
|
|
|
// If we have any inlined functions, this will be the deepest most
|
|
|
|
// inlined location
|
2010-09-07 12:20:48 +08:00
|
|
|
if (sc_ptr->line_entry.IsValid())
|
2010-06-09 00:52:24 +08:00
|
|
|
{
|
|
|
|
s->PutCString(" at ");
|
2010-09-07 12:20:48 +08:00
|
|
|
sc_ptr->line_entry.DumpStopContext (s, show_fullpaths);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (show_inline_blocks)
|
|
|
|
{
|
2011-04-23 10:04:55 +08:00
|
|
|
if (inlined_block)
|
2010-09-07 12:20:48 +08:00
|
|
|
{
|
2011-04-23 10:04:55 +08:00
|
|
|
inlined_block->Block::DumpStopContext (s,
|
|
|
|
NULL,
|
|
|
|
inline_call_site,
|
|
|
|
show_fullpaths,
|
|
|
|
show_inline_blocks);
|
2010-09-07 12:20:48 +08:00
|
|
|
}
|
|
|
|
else if (child_inline_call_site)
|
|
|
|
{
|
|
|
|
SymbolContext sc;
|
|
|
|
CalculateSymbolContext(&sc);
|
|
|
|
if (sc.function)
|
|
|
|
{
|
|
|
|
s->EOL();
|
|
|
|
s->Indent (sc.function->GetMangled().GetName().AsCString());
|
|
|
|
if (child_inline_call_site && child_inline_call_site->IsValid())
|
|
|
|
{
|
|
|
|
s->PutCString(" at ");
|
|
|
|
child_inline_call_site->DumpStopContext (s, show_fullpaths);
|
|
|
|
}
|
|
|
|
}
|
2010-06-09 00:52:24 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
Block::DumpSymbolContext(Stream *s)
|
|
|
|
{
|
2010-08-21 10:22:51 +08:00
|
|
|
SymbolContext sc;
|
|
|
|
CalculateSymbolContext(&sc);
|
|
|
|
if (sc.function)
|
|
|
|
sc.function->DumpSymbolContext(s);
|
2010-06-09 00:52:24 +08:00
|
|
|
s->Printf(", Block{0x%8.8x}", GetID());
|
|
|
|
}
|
|
|
|
|
2010-09-20 13:20:02 +08:00
|
|
|
void
|
|
|
|
Block::DumpAddressRanges (Stream *s, lldb::addr_t base_addr)
|
|
|
|
{
|
|
|
|
if (!m_ranges.empty())
|
|
|
|
{
|
|
|
|
std::vector<VMRange>::const_iterator pos, end = m_ranges.end();
|
|
|
|
for (pos = m_ranges.begin(); pos != end; ++pos)
|
|
|
|
pos->Dump (s, base_addr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-06-09 00:52:24 +08:00
|
|
|
bool
|
|
|
|
Block::Contains (addr_t range_offset) const
|
|
|
|
{
|
|
|
|
return VMRange::ContainsValue(m_ranges, range_offset);
|
|
|
|
}
|
|
|
|
|
2010-09-03 05:44:10 +08:00
|
|
|
bool
|
|
|
|
Block::Contains (const Block *block) const
|
|
|
|
{
|
|
|
|
if (this == block)
|
2010-09-14 11:16:58 +08:00
|
|
|
return false; // This block doesn't contain itself...
|
2010-09-03 05:44:10 +08:00
|
|
|
|
2010-09-14 11:16:58 +08:00
|
|
|
// Walk the parent chain for "block" and see if any if them match this block
|
2010-09-03 05:44:10 +08:00
|
|
|
const Block *block_parent;
|
|
|
|
for (block_parent = block->GetParent();
|
|
|
|
block_parent != NULL;
|
|
|
|
block_parent = block_parent->GetParent())
|
|
|
|
{
|
2010-09-14 11:16:58 +08:00
|
|
|
if (this == block_parent)
|
|
|
|
return true; // One of the parents of "block" is this object!
|
2010-09-03 05:44:10 +08:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2010-06-09 00:52:24 +08:00
|
|
|
bool
|
|
|
|
Block::Contains (const VMRange& range) const
|
|
|
|
{
|
|
|
|
return VMRange::ContainsRange(m_ranges, range);
|
|
|
|
}
|
|
|
|
|
2010-08-21 10:22:51 +08:00
|
|
|
Block *
|
|
|
|
Block::GetParent () const
|
2010-06-09 00:52:24 +08:00
|
|
|
{
|
2010-08-21 10:22:51 +08:00
|
|
|
if (m_parent_scope)
|
2010-06-09 00:52:24 +08:00
|
|
|
{
|
2010-08-21 10:22:51 +08:00
|
|
|
SymbolContext sc;
|
|
|
|
m_parent_scope->CalculateSymbolContext(&sc);
|
|
|
|
if (sc.block)
|
|
|
|
return sc.block;
|
2010-06-09 00:52:24 +08:00
|
|
|
}
|
2010-08-21 10:22:51 +08:00
|
|
|
return NULL;
|
2010-06-09 00:52:24 +08:00
|
|
|
}
|
|
|
|
|
Added support for inlined stack frames being represented as real stack frames
which is now on by default. Frames are gotten from the unwinder as concrete
frames, then if inline frames are to be shown, extra information to track
and reconstruct these frames is cached with each Thread and exanded as needed.
I added an inline height as part of the lldb_private::StackID class, the class
that helps us uniquely identify stack frames. This allows for two frames to
shared the same call frame address, yet differ only in inline height.
Fixed setting breakpoint by address to not require addresses to resolve.
A quick example:
% cat main.cpp
% ./build/Debug/lldb test/stl/a.out
Current executable set to 'test/stl/a.out' (x86_64).
(lldb) breakpoint set --address 0x0000000100000d31
Breakpoint created: 1: address = 0x0000000100000d31, locations = 1
(lldb) r
Launching 'a.out' (x86_64)
(lldb) Process 38031 Stopped
* thread #1: tid = 0x2e03, pc = 0x0000000100000d31, where = a.out`main [inlined] std::string::_M_data() const at /usr/include/c++/4.2.1/bits/basic_string.h:280, stop reason = breakpoint 1.1, queue = com.apple.main-thread
277
278 _CharT*
279 _M_data() const
280 -> { return _M_dataplus._M_p; }
281
282 _CharT*
283 _M_data(_CharT* __p)
(lldb) bt
thread #1: tid = 0x2e03, stop reason = breakpoint 1.1, queue = com.apple.main-thread
frame #0: pc = 0x0000000100000d31, where = a.out`main [inlined] std::string::_M_data() const at /usr/include/c++/4.2.1/bits/basic_string.h:280
frame #1: pc = 0x0000000100000d31, where = a.out`main [inlined] std::string::_M_rep() const at /usr/include/c++/4.2.1/bits/basic_string.h:288
frame #2: pc = 0x0000000100000d31, where = a.out`main [inlined] std::string::size() const at /usr/include/c++/4.2.1/bits/basic_string.h:606
frame #3: pc = 0x0000000100000d31, where = a.out`main [inlined] operator<< <char, std::char_traits<char>, std::allocator<char> > at /usr/include/c++/4.2.1/bits/basic_string.h:2414
frame #4: pc = 0x0000000100000d31, where = a.out`main + 33 at /Volumes/work/gclayton/Documents/src/lldb/test/stl/main.cpp:14
frame #5: pc = 0x0000000100000d08, where = a.out`start + 52
Each inline frame contains only the variables that they contain and each inlined
stack frame is treated as a single entity.
llvm-svn: 111877
2010-08-24 08:45:41 +08:00
|
|
|
Block *
|
2010-08-25 05:05:24 +08:00
|
|
|
Block::GetContainingInlinedBlock ()
|
|
|
|
{
|
2010-09-07 12:20:48 +08:00
|
|
|
if (GetInlinedFunctionInfo())
|
2010-08-25 05:05:24 +08:00
|
|
|
return this;
|
|
|
|
return GetInlinedParent ();
|
|
|
|
}
|
|
|
|
|
|
|
|
Block *
|
|
|
|
Block::GetInlinedParent ()
|
Added support for inlined stack frames being represented as real stack frames
which is now on by default. Frames are gotten from the unwinder as concrete
frames, then if inline frames are to be shown, extra information to track
and reconstruct these frames is cached with each Thread and exanded as needed.
I added an inline height as part of the lldb_private::StackID class, the class
that helps us uniquely identify stack frames. This allows for two frames to
shared the same call frame address, yet differ only in inline height.
Fixed setting breakpoint by address to not require addresses to resolve.
A quick example:
% cat main.cpp
% ./build/Debug/lldb test/stl/a.out
Current executable set to 'test/stl/a.out' (x86_64).
(lldb) breakpoint set --address 0x0000000100000d31
Breakpoint created: 1: address = 0x0000000100000d31, locations = 1
(lldb) r
Launching 'a.out' (x86_64)
(lldb) Process 38031 Stopped
* thread #1: tid = 0x2e03, pc = 0x0000000100000d31, where = a.out`main [inlined] std::string::_M_data() const at /usr/include/c++/4.2.1/bits/basic_string.h:280, stop reason = breakpoint 1.1, queue = com.apple.main-thread
277
278 _CharT*
279 _M_data() const
280 -> { return _M_dataplus._M_p; }
281
282 _CharT*
283 _M_data(_CharT* __p)
(lldb) bt
thread #1: tid = 0x2e03, stop reason = breakpoint 1.1, queue = com.apple.main-thread
frame #0: pc = 0x0000000100000d31, where = a.out`main [inlined] std::string::_M_data() const at /usr/include/c++/4.2.1/bits/basic_string.h:280
frame #1: pc = 0x0000000100000d31, where = a.out`main [inlined] std::string::_M_rep() const at /usr/include/c++/4.2.1/bits/basic_string.h:288
frame #2: pc = 0x0000000100000d31, where = a.out`main [inlined] std::string::size() const at /usr/include/c++/4.2.1/bits/basic_string.h:606
frame #3: pc = 0x0000000100000d31, where = a.out`main [inlined] operator<< <char, std::char_traits<char>, std::allocator<char> > at /usr/include/c++/4.2.1/bits/basic_string.h:2414
frame #4: pc = 0x0000000100000d31, where = a.out`main + 33 at /Volumes/work/gclayton/Documents/src/lldb/test/stl/main.cpp:14
frame #5: pc = 0x0000000100000d08, where = a.out`start + 52
Each inline frame contains only the variables that they contain and each inlined
stack frame is treated as a single entity.
llvm-svn: 111877
2010-08-24 08:45:41 +08:00
|
|
|
{
|
|
|
|
Block *parent_block = GetParent ();
|
|
|
|
if (parent_block)
|
|
|
|
{
|
2010-09-07 12:20:48 +08:00
|
|
|
if (parent_block->GetInlinedFunctionInfo())
|
Added support for inlined stack frames being represented as real stack frames
which is now on by default. Frames are gotten from the unwinder as concrete
frames, then if inline frames are to be shown, extra information to track
and reconstruct these frames is cached with each Thread and exanded as needed.
I added an inline height as part of the lldb_private::StackID class, the class
that helps us uniquely identify stack frames. This allows for two frames to
shared the same call frame address, yet differ only in inline height.
Fixed setting breakpoint by address to not require addresses to resolve.
A quick example:
% cat main.cpp
% ./build/Debug/lldb test/stl/a.out
Current executable set to 'test/stl/a.out' (x86_64).
(lldb) breakpoint set --address 0x0000000100000d31
Breakpoint created: 1: address = 0x0000000100000d31, locations = 1
(lldb) r
Launching 'a.out' (x86_64)
(lldb) Process 38031 Stopped
* thread #1: tid = 0x2e03, pc = 0x0000000100000d31, where = a.out`main [inlined] std::string::_M_data() const at /usr/include/c++/4.2.1/bits/basic_string.h:280, stop reason = breakpoint 1.1, queue = com.apple.main-thread
277
278 _CharT*
279 _M_data() const
280 -> { return _M_dataplus._M_p; }
281
282 _CharT*
283 _M_data(_CharT* __p)
(lldb) bt
thread #1: tid = 0x2e03, stop reason = breakpoint 1.1, queue = com.apple.main-thread
frame #0: pc = 0x0000000100000d31, where = a.out`main [inlined] std::string::_M_data() const at /usr/include/c++/4.2.1/bits/basic_string.h:280
frame #1: pc = 0x0000000100000d31, where = a.out`main [inlined] std::string::_M_rep() const at /usr/include/c++/4.2.1/bits/basic_string.h:288
frame #2: pc = 0x0000000100000d31, where = a.out`main [inlined] std::string::size() const at /usr/include/c++/4.2.1/bits/basic_string.h:606
frame #3: pc = 0x0000000100000d31, where = a.out`main [inlined] operator<< <char, std::char_traits<char>, std::allocator<char> > at /usr/include/c++/4.2.1/bits/basic_string.h:2414
frame #4: pc = 0x0000000100000d31, where = a.out`main + 33 at /Volumes/work/gclayton/Documents/src/lldb/test/stl/main.cpp:14
frame #5: pc = 0x0000000100000d08, where = a.out`start + 52
Each inline frame contains only the variables that they contain and each inlined
stack frame is treated as a single entity.
llvm-svn: 111877
2010-08-24 08:45:41 +08:00
|
|
|
return parent_block;
|
|
|
|
else
|
|
|
|
return parent_block->GetInlinedParent();
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-08-25 05:05:24 +08:00
|
|
|
bool
|
|
|
|
Block::GetRangeContainingOffset (const addr_t offset, VMRange &range)
|
|
|
|
{
|
|
|
|
uint32_t range_idx = VMRange::FindRangeIndexThatContainsValue (m_ranges, offset);
|
|
|
|
if (range_idx < m_ranges.size())
|
|
|
|
{
|
|
|
|
range = m_ranges[range_idx];
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
range.Clear();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
Added support for inlined stack frames being represented as real stack frames
which is now on by default. Frames are gotten from the unwinder as concrete
frames, then if inline frames are to be shown, extra information to track
and reconstruct these frames is cached with each Thread and exanded as needed.
I added an inline height as part of the lldb_private::StackID class, the class
that helps us uniquely identify stack frames. This allows for two frames to
shared the same call frame address, yet differ only in inline height.
Fixed setting breakpoint by address to not require addresses to resolve.
A quick example:
% cat main.cpp
% ./build/Debug/lldb test/stl/a.out
Current executable set to 'test/stl/a.out' (x86_64).
(lldb) breakpoint set --address 0x0000000100000d31
Breakpoint created: 1: address = 0x0000000100000d31, locations = 1
(lldb) r
Launching 'a.out' (x86_64)
(lldb) Process 38031 Stopped
* thread #1: tid = 0x2e03, pc = 0x0000000100000d31, where = a.out`main [inlined] std::string::_M_data() const at /usr/include/c++/4.2.1/bits/basic_string.h:280, stop reason = breakpoint 1.1, queue = com.apple.main-thread
277
278 _CharT*
279 _M_data() const
280 -> { return _M_dataplus._M_p; }
281
282 _CharT*
283 _M_data(_CharT* __p)
(lldb) bt
thread #1: tid = 0x2e03, stop reason = breakpoint 1.1, queue = com.apple.main-thread
frame #0: pc = 0x0000000100000d31, where = a.out`main [inlined] std::string::_M_data() const at /usr/include/c++/4.2.1/bits/basic_string.h:280
frame #1: pc = 0x0000000100000d31, where = a.out`main [inlined] std::string::_M_rep() const at /usr/include/c++/4.2.1/bits/basic_string.h:288
frame #2: pc = 0x0000000100000d31, where = a.out`main [inlined] std::string::size() const at /usr/include/c++/4.2.1/bits/basic_string.h:606
frame #3: pc = 0x0000000100000d31, where = a.out`main [inlined] operator<< <char, std::char_traits<char>, std::allocator<char> > at /usr/include/c++/4.2.1/bits/basic_string.h:2414
frame #4: pc = 0x0000000100000d31, where = a.out`main + 33 at /Volumes/work/gclayton/Documents/src/lldb/test/stl/main.cpp:14
frame #5: pc = 0x0000000100000d08, where = a.out`start + 52
Each inline frame contains only the variables that they contain and each inlined
stack frame is treated as a single entity.
llvm-svn: 111877
2010-08-24 08:45:41 +08:00
|
|
|
bool
|
|
|
|
Block::GetRangeContainingAddress (const Address& addr, AddressRange &range)
|
|
|
|
{
|
|
|
|
SymbolContext sc;
|
|
|
|
CalculateSymbolContext(&sc);
|
|
|
|
if (sc.function)
|
|
|
|
{
|
|
|
|
const AddressRange &func_range = sc.function->GetAddressRange();
|
|
|
|
if (addr.GetSection() == func_range.GetBaseAddress().GetSection())
|
|
|
|
{
|
|
|
|
const addr_t addr_offset = addr.GetOffset();
|
|
|
|
const addr_t func_offset = func_range.GetBaseAddress().GetOffset();
|
|
|
|
if (addr_offset >= func_offset && addr_offset < func_offset + func_range.GetByteSize())
|
|
|
|
{
|
|
|
|
addr_t offset = addr_offset - func_offset;
|
|
|
|
|
|
|
|
uint32_t range_idx = VMRange::FindRangeIndexThatContainsValue (m_ranges, offset);
|
|
|
|
if (range_idx < m_ranges.size())
|
|
|
|
{
|
|
|
|
range.GetBaseAddress() = func_range.GetBaseAddress();
|
|
|
|
range.GetBaseAddress().SetOffset(func_offset + m_ranges[range_idx].GetBaseAddress());
|
|
|
|
range.SetByteSize(m_ranges[range_idx].GetByteSize());
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
range.Clear();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2011-04-23 10:04:55 +08:00
|
|
|
bool
|
|
|
|
Block::GetRangeAtIndex (uint32_t range_idx, AddressRange &range)
|
|
|
|
{
|
|
|
|
if (range_idx < m_ranges.size())
|
|
|
|
{
|
|
|
|
SymbolContext sc;
|
|
|
|
CalculateSymbolContext(&sc);
|
|
|
|
if (sc.function)
|
|
|
|
{
|
|
|
|
range.GetBaseAddress() = sc.function->GetAddressRange().GetBaseAddress();
|
|
|
|
range.GetBaseAddress().Slide(m_ranges[range_idx].GetBaseAddress ());
|
|
|
|
range.SetByteSize (m_ranges[range_idx].GetByteSize());
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2010-11-14 08:22:48 +08:00
|
|
|
|
|
|
|
bool
|
|
|
|
Block::GetStartAddress (Address &addr)
|
|
|
|
{
|
|
|
|
if (m_ranges.empty())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
SymbolContext sc;
|
|
|
|
CalculateSymbolContext(&sc);
|
|
|
|
if (sc.function)
|
|
|
|
{
|
|
|
|
addr = sc.function->GetAddressRange().GetBaseAddress();
|
|
|
|
addr.Slide(m_ranges.front().GetBaseAddress ());
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2010-06-09 00:52:24 +08:00
|
|
|
void
|
|
|
|
Block::AddRange(addr_t start_offset, addr_t end_offset)
|
|
|
|
{
|
|
|
|
m_ranges.resize(m_ranges.size()+1);
|
|
|
|
m_ranges.back().Reset(start_offset, end_offset);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return the current number of bytes that this object occupies in memory
|
|
|
|
size_t
|
|
|
|
Block::MemorySize() const
|
|
|
|
{
|
|
|
|
size_t mem_size = sizeof(Block) + m_ranges.size() * sizeof(VMRange);
|
|
|
|
if (m_inlineInfoSP.get())
|
|
|
|
mem_size += m_inlineInfoSP->MemorySize();
|
2010-08-25 05:05:24 +08:00
|
|
|
if (m_variable_list_sp.get())
|
|
|
|
mem_size += m_variable_list_sp->MemorySize();
|
2010-06-09 00:52:24 +08:00
|
|
|
return mem_size;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2010-08-21 10:22:51 +08:00
|
|
|
void
|
|
|
|
Block::AddChild(const BlockSP &child_block_sp)
|
2010-06-09 00:52:24 +08:00
|
|
|
{
|
2010-08-21 10:22:51 +08:00
|
|
|
if (child_block_sp)
|
|
|
|
{
|
|
|
|
Block *block_needs_sibling = NULL;
|
2010-06-09 00:52:24 +08:00
|
|
|
|
2010-08-21 10:22:51 +08:00
|
|
|
if (!m_children.empty())
|
|
|
|
block_needs_sibling = m_children.back().get();
|
2010-06-09 00:52:24 +08:00
|
|
|
|
2010-08-21 10:22:51 +08:00
|
|
|
child_block_sp->SetParentScope (this);
|
|
|
|
m_children.push_back (child_block_sp);
|
2010-06-09 00:52:24 +08:00
|
|
|
|
2010-08-21 10:22:51 +08:00
|
|
|
if (block_needs_sibling)
|
|
|
|
block_needs_sibling->SetSibling (child_block_sp.get());
|
|
|
|
}
|
2010-06-09 00:52:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Block::SetInlinedFunctionInfo(const char *name, const char *mangled, const Declaration *decl_ptr, const Declaration *call_decl_ptr)
|
|
|
|
{
|
|
|
|
m_inlineInfoSP.reset(new InlineFunctionInfo(name, mangled, decl_ptr, call_decl_ptr));
|
|
|
|
}
|
|
|
|
|
2010-08-19 03:29:16 +08:00
|
|
|
|
|
|
|
|
|
|
|
VariableListSP
|
|
|
|
Block::GetVariableList (bool get_child_variables, bool can_create)
|
|
|
|
{
|
|
|
|
VariableListSP variable_list_sp;
|
2010-08-21 10:22:51 +08:00
|
|
|
if (m_parsed_block_variables == false)
|
2010-08-19 03:29:16 +08:00
|
|
|
{
|
2010-08-25 05:05:24 +08:00
|
|
|
if (m_variable_list_sp.get() == NULL && can_create)
|
2010-08-21 10:22:51 +08:00
|
|
|
{
|
|
|
|
m_parsed_block_variables = true;
|
|
|
|
SymbolContext sc;
|
|
|
|
CalculateSymbolContext(&sc);
|
|
|
|
assert(sc.module_sp);
|
|
|
|
sc.module_sp->GetSymbolVendor()->ParseVariablesForContext(sc);
|
|
|
|
}
|
2010-08-19 03:29:16 +08:00
|
|
|
}
|
|
|
|
|
2010-08-25 05:05:24 +08:00
|
|
|
if (m_variable_list_sp.get())
|
2010-08-19 03:29:16 +08:00
|
|
|
{
|
|
|
|
variable_list_sp.reset(new VariableList());
|
|
|
|
if (variable_list_sp.get())
|
2010-08-25 05:05:24 +08:00
|
|
|
variable_list_sp->AddVariables(m_variable_list_sp.get());
|
2010-08-19 03:29:16 +08:00
|
|
|
|
|
|
|
if (get_child_variables)
|
|
|
|
{
|
2010-09-07 12:20:48 +08:00
|
|
|
for (Block *child_block = GetFirstChild();
|
|
|
|
child_block != NULL;
|
|
|
|
child_block = child_block->GetSibling())
|
|
|
|
{
|
|
|
|
if (child_block->GetInlinedFunctionInfo() == NULL)
|
|
|
|
{
|
|
|
|
VariableListSP child_block_variable_list(child_block->GetVariableList(get_child_variables, can_create));
|
|
|
|
if (child_block_variable_list.get())
|
|
|
|
variable_list_sp->AddVariables(child_block_variable_list.get());
|
|
|
|
}
|
|
|
|
|
2010-08-19 03:29:16 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return variable_list_sp;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t
|
Added support for inlined stack frames being represented as real stack frames
which is now on by default. Frames are gotten from the unwinder as concrete
frames, then if inline frames are to be shown, extra information to track
and reconstruct these frames is cached with each Thread and exanded as needed.
I added an inline height as part of the lldb_private::StackID class, the class
that helps us uniquely identify stack frames. This allows for two frames to
shared the same call frame address, yet differ only in inline height.
Fixed setting breakpoint by address to not require addresses to resolve.
A quick example:
% cat main.cpp
% ./build/Debug/lldb test/stl/a.out
Current executable set to 'test/stl/a.out' (x86_64).
(lldb) breakpoint set --address 0x0000000100000d31
Breakpoint created: 1: address = 0x0000000100000d31, locations = 1
(lldb) r
Launching 'a.out' (x86_64)
(lldb) Process 38031 Stopped
* thread #1: tid = 0x2e03, pc = 0x0000000100000d31, where = a.out`main [inlined] std::string::_M_data() const at /usr/include/c++/4.2.1/bits/basic_string.h:280, stop reason = breakpoint 1.1, queue = com.apple.main-thread
277
278 _CharT*
279 _M_data() const
280 -> { return _M_dataplus._M_p; }
281
282 _CharT*
283 _M_data(_CharT* __p)
(lldb) bt
thread #1: tid = 0x2e03, stop reason = breakpoint 1.1, queue = com.apple.main-thread
frame #0: pc = 0x0000000100000d31, where = a.out`main [inlined] std::string::_M_data() const at /usr/include/c++/4.2.1/bits/basic_string.h:280
frame #1: pc = 0x0000000100000d31, where = a.out`main [inlined] std::string::_M_rep() const at /usr/include/c++/4.2.1/bits/basic_string.h:288
frame #2: pc = 0x0000000100000d31, where = a.out`main [inlined] std::string::size() const at /usr/include/c++/4.2.1/bits/basic_string.h:606
frame #3: pc = 0x0000000100000d31, where = a.out`main [inlined] operator<< <char, std::char_traits<char>, std::allocator<char> > at /usr/include/c++/4.2.1/bits/basic_string.h:2414
frame #4: pc = 0x0000000100000d31, where = a.out`main + 33 at /Volumes/work/gclayton/Documents/src/lldb/test/stl/main.cpp:14
frame #5: pc = 0x0000000100000d08, where = a.out`start + 52
Each inline frame contains only the variables that they contain and each inlined
stack frame is treated as a single entity.
llvm-svn: 111877
2010-08-24 08:45:41 +08:00
|
|
|
Block::AppendVariables
|
|
|
|
(
|
|
|
|
bool can_create,
|
|
|
|
bool get_parent_variables,
|
|
|
|
bool stop_if_block_is_inlined_function,
|
|
|
|
VariableList *variable_list
|
|
|
|
)
|
2010-08-19 03:29:16 +08:00
|
|
|
{
|
|
|
|
uint32_t num_variables_added = 0;
|
|
|
|
VariableListSP variable_list_sp(GetVariableList(false, can_create));
|
|
|
|
|
2010-09-07 12:20:48 +08:00
|
|
|
bool is_inlined_function = GetInlinedFunctionInfo() != NULL;
|
2010-08-19 03:29:16 +08:00
|
|
|
if (variable_list_sp.get())
|
|
|
|
{
|
|
|
|
num_variables_added = variable_list_sp->GetSize();
|
|
|
|
variable_list->AddVariables(variable_list_sp.get());
|
|
|
|
}
|
Added support for inlined stack frames being represented as real stack frames
which is now on by default. Frames are gotten from the unwinder as concrete
frames, then if inline frames are to be shown, extra information to track
and reconstruct these frames is cached with each Thread and exanded as needed.
I added an inline height as part of the lldb_private::StackID class, the class
that helps us uniquely identify stack frames. This allows for two frames to
shared the same call frame address, yet differ only in inline height.
Fixed setting breakpoint by address to not require addresses to resolve.
A quick example:
% cat main.cpp
% ./build/Debug/lldb test/stl/a.out
Current executable set to 'test/stl/a.out' (x86_64).
(lldb) breakpoint set --address 0x0000000100000d31
Breakpoint created: 1: address = 0x0000000100000d31, locations = 1
(lldb) r
Launching 'a.out' (x86_64)
(lldb) Process 38031 Stopped
* thread #1: tid = 0x2e03, pc = 0x0000000100000d31, where = a.out`main [inlined] std::string::_M_data() const at /usr/include/c++/4.2.1/bits/basic_string.h:280, stop reason = breakpoint 1.1, queue = com.apple.main-thread
277
278 _CharT*
279 _M_data() const
280 -> { return _M_dataplus._M_p; }
281
282 _CharT*
283 _M_data(_CharT* __p)
(lldb) bt
thread #1: tid = 0x2e03, stop reason = breakpoint 1.1, queue = com.apple.main-thread
frame #0: pc = 0x0000000100000d31, where = a.out`main [inlined] std::string::_M_data() const at /usr/include/c++/4.2.1/bits/basic_string.h:280
frame #1: pc = 0x0000000100000d31, where = a.out`main [inlined] std::string::_M_rep() const at /usr/include/c++/4.2.1/bits/basic_string.h:288
frame #2: pc = 0x0000000100000d31, where = a.out`main [inlined] std::string::size() const at /usr/include/c++/4.2.1/bits/basic_string.h:606
frame #3: pc = 0x0000000100000d31, where = a.out`main [inlined] operator<< <char, std::char_traits<char>, std::allocator<char> > at /usr/include/c++/4.2.1/bits/basic_string.h:2414
frame #4: pc = 0x0000000100000d31, where = a.out`main + 33 at /Volumes/work/gclayton/Documents/src/lldb/test/stl/main.cpp:14
frame #5: pc = 0x0000000100000d08, where = a.out`start + 52
Each inline frame contains only the variables that they contain and each inlined
stack frame is treated as a single entity.
llvm-svn: 111877
2010-08-24 08:45:41 +08:00
|
|
|
|
2010-08-19 03:29:16 +08:00
|
|
|
if (get_parent_variables)
|
|
|
|
{
|
Added support for inlined stack frames being represented as real stack frames
which is now on by default. Frames are gotten from the unwinder as concrete
frames, then if inline frames are to be shown, extra information to track
and reconstruct these frames is cached with each Thread and exanded as needed.
I added an inline height as part of the lldb_private::StackID class, the class
that helps us uniquely identify stack frames. This allows for two frames to
shared the same call frame address, yet differ only in inline height.
Fixed setting breakpoint by address to not require addresses to resolve.
A quick example:
% cat main.cpp
% ./build/Debug/lldb test/stl/a.out
Current executable set to 'test/stl/a.out' (x86_64).
(lldb) breakpoint set --address 0x0000000100000d31
Breakpoint created: 1: address = 0x0000000100000d31, locations = 1
(lldb) r
Launching 'a.out' (x86_64)
(lldb) Process 38031 Stopped
* thread #1: tid = 0x2e03, pc = 0x0000000100000d31, where = a.out`main [inlined] std::string::_M_data() const at /usr/include/c++/4.2.1/bits/basic_string.h:280, stop reason = breakpoint 1.1, queue = com.apple.main-thread
277
278 _CharT*
279 _M_data() const
280 -> { return _M_dataplus._M_p; }
281
282 _CharT*
283 _M_data(_CharT* __p)
(lldb) bt
thread #1: tid = 0x2e03, stop reason = breakpoint 1.1, queue = com.apple.main-thread
frame #0: pc = 0x0000000100000d31, where = a.out`main [inlined] std::string::_M_data() const at /usr/include/c++/4.2.1/bits/basic_string.h:280
frame #1: pc = 0x0000000100000d31, where = a.out`main [inlined] std::string::_M_rep() const at /usr/include/c++/4.2.1/bits/basic_string.h:288
frame #2: pc = 0x0000000100000d31, where = a.out`main [inlined] std::string::size() const at /usr/include/c++/4.2.1/bits/basic_string.h:606
frame #3: pc = 0x0000000100000d31, where = a.out`main [inlined] operator<< <char, std::char_traits<char>, std::allocator<char> > at /usr/include/c++/4.2.1/bits/basic_string.h:2414
frame #4: pc = 0x0000000100000d31, where = a.out`main + 33 at /Volumes/work/gclayton/Documents/src/lldb/test/stl/main.cpp:14
frame #5: pc = 0x0000000100000d08, where = a.out`start + 52
Each inline frame contains only the variables that they contain and each inlined
stack frame is treated as a single entity.
llvm-svn: 111877
2010-08-24 08:45:41 +08:00
|
|
|
if (stop_if_block_is_inlined_function && is_inlined_function)
|
|
|
|
return num_variables_added;
|
|
|
|
|
2010-08-19 03:29:16 +08:00
|
|
|
Block* parent_block = GetParent();
|
|
|
|
if (parent_block)
|
Added support for inlined stack frames being represented as real stack frames
which is now on by default. Frames are gotten from the unwinder as concrete
frames, then if inline frames are to be shown, extra information to track
and reconstruct these frames is cached with each Thread and exanded as needed.
I added an inline height as part of the lldb_private::StackID class, the class
that helps us uniquely identify stack frames. This allows for two frames to
shared the same call frame address, yet differ only in inline height.
Fixed setting breakpoint by address to not require addresses to resolve.
A quick example:
% cat main.cpp
% ./build/Debug/lldb test/stl/a.out
Current executable set to 'test/stl/a.out' (x86_64).
(lldb) breakpoint set --address 0x0000000100000d31
Breakpoint created: 1: address = 0x0000000100000d31, locations = 1
(lldb) r
Launching 'a.out' (x86_64)
(lldb) Process 38031 Stopped
* thread #1: tid = 0x2e03, pc = 0x0000000100000d31, where = a.out`main [inlined] std::string::_M_data() const at /usr/include/c++/4.2.1/bits/basic_string.h:280, stop reason = breakpoint 1.1, queue = com.apple.main-thread
277
278 _CharT*
279 _M_data() const
280 -> { return _M_dataplus._M_p; }
281
282 _CharT*
283 _M_data(_CharT* __p)
(lldb) bt
thread #1: tid = 0x2e03, stop reason = breakpoint 1.1, queue = com.apple.main-thread
frame #0: pc = 0x0000000100000d31, where = a.out`main [inlined] std::string::_M_data() const at /usr/include/c++/4.2.1/bits/basic_string.h:280
frame #1: pc = 0x0000000100000d31, where = a.out`main [inlined] std::string::_M_rep() const at /usr/include/c++/4.2.1/bits/basic_string.h:288
frame #2: pc = 0x0000000100000d31, where = a.out`main [inlined] std::string::size() const at /usr/include/c++/4.2.1/bits/basic_string.h:606
frame #3: pc = 0x0000000100000d31, where = a.out`main [inlined] operator<< <char, std::char_traits<char>, std::allocator<char> > at /usr/include/c++/4.2.1/bits/basic_string.h:2414
frame #4: pc = 0x0000000100000d31, where = a.out`main + 33 at /Volumes/work/gclayton/Documents/src/lldb/test/stl/main.cpp:14
frame #5: pc = 0x0000000100000d08, where = a.out`start + 52
Each inline frame contains only the variables that they contain and each inlined
stack frame is treated as a single entity.
llvm-svn: 111877
2010-08-24 08:45:41 +08:00
|
|
|
num_variables_added += parent_block->AppendVariables (can_create, get_parent_variables, stop_if_block_is_inlined_function, variable_list);
|
2010-08-19 03:29:16 +08:00
|
|
|
}
|
|
|
|
return num_variables_added;
|
|
|
|
}
|
|
|
|
|
2010-06-09 00:52:24 +08:00
|
|
|
void
|
2010-08-21 10:22:51 +08:00
|
|
|
Block::SetBlockInfoHasBeenParsed (bool b, bool set_children)
|
2010-06-09 00:52:24 +08:00
|
|
|
{
|
2010-08-21 10:22:51 +08:00
|
|
|
m_parsed_block_info = b;
|
|
|
|
if (set_children)
|
2010-06-09 00:52:24 +08:00
|
|
|
{
|
2010-08-21 10:22:51 +08:00
|
|
|
m_parsed_child_blocks = true;
|
|
|
|
for (Block *child_block = GetFirstChild(); child_block != NULL; child_block = child_block->GetSibling())
|
|
|
|
child_block->SetBlockInfoHasBeenParsed (b, true);
|
2010-06-09 00:52:24 +08:00
|
|
|
}
|
|
|
|
}
|