32-бітний фрагмент машинного коду x86, 1 байт
48 dec eax
Введення в EAX, вихід у EAX: 0 для істинного, не нульове значення для помилкового. (Також прапор ZF залишається встановленим як істинний, невідомий для помилкового, щоб ви могли je was_equal
). Як "бонус", ви не повинні турбуватися про обгортання; 32-розрядний x86 може адресувати лише 4 Гбіт пам'яті, тому ви не можете зробити M достатньо великим, щоб обернутись навколо і знайти 1 == 2**32 + 1
щось подібне.
Щоб зробити функцію дзвінка, додайте 0xC3
ret
інструкцію після повторення 0x48
M разів. (Не зараховується до загальної кількості, оскільки для багатьох мов потрібно повторювати лише функціональне тіло або вираз, щоб мати можливість змагатися).
Викликається з GNU C за допомогою прототипу функції атрибута x86 функції __attribute__((regparm(1))) int checkeqM(int eax);
GNU Cregparm
, наприклад -mregparm
, використовує EAX для передачі першого цілого аргументу.
Наприклад, ця повна програма займає 2 аргументи, а JITs M копіює інструкцію + a ret
в буфер, а потім викликає її як функцію. (Потрібна виконувана купа; компілювати з gcc -O3 -m32 -z execstack
)
/******* Test harness: JIT into a buffer and call it ******/
// compile with gcc -O3 -no-pie -fno-pie -m32 -z execstack
// or use mprotect or VirtualProtect instead of -z execstack
// or mmap(PROT_EXEC|PROT_READ|PROT_WRITE) instead of malloc
// declare a function pointer to a regparm=1 function
// The special calling convention applies to this function-pointer only
// So main() can still get its args properly, and call libc functions.
// unlike if you compile with -mregparm=1
typedef int __attribute__((regparm(1))) (*eax_arg_funcptr_t)(unsigned arg);
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
if (argc<3) return -1;
unsigned N=strtoul(argv[1], NULL, 0), M = strtoul(argv[2], NULL, 0);
char *execbuf = malloc(M+1); // no error checking
memset(execbuf, 0x48, M); // times M dec eax
execbuf[M] = 0xC3; // ret
// Tell GCC we're about to run this data as code. x86 has coherent I-cache,
// but this also stops optimization from removing these as dead stores.
__builtin___clear_cache (execbuf, execbuf+M+1);
// asm("" ::: "memory"); // compiler memory barrier works too.
eax_arg_funcptr_t execfunc = (eax_arg_funcptr_t) execbuf;
int res = execfunc(N);
printf("%u == %u => %d\n", N,M, res );
return !!res; // exit status only takes the low 8 bits of return value
}
виконувані файли, що не належать до PIE , завантажуються нижче у віртуальну пам'ять; може зробити більший суміжний молоток.
$ gcc -g -O3 -m32 -no-pie -fno-pie -fno-plt -z execstack coderepeat-i386.c
$ time ./a.out 2747483748 2747483748 # 2^31 + 600000100 is close to as big as we can allocate successfully
2747483748 == 2747483748 => 0
real 0m1.590s # on a 3.9GHz Skylake with DDR4-2666
user 0m0.831s
sys 0m0.755s
$ echo $?
0
# perf stat output:
670,816 page-faults # 0.418 M/sec
6,235,285,157 cycles # 3.885 GHz
5,370,142,756 instructions # 0.86 insn per cycle
Зверніть увагу , що GNU C не підтримує об'єкт розміром більше ptrdiff_t
(32-розрядний), але malloc
і memset
зробити ще роботи, так що ця програма успішно.
Фрагмент машинного коду великого пальця ARM, 2 байти
3802 subs r0, #2
Перший аргумент r0
і значення повернення в r0
- це стандартна конвенція про виклик ARM. Тут також встановлюються прапори ( s
суфікс). Кумедний факт; версія, що не встановлює флаг, sub
- це 32-бітова інструкція.
Інструкція повернення, яку потрібно додати, - це bx lr
.
Фрагмент машинного коду AArch64, 4 байти
d1001000 sub x0, x0, #0x4
Працює для 64-бітних цілих чисел. Введення / виведення в x0
, відповідно до стандартного режиму виклику. int64_t foo(uint64_t);
AArch64 не має режиму великого пальця (поки що), тому 1 інструкція - це найкраще, що ми можемо зробити.
L
з'єднаний після себе,M
повинен повертати, чиN
рівний його вхідL*M
?