【问题标题】:x86 Assembly: Segmentation Fault (Core dumped) while trying to reverse print arrayx86 程序集:尝试反转打印数组时出现分段错误(核心转储)
【发布时间】:2020-04-21 12:35:50
【问题描述】:

在我的代码中,我试图反向打印一个数组。 我的两个主要想法是要么使用堆栈和 LIFO 属性来执行此操作,要么使用循环作为从 10 到 0 的索引来反向访问元素。由于堆栈方法的对齐问题,我选择了第二个。

我对组装很陌生,希望能得到一些帮助,以了解我的错误在哪里。提前致谢!

DEFAULT REL
; external functions for inputs/outputs printf and scanf/printf
extern printf
extern scanf

section .data
prompt      db "Entrez un entier : ",0
longIntFormat  db "%ld",0


section .bss
entier resb 10       ; array of 10 integers


section.text
push rbp

    mov rcx, 0  
    mov rdx, 0     ; initialise counter
    lea rcx, [entier]    ; load array into register rcx

; fills the array with user input
_getLoop:
; call printf
    lea rdi,[prompt]
        mov rax,0
    call printf wrt ..plt

; call scanf
        lea rdi,[longIntFormat]
        lea rsi, [rcx + rdx]     ; array + Index
        mov rax,0
    call scanf wrt ..plt
    inc rdx                      ; inc. Index/counter
    cmp rdx, 10
    jl _getLoop                  ; While counter is less than 10 (size of array)

    mov rcx, 0          ; set rcx to 0
    mov rdx, 10         ; counter set to 10
    lea rcx, [entier]   ; load array into rcx

; print the array in reverse using the counter as Index
_printLoop:

; call printf
    lea rdi, [rcx + rdx]     ; rdi = [array + Index]
        mov rax,0
    call printf wrt ..plt
    dec rdx
    cmp rdx, 0               ; compare counter with 0
    jge _printLoop           ; Once 0 is reached the loop has gone through all the array

;restores registers
pop rbp

; returns 0 to C program
        mov     rax, 0            
        ret

【问题讨论】:

  • 为什么你的jl _getLoop 后面有一个ret?这似乎过早地从函数中返回。
  • jl 之后的 ret 是错误的,应该保留原始值还是每次调用都对 rcx 和 rdx 有影响?我真的不知道 printf 和 scanf 来自哪个库,但如果这可以帮助它们被定义为DEFAULT REL extern printf extern scanf
  • 您使用的是什么操作系统?不同系统上的调用约定不同。
  • 你的代码被截断了吗?最后一条指令是jge,但是如果没有跳转会发生什么?看到调用它的代码也很好;即完整的程序,包含构建和运行它所需的一切(包括用于构建它的命令!)。换句话说,minimal reproducible example.
  • 添加了代码的末尾,我真的不知道它可以运行什么,它用于分配,我正在基于 Web 的命令行上运行代码。应该是用YASM,因为我的课程就是基于它的

标签: assembly segmentation-fault x86-64 yasm


【解决方案1】:

有很多错误,例如:

  • 一个函数可以根据calling convention改变几个寄存器
  • scanfprintf 的格式字符串语法略有不同。如果您打算修改 printf 输出(例如使用 \n),则必须创建另一个格式字符串。
  • 在您的循环中,您忘记将任何格式字符串传递给printf,只是一个整数的地址。 (printf 需要一个格式字符串并按值获取整数。)
  • "%ld" 表示“长整数”。在我的系统上,这是一个四字(8 字节)。您严格地只处理一个字节。
  • 在调用函数之前,堆栈必须与 16 的倍数对齐。内核遵循 x86-64 System V ABI 并确保在进程入口时是这种情况(通常入口点称为_start) .如果您推送/弹出,请确保在调用之前不要让堆栈错位。
  • _start(进程入口点)不是函数;你不能从中ret。调用 glibc 的 exit 函数以确保刷新 stdio 缓冲区,或进行原始的 _exit 系统调用。
  • section.text 缺少一个空格。它被解析为像foo.bar: 这样的标签名称,而不是切换到.text 部分的指令。所以你的代码最终出现在.data(或者可能是.bss),并且由于这些部分链接到不可执行的内存页面而出现了段错误。

看看我更正的 - 现在正在运行 - 程序:

DEFAULT REL
; external functions for inputs/outputs printf and scanf/printf
extern printf, fflush
extern scanf

section .data
    prompt      db "Entrez un entier : ",0
    longIntFormat  db " %ld",0


section .bss
    entier resq 10              ; array of 10 integers


global _start
section .text

_start:
    ;and rsp, -16                ; Align stack to 16 (the ABI already guarantees this for _start)

    mov rbx, 0                  ; initialise counter

; fills the array with user input
_getLoop:
; call printf
    lea rdi,[prompt]
    mov rax,0
    call printf wrt ..plt

; call scanf
    lea rdi,[longIntFormat]
    lea rsi, [entier + rbx * 8]    ; array + Index
    mov rax,0
    call scanf wrt ..plt
    inc rbx                     ; inc. Index/counter
    cmp rbx, 10
    jl _getLoop                 ; While counter is less than 10 (size of array)

    mov rbx, 9                  ; counter set to 10

; print the array in reverse using the counter as Index
_printLoop:

; call printf
    lea rdi,[longIntFormat]
    mov rsi, [entier + rbx*8]   ; rdi = [array + Index]
    mov rax,0
    call printf wrt ..plt
    dec rbx
    cmp rbx, 0                  ; compare counter with 0
    jge _printLoop

    xor edi, edi                ; RDI=0: all streams
    call fflush  wrt ..plt

    mov rax,60                  ; SYS_EXIT
    mov rdi,0                   ; Exitcode: RDI=0
    syscall                     ; Call Linux64

【讨论】:

  • 程序确实有效,我想知道,fflush函数有什么用?除此之外,其余的现在完全有道理了,谢谢!
  • @HowLong: printf 写入缓冲区,当程序以SYSCALL: stackoverflow.com/a/55799627/3512216 结束时不会输出。由于对齐(=更改)堆栈,您无法以 ret 结束程序。
  • @HowLong:更根本的是,你永远不能从ret_start。这不是一个函数;在进入_start 时,RSP 指向argc(并且由 x86-64 System V ABI 保证为 16 字节对齐)。 and rsp, -16 在这一点上是无操作的。您可以假设您的内核和动态链接器遵循 ABI。 (您的 push rbp 未对齐,导致 scanf 段错误:glibc scanf Segmentation faults when called from a function that doesn't align RSP
  • @HowLong:我建议在使用来自_start 的libc 函数的程序中使用call exit wrt ..plt。这将刷新并做任何其他可能需要的事情。尽管使用 _start 中的 libc 函数而不是 main 仍然是一种 hack;除非您知道自己在做什么,否则不建议这样做,并且仅因为 libc 使用动态链接器挂钩在您的 _start 执行之前对其进行初始化才有效。静态链接会让你的程序崩溃。
猜你喜欢
  • 2020-08-15
  • 1970-01-01
  • 2023-01-01
  • 1970-01-01
  • 2022-12-11
  • 1970-01-01
  • 2021-06-15
  • 2021-12-31
  • 1970-01-01
相关资源
最近更新 更多