【问题标题】:Calling read syscall from assembly (x86/64) yields segmentation fault (compiler construction)从程序集(x86/64)调用读取系统调用会产生分段错误(编译器构造)
【发布时间】:2016-10-02 17:16:06
【问题描述】:

我正在为类 C 语言构建编译器,并且我正在尝试将在程序集中实现的基本“void readString(int, char*)”函数与我的编译器生成的程序集链接起来。

编译后的类c文件是

void main () {
    char t[20];     
    readString(7,t); // Read 7 bytes and place them in t buffer
}

编译后,生成的文件是:out.s文件(注意调用约定是:通过堆栈传递参数。在这种语言中,整数也有2字节大小):

.$0:
.globl main
main:
pushq %rbp
movq %rsp,%rbp
subq $20,%rsp

.$1:
movw $7,%ax  # Push first argument in the stack
pushw %ax

.$2:
leaq -20(%rbp),%rax # Push address of the second arg in the stack
pushq %rax

.$3:
subq $8,%rsp   # this is not important, needed for the convention being followed
pushq 16(%rbp) # pushing "access link",
call _readString 
addq $26,%rsp # caller clears the "leftovers" 

.$4:
.$main_0_11:
movq %rbp,%rsp
popq %rbp
ret

库函数的 reads.asm 中的代码:

.intel_syntax noprefix
            .global _readString

_readString     push    rbp
            mov     rbp, rsp
            push    rdi
            push    rsi

            mov     rdi, [rbp+32]           # First argument 
            mov     rsi, [rbp+34]           # Second Argument

            mov     rdx, rdi

doRead:
            mov     byte ptr [rsi], 0x00
            xor     rax, rax
            mov     rdi, rax
            syscall                         # read syscall, reads up to $rdx bytes
            or      rax, rax                # nothing read
            jz      finish                  #
            add     rsi, rax                #
            cmp     byte ptr [rsi-1], 0x0a  # check if last character read was '\n'
            jne     addZero                 #
            sub     rsi, 1                  # if so, replace with '\0'
addZero:
            mov     byte ptr [rsi], 0x00
finish:
            pop     rsi
            pop     rdi
            pop     rbp
            ret

链接/运行如下

$ gcc -c out.s
$ gcc -c reads.s 
$ gcc out.o reads.o 
$ ./a.out
[2] Segmentation fault  ./a.out

描述调用约定的图片

【问题讨论】:

  • 您几乎肯定在处理堆栈对齐问题。查找调用约定并确保您的堆栈在调用之前正确对齐。
  • 你完全正确!谢谢 :-) 我会发布修复程序
  • IDK 如果您意识到,但您在这里发明了自己的非标准 64 位调用约定。 x86-64 System V 和 Windows 在寄存器中传递函数参数。 (当它们用完寄存器时,窄 args 仍然占用 8B 堆栈槽;它们从不使用 PUSHW)。请参阅 x86 tag wiki 了解 ABI 链接,并查看 gcc/clang 输出以了解它们是如何做到的。
  • 抱歉,回答延迟。我确实意识到这一点,但这是一个深奥的编译器,所以它不需要符合任何标准约定。虽然我不知道 x86 标签 wiki。感谢您发布它。

标签: assembly compiler-construction x86 system-calls static-linking


【解决方案1】:

好的,正如@David Hoelzer 建议的那样,我再次检查了“readString”从堆栈中获取参数的方式,并且它们按顺序颠倒了!

所以 reads.asm 的第一行会变成

_readString:
            push    rbp
            mov     rbp, rsp
            push    rdi
            push    rsi

            movzx   edi, word ptr [rbp+40] # Get first argument, check comments for more info
            mov     rsi, [rbp+32]  # Second Argument


            mov     rdx, rdi

【讨论】:

  • 而不是 mov di, [rbp+40] and rdi, 0xffff 你可以只做 movzx rdi, word ptr [rbp+40] 这会将 16 位字从 [rbp+40] 移动并将其零扩展到 64 位寄存器 RDI 更好的做法是使用movzx edi, word ptr [rbp+40],它会编码为更少的字节,但效果相同。
  • 我在我的第一个评论中做了一个小补充,通过使用 EDI 而不是 RDI 在较短的编码中做同样的事情。零扩展到 32 位寄存器 EDI 起作用的原因是,在 64 位模式下,当目标寄存器为 32 位时,CPU 会自动将其零扩展到相应的 64 位寄存器( RDI 在这种情况下)。在这种情况下使用 RDI 编码为带有前缀字节的 5 字节指令,而 EDI 将编码为 4 个字节。
  • 不错!你知道为什么会这样吗?是故意的还是仅仅因为没有人关心零扩展寄存器的一半并避免硬件进一步复杂化?
  • 如果您指的是 CPU 自动将 32 位目标寄存器零扩展至 64 位寄存器(在 64 位模式下),从处理器的角度来看,它与性能有关,因为它可以减少错误的依赖和部分寄存器停顿。
猜你喜欢
  • 1970-01-01
  • 2018-12-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多