Commit Graph

155 Commits

Author SHA1 Message Date
Enrico Granata 99f0b8f935 When defining a scripted command, it is possible to provide a docstring and that will be used as the help text for the command
If no docstring is provided, a default help text is created
LLDB will refuse to create scripted commands if the scripting language is anything but Python
Some additional comments in AppleObjCRuntimeV2.cpp to describe the memory layout expected by the dynamic type lookup code

llvm-svn: 137801
2011-08-17 01:30:04 +00:00
Enrico Granata 223383ed6c Changes to Python commands:
- They now have an SBCommandReturnObject instead of an SBStream as third argument
 - The class CommandObjectPythonFunction has been merged into CommandObjectCommands.cpp
 - The command to manage them is now:
  command script with subcommands add, list, delete, clear
   command alias is returned to its previous functionality
 - Python commands are now part of an user dictionary, instead of being seen as aliases
 

llvm-svn: 137785
2011-08-16 23:24:13 +00:00
Enrico Granata be93a35a8a Python commands:
It is now possible to use 'command alias --python' to define a command name that actually triggers execution of a Python function
 (e.g. command alias --python foo foo_impl makes a command named 'foo' that runs Python function 'foo_impl')
 The Python function foo_impl should have as signature: def foo_impl(debugger, args, stream, dict): where
  debugger is an object wrapping an LLDB SBDebugger
  args is the command line arguments, as an unparsed Python string
  stream is an SBStream that represents the standard output
  dict is an internal utility parameter and should be left untouched
 The function should return None on no error, or an error string to describe any problems

llvm-svn: 137722
2011-08-16 16:49:25 +00:00
Johnny Chen 0a76c28a87 To silence the static analyzer.
llvm-svn: 137329
2011-08-11 19:17:45 +00:00
Enrico Granata 6f3533fb1d Public API changes:
- Completely new implementation of SBType
 - Various enhancements in several other classes
Python synthetic children providers for std::vector<T>, std::list<T> and std::map<K,V>:
 - these return the actual elements into the container as the children of the container
 - basic template name parsing that works (hopefully) on both Clang and GCC
 - find them in examples/synthetic and in the test suite in functionalities/data-formatter/data-formatter-python-synth
