iOS 野指針定位:野指針嗅探器

一. 前言

最近被指派去解決一些線上的崩潰問題婉刀,經(jīng)常遇到野指針導(dǎo)致的崩潰芭概。相對(duì)于其他的原因引起的崩潰來說玲昧,野指針導(dǎo)致崩潰最難定位的耙饰,這里主要總結(jié)了兩種思路來定位野指針導(dǎo)致的崩潰。

二. 野指針

1.定義

當(dāng)所指向的對(duì)象被釋放或者收回拴签,但是對(duì)該指針沒有作任何的修改孝常,以至于該指針仍舊指向已經(jīng)回收的內(nèi)存地址,此情況下該指針便稱野指針.

2. 為什么Obj-C野指針的Crash那么多篓吁?

一般app版本發(fā)布之前都會(huì)經(jīng)過多輪研發(fā)自測(cè)茫因、測(cè)試內(nèi)測(cè)灰度測(cè)試杖剪、開放部分客戶公測(cè)等,按理說很多Crash的場(chǎng)景都應(yīng)該覆蓋到了驰贷,但由于野指針隨機(jī)性盛嘿,很經(jīng)常會(huì)使得測(cè)試的時(shí)候,它是沒有問題括袒,等到真正用戶使用的時(shí)候才有問題次兆,

隨機(jī)性問題可以大概分為兩類:

  • 跑不進(jìn)出錯(cuò)的邏輯,執(zhí)行不到出錯(cuò)的代碼锹锰,這種可以提高測(cè)試場(chǎng)景覆蓋度來解決芥炭。
  • 跑進(jìn)了有問題的邏輯漓库,但是野指針指向的地址并不一定會(huì)導(dǎo)致Crash,這就有點(diǎn)看人品了园蝠?

為什么跑進(jìn)了有問題邏輯渺蒿,但還是不一定會(huì)導(dǎo)致Crash呢?

3.分析

野指針是指指向一個(gè)已刪除對(duì)象未申請(qǐng)訪問受限內(nèi)存區(qū)域的指針彪薛。本文說的Obj-C野指針茂装,說的是Obj-C對(duì)象釋放之后指針未置空,導(dǎo)致的野指針Obj-C里面一般不會(huì)出現(xiàn)為初始化對(duì)象的常識(shí)性錯(cuò)誤)善延。

既然是訪問已經(jīng)釋放的對(duì)象為什么不是必現(xiàn)Crash呢少态?

因?yàn)?code>dealloc執(zhí)行后只是告訴系統(tǒng),這片內(nèi)存我不用了易遣,而系統(tǒng)并沒有就讓這片內(nèi)存不能訪問彼妻。

現(xiàn)實(shí)大概是下面幾種可能的情況:

  1. 對(duì)象釋放內(nèi)存沒被改動(dòng)過,原來的內(nèi)存保存完好豆茫,可能不Crash或者出現(xiàn)邏輯錯(cuò)誤(隨機(jī)Crash)侨歉。

  2. 對(duì)象釋放內(nèi)存沒被改動(dòng)過,但是它自己析構(gòu)的時(shí)候已經(jīng)刪掉某些必要的東西澜薄,可能不Crash为肮、Crash在訪問依賴的對(duì)象比如類成員上、出現(xiàn)邏輯錯(cuò)誤(隨機(jī)Crash)肤京。

  3. 對(duì)象釋放內(nèi)存被改動(dòng)過颊艳,寫上了不可訪問數(shù)據(jù),直接就出錯(cuò)了很可能Crashobjc_msgSend上面(必現(xiàn)Crash忘分,常見)棋枕。

  4. 對(duì)象釋放后內(nèi)存被改動(dòng)過,寫上了可以訪問的數(shù)據(jù)妒峦,可能不Crash重斑、出現(xiàn)邏輯錯(cuò)誤間接訪問到不可訪問的數(shù)據(jù)(隨機(jī)Crash)肯骇。

  5. 對(duì)象釋放后內(nèi)存被改動(dòng)過窥浪,寫上了可以訪問的數(shù)據(jù),但是再次訪問的時(shí)候執(zhí)行的代碼把別的數(shù)據(jù)寫壞了笛丙,遇到這種Crash只能哭了(隨機(jī)Crash漾脂,難度大,概率低)E哐臁骨稿!

  6. 對(duì)象釋放后再次release(幾乎是必現(xiàn)Crash,但也有例外,很常見)坦冠。

