前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >GNU C++的符号改编机制介绍[转]前言正文

GNU C++的符号改编机制介绍[转]前言正文

作者头像
用户2930595
发布2018-08-23 09:52:40
7660
发布2018-08-23 09:52:40
举报

前言

在逆向so时,经常发现函数名被添加了额外的字符,比如:

代码语言:javascript
复制
_Z13dvmThreadSelfv

无意间看到这篇文章,讲得非常好,特意转载一下。

正文

众所周知,强大的C++相较于C增添了许多功能。这其中就包括类、命名空间和重载这些特性。 对于类来说,不同类中可以定义名字相同的函数和变量,彼此不会相互干扰。命名空间可以保证在各个不同名字空间内的类、函数和变量名字不会互相影响。而重载可以保证即使在同一个命名空间内的同一个类中,函数名字也可以相同,只要参数不一样就可以。 这样的设计方便了程序开发者,不用担心不同开发者都定义相同名字的函数的问题。但是,这也使得符号管理变得更为复杂。 对于在不同类中的同名函数,或者在不同名字空间中的同名函数,或者在同一名字空间或类中的同名重载函数,在最终的编译和链接过程中是怎么将它们区分开来的呢?为了支持C++这些特性,人们发明了所谓的符号改编(Name Mangling)机制。 其原理其实很简单,就是按照函数所在名字空间、类以及参数的不同,按照一定规则对函数进行重命名。不同的编译器其命名规则都不尽相同,这里我们主要介绍GNU C++编译器所使用的规则。主要分为以下几种情况: 1)全局变量: 即在命名空间和类之外的变量,改编后的符号名就是变量名,也就是不做任何修改。 2)全局函数: 以“_Z”开头,然后是函数名字符的个数,接着是函数名,最后是函数参数的别名。 关于函数参数的别名,后面还会有详细的介绍。 3)类或命名空间中的变量或函数: 以“_ZN”开头,然后是变量或函数所在名字空间或类名字的字符长度,然后接着的是真正的名字空间或类名,然后是变量或函数名的长度和变量或函数名,后面紧跟字母“E”,最后如果是函数的话则跟参数别名,如果是变量则什么都不用加。 4)构造函数和析构函数 以”_ZN”开头,然后是构造函数所在名字空间和类名字的字符长度,然后接着的是真正的名字空间或类名,然后构造函数接“C1”或者“C2”,析构函数接“D1”或者“D2”,然后加上字母“E”,最后接函数参数别名结束。 介绍完命名规则,下面我们再具体介绍一下函数参数别名的规则。主要分为下面几种情况: 1)函数参数是基本类型时 每个基本类型的别名如下表:

2)函数参数是类或结构体时 当函数的参数中含有类或结构体时,在类或者结构体名字前加上类或结构体名的字符长度。 例如,全局函数int structure_func(int i, struct test s, double d),其经过符号改编后,函数名变成了_Z14structure_funci4testd

3)函数参数是指针()时 当函数参数中含有指针时,该参数的别名是“P”(大写)加上该指针指向的参数类型的别名。当参数为指针的指针时,该参数的别名是“PP”加上所指向的参数类型的别名,以此类推。 4)函数参数是一维数组时 当函数参数中含有一维数组时,和参数是指针的处理方式一样,也是“P”加上作为参数的数组其元素类型的别名。 5)函数参数是多维数组时 对于多维数组,第一维可以看做是指针,其它维则看做是数组。 当函数参数中含有多维数组时,以“P”(代表数组的第一维)开始,后面接“A”加上各维数组的长度,以“_”间隔,最后以下划线加数组元素类型的别名结束。 例如,全局函数void multi_array_func(int a[10][10][20][30]),其经过符号改编后,函数名变成了_Z16multi_array_funcPA10_A20_A30_i 6)函数参数含有const修饰符时 当函数参数中含有const修饰符时,以“K”(大写)开始,后面接修饰参数类型的别名。 7)函数参数是引用(&)时 当函数参数中含有引用时,该参数的别名是“R”(大写)加上该引用所引用的变量类型的别名。 例如,全局函数void ref_const_func(const int &i),其经过符号改编后,函数名变成了_Z14ref_const_funcRKi*。

8)函数参数是别的命名空间中的类或结构体 当函数的参数含有别的命名空间中的类或结构体时,该参数的别名是“N”(大写),加上空间名的长度,再加上空间名,接着是类或结构名的长度和类或结构的名字,最后以“E”(大写)结束。 再举一个复杂点的例子,假设代码如下:

代码语言:javascript
复制
namespace NS1
{
    class Test1
    {
    };
}

namespace NS2
{
    class Test2
    {
    public:
    void MyFunction(NS1:Test1 t) {}
    };
}

那么MyFunction经过符号改变后变成了什么呢? 答案是:_ZN3NS25Test210MyFunctionEN3NS15Test1E。 最后,稍微总结一下。其实所谓GNU C++的符号改编机制非常简单,只要记住下面几点就可以了:1)除了全局变量不用做改编之外,其它所需要改编符号的时候,都是以_Z开始; 2)若想表示某个符号是在命名空间或类中的,要以“N”开始,以“E”结束; 3)所有的名字空间名、类名、函数名或变量名,改编的时候都是名字所包含的字符数加上真正的名字; 4)所有的名字按照从外层到里层的顺序进行改编; 5)如果是函数的话,所有的参数按照前后出现的顺序进行改编。 最后再提一句,这里的符号改编机制都是暗地里编译器帮你做的。只要你的程序使用GNU C++编译器进行编译,它都会用上文所述的规则对你的各种符号名进行改编(包括变量和函数)。

如果你的程序有一些用C语言编写及编译,而另外一些用C++语言编写及编译,并且这两部分还会互相调用到,则需要进行特殊处理。 C++程序在编译的时候会用符号改编,而C程序在调用的时候并不会用符号改编,而是还用原始的函数名作为符号名进行调用,这样C程序就找不到那个对应的C++函数了。 或者,倒过来,C程序在编译的时候不会进行符号重编,而C++程序在调用的时候也会将这个函数名进行重编,这样C++程序同样也找不到那个对应的C函数了。 解决的方法是把那些需要让C程序用到的C++程序中的变量和函数,或者C++程序用到的C程序中的变量和函数,单独抽出来,让编译器不对它们进行符号重编。 具体方法是将它们用extern "C"包起来:

代码语言:javascript
复制
extern "C"
{
    void func();    
    ......
}

当然,也可以一个一个指定:

代码语言:javascript
复制
extern "C" void func();  
......  
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016.12.26 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 正文
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档