tnblog
首页
视频
资源
登录

.Net Windbg 与汇编基础(学习笔记)

4398人阅读 2022/9/19 13:59 总访问:3470690 评论:0 收藏:0 手机
分类: windbg

.Net Windbg 与汇编基础(学习笔记)

为什么要学习汇编?


有时候再Debug下可以运行的逻辑,但在Release下却无法实现。
举例:主线程创建一个工作线程,在500ms后准备终止工作线程,在Release模式下无法终止。。。(代码如下)

  1. internal class Program
  2. {
  3. static void Main(string[] args)
  4. {
  5. var worker = new Worker();
  6. Console.WriteLine("Main thread: Start the work thread...");
  7. var workerTask = Task.Run(worker.DoWork);
  8. // 等待 500 毫秒以确保工作线程已在执行
  9. Thread.Sleep(500);
  10. Console.WriteLine("Main thread: request to terminate the work thread...");
  11. worker.RequestStop();
  12. workerTask.Wait();
  13. Console.WriteLine("Main thread: The work thread is terminated");
  14. Console.ReadLine();
  15. }
  16. }
  17. public class Worker
  18. {
  19. private bool _shouldStop;
  20. public void DoWork()
  21. {
  22. bool work = false;
  23. while (!_shouldStop)
  24. {
  25. work = !work; // do sth.
  26. }
  27. Console.WriteLine("Work threads: being terminated...");
  28. }
  29. public void RequestStop()
  30. {
  31. _shouldStop = true;
  32. }
  33. }


接下来我们通过Debug运行,通过改变_shouldStop变量的值,发现该线程正常的在500ms后退出。


然后我们通过Release运行,并进行发布却没有达到预期的效果。


随后我们通过windbg附加进程的方式进行调试。


首先我们来看一下所有线程栈。

  1. ~*e !clrstack


我们可以看到指向了Program类中的DoWork方法第41行,我们可以点一下,也可以执行一下下面的命令看执行哪里。

  1. !U /d 00007ff933d7bdef


接着我们可以下个断点。

  1. bp 00007ff9`33d7bdf4
  2. g


然后我们打开汇编窗口。


通过执行t命令不断的单步执行,我们发现主要执行的是如下几句汇编代码。


test汇编命令表示判断寄存器中ecxecx的值。
je如果判断的值相等就跳转到00007FF933D7BDEA这个位置。
我们先来看看ecx的值

  1. r ecx


0永远等于0,所以这个问题的关键在于它把_shouldStop的值放到了寄存器中,无论你怎么改变值它寄存器中永远不会改变,所以解决方法就是让它不从寄存器从内存中去比较值。
我们可以看看当前内存中的_shouldStop值。

  1. !name2ee Exmaple_2_1_1!Exmaple_2_1_1.Worker
  2. # 这样找也可以 加类名
  3. !dumpheap -type Worker
  4. # 然后我们找到第一个地址,可以看到_shouldStop值为1
  5. !DumpObj /d 00000251407eb1d0


出现了这种情况,我们应该给_shouldStop添加上volatile关键字,表示该值是一个异变的结构。

  1. private volatile bool _shouldStop;


然后我们再次运行程序,发现就可以了。


我们再通过windbg的方式来看看。

  1. !name2ee Exmaple_2_1_1!Exmaple_2_1_1.Worker.DoWork
  2. !U /d 00007ff933d6bde0


在第39行中我们发现与原来的汇编代码有所出入,这里是直接通过rcx+8与0进行比较(rcx表示类的地址,+8表示偏移8位)。
直接从内存中进行比较就没问题了。

内存单元和CPU三大总线

理解内存单元


Bit:计算机中最小的信息单元 (8bit=1Byte), Bitmap 算法就得益于它的威力。
Byte:计算机中最小的数据存储单元,简而言之,一个地址占用一个1byte。
我们通过vs来查看数据的格式大小。

  1. static void Main(string[] args)
  2. {
  3. Console.WriteLine("hello world!");
  4. Console.ReadLine();
  5. }


接下来,我们也可以通过windbg来进行查看。
对应的依次是:db,dw,dd, dq。

CPU三大总线


地址总线: 现在的 intel i7 的CPU上,一般是 48 根地址总线,每根线可以表示 0/1 两种状态(高电平,低电平),
所以它最多能表示 248 个地址,即地址范围是: 0 ~ 0000ffff`ffffffff。
一个地址能存放一个byte,所以最大寻址空间为:248 * 1byte = 256T


