精华内容
下载资源
问答
  • 35 Spark系统运行循环流程

    千次阅读 2016-05-14 01:27:38
    DT大数据梦工厂第三十五课 Spark系统运行循环流程 内容: 1. TaskScheduler工作原理 2. TaskScheduler源码

    本节课内容:

    1.     TaskScheduler工作原理

    2.     TaskScheduler源码

     

    一、TaskScheduler工作原理

           总体调度图:


           通过前几节课的讲解,RDD和DAGScheduler以及Worker都已有深入的讲解,这节课我们主要讲解TaskScheduler的运行原理。

           回顾:

           DAGScheduler面向整个Job划分多个Stage,划分是从后往前的回溯过程;运行时从前往后运行的。每个Stage中有很多任务Task,Task是可以并行执行的。它们的执行逻辑完全相同的,只不过是处理的数据不同而已,DAGScheduler通过TaskSet的方式,把其构造的所有Task提交给底层调度器TaskScheduler。

    TaskScheduler是一个trait,与具体的资源调度解耦合,这符合面向对象中依赖抽象不依赖具体的原则,带来底层资源调度器的可插拔性,导致Spark可以运行的众多的资源调度模式上,例如:StandAlone、Yarn、Mesos、Local、EC2或者其他自定义的资源调度器。

    在StandAlone模式下,我们来看看TaskScheduler的一个实现TaskSchedulerImpl。

    1.TaskScheduler的核心任务

    TaskScheduler的核心任务是提交TaskSet到集群并汇报结果,主要负责Application的不同Job之间的调度。

    具体来讲有以下几点:

    (1)    为TaskSet创建和维护一个TaskSetManager并追踪任务的本地性以及错误信息;

    (2)    Task执行失败时启动重试机制,以及遇到Straggle任务会在其他节点上启动备份任务;

    (3)    向DAGScheduler汇报执行情况,包括在shuffle输出丢失的时候报告fetch failed错误等信息。

    2.TaskScheduler的核心功能

           (1)注册当前程序。

    TaskScheduler内部会握有SchedulerBackend引用,SchedulerBackend是一个trait,它主要负责管理Executor资源,从StandAlone模式来讲,具体实现是SparkDeploySchedulerBackend。SparkDeploySchedulerBackend在启动时会构造AppClient实例并在该实例start的时候启动ClientEndpoint(消息循环体),ClientEndpoint在启动时会向Master注册当前程序。

    (1)   注册Executor信息。

    SparkDeploySchedulerBackend的父类CoarseGrainedSchedulerBackend会在Start的时候实例化一个类型DriverEndpoint的消息循环体。DriverEndpoint就是我们程序运行时的Driver对象。SparkDeploySchedulerBackend是专门给来负责收集Worker上的资源信息,当ExecutorBackend启动的时候会发送RegisteredExecutor信息向Driver中的DriverBackend进行注册。(可以参考前几讲的Master注册部分。)此时SparkDeploySchedulerBackend就掌握了当前应用应用程序所拥有的计算资源,TaskScheduler就是通过SparkDeploySchedulerBackend拥有的计算资源来具体运行Task。

    补充:SparkContext、DAGScheduler、TaskSchedulerImpl、SparkDeploySchedulerBackend在应用程序启动的时候值实例化一次,应用程序存在期间始终存在这些对象。SparkDeploySchedulerBackend是一个辅助类,主要是帮助TaskSchedulerImpl中的Task获取计算资源和发送Task到集群中

    3.TaskScheduler的实例化时机

           TaskScheduler是在SparkContext实例化时进行实例化的,如TaskSchedulerImpl的实例化。(Spark1.6.0  SparkContext.scala  #521-#526)

    // Create and start the scheduler
        val (sched, ts) = SparkContext.createTaskScheduler(this, master)
        _schedulerBackend = sched
        _taskScheduler = ts
        _dagScheduler = new DAGScheduler(this)
        _heartbeatReceiver.ask[Boolean](TaskSchedulerIsSet)
    

    在SparkContext#createTaskScheduler方法中会创建TaskScheduler和SparkDeploySchedulerBackend:
    private def createTaskScheduler(
          sc: SparkContext,
          master: String): (SchedulerBackend, TaskScheduler) = {
        import SparkMasterRegex._ 
        //省略部分代码
    	case SPARK_REGEX(sparkUrl) =>
            val scheduler = new TaskSchedulerImpl(sc)
            val masterUrls = sparkUrl.split(",").map("spark://" + _)
            val backend = new SparkDeploySchedulerBackend(scheduler, sc, masterUrls)
    		//利用SparkDeploySchedulerBackend来初始化TaskScheduler
            scheduler.initialize(backend)  //1
            (backend, scheduler)
    }
    

    4.TaskScheduler初始化

    <pre name="code" class="plain">//1被处调用
    def initialize(backend: SchedulerBackend) {
       	 this.backend = backend
       	 // temporarily set rootPool name to empty
      	 rootPool = new Pool("", schedulingMode, 0, 0) //2
    	//根据rootPool中的算法创建可调度对象
       	 schedulableBuilder = {
         	 schedulingMode match {
            case SchedulingMode.FIFO =>
    		//FIFO模式
              new FIFOSchedulableBuilder(rootPool)
            case SchedulingMode.FAIR =>
    		//Fair模式
              new FairSchedulableBuilder(rootPool, conf)
          }
        }
        //创建调度池
        schedulableBuilder.buildPools()
      }
    


    
    

    创建调度池

           (1)创建rootPool(实现调度算法)
    //2处被调用
    private[spark] class Pool(
        val poolName: String,
        val schedulingMode: SchedulingMode,
        initMinShare: Int,
        initWeight: Int)
      extends Schedulable
      with Logging {
    
      val schedulableQueue = new ConcurrentLinkedQueue[Schedulable]
      val schedulableNameToSchedulable = new ConcurrentHashMap[String, Schedulable]
      var weight = initWeight
      var minShare = initMinShare
      var runningTasks = 0
      var priority = 0
    
      // A pool's stage id is used to break the tie in scheduling.
      var stageId = -1
      var name = poolName
      var parent: Pool = null
    	//根据不同的调度算法创建调度算法的实例
      var taskSetSchedulingAlgorithm: SchedulingAlgorithm = {
        schedulingMode match {
          case SchedulingMode.FAIR =>
            new FairSchedulingAlgorithm()
          case SchedulingMode.FIFO =>
            new FIFOSchedulingAlgorithm()
        }
      }
    

    (2)创建可调度对象

    Org.apache.spark.scheduler.Pool包含了一组可以调度的实体。对于FIFO来说,rootPool包含了一组TaskSetManager;而对于FAIR来说,rootPool包含了一组Pool,这些Pool构成了一颗调度树,其中这棵树的叶子节点就是TaskSetManager。

    (3)创建调度池

    schedulableBuilder.buildPools()因调度方式而异,如果是FIFO,它的实现是空的如下:

    private[spark] class FIFOSchedulableBuilder(val rootPool: Pool)
      extends SchedulableBuilder with Logging {
      override def buildPools() {
        // nothing
      }
    	//定义了如何将TaskSetManager加入到调度池中
      override def addTaskSetManager(manager: Schedulable, properties: Properties) {
        rootPool.addSchedulable(manager) //3
      }
    }
    
    因为rootPool并没有包含Pool,而是直接包含TaskSetManager:submitTasks直接将TaskSetManager添加到rootPool(调度队列,队列默认是先入先出)即可
     //将可调度对象加入到调度队列  3处被调用
     override def addSchedulable(schedulable: Schedulable) {
        require(schedulable != null)
        schedulableQueue.add(schedulable)
        schedulableNameToSchedulable.put(schedulable.name, schedulable)
        schedulable.parent = this
      }
    

           而FAIR模式则需要在运行前先进行一定的配置。它需要在rootPool的基础上根据这个配置文件来构建这颗调度树。

    具体实现见如下代码:
    override def buildPools() {
        var is: Option[InputStream] = None
        try {
          is = Option {
    		//以spark.scheduler.allocation.file设置的文件名字来创建FileInputStream
            schedulerAllocFile.map { f =>
              new FileInputStream(f)
            }.getOrElse {
    		//若spark.Scheduler.allocation.file没有设置,则直接以fairscheduler.xml创建
    		//FileInputStream
              Utils.getSparkClassLoader.getResourceAsStream(DEFAULT_SCHEDULER_FILE)
            }
          }
    	//以is对应的内容创建Pool
          is.foreach { i => buildFairSchedulerPool(i) }
        } finally {
          is.foreach(_.close())
        }
    	创建名为“default”的Pool
        buildDefaultPool()
      }
    

    (4)调度算法

    private[spark] traitSchedulingAlgorithm {
      <span style="white-space:pre">	</span>def comparator(s1: Schedulable, s2:Schedulable): Boolean
        }

    从代码来看调度算法是一个trait,需要子类实现。其实质就是封装了一个比较函数。子类只需实现这个比较函数即可。

    (a)FIFO

    采用FIFO任务调度的顺序:

    首先要保证JobID较小的先被调度,如果是同一个Job,那么StageID小的先被调度(同一个Job,可能多个Stage可以并行执行,比如Stage划分过程中Stage0和Stage1)。


    调度算法:

    private[spark] class FIFOSchedulingAlgorithm extends SchedulingAlgorithm {
    	//比较可调度对象s1与s2,这里s1与s2其实就是TaskSetManager。
      override def comparator(s1: Schedulable, s2: Schedulable): Boolean = {
        val priority1 = s1.priority     //这个priority实际上就是Job ID
        val priority2 = s2.priority     //同上
        var res = math.signum(priority1 - priority2)  //首先比较Job ID
        if (res == 0) { //如果Job ID相同,那么比较Stage ID
          val stageId1 = s1.stageId
          val stageId2 = s2.stageId
          res = math.signum(stageId1 - stageId2)
        }
        if (res < 0) {
          true
        } else {
          false
        }
      }
    }
    
    (2)FAIR

    对于FAIR,首先是挂在rootPool下面的pool先确定调度顺序,然后在每个pool内部使用相同的算法来确定TaskSetManager的调度顺序。


    算法实现:

    private[spark] class FairSchedulingAlgorithm extends SchedulingAlgorithm {
      override def comparator(s1: Schedulable, s2: Schedulable): Boolean = {
    	//最小共享,可以理解为执行需要的最小资源即CPU核数,其他相同时,所需最小核数小的优先
    //调度
        val minShare1 = s1.minShare
        val minShare2 = s2.minShare
    //运行的任务的数量
        val runningTasks1 = s1.runningTasks
        val runningTasks2 = s2.runningTasks
    	//查看是否有调度队列处于饥饿状态,看可分配的核数是否少于任务数,如果资源不够用,那么
    //处于挨饿状态
        val s1Needy = runningTasks1 < minShare1
        val s2Needy = runningTasks2 < minShare2
    	//计算ShareRatio, 最小资源占用比例,这里可以理解为偏向任务较轻的
        val minShareRatio1 = runningTasks1.toDouble / math.max(minShare1, 1.0).toDouble
        val minShareRatio2 = runningTasks2.toDouble / math.max(minShare2, 1.0).toDouble
    	//计算Task的Weight比重即权重,任务数相同,权重高的优先
        val taskToWeightRatio1 = runningTasks1.toDouble / s1.weight.toDouble
        val taskToWeightRatio2 = runningTasks2.toDouble / s2.weight.toDouble
        var compare: Int = 0
    	//首先处于饥饿优先
        if (s1Needy && !s2Needy) {
          return true
        } else if (!s1Needy && s2Needy) {
          return false
        } else if (s1Needy && s2Needy) {
          //都处于挨饿状态则,需要资源占用比小的优先
          compare = minShareRatio1.compareTo(minShareRatio2)
        } else {
    	//都不挨饿,则比较权重比,比例低的优先
          compare = taskToWeightRatio1.compareTo(taskToWeightRatio2)
        }
    
        if (compare < 0) {
          true
        } else if (compare > 0) {
          false
        } else {
    	//如果都一样,那么比较名字,按照字母顺序比较,所以名字比较重要
          s1.name < s2.name
        }
      }
    }
    

    注:

    公平原则本着的原则就是谁最需要就给谁,所以挨饿者优先;

    资源占用比这块有点费解,如果把他理解成一个贪心问题就容易理解了。对于都是出于挨饿状态的任务可以这样理解,负载大的即时给你资源你也不一定能有效缓解,莫不如给负载小的,让其快速使用,完成后可以释放更多的资源,这是一种贪心策略。如JobA和JobB的Task数量相同都是10,A的minShare是2,B的是5,那占用比为5和2,显然B的占用比更小,贪心的策略应该给B先调度处理;

    对于都处于满足状态的,当然谁的权重有着更好的决定性,权重比低得优先(偏向权利大的);

    如果所有上述的比较都相同,那么名字字典排序靠前的优先(哈哈,名字很重要哦);名字aaa要比abc优先,所以这里在给Pool或者TaskSetManager起名字的时候要考虑这一点

     (备注来源:https://yq.aliyun.com/articles/6041

     补充:这两种调度的排序算法针对的可比较对象都是Schedule的具体对象,这里我们对这个对象Schedulable做简单的解释。

           前面讲到,Schedulable可调度对象在Spark有两种形式:Pool和TaskSetManager。Pool是一个调度池,Pool里面还可以有子Pool,Spark中的rootPool即根节点默认是一个无名(default)的Pool。对于FIFO和FAIR有不同的层次。

           对于FIFO模式的调度,rootPool管理的直接就是TaskSetManager,没有子Pool这个概念,就只有两层,rootPool和叶子节点TaskSetManager,实现如下所示。


    但对于FAIR这种模式来说,是三层的,根节点是rootPool,为无名Pool,下一层为用户定义的Pool(不指定名称默认名称为default),再下一层才是TaskSetManager,即根调度池管理一组调度池,每个调度池管理自己的TaskSetManager,其实现如下所示。



    这里的调度顺序是指在一个SparkContext之内的调度,一般情况下我们自行使用是不太会需要Pool这个概念的,因为不存在Pool之间的竞争,但如果我们提供一个Spark应用,大家都可以提交任务,服务端有一个常驻的任务,对应一个SparkContext,每个用户提交的任务都由其代理执行,那么针对每个用户提交的任务可以按照用户等级和任务优先级设置一个Pool,这样不同的用户的Pool之间就存在竞争关系了,可以用Pool的优先级来区分任务和用户的优先级了,**但要再强调一点名字很重要,因为FAIR机制中,如果其他比较无法判断,那么会按照名字来进行字典排序的**。(补充来源:https://yq.aliyun.com/articles/6041)

    5.创建DAGScheduler,调用TaskScheduler#start方法(SparkContext初始化过程中)

    //SparkContext.scala
    525)     _dagScheduler = new DAGScheduler(this)
    526)     _heartbeatReceiver.ask[Boolean](TaskSchedulerIsSet)
    527)
    528)    // start TaskScheduler after taskScheduler sets DAGScheduler reference in DAGScheduler's
    529)    // constructor
    530)    _taskScheduler.start()
    


    //TaskSchedulerImpl.scala
    override def start() {
       //启动SparkDeploySchedulerBackend 
    	 backend.start()
    
        if (!isLocal && conf.getBoolean("spark.speculation", false)) {
          logInfo("Starting speculative execution thread")
          speculationScheduler.scheduleAtFixedRate(new Runnable {
            override def run(): Unit = Utils.tryOrStopSparkContext(sc) {
              checkSpeculatableTasks()
            }
          }, SPECULATION_INTERVAL_MS, SPECULATION_INTERVAL_MS, TimeUnit.MILLISECONDS)
        }
      }
    


    6.启动Executor

    override def start() {
        super.start()
        launcherBackend.connect()
    
        // The endpoint for executors to talk to us
        val driverUrl = rpcEnv.uriOf(SparkEnv.driverActorSystemName,
          RpcAddress(sc.conf.get("spark.driver.host"), sc.conf.get("spark.driver.port").toInt),
          CoarseGrainedSchedulerBackend.ENDPOINT_NAME)
        val args = Seq(
          "--driver-url", driverUrl,
          "--executor-id", "{{EXECUTOR_ID}}",
          "--hostname", "{{HOSTNAME}}",
          "--cores", "{{CORES}}",
          "--app-id", "{{APP_ID}}",
          "--worker-url", "{{WORKER_URL}}")
        val extraJavaOpts = sc.conf.getOption("spark.executor.extraJavaOptions")
          .map(Utils.splitCommandString).getOrElse(Seq.empty)
        val classPathEntries = sc.conf.getOption("spark.executor.extraClassPath")
          .map(_.split(java.io.File.pathSeparator).toSeq).getOrElse(Nil)
        val libraryPathEntries = sc.conf.getOption("spark.executor.extraLibraryPath")
    // Start executors with a few necessary configs for registering with the scheduler
        val sparkJavaOpts = Utils.sparkJavaOpts(conf, SparkConf.isExecutorStartupConf)
        val javaOpts = sparkJavaOpts ++ extraJavaOpts
    	//定义分配资源的进程名称
        val command = Command("org.apache.spark.executor.CoarseGrainedExecutorBackend",
          args, sc.executorEnvs, classPathEntries ++ testingClassPath, libraryPathEntries, javaOpts)
          .map(_.split(java.io.File.pathSeparator).toSeq).getOrElse(Nil)
    	//省略部分代码,详细内容参见前几节,Executor注册过程。
        client = new AppClient(sc.env.rpcEnv, masters, appDesc, this, conf)
    	//注册应用程序
        client.start()
        launcherBackend.setState(SparkAppHandle.State.SUBMITTED)
        waitForRegistration()
        launcherBackend.setState(SparkAppHandle.State.RUNNING)
      }
    
    在client#start方法中最终会注册应用程序。

    二、总结

    在SparkContext实例化的时候调用createTaskScheduler来创建TaskSchedulerImpl和SparkDeploySchedulerBackend,同时在SparkContext实例化的时候会调用TaskSchedulerImpl#start方法,在该方法中会调用SparkDeploySchedulerBackend#start;在这个start方法中会创建AppClient对象并调用AppClient#start方法,这时会创建ClientEndpoint,在创建ClientEndpoint时会传入来指定具体为当前应用程序启动的Executor进程的入口类的名称为CoarseGrainedExecutorBackend,然后ClientEndpoint启动并通过tryRegisterMaster来注册当前的应用程序到Master中,Master接收到注册信息后,如果可以运行程序,则会为该程序生成JobID并通过Schedule来分配计算资源,具体计算资源分配是通过应用程序运行方式、Memory、core等配置信息来决定的,最后Master会发送指令给Worker;Worker中为当前应用程序分配计算资源时,首先分配ExecutorRunner,ExecutorRunner内部会通过Thread的方式构建ProcessBuilder来启动另一个JVM进程,这个JVM进程启动时候会加载的main方法所在的类的名称就是创建ClientEndpoint传入的Command指定的入口类CoarseGrainedExecutorBackend,此时JVM在通过ProcessBuilder启动的时候获得了CoarseGrainedExecutorBackend后,加载并调用其中的main方法。在main方法中会实例化CoarseGrainedExecutorBackend本身这个消息循环体,而其实例化时会通过回调onStart向DriverEndpoint发送RegisterExecutor来注册当前的CoarseGrainedExecutorBackend,此时DriverEndpoint收到该注册信息并保存在了SparkDeployScheduler实例的内存数据结构中,这样Driver就获得了计算资源!


    Task运行各个阶段交互过程





                                                                       图35-6 资源分配过程





    备注:有关FIFO、FAIR调度算法解析部分参考 张安站 --Spark技术内幕一书


    -----------------------------------------EOF---------------------------------------------------------------------------------------------------------



    展开全文
  • 另外,土壤干旱意味着更多的负土壤水势和较低的土壤导水率,通常会导致在叶片和大气层之间更高的蒸汽压梯度,造成植物水分循环系统的胁迫在高温下进一步加剧。结果,木质部的高压触发了茎中水分运输的栓塞和部分失效...

    2012年真是极其艰难的一年!根据玛雅历法所推算的世界末日让全球忧心忡忡,但最后被证实是杞人忧天。但是这一年,全球的日子的确不好过:美国的干旱和热浪,英国、肯尼亚、索马里、日本和澳大利亚非同寻常的降雨记录,西班牙的干旱,中国的洪水,当然还有超级风暴桑迪。气候变化的问题,近10年来,不管是在科学界还是坊间,甚至尤其是政府首脑,都将这个问题看做一个非常重要的问题。科学家一直在提醒,气候变化与灾难性极端气候是紧密相连的。所以这一年如此多的极端气象事件,很容易使人们再次联想这个问题:这样的极端天气,是由于气候变化影响的吗?在此背景下,一个被称为“气候归因科学”(climate attribution science)的研究正在萌芽,主要研究极端事件的效应,确定有多少是人类活动导致的气候变化所引起的,有多少是自然的变化,诸如厄尔尼诺/拉尼娜-南方涛动的气候模式、海表温度、入射太阳辐射的变化,或者许多其他可能的因素[1]

    最近发布的《美国气象学会通报》[2],对全球10多个极端事件所做的20个相关科学研究进行了分析,试图寻找人为导致的气候变化的相对影响有多大。这些证据令人信服地表明,人类引起的变化是一个重要的因素,约占一半。在许多情况下,人类对于气候的影响增加了相关极端事件的风险。本周的科学新闻(Science News)[1]也对此做了报道,并列出了一些事件及其相应的研究结论:

    (1) 2011年12月:新西兰南岛两天的极端降雨所产生的滑坡是500年一遇的事件。结论:这次极端事件中所提供的总水分因为人为温室气体的排放增加了1-5%。

    (2) 2012年9月:北极海冰达到创纪录的新低340万平方公里。研究调查了三个不同的因素:比正常地表大气温暖的条件,与全球变暖有关;海冰在融化季节到来之前就变薄,也与全球变暖有关;以及8月肆掠北极的风暴,搅动大洋,使海冰破裂被送到温暖的南部。结论:全球变暖是主因,温暖大气条件和海冰变薄在一定程度起作用,但它们本身也是全球变暖所导致的结果。

    (3)2012年夏天:澳大利亚东部的暴雨。结论:2012年的拉尼娜事件(很长时间处于比正常年份更湿润的条件)对强降雨有非常大的贡献,但不是全部。因全球变暖导致澳大利亚北部海表温度的变化也可能起了作用,使得未来降雨量有增加5%的几率。

    (4)超级风暴桑迪:尽管这不是在历史上袭击美国东海岸的最强大暴风雨,但这场暴风雨的实际影响却并非来自暴风雨本身,而是暴风雨之后的潮水上涌和淹水,这倒是打破了这个海岸的历史记录。结论:暴风雨恰逢纽约港的大潮。而海平面上升可能会加剧未来的这种泛滥,即使暴风雨本身可能并没有那么严重,但却更容易发生类似桑迪水平的极端事件。

    关于极端气候的话题,最近真的是很热。半个月前,Nature也有篇综述文章“极端气候事件与碳循环”[3],专门介绍了极端气候对地球陆地碳循环的影响,下面对此做一简要介绍。

    在过去50年里,地球上人为排放的CO2约有25-30%被生态系统所吸收,其中大部分CO2被吸收后积累在森林生物量和土壤中。人类其他的影响还包括生态系统中氮肥的增加,大气中CO2浓度的增加和氮施肥导致植被生长增强,还逐步增加了北半球的生长季长度。因此,曾经我们认为,这些持续的环境变化可能增加了全球陆地碳吸收,响应也减轻了大气中CO2水平的人为增加,并在气候/碳循环系统中提供了一个负反馈。究竟在何种程度上,持续多长时间,以及在何种生态系统中,这个CO2吸收和负反馈将一直继续?越来越多的证据却表明,极端事件(如热浪、干旱或风暴)的发生及其相关干扰,可能会部分抵消掉这些碳汇,甚至导致碳库的净损失,将生态系统储存的CO2释放到大气中。因为极端事件首先可触发生态系统即时的反应,之后还有一些后续反应。例如,树木死亡、火灾或虫害,它们对碳通量和碳库的影响是非线性的。因此,极端气候即使发生在很小的频率,或严重程度的改变也并不大,仍可能大幅度减少碳汇并可能对气候变暖产生相当大的正反馈作用。由负反馈效应转变为正反馈效应,这将是一个质的转变。

    气候学和水文学研究气候和极端天气事件有着悠久的历史,一般普遍采用应用统计学框架来定义极端气候。现在的问题是,这种仅仅基于气候统计对极端事件的定义,是否适合评估对生态系统及其碳循环的影响?例如,考虑这个问题:如果在过去100年某地的年降雨量总是介于500mm和510mm之间(虽然也有相当大的季节性变化), 那么当某年的降水量为499mm,尽管实际上只有1-11mm的不同,或者说在生态学上只产生了0.2-2%微不足道的影响,那么在统计学上也可被看做是极端事件了。同样,在现实世界中,如果在某高纬度地区冬季的月气温一般在30°C和40°C之间,那么根据气候学的定义,某月25°C也是极端事件,但这远低于任何可能导致生态系统发生响应的关键阈值,也就是说,这样的变化可能并没有带来实际灾难性的影响。

    因此,过去对气候极端事件的定义可能在类似的研究中是不合适的,应该给一个特定的定义,考虑生态系统预期响应的极端性,而不仅仅考虑气象本身的驱动力。根据这样一个原则,将极端气候事件定义为:发生的一段情节或一个事件,其中统计学上罕见或不寻常的气候来临改变了生态系统结构或功能,使其范围在典型或正常的变化之外。这个定义包含了一系列气象指标,也许对单一变量来说并非极端,但对变量组合来说则是极端的(多元极端或复合事件),比如热浪和干旱的组合,或者干旱之后的极端降雨事件。同时,为了进一步强调了影响,可将这个定义重新描述为与生物圈有关的极端气候:“在规定时间和空间内,生态系统的功能(如碳吸收)高于或低于一个确定的极端百分数所出现的条件,可通过单一或多元的异常气象变量进行描述”。根据这个定义,鉴定和检测极端气候事件问题首次聚焦到对生态系统的诊断上,这就需要将极端生态系统对气象变量的即时和滞后效应进行归因,这显然是更为合理的评价方法。

    有证据表明,极端事件不仅并发影响碳循环(例如,在火灾事件中减少植被生产力或破坏碳库),同时还会带动滞后反应。例如,一个异常温暖季之后的一年,草原中土壤的异养呼吸增强,抵消了生态系统碳的净吸收;土壤冻结增加了森林中异养呼吸应对夏季干旱的敏感性;在许多案例中,严重干旱会导致树木死亡率的增加。有关极端事件对生态系统碳循环的滞后和遗留效应,我们还不太了解,可能会涉及到不同组织水平和时间尺度上的多个协同和拮抗机制,为此提出了层次响应框架。这些机制包括:(1) 减少植物对非生物胁迫(例如,通过抗氧化剂,渗透性或膜稳定性的改变)的抗性、害虫和病原体(例如,通过改变次生代谢产物),以及它们对植物性能的影响;(2) 改变凋落物和根际沉积的数量、质量和时间;(3) 对土壤物理和化学特性(土壤有机质组分、团聚体稳定性、疏水性)的影响;以及(4) 植物、微生物和动物物种组成的变化(例如,真菌数目增加,因为真菌比细菌更抗旱),以及碳和氮循环的相关变化,这会反馈于(1)-(3)。

    除了上述在生态系统水平的机制外,在社会和经济系统中同样存在滞后效应。例如,如果低产量提高了食品的价格,就会鼓励更多人将森林转变为农田或草原。

    极端气候可诱导一系列相互关联的影响,所有这些影响有可能在不同时间尺度深刻地改变生态系统的碳平衡。下图采用热浪和干旱的效应对此进行了说明:这种类型的极端气候对CO2的通量有直接影响,因为光合作用和呼吸作用都会对更温暖的温度和土壤水分限制发生反应。此外,这些因素在叶片、生态系统和区域尺度是协同工作的。干旱导致植物气孔关闭,减少叶片蒸腾作用和蒸发冷却作用,加重了高气温(和强劲短波辐射)的影响。同样,在区域尺度上,土壤水分-温度反馈更可能导致在干燥土壤条件的热浪。




    另外,土壤干旱意味着更多的负土壤水势和较低的土壤导水率,通常会导致在叶片和大气层之间更高的蒸汽压梯度,造成植物水分循环系统的胁迫在高温下进一步加剧。结果,木质部的高压触发了茎中水分运输的栓塞和部分失效,甚至是一个致死的促成因素。这种机制,连同与碳水化合物代谢和虫害相关的机制,目前被认为是树木在干旱条件下死亡的一个主因。在这方面,植物死亡可能是炎热和干旱的一个滞后效应,至少在几十年里都会影响碳平衡,在当地和区域水文与气候的反馈作用下,导致植被覆盖的变化。干旱胁迫的进一步影响包括火灾风险的增加,病原体和害虫爆发的延迟反应,后者也与生态系统应对暴风雨的灵敏度以及森林中与风灾死亡有关。

    虽然这种由不同极端气候引发的机制,可在概念上进行描述,但是它们具体的影响是高度依赖于生态系统类型的,这总结在下表中。

     

    土地覆

    盖类型

    极端事件

    关键影响机制

    高度敏感地区的记录

    对未来发生

    的科学认识

    碳循环影响

    的科学认识

    森林

    暴风

    l   风倒事件将碳库从活生物量转换为干的死木

    l   风倒事件增加了增加火灾和病原体暴发的风险

    亚马逊流域、北美、中欧

     

     

     

    干旱炎热

    l   水的有效性会影响植物生理、物候和碳分配模式

    l   增加树木死亡、火灾风险和病原体的易感性

    l   植被组成发生变化(影响大,长寿命的树木会延迟这种影响)

    中欧、北美西部、亚马逊流域

    低到中

    中到高

     

    火灾

    l   树木死亡对森林中巨大碳库有巨大而快速的影响

    北美西部、东南亚、地中海、环寒带林区、亚马逊流域

     

     

     

    冰暴和霜冻

     

    l   物理伤害可以包括对整个森林的破坏

    l   木质部栓塞和干燥

    中国、北美

     

    中到高(对低温来说)

     

    草原

     

    干旱炎热

    l   物种组成变化(尤其是加上额外的压力如过度放牧)

    l   退化和沙漠化(特别是加上过度放牧)

    l   侵蚀(加上强降雨或风暴)

    北美、欧洲、中亚

     

    低到中

    中到高

     

    农田

     

    风暴

     

    l   风力侵蚀和对碳循环后果不清楚的土壤位移

    l   直接损害作物

    中国、北美

     

     

    强降雨

    (包括冰雹)

     

    l   造成土壤损失和位移的侵蚀,以及因此对碳的影响

    l   影响土壤长期生产能力的侵蚀

    l   农作物损坏,或冰雹和土壤淹水及后续的厌氧条件产生的问题

    l   作物倒伏,即谷类作物的茎被垂向茎永久取代

    l   增加害虫和病原体

    热带地区、北美、澳大利亚、地中海、西欧、东亚

    中到高

    (对冰雹来说是低)

     

    干旱和炎热

    l   降低生长或整个作物歉收

    欧洲、北美、中国

    低到中

     

    极端寒冷

    l   降低生长

    l   整个冬季作物歉收,尤其是春季的霜冻(结合干旱胁迫)

    北美、南澳大利亚、欧洲

    中到高


    展开全文
  • 循环神经网络教程-第一部分 RNN介绍

    千次阅读 2016-12-29 15:11:02
    循环神经网络教程-第一部分 RNN介绍循环神经网络(RNNs)是非常流行的模型,它在许多NLP任务上都表现出了巨大的潜力。虽然它很流行,但是详细介绍RNNs以及如何实现RNNs的文章却很少。这篇教程旨在解决上述问题,教程...

    循环神经网络教程-第一部分 RNN介绍

    循环神经网络(RNN)是非常流行的模型,它在许多NLP任务上都表现出了巨大的潜力。虽然它很流行,但是详细介绍RNN以及如何实现RNN的文章却很少。这篇教程旨在解决上述问题,教程分为4部分:
    1. 介绍RNN(这篇教程)
    2. 用Tensorflow实现RNN
    3. 理解BPTT后向传播算法以及梯度消失/爆炸问题
    4. 实现GRU/LSTM
    作为教程的一部分,我们将会实现一个基于RNN的语言模型。语言模型的应用有两个方面:首先,它允许我们根据句子在现实世界出现的可能性对任意的句子进行打分,这给了我们一种衡量句子语法和语义正确性的方法(也就是说看一句话是不是正常人说的,句子越正常,分数就越高)。这种模型通常是机器翻译系统的一部分。第二,我们可以用语言模型生成文本(我认为这是更加酷炫的应用)。在莎士比亚的著作上训练语言模型可以生成莎士比亚风格的文章。Andrej Karpathy的这篇有趣的博客演示了可以用基于RNN的字符级的语言模型做些什么。

    如果你对基本的神经网络不熟悉的话,你可能想从头开始实施一个神经网络 ,这篇博客会介绍基本神经网络的概念以及具体实施。

    WHAT IS RNN?

    RNN的想法是利用序列信息。在传统的神经网络中,我们假设输入(和输出)
    是相互独立的,但是对于很多任务来说,这种方法很不好。如果你想要在一个句子中预测下一个词,那么你最好知道这个词的前一个词是什么。RNN称为“复现”,因为它对句子中的每一个元素都执行相同的任务,其输出依赖于前面的计算。另一种思考RNN的方式是认为它有“记忆”的功能,可以捕捉先前已经计算过的信息。从理论上来讲,RNN可以利用任意长度序列中的信息,但是在实际上在它们仅限于回顾几步(在后面做详细介绍)。典型的RNN是这个样子:
    一个循环神经网络以及涉及到前向计算中的计算时间的展开.

    上图展示了展开到完整网络的RNN。展开只是意味着我们为完整的序列写出了网络。例如,如果我们关心的是有5个词的句子,网络将会展开成一个5层的网络,每一个词对应着一个网络。RNN中的计算公式如下:

    • xttx1onehot
    • stt""st 是根据上一时刻的隐层状态和当前的输入计算的: st=f(Uxt+Wst1)。函数f通常是非线性,例如tanhReLUs10
    • ot 是t时刻的输出。例如,如果我们想预测句子中的下一个单词,那么它将会是一个概率向量,向量中的每一个值代表着词表中对应单词的概率。 ot=softmax(Vst)

    需要注意以下几点:

    • 我们可以认为st 是网络的记忆单元,st 捕捉前面所有时刻发生的信息。输出ot 仅根据时刻t的记忆进行计算。正如上面简要提到的那样,在实践中稍微有点复杂,因为st 通常情况下不能捕捉很久之前时刻的信息。
    • 和传统的深度神经网络在每一层都使用不同的参数不同的是,RNN在的所有的步骤中都使用同样的参数(U,V,W)。这反应了我们在每一步都用不同的输入执行同样的任务这一事实。这极大地减少了我们需要学习的参数的数量。
    • 上图在每一步都有输出,但是根据任务来说这不是必须的。例如,当预测一个句子的情感的时候,我们只关心最终的输出,而不是每一个单词的情感。同样地,我们也不需要在每一步都有输入。RNN最大的特征就是它的隐藏状态,其捕捉了句子的某些信息。

    WHAT CAN RNNS DO?

    RNN在许多NLP任务上都取得了巨大的成功。在这一点上我要说明的是大多数用RNN的类型都是LSTM,LSTM可以比原始的RNN更好地捕捉长期依赖。但是不要担心,LSTM本质上和RNN一样,只不过是在隐藏层的计算方式不一样,我们会在后面的教程里讲解。下面是RNN在NLP上面的一些应用(不包含全部)

    构建语言模型&生成文本

    给定一个词语序列我们想要预测给定前一个词时每一个词的概率。语言模型可以让我们评价一个句子的可能性,这对于机器翻译来说是很重要的(因为概率越高的句子越正确).预测下一个词的副作用就是我们可以得到一个生成模型,这使得我们通过输出概率中采样生成新的文本。基于我们的训练数据,我们可以生成各种各样的东西。在语言建模中,我们的输入是一个词语序列(例如被编码为one-hot向量),我们的输出是预测的词语序列。当训练网络是,我们设置ot=xt+1, 因为我们想让t 时刻的输出就是下一个单词。

    关于语言模型与生成文本的相关论文:

    机器翻译

    机器翻译和语言模模型的相似之处在于输入都是一个源语言(e.g.德语)的词语序列,我们想要的输出是目标语言(e.g.英语)的词语序列。关键的不同在于机器翻译的输出要在完成整个输入之后才开始生成,因为翻译句子的第一个单词可能需要整个输入序列的信息。

    RNN for Machine Translation.

    关于机器翻译的论文:

    语言识别

    给定来自声波的声学信号的输入序列,我们可以预测语音段的序列及其概率。

    语言识别的论文

    生成图像描述

    和卷积神经网络一起,RNN被用来作模型的一部分以生成对未标注的图像的描述。这项工作是相当惊人的。组合模型甚至可以将生产的词语图像中找到的特征对齐。
    用于生成图像描述的深视觉语义对齐

    训练RNN

    训练RNN类似于训练传统的神经网络,我们也利用后向传播算法,但是有一点不同。因为网络中每一步的参数都相同,每一个输出的梯度不仅依赖于当前的时刻,也依赖于前面是时刻。例如,为了计算在 t=4时刻的梯度,我们需要反向传播前3个时刻并将梯度求和,这就是所谓的BPTT,不要担心,后面的博客对此会有详细的介绍。现在我们只需要知道一般的用BPTT训练的RNN会有长期依赖的困难(e.g. 相距很远的时刻之间的依赖性)。现在存在一些机制解决这些问题,某些RNN类型(比如说LSTM)就是专门用来解决这个问题的。

    RNN 扩展

    多年来,研究人员开发了更复杂的RNN类型来处理vanilla RNN模型的一些缺点。我们将在后面的文章中更详细地介绍这些内容,本部分作为简要概述,以便您熟悉模型的分类。

    双向RNN是基于这样的想法,在t时刻的输入也许不仅仅依赖于序列中的前一个元素,可能也依赖于后面的元素。例如:为了预测序列中的一个缺失词,你也许想要查看左边和右边的上下文。双向RNN非常简单,它们只是堆叠在彼此顶部的两个RNN,然后基于两个RNN的隐藏状态计算输出。
    双向RNN

    深度(双向)RNN和双向RNN类似,只是现在在每一个时刻都有了多层。实践当中这给了我们更高的学习能力(但同时我们也需要很多训练数据)。
    深度RNN

    LSTM 网络如今非常流行,上面我们已经简要介绍过LSTM,它和RNN没有本质的区别,但是它们两个用了不同的函数计算隐层的状态。LSTM中的记忆被称作细胞,你也可以吧它们当做黑匣子,它们用上一个时刻的状态ht1 和当前的输入xt 作为输入。内部的这些单元决定从记忆中记住什么(遗忘什么)。然后它们组成了先前的状态,当前的记忆以及输入。事实证明这些类型的单元在捕捉长期依赖当中非常有效。

    总结

    我希望你对RNN是什么以及他们能做什么有一个基本的了解。在下一篇文章中,我们将使用Python和Theano实现我们的语言模型RNN的第一个版本。

    展开全文
  • 前文介绍了OllyDbg和Cheat Engine工具逆向分析用法,完成植物大战僵尸的游戏辅助器,包括修改阳光值和自动拾取阳光个功能。这篇文章将带领大家来学习科锐钱林松老师的视频,详细讲解条件语句和循环语句源码还原及...

    您可能之前看到过我写的类似文章,为什么还要重复撰写呢?只是想更好地帮助初学者了解病毒逆向分析和系统安全,更加成体系且不破坏之前的系列。因此,我重新开设了这个专栏,准备系统整理和深入学习系统安全、逆向分析和恶意代码检测,“系统安全”系列文章会更加聚焦,更加系统,更加深入,也是作者的慢慢成长史。换专业确实挺难的,逆向分析也是块硬骨头,但我也试试,看看自己未来四年究竟能将它学到什么程度,漫漫长征路,偏向虎山行。享受过程,一起加油~

    系统安全系列作者将深入研究恶意样本分析、逆向分析、攻防实战和Windows漏洞利用等,通过在线笔记和实践操作的形式分享与博友们学习,希望能与您一起进步。前文介绍了OllyDbg和Cheat Engine工具逆向分析用法,完成植物大战僵尸的游戏辅助器,包括修改阳光值和自动拾取阳光两个功能。这篇文章将带领大家来学习科锐钱林松老师的视频,详细讲解条件语句和循环语句源码还原及流程控制逆向。希望对入门的同学有帮助。

    话不多说,让我们开始新的征程吧!您的点赞、评论、收藏将是对我最大的支持,感恩安全路上一路前行,如果有写得不好的地方,可以联系我修改。基础性文章,希望对您有所帮助,作者的目的是与安全人共同进步,加油~

    作者的github资源:

    前文分析:


    声明:本人坚决反对利用教学方法进行犯罪的行为,一切犯罪行为必将受到严惩,绿色网络需要我们共同维护,更推荐大家了解它们背后的原理,更好地进行防护。


    一.C++逆向条件结构基础入门

    大家写过相关的算法吗?
    加密代码中会涉及循环和分支,你要识别算法,首先就是需要将它的算法处理流程识别出来。当我们还原出等价的高级代码之后,就没有逆向分析人员的事情了,因为接下来涉及到密码学、数学相关人员的工作,逆向人员把加密的代码还原出来后就应该扔给研究密码学的数学家,他们负责玩数学对抗,而逆向关注的是编译原理和代码还原。同时,逆向还涵盖了识别对象、识别算法、识别优化、识别虚函数对象的继承关系等等,这里主要结合项目相关的加密和解密进行讲解。接着作者准备穿插着VC++6.0和VS2019两个版本进行讲解。


    1.单分支结构分析

    第一步,通过VC++6.0编写一个最简单的程序,创建工程名称为“RE_SF”。

    在这里插入图片描述
    在这里插入图片描述

    运行结果如下图所示,可以看到“Hello World”。

    在这里插入图片描述


    第二步,编写单分支结构的相关代码。

    #include "stdafx.h"
    
    int main(int argc, char* argv[])
    {
    	if (argc > 8 )
    	{
    		printf("argc > 8\r\n");
    	}
    	return 0;
    }
    

    接着选择“Win32 Release”编译运行代码。

    在这里插入图片描述


    第三步,接着用OD软件打开EXE文件。
    此时创建的工程目录分布如下图所示。

    在这里插入图片描述

    OllyDbg打开之后显示如下图所示的界面,程序入口地址是0x00401051。

    • 程序入口:0x00401051

    在这里插入图片描述


    第四步,首先我们需要定位main函数,其方法非常简单,找到有三个PUSH的下面就是main函数,因为main函数有三个参数(argc、argv[],、envp[])。接着按F2下断点。

    • 主函数:0x00401100

    在这里插入图片描述

    继续按下F7跟进,会看到一个单分支结构。那么,单分支结构它有什么特点呢?

    • 进入主函数:0x00401000

    在这里插入图片描述


    第五步,先给大家普及单分支语句的代码定式基础知识。
    在高级语言中单分支代码定式如下:

    //程序语言
    if(...)
    {
        ...
    }
    
    //代码定式
    IF_BEGIN:
        jxx IF_END
            ...
        IF_END:
            ...
    

    为什么需要记住这个代码定式呢?
    因为对于流程控制的识别,我们关键是要找到IF语句的作用域(上界和下界),上界在jxx的位置,称之为IF_BEGIN。接着有个jxx条件跳转,跳转到目标且没有其他的特征,这种就称之为单分支的代码定式。

    回到我们的汇编代码,拿到这个代码之后,发现存在一个箭头指向跳转目标,这样就出现了IF模块的上界和下界,条件判断作为IF的上界,条件跳转的目标作为IF下界,通过这种套路方式来还原代码。

    在这里插入图片描述


    第六步,分析嵌套的单分支语句。
    假设我们的判断中再嵌套一层或增加一个分支,又该怎么判断呢?对于我们还原代码的人来说,不用管它,你把上下界圈出来,然后递归解决。所有流程问题只要找到上下界,剩下的问题就变成了顺序结构,再看着代码一条条还原即可。

    在这里插入图片描述

    #include "stdafx.h"
    #include <stdlib.h>
    
    int main(int argc, char* argv[])
    {
    	if (argc > 8 )
    	{
    		printf("argc > 8\r\n");
    		if (argc < 80)
    		{
    			printf("argc < 80\r\n");
    		}
    	}
    
    	system("pause");
    	return 0;
    }
    

    接着运行程序生成新的EXE程序,然后通过OD软件打开分析,给主函数下个断点然后进入主函数显示如下图所示的界面。

    • 主函数内容:0x00401000

    在这里插入图片描述


    第七步,同样的方法将两层单分支语句的上下界圈出来。
    我们发现这两个判断的下界重合了,都是跳转到0x00401029位置,这就明显是个嵌套。

    在这里插入图片描述

    总结下IF语句的特点:

    • 观察它的条件跳(上下界)
    • 条件跳的目标上面的代码没有其他特征,即“ADD ESP, 4"

    那么,怎么还原出高级代码呢?


    第八步,通过汇编代码还原出高级代码。
    还原代码需要进行反条节操作,并且学会查询相关指令。比如JLE、JGE是什么意思呢?

    • JLE(jump if less or equal,or not greater):汇编语言中的条件转移指令,小于或等于则条件转移
    • JGE:大于或等于转移指令

    注意,在还原的时候需要做反条件操作。那么,什么叫反条件呢?具体解释如下:

    • JLE:小于等于跳转 --> 代码还原就是“不小于等于”,即:大于跳转
    • JGE:大于等于跳转 --> 代码还原就是“不大于等于”,即:小于跳转

    反条件
    因为当我们满足这个条件的时候它会跳转到另一个地方(结束地方),它没有执行具体的代码;所以如果我们想要执行模块中的代码,就需要反条件处理。即汇编的语义和高级语言的语义是反的,高级语言的语义是满足条件则执行语句块,而汇编的语义是满足条件不执行语句块。

    接着我们继续看触发跳转的代码,它是通过CMP比较来触发的。

    • CMP ESI, 8
      ESI是通过参数传递过来的,然后和8进行不小于等于的比较
    • CMP ESI, 50
      ESI和50进行不大于等于的比较

    在这里插入图片描述

    此时,我们再将单分支步骤简单归纳如下:

    • (1) 通过反汇编代码序列,匹配代码定式;
    • (2) 如果是单分支if结果,则将条件转义指令jxx做反条件还原

    第九步,接着我们换个工具用VS2019打开我们的代码,生成新的Release版本。

    在这里插入图片描述

    然后删除本地的Release资源,生成一个新的Release方案。

    在这里插入图片描述


    第十步,用IDA Pro打开可执行EXE程序进行分析。
    同样,使用IDA也是可以进行逆向分析的,打开新生成的逆向分析工具如下所示。

    在这里插入图片描述

    右键选择“Text View”查看源代码。

    在这里插入图片描述

    找到main函数,然后点击“_main”位置高亮显示。

    在这里插入图片描述

    按下“N”键可以对函数进行重命名,如下图所示。

    在这里插入图片描述

    注意,前面分享的识别方法和编译器版本、编程语言(C++、VB)等都没有关系,它是编译原理的问题。接着我们重点还是回归到代码上去,点击“loc_401039”函数高亮,同样的方法划分出这个单分支的上界和下界,并且嵌套了一个单分支,最终还原出源代码。

    在这里插入图片描述

    所以,不论使用VC++6.0或VS编译工具,还是使用IDA或OD分析工具,它们还原代码的原理及方法都是一样的。在实际项目中,不论你用什么分析工具,最终能分析出结果就好。



    2.双分支结构分析

    第一步,编写双分支代码。

    在这里插入图片描述


    第二步,普及双分支语句的代码定式基础知识。
    在高级语言中双分支代码定式如下。该代码序列关键是发现jxx后,需要检查目标看看下面有没有一个jmp指令,如果有个jmp且是往下跳的,if-else就成立了。

    //程序语言
    if(...) 
    {
        ...
    }
    else 
    {
    	...
    }
    
    //代码定式
    IF_BEGIN:
    jxx ELSE_BEGIN
        ...
    IF_END:
        jmp ELSE_END
    ELSE_BEGIN:
        ...
    ELSE_END:
        ...
    

    第三步,接着生成新的exe文件,用OD打开分析。
    同样的方法进入主函数,然后F7单步步入0x00401000位置,如下图所示。

    在这里插入图片描述

    核心代码及其跳转如下图所示。

    • JLE --> 0x0040100E:PUSH操作
    • JMP --> 0x00401013:CALL操作

    在这里插入图片描述

    双分支结构特点:

    • jxx的目标处上一行指令为jmp,而且是往高地址去的jmp(往下跳)。如果是循环,后面会讲到它可能往上跳。

    确定上下界之后,生成如下图所示的if模块和else模块,同样的反条件处理还原代码。

    在这里插入图片描述

    注意,这里有一个小小的优化,编译原理中的代码外提。它是什么意思呢?
    假设有个节点A,现在有了流程分支B1和B2,B1完成后执行C,B2完成后也会执行C。编译器为了减小代码的节点,因为代码节点越多,代码越长,就做了等价流程的代码外提优化,从而汇总到C,少了一个节点。

    • 编译器会视情况减少节点的优化
    • 编译器也会增加节点来减小路径的优化

    在这里插入图片描述


    第四步,采用同样的方法用IDA工具分析还原代码,其效果也一样。

    在这里插入图片描述

    在这里插入图片描述

    接着你可能会疑问这两个PUSH是干啥呢?**

    • push offset aArgc8 ; “argc > 8\r\n”
    • push offset aArgc8_0 ; “argc <= 8\r\n”

    C语言中没有标准的高级语法对应汇编中的PUSH操作,说明它有代码优化了,就是代码外提操作。它们有个公共的函数调用被提到下面去了,就是下图所示的两行代码,这个时候我们要把它放回去方便还原。

    在这里插入图片描述

    接着我们复制汇编代码至C语言中进行还原,方便大家理解。代码如下,此时增加了if和else的上下界,但发现两个push无法还原。

    在这里插入图片描述

    同时将代码外提部分分别放到if和else模块中,就能实现最终代码还原,如下图所示。最后删除掉多余的汇编注释即可。

    在这里插入图片描述

    继续还原条件判断内容,JLE小于等于换成大于8就好。在真实环境中,还会遇到双分支中有循环或条件嵌套的问题,不要担心,找到上下界继续分析即可。

    #include "stdafx.h"
    #include <stdlib.h>
    
    int main(int argc, char* argv[])
    {
    
    //.text:00401000                 cmp     [esp+argc], 8
    //.text:00401005                 jle     short loc_40100E
    	if (argc > 8)
    	{
    //.text:00401007                 push    offset aArgc8   ; "argc > 8\r\n"
    //.text:0040100C                 jmp     short loc_401013
    //.text:00401013                 call    sub_4010C6
    		printf("argc > 8\r\n");
    //.text:00401018                 add     esp, 4
    	}
    //.text:0040100E ; ---------------------------------------------------------------------------
    //.text:0040100E
    	else
    	{
    //.text:0040100E loc_40100E:                             ; CODE XREF: _main+5↑j
    //.text:0040100E                 push    offset aArgc8_0 ; "argc <= 8\r\n"
    //.text:00401013                 call    sub_4010C6
    		printf("argc <= 8\r\n");
    //.text:00401018                 add     esp, 4
    	}
    //.text:00401013
    //.text:00401013 loc_401013:                             ; CODE XREF: _main+C↑j
    //.text:0040101B                 push    offset aPause   ; "pause"
    	system("pause");
    	return 0;
    }
    


    二.C++逆向循环结构基础入门

    1.do-while结构分析

    循环包括do-while、while和for三种,你会觉得哪一种消息最高呢?do-while是三种循环中效率最高的,由于其无条件先执行一次,所以大家很少使用,但其效率很高。

    基本语法
    先执行,再判断。先执行一遍循环操作,若符合条件,循环操作继续执行,否则退出循环。

    do{
        循环操作语句;
    }whlie(循环条件);
    

    在这里插入图片描述

    第一步,我们编写一个1加到100的循环代码,这次直接使用Debug版本。

    #include "stdafx.h"
    #include <stdlib.h>
    
    int main(int argc, char* argv[])
    {
    	int n = 1;
    	int nSum = 0;
    
    	//do-while 执行一次
    	do {
    		nSum = nSum + n;
    		n++;
    	} while(n <= 100);
    	
    	printf("%d", nSum);
    	system("pause");
    	return 0;
    }
    

    第二步,通过OD打开运行的EXE程序“RE_XH.exe”。

    • 程序入口地址:0x00401260

    在这里插入图片描述


    第三步,往下查找代码,发现3个PUSH后(参数)就是主函数,然后F2添加断点并F7步入主函数。

    • 主函数:CALL RE_XH.00401005

    在这里插入图片描述

    在这里插入图片描述


    第四步,分析汇编代码。
    这里存在一个JLE跳转,如果条件跳往上跳就是do-while循环。

    在这里插入图片描述

    循环肯定会往上走,否则构成不了循环,它需要反复执行同一代码段。如果跳转的目标没有检查条件,就是do-while循环。简单总结下识别do-while循环步骤:

    • 识别代码定式
    • 如果是do循环,则按jxx同条件还原等价高级代码

    注意,同条件的就只有do-while结构。在do-while循环中,它跟汇编的语义是一样的,只有当条件满足则流程更新到循环的起始地点,所以它是正条件还原。而前面的if-else判断都是反条件

    //程序语言
    do
    {
        ...
    }while(xxx);
    
    //代码定式
    DO_BEGIN:
        ...
        jxx DO_BEGIN
    DO_END:
        ...
    


    2.while结构分析

    基本语法
    先判断,再执行。

    whlie(循环条件){
        循环操作语句;
    }
    

    在这里插入图片描述

    第一步,我们编写一个1加到100的循环代码。

    #include "stdafx.h"
    #include <stdlib.h>
    
    int main(int argc, char* argv[])
    {
    	int n = 1;
    	int nSum = 0;
    
    	while (n <= 100)
    	{
    		nSum = nSum + n;
    		n++;
    	} 
    	
    	printf("%d", nSum);
    	system("pause");
    	return 0;
    }
    

    第二步,分析while循环的代码定式。
    注意,该循环的Debug版本和Release版本存在差异,接下来会对比分析。我们先给出代码定式,如下所示。

    //程序语言
    while(xxx)
    {
        ...
    }
    
    //代码定式
    WHILE_BEGIN:
        jxx WHILE_END
            ...
        jmp WHILE_BEGIN
    WHILE_END:
        ...
    

    while循环的条件跳是往上跳的,它需要反复执行同一代码段。


    第三步,通过OD打开运行的EXE程序“RE_XH.exe”。

    • 程序入口地址:0x00401260

    在这里插入图片描述

    第四步,往下查找代码,发现3个PUSH后(参数)就是主函数,然后F2添加断点并F7步入主函数。

    • 主函数:CALL RE_XH.00401005

    在这里插入图片描述


    第五步,分析汇编代码。
    这里存在一个JG跳转,它有点像if语句,下面还有一个JMP,有点像if-else指令,但是它的跳转是地址减量跳或往上跳,所以它是循环。

    在这里插入图片描述

    这时会发现while循环比刚才的多了一个跳转。我们会过计算机组成原理,当处理器执行跳转指令时,流水线会暂时挂起失效,本来流水线在取指令时已经准备预读后面的代码了,结果在译码过程中是个跳转,后面的代码预读就会出错,然后做流水线清理工作。所以,while循环有两跳对流水线的伤害比do-while大。


    第六步,接着我们用高版本VS2019编译一个Release版本,并用IDA进行分析,看看高版本有什么优化。

    在这里插入图片描述

    查看Text View,我们定位到main函数之后,看看它做的优化。它把循环的起点对齐到十六进制10的倍数地址,中间会用nop进行空指令填充。同时,它的汇编循环体变成了do-while循环。

    在这里插入图片描述

    注意,前面的VC++ Debug版本用IDA工具打开如下图所示。上图和下图同样都是while循环,但低版本可以看到JG(往下跳)和JMP(往上跳)两个跳转,典型的while循环;而高版本的却修改成了do-while循环的形式。

    在这里插入图片描述

    问题1:由于do-while循环会执行一次循环体,难道它不担心编译器出错吗?
    其实它比较的数值是常量,常量可以在编译期间预置其结果的,其实编译器在第一次的判断时先进行了一次常量传播,令n等于1,即判断的是 while(1<=100),比较1和100的关系条件必成立。

    问题2:那么,如果将100替换成变量,编译器还能识别吗?或者会报错?
    此时的编译器会将其进行转换,变成如下图所示的形式再执行do-while循环。其中if(n<=argc)条件判断嵌套一个循环。

    在这里插入图片描述

    对应的汇编代码如下,其中“jl short loc_401016”往下跳,还原成一个单分支,循环里面还有一个跳转“jle short loc_40100F”往上跳,满足do-while循环,最终还原成if加do-while,或者你知道有这个优化,直接还原成带变量的while循环也可以。

    在这里插入图片描述

    但需要注意,能不能把do-while直接还原成while循环,还需要看看这两个条件有没有相关性。如果有相关性才能还原,比如外层判断是文件的打开状态,while是迭代n值,这种情况不能还原。下图可以看到if和循环都是EAX参数比较,所以具有相关性。

    在这里插入图片描述



    3.for结构分析

    下面开始分析for循环结构。

    for(表达式1;表达式2;表达式3;)
    {
    	语句;
    }
    

    第一步,我们编写一个for循环代码。

    #include "stdafx.h"
    #include <stdlib.h>
    
    int main(int argc, char* argv[])
    {
    	int nSum = 0;
    
    	for (int n = 1; n<=argc; n++)
    	{
    		nSum = nSum + n;
    	} 
    	
    	printf("%d", nSum);
    	system("pause");
    	return 0;
    }
    

    第二步,编译生成新的Debug版可执行程序。

    在这里插入图片描述


    第三步,通过IDA打开运行的EXE程序“RE_XH.exe”。
    产出了三个跳转代码,如下图所示。

    在这里插入图片描述

    其代码定式如下所示,可以看到JMP、JG和JMP三个跳转。注意,for循环中FOR_STEP地址是低于BODY执行体的地址的。

    在这里插入图片描述


    第四步,分析汇编代码。
    首先MOV进行初始化赋值1,接着JMP跳转到比较部分,比较不成立则JG直接跳出循环,否则执行循环体BODY内容,接着继续JMP跳转上去执行n++操作。

    在这里插入图片描述

    注意,由于Release版本都被编译器优化成了do-while循环,所以我们需要在Debug版下进行对比。


    第五步,通过VS2019生成Release版本,然后用IDA打开代码对比。

    在这里插入图片描述

    IDA打开如下图所示,发现和do-while一样,高版本做了一点小处理,每次循环总次数增加了4(add eax,4),从而提升效率。

    在这里插入图片描述



    三.总结

    写到这里,这篇文章就介绍完毕,希望对您有所帮助,最后进行简单的总结下。

    • 条件语句逆向分析
    • 循环语句逆向分析

    学安全一年,认识了很多安全大佬和朋友,希望大家一起进步。这篇文章中如果存在一些不足,还请海涵。作者作为网络安全初学者的慢慢成长路吧!希望未来能更透彻撰写相关文章。同时非常感谢参考文献中的安全大佬们的文章分享,深知自己很菜,得努力前行。

    2020年8月18新开的“娜璋AI安全之家”,主要围绕Python大数据分析、网络空间安全、人工智能、Web渗透及攻防技术进行讲解,同时分享CCF、SCI、南核北核论文的算法实现。娜璋之家会更加系统,并重构作者的所有文章,从零讲解Python和安全,写了近十年文章,真心想把自己所学所感所做分享出来,还请各位多多指教,真诚邀请您的关注!谢谢。

    (By:Eastmount 2020-12-25 周五夜于武汉 https://blog.csdn.net/Eastmount)


    参考资料:
    真心推荐大家好好看看这些视频和文章,感恩这些大佬!前非常推荐钱老师的视频,感谢华科UP主。
    [1] 科锐逆向的钱林松老师受华中科技大学邀请- “逆向分析计算引导”
    [2] C语言逆向工程之游戏辅助开发 - C语言Plus
    [3] https://www.bilibili.com/video/BV1J5411x7qz?p=1

    展开全文
  • 传统DNN或者CNN无法对时间序列上的变化进行建模,即当前的预测只跟当前的输入样本...循环神经网络RNN包含循环的网络,可以记录信息的持久化信息,特别适合应用在跟时间序列相关的场合。   RNN之父Jürgen
  • 最近听到小伙伴们因为循环录入资料的问题犯难,看上去用UiPath做资料录入不难,做一个循环也不难,但就是不知道怎样将个功能搭配在一起用。所以半夜爬起来跟大家分享一下设计思路。 资料录入—>Type Into/Set ...
  • c语言循环优化

    千次阅读 2014-01-13 11:53:27
    C语言常规优化策略 3 循环优化 提高程序效率的核心是对影响代码执行速度的关键程序段...本节有关各种循环优化技术的讨论基本上以下面的一个程序段为对象,程序的涵义为:对于个给定的 数组a、b,计算a[8]b
  • C语言循环结构

    万次阅读 多人点赞 2018-11-28 23:21:45
    循环结构 一,知识点  1 for循环:  for (循环控制变量初始化; 循环终止条件; 循环控制变量增量)  {  循环体  }  循环执行步骤:第一,先进行循环控制变量初始化;  第二,执行循环终止条件,如果...
  • 保险系统部分模式

    千次阅读 2004-12-17 14:47:00
    Wolfgang Keller 关于保险系统采用的一些pattern的介绍,这是网上流行的一篇译文,但是初看之下有些行业词汇的误译,特意做了修正,顺便排版成word文档,...保险系统部分模式作者:Wolfgang Keller 著,liwenhua 译
  • .Net消息循环

    千次阅读 2014-10-23 09:47:41
    .NET对Windows消息循环的封装 .NET对Windows消息循环的封装 第一部分   在讲.NET对消息的包装前,先了解下传统的Windows程序。  先看一下直接用C++构造一个窗体的代码,以下代码在VS2005中...
  • 流程控制 while/until 循环

    千次阅读 2020-10-23 09:20:52
    在前面的章节中,我们开发了菜单驱动程序,来产生各种...在这一章中,我们将看一个叫做循环的程序概念,其可用来使程序的某些部分重复。shell 为循环提供了三个复合命令。 本章我们将查看其中的个命令,随后章节介绍
  • 操作系统题库(选择题部分,带解析)

    万次阅读 多人点赞 2018-06-22 10:59:52
    连续文件顺序文件:包括连续文件和串联文件顺序文件:记录按在其文件的中的逻辑顺序依次存入储存介质而建立的,即顺序文件中的物理记录和逻辑记录的顺序是一致的连续文件:连续文件中次序相继的个记录在存储介质中...
  • Java的循环语句

    千次阅读 2014-04-21 21:01:21
    Java 的循环语句有for,while 和 do-while 。这些语句创造了我们通常所称的循环(loops)。你可能知道,一个循环重复执行同一套指令直到一个结束条件出现。你将看到,Java 有适合任何编程所需要的循环结构。 ...
  • 本文目录Python语言设计编写的成绩管理系统基本要求具体要求合理的创建标题,有助于目录的生成分析要求实现功能如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建...
  • [libevent]事件主循环

    千次阅读 2015-04-20 13:25:42
    libevent事件处理的中心部分——事件主循环,根据系统提供的事件多路分发机制执行事件循环,对已注册的就绪事件,调用注册事件的回调函数来处理事件。 事件处理主循环 libevent的事件主循环主要是通过event_base_...
  • 循环神经网络

    万次阅读 2019-05-25 17:53:21
    今天突然发现百度词条对循环神经网络的解释全面的令人感动,尤其是提到了ESN和LSM这类水库计算模型 链接:...
  • shell脚本之for循环

    万次阅读 2018-12-18 11:37:28
    通常你需要重复一组命令直至达到某个特定条件,比如处理某个目录下的所有文件、系统上的所有用户或是某个文本文件中的所有行。 bash shell提供了 for 命令,允许你创建一个遍历一系列值的循环。每次迭代都使用其中...
  • Windows消息循环机制

    千次阅读 2015-07-13 17:25:52
    理解消息循环和整个消息传送机制对Windows编程来说非常重要。如果对消息处理的整个过程不了解,在windows编程中会遇到很多令人困惑的地方。 什么是消息(Message) 每个消息是一个整型数值,如果查看头文件(查看...
  • /*假设图书馆的图书包含书名、编号和作者属性,读者包含姓名和借书证号属性,每个读者最多可借5本书。设计一个类object,从它派生出图书类Book和读者类Reader,在Reader类中有一个 rentbook()成员函数用于借阅图书。...
  • 操作系统知识点整理(完整版)

    万次阅读 多人点赞 2017-12-26 22:34:05
    3)按功能可把软件分为“系统软件”和“应用软件”两部分 系统软件:操作系统语言处理程序,数据库管理系统 应用软件:各种管理软件,用于工程计算的软件包,辅助设计软件 4)通常把未配置任何软件的计算机称为...
  • 心脏工作原理和血液循环路线

    万次阅读 2018-10-10 22:39:06
    心脏的结构 心脏的工作过程 血液循环路线 体循环循环 动脉里面流动的一定时静脉血吗
  • c++ 对for循环的并行优化例子

    万次阅读 多人点赞 2018-08-17 16:14:53
    什么是并行优化? 并行优化是代码优化的基本方法,从大到小一共可以...这步是代码优化的重心,一般做完这步,系统性能会有明显的提升。今天要讨论的是第三步,for循环的并行优化。与前两者不同的是,for循环...
  • PDCA&OODA循环

    千次阅读 2019-12-18 15:37:52
    PDCA循环 1.PDCA循环是美国质量管理专家休哈特博士首先提出的,由戴明采纳、宣传...1、P (Plan) 计划,包括方针和目标的确定,以及活动规划的制定。 2、D (Do) 执行,根据已知的信息,设计具体的方法、方案和计划布...
  • for循环dos

    千次阅读 2013-11-28 17:30:52
    for命令是一种对一系列对象依次循环执行同一个或多个命令的在命令行或批处理中运行的命令,结合一些Windows管理中的程序后,其处理功能强大、应用灵活方便程度令人刮目相看。但是,其帮助信息也因此复杂往往令初学者...
  • 操作系统定义:操作系统是计算机系统中的一个系统软件,它是这样一些程序模块的集合---他们管理和控制计算机系统中的硬件及软件资源,合理的组织计算机工作流程,以便有效的利用这些资源为用户提供一个具有足够的...
  • 翻译的)1,2(操作系统的介绍部分)

    千次阅读 2015-03-30 14:36:42
    那啥书名具体不知道怎么说才好,反正看英文知道是那么个意思,翻译成中文就是别扭大家就简单接受一下吧,多多包涵(六级擦线而过,灰溜溜~~) Operating Systems in ...Chapter2 操作系统介绍  如果你已经对系
  • 在成功的加密项目中,激励循环(Incentive Loops)是很常见的。最棒的加密货币平台或代币通常都内置了鲁棒性(Robust)很好的激励循环(机制)。通过有机增长方式(译者注:Organic growth-有机增长,是指一个公司...
  • 超硬核!操作系统学霸笔记,考试复习面试全靠它

    万次阅读 多人点赞 2021-03-22 18:43:49
    之后会发布基于基础知识的大部分算法的模拟代码合集,敬请关注。
  • 深度学习:循环神经网络RNN

    千次阅读 2017-09-01 19:49:34
    循环神经网络(recurrent neural network,RNN)是一种具有反馈结构的神经网络,其输出不但与当前输入和网络的权值有关,而且也与之前网络的输入有关;RNN通过添加跨越时间点的自连接隐藏层,对时间进行建模;...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 456,002
精华内容 182,400
关键字:

循环系统包括哪两部分