如何使用EQU将数字添加到持有nasm中另一个变量大小的变量?

时间:2017-10-09 23:33:15

标签: assembly nasm

嘿嘿,我所有的功课都有一些问题。问题是我为变量分配了一个字符串,使用变量来保存消息变量的大小,然后使用EQU方法创建另一个变量,该变量将5加到包含消息大小的变量中。

编辑: 这是我被分配的问题。我以为我做得对,但是我一直得到一个分段错误(核心转储)让我觉得我搞砸了。 enter image description here

SECTION .data
message:
    DB "You already know what the next", 0Ah
    DB "variable will be, don't you?", 0
length: EQU ($ - message)
length5: EQU (length + 5)

SECTION .text
global _main
_main:
add eax, length5
inc eax

int 80h

1 个答案:

答案 0 :(得分:2)

我决定玩一下源代码,向你展示一些装配原则,以及为什么我不喜欢你的变量"术语用法,加上Jester礼貌地说"为什么作业不是非常明确" 。 (它实际上有点含糊不清,你可以看到我在我的例子中选择了不同的解释)

我使用的第一个来源(我命名为so_nasm_syntax_equ.asm):

SECTION .data
message:        ; these compile to machine code bytes
    DB "You already know what the next", 0Ah
    DB "variable will be, don't you?", 0
length EQU ($ - message)        ; these don't compile to machine code, they define
length5 EQU (length + 5)        ; only constants for assembler during compilation
length5var:
    DB  length5                 ; this will compile as single byte in .data section
length5var2:
    DB  length+5                ; with value of that constant (plus another 5 here)
; meanwhile "length5var" is another constant for assembler, having as value memory
; address of target location where that byte containing the value will land.

SECTION .text
global _start
_start:

    inc byte [length5var]       ; one way to add 1 to the length5var
    add byte [length5var],1     ; another way to add 1 to the length5var
    ; one more way to add one to length5var (this time using two instructions)
    mov eax,1                   ; also demonstrating the aliasing of al/ax/eax/rax
    add [length5var],al         ; being single register, just of different bit size

    ; call sys_exit(0) to terminate correctly
    mov eax,1
    xor ebx,ebx
    int 80h

    ; you can't add to constant anything
    ; this will try to increment value in memory at address 0x41, leading to crash
    inc byte [length5]          ; as that memory address doesn't belong to the .data

    rett                ; warnings test
    ; vs
    ret

要编译它我使用(64b" neon" linux发行版使用):

nasm -w+all so_nasm_syntax_equ.asm -l so_nasm_syntax_equ.lst -f elf32
ld -m elf_i386 so_nasm_syntax_equ.o -o so_nasm_syntax_equ

编译输出:

so_nasm_syntax_equ.asm:33: warning: label alone on a line without a colon might be in error

产生了列表文件,我将最终与评论/解释交错:

 1                                  SECTION .data

第一个在线号码是行号。

 2                                  message:        ; these compile to machine code bytes
 3 00000000 596F7520616C726561-         DB "You already know what the next", 0Ah
 4 00000009 6479206B6E6F772077-
 5 00000012 68617420746865206E-
 6 0000001B 6578740A           
 7 0000001F 7661726961626C6520-         DB "variable will be, don't you?", 0
 8 00000028 77696C6C2062652C20-
 9 00000031 646F6E277420796F75-
10 0000003A 3F00

行号后的8位六进制数是"地址" (偏移到内存中),下面的六位数对的跟踪是最终的机器代码,即要存储在可执行文件中的字节值,稍后由OS加载到内存中,为其初始化和准备环境,最后通过跳转执行它到入口点。尾随" - "在字节值中只标记该行的机器代码未完成,并在下一行继续。

注意行2 message:本身没有产生任何机器代码。所有这一切都是创建符号 message,它在编译期间可用于汇编程序(或者当您将特定符号声明为 global <时也可用于链接器) / em>的)。此处符号message的值为0x00000000 =第一个字节的内存地址偏移量,其值为0x59,等于UTF8编码中的字母'Y'(以及用ASCII编码)。

