diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index db236d4..f9790cf 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -20,5 +20,6 @@ jobs:
         autoconf --version
     - run: |
         ./bootstrap.sh
-        ./configure --with-asan --with-ubsan
+        mkdir build && cd build
+        ../configure --with-asan --with-ubsan
         make -j$(nproc) check
diff --git a/.gitignore b/.gitignore
index 3971381..c3f60a5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,3 +32,8 @@ Makefile
 /tests/libbig-dynstr.debug
 /tests/contiguous-note-sections
 /tests/simple-pie
+
+.direnv/
+.vscode/
+.idea/
+result
diff --git a/README.md b/README.md
index 18a72ee..e0a2e3a 100644
--- a/README.md
+++ b/README.md
@@ -70,6 +70,7 @@ libraries.  In particular, it can do the following:
 
 ## Compiling and Testing
 
+### Via Autotools
 ```console
 ./bootstrap.sh
 ./configure
@@ -77,6 +78,13 @@ make
 make check
 sudo make install
 ```
+### Via Nix
+
+You can build with Nix in several ways.
+
+1. Building via `nix build` will produce the result in `./result/bin/patchelf`. If you would like to build _patchelf_ with _musl_ try `nix build .#patchelf-musl`
+
+2. You can launch a development environment with `nix develop` and follow the autotools steps above. If you would like to develop with _musl_ try `nix develop .#musl`
 
 ## Author
 
@@ -99,6 +107,18 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 ## Release History
 
+0.14.5 (February 21, 2022):
+
+* fix faulty version in 0.14.4
+
+0.14.4 (February 21, 2022):
+
+* Several test fixes to fix patchelf test suite on openbsd by @klemensn
+* Allow multiple modifications in same call by @fzakaria in https://github.com/NixOS/patchelf/pull/361
+* Add support to build with musl by @fzakaria in https://github.com/NixOS/patchelf/pull/362
+* Fix typo: s/folllow/follow/ by @bjornfor in https://github.com/NixOS/patchelf/pull/366
+* mips: fix incorrect polarity on dyn_offset; closes #364 by @a-m-joseph in https://github.com/NixOS/patchelf/pull/365
+
 0.14.3 (December 05, 2021):
 
 * this release adds support for static, pre-compiled patchelf binaries
diff --git a/debian/changelog b/debian/changelog
index 6447b44..6fd6b97 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+patchelf (0.14.5-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Mon, 14 Mar 2022 06:31:40 -0000
+
 patchelf (0.14.3-1) unstable; urgency=medium
 
   [ Felipe Sateler ]
diff --git a/flake.nix b/flake.nix
index 045b6d7..8c3988c 100644
--- a/flake.nix
+++ b/flake.nix
@@ -21,6 +21,10 @@
 
     {
       overlay = final: prev: {
+        patchelf-new-musl = final.pkgsMusl.callPackage ./patchelf.nix {
+          inherit version;
+          src = self;
+        };
         patchelf-new = final.callPackage ./patchelf.nix {
           inherit version;
           src = self;
@@ -85,12 +89,23 @@
         build = self.hydraJobs.build.${system};
       });
 
+      devShell = forAllSystems (system: self.devShells.${system}.glibc);
+
+      devShells = forAllSystems (system:
+        {
+          glibc = self.packages.${system}.patchelf;
+          musl = self.packages.${system}.patchelf-musl;
+        });
+
       defaultPackage = forAllSystems (system:
-        (import nixpkgs {
-          inherit system;
-          overlays = [ self.overlay ];
-        }).patchelf-new
+        self.packages.${system}.patchelf
       );
 
+      packages = forAllSystems (system:
+        {
+          patchelf = nixpkgsFor.${system}.patchelf-new;
+          patchelf-musl = nixpkgsFor.${system}.patchelf-new-musl;
+        });
+
     };
 }
diff --git a/src/Makefile.am b/src/Makefile.am
index ed7a19b..fa0a9cc 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -19,4 +19,4 @@ endif
 
 bin_PROGRAMS = patchelf
 
-patchelf_SOURCES = patchelf.cc elf.h
+patchelf_SOURCES = patchelf.cc elf.h patchelf.h
diff --git a/src/patchelf.cc b/src/patchelf.cc
index 1aeae88..b2552f7 100644
--- a/src/patchelf.cc
+++ b/src/patchelf.cc
@@ -41,6 +41,7 @@
 #include <unistd.h>
 
 #include "elf.h"
+#include "patchelf.h"
 
 #ifndef PACKAGE_STRING
 #define PACKAGE_STRING "patchelf"
@@ -63,11 +64,6 @@ static int forcedPageSize = -1;
 #define EM_LOONGARCH    258
 #endif
 
