自动为 enum 类型添加位运算操作符

有时候需要在 C++ 里用 enum (class) 表示 flags,进行基础的 bitwise 运算,而哪怕是支持自动到 underlying integer 转换的 traditional enum,也需要额外的 cast 才能实现,所以在需要的时候为 enum 添加位运算支持还是有一定市场的。

单独为每个需要的 enum 提供相关重载可以实现细粒度的控制,并且必要的时候可以加上 range-check 的校验。

然而这里有两个问题:

  1. 大部分情况下不需要 range-check 这种检查,因为通常不会在接口这一层暴露
  2. 如果一个 module 里会用到两个以上的 enum-based-flags,那么单独实现会很蛋疼

所以一个解决方案是,提供基于函数模板的重载,并且利用 ADL 将这部分重载锁在一个具体的 namespace 内,例如马上(C++ 20)就要被废弃的 rel_ops

同时,我们希望这部分重载只对 enum / enum class 有效,其他类型不进行应用。可以通过 SFINAE 实现这点。

利用快下班的时间做了一个基本的实现:

// Casts an enum value into an equivalent integer.
template<typename E>
constexpr auto enum_cast(E e) noexcept
{
    return static_cast<std::underlying_type_t<E>>(e);
}

namespace enum_ops {

template<typename E>
constexpr std::enable_if_t<std::is_enum<E>::value, E> operator|(E lhs, E rhs) noexcept
{
    return E(enum_cast(lhs) | enum_cast(rhs));
}

template<typename E>
constexpr std::enable_if_t<std::is_enum<E>::value, E&> operator|=(E& lhs, E rhs) noexcept
{
    lhs = E(enum_cast(lhs) | enum_cast(rhs));
    return lhs;
}

template<typename E>
constexpr std::enable_if_t<std::is_enum<E>::value, E> operator&(E lhs, E rhs) noexcept
{
    return E(enum_cast(lhs) & enum_cast(rhs));
}

template<typename E>
constexpr std::enable_if_t<std::is_enum<E>::value, E&> operator&=(E& lhs, E rhs) noexcept
{
    lhs = E(enum_cast(lhs) & enum_cast(rhs));
    return lhs;
}

template<typename E>
constexpr std::enable_if_t<std::is_enum<E>::value, E> operator^(E lhs, E rhs) noexcept
{
    return E(enum_cast(lhs) ^ enum_cast(rhs));
}

template<typename E>
constexpr std::enable_if_t<std::is_enum<E>::value, E&> operator^=(E& lhs, E rhs) noexcept
{
    lhs = E(enum_cast(lhs) ^ enum_cast(rhs));
    return lhs;
}

template<typename E>
constexpr std::enable_if_t<std::is_enum<E>::value, E> operator~(E op) noexcept
{
    return E(~enum_cast(op));
}

}   // namespace enum_ops

测试代码:

enum class ChangeLevel : unsigned int {
    None = 0,
    Local = 1 << 0,
    Server = 1 << 1,
    All = Local | Server
};

TEST(EnumOps, General)
{
    using namespace enum_ops;

    ChangeLevel level = ChangeLevel::None;
    level |= ChangeLevel::Local;
    level |= ChangeLevel::Server;

    EXPECT_TRUE(level == ChangeLevel::All);
    EXPECT_TRUE(enum_cast(level & ChangeLevel::Local) != 0);
    EXPECT_TRUE(enum_cast(level & ChangeLevel::Server) != 0);
}

TEST(EnumOps, NonEnumType)
{
    using namespace enum_ops;

    unsigned int l = 1;
    unsigned int r = 1 << 1;
    auto rv = l | r;
    EXPECT_EQ(3, rv);

    // can't compile
    //std::string s1 = "hello";
    //std::string s2 = "world";
    //s1 | s2;
}

Monthly Read Posts in Nov 2017

The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)
CppCon 2014: Unicode in CPP

编码这块的内容基本这两个 post 就足够了


UNDERSTANDING SSL/TLS: PART 1 - WHAT IS IT?

预警:作者只写了 part 1 之后就太监了…


CppCon 2015: Implementation of a component-based entity system in modern C++

不明觉厉的东西,没看太明白,似乎游戏开发用的多?


