一文详解C#中方法重载的底层玩法
目录
- 一:为什么 C 不支持
- 二:C++ 符号表突破
- 三:C#如何实现突破
#include int say() { return 1; }int say(int i) { return i; }int main(){ say(10); return 0; }
文章图片
从错误信息看,它说
say
方法已经存在了,尴尬。。。一:为什么 C 不支持 要想寻找答案,需要了解一点点底层知识,那就是编译器在编译 C 方法时会将函数名作为符号添加到符号表中,这个符号表 就是call到say方法字节码中间的一个载体,画个图大概就是这样。
文章图片
简而言之,call 先跳转到符号表, 然后再 jmp 到 say 方法,问题就出现在这里,符号表是一种类字典结构,是不可以出现符号相同的情况。对了,在 windbg 中我们可以用
x
命令去搜索这些符号,为了论证我的说法,可以在汇编层面给大家验证下,修改代码如下:
#include int say(int i) { return i; }int main(){ say(10); return 0; }
接下来再看下汇编。
--------------- say(10) -----------00C41771push0Ah00C41773call_say (0C412ADh)--------------- 符号表 -----------00C412ADjmpsay (0C417B0h)--------------- say body -----------00C417B0pushebp00C417B1movebp,esp00C417B3subesp,0C0h00C417B9pushebx00C417BApushesi00C417BBpushedi00C417BCmovedi,ebp00C417BExorecx,ecx00C417C0moveax,0CCCCCCCCh00C417C5rep stosdword ptr es:[edi]00C417C7movecx,offset _2440747F_ConsoleApplication6@c (0C4C008h)...
知道了原理后,我们再看看 C++ 是如何在符号表上实现唯一性突破。
二:C++ 符号表突破 为了方便讲述,我们先上一段 C++ 方法重载的代码。
using namespace std; class Person{public: void sayhello(int i) {cout << i << endl; } void sayhello(const char* c) {cout << c << endl; }}; int main(int argc){ Person person; person.sayhello(10); person.sayhello("hello world"); }
按理说
sayhello
有多个,肯定是无法突破的,带着好奇心我们看下它的反汇编代码。----------person.sayhello(10); ----------------003B2E5Fpush0Ah003B2E61leaecx,[person]003B2E64callPerson::sayhello (03B13A2h) ------------person.sayhello("hello world"); ----------------003B2E69pushoffset string "hello world" (03B9C2Ch)003B2E6Eleaecx,[person]003B2E71callPerson::sayhello (03B1302h)
从汇编代码看, 调的都是
Person::sayhello
这个符号,奇怪的是他们属于不同的地址: 03B13A2h
, 03B1302h
,这就太奇怪了,哈哈,字典类符号表肯定是没有问题的,问题是 Visual Studio 20222
的反汇编窗口在调试时做了一些内部转换,算是蒙蔽了我们双眼吧,真是可气!!!居然运行时汇编代码都还不够彻底,那现在我们怎么继续挖呢? 可以用
IDA
去看这个程序的静态反汇编代码,截图如下:文章图片
从代码上的注释可以清楚的看到,原来:
Person::sayhello(int)
变成了j_?sayhello@Person@@QAEXH@Z
。Person::sayhello(char const *)
变成了j_?sayhello@Person@@QAEXPBD@Z
三:C#如何实现突破 我们都知道 C# 的底层 CLR 是由 C++ 写的,所以大概率玩法都是一样,接下来上一段代码:
internal class Program{static void Main(string[] args){//故意做一次重复Say(10); Say("hello world"); Say(10); Say("hello world"); Console.ReadLine(); }static void Say(int i){Console.WriteLine(i); }static void Say(string s){Console.WriteLine(s); }}
由于 C# 的方法是由
JIT
在运行时动态编译的,并且首次编译方法会先跳转到 JIT 的桩地址,所以断点必须下在第二次调用 Say(10)
处才能看到方法的符号地址,汇编代码如下:----------- Say(10); -----------00007FFB82134DFCmovecx,0Ah00007FFB82134E01callMethod stub for: ConsoleApp1.Program.Say(Int32) (07FFB81F6F118h)00007FFB82134E06nop----------- Say("hello world"); -----------00007FFB82134E07movrcx,qword ptr [1A8C65E8h]00007FFB82134E0FcallMethod stub for: ConsoleApp1.Program.Say(System.String) (07FFB81F6F120h)00007FFB82134E14nop
从输出信息看,同样也是两个符号表地址,然后由符号表地址 jmp 到最后的方法体。
----------- Say(10); -----------00007FFB82134E01callMethod stub for: ConsoleApp1.Program.Say(Int32) (07FFB81F6F118h)----------- 符号表 -----------00007FFB81F6F118jmpConsoleApp1.Program.Say(Int32) (07FFB82134F10h)----------- Say body -----------00007FFB82134F10pushrbp00007FFB82134F11pushrdi00007FFB82134F12pushrsi00007FFB82134F13subrsp,20h00007FFB82134F17movrbp,rsp00007FFB82134F1Amovdword ptr [rbp+40h],ecx00007FFB82134F1Dcmpdword ptr [7FFB82036B80h],000007FFB82134F24jeConsoleApp1.Program.Say(Int32)+01Bh (07FFB82134F2Bh)00007FFB82134F26call00007FFBE1C2CC40
暂时还不知道怎么看 JIT 改名后方法名,有知道的朋友可以留言一下哈,但总的来说还是 C++ 这一套。
【一文详解C#中方法重载的底层玩法】以上就是一文详解C#中方法重载的底层玩法 的详细内容,更多关于C#方法重载的资料请关注脚本之家其它相关文章!
推荐阅读
- c++学习|17.6 unique_lock详解
- Shell for while 循环
- netty系列之:在netty中使用TCP协议请求DNS服务器
- 中关村系统之家浅析Windows7系统4个版本的异同点
- 在xp系统之家win7纯净版中怎样缩短Aero Peek延迟?
- 中关村系统之家教你如何应对无法删除内存卡文件的问题
- Win7系统之家浅析Win PE在Windows系统中的妙用
- 中关村系统之家详细说明U盘病毒给Windows电脑造成的危害
- pe系统之家win7旗舰版中Win键的技巧妙用
- 将系统之家win7纯净版gho系统中所有文件夹设置为平铺的步骤