Monthly Read Posts in Nov 2016

Stack Frame Layout on X86-64

Main observations

For AMD64 X64 ABI compiliant platforms (such as Linux), the major changes include:

  • Introduces a red-zone storage area on the stack
  • Officially makes frame pointer explicitly optional

For Windows X64 ABI(though depictated in supplementary):

  • No red-zone; instead, a pre-allocated home-space is enforced for every function call
  • Cleanup of old calling convention legacy; no more worry about cdecl/fastcall/stdcall etc.

Understanding Security in IoT: SSL/TLS
我也想来谈谈HTTPS

Why HTTPS

Why HTTP is not secure?

Because it transports information in plain text and with no anti-eavesdroping protection.

How does it work

After TCP connection is established, the client and server will futher start TLS/SSL handshake:

  1. Client-Hello
  2. Server-Hello
  3. Authentication and Key Exchange
  4. Server-Finish
  5. Client-Finish

Extra Bonus

How to use openssl for https.

帧哥在他的技术博客分站上有三篇和 TLS/SSL 协议实现相关的 posts,可以放到下个月的 reading list 里

ON DRY AND THE COST OF WRONGFUL ABSTRACTIONS

Insight

Duplicating code sometimes can be a good thing (when the cost imposed by abstractions is too high); namely, context matters.

Reasons

Programmers spend far more time reading code (to understand logic behind) than writing code.

Abstractions cut both ways: every abstraction comes at a cost.

Every abstraction imposes an extra layer of knowledge, which places cognitive load on brain, and in turn, increases the time spent on understanding that piece of code.

In some extreme cases(everything is encapsulated in abstractions), the abstraction lost its value, and became a wrongful one.

Conclusion

Always abstract if the cost of abstraction does not surpass the cost of duplicating code.

But, it’s just hard to get it right in real practice.

Extra Comments

之前看过一篇类似的 post,标题大概是 dependency versus redundancy,印象中里面叙述的观点比这边更加的具体。

避免使用 breakpad 时调试模式下某些错误跳过调试器自动引发崩溃处理

现象

某个小朋友(虽然和我同年同届…)写代码时不注意,出现未知错误,但是即使调试器启用所有断点选项,也并没有断在出现错误的地方,而是直接进入了我写的 crash handler。

分析过程

在调用会出现错误的函数前加入

1
*static_cast<int*>(0x0) = 0xDEADBEEF;

调试运行,debugger 成功断下;说明相关功能均正常,问题应该只和该函数产生的错误有关。

打开 crash handler 生成的 dump 文件,设置符号表,发现错误发生原因是: vector::operator[] 访问下标越界,但是该错误直接触发 crash handler

解决方案

去掉 client 安装 crash handler 前的

1
_CrtSetReportMode(_CRT_ASSERT, 0);

即可解决问题。

原因

因为标准规定 vector::operator[] 操作不做 bounds checking,有问题情况下直接导致 undefined behavior;MSVC 为了更快诊断问题,Debug 模式下会对这个操作进行 CRT ASSERT。

_CrtSetReportMode() 屏蔽了 CRT ASSERT,所以直接进入了 crash handler。

推荐 CSAPP:3e 课程视频以及 Bomb Lab

经过差不多 7 年时间(2008 ~ 2015),CSAPP 终于出了 3rd edition;根据官方的 changelog,新版全面用 x64 的体系结构去替代了旧版的 ia-32。

不管手头有没有第三版的书(讲道理其实挺贵的,机工的翻译版 2016/12 会发行,但是不知道英文版/影印版什么时候发),课程的在线视频都是很好的一个补充手段。

视频地址:csapp:3e coruse videos

温馨提示:需要梯子 需要梯子 需要梯子

另外顺手强烈建议尝试一下课程提供的 labs,例如前两天在玩的 bomb lab,简直是专门量身为 chapter 3 相关内容定制的,可以迅速帮助学习者熟悉汇编和最基本的逆向知识。

与此形成强烈反差的是国内高校的课程和训练方式,简直何厚铧。

labs 地址:csapp:3e labs

(再次)温馨提示:需要梯子 需要梯子 需要梯子

最后贴一张 bomb lab 通关截图

