首页 > 解决方案 > NASM x86_64 printf 第 7 个参数

问题描述

我有一个dprintf用 NASM 编写的简单程序,它打印一个包含 6 个以上参数的长格式。我根据调用约定(RDIRSIRDXRCXR8R9)传递参数。只要我只使用那些我的程序就可以正常工作。

我无法弄清楚为什么每次尝试将某些内容作为附加参数推送到堆栈时都会出现段错误。这是来源:

;a comment
%macro DATA 0
section .data
    string: db "%6$ca comment%1$c%4$cmacro DATA 0%1$csection .data%1$c%2$cstring: db %3$c%5$s%3$c, 0%1$c%2$cpath: db %3$cGrace_kid.s%3$c, 0%1$c%4$cendmacro%1$c%4$cdefine SC_OPEN 0x2000005%1$c%4$cmacro MAIN 0%1$c%1$cDATA%1$c%1$csection .text%1$c%2$cglobal start%1$c%2$cglobal _main%1$c%2$cextern _dprintf%1$c%1$cstart:%1$c%2$ccall _main%1$c%2$cret%1$c%1$c_main:%1$c%2$cpush rbp%1$c%2$cmov rbp, rsp%1$c%2$cmov rax, SC_OPEN%1$c%2$clea rdi, [rel path]%1$c%2$cmov rsi, 0x0200%1$c%2$cxor rsi, 0x0002%1$c%2$cmov rdx, 0640o%1$c%2$cclc%1$c%2$csyscall%1$c%2$cjc ret%1$c%2$ccmp rax, 0%1$c%2$cjle ret%1$c%2$cmov rdi,rax%1$c%2$clea rsi, [rel string]%1$c%2$cmov rdx, 10%1$c%2$cmov rcx, 9%1$c%2$ccall _dprintf%1$c%2$cxor rax, rax%1$cret:%1$c%2$cleave%1$c%2$cret%1$c%4$cendmacro%1$c%1$cMAIN%1$c", 0
    path: db "Grace_kid.s", 0
%endmacro
%define SC_OPEN 0x2000005
%macro MAIN 0

DATA

section .text
    global start
    global _main
    extern _dprintf

start:
    call _main
    ret

_main:
    push rbp
    mov rbp, rsp
    ;sub rsp, 16
    mov rax, SC_OPEN
    lea rdi, [rel path]
    mov rsi, 0x0200
    xor rsi, 0x0002
    mov rdx, 0640o
    clc
    syscall
    jc ret
    cmp rax, 0
    jle ret
    mov rdi, rax
    lea rsi, [rel string]
    mov rdx, 10
    mov rcx, 9
    mov r8, 34
    mov r9, 37
    mov rbx, 59
    push rbx
    xor rax, rax
    call _dprintf
    xor rax, rax
ret:
    leave
    ret
%endmacro

MAIN

我组装和链接这些命令:

nasm -fmacho64 file.s
ld file.o -macosx_version_min 10.14 -lSystem

这工作得很好,但我想添加额外的参数。我尝试使用以下方法将其推入堆栈:

    mov rbx, 59
    push rbx

我是否将一些字节分给RSP会产生段错误。

我在 MacOS Mojave 下,我正在使用最新版本的 NASM。

标签: assemblystackprintfx86-64nasm

解决方案


由于这个问题最终具有一定的价值,它可能可以使用答案。有两个重大问题:

  • _dprintf使用 7 个参数调用,这些参数具有C等效项:

    dprintf (fd, format_str, 10, 9, 34, 47, 59)
    

    问题是在你的格式字符串中你有%5$s. 第 5 个可变参数是值 59,而不是指向字符串的指针。dprintf正在尝试访问它没有权限的内存,并且您收到错误 EXC_BAD_ACCESS 和段错误。您也有%6$c格式字符串,但没有第 6 个可变参数。从您的评论中可以清楚地看出,您希望它format_str本身是第 5 个参数,而值 59 是第 6 个参数。推送最后 2 个参数的代码应该如下所示:

    push 59
    lea rbx, [rel string]
    push rbx
    xor rax, rax
    call _dprintf
    

    相应的C调用将是:

    dprintf (fd, format_str, 10, 9, 34, 47, format_str, 59)
    

    注意:当压入堆栈中不适合寄存器的参数时,它们必须以相反的顺序压入

  • x86-64 System V ABI 调用约定在调用符合要求的函数(包括SystemC库)之前,要求堆栈至少对齐 16 字节。在 MacOS 上,系统库对堆栈对齐问题非常敏感,因为它出于性能原因尽可能使用对齐的 SIMD 指令,即使仅使用整数类参数也是如此。

    _main也符合这个标准。ABI 需要在呼叫前的点进行 16 次再见对齐。如果您传递需要 256 位 SIMD 向量的参数,则需要 32 字节对齐 - 但这里不是这种情况。进入_main(或任何符合 x86-64 调用约定规则的函数)后,堆栈未对齐 8,因为返回地址现在在堆栈上。push RBP从 RSP 中减去 8,堆栈现在再次在 16 字节边界上对齐。如果您在堆栈上压入偶数个参数以满足诸如dprintf对齐之类的调用,则仍然完好无损。如果您传递一个奇数,您将再次错位。在这些情况下,您必须在推送参数之前从 RSP 中减去 8。

    如果你真的打算这样做:

    dprintf (fd, format_str, 10, 9, 34, 47, 59)
    

    在将额外的 1 参数压入堆栈之前,您必须从 RSP 中减去 8。代码看起来像:

    push rax         ; Push any register on stack or use `add rsp, -8` to align parameters
    push 59
    xor rax, rax
    call _dprintf
    

    如果您将 2 个额外参数传递给dprintf不需要这样的堆栈调整,因为被推送的偶数个参数不会破坏 16 字节对齐


推荐阅读