C++ 反汇编/栈帧

学习目的:

  • 便于理解部分 shader 反汇编 后的代码查看
  • 便于处理部分业务逻辑层的算法、逻辑优化等
  • 如果未来有空,我会尝试自己写一个新的语言出来玩玩(但也就是提供具体的语法,逻辑运算,函数调用等功能的编译器 就够了,深入开发需要很巨量工作量。而且如果还加上预编译器链接器编译器的代码优化,那就麻烦了。因为一门语言要活跃起来需要软件生态支持,需要巨量的时间去实现各种现成库、调式工具等,来给使用者提高开发效率,否则没有意义,但如果是为了学习原理去重新一个简单的还是可以的。)

查看 C++ ASM

查看C++的ASM有好几种方式,不同的编译器,不同的版本,不同的ASM风格编译出来都不一样(汇编语法风格:有:Intel,AT&T的)

在线 C++ 反汇编

https://godbolt.org/
在这里插入图片描述

g++ -S

有这么一段C++程序:

int main()
{
    int a = 1;
    int b = -1;
    return a + b;
}

编译:g++ -S .\a.cpp,得到:a.s 文件:

	.file	"a.cpp"
	.text
	.def	___main;	.scl	2;	.type	32;	.endef
	.globl	_main
	.def	_main;	.scl	2;	.type	32;	.endef
_main:
LFB0:
	.cfi_startproc
	pushl	%ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	movl	%esp, %ebp
	.cfi_def_cfa_register 5
	andl	$-16, %esp
	subl	$16, %esp
	call	___main
	movl	$1, 12(%esp)
	movl	$2, 8(%esp)
	movl	12(%esp), %edx
	movl	8(%esp), %eax
	addl	%edx, %eax
	leave
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
LFE0:
	.ident	"GCC: (MinGW.org GCC Build-20200227-1) 9.2.0"

VS 调试时断点查看反汇编信息

VS 下有好几种方法可以查看 MSVC 的编译器 CL.exe 编译出来的 C++ 汇编代码。

有这么一段C++程序:

int main()
{
    int a = 1;
    int b = -1;
    return a + b;
}

中断点后,在菜单栏选择:调试->窗口->反汇编(Ctrl+Alt+D)
在这里插入图片描述
反汇编结果

--- D:\jave\Work Files\CPP\dasm\DASM\DASM\DASM.cpp -----------------------------
     1: int main()
     2: {
00AC1700  push        ebp  
00AC1701  mov         ebp,esp  
00AC1703  sub         esp,0D8h  
00AC1709  push        ebx  
00AC170A  push        esi  
00AC170B  push        edi  
00AC170C  lea         edi,[ebp-0D8h]  
00AC1712  mov         ecx,36h  
00AC1717  mov         eax,0CCCCCCCCh  
00AC171C  rep stos    dword ptr es:[edi]  
00AC171E  mov         ecx,offset _3D4FA793_DASM@cpp (0ACC000h)  
00AC1723  call        @__CheckForDebuggerJustMyCode@4 (0AC1208h)  
     3:     int a = 1;
00AC1728  mov         dword ptr [a],1  
     4:     int b = -1;
00AC172F  mov         dword ptr [b],0FFFFFFFFh  
     5:     return a + b;
00AC1736  mov         eax,dword ptr [a]  
00AC1739  add         eax,dword ptr [b]  
     6: }
00AC173C  pop         edi  
     6: }
00AC173D  pop         esi  
00AC173E  pop         ebx  
00AC173F  add         esp,0D8h  
00AC1745  cmp         ebp,esp  
00AC1747  call        __RTC_CheckEsp (0AC1212h)  
00AC174C  mov         esp,ebp  
00AC174E  pop         ebp  
00AC174F  ret  

VS 在项目属性的文件输出源码+汇编

项目属性->配置属性->C/C+±>输出文件->汇编程序输出->带源代码的程序集(/FAs)

注意 Debug、Release 下的项目属性是分开调整的。
在这里插入图片描述
接下来直接编译编译生成项目,这里我是在Debug下测试的,所以在Debug目录下可以看到有Dasm.asm文件,如下:
在这里插入图片描述

CPP的源码还是和上面的一样,查看一样 DASM.asm的汇编源码是怎么样的:

Debug 下的:[ProjectName].asm

; Listing generated by Microsoft (R) Optimizing Compiler Version 19.25.28610.4 

	TITLE	D:\jave\Work Files\CPP\dasm\DASM\DASM\DASM.cpp
	.686P
	.XMM
	include listing.inc
	.model	flat

INCLUDELIB MSVCRTD
INCLUDELIB OLDNAMES

msvcjmc	SEGMENT
__3D4FA793_DASM@cpp DB 01H
msvcjmc	ENDS
PUBLIC	_main
PUBLIC	__JustMyCode_Default
EXTRN	@__CheckForDebuggerJustMyCode@4:PROC
EXTRN	__RTC_CheckEsp:PROC
EXTRN	__RTC_InitBase:PROC
EXTRN	__RTC_Shutdown:PROC
;	COMDAT rtc$TMZ
rtc$TMZ	SEGMENT
__RTC_Shutdown.rtc$TMZ DD FLAT:__RTC_Shutdown
rtc$TMZ	ENDS
;	COMDAT rtc$IMZ
rtc$IMZ	SEGMENT
__RTC_InitBase.rtc$IMZ DD FLAT:__RTC_InitBase
rtc$IMZ	ENDS
; Function compile flags: /Odt
;	COMDAT __JustMyCode_Default
_TEXT	SEGMENT
__JustMyCode_Default PROC				; COMDAT
	push	ebp
	mov	ebp, esp
	pop	ebp
	ret	0
__JustMyCode_Default ENDP
_TEXT	ENDS
; Function compile flags: /Odtp /RTCsu /ZI
; File D:\jave\Work Files\CPP\dasm\DASM\DASM\DASM.cpp
;	COMDAT _main
_TEXT	SEGMENT
_b$ = -20						; size = 4
_a$ = -8						; size = 4
_main	PROC						; COMDAT

; 2    : {

	push	ebp
	mov	ebp, esp
	sub	esp, 216				; 000000d8H
	push	ebx
	push	esi
	push	edi
	lea	edi, DWORD PTR [ebp-216]
	mov	ecx, 54					; 00000036H
	mov	eax, -858993460				; ccccccccH
	rep stosd
	mov	ecx, OFFSET __3D4FA793_DASM@cpp
	call	@__CheckForDebuggerJustMyCode@4

; 3    :     int a = 1;

	mov	DWORD PTR _a$[ebp], 1

; 4    :     int b = -1;

	mov	DWORD PTR _b$[ebp], -1

; 5    :     return a + b;

	mov	eax, DWORD PTR _a$[ebp]
	add	eax, DWORD PTR _b$[ebp]

; 6    : }

	pop	edi
	pop	esi
	pop	ebx
	add	esp, 216				; 000000d8H
	cmp	ebp, esp
	call	__RTC_CheckEsp
	mov	esp, ebp
	pop	ebp
	ret	0
_main	ENDP
_TEXT	ENDS
END

上面Debug下的调试信息太多了,再调整到Release,生成看看:

Release 下的:[ProjectName].asm

; Listing generated by Microsoft (R) Optimizing Compiler Version 19.25.28610.4 

	TITLE	d:\jave\work files\cpp\dasm\dasm\dasm\dasm.cpp
	.686P
	.XMM
	include listing.inc
	.model	flat

INCLUDELIB OLDNAMES

EXTRN	@__security_check_cookie@4:PROC
PUBLIC	_main
; Function compile flags: /Ogtp
; File D:\jave\Work Files\CPP\dasm\DASM\DASM\DASM.cpp
;	COMDAT _main
_TEXT	SEGMENT
_main	PROC						; COMDAT

; 3    :     int a = 1;
; 4    :     int b = -1;
; 5    :     return a + b;

	xor	eax, eax

; 6    : }

	ret	0
_main	ENDP
_TEXT	ENDS
END

可以看到,Release 下,因:代码优化 后,return 之前的一些代码,压根没生成对应的汇编处理,因为MSVC cl.exe 下编译的Main函数是否返回值,都无关紧要的。

当然,如果你仍然想在Release “比较干净”的代码上查看asm,可以打开项目配置:C/C++->优化关闭优化 /Od (Od的意思:Optimization Disabled ),这样就可以查看到不优化而不会删除了无用逻辑后的汇编是怎么样的。

VS 调试数据

调试->窗口-> 都可以查看:

  • 模块:该进程以加载模块(都是一些已经加载的哪些库)
  • 进程:当前相关进程
  • 内存:当前机器内存(在配合反汇编调试、查看数据是很有用的)
  • 反汇编:就是我们上面提到的
  • 寄存器:可以查看当前寄存器的数据状态(在配合反汇编调试、查看数据是很有用的)
    在这里插入图片描述

模块

在这里插入图片描述
可查看到使用了的各种动态链接的文件

进程

在这里插入图片描述

内存

在这里插入图片描述

这里的内存查看应该是此程序进程的逻辑内存地址(即:进程内存段+逻辑内存地址,来寻址的)

寄存器

在这里插入图片描述
寄存器中,可以查看CPU算术寄存器,段寄存器,浮点寄存器,等。

在内存中的一些字符表信息

如果我们在程序中列出一些常量字符串,然后我们还可以用附加该.exe程序进程后调式(就是断点调试该程序),查看内存可以看到一些字符表的信息,如下,我定位查找在程序中明文使用了Hello world!,然后根据字符串变量指向的常量地址,再通过上面介绍的内存来定位(Hello world!在下面内容中第三行),该字符的地址的前前后后也查找到了其他的一些字符串表的内容,这些字符串应该是编译、链接、合并其他的程序内带上的字符表。

Stack around the variable '.' was corrupted.
The variable '..' is being used without being initialized
Hello world!

The value of ESP was not properly saved across a function call.

This is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling onvention

A cast to a smaller data type has caused a loss of data.  If this was intentional, you should mask the source of the cast with the appropriate bitmask.

For example:
	char c = (i & 0xFF);
	