您无法从该message符号中扣除任何其他内容,不知道在其后定义了多少字节,或者在DB之后使用message指令等,{{1本身就像内存地址,仅此而已。这就是为什么我不喜欢单词&#34;变量&#34;在汇编中,例如在C / C ++中的变量要多得多,不仅它们指向已分配空间的第一个字节,而且编译器也知道变量的类型以及它的总分配大小,并在表达式。汇编程序没有这个,message = 0x00000000并且完全没有。

11                                  length EQU ($ - message)        ; these don't compile to machine code, they define
12                                  length5 EQU (length + 5)        ; only constants for assembler during compilation

这里我用EQU指令定义了两个常量,现在是length = 0x3Clength5 = 0x41,但是它们永远不会到达二进制文件,它们在编译剩余行时只能看到NASM这个源代码。

13                                  length5var:
14 0000003C 41                          DB  length5                 ; this will compile as single byte in .data section

以下是用于定义单字节值的length5常量,由另一个符号length5var = 0x0000003C指向。

15                                  length5var2:
16 0000003D 41                          DB  length+5                ; with value of that constant (plus another 5 here)

这是定义的另一个字节,这次使用常量length和算术表达式(常数+5),可以在编译期间进行评估,因此它将再次生成可执行文件中的0x41值。我还在其前面定义了另一个标签,因此length5var2常量等于0x0000003D

17                                  ; meanwhile "length5var" is another constant for assembler, having as value memory
18                                  ; address of target location where that byte containing the value will land.
19                                  
20                                  SECTION .text
21                                  global _start
22                                  _start:

此处_start值定义为0x00000000部分的偏移.textmessage在此列表中具有相同的偏移量,但它与.data部分相关操作系统将二进制文件加载到内存后,两者将以不同的值结束,并在加载过程中将其重定位到OS分配的目标地址。)

同样_startglobal,因此链接器可以在.o文件中找到它并在链接过程中使用它(为OS加载器标记应用程序的正确入口点)

23                                  
24 00000000 FE05[3C000000]              inc byte [length5var]       ; one way to add 1 to the length5var
25 00000006 8005[3C000000]01            add byte [length5var],1     ; another way to add 1 to the length5var

这些应该是自我解释的,只需检查length5var地址0x0000003C如何成为指令机器代码的一部分(在其原始0x0000003C值中,操作系统在执行之前加载二进制文件时将重定位到正确的最终地址。

26                                      ; one more way to add one to length5var (this time using two instructions)
27 0000000D B801000000                  mov eax,1                   ; also demonstrating the aliasing of al/ax/eax/rax
28 00000012 0005[3C000000]              add [length5var],al         ; being single register, just of different bit size

这是另一种向内存值添加1的方法,这次使用寄存器al作为值1的来源进行添加,并且还使用al寄存器添加{{ 1}}指令汇编程序能够扣除内存操作数大小,因此我不必在add之前添加byte,因为[length5var] 字节大小。 al当然等于al,因为我将整个32位1加载到值eax,而1是{8}的最低8位的别名{1}},它们也等于值al

eax

这将终止代码,实际上是来自外部的唯一可见效果(正确终止而不会崩溃)。要查看这些1指令的实际运行情况,您可以使用调试器并单步调试它们,验证内存值是否从29 30 ; call sys_exit(0) to terminate correctly 31 00000018 B801000000 mov eax,1 32 0000001D 31DB xor ebx,ebx 33 0000001F CD80 int 80h 变为inc/add(以及从第二次添加等的0x42到0x43等) )。

0x41

