From 4a13f9df266b1d093db6ceb82caa26b6fd67f2b2 Mon Sep 17 00:00:00 2001 From: Scott Duensing Date: Sun, 26 Jul 2026 20:28:52 -0500 Subject: [PATCH] link816 now accepts library archives. --- runtime/include/iigs/gsos.h | 62 +++++++++++++++++++++++++++ runtime/src/iigsGsos.s | 12 ++++++ src/link816/link816.cpp | 83 ++++++++++++++++++++++++++++++++++++- 3 files changed, 156 insertions(+), 1 deletion(-) diff --git a/runtime/include/iigs/gsos.h b/runtime/include/iigs/gsos.h index 8ce3093..92060e6 100644 --- a/runtime/include/iigs/gsos.h +++ b/runtime/include/iigs/gsos.h @@ -21,6 +21,7 @@ // $2001 Create // $2002 Destroy // $2004 ChangePath +// $2008 Volume // $2010 Open // $2012 Read // $2013 Write @@ -29,6 +30,8 @@ // $2017 GetMark // $2018 SetEOF // $2019 GetEOF +// $2020 GetDevNumber +// $202C DInfo // $2029 Quit (special — no return) // See "GS/OS Reference" for the full ~50 calls and parm-block layouts. @@ -165,6 +168,61 @@ typedef struct { unsigned long resourceBlocks;// [out] } DirEntryRecGS; +// Class-1 Create parm block ($2001). Set pCount=5 to create a file or a +// directory: pathname + access + fileType + auxType + storageType. +// storageType 13 ($0D) = directory (use fileType $0F); 1 = seedling file. +typedef struct { + unsigned short pCount; // 5 (or up to 7 with eof / resourceEOF) + void *pathname; // [in] GSString * + unsigned short access; // [in] + unsigned short fileType; // [in] + unsigned long auxType; // [in] + unsigned short storageType; // [in] 13 = directory + unsigned long eof; // [in] (pCount >= 6) + unsigned long resourceEOF; // [in] (pCount >= 7) +} CreateRecGS; + +// Class-1 Volume parm block ($2008). Given a device or volume name in +// devName, returns block counts + block size; free bytes = freeBlocks * +// blockSize. volName must point to a caller-allocated ResultBuf (the OS +// writes the volume name there), so pCount is at least 2 whenever freeBlocks +// (pCount>=4) is wanted. +typedef struct { + unsigned short pCount; // 6 for free-space (through blockSize) + void *devName; // [in] GSString * + void *volName; // [out] ResultBuf * + unsigned long totalBlocks; // [out] + unsigned long freeBlocks; // [out] + unsigned short fileSysID; // [out] + unsigned short blockSize; // [out] +} VolumeRecGS; + +// Class-1 GetDevNumber parm block ($2020). Maps a device OR volume name to a +// device number (used to bridge a volume name to a DInfo call). +typedef struct { + unsigned short pCount; // 2 + void *devName; // [in] GSString * (device or volume name) + unsigned short devNum; // [out] +} DevNumRecGS; + +// Class-1 DInfo parm block ($202C). Given a device number, returns the device +// NAME (which is what the Volume call wants) among other characteristics. +// pCount=2 fills only devName. +typedef struct { + unsigned short pCount; // 2 (devNum in, devName out) .. up to 12 + unsigned short devNum; // [in] + void *devName; // [out] ResultBuf * + unsigned short characteristics; // [out] + unsigned long totalBlocks; // [out] + unsigned short slotNum; // [out] + unsigned short unitNum; // [out] + unsigned short version; // [out] + unsigned short deviceID; // [out] + unsigned short headLink; // [out] + unsigned short forwardLink; // [out] + void *extendedDIBPtr; // [out] +} DInfoRecGS; + // Open / Read / Write / Close wrappers. Each returns 0 on success or // a non-zero GS/OS error code (see gsos.h reference for codes). The // parm block lives on the caller's stack; you set the input fields @@ -186,6 +244,10 @@ extern unsigned short gsosChangePath(ChangePathRecGS *p); extern unsigned short gsosGetPrefix (PrefixRecGS *p); extern unsigned short gsosGetFileInfo(FileInfoRecGS *p); extern unsigned short gsosGetDirEntry(DirEntryRecGS *p); +extern unsigned short gsosCreate (CreateRecGS *p); +extern unsigned short gsosVolume (VolumeRecGS *p); +extern unsigned short gsosGetDevNumber(DevNumRecGS *p); +extern unsigned short gsosDInfo (DInfoRecGS *p); // Returns 1 when a real GS/OS dispatch surface is linked (either // iigsGsos.o for bare-metal or libcGno.o for GNO/ME), 0 when only diff --git a/runtime/src/iigsGsos.s b/runtime/src/iigsGsos.s index 35d3345..ae49075 100644 --- a/runtime/src/iigsGsos.s +++ b/runtime/src/iigsGsos.s @@ -60,6 +60,9 @@ .globl gsosGetFileInfo .globl gsosGetDirEntry .globl gsosCreate + .globl gsosVolume + .globl gsosGetDevNumber + .globl gsosDInfo ; __gsosIsRealImpl — sentinel that distinguishes a REAL GS/OS dispatch ; surface from the universal-success stub in iigsGsosStub.s. Both @@ -104,6 +107,15 @@ __gsosIsRealImpl: gsosCreate: gsosDispatch 0x2001, __gsosCreatePb +gsosVolume: + gsosDispatch 0x2008, __gsosVolumePb + +gsosGetDevNumber: + gsosDispatch 0x2020, __gsosGetDevNumberPb + +gsosDInfo: + gsosDispatch 0x202c, __gsosDInfoPb + gsosOpen: gsosDispatch 0x2010, __gsosOpenPb diff --git a/src/link816/link816.cpp b/src/link816/link816.cpp index 8fd91c6..954e754 100644 --- a/src/link816/link816.cpp +++ b/src/link816/link816.cpp @@ -1017,14 +1017,95 @@ struct Linker { // helper of the same name don't collide on insertion. std::map localSyms; + // Load one ELF object or, when `path` is a Unix `ar` archive, every object + // member it holds. Archive support lets JoeyLib examples link against a + // real libjoey.a exactly like the other ports; --gc-sections then drops + // whatever the image doesn't reference, matching classic member selection. void addObject(const std::string &path) { + std::vector data = readFile(path); + static const unsigned char AR_MAGIC[8] = + {'!', '<', 'a', 'r', 'c', 'h', '>', '\n'}; + if (data.size() >= 8 && std::memcmp(data.data(), AR_MAGIC, 8) == 0) { + addArchive(path, data); + return; + } auto o = std::make_unique(); o->path = path; - o->raw = readFile(path); + o->raw = std::move(data); o->parse(); objs.push_back(std::move(o)); } + // Split a GNU-format `ar` archive into its object members and load each as + // its own InputObject (path "archive.a(member)", so the file-static + // local-symbol key name@basename stays unique per member). Skips the + // symbol index ("/") and long-name string table ("//"). + void addArchive(const std::string &path, + const std::vector &data) { + size_t pos = 8; // past "!\n" + std::string longNames; // "//" GNU long-name table + size_t members = 0; + + while (pos + 60 <= data.size()) { + const char *hdr = reinterpret_cast(&data[pos]); + if (hdr[58] != '`' || hdr[59] != '\n') + die("'" + path + "': corrupt archive member header"); + + char sizeField[11]; + std::memcpy(sizeField, hdr + 48, 10); + sizeField[10] = '\0'; + long msize = std::strtol(sizeField, nullptr, 10); + if (msize < 0) + die("'" + path + "': bad archive member size"); + size_t dataOff = pos + 60; + if (dataOff + static_cast(msize) > data.size()) + die("'" + path + "': archive member overruns file"); + + char nameField[17]; + std::memcpy(nameField, hdr, 16); + nameField[16] = '\0'; + std::string name(nameField); + while (!name.empty() && name.back() == ' ') name.pop_back(); + + bool isObject = true; + if (name == "/" || name == "/SYM64/" || + name == "__.SYMDEF" || name == "__.SYMDEF SORTED") { + isObject = false; // archive symbol index + } else if (name == "//") { + longNames.assign(reinterpret_cast(&data[dataOff]), + static_cast(msize)); + isObject = false; // GNU long-name string table + } else if (name.size() >= 2 && name[0] == '/' && + std::isdigit((unsigned char)name[1])) { + size_t off = static_cast( + std::strtoul(name.c_str() + 1, nullptr, 10)); + name.clear(); // "/" -> resolve in "//" + for (size_t i = off; i < longNames.size() && + longNames[i] != '\n' && longNames[i] != '/'; + ++i) + name.push_back(longNames[i]); + } else if (!name.empty() && name.back() == '/') { + name.pop_back(); // GNU short name "foo.o/" + } + + if (isObject) { + auto o = std::make_unique(); + o->path = path + "(" + name + ")"; + o->raw.assign(data.begin() + dataOff, + data.begin() + dataOff + + static_cast(msize)); + o->parse(); + objs.push_back(std::move(o)); + ++members; + } + + pos = dataOff + static_cast(msize); + if (pos & 1) ++pos; // members are 2-byte aligned + } + if (members == 0) + die("'" + path + "': archive holds no object members"); + } + // Resolve a reloc to (target, name) using the symbol table and the // per-object section base map. Requires link() to have populated // objOff/globalSyms/lastLayout first. Returns false when the