(自白:前四关我真的是非常认真的玩的,后面两关实在太长了,disas 出来的内容都要好机屏幕才能显示完,实在没耐心了就直接“眼算栈平衡,手改RIP”给过去了…

在 WSL 的 VIM 里安装 YCM

注 1:此处的 WSL 指的是 Windows Subsytem for Linux,不是“猥琐流”

注 2:如果是在中国并且没有专用的全局科学上网线路(比如在自己家里),建议找一个类似 SS 的梯子,让 WSL 里的网络操作走代理,提高速度,避免因为某些连接被和谐导致悲剧发生。

如果有 SS,那么进入 WSL 后执行命令

1
2
export http_proxy=http://127.0.0.1:1080
export https_proxy=http://127.0.0.1:1080

对当前用户设置代理。

如果需要使用 apt-get 操作,那么需要首先 su 进入 root 账户,在 root 下设置代理。

此处设置的环境变量在某个 terminal 退出后自动销毁,下次进入 terminal 需要重复设置。如果你的代理非常稳定非常快,那么可以考虑改写 profile,避免每次手输。

Special thanks to @xqq here

更新自带的 VIM

因为 WSL 里的 ubuntu 是 14.04 LTS,因此里面附带的 vim 并不满足最新的 YCM 的最低版本要求,如果不升级会出现 ycm 工作异常。

1
2
sudo add-apt-repository ppa:pkg-vim/vim-daily
sudo apt-get update && sudo apt-get upgrade

安装构建基础依赖

主要包括:CMake 和 python-dev packages

1
2
sudo apt-get install build-essential cmake
sudo apt-get install python-dev python3-dev

安装 VIM 插件管理器 Vundle

1
git clone https://github.com/VundleVim/Vundle.vim.git ~/.vim/bundle/Vundle.vim

插件设置参考 这里

安装 YCM

虽然我们安装了 vundle,但是我们不会直接通过 vundle 装 ycm,因为太慢了。

当然如果你在国外或者有科学上网专线,那就无所谓了…不过谁让我们是穷屌丝呢。

首先进入目录 ~/.vim/bundle,然后把 YCM 的 repo 拉下来,记得要用 --recursive,因为现在的 ycm 依赖的第三方模块太多了…

1
git clone --recursive https://github.com/Valloric/YouCompleteMe.git

这个操作对你的梯子是一个极大的考验,如果中途 clone 挂了,就进入 ycm 的目录,接着用 submodule update 去继续拖代码即可。

想当初我的 SS 在这里断了 5-6 次,每一次 connection lost 都让我问候了一遍党和国家…

代码拉完之后还没结束,这个时候打开 .vimrc,把 ycm 加到 vundle 的设置列表里,然后打开 vim 走一遍 PluginInstall。你应该会看到 ycm 被飞快的安装完了(其实也就校验了一下😁)

编译 ycm

源代码拖完了就该轮到编译了

1
2
cd ~/.vim/bundle/YouCompleteMe
./install.py --clang-completer

脚本首先会去下载 clang-3.9,然后走一遍 cmake 的编译,等一会儿就好。

Debut

来一张最终效果图

在 Windows 上构建并接入最新发布分支的 breakpad

新直播姬项目重构的差不多了,于是前几天 leader 对我说:组织已经决定了,你来接入 crash dump 的处理收集!

于是我就念了句口号:Hail Hydra,然后开始研究怎么接入 google-breakpad

breakpad 一直是 chromium 收集 crash dump 的御用雏形(在其之上加了一些私货),不过之前在做大屎豹的时候一直都是别人在负责这块功能,所以并不熟悉;不过没吃过猪肉还是见过猪跑的。

项目最新的发布分支是 chrome_53, 那就选择从这个 remote branch 切出一个 local branch。但是切完之后我就发现不对劲了:特么以前一直随包附带的 gyp 目录呢???难道最新的发布版本修改了 Windows 上的构建流程?

按照 Google 的一贯尿性,这是极有可能的,看看隔壁 chromium 的 tool-chain 改了多少次就知道了;于是打开官方项目的 README 看看 build instructions,嗯,果然改了,踏马直接把 build on Windows 的相关内容给删了! WTF??!

Google 一下发现虽然 gyp 和 doc 被移除了,但是之前的 gyp 构建方案还是可以用的。(嗯,我日你个仙人板)

研究了一下,可用的构建流程如下:

  1. 在本地 clone 一个 gyp,直接用 master 分支就好,并且保证 gyp.bat 在环境变量 %PATH% 中(无所谓是你手动添加还是在命令行中用 set path 添加)
  2. 打开文件 breakpad\src\client\windows\breakpad_client.gyp,注释掉 targets : dependencies 列表中的最后三个和 test 有关的依赖引用;因为某些测试工程的编译依赖 gtest,但是当前项目并不附带,所以你直接编译肯定会一坨错误,所以这里直接不构建测试工程。当然如果你需要的化,自己根据 gyp 设置一下 gtest 目录。
  3. 在 breakpad 的项目目录打开 cmd,用 SET GYP_MSVS_VERSION=2013 设置目标 Visual Studio 工程的版本;如果要生成 VS 2015 的文件,版本改成 2015 就好了
  4. 运行 gyp.bat --no-circular-check src\client\windows\breakpad_client.gyp -Dwin_release_RuntimeLibrary=0 -Dwin_debug_RuntimeLibrary=1;最后两个参数设置的是 /MT(d) 还是 /MD(d)
    • 0:/MT
    • 1:/MTd
    • 2:/MD
    • 3:/MDd
  5. 生成的项目文件在目录 src\client\windows\ 中,直接打开编译即可;(Visual Studio 2015 编译可能会出现 warning C4091,导致编译失败,直接屏蔽掉这个错误)

使用上要注意的几个坑

Disclaimers:因为服务端暂时不支持,考虑到简易性,我在直播姬里用的是进程内抓 dump。

一个简单靠谱的 demo

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
namespace {
using google_breakpad::ExceptionHandler;
const wchar_t kCrashDumpDirName[] = L"crash_dumps";
const wchar_t kHintMessage[] = L"我...挂了...囧rz";
// We leak the object on purpose.
ExceptionHandler* exception_handler = nullptr;
bool OnMinidumpGenerated(const wchar_t* dump_path, const wchar_t* minidump_id, void* context,
EXCEPTION_POINTERS* exinfo, MDRawAssertionInfo* assertion, bool succeeded)
{
if (succeeded)
{
MessageBoxW(nullptr, kHintMessage, L"Error", MB_OK);
}
return succeeded;
}
void InstallCrashHandler()
{
// This is needed for CRT to not show dialog for invalid param
// failures and instead let the code handle it.
_CrtSetReportMode(_CRT_ASSERT, 0);
std::wstring dump_dir_path = SetupCrashDumpDirectory(kCrashDumpDirName);
exception_handler = new ExceptionHandler(dump_dir_path,
nullptr,
OnMinidumpGenerated,
nullptr,
ExceptionHandler::HANDLER_ALL,
MiniDumpNormal,
L"",
nullptr);
}
} // namespace
int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE prev, wchar_t*, int)
{
InstallCrashHandler();
// ...
return 0;
}

