[异常分发]三:R0下EventList又是什么时候添加的?

1)在创建调试线程中添加异常事件信息
调用过程PspCreateThread --> PspUserThreadStartup(初始化线程的参 数) -->  DbgkCreateThread (这里格式化ApiMsg作派发消息的参 数) -->  DbgkpSendApiMessage 

备注  
这里只关心的DbgkpSendApiMeassage函数的处理过程, 关于创建进程、创建调试对象(NtCreateDebugObject),创建线程及调试线程等信息请参考其他分析。

DbgkCreateThread函数
获取当前的相关信息,如果调试端口不为NULL,则根据状态来格式化ApiMsg结构体信息,调用消息派发函数,传入参数为标志值决定是否需要挂起进程及ApiMsg结构体的指针,也是要添加到调试对象异常链中的成员信息。

代码:
PAGE:00591478     push ebx                             ; 参数,决定是否要挂起进程
PAGE:00591479     lea eax, [ebp+DBGKM_APIMSG]         ; 取赋相应值的ApiMsg结构体地址
PAGE:0059147F     push eax
PAGE:00591480     call _DbgkpSendApiMessage@8         ; DbgkpSendApiMessage(x,x)

DbgkpSendApiMeassage
根据传入的标志值来决定是否要挂起进程,ApiMsg信息是用来填充Debug_Event结构体信息, 添加到;Debug_Object.EventList中主要通过DbgkpQueueMessage来实现。

代码:

PAGE:00591008     mov ecx, [ebp+pApiMsg]
PAGE:0059100B     mov eax, large fs:_ETHREAD.Tcb.UserAffinity
PAGE:00591011    mov [ecx+DBGKM_APIMSG.ReturnedStatus], 103h ; 返回标志为: STATUS_PENDING
PAGE:00591018     mov eax, [eax+_ETHREAD.Tcb.___u6.ApcState.Process] ;
PAGE:0059101B     xor edx, edx
PAGE:0059101D     inc edx                             ; edx = 1
PAGE:0059101E     lea esi, [eax+_ETHREAD.CrossThreadFlags] ;获取标志地址
PAGE:00591024     lock or [esi], edx                     ;修改Flages = 1
PAGE:00591027     push ebx                            ; TargetDebugObject  NULL
PAGE:00591028     push ebx                            ; Flags     NULL
PAGE:00591029     push ecx                            ; ApiMsg  传入参数ApiMsg的地址
PAGE:0059102A     push large dword ptr fs:_KPCR.PrcbData.CurrentThread ; Thread 结构体首地址
PAGE:00591031     push eax                            ; Process结构体地址
PAGE:00591032     call _DbgkpQueueMessage@20       ; 添加链表操作在这里完成

 DbgkpQueueMessage
完善结构信息后添加Debug_Event到Debug_Object.EventList链表的尾节点
         Debug_Event结构体赋值操作主要如下图所示。

PAGE:0058FD6F     mov esi, [ebp+FunUseThisReAs_LocalValue_pDebugObject]
PAGE:0058FD72     mov [ebp+Flages_LV], esi            ; 将标志值保存到局部变量中
PAGE:0058FD75     and [ebp+Flages_LV], 2              ; DEBUG_EVENT_NOWAIT = 2
PAGE:0058FD79     jz  short NOT_DEBUG_EVENT_NOWAIT    ;
      ;Flag=2 则申请内核空间,这里省略
NOT_DEBUG_EVENT_NOWAIT:                 
PAGE:0058FDC3     mov ecx, offset _DbgkpProcessDebugPortMutex ; FastMutex  
PAGE:0058FDC8     lea ebx, [ebp+StaticDebugEvent_var_B8_LV] ;  DebugEvent = &StaticDebugEvent;
PAGE:0058FDCE     mov [ebp+var_8C], esi        ; 修改结构体成员标识  DebugEvent->Flags = Flags;
PAGE:0058FDD4     call @ExAcquireFastMutex@4    
PAGE:0058FDD9     mov eax, [ebp+Process_Re]

  ;取调试端口保存到变量中, 取出ApiNumber,根据不同类型的序号作相应的处理

 typedef enum _DBGKM_APINUMBER {            //对应的序号
    DbgKmExceptionApi,
    DbgKmCreateThreadApi,
    DbgKmCreateProcessApi,
    DbgKmExitThreadApi,
    DbgKmExitProcessApi,
    DbgKmLoadDllApi,
    DbgKmUnloadDllApi,
    DbgKmMaxApiNumber
} DBGKM_APINUMBER;

 

