[LLVMdev] [llvm] r188726 - Adding PIC support for ELF on x86_64 platforms (original) (raw)
Keno Fischer kfischer at college.harvard.edu
Sun Feb 1 22:06:30 PST 2015
- Previous message: [LLVMdev] FYI, cheat sheet for updating out-of-tree targets for TTI changes
- Next message: [LLVMdev] [llvm] r188726 - Adding PIC support for ELF on x86_64 platforms
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
Hi Lang,
Did you ever make any progress on this? I ran into the same problem when rewriting the GOT support (though for a different reason). If not, I can take a crack at it, but I wanted to avoid duplicating effort.
Thanks, Keno
On Tue, Jan 27, 2015 at 12:07 AM, Lang Hames <lhames at gmail.com> wrote:
Hi Andy,
Thanks very much for the insight. I'll see if I can come up with a scheme to support reapplication without having the object file present. Cheers, Lang. On Mon, Jan 26, 2015 at 1:59 PM, Kaylor, Andrew <andrew.kaylor at intel.com> wrote:
Hi Lang,
Yeah, I remember this case. Basically what’s happening is that there are relocations for ELF on x86 that use a value that is present in the object image as part of the calculation for the final value that goes in the same location. If you ever find yourself applying relocations for a second time (for instance, because the loaded object location is remapped for out-of-proc execution) the original value is no longer in the loaded object. You could probably figure out a way to combine the placeholder value with the addend field, either while the relocation records are being built (though I’m not sure if the relevant sections have been loaded yet at that point) or the first time the relocation is applied (though it might be a bit cumbersome to know whether or not any given application was the first). -Andy
From: Lang Hames [mailto:lhames at gmail.com] Sent: Monday, January 26, 2015 1:14 PM To: Kaylor, Andrew Cc: Commit Messages and Patches for LLVM Subject: Re: [llvm] r188726 - Adding PIC support for ELF on x8664 platforms Hi Andy, In the following snippet, do you recall what made it necessary to read the value from the object file via Placeholder, rather than from the copied section? + // Get the placeholder value from the generated object since + // a previous relocation attempt may have overwritten the loaded version + uint64t *Placeholder = reinterpretcast<uint64t*>(Section.ObjAddress + + Offset); + uint64t *Target = reinterpretcast<uint64t*>(Section.Address + Offset); + uint64t FinalAddress = Section.LoadAddress + Offset; + *Target = *Placeholder + Value + Addend - FinalAddress; + break; I''d like to make my new JIT APIs more aggressive about freeing the ObjectFile instances (ideally we'd be able to free immediately after a call to loadObject), but at the moment I have to hold it at least until resolveRelocations is called. I had a quick chat with Tim Northover about this and our guess was that it had something to do with relocations being applied more than once? Cheers, Lang. On Mon, Aug 19, 2013 at 4:27 PM, Andrew Kaylor <andrew.kaylor at intel.com> wrote: Author: akaylor Date: Mon Aug 19 18:27:43 2013 New Revision: 188726 URL: http://llvm.org/viewvc/llvm-project?rev=188726&view=rev Log: Adding PIC support for ELF on x8664 platforms Modified: llvm/trunk/lib/ExecutionEngine/RuntimeDyld/RuntimeDyld.cpp llvm/trunk/lib/ExecutionEngine/RuntimeDyld/RuntimeDyldELF.cpp llvm/trunk/lib/ExecutionEngine/RuntimeDyld/RuntimeDyldELF.h llvm/trunk/lib/ExecutionEngine/RuntimeDyld/RuntimeDyldImpl.h Modified: llvm/trunk/lib/ExecutionEngine/RuntimeDyld/RuntimeDyld.cpp URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/lib/ExecutionEngine/RuntimeDyld/RuntimeDyld.cpp?rev=188726&r1=188725&r2=188726&view=diff ============================================================================== --- llvm/trunk/lib/ExecutionEngine/RuntimeDyld/RuntimeDyld.cpp (original) +++ llvm/trunk/lib/ExecutionEngine/RuntimeDyld/RuntimeDyld.cpp Mon Aug 19 18:27:43 2013 @@ -169,6 +169,9 @@ ObjectImage *RuntimeDyldImpl::loadObject } } + // Give the subclasses a chance to tie-up any loose ends. + finalizeLoad(); + return obj.take(); } @@ -424,6 +427,10 @@ uint8t *RuntimeDyldImpl::createStubFunc writeInt16BE(Addr+6, 0x07F1); // brc 15,%r1 // 8-byte address stored at Addr + 8 return Addr; + } else if (Arch == Triple::x8664) { + *Addr = 0xFF; // jmp + *(Addr+1) = 0x25; // rip + // 32-bit PC-relative address of the GOT entry will be stored at Addr+2 } return Addr; } @@ -473,6 +480,7 @@ void RuntimeDyldImpl::resolveExternalSym // MemoryManager. uint8t Addr = (uint8t) MemMgr->getPointerToNamedFunction(Name.data(), true); + updateGOTEntries(Name, (uint64t)Addr); DEBUG(dbgs() << "Resolving relocations Name: " << Name_ _<< "\t" << format("%p", Addr)_ _<< "\n");_ _Modified: llvm/trunk/lib/ExecutionEngine/RuntimeDyld/RuntimeDyldELF.cpp_ _URL:_ _http://llvm.org/viewvc/llvm-project/llvm/trunk/lib/ExecutionEngine/RuntimeDyld/RuntimeDyldELF.cpp?rev=188726&r1=188725&r2=188726&view=diff_ _==============================================================================_ _--- llvm/trunk/lib/ExecutionEngine/RuntimeDyld/RuntimeDyldELF.cpp_ _(original)_ _+++ llvm/trunk/lib/ExecutionEngine/RuntimeDyld/RuntimeDyldELF.cpp Mon Aug_ _19 18:27:43 2013_ _@@ -202,7 +202,8 @@ void RuntimeDyldELF::resolveX8664Reloca_ _uint64t Offset,_ _uint64t Value,_ _uint32t Type,_ _- int64t Addend) {_ _+ int64t Addend,_ _+ uint64t SymOffset) {_ _switch (Type) {_ _default:_ _llvmunreachable("Relocation type not implemented yet!");_ _@@ -227,6 +228,21 @@ void RuntimeDyldELF::resolveX8664Reloca_ _<< " at " << format("%p\n",Target));_ _break;_ _}_ _+ case ELF::RX8664GOTPCREL: {_ _+ // findGOTEntry returns the 'G + GOT' part of the relocation_ _calculation_ _+ // based on the load/target address of the GOT (not the_ _current/local addr)._ _+ uint64t GOTAddr = findGOTEntry(Value, SymOffset);_ _+ uint32t *Target = reinterpretcast<uint32t*>(Section.Address + Offset); + uint64t FinalAddress = Section.LoadAddress + Offset; + // The processRelocationRef method combines the symbol offset and the addend + // and in most cases that's what we want. For this relocation type, we need + // the raw addend, so we subtract the symbol offset to get it. + int64t RealOffset = GOTAddr + Addend - SymOffset - FinalAddress; + assert(RealOffset <= INT32MAX && RealOffset >= INT32MIN); + int32t TruncOffset = (RealOffset & 0xFFFFFFFF); + *Target = TruncOffset; + break; + } case ELF::RX8664PC32: { // Get the placeholder value from the generated object since // a previous relocation attempt may have overwritten the loaded version @@ -240,6 +256,16 @@ void RuntimeDyldELF::resolveX8664Reloca *Target = TruncOffset; break; } + case ELF::RX8664PC64: { + // Get the placeholder value from the generated object since + // a previous relocation attempt may have overwritten the loaded version + uint64t *Placeholder = reinterpretcast<uint64t*>(Section.ObjAddress + + Offset); + uint64t *Target = reinterpretcast<uint64t*>(Section.Address + Offset); + uint64t FinalAddress = Section.LoadAddress + Offset; + *Target = *Placeholder + Value + Addend - FinalAddress; + break; + } } } @@ -584,7 +610,7 @@ void RuntimeDyldELF::findOPDEntrySection // Finally compares the Symbol value and the target symbol offset // to check if this .opd entry refers to the symbol the relocation // points to. - if (Rel.Addend != (intptrt)TargetSymbolOffset) + if (Rel.Addend != (int64t)TargetSymbolOffset) continue; sectioniterator tsi(Obj.endsections()); @@ -757,17 +783,19 @@ void RuntimeDyldELF::resolveSystemZReloc void RuntimeDyldELF::resolveRelocation(const RelocationEntry &RE, uint64t Value) { const SectionEntry &Section = Sections[RE.SectionID]; - return resolveRelocation(Section, RE.Offset, Value, RE.RelType, RE.Addend); + return resolveRelocation(Section, RE.Offset, Value, RE.RelType, RE.Addend, + RE.SymOffset); } void RuntimeDyldELF::resolveRelocation(const SectionEntry &Section, uint64t Offset, uint64t Value, uint32t Type, - int64t Addend) { + int64t Addend, + uint64t SymOffset) { switch (Arch) { case Triple::x8664: - resolveX8664Relocation(Section, Offset, Value, Type, Addend); + resolveX8664Relocation(Section, Offset, Value, Type, Addend, SymOffset); break; case Triple::x86: resolveX86Relocation(Section, Offset, @@ -830,6 +858,7 @@ void RuntimeDyldELF::processRelocationRe } if (lsi != Symbols.end()) { Value.SectionID = lsi->second.first; + Value.Offset = lsi->second.second; Value.Addend = lsi->second.second + Addend; } else { // Search for the symbol in the global symbol table @@ -838,6 +867,7 @@ void RuntimeDyldELF::processRelocationRe gsi = GlobalSymbolTable.find(TargetName.data()); if (gsi != GlobalSymbolTable.end()) { Value.SectionID = gsi->second.first; + Value.Offset = gsi->second.second; Value.Addend = gsi->second.second + Addend; } else { switch (SymType) { @@ -860,6 +890,7 @@ void RuntimeDyldELF::processRelocationRe Value.Addend = Addend; break; } + case SymbolRef::STData: case SymbolRef::STUnknown: { Value.SymbolName = TargetName.data(); Value.Addend = Addend; @@ -1150,8 +1181,67 @@ void RuntimeDyldELF::processRelocationRe ELF::R390PC32DBL, Addend); else resolveRelocation(Section, Offset, StubAddress, RelType, Addend); + } else if (Arch == Triple::x8664 && RelType == ELF::RX8664PLT32) { + // The way the PLT relocations normally work is that the linker allocates the + // PLT and this relocation makes a PC-relative call into the PLT. The PLT + // entry will then jump to an address provided by the GOT. On first call, the + // GOT address will point back into PLT code that resolves the symbol. After + // the first call, the GOT entry points to the actual function. + // + // For local functions we're ignoring all of that here and just replacing + // the PLT32 relocation type with PC32, which will translate the relocation + // into a PC-relative call directly to the function. For external symbols we + // can't be sure the function will be within 2^32 bytes of the call site, so + // we need to create a stub, which calls into the GOT. This case is + // equivalent to the usual PLT implementation except that we use the stub + // mechanism in RuntimeDyld (which puts stubs at the end of the section) + // rather than allocating a PLT section. + if (Value.SymbolName) { + // This is a call to an external function. + // Look for an existing stub. + SectionEntry &Section = Sections[SectionID]; + StubMap::constiterator i = Stubs.find(Value); + uintptrt StubAddress; + if (i != Stubs.end()) { + StubAddress = uintptrt(Section.Address) + i->second; + DEBUG(dbgs() << " Stub function found\n");_ _+ } else {_ _+ // Create a new stub function (equivalent to a PLT entry)._ _+ DEBUG(dbgs() << " Create a new stub function\n");_ _+_ _+ uintptrt BaseAddress = uintptrt(Section.Address);_ _+ uintptrt StubAlignment = getStubAlignment();_ _+ StubAddress = (BaseAddress + Section.StubOffset +_ _+ StubAlignment - 1) & -StubAlignment;_ _+ unsigned StubOffset = StubAddress - BaseAddress;_ _+ Stubs[Value] = StubOffset;_ _+ createStubFunction((uint8t *)StubAddress);_ _+_ _+ // Create a GOT entry for the external function._ _+ GOTEntries.pushback(Value);_ _+_ _+ // Make our stub function a relative call to the GOT entry._ _+ RelocationEntry RE(SectionID, StubOffset + 2,_ _+ ELF::RX8664GOTPCREL, -4);_ _+ addRelocationForSymbol(RE, Value.SymbolName);_ _+_ _+ // Bump our stub offset counter_ _+ Section.StubOffset = StubOffset + getMaxStubSize();_ _+ }_ _+_ _+ // Make the target call a call into the stub table._ _+ resolveRelocation(Section, Offset, StubAddress,_ _+ ELF::RX8664PC32, Addend);_ _+ } else {_ _+ RelocationEntry RE(SectionID, Offset, ELF::RX8664PC32,_ _Value.Addend,_ _+ Value.Offset);_ _+ addRelocationForSection(RE, Value.SectionID);_ _+ }_ _} else {_ _- RelocationEntry RE(SectionID, Offset, RelType, Value.Addend);_ _+ if (Arch == Triple::x8664 && RelType == ELF::RX8664GOTPCREL) {_ _+ GOTEntries.pushback(Value);_ _+ }_ _+ RelocationEntry RE(SectionID, Offset, RelType, Value.Addend,_ _Value.Offset);_ _if (Value.SymbolName)_ _addRelocationForSymbol(RE, Value.SymbolName);_ _else_ _@@ -1159,6 +1249,106 @@ void RuntimeDyldELF::processRelocationRe_ _}_ _}_ _+void RuntimeDyldELF::updateGOTEntries(StringRef Name, uint64t Addr) {_ _+ for (int i = 0, e = GOTEntries.size(); i != e; ++i) {_ _+ if (GOTEntries[i].SymbolName != 0 && GOTEntries[i].SymbolName ==_ _Name) {_ _+ GOTEntries[i].Offset = Addr;_ _+ }_ _+ }_ _+}_ _+_ _+sizet RuntimeDyldELF::getGOTEntrySize() {_ _+ // We don't use the GOT in all of these cases, but it's essentially_ _free_ _+ // to put them all here._ _+ sizet Result = 0;_ _+ switch (Arch) {_ _+ case Triple::x8664:_ _+ case Triple::aarch64:_ _+ case Triple::ppc64:_ _+ case Triple::ppc64le:_ _+ case Triple::systemz:_ _+ Result = sizeof(uint64t);_ _+ break;_ _+ case Triple::x86:_ _+ case Triple::arm:_ _+ case Triple::thumb:_ _+ case Triple::mips:_ _+ case Triple::mipsel:_ _+ Result = sizeof(uint32t);_ _+ break;_ _+ default: llvmunreachable("Unsupported CPU type!");_ _+ }_ _+ return Result;_ _+}_ _+_ _+uint64t RuntimeDyldELF::findGOTEntry(uint64t LoadAddress,_ _+ uint64t Offset) {_ _+ assert(GOTSectionID != 0_ _+ && "Attempting to lookup GOT entry but the GOT was never_ _allocated.");_ _+ if (GOTSectionID == 0) {_ _+ return 0;_ _+ }_ _+_ _+ sizet GOTEntrySize = getGOTEntrySize();_ _+_ _+ // Find the matching entry in our vector._ _+ int GOTIndex = -1;_ _+ uint64t SymbolOffset = 0;_ _+ for (int i = 0, e = GOTEntries.size(); i != e; ++i) {_ _+ if (GOTEntries[i].SymbolName == 0) {_ _+ if (getSectionLoadAddress(GOTEntries[i].SectionID) == LoadAddress_ _&&_ _+ GOTEntries[i].Offset == Offset) {_ _+ GOTIndex = i;_ _+ SymbolOffset = GOTEntries[i].Offset;_ _+ break;_ _+ }_ _+ } else {_ _+ // GOT entries for external symbols use the addend as the address_ _when_ _+ // the external symbol has been resolved._ _+ if (GOTEntries[i].Offset == LoadAddress) {_ _+ GOTIndex = i;_ _+ // Don't use the Addend here. The relocation handler will use_ _it._ _+ break;_ _+ }_ _+ }_ _+ }_ _+ assert(GOTIndex != -1 && "Unable to find requested GOT entry.");_ _+ if (GOTIndex == -1)_ _+ return 0;_ _+_ _+ if (GOTEntrySize == sizeof(uint64t)) {_ _+ uint64t *LocalGOTAddr = (uint64t*)getSectionAddress(GOTSectionID);_ _+ // Fill in this entry with the address of the symbol being_ _referenced._ _+ LocalGOTAddr[GOTIndex] = LoadAddress + SymbolOffset;_ _+ } else {_ _+ uint32t *LocalGOTAddr = (uint32t*)getSectionAddress(GOTSectionID);_ _+ // Fill in this entry with the address of the symbol being_ _referenced._ _+ LocalGOTAddr[GOTIndex] = (uint32t)(LoadAddress + SymbolOffset);_ _+ }_ _+_ _+ // Calculate the load address of this entry_ _+ return getSectionLoadAddress(GOTSectionID) + (GOTIndex * GOTEntrySize);_ _+}_ _+_ _+void RuntimeDyldELF::finalizeLoad() {_ _+ // Allocate the GOT if necessary_ _+ sizet numGOTEntries = GOTEntries.size();_ _+ if (numGOTEntries != 0) {_ _+ // Allocate memory for the section_ _+ unsigned SectionID = Sections.size();_ _+ sizet TotalSize = numGOTEntries * getGOTEntrySize();_ _+ uint8t *Addr = MemMgr->allocateDataSection(TotalSize, getGOTEntrySize(), + SectionID, false); + if (!Addr) + reportfatalerror("Unable to allocate memory for GOT!"); + Sections.pushback(SectionEntry(".got", Addr, TotalSize, 0)); + // For now, initialize all GOT entries to zero. We'll fill them in as + // needed when GOT-based relocations are applied. + memset(Addr, 0, TotalSize); + GOTSectionID = SectionID; + } +} + bool RuntimeDyldELF::isCompatibleFormat(const ObjectBuffer *Buffer) const { if (Buffer->getBufferSize() < strlen(ELF::ElfMagic))_ _return false;_ _Modified: llvm/trunk/lib/ExecutionEngine/RuntimeDyld/RuntimeDyldELF.h_ _URL:_ _http://llvm.org/viewvc/llvm-project/llvm/trunk/lib/ExecutionEngine/RuntimeDyld/RuntimeDyldELF.h?rev=188726&r1=188725&r2=188726&view=diff_ _==============================================================================_ _--- llvm/trunk/lib/ExecutionEngine/RuntimeDyld/RuntimeDyldELF.h (original)_ _+++ llvm/trunk/lib/ExecutionEngine/RuntimeDyld/RuntimeDyldELF.h Mon Aug_ _19 18:27:43 2013_ _@@ -35,13 +35,15 @@ class RuntimeDyldELF : public RuntimeDyl_ _uint64t Offset,_ _uint64t Value,_ _uint32t Type,_ _- int64t Addend);_ _+ int64t Addend,_ _+ uint64t SymOffset=0);_ _void resolveX8664Relocation(const SectionEntry &Section,_ _uint64t Offset,_ _uint64t Value,_ _uint32t Type,_ _- int64t Addend);_ _+ int64t Addend,_ _+ uint64t SymOffset);_ _void resolveX86Relocation(const SectionEntry &Section,_ _uint64t Offset,_ _@@ -84,8 +86,18 @@ class RuntimeDyldELF : public RuntimeDyl_ _ObjSectionToIDMap &LocalSections,_ _RelocationValueRef &Rel);_ _+ uint64t findGOTEntry(uint64t LoadAddr, uint64t Offset);_ _+ sizet getGOTEntrySize();_ _+_ _+ virtual void updateGOTEntries(StringRef Name, uint64t Addr);_ _+_ _+ SmallVector<RelocationValueRef, 2> GOTEntries; + unsigned GOTSectionID; + public: - RuntimeDyldELF(RTDyldMemoryManager *mm) : RuntimeDyldImpl(mm) {} + RuntimeDyldELF(RTDyldMemoryManager *mm) : RuntimeDyldImpl(mm), + GOTSectionID(0) + {} virtual void resolveRelocation(const RelocationEntry &RE, uint64t Value); virtual void processRelocationRef(unsigned SectionID, @@ -97,6 +109,7 @@ public: virtual bool isCompatibleFormat(const ObjectBuffer *Buffer) const; virtual ObjectImage *createObjectImage(ObjectBuffer *InputBuffer); virtual StringRef getEHFrameSection(); + virtual void finalizeLoad(); virtual ~RuntimeDyldELF(); }; Modified: llvm/trunk/lib/ExecutionEngine/RuntimeDyld/RuntimeDyldImpl.h URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/lib/ExecutionEngine/RuntimeDyld/RuntimeDyldImpl.h?rev=188726&r1=188725&r2=188726&view=diff ============================================================================== --- llvm/trunk/lib/ExecutionEngine/RuntimeDyld/RuntimeDyldImpl.h (original) +++ llvm/trunk/lib/ExecutionEngine/RuntimeDyld/RuntimeDyldImpl.h Mon Aug 19 18:27:43 2013 @@ -80,14 +80,18 @@ public: unsigned SectionID; /// Offset - offset into the section. - uintptrt Offset; + uint64t Offset; /// RelType - relocation type. uint32t RelType; /// Addend - the relocation addend encoded in the instruction itself. Also /// used to make a relocation section relative instead of symbol relative. - intptrt Addend; + int64t Addend; + + /// SymOffset - Section offset of the relocation entry's symbol (used for GOT + /// lookup). + uint64t SymOffset; /// True if this is a PCRel relocation (MachO specific). bool IsPCRel; @@ -97,20 +101,26 @@ public: RelocationEntry(unsigned id, uint64t offset, uint32t type, int64t addend) : SectionID(id), Offset(offset), RelType(type), Addend(addend), - IsPCRel(false), Size(0) {} + SymOffset(0), IsPCRel(false), Size(0) {} + + RelocationEntry(unsigned id, uint64t offset, uint32t type, int64t addend, + uint64t symoffset) + : SectionID(id), Offset(offset), RelType(type), Addend(addend), + SymOffset(symoffset), IsPCRel(false), Size(0) {} RelocationEntry(unsigned id, uint64t offset, uint32t type, int64t addend, bool IsPCRel, unsigned Size) : SectionID(id), Offset(offset), RelType(type), Addend(addend), - IsPCRel(IsPCRel), Size(Size) {} + SymOffset(0), IsPCRel(IsPCRel), Size(Size) {} }; class RelocationValueRef { public: unsigned SectionID; - intptrt Addend; + uint64t Offset; + int64t Addend; const char *SymbolName; - RelocationValueRef(): SectionID(0), Addend(0), SymbolName(0) {} + RelocationValueRef(): SectionID(0), Offset(0), Addend(0), SymbolName(0) {} inline bool operator==(const RelocationValueRef &Other) const { return std::memcmp(this, &Other, sizeof(RelocationValueRef)) == 0; @@ -175,7 +185,7 @@ protected: else if (Arch == Triple::ppc64 || Arch == Triple::ppc64le) return 44; else if (Arch == Triple::x8664) - return 8; // GOT + return 6; // 2-byte jmp instruction + 32-bit relative address else if (Arch == Triple::systemz) return 16; else @@ -292,6 +302,11 @@ protected: /// \brief Resolve relocations to external symbols. void resolveExternalSymbols(); + + /// \brief Update GOT entries for external symbols. + // The base class does nothing. ELF overrides this. + virtual void updateGOTEntries(StringRef Name, uint64t Addr) {} + virtual ObjectImage *createObjectImage(ObjectBuffer *InputBuffer); public: RuntimeDyldImpl(RTDyldMemoryManager *mm) : MemMgr(mm), HasError(false) {} @@ -336,6 +351,8 @@ public: virtual bool isCompatibleFormat(const ObjectBuffer *Buffer) const = 0; virtual StringRef getEHFrameSection(); + + virtual void finalizeLoad() {} }; } // end namespace llvm
llvm-commits mailing list llvm-commits at cs.uiuc.edu http://lists.cs.uiuc.edu/mailman/listinfo/llvm-commits
LLVM Developers mailing list LLVMdev at cs.uiuc.edu http://llvm.cs.uiuc.edu http://lists.cs.uiuc.edu/mailman/listinfo/llvmdev -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20150202/1f9acfad/attachment.html>
- Previous message: [LLVMdev] FYI, cheat sheet for updating out-of-tree targets for TTI changes
- Next message: [LLVMdev] [llvm] r188726 - Adding PIC support for ELF on x86_64 platforms
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]