CppCon 2015: Lambdas from First Principles

几乎是最好的 lambda 深入浅出型 talk


CppCon 2015: Racing the Filesystem

各家系统文件系统操作的各种坑,顺带推销做着自己的 Boost.AFIO 库

然而我想了一下,好似平时都没怎么考虑过这些 racing conditions…


CppCon 2015: Effective C++ Implementation of Class Properties

最后给的实现方式很巧妙。但是实话说,工程上使用这些东西是个风险,如果语言自身不提供相应语法糖,我宁愿自己手写 getter/setter

用 Wirshark 捕捉分析经过 localhost 的网络包

最近写 WinAntHttp 的时候需要用 Wireshark 捕捉并分析发往 mock server 的 http 请求。

然而因为 mock server 是直接跑在本机 localhost 上的,而在 Windows 上,发往 localhost 的网络包都不会经过网卡,所以 wireshark 基本无法捕捉。

研究了一会儿之后发现两个解决方案:

第一个是安装 npcap,这个是 wincap 的一个 alternative,但是支持本地请求。

不过因为这个需要替换掉原来的 wincap,所以我并没有采用。

第二个是使用 RawCap,这货能抓到本地网络包,并且输出的格式是 wireshark-compatible

1
2
# administrator privilege is required!
RawCap.exe 127.0.0.1 network_cap.pcap

考虑到使用频率和侵入性等因素,最终选择使用 RawCap

抓到包之后 wireshark 不能马上直接看到 http 请求,因为普通的 http 请求是和 server 的 80 端口通信,mock server 的 Flask 默认使用 5000 作为通信端口。

通过菜单 Analyze - Decode As,将 TCP 5000 端口按照 HTTP 协议解析,这样 wireshark 就可以正常的从 tcp packets 中拆出正确的 http message 了。

使用项目自带的 python 编译 bililive-win

众所周知,Google 喜欢拿 python 做各种工具链,比如构建系统;然而 G 家用的又是 python 2.x,并且目测在未来一段时间内都不会做升级,因此相关的工具链环境也被锁死在了 python 2.x。

某直播姬因为用的 chromium 的框架,部分资源文件的构建就依赖 python 2.x。

在系统安装 python 2.x 并且加入环境变量的情况下这不算个问题;然而如果想把 python 3.x 作为主 python 环境,那么构建一定会失败,哪怕项目的 third-party 下自己附带了一个 python 2.6….

研究了一下发现可以强制使用项目自带的 python 2.6:

找到目标项目的 vcxproj 文件,用编辑器(例如 VS Code)打开,搜索 python,确认是调用,将 python 改为指向 third-party 下的 exe。

例如,将 call python "..\tools\grit\grit.py 替换为 call $(SolutionDir)..\third_party\python_26\python.exe

对于涉及到 cygwin 的调用,就先将 third-party/python2_6 加入环境变量

call "$(ProjectDir)..\third_party\cygwin\setup_env.bat" &amp;&amp; set CYGWIN=nontsec&amp;&amp; 替换为 set PATH=..\..\third_party\python_26;%PATH% &amp;&amp; call "$(ProjectDir)..\third_party\cygwin\setup_env.bat" &amp;&amp; set CYGWIN=nontsec&amp;&amp;

保存后生效

一次 dump 分析的复盘

首先有主站的运营同学反馈某个用户的投稿工具一选择上传视频就崩溃,100% 重现。

要到 crash dump 之后挂上 windbg,首先用 lmvm bililive 检查一下用户使用的版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Image path: D:\ugc_assistant\2.0.0.1054\bililive.dll
Image name: bililive.dll
Browse all global symbols functions data
Timestamp: Fri Sep 1 17:53:54 2017 (59A92E32)
CheckSum: 00A12C47
ImageSize: 00A34000
File version: 2.0.0.1054
Product version: 2.0.0.1054
File flags: 0 (Mask 17)
File OS: 4 Unknown Win32
File type: 1.0 App
File date: 00000000.00000000
Translations: 0409.04b0
CompanyName: bilibili
ProductName: bilibili投稿工具
InternalName: bililive_dll
OriginalFilename: bililive.dll