如圖所示:

image.png

正是因?yàn)橐爸羔樣腥缟隙喾N情況形耗,所以導(dǎo)致crash率一直降不下去。

三. 解決思路

1. 方案一

主要是依據(jù)騰訊Bugly工程師:陳其鋒的分享得來辙浑。

Demo: FJFZombieSnifferDemo

A. 主要思路
  • 通過fishhook替換C函數(shù)free方法為自身方法safe_free激涤,就類似runtime方法交換
bool init_safe_free() {
    _unfreeQueue = ds_queue_create(MAX_STEAL_MEM_NUM);
    orig_free = (void(*)(void*))dlsym(RTLD_DEFAULT, "free");
    rebind_symbols((struct rebinding[]){{"free", (void*)safe_free}}, 1);
    return true;
}
  • 然后在safe_free方法中對(duì)已經(jīng)釋放變量內(nèi)存例衍,填充0x55昔期,使已經(jīng)釋放變量不能訪問,從而使某些野指針從不必現(xiàn)Crash變成了必現(xiàn)佛玄。

這里之所以填充為0x55是因?yàn)?code>Xcode的僵尸對(duì)象填充的就是0x55硼一。
如果填充為像0x22這樣的數(shù)據(jù)也是可以,因?yàn)橹斑@里是存儲(chǔ)的是一個(gè)對(duì)象梦抢,這個(gè)對(duì)象被數(shù)據(jù)覆蓋了般贼,當(dāng)你調(diào)用方法的時(shí)候,數(shù)據(jù)無法響應(yīng)對(duì)應(yīng)的方法奥吩,因此也會(huì)導(dǎo)致崩潰哼蛆。


void safe_free(void* p){
    size_tmemSiziee=malloc_size(p);
    memset(p,0x55, memSiziee);
    orig_free(p);
    return;

  • 但是由于填充了0x55的內(nèi)存地址很可能被新的數(shù)據(jù)內(nèi)容填充,使得野指針crash又變得不必現(xiàn)霞赫。

例如下面這種情況:

  UIView *testObj = [[UIView alloc] init];
    [testObj release];
    for (int i = 0; i < 10; i++) {
        UIView* testView = [[UIView alloc] initWithFrame:CGRectMake(0,200,CGRectGetWidth(self.view.bounds), 60)];
        [self.view addSubview:testView];
    }
    [testObj setNeedsLayout];

這里的testObj指向的內(nèi)存空間內(nèi)容被填充為0x55腮介,然后調(diào)用free真正釋放了,這塊內(nèi)存空間,被系統(tǒng)回收利用端衰,但testObj仍然指向這塊內(nèi)存空間叠洗,

緊接著新生成的UIView很快的就會(huì)覆蓋了testObj指向的內(nèi)存空間,這時(shí)候testObj指向的仍然還是一個(gè)UIView對(duì)象旅东,這時(shí)候調(diào)用UIView的實(shí)例方法setNeedsLayout方法完全不會(huì)發(fā)生Crash.

沒有發(fā)生Crash可不是好事灭抑,因?yàn)檫@種情況如果后續(xù)再Crash,問題就非常難查抵代,因?yàn)槟憧吹降?code>Crash棧很可能和出錯(cuò)的代碼完全沒有關(guān)聯(lián)腾节。既然這個(gè)問題這么棘手,最好還是和之前一樣荤牍,讓這個(gè)Crash提前暴露案腺。

  • 為了防止上面這種情況,我們干脆就不釋放這片內(nèi)存了康吵。也就是當(dāng)free被調(diào)用的時(shí)候我們不真的調(diào)用free救湖,而是自己保留著內(nèi)存,這樣系統(tǒng)不知道這片內(nèi)存已經(jīng)不需要用了涎才,自然就不會(huì)被再次寫上別的數(shù)據(jù).