New summary string token ${svar :
 - the syntax is just the same as in ${var but this new token lets you read the values
   coming from the synthetic children provider instead of the actual children
 - Python providers above provide a synthetic child len that returns the number of elements
   into the container
Full bug fix for the issue in which getting byte size for a non-complete type would crash LLDB
Several other fixes, including:
 - inverted the order of arguments in the ClangASTType constructor
 - EvaluationPoint now only returns SharedPointer's to Target and Process
 - the help text for several type subcommands now correctly indicates argument-less options as such

llvm-svn: 136504
2011-07-29 19:53:35 +00:00
Enrico Granata c53114e30a new flag -P to type synth add lets you type a Python class interactively
added a final newline to fooSynthProvider.py
new option to automatically save user input in InputReaderEZ
checking for NULL pointers in several new places

llvm-svn: 135916
2011-07-25 16:59:05 +00:00
Enrico Granata a37a065c33 Python synthetic children:
- you can now define a Python class as a synthetic children producer for a type
   the class must adhere to this "interface":
        def __init__(self, valobj, dict):
     	def get_child_at_index(self, index):
     	def get_child_index(self, name):
   then using type synth add -l className typeName
   (e.g. type synth add -l fooSynthProvider foo)
   (This is still WIP with lots to be added)
   A small test case is available also as reference

llvm-svn: 135865
2011-07-24 00:14:56 +00:00
Enrico Granata f2bbf717f7 Python summary strings:
- you can use a Python script to write a summary string for data-types, in one of
   three ways:
    -P option and typing the script a line at a time
    -s option and passing a one-line Python script
    -F option and passing the name of a Python function
   these options all work for the "type summary add" command
   your Python code (if provided through -P or -s) is wrapped in a function
   that accepts two parameters: valobj (a ValueObject) and dict (an LLDB
   internal dictionary object). if you use -F and give a function name,
   you're expected to define the function on your own and with the right
   prototype. your function, however defined, must return a Python string
 - test case for the Python summary feature
 - a few quirks:
  Python summaries cannot have names, and cannot use regex as type names
  both issues will be fixed ASAP
major redesign of type summary code:
 - type summary working with strings and type summary working with Python code
   are two classes, with a common base class SummaryFormat
 - SummaryFormat classes now are able to actively format objects rather than
   just aggregating data
 - cleaner code to print descriptions for summaries
the public API now exports a method to easily navigate a ValueObject hierarchy
New InputReaderEZ and PriorityPointerPair classes
Several minor fixes and improvements

llvm-svn: 135238
2011-07-15 02:26:42 +00:00
Caroline Tice d61c10bc79 Add 'batch_mode' to CommandInterpreter. Modify InputReaders to
not write output (prompts, instructions,etc.) if the CommandInterpreter
is in batch_mode.

Also, finish updating InputReaders to write to the asynchronous stream,
rather than using the Debugger's output file directly.

llvm-svn: 133162
2011-06-16 16:27:19 +00:00
Caroline Tice c1338e8d38 Add error message; clean up comment.
llvm-svn: 132997
2011-06-14 16:36:12 +00:00
Caroline Tice 1f499bc039 Cleaning up the Python script interpreter: Use the
embedded_interpreter.py file rather than keeping it
all in a string and compiling the string (easier to maintain,
easier to read, remove redundancy).

llvm-svn: 132935
2011-06-13 21:33:00 +00:00
Caroline Tice c928f59c90 Use Py_InitializeEx(0) instead of Py_Initialize,
to prevent Python from installing its own signal 
handlers.

llvm-svn: 132492
2011-06-02 22:09:43 +00:00
Caroline Tice e67afe15b4 Pre-load the Python script interpreter with the following
convenience variables (from the ExecutionContext) each time
it is entered: lldb.debugger, lldb.target, lldb.process, 
lldb.thread, lldb.frame.

If a frame (or thread, process, etc) does not currently exist,
the variable contains the Python value 'None'.

llvm-svn: 130792
2011-05-03 21:21:50 +00:00
Caroline Tice 969ed3d10f This patch captures and serializes all output being written by the
command line driver, including the lldb prompt being output by
editline, the asynchronous process output & error messages, and
asynchronous messages written by target stop-hooks.

As part of this it introduces a new Stream class,
StreamAsynchronousIO.  A StreamAsynchronousIO object is created with a
broadcaster, who will eventually broadcast the stream's data for a
listener to handle, and an event type indicating what type of event
the broadcaster will broadcast.  When the Write method is called on a
StreamAsynchronousIO object, the data is appended to an internal
string.  When the Flush method is called on a StreamAsynchronousIO
object, it broadcasts it's data string and clears the string.

Anything in lldb-core that needs to generate asynchronous output for
the end-user should use the StreamAsynchronousIO objects.

I have also added a new notification type for InputReaders, to let
them know that a asynchronous output has been written. This is to
allow the input readers to, for example, refresh their prompts and
lines, if desired.  I added the case statements to all the input
readers to catch this notification, but I haven't added any code for
handling them yet (except to the IOChannel input reader).

llvm-svn: 130721
2011-05-02 20:41:46 +00:00
Greg Clayton fc36f79170 Abtracted the innards of lldb-core away from the SB interface. There was some
overlap in the SWIG integration which has now been fixed by introducing
callbacks for initializing SWIG for each language (python only right now).
There was also a breakpoint command callback that called into SWIG which has
been abtracted into a callback to avoid cross over as well.

Added a new binary: lldb-platform

This will be the start of the remote platform that will use as much of the 
Host functionality to do its job so it should just work on all platforms.
It is pretty hollowed out for now, but soon it will implement a platform
using the GDB remote packets as the transport.

llvm-svn: 128053
2011-03-22 01:14:58 +00:00
Johnny Chen 24e99aa833 Minor typo fix and TAB removals.
llvm-svn: 127439
2011-03-11 00:28:50 +00:00
Caroline Tice c288e8ca0c Add some explanatory comments.
llvm-svn: 127438
2011-03-11 00:21:55 +00:00
Caroline Tice 6258c53e12 Add thread state initialization to the thread where the interactive
interpreter is run (which is separate from the thread where
Py_Initialize is called, where this normally gets set up).

llvm-svn: 127191
2011-03-07 23:24:28 +00:00
Greg Clayton 51b1e2d271 Use Host::File in lldb_private::StreamFile and other places to cleanup host
layer a bit more.

llvm-svn: 125149
2011-02-09 01:08:52 +00:00
Greg Clayton 2da6d49523 Patch that allows for thread_t to be something more complex than an
integer. Modified patch from Kirk Beitz.

llvm-svn: 125067
2011-02-08 01:34:25 +00:00
Greg Clayton a3406614e0 Abtract terminal stuff into a new lldb_private::Terminal class
where the implementation is hidden in the host layer. This avoids
a slew of "#if LLDB_CONFIG_TERMIOS_SUPPORTED" statements in the
code and keeps things cleaner.

llvm-svn: 125057
2011-02-07 23:24:47 +00:00
Greg Clayton 6c3e431e9b More termios fixes. We need to currently make sure to include:
#include "lldb/Host/Config.h"

Or the LLDB_CONFIG_TERMIOS_SUPPORTED defined won't be set. I will fix all
of this Termios stuff later today by moving lldb/Core/TTYState.* into the 
host layer and then we conditionalize all of this inside TTYState.cpp and
then we get rid of LLDB_CONFIG_TERMIOS_SUPPORTED all together.

Typically, when we start to see too many "#if LLDB_CONFIG_XXXX" preprocessor
directives, this is a good indicator that something needs to be moved over to
the host layer. TTYState can be modified to do all of the things that many
areas of the code are currently doing, and it will avoid all of the 
preprocessor noise.

llvm-svn: 125027
2011-02-07 19:22:32 +00:00
Greg Clayton cdd074fbc7 More termios fixes from Kirk Beitz.
llvm-svn: 125024
2011-02-07 19:04:58 +00:00
Greg Clayton 95e314260e Header patch, virtual dtor patch and missed UUID patch from Kirk Beitz.
llvm-svn: 124931
2011-02-05 02:56:16 +00:00
Caroline Tice eb5cfe4f2b Fix exit instructions for interactive interpreter, now that ctrl-D works.
llvm-svn: 124811
2011-02-03 20:08:40 +00:00
Greg Clayton 645bf5420d Added support for some new environment variables within LLDB to enable some
extra launch options:

LLDB_LAUNCH_FLAG_DISABLE_ASLR disables ASLR for all launched processes

LLDB_LAUNCH_FLAG_DISABLE_STDIO will disable STDIO (reroute to "/dev/null")
for all launched processes

LLDB_LAUNCH_FLAG_LAUNCH_IN_TTY will force all launched processes to be
launched in new terminal windows.

Also, don't init python if we never create a script interpreter.

llvm-svn: 124341
2011-01-27 01:01:10 +00:00
Greg Clayton 1a65ae11bd Enabled extra warnings and fixed a bunch of small issues.
llvm-svn: 124250
2011-01-25 23:55:37 +00:00
Caroline Tice 6760a51739 Replace Mutex guarding python interpreter access with Predicate,
allowing timeouts & informing the user when the lock is unavailable.


Fixed problem where Debugger::Terminate was clearing the debugger list
even when the global ref count was greater than zero.

llvm-svn: 123674
2011-01-17 21:55:19 +00:00
Caroline Tice 8f5b2eb1e2 Recent modifications to the Python script interpreter caused some problems
when handling one-liner commands that contain escaped characters.  In
order to deal with the new namespace/dictionary stuff, the command was
being embedded within a second string, which messed up the escaping.

This fixes the problem by handling one-liners in a different manner, so they
no longer need to be embedded within another string, and can still be
processed in the proper namespace/dictionary context.

llvm-svn: 123467
2011-01-14 21:09:29 +00:00
Caroline Tice 2f88aadff1 Split up the Python script interpreter code to allow multiple script interpreter objects to
exist within the same process (one script interpreter object per debugger object).  The
python script interpreter objects are all using the same global Python script interpreter;
they use separate dictionaries to keep their data separate, and mutex's to prevent any object
attempting to use the global Python interpreter when another object is already using it.

llvm-svn: 123415
2011-01-14 00:29:16 +00:00
Caroline Tice a51174af72 Add termination instructions when entering the interactive script interpreter.
llvm-svn: 121884
2010-12-15 19:51:12 +00:00
Caroline Tice efed613172 Add the ability to catch and do the right thing with Interrupts (often control-c)
and end-of-file (often control-d).

llvm-svn: 119837
2010-11-19 20:47:54 +00:00
Caroline Tice 5c2816d903 Move the embedded Python interpreter onto a separate thread, to prevent
main thread from having to wait on it (which was causing some I/O 
hangs).

llvm-svn: 118700
2010-11-10 19:18:14 +00:00
Caroline Tice 3cc8751d59 Remove references to particular Python version (use the system default
version);  change include statements to use Python.h in the Python framework
on Mac OS X systems; leave it using regular Python.h on other systems.

Note:  I think this *ought* to work properly on Linux systems, but I don't have
a system to test it on...

llvm-svn: 117612
2010-10-28 21:51:20 +00:00
Caroline Tice 04a339a084 Flush the prompts immediately in the breakpoint command input readers, to make
sure they come out at the correct times.

llvm-svn: 117470
2010-10-27 18:34:42 +00:00
Caroline Tice dc8f777f6b Prevent Python script interpreter initialization from changing
the termios settings on the debugger's input handle.

llvm-svn: 116725
2010-10-18 18:24:17 +00:00
Greg Clayton dd36defda7 Added a new Host call to find LLDB related paths:
static bool
    Host::GetLLDBPath (lldb::PathType path_type, FileSpec &file_spec);
    
This will fill in "file_spec" with an appropriate path that is appropriate
for the current Host OS. MacOSX will return paths within the LLDB.framework,
and other unixes will return the paths they want. The current PathType
enums are:

typedef enum PathType
{
    ePathTypeLLDBShlibDir,          // The directory where the lldb.so (unix) or LLDB mach-o file in LLDB.framework (MacOSX) exists
    ePathTypeSupportExecutableDir,  // Find LLDB support executable directory (debugserver, etc)
    ePathTypeHeaderDir,             // Find LLDB header file directory
    ePathTypePythonDir              // Find Python modules (PYTHONPATH) directory
} PathType;

All places that were finding executables are and python paths are now updated
to use this Host call.

Added another new host call to launch the inferior in a terminal. This ability
will be very host specific and doesn't need to be supported on all systems.
MacOSX currently will create a new .command file and tell Terminal.app to open
the .command file. It also uses the new "darwin-debug" app which is a small
app that uses posix to exec (no fork) and stop at the entry point of the 
program. The GDB remote plug-in is almost able launch a process and attach to
it, it currently will spawn the process, but it won't attach to it just yet.
This will let LLDB not have to share the terminal with another process and a
new terminal window will pop up when you launch. This won't get hooked up
until we work out all of the kinks. The new Host function is:

    static lldb::pid_t
    Host::LaunchInNewTerminal (
        const char **argv,   // argv[0] is executable
        const char **envp,
        const ArchSpec *arch_spec,
        bool stop_at_entry,
        bool disable_aslr);

Cleaned up FileSpec::GetPath to not use strncpy() as it was always zero 
filling the entire path buffer.

Fixed an issue with the dynamic checker function where I missed a '$' prefix
that should have been added.

llvm-svn: 116690
2010-10-17 22:03:32 +00:00
Greg Clayton c6ed542c90 More SWIG cleanup. Moved the breakpoint callback function back to the
ScriptInterpreterPython class and made a simple callback function that
ScriptInterpreterPython::BreakpointCallbackFunction() now calls so we don't
include any internal API stuff into the cpp file that is generated by SWIG.

Fixed a few build warnings in debugserver.

llvm-svn: 115926
2010-10-07 17:14:24 +00:00
Caroline Tice 988efc1ba2 Fix one-liner Python breakpoint commands to be wrapped up in an automatically
generated Python function, and passed the stoppoint context frame and
bp_loc as parameters.

llvm-svn: 114894
2010-09-27 21:35:15 +00:00
Caroline Tice 18474c933c Automatically wrap *all* Python code entered for a breakpoint command inside
an auto-generated Python function, and pass the stoppoint context frame and
breakpoint location as parameters to the function (named 'frame' and 'bp_loc'),
to be used inside the breakpoint command Python code, if desired.

llvm-svn: 114849
2010-09-27 18:00:20 +00:00
Caroline Tice 867b185d8d Update help text for breakpoint command one-liners.
Fix minor bug in 'commands alias'; alias commands can now handle command options 
and arguments in the same alias.  Also fixes problem that disallowed "process launch --" as
an alias.

Fix typo in comment in Python script interpreter.

llvm-svn: 114499
2010-09-21 23:25:40 +00:00
Caroline Tice 650b92683a Re-write/clean up code that generated Python breakpoint commands.
Add a warning if no command was attached to the breakpoint.
Update the help slightly.

llvm-svn: 114467
2010-09-21 19:25:28 +00:00
Greg Clayton a701509229 Fixed the way set/show variables were being accessed to being natively
accessed by the objects that own the settings. The previous approach wasn't
very usable and made for a lot of unnecessary code just to access variables
that were already owned by the objects.

While I fixed those things, I saw that CommandObject objects should really
have a reference to their command interpreter so they can access the terminal
with if they want to output usaage. Fixed up all CommandObjects to take
an interpreter and cleaned up the API to not need the interpreter to be
passed in.

Fixed the disassemble command to output the usage if no options are passed
down and arguments are passed (all disassebmle variants take options, there
are no "args only").

llvm-svn: 114252
2010-09-18 01:14:36 +00:00
Caroline Tice e7e92b771a Remove help text that is no longer correct.
Fix Python script interpreter to not fail when the Debugger does
not have input/output file handles.

llvm-svn: 113880
2010-09-14 22:49:06 +00:00
Johnny Chen 4550154d31 Fixed some comments.
llvm-svn: 113673
2010-09-11 00:23:59 +00:00
Johnny Chen 94de55d5c2 Added the capability to specify a one-liner Python script as the callback
command for a breakpoint, for example:

(lldb) breakpoint command add -p 1 "conditional_break.stop_if_called_from_a()"

The ScriptInterpreter interface has an extra method:

    /// Set a one-liner as the callback for the breakpoint command.
    virtual void 
    SetBreakpointCommandCallback (CommandInterpreter &interpreter,
                                  BreakpointOptions *bp_options,
                                  const char *oneliner);

to accomplish the above.

Also added a test case to demonstrate lldb's use of breakpoint callback command
to stop at function c() only when its immediate caller is function a().  The
following session shows the user entering the following commands:

1) command source .lldb (set up executable, breakpoint, and breakpoint command)
2) run (the callback mechanism will skip two breakpoints where c()'s immeidate caller is not a())
3) bt (to see that indeed c()'s immediate caller is a())
4) c (to continue and finish the program)

