ASM Core Api 詳解

前言

前面一篇文章 ASM 簡(jiǎn)介 對(duì) ASM 框架做了簡(jiǎn)單的介紹惊搏。

本篇文章主要對(duì)該框架的 Core Api 其中重要的一些類進(jìn)行詳細(xì)的介紹,讓大家可以更得心應(yīng)手的使用 ASM饼暑。

在開(kāi)始之前卸奉,讓我們先回顧一下 ASM Core Api 調(diào)用流程:

  1. ASM 提供了一個(gè)類ClassReader可以方便地讓我們對(duì)class文件進(jìn)行讀取與解析盐类;

  2. ASMClassReader解析class文件過(guò)程中拭荤,解析到某一個(gè)結(jié)構(gòu)就會(huì)通知到ClassVisitor的相應(yīng)方法(eg:解析到類方法時(shí)插勤,就會(huì)回調(diào)ClassVisitor.visitMethod方法)谍肤;

  3. 可以通過(guò)更改ClassVisitor中相應(yīng)結(jié)構(gòu)方法返回值琅关,實(shí)現(xiàn)對(duì)類的代碼切入(eg:更改ClassVisitor.visitMethod()方法的默認(rèn)返回值MethodVisitor實(shí)例煮岁,通過(guò)操作該自定義MethodVisitor從而實(shí)現(xiàn)對(duì)原方法的改寫(xiě));

  4. 其它的結(jié)構(gòu)遍歷也如同ClassVisitor涣易;

  5. 通過(guò)ClassWritertoByteArray()方法画机,得到class文件的字節(jié)碼內(nèi)容,最后通過(guò)文件流寫(xiě)入方式覆蓋掉原先的內(nèi)容新症,實(shí)現(xiàn)class文件的改寫(xiě)步氏。

以上,就是 ASM Core Api 的整體運(yùn)作流程账劲。

接下來(lái)戳护,我將對(duì)其中涉及到的重要的類進(jìn)行詳細(xì)解析。

ClassReader

ClassReader

這個(gè)類會(huì)提供你要轉(zhuǎn)變的類的字節(jié)數(shù)組瀑焦,它的accept方法腌且,接受一個(gè)具體的ClassVisitor,并調(diào)用實(shí)現(xiàn)中具體的 visit,
visitSource, visitOuterClass, visitAnnotation, visitAttribute, visitInnerClass,visitField, visitMethodvisitEnd 方法榛瓮。

ClassReader.accept(ClassVisitor classVisitor, int parsingOptions)中铺董,第二個(gè)參數(shù)parsingOptions的取值有以下選項(xiàng):

  • ClassReader.SKIP_DEBUG:表示不遍歷調(diào)試內(nèi)容,即跳過(guò)源文件,源碼調(diào)試擴(kuò)展精续,局部變量表坝锰,局部變量類型表和行號(hào)表屬性,即以下方法既不會(huì)被解析也不會(huì)被訪問(wèn)(ClassVisitor.visitSource重付,MethodVisitor.visitLocalVariable顷级,MethodVisitor.visitLineNumber)。使用此標(biāo)識(shí)后确垫,類文件調(diào)試信息會(huì)被去除弓颈,請(qǐng)警記。
  • ClassReader.SKIP_CODE:設(shè)置該標(biāo)識(shí)删掀,則代碼屬性將不會(huì)被轉(zhuǎn)換和訪問(wèn)翔冀,例如方法體代碼不會(huì)進(jìn)行解析和訪問(wèn)。
  • ClassReader.SKIP_FRAMES:設(shè)置該標(biāo)識(shí)披泪,表示跳過(guò)棧圖(StackMap)和棧圖表(StackMapTable)屬性纤子,即MethodVisitor.visitFrame方法不會(huì)被轉(zhuǎn)換和訪問(wèn)。當(dāng)設(shè)置了ClassWriter.COMPUTE_FRAMES時(shí)款票,設(shè)置該標(biāo)識(shí)會(huì)很有用控硼,因?yàn)樗苊饬嗽L問(wèn)幀內(nèi)容(這些內(nèi)容會(huì)被忽略和重新計(jì)算,無(wú)需訪問(wèn))徽职。
  • ClassReader.EXPAND_FRAMES:該標(biāo)識(shí)用于設(shè)置擴(kuò)展棧幀圖象颖。默認(rèn)棧圖以它們?cè)几袷剑╒1_6以下使用擴(kuò)展格式佩厚,其他使用壓縮格式)被訪問(wèn)姆钉。如果設(shè)置該標(biāo)識(shí),棧圖則始終以擴(kuò)展格式進(jìn)行訪問(wèn)(此標(biāo)識(shí)在ClassReaderClassWriter中增加了解壓/壓縮步驟抄瓦,會(huì)大幅度降低性能)潮瓶。

ClassWriter

ClassWriter

這個(gè)類是ClassVisitor的一個(gè)實(shí)現(xiàn)類,這個(gè)類中的toByteArray方法會(huì)將最終修改的字節(jié)碼以 byte 數(shù)組形式返回钙姊。它可以單獨(dú)使用毯辅,也可以傳遞給一個(gè)或多個(gè)ClassReaderClassVisitor適配器修改一個(gè)或多個(gè)已存在的Java類的類文件。

我們知道煞额,類文件有著自己嚴(yán)格的格式思恐,當(dāng)我們想要注入相關(guān)代碼時(shí),不是直接注入相關(guān)指令就可以的膊毁,比如對(duì)于方法注入胀莹,我們可能還需要對(duì)棧幀圖( stack map frames)進(jìn)行計(jì)算:你需要計(jì)算所有的幀,找到有對(duì)象跳轉(zhuǎn)或者絕對(duì)跳轉(zhuǎn)的幀婚温,最后還要壓縮剩余的幀描焰。同樣,對(duì)于棧幀的局部變量表和操作數(shù)棧的大小也要自己進(jìn)行計(jì)算栅螟。這些計(jì)算操作具備一定的難度荆秦,幸運(yùn)的是篱竭,當(dāng)我們創(chuàng)建一個(gè)ClassWriter時(shí),可以配置 ASM 自動(dòng)幫我們對(duì)指定的內(nèi)容進(jìn)行計(jì)算步绸。具體的配置標(biāo)識(shí)如下:

ClassWriter的構(gòu)造函數(shù)需要傳入一個(gè) flag掺逼,其含義為:

  • ClassWriter(0):表示 ASM 不會(huì)自動(dòng)自動(dòng)幫你計(jì)算棧幀和局部變量表和操作數(shù)棧大小。
  • ClassWriter(ClassWriter.COMPUTE_MAXS):表示 ASM 會(huì)自動(dòng)幫你計(jì)算局部變量表和操作數(shù)棧的大小坪圾,但是你還是需要調(diào)用visitMaxs方法,但是可以使用任意參數(shù)惑朦,因?yàn)樗鼈儠?huì)被忽略兽泄。帶有這個(gè)標(biāo)識(shí),對(duì)于棧幀大小漾月,還是需要你手動(dòng)計(jì)算病梢。
  • ClassWriter(ClassWriter.COMPUTE_FRAMES):表示 ASM 會(huì)自動(dòng)幫你計(jì)算所有的內(nèi)容。你不必去調(diào)用visitFrame梁肿,但是你還是需要調(diào)用visitMaxs方法(參數(shù)可任意設(shè)置蜓陌,同樣會(huì)被忽略)。

使用這些標(biāo)識(shí)很方便吩蔑,但是會(huì)帶來(lái)一些性能上的損失:COMPUTE_MAXS標(biāo)識(shí)會(huì)使ClassWriter慢10%钮热,COMPUTE_FRAMES標(biāo)識(shí)會(huì)使ClassWriter慢2倍,

ClassVisitor

ClassVisitor

一個(gè)可以訪問(wèn)Java類的訪問(wèn)者烛芬。其方法被調(diào)用次序必須滿足:

visit visitSource? visitOuterClass? ( visitAnnotation | visitAttribute )* ( visitInnerClass | visitField | visitMethod )* visitEnd