struct DSQueue* _unfreeQueue = NULL;//用來保存自己偷偷保留的內(nèi)存:1這個(gè)隊(duì)列要線程安全或者自己加鎖;2這個(gè)隊(duì)列內(nèi)部應(yīng)該盡量少申請(qǐng)和釋放堆內(nèi)存。
int unfreeSize = 0;//用來記錄我們偷偷保存的內(nèi)存的大小
#define MAX_STEAL_MEM_SIZE 1024*1024*100//最多存這么多內(nèi)存,大于這個(gè)值就釋放一部分
#define MAX_STEAL_MEM_NUM 1024*1024*10//最多保留這么多個(gè)指針耍铜,再多就釋放一部分
#define BATCH_FREE_NUM 100//每次釋放的時(shí)候釋放指針數(shù)量
  • 為了防止系統(tǒng)內(nèi)存過快耗盡,我們需要在自己保留的內(nèi)存大于一定值的時(shí)候就釋放一部分邑闺,防止被系統(tǒng)殺死。同時(shí)在系統(tǒng)內(nèi)存警告的時(shí)候棕兼,也要釋放一部分內(nèi)存陡舅。
//系統(tǒng)內(nèi)存警告的時(shí)候調(diào)用這個(gè)函數(shù)釋放一些內(nèi)存
void free_some_mem(size_t freeNum){
#ifdef DEBUG
    size_t count = ds_queue_length(_unfreeQueue);
    freeNum= freeNum > count ? count:freeNum;
    for (int i=0; i<freeNum; i++) {
        void *unfreePoint = ds_queue_get(_unfreeQueue);
        size_t memSiziee = malloc_size(unfreePoint);
        __sync_fetch_and_sub(&unfreeSize, memSiziee);
        orig_free(unfreePoint);
    }
#endif
}

  • 但是如果只是對(duì)已經(jīng)釋放的對(duì)象內(nèi)存空間填充為0x55,這樣發(fā)生Crash的時(shí)候,我們得到的崩潰信息非常有限伴挚,但對(duì)于崩潰信息靶衍,我們肯定希望知道更具體一點(diǎn):比如是哪個(gè)類,調(diào)了什么方法茎芋,對(duì)象的地址之類颅眶。

  • 為了解決上述的問題,我們引入了一個(gè)代理類MOACatcher繼承自NSProxy田弥,同時(shí)MOACatcher持有一個(gè)originClass涛酗,重寫消息轉(zhuǎn)發(fā)的三個(gè)方法以及NSObject的實(shí)例方法,來進(jìn)行異常信息的打印偷厦。

為什么選擇NSProxy做代理: 使用NSProxy和NSObject設(shè)計(jì)代理類的差異

- (BOOL)respondsToSelector: (SEL)aSelector
{
    return [self.originClass instancesRespondToSelector:aSelector];
}

- (NSMethodSignature *)methodSignatureForSelector: (SEL)sel
{
    return [self.originClass instanceMethodSignatureForSelector:sel];
}

- (void)forwardInvocation: (NSInvocation *)invocation
{
    [self _throwMessageSentExceptionWithSelector: invocation.selector];
}

#pragma mark - Private
- (void)_throwMessageSentExceptionWithSelector: (SEL)selector
{
    @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"(-[%@ %@]) was sent to a zombie object at address: %p", NSStringFromClass(self.originClass), NSStringFromSelector(selector), self] userInfo:nil];
}
  • 因?yàn)?code>NSProxy只能作為Objc對(duì)象的代理商叹,所以safe_free函數(shù)需要添加判斷。
void safe_free(void* p){
    
    int unFreeCount = ds_queue_length(_unfreeQueue);
    // 保留的內(nèi)存大于一定值的時(shí)候就釋放一部分
    if (unFreeCount > MAX_STEAL_MEM_NUM*0.9 || unfreeSize>MAX_STEAL_MEM_SIZE) {
        free_some_mem(BATCH_FREE_NUM);
    }
    else{
        size_t memSiziee = malloc_size(p);
        if (memSiziee > sYHCatchSize) {//有足夠的空間才覆蓋
            id obj=(id)p;
            Class origClass= object_getClass(obj);
            // 判斷是不是objc對(duì)象
            char *type = @encode(typeof(obj));
            if (strcmp("@", type) == 0) {
                memset(obj, 0x55, memSiziee);
                memcpy(obj, &sYHCatchIsa, sizeof(void*));//把我們自己的類的isa復(fù)制過去
                
                object_setClass(obj, [MOACatcher class]);
                ((MOACatcher *)obj).originClass = origClass;
                __sync_fetch_and_add(&unfreeSize,(int)memSiziee);//多線程下int的原子加操作,多線程對(duì)全局變量進(jìn)行自加只泼,不用理線程鎖了
                ds_queue_put(_unfreeQueue, p);
            }else{
               orig_free(p);
            }
        }else{
           orig_free(p);
        }
    }
}

