assembly - 在 nasm 程序集中调用函数时出现分段错误
问题描述
我试图在 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-generic 32 位内核中)。
mov edx, var2
传递一个非常大的整数(地址)作为 size。这就是为什么您在第二条消息之后得到垃圾的原因;系统调用正在将write
内存读取到未映射页面附近的某个位置,然后停止。
在没有错误的内核上,然后write
返回并继续执行,直到_exit
您期望的系统调用。
该指令
int 0x80
导致分段错误。
IDK 是否比破坏用户空间并在以后导致故障更疯狂。
可能不值得在任何地方报告这个内核错误。Ubuntu 12.04 LTS于 2017 年终止。该错误在现代内核中不存在,并且可能在该内核发布后的 7 年中作为其他一些更改的一部分被偶然发现或修复。
最终从未映射页面读取的带有 write() 的非错误内核中会发生什么
write(2)
手册页绝对没有记录在错误参数上引发信号的可能性,只有错误代码,如EFAULT
.
我无法使用 x86-64 Linux 内核 5.0.1 在 Arch Linux 上重现段错误;我得到了预期的垃圾写入,然后write(2)
返回在它到达未映射页面之前写入的字节数。然后继续执行,直到_exit(5)
系统调用并且进程以 status=5 干净地退出。
当您传递一个包含未映射页面的指针+大小时,我认为即使在写入一些字节之后write
也可能会返回,但事实并非如此。-EFAULT
手册页中的措辞没有提到这种具体情况,但是如何处理在写入过程中检测到的其他错误的措辞与此一致。(通常这些错误是由于磁盘已满,或者管道的另一端关闭。)
请注意,成功的 write() 可能传输少于 count 个字节。这种部分写入可能由于各种原因而发生;...
...
如果发生部分写入,调用者可以进行另一个 write() 调用以传输剩余字节。随后的调用将传输更多字节或可能导致错误(例如,如果磁盘现在已满)。
当您执行此操作时, Linux 绝对不会一直传输到最后一个映射页面的末尾。但有趣的是看看不同情况会发生什么。
似乎它以块的形式复制,并检查每个块的可读性。当一个块从一个未映射的页面读取时,会检测到错误并返回部分写入。如果您使用 再次拨打电话address = buf + first_retval
,您可能会得到一个-EFAULT
. 所以这很像用部分写入填充磁盘,然后-ENOSPC
在尝试写入其余部分时通过获取来检测它。
tmpfs
将输出重定向到x86-64 Linux 5.0.1 上的文件 (in ) 我得到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
,我在第一个错误调用和下一个错误调用中得到了令人惊讶的 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
推荐阅读
- webdriver-io - 如何使用 element.isSelected() 使用 webdriver.io 检查复选框
- git - 阻止 git 获取项目中的背景更改
- java - IntelliJ 在运行时不显示调用堆栈,而 Eclipse 显示同一项目的错误
- c# - 为什么在 CreateDecryptor 方法中设置 IV / Key 时在 AesManaged 中设置它们就可以了?
- encryption - 使用隐写术在 bmp 图像中查找秘密信息
- python - 如何为生成器函数创建循环而不是每次下一次调用
- python - 我将如何添加播放列表
- python - 在 KFold 交叉验证的情况下如何显示平均分类报告和混淆矩阵
- c - c中scanf的内部工作原理
- python - 在 Python 3.X 中动态导入模块