anti-debug0——结构

本文深入探讨了Windows平台上的反调试技术,详细介绍了TEB和PEB结构体的作用及访问方式,以及SEH和VEH两种异常处理技术的原理与应用。通过分析TEB和PEB中的关键成员,如ProcessEnvironmentBlock、ImageBaseAddress等,揭示了反调试技术如何利用这些信息进行自我保护。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

注:这里的反调试技术主要针对Windows平台

 

TEB

TEB指线程环境块,它是一个结构体,包含进程中运行线程的各种信息,每个线程中都有一个对应的TEB结构体。由于这个结构体实在太复杂,这里只说几个跟调试有关的部分。

 

+0x30 ProcessEnvironmentBlock

它是指向PEB结构体的指针,PEB是进程控制块,每个进程都有一个对应的PEB

 

+0x00 NtTib

NtTib是TEB结构体的第一个成员,定义如下:

typedef struct __NT_TIB {

struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList;

PVOID StackBase;

PVOID StackLimit;

PVOID SubSystemTib;

union{

PVOID FiberData;

DWORD Version;

};

PVOID ArbitraryUserPointer;

struct _NT_TIB *Self;

} NT_TIB;

 

访问方式:

Ntdll.NtCurrentTeb(),代码如下

_asm{

mov eax, DWORD PTR FS:[18]

ret

}

所以本质上可以直接通过FS寄存器来访问TEB结构体,如

TEB起始地址,FS:[0x18]

PEB起始地址,FS:[0x30]

SEH起始地址,FS:[0]

 

PEB

PEB是存放进程信息的结构体,它的结构同样十分复杂,这里也只记录关键部分。

关于调试状态的成员在反调试时再来看,这里只说与进程相关的成员

+0x08 ImageBaseAddress用来表示进程的ImageBase可用API GetModuleHandle()来获取

+0x0c Ldr指向_PEB_LDR_DATA结构体,可以直接获取dll模块加载基地址

 

访问方式:

FS:[0x30]

 

TEB.ProcessEnvironmentBlock

 

mov eax,DWORD PTR FS:[18]

mov eax,DWORD PTR DS:[eax+0x30]

都差不多吧

 

异常处理

Windows异常处理是操作系统处理程序错误或异常的一系列流程和技术的总称,开发人员主要使用两种异常处理技术,分别是SEH和VEH

 

SEH

SEH(结构化异常处理),是Windows系统用于自身除错的一种机制,它告诉系统当程序运行出现异常或错误时由谁来处理,从程序设计的角度来说,就是系统在终结程序之前给程序提供的一个执行其预先设定的回调函数的机会。

 

相关数据结构

1.EXCEPTION_REGISTRATION_RECORD

TIB在上面提到的TEB+0位置的结构,其中字段ExceptionList是一个链表,它是由EXCEPTION_REGISTRATION_RECORD结构作为表项组成的,定义如下:

typedef struct _EXCEPTION_REGISTRATION_RECORD{

struct _EXCEPTION_REGISTRATION_RECORD *Next;

PEXCEPTION_ROUTINE Handler;

} EXCEPTION_REGISTRATION_RECORD;

异常处理回调函数的地址就放在Handler中。

2.EXCEPTION_POINTERS

在程序的执行过程中,当发生一个异常时,若并非是调试状态,则操作系统会将异常信息转交给用户态的异常处理过程。有一个重要的事实是,内核态和用户态使用的堆栈并非同一个,所以在操作系统转交异常信息的方式是将一个EXCEPTION_POINTERS结构放入用户栈中,这个结构定义如下

typedef struct _EXCEPTION_POINTERS {

PEXCEPTION_RECORD ExceptionRecord;

PCONTEXT ContextRecord;//表示CPU当前线程状态

}EXCEPTION_POINTERS

可以看到,它本质上包装了另外两个结构而已。

3.EXCEPTION_RECORD

EXCEPTION_RECORD结构就是异常信息的本体了,定义如下