這里騰訊Bugly分享的有點(diǎn)不同:

  • object_setClass可以替換一個(gè)isa剖笙,但是如果直接替換會(huì)發(fā)生死鎖。這里先對(duì)obj對(duì)象進(jìn)行0x55填充请唱,然后將自己類的isa復(fù)制過去弥咪,之后調(diào)用object_setClass將原有類替換為代理類MOACatcher,而Bugly的分享也是先對(duì)obj對(duì)象進(jìn)行0x55填充籍滴,然后將自己類的isa復(fù)制過去,之后強(qiáng)轉(zhuǎn)為MOACatcher.

  • 同樣這里使用了編碼類型來判斷是不是objc對(duì)象酪夷,Bugly的分享是通過先獲取所有的objc的類存儲(chǔ)在數(shù)組中,通過判斷數(shù)組中是否含有當(dāng)前類來進(jìn)行判斷孽惰。

2. 方案二

方案二是騎神提出的一種思路:

Demo地址: LXDZombieSniffer

主要思路:
  • 通過objcruntime方法進(jìn)行方法交換晚岭,交換了根類的NSObjectNSProxydealloc方法為originalDeallocImp执赡。
 NSMutableDictionary *deallocImps = [NSMutableDictionary dictionary];
    for (Class rootClass in _rootClasses) {
        IMP originalDeallocImp = __lxd_swizzleMethodWithBlock(class_getInstanceMethod(rootClass, @selector(dealloc)), swizzledDeallocBlock);
        [deallocImps setObject: [NSValue valueWithBytes: &originalDeallocImp objCType: @encode(typeof(IMP))] forKey: NSStringFromClass(rootClass)];
    }
    _rootClassDeallocImps = [deallocImps copy];
  • 為了避免 內(nèi)存空間釋放之后被復(fù)寫造成野指針問題麦轰,通過字典_rootClassDeallocImps存儲(chǔ)被釋放的對(duì)象,同時(shí)設(shè)置在30秒之后調(diào)用dealloc方法將存儲(chǔ)的對(duì)象釋放休玩,避免內(nèi)存空間增大狂鞋。
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        swizzledDeallocBlock = [^void(id obj) {
            Class currentClass = [obj class];
            NSString *clsName = NSStringFromClass(currentClass);
            if ([__lxd_sniff_white_list() containsObject: clsName]) {
                __lxd_dealloc(obj);
            } else {
                NSValue *objVal = [NSValue valueWithBytes: &obj objCType: @encode(typeof(obj))];
                object_setClass(obj, [LXDZombieProxy class]);
                ((LXDZombieProxy *)obj).originClass = currentClass;
                
                dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(30 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                    __unsafe_unretained id deallocObj = nil;
                    [objVal getValue: &deallocObj];
                    object_setClass(deallocObj, currentClass);
                    __lxd_dealloc(deallocObj);
                });
            }
        } copy];
    });
  • 也同樣為了獲取更多的崩潰信息采用了繼承自NSProxy 類的LXDZombieProxy的來進(jìn)行消息轉(zhuǎn)發(fā)片择,重寫消息轉(zhuǎn)發(fā)方法以及內(nèi)存管理相關(guān)的方法。

  • 因?yàn)?code>objc內(nèi)部還有一些底層的類骚揍,這些類我們項(xiàng)目中一般不涉及字管,因此不會(huì)是這些類造成野指針啰挪,就可以通過白名單機(jī)制,放棄對(duì)這些類的dealloc方法的捕獲嘲叔。

