JVM-Sandbox 啟動(dòng)過(guò)程源碼分析

JVM-Sandbox 啟動(dòng)有兩種方式:ATTACHAGENT誊抛。

AGENT 方式的入口

必須和服務(wù)一起啟動(dòng)度气,需要修改服務(wù)的啟動(dòng)命令陵霉,如:

java -javaagent:${HOME}/sandbox/lib/sandbox-agent.jar=server.port=8820\;server.ip=0.0.0.0 \
     -jar ${HOME}/.sandbox-module/repeater-bootstrap.jar

通過(guò)sandbox-agent.jar的pom文件汁展;或者通過(guò)jar -xvf sandbox-agent.jar解壓jar包猎提,查看META-INF目錄下的MANIFEST.MF文件我們可以獲得程序入口。

pom 方式查看入口

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-assembly-plugin</artifactId>
    <executions>
        <execution>
            <goals>
                <goal>attached</goal>
            </goals>
            <phase>package</phase>
            <configuration>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
                <archive>
                    <manifestEntries>
                        <Premain-Class>com.alibaba.jvm.sandbox.agent.AgentLauncher</Premain-Class>
                        <Agent-Class>com.alibaba.jvm.sandbox.agent.AgentLauncher</Agent-Class>
                        <Can-Redefine-Classes>true</Can-Redefine-Classes>
                        <Can-Retransform-Classes>true</Can-Retransform-Classes>
                    </manifestEntries>
                </archive>
            </configuration>
        </execution>
    </executions>
</plugin>

解壓jar包方式查看入口

admin@wangyuhao lib % jar -xvf  sandbox-agent.jar       
  已創(chuàng)建: META-INF/
  已解壓: META-INF/MANIFEST.MF
  已創(chuàng)建: com/
  已創(chuàng)建: com/alibaba/
  已創(chuàng)建: com/alibaba/jvm/
  已創(chuàng)建: com/alibaba/jvm/sandbox/
  已創(chuàng)建: com/alibaba/jvm/sandbox/agent/
  已解壓: com/alibaba/jvm/sandbox/agent/SandboxClassLoader.class
  已解壓: com/alibaba/jvm/sandbox/agent/AgentLauncher.class
admin@wangyuhao lib % cat META-INF/MANIFEST.MF 
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Created-By: Apache Maven
Built-By: admin
Build-Jdk: 1.8.0_281
Agent-Class: com.alibaba.jvm.sandbox.agent.AgentLauncher
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Premain-Class: com.alibaba.jvm.sandbox.agent.AgentLauncher

通過(guò)上述兩種方式我們可以找到程序入口類是AgentLauncher寸五,這種方式啟動(dòng)是調(diào)用的premain方法:

    /**
     * 啟動(dòng)加載
     *
     * @param featureString 啟動(dòng)參數(shù)
     *                      [namespace,prop]
     * @param inst          inst
     */
    public static void premain(String featureString, Instrumentation inst) {
        System.out.println("Sandbox 以Agent方式啟動(dòng)");
        LAUNCH_MODE = LAUNCH_MODE_AGENT;
        install(toFeatureMap(featureString), inst);
    }

這里的核心是install(toFeatureMap(featureString), inst);方法梳凛,主要作用是在當(dāng)前JVM安裝jvm-sandbox

ATTACH 方式的入口

即插即用的啟動(dòng)模式播歼,可以在不重啟目標(biāo)JVM的情況下完成沙箱的植入伶跷。原理和GREYS掰读、BTrace類似秘狞,利用了JVM的Attach機(jī)制實(shí)現(xiàn),它是調(diào)用的agentmain方法蹈集。

這種方式加載稍微復(fù)雜一點(diǎn)烁试,他的啟動(dòng)入口是執(zhí)行sandbox命令,如:

./sandbox.sh -p `ps -ef | grep java | grep 'com.alibaba.repeater.console.start.Application' | grep -v grep | awk '{print $2}'`

ps -ef | grep java | grep 'com.alibaba.repeater.console.start.Application' | grep -v grep | awk '{print $2}'的作用是用來(lái)找到當(dāng)前jvm程序的進(jìn)程號(hào)拢肆,轉(zhuǎn)換過(guò)來(lái)的命令是:./sandbox.sh -p 67672减响。

