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

Check that register is a negative 4 bytes value in NASM

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

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

经过研究,我发现如果将base10中的-86这样的值的每一位取反,再加1,就得到取反后的值86。

基于此,负反转值将小于负值的位数。

我正在 运行 NASM x86_64 Linux[ 中的代码=39=].

我正在应用以下代码,当 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

下面是我如何编译 NASM 代码:

nasm -f elf64 Program.s -o Program.o -Werror 
gcc Program.o -o a.out

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

看来我误解了检查寄存器是否包含负整数的方法。有帮助吗?

比那容易多了。对 64 位执行 test rax, rax 或对 32 位执行 test eax, eax
然后用条件指令检查Sign Flag (SF)。如果已设置,则数字为负数。

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

解释了为什么 test 在与零进行比较时比 cmp 稍微更有效。 (主要是代码大小的 1 个字节)。

ess-than条件测试SF和OF,但与零比较不会溢出,所以相当于只测试SF(符号标志,从结果的MSB开始设置)。在这种情况下,您可以选择 jnljns(不少于或未签名),或 jge。选择你最喜欢的语义。


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


您尝试使用 2 的补码身份
实施 -I < I(我认为)
时遇到问题 -x = ~x + 1

xor rax, 0xFFFFFF 仅翻转低 24 位(即 6 F 位,而不是 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.

注意正和非负的区别。 -I < I 对于 I=0 是假的,但是你问的是检查负数(非负数的反面,而不是正数的反面)。


I 是一个 assemble-时间常数

您可以使用the NASM preprocessor进行测试。

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

我注释掉了 %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.

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