首先要保证项目的 include directory 中一定要包含 breakpad\src\ 这个路径,因为 breakpad 内部代码的 include 是基于这个假设

只需要在 exe 文件的入口函数(main 或者 WinMain)中设置 breakpad 的 exception handler 即可,哪怕你的项目有很多额外的 DLLs;Handler 是进程全局的,这点由操作系统保证(本来 breakpad 就是基于操作系统提供的 API 做的一个 wrapper)。

不过要注意在自己的代码中,尤其是用的第三方的 DLL,不要出现用了 SetUnexpectedHandler() 导致 override 了 breakpad 的 handler 的情况。因为这个我被坑过一下午 -‘’-

保存 crash dump 的文件夹一定要事先存在,breakpad 不会帮你创建,事实上,如果文件夹不存在,breakpad 的 handler 甚至工作不正常,还是走回 Windows 的崩溃反馈流程去了。

第二个参数是一个 exception filter callback,用来过滤一些异常,这里不需要直接填空。

第三个参数是 mini dump 写完之后的回调,用来做一些提示用户的善后操作,参数 succeeded 提示 dump 是否成功写入;通常情况下,这个 routine 返回后 breakpad 会自动结束程序,避免异常造成更进一步的破坏,这是非常合理的做法,所以不要在这个 callback 里做一些 non-trivial 的事情。

王之鄙视

最后鄙视一下 google breakpad team 这种不想做兼容就索性删掉相关内容的做法。

C++ is fine, it's you that suck

这篇主要内容是吐槽,干货不多。

曾经我以为只要是我设计的代码结构里,基本不会碰到需要 multiple inheritance 的情况,更不用说需要 virtual inheritance,结果没想到这周就被啪啪啪打脸了。

