我试图在nasm中调用我自己的函数,它可以工作2次,然后给出细分错误。我创建了两个函数display1和display2,分别显示“ This is message1”和“ This is message2”。这些功能第一次正确,但是在两次调用这些功能时显示分段错误。
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
This is message1
This is message2
.symtab.strtab.shstrtab.text.data�N!�$�'
�
�U�����"'��,1��6�=���I���P���functions.nasmdisplay1display2var1len1var2len2_start__bss_start_edata_endThis is message1
This is message2
.symtab.strtab.shstrtab.text.data�N!�$�'
�
�U�����"'��,1��6�=���I���P���functions.nasmdisplay1display2var1len1var2len2_start__bss_start_edata_endSegmentation fault (core dumped)
恭喜,您已经找到了一个内核错误(在您非常老的Ubuntu 12.04 / Linux 3.13.0-32-通用32位内核中)。
mov edx, var2
传递一个非常大的整数(地址)作为size。这就是为什么在第二条消息之后会出现垃圾的原因。该write
系统调用读取内存高达附近某处未映射的页面,然后停止。
在非Buggy内核上,然后write
返回并继续执行,直到_exit
您期望的系统调用为止。
该指令
int 0x80
导致分段错误。
IDK是否比破坏用户空间或稍后导致故障更疯狂。
可能不值得在任何地方报告此内核错误。Ubuntu 12.04 LTS于2017年停产。该错误在现代内核中不存在,并且可能是由于该内核为最新版本以来7年内其他更改的一部分而被偶然发现或修复。
该write(2)
手册页绝对不会不记录坏ARGS提高只是像错误代码的信号,的可能性EFAULT
。
我无法在带有x86-64 Linux内核5.0.1的Arch Linux上重现该段错误;我得到了预期的垃圾写入,然后write(2)
返回写入未映射页面之前写入的字节数。然后继续执行直到_exit(5)
系统调用,并且进程以status = 5干净退出。
我以为在传递包含未映射页面的指针+大小时,即使写了一些字节后write
也可能会返回-EFAULT
,但事实并非如此。手册页中的措词没有提及这种特定情况,但是有关如何处理在编写过程中发现的其他错误的措辞与此一致。(通常,这些错误是由于磁盘已满或管道另一端关闭引起的。)
请注意,成功的write()传输的字节数可能少于计数字节。发生这种部分写入的原因可能多种多样。...
...
在一个部分写入的情况下,主叫方可以使另一个write()调用传输剩余的字节。随后的调用将传输更多的字节,或者可能导致错误(例如,如果磁盘现在已满)。
Linux的肯定并不会总是一路,当你做到这一点传输到最后一个页面映射结束。但是有趣的是看到不同情况下会发生什么。
似乎它是按块复制的,并随着每个块检查其可读性。当从未映射的页面读取块时,将检测到错误,并返回部分写入。如果再打一次电话address = buf + first_retval
,您可能会得到一个-EFAULT
。因此,这非常像用部分写操作填充磁盘,然后-ENOSPC
在尝试写其余部分时通过获取进行检测。
将输出重定向到tmpfs
x86-64 Linux 5.0.1上的文件(中),我得到write()
4078.的大小4096-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
,在第一个坏调用和-EFAULT
下一个坏调用中,我得到了令人惊讶的18字节部分写入。
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
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句