Количество выполненных инструкций для программы Hello World Nasm Assembly и C
У меня есть простой отладчик (с помощью ptrace: http://pastebin.com/D0um3bUi), чтобы подсчитать количество инструкций, выполненных для данной исполняемой программы ввода. Он использует режим однократного выполнения ptrace для подсчета инструкций.
Для этого, когда программа 1) исполняемый файл (a.out из gcc main.c) указан как входной сигнал для моего тестового отладчика, он печатает около 100 тыс. по мере выполнения инструкций. Когда я использую параметр -static
, он дает 10681 инструкцию.
Теперь в 2) я создаю программу сборки и использую NASM для компиляции и компоновки, а затем, когда этот исполняемый файл передается в качестве ввода отладчиков, он отображает 8 инструкций как счетчик, а apt.
Число инструкций, выполняемых в программе 1), является высоким из-за того, что программа связана с системной библиотекой во время выполнения? используется -статический и который уменьшает счетчик в 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
Ответы
Ответ 1
Количество инструкций, выполняемых в программе 1), является высоким из-за ссылки программы на системную библиотеку во время выполнения?
Да, динамические ссылки и файлы запуска CRT (C runtime).
используется -static
и который уменьшает счетчик в 1/10.
Итак, просто оставив начальные файлы CRT, которые делают вещи перед вызовом main
и после.
Как я могу убедиться, что количество команд - это только основная функция в программе 1) `
Измерить пустой main
, а затем вычесть это число из будущих измерений.
Если ваши счетчики команд более умны и не смотрят на символы в исполняемом файле для трассировки процесса, он не сможет определить, откуда пришел код.
и как программа 2) сообщает отладчику.
Это потому, что в этой программе нет другого кода. Это не то, что вы каким-то образом помогли отладчику проигнорировать некоторые инструкции, так что вы сделали программу без каких-либо инструкций, которые вы не поместили там сами.
Если вы хотите увидеть, что на самом деле происходит, когда вы запускаете вывод gcc, gdb a.out
, b _start
, r
и одношаговый. Как только вы углубитесь в дерево вызовов, вы сомневаетесь. захотите использовать fin
для завершения выполнения текущей функции, так как вы не хотите выполнять однократное выполнение 1 миллион инструкций или даже 10k.
Ответ 2
Питер дал очень хороший ответ, и я собираюсь продолжить с ответом, который является достойным и может получить некоторые отрицательные голоса. При прямой связи с LD или косвенно с GCC точкой входа по умолчанию для исполняемых файлов ELF является метка _start
.
В вашем коде NASM используется глобальная метка _start
поэтому при _start
вашей программы первый код в вашей программе будет инструкцией _start
. При использовании GCC вашей программой типичной точкой входа является функция main
. Что скрыто от вас, так это то, что ваша C-программа также имеет метку _start
но она предоставляется объектами запуска C-среды выполнения.
Теперь возникает вопрос - есть ли способ обойти файлы запуска C, чтобы избежать кода запуска? Технически да, но это опасная территория, которая может привести к неопределенному поведению. Если вы любите приключения, вы можете сказать GCC изменить точку входа вашей программы с помощью -e
командной строки -e
. Вместо _start
мы могли бы сделать нашу точку входа main
обходя код запуска C. Поскольку мы -nostartfiles
код запуска C, мы также можем обойтись без связывания в коде запуска среды выполнения C с -nostartfiles
.
Вы можете использовать эту командную строку для компиляции вашей C-программы:
gcc test.c -e main -nostartfiles
К сожалению, в коде C есть кое-что, что нужно исправить. Обычно при использовании объектов запуска среды выполнения C после инициализации среды выполняется вызов CALL для main
. Обычно main
выполняет команду RET, которая возвращает код выполнения C. В этот момент среда выполнения C грациозно выходит из вашей программы. RET -nostartfiles
возвращаться при -nostartfiles
опции -nostartfiles
, так что, скорее всего, это будет ошибка. Чтобы обойти это, мы можем вызвать функцию библиотеки 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. Это обсуждается в этом fooobar.com/info/4610/.... Динамическая версия работает, потому что общий объект может зарегистрировать функцию, которая вызывается динамическим загрузчиком для выполнения инициализации. При статическом построении это обычно выполняется средой выполнения 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 для работы с библиотекой MUSL C вместо библиотеки GLIBC по умолчанию в большинстве дистрибутивов Linux. Параметры аналогичны GCC, поэтому вы должны быть в состоянии сделать:
/usr/local/musl/x86-64/bin/musl-gcc -e main -static -nostartfiles test.c
При запуске ./a.out
сгенерированного с помощью GLIBC, он, скорее всего, будет ./a.out
MUSL не требует инициализации перед использованием большинства функций библиотеки C, поэтому он должен работать даже с -static
GCC.
Более справедливое сравнение
Одна из проблем вашего сравнения заключается в том, что вы вызываете системный вызов SYS_WRITE непосредственно в NASM, в C вы используете printf
. Пользователь EOF правильно прокомментировал, что вы можете сделать это более справедливым сравнением, вызвав функцию write
в C вместо 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
.
Я собираюсь выпустить предупреждение о том, что такой код и хитрость, скорее всего, не будут приняты во внимание при рассмотрении кода, за исключением некоторых незначительных случаев разработки программного обеспечения.