This is the mail archive of the frysk@sourceware.org mailing list for the frysk project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Stepping over signals, in state "detaching" did not handle handleStoppedEvent SIGTRAP(5)


Here is current work on stepping over a signal, the race condition involved is:
inside frysk-core:


./TestRunner -debug frysk.stepping.SteppingEngine=FINE frysk.stepping.TestStepping.testStepOverSignal

There was 1 error:
1) testStepOverSignal(frysk.stepping.TestStepping)java.lang.RuntimeException: {frysk.proc.live.LinuxPtraceTask@327b6455,pid=29497,tid=29497,state=detaching} in state "detaching" did not handle handleStoppedEvent SIGTRAP(5)
at frysk.proc.live.State.unhandled(TestRunner)
at frysk.proc.live.LinuxPtraceTaskState$2.handleStoppedEvent(TestRunner)
at frysk.proc.live.LinuxPtraceTask.processStoppedEvent(TestRunner)
at frysk.proc.live.LinuxWaitBuilder.stopped(TestRunner)
at frysk.sys.Wait.wait(TestRunner)
at frysk.sys.Wait.wait(TestRunner)
at frysk.event.WaitEventLoop.block(TestRunner)
at frysk.event.EventLoop.runEventLoop(TestRunner)
at frysk.event.EventLoop.runPolling(TestRunner)
at frysk.testbed.TestLib.assertRunUntilStop(TestRunner)
at frysk.testbed.TestLib.assertRunUntilStop(TestRunner)
at frysk.stepping.TestStepping.testStepOverSignal(TestRunner)
at frysk.junit.Runner.runCases(TestRunner)
at frysk.junit.Runner.runTestCases(TestRunner)
at TestRunner.main(TestRunner)



happens quite frequently but every once in a while get a pass
>From f41667247d643ca379221e194d49ad65d1bf8527 Mon Sep 17 00:00:00 2001
From: Nurdin Premji <npremji@localhost.localdomain>
Date: Tue, 29 Apr 2008 14:20:57 -0400
Subject: [PATCH] Stepping over signals

---
 frysk-core/frysk/hpd/StepCommand.java             |   12 ++-
 frysk-core/frysk/rt/Breakpoint.java               |    4 +-
 frysk-core/frysk/stepping/SteppingEngine.java     |   87 ++++++++++++++--
 frysk-core/frysk/stepping/TaskStepEngine.java     |    7 +-
 frysk-core/frysk/stepping/TestStepping.java       |  112 +++++++++++++++++++++
 frysk-core/frysk/stepping/TestSteppingEngine.java |    3 +-
 6 files changed, 204 insertions(+), 21 deletions(-)

diff --git a/frysk-core/frysk/hpd/StepCommand.java b/frysk-core/frysk/hpd/StepCommand.java
index a8cc3c0..7cd854e 100644
--- a/frysk-core/frysk/hpd/StepCommand.java
+++ b/frysk-core/frysk/hpd/StepCommand.java
@@ -60,10 +60,17 @@ public class StepCommand extends ParameterizedCommand {
 		((Options)options).instruction = true;	
 	    }
 	});
+	
+	add(new CommandOption("stepoversignal", 's', "step over signals", null) {
+	    void parse(String argument, Object options) {
+		((Options) options).stepOverSignal = true;
+	    }
+	});
     }
     
     private class Options {
 	boolean instruction = false;
+	boolean stepOverSignal = false;
     }
 	
     Object options() {
@@ -79,7 +86,10 @@ public class StepCommand extends ParameterizedCommand {
 	    taskList.add(taskIter.next());
 	}
 	if (cli.steppingObserver != null) {
-	     
+	    if (((Options) options).stepOverSignal == true) {
+		cli.getSteppingEngine().setStepOverSignal(true);
+	    }
+	    
 	    if (((Options) options).instruction == false) {
 		fine.log(this, "Stepping line");
 		cli.getSteppingEngine().stepLine(taskList);
diff --git a/frysk-core/frysk/rt/Breakpoint.java b/frysk-core/frysk/rt/Breakpoint.java
index f6214f4..74d0414 100644
--- a/frysk-core/frysk/rt/Breakpoint.java
+++ b/frysk-core/frysk/rt/Breakpoint.java
@@ -83,7 +83,6 @@ public class Breakpoint implements TaskObserver.Code {
         else {
 	    fine.log(this, "updateHit adding instruction observer", task,
 		     "address", address);
-		
 	    this.steppingEngine.blockedByActionPoint(task, this);
 	    task.requestUnblock(this);
         }
@@ -107,7 +106,8 @@ public class Breakpoint implements TaskObserver.Code {
             monitor.notifyAll();
         }
         //    System.err.println("BreakPoint.addedTo");
-        ((Task) observable).requestDeleteInstructionObserver(this.steppingEngine.getSteppingObserver());
+        Task task = (Task) observable;
+        this.steppingEngine.requestRemoveSteppingObserver(task); 
     }
 
     public boolean isAdded () {
diff --git a/frysk-core/frysk/stepping/SteppingEngine.java b/frysk-core/frysk/stepping/SteppingEngine.java
index c380dd3..13a5ec3 100644
--- a/frysk-core/frysk/stepping/SteppingEngine.java
+++ b/frysk-core/frysk/stepping/SteppingEngine.java
@@ -53,6 +53,7 @@ import frysk.sys.ProcessIdentifier;
 import frysk.sys.ProcessIdentifierFactory;
 import lib.dwfl.DwflLine;
 import frysk.debuginfo.DebugInfoFrame;
+import frysk.debuginfo.DebugInfoStackFactory;
 import frysk.event.RequestStopEvent;
 import frysk.proc.Action;
 import frysk.proc.Manager;
@@ -96,6 +97,18 @@ public class SteppingEngine {
     /* Observer used to block Tasks, as well as providing the mechanism to 
      * step them, by an instruction each time. */
     private SteppingObserver steppingObserver;
+    
+    public void requestAddSteppingObserver(Task task) 
+    {
+	task.requestAddInstructionObserver(steppingObserver);
+	task.requestAddSignaledObserver(steppingObserver);
+    }
+    
+    public void requestRemoveSteppingObserver(Task task)
+    {
+	task.requestDeleteInstructionObserver(steppingObserver);
+	task.requestDeleteSignaledObserver(steppingObserver);
+    }
 
     /* Ensures that newly spawned Tasks are maintained by the SteppingEngine
      * class, and also makes sure that exiting Tasks are taken care of and cleaned
@@ -448,6 +461,26 @@ public class SteppingEngine {
 	    this.steppingObserver.notifyNotBlocked(tse);
 	}
     }
+    
+    public void stepOverSignal(Task task) {
+	DebugInfoFrame frame = DebugInfoStackFactory
+	    .createDebugInfoStackTrace(task);
+	
+	long address = frame.getAddress();
+
+	fine.log(this, "Looking for address: 0x", Long.toHexString(address), "Line #: ", frame.getLine().getLine());
+	
+	TaskStepEngine tse = (TaskStepEngine) this.taskStateMap.get(task);
+	tse.setState(new StepOutState(task));
+	this.steppingObserver.notifyNotBlocked(tse);
+
+	int i = ((Integer) this.contextMap.get(task.getProc())).intValue();
+	this.contextMap.put(task.getProc(), new Integer(++i));
+
+	this.breakpoint = new SteppingBreakpoint(this, address);
+	this.breakpointMap.put(task, this.breakpoint);
+	task.requestAddCodeObserver(this.breakpoint, address);
+    }
 
     /**
      * Performs a step-over operation on a list of tasks; none of the given
@@ -479,6 +512,13 @@ public class SteppingEngine {
 	    }
 	}
     }
+    
+    
+    private boolean stepOverSignal = false;
+    public void setStepOverSignal(boolean bol) {
+	fine.log(this, "Setting stepOverSignal");
+	stepOverSignal = bol;
+    }
 
     /**
      * Sets the stage for stepping out of a frame. Runs until a breakpoint on the
@@ -596,7 +636,7 @@ public class SteppingEngine {
 		    this.steppingObserver.notifyNotBlocked(tse);
 		}
 		continueForStepping(t, false);
-		t.requestDeleteInstructionObserver(this.steppingObserver);
+		requestRemoveSteppingObserver(t);
 	    }
 	}
     }
@@ -715,7 +755,8 @@ public class SteppingEngine {
 		tse = (TaskStepEngine) this.taskStateMap.get(t);
 		tse.setState(new RunningState(t));
 		this.steppingObserver.notifyNotBlocked(tse);
-		t.requestDeleteInstructionObserver(this.steppingObserver);
+		
+		requestRemoveSteppingObserver(t);
 	    }
 	    return;
 	} else
@@ -735,7 +776,7 @@ public class SteppingEngine {
 		    tse = (TaskStepEngine) this.taskStateMap.get(t);
 		    tse.setState(new RunningState(t));
 		    this.steppingObserver.notifyNotBlocked(tse);
-		    t.requestDeleteInstructionObserver(this.steppingObserver);
+		    requestRemoveSteppingObserver(t);
 		} else {
 		    /* Put all threads back into a master list */
 		    temp.add(t);
@@ -829,7 +870,7 @@ public class SteppingEngine {
 	    t.requestDeleteTerminatingObserver(this.threadLifeObservable);
 	    t.requestDeleteTerminatedObserver(this.threadLifeObservable);
 	    t.requestDeleteClonedObserver(this.threadLifeObservable);
-	    t.requestDeleteInstructionObserver(this.steppingObserver);
+	    requestRemoveSteppingObserver(t);
 	    cleanTask(t);
 	}
     }
@@ -895,7 +936,8 @@ public class SteppingEngine {
     public SteppingObserver getSteppingObserver() {
 	return this.steppingObserver;
     }
-
+    
+    
     /**
      * Sets a SteppingBreakpoint on the given Task at the given address.
      * 
@@ -990,7 +1032,7 @@ public class SteppingEngine {
 	 // Requests the addition of the stepping observer to task if 
 	 // not inserted already.
 	if (!(task.isInstructionObserverAdded(this.steppingObserver))) {
-	    task.requestAddInstructionObserver(this.steppingObserver);
+	    requestAddSteppingObserver(task);
 	}
 	
 	// Add the observer to the task's blockers list
@@ -1053,7 +1095,7 @@ public class SteppingEngine {
     }
 
     protected class SteppingObserver extends Observable implements
-	    TaskObserver.Instruction {
+	    TaskObserver.Instruction, TaskObserver.Signaled{
 
 	/**
 	 * Callback for TaskObserver.Instruction. Each time a Task is blocked, either
@@ -1069,6 +1111,14 @@ public class SteppingEngine {
 	 * @return Action.BLOCK Continue blocking this incoming Task
 	 */
 	public synchronized Action updateExecuted(Task task) {
+	    
+	    DebugInfoFrame frame = DebugInfoStackFactory
+	    .createDebugInfoStackTrace(task);
+	
+	    long address = frame.getAddress();
+
+	    fine.log(this, "At address: 0x", Long.toHexString(address), "Line #: ", frame.getLine().getLine(), "stepOverSignal set to: " + stepOverSignal);
+	    
 	    //       System.err.println("SE.SO.updateEx: " + task);
 	    /* Check to see if acting upon this event produces a stopped state
 	     * change. If so, decrement the number of Tasks active in the Task's 
@@ -1131,6 +1181,19 @@ public class SteppingEngine {
 	    this.setChanged();
 	    this.notifyObservers(tse);
 	}
+
+	public Action updateSignaled(Task task, Signal signal) {
+	    fine.log(this, "updateSignaled called");
+	    if (stepOverSignal) {
+		fine.log(this, "updateSignaled, stepping over");
+		
+		requestRemoveSteppingObserver(task);
+		stepOverSignal(task);
+		fine.log(this, "finished step over signal");
+		return Action.BLOCK;
+	    }
+	    return Action.CONTINUE;
+	}
     }
 
     public void requestAdd() {
@@ -1157,7 +1220,7 @@ public class SteppingEngine {
 	Iterator i = list.iterator();
 	while (i.hasNext()) {
 	    t = (Task) i.next();
-	    t.requestAddInstructionObserver(this.steppingObserver);
+	   requestAddSteppingObserver(t);
 	}
     }
 
@@ -1189,8 +1252,8 @@ public class SteppingEngine {
 		    offspring, SteppingEngine.this));
 	    SteppingEngine.this.threadsList.addLast(offspring);
 
-	    offspring.requestAddInstructionObserver(SteppingEngine.this.steppingObserver);
-
+	    requestAddSteppingObserver(offspring);
+	    
 	    offspring.requestAddClonedObserver(this);
 	    offspring.requestAddTerminatingObserver(this);
 	    offspring.requestAddTerminatedObserver(this);
@@ -1329,7 +1392,7 @@ public class SteppingEngine {
 		addy = address;
 		fine.log(this, "updateHit task", task, "address", address,
 			 "adding instruction observer");
-		task.requestAddInstructionObserver(SteppingEngine.this.steppingObserver);
+		requestAddSteppingObserver(task);
 	    }
 
 	    ++triggered;
@@ -1353,7 +1416,7 @@ public class SteppingEngine {
 	    }
 
 	    Task t = (Task) observable;
-	    t.requestDeleteInstructionObserver(steppingObserver);
+	    requestRemoveSteppingObserver(t);
 	    continueForStepping(t, false);
 	}
 
diff --git a/frysk-core/frysk/stepping/TaskStepEngine.java b/frysk-core/frysk/stepping/TaskStepEngine.java
index be1cf4b..9120fd5 100644
--- a/frysk-core/frysk/stepping/TaskStepEngine.java
+++ b/frysk-core/frysk/stepping/TaskStepEngine.java
@@ -183,17 +183,16 @@ public class TaskStepEngine {
     }
 
     /**
-         * Returns the current State of this TaskStepEngine's Task.
+         * Sets the current State of this TaskStepEngine's Task.
          * 
-         * @param newState
-         *                The current State of this TaskStepEngine's Task
+         * @param newState The new State of this TaskStepEngine's Task
          */
     public void setState(State newState) {
 	this.state = newState;
     }
 
     /**
-         * Sets the current State of this TaskStepEngine's Task.
+         * Returns the current State of this TaskStepEngine's Task.
          * 
          * @return state The current State of this TaskStepEngine's Task.
          */
diff --git a/frysk-core/frysk/stepping/TestStepping.java b/frysk-core/frysk/stepping/TestStepping.java
index 188a27f..ca069e8 100644
--- a/frysk-core/frysk/stepping/TestStepping.java
+++ b/frysk-core/frysk/stepping/TestStepping.java
@@ -49,9 +49,11 @@ import frysk.testbed.SynchronizedOffspring;
 import frysk.config.Prefix;
 import frysk.debuginfo.DebugInfoFrame;
 import frysk.debuginfo.DebugInfoStackFactory;
+import frysk.proc.Action;
 import frysk.proc.Manager;
 import frysk.proc.Proc;
 import frysk.proc.Task;
+import frysk.proc.TaskObserver.Terminated;
 import frysk.rt.BreakpointManager;
 import frysk.rt.LineBreakpoint;
 import frysk.rt.SourceBreakpoint;
@@ -134,6 +136,9 @@ public class TestStepping extends TestLib {
 	assertTrue("Line information present", frame.getLine() != SourceLocation.UNKNOWN);
 
 	/** The stepping operation */
+	
+	//Send a signal to myTask
+	
 	this.se.stepLine(theTask);
 
 	this.testStarted = true;
@@ -431,6 +436,112 @@ public class TestStepping extends TestLib {
 	assertRunUntilStop("Running test");
 	cleanup();
     }
+    
+    public void testStepThroughSignal() {
+	/** Variable setup */
+
+	File source = Prefix.sourceFile("frysk-core/frysk/pkglibdir/funit-signal.c");
+
+	this.scanner = new TestfileTokenScanner(source);
+
+	/* The line number where the test begins */
+	int start = this.scanner.findTokenLine("_signalRaiseCall_");
+
+	/* The line number the test should end up at */
+	int end = this.scanner.findTokenLine("_signalHandlerEntry_");
+	
+	/* The test process */
+	SynchronizedOffspring process
+	    = new SynchronizedOffspring(Signal.USR1,
+					new String[] {
+					    getExecPath("funit-signal"),
+					    Integer.toString(Pid.get().intValue()),
+					    Integer.toString(Signal.USR1.intValue())
+					});
+	this.testStarted = false;
+
+	/** Test initialization */
+	Task myTask = initTask(process, source, start, end);
+
+	this.currentTest = new AssertLine(end, myTask);
+
+	DebugInfoFrame frame = DebugInfoStackFactory
+	    .createDebugInfoStackTrace(myTask);
+	assertTrue("Line information present", frame.getLine() != SourceLocation.UNKNOWN);
+	
+	/** The stepping operation */
+	this.se.stepLine(myTask);
+
+	this.testStarted = true;
+
+	/** Run to completion */
+	assertRunUntilStop("Running test");
+	cleanup();
+    }
+    
+    public void testStepOverSignal() {
+	/** Variable setup */
+
+	File source = Prefix.sourceFile("frysk-core/frysk/pkglibdir/funit-signal.c");
+
+	this.scanner = new TestfileTokenScanner(source);
+
+	/* The line number where the test begins */
+	int start = this.scanner.findTokenLine("_signalRaiseCall_");
+	
+	/* The test process */
+	SynchronizedOffspring process
+	    = new SynchronizedOffspring(Signal.USR1,
+					new String[] {
+					    getExecPath("funit-signal"),
+					    Integer.toString(Pid.get().intValue()),
+					    Integer.toString(Signal.USR1.intValue())
+					});
+	this.testStarted = false;
+
+	/** Test initialization */
+	Task myTask = initTask(process, source, start, start);
+
+	myTask.requestAddTerminatedObserver(new Terminated(){
+
+	    public Action updateTerminated(Task task,
+		    frysk.isa.signals.Signal signal, int value) {
+		// TODO Auto-generated method stub
+		return null;
+	    }
+
+	    public void addFailed(Object observable, Throwable w) {
+		// TODO Auto-generated method stub
+		
+	    }
+
+	    public void addedTo(Object observable) {
+		// TODO Auto-generated method stub
+		
+	    }
+
+	    public void deletedFrom(Object observable) {
+		// TODO Auto-generated method stub
+		
+	    }});
+	
+	this.se.setStepOverSignal(true);
+	
+	this.currentTest = new AssertLine(start, myTask);
+
+	DebugInfoFrame frame = DebugInfoStackFactory
+	    .createDebugInfoStackTrace(myTask);
+	assertTrue("Line information present", frame.getLine() != SourceLocation.UNKNOWN);
+	
+	/** The stepping operation */
+	this.se.stepLine(myTask);
+
+	this.testStarted = true;
+
+	/** Run to completion */
+	assertRunUntilStop("Running test");
+	cleanup();
+    }
 
     public void testASMSingleStep() {
 
@@ -1125,6 +1236,7 @@ public class TestStepping extends TestLib {
 	    DebugInfoFrame frame = DebugInfoStackFactory
 		.createDebugInfoStackTrace(task);
 	    int lineNr = frame.getLine().getLine();
+	    
 	    assertEquals("line number", success, lineNr);
 	    Manager.eventLoop.requestStop();
 	}
diff --git a/frysk-core/frysk/stepping/TestSteppingEngine.java b/frysk-core/frysk/stepping/TestSteppingEngine.java
index bbe5ad1..62ce036 100644
--- a/frysk-core/frysk/stepping/TestSteppingEngine.java
+++ b/frysk-core/frysk/stepping/TestSteppingEngine.java
@@ -330,8 +330,7 @@ public class TestSteppingEngine extends TestLib {
 	assertTrue("Line information present", frame.getLine() != SourceLocation.UNKNOWN);
 
 	/** The stepping operation */
-	this.se.stepOver(theTask, DebugInfoStackFactory
-		.createDebugInfoStackTrace(theTask));
+	this.se.stepOver(theTask, frame);
 
 	this.testStarted = true;
 	// System.err.println("waiting for finish");
-- 
1.5.4.1

>From 560975fbd0c8f181342d71829a98d86dda4380bc Mon Sep 17 00:00:00 2001
From: Nurdin Premji <npremji@localhost.localdomain>
Date: Tue, 29 Apr 2008 14:23:16 -0400
Subject: [PATCH] Added funit-signal.c

---
 frysk-core/frysk/pkglibdir/funit-signal.c |  130 +++++++++++++++++++++++++++++
 1 files changed, 130 insertions(+), 0 deletions(-)
 create mode 100644 frysk-core/frysk/pkglibdir/funit-signal.c

diff --git a/frysk-core/frysk/pkglibdir/funit-signal.c b/frysk-core/frysk/pkglibdir/funit-signal.c
new file mode 100644
index 0000000..f227d3d
--- /dev/null
+++ b/frysk-core/frysk/pkglibdir/funit-signal.c
@@ -0,0 +1,130 @@
+// This file is part of the program FRYSK.
+//
+// Copyright 2008, Red Hat Inc.
+//
+// FRYSK is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// FRYSK is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+// 
+// You should have received a copy of the GNU General Public License
+// along with FRYSK; if not, write to the Free Software Foundation,
+// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+// 
+// In addition, as a special exception, Red Hat, Inc. gives You the
+// additional right to link the code of FRYSK with code not covered
+// under the GNU General Public License ("Non-GPL Code") and to
+// distribute linked combinations including the two, subject to the
+// limitations in this paragraph. Non-GPL Code permitted under this
+// exception must only link to the code of FRYSK through those well
+// defined volatile interfaces identified in the file named EXCEPTION found in
+// the source code files (the "Approved volatile interfaces"). The files of
+// Non-GPL Code may instantiate templates or use macros or inline
+// functions from the Approved volatile interfaces without causing the
+// resulting work to be covered by the GNU General Public
+// License. Only Red Hat, Inc. may make changes or additions to the
+// list of Approved volatile interfaces. You must obey the GNU General Public
+// License in all respects for all of the FRYSK code and other code
+// used in conjunction with FRYSK except the Non-GPL Code covered by
+// this exception. If you modify this file, you may extend this
+// exception to your version of the file, but you are not obligated to
+// do so. If you do not wish to provide this exception without
+// modification, you must delete this exception statement from your
+// version and license this file solely under the GPL without
+// exception.
+
+#include <signal.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <pthread.h>
+#include <errno.h>
+
+pthread_t tester_thread;
+
+
+volatile int j = 0;
+volatile int lock = 1;
+volatile int sig;
+volatile pid_t pid;
+
+void
+*signal_parent ()
+{
+	while (lock);
+
+	kill (pid, sig);
+
+	while(1);
+}
+
+void
+handler (int sig)
+{										// _signalHandlerEntry_
+
+	if (sig == SIGUSR1)
+	{
+		--j;
+		++j;
+	}
+	else
+		exit(EXIT_FAILURE);
+
+	return;
+	
+}
+
+int
+main (int argc, char ** argv)
+{
+
+	if (argc < 3)
+	{
+		printf("Usage: funit-signal <pid> <signal>\n");
+		exit(0);
+	}
+
+	errno = 0;
+	pid_t target_pid = (pid_t) strtoul(argv[1], (char **) NULL, 10);
+	if (errno)
+	{
+		perror ("Invalid pid");
+		exit (EXIT_FAILURE);
+	}
+
+	errno = 0;
+	int signal = (int) strtoul (argv[2], (char **) NULL, 10);
+	if (errno)
+	{
+		perror ("Invalid signal");
+	exit (EXIT_FAILURE);
+	}
+
+	pid = target_pid;
+	sig = signal;
+
+	struct sigaction sa;
+	sa.sa_handler = handler;
+	sigemptyset(&sa.sa_mask);
+	sa.sa_flags = SA_RESTART;
+	sigaction(SIGUSR1, &sa, NULL);
+
+	pthread_create (&tester_thread, NULL, signal_parent, NULL);
+
+	lock = 0;
+
+	while(1) 
+	{
+		--j;
+		++j;
+		--j;
+		++j;
+		raise(SIGUSR1);  						// _signalRaiseCall_
+	}	
+	return 0;
+
+}
-- 
1.5.4.1


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]