C++20增加的生成器特性

不得不说,C++的语法真是越来越高级了,编译器为程序做的事情也是越来越多了。
比方说下面的这个程序,有两个点可以说说:

  1. 生成器 —— 编译器为了实现生成器,不得不把这一个函数拆成两个部分,分为初始化部分和程序运行部分。初始化部分主要用来初始保存生成器运行状态的内存空间。每当co_yield返回后,这片内存空间需要保持当前变量的值,以方便程序再次进入生成器后继续运行。

  2. 异步 —— 在新的标准里,我们实现异步的编码成本更低了。编译器同样为我们做了大量工作,这个例子中,主线程执行到subfuc函数的co_await后,会启动两个线程,一个执行异步函数awaitfuc,另外一个等待这个函数结束执行后面的代码,而主线程本身则是跳出函数执行printf函数。没错,我们简简单单的一句话就让编译器生成了这么多代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <experimental/generator>
#include <future>
#include <windows.h>
using namespace std::experimental;
generator<int> foo()
{
int p = 1;
int q = 2;
for (int j = 0; j < 10; j++) {
for (int i = 0; i < 10; i++) {
co_yield i + p;
}
}
int pp = 3;
}
std::future<int> awaitfuc()
{
Sleep(5000);
co_return 100;
}
std::future<int> subfuc()
{
auto p = co_await std::async(awaitfuc);
printf("return 100\n", p.get());
for (int i : foo()) {
printf("%d ", i);
}
co_return 0;
}
int main()
{
subfuc();
printf("hello\n");
Sleep(100000);
return 0;
}

Tips

调试器是怎么匹配程序的符号文件的

微软的天才软件工程师们设计的PE(Portable Executable)文件数据结构有极强的扩展性和兼容性。我们关心的符号文件信息存储在PE结构中,一个叫做Debug Directory的节里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
0:000> !dh -f ntdll
File Type: DLL
FILE HEADER VALUES
8664 machine (X64)
7 number of sections
590296CE time date stamp Thu Apr 27 18:11:42 2017
0 file pointer to symbol table
0 number of symbols
F0 size of optional header
2022 characteristics
Executable
App can handle >2gb addresses
DLL
OPTIONAL HEADER VALUES
20B magic #
9.00 linker version
FB800 size of code
A9600 size of initialized data
0 size of uninitialized data
0 address of entry point
1000 base of code
----- new -----
00000000774e0000 image base
1000 section alignment
200 file alignment
3 subsystem (Windows CUI)
6.01 operating system version
6.01 image version
6.01 subsystem version
1AA000 size of image
400 size of headers
1B5DB0 checksum
0000000000040000 size of stack reserve
0000000000001000 size of stack commit
0000000000100000 size of heap reserve
0000000000001000 size of heap commit
140 DLL characteristics
Dynamic base
NX compatible
101200 [ F1A3] address [size] of Export Directory
0 [ 0] address [size] of Import Directory
14E000 [ 5A028] address [size] of Resource Directory
13B000 [ 127EC] address [size] of Exception Directory
1A2E00 [ 4300] address [size] of Security Directory
1A9000 [ 4E8] address [size] of Base Relocation Directory
FC58C [ 38] address [size] of Debug Directory
0 [ 0] address [size] of Description Directory
0 [ 0] address [size] of Special Directory
0 [ 0] address [size] of Thread Storage Directory
0 [ 0] address [size] of Load Configuration Directory
0 [ 0] address [size] of Bound Import Directory
0 [ 0] address [size] of Import Address Table Directory
0 [ 0] address [size] of Delay Import Directory
0 [ 0] address [size] of COR20 Header Directory
0 [ 0] address [size] of Reserved Directory

