x86反汇编_深度学习_链表OJ题反汇编

1. 移除链表元素

1.1 反汇编分析

1.2 映射C语言代码

1.3 输出结果

1.4 📝 极简战术卡:单链表定点清除 (Remove Elements)

  • 核心阵型(双指针)

    • cur = head; (探路兵)

    • prev = NULL; (垫后部队,必须置空,严防野指针!)

  • 核心微操(分情况爆破)

    • 命中头节点(重点):

      • head = cur->next; (必须同步后撤大本营!)

      • free(cur);

      • cur = head;

    • 命中中后部:

      • prev->next = cur->next; (跨过废墟)

      • free(cur);

      • cur = prev->next;

    • 未命中(正常行军):

      • prev = cur; cur = cur->next;
  • 终极闭环(情报回传)

    • C语言默认传值调用,函数内部的 head 只是拷贝。

    • 必须在末尾 return head;,让外部主函数重新接收最新的头节点坐标。


2. 链表的中间节点

2.1 反汇编分析

2.2 映射C语言代码

2.3 输出结果

2.4 📝 极简战术卡:寻找链表中间节点 (Middle Node)

2.4.1. 核心战术(快慢指针联合侦察)

  • slow = head; (探路兵:每次步长 1 节点)

  • fast = head; (侦察机:每次步长 2 节点)

  • 物理定论: 侦察机撞线(到达末尾)时,探路兵必在绝对中点。

2.4.2 绝命雷区与绝对防御(while 循环条件)

  • 💥 死亡阵型(引发段错误): while(fast->next != NULL && fast != NULL)

    • 死因: 偶数节点下,fast 变 NULL 后,强行读取 fast->next 会瞬间踩爆空指针!
  • 🛡️ 金身防御(标准答案): while(fast != NULL && fast->next != NULL)

    • 原理: 利用 C 语言 &&短路求值(Short-Circuit) 特性。把 fast != NULL 这个盾牌顶在最前面,一旦为假,直接短路跳出,后半句绝不执行!

2.4.3 边界防弹衣(特殊地形拦截)

  • 极度缺员(空链表): if (head == NULL) return NULL;

  • 孤军奋战(单节点): if (head->next == NULL) return head; (防患于未然,直接返回头节点)。

2.4.4 阵型结算(奇偶差异)

  • 奇数节点(如 7 个): fast 停在最后一个节点(fast->next == NULL 跳出)。slow 停在正中间(第 4 个)。

  • 偶数节点(如 6 个): fast 飞出边界停在废墟(fast == NULL 跳出)。slow 停在下半区的第一个(第 4 个)。


3. 找到链表倒数第N个节点

3.1 反汇编分析

3.2 映射C语言代码

3.3 输出结果

3.4 📝 极简战术卡:寻找倒数第 N 个节点 (The_last_N_nodes)

3.4.1 核心阵型("游标卡尺"战术)

  • 创建两名同起点的士兵: fast = head; slow = head;

  • 战术本质: 制造一个长度绝对为 N 的真空地带,然后整体平移。

3.4.2 绝对防御装甲(防崩溃指南)

  • 防"神经病指令": if (num <= 0) return NULL; (拦截无效参数)。

  • 防"敌后空降(N > 链表总长)": 在 for 循环突进时,必须紧盯先锋状态:if (fast == NULL) break;

    突进结束后,必须进行战损评估: if (i != num) return NULL;(步数没走够说明越界,直接撤退)。

3.4.3 两段式推进微操

  • 第一阶段(造尺子): for 循环fast 独自突进 N 步。

  • 第二阶段(平移卡尺): while (fast != NULL)fastslow 保持 1 步的同频推进。当 fast 踩空坠崖时,slow 所在的坐标就是完美靶心!

3.4.4 逆向战场特别收录:破除工具幻象

  • 敌情: 调试器引擎"自作聪明",导致反汇编错位(如将纯正的 8B 55 FC 解析为残缺的 db 8Bpush ebpcld)。

  • 决断: 果断下达 【从模块中移除分析】 指令!

  • 底层原理: 撕毁错误的启发式标签,迫使工具退化为 "重新线性扫描" ,放弃虚假的语法高亮,直取物理层面的绝对真实(mov edx, [ebp-0x4])!


4. 合并链表

4.1 反汇编分析

4.2 映射C语言代码

4.3 输出结果

4.4 📝 极简战术卡:合并有序链表 (Merge_List)

