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 з брязкотом 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 значною мірою не залежать від фактичного вихідного коду. Наприклад, якщо після декларації f дотримується 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 до зміщення, щоб отримати 0x243 , зміщення inux-x86-64.so.2
. Додавання 0x400000 та 0x243 дає 0x400243 , місце в пам'яті inux-x86-64.so.2
.
Для отримання цієї адреси пам'яті ми можемо використовувати багатосимвольні константи символів, які демонструють поведінку, визначену реалізацією. 0x400243 - це (64) (2) (67) в базовій 256, а багатосимвольні константи символів gcc використовують порядок байтів великих розмірів, тому отримує '@\2C'
адресу пам'яті потрібного рядка.
Нарешті, puts
друкується (нульове закінчення) жало в цьому місці пам'яті та кінцевій новій лінії, створюючи 17 байт виводу.