Changing the code in this way will not affect the quality of the resulting optimized code
Stack memory was corrupted
A local variable was used before it was initialized
Stack memory around _alloca was corrupted
Unknown Runtime Check Error.
R.u.n.t.i.m.e. .C.h.e.c.k. .E.r.r.o.r.......
U.n.a.b.l.e. .t.o. .d.i.s.p.l.a.y. .R.T.C. .M.e.s.s.a.g.e.
R.u.n.-.T.i.m.e. .C.h.e.c.k. .F.a.i.l.u.r.e.
#.%.d. .-. .%.s
Unknown Filename
Unknown Module Name
Run-Time Check Failure #%d - %s
Stack corrupted near unknown variable
%.2X 
Stack area around _alloca memory reserved by this function is corrupted
Data:
	Allocation number within this function:
	Size:
	Address:
		Stack area around _alloca memory reserved by this function is corrupted
		A variable is being used without being initialized tack pointer corruption
		Cast to smaller type causing loss of data
		Stack memory corruption
		Local variable used before initialization
		Stack around _alloca corrupted..

编译出来的ASM文件通常包含的数据

了解我们编译出来的到底是什么文件,首先先了解,我们平时用gcc/g++或是MSVC cl等编译器来编译源文件时,是怎么个步骤:

  • Pre-Processing : 预编译的处理,就是对一些#define开头的宏展开,#include 文件引入到对应位置,或是预编译指令处理:#if #ifdef #ifndef #elif #else #endif 等,但是#pragma会保留不处理,这是编译器要处理的编译指令,GCC中对应:g++ -E a.cpp -i out.txt,或是g++ -E a.cpp > out.txt
  • Compilation : 编译,对预编译后的所有源文件开是编译处理:词法分析、语法分析、语义分析、生成中间代码、中间代码优化(可选),GCC中对应:g++ -S a.app生成 a.s,这时带有预编译、与编译的处理的
  • Assembly : 汇编编译,对中间代码优化,GCC中对应::g++ -c a.app生成 a.o
  • Links:将目标文件(.o,.lib)或是可执行文件(PE/ELF之类的可执行文件)或是动态链接文件(DLL)链接处理。静态链接会生成新的目标文件,将各个子目标文件的段数据合并,调整各个段数据(.text:代码或是只读数据, .data:静态或是全局数据, .bss:未初始化的静态或全局数据, .symtable:变量或是函数的symbol table符号表, and so on)的虚拟内存地址(VMA:Virtual Memory Address)与段大小(Size),还有程序入口指令地址,等。

编译出来的文件与Window PE(Portable Executable:便携式可执行的),Linux ELF(Executable Linkable Format:可执行可链接的格式)有几分相似,他们都是COFF(Common File Format:公共文件格式)格式之一。

基本分段/分块

  • 代码区 - 在反汇编代码中的:_TEXT SEGMENT ... _TEXT ENDS之间
  • 数据区
    • 全局已初始化区:_DATA SEGMENT ... _DATA ENDS 之间
    • 全局未初始化区:_BSS SEGMENT ... _BSS ENDS 之间
    • 常量区:CONST SEGMENT ... CONST ENDS 之间

以上的这些定义区块,可以在 VSC 或是 Sublime 在使用正则快速搜索,写了个正则为:

  • 通用格式:(\w*)\W*?SEGMENT[\w\W]*?\1\W*?ENDS
    • 查找:_TEXT代码区块:(_TEXT)\W*?SEGMENT[\w\W]*?\1\W*?ENDS
    • 查找:_DATA 全局已初始化区:(_DATA)\W*?SEGMENT[\w\W]*?\1\W*?ENDS
    • 查找:_BSS 全局未初始化区:(_BSS)\W*?SEGMENT[\w\W]*?\1\W*?ENDS
    • 查找:CONST 常量区:(CONST)\W*?SEGMENT[\w\W]*?\1\W*?ENDS

但是上面的正则在 Release 搜索不完整,因为 Release 下有些区块标记会的 XXX SEGMENT会忽略掉,在下面列出的 Release 的反汇编可以看到。但也不影响我们理解这些概念。

这些区块或叫:分段数据,每一个编译出来的文件都会有的,然后他们不同文件最终会链接合并(能直接合并在一块的都是静态链接库)为一个:目标文件(库、可执行文件等)

所以目标文件一般是多个静态库其他的编译文件合并的文件,动态链接就不一样了,它是编译时有检测,运行时才重定位使用到的动态库:函数或数据的地址

反汇编实例

就已字符常量区为例,我们用一段示例C++程序:

// jave.lin - test for deassembly code
int s_i = 999; // static_int
int s_i1 = 0xff001122;
float s_f_ui; // static_float, un initialize 未初始化
float s_f1 = 0.888; // static_float

const int c_i = 1000; // const int,不在常量区
const float c_f = 0.333; // const float
const char* c_str_ui; // const char*, un initialize
const char* c_str = "This is Global const str."; // const char*