4.4.1 正向 C 语言战略(核心框架)

  • 神级微操"哨兵节点(Dummy Node)": malloc 一个不存数据的临时头节点(前线指挥所)。战术价值: 彻底免除 "判断谁是第一个节点" 的繁琐逻辑,所有数据直接往它身后排队。

  • 拉链式遍历: while (var_1 && var_2),谁小就挂谁,挂完自己往后挪一步,tail 也紧跟其后。

  • 残部无缝收编: 循环结束后,必有一条链表先空。直接用 tail->next 对准另一条没空的链表头部,瞬间完成整编(不需要再用 while 遍历剩下的节点)。

  • 🔥 避坑排雷(内存泄漏预警): 链表合并后,"原头节点"可能已经沦为小弟。打扫战场时(freeDestroy),必须对准合并后返回的"新头节点"开火,否则会导致堆内存大面积泄漏!

4.4.2 逆向汇编情报(底层真容)

  • 结构体尺寸暴露: push 0x8 配合 call malloc,直接在汇编层暴露了哨兵节点的物理大小(8 字节)。

  • 逻辑与(&&)的连发狙击: C 语言里的 A && B,在底层是极其干脆的连续两枪 cmp + je。一旦发现 0(NULL),立刻顺着相同的跳板脱离战场。

  • 编译器的"反向思维": C 语言写的是 <=,汇编里执行的是 cmp + jg(大于则跳转)。CPU 采用"反向过滤",一旦 var_1 > var_2,立刻踢给 else 分支;否则顺理成章进入 if 分支。

  • 缝合指针的物理本质: 所谓的 tail->next = node,在底层就是一条极其暴力的内存覆写指令:mov dword ptr ds:[ecx+0x4], edx


5. 反转链表

5.1 反汇编分析

5.2 映射C语言代码

5.3 输出结果

5.4 📝 极简战术卡:原地反转单链表 (Reverse_List)

5.4.1 正向 C 语言战略(三车道滑动窗口)

  • 绝对防御装甲: if (phead == NULL || phead->next == NULL)。必须前置拦截空链表和单节点链表,防止后续指针解引用时引发段错误(Segfault)导致全军覆没。

  • 三大特种兵列阵: 殿后卫 prev(初始为 NULL)、中军将 curr(初始为 head)、先锋兵 next(负责探路)。

  • 四步物理微操(循环体):

    • 探路: 先锋前出,咬住下一个节点坐标(防走失)。

    • 转身: 中军过河拆桥,指针反向指向殿后卫。

    • 跟进: 殿后卫向前接替中军的位置。

    • 突进: 中军向前接替先锋的位置。

5.4.2 逆向汇编情报(底层真容)

  • 短路求值(Short-Circuit)的物理真相: 逻辑或 || 在底层是极其冷酷的连续 cmp 和跳跃。只要第一道 NULL 检查命中,CPU 直接执行 je 脱离战场,看都不看第二个条件,极其高效。

  • 指针缝合的暴力美学: 所谓"转身",在汇编里没有任何高级概念,仅仅是一句极其干脆的寄存器覆写指令:mov dword ptr ds:[ecx+0x4], edx

  • 零战损(O(1) 空间复杂度): 整个循环体没有一次 call malloccall free,纯靠通用寄存器(eaxecxedx)在原地把内存坐标颠倒乾坤。


6. 判断回文结构

6.1 反汇编分析

6.2 映射C语言代码

6.3 输出结果

6.4 📝 判断链表回文结构反汇编分析要点总结

6.4.1 内存布局的"特征码"

在分析 SListNode 这种简单结构体时,注意偏移量(Offset):

  • `reg`:通常访问的是结构体的第一个成员(本例中是 data)。

  • `reg + 0x4`:在 32 位程序中,这通常是下一个成员。看到它被赋值给指针变量,基本可以确定是 next 指针。

  • 规律:如果你看到 mov eax, [eax+4] 这种"自己指向自己下一跳 "的指令,这就是标准的链表遍历

6.4.2 函数调用的"意图推断"

当遇到无法直接看到源码的 call 时,通过输入和输出来盲推:

  • call 00401014: 传入头指针,返回一个中间位置的指针 →\rightarrow→ 推断为 GetMiddleNode

  • call 00401041: 传入一个节点,返回一个新链表头 →\rightarrow→ 推断为 ReverseList

6.4.3 💡 逆向思维模型

  • 边界检查: if (head == NULL)

  • 寻找中点: 利用快慢指针或计数。

  • 反转局部: 将后半段链表反转。

  • 双向对冲比较: 从两头(或从中点和头)向中间遍历对比数据。从两头(或从中点和头)向中间遍历对比数据。


7. 找到相交节点

7.1 反汇编分析

7.2 映射C语言代码

7.3 输出结果

7.4 📝 链表_找到相交节点算法反汇编分析总结

