Assembly 为什么内存(x86/nasm)中的数据段之间存在空地址空间?
我正在尝试编写一个小程序,询问用户的姓名,对用户输入进行编码,然后向stdout打印一条消息,详细说明编码的输入。例如,用户输入名称“John”,它会将“您的代码名为:Red5”打印到stdoutAssembly 为什么内存(x86/nasm)中的数据段之间存在空地址空间?,assembly,x86,nasm,x86-64,Assembly,X86,Nasm,X86 64,我正在尝试编写一个小程序,询问用户的姓名,对用户输入进行编码,然后向stdout打印一条消息,详细说明编码的输入。例如,用户输入名称“John”,它会将“您的代码名为:Red5”打印到stdout SECTION .data ; Section containing initialised data RequestName: db "Please enter your name: " REQUESTLEN: equ $-RequestName
SECTION .data ; Section containing initialised data
RequestName: db "Please enter your name: "
REQUESTLEN: equ $-RequestName
OutputMsg: db "Your code name is: "
OUTPUTLEN: equ $-OutputMsg
SECTION .bss ; Section containing uninitialized data
EncodedName: resb ENCODELEN
ENCODELEN: equ 1024
我有输出消息的第一部分,“您的代码名是:”,存储(开始)在内存地址“OutputMsg”,还有输出消息的第二部分,即编码的用户输入“Red5”,存储在内存地址“EncodedName”中。因此,要将所需消息打印到stdout,我使用以下代码将两者连接起来:
mov rdx,OUTPUTLEN ; Length of string 'OutputMsg'
add rdx,r8 ; r8 contains the number of bytes entered by the user
; the code name is always equ in length to user input
mov rax,4 ; sys_write
mov rbx,1 ; stdout
mov rcx,OutputMsg ; Offset of string to print to stdout
int 80h ; Make kernel call
这几乎和预期的一样。但是,输出中缺少最后一个字符。因此,我得到的不是“您的代码名为:Red5”,而是“您的代码名为:Red”。在调试器中检查内存时,内存地址为空(0x00)在“OutputMsg”的结尾和“EncodedName”的偏移量之间错误地“放置”
Address Binary ASCII
0x… 60012a 0x20 Space (This is the end of the data item ‘OutputMsg’)
0x… 60012b 0x00 NUL
0x… 60012c 0x52 R (The start of SECTION .bss / 'EncodedName')
我已经使用其他几个代码示例对此进行了测试,在部分.data
在内存中结束和部分.bss
开始之间似乎总是存在NUL
字符的“随机”位置
1) 是什么原因导致这个空地址空间,因为它没有包含在我的源代码中
2) 空地址空间出现在部分的末尾。在我所看到的所有示例中,我假设这是预期的行为。空地址空间的具体原因是什么,是否“标记”一部分的结尾和下一部分的开头?为什么有必要这样做
3) 如何计算空间的大小。我发现,根据程序和我正在查看的部分,有时这个空间是一个字节,有时是两个/三个字节;在运行之前,我如何知道这个空空间将是多少字节
我可以解决这个问题。但是,我想了解发生了什么。我编写了代码,将两个部分的字符串连接起来,以便打印到stdout。我无法解释的意外空地址空间正在中断我的计算
NASM版本2.11.08体系结构x86 | Ubuntu 16.04数据对齐:
通常认为内存是一个扁平的字节数组:
Data: | 0x50 | 0x69 | 0x70 | 0x43 | 0x68 | 0x69 | 0x70 | 0x73 |
Address: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
ASCII: | P | i | p | C | h | i | p | s |
然而,CPU本身不会一次读取或写入一个字节的数据到内存中。效率是游戏的名称,因此,计算机的CPU将从内存中读取数据,每次读取固定数量的字节。处理器访问内存的大小称为其内存访问粒度(MAG)
内存访问粒度因体系结构而异。一般来说,MAG等于相关处理器的本机字大小,即IA-32的粒度为4字节
如果CPU一次仅从内存中读取一个字节,则需要访问内存8次才能读取上述整个阵列。将此与CPU一次访问内存4字节(粒度为4字节)进行比较。在这种情况下,CPU只需访问内存两次;1=字节0-3,2=字节4-7
内存对齐在哪里起作用:
好的,让我们假设一个4字节的MAG。正如我们所看到的,为了从内存中读取字符串“PipChips”,CPU需要访问内存两次。现在,让我们假设数据在内存中的对齐方式略有不同。让我们假设:
Data: | 0x6B | 0x50 | 0x69 | 0x70 | 0x43 | 0x68 | 0x69 | 0x70 | 0x73 |
Address: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
ASCII: | k | P | i | p | C | h | i | p | s |
在本例中,要访问相同的数据,CPU总共需要访问内存3次;1=字节0-3,2=字节4-7,第三次访问“s”,位于内存地址8。此外,由于数据存储在未对齐的地址,处理器必须执行额外的工作,以便移出不必要的字节,这些字节是不必要地从内存中读取的
这就是内存对齐的作用。CPU有一个MAG,其主要目的是提高机器效率。因此,将内存中的数据对齐以匹配机器内存访问边界可以创建更高效的代码
这是对记忆对齐的(n)(过于)简单的解释,但它回答了以下问题:
1)由于我的源代码中没有包含此空地址空间,是什么原因导致此空地址空间?
“空地址空间”是根据节
数据的对齐要求生成的。如果未指定节属性的值,则假定为NASM默认值。请参阅
2)此空地址空间的具体原因是什么?
对齐内存数据的主要原因是为了软件效率和健壮性。如前所述,处理器将以字大小的粒度访问内存
3)如何计算空间大小?
汇编程序将填充该节,以便紧接其后的数据自动与指定内存访问边界的实例对齐。在原始问题中,section.data
将在地址0x…60012a
处结束,如果没有必要的填充,则使用section.bss代码>从地址60012b开始。在这里,数据不会与CPU的访问粒度定义的内存访问边界正确对齐。因此,NASM明智地添加了一个nul
字符的填充,以便将内存地址四舍五入到下一个可被4整除的地址,从而正确地进行对齐数据
内存访问的微妙之处很多;要获得更深入的解释,请参阅,以及大量的在线文章,例如;对于你们当中的受虐狂,总是有手册
一般来说,数据是不正确的
EncodedName: resb ENCODELEN
ENCODELEN: equ 1024
CompleteOutput: resb COMPLETELEN
COMPLETELEN: equ 2048
; Read user input from stdin:
mov rax,0 ; sys_read
mov rdi,0 ; stdin
mov rsi,EncodedName ; Memory offset in which to read input data
mov rdx,ENCODELEN ; Length of memory buffer
syscall ; Kernel call
mov r8,rax ; Save the number of bytes read by stdin
; Move string 'OutputMsg' to memory address 'CompleteOutput':
mov rdi,CompleteOutput ; Destination memory address
mov rsi,OutputMsg ; Offset of 'string' to move to destination
mov rcx,OUTPUTLEN ; Length of string being moved
rep movsb ; Move string, iteration, per byte
; Concatenate 'OutputMsg' with 'EncodedName' in memory:
mov rdi,CompleteOutput ; Destination memory address
add rdi,OUTPUTLEN ; Add length of string already moved, so we append strings, as opposed to overwrite
mov rsi,EncodedName ; Offset memory address of string being moved
mov rcx,r8 ; String length, during sys_read, the number of bytes read was saved in r8
rep movsb ; Move string into place
; Write string to stdout:
mov rdx,OUTPUTLEN ; Length of 'OutputMsg'
add rdx,r8 ; add length of 'EncodedName'
mov rax,1 ; sys_write
mov rdi,1 ; stdout
mov rsi,CompleteOutput ; Memory offset of string
syscall ; Make system call