起因在于我要为底层的 obs properties 体系设计一个 wrapper,为上层提供类似的等价物。

properties 描述了一个 obs source 所具备的特性集合,不同的 obs source 拥有不同的 properties;而 properties 实质上又是一个由众多不同类型的 property 构成的集合,因此不同类型的 property 才是操作的基本单位。

一个 source 的 properties 可能实际上包含:

  • 描述数值类型的 int-property or float-property
  • 描述文字信息的 text-property
  • 描述一系列子属性的 list-property
  • etc.

但是蛋疼的地方在于,在 obs core 这一层,逻辑上不同类型的 property 却具有相同类型的数据类型,都由 obs_property_t 来表示;

于是,除了通吃的诸如 get_name(), get_description() 操作之外,obs 还提供了返回具体类型的函数接口,以及分别针对某个类型的一组操作函数接口。

考虑到几个原则

  • proxy layer 本身是一个屏蔽底层细节的模块,只能在边界暴露必要的接口
  • properties 要反映出是 property 的序列容器(能够便利定位到某个 property)
  • property 无论是共有的操作还是特定的操作都要暴露给外界,并且要屏蔽底层实现 property 的细节

最适合我的实现反而是拥有如下两条 hierarchical path 的多继承设计

1
2
3
4
5
6
7
Property [pure]
/ \
/ \
SpecificProperty[pure] PropertyImpl [实现基础操作,兼容 properties 的存储]
\ /
\ /
SpecificPropertyImpl

左侧两个基类都是 pure abstract class,负责对外暴露接口,右侧的 impl class 是在 proxy layer 中的实际类型。

由于考虑到 properties contains a series of property 的事实,PropertyImpl 实际上对外表现出值类型语义(嗯哼,我才不会说我硬生生的在另外的 properties wrapper 上给加上了迭代器支持…)。

上面继承出现了菱形继承,于是 virtual inheritance 就进来了,不然 Property 的析构函数就有两份,并且还会执行两次。

考虑上层在某些时候需要从 Property 获得更为具体的 SpecificProperty,以执行一些特定的属性操作,这个时候就需要利用 dynamic_cast 做一些 down-cast。

然而…..这是不可能的,因为整个程序是基于 chromium 的框架开发,而 chromium 强制把 RTTI 关闭了…

于是一个晚上加上一个白天,最后终于想出一个 workaround 给绕了过去…而在构想最终的 workaround 期间,我甚至有冲动要自己艹 memory layout 来计算偏移…

关闭 RTTI 是一个非常具有代表性的行为,和多重继承,虚继承类似,很多人认为这些东西是非常 evil 的;因为他们不光带来了性能开销,并且实际作用都可以藉由各种 workaround 实现。于是先把你的设计批判一番,再接着滔滔不绝的给你讲解各种可能可以用得上的 idioms…

然而,工程设计是非常 non-trivial 的,现实世界是非常 cruel 的,你永远不知道你面临的会是什么问题,尤其是遇到你的 code base 中存在来源于其他不受控制的代码的时候。

或许是对这种人已经感到了厌倦,甚至是无语,Bjarne Stroustrup 特意在他的 FAQ 里提到了这么两点:

  • language-supported inheritance is typically superior to workarounds for ease of programming, for detecting logical problems, for maintainability, and often for performance FAQ
  • It really bothers me when people think they know what’s best for your problem even though they’ve never seen your problem!! How can anybody possibly know that multiple inheritance won’t help you accomplish your goals without knowing your goals?!?!?!?!!! FAQ

C++ 之所以发展成今天这样,成为一个 multi-paradigm 的语言,本就来源于 BS 认为世界太过 undeterministic,依靠单一的范式很难精准有效的解决大部分问题。

回到前面 RTTI 的问题,十来年前,各大主流编译器 (MSVC, GCC 等)是默认关闭 RTTI 的,理由是 for better performance;而现在,RTTI 的是默认打开的,这至少意味着主流编译器认为 RTTI 所带来的 side-effect penalty 已经是 negligible。

打开 RTTI 的副作用是什么呢?所有 polymorphic class 的 vtable 多了一个 slot 专门存放类型信息。

If you don’t want to use a feature, just don’t use it

相较之下,禁用异常就真的是非常不理智的行为了。