test/conditional_break $ ../../build/Debug/lldb
(lldb) command source .lldb
Executing commands in '.lldb'.
(lldb) file a.out
Current executable set to 'a.out' (x86_64).
(lldb) breakpoint set -n c
Breakpoint created: 1: name = 'c', locations = 1
(lldb) script import sys, os
(lldb) script sys.path.append(os.path.join(os.getcwd(), os.pardir))
(lldb) script import conditional_break
(lldb) breakpoint command add -p 1 "conditional_break.stop_if_called_from_a()"
(lldb) run
run
Launching '/Volumes/data/lldb/svn/trunk/test/conditional_break/a.out'  (x86_64)
(lldb) Checking call frames...
Stack trace for thread id=0x2e03 name=None queue=com.apple.main-thread:
  frame #0: a.out`c at main.c:39
  frame #1: a.out`b at main.c:34
  frame #2: a.out`a at main.c:25
  frame #3: a.out`main at main.c:44
  frame #4: a.out`start
c called from b
Continuing...
Checking call frames...
Stack trace for thread id=0x2e03 name=None queue=com.apple.main-thread:
  frame #0: a.out`c at main.c:39
  frame #1: a.out`b at main.c:34
  frame #2: a.out`main at main.c:47
  frame #3: a.out`start
c called from b
Continuing...
Checking call frames...
Stack trace for thread id=0x2e03 name=None queue=com.apple.main-thread:
  frame #0: a.out`c at main.c:39
  frame #1: a.out`a at main.c:27
  frame #2: a.out`main at main.c:50
  frame #3: a.out`start