visit必須第一個(gè)被調(diào)用隧期,然后最多調(diào)用一次visitSource,同樣接著最多調(diào)用一次visitOuterClass赘娄,接下來(lái)按任意順序可多次調(diào)用visitAnnotationvisitAttribute仆潮;接下來(lái)visitInnerClassvisitField遣臼,visitMethod同樣按任意順序可多次調(diào)用性置;最后調(diào)用一次visitEnd,表示類訪問(wèn)結(jié)束揍堰。

注: ASM 文檔原文內(nèi)容為:

This means that visit must be called ?rst, followed by at most one call to visitSource, followed by at most one call to visitOuterClass, followed by any number of calls in any order to visitAnnotation and visitAttribute, followed by any number of calls in any order to visitInnerClass,visitField and visitMethod , and terminated by a single call to visitEnd.

黑體加粗句子我的翻譯是:以任意順序訪問(wèn)visitInnerClass,visitFieldvisitMethod鹏浅,但是在我機(jī)器上試驗(yàn)得到的結(jié)果是這3者的訪問(wèn)順序是固定的:visitInnerClass->visitField->visitMethod,所以屏歹,此處可能是翻譯有問(wèn)題隐砸,應(yīng)該是以一定的順序可多次調(diào)用 visitInnerClass,visitFieldvisitMethod。如有差錯(cuò)西采,煩請(qǐng)指正凰萨,感謝! ^-^

MethodVisitor

MethodVisitor

ASM 生成和轉(zhuǎn)換class文件方法使用的是抽象類MethodVisitorClassVisitor.visitMethod方法返回的就是該實(shí)例胖眷。

其方法調(diào)用時(shí)序?yàn)椋?/p>

visitAnnotationDefault?
( visitAnnotation | visitParameterAnnotation | visitAttribute )*
( visitCode
( visitTryCatchBlock | visitLabel | visitFrame | visitXxxInsn |
visitLocalVariable | visitLineNumber )*
visitMaxs )?
visitEnd

即如果有annotations或者attributes武通,它們必須被第一個(gè)訪問(wèn),接下來(lái)對(duì)于非抽象方法訪問(wèn)的就是方法內(nèi)部字節(jié)碼(visitCode)珊搀,然后在visitCodevisitMaxs中的那些指令會(huì)按上面所示方法順序訪問(wèn)冶忱,最后類方法訪問(wèn)結(jié)束回調(diào)visitEnd

class文件中境析,方法中的代碼是以一系列的字節(jié)碼指令組成的囚枪。如果要生成或者改變類內(nèi)容,則需要先了解下這些指令的工作模型劳淆。