PAGE:0058FDDC    mov eax, [eax+_EPROCESS.DebugPort]  ; 取出进程的调试端口
PAGE:0058FDE2     mov [ebp+FunUseThisReAs_LocalValue_pDebugObject], eax ; 调试端口保存到局部变量中
PAGE:0058FDE5     mov eax, [ebp+ApiMsg_Re]            ; 去参数所指向的地址值
PAGE:0058FDE8     mov eax, [eax+DBGKM_APIMSG.ApiNumber] ; 取ApiMsg的序号
PAGE:0058FDEB     cmp eax, 1             ; ApiNumber是否为1  DbgKmCreateThreadApi=1
PAGE:0058FDEE     jz  short CREATE_THREAD
PAGE:0058FDF0     cmp eax, 2            ; ApiNumber是否为2   DbgKmCreateProcessApi=2
PAGE:0058FDF3     jnz short NOT_CREATE_PROCESS        ;              
CREATE_THREAD:                   
PAGE:0058FDF5     mov ecx, [ebp+Thread_Re] 
;80 = PS_CROSS_THREAD_FLAGS_SKIP_CREATION_MSG
PAGE:0058FDF8     test byte ptr [ecx+_ETHREAD.CrossThreadFlags], 80h ;
PAGE:0058FDFF     jz  short NOT_CREATE_PROCESS       ;不为80跳走
PAGE:0058FE01     and [ebp+FunUseThisReAs_LocalValue_pDebugObject], 0 ;变量=NULL
NOT_CREATE_PROCESS:   
PAGE:0058FE05     cmp eax, 3                          ; 3 = DbgKmExitThreadApi
PAGE:0058FE08     jz  short EXIT_THREAD
PAGE:0058FE0A     cmp eax, 4                          ; 4 = DbgKmExitProcessApi
PAGE:0058FE0D     jnz short DEBUG_EVENT_NOWAIT_END
PAGE:0058FE0F
EXIT_THREAD:             
PAGE:0058FE0F     mov eax, [ebp+Thread_Re] 
; 1 = PS_CROSS_THREAD_FLAGS_SKIP_CREATION_MSG
PAGE:0058FE12     test byte ptr [eax+(_ETHREAD.CrossThreadFlags+1)], 1 
PAGE:0058FE19     jz  short  DEBUG_EVENT_NOWAIT_END
PAGE:0058FE1B     and [ebp+FunUseThisReAs_LocalValue_pDebugObject], 0 ;
DEBUG_EVENT_NOWAIT_END:      
PAGE:0058FE1F     and [ebx+Debug_event.ContinueEvent.Header.SignalState], 0 ; ebx = &Debug_Evnt
PAGE:0058FE23     mov esi, [ebp+ApiMsg_Re]
PAGE:0058FE26     mov [ebx+Debug_event.ContinueEvent.Header.___u0._s0.Type], 1 ; 共用体Type=1
PAGE:0058FE2A     mov [ebx+Debug_event.ContinueEvent.Header.___u0._s2.Size], 4 ; Size = 4
PAGE:0058FE2E     lea eax, [ebx+Debug_event.ContinueEvent.Header.WaitListHead] ;
                     ;前后指针指向相同的地址,为初始化单节点为双向链表节点
PAGE:0058FE31     mov [eax+Debug_event.EventList.Blink], eax ; 这里是初始化debug_Event 的 List_Head
PAGE:0058FE34     mov [eax+Debug_event.EventList.Flink], eax ; 头和尾都指向自身
PAGE:0058FE36     mov eax, [ebp+Process_Re]
PAGE:0058FE39     push edi
PAGE:0058FE3A     mov [ebx+Debug_event.Process], eax     ;保存Process
PAGE:0058FE3D     mov eax, [ebp+Thread_Re]     
                  
;DebugEvent.ApiMsg = ApiMsg(传入参数)
PAGE:0058FE40     lea edi, [ebx+Debug_event.ApiMsg]    ;目标地址
PAGE:0058FE43     push 1Eh                ;给ecx准备值
PAGE:0058FE45     mov [ebx+Debug_event.Thread], eax      ;保存Thread
PAGE:0058FE48     mov [ebp+ApiMsg_LV], edi        ;目标地址保存在局部变量中
PAGE:0058FE4B     pop ecx                 ; ECX = 1EH, edi(Debug_Event->ApiMsg) <- esi(ApiMsg)
PAGE:0058FE4C     rep movsd          ;ApiMsg的赋值,esi 为传入的ApiMsg,
      ;完成Debug_Event.ClientId = Ethread.cid (包含进程和线程)
