检查寄存器是否为 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开始设置)。在这种情况下,您可以选择 jnl
或 jns
(不少于或未签名),或 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。
我想检查 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开始设置)。在这种情况下,您可以选择 jnl
或 jns
(不少于或未签名),或 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。