diff --git a/llvm/test/tools/llvm-objcopy/MachO/binary-output.test b/llvm/test/tools/llvm-objcopy/MachO/binary-output.test
new file mode 100644
--- /dev/null
+++ b/llvm/test/tools/llvm-objcopy/MachO/binary-output.test
@@ -0,0 +1,64 @@
+## Show that llvm-objcopy extracts section contents in MachO properly, that is:
+##
+##   - Remove gap before the first section: __bar section content is at the
+##     beginning of the output.
+##   - Preserve gaps between sections: 12 bytes padding (zeroes) before __foo
+##     section content.
+##
+# RUN: yaml2obj %s > %t
+# RUN: llvm-objcopy -Obinary %t %t.bin
+# RUN: od -t x1 %t.bin | FileCheck %s
+
+# CHECK:      0000000    aa  bb  cc  dd  00  00  00  00  00  00  00  00  00  00  00  00
+# CHECK-NEXT: 0000020    ee  ff
+# CHECK-NEXT: 0000022{{$}}
+
+--- !mach-o
+FileHeader:
+  magic:           0xFEEDFACF
+  cputype:         0x01000007
+  cpusubtype:      0x80000003
+  filetype:        0x00000002
+  ncmds:           1
+  sizeofcmds:      232
+  flags:           0x00002000
+  reserved:        0x00000000
+LoadCommands:
+  - cmd:             LC_SEGMENT_64
+    cmdsize:         232
+    segname:         '__DATA'
+    vmaddr:          0x0000000000001000
+    vmsize:          32
+    fileoff:         472
+    filesize:        6
+    maxprot:         7
+    initprot:        7
+    nsects:          2
+    flags:           0
+    Sections:
+      - sectname:        __bar
+        segname:         __DATA
+        addr:            0x0000000000001000
+        content:         'AABBCCDD'
+        size:            4
+        offset:          0x0000010c
+        align:           0
+        reloff:          0x00000000
+        nreloc:          0
+        flags:           0x00000000
+        reserved1:       0x00000000
+        reserved2:       0x00000000
+        reserved3:       0x00000000
+      - sectname:        __foo
+        segname:         __DATA
+        addr:            0x0000000000001010
+        size:            2
+        content:         'EEFF'
+        offset:          0x00000110
+        align:           0
+        reloff:          0x00000000
+        nreloc:          0
+        flags:           0x00000000
+        reserved1:       0x00000000
+        reserved2:       0x00000000
+        reserved3:       0x00000000
diff --git a/llvm/tools/llvm-objcopy/MachO/MachOObjcopy.cpp b/llvm/tools/llvm-objcopy/MachO/MachOObjcopy.cpp
--- a/llvm/tools/llvm-objcopy/MachO/MachOObjcopy.cpp
+++ b/llvm/tools/llvm-objcopy/MachO/MachOObjcopy.cpp
@@ -198,15 +198,30 @@
   return Error::success();
 }
 
-Error writeOutput(Object &O, Buffer &Out, bool Is64Bit, bool IsLittleEndian) {
+static std::unique_ptr<Writer> createWriter(const CopyConfig &Config, Object &O,
+                                            Buffer &Out, bool Is64Bit,
+                                            bool IsLittleEndian) {
   // TODO: Support 16KB pages which are employed in iOS arm64 binaries:
   //       https://github.com/llvm/llvm-project/commit/1bebb2832ee312d3b0316dacff457a7a29435edb
   const uint64_t PageSize = 4096;
 
-  MachOWriter Writer(O, Is64Bit, IsLittleEndian, PageSize, Out);
-  if (auto E = Writer.finalize())
+  switch (Config.OutputFormat) {
+  case FileFormat::Binary:
+    return std::make_unique<BinaryWriter>(
+        BinaryWriter(O, Is64Bit, IsLittleEndian, PageSize, Out));
+  default:
+    return std::make_unique<MachOWriter>(
+        MachOWriter(O, Is64Bit, IsLittleEndian, PageSize, Out));
+  }
+}
+
+Error writeOutput(const CopyConfig &Config, Object &O, Buffer &Out,
+                  bool Is64Bit, bool IsLittleEndian) {
+  std::unique_ptr<Writer> W =
+      createWriter(Config, O, Out, Is64Bit, IsLittleEndian);
+  if (auto E = W->finalize())
     return E;
-  return Writer.write();
+  return W->write();
 }
 
 Error executeObjcopyOnRawBinary(const CopyConfig &Config, MemoryBuffer &In,
@@ -218,7 +233,7 @@
   if (Error E = handleArgs(Config, *O))
     return createFileError(Config.InputFilename, std::move(E));
 
-  return writeOutput(*O, Out, MI.Is64Bit, MI.IsLittleEndian);
+  return writeOutput(Config, *O, Out, MI.Is64Bit, MI.IsLittleEndian);
 }
 
 Error executeObjcopyOnBinary(const CopyConfig &Config,
@@ -234,7 +249,7 @@
   if (Error E = handleArgs(Config, *O))
     return createFileError(Config.InputFilename, std::move(E));
 
-  return writeOutput(*O, Out, In.is64Bit(), In.isLittleEndian());
+  return writeOutput(Config, *O, Out, In.is64Bit(), In.isLittleEndian());
 }
 
 } // end namespace macho