7.4.1 算法逻辑:对齐长度法 (The Alignment Method)

  • 这段汇编完美还原了解决相交链表问题的经典 O(n)O(n)O(n) 时间复杂度算法:

  • 第一步: 双链表统计。两个独立的 while 循环,利用 count++ptr = ptr->next 统计长度。

  • 第二步: 尾节点校验。通过 cmp ecx, [ebp-0x10] 判断两个链表的最后一个节点地址是否一致。这是判断相交的必要条件(若末尾不同,必不相交)。

  • 第三步: 长短链表对齐。计算差值 abs,让长链表的指针先行 N 步。

  • 第四步: 同步碰撞。两个指针同时向后走,地址相等时即为交点。

7.4.2 关键汇编特征模式 (Pattern Recognition)

  • 在分析中,以下指令组合具有极高的识别价值:

  • 遍历特征: mov edx, [ecx+0x4] 紧跟 mov [ebp-0xC], edx→\rightarrow→ 这是标准的 p = p->next

  • 地址比较 vs 数据比较:

    • mov edx, [eax] / cmp edx, [ecx] →\rightarrow→ 比较的是节点存的值(data)。

    • cmp eax, ecx →\rightarrow→ 比较的是节点在内存中的地址(指针本身)。

    • 本题的核心就在于地址比较

  • 循环计数器:

    • while 内部看到 add [ebp-X], 1,通常意味着正在统计链表长度或索引

7.4.3 逆向中的变量管理技巧

  • 这道题涉及了约 7 个局部变量var_0x4var_0x1C),在反汇编中跟踪它们需要遵循以下原则:

    • 寄存器缓存:在 Debug 模式 下,由于不开启优化,寄存器(eax, ecx, edx)频繁作为内存变量的"中转站"。

    • 栈帧定位:

      • [ebp+0x8][ebp+0xC] 是外部传入的两个链表头(List1, List2)。

      • [ebp-0x4][ebp-0x8] 是计数器(len1, len2)。

      • [ebp-0x14][ebp-0x18] 是用于对齐和最终遍历的移动指针(pLong, pShort)。

7.4.4 环境线索:为什么代码这么多?

  • 你可能注意到代码中有很多 mov 和跳转,这揭示了编译器的环境:

  • Debug 编译: 使用了 sub esp, 0x5C 预留了大量栈空间,并用 0xCCCCCCCC 初始化。这意味着代码没有经过逻辑精简,保留了所有中间变量。

  • 栈平衡检查: 末尾的 call _chkesp 证明了这是为了防止函数调用导致栈溢出而存在的防御性代码。


8. 判断环形链表

8.1 反汇编分析

8.2 映射C语言代码

8.3 输出结果


9. 找到环点

9.1 思路

思路: 找到相遇点,两个指针一起走,即可走到环点。

以下是论证:

9.2 反汇编分析

9.3 映射C语言代码

9.4 📝 事项总结:Floyd 判圈算法分析要点

  • 快慢步频差: 看到一个指针走一步,另一个指针在同一个循环内通过 [reg+4] 访问两次 next,基本可以锁定是在找环或找中点。

  • 两次循环结构:

    • 第一个循环: 判断是否有环(条件是 ptr1 == ptr2)。

    • 第二个循环: 找入口(条件是 ptr_head == ptr_meeting)。

  • 返回值语义:

    • 返回 0NULL:通常表示链表是线性的(无环)。

    • 返回 eax(节点指针):表示找到了环的入口。


10. 分割链表

10.1 反汇编分析

10.2 映射C语言代码

10.3 输出结果

10.4 🧩 分割链表(Partition List)核心特征总结

10.4.1 哨兵节点的开场布局 (Initialization)

这是最显著的特征。为了简化逻辑,代码通常会创建两个"虚头节点"。

  • 汇编表现: 在函数头连续出现两次 malloc(0x8)(或对应结构体大小的内存分配)。

  • 特征指令:

c 复制代码
push 0x8
call malloc        ; 创建"小于x"链表的哨兵
mov [ebp-0xC], eax
...
push 0x8
call malloc        ; 创建"大于等于x"链表的哨兵
mov [ebp-0x14], eax
  • 逻辑映射: 看到这一对 malloc 就要意识到,程序准备了两条"流水线"来分拣数据。

10.4.2 双路分流的选择逻辑 (The Partition Loop)

在一个大的 while 循环内部,必然存在一个基于目标值(Pivot)的判断。

  • 汇编表现: 取当前节点的 data 值,与参数(通常是 arg_0xC)对比,并根据结果产生两个方向的指针更新。

  • 特征动作:

    • 分支 A: 将当前节点挂载到第一个哨兵链表的尾部。

    • 分支 B: 将当前节点挂载到第二个哨兵链表的尾部。

  • 注意:每个分支最后通常都有一个 mov dword ptr [edx+4], 0,这是为了强行断开原有的物理连接,防止链表成环。

