diff --git a/llvm/docs/CommandGuide/llvm-objcopy.rst b/llvm/docs/CommandGuide/llvm-objcopy.rst --- a/llvm/docs/CommandGuide/llvm-objcopy.rst +++ b/llvm/docs/CommandGuide/llvm-objcopy.rst @@ -503,6 +503,7 @@ - `binary` - `ihex` +- `ihex-flat` - `elf32-i386` - `elf32-x86-64` - `elf64-x86-64` @@ -529,6 +530,12 @@ Additionally, all targets except `binary` and `ihex` can have `-freebsd` as a suffix. +`ihex-flat` is permitted as an output format only. It's very similar +to `ihex`, except that it represents all addresses as being in a flat +32-bit address space, and never uses the x86-16 style segment:offset +representation. This makes the output hex files slightly easier to +consume, because fewer different record types are involved. + BINARY INPUT AND OUTPUT ----------------------- diff --git a/llvm/include/llvm/ObjCopy/CommonConfig.h b/llvm/include/llvm/ObjCopy/CommonConfig.h --- a/llvm/include/llvm/ObjCopy/CommonConfig.h +++ b/llvm/include/llvm/ObjCopy/CommonConfig.h @@ -32,6 +32,7 @@ ELF, Binary, IHex, + IHexFlat, }; // This type keeps track of the machine info for various architectures. This diff --git a/llvm/lib/ObjCopy/ELF/ELFObjcopy.cpp b/llvm/lib/ObjCopy/ELF/ELFObjcopy.cpp --- a/llvm/lib/ObjCopy/ELF/ELFObjcopy.cpp +++ b/llvm/lib/ObjCopy/ELF/ELFObjcopy.cpp @@ -159,7 +159,9 @@ case FileFormat::Binary: return std::make_unique(Obj, Out); case FileFormat::IHex: - return std::make_unique(Obj, Out); + return std::make_unique(Obj, Out, true); + case FileFormat::IHexFlat: + return std::make_unique(Obj, Out, false); default: return createELFWriter(Config, Obj, Out, OutputElfType); } diff --git a/llvm/lib/ObjCopy/ELF/ELFObject.h b/llvm/lib/ObjCopy/ELF/ELFObject.h --- a/llvm/lib/ObjCopy/ELF/ELFObject.h +++ b/llvm/lib/ObjCopy/ELF/ELFObject.h @@ -271,6 +271,9 @@ // Extended linear address uint32_t BaseAddr = 0; + // Whether we're emitting segment:offset format at all + bool MayUseSegmentOffset; + // Write segment address corresponding to 'Addr' uint64_t writeSegmentAddr(uint64_t Addr); // Write extended linear (base) address corresponding to 'Addr' @@ -284,8 +287,8 @@ virtual void writeData(uint8_t Type, uint16_t Addr, ArrayRef Data); public: - explicit IHexSectionWriterBase(WritableMemoryBuffer &Buf) - : BinarySectionWriter(Buf) {} + IHexSectionWriterBase(WritableMemoryBuffer &Buf, bool MayUseSegmentOffset) + : BinarySectionWriter(Buf), MayUseSegmentOffset(MayUseSegmentOffset) {} uint64_t getBufferOffset() const { return Offset; } Error visit(const Section &Sec) final; @@ -298,7 +301,8 @@ // Real IHEX section writer class IHexSectionWriter : public IHexSectionWriterBase { public: - IHexSectionWriter(WritableMemoryBuffer &Buf) : IHexSectionWriterBase(Buf) {} + IHexSectionWriter(WritableMemoryBuffer &Buf, bool MayUseSegmentOffset) + : IHexSectionWriterBase(Buf, MayUseSegmentOffset) {} void writeData(uint8_t Type, uint16_t Addr, ArrayRef Data) override; Error visit(const StringTableSection &Sec) override; @@ -376,6 +380,8 @@ std::set Sections; size_t TotalSize = 0; + bool MayUseSegmentOffset; + Error checkSection(const SectionBase &Sec); uint64_t writeEntryPointRecord(uint8_t *Buf); uint64_t writeEndOfFileRecord(uint8_t *Buf); @@ -384,7 +390,8 @@ ~IHexWriter() {} Error finalize() override; Error write() override; - IHexWriter(Object &Obj, raw_ostream &Out) : Writer(Obj, Out) {} + IHexWriter(Object &Obj, raw_ostream &Out, bool MayUseSegmentOffset) + : Writer(Obj, Out), MayUseSegmentOffset(MayUseSegmentOffset) {} }; class SectionBase { diff --git a/llvm/lib/ObjCopy/ELF/ELFObject.cpp b/llvm/lib/ObjCopy/ELF/ELFObject.cpp --- a/llvm/lib/ObjCopy/ELF/ELFObject.cpp +++ b/llvm/lib/ObjCopy/ELF/ELFObject.cpp @@ -340,7 +340,7 @@ while (!Data.empty()) { uint64_t DataSize = std::min(Data.size(), ChunkSize); if (Addr > SegmentAddr + BaseAddr + 0xFFFFU) { - if (Addr > 0xFFFFFU) { + if (Addr > 0xFFFFFU || !MayUseSegmentOffset) { // Write extended address record, zeroing segment address // if needed. if (SegmentAddr != 0) @@ -2670,12 +2670,19 @@ if (Obj.Entry == 0) return 0; - if (Obj.Entry <= 0xFFFFFU) { + if (MayUseSegmentOffset && Obj.Entry <= 0xFFFFFU) { + // Write x86-16 style segment:offset, with the division between the two + // chosen arbitrarily so that the low 16 bits all go in the offset, e.g. + // 0xABCDE -> 0xA000:0xBCDE. Data[0] = ((Obj.Entry & 0xF0000U) >> 12) & 0xFF; support::endian::write(&Data[2], static_cast(Obj.Entry), support::big); HexData = IHexRecord::getLine(IHexRecord::StartAddr80x86, 0, Data); } else { + // Write a 32-bit start address record assuming a flat address space, + // either because the address doesn't fit in 20 bits, or because we're in + // IHexFlat mode where the user doesn't want any segment:offset + // representations of anything anyway. support::endian::write(Data, static_cast(Obj.Entry), support::big); HexData = IHexRecord::getLine(IHexRecord::StartAddr, 0, Data); @@ -2691,7 +2698,7 @@ } Error IHexWriter::write() { - IHexSectionWriter Writer(*Buf); + IHexSectionWriter Writer(*Buf, MayUseSegmentOffset); // Write sections. for (const SectionBase *Sec : Sections) if (Error Err = Sec->accept(Writer)) @@ -2743,7 +2750,7 @@ return createStringError(errc::not_enough_memory, "failed to allocate memory buffer of 0 bytes"); - IHexSectionWriterBase LengthCalc(*EmptyBuffer); + IHexSectionWriterBase LengthCalc(*EmptyBuffer, MayUseSegmentOffset); for (const SectionBase *Sec : Sections) if (Error Err = Sec->accept(LengthCalc)) return Err; diff --git a/llvm/test/tools/llvm-objcopy/ELF/ihex-writer.test b/llvm/test/tools/llvm-objcopy/ELF/ihex-writer.test --- a/llvm/test/tools/llvm-objcopy/ELF/ihex-writer.test +++ b/llvm/test/tools/llvm-objcopy/ELF/ihex-writer.test @@ -1,5 +1,6 @@ # RUN: yaml2obj %p/Inputs/ihex-elf-sections.yaml -o %t -# RUN: llvm-objcopy -O ihex %t - | FileCheck %s +# RUN: llvm-objcopy -O ihex %t - | FileCheck %s --check-prefixes=CHECK,80X86 +# RUN: llvm-objcopy -O ihex-flat %t - | FileCheck %s --check-prefixes=CHECK,FLAT # Check ihex output, when we have segments in ELF file # In such case only sections in PT_LOAD segments will @@ -24,16 +25,19 @@ # Check that zero length section is not written # RUN: llvm-objcopy -O ihex --only-section=.text %t-sec2 - | FileCheck %s --check-prefix=ZERO_SIZE_SEC -# Check 80x86 start address record. It is created for start -# addresses less than 0x100000 -# RUN: llvm-objcopy -O ihex --set-start=0xFFFF %t - | FileCheck %s --check-prefix=START1 +# Check start address records in ihex mode. 80x86 style segment:offset +# is used for start addresses less than 0x100000, and otherwise the +# i386 flat style is used. +# RUN: llvm-objcopy -O ihex --set-start=0xABCDE %t - | FileCheck %s --check-prefix=START-SEG-OFF +# RUN: llvm-objcopy -O ihex --set-start=0x100000 %t - | FileCheck %s --check-prefix=START-HIGH -# Check i386 start address record (05). It is created for -# start addresses which doesn't fit 20 bits -# RUN: llvm-objcopy -O ihex --set-start=0x100000 %t - | FileCheck %s --check-prefix=START2 +# Check start address records in ihex-flat mode, which should use i386 +# style unconditionally. +# RUN: llvm-objcopy -O ihex-flat --set-start=0xABCDE %t - | FileCheck %s --check-prefix=START-FLAT +# RUN: llvm-objcopy -O ihex-flat --set-start=0x100000 %t - | FileCheck %s --check-prefix=START-HIGH # We allow sign extended 32 bit start addresses as well. -# RUN: llvm-objcopy -O ihex --set-start=0xFFFFFFFF80001000 %t - | FileCheck %s --check-prefix=START3 +# RUN: llvm-objcopy -O ihex --set-start=0xFFFFFFFF80001000 %t - | FileCheck %s --check-prefix=START-SIGN-EXT # Start address which exceeds 32 bit range triggers an error # RUN: not llvm-objcopy -O ihex --set-start=0xF00000000 %t %t6.hex 2>&1 | FileCheck %s --check-prefix=BAD-START @@ -41,10 +45,11 @@ # CHECK: :10000000000102030405060708090A0B0C0D0E0F78 # CHECK-NEXT: :05001000101112131491 # CHECK-NEXT: :08FFF800303132333435363765 -# CHECK-NEXT: :020000021000EC +# 80X86-NEXT: :020000021000EC +# FLAT-NEXT: :020000040001F9 # CHECK-NEXT: :030000003839404C # CHECK-NEXT: :0401000040414243F5 -# CHECK-NEXT: :020000020000FC +# 80X86-NEXT: :020000020000FC # CHECK-NEXT: :020000040010EA # CHECK-NEXT: :08FFF800505152535455565765 # CHECK-NEXT: :020000040011E9 @@ -78,7 +83,8 @@ # ZERO_SIZE_SEC-NOT: :00FFFF00 # ZERO_SIZE_SEC: :00000001FF -# START1: :040000030000FFFFFB -# START2: :0400000500100000E7 -# START3: :040000058000100067 +# START-SEG-OFF: :04000003A000BCDEBF +# START-FLAT: :04000005000ABCDE53 +# START-HIGH: :0400000500100000E7 +# START-SIGN-EXT: :040000058000100067 # BAD-START: error: {{.*}}: Entry point address 0x{{.*}} overflows 32 bits diff --git a/llvm/tools/llvm-objcopy/ObjcopyOptions.cpp b/llvm/tools/llvm-objcopy/ObjcopyOptions.cpp --- a/llvm/tools/llvm-objcopy/ObjcopyOptions.cpp +++ b/llvm/tools/llvm-objcopy/ObjcopyOptions.cpp @@ -704,6 +704,7 @@ Config.OutputFormat = StringSwitch(OutputFormat) .Case("binary", FileFormat::Binary) .Case("ihex", FileFormat::IHex) + .Case("ihex-flat", FileFormat::IHexFlat) .Default(FileFormat::Unspecified); if (Config.OutputFormat == FileFormat::Unspecified) { if (OutputFormat.empty()) { diff --git a/llvm/tools/llvm-objcopy/llvm-objcopy.cpp b/llvm/tools/llvm-objcopy/llvm-objcopy.cpp --- a/llvm/tools/llvm-objcopy/llvm-objcopy.cpp +++ b/llvm/tools/llvm-objcopy/llvm-objcopy.cpp @@ -121,6 +121,7 @@ // GNU objcopy. See https://bugs.llvm.org/show_bug.cgi?id=42171 for details. case FileFormat::Binary: case FileFormat::IHex: + case FileFormat::IHexFlat: case FileFormat::Unspecified: Expected ELFConfig = ConfigMgr.getELFConfig(); if (!ELFConfig)