【问题标题】:ASM x64 function pointer not returning the good valueASM x64 函数指针未返回正确值
【发布时间】:2020-11-07 00:32:50
【问题描述】:

我在汇编中遇到函数指针问题,即使我的函数返回负数,它总是将rax 设置为正数,我用一个比较两个整数的函数做了一个最小的可复制示例,它的作用相同事情:

ASM 功能代码[编辑]:

global foo

section .data
msg: db `superior\n`
msg_len: equ $-msg

section .text
foo:
    push rbx
    mov rbx, rdi
    mov rdi, 2
    mov rsi, 1
    sub rsp, 8  ; align the stack frame
    call rbx
    add rsp, 8
    test rax, rax   ;[EDIT] correct: test eax, eax
    js  bar
    mov rax, 1
    mov rdi, 1
    mov rsi, msg
    mov rdx, msg_len
    syscall

bar:
    mov rdi, 1
    mov rsi, 2
    sub rsp, 8  ; same here
    call rbx
    add rsp, 8
    test rax, rax  ;[EDIT] correct: test eax, eax
    js exit
    mov rax, 1
    mov rdi, 1
    mov rsi, msg
    mov rdx, msg_len
    syscall

exit:
    pop rbx   ;restoring initial data of rbx
    ret

main.c 代码:

#include <stdio.h>

int foo(int (*f)());  //my asm function prototype

int cmp(int i, int j)
{
  printf("%d - %d\n", i, j);
  return(i - j);
}

int main(void)
{
  foo(&cmp);
  return (0);
}

输出是:

2 - 1
superior
1 - 2
superior

但应该只是:

2 - 1
superior

编译:

nasm -f elf64 foo.s
gcc -c main.c -o main.o
gcc main.o foo.o

感谢您的帮助

[编辑] 它不起作用,因为我检查了 rax 而不是 eax,现在它起作用了。感谢您的帮助

【问题讨论】:

  • 你的跳转指令不应该是标签foo中的JG吗? JA 不检查标志标志。
  • @The_aLiEn 也许吧,但 ja 不是代表“跳到上面”吗?
  • @The_aLiEn 我检查了 jg 也做了同样的事情
  • 您的代码有多个 ABI 违规。它正在破坏属于调用者的rbx,没有恢复它,并使用rsp 未对齐mod 16 进行函数调用。我不确定这些是否是你所看到的来源,但真的有如果没有固定它们,它就没有希望可靠地工作。
  • @Fayeure:阅读wiki.osdev.org/System_V_ABI,“x86-64”部分,了解调用约定。这些是必须了解和遵循的。 rbx 是一个被调用者保存的寄存器,你必须保存它;也就是说,您必须确保当您的函数返回时,rbx 包含与输入函数时相同的值。您可以在函数开始时将其推入堆栈并在结束时将其弹出(这将代替您的堆栈调整)。但更简单的是使用调用者保存的“临时”寄存器代替; rax, rcx, rdx, r8, r9, r10, r11 中的任何一个都可以。

标签: c assembly x86-64 nasm function-pointers


【解决方案1】:

int 是 32 位,但 rax 是 64 位寄存器。返回int 的函数会将其返回值放入eax,这通常会将rax 的高半部分归零。所以如果cmp返回-1,这是32位数字0xffffffff,那么rax将包含0x00000000ffffffff。这不是一个负数 64 位数字,所以test rax, rax 不会设置符号标志。

尝试使用test eax, eax 作为您的测试。

【讨论】:

  • 谢谢你是 Stack Overflow 之神,原来是这样:D
【解决方案2】:

您的代码似乎太复杂了。

首先,让我们用 C 之类的语言编写要做的事情:

int foo(int (*f)()) {
  if (cmp(2, 1) > 0) {
    PRINT;
  }
  if (cmp(1, 2) > 0) {
    PRINT;
  }
}

那么,我们就按照这个来写汇编代码吧:

global foo

section .data
msg: db `superior\n`
msg_len: equ $-msg

section .text
  ; int foo(int (*f)()) {
foo:
  mov rbx, rdi   ; function pointer stored in rbx
  ;   if (cmp(2, 1) > 0) {
  mov rdi, 2     ; first integer
  mov rsi, 1     ; second integer
  call rbx       ; call function pointer
  cmp rax, 0
  jle bar        ; jump if rdi <= rsi (signed)
  ;     PRINT;
  mov rax, 1
  mov rdi, 1
  mov rsi, msg
  mov rdx, msg_len
  syscall        ; write "superior\n"
  ;   }
bar:
  ;   if (cmp(1, 2) > 0) {
  mov rdi, 1
  mov rsi, 2
  call rbx
  cmp rax, 0
  jle bar2
  ;     PRINT;
  mov rax, 1
  mov rdi, 1
  mov rsi, msg
  mov rdx, msg_len
  syscall        ; write "superior\n"
  ;   }
bar2:
  ; }
  ret

要保留您的代码,需要修复的要点是:

  • ja 用于无符号比较。 jg 应该用于签名比较。
  • bar2后面有代码要打印,但ja bar2没有跳转后也有代码要打印。您应该在 bar: 之前添加 ret 以防止执行此操作。

【讨论】:

  • 已编辑,我在帖子中手动编写了代码,因为我在虚拟机上我忘记了那行但不是那个
  • 我想比较两次这就是为什么jmp foo2
  • 这不是因为我忘记了cmp,它与cmp rax, 0做同样的事情
  • @Fayeure 您的cmp 将打印x - x,因此您应该进行一次比较而不进行比较两次,同时仅打印一次x - x
  • 我打印 x - x 只是为了表明它调用了函数但没有返回好的值,没有输出只是 superior superior 和我有同样的问题
猜你喜欢
  • 2019-11-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-01
  • 1970-01-01
  • 1970-01-01
  • 2017-09-05
  • 2018-03-07
相关资源
最近更新 更多