31

所以今天早上我发布了一个关于装配的困惑问题,我得到了一些非常真诚的帮助,我真的很感激。

现在我开始着手组装并开始了解它是如何工作的。

我觉得我理解的东西包括堆栈、中断、二进制/十六进制,以及大多数基本操作的一般情况(jmp、push、mov 等)。

我正在努力理解并希望得到帮助的概念如下 - 如果您能解决以下任何问题,这将是一个巨大的帮助:

  1. .data 部分到底发生了什么?是我们声明的那些变量吗?
  2. 如果是这样,我们可以稍后在代码部分声明变量吗?如果不是,为什么不呢?如果是这样,那么我们如何以及为什么使用数据部分?
  3. 什么是寄存器?它与变量相比如何?我的意思是我知道这是一个存储一小部分信息的位置……但这对我来说听起来就像一个变量。
  4. 如何制作数组?我知道这似乎有点随机,但我很好奇我将如何去做这样的事情。
  5. 是否有一个关于每个寄存器应该用于什么的常见做法列表?我仍然没有完全理解它们,但注意到有些人说,例如,应该使用某个寄存器来存储来自过程的“返回值”——是否有一个全面的或至少是信息丰富的此类做法列表?
  6. 我学习汇编的原因之一是为了更好地理解我的高级代码背后发生了什么。考虑到这一点——当我用 C++ 编程时,我经常考虑堆栈和堆。在汇编中,我知道堆栈是什么——“堆”在哪里?

一些信息:我将 masm32 与 WinAsm 一起用作 IDE,并且我正在使用 Windows 7。我以前有很多使用高级语言(如 c++/java)进行编程的经验。


编辑:感谢大家的帮助,像往常一样提供丰富的信息!好东西!最后一件事——我想知道堆栈指针和基指针或 ESP 和 EBP 之间有什么区别。有人可以帮我吗?

编辑:我想我现在明白了...... ESP 总是指向堆栈的顶部。但是,您可以将 EBP 指向任何您想要的位置。ESP 是自动处理的,但您可以使用 EBP 做任何您想做的事情。例如:

push 6
push 5
push 4
mov EBP, ESP
push 3
push 2

在这种情况下,EBP 现在指向持有 4 的地址,但 ESP 现在指向持有 2 的地址。

在实际应用中,6、5 和 4 可能是函数参数,而 3 和 2 可能是该函数中的局部变量。

4

2 回答 2

33

让我们尝试按顺序回答!

  1. 数据部分包含您希望系统在调用程序入口点之前自动为您初始化的任何内容。你是对的,通常全局变量在这里结束。零初始化数据通常不包含在可执行文件中,因为没有理由 - 程序加载器的几个指令就是生成该空间所需的全部。一旦您的程序开始运行,ZI 和数据区域通常可以互换。 维基百科有更多的信息。

  2. 在汇编编程时变量并不真正存在,至少在编写 C 代码时不存在。你所拥有的只是你做出的关于如何布置你的记忆的决定。变量可以在堆栈上、内存中的某个位置,或者只存在于寄存器中。

  3. 寄存器是处理器的内部数据存储。通常,您只能对处理器寄存器中的值进行操作。您可以在内存中加载和存储它们的内容,这是计算机工作的基本操作。这是一个简单的例子。这个 C 代码:

    int a = 5;
    int b = 6;
    int *d = (int *)0x12345678; // assume 0x12345678 is a valid memory pointer
    *d = a + b;
    

    可能会被翻译成一些(简化的)程序集,如下所示:

    load  r1, 5
    load  r2, 6
    load  r4, 0x1234568
    add   r3, r1, r2
    store r4, r3
    

    在这种情况下,您可以将寄存器视为变量,但一般情况下,任何一个变量都没有必要始终保留在同一个寄存器中。根据您的例程有多复杂,它甚至可能是不可能的。您需要将一些数据压入堆栈,将其他数据弹出,等等。“变量”是逻辑数据,而不是它存在于内存或寄存器等中的位置。

  4. 数组只是一个连续的内存块 - 对于本地数组,您可以适当地减少堆栈指针。对于全局数组,您可以在数据部分中声明该块。

  5. 有很多关于寄存器的约定——查看你平台的 ABI 或调用约定文档,了解如何正确使用它们的详细信息。您的汇编程序文档也可能包含信息。检查维基百科上的 ABI 文章

  6. 您的汇编程序可以进行与任何 C 程序相同的系统调用,因此您只需调用malloc()即可从堆中获取内存。