c called from a
Stopped at c() with immediate caller as a().
a(1) returns 4
b(2) returns 5
Process 20420 Stopped
* thread #1: tid = 0x2e03, 0x0000000100000de8 a.out`c + 7 at main.c:39, stop reason = breakpoint 1.1, queue = com.apple.main-thread
  36   	
  37   	int c(int val)
  38   	{
  39 ->	    return val + 3;
  40   	}
  41   	
  42   	int main (int argc, char const *argv[])
(lldb) bt
bt
thread #1: tid = 0x2e03, stop reason = breakpoint 1.1, queue = com.apple.main-thread
  frame #0: 0x0000000100000de8 a.out`c + 7 at main.c:39
  frame #1: 0x0000000100000dbc a.out`a + 44 at main.c:27
  frame #2: 0x0000000100000e4b a.out`main + 91 at main.c:50
  frame #3: 0x0000000100000d88 a.out`start + 52
(lldb) c
c
Resuming process 20420
Process 20420 Exited
a(3) returns 6
(lldb) 

llvm-svn: 113596
2010-09-10 18:21:10 +00:00
Johnny Chen d0211cc657 Don't flatten lldb and import everything into the global namespace. Instead,
set the debugger_unique_id with the lldb prefix.

llvm-svn: 113589
2010-09-10 16:19:10 +00:00
Johnny Chen 705c3b6aaa There is no need to restore (sys.stdin, sys.stdout) of the python script
interpreter right before calling Py_Finalize().  This also fixed the crash as
reported in rdar://problem/8252903.