拿到版本之后从构建机上拷出对应的 PDB,设置路径,重新加载符号。

因为投稿工具有大量的工作线程,所以紧接着用 ~# 检查发生异常的线程:

1
2
3
. 18 Id: 1a68.2620 Suspend: 0 Teb: fff72000 Unfrozen
Start: bililive!base::`anonymous namespace'::ThreadFunc (5da3abd0)
Priority: 0 Priority class: 32 Affinity: f

崩溃发生在一个工作线程,接下来用 ~18s 将这个线程设置为当前线程,并且用 .ecxr 切换到异常 context 上。

因为已经有 PDB 了,所以直接用 kPL 把 callback 附加对应的源码参数显示出来,同时不显示对应的源码文件和行号(因为对源码比较熟悉,定位到第一现场后可以直接调到源文件,全都打印出来反而容易干扰输出):

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
# ChildEBP RetAddr
00 084ff4c0 5dea879f bililive!_invalid_parameter_noinfo(void)+0xc
01 084ff4d8 5da6c8d9 bililive!rand_s(
unsigned int * _RandomValue = 0x084ff59c)+0x82
02 084ff5a0 5da6c936 bililive!`anonymous namespace'::RandUint32(void)+0x19
03 084ff5a8 5da51f65 bililive!base::RandUint64(void)+0x6
04 084ff670 5da52014 bililive!base::RandGenerator(
unsigned int64 range = 0x80000000)+0x95
05 084ff73c 5da1c54e bililive!base::RandInt(
int min = 0n0,
int max = 0n2147483647)+0x84
06 084ff8a8 5da1bfa3 bililive!bililive::VideoUploadTask::DoUpload(void)+0x22e
07 084ff9b0 5da36c46 bililive!bililive::VideoUploadTask::DoStart(void)+0x3b3
08 084ffaf4 5da3611d bililive!base::MessageLoop::RunTask(
struct base::PendingTask * pending_task = 0x084ffb44)+0x346
09 084ffb88 5da56fba bililive!base::MessageLoop::DoWork(void)+0x1cd
0a 084ffb94 5da579dd bililive!base::MessagePumpForIO::DoRunLoop(void)+0x7a
0b 084ffbb4 5da3686d bililive!base::MessagePumpWin::Run(
class base::MessagePump::Delegate * delegate = 0x084ffcc8)+0x3d
0c 084ffc7c 5da380e3 bililive!base::MessageLoop::RunInternal(void)+0x9d
0d 084ffc84 5da367a6 bililive!base::RunLoop::Run(void)+0x13
0e 084ffca8 5da3afeb bililive!base::MessageLoop::Run(void)+0x16
0f 084ffcb0 5da3b462 bililive!base::Thread::Run(
class base::MessageLoop * message_loop = 0x084ffcc8)+0xb
10 084ffd7c 5da3ac25 bililive!base::Thread::ThreadMain(void)+0xd2
11 084ffd90 751633ca bililive!base::`anonymous namespace'::ThreadFunc(
void * params = 0x000006ec)+0x55
12 084ffd9c 771d9ed2 kernel32!BaseThreadInitThunk+0xe
13 084ffddc 771d9ea5 ntdll!__RtlUserThreadStart+0x70
14 084ffdf4 00000000 ntdll!_RtlUserThreadStart+0x1b

发现异常是由 base::RandUint64() 内部调用的 CRT 的 rand_s() 触发的(这里 CRT 被静态链接了),并且看 _invalid_parameter_noinfo 的名字,很像是不满足函数参数一类的。

根据 MSDN 这里的描述rand_s() 仅会在参数指针为空时,才会往外抛异常,然而这里我们明显能看出来,参数不为空。

既然这样,那我们就跳到 stack frame 00 对应的源码去看看,然而……

正值某国开会,对境外站点的访问遭到了封锁,不能从微软的服务器拖对应的 PDB….

死马当活马医,既然如此就考虑通过反汇编入手,看看能否找到 root cause。

先利用 uf bililive!rand_s 反汇编整个函数,还好不是很长

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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
0:018> uf bililive!rand_s
bililive!rand_s [f:\dd\vctools\crt\crtw32\misc\rand_s.c @ 60]:
60 5dea871d 55 push ebp
60 5dea871e 8bec mov ebp,esp
60 5dea8720 51 push ecx
60 5dea8721 53 push ebx
60 5dea8722 56 push esi
61 5dea8723 ff359c171a5e push dword ptr [bililive!g_pfnRtlGenRandom (5e1a179c)]
61 5dea8729 ff154c64fd5d call dword ptr [bililive!_imp__DecodePointer (5dfd644c)]
61 5dea872f 8bd8 mov ebx,eax
62 5dea8731 8b4508 mov eax,dword ptr [ebp+8]
62 5dea8734 85c0 test eax,eax
62 5dea8736 7516 jne bililive!rand_s+0x31 (5dea874e) Branch
bililive!rand_s+0x1b [f:\dd\vctools\crt\crtw32\misc\rand_s.c @ 62]:
62 5dea8738 e87297ffff call bililive!_errno (5dea1eaf)
62 5dea873d 6a16 push 16h
62 5dea873f 5e pop esi
62 5dea8740 8930 mov dword ptr [eax],esi
62 5dea8742 e8eb61ffff call bililive!_invalid_parameter_noinfo (5de9e932)
62 5dea8747 8bc6 mov eax,esi
62 5dea8749 e9cf000000 jmp bililive!rand_s+0x100 (5dea881d) Branch
bililive!rand_s+0x31 [f:\dd\vctools\crt\crtw32\misc\rand_s.c @ 63]:
63 5dea874e 832000 and dword ptr [eax],0
63 5dea8751 57 push edi
65 5dea8752 85db test ebx,ebx
65 5dea8754 0f85a3000000 jne bililive!rand_s+0xe0 (5dea87fd) Branch
bililive!rand_s+0x3d [f:\dd\vctools\crt\crtw32\misc\rand_s.c @ 75]:
75 5dea875a 8b352464fd5d mov esi,dword ptr [bililive!_imp__LoadLibraryExW (5dfd6424)]
75 5dea8760 6800080000 push 800h
75 5dea8765 53 push ebx
75 5dea8766 bb4067135e mov ebx,offset bililive!`string' (5e136740)
75 5dea876b 53 push ebx
75 5dea876c ffd6 call esi
77 5dea876e 8b3d5464fd5d mov edi,dword ptr [bililive!_imp__GetLastError (5dfd6454)]
77 5dea8774 8945fc mov dword ptr [ebp-4],eax
77 5dea8777 85c0 test eax,eax
77 5dea8779 7528 jne bililive!rand_s+0x86 (5dea87a3) Branch
bililive!rand_s+0x5e [f:\dd\vctools\crt\crtw32\misc\rand_s.c @ 77]:
77 5dea877b ffd7 call edi
77 5dea877d 83f857 cmp eax,57h
77 5dea8780 750e jne bililive!rand_s+0x73 (5dea8790) Branch
bililive!rand_s+0x65 [f:\dd\vctools\crt\crtw32\misc\rand_s.c @ 81]:
81 5dea8782 6a00 push 0
81 5dea8784 6a00 push 0
81 5dea8786 53 push ebx
81 5dea8787 ffd6 call esi
81 5dea8789 8945fc mov dword ptr [ebp-4],eax
84 5dea878c 85c0 test eax,eax
84 5dea878e 7513 jne bililive!rand_s+0x86 (5dea87a3) Branch
bililive!rand_s+0x73 [f:\dd\vctools\crt\crtw32\misc\rand_s.c @ 86]:
86 5dea8790 e81a97ffff call bililive!_errno (5dea1eaf)
86 5dea8795 6a16 push 16h
86 5dea8797 5e pop esi
86 5dea8798 8930 mov dword ptr [eax],esi
86 5dea879a e89361ffff call bililive!_invalid_parameter_noinfo (5de9e932)
86 5dea879f 8bc6 mov eax,esi
86 5dea87a1 eb79 jmp bililive!rand_s+0xff (5dea881c) Branch
bililive!rand_s+0x86 [f:\dd\vctools\crt\crtw32\misc\rand_s.c @ 89]:
89 5dea87a3 68c862105e push offset bililive!`string' (5e1062c8)
89 5dea87a8 50 push eax
89 5dea87a9 ff152864fd5d call dword ptr [bililive!_imp__GetProcAddress (5dfd6428)]
89 5dea87af 8bd8 mov ebx,eax
90 5dea87b1 85db test ebx,ebx
90 5dea87b3 7522 jne bililive!rand_s+0xba (5dea87d7) Branch
bililive!rand_s+0x98 [f:\dd\vctools\crt\crtw32\misc\rand_s.c @ 92]:
92 5dea87b5 e8f596ffff call bililive!_errno (5dea1eaf)
92 5dea87ba 8bf0 mov esi,eax
92 5dea87bc ffd7 call edi
92 5dea87be 50 push eax
92 5dea87bf e8fe96ffff call bililive!_get_errno_from_oserr (5dea1ec2)
92 5dea87c4 59 pop ecx
92 5dea87c5 8906 mov dword ptr [esi],eax
92 5dea87c7 e86661ffff call bililive!_invalid_parameter_noinfo (5de9e932)
92 5dea87cc ffd7 call edi
92 5dea87ce 50 push eax
92 5dea87cf e8ee96ffff call bililive!_get_errno_from_oserr (5dea1ec2)
92 5dea87d4 59 pop ecx
92 5dea87d5 eb45 jmp bililive!rand_s+0xff (5dea881c) Branch
bililive!rand_s+0xba [f:\dd\vctools\crt\crtw32\misc\rand_s.c @ 94]:
94 5dea87d7 8b356462fd5d mov esi,dword ptr [bililive!_imp__EncodePointer (5dfd6264)]
94 5dea87dd 53 push ebx
94 5dea87de ffd6 call esi
95 5dea87e0 6a00 push 0
95 5dea87e2 8bf8 mov edi,eax
95 5dea87e4 ffd6 call esi
100 5dea87e6 b99c171a5e mov ecx,offset bililive!g_pfnRtlGenRandom (5e1a179c)
100 5dea87eb 8739 xchg edi,dword ptr [ecx]
100 5dea87ed 3bf8 cmp edi,eax
100 5dea87ef 7409 je bililive!rand_s+0xdd (5dea87fa) Branch
bililive!rand_s+0xd4 [f:\dd\vctools\crt\crtw32\misc\rand_s.c @ 109]:
109 5dea87f1 ff75fc push dword ptr [ebp-4]
109 5dea87f4 ff152c64fd5d call dword ptr [bililive!_imp__FreeLibrary (5dfd642c)]
bililive!rand_s+0xdd [f:\dd\vctools\crt\crtw32\misc\rand_s.c @ 109]:
109 5dea87fa 8b4508 mov eax,dword ptr [ebp+8]
bililive!rand_s+0xe0 [f:\dd\vctools\crt\crtw32\misc\rand_s.c @ 113]:
113 5dea87fd 6a04 push 4
113 5dea87ff 50 push eax
113 5dea8800 ffd3 call ebx
113 5dea8802 85c0 test eax,eax
113 5dea8804 7514 jne bililive!rand_s+0xfd (5dea881a) Branch
bililive!rand_s+0xe9 [f:\dd\vctools\crt\crtw32\misc\rand_s.c @ 115]:
115 5dea8806 e8a496ffff call bililive!_errno (5dea1eaf)
115 5dea880b c7000c000000 mov dword ptr [eax],0Ch
116 5dea8811 e89996ffff call bililive!_errno (5dea1eaf)
116 5dea8816 8b00 mov eax,dword ptr [eax]
116 5dea8818 eb02 jmp bililive!rand_s+0xff (5dea881c) Branch
bililive!rand_s+0xfd [f:\dd\vctools\crt\crtw32\misc\rand_s.c @ 118]:
118 5dea881a 33c0 xor eax,eax
bililive!rand_s+0xff [f:\dd\vctools\crt\crtw32\misc\rand_s.c @ 118]:
118 5dea881c 5f pop edi
bililive!rand_s+0x100 [f:\dd\vctools\crt\crtw32\misc\rand_s.c @ 118]:
118 5dea881d 5e pop esi
118 5dea881e 5b pop ebx
119 5dea881f 8be5 mov esp,ebp
119 5dea8821 5d pop ebp
119 5dea8822 c3 ret

那么发生问题的是哪一行呢?往回看调用栈第一个栈帧

1
00 084ff4c0 5dea879f bililive!_invalid_parameter_noinfo(void)+0xc

5dea879f 是第一个栈帧的 return address,也就是这个函数返回到父函数后要执行的下一条指令的地址,于是就能定位到

1
2
86 5dea879a e89361ffff call bililive!_invalid_parameter_noinfo (5de9e932)
86 5dea879f 8bc6 mov eax,esi

因为汇编的特殊性,比较好的选择是从问题的指令开始,往回倒推,着重小心涉及 branching 的指令。

NOTE:为了专注问题,这里跳过其他非相关的汇编代码

1
2
3
4
86 5dea8790 e81a97ffff call bililive!_errno (5dea1eaf)
86 5dea8795 6a16 push 16h
86 5dea8797 5e pop esi
86 5dea8798 8930 mov dword ptr [eax],esi

这里往 eax 指向的内存写入了 0x16,根据上下文,我猜 函数 bililive!_errno() 返回了 CRT errno 的地址,于是存到了 eax;翻一下文档,0x16 错误值代表的刚好是 EINVAL

根据

1
2
84 5dea878c 85c0 test eax,eax
84 5dea878e 7513 jne bililive!rand_s+0x86 (5dea87a3) Branch

我们知道,在此之前 eax 等于 0(否则已经跳转到另外一个地址,就不会执行到出错的地方了)。

1
2
3
4
81 5dea8782 6a00 push 0
81 5dea8784 6a00 push 0
81 5dea8786 53 push ebx
81 5dea8787 ffd6 call esi

前面 eax 为 0 是 esi 的执行结果,大概率是失败了(FALSE);这句直接把刚才 eax 分析的重要性给抹杀了,orz…

这里 esi 应该是某个函数指针,目前看有三个参数,最后两个都是 0 (别忘了 cdecl calling convention 下,参数从右往左压栈)

1
2
3
77 5dea877b ffd7 call edi
77 5dea877d 83f857 cmp eax,57h
77 5dea8780 750e jne bililive!rand_s+0x73 (5dea8790) Branch

这段比较有意思,因为无论 jne 是不是要跳,两个目的地都是我们刚才执行过的指令,这意味着,这里 eax 可能为 57h,也可能不是,不过还好不影响我们的主逻辑。

1
2
3
4
5
6
7
8
9
10
75 5dea875a 8b352464fd5d mov esi,dword ptr [bililive!_imp__LoadLibraryExW (5dfd6424)]
75 5dea8760 6800080000 push 800h
75 5dea8765 53 push ebx
75 5dea8766 bb4067135e mov ebx,offset bililive!`string' (5e136740)
75 5dea876b 53 push ebx
75 5dea876c ffd6 call esi
77 5dea876e 8b3d5464fd5d mov edi,dword ptr [bililive!_imp__GetLastError (5dfd6454)]
77 5dea8774 8945fc mov dword ptr [ebp-4],eax
77 5dea8777 85c0 test eax,eax
77 5dea8779 7528 jne bililive!rand_s+0x86 (5dea87a3) Branch

