The listing assigned each compiled function to the file of its first source-line marker. When the optimizer schedules code inlined from a header first (std::map::find, std::string::c_str, ...), that marker is the header's, so a correctly compiled function was reported as "no compiled function found after this annotation". Checks now compile with /Z7, which puts CodeView debug info in the object without changing codegen, and CoffObject.functionLines() reads each function's definition file and line from its line table. The binder uses that location and keeps the listing as the fallback for objects without debug info; the listing still provides instruction text and codeHash.
275 lines
9.4 KiB
JavaScript
275 lines
9.4 KiB
JavaScript
// Builders for synthetic COFF objects and PE images used by the tests.
|
|
|
|
export function buildCoff({ sections, symbols }) {
|
|
const strings = [];
|
|
let strSize = 4;
|
|
const addString = (s) => {
|
|
const off = strSize;
|
|
strings.push(Buffer.from(s + '\0', 'latin1'));
|
|
strSize += s.length + 1;
|
|
return off;
|
|
};
|
|
const nameField = (name, isSymbol) => {
|
|
const b = Buffer.alloc(8);
|
|
if (name.length <= 8) b.write(name, 'latin1');
|
|
else if (isSymbol) b.writeUInt32LE(addString(name), 4);
|
|
else b.write('/' + addString(name), 'latin1');
|
|
return b;
|
|
};
|
|
|
|
const headerSize = 20 + sections.length * 40;
|
|
let p = headerSize;
|
|
const layout = sections.map((s) => {
|
|
const dataPtr = p;
|
|
p += s.data.length;
|
|
const relocPtr = p;
|
|
p += (s.relocs?.length ?? 0) * 10;
|
|
return { dataPtr, relocPtr };
|
|
});
|
|
const symPtr = p;
|
|
|
|
const header = Buffer.alloc(20);
|
|
header.writeUInt16LE(0x14c, 0);
|
|
header.writeUInt16LE(sections.length, 2);
|
|
header.writeUInt32LE(symPtr, 8);
|
|
header.writeUInt32LE(symbols.length, 12);
|
|
|
|
const secHeaders = sections.map((s, i) => {
|
|
const h = Buffer.alloc(40);
|
|
nameField(s.name, false).copy(h, 0);
|
|
h.writeUInt32LE(s.data.length, 16);
|
|
h.writeUInt32LE(layout[i].dataPtr, 20);
|
|
h.writeUInt32LE(s.relocs?.length ? layout[i].relocPtr : 0, 24);
|
|
h.writeUInt16LE(s.relocs?.length ?? 0, 32);
|
|
h.writeUInt32LE(s.characteristics ?? 0x60501020, 36);
|
|
return h;
|
|
});
|
|
|
|
const body = [];
|
|
sections.forEach((s) => {
|
|
body.push(s.data);
|
|
for (const r of s.relocs ?? []) {
|
|
const b = Buffer.alloc(10);
|
|
b.writeUInt32LE(r.offset, 0);
|
|
b.writeUInt32LE(r.symbolIndex, 4);
|
|
b.writeUInt16LE(r.type, 8);
|
|
body.push(b);
|
|
}
|
|
});
|
|
|
|
const symTable = symbols.map((s) => {
|
|
const b = Buffer.alloc(18);
|
|
nameField(s.name, true).copy(b, 0);
|
|
b.writeUInt32LE(s.value ?? 0, 8);
|
|
b.writeInt16LE(s.section, 12);
|
|
b.writeUInt16LE(s.type ?? 0, 14);
|
|
b.writeUInt8(s.storageClass ?? 2, 16);
|
|
return b;
|
|
});
|
|
const strTable = Buffer.concat([Buffer.alloc(4), ...strings]);
|
|
strTable.writeUInt32LE(strSize, 0);
|
|
return Buffer.concat([header, ...secHeaders, ...body, ...symTable, strTable]);
|
|
}
|
|
|
|
/**
|
|
* A CodeView (C13) .debug$S section with one line table per function, as /Z7 writes it.
|
|
* @param {Array<{symbolIndex: number, file: string, lines: Array<[offset: number, line: number]>}>} functions
|
|
* @returns {{data: Buffer, relocs: Array<{offset, symbolIndex, type}>}}
|
|
*/
|
|
export function buildDebugS(functions) {
|
|
const files = [...new Set(functions.map((f) => f.file))];
|
|
const strings = Buffer.concat([Buffer.alloc(1), ...files.map((f) => Buffer.from(f + '\0', 'latin1'))]);
|
|
const nameOffset = (f) => 1 + files.slice(0, files.indexOf(f)).reduce((n, s) => n + s.length + 1, 0);
|
|
// File checksum entries: name offset, checksum size 0, kind 0, padded to 4 bytes.
|
|
const checksums = Buffer.concat(files.map((f) => { const b = Buffer.alloc(8); b.writeUInt32LE(nameOffset(f), 0); return b; }));
|
|
const fileId = (f) => files.indexOf(f) * 8;
|
|
|
|
const parts = [Buffer.from([4, 0, 0, 0])];
|
|
const relocs = [];
|
|
let at = 4;
|
|
const subsection = (type, body) => {
|
|
const head = Buffer.alloc(8);
|
|
head.writeUInt32LE(type, 0);
|
|
head.writeUInt32LE(body.length, 4);
|
|
const pad = Buffer.alloc((4 - (body.length % 4)) % 4);
|
|
parts.push(head, body, pad);
|
|
const bodyAt = at + 8;
|
|
at += 8 + body.length + pad.length;
|
|
return bodyAt;
|
|
};
|
|
for (const fn of functions) {
|
|
const body = Buffer.alloc(12 + 12 + fn.lines.length * 8);
|
|
body.writeUInt32LE(Math.max(...fn.lines.map(([o]) => o)) + 1, 8);
|
|
body.writeUInt32LE(fileId(fn.file), 12);
|
|
body.writeUInt32LE(fn.lines.length, 16);
|
|
body.writeUInt32LE(12 + fn.lines.length * 8, 20);
|
|
fn.lines.forEach(([offset, line], i) => {
|
|
body.writeUInt32LE(offset, 24 + i * 8);
|
|
body.writeUInt32LE((line | 0x80000000) >>> 0, 28 + i * 8);
|
|
});
|
|
const bodyAt = subsection(0xf2, body);
|
|
relocs.push({ offset: bodyAt, symbolIndex: fn.symbolIndex, type: 0x0b }, { offset: bodyAt + 4, symbolIndex: fn.symbolIndex, type: 0x0a });
|
|
}
|
|
subsection(0xf3, strings);
|
|
subsection(0xf4, checksums);
|
|
return { data: Buffer.concat(parts), relocs };
|
|
}
|
|
|
|
/**
|
|
* @param {{imageBase?: number, sections: Array<{name, rva, data, characteristics?}>, baseRelocs?: number[]}} p
|
|
* baseRelocs are virtual addresses of HIGHLOW fixups.
|
|
*/
|
|
export function buildPe({ imageBase = 0x400000, sections, baseRelocs = [] }) {
|
|
const all = [...sections];
|
|
if (baseRelocs.length) {
|
|
const pages = new Map();
|
|
for (const va of baseRelocs) {
|
|
const rva = va - imageBase;
|
|
const page = rva & ~0xfff;
|
|
if (!pages.has(page)) pages.set(page, []);
|
|
pages.get(page).push(rva & 0xfff);
|
|
}
|
|
const blocks = [];
|
|
for (const [page, offs] of pages) {
|
|
if (offs.length % 2) offs.push(-1);
|
|
const b = Buffer.alloc(8 + offs.length * 2);
|
|
b.writeUInt32LE(page, 0);
|
|
b.writeUInt32LE(b.length, 4);
|
|
offs.forEach((o, i) => b.writeUInt16LE(o < 0 ? 0 : (3 << 12) | o, 8 + i * 2));
|
|
blocks.push(b);
|
|
}
|
|
const last = all[all.length - 1];
|
|
const rva = Math.ceil((last.rva + last.data.length) / 0x1000) * 0x1000 + 0x1000;
|
|
all.push({ name: '.reloc', rva, data: Buffer.concat(blocks), characteristics: 0x42000040, isReloc: true });
|
|
}
|
|
|
|
const peOff = 0x40;
|
|
const optSize = 224;
|
|
const secTable = peOff + 4 + 20 + optSize;
|
|
let raw = Math.ceil((secTable + all.length * 40) / 0x200) * 0x200;
|
|
const placed = all.map((s) => {
|
|
const rawSize = Math.ceil(s.data.length / 0x200) * 0x200;
|
|
const r = { ...s, rawPtr: raw, rawSize };
|
|
raw += rawSize;
|
|
return r;
|
|
});
|
|
const buf = Buffer.alloc(raw);
|
|
buf.writeUInt16LE(0x5a4d, 0);
|
|
buf.writeUInt32LE(peOff, 0x3c);
|
|
buf.writeUInt32LE(0x4550, peOff);
|
|
const coff = peOff + 4;
|
|
buf.writeUInt16LE(0x14c, coff);
|
|
buf.writeUInt16LE(placed.length, coff + 2);
|
|
buf.writeUInt16LE(optSize, coff + 16);
|
|
const opt = coff + 20;
|
|
buf.writeUInt16LE(0x10b, opt);
|
|
buf.writeUInt32LE(0x1000, opt + 16);
|
|
buf.writeUInt32LE(imageBase, opt + 28);
|
|
buf.writeUInt32LE(16, opt + 92);
|
|
const reloc = placed.find((s) => s.isReloc);
|
|
if (reloc) {
|
|
buf.writeUInt32LE(reloc.rva, opt + 96 + 5 * 8);
|
|
buf.writeUInt32LE(reloc.data.length, opt + 100 + 5 * 8);
|
|
}
|
|
placed.forEach((s, i) => {
|
|
const o = secTable + i * 40;
|
|
buf.write(s.name, o, 'latin1');
|
|
buf.writeUInt32LE(s.data.length, o + 8);
|
|
buf.writeUInt32LE(s.rva, o + 12);
|
|
buf.writeUInt32LE(s.rawSize, o + 16);
|
|
buf.writeUInt32LE(s.rawPtr, o + 20);
|
|
buf.writeUInt32LE(s.characteristics ?? 0x60000020, o + 36);
|
|
s.data.copy(buf, s.rawPtr);
|
|
});
|
|
return buf;
|
|
}
|
|
|
|
/**
|
|
* A tiny "game": Player_Update at 0x401000 calls Callee at 0x401100 and reads
|
|
* g_val at 0x405000.
|
|
*
|
|
* 55 push ebp
|
|
* 8b ec mov ebp, esp
|
|
* e8 rel32 call Callee
|
|
* a1 imm32 mov eax, [g_val]
|
|
* 5d pop ebp
|
|
* c3 ret
|
|
*/
|
|
export const FN_ADDR = 0x401000;
|
|
export const CALLEE_ADDR = 0x401100;
|
|
export const GLOBAL_ADDR = 0x405000;
|
|
|
|
export function fixture({ objPatch } = {}) {
|
|
const text = Buffer.alloc(0x200, 0xcc);
|
|
const code = Buffer.from([0x55, 0x8b, 0xec, 0xe8, 0, 0, 0, 0, 0xa1, 0, 0, 0, 0, 0x5d, 0xc3]);
|
|
code.copy(text, 0);
|
|
text.writeInt32LE(CALLEE_ADDR - (FN_ADDR + 8), 4);
|
|
text.writeUInt32LE(GLOBAL_ADDR, 9);
|
|
Buffer.from([0xc3]).copy(text, CALLEE_ADDR - FN_ADDR);
|
|
const data = Buffer.alloc(0x10);
|
|
const pe = buildPe({
|
|
sections: [
|
|
{ name: '.text', rva: 0x1000, data: text },
|
|
{ name: '.data', rva: 0x5000, data, characteristics: 0xc0000040 },
|
|
],
|
|
baseRelocs: [FN_ADDR + 9],
|
|
});
|
|
|
|
const objCode = Buffer.from(code);
|
|
objPatch?.(objCode);
|
|
const obj = buildCoff({
|
|
sections: [
|
|
{
|
|
name: '.text$mn',
|
|
data: objCode,
|
|
characteristics: 0x60501020, // code, COMDAT
|
|
relocs: [
|
|
{ offset: 4, symbolIndex: 2, type: 0x14 },
|
|
{ offset: 9, symbolIndex: 3, type: 0x06 },
|
|
],
|
|
},
|
|
],
|
|
symbols: [
|
|
{ name: '.text$mn', section: 1, storageClass: 3 },
|
|
{ name: '?Update@Player@@QAEXXZ', section: 1, type: 0x20, storageClass: 2 },
|
|
{ name: '?Callee@@YAXXZ', section: 0, type: 0x20, storageClass: 2 },
|
|
{ name: '?g_val@@3HA', section: 0, storageClass: 2 },
|
|
],
|
|
});
|
|
|
|
const listing = [
|
|
'; Listing generated by Microsoft (R) Optimizing Compiler Version 16.00.40219.01',
|
|
'PUBLIC\t?Update@Player@@QAEXXZ\t\t\t\t; Player::Update',
|
|
'EXTRN\t?Callee@@YAXXZ:PROC\t\t\t\t; Callee',
|
|
'EXTRN\t?g_val@@3HA:DWORD\t\t\t\t; g_val',
|
|
'; Function compile flags: /Ogtp',
|
|
'; File z:\\job\\src\\player.cpp',
|
|
';\tCOMDAT ?Update@Player@@QAEXXZ',
|
|
'_TEXT\tSEGMENT',
|
|
'?Update@Player@@QAEXXZ PROC\t\t\t\t; Player::Update, COMDAT',
|
|
'',
|
|
'; 5 : {',
|
|
'',
|
|
' 00000\t55\t\t push\t ebp',
|
|
' 00001\t8b ec\t\t mov\t ebp, esp',
|
|
'',
|
|
'; 6 : Callee();',
|
|
'',
|
|
' 00003\te8 00 00 00 00\t call\t ?Callee@@YAXXZ\t\t; Callee',
|
|
'',
|
|
'; 7 : return g_val;',
|
|
'',
|
|
' 00008\ta1 00 00 00 00\t mov\t eax, DWORD PTR ?g_val@@3HA ; g_val',
|
|
'',
|
|
'; 8 : }',
|
|
'',
|
|
' 0000d\t5d\t\t pop\t ebp',
|
|
' 0000e\tc3\t\t ret\t 0',
|
|
'?Update@Player@@QAEXXZ ENDP\t\t\t\t; Player::Update',
|
|
'_TEXT\tENDS',
|
|
'END',
|
|
].join('\r\n');
|
|
|
|
const source = ['#include "player.h"', '', '// FUNCTION: fn_00001', 'int Player::Update()', '{', ' Callee();', ' return g_val;', '}', ''].join('\n');
|
|
return { pe, obj, listing, source };
|
|
}
|