static inline NSMutableSet *__lxd_sniff_white_list() {
    static NSMutableSet *lxd_sniff_white_list;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        lxd_sniff_white_list = [[NSMutableSet alloc] init];
    });
    return lxd_sniff_white_list;
}

四. 方法對(duì)比

第一種方案:
通過free函數(shù)來進(jìn)行野指針定位

  • 優(yōu)點(diǎn): 覆蓋范圍廣亡呵,覆蓋了OC、C++硫戈、C函數(shù)锰什,對(duì)于iOS項(xiàng)目適用于混編的工程。
  • 缺點(diǎn): 想要獲得具體的崩潰信息丁逝,還是需要進(jìn)行Objc對(duì)象的判斷汁胆,同時(shí)free函數(shù)的覆蓋范圍廣,也會(huì)造成一定性能的損耗霜幼,畢竟我們?cè)?code>safe_free中添加了一些判斷嫩码。

第二種方案:

通過dealloc函數(shù)來進(jìn)行野指針定位

優(yōu)點(diǎn): 針對(duì)OC語言,利用OC的方法交換辛掠、消息轉(zhuǎn)發(fā)等特性谢谦,對(duì)于iOS項(xiàng)目來說更具有針對(duì)性可擴(kuò)展性

缺點(diǎn): 相對(duì)作用范圍較小

五. 詳見:

iOS監(jiān)控-野指針定位
如何定位Obj-C野指針隨機(jī)Crash(一):先提高野指針Crash率
如何定位Obj-C野指針隨機(jī)Crash(二):讓非必現(xiàn)Crash變成必現(xiàn)
如何定位Obj-C野指針隨機(jī)Crash(三):加點(diǎn)黑科技讓Crash自報(bào)家門

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末萝衩,一起剝皮案震驚了整個(gè)濱河市回挽,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌猩谊,老刑警劉巖千劈,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異牌捷,居然都是意外死亡墙牌,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門暗甥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來喜滨,“玉大人,你說我怎么就攤上這事撤防∷浞纾” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵寄月,是天一觀的道長辜膝。 經(jīng)常有香客問我,道長漾肮,這世上最難降的妖魔是什么厂抖? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮克懊,結(jié)果婚禮上忱辅,老公的妹妹穿的比我還像新娘七蜘。我一直安慰自己,他們只是感情好耕蝉,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布崔梗。 她就那樣靜靜地躺著,像睡著了一般垒在。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上扔亥,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天场躯,我揣著相機(jī)與錄音,去河邊找鬼旅挤。 笑死踢关,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的粘茄。 我是一名探鬼主播签舞,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼柒瓣!你這毒婦竟也來了儒搭?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤芙贫,失蹤者是張志新(化名)和其女友劉穎搂鲫,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體磺平,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡魂仍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了拣挪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片擦酌。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖菠劝,靈堂內(nèi)的尸體忽然破棺而出赊舶,到底是詐尸還是另有隱情,我是刑警寧澤闸英,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布锯岖,位于F島的核電站,受9級(jí)特大地震影響甫何,放射性物質(zhì)發(fā)生泄漏出吹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一辙喂、第九天 我趴在偏房一處隱蔽的房頂上張望捶牢。 院中可真熱鬧鸠珠,春花似錦、人聲如沸秋麸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽灸蟆。三九已至驯耻,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間炒考,已是汗流浹背可缚。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留斋枢,地道東北人帘靡。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像瓤帚,于是被迫代替她去往敵國和親描姚。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時(shí)...
    歐辰_OSR閱讀 29,320評(píng)論 8 265
  • 原文地址 野指針 當(dāng)所指向的對(duì)象被釋放或者收回戈次,但是對(duì)該指針沒有作任何的修改轩勘,以至于該指針仍舊指向已經(jīng)回收的內(nèi)存地...
    sindri的小巢閱讀 10,238評(píng)論 22 83
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,089評(píng)論 1 32
  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,679評(píng)論 0 9
  • 說到情緒朝扼,腦海立刻浮現(xiàn)出“沖動(dòng)是魔鬼”這句話赃阀。身邊有很多人,有好脾氣擎颖,也有急脾氣榛斯。 20出頭時(shí),很容易沖動(dòng)...
    _聽風(fēng)099舒卉閱讀 166評(píng)論 0 0