🚀 go-pugleaf

RetroBBS NetNews Server

Inspired by RockSolid Light RIP Retro Guy

1 total messages Started by Michael Tiemann Sun, 12 Mar 1989 02:21
GDB 3.1+
#711
Author: Michael Tiemann
Date: Sun, 12 Mar 1989 02:21
639 lines
19475 bytes
Some people have noticed that GDB 3.1 does not quite replace GDB+
2.8.1 when it comes to debugging GNU C++ code.  This is more my fault
than Randy's.  The following diffs should go a long way toward
remedying the problem.  You will also need a small patch for dbxout.c
for GNU C++ 1.34.0, which is enclosed at the bottom.

Michael

yahi% diff -c2 dbxread.c~ dbxread.c
*** dbxread.c~	Mon Jan 30 08:59:56 1989
--- dbxread.c	Sat Mar 11 17:59:56 1989
***************
*** 3786,3789 ****
--- 3786,3794 ----
    int read_possible_virtual_info = 0;
  
+   if (TYPE_CODE (type) == TYPE_CODE_UNDEF)
+     {
+       TYPE_MAIN_VARIANT (type) = type;
+     }
+ 
    TYPE_CODE (type) = TYPE_CODE_STRUCT;
  
***************
*** 4010,4013 ****
--- 4015,4021 ----
  
  	      new_sublist->fn_field.type = read_type (pp);
+ 	      smash_to_method_type (new_sublist->fn_field.type,
+ 				    TYPE_DOMAIN_TYPE (new_sublist->fn_field.type),
+ 				    TYPE_TARGET_TYPE (new_sublist->fn_field.type));
  	      new_sublist->fn_field.args = read_args (pp, ':');
  	      p = *pp;
yahi% diff -c2 values.c~ values.c
*** values.c~	Tue Jan 31 09:55:59 1989
--- values.c	Sat Mar 11 18:14:26 1989
***************
*** 659,669 ****
        = fill_in_vptr_fieldno (type);
  
!   /* Pretend that this array is just an array of pointers to integers.
!      This will have to change for multiple inheritance.  */
!   vtbl = value_copy (value_field (arg1, TYPE_VPTR_FIELDNO (type)));
!   VALUE_TYPE (vtbl) = lookup_pointer_type (builtin_type_int);
  
    /* Index into the virtual function table.  */
!   vfn = value_subscript (vtbl, vi);
  
    /* Reinstantiate the function pointer with the correct type.  */
--- 659,669 ----
        = fill_in_vptr_fieldno (type);
  
!   /* The virtual function table is now an array of structures
!      which have the form { int16 offset, delta; void* pfn; }.  */
!   vtbl = value_ind (value_field (arg1, TYPE_VPTR_FIELDNO (type)));
  
    /* Index into the virtual function table.  */
!   vfn = value_struct_elt (value_subscript (vtbl, vi), 0, "pfn",
! 			  "internal error in virtual function lookup");
  
    /* Reinstantiate the function pointer with the correct type.  */
yahi% diff -c2 symseg.h~ symseg.h
*** symseg.h~	Mon Jan 23 12:42:18 1989
--- symseg.h	Sat Mar 11 17:44:56 1989
***************
*** 122,125 ****
--- 122,126 ----
    /* C++ */
    TYPE_CODE_MEMBER,		/* Member type */
+   TYPE_CODE_METHOD,		/* Method type */
    TYPE_CODE_REF,		/* C++ Reference types */
  };
***************
*** 147,151 ****
    /* For a pointer type, describes the type of object pointed to.
       For an array type, describes the type of the elements.
!      For a function type, describes the type of the value.
       For a range type, describes the type of the full range.
       Unused otherwise.  */
--- 148,152 ----
    /* For a pointer type, describes the type of object pointed to.
       For an array type, describes the type of the elements.
!      For a function or method type, describes the type of the value.
       For a range type, describes the type of the full range.
       Unused otherwise.  */
***************
*** 240,244 ****
  	  char *name;
  #endif
! 	  /* The type of the argument */
  	  struct type *type;
  	  /* The argument list */
--- 241,245 ----
  	  char *name;
  #endif
! 	  /* The return value of the method */
  	  struct type *type;
  	  /* The argument list */