PAGE:0058FE4E     mov ecx, [ebp+FunUseThisReAs_LocalValue_pDebugObject]
PAGE:0058FE51     mov esi, eax                  ;eax = thread
PAGE:0058FE53     mov eax, [esi+_ETHREAD.cid.UniqueProcess]
PAGE:0058FE59     mov [ebx+Debug_event.ClientId.UniqueProcess], eax  
PAGE:0058FE5C     mov eax, [esi+_ETHREAD.cid.UniqueThread]
PAGE:0058FE62     xor edi, edi
PAGE:0058FE64     cmp ecx, edi                        ; 判断变量值是否为NULL
PAGE:0058FE66     mov [ebx+Debug_event.ClientId.UniqueThread], eax 
                        ;以上主要赋值操作如前图所示
PAGE:0058FE69     jnz short pDEBUG_OBJECT_NOT_QEU_0   ; 判断标志是否为0,有跳过则设置为NULL
PAGE:0058FE6B     mov [ebp+Thread_Re], 0C0000353h    ; STATUS_PORT_NOT_SET
PAGE:0058FE72     jmp short EQU_0_END
pDEBUG_OBJECT_NOT_QEU_0:             
PAGE:0058FE74     add ecx, 10h                        ; FastMutex
PAGE:0058FE77     mov [ebp+TargetDebugObject_Re], ecx
PAGE:0058FE7A     call @ExAcquireFastMutex@4          ; ExAcquireFastMutex(x)
PAGE:0058FE7F     mov edx, [ebp+FunUseThisReAs_LocalValue_pDebugObject]
PAGE:0058FE82     test byte ptr [edx+_DEBUG_OBJECT.Flags], 1 ; 1 = DEBUG_OBJECT_DELETE_PENDING
PAGE:0058FE86     jnz short IS_NOT_DELETE_PENDING     ;用来判断是否被调试
            ; if ((Flages_LV=(Flages_Re& DEBUG_EVENT_NOWAIT)) == 0)
PAGE:0058FE88     cmp [ebp+Flages_LV], edi      
        ;完成添加Debug_Event到Debug_Object.EventList尾部操作
PAGE:0058FE8B     lea eax, [edx+_DEBUG_OBJECT.EventList] ; eax = &Debug_Object->EventLsit
PAGE:0058FE8E     mov ecx, [eax+Debug_event.EventList.Blink] ; EventList(Flink)偏移4是她的第一个成员Blink
; debug_event->EventList.Flink = &Debug_object->EventList.Flink
PAGE:0058FE91     mov [ebx+Debug_event.EventList.Flink], eax 
;debug_Event->EventList.Blink = Debug_object->EventList.Blink
PAGE:0058FE93     mov [ebx+Debug_event.EventList.Blink], ecx ; 
PAGE:0058FE96     mov [ecx+Debug_event.EventList.Flink], ebx ; 
PAGE:0058FE98     mov [eax+(_DEBUG_OBJECT.EventList.Blink-30h)], ebx
PAGE:0058FE9B     jnz short FLAGES_NOT_EQU_0         ;
; SetEvent()函数设置一个事件对象的信号状态去发信号, 并试图满足尽可能多的等待,之前的事件对象状态作为返回值返回
PAGE:0058FE9D     push edi                            ; Wait    = FALSE
PAGE:0058FE9E     push edi                            ; Increment = 0
PAGE:0058FE9F     push edx                            ; Event pDebug_Object   这个没理解
PAGE:0058FEA0     call _KeSetEvent@12                 ; KeSetEvent(x,x,x)
PAGE:0058FEA5
FLAGES_NOT_EQU_0:    
 ; thread_re = STATUS_SUCCESS = 0 这里又用函数参数作局部变量使用