llvm-svn: 110731
2010-08-10 21:26:55 +00:00
Johnny Chen 7dc2e4784e We can do better when reporting the status of one-liner script execution.
Change the prototype of ScriptInterpreter::ExecuteOneLine() to return bool
instead of void and take one additional parameter as CommandReturnObject *.

Propagate the status of one-liner execution back appropriately.

llvm-svn: 109899
2010-07-30 22:33:14 +00:00
Greg Clayton a624358728 Added some comments to clarify where "init_lldb" comes from.
llvm-svn: 107801
2010-07-07 18:40:03 +00:00
Caroline Tice ebc1bb277c Add a unique ID to each debugger instance.
Add functions to look up debugger by id
Add global variable to lldb python module, to hold debugger id
Modify embedded Python interpreter to update the global variable with the
 id of its current debugger.
Modify the char ** typemap definition in lldb.swig to accept 'None' (for NULL)
 as a valid value.

The point of all this is so that, when you drop into the embedded interpreter
from the command interpreter (or when doing Python-based breakpoint commands),
there is a way for the Python side to find/get the correct debugger
instance ( by checking debugger_unique_id, then calling 
SBDebugger::FindDebuggerWithID  on it).

llvm-svn: 107287
2010-06-30 16:22:25 +00:00
Greg Clayton 6611103cfe Very large changes that were needed in order to allow multiple connections
to the debugger from GUI windows. Previously there was one global debugger
instance that could be accessed that had its own command interpreter and
current state (current target/process/thread/frame). When a GUI debugger
was attached, if it opened more than one window that each had a console
window, there were issues where the last one to setup the global debugger
object won and got control of the debugger.