使用!dh命令可以显示PE文件的关键信息,这里可以看到Debug Directory的偏移地址是FC58C,大小是38个字节,其对应结构体是_IMAGE_DEBUG_DIRECTORY。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
typedef struct _IMAGE_DEBUG_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Type;
DWORD SizeOfData;
DWORD AddressOfRawData;
DWORD PointerToRawData;
} IMAGE_DEBUG_DIRECTORY, *PIMAGE_DEBUG_DIRECTORY;
#define IMAGE_DEBUG_TYPE_UNKNOWN 0
#define IMAGE_DEBUG_TYPE_COFF 1
#define IMAGE_DEBUG_TYPE_CODEVIEW 2
#define IMAGE_DEBUG_TYPE_FPO 3
#define IMAGE_DEBUG_TYPE_MISC 4
#define IMAGE_DEBUG_TYPE_EXCEPTION 5
#define IMAGE_DEBUG_TYPE_FIXUP 6
#define IMAGE_DEBUG_TYPE_OMAP_TO_SRC 7
#define IMAGE_DEBUG_TYPE_OMAP_FROM_SRC 8
#define IMAGE_DEBUG_TYPE_BORLAND 9
#define IMAGE_DEBUG_TYPE_RESERVED10 10
#define IMAGE_DEBUG_TYPE_CLSID 11
1
2
3
4
5
6
7
8
9
10
11
0:000> dt ntdll+FC58C ole32!_IMAGE_DEBUG_DIRECTORY
+0x000 Characteristics : 0
+0x004 TimeDateStamp : 0x590288a9
+0x008 MajorVersion : 0
+0x00a MinorVersion : 0
+0x00c Type : 2
+0x010 SizeOfData : 0x22
+0x014 AddressOfRawData : 0xfc5c8
+0x018 PointerToRawData : 0xfb9c8

需要注意的是这三个数据成员,Type,SizeOfData以及AddressOfRawData。其中Type是Debug数据类型,SizeOfData是数据大小,AddressOfRawData是数据对应的内存地址。通过dt命令,可以查看结构体和数据的对应关系。从上面的输出可知Debug数据类型是CODEVIEW,数据大小是0x22个字节,数据的内存偏移是0xfc5c8。

1
2
3
4
5
6
7
8
9
10
11
0:000> db ntdll+0xfc5c8
00000000`775dc5c8 52 53 44 53 49 7b 4d 74-81 7b 0c 47 a2 d8 a8 d2 RSDSI{Mt.{.G....
00000000`775dc5d8 62 fc 8a 29 02 00 00 00-6e 74 64 6c 6c 2e 70 64 b..)....ntdll.pd
00000000`775dc5e8 62 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 b...............
00000000`775dc5f8 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
00000000`775dc608 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
00000000`775dc618 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
00000000`775dc628 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
00000000`775dc638 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

使用db命令查看这部分数据,我们可以发现ntdll.pdb的字符串。实际上,通过type已经知道了Debug数据类型是CODEVIEW,这样就可以确定数据的结构体是:

1
2
3
4
5
6
7
8
9
struct CV_INFO_PDB
{
DWORD CvSignature;
GUID Signature;
DWORD Age;
BYTE PdbFileName[];
} ;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
0:000> dt _guid ntdll+0xfc5c8+4
ntdll!_GUID
{744d7b49-7b81-470c-a2d8-a8d262fc8a29}
+0x000 Data1 : 0x744d7b49
+0x004 Data2 : 0x7b81
+0x006 Data3 : 0x470c
+0x008 Data4 : [8] "???"
可以看到CvSignature = “RSDS”, Signature = {744d7b49-7b81-470c-a2d8-a8d262fc8a29},Age = 2,PdbFileName=“ntdll.pdb”。
0:000> !lmi ntdll
Loaded Module Info: [ntdll]
Module: ntdll
Base Address: 00000000774e0000
Image Name: ntdll.dll
Machine Type: 34404 (X64)
Time Stamp: 590296ce Thu Apr 27 18:11:42 2017
Size: 1aa000
CheckSum: 1b5db0
Characteristics: 2022 perf
Debug Data Dirs: Type Size VA Pointer
CODEVIEW 22, fc5c8, fb9c8 RSDS - GUID: {744D7B49-7B81-470C-A2D8-A8D262FC8A29}
Age: 2, Pdb: ntdll.pdb
CLSID 4, fc5c4, fb9c4 [Data not mapped]
Image Type: FILE - Image read successfully from debugger.
C:\Windows\SYSTEM32\ntdll.dll
Symbol Type: PDB - Symbols loaded successfully from image path.
d:\symbols\ntdll.pdb\744D7B497B81470CA2D8A8D262FC8A292\ntdll.pdb
Load Report: public symbols , not source indexed
d:\symbols\ntdll.pdb\744D7B497B81470CA2D8A8D262FC8A292\ntdll.pdb