diff --git a/llvm/tools/llvm-objcopy/MachO/MachOWriter.h b/llvm/tools/llvm-objcopy/MachO/MachOWriter.h
--- a/llvm/tools/llvm-objcopy/MachO/MachOWriter.h
+++ b/llvm/tools/llvm-objcopy/MachO/MachOWriter.h
@@ -19,7 +19,14 @@
 namespace objcopy {
 namespace macho {
 
-class MachOWriter {
+class Writer {
+public:
+  virtual ~Writer() {}
+  virtual Error finalize() = 0;
+  virtual Error write() = 0;
+};
+
+class MachOWriter : public Writer {
   Object &O;
   bool Is64Bit;
   bool IsLittleEndian;
@@ -54,10 +61,25 @@
               Buffer &B)
       : O(O), Is64Bit(Is64Bit), IsLittleEndian(IsLittleEndian),
         PageSize(PageSize), B(B), LayoutBuilder(O, Is64Bit, PageSize) {}
-
+  ~MachOWriter() {}
   size_t totalSize() const;
-  Error finalize();
-  Error write();
+  Error finalize() override;
+  Error write() override;
+};
+
+class BinaryWriter : public Writer {
+  Object &O;
+  Buffer &B;
+  MachOLayoutBuilder LayoutBuilder;
+  std::vector<Section *> OrderedSections;
+
+public:
+  BinaryWriter(Object &O, bool Is64Bit, bool IsLittleEndian, uint64_t PageSize,
+               Buffer &B)
+      : O(O), B(B), LayoutBuilder(O, Is64Bit, PageSize) {}
+  ~BinaryWriter() {}
+  Error finalize() override;
+  Error write() override;
 };
 
 } // end namespace macho
diff --git a/llvm/tools/llvm-objcopy/MachO/MachOWriter.cpp b/llvm/tools/llvm-objcopy/MachO/MachOWriter.cpp
--- a/llvm/tools/llvm-objcopy/MachO/MachOWriter.cpp
+++ b/llvm/tools/llvm-objcopy/MachO/MachOWriter.cpp
@@ -495,6 +495,47 @@
   return B.commit();
 }
 
+Error BinaryWriter::finalize() {
+  if (Error E = LayoutBuilder.layout())
+    return E;
+
+  // Sort sections by their addresses.
+  for (LoadCommand &LC : O.LoadCommands)
+    for (Section &Sec : LC.Sections)
+      if (!Sec.isVirtualSection())
+        OrderedSections.push_back(&Sec);
+
+  llvm::stable_sort(OrderedSections, [](const Section *A, const Section *B) {
+    return A->Addr < B->Addr;
+  });
+
+  // Remove gap at the start. `Addr` is used as the file offset in the
+  // output file.
+  if (!OrderedSections.empty()) {
+    uint64_t AddrBase = OrderedSections[0]->Addr;
+    for (Section *Sec : OrderedSections)
+      Sec->Addr -= AddrBase;
+  }
+
+  return Error::success();
+}
+
+Error BinaryWriter::write() {
+  size_t TotalSize =
+      OrderedSections.empty()
+          ? 0
+          : (OrderedSections.back()->Addr + OrderedSections.back()->Size);
+
+  if (Error E = B.allocate(TotalSize))
+    return E;
+  memset(B.getBufferStart(), 0, TotalSize);
+
+  for (const Section *Sec : OrderedSections)
+    memcpy(B.getBufferStart() + Sec->Addr, Sec->getContent().data(), Sec->Size);
+
+  return B.commit();
+}
+
 } // end namespace macho
 } // end namespace objcopy
 } // end namespace llvm