我们可以通过!address -summary命令查看地址空间,用户空间占用126T。


这里用户态的最大寻址空间为128T可以看到我这里Free+MappendFile的总和就是用户态最大的存储地址空间,另外还有128T在内核态中。
如果你想看更详细的请执行!address命令


数据总线: 现在的 intel i7 的CPU上,一般是 64 根数据总线,它决定了一次性可以从内存中读取 64bit 的数据,也就是 8byte。


比如说:一个 long 类型,在 32 位操作系统上需要走两次内存,在 64bit 上只需要一次。



控制总线: 它决定了对计算机外部器件的控制能力,比如说对内存可以发起 “读”或 “写” 命令。


综上:CPU读写内存,需要经过 地址总线,数据总线,控制总线 的多次往返,那么如何规避这些不必要的开销是我们思考的问题! 比如合理利用 CPU 内部的“CPU 缓存 & CPU 寄存器”

常见的寄存器


1.寄存器是寄宿于 CPU 中的信息存储部件,通过内部总线实现了高效计算,比读内存速度要快几个数量级。
2.在 高级调试 中,我们需要熟练掌握这 10 个寄存器 (32bit) ,大体上分为 4 类。

数据寄存器

寄存器名称 描述
EAX 累加器
EBX 基数寄存器
ECX 计数寄存器
EDX 数据寄存器


JIT 在将 IL 代码转成 汇编代码的时候,一般会遵守一些约定成俗的规定,比如:
在数据的 + ,- ,*,/ 方面,优先会使用 eax 寄存器,在方法的返回值上面,在方法同样优先使用 eax 。
(举例代码如下)

  1. static void Main(string[] args)
  2. {
  3. /* +,-,*,/ */
  4. var a = 10;
  5. var b = a + 10;
  6. var c = a - 20;
  7. var d = ++a;
  8. //获取方法返回值
  9. var age = GetAge();
  10. }
  11. static int GetAge()
  12. {
  13. int age = 10;
  14. return age;
  15. }


[ebp-3Ch]赋值操作

  1. mov dword ptr [ebp-3Ch],0Ah


先将eax寄存器附上[ebp-3Ch](a)的值,然后做一个add相加0Ah(这里是16进制相当于10进制的10),然后再赋值给[ebp-40h](b)。

  1. mov eax,dword ptr [ebp-3Ch]
  2. add eax,0Ah
  3. mov dword ptr [ebp-40h],eax


与上不同的是它这里加的负数。

  1. mov eax,dword ptr [ebp-3Ch]
  2. add eax,0FFFFFFECh
  3. mov dword ptr [ebp-44h],eax


然后我们来看方法返回值这里,返回是通过EAX寄存器来进行赋值的,我们可以通过按F10来进行调试。

  1. call CLRStub[MethodDescPrestub]@a859fb0804a0ac18 (04A0AC18h)
  2. mov dword ptr [ebp-54h],eax
  3. mov eax,dword ptr [ebp-54h]
  4. mov dword ptr [ebp-4Ch],eax


可以清晰的看到EAX发生了改变。

变址寄存器

寄存器名称 描述
ESI 源变址寄存器
EDI 目的变址寄存器


