首页 > 解决方案 > 在 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)

标签: assemblyx86nasm

解决方案


恭喜,您发现了一个内核错误(在您非常旧的 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(2)Linux 手册页

请注意,成功的 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

推荐阅读