Swift Name Mangling - Swift语言的名字重整技术

译自: https://www.mikeash.com/pyblog/friday-qa-2014-08-15-swift-name-mangling.html

Friday Q&A 2014-08-08: Swift Name Mangling

by Gwynne Raskind 



自从我写上一篇的周五问答文章,已经过去了很长一段时间,但今天,我继续写一篇周五问答的文章,用一种全新的理念介绍一个全新的主题:Swift语言。在过去的几篇文章中, Mike一步一步深入的介绍了Swift的内部结构是什么样子,但他也只是稍微触及到了程序链接时使用的Swift二进制文件的符号特点:mangled symbol names(重整符号名称)。


C/C++

在比如C这样的语言中,任何给定的名字(符号)只能对应唯一的一个函数或数据,不需要名字重整(name mangling)。即便如此,如果你看一个典型的纯C二进制的符号表,你会发现每个函数名有一个_(下划线)的前缀。例如:

$ echo 'int main() { return 0; }' | xcrun clang -x c - -o ./test
    $ xctest nm ./test
    0000000100000000 T __mh_execute_header
    0000000100000f80 T _main
                     U dyld_stub_binder
    $

这种简单的“mangling”(重整)已有很长的历史,实际上没有多少用处,但仍为兼容性和一致性起到一些效果。按照惯例,C中定义的名字会有下划线,而纯汇编定义的全局符号则没有(尽管许多汇编语言的作者为了一致性,也会预先考虑下划线的定义)。      

对于Objective-C语言来说,它的符号名字不会有异议或者说冲突;Objective-C的方法实现的形式是: -[class selector],并且objective – c不允许相同的类使用不同的参数来重载相同的selectors。

 好吧,让我们来看一下重整名字的例子!   

一个没有额外信息的简单的名字可能会产生异议,所以必须做一些处理。下面我们来看一看C++的例子:

$ cat | xcrun clang -x c++ - -o test
    int foo(int a) { return a * 2; }
    int foo(double a) { return a * 2.0; }
    int main() { return foo(1) + foo(1.0); }
    ^D
    $ xcrun nm -a test
    0000000100000f30 T __Z3food
    0000000100000f10 T __Z3fooi
    0000000100000000 T __mh_execute_header
    0000000100000f60 T _main
                     U dyld_stub_binder


因为foo对应两个包含不同参数的函数,这在c++中是合法的定义,所以我们不能简单地生成两个_foo符号,因为链接器会不知道如何链接,无法区分不同的函数实现。因此,c++编译器使用一组严格的编码规则“mangles”(重整)了符号。

跟C和objective – C不一样,C++和Swift函数符号以名字本身来命名,就不足以区分一个函数的不同实现,带有不同的参数类型(比如,foo(int)和foo(双))的同名函数,需要更多信息来区分。如果使用代码中定义的全部参数信息(比如“foo(int)”),那么会在连接器中产生很多额外的代码;并且当多个类型名称映射到同样的基本类型时(如unsigned和unsigned int),也会产生混淆。所以,在c++中,使用了一些晦涩难懂的类型定义和转换规则,其结果是编译器和链接器能流畅的工作,不会有丝毫混淆。这个处理逻辑跟Swift比较类似。

上面的foo的例子的符号解释如下:

1. 首先,最前面的 ‘_’ 表示这是C风格的符号。   

2. 其次, ‘_Z’, 这个前缀标记这个符号是一个mangled(重整)的全局C++名字。   

3. 接下来的数据定义了函数名字包含的字符的个数;在本例中,3foo意味着“foo”这个名字,有三个字符。   

4. 字符’d’和’i’分别对应基本的’double’和’int’数据类型,返回值不属于c++函数命名的一部分,所以参数列表简单地反映函数的全名。


想获取更多的关于经典C++编译器重整名字的内容,请参考 Itanium C++ ABI documentation.


Swift

这些都是非常有趣的,但真正切入Swift正题,前面已经讲了很多C、C++的内容。

Swift的名字重整技术与c++有些不同。Swift使用的编码基于c++的名字重整规则,但它包含更多的信息和概念,以适用于一个更成熟的类型系统。      