首先 jne 不跳,因为目标地址不在执行范围内,这意味着 eax 为 0。

同时可以发现 edi 保存的是 GetLastError() 的地址;并且这里的 edi 和上一段汇编代码的 edi 是一个

esi 保存了 LoadLibraryExW() 的地址,并且这个调用失败了(因为 eax 为 0)。

并且可以发现,上面分析过的有段汇编,同样执行了一次 esi

这里 LoadLibraryEx() 加载的函数名字的地址是 5e136740,用 db 查看一下发现是

1
2
5e136740 41 00 44 00 56 00 41 00-50 00 49 00 33 00 32 00 A.D.V.A.P.I.3.2.
5e136750 2e 00 44 00 4c 00 4c 00-00 00 00 00 00 00 00 00 ..D.L.L.........

在往上看,函数就到头了,而且没有直接跳到异常上下文的指令,所以基本上可以确定这个异常由 LoadLibraryEx() 调用失败引发

整理一下逻辑就是:

首先调用 bililive!_imp__DecodePointer 失败,然后进行一系列的 fallback,例如利用 LoadLibraryEx() 加载 ADVAPI32.DLL

如果第一次调用失败且 Last-Error-Code 是 57h,则换一套参数在调用一次 LoadLibraryEx(),否则就直接失败