于 2010-03-01T01:00:02.240 回答
18

我想补充一下。计算机上的程序通常分为三个部分,尽管还有其他部分。

代码段 - .code、.text:http ://en.wikipedia.org/wiki/Code_segment

在计算中,代码段,也称为文本段或简称为文本,是一个短语,用于指代内存的一部分或包含可执行指令的目标文件的一部分。它具有固定大小,通常是只读的。如果文本部分不是只读的,则特定架构允许自修改代码。如果只读代码可以由多个进程同时执行,则它是可重入的。作为内存区域,代码段位于内存的较低部分或最底部,以防止堆和堆栈溢出覆盖它。

数据段 - .data:http ://en.wikipedia.org/wiki/Data_segment

数据段是目标文件或内存中程序的部分之一,它包含由程序员初始化的全局变量和静态变量。它有一个固定的大小,因为这部分中的所有数据都是在程序加载之前由程序员设置的。但是,它不是只读的,因为变量的值可以在运行时更改。这与 Rodata(常量、只读数据)部分以及代码段(也称为文本段)形成对比。

BSS:http ://en.wikipedia.org/wiki/.bss

在计算机编程中,.bss 或 bss(最初代表 Block Started by Symbol)被许多编译器和链接器用作数据段的一部分的名称,其中包含仅用零值数据填充的静态变量和全局变量最初(即执行开始时)。它通常被称为“bss 部分”或“bss 段”。程序加载器在加载程序时初始化为 bss 段分配的内存。

正如其他人所描述的,寄存器是 CPU 存储数据或内存地址的工具。操作是在寄存器上执行的,例如add eax, ebx并取决于汇编方言,这意味着不同的事情。在这种情况下,这转换为将 ebx 的内容添加到 eax 并将其存储在 eax 中(NASM 语法)。GNU AS (AT&T) 中的等价物是:movl $ebx, $eax. 不同的组装方言有不同的规则和运算符。由于这个原因,我不是 MASM 的粉丝——它与 NASM、YASM 和 GNU AS 都非常不同。

与 C 并没有真正的一般交互。 ABI 指定这是如何发生的;例如,在 x86 (unix) 上,您会发现方法的参数被压入堆栈,而在 Unix 上的 x86-64 中,前几个参数将位于寄存器中。两个 ABI 都希望函数的结果存储在 eax/rax 寄存器中。

这是一个为 Windows 和 Linux 组装的 32 位添加例程。

_Add
    push    ebp             ; create stack frame
    mov     ebp, esp
    mov     eax, [ebp+8]    ; grab the first argument
    mov     ecx, [ebp+12]   ; grab the second argument
    add     eax, ecx        ; sum the arguments
    pop     ebp             ; restore the base pointer
    ret

在这里,你可以明白我的意思。“返回”值在 eax 中找到。相比之下,x64 版本看起来像这样:

_Add
    push    rbp             ; create stack frame
    mov     rbp, rsp
    mov     eax, edi        ; grab the first argument
    mov     ecx, esi        ; grab the second argument
    add     eax, ecx        ; sum the arguments
    pop     rbp             ; restore the base pointer
    ret

有一些文件定义了这种事情。这是 UNIX x64 ABI:http ://www.x86-64.org/documentation/abi-0.99.pdf 。我相信您可能会找到适用于您需要的任何处理器、平台等的 ABI。

如何在汇编中对数组进行操作?指针算术。如果整数大小为 4 个字节,则给定下eax一个存储整数的基地址。[eax+4]您可以使用对 malloc/calloc 的调用来创建此空间,或者调用内存分配系统调用,无论您的系统上有什么。

什么是“堆”?再次根据维基百科,它是为动态内存分配保留的内存区域。在您调用 calloc、malloc 或内存分配系统调用之前,您不会在汇编程序中看到它,但它就在那里。

对不起这篇文章。

于 2010-03-01T01:58:00.030 回答