但是如果您尝试以相同的方式使用该0x42常量,则NASM将仅使用数值34 35 ; you can't add to constant anything 36 ; this will try to increment value in memory at address 0x41, leading to crash 37 00000021 FE0541000000 inc byte [length5] ; as that memory address doesn't belong to the .data 替换length5符号并将其编译为{{1这被理解为绝对寻址,试图访问绝对地址length5的内存(不会重新定位)。

实际上这表明0x41inc byte [0x41]不是等同的符号常量,0x41编译为&#34;地址0x00&#34;,使NASM意识到它链接到message部分,并将其与根据需要生成重定位数据一起使用,而length5只是纯数值message。当您将其用作地址时,它将不会被重新定位,并且将访问绝对地址.data(如果应用程序不会被之前的length5终止,则会导致崩溃)。

机器码中经过重定位的值在机器码中用0x41标记,将第二个0x41机器码与前一个机器码进行比较。操作码int 80h相同,编码值[]inc不同,但那些FE05标记(不属于该特定位置的机器代码,只是标记在列表中为列表文件的读者标记)意味着,NASM +链接器将为OS生成附带的重定位表,它将知道在二进制文件加载到内存后用实际的最终地址修补哪些代码字节。

因此,如果您在准备执行时检查在调试器中反汇编的二进制文件,则第一个0x3C操作码0x41将看起来像[](我的操作系统在调试器下加载了二进制文件以这种方式,inc在内存中的地址FE05[3C000000]结束。第二个FE05E4900408操作码仍然是length5var(OS加载器没有对此进行重定位)。

0x80490e4

这只是关于没有冒号的标签警告的测试。如果使用得当,这可以帮助您在指令中捕获拼写错误,同时您可以通过始终在标签后使用冒号来区分指令与标签。因此,inc不会产生警告,但您在源中看到的不是指令,而是标签。

如果没有警告,FE0541000000会自动转换为标签,如果是错字(而不是38 39 rett ; warnings test 40 ; vs 41 00000027 C3 ret 指令),则rett:机器代码操作码为rett指令将在代码中丢失,产生意外的代码行为。上面的示例确实只生成单个ret操作码,用于正确的0xC3

要完成基于汇编语言使用的基本内容的完整之旅,在应用ret删除一些无用的符号(调试)信息之后,这就是可执行二进制内容的外观:

0xC3

在偏移ret处,您可以看到strip so_nasm_syntax_equ操作码已被链接器重定位到$ strip so_nasm_syntax_equ $ hd -v so_nasm_syntax_equ 00000000 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 |.ELF............| 00000010 02 00 03 00 01 00 00 00 80 80 04 08 34 00 00 00 |............4...| 00000020 00 01 00 00 00 00 00 00 34 00 20 00 02 00 28 00 |........4. ...(.| 00000030 04 00 03 00 01 00 00 00 00 00 00 00 00 80 04 08 |................| 00000040 00 80 04 08 a8 00 00 00 a8 00 00 00 05 00 00 00 |................| 00000050 00 10 00 00 01 00 00 00 a8 00 00 00 a8 90 04 08 |................| 00000060 a8 90 04 08 3e 00 00 00 3e 00 00 00 06 00 00 00 |....>...>.......| 00000070 00 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000080 fe 05 e4 90 04 08 80 05 e4 90 04 08 01 b8 01 00 |................| 00000090 00 00 00 05 e4 90 04 08 b8 01 00 00 00 31 db cd |.............1..| 000000a0 80 fe 05 41 00 00 00 c3 59 6f 75 20 61 6c 72 65 |...A....You alre| 000000b0 61 64 79 20 6b 6e 6f 77 20 77 68 61 74 20 74 68 |ady know what th| 000000c0 65 20 6e 65 78 74 0a 76 61 72 69 61 62 6c 65 20 |e next.variable | 000000d0 77 69 6c 6c 20 62 65 2c 20 64 6f 6e 27 74 20 79 |will be, don't y| 000000e0 6f 75 3f 00 41 41 00 2e 73 68 73 74 72 74 61 62 |ou?.AA..shstrtab| 000000f0 00 2e 74 65 78 74 00 2e 64 61 74 61 00 00 00 00 |..text..data....| 00000100 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000110 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000120 00 00 00 00 00 00 00 00 0b 00 00 00 01 00 00 00 |................| 00000130 06 00 00 00 80 80 04 08 80 00 00 00 28 00 00 00 |............(...| 00000140 00 00 00 00 00 00 00 00 10 00 00 00 00 00 00 00 |................| 00000150 11 00 00 00 01 00 00 00 03 00 00 00 a8 90 04 08 |................| 00000160 a8 00 00 00 3e 00 00 00 00 00 00 00 00 00 00 00 |....>...........| 00000170 04 00 00 00 00 00 00 00 01 00 00 00 03 00 00 00 |................| 00000180 00 00 00 00 00 00 00 00 e6 00 00 00 17 00 00 00 |................| 00000190 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 |................| 000001a0 部分中的目标地址。虽然偏移量00000080中的其他inc保持不变,但仍有机器代码.data

看起来这对我来说非常有启发性,因为我一直在混淆机器代码的哪一部分由链接器修补,哪些是在加载二进制文件时通过操作系统(通常在组装时编程时不需要这样做,重要的是要理解那些地址和符号是编译时常量,当你想使用动态内存管理时,你必须编写所有代码,动态地存储/使用内存地址值。