下面簡(jiǎn)單介紹指令的工作模型链沼,了解這些內(nèi)容就基本能夠完成對(duì)類的一些簡(jiǎn)單的變換操作。如需更詳細(xì)介紹沛鸵,請(qǐng)參考 JVM 規(guī)范括勺。

  • JVM 執(zhí)行模型
    在介紹字節(jié)碼指令之前,有必要先介紹下 JVM 的執(zhí)行模型曲掰。我們都知道疾捍,Java 代碼是運(yùn)行在線程中的栏妖,每條線程都擁有屬于自己的運(yùn)行棧乱豆,棧是由一個(gè)或多個(gè)幀組成的,也叫棧幀(StackFrame)宛裕。每個(gè)棧幀代表一個(gè)方法調(diào)用:每當(dāng)線程調(diào)用一個(gè)Java方法時(shí)孵奶,JVM就會(huì)在該線程對(duì)應(yīng)的棧中壓入一個(gè)幀;當(dāng)執(zhí)行這個(gè)方法時(shí)油航,它使用這個(gè)幀來(lái)存儲(chǔ)參數(shù)、局部變量沙合、中間運(yùn)算結(jié)果等等首懈;當(dāng)方法執(zhí)行結(jié)束(無(wú)論是正常返回還是拋異常)時(shí),該棧幀就會(huì)彈出盯仪,然后繼續(xù)運(yùn)行下一個(gè)棧幀(棧頂棧幀)的方法調(diào)用。

  • 棧幀
    棧幀由三部分組成:局部變量表揭鳞、操作數(shù)棧乓梨、幀數(shù)據(jù)區(qū)昆雀。
    局部變量表 被組織為以一個(gè)字長(zhǎng)(32 bit)為單位揩懒、從0開(kāi)始計(jì)數(shù)的數(shù)組渠缕,類型為shortbytechar的值在存入數(shù)組前要被轉(zhuǎn)換成int值测暗,而longdouble在數(shù)組中占據(jù)連續(xù)的兩項(xiàng)稚字,在訪問(wèn)局部變量中的longdouble時(shí)袄友,只需取出連續(xù)兩項(xiàng)的第一項(xiàng)的索引值即可,如某個(gè)long值在局部變量 區(qū)中占據(jù)的索引時(shí)3、4項(xiàng),取值時(shí),指令只需取索引為3的long值即可纱耻。
    操作數(shù)棧 和局部變量表一樣镐躲,操作數(shù)棧也被組織成一個(gè)以字長(zhǎng)為單位的數(shù)組入录。但和前者不同的是分预,它不是通過(guò)索引來(lái)訪問(wèn)的配乓,而是通過(guò)入棧和出棧來(lái)訪問(wèn)的。可把操作數(shù)棧理解為存儲(chǔ)計(jì)算時(shí),臨時(shí)數(shù)據(jù)的存儲(chǔ)區(qū)域蝗锥。
    幀數(shù)據(jù)區(qū) 幀數(shù)據(jù)區(qū)除了局部變量表和操作數(shù)棧外跃洛,Java棧幀還需要一些數(shù)據(jù)來(lái)支持常量池解析、正常方法返回以及異常派發(fā)機(jī)制终议。這些數(shù)據(jù)都保存在Java棧幀的幀數(shù)據(jù)區(qū)中汇竭。
    當(dāng)JVM執(zhí)行到需要常量池?cái)?shù)據(jù)的指令時(shí),它都會(huì)通過(guò)幀數(shù)據(jù)區(qū)中指向常量池的指針來(lái)訪問(wèn)它穴张。
    除了處理常量池解析外细燎,幀里的數(shù)據(jù)還要處理Java方法的正常結(jié)束和異常終止。如果是通過(guò)return正常結(jié)束皂甘,則當(dāng)前棧幀從Java棧中彈出玻驻,恢復(fù)發(fā)起調(diào)用的方法的棧。如果方法有返回值偿枕,JVM會(huì)把返回值壓入到發(fā)起調(diào)用方法的操作數(shù)棧璧瞬。
    為了處理Java方法中的異常情況,幀數(shù)據(jù)區(qū)還必須保存一個(gè)對(duì)此方法異常引用表的引用益老。當(dāng)異常拋出時(shí)彪蓬,JVM給catch塊中的代碼。如果沒(méi)發(fā)現(xiàn)捺萌,方法立即終止档冬,然后JVM用幀區(qū)數(shù)據(jù)的信息恢復(fù)發(fā)起調(diào)用的方法的幀。然后再發(fā)起調(diào)用方法的上下文重新拋出同樣的異常桃纯。

局部變量表和操作數(shù)棧的大小決于方法代碼酷誓,它們?cè)诰幾g時(shí)進(jìn)行計(jì)算,并與類中的字節(jié)碼指令一起存儲(chǔ)态坦。因此盐数,對(duì)于同一個(gè)方法調(diào)用,所有幀的大小都是一樣的伞梯,但是對(duì)于不同的方法調(diào)用玫氢,各個(gè)棧幀都擁有不同大小的局部變量表和操作數(shù)棧。


表 3.1 展示一個(gè)帶有3個(gè)幀的運(yùn)行棧樣例谜诫。第一個(gè)幀包含3個(gè)局部變量漾峡,其操作數(shù)棧為4個(gè)字長(zhǎng)大小,包含2個(gè)值喻旷。第二個(gè)幀包含2個(gè)局部變量和2個(gè)操作數(shù)值生逸。第三個(gè)幀處于棧頂(當(dāng)前幀),包含4個(gè)局部變量和2個(gè)操作數(shù)值。

