fix(macos): add MH_PIE flag + LC_DYLD_INFO_ONLY to fix SIGKILL on macOS

Root cause of 'zsh: killed' (SIGKILL) was two missing Mach-O elements:

1. Missing MH_PIE flag (0x200000): macOS 11+ (especially Apple Silicon/arm64)
   requires all executables to be Position Independent. The kernel kills
   non-PIE binaries with SIGKILL before even checking the code signature.
   This is why even empty.pe (no externals) was killed.

2. Missing LC_DYLD_INFO_ONLY (0x80000022): The BIND opcodes for external
   symbol resolution were generated but never referenced by any load
   command. dyld had no way to find them, so symbols like printf were
   never bound. Now properly referenced via dyld_info_command.

Additional changes:
- Generate rebase opcodes for Abs64 fixups (required for PIE: dyld adds
  ASLR slide to absolute addresses in .data at runtime)
- Reorganize LINKEDIT layout: rebase -> bind -> symtab -> indirect -> strtab -> sig
- Update ncmds from 11 to 12, sizeofcmds += 48 (dyld_info_command size)
- Applied to both MachOWriter.cs (x64) and MachOWriterArm64.cs (arm64)
- Workflow: add PIE/LC_DYLD_INFO_ONLY verification + time.pe run test
这个提交包含在:
2026-08-05 14:10:34 +08:00
父节点 9c1d4e58cc
当前提交 2ea642875f
修改 3 个文件,包含 158 行新增34 行删除
+22
查看文件
@@ -106,6 +106,14 @@ jobs:
file /tmp/hello.macos
chmod +x /tmp/hello.macos
echo "=== verify PIE flag + LC_DYLD_INFO_ONLY ==="
otool -hv /tmp/hello.macos | head -5
otool -l /tmp/hello.macos | grep -E "LC_DYLD_INFO_ONLY|LC_CODE_SIGNATURE|LC_UUID|LC_BUILD_VERSION" || true
# 编译 time.pe(用户报告的被 kill 的程序)
$PAZE tests/time.pe --target macos $ARCH_FLAG -o /tmp/time.macos 2>&1 || true
chmod +x /tmp/time.macos 2>/dev/null || true
echo "=== codesign display (paze-generated) ==="
codesign -dv --verbose=4 /tmp/hello.macos 2>&1 || true
echo "=== codesign verify (paze-generated) ==="
@@ -114,6 +122,9 @@ jobs:
echo "=== try run (paze-generated signature) ==="
/tmp/hello.macos 2>&1; echo "exit code: $?"
echo "=== try run time.macos ==="
/tmp/time.macos 2>&1; echo "time exit code: $?"
# 对照实验:用系统 codesign -s - 重新签名,排除 paze 签名生成器的问题
echo "=== re-sign with system codesign -s - ==="
cp /tmp/hello.macos /tmp/hello_resigned.macos
@@ -143,6 +154,17 @@ jobs:
echo "=== dyld debug (hello.macos) ==="
DYLD_PRINT_LIBRARIES=1 DYLD_PRINT_BINDINGS=1 /tmp/hello.macos 2>&1 | head -20; echo "dyld exit: $?"
# clang 对比测试:用 clang 编译同样逻辑的程序,对比 Mach-O 结构
echo "=== clang reference build ==="
echo 'int main(void){return 0;}' > /tmp/ref.c
clang -o /tmp/ref.macos /tmp/ref.c
file /tmp/ref.macos
/tmp/ref.macos; echo "clang ref exit: $?"
echo "--- clang ref otool -l (segments) ---"
otool -l /tmp/ref.macos 2>&1 | grep -E "LC_SEGMENT|segname|vmaddr|vmsize|fileoff|filesize|cmd LC" | head -40
echo "--- paze hello otool -l (segments) ---"
otool -l /tmp/hello.macos 2>&1 | grep -E "LC_SEGMENT|segname|vmaddr|vmsize|fileoff|filesize|cmd LC" | head -40
exit 0
- name: Dump Mach-O load commands
+68 -17
查看文件
@@ -31,6 +31,14 @@ public sealed class MachOWriter : IExecutableWriter
private const uint LC_CODE_SIGNATURE = 0x1D;
private const uint LC_BUILD_VERSION = 0x32;
private const uint LC_UUID = 0x1B;
private const uint LC_DYLD_INFO_ONLY = 0x80000022;
private const int MH_PIE = 0x200000;
// Rebase opcodesdyld 对 PIE 二进制中绝对地址加 ASLR slide)
private const byte REBASE_OPCODE_DONE = 0x00;
private const byte REBASE_OPCODE_SET_TYPE_IMM = 0x10;
private const byte REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB128 = 0x20;
private const byte REBASE_OPCODE_DO_REBASE_IMM_TIMES = 0x50;
private const int REBASE_TYPE_POINTER = 1;
private const int VM_PROT_READ = 1, VM_PROT_WRITE = 2, VM_PROT_EXECUTE = 4;
private const int S_NON_LAZY_SYMBOL_POINTERS = 0x06;
// nlist n_type
@@ -164,8 +172,9 @@ public sealed class MachOWriter : IExecutableWriter
int lcCodeSig = 16; // linkedit_data_commandcmd+cmdsize+dataoff+datasize
int lcBuildVer = 24; // build_version_commandcmd+cmdsize+platform+minos+sdk+ntools
int lcUuid = 24; // uuid_commandcmd+cmdsize+uuid[16]
int sizeofcmds = segTextCmd + segDataCmd + segLinkCmd + lcDylinker + lcDylib + lcMain + lcSymtab + lcDysymtab + lcCodeSig + lcBuildVer + lcUuid;
int ncmds = 11;
int lcDyldInfo = 48; // dyld_info_commandrebase+bind+lazy_bind+export 各 off/size
int sizeofcmds = segTextCmd + segDataCmd + segLinkCmd + lcDylinker + lcDylib + lcMain + lcSymtab + lcDysymtab + lcCodeSig + lcBuildVer + lcUuid + lcDyldInfo;
int ncmds = 12;
int headerSize = 32;
int textFileOff = headerSize + sizeofcmds; // __text 文件偏移
@@ -187,22 +196,12 @@ public sealed class MachOWriter : IExecutableWriter
long dataSegFileEnd = dataFileOff + data.Count;
long dataSegVmEnd = bssVmaddr + bssSize;
// __LINKEDIT 段
// __LINKEDIT 段(偏移在 fixup 处理后计算,因 rebase 数据需先生成)
long linkSegFileOff = Align(dataSegFileEnd, Page);
long linkSegVmaddr = Align(dataSegVmEnd, Page);
// LINKEDIT 内布局:bind, symtab, indirect, strtab —— 每个数据结构 8 字节对齐,
// 否则 dyld 报 "mis-aligned LINKEDIT content" 并拒绝加载(SIGKILL)。
long bindOff = linkSegFileOff;
long symtabOff = Align(bindOff + bind.Count, 8);
long indirectOff = Align(symtabOff + symtab.Count, 8);
long strtabOff = Align(indirectOff + indirect.Count, 8);
// 代码签名(ad-hoc):16 字节对齐后附加到 __LINKEDIT 末尾
long sigOff = Align(strtabOff + strtab.Count, 16);
int codeLimit = (int)sigOff;
int sigBlobSize = MachOCodeSignature.ComputeBlobSize(codeLimit);
long linkSegFileEnd = sigOff + sigBlobSize;
long linkSegVmEnd = linkSegVmaddr + (linkSegFileEnd - linkSegFileOff);
long rebaseOff = 0, bindOff = 0, symtabOff = 0, indirectOff = 0, strtabOff = 0, sigOff = 0;
int codeLimit = 0, sigBlobSize = 0;
long linkSegFileEnd = 0, linkSegVmEnd = 0;
// ---- 填充 main 符号 n_value ----
long mainVmaddr = textVmaddr + mainOff;
@@ -219,6 +218,7 @@ public sealed class MachOWriter : IExecutableWriter
}
// ---- 解析 fixup ----
var rebaseFixups = new List<(int seg, long off)>();
foreach (var f in img.Fixups)
{
var list = f.Section == img.Data ? data : f.Section == img.RData ? cstring : text;
@@ -251,9 +251,43 @@ public sealed class MachOWriter : IExecutableWriter
{
long target = SymVmaddr(f.Symbol, img, textVmaddr, cstringVmaddr, dataVmaddr, bssVmaddr, stubsVmaddr, externals);
Write64At(list, f.Offset, target);
// PIE: 记录需要 dyld rebase 的绝对地址位置(segment index + 段内偏移)
if (f.Section == img.Data)
rebaseFixups.Add((1, got.Count + f.Offset)); // __DATA seg
else if (f.Section == img.Text)
rebaseFixups.Add((0, textFileOff + f.Offset)); // __TEXT seg
else if (f.Section == img.RData)
rebaseFixups.Add((0, textFileOff + text.Count + f.Offset)); // __TEXT seg (__cstring)
}
}
// ---- 生成 rebase opcodesPIE: dyld 对绝对地址加 ASLR slide----
var rebase = new List<byte>();
if (rebaseFixups.Count > 0)
{
rebaseFixups.Sort((a, b) => a.seg == b.seg ? a.off.CompareTo(b.off) : a.seg.CompareTo(b.seg));
rebase.Add((byte)(REBASE_OPCODE_SET_TYPE_IMM | REBASE_TYPE_POINTER));
foreach (var (seg, off) in rebaseFixups)
{
rebase.Add((byte)(REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB128 | seg));
WriteUleb128(rebase, (ulong)off);
rebase.Add((byte)(REBASE_OPCODE_DO_REBASE_IMM_TIMES | 1));
}
rebase.Add(REBASE_OPCODE_DONE);
}
// ---- LINKEDIT 内布局:rebase, bind, symtab, indirect, strtab, sig ----
rebaseOff = rebase.Count > 0 ? linkSegFileOff : 0;
bindOff = rebase.Count > 0 ? Align(rebaseOff + rebase.Count, 8) : linkSegFileOff;
symtabOff = Align(bindOff + bind.Count, 8);
indirectOff = Align(symtabOff + symtab.Count, 8);
strtabOff = Align(indirectOff + indirect.Count, 8);
sigOff = Align(strtabOff + strtab.Count, 16);
codeLimit = (int)sigOff;
sigBlobSize = MachOCodeSignature.ComputeBlobSize(codeLimit);
linkSegFileEnd = sigOff + sigBlobSize;
linkSegVmEnd = linkSegVmaddr + (linkSegFileEnd - linkSegFileOff);
// ============ 装配 Mach-O ============
var f2 = new List<byte>();
@@ -264,7 +298,7 @@ public sealed class MachOWriter : IExecutableWriter
Write32At(f2, (uint)MH_EXECUTE);
Write32At(f2, (uint)ncmds);
Write32At(f2, (uint)sizeofcmds);
Write32At(f2, 0); // flags(非 PIE
Write32At(f2, (uint)MH_PIE); // flags = MH_PIEmacOS 11+ arm64 强制要求 PIE,x64 也推荐
Write32At(f2, 0); // reserved
// ---- LC_SEGMENT_64 __TEXT ----
@@ -289,6 +323,18 @@ public sealed class MachOWriter : IExecutableWriter
WriteSegment64(f2, "__LINKEDIT", linkSegVmaddr, linkSegVmEnd - linkSegVmaddr,
linkSegFileOff, linkSegFileEnd - linkSegFileOff, VM_PROT_READ, VM_PROT_READ, 0);
// ---- LC_DYLD_INFO_ONLY ----rebase/bind 信息,dyld 通过此命令处理符号绑定和 PIE 地址修正)
Write32At(f2, LC_DYLD_INFO_ONLY);
Write32At(f2, 48); // cmdsize (dyld_info_command)
Write32At(f2, (uint)(rebase.Count > 0 ? rebaseOff : 0)); // rebase_off
Write32At(f2, (uint)rebase.Count); // rebase_size
Write32At(f2, (uint)bindOff); // bind_off
Write32At(f2, (uint)bind.Count); // bind_size
Write32At(f2, 0); // lazy_bind_off(非懒绑定,无 lazy stub
Write32At(f2, 0); // lazy_bind_size
Write32At(f2, 0); // export_off(主可执行文件无需导出符号)
Write32At(f2, 0); // export_size
// ---- LC_LOAD_DYLINKER ----
Write32At(f2, LC_LOAD_DYLINKER);
Write32At(f2, (uint)lcDylinker);
@@ -370,6 +416,11 @@ public sealed class MachOWriter : IExecutableWriter
// ---- __LINKEDIT 段数据 ----(各子表之间 8 字节对齐填充)
while (f2.Count < linkSegFileOff) f2.Add(0);
if (rebase.Count > 0)
{
f2.AddRange(rebase);
while (f2.Count < bindOff) f2.Add(0);
}
f2.AddRange(bind);
while (f2.Count < symtabOff) f2.Add(0);
f2.AddRange(symtab);
+68 -17
查看文件
@@ -34,6 +34,14 @@ public sealed class MachOWriterArm64 : IExecutableWriter
private const uint LC_CODE_SIGNATURE = 0x1D;
private const uint LC_BUILD_VERSION = 0x32;
private const uint LC_UUID = 0x1B;
private const uint LC_DYLD_INFO_ONLY = 0x80000022;
private const int MH_PIE = 0x200000;
// Rebase opcodesdyld 对 PIE 二进制中绝对地址加 ASLR slide)
private const byte REBASE_OPCODE_DONE = 0x00;
private const byte REBASE_OPCODE_SET_TYPE_IMM = 0x10;
private const byte REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB128 = 0x20;
private const byte REBASE_OPCODE_DO_REBASE_IMM_TIMES = 0x50;
private const int REBASE_TYPE_POINTER = 1;
private const int VM_PROT_READ = 1, VM_PROT_WRITE = 2, VM_PROT_EXECUTE = 4;
private const int S_NON_LAZY_SYMBOL_POINTERS = 0x06;
private const byte N_EXT = 0x01, N_SECT = 0x0e;
@@ -143,8 +151,9 @@ public sealed class MachOWriterArm64 : IExecutableWriter
int lcCodeSig = 16; // linkedit_data_commandcmd+cmdsize+dataoff+datasize
int lcBuildVer = 24; // build_version_commandcmd+cmdsize+platform+minos+sdk+ntools
int lcUuid = 24; // uuid_commandcmd+cmdsize+uuid[16]
int sizeofcmds = segTextCmd + segDataCmd + segLinkCmd + lcDylinker + lcDylib + lcMain + lcSymtab + lcDysymtab + lcCodeSig + lcBuildVer + lcUuid;
int ncmds = 11;
int lcDyldInfo = 48; // dyld_info_commandrebase+bind+lazy_bind+export 各 off/size
int sizeofcmds = segTextCmd + segDataCmd + segLinkCmd + lcDylinker + lcDylib + lcMain + lcSymtab + lcDysymtab + lcCodeSig + lcBuildVer + lcUuid + lcDyldInfo;
int ncmds = 12;
int headerSize = 32;
int textFileOff = headerSize + sizeofcmds;
@@ -166,22 +175,12 @@ public sealed class MachOWriterArm64 : IExecutableWriter
long dataSegFileEnd = dataFileOff + data.Count;
long dataSegVmEnd = bssVmaddr + bssSize;
// __LINKEDIT 段
// __LINKEDIT 段(偏移在 fixup 处理后计算,因 rebase 数据需先生成)
long linkSegFileOff = Align(dataSegFileEnd, Page);
long linkSegVmaddr = Align(dataSegVmEnd, Page);
// LINKEDIT 内布局:bind, symtab, indirect, strtab —— 每个数据结构 8 字节对齐,
// 否则 dyld 报 "mis-aligned LINKEDIT content" 并拒绝加载(SIGKILL)。
long bindOff = linkSegFileOff;
long symtabOff = Align(bindOff + bind.Count, 8);
long indirectOff = Align(symtabOff + symtab.Count, 8);
long strtabOff = Align(indirectOff + indirect.Count, 8);
// 代码签名(ad-hoc):16 字节对齐后附加到 __LINKEDIT 末尾
long sigOff = Align(strtabOff + strtab.Count, 16);
int codeLimit = (int)sigOff;
int sigBlobSize = MachOCodeSignature.ComputeBlobSize(codeLimit);
long linkSegFileEnd = sigOff + sigBlobSize;
long linkSegVmEnd = linkSegVmaddr + (linkSegFileEnd - linkSegFileOff);
long rebaseOff = 0, bindOff = 0, symtabOff = 0, indirectOff = 0, strtabOff = 0, sigOff = 0;
int codeLimit = 0, sigBlobSize = 0;
long linkSegFileEnd = 0, linkSegVmEnd = 0;
// ---- 填充 main 符号 n_value ----
long mainVmaddr = textVmaddr + mainOff;
@@ -205,6 +204,7 @@ public sealed class MachOWriterArm64 : IExecutableWriter
}
// ---- 解析 fixup ----
var rebaseFixups = new List<(int seg, long off)>();
foreach (var f in img.Fixups)
{
var list = f.Section == img.Data ? data : f.Section == img.RData ? cstring : text;
@@ -250,9 +250,43 @@ public sealed class MachOWriterArm64 : IExecutableWriter
{
long target = SymVmaddr(f.Symbol, img, textVmaddr, cstringVmaddr, dataVmaddr, bssVmaddr, stubsVmaddr, externals);
Write64At(list, f.Offset, target);
// PIE: 记录需要 dyld rebase 的绝对地址位置(segment index + 段内偏移)
if (f.Section == img.Data)
rebaseFixups.Add((1, got.Count + f.Offset)); // __DATA seg
else if (f.Section == img.Text)
rebaseFixups.Add((0, textFileOff + f.Offset)); // __TEXT seg
else if (f.Section == img.RData)
rebaseFixups.Add((0, textFileOff + text.Count + f.Offset)); // __TEXT seg (__cstring)
}
}
// ---- 生成 rebase opcodesPIE: dyld 对绝对地址加 ASLR slide----
var rebase = new List<byte>();
if (rebaseFixups.Count > 0)
{
rebaseFixups.Sort((a, b) => a.seg == b.seg ? a.off.CompareTo(b.off) : a.seg.CompareTo(b.seg));
rebase.Add((byte)(REBASE_OPCODE_SET_TYPE_IMM | REBASE_TYPE_POINTER));
foreach (var (seg, off) in rebaseFixups)
{
rebase.Add((byte)(REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB128 | seg));
WriteUleb128(rebase, (ulong)off);
rebase.Add((byte)(REBASE_OPCODE_DO_REBASE_IMM_TIMES | 1));
}
rebase.Add(REBASE_OPCODE_DONE);
}
// ---- LINKEDIT 内布局:rebase, bind, symtab, indirect, strtab, sig ----
rebaseOff = rebase.Count > 0 ? linkSegFileOff : 0;
bindOff = rebase.Count > 0 ? Align(rebaseOff + rebase.Count, 8) : linkSegFileOff;
symtabOff = Align(bindOff + bind.Count, 8);
indirectOff = Align(symtabOff + symtab.Count, 8);
strtabOff = Align(indirectOff + indirect.Count, 8);
sigOff = Align(strtabOff + strtab.Count, 16);
codeLimit = (int)sigOff;
sigBlobSize = MachOCodeSignature.ComputeBlobSize(codeLimit);
linkSegFileEnd = sigOff + sigBlobSize;
linkSegVmEnd = linkSegVmaddr + (linkSegFileEnd - linkSegFileOff);
// ============ 装配 Mach-O ============
var f2 = new List<byte>();
@@ -263,7 +297,7 @@ public sealed class MachOWriterArm64 : IExecutableWriter
Write32At(f2, (uint)MH_EXECUTE);
Write32At(f2, (uint)ncmds);
Write32At(f2, (uint)sizeofcmds);
Write32At(f2, 0); // flags(非 PIE
Write32At(f2, (uint)MH_PIE); // flags = MH_PIEmacOS 11+ arm64 强制要求 PIE
Write32At(f2, 0); // reserved
// ---- LC_SEGMENT_64 __TEXT ----
@@ -287,6 +321,18 @@ public sealed class MachOWriterArm64 : IExecutableWriter
WriteSegment64(f2, "__LINKEDIT", linkSegVmaddr, linkSegVmEnd - linkSegVmaddr,
linkSegFileOff, linkSegFileEnd - linkSegFileOff, VM_PROT_READ, VM_PROT_READ, 0);
// ---- LC_DYLD_INFO_ONLY ----rebase/bind 信息,dyld 通过此命令处理符号绑定和 PIE 地址修正)
Write32At(f2, LC_DYLD_INFO_ONLY);
Write32At(f2, 48); // cmdsize (dyld_info_command)
Write32At(f2, (uint)(rebase.Count > 0 ? rebaseOff : 0)); // rebase_off
Write32At(f2, (uint)rebase.Count); // rebase_size
Write32At(f2, (uint)bindOff); // bind_off
Write32At(f2, (uint)bind.Count); // bind_size
Write32At(f2, 0); // lazy_bind_off(非懒绑定,无 lazy stub
Write32At(f2, 0); // lazy_bind_size
Write32At(f2, 0); // export_off(主可执行文件无需导出符号)
Write32At(f2, 0); // export_size
// ---- LC_LOAD_DYLINKER ----
Write32At(f2, LC_LOAD_DYLINKER);
Write32At(f2, (uint)lcDylinker);
@@ -367,6 +413,11 @@ public sealed class MachOWriterArm64 : IExecutableWriter
// ---- __LINKEDIT 段数据 ----(各子表之间 8 字节对齐填充)
while (f2.Count < linkSegFileOff) f2.Add(0);
if (rebase.Count > 0)
{
f2.AddRange(rebase);
while (f2.Count < bindOff) f2.Add(0);
}
f2.AddRange(bind);
while (f2.Count < symtabOff) f2.Add(0);
f2.AddRange(symtab);