【发布时间】:2021-08-13 00:08:22
【问题描述】:
我正在汇编中编写一个函数,它本质上将 args 推入堆栈,然后创建一个堆栈帧(即保存先前的并将堆栈基指针移动到堆栈指针的值)。然后我尝试通过将基指针偏移 4 + 2 来访问我的参数(4 个字节是内存地址的长度,2 是我想要的 arg 的长度)。
这是我的程序(两行之间是内存):
section .data
txt dw '25'
section .text
global _start
_exit:
mov rax, 60
mov rdi, 0
syscall
_print_arg:
;; function_initialisation
push rbp ;; save old stackbase value
mov rbp, rsp ;; set new stack base from tail of last value added to stack
;; actual function
mov rax, 1
mov rdi, 1
;;________________________
lea rsi, [rbp + 4 + 2] ;; access stackbase, skip return address (4 bytes long) and go to start of our param (which is 2 bytes long / word)
;;¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬
mov rdx, 2
syscall
;; function_finalisation
mov rsp, rbp ;; set stack pointer as this frames stack base (ie set ebp to tail of old function)
pop rbp ;; set stack base pointer as original base of caller
ret ;; return to last value stored on stack, which at this point is the implicitly pushed return address
_start:
push word [txt] ;; push only arg to stack, word
call _print_arg ;; implicitly push return address, call function
pop rax ;; just a way to pop old value off the stack
jmp _exit ;; exit routine, just a goto
我首先尝试直接打印我推送到堆栈的变量,这很有效,所以我知道这不是无法打印的内容问题。我的猜测是我对堆栈和操作指针寄存器的理解存在根本缺陷。
【问题讨论】:
-
x86-64 中的返回地址是 8 个字节(qword),
push rbp也是如此。使用调试器查看寄存器和内存。 (此外,标准调用约定在 regs 中传递 args,而堆栈 args 在 8 字节槽中传递。您可以随意做任何您想做的奇怪事情,例如将堆栈与 2 字节push wordASCII 值不对齐如果你愿意,只要你不打算调用任何 libc 函数。但你仍然必须匹配调用者和被调用者。) -
我建议查看 C 编译器输出,例如 How to remove "noise" from GCC/clang assembly output?。另请参阅What are the calling conventions for UNIX & Linux system calls (and user-space functions) on i386 and x86-64 - 用户空间函数调用约定与系统调用调用约定非常相似。
-
英特尔处理器是小端的,多字节值的地址是最低字节的地址。所以你的两个字节参数是字节[rbp+16]和[rbp+17]。它使用最低字节的地址来寻址,即 rbp+16。
-
[rbp]是保存的rbp值的地址。 [rbp+8] 是返回地址的地址。 [rbp+16]是参数的地址。
-
您最好努力寻找 64 位教程。 32 位和 64 位之间有很多差异,以至于您将浪费大量时间编写代码,最终不得不扔掉并重新编写代码,并学习不正确的习惯,然后您将不得不忘掉。 32 位代码中的某些内容将继承并提供对 64 位代码的有用洞察,但您必须成为专家才能知道它们是哪些内容。
标签: linux assembly x86-64 nasm function-parameter