Код (Text): .file "dizasm" .text .globl _start .type _start, @function _start: .LFB3: pushl %ebp .LCFI0: movl %esp, %ebp .LCFI1: subl $16, %esp .LCFI2: movl $786432, -4(%ebp) leave ret .LFE3: .size _start, .-_start .section .eh_frame,"a",@progbits .Lframe1: .long .LECIE1-.LSCIE1 .LSCIE1: .long 0x0 .byte 0x1 .string "zP" .uleb128 0x1 .sleb128 -4 .byte 0x8 .uleb128 0x5 .byte 0x0 .long __start .byte 0xc .uleb128 0x4 .uleb128 0x4 .byte 0x88 .uleb128 0x1 .align 4 .LECIE1: .LSFDE1: .long .LEFDE1-.LASFDE1 .LASFDE1: .long .LASFDE1-.Lframe1 .long .LFB3 .long .LFE3-.LFB3 .uleb128 0x0 .byte 0x4 .long .LCFI0-.LFB3 .byte 0xe .uleb128 0x8 .byte 0x85 .uleb128 0x2 .byte 0x4 .long .LCFI1-.LCFI0 .byte 0xd .uleb128 0x5 .align 4 .LEFDE1: .ident "GCC: (GNU) 4.0.2 20051125 (Red Hat 4.0.2-8)" .section .note.GNU-stack,"",@progbits Мне просто интересно, можно ли это как-то использовать при отладке или оптимизации проги (я только начал изучать системное прг. Я прикладной программист (Java))
Great Когда-то это было программой, которая умножает два числа Как я понимаю, основные операции в этом блоке: Код (Text): pushl %ebp .LCFI0: movl %esp, %ebp .LCFI1: subl $16, %esp .LCFI2: movl $786432, -4(%ebp) leave ret или я не прав? Но тогда я не вижу в чем их смысл. push ebp -- сохраняем ebp; mov esp, ebp -- извлекаем из ebp и помещаем в esp sub $16, esp -- вычитаем... Где тут умножение?
практически любой компилятор Код (Text): int func( ) { return 2*3; } скомилирует примерно в Код (Text): push ebp mov ebp, esp mov eax, 6 leave retn ну может разве что без пролога и эпилога, но всегда константные выражения вычисляются на этапе компиляции они просто максимально упрощаются. например 2*3+a будет упрощено до 6+a А 4+5+b, где const int b = 6, будет упрощено до 15 сразу при компиляции.
Теперь все понятно, так как программа, которая читает значение с консоли и выводит его на экран с увеличением на 1 выглядит так: Код (Text): .file "mytest_prg1" .text .globl BIOS_STARTER_POINT .type BIOS_STARTER_POINT, @function BIOS_STARTER_POINT: .LFB3: pushl %ebp .LCFI0: movl %esp, %ebp .LCFI1: subl $24, %esp .LCFI2: andl $-16, %esp movl $0, %eax addl $15, %eax addl $15, %eax shrl $4, %eax sall $4, %eax subl %eax, %esp subl $12, %esp pushl $0 .LCFI3: call default_BIOS_INPUT addl $16, %esp movl %eax, -8(%ebp) movl default_BIOS, %eax movl default_BIOS_LENGTH, %edx movl -8(%ebp), %ecx incl %ecx subl $4, %esp pushl %eax pushl %edx pushl %ecx call default_BIOS_OUTPUT addl $16, %esp leave ret .LFE3: .size BIOS_STARTER_POINT, .-BIOS_STARTER_POINT
Тут даже видно какие функции вызываются - просто класс! Интересно, а можно ли вот так разобрать программу, изменить в этом тексте что-нить (например, константу или функцию) - и собрать обратно? Это было бы отлично!
можно. только геморно немного пересобирать будет. не с каждой программой выйдет. лучше править прямо бинарник
Еще один вопрос: (может после более детального прочтения документации я не буду таких задавать...) Я вскрывал разные исполняемые файлы. Если основная часть программы выполняется в блоках типа Код (Text): push ebp mov ebp, esp mov eax, 6 leave retn то зачем тогда нужны остальные инструкции? Неужели нет в мире компилера, способного писать программы без ? Ну то есть по минимуму: заголовок, сигнатура, исполняемая часть. Прога которая считает произведение двух чисел весит 15 килобайт! Зачем нужны инструкции типа: Код (Text): call default_BIOS_INPUT addl $16, %esp movl %eax, -8(%ebp) movl default_BIOS, %eax movl default_BIOS_LENGTH, %edx ? Или это просто особенности формата исполняемого файла (то есть меньше сделать нельзя?)
click_and_die В первом фрагменте кода компилятор (или программист, если этот код был написан изначально на ассемблере) создал стековый фрейм (push ebp / mov ebp, esp) и уничтожил (leave). Стековый фрейм обычно используется для хранения локальных переменных и ещё для адресации аргументов функции через ebp, что даёт небольшой выйгрыш в размере, по сравнению с прямой адресацией через esp, и гораздо удобнее. В данном примере никаких локальных переменных нет. Аргументов тоже нет. Оптимизирующий компилятор не только не задействовал бы стековый фрейм, но и заинлайнил бы автоматически данную функцию. Если речь о GCC, то нужно включить соответствующие ключи оптимизации (-O1, -O2 или -fomit-frame-pointer). Если же речь идёт о freepascal или другом подобном чуде, то... в общем, не судьба. Можно, конечно, издеваться над форматом экзешника, но в Линуксе это не очень актуально. Минимальный рабочий эльф занимает около 40-50 байт. Вот статья (есть ошибки): http://www.muppetlabs.com/~breadbox/software/tiny/teensy.html