但是这里无论是不是 57H,结果都一样,失败了…

既然是调用失败,那有必要看一下具体的 last error code 是啥。

直接用 !teb 看这个线程 TEB 的数据,里面就有 last-error-code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
0:018> !teb
TEB at fff72000
ExceptionList: 084fed8c
StackBase: 08500000
StackLimit: 084fe000
SubSystemTib: 00000000
FiberData: 00001e00
ArbitraryUserPointer: 00000000
Self: fff72000
EnvironmentPointer: 00000000
ClientId: 00001a68 . 00002620
RpcHandle: 00000000
Tls Storage: 03ef1910
PEB Address: fffde000
LastErrorValue: 126
LastStatusValue: c0000135
Count Owned Locks: 0
HardErrorMode: 0

126 意味着 Error code: (Win32) 0x7e (126) - The specified module could not be found.

lmo 可以看到

1
2
3
4
756a0000 75740000 advapi32 (deferred)
Mapped memory image file: d:\dbgsymbols\sys_symbols\advapi32.dll\4CE7B706a0000\advapi32.dll
Image path: C:\Windows\SysWOW64\advapi32.dll
Image name: advapi32.dll

advapi32 被延迟加载了,但是系统还是能够找到的。这种情况下的无法加载,我能想到的大概只有:

  1. 这个 DLL 某个依赖的 DLL 找不到
  2. 加载行为被 HOOK 干掉了

