Files

5.1 KiB
Raw Permalink Blame History

Swift 最小解析示例

示例符号均取自官方测试向量(swiftlang/swift test/Demangle/Inputs/manglings.txt 与 ABI 文档 Mangling.rst),可本机直接对照。语法规则见 layout。

1. 符号提取与解码命令链

# Mach-O: 提取 $s 符号 → 批量解码
llvm-nm -m sample | awk '$3 ~ /^\$s/ {print $3}' | swift-demangle | head -20
# ELF 同效果
readelf -s sample | awk '$8 ~ /^\$s/ {print $8}' | swift-demangle | head -20
# 单条解码(macOS 用 xcrun swift-demangle;无参数时读 stdin)
echo '$s4main6myFuncyyF' | swift-demangle
# $s4main6myFuncyyF ---> main.myFunc() -> ()
# 精简模式与展开树
echo '$s4main3FooC3barSiyF' | swift-demangle -simplified
echo '$s4main3FooC3barSiyF' | swift-demangle -expand

2. 官方测试向量对照(manglings.txt 实测摘录)

mangled 名 demangled 结果
$s4main6myFuncyyF main.myFunc() -> ()
$s3foo3barC3bas3zimyAaEC_tF foo.bar.bas(zim: foo.zim) -> ()
_$s3foo3barC3bas3zimyAaEC_tFTo {T:_$s3foo3barC3bas3zimyAaEC_tF,C} @objc foo.bar.bas(zim: foo.zim) -> ()(_To = @objc 桥接 thunk)
$s4main3fooyySiFyyXEfU_TA.1 {T:} partial apply forwarder for closure #1 () -> () in main.foo(Swift.Int) -> () with unmangled suffix ".1"(闭包 forwarder)
$s4main8MyStructV3fooyyF main.MyStruct.foo() -> ()(结构体方法)
_T04main1_yyF main._() -> ()(Swift 4.0 _T0 前缀)
_TtVCC4main3Foo4Ding3Str main.Foo.Ding.Str(Swift 3.x _Tt 前缀,V=struct 链)

对照要点:yyF 中两个 y 分别是结果 () 与参数 ();Si 为结果 Int 时排在参数前(3barSiyF → bar() -> Int);_To 与本体成对出现;TA 是闭包 forwarder(闭包入点)。

3. Python 最小解码器(无替换/无泛型的简单符号)

import re
KIND = {'C': 'class', 'O': 'enum', 'V': 'struct', 'P': 'protocol'}
STDLIB = {'Si': 'Int', 'SS': 'String', 'Sq': 'Optional', 'Sa': 'Array',
          'Sd': 'Double', 'Sb': 'Bool'}

def ident(sym, p):            # 长度前缀标识符:3Foo -> "Foo"
    m = re.match(r'(\d+)', sym[p:])
    n = int(m.group(1)); start = p + len(m.group(1))
    return sym[start:start+n], start+n

def typ(sym, p):              # 类型单元:y=() Si=Int ...
    c = sym[p]
    if c == 'y': return '()', p + 1
    if c == 'S': return STDLIB.get(sym[p:p+2], 'S?'), p + 2
    raise ValueError('unhandled type char %r' % c)

def sig(sym, p):              # 签名顺序:结果类型在前、参数在后
    result, p = typ(sym, p)
    params, p = typ(sym, p)
    if sym[p] != 'F': raise ValueError('no F')
    return '%s -> %s' % (params, result), p + 1

def decode(sym):
    p = 2 if sym.startswith('$s') else 3   # 仅支持 $s / _$s
    mod, p = ident(sym, p)
    parts = [mod]
    while p < len(sym) and sym[p].isdigit():
        name, p = ident(sym, p)
        if p < len(sym) and sym[p] in KIND:
            parts.append('%s (%s)' % (name, KIND[sym[p]])); p += 1
        else:
            parts.append(name)
    s, _ = sig(sym, p)
    return '.'.join(parts) + s

print(decode('$s4main6myFuncyyF'))              # main.myFunc() -> ()
print(decode('$s4main3FooC3barSiyF'))           # main.Foo (class).bar() -> Int
print(decode('$s13ExampleModule3FooC3barSiyF')) # ExampleModule.Foo (class).bar() -> Int

局限(对照官方语法):不处理替换表(A 起)、泛型签名(G)、标签列表、E 扩展与 To/TE/Tg 后缀——复杂符号以 swift-demangle 为准,本解码器用于快速手读与理解编码顺序。

4. 反射段定位(stripped 产物的类型结构来源)

otool -l sample | grep -A2 'sectname __swift5_proto'    # conformance 记录
otool -l sample | grep -A2 'sectname __swift5_protos'   # 协议描述符
otool -l sample | grep -A2 'sectname __swift5_fieldmd'  # 字段描述
otool -l sample | grep -A2 'sectname __swift5_types'    # 类型描述符
# ELF 侧
readelf -S sample | grep -i swift5

5. 字节样例(符号在文件中的形态)

$s4main6myFuncyyF 的 ASCII 字节(符号表字符串池内):
24 73 34 6d 61 69 6e 36 6d 79 46 75 6e 63 79 79 46 00
 $  s  4  m  a  i  n  6  m  y  F  u  n  c  y  y  F \0
└前缀┘ └模块"main"┘ └函数名"myFunc"┘ └yyF 签名┘

对照要点:正常符号以 \0 结尾(字符串表内),但含 symbolic reference 的符号名内嵌 \x01-\x1f + 指针字节,strings/grep 按文本处理会截断——提取一律走符号表(llvm-nm/readelf -s)。

实现教训(内化)

  • 解码顺序是"模块 → 类型上下文 → 实体名 → 签名",签名里结果类型在参数前——手读符号按这个顺序切,别倒着猜
  • _To/TA/Tg5 等后缀是实体修饰,解码时先剥离再读主体
  • 官方测试向量(manglings.txt)是最好的离线对照集:任何解码器/工具的输出都可拿它验收

使用注意

  • 符号提取用符号表工具,不用 strings;strip 产物靠 __swift5_* 反射段(见 SKILL.md 坑 5)
  • 与 re-format-macho(段定位)、re-cpp-abi(vtable 思路)配合使用