To avoid this we now create instances of the lldb_private::Debugger that each 
has its own state:
- target list for targets the debugger instance owns
- current process/thread/frame
- its own command interpreter
- its own input, output and error file handles to avoid conflicts
- its own input reader stack

So now clients should call:

    SBDebugger::Initialize(); // (static function)

    SBDebugger debugger (SBDebugger::Create());
    // Use which ever file handles you wish
    debugger.SetErrorFileHandle (stderr, false);
    debugger.SetOutputFileHandle (stdout, false);
    debugger.SetInputFileHandle (stdin, true);

    // main loop
    
    SBDebugger::Terminate(); // (static function)
    
SBDebugger::Initialize() and SBDebugger::Terminate() are ref counted to
ensure nothing gets destroyed too early when multiple clients might be
attached.

Cleaned up the command interpreter and the CommandObject and all subclasses
to take more appropriate arguments.

llvm-svn: 106615
2010-06-23 01:19:29 +00:00
Jason Molenda b1823404c3 Committing patch from Joseph Ranieri to handle 'exit()' the same
as 'quit()' in the python script environment.

llvm-svn: 105756
2010-06-09 21:56:00 +00:00
Eli Friedman d0edb5b4a8 Don't include Python.h in the shared header.
llvm-svn: 105737
2010-06-09 18:31:38 +00:00
Chris Lattner 30fdc8d841 Initial checkin of lldb code from internal Apple repo.
llvm-svn: 105619
2010-06-08 16:52:24 +00:00