int main()
{
    const char *l_c_str_helloworld = "Hello world!"; // local const string,明文编写字符串比较特别,通过反汇编可以看到,右的值都是常量
    const char* l_c_str_second = "This is Seconds const str."; // local const string,明文编写字符串比较特别,通过反汇编可以看到,右的值都是常量
    char l_d_str_thrid[50] = "This is Thrid dynamic str."; // local dynamic string,明文编写字符串比较特别,通过反汇编可以看到,右的值都是常量

    l_d_str_thrid[0] = '#'; //明文编写字符不会有常量,因为当做一个byte字节数据来处理

    int l_i = s_i; // local int = static int
    int l_i1 = s_i1; // local int = static int
    int l_f_ui = s_f_ui; // local float = static float
    int l_f1 = s_f1; // local float = static float
    const int l_c_i = c_i; // local const int = const int,通过反汇编可以看到,c_i被当做立即数使用了,c_i不存在常量区
    //const int* p_l_c_i = &c_i; // 但是如果有代码使用到c_i的地址,c_i在常量区就会有储存,这是编译器的优化
    const float l_c_f = c_f; // local const float = const float,通过反汇编可以看到,c_f在常量区有存储,这与const int是有区别的
    const char* l_c_str_ui = c_str_ui; // local const char* = const char*
    const char* l_c_str = c_str; // local const char* = const char*

    return 0;
}

Debug下的反汇编

下面可以查看刚刚上面说的对应区块内容

; Listing generated by Microsoft (R) Optimizing Compiler Version 19.25.28614.0 

	TITLE	D:\jave\Work Files\CPP\dasm\DASM\DASM\DASM.cpp
	.686P
	.XMM
	include listing.inc
	.model	flat

INCLUDELIB MSVCRTD
INCLUDELIB OLDNAMES

PUBLIC	?s_i@@3HA					; s_i
PUBLIC	?s_i1@@3HA					; s_i1
PUBLIC	?s_f_ui@@3MA					; s_f_ui
PUBLIC	?s_f1@@3MA					; s_f1
PUBLIC	?c_str_ui@@3PBDB				; c_str_ui
PUBLIC	?c_str@@3PBDB					; c_str
PUBLIC	??_C@_0BK@OEHJENGO@This?5is?5Global?5const?5str?4@ ; `string'
_BSS	SEGMENT
?s_f_ui@@3MA DD	01H DUP (?)				; s_f_ui
?c_str_ui@@3PBDB DD 01H DUP (?)				; c_str_ui
_BSS	ENDS
msvcjmc	SEGMENT
__3D4FA793_DASM@cpp DB 01H
msvcjmc	ENDS
;	COMDAT ??_C@_0BK@OEHJENGO@This?5is?5Global?5const?5str?4@
CONST	SEGMENT
??_C@_0BK@OEHJENGO@This?5is?5Global?5const?5str?4@ DB 'This is Global con'
	DB	'st str.', 00H				; `string'
CONST	ENDS
_DATA	SEGMENT
?s_i@@3HA DD	03e7H					; s_i
?s_i1@@3HA DD	0ff001122H				; s_i1
?s_f1@@3MA DD	03f6353f8r			; 0.888	; s_f1
?c_str@@3PBDB DD FLAT:??_C@_0BK@OEHJENGO@This?5is?5Global?5const?5str?4@ ; c_str
_DATA	ENDS
PUBLIC	_main
PUBLIC	__JustMyCode_Default
PUBLIC	??_C@_0N@KNIDPCKA@Hello?5world?$CB@		; `string'
PUBLIC	??_C@_0BL@LGABGNAE@This?5is?5Seconds?5const?5str?4@ ; `string'
PUBLIC	??_C@_0BL@DIJMBJEP@This?5is?5Thrid?5dynamic?5str?4@ ; `string'
PUBLIC	__real@3eaa7efa
EXTRN	@_RTC_CheckStackVars@8:PROC
EXTRN	@__CheckForDebuggerJustMyCode@4:PROC
EXTRN	@__security_check_cookie@4:PROC
EXTRN	__RTC_CheckEsp:PROC
EXTRN	__RTC_InitBase:PROC
EXTRN	__RTC_Shutdown:PROC
EXTRN	___security_cookie:DWORD
EXTRN	__fltused:DWORD
;	COMDAT __real@3eaa7efa
CONST	SEGMENT
__real@3eaa7efa DD 03eaa7efar			; 0.333
CONST	ENDS
;	COMDAT rtc$TMZ
rtc$TMZ	SEGMENT
__RTC_Shutdown.rtc$TMZ DD FLAT:__RTC_Shutdown
rtc$TMZ	ENDS
;	COMDAT rtc$IMZ
rtc$IMZ	SEGMENT
__RTC_InitBase.rtc$IMZ DD FLAT:__RTC_InitBase
rtc$IMZ	ENDS
;	COMDAT ??_C@_0BL@DIJMBJEP@This?5is?5Thrid?5dynamic?5str?4@
CONST	SEGMENT
??_C@_0BL@DIJMBJEP@This?5is?5Thrid?5dynamic?5str?4@ DB 'This is Thrid dyn'
	DB	'amic str.', 00H				; `string'
CONST	ENDS
;	COMDAT ??_C@_0BL@LGABGNAE@This?5is?5Seconds?5const?5str?4@
CONST	SEGMENT
??_C@_0BL@LGABGNAE@This?5is?5Seconds?5const?5str?4@ DB 'This is Seconds c'
	DB	'onst str.', 00H				; `string'
CONST	ENDS
;	COMDAT ??_C@_0N@KNIDPCKA@Hello?5world?$CB@
CONST	SEGMENT
??_C@_0N@KNIDPCKA@Hello?5world?$CB@ DB 'Hello world!', 00H ; `string'
CONST	ENDS
; Function compile flags: /Odt
;	COMDAT __JustMyCode_Default
_TEXT	SEGMENT
__JustMyCode_Default PROC				; COMDAT
	push	ebp
	mov	ebp, esp
	pop	ebp
	ret	0
