第七色在线视频,2021少妇久久久久久久久久,亚洲欧洲精品成人久久av18,亚洲国产精品特色大片观看完整版,孙宇晨将参加特朗普的晚宴

為了賬號安全,請及時綁定郵箱和手機立即綁定

記一起由 Clang 編譯器優(yōu)化觸發(fā)的 Crash

標簽:
C++

摘要:一个有意思的 Crash 探究过程,Clang 有 GCC 没有

troubleshooting-crash-clang-compiler-optimization

如果有人告诉你,下面的 C++ 函数会导致程序 crash,你会想到哪些原因呢?


std::string  b2s(bool  b) {

return b ? "true" : "false";

}

如果再多给一些描述,比如:

  • Crash 以一定的概率复现

  • Crash 原因是段错误(SIGSEGV)

  • 现场的 Backtrace 经常是不完整甚至完全丢失的。

  • 只有优化级别在 -O2 以上才会(更容易)复现

  • 仅在 Clang 下复现,GCC 复现不了

好了,一些老鸟可能已经有线索了,下面给出一个最小化的复现程序和步骤:


// file crash.cpp

#include  <iostream>

#include  <string>

  

std::string  __attribute__((noinline)) b2s(bool b) {

return b ? "true" : "false";

}

  

union {

unsigned  char c;

bool b;

} volatile u;

  

int  main() {

u.c = 0x80;

std::cout << b2s(u.b) << std::endl;

return  0;

}


$ clang++ -O2 crash.cpp

$ ./a.out

truefalse,d$x4DdzRx

  

Segmentation fault (core dumped)

  

$ gdb ./a.out core.3699

