清明时节没出门,在B站上发现一个windbg教程,学习了堆栈一段
https://www.bilibili.com/video/BV1j5411P7Tp?t=150
1.B站内容
Introduction to Windbg Series 1 Part 8 - Commands k for callstack or stackback
Basic commands for windbg - k
Concept of Callstack
-Callstack is the most important information for any kind of debugging.
-Stack of functions. -- 栈属于函数
-A callstack is there for each thread in the OS. -- 每个线程都有
-k is the command for callstack
-Many variants - kb,kvn,kM --几个常用变种
-Take 3 inputs optional which is esp,eip and ebp which is useful in some case like stack corruption. ---和参数相关的几个重要寄存器
-Correct symbols are needed to show the stack correctly. --符号很重要
-Works in both kernel mode and user mode.
然后是一个DEMO,kCmd演示:
2 什么是栈
为加强理解,又拿出张银奎老师的巨著《软件调试》读了第22章节 栈和函数调用
什么是栈?
从数据结构角度,栈是用来存储数据的容器,放入数据称为push,取数据称为pop,存取数据的一条基本规则是后进先出LIFO
对基于栈的计算机系统而言,栈是存储局部变量和进行函数调用所比不可少的连续内存区域。x86系统,栈是朝低地址生长的。
ESP指向栈顶
一个普通的用户线程都有两个栈:内核态栈和用户态栈
挖雷小程序演示,需要使用windbg 6.12 x86,用windbg 10.x x86不行,找不到IP counter
open winmine.exe
ntdll!LdrpDoDebuggerBreak+0x2c:
772a10a6 cc int 3
进入第一个断点
0:000> k
ChildEBP RetAddr
000cfb34 77280ff3 ntdll!LdrpDoDebuggerBreak+0x2c
000cfcb0 77249f31 ntdll!LdrpInitializeProcess+0x12ce
000cfd00 77239799 ntdll!_LdrpInitialize+0x78
000cfd10 00000000 ntdll!LdrInitializeThunk+0x10
0:000> !teb
TEB at 7efdd000
ExceptionList: 000cfb24
StackBase: 000d0000
StackLimit: 000cc000
SubSystemTib: 00000000
FiberData: 00001e00
ArbitraryUserPointer: 00000000
Self: 7efdd000
EnvironmentPointer: 00000000
ClientId: 00002dec . 00002c28
RpcHandle: 00000000
Tls Storage: 7efdd02c
PEB Address: 7efde000
LastErrorValue: 0
LastStatusValue: 0
Count Owned Locks: 0
HardErrorMode: 0
显示线程栈的结构
0:000> dd 000cc000-10
000cbff0 ???????? ???????? ???????? ????????
000cc000 00000000 00000000 00000000 00000000
000cc010 00000000 00000000 00000000 00000000
000cc020 00000000 00000000 00000000 00000000
000cc030 00000000 00000000 00000000 00000000
000cc040 00000000 00000000 00000000 00000000
000cc050 00000000 00000000 00000000 00000000
000cc060 00000000 00000000 00000000 00000000
StackLimit 更低的地址是没有分配的,所以是?号,不可读
0:000> !address 000cbff0
Failed to map Heaps (error 80004005)
Usage: Stack
Allocation Base: 00090000
Base Address: 000ca000
End Address: 000cc000
Region Size: 00002000
Type: 00020000 MEM_PRIVATE
State: 00001000 MEM_COMMIT
Protect: 00000104 PAGE_READWRITE|PAGE_GUARD
More info: ~0k
可以看到有PAGE_GUARD保护
3.观察函数调用和返回过程
int __stdcall Proc(int n)
{
int a=n;
printf("A test to inspect stack, n=%d,a=%d.",n,a);
return n*a;
}
int main()
{
return Proc(122);
}
0:000> bp HiStack!main
0:000> g
Breakpoint 0 hit
HiStack!main:
00391040 6a7a push 7Ah
0:000> r eip,esp
eip=00391040 esp=004dfb5c
0:000> p
eip=00391042 esp=004dfb58
0:000> dd 004dfb58 l1 -- esp的值就是压入堆栈的7a
004dfb58 0000007a
0:000> t
光标移到了子函数
HiStack!printf:
00391010 55 push ebp
eip=00391010 esp=004dfb4c
eip就是当前子函数的位置
esp的内容应该是返回地址,可以查一下
0:000> dd 004dfb4c l1
004dfb4c 0039104e
0039104e 通过反汇编窗口查一下:
00391049 e8c2ffffff call HiStack!printf (00391010)
0039104e 83c40c add esp,0Ch
正是调用子函数后面的地址位置。说明call命令把EIP压栈了。
从函数ret后
eip=0039104e esp=004dfb50
esp的值把最初的7Ah也给弹出去了004dfb5c。
4. 局部变量和栈帧
LocalVar.cpp
int main()
{
FuncA();
FuncB("Dbg");
FuncC("Dbg");
return 0;
}
#pragma optimize( "", on )
int FuncA()
{
int l,m,n;
char sz[]="Advanced SW Debugging";
l=sz[0];
m=sz[4];
n=sz[8];
return l*m*n;
}
-----
FuncA
00391040 55 push ebp
00391041 8bec mov ebp,esp
00391043 0f100d08213900 movups xmm1,xmmword ptr [LocalVar!`string' (00392108)]
0039104a 83ec18 sub esp,18h ---esp寄存器被递减24字节,也就是分配了24字节的栈空间。这个空间为字符串变量分配的, 尽管字符串需要22个字节,但因为内存对齐的需要,所以编译器会实际分配24个字节。三个整数变量未分配栈空间,看来编译器想使用寄存器存储三个整数变量。
0039104d 0f28c1 movaps xmm0,xmm1
00391050 660f7ec8 movd eax,xmm1
00391054 660f73d804 psrldq xmm0,4
00391059 660f7ec1 movd ecx,xmm0
0039105d 0fbec0 movsx eax,al
00391060 660f73d908 psrldq xmm1,8
00391065 0fbec9 movsx ecx,cl
00391068 0fafc1 imul eax,ecx
0039106b 660f7ec9 movd ecx,xmm1
0039106f 0fbec9 movsx ecx,cl
00391072 0fafc1 imul eax,ecx
00391075 8be5 mov esp,ebp
00391077 5d pop ebp
00391078 c3 ret
00391079 cc int 3
0039107a cc int 3
0039107b cc int 3
-------------------------------------
如何引用局部变量?
void FuncB(char * szPara)
{
char szTemp[5];
strncpy(szTemp,szPara,sizeof(szTemp)-1);
printf("%s;Len=%d.\n",szTemp,strlen(szTemp));
}
LocalVar!FuncB:
00391080 55 push ebp
00391081 8bec mov ebp,esp
00391083 83ec0c sub esp,0Ch --- szPara,szTemp预留12个字节
00391086 a104303900 mov eax,dword ptr [LocalVar!__security_cookie (00393004)]
0039108b 33c5 xor eax,ebp
0039108d 8945fc mov dword ptr [ebp-4],eax
00391090 6a04 push 4 -- 压入参数4,准备调用strncpy
00391092 51 push ecx --压入szPara
00391093 8d45f4 lea eax,[ebp-0Ch]
00391096 50 push eax -- 压入szTemp
00391097 ff15c4203900 call dword ptr [LocalVar!_imp__strncpy (003920c4)]
0039109d 8d45f4 lea eax,[ebp-0Ch]
003910a0 83c40c add esp,0Ch
003910a3 8d5001 lea edx,[eax+1]
003910a6 8a08 mov cl,byte ptr [eax]
003910a8 40 inc eax
----------------------------------------------
#pragma optimize( "", off )
void FuncC(char * szPara)
{
char szTemp[5];
strncpy(szTemp,szPara,sizeof(szTemp)-1);
printf("%s;Len=%d.\n",szTemp,strlen(szTemp));
}
LocalVar!FuncC:
003910d0 55 push ebp --压入EBP寄存器当前值
003910d1 8bec mov ebp,esp --ESP的当前值(栈顶)赋给EBP
003910d3 83ec1c sub esp,1Ch --为变量szTemp分配空间。应该是8,怎么分了24?
003910d6 a104303900 mov eax,dword ptr [LocalVar!__security_cookie (00393004)]
003910db 33c5 xor eax,ebp
003910dd 8945fc mov dword ptr [ebp-4],eax
003910e0 6a04 push 4 --准备调用strncpy,压入参数4
003910e2 8b4508 mov eax,dword ptr [ebp+8] -- 将szPara赋给EAX
003910e5 50 push eax --压入szPara
003910e6 8d4df4 lea ecx,[ebp-0Ch] -- 将szTemp的有效地址放入ECX寄存器
003910e9 51 push ecx --压szTemp
003910ea ff15c4203900 call dword ptr [LocalVar!_imp__strncpy (003920c4)] --调用strncpy
003910f0 83c40c add esp,0Ch -- 调整栈指针,释放前面3次压入的参数
003910f3 8d55f4 lea edx,[ebp-0Ch] --将szTemp放入edx
003910f6 8955ec mov dword ptr [ebp-14h],edx --
003910f9 8b45ec mov eax,dword ptr [ebp-14h]
003910fc 83c001 add eax,1
003910ff 8945e8 mov dword ptr [ebp-18h],eax
00391102 8b4dec mov ecx,dword ptr [ebp-14h]
00391105 8a11 mov dl,byte ptr [ecx]
00391107 8855f3 mov byte ptr [ebp-0Dh],dl
0039110a 8345ec01 add dword ptr [ebp-14h],1
0039110e 807df300 cmp byte ptr [ebp-0Dh],0
00391112 75ee jne LocalVar!FuncC+0x32 (00391102)
00391114 8b45ec mov eax,dword ptr [ebp-14h]
00391117 2b45e8 sub eax,dword ptr [ebp-18h]
0039111a 8945e4 mov dword ptr [ebp-1Ch],eax
0039111d 8b4de4 mov ecx,dword ptr [ebp-1Ch]
00391120 51 push ecx --压入ECX即strlen(szTemp)
00391121 8d55f4 lea edx,[ebp-0Ch] --将szTemp的有效地址放入EDX寄存器
00391124 52 push edx --压入EDX,即szTemp
00391125 6820213900 push offset LocalVar!`string' (00392120) --压入常量字符串 "%s;Len=%d.\n"
0039112a e8e1feffff call LocalVar!printf (00391010)
0039112f 83c40c add esp,0Ch
00391132 8b4dfc mov ecx,dword ptr [ebp-4]
00391135 33cd xor ecx,ebp
00391137 e827000000 call LocalVar!__security_check_cookie (00391163)
0039113c 8be5 mov esp,ebp
0039113e 5d pop ebp
0039113f c3 ret