我会使用一个复杂的例子,它是故意那样构造的,并且完全跟Swift无关的代码:

$ xcrun swiftc -emit-library -o test -
        struct e {
                enum f {
                        case G, H, I
                }
        }
        class a {
                class b {
                        class c {
                                func d(y: a, x w: b, v u: (x: Int) -> Int) -> e.f {
                                        return e.f.G
                                }
                        }
                }
        }
        ^D
        $ xcrun nm -g test
        ...
        0000000000001c90 T __TFCCC4test1a1b1c1dfS2_FTS0_1xS1_1vFT1xSi_Si_OVS_1e1f
    ...
    $

我们来看看: 
__TFCCC4test1a1b1c1dfS2_FTS0_1xS1_1vFT1xSi_Si_OVS_1e1f

下面一步一步分析:

1. 最开始的字符’_’对Swift符号是必须的

2. ‘_T’是Swift全局符号的标记

3. ‘F’标记这个符号的类型是一个函数

4. ‘C’代表了这是一个“class”类型,在我们的例子里,需要处理三个嵌套的类,所以‘C’出现了三次

5. ‘4test’是模块名,类名有三个,’1a’,’1b’,’1c’

6. 此时此刻,Swift创建了解析类名的堆栈,‘1d’后将查找’f’。从里到外,test.a, test.a.b, testa.a.b.c是类名。由于’1d’没有对应的嵌套类型(因为只有三个C),所以它成为了符号名字-test.a.b.c.d的最里面的部分

7. 小写的’f’标记这个符号是一个“uncurried function”类型,

8. Because we’re now parsing a function type, the list of argument types comes next, followed by the return type. For an uncurried function type, the curried parameter(s) come first. S2_ is a substitution, meaning it will use the third non substituted typeencountered during parsing of the name thus far (the index is zero-based). In this case, this would be test.a.b.c (the third class type).

9.’F’标记函数参数列表的开始。显而易见,名字重整技术是围绕着类型来展开的。

10.’T’标记一个’tuple’的开始,它是一个类型列表

11. S0_ is a substitution of the first type encountered in parsing, in this case test.a; the first parameter has this type.

12. 1x is the external name of the second parameter. Notice that Swift does not encode internal names as part of the mangled signature.

13. S1_ is a substitute of the second type encountered in parsing, in this case test.a.b; the second parameter has this type and the name x.

14. 1v is the external name of the third parameter.

15. F marks the start of another function type.

16. T marks the start of another tuple, the function’s parameters (the function type is unnamed).

17. 1x is the external name of the closure’s first parameter.

18. Si is Swift.Int, a shorthand for the Int builtin type.

19. _ marks the end of the closure’s arguments tuple.

20. Si is another Int, the closure’s return type

21. _ marks the end of the uncurried function’s arguments tuple.

22. O marks the start of an enum type.

23. V marks the start of a struct type, which will contain the enum. (As we saw with the classes earlier, types are nested from the inside out in mangled names).

24. S_ substitutes the (only) seen module name, test. Notice that this is not a type substitution!

25. 1e is the name of the struct.

26. 1f is the name of the enum.

27. The parser sees the end of the mangled name and unwinds through the two parsed names as it did with the class names earlier.

解决方法

按照这个规则自己去还原符号也是可行的,不过还是比较费时,还可能有bug。

幸好Xcode 自带了个工具, 可以查看这些 mangled name 的本来面目,就不需要自己重新去实现这个原理了:

<span style="font-size:14px;">xcrun swift-demangle __TFV5hello4Rectg9subscriptFOS_9DirectionSi 
_TFV5hello4Rectg9subscriptFOS_9DirectionSi ---> hello.Rect.subscript.getter (hello.Direction) -> Swift.Int </span>

总结

Object-C类似于C语言,Swift类似于C++,有函数重载,有虚函数表,需要命名重整。


参考:

1. Swift 内部机制浅析

2. Swift Name Mangling

3. 简述Swift和C的交互

4. Swift reverse engineering

    原文作者:skylin19840101
    原文地址: https://blog.csdn.net/skylin19840101/article/details/51425216
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