@scriptc/compiler 0.0.27 → 0.0.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/backend/cc.d.ts +6 -3
- package/dist/backend/cc.js +80 -27
- package/dist/backend/cc.js.map +1 -1
- package/dist/backend/object-localize.d.ts +16 -0
- package/dist/backend/object-localize.js +936 -0
- package/dist/backend/object-localize.js.map +1 -0
- package/dist/backend/object-localize.test.d.ts +1 -0
- package/dist/backend/object-localize.test.js +555 -0
- package/dist/backend/object-localize.test.js.map +1 -0
- package/dist/index.js +28 -7
- package/dist/index.js.map +1 -1
- package/dist/library/profile.js +13 -8
- package/dist/library/profile.js.map +1 -1
- package/package.json +2 -2
- package/surface-manifest.json +1 -1
|
@@ -0,0 +1,936 @@
|
|
|
1
|
+
/* Format-aware symbol localization for multi-instance library mode
|
|
2
|
+
* (abi.localize_runtime) — the in-process half of cc.ts's
|
|
3
|
+
* localizeLibraryObjects. Host toolchains cover the classic pairings
|
|
4
|
+
* (darwin's ld64 does combine+demote in one step; host-linux binutils runs
|
|
5
|
+
* `ld -r` + objcopy), but no portable toolchain performs the COFF
|
|
6
|
+
* equivalent at all — lld-link has no relocatable mode (mirroring MSVC
|
|
7
|
+
* link.exe) and llvm-objcopy rejects symbol-scope flags for COFF — and
|
|
8
|
+
* cross ELF demotion would otherwise require a host binutils/llvm-objcopy
|
|
9
|
+
* that neither `zig cc` nor a stock macOS ships. These transforms close
|
|
10
|
+
* both gaps with no external tool:
|
|
11
|
+
*
|
|
12
|
+
* localizeElfObject demote every defined global/weak symbol outside
|
|
13
|
+
* the keep set to a local symbol in a relocatable
|
|
14
|
+
* ELF64 object (the output of `zig cc -r`), and
|
|
15
|
+
* resolve COMDAT section groups the way binutils'
|
|
16
|
+
* --force-group-allocation does — a group whose
|
|
17
|
+
* signature repeats across archives sharing
|
|
18
|
+
* runtime objects must not be deduplicated against
|
|
19
|
+
* another archive's copy at the embedder's link.
|
|
20
|
+
*
|
|
21
|
+
* mergeAndLocalizeCoffObjects
|
|
22
|
+
* the COFF combine+demote in one pass: pull
|
|
23
|
+
* support objects on undefined-symbol demand (the
|
|
24
|
+
* staging-archive member semantics `ld -r` gives
|
|
25
|
+
* the other formats), concatenate the selected
|
|
26
|
+
* objects' sections, resolve cross-object symbol
|
|
27
|
+
* references by index, then demote every defined
|
|
28
|
+
* external outside the keep set to a static
|
|
29
|
+
* symbol.
|
|
30
|
+
*
|
|
31
|
+
* Shared demotion rule (GNU objcopy --keep-global-symbols semantics):
|
|
32
|
+
* undefined references keep their global binding (the target C/math runtime,
|
|
33
|
+
* system APIs, and sanitizer ABI are the embedder's by design), and COMMON
|
|
34
|
+
* symbols stay global — sanitizer image-registration guards are COMMONs whose
|
|
35
|
+
* merging across archives is the documented single-registration discipline.
|
|
36
|
+
* Windows embedders additionally link advapi32, iphlpapi, and ws2_32. */
|
|
37
|
+
const textDecoder = new TextDecoder();
|
|
38
|
+
const textEncoder = new TextEncoder();
|
|
39
|
+
class ObjectFormatError extends Error {
|
|
40
|
+
constructor(message) {
|
|
41
|
+
super(message);
|
|
42
|
+
this.name = "ObjectFormatError";
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function fail(message) {
|
|
46
|
+
throw new ObjectFormatError(message);
|
|
47
|
+
}
|
|
48
|
+
/* ────────────────────────────── ELF64 ─────────────────────────────────── */
|
|
49
|
+
const SHT_NULL = 0;
|
|
50
|
+
const SHT_SYMTAB = 2;
|
|
51
|
+
const SHT_RELA = 4;
|
|
52
|
+
const SHT_NOBITS = 8;
|
|
53
|
+
const SHT_REL = 9;
|
|
54
|
+
const SHT_GROUP = 17;
|
|
55
|
+
const SHT_SYMTAB_SHNDX = 18;
|
|
56
|
+
const SHT_LLVM_ADDRSIG = 0x6fff4c03;
|
|
57
|
+
const SHF_GROUP = 0x200n;
|
|
58
|
+
const SHN_UNDEF = 0;
|
|
59
|
+
const SHN_LORESERVE = 0xff00;
|
|
60
|
+
const SHN_COMMON = 0xfff2;
|
|
61
|
+
const SHN_XINDEX = 0xffff;
|
|
62
|
+
const STB_LOCAL = 0;
|
|
63
|
+
/** Demote every defined global/weak symbol not named in `keep` to a local
|
|
64
|
+
* symbol in a relocatable ELF64 little-endian object, resolving COMDAT
|
|
65
|
+
* section groups in the same pass. The input is the combined object a
|
|
66
|
+
* relocatable link produced; the output is byte-rebuilt (fresh layout,
|
|
67
|
+
* same section content). */
|
|
68
|
+
export function localizeElfObject(object, keep) {
|
|
69
|
+
const view = new DataView(object.buffer, object.byteOffset, object.byteLength);
|
|
70
|
+
if (object.byteLength < 64 || view.getUint32(0, false) !== 0x7f454c46) {
|
|
71
|
+
fail("not an ELF object");
|
|
72
|
+
}
|
|
73
|
+
if (object[4] !== 2 || object[5] !== 1) {
|
|
74
|
+
fail("unsupported ELF class/encoding (need ELF64, little-endian)");
|
|
75
|
+
}
|
|
76
|
+
if (view.getUint16(16, true) !== 1)
|
|
77
|
+
fail("not a relocatable ELF object (ET_REL)");
|
|
78
|
+
const machine = view.getUint16(18, true);
|
|
79
|
+
const flags = view.getUint32(48, true);
|
|
80
|
+
const shoff = Number(view.getBigUint64(40, true));
|
|
81
|
+
const shentsize = view.getUint16(58, true);
|
|
82
|
+
if (shentsize !== 64)
|
|
83
|
+
fail(`unsupported ELF section header size ${shentsize}`);
|
|
84
|
+
let shnum = view.getUint16(60, true);
|
|
85
|
+
let shstrndx = view.getUint16(62, true);
|
|
86
|
+
if (shnum === 0)
|
|
87
|
+
shnum = Number(view.getBigUint64(shoff + 32, true));
|
|
88
|
+
if (shstrndx === SHN_XINDEX)
|
|
89
|
+
shstrndx = view.getUint32(shoff + 40, true);
|
|
90
|
+
const sections = [];
|
|
91
|
+
for (let i = 0; i < shnum; i++) {
|
|
92
|
+
const at = shoff + i * 64;
|
|
93
|
+
const type = view.getUint32(at + 4, true);
|
|
94
|
+
const offset = Number(view.getBigUint64(at + 24, true));
|
|
95
|
+
const size = view.getBigUint64(at + 32, true);
|
|
96
|
+
sections.push({
|
|
97
|
+
nameOffset: view.getUint32(at, true),
|
|
98
|
+
type,
|
|
99
|
+
flags: view.getBigUint64(at + 8, true),
|
|
100
|
+
addr: view.getBigUint64(at + 16, true),
|
|
101
|
+
size,
|
|
102
|
+
link: view.getUint32(at + 40, true),
|
|
103
|
+
info: view.getUint32(at + 44, true),
|
|
104
|
+
addralign: view.getBigUint64(at + 48, true),
|
|
105
|
+
entsize: view.getBigUint64(at + 56, true),
|
|
106
|
+
data: type === SHT_NOBITS || type === SHT_NULL
|
|
107
|
+
? new Uint8Array(0)
|
|
108
|
+
: object.subarray(offset, offset + Number(size)),
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
const symtabIndex = sections.findIndex((s) => s.type === SHT_SYMTAB);
|
|
112
|
+
if (symtabIndex < 0)
|
|
113
|
+
fail("ELF object has no symbol table");
|
|
114
|
+
const symtab = sections[symtabIndex];
|
|
115
|
+
const strtab = sections[symtab.link];
|
|
116
|
+
if (strtab === undefined)
|
|
117
|
+
fail("ELF symbol table names an invalid string table");
|
|
118
|
+
// SHT_SYMTAB_SHNDX (only present when section indices overflow st_shndx):
|
|
119
|
+
// resolve the real indices up front and drop the section — the rebuilt
|
|
120
|
+
// object's section count only shrinks, so inline indices always fit.
|
|
121
|
+
const shndxSection = sections.find((s) => s.type === SHT_SYMTAB_SHNDX && sections[s.link] === symtab);
|
|
122
|
+
const shndxView = shndxSection === undefined
|
|
123
|
+
? null
|
|
124
|
+
: new DataView(shndxSection.data.buffer, shndxSection.data.byteOffset, shndxSection.data.byteLength);
|
|
125
|
+
const symbolName = (offset) => {
|
|
126
|
+
const bytes = strtab.data;
|
|
127
|
+
let end = offset;
|
|
128
|
+
while (end < bytes.length && bytes[end] !== 0)
|
|
129
|
+
end++;
|
|
130
|
+
return textDecoder.decode(bytes.subarray(offset, end));
|
|
131
|
+
};
|
|
132
|
+
const symCount = Number(symtab.size) / 24;
|
|
133
|
+
const symView = new DataView(symtab.data.buffer, symtab.data.byteOffset, symtab.data.byteLength);
|
|
134
|
+
const symbols = [];
|
|
135
|
+
for (let i = 0; i < symCount; i++) {
|
|
136
|
+
const at = i * 24;
|
|
137
|
+
let shndx = symView.getUint16(at + 6, true);
|
|
138
|
+
if (shndx === SHN_XINDEX) {
|
|
139
|
+
if (shndxView === null)
|
|
140
|
+
fail("ELF symbol uses SHN_XINDEX without a SHT_SYMTAB_SHNDX table");
|
|
141
|
+
shndx = shndxView.getUint32(i * 4, true);
|
|
142
|
+
}
|
|
143
|
+
symbols.push({
|
|
144
|
+
nameOffset: symView.getUint32(at, true),
|
|
145
|
+
name: symbolName(symView.getUint32(at, true)),
|
|
146
|
+
info: symView.getUint8(at + 4),
|
|
147
|
+
other: symView.getUint8(at + 5),
|
|
148
|
+
shndx,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
// Section groups resolve away (the --force-group-allocation discipline),
|
|
152
|
+
// and .llvm_addrsig drops — its symbol indices go stale under the symbol
|
|
153
|
+
// reorder below, and its only consumer (ICF safety analysis) treats
|
|
154
|
+
// absence conservatively.
|
|
155
|
+
const sectionKept = sections.map((s) => s.type !== SHT_GROUP &&
|
|
156
|
+
s.type !== SHT_LLVM_ADDRSIG &&
|
|
157
|
+
!(shndxSection !== undefined && s === shndxSection));
|
|
158
|
+
const sectionMap = [];
|
|
159
|
+
{
|
|
160
|
+
let next = 0;
|
|
161
|
+
for (let i = 0; i < sections.length; i++)
|
|
162
|
+
sectionMap.push(sectionKept[i] === true ? next++ : -1);
|
|
163
|
+
}
|
|
164
|
+
for (let i = 0; i < sections.length; i++) {
|
|
165
|
+
if (sectionKept[i] === true)
|
|
166
|
+
sections[i].flags &= ~SHF_GROUP;
|
|
167
|
+
}
|
|
168
|
+
// Demote and drop decisions per symbol. A symbol anchored to a dropped
|
|
169
|
+
// section (a group's own section symbol) leaves with it; nothing may
|
|
170
|
+
// still reference it afterwards.
|
|
171
|
+
const symbolKept = [];
|
|
172
|
+
for (let i = 0; i < symCount; i++) {
|
|
173
|
+
const sym = symbols[i];
|
|
174
|
+
const regular = sym.shndx > SHN_UNDEF && sym.shndx < SHN_LORESERVE;
|
|
175
|
+
symbolKept.push(!(regular && sectionKept[sym.shndx] !== true));
|
|
176
|
+
if (i === 0)
|
|
177
|
+
continue;
|
|
178
|
+
const binding = sym.info >> 4;
|
|
179
|
+
const defined = sym.shndx !== SHN_UNDEF && sym.shndx !== SHN_COMMON;
|
|
180
|
+
if (binding !== STB_LOCAL && defined && !keep.has(sym.name)) {
|
|
181
|
+
sym.info = (STB_LOCAL << 4) | (sym.info & 0xf);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// Reorder: locals first (the spec's symbol-table contract), stable within
|
|
185
|
+
// each half, index 0 pinned.
|
|
186
|
+
const localOrder = [];
|
|
187
|
+
const globalOrder = [];
|
|
188
|
+
for (let i = 0; i < symCount; i++) {
|
|
189
|
+
if (symbolKept[i] !== true)
|
|
190
|
+
continue;
|
|
191
|
+
((symbols[i].info >> 4) === STB_LOCAL ? localOrder : globalOrder).push(i);
|
|
192
|
+
}
|
|
193
|
+
const symbolOrder = [...localOrder, ...globalOrder];
|
|
194
|
+
const symbolMap = new Array(symCount).fill(-1);
|
|
195
|
+
symbolOrder.forEach((oldIndex, newIndex) => (symbolMap[oldIndex] = newIndex));
|
|
196
|
+
const newSymtabData = new Uint8Array(symbolOrder.length * 24);
|
|
197
|
+
const newSymtabView = new DataView(newSymtabData.buffer);
|
|
198
|
+
symbolOrder.forEach((oldIndex, newIndex) => {
|
|
199
|
+
const sym = symbols[oldIndex];
|
|
200
|
+
const at = newIndex * 24;
|
|
201
|
+
newSymtabView.setUint32(at, sym.nameOffset, true);
|
|
202
|
+
newSymtabView.setUint8(at + 4, sym.info);
|
|
203
|
+
newSymtabView.setUint8(at + 5, sym.other);
|
|
204
|
+
const shndx = sym.shndx > SHN_UNDEF && sym.shndx < SHN_LORESERVE ? sectionMap[sym.shndx] : sym.shndx;
|
|
205
|
+
newSymtabView.setUint16(at + 6, shndx, true);
|
|
206
|
+
newSymtabView.setBigUint64(at + 8, symView.getBigUint64(oldIndex * 24 + 8, true), true);
|
|
207
|
+
newSymtabView.setBigUint64(at + 16, symView.getBigUint64(oldIndex * 24 + 16, true), true);
|
|
208
|
+
});
|
|
209
|
+
symtab.data = newSymtabData;
|
|
210
|
+
symtab.size = BigInt(newSymtabData.length);
|
|
211
|
+
symtab.info = localOrder.length;
|
|
212
|
+
// Relocation sections against this symbol table: remap symbol indices.
|
|
213
|
+
for (const section of sections) {
|
|
214
|
+
if ((section.type !== SHT_RELA && section.type !== SHT_REL) || section.link !== symtabIndex) {
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
const entry = section.type === SHT_RELA ? 24 : 16;
|
|
218
|
+
const data = new Uint8Array(section.data); // private copy — inputs may be shared
|
|
219
|
+
const relView = new DataView(data.buffer);
|
|
220
|
+
for (let at = 0; at + entry <= data.length; at += entry) {
|
|
221
|
+
const info = relView.getBigUint64(at + 8, true);
|
|
222
|
+
const oldSym = Number(info >> 32n);
|
|
223
|
+
if (oldSym === 0)
|
|
224
|
+
continue;
|
|
225
|
+
const newSym = symbolMap[oldSym];
|
|
226
|
+
if (newSym === undefined || newSym < 0) {
|
|
227
|
+
fail("ELF relocation references a symbol dropped with its section group");
|
|
228
|
+
}
|
|
229
|
+
relView.setBigUint64(at + 8, (BigInt(newSym) << 32n) | (info & 0xffffffffn), true);
|
|
230
|
+
}
|
|
231
|
+
section.data = data;
|
|
232
|
+
}
|
|
233
|
+
// Rebuild. sh_link is a section index in every use this object can carry;
|
|
234
|
+
// sh_info is one only on relocation sections.
|
|
235
|
+
const kept = sections.filter((_, i) => sectionKept[i] === true);
|
|
236
|
+
let offset = 64;
|
|
237
|
+
const offsets = [];
|
|
238
|
+
for (const section of kept) {
|
|
239
|
+
if (section.type === SHT_NULL || section.type === SHT_NOBITS) {
|
|
240
|
+
offsets.push(0);
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
const align = Number(section.addralign > 1n ? section.addralign : 1n);
|
|
244
|
+
offset = Math.ceil(offset / align) * align;
|
|
245
|
+
offsets.push(offset);
|
|
246
|
+
offset += section.data.length;
|
|
247
|
+
}
|
|
248
|
+
const shoffOut = Math.ceil(offset / 8) * 8;
|
|
249
|
+
const out = new Uint8Array(shoffOut + kept.length * 64);
|
|
250
|
+
const outView = new DataView(out.buffer);
|
|
251
|
+
out.set(object.subarray(0, 64));
|
|
252
|
+
outView.setBigUint64(24, 0n, true); // e_entry
|
|
253
|
+
outView.setBigUint64(32, 0n, true); // e_phoff
|
|
254
|
+
outView.setUint16(18, machine, true);
|
|
255
|
+
outView.setUint32(48, flags, true);
|
|
256
|
+
outView.setBigUint64(40, BigInt(shoffOut), true);
|
|
257
|
+
outView.setUint16(54, 0, true); // e_phentsize
|
|
258
|
+
outView.setUint16(56, 0, true); // e_phnum
|
|
259
|
+
outView.setUint16(60, kept.length, true);
|
|
260
|
+
outView.setUint16(62, sectionMap[shstrndx], true);
|
|
261
|
+
kept.forEach((section, i) => {
|
|
262
|
+
if (section.type !== SHT_NULL && section.type !== SHT_NOBITS) {
|
|
263
|
+
out.set(section.data, offsets[i]);
|
|
264
|
+
}
|
|
265
|
+
const at = shoffOut + i * 64;
|
|
266
|
+
outView.setUint32(at, section.nameOffset, true);
|
|
267
|
+
outView.setUint32(at + 4, section.type, true);
|
|
268
|
+
outView.setBigUint64(at + 8, section.flags, true);
|
|
269
|
+
outView.setBigUint64(at + 16, section.addr, true);
|
|
270
|
+
outView.setBigUint64(at + 24, BigInt(offsets[i]), true);
|
|
271
|
+
outView.setBigUint64(at + 32, section.type === SHT_NOBITS ? section.size : BigInt(section.data.length), true);
|
|
272
|
+
outView.setUint32(at + 40, section.link === 0 ? 0 : sectionMap[section.link] ?? 0, true);
|
|
273
|
+
outView.setUint32(at + 44, section.type === SHT_RELA || section.type === SHT_REL
|
|
274
|
+
? sectionMap[section.info] ?? 0
|
|
275
|
+
: section === symtab
|
|
276
|
+
? symtab.info
|
|
277
|
+
: section.info, true);
|
|
278
|
+
outView.setBigUint64(at + 48, section.addralign, true);
|
|
279
|
+
outView.setBigUint64(at + 56, section.entsize, true);
|
|
280
|
+
});
|
|
281
|
+
return out;
|
|
282
|
+
}
|
|
283
|
+
/* ────────────────────────────── COFF ──────────────────────────────────── */
|
|
284
|
+
const IMAGE_FILE_MACHINE_AMD64 = 0x8664;
|
|
285
|
+
const IMAGE_SCN_LNK_NRELOC_OVFL = 0x01000000;
|
|
286
|
+
const IMAGE_SCN_LNK_COMDAT = 0x00001000;
|
|
287
|
+
const IMAGE_SYM_UNDEFINED = 0;
|
|
288
|
+
const IMAGE_SYM_ABSOLUTE = -1;
|
|
289
|
+
const IMAGE_SYM_CLASS_EXTERNAL = 2;
|
|
290
|
+
const IMAGE_SYM_CLASS_STATIC = 3;
|
|
291
|
+
const IMAGE_SYM_CLASS_SECTION = 104;
|
|
292
|
+
const IMAGE_SYM_CLASS_WEAK_EXTERNAL = 105;
|
|
293
|
+
const IMAGE_COMDAT_SELECT_NODUPLICATES = 1;
|
|
294
|
+
const IMAGE_COMDAT_SELECT_ANY = 2;
|
|
295
|
+
const IMAGE_COMDAT_SELECT_SAME_SIZE = 3;
|
|
296
|
+
const IMAGE_COMDAT_SELECT_EXACT_MATCH = 4;
|
|
297
|
+
const IMAGE_COMDAT_SELECT_ASSOCIATIVE = 5;
|
|
298
|
+
const IMAGE_COMDAT_SELECT_LARGEST = 6;
|
|
299
|
+
function readCString(bytes, offset) {
|
|
300
|
+
let end = offset;
|
|
301
|
+
while (end < bytes.length && bytes[end] !== 0)
|
|
302
|
+
end++;
|
|
303
|
+
return textDecoder.decode(bytes.subarray(offset, end));
|
|
304
|
+
}
|
|
305
|
+
function parseCoff(object, label) {
|
|
306
|
+
const view = new DataView(object.buffer, object.byteOffset, object.byteLength);
|
|
307
|
+
if (object.byteLength < 20)
|
|
308
|
+
fail(`${label}: not a COFF object`);
|
|
309
|
+
const machine = view.getUint16(0, true);
|
|
310
|
+
if (machine === 0 && view.getUint16(2, true) === 0xffff) {
|
|
311
|
+
fail(`${label}: bigobj/import-descriptor COFF members are not supported here`);
|
|
312
|
+
}
|
|
313
|
+
const sectionCount = view.getUint16(2, true);
|
|
314
|
+
const symtabOffset = view.getUint32(8, true);
|
|
315
|
+
const symbolCount = view.getUint32(12, true);
|
|
316
|
+
if (view.getUint16(16, true) !== 0)
|
|
317
|
+
fail(`${label}: unexpected optional header in a COFF object`);
|
|
318
|
+
const strtabOffset = symtabOffset + symbolCount * 18;
|
|
319
|
+
const strtab = object.subarray(strtabOffset);
|
|
320
|
+
const sectionName = (raw) => {
|
|
321
|
+
if (raw[0] === 0x2f /* '/' */) {
|
|
322
|
+
const spelled = textDecoder.decode(raw.subarray(1)).replace(/\0+$/, "").trim();
|
|
323
|
+
const offset = Number.parseInt(spelled, 10);
|
|
324
|
+
if (!Number.isFinite(offset))
|
|
325
|
+
fail(`${label}: malformed long section name`);
|
|
326
|
+
return readCString(strtab, offset);
|
|
327
|
+
}
|
|
328
|
+
let end = 0;
|
|
329
|
+
while (end < 8 && raw[end] !== 0)
|
|
330
|
+
end++;
|
|
331
|
+
return textDecoder.decode(raw.subarray(0, end));
|
|
332
|
+
};
|
|
333
|
+
const sections = [];
|
|
334
|
+
for (let i = 0; i < sectionCount; i++) {
|
|
335
|
+
const at = 20 + i * 40;
|
|
336
|
+
const rawSize = view.getUint32(at + 16, true);
|
|
337
|
+
const rawPointer = view.getUint32(at + 20, true);
|
|
338
|
+
const relocPointer = view.getUint32(at + 24, true);
|
|
339
|
+
let relocCount = view.getUint16(at + 32, true);
|
|
340
|
+
const characteristics = view.getUint32(at + 36, true);
|
|
341
|
+
let relocAt = relocPointer;
|
|
342
|
+
if ((characteristics & IMAGE_SCN_LNK_NRELOC_OVFL) !== 0 && relocCount === 0xffff) {
|
|
343
|
+
relocCount = view.getUint32(relocPointer, true) - 1;
|
|
344
|
+
relocAt += 10;
|
|
345
|
+
}
|
|
346
|
+
const relocs = [];
|
|
347
|
+
for (let r = 0; r < relocCount; r++) {
|
|
348
|
+
const rAt = relocAt + r * 10;
|
|
349
|
+
relocs.push({
|
|
350
|
+
virtualAddress: view.getUint32(rAt, true),
|
|
351
|
+
symbolIndex: view.getUint32(rAt + 4, true),
|
|
352
|
+
type: view.getUint16(rAt + 8, true),
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
sections.push({
|
|
356
|
+
name: sectionName(object.subarray(at, at + 8)),
|
|
357
|
+
virtualSize: view.getUint32(at + 8, true),
|
|
358
|
+
virtualAddress: view.getUint32(at + 12, true),
|
|
359
|
+
characteristics,
|
|
360
|
+
data: rawPointer === 0 ? null : object.subarray(rawPointer, rawPointer + rawSize),
|
|
361
|
+
rawSize,
|
|
362
|
+
relocs,
|
|
363
|
+
comdatSelection: 0,
|
|
364
|
+
comdatAssoc: 0,
|
|
365
|
+
comdatLeader: -1,
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
const symbols = [];
|
|
369
|
+
const primaryByRaw = new Map();
|
|
370
|
+
const rawByPrimary = [];
|
|
371
|
+
for (let i = 0; i < symbolCount;) {
|
|
372
|
+
const at = symtabOffset + i * 18;
|
|
373
|
+
const nameIsLong = view.getUint32(at, true) === 0;
|
|
374
|
+
const name = nameIsLong
|
|
375
|
+
? readCString(strtab, view.getUint32(at + 4, true))
|
|
376
|
+
: (() => {
|
|
377
|
+
const raw = object.subarray(at, at + 8);
|
|
378
|
+
let end = 0;
|
|
379
|
+
while (end < 8 && raw[end] !== 0)
|
|
380
|
+
end++;
|
|
381
|
+
return textDecoder.decode(raw.subarray(0, end));
|
|
382
|
+
})();
|
|
383
|
+
const auxCount = view.getUint8(at + 17);
|
|
384
|
+
primaryByRaw.set(i, symbols.length);
|
|
385
|
+
rawByPrimary.push(i);
|
|
386
|
+
symbols.push({
|
|
387
|
+
name,
|
|
388
|
+
value: view.getUint32(at + 8, true),
|
|
389
|
+
sectionNumber: view.getInt16(at + 12, true),
|
|
390
|
+
type: view.getUint16(at + 14, true),
|
|
391
|
+
storageClass: view.getUint8(at + 16),
|
|
392
|
+
aux: object.slice(at + 18, at + 18 + auxCount * 18),
|
|
393
|
+
});
|
|
394
|
+
i += 1 + auxCount;
|
|
395
|
+
}
|
|
396
|
+
// COMDAT metadata rides the section symbol's aux record (selection,
|
|
397
|
+
// associated section) and the record after it (the COMDAT symbol — the
|
|
398
|
+
// linker's deduplication name).
|
|
399
|
+
for (let sectionIndex = 1; sectionIndex <= sections.length; sectionIndex++) {
|
|
400
|
+
const section = sections[sectionIndex - 1];
|
|
401
|
+
if ((section.characteristics & IMAGE_SCN_LNK_COMDAT) === 0)
|
|
402
|
+
continue;
|
|
403
|
+
let sawSectionSymbol = false;
|
|
404
|
+
for (let primary = 0; primary < symbols.length; primary++) {
|
|
405
|
+
const sym = symbols[primary];
|
|
406
|
+
if (sym.sectionNumber !== sectionIndex)
|
|
407
|
+
continue;
|
|
408
|
+
if (!sawSectionSymbol && sym.value === 0 && sym.name === section.name && sym.aux.length >= 18) {
|
|
409
|
+
sawSectionSymbol = true;
|
|
410
|
+
const auxView = new DataView(sym.aux.buffer, sym.aux.byteOffset, sym.aux.byteLength);
|
|
411
|
+
section.comdatSelection = auxView.getUint8(14);
|
|
412
|
+
section.comdatAssoc = auxView.getUint16(12, true);
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
section.comdatLeader = primary;
|
|
416
|
+
break;
|
|
417
|
+
}
|
|
418
|
+
if (!sawSectionSymbol)
|
|
419
|
+
fail(`${label}: COMDAT section ${section.name} has no section symbol`);
|
|
420
|
+
}
|
|
421
|
+
return { machine, sections, symbols, primaryByRaw, rawByPrimary, label };
|
|
422
|
+
}
|
|
423
|
+
function coffDefines(sym) {
|
|
424
|
+
return ((sym.storageClass === IMAGE_SYM_CLASS_EXTERNAL &&
|
|
425
|
+
(sym.sectionNumber > 0 || sym.sectionNumber === IMAGE_SYM_ABSOLUTE)) ||
|
|
426
|
+
coffIsCommon(sym));
|
|
427
|
+
}
|
|
428
|
+
function coffIsCommon(sym) {
|
|
429
|
+
return (sym.storageClass === IMAGE_SYM_CLASS_EXTERNAL &&
|
|
430
|
+
sym.sectionNumber === IMAGE_SYM_UNDEFINED &&
|
|
431
|
+
sym.value > 0);
|
|
432
|
+
}
|
|
433
|
+
function coffIsUndefined(sym) {
|
|
434
|
+
return ((sym.storageClass === IMAGE_SYM_CLASS_EXTERNAL &&
|
|
435
|
+
sym.sectionNumber === IMAGE_SYM_UNDEFINED &&
|
|
436
|
+
sym.value === 0) ||
|
|
437
|
+
sym.storageClass === IMAGE_SYM_CLASS_WEAK_EXTERNAL);
|
|
438
|
+
}
|
|
439
|
+
function coffSectionContentsEqual(a, b) {
|
|
440
|
+
if (a.rawSize !== b.rawSize)
|
|
441
|
+
return false;
|
|
442
|
+
if (a.data === null || b.data === null)
|
|
443
|
+
return a.data === b.data;
|
|
444
|
+
if (a.data.length !== b.data.length)
|
|
445
|
+
return false;
|
|
446
|
+
for (let i = 0; i < a.data.length; i++) {
|
|
447
|
+
if (a.data[i] !== b.data[i])
|
|
448
|
+
return false;
|
|
449
|
+
}
|
|
450
|
+
return true;
|
|
451
|
+
}
|
|
452
|
+
/** Combine a program object with the support objects it (transitively)
|
|
453
|
+
* needs into ONE COFF object, then demote every defined external outside
|
|
454
|
+
* `keep` to a static symbol. Support objects join on undefined-symbol
|
|
455
|
+
* demand — the staging-archive member semantics the other formats get from
|
|
456
|
+
* `ld -r` — so an unused member's undefined references never reach the
|
|
457
|
+
* embedder's link. x86_64 only (the one COFF target scriptc produces). */
|
|
458
|
+
export function mergeAndLocalizeCoffObjects(program, support, keep, labels) {
|
|
459
|
+
const objects = [
|
|
460
|
+
parseCoff(program, labels?.program ?? "program object"),
|
|
461
|
+
...support.map((bytes, i) => parseCoff(bytes, labels?.support?.[i] ?? `support object ${i}`)),
|
|
462
|
+
];
|
|
463
|
+
for (const object of objects) {
|
|
464
|
+
if (object.machine !== IMAGE_FILE_MACHINE_AMD64) {
|
|
465
|
+
fail(`${object.label}: unsupported COFF machine 0x${object.machine.toString(16)}`);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
// Member selection: archive semantics over the support list. A definition
|
|
469
|
+
// (including a COMMON) satisfies an undefined reference; first definer in
|
|
470
|
+
// list order wins the pull, matching `ar` member order.
|
|
471
|
+
const definers = new Map();
|
|
472
|
+
objects.forEach((object, index) => {
|
|
473
|
+
if (index === 0)
|
|
474
|
+
return;
|
|
475
|
+
for (const sym of object.symbols) {
|
|
476
|
+
if (!coffDefines(sym))
|
|
477
|
+
continue;
|
|
478
|
+
const list = definers.get(sym.name);
|
|
479
|
+
if (list === undefined)
|
|
480
|
+
definers.set(sym.name, [index]);
|
|
481
|
+
else
|
|
482
|
+
list.push(index);
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
const included = objects.map((_, i) => i === 0);
|
|
486
|
+
// Archive extraction consults the linker's CURRENT symbol state: an
|
|
487
|
+
// undefined in a newly pulled member is already satisfied when the
|
|
488
|
+
// program object (or an earlier member) defines it. Record every
|
|
489
|
+
// selected definition as soon as its member joins, before that member's
|
|
490
|
+
// own undefineds are considered. Without this set, a support member can
|
|
491
|
+
// spuriously pull an alternate definition that a real archive link would
|
|
492
|
+
// leave untouched, and the merge later reports a false duplicate.
|
|
493
|
+
const selectedDefinitions = new Set();
|
|
494
|
+
const addDefinitions = (object) => {
|
|
495
|
+
for (const sym of object.symbols) {
|
|
496
|
+
if (coffDefines(sym))
|
|
497
|
+
selectedDefinitions.add(sym.name);
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
addDefinitions(objects[0]);
|
|
501
|
+
const queue = [0];
|
|
502
|
+
while (queue.length > 0) {
|
|
503
|
+
const object = objects[queue.shift()];
|
|
504
|
+
for (const sym of object.symbols) {
|
|
505
|
+
if (!coffIsUndefined(sym))
|
|
506
|
+
continue;
|
|
507
|
+
if (selectedDefinitions.has(sym.name))
|
|
508
|
+
continue;
|
|
509
|
+
for (const candidate of definers.get(sym.name) ?? []) {
|
|
510
|
+
if (included[candidate] !== true) {
|
|
511
|
+
included[candidate] = true;
|
|
512
|
+
addDefinitions(objects[candidate]);
|
|
513
|
+
queue.push(candidate);
|
|
514
|
+
}
|
|
515
|
+
break;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
const selected = objects.filter((_, i) => included[i] === true);
|
|
520
|
+
// COMDAT resolution, the ELF arm's --force-group-allocation analog:
|
|
521
|
+
// resolve duplicate names inside THIS merge according to their selection
|
|
522
|
+
// contract (mingw's .refptr.<name> stubs use ANY), then clear the COMDAT
|
|
523
|
+
// flag on the survivors at emission. A section whose deduplication name
|
|
524
|
+
// repeats across archives sharing runtime objects must not be deduplicated
|
|
525
|
+
// against another archive's copy at the embedder's link, where its
|
|
526
|
+
// relocations bind that archive's private (demoted) definitions.
|
|
527
|
+
const sectionDropped = new Map();
|
|
528
|
+
const comdatKept = new Map();
|
|
529
|
+
for (const object of selected) {
|
|
530
|
+
const dropped = object.sections.map(() => false);
|
|
531
|
+
sectionDropped.set(object, dropped);
|
|
532
|
+
object.sections.forEach((section, i) => {
|
|
533
|
+
// .llvm_addrsig: its ULEB symbol indices go stale under the merge and
|
|
534
|
+
// its absence is read conservatively. .debug$S/.debug$T (CodeView):
|
|
535
|
+
// item indices resolve against the OWNING object's .debug$T, a
|
|
536
|
+
// per-object contract no single merged object can express — the
|
|
537
|
+
// localized member carries no CodeView, like an objcopy'd ELF member
|
|
538
|
+
// carries its DWARF untouched but a stripped one loses it.
|
|
539
|
+
if (section.name === ".llvm_addrsig" || section.name.startsWith(".debug$")) {
|
|
540
|
+
dropped[i] = true;
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
for (const object of selected) {
|
|
545
|
+
const dropped = sectionDropped.get(object);
|
|
546
|
+
object.sections.forEach((section, i) => {
|
|
547
|
+
if (dropped[i] === true || (section.characteristics & IMAGE_SCN_LNK_COMDAT) === 0)
|
|
548
|
+
return;
|
|
549
|
+
if (section.comdatSelection === IMAGE_COMDAT_SELECT_ASSOCIATIVE)
|
|
550
|
+
return;
|
|
551
|
+
if (section.comdatSelection !== IMAGE_COMDAT_SELECT_NODUPLICATES &&
|
|
552
|
+
section.comdatSelection !== IMAGE_COMDAT_SELECT_ANY &&
|
|
553
|
+
section.comdatSelection !== IMAGE_COMDAT_SELECT_SAME_SIZE &&
|
|
554
|
+
section.comdatSelection !== IMAGE_COMDAT_SELECT_EXACT_MATCH &&
|
|
555
|
+
section.comdatSelection !== IMAGE_COMDAT_SELECT_LARGEST) {
|
|
556
|
+
fail(`${object.label}: COMDAT section ${section.name} has unsupported selection ${section.comdatSelection}`);
|
|
557
|
+
}
|
|
558
|
+
const leader = section.comdatLeader >= 0 ? object.symbols[section.comdatLeader] : undefined;
|
|
559
|
+
if (leader === undefined || leader.storageClass !== IMAGE_SYM_CLASS_EXTERNAL) {
|
|
560
|
+
fail(`${object.label}: COMDAT section ${section.name} has no external leader symbol`);
|
|
561
|
+
}
|
|
562
|
+
const existing = comdatKept.get(leader.name);
|
|
563
|
+
if (existing === undefined) {
|
|
564
|
+
comdatKept.set(leader.name, { object, index: i });
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
const existingSection = existing.object.sections[existing.index];
|
|
568
|
+
if (section.comdatSelection !== existingSection.comdatSelection) {
|
|
569
|
+
fail(`conflicting COMDAT selections for ${leader.name} ` +
|
|
570
|
+
`(${existing.object.label}: ${existingSection.comdatSelection}, ${object.label}: ${section.comdatSelection})`);
|
|
571
|
+
}
|
|
572
|
+
if (section.comdatSelection === IMAGE_COMDAT_SELECT_NODUPLICATES) {
|
|
573
|
+
fail(`duplicate IMAGE_COMDAT_SELECT_NODUPLICATES section ${section.name} (${existing.object.label} and ${object.label})`);
|
|
574
|
+
}
|
|
575
|
+
if (section.comdatSelection === IMAGE_COMDAT_SELECT_SAME_SIZE &&
|
|
576
|
+
section.rawSize !== existingSection.rawSize) {
|
|
577
|
+
fail(`IMAGE_COMDAT_SELECT_SAME_SIZE mismatch for ${leader.name} ` +
|
|
578
|
+
`(${existing.object.label}: ${existingSection.rawSize} bytes, ${object.label}: ${section.rawSize} bytes)`);
|
|
579
|
+
}
|
|
580
|
+
if (section.comdatSelection === IMAGE_COMDAT_SELECT_EXACT_MATCH &&
|
|
581
|
+
!coffSectionContentsEqual(existingSection, section)) {
|
|
582
|
+
fail(`IMAGE_COMDAT_SELECT_EXACT_MATCH mismatch for ${leader.name} ` +
|
|
583
|
+
`(${existing.object.label} and ${object.label})`);
|
|
584
|
+
}
|
|
585
|
+
if (section.comdatSelection === IMAGE_COMDAT_SELECT_LARGEST &&
|
|
586
|
+
section.rawSize > existingSection.rawSize) {
|
|
587
|
+
sectionDropped.get(existing.object)[existing.index] = true;
|
|
588
|
+
comdatKept.set(leader.name, { object, index: i });
|
|
589
|
+
}
|
|
590
|
+
else {
|
|
591
|
+
dropped[i] = true;
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
// Associative sections follow their target's final fate. Resolve these
|
|
596
|
+
// only after every non-associative winner is known: LARGEST can replace a
|
|
597
|
+
// section seen in an earlier object, whose associates must then leave too.
|
|
598
|
+
for (const object of selected) {
|
|
599
|
+
const dropped = sectionDropped.get(object);
|
|
600
|
+
object.sections.forEach((section, i) => {
|
|
601
|
+
if (dropped[i] === true || section.comdatSelection !== IMAGE_COMDAT_SELECT_ASSOCIATIVE)
|
|
602
|
+
return;
|
|
603
|
+
const seen = new Set([i]);
|
|
604
|
+
let target = section.comdatAssoc - 1;
|
|
605
|
+
while (true) {
|
|
606
|
+
if (target < 0 || target >= object.sections.length) {
|
|
607
|
+
fail(`${object.label}: associative COMDAT ${section.name} names an invalid section`);
|
|
608
|
+
}
|
|
609
|
+
if (seen.has(target)) {
|
|
610
|
+
fail(`${object.label}: associative COMDAT ${section.name} contains an association cycle`);
|
|
611
|
+
}
|
|
612
|
+
seen.add(target);
|
|
613
|
+
const targetSection = object.sections[target];
|
|
614
|
+
if ((targetSection.characteristics & IMAGE_SCN_LNK_COMDAT) === 0) {
|
|
615
|
+
fail(`${object.label}: associative COMDAT ${section.name} targets a non-COMDAT section`);
|
|
616
|
+
}
|
|
617
|
+
if (dropped[target] === true) {
|
|
618
|
+
dropped[i] = true;
|
|
619
|
+
break;
|
|
620
|
+
}
|
|
621
|
+
if (targetSection.comdatSelection !== IMAGE_COMDAT_SELECT_ASSOCIATIVE)
|
|
622
|
+
break;
|
|
623
|
+
target = targetSection.comdatAssoc - 1;
|
|
624
|
+
}
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
const canonical = new Map();
|
|
628
|
+
for (const object of selected) {
|
|
629
|
+
const dropped = sectionDropped.get(object);
|
|
630
|
+
for (const sym of object.symbols) {
|
|
631
|
+
if (sym.storageClass !== IMAGE_SYM_CLASS_EXTERNAL)
|
|
632
|
+
continue;
|
|
633
|
+
if (sym.sectionNumber > 0 && dropped[sym.sectionNumber - 1] === true)
|
|
634
|
+
continue;
|
|
635
|
+
const existing = canonical.get(sym.name);
|
|
636
|
+
if (coffDefines(sym) && !coffIsCommon(sym)) {
|
|
637
|
+
if (existing?.kind === "defined") {
|
|
638
|
+
fail(`duplicate external symbol ${sym.name} (${existing.definer ?? "?"} and ${object.label})`);
|
|
639
|
+
}
|
|
640
|
+
canonical.set(sym.name, {
|
|
641
|
+
kind: "defined",
|
|
642
|
+
definer: object.label,
|
|
643
|
+
commonSize: 0,
|
|
644
|
+
outIndex: -1,
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
else if (coffIsCommon(sym)) {
|
|
648
|
+
if (existing === undefined || existing.kind === "undefined") {
|
|
649
|
+
canonical.set(sym.name, { kind: "common", commonSize: sym.value, outIndex: -1 });
|
|
650
|
+
}
|
|
651
|
+
else if (existing.kind === "common" && sym.value > existing.commonSize) {
|
|
652
|
+
existing.commonSize = sym.value;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
else if (existing === undefined) {
|
|
656
|
+
canonical.set(sym.name, { kind: "undefined", commonSize: 0, outIndex: -1 });
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
// A weak external's resolution happens at the embedder's link; a weak
|
|
661
|
+
// reference to a name this pass demotes would dangle there. scriptc's
|
|
662
|
+
// toolchains emit no weak externals in these objects — refuse rather than
|
|
663
|
+
// approximate if one ever appears against a demoted name.
|
|
664
|
+
for (const object of selected) {
|
|
665
|
+
for (const sym of object.symbols) {
|
|
666
|
+
if (sym.storageClass !== IMAGE_SYM_CLASS_WEAK_EXTERNAL)
|
|
667
|
+
continue;
|
|
668
|
+
const target = canonical.get(sym.name);
|
|
669
|
+
if (target?.kind === "defined" && !keep.has(sym.name)) {
|
|
670
|
+
fail(`${object.label}: weak external ${sym.name} would dangle after localization`);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
const outSections = [];
|
|
675
|
+
const sectionMaps = new Map();
|
|
676
|
+
for (const object of selected) {
|
|
677
|
+
const map = [0];
|
|
678
|
+
const dropped = sectionDropped.get(object);
|
|
679
|
+
object.sections.forEach((section, i) => {
|
|
680
|
+
if (dropped[i] === true) {
|
|
681
|
+
map.push(0);
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
outSections.push({ section, object });
|
|
685
|
+
map.push(outSections.length);
|
|
686
|
+
});
|
|
687
|
+
sectionMaps.set(object, map);
|
|
688
|
+
}
|
|
689
|
+
if (outSections.length > 0xfffe)
|
|
690
|
+
fail("merged COFF object exceeds the section-count limit");
|
|
691
|
+
const outSymbols = [];
|
|
692
|
+
const symbolMaps = new Map(); // raw old → raw new
|
|
693
|
+
let rawOut = 0;
|
|
694
|
+
for (const object of selected) {
|
|
695
|
+
const map = new Map();
|
|
696
|
+
symbolMaps.set(object, map);
|
|
697
|
+
const sectionMap = sectionMaps.get(object);
|
|
698
|
+
object.symbols.forEach((sym, primaryIndex) => {
|
|
699
|
+
const rawOld = object.rawByPrimary[primaryIndex];
|
|
700
|
+
const mapSection = () => sym.sectionNumber > 0 ? sectionMap[sym.sectionNumber] ?? 0 : sym.sectionNumber;
|
|
701
|
+
if (sym.storageClass === IMAGE_SYM_CLASS_EXTERNAL) {
|
|
702
|
+
const inDroppedSection = sym.sectionNumber > 0 &&
|
|
703
|
+
sectionDropped.get(object)[sym.sectionNumber - 1] === true;
|
|
704
|
+
const entry = canonical.get(sym.name);
|
|
705
|
+
if (entry.outIndex >= 0) {
|
|
706
|
+
// Later appearance of a known external: reference the canonical
|
|
707
|
+
// entry, unless THIS record is the definition that must land (a
|
|
708
|
+
// dropped duplicate COMDAT's symbol is a reference to the kept
|
|
709
|
+
// copy, never a second landing).
|
|
710
|
+
const isTheDefinition = entry.kind === "defined" && coffDefines(sym) && !coffIsCommon(sym) && !inDroppedSection;
|
|
711
|
+
if (!isTheDefinition) {
|
|
712
|
+
map.set(rawOld, entry.outIndex);
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
// The canonical slot was created by an earlier reference; the
|
|
716
|
+
// definition replaces it in place.
|
|
717
|
+
const slot = outSymbols[entry.outSlot];
|
|
718
|
+
slot.sym = sym;
|
|
719
|
+
slot.object = object;
|
|
720
|
+
slot.sectionNumber = mapSection();
|
|
721
|
+
slot.value = sym.value;
|
|
722
|
+
slot.storageClass = keep.has(sym.name)
|
|
723
|
+
? IMAGE_SYM_CLASS_EXTERNAL
|
|
724
|
+
: IMAGE_SYM_CLASS_STATIC;
|
|
725
|
+
map.set(rawOld, entry.outIndex);
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
const sectionNumber = entry.kind === "defined" && coffDefines(sym) && !coffIsCommon(sym)
|
|
729
|
+
? mapSection()
|
|
730
|
+
: IMAGE_SYM_UNDEFINED;
|
|
731
|
+
const value = entry.kind === "common" ? entry.commonSize : entry.kind === "defined" && !coffIsCommon(sym) ? sym.value : 0;
|
|
732
|
+
const demote = entry.kind === "defined" &&
|
|
733
|
+
coffDefines(sym) &&
|
|
734
|
+
!coffIsCommon(sym) &&
|
|
735
|
+
!keep.has(sym.name);
|
|
736
|
+
entry.outIndex = rawOut;
|
|
737
|
+
entry.outSlot = outSymbols.length;
|
|
738
|
+
outSymbols.push({
|
|
739
|
+
sym,
|
|
740
|
+
object,
|
|
741
|
+
sectionNumber,
|
|
742
|
+
storageClass: demote ? IMAGE_SYM_CLASS_STATIC : IMAGE_SYM_CLASS_EXTERNAL,
|
|
743
|
+
value,
|
|
744
|
+
});
|
|
745
|
+
map.set(rawOld, rawOut);
|
|
746
|
+
rawOut += 1; // externals carry no aux records in this pipeline
|
|
747
|
+
if (sym.aux.length > 0) {
|
|
748
|
+
fail(`${object.label}: external symbol ${sym.name} carries aux records`);
|
|
749
|
+
}
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
// A record anchored to a dropped section (.llvm_addrsig's section
|
|
753
|
+
// symbol) leaves with it; nothing may still reference it afterwards.
|
|
754
|
+
if (sym.sectionNumber > 0 && sectionMap[sym.sectionNumber] === 0)
|
|
755
|
+
return;
|
|
756
|
+
// Non-external record: copy through with a remapped section number.
|
|
757
|
+
map.set(rawOld, rawOut);
|
|
758
|
+
outSymbols.push({
|
|
759
|
+
sym,
|
|
760
|
+
object,
|
|
761
|
+
sectionNumber: mapSection(),
|
|
762
|
+
storageClass: sym.storageClass,
|
|
763
|
+
value: sym.value,
|
|
764
|
+
});
|
|
765
|
+
rawOut += 1 + sym.aux.length / 18;
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
// Pass 3 — remap relocations and aux records now every index is known.
|
|
769
|
+
for (const { section, object } of outSections) {
|
|
770
|
+
const map = symbolMaps.get(object);
|
|
771
|
+
for (const reloc of section.relocs) {
|
|
772
|
+
const mapped = map.get(reloc.symbolIndex);
|
|
773
|
+
if (mapped === undefined) {
|
|
774
|
+
// Relocations may address a record via its primary index only.
|
|
775
|
+
fail(`${object.label}: relocation in ${section.name} references a non-primary symbol`);
|
|
776
|
+
}
|
|
777
|
+
reloc.symbolIndex = mapped;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
// Serialize.
|
|
781
|
+
const strings = [];
|
|
782
|
+
let strtabSize = 4;
|
|
783
|
+
const stringOffsets = new Map();
|
|
784
|
+
const internString = (name) => {
|
|
785
|
+
const existing = stringOffsets.get(name);
|
|
786
|
+
if (existing !== undefined)
|
|
787
|
+
return existing;
|
|
788
|
+
const bytes = textEncoder.encode(`${name}\0`);
|
|
789
|
+
strings.push(bytes);
|
|
790
|
+
const offset = strtabSize;
|
|
791
|
+
stringOffsets.set(name, offset);
|
|
792
|
+
strtabSize += bytes.length;
|
|
793
|
+
return offset;
|
|
794
|
+
};
|
|
795
|
+
const headerSize = 20 + outSections.length * 40;
|
|
796
|
+
let dataOffset = headerSize;
|
|
797
|
+
const dataOffsets = [];
|
|
798
|
+
for (const { section } of outSections) {
|
|
799
|
+
if (section.data === null) {
|
|
800
|
+
dataOffsets.push(0);
|
|
801
|
+
continue;
|
|
802
|
+
}
|
|
803
|
+
dataOffset = Math.ceil(dataOffset / 4) * 4;
|
|
804
|
+
dataOffsets.push(dataOffset);
|
|
805
|
+
dataOffset += section.rawSize;
|
|
806
|
+
}
|
|
807
|
+
const relocOffsets = [];
|
|
808
|
+
for (const { section } of outSections) {
|
|
809
|
+
if (section.relocs.length === 0) {
|
|
810
|
+
relocOffsets.push(0);
|
|
811
|
+
continue;
|
|
812
|
+
}
|
|
813
|
+
dataOffset = Math.ceil(dataOffset / 2) * 2;
|
|
814
|
+
relocOffsets.push(dataOffset);
|
|
815
|
+
dataOffset += (section.relocs.length + (section.relocs.length > 0xfffe ? 1 : 0)) * 10;
|
|
816
|
+
}
|
|
817
|
+
const symtabOffset = Math.ceil(dataOffset / 4) * 4;
|
|
818
|
+
const totalSymbols = rawOut;
|
|
819
|
+
const preStrtab = symtabOffset + totalSymbols * 18;
|
|
820
|
+
const buffers = [];
|
|
821
|
+
const header = new Uint8Array(headerSize);
|
|
822
|
+
const headerView = new DataView(header.buffer);
|
|
823
|
+
headerView.setUint16(0, IMAGE_FILE_MACHINE_AMD64, true);
|
|
824
|
+
headerView.setUint16(2, outSections.length, true);
|
|
825
|
+
headerView.setUint32(4, 0, true); // deterministic timestamp
|
|
826
|
+
headerView.setUint32(8, symtabOffset, true);
|
|
827
|
+
headerView.setUint32(12, totalSymbols, true);
|
|
828
|
+
outSections.forEach(({ section }, i) => {
|
|
829
|
+
const at = 20 + i * 40;
|
|
830
|
+
const nameBytes = textEncoder.encode(section.name);
|
|
831
|
+
if (nameBytes.length <= 8) {
|
|
832
|
+
header.set(nameBytes, at);
|
|
833
|
+
}
|
|
834
|
+
else {
|
|
835
|
+
header.set(textEncoder.encode(`/${internString(section.name)}`), at);
|
|
836
|
+
}
|
|
837
|
+
headerView.setUint32(at + 8, section.virtualSize, true);
|
|
838
|
+
headerView.setUint32(at + 12, section.virtualAddress, true);
|
|
839
|
+
headerView.setUint32(at + 16, section.rawSize, true);
|
|
840
|
+
headerView.setUint32(at + 20, dataOffsets[i], true);
|
|
841
|
+
headerView.setUint32(at + 24, relocOffsets[i], true);
|
|
842
|
+
headerView.setUint32(at + 28, 0, true); // line numbers (deprecated)
|
|
843
|
+
const overflow = section.relocs.length > 0xfffe;
|
|
844
|
+
headerView.setUint16(at + 32, overflow ? 0xffff : section.relocs.length, true);
|
|
845
|
+
headerView.setUint16(at + 34, 0, true);
|
|
846
|
+
const characteristics = section.characteristics & ~IMAGE_SCN_LNK_COMDAT;
|
|
847
|
+
headerView.setUint32(at + 36, overflow
|
|
848
|
+
? characteristics | IMAGE_SCN_LNK_NRELOC_OVFL
|
|
849
|
+
: characteristics & ~IMAGE_SCN_LNK_NRELOC_OVFL, true);
|
|
850
|
+
});
|
|
851
|
+
buffers.push({ at: 0, bytes: header });
|
|
852
|
+
outSections.forEach(({ section }, i) => {
|
|
853
|
+
if (section.data !== null)
|
|
854
|
+
buffers.push({ at: dataOffsets[i], bytes: section.data });
|
|
855
|
+
if (section.relocs.length === 0)
|
|
856
|
+
return;
|
|
857
|
+
const overflow = section.relocs.length > 0xfffe;
|
|
858
|
+
const relocBytes = new Uint8Array((section.relocs.length + (overflow ? 1 : 0)) * 10);
|
|
859
|
+
const relocView = new DataView(relocBytes.buffer);
|
|
860
|
+
let at = 0;
|
|
861
|
+
if (overflow) {
|
|
862
|
+
relocView.setUint32(0, section.relocs.length + 1, true);
|
|
863
|
+
at = 10;
|
|
864
|
+
}
|
|
865
|
+
for (const reloc of section.relocs) {
|
|
866
|
+
relocView.setUint32(at, reloc.virtualAddress, true);
|
|
867
|
+
relocView.setUint32(at + 4, reloc.symbolIndex, true);
|
|
868
|
+
relocView.setUint16(at + 8, reloc.type, true);
|
|
869
|
+
at += 10;
|
|
870
|
+
}
|
|
871
|
+
buffers.push({ at: relocOffsets[i], bytes: relocBytes });
|
|
872
|
+
});
|
|
873
|
+
const symtabBytes = new Uint8Array(totalSymbols * 18);
|
|
874
|
+
const symtabView = new DataView(symtabBytes.buffer);
|
|
875
|
+
{
|
|
876
|
+
let raw = 0;
|
|
877
|
+
for (const out of outSymbols) {
|
|
878
|
+
const at = raw * 18;
|
|
879
|
+
const nameBytes = textEncoder.encode(out.sym.name);
|
|
880
|
+
if (nameBytes.length <= 8) {
|
|
881
|
+
symtabBytes.set(nameBytes, at);
|
|
882
|
+
}
|
|
883
|
+
else {
|
|
884
|
+
symtabView.setUint32(at, 0, true);
|
|
885
|
+
symtabView.setUint32(at + 4, internString(out.sym.name), true);
|
|
886
|
+
}
|
|
887
|
+
symtabView.setUint32(at + 8, out.value, true);
|
|
888
|
+
symtabView.setInt16(at + 12, out.sectionNumber, true);
|
|
889
|
+
symtabView.setUint16(at + 14, out.sym.type, true);
|
|
890
|
+
symtabView.setUint8(at + 16, out.storageClass);
|
|
891
|
+
symtabView.setUint8(at + 17, out.sym.aux.length / 18);
|
|
892
|
+
// Aux records: remap the fields that carry indices. Section
|
|
893
|
+
// definitions (on section symbols) carry an associated section
|
|
894
|
+
// number; weak externals carry a default-symbol index. Others copy
|
|
895
|
+
// raw (.file's aux is the filename bytes).
|
|
896
|
+
const aux = new Uint8Array(out.sym.aux); // private copy
|
|
897
|
+
if ((out.storageClass === IMAGE_SYM_CLASS_STATIC ||
|
|
898
|
+
out.storageClass === IMAGE_SYM_CLASS_SECTION) &&
|
|
899
|
+
aux.length === 18 &&
|
|
900
|
+
out.sym.value === 0 &&
|
|
901
|
+
out.sym.sectionNumber > 0) {
|
|
902
|
+
const auxView = new DataView(aux.buffer);
|
|
903
|
+
const assoc = auxView.getUint16(12, true);
|
|
904
|
+
const sectionMap = sectionMaps.get(out.object);
|
|
905
|
+
auxView.setUint16(12, assoc > 0 ? sectionMap[assoc] ?? 0 : 0, true);
|
|
906
|
+
auxView.setUint8(14, 0); // COMDAT selection cleared with the flag
|
|
907
|
+
}
|
|
908
|
+
else if (out.storageClass === IMAGE_SYM_CLASS_WEAK_EXTERNAL && aux.length >= 18) {
|
|
909
|
+
const auxView = new DataView(aux.buffer);
|
|
910
|
+
const tag = auxView.getUint32(0, true);
|
|
911
|
+
const mapped = symbolMaps.get(out.object).get(tag);
|
|
912
|
+
if (mapped === undefined)
|
|
913
|
+
fail(`${out.object.label}: weak external tag is not a primary symbol`);
|
|
914
|
+
auxView.setUint32(0, mapped, true);
|
|
915
|
+
}
|
|
916
|
+
symtabBytes.set(aux, at + 18);
|
|
917
|
+
raw += 1 + aux.length / 18;
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
buffers.push({ at: symtabOffset, bytes: symtabBytes });
|
|
921
|
+
const strtabBytes = new Uint8Array(strtabSize);
|
|
922
|
+
new DataView(strtabBytes.buffer).setUint32(0, strtabSize, true);
|
|
923
|
+
{
|
|
924
|
+
let at = 4;
|
|
925
|
+
for (const chunk of strings) {
|
|
926
|
+
strtabBytes.set(chunk, at);
|
|
927
|
+
at += chunk.length;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
buffers.push({ at: preStrtab, bytes: strtabBytes });
|
|
931
|
+
const out = new Uint8Array(preStrtab + strtabSize);
|
|
932
|
+
for (const { at, bytes } of buffers)
|
|
933
|
+
out.set(bytes, at);
|
|
934
|
+
return out;
|
|
935
|
+
}
|
|
936
|
+
//# sourceMappingURL=object-localize.js.map
|