diff --git a/llvm/include/llvm/MC/StringTableBuilder.h b/llvm/include/llvm/MC/StringTableBuilder.h --- a/llvm/include/llvm/MC/StringTableBuilder.h +++ b/llvm/include/llvm/MC/StringTableBuilder.h @@ -37,6 +37,7 @@ private: DenseMap StringIndexMap; size_t Size = 0; + size_t PaddingSize = 0; Kind K; unsigned Alignment; bool Finalized = false; @@ -80,6 +81,7 @@ } size_t getSize() const { return Size; } + void setPaddingSize(size_t Pad) { PaddingSize = Pad; } void clear(); void write(raw_ostream &OS) const; diff --git a/llvm/include/llvm/ObjectYAML/XCOFFYAML.h b/llvm/include/llvm/ObjectYAML/XCOFFYAML.h --- a/llvm/include/llvm/ObjectYAML/XCOFFYAML.h +++ b/llvm/include/llvm/ObjectYAML/XCOFFYAML.h @@ -59,10 +59,17 @@ uint8_t NumberOfAuxEntries; }; +struct StringTable { + bool Suppressed; + uint32_t Length; + std::vector Strings; +}; + struct Object { FileHeader Header; std::vector
Sections; std::vector Symbols; + StringTable StrTbl; Object(); }; } // namespace XCOFFYAML @@ -100,6 +107,10 @@ static void mapping(IO &IO, XCOFFYAML::Section &Sec); }; +template <> struct MappingTraits { + static void mapping(IO &IO, XCOFFYAML::StringTable &Str); +}; + template <> struct MappingTraits { static void mapping(IO &IO, XCOFFYAML::Object &Obj); }; diff --git a/llvm/lib/MC/StringTableBuilder.cpp b/llvm/lib/MC/StringTableBuilder.cpp --- a/llvm/lib/MC/StringTableBuilder.cpp +++ b/llvm/lib/MC/StringTableBuilder.cpp @@ -167,6 +167,9 @@ } } + Size += PaddingSize; + Size = alignTo(Size, Alignment); + if (K == MachO || K == MachOLinked) Size = alignTo(Size, 4); // Pad to multiple of 4. if (K == MachO64 || K == MachO64Linked) diff --git a/llvm/lib/ObjectYAML/XCOFFEmitter.cpp b/llvm/lib/ObjectYAML/XCOFFEmitter.cpp --- a/llvm/lib/ObjectYAML/XCOFFEmitter.cpp +++ b/llvm/lib/ObjectYAML/XCOFFEmitter.cpp @@ -14,12 +14,12 @@ #include "llvm/ADT/DenseMap.h" #include "llvm/BinaryFormat/XCOFF.h" #include "llvm/MC/StringTableBuilder.h" -#include "llvm/Object/XCOFFObjectFile.h" #include "llvm/ObjectYAML/ObjectYAML.h" +#include "llvm/Object/XCOFFObjectFile.h" #include "llvm/ObjectYAML/yaml2obj.h" #include "llvm/Support/EndianStream.h" -#include "llvm/Support/raw_ostream.h" #include "llvm/Support/LEB128.h" +#include "llvm/Support/raw_ostream.h" using namespace llvm; @@ -33,7 +33,7 @@ public: XCOFFWriter(XCOFFYAML::Object &Obj, raw_ostream &OS, yaml::ErrorHandler EH) : Obj(Obj), W(OS, support::big), ErrHandler(EH), - Strings(StringTableBuilder::XCOFF) { + StrTbl(StringTableBuilder::XCOFF) { Is64Bit = Obj.Header.Magic == (llvm::yaml::Hex16)XCOFF::XCOFF64; } bool writeXCOFF(); @@ -43,6 +43,7 @@ bool initFileHeader(uint64_t CurrentOffset); bool initSectionHeader(uint64_t &CurrentOffset); bool initRelocations(uint64_t &CurrentOffset); + bool initStringTable(); bool assignAddressesAndIndices(); void writeFileHeader(); void writeSectionHeader(); @@ -54,7 +55,7 @@ bool Is64Bit = false; support::endian::Writer W; yaml::ErrorHandler ErrHandler; - StringTableBuilder Strings; + StringTableBuilder StrTbl; uint64_t StartOffset; // Map the section name to its corrresponding section index. DenseMap SectionIndexMap = { @@ -140,20 +141,53 @@ return initRelocations(CurrentOffset); } +bool XCOFFWriter::initStringTable() { + StrTbl.clear(); + + // All specified strings should be added to the string table. + for (StringRef StringEnt : Obj.StrTbl.Strings) + StrTbl.add(StringEnt); + + size_t NumOfStrings = Obj.StrTbl.Strings.size(); + size_t StrTblIdx = 0; + for (XCOFFYAML::Symbol &YamlSym : Obj.Symbols) { + if (nameShouldBeInStringTable(YamlSym.SymbolName)) { + if (StrTblIdx < NumOfStrings) { + // Overwrite the symbol name with the specified string. + YamlSym.SymbolName = Obj.StrTbl.Strings[StrTblIdx]; + ++StrTblIdx; + } else + // Unoverwritten names are still stored in the string table. + StrTbl.add(YamlSym.SymbolName); + } + } + + // Set the string table length field with a custom value, if specified. + if (Obj.StrTbl.Length) { + size_t StrTblSize = StrTbl.getSize(); + if (Obj.StrTbl.Length < StrTblSize) { + ErrHandler("specified length (" + Twine(Obj.StrTbl.Length) + + ") is less than the content size (" + Twine(StrTblSize) + ")"); + return false; + } + // Add zeros as padding after strings. + size_t PaddingSize = Obj.StrTbl.Length - StrTblSize; + StrTbl.setPaddingSize(PaddingSize); + } + + StrTbl.finalize(); + return true; +} + bool XCOFFWriter::initFileHeader(uint64_t CurrentOffset) { // The default format of the object file is XCOFF32. InitFileHdr.Magic = XCOFF::XCOFF32; InitFileHdr.NumberOfSections = Obj.Sections.size(); InitFileHdr.NumberOfSymTableEntries = Obj.Symbols.size(); - for (const XCOFFYAML::Symbol &YamlSym : Obj.Symbols) { + for (const XCOFFYAML::Symbol &YamlSym : Obj.Symbols) // Add the number of auxiliary symbols to the total number. InitFileHdr.NumberOfSymTableEntries += YamlSym.NumberOfAuxEntries; - if (nameShouldBeInStringTable(YamlSym.SymbolName)) - Strings.add(YamlSym.SymbolName); - } - // Finalize the string table. - Strings.finalize(); // Calculate SymbolTableOffset for the file header. if (InitFileHdr.NumberOfSymTableEntries) { @@ -171,7 +205,6 @@ } bool XCOFFWriter::assignAddressesAndIndices() { - Strings.clear(); uint64_t FileHdrSize = Is64Bit ? XCOFF::FileHeaderSize64 : XCOFF::FileHeaderSize32; uint64_t SecHdrSize = @@ -182,8 +215,13 @@ // Calculate section header info. if (!initSectionHeader(CurrentOffset)) return false; + // Calculate file header info. - return initFileHeader(CurrentOffset); + if (!initFileHeader(CurrentOffset)) + return false; + + // Initialize the string table. + return initStringTable(); } void XCOFFWriter::writeFileHeader() { @@ -309,13 +347,13 @@ for (const XCOFFYAML::Symbol &YamlSym : Obj.Symbols) { if (Is64Bit) { W.write(YamlSym.Value); - W.write(Strings.getOffset(YamlSym.SymbolName)); + W.write(StrTbl.getOffset(YamlSym.SymbolName)); } else { if (nameShouldBeInStringTable(YamlSym.SymbolName)) { // For XCOFF32: A value of 0 indicates that the symbol name is in the // string table. W.write(0); - W.write(Strings.getOffset(YamlSym.SymbolName)); + W.write(StrTbl.getOffset(YamlSym.SymbolName)); } else { writeName(YamlSym.SymbolName, W); } @@ -355,8 +393,8 @@ if (!Obj.Symbols.empty() && !writeSymbols()) return false; // Write the string table. - if (Strings.getSize() > 4) - Strings.write(W.OS); + if (StrTbl.getSize() > 4 && !Obj.StrTbl.Suppressed) + StrTbl.write(W.OS); return true; } diff --git a/llvm/lib/ObjectYAML/XCOFFYAML.cpp b/llvm/lib/ObjectYAML/XCOFFYAML.cpp --- a/llvm/lib/ObjectYAML/XCOFFYAML.cpp +++ b/llvm/lib/ObjectYAML/XCOFFYAML.cpp @@ -143,7 +143,7 @@ } void MappingTraits::mapping(IO &IO, XCOFFYAML::Symbol &S) { - IO.mapRequired("Name", S.SymbolName); + IO.mapOptional("Name", S.SymbolName); IO.mapOptional("Value", S.Value); IO.mapOptional("Section", S.SectionName); IO.mapOptional("Type", S.Type); @@ -151,11 +151,18 @@ IO.mapOptional("NumberOfAuxEntries", S.NumberOfAuxEntries); } +void MappingTraits::mapping(IO &IO, XCOFFYAML::StringTable &Str) { + IO.mapOptional("Suppress", Str.Suppressed); + IO.mapOptional("Length", Str.Length); + IO.mapOptional("Strings", Str.Strings); +} + void MappingTraits::mapping(IO &IO, XCOFFYAML::Object &Obj) { IO.mapTag("!XCOFF", true); IO.mapRequired("FileHeader", Obj.Header); IO.mapOptional("Sections", Obj.Sections); IO.mapOptional("Symbols", Obj.Symbols); + IO.mapOptional("StringTable", Obj.StrTbl); } } // namespace yaml diff --git a/llvm/test/tools/obj2yaml/XCOFF/aix.yaml b/llvm/test/tools/obj2yaml/XCOFF/aix.yaml --- a/llvm/test/tools/obj2yaml/XCOFF/aix.yaml +++ b/llvm/test/tools/obj2yaml/XCOFF/aix.yaml @@ -77,4 +77,5 @@ # CHECK-NEXT: Type: 0x0 # CHECK-NEXT: StorageClass: C_HIDEXT # CHECK-NEXT: NumberOfAuxEntries: 1 -# CHECK-NEXT: ... + +## TODO: Dump the string table. diff --git a/llvm/test/tools/yaml2obj/XCOFF/string-table.yaml b/llvm/test/tools/yaml2obj/XCOFF/string-table.yaml --- a/llvm/test/tools/yaml2obj/XCOFF/string-table.yaml +++ b/llvm/test/tools/yaml2obj/XCOFF/string-table.yaml @@ -0,0 +1,232 @@ +## Check that yaml2obj is able to customize the string table. +## `Length`, `Strings` and/or `Suppress` can be specified in YAML. +## Here we test the behaviour in various cases. + +## Case 1: when no `Length` or `Strings` is specified for a string table, +## yaml2obj writes the default content (i.e. long symbol names in +## XCOFF32 or any symbol names in XCOFF64). +# RUN: yaml2obj --docnum=1 %s -DSYMNAME='nameInStrTbl' -o %t1 +# RUN: llvm-readobj %t1 --string-table | FileCheck %s --check-prefix=CASE1 + +# CASE1: StringTable { +# CASE1-NEXT: Length: 17 +# CASE1-NEXT: [ 4] nameInStrTbl +# CASE1-NEXT: } + +--- !XCOFF +FileHeader: + MagicNumber: 0x1DF +Symbols: + - Name: [[SYMNAME=]] +StringTable: + Suppress: [[SUPPRESS=false]] + Length: [[STRTABSIZE=0]] + +## We can specify `Length` only when the length value is equal to or greater +## than the content size. For greater cases, zeros are added as padding. +## Case 2-4 are trying to check this function. Notice that we do not specify +## `Strings` in these cases. + +## Case 2: produce a string table with a specified `Length`. In this case, +## there is no default content and the content is filled with zeroes. +# RUN: yaml2obj --docnum=1 %s -DSTRTABSIZE=20 -o %t2 +# RUN: llvm-readobj %t2 -s --string-table | FileCheck %s --check-prefix=CASE2 + +# CASE2: StringTable { +# CASE2-NEXT: Length: 20 +# CASE2-NEXT: } + +## Case 3: if the value of `Length` is greater than the content size, yaml2obj +## adds zeros as padding after the default content. +# RUN: yaml2obj --docnum=1 %s -DSYMNAME='nameInStrTbl' -DSTRTABSIZE=20 -o %t3 +# RUN: llvm-readobj %t3 --string-table | FileCheck %s --check-prefix=CASE3 + +# CASE3: StringTable { +# CASE3-NEXT: Length: 20 +# CASE3-NEXT: [ 4] nameInStrTbl +# CASE3-NEXT: } + +## Case 4: an error is reported when the value of "Length" is less than the +## content size. +# RUN: not yaml2obj --docnum=1 %s -DSYMNAME='nameInStrTbl' -DSTRTABSIZE=10 \ +# RUN: -o %t4 2>&1 | FileCheck %s --check-prefix=CASE4 + +# CASE4: yaml2obj: error: specified length (10) is less than the content size (17) + +## We can specify `Strings` for a string table. Default contents (ie. symbol +## names in string table) will be overwritten by specified values. Case 5-7 +## are trying to check this function. Notice that we do not specify `Length` +## in those cases, and the length value is implicitly generated. + +## Case 5: produce a string table with specified `Strings` directly. In this +## case, there is no default content. +# RUN: yaml2obj --docnum=2 %s -o %t5 +# RUN: llvm-readobj %t5 --string-table | FileCheck %s --check-prefix=CASE5 + +# CASE5: StringTable { +# CASE5-NEXT: Length: 8 +# CASE5-NEXT: [ 4] b +# CASE5-NEXT: [ 6] a +# CASE5-NEXT: } + +--- !XCOFF +FileHeader: + MagicNumber: 0x1DF +Symbols: + - Name: [[SYMNAME=]] + - Name: [[SYMNAME2=]] + - Name: [[SYMNAME3=]] +StringTable: + Suppress: [[SUPPRESS=false]] + Length: [[STRTABSIZE=0]] + Strings: + - a + - b + +## Case 6: if the number of `Strings` is greater than default strings, all +## default strings will be overwritten by specified ones. +# RUN: yaml2obj --docnum=2 %s -DSYMNAME='nameInStrTbl' -o %t6 +# RUN: llvm-readobj %t6 -s --string-table | FileCheck %s --check-prefix=CASE6 + +# CASE6: Symbols [ +# CASE6-NEXT: Symbol { +# CASE6-NEXT: Index: 0 +# CASE6-NEXT: Name: a +# CASE6-NEXT: Value: 0x0 +# CASE6-NEXT: Section: N_UNDEF +# CASE6-NEXT: Type: 0x0 +# CASE6-NEXT: StorageClass: C_NULL (0x0) +# CASE6-NEXT: NumberOfAuxEntries: 0 +# CASE6-NEXT: } +# CASE6-NEXT: Symbol { +# CASE6-NEXT: Index: 1 +# CASE6-NEXT: Name: +# CASE6-NEXT: Value: 0x0 +# CASE6-NEXT: Section: N_UNDEF +# CASE6-NEXT: Type: 0x0 +# CASE6-NEXT: StorageClass: C_NULL (0x0) +# CASE6-NEXT: NumberOfAuxEntries: 0 +# CASE6-NEXT: } +# CASE6-NEXT: Symbol { +# CASE6-NEXT: Index: 2 +# CASE6-NEXT: Name: +# CASE6-NEXT: Value: 0x0 +# CASE6-NEXT: Section: N_UNDEF +# CASE6-NEXT: Type: 0x0 +# CASE6-NEXT: StorageClass: C_NULL (0x0) +# CASE6-NEXT: NumberOfAuxEntries: 0 +# CASE6-NEXT: } +# CASE6-NEXT: ] +# CASE6-NEXT: StringTable { +# CASE6-NEXT: Length: 8 +# CASE6-NEXT: [ 4] b +# CASE6-NEXT: [ 6] a +# CASE6-NEXT: } + +## Case 7: if the number of `Strings` is less than default strings, default +## strings will be partially overwritten by specified ones, and the +## unoverwritten strings will still be stored after specified strings +## in the string table. +# RUN: yaml2obj --docnum=2 %s -DSYMNAME='nameInStrTbl' \ +# RUN: -DSYMNAME2='name2InStrTbl' -DSYMNAME3='name3InStrTbl' -o %t7 +# RUN: llvm-readobj %t7 -s --string-table | FileCheck %s --check-prefix=CASE7 + +# CASE7: Symbols [ +# CASE7-NEXT: Symbol { +# CASE7-NEXT: Index: 0 +# CASE7-NEXT: Name: a +# CASE7-NEXT: Value: 0x0 +# CASE7-NEXT: Section: N_UNDEF +# CASE7-NEXT: Type: 0x0 +# CASE7-NEXT: StorageClass: C_NULL (0x0) +# CASE7-NEXT: NumberOfAuxEntries: 0 +# CASE7-NEXT: } +# CASE7-NEXT: Symbol { +# CASE7-NEXT: Index: 1 +# CASE7-NEXT: Name: b +# CASE7-NEXT: Value: 0x0 +# CASE7-NEXT: Section: N_UNDEF +# CASE7-NEXT: Type: 0x0 +# CASE7-NEXT: StorageClass: C_NULL (0x0) +# CASE7-NEXT: NumberOfAuxEntries: 0 +# CASE7-NEXT: } +# CASE7-NEXT: Symbol { +# CASE7-NEXT: Index: 2 +# CASE7-NEXT: Name: name3InStrTbl +# CASE7-NEXT: Value: 0x0 +# CASE7-NEXT: Section: N_UNDEF +# CASE7-NEXT: Type: 0x0 +# CASE7-NEXT: StorageClass: C_NULL (0x0) +# CASE7-NEXT: NumberOfAuxEntries: 0 +# CASE7-NEXT: } +# CASE7-NEXT: ] +# CASE7-NEXT: StringTable { +# CASE7-NEXT: Length: 22 +# CASE7-NEXT: [ 4] name3InStrTbl +# CASE7-NEXT: [ 12] b +# CASE7-NEXT: [ 14] a +# CASE7-NEXT: } + +## We can specify both `Length` and `Strings` when the length value is equal +## to or greater than the content size. Case 8-10 are trying to check this. + +## Case 8: produce a string table with specified `Length` and `Strings` when +## the length value is greater than the size of specified strings. +## In this case, there is no default content. +# RUN: yaml2obj --docnum=2 %s -DSTRTABSIZE=20 -o %t8 +# RUN: llvm-readobj %t8 --string-table | FileCheck %s --check-prefix=CASE8 + +# CASE8: StringTable { +# CASE8-NEXT: Length: 20 +# CASE8-NEXT: [ 4] b +# CASE8-NEXT: [ 6] a +# CASE8-NEXT: } + +## Case 9: for a string table with default contents, we can specify `Length` +## and `Strings` when the length size is greater than the final +## content size. +# RUN: yaml2obj --docnum=2 %s -DSTRTABSIZE=30 -DSYMNAME='nameInStrTbl' \ +# RUN: -DSYMNAME2='name2InStrTbl' -DSYMNAME3='name3InStrTbl' -o %t9 +# RUN: llvm-readobj %t9 --string-table | FileCheck %s --check-prefix=CASE9 + +# CASE9: StringTable { +# CASE9-NEXT: Length: 30 +# CASE9-NEXT: [ 4] name3InStrTbl +# CASE9-NEXT: [ 12] b +# CASE9-NEXT: [ 14] a +# CASE9-NEXT: } + +## Case 10: an error will be reported when the value of `Length` is less than +## the final content size. None of `Length`, `Strings` or default +## contents is empty in this case. +# RUN: not yaml2obj --docnum=2 %s -DSTRTABSIZE=10 -DSYMNAME='nameInStrTbl' \ +# RUN: -DSYMNAME2='name2InStrTbl' -DSYMNAME3='name3InStrTbl' -o %t9 2>&1 \ +# RUN: | FileCheck %s --check-prefix=CASE10 + +# CASE10: yaml2obj: error: specified length (10) is less than the content size (22) + +## We can set `Suppress` as true to suppress the emission of string table. +## Case 11-13 are trying to check this. + +## Case 11: both `Suppress` and `Length` are specified. +# RUN: yaml2obj --docnum=1 %s -DSTRTABSIZE=10 -DSUPPRESS=true -o %t11 +# RUN: llvm-readobj %t11 --string-table | FileCheck %s --check-prefix=CASE11 + +# CASE11: StringTable { +# CASE11-NEXT: Length: 0 +# CASE11-NEXT: } + +## Case 12: both `Suppress` and `Strings` are specified. +# RUN: yaml2obj --docnum=2 %s -DSUPPRESS=true -o %t12 +# RUN: llvm-readobj %t12 --string-table | FileCheck %s --check-prefix=CASE12 + +# CASE12: StringTable { +# CASE12-NEXT: Length: 0 +# CASE12-NEXT: } + +## Case 13: for a string table with default contents, `Suppress` of true triggers +## an error (since symbol names cannot be found from the string table). +# RUN: yaml2obj --docnum=1 %s -DSYMNAME='nameInStrTbl' -DSUPPRESS=true -o %t13 +# RUN: not llvm-readobj -s 2>&1 %t13 | FileCheck -DFILE=%t13 %s --check-prefix=CASE13 + +# CASE13: llvm-readobj: error: '[[FILE]]': Bad offset for string table entry