深入sandbox.sh這個(gè)腳本可以發(fā)現(xiàn)其核心是attach_jvm函數(shù)。:

# attach sandbox to target JVM
# return : attach jvm local info
function attach_jvm() {

  # got an token
  local token
  token="$(date | head | cksum | sed 's/ //g')"

  # attach target jvm
  "${SANDBOX_JAVA_HOME}/bin/java" \
    ${SANDBOX_JVM_OPS} \
    -jar "${SANDBOX_LIB_DIR}/sandbox-core.jar" \
    "${TARGET_JVM_PID}" \
    "${SANDBOX_LIB_DIR}/sandbox-agent.jar" \
    "home=${SANDBOX_HOME_DIR};token=${token};server.ip=${TARGET_SERVER_IP};server.port=${TARGET_SERVER_PORT};namespace=${TARGET_NAMESPACE}" ||
    exit_on_err 1 "attach JVM ${TARGET_JVM_PID} fail."

  # get network from attach result
  SANDBOX_SERVER_NETWORK=$(grep "${token}" "${SANDBOX_TOKEN_FILE}" | grep "${TARGET_NAMESPACE}" | tail -1 | awk -F ";" '{print $3";"$4}')
  [[ -z ${SANDBOX_SERVER_NETWORK} ]] &&
    exit_on_err 1 "attach JVM ${TARGET_JVM_PID} fail, attach lose response."

}

attach_jvm函數(shù)首先會(huì)生成一個(gè)帶時(shí)間戳信息的唯一token郭怪,然后使用java命令啟動(dòng)sandbox代理支示,最后獲取attach結(jié)果的網(wǎng)絡(luò)信息。核心是執(zhí)行了java -jar sandbox-cor.jar然后傳了三個(gè)參數(shù)鄙才,簡(jiǎn)化后的關(guān)鍵信息如下:

java -jar /home/admin/sandbox/lib/sandbox-core.jar  76092 "/home/admin/sandbox/lib/sandbox-agent.jar" "home=/home/admin/sandbox;token=2019091703032893;server.ip=0.0.0.0;server.port=12345;namespace=default" 

通過(guò)上述sandbox-agent.jar 查詢?nèi)肟陬惖姆绞剿毯瑁梢哉业?code>sandbox-cor.jar的入口類為:CoreLauncher,入口即為該類的main方法攒庵。

public static void main(String[] args) {
    try {

        // check args
        if (args.length != 3
                || StringUtils.isBlank(args[0])
                || StringUtils.isBlank(args[1])
                || StringUtils.isBlank(args[2])) {
            throw new IllegalArgumentException("illegal args");
        }

        new CoreLauncher(args[0], args[1], args[2]);
    } catch (Throwable t) {
        t.printStackTrace(System.err);
        System.err.println("sandbox load jvm failed : " + getCauseMessage(t));
        System.exit(-1);
    }
}

main方法通過(guò)解析接口傳過(guò)來(lái)的三個(gè)參數(shù)嘴纺,三個(gè)參數(shù)分別為:

  • targetJvmPid(PID(JVM進(jìn)程ID)):76092。
  • agentJarPath(agent.jar全路徑):/home/admin/sandbox/lib/sandbox-core.jar浓冒。
  • cfg(配置信息):home=/home/admin/sandbox;token=2019091703032893;server.ip=0.0.0.0;server.port=12345;namespace=default栽渴。

最后通過(guò)獲取到的信息調(diào)用VirtualMachine.attach()方法,通過(guò)attach來(lái)執(zhí)行agent.jar稳懒。

// 加載Agent
private void attachAgent(final String targetJvmPid,
                         final String agentJarPath,
                         final String cfg) throws Exception {
    
    VirtualMachine vmObj = null;
    try {
        vmObj = VirtualMachine.attach(targetJvmPid);
        if (vmObj != null) {
            vmObj.loadAgent(agentJarPath, cfg);
        }

    } finally {
        if (null != vmObj) {
            vmObj.detach();
        }
    }

}

Attach實(shí)現(xiàn)原理可以參考如下資料:

通過(guò)上述方法就調(diào)用了sandbox-agent.jar中的AgentLauncher類的agentmain方法:

/**
 * 動(dòng)態(tài)加載
 *
 * @param featureString 啟動(dòng)參數(shù)
 *                      [namespace,token,ip,port,prop]
 * @param inst          inst
 */
public static void agentmain(String featureString, Instrumentation inst) {
    System.out.println("Sandbox 以ATTACH方式啟動(dòng)");
    LAUNCH_MODE = LAUNCH_MODE_ATTACH;
    final Map<String, String> featureMap = toFeatureMap(featureString);
    writeAttachResult(
            getNamespace(featureMap),
            getToken(featureMap),
            install(featureMap, inst)
    );
}

CoreLauncher類主要完成了sandbox-agent.jar的代理加載闲擦,核心方法還是 install(featureMap, inst)方法。

由此可見(jiàn)ATTACHAGENT兩種啟動(dòng)方式最后都是調(diào)用的 install(featureMap, inst)子方法來(lái)完成sandbox的加載场梆。

Agent 初始化過(guò)程(install方法)

在 install 方法中完成對(duì) agent 的初始化墅冷,在初始化的過(guò)程中使用到了自定義的 SandboxClassLoader 對(duì)沙箱類進(jìn)行加載,ModuleJarClassLoader 對(duì)./modele辙谜、~/.sanbox-modele目錄中module俺榆。jar進(jìn)行加載,實(shí)現(xiàn)沙箱內(nèi)部類與業(yè)務(wù)類隔離装哆。

sandbox-agent.jar中的AgentLauncher類的install方法源碼如下:

/**
 * 在當(dāng)前JVM安裝jvm-sandbox
 *
 * @param featureMap 啟動(dòng)參數(shù)配置
 * @param inst       inst
 * @return 服務(wù)器IP:PORT
 */
private static synchronized InetSocketAddress install(final Map<String, String> featureMap,
                                                      final Instrumentation inst) {

    final String namespace = getNamespace(featureMap);
    final String propertiesFilePath = getPropertiesFilePath(featureMap);
    final String coreFeatureString = toFeatureString(featureMap);

    try {
        final String home = getSandboxHome(featureMap);
        // 將Spy注入到BootstrapClassLoader
        inst.appendToBootstrapClassLoaderSearch(new JarFile(new File(
                getSandboxSpyJarPath(home)
                // SANDBOX_SPY_JAR_PATH
        )));

        // 構(gòu)造自定義的類加載器罐脊,盡量減少Sandbox對(duì)現(xiàn)有工程的侵蝕
        final ClassLoader sandboxClassLoader = loadOrDefineClassLoader(
                namespace,
                getSandboxCoreJarPath(home)
                // SANDBOX_CORE_JAR_PATH
        );

        // CoreConfigure類定義
        final Class<?> classOfConfigure = sandboxClassLoader.loadClass(CLASS_OF_CORE_CONFIGURE);

        // 反序列化成CoreConfigure類實(shí)例
        final Object objectOfCoreConfigure = classOfConfigure.getMethod("toConfigure", String.class, String.class)
                .invoke(null, coreFeatureString, propertiesFilePath);

        // CoreServer類定義
        final Class<?> classOfProxyServer = sandboxClassLoader.loadClass(CLASS_OF_PROXY_CORE_SERVER);

        // 獲取CoreServer單例
        final Object objectOfProxyServer = classOfProxyServer
                .getMethod("getInstance")
                .invoke(null);

        // CoreServer.isBind()
        final boolean isBind = (Boolean) classOfProxyServer.getMethod("isBind").invoke(objectOfProxyServer);


        // 如果未綁定,則需要綁定一個(gè)地址
        if (!isBind) {
            try {
                classOfProxyServer
                        .getMethod("bind", classOfConfigure, Instrumentation.class)
                        .invoke(objectOfProxyServer, objectOfCoreConfigure, inst);
            } catch (Throwable t) {
                classOfProxyServer.getMethod("destroy").invoke(objectOfProxyServer);
                throw t;
            }

        }

        // 返回服務(wù)器綁定的地址
        return (InetSocketAddress) classOfProxyServer
                .getMethod("getLocal")
                .invoke(objectOfProxyServer);


    } catch (Throwable cause) {
        throw new RuntimeException("sandbox attach failed.", cause);
    }

}