更重要的,不要因为存在某个 feature 就不明真相的乱用,掉坑里了又愤愤不平的搞个大新闻,四处宣扬这个 feature 简直是 shit,并且 C++ 是 a pile of shit。

Remember, C++ is fine, it is you that suck

Bonus

最后贴两个 presentation,分别是 Bjarne Stroustrup 和 Herb Sutter 在 CppCon 2015 上的分享。

作为开篇分享,这两个 talks 应该说是为 write moder c++ programs 奠定了一个基调,里面对 resource ownership, type safety, data-safety 都做了非常精彩的叙述,并且还联手提炼了一个 C++ Core Guidelines 出来。

当然里面也有各种私货,例如 BS 吐槽 xx coding style,BS 推销 VS 的静态分析功能…

在 Windows 10 上获取正确的系统版本

大约从 Windows 8 开始,微软开始觉得大家之前那种不同系统版本给不同的代码的方式太不和谐了,怎么可以搞 discrimination 呢?要和谐,要有爱!于是把常用的 GetVersionEx() 给标记成了 deprecated,并且建议大家使用另外一套更加政治正确的 version verification API。

然后就到了 Windows 10,微软发现大家这个版本歧视还是搞得很严重啊,太不和谐了,于是不光把原来的 version verification 给 deprecate 了,甚至 GetVersionEx() 返回的结果都给强行改成和 windows 8.1 一样。

这下没法简单区分 Windows 8.1 和 Windows 10 了,耶,one Windows, a universal windows platform! Hail Microsoft!

当然微软还是留了一条后路,如果真的需要获得正确的 Windows 版本,那么就需要给 EXE 挂一个 manifest,往里面加兼容版本的 UUID item。

但是这样做就非常麻烦,而且似乎就算加了 manifest,程序也得使用正确的 SDK 版本。

于是就有其他非常规的方式,比如 RtlGetVersion()

RtlGetVersion() 非常神奇,GetVersionEx() 实际上内部调用的就是这个函数,但是 RtlGetVersion() 不会做额外的模糊版本号的事情。

这个 API 位于 ntdll.dll 中,所以直接动态调用就好(或者你有 DDK,直接用 DDK 提供的那份声明也可以)。

于是只需要

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#define DECLARE_DLL_FUNCTION(fn, type, dll) \
auto fn = reinterpret_cast<type>(GetProcAddress(GetModuleHandleW(L##dll), #fn))
struct VersionNumber {
unsigned long major_version;
unsigned long minor_version;
};
void GetSystemVersion(VersionNumber& version_number) noexcept
{
constexpr NTSTATUS kStatusSuccess = 0L;
DECLARE_DLL_FUNCTION(RtlGetVersion, NTSTATUS(WINAPI*)(PRTL_OSVERSIONINFOW), "ntdll.dll");
if (!RtlGetVersion) {
return;
}
RTL_OSVERSIONINFOW ovi { sizeof(ovi) };
if (RtlGetVersion(&ovi) != kStatusSuccess) {
return;
}
version_number.major_version = ovi.dwMajorVersion;
version_number.minor_version = ovi.dwMinorVersion;
}

就可以达到生命的伟大和谐。

RANT #1: 有另外一种方式的思路是通过获取 kernel32.dll 或其他系统 DLL 的文件版本,比如 chromium 就是这么做的。但是总觉得这种方式太容易玩脱。

RANT #2: 宏 DECLARE_DLL_FUNCTION 是从某花之前的一篇 post 改的。我觉得他给的宏问题太大,实用性比较差。

API 原型没有必要分离出来,太累赘;如果硬要说分离有什么好处的话,大概就是这些 API 都保存在一个单独的 .h 里,不用自己手写。但是这样又会引入另外一个问题,很容易玩脱违反 ODR,然后打出 GG,extern 都拯救不了。

其实 DECLARE_DLL_FUNCTION 长现在这个样子有个原因是没发现什么简单有效的方法从函数指针的原型声明中分理处变量名…

RANT #3: 微软刻意模糊系统版本估计是希望大家尽量走 UWP 的方式,毕竟可以通过链接到不同版本的 SDK 来区分支持的系统,而分发又是直接通过 microsoft app store 完成。但是对于 native code app 来说,这样做简直就是自己挖坑别人跳,快赶上 Apple 一样无赖了。

Bonus: 欢迎使用 kbase::OSInfo::IsVersionOrGreater() 来解决版本检查,Sample 可以看这里