yahi% diff -c2 symtab.c~ symtab.c
*** symtab.c~	Tue Jan 17 11:29:19 1989
--- symtab.c	Sat Mar 11 17:47:40 1989
***************
*** 362,365 ****
--- 362,419 ----
  }
  
+ struct type *
+ lookup_method_type (type, domain)
+      struct type *type, *domain;
+ {
+   register struct type *mtype = TYPE_MAIN_VARIANT (type);
+   struct type *main_type;
+ 
+   main_type = mtype;
+   while (mtype)
+     {
+       if (TYPE_DOMAIN_TYPE (mtype) == domain)
+ 	return mtype;
+       mtype = TYPE_NEXT_VARIANT (mtype);
+     }
+ 
+   /* This is the first time anyone wanted this member type.  */
+   if (TYPE_FLAGS (type) & TYPE_FLAG_PERM)
+     mtype  = (struct type *) xmalloc (sizeof (struct type));
+   else
+     mtype  = (struct type *) obstack_alloc (symbol_obstack,
+ 					    sizeof (struct type));
+ 
+   bzero (mtype, sizeof (struct type));
+   if (main_type == 0)
+     main_type = mtype;
+   else
+     {
+       TYPE_NEXT_VARIANT (mtype) = TYPE_NEXT_VARIANT (main_type);
+       TYPE_NEXT_VARIANT (main_type) = mtype;
+     }
+   TYPE_MAIN_VARIANT (mtype) = main_type;
+   TYPE_TARGET_TYPE (mtype) = type;
+   TYPE_DOMAIN_TYPE (mtype) = domain;
+   /* New type is permanent if type pointed to is permanent.  */
+   if (TYPE_FLAGS (type) & TYPE_FLAG_PERM)
+     TYPE_FLAGS (mtype) |= TYPE_FLAG_PERM;
+ 
+   /* In practice, this is never used.  */
+   TYPE_LENGTH (mtype) = 1;
+   TYPE_CODE (mtype) = TYPE_CODE_MEMBER;
+ 
+ #if 0
+   /* Now splice in the new member pointer type.  */
+   if (main_type)
+     {
+       /* This type was not "smashed".  */
+       TYPE_CHAIN (mtype) = TYPE_CHAIN (main_type);
+       TYPE_CHAIN (main_type) = mtype;
+     }
+ #endif
+ 
+   return mtype;
+ }
+ 
  /* Given a type TYPE, return a type which has offset OFFSET,
     via_virtual VIA_VIRTUAL, and via_public VIA_PUBLIC.
***************
*** 491,494 ****
--- 545,565 ----
  
    TYPE_MAIN_VARIANT (type) = lookup_member_type (domain, to_type);
+ }
+ 
+ /* Smash TYPE to be a type of method of DOMAIN with type TO_TYPE.  */
+ 
+ void
+ smash_to_method_type (type, domain, to_type)
+      struct type *type, *domain, *to_type;
+ {
+   bzero (type, sizeof (struct type));
+   TYPE_TARGET_TYPE (type) = to_type;
+   TYPE_DOMAIN_TYPE (type) = domain;
+ 
+   /* In practice, this is never needed.  */
+   TYPE_LENGTH (type) = 1;
+   TYPE_CODE (type) = TYPE_CODE_METHOD;
+ 
+   TYPE_MAIN_VARIANT (type) = lookup_method_type (domain, to_type);
  }
  
yahi% diff -c2 valops.c~ valops.c
*** valops.c~	Thu Jan 19 15:09:14 1989
--- valops.c	Fri Mar 10 22:03:47 1989
***************
*** 525,536 ****
      /* If it's a member function, just look at the function
         part of it.  */
-     if (code == TYPE_CODE_MEMBER)
-       {
- 	ftype = TYPE_TARGET_TYPE (ftype);
- 	code = TYPE_CODE (ftype);
-       }
- 
      /* Determine address to call.  */
