为什么反汇编后的数据会变成指令?

2023-12-15

我需要一些帮助来理解此时此刻发生的事情 这段代码“发生”:“jmp Begin”。 我只知道 .com 文件可以是 64kb,因此您希望将所有内容放在一个段中。如果你想放置变量,你需要jmp。但是当我搜索它时,很多指南只是在评论中说 jmp Begin 只是跳过数据,没有其他任何内容。这是我的问题: 这一刻到底发生了什么:

enter image description here

看来它运行这个

        mov     al, a
        mov     bl, b
        sub     al, bl

但我不明白为什么它在涡轮调试器中看起来像这样。 当我将 Result 的起始值从 ?对于大于 0 的值,它会更改为其他值,当我将其更改为 90 时,它看起来完全正常。我对组装完全陌生,我似乎根本无法掌握它。这是我的整个代码:

            .MODEL TINY

Code        SEGMENT

            ORG    100h
            ASSUME CS:Code, DS:Code

Start:
                jmp     Begin
a               EQU     20
b               EQU     10
c               EQU     100
d               EQU     5
Result          DB      ?


Begin:

            mov     al, a
            mov     bl, b
            sub     al, bl
            mov     bl, c
            mul     bl
            mov     bl, d
            div     bl              
            mov     Result, al
            mov     ah, 4ch
            int     21h

Code        ENDS
            END             Start

我试着给你一个解释。

问题在于,在过去(今天仍然部分如此),处理器没有区分内存中的代码和数据字节。这意味着 .com 文件中的任何字节都可以用作代码和数据。调试器不知道哪些字节将作为代码执行以及哪些字节将用作数据。在棘手的情况下,字节实际上可以用作代码和数据......您的程序可以在内存中创建作为代码有效的数据,并且您可以跳转到它来执行它。

在许多(但不是全部)情况下,调试器实际上可以找出什么是代码,什么是数据,但是这种代码分析可能会变得非常复杂,因此大多数调试器/反汇编器根本没有这样的代码流分析器。因此,他们只是在文件/内存中选择一个偏移量(这通常是当前指令指针),并从该偏移量开始,将一系列连续字节串行解码为汇编指令不遵循任何jmp指示直到调试器的屏幕完全充满足够数量的反汇编行。愚蠢的反汇编器/调试器不关心反汇编的字节实际上是用作程序中的指令还是数据,它们将它们视为指令。

如果您正在调试程序并且调试器在断点处停止,那么它将获取当前指令指针,并使用原语“填充调试器屏幕”方法从该偏移量开始再次执行哑反汇编。

这种连续字节的串行反汇编是一种在大多数情况下都有效的简单方法。如果您串行解码非jmp指令彼此跟随,您几乎可以确定处理器将按此顺序执行它们。然而,一旦你到达并解码jmp指令您无法确定以下字节作为代码是否有效。但是,您可以尝试将它们解码为指令,希望代码中间没有混合数据(是的,在大多数情况下,在jmp(或类似的控制流指令),这就是为什么调试器给你一个愚蠢的反汇编作为“可能有用的预测”)。事实上,大多数代码通常充满了条件跳转和反汇编它们之后的字节,因为代码对调试器来说是非常有用的帮助。跳转指令后的代码中间有数据的情况非常罕见,我们可以将其视为边缘情况。

假设您有一个简单的 .com 程序,它只是跳过一些数据,然后存在一个int 20h:

    jmp start
    db  90h
start:
    int 20h

反汇编程序可能会通过从偏移量 0000 开始反汇编来告诉您类似以下内容:

--> 0000   eb 01        jmp short 0003
    0002   90           nop
    0003   cd 20        int 20h

酷,这看起来和我们的 asm 源代码一模一样……现在让我们稍微改变一下程序:让我们改变数据……

    jmp start
    db  cdh
start:
    int 20h

现在反汇编程序将向您展示:

--> 0000   eb 01        jmp short 0003
    0002   cd cd        int cdh
    0004   20 ...... whatever...

问题是某些指令由超过 1 个字节组成,调试器并不关心字节是否代表代码或数据。在上面的示例中,如果反汇编器从偏移量 0000 到程序末尾(包括数据)连续反汇编字节,那么您的 1 字节数据将反汇编为 2 字节指令(“窃取”实际代码的第一个字节),因此调试器尝试反汇编的下一条指令将位于偏移量 0004 而不是 0003 处,您的位置jmp正常情况下会跳。在第一个示例中,我们没有遇到这样的问题,因为数据被反汇编为 1 字节指令,并且偶然反汇编程序的数据部分后,调试器要反汇编的下一条指令位于偏移量 0003 处,这正是您的目标jmp.

然而,幸运的是,调试器在这种情况下向您显示的内容并不是程序执行时会发生的情况。通过执行一条指令,程序实际上会跳转到偏移量 0003,调试器将再次执行愚蠢的反汇编,但这次从偏移量 0003 开始​​,该偏移量位于上一个错误反汇编指令的中间...

