f(){puts('@C');}
Обратите внимание, что есть байт STX ( 0x02 ) между @
и C
.
Попробуйте онлайн!
портативность
Это было протестировано с gcc 6.3.1 и clang 3.9.1 на Fedora 25, gcc 4.8.4 на Ubuntu 14.04.4 и gcc 4.8.3 на openSUSE 13.2, где он выводит следующий вывод.
inux-x86-64.so.2
Я ожидаю, что это приведет к одинаковому выводу со всеми версиями gcc, при условии, что он компилируется в исполняемый файл следующего типа.
ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2
Разным платформам потребуется разный адрес памяти и, возможно, другой порядок байтов в многосимвольной символьной константе.
Например, замена @\2C
на @\2\4
печать exec/ld-elf.so.1
и перевод строки во FreeBSD 11 на clang 3.8.0.
Проверка в автономном режиме
$ printf "%b\n" "f(){puts('@\2C');}main(){f();}" > quine.c
$ gcc -w -o quine quine.c
$ ./quine
inux-x86-64.so.2
$ ./quine | wc -c
17
Как это устроено
По умолчанию ld использует 0x400000 в качестве базового адреса текстового сегмента, что означает, что мы можем найти содержимое ELF, начиная с адреса памяти 0x400000 .
Первые 640 байтов ELF в значительной степени не зависят от фактического исходного кода. Например, если объявление о й следует main(){f();}
и ничего, они выглядят следующим образом .
00000000: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 .ELF............
00000010: 02 00 3e 00 01 00 00 00 00 04 40 00 00 00 00 00 ..>.......@.....
00000020: 40 00 00 00 00 00 00 00 e8 19 00 00 00 00 00 00 @...............
00000030: 00 00 00 00 40 00 38 00 09 00 40 00 1e 00 1b 00 ....@.8...@.....
00000040: 06 00 00 00 05 00 00 00 40 00 00 00 00 00 00 00 ........@.......
00000050: 40 00 40 00 00 00 00 00 40 00 40 00 00 00 00 00 @.@.....@.@.....
00000060: f8 01 00 00 00 00 00 00 f8 01 00 00 00 00 00 00 ................
00000070: 08 00 00 00 00 00 00 00 03 00 00 00 04 00 00 00 ................
00000080: 38 02 00 00 00 00 00 00 38 02 40 00 00 00 00 00 8.......8.@.....
00000090: 38 02 40 00 00 00 00 00 1c 00 00 00 00 00 00 00 8.@.............
000000a0: 1c 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 ................
000000b0: 01 00 00 00 05 00 00 00 00 00 00 00 00 00 00 00 ................
000000c0: 00 00 40 00 00 00 00 00 00 00 40 00 00 00 00 00 ..@.......@.....
000000d0: 04 07 00 00 00 00 00 00 04 07 00 00 00 00 00 00 ................
000000e0: 00 00 20 00 00 00 00 00 01 00 00 00 06 00 00 00 .. .............
000000f0: 08 0e 00 00 00 00 00 00 08 0e 60 00 00 00 00 00 ..........`.....
00000100: 08 0e 60 00 00 00 00 00 1c 02 00 00 00 00 00 00 ..`.............
00000110: 20 02 00 00 00 00 00 00 00 00 20 00 00 00 00 00 ......... .....
00000120: 02 00 00 00 06 00 00 00 20 0e 00 00 00 00 00 00 ........ .......
00000130: 20 0e 60 00 00 00 00 00 20 0e 60 00 00 00 00 00 .`..... .`.....
00000140: d0 01 00 00 00 00 00 00 d0 01 00 00 00 00 00 00 ................
00000150: 08 00 00 00 00 00 00 00 04 00 00 00 04 00 00 00 ................
00000160: 54 02 00 00 00 00 00 00 54 02 40 00 00 00 00 00 T.......T.@.....
00000170: 54 02 40 00 00 00 00 00 44 00 00 00 00 00 00 00 T.@.....D.......
00000180: 44 00 00 00 00 00 00 00 04 00 00 00 00 00 00 00 D...............
00000190: 50 e5 74 64 04 00 00 00 b0 05 00 00 00 00 00 00 P.td............
000001a0: b0 05 40 00 00 00 00 00 b0 05 40 00 00 00 00 00 ..@.......@.....
000001b0: 3c 00 00 00 00 00 00 00 3c 00 00 00 00 00 00 00 <.......<.......
000001c0: 04 00 00 00 00 00 00 00 51 e5 74 64 06 00 00 00 ........Q.td....
000001d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000001e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000001f0: 00 00 00 00 00 00 00 00 10 00 00 00 00 00 00 00 ................
00000200: 52 e5 74 64 04 00 00 00 08 0e 00 00 00 00 00 00 R.td............
00000210: 08 0e 60 00 00 00 00 00 08 0e 60 00 00 00 00 00 ..`.......`.....
00000220: f8 01 00 00 00 00 00 00 f8 01 00 00 00 00 00 00 ................
00000230: 01 00 00 00 00 00 00 00 2f 6c 69 62 36 34 2f 6c ......../lib64/l
00000240: 64 2d 6c 69 6e 75 78 2d 78 38 36 2d 36 34 2e 73 d-linux-x86-64.s
00000250: 6f 2e 32 00 04 00 00 00 10 00 00 00 01 00 00 00 o.2.............
00000260: 47 4e 55 00 00 00 00 00 02 00 00 00 06 00 00 00 GNU.............
00000270: 20 00 00 00 04 00 00 00 14 00 00 00 03 00 00 00 ...............
Использование, например, main(int c, char**v){f();}
вместо этого изменяет несколько байтов, но не смещение строки /lib64/ld-linux-x86-64.so.2
, которое мы будем использовать для вывода.
Смещение указанной строки составляет 0x238 и его длина составляет 27 байтов. Мы хотим вывести только 17 байтов (и последний будет новой строкой , если мы будем использовать puts
), поэтому мы добавляем 11 к смещению, чтобы получить смещение 0x243inux-x86-64.so.2
. Добавление 0x400000 и 0x243 дает 0x400243 , место в памяти inux-x86-64.so.2
.
Чтобы получить этот адрес памяти, мы можем использовать многосимвольные символьные константы, которые демонстрируют поведение, определяемое реализацией. 0x400243 - это (64) (2) (67) в базе 256, а многосимвольные константы символов в gcc используют порядок байтов с прямым порядком байтов, поэтому '@\2C'
выдает адрес памяти нужной строки.
Наконец, puts
печатает строку с нулевым символом в этой ячейке памяти и завершающий символ новой строки, создавая 17 байтов вывода.