!     if (code == TYPE_CODE_FUNC)
        {
  	funaddr = VALUE_ADDRESS (function);
--- 525,530 ----
      /* If it's a member function, just look at the function
         part of it.  */
      /* Determine address to call.  */
!     if (code == TYPE_CODE_FUNC || code == TYPE_CODE_METHOD)
        {
  	funaddr = VALUE_ADDRESS (function);
***************
*** 540,545 ****
        {
  	funaddr = value_as_long (function);
! 	if (TYPE_CODE (TYPE_TARGET_TYPE (ftype))
! 	    == TYPE_CODE_FUNC)
  	  value_type = TYPE_TARGET_TYPE (TYPE_TARGET_TYPE (ftype));
  	else
--- 534,539 ----
        {
  	funaddr = value_as_long (function);
! 	if (TYPE_CODE (TYPE_TARGET_TYPE (ftype)) == TYPE_CODE_FUNC
! 	    || TYPE_CODE (TYPE_TARGET_TYPE (ftype)) == TYPE_CODE_METHOD)
  	  value_type = TYPE_TARGET_TYPE (TYPE_TARGET_TYPE (ftype));
  	else
yahi% diff -c2 valprint.c~ valprint.c
*** valprint.c~	Wed Jan 11 10:51:31 1989
--- valprint.c	Fri Mar 10 22:41:35 1989
***************
*** 186,190 ****
  	  break;
  	}
!       if (TYPE_CODE (TYPE_TARGET_TYPE (type)) == TYPE_CODE_MEMBER)
  	{
  	  struct type *domain = TYPE_DOMAIN_TYPE (TYPE_TARGET_TYPE (type));
--- 186,190 ----
  	  break;
  	}
!       if (TYPE_CODE (TYPE_TARGET_TYPE (type)) == TYPE_CODE_METHOD)
  	{
  	  struct type *domain = TYPE_DOMAIN_TYPE (TYPE_TARGET_TYPE (type));
***************
*** 195,297 ****
  
  	  val = unpack_long (builtin_type_int, valaddr);
! 	  if (TYPE_CODE (target) == TYPE_CODE_FUNC)
  	    {
! 	      if (val < 128)
  		{
! 		  len = TYPE_NFN_FIELDS (domain);
! 		  for (i = 0; i < len; i++)
! 		    {
! 		      f = TYPE_FN_FIELDLIST1 (domain, i);
! 		      len2 = TYPE_FN_FIELDLIST_LENGTH (domain, i);
  
! 		      for (j = 0; j < len2; j++)
! 			{
! 			  QUIT;
! 			  if (TYPE_FN_FIELD_VOFFSET (f, j) == val)
! 			    {
! 			      kind = "virtual";
! 			      goto common;
! 			    }
! 			}
! 		    }
! 		}
! 	      else
! 		{
! 		  struct symbol *sym = find_pc_function ((CORE_ADDR) val);
! 		  if (sym == 0)
! 		    error ("invalid pointer to member function");
! 		  len = TYPE_NFN_FIELDS (domain);
! 		  for (i = 0; i < len; i++)
  		    {
! 		      f = TYPE_FN_FIELDLIST1 (domain, i);
! 		      len2 = TYPE_FN_FIELDLIST_LENGTH (domain, i);
! 
! 		      for (j = 0; j < len2; j++)
  			{
! 			  QUIT;
! 			  if (!strcmp (SYMBOL_NAME (sym), TYPE_FN_FIELD_PHYSNAME (f, j)))
! 			    goto common;
  			}
  		    }
  		}
- 	    common:
- 	      if (i < len)
- 		{
- 		  fputc ('&', stream);
- 		  type_print_varspec_prefix (TYPE_FN_FIELD_TYPE (f, j), stream, 0, 0);
- 		  fprintf (stream, kind);
- 		  if (TYPE_FN_FIELD_PHYSNAME (f, j)[0] == '_'
- 		      && TYPE_FN_FIELD_PHYSNAME (f, j)[1] == '$')
- 		    type_print_method_args
- 		      (TYPE_FN_FIELD_ARGS (f, j) + 1, "~",
- 		       TYPE_FN_FIELDLIST_NAME (domain, i), stream);
- 		  else
- 		    type_print_method_args
- 		      (TYPE_FN_FIELD_ARGS (f, j), "",
- 		       TYPE_FN_FIELDLIST_NAME (domain, i), stream);
- 		  break;
- 		}
  	    }
  	  else
  	    {
! 	      /* VAL is a byte offset into the structure type DOMAIN.
! 		 Find the name of the field for that offset and
! 		 print it.  */
! 	      int extra = 0;
! 	      int bits = 0;
! 	      len = TYPE_NFIELDS (domain);
! 	      val <<= 3;	/* @@ Make VAL into bit offset */
  	      for (i = 0; i < len; i++)
  		{
! 		  int bitpos = TYPE_FIELD_BITPOS (domain, i);
! 		  QUIT;
! 		  if (val == bitpos)
! 		    break;
! 		  if (val < bitpos && i > 0)
  		    {
! 		      int ptrsize = (TYPE_LENGTH (builtin_type_char) * TYPE_LENGTH (target));
! 		      /* Somehow pointing into a field.  */
! 		      i -= 1;
! 		      extra = (val - TYPE_FIELD_BITPOS (domain, i));
! 		      if (extra & 0x3)
! 			bits = 1;
! 		      else
! 			extra >>= 3;
! 		      break;
  		    }
  		}
! 	      if (i < len)
  		{
! 		  fputc ('&', stream);
! 		  type_print_base (domain, stream, 0, 0);
! 		  fprintf (stream, "::");
! 		  fprintf (stream, "%s", TYPE_FIELD_NAME (domain, i));
! 		  if (extra)
! 		    fprintf (stream, " + %d bytes", extra);
! 		  if (bits)
! 		    fprintf (stream, " (offset in bits)");
  		  break;
  		}
  	    }
  	  fputc ('(', stream);
  	  type_print (type, "", stream, -1);
--- 195,303 ----
  
  	  val = unpack_long (builtin_type_int, valaddr);
! 	  if (val < 128)
  	    {
! 	      len = TYPE_NFN_FIELDS (domain);
! 	      for (i = 0; i < len; i++)
  		{
! 		  f = TYPE_FN_FIELDLIST1 (domain, i);
! 		  len2 = TYPE_FN_FIELDLIST_LENGTH (domain, i);
  
! 		  for (j = 0; j < len2; j++)
  		    {
! 		      QUIT;
! 		      if (TYPE_FN_FIELD_VOFFSET (f, j) == val)
  			{
! 			  kind = "virtual";
! 			  goto common;
  			}
  		    }
  		}
  	    }
  	  else
  	    {
! 	      struct symbol *sym = find_pc_function ((CORE_ADDR) val);
! 	      if (sym == 0)
! 		error ("invalid pointer to member function");
! 	      len = TYPE_NFN_FIELDS (domain);
  	      for (i = 0; i < len; i++)
  		{
! 		  f = TYPE_FN_FIELDLIST1 (domain, i);
! 		  len2 = TYPE_FN_FIELDLIST_LENGTH (domain, i);
! 
! 		  for (j = 0; j < len2; j++)
  		    {
! 		      QUIT;
! 		      if (!strcmp (SYMBOL_NAME (sym), TYPE_FN_FIELD_PHYSNAME (f, j)))
! 			goto common;
  		    }
  		}
! 	    }
! 	common:
! 	  if (i < len)
! 	    {
! 	      fputc ('&', stream);
! 	      type_print_varspec_prefix (TYPE_FN_FIELD_TYPE (f, j), stream, 0, 0);
! 	      fprintf (stream, kind);
! 	      if (TYPE_FN_FIELD_PHYSNAME (f, j)[0] == '_'
! 		  && TYPE_FN_FIELD_PHYSNAME (f, j)[1] == '$')
! 		type_print_method_args
! 		  (TYPE_FN_FIELD_ARGS (f, j) + 1, "~",
! 		   TYPE_FN_FIELDLIST_NAME (domain, i), stream);
! 	      else
! 		type_print_method_args
! 		  (TYPE_FN_FIELD_ARGS (f, j), "",
! 		   TYPE_FN_FIELDLIST_NAME (domain, i), stream);
! 	      break;
! 	    }
! 	  fputc ('(', stream);
! 	  type_print (type, "", stream, -1);
! 	  fprintf (stream, ") %d", val >> 3);
! 	}
!       else if (TYPE_CODE (TYPE_TARGET_TYPE (type)) == TYPE_CODE_MEMBER)
! 	{
! 	  struct type *domain = TYPE_DOMAIN_TYPE (TYPE_TARGET_TYPE (type));
! 	  struct type *target = TYPE_TARGET_TYPE (TYPE_TARGET_TYPE (type));
! 	  struct fn_field *f;
! 	  int j, len2;
! 	  char *kind = "";
! 
! 	  /* VAL is a byte offset into the structure type DOMAIN.
! 	     Find the name of the field for that offset and
! 	     print it.  */
! 	  int extra = 0;
! 	  int bits = 0;
! 	  len = TYPE_NFIELDS (domain);
! 	  val <<= 3;		/* @@ Make VAL into bit offset */
! 	  for (i = 0; i < len; i++)
! 	    {
! 	      int bitpos = TYPE_FIELD_BITPOS (domain, i);
! 	      QUIT;
! 	      if (val == bitpos)
! 		break;
! 	      if (val < bitpos && i > 0)
  		{
! 		  int ptrsize = (TYPE_LENGTH (builtin_type_char) * TYPE_LENGTH (target));
! 		  /* Somehow pointing into a field.  */
! 		  i -= 1;
! 		  extra = (val - TYPE_FIELD_BITPOS (domain, i));
! 		  if (extra & 0x3)
! 		    bits = 1;
! 		  else
! 		    extra >>= 3;
  		  break;
  		}
  	    }
+ 	  if (i < len)
+ 	    {
+ 	      fputc ('&', stream);
+ 	      type_print_base (domain, stream, 0, 0);
+ 	      fprintf (stream, "::");
+ 	      fprintf (stream, "%s", TYPE_FIELD_NAME (domain, i));
+ 	      if (extra)
+ 		fprintf (stream, " + %d bytes", extra);
+ 	      if (bits)
+ 		fprintf (stream, " (offset in bits)");
+ 	      break;
+ 	    }
  	  fputc ('(', stream);
  	  type_print (type, "", stream, -1);
***************
*** 573,576 ****
--- 579,583 ----
         &&
         (code == TYPE_CODE_PTR || code == TYPE_CODE_FUNC
+ 	|| code == TYPE_CODE_METHOD
  	|| code == TYPE_CODE_ARRAY
  	|| code == TYPE_CODE_MEMBER
***************
*** 702,705 ****
--- 709,721 ----
        break;
  
+     case TYPE_CODE_METHOD:
+       type_print_varspec_prefix (TYPE_TARGET_TYPE (type), stream, 0,
+ 				 passed_a_ptr);
+       fputc (' ', stream);
+       type_print_base (TYPE_DOMAIN_TYPE (type), stream, 0,
+ 		       passed_a_ptr);
+       fprintf (stream, "::");
+       break;
+ 
      case TYPE_CODE_REF:
        type_print_varspec_prefix (TYPE_TARGET_TYPE (type), stream, 0, 1);
***************
*** 758,761 ****
--- 774,783 ----
        break;
  
+     case TYPE_CODE_METHOD:
+       if (passed_a_ptr)
+ 	fputc (')', stream);
+       type_print_varspec_suffix (TYPE_TARGET_TYPE (type), stream, 0, 0);
+       break;
+ 
      case TYPE_CODE_PTR:
      case TYPE_CODE_REF:
***************
*** 819,822 ****
--- 841,845 ----
      case TYPE_CODE_REF:
      case TYPE_CODE_FUNC:
+     case TYPE_CODE_METHOD:
        type_print_base (TYPE_TARGET_TYPE (type), stream, show, level);
        break;
***************
*** 887,891 ****
  		  if (TYPE_FN_FIELD_VIRTUAL_P (f, j))
  		    fprintf (stream, "virtual ");
! 		  type_print (TYPE_TARGET_TYPE (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j))), "", stream, 0);
  		  if (TYPE_FN_FIELD_PHYSNAME (f, j)[0] == '_'
  		      && TYPE_FN_FIELD_PHYSNAME (f, j)[1] == '$')
--- 910,914 ----
  		  if (TYPE_FN_FIELD_VIRTUAL_P (f, j))
  		    fprintf (stream, "virtual ");
! 		  type_print (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)), "", stream, 0);
  		  if (TYPE_FN_FIELD_PHYSNAME (f, j)[0] == '_'
  		      && TYPE_FN_FIELD_PHYSNAME (f, j)[1] == '$')
yahi% gdb a.out
GDB 3.1, Copyright (C) 1988 Free Software Foundation, Inc.
There is ABSOLUTELY NO WARRANTY for GDB; type "info warranty" for details.
GDB is free software and you are welcome to distribute copies of it
 under certain conditions; type "info copying" to see the conditions.
Reading symbol data from /home/sioux/mdt/dist-gdb/a.out...done.
Function fatal not defined.
Type "help" for a list of commands.
(gdb) pty a
struct a  {
    int x;
    int y;

    int f ();
    int f (int, int);

}
(gdb) pty b
struct b : public a  {
    int z;
    int w;

    int f ();
    int f (int, int);

}
(gdb) b main
Breakpoint 1 at 0x21a8: file test.c, line 30.
(gdb) r
Starting program: /home/sioux/mdt/dist-gdb/a.out 

Bpt 1, main () (test.c line 33)
33	  x.f (5, 6);
(gdb) p x
$1 = {x = 0, y = 0}
(gdb) p x.f (-1, -2)
$2 = -134218336
(gdb) p x.f()
calling f for a(-1,-2)
$3 = -134218336
(gdb) b a::f
[0] cancel
[1] all
[2] file:test.c; line number:10
[3] file:test.c; line number:6
> 1
Breakpoint 2 at 0x2138: file test.c, line 10.
Breakpoint 3 at 0x2178: file test.c, line 6.
Multiple breakpoints were set.
Use the "delete" command to delete unwanted breakpoints.
(gdb) c
Continuing.

Bpt 2, f_PSa_SI_SI ($this=(struct a *) 0xf7fffda0, i=5, j=6) (test.c line 10)
10	      x = i;
(gdb) p x
$4 = -1
(gdb) i ad this
No symbol "this" in current context.
(gdb) p ad $this
No symbol "ad" in current context.
(gdb) i ad $this
Symbol "$this" is a local variable at frame offset 68.
(gdb) i ad x
Symbol "x" is a field of the local class variable `this'
(gdb) set x = 10
(gdb) p *$this
Attempt to take contents of a non-pointer value.
(gdb) p $this
$5 = void
(gdb) p x
$6 = 10
(gdb) quit
The program is running.  Quit anyway? (y or n) y
yahi% 

As you can see, GDB 3.1 now behaves more like a C++ debugger.

yahi% diff -c2 dbxout.c~ dbxout.c
*** dbxout.c~	Mon Feb 27 17:43:02 1989
--- dbxout.c	Sat Mar 11 17:24:26 1989
***************
*** 523,527 ****
  		  t = tem; /* Get to the FUNCTION_DECL */
  		  dbxout_type (TREE_TYPE (t), 0); /* METHOD_TYPE */
- 		  dbxout_args (TYPE_ARG_TYPES (TREE_TYPE (t)));
  		  fprintf (asmfile, ":%s;%c%c",
  			   IDENTIFIER_POINTER (DECL_NAME (t)),
--- 523,526 ----
***************
*** 575,579 ****
  		  t = tem; /* Get to the FUNCTION_DECL */
  		  dbxout_type (TREE_TYPE (t), 0); /* METHOD_TYPE */
- 		  dbxout_args (TYPE_ARG_TYPES (TREE_TYPE (t)));
  		  fprintf (asmfile, ":%s;%c%c",
  			   IDENTIFIER_POINTER (DECL_NAME (t)),
--- 574,577 ----
***************
*** 695,698 ****
--- 693,697 ----
  	  CHARS (1);
  	  dbxout_type (TREE_TYPE (type), 0);
+ 	  dbxout_args (TYPE_ARG_TYPES (type));
  	}
        else
***************
*** 786,791 ****
  	break;
        FORCE_TEXT;
        fprintf (asmfile, ".stabs \"%s:%c",
! 	       IDENTIFIER_POINTER (DECL_ORIGINAL_NAME (decl)),
  	       TREE_PUBLIC (decl) ? 'F' : 'f');
  
--- 785,792 ----
  	break;
        FORCE_TEXT;
+       /* For GDB 3.1, it may be better to use DECL_NAME (instead of DECL_ORIGINAL_NAME
+ 	 in this case.  */
        fprintf (asmfile, ".stabs \"%s:%c",
! 	       IDENTIFIER_POINTER (DECL_NAME (decl)),
  	       TREE_PUBLIC (decl) ? 'F' : 'f');
  
yahi% 

Michael
Thread Navigation

This is a paginated view of messages in the thread with full content displayed inline.

Messages are displayed in chronological order, with the original post highlighted in green.

Use pagination controls to navigate through all messages in large threads.

Back to All Threads