汇编语言 - .data、.code 和寄存器是什么?

35
今早我发了一个有关汇编的困惑问题,并得到了一些真正非常好的帮助,我真的很感激。现在我开始涉及汇编,开始理解它的工作原理。我觉得我理解得还可以的包括堆栈、中断、二进制/十六进制以及大多数基本操作的含义(jmp、push、mov等)。我现在正在努力理解的概念并希望得到帮助的是:1.数据段(data)中到底发生了什么?我们在声明变量吗?2.如果是这样,我们能否在代码段中稍后声明变量?如果不行,为什么?如果能,又该如何声明,为什么要使用数据段呢?3.什么是寄存器?与变量相比如何?我的意思是,我知道它是存储小块信息的位置...但对我来说这听起来就像是一个变量。4.如何创建数组?我知道这似乎有点随机,但我很好奇我应该如何做类似这样的事情。5.是否有一份列出每个寄存器应该用于什么的通用实践列表?我仍然不完全理解它们,但注意到有些人说,例如,某个寄存器应该用于存储过程的“返回值”——是否有这样的实践的全面或至少信息丰富的列表?6.我学习汇编的一个原因是为了更好地理解高级代码背后发生的事情。考虑到这一点,在我编写c++时,经常会考虑堆栈和堆。在汇编中,我知道堆栈在哪里——“堆”在哪里呢? 我使用WinAsm作为IDE,正在Windows 7上开发,以masm32为基础。我有很多以前在高级语言(如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可能是该函数内的局部变量。

我可以问一下你是否有书,如果有的话,可能是哪一本书呢? :) - user1831086
不好意思,只有互联网。https://dev59.com/fHE95IYBdhLWcg3wV8e_ --> 在那篇文章中,我列出了我一直在使用的资源。我今天才开始学习汇编语言,所以我会继续更新这个列表,收集更多的资源。 - Cam
我也开始学习汇编语言了。有一本免费的PDF书叫做“从基础开始编程”,看起来很不错。目前这是我的主要学习资源。 - morgancodes
2个回答

37

让我们按顺序尝试回答!

  1. 数据段包含任何你希望在程序调用入口点之前由系统自动初始化的内容。你是对的,通常全局变量会在这里定义。零初始化数据通常不包含在可执行文件中,因为没有必要-只需要向程序装载器添加几个指令即可生成该空间。一旦程序开始运行,ZI和数据段通常可以互换使用。 Wikipedia 上有更多信息。

  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
    

    在这种情况下,你可以将寄存器看作变量,但一般来说,并不需要任何一个变量始终保留在同一个寄存器中;根据你的程序有多复杂,甚至可能不可能这样做。你需要将一些数据压入堆栈中,弹出其他数据等。'变量'就是那个逻辑数据块,而不是它在内存或寄存器中的位置。

    数组只是一块连续的内存 - 对于本地数组,你可以适当地减少堆栈指针的值。对于全局数组,你可以在数据段声明该块。

    关于寄存器有许多约定 - 有关如何正确使用它们的详细信息,请查阅平台的ABI或调用约定文档。你的汇编器文档可能也有相关信息。请参阅维基百科上的ABI文章

    你的汇编程序可以像C程序一样进行系统调用,因此你可以调用malloc()从堆中获取内存。


+1 再次感谢Carl。非常有帮助!特别是第3个示例很有用。我想,归根结底,汇编必须与C不同,否则它实际上将是C本身。对于#4...我以为堆栈严格按照先进先出的顺序进行。然而,我猜您可以在不推入/弹出的情况下从堆栈中读取吗?此外,我发现这个链接似乎很有用和相关:http://en.wikibooks.org/wiki/X86_Disassembly/Functions_and_Stack_Frames - Cam
2
是的,您可以在不进行push/pop操作的情况下从堆栈中读取数据:您只需要堆栈指针和偏移量。在x86汇编语言中,堆栈指针包含在esp寄存器中,因此您可以使用esp + offset(实际上,如果您反汇编C应用程序,您将看到该方法用于访问函数调用中的“本地”变量)。 - slugster
1
@incrediman,栈只是放在内存中的一切东西之中,所以您可以按照自己的方式访问它。它只是一个栈,用于存储函数调用的执行上下文等内容。 - Carl Norum
首先,在我的原始评论中,我指的是后进先出,而不是先进先出(但现在无法编辑)!其次...感谢@slugster和Carl,这很有道理。另外,我正在阅读我刚刚发布的链接,现在我明白了Carl在第4点中的意思。非常酷。但是,你会如何声明一个全局数组呢?使用malloc吗? - Cam
1
一个存储变量的寄存器就像内存存储变量一样,但是:a)寄存器没有内存地址(对于所有现代处理器都是如此),b)它比内存变量更快地访问和操作,c)它使用较少的指令字节(较短的指令)来寻址,而d)在任何CPU架构中寄存器数量非常少,大约为2、8、16或32个CISC处理器,以及32到128个RISC处理器,而同时可以有数十亿个内存变量。 - wallyk
显示剩余2条评论

21
我想要补充一些内容。计算机上的程序通常分为三个部分,但也有其他部分。
代码段 - .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(最初代表由符号开始的块)被许多编译器和链接器用作包含仅填充零值数据的静态变量和全局变量的一部分的名称(即当执行开始时)。它通常被称为“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)上,您会发现一个方法的参数被推送到堆栈上,而在x86-64上的Unix上,前几个参数将被放置在寄存器中。两种ABI都期望函数的结果存储在eax/rax寄存器中。
这是一个32位加法例程,可以在Windows和Linux上汇编。
_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。我相信你可能会找到任何处理器、平台等所需的ABIs。

如何在汇编语言中操作数组?使用指针算术。如果整数大小为4个字节,则在基地址eax处下一个存储的整数将位于[eax+4]。您可以通过调用malloc/calloc来创建此空间,或者调用内存分配系统调用(在您的系统上),以实现这一点。

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

对于篇幅,很抱歉。


非常有用的信息!不过,UNIX x64 ABI链接现在已经失效了。我认为你提供的内容可以在这里找到,但我不确定它是否是同一份文档。 - Christopher Bradshaw

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接