當(dāng)空棧壓入一個(gè)幀時(shí)槽袄,其局部變量表會(huì)被初始化壓入目標(biāo)對(duì)象實(shí)例this(對(duì)于非靜態(tài)方法)和方法參數(shù)變量烙无。比如,調(diào)用a.equals(b)時(shí)遍尺,會(huì)創(chuàng)建一個(gè)幀截酷,其局部變量表初始化有2個(gè)局部變量ab(其他變量為被初始化)。

  • 字節(jié)碼指令
    參考 Jvm系列2—字節(jié)碼指令
    在基于堆棧的的虛擬機(jī)中狮鸭,指令的主戰(zhàn)場(chǎng)便是操作數(shù)棧合搅,除了load是從局部變量表加載數(shù)據(jù)到操作數(shù)棧以及store儲(chǔ)存數(shù)據(jù)到局部變量表,其余指令基本都是用于操作數(shù)棧的歧蕉。

  • 示例

package pkg;
public class Bean {
    private int f;
    public int getF() {
     return this.f;
}
  public void setF(int f) {
    this.f = f;
  }
}

上面代碼的getF方法的字節(jié)碼如下:

ALOAD 0
GETFIELD pkg/Bean f I
IRETURN

第一條指令讀取局部變量表索引0的局部變量this,并將值壓入到操作數(shù)棧中康铭。第二條指令先獲取操作數(shù)棧棧頂值this(彈出棧)惯退,獲取該實(shí)例類成員f,并將其壓入棧中从藤。最后一條指令將操作數(shù)棧彈出催跪,將值返回給調(diào)用者。具體過(guò)程如下圖3.2 所示:

上面代碼的setF(int f)方法的字節(jié)碼如下:

ALOAD 0
ILOAD 1
PUTFIELD pkg/Bean f I
RETURN

第一條指令將局部變量表索引0的變量this壓入到操作數(shù)棧夷野;第二條指令將局部變量表索引1的變量f壓入到操作數(shù)棧懊蒸;第三條指令彈出這個(gè)值,并且將一個(gè)int值付給this.f悯搔;最后一條指令將當(dāng)前棧幀銷毀并將結(jié)果返回給調(diào)用者骑丸。具體過(guò)程如下圖3.3 所示:

AnnotationVisitor

AnnotationVisitor

AnnotationVisitor api 訪問(wèn)時(shí)序如下:

( visit | visitEnum | visitAnnotation | visitArray )* visitEnd

Type

Type

Type對(duì)應(yīng)的是 Java 類型,該類提供一些方法方便我們操控 Java 類型和描述符轉(zhuǎn)換妒貌。
比如:

  • Type.INT_TYPE表示一個(gè)int類型的Type實(shí)例通危。
  • Class -> TypeType.getType(String.class)會(huì)返回String對(duì)應(yīng)的Type類型。
  • Descriptor -> TypeType.getType("Ljava/lang/String;)會(huì)返回類型描述符對(duì)應(yīng)的Type類型灌曙。
  • InternalName -> TypeType.getObjectType("java/lang/String")會(huì)返回參數(shù) InternalName 對(duì)應(yīng)的Type類型菊碟。
  • Type -> ClassNameType.getType(String.class).getClassName()會(huì)返回java.lang.String
  • Type -> InternalNameType.getType(String.class).getInternalName()會(huì)返回String.class的 InternalName在刺,即java/lang/String逆害,該方法只適用于class類型或者interface類型。
  • Type -> DescriptorType.getType(String.class).getDescriptor()會(huì)返回String.class的 Descriptor蚣驼,即Ljava/lang/String;
  • MethodDescriptor -> ArgumentTypeType.getArgumentTypes("(I)V")會(huì)返回方法描述符對(duì)應(yīng)的參數(shù)Type[]數(shù)組魄幕,比如此處返回的是{Type.INT_TYPE}
  • MethodDescriptor -> ReturnTypeType.getReturnType("(I)V")會(huì)返回方法描述符對(duì)應(yīng)的函數(shù)返回值Type類型隙姿,比如此處返回的是Type.VOID_TYPE梅垄。

Notice

  1. 通常我們綁定ClassVisitorClassReader的代碼如下:
byte[] b1 = ...; 
ClassWriter cw = new ClassWriter(0); 
// cv forwards all events to cw 
ClassVisitor cv = new ClassVisitor(ASM4, cw) { }; 
ClassReader cr = new ClassReader(b1); 
cr.accept(cv, 0);
byte[] b2 = cw.toByteArray(); // b2 represents the same class as b1 

假設(shè)我們并不想做出改動(dòng)類本身行為,那么按照上面的代碼,效率會(huì)比較低队丝,因?yàn)楸仨毥馕鲎止?jié)數(shù)組并且要經(jīng)歷事件循環(huán)靡馁;如果可以直接復(fù)制原本的字節(jié)數(shù)組b1b2,那么效率將大大提升机久。幸運(yùn)的是臭墨,ASM 已考慮到這種情況,并為我們提供了優(yōu)化方法膘盖,如下所示:

