首页
视频
资源
登录
原
.Net Windbg 与汇编基础(学习笔记)
1988
人阅读
2022/9/19 13:59
总访问:
2136885
评论:
0
收藏:
0
手机
分类:
windbg
 ># .Net Windbg 与汇编基础(学习笔记) [TOC] ## 为什么要学习汇编? tn2>有时候再Debug下可以运行的逻辑,但在Release下却无法实现。 举例:主线程创建一个工作线程,在500ms后准备终止工作线程,在Release模式下无法终止。。。(代码如下) ```csharp internal class Program { static void Main(string[] args) { var worker = new Worker(); Console.WriteLine("Main thread: Start the work thread..."); var workerTask = Task.Run(worker.DoWork); // 等待 500 毫秒以确保工作线程已在执行 Thread.Sleep(500); Console.WriteLine("Main thread: request to terminate the work thread..."); worker.RequestStop(); workerTask.Wait(); Console.WriteLine("Main thread: The work thread is terminated"); Console.ReadLine(); } } public class Worker { private bool _shouldStop; public void DoWork() { bool work = false; while (!_shouldStop) { work = !work; // do sth. } Console.WriteLine("Work threads: being terminated..."); } public void RequestStop() { _shouldStop = true; } } ``` tn2>接下来我们通过Debug运行,通过改变_shouldStop变量的值,发现该线程正常的在500ms后退出。  tn2>然后我们通过Release运行,并进行发布却没有达到预期的效果。  tn2>随后我们通过windbg附加进程的方式进行调试。  tn2>首先我们来看一下所有线程栈。 ```bash ~*e !clrstack ```  tn2>我们可以看到指向了Program类中的DoWork方法第41行,我们可以点一下,也可以执行一下下面的命令看执行哪里。 ```bash !U /d 00007ff933d7bdef ```  tn2>接着我们可以下个断点。 ```bash bp 00007ff9`33d7bdf4 g ```  tn2>然后我们打开汇编窗口。  tn2>通过执行`t`命令不断的单步执行,我们发现主要执行的是如下几句汇编代码。  tn2>`test`汇编命令表示判断寄存器中`ecx`与`ecx`的值。 `je`如果判断的值相等就跳转到`00007FF933D7BDEA`这个位置。 我们先来看看`ecx`的值 ```bash r ecx ```  tn2>`0`永远等于`0`,所以这个问题的关键在于它把`_shouldStop`的值放到了寄存器中,无论你怎么改变值它寄存器中永远不会改变,所以解决方法就是让它不从寄存器从内存中去比较值。 我们可以看看当前内存中的`_shouldStop`值。 ```bash !name2ee Exmaple_2_1_1!Exmaple_2_1_1.Worker # 这样找也可以 加类名 !dumpheap -type Worker # 然后我们找到第一个地址,可以看到_shouldStop值为1 !DumpObj /d 00000251407eb1d0 ```  tn2>出现了这种情况,我们应该给`_shouldStop`添加上`volatile`关键字,表示该值是一个异变的结构。 ```csharp private volatile bool _shouldStop; ``` tn2>然后我们再次运行程序,发现就可以了。  tn2>我们再通过windbg的方式来看看。 ```csharp !name2ee Exmaple_2_1_1!Exmaple_2_1_1.Worker.DoWork !U /d 00007ff933d6bde0 ```  tn2>在第39行中我们发现与原来的汇编代码有所出入,这里是直接通过rcx+8与0进行比较(rcx表示类的地址,+8表示偏移8位)。 直接从内存中进行比较就没问题了。 ## 内存单元和CPU三大总线 >### 理解内存单元 tn2>Bit:计算机中最小的信息单元 (8bit=1Byte), Bitmap 算法就得益于它的威力。 Byte:计算机中最小的数据存储单元,简而言之,一个地址占用一个1byte。 我们通过vs来查看数据的格式大小。 ```csharp static void Main(string[] args) { Console.WriteLine("hello world!"); Console.ReadLine(); } ```  tn2>接下来,我们也可以通过windbg来进行查看。 对应的依次是:db,dw,dd, dq。  >### CPU三大总线 tn2>地址总线: 现在的 intel i7 的CPU上,一般是 48 根地址总线,每根线可以表示 0/1 两种状态(高电平,低电平), 所以它最多能表示 248 个地址,即地址范围是: 0 ~ 0000ffff\`ffffffff。 一个地址能存放一个byte,所以最大寻址空间为:248 * 1byte = 256T  tn2>我们可以通过`!address -summary`命令查看地址空间,用户空间占用126T。  tn2>这里用户态的最大寻址空间为`128T`可以看到我这里Free+MappendFile的总和就是用户态最大的存储地址空间,另外还有`128T`在内核态中。 如果你想看更详细的请执行`!address`命令  tn2>数据总线: 现在的 intel i7 的CPU上,一般是 64 根数据总线,它决定了一次性可以从内存中读取 64bit 的数据,也就是 8byte。  tn2>比如说:一个 long 类型,在 32 位操作系统上需要走两次内存,在 64bit 上只需要一次。 <br/> tn2>控制总线: 它决定了对计算机外部器件的控制能力,比如说对内存可以发起 “读”或 “写” 命令。  tn2>综上:CPU读写内存,需要经过 地址总线,数据总线,控制总线 的多次往返,那么如何规避这些不必要的开销是我们思考的问题! 比如合理利用 CPU 内部的“CPU 缓存 & CPU 寄存器” ##常见的寄存器 tn2>1.寄存器是寄宿于 CPU 中的信息存储部件,通过内部总线实现了高效计算,比读内存速度要快几个数量级。 2.在 高级调试 中,我们需要熟练掌握这 10 个寄存器 (32bit) ,大体上分为 4 类。  >### 数据寄存器 | 寄存器名称 | 描述 | | ------------ | ------------ | | EAX | 累加器 | | EBX | 基数寄存器 | | ECX | 计数寄存器 | | EDX | 数据寄存器 | tn2>JIT 在将 IL 代码转成 汇编代码的时候,一般会遵守一些约定成俗的规定,比如: 在数据的 `+ ,- ,*,/` 方面,优先会使用 eax 寄存器,在方法的返回值上面,在方法同样优先使用 eax 。 (举例代码如下) ```csharp static void Main(string[] args) { /* +,-,*,/ */ var a = 10; var b = a + 10; var c = a - 20; var d = ++a; //获取方法返回值 var age = GetAge(); } static int GetAge() { int age = 10; return age; } ```  tn2>为`[ebp-3Ch]`赋值操作 ```csharp mov dword ptr [ebp-3Ch],0Ah ``` tn2>先将eax寄存器附上`[ebp-3Ch]`(a)的值,然后做一个`add`相加`0Ah`(这里是16进制相当于10进制的10),然后再赋值给`[ebp-40h]`(b)。 ```bash mov eax,dword ptr [ebp-3Ch] add eax,0Ah mov dword ptr [ebp-40h],eax ``` tn2>与上不同的是它这里加的负数。 ```bash mov eax,dword ptr [ebp-3Ch] add eax,0FFFFFFECh mov dword ptr [ebp-44h],eax ``` tn2>然后我们来看方法返回值这里,返回是通过EAX寄存器来进行赋值的,我们可以通过按F10来进行调试。 ```bash call CLRStub[MethodDescPrestub]@a859fb0804a0ac18 (04A0AC18h) mov dword ptr [ebp-54h],eax mov eax,dword ptr [ebp-54h] mov dword ptr [ebp-4Ch],eax ```   tn2>可以清晰的看到EAX发生了改变。 >### 变址寄存器 | 寄存器名称 | 描述 | | ------------ | ------------ | | ESI | 源变址寄存器 | | EDI | 目的变址寄存器 | tn2>常用于做字符串的赋值,比如在 C 语言的 main 序幕代码中,就有一段初始化栈空间的操作,代码的意思就是从 edi 开始,依次将 eax 中的 0CCCCCCCCh 赋值 ecx=0x17(转换成10进制就是23次) 次,可以看到这段区间内都是 cc 符。(代码如下) ```c #include <iostream> int main() { int nums[20] = { 10,11,12,13 }; } ```    >### 栈指针寄存器 | 寄存器名称 | 描述 | | ------------ | ------------ | | EBP | 基址指针寄存器 | | ESP | 堆栈指针寄存器 | tn2>每一个方法都有一个属于自己的方法栈帧,这个栈帧的范围就是用 EBP 和 ESP 标识的。  tn2>我们可以通过刚刚的例子通过汇编代码用ESP算出EBP的大小。 ```bash mov ebp,esp sub esp,11Ch push ebx push esi push edi ```  tn2>当前esp为`00CFF85C`,然后加上`11C`,再加上的三个入栈的`ebx`、`esi`、`edi`每个占4字节,最后得出EBP的基栈地址`CFF984`。  >### 控制寄存器 | 寄存器名称 | 描述 | | ------------ | ------------ | | EIP | 指令指针寄存器 | | PSW | 状态标志寄存器 | tn2>EIP: 用来保存程序下一步需要执行的指令地址,跳跃的长度就是机器码的byte数。  tn2>PSW :用来保存运算(CMP,TEST)过程中出现的比如 ZF(零标志位), OF (溢出标志)标志位等。 ## 常见语句的汇编代码 >### 赋值语句 tn2>在这里a,b,c赋值时它们的值分别为`0,0,10`,我们来看汇编就一目了然,先将b放入eax中再给a赋值eax,然后再把c放入eax中再给b赋值eax,最后给c赋值`0AH`(十进制为10)。 ```csharp internal class Program { private static int a = b; private static int b = c; private static int c = 10; static void Main(string[] args) { Console.WriteLine($"a={a},b={b},c={c}"); var txt = Convert.ToInt32(Console.ReadLine()); if (txt == 2) { Console.WriteLine("txt==2"); } else { Console.WriteLine("txt!=2"); } } } ```  tn>mov:用来将源操作数复制到目的操作数当中,是一个数据传送指令。 >### 条件跳转语句 | 命令名称 | 格式 |描述 | | ------------ | ------------ |------------ | | CMP | CMP destination,source | 目的操作数减去源操作数的隐含减法操作,不修改任何操作数。 | | PSW | JE address (Jump Equals) | 即 ZF=1 时跳转。 | | JMP | | 无条件地址跳转指令。 | | CALL | | 函数调用指令。 |  ## 案例样本分析 tn2>在一个dump中发生栈溢出的情况,发现在某一个 IsMatched 方法中,汇编代码高达9w行,rsp +xxxx 高达 8w 行,导致默认的 1M 栈空间不够而溢出! 经过分析发现他用了一个超大的struct,而且还有嵌套 struct, 当一个方法的“参数”和“返回值”都是 struct 时,会在父方法和子方法的栈上分配大量的 栈空间。 观察下面代码看会产生多少struct。 ```csharp internal class Program { static void Main(string[] args) { Person person1 = new Person() { A = int.MaxValue, B = int.MaxValue, C = int.MaxValue, D = int.MaxValue }; Person person2 = person1; Console.WriteLine(person2.A); } static Person Test(Person person) { person.A = int.MaxValue; person.B = int.MaxValue; person.C = int.MaxValue; person.D = int.MaxValue; return person; } } public struct Person { public int A; public int B; public int C; public int D; } ``` tn2>通过EBP发现数据一共有六对,每4个`7fffffff`为一对,主要的结果如下:  