__JustMyCode_Default ENDP
_TEXT	ENDS
; Function compile flags: /Odtp /RTCsu /ZI
; File D:\jave\Work Files\CPP\dasm\DASM\DASM\DASM.cpp
;	COMDAT _main
_TEXT	SEGMENT
_l_c_str$ = -180					; size = 4
_l_c_str_ui$ = -168					; size = 4
_l_c_f$ = -156						; size = 4
_l_c_i$ = -144						; size = 4
_l_f1$ = -132						; size = 4
_l_f_ui$ = -120						; size = 4
_l_i1$ = -108						; size = 4
_l_i$ = -96						; size = 4
_l_d_str_thrid$ = -84					; size = 50
_l_c_str_second$ = -24					; size = 4
_l_c_str_helloworld$ = -12				; size = 4
__$ArrayPad$ = -4					; size = 4
_main	PROC						; COMDAT

; 13   : {

	push	ebp
	mov	ebp, esp
	sub	esp, 376				; 00000178H
	push	ebx
	push	esi
	push	edi
	lea	edi, DWORD PTR [ebp-376]
	mov	ecx, 94					; 0000005eH
	mov	eax, -858993460				; ccccccccH
	rep stosd
	mov	eax, DWORD PTR ___security_cookie
	xor	eax, ebp
	mov	DWORD PTR __$ArrayPad$[ebp], eax
	mov	ecx, OFFSET __3D4FA793_DASM@cpp
	call	@__CheckForDebuggerJustMyCode@4

; 14   :     const char *l_c_str_helloworld = "Hello world!"; // local const string,明文编写字符串比较特别,通过反汇编可以看到,右的值都是常量

	mov	DWORD PTR _l_c_str_helloworld$[ebp], OFFSET ??_C@_0N@KNIDPCKA@Hello?5world?$CB@

; 15   :     const char* l_c_str_second = "This is Seconds const str."; // local const string,明文编写字符串比较特别,通过反汇编可以看到,右的值都是常量

	mov	DWORD PTR _l_c_str_second$[ebp], OFFSET ??_C@_0BL@LGABGNAE@This?5is?5Seconds?5const?5str?4@

; 16   :     char l_d_str_thrid[50] = "This is Thrid dynamic str."; // local dynamic string,明文编写字符串比较特别,通过反汇编可以看到,右的值都是常量

	mov	ecx, 6
	mov	esi, OFFSET ??_C@_0BL@DIJMBJEP@This?5is?5Thrid?5dynamic?5str?4@
	lea	edi, DWORD PTR _l_d_str_thrid$[ebp]
	rep movsd
	movsw
	movsb
	xor	eax, eax
	mov	DWORD PTR _l_d_str_thrid$[ebp+27], eax
	mov	DWORD PTR _l_d_str_thrid$[ebp+31], eax
	mov	DWORD PTR _l_d_str_thrid$[ebp+35], eax
	mov	DWORD PTR _l_d_str_thrid$[ebp+39], eax
	mov	DWORD PTR _l_d_str_thrid$[ebp+43], eax
	mov	WORD PTR _l_d_str_thrid$[ebp+47], ax
	mov	BYTE PTR _l_d_str_thrid$[ebp+49], al

; 17   : 
; 18   :     l_d_str_thrid[0] = '#'; //明文编写字符不会有常量,因为当做一个byte字节数据来处理

	mov	eax, 1
	imul	ecx, eax, 0
	mov	BYTE PTR _l_d_str_thrid$[ebp+ecx], 35	; 00000023H

; 19   : 
; 20   :     int l_i = s_i; // local int = static int

	mov	eax, DWORD PTR ?s_i@@3HA		; s_i
	mov	DWORD PTR _l_i$[ebp], eax

; 21   :     int l_i1 = s_i1; // local int = static int

	mov	eax, DWORD PTR ?s_i1@@3HA		; s_i1
	mov	DWORD PTR _l_i1$[ebp], eax

; 22   :     int l_f_ui = s_f_ui; // local float = static float

	cvttss2si eax, DWORD PTR ?s_f_ui@@3MA
	mov	DWORD PTR _l_f_ui$[ebp], eax

; 23   :     int l_f1 = s_f1; // local float = static float

	cvttss2si eax, DWORD PTR ?s_f1@@3MA
	mov	DWORD PTR _l_f1$[ebp], eax

; 24   :     const int l_c_i = c_i; // local const int = const int,通过反汇编可以看到,c_i被当做立即数使用了,c_i不存在常量区

	mov	DWORD PTR _l_c_i$[ebp], 1000		; 000003e8H

; 25   :     //const int* p_l_c_i = &c_i; // 但是如果有代码使用到c_i的地址,c_i在常量区就会有储存,这是编译器的优化
; 26   :     const float l_c_f = c_f; // local const float = const float,通过反汇编可以看到,c_f在常量区有存储,这与const int是有区别的

	movss	xmm0, DWORD PTR __real@3eaa7efa
	movss	DWORD PTR _l_c_f$[ebp], xmm0

; 27   :     const char* l_c_str_ui = c_str_ui; // local const char* = const char*

	mov	eax, DWORD PTR ?c_str_ui@@3PBDB		; c_str_ui
	mov	DWORD PTR _l_c_str_ui$[ebp], eax

; 28   :     const char* l_c_str = c_str; // local const char* = const char*

	mov	eax, DWORD PTR ?c_str@@3PBDB		; c_str
	mov	DWORD PTR _l_c_str$[ebp], eax

; 29   : 
; 30   :     return 0;

	xor	eax, eax

; 31   : }

	push	edx
	mov	ecx, ebp
	push	eax
	lea	edx, DWORD PTR $LN5@main
	call	@_RTC_CheckStackVars@8
	pop	eax
	pop	edx
	pop	edi
	pop	esi
	pop	ebx
	mov	ecx, DWORD PTR __$ArrayPad$[ebp]
	xor	ecx, ebp
	call	@__security_check_cookie@4
	add	esp, 376				; 00000178H
	cmp	ebp, esp
	call	__RTC_CheckEsp
	mov	esp, ebp
	pop	ebp
	ret	0
	npad	3