再来看看Windbg匹配ntdll.pdb的真实路径,d:\symbols\ntdll.pdb\744D7B497B81470CA2D8A8D262FC8A292\ntdll.pdb。对比一下就可以发现其中的奥秘。原来Windbg识别执行程序的PDB路径是依赖guid,age和PdbFileName。具体来说就是 {符号设置路径}{PdbFileName}{guid}{age}{PdbFileName}。
如果想写程序获取这些信息并不需要像上面那样解析PE文件结构,实际上微软给我们提供了这方面的支持,在dbghelp.dll里导出了一个叫做SymSrvGetFileIndexInfo的函数,这个函数获得的SYMSRV_INDEX_INFO结构中,就包含以上我们需要的数据。

Debugging

测试math-plugin

Inline

Simple inline \(a = b + c\).

This equation \(\cos 2\theta = \cos^2 \theta - \sin^2 \theta = 2 \cos^2 \theta - 1 \) is inline.

Block

$$\frac{\partial u}{\partial t}
= h^2 \left( \frac{\partial^2 u}{\partial x^2} +
\frac{\partial^2 u}{\partial y^2} +
\frac{\partial^2 u}{\partial z^2}\right)$$

$$\begin{aligned} \dot{x} & = \sigma(y-x) \\ \dot{y} & = \rho x - y - xz \\ \dot{z} & = -\beta z + xy \end{aligned}$$

Tips

调试COM的一个tip

最近遇到朋友的一个程序崩溃,原因是接口没有释放的时候调用了CoUninitialize,接着才释放接口。这个应该是个很明显的问题,但是朋友告诉我以前代码就是这个样子的,没有崩溃过,最近修改了部分代码但并不是这一块的。为了看看究竟什么回事,我把没有崩溃的程序抓了dump,看了COM的初始化引用计数:

1
2
3
4
5
6
7
8
9
10
11
0:000> dt _teb @$teb ReservedForOle
ntdll!_TEB
+0x1758 ReservedForOle : 0x00000000`00271b00 Void
0:000> dt ole32!SOleTlsData 0x00000000`00271b00 cComInits pNativeApt
+0x028 cComInits : 5
+0x080 pNativeApt : 0x00000000`00272680 CComApartment
0:000> dt 0x00000000`00272680 CComApartment _AptKind
ole32!CComApartment
+0x010 _AptKind : 4 ( APTKIND_APARTMENTTHREADED )

没有崩溃的时候,引用计数确实不为0,也能看出是个STA。后来朋友发现,之所以之前没有崩溃,是因为之前线程加载的某个dll中,有初始化COM的调用,所以引用计数不为0。后来移开了这个dll,问题就出现了。

Tips

gotcha sdk 文件监控功能更新

在15年的一篇blog中,我介绍了gotcha sdk。gotcha sdk 全盘文件名搜索开发库

当时gotcha sdk没有提供文件监控功能,也就是说当搜索文件发生变化的时候,这个变化不会体现到搜索结果列表中。其实这个功能一直在todo list中,只不过忙的时候没时间写这部分代码,闲的时候又忘了。前几天终于有时间把这部分代码补上,升级了sdk。

gotcha sdk 代码SVN:
http://code.taobao.org/svn/gotcha_sdk/

NTInternals

切换到session 0

这是一个小技巧,可以帮助我们从session 1切换到session 0,并且获得system权限。有了system权限,可以做一些admin做不了的事情,具体哪些事情大伙可以自己挖掘。

1
2
切换到session 0: rundll32 winsta.dll WinStationSwitchToServicesSession
切换会原session: rundll32 winsta.dll WinStationRevertFromServicesSession

但是如果直接切换到session 0,会发现一个问题,我们没有桌面程序,所以什么事情也做不了。解决方法也很简单,创建一个explorer就可以了。但是普通方法创建explorer,怎么会不能创建到session 0,于是这里可想而知,我们需要一个服务来创建explorer。专门写一个服务程序未免太麻烦,这里可以使用cmd来快速创建explorer。

1
2
sc create desktop0 binpath= "cmd /c start explorer.exe" type= own type= interact
net start desktop0

虽然cmd不是服务,但是也会被运行起来,只不过不能与服务管理器交互,所以在超时的时候会被结束。不过那个时候已经没关系了,因为explorer已经创建起来了。接下来就可以切换了session 0,用system权限管理电脑了。

Tips

0cchext插件实用命令dttoc