假设您调试第二个示例程序并逐一执行其中的所有指令。当您使用指令指针 == 0000 启动程序时,调试器会显示以下内容:

--> 0000   eb 01        jmp short 0003
    0002   cd cd        int cdh
    0004   20 ...... whatever...

然而,当您触发“step”命令来执行一条指令时,指令指针(IP)将更改为 0003,并且调试器从偏移量 0003 再次执行“哑反汇编”,直到调试器屏幕被填满,因此您将看到以下内容:

--> 0003   cd 20      int 20h
    0005   ...... whatever...

结论:如果您有愚蠢的反汇编程序,并且将数据混合到代码中间(使用jmps 围绕数据),那么愚蠢的反汇编程序会将您的数据视为代码,这可能会导致您遇到的“小”问题。

具有流分析功能的高级反汇编程序(如 Ida Pro)将按照跳转指令进行反汇编。拆解你的后jmp在偏移量 0000 处,它会发现下一条要反汇编的指令是jmp在 0003 处,它会拆卸int 20h作为下一步。它将标记db cdh偏移量 0002 处的字节作为数据。

补充说明:

正如您已经注意到的(相当过时的)8086 指令集中的指令可以是 1-6 个字节长之间的任何位置,但jmp or call可以以字节粒度跳转到内存中的任何位置。指令的长度通常可以根据指令的前 1 或 2 个字节来确定。然而,仅当处理器以其特殊IP(指令指针寄存器)定位指令的第一个字节并尝试在给定偏移处执行字节时,字节才会“粘在一起”到指令中。让我们看一个棘手的例子:内存中偏移量 0000 处有字节 eb ff 26 05 00 03 00,然后逐步执行它。

--> 0000   eb ff        jmp short 0001
    0002   26 05 00 03  es: add ax, 300h
    0006   00 ...... whatever...

处理器指令指针 (IP) 指向偏移量 0000,因此它对指令进行解码,并且其中的字节在执行时“粘在一起形成一条指令”。 (处理器在 0000 处执行指令解码。)由于第一个字节是 eb,因此它知道指令长度是 2 个字节。调试器也知道这一点,因此它会为您解码指令,并根据错误的假设生成一些额外的错误反汇编,即处理器在某些时候会在偏移量 0002 处执行指令,然后在偏移量 0006 处执行指令,等等...你会发现这不是真的,处理器会将字节以完全不同的偏移量组合成指令。

正如你所看到的,我棘手的字节代码包含一个jmp跳转到偏移量0001,即执行的中间位置jmp指令本身!!!然而,这根本不是问题。处理器并不关心它,而是愉快地跳转到偏移量 0001,因此下一步它将尝试解码那里的指令(或“将字节粘在一起”)。让我们看看处理器会在0001处找到什么样的指令:

--> 0001   ff 26 05 00  jmp word ptr [5]
    0005   03 00        add ax, word ptr [bx+si]

正如你所看到的,我们的下一条指令位于 0001,调试器向我们展示了偏移量 0005 处的一些垃圾反汇编,这是基于处理器将在某个时刻到达该偏移量的错误假设......

0001 处的指令告诉处理器从偏移量 0005 处拾取一个字,并将其解释为跳转到那里的偏移量。正如你看到的价值word ptr [5]是 3(作为小端 16 位值),因此处理器将 3 放入其 IP 寄存器(跳转到 0003)。让我们看看它在偏移 0003 处找到了什么:

--> 0003   05 00 03     add ax, 300h

以调试器的方式显示我棘手的字节代码 eb ff 26 05 00 03 00 的反汇编是很困难的,因为处理器执行的实际指令位于重叠的内存区域中。处理器首先执行字节0000-0001,然后执行0001-0004,最后执行0003-0005。