-using FileContents = std::shared_ptr<std::vector<unsigned char>>;
-
-#define ElfFileParams class Elf_Ehdr, class Elf_Phdr, class Elf_Shdr, class Elf_Addr, class Elf_Off, class Elf_Dyn, class Elf_Sym, class Elf_Verneed, class Elf_Versym
-#define ElfFileParamNames Elf_Ehdr, Elf_Phdr, Elf_Shdr, Elf_Addr, Elf_Off, Elf_Dyn, Elf_Sym, Elf_Verneed, Elf_Versym
-
 
 static std::vector<std::string> splitColonDelimitedString(const char * s)
 {
@@ -85,163 +81,6 @@ static bool hasAllowedPrefix(const std::string & s, const std::vector<std::strin
     return std::any_of(allowedPrefixes.begin(), allowedPrefixes.end(), [&](const std::string & i) { return !s.compare(0, i.size(), i); });
 }
 
-
-template<ElfFileParams>
-class ElfFile
-{
-public:
-
-    const FileContents fileContents;
-
-private:
-
-    std::vector<Elf_Phdr> phdrs;
-    std::vector<Elf_Shdr> shdrs;
-
-    bool littleEndian;
-
-    bool changed = false;
-
-    bool isExecutable = false;
-
-    using SectionName = std::string;
-    using ReplacedSections = std::map<SectionName, std::string>;
-
-    ReplacedSections replacedSections;
-
-    std::string sectionNames; /* content of the .shstrtab section */
-
-    /* Align on 4 or 8 bytes boundaries on 32- or 64-bit platforms
-       respectively. */
-    size_t sectionAlignment = sizeof(Elf_Off);
-
-    std::vector<SectionName> sectionsByOldIndex;
-
-public:
-    explicit ElfFile(FileContents fileContents);
-
-    bool isChanged()
-    {
-        return changed;
-    }
-
-private:
-
-    struct CompPhdr
-    {
-        ElfFile * elfFile;
-        bool operator ()(const Elf_Phdr & x, const Elf_Phdr & y)
-        {
-            // A PHDR comes before everything else.
-            if (elfFile->rdi(y.p_type) == PT_PHDR) return false;
-            if (elfFile->rdi(x.p_type) == PT_PHDR) return true;
-
-            // Sort non-PHDRs by address.
-            return elfFile->rdi(x.p_paddr) < elfFile->rdi(y.p_paddr);
-        }
-    };
-
-    friend struct CompPhdr;
-
-    void sortPhdrs();
-
-    struct CompShdr
-    {
-        ElfFile * elfFile;
-        bool operator ()(const Elf_Shdr & x, const Elf_Shdr & y)
-        {
-            return elfFile->rdi(x.sh_offset) < elfFile->rdi(y.sh_offset);
-        }
-    };
-
-    friend struct CompShdr;
-
-    unsigned int getPageSize() const;
-
-    void sortShdrs();
-
-    void shiftFile(unsigned int extraPages, Elf_Addr startPage);
-
-    std::string getSectionName(const Elf_Shdr & shdr) const;
-
-    Elf_Shdr & findSection(const SectionName & sectionName);
-
-    std::optional<std::reference_wrapper<Elf_Shdr>> findSection2(const SectionName & sectionName);
-
-    unsigned int findSection3(const SectionName & sectionName);
-
-    std::string & replaceSection(const SectionName & sectionName,
-        unsigned int size);
-
-    bool haveReplacedSection(const SectionName & sectionName) const;
-
-    void writeReplacedSections(Elf_Off & curOff,
-        Elf_Addr startAddr, Elf_Off startOffset);
-
-    void rewriteHeaders(Elf_Addr phdrAddress);
-
-    void rewriteSectionsLibrary();
-
-    void rewriteSectionsExecutable();
-
-    void normalizeNoteSegments();
-
-public:
-
-    void rewriteSections();
-
-    std::string getInterpreter();
-
-    typedef enum { printSoname, replaceSoname } sonameMode;
-
-    void modifySoname(sonameMode op, const std::string & newSoname);
-
-    void setInterpreter(const std::string & newInterpreter);
-
-    typedef enum { rpPrint, rpShrink, rpSet, rpAdd, rpRemove } RPathOp;
-
-    void modifyRPath(RPathOp op, const std::vector<std::string> & allowedRpathPrefixes, std::string newRPath);
-    std::string shrinkRPath(char* rpath, std::vector<std::string> &neededLibs, const std::vector<std::string> & allowedRpathPrefixes);
-    void removeRPath(Elf_Shdr & shdrDynamic);
-
-    void addNeeded(const std::set<std::string> & libs);
-
-    void removeNeeded(const std::set<std::string> & libs);
-
-    void replaceNeeded(const std::map<std::string, std::string> & libs);
-
-    void printNeededLibs() /* should be const */;
-
-    void noDefaultLib();
-
-    void clearSymbolVersions(const std::set<std::string> & syms);
-
-private:
-
-    /* Convert an integer in big or little endian representation (as
-       specified by the ELF header) to this platform's integer
-       representation. */
-    template<class I>
-    I rdi(I i) const;
-
-    /* Convert back to the ELF representation. */
-    template<class I>
-    I wri(I & t, unsigned long long i) const
-    {
-        t = rdi((I) i);
-        return i;
-    }
-
-    Elf_Ehdr *hdr() {
-      return (Elf_Ehdr *)fileContents->data();
-    }
-
-    const Elf_Ehdr *hdr() const {
-      return (const Elf_Ehdr *)fileContents->data();
-    }
-};
-
-
 /* !!! G++ creates broken code if this function is inlined, don't know
    why... */
 template<ElfFileParams>
@@ -513,14 +352,14 @@ void ElfFile<ElfFileParamNames>::sortShdrs()
     for (unsigned int i = 1; i < rdi(hdr()->e_shnum); ++i)
         if (rdi(shdrs[i].sh_link) != 0)
             wri(shdrs[i].sh_link,
-                findSection3(linkage[getSectionName(shdrs[i])]));
+                getSectionIndex(linkage[getSectionName(shdrs[i])]));
 
     /* And the st_info mappings. */
     for (unsigned int i = 1; i < rdi(hdr()->e_shnum); ++i)
         if (rdi(shdrs.at(i).sh_info) != 0 &&
             (rdi(shdrs.at(i).sh_type) == SHT_REL || rdi(shdrs.at(i).sh_type) == SHT_RELA))
             wri(shdrs.at(i).sh_info,
-                findSection3(info.at(getSectionName(shdrs.at(i)))));
+                getSectionIndex(info.at(getSectionName(shdrs.at(i)))));
 
     /* And the .shstrtab index. Note: the match here is done by checking the offset as searching
      * by name can yield incorrect results in case there are multiple sections with the same
@@ -630,9 +469,9 @@ std::string ElfFile<ElfFileParamNames>::getSectionName(const Elf_Shdr & shdr) co
 
 
 template<ElfFileParams>
-Elf_Shdr & ElfFile<ElfFileParamNames>::findSection(const SectionName & sectionName)
+Elf_Shdr & ElfFile<ElfFileParamNames>::findSectionHeader(const SectionName & sectionName)
 {
-    auto shdr = findSection2(sectionName);
+    auto shdr = tryFindSectionHeader(sectionName);
     if (!shdr) {
         std::string extraMsg;
         if (sectionName == ".interp" || sectionName == ".dynamic" || sectionName == ".dynstr")
@@ -644,9 +483,9 @@ Elf_Shdr & ElfFile<ElfFileParamNames>::findSection(const SectionName & sectionNa
 
 
 template<ElfFileParams>
-std::optional<std::reference_wrapper<Elf_Shdr>> ElfFile<ElfFileParamNames>::findSection2(const SectionName & sectionName)
+std::optional<std::reference_wrapper<Elf_Shdr>> ElfFile<ElfFileParamNames>::tryFindSectionHeader(const SectionName & sectionName)
 {
-    auto i = findSection3(sectionName);
+    auto i = getSectionIndex(sectionName);
     if (i)
         return shdrs.at(i);
     return {};
@@ -654,7 +493,7 @@ std::optional<std::reference_wrapper<Elf_Shdr>> ElfFile<ElfFileParamNames>::find
 
 
 template<ElfFileParams>
-unsigned int ElfFile<ElfFileParamNames>::findSection3(const SectionName & sectionName)
+unsigned int ElfFile<ElfFileParamNames>::getSectionIndex(const SectionName & sectionName)
 {
     for (unsigned int i = 1; i < rdi(hdr()->e_shnum); ++i)
         if (getSectionName(shdrs.at(i)) == sectionName) return i;
@@ -677,7 +516,7 @@ std::string & ElfFile<ElfFileParamNames>::replaceSection(const SectionName & sec
     if (i != replacedSections.end()) {
         s = std::string(i->second);
     } else {
-        auto shdr = findSection(sectionName);
+        auto shdr = findSectionHeader(sectionName);
         s = std::string((char *) fileContents->data() + rdi(shdr.sh_offset), rdi(shdr.sh_size));
     }
 
@@ -697,7 +536,7 @@ void ElfFile<ElfFileParamNames>::writeReplacedSections(Elf_Off & curOff,
        clobbering previously written new section contents. */
     for (auto & i : replacedSections) {
         const std::string & sectionName = i.first;
-        Elf_Shdr & shdr = findSection(sectionName);
+        Elf_Shdr & shdr = findSectionHeader(sectionName);
         if (rdi(shdr.sh_type) != SHT_NOBITS)
             memset(fileContents->data() + rdi(shdr.sh_offset), 'X', rdi(shdr.sh_size));
     }