至于具体是哪个就不得而知了。

不过起码我们找到了问题不是么…..

完整的 dump 和 PDB 包可以在这里下载:链接: https://pan.baidu.com/s/1i5xjGt3 密码: nnsh

不要问我为什么是百度网盘。。。现在能够方便做外链的免费网盘没几家了啊

发现一个(疑似)VS 2013 lambda 实现的 Bug

考虑如下代码段:

#include <iostream>

class Foo {
public:
    void Thunk()
    {
        auto lambda = [](Foo* f, const char* msg) {
            auto pfn = &Foo::Print;  // complained this statement
            (*f.*pfn)(msg);
        };

        lambda(this, "abc");
    }

    void Print(const char* msg)
    {
        std::cout << msg << std::endl;
    }
};

int main()
{
    Foo foo;

    foo.Thunk();

    return 0;
}

在 Visual Studio 2013 with update 5 上会提示编译警告:

Line 16: warning C4573: the usage of ‘Foo::Print’ requires the compiler to capture ‘this’ but the current default capture mode does not allow it

然而相同的代码在 Visual Studio 2017 上,以及 clang 3.8 上均不会出现类似警告。

我在 StackOverflow 上提了一个问题,然而截止本文发布前,并没有看起来靠谱的答案出现。

Monthly Read Posts in Oct 2017

Coroutines and Fibers. Why and When

所以什么时候各大语言的 coroutine 标准化工作可以结束


Efficient Minidumps 1 & 2

如何最经济的在 dump 中保存足够多的信息


lambda + shared_ptr = memory leak

Self-reference makes reference-count go weird.


Asynchronous and non-blocking IO
Blocking I/O, Nonblocking I/O, And Epoll
Async IO on Linux: select, poll, and epoll
I/O多路复用之 epoll 系统调用
Linux环境开发(一):同异步、阻塞的IO模型相关问题-async-blocking-io-model.html)

I/O multiplexing and Epoll 快速入门;最后两篇比较水,内容基本前三篇都有覆盖


CppCon 2015: Edouard Alligand & Joel Falcou “Introducing brigand”

yet another TMP library


CppCon 2015: Joshua Gerrard “The dangers of C-style casts”

老调重弹


How to infix your code - Pascal Bormann - CppCon 2015

Let’s get fun


Practical Move Semantics - Titus Winters - CppCon 2015


Traits Go Mainstream - Leor Zolman - CppCon 2015

这个 talk 对于解释为什么会有 SFINAE 做的比较到位,推荐