核心流程:

  1. 通過(guò)Instrumentation調(diào)用BootstrapClassLoader去加載sandbox-spy.jar定嗓,sandbox-spy.jar的主要作用是完成目標(biāo)JVM和sandbox的通訊。
  2. 創(chuàng)建SandboxClassLoader萍桌,并通過(guò)該ClassLoader去加載sandbox-core.jar宵溅。
  3. 通過(guò)SandboxClassLoader去加載CoreConfigure類,然后將所有沙箱配置映射賦值到該類的實(shí)例上炎。
  4. 通過(guò)SandboxClassLoader去加載ProxyCoreServer類恃逻,并獲得一個(gè)JettyCoreServer實(shí)例。
  5. 然后調(diào)用JettyCoreServerbind方法完成Spy的初始化(SpyUtils.init(cfg.getNamespace());)藕施、HTTP 服務(wù)的初始化和啟動(dòng)寇损、通過(guò)ModuleJarClassLoader加載所有mudule(jvmSandbox.getCoreModuleManager().reset();)。
  6. 最后裳食,返回代理核心服務(wù)器JettyCoreServer的服務(wù)器綁定的地址矛市。
image.png

Spy 間諜類

install方法首先會(huì)通過(guò)Instrumentation 實(shí)例將 sandbox-spy.jar 添加到 BootstrapClassLoader 的搜索范圍內(nèi)。

// 將Spy注入到BootstrapClassLoader
inst.appendToBootstrapClassLoaderSearch(new JarFile(new File(
        getSandboxSpyJarPath(home)
        // SANDBOX_SPY_JAR_PATH
)));

使用BootstrapClassLoader去加載spy的最要目的應(yīng)該是保證Spy能增強(qiáng)所有的類诲祸,包括JDK自帶的一些類浊吏,Spy的主要作用是完成目標(biāo)JVM和sandbox間的通訊,sandbox會(huì)將方法的執(zhí)行分為三個(gè)階段BEFORE(方法執(zhí)行前)救氯、RETURN (方法返回)和 THROWS(方法異常) 三個(gè)環(huán)節(jié)找田。

// BEFORE
try {

   /*
    * do something...
    */

    // RETURN
    return;

} catch (Throwable cause) {
    // THROWS
}

基于BEFORERETURNTHROWS三個(gè)環(huán)節(jié)事件分離着憨,沙箱的模塊可以完成很多類AOP的操作墩衙。

  1. 可以感知和改變方法調(diào)用的入?yún)?/li>
  2. 可以感知和改變方法調(diào)用返回值和拋出的異常
  3. 可以改變方法執(zhí)行的流程
    • 在方法體執(zhí)行之前直接返回自定義結(jié)果對(duì)象,原有方法代碼將不會(huì)被執(zhí)行
    • 在方法體返回之前重新構(gòu)造新的結(jié)果對(duì)象享扔,甚至可以改變?yōu)閽伋霎惓?/li>
    • 在方法體拋出異常之后重新拋出新的異常底桂,甚至可以改變?yōu)檎7祷?/li>

要完成這些動(dòng)作都是依賴Spy暴露出來(lái)的xxxOnBefore()xxxOnReturn()xxxOnThrows()等鉤子函數(shù)來(lái)完成通訊惧眠,下圖是官網(wǎng)提供的圖:

image.png

為了更加直觀的看到代碼增強(qiáng)后的效果籽懦,我在我的服務(wù)里面新寫(xiě)了下面一個(gè)測(cè)試類,并通過(guò)自帶的debug-trace模塊來(lái)查看spyEnhance方法的執(zhí)行耗時(shí)氛魁,然后反編譯JVM中內(nèi)存中的class文件查看效果暮顺。

原始TestService類:

@Service("testService")
public class TestService {

    /**
     * 未增強(qiáng)方法
     *
     * @param name
     * @return
     */
    public String notEnhance(String name) {
        System.out.println("notEnhance");
        return "1";
    }
    
    /**
     * 增強(qiáng)方法
     *
     * @return
     */
    public String spyEnhance() {
        System.out.println("spyEnhance");
        return "1";
    }

}

通過(guò)命令監(jiān)聽(tīng)spyEnhance方法:

./sandbox.sh -p `ps -ef | grep java | grep 'com.alibaba.repeater.console.start.Application' | grep -v grep | awk '{print $2}'`  -d 'debug-trace/trace?class=com.alibaba.repeater.console.service.impl.TestService&method=spyEnhance'

然后反編譯JVM 內(nèi)存中TestClass類:

ClassLoader:                                                                                                                                                                                                                                                                                                       
+-sun.misc.Launcher$AppClassLoader@18b4aac2                                                                                                                                                                                                                                                                        
  +-sun.misc.Launcher$ExtClassLoader@3ec300f1                                                                                                                                                                                                                                                                      

Location:                                                                                                                                                                                                                                                                                                          
/Users/admin/Documents/workspace/jvm-sandbox-repeater/repeater-console/repeater-console-service/target/classes/                                                                                                                                                                                                    

       /*
        * Decompiled with CFR.
        * 
        * Could not load the following classes:
        *  java.com.alibaba.jvm.sandbox.spy.Spy
        *  java.com.alibaba.jvm.sandbox.spy.Spy$Ret
        */
       package com.alibaba.repeater.console.service.impl;
       
       import java.com.alibaba.jvm.sandbox.spy.Spy;
       import org.springframework.stereotype.Service;
       
       @Service(value="testService")
       public class TestService {
           public String notEnhance(String name) {
/*21*/         System.out.println("notEnhance");
/*22*/         return "1";
           }
       
           /*
            * Enabled aggressive block sorting
            * Enabled unnecessary exception pruning
            * Enabled aggressive exception aggregation
            */
           public String spyEnhance() {
               try {
                   Spy.Ret ret = Spy.spyMethodOnBefore((Object[])new Object[0], (String)"default", (int)1006, (int)1004, (String)"com.alibaba.repeater.console.service.impl.TestService", (String)"spyEnhance", (String)"()Ljava/lang/String;", (Object)this);
                   int n = ret.state;
                   if (n == 1) return (String)ret.respond;
                   if (n == 2) {
                       Spy.Ret ret2;
                       throw (Throwable)ret2.respond;
                   }
                   Spy.spyMethodOnCallBefore((int)31, (String)"java.io.PrintStream", (String)"println", (String)"(Ljava/lang/String;)V", (String)"default", (int)1006);
                   try {
                       System.out.println("spyEnhance");
                   }
                   catch (Throwable throwable) {
                       Spy.spyMethodOnCallThrows((String)throwable.getClass().getName(), (String)"default", (int)1006);
                       throw throwable;
                   }
                   Spy.spyMethodOnCallReturn((String)"default", (int)1006);
/*32*/             Spy.Ret ret3 = Spy.spyMethodOnReturn((Object)"1", (String)"default", (int)1006);
                   int n2 = ret3.state;
                   if (n2 == 1) return (String)ret3.respond;
                   if (n2 == 2) Spy.Ret ret4;
                   throw (Throwable)ret4.respond;
                   return "1";
               }
               catch (Throwable throwable) {
                   Throwable throwable2 = throwable;
                   Spy.Ret ret = Spy.spyMethodOnThrows((Throwable)throwable2, (String)"default", (int)1006);
                   int n = ret.state;
                   if (n == 1) return (String)ret.respond;
                   if (n == 2) throw (Throwable)ret.respond;
                   throw throwable2;
               }
           }
       }

Spy.spyMethodOnBefore源碼如下:

public static Ret spyMethodOnBefore(final Object[] argumentArray,
                                    final String namespace,
                                    final int listenerId,
                                    final int targetClassLoaderObjectID,
                                    final String javaClassName,
                                    final String javaMethodName,
                                    final String javaMethodDesc,
                                    final Object target) throws Throwable {
    final Thread thread = Thread.currentThread();
    if (selfCallBarrier.isEnter(thread)) {
        return Ret.RET_NONE;
    }
    final SelfCallBarrier.Node node = selfCallBarrier.enter(thread);
    try {
        final SpyHandler spyHandler = namespaceSpyHandlerMap.get(namespace);
        if (null == spyHandler) {
            return Ret.RET_NONE;
        }
        return spyHandler.handleOnBefore(
                listenerId, targetClassLoaderObjectID, argumentArray,
                javaClassName,
                javaMethodName,
                javaMethodDesc,
                target
        );
    } catch (Throwable cause) {
        handleException(cause);
        return Ret.RET_NONE;
    } finally {
        selfCallBarrier.exit(thread, node);
    }
}