PAGE:0058FEA5     mov [ebp+Thread_Re], edi      
PAGE:0058FEA8     jmp short REALSE_FASE_MUTEX
IS_NOT_DELETE_PENDING:   
PAGE:0058FEAA     mov [ebp+Thread_Re], 0C0000354h     ; STATUS_DEBUGGER_INACTIVE = 354

 2)  中断异常添加的异常信息
      被调试进程产生异常时,内核异常处理对应接口KiTrap捕获异常进行处理。部分信息如下
        X86异常及中断号
         中断号                     异常                对应函数
           0                    除零错误              _KiTrap00
           1                    调试陷阱              _KiTrap01
           3                       断点                 _KiTrap03
        ………..
     这里仅简述_kiTrap03主要处理过程 
kiTrap03 : 为当前发生异常建立一个TrapFrame,保存寄存器, 当前的状态,发生异常时的处理模式,然后掉用CommonDispatchException 

代码:
CommonDispatchException: 
    mov esi, ecx                        ; Ethread
    mov edi, edx                        ; 调试端口
    mov edx, eax
    mov ebx, [ebp+_KTRAP_FRAME._Eip]
    dec ebx                             ; ebx = EIP - 1
    mov ecx, 3
    mov eax, 80000003h                  ; Int3断点
    call CommonDispatchException

CommonDispatchExceptioin函数主要完成Exception_Record结构体的赋值,然后调用KiDispatchException

代码:
CommonDispatchExceptioin:   
    mov [esp+EXCEPTION_RECORD.ExceptionCode], eax ; 参数 eax = ExceptioinCode   创建异常代码
    xor eax, eax
    mov [esp+EXCEPTION_RECORD.ExceptionFlags], eax ; 设置异常标志
    mov [esp+EXCEPTION_RECORD.ExceptionRecord], eax ; 设置副异常代码
    mov [esp+EXCEPTION_RECORD.ExceptionAddress], ebx ; 参数 ebx = EIP  (TRAP3  EIP-1)  异常地址
    mov [esp+EXCEPTION_RECORD.NumberParameters], ecx ; 参数 ecx ErNumberParameters   ( TRAP3 = 3 )
    cmp ecx, 0                          ; 这里传入参数为3
    jz  short ParameNumber_EQU_0
    lea ebx, [esp+EXCEPTION_RECORD.ExceptionInformation] ; Exception_Record.ExceptionInformation 是个数组
    mov [ebx+(EXCEPTION_RECORD.ExceptionInformation-14h)], edx ; 外面eax的值,可能为0
    mov [ebx+4], esi                    ; EThread 地址 ExcptionInfo[1] = Ethread
    mov [ebx+8], edi                    ; ExceptionInfo[2] = edi
ParameNumber_EQU_0:                mov ecx, esp
    test byte ptr [ebp+(_KTRAP_FRAME.EFlags+2)], 2
    jz  short NOT_VM
    mov eax, 0FFFFh
    jmp short de20                      ; 处理模式
NOT_VM:        
mov eax, [ebp+_KTRAP_FRAME.SegCs]
de20:                                
    and eax, 1                          ; eax保存之前的模式
    push 1                              ; 是否第一次
    push eax                            ; 之前的模式
    push ebp                            ; TrapFrame
    push 0                              ; ExceptionFrame  X86不是用为NULL
    push ecx                            ; 异常记录(传入参数)
    call _KiDispatchException@20        

KiDispatchException完成异常记录的信息格式 DbgkForwardException完成异常信息的添加,还是通过调用DbgkpSendApiMessage来完成。这里主要简述用户模式调试异常的分发。

代码:
mov eax, [esi+_EXCEPTION_RECORD.ExceptionCode]
    cmp eax, 80000003h
    jz  short STATUS_BREAKPOINT   ; 是断点就修改正EIP,即EIP = EIP - 1;
    cmp eax, 10000004h           ; KI_EXCEPTION_ACCESS_VIOLATION (KI_EXCEPTION_INTERNAL | 0x4)
    jnz short END_EXCEPTION_BLOCK       ;
    mov [esi+_EXCEPTION_RECORD.ExceptionCode], 0C0000005h ; 异常代码: 访问异常
    cmp byte ptr [ebp+PreviousMode], 1  ; 判断是否UseMode
    jnz short END_EXCEPTION_BLOCK       ; 不是用户模式就跳出
    lea eax, [ebp+ContextFrame]
    push eax                            ; Context
    push esi                            ; ExceptionRecord
    call _KiCheckForAtlThunk@8     ; 决定一个访问异常被提起由于一个试图执行一个ATL thunk在非执行非栈区
                                        ; 如果是这样,thunk将效仿继续执行
    test al, al                    ; 返回值:TRUE => Context进行了更新,以反映效仿的ATL的thunk,恢复执行。
    jnz Handled1                        ; 返回值是TRUE,则跳到HANDLE1去
    cmp byte ptr ds:0FFDF0280h, 1       ; SharedUserData->ProcessorFeatures[PF_NX_ENABLED] = TRUE
jnz short END_EXCEPTION_BLOCK
; ExceptionInfo[0] = 8 = EXCEPTION_EXECUTE_FAULT
    cmp [esi+_EXCEPTION_RECORD.ExceptionInformation], 8 
    jnz short END_EXCEPTION_BLOCK       ; 不是执行出错就跳出
    mov eax, _KeFeatureBits             ; KeFeatureBits = 0
    test eax, 40000000h                 ; KF_GLOBAL_32BIT_EXECUTE = 40...全局32位可执行
    jnz short SET_EXCPTIONINFO_EQU_0    ;
    mov ecx, large fs:_KPCR.PrcbData.CurrentThread
    mov ecx, [ecx+_KTHREAD.___u6.ApcState.Process]
test [ecx+_EPROCESS.Pcb.___u25.Flags.field_0], 2 ; ExecuteEnable bit1位置,所以是否为0,
    jnz short SET_EXCPTIONINFO_EQU_0
test eax, eax                          ; eax&KF_GLOBAL_32BIT_NOEXECUTE    
    js  short END_EXCEPTION_BLOCK       ; 符号位为负  eax&800  就看高位有没有了
    mov eax, large fs:_KPCR.PrcbData.CurrentThread
    mov eax, [eax+_ETHREAD.Tcb.___u6.ApcState.Process]
    test [eax+_EPROCESS.Pcb.___u25.Flags.field_0], 1 ; ExecuteDisable bit0
    jnz short END_EXCEPTION_BLOCK

SET_EXCPTIONINFO_EQU_0:                 ; CODE XREF: KiDispatchException(x,x,x,x,x)+C6 j
                                        ; KiDispatchException(x,x,x,x,x)+D6 j
    and [esi+_EXCEPTION_RECORD.ExceptionInformation], 0
    jmp short END_EXCEPTION_BLOCK


STATUS_BREAKPOINT:                      ; CODE XREF: KiDispatchException(x,x,x,x,x)+83 j
    dec [ebp+ContextFrame._Eip]         ; 断点的话就需要把EIP向前移一个字节
END_EXCEPTION_BLOCK:                    ; CODE XREF: KiDispatchException(x,x,x,x,x)+8A 

根据是什么模式,调试接口是否NULL来决定是内核处理,还是用户方式处理, 这里仅列出了用户的处理流程

代码:
cmp byte ptr [ebp+PreviousMode], 0  ; 判断是否为KernelMode
   jnz short NOT_KERNEL_MODE
   
    ;判断是第几次处理,要使用DebugPort还是ExceptionProt,然就调用对应的下面三种方式;
NOT_KERNEL_MODE:                       
    cmp [ebp+FirstChance], 1              ;是否是第一次机会
    jnz NOT_FIRST_CHANCE_           ;不是则是去第二次分发地方处理
   
;有异常调系统首先发给调试器处理(前提是有调试), 如果调试器没有处理,系统还有机会派发第二次给调试器处理。
;第一次机会,调试端口
    push 0                              ; 不是第二次
    push 1                              ; DebugException
    push esi                            ; 异常记录
call _DbgkForwardException@12       ; DbgkForwardException(x,x,x)
;如果调试器第一次没有处理还会有第二次机会接受到系统派发的异常处理信息给调试器处理。
;第二次机会,调试端口
    push 1                              ; 这里就是第二次机会了
    push 1                              ; 调试端口
    push esi                            ; ExceptionRecord
call _DbgkForwardException@12       ; DbgkForwardException(x,x,x)
;如果派发2次给调试器处理,都没处理的话就会发异常端口来处理异常。 
;第二次机会,异常端口
    push 1                              ; 第二次机会
    push 0                              ; 不是调试端口
    push esi                            ; ExceptionRecord
    call _DbgkForwardException@12       ; 根据传入的DebugException来决定调用哪种接口处理信息

DbgkForwardException 根据传入参数来决定是第几次机会,发送什么端口及异常记录信息