Core was generated by `./a.out'.

Program terminated with signal SIGSEGV, Segmentation fault.

#0 0x0000012cfffff0d4 in ?? ()

(gdb) bt

#0 0x0000012cfffff0d4 in ?? ()

#1 0x00000064fffff0f4 in ?? ()

#2 0x00000078fffff124 in ?? ()

#3 0x000000b4fffff1e4 in ?? ()

#4 0x000000fcfffff234 in ?? ()

#5 0x00000144fffff2f4 in ?? ()

#6 0x0000018cfffff364 in ?? ()

#7 0x0000000000000014 in ?? ()

#8 0x0110780100527a01 in ?? ()

#9 0x0000019008070c1b in ?? ()

#10 0x0000001c00000010 in ?? ()

#11 0x0000002ffffff088 in ?? ()

#12 0xe2ab001010074400 in ?? ()

#13 0x0000000000000000 in ?? ()

因为 backtrace 信息不完整,说明程序并不是在第一时间 crash 的。面对这种情况,为了快速找出第一现场,我们可以试试 AddressSanitizer(ASan):


$ clang++ -g -O2 -fno-omit-frame-pointer -fsanitize=address crash.cpp

$ ./a.out

=================================================================

==3699==ERROR: AddressSanitizer: global-buffer-overflow on address 0x000000552805 at pc 0x0000004ff83a bp 0x7ffd7610d240 sp 0x7ffd7610c9f0

READ of size 133 at 0x000000552805 thread T0

#0 0x4ff839 in __asan_memcpy (a.out+0x4ff839)

#1 0x5390a7 in b2s[abi:cxx11](bool) crash.cpp:6

#2 0x5391be in main crash.cpp:16:18

#3 0x7faed604df42 in __libc_start_main (/usr/lib64/libc.so.6+0x23f42)

#4 0x41c43d in _start (a.out+0x41c43d)

  

0x000000552805 is located 59 bytes to the left of global variable '<string literal>' defined in 'crash.cpp:6:25' (0x552840) of size 6

'<string literal>' is ascii string 'false'

0x000000552805 is located 0 bytes to the right of global variable '<string literal>' defined in 'crash.cpp:6:16' (0x552800) of size 5

'<string literal>' is ascii string 'true'

SUMMARY: AddressSanitizer: global-buffer-overflow (/home/dutor.hou/Wdir/nebula-graph/build/bug/a.out+0x4ff839) in __asan_memcpy

Shadow bytes around the buggy address:

…

...

从 ASan 给出的信息,我们可以定位到是函数 b2s(bool) 在读取字符串常量 "true" 的时候,发生了“全局缓冲区溢出”。好了,我们再次以上帝视角审视一下问题函数和复现程序,“似乎”可以得出结论:因为 b2s 的布尔类型参数 b 没有初始化,所以 b 中存储的是一个 01 之外的值[1]。那么问题来了,为什么 b 的这种取值会导致“缓冲区溢出”呢?感兴趣的可以将 b 的类型由 bool 改成 char 或者 int,问题就可以得到修复。

想要解答这个问题,我们不得不看下 clang++ 为 b2s 生成了怎样的指令(之前我们提到 GCC 下没有出现 crash,所以问题可能和代码生成有关)。在此之前,我们应该了解:

  • 样例程序中,b2s 的返回值是一个临时的 std::string 对象,是保存在栈上的

  • C++ 11 之后,GCC 的 std::string 默认实现使用了 SBO(Small Buffer Optimization),其定义大致为 std::string{ char *ptr; size_t size; union{ char buf[16]; size_t capacity}; }。对于长度小于 16 的字符串,不需要额外申请内存。

OK,那我们现在来看一下 b2s 的反汇编并给出关键注解:


(gdb) disas b2s

Dump of assembler code for function b2s[abi:cxx11](bool):

0x00401200 <+0>: push %r14

0x00401202 <+2>: push %rbx

0x00401203 <+3>: push %rax

0x00401204 <+4>: mov %rdi,%r14 # 将返回值(string)的起始地址保存到 r14

0x00401207 <+7>: mov $0x402010,%ecx # 将 "true" 的起始地址保存至 ecx

0x0040120c <+12>: mov $0x402015,%eax # 将 "false" 的起始地址保存至 eax

0x00401211 <+17>: test %esi,%esi # “测试” 参数 b 是否非零

0x00401213 <+19>: cmovne %rcx,%rax # 如果 b 非零,则将 "true" 地址保存至 rax

0x00401217 <+23>: lea 0x10(%rdi),%rdi # 将 string 中的 buf 起始地址保存至 rdi

# (同时也是后面 memcpy 的第一个参数)

0x0040121b <+27>: mov %rdi,(%r14) # 将 rdi 保存至 string 的 ptr 字段,即 SBO

0x0040121e <+30>: mov %esi,%ebx # 将 b 的值保存至 ebx

0x00401220 <+32>: xor $0x5,%rbx # 将 0x5 异或到 rbx(也即 ebx)

# 注意,如果 rbx 非 0 即 1,那么 rbx 保存的就是 4 或 5,

# 即 "true" 或 "false" 的长度

0x00401224 <+36>: mov %rax,%rsi # 将字符串起始地址保存至 rsi,即 memcpy 的第二个参数

0x00401227 <+39>: mov %rbx,%rdx # 将字符串的长度保存至 rdx,即 memcpy 的第三个参数

0x0040122a <+42>: callq <memcpy@plt> # 调用 memcpy

0x0040122f <+47>: mov %rbx,0x8(%r14) # 将字符串长度保存到 string::size

0x00401233 <+51>: movb $0x0,0x10(%r14,%rbx,1) # 将 string 以 '\0' 结尾

0x00401239 <+57>: mov %r14,%rax # 将 string 地址保存至 rax,即返回值

0x0040123c <+60>: add $0x8,%rsp

0x00401240 <+64>: pop %rbx

0x00401241 <+65>: pop %r14

0x00401243 <+67>: retq

End of assembler dump.

到这里,问题就无比清晰了:

  1. clang++ 假设了 bool 类型的值非 01

  2. 在编译期,”true””false” 长度已知

  3. 使用异或指令( 0x5 ^ false == 5, 0x5 ^ true == 4)计算要拷贝的字符串的长度

  4. bool 类型不符合假设时,长度计算错误

  5. 因为 memcpy 目标地址在栈上(仅对本例而言),因此栈上的缓冲区也可能溢出,从而导致程序跑飞,backtrace 缺失。

注:

  1. C++ 标准要求 bool 类型至少_能够_表示两个状态: truefalse ,但并没有规定 sizeof(bool) 的大小。但在几乎所有的编译器实现上, bool 都占用一个寻址单位,即字节。因此,从存储角度,取值范围为 0x00-0xFF,即 256 个状态。
點擊查看更多內(nèi)容
TA 點贊

若覺得本文不錯,就分享一下吧!

評論

作者其他優(yōu)質(zhì)文章

正在加載中
  • 推薦
  • 評論
  • 收藏
  • 共同學(xué)習(xí),寫下你的評論
感謝您的支持,我會繼續(xù)努力的~
掃碼打賞,你說多少就多少
贊賞金額會直接到老師賬戶
支付方式
打開微信掃一掃,即可進行掃碼打賞哦
今天注冊有機會得

100積分直接送

付費專欄免費學(xué)

大額優(yōu)惠券免費領(lǐng)

立即參與 放棄機會
微信客服

購課補貼
聯(lián)系客服咨詢優(yōu)惠詳情

幫助反饋 APP下載

慕課網(wǎng)APP
您的移動學(xué)習(xí)伙伴

公眾號

掃描二維碼
關(guān)注慕課網(wǎng)微信公眾號

舉報

0/150
提交
取消