【问题标题】:Why does this program loop?为什么这个程序循环?
【发布时间】:2020-09-22 19:52:25
【问题描述】:

我编写了以下程序,我想知道它的行为(1)是否会一直循环;或 (2) 它有意外的行为。我知道我在函数末尾缺少ret(这使程序运行良好),但我想知道为什么程序在不包含时会循环:

# file.s
.include "utils.s"

.data
str:    .string "Tony"

.text
.globl _start
_start:
    mov $str,   %rdi
    call print_string
    mov $10, %edi
    mov $SYS_EXIT, %eax
    syscall
# utils.o
SYS_EXIT    = 60
SYS_WRITE   = 1
SYS_STDOUT  = 1

print_string:
    # void print_string(char *)
    mov %rdi,       %rsi
    mov $1,         %rdi
    mov $4,         %rdx
    mov $SYS_WRITE, %eax
    syscall

组装/链接/运行:

$ as utils.s -o utils.o && as file.s -o file.o && ld file.o utils.o -o file
$ ./file

print_string.syscall 之后在gdb 中查看此内容时,它会返回到mov $str, %rdi,但为什么会出现这种情况?

注意:segfaults 全部在一个文件中,但当 print_string 移动到第二个文件时它会循环,所以这可能与组装/链接有关,而不是代码本身。

【问题讨论】:

  • 在调试器中单步执行,以查看在您编写的内容结束后机器代码执行的字节数。如果这是一个简单的静态可执行文件,它可能只是零,并且会在 00 00 解码到的 add %al, (%rax) 指令上出错。显示一个实际的minimal reproducible example 你是如何将它构建到一个可执行文件中的。如果您链接print_string first,则执行从它到_start,与您在问题中显示的相反。这就是为什么 SO 问题需要真正的minimal reproducible examples,而不是你认为可能等同的东西。
  • UB。你可能已经跳了,你可能已经将一个寄存器弹出到一个跳转位置,很多事情都可能导致这种情况。不过,您正在阅读程序的末尾,谁知道...
  • 就像现在一样,您的执行已经执行了 print_string 两次。你不想让你的 start 函数返回来防止这种情况发生吗?
  • 嗨 @PeterCordes 更新了您的建议
  • 你做了.include "utils.s",所以call print_string将调用你包含的print_string的副本,而不是另一个.o文件中的副本(不是.globl所以不是'甚至无法到达)。不要将代码放在你要去.include的文件中。

标签: assembly x86


【解决方案1】:

您执行了.include "utils.s",因此call print_string 将调用您包含的print_string 的副本,而不是另一个.o 文件中的副本(不是.globl,因此甚至无法从其他文件,例如 C 中的 static void foo(char*) 函数)。

执行从print_string 结束到_start

不要将代码放在你要去.include的文件中,定义常量和宏。如果你确实使用了.globl print_string,那么对于同一个全局符号,您会有两个相互冲突的定义。当然,您会将每个函数的定义复制到包含它的每个其他文件中,从而违背了链接和函数的目的。


更详细地说,处理 .include 后的 file.s 将如下所示:

# implicit  .text  - the default section at the top of the file
SYS_EXIT    = 60
SYS_WRITE   = 1
SYS_STDOUT  = 1

print_string:
    # void print_string(char *)
    mov %rdi,       %rsi
    mov $1,         %rdi
    mov $4,         %rdx
    mov $SYS_WRITE, %eax
    syscall

.data
str:    .string "Tony"

.text
.globl _start
_start:
    mov $str,   %rdi
    call print_string
    mov $10, %edi
    mov $SYS_EXIT, %eax
    syscall

请注意,如果您省略 utils.old 不会抱怨,因为此文件没有未解析的符号引用。

【讨论】:

  • 哦,我明白了。所以基本上 call print_string 或多或少(至少在功能上,因为在这种情况下,返回地址无关紧要,因为没有调用 ret)只是做一个 jmp print_string 并且因为包含在它上面,它会一直循环回来。准确吗?
  • @samuelbrody1249:是的。当推送返回地址溢出 8MiB 堆栈空间时,最终会出现段错误,但这将需要大量缓慢的写入系统调用,特别是如果你让它打印到终端。
猜你喜欢
  • 2021-03-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多