通過(guò)反編譯的代碼我們可以看出,通過(guò)注入的Spy.spyMethodOnBefore()方法作為sandbox的入口秀存,然后調(diào)用了sandbox的事件分發(fā)處理器EventListener.onEvent()方法捶码,sandbox通過(guò)Spy完成了目標(biāo)JVM和sandbox的通訊,打開(kāi)了鏈接兩個(gè)世界的大門(mén)或链。

sandbox增強(qiáng)器EventEnhancer內(nèi)部提供了輸出增強(qiáng)類的方法惫恼,是否輸出增強(qiáng)代碼的開(kāi)關(guān)isDumpClass=true需要手動(dòng)開(kāi)啟,開(kāi)關(guān)打開(kāi)后澳盐,在自己項(xiàng)目的sandbox-class-dump目錄可以查看到被增強(qiáng)的所有類祈纯,源碼如下:

private static final boolean isDumpClass = true;

/*
 * dump class to file
 * 用于代碼調(diào)試
 */
private static byte[] dumpClassIfNecessary(String className, byte[] data) {
    if (!isDumpClass) {
        return data;
    }
    final File dumpClassFile = new File("./sandbox-class-dump/" + className + ".class");
    final File classPath = new File(dumpClassFile.getParent());

    // 創(chuàng)建類所在的包路徑
    if (!classPath.mkdirs()
            && !classPath.exists()) {
        logger.warn("create dump classpath={} failed.", classPath);
        return data;
    }

    // 將類字節(jié)碼寫(xiě)入文件
    try {
        writeByteArrayToFile(dumpClassFile, data);
        logger.info("dump {} to {} success.", className, dumpClassFile);
    } catch (IOException e) {
        logger.warn("dump {} to {} failed.", className, dumpClassFile, e);
    }

    return data;
}

核心類的加載

命令執(zhí)行原理

sandbox完成啟動(dòng)后令宿,后續(xù)的所有命令的執(zhí)行其實(shí)是直接訪問(wèn)的HTTP服務(wù)器,比如 ./sandbox.sh -p 7640 -l命令腕窥,最后執(zhí)行的是 curl 命令粒没,翻譯過(guò)來(lái)是:curl -N -s "http://10.242.232.9:8820/sandbox/default/module/http/sandbox-module-mgr/list"

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末簇爆,一起剝皮案震驚了整個(gè)濱河市癞松,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌入蛆,老刑警劉巖响蓉,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異安寺,居然都是意外死亡厕妖,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)挑庶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人软能,你說(shuō)我怎么就攤上這事迎捺。” “怎么了查排?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵凳枝,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我跋核,道長(zhǎng)岖瑰,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任砂代,我火速辦了婚禮蹋订,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘刻伊。我一直安慰自己露戒,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布捶箱。 她就那樣靜靜地躺著智什,像睡著了一般。 火紅的嫁衣襯著肌膚如雪丁屎。 梳的紋絲不亂的頭發(fā)上荠锭,一...
    開(kāi)封第一講書(shū)人閱讀 49,111評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音晨川,去河邊找鬼证九。 笑死键思,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的甫贯。 我是一名探鬼主播吼鳞,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼叫搁!你這毒婦竟也來(lái)了赔桌?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤渴逻,失蹤者是張志新(化名)和其女友劉穎疾党,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體惨奕,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡雪位,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了梨撞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片雹洗。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖卧波,靈堂內(nèi)的尸體忽然破棺而出时肿,到底是詐尸還是另有隱情,我是刑警寧澤港粱,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布螃成,位于F島的核電站,受9級(jí)特大地震影響查坪,放射性物質(zhì)發(fā)生泄漏寸宏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一偿曙、第九天 我趴在偏房一處隱蔽的房頂上張望氮凝。 院中可真熱鬧,春花似錦遥昧、人聲如沸覆醇。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)永脓。三九已至,卻和暖如春鞋仍,著一層夾襖步出監(jiān)牢的瞬間常摧,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留落午,地道東北人谎懦。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像溃斋,于是被迫代替她去往敵國(guó)和親界拦。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

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