检查寄存器是否为 NASM 中的负 4 字节值

use*_*588 1 x86 assembly bit-manipulation nasm

我想检查I放入rax寄存器的值是否为负数或空 8 字节值(long intC 中的负数)。

它让我检查rax寄存器中的 64 位是否对应于有符号位值。

经过研究,我发现如果我们将base10中的-86之类的值的每一位反转并加1,我们得到反转值86。

基于此,负反转值在位方面将小于负值。

我正在x86_64 Linux上的NASM 中构建和运行代码。

我正在应用以下代码,当I为负时显示一条消息:

section .data
msg db "I is negative", 0

section .text
global main
extern printf, exit

%define I 9

main:
mov rax, I
; Invert the bits into rax 
xor rax, 0xFFFFFF
inc rax
mov rbx, I
cmp rax, rbx
jl  EXIT
; Display message when I is negative
lea rdi, [msg]
xor rax, rax
call printf

EXIT:
call exit
ret
Run Code Online (Sandbox Code Playgroud)

这是我编译NASM代码的方法:

nasm -f elf64 Program.s -o Program.o -Werror 
gcc Program.o -o a.out
Run Code Online (Sandbox Code Playgroud)

但是这个程序是错误的,因为它不能正常工作。

似乎我误解了检查寄存器是否包含负整数的方式。有什么帮助吗?

Pet*_*des 5

EAX 是 RAX 的低 4 字节。

test eax,eax               ; sets flags the same as cmp eax,0, like from eax-0
jnl  I_was_non_negative    ; jumps if EAX was *not* less-than 0.  Synonym for jge
Run Code Online (Sandbox Code Playgroud)

使用 CMP reg,0 与 OR reg,reg 测试寄存器是否为零?解释了为什么testcmp与零进行比较更有效。(主要是代码大小的 1 个字节)。

lESS-比条件测试SF和,但与零进行比较不会溢出,所以它相当于只是测试SF(符号标志,从结果的MSB集)。在这种情况下,您可以选择jnljns(不少于或未签名),或jge。选择具有您最喜欢的语义的任何一个。


int在 C 中是 4 个字节,而不是 8。(在所有标准的 32 位和 64 位 x86 ABI 中,包括您将在 Linux 上找到的 x86-64 System V)。


您尝试-I < I使用 2 的补码标识来实现(我认为)的问题
-x = ~x + 1

xor rax, 0xFFFFFF只翻转低 24 位(即 6F位,而不是 8 位)。

xor rax, 0xFFFFFFFF不可编码,因为它不适合 32 位符号扩展立即数。即使对于 64 位操作数大小,x86-64 仍然使用 8 或 32 位立即数,而不是 8 或 64,因为这将是非常臃肿的代码大小。有关可编码内容,请参阅https://felixcloutier.com/x86/XOR.html。(有一个mov r64, imm64,但这是唯一一个需要 64 位立即数的指令。)

因此,如果您使用xor eax, -1(或not eax) 而不是坚持使用 64 位操作数大小,那么也许您的奇怪代码可以用于比较-I < I. 但是翻转 64 位寄存器的低 32 位或 24 位,然后进行 64 位比较并没有帮助。高位将始终为零。

如果您使用了 32 位比较,那么您将遇到最大负数的问题。 0x80000000是它自己的 2 的反码。即neg eax会保持不变(并设置 OF 因为0 - 0x80000000导致有符号溢出回到负数)。

如果您在否定之前将 EAX 中的 4 字节输入符号扩展到 RAX 中的 8 个字节,那么它可能会起作用。

    mov     eax, I
    movsxd  rax, eax     ; or cdqe
   ; not     rax
   ; inc     rax
    neg     rax
    cmp     rax, I        ; use I as a sign-extended 32-bit immediate
    jl     I_is_positive  ; won't be taken for 0 either.
Run Code Online (Sandbox Code Playgroud)

注意正数和非负数的区别。 -I < I对于 I=0 是错误的,但是您询问检查是否为负(非负的反面,而不是正的反面)。


I 是一个组装时间常数

您可以使用NASM 预处理器来测试它。

default rel        ; always a good idea to use RIP-relative for static data

; %define I 9

extern puts
global main
main:
%if  I < 0
    lea rdi, [rel msg]
    xor eax, eax
    call puts              ; puts appends a newline
%endif

   xor  eax,eax           ; return 0. Otherwise we might as well jmp puts to tailcall it
   ret

section .rodata    ; read-only data can go here

msg:  db "I is negative", 0     ; colon after labels is always good style in NASM
Run Code Online (Sandbox Code Playgroud)

我注释掉了%define所以我可以在命令行上传递它:

$ nasm -felf64 -DI=0 nasm.asm  && gcc -no-pie nasm.o && ./a.out   # no output for I=0
$ nasm -felf64 -DI=9 nasm.asm  && gcc -no-pie nasm.o && ./a.out   # or I=9
$ nasm -felf64 -DI=-9 nasm.asm  && gcc -no-pie nasm.o && ./a.out 
I is negative

$ nasm -felf64 -DI=0x80000000 nasm.asm  && gcc -no-pie nasm.o && ./a.out  # NASM doesn't truncate to 32-bit 2's complement though.
$ nasm -felf64 -DI=0x8000000000000000 nasm.asm  && gcc -no-pie nasm.o && ./a.out # apparently it's 64-bit.
Run Code Online (Sandbox Code Playgroud)

我不得不使用,-no-pie因为我使用了call puts而不是call puts@plt或任何与 PIE 兼容的东西。出于某种原因,在制作 PIE 而不是位置相关的可执行文件时,链接器不会重写直接调用以使用 PLT。