typedef struct _EXCEPTION_RECORD {

NTSTATUS ExceptionCode;//异常代码,表示异常产生的原因

ULONG ExceptionFlags;

struct _EXCEPTION_RECORD *ExceptionRecord;

PVOID ExceptionAddress;//异常发生地址

ULONG NumberParameters;

ULONG_PTR ExceptionInformation[];

}EXCEPTION_RECORD;

 

安装SEH

由于TEB+0x00处为ExceptionList结构,所以只要将链表头指针放入该结构中就算是安装完成了,所以,我们经常可以看到在函数开头处有类似这样的代码

 

就是在给fs:[0]赋值的,就说明是在安装SEH,更标准的写法是这样

push offset SEHandler

push fs:[0]

push fs:[0],esp

 

/* ---------------------------------------------------------------- * 执行Merge Join算子的主函数 * * 参数: * - node: Merge Join算子的状态信息 * * 返回: * - TupleTableSlot类型的指针,表示Merge Join算子的输出结果 * ---------------------------------------------------------------- */ TupleTableSlot* ExecMergeJoin(MergeJoinState* node) { bool qual_result = false; // 存储连接条件的判定结果 int compare_result; // 存储合并键的比较结果 TupleTableSlot* inner_tuple_slot = NULL; // 存储内部表的元组槽 TupleTableSlot* outer_tuple_slot = NULL; // 存储外部表的元组槽 /* * 从节点中获取相关信息 */ PlanState* inner_plan = innerPlanState(node); // 获取内部表的计划状态 PlanState* outer_plan = outerPlanState(node); // 获取外部表的计划状态 ExprContext* econtext = node->js.ps.ps_ExprContext; // 获取表达式上下文 List* join_qual = node->js.joinqual; // 获取连接条件 List* other_qual = node->js.ps.qual; // 获取其他限制条件 bool do_fill_outer = node->mj_FillOuter; // 是否填充外部表的标志 bool do_fill_inner = node->mj_FillInner; // 是否填充内部表的标志 /* * 检查是否仍在从先前的连接中投影出元组 * (因为在投影表达式中存在返回集的函数)。如果是这样,尝试投影另一个。 */ if (node->js.ps.ps_TupFromTlist) { TupleTableSlot* result = NULL; ExprDoneCond isDone; result = ExecProject(node->js.ps.ps_ProjInfo, &isDone); if (isDone == ExprMultipleResult) return result; /* 完成对源元组的处理... */ node->js.ps.ps_TupFromTlist = false; } /* * 重置每个元组的内存上下文,以释放在前一个元组周期中分配的任何表达式评估存储。 * 请注意,这只能在我们完成了从连接元组投影出元组之后发生。 */ ResetExprContext(econtext); /* * 好的,一切都准备就绪,让我们开始工作 */ for (;;) { MJ_dump(node); /* * 获取连接的当前状态并根据需要执行相应的操作。 */ switch (node->mj_JoinState) { /* * EXEC_MJ_INITIALIZE_OUTER表示这是第一次调用ExecMergeJoin(), * 因此我们需要获取外部和内部子计划的第一个可匹配元组。 * 我们在INITIALIZE_OUTER状态下处理外部子计划,然后转到INITIALIZE_INNER状态处理内部子计划。 */ case EXEC_MJ_INITIALIZE_OUTER: MJ_printf("ExecMergeJoin: EXEC_MJ_INITIALIZE_OUTER\n"); outer_tuple_slot = ExecProcNode(outer_plan); node->mj_OuterTupleSlot = outer_tuple_slot; /* 计算连接值并检查是否不可匹配 */ switch (MJEvalOuterValues(node)) { case MJEVAL_MATCHABLE: /* 可以开始获取第一个内部元组 */ node->mj_JoinState = EXEC_MJ_INITIALIZE_INNER; break; case MJEVAL_NONMATCHABLE: /* 保持在相同的状态以获取下一个外部元组 */ if (do_fill_outer) { /* * 为内部元组生成一个带有空值的虚拟连接元组, * 如果通过非连接条件,则返回该元组。 */ TupleTableSlot* result = NULL; result = MJFillOuter(node); if (result != NULL) return result; } break; case MJEVAL_ENDOFJOIN: /* 不再有外部元组 */ MJ_printf("ExecMergeJoin: nothing in outer subplan\n"); if (do_fill_inner) { /* * 需要为剩余的内部元组发出右连接元组。 * 我们设置MatchedInner = true以强制ENDOUTER状态推进内部。 */ node->mj_JoinState = EXEC_MJ_ENDOUTER; node->mj_MatchedInner = true; break; } /* * 如果MergeJoin的一侧返回0个元组,并且不需要生成具有null的虚构连接元组, * 那么我们应该更早地在MergeJoin下取消初始化消费者。 * 应该注意,我们不能在predpush中提前取消初始化。 */ if (((PlanState*)node) != NULL && !CheckParamWalker((PlanState*)node)) { ExecEarlyDeinitConsumer((PlanState*)node); } /* Otherwise we're done. */ goto done; default: break; } break; case EXEC_MJ_INITIALIZE_INNER: MJ_printf("ExecMergeJoin: EXEC_MJ_INITIALIZE_INNER\n"); inner_tuple_slot = ExecProcNode(inner_plan); node->mj_InnerTupleSlot = inner_tuple_slot; /* 计算连接值并检查不匹配 */ switch (MJEvalInnerValues(node, inner_tuple_slot)) { case MJEVAL_MATCHABLE: /* * OK,我们有了初始元组。首先跳过非匹配的元组。 */ node->mj_JoinState = EXEC_MJ_SKIP_TEST; break; case MJEVAL_NONMATCHABLE: /* 在前进之前进行标记,如果需要的话 */ if (node->mj_ExtraMarks) ExecMarkPos(inner_plan); /* 保持相同的状态以获取下一个内部元组 */ if (do_fill_inner) { /* * 为外部元组生成带有 null 值的假连接元组,并在它通过非连接条件时返回 */ TupleTableSlot* result = NULL; result = MJFillInner(node); if (result != NULL) return result; } break; case MJEVAL_ENDOFJOIN: /* 不再有内连接 */ MJ_printf("ExecMergeJoin: nothing in inner subplan\n"); if (do_fill_outer) { /* * 需要为所有外部元组生成左连接元组,包括我们刚刚获取的元组。 * 我们设置 MatchedOuter = false,以强制在推进外部之前在 ENDINNER 状态下发出第一个元组。 */ node->mj_JoinState = EXEC_MJ_ENDINNER; node->mj_MatchedOuter = false; break; } /* * 如果 MergeJoin 的一侧返回 0 元组并且不需要生成具有空值的虚假连接元组, * 我们应该更早地在 MergeJoin 下去初始化消费者。 * 注意我们不能在 predpush 内部进行提前的去初始化。 */ if (((PlanState*)node) != NULL && !CheckParamWalker((PlanState*)node)) { ExecEarlyDeinitConsumer((PlanState*)node); } /* Otherwise we're done. */ goto done; default: break; } break; /* * EXEC_MJ_JOINTUPLES 表示我们有两个满足合并条件的元组,所以我们将它们连接起来, * 然后继续获取下一个内部元组(EXEC_MJ_NEXTINNER)。 */ case EXEC_MJ_JOINTUPLES: MJ_printf("ExecMergeJoin: EXEC_MJ_JOINTUPLES\n"); /* * 设置下一个状态机状态。如果我们返回了这个连接元组,或者只是继续执行状态机,都会发生正确的事情。 */ node->mj_JoinState = EXEC_MJ_NEXTINNER; /* * 检查额外的条件,以查看我们是否真的想要返回这个连接元组。如果不是,可以继续合并。 * 我们必须区分额外的 joinquals(必须通过以考虑元组对于外连接逻辑是“匹配的”)和其他quals * (在我们实际返回元组之前必须通过)。 * * 我们不在这里使用 ResetExprContext,假设我们在检查合并条件时刚刚使用了一个。 * 每个元组应该足够了。我们必须为 ExecQual 设置表达式上下文链接,以便使用这些元组。 */ outer_tuple_slot = node->mj_OuterTupleSlot; econtext->ecxt_outertuple = outer_tuple_slot; inner_tuple_slot = node->mj_InnerTupleSlot; econtext->ecxt_innertuple = inner_tuple_slot; qual_result = (join_qual == NIL || ExecQual(join_qual, econtext, false)); MJ_DEBUG_QUAL(join_qual, qual_result); if (qual_result) { node->mj_MatchedOuter = true; node->mj_MatchedInner = true; /* * 在反连接中,我们从不返回匹配的元组。 * JOIN_RIGHT_ANTI_FULL 不能创建 mergejoin 计划,所以我们在这里不考虑它。 */ if (node->js.jointype == JOIN_ANTI || node->js.jointype == JOIN_LEFT_ANTI_FULL) { node->mj_JoinState = EXEC_MJ_NEXTOUTER; break; } /* * 在半连接中,我们会考虑返回第一次匹配,但之后我们就完成了这个外部元组的处理。 */ if (node->js.jointype == JOIN_SEMI) node->mj_JoinState = EXEC_MJ_NEXTOUTER; qual_result = (other_qual == NIL || ExecQual(other_qual, econtext, false)); MJ_DEBUG_QUAL(other_qual, qual_result); if (qual_result) { /* * 资格鉴定成功。现在形成所需的投影元组,并返回包含它的槽。 */ ExprDoneCond isDone; MJ_printf("ExecMergeJoin: returning tuple\n"); TupleTableSlot* result = ExecProject(node->js.ps.ps_ProjInfo, &isDone); if (isDone != ExprEndResult) { node->js.ps.ps_TupFromTlist = (isDone == ExprMultipleResult); return result; } } else InstrCountFiltered2(node, 1); } else InstrCountFiltered1(node, 1); break; /* * EXEC_MJ_NEXTINNER 表示将内部扫描器前进到下一个元组。 * 如果元组不是 nil,然后我们继续测试它是否符合连接条件。 * * 在前进之前,我们检查是否必须为此内部元组发出外连接填充元组。 */ case EXEC_MJ_NEXTINNER: MJ_printf("ExecMergeJoin: EXEC_MJ_NEXTINNER\n"); if (do_fill_inner && !node->mj_MatchedInner) { /* * 为外部生成具有null的伪联接元组, * 并在它通过非联接quals时返回它。 */ node->mj_MatchedInner = true; /* do it only once */ TupleTableSlot* result = MJFillInner(node); if (result != NULL) return result; } /* * 现在我们获取下一个内部元组(如果有的话)。 * 如果没有,则前进到下一个外部元组(它可能能够连接到先前标记的元组)。 * * 注意:这里不能执行“extraMarks”,因为我们可能需要返回到先前标记的元组。 */ inner_tuple_slot = ExecProcNode(inner_plan); node->mj_InnerTupleSlot = inner_tuple_slot; MJ_DEBUG_PROC_NODE(inner_tuple_slot); node->mj_MatchedInner = false; /* 计算联接值并检查不匹配性 */ switch (MJEvalInnerValues(node, inner_tuple_slot)) { case MJEVAL_MATCHABLE: /* * 测试新的内部元组,看看它是否与外部元组匹配。 * * 如果它们匹配,则将它们连接并转到下一个内部元组(EXEC_MJ_JOINTUPLES)。 * * 如果它们不匹配,则前进到下一个外部元组。 */ compare_result = MJCompare(node); MJ_DEBUG_COMPARE(compare_result); if (compare_result == 0) node->mj_JoinState = EXEC_MJ_JOINTUPLES; else { Assert(compare_result < 0); node->mj_JoinState = EXEC_MJ_NEXTOUTER; } break; case MJEVAL_NONMATCHABLE: /* * 该元组包含一个NULL,因此不能与任何外部元组匹配, * 因此我们可以跳过比较,假定新元组大于当前外部元组。 */ node->mj_JoinState = EXEC_MJ_NEXTOUTER; break; case MJEVAL_ENDOFJOIN: /* * 没有更多的内部元组。然而,这可能只是内部计划的有效结束而不是物理结束,所以强制 mj_InnerTupleSlot 为 null,以确保我们不会获取更多的内部元组。 * (我们需要这个hack,因为我们没有过渡到内部计划被假定已用尽的状态。) */ node->mj_InnerTupleSlot = NULL; node->mj_JoinState = EXEC_MJ_NEXTOUTER; break; default: break; } break; /* * EXEC_MJ_NEXTOUTER 表示 * * outer inner * 外部元组 - 5 5 - 标记的元组 * 5 5 * 6 6 - 内部元组 * 7 7 * * 我们知道我们刚刚碰到了第一个大于当前外部元组的内部元组(或者可能是内部流的结束), * 所以获取一个新的外部元组,然后继续测试它是否与标记的元组匹配(EXEC_MJ_TESTOUTER)。 * * 在推进之前,我们检查是否必须为这个外部元组发出外连接填充元组。 */ case EXEC_MJ_NEXTOUTER: MJ_printf("ExecMergeJoin: EXEC_MJ_NEXTOUTER\n"); if (do_fill_outer && !node->mj_MatchedOuter) { /* * 生成一个具有内部元组的空值的虚拟连接元组,如果通过非连接限定符,则返回它。 */ node->mj_MatchedOuter = true; /* 只做一次 */ TupleTableSlot* result = MJFillOuter(node); if (result != NULL) return result; } /* * 现在我们得到下一个外部元组,如果有的话 */ outer_tuple_slot = ExecProcNode(outer_plan); node->mj_OuterTupleSlot = outer_tuple_slot; MJ_DEBUG_PROC_NODE(outer_tuple_slot); node->mj_MatchedOuter = false; /* 计算联接值并检查不匹配性 */ switch (MJEvalOuterValues(node)) { case MJEVAL_MATCHABLE: /* 根据标记的元组测试新元组 */ node->mj_JoinState = EXEC_MJ_TESTOUTER; break; case MJEVAL_NONMATCHABLE: /* 无法匹配,所以获取下一个外部元组 */ node->mj_JoinState = EXEC_MJ_NEXTOUTER; break; case MJEVAL_ENDOFJOIN: /* 不再有外部元组*/ MJ_printf("ExecMergeJoin: end of outer subplan\n"); inner_tuple_slot = node->mj_InnerTupleSlot; if (do_fill_inner && !TupIsNull(inner_tuple_slot)) { /* * 需要为剩余的内部元组发出右联接元组。 */ node->mj_JoinState = EXEC_MJ_ENDOUTER; break; } /* 否则我们就结束。 */ goto done; default: break; } break; /* * 如果新的外部元组和标记的元组满足合并子句, * 则我们知道在外部扫描中存在重复项,因此我们必须将内部扫描恢复到标记的元组, * 然后继续将新的外部元组与内部元组连接。 * * 这种情况是当 * outer inner * 4 5 - 标记的元组 * outer tuple - 5 5 * new outer tuple - 5 5 * 6 8 - 内部元组 * 7 12 * * 新的外部元组 == 标记的元组 * * 如果外部元组未通过测试,则我们已经完成了标记的元组, * 并且必须寻找与当前内部元组的匹配项。 * 因此,我们将继续跳过外部元组,直到 outer >= inner (EXEC_MJ_SKIP_TEST)。 * * 这种情况是当 * * outer inner * 5 5 - 标记的元组 * outer tuple - 5 5 * new outer tuple - 6 8 - 内部元组 * 7 12 * * 新的外部元组 > 标记的元组 * */ case EXEC_MJ_TESTOUTER: MJ_printf("ExecMergeJoin: EXEC_MJ_TESTOUTER\n"); /* * 在这里,我们必须将外部元组与标记的内部元组进行比较。 * (我们可以忽略 MJEvalInnerValues 的结果,因为标记的内部元组肯定是可匹配的。) */ inner_tuple_slot = node->mj_MarkedTupleSlot; (void)MJEvalInnerValues(node, inner_tuple_slot); compare_result = MJCompare(node); MJ_DEBUG_COMPARE(compare_result); if (compare_result == 0) { /* * 合并子句匹配,所以现在我们将内部扫描位置还原为第一个标记,并继续将该元组(以及任何后续元组)与新外部元组进行连接。 * * 注意:我们不需要担心 rescanned 内部元组的 MatchedInner 状态。 * 我们知道它们都将与这个新外部元组匹配,因此不会作为填充元组返回。 * 这仅在对右连接或全连接进行额外的 joinquals 时有效,因为我们要求额外的 joinquals 在这种情况下是常量。 * 否则,一些 rescanned 元组可能不符合额外的 joinquals,这就明显不会发生在常量为 true 的额外 join_qual 上, * 而常量为 false 的情况则通过强制合并子句永不匹配来处理,因此我们永远不会到达这里。 */ ExecRestrPos(inner_plan); /* * ExecRestrPos 可能会给我们返回一个新的 Slot,但由于它没有这样做,所以使用标记的 Slot。 * (不能假定先前返回的 mj_InnerTupleSlot 包含所需的元组。) */ node->mj_InnerTupleSlot = inner_tuple_slot; /* 我们不需要再次执行MJEvalInnerValues */ node->mj_JoinState = EXEC_MJ_JOINTUPLES; } else { /* ---------------- * 如果新的外部元组与标记的内部元组不匹配, * 则我们有一种情况,如下所示: * * 外部 内部 * 4 4 - 标记的元组 * 新外部 - 5 4 * 6 5 - 内部元组 * 7 * * 这意味着所有随后的外部元组都将大于我们标记的内部元组。 * 因此,我们无需重新访问任何标记的元组,可以继续寻找与当前内部元组匹配的元组。 * 如果没有更多的内部元组,那么不可能再有更多的匹配。 * ---------------- */ Assert(compare_result > 0); inner_tuple_slot = node->mj_InnerTupleSlot; /* 重新加载当前内部的比较数据 */ switch (MJEvalInnerValues(node, inner_tuple_slot)) { case MJEVAL_MATCHABLE: /* 继续将其与当前外部进行比较 */ node->mj_JoinState = EXEC_MJ_SKIP_TEST; break; case MJEVAL_NONMATCHABLE: /* * 当前内部元组不可能与任何外部元组匹配; * 最好前进内部扫描而不是外部。 */ node->mj_JoinState = EXEC_MJ_SKIPINNER_ADVANCE; break; case MJEVAL_ENDOFJOIN: /* 不再有内部元组 */ if (do_fill_outer) { /* * 需要为剩余的外部元组发出左联接元组。 */ node->mj_JoinState = EXEC_MJ_ENDINNER; break; } /* Otherwise we're done. */ goto done; default: break; } } break; /* ---------------------------------------------------------- * EXEC_MJ_SKIP 意味着比较元组,如果它们不匹配, * 则跳过较小的那个。 * * 例如: * * 外部 内部 * 5 5 * 5 5 * 外部元组 - 6 8 - 内部元组 * 7 12 * 8 14 * * 我们必须推进外部扫描,直到找到外部的 8。 * * 另一方面: * * 外部 内部 * 5 5 * 5 5 * 外部元组 - 12 8 - 内部元组 * 14 10 * 17 12 * * 我们必须推进内部扫描,直到找到内部的 12。 * ---------------------------------------------------------- */ case EXEC_MJ_SKIP_TEST: MJ_printf("ExecMergeJoin: EXEC_MJ_SKIP_TEST\n"); /* * 在我们继续之前,请确保当前的元组不满足 merge_clauses。 * 如果满足条件,那么我们更新标记的元组位置并进行连接。 */ compare_result = MJCompare(node); MJ_DEBUG_COMPARE(compare_result); if (compare_result == 0) { ExecMarkPos(inner_plan); if (node->mj_InnerTupleSlot == NULL) { ereport(ERROR, (errcode(ERRCODE_UNEXPECTED_NULL_VALUE), errmsg("mj_InnerTupleSlot cannot be NULL"))); } MarkInnerTuple(node->mj_InnerTupleSlot, node); node->mj_JoinState = EXEC_MJ_JOINTUPLES; } else if (compare_result < 0) node->mj_JoinState = EXEC_MJ_SKIPOUTER_ADVANCE; else /* compare_result > 0 */ node->mj_JoinState = EXEC_MJ_SKIPINNER_ADVANCE; break; /* * SKIPOUTER_ADVANCE:前进到已知不与任何内部元组连接的外部元组。 * * 在前进之前,我们检查是否必须为此外部元组发出外连接填充元组。 */ case EXEC_MJ_SKIPOUTER_ADVANCE: MJ_printf("ExecMergeJoin: EXEC_MJ_SKIPOUTER_ADVANCE\n"); if (do_fill_outer && !node->mj_MatchedOuter) { /* * 生成一个带有内部元组的空值的虚拟连接元组,并且如果它通过了非连接限定条件,则返回它。 */ node->mj_MatchedOuter = true; /* do it only once */ TupleTableSlot* result = MJFillOuter(node); if (result != NULL) return result; } /* * 现在我们得到下一个外部元组,如果有的话 */ outer_tuple_slot = ExecProcNode(outer_plan); node->mj_OuterTupleSlot = outer_tuple_slot; MJ_DEBUG_PROC_NODE(outer_tuple_slot); node->mj_MatchedOuter = false; /* 计算联接值并检查不匹配性 */ switch (MJEvalOuterValues(node)) { case MJEVAL_MATCHABLE: /* 用新元组测试当前内部元组 */ node->mj_JoinState = EXEC_MJ_SKIP_TEST; break; case MJEVAL_NONMATCHABLE: /* 无法匹配,所以获取下一个外部元组 */ node->mj_JoinState = EXEC_MJ_SKIPOUTER_ADVANCE; break; case MJEVAL_ENDOFJOIN: /* 不再有外部元组 */ MJ_printf("ExecMergeJoin: end of outer subplan\n"); inner_tuple_slot = node->mj_InnerTupleSlot; if (do_fill_inner && !TupIsNull(inner_tuple_slot)) { /* * 需要为剩余的内部元组发出右联接元组。 */ node->mj_JoinState = EXEC_MJ_ENDOUTER; break; } /* Otherwise we're done. */ goto done; default: break; } break; /* * SKIPINNER_ADVANCE:前进到已知不与任何外部元组连接的内部元组。 * * 在前进之前,我们检查是否必须为此内部元组发出外连接填充元组。 */ case EXEC_MJ_SKIPINNER_ADVANCE: MJ_printf("ExecMergeJoin: EXEC_MJ_SKIPINNER_ADVANCE\n"); if (do_fill_inner && !node->mj_MatchedInner) { /* * 生成一个带有外部元组的空值的虚拟连接元组,并且如果它通过了非连接限定条件,则返回它。 */ node->mj_MatchedInner = true; /* do it only once */ TupleTableSlot* result = MJFillInner(node); if (result != NULL) return result; } /* 如果需要,在前进前做好标记 */ if (node->mj_ExtraMarks) ExecMarkPos(inner_plan); /* * 现在我们得到下一个内部元组,如果有的话 */ inner_tuple_slot = ExecProcNode(inner_plan); node->mj_InnerTupleSlot = inner_tuple_slot; MJ_DEBUG_PROC_NODE(inner_tuple_slot); node->mj_MatchedInner = false; /* 计算联接值并检查不匹配性 */ switch (MJEvalInnerValues(node, inner_tuple_slot)) { case MJEVAL_MATCHABLE: /* 继续将其与当前外部进行比较 */ node->mj_JoinState = EXEC_MJ_SKIP_TEST; break; case MJEVAL_NONMATCHABLE: /* * 当前内部元组不可能与任何外部元组匹配; * 最好前进内部扫描而不是外部。 */ node->mj_JoinState = EXEC_MJ_SKIPINNER_ADVANCE; break; case MJEVAL_ENDOFJOIN: /* 不再有内部元组 */ MJ_printf("ExecMergeJoin: end of inner subplan\n"); outer_tuple_slot = node->mj_OuterTupleSlot; if (do_fill_outer && !TupIsNull(outer_tuple_slot)) { /* * 需要为剩余的外部元组发出左联接元组 */ node->mj_JoinState = EXEC_MJ_ENDINNER; break; } /* Otherwise we're done. */ goto done; default: break; } break; /* * EXEC_MJ_ENDOUTER 意味着我们已经用完了外部元组, * 但正在进行右连接/全连接,因此必须对任何剩余的未匹配内部元组进行空值填充。 */ case EXEC_MJ_ENDOUTER: MJ_printf("ExecMergeJoin: EXEC_MJ_ENDOUTER\n"); Assert(do_fill_inner); if (!node->mj_MatchedInner) { /* * 生成一个带有外部元组的空值的虚拟连接元组,并且如果它通过了非连接限定条件,则返回它。 */ node->mj_MatchedInner = true; /* do it only once */ TupleTableSlot* result = MJFillInner(node); if (result != NULL) return result; } /* 如果需要,在前进前做好标记 */ if (node->mj_ExtraMarks) ExecMarkPos(inner_plan); /* * 现在我们得到下一个内部元组,如果有的话 */ inner_tuple_slot = ExecProcNode(inner_plan); node->mj_InnerTupleSlot = inner_tuple_slot; MJ_DEBUG_PROC_NODE(inner_tuple_slot); node->mj_MatchedInner = false; if (TupIsNull(inner_tuple_slot)) { MJ_printf("ExecMergeJoin: end of inner subplan\n"); goto done; } /* 否则将保持ENDOUTER状态并处理下一个元组。 */ break; /* * EXEC_MJ_ENDINNER 意味着我们已经用完了内部元组, * 但正在进行左连接/全连接,因此必须对任何剩余的未匹配外部元组进行空值填充。 */ case EXEC_MJ_ENDINNER: MJ_printf("ExecMergeJoin: EXEC_MJ_ENDINNER\n"); Assert(do_fill_outer); if (!node->mj_MatchedOuter) { /* * 生成一个带有内部元组的空值的虚拟连接元组,并且如果它通过了非连接限定条件,则返回它。 */ node->mj_MatchedOuter = true; /* do it only once */ TupleTableSlot* result = MJFillOuter(node); if (result != NULL) return result; } /* * 现在我们得到下一个外部元组,如果有的话 */ outer_tuple_slot = ExecProcNode(outer_plan); node->mj_OuterTupleSlot = outer_tuple_slot; MJ_DEBUG_PROC_NODE(outer_tuple_slot); node->mj_MatchedOuter = false; if (TupIsNull(outer_tuple_slot)) { MJ_printf("ExecMergeJoin: end of outer subplan\n"); goto done; } /* 否则将保持ENDINNER状态并处理下一个元组。 */ break; /* * 破坏状态值? */ default: ereport(ERROR, (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE), errmsg("unrecognized mergejoin state: %d", (int)node->mj_JoinState))); } } done: ExecEarlyFree(innerPlanState(node)); ExecEarlyFree(outerPlanState(node)); EARLY_FREE_LOG(elog(LOG, "Early Free: MergeJoin is done " "at node %d, memory used %d MB.", (node->js.ps.plan)->plan_node_id, getSessionMemoryUsageMB())); return NULL; } ———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.youkuaiyun.com/qq_43899283/article/details/134599172
06-14
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值