DbgkForwardException(
        pExceptionRecord ,  异常记录结构体指针
        isDebugException,  是否是调试异常,是则发送调试端口
        isSecondChance)  是否是第二次

 

代码:
 mov eax, large fs:_KPCR.PrcbData.CurrentThread
    push ebx
    mov ebx, [ebp+DebugException]       ; DebugPort = TRUE   ExceptionPort = FALSE
    test bl, bl
    mov [ebp+ApiMsg], 78005Ch           ; 这部分是格式化ApiMsg、
    mov [ebp+ZeroInit], 8
    mov eax, [eax+_ETHREAD.Tcb.___u6.ApcState.Process]
    jz  short DebugException_EQU_FALSE  ; DebugException = FALSE  则是使用ExceptionPort
    mov ecx, large fs:_KPCR.PrcbData.CurrentThread
    test byte ptr [ecx+_ETHREAD.CrossThreadFlags], 4 ; PS_CROSS_THREAD_FLAGS_HIDEFROMDBG
    jz  short NOT_CROSS_THREAD_FLAGES
    xor eax, eax                        ; eax = NULL
    jmp short NOT_CROSS_THREAD_FLAGES_END_IfElse ; dl = FALSE
NOT_CROSS_THREAD_FLAGES: 
    mov eax, [eax+_EPROCESS.DebugPort]  ; DebugException = TRUE 时使用DebugPort
NOT_CROSS_THREAD_FLAGES_END_IfElse:   
    xor dl, dl                          ; DL= FALSE
    jmp short debugException_EQU_FALSE_END_IfElse
DebugException_EQU_FALSE:           
    mov eax, [eax+_EPROCESS.ExceptionPort] ; DebugException = FALSE  则是使用ExceptionPort
    mov [ebp+ZeroInit], 7                  ; LPC_EXCEPTION
    mov dl, 1                            ; DL= TRUE
debugException_EQU_FALSE_END_IfElse:   
    test eax, eax                       ; 判断调试端口是否为NULL
    jz  short END_FUN_SET_RETURN_VALUE
    push esi
    mov esi, [ebp+ExceptionRecord]
    push edi
    push 14h
    pop ecx                             ; ECX = 14H
    lea edi, [ebp+var_58]               ; [ebp+var_58] = [ebp+ExceptioinRecord]   共复制14H个字节
    rep movsd
    xor ecx, ecx                        ; ECX = 0
    cmp [ebp+SecondChance], cl          ; 判断是否是第一次机会 0是第一次,1是第二次
    pop edi                             ; 恢复前面压栈保存的寄存器
setz cl                             ; Set byte if zero (ZF=1)  如果zf标志位1,设置一个1字节寄存器为1
    test dl, dl                         ; dl 是个标志,主要看使用ExceptionPort还是DebugPort
                                        ; ExcepP =>dl = 1      DebugP => dl = 0
    pop esi                             ;
    push ebx                            ; SuspendProcess   BOOL值决定哪种端口使用哪种端口
    mov [ebp+FirstChance], ecx    ; ecx = !(SecondChance)
                               ; 在SecondChance为0的情况下才会SetZ使cl=>1
                               ; 也就是SecondChance=0 =>[var_8]=1    SecondChance=1 =>[var_8]=0
    jz  short DEBUG_PORT
    push eax                            ; Port
    lea eax, [ebp+ApiMsg]
    push eax                            ; ApiMsg
    call _DbgkpSendApiMessageLpc@12     ; DbgkpSendApiMessageLpc(x,x,x)
    jmp short DEBUG_PORT_END_IfElse
DEBUG_PORT:                          
    lea eax, [ebp+ApiMsg]
    push eax                            ; pApiMsg
    call _DbgkpSendApiMessage@8         ; DbgkpSendApiMessage(x,x)
DEBUG_PORT_END_IfElse:                 
    test eax, eax                  ; 判断DbgkpSendApiMsg是否成功,返回值又是函数中处理信息的函数的返回值
    jl  short END_FUN_SET_RETURN_VALUE
    test bl, bl                       ; 决定使用哪种Port的标志值
    jz  short IS_DEUBG_PORT       ; 

说明: 

      这里只简单的分析了主要的模块,还有很多函数没分析。 又水平确实有限,不足和错误地方难免,还望各位不吝指出或帮忙补充。

转载于:https://www.cnblogs.com/DreamOfGalaxy/articles/4491986.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值