Loading...
mach_o_writer/NListSymbolTableWriter.cpp dyld-1340 /dev/null
--- dyld/dyld-1340/mach_o_writer/NListSymbolTableWriter.cpp
+++ /dev/null
@@ -1,514 +0,0 @@
-/*
- * Copyright (c) 2021 Apple Inc. All rights reserved.
- *
- * @APPLE_LICENSE_HEADER_START@
- *
- * This file contains Original Code and/or Modifications of Original Code
- * as defined in and that are subject to the Apple Public Source License
- * Version 2.0 (the 'License'). You may not use this file except in
- * compliance with the License. Please obtain a copy of the License at
- * http://www.opensource.apple.com/apsl/ and read it before using this
- * file.
- *
- * The Original Code and all software distributed under the License are
- * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
- * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
- * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
- * Please see the License for the specific language governing rights and
- * limitations under the License.
- *
- * @APPLE_LICENSE_HEADER_END@
- */
-
-#include "MemoryBuffer.h"
-#include <algorithm>
-#include <sys/types.h>
-#include <assert.h>
-#include <limits.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include <mach-o/loader.h>
-#include <mach-o/nlist.h>
-#include <mach-o/stab.h>
-#include "Algorithm.h"
-
-// mach_o
-#include "Symbol.h"
-#include "Misc.h"
-
-// mach_o_writer
-#include "NListSymbolTableWriter.h"
-
-using mach_o::DebugNoteFileInfo;
-
-namespace mach_o {
-
-//
-// MARK: --- NListSymbolTable building methods ---
-//
-
-uint32_t NListSymbolTableWriter::countDebugNoteNLists(std::span<const DebugBuilderNote> debugNotes)
-{
-    uint32_t debugStabNlists=0;
-    bool startedSO=false;
-
-    for ( const DebugBuilderNote& note : debugNotes ) {
-        if ( note.fileInfo->srcDir().empty() && note.fileInfo->srcName().empty() ) {
-            debugStabNlists += 1;
-        } else {
-            if ( !startedSO ) {
-                startedSO = true;
-                debugStabNlists += 1;
-            }
-            debugStabNlists += 4;
-            if ( note.fileInfo->hasOriginLibInfo() )
-                debugStabNlists += 1;
-
-            for ( const DebugBuilderNoteItem& item : note.items ) {
-                if ( item.type == N_FUN )
-                    debugStabNlists += 4;
-                else
-                    debugStabNlists += 1;
-            }
-        }
-    }
-    return debugStabNlists;
-}
-
-template <typename T>
-void NListSymbolTableWriter::addStabsFromDebugNotes(std::span<const DebugBuilderNote> debugNotes, bool zeroModTimes, NListBuffer& nlists)
-{
-    typedef __typeof(T::n_value) V;
-
-    bool startedSOs = false;
-    for (const DebugBuilderNote& note : debugNotes) {
-        uint32_t mtime = (zeroModTimes ? 0 : note.fileInfo->objModTime());
-        if ( note.srcDirPoolOffset == 0 && note.srcNamePoolOffset == 0 ) {
-            nlists.add(T{{.n_strx = note.objPathPoolOffset}, N_AST, 0, 0, (V)mtime});
-        }
-        else {
-            if ( !startedSOs )
-                nlists.add(T{{.n_strx = 1}, N_SO, 1, 0, 0}); // match ld64 which always started debug notes with an "end SO"
-            // Put this before the other N_SO's.  We can't put it right before the N_OSO as lldb expects the N_OSO
-            // to be immediately preceded by the N_SO
-            if ( note.originLibPathPoolOffset != 0 ) {
-                nlists.add(T{{.n_strx = note.originLibPathPoolOffset}, N_LIB, 0, 0, 0});
-            }
-            startedSOs = true;
-            nlists.add(T{{.n_strx = note.srcDirPoolOffset},  N_SO,  0,                           0, 0});
-            nlists.add(T{{.n_strx = note.srcNamePoolOffset}, N_SO,  0,                           0, 0});
-            nlists.add(T{{.n_strx = note.objPathPoolOffset}, N_OSO, note.fileInfo->objSubType(), 1, (V)mtime});
-            for (const DebugBuilderNoteItem& item : note.items) {
-                uint32_t stringPoolOffset = item.stringPoolOffset;
-                switch ( item.type ) {
-                case N_FUN:
-                    // for functions, we use four symbols to record the name, address, size, and sectNum
-                    nlists.add(T{{.n_strx = 1},                N_BNSYM, item.sectNum, 0, (V)item.addr});
-                    nlists.add(T{{.n_strx = stringPoolOffset}, N_FUN,   item.sectNum, 0, (V)item.addr});
-                    nlists.add(T{{.n_strx = 1},                N_FUN,   0,            0, (V)item.size});
-                    nlists.add(T{{.n_strx = 1},                N_ENSYM, item.sectNum, 0, (V)item.addr});
-                    break;
-                case N_STSYM:
-                    // for static variables, we record the name, address, and sectNum
-                    nlists.add(T{{.n_strx = stringPoolOffset}, N_STSYM, item.sectNum, 0, (V)item.addr});
-                    break;
-                case N_GSYM:
-                    // for global variables, we record just the name
-                    nlists.add(T{{.n_strx = stringPoolOffset}, N_GSYM,  0,            0, 0});
-                    break;
-                default:
-                    assert(false && "invalid debug note item");
-                    break;
-                }
-            }
-            nlists.add(T{{.n_strx = 1},              N_SO, 1, 0, 0});
-        }
-    }
-}
-
-
-/*!
- * @class NListStringPoolBuffer
- *
- * @abstract
- *      Simple NList string pool buffer, used in unit-tests.
- */
-struct NListStringPoolBuffer
-{
-    std::vector<char> buffer;
-    uint32_t          pos = 0;
-
-    NListStringPoolBuffer()
-    {
-        add(' ');
-        add('\0');
-    }
-
-    NListStringPoolBuffer(const NListStringPoolBuffer&) = delete;
-    NListStringPoolBuffer(NListStringPoolBuffer&&) = default;
-    NListStringPoolBuffer& operator=(const NListStringPoolBuffer&) = delete;
-    NListStringPoolBuffer& operator=(NListStringPoolBuffer&&) = default;
-
-    uint32_t add(CString str);
-    uint32_t add(std::span<const char> bytes);
-    uint32_t add(char ch);
-    uint32_t size() { return pos; }
-    char*    data() { return buffer.data(); }
-
-    void     finalize(bool is64)
-    {
-        uint32_t pointerSize = is64 ? 8 : 4;
-        while ( size() % pointerSize )
-            add('\0');
-    }
-
-    std::pair<uint32_t, char*> reserve(size_t);
-};
-
-NListSymbolTableWriter::SymbolPartition::SymbolPartition(std::span<const Symbol> symbols, bool objectFile)
-{
-    for (const Symbol& symbol : symbols) {
-        int      libOrdinal;
-        bool     weakImport;
-        uint64_t size;
-        uint8_t  p2Align;
-        if ( symbol.isUndefined(libOrdinal, weakImport) || symbol.isTentativeDef(size, p2Align) )
-            undefs.push_back(symbol);
-        else if ( symbol.scope() == Symbol::Scope::global )
-            globals.push_back(symbol);
-        else if ( (symbol.scope() == Symbol::Scope::linkageUnit) && objectFile )
-            globals.push_back(symbol); // in .o files hidden symbols are in globals range
-        else if ( (symbol.scope() == Symbol::Scope::autoHide) && objectFile )
-            globals.push_back(symbol); // in .o files hidden symbols are in globals range
-        else
-            locals.push_back(symbol);
-    }
-
-    // for historical binary search reasons, globals are sorted by name
-    std::sort(globals.begin(), globals.end(), [&](const Symbol& a, const Symbol& b) {
-        return a.name() < b.name();
-    });
-    // undefs are sorted by name
-    std::sort(undefs.begin(), undefs.end(), [&](const Symbol& a, const Symbol& b) {
-        return a.name() < b.name();
-    });
-    // locals are already sorted by their position in their section.  We don't need to sort them again
-}
-
-NListSymbolTableWriter::NListSymbolTableWriter(std::span<const Symbol> symbols, uint64_t prefLoadAddr, bool is64, std::span<DebugBuilderNote> debugNotes,
-                                   bool zeroModTimes, bool objectFile)
-    : NListSymbolTableWriter(SymbolPartition(symbols, objectFile), debugNotes, prefLoadAddr, is64, zeroModTimes)
-{}
-
-
-NListSymbolTableWriter::NListSymbolTableWriter(const SymbolPartition& partition, std::span<DebugBuilderNote> debugNotes,
-                 uint64_t prefLoadAddr, bool is64, bool zeroModTimes)
-    : NListSymbolTableWriter(partition.globals, partition.undefs, partition.locals, debugNotes, prefLoadAddr, is64, zeroModTimes)
-{}
-
-NListSymbolTableWriter::NListSymbolTableWriter(std::span<const Symbol> globals, std::span<const Symbol> undefs,
-                                               std::span<const Symbol> locals, std::span<DebugBuilderNote> debugNotes,
-                                               uint64_t prefLoadAddr, bool is64, bool zeroModTimes)
-    : NListSymbolTable()
-{
-    uint32_t numDebugNlist = countDebugNoteNLists(debugNotes);
-    size_t nlistSize = (locals.size() + globals.size() + undefs.size() + numDebugNlist) * (is64 ? sizeof(nlist_64) : sizeof(struct nlist));
-
-    NListStringPoolBuffer stringPoolBuffer;
-
-    size_t strxAllCount = globals.size() * 2 + locals.size() + undefs.size();
-    std::vector<uint32_t> strxAll(strxAllCount);
-    std::span<uint32_t> globalsStrx = std::span(strxAll).subspan(0, globals.size());
-    std::span<uint32_t> reexportsStrx = std::span(globalsStrx.end().base(), globals.size());
-    std::span<uint32_t> undefsStrx = std::span(reexportsStrx.end().base(), undefs.size());
-    std::span<uint32_t> localsStrx = std::span(undefsStrx.end().base(), locals.size());
-    for ( size_t i = 0; i < globals.size(); ++i ) {
-        const Symbol& s = globals[i];
-        globalsStrx[i] = stringPoolBuffer.add(s.name());
-        int32_t ordinal;
-        const char* importName=nullptr;
-        if ( s.isReExport(ordinal, importName) )
-            reexportsStrx[i] = stringPoolBuffer.add(s.name());
-    }
-    for ( size_t i = 0; i < undefs.size(); ++i ) {
-        const Symbol& s = undefs[i];
-        undefsStrx[i] = stringPoolBuffer.add(s.name());
-    }
-    for ( size_t i = 0; i < locals.size(); ++i ) {
-        const Symbol& s = locals[i];
-        localsStrx[i] = stringPoolBuffer.add(s.name());
-    }
-
-    for ( DebugBuilderNote& debugNote : debugNotes ) {
-        if ( CString srcDir = debugNote.fileInfo->srcDir(); !srcDir.empty() )
-            debugNote.srcDirPoolOffset = stringPoolBuffer.add(srcDir);
-        if ( CString srcName = debugNote.fileInfo->srcName(); !srcName.empty() )
-            debugNote.srcNamePoolOffset = stringPoolBuffer.add(srcName);
-        if ( CString originLibPath = debugNote.fileInfo->originLibPath(); !originLibPath.empty() )
-            debugNote.originLibPathPoolOffset=stringPoolBuffer.add(originLibPath);
-        if ( CString objPath = debugNote.fileInfo->objPath(); !objPath.empty() )
-            debugNote.objPathPoolOffset=stringPoolBuffer.add(objPath);
-
-        for ( DebugBuilderNoteItem& item : debugNote.items ) {
-            item.stringPoolOffset=stringPoolBuffer.add(item.name);
-        }
-    }
-    stringPoolBuffer.finalize(is64);
-
-    *this = NListSymbolTableWriter(NListLayout{ globals, globalsStrx, reexportsStrx, undefs, undefsStrx, locals, localsStrx, debugNotes, numDebugNlist }, NListBuffer(nlistSize), std::move(stringPoolBuffer.buffer), prefLoadAddr, is64, zeroModTimes);
-}
-
-NListSymbolTableWriter::NListSymbolTableWriter(NListLayout layout, std::span<uint8_t> nlistBuffer, uint64_t prefLoadAddr, bool is64, bool zeroModTimes): NListSymbolTableWriter(layout, NListBuffer(nlistBuffer), {}, prefLoadAddr, is64, zeroModTimes) {}
-
-NListSymbolTableWriter::NListSymbolTableWriter(NListLayout layout, NListBuffer nlist, std::vector<char> stringPoolBuffer, uint64_t prefLoadAddr, bool is64, bool zeroModTimes)
-    : _nlistBuffer(std::move(nlist)), _stringPoolBuffer(std::move(stringPoolBuffer))
-{
-    // partition symbols into locals, globals, and undefs
-    _localsCount    = (uint32_t)layout.locals.size() + layout.debugNotesNListCount;
-    _globalsCount   = (uint32_t)layout.globals.size();
-    _undefsCount    = (uint32_t)layout.undefs.size();
-    _nlistCount     = _localsCount + _globalsCount + _undefsCount;
-
-    assert(layout.globals.size()   == layout.globalsStrx.size());
-    assert(layout.globals.size()   == layout.reexportStrx.size());
-    assert(layout.undefs.size()    == layout.undefsStrx.size());
-    assert(layout.locals.size()    == layout.localsStrx.size());
-    std::span<uint8_t> nlistBuffer = _nlistBuffer.buffer;
-
-    // convert each symbol to nlist
-    _preferredLoadAddress = prefLoadAddr;
-    if ( is64 ) {
-        assert(nlistBuffer.size() == (_localsCount + _globalsCount + _undefsCount) * sizeof(nlist_64));
-
-        // symbol table strings are added in the order of globals, imports, locals
-        // but the nlist itself is emitted as locals, globals, imports.
-        // So we'll walk in the string order, and then create the nlist after
-
-        std::span<nlist_64> nlist64Buffer = std::span<nlist_64>((nlist_64*)nlistBuffer.data(), nlistBuffer.size() / sizeof(nlist_64));
-        std::span<nlist_64> globalsBuffer(nlist64Buffer.subspan(_localsCount, _globalsCount));
-        std::span<nlist_64> undefsBuffer(nlist64Buffer.subspan(_localsCount + _globalsCount, _undefsCount));
-        std::span<nlist_64> localsBuffer(nlist64Buffer.subspan(0, _localsCount));
-
-        dispatchForEach(layout.globals, [this, globalsBuffer, &layout](size_t i, const Symbol& sym) {
-            globalsBuffer[i] = nlist64FromSymbol(sym, layout.globalsStrx[i], layout.reexportStrx[i]);
-        });
-        dispatchForEach(layout.undefs, [this, undefsBuffer, &layout](size_t i, const Symbol& sym) {
-            undefsBuffer[i] = nlist64FromSymbol(sym, layout.undefsStrx[i], 0);
-        });
-        dispatchForEach(layout.locals, [this, localsBuffer, &layout](size_t i, const Symbol& sym) {
-            localsBuffer[i] = nlist64FromSymbol(sym, layout.localsStrx[i], 0);
-        });
-
-        NListBuffer stabsBuffer = localsBuffer.subspan(layout.locals.size());
-        assert((stabsBuffer.buffer.size() / sizeof(nlist_64)) == layout.debugNotesNListCount);
-        addStabsFromDebugNotes<nlist_64>(layout.debugNotes, zeroModTimes, stabsBuffer);
-    }
-    else {
-        // symbol table strings are added in the order of globals, imports, locals
-        // but the nlist itself is emitted as locals, globals, imports.
-        // So we'll walk in the string order, and then create the nlist after
-
-        std::span<struct nlist> nlist32Buffer = std::span<struct nlist>((struct nlist*)nlistBuffer.data(), nlistBuffer.size() / sizeof(struct nlist));
-        std::span<struct nlist> globalsBuffer(nlist32Buffer.subspan(_localsCount, _globalsCount));
-        std::span<struct nlist> undefsBuffer(nlist32Buffer.subspan(_localsCount + _globalsCount, _undefsCount));
-        std::span<struct nlist> localsBuffer(nlist32Buffer.subspan(0, _localsCount));
-
-        dispatchForEach(layout.globals, [this, globalsBuffer, &layout](size_t i, const Symbol& sym) {
-            globalsBuffer[i] = nlistFromSymbol(sym, layout.globalsStrx[i], layout.reexportStrx[i]);
-        });
-        dispatchForEach(layout.undefs, [this, undefsBuffer, &layout](size_t i, const Symbol& sym) {
-            undefsBuffer[i] = nlistFromSymbol(sym, layout.undefsStrx[i], 0);
-        });
-        dispatchForEach(layout.locals, [this, localsBuffer, &layout](size_t i, const Symbol& sym) {
-            localsBuffer[i] = nlistFromSymbol(sym, layout.localsStrx[i], 0);
-        });
-
-        NListBuffer stabsBuffer = localsBuffer.subspan(layout.locals.size());
-        assert((stabsBuffer.buffer.size() / sizeof(struct nlist)) == layout.debugNotesNListCount);
-        addStabsFromDebugNotes<struct nlist>(layout.debugNotes, zeroModTimes, stabsBuffer);
-    }
-
-    // fill in all ivars as if this came from a mach-o file
-    _preferredLoadAddress = prefLoadAddr;
-    _stringPool           = _stringPoolBuffer.data();
-    _nlist32              = is64 ? nullptr : (struct nlist*)nlistBuffer.data();
-    _nlist64              = is64 ? (nlist_64*)nlistBuffer.data() : nullptr;
-    _stringPoolSize       = (uint32_t)_stringPoolBuffer.size();
-}
-
-static uint8_t ntypeFromSymbol(const Symbol& symbol)
-{
-    switch ( symbol.scope() ) {
-        case Symbol::Scope::global:
-        case Symbol::Scope::globalNeverStrip:
-        case Symbol::Scope::autoHide:
-            return N_EXT;
-        case Symbol::Scope::linkageUnit:
-            return N_EXT | N_PEXT;
-        case Symbol::Scope::translationUnit:
-            return 0;
-        case Symbol::Scope::wasLinkageUnit:
-            return N_PEXT;
-   }
-}
-
-static uint16_t weakDefDesc(const Symbol& symbol)
-{
-    uint16_t desc = 0;
-    if ( symbol.isWeakDef() ) {
-        switch ( symbol.scope() ) {
-            case Symbol::Scope::globalNeverStrip:
-            case Symbol::Scope::global:
-            case Symbol::Scope::linkageUnit:
-            case Symbol::Scope::wasLinkageUnit:
-                desc = N_WEAK_DEF;
-                break;
-            case Symbol::Scope::autoHide:
-                desc = N_WEAK_DEF | N_WEAK_REF;
-                break;
-            case Symbol::Scope::translationUnit:
-                break;
-        }
-    }
-    return desc;
-}
-
-struct nlist_64 NListSymbolTableWriter::nlist64FromSymbol(const Symbol& symbol, uint32_t strx, uint32_t reexportStrx)
-{
-    struct nlist_64 result;
-    int             libOrdinal;
-    uint64_t        absAddress;
-    bool            weakImport;
-    uint64_t        implOffset;
-    uint64_t        size;
-    uint64_t        stubOffset;
-    uint8_t         p2align;
-    const char*     importName;
-    uint32_t        fvtIndex;
-    if ( symbol.isTentativeDef(size, p2align) ) {
-        result.n_un.n_strx = strx;
-        result.n_type      = N_UNDF | ntypeFromSymbol(symbol);
-        result.n_sect      = 0;
-        result.n_desc      = 0;
-        result.n_value     = size;
-        SET_COMM_ALIGN(result.n_desc,p2align);
-    }
-    else if ( symbol.isUndefined(libOrdinal, weakImport) ) {
-        result.n_un.n_strx = strx;
-        result.n_type      = N_UNDF | N_EXT;
-        result.n_sect      = 0;
-        result.n_desc      = (libOrdinal << 8) | (weakImport ? N_WEAK_REF : 0);
-        result.n_value     = 0;
-    }
-    else if ( symbol.isAbsolute(absAddress) )  {
-        uint16_t desc = 0;
-        if ( symbol.scope() == Symbol::Scope::globalNeverStrip )
-            desc |= REFERENCED_DYNAMICALLY;
-
-        result.n_un.n_strx = strx;
-        result.n_type      = N_ABS | ntypeFromSymbol(symbol);
-        result.n_sect      = symbol.sectionOrdinal();
-        result.n_desc      = desc;
-        result.n_value     = absAddress;
-    }
-    else if ( symbol.isRegular(implOffset) || symbol.isThreadLocal(implOffset) ) {
-        uint16_t desc = weakDefDesc(symbol);
-        if ( symbol.dontDeadStrip() )
-            desc |= N_NO_DEAD_STRIP;
-        if ( symbol.cold() )
-            desc |= N_COLD_FUNC;
-        if ( symbol.scope() == Symbol::Scope::globalNeverStrip )
-            desc |= REFERENCED_DYNAMICALLY;
-        if ( symbol.isThumb() )
-            desc |= N_ARM_THUMB_DEF;
-        result.n_un.n_strx = strx;
-        result.n_type      = N_SECT | ntypeFromSymbol(symbol);
-        result.n_sect      = symbol.sectionOrdinal();
-        result.n_desc      = desc;
-        result.n_value     = _preferredLoadAddress + implOffset;
-    }
-    else if ( symbol.isFunctionVariant(fvtIndex) ) {
-        // Note:  currently cannot use no-dead-strip, cold, or dynamically-reference with funtion variants
-        result.n_un.n_strx = strx;
-        result.n_type      = N_SECT | ntypeFromSymbol(symbol);
-        result.n_sect      = symbol.sectionOrdinal();
-        result.n_desc      = 0;
-        result.n_value     = _preferredLoadAddress + symbol.implOffset();
-    }
-    else if ( symbol.isAltEntry(implOffset) ) {
-        uint64_t desc = N_ALT_ENTRY | weakDefDesc(symbol);
-        if ( symbol.dontDeadStrip() )
-            desc |= N_NO_DEAD_STRIP;
-        result.n_un.n_strx = strx;
-        result.n_type      = N_SECT | ntypeFromSymbol(symbol);
-        result.n_sect      = symbol.sectionOrdinal();
-        result.n_desc      = desc;
-        result.n_value     = _preferredLoadAddress + implOffset;
-    }
-    else if ( symbol.isReExport(libOrdinal, importName) ) {
-        // re-exports can't be local, they're always global in linked images,
-        // in object files they can have global/linkage unit scope or be undefined.
-        assert(symbol.scope() != Symbol::Scope::translationUnit && "re-exports can't have a translation unit");
-        result.n_un.n_strx = strx;
-        result.n_type      = N_INDR | ntypeFromSymbol(symbol);
-        result.n_sect      = 0;
-        result.n_desc      = 0;
-        result.n_value     = reexportStrx;
-    }
-    else if ( symbol.isDynamicResolver(stubOffset) ) {
-        result.n_un.n_strx = strx;
-        result.n_type      = N_SECT | ntypeFromSymbol(symbol);
-        result.n_sect      = symbol.sectionOrdinal();
-        result.n_desc      = N_SYMBOL_RESOLVER;
-        result.n_value     = _preferredLoadAddress + symbol.implOffset();
-    }
-    else {
-        assert(false && "unhandled symbol kind");
-    }
-
-    return result;
-}
-
-// avoid duplicating code by filling in nlist_64 and converting to nlist
-struct nlist NListSymbolTableWriter::nlistFromSymbol(const Symbol& symbol, uint32_t strx, uint32_t reexportStrx)
-{
-    struct nlist_64 result64 = nlist64FromSymbol(symbol, strx, reexportStrx);
-    struct nlist result;
-    result.n_un.n_strx = result64.n_un.n_strx;
-    result.n_type      = result64.n_type;
-    result.n_sect      = result64.n_sect;
-    result.n_desc      = result64.n_desc;
-    result.n_value     = (uint32_t)result64.n_value;
-    return result;
-}
-
-std::pair<uint32_t, char*> NListStringPoolBuffer::reserve(size_t size)
-{
-    size_t startPos = pos;
-    pos += size;
-    buffer.resize(buffer.size() + size);
-    return std::make_pair(startPos, buffer.data() + startPos);
-}
-
-uint32_t NListStringPoolBuffer::add(std::span<const char> bytes)
-{
-    auto [startPos, ptr] = reserve(bytes.size());
-    memcpy(ptr, bytes.data(), bytes.size());
-    return startPos;
-}
-
-uint32_t NListStringPoolBuffer::add(char ch)
-{
-    auto [startPos, ptr] = reserve(1);
-    *ptr = ch;
-    return startPos;
-}
-
-uint32_t NListStringPoolBuffer::add(CString cstr)
-{
-    return add(std::span(cstr.c_str(), cstr.size() + 1));
-}
-
-} // namespace mach_o