最近给0cchext添加了一个实用的逆向命令,dttoc,这个命令可以把dt命令输出的结构体转化为C的结构,方便我们做逆向还原工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
0:000> !0cchext.dttoc nt!_peb
struct _PEB {
BYTE InheritedAddressSpace;
BYTE ReadImageFileExecOptions;
BYTE BeingDebugged;
union {
BYTE BitField;
struct {
BYTE ImageUsesLargePages:1;
BYTE IsProtectedProcess:1;
BYTE IsImageDynamicallyRelocated:1;
BYTE SkipPatchingUser32Forwarders:1;
BYTE IsPackagedProcess:1;
BYTE IsAppContainer:1;
BYTE IsProtectedProcessLight:1;
BYTE IsLongPathAwareProcess:1;
};
};
VOID* Mutant;
VOID* ImageBaseAddress;
_PEB_LDR_DATA* Ldr;
_RTL_USER_PROCESS_PARAMETERS* ProcessParameters;
VOID* SubSystemData;
VOID* ProcessHeap;
_RTL_CRITICAL_SECTION* FastPebLock;
_SLIST_HEADER* AtlThunkSListPtr;
VOID* IFEOKey;
union {
DWORD CrossProcessFlags;
struct {
DWORD ProcessInJob:1;
DWORD ProcessInitializing:1;
DWORD ProcessUsingVEH:1;
DWORD ProcessUsingVCH:1;
DWORD ProcessUsingFTH:1;
DWORD ReservedBits0:27;
};
};
union {
VOID* KernelCallbackTable;
VOID* UserSharedInfoPtr;
};
DWORD SystemReserved[1];
_SLIST_HEADER* AtlThunkSListPtr32;
VOID* ApiSetMap;
DWORD TlsExpansionCounter;
VOID* TlsBitmap;
DWORD TlsBitmapBits[2];
VOID* ReadOnlySharedMemoryBase;
VOID* SparePvoid0;
VOID** ReadOnlyStaticServerData;
VOID* AnsiCodePageData;
VOID* OemCodePageData;
VOID* UnicodeCaseTableData;
DWORD NumberOfProcessors;
DWORD NtGlobalFlag;
_LARGE_INTEGER CriticalSectionTimeout;
DWORD HeapSegmentReserve;
DWORD HeapSegmentCommit;
DWORD HeapDeCommitTotalFreeThreshold;
DWORD HeapDeCommitFreeBlockThreshold;
DWORD NumberOfHeaps;
DWORD MaximumNumberOfHeaps;
VOID** ProcessHeaps;
VOID* GdiSharedHandleTable;
VOID* ProcessStarterHelper;
DWORD GdiDCAttributeList;
_RTL_CRITICAL_SECTION* LoaderLock;
DWORD OSMajorVersion;
DWORD OSMinorVersion;
WORD OSBuildNumber;
WORD OSCSDVersion;
DWORD OSPlatformId;
DWORD ImageSubsystem;
DWORD ImageSubsystemMajorVersion;
DWORD ImageSubsystemMinorVersion;
DWORD ActiveProcessAffinityMask;
DWORD GdiHandleBuffer[34];
void* PostProcessInitRoutine;
VOID* TlsExpansionBitmap;
DWORD TlsExpansionBitmapBits[32];
DWORD SessionId;
_ULARGE_INTEGER AppCompatFlags;
_ULARGE_INTEGER AppCompatFlagsUser;
VOID* pShimData;
VOID* AppCompatInfo;
_UNICODE_STRING CSDVersion;
_ACTIVATION_CONTEXT_DATA* ActivationContextData;
_ASSEMBLY_STORAGE_MAP* ProcessAssemblyStorageMap;
_ACTIVATION_CONTEXT_DATA* SystemDefaultActivationContextData;
_ASSEMBLY_STORAGE_MAP* SystemAssemblyStorageMap;
DWORD MinimumStackCommit;
_FLS_CALLBACK_INFO* FlsCallback;
_LIST_ENTRY FlsListHead;
VOID* FlsBitmap;
DWORD FlsBitmapBits[4];
DWORD FlsHighIndex;
VOID* WerRegistrationData;
VOID* WerShipAssertPtr;
VOID* pUnused;
VOID* pImageHeaderHash;
union {
DWORD TracingFlags;
struct {
QWORD HeapTracingEnabled:1;
QWORD CritSecTracingEnabled:1;
QWORD LibLoaderTracingEnabled:1;
QWORD SpareTracingBits:29;
};
};
QWORD CsrServerReadOnlySharedMemoryBase;
DWORD TppWorkerpListLock;
_LIST_ENTRY TppWorkerpList;
VOID* WaitOnAddressHashTable[128];
};

Debugging

Delphi异常0EEDFADE

0EEDFADE是Delphi内部异常代码,该异常通常有7个参数,我们用的上的是第二个参数,这个参数指向的是Exception的对象,通过这个对象,我们就可以查出异常的一些信息。

