link816 now accepts library archives.

This commit is contained in:
Scott Duensing 2026-07-26 20:28:52 -05:00
parent 5e0547e72c
commit 4a13f9df26
3 changed files with 156 additions and 1 deletions

View file

@ -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

View file

@ -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

View file

@ -1017,14 +1017,95 @@ struct Linker {
// helper of the same name don't collide on insertion.
std::map<std::string, uint32_t> 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<uint8_t> 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<InputObject>();
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<uint8_t> &data) {
size_t pos = 8; // past "!<arch>\n"
std::string longNames; // "//" GNU long-name table
size_t members = 0;
while (pos + 60 <= data.size()) {
const char *hdr = reinterpret_cast<const char *>(&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<size_t>(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<const char *>(&data[dataOff]),
static_cast<size_t>(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<size_t>(
std::strtoul(name.c_str() + 1, nullptr, 10));
name.clear(); // "/<offset>" -> 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<InputObject>();
o->path = path + "(" + name + ")";
o->raw.assign(data.begin() + dataOff,
data.begin() + dataOff +
static_cast<size_t>(msize));
o->parse();
objs.push_back(std::move(o));
++members;
}
pos = dataOff + static_cast<size_t>(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