10.4.3 "断桥重连"的桥梁指令 (The Final Connection)

在循环结束后,汇编中会出现一段不属于循环体的"接龙"操作,这是算法的灵魂。

  • 特征指令:
c 复制代码
mov ecx, [ebp-0x10]  ; 取"小链表"的尾巴
mov edx, [ebp-0x14]  ; 取"大链表"的哨兵头
mov eax, [edx+0x4]   ; 取"大链表"的第一个真实节点
mov [ecx+0x4], eax   ; 拼接!
  • 逻辑映射: 这段指令的作用是把"小链表"的尾部指向"大链表"的开头,完成逻辑重组。

10.4.4 现场清理与结果返回 (Cleanup)

由于哨兵节点是用 malloc 申请的临时空间,函数结束前必须销毁它们,否则会内存泄漏。

  • 汇编表现: 连续出现两次 free 调用,释放的对象正是最初那两个哨兵。

  • 返回特征: return 的不再是原始参数,而是第一个哨兵的 next 指针(即 var_0xC->next)。

10.5 💡 逆向心得建议

你在分析时那种"冥冥之中的感觉"其实来源于对数据流向的把握。对于分割链表:

  • 盯住指针的"接力": 如果看到两组指针(一个负责头,一个负责跑)在同时运动,那就是在重组结构。

  • 忽略琐碎的 mov: 在汇编中,为了操作 ebp 偏移,会有很多重复的装载。你只需要抓住核心的 mov edx+4, eax(修改 next 指针),就能看清逻辑。

  • 识别哨兵的意义: 看到 free(var_0xC) 却 return var_0xC->next,这就是标准的哨兵用法------"过河拆桥"。


11. 深拷贝链表

11.1 反汇编分析

11.2 映射C语言代码

11.3 输出结果

11.4 📝 复杂链表深拷贝(Random Pointer)分析总结

11.4.1 构体内存布局的精准识别

在 32 位底层环境下,识别结构体是所有分析的基石:

  • 偏移量特征:通过汇编指令中的 +0, +4, +8 偏移,结合 push 0xC (12字节) 的 malloc 调用,可以推断出节点布局:

    • 0x0: 数据域(val/data)

    • +0x4: 指针域(next)

    • +0x8: 特殊指针域(random)

  • 多级解引用: 看到类似 mov eax, [ecx+8] 紧跟 mov edx, [eax+4],要立刻反应出这是在通过 random 指针寻找它后继的"克隆节点"。

11.4.2 三段式算法模式的识别

这道题在汇编控制流(CFG)中呈现出三个清晰的 while 循环块:

  • 第一阶段:交错插入(Interweaving)

    • 核心动作: Old1 -> New1 -> Old2 -> New2

    • 汇编特征: 循环内频繁调用 malloc,且新节点的 next 指向原节点的 next,原节点的 next 指向新节点。

  • 第二阶段:随机指针校准(Random Alignment)

    • 核心动作: copy->random = cur->random->next

    • 汇编特征: 出现大量的 cmp [reg+8], 0(判空)以及利用"交错步长"进行的指针赋值。这是整个算法中最具辨识度的代码块。

  • 第三阶段:链表拆分(Detaching)

    • 核心动作: 将混合链表拆分为原链表和新链表。

    • 汇编特征: 两个移动指针(var_0x4var_0x18)同步步进,且伴随着 cur->next = copy->next 这种"跳跃式"的指针修改,用于恢复原链表。


本章完~

相关推荐
乾复道15 小时前
k8s使用说明
汇编·程序人生
技术不好的崎鸣同学16 小时前
x64汇编之堆栈工作原理理论篇(继上篇内容讲解)
汇编
是星辰吖~1 天前
X86汇编:复杂逻辑判断_针对性扫盲
汇编
技术不好的崎鸣同学1 天前
x64汇编之用调试器进行程序分析:GDB
汇编
是星辰吖~1 天前
X86反汇编_深度学习:从 C 指针到汇编逻辑
汇编
iCxhust2 天前
c#多串口重量采集上位机程序
开发语言·汇编·c#·微机原理·8088单板机
AI科技星2 天前
万有引力G与真空介电常数ε0全维度完整关系式汇编(基于v=c螺旋时空理论)
c语言·开发语言·前端·javascript·网络·汇编·electron
技术不好的崎鸣同学2 天前
x64汇编之GDB进阶与printf
汇编
是星辰吖~3 天前
X86反汇编:深度学习阶段_2
汇编