在一些较新的 RISC 架构中,指令的长度是固定的,它们必须位于对齐的内存区域上,并且不可能跳转到任何地方,因此调试器的工作比 x86 的情况要容易得多。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为什么反汇编后的数据会变成指令? 的相关文章

  • 为什么我可以使用 ret 退出 main?

    我即将弄清楚程序堆栈到底是如何设置的 我了解到用以下方式调用该函数 call pointer 实际上等同于 mov register pc programcounter add register 1 where 1 is one instr
  • 汇编指令陷阱有什么作用?

    当程序需要时 程序通常会发出软件陷阱 由操作系统提供服务 通用异常处理程序 操作系统确定陷阱的原因并做出响应 适当地 汇编指令 trap 和 BASIC 中的 TRAP 指令一样吗 答案似乎是肯定的 你能接受还是拒绝我的结论 不中断 的代码
  • 遍历内存编辑每个字节

    我正在编写汇编代码 提示用户输入一串小写字符 然后输出包含所有大写字符的相同字符串 我的想法是迭代从特定地址开始的字节 并从每个字节中减去 20H 将小写变为大写 直到到达具有特定值的字节 我对 Assembly 相当缺乏经验 所以我不确定
  • 预取双类成员需要转换为 char*?

    我有一个正在使用的课程 mm prefetch 预先请求包含 double 类型的类成员的缓存行 class MyClass double getDouble return dbl other members double dbl othe
  • 为 Visual Studio 应用程序设置平台目标的目的是什么?

    对于任何 VS 项目 都可以在该项目的构建属性中设置平台目标 您可以将其设置为任何 CPU x86 x64 或 Itanium 我的问题是 如果我将此值设置为 x86 是否意味着我无法在 x64 计算机上运行该项目 如果是这样 为什么还要使
  • 即使我确实为变量设置了初始值,数据段也没有被初始化

    我已经编写了一个代码 该代码应该生成某种数字列表 但是即使我为它们分配了初始值 我的数据段变量也没有被初始化 This is how DS 0000 looks when I run it 这是我的代码 但数据段只保留垃圾值 MODEL s
  • 如何让c代码执行hex机器代码?

    我想要一个简单的 C 方法能够在 Linux 64 位机器上运行十六进制字节码 这是我的 C 程序 char code x48 x31 xc0 include
  • 为什么不能执行 mov [eax], [ebx] [重复]

    这个问题在这里已经有答案了 我可以做这个 mov eax ebx 和这个 mov eax ebx 甚至这个 mov eax ebx 但不是这个 错误C2415 mov eax ebx 只是wtf 为什么 它与 ptr1 ptr2 相同 为什
  • 在 x86 Intel VT-X 非根模式下,是否可以在每个指令边界传递中断?

    除了不将中断传送到虚拟处理器的某些正常指定条件 cli if 0 等 之外 客户机中的所有指令实际上都是可中断的吗 也就是说 当传入的硬件中断先传递给 LAPIC 然后传递给处理器时 据说会发生一些内部魔法 将其转换为虚拟中断给来宾 使用虚
  • NASM 轮班操作员

    您将如何在寄存器上进行 NASM 中的位移位 我读了手册 它似乎只提到了这些操作员 gt gt lt lt 当我尝试使用它们时 NASM 抱怨移位运算符处理标量值 您能解释什么是标量值并举例说明如何使用 gt gt and lt lt 另外
  • Visual Studio 2017 上的简单装配程序

    386 model flat c stack 100h printf PROTO arg1 Ptr Byte data msg1 byte Hello World 0Ah 0 code main proc INVOKE printf ADD
  • 奇怪的 MSC 8.0 错误:“ESP 的值未在函数调用中正确保存...”

    我们最近尝试将一些 Visual Studio 项目分解为库 并且在测试项目中一切似乎都编译和构建得很好 其中一个库项目作为依赖项 然而 尝试运行该应用程序给我们带来了以下令人讨厌的运行时错误消息 运行时检查失败 0 ESP 的值未在函数调
  • 为什么 Visual Studio 使用 xchg ax,ax

    我正在查看程序的反汇编 因为它崩溃了 并注意到很多 xchg ax ax 我用谷歌搜索了一下 发现它本质上是一个 nop 但是为什么 Visual Studio 会执行 xchg 而不是 noop 该应用程序是一个C NET3 5 64位应
  • 在linux x86平台上学习ARM所需的工具[关闭]

    Closed 此问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 我有一个 x86 linux 机器 在阅读一些关于 ARM 的各种信息时 我很好奇 现在我想花一些时间学
  • 汇编8086监听键盘中断

    我有与此完全相同的问题 边画边听键盘 https stackoverflow com questions 13970325 8086 listen to keyboard while drawing 但第一个答案 接受的答案 只听键盘一次
  • 为什么在强度降低乘法和循环进位加法之后,这段代码的执行速度会变慢?

    我正在读书阿格纳 雾 https en wikipedia org wiki Agner Fog s 优化手册 https en wikipedia org wiki Agner Fog Optimization 我遇到了这个例子 doub
  • “rep stos”x86 汇编指令序列有什么作用?

    我最近偶然发现了以下汇编指令序列 rep stos dword ptr edi For ecx重复 存储内容eax到哪里edi指向 递增或递减edi 取决于方向标志 每次 4 个字节 通常 这用于memset型操作 通常 该指令简单地写成r
  • AVX-512CD(冲突检测)与原子变量访问有何不同?

    所以我在看他们展示了如何 void Histogram const float age int const hist const int n const float group width const int m const float o
  • 如何在 Linux x86_64 上模拟 iret

    我正在编写一个基于 Intel VT 的调试器 由于当 NMI Exiting 1 时 iret 指令在 vmx guest 中的性能发生了变化 所以我应该自己处理vmx主机中的NMI 否则 guest会出现nmi可重入错误 我查了英特尔手
  • “mov (%ebx,%eax,4),%eax”如何工作? [复制]

    这个问题在这里已经有答案了 一直在从事装配作业 并且在很大程度上我对装配非常了解 或者至少对于这项任务来说足够好 但这个 mov 的声明让我很困惑 如果有人能解释这个 mov 语句如何操作寄存器值 我将非常感激 mov ebx eax 4

随机推荐