$LN5@main:
	DD	1
	DD	$LN4@main
$LN4@main:
	DD	-84					; ffffffacH
	DD	50					; 00000032H
	DD	$LN3@main
$LN3@main:
	DB	108					; 0000006cH
	DB	95					; 0000005fH
	DB	100					; 00000064H
	DB	95					; 0000005fH
	DB	115					; 00000073H
	DB	116					; 00000074H
	DB	114					; 00000072H
	DB	95					; 0000005fH
	DB	116					; 00000074H
	DB	104					; 00000068H
	DB	114					; 00000072H
	DB	105					; 00000069H
	DB	100					; 00000064H
	DB	0
_main	ENDP
_TEXT	ENDS
END

Release下的反汇编

Release下的,有些XXX SEGMENT头部标记没了

; Listing generated by Microsoft (R) Optimizing Compiler Version 19.25.28614.0 

	TITLE	d:\jave\work files\cpp\dasm\dasm\dasm\dasm.cpp
	.686P
	.XMM
	include listing.inc
	.model	flat

INCLUDELIB OLDNAMES

PUBLIC	??_C@_0BK@OEHJENGO@This?5is?5Global?5const?5str?4@ ; `string'
PUBLIC	??_C@_0N@KNIDPCKA@Hello?5world?$CB@		; `string'
PUBLIC	??_C@_0BL@LGABGNAE@This?5is?5Seconds?5const?5str?4@ ; `string'
PUBLIC	??_C@_0BL@DIJMBJEP@This?5is?5Thrid?5dynamic?5str?4@ ; `string'
PUBLIC	?s_i1@@3HA					; s_i1
PUBLIC	?s_f_ui@@3MA					; s_f_ui
PUBLIC	?s_f1@@3MA					; s_f1
PUBLIC	?c_str@@3PBDB					; c_str
PUBLIC	?s_i@@3HA					; s_i
PUBLIC	?c_str_ui@@3PBDB				; c_str_ui
EXTRN	@__security_check_cookie@4:PROC
?s_f_ui@@3MA DD	01H DUP (?)				; s_f_ui
?c_str_ui@@3PBDB DD 01H DUP (?)				; c_str_ui
_BSS	ENDS
?s_i1@@3HA DD	0ff001122H				; s_i1
?s_f1@@3MA DD	03f6353f8r			; 0.888	; s_f1
?c_str@@3PBDB DD FLAT:??_C@_0BK@OEHJENGO@This?5is?5Global?5const?5str?4@ ; c_str
?s_i@@3HA DD	03e7H					; s_i
CONST	ENDS
;	COMDAT ??_C@_0BL@DIJMBJEP@This?5is?5Thrid?5dynamic?5str?4@
CONST	SEGMENT
??_C@_0BL@DIJMBJEP@This?5is?5Thrid?5dynamic?5str?4@ DB 'This is Thrid dyn'
	DB	'amic str.', 00H				; `string'
CONST	ENDS
;	COMDAT ??_C@_0BL@LGABGNAE@This?5is?5Seconds?5const?5str?4@
CONST	SEGMENT
??_C@_0BL@LGABGNAE@This?5is?5Seconds?5const?5str?4@ DB 'This is Seconds c'
	DB	'onst str.', 00H				; `string'
CONST	ENDS
;	COMDAT ??_C@_0N@KNIDPCKA@Hello?5world?$CB@
CONST	SEGMENT
??_C@_0N@KNIDPCKA@Hello?5world?$CB@ DB 'Hello world!', 00H ; `string'
CONST	ENDS
;	COMDAT ??_C@_0BK@OEHJENGO@This?5is?5Global?5const?5str?4@
CONST	SEGMENT
??_C@_0BK@OEHJENGO@This?5is?5Global?5const?5str?4@ DB 'This is Global con'
	DB	'st str.', 00H				; `string'
CONST	ENDS
PUBLIC	_main
EXTRN	__fltused:DWORD
; Function compile flags: /Ogtp
; File D:\jave\Work Files\CPP\dasm\DASM\DASM\DASM.cpp
;	COMDAT _main
_TEXT	SEGMENT
_main	PROC						; COMDAT

