Assembly 在nasm程序集中调用函数时出现分段错误
我试图在nasm中调用我自己的函数,它工作了2次,然后给出了分段错误。我创建了两个函数display1和display2,分别显示“thisismessage1”和“thisismessage2”。这些函数第一次是正确的,但在调用这些函数两次时显示分段错误Assembly 在nasm程序集中调用函数时出现分段错误,assembly,x86,nasm,Assembly,X86,Nasm,我试图在nasm中调用我自己的函数,它工作了2次,然后给出了分段错误。我创建了两个函数display1和display2,分别显示“thisismessage1”和“thisismessage2”。这些函数第一次是正确的,但在调用这些函数两次时显示分段错误 global _start section .text display1: mov eax, 0x4 mov ebx, 0x1 mov ecx, var1 mov edx, len1 int 0x8
global _start
section .text
display1:
mov eax, 0x4
mov ebx, 0x1
mov ecx, var1
mov edx, len1
int 0x80
ret
display2:
mov eax, 0x4
mov ebx, 0x1
mov ecx, var2
mov edx, var2
int 0x80
ret
_start:
call display1
call display2
call display1
call display2
mov eax, 0x1
mov ebx, 0x5
int 0x80
section .data
var1: db "This is message1", 0x0A, 0x00
len1 equ $-var1
var2: db "This is message2", 0x0A, 0x00
len2 equ $-var2
恭喜你,你发现了一个内核bug(在非常旧的Ubuntu 12.04/Linux 3.13.0-32-generic 32位内核中)
mov-edx,var2
传递一个非常大的整数(地址)作为大小。这就是为什么在第二条消息之后你会收到垃圾;write
系统调用将内存读取到未映射页面附近的某个位置,然后停止
在一个没有bug的内核上,然后write
返回并继续执行,直到\u退出
系统调用
指令int 0x80
导致分段错误
IDK这是否比破坏用户空间并导致以后出现故障更疯狂
可能不值得在任何地方报告这个内核错误。Ubuntu 12.04版本。这个错误在现代内核中并不存在,并且可能是由于偶然注意到的,或者是在内核更新后的7年中作为其他一些变化的一部分修复的
使用write()的无缺陷内核最终会从未映射的页面进行读取,会发生什么 绝对不记录在错误参数上发出信号的可能性,仅记录错误代码,如
EFAULT
我无法在带有x86-64 Linux内核5.0.1的Arch Linux上复制segfault;我会写入预期的垃圾,然后write(2)
返回在垃圾到达未映射页面之前写入的字节数。然后继续执行,直到退出(5)系统调用,进程干净地退出,状态为5
我认为,当传递包含未映射页面的指针+大小时,即使在写入一些字节后,write
也可能返回-EFAULT
,但事实并非如此。手册页中的措辞没有提到这一具体情况,但通过书写部分检测到的其他错误如何处理的措辞与此一致。(通常,这些错误是由于磁盘变满或管道另一端关闭等原因造成的。)
请注意,成功的write()传输的字节数可能少于count字节。这种部分写入可能由于各种原因而发生…
如果发生 部分写入,调用方可以进行另一个write()调用来传输剩余的字节。后续调用将传输更多字节 或者可能导致错误(例如,如果磁盘现在已满) 当您这样做时,Linux肯定不会一直传输到最后一个映射页面的末尾。但有趣的是,看看在不同的情况下会发生什么 它似乎是分块复制的,并在执行过程中检查每个块的可读性。当区块从未映射的页面读取时,会检测到错误,并返回部分写入。如果您用
address=buf+first\u retval
再次拨打电话,您可能会得到-EFAULT
。因此,这非常类似于用部分写入填充磁盘,然后在尝试写入其余部分时通过获取-ENOSPC
来检测它
将输出重定向到x86-64 Linux 5.0.1上的文件(在tmpfs
中),我得到的write()
大小为40784096-18=4078
,我使用了最近的ld
(Binutils 2.32),因此.data
部分在可执行文件中是4k对齐的,并且该部分的开头在内存中也是页面对齐的。因此,页面的结尾位于var2+4096-len1
$ strace ./2write > foo
strace: [ Process PID=28961 runs in 32 bit mode. ]
write(1, "This is message1\n\0", 18) = 18
write(1, "This is message2\n\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 134520850) = 4078
write(1, "This is message1\n\0", 18) = 18
write(1, "This is message2\n\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 134520850) = 4078
exit(5) = ?
+++ exited with 5 +++
与写入终端相比,我得到的大小为2048
与写入/dev/null
相比,我通过写入返回134520850
获得了成功。null
特殊块设备的驱动程序甚至不读取用户空间内存,它只是从write
系统调用中返回成功,使其达到如此程度。因此,任何东西都不会检查-EFAULT
通过管道将输出传输到wc
,我在第一个错误调用中得到了令人惊讶的18字节部分写入,在下一个错误调用中得到了-EFAULT
strace ./2write | wc
execve("./2write", ["./2write"], 0x7ffdba771cf0 /* 53 vars */) = 0
strace: [ Process PID=29008 runs in 32 bit mode. ]
write(1, "This is message1\n\0", 18) = 18
write(1, "This is message2\n\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 134520850) = 18
write(1, "This is message1\n\0", 18) = 18
write(1, "This is message2\n\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 134520850) = -1 EFAULT (Bad address)
exit(5) = ?
+++ exited with 5 +++
3 9 54
在随后的程序运行中,我立即得到了-EFAULT
。我猜Linux可能在第一次调用后为管道缓冲区分配了更多内存,因此它能够在复制任何数据之前,向前看足够远,立即注意到错误的地址
peter@volta:/tmp$ strace ./2write | wc
execve("./2write", ["./2write"], 0x7fff868a41b0 /* 53 vars */) = 0
strace: [ Process PID=29015 runs in 32 bit mode. ]
write(1, "This is message1\n\0", 18) = 18
write(1, "This is message2\n\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 134520850) = -1 EFAULT (Bad address)
write(1, "This is message1\n\0", 18) = 18
write(1, "This is message2\n\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 134520850) = -1 EFAULT (Bad address)
exit(5) = ?
2 6 36
由于使用了
mov-edx,var2
作为长度,因此会收到大量垃圾。指针是大整数。但是,在将一些可读数据复制到给定的文件描述符之后,一旦到达未映射的页面,系统调用最终应该只返回-EFAULT
。(在Linux 5.0.1上,当它们从未映射页面停止时,实际上返回2048
,而不是错误)。然后它就会回来。EAX=1/int 0x80是sys\u exit
无法复制,在Linux上运行良好(打印文本+垃圾,然后以状态=5退出),在与NASM组装并链接到带有gcc-no pie-nostdlib-m32
或ld-melf\u i386
的32位静态可执行文件之后。使用调试器执行单个步骤。也许还可以尝试strace
来查看\u exit
系统调用发生了什么。是的,得到了错误,它是mov edx,len2```而不是````mov edx,var2
谢谢@PeterCordesRight,但这并不能解释segfault。我无法复制。这是奇怪的部分。可能是我用edx寄存器描述的长度得到了一些未定义的值,这些值使用了一些其他进程使用的内存的私有空间,并给了我SEGFULT错误。
peter@volta:/tmp$ strace ./2write | wc
execve("./2write", ["./2write"], 0x7fff868a41b0 /* 53 vars */) = 0
strace: [ Process PID=29015 runs in 32 bit mode. ]
write(1, "This is message1\n\0", 18) = 18
write(1, "This is message2\n\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 134520850) = -1 EFAULT (Bad address)
write(1, "This is message1\n\0", 18) = 18
write(1, "This is message2\n\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 134520850) = -1 EFAULT (Bad address)
exit(5) = ?
2 6 36