@@ -705,7 +544,7 @@ void ElfFile<ElfFileParamNames>::writeReplacedSections(Elf_Off & curOff,
     std::set<unsigned int> noted_phdrs = {};
     for (auto & i : replacedSections) {
         const std::string & sectionName = i.first;
-        auto & shdr = findSection(sectionName);
+        auto & shdr = findSectionHeader(sectionName);
         Elf_Shdr orig_shdr = shdr;
         debug("rewriting section '%s' from offset 0x%x (size %d) to offset 0x%x (size %d)\n",
             sectionName.c_str(), rdi(shdr.sh_offset), rdi(shdr.sh_size), curOff, i.second.size());
@@ -1024,7 +863,7 @@ void ElfFile<ElfFileParamNames>::normalizeNoteSegments()
 
     /* We don't need to do anything if no note segments were replaced. */
     bool replaced_note = std::any_of(replacedSections.begin(), replacedSections.end(),
-        [this](std::pair<const std::string, std::string> & i) { return rdi(findSection(i.first).sh_type) == SHT_NOTE; });
+        [this](std::pair<const std::string, std::string> & i) { return rdi(findSectionHeader(i.first).sh_type) == SHT_NOTE; });
     if (!replaced_note) return;
 
     std::vector<Elf_Phdr> newPhdrs;
@@ -1128,61 +967,61 @@ void ElfFile<ElfFileParamNames>::rewriteHeaders(Elf_Addr phdrAddress)
     /* Update all those nasty virtual addresses in the .dynamic
        section.  Note that not all executables have .dynamic sections
        (e.g., those produced by klibc's klcc). */
-    auto shdrDynamic = findSection2(".dynamic");
+    auto shdrDynamic = tryFindSectionHeader(".dynamic");
     if (shdrDynamic) {
         auto dyn_table = (Elf_Dyn *) (fileContents->data() + rdi((*shdrDynamic).get().sh_offset));
         unsigned int d_tag;
         for (auto dyn = dyn_table; (d_tag = rdi(dyn->d_tag)) != DT_NULL; dyn++)
             if (d_tag == DT_STRTAB)
-                dyn->d_un.d_ptr = findSection(".dynstr").sh_addr;
+                dyn->d_un.d_ptr = findSectionHeader(".dynstr").sh_addr;
             else if (d_tag == DT_STRSZ)
-                dyn->d_un.d_val = findSection(".dynstr").sh_size;
+                dyn->d_un.d_val = findSectionHeader(".dynstr").sh_size;
             else if (d_tag == DT_SYMTAB)
-                dyn->d_un.d_ptr = findSection(".dynsym").sh_addr;
+                dyn->d_un.d_ptr = findSectionHeader(".dynsym").sh_addr;
             else if (d_tag == DT_HASH)
-                dyn->d_un.d_ptr = findSection(".hash").sh_addr;
+                dyn->d_un.d_ptr = findSectionHeader(".hash").sh_addr;
             else if (d_tag == DT_GNU_HASH) {
-                auto shdr = findSection2(".gnu.hash");
+                auto shdr = tryFindSectionHeader(".gnu.hash");
                 // some binaries might this section stripped
                 // in which case we just ignore the value.
                 if (shdr) dyn->d_un.d_ptr = (*shdr).get().sh_addr;
             } else if (d_tag == DT_JMPREL) {
-                auto shdr = findSection2(".rel.plt");
-                if (!shdr) shdr = findSection2(".rela.plt");
+                auto shdr = tryFindSectionHeader(".rel.plt");
+                if (!shdr) shdr = tryFindSectionHeader(".rela.plt");
                 /* 64-bit Linux, x86-64 */
-                if (!shdr) shdr = findSection2(".rela.IA_64.pltoff"); /* 64-bit Linux, IA-64 */
+                if (!shdr) shdr = tryFindSectionHeader(".rela.IA_64.pltoff"); /* 64-bit Linux, IA-64 */
                 if (!shdr) error("cannot find section corresponding to DT_JMPREL");
                 dyn->d_un.d_ptr = (*shdr).get().sh_addr;
             }
             else if (d_tag == DT_REL) { /* !!! hack! */
-                auto shdr = findSection2(".rel.dyn");
+                auto shdr = tryFindSectionHeader(".rel.dyn");
                 /* no idea if this makes sense, but it was needed for some
                    program */
-                if (!shdr) shdr = findSection2(".rel.got");
+                if (!shdr) shdr = tryFindSectionHeader(".rel.got");
                 /* some programs have neither section, but this doesn't seem
                    to be a problem */
                 if (!shdr) continue;
                 dyn->d_un.d_ptr = (*shdr).get().sh_addr;
             }
             else if (d_tag == DT_RELA) {
-                auto shdr = findSection2(".rela.dyn");
+                auto shdr = tryFindSectionHeader(".rela.dyn");
                 /* some programs lack this section, but it doesn't seem to
                    be a problem */
                 if (!shdr) continue;
                 dyn->d_un.d_ptr = (*shdr).get().sh_addr;
             }
             else if (d_tag == DT_VERNEED)
-                dyn->d_un.d_ptr = findSection(".gnu.version_r").sh_addr;
+                dyn->d_un.d_ptr = findSectionHeader(".gnu.version_r").sh_addr;
             else if (d_tag == DT_VERSYM)
-                dyn->d_un.d_ptr = findSection(".gnu.version").sh_addr;
+                dyn->d_un.d_ptr = findSectionHeader(".gnu.version").sh_addr;
             else if (d_tag == DT_MIPS_RLD_MAP_REL) {
                 /* the MIPS_RLD_MAP_REL tag stores the offset to the debug
                    pointer, relative to the address of the tag */
-                auto shdr = findSection2(".rld_map");
+                auto shdr = tryFindSectionHeader(".rld_map");
                 if (shdr) {
-                    auto rld_map_addr = findSection(".rld_map").sh_addr;
+                    auto rld_map_addr = findSectionHeader(".rld_map").sh_addr;
                     auto dyn_offset = ((char*)dyn) - ((char*)dyn_table);
-                    dyn->d_un.d_ptr = rld_map_addr + dyn_offset - (*shdrDynamic).get().sh_addr;
+                    dyn->d_un.d_ptr = rld_map_addr - dyn_offset - (*shdrDynamic).get().sh_addr;
                 } else {
                     /* ELF file with DT_MIPS_RLD_MAP_REL but without .rld_map
                        is broken, and it's not our job to fix it; yet, we have
@@ -1212,7 +1051,7 @@ void ElfFile<ElfFileParamNames>::rewriteHeaders(Elf_Addr phdrAddress)
                 }
                 const std::string & section = sectionsByOldIndex.at(shndx);
                 assert(!section.empty());
-                auto newIndex = findSection3(section); // inefficient
+                auto newIndex = getSectionIndex(section); // inefficient
                 //debug("rewriting symbol %d: index = %d (%s) -> %d\n", entry, shndx, section.c_str(), newIndex);
                 wri(sym->st_shndx, newIndex);
                 /* Rewrite st_value.  FIXME: we should do this for all
@@ -1236,8 +1075,8 @@ static void setSubstr(std::string & s, unsigned int pos, const std::string & t)
 template<ElfFileParams>
 std::string ElfFile<ElfFileParamNames>::getInterpreter()
 {
-    auto shdr = findSection(".interp");
-    return std::string((char *) fileContents->data() + rdi(shdr.sh_offset), rdi(shdr.sh_size));
+    auto shdr = findSectionHeader(".interp");
+    return std::string((char *) fileContents->data() + rdi(shdr.sh_offset), rdi(shdr.sh_size) - 1);
 }
 
 template<ElfFileParams>
@@ -1248,8 +1087,8 @@ void ElfFile<ElfFileParamNames>::modifySoname(sonameMode op, const std::string &
         return;
     }
 
-    auto shdrDynamic = findSection(".dynamic");
-    auto shdrDynStr = findSection(".dynstr");
+    auto shdrDynamic = findSectionHeader(".dynamic");
+    auto shdrDynStr = findSectionHeader(".dynstr");
     char * strTab = (char *) fileContents->data() + rdi(shdrDynStr.sh_offset);
 
     /* Walk through the dynamic section, look for the DT_SONAME entry. */
@@ -1311,6 +1150,7 @@ void ElfFile<ElfFileParamNames>::modifySoname(sonameMode op, const std::string &
     }
 
     changed = true;
+    this->rewriteSections();
 }
 
 template<ElfFileParams>
@@ -1319,6 +1159,7 @@ void ElfFile<ElfFileParamNames>::setInterpreter(const std::string & newInterpret
     std::string & section = replaceSection(".interp", newInterpreter.size() + 1);
     setSubstr(section, 0, newInterpreter + '\0');
     changed = true;
+    this->rewriteSections();
 }
 
 
@@ -1395,13 +1236,14 @@ void ElfFile<ElfFileParamNames>::removeRPath(Elf_Shdr & shdrDynamic) {
         }
     }
     memset(last, 0, sizeof(Elf_Dyn) * (dyn - last));
+    this->rewriteSections();
 }
 
 template<ElfFileParams>
 void ElfFile<ElfFileParamNames>::modifyRPath(RPathOp op,
     const std::vector<std::string> & allowedRpathPrefixes, std::string newRPath)
 {
-    auto shdrDynamic = findSection(".dynamic");
+    auto shdrDynamic = findSectionHeader(".dynamic");
 
     if (rdi(shdrDynamic.sh_type) == SHT_NOBITS) {
             debug("no dynamic section\n");
@@ -1410,7 +1252,7 @@ void ElfFile<ElfFileParamNames>::modifyRPath(RPathOp op,
 
     /* !!! We assume that the virtual address in the DT_STRTAB entry
        of the dynamic section corresponds to the .dynstr section. */
-    auto shdrDynStr = findSection(".dynstr");
+    auto shdrDynStr = findSectionHeader(".dynstr");
     char * strTab = (char *) fileContents->data() + rdi(shdrDynStr.sh_offset);
 
 
@@ -1541,6 +1383,7 @@ void ElfFile<ElfFileParamNames>::modifyRPath(RPathOp op,
         newDyn.d_un.d_val = shdrDynStr.sh_size;
         setSubstr(newDynamic, 0, std::string((char *) &newDyn, sizeof(Elf_Dyn)));
     }
+    this->rewriteSections();
 }
 
 
@@ -1549,8 +1392,8 @@ void ElfFile<ElfFileParamNames>::removeNeeded(const std::set<std::string> & libs
 {
     if (libs.empty()) return;
 
-    auto shdrDynamic = findSection(".dynamic");
-    auto shdrDynStr = findSection(".dynstr");
+    auto shdrDynamic = findSectionHeader(".dynamic");
+    auto shdrDynStr = findSectionHeader(".dynstr");
     char * strTab = (char *) fileContents->data() + rdi(shdrDynStr.sh_offset);
 
     auto dyn = (Elf_Dyn *)(fileContents->data() + rdi(shdrDynamic.sh_offset));
@@ -1570,6 +1413,8 @@ void ElfFile<ElfFileParamNames>::removeNeeded(const std::set<std::string> & libs
     }
 
     memset(last, 0, sizeof(Elf_Dyn) * (dyn - last));
+
+    this->rewriteSections();
 }
 
 template<ElfFileParams>
@@ -1577,8 +1422,8 @@ void ElfFile<ElfFileParamNames>::replaceNeeded(const std::map<std::string, std::
 {
     if (libs.empty()) return;
 
-    auto shdrDynamic = findSection(".dynamic");
-    auto shdrDynStr = findSection(".dynstr");
+    auto shdrDynamic = findSectionHeader(".dynamic");
+    auto shdrDynStr = findSectionHeader(".dynstr");
     char * strTab = (char *) fileContents->data() + rdi(shdrDynStr.sh_offset);
 
     auto dyn = (Elf_Dyn *)(fileContents->data() + rdi(shdrDynamic.sh_offset));
@@ -1634,7 +1479,7 @@ void ElfFile<ElfFileParamNames>::replaceNeeded(const std::map<std::string, std::
     // be replaced.
 
     if (verNeedNum) {
-        auto shdrVersionR = findSection(".gnu.version_r");
+        auto shdrVersionR = findSectionHeader(".gnu.version_r");
         // The filename strings in the .gnu.version_r are different from the
         // ones in .dynamic: instead of being in .dynstr, they're in some
         // arbitrary section and we have to look in ->sh_link to figure out
@@ -1693,6 +1538,8 @@ void ElfFile<ElfFileParamNames>::replaceNeeded(const std::map<std::string, std::
             --verNeedNum;
         }
     }
+
+    this->rewriteSections();
 }
 
 template<ElfFileParams>
@@ -1700,8 +1547,8 @@ void ElfFile<ElfFileParamNames>::addNeeded(const std::set<std::string> & libs)
 {
     if (libs.empty()) return;
 
-    auto shdrDynamic = findSection(".dynamic");
-    auto shdrDynStr = findSection(".dynstr");
+    auto shdrDynamic = findSectionHeader(".dynamic");
+    auto shdrDynStr = findSectionHeader(".dynstr");
 
     unsigned int length = 0;
 
@@ -1743,13 +1590,15 @@ void ElfFile<ElfFileParamNames>::addNeeded(const std::set<std::string> & libs)
     }
 
     changed = true;
+
+    this->rewriteSections();
 }
 
 template<ElfFileParams>
 void ElfFile<ElfFileParamNames>::printNeededLibs() // const
 {
-    const auto shdrDynamic = findSection(".dynamic");
-    const auto shdrDynStr = findSection(".dynstr");
+    const auto shdrDynamic = findSectionHeader(".dynamic");
+    const auto shdrDynStr = findSectionHeader(".dynstr");
     const char *strTab = (char *)fileContents->data() + rdi(shdrDynStr.sh_offset);
 
     const Elf_Dyn *dyn = (Elf_Dyn *) (fileContents->data() + rdi(shdrDynamic.sh_offset));
@@ -1766,7 +1615,7 @@ void ElfFile<ElfFileParamNames>::printNeededLibs() // const
 template<ElfFileParams>
 void ElfFile<ElfFileParamNames>::noDefaultLib()
 {
-    auto shdrDynamic = findSection(".dynamic");
+    auto shdrDynamic = findSectionHeader(".dynamic");
 
     auto dyn = (Elf_Dyn *)(fileContents->data() + rdi(shdrDynamic.sh_offset));
     auto dynFlags1 = (Elf_Dyn *)nullptr;
@@ -1799,6 +1648,7 @@ void ElfFile<ElfFileParamNames>::noDefaultLib()
         setSubstr(newDynamic, 0, std::string((char *) &newDyn, sizeof(Elf_Dyn)));
     }
 
+    this->rewriteSections();
     changed = true;
 }
 
@@ -1807,9 +1657,9 @@ void ElfFile<ElfFileParamNames>::clearSymbolVersions(const std::set<std::string>
 {
     if (syms.empty()) return;
 
-    auto shdrDynStr = findSection(".dynstr");
-    auto shdrDynsym = findSection(".dynsym");
-    auto shdrVersym = findSection(".gnu.version");
+    auto shdrDynStr = findSectionHeader(".dynstr");
+    auto shdrDynsym = findSectionHeader(".dynsym");
+    auto shdrVersym = findSectionHeader(".gnu.version");
 
     auto strTab = (char *)fileContents->data() + rdi(shdrDynStr.sh_offset);
     auto dynsyms = (Elf_Sym *)(fileContents->data() + rdi(shdrDynsym.sh_offset));
@@ -1828,6 +1678,7 @@ void ElfFile<ElfFileParamNames>::clearSymbolVersions(const std::set<std::string>
         }
     }
     changed = true;
+    this->rewriteSections();
 }
 
 static bool printInterpreter = false;
@@ -1887,7 +1738,6 @@ static void patchElf2(ElfFile && elfFile, const FileContents & fileContents, con
         elfFile.noDefaultLib();
 
     if (elfFile.isChanged()){
-        elfFile.rewriteSections();
         writeFile(fileName, elfFile.fileContents);
     } else if (alwaysWrite) {
         debug("not modified, but alwaysWrite=true\n");
diff --git a/src/patchelf.h b/src/patchelf.h
new file mode 100644
index 0000000..93a0e5c
--- /dev/null
+++ b/src/patchelf.h
@@ -0,0 +1,159 @@
+using FileContents = std::shared_ptr<std::vector<unsigned char>>;
+
+#define ElfFileParams class Elf_Ehdr, class Elf_Phdr, class Elf_Shdr, class Elf_Addr, class Elf_Off, class Elf_Dyn, class Elf_Sym, class Elf_Verneed, class Elf_Versym
+#define ElfFileParamNames Elf_Ehdr, Elf_Phdr, Elf_Shdr, Elf_Addr, Elf_Off, Elf_Dyn, Elf_Sym, Elf_Verneed, Elf_Versym
+
+template<ElfFileParams>
+class ElfFile
+{
+public:
+
+    const FileContents fileContents;
+
+private:
+
+    std::vector<Elf_Phdr> phdrs;
+    std::vector<Elf_Shdr> shdrs;
+
+    bool littleEndian;
+
+    bool changed = false;
+
+    bool isExecutable = false;
+
+    using SectionName = std::string;
+    using ReplacedSections = std::map<SectionName, std::string>;
+
+    ReplacedSections replacedSections;
+
+    std::string sectionNames; /* content of the .shstrtab section */
+
+    /* Align on 4 or 8 bytes boundaries on 32- or 64-bit platforms
+       respectively. */
+    size_t sectionAlignment = sizeof(Elf_Off);
+
+    std::vector<SectionName> sectionsByOldIndex;
+
+public:
+    explicit ElfFile(FileContents fileContents);
+
+    bool isChanged()
+    {
+        return changed;
+    }
+
+private:
+
+    struct CompPhdr
+    {
+        ElfFile * elfFile;
+        bool operator ()(const Elf_Phdr & x, const Elf_Phdr & y)
+        {
+            // A PHDR comes before everything else.
+            if (elfFile->rdi(y.p_type) == PT_PHDR) return false;
+            if (elfFile->rdi(x.p_type) == PT_PHDR) return true;
+
+            // Sort non-PHDRs by address.
+            return elfFile->rdi(x.p_paddr) < elfFile->rdi(y.p_paddr);
+        }
+    };
+
+    friend struct CompPhdr;
+
+    void sortPhdrs();
+
+    struct CompShdr
+    {
+        ElfFile * elfFile;
+        bool operator ()(const Elf_Shdr & x, const Elf_Shdr & y)
+        {
+            return elfFile->rdi(x.sh_offset) < elfFile->rdi(y.sh_offset);
+        }
+    };
+
+    friend struct CompShdr;
+
+    unsigned int getPageSize() const;
+
+    void sortShdrs();
+
+    void shiftFile(unsigned int extraPages, Elf_Addr startPage);
+
+    std::string getSectionName(const Elf_Shdr & shdr) const;
+
+    Elf_Shdr & findSectionHeader(const SectionName & sectionName);
+
+    std::optional<std::reference_wrapper<Elf_Shdr>> tryFindSectionHeader(const SectionName & sectionName);
+
+    unsigned int getSectionIndex(const SectionName & sectionName);
+
+    std::string & replaceSection(const SectionName & sectionName,
+        unsigned int size);
+
+    bool haveReplacedSection(const SectionName & sectionName) const;
+
+    void writeReplacedSections(Elf_Off & curOff,
+        Elf_Addr startAddr, Elf_Off startOffset);
+
+    void rewriteHeaders(Elf_Addr phdrAddress);
+
+    void rewriteSectionsLibrary();
+
+    void rewriteSectionsExecutable();
+
+    void normalizeNoteSegments();
+
+public:
+
+    void rewriteSections();
+
+    std::string getInterpreter();
+
+    typedef enum { printSoname, replaceSoname } sonameMode;
+
+    void modifySoname(sonameMode op, const std::string & newSoname);
+
+    void setInterpreter(const std::string & newInterpreter);
+
+    typedef enum { rpPrint, rpShrink, rpSet, rpAdd, rpRemove } RPathOp;
+
+    void modifyRPath(RPathOp op, const std::vector<std::string> & allowedRpathPrefixes, std::string newRPath);
+    std::string shrinkRPath(char* rpath, std::vector<std::string> &neededLibs, const std::vector<std::string> & allowedRpathPrefixes);
+    void removeRPath(Elf_Shdr & shdrDynamic);
+
+    void addNeeded(const std::set<std::string> & libs);
+
+    void removeNeeded(const std::set<std::string> & libs);
+
+    void replaceNeeded(const std::map<std::string, std::string> & libs);
+
+    void printNeededLibs() /* should be const */;
+
+    void noDefaultLib();
+
+    void clearSymbolVersions(const std::set<std::string> & syms);
+
+private:
+
+    /* Convert an integer in big or little endian representation (as
+       specified by the ELF header) to this platform's integer
+       representation. */
+    template<class I>
+    I rdi(I i) const;
+
+    /* Convert back to the ELF representation. */
+    template<class I>
+    I wri(I & t, unsigned long long i) const
+    {
+        t = rdi((I) i);
+        return i;
+    }
+
+    Elf_Ehdr *hdr() {
+      return (Elf_Ehdr *)fileContents->data();
+    }
+
+    const Elf_Ehdr *hdr() const {
+      return (const Elf_Ehdr *)fileContents->data();
+    }
+};
diff --git a/tests/Makefile.am b/tests/Makefile.am
index c1d61ce..b280e62 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -39,7 +39,8 @@ src_TESTS = \
   basic-flags.sh \
   set-empty-rpath.sh \
   phdr-corruption.sh \
-  replace-needed.sh
+  replace-needed.sh \
+  replace-add-needed.sh
 
 build_TESTS = \
   $(no_rpath_arch_TESTS)
@@ -83,7 +84,7 @@ main_scoped_LDFLAGS = $(LDFLAGS_local)
 
 big-dynstr.c: main.c
 	cat $< > big-dynstr.c
-	for i in $$(seq 1 2000); do echo "void f$$i(void) { };"; done >> big-dynstr.c
+	i=1; while [ $$i -le 2000 ]; do echo "void f$$i(void) { };"; i=$$(($$i + 1)); done >> big-dynstr.c
 
 nodist_big_dynstr_SOURCES = big-dynstr.c
 big_dynstr_LDADD = -lfoo $(AM_LDADD)
@@ -108,7 +109,7 @@ check_PROGRAMS += libfoo.so libfoo-scoped.so libbar.so libbar-scoped.so libsimpl
                   phdr-corruption.so
 
 libbuildid_so_SOURCES = simple.c
-libbuildid_so_LDFLAGS = $(LDFLAGS_sharedlib) -Wl,-build-id
+libbuildid_so_LDFLAGS = $(LDFLAGS_sharedlib) -Wl,--build-id
 
 libfoo_so_SOURCES = foo.c
 libfoo_so_LDADD = -lbar $(AM_LDADD)
@@ -129,7 +130,8 @@ libbar_scoped_so_LDFLAGS = $(LDFLAGS_sharedlib)
 libsimple_so_SOURCES = simple.c
 libsimple_so_LDFLAGS = $(LDFLAGS_sharedlib)
 
-libtoomanystrtab_so_SOURCES = too-many-strtab.c
+too_many_strtab_SOURCES = too-many-strtab.c too-many-strtab2.s
+libtoomanystrtab_so_SOURCES = too-many-strtab.c too-many-strtab2.s
 libtoomanystrtab_so_LDFLAGS = $(LDFLAGS_sharedlib)
 
 no_rpath_SOURCES = no-rpath.c
@@ -137,9 +139,9 @@ no_rpath_SOURCES = no-rpath.c
 no_rpath_CFLAGS =
 
 contiguous_note_sections_SOURCES = contiguous-note-sections.s contiguous-note-sections.ld
-contiguous_note_sections_LDFLAGS = -nostdlib -T contiguous-note-sections.ld
+contiguous_note_sections_LDFLAGS = -nostdlib -T $(srcdir)/contiguous-note-sections.ld
 contiguous_note_sections_CFLAGS = -pie
 
 phdr_corruption_so_SOURCES = void.c phdr-corruption.ld
-phdr_corruption_so_LDFLAGS = -nostdlib -shared -Wl,-Tphdr-corruption.ld
+phdr_corruption_so_LDFLAGS = -nostdlib -shared -Wl,-T$(srcdir)/phdr-corruption.ld
 phdr_corruption_so_CFLAGS =
diff --git a/tests/build-id.sh b/tests/build-id.sh
index 45a6c4d..81b8f06 100755
--- a/tests/build-id.sh
+++ b/tests/build-id.sh
@@ -16,4 +16,5 @@ long_rpath="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
 ../src/patchelf \
   --set-rpath "$long_rpath" "${SCRATCH}/libbuildid.so"
 
-readelf -n "${SCRATCH}/libbuildid.so" | grep -q "Build ID"
+# older readelf versions do not recognize build id, but we can grep by constant
+readelf -n "${SCRATCH}/libbuildid.so" |  grep -q -F -e 'Build ID' -e 'Unknown note type: (0x00000003)'
diff --git a/tests/endianness.sh b/tests/endianness.sh
index 23b4aea..4f84a08 100755
--- a/tests/endianness.sh
+++ b/tests/endianness.sh
@@ -6,7 +6,7 @@ for arch in ppc64 ppc64le; do
     rm -rf ${SCRATCH}
     mkdir -p ${SCRATCH}
 
-    cp endianness/${arch}/main endianness/${arch}/libtest.so ${SCRATCH}/
+    cp ${srcdir}/endianness/${arch}/main ${srcdir}/endianness/${arch}/libtest.so ${SCRATCH}/
 
     rpath="${PWD}/${SCRATCH}"
 
@@ -19,6 +19,6 @@ for arch in ppc64 ppc64le; do
     # check whether rpath is still present
     if ! ${PATCHELF} --print-rpath ${SCRATCH}/main-shrunk | grep -q "$rpath"; then
         echo "rpath was removed for ${arch}"
-	exit 1
+        exit 1
     fi
 done
diff --git a/tests/grow-file.sh b/tests/grow-file.sh
index e01d2be..4c405df 100755
--- a/tests/grow-file.sh
+++ b/tests/grow-file.sh
@@ -8,7 +8,7 @@ mkdir -p ${SCRATCH}
 cp simple-pie ${SCRATCH}/simple-pie
 
 # Add a 40MB rpath
-head -c 40000000 /dev/urandom > ${SCRATCH}/foo.bin
+tr -cd 'a-z0-9' < /dev/urandom | dd count=40 bs=1000000 > ${SCRATCH}/foo.bin
 
 # Grow the file
 ../src/patchelf --add-rpath @${SCRATCH}/foo.bin ${SCRATCH}/simple-pie
diff --git a/tests/replace-add-needed.sh b/tests/replace-add-needed.sh
new file mode 100755
index 0000000..ab0d353
--- /dev/null
+++ b/tests/replace-add-needed.sh
@@ -0,0 +1,38 @@
+#! /bin/sh -e
+SCRATCH=scratch/$(basename $0 .sh)
+PATCHELF=$(readlink -f "../src/patchelf")
+
+rm -rf ${SCRATCH}
+mkdir -p ${SCRATCH}
+
+cp simple ${SCRATCH}/
+cp libfoo.so ${SCRATCH}/
+cp libbar.so ${SCRATCH}/
+
+cd ${SCRATCH}
+
+libcldd=$(ldd ./simple | awk '/ => / { print $3 }' | grep -E "(libc.so|ld-musl)")
+
+# We have to set the soname on these libraries
+${PATCHELF} --set-soname libbar.so ./libbar.so
+
+# Add a libbar.so so we can rewrite it later
+${PATCHELF} --add-needed libbar.so ./simple
+
+# Make the NEEDED in libfoo the same as simple
+# This is a current "bug" in musl
+# https://www.openwall.com/lists/musl/2021/12/21/1
+${PATCHELF} --replace-needed libbar.so $(readlink -f ./libbar.so) ./libfoo.so
+
+${PATCHELF} --replace-needed libc.so.6 ${libcldd} \
+            --replace-needed libbar.so $(readlink -f ./libbar.so) \
+            --add-needed $(readlink -f ./libfoo.so) \
+            ./simple
+
+exitCode=0
+./simple || exitCode=$?
+
+if test "$exitCode" != 0; then
+    ldd ./simple
+    exit 1
+fi
\ No newline at end of file
diff --git a/tests/too-many-strtab.c b/tests/too-many-strtab.c
index 0f51316..1075f35 100644
--- a/tests/too-many-strtab.c
+++ b/tests/too-many-strtab.c
@@ -1,3 +1 @@
-const int __attribute__((section (".shstrtab"))) lel = 42;
-
-int main(){return 0;}
+int main(){ return 0; }
diff --git a/tests/too-many-strtab2.s b/tests/too-many-strtab2.s
new file mode 100644
index 0000000..1928a82
--- /dev/null
+++ b/tests/too-many-strtab2.s
@@ -0,0 +1,5 @@
+/*
+ * Create additional .shstrtab section
+ */
+.section ".shstrtab", "", %3
+.byte 0
diff --git a/version b/version
index 94ec240..6fd113f 100644
--- a/version
+++ b/version
@@ -1 +1 @@
-0.14.3
\ No newline at end of file
+0.14.5
\ No newline at end of file