常用于做字符串的赋值,比如在 C 语言的 main 序幕代码中,就有一段初始化栈空间的操作,代码的意思就是从 edi 开始,依次将 eax 中的 0CCCCCCCCh 赋值 ecx=0x17(转换成10进制就是23次) 次,可以看到这段区间内都是 cc 符。(代码如下)

  1. #include <iostream>
  2. int main()
  3. {
  4. int nums[20] = { 10,11,12,13 };
  5. }

栈指针寄存器

寄存器名称 描述
EBP 基址指针寄存器
ESP 堆栈指针寄存器


每一个方法都有一个属于自己的方法栈帧,这个栈帧的范围就是用 EBP 和 ESP 标识的。


我们可以通过刚刚的例子通过汇编代码用ESP算出EBP的大小。

  1. mov ebp,esp
  2. sub esp,11Ch
  3. push ebx
  4. push esi
  5. push edi


当前esp为00CFF85C,然后加上11C,再加上的三个入栈的ebxesiedi每个占4字节,最后得出EBP的基栈地址CFF984

控制寄存器

寄存器名称 描述
EIP 指令指针寄存器
PSW 状态标志寄存器


EIP: 用来保存程序下一步需要执行的指令地址,跳跃的长度就是机器码的byte数。


PSW :用来保存运算(CMP,TEST)过程中出现的比如 ZF(零标志位), OF (溢出标志)标志位等。

常见语句的汇编代码

赋值语句


在这里a,b,c赋值时它们的值分别为0,0,10,我们来看汇编就一目了然,先将b放入eax中再给a赋值eax,然后再把c放入eax中再给b赋值eax,最后给c赋值0AH(十进制为10)。

  1. internal class Program
  2. {
  3. private static int a = b;
  4. private static int b = c;
  5. private static int c = 10;
  6. static void Main(string[] args)
  7. {
  8. Console.WriteLine($"a={a},b={b},c={c}");
  9. var txt = Convert.ToInt32(Console.ReadLine());
  10. if (txt == 2)
  11. {
  12. Console.WriteLine("txt==2");
  13. }
  14. else
  15. {
  16. Console.WriteLine("txt!=2");
  17. }
  18. }
  19. }

mov:用来将源操作数复制到目的操作数当中,是一个数据传送指令。

条件跳转语句

命令名称 格式 描述
CMP CMP destination,source 目的操作数减去源操作数的隐含减法操作,不修改任何操作数。
PSW JE address (Jump Equals) 即 ZF=1 时跳转。
JMP 无条件地址跳转指令。
CALL 函数调用指令。

案例样本分析


在一个dump中发生栈溢出的情况,发现在某一个 IsMatched 方法中,汇编代码高达9w行,rsp +xxxx 高达 8w 行,导致默认的 1M 栈空间不够而溢出!
经过分析发现他用了一个超大的struct,而且还有嵌套 struct, 当一个方法的“参数”和“返回值”都是 struct 时,会在父方法和子方法的栈上分配大量的 栈空间。
观察下面代码看会产生多少struct。

  1. internal class Program
  2. {
  3. static void Main(string[] args)
  4. {
  5. Person person1 = new Person() { A = int.MaxValue, B = int.MaxValue, C = int.MaxValue, D = int.MaxValue };
  6. Person person2 = person1;
  7. Console.WriteLine(person2.A);
  8. }
  9. static Person Test(Person person)
  10. {
  11. person.A = int.MaxValue;
  12. person.B = int.MaxValue;
  13. person.C = int.MaxValue;
  14. person.D = int.MaxValue;
  15. return person;
  16. }
  17. }
  18. public struct Person
  19. {
  20. public int A;
  21. public int B;
  22. public int C;
  23. public int D;
  24. }


通过EBP发现数据一共有六对,每4个7fffffff为一对,主要的结果如下:


欢迎加群讨论技术,1群:677373950(满了,可以加,但通过不了),2群:656732739

评价

net core 使用 EF Code First