byte[] b1 = ... 
ClassReader cr = new ClassReader(b1); 
ClassWriter cw = new ClassWriter(cr, 0); //pass cr to cw directly
ChangeVersionAdapter ca = new ChangeVersionAdapter(cw); 
cr.accept(ca, 0);
byte[] b2 = cw.toByteArray(); 

原理如下:

  • 如果ClassReader組件檢測(cè)到作為其accept參數(shù)的ClassVisitor返回的MethodVisitor是來(lái)自ClassWriter的胧弛,這表明該方法沒(méi)有被改動(dòng),事實(shí)上甚至不會(huì)被程序感知侠畔。
  • 這種情況下结缚,ClassReader組件就不去解析該方法內(nèi)容,并且不會(huì)產(chǎn)生相應(yīng)事件软棺,只是從ClassWriter復(fù)制這部分方法的字節(jié)碼數(shù)組红竭。

使用優(yōu)化方法,性能上比前者提升有2倍多速度喘落。需要注意的是茵宪,這種優(yōu)化方法需要復(fù)制類中所有常量到新的字節(jié)數(shù)組中,這種優(yōu)化對(duì)于增加成員瘦棋,方法和指令來(lái)說(shuō)稀火,是沒(méi)有問(wèn)題的,但是對(duì)于刪除或者更改類元素名稱來(lái)說(shuō)赌朋,會(huì)大大增加類文件大小凰狞,因此,建議對(duì)于 增加 動(dòng)作的轉(zhuǎn)換使用優(yōu)化方法箕慧。

參考

JVM中的棧和局部變量
Jvm系列2—字節(jié)碼指令

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末服球,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子颠焦,更是在濱河造成了極大的恐慌斩熊,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件伐庭,死亡現(xiàn)場(chǎng)離奇詭異粉渠,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)圾另,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門霸株,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人集乔,你說(shuō)我怎么就攤上這事去件。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵尤溜,是天一觀的道長(zhǎng)倔叼。 經(jīng)常有香客問(wèn)我,道長(zhǎng)宫莱,這世上最難降的妖魔是什么丈攒? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮授霸,結(jié)果婚禮上巡验,老公的妹妹穿的比我還像新娘。我一直安慰自己碘耳,他們只是感情好显设,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著藏畅,像睡著了一般敷硅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上愉阎,一...
    開(kāi)封第一講書(shū)人閱讀 49,772評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音力奋,去河邊找鬼榜旦。 笑死,一個(gè)胖子當(dāng)著我的面吹牛景殷,可吹牛的內(nèi)容都是我干的溅呢。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼猿挚,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼咐旧!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起绩蜻,我...
    開(kāi)封第一講書(shū)人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤铣墨,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后办绝,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體伊约,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年孕蝉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了屡律。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡降淮,死狀恐怖超埋,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤霍殴,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布媒惕,位于F島的核電站,受9級(jí)特大地震影響繁成,放射性物質(zhì)發(fā)生泄漏吓笙。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一巾腕、第九天 我趴在偏房一處隱蔽的房頂上張望面睛。 院中可真熱鬧,春花似錦尊搬、人聲如沸叁鉴。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)幌墓。三九已至,卻和暖如春冀泻,著一層夾襖步出監(jiān)牢的瞬間常侣,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工弹渔, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留胳施,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓肢专,卻偏偏與公主長(zhǎng)得像舞肆,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子博杖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348

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