一次 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 做的比较到位,推荐

Emulate Haskell Infix Operator in CPP

Usage at a glance

#include <iostream>
#include <vector>

#include "infix_op.h"

int main()
{
    auto find = Infix([](const auto& container, const auto& value) {
        return std::find(container.begin(), container.end(), value);
    });

    auto seq = std::vector<int> { 1, 3, 5, 7, 9 };

    bool found = (seq | find | 7) != seq.cend();
    std::cout << found << std::endl;

    return 0;
}

Implementation:

#pragma once

#include <xutility>

template<typename BinaryFn>
struct InfixOp {
    explicit InfixOp(BinaryFn&& fn)
        : fn_(std::forward<BinaryFn>(fn))
    {}

    BinaryFn fn_;
};

template<typename BinaryFn>
InfixOp<BinaryFn> Infix(BinaryFn&& fn)
{
    return InfixOp<BinaryFn>(std::forward<BinaryFn>(fn));
}

template<typename T, typename BinaryFn>
struct InfixExpr {
    InfixExpr(T&& lhs, BinaryFn&& op)
        : lhs_(std::forward<T>(lhs)), op_(std::forward<BinaryFn>(op))
    {}

    template<typename U>
    decltype(auto) operator|(U&& rhs)
    {
        return op_(lhs_, std::forward<U>(rhs));
    }

    T lhs_;
    BinaryFn op_;
};

template<typename T, typename BinaryFn>
InfixExpr<T, BinaryFn> operator|(T&& lhs, InfixOp<BinaryFn> op)
{
    return InfixExpr<T, BinaryFn>(std::forward<T>(lhs), std::move(op.fn_));
}

Have fun.

一个轻量型的 Command-Mapping Macros

某 Bililive 沿用了 Chromium 负责在 UI 层不同模块通讯的 command/handler 的机制,但是因为 Chromium 对 command 机制使用得很节制,因此相关的代码量不多,其处理函数一直是一个大大的 switch...case

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void ExecuteCommandWithParams(Bililive* receiver, int command, const CommandParamsDetails& params)
{
switch (command) {
case IDC_ALPHA:
// handler code
break;
case IDC_BRAVO:
// handler code
break;
case IDC_CHARLIE:
// handler code
break;
...
}
}

然而到了 Bililive 这里,因为开发人员水平限制,没有最好模块内外的通讯规划,导致存在大量的 command 通信,而 handler 代码依然是直接原封不动写在 switch 里。于是 ExecuteCommandWithParams() 就包含了上千行的实现,而且由于都在一个函数内,没有语法上的 scope 和清晰的语义划分,维护起来相当痛苦。

一个有效的解决方案是采用类似 MFC Message-Mapping 的方式,将不同的 message handler 分离到单独的函数里。至于参数,因为所有 command 的参数都是一致的,直接简化了处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include "base/logging.h"
#define BEGIN_BILILIVE_COMMAND_MAP(cmd_receiver, cmd_id, cmd_params) \
{ \
auto __receiver = cmd_receiver; \
auto __id = cmd_id; \
const auto& __params = cmd_params; \
switch (cmd_id) { \
#define ON_BILILIVE_COMMAND(cmd_id, fn) \
case cmd_id: { \
fn(__receiver, __params); \
} \
break; \
#define ON_BILILIVE_COMMAND_UNHANDLED_ERROR() \
default: { \
NOTREACHED() << "Unhandled command " << __id; \
} \
break; \
#define END_BILILIVE_COMMAND_MAP() \
} \
}

于是处理相关的代码就变成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void OnAlpha(Bililive* receiver, const CommandParamsDetails& params)
{}
void OnBravo(Bililive* receiver, const CommandParamsDetails& params)
{}
void OnCharlie(Bililive* receiver, const CommandParamsDetails& params)
{}
void ExecuteCommandWithParams(Bililive* receiver, int command, const CommandParamsDetails& params)
{
BEGIN_BILILIVE_COMMAND_MAP(receiver, command, params)
ON_BILILIVE_COMMAND(IDC_ALPHA, OnAlpha)
ON_BILILIVE_COMMAND(IDC_BRAVO, OnBravo)
ON_BILILIVE_COMMAND(IDC_CHARLIE, OnCharlie)
ON_BILILIVE_COMMAND_UNHANDLED_ERROR()
END_BILILIVE_COMMAND_MAP()
}

Monthly Read Posts in Sep 2017

The Horror of the Standard Library

一个 allocator 引发的惨案…


CppCon2015: Extreme Type Safety with Opaque Typedefs

类型自带语义,但是 typedef/using 只是类型别名,编译器无法区分。而 opaque typedefs 声称可以通过通过 wrapper 的方式生成具有单独类型语义 typedefs。


HTTP Series

https://www.code-maze.com/http-series-part-1/
https://www.code-maze.com/http-series-part-2/
https://www.code-maze.com/http-series-part-3/
https://www.code-maze.com/http-series-part-4/
https://www.code-maze.com/http-series-part-5/

可以作为不错的 HTTP 协议扫盲


CppCon2015: C++ Requests - Curl for People

受到 python requests 启发的 C++ counterpart implementation。

基于 CURL,但是做了非常细致的封装。

看了一下 demo,感觉是个非常不错 curl wrapper


Android GC 那点事儿

行文杂乱,讲道理还不如看专业的 posts…


How to Return a Smart Pointer AND Use Covariance

为 C++ 中实现涉及 smart pointer 的 covariance 接口提供了一个非常不错而且可用的解决方案。

Fix 使用 GDB 调试 Clang 编译的程序时标准库类型始终显示 incomplete type

现象描述

系统是 Linux mint 18,其实就是 ubuntu 16.04 LTS,所用的 clang 版本是 apt 默认的 3.8;gdb 使用系统自带。

使用 clang 编译出来的二进制,哪怕开启了 -g 生成调试信息,在 gdb 下 std::string 始终显示为 incomplete type;而 primitive 类型的值可以正确显示

并且通过 apt 安装 lldb,使用 lldb 调试能够看到变量的值。

因此基本可以确定问题发生在 gdb 和 clang 这一对上

解决方案

来自路边社的资料:clang 的 linux 版本输出调试信息时,默认去掉了标准库相关的组件信息 (??? MMP)

在编译时追加 -fno-limit-debug-info 标志,就可以保证输出完整的调试信息

安利时刻

为什么使用 clang 编译的原因很简单,ubuntu 16.04 上的 apt 默认的 gcc/g++ 5.4 有 bug,编译我的项目代码一定会出错…对比直接从 ppa test 安装 gcc 6.x/7.x 而言,安装 clang 无疑最省事儿…

另外我发现这个问题的解决方案得益于 vscode-lldb-debugger 这个扩展。

正是这个扩展在输出里通过 warning 告诉我需要用相关的编译开关输出完整调试信息,这个问题才的可以解决。(从侧面说明程序员还是应该关注一下 warning XD)

最后安利一个 gdb gui 前端:Nemiver

不过现在看起来 linux 上最好的 gdb/lldb 前端应该是 vscode -.-