欢迎加群讨论技术,1群:677373950(满了,可以加,但通过不了),2群:656732739
评价
{{titleitem}}
{{titleitem}}
{{item.content}}
{{titleitem}}
{{titleitem}}
{{item.content}}
尘叶心繁
这一世以无限游戏为使命!
博主信息
排名
6
文章
6
粉丝
16
评论
8
文章类别
.net后台框架
160篇
linux
17篇
linux中cve
1篇
windows中cve
0篇
资源分享
10篇
Win32
3篇
前端
28篇
传说中的c
4篇
Xamarin
9篇
docker
15篇
容器编排
101篇
grpc
4篇
Go
15篇
yaml模板
1篇
理论
2篇
更多
Sqlserver
4篇
云产品
38篇
git
3篇
Unity
1篇
考证
2篇
RabbitMq
23篇
Harbor
1篇
Ansible
8篇
Jenkins
17篇
Vue
1篇
Ids4
18篇
istio
1篇
架构
2篇
网络
7篇
windbg
4篇
AI
17篇
threejs
2篇
人物
1篇
嵌入式
2篇
python
8篇
最新文章
最新评价
{{item.articleTitle}}
{{item.blogName}}
:
{{item.content}}
关于我们
ICP备案 :
渝ICP备18016597号-1
网站信息:
2018-2023
TNBLOG.NET
技术交流:
群号656732739
联系我们:
contact@tnblog.net
欢迎加群
欢迎加群交流技术