以Delphi XE2为例,Class name的偏移为(不同的版本偏移有所不同):

1
2
x86_vmtClassName = -56(0x38);
x64_vmtClassName = -112(0x70);

我们可以用如下命令获取相关信息:

1
2
x86: da poi(poi(exception_object)-38)+1;du /c 100 poi(exception_object+4)
x64: da poi(poi(exception_object)-70)+1;du /c 100 poi(exception_object+8)

以上命令就能获取异常的类名,而exception_object+sizeof(pointer)则是Exception Message的所在偏移,这是一个unicode string。实际效果如下:

1
2
3
0:002> da poi(poi(003a2800)-38)+1;du /c 100 poi(003a2800 +4)
00b9ec47 "TTransportExceptionUnknown"
00375b8c "ServerTransport.Accept() may not return NULL"

当然,我们也可以设置event filter去截获异常:

1
2
x86: sxe -c "da poi(poi(poi(@ebp+1c))-38)+1;du /c 100 poi(poi(@ebp+1c)+4)" 0EEDFADE
x64: sxe -c "da poi(poi(poi(@rbp+48))-70)+1;du /c 100 poi(poi(@rbp+48)+8)" 0EEDFADE

Debugging

Windows 10设置系统DPI

现在的显示器分辨率越来越高2K,4K甚至5K,而很多程序并不支持这一的高分辨率,所以这些程序在桌面上会显示的很小,好在Windows 8以后的系统中,我们可以设置DPI来放大程序的窗口,如下图所示:

20170309145111

但是,微软并没有把设置DPI的接口文档化。所以我把这个功能逆了一下,还原的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/*
BOOL ApplyDpiSetting(int val);
val 为DPI要设置的数字,例如0是100%,1是125%,以此类推,注意250%以后是300%。
另外上面的对应关系只是通常情况下的,还有可能有其他对应关系,例如0是300%,-1是250%等等。
具体怎么对应可以通过GetDpiForMonitor函数来获取
*/
typedef struct _SET_DPI {
DISPLAYCONFIG_DEVICE_INFO_HEADER header;
ULONG val;
} SET_DPI;
BOOL ApplyDpiSetting(ULONG val)
{
UINT32 num_of_paths = 0;
UINT32 num_of_modes = 0;
DISPLAYCONFIG_PATH_INFO* display_paths = NULL;
DISPLAYCONFIG_MODE_INFO* display_modes = NULL;
BOOL retval = FALSE;
do
{
if (GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS,
&num_of_paths,
&num_of_modes) != ERROR_SUCCESS) {
break;
}
display_paths = (DISPLAYCONFIG_PATH_INFO*)calloc((int)num_of_paths, sizeof(DISPLAYCONFIG_PATH_INFO));
display_modes = (DISPLAYCONFIG_MODE_INFO*)calloc((int)num_of_modes, sizeof(DISPLAYCONFIG_MODE_INFO));
if (QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS,
&num_of_paths,
display_paths,
&num_of_modes,
display_modes,
NULL) != ERROR_SUCCESS) {
break;
}
SET_DPI dpi;
dpi.header.type = (DISPLAYCONFIG_DEVICE_INFO_TYPE)0xFFFFFFFC;
dpi.header.size = sizeof(dpi);
dpi.header.adapterId = display_paths[0].sourceInfo.adapterId;
dpi.header.id = display_paths[0].sourceInfo.id;
dpi.val = val;
if (DisplayConfigSetDeviceInfo((DISPLAYCONFIG_DEVICE_INFO_HEADER*)&dpi) == ERROR_SUCCESS) {
retval = TRUE;
}
} while (0);
if (display_paths) {
free(display_paths);
}
if (display_modes) {
free(display_modes);
}
return retval;
}

Tips

让编译器不推荐(deprecate)使用一个函数

在开发一些公共库函数的时候,我们常常会对函数进行改写,这个时候我们会希望使用者用新的函数。为了提醒使用者,我们可以通过将函数声明为deprecated,这样编译器在编译的时候会抛出一个C4995或者C4996的警告。这个警告我们应该也经常看到过,比如使用strcpy,编译器会提示我们使用strcpy_s。

使用这个编译器特性有两种方法:

  1. __declspec(deprecated)
  2. #pragma deprecated

当然我们还可以给警告自定义消息信息

1
__declspec(deprecated("** this is a deprecated function **")) void func2(int) {}

Tips