; 14   :     const char *l_c_str_helloworld = "Hello world!"; // local const string,明文编写字符串比较特别,通过反汇编可以看到,右的值都是常量
; 15   :     const char* l_c_str_second = "This is Seconds const str."; // local const string,明文编写字符串比较特别,通过反汇编可以看到,右的值都是常量
; 16   :     char l_d_str_thrid[50] = "This is Thrid dynamic str."; // local dynamic string,明文编写字符串比较特别,通过反汇编可以看到,右的值都是常量
; 17   : 
; 18   :     l_d_str_thrid[0] = '#'; //明文编写字符不会有常量,因为当做一个byte字节数据来处理
; 19   : 
; 20   :     int l_i = s_i; // local int = static int
; 21   :     int l_i1 = s_i1; // local int = static int
; 22   :     int l_f_ui = s_f_ui; // local float = static float
; 23   :     int l_f1 = s_f1; // local float = static float
; 24   :     const int l_c_i = c_i; // local const int = const int,通过反汇编可以看到,c_i被当做立即数使用了,c_i不存在常量区
; 25   :     //const int* p_l_c_i = &c_i; // 但是如果有代码使用到c_i的地址,c_i在常量区就会有储存,这是编译器的优化
; 26   :     const float l_c_f = c_f; // local const float = const float,通过反汇编可以看到,c_f在常量区有存储,这与const int是有区别的
; 27   :     const char* l_c_str_ui = c_str_ui; // local const char* = const char*
; 28   :     const char* l_c_str = c_str; // local const char* = const char*
; 29   : 
; 30   :     return 0;

	xor	eax, eax

; 31   : }

	ret	0
_main	ENDP
_TEXT	ENDS
END

Release下的逻辑代码都因为优化删除了。

但是一些全局的变量,和常量在生成的目标文件也是存在的。

删除 JMC 汇编检测代码 - CheckForJustMyCode

编译出来的汇编代码代码可以看到每一个函数调用都有一段__CheckForDebuggerJustMyCode,觉得碍眼也可以通过:项目->属性->配置属性->C/C+±>常规->支持仅我的代码调式:->选“否” 来关闭这些代码的调用。
在这里插入图片描述

上面就是简单介绍一些查看反汇编的内容


栈帧

栈:stack
栈帧: stack frame

简单理解:栈帧 就是 里头的 一帧数据

有些简单或是底层、嵌入式的汇编程序编写,可能没有栈帧(反正就是不使用高级语言编写的程序,有可能一个函数都没调用,都是由基础指令处理的程序,即:没有call),但理论上可以通过一些类似一些 push, pop, mov, jmp,等命令来控制BP(Based Pointer),SP(Stack Pointer),IP(Instruction Pointer)模拟。以此来实现类似的栈帧功能。

但要看懂高级语言的反汇编指令(有call指令调用的),需要了解:栈帧。

关于栈帧的理解可参考:

看完上面的一些零零散散的文章后,可总结

总结

注意下面描述时,需要区别:栈,栈帧两个不同的概念

  • 系统维护栈,这个栈指针内存对象假设声明为:char *pStack;
  • 与数据结构中的栈差不多的,都是后进先出(后push,先pop)栈内存大小:每个独立进程都有的一份运行需要的栈数据,通常是2M的大小
    • 即:char *pStack = malloc(2 * 1024 * 1024);
    • 然后定义一个栈底指针:char *pStackBased = pStack + (2 * 1024 * 1024); - SBP(Stack Based Pointer)
    • 然后定义一个栈顶指针:*char *pStackTop = pStack; - ESP(与汇编的一样的名字)
    • 由上面的SBP,ESP指向的地址位置,就可以看出此栈与数据结构中的栈不太一样
      • push时,栈顶pStackTop的地址是减少的,在汇编里头一般可以看到:push eax会是push 999,类似下列伪代码:
      • pop时,栈顶 pStackTop 的地址是增加的,在汇编里头一般可以看到:pop ebp,类似下列伪代码:
void push(const data_type data) {
	*(pStackTop--) = data; // 先放置到sTackTop上,再偏移指针到下一个位置,注意这里的pStackTop是--的,因为这个系统栈的栈底在最高的地址
}
void pop(register_type &register_ref) {
	register_ref = *(++pStackTop); // 先偏移到上一次栈顶,再取栈顶上存放的数据
}
  • 如果我们的程序调用层次太多,就容易调用栈溢出(假设栈的基地址为:SBP(Stack Based Pointer),即我们前面假设的:pStack,溢出就是:(SBP - ESP) > (2 * 1024 * 1024)),常见的就是我们程序中的无限递归的BUG

栈帧

  • 栈帧是系统维护的内存中的一块内存,只不过它仅仅包含的是某个方法调用时的函数栈帧状态需要的数据,一个栈帧通常包含一些状态数据:EBP、ESP、EIP
