x86-64机器代码,8个字节
受Bruce Forte解决方案的启发,但略低于标准杆。:-)
8D 07 lea eax, [rdi] ; put copy of input parameter in EAX
D1 EF shr edi, 1 ; shift LSB into CF
InfiniteLoop:
F3 73 FD rep jnc InfiniteLoop ; test CF; infinite loop back here if input was even
C3 ret ; return with original input in EAX if it was odd
EDI
遵循系统V AMD64调用约定,在寄存器中采用单个整数参数。
最初将复制此值并将其放入,EAX
以便在适当时可以将其返回。(因为我们需要一条奇数字节的指令,所以LEA
使用它来代替普通MOV
指令。)
然后,in的值EDI
右移1,这会将移出的位放入进位标志(CF)。如果数字为偶数,则该位为0;如果为奇数,则为1。
然后,我们使用JNC
指令测试CF,该指令仅在CF为0(即数字为偶数)时才跳转。这意味着我们将陷入偶数值的无限循环。对于奇数值,我们会掉线并EAX
返回原始值(in )。
JNC
但是,该指令有一个技巧—它有一个REP
前缀!通常,REP
前缀仅与字符串指令一起使用,但是由于Intel和AMD手册都同意REP
忽略无关的/多余的/冗余的前缀,因此我们在此处的分支指令上添加一个前缀以使其长度为3个字节。这样,在跳转指令中编码的相对偏移量也是奇数。(当然,REP
它本身也是一个奇数字节的前缀。)
谢天谢地,RET
它使用奇数字节编码!
在线尝试!
如果您认为返回的值不为奇数而返回一个无限循环(以至于您永不返回)满足挑战的“输出”要求,或者您只是想得到更有趣的东西,则可以使用以下函数将值输出到串行端口(当然,只有在奇数的情况下)。
x86-64机器代码(输出到串行端口),17个字节
8D 07 lea eax, [rdi] ; put copy of input parameter (EDI) in EAX
B1 F7 mov cl, 0xf7 ; put 0xF7 into low-order bits of CX
B5 03 mov ch, 0x03 ; put 0x03 into high-order bits of CX
FE C1 inc cl ; increment low-order bits of CX to 0xF8 (so all together it's now 0x3F8)
0F B7 D1 movzx edx, cx ; move CX to DX ("MOV DX, CX" would have a 16-bit prefix of 0x66)
D1 EF shr edi, 1 ; shift LSB of input parameter into CF
73 01 jnc IsEven ; test CF: branch if 0 (even), fall through if 1 (odd)
EF out dx, eax ; output EAX (original input) to I/O port 0x3F8 (in DX)
IsEven:
C3 ret ; return
使代码更有趣的是,代码执行了更多操作,这意味着使用仅使用奇数字节编码的指令来完成所有操作更具挑战性。当然,这也意味着它在代码高尔夫上失败了,所以这是一种折衷—您是否想要有趣和具有挑战性,还是想做空?
无论如何,这使用x86 OUT
指令写入I / O端口0x3F8,这是PC上的标准COM1串行端口。当然,有趣的部分是所有标准I / O端口(串行和并行)都具有偶数地址,因此不能简单地将它们编码为OUT
指令的立即数或直接移入寄存器。您必须使用小于实际值的值进行初始化,然后递增寄存器中的值。您还限于使用某些寄存器进行操作,因为当用作操作数时,需要在指令中使用奇数字节编码的寄存器。
另外,我必须在循环的顶部初始化DX
寄存器(通过CX
寄存器),即使仅在值是奇数时才需要这样做,以确保JNC
指令具有奇数偏移量。但是,由于我们要跳过的是OUT
指令,因此这些代码所做的只是浪费周期和破坏暂存器。它实际上不会输出任何内容,因此不会违反规则。
最后,此函数将返回(将输出完成或未完成输出到串行端口之后),输入值保留在中EAX
。但这实际上并没有违反任何规则。所有用汇编语言功能将与价值回报EAX
-the问题就是它是一个显著值或垃圾值。这是由函数的文档确定的(本质上,它是返回值还是返回void
),在这种情况下,我正在将其记录为未返回值。:-)
没有TIO链接,因为它没有实现到串行端口的输出。您将需要真正的铁力或想象力。