下面这些内容很老了看这篇:https://www.tnblog.net/aojiancc2/article/details/5365 项目使用多层,把数据库访问...

cAPS.net 保存base64位格式的图片

publicvoidUpload() { //取出图片对应的base64位字符 stringimgBase=Request[&quot;imgBase&quot;]; //c#里边的base6...

Quartz.net实例动态改变周期调度。misfire、Cron

Quartz:Java编写的开源的任务调度作业框架 类似Timer之类定时执行的功能,但是更强大Quartz.NET:是把Quartz转成C# NuGet...

.net Windows服务发布、安装、卸载、监听脚本。服务调试

一、脚本 为方便不用每次都去写安装卸载的脚本1.安装脚本@echooff @echo开始安装【服务】 %SystemRoot%\Microsoft.NET\Fr...

c、VB.net中全角半角转换方法

///&lt;summary&gt; ///转全角的函数(SBCcase) ///&lt;/summary&gt; ///&lt;paramname=&quot;input&quot;&gt;任意字符串...

.net mvc分部页,.net core分部页

.net分部页的三种方式第一种:@Html.Partial(&quot;_分部页&quot;)第二种:@{ Html.RenderPartial(&quot;分部页&quot;);}...

C.net 配合小程序实现经过第三方服务器中转文件

某些时候,微信小程序前段上传文件的时候需要经过第三方服务器再将文件上传到客户的服务器;操作如下:1:(小程序内向中端服...

.net实现QQ邮箱发送邮件功能

1、微软已经帮我们封装好了发送邮件的类MailMessage,MailMessage类构造一些邮件信息,然后通过SmtpClient进行邮件发送。Mai...

StackExchange.Redis操作redis(net core支持)

官方git开源地址https://github.com/StackExchange/StackExchange.Redis官方文档在docs里边都是官方的文档通过nuget命令下...

windows 自带的netsh进行端口映射

使用netsh 把本地任意ip的25566端口 映射到192.168.81.234的25565端口netshinterfaceportproxyaddv4tov4listenaddress=0.0....

确保.net程序始终以管理员身份运行

usingSystem; usingSystem.Collections.Generic; usingSystem.Linq; usingSystem.Text; usingSystem.Threading.Tasks; ...

ASP.net Timer细节处理

Timer的用法:1:本人称之为计时器,是asp.net官方的一种。用法即是计时所用 2:关于计时有很多中方式,本人学识有限,暂...

.net core 使用session

tip:net core 2.2后可以直接启用session了,不用在自己添加一次session依赖,本身就添加了使用nuget添加引用Microsoft.AspN...

通俗易懂,什么是.net?什么是.net Framework?什么是.net core?

朋友圈@蓝羽 看到一篇文章写的太详细太通俗了,搬过来细细看完,保证你对.NET有个新的认识理解原文地址:https://www.cnblo...

asp.net core2.0 依赖注入 AddTransient与AddScoped的区别

asp.net core主要提供了三种依赖注入的方式其中AddTransient与AddSingleton比较好区别AddTransient瞬时模式:每次都获取一...
这一世以无限游戏为使命!
排名
2
文章
635
粉丝
44
评论
93
docker中Sware集群与service
尘叶心繁 : 想学呀!我教你呀
一个bug让程序员走上法庭 索赔金额达400亿日元
叼着奶瓶逛酒吧 : 所以说做程序员也要懂点法律知识
.net core 塑形资源
剑轩 : 收藏收藏
映射AutoMapper
剑轩 : 好是好,这个对效率影响大不大哇,效率高不高
ASP.NET Core 服务注册生命周期
剑轩 : http://www.tnblog.net/aojiancc2/article/details/167
ICP备案 :渝ICP备18016597号-1
网站信息:2018-2025TNBLOG.NET
技术交流:群号656732739
联系我们:contact@tnblog.net
公网安备:50010702506256
欢迎加群交流技术