EBP, ESP, EIP
  • 栈帧的状态数据范围:就是EBP(高地址)~ESP(底地址)之间的内存数据
    • EBP:Extend Based Pointer,一直指向的是当前栈帧的基地址,也叫栈帧的底,相对ESP来说,EBP是高地址,在整个函数调用的栈帧中是不变的,除非到准备弹出该栈帧时,会设置回上一个栈帧(父级调用函数)的EBP,
    • ESP:Extend Stack Pointer,一直指向的是当前栈帧的顶,相对EBP来说,ESP是底地址,也是一直指向栈的栈顶(注意是栈的栈顶),栈的栈顶与栈帧的栈顶都是相同的,也就说ESP当前会一直指向栈顶,与栈帧顶,与前面介绍栈的ESP一样push、pop都会减少与增加ESP的地址。
    • EIP:Extend Instruction Pointer,一直指向CPU当前准备需要执行的指令指针。asm 函数调用call其中就包含了对EIP定位到指定函数指令首地址(call functionAddress包含两个操作等价于伪代码:(一) push (current eip + size(current cmd)),(二)mov eip, functionAddress

所以一个栈帧的状态需要上面三个数据:EBP,ESP,EIP

那么父函数调用子函数,然后子函数执行完了之后如何恢复回父函数呢?

这句话也可以这么问:

弹出一个栈帧,如何将EBP,ESP,EIP恢复到上一帧的这些数据呢?

答案就是:

在构建新的一个栈帧时,就存有上一帧(上一个函数)的这三个EBP,ESP,EIP数据即可。

所以我们在反汇编代码中可以看到(不开启代码优化)每个函数调用前都用类似下面的码:

示例对应的C++源码:

#include<iostream>
#include<typeinfo>
//#include"my_funcs.h"
int 
//_cdecl 
_stdcall
add(int a, int b, int c)
{
	int local1 = 0;
	int local2 = 10;
	int local3 = 1;
	long long local4 = 2;
	return a + b + c + local1 + local2;
}

int main() {
	int a = 1;
	int b = 2;
	int c = 3;
	int d = add(a, b, c);
	printf("d = %d\n", d);
	//d = multiply(b, d);
	//printf("d = %d\n", d);
	return c;
}

下面是反汇编后add函数前、中、后的处理

调用函数前

在这里插入图片描述

add函数有三个参数,在调用前,都push到栈里,参数入栈的顺序是按:调用约定来决定的。调用约定同时也决定了函数栈帧在恢复到上一个栈帧是在 被调用 函数栈帧中恢复,还是在上一个调用 函数中的栈帧恢复。

调用函数内

在这里插入图片描述

  • push ebp 先将上一个栈帧的 ebp 备份到当前栈顶
  • mov ebp, espesp寄存器的值赋值给ebp寄存器,可理解为:将当前栈顶作为此栈帧的新的ebp
  • sub esp, 14hesp寄存器的值减少 14h,可理解为:分配好此栈帧的内存大小,大小为十六进制的:14h,对应十进制为:20
其他常用寄存器 EAX
  • EAX:Extend Accumulator X,Accumulator是累积、累加的意思,就是一般做运算用的,X代表他是一个通过寄存器的意思。用于栈帧弹出前,将该函数栈帧的返回值设置到EAX,以便恢复到上一个栈帧的下一条指令处理用(如果有返回值的话),下一条命令只要接收EAX寄存器的值就可以了。在反汇编的代码中除了常在在函数结尾返回值对EAX赋值处理,在一般的立即数赋值或是初始化都话看到有此寄存器的频繁使用
call 指令

可参考:“The call instruction pushes the return address onto the stack then jumps to the destination.”
或是看我之前翻译

call functions-address

相当于

push eip+2 ; // 2 是不确定,不通编译器、或是不同编译平台目标的指令处理的下一条指令是不同长度的, eip+2组合就相当于下一条指令的地址,push eip+2就将它备份入栈,返回后续ret指令恢复eip指令地址而使用的备份数据
jump functions-address
ret 指令

可参考:“The ret instruction pops and jumps to the return address on the stack. A nonzero #n in the RET instruction indicates that after popping the return address, the value #n should be added to the stack pointer.” - MS 文档
或是看我之前翻译

简单的描述 ret 指令,相当远下列过程:

ret 后无数值
push ebp
mov ebp, esp
... ; // 无局部变量,所以没有申请栈
ret

相当于

push ebp
mov ebp, esp
...
jump return-address
ret 后有数值的
push ebp
mov ebp, esp
sub esp, 0x0ch ; 分配有12个byte的局部变量的空间
...
ret 0x0ch ; 0x0ch == 12

相当于

push ebp
mov ebp, esp
sub esp, 0x0ch ; 分配有12个byte的局部变量的空间
...
add esp, 0x0ch ; 12 个 byte的局部变量的空间清理
jump return-address

有了前面了解:call、ret指令后,就可以了解:调用函数末端-返回-清理栈

调用函数末端-返回-清理栈
_stdcall

在这里插入图片描述

_stdcall 调用约定中就指令在callee中清理参数压栈的空间

_cdecl

在这里插入图片描述
_cdecl 调用约定中没有清理

因为_cdecl是在caller中清理参数压栈的空间
在这里插入图片描述

在_cdecl 中,push三个int,每个int 占4 个byte,一共12个byte,十进制的12相当于十六进制就是0x0ch,在caller中add esp, 0ch删除参数压栈的空间(所以这样时为何_cdecl可以处理变长参数,因为在caller压栈所少个是知道的,在caller再根据数量清理即可)

References

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值