Количество выполненных Инструкций отличается для программы Hello World Nasm Assembly и C
У меня есть простой отладчик (используя ptrace: http://pastebin.com/D0um3bUi) для подсчета количества инструкций, выполненных для данной входной исполняемой программы. Для подсчета команд используется одностадийный режим выполнения ptrace.
Для этого, когда исполняемый файл программы 1) (a.out из gcc main.c) указан в качестве входных данных для моего тестового отладчика, он печатает около 100 тыс. В качестве выполненных инструкций. Когда я использую -static
Опция дает 10681 инструкцию.
Теперь в 2) я создаю программу сборки и использую NASM для компиляции и компоновки, а затем, когда этот исполняемый файл задан в качестве входных данных для отладчиков теста, он показывает 8 команд в качестве числа и является подходящим.
Количество инструкций, выполненных в программе 1), велико из-за связи программы с системной библиотекой во время выполнения? используется -static и который уменьшает количество в 1/10. Как я могу убедиться, что счетчик команд соответствует только основной функции в Программе 1) и как Программа 2) сообщает об этом отладчику?
1)
#include <stdio.h>
int main()
{
printf("Hello, world!\n");
return 0;
}
Я использую gcc для создания исполняемого файла.
2)
; 64-bit "Hello World!" in Linux NASM
global _start ; global entry point export for ld
section .text
_start:
; sys_write(stdout, message, length)
mov rax, 1 ; sys_write
mov rdi, 1 ; stdout
mov rsi, message ; message address
mov rdx, length ; message string length
syscall
; sys_exit(return_code)
mov rax, 60 ; sys_exit
mov rdi, 0 ; return 0 (success)
syscall
section .data
message: db 'Hello, world!',0x0A ; message and newline
length: equ $-message ; NASM definition pseudo-
nasm -f elf64 -o main.o -s main.asm
ld -o main main.o
2 ответа
Количество инструкций, выполненных в программе 1), велико из-за связи программы с системной библиотекой во время выполнения?
Да, динамическое связывание плюс файлы запуска CRT (C runtime).
используемый
-static
и который уменьшает количество в 1/10.
Так что просто оставили стартовые файлы CRT, которые делают вещи перед вызовом main
, и после.
Как я могу убедиться, что счетчик команд является только счетчиком основной функции в Программе 1)`
Мера пустая main
затем вычтите это число из будущих измерений.
Если ваши счетчики команд не будут умнее и не будут искать символы в исполняемом файле для процесса, который он отслеживает, он не сможет определить, откуда и откуда пришел код.
и как программа 2) сообщает отладчику.
Это потому, что в этой программе нет другого кода. Дело не в том, что вы каким-то образом помогли отладчику игнорировать некоторые инструкции, а в том, что вы создали программу без каких-либо инструкций, которые вы не поместили туда самостоятельно.
Если вы хотите увидеть, что на самом деле происходит, когда вы запускаете вывод gcc, gdb a.out
, b _start
, r
и одношаговый. Как только вы углубитесь в дерево вызовов, вы, вероятно, собирается хотеть использовать fin
завершить выполнение текущей функции, так как вы не хотите пошагово проходить буквально 1 миллион инструкций или даже 10 тыс.
Питер дал очень хороший ответ, и я собираюсь продолжить с ответом, который является достойным и может получить некоторые отрицательные голоса. При прямой связи с LD или косвенно с GCC точкой входа по умолчанию для исполняемых файлов ELF является метка _start
,
Ваш код NASM использует глобальную метку _start
поэтому, когда ваша программа будет запущена, первый код в вашей программе будет инструкцией _start
, При использовании GCC типичной точкой входа вашей программы является функция main
, Что скрыто от вас, так это то, что ваша программа на С также имеет _start
метка, но она предоставляется объектами запуска среды выполнения C.
Теперь возникает вопрос - есть ли способ обойти файлы запуска C, чтобы избежать кода запуска? Технически да, но это опасная территория, которая может привести к неопределенному поведению. Если вы любите приключения, вы можете попросить GCC изменить точку входа в вашу программу с помощью -e
опция командной строки. Скорее, чем _start
мы могли бы сделать нашу точку входа main
в обход кода запуска C Так как мы обходим код запуска C, мы также можем обойтись без ссылки в коде запуска среды выполнения C с помощью -nostartfiles
вариант.
Вы можете использовать эту командную строку для компиляции вашей C- программы:
gcc test.c -e main -nostartfiles
К сожалению, в коде C есть кое-что, что нужно исправить. Обычно при использовании объектов запуска среды выполнения C после инициализации среды выполняется CALL для main
, Обычно main
выполняет инструкцию RET, которая возвращает код выполнения C В этот момент среда выполнения C грациозно выходит из вашей программы. RET некуда возвращаться, когда -nostartfiles
опция используется, так что, скорее всего, это будет segfault. Чтобы обойти это, мы можем вызвать библиотеку C _exit
функция для выхода из нашей программы.
#include <stdio.h>
int main()
{
printf("Hello, world!\n");
_exit(0); /* We exit application here, never reaching the return */
return 0;
}
Если вы не пропустите указатели фреймов, GCC выпустит несколько дополнительных инструкций для настройки стекового фрейма и его удаления, но издержки минимальны.
Специальное примечание
Процесс выше, похоже, не работает для статических сборок (-static
опция в GCC) со стандартной библиотекой glibc C. Это обсуждается в этом ответе Stackru. Динамическая версия работает, потому что общий объект может зарегистрировать функцию, которая вызывается динамическим загрузчиком для выполнения инициализации. При статическом построении это обычно выполняется средой выполнения C, но мы пропустили эту инициализацию. Из-за этого GLIBC функционирует как printf
может потерпеть неудачу. Существуют заменяющие библиотеки C, совместимые со стандартами, которые могут работать без инициализации среды выполнения C. Одним из таких продуктов является MUSL.
Установка MUSL в качестве альтернативы GLIBC
В 64-битной Ubuntu эти команды должны собрать и установить 64-битную версию MUSL:
git clone git://git.musl-libc.org/musl
cd musl
./configure --prefix=/usr/local/musl/x86-64
make
sudo make install
Затем вы можете использовать оболочку MUSL для GCC для работы с библиотекой CMUSL вместо библиотеки GLIBC по умолчанию в большинстве дистрибутивов Linux. Параметры аналогичны GCC, поэтому вы должны быть в состоянии сделать:
/usr/local/musl/x86-64/bin/musl-gcc -e main -static -nostartfiles test.c
При беге ./a.out
сгенерированный с GLIBC, это, вероятно, segfault. MUSL не требует инициализации перед использованием большинства функций библиотеки C, поэтому он должен работать даже с -static
ОпцияGCC.
Более справедливое сравнение
Одной из проблем вашего сравнения является то, что вы вызываете системный вызов SYS_WRITE непосредственно в NASM, в C, который вы используете printf
, Пользователь EOF правильно прокомментировал, что вы можете сделать это более справедливым сравнением, вызвав write
функция в С вместо printf
, write
имеет гораздо меньше накладных расходов. Вы можете изменить свой код так:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
char *str = "Hello, world\n";
write (STDOUT_FILENO, str, 13);
_exit(0);
return 0;
}
Это будет иметь больше издержек, чем прямой системный вызов SYS_WRITE NASM, но гораздо меньше, чем printf
будет генерировать.
Я собираюсь выпустить предупреждение о том, что такой код и хитрость, скорее всего, не будут приняты во внимание при рассмотрении кода, за исключением некоторых незначительных случаев разработки программного обеспечения. Я не думаю, что мне нужно сказать вам, какой обзор кода является представителем этой ситуации: