из кода на Си —------------------nano asm.c #include <stdio.h> int main() { register int *foo asm ("r12") = 77; return *foo; } ------------------------------------получил следующий код: ---------------nano asm.s main: pushq %r12 xorl %eax, %eax movl $77, (%r12) popq %r12 ret ------------------------------------(и) objdump -d asm, выдает следующее: ~/C$ objdump -d asm asm: file format elf64-x86-64 Disassembly of section .init: 0000000000401000 <_init>: 401000: f3 0f 1e fa endbr64 401004: 48 83 ec 08 sub $0x8,%rsp 401008: 48 8b 05 e9 2f 00 00 mov 0x2fe9(%rip),%rax # 403ff8 <__gmon_start__@Base> 40100f: 48 85 c0 test %rax,%rax 401012: 74 02 je 401016 <_init+0x16> 401014: ff d0 call *%rax 401016: 48 83 c4 08 add $0x8,%rsp 40101a: c3 ret Disassembly of section .text: 0000000000401020 <main>: 401020: f3 0f 1e fa endbr64 401024: 41 54 push %r12 401026: 31 c0 xor %eax,%eax 401028: 41 c7 04 24 4d 00 00 movl $0x4d,(%r12) 40102f: 00 401030: 41 5c pop %r12 401032: c3 ret 401033: 66 2e 0f 1f 84 00 00 cs nopw 0x0(%rax,%rax,1) 40103a: 00 00 00 40103d: 0f 1f 00 nopl (%rax) 0000000000401040 <_start>: 401040: f3 0f 1e fa endbr64 401044: 31 ed xor %ebp,%ebp 401046: 49 89 d1 mov %rdx,%r9 401049: 5e pop %rsi 40104a: 48 89 e2 mov %rsp,%rdx 40104d: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp 401051: 50 push %rax 401052: 54 push %rsp 401053: 45 31 c0 xor %r8d,%r8d 401056: 31 c9 xor %ecx,%ecx 401058: 48 c7 c7 20 10 40 00 mov $0x401020,%rdi 40105f: ff 15 8b 2f 00 00 call *0x2f8b(%rip) # 403ff0 <__libc_start_main@GLIBC_2.34> 401065: f4 hlt 401066: 66 2e 0f 1f 84 00 00 cs nopw 0x0(%rax,%rax,1) 40106d: 00 00 00 0000000000401070 <_dl_relocate_static_pie>: 401070: f3 0f 1e fa endbr64 401074: c3 ret 401075: 66 2e 0f 1f 84 00 00 cs nopw 0x0(%rax,%rax,1) 40107c: 00 00 00 40107f: 90 nop 0000000000401080 <deregister_tm_clones>: 401080: b8 28 40 40 00 mov $0x404028,%eax 401085: 48 3d 28 40 40 00 cmp $0x404028,%rax 40108b: 74 13 je 4010a0 <deregister_tm_clones+0x20> 40108d: b8 00 00 00 00 mov $0x0,%eax 401092: 48 85 c0 test %rax,%rax 401095: 74 09 je 4010a0 <deregister_tm_clones+0x20> 401097: bf 28 40 40 00 mov $0x404028,%edi 40109c: ff e0 jmp *%rax 40109e: 66 90 xchg %ax,%ax 4010a0: c3 ret 4010a1: 66 66 2e 0f 1f 84 00 data16 cs nopw 0x0(%rax,%rax,1) 4010a8: 00 00 00 00 4010ac: 0f 1f 40 00 nopl 0x0(%rax) 00000000004010b0 <register_tm_clones>: 4010b0: be 28 40 40 00 mov $0x404028,%esi 4010b5: 48 81 ee 28 40 40 00 sub $0x404028,%rsi 4010bc: 48 89 f0 mov %rsi,%rax 4010bf: 48 c1 ee 3f shr $0x3f,%rsi 4010c3: 48 c1 f8 03 sar $0x3,%rax 4010c7: 48 01 c6 add %rax,%rsi 4010ca: 48 d1 fe sar %rsi 4010cd: 74 11 je 4010e0 <register_tm_clones+0x30> 4010cf: b8 00 00 00 00 mov $0x0,%eax 4010d4: 48 85 c0 test %rax,%rax 4010d7: 74 07 je 4010e0 <register_tm_clones+0x30> 4010d9: bf 28 40 40 00 mov $0x404028,%edi 4010de: ff e0 jmp *%rax 4010e0: c3 ret 4010e1: 66 66 2e 0f 1f 84 00 data16 cs nopw 0x0(%rax,%rax,1) 4010e8: 00 00 00 00 4010ec: 0f 1f 40 00 nopl 0x0(%rax) 00000000004010f0 <__do_global_dtors_aux>: 4010f0: f3 0f 1e fa endbr64 4010f4: 80 3d 2d 2f 00 00 00 cmpb $0x0,0x2f2d(%rip) # 404028 <__TMC_END__> 4010fb: 75 13 jne 401110 <__do_global_dtors_aux+0x20> 4010fd: 55 push %rbp 4010fe: 48 89 e5 mov %rsp,%rbp 401101: e8 7a ff ff ff call 401080 <deregister_tm_clones> 401106: c6 05 1b 2f 00 00 01 movb $0x1,0x2f1b(%rip) # 404028 <__TMC_END__> 40110d: 5d pop %rbp 40110e: c3 ret 40110f: 90 nop 401110: c3 ret 401111: 66 66 2e 0f 1f 84 00 data16 cs nopw 0x0(%rax,%rax,1) 401118: 00 00 00 00 40111c: 0f 1f 40 00 nopl 0x0(%rax) 0000000000401120 <frame_dummy>: 401120: f3 0f 1e fa endbr64 401124: eb 8a jmp 4010b0 <register_tm_clones> Disassembly of section .fini: 0000000000401128 <_fini>: 401128: f3 0f 1e fa endbr64 40112c: 48 83 ec 08 sub $0x8,%rsp 401130: 48 83 c4 08 add $0x8,%rsp 401134: c3 ret ------------------------------------------------------- 0000000000401020 <main> - это вроде понятно... но откуда появляется код в остальных частях (0000000000401120 <frame_dummy> и тд)??? И! код в <_fini> - реально нужен/для чего этот "плюс минус"? Где об этом можно почитать?
Комментариях к исходникам вашего компилятора. Все о чем вы спрашиваете это самодеятельность компилятора и/или требование текущей версии glibc. В остальном код довольно прост. Тут вызывается start, который выполняет main из glibc, чтобы инициализировать библиотеку и после уже glibc возвращает управление программе на точку main.
Да - думаю все так... но! 1 - через "комментарии кода компилятора" это циклопическая задача = невозможно для отдельно взятого человека. 2 - забыл упомянуть что это код ELF файла linux...т.е. это тоже накладывает доп инфу/вопросы. 3 - "выполняет main из glibc, чтобы инициализировать библиотеку и после уже glibc " скорее всего то что происходит можно так описать... но для меня это звучит: "кто на ком стоял?"(glibc и есть библиотека)/ не понятно... поэтому решил начать с конца: "код в <_fini> - реально нужен/для чего этот "плюс минус"???? : Код (Text): 0000000000401128 <_fini>: 401128: f3 0f 1e fa endbr64 40112c: 48 83 ec 08 sub $0x8,%rsp 401130: 48 83 c4 08 add $0x8,%rsp 401134: c3 ret endbr64 — часть технологии Intel Control-Flow Enforcement (CET), которая обеспечивает аппаратную защиту от атак, манипулирующих потоком управления, чтобы использовать существующий код в злонамеренных целях. этот код (sub $0x8,%rsp) часть механизма защиты ? --- Сообщение объединено, 17 фев 2025 в 06:48 --- это тоже хороший вопрос... уже можно идти читать комментарии кода библиотеки ) шучу.... Код (C): int main() { register int *foo asm ("r12") = 77; return *foo; } заголовок можно убрать...но ggc14 не будет компилировать, а вот gcc12 например будет: Код (ASM): main: pushq %rbp movq %rsp, %rbp pushq %r12 movl $77, %r12d movq %r12, %rax movl (%rax), %eax movq -8(%rbp), %r12 leave ret (очевидно что никаких вызовов нет...это про библиотеки) думаю это благодаря не стандартизированному обращению к регистрам ЦП : asm ("r12") и это не суть... код выбирался максимально простой намеренно и плюсом к тому - содержащий "фундаментальный касяк программирования на Си"(register int *foo asm ("r12") = 77; - прямое обращение к регистрам). Еще добавлю: по моему endbr64 добавлен для того что бы именно такой код не работал: Код (C): #include <stdio.h> #pragma code_seg(".shell") __declspec(allocate(".shell")) static const unsigned char s_readCs[] { 0x66, 0x8C, 0xC8, // mov ax, cs 0xC3 // ret }; typedef unsigned short (*ReadCs)(); unsigned short _asm_read_cs() { return ((ReadCs)&s_readCs[0])(); } int main() { printf("0x%X\n", _asm_read_cs()); return 0; } очень интересно мнение автора кода и как он будет выглядеть на GCC?
Нужно понимать, что C/C++ это не языки в вакууме как ассемблер - у них есть подлежащая инфраструктура которая libc. Особенно сложно с этим в C++ где есть new/delete и механизм исключений, но даже в plain C есть проблемки - например операция копирования a = b может привести для сложных типов к call _some_inner_libc_func. И первая линия обвязки происходит вокруг функции main - это не настоящая точка входа в исполнимый файл - на самом деле настоящая точка входа находится внутри libc и в ней происходит масса нужных вещей таких как инициализация глобальных переменных, подготовка параметров командной строки так как их ожидает int main(int argc, char **argv) и вопросы инициализации многопоточности и всякое такое. Где то под конец настоящей точки входа находится call main <--- наш main, а после еще всякие деинициализации и завершения занятых у оси ресурсов в обратном порядке. Строго говоря в изначальном языке C функция main не просто не особенная, а самая бытовая-типовая вообще ничем совершенно не отличающаяся от прочих пользовательских функций кроме того специфического факта, что где то в недрах libc есть call main - а в соответствии со соглашением о вызовах функций C нет ничего страшного если она будет заявлена без своих правильных параметров и не будет к ним прикасаться - чистит от параметров стек вызывающий код, поэтому int main(), int main(int argc) и void main(int argc, char **argv) спокойно работают разве что в последнем случае еще результат будет случайно-мусорный (а это уже реально неаккуратно в POSIX). Не знаю насчёт nano C, но все полновесные компиляторы типа clang/gcc/msvc имеют опции компиляции/линкера типа nostdlib и entrypoint чтобы можно было избавится от всего этого, но ценой того, что собственно функциями libc зачастую станет невозможно пользоваться - многие из них требуют правильной вот этой вот инициализации. Если иметь это ввиду, то можно делать экзешники на C размером сотни байт и тому подобные трюки.
если "nano C" это из моей писанины то - nanao это текстовый редактор...и у меня привычка писать nano в начале с именем файла так как очень часто делаю "копипасту" этого : nano hello.S , к примеру для создания ф. с кодом на ассемблере... речь о GCC , G++ linux во всех случаях кроме ISPC (указываю всегда). "plain C" - что вы имели в виду? и если знаете (есть) - можно по примеру с точкой входа _start привести пример для входа в libc? PS например так : gcc -static -no-pie -Os -m64 -fno-asynchronous-unwind-tables -fno-dwarf2-cfi-asm -Wall -S asm.c -o asm.s gcc -static -no-pie -Wall asm.s -o asm
Си без плюсов, когда не g++, а gcc. Ну вот тут есть упраженения на тему: https://www.opennet.ru/base/dev/smallest_code